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,153 @@
|
||||
// spec: e2e/plans/chore-scheduler.plan.md — Section 5: Parent Mode Chore Card Schedule States
|
||||
|
||||
import { test, expect } from '@playwright/test'
|
||||
import {
|
||||
createChild,
|
||||
createChoreTask,
|
||||
assignChore,
|
||||
setDaysSchedule,
|
||||
choreCard,
|
||||
todayDayIndex,
|
||||
nonTodayDayIndex,
|
||||
} from './helpers'
|
||||
|
||||
const CHILD_NAME = 'ParentCardChild'
|
||||
const ALWAYS_ACTIVE = 'AlwaysActiveChore'
|
||||
const SCHEDULED_TODAY = 'ScheduledTodayChore'
|
||||
const NOT_SCHEDULED_TODAY = 'NotScheduledTodayChore'
|
||||
const EXPIRED_CHORE = 'ExpiredChore'
|
||||
const ANYTIME_CHORE = 'AnytimeChore'
|
||||
|
||||
test.describe('Parent Mode — Chore Card Schedule States', () => {
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
let childId = ''
|
||||
let alwaysId = ''
|
||||
let todayId = ''
|
||||
let notTodayId = ''
|
||||
let expiredId = ''
|
||||
let anytimeId = ''
|
||||
|
||||
test.beforeAll(async ({ request }) => {
|
||||
childId = await createChild(request, CHILD_NAME)
|
||||
alwaysId = await createChoreTask(request, ALWAYS_ACTIVE)
|
||||
todayId = await createChoreTask(request, SCHEDULED_TODAY)
|
||||
notTodayId = await createChoreTask(request, NOT_SCHEDULED_TODAY)
|
||||
expiredId = await createChoreTask(request, EXPIRED_CHORE)
|
||||
anytimeId = await createChoreTask(request, ANYTIME_CHORE)
|
||||
|
||||
await assignChore(request, childId, [alwaysId, todayId, notTodayId, expiredId, anytimeId])
|
||||
|
||||
const today = todayDayIndex()
|
||||
const nonToday = nonTodayDayIndex()
|
||||
|
||||
// ScheduledTodayChore: scheduled for today, deadline 23:59 (far future)
|
||||
await setDaysSchedule(request, childId, todayId, [today], { hour: 23, minute: 59 })
|
||||
|
||||
// NotScheduledTodayChore: scheduled for a non-today day
|
||||
await setDaysSchedule(request, childId, notTodayId, [nonToday], { hour: 8, minute: 0 })
|
||||
|
||||
// ExpiredChore: scheduled for today, deadline 00:01 (guaranteed past)
|
||||
await setDaysSchedule(request, childId, expiredId, [today], { hour: 0, minute: 1 })
|
||||
|
||||
// AnytimeChore: scheduled for today, no deadline
|
||||
await setDaysSchedule(request, childId, anytimeId, [today], { hasDeadline: false })
|
||||
|
||||
// AlwaysActiveChore: no schedule (already default)
|
||||
})
|
||||
|
||||
test.afterAll(async ({ request }) => {
|
||||
if (childId) await request.delete(`/api/child/${childId}`)
|
||||
for (const id of [alwaysId, todayId, notTodayId, expiredId, anytimeId]) {
|
||||
if (id) await request.delete(`/api/task/${id}`)
|
||||
}
|
||||
})
|
||||
|
||||
test('5.1 Chore with no schedule — normal appearance, no annotations', async ({ page }) => {
|
||||
await page.goto(`/parent/${childId}`)
|
||||
const card = choreCard(page, ALWAYS_ACTIVE)
|
||||
await card.waitFor()
|
||||
|
||||
await expect(card).not.toHaveClass(/chore-inactive/)
|
||||
await expect(card.locator('.chore-stamp')).not.toBeVisible()
|
||||
await expect(card.locator('.due-label')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('5.2 Chore scheduled for today — shows "Due by" label, not grayed out', async ({ page }) => {
|
||||
await page.goto(`/parent/${childId}`)
|
||||
const card = choreCard(page, SCHEDULED_TODAY)
|
||||
await card.waitFor()
|
||||
|
||||
await expect(card).not.toHaveClass(/chore-inactive/)
|
||||
await expect(card.locator('.due-label')).toHaveText('Due by 11:59 PM', { timeout: 10000 })
|
||||
})
|
||||
|
||||
test('5.3 Chore not scheduled for today — grayed out in parent mode but visible', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto(`/parent/${childId}`)
|
||||
const card = choreCard(page, NOT_SCHEDULED_TODAY)
|
||||
await card.waitFor()
|
||||
|
||||
// Always visible in parent mode
|
||||
await expect(card).toBeVisible()
|
||||
// Grayed out because off-day
|
||||
await expect(card).toHaveClass(/chore-inactive/)
|
||||
// No TOO LATE badge (not expired — just off-day)
|
||||
await expect(card.getByText('TOO LATE')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('5.4 Chore with expired deadline — grayed out with TOO LATE badge', async ({ page }) => {
|
||||
await page.goto(`/parent/${childId}`)
|
||||
const card = choreCard(page, EXPIRED_CHORE)
|
||||
await card.waitFor()
|
||||
|
||||
await expect(card).toBeVisible()
|
||||
await expect(card).toHaveClass(/chore-inactive/)
|
||||
await expect(card.getByText('TOO LATE')).toBeVisible()
|
||||
})
|
||||
|
||||
test('5.5 Chore with "Anytime" deadline — no "Due by" label, no TOO LATE badge', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto(`/parent/${childId}`)
|
||||
const card = choreCard(page, ANYTIME_CHORE)
|
||||
await card.waitFor()
|
||||
|
||||
await expect(card).not.toHaveClass(/chore-inactive/)
|
||||
await expect(card.locator('.due-label')).not.toBeVisible()
|
||||
await expect(card.getByText('TOO LATE')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('5.6 Kebab menu always accessible on grayed-out chores', async ({ page }) => {
|
||||
await page.goto(`/parent/${childId}`)
|
||||
const card = choreCard(page, NOT_SCHEDULED_TODAY)
|
||||
await card.waitFor()
|
||||
|
||||
// Grayed out card still has Options button after clicking
|
||||
await card.click()
|
||||
await expect(card).toHaveClass(/item-ready/, { timeout: 3000 })
|
||||
await expect(card.getByRole('button', { name: 'Options' })).toBeVisible()
|
||||
await card.getByRole('button', { name: 'Options' }).click()
|
||||
await expect(page.getByRole('button', { name: 'Schedule' })).toBeVisible()
|
||||
|
||||
// Schedule modal opens normally
|
||||
await page.getByRole('button', { name: 'Schedule' }).click()
|
||||
await expect(page.getByText('Schedule Chore')).toBeVisible()
|
||||
})
|
||||
|
||||
test('5.7 Expired chore cannot be triggered by tapping — no confirm dialog', async ({ page }) => {
|
||||
await page.goto(`/parent/${childId}`)
|
||||
const card = choreCard(page, EXPIRED_CHORE)
|
||||
await card.waitFor()
|
||||
|
||||
// First click selects the card even when expired
|
||||
await card.click()
|
||||
await expect(card).toHaveClass(/item-ready/, { timeout: 3000 })
|
||||
|
||||
// Second click attempts to trigger — but expired chores are blocked in triggerTask()
|
||||
await card.click()
|
||||
// Confirm dialog should NOT appear for expired chores
|
||||
await expect(page.locator('.modal-dialog')).not.toBeVisible({ timeout: 1000 })
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user