Files
SlabUploader/frontend/src/__tests__/tv1-tv2-mask.test.ts
T
grokbot_ty 3c2f8871a8 feat(#21): Gate B green/black sheet mask (TV-1, TV-2)
Pure client modules for TECH-SPEC sheet detect, chroma-key / black
threshold with border flood-fill, four knobs (live re-run), and mask
confirm hooks. Synthetic fixtures. U2Net never invoked.

TDD: red evidence in test-results/tv1-tv2-red.txt, then minimum impl.

Results:
TV-1 | pass | test-results/tv1-tv2-green.txt
TV-2 | pass | test-results/tv1-tv2-green.txt
2026-09-07 05:53:43 +00:00

83 lines
3.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
confirmMask,
createMaskSession,
defaultKnobs,
removeSheetBackground,
reRunMask,
requestRetake,
} from "../lib/pipeline/mask.js";
import {
blackSheetFixture,
greenSheetFixture,
maskAt,
} from "./fixtures/synthetic.js";
describe("TV-1 green-sheet chroma-key", () => {
it("keeps interior green-ish patch that does not touch the border", () => {
const { image, interiorGreen, woodSample, borderSample } = greenSheetFixture();
const knobs = { ...defaultKnobs(), sheet: "green" as const };
const result = removeSheetBackground(image, knobs);
expect(result.u2netInvoked).toBe(false);
expect(result.sheetUsed).toBe("green");
expect(maskAt(result.mask, result.width, borderSample.x, borderSample.y)).toBe(0);
expect(maskAt(result.mask, result.width, woodSample.x, woodSample.y)).toBeGreaterThan(0);
expect(
maskAt(result.mask, result.width, interiorGreen.x, interiorGreen.y),
).toBeGreaterThan(0);
});
it("re-runs live when knobs change and still skips U2Net", () => {
const { image } = greenSheetFixture();
const first = removeSheetBackground(image, { ...defaultKnobs(), sheet: "green" });
const second = reRunMask(image, {
...defaultKnobs(),
sheet: "green",
sensitivity: 0.7,
edgeOffset: 1,
feather: 1,
});
expect(first.u2netInvoked).toBe(false);
expect(second.u2netInvoked).toBe(false);
expect(second.knobs.sensitivity).toBe(0.7);
});
});
describe("TV-2 black-sheet threshold + border flood-fill", () => {
it("keeps dark interior wood that does not touch the border", () => {
const { image, darkInterior, woodSample, borderSample } = blackSheetFixture();
const knobs = { ...defaultKnobs(), sheet: "black" as const };
const result = removeSheetBackground(image, knobs);
expect(result.u2netInvoked).toBe(false);
expect(result.sheetUsed).toBe("black");
expect(maskAt(result.mask, result.width, borderSample.x, borderSample.y)).toBe(0);
expect(maskAt(result.mask, result.width, woodSample.x, woodSample.y)).toBeGreaterThan(0);
expect(
maskAt(result.mask, result.width, darkInterior.x, darkInterior.y),
).toBeGreaterThan(0);
});
it("flood-fill removes only border-connected dark pixels", () => {
const { image, darkInterior, borderSample } = blackSheetFixture();
const result = removeSheetBackground(image, { ...defaultKnobs(), sheet: "black" });
// Border sheet gone; isolated dark island kept.
expect(maskAt(result.mask, result.width, borderSample.x, borderSample.y)).toBe(0);
expect(
maskAt(result.mask, result.width, darkInterior.x, darkInterior.y),
).toBeGreaterThan(0);
});
});
describe("mask confirm hooks (pure)", () => {
it("starts pending, confirm and retake transition without DOM", () => {
const { image } = greenSheetFixture();
const result = removeSheetBackground(image, { ...defaultKnobs(), sheet: "green" });
const session = createMaskSession(result);
expect(session.state).toBe("pending");
expect(confirmMask(session).state).toBe("confirmed");
expect(requestRetake(session).state).toBe("retake");
});
});