teqo/tests/unit/e2eNavigationConventions.unit.spec.ts
Cursor Agent 84fed02ee5 test(miss-54): deterministic guard for unsettled goto→goto + nav convention
The SavedFilters incident shape — a page.goto fired while the previous
heavy RSC navigation was still in flight (net::ERR_ABORTED) — gets a
build-level guard: two gotos with no settle between them (await expect /
waitForURL / click / reload / login / catch) fail the suite. Measured
baseline on the current tree: 58 gotos across 17 specs, 0 unsettled
pairs — the six SOFT pairs from the miss audit all settle on awaited
assertions, so the guard starts clean without churning them.

TESTING.md records the convention the miss asked for: goto for cold
loads and URL contracts; in-shell navigation for continuity scenarios;
interactive-state waits before dynamic clicks; a dev-mode caveat for the
campaignLeaderships chip spec (deterministic dev failure at the
suggestion/commit step, prod green — ledgered).
2026-07-31 20:57:18 -03:00

86 lines
3 KiB
TypeScript

import { readdirSync, readFileSync } from 'node:fs'
import { relative, resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
// Miss #54 (2026-07-30): a `page.goto` fired while the previous heavy RSC
// navigation was still in flight aborted with `net::ERR_ABORTED`
// (campaignSavedFilters, prod build). `goto` stays legal for cold loads and
// URL contracts — the anti-pattern is two gotos with NOTHING between them
// that settles the first navigation. A settle is any awaited interaction
// with the landed page (assertion, fill, click, reload, login…): it proves
// the previous route rendered before the next one starts.
//
// Miss #53 (2026-07-30): the biometrics probe is one-shot per island mount,
// so a ceremony that starts before the CDP virtual authenticator answers
// never sees the enrollment UI. Specs registering a virtual authenticator
// must gate on `expectCampaignBiometricsReady` first.
const e2eRoot = resolve(process.cwd(), 'tests/e2e')
const specFiles = readdirSync(e2eRoot, { recursive: true, withFileTypes: true })
.filter((entry) => entry.isFile() && entry.name.endsWith('.e2e.spec.ts'))
.map((entry) => resolve(entry.parentPath, entry.name))
const SETTLE_TOKENS = [
'await expect(',
'waitForURL(',
'.click(',
'.check(',
'.fill(',
'.press(',
'.hover(',
'clearCookies(',
'.reload(',
'campaign.login(',
'.catch(',
'await request.',
'checkRadixWhenHydrated(',
'expectCampaignBiometricsReady(',
] as const
describe('e2e navigation discipline (miss #54)', () => {
it('never fires two page.goto without settling the first navigation', () => {
const offenders: string[] = []
for (const file of specFiles) {
const lines = readFileSync(file, 'utf8').split('\n')
let lastGoto: number | null = null
let settledSince = false
for (const [index, line] of lines.entries()) {
if (SETTLE_TOKENS.some((token) => line.includes(token))) settledSince = true
if (!line.includes('page.goto(')) continue
if (lastGoto !== null && !settledSince) {
offenders.push(
`${relative(process.cwd(), file)}:${index + 1} (goto at ${lastGoto + 1} unsettled)`,
)
}
lastGoto = index
settledSince = false
}
}
expect(
offenders,
'settle the first navigation (await expect/waitForURL/click…) before the next page.goto, or navigate in-shell (miss #54)',
).toEqual([])
})
})
describe('e2e WebAuthn virtual authenticator (miss #53)', () => {
it('gates every virtual-authenticator spec on expectCampaignBiometricsReady', () => {
const offenders: string[] = []
for (const file of specFiles) {
const source = readFileSync(file, 'utf8')
if (!source.includes('WebAuthn.addVirtualAuthenticator')) continue
if (source.includes('expectCampaignBiometricsReady(')) continue
offenders.push(relative(process.cwd(), file))
}
expect(
offenders,
'call expectCampaignBiometricsReady(page) before the surface whose island probes the authenticator',
).toEqual([])
})
})