| 1 | import { ThreadId } from "@t3tools/contracts"; |
| 2 | import { describe, expect, it } from "vite-plus/test"; |
| 3 | |
| 4 | import { fileChipMenu, fileChipShareSource, resolveFileChipTarget } from "./fileChipMenu"; |
| 5 | |
| 6 | describe("resolveFileChipTarget", () => { |
| 7 | it("resolves a workspace-relative link to both paths", () => { |
| 8 | expect(resolveFileChipTarget("src/app.ts:12", "/repo")).toEqual({ |
| 9 | fullPath: "/repo/src/app.ts", |
| 10 | relativePath: "src/app.ts", |
| 11 | }); |
| 12 | }); |
| 13 | |
| 14 | it("keeps only the full path for a host file outside the workspace", () => { |
| 15 | expect(resolveFileChipTarget("/tmp/report.md", "/repo")).toEqual({ |
| 16 | fullPath: "/tmp/report.md", |
| 17 | }); |
| 18 | }); |
| 19 | |
| 20 | it("keeps only the relative path when the workspace root is unknown", () => { |
| 21 | expect(resolveFileChipTarget("src/app.ts", null)).toEqual({ relativePath: "src/app.ts" }); |
| 22 | }); |
| 23 | |
| 24 | it("ignores links that are not files or cannot be opened", () => { |
| 25 | expect(resolveFileChipTarget("https://example.com/app.ts", "/repo")).toBeNull(); |
| 26 | expect(resolveFileChipTarget("~/report.md", "/repo")).toBeNull(); |
| 27 | expect(resolveFileChipTarget("../other/file.ts", "/repo")).toBeNull(); |
| 28 | }); |
| 29 | }); |
| 30 | |
| 31 | describe("fileChipMenu", () => { |
| 32 | it("offers only the copies the target can satisfy", () => { |
| 33 | expect(fileChipMenu({ fullPath: "/tmp/report.md" })).toEqual({ |
| 34 | title: "/tmp/report.md", |
| 35 | actions: [ |
| 36 | { id: "copy-full-path", title: "Copy full path" }, |
| 37 | { id: "open-file", title: "Open in file viewer" }, |
| 38 | ], |
| 39 | }); |
| 40 | expect(fileChipMenu({ relativePath: "src/app.ts" }).actions.map(({ id }) => id)).toEqual([ |
| 41 | "copy-relative-path", |
| 42 | "open-file", |
| 43 | ]); |
| 44 | }); |
| 45 | }); |
| 46 | |
| 47 | describe("file chip downloads", () => { |
| 48 | const threadId = ThreadId.make("thread-1"); |
| 49 | |
| 50 | it.each([ |
| 51 | [ |
| 52 | "/tmp/maria-counter/maria-counter-final.mp4", |
| 53 | "/tmp/maria-counter/maria-counter-final.mp4", |
| 54 | "video/mp4", |
| 55 | ], |
| 56 | ["/tmp/take%2520%23one.mp4:12", "/tmp/take%20#one.mp4", "video/mp4"], |
| 57 | ["/tmp/report.pdf", "/tmp/report.pdf", "application/pdf"], |
| 58 | ["screens/image.PNG", "/repo/screens/image.PNG", "image/png"], |
| 59 | ])("offers a host download for %s", (href, path, mimeType) => { |
| 60 | const target = resolveFileChipTarget(href, "/repo")!; |
| 61 | expect(fileChipMenu(target).actions).toContainEqual({ |
| 62 | id: "save", |
| 63 | title: "Save or share", |
| 64 | }); |
| 65 | expect(fileChipShareSource(target, threadId)).toEqual({ |
| 66 | name: path.split("/").at(-1), |
| 67 | mimeType, |
| 68 | resource: { _tag: "media-file", threadId, path }, |
| 69 | }); |
| 70 | }); |
| 71 | |
| 72 | it("retains the thread context for a relative file without a known workspace root", () => { |
| 73 | expect( |
| 74 | fileChipShareSource(resolveFileChipTarget("clips/demo.mp4", null)!, threadId), |
| 75 | ).toMatchObject({ |
| 76 | resource: { _tag: "media-file", threadId, path: "clips/demo.mp4" }, |
| 77 | }); |
| 78 | }); |
| 79 | |
| 80 | it("does not offer downloads the host asset endpoint cannot serve", () => { |
| 81 | for (const href of ["src/app.ts", "/tmp/archive.zip", "/tmp/clip.mp4.txt"]) { |
| 82 | const target = resolveFileChipTarget(href, "/repo")!; |
| 83 | expect(fileChipShareSource(target, threadId)).toBeNull(); |
| 84 | expect(fileChipMenu(target).actions.some(({ id }) => id === "save")).toBe(false); |
| 85 | } |
| 86 | }); |
| 87 | }); |