| 1 | import { renderToStaticMarkup } from "react-dom/server"; |
| 2 | import { ProviderDriverKind } from "@t3tools/contracts"; |
| 3 | import { describe, expect, it } from "vite-plus/test"; |
| 4 | |
| 5 | import { ComposerCommandMenu, composerSuggestionOptionId } from "./ComposerCommandMenu"; |
| 6 | |
| 7 | describe("composerSuggestionOptionId", () => { |
| 8 | it("keeps whitespace, escape-like paths, and malformed UTF-16 distinct", () => { |
| 9 | const paths = [ |
| 10 | "docs/my file.md", |
| 11 | "docs/my_file.md", |
| 12 | "docs/my%20file.md", |
| 13 | "docs/my\tfile.md", |
| 14 | "docs/\ud800.md", |
| 15 | "docs/\ud801.md", |
| 16 | "docs/\udc00.md", |
| 17 | "docs/\ufffd.md", |
| 18 | "docs/\\ud800.md", |
| 19 | "docs/\ud83d\ude80.md", |
| 20 | ]; |
| 21 | const ids = paths.map((path) => composerSuggestionOptionId("suggestions", `path:file:${path}`)); |
| 22 | |
| 23 | expect(new Set(ids).size).toBe(paths.length); |
| 24 | for (const id of ids) expect(id).not.toMatch(/\s|[\ud800-\udfff]/u); |
| 25 | expect(composerSuggestionOptionId("other-composer", paths[0]!)).not.toBe( |
| 26 | composerSuggestionOptionId("suggestions", paths[0]!), |
| 27 | ); |
| 28 | }); |
| 29 | }); |
| 30 | |
| 31 | describe("ComposerCommandMenu", () => { |
| 32 | it("renders slash commands with their descriptions", () => { |
| 33 | const markup = renderToStaticMarkup( |
| 34 | <ComposerCommandMenu |
| 35 | listId="test-suggestions" |
| 36 | items={[ |
| 37 | { |
| 38 | id: "slash:model", |
| 39 | type: "slash-command", |
| 40 | command: "model", |
| 41 | label: "/model", |
| 42 | description: "Switch response model for this thread", |
| 43 | }, |
| 44 | ]} |
| 45 | resolvedTheme="dark" |
| 46 | isLoading={false} |
| 47 | triggerKind="slash-command" |
| 48 | activeItemId="slash:model" |
| 49 | onHighlightedItemChange={() => {}} |
| 50 | onSelect={() => {}} |
| 51 | />, |
| 52 | ); |
| 53 | |
| 54 | expect(markup).toContain("/model"); |
| 55 | expect(markup).toContain("Switch response model for this thread"); |
| 56 | }); |
| 57 | |
| 58 | it("shows the app source for an app skill", () => { |
| 59 | const markup = renderToStaticMarkup( |
| 60 | <ComposerCommandMenu |
| 61 | listId="test-suggestions" |
| 62 | items={[ |
| 63 | { |
| 64 | id: "skill:codex:browser", |
| 65 | type: "skill", |
| 66 | provider: ProviderDriverKind.make("codex"), |
| 67 | skill: { |
| 68 | name: "browser", |
| 69 | path: "/Users/maria/.codex/plugins/browser/skills/browser/SKILL.md", |
| 70 | scope: "user", |
| 71 | enabled: true, |
| 72 | }, |
| 73 | label: "Browser", |
| 74 | description: "Open and control the in-app browser", |
| 75 | }, |
| 76 | ]} |
| 77 | resolvedTheme="dark" |
| 78 | isLoading={false} |
| 79 | triggerKind="skill" |
| 80 | activeItemId="skill:codex:browser" |
| 81 | onHighlightedItemChange={() => {}} |
| 82 | onSelect={() => {}} |
| 83 | />, |
| 84 | ); |
| 85 | |
| 86 | expect(markup).toContain("Browser"); |
| 87 | expect(markup).toContain('data-slot="badge"'); |
| 88 | expect(markup).toContain(">App Skill</span>"); |
| 89 | expect(markup).toContain("Open and control the in-app browser"); |
| 90 | expect(markup).toContain("<svg"); |
| 91 | }); |
| 92 | |
| 93 | it("shows the repo source for a slash skill", () => { |
| 94 | const markup = renderToStaticMarkup( |
| 95 | <ComposerCommandMenu |
| 96 | listId="test-suggestions" |
| 97 | items={[ |
| 98 | { |
| 99 | id: "skill:codex:ask-matt", |
| 100 | type: "skill", |
| 101 | provider: ProviderDriverKind.make("codex"), |
| 102 | skill: { |
| 103 | name: "ask-matt", |
| 104 | displayName: "Ask Matt", |
| 105 | path: "/skills/ask-matt/SKILL.md", |
| 106 | scope: "repo", |
| 107 | enabled: true, |
| 108 | }, |
| 109 | label: "/skill:ask-matt", |
| 110 | description: "Find the right skill or workflow", |
| 111 | }, |
| 112 | ]} |
| 113 | resolvedTheme="dark" |
| 114 | isLoading={false} |
| 115 | triggerKind="slash-command" |
| 116 | activeItemId="skill:codex:ask-matt" |
| 117 | onHighlightedItemChange={() => {}} |
| 118 | onSelect={() => {}} |
| 119 | />, |
| 120 | ); |
| 121 | |
| 122 | expect(markup).toContain('<span class="text-secondary-label">/skill:</span>Ask Matt'); |
| 123 | expect(markup).toContain('data-slot="badge"'); |
| 124 | expect(markup).toContain("lucide-folder"); |
| 125 | expect(markup).toContain(">Repo</span>"); |
| 126 | expect(markup).toContain("Find the right skill or workflow"); |
| 127 | }); |
| 128 | }); |