chore(01-13): wave-0 — clean broken Approach-A artifacts per 01-11-SUMMARY
Restore a clean baseline before promoting thec647f61prototype to production paths (Wave 1) and building out Approach-B driver scaffolding (Wave 2). All deletions trace back to falsifications documented in 01-11-SUMMARY.md. Deleted — broken Approach-A files: - src/test-hooks/sw-hooks.ts MV3 SW blocks dynamic import (Chromium es_modules.md; w3c/webextensions#212). The gated `await import('../test-hooks/ sw-hooks')` from 01-11 Wave 1 never resolved → SW silently died → production listeners never registered. File was dead-on-arrival; no fix possible while MV3 SWs disallow dynamic import. Approach-B replaces SW-side instrumentation with the extension-internal harness page's chrome.action.* + chrome.notifications.* surface (full privilege; no monkey-patching needed). - tests/uat/lib/{launch,extension,sw,offscreen,assertions}.ts Popup-bridge architecture (01-11dbd977c) — falsification 2 + falsification 3 in 01-11-SUMMARY: `sw.evaluate` exposes only chrome.{loadTimes,csi}, NOT chrome.action.* / chrome.notifications.* / chrome.runtime.sendMessage; setPopup-juggling for extension-id resolution turned out to be unnecessary (browser.extensions() works directly per the prototype). These files will be reborn in Wave 2 around the extension-page architecture. Kept: tests/uat/lib/zip.ts (host-side JSZip work — architecture- agnostic; A12+A13 still use it) and tests/uat/lib/test-hook- contract.d.ts (type mirror — extended in Wave 3 but kept as-is here). - tests/uat/prototype/probe_{offscreen,sw,tabs,tabs2}.mjs Feasibility-research probes (01-11 spike) that empirically falsified the Approach-A hypotheses. The findings are encoded in 01-11- SUMMARY.md; the probes themselves are dead code. - tests/uat/harness.test.ts 01-11 Wave 2 popup-bridge orchestrator (dbd977c). Imports the now-deleted tests/uat/lib/{assertions,extension,sw,offscreen,launch} modules — would not typecheck after this commit. Reborn in Wave 3A as the Approach-B orchestrator (extension-internal page driver + A0 grep gate + 13 assertion drivers). Reverted — SW-side dynamic-import gate comment block: - src/background/index.ts lines 13-29 The existing comment block (post-spike) described the SW-side gated dynamic import that never landed. Rewritten to cite 01-13 Approach-B explicitly, link to 01-11-SUMMARY.md falsification, and clarify that the Tier-1 grep gate's enduring value is catching regressions in the offscreen chunk's __MOKOSH_UAT__ gate (the SW chunk is hook-free by construction). Updated — Tier-1 grep gate FORBIDDEN_HOOK_STRINGS inventory: - tests/background/no-test-hooks-in-prod-bundle.test.ts Removed: `simulateUserStop` (Approach-A naming; replaced by Approach-B `dispatchEndedOnTrack` which matches the W3C dispatchEvent semantics per RESEARCH §7 BLOCKER — track.stop() does NOT fire 'ended' per spec, so the simulation MUST use dispatchEvent). Added: `installFakeDisplayMedia`, `uninstallFakeDisplayMedia`, `dispatchEndedOnTrack`, `__mokoshOffscreenQuery`. Total inventory: 8 surface strings (was 5). Each MUST be absent from every file under dist/ post-build. Verification (all GREEN): - `npm run build` — exit 0; dist/ populated. - `grep -rln <forbidden> dist/` — 0 matches. - `npm run build:test` — exit 0; dist-test/ populated; offscreen-hooks chunk contains `installFakeDisplayMedia` (gate runs correctly against the test build's distinct artifact). - `npx tsc --noEmit` — exit 0 (root + tests/uat/tsconfig.json). - `npx vitest run` — 92/92 tests passing (was 89; the +3 new tests come from the FORBIDDEN_HOOK_STRINGS list expanding 5 → 8 — each forbidden string is one parametric `it(...)` block). Both prior-failing tests now GREEN: - tests/background/sw-bundle-import.test.ts (was missing dist/ → 92/92 requires the test run to have a current dist/; vitest gate test rebuilds via execFile when SKIP_BUILD≠1, otherwise relies on prior `npm run build`). - tests/background/no-test-hooks-in-prod-bundle.test.ts (was failing on stale dist; now GREEN against the freshly-rebuilt clean bundle). Wave 1 (next): promote tests/uat/prototype/{extension-page-harness.html, extension-page-harness.ts,a6.test.ts} to tests/uat/ via `git mv`; update vite.test.config.ts rollup input. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,199 +0,0 @@
|
||||
// tests/uat/lib/assertions.ts — Plan 01-11 harness assertion runner.
|
||||
//
|
||||
// Centralizes:
|
||||
// - `assertEqual` / `assertMatch` / `assertTrue` — thin wrappers
|
||||
// over `node:assert/strict` with explicit Plan 01-11 diagnostic
|
||||
// framing (cite the bug-class on Bug A / Bug B assertions).
|
||||
// - `runAssertion(name, fn)` — wraps each assertion in a try/catch
|
||||
// so the harness can collect a per-assertion pass/fail map AND
|
||||
// dump SW/offscreen console buffers on the FIRST failure (bail
|
||||
// semantics per RESEARCH §5).
|
||||
// - `waitFor(probe, predicate, timeoutMs)` — polling helper used by
|
||||
// assertions that need to wait for async state transitions
|
||||
// (badge changes, downloads, etc.).
|
||||
//
|
||||
// References:
|
||||
// - node:assert/strict: https://nodejs.org/api/assert.html#strict-assertion-mode
|
||||
|
||||
import { strict as assert } from 'node:assert';
|
||||
|
||||
/**
|
||||
* Per-assertion outcome record. Accumulated by runAssertion + flushed
|
||||
* to the harness's final summary line.
|
||||
*/
|
||||
export interface AssertionRecord {
|
||||
readonly index: number;
|
||||
readonly name: string;
|
||||
readonly passed: boolean;
|
||||
readonly errorMessage: string;
|
||||
readonly durationMs: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Console buffers captured from SW + offscreen contexts. The harness
|
||||
* wires `sw.on('console', ...)` + `offPage.on('console', ...)` at
|
||||
* launch + before each assertion-relevant phase; on failure these
|
||||
* buffers are dumped to stderr for triage.
|
||||
*/
|
||||
export interface ConsoleBuffers {
|
||||
swLines: string[];
|
||||
offscreenLines: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a single assertion, capturing its outcome + duration. On error,
|
||||
* dump the per-context console buffers to stderr BEFORE rethrowing so
|
||||
* the harness's top-level catch sees the diagnostic context.
|
||||
*
|
||||
* @param index - 0-13 (0 = grep gate, 1-13 = functional).
|
||||
* @param name - Human-readable assertion title.
|
||||
* @param buffers - Console buffers to dump on failure (may be empty).
|
||||
* @param fn - Async assertion body.
|
||||
* @returns Outcome record.
|
||||
*/
|
||||
export async function runAssertion(
|
||||
index: number,
|
||||
name: string,
|
||||
buffers: ConsoleBuffers,
|
||||
fn: () => Promise<void>,
|
||||
): Promise<AssertionRecord> {
|
||||
const start = Date.now();
|
||||
try {
|
||||
await fn();
|
||||
const durationMs = Date.now() - start;
|
||||
process.stdout.write(` [PASS] A${index}: ${name} (${durationMs}ms)\n`);
|
||||
return {
|
||||
index,
|
||||
name,
|
||||
passed: true,
|
||||
errorMessage: '',
|
||||
durationMs,
|
||||
};
|
||||
} catch (err) {
|
||||
const durationMs = Date.now() - start;
|
||||
const errorMessage =
|
||||
err instanceof Error ? `${err.name}: ${err.message}` : String(err);
|
||||
process.stderr.write(` [FAIL] A${index}: ${name} (${durationMs}ms)\n`);
|
||||
process.stderr.write(` ${errorMessage}\n`);
|
||||
dumpBuffers(buffers, index);
|
||||
return {
|
||||
index,
|
||||
name,
|
||||
passed: false,
|
||||
errorMessage,
|
||||
durationMs,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump SW + offscreen console buffers to stderr with structured framing.
|
||||
* Cap at the last 30 lines per context to keep failure output readable.
|
||||
*
|
||||
* @param buffers - The accumulating buffers.
|
||||
* @param assertionIndex - For framing the dump preamble.
|
||||
*/
|
||||
function dumpBuffers(buffers: ConsoleBuffers, assertionIndex: number): void {
|
||||
const TAIL = 30;
|
||||
const swTail = buffers.swLines.slice(-TAIL);
|
||||
const offTail = buffers.offscreenLines.slice(-TAIL);
|
||||
if (swTail.length > 0) {
|
||||
process.stderr.write(
|
||||
` --- SW console (last ${swTail.length} lines, assertion A${assertionIndex}) ---\n`,
|
||||
);
|
||||
for (const line of swTail) {
|
||||
process.stderr.write(` ${line}\n`);
|
||||
}
|
||||
}
|
||||
if (offTail.length > 0) {
|
||||
process.stderr.write(
|
||||
` --- Offscreen console (last ${offTail.length} lines, assertion A${assertionIndex}) ---\n`,
|
||||
);
|
||||
for (const line of offTail) {
|
||||
process.stderr.write(` ${line}\n`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strict equality with a context-bearing message. Wraps
|
||||
* `assert.strictEqual` so the failure surface is uniform across
|
||||
* assertions.
|
||||
*
|
||||
* @param actual - Observed value.
|
||||
* @param expected - Expected value.
|
||||
* @param msg - Context for the failure diagnostic.
|
||||
*/
|
||||
export function assertEqual<T>(actual: T, expected: T, msg: string): void {
|
||||
assert.strictEqual(actual, expected, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that `actual` matches `regex`. Wraps `assert.match`.
|
||||
*
|
||||
* @param actual - String to test.
|
||||
* @param regex - Pattern.
|
||||
* @param msg - Context for the failure diagnostic.
|
||||
*/
|
||||
export function assertMatch(actual: string, regex: RegExp, msg: string): void {
|
||||
assert.match(actual, regex, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that `cond` is truthy. Wraps `assert.ok`.
|
||||
*
|
||||
* @param cond - Boolean expression.
|
||||
* @param msg - Context for the failure diagnostic.
|
||||
*/
|
||||
export function assertTrue(cond: boolean, msg: string): void {
|
||||
assert.ok(cond, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the actual value is greater than or equal to expected.
|
||||
* Used by assertion 9 (icon size floors) + assertion 11 (segment count).
|
||||
*
|
||||
* @param actual - Observed value.
|
||||
* @param expected - Minimum acceptable value.
|
||||
* @param msg - Context for the failure diagnostic.
|
||||
*/
|
||||
export function assertGte(actual: number, expected: number, msg: string): void {
|
||||
assert.ok(
|
||||
actual >= expected,
|
||||
`${msg} — expected >= ${expected}, got ${actual}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll `probe` until `predicate(probe())` returns true OR timeoutMs
|
||||
* elapses. Throws on timeout with a structured diagnostic.
|
||||
*
|
||||
* @param probe - Async function producing a value to test.
|
||||
* @param predicate - Returns true when the value satisfies the wait.
|
||||
* @param timeoutMs - Maximum wait time.
|
||||
* @param description - Human-readable description for the diagnostic.
|
||||
* @param pollIntervalMs - Interval between probe calls (default 100ms).
|
||||
* @returns The last probed value that satisfied the predicate.
|
||||
* @throws If timeoutMs elapses without predicate satisfaction.
|
||||
*/
|
||||
export async function waitFor<T>(
|
||||
probe: () => Promise<T>,
|
||||
predicate: (v: T) => boolean,
|
||||
timeoutMs: number,
|
||||
description: string,
|
||||
pollIntervalMs: number = 100,
|
||||
): Promise<T> {
|
||||
const start = Date.now();
|
||||
let lastValue: T | undefined;
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
lastValue = await probe();
|
||||
if (predicate(lastValue)) {
|
||||
return lastValue;
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
||||
}
|
||||
throw new Error(
|
||||
`waitFor timeout ${timeoutMs}ms — ${description}; ` +
|
||||
`last probed value: ${JSON.stringify(lastValue)}`,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user