Files
vfxreview/lib/batch-upload-parser.test.ts
T
twotalesanimation 2dee1bf6fc
Deploy / deploy (push) Successful in 6m39s
feat(batch-upload): bulk footage plate upload matched by filename
Adds a third section to the Batch Upload page that attaches plate movies to
shots derived from the filename: DH2_VFX_058_120_REF.mov becomes the plate
"REF" on shot DH2_VFX_058_120. Shot matching reuses findMatchingShotCode, so
it behaves like the rest of the page (case-insensitive, longest code wins,
underscore-boundary only) and the filename remainder becomes the label.

Bytes go straight to Hetzner via the existing presign endpoint since plates
are multi-GB, with XHR for per-file progress. Files whose shot already has a
plate of the same name are skipped so re-running a batch does not stack
duplicates.

Also resets the thumbnail and plate previews when the project changes — they
are resolved against a specific project, but only the version-upload flow was
being cleared.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 12:15:29 +02:00

68 lines
2.3 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { PLATE_EXTS, parsePlateFilename, plateContentType } from "./batch-upload-parser";
const SHOTS = ["DH2_VFX_058_120", "DH2_VFX_058_120_B", "DH2_VFX_058_10", "UNG_106_035_010"];
describe("parsePlateFilename", () => {
it("derives the shot and label from a suffixed plate name", () => {
const p = parsePlateFilename("DH2_VFX_058_120_REF.mov", SHOTS);
expect(p.shotCode).toBe("DH2_VFX_058_120");
expect(p.label).toBe("REF");
expect(p.ext).toBe("mov");
});
it("returns an empty label when the filename is exactly the shot code", () => {
const p = parsePlateFilename("DH2_VFX_058_120.mov", SHOTS);
expect(p.shotCode).toBe("DH2_VFX_058_120");
expect(p.label).toBe("");
});
it("prefers the longest matching shot code", () => {
const p = parsePlateFilename("DH2_VFX_058_120_B_REF.mov", SHOTS);
expect(p.shotCode).toBe("DH2_VFX_058_120_B");
expect(p.label).toBe("REF");
});
it("does not match a shot code that is only a partial segment", () => {
// "DH2_VFX_058_10" must not swallow "DH2_VFX_058_1000"
expect(parsePlateFilename("DH2_VFX_058_1000_REF.mov", SHOTS).shotCode).toBeNull();
});
it("matches case-insensitively but reports the canonical shot code", () => {
const p = parsePlateFilename("dh2_vfx_058_120_ref.mov", SHOTS);
expect(p.shotCode).toBe("DH2_VFX_058_120");
expect(p.label).toBe("ref");
});
it("keeps multi-part labels intact", () => {
expect(parsePlateFilename("UNG_106_035_010_BG_CLEAN_v002.mov", SHOTS).label).toBe(
"BG_CLEAN_v002"
);
});
it("returns no shot code when nothing matches", () => {
const p = parsePlateFilename("random_file.mov", SHOTS);
expect(p.shotCode).toBeNull();
expect(p.label).toBe("");
});
it("handles filenames with dots in them", () => {
const p = parsePlateFilename("DH2_VFX_058_120_REF.v2.mov", SHOTS);
expect(p.shotCode).toBe("DH2_VFX_058_120");
expect(p.label).toBe("REF.v2");
expect(p.ext).toBe("mov");
});
});
describe("plateContentType", () => {
it("maps every accepted extension to a video/* type", () => {
for (const ext of PLATE_EXTS) {
expect(plateContentType(`shot.${ext}`)).toMatch(/^video\//);
}
});
it("is case-insensitive", () => {
expect(plateContentType("SHOT.MOV")).toBe("video/quicktime");
});
});