// spec: e2e/plans/task-modified.plan copy.md import { test, expect, type APIRequestContext, type Locator, type Page } from '@playwright/test' const CHILD_NAME = 'ModifyRewardChild' const REWARD_NAME = 'ModifyTestReward' const REWARD_COST = 100 async function createChild(request: APIRequestContext, name: string): Promise { const pre = await request.get('/api/child/list') for (const c of (await pre.json()).children ?? []) { if (c.name === name) await request.delete(`/api/child/${c.id}`) } await request.put('/api/child/add', { data: { name, age: 8 } }) const list = await request.get('/api/child/list') const { children } = (await list.json()) as { children: { name: string; id: string }[] } return children?.find((c) => c.name === name)?.id ?? '' } async function createReward( request: APIRequestContext, name: string, cost: number, ): Promise { const pre = await request.get('/api/reward/list') for (const r of (await pre.json()).rewards ?? []) { if (r.name === name) await request.delete(`/api/reward/${r.id}`) } await request.put('/api/reward/add', { data: { name, description: 'E2E test reward', cost } }) const list = await request.get('/api/reward/list') const { rewards } = (await list.json()) as { rewards: { name: string; id: string }[] } return rewards?.find((r) => r.name === name)?.id ?? '' } function rewardSection(page: Page): Locator { return page.locator('.child-list-container').filter({ has: page.locator('h3', { hasText: 'Rewards' }), }) } async function openEditModal(page: Page, card: Locator): Promise { await card.click() await expect(card).toHaveClass(/item-ready/, { timeout: 3000 }) await card.getByTitle('Edit custom value').click() await expect(page.getByRole('button', { name: 'Save' })).toBeVisible() } async function seedPendingReward( request: APIRequestContext, childId: string, rewardId: string, rewardCost: number, ): Promise { await request.put(`/api/child/${childId}/edit`, { data: { points: rewardCost } }) const requestResp = await request.post(`/api/child/${childId}/request-reward`, { data: { reward_id: rewardId }, }) if (requestResp.status() === 409) { await request.post(`/api/child/${childId}/cancel-request-reward`, { data: { reward_id: rewardId }, }) await request.post(`/api/child/${childId}/request-reward`, { data: { reward_id: rewardId }, }) } } test.describe('Reward edit cost', () => { test.describe.configure({ mode: 'serial' }) let childId = '' let rewardId = '' test.beforeAll(async ({ request }) => { childId = await createChild(request, CHILD_NAME) rewardId = await createReward(request, REWARD_NAME, REWARD_COST) await request.put(`/api/child/${childId}/set-rewards`, { data: { reward_ids: [rewardId] }, }) }) test.afterAll(async ({ request }) => { if (childId) await request.delete(`/api/child/${childId}`) if (rewardId) await request.delete(`/api/reward/${rewardId}`) }) test('Edit button appears on first click of a reward card', async ({ page }) => { await page.goto(`/parent/${childId}`) const card = rewardSection(page).locator('.item-card').filter({ hasText: REWARD_NAME }) await card.waitFor({ state: 'visible' }) await expect(card.getByTitle('Edit custom value')).not.toBeVisible() await card.click() await expect(card).toHaveClass(/item-ready/, { timeout: 3000 }) await expect(card.getByTitle('Edit custom value')).toBeVisible() }) test('Edit button opens modal with reward name and "Assign new cost" subtitle', async ({ page, }) => { await page.goto(`/parent/${childId}`) const card = rewardSection(page).locator('.item-card').filter({ hasText: REWARD_NAME }) await card.waitFor({ state: 'visible' }) await card.click() await expect(card).toHaveClass(/item-ready/, { timeout: 3000 }) await card.getByTitle('Edit custom value').click() await expect(page.locator('.modal-title')).toHaveText(REWARD_NAME) await expect(page.locator('.modal-subtitle')).toContainText('new cost') await page.getByRole('button', { name: 'Cancel' }).click() }) test('Input is pre-populated with the current cost', async ({ page }) => { await page.goto(`/parent/${childId}`) const card = rewardSection(page).locator('.item-card').filter({ hasText: REWARD_NAME }) await card.waitFor({ state: 'visible' }) await openEditModal(page, card) await expect(page.locator('#custom-value')).toHaveValue(String(REWARD_COST)) await page.getByRole('button', { name: 'Cancel' }).click() }) test('Cancel does not change the points needed display', async ({ page }) => { await page.goto(`/parent/${childId}`) const card = rewardSection(page).locator('.item-card').filter({ hasText: REWARD_NAME }) await card.waitFor({ state: 'visible' }) await openEditModal(page, card) await page.locator('#custom-value').fill('999') await page.getByRole('button', { name: 'Cancel' }).click() await expect(page.getByRole('button', { name: 'Save' })).not.toBeVisible() // Child has 0 points; reward cost 100 → "100 more points" await expect(card.getByText(`${REWARD_COST} more points`)).toBeVisible() await expect(card.locator('.override-badge')).not.toBeVisible() }) test('Saving a new cost updates the points needed and shows ⭐ badge', async ({ page }) => { await page.goto(`/parent/${childId}`) const card = rewardSection(page).locator('.item-card').filter({ hasText: REWARD_NAME }) await card.waitFor({ state: 'visible' }) await openEditModal(page, card) await page.locator('#custom-value').fill('50') await page.getByRole('button', { name: 'Save' }).click() await expect(page.getByRole('button', { name: 'Save' })).not.toBeVisible() // Child still has 0 points; override cost 50 → "50 more points" await expect(card.getByText('50 more points')).toBeVisible() await expect(card.locator('.override-badge')).toBeVisible() }) test('Setting cost to 0 makes the reward immediately redeemable', async ({ page }) => { await page.goto(`/parent/${childId}`) const card = rewardSection(page).locator('.item-card').filter({ hasText: REWARD_NAME }) await card.waitFor({ state: 'visible' }) await openEditModal(page, card) await page.locator('#custom-value').fill('0') await page.getByRole('button', { name: 'Save' }).click() await expect(page.getByRole('button', { name: 'Save' })).not.toBeVisible() await expect(card.getByText('REWARD READY')).toBeVisible() }) test('Negative value disables the Save button', async ({ page }) => { await page.goto(`/parent/${childId}`) const card = rewardSection(page).locator('.item-card').filter({ hasText: REWARD_NAME }) await card.waitFor({ state: 'visible' }) await openEditModal(page, card) await page.locator('#custom-value').fill('-1') await expect(page.getByRole('button', { name: 'Save' })).toBeDisabled() await page.getByRole('button', { name: 'Cancel' }).click() }) test('Value above 10000 disables Save; exactly 10000 enables it', async ({ page }) => { await page.goto(`/parent/${childId}`) const card = rewardSection(page).locator('.item-card').filter({ hasText: REWARD_NAME }) await card.waitFor({ state: 'visible' }) await openEditModal(page, card) await page.locator('#custom-value').fill('10001') await expect(page.getByRole('button', { name: 'Save' })).toBeDisabled() await page.locator('#custom-value').fill('10000') await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled() await page.getByRole('button', { name: 'Cancel' }).click() }) test('Editing a pending reward shows a warning dialog — Cancel aborts the edit', async ({ page, request, }) => { // Ensure a fresh pending request exists for this test. await seedPendingReward(request, childId, rewardId, REWARD_COST) await page.goto(`/parent/${childId}`) const card = rewardSection(page).locator('.item-card').filter({ hasText: REWARD_NAME }) await card.waitFor({ state: 'visible' }) await expect(card.getByText('PENDING')).toBeVisible() // First click → edit button visible await card.click() await expect(card).toHaveClass(/item-ready/, { timeout: 3000 }) await card.getByTitle('Edit custom value').click() // PendingRewardDialog should appear warning about the pending state await expect(page.getByText(/currently pending/i)).toBeVisible() // Clicking "Reject" closes the dialog without opening the override modal await page.getByRole('button', { name: 'Reject', exact: true }).click() await expect(page.getByText(/currently pending/i)).not.toBeVisible() await expect(page.getByRole('button', { name: 'Save' })).not.toBeVisible() }) test('Editing a pending reward — confirming the warning opens the override modal', async ({ page, request, }) => { // Seed pending state in this test to avoid cross-test coupling. await seedPendingReward(request, childId, rewardId, REWARD_COST) await page.goto(`/parent/${childId}`) const card = rewardSection(page).locator('.item-card').filter({ hasText: REWARD_NAME }) await card.waitFor({ state: 'visible' }) await expect(card.getByText('PENDING')).toBeVisible() await card.click() await expect(card).toHaveClass(/item-ready/, { timeout: 3000 }) await card.getByTitle('Edit custom value').click() // PendingRewardDialog visible await expect(page.getByText(/currently pending/i)).toBeVisible() // Confirming cancels the pending request and opens the override modal await page.getByRole('button', { name: 'Approve', exact: true }).click() await expect(page.getByRole('button', { name: 'Save' })).toBeVisible({ timeout: 5000 }) await page.getByRole('button', { name: 'Cancel' }).click() }) })