/** * @slowcook 0.17.6+ — recon shape-preservation tests. * * Reads mock UI files for a story and emits structural assertions to * tests/integration/story-N-shape.test.tsx. Pure deterministic; no LLM. * * Asserts SHAPE only (per `feedback_shape_tests_belong_to_recon_not_testgen`): * - testid presence + cardinality * - DOM containment (e.g. badge inside
) * - className tokens (visual/a11y signals like `rounded-full`, `min-h-[44px]`) * - CSS variable tokens (no inline hex/rgb) * * Never asserts WIRING (per `feedback_testgen_must_not_over_assert_mock`): * - No spied-on hooks, no import existence, no readFileSync of source * - No mock-only chrome (subtrees marked `data-mock-chrome="true"` are * mechanically excluded from the testid+class extraction) * * Test rendering uses plain props (no scenario-runtime), so mock-chrome * never actually renders in the test even if it weren't filtered. */ import { join } from "node:path"; export interface ExtractedShape { /** Component file scanned (mock-side). */ file: string; /** Testids found on non-mock-chrome JSX (deduped). */ testids: string[]; /** Visual className tokens worth preserving (subset; see TOKENS_OF_INTEREST). */ visualTokens: string[]; /** Whether the component renders any element with a `
` ancestor. */ hasHeader: boolean; /** Component name (best-effort). */ componentName: string | null; /** * design #10 — dense, paradigm-aware per-element class facts. Unlike * `visualTokens` (filtered through the 17-item TOKENS_OF_INTEREST * allowlist), this captures the element's *actual* utility-class set so * the shape test can assert `brewed ⊇ mock` containment per element and * drift can't hide in unpinned classes. Optional + additive: absent on * hand-built fixtures and pre-#10 callers. */ elementClasses?: ElementClassFacts[]; } /** design #10 — one element's testid anchor + its utility-class tokens. */ export interface ElementClassFacts { /** data-testid on the element, or null (anchor for per-element assertions). */ testid: string | null; /** Utility-shaped class tokens on the element (string-literal classNames only). */ tokens: string[]; } /** * Scan a mock UI file, return the SHAPE-relevant facts after stripping * mock-only chrome subtrees. */ export declare function extractShape(absFile: string, repoRoot: string): ExtractedShape | null; /** * design #10 — extract per-element utility-class facts from a (mock-chrome- * stripped) source. Paradigm-aware: only reads STRING-LITERAL classNames * (`className="..."` / `'...'` / plain backtick). `className={...}` brace * expressions — CSS-modules (`{styles.x}`), `cx(...)`, conditional template * literals with `${}` — are SKIPPED, because their tokens are either hashed * identifiers (noise, not design intent) or computed; extracting them would * be over-extraction. Styling fidelity for those mocks defers to the #8 eye. * * Pairs each element's tokens with its `data-testid` (the stable anchor for * per-element containment assertions). Only elements carrying ≥1 utility * token are returned. */ export declare function extractElementClasses(source: string): ElementClassFacts[]; /** * Heuristic: is `t` a utility-class token (Tailwind-shaped) rather than a * component/module identifier? Keeps lowercase-led tokens + any token with * a utility marker (`-`, `:`, `[`); drops capitalised identifiers and the * empty string. Errs permissive — string-literal classNames in a utility * mock are virtually all utilities. */ export declare function isUtilityShaped(t: string): boolean; /** * Strip JSX subtrees marked as mock-only chrome. Two conventions: * 1. data-mock-chrome="true" attribute on the wrapping element * 2. JSX comment immediately preceding (matching /Mock-only/i) * * Conservative — false positives (treating real shape as chrome) are * worse than false negatives. */ export declare function stripMockChromeSubtrees(source: string): string; interface SynthOpts { story: string; shapes: ExtractedShape[]; /** * 0.19.0-alpha.6 (#75) — emit mode. * "v1": source-grep tests (asserts text in brewed src/). Cheap, * no render, but fragile (passes when testid is in a comment; * misses dynamic className composition). * "v2": render-and-assert tests. Imports the component, mounts * via @testing-library/react, queries the live DOM. Catches * drift v1 misses; requires components to render with default * props (or skips downstream assertions cleanly). * Default: v1. Callers opt into v2 via --shape-mode v2. */ emitMode?: "v1" | "v2"; } /** * 0.19.0-alpha.6 — convert a `src/.tsx` source path to its * `@/` tsconfig-alias import specifier (drops the `.tsx` * extension; keeps directory structure). Pure: no IO. * * Examples: * src/components/members/MemberReactionsPage.tsx * → @/components/members/MemberReactionsPage * src/lib/foo.ts * → @/lib/foo * mock/src/components/X.tsx (already mock-ish) * → @/components/X (caller should pass mockToSrcPath first) */ export declare function srcPathToAliasImport(srcPath: string): string; export declare function synthesiseShapeTestFile(opts: SynthOpts): string; /** * 0.19.0-alpha.6 (#75) — v2: render-and-assert structural tests. * * For each mock file, emit a test that: * 1. Imports the brewed src/ component via the @/ alias. * 2. Renders it with default props (``) using * @testing-library/react. * 3. Queries the live DOM for testid presence, className tokens * on the rendered output, and
landmark presence. * * Keeps v1's "no inline hex" anti-wiring assertion (still source- * grep, cheap, catches token-family drift). The render assertions * are the v2 win — catches dynamic-className drift v1 missed. * * Defensive: every assertion wraps render in a top-level * "renders with default props" test. If that fails (component * needs real props), the downstream tests fail too — clean signal * that recon needs scenario-derived prop synthesis (a future α). */ export declare function synthesiseShapeTestFileV2(opts: SynthOpts): string; /** * Walk mock/src/ + return any file that contains `story-N` in source * OR is referenced by a scenario file for the story. * * For 0.17.6 first cut: simple — any file under mock/src/components/ * that lives in a directory referenced by the story's scenario. * Conservative — caller can pass explicit list via --mock-files. */ export declare function findMockFilesForStory(repoRoot: string, story: string): string[]; export { join as _join }; //# sourceMappingURL=shape-preserve.d.ts.map