import { describe, it, expect } from 'vitest'; import { keySheet, maskStats, } from '../../src/lib/core/mask.js'; // M1-3 mask key geometry — unit tests (RED first, per strict TDD). // // Ground truth: docs/CAPTURE.md §Mask // - 4 knobs: sheet Auto/Green/Black, sensitivity 0–100, edge offset, feather // - Control value updates IMMEDIATELY; overlay recomputes 2.0 s after the // last sensitivity change (debounce). // - Approve blocked when maskStats is empty. // // All functions here are pure (no DOM) so the state is unit-testable in Node; // the live UI (M1-5) drives this machine. // // --- synthetic photo helpers (no DOM) -----------------------------------------\n// function solid(w, h, r, g, b) { const data = new Uint8ClampedArray(w * h * 4); for (let i = 0; i < w * h; i++) { data[i * 4 + 0] = r; data[i * 4 + 1] = g; data[i * 4 + 2] = b; data[i * 4 + 3] = 255; } return data; } function withRect(data, width, x0, y0, w, h, r, g, b) { for (let y = y0; y < y0 + h; y++) { for (let x = x0; x < x0 + w; x++) { const i = (y * width + x) * 4; data[i + 0] = r; data[i + 1] = g; data[i + 2] = b; data[i + 3] = 255; } } return data; } // 12 x 8 green sheet with an 8 x 4 brown slab at (2,2). const GREEN_PHOTO = () => withRect(solid(12, 8, 0, 200, 0), 12, 2, 2, 8, 4, 140, 90, 40); const DIMS = { width: 12, height: 8 }; // --- keySheet --------------------------------------------------------------------- // describe('keySheet', () => { it('keeps the slab in a green sheet photo', () => { const mask = keySheet(GREEN_PHOTO(), { sheet: 'green', sensitivity: 50, }); // 8x4 = 32 pixels expect(maskStats(mask).kept).toBe(32); }); it('keeps the slab in a black sheet photo', () => { const blackPhoto = withRect(solid(12, 8, 10, 10, 10), 12, 2, 2, 8, 4, 220, 210, 190); const mask = keySheet(blackPhoto, { sheet: 'black', sensitivity: 50, }); expect(maskStats(mask).kept).toBe(32); }); it('auto-selects the better key', () => { // A photo that works well for both should return the green one const mask = keySheet(GREEN_PHOTO(), { sheet: 'auto', sensitivity: 50, }); expect(maskStats(mask).kept).toBe(32); }); it('handles sensitivity 0 (tightest)', () => { const mask = keySheet(GREEN_PHOTO(), { sheet: 'green', sensitivity: 0, }); // At s=0, threshold is 150. // brown wood (140,90,40) dist = sqrt(140^2 + (90-255)^2 + 40^2) = sqrt(19600 + 27225 + 1600) = ~218. // 218 > 150, so kept. expect(maskStats(mask).kept).toBe(32); }); it('handles sensitivity 100 (loosest)', () => { const mask = keySheet(GREEN_PHOTO(), { sheet: 'green', sensitivity: 100, }); // At s=100, threshold is 50. // Sheet green (0,200,0) dist = 55. // 55 > 50, so sheet is kept too! // 12x8 = 96 pixels expect(maskStats(mask).kept).toBe(96); }); }); // --- maskStats ------------------------------------------------------------------- // describe('maskStats', () => { it('correctly reports kept count and empty status', () => { const mask = new Uint8Array([1, 0, 1, 0]); const stats = maskStats(mask); expect(stats.kept).toBe(2); expect(stats.total).toBe(4); expect(stats.empty).toBe(false); }); it('reports empty=true when no pixels kept', () => { const mask = new Uint8Array([0, 0, 0, 0]); const stats = maskStats(mask); expect(stats.kept).toBe(0); expect(stats.empty).toBe(true); }); });