import { afterEach, describe, expect, it, vi } from "vitest"; import type { OAuthUserAgent } from "@atcute/oauth-browser-client"; import { atUriOf, knotOf, labelKeyOf } from "$lib/api/syntax"; import type * as Ticket from "$lib/api/ticket"; const agent = { sub: "did:plc:limpet" } as unknown as OAuthUserAgent; const knot = knotOf("knot.example.com"); const uri = atUriOf("at://did:plc:barnacle/org.tangled.track.ticket/3lk"); const storedCid = "bafyreistored" + "a".repeat(46); const nextCid = "bafyreinext" + "a".repeat(48); const stored = { $type: "org.tangled.track.ticket", title: { $type: "org.tangled.markup.markdown#inline", text: "kelp" }, body: { $type: "org.tangled.markup.markdown", text: "forest", blobs: [{ alt: "seal" }] }, state: "open", targetBranch: "main", tangledLabels: [{ key: "bug" }], mentions: ["did:plc:urchin"], createdAt: "2026-01-01T00:00:00Z", "x-tngl-editor": "did:plc:limpet" }; const load = async () => { vi.resetModules(); vi.doMock("$app/environment", () => ({ browser: true })); vi.doMock("$lib/auth/agent", () => ({ mintServiceAuth: async () => "token" })); const ticket = (await import("$lib/api/ticket")) as typeof Ticket; const sent: Record[] = []; vi.stubGlobal("fetch", async (_input: URL | RequestInfo, init?: RequestInit) => { if (!init?.body) return Response.json({ uri, cid: storedCid, value: stored }); sent.push(JSON.parse(String(init.body))); return Response.json({ uri, cid: nextCid }); }); return { ticket, sent }; }; const { "x-tngl-editor": _, ...kept } = stored; afterEach(() => { vi.unstubAllGlobals(); }); describe("ticket writes on the knot", () => { it("edits the prose and keeps everything the form doesn't show", async () => { const { ticket, sent } = await load(); await ticket.editTicket(agent, knot, uri, { title: "kelp bed", body: "deeper forest" }); expect(sent[0]).toMatchObject({ swapRecord: storedCid }); expect(sent[0].record).toEqual({ ...kept, title: { ...kept.title, text: "kelp bed" }, body: { ...kept.body, text: "deeper forest" } }); }); it("leaves a blank body out, because the knot refuses an empty one", async () => { const { ticket, sent } = await load(); await ticket.editTicket(agent, knot, uri, { title: "kelp", body: " " }); expect(sent[0].record).not.toHaveProperty("body"); }); it("moves a pull to another target branch", async () => { const { ticket, sent } = await load(); await ticket.editTicket(agent, knot, uri, { title: "kelp", body: "forest", targetBranch: "next" }); expect(sent[0].record).toMatchObject({ targetBranch: "next" }); }); it("changes state by rewriting the ticket", async () => { const { ticket, sent } = await load(); const written = await ticket.setTicketState(agent, knot, uri, "closed"); expect(sent[0].record).toEqual({ ...kept, state: "closed" }); expect(written.cid).toBe(nextCid); }); it("replaces the label set and drops it when it ends up empty", async () => { const { ticket, sent } = await load(); await ticket.setTicketLabels(agent, knot, uri, [labelKeyOf("docs"), labelKeyOf("wontfix")]); await ticket.setTicketLabels(agent, knot, uri, []); expect(sent[0].record).toMatchObject({ tangledLabels: [ { $type: "org.tangled.label.defs#labelToken", key: "docs" }, { $type: "org.tangled.label.defs#labelToken", key: "wontfix" } ] }); expect(sent[1].record).not.toHaveProperty("tangledLabels"); }); });