ed5680253e
Every serious defect in this project was found by opening the app, most recently a feature that rendered blank cards to every user while its tests passed, and a progress bar labelled in the wrong unit. Five checks now run without a person: that the age-four figures on screen match what the API computed, that a multi-week period draws as one bar rather than fragments, that Sundays and holidays are red where Saturdays are not, that no identifier leaks into visible text, and that no page logs an error. Three of them were confirmed by reintroducing the original bug and watching the right test fail. Dates are derived rather than written down: Easter is computed independently instead of asked of the system under test, and the fixture period is placed relative to today, so the suite does not expire. It runs as its own job and does not block a merge until it has proved itself stable.
58 lines
2.9 KiB
TypeScript
58 lines
2.9 KiB
TypeScript
/**
|
|
* Regression guard for the age-4 savable/expiring arithmetic actually
|
|
* reaching the Dashboard UI.
|
|
*
|
|
* Domain rule (see AGENTS.md "Domain rules" and
|
|
* backend/app/config.py's `LeaveRules`): after the child turns 4, only 96
|
|
* days may be saved *per child* — for a `couple` workspace with two
|
|
* parents, that's a 48-day cap *per parent*, not 96 each. A prior bug
|
|
* modelled a two-parent family as able to save 192 days combined by
|
|
* applying the per-child cap to each parent independently — see
|
|
* `AnalyticsService`'s module docstring, which documents this exact
|
|
* failure mode. This test asserts the invariant holds in the API response
|
|
* AND that the Dashboard renders those same numbers (not some other,
|
|
* separately-computed figure) — the historically-buggy progress bar was a
|
|
* *wiring* defect, not a backend one.
|
|
*/
|
|
import { test, expect } from '@playwright/test'
|
|
import { apiGetOverview, apiLogin, DEMO_EMAIL, DEMO_PASSWORD } from '../support/api'
|
|
import { loadSeedFixture } from '../support/fixture'
|
|
|
|
test('per-parent expiring/savable figures respect the 48/96 age-4 cap, and the Dashboard shows the same numbers', async ({
|
|
page,
|
|
}) => {
|
|
const fixture = loadSeedFixture()
|
|
const token = await apiLogin(DEMO_EMAIL, DEMO_PASSWORD)
|
|
const overview = await apiGetOverview(token, fixture.workspaceId, fixture.childId)
|
|
|
|
// Ground truth from the API, independent of anything rendered.
|
|
expect(overview.parent_projections.length).toBe(2) // seed_demo.py: Alex + Sam
|
|
for (const projection of overview.parent_projections) {
|
|
expect(projection.savable_after_b4).toBeLessThanOrEqual(projection.savable_cap_at_b4)
|
|
expect(projection.savable_after_b4).toBeGreaterThanOrEqual(0)
|
|
expect(projection.expiring_at_b4).toBeGreaterThanOrEqual(0)
|
|
}
|
|
// The literal regression this guards: a two-parent workspace's per-parent
|
|
// caps must sum to the per-CHILD ceiling (96), never double it (192).
|
|
const capSum = overview.parent_projections.reduce((sum, p) => sum + p.savable_cap_at_b4, 0)
|
|
expect(capSum).toBeLessThanOrEqual(96)
|
|
const savableSum = overview.parent_projections.reduce((sum, p) => sum + p.savable_after_b4, 0)
|
|
expect(savableSum).toBeLessThanOrEqual(96)
|
|
|
|
// Now confirm the Dashboard actually surfaces these same figures.
|
|
await page.goto('/')
|
|
await expect(page.getByTestId('dashboard')).toBeVisible()
|
|
|
|
for (const projection of overview.parent_projections) {
|
|
const metric = page.getByTestId(`metric-expiring-parent-${projection.personal_data_uuid}`)
|
|
await expect(metric).toBeVisible()
|
|
const value = metric.locator('[data-testid^="metric-value-"]')
|
|
await expect(value).toHaveText(String(projection.expiring_at_b4))
|
|
}
|
|
|
|
const combinedMetric = page.getByTestId('metric-expiring-combined')
|
|
await expect(combinedMetric).toBeVisible()
|
|
const combinedValue = combinedMetric.locator('[data-testid^="metric-value-"]')
|
|
await expect(combinedValue).toHaveText(String(overview.combined_expiring_at_b4))
|
|
})
|