# 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
