feat: add enable/disable toggle for chore scheduling in ScheduleModal
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m39s
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m39s
- Introduced a toggle button to enable or disable chores in the scheduler. - The toggle will be available in both types of schedulers. - Added UI design proposal and considerations for mobile dimensions. - Included E2E tests to ensure toggle state persistence and correct behavior in child view.
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
// 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()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user