Files
chore/frontend/vue-app/e2e/mode_parent/rewards/reward-delete-default.spec.ts
Ryan Kegel 8da04676ca
Some checks failed
Chore App Build, Test, and Push Docker Images / build-and-push (push) Failing after 2m33s
Add end-to-end tests for parent rewards management
- Implement tests for creating, editing, canceling, and deleting rewards in parent mode.
- Include scenarios for converting default rewards to user items and verifying restoration of default rewards after deletion.
- Create a comprehensive test plan outlining the steps and expectations for each scenario.
2026-03-12 23:53:36 -04:00

125 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// spec: frontend/vue-app/e2e/plans/parent-rewards-management.plan.md
// seed: e2e/seed.spec.ts
import { test, expect } from '@playwright/test'
test.describe('Default reward cost edit and restore', () => {
test.beforeEach(async ({ page }, testInfo) => {
test.skip(testInfo.project.name === 'chromium-no-pin', 'Requires parent-authenticated mode')
})
test.describe.configure({ mode: 'serial' })
test('Edit default cost and verify restoration on delete', async ({ page }) => {
await page.goto('/parent/rewards')
// cleanup any user-copy of "Choose meal"
// Keep deleting rows until there are none left avoids DOM-shift race when
// multiple matching rows exist (e.g. leftovers from previous specs). Because
// other workers may modify the list concurrently, we also retry the entire
// loop once after a short delay if a delete button still shows up.
async function purge(): Promise<void> {
while (
await page
.getByText('Choose meal', { exact: true })
.locator('..')
.getByRole('button', { name: 'Delete' })
.count()
) {
const btn = page
.getByText('Choose meal', { exact: true })
.locator('..')
.getByRole('button', { name: 'Delete' })
.first()
await btn.click()
await expect(page.locator('text=Are you sure you want to delete')).toBeVisible()
await page.locator('button.btn-danger:has-text("Delete")').click()
// give UI a moment to refresh the list
await page.waitForTimeout(200)
}
}
await purge()
// if something else sneaks one in (concurrent spec), wait then try again
if (
await page
.getByText('Choose meal', { exact: true })
.locator('..')
.getByRole('button', { name: 'Delete' })
.count()
) {
await page.waitForTimeout(500)
await purge()
}
// reload to make sure the list has settled after all deletions
await page.reload()
await page.waitForURL('/parent/rewards')
// ensure default exists with no delete icon
const defaultRow = page.getByText('Choose meal', { exact: true }).first()
await expect(defaultRow).toBeVisible()
// sometimes concurrent specs race and temporarily re-add a delete icon; give
// it a moment and retry once
let deleteCount = await defaultRow.locator('..').getByRole('button', { name: 'Delete' }).count()
if (deleteCount) {
await page.waitForTimeout(500)
deleteCount = await defaultRow.locator('..').getByRole('button', { name: 'Delete' }).count()
}
await expect(deleteCount).toBe(0)
// open edit and change cost
await defaultRow.click()
await expect(page.locator('input#name')).toHaveValue('Choose meal')
await page.evaluate(() => {
const setVal = (sel: string, val: string) => {
const el = document.querySelector(sel) as HTMLInputElement | null
if (el) {
el.value = val
el.dispatchEvent(new Event('input', { bubbles: true }))
el.dispatchEvent(new Event('change', { bubbles: true }))
}
}
setVal('#cost', '50')
})
await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled()
await page.click('button:has-text("Save")')
// After save: pick the row wrapper that now shows 50 pts (user copy)
const customRow = page
.getByText('Choose meal', { exact: true })
.locator('..')
.filter({ has: page.locator('text=50 pts') })
.first()
await expect(customRow).toBeVisible({ timeout: 10000 })
await expect(customRow.getByRole('button', { name: /Delete/ })).toBeVisible()
// delete and confirm restoration using the same customRow
await customRow.getByRole('button', { name: /Delete/ }).click()
await expect(page.locator('text=Are you sure you want to delete')).toBeVisible()
await page.locator('button.btn-danger:has-text("Delete")').click()
// wait until the exact 'Choose meal' copy showing the edited cost is gone
await expect(
page
.getByText('Choose meal', { exact: true })
.locator('..')
.filter({ has: page.locator('text=50 pts') }),
).toHaveCount(0, { timeout: 10000 })
// reload the list to clear any lingering delete icon artifacts
await page.reload()
await page.waitForURL('/parent/rewards')
// ensure no delete button is visible on the exact default 'Choose meal' row
await expect(
page
.getByText('Choose meal', { exact: true })
.locator('..')
.getByRole('button', { name: 'Delete' }),
).toHaveCount(0, { timeout: 10000 })
const finalRow = page.getByText('Choose meal', { exact: true }).first()
await expect(finalRow).toBeVisible()
await expect(finalRow).not.toContainText('50 pts')
})
})