1import { describe, expect, it } from "vite-plus/test";
2
3import {
4 appendInlineContextReference,
5 collectInlineContextIds,
6 ensureInlineContextReferences,
7 formatInlineContextReference,
8 insertInlineContextReference,
9 inlineContextReferenceReplacement,
10 removeInlineContextReference,
11 stripInlineContextReferences,
12 toComposerContextId,
13 toKindScopedComposerContextId,
14} from "./composerContextReferences";
15
16const review = { kind: "review-comment", contextId: "rc-1", label: "a.ts L4" };
17const preview = { kind: "preview-annotation", contextId: "pa-1", label: "Checkout" };
18const reviewLink = "[a.ts L4](t3-context://v1/review-comment/rc-1)";
19const previewLink = "[Checkout](t3-context://v1/preview-annotation/pa-1)";
20
21describe("composerContextReferences", () => {
22 it.each([
23 { prompt: "before selected after", start: 7, end: 15, expected: `before ${reviewLink} after` },
24 { prompt: "selected", start: 0, end: 8, expected: `${reviewLink} ` },
25 { prompt: "aSELECTb", start: 1, end: 7, expected: `a ${reviewLink} b` },
26 ])(
27 "replaces selected text in '$prompt' with the attachment chip",
28 ({ prompt, start, end, expected }) => {
29 const edit = inlineContextReferenceReplacement(prompt, { start, end }, [review]);
30 expect(`${prompt.slice(0, edit.start)}${edit.text}${prompt.slice(edit.end)}`).toBe(expected);
31 },
32 );
33 it("formats, collects and strips references of any kind", () => {
34 expect(formatInlineContextReference(review)).toBe(reviewLink);
35 const prompt = `x ${reviewLink} y ${previewLink} ${reviewLink}`;
36 expect(collectInlineContextIds(prompt)).toEqual(["rc-1", "pa-1"]);
37 expect(stripInlineContextReferences(prompt)).toBe("x y ");
38 });
39
40 it("inserts at the cursor and appends at the end with boundary spacing", () => {
41 expect(insertInlineContextReference("ab", 1, review)).toEqual({
42 prompt: `a ${reviewLink} b`,
43 cursor: 2 + reviewLink.length + 1,
44 });
45 expect(appendInlineContextReference("hello", preview)).toBe(`hello ${previewLink} `);
46 expect(appendInlineContextReference("", preview)).toBe(`${previewLink} `);
47 });
48
49 it("removes every occurrence of an id and reports the earliest cursor", () => {
50 const prompt = `${reviewLink} mid ${reviewLink} end`;
51 expect(removeInlineContextReference(prompt, "rc-1")).toEqual({
52 prompt: "mid end",
53 cursor: 0,
54 });
55 expect(removeInlineContextReference("plain", "rc-1")).toEqual({ prompt: "plain", cursor: 5 });
56 });
57
58 it("appends links only for records the prompt does not mention", () => {
59 expect(ensureInlineContextReferences(`see ${reviewLink}`, [review, preview])).toBe(
60 `see ${reviewLink} ${previewLink} `,
61 );
62 expect(ensureInlineContextReferences("", [review])).toBe(`${reviewLink} `);
63 expect(ensureInlineContextReferences(reviewLink, [review])).toBe(reviewLink);
64 });
65});
66
67describe("toComposerContextId", () => {
68 it("keeps same-kind raw IDs distinct when one contains the namespace prefix", () => {
69 expect(toKindScopedComposerContextId("image", "x")).toBe("image_x");
70 expect(toKindScopedComposerContextId("image", "image_x")).toBe("image_image_x");
71 });
72 it("keeps ids that already fit the grammar and folds the rest deterministically", () => {
73 expect(toComposerContextId("file-comment-1700-1")).toBe("file-comment-1700-1");
74 const folded = toComposerContextId("pull-request-selection:src/a.ts:4-9");
75 expect(folded).toMatch(/^pull-request-selection-src-a-ts-4-9-[0-9a-f]{16}$/);
76 expect(toComposerContextId("pull-request-selection:src/a.ts:4-9")).toBe(folded);
77 expect(toComposerContextId("pull-request-selection:src/b.ts:4-9")).not.toBe(folded);
78 expect(toComposerContextId("::")).toMatch(/^ctx-[0-9a-f]{16}$/);
79 });
80 it("tells apart producer ids that agree past the slug's truncation point", () => {
81 // The slug keeps 48 characters, so only the digest distinguishes these two.
82 const shared = `pull-request-finding:${"a".repeat(60)}`;
83 const first = toComposerContextId(`${shared}:1`);
84 const second = toComposerContextId(`${shared}:2`);
85
86 expect(first).not.toBe(second);
87 expect(first.length).toBeLessThanOrEqual(128);
88 });
89});