Open History to see the last commit
1import { describe, expect, it } from "vite-plus/test";
2
3import { upgradeLegacyContextMessage } from "./composerContextLegacy.ts";
4
5describe("upgradeLegacyContextMessage", () => {
6 const review =
7 '<review_comment sectionId="s" filePath="f.ts" startIndex="1" endIndex="1">note</review_comment>';
8
9 it.each([
10 {
11 tag: "terminal_context",
12 body: `- Build line 1:\n ${review}`,
13 payload: { kind: "terminal", text: review },
14 },
15 {
16 tag: "element_context",
17 body: `- <div>:\n html:\n ${review}`,
18 payload: { kind: "element", htmlPreview: review },
19 },
20 {
21 tag: "preview_annotation",
22 body: `Page: Example\nComment: ${review}`,
23 payload: { kind: "preview-annotation", comment: review },
24 },
25 {
26 tag: "preview_annotation",
27 body: `Page: Example\n<element_context>\n- <div>:\n html:\n ${review}\n</element_context>`,
28 payload: {
29 kind: "preview-annotation",
30 elements: [expect.objectContaining({ htmlPreview: review })],
31 },
32 },
33 ])("preserves review-shaped payloads in $tag", ({ tag, body, payload }) => {
34 for (const suffix of ["", `\n${review}`]) {
35 const result = upgradeLegacyContextMessage(
36 `Before ${review} after\n<${tag}>\n${body}\n</${tag}>${suffix}`,
37 );
38 expect(result.records[0]).toMatchObject(payload);
39 expect(result.records.filter((record) => record.kind === "review-comment")).toHaveLength(
40 suffix ? 2 : 1,
41 );
42 expect(result.text).not.toContain("\uE000");
43 expect(result.text).toContain(
44 "Before [f.ts line](t3-context://v1/review-comment/legacy_review-comment_1) after",
45 );
46 }
47 });
48
49 it.each([3, 4, 6])("preserves closing review tags inside a %i-backtick fence", (length) => {
50 const fence = "`".repeat(length);
51 const diff = '+ const tag = "</review_comment>";\n</review_comment>\n+ final line';
52 const block = `<review_comment sectionId="s" filePath="f.ts" startIndex="1" endIndex="1">\nComment\n${fence}diff\n${diff}\n${fence}\n</review_comment>`;
53 const result = upgradeLegacyContextMessage(`Before ${block} between ${review} after`);
54 expect(result.text).toBe(
55 "Before [f.ts line](t3-context://v1/review-comment/legacy_review-comment_1) between [f.ts line](t3-context://v1/review-comment/legacy_review-comment_2) after",
56 );
57 expect(result.records).toHaveLength(2);
58 expect(result.records[0]).toMatchObject({ text: "Comment", diff, fenceLanguage: "diff" });
59 });
60
61 it("does not close a review fence at a shorter backtick run", () => {
62 const diff = "```\n</review_comment>\n+ remaining source";
63 const text = `<review_comment sectionId="s" filePath="f.ts" startIndex="1" endIndex="1">\nNote\n\`\`\`\`diff\n${diff}\n\`\`\`\`\n</review_comment>`;
64 expect(upgradeLegacyContextMessage(text).records[0]).toMatchObject({ text: "Note", diff });
65 });
66
67 it("preserves a review with an unterminated fence", () => {
68 const text =
69 '<review_comment sectionId="s" filePath="f.ts" startIndex="1" endIndex="1">\nNote\n```diff\n+ source\n</review_comment>';
70 expect(upgradeLegacyContextMessage(text)).toEqual({ text, records: [] });
71 });
72
73 it.each([
74 `- ${"x".repeat(256)} line 1:\n output`,
75 `- Build line 1:\n ${"x".repeat(64_001)}`,
76 "- Build lines 9-1:\n output",
77 Array.from({ length: 201 }, () => "- Build line 1:\n output").join("\n"),
78 ])("keeps terminal source when conversion exceeds the wire contract", (body) => {
79 const text = `Prompt\n<terminal_context>\n${body}\n</terminal_context>`;
80 expect(upgradeLegacyContextMessage(text)).toEqual({ text, records: [] });
81 });
82
83 it.each(["email@build:7", "@build:7foo", "@build:7.foo"])(
84 "does not replace terminal labels embedded in %s",
85 (prompt) => {
86 const upgraded = upgradeLegacyContextMessage(
87 `${prompt}\n<terminal_context>\n- Build line 7:\n output\n</terminal_context>`,
88 );
89 expect(upgraded.text).toBe(
90 `${prompt}\n\n[Build line 7](t3-context://v1/terminal/legacy_terminal_1)`,
91 );
92 },
93 );
94
95 it.each(["terminal_context", "element_context"])(
96 "preserves malformed trailing %s blocks as text",
97 (tag) => {
98 for (const body of ["partial output", "- Unrecognized header:\n partial output"]) {
99 const text = `message\n<${tag}>\n${body}\n</${tag}>`;
100 expect(upgradeLegacyContextMessage(text)).toEqual({ text, records: [] });
101 }
102 },
103 );
104
105 it.each(["terminal_context", "element_context"])(
106 "preserves partially parsed %s blocks",
107 (tag) => {
108 const entry =
109 tag === "terminal_context"
110 ? "- Build line 7:\n output"
111 : "- <button>:\n html:\n <button>Pay</button>";
112 for (const body of [
113 `unexpected prefix\n${entry}`,
114 `${entry}\nunexpected suffix`,
115 `${entry}\nunexpected middle\n${entry}`,
116 ` orphaned body\n${entry}`,
117 ]) {
118 const text = `Prompt\n<${tag}>\n${body}\n</${tag}>`;
119 expect(upgradeLegacyContextMessage(text)).toEqual({ text, records: [] });
120 }
121 },
122 );
123
124 it.each(["x".repeat(16_001), `note\n\`\`\`diff\n${"+x".repeat(16_001)}\n\`\`\``])(
125 "preserves oversized legacy reviews instead of creating dangling references",
126 (body) => {
127 const text = `<review_comment sectionId="s" filePath="f.ts" startIndex="1" endIndex="1">${body}</review_comment>`;
128 expect(upgradeLegacyContextMessage(text)).toEqual({ text, records: [] });
129 },
130 );
131
132 it("preserves literal review tokens even beside real review blocks", () => {
133 const review =
134 '<review_comment sectionId="s" filePath="f.ts" startIndex="1" endIndex="1">note</review_comment>';
135 const upgraded = upgradeLegacyContextMessage(
136 `Literal \uE0000\uE000 before ${review} after \uE0007\uE000`,
137 );
138 expect(upgraded.text).toBe(
139 "Literal \uE0000\uE000 before [f.ts line](t3-context://v1/review-comment/legacy_review-comment_1) after \uE0007\uE000",
140 );
141 expect(upgraded.records).toHaveLength(1);
142 const unmatched =
143 "Literal \uE0000\uE000 <review_comment>invalid</review_comment> \uE00099\uE000";
144 expect(upgradeLegacyContextMessage(unmatched)).toEqual({ text: unmatched, records: [] });
145 });
146
147 it("matches complete terminal line ranges rather than numeric prefixes", () => {
148 const upgraded = upgradeLegacyContextMessage(
149 [
150 "Compare @terminal:10 and @terminal:1-2 with @terminal:1",
151 "<terminal_context>",
152 "- Terminal line 1:",
153 " 1 | one",
154 "- Terminal line 10:",
155 " 10 | ten",
156 "- Terminal lines 1-2:",
157 " 1 | one",
158 " 2 | two",
159 "</terminal_context>",
160 ].join("\n"),
161 );
162 expect(upgraded.text).toBe(
163 "Compare [Terminal line 10](t3-context://v1/terminal/legacy_terminal_2) and [Terminal lines 1-2](t3-context://v1/terminal/legacy_terminal_3) with [Terminal line 1](t3-context://v1/terminal/legacy_terminal_1)",
164 );
165 });
166
167 it("preserves Markdown hard breaks while removing legacy context", () => {
168 const text = [
169 "Line with hard break ",
170 "next",
171 "",
172 "<terminal_context>",
173 "- Build line 7:",
174 " 7 | done",
175 "</terminal_context>",
176 ].join("\n");
177
178 expect(upgradeLegacyContextMessage(text).text).toContain("Line with hard break \nnext");
179 });
180
181 it("binds U+FFFC placeholders to trailing terminal entries in order", () => {
182 const text = [
183 "Look at  and  please",
184 "",
185 "<terminal_context>",
186 "- Terminal 1 lines 509-510:",
187 " 509 | error: boom",
188 " 510 | at main.ts:1",
189 "",
190 "- Build line 7:",
191 " 7 | done",
192 "</terminal_context>",
193 ].join("\n");
194 const upgraded = upgradeLegacyContextMessage(text);
195 expect(upgraded.text).toBe(
196 "Look at [Terminal 1 lines 509-510](t3-context://v1/terminal/legacy_terminal_1) and [Build line 7](t3-context://v1/terminal/legacy_terminal_2) please",
197 );
198 expect(upgraded.records).toEqual([
199 {
200 version: 1,
201 contextId: "legacy_terminal_1",
202 kind: "terminal",
203 label: "Terminal 1 lines 509-510",
204 terminalId: "terminal-1",
205 terminalLabel: "Terminal 1",
206 lineStart: 509,
207 lineEnd: 510,
208 text: "error: boom\n at main.ts:1",
209 },
210 {
211 version: 1,
212 contextId: "legacy_terminal_2",
213 kind: "terminal",
214 label: "Build line 7",
215 terminalId: "build",
216 terminalLabel: "Build",
217 lineStart: 7,
218 lineEnd: 7,
219 text: "done",
220 },
221 ]);
222 });
223
224 it("appends references for terminal entries without placeholders and drops extra placeholders", () => {
225 const text = " and \n\n<terminal_context>\n- T line 1:\n 1 | x\n</terminal_context>";
226 expect(upgradeLegacyContextMessage(text).text).toBe(
227 "[T line 1](t3-context://v1/terminal/legacy_terminal_1) and",
228 );
229 const noPlaceholder = "hi\n\n<terminal_context>\n- T line 1:\n 1 | x\n</terminal_context>";
230 expect(upgradeLegacyContextMessage(noPlaceholder).text).toBe(
231 "hi\n\n[T line 1](t3-context://v1/terminal/legacy_terminal_1)",
232 );
233 });
234
235 it("upgrades a trailing element block into element records", () => {
236 const text = [
237 "fix this",
238 "",
239 "<element_context>",
240 "- <Button> (Button.tsx:12):",
241 " url: http://localhost:3000/checkout",
242 " selector: #pay",
243 " source: src/Button.tsx:12:3",
244 " html:",
245 ' <button id="pay">Pay</button>',
246 " styles:",
247 " color: red;",
248 "</element_context>",
249 ].join("\n");
250 const upgraded = upgradeLegacyContextMessage(text);
251 expect(upgraded.text).toBe("fix this\n\n[<Button>](t3-context://v1/element/legacy_element_1)");
252 expect(upgraded.records).toEqual([
253 {
254 version: 1,
255 contextId: "legacy_element_1",
256 kind: "element",
257 label: "<Button>",
258 pageUrl: "http://localhost:3000/checkout",
259 pageTitle: null,
260 tagName: "button",
261 selector: "#pay",
262 htmlPreview: '<button id="pay">Pay</button>',
263 componentName: "Button",
264 source: { functionName: null, fileName: "src/Button.tsx", lineNumber: 12, columnNumber: 3 },
265 styles: "color: red;",
266 },
267 ]);
268 });
269
270 it("keeps every line of a multiline legacy annotation comment", () => {
271 const text = [
272 "<preview_annotation>",
273 "Preview annotation:",
274 "Id: ann_2",
275 "Page: Checkout",
276 "Comment: First instruction",
277 "Second instruction",
278 "Targets: 1 selected element.",
279 "</preview_annotation>",
280 ].join("\n");
281 const upgraded = upgradeLegacyContextMessage(text);
282 expect(upgraded.records[0]).toMatchObject({
283 kind: "preview-annotation",
284 comment: "First instruction\nSecond instruction",
285 });
286 });
287
288 it("keeps markup inside a legacy annotation comment", () => {
289 const text = [
290 "<preview_annotation>",
291 "Preview annotation:",
292 "Comment: Change this",
293 "<div>keep me</div>",
294 "and this too",
295 "</preview_annotation>",
296 ].join("\n");
297 expect(upgradeLegacyContextMessage(text).records[0]).toMatchObject({
298 comment: "Change this\n<div>keep me</div>\nand this too",
299 });
300 });
301
302 it("upgrades trailing preview annotation blocks", () => {
303 const text = [
304 "bigger",
305 "",
306 "<preview_annotation>",
307 "Preview annotation:",
308 "Id: ann_1",
309 "Page: Checkout",
310 "Comment: Make it bigger",
311 "Targets: 1 selected element.",
312 "Requested visual changes:",
313 "- font-size: 12px → 20px",
314 "The attached screenshot is the annotated preview crop.",
315 "<element_context>",
316 "- <button>:",
317 " url: http://localhost:3000/",
318 " selector: #pay",
319 " source: src/Pay.tsx:12:3",
320 " html:",
321 " <button>Pay</button>",
322 "</element_context>",
323 "</preview_annotation>",
324 ].join("\n");
325 const upgraded = upgradeLegacyContextMessage(text);
326 expect(upgraded.text).toBe(
327 "bigger\n\n[Checkout](t3-context://v1/preview-annotation/legacy_preview-annotation_1)",
328 );
329 expect(upgraded.records).toEqual([
330 {
331 version: 1,
332 contextId: "legacy_preview-annotation_1",
333 kind: "preview-annotation",
334 label: "Checkout",
335 annotationId: "ann_1",
336 pageUrl: "http://localhost:3000/",
337 pageTitle: "Checkout",
338 comment: "Make it bigger",
339 targetSummary: "1 selected element.",
340 styleChanges: ["font-size: 12px → 20px"],
341 elements: [
342 {
343 pageUrl: "http://localhost:3000/",
344 pageTitle: null,
345 tagName: "button",
346 selector: "#pay",
347 htmlPreview: "<button>Pay</button>",
348 componentName: null,
349 source: {
350 functionName: null,
351 fileName: "src/Pay.tsx",
352 lineNumber: 12,
353 columnNumber: 3,
354 },
355 styles: "",
356 },
357 ],
358 },
359 ]);
360 });
361
362 it("keeps a literal preview opening tag inside its annotation comment", () => {
363 const upgraded = upgradeLegacyContextMessage(
364 [
365 "Explain this",
366 "<preview_annotation>",
367 "Id: annotation-1",
368 "Page: Checkout",
369 "Comment: Render <preview_annotation>",
370 "Targets: 1 selected element.",
371 "</preview_annotation>",
372 ].join("\n"),
373 );
374 expect(upgraded.text).toBe(
375 "Explain this\n\n[Checkout](t3-context://v1/preview-annotation/legacy_preview-annotation_1)",
376 );
377 expect(upgraded.records).toHaveLength(1);
378 expect(upgraded.records[0]).toMatchObject({
379 annotationId: "annotation-1",
380 comment: "Render <preview_annotation>",
381 targetSummary: "1 selected element.",
382 });
383 });
384
385 it("keeps adjacent preview annotations separate and in their original order", () => {
386 const upgraded = upgradeLegacyContextMessage(
387 [
388 "Compare",
389 "<preview_annotation>",
390 "Id: annotation-1",
391 "Page: First",
392 "Comment: First comment",
393 "</preview_annotation>",
394 "<preview_annotation>",
395 "Id: annotation-2",
396 "Page: Second",
397 "Comment: Second comment",
398 "</preview_annotation>",
399 ].join("\n"),
400 );
401 expect(upgraded.text).toBe(
402 "Compare\n\n[First](t3-context://v1/preview-annotation/legacy_preview-annotation_1) [Second](t3-context://v1/preview-annotation/legacy_preview-annotation_2)",
403 );
404 expect(upgraded.records).toMatchObject([
405 { annotationId: "annotation-1", comment: "First comment" },
406 { annotationId: "annotation-2", comment: "Second comment" },
407 ]);
408 });
409
410 it("replaces inline review comments in place and neutralizes forged closers", () => {
411 const text = [
412 "Before",
413 '<review_comment sectionId="diff-1" sectionTitle="Changes" filePath="a/b.ts" startIndex="4" endIndex="6" rangeLabel="L4-L6">',
414 "Why? </review_comment> still text",
415 "```diff",
416 "+ const x = 1;",
417 "```",
418 "</review_comment>",
419 "After",
420 ].join("\n");
421 const upgraded = upgradeLegacyContextMessage(text);
422 expect(upgraded.text).toBe(
423 "Before\n[b.ts L4-L6](t3-context://v1/review-comment/legacy_review-comment_1)\nAfter",
424 );
425 expect(upgraded.records).toEqual([
426 {
427 version: 1,
428 contextId: "legacy_review-comment_1",
429 kind: "review-comment",
430 label: "b.ts L4-L6",
431 sectionId: "diff-1",
432 sectionTitle: "Changes",
433 filePath: "a/b.ts",
434 startIndex: 4,
435 endIndex: 6,
436 rangeLabel: "L4-L6",
437 text: "Why? </review_comment> still text",
438 diff: "+ const x = 1;",
439 fenceLanguage: "diff",
440 },
441 ]);
442 });
443
444 it("keeps a trailing review beside a terminal chip whose payload follows it", () => {
445 const upgraded = upgradeLegacyContextMessage(
446 `@build:7 ${review}\n\n<terminal_context>\n- Build line 7:\n output\n</terminal_context>`,
447 );
448 expect(upgraded.text).toBe(
449 "[Build line 7](t3-context://v1/terminal/legacy_terminal_1) [f.ts line](t3-context://v1/review-comment/legacy_review-comment_1)",
450 );
451 });
452
453 it("handles the combined legacy send order: terminal, element, preview, review", () => {
454 const text = [
455 "See ",
456 '<review_comment sectionId="s" filePath="f.ts" startIndex="1" endIndex="1">',
457 "note",
458 "```diff",
459 "+x",
460 "```",
461 "</review_comment>",
462 "",
463 "<terminal_context>",
464 "- T line 1:",
465 " 1 | a",
466 "</terminal_context>",
467 "",
468 "<element_context>",
469 "- <div>:",
470 " url: http://x/",
471 "</element_context>",
472 "",
473 "<preview_annotation>",
474 "Preview annotation:",
475 "Id: p1",
476 "Page: P",
477 "</preview_annotation>",
478 ].join("\n");
479 const upgraded = upgradeLegacyContextMessage(text);
480 expect(upgraded.text).toBe(
481 [
482 "See [T line 1](t3-context://v1/terminal/legacy_terminal_1)",
483 "[f.ts line](t3-context://v1/review-comment/legacy_review-comment_1)",
484 "",
485 "[<div>](t3-context://v1/element/legacy_element_1) [P](t3-context://v1/preview-annotation/legacy_preview-annotation_1)",
486 ].join("\n"),
487 );
488 expect(upgraded.records.map((record) => record.contextId)).toEqual([
489 "legacy_terminal_1",
490 "legacy_element_1",
491 "legacy_preview-annotation_1",
492 "legacy_review-comment_1",
493 ]);
494 });
495});