|
1 | 1 | import { describe, expect, it } from "vitest"; |
2 | | -import { parseBundleInput } from "./AgentBundleImportDialog"; |
| 2 | +import { type ParsedBundle, parseBundleInput } from "./AgentBundleImportDialog"; |
3 | 3 |
|
4 | | -describe("parseBundleInput", () => { |
5 | | - it("returns an error for empty input", () => { |
6 | | - const out = parseBundleInput(""); |
7 | | - expect(out.ok).toBe(false); |
8 | | - }); |
| 4 | +type Expected = |
| 5 | + | { ok: true; value: ParsedBundle } |
| 6 | + | { ok: false; errorMatch?: RegExp }; |
9 | 7 |
|
10 | | - it("parses a single agent.md block", () => { |
11 | | - const out = parseBundleInput( |
12 | | - "--- agent.md ---\nYou are the growth review agent.\n", |
13 | | - ); |
14 | | - expect(out).toEqual({ |
| 8 | +const cases: Array<{ name: string; input: string; expected: Expected }> = [ |
| 9 | + { |
| 10 | + name: "rejects empty input", |
| 11 | + input: "", |
| 12 | + expected: { ok: false }, |
| 13 | + }, |
| 14 | + { |
| 15 | + name: "parses a single agent.md block", |
| 16 | + input: "--- agent.md ---\nYou are the growth review agent.\n", |
| 17 | + expected: { |
15 | 18 | ok: true, |
16 | 19 | value: { agent_md: "You are the growth review agent." }, |
17 | | - }); |
18 | | - }); |
19 | | - |
20 | | - it("parses multiple skill blocks", () => { |
21 | | - const out = parseBundleInput( |
22 | | - [ |
23 | | - "--- skills/research/SKILL.md ---", |
24 | | - "Research body", |
25 | | - "--- skills/draft-post/SKILL.md ---", |
26 | | - "Draft body", |
27 | | - ].join("\n"), |
28 | | - ); |
29 | | - expect(out).toEqual({ |
| 20 | + }, |
| 21 | + }, |
| 22 | + { |
| 23 | + name: "parses multiple skill blocks", |
| 24 | + input: [ |
| 25 | + "--- skills/research/SKILL.md ---", |
| 26 | + "Research body", |
| 27 | + "--- skills/draft-post/SKILL.md ---", |
| 28 | + "Draft body", |
| 29 | + ].join("\n"), |
| 30 | + expected: { |
30 | 31 | ok: true, |
31 | 32 | value: { |
32 | 33 | skills: [ |
33 | 34 | { id: "research", body: "Research body" }, |
34 | 35 | { id: "draft-post", body: "Draft body" }, |
35 | 36 | ], |
36 | 37 | }, |
37 | | - }); |
38 | | - }); |
| 38 | + }, |
| 39 | + }, |
| 40 | + { |
| 41 | + name: "parses agent.md plus skills together", |
| 42 | + input: [ |
| 43 | + "--- agent.md ---", |
| 44 | + "Main prompt", |
| 45 | + "", |
| 46 | + "--- skills/research/SKILL.md ---", |
| 47 | + "Research body", |
| 48 | + ].join("\n"), |
| 49 | + expected: { |
| 50 | + ok: true, |
| 51 | + value: { |
| 52 | + agent_md: "Main prompt", |
| 53 | + skills: [{ id: "research", body: "Research body" }], |
| 54 | + }, |
| 55 | + }, |
| 56 | + }, |
| 57 | + { |
| 58 | + name: "tolerates CRLF line endings", |
| 59 | + input: "--- agent.md ---\r\nMain prompt\r\n", |
| 60 | + expected: { ok: true, value: { agent_md: "Main prompt" } }, |
| 61 | + }, |
| 62 | + { |
| 63 | + name: "rejects an unsupported file path", |
| 64 | + input: "--- tools/foo/source.ts ---\nconsole.log('hi')\n", |
| 65 | + expected: { ok: false, errorMatch: /Unsupported file path/ }, |
| 66 | + }, |
| 67 | + { |
| 68 | + name: "rejects skill ids with spaces", |
| 69 | + input: "--- skills/Bad Id/SKILL.md ---\nbody\n", |
| 70 | + expected: { ok: false }, |
| 71 | + }, |
| 72 | + { |
| 73 | + name: "rejects skill ids with uppercase letters", |
| 74 | + input: "--- skills/MySkill/SKILL.md ---\nbody\n", |
| 75 | + expected: { ok: false }, |
| 76 | + }, |
| 77 | + { |
| 78 | + name: "ignores leading content before the first header", |
| 79 | + input: [ |
| 80 | + "# notes for myself, not in any block", |
| 81 | + "--- agent.md ---", |
| 82 | + "Prompt", |
| 83 | + ].join("\n"), |
| 84 | + expected: { ok: true, value: { agent_md: "Prompt" } }, |
| 85 | + }, |
| 86 | +]; |
39 | 87 |
|
40 | | - it("parses agent.md plus skills together", () => { |
41 | | - const out = parseBundleInput( |
42 | | - [ |
43 | | - "--- agent.md ---", |
44 | | - "Main prompt", |
45 | | - "", |
46 | | - "--- skills/research/SKILL.md ---", |
47 | | - "Research body", |
48 | | - ].join("\n"), |
49 | | - ); |
50 | | - expect(out.ok).toBe(true); |
51 | | - if (out.ok) { |
52 | | - expect(out.value.agent_md).toBe("Main prompt"); |
53 | | - expect(out.value.skills).toEqual([ |
54 | | - { id: "research", body: "Research body" }, |
55 | | - ]); |
| 88 | +describe("parseBundleInput", () => { |
| 89 | + it.each(cases)("$name", ({ input, expected }) => { |
| 90 | + const out = parseBundleInput(input); |
| 91 | + if (expected.ok) { |
| 92 | + expect(out).toEqual(expected); |
| 93 | + return; |
56 | 94 | } |
57 | | - }); |
58 | | - |
59 | | - it("tolerates CRLF line endings", () => { |
60 | | - const out = parseBundleInput("--- agent.md ---\r\nMain prompt\r\n"); |
61 | | - expect(out).toEqual({ ok: true, value: { agent_md: "Main prompt" } }); |
62 | | - }); |
63 | | - |
64 | | - it("rejects an unsupported file path", () => { |
65 | | - const out = parseBundleInput( |
66 | | - "--- tools/foo/source.ts ---\nconsole.log('hi')\n", |
67 | | - ); |
68 | | - expect(out.ok).toBe(false); |
69 | | - if (!out.ok) expect(out.error).toMatch(/Unsupported file path/); |
70 | | - }); |
71 | | - |
72 | | - it("rejects skill ids with disallowed characters", () => { |
73 | | - const out = parseBundleInput("--- skills/Bad Id/SKILL.md ---\nbody\n"); |
74 | 95 | expect(out.ok).toBe(false); |
75 | | - }); |
76 | | - |
77 | | - it("ignores leading content before the first header", () => { |
78 | | - const out = parseBundleInput( |
79 | | - [ |
80 | | - "# notes for myself, not in any block", |
81 | | - "--- agent.md ---", |
82 | | - "Prompt", |
83 | | - ].join("\n"), |
84 | | - ); |
85 | | - expect(out).toEqual({ ok: true, value: { agent_md: "Prompt" } }); |
| 96 | + if (!out.ok && expected.errorMatch) { |
| 97 | + expect(out.error).toMatch(expected.errorMatch); |
| 98 | + } |
86 | 99 | }); |
87 | 100 | }); |
0 commit comments