mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 06:30:03 +00:00
feat(cli): persistence-tiered findings, frozen-sweep guard, occlusion coverage
Layout findings now distinguish held defects from entrance/exit transients: a dynamic issue seen at a single grid sample demotes to info, while content_overlap held across two-plus samples (or 500ms+) promotes to error, resolving the long-standing re-promotion TODO. Static compositions keep their severity. check gains a sweep_static error when a 3s+ composition shows zero geometry change across every sample (a frozen timeline makes every green verdict unreliable); skipped when the motion sidecar already reported motion_frozen. text_occluded findings carry a coveredFraction; atomic labels (short, no whitespace) flag on any cover while prose needs 15%, since partial cover changes what a short label reads as. Deprecation-test scaffolding consolidates into deprecationTestHarness; tier logic and logger tests restructured under the complexity gate without suppression markers. Detection mechanics adapted from Adam Rosler's open-sourced visual-linter design (github.com/Adam-Rosler/hyperframes-visual-linter-design); the elementFromPoint paint model, opt-out attributes, and single-audit architecture are unchanged.
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
// Shared scaffolding for the U5 deprecation tests in inspect.test.ts,
|
||||
// layout.test.ts, and validate.test.ts: those commands all fail fast (via a
|
||||
// mocked dynamic import) so the tests can assert the shared deprecation
|
||||
// envelope (stderr notice, JSON `_meta.deprecated`) without needing a real
|
||||
// project or headless Chrome.
|
||||
//
|
||||
// vi.mock factories are hoisted above imports, so each test file keeps its
|
||||
// own thin `vi.mock("<path>", () => someFactory())` call (mocking a module
|
||||
// path can't itself be shared across files) but delegates the factory body
|
||||
// here.
|
||||
import type { ArgsDef, CommandDef } from "citty";
|
||||
import { runCommand } from "citty";
|
||||
import { expect, vi } from "vitest";
|
||||
|
||||
const FAKE_PROJECT = {
|
||||
dir: "/fake-project",
|
||||
name: "fake-project",
|
||||
indexPath: "/fake-project/index.html",
|
||||
};
|
||||
|
||||
export function resolveProjectMock() {
|
||||
return { resolveProject: vi.fn(() => FAKE_PROJECT) };
|
||||
}
|
||||
|
||||
export function bundleToSingleHtmlFailureMock() {
|
||||
return {
|
||||
bundleToSingleHtml: vi.fn(async () => {
|
||||
throw new Error("bundling failed (test double)");
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function lintProjectFailureMock() {
|
||||
return {
|
||||
lintProject: vi.fn(async () => {
|
||||
throw new Error("lint failed (test double)");
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* citty's `meta` is `Resolvable<CommandMeta>` (object | promise | thunk).
|
||||
* These test files always define it as a synchronous object literal, so
|
||||
* narrow to that shape instead of asserting it with `as`.
|
||||
*/
|
||||
export function metaDescription<T extends ArgsDef = ArgsDef>(command: CommandDef<T>): string {
|
||||
const meta = command.meta;
|
||||
if (meta && typeof meta === "object" && "description" in meta) {
|
||||
return String(meta.description ?? "");
|
||||
}
|
||||
throw new Error("expected a synchronous meta object");
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a command with stdout/stderr writes captured (and process.exit /
|
||||
* console.log stubbed so the run stays silent and non-terminating), and
|
||||
* return the captured text for the caller to assert on.
|
||||
*/
|
||||
export async function runAndCaptureStdio<T extends ArgsDef = ArgsDef>(
|
||||
command: CommandDef<T>,
|
||||
rawArgs: string[] = ["--json"],
|
||||
): Promise<{ stderrText: string; stdoutText: string }> {
|
||||
const stderrWrites: string[] = [];
|
||||
const stdoutWrites: string[] = [];
|
||||
vi.spyOn(process.stderr, "write").mockImplementation((chunk: unknown) => {
|
||||
stderrWrites.push(String(chunk));
|
||||
return true;
|
||||
});
|
||||
vi.spyOn(process.stdout, "write").mockImplementation((chunk: unknown) => {
|
||||
stdoutWrites.push(String(chunk));
|
||||
return true;
|
||||
});
|
||||
vi.spyOn(process, "exit").mockImplementation(() => undefined as never);
|
||||
vi.spyOn(console, "log").mockImplementation(() => {});
|
||||
|
||||
await runCommand(command, { rawArgs });
|
||||
|
||||
return { stderrText: stderrWrites.join(""), stdoutText: stdoutWrites.join("") };
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a command with process.exit stubbed and console.log spied, returning
|
||||
* the first console.log call that looks like a JSON object (the `--json`
|
||||
* failure envelope). Callers assert on definedness/shape themselves, since
|
||||
* that differs slightly per call site.
|
||||
*/
|
||||
export async function runAndFindJsonLogCall<T extends ArgsDef = ArgsDef>(
|
||||
command: CommandDef<T>,
|
||||
rawArgs: string[] = ["--json"],
|
||||
): Promise<unknown[] | undefined> {
|
||||
vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
||||
vi.spyOn(process, "exit").mockImplementation(() => undefined as never);
|
||||
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
||||
|
||||
await runCommand(command, { rawArgs });
|
||||
|
||||
return logSpy.mock.calls.find(([arg]) => typeof arg === "string" && arg.trim().startsWith("{"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience wrapper: parse the JSON envelope found by runAndFindJsonLogCall.
|
||||
* `parsed` is intentionally left as JSON.parse's inferred `any` (matching
|
||||
* every call site's prior inline `JSON.parse(...)` usage) rather than
|
||||
* annotated `unknown`, since callers assert directly into its shape
|
||||
* (`.ok`, `._meta.deprecated`) the same way the original inline tests did.
|
||||
*/
|
||||
export async function runAndParseJsonEnvelope<T extends ArgsDef = ArgsDef>(
|
||||
command: CommandDef<T>,
|
||||
rawArgs: string[] = ["--json"],
|
||||
) {
|
||||
const jsonCall = await runAndFindJsonLogCall(command, rawArgs);
|
||||
expect(jsonCall).toBeDefined();
|
||||
const parsed = JSON.parse(String(jsonCall?.[0]));
|
||||
return { jsonCall, parsed };
|
||||
}
|
||||
Reference in New Issue
Block a user