// spec: e2e/plans/chore-scheduler.plan.md — Section 2: Schedule Modal Specific Days Mode import { test, expect } from '@playwright/test' import { createChild, createChoreTask, assignChore, deleteSchedule, setDaysSchedule, choreCard, openScheduleModal, setTimePicker, nonTodayDayIndex, } from './helpers' const CHILD_NAME = 'ScheduleDaysChild' const CHORE_NAME = 'ScheduleDaysChore' test.describe('Schedule Modal — Specific Days mode', () => { 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('2.1 Clicking day chips toggles them active/inactive', async ({ page }) => { await page.goto(`/parent/${childId}`) const card = choreCard(page, CHORE_NAME) await card.waitFor() await openScheduleModal(page, card) const moChip = page.locator('.day-chips').getByRole('button', { name: 'Mo' }) await moChip.click() await expect(moChip).toHaveClass(/active/) await moChip.click() await expect(moChip).not.toHaveClass(/active/) }) test('2.2 Default deadline row appears when at least one day is selected', async ({ page }) => { await page.goto(`/parent/${childId}`) const card = choreCard(page, CHORE_NAME) await card.waitFor() await openScheduleModal(page, card) await expect(page.locator('.default-deadline-row')).not.toBeVisible() await page.locator('.day-chips').getByRole('button', { name: 'Tu' }).click() await expect(page.locator('.default-deadline-row')).toBeVisible() await expect(page.locator('.default-deadline-row')).toContainText('Deadline') }) test('2.3 Default deadline "Anytime" toggle hides and restores time picker', async ({ page }) => { await page.goto(`/parent/${childId}`) const card = choreCard(page, CHORE_NAME) await card.waitFor() await openScheduleModal(page, card) // Select a day to show the deadline row await page.locator('.day-chips').getByRole('button', { name: 'Tu' }).click() await expect(page.locator('.default-deadline-row')).toBeVisible() // Time picker is visible by default await expect( page.locator('.default-deadline-row').locator('.time-picker-popover'), ).toBeVisible() // Click "Clear (Anytime)" — time picker hides, "Anytime" label appears await page .locator('.default-deadline-row') .getByRole('button', { name: 'Clear (Anytime)' }) .click() await expect( page.locator('.default-deadline-row').locator('.time-picker-popover'), ).not.toBeVisible() await expect(page.locator('.default-deadline-row').getByText('Anytime')).toBeVisible() // Click "Set deadline" — time picker reappears await page .locator('.default-deadline-row') .getByRole('button', { name: 'Set deadline' }) .click() await expect( page.locator('.default-deadline-row').locator('.time-picker-popover'), ).toBeVisible() }) test('2.4 Exception time — "Set different time" creates per-day override', async ({ page }) => { await page.goto(`/parent/${childId}`) const card = choreCard(page, CHORE_NAME) await card.waitFor() await openScheduleModal(page, card) // Select Monday and Wednesday await page.locator('.day-chips').getByRole('button', { name: 'Mo' }).click() await page.locator('.day-chips').getByRole('button', { name: 'We' }).click() // Wednesday row: click "Set different time" const weRow = page.locator('.exception-row').filter({ has: page.locator('.exception-day-name', { hasText: 'Wednesday' }), }) await weRow.getByRole('button', { name: 'Set different time' }).click() // Wednesday now shows its own time picker await expect(weRow.locator('.time-picker-popover')).toBeVisible() // Monday row still shows the default label (no time picker override) const moRow = page.locator('.exception-row').filter({ has: page.locator('.exception-day-name', { hasText: 'Monday' }), }) await expect(moRow.locator('.default-label')).toBeVisible() await expect(moRow.locator('.time-picker-popover')).not.toBeVisible() }) test('2.5 Exception time — "Reset to default" removes the override', async ({ page }) => { await page.goto(`/parent/${childId}`) const card = choreCard(page, CHORE_NAME) await card.waitFor() await openScheduleModal(page, card) // Set up Monday + Wednesday, add Wednesday exception await page.locator('.day-chips').getByRole('button', { name: 'Mo' }).click() await page.locator('.day-chips').getByRole('button', { name: 'We' }).click() const weRow = page.locator('.exception-row').filter({ has: page.locator('.exception-day-name', { hasText: 'Wednesday' }), }) await weRow.getByRole('button', { name: 'Set different time' }).click() await expect(weRow.locator('.time-picker-popover')).toBeVisible() // Reset Wednesday to default await weRow.getByRole('button', { name: 'Reset to default' }).click() await expect(weRow.locator('.default-label')).toBeVisible() await expect(weRow.locator('.time-picker-popover')).not.toBeVisible() }) test('2.6 Save with specific days — schedule persists on reopen', async ({ page }) => { await page.goto(`/parent/${childId}`) const card = choreCard(page, CHORE_NAME) await card.waitFor() await openScheduleModal(page, card) // Select Monday and Friday await page.locator('.day-chips').getByRole('button', { name: 'Mo' }).click() await page.locator('.day-chips').getByRole('button', { name: 'Fr' }).click() // Set deadline to 03:00 PM const timePicker = page.locator('.default-deadline-row').locator('.time-picker-popover') await setTimePicker(page, timePicker, 3, 0, 'PM') // Save await page.getByRole('button', { name: 'Save' }).click() await expect(page.locator('.modal-title')).not.toBeVisible() // Navigate fresh to reset card ready-state, then reopen to verify await page.goto(`/parent/${childId}`) await card.waitFor() await openScheduleModal(page, card) await expect(page.locator('.day-chips').getByRole('button', { name: 'Mo' })).toHaveClass( /active/, ) await expect(page.locator('.day-chips').getByRole('button', { name: 'Fr' })).toHaveClass( /active/, ) await expect(page.locator('.default-deadline-row').locator('.time-display')).toHaveText( '03:00 PM', ) }) test('2.7 Save with zero days deletes the schedule', async ({ page, request }) => { // Seed: create a days schedule so the modal opens with existing days await setDaysSchedule(request, childId, choreId, [nonTodayDayIndex()], { hour: 9, minute: 0 }) await page.goto(`/parent/${childId}`) const card = choreCard(page, CHORE_NAME) await card.waitFor() await openScheduleModal(page, card) // Deselect all active chips const chips = page.locator('.day-chips').getByRole('button') for (const chip of await chips.all()) { const cls = await chip.getAttribute('class') if (cls?.includes('active')) await chip.click() } // Save (removing schedule) await page.getByRole('button', { name: 'Save' }).click() await expect(page.locator('.modal-title')).not.toBeVisible() // Navigate fresh to reset card ready-state, then reopen to verify no days selected await page.goto(`/parent/${childId}`) await card.waitFor() await openScheduleModal(page, card) for (const chip of await page.locator('.day-chips').getByRole('button').all()) { await expect(chip).not.toHaveClass(/active/) } }) test('2.8 Dirty detection — changing days enables Save', async ({ page, request }) => { const nonToday = nonTodayDayIndex() await setDaysSchedule(request, childId, choreId, [nonToday], { hour: 8, minute: 0 }) await page.goto(`/parent/${childId}`) const card = choreCard(page, CHORE_NAME) await card.waitFor() await openScheduleModal(page, card) // Save should be disabled (nothing changed yet) await expect(page.getByRole('button', { name: 'Save' })).toBeDisabled() // Toggle a different day chip → dirty const otherDay = (nonToday + 1) % 7 const dayLabels = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'] await page.locator('.day-chips').getByRole('button', { name: dayLabels[otherDay] }).click() await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled() }) })