diff --git a/src/app/actions/cards.test.ts b/src/app/actions/cards.test.ts new file mode 100644 index 0000000..850fd41 --- /dev/null +++ b/src/app/actions/cards.test.ts @@ -0,0 +1,50 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { createCard } from "@/app/actions/cards"; +import { requireSession } from "@/lib/auth/session"; +import { getServerPb } from "@/lib/pb/server"; +import { resolveWorkArea } from "@/lib/work-areas/server"; + +vi.mock("next/cache", () => ({ revalidatePath: vi.fn() })); +vi.mock("@/lib/auth/session", () => ({ requireSession: vi.fn() })); +vi.mock("@/lib/pb/server", () => ({ getServerPb: vi.fn() })); +vi.mock("@/lib/work-areas/server", () => ({ resolveWorkArea: vi.fn() })); +vi.mock("@/app/actions/activity", () => ({ logActivity: vi.fn() })); + +describe("owner card creation", () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.mocked(requireSession).mockResolvedValue({ userId: "owner-1", role: "owner", clientId: "" }); + vi.mocked(resolveWorkArea).mockResolvedValue("area-1"); + }); + + it("assigns the next project card number when creating a card", async () => { + const create = vi.fn(async () => ({ id: "new-card" })); + const cards = { + getFirstListItem: vi.fn(async () => ({ position: 1000 })), + getFullList: vi.fn(async () => [{ number: 0 }, { number: 5 }, { number: 3 }]), + create, + }; + vi.mocked(getServerPb).mockResolvedValue({ collection: () => cards } as never); + + const result = await createCard({ + projectId: "project-1", + columnId: "inbox-1", + title: "Prepare a delivery note", + clientSlug: "mega-infrastructure", + projectSlug: "mega-dashboard", + areaId: "area-1", + }); + + expect(result).toEqual({ id: "new-card" }); + expect(cards.getFullList).toHaveBeenCalledWith({ + filter: 'project = "project-1"', + fields: "number", + }); + expect(create).toHaveBeenCalledWith(expect.objectContaining({ + project: "project-1", + work_area: "area-1", + number: 6, + })); + }); +}); diff --git a/src/app/actions/cards.ts b/src/app/actions/cards.ts index 2800b42..b50961c 100644 --- a/src/app/actions/cards.ts +++ b/src/app/actions/cards.ts @@ -58,6 +58,10 @@ export async function createCard(input: { sort: "-position", }) .catch(() => null); + const numbered = await pb.collection("cards").getFullList({ + filter: `project = "${input.projectId}"`, + fields: "number", + }); const card = await pb.collection("cards").create({ project: input.projectId, @@ -71,6 +75,7 @@ export async function createCard(input: { created_by_role: "owner", accepted: true, archived: false, + number: nextCardNumber(numbered.map((existing) => existing.number)), }); await logActivity({ diff --git a/src/lib/intake/brief.test.ts b/src/lib/intake/brief.test.ts index 261cec1..4ec7832 100644 --- a/src/lib/intake/brief.test.ts +++ b/src/lib/intake/brief.test.ts @@ -54,7 +54,7 @@ describe("parseBrief — it is a whitelist, not a parser", () => { number: 1, }); - const brief = parseBrief(hostile, turns) as Record; + const brief = parseBrief(hostile, turns) as unknown as Record; expect(brief.priority).toBeUndefined(); expect(brief.accepted).toBeUndefined(); diff --git a/src/lib/roadmap/load.test.ts b/src/lib/roadmap/load.test.ts index 0ca01d3..3b5ff44 100644 --- a/src/lib/roadmap/load.test.ts +++ b/src/lib/roadmap/load.test.ts @@ -42,6 +42,7 @@ function card(overrides: Partial = {}): CardRecord { updated: "2026-01-01 00:00:00.000Z", project: "p1", column: "col1", + work_area: "", title: "Refactor billing modal", description: "", position: 0, diff --git a/tests/integration/intake.test.ts b/tests/integration/intake.test.ts index d454fef..c99ee80 100644 --- a/tests/integration/intake.test.ts +++ b/tests/integration/intake.test.ts @@ -56,7 +56,7 @@ describe("a hostile brief cannot escalate", () => { created_by_role: "owner", }); - const brief = parseBrief(hostile, []) as Record; + const brief = parseBrief(hostile, []) as unknown as Record; for (const forbidden of [ "priority",