feat: add enable/disable toggle for chore scheduling in ScheduleModal
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m39s

- Introduced a toggle button to enable or disable chores in the scheduler.
- The toggle will be available in both types of schedulers.
- Added UI design proposal and considerations for mobile dimensions.
- Included E2E tests to ensure toggle state persistence and correct behavior in child view.
This commit is contained in:
2026-03-20 16:42:13 -04:00
parent db6e0a7ce8
commit ef9cb01d92
45 changed files with 3543 additions and 232 deletions

View File

@@ -0,0 +1,558 @@
# 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