Open History to see the last commit
1import "~/index.css";
2
3import { EnvironmentId, MessageId, ThreadId } from "@t3tools/contracts";
4import { serializeAssistantCitation } from "@t3tools/shared/assistantCitations";
5import { expect, it } from "vite-plus/test";
6
7import { formatInlineContextReference } from "~/lib/composerContextReferences";
8import {
9 environmentContextRecord,
10 environmentContextReference,
11} from "~/lib/composerContextRecords";
12import {
13 composerContextRecordsFromDraft,
14 EMPTY_COMPOSER_CONTEXT_RECORDS,
15} from "../composerContextPresentation";
16import { render } from "../../../test/render";
17import { CollapsedComposerPromptPreview } from "./CollapsedComposerPromptPreview";
18
19function quote(text: string, comment?: string): string {
20 return serializeAssistantCitation({
21 version: 1,
22 environmentId: EnvironmentId.make("environment:test"),
23 threadId: ThreadId.make("thread:test"),
24 messageId: MessageId.make("message:assistant"),
25 text,
26 start: 0,
27 end: text.length,
28 prefix: "",
29 suffix: "",
30 ...(comment === undefined ? {} : { comment }),
31 });
32}
33
34it("shows quotes, file mentions, context references and skills as chips", async () => {
35 const buildHost = environmentContextRecord(EnvironmentId.make("environment:build"), "Build host");
36 // No draft record backs this one, so the editor shows it as unresolved and so must the row.
37 const terminal = formatInlineContextReference({
38 kind: "terminal",
39 contextId: "terminal_1",
40 label: "Terminal 1",
41 });
42 const environment = formatInlineContextReference(environmentContextReference(buildHost));
43 const prompt = `${quote("Cache the parsed config.")} vs ${quote("Retry on 503.", "Also on 429?")} in [package.json](package.json) and [file.txt](C:\\dir\\file.txt) per ${terminal} on ${environment} with $review-pr please`;
44
45 await using screen = await render(
46 <CollapsedComposerPromptPreview
47 prompt={prompt}
48 skills={[
49 { name: "review-pr", displayName: "Review PR", path: "/skills/review-pr", enabled: true },
50 ]}
51 contextRecords={composerContextRecordsFromDraft({
52 terminalContexts: [],
53 environmentContexts: [buildHost],
54 })}
55 theme="light"
56 />,
57 // The inert preview has no router-dependent children.
58 { router: false },
59 );
60 const { container } = screen;
61
62 const chips = [...container.querySelectorAll('[data-slot="context-chip"]')];
63 expect(chips.map((chip) => chip.textContent)).toEqual([
64 "Cache the parsed config.",
65 "Also on 429?",
66 "package.json",
67 "file.txt",
68 "Terminal 1",
69 "Build host",
70 "Review PR",
71 ]);
72 expect(
73 chips
74 .filter((chip) => chip.getAttribute("data-state") === "unresolved")
75 .map((c) => c.textContent),
76 ).toEqual(["Terminal 1"]);
77 expect(chips.every((chip) => chip.querySelector("svg") !== null)).toBe(true);
78 expect(container.textContent).toBe(
79 "Cache the parsed config. vs Also on 429? in package.json and file.txt per Terminal 1 on Build host with Review PR please",
80 );
81 // The row is one tap target that expands the composer, so chips stay inert.
82 expect(container.querySelector("button, a, [tabindex]")).toBeNull();
83});
84
85it("leaves unknown skills as the text the user typed", async () => {
86 await using screen = await render(
87 <CollapsedComposerPromptPreview
88 prompt="run $missing now"
89 skills={[]}
90 contextRecords={EMPTY_COMPOSER_CONTEXT_RECORDS}
91 theme="dark"
92 />,
93 // The inert preview has no router-dependent children.
94 { router: false },
95 );
96 const { container } = screen;
97
98 expect(container.querySelector('[data-slot="context-chip"]')).toBeNull();
99 expect(container.textContent).toBe("run $missing now");
100});