| 1 | // Fixture for bench/chat-file-reference.mjs: one assistant answer that names a |
| 2 | // file in inline code and shows an SVG fence, wired to the same providers the |
| 3 | // transcript uses. |
| 4 | import { Fragment, useSyncExternalStore } from "react"; |
| 5 | import { createRoot } from "react-dom/client"; |
| 6 | import { LocaleProvider } from "../src/lib/i18n"; |
| 7 | import { parseMarkdownToBlocks } from "../src/lib/markdownPipeline"; |
| 8 | import { hastBlockToJsx } from "../src/lib/hastJsx"; |
| 9 | import { createComponents } from "../src/components/markdownComponents"; |
| 10 | import { ChatFileScopeProvider, ChatFileTurnProvider, useChatFileCandidateReport } from "../src/components/ChatFileLinkContext"; |
| 11 | import { fileNavigationOwner } from "../src/lib/fileNavigationCommands"; |
| 12 | import { useActivityBarStore } from "../src/store/activityBar"; |
| 13 | import { installDesktopHostStub } from "../src/__tests__/desktopHostStub"; |
| 14 | import type { AppBindings } from "../src/lib/bridge"; |
| 15 | |
| 16 | // Deliberately namespace-free: the model's usual spelling, and the one |
| 17 | // that used to fail to render as an image. |
| 18 | const SVG = `<svg viewBox="0 0 20 10"> |
| 19 | <defs><linearGradient id="g"><stop offset="0" stop-color="#f60"/></linearGradient></defs> |
| 20 | <rect width="20" height="10" fill="url(#g)"/> |
| 21 | <text x="2" y="7">preview</text> |
| 22 | </svg>`; |
| 23 | |
| 24 | // The sanitizer runs in the Go host, which this browser fixture cannot call. |
| 25 | // SANITIZED is therefore the host's real output for SVG above, captured byte |
| 26 | // for byte and pinned by TestMarkdownSVGBenchFixtureMatchesTheSanitizer: if the |
| 27 | // host stops producing these bytes that Go test fails and this fixture must be |
| 28 | // updated. Rendering a hand-written stand-in instead is exactly how a preview |
| 29 | // that never loads in a browser still passes. |
| 30 | const SANITIZED = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 10"> |
| 31 | <defs><linearGradient id="g"><stop offset="0" stop-color="#f60"></stop></linearGradient></defs> |
| 32 | <rect width="20" height="10" fill="url(#g)"></rect> |
| 33 | <text x="2" y="7">preview</text> |
| 34 | </svg>`; |
| 35 | |
| 36 | installDesktopHostStub(({ |
| 37 | main: { |
| 38 | App: { |
| 39 | ResolveChatFileReferencesForTab: async (_tabId: string, turnKey: string, candidates: Array<{ key: string; path: string }>) => ({ |
| 40 | turnKey, |
| 41 | references: candidates.map(candidate => candidate.path === "/repo/out/diagram.svg" |
| 42 | ? { key: candidate.key, path: candidate.path, status: "resolved", displayPath: "out/diagram.svg", kind: "image", actions: ["preview", "reveal-tree", "copy-path", "save-copy", "source", "open-native", "reveal-native"] } |
| 43 | : { key: candidate.key, path: candidate.path, status: "unavailable", actions: [], reason: "not-found" }), |
| 44 | }), |
| 45 | SanitizeMarkdownSVG: async (content: string) => ({ ok: true, svg: content.includes("linearGradient id=\"g\"") && content.includes("/>") ? SANITIZED : content }), |
| 46 | ResolveReferencePathForTab: async (_tabId: string, path: string) => path.startsWith("/") ? path : `/repo/${path}`, |
| 47 | ReadReferenceFileForTab: async (_tabId: string, path: string) => ({ path, body: "PREVIEW BODY", size: 12, truncated: false, binary: false }), |
| 48 | } as Partial<AppBindings> as AppBindings, |
| 49 | }, |
| 50 | }).main.App); |
| 51 | |
| 52 | const blocks = parseMarkdownToBlocks(`Wrote \`/repo/out/diagram.svg\` and used \`/repo/out/missing.svg\`.\n\n\`\`\`svg\n${SVG}\n\`\`\`\n`); |
| 53 | const components = createComponents(false); |
| 54 | |
| 55 | /** Surfaces the running owner's committed navigation result. */ |
| 56 | function RequestProbe() { |
| 57 | const owner = fileNavigationOwner(); |
| 58 | const snapshot = useSyncExternalStore(owner.subscribe, () => { |
| 59 | const dock = useActivityBarStore.getState().tabs.find(tab => tab.type === "file"); |
| 60 | return dock ? owner.getSnapshot(dock.id) : null; |
| 61 | }); |
| 62 | const navigation = snapshot?.navigation; |
| 63 | return <pre id="request" hidden>{navigation ? JSON.stringify({ |
| 64 | source: navigation.resource.access.source, |
| 65 | path: navigation.resource.requestedPath, |
| 66 | action: navigation.params.action, |
| 67 | }) : ""}</pre>; |
| 68 | } |
| 69 | |
| 70 | function Reporter() { |
| 71 | useChatFileCandidateReport(blocks, 1); |
| 72 | return null; |
| 73 | } |
| 74 | |
| 75 | createRoot(document.getElementById("root")!).render( |
| 76 | <LocaleProvider> |
| 77 | <ChatFileScopeProvider scopeKey="bench" tabId="tab-bench"> |
| 78 | <ChatFileTurnProvider turnKey="turn-bench" factsVersion={1} presentedFiles={[]} modifiedFiles={[]} tabId="tab-bench"> |
| 79 | <Reporter /> |
| 80 | <div className="md">{blocks.map(block => <Fragment key={block.key}>{hastBlockToJsx(block, components)}</Fragment>)}</div> |
| 81 | </ChatFileTurnProvider> |
| 82 | </ChatFileScopeProvider> |
| 83 | <RequestProbe /> |
| 84 | </LocaleProvider>, |
| 85 | ); |
| 86 |