|
| 1 | +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; |
| 2 | +import { tmpdir } from "node:os"; |
| 3 | +import { basename, join } from "node:path"; |
| 4 | +import { afterEach, describe, expect, it } from "vitest"; |
| 5 | + |
| 6 | +import { |
| 7 | + ExtractedFrameSequenceError, |
| 8 | + extractedFrameIndex, |
| 9 | + framePathsFromDirectory, |
| 10 | +} from "./extractedFrameIndex.js"; |
| 11 | + |
| 12 | +const roots: string[] = []; |
| 13 | + |
| 14 | +function frameDir(): string { |
| 15 | + const root = mkdtempSync(join(tmpdir(), "hf-extracted-frame-index-")); |
| 16 | + roots.push(root); |
| 17 | + return root; |
| 18 | +} |
| 19 | + |
| 20 | +function seed(root: string, ...files: string[]): void { |
| 21 | + for (const file of files) writeFileSync(join(root, file), file); |
| 22 | +} |
| 23 | + |
| 24 | +afterEach(() => { |
| 25 | + for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); |
| 26 | +}); |
| 27 | + |
| 28 | +describe("extractedFrameIndex", () => { |
| 29 | + it("derives frame identity across the five-to-six-digit boundary", () => { |
| 30 | + expect(extractedFrameIndex("frame_99999.jpg", "jpg")).toBe(99_998); |
| 31 | + expect(extractedFrameIndex("frame_100000.jpg", "jpg")).toBe(99_999); |
| 32 | + }); |
| 33 | + |
| 34 | + it("refuses malformed, zero, and wrong-format frame candidates", () => { |
| 35 | + expect(() => extractedFrameIndex("frame_bad.jpg", "jpg")).toThrow(ExtractedFrameSequenceError); |
| 36 | + expect(() => extractedFrameIndex("frame_00000.jpg", "jpg")).toThrow( |
| 37 | + ExtractedFrameSequenceError, |
| 38 | + ); |
| 39 | + expect(() => extractedFrameIndex("frame_00001.png", "jpg")).toThrow( |
| 40 | + ExtractedFrameSequenceError, |
| 41 | + ); |
| 42 | + }); |
| 43 | +}); |
| 44 | + |
| 45 | +describe("framePathsFromDirectory", () => { |
| 46 | + it("maps by the filename ordinal instead of directory or lexical position", () => { |
| 47 | + const root = frameDir(); |
| 48 | + seed( |
| 49 | + root, |
| 50 | + ...Array.from({ length: 10 }, (_, index) => `frame_${index + 1}.jpg`).reverse(), |
| 51 | + "notes.txt", |
| 52 | + ); |
| 53 | + |
| 54 | + const paths = framePathsFromDirectory(root, "jpg"); |
| 55 | + |
| 56 | + expect(paths.size).toBe(10); |
| 57 | + expect(basename(paths.get(8)!)).toBe("frame_9.jpg"); |
| 58 | + expect(basename(paths.get(9)!)).toBe("frame_10.jpg"); |
| 59 | + }); |
| 60 | + |
| 61 | + it("fails loudly when two filenames claim the same numeric frame", () => { |
| 62 | + const root = frameDir(); |
| 63 | + seed(root, "frame_1.jpg", "frame_00001.jpg"); |
| 64 | + |
| 65 | + expect(() => framePathsFromDirectory(root, "jpg")).toThrow(/duplicate.*frame index 0/i); |
| 66 | + }); |
| 67 | + |
| 68 | + it("fails loudly instead of shifting later frames across a gap", () => { |
| 69 | + const root = frameDir(); |
| 70 | + seed(root, "frame_00001.jpg", "frame_00003.jpg"); |
| 71 | + |
| 72 | + expect(() => framePathsFromDirectory(root, "jpg")).toThrow(/missing.*frame index 1/i); |
| 73 | + }); |
| 74 | + |
| 75 | + it("fails on frame-prefixed malformed candidates but ignores unrelated files", () => { |
| 76 | + const root = frameDir(); |
| 77 | + seed(root, "frame_00001.jpg", "frame_bad.jpg", "notes.txt"); |
| 78 | + |
| 79 | + expect(() => framePathsFromDirectory(root, "jpg")).toThrow(/invalid.*frame filename/i); |
| 80 | + }); |
| 81 | +}); |
0 commit comments