# Chore Scheduler E2E Tests ## Application Overview The chore scheduler allows parents to configure when chores appear for their children and set deadline times. Schedules operate in two modes: **Specific Days** (select weekdays + per-day deadlines with optional exceptions) and **Every X Days** (interval-based with anchor date). Schedules can be **paused** (enabled/disabled toggle) — a paused schedule makes the chore appear every day with no deadline, as if unscheduled. All schedule logic runs client-side; the backend only stores and returns raw schedule data. **Key behavioral difference between parent and child mode:** - **Parent mode** (`/parent/:id`): All assigned chores are always visible regardless of schedule. Off-day and expired chores are grayed out (`.chore-inactive`). A kebab menu provides Schedule, Edit Points, Extend Time (when expired), and Reset (when completed) options. - **Child mode** (`/child/:id`): Chores not scheduled for today are **hidden entirely** (filtered out client-side). Expired chores show a `TOO LATE` badge and are grayed out. No kebab menu or scheduling controls exist in child mode. **Specs covered:** - `feat-calendar-chore.md` — base schedule feature - `feat-calendar-schedule-refactor-01.md` — Specific Days UI refactor (day chips, default deadline, exceptions, TimePickerPopover) - `feat-calendar-schedule-refactor-02.md` — Every X Days UI refactor (stepper, DateInputField, anchor date, next occurrence preview, Anytime toggle) - `feat-schedule-enable-disable.md` — enable/disable (pause) toggle --- ## Implementation Tests live in `e2e/mode_parent/chore-scheduler/`. Each spec creates its own isolated child and chore tasks via the API so files can run in parallel. All created resources are deleted in `afterAll`. ## Playwright Projects | Project | testMatch | Notes | | -------------------------- | -------------------------------------------- | ----------------------- | | `chromium-chore-scheduler` | `/mode_parent/chore-scheduler/.+\.spec\.ts/` | Depends on `setup` only | **Config:** Add project to `playwright.config.ts` with `dependencies: ['setup']`. Exclude from the catch-all project via `testIgnore`. --- ## Seed Strategy Each spec file creates an isolated child and one or more chore tasks via `beforeAll` API calls. Schedule configuration is done via the `PUT /api/child/:childId/task/:taskId/schedule` API. Cleanup (`delete child + tasks`) runs in `afterAll`. For tests that need to verify child-mode behavior, the test navigates to `/child/:id` (no parent auth required for child view). For parent-mode behavior, navigate to `/parent/:id`. **Time-sensitive tests:** Tests that depend on "Due by" labels or "TOO LATE" badges must set a schedule deadline far in the future (e.g., 23:59) to avoid the chore expiring mid-test, or set a deadline in the past (e.g., 00:01) to guarantee expiry. Use `page.clock` or carefully chosen deadline times to control time-dependent state. --- ## Test Scenarios ### 1. Schedule Modal — Opening & Structure **File:** `e2e/mode_parent/chore-scheduler/schedule-modal.spec.ts` **Seed:** Creates child `ScheduleModalChild` and chore `ScheduleModalChore` via API. Assigns chore to child. Deletes all in `afterAll`. #### 1.1. Schedule option appears in kebab menu - Navigate to `/parent/:childId` - Click the chore card, then click the kebab "Options" button - expect: "Schedule" button is visible in the menu #### 1.2. Schedule modal opens with correct title and subtitle - Open kebab menu → click "Schedule" - expect: Modal heading shows "Schedule Chore" - expect: Modal subtitle shows the chore's name #### 1.3. Default state for new schedule — days mode selected, no days checked - Open the Schedule modal for a chore with no existing schedule - expect: "Specific Days" mode button is active - expect: No day chips are in the active state - expect: Enable/disable toggle shows "Enabled" label and is ON #### 1.4. Mode toggle switches between Specific Days and Every X Days - Open the Schedule modal - Click "Every X Days" button - expect: Interval form is visible (stepper, "Starting on" date field, deadline row) - Click "Specific Days" button - expect: Day chips row is visible #### 1.5. Cancel closes the modal without saving - Open the Schedule modal, select Monday, click Cancel - Reopen the modal - expect: No days are selected (change was not persisted) #### 1.6. Save is disabled when form is not dirty - Open the Schedule modal (no existing schedule) - expect: Save button is disabled --- ### 2. Schedule Modal — Specific Days Mode **File:** `e2e/mode_parent/chore-scheduler/schedule-days-mode.spec.ts` **Seed:** Creates child `ScheduleDaysChild` and chore `ScheduleDaysChore` via API. Assigns chore. Deletes all in `afterAll`. #### 2.1. Clicking day chips toggles them active/inactive - Open Schedule modal - Click "Mo" chip - expect: "Mo" chip has active styling - Click "Mo" chip again - expect: "Mo" chip is no longer active #### 2.2. Default deadline row appears when at least one day is selected - Open Schedule modal, no days selected - expect: No deadline row visible - Click "Tu" chip - expect: Deadline row with "Deadline" label and a time picker appears #### 2.3. Default deadline "Anytime" toggle hides the time picker - Select a day so the deadline row appears - Click "Clear (Anytime)" link - expect: Time picker disappears, "Anytime" label is shown - Click "Set deadline" link - expect: Time picker reappears #### 2.4. Exception time — "Set different time" creates per-day override - Select Monday and Wednesday - In the day list, click "Set different time" on Wednesday - expect: A separate time picker appears for Wednesday - expect: Monday still shows the default time label #### 2.5. Exception time — "Reset to default" removes the override - With Wednesday exception set, click "Reset to default" on Wednesday - expect: Wednesday row shows the default time label again #### 2.6. Save with specific days — schedule persists on reopen - Select Monday and Friday, set default deadline to 03:00 PM, Save - Reopen the Schedule modal - expect: Monday and Friday chips are active - expect: Default deadline shows 03:00 PM #### 2.7. Save with zero days selected deletes the schedule - Open a schedule that has days selected, uncheck all days, Save - Reopen the modal - expect: No days selected (schedule was removed), form appears as new #### 2.8. Dirty detection — changing days enables Save - Open modal with an existing schedule - Toggle a different day - expect: Save becomes enabled --- ### 3. Schedule Modal — Every X Days Mode **File:** `e2e/mode_parent/chore-scheduler/schedule-interval-mode.spec.ts` **Seed:** Creates child `ScheduleIntervalChild` and chore `ScheduleIntervalChore` via API. Assigns chore. Deletes all in `afterAll`. #### 3.1. Interval stepper increments and decrements within 1–7 - Switch to "Every X Days" mode - expect: Stepper shows "1" by default - expect: Decrement (−) button is disabled at 1 - Click increment (+) 6 times - expect: Value shows "7" - expect: Increment (+) button is disabled at 7 #### 3.2. Anchor date field shows today's date by default - Switch to "Every X Days" mode - expect: "Starting on" date field contains today's ISO date #### 3.3. Next occurrence preview label updates correctly - Set interval to 2, set anchor date to today - expect: "Next occurrence" label shows a date 2 days from today #### 3.4. Interval deadline "Anytime" toggle works - Switch to interval mode; deadline time picker is visible by default - Click "Clear (Anytime)" - expect: Time picker disappears, "Anytime" label shown - Click "Set deadline" - expect: Time picker reappears #### 3.5. Save interval schedule — persists on reopen - Set interval to 3, pick a specific anchor date, set deadline to 04:30 PM, Save - Reopen Schedule modal - expect: "Every X Days" mode is active - expect: Interval shows 3, anchor date matches, deadline shows 04:30 PM #### 3.6. Dirty detection — changing interval enables Save - Open modal with an existing interval schedule - Change interval from 3 to 5 - expect: Save becomes enabled --- ### 4. Schedule Enable/Disable Toggle **File:** `e2e/mode_parent/chore-scheduler/schedule-enable-disable.spec.ts` **Seed:** Creates child `ScheduleToggleChild` and chore `ScheduleToggleChore` via API. Assigns chore. Creates a "Specific Days" schedule for a non-today weekday via API. Deletes all in `afterAll`. #### 4.1. Toggle row is visible in the schedule modal - Open Schedule modal - expect: Toggle row with "Enabled" label and a switch is visible above the mode buttons #### 4.2. Toggling OFF shows "Paused" label and dims the form body - Click the toggle switch to OFF - expect: Label changes to "Paused" - expect: Mode toggle and form fields have reduced opacity (dimmed) - expect: Cancel and Save buttons remain fully interactive #### 4.3. Toggling ON shows "Enabled" label and restores the form body - Toggle OFF, then toggle back ON - expect: Label returns to "Enabled" - expect: Form body opacity is restored #### 4.4. Save paused state — persists on reopen - Toggle OFF, Save - Reopen Schedule modal - expect: Toggle is OFF, label shows "Paused", form body is dimmed #### 4.5. Dirty detection — toggling enabled state enables Save - Open an existing schedule (enabled), toggle OFF - expect: Save becomes enabled - Toggle back ON - expect: Save is disabled again (reverted to original) #### 4.6. Paused schedule in parent mode — chore is visible (not grayed out) - Set up a schedule for a non-today weekday, then pause it via API - Navigate to `/parent/:childId` - expect: Chore card is visible and NOT grayed out (paused = show always like unscheduled) #### 4.7. Paused schedule in child mode — chore is visible (not hidden) - Same paused schedule as 4.6 - Navigate to `/child/:childId` - expect: Chore card IS visible (paused = scheduled for every day) #### 4.8. Paused schedule shows no "Due by" label - Pause a schedule that has a deadline set - Navigate to `/parent/:childId` - expect: No "Due by" sub-text on the chore card (paused = no deadline) --- ### 5. Parent Mode — Chore Card Schedule States **File:** `e2e/mode_parent/chore-scheduler/parent-card-states.spec.ts` **Seed:** Creates child `ParentCardChild` with multiple chores via API. Assigns chores. Creates various schedules via API. Deletes all in `afterAll`. **Chores:** - `AlwaysActiveChore` — no schedule (always visible, normal appearance) - `ScheduledTodayChore` — schedule includes today's weekday, deadline at 23:59 - `NotScheduledTodayChore` — schedule for a weekday that is NOT today - `ExpiredChore` — schedule for today's weekday, deadline at 00:01 (guaranteed past) - `AnytimeChore` — schedule for today, deadline set to "Anytime" (no expiry) #### 5.1. Chore with no schedule — normal appearance, no annotations - Navigate to `/parent/:childId` - expect: `AlwaysActiveChore` card is visible with normal styling (no grayout, no badge) #### 5.2. Chore scheduled for today — shows "Due by" label - expect: `ScheduledTodayChore` card is visible - expect: Card shows "Due by 11:59 PM" sub-text - expect: Card is NOT grayed out #### 5.3. Chore not scheduled for today — grayed out in parent mode - expect: `NotScheduledTodayChore` card IS visible (parent mode shows all chores) - expect: Card has grayed-out styling (`.chore-inactive` class — reduced opacity) #### 5.4. Chore with expired deadline — grayed out with TOO LATE badge - expect: `ExpiredChore` card is visible - expect: Card has grayed-out styling - expect: "TOO LATE" badge is visible on the card #### 5.5. Chore with "Anytime" deadline — no "Due by" label, no TOO LATE - expect: `AnytimeChore` card is visible with normal styling - expect: No "Due by" sub-text - expect: No "TOO LATE" badge #### 5.6. Kebab menu always accessible on grayed-out chores - Click the `NotScheduledTodayChore` card (grayed out) - expect: Kebab "Options" button is visible - Click "Options" → "Schedule" is available - expect: Schedule modal opens normally #### 5.7. Chore can still be triggered by parent regardless of schedule state - Click the `ExpiredChore` (which has TOO LATE badge) - Click again to activate - expect: Confirmation dialog appears (parent can still trigger expired chores) --- ### 6. Child Mode — Chore Visibility and Schedule States **File:** `e2e/mode_parent/chore-scheduler/child-card-states.spec.ts` **Seed:** Creates child `ChildCardChild` with multiple chores. Assigns chores. Creates schedules via API. Deletes all in `afterAll`. Uses same chore set as test 5 where applicable. **Chores:** - `ChildAlwaysChore` — no schedule - `ChildTodayChore` — scheduled for today, deadline at 23:59 - `ChildNotTodayChore` — scheduled for a non-today weekday - `ChildExpiredChore` — scheduled for today, deadline at 00:01 (past) - `ChildAnytimeChore` — scheduled for today, deadline "Anytime" #### 6.1. Chore with no schedule — visible normally in child mode - Navigate to `/child/:childId` - expect: `ChildAlwaysChore` card is visible with normal styling #### 6.2. Chore scheduled for today — visible with "Due by" label - expect: `ChildTodayChore` card is visible - expect: "Due by 11:59 PM" sub-text shown on card - expect: Card is NOT grayed out #### 6.3. Chore not scheduled for today — HIDDEN in child mode - expect: `ChildNotTodayChore` card is NOT visible (fully hidden, not just grayed out) #### 6.4. Chore with expired deadline — grayed out with TOO LATE badge - expect: `ChildExpiredChore` card IS visible (still shown, but expired) - expect: Card has grayed-out styling - expect: "TOO LATE" badge visible #### 6.5. Chore with "Anytime" — visible, no badge, no "Due by" - expect: `ChildAnytimeChore` card visible with normal styling - expect: No "Due by" label, no "TOO LATE" badge #### 6.6. No kebab menu or schedule controls in child mode - Click on `ChildTodayChore` card - expect: No "Options" button or kebab menu appears on the card --- ### 7. Parent vs Child Mode Comparison **File:** `e2e/mode_parent/chore-scheduler/parent-vs-child.spec.ts` **Seed:** Creates child `CompareChild` and two chores: `VisibleBothChore` (scheduled for today) and `ParentOnlyChore` (scheduled for a non-today weekday). Assigns both. Deletes all in `afterAll`. #### 7.1. Parent mode shows all chores including off-day ones - Navigate to `/parent/:childId` - expect: Both `VisibleBothChore` and `ParentOnlyChore` are visible - expect: `ParentOnlyChore` is grayed out (`chore-inactive`) - expect: `VisibleBothChore` is NOT grayed out #### 7.2. Child mode hides off-day chores - Navigate to `/child/:childId` - expect: `VisibleBothChore` IS visible - expect: `ParentOnlyChore` is NOT visible (hidden entirely) #### 7.3. Adding a schedule via parent mode hides chore in child mode on wrong day - Navigate to `/parent/:childId` - Open Schedule for `VisibleBothChore`, set it to a non-today weekday only, Save - Navigate to `/child/:childId` - expect: `VisibleBothChore` is no longer visible in child mode #### 7.4. Removing a schedule via parent mode shows chore in child mode again - Navigate to `/parent/:childId` - Open Schedule for the chore, uncheck all days (removes schedule), Save - Navigate to `/child/:childId` - expect: Chore is visible again in child mode (no schedule = always shown) #### 7.5. Paused schedule — chore visible in both modes - Set a non-today schedule and pause it via API - Navigate to `/parent/:childId` - expect: Chore visible, NOT grayed out (paused = acts unscheduled) - Navigate to `/child/:childId` - expect: Chore IS visible (paused = shows every day) --- ### 8. Extend Time **File:** `e2e/mode_parent/chore-scheduler/extend-time.spec.ts` **Seed:** Creates child `ExtendTimeChild` and chore `ExtendTimeChore` with a schedule for today and a deadline at 00:01 (guaranteed expired). Assigns chore. Deletes all in `afterAll`. #### 8.1. "Extend Time" appears in kebab menu only when chore is expired - Navigate to `/parent/:childId` - expect: `ExtendTimeChore` shows "TOO LATE" badge (deadline is 00:01, already past) - Click chore card, then "Options" - expect: "Extend Time" menu item is visible #### 8.2. "Extend Time" is absent when chore is not expired - Set up a second chore with deadline at 23:59 (not yet passed) - Click chore card, then "Options" - expect: "Extend Time" menu item is NOT visible #### 8.3. Clicking "Extend Time" removes the TOO LATE badge - Click "Extend Time" on the expired chore - expect: "TOO LATE" badge disappears from the card - expect: Card is no longer grayed out #### 8.4. Extended chore no longer shows "Due by" label - After extending, the chore card - expect: No "Due by" sub-text (extended = no deadline for remainder of day) #### 8.5. Extending the same chore twice returns 409 - After the first extension, try opening kebab again - expect: "Extend Time" is no longer in the menu (chore is no longer expired) #### 8.6. Extension effect is visible in child mode - After extending the chore in parent mode - Navigate to `/child/:childId` - expect: Chore is visible and NOT showing "TOO LATE" badge --- ### 9. Schedule with Interval Mode — Anchor Date & Next Occurrence **File:** `e2e/mode_parent/chore-scheduler/interval-anchor.spec.ts` **Seed:** Creates child `IntervalAnchorChild` and chore `IntervalAnchorChore`. Assigns chore. Deletes all in `afterAll`. #### 9.1. Interval schedule set to every 1 day from today — chore active today - Create interval schedule: `interval_days=1`, `anchor_date=today`, deadline 23:59 - Navigate to `/parent/:childId` - expect: Chore is NOT grayed out (hits today) - Navigate to `/child/:childId` - expect: Chore IS visible #### 9.2. Interval schedule set to every 2 days from yesterday — chore NOT active today - Create interval schedule: `interval_days=2`, `anchor_date=yesterday` - Navigate to `/parent/:childId` - expect: Chore IS grayed out (yesterday + 2 = tomorrow, not today) - Navigate to `/child/:childId` - expect: Chore is NOT visible #### 9.3. Interval schedule set to every 2 days starting today — active today - Create interval schedule: `interval_days=2`, `anchor_date=today` - Navigate to `/parent/:childId` - expect: Chore is NOT grayed out - Navigate to `/child/:childId` - expect: Chore IS visible #### 9.4. Interval "Anytime" deadline — no "Due by" label - Create interval schedule with `interval_has_deadline=false` - Navigate to `/parent/:childId` - expect: No "Due by" sub-text on the chore card --- ### 10. SSE Real-Time Updates **File:** `e2e/mode_parent/chore-scheduler/schedule-sse.spec.ts` **Seed:** Creates child `SSEScheduleChild` and chore `SSEScheduleChore`. Assigns chore. Deletes all in `afterAll`. #### 10.1. Setting a schedule via API reflects in an already-open parent view - Navigate to `/parent/:childId` - expect: Chore is visible, no schedule annotations - Via API call (`request.put`), create a schedule for a non-today weekday - expect: Within a few seconds, the chore card becomes grayed out (SSE `chore_schedule_modified` fires, list refreshes) #### 10.2. Deleting a schedule via API un-grays the chore in parent view - (Continuing from 10.1) Delete the schedule via API - expect: Within a few seconds, the chore card returns to normal styling #### 10.3. Extending time via API reflects in an already-open parent view - Set up a schedule with a past deadline (00:01) via API so chore is expired - Navigate to `/parent/:childId` - expect: "TOO LATE" badge visible - Extend time via API call - expect: "TOO LATE" badge disappears (SSE `chore_time_extended` fires, list refreshes) --- ### 11. Schedule Loading — Backward Compatibility **File:** `e2e/mode_parent/chore-scheduler/schedule-load.spec.ts` **Seed:** Creates child and chore. Uses direct API calls to create schedules with specific field combinations. Deletes all in `afterAll`. #### 11.1. Schedule with `enabled` field missing defaults to enabled - Create schedule via API without `enabled` field - Open Schedule modal - expect: Toggle shows "Enabled" (defaults to true) #### 11.2. Schedule with `interval_has_deadline=false` shows "Anytime" - Create interval schedule with `interval_has_deadline=false` - Open Schedule modal - expect: "Anytime" label is shown instead of time picker #### 11.3. Existing days schedule with mixed times loads correctly - Create days schedule with 3 days: Mon (08:00), Wed (08:00), Fri (10:30) — Fri is an exception - Open Schedule modal - expect: Mo, We, Fr chips are active - expect: Default deadline shows 08:00 AM - expect: Friday row shows its own time picker with 10:30 AM #### 11.4. Interval schedule loads anchor date and interval correctly - Create interval schedule via API with `anchor_date="2026-03-15"`, `interval_days=3` - Open Schedule modal - expect: "Every X Days" mode is active - expect: Stepper shows 3 - expect: Date field shows 2026-03-15 --- ## CSS Spec References - `.chore-inactive`: `opacity: 0.45; filter: grayscale(60%)` — applied in both parent and child views - `.chore-stamp`: absolute-positioned badge overlay for "TOO LATE", "COMPLETED", "PENDING" - `.due-label`: "Due by X:XX PM" sub-text below point value - `.schedule-body.disabled`: `opacity: 0.45; pointer-events: none` — dim wrapper when schedule is paused - `.toggle-track.on`: `background: var(--btn-primary)` — toggle ON state