fix(03): WR-01/WR-02/IN-02 recurrence-edit helper text, all-day toggle clamp, edit-mode parse-failure guard

This commit is contained in:
Lucas Berger
2026-06-09 11:02:09 -04:00
parent 5168920eb1
commit eed178fb39
2 changed files with 135 additions and 13 deletions
@@ -262,6 +262,36 @@ describe('EventForm', () => {
expect(timeInputs.length).toBeGreaterThan(0)
})
// WR-02 (iteration 2): toggling all-day ON deterministically clamps endDate to
// max(startDate, endDate). When the end day is BEHIND the start day, it snaps forward
// to a single-day event rather than validating as an inconsistent span, and any stale
// end-time error from the timed view is cleared.
it('toggling All-day ON clamps an end date that is behind the start date up to the start date', async () => {
renderForm()
fireEvent.change(screen.getByPlaceholderText('Event title'), {
target: { value: 'Span Title' },
})
const dateInputs = document.querySelectorAll('input[type="date"]')
expect(dateInputs.length).toBeGreaterThanOrEqual(2)
// Start 2026-06-10, end 2026-06-09 (end behind start) — invalid timed span
fireEvent.change(dateInputs[0], { target: { value: '2026-06-10' } })
fireEvent.change(dateInputs[1], { target: { value: '2026-06-09' } })
// Toggle all-day ON: endDate must clamp up to the start date (single-day event)
const allDaySwitch = screen.getByRole('switch')
fireEvent.click(allDaySwitch)
const dateInputsAfter = document.querySelectorAll('input[type="date"]')
expect((dateInputsAfter[1] as HTMLInputElement).value).toBe('2026-06-10')
// The clamped all-day event validates cleanly (no end-time error surfaced)
const saveButton = screen.getByText('Create Event')
fireEvent.click(saveButton)
await waitFor(() => {
expect(screen.queryByText('End time must be after start')).toBeNull()
})
})
// ── Calendar picker D-02 ───────────────────────────────────────────────────
it('calendar picker is absent when fetchWritableCalendars returns 1 calendar (D-02)', () => {
@@ -368,6 +398,39 @@ describe('EventForm', () => {
})
})
// WR-01 (iteration 2): edit mode shows explanatory helper text near the disabled
// recurrence select so the locked schedule is not a silent surprise.
it('WR-01: edit mode surfaces helper text that repeat cannot be changed', () => {
renderForm({ mode: 'edit', uid: 'edit-uid-456', eventOccurrence: EDIT_OCCURRENCE })
expect(screen.getByText(/Repeat can't be changed yet/i)).toBeDefined()
})
it('WR-01: create mode does NOT show the repeat helper text', () => {
renderForm({ mode: 'create' })
expect(screen.queryByText(/Repeat can't be changed yet/i)).toBeNull()
})
// IN-02 (iteration 2): in edit mode an unparseable cached start/end must leave the
// field blank and block submit, rather than silently rewriting the event to today/09:00.
it('IN-02: edit mode with an unparseable start leaves the date blank and blocks submit', async () => {
const corruptOccurrence: CalendarOccurrence = {
...EDIT_OCCURRENCE,
start: 'not-a-real-date',
}
renderForm({ mode: 'edit', uid: 'edit-uid-456', eventOccurrence: corruptOccurrence })
// The start date input must be blank (not today's date)
const dateInputs = document.querySelectorAll('input[type="date"]')
expect((dateInputs[0] as HTMLInputElement).value).toBe('')
// Submit must be blocked with a guidance message; updateEvent must NOT fire.
fireEvent.click(screen.getByText('Save Changes'))
await waitFor(() => {
expect(screen.getByText(/Couldn't read this event's date/i)).toBeDefined()
})
expect(mockUpdateEvent).not.toHaveBeenCalled()
})
// ── Close behaviors ────────────────────────────────────────────────────────
it('pressing Escape closes the form', () => {