| 1 | import assert from "node:assert/strict"; |
| 2 | import React from "react"; |
| 3 | import { renderToStaticMarkup } from "react-dom/server"; |
| 4 | import { LocaleProvider } from "../lib/i18n"; |
| 5 | import { ModifiedFiles, PresentedFiles } from "../components/PresentedFiles"; |
| 6 | import type { PresentedFileView } from "../lib/chatViewSource"; |
| 7 | import type { WireCompletionSummary } from "../lib/types"; |
| 8 | |
| 9 | const files: PresentedFileView[] = Array.from({ length: 5 }, (_, index) => ({ |
| 10 | path: `output/file-${index + 1}.html`, |
| 11 | description: `Preview ${index + 1}`, |
| 12 | toolCallId: `present-${index + 1}`, |
| 13 | })); |
| 14 | |
| 15 | const markup = renderToStaticMarkup( |
| 16 | <LocaleProvider><PresentedFiles files={files} tabId="tab-1" /></LocaleProvider>, |
| 17 | ); |
| 18 | |
| 19 | assert.equal((markup.match(/class="presented-file"/g) ?? []).length, 4); |
| 20 | assert.match(markup, /file-1\.html/); |
| 21 | assert.doesNotMatch(markup, /file-5\.html/); |
| 22 | assert.match(markup, /5/); |
| 23 | assert.equal((markup.match(/presented-file__split/g) ?? []).length, 4, "every presented card uses one split open control"); |
| 24 | |
| 25 | const changes = renderToStaticMarkup(<LocaleProvider><ModifiedFiles tabId="tab-1" files={[ |
| 26 | { path: "src/app.ts", toolCallId: "write-1", operation: "modified" }, |
| 27 | ]} summary={{ |
| 28 | preset: "balanced", verdict: "complete", mutations: 2, changed_files: 2, checks_passed: 0, checks_failed: 0, |
| 29 | checks_suppressed: 0, review: "passed", constraint_degraded: false, |
| 30 | receipt: { verdict: "complete", diff: { id: "result", turn: 1, coverage: "complete", added: 7, removed: 3, reasons: [], files: [ |
| 31 | { path: "src/app.ts", kind: "modify", added: 5, removed: 2 }, |
| 32 | { path: "README.md", kind: "modify", added: 2, removed: 1 }, |
| 33 | ] } }, |
| 34 | }} /></LocaleProvider>); |
| 35 | assert.match(changes, /Edited 2 files/); |
| 36 | assert.match(changes, /\+7/); |
| 37 | assert.match(changes, /−3/); |
| 38 | assert.match(changes, /src\/app\.ts/); |
| 39 | assert.match(changes, /README\.md/); |
| 40 | console.log("presented files UI: changed summary and four-card split controls passed"); |
| 41 | |
| 42 | const mutationFiles = [{ path: "src/app.ts", toolCallId: "edit-1", operation: "modified" as const }]; |
| 43 | const renderChanges = (diff?: NonNullable<WireCompletionSummary["receipt"]>["diff"]) => renderToStaticMarkup( |
| 44 | <LocaleProvider><ModifiedFiles files={mutationFiles} summary={diff ? { |
| 45 | preset: "balanced", verdict: "complete", mutations: 2, changed_files: diff.files.length, |
| 46 | checks_passed: 0, checks_failed: 0, checks_suppressed: 0, review: "passed", constraint_degraded: false, |
| 47 | receipt: { verdict: "complete", diff }, |
| 48 | } : undefined} /></LocaleProvider>, |
| 49 | ); |
| 50 | assert.equal(renderChanges({ id: "reverted", turn: 1, coverage: "complete", added: 0, removed: 0, reasons: [], files: [] }), "", |
| 51 | "a complete empty receipt must not resurrect reverted mutations"); |
| 52 | const partial = renderChanges({ id: "partial", turn: 1, coverage: "partial", added: 4, removed: 2, reasons: ["limited"], files: [ |
| 53 | { path: "src/app.ts", kind: "modify", added: 4, removed: 2 }, |
| 54 | ] }); |
| 55 | assert.match(partial, /Partial statistics/); |
| 56 | assert.match(partial, /\+4/); |
| 57 | const partialEmpty = renderChanges({ id: "partial-empty", turn: 1, coverage: "partial", added: 0, removed: 0, reasons: [], files: [] }); |
| 58 | assert.match(partialEmpty, /src\/app.ts/, "an incomplete empty inventory must preserve known mutation paths"); |
| 59 | assert.match(partialEmpty, /Partial statistics/); |
| 60 | assert.doesNotMatch(partialEmpty, /\+0|−0/); |
| 61 | const unknown = renderChanges(); |
| 62 | assert.match(unknown, /src\/app.ts/); |
| 63 | assert.doesNotMatch(unknown, /\+0|−0/, "mutation calls alone cannot establish net line counts"); |
| 64 | const uncounted = renderChanges({ id: "uncounted", turn: 1, coverage: "partial", added: 0, removed: 0, reasons: [], files: [ |
| 65 | { path: "src/app.ts", kind: "modify", uncounted: true }, |
| 66 | ] }); |
| 67 | assert.doesNotMatch(uncounted, /\+0|−0/); |
| 68 | const modes = renderChanges({ id: "mode", turn: 1, coverage: "complete", added: 0, removed: 0, reasons: [], files: [ |
| 69 | { path: "src/app.ts", kind: "modify", modeOnly: true }, |
| 70 | ] }); |
| 71 | assert.match(modes, /Permissions only/); |
| 72 | console.log("presented files UI: reverted, partial, unknown and permissions-only receipts passed"); |
| 73 |