import { describe, expect, it } from "vitest"; import { computeCompositeLayout } from "./compositeLayout"; describe("computeCompositeLayout", () => { it("anchors the overlay in the lower-right corner", () => { const layout = computeCompositeLayout({ canvasSize: { width: 1920, height: 1080 }, screenSize: { width: 1920, height: 1080 }, webcamSize: { width: 1280, height: 720 }, }); expect(layout).not.toBeNull(); expect(layout!.webcamRect).not.toBeNull(); expect(layout!.webcamRect!.x + layout!.webcamRect!.width).toBeLessThanOrEqual(1920); expect(layout!.webcamRect!.y + layout!.webcamRect!.height).toBeLessThanOrEqual(1080); expect(layout!.webcamRect!.x).toBeGreaterThan(1920 / 2); expect(layout!.webcamRect!.y).toBeGreaterThan(1080 / 2); }); it("keeps the overlay within the configured stage fraction while preserving aspect ratio", () => { const layout = computeCompositeLayout({ canvasSize: { width: 1280, height: 720 }, screenSize: { width: 1280, height: 720 }, webcamSize: { width: 1920, height: 1080 }, }); const refDim = Math.sqrt(1280 * 720); const defaultFraction = 25 / 100; // DEFAULT_WEBCAM_SIZE_PRESET = 25 expect(layout).not.toBeNull(); expect(layout!.webcamRect).not.toBeNull(); expect(layout!.webcamRect!.width).toBeLessThanOrEqual(Math.round(refDim * defaultFraction) + 1); expect(layout!.webcamRect!.height).toBeLessThanOrEqual( Math.round(refDim * defaultFraction) + 1, ); expect( Math.abs(layout!.webcamRect!.width * 1080 - layout!.webcamRect!.height * 1920), ).toBeLessThanOrEqual(1920); }); it("produces consistent webcam size across landscape and portrait aspect ratios", () => { const webcamSize = { width: 1280, height: 720 }; const landscape = computeCompositeLayout({ canvasSize: { width: 1920, height: 1080 }, screenSize: { width: 1920, height: 1080 }, webcamSize, webcamSizePreset: 50, }); const portrait = computeCompositeLayout({ canvasSize: { width: 1080, height: 1920 }, screenSize: { width: 1080, height: 1920 }, webcamSize, webcamSizePreset: 50, }); expect(landscape).not.toBeNull(); expect(portrait).not.toBeNull(); // Same total pixel count — webcam area should be comparable const landscapeArea = landscape!.webcamRect!.width * landscape!.webcamRect!.height; const portraitArea = portrait!.webcamRect!.width * portrait!.webcamRect!.height; expect(landscapeArea).toBe(portraitArea); }); it("scales the webcam proportionally as webcamSizePreset increases", () => { const canvasSize = { width: 1920, height: 1080 }; const screenSize = { width: 1920, height: 1080 }; const webcamSize = { width: 1280, height: 720 }; const small = computeCompositeLayout({ canvasSize, screenSize, webcamSize, webcamSizePreset: 10, }); const medium = computeCompositeLayout({ canvasSize, screenSize, webcamSize, webcamSizePreset: 25, }); const large = computeCompositeLayout({ canvasSize, screenSize, webcamSize, webcamSizePreset: 50, }); expect(small!.webcamRect!.width).toBeLessThan(medium!.webcamRect!.width); expect(medium!.webcamRect!.width).toBeLessThan(large!.webcamRect!.width); expect(small!.webcamRect!.height).toBeLessThan(medium!.webcamRect!.height); expect(medium!.webcamRect!.height).toBeLessThan(large!.webcamRect!.height); }); it("clamps webcamSizePreset to the valid range (10–50)", () => { const canvasSize = { width: 1920, height: 1080 }; const screenSize = { width: 1920, height: 1080 }; const webcamSize = { width: 1280, height: 720 }; const atMin = computeCompositeLayout({ canvasSize, screenSize, webcamSize, webcamSizePreset: 10, }); const belowMin = computeCompositeLayout({ canvasSize, screenSize, webcamSize, webcamSizePreset: 1, }); const atMax = computeCompositeLayout({ canvasSize, screenSize, webcamSize, webcamSizePreset: 50, }); const aboveMax = computeCompositeLayout({ canvasSize, screenSize, webcamSize, webcamSizePreset: 100, }); // Values below 10 should clamp to 10 expect(belowMin!.webcamRect!.width).toBe(atMin!.webcamRect!.width); expect(belowMin!.webcamRect!.height).toBe(atMin!.webcamRect!.height); // Values above 50 should clamp to 50 expect(aboveMax!.webcamRect!.width).toBe(atMax!.webcamRect!.width); expect(aboveMax!.webcamRect!.height).toBe(atMax!.webcamRect!.height); }); it("centers the combined screen and webcam stack in vertical stack mode", () => { const layout = computeCompositeLayout({ canvasSize: { width: 1920, height: 1080 }, maxContentSize: { width: 1536, height: 864 }, screenSize: { width: 1920, height: 1080 }, webcamSize: { width: 1280, height: 720 }, layoutPreset: "vertical-stack", }); expect(layout).not.toBeNull(); // Webcam is full-width at the bottom expect(layout!.webcamRect).not.toBeNull(); expect(layout!.webcamRect!.x).toBe(0); expect(layout!.webcamRect!.width).toBe(1920); expect(layout!.webcamRect!.borderRadius).toBe(0); // Screen fills remaining space at the top (cover mode) expect(layout!.screenRect.x).toBe(0); expect(layout!.screenRect.y).toBe(0); expect(layout!.screenRect.width).toBe(1920); expect(layout!.screenCover).toBe(true); }); it("keeps the screen full-canvas and omits the webcam when dimensions are unavailable in stack mode", () => { const layout = computeCompositeLayout({ canvasSize: { width: 1920, height: 1080 }, maxContentSize: { width: 1536, height: 864 }, screenSize: { width: 1920, height: 1080 }, layoutPreset: "vertical-stack", }); expect(layout).not.toBeNull(); expect(layout?.screenRect).toEqual({ x: 0, y: 0, width: 1920, height: 1080, }); expect(layout?.webcamRect).toBeNull(); expect(layout?.screenCover).toBe(true); }); it("forces circular and square masks to use square dimensions", () => { const circularLayout = computeCompositeLayout({ canvasSize: { width: 1920, height: 1080 }, screenSize: { width: 1920, height: 1080 }, webcamSize: { width: 1280, height: 720 }, webcamMaskShape: "circle", }); const squareLayout = computeCompositeLayout({ canvasSize: { width: 1920, height: 1080 }, screenSize: { width: 1920, height: 1080 }, webcamSize: { width: 1280, height: 720 }, webcamMaskShape: "square", }); expect(circularLayout?.webcamRect).not.toBeNull(); expect(squareLayout?.webcamRect).not.toBeNull(); expect(circularLayout?.webcamRect?.width).toBe(circularLayout?.webcamRect?.height); expect(squareLayout?.webcamRect?.width).toBe(squareLayout?.webcamRect?.height); expect(circularLayout?.webcamRect?.maskShape).toBe("circle"); expect(squareLayout?.webcamRect?.maskShape).toBe("square"); }); it("applies larger rounding for the rounded webcam mask", () => { const roundedLayout = computeCompositeLayout({ canvasSize: { width: 1920, height: 1080 }, screenSize: { width: 1920, height: 1080 }, webcamSize: { width: 1280, height: 720 }, webcamMaskShape: "rounded", }); const rectangleLayout = computeCompositeLayout({ canvasSize: { width: 1920, height: 1080 }, screenSize: { width: 1920, height: 1080 }, webcamSize: { width: 1280, height: 720 }, webcamMaskShape: "rectangle", }); expect(roundedLayout?.webcamRect).not.toBeNull(); expect(rectangleLayout?.webcamRect).not.toBeNull(); expect(roundedLayout?.webcamRect?.borderRadius).toBeGreaterThan( rectangleLayout?.webcamRect?.borderRadius ?? 0, ); expect(roundedLayout?.webcamRect?.maskShape).toBe("rounded"); }); });