- src: phase_z2 composition/mapper/pipeline/placement_planner/retry, ai_fallback(prompts/schema/validate), mdx_text_atoms 신규 - Front: PipelineTracePanel 신규, FramePanel/SlideCanvas/Home/designAgentApi 등 갱신 + 테스트 4종 추가 - templates/phase_z2: catalog(component_expansion_registry, node_slot_mapping 신규), frames, families, slide_base 갱신 - tests/matching: phase2~26 매칭 실험 스크립트·리포트·온톨로지 전체 (미커밋 진행분) - tests: b4_v4 evidence, task5~28.5 시리즈, regression(imp95 baseline) 등 신규 테스트 대량 추가 - docs/reference: MDX 구조 인벤토리, MDX→Frame 구조 계약 문서 - scripts: mdx 계약/parity/coverage/viewport 체크, gitea comment, run sync 유틸 - .gitignore: tmp*.json, chromedriver, .orchestrator, *.pkl, Front_test* 등 임시/스냅샷 제외 미완성 작업의 보존용 스냅샷 커밋 (2026-07-02) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
118 lines
5.9 KiB
TypeScript
118 lines
5.9 KiB
TypeScript
// 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(",
|
|
);
|
|
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(",
|
|
);
|
|
expect(flushIdx).toBeGreaterThan(-1);
|
|
expect(diagIdx).toBeGreaterThan(flushIdx);
|
|
expect(diagIdx).toBeLessThan(runPipelineIdx);
|
|
});
|
|
});
|