fix(07-04): make calendar populated-state test non-vacuous (BL-01) + deterministic seed window (BL-02)

Deep review found the calendar 'populated state' assertions were vacuous:
- getByText('Nothing here').toHaveCount(0) targeted CalendarShell's EmptyState,
  which CalendarShell NEVER renders (success branch always mounts ScheduleXCalendar;
  EmptyState.tsx is dead code, imported by nothing). The check was permanently green
  regardless of the seed — a regression dropping all events would have shipped green.
- .sx-react-calendar-wrapper renders on any successful auth, with or without events,
  so it never proved the seed reached the UI.

Replaced the dead-EmptyState check with a real DB→UI proof: assert the seeded event
title 'Seeded Test Event' is rendered in the grid. Verified non-vacuous — passes with
the seed on both profiles; with /api/events mocked to [] the title is absent (would fail).

BL-02: the seed anchored the event at now+24h. Both phone profiles render the
month-agenda view of the CURRENT month, so on a month's last day 'tomorrow' falls into
the next month and vanishes from the grid, making the new visibility assertion date-fragile.
Re-anchored to noon-today (UTC) — always today's local date, always in the current-month view.

Verified: full 58-test suite passes both profiles; typecheck clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Lucas Berger
2026-06-11 07:38:41 -04:00
co-authored by Claude Opus 4.8
parent f52b722b9b
commit 53c3ca56b8
2 changed files with 19 additions and 6 deletions
+9 -4
View File
@@ -67,14 +67,19 @@ test.describe('Rule 5 — populated calendar state', () => {
// The Schedule-X React adapter emits a div.sx-react-calendar-wrapper. // The Schedule-X React adapter emits a div.sx-react-calendar-wrapper.
// Prefer a stable locator: the class name is documented in apps/pwa/src/styles/index.css. // Prefer a stable locator: the class name is documented in apps/pwa/src/styles/index.css.
// No semantic role exists for the widget wrapper, so CSS class is the documented fallback. // No semantic role exists for the widget wrapper, so CSS class is the documented fallback.
// NOTE: the wrapper renders on any successful auth — this proves the grid mounts, NOT that
// the seed reached the UI. The DB→UI proof is the separate "seeded event is rendered" test.
const calendarGrid = page.locator('.sx-react-calendar-wrapper') const calendarGrid = page.locator('.sx-react-calendar-wrapper')
await expect(calendarGrid).toBeVisible() await expect(calendarGrid).toBeVisible()
}) })
test('EmptyState "Nothing here" is NOT present when events are seeded', async ({ page }) => { test('seeded event "Seeded Test Event" is rendered in the grid (DB→UI proof)', async ({ page }) => {
// CalendarShell renders EmptyState when the events query succeeds with zero occurrences. // The one assertion that actually proves the seeded row flows DB → API → query → grid.
// With the seeded event, EmptyState must not appear. Use toHaveCount(0) for strict absence. // global-setup seeds a timed event titled 'Seeded Test Event' (noon today) on calendar 10.
await expect(page.getByText('Nothing here')).toHaveCount(0) // Schedule-X renders the event with its title text inside the grid. If the seed broke, the
// /api/events join regressed, or hydration dropped events, THIS fails (unlike a wrapper /
// dead-EmptyState check, which would stay green). Deep-review BL-01.
await expect(page.getByText('Seeded Test Event').first()).toBeVisible()
}) })
test('no horizontal overflow on populated /calendar (Rule 2)', async ({ page }) => { test('no horizontal overflow on populated /calendar (Rule 2)', async ({ page }) => {
+10 -2
View File
@@ -92,9 +92,17 @@ export default async function globalSetup(): Promise<void> {
) )
// Seed one timed (not all-day) calendar event on shared calendar id=10. // Seed one timed (not all-day) calendar event on shared calendar id=10.
// Uses a future dtstart_utc so the event is visible in the UI's default "upcoming" view. // Anchor to NOON TODAY (UTC) — deliberately NOT "tomorrow" (deep-review BL-02):
// both device profiles are phone-width and render the month-agenda view of the
// CURRENT month. On the last day of a month "tomorrow" rolls into the next month
// and disappears from the rendered grid, making any "seeded event is visible"
// assertion date-fragile. Noon-today lands on today's local calendar date in every
// project timezone and is always inside the current-month view.
const uid = 'e2e-seed-event-001' const uid = 'e2e-seed-event-001'
const futureStart = new Date(Date.now() + 24 * 60 * 60 * 1000) // tomorrow UTC const _now = new Date()
const futureStart = new Date(
Date.UTC(_now.getUTCFullYear(), _now.getUTCMonth(), _now.getUTCDate(), 12, 0, 0),
)
// MariaDB TIMESTAMP requires 'YYYY-MM-DD HH:MM:SS' format, not ISO 8601 with 'T'. // MariaDB TIMESTAMP requires 'YYYY-MM-DD HH:MM:SS' format, not ISO 8601 with 'T'.
const futureStartUtc = futureStart const futureStartUtc = futureStart
.toISOString() .toISOString()