// IMP-47B u11 — Frontend ai_repair_status notification surfacing. // // Scope (Stage 2 unit u11 contract): // 1) loadRun → RunMeta.ai_repair_status exposes the u8 step20 payload. // 2) formatAiRepairHumanReviewMessage(...) returns user-facing notification // text on the three failure axes (error / coverage_violated / // unsupported_kind) and returns null on success / no-AI paths. // // Pure-function unit test (no React Testing Library required — vitest is // already in devDependencies; @testing-library/* is NOT installed). The // Home.tsx wiring is a 2-line site that calls this helper after // setRunMeta(...); covering the helper covers the user-visible message text // directly without DOM rendering. // // File extension is `.tsx` per Stage 2 unit contract path; no JSX is required // for these assertions but the extension allows future RTL-based tests to // land here without renaming. import { describe, it, expect } from "vitest"; import { formatAiRepairHumanReviewMessage, type AiRepairStatus, } from "../src/services/designAgentApi"; const baseCounts = { total: 1, applied: 0, no_proposal: 0, no_zone_match: 0, unsupported_kind: 0, error: 0, }; describe("formatAiRepairHumanReviewMessage (IMP-47B u11)", () => { it("returns null when ai_repair_status is null (legacy / pre-Step12 abort)", () => { expect(formatAiRepairHumanReviewMessage(null)).toBeNull(); expect(formatAiRepairHumanReviewMessage(undefined)).toBeNull(); }); it("returns null when human_review_required=false (success / no-AI path)", () => { const ok: AiRepairStatus = { status: "ok", counts: { ...baseCounts, total: 0 }, unsupported_kind_records: [], error_records: [], coverage_status: "ok", dropped_section_ids: [], human_review_required: false, }; expect(formatAiRepairHumanReviewMessage(ok)).toBeNull(); const applied: AiRepairStatus = { ...ok, status: "applied", counts: { ...baseCounts, total: 1, applied: 1 }, }; expect(formatAiRepairHumanReviewMessage(applied)).toBeNull(); }); it("surfaces AI call failures with count + frame/manual guidance", () => { const errored: AiRepairStatus = { status: "error", counts: { ...baseCounts, total: 2, error: 2 }, unsupported_kind_records: [], error_records: [ { unit_index: 0, source_section_ids: ["03-1"], error: "timeout" }, { unit_index: 1, source_section_ids: ["03-2"], error: "validation" }, ], coverage_status: "ok", dropped_section_ids: [], human_review_required: true, }; const msg = formatAiRepairHumanReviewMessage(errored); expect(msg).not.toBeNull(); expect(msg).toContain("AI 재구성 호출 실패"); expect(msg).toContain("2"); expect(msg).toContain("다른 frame 선택 또는 수동 편집 필요"); }); it("surfaces coverage violations with the dropped section ids", () => { const dropped: AiRepairStatus = { status: "coverage_violated", counts: { ...baseCounts, total: 1, applied: 1 }, unsupported_kind_records: [], error_records: [], coverage_status: "violated", dropped_section_ids: ["03-2"], human_review_required: true, }; const msg = formatAiRepairHumanReviewMessage(dropped); expect(msg).not.toBeNull(); expect(msg).toContain("콘텐츠 누락"); expect(msg).toContain("03-2"); expect(msg).toContain("다른 frame 선택 또는 수동 편집 필요"); }); it("surfaces unsupported proposal kinds with the unsupported count", () => { const unsupported: AiRepairStatus = { status: "unsupported_kind", counts: { ...baseCounts, total: 1, unsupported_kind: 1 }, unsupported_kind_records: [ { unit_index: 0, source_section_ids: ["03-1"], apply_status: "unsupported_kind_for_reject_route:builder_options_patch", }, ], error_records: [], coverage_status: "ok", dropped_section_ids: [], human_review_required: true, }; const msg = formatAiRepairHumanReviewMessage(unsupported); expect(msg).not.toBeNull(); expect(msg).toContain("AI 제안 형식 미지원"); expect(msg).toContain("1"); expect(msg).toContain("다른 frame 선택 또는 수동 편집 필요"); }); it("falls back to a generic human_review message on unknown status enums", () => { const future: AiRepairStatus = { status: "future_axis_not_yet_mapped", counts: { ...baseCounts, total: 0 }, unsupported_kind_records: [], error_records: [], coverage_status: "ok", dropped_section_ids: [], human_review_required: true, }; const msg = formatAiRepairHumanReviewMessage(future); expect(msg).not.toBeNull(); expect(msg).toContain("human_review"); expect(msg).toContain("future_axis_not_yet_mapped"); }); });