Skip to content
Source
Sign inSign up
Open History to see the last commit

codexMarkdownDirectives.test.ts

171 lines6.1 KB

1import { describe, expect, it } from "vite-plus/test";
2import remarkParse from "remark-parse";
3import { unified } from "unified";
4​
5import {
6 remarkCodexDirectives,
7 renderCodexDirectivesForCopy,
8 renderCodexInlineDirectivesAsMarkdown,
9 splitCodexArtifactTemplateMarkdown,
10} from "./codexMarkdownDirectives.ts";
11import { parseMarkdownFileLink } from "./markdownLinks.ts";
12​
13interface TestNode {
14 readonly type: string;
15 readonly value?: string;
16 readonly url?: string;
17 readonly position?: {
18 readonly start: { readonly offset?: number };
19 readonly end: { readonly offset?: number };
20 };
21 readonly data?: {
22 readonly hName?: string;
23 readonly hProperties?: Readonly<Record<string, unknown>>;
24 };
25 readonly children?: readonly TestNode[];
27​
28const FILE_CITATION = ':codex-file-citation{path="outputs/report.xlsx" purpose="output"}';
29const FOLLOW_UP =
30 ':codex-followup[Prepare print version]{prompt="Prepare the document for printing."}';
31const ARTIFACT_TEMPLATE =
32 '::artifact-template{skill_name="artifact-template-hello-world" skill_directory="/Users/test/.codex/skills/artifact-template-hello-world" display_name="Hello World" artifact_kind="document"}';
33​
34function parse(markdown: string): TestNode {
35 const processor = unified().use(remarkParse).use(remarkCodexDirectives);
36 return processor.runSync(processor.parse(markdown), { value: markdown }) as TestNode;
38​
39function parseOrdinaryMarkdown(markdown: string): TestNode {
40 return unified().use(remarkParse).parse(markdown) as TestNode;
42​
43describe("remarkCodexDirectives", () => {
44 it("renders a file citation as a link without changing its source position", () => {
45 const markdown = `Created ${FILE_CITATION}.`;
46 const link = parse(markdown).children?.[0]?.children?.[1];
47​
48 expect(link).toMatchObject({
49 type: "link",
50 url: "outputs/report.xlsx",
51 children: [{ type: "text", value: "report.xlsx" }],
52 position: {
53 start: { offset: markdown.indexOf(FILE_CITATION) },
54 end: { offset: markdown.indexOf(FILE_CITATION) + FILE_CITATION.length },
55 },
56 });
57 });
58​
59 it("renders an artifact template as semantic block metadata", () => {
60 expect(parse(ARTIFACT_TEMPLATE).children?.[0]).toMatchObject({
61 type: "paragraph",
62 children: [],
63 data: {
64 hName: "div",
65 hProperties: {
66 dataCodexArtifactTemplate: "true",
67 dataArtifactKind: "document",
68 dataDisplayName: "Hello World",
69 dataSkillName: "artifact-template-hello-world",
70 },
71 },
72 });
73 });
74​
75 it.each([
76 "Meeting at 10:30",
77 ":::note\ncontent\n:::",
78 ':codex-file-citation-extra{path="outputs/report.xlsx"}',
79 ':codex-followup-extra[Label]{prompt="Prompt"}',
80 ])("does not change unrelated colon syntax: %s", (markdown) => {
81 expect(parse(markdown)).toEqual(parseOrdinaryMarkdown(markdown));
82 });
83​
84 it.each([
85 ':codex-file-citation{purpose="output"}',
86 ":codex-followup[Prepare print version]",
87 ':codex-followup[ ]{prompt="Prepare the document for printing."}',
88 ])("keeps malformed supported directives literal: %s", (markdown) => {
89 expect(parse(markdown)).toEqual(parseOrdinaryMarkdown(markdown));
90 });
91});
92​
93describe.each([
94 { name: "renderCodexDirectivesForCopy", render: renderCodexDirectivesForCopy },
95 { name: "renderCodexInlineDirectivesAsMarkdown", render: renderCodexInlineDirectivesAsMarkdown },
96])("$name file citation round trips", ({ render }) => {
97 // regression: #12243 copied Codex citations keep their backslashes
98 it.each([
99 "C:\\Users\\test\\[draft]\\report.md",
100 "\\\\server\\share\\report.md",
101 "/tmp/report%5C.md",
102 ])("preserves the literal path and line: %s", (path) => {
103 const markdown = render(`:codex-file-citation{path="${path}" line_range_start="7"}`);
104 const link = parseOrdinaryMarkdown(markdown).children?.[0]?.children?.[0];
105​
106 expect(link?.type).toBe("link");
107 expect(parseMarkdownFileLink(link?.url ?? "")).toEqual({ path, line: 7 });
108 });
109});
110​
111describe("native Markdown adapters", () => {
112 it("renders follow-up suggestions as their label Markdown", () => {
113 expect(
114 renderCodexInlineDirectivesAsMarkdown(
115 `- ${FOLLOW_UP}\n- :codex-followup[Review **document**]{prompt="Review it."}`,
116 ),
117 ).toBe("- Prepare print version\n- Review **document**");
118 });
119​
120 it.each([
121 `\\${FILE_CITATION}`,
122 `\`${FILE_CITATION}\``,
123 `[See ${FILE_CITATION}](https://example.com)`,
124 ])("does not render excluded citation syntax: %s", (markdown) => {
125 expect(renderCodexInlineDirectivesAsMarkdown(markdown)).toBe(markdown);
126 });
127​
128 it("splits artifact cards from surrounding native Markdown", () => {
129 expect(splitCodexArtifactTemplateMarkdown(`Before\n\n${ARTIFACT_TEMPLATE}\n\nAfter`)).toEqual([
130 { kind: "markdown", markdown: "Before\n\n", sourceOffset: 0 },
131 {
132 kind: "artifact-template",
133 sourceOffset: 8,
134 template: {
135 artifactKind: "document",
136 displayName: "Hello World",
137 skillDirectory: "/Users/test/.codex/skills/artifact-template-hello-world",
138 skillName: "artifact-template-hello-world",
139 },
140 },
141 {
142 kind: "markdown",
143 markdown: "\n\nAfter",
144 sourceOffset: 8 + ARTIFACT_TEMPLATE.length,
145 },
146 ]);
147 });
148​
149 it("leaves malformed and code artifact-template examples in Markdown", () => {
150 const malformed = '::artifact-template{display_name="Hello World"}';
151 const code = `\`${ARTIFACT_TEMPLATE}\``;
152 expect(splitCodexArtifactTemplateMarkdown(malformed)).toEqual([
153 { kind: "markdown", markdown: malformed, sourceOffset: 0 },
154 ]);
155 expect(splitCodexArtifactTemplateMarkdown(code)).toEqual([
156 { kind: "markdown", markdown: code, sourceOffset: 0 },
157 ]);
158 });
159});
160​
161describe("directive copy adapter", () => {
162 it("copies the Markdown representations shown by citation chips and template cards", () => {
163 expect(
164 renderCodexDirectivesForCopy(
165 `Created ${FILE_CITATION}.\n\n${ARTIFACT_TEMPLATE}\n\n- ${FOLLOW_UP}`,
166 ),
167 ).toBe(
168 "Created [report.xlsx](<outputs/report.xlsx>).\n\nHello World (Document template)\n\n- Prepare print version",
169 );
170 });
171});