All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m39s
- Introduced a toggle button to enable or disable chores in the scheduler. - The toggle will be available in both types of schedulers. - Added UI design proposal and considerations for mobile dimensions. - Included E2E tests to ensure toggle state persistence and correct behavior in child view.
179 lines
6.3 KiB
TypeScript
179 lines
6.3 KiB
TypeScript
// spec: e2e/plans/chore-scheduler.plan.md — Section 3: Schedule Modal Every X Days Mode
|
||
|
||
import { test, expect } from '@playwright/test'
|
||
import {
|
||
createChild,
|
||
createChoreTask,
|
||
assignChore,
|
||
deleteSchedule,
|
||
setIntervalSchedule,
|
||
choreCard,
|
||
openScheduleModal,
|
||
setTimePicker,
|
||
setDateInput,
|
||
todayISO,
|
||
futureDateISO,
|
||
} from './helpers'
|
||
|
||
const CHILD_NAME = 'ScheduleIntervalChild'
|
||
const CHORE_NAME = 'ScheduleIntervalChore'
|
||
|
||
test.describe('Schedule Modal — Every X 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('3.1 Interval stepper increments and decrements within 1–7', async ({ page }) => {
|
||
await page.goto(`/parent/${childId}`)
|
||
const card = choreCard(page, CHORE_NAME)
|
||
await card.waitFor()
|
||
await openScheduleModal(page, card)
|
||
|
||
await page.getByRole('button', { name: 'Every X Days' }).click()
|
||
|
||
// Default value is 1
|
||
await expect(page.locator('.stepper-value')).toHaveText('1')
|
||
|
||
// Decrement is disabled at 1
|
||
const decBtn = page.locator('.stepper').getByRole('button').first()
|
||
await expect(decBtn).toBeDisabled()
|
||
|
||
// Increment 6 times → value becomes 7
|
||
const incBtn = page.locator('.stepper').getByRole('button').last()
|
||
for (let i = 0; i < 6; i++) await incBtn.click()
|
||
await expect(page.locator('.stepper-value')).toHaveText('7')
|
||
|
||
// Increment is disabled at 7
|
||
await expect(incBtn).toBeDisabled()
|
||
})
|
||
|
||
test("3.2 Anchor date field shows today's date by default", async ({ page }) => {
|
||
await page.goto(`/parent/${childId}`)
|
||
const card = choreCard(page, CHORE_NAME)
|
||
await card.waitFor()
|
||
await openScheduleModal(page, card)
|
||
|
||
await page.getByRole('button', { name: 'Every X Days' }).click()
|
||
await expect(page.locator('input.date-input-hidden')).toHaveValue(todayISO())
|
||
})
|
||
|
||
test('3.3 Next occurrence preview label updates correctly', async ({ page }) => {
|
||
await page.goto(`/parent/${childId}`)
|
||
const card = choreCard(page, CHORE_NAME)
|
||
await card.waitFor()
|
||
await openScheduleModal(page, card)
|
||
|
||
await page.getByRole('button', { name: 'Every X Days' }).click()
|
||
|
||
// Set interval to 2 (click + once from default 1)
|
||
await page.locator('.stepper').getByRole('button').last().click()
|
||
await expect(page.locator('.stepper-value')).toHaveText('2')
|
||
|
||
// Ensure anchor is today
|
||
await setDateInput(page, todayISO())
|
||
|
||
// Next occurrence row should be visible
|
||
await expect(page.locator('.next-occurrence-label')).toBeVisible()
|
||
await expect(page.locator('.next-occurrence-label')).toContainText('Next occurrence:')
|
||
})
|
||
|
||
test('3.4 Interval deadline "Anytime" toggle works', async ({ page }) => {
|
||
await page.goto(`/parent/${childId}`)
|
||
const card = choreCard(page, CHORE_NAME)
|
||
await card.waitFor()
|
||
await openScheduleModal(page, card)
|
||
|
||
await page.getByRole('button', { name: 'Every X Days' }).click()
|
||
|
||
// Time picker visible by default
|
||
await expect(page.locator('.interval-time-row').locator('.time-picker-popover')).toBeVisible()
|
||
|
||
// Click "Clear (Anytime)"
|
||
await page
|
||
.locator('.interval-time-row')
|
||
.getByRole('button', { name: 'Clear (Anytime)' })
|
||
.click()
|
||
await expect(
|
||
page.locator('.interval-time-row').locator('.time-picker-popover'),
|
||
).not.toBeVisible()
|
||
await expect(page.locator('.interval-time-row').getByText('Anytime')).toBeVisible()
|
||
|
||
// Click "Set deadline" — time picker reappears
|
||
await page.locator('.interval-time-row').getByRole('button', { name: 'Set deadline' }).click()
|
||
await expect(page.locator('.interval-time-row').locator('.time-picker-popover')).toBeVisible()
|
||
})
|
||
|
||
test('3.5 Save interval schedule — persists on reopen', async ({ page }) => {
|
||
const anchor = futureDateISO(60) // 60 days ahead — well past today min constraint
|
||
await page.goto(`/parent/${childId}`)
|
||
const card = choreCard(page, CHORE_NAME)
|
||
await card.waitFor()
|
||
await openScheduleModal(page, card)
|
||
|
||
await page.getByRole('button', { name: 'Every X Days' }).click()
|
||
|
||
// Set interval to 3 (click + twice from default 1)
|
||
const incBtn = page.locator('.stepper').getByRole('button').last()
|
||
await incBtn.click()
|
||
await incBtn.click()
|
||
await expect(page.locator('.stepper-value')).toHaveText('3')
|
||
|
||
// Set anchor date
|
||
await setDateInput(page, anchor)
|
||
|
||
// Set deadline to 04:30 PM
|
||
const timePicker = page.locator('.interval-time-row').locator('.time-picker-popover')
|
||
await setTimePicker(page, timePicker, 4, 30, '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.getByRole('button', { name: 'Every X Days' })).toHaveClass(/active/)
|
||
await expect(page.locator('.stepper-value')).toHaveText('3')
|
||
await expect(page.locator('input.date-input-hidden')).toHaveValue(anchor)
|
||
await expect(page.locator('.interval-time-row').locator('.time-display')).toHaveText('04:30 PM')
|
||
})
|
||
|
||
test('3.6 Dirty detection — changing interval enables Save', async ({ page, request }) => {
|
||
await setIntervalSchedule(request, childId, choreId, 3, futureDateISO(30))
|
||
|
||
await page.goto(`/parent/${childId}`)
|
||
const card = choreCard(page, CHORE_NAME)
|
||
await card.waitFor()
|
||
await openScheduleModal(page, card)
|
||
|
||
// Save is disabled (no changes yet)
|
||
await expect(page.getByRole('button', { name: 'Save' })).toBeDisabled()
|
||
|
||
// Change interval from 3 to 5 (click + twice)
|
||
const incBtn = page.locator('.stepper').getByRole('button').last()
|
||
await incBtn.click()
|
||
await incBtn.click()
|
||
await expect(page.locator('.stepper-value')).toHaveText('5')
|
||
|
||
// Save is now enabled
|
||
await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled()
|
||
})
|
||
})
|