/** * 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)) })