// IMP-42 u4 — Source-slice coverage for the unconditional handleGenerate // DIAG console.log on the frontend → backend boundary (issue #71). // // Scope (Stage 2 unit u4 contract): // 1) A single `console.log("[DIAG raw overrides]", ...)` call exists // inside handleGenerate and precedes the runPipeline call site. // 2) The DIAG call is unconditional — not wrapped in `if (...)` / `?:` / // env-var gate / `__DEV__`-style guard. "Silence is the bug" per // Stage 1 scope-lock (Codex #3) and the Step 13 backend mirror // already landed in u3. // 3) The DIAG payload carries shape-only metadata — uploaded file name // and the override payload object — without referencing raw MDX // content or any other sample-specific identifier (RULE 0). // // Why source-slice (per Stage 2 plan): Home.tsx handleGenerate is wired to // React state, toast, and a 700-line component tree; the cheapest way to // pin a single-line surface and prove placement relative to runPipeline is // to read the source and assert ordering. No React rendering, no fetch // mock, no DOM. Mirrors the existing pure-helper pattern in // tests/imp41_application_mode.test.ts. import { describe, it, expect } from "vitest"; import { readFileSync } from "node:fs"; import { resolve } from "node:path"; const HOME_TSX_PATH = resolve(__dirname, "..", "src", "pages", "Home.tsx"); const HOME_TSX_SOURCE = readFileSync(HOME_TSX_PATH, "utf-8"); // Locate the handleGenerate callback body. The closing brace of // useCallback's `async () => { ... }` is the next line whose indent matches // the opening `useCallback(async () => {` exactly — but a simpler proxy is // "from the handleGenerate keyword to the next useCallback declaration or // the end-of-file." This is sufficient to scope every assertion below to // the right function body. function sliceHandleGenerateBody(source: string): string { const startMarker = "const handleGenerate = useCallback(async () =>"; const startIdx = source.indexOf(startMarker); if (startIdx === -1) { throw new Error("handleGenerate declaration not found in Home.tsx"); } // End at the next top-level `const ` that begins a new useCallback / // useMemo / hook binding. handleGenerate is followed by additional // hooks (handleFileUpload sibling pattern); slicing to the next // declaration is more than enough to capture the full body. const afterStart = source.slice(startIdx + startMarker.length); const nextDeclIdx = afterStart.search(/\n {2}const [A-Za-z]/); return nextDeclIdx === -1 ? afterStart : afterStart.slice(0, nextDeclIdx); } const HANDLE_GENERATE_BODY = sliceHandleGenerateBody(HOME_TSX_SOURCE); describe("handleGenerate [DIAG raw overrides] (IMP-42 u4)", () => { it("emits exactly one console.log labelled '[DIAG raw overrides]' inside handleGenerate", () => { const matches = HANDLE_GENERATE_BODY.match( /console\.log\(\s*"\[DIAG raw overrides\]"/g, ); expect(matches).not.toBeNull(); // Exactly one DIAG site per Stage 2 contract — multiple calls would // either be a copy-paste regression or evidence that the helper // moved without removing the old site. expect(matches?.length).toBe(1); }); it("places the DIAG console.log before the runPipeline call site", () => { const diagIdx = HANDLE_GENERATE_BODY.indexOf('console.log("[DIAG raw overrides]"'); const runPipelineIdx = HANDLE_GENERATE_BODY.indexOf( "runPipeline(state.uploadedFile, overrides)", ); expect(diagIdx).toBeGreaterThan(-1); expect(runPipelineIdx).toBeGreaterThan(-1); expect(diagIdx).toBeLessThan(runPipelineIdx); }); it("is unconditional — no env-var gate or if-guard wraps the DIAG call", () => { // Slice the 80 chars immediately preceding the DIAG console.log and // confirm none of the common gating patterns appear directly above. const diagIdx = HANDLE_GENERATE_BODY.indexOf('console.log("[DIAG raw overrides]"'); const preface = HANDLE_GENERATE_BODY.slice(Math.max(0, diagIdx - 200), diagIdx); // Stage 1 contract: silence is the bug. Any gate here is a regression. expect(preface).not.toMatch(/if\s*\([^)]*\)\s*$/m); expect(preface).not.toMatch(/process\.env/); expect(preface).not.toMatch(/import\.meta\.env/); expect(preface).not.toMatch(/__DEV__/); expect(preface).not.toMatch(/DIAG_VERBOSE/i); expect(preface).not.toMatch(/DEBUG/); }); it("forwards the file name and overrides object as shape-only payload", () => { // The DIAG payload must include the uploaded file name (so the user // can correlate the log line with the MDX they uploaded) and the // overrides object (so the user can see what crossed the wire). // It must NOT spread MDX text content or any other large blob — // sample-agnostic and reviewable in a single log line. const diagIdx = HANDLE_GENERATE_BODY.indexOf('console.log("[DIAG raw overrides]"'); const window = HANDLE_GENERATE_BODY.slice(diagIdx, diagIdx + 300); // Both fields appear in the payload object literal. expect(window).toMatch(/file:\s*state\.uploadedFile\.name/); expect(window).toMatch(/\boverrides\b/); // Sanity: the payload does not pass MDX raw content / a File blob. expect(window).not.toMatch(/mdxContent|rawMdx|normalizedContent/); }); it("runs after flushUserOverrides() so the persisted PUT is already committed", () => { // Ordering invariant from IMP-52 u10 (already in place): // flushUserOverrides() → DIAG → runPipeline // Asserts the DIAG sits between the flush and the network call so the // logged overrides match what backend reads from disk. const flushIdx = HANDLE_GENERATE_BODY.indexOf("await flushUserOverrides()"); const diagIdx = HANDLE_GENERATE_BODY.indexOf('console.log("[DIAG raw overrides]"'); const runPipelineIdx = HANDLE_GENERATE_BODY.indexOf( "runPipeline(state.uploadedFile, overrides)", ); expect(flushIdx).toBeGreaterThan(-1); expect(diagIdx).toBeGreaterThan(flushIdx); expect(diagIdx).toBeLessThan(runPipelineIdx); }); });