// spec: e2e/plans/chore-scheduler.plan.md — Section 11: Schedule Loading / Backward Compatibility import { test, expect } from '@playwright/test' import { createChild, createChoreTask, assignChore, deleteSchedule, setIntervalSchedule, choreCard, openScheduleModal, futureDateISO, nonTodayDayIndex, } from './helpers' const CHILD_NAME = 'LoadCompatChild' const CHORE_NAME = 'LoadCompatChore' test.describe('Schedule Loading — Backward Compatibility', () => { test.describe.configure({ mode: 'serial' }) let childId = '' let choreId = '' test.beforeAll(async ({ request }) => { childId = await createChild(request, CHILD_NAME) choreId = await createChoreTask(request, CHORE_NAME) await assignChore(request, childId, [choreId]) }) test.afterAll(async ({ request }) => { if (childId) await request.delete(`/api/child/${childId}`) if (choreId) await request.delete(`/api/task/${choreId}`) }) test.beforeEach(async ({ request }) => { await deleteSchedule(request, childId, choreId) }) test('11.1 Schedule without "enabled" field defaults to enabled in modal', async ({ page, request, }) => { // Create schedule via raw API call — omit the `enabled` field await request.put(`/api/child/${childId}/task/${choreId}/schedule`, { data: { mode: 'days', day_configs: [{ day: nonTodayDayIndex(), hour: 8, minute: 0 }], default_hour: 8, default_minute: 0, default_has_deadline: true, // enabled intentionally omitted — backend defaults to true }, }) await page.goto(`/parent/${childId}`) const card = choreCard(page, CHORE_NAME) await card.waitFor() await openScheduleModal(page, card) // Toggle should default to "Enabled" await expect(page.getByRole('switch')).toHaveAttribute('aria-checked', 'true') await expect(page.locator('.toggle-label')).toHaveText('Enabled') }) test('11.2 Schedule with interval_has_deadline=false shows "Anytime" in modal', async ({ page, request, }) => { await setIntervalSchedule(request, childId, choreId, 2, futureDateISO(30), { hasDeadline: false, }) await page.goto(`/parent/${childId}`) const card = choreCard(page, CHORE_NAME) await card.waitFor() await openScheduleModal(page, card) // "Every X Days" mode is active await expect(page.getByRole('button', { name: 'Every X Days' })).toHaveClass(/active/) // Anytime label is shown instead of time picker await expect(page.locator('.interval-time-row').getByText('Anytime')).toBeVisible() await expect( page.locator('.interval-time-row').locator('.time-picker-popover'), ).not.toBeVisible() }) test('11.3 Days schedule with per-day exception loads deadline rows correctly', async ({ page, request, }) => { // Monday=08:00, Wednesday=08:00 (default), Friday=10:30 (exception) // Use raw API to set up mixed schedule await request.put(`/api/child/${childId}/task/${choreId}/schedule`, { data: { mode: 'days', day_configs: [ { day: 1, hour: 8, minute: 0 }, // Monday { day: 3, hour: 8, minute: 0 }, // Wednesday { day: 5, hour: 10, minute: 30 }, // Friday — exception from default 08:00 ], default_hour: 8, default_minute: 0, default_has_deadline: true, enabled: true, }, }) await page.goto(`/parent/${childId}`) const card = choreCard(page, CHORE_NAME) await card.waitFor() await openScheduleModal(page, card) // Monday, Wednesday, Friday chips should be active await expect(page.locator('.day-chips').getByRole('button', { name: 'Mo' })).toHaveClass( /active/, ) await expect(page.locator('.day-chips').getByRole('button', { name: 'We' })).toHaveClass( /active/, ) await expect(page.locator('.day-chips').getByRole('button', { name: 'Fr' })).toHaveClass( /active/, ) // Default deadline shows 08:00 AM await expect(page.locator('.default-deadline-row').locator('.time-display')).toHaveText( '08:00 AM', ) // Friday has its own time picker (exception) — "Reset to default" button visible const frRow = page.locator('.exception-row').filter({ has: page.locator('.exception-day-name', { hasText: 'Friday' }), }) await expect(frRow.getByRole('button', { name: 'Reset to default' })).toBeVisible() await expect(frRow.locator('.time-display')).toHaveText('10:30 AM') // Monday has no exception — shows default label const moRow = page.locator('.exception-row').filter({ has: page.locator('.exception-day-name', { hasText: 'Monday' }), }) await expect(moRow.locator('.default-label')).toBeVisible() }) test('11.4 Interval schedule restores anchor date and interval on reopen', async ({ page, request, }) => { const anchorDate = '2026-06-15' const intervalDays = 3 await setIntervalSchedule(request, childId, choreId, intervalDays, anchorDate, { hour: 9, minute: 0, }) await page.goto(`/parent/${childId}`) const card = choreCard(page, CHORE_NAME) await card.waitFor() await openScheduleModal(page, card) // "Every X Days" mode is active await expect(page.getByRole('button', { name: 'Every X Days' })).toHaveClass(/active/) // Stepper shows 3 await expect(page.locator('.stepper-value')).toHaveText(`${intervalDays}`) // Date input has the correct anchor date await expect(page.locator('input.date-input-hidden')).toHaveValue(anchorDate) }) })