// spec: e2e/plans/chore-scheduler.plan.md — Section 8: Extend Time import { test, expect } from '@playwright/test' import { createChild, createChoreTask, assignChore, setDaysSchedule, choreCard, openChoreOptions, todayDayIndex, todayISO, goToChildView, } from './helpers' const CHILD_NAME = 'ExtendTimeChild' const EXPIRED_NAME = 'ExtendTimeChore' const FUTURE_NAME = 'FutureDeadlineChore' test.describe('Extend Time', () => { test.describe.configure({ mode: 'serial' }) let childId = '' let expiredId = '' let futureId = '' test.beforeAll(async ({ request }) => { childId = await createChild(request, CHILD_NAME) expiredId = await createChoreTask(request, EXPIRED_NAME) futureId = await createChoreTask(request, FUTURE_NAME) await assignChore(request, childId, [expiredId, futureId]) const today = todayDayIndex() // ExpiredChore: scheduled for today, deadline at 00:01 (guaranteed past) await setDaysSchedule(request, childId, expiredId, [today], { hour: 0, minute: 1 }) // FutureChore: scheduled for today, deadline at 23:59 (not expired) await setDaysSchedule(request, childId, futureId, [today], { hour: 23, minute: 59 }) }) test.afterAll(async ({ request }) => { if (childId) await request.delete(`/api/child/${childId}`) if (expiredId) await request.delete(`/api/task/${expiredId}`) if (futureId) await request.delete(`/api/task/${futureId}`) }) test('8.1 "Extend Time" appears in kebab menu only when chore is expired', async ({ page }) => { await page.goto(`/parent/${childId}`) const card = choreCard(page, EXPIRED_NAME) await card.waitFor() // Expired chore shows TOO LATE badge await expect(card.getByText('TOO LATE')).toBeVisible() await openChoreOptions(page, card) await expect(page.getByRole('button', { name: 'Extend Time' })).toBeVisible() }) test('8.2 "Extend Time" is absent when chore is not expired', async ({ page }) => { await page.goto(`/parent/${childId}`) const card = choreCard(page, FUTURE_NAME) await card.waitFor() await openChoreOptions(page, card) await expect(page.getByRole('button', { name: 'Extend Time' })).not.toBeVisible() }) test('8.3 Clicking "Extend Time" removes the TOO LATE badge', async ({ page }) => { await page.goto(`/parent/${childId}`) const card = choreCard(page, EXPIRED_NAME) await card.waitFor() await expect(card.getByText('TOO LATE')).toBeVisible() await openChoreOptions(page, card) await page.getByRole('button', { name: 'Extend Time' }).click() // After extension the badge disappears and card is no longer grayed out await expect(card.getByText('TOO LATE')).not.toBeVisible() await expect(card).not.toHaveClass(/chore-inactive/) }) test('8.4 Extended chore no longer shows "Due by" label', async ({ page }) => { // Card was extended in 8.3 so this verifies the extension persists await page.goto(`/parent/${childId}`) const card = choreCard(page, EXPIRED_NAME) await card.waitFor() await expect(card.getByText('TOO LATE')).not.toBeVisible() await expect(card.locator('.due-label')).not.toBeVisible() }) test('8.5 "Extend Time" disappears from kebab after extension', async ({ page }) => { await page.goto(`/parent/${childId}`) const card = choreCard(page, EXPIRED_NAME) await card.waitFor() // Chore was extended in 8.3 — should no longer be expired await openChoreOptions(page, card) await expect(page.getByRole('button', { name: 'Extend Time' })).not.toBeVisible() }) test('8.6 Extension effect visible in child mode', async ({ page }) => { // After extending in parent mode (done in 8.3), verify child view await goToChildView(page, childId) const card = choreCard(page, EXPIRED_NAME) // Chore is still visible (was scheduled for today + extended) and no TOO LATE badge await expect(card).toBeVisible() await expect(card.getByText('TOO LATE')).not.toBeVisible() }) test('8.6b Extending same chore twice via API returns 409', async ({ request }) => { // The chore was already extended in 8.3; a second API extension should fail const res = await request.post(`/api/child/${childId}/task/${expiredId}/extend`, { data: { date: todayISO() }, }) expect(res.status()).toBe(409) }) })