Timeline: curator event create/edit forms (#781) #832
@@ -35,6 +35,7 @@ let {
|
||||
suggestedDateIso = '',
|
||||
dateLabel = m.form_label_date(),
|
||||
dateRequired = true,
|
||||
dateError = '',
|
||||
dateTestId = undefined,
|
||||
precisionTestId = undefined,
|
||||
endDateInnerTestId = undefined
|
||||
@@ -49,6 +50,8 @@ let {
|
||||
suggestedDateIso?: string;
|
||||
dateLabel?: string;
|
||||
dateRequired?: boolean;
|
||||
/** Server-side date error (e.g. blank required field) wired to the field's aria-invalid. */
|
||||
dateError?: string;
|
||||
dateTestId?: string;
|
||||
precisionTestId?: string;
|
||||
endDateInnerTestId?: string;
|
||||
@@ -83,6 +86,9 @@ onMount(() => {
|
||||
});
|
||||
|
||||
const dateInvalid = $derived(dateDirty && dateDisplay.length > 0 && dateIso === '');
|
||||
// Either the client-side malformed-date cue or a server-provided required-field
|
||||
// error marks the field invalid (REQ-011 per-field aria-invalid).
|
||||
const dateFieldInvalid = $derived(dateInvalid || dateError.length > 0);
|
||||
|
||||
// Inline mirror of the server guard (#678). ISO YYYY-MM-DD strings compare
|
||||
// lexicographically, so no Date object is needed. Server stays the gate —
|
||||
@@ -128,17 +134,21 @@ $effect(() => {
|
||||
maxlength="10"
|
||||
aria-required={dateRequired ? 'true' : undefined}
|
||||
class="block min-h-[48px] w-full rounded border border-line px-2 py-3 text-sm shadow-sm
|
||||
{dateInvalid
|
||||
{dateFieldInvalid
|
||||
? 'border-red-400 focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500'
|
||||
: 'focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring'}"
|
||||
aria-invalid={dateInvalid ? 'true' : undefined}
|
||||
aria-describedby={dateInvalid ? `${dateInputName}-error` : undefined}
|
||||
aria-invalid={dateFieldInvalid ? 'true' : undefined}
|
||||
aria-describedby={dateFieldInvalid ? `${dateInputName}-error` : undefined}
|
||||
/>
|
||||
<input type="hidden" name={dateInputName} value={dateIso} />
|
||||
{#if dateInvalid}
|
||||
<p id="{dateInputName}-error" class="mt-1 text-xs text-red-600">
|
||||
<p id="{dateInputName}-error" class="mt-1 text-xs text-danger">
|
||||
<span aria-hidden="true">⚠ </span>{m.form_date_error()}
|
||||
</p>
|
||||
{:else if dateError}
|
||||
<p id="{dateInputName}-error" class="mt-1 text-xs text-danger">
|
||||
<span aria-hidden="true">⚠ </span>{dateError}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -183,7 +193,7 @@ $effect(() => {
|
||||
/>
|
||||
{#if endBeforeStart}
|
||||
<!-- Non-colour cue (WCAG 1.4.1): warning glyph + text, not red alone. -->
|
||||
<p id="{dateInputName}-end-error" class="mt-1 text-xs text-red-600">
|
||||
<p id="{dateInputName}-end-error" class="mt-1 text-xs text-danger">
|
||||
<span aria-hidden="true">⚠ </span>{m.error_invalid_date_range()}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
@@ -221,16 +221,12 @@ async function confirmDelete(e: SubmitEvent) {
|
||||
endDateInputName="eventDateEnd"
|
||||
precisionInputName="precision"
|
||||
dateLabel={m.form_label_date()}
|
||||
dateError={dateError}
|
||||
dateTestId="event-date"
|
||||
precisionTestId="event-precision"
|
||||
endDateInnerTestId="event-end-date"
|
||||
/>
|
||||
</div>
|
||||
{#if dateError}
|
||||
<p class="mt-2 text-sm text-danger" role="alert">
|
||||
<span aria-hidden="true">⚠ </span>{dateError}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Beschreibung -->
|
||||
|
||||
@@ -73,6 +73,15 @@ describe('EventForm — required-field error (REQ-010)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('EventForm — server date error wired per-field (REQ-011)', () => {
|
||||
it('marks the date field aria-invalid and shows the message on a server date error', async () => {
|
||||
render(EventForm, { form: { dateError: 'Bitte ein Datum eingeben.' } });
|
||||
await expect.element(page.getByText('Bitte ein Datum eingeben.')).toBeInTheDocument();
|
||||
const dateInput = document.querySelector('#eventDate') as HTMLInputElement;
|
||||
expect(dateInput.getAttribute('aria-invalid')).toBe('true');
|
||||
});
|
||||
});
|
||||
|
||||
describe('EventForm — submitting state (named AC)', () => {
|
||||
it('renders an enabled submit button initially', async () => {
|
||||
render(EventForm, { event: makeEvent() });
|
||||
|
||||
Reference in New Issue
Block a user