Files
vfxreview/lib/board-parse.test.ts
T
twotalesanimation 34af26dfbf feat(board): daily board linked to shots
A /board page that replaces the shared doc used as the day-to-day tracker.
One card per active/on-hold project with a status line, dated milestones
and to-do lines typed or pasted in the doc's own format:

  DH2_VFX_027_090 finalise (CHRIS)
  DH2_VFX_005_010 gfx update @michelle wait: client GFX ADH_GFX_005_020

Shot codes link to the shot (live status + latest version), (NAME)/@name
assigns, wait: marks a blocker, headings group lines. Codes that match no
shot are flagged, with a relink when there is an obvious near miss.

Board lines are their own model, not Tasks: tasks drive shot status and
notifications, and a note like "finalise" must not touch either.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 17:30:40 +02:00

108 lines
3.8 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { parseBoardLine, parseBoardText, suggestShot } from "./board-parse";
const shots = [
{ id: "s1", shotCode: "DH2_VFX_027_090" },
{ id: "s2", shotCode: "DH2_VFX_005_010" },
{ id: "s3", shotCode: "DH2_VFX_004_100" },
{ id: "s4", shotCode: "UNG_117_023_010", clientCode: "EP17_VFX_003" },
];
const users = [
{ id: "u1", name: "Chris Pinto", email: "chris@example.com" },
{ id: "u2", name: "Michelle Pinto", email: "michelle@example.com" },
];
const ctx = { shots, users };
describe("parseBoardLine", () => {
it("links the shot and assigns from (NAME)", () => {
expect(parseBoardLine("DH2_VFX_027_090 finalise (CHRIS)", ctx)).toEqual({
text: "finalise",
code: "DH2_VFX_027_090",
shotId: "s1",
assigneeId: "u1",
waitingOn: null,
group: "",
});
});
it("matches codes case-insensitively and by client alias", () => {
expect(parseBoardLine("dh2_vfx_027_090 x", ctx).shotId).toBe("s1");
const alias = parseBoardLine("EP17_VFX_003 warp fix", ctx);
expect(alias.shotId).toBe("s4");
expect(alias.code).toBe("UNG_117_023_010");
});
it("keeps an unknown code as typed for flagging", () => {
const r = parseBoardLine("DH2_004_100 not on our tracker?", ctx);
expect(r.code).toBe("DH2_004_100");
expect(r.shotId).toBeNull();
expect(r.text).toBe("not on our tracker?");
});
it("assigns from @name and keeps the rest of a parenthesised group", () => {
expect(parseBoardLine("DH2_VFX_005_010 gfx @michelle", ctx).assigneeId).toBe("u2");
const r = parseBoardLine("(MICHELLE ROTO) Maagie shot", ctx);
expect(r.assigneeId).toBe("u2");
expect(r.text).toBe("ROTO Maagie shot");
});
it("leaves parentheses that are not a user alone", () => {
const r = parseBoardLine("DH2_VFX_005_010 track (90% done)", ctx);
expect(r.assigneeId).toBeNull();
expect(r.text).toBe("track (90% done)");
});
it("splits off what the line is waiting on", () => {
const r = parseBoardLine(
"DH2_VFX_005_010 gfx required for update (MICHELLE) wait: client GFX ADH_GFX_005_020",
ctx,
);
expect(r.shotId).toBe("s2");
expect(r.waitingOn).toBe("client GFX ADH_GFX_005_020");
expect(r.text).toBe("gfx required for update");
expect(parseBoardLine("x waiting on: grade", ctx).waitingOn).toBe("grade");
// no colon → prose
expect(parseBoardLine("awaiting gfx", ctx).waitingOn).toBeNull();
});
it("takes a note with no shot code", () => {
const r = parseBoardLine("- Deposit received", ctx);
expect(r).toMatchObject({ text: "Deposit received", code: null, shotId: null });
});
});
describe("parseBoardText", () => {
it("groups lines under headings and skips blanks", () => {
const items = parseBoardText(
"DH2_VFX_027_090 finalise (CHRIS)\n\nWATER DROPS:\nDH2_VFX_005_010 still do (CHRIS)\nDH2_VFX_004_100\n",
ctx,
);
expect(items.map((i) => [i.code, i.group, i.text])).toEqual([
["DH2_VFX_027_090", "", "finalise"],
["DH2_VFX_005_010", "WATER DROPS", "still do"],
["DH2_VFX_004_100", "WATER DROPS", ""],
]);
});
it("treats an all-caps line without a code as a heading", () => {
const items = parseBoardText("FLAGS\nDH2_004_100 not on our tracker?", ctx);
expect(items).toHaveLength(1);
expect(items[0].group).toBe("FLAGS");
});
it("never treats a single typed line as a heading", () => {
expect(parseBoardText("CALL CLIENT", ctx)).toHaveLength(1);
});
});
describe("suggestShot", () => {
it("suggests the shot with the same prefix and numbers", () => {
expect(suggestShot("DH2_004_100", shots)?.id).toBe("s3");
});
it("does not guess between several candidates", () => {
const dup = [...shots, { id: "s5", shotCode: "DH2_GFX_004_100" }];
expect(suggestShot("DH2_004_100", dup)).toBeNull();
});
});