Some checks failed
Multi-MDX Regression (IMP-91) / multi-mdx-regression (push) Failing after 21s
- u1 FramePanel.tsx: extract `applyFrameSelection(candidate, onFrameSelect)` pure helper; collapse `handleFrameSelect` to direct onFrameSelect for every V4 label; drop `window.confirm` reject popup (IMP-47B u11 regression noise per `feedback_auto_pipeline_first`). New vitest pin `imp84_framepanel_reject_silent.test.ts` covers helper invocation across all 4 V4 labels + source-presence pins. - u2 templates/phase_z2/slide_base.html: delete `.zone--provisional` CSS, `.zone__needs-adaptation-badge` CSS, the zone--provisional class fragment in the zone div, and the badge `<span>` render at the provisional zone. Preserve `data-provisional="1"` attribute as silent telemetry. New pytest `tests/phase_z2/test_imp84_provisional_silent_render.py` pins the silent contract independently of the IMP-30 first-render file. - u3 tests/test_phase_z2_imp30_first_render.py: invert the three IMP-30 u5 positive provisional-visual assertions to IMP-84 silent-contract negatives (no class, no badge, no CSS selectors); preserve positive `data-provisional` telemetry assertions. Docstrings updated to IMP-84 silent contract. Out of scope (Round #4 + #92 contract): Home.tsx `toast.error(aiReviewMsg)` call line, designAgentApi.ts `api_error_kinds`/`api_error_kind` schema and operational-only formatter, FramePanel reject badge/tooltip read-only labels (L102/L147/L156), and backend `zone.provisional` flag emission. Stage 4 PASS: u1 vitest 10/10, u2 pytest 5/5, u3 pytest 29/29 (incl. 3 IMP-84 inverted assertions: `test_imp84_provisional_zone_silent_no_class_no_badge`, `test_imp84_provisional_badge_never_rendered_in_mixed_zones`, `test_imp84_slide_base_css_strips_provisional_visual_selectors`). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
123 lines
5.2 KiB
TypeScript
123 lines
5.2 KiB
TypeScript
// IMP-#84 u1 — FramePanel reject silent-automation contract.
|
|
//
|
|
// Stage 2 unit u1 scope:
|
|
// 1) `applyFrameSelection(candidate, onFrameSelect)` invokes onFrameSelect
|
|
// with candidate.id verbatim for EVERY V4 label
|
|
// (use_as_is / light_edit / restructure / reject) — no window.confirm
|
|
// gate, no label-conditional branch, no frame swap.
|
|
// 2) Source-presence checks pin the FramePanel.tsx wiring so the runtime
|
|
// button → handler → helper chain stays intact even though we cannot
|
|
// mount React (no jsdom / RTL / happy-dom in Front devDependencies —
|
|
// verified against the IMP-56 u20 `imp90_bottom_actions.test.ts` and
|
|
// IMP-92 u5 `imp47b_human_review_toast.test.tsx` precedent that
|
|
// explicitly skip DOM mounting).
|
|
// 3) No `window.confirm` substring remains in FramePanel.tsx after u1.
|
|
//
|
|
// Out of scope (Stage 2 exit-report contract):
|
|
// - Home.tsx:523-524 `toast.error(aiReviewMsg)` (#92 operational-only).
|
|
// - FramePanel reject badge/tooltip read-only labels at L102/L147/L156
|
|
// (no popup trigger; preserved as silent operator hint).
|
|
// - Backend `zone.provisional` emission (handled by u2 template-only).
|
|
|
|
import { readFileSync } from "node:fs";
|
|
import { resolve, dirname } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { describe, it, expect, vi } from "vitest";
|
|
import { applyFrameSelection } from "../src/components/FramePanel";
|
|
import type { FrameCandidate } from "../src/types/designAgent";
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const FRAME_PANEL_SOURCE = readFileSync(
|
|
resolve(__dirname, "../src/components/FramePanel.tsx"),
|
|
"utf-8",
|
|
);
|
|
|
|
function makeCandidate(
|
|
label: FrameCandidate["label"],
|
|
id: string,
|
|
): FrameCandidate {
|
|
return {
|
|
id,
|
|
name: `Frame ${id}`,
|
|
score: 0.5,
|
|
confidence: "medium",
|
|
label,
|
|
};
|
|
}
|
|
|
|
describe("applyFrameSelection (IMP-#84 u1 — silent-automation contract)", () => {
|
|
it("forwards candidate.id to onFrameSelect for use_as_is label", () => {
|
|
const onFrameSelect = vi.fn();
|
|
applyFrameSelection(makeCandidate("use_as_is", "frame_a"), onFrameSelect);
|
|
expect(onFrameSelect).toHaveBeenCalledTimes(1);
|
|
expect(onFrameSelect).toHaveBeenCalledWith("frame_a");
|
|
});
|
|
|
|
it("forwards candidate.id to onFrameSelect for light_edit label", () => {
|
|
const onFrameSelect = vi.fn();
|
|
applyFrameSelection(makeCandidate("light_edit", "frame_b"), onFrameSelect);
|
|
expect(onFrameSelect).toHaveBeenCalledTimes(1);
|
|
expect(onFrameSelect).toHaveBeenCalledWith("frame_b");
|
|
});
|
|
|
|
it("forwards candidate.id to onFrameSelect for restructure label", () => {
|
|
const onFrameSelect = vi.fn();
|
|
applyFrameSelection(makeCandidate("restructure", "frame_c"), onFrameSelect);
|
|
expect(onFrameSelect).toHaveBeenCalledTimes(1);
|
|
expect(onFrameSelect).toHaveBeenCalledWith("frame_c");
|
|
});
|
|
|
|
it("forwards candidate.id to onFrameSelect for reject label — no popup, no frame swap", () => {
|
|
// Reject is the silent-automation pivot case: prior IMP-47B u11 gated
|
|
// this path with window.confirm; post-IMP-#84 the helper invokes
|
|
// onFrameSelect with the reject frame.id directly. Backend / AI 격리
|
|
// contract handles AI 재구성 (content-only, frame preserved).
|
|
const onFrameSelect = vi.fn();
|
|
applyFrameSelection(makeCandidate("reject", "frame_d"), onFrameSelect);
|
|
expect(onFrameSelect).toHaveBeenCalledTimes(1);
|
|
expect(onFrameSelect).toHaveBeenCalledWith("frame_d");
|
|
});
|
|
|
|
it("does not call onFrameSelect more than once per invocation", () => {
|
|
const onFrameSelect = vi.fn();
|
|
applyFrameSelection(makeCandidate("reject", "frame_e"), onFrameSelect);
|
|
applyFrameSelection(makeCandidate("use_as_is", "frame_f"), onFrameSelect);
|
|
expect(onFrameSelect).toHaveBeenCalledTimes(2);
|
|
expect(onFrameSelect).toHaveBeenNthCalledWith(1, "frame_e");
|
|
expect(onFrameSelect).toHaveBeenNthCalledWith(2, "frame_f");
|
|
});
|
|
});
|
|
|
|
describe("FramePanel.tsx source — silent-automation wiring pins (IMP-#84 u1)", () => {
|
|
it("has no window.confirm(...) call (popup removed; narrative mentions in comments are allowed)", () => {
|
|
// Match the call form `window.confirm(` rather than the bare substring
|
|
// so that explanatory comments documenting the removed popup are not
|
|
// flagged. A re-introduced call would carry an opening paren.
|
|
expect(FRAME_PANEL_SOURCE).not.toMatch(/\bwindow\.confirm\s*\(/);
|
|
});
|
|
|
|
it("does not embed the legacy reject-confirm Korean prompt body", () => {
|
|
// Prior IMP-47B u11 string fragment; absence guards against re-introduction.
|
|
expect(FRAME_PANEL_SOURCE).not.toContain("V4 reject 라벨입니다");
|
|
expect(FRAME_PANEL_SOURCE).not.toContain("계속하시겠습니까?");
|
|
});
|
|
|
|
it("wires the button onClick to handleFrameSelect(candidate)", () => {
|
|
expect(FRAME_PANEL_SOURCE).toContain(
|
|
"onClick={() => handleFrameSelect(candidate)}",
|
|
);
|
|
});
|
|
|
|
it("delegates handleFrameSelect body to applyFrameSelection", () => {
|
|
expect(FRAME_PANEL_SOURCE).toContain(
|
|
"applyFrameSelection(candidate, onFrameSelect)",
|
|
);
|
|
});
|
|
|
|
it("exports applyFrameSelection as a named export for caller-independent reuse", () => {
|
|
expect(FRAME_PANEL_SOURCE).toMatch(
|
|
/export function applyFrameSelection\(/,
|
|
);
|
|
});
|
|
});
|