Refactored frontend directory
Some checks failed
Chore App Build, Test, and Push Docker Images / build-and-push (push) Failing after 2m46s
Some checks failed
Chore App Build, Test, and Push Docker Images / build-and-push (push) Failing after 2m46s
This commit is contained in:
281
frontend/e2e/plans/bugs-1.0.6-set01.plan.md
Normal file
281
frontend/e2e/plans/bugs-1.0.6-set01.plan.md
Normal file
@@ -0,0 +1,281 @@
|
||||
# E2E Test Plan — Bug Fixes 1.0.6 Set 1
|
||||
|
||||
Covers the bug fixes from `.github/specs/bugs-1.0.6-01/bugs-1.0.6-set01.md`. Each section proposes new E2E specs or additions to existing specs.
|
||||
|
||||
---
|
||||
|
||||
## 1. Assign View Shows Child Name
|
||||
|
||||
**File:** `e2e/mode_parent/task-assignment/assign-title-shows-child-name.spec.ts`
|
||||
**Project:** `chromium-task-assignment`
|
||||
|
||||
**Seed:** Create an isolated child with a known name via API. Create at least one chore, reward, kindness, and penalty task.
|
||||
|
||||
#### 1.1. "Assign Chores" heading contains child name
|
||||
|
||||
- Navigate to `/parent/{childId}/assign-chores?name={childName}`
|
||||
- Verify the heading text includes the child's name
|
||||
|
||||
#### 1.2. "Assign Rewards" heading contains child name
|
||||
|
||||
- Navigate to `/parent/{childId}/assign-rewards?name={childName}`
|
||||
- Verify heading
|
||||
|
||||
#### 1.3. "Assign Kindnesses" heading contains child name
|
||||
|
||||
- Navigate to `/parent/{childId}/assign-kindness?name={childName}`
|
||||
- Verify heading
|
||||
|
||||
#### 1.4. "Assign Penalties" heading contains child name
|
||||
|
||||
- Navigate to `/parent/{childId}/assign-penalties?name={childName}`
|
||||
- Verify heading
|
||||
|
||||
---
|
||||
|
||||
## 2. General Chore Approval — No COMPLETED Stamp
|
||||
|
||||
**File:** `e2e/mode_parent/task-activation/general-chore-no-completed.spec.ts`
|
||||
**Project:** `chromium-task-activation`
|
||||
|
||||
**Seed:** Create an isolated child, one general (unscheduled) chore, and one scheduled chore. Assign both to the child.
|
||||
|
||||
#### 2.1. Approving a general chore awards points
|
||||
|
||||
- Click general chore → confirm in dialog → approve
|
||||
- Verify child's points increase by chore value
|
||||
|
||||
#### 2.2. General chore does NOT show COMPLETED stamp after approval
|
||||
|
||||
- After approval, verify the chore card does not contain "COMPLETED" text
|
||||
|
||||
#### 2.3. General chore can be triggered again immediately after approval
|
||||
|
||||
- Click the same general chore again
|
||||
- Verify the confirmation dialog re-appears (chore is re-triggerable)
|
||||
|
||||
#### 2.4. Scheduled chore DOES show COMPLETED after approval (contrast)
|
||||
|
||||
- Trigger and approve the scheduled chore
|
||||
- Verify "COMPLETED" stamp is visible on that card
|
||||
|
||||
---
|
||||
|
||||
## 3. Extend Time Removes TOO LATE Banner
|
||||
|
||||
**Existing file:** `e2e/mode_parent/chore-scheduler/extend-time.spec.ts`
|
||||
|
||||
The existing spec already has tests for "Extend Time removes TOO LATE badge" and "gray styling removed." The bug fix ensures `refresh()` is called immediately, so the existing tests should pass without page reload. If not already asserted:
|
||||
|
||||
#### 3.1. TOO LATE disappears without page reload (addition to existing spec)
|
||||
|
||||
- After clicking "Extend Time," assert TOO LATE text is hidden within 3 seconds
|
||||
- Do NOT call `page.reload()` between extension and assertion
|
||||
|
||||
---
|
||||
|
||||
## 4. Notification Card Overflow
|
||||
|
||||
**File:** `e2e/mode_parent/notifications/notification-card-overflow.spec.ts`
|
||||
**Project:** `chromium-task-activation` or new `chromium-notifications`
|
||||
|
||||
**Seed:** Create child + a chore with a very long name (50+ characters). Assign and trigger it so a notification is created.
|
||||
|
||||
#### 4.1. Notification icon stays within card boundaries
|
||||
|
||||
- Navigate to notifications view
|
||||
- Get the bounding box of the notification card and its icon
|
||||
- Assert the icon's bounding box is fully contained within the card's bounding box
|
||||
|
||||
#### 4.2. Long notification text does not visually overflow
|
||||
|
||||
- Assert the card element has appropriate overflow CSS (`overflow: hidden` or similar)
|
||||
- Optionally use `toHaveScreenshot()` for visual regression
|
||||
|
||||
**Note:** This test benefits from visual regression testing (`expect(page).toHaveScreenshot()`) more than functional assertions, since overflow is inherently visual.
|
||||
|
||||
---
|
||||
|
||||
## 5. Override SSE Updates Child View
|
||||
|
||||
**File:** `e2e/mode_parent/task-modification/override-sse-child-update.spec.ts`
|
||||
**Project:** `chromium-task-modification`
|
||||
|
||||
**Seed:** Create child + one chore, one kindness, one penalty. Assign all to the child.
|
||||
|
||||
#### 5.1. Changing chore override points updates child view in real time
|
||||
|
||||
- Open child view in the browser (`goToChildView`)
|
||||
- Via API, set a point override on the chore: `PUT /api/child/{id}/task/{taskId}/override`
|
||||
- Without page reload, verify the chore card displays the new point value
|
||||
|
||||
#### 5.2. Changing kindness override points updates child view
|
||||
|
||||
- Same pattern for kindness task — set override via API, assert child view updates
|
||||
|
||||
#### 5.3. Changing penalty override points updates child view
|
||||
|
||||
- Same pattern for penalty task
|
||||
|
||||
#### 5.4. Deleting an override reverts child view to original points
|
||||
|
||||
- Remove the override via API
|
||||
- Verify child view reverts to original point value without reload
|
||||
|
||||
---
|
||||
|
||||
## 6. Scroll to Edited Task After Save
|
||||
|
||||
**File:** `e2e/mode_parent/task-modification/scroll-to-edited-task.spec.ts`
|
||||
**Project:** `chromium-task-modification`
|
||||
|
||||
**Seed:** Create child + 10–15 chores and 5+ rewards (enough to require both vertical and horizontal scrolling). Assign all to the child.
|
||||
|
||||
#### 6.1. Edited chore card scrolls into viewport after override save
|
||||
|
||||
- Navigate to parent child view
|
||||
- Open override modal on a chore that requires scrolling to reach
|
||||
- Save a new point value
|
||||
- Assert the edited card `toBeInViewport()` after the list refreshes
|
||||
|
||||
#### 6.2. Edited reward card scrolls into viewport after override save
|
||||
|
||||
- Open override modal on a distant reward
|
||||
- Save, assert card `toBeInViewport()`
|
||||
|
||||
---
|
||||
|
||||
## 7. Task Sorting — Parent Mode
|
||||
|
||||
**File:** `e2e/mode_parent/task-sorting/parent-sort-order.spec.ts`
|
||||
**Project:** New `chromium-task-sorting` or `chromium-chore-scheduler`
|
||||
|
||||
**Seed:** Create child + 5 chores in different states:
|
||||
|
||||
1. A pending chore (child triggered, not yet approved)
|
||||
2. A general (unscheduled) chore
|
||||
3. An expired scheduled chore (today, deadline in the past → TOO LATE)
|
||||
4. A completed scheduled chore (triggered + approved)
|
||||
5. A chore scheduled on a non-today day (unscheduled for today)
|
||||
|
||||
Also create 2+ rewards: one with a pending request, one without.
|
||||
|
||||
#### 7.1. Parent chore sort: pending → general → expired → completed → unscheduled
|
||||
|
||||
- Navigate to parent child view
|
||||
- Read all chore card names in DOM order
|
||||
- Assert the order matches: pending chore first, then general, then expired, then completed, then unscheduled-today
|
||||
|
||||
#### 7.2. Parent reward sort: pending requests appear first
|
||||
|
||||
- Read all reward card names in DOM order
|
||||
- Assert the pending-request reward appears before other rewards
|
||||
|
||||
---
|
||||
|
||||
## 8. Task Sorting — Child Mode
|
||||
|
||||
**File:** `e2e/mode_parent/task-sorting/child-sort-order.spec.ts`
|
||||
**Project:** Same as §7
|
||||
|
||||
**Seed:** Same child + tasks as §7, plus rewards with varying costs.
|
||||
|
||||
#### 8.1. Child chore sort: scheduled today (earliest deadline first) → general → pending
|
||||
|
||||
- Switch to child view (`goToChildView`)
|
||||
- Read all visible chore card names in DOM order
|
||||
- Assert: scheduled-today chores first (sorted by deadline), then general, then pending
|
||||
- Non-today chores should be hidden in child mode
|
||||
|
||||
#### 8.2. Child reward sort: pending first, then ascending by points needed
|
||||
|
||||
- Read all reward card names in DOM order
|
||||
- Assert: pending reward first, remaining sorted by ascending cost/points-needed
|
||||
|
||||
---
|
||||
|
||||
## 9. Notification Click Navigates and Scrolls to Task
|
||||
|
||||
**File:** `e2e/mode_parent/notifications/notification-click-scroll.spec.ts`
|
||||
**Project:** `chromium-task-activation` or new `chromium-notifications`
|
||||
|
||||
**Seed:** Create child + many chores (10+). Trigger one chore to create a pending notification. The triggered chore should be far enough in the list to require scrolling.
|
||||
|
||||
#### 9.1. Clicking a chore notification navigates to parent child view
|
||||
|
||||
- Navigate to notifications page
|
||||
- Click the pending chore notification
|
||||
- Verify URL matches `/parent/{childId}` with `scrollTo` and `entityType` query params
|
||||
|
||||
#### 9.2. Target chore card is scrolled into viewport after navigation
|
||||
|
||||
- After navigation from notification click, assert the target chore card `toBeInViewport()`
|
||||
|
||||
#### 9.3. Works for reward notifications too
|
||||
|
||||
- Request a reward → navigate to notifications → click → verify reward card is in viewport
|
||||
|
||||
---
|
||||
|
||||
## 10. Pending Status Resets on Task/Reward Modification
|
||||
|
||||
**File:** `e2e/mode_parent/task-modification/pending-reset-on-edit.spec.ts`
|
||||
**Project:** `chromium-task-modification`
|
||||
|
||||
**Seed:** Create child + one chore + one reward. Assign both. Create a chore schedule.
|
||||
|
||||
#### 10.1. Editing chore points clears pending status
|
||||
|
||||
- Trigger the chore (creates pending confirmation)
|
||||
- Verify chore card shows pending state
|
||||
- Edit the parent task's points via API: `POST /api/task/{taskId}/edit` with new points
|
||||
- Refresh or wait for SSE — verify the chore card no longer shows pending state
|
||||
|
||||
#### 10.2. Changing chore schedule clears pending status
|
||||
|
||||
- Trigger the chore again
|
||||
- Set a new schedule via API: `PUT /api/child/{childId}/task/{taskId}/schedule`
|
||||
- Verify pending is cleared
|
||||
|
||||
#### 10.3. Editing reward cost clears pending reward request
|
||||
|
||||
- Request the reward (creates pending request)
|
||||
- Edit the reward's cost via API
|
||||
- Verify the reward card no longer shows pending state
|
||||
|
||||
#### 10.4. Completed chores remain completed after point edit
|
||||
|
||||
- Complete and approve a scheduled chore so it shows COMPLETED
|
||||
- Edit the parent task's points
|
||||
- Verify the chore still shows COMPLETED stamp
|
||||
|
||||
#### 10.5. Editing task name only (no point change) does NOT clear pending
|
||||
|
||||
- Trigger the chore
|
||||
- Edit the task name only (points unchanged) via API
|
||||
- Verify the chore still shows pending state
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| # | Area | Spec File | Tests | Priority |
|
||||
| --- | -------------------------- | ------------------------------------------------------- | ----- | -------- |
|
||||
| 1 | Assign view child name | `task-assignment/assign-title-shows-child-name.spec.ts` | 4 | Medium |
|
||||
| 2 | General chore no COMPLETED | `task-activation/general-chore-no-completed.spec.ts` | 4 | **High** |
|
||||
| 3 | Extend time TOO LATE | Existing `chore-scheduler/extend-time.spec.ts` | 0–1 | Low |
|
||||
| 4 | Notification overflow | `notifications/notification-card-overflow.spec.ts` | 2 | Low |
|
||||
| 5 | Override SSE child update | `task-modification/override-sse-child-update.spec.ts` | 4 | **High** |
|
||||
| 6 | Scroll to edited task | `task-modification/scroll-to-edited-task.spec.ts` | 2 | Medium |
|
||||
| 7 | Parent sort order | `task-sorting/parent-sort-order.spec.ts` | 2 | **High** |
|
||||
| 8 | Child sort order | `task-sorting/child-sort-order.spec.ts` | 2 | **High** |
|
||||
| 9 | Notification click scroll | `notifications/notification-click-scroll.spec.ts` | 3 | Medium |
|
||||
| 10 | Pending reset on edit | `task-modification/pending-reset-on-edit.spec.ts` | 5 | **High** |
|
||||
|
||||
**Recommended implementation order:** #2, #10, #7, #8, #5, #9, #6, #1, #3, #4
|
||||
|
||||
**Notes:**
|
||||
|
||||
- Bug #1 (uppercase verification code) is omitted from this E2E plan because reaching the PIN setup step requires email delivery; this is better covered by a component-level test on `ParentPinSetup.vue`.
|
||||
- Bug #4 (extend time) is largely already covered by the existing `extend-time.spec.ts` — only a minor addition may be needed.
|
||||
- Bug #5 (notification overflow) is best validated through visual regression (`toHaveScreenshot()`) rather than functional assertions.
|
||||
96
frontend/e2e/plans/child-options.plan.md
Normal file
96
frontend/e2e/plans/child-options.plan.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# Child Kebab Menu Options
|
||||
|
||||
## Application Overview
|
||||
|
||||
Tests for the per-child kebab (⋮) menu on the Parent dashboard (`/parent`). The menu is only visible when the parent is authenticated (PIN entered). Each option card exposes an "Options" button (`aria-label="Options"`) that opens a dropdown with three actions: **Edit Child**, **Delete Points**, and **Delete Child**.
|
||||
|
||||
All tests run under the `chromium-child-options` project which completes before `chromium-create-child` starts, preventing `deleteAllChildren()` in the create-child suite from interfering with children created by these tests.
|
||||
|
||||
Each test creates its own named child via the API in `beforeEach` and cleans up in `afterEach`, so the tests are fully isolated and can run in parallel with each other.
|
||||
|
||||
## Test Scenarios
|
||||
|
||||
### 1. Edit Child ✅ IMPLEMENTED
|
||||
|
||||
**File:** `e2e/mode_parent/child-options/edit-child.spec.ts`
|
||||
|
||||
**Seed child:** `KebabEdit` (created via `PUT /api/child/add`, deleted in `afterEach`)
|
||||
|
||||
#### 1.1. Edit Child menu item navigates to the child editor ✅
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Create child `KebabEdit` via API in `beforeEach`
|
||||
|
||||
2. Navigate to `/parent`
|
||||
- expect: A card with heading `KebabEdit` is visible
|
||||
|
||||
3. Click the "Options" button on the `KebabEdit` card
|
||||
- expect: The kebab dropdown opens (`aria-expanded="true"` on the Options button)
|
||||
|
||||
4. Click "Edit Child" in the dropdown
|
||||
- expect: URL matches `/parent/<id>/edit`
|
||||
- expect: Page heading "Edit Child" is visible
|
||||
|
||||
---
|
||||
|
||||
### 2. Delete Points ✅ IMPLEMENTED
|
||||
|
||||
**File:** `e2e/mode_parent/child-options/delete-points.spec.ts`
|
||||
|
||||
**Seed child:** `KebabPoints` (created via `PUT /api/child/add`, seeded to 50 points via `PUT /api/child/:id/edit`, deleted in `afterEach`)
|
||||
|
||||
#### 2.1. Delete Points resets child points to 0 ✅
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Create child `KebabPoints` with 50 points via API in `beforeEach`
|
||||
|
||||
2. Navigate to `/parent`
|
||||
|
||||
3. Click the "Options" button on the `KebabPoints` card
|
||||
- expect: The kebab dropdown opens (`aria-expanded="true"` on the Options button)
|
||||
|
||||
4. Click "Delete Points" in the dropdown
|
||||
- expect: The card shows `Points: 0`
|
||||
|
||||
---
|
||||
|
||||
### 3. Delete Child ✅ IMPLEMENTED
|
||||
|
||||
**File:** `e2e/mode_parent/child-options/delete-child.spec.ts`
|
||||
|
||||
**Seed child:** `KebabDelete` (created per-test, cleaned up in `afterEach`)
|
||||
|
||||
**Note:** The two tests in this file share the same child name and use `test.describe.configure({ mode: 'serial' })` to guarantee they never run in parallel with each other.
|
||||
|
||||
#### 3.1. Confirm deletes the child from the list ✅
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Create child `KebabDelete` via API
|
||||
|
||||
2. Navigate to `/parent`
|
||||
- expect: Card with heading `KebabDelete` is visible
|
||||
|
||||
3. Click "Options" → "Delete Child"
|
||||
- expect: Confirmation dialog "Are you sure you want to delete this child?" appears
|
||||
|
||||
4. Click the "Delete" button in the dialog
|
||||
- expect: The `KebabDelete` card is no longer visible in the list
|
||||
|
||||
#### 3.2. Cancel does not delete the child ✅
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Create child `KebabDelete` via API
|
||||
|
||||
2. Navigate to `/parent`
|
||||
- expect: Card with heading `KebabDelete` is visible
|
||||
|
||||
3. Click "Options" → "Delete Child"
|
||||
- expect: Confirmation dialog "Are you sure you want to delete this child?" appears
|
||||
|
||||
4. Click the "Cancel" button in the dialog
|
||||
- expect: The confirmation dialog is dismissed
|
||||
- expect: The `KebabDelete` card is still visible in the list
|
||||
558
frontend/e2e/plans/chore-scheduler.plan.md
Normal file
558
frontend/e2e/plans/chore-scheduler.plan.md
Normal 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 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
|
||||
286
frontend/e2e/plans/create-child.plan.md
Normal file
286
frontend/e2e/plans/create-child.plan.md
Normal file
@@ -0,0 +1,286 @@
|
||||
# Create Child
|
||||
|
||||
## Application Overview
|
||||
|
||||
Tests for creating a new child from the Parent dashboard. The user is authenticated and in parent mode via stored auth state (tests/.auth/user.json). All tests start at /parent/children (Children List view).
|
||||
|
||||
## Test Scenarios
|
||||
|
||||
### 1. Happy Path ✅ IMPLEMENTED
|
||||
|
||||
**Seed:** `frontend/vue-app/seed.ts`
|
||||
|
||||
#### 1.1. Create a child with name and age only ✅
|
||||
|
||||
**File:** `tests/create-child/happy-path.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to /parent (router auto-redirects from /)
|
||||
|
||||
|
||||
- expect: The Children List page is displayed
|
||||
|
||||
2. Click the 'Add Child' floating action button (FAB) in the bottom-right corner
|
||||
|
||||
|
||||
- expect: The Create Child form is displayed with Name, Age, and Image fields
|
||||
|
||||
3. Enter 'Alice' in the Name field
|
||||
|
||||
|
||||
- expect: The Name field shows 'Alice'
|
||||
- expect: The Create button remains disabled until all required fields are valid
|
||||
|
||||
4. Enter '8' in the Age field
|
||||
|
||||
|
||||
- expect: The Age field shows '8'
|
||||
- expect: The Create button becomes enabled
|
||||
|
||||
5. Leave the Image field as the default pre-selected value and click the Create button
|
||||
|
||||
|
||||
- expect: No error messages are displayed
|
||||
- expect: Navigation returns to /parent
|
||||
- expect: The child 'Alice' appears in the children list
|
||||
|
||||
#### 1.2. Create a child via the inline 'Create' button in empty state ✅
|
||||
|
||||
**File:** `tests/create-child/happy-path.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to /parent with no children in the account
|
||||
|
||||
|
||||
- expect: An empty state message "No children" is shown with an inline 'Create' button
|
||||
|
||||
2. Click the inline 'Create' button (with class "round-btn")
|
||||
|
||||
|
||||
- expect: The Create Child form is displayed
|
||||
|
||||
3. Enter 'Bob' in the Name field and '10' in the Age field, then click Create
|
||||
|
||||
|
||||
- expect: Create button is initially disabled, becomes enabled after both fields are filled
|
||||
- expect: No errors are displayed
|
||||
- expect: Navigation returns to /parent
|
||||
- expect: 'Bob' appears in the children list
|
||||
|
||||
#### 1.3. Create a child with a custom uploaded image ✅
|
||||
|
||||
**File:** `tests/create-child/happy-path.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to /parent and click the 'Add Child' FAB
|
||||
|
||||
|
||||
- expect: The Create Child form is displayed
|
||||
|
||||
2. Enter 'Grace' in the Name field and '6' in the Age field
|
||||
|
||||
|
||||
- expect: Name and Age fields are populated
|
||||
|
||||
3. Click 'Add from device' button and choose a valid PNG or JPEG file
|
||||
|
||||
|
||||
- expect: The image file is selected and uploaded
|
||||
|
||||
4. Click the Create button
|
||||
|
||||
|
||||
- expect: Create button becomes enabled after async form initialization
|
||||
- expect: No error messages are displayed
|
||||
- expect: Navigation returns to /parent
|
||||
- expect: 'Grace' appears in the children list with the uploaded image
|
||||
|
||||
### 2. Validation - Required Fields ✅ IMPLEMENTED
|
||||
|
||||
**Seed:** `frontend/vue-app/seed.ts`
|
||||
|
||||
**Note:** Form validation is client-side. Invalid inputs disable the submit button rather than showing error messages.
|
||||
|
||||
#### 2.1. Reject submission when Name is empty ✅
|
||||
|
||||
**File:** `tests/create-child/validation.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to /parent and click the 'Add Child' FAB
|
||||
|
||||
|
||||
- expect: The Create Child form is displayed at /parent/children/create
|
||||
|
||||
2. Leave the Name field empty, enter '7' in the Age field
|
||||
|
||||
|
||||
- expect: The Create button remains disabled
|
||||
- expect: Form stays on /parent/children/create (no navigation)
|
||||
|
||||
#### 2.2. Reject submission when Name is whitespace only ✅
|
||||
|
||||
**File:** `tests/create-child/validation.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to /parent and click the 'Add Child' FAB
|
||||
|
||||
|
||||
- expect: The Create Child form is displayed at /parent/children/create
|
||||
|
||||
2. Enter only spaces ' ' in the Name field, enter '7' in the Age field
|
||||
|
||||
|
||||
- expect: The Create button remains disabled (whitespace is treated as empty)
|
||||
- expect: Form stays on /parent/children/create
|
||||
|
||||
#### 2.3. Reject submission when Age is empty ✅
|
||||
|
||||
**File:** `tests/create-child/validation.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to /parent and click the 'Add Child' FAB
|
||||
|
||||
|
||||
- expect: The Create Child form is displayed at /parent/children/create
|
||||
|
||||
2. Enter 'Charlie' in the Name field, clear the Age field
|
||||
|
||||
|
||||
- expect: The Create button remains disabled
|
||||
- expect: Form stays on /parent/children/create
|
||||
|
||||
### 3. Validation - Boundary Conditions ✅ IMPLEMENTED
|
||||
|
||||
**Seed:** `frontend/vue-app/seed.ts`
|
||||
|
||||
#### 3.1. Reject negative age ✅
|
||||
|
||||
**File:** `tests/create-child/validation.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to /parent and click the 'Add Child' FAB
|
||||
|
||||
|
||||
- expect: The Create Child form is displayed at /parent/children/create
|
||||
|
||||
2. Enter 'Dave' in the Name field, enter '-1' in the Age field
|
||||
|
||||
|
||||
- expect: The Create button remains disabled (negative age is invalid)
|
||||
- expect: Form stays on /parent/children/create
|
||||
|
||||
#### 3.2. Enforce maximum Name length of 64 characters ✅
|
||||
|
||||
**File:** `tests/create-child/validation.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to /parent and click the 'Add Child' FAB
|
||||
|
||||
|
||||
- expect: The Create Child form is displayed at /parent/children/create
|
||||
|
||||
2. Attempt to type a name with 65 characters in the Name field
|
||||
|
||||
|
||||
- expect: The input is capped at 64 characters by HTML maxlength attribute
|
||||
|
||||
3. Enter '5' in the Age field and click Create
|
||||
|
||||
|
||||
- expect: The Create button becomes enabled
|
||||
- expect: The form submits successfully with the 64-character name
|
||||
- expect: Navigation returns to /parent
|
||||
|
||||
#### 3.3. Reject age greater than 120 ✅
|
||||
|
||||
**File:** `tests/create-child/validation.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to /parent and click the 'Add Child' FAB
|
||||
|
||||
|
||||
- expect: The Create Child form is displayed at /parent/children/create
|
||||
|
||||
2. Enter 'Eve' in the Name field, enter '121' in the Age field
|
||||
|
||||
|
||||
- expect: The Create button remains disabled (age > 120 is invalid)
|
||||
- expect: Form stays on /parent/children/create
|
||||
|
||||
### 4. Navigation ✅ IMPLEMENTED
|
||||
|
||||
**Seed:** `frontend/vue-app/seed.ts`
|
||||
|
||||
#### 4.1. Cancel navigates back without saving ✅
|
||||
|
||||
**File:** `tests/create-child/navigation.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to /parent and click the 'Add Child' FAB
|
||||
|
||||
|
||||
- expect: The Create Child form is displayed
|
||||
|
||||
2. Enter 'Frank' in the Name field and '9' in the Age field, then click the Cancel button
|
||||
|
||||
|
||||
- expect: Navigation returns to /parent
|
||||
- expect: 'Frank' does NOT appear in the children list
|
||||
|
||||
### 5. Real-Time Updates via SSE ✅ IMPLEMENTED
|
||||
|
||||
**Seed:** `frontend/vue-app/seed.ts`
|
||||
|
||||
#### 5.1. New child appears in list without page reload ✅
|
||||
|
||||
**File:** `tests/create-child/sse.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Open two browser tabs both on /parent
|
||||
|
||||
|
||||
- expect: Both tabs show the Children List page
|
||||
|
||||
2. In Tab 1, click 'Add Child' FAB, fill in name 'Hannah' and age '4', then submit
|
||||
|
||||
|
||||
- expect: Tab 1 navigates to /parent and 'Hannah' is visible
|
||||
|
||||
3. Switch to Tab 2 which is still on /parent
|
||||
|
||||
|
||||
- expect: 'Hannah' appears in Tab 2 without a manual refresh (SSE real-time update)
|
||||
|
||||
### 6. Authorization ✅ IMPLEMENTED
|
||||
|
||||
**Seed:** `frontend/vue-app/seed.ts`
|
||||
|
||||
#### 6.1. Add Child FAB is hidden when parent auth is expired ✅
|
||||
|
||||
**File:** `tests/create-child/authorization.spec.ts`
|
||||
|
||||
**Note:** Uses `chromium-no-pin` project storage state to simulate a user without parent authentication.
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to /parent using the no-pin storage state (parent auth not present)
|
||||
|
||||
|
||||
- expect: The 'Add Child' FAB is NOT visible on the page
|
||||
|
||||
## Key Changes from Original Plan
|
||||
|
||||
1. **Validation Strategy**: Changed from server-side error messages to client-side form validation that disables the submit button for invalid inputs
|
||||
2. **Async Form Handling**: Tests include logic to handle async form initialization races with default image loading
|
||||
3. **Authorization test**: Uses `STORAGE_STATE_NO_PIN` (chromium-no-pin project) rather than manually clearing localStorage
|
||||
481
frontend/e2e/plans/parent-item-management.plan.md
Normal file
481
frontend/e2e/plans/parent-item-management.plan.md
Normal file
@@ -0,0 +1,481 @@
|
||||
# Parent create and edit chores, kindness acts, and penalties
|
||||
|
||||
## Application Overview
|
||||
|
||||
Focus on parent-mode flows: a parent logs in via PIN, then creates, edits, and deletes three types of items (chore, kindness act, penalty) separately. Each scenario assumes the parent is already authenticated (PIN entered) and navigates to `/parent/tasks/chores`, then switches tabs as needed.
|
||||
|
||||
Tests are executed in parent mode; children should not be able to perform these operations. The intent is to verify that creation forms work, that existing items can be updated correctly, and that proper deletion and default-item restoration logic functions.
|
||||
|
||||
All item names use a `${Date.now()}` suffix to avoid collisions between parallel test runs.
|
||||
|
||||
## Test Scenarios
|
||||
|
||||
### 1. Chore management
|
||||
|
||||
**Seed:** `e2e/seed.spec.ts`
|
||||
|
||||
#### 1.1. Create a new chore (parent mode)
|
||||
|
||||
**File:** `e2e/mode_parent/tasks/chore-create-edit.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `/parent/tasks/chores`
|
||||
|
||||
|
||||
- expect: The chores list loads and the default chore 'Clean your mess' is visible
|
||||
|
||||
2. Click the 'Create Chore' button
|
||||
|
||||
|
||||
- expect: The Create Chore form is displayed with a 'Chore Name' field and a Points field
|
||||
|
||||
3. Enter a unique name (e.g. `Wash dishes ${suffix}`) and '10' in Points using DOM events
|
||||
|
||||
|
||||
- expect: Submit/Create button becomes enabled
|
||||
|
||||
4. Click the Create button
|
||||
|
||||
|
||||
- expect: No `.error` elements are shown
|
||||
- expect: The new chore appears in the chores list
|
||||
|
||||
#### 1.2. Cancel chore creation
|
||||
|
||||
**File:** `e2e/mode_parent/tasks/chore-cancel.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `/parent/tasks/chores` and click 'Create Chore'
|
||||
|
||||
|
||||
- expect: Create Chore form appears with 'Chore Name' label visible
|
||||
|
||||
2. Fill in 'Test' for name and '5' for points, then click 'Cancel'
|
||||
|
||||
|
||||
- expect: User is returned to the chores list (default chore 'Clean your mess' is visible with exact match)
|
||||
- expect: No element with text 'Test' exists in the list
|
||||
|
||||
#### 1.3. Edit an existing chore
|
||||
|
||||
**File:** `e2e/mode_parent/tasks/chore-create-edit.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `/parent/tasks/chores`; create `Wash dishes ${suffix}` if it doesn't already exist
|
||||
|
||||
|
||||
- expect: The chore appears in the list
|
||||
|
||||
2. Click the chore row to open the edit form
|
||||
|
||||
|
||||
- expect: Edit form appears with 'Chore Name' label visible
|
||||
|
||||
3. Change the name to `Wash car ${suffix}` and points using DOM events
|
||||
|
||||
|
||||
- expect: Save button becomes enabled
|
||||
|
||||
4. Click Save
|
||||
|
||||
|
||||
- expect: No errors are displayed
|
||||
- expect: The updated chore name `Wash car ${suffix}` appears in the list
|
||||
|
||||
#### 1.4. Cancel chore edit
|
||||
|
||||
**File:** `e2e/mode_parent/tasks/chore-cancel.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `/parent/tasks/chores`; create 'Wash dishes' if it doesn't exist; click the row
|
||||
|
||||
|
||||
- expect: Edit form is displayed
|
||||
|
||||
2. Change the name to 'Should not save' via DOM events, then click 'Cancel'
|
||||
|
||||
|
||||
- expect: The list is visible again (default chore 'Clean your mess' shown with exact match)
|
||||
- expect: 'Wash dishes' (exact) is still present; 'Should not save' does not appear
|
||||
|
||||
#### 1.5. Convert a default chore to a user item by editing
|
||||
|
||||
**File:** `e2e/mode_parent/tasks/chore-convert-default.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `/parent/tasks/chores` and locate the default chore 'Clean your mess'
|
||||
|
||||
|
||||
- expect: Item is visible and has no delete button (`button[aria-label="Delete item"]` count is 0)
|
||||
|
||||
2. Click the row to open the edit form
|
||||
|
||||
|
||||
- expect: Edit form opens with `input#name` value of 'Clean your mess'
|
||||
|
||||
3. Change the name to 'Clean your mess (custom)' and points to '20' via DOM events
|
||||
|
||||
|
||||
- expect: Save button becomes enabled
|
||||
|
||||
4. Click Save
|
||||
|
||||
|
||||
- expect: The chore row for 'Clean your mess' now has a visible delete button
|
||||
|
||||
#### 1.6. Delete a chore
|
||||
|
||||
**File:** `e2e/mode_parent/tasks/chore-create-edit.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `/parent/tasks/chores`; create a uniquely-named chore if necessary
|
||||
|
||||
|
||||
- expect: The chore appears in the list with a delete control
|
||||
|
||||
2. Click the delete icon (`button[aria-label="Delete item"]`) and confirm the dialog
|
||||
|
||||
|
||||
- expect: The item is removed from the list
|
||||
|
||||
#### 1.7. Edit default chore points and verify system restoration on delete
|
||||
|
||||
**File:** `e2e/mode_parent/tasks/chore-delete-default.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `/parent/tasks/chores`; clean up any leftover user-copy of 'Take out trash' (with delete icon) from previous runs
|
||||
2. Verify the default 'Take out trash' is visible with no delete icon
|
||||
3. Click 'Take out trash' to open the edit form; confirm `input#name` reads 'Take out trash'; change points to '5' via DOM events
|
||||
|
||||
|
||||
- expect: Save button becomes enabled
|
||||
|
||||
4. Click Save
|
||||
|
||||
|
||||
- expect: Exactly one 'Take out trash' row is in the list
|
||||
- expect: A delete icon is now visible on the 'Take out trash' row
|
||||
- expect: The points display reads '5 pts'
|
||||
|
||||
5. Click the delete icon and confirm the dialog
|
||||
|
||||
|
||||
- expect: 'Take out trash' is still visible in the list (system default restored)
|
||||
- expect: No delete icon on the 'Take out trash' row
|
||||
- expect: Points display is restored to '20 pts'
|
||||
|
||||
### 2. Kindness act management
|
||||
|
||||
**Seed:** `e2e/seed.spec.ts`
|
||||
|
||||
#### 2.1. Create a new kindness act (parent mode)
|
||||
|
||||
**File:** `e2e/mode_parent/tasks/kindness-create-edit.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `/parent/tasks/chores` and click the 'Kindness Acts' tab
|
||||
|
||||
|
||||
- expect: Kindness acts list is visible
|
||||
|
||||
2. Click 'Create Kindness Act'
|
||||
|
||||
|
||||
- expect: Create form appears with a 'Name' field
|
||||
|
||||
3. Enter a unique name (e.g. `Share toys ${suffix}`) and '5' in Points via DOM events
|
||||
|
||||
|
||||
- expect: Create button becomes enabled
|
||||
|
||||
4. Click Create
|
||||
|
||||
|
||||
- expect: No `.error` elements shown
|
||||
- expect: The new kindness act name appears in the list
|
||||
|
||||
#### 2.2. Cancel kindness act creation
|
||||
|
||||
**File:** `e2e/mode_parent/tasks/kindness-cancel.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `/parent/tasks/chores`, click 'Kindness Acts', then click 'Create Kindness Act'
|
||||
|
||||
|
||||
- expect: Create form is visible
|
||||
|
||||
2. Fill in 'Test' for name and '5' for points, then click 'Cancel'
|
||||
|
||||
|
||||
- expect: Returned to the list
|
||||
- expect: 'Should not save' does not appear in the list
|
||||
|
||||
#### 2.3. Edit an existing kindness act
|
||||
|
||||
**File:** `e2e/mode_parent/tasks/kindness-create-edit.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `/parent/tasks/chores`, click 'Kindness Acts'; create `Share toys ${suffix}` fresh
|
||||
|
||||
|
||||
- expect: Item is displayed in the list
|
||||
|
||||
2. Click the row to open the edit form
|
||||
|
||||
|
||||
- expect: Edit form appears with 'Name' label and Save button visible
|
||||
|
||||
3. Change name to `Help with homework ${suffix}` and points to '8' via DOM events
|
||||
|
||||
|
||||
- expect: Save button becomes enabled
|
||||
|
||||
4. Click Save
|
||||
|
||||
|
||||
- expect: The updated name `Help with homework ${suffix}` appears in the list
|
||||
|
||||
#### 2.4. Cancel kindness act edit
|
||||
|
||||
**File:** `e2e/mode_parent/tasks/kindness-cancel.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `/parent/tasks/chores`, click 'Kindness Acts'; ensure 'Share toys' (exact) exists; click its row
|
||||
|
||||
|
||||
- expect: Edit form is displayed
|
||||
|
||||
2. Change name to 'Never saved' via DOM events, then click 'Cancel'
|
||||
|
||||
|
||||
- expect: 'Share toys' (exact) is still visible in the list
|
||||
|
||||
#### 2.5. Convert a default kindness act to a user item by editing
|
||||
|
||||
**File:** `e2e/mode_parent/tasks/kindness-convert-default.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `/parent/tasks/chores`, click 'Kindness Acts'; locate the default 'Be good for the day'
|
||||
|
||||
|
||||
- expect: Item is visible with no delete icon
|
||||
|
||||
2. Click the row; rename to 'Be good today (edited)' and change points to '7' via `#name`/`#points` inputs
|
||||
|
||||
|
||||
- expect: Save button becomes enabled
|
||||
|
||||
3. Click Save
|
||||
|
||||
|
||||
- expect: 'Be good today (edited)' row has a visible delete icon
|
||||
|
||||
4. _(Cleanup)_ Click the delete icon and confirm — restores the default so other tests see a clean state
|
||||
|
||||
#### 2.6. Delete a kindness act
|
||||
|
||||
**File:** `e2e/mode_parent/tasks/kindness-create-edit.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `/parent/tasks/chores`, click 'Kindness Acts'; create a uniquely-named act
|
||||
|
||||
|
||||
- expect: The item appears in the list with a delete control
|
||||
|
||||
2. Click the delete icon and confirm
|
||||
|
||||
|
||||
- expect: The act is removed from the list
|
||||
|
||||
#### 2.7. Edit default kindness act points and verify system restoration on delete
|
||||
|
||||
**File:** `e2e/mode_parent/tasks/kindness-delete-default.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `/parent/tasks/chores`, click 'Kindness Acts'; clean up any leftover user-copy of 'Be good for the day' from previous runs
|
||||
2. Verify the default 'Be good for the day' is visible with no delete icon
|
||||
3. Click the row; confirm `input#name` reads 'Be good for the day'; change points to '3' via DOM events
|
||||
|
||||
|
||||
- expect: Save button becomes enabled
|
||||
|
||||
4. Click Save
|
||||
|
||||
|
||||
- expect: Exactly one 'Be good for the day' row is in the list
|
||||
- expect: A delete icon is now visible on the row
|
||||
- expect: Points display reads '3 pts'
|
||||
|
||||
5. Click the delete icon and confirm the dialog
|
||||
|
||||
|
||||
- expect: 'Be good for the day' is still visible (system default restored)
|
||||
- expect: No delete icon on the row
|
||||
- expect: Points display is restored to '15 pts'
|
||||
|
||||
### 3. Penalty management
|
||||
|
||||
**Seed:** `e2e/seed.spec.ts`
|
||||
|
||||
#### 3.1. Create a new penalty (parent mode)
|
||||
|
||||
**File:** `e2e/mode_parent/tasks/penalty-create-edit.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `/parent/tasks/chores` and click the 'Penalties' tab
|
||||
|
||||
|
||||
- expect: Penalties list is shown
|
||||
|
||||
2. Click 'Create Penalty'
|
||||
|
||||
|
||||
- expect: Create Penalty form appears
|
||||
|
||||
3. Enter a unique name (e.g. `No screen time ${suffix}`) and '30' in Points via DOM events
|
||||
|
||||
|
||||
- expect: Create button becomes enabled
|
||||
|
||||
4. Click Create
|
||||
|
||||
|
||||
- expect: No validation errors
|
||||
- expect: The new penalty name appears in the list
|
||||
|
||||
#### 3.2. Cancel penalty creation
|
||||
|
||||
**File:** `e2e/mode_parent/tasks/penalty-cancel.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `/parent/tasks/chores`, click 'Penalties', then click 'Create Penalty'
|
||||
|
||||
|
||||
- expect: Create penalty form is visible
|
||||
|
||||
2. Fill 'Should not save' and '10' points via DOM events, then click 'Cancel'
|
||||
|
||||
|
||||
- expect: 'Should not save' does not appear in the list
|
||||
|
||||
#### 3.3. Edit an existing penalty
|
||||
|
||||
**File:** `e2e/mode_parent/tasks/penalty-create-edit.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `/parent/tasks/chores`, click 'Penalties'; create `No screen time ${suffix}` fresh
|
||||
|
||||
|
||||
- expect: Item appears in list
|
||||
|
||||
2. Click the row; wait for `#name` to have the original value
|
||||
|
||||
|
||||
- expect: Edit form opens with existing name pre-filled
|
||||
|
||||
3. Change name to `No dessert ${suffix}` and points to '20' via DOM events
|
||||
|
||||
|
||||
- expect: Save button becomes enabled
|
||||
|
||||
4. Click Save
|
||||
|
||||
|
||||
- expect: `No dessert ${suffix}` appears in the list
|
||||
|
||||
#### 3.4. Cancel penalty edit
|
||||
|
||||
**File:** `e2e/mode_parent/tasks/penalty-cancel.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `/parent/tasks/chores`, click 'Penalties'; ensure 'No screen time' (exact) exists; click the row
|
||||
|
||||
|
||||
- expect: Edit form is displayed
|
||||
|
||||
2. Change name to 'Never saved' via DOM events, then click 'Cancel'
|
||||
|
||||
|
||||
- expect: 'No screen time' (exact) is still visible
|
||||
|
||||
#### 3.5. Convert a default penalty to a user item by editing
|
||||
|
||||
**File:** `e2e/mode_parent/tasks/penalty-convert-default.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `/parent/tasks/chores`, click 'Penalties'; locate the default 'Fighting'
|
||||
|
||||
|
||||
- expect: Item is visible with no delete icon
|
||||
|
||||
2. Click the row; rename to 'Fighting (custom)' and change points to '15' via `#name`/`#points` inputs
|
||||
|
||||
|
||||
- expect: Save button becomes enabled
|
||||
|
||||
3. Click Save
|
||||
|
||||
|
||||
- expect: 'Fighting' row now has a visible delete icon
|
||||
|
||||
#### 3.6. Delete a penalty
|
||||
|
||||
**File:** `e2e/mode_parent/tasks/penalty-create-edit.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `/parent/tasks/chores`, click 'Penalties'; create a uniquely-named penalty
|
||||
|
||||
|
||||
- expect: Delete control (`button[aria-label="Delete item"]`) is visible on the row
|
||||
|
||||
2. Click the delete icon and confirm via `button.btn-danger`
|
||||
|
||||
|
||||
- expect: Item is removed from the list
|
||||
|
||||
#### 3.7. Edit default penalty points and verify system restoration on delete
|
||||
|
||||
**File:** `e2e/mode_parent/tasks/penalty-delete-default.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to `/parent/tasks/chores`, click 'Penalties'; clean up any leftover user-copy of 'Fighting' from previous runs
|
||||
2. Verify the default 'Fighting' is visible with no delete icon
|
||||
3. Click the row; confirm `input#name` reads 'Fighting'; change points to '3' via DOM events
|
||||
|
||||
|
||||
- expect: Save button becomes enabled
|
||||
|
||||
4. Click Save
|
||||
|
||||
|
||||
- expect: Exactly one 'Fighting' row is in the list
|
||||
- expect: A delete icon is now visible on the row
|
||||
- expect: Points display reads '3 pts'
|
||||
|
||||
5. Click the delete icon and confirm the dialog
|
||||
|
||||
|
||||
- expect: 'Fighting' is still visible (system default restored)
|
||||
- expect: No delete icon on the row
|
||||
- expect: Points display is restored to '10 pts'
|
||||
602
frontend/e2e/plans/parent-notifications.plan.md
Normal file
602
frontend/e2e/plans/parent-notifications.plan.md
Normal file
@@ -0,0 +1,602 @@
|
||||
# Parent Notifications E2E Tests
|
||||
|
||||
## Application Overview
|
||||
|
||||
The parent notifications feature adds two channels for alerting parents when a child performs an action requiring their attention:
|
||||
|
||||
1. **Web Push Notifications** — When a child confirms a chore is complete, or requests an affordable reward, the parent receives a native browser push notification via a Service Worker. The payload contains `child_id`, `entity_id`, and `entity_type`. Supporting browsers (Chrome desktop) show "Approve" and "Deny" action buttons; clicking an action button calls the matching backend API (`approve-chore`, `reject-chore`, `trigger-reward`, or `deny-reward-request`) without opening the app. Tapping the notification body opens or focuses the app and navigates to `/parent/:id?scrollTo=<entityId>&entityType=<chore|reward>`.
|
||||
|
||||
2. **Daily Email Digest** — At 9 pm in the parent's local timezone, a HTML digest email is sent for any still-pending items (only if at least one exists). Each item has three links: **View** (ParentView deep link), **Approve** (`GET /api/digest-action/<token>`), and **Deny** (`GET /api/digest-action/<token>`). Tokens are HMAC-SHA256 signed, valid for 24 hours, and single-use. On success the endpoint executes the action and 302-redirects to the ParentView deep link; on error (expired, already-used, tampered) it returns a 400 HTML page with no redirect.
|
||||
|
||||
**In-app notification view** — `/parent/notifications` lists all pending confirmations via `GET /api/pending-confirmations`. Clicking an item navigates to `/parent/:id?scrollTo=<entityId>&entityType=<type>`. Approval and rejection happen via **ParentView**:
|
||||
|
||||
- **Pending chores**: card shows a `.chore-stamp.pending-stamp` "PENDING" badge; clicking the card opens `ChoreApproveDialog` which has Approve and Reject buttons.
|
||||
- **Pending rewards**: card shows a `.pending` "PENDING" badge when `item.redeeming === true`; clicking opens a reward-confirm dialog that calls `POST trigger-reward`. A **Deny** button (added by this feature) calls `POST deny-reward-request`.
|
||||
|
||||
The notification view refreshes reactively via SSE events: `child_chore_confirmation` (operations: `CONFIRMED`, `APPROVED`, `REJECTED`, `CANCELLED`) and `child_reward_request` (operations: `CREATED`, `CANCELLED`, `GRANTED`).
|
||||
|
||||
**Spec covered:** `feat-parent-chore-notifications.md`
|
||||
|
||||
---
|
||||
|
||||
## Implementation
|
||||
|
||||
Tests live in `e2e/mode_parent/notifications/`. Each spec creates its own isolated child and entities via API so files can run in parallel within the bucket. All created resources are deleted in `afterAll`.
|
||||
|
||||
## Playwright Projects
|
||||
|
||||
| Project | testMatch | Notes |
|
||||
| ------------------------------- | -------------------------------------------- | ----------------------- |
|
||||
| `chromium-parent-notifications` | `/mode_parent\/notifications\/.+\.spec\.ts/` | Depends on `setup` only |
|
||||
|
||||
**Config:** Add project to `playwright.config.ts` with `dependencies: ['setup']` and `storageState: STORAGE_STATE`. Add `/mode_parent\/notifications\//` to the `testIgnore` list of any existing catch-all project so notification specs do not run in two buckets simultaneously.
|
||||
|
||||
---
|
||||
|
||||
## Seed Strategy
|
||||
|
||||
Each spec file uses `beforeAll` to create isolated children, tasks, and rewards via the authenticated `request` fixture, and deletes them all in `afterAll`. The standard pattern is: pre-delete by name, create via `PUT`, re-fetch to get the ID.
|
||||
|
||||
**Push subscription tests:** Service Worker registration and `PushManager.subscribe()` require a VAPID public key configured in the dev server environment. Tests 1.1 and 1.2 (intercepting the subscription POST to verify endpoint/timezone) depend on VAPID being configured and `PushManager` being available — both absent in headless Chromium. These tests have been **removed** from the spec. The remaining tests (1.3–1.5) cover the permission-denied path and unauthenticated API rejection, which do not require VAPID.
|
||||
|
||||
**Digest action token prerequisite:** The email digest scheduler is disabled in `DB_ENV=e2e` (to avoid sending real email). To exercise `GET /api/digest-action/<token>` in E2E tests, the backend must expose a test-only helper endpoint active **only** when `DB_ENV=e2e`:
|
||||
|
||||
```
|
||||
POST /api/admin/test/digest-token
|
||||
Auth: required (uses the E2E parent session)
|
||||
Body: { child_id, entity_id, entity_type, action, expires_in_hours? }
|
||||
Response 200: { token: string }
|
||||
```
|
||||
|
||||
This endpoint creates a valid `DigestActionToken` (with correct HMAC-SHA256 signature) in TinyDB and returns the token string. All digest-action tests call this helper in `beforeAll` to obtain tokens for their scenarios.
|
||||
|
||||
---
|
||||
|
||||
## Test Scenarios
|
||||
|
||||
### 1. Push Subscription Registration
|
||||
|
||||
**File:** `e2e/mode_parent/notifications/push-subscription.spec.ts`
|
||||
|
||||
**Seed:** No child/task setup required. Tests use `page.context().grantPermissions(['notifications'])` as needed. No `afterAll` cleanup.
|
||||
|
||||
> **Note on trigger**: Push subscription is now initiated by the Push Notifications toggle in `/parent/profile` (which calls `subscribeToPushWithResult()` directly). For these tests, triggering is done via the profile toggle rather than navigating to `/parent/tasks/chores`. PIN entry also triggers subscription in production but pre-authenticated E2E sessions bypass the PIN flow.
|
||||
|
||||
#### 1.3. No subscription POST is sent when notification permission is not granted
|
||||
|
||||
- Do NOT grant notification permission (leave default)
|
||||
- Track calls to `POST /api/push-subscription` via `page.route()`
|
||||
- Navigate to `/parent/profile` and attempt to click the Push Notifications toggle
|
||||
- expect: No POST to `/api/push-subscription` was captured
|
||||
|
||||
#### 1.4. Backend rejects unauthenticated subscription POST (401)
|
||||
|
||||
- Create a fresh `APIRequestContext` without storageState (no auth cookies) via `playwright.request.newContext()`
|
||||
- POST to `/api/push-subscription` with a minimal payload
|
||||
- expect: Response status is 401
|
||||
|
||||
#### 1.5. Backend rejects unauthenticated subscription DELETE (401)
|
||||
|
||||
- Using the same unauthenticated context as 1.4, send `DELETE /api/push-subscription`
|
||||
- expect: Response status is 401
|
||||
|
||||
---
|
||||
|
||||
### 2. Chore Notification — Appearance and In-App Approval
|
||||
|
||||
**File:** `e2e/mode_parent/notifications/chore-notification-approve.spec.ts`
|
||||
|
||||
**Seed:** Creates child `ChoreNotifApproveChild` and chore `ChoreNotifApproveChore` (5 points, type `chore`) via API. Assigns chore to child. Sets a daily interval schedule (`mode: 'interval', interval_days: 1, interval_has_deadline: false`) so the chore persists post-approval. In `beforeAll`, calls `POST /child/<childId>/confirm-chore` with `{ task_id: choreId }`. Deletes child and task in `afterAll`.
|
||||
|
||||
#### 2.1. Confirmed chore appears in the notification view
|
||||
|
||||
- Navigate to `/parent/notifications`
|
||||
- expect: A list item is visible containing the text `ChoreNotifApproveChore`
|
||||
- expect: The item contains the word "completed" (not "requested")
|
||||
|
||||
#### 2.2. Notification item displays the child's name
|
||||
|
||||
- Navigate to `/parent/notifications`
|
||||
- expect: The notification item for `ChoreNotifApproveChore` also shows `ChoreNotifApproveChild`
|
||||
|
||||
#### 2.3. Clicking notification navigates to ParentView with correct query params
|
||||
|
||||
- Navigate to `/parent/notifications`
|
||||
- Click the `ChoreNotifApproveChore` notification item
|
||||
- `await page.waitForURL(new RegExp('/parent/' + childId + '\\?scrollTo=.*&entityType=chore'), { timeout: 5000 })`
|
||||
- expect: URL contains `scrollTo=<choreId>` and `entityType=chore`
|
||||
- expect: The "Chores" section heading is visible
|
||||
|
||||
#### 2.4. Pending chore card shows PENDING stamp in ParentView
|
||||
|
||||
- Navigate to `/parent/<childId>`
|
||||
- Locate the chore card for `ChoreNotifApproveChore` in the Chores section
|
||||
- expect: Card contains an element with text "PENDING"
|
||||
|
||||
#### 2.5. Clicking a pending chore card opens ChoreApproveDialog
|
||||
|
||||
- Navigate to `/parent/<childId>`
|
||||
- Click the `ChoreNotifApproveChore` card
|
||||
- expect: A dialog element is visible containing both an "Approve" button and a "Reject" button
|
||||
|
||||
#### 2.6. Approving via ChoreApproveDialog removes the PENDING stamp
|
||||
|
||||
- Click the "Approve" button in the dialog
|
||||
- expect: The dialog closes
|
||||
- expect: The "PENDING" stamp is no longer present on the `ChoreNotifApproveChore` card
|
||||
|
||||
#### 2.7. Approving a chore clears it from the notification view
|
||||
|
||||
- After approving (2.6), navigate to `/parent/notifications`
|
||||
- expect: No list item containing `ChoreNotifApproveChore` is present
|
||||
|
||||
#### 2.8. Approving a chore awards points to the child
|
||||
|
||||
- Note child's point balance before approval
|
||||
- Approve the chore via the dialog
|
||||
- expect: Child's point balance increases by the chore's point value (5)
|
||||
|
||||
---
|
||||
|
||||
### 3. Chore Notification — In-App Rejection
|
||||
|
||||
**File:** `e2e/mode_parent/notifications/chore-notification-deny.spec.ts`
|
||||
|
||||
**Seed:** Creates child `ChoreNotifDenyChild` and chore `ChoreNotifDenyChore` (5 points) via API. Assigns chore. Sets daily interval schedule. In `beforeAll`, calls `POST /child/<childId>/confirm-chore`. Deletes all in `afterAll`.
|
||||
|
||||
#### 3.1. Confirmed chore appears in the notification view
|
||||
|
||||
- Navigate to `/parent/notifications`
|
||||
- expect: A list item is visible containing `ChoreNotifDenyChore`
|
||||
|
||||
#### 3.2. ChoreApproveDialog shows both Approve and Reject buttons
|
||||
|
||||
- Navigate to `/parent/<childId>`, click the `ChoreNotifDenyChore` card
|
||||
- expect: Dialog with "Approve" and "Reject" buttons is visible
|
||||
|
||||
#### 3.3. Rejecting via ChoreApproveDialog removes the PENDING stamp
|
||||
|
||||
- Click the "Reject" button
|
||||
- expect: Dialog closes
|
||||
- expect: "PENDING" stamp is no longer present on the `ChoreNotifDenyChore` card
|
||||
|
||||
#### 3.4. Rejecting a chore clears it from the notification view
|
||||
|
||||
- After rejecting (3.3), navigate to `/parent/notifications`
|
||||
- expect: No list item containing `ChoreNotifDenyChore` is present
|
||||
|
||||
#### 3.5. Rejecting a chore does not award points
|
||||
|
||||
- Note child's point balance before rejection
|
||||
- Reject the pending chore via ChoreApproveDialog
|
||||
- expect: Child's point balance is unchanged
|
||||
|
||||
---
|
||||
|
||||
### 4. Reward Notification — Appearance and In-App Grant
|
||||
|
||||
**File:** `e2e/mode_parent/notifications/reward-notification-approve.spec.ts`
|
||||
|
||||
**Seed:** Creates child `RewardNotifApproveChild` with `points = 30` via API. Creates reward `RewardNotifApproveReward` with `cost = 10`. Assigns reward to child. In `beforeAll`, calls `POST /child/<childId>/request-reward` with `{ reward_id: rewardId }`. Deletes child and reward in `afterAll`.
|
||||
|
||||
#### 4.1. Requested reward appears in the notification view
|
||||
|
||||
- Navigate to `/parent/notifications`
|
||||
- expect: A list item is visible containing `RewardNotifApproveReward`
|
||||
- expect: The item contains the word "requested" (not "completed")
|
||||
|
||||
#### 4.2. Notification item displays the child's name
|
||||
|
||||
- Navigate to `/parent/notifications`
|
||||
- expect: The notification item for `RewardNotifApproveReward` also shows `RewardNotifApproveChild`
|
||||
|
||||
#### 4.3. Clicking reward notification navigates to ParentView with correct query params
|
||||
|
||||
- Navigate to `/parent/notifications`
|
||||
- Click the `RewardNotifApproveReward` notification item
|
||||
- `await page.waitForURL(new RegExp('/parent/' + childId + '\\?scrollTo=.*&entityType=reward'), { timeout: 5000 })`
|
||||
- expect: URL contains `scrollTo=<rewardId>` and `entityType=reward`
|
||||
|
||||
#### 4.4. Pending reward card shows PENDING badge in ParentView
|
||||
|
||||
- Navigate to `/parent/<childId>`
|
||||
- Locate the reward card for `RewardNotifApproveReward` in the Rewards section
|
||||
- expect: Card contains an element with text "PENDING"
|
||||
|
||||
#### 4.5. Clicking a pending reward card opens the grant confirmation dialog
|
||||
|
||||
- Navigate to `/parent/<childId>`, click the `RewardNotifApproveReward` card
|
||||
- expect: A reward confirmation dialog appears
|
||||
|
||||
#### 4.6. Confirming the grant removes the PENDING badge and updates child points
|
||||
|
||||
- In the grant confirmation dialog, click the confirm button
|
||||
- expect: Dialog closes
|
||||
- expect: The "PENDING" badge is no longer visible on the reward card
|
||||
- expect: Child's point balance decreases by the reward cost (30 → 20)
|
||||
|
||||
#### 4.7. Granting a reward clears it from the notification view
|
||||
|
||||
- After confirming the grant (4.6), navigate to `/parent/notifications`
|
||||
- expect: No list item containing `RewardNotifApproveReward` is present
|
||||
|
||||
---
|
||||
|
||||
### 5. Reward Notification — In-App Denial
|
||||
|
||||
**File:** `e2e/mode_parent/notifications/reward-notification-deny.spec.ts`
|
||||
|
||||
**Seed:** Creates child `RewardNotifDenyChild` with `points = 30`. Creates reward `RewardNotifDenyReward` with `cost = 10`. Assigns reward to child. Uses `beforeEach` to call `POST /child/<childId>/request-reward` (creates a fresh pending request before each test) and `afterEach` to call `POST /child/<childId>/cancel-request-reward` to clean up any unconsumed requests. Deletes child and reward in `afterAll`.
|
||||
|
||||
#### 5.1. Requested reward appears in the notification view
|
||||
|
||||
- Navigate to `/parent/notifications`
|
||||
- expect: A list item is visible containing `RewardNotifDenyReward`
|
||||
|
||||
#### 5.2. Pending reward card shows PENDING badge in ParentView
|
||||
|
||||
- Navigate to `/parent/<childId>`
|
||||
- expect: The `RewardNotifDenyReward` card shows a "PENDING" badge
|
||||
|
||||
#### 5.3. Denying a reward request removes the PENDING badge
|
||||
|
||||
- Trigger the deny action for the pending reward (target the Deny button by its label or role — exact placement determined by implementation)
|
||||
- expect: The "PENDING" badge disappears from the reward card
|
||||
- expect: Child's point balance is unchanged
|
||||
|
||||
#### 5.4. Denying a reward request clears it from the notification view
|
||||
|
||||
- After denying (5.3), navigate to `/parent/notifications`
|
||||
- expect: No list item containing `RewardNotifDenyReward` is present
|
||||
|
||||
#### 5.5. `POST deny-reward-request` returns success when a pending request exists (API layer)
|
||||
|
||||
- Via the authenticated `request` fixture, call `POST /child/<childId>/deny-reward-request` with `{ reward_id: rewardId }`
|
||||
- expect: Response status is 200
|
||||
|
||||
#### 5.6. `POST deny-reward-request` returns 404 when no pending request exists (API layer)
|
||||
|
||||
- Cancel any existing pending request first via `POST /child/<childId>/cancel-request-reward`
|
||||
- Call `POST /child/<childId>/deny-reward-request` with `{ reward_id: rewardId }`
|
||||
- expect: Response status is 404
|
||||
|
||||
---
|
||||
|
||||
### 6. ParentView Deep-Link Navigation
|
||||
|
||||
**File:** `e2e/mode_parent/notifications/deep-link-navigation.spec.ts`
|
||||
|
||||
**Seed:** Creates child `DeepLinkChild` with `points = 100`. Creates 10 chores `DeepLinkChore1` … `DeepLinkChore10` (each 5 points, type `chore`). Creates reward `DeepLinkReward` with `cost = 20`. Assigns all 10 chores and the reward to the child. Confirms `DeepLinkChore10` via `POST /child/<childId>/confirm-chore`. Requests the reward via `POST /child/<childId>/request-reward`. Uses narrow viewport `{ width: 480, height: 800 }` so cards near the bottom require actual scrolling. Deletes child, all tasks, and reward in `afterAll`.
|
||||
|
||||
#### 6.1. Deep link to chore — correct card is scrolled into viewport
|
||||
|
||||
- `await page.setViewportSize({ width: 480, height: 800 })`
|
||||
- Navigate to `/parent/<childId>?scrollTo=<choreId>&entityType=chore`
|
||||
- Wait for chore list to load and allow 800 ms for the scroll delay to complete
|
||||
- expect: The `DeepLinkChore10` card is within the visible viewport
|
||||
|
||||
#### 6.2. Deep link to reward — correct card is scrolled into viewport
|
||||
|
||||
- `await page.setViewportSize({ width: 480, height: 800 })`
|
||||
- Navigate to `/parent/<childId>?scrollTo=<rewardId>&entityType=reward`
|
||||
- Wait for reward list to load and scroll delay
|
||||
- expect: The `DeepLinkReward` card is within the visible viewport
|
||||
|
||||
#### 6.3. Deep link with unknown `entityType` — page loads without JS error
|
||||
|
||||
- Listen for `page.on('pageerror', ...)` before navigating
|
||||
- Navigate to `/parent/<childId>?scrollTo=<choreId>&entityType=unknown`
|
||||
- expect: No pageerror event fires
|
||||
- expect: Page loads and the "Chores" section heading is visible
|
||||
|
||||
#### 6.4. Deep link with missing `scrollTo` — page loads normally
|
||||
|
||||
- Navigate to `/parent/<childId>?entityType=chore` (no `scrollTo` query param)
|
||||
- expect: Page loads without error and "Chores" section is visible
|
||||
|
||||
#### 6.5. Deep link with no query params — page loads normally
|
||||
|
||||
- Navigate to `/parent/<childId>` (bare URL, no query params)
|
||||
- expect: Page loads and both "Chores" and "Rewards" section headings are visible
|
||||
|
||||
#### 6.6. Clicking a chore notification produces the correct deep link
|
||||
|
||||
- Navigate to `/parent/notifications`
|
||||
- Click the `DeepLinkChore10` notification item (confirmed chore)
|
||||
- expect: URL matches `/parent/<childId>?scrollTo=<choreId>&entityType=chore`
|
||||
- Wait 800 ms for scroll delay
|
||||
- expect: `DeepLinkChore10` card is in viewport
|
||||
|
||||
#### 6.7. Clicking a reward notification produces the correct deep link
|
||||
|
||||
- Navigate to `/parent/notifications`
|
||||
- Click the `DeepLinkReward` notification item
|
||||
- expect: URL matches `/parent/<childId>?scrollTo=<rewardId>&entityType=reward`
|
||||
- Wait 800 ms
|
||||
- expect: `DeepLinkReward` card is in viewport
|
||||
|
||||
---
|
||||
|
||||
### 7. Digest Action Token — Happy Paths
|
||||
|
||||
Each combination of `entity_type × action` gets its own spec file to keep seeds isolated and specs independently runnable.
|
||||
|
||||
**Files:**
|
||||
|
||||
- `e2e/mode_parent/notifications/digest-action-approve-chore.spec.ts`
|
||||
- `e2e/mode_parent/notifications/digest-action-deny-chore.spec.ts`
|
||||
- `e2e/mode_parent/notifications/digest-action-approve-reward.spec.ts`
|
||||
- `e2e/mode_parent/notifications/digest-action-deny-reward.spec.ts`
|
||||
|
||||
**Seed (shared pattern for all four specs):** Creates an isolated child, task or reward, assigns it, creates a pending confirmation (via `confirm-chore` or `request-reward`), then calls the authenticated `POST /api/admin/test/digest-token` helper to obtain a valid signed token for the required action. All `GET /digest-action/<token>` calls use a **fresh unauthenticated `APIRequestContext`** (no cookies) created via `playwright.request.newContext({ ignoreHTTPSErrors: true })`. The verifying `GET /api/pending-confirmations` and `GET /api/child/<id>` calls use the authenticated `request` fixture. Deletes child and task/reward in `afterAll`.
|
||||
|
||||
#### 7.1. Approve-chore token — 302 redirect to correct ParentView URL
|
||||
|
||||
- Call `GET /api/digest-action/<approve_chore_token>` (unauthenticated, redirect following disabled)
|
||||
- expect: Response status is 302
|
||||
- expect: `response.headers()['location']` contains `/parent/<childId>`, `scrollTo=<choreId>`, and `entityType=chore`
|
||||
|
||||
#### 7.2. Approve-chore token — approve action is performed, points awarded
|
||||
|
||||
- After calling token (7.1), wait 200 ms for the action to complete
|
||||
- Via authenticated `request`: `GET /api/pending-confirmations`
|
||||
- expect: No pending confirmation record exists for this chore and child
|
||||
- Via authenticated `request`: `GET /api/child/<childId>`
|
||||
- expect: Points increased by the chore's point value
|
||||
|
||||
#### 7.3. Deny-chore token — 302 redirect to correct ParentView URL
|
||||
|
||||
- Call `GET /api/digest-action/<deny_chore_token>` (unauthenticated, redirect following disabled)
|
||||
- expect: Response status is 302
|
||||
- expect: Location header contains the correct ParentView deep link (`entityType=chore`)
|
||||
|
||||
#### 7.4. Deny-chore token — rejection action is performed, no points awarded
|
||||
|
||||
- After calling token (7.3), verify:
|
||||
- expect: Pending confirmation for this chore is removed from `GET /api/pending-confirmations`
|
||||
- expect: Child's point balance is unchanged
|
||||
|
||||
#### 7.5. Approve-reward token — 302 redirect to correct ParentView URL
|
||||
|
||||
- Call `GET /api/digest-action/<approve_reward_token>` (unauthenticated, redirect following disabled)
|
||||
- expect: Response status is 302
|
||||
- expect: Location header contains `/parent/<childId>`, `scrollTo=<rewardId>`, and `entityType=reward`
|
||||
|
||||
#### 7.6. Approve-reward token — reward is triggered and child points are deducted
|
||||
|
||||
- After calling token (7.5):
|
||||
- expect: Pending confirmation for this reward is gone from `GET /api/pending-confirmations`
|
||||
- expect: Child's point balance decreased by the reward cost
|
||||
|
||||
#### 7.7. Deny-reward token — 302 redirect to correct ParentView URL
|
||||
|
||||
- Call `GET /api/digest-action/<deny_reward_token>` (unauthenticated, redirect following disabled)
|
||||
- expect: Response status is 302
|
||||
- expect: Location header contains the correct ParentView deep link for this child and reward
|
||||
|
||||
#### 7.8. Deny-reward token — pending reward request is removed, points unchanged
|
||||
|
||||
- After calling token (7.7):
|
||||
- expect: Pending confirmation for this reward is gone
|
||||
- expect: Child's point balance is unchanged
|
||||
|
||||
---
|
||||
|
||||
### 8. Digest Action Token — Error Paths
|
||||
|
||||
**File:** `e2e/mode_parent/notifications/digest-action-errors.spec.ts`
|
||||
|
||||
**Seed:** Creates child `DigestErrorChild` and chore `DigestErrorChore` via API. Assigns chore. Calls `POST /child/<childId>/confirm-chore` to create a pending confirmation. Uses `POST /api/admin/test/digest-token` to generate tokens with specific expiry configurations. All `GET /digest-action/<token>` calls use a fresh unauthenticated context. Deletes child and task in `afterAll`.
|
||||
|
||||
#### 8.1. Expired token returns 400
|
||||
|
||||
- Call `POST /api/admin/test/digest-token` with `expires_in_hours: -1` (already expired at creation)
|
||||
- Call `GET /api/digest-action/<expired_token>` (unauthenticated, redirect following disabled)
|
||||
- expect: Response status is 400
|
||||
- expect: No `Location` header is present (no redirect occurred)
|
||||
|
||||
#### 8.2. Already-used token returns 400 on second use
|
||||
|
||||
- Create a valid token via test helper
|
||||
- First call: `GET /api/digest-action/<token>` — expect: status 302 (token consumed, action executed)
|
||||
- Second call: `GET /api/digest-action/<token>` with the same token string
|
||||
- expect: Response status is 400
|
||||
|
||||
#### 8.3. Tampered token returns 400
|
||||
|
||||
- Create a valid token via test helper; store the raw token string
|
||||
- Modify one character of the token (e.g. replace the final character with a different letter)
|
||||
- Call `GET /api/digest-action/<tampered_token>` (unauthenticated)
|
||||
- expect: Response status is 400
|
||||
|
||||
#### 8.4. Completely fake token returns 400
|
||||
|
||||
- Call `GET /api/digest-action/this-token-does-not-exist`
|
||||
- expect: Response status is 400
|
||||
|
||||
#### 8.5. Error response is a plain HTML page with no redirect
|
||||
|
||||
- For any 400 response from 8.1–8.4:
|
||||
- expect: `content-type` header contains `text/html`
|
||||
- expect: The response body does not include a `Location` header
|
||||
|
||||
---
|
||||
|
||||
### 9. Service Worker Push Delivery — Manual Tests and Mock Strategy
|
||||
|
||||
> **Playwright limitation:** The following scenarios **cannot be automated** with standard Playwright:
|
||||
>
|
||||
> - Playwright has no API to dispatch a `push` event to a registered Service Worker.
|
||||
> - OS-level notification tray interactions ("Approve"/"Deny" action buttons) are outside the browser's JavaScript sandbox.
|
||||
> - Delivering a real push message requires a live Web Push server sending to the browser's push endpoint, which is impractical in a local E2E environment.
|
||||
>
|
||||
> These scenarios must be verified **manually** during feature QA, or via the **mock strategy** below.
|
||||
|
||||
#### Recommended Mock Strategy — Service Worker Test Hook
|
||||
|
||||
Add a test-only message handler to `public/sw.js`, guarded by `self.location.hostname === 'localhost'`:
|
||||
|
||||
```js
|
||||
// sw.js — localhost test hook only
|
||||
self.addEventListener('message', async (event) => {
|
||||
if (event.data?.type !== '__TEST_NOTIFICATION_CLICK__') return
|
||||
const { action, childId, entityId, entityType } = event.data
|
||||
|
||||
if (action === 'approve') {
|
||||
const url =
|
||||
entityType === 'chore'
|
||||
? `/api/child/${childId}/approve-chore`
|
||||
: `/api/child/${childId}/trigger-reward`
|
||||
const body =
|
||||
entityType === 'chore'
|
||||
? JSON.stringify({ task_id: entityId })
|
||||
: JSON.stringify({ reward_id: entityId })
|
||||
await fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body,
|
||||
})
|
||||
} else if (action === 'deny') {
|
||||
const url =
|
||||
entityType === 'chore'
|
||||
? `/api/child/${childId}/reject-chore`
|
||||
: `/api/child/${childId}/deny-reward-request`
|
||||
const body =
|
||||
entityType === 'chore'
|
||||
? JSON.stringify({ task_id: entityId })
|
||||
: JSON.stringify({ reward_id: entityId })
|
||||
await fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body,
|
||||
})
|
||||
} else {
|
||||
// body tap — focus or open the app at the deep link
|
||||
const deepLink = `/parent/${childId}?scrollTo=${entityId}&entityType=${entityType}`
|
||||
const clients = await self.clients.matchAll({ type: 'window', includeUncontrolled: true })
|
||||
if (clients[0]) {
|
||||
await clients[0].focus()
|
||||
await clients[0].navigate(deepLink)
|
||||
} else {
|
||||
await self.clients.openWindow(deepLink)
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Invoke from a Playwright spec after the SW is registered and controlling the page:
|
||||
|
||||
```ts
|
||||
await page.evaluate(
|
||||
({ childId, entityId, entityType, action }) => {
|
||||
navigator.serviceWorker.controller?.postMessage({
|
||||
type: '__TEST_NOTIFICATION_CLICK__',
|
||||
action,
|
||||
childId,
|
||||
entityId,
|
||||
entityType,
|
||||
})
|
||||
},
|
||||
{ childId, entityId, entityType, action: 'approve' },
|
||||
)
|
||||
```
|
||||
|
||||
This allows a spec to simulate notification action clicks without OS interaction, enabling API verification and navigation assertions on top of the mock.
|
||||
|
||||
#### Manual QA Checklist (no spec file — document only)
|
||||
|
||||
- Parent tab active: SSE in-app toast appears; no OS push notification is sent
|
||||
- Parent tab in background or closed: OS push notification appears showing the child name and chore/reward name
|
||||
- Notification does NOT appear for a reward request when the child cannot afford the reward
|
||||
- Notification shows "Approve" and "Deny" action buttons on Chrome desktop; no action buttons on iOS Safari (fallback: body tap only)
|
||||
- Clicking "Approve" on a chore notification calls `POST approve-chore`, dismisses the notification tray entry, and does NOT open or focus the app
|
||||
- Clicking "Deny" on a chore notification calls `POST reject-chore`, dismisses, no app focus
|
||||
- Clicking "Approve" on a reward notification calls `POST trigger-reward`, dismisses, no app focus
|
||||
- Clicking "Deny" on a reward notification calls `POST deny-reward-request`, dismisses, no app focus
|
||||
- Tapping the chore notification body opens (or focuses) the app at `/parent/<childId>?scrollTo=<choreId>&entityType=chore` with the chore card scrolled into view
|
||||
- Tapping the reward notification body opens/focuses the app at `/parent/<childId>?scrollTo=<rewardId>&entityType=reward` with the reward card scrolled into view
|
||||
- If the app is already open in another tab, tapping the notification body focuses the existing tab and navigates it — a second tab is NOT opened
|
||||
- iOS Safari fall-back: notification body tap still navigates correctly to the deep link; no action buttons are rendered
|
||||
- After the app tab is fully closed and a push notification arrives and is tapped, the app correctly opens at the deep-link URL
|
||||
|
||||
---
|
||||
|
||||
### 10. User Profile Notification Settings
|
||||
|
||||
**File:** `e2e/mode_parent/notifications/user-profile-notification-settings.spec.ts`
|
||||
|
||||
**Seed:** No child/task setup required. Tests navigate directly to the User Profile page. No `afterAll` cleanup beyond any push subscription state left by the tests (handled internally by `beforeEach`/`afterEach` where needed).
|
||||
|
||||
#### 10.1. Email Digest toggle is visible on User Profile page
|
||||
|
||||
- Navigate to `/parent/profile` (or the route that renders `UserProfile.vue`)
|
||||
- expect: An element with the label text "Daily Digest" is visible
|
||||
|
||||
#### 10.2. Email Digest toggle initialises from the server value
|
||||
|
||||
- Via authenticated `request`, call `PUT /api/user/profile` with `{ email_digest_enabled: true }` to set a known state
|
||||
- Navigate to `/parent/profile`
|
||||
- expect: The Daily Digest toggle is checked/on
|
||||
- Via authenticated `request`, call `PUT /api/user/profile` with `{ email_digest_enabled: false }`
|
||||
- Reload the page
|
||||
- expect: The Daily Digest toggle is unchecked/off
|
||||
|
||||
#### 10.3. Toggling Email Digest off calls the correct API
|
||||
|
||||
- Via `request`, ensure `email_digest_enabled` is `true` before the test
|
||||
- Navigate to `/parent/profile`
|
||||
- Intercept `PUT /api/user/profile` with `page.route()`
|
||||
- Click the Daily Digest toggle to switch it off
|
||||
- expect: Intercepted PUT body contains `email_digest_enabled: false`
|
||||
- expect: Toggle visually reflects the off state
|
||||
|
||||
#### 10.4. Toggling Email Digest on calls the correct API
|
||||
|
||||
- Via `request`, ensure `email_digest_enabled` is `false` before the test
|
||||
- Navigate to `/parent/profile`
|
||||
- Intercept `PUT /api/user/profile` with `page.route()`
|
||||
- Click the Daily Digest toggle to switch it on
|
||||
- expect: Intercepted PUT body contains `email_digest_enabled: true`
|
||||
- expect: Toggle visually reflects the on state
|
||||
|
||||
#### 10.5. Push Notifications toggle is visible on User Profile page
|
||||
|
||||
- Navigate to `/parent/profile`
|
||||
- expect: An element with the label text "Push Notifications" is visible
|
||||
|
||||
#### 10.6. Push Notifications toggle initialises to off when no subscription exists
|
||||
|
||||
- Do NOT call `page.context().grantPermissions(['notifications'])` (leave default denied/prompt)
|
||||
- Navigate to `/parent/profile`
|
||||
- expect: The Push Notifications toggle is unchecked/off
|
||||
|
||||
#### 10.8. Push Notifications toggle is disabled when browser permission is denied
|
||||
|
||||
- Call `page.context().clearPermissions()` then navigate to `/parent/profile` with notifications permission absent (or set to `'denied'` if Playwright supports it)
|
||||
- expect: The Push Notifications toggle input is disabled (`aria-disabled` or `disabled` attribute set)
|
||||
|
||||
---
|
||||
|
||||
## Test Summary
|
||||
|
||||
| # | Group | Spec File(s) | Test Cases |
|
||||
| --- | --------------------------------- | -------------------------------------------- | ---------- |
|
||||
| 1 | Push subscription registration | `push-subscription.spec.ts` | 3 |
|
||||
| 2 | Chore notification — approve flow | `chore-notification-approve.spec.ts` | 8 |
|
||||
| 3 | Chore notification — reject flow | `chore-notification-deny.spec.ts` | 5 |
|
||||
| 4 | Reward notification — grant flow | `reward-notification-approve.spec.ts` | 7 |
|
||||
| 5 | Reward notification — deny flow | `reward-notification-deny.spec.ts` | 6 |
|
||||
| 6 | ParentView deep-link navigation | `deep-link-navigation.spec.ts` | 7 |
|
||||
| 7 | Digest token — happy paths | 4 spec files (one per `entity×action`) | 8 |
|
||||
| 8 | Digest token — error paths | `digest-action-errors.spec.ts` | 5 |
|
||||
| 9 | SW push — manual/mock | (no spec file) | checklist |
|
||||
| 10 | User Profile notification toggles | `user-profile-notification-settings.spec.ts` | 7 |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- A backend test-only endpoint `POST /api/admin/test/digest-token` (guarded by `DB_ENV=e2e`) is a **prerequisite** for scenario groups 7 and 8 to work.
|
||||
- The SW mock strategy in group 9 requires a `__TEST_NOTIFICATION_CLICK__` message handler added to `sw.js` (localhost-only, not present in production builds).
|
||||
- The `playwright.config.ts` catch-all project must get `e2e/mode_parent/notifications/` added to its `testIgnore` list to prevent double-running.
|
||||
- Tests 1.1, 1.2, 10.7, 10.9, and 10.10 were removed: all required `PushManager.subscribe()` which is unavailable in headless Chromium without a VAPID key. The features they covered work correctly in a real browser; these scenarios are verified via manual QA (see §9).
|
||||
65
frontend/e2e/plans/parent-profile-button.plan.md
Normal file
65
frontend/e2e/plans/parent-profile-button.plan.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Parent Profile Button
|
||||
|
||||
## Application Overview
|
||||
|
||||
In parent mode, the top right of the screen should display the parent's profile icon. When clicking this icon, a menu should appear giving options to select.
|
||||
This is a parent mode only test, and probably should be in it's own bucket of tests
|
||||
|
||||
**Profile Button Badge:**
|
||||
|
||||
- There are 2 modes for the parent profile button in parent mode: Temporary access and Permanent access.
|
||||
- In temporary mode, there is no badge overlaying the profile icon button. This mode will revert back to child mode after after 1 minute or a reload of the site (see feat-parent-mode-expire.md)
|
||||
- In permanent mode, there should be a lock emoji badge over the icon button. This lets the user know that parent mode is active for 2 days. Reloading the page still keeps permanent mode. (see feat-parent-mode-expire.md)
|
||||
|
||||
**Profile Button Menu Options:**
|
||||
When clicking the profile button, we should see the user's selected profile picture, name, and email. Verify those are present. Under this are options.
|
||||
|
||||
1. Profile
|
||||
- This option should take the user to the Edit Profile page. We just need to verify that page is there, testing the page will be for another plan.
|
||||
2. Child Mode
|
||||
- Clicking this will take us out of parent mode and back to child mode. This can be verified by no longer having the view-selector navigation tab at the top-middle of the screen.
|
||||
- In Child mode, clicking the profile button again should prompt us for the parent PIN.
|
||||
- Entering the correct PIN will take us back into temporary parent mode. It will take us back into permanent parent mode if the checkbox is selected during PIN entry.
|
||||
3. Sign Out
|
||||
- This will take us back to the landing page.
|
||||
|
||||
---
|
||||
|
||||
## Implementation
|
||||
|
||||
### Constants
|
||||
|
||||
`E2E_FIRST_NAME` and `STORAGE_STATE_TEMP_PARENT` were added to `e2e/e2e-constants.ts`. `E2E_FIRST_NAME` holds the first name of the seeded test user and is used to verify the name appears in the profile menu.
|
||||
|
||||
### Auth Setup
|
||||
|
||||
`e2e/auth-temp-parent.setup.ts` was created to produce a temporary parent mode storage state (logs in and enters PIN without checking "Stay in parent mode"). It depends on `setup` so it runs after the database is seeded.
|
||||
|
||||
### Playwright Projects (`playwright.config.ts`)
|
||||
|
||||
Two new isolated projects were added:
|
||||
|
||||
- **`chromium-profile-button`** — uses the standard permanent parent mode storage state. Runs all spec files under `e2e/mode_parent/profile-button/`.
|
||||
- **`chromium-profile-button-temp`** — uses the temporary parent mode storage state. Runs the temporary-mode badge spec only.
|
||||
|
||||
Both projects are excluded from the catch-all `chromium-tasks-rewards` project.
|
||||
|
||||
### Test Files
|
||||
|
||||
**`e2e/mode_parent/profile-button/profile-button.spec.ts`** ✅ IMPLEMENTED
|
||||
|
||||
Covers all permanent parent mode scenarios (7 tests):
|
||||
|
||||
1. **Badge – 🔒 visible in permanent mode** ✅
|
||||
2. **Menu – shows user name and email** ✅
|
||||
3. **Menu → Profile navigates to User Profile page** ✅
|
||||
4. **Menu → Child Mode exits parent mode** ✅ (view-selector disappears, button label changes to "Parent login")
|
||||
5. **Menu → Child Mode re-entry without checkbox enters temporary mode** ✅ (no 🔒 badge after re-entry)
|
||||
6. **Menu → Child Mode re-entry with checkbox enters permanent mode** ✅ (🔒 badge visible after re-entry)
|
||||
7. **Menu → Sign Out navigates to landing page** ✅ ("Sign In" nav button visible)
|
||||
|
||||
**`e2e/mode_parent/profile-button/profile-button-temporary.spec.ts`** ✅ IMPLEMENTED
|
||||
|
||||
Covers the temporary parent mode badge scenario (1 test):
|
||||
|
||||
1. **Badge – no 🔒 badge in temporary mode** ✅ (enters temp mode by exiting to child mode then re-entering PIN without "Stay" checkbox)
|
||||
113
frontend/e2e/plans/parent-rewards-management.plan.md
Normal file
113
frontend/e2e/plans/parent-rewards-management.plan.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Parent Rewards Management
|
||||
|
||||
## Application Overview
|
||||
|
||||
Focus on parent-mode flows interacting with the Rewards section. A parent must be authenticated via PIN and can create, cancel, edit, delete, and manage default rewards. The scenarios mirror the chore/kindness/penalty plans but target `/parent/rewards` and the reward-specific fields (name, cost, description). All tests run under parent mode in the `mode_parent/rewards` directory. Unique names use a `${Date.now()}` suffix to avoid collisions.
|
||||
|
||||
## Test Scenarios
|
||||
|
||||
### 1. Create and manage rewards
|
||||
|
||||
**Seed:** `e2e/seed.spec.ts`
|
||||
|
||||
#### 1.1. Create a new reward
|
||||
|
||||
**File:** `e2e/mode_parent/rewards/reward-create-edit.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
1. -
|
||||
- expect: Navigate to `/parent/rewards` and verify the default reward list loads (some reward name visible)
|
||||
2. Click the 'Create Reward' FAB
|
||||
- expect: The create form appears with 'Reward Name' and 'Cost' fields
|
||||
- expect: Create button is disabled initially
|
||||
3. Enter a unique name (e.g. `Toy car ${suffix}`) and `20` in Cost via DOM events
|
||||
- expect: Create button becomes enabled
|
||||
4. Click the Create button
|
||||
- expect: No `.error` elements are shown
|
||||
- expect: The new reward appears in the list
|
||||
|
||||
#### 1.2. Cancel reward creation
|
||||
|
||||
**File:** `e2e/mode_parent/rewards/reward-cancel.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
1. -
|
||||
- expect: Navigate to `/parent/rewards` and click 'Create Reward'
|
||||
- expect: Create form is displayed with name label visible
|
||||
2. Fill 'Test' for name and '5' for cost then click 'Cancel'
|
||||
- expect: User is returned to the rewards list
|
||||
- expect: No element with text 'Test' exists
|
||||
|
||||
#### 1.3. Edit an existing reward
|
||||
|
||||
**File:** `e2e/mode_parent/rewards/reward-create-edit.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
1. -
|
||||
- expect: Navigate to `/parent/rewards`; create `Toy car ${suffix}` if absent
|
||||
- expect: The reward appears in the list
|
||||
2. Click the reward row to open edit form
|
||||
- expect: Edit form appears with 'Reward Name' label visible
|
||||
3. Change the name to `Toy boat ${suffix}` and cost to '25' via DOM events
|
||||
- expect: Save button becomes enabled
|
||||
4. Click Save
|
||||
- expect: No errors are displayed
|
||||
- expect: Updated reward name appears in the list
|
||||
|
||||
#### 1.4. Cancel reward edit
|
||||
|
||||
**File:** `e2e/mode_parent/rewards/reward-cancel.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
1. -
|
||||
- expect: Navigate to `/parent/rewards`; ensure `Toy car` exists; click its row
|
||||
- expect: Edit form is displayed
|
||||
2. Change the name to 'Should not save' via DOM events then click 'Cancel'
|
||||
- expect: Rewards list visible again
|
||||
- expect: `Toy car` is still present
|
||||
- expect: `Should not save` does not appear
|
||||
|
||||
#### 1.5. Convert a default reward to a user item by editing
|
||||
|
||||
**File:** `e2e/mode_parent/rewards/reward-convert-default.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
1. -
|
||||
- expect: Navigate to `/parent/rewards` and locate a default reward such as 'Choose meal'
|
||||
- expect: Item is visible with no delete button
|
||||
2. Click the row to open edit form
|
||||
- expect: Edit form opens with `input#name` value matching the default
|
||||
3. Change the name to 'Choose meal (custom)' and cost to '35'
|
||||
- expect: Save button becomes enabled
|
||||
4. Click Save
|
||||
- expect: Row now shows a visible delete button
|
||||
|
||||
#### 1.6. Delete a reward
|
||||
|
||||
**File:** `e2e/mode_parent/rewards/reward-create-edit.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
1. -
|
||||
- expect: Navigate to `/parent/rewards`; create a unique reward if necessary
|
||||
- expect: The reward appears in the list with a delete control
|
||||
2. Click the delete icon and confirm the dialog
|
||||
- expect: Item is removed from the list
|
||||
|
||||
#### 1.7. Edit default reward cost and verify system restoration on delete
|
||||
|
||||
**File:** `e2e/mode_parent/rewards/reward-delete-default.spec.ts`
|
||||
|
||||
**Steps:**
|
||||
1. -
|
||||
- expect: Navigate to `/parent/rewards`; clean up any user-copy of 'Choose meal' from prior runs
|
||||
- expect: Verify default 'Choose meal' is present with no delete icon
|
||||
2. Click 'Choose meal' to open edit form; confirm input#name reads 'Choose meal'; change cost to '50' via DOM events
|
||||
- expect: Save button becomes enabled
|
||||
3. Click Save
|
||||
- expect: Exactly one 'Choose meal' row exists
|
||||
- expect: Delete icon now visible on that row
|
||||
- expect: Cost display reads '50 pts'
|
||||
4. Click delete icon and confirm
|
||||
- expect: 'Choose meal' remains in list (default restored)
|
||||
- expect: No delete icon present
|
||||
- expect: Cost display returns to original value
|
||||
72
frontend/e2e/plans/task-activated.plan.md
Normal file
72
frontend/e2e/plans/task-activated.plan.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Parent Activate Task Points, and Reward Redemption, Flow
|
||||
|
||||
## Application Overview
|
||||
|
||||
In parent mode, with a populated list of chores, kindness acts, penalties, and rewards, tests must be run to activate tasks to give a child points, and redeem rewards (remove child points.)
|
||||
|
||||
- When the parent activates a chore, a confimation box should appear. Both "Yes" and "Cancel" should be tested. "Yes" will give points to the child. The activated chore should then be disabled and showing the text "Completed" If this chore is activated again, nothing happens.
|
||||
- When the parent activates a kindness act, a confimation box should appear. Both "Yes" and "Cancel" should be tested. "Yes" will give points to the child. Kindness acts can be activated more than once.
|
||||
- When the parent activates a negitive task (penalty), a confimation box should appear. Both "Yes" and "Cancel" should be tested. "Yes" will remove points from the child. The points can not be negitive. Penalties can be activated more than once.
|
||||
- A reward can show how many more points are required to redeem the reward. If a parent activates in this state, nothing happens. When a point can be redeemed, it will have a green text stating that it can be redeemed. When a parent activates this a confirmation dialog appears. Both "Yes" and "Cancel" should be tested. "Yes" removes points from the child. This will cause the rewards status text to be changed in most cases to show how many points are required to redeem.
|
||||
- Flow: The rows for chores, kindness acts, penalties, and rewards are scrollable. If there are many items in a row, the user can scroll through them.
|
||||
- Activation: Activation happens with 2 clicks. The first click will center an item in the scroll box, and the next click will activate the item.
|
||||
- Kebab and Edit button: When a chore is clicked, the top right of the chore will show a kebab icon, when the chore is activated or when somewhere else is clicked, that kebab menu will disappear. No need to test the kebab items yet. On kindness acts, penalties, and rewards, an edit icon will appear in the top right. The behavior is the same as the kebab menu. No need to test what the edit button does yet.
|
||||
- Points will be displayed in a child card at the top of the screen, as well as the child's name, age, and profile icon.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Status
|
||||
|
||||
All tests live in `e2e/mode_parent/task-activation/` and run under the `chromium-task-activation` Playwright project. **38 tests total — all passing.**
|
||||
|
||||
### chore-activation.spec.ts ✅
|
||||
|
||||
- Confirmation dialog appears on chore click
|
||||
- Cancelling the dialog leaves points unchanged
|
||||
- Confirming the dialog awards the chore's points to the child
|
||||
- Completed chore shows "Completed" text and is disabled
|
||||
- Clicking a completed chore a second time does nothing (no dialog, no extra points)
|
||||
- Kebab menu disappears when a different chore card is clicked
|
||||
|
||||
### kindness-activation.spec.ts ✅
|
||||
|
||||
- Confirmation dialog appears on kindness act click
|
||||
- Cancelling the dialog leaves points unchanged
|
||||
- Confirming the dialog awards points and kindness acts can be triggered more than once (points accumulate)
|
||||
- Edit button appears on the first click of a kindness card
|
||||
- Edit button disappears when a different kindness card is clicked
|
||||
|
||||
### penalty-activation.spec.ts ✅
|
||||
|
||||
- Confirmation dialog appears on penalty click
|
||||
- Cancelling the dialog leaves points unchanged
|
||||
- Confirming the dialog deducts points from the child
|
||||
- Points cannot go below zero (floor at 0)
|
||||
- Penalties can be activated more than once
|
||||
- Edit button disappears when a different penalty card is clicked
|
||||
|
||||
### reward-redemption.spec.ts ✅
|
||||
|
||||
- Reward shows a "points required" status when the child does not have enough points
|
||||
- Clicking a non-redeemable reward opens no dialog (nothing happens)
|
||||
- Reward shows a "REWARD READY" status when the child has enough points
|
||||
- Edit button appears on the first click of a ready reward card
|
||||
- Edit button disappears when a different reward card is clicked
|
||||
- Cancelling the redemption dialog leaves points unchanged
|
||||
- Confirming the dialog deducts the reward cost from the child's points and updates the status text
|
||||
|
||||
### scroll-and-flow.spec.ts ✅
|
||||
|
||||
- Kindness list is horizontally scrollable when many items are assigned (content width exceeds the viewport and the last card is off-screen)
|
||||
- Scrolling the list reveals hidden items (last card enters the viewport; first card leaves it)
|
||||
- First click on an off-screen item scrolls it into view and marks it ready (the `centerItem` animation completes and the card is visible within the viewport)
|
||||
- Scrolling does not prevent first-click centering on a partially visible item (pre-scrolled list recenters on click)
|
||||
|
||||
### pending-reward-warning.spec.ts ✅
|
||||
|
||||
- Reward card shows a "PENDING" banner when a child has requested that reward
|
||||
- Activating a chore while a reward is pending shows a warning dialog; cancelling leaves points and the pending request unchanged
|
||||
- Activating a chore while a reward is pending shows a warning dialog; confirming cancels the pending request and proceeds to the normal task confirmation
|
||||
- Activating a kindness act while a reward is pending shows a warning dialog; both cancel and confirm paths verified
|
||||
- Activating a penalty while a reward is pending shows a warning dialog; both cancel and confirm paths verified
|
||||
- Activating a different ready reward while a reward request is pending shows a warning dialog; cancelling preserves the pending request; confirming cancels it (the reward confirm dialog does not auto-open — the user must re-click)
|
||||
149
frontend/e2e/plans/task-assignment.plan.md
Normal file
149
frontend/e2e/plans/task-assignment.plan.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Parent Assign Chores, Kindness Acts, Penalties, and Rewards to a Child
|
||||
|
||||
## Application Overview
|
||||
|
||||
Tests for the parent-mode flows where a parent assigns task items (chores, kindness acts, penalties) and rewards to a specific child. Each suite navigates from the child's ParentView (`/parent/:id`) to the relevant assign view and verifies that selections are saved and reflected back in the ParentView.
|
||||
|
||||
The parent is authenticated via stored auth state (`e2e/.auth/user.json`). Each spec creates its own test child and items via the API so it is fully isolated from parallel test projects that mutate seed data. All created resources are deleted in `afterAll`. Tests run in `serial` mode within each suite to preserve assignment state across the sequence.
|
||||
|
||||
## Playwright Project
|
||||
|
||||
**Project name:** `chromium-task-assignment`
|
||||
**Config:** `playwright.config.ts`
|
||||
**testMatch:** `/mode_parent/task-assignment/.+\.spec\.ts/`
|
||||
**Dependencies:** `setup`
|
||||
**Excluded from:** `chromium-tasks-rewards` (via `testIgnore`)
|
||||
|
||||
---
|
||||
|
||||
## Test Scenarios
|
||||
|
||||
### 1. Chore Assignment ✅ IMPLEMENTED
|
||||
|
||||
**File:** `e2e/mode_parent/task-assignment/chore-assignment.spec.ts`
|
||||
|
||||
**Seed:** Creates child `AssignChoreChild` and tasks `AssignTestChore1/2/3` (type `chore`) via API in `beforeAll`. Deletes all in `afterAll`.
|
||||
|
||||
#### 1.1. "Assign Chores" button navigates to the chore assign view ✅
|
||||
|
||||
- Click `Assign Chores` from the child's ParentView
|
||||
- expect: navigates to `/parent/:id/assign-chores` with the heading and test chores listed; nothing pre-checked on a fresh child
|
||||
|
||||
#### 1.2. Select chores and submit — items appear in ParentView ✅
|
||||
|
||||
- Select `AssignTestChore1` and `AssignTestChore2`, click `Submit`
|
||||
- expect: returns to ParentView; both chores are visible
|
||||
|
||||
#### 1.3. Re-opening assign view shows assigned items as pre-checked ✅
|
||||
|
||||
- Navigate back to the assign-chores view
|
||||
- expect: `AssignTestChore1` and `AssignTestChore2` are pre-checked; `AssignTestChore3` is not
|
||||
|
||||
#### 1.4. Uncheck all chores and submit — items are removed from ParentView ✅
|
||||
|
||||
- Uncheck both assigned chores, click `Submit`
|
||||
- expect: returns to ParentView; neither chore is visible
|
||||
|
||||
#### 1.5. Cancel does not persist selection changes ✅
|
||||
|
||||
- Check `AssignTestChore3`, click `Cancel`, then navigate back to the view
|
||||
- expect: `AssignTestChore3` is still unchecked
|
||||
|
||||
---
|
||||
|
||||
### 2. Kindness Act Assignment ✅ IMPLEMENTED
|
||||
|
||||
**File:** `e2e/mode_parent/task-assignment/kindness-assignment.spec.ts`
|
||||
|
||||
**Seed:** Creates child `AssignKindnessChild` and task `AssignTestKindness1` (type `kindness`) via API in `beforeAll`. Deletes all in `afterAll`.
|
||||
|
||||
#### 2.1. "Assign Kindness Acts" button navigates to the kindness assign view ✅
|
||||
|
||||
- Click `Assign Kindness Acts` from the child's ParentView
|
||||
- expect: navigates to `/parent/:id/assign-kindness` with the heading and `AssignTestKindness1` listed and unchecked
|
||||
|
||||
#### 2.2. Select a kindness act and submit — item appears in ParentView ✅
|
||||
|
||||
- Select `AssignTestKindness1`, click `Submit`
|
||||
- expect: returns to ParentView; item is visible
|
||||
|
||||
#### 2.3. Re-opening assign view shows the assigned item as pre-checked ✅
|
||||
|
||||
- Navigate back to the assign-kindness view
|
||||
- expect: `AssignTestKindness1` is pre-checked
|
||||
|
||||
#### 2.4. Uncheck the kindness act and submit — item is removed from ParentView ✅
|
||||
|
||||
- Uncheck `AssignTestKindness1`, click `Submit`
|
||||
- expect: returns to ParentView; item is no longer visible
|
||||
|
||||
#### 2.5. Cancel does not persist selection changes ✅
|
||||
|
||||
- Check `AssignTestKindness1`, click `Cancel`, then navigate back to the view
|
||||
- expect: `AssignTestKindness1` is still unchecked
|
||||
|
||||
---
|
||||
|
||||
### 3. Penalty Assignment ✅ IMPLEMENTED
|
||||
|
||||
**File:** `e2e/mode_parent/task-assignment/penalty-assignment.spec.ts`
|
||||
|
||||
**Seed:** Creates child `AssignPenaltyChild` and tasks `AssignTestPenalty1/2/3` (type `penalty`) via API in `beforeAll`. Deletes all in `afterAll`.
|
||||
|
||||
#### 3.1. "Assign Penalties" button navigates to the penalty assign view ✅
|
||||
|
||||
- Click `Assign Penalties` from the child's ParentView
|
||||
- expect: navigates to `/parent/:id/assign-penalties` with the heading and test penalties listed and unchecked
|
||||
|
||||
#### 3.2. Select penalties and submit — items appear in ParentView ✅
|
||||
|
||||
- Select `AssignTestPenalty1` and `AssignTestPenalty2`, click `Submit`
|
||||
- expect: returns to ParentView; both penalties are visible
|
||||
|
||||
#### 3.3. Re-opening assign view shows assigned penalties as pre-checked ✅
|
||||
|
||||
- Navigate back to the assign-penalties view
|
||||
- expect: `AssignTestPenalty1` and `AssignTestPenalty2` are pre-checked; `AssignTestPenalty3` is not
|
||||
|
||||
#### 3.4. Uncheck all penalties and submit — items are removed from ParentView ✅
|
||||
|
||||
- Uncheck both assigned penalties, click `Submit`
|
||||
- expect: returns to ParentView; neither penalty is visible
|
||||
|
||||
#### 3.5. Cancel does not persist selection changes ✅
|
||||
|
||||
- Check `AssignTestPenalty3`, click `Cancel`, then navigate back to the view
|
||||
- expect: `AssignTestPenalty3` is still unchecked
|
||||
|
||||
---
|
||||
|
||||
### 4. Reward Assignment ✅ IMPLEMENTED
|
||||
|
||||
**File:** `e2e/mode_parent/task-assignment/reward-assignment.spec.ts`
|
||||
|
||||
**Seed:** Creates child `AssignRewardChild` and rewards `AssignTestReward1/2/3` (cost 10) via API in `beforeAll`. Deletes all in `afterAll`.
|
||||
|
||||
#### 4.1. "Assign Rewards" button navigates to the reward assign view ✅
|
||||
|
||||
- Click `Assign Rewards` from the child's ParentView
|
||||
- expect: navigates to `/parent/:id/assign-rewards` with the heading and test rewards listed and unchecked
|
||||
|
||||
#### 4.2. Select rewards and submit — items appear in ParentView ✅
|
||||
|
||||
- Select `AssignTestReward1` and `AssignTestReward2`, click `Submit`
|
||||
- expect: returns to ParentView; both rewards are visible
|
||||
|
||||
#### 4.3. Re-opening assign view shows assigned rewards as pre-checked ✅
|
||||
|
||||
- Navigate back to the assign-rewards view
|
||||
- expect: `AssignTestReward1` and `AssignTestReward2` are pre-checked; `AssignTestReward3` is not
|
||||
|
||||
#### 4.4. Uncheck all rewards and submit — items are removed from ParentView ✅
|
||||
|
||||
- Uncheck both assigned rewards, click `Submit`
|
||||
- expect: returns to ParentView; neither reward is visible
|
||||
|
||||
#### 4.5. Cancel does not persist selection changes ✅
|
||||
|
||||
- Check `AssignTestReward3`, click `Cancel`, then navigate back to the view
|
||||
- expect: `AssignTestReward3` is still unchecked
|
||||
239
frontend/e2e/plans/task-modified.plan.md
Normal file
239
frontend/e2e/plans/task-modified.plan.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# Parent Activate Modifies Task (changing points and resetting completed tasks)
|
||||
|
||||
## Application Overview
|
||||
|
||||
In parent mode, with a populated list of chores, kindness acts, penalties, and rewards, tests must be run to make sure a chore has a kebab menu that lets a parent make modifications to the chore. For kindness acts, penalties, and rewards, parents should be able to click on the edit button and change the amount of points or cost of the item.
|
||||
|
||||
This is a continuation of tests from task-assignment.plan.md
|
||||
|
||||
**Chores:**
|
||||
|
||||
- When the parent clicks on a chore's kebab menu, there should be an item that allows the parent to "Edit Points"
|
||||
- When the parent clicks on a chore's kebab menu with the chore already in the COMPLETED state, there should be a "Reset" option.
|
||||
- When a chore's "Edit Points" are clicked, a new dialog should appear with the chore's title, icon, and point value. These points should be editable. The value should not be negitive. If cancelled, nothing happens. If Save is clicked, the chore's point value should match what was entered. There should also be a star emoji that appears on the chore's card on the top right. The maximum value for the points should be 10000. Anything higher, the save button should disable.
|
||||
- Clicking outside the points dialog should not close it. It should be modal.
|
||||
- When the parent clicks "Reset" from a completed chore. That chore should become active again. The completed banner should be gone and the chore can be activated again.
|
||||
- Everytime a resetted chore is activated it should go back to the completed state until reset again.
|
||||
|
||||
**Kindness Acts, Penalties, Rewards**
|
||||
|
||||
- When the parent clicks on these item's edit button, a new dialog should appear with the item's title, icon, and point/cost value. These points/costs should be editable. The value should not be negitive. If cancelled, nothing happens. If Save is clicked, the item's point value should match what was entered. There should also be a star emoji that appears on the chore's card on the top right. The maximum value for the points should be 10000. Anything higher, the save button should disable.
|
||||
|
||||
**Other Tests:**
|
||||
When a point/cost value is changed, make sure the new value take affect when the parent activates the task. This testing is done in task-assignment.plan.md, but we want to verify the new point values work. There may be some test overlap.
|
||||
|
||||
---
|
||||
|
||||
## Implementation
|
||||
|
||||
Tests are in `e2e/mode_parent/task-modification/`. Each spec creates its own isolated child via the API so all 6 files run in parallel under the `chromium-task-modification` project.
|
||||
|
||||
## Playwright Project
|
||||
|
||||
**Project name:** `chromium-task-modification`
|
||||
**Config:** `playwright.config.ts`
|
||||
**testMatch:** `/mode_parent/task-modification/.+\.spec\.ts/`
|
||||
**Dependencies:** `setup`
|
||||
**Excluded from:** `chromium-tasks-rewards` (via `testIgnore`)
|
||||
|
||||
---
|
||||
|
||||
## Test Scenarios
|
||||
|
||||
### 1. Chore Edit Points ✅ IMPLEMENTED
|
||||
|
||||
**File:** `e2e/mode_parent/task-modification/chore-edit-points.spec.ts`
|
||||
|
||||
**Seed:** Creates an isolated child and a chore task via API in `beforeAll`. Deletes all in `afterAll`.
|
||||
|
||||
#### 1.1. Kebab button visibility ✅
|
||||
|
||||
- Kebab button is hidden initially; becomes visible after the chore card is clicked; hides again when a different card is clicked
|
||||
|
||||
#### 1.2. Kebab menu contents ✅
|
||||
|
||||
- Menu contains "Edit Points" and "Schedule" options
|
||||
|
||||
#### 1.3. Override modal opens pre-populated ✅
|
||||
|
||||
- "Edit Points" opens the override modal with the current point value already filled in
|
||||
|
||||
#### 1.4. Modal is blocking ✅
|
||||
|
||||
- Clicking outside the modal does not close it
|
||||
|
||||
#### 1.5. Cancel does not save ✅
|
||||
|
||||
- Card still shows original points after Cancel; no ⭐ badge appears
|
||||
|
||||
#### 1.6. Save updates displayed points ✅
|
||||
|
||||
- Saving a valid new value updates the point display on the card
|
||||
|
||||
#### 1.7. ⭐ override badge appears after save ✅
|
||||
|
||||
- ⭐ badge appears on the card after saving a custom value
|
||||
|
||||
#### 1.8. ⭐ badge persists on revert ✅
|
||||
|
||||
- ⭐ badge remains even when the value is changed back to the original default
|
||||
|
||||
#### 1.9. Validation — negative and over-limit ✅
|
||||
|
||||
- Negative value disables the Save button; value above 10000 disables Save; exactly 10000 enables it
|
||||
|
||||
#### 1.10. Zero is valid ✅
|
||||
|
||||
- Card shows "0 Points" with ⭐ badge when zero is saved
|
||||
|
||||
---
|
||||
|
||||
### 2. Chore Reset ✅ IMPLEMENTED
|
||||
|
||||
**File:** `e2e/mode_parent/task-modification/chore-reset.spec.ts`
|
||||
|
||||
**Seed:** Creates an isolated child and a chore task via API in `beforeAll`. Activates and confirms the chore as needed per test. Deletes all in `afterAll`.
|
||||
|
||||
#### 2.1. "Reset" absent before completion ✅
|
||||
|
||||
- "Reset" is not present in the kebab menu when the chore is not yet completed
|
||||
|
||||
#### 2.2. COMPLETED stamp after activation ✅
|
||||
|
||||
- Chore shows COMPLETED stamp after activation and parent confirmation
|
||||
|
||||
#### 2.3. "Reset" appears after completion ✅
|
||||
|
||||
- "Reset" appears in the kebab menu once the chore is in COMPLETED state
|
||||
|
||||
#### 2.4. Reset removes COMPLETED stamp ✅
|
||||
|
||||
- Clicking "Reset" removes the COMPLETED stamp and returns the chore to active
|
||||
|
||||
#### 2.5. Chore is re-activatable after reset ✅
|
||||
|
||||
- After reset, the chore can be activated again and returns to COMPLETED on confirmation
|
||||
|
||||
#### 2.6. Reset → activate cycle is repeatable ✅
|
||||
|
||||
- The reset and re-activate cycle can be repeated multiple times without error
|
||||
|
||||
---
|
||||
|
||||
### 3. Kindness Act Edit Points ✅ IMPLEMENTED
|
||||
|
||||
**File:** `e2e/mode_parent/task-modification/kindness-edit-points.spec.ts`
|
||||
|
||||
**Seed:** Creates an isolated child and two kindness act tasks via API in `beforeAll`. Deletes all in `afterAll`.
|
||||
|
||||
#### 3.1. Edit button visibility ✅
|
||||
|
||||
- Edit button appears on first click of a kindness card; disappears when a different card is clicked
|
||||
|
||||
#### 3.2. Override modal opens pre-populated ✅
|
||||
|
||||
- Edit button opens modal with the kindness name, "Assign new points" subtitle, and current point value filled in
|
||||
|
||||
#### 3.3. Cancel does not save ✅
|
||||
|
||||
- Points remain unchanged and no ⭐ badge appears after Cancel
|
||||
|
||||
#### 3.4. Save updates displayed points and badge ✅
|
||||
|
||||
- Saving a new value updates the displayed points and shows ⭐ badge
|
||||
|
||||
#### 3.5. Validation — negative and over-limit ✅
|
||||
|
||||
- Negative value disables the Save button; value above 10000 disables Save; exactly 10000 enables it
|
||||
|
||||
---
|
||||
|
||||
### 4. Penalty Edit Points ✅ IMPLEMENTED
|
||||
|
||||
**File:** `e2e/mode_parent/task-modification/penalty-edit-points.spec.ts`
|
||||
|
||||
**Seed:** Creates an isolated child and two penalty tasks via API in `beforeAll`. Deletes all in `afterAll`.
|
||||
|
||||
#### 4.1. Edit button visibility ✅
|
||||
|
||||
- Edit button appears on first click of a penalty card; disappears when a different card is clicked
|
||||
|
||||
#### 4.2. Override modal opens pre-populated ✅
|
||||
|
||||
- Edit button opens modal with the penalty name, "Assign new points" subtitle, and the raw (positive) point value filled in — not the negated display value
|
||||
|
||||
#### 4.3. Cancel does not save ✅
|
||||
|
||||
- Display remains unchanged after Cancel
|
||||
|
||||
#### 4.4. Save updates card and badge ✅
|
||||
|
||||
- Saving a new value updates the card to show the negated override value and ⭐ badge
|
||||
|
||||
#### 4.5. Validation — negative and over-limit ✅
|
||||
|
||||
- Negative value disables the Save button; value above 10000 disables Save; exactly 10000 enables it
|
||||
|
||||
---
|
||||
|
||||
### 5. Reward Edit Cost ✅ IMPLEMENTED
|
||||
|
||||
**File:** `e2e/mode_parent/task-modification/reward-edit-cost.spec.ts`
|
||||
|
||||
**Seed:** Creates an isolated child and a reward (with a pending redemption for warning-dialog tests) via API in `beforeAll`. Deletes all in `afterAll`.
|
||||
|
||||
#### 5.1. Edit button visibility ✅
|
||||
|
||||
- Edit button appears on first click of a reward card
|
||||
|
||||
#### 5.2. Override modal opens pre-populated ✅
|
||||
|
||||
- Edit button opens modal with the reward name, "Assign new cost" subtitle, and the current cost filled in
|
||||
|
||||
#### 5.3. Cancel does not save ✅
|
||||
|
||||
- Points needed display remains unchanged after Cancel
|
||||
|
||||
#### 5.4. Save updates cost and badge ✅
|
||||
|
||||
- Saving a new cost updates the points needed display and shows ⭐ badge
|
||||
|
||||
#### 5.5. Zero cost makes reward immediately redeemable ✅
|
||||
|
||||
- Setting cost to 0 marks the reward as immediately redeemable
|
||||
|
||||
#### 5.6. Validation — negative and over-limit ✅
|
||||
|
||||
- Negative value disables the Save button; value above 10000 disables Save; exactly 10000 enables it
|
||||
|
||||
#### 5.7. Warning dialog for pending redemption ✅
|
||||
|
||||
- Editing a reward with an active pending redemption shows a warning dialog; Cancel aborts the edit
|
||||
|
||||
#### 5.8. Confirming warning opens override modal ✅
|
||||
|
||||
- Confirming the warning dialog proceeds to open the override modal normally
|
||||
|
||||
---
|
||||
|
||||
### 6. Modified Points Take Effect on Activation ✅ IMPLEMENTED
|
||||
|
||||
**File:** `e2e/mode_parent/task-modification/modified-points-activation.spec.ts`
|
||||
|
||||
**Seed:** Creates an isolated child, a chore, a kindness act, a penalty, and a reward via API in `beforeAll`. Sets overrides for each. Deletes all in `afterAll`.
|
||||
|
||||
#### 6.1. Modified chore points awarded on activation ✅
|
||||
|
||||
- Child receives the overridden point value (not the default) when the chore is activated and confirmed
|
||||
|
||||
#### 6.2. Modified kindness points awarded on activation ✅
|
||||
|
||||
- Child receives the overridden point value when the kindness act is activated and confirmed
|
||||
|
||||
#### 6.3. Modified penalty points deducted on activation ✅
|
||||
|
||||
- Child loses the overridden point value when the penalty is applied
|
||||
|
||||
#### 6.4. Modified reward cost reflected in display ✅
|
||||
|
||||
- Points needed shown on the reward card reflects the overridden cost value
|
||||
169
frontend/e2e/plans/user-profile.plan.md
Normal file
169
frontend/e2e/plans/user-profile.plan.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# User Profile
|
||||
|
||||
## Application Overview
|
||||
|
||||
The User Profile page at `/parent/profile` allows changing a profile image, first name, and last name. It also provides links to: Change Parent PIN, Change Password, and Delete Account. Save and Cancel buttons control form submission.
|
||||
|
||||
**Email resolution for e2e PIN/password tests:** A test-only backend endpoint (`GET /user/e2e-get-pin-code`) returns the verification code directly. This endpoint is gated to non-production environments. Password reset tests simply verify the email-sent modal appears without inspecting the actual email.
|
||||
|
||||
---
|
||||
|
||||
## Implementation
|
||||
|
||||
Tests are split into three isolated Playwright project buckets, all under `e2e/mode_parent/user-profile/`.
|
||||
|
||||
## Playwright Projects
|
||||
|
||||
| Project | File | Notes |
|
||||
| ------------------------------ | ------------------------- | ------------------------------------------- |
|
||||
| `chromium-user-profile` | `profile-editing.spec.ts` | Restores profile via API in `afterEach` |
|
||||
| `chromium-user-profile-pin` | `change-pin.spec.ts` | Restores original PIN via API in `afterAll` |
|
||||
| `chromium-user-profile-delete` | `delete-account.spec.ts` | Re-seeds entire DB in `afterAll`; run last |
|
||||
|
||||
**Config:** `playwright.config.ts` — all three depend on `setup` only.
|
||||
|
||||
---
|
||||
|
||||
## Test Scenarios
|
||||
|
||||
### 1. Profile Editing ✅ IMPLEMENTED
|
||||
|
||||
**File:** `e2e/mode_parent/user-profile/profile-editing.spec.ts`
|
||||
|
||||
**Seed:** Snapshots profile via API in `beforeEach`; restores it in `afterEach`.
|
||||
|
||||
#### 1.1. Page loads with correct data ✅
|
||||
|
||||
- Profile heading is visible; First Name, Last Name, and Email fields show the seeded e2e values
|
||||
|
||||
#### 1.2. Cancel navigates back ✅
|
||||
|
||||
- Clicking Cancel returns to the previous page without saving
|
||||
|
||||
#### 1.3. Save disabled when form is clean ✅
|
||||
|
||||
- Save button is disabled when no fields have been changed
|
||||
|
||||
#### 1.4. Save disabled when First Name is empty ✅
|
||||
|
||||
- Clearing the First Name field disables the Save button
|
||||
|
||||
#### 1.5. Save disabled when Last Name is empty ✅
|
||||
|
||||
- Clearing the Last Name field disables the Save button
|
||||
|
||||
#### 1.6. Save disabled when both name fields are empty ✅
|
||||
|
||||
- Clearing both fields keeps Save disabled
|
||||
|
||||
#### 1.7. Save enables when a name is changed ✅
|
||||
|
||||
- Editing any name field enables the Save button
|
||||
|
||||
#### 1.8. Save persists name changes ✅
|
||||
|
||||
- After saving, re-opening the profile shows the new values; a "Profile Updated" confirmation modal appears on save
|
||||
|
||||
#### 1.9. Cancel discards unsaved changes ✅
|
||||
|
||||
- Changes made before Cancel are not reflected when the profile is re-opened
|
||||
|
||||
#### 1.10. Email field is read-only ✅
|
||||
|
||||
- The email input is disabled and shows the account email
|
||||
|
||||
#### 1.11. Change profile image (built-in) ✅
|
||||
|
||||
- Selecting an unselected built-in image and saving shows the confirmation modal; image persists on reload
|
||||
|
||||
#### 1.12. Upload a custom profile image ✅
|
||||
|
||||
- Using "Add from device" to upload a local file selects it as the profile image; saving shows the confirmation modal
|
||||
|
||||
#### 1.13. Change Password shows email-sent modal ✅
|
||||
|
||||
- Clicking Change Password shows a loading modal that transitions to a "Password Change Email Sent" confirmation; OK dismisses it and returns to the profile page
|
||||
|
||||
---
|
||||
|
||||
### 2. Change Parent PIN ✅ IMPLEMENTED
|
||||
|
||||
**File:** `e2e/mode_parent/user-profile/change-pin.spec.ts`
|
||||
|
||||
**Seed:** Restores the original e2e PIN via the test API helper in `afterAll`.
|
||||
|
||||
#### 2.1. Change Parent PIN link navigates to PIN setup page ✅
|
||||
|
||||
- Clicking the link navigates to `/parent/pin-setup` and shows the "Set up your Parent PIN" step
|
||||
|
||||
#### 2.2. Back from PIN setup returns to profile ✅
|
||||
|
||||
- Browser back from the PIN setup page returns to `/parent/profile`
|
||||
|
||||
#### 2.3. Send Verification Code advances to step 2 ✅
|
||||
|
||||
- Clicking the button shows the "Enter Verification Code" step with a code input; Verify Code is disabled while the input is empty
|
||||
|
||||
#### 2.4. Resend Code button appears after delay ✅
|
||||
|
||||
- A "Resend Code" button appears within ~10 seconds of reaching step 2
|
||||
|
||||
#### 2.5. Invalid code shows error ✅
|
||||
|
||||
- Submitting a wrong code shows an "invalid code" error message
|
||||
|
||||
#### 2.6. Valid code advances to step 3 (Create PIN) ✅
|
||||
|
||||
- Entering the code from the test endpoint and clicking Verify Code shows the "Create Parent PIN" step with New PIN and Confirm PIN fields; Set PIN is initially disabled
|
||||
|
||||
#### 2.7. Set PIN disabled when PINs do not match ✅
|
||||
|
||||
- Entering different values in both fields keeps Set PIN disabled
|
||||
|
||||
#### 2.8. Set PIN disabled for fewer than 4 digits ✅
|
||||
|
||||
- Entering a 2-digit value in both fields keeps Set PIN disabled
|
||||
|
||||
#### 2.9. Set PIN succeeds with matching 4-digit PIN ✅
|
||||
|
||||
- Entering matching 4-digit values and clicking Set PIN shows the "Parent PIN Set!" success step with a Back button
|
||||
|
||||
#### 2.10. Back from success step exits parent mode ✅
|
||||
|
||||
- Clicking Back on the success step navigates to the child selector (`/child`) and exits parent mode
|
||||
|
||||
---
|
||||
|
||||
### 3. Delete Account ✅ IMPLEMENTED
|
||||
|
||||
**File:** `e2e/mode_parent/user-profile/delete-account.spec.ts`
|
||||
|
||||
**Seed:** Re-seeds the entire e2e database via `POST /auth/e2e-seed` in `afterAll` to restore the test user.
|
||||
|
||||
#### 3.1. Delete My Account opens confirmation dialog ✅
|
||||
|
||||
- Clicking the button shows a "Delete Your Account?" modal with an email confirmation input; Delete is disabled and Cancel is visible
|
||||
|
||||
#### 3.2. Delete button stays disabled for incomplete email ✅
|
||||
|
||||
- Entering a partial email keeps the Delete button disabled
|
||||
|
||||
#### 3.3. Delete button enables when matching email is entered ✅
|
||||
|
||||
- Entering the exact account email enables the Delete button
|
||||
|
||||
#### 3.4. Cancel closes the dialog without deleting ✅
|
||||
|
||||
- Clicking Cancel dismisses the modal and stays on the profile page with no account changes
|
||||
|
||||
#### 3.5. Backdrop click does NOT close the dialog ✅
|
||||
|
||||
- Clicking the modal backdrop leaves the dialog open (no backdrop-dismiss behavior)
|
||||
|
||||
#### 3.6. Successful deletion redirects to landing page ✅
|
||||
|
||||
- After entering the email and clicking Delete, an "Account Deleted" confirmation modal appears; clicking OK redirects to the landing page (`/`)
|
||||
|
||||
#### 3.7. Login after deletion is blocked ✅
|
||||
|
||||
- Attempting to sign in with the deleted account's credentials shows a "marked for deletion" error and stays on the login page
|
||||
Reference in New Issue
Block a user