Skip to content
Source
Sign inSign up

enrichedLinkPresentation.test.ts

168 lines5.7 KB

1import { describe, expect, it } from "vite-plus/test";
2import type { ComposerContextId, EnvironmentId, ThreadId } from "@t3tools/contracts";
3import {
4 enrichedLinkVariantPattern,
5 buildEnrichedModelReferences,
6 enrichedModelLinkRegex,
7 enrichedModelReference,
8 enrichedTextLinkRegex,
9 enrichedSkillLinkRegex,
10 enrichedThreadLinkPresentation,
11 ENRICHED_INLINE_FILE_LINK_REGEX,
12 threadChipTargets,
13} from "./enrichedLinkPresentation";
14​
15describe("Enriched link presentation", () => {
16 it("recognizes file-shaped code without turning commands or URLs into links", () => {
17 for (const path of [
18 "src/app.tsx",
19 "./README.md",
20 "/tmp/report.md",
21 "C:\\repo\\app.tsx",
22 "app.tsx:12:3",
23 "src/http.ts:42-64",
24 "http.ts:42-64",
25 ]) {
26 expect(ENRICHED_INLINE_FILE_LINK_REGEX.test(path), path).toBe(true);
27 }
28 for (const text of [
29 "npm run dev",
30 "port:3000",
31 "port:3000-3010",
32 "TODO:12",
33 "example.com",
34 "https://example.com/app.tsx",
35 "x + y",
36 "README.md",
37 "/app/settings",
38 ]) {
39 expect(ENRICHED_INLINE_FILE_LINK_REGEX.test(text), text).toBe(false);
40 }
41 });
42​
43 it("only recognizes complete known skill names and escapes their punctuation", () => {
44 const regex = enrichedSkillLinkRegex([{ name: "check" }, { name: "check.mobile" }]);
45 expect(regex?.exec("Try $check.mobile next")?.[0]).toBe("$check.mobile");
46 expect(regex?.exec("Try $check-mobile next")).toBeNull();
47 expect(regex?.exec("Try $checkXmobile next")).toBeNull();
48 expect(regex?.exec("prefix$check next")).toBeNull();
49 expect(enrichedSkillLinkRegex([])).toBeNull();
50 });
51​
52 it("recognizes only complete known models in prose and resolves their display names", () => {
53 const models = buildEnrichedModelReferences([
54 {
55 instanceId: "codex",
56 driver: "codex",
57 models: [
58 { slug: "gpt-6.1", name: "OpenAI GPT-6.1", subProvider: "OpenAI" },
59 { slug: "gpt-6.1-sol[1m]", name: "GPT-6.1 Sol" },
60 ],
61 },
62 ]);
63 const regex = enrichedModelLinkRegex(models);
64 for (const text of [
65 "Try model:codex/gpt-6.1 next",
66 "Try (model:codex/gpt-6.1).",
67 "Try [model:codex/gpt-6.1] next",
68 '"model:codex/gpt-6.1"',
69 ]) {
70 const source = regex?.exec(text)?.[0];
71 expect(source, text).toBe("model:codex/gpt-6.1");
72 expect(enrichedModelReference(source!, models)?.label).toBe("GPT-6.1");
73 }
74 expect(regex?.exec("Try model:codex/gpt-6.1-sol[1m].")?.[0]).toBe(
75 "model:codex/gpt-6.1-sol[1m]",
76 );
77 for (const text of [
78 "model:openai/gpt-4o",
79 "model:codex/gpt-6.1-extra",
80 "model:codex/gpt-6.1.2",
81 "model:codex/gpt-6X1",
82 "premodel:codex/gpt-6.1",
83 ]) {
84 expect(regex?.exec(text), text).toBeNull();
85 }
86 expect(enrichedModelReference("model:openai/gpt-4o", models)).toBeUndefined();
87 expect(enrichedModelLinkRegex([])).toBeNull();
88 expect(enrichedTextLinkRegex([], [])).toBeNull();
89 const combined = enrichedTextLinkRegex([{ name: "review" }], models);
90 expect(
91 "Use $review with (model:codex/gpt-6.1).".match(new RegExp(combined!.source, "gu")),
92 ).toEqual(["$review", "model:codex/gpt-6.1"]);
93 });
94​
95 it("matches only the complete URL, including literal regex metacharacters", () => {
96 const url = "file:///workspace/app[1].tsx?line=3+4";
97 const pattern = new RegExp(enrichedLinkVariantPattern(url));
98 expect(pattern.test(url)).toBe(true);
99 expect(pattern.test(`${url}/child`)).toBe(false);
100 expect(pattern.test("file:///workspace/app1Xtsx?line=34")).toBe(false);
101 });
102​
103 it("presents thread links as thread chips and leaves other links alone", () => {
104 for (const href of ["t3-thread://v1/thread%3Aproject%3A1", "t3-thread://v2/env/thread_1"]) {
105 expect(enrichedThreadLinkPresentation(href), href).toEqual({
106 accent: "#009c96",
107 icon: { kind: "chip", name: "messages-square" },
108 });
109 }
110 for (const href of [
111 "https://github.com/example/repo/pull/7",
112 "https://example.com",
113 "t3-context://v1/thread/thread_1",
114 ]) {
115 expect(enrichedThreadLinkPresentation(href), href).toBeUndefined();
116 }
117 });
118​
119 it("finds each linked thread once and resolves thread records to their own environment", () => {
120 const here = "env-here" as EnvironmentId;
121 const there = "env-there" as EnvironmentId;
122 expect(
123 threadChipTargets(
124 here,
125 "See [A](t3-thread://v1/thread_a), [A again](t3-thread://v1/thread_a), [B](t3-thread://v1/thread_b) and [D](t3-thread://v2/env-there/thread_d).",
126 [
127 {
128 version: 1,
129 contextId: "thr1" as ComposerContextId,
130 kind: "thread",
131 label: "C",
132 environmentId: there,
133 threadId: "thread_c" as ThreadId,
134 title: "C",
135 },
136 ],
137 ),
138 ).toEqual([
139 { href: "t3-thread://v1/thread_a", environmentId: here, threadId: "thread_a", link: true },
140 { href: "t3-thread://v1/thread_b", environmentId: here, threadId: "thread_b", link: true },
141 {
142 href: "t3-thread://v2/env-there/thread_d",
143 environmentId: there,
144 threadId: "thread_d",
145 link: true,
146 },
147 {
148 href: "t3-context://v1/thread/thr1",
149 environmentId: there,
150 threadId: "thread_c",
151 link: false,
152 },
153 ]);
154 expect(threadChipTargets(here, "No links here.")).toEqual([]);
155 });
156​
157 it("finds thread links with angle-bracketed destinations", () => {
158 const here = "env-here" as EnvironmentId;
159 expect(threadChipTargets(here, "See [A](<t3-thread://v2/env-here/thread_a>).")).toEqual([
160 {
161 href: "t3-thread://v2/env-here/thread_a",
162 environmentId: here,
163 threadId: "thread_a",
164 link: true,
165 },
166 ]);
167 });
168});