/** * @file Tests for the `/artifacts` page: the library lists what the server * answers, the Hub tab and Share appear only in server mode, a skill is * installed only after a confirmation (and a taken name asks to replace), * and a file preview is a sandboxed iframe with no `allow-*` token. Adding: * a plugin is picked from the list its marketplace link answers (a link the * server cannot read opens the by-hand form), a skill is uploaded from one * Markdown file under the name the user types, or written in the dialog. * @author Nguyễn Ngọc Trí Vĩ */ import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, waitFor, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { MemoryRouter } from "react-router-dom"; import { Artifacts } from "../Artifacts"; import { api, type Artifact } from "../../lib/api"; import { confirmDialog } from "../../lib/confirm"; vi.mock("../../lib/confirm", () => ({ confirmDialog: vi.fn() })); vi.mock("../../lib/api", () => ({ api: { lanes: { list: vi.fn() }, artifacts: { settings: vi.fn(), list: vi.fn(), update: vi.fn(), remove: vi.fn(), install: vi.fn(), setInstallsEnabled: vi.fn(), skills: vi.fn(), addSkill: vi.fn(), addPlugin: vi.fn(), inspectMarket: vi.fn(), uploadSkills: vi.fn(), writeSkill: vi.fn(), uploadFile: vi.fn(), candidates: vi.fn(), rawUrl: (id: number, path?: string) => `/api/artifacts/${id}/raw?path=${path ?? ""}`, }, }, })); const item = (over: Partial): Artifact => ({ id: 1, kind: "file", title: "report.html", description: "", ownerId: 1, owner: "vi", mine: true, canChange: true, laneId: 1, sessionId: null, source: "out/report.html", market: null, url: null, files: [{ path: "report.html", size: 10 }], size: 10, shared: false, sharedAt: null, installs: 0, createdAt: "2026-10-14T00:00:00Z", updatedAt: "2026-10-14T00:00:00Z", ...over, }); const open = () => render( ); beforeEach(() => { vi.mocked(api.artifacts.settings).mockResolvedValue({ serverMode: false, canShare: false, installsEnabled: true, canManage: true, }); vi.mocked(api.artifacts.list).mockResolvedValue({ items: [ item({}), item({ id: 2, kind: "skill", title: "deploy", source: "deploy", files: [{ path: "SKILL.md", size: 3 }] }), ], }); vi.mocked(confirmDialog).mockReset(); vi.mocked(api.artifacts.install).mockReset(); vi.mocked(api.lanes.list).mockResolvedValue({ lanes: [], counts: {} } as never); vi.mocked(api.artifacts.skills).mockResolvedValue({ items: [] }); }); const addMenu = async (user: ReturnType, label: string) => { open(); await screen.findByText("deploy"); await user.click(screen.getByTestId("artifacts-add")); await user.click(screen.getByRole("button", { name: label })); }; describe("Adding", () => { it("adds the plugins picked from a marketplace link", async () => { const user = userEvent.setup(); vi.mocked(api.artifacts.inspectMarket).mockResolvedValue({ market: "https://git.x.vn/o/skills.git", name: "sg", description: "", plugins: [ { name: "sg-dev", key: "sg-dev@sg", description: "Dev", version: "", category: "" }, { name: "sg-common", key: "sg-common@sg", description: "", version: "", category: "" }, ], }); vi.mocked(api.artifacts.addPlugin).mockResolvedValue({ item: item({ kind: "plugin" }) }); await addMenu(user, "Plugin"); await user.type(screen.getByLabelText("Marketplace link"), "https://git.x.vn/o/skills.git"); await user.click(screen.getByRole("button", { name: "Read plugins" })); await screen.findByTestId("market-plugins"); await user.click(screen.getByLabelText("Select all")); await user.click(screen.getByRole("button", { name: "Add 2 plugin(s)" })); await waitFor(() => expect(api.artifacts.addPlugin).toHaveBeenCalledTimes(2)); expect(api.artifacts.addPlugin).toHaveBeenCalledWith({ key: "sg-dev@sg", market: "https://git.x.vn/o/skills.git", title: "sg-dev", description: "Dev", }); }); it("opens the by-hand form when the link cannot be read", async () => { const user = userEvent.setup(); vi.mocked(api.artifacts.inspectMarket).mockRejectedValue( Object.assign(new Error("needs a login"), { code: "ENEEDLOGIN" }) ); await addMenu(user, "Plugin"); await user.type(screen.getByLabelText("Marketplace link"), "https://git.x.vn/o/private.git"); await user.click(screen.getByRole("button", { name: "Read plugins" })); expect(await screen.findByRole("alert")).toHaveTextContent("needs a login"); expect(screen.getByLabelText("Marketplace (owner/repo or git URL)")).toHaveValue( "https://git.x.vn/o/private.git" ); }); it("uploads one Markdown file as a skill under the typed name", async () => { const user = userEvent.setup(); vi.mocked(api.artifacts.uploadSkills).mockResolvedValue({ items: [item({ kind: "skill" })] }); await addMenu(user, "Skill"); const md = new File(["# Steps"], "notes.md", { type: "text/markdown" }); await user.upload(screen.getByTestId("skill-files"), md); expect(await screen.findByTestId("skill-shape")).toHaveTextContent("becomes the skill's SKILL.md"); const name = screen.getByLabelText(/Skill name/); await waitFor(() => expect(name).toHaveValue("notes")); await user.clear(name); await user.type(name, "Viết báo cáo"); expect(screen.getByTestId("skill-slug")).toHaveTextContent("/Viet-bao-cao"); await user.type(screen.getByLabelText(/Description/), "Use for reports"); await user.click(within(screen.getByRole("dialog")).getByRole("button", { name: "Add" })); await waitFor(() => expect(api.artifacts.uploadSkills).toHaveBeenCalled()); const [files, fields] = vi.mocked(api.artifacts.uploadSkills).mock.calls[0]!; expect(files.map((f) => f.path)).toEqual(["notes.md"]); expect(fields).toMatchObject({ name: "Viết báo cáo", description: "Use for reports" }); }); it("writes a new skill", async () => { const user = userEvent.setup(); vi.mocked(api.artifacts.writeSkill).mockResolvedValue({ item: item({}), items: [] }); await addMenu(user, "Skill"); await user.click(screen.getByRole("tab", { name: "Write new" })); const add = within(screen.getByRole("dialog")).getByRole("button", { name: "Add" }); await user.type(screen.getByLabelText(/Skill name/), "standup"); await user.type(screen.getByLabelText(/Instructions/), "Summarise."); expect(add).toBeDisabled(); // no description yet await user.type(screen.getByLabelText(/Description/), "Daily standup"); await user.click(add); await waitFor(() => expect(api.artifacts.writeSkill).toHaveBeenCalledWith({ name: "standup", description: "Daily standup", content: "Summarise.", title: undefined, }) ); }); }); describe("Artifacts", () => { it("lists the library, with no Hub or Share in local mode", async () => { open(); expect(await screen.findByText("report.html")).toBeInTheDocument(); expect(screen.getByText("deploy")).toBeInTheDocument(); expect(screen.queryByRole("button", { name: "Hub" })).toBeNull(); expect(screen.queryByRole("button", { name: /Share/ })).toBeNull(); }); it("shows the Hub and Share in server mode", async () => { vi.mocked(api.artifacts.settings).mockResolvedValue({ serverMode: true, canShare: true, installsEnabled: true, canManage: false, }); open(); expect(await screen.findByRole("button", { name: "Hub" })).toBeInTheDocument(); expect(screen.getAllByRole("button", { name: /^Share$/ }).length).toBe(2); }); it("installs a skill only after a confirmation, and asks before replacing", async () => { const user = userEvent.setup(); open(); await screen.findByText("deploy"); vi.mocked(confirmDialog).mockResolvedValueOnce(false); await user.click(screen.getByRole("button", { name: "Install" })); expect(api.artifacts.install).not.toHaveBeenCalled(); vi.mocked(confirmDialog).mockResolvedValue(true); vi.mocked(api.artifacts.install) .mockRejectedValueOnce(Object.assign(new Error("taken"), { code: "EEXISTS" })) .mockResolvedValueOnce({ installed: "/h/.claude/skills/deploy", replaced: true }); await user.click(screen.getByRole("button", { name: "Install" })); await waitFor(() => expect(api.artifacts.install).toHaveBeenLastCalledWith(2, true)); const calls = vi.mocked(confirmDialog).mock.calls; expect(calls[calls.length - 1]?.[0].title).toMatch(/Replace/); }); it("previews a file in a sandboxed iframe that may run nothing", async () => { const user = userEvent.setup(); open(); await screen.findByText("report.html"); await user.click(screen.getAllByRole("button", { name: "Preview" })[0]!); const frame = await screen.findByTestId("artifact-preview"); expect(frame.getAttribute("sandbox")).toBe(""); expect(frame.getAttribute("src")).toBe("/api/artifacts/1/raw?path=report.html"); }); });