Files
vfxreview/lib/shot-code.test.ts
twotalesanimation ed9f9b13c1
Deploy / deploy (push) Successful in 6m8s
feat(client-portal): show shots and task titles in the client's own naming
Shot.clientCode already drove slates, overlay burn-ins and delivered file
names, but the client portal — the one place the client actually reads — was
still showing our internal codes.

Task titles were the larger leak. They follow the house convention
"UNG_106_010_020_cmp_TT_v004", so the internal code sat embedded in the
string the client reads and copies, in three places on the shot list and
again in the player header. Swapping only the heading would have left it on
display directly underneath.

lib/shot-code.ts resolves both: clientShotCode picks the alias when set, and
clientTaskTitle swaps the code out of a title by substring, leaving a
hand-edited title that does not carry the code untouched rather than guessing
at it.

Applied at the API boundary rather than at each render site, so the shot
list, search, playlist labels, alt text, player header and every
copy-to-clipboard button inherit it and nothing downstream has to remember
which code it is holding. The internal code is no longer sent to the browser
on these routes at all.

Slack notifications deliberately keep the internal code: those go to our
artists, who need the code that matches the comp and the SAN path.

The two existing "clientCode || shotCode" expressions in preview.ts and
deliveries.ts now use the same helper so slates, deliveries and the portal
cannot drift apart. Shots with no clientCode are unchanged throughout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 23:16:53 +02:00

46 lines
1.8 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { clientShotCode, clientTaskTitle } from "./shot-code";
describe("clientShotCode", () => {
it("prefers the client's alias", () => {
expect(clientShotCode({ shotCode: "UNG_106_010_020", clientCode: "EP6_VFX_012" })).toBe("EP6_VFX_012");
});
it("falls back to our code when there is no alias", () => {
expect(clientShotCode({ shotCode: "UNG_106_010_020", clientCode: null })).toBe("UNG_106_010_020");
expect(clientShotCode({ shotCode: "UNG_106_010_020" })).toBe("UNG_106_010_020");
});
it("ignores a whitespace-only alias", () => {
expect(clientShotCode({ shotCode: "UNG_106_010_020", clientCode: " " })).toBe("UNG_106_010_020");
});
});
describe("clientTaskTitle", () => {
const shot = { shotCode: "UNG_106_010_020", clientCode: "EP6_VFX_012" };
it("swaps the internal code out of a house-convention title", () => {
expect(clientTaskTitle("UNG_106_010_020_cmp_TT_v004", shot)).toBe("EP6_VFX_012_cmp_TT_v004");
});
it("leaves the title alone when there is no alias", () => {
const t = "UNG_106_010_020_cmp_TT_v004";
expect(clientTaskTitle(t, { shotCode: "UNG_106_010_020", clientCode: null })).toBe(t);
});
it("leaves a title that does not carry the code untouched", () => {
// Hand-edited titles are not guessed at — better an unhelpful title than a
// mangled one.
expect(clientTaskTitle("Roto pass", shot)).toBe("Roto pass");
});
it("swaps every occurrence", () => {
expect(clientTaskTitle("UNG_106_010_020 / UNG_106_010_020_cmp", shot)).toBe("EP6_VFX_012 / EP6_VFX_012_cmp");
});
it("is a no-op when the alias equals the code", () => {
const same = { shotCode: "UNG_106_010_020", clientCode: "UNG_106_010_020" };
expect(clientTaskTitle("UNG_106_010_020_cmp", same)).toBe("UNG_106_010_020_cmp");
});
});