Files
chore/frontend/e2e/plans/chore-scheduler.plan.md
Ryan Kegel 127378797c
Some checks failed
Chore App Build, Test, and Push Docker Images / build-and-push (push) Failing after 2m46s
Refactored frontend directory
2026-04-25 00:40:15 -04:00

559 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 17
- 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