import { describe, expect, test } from "bun:test" import { availableQualities, effectiveTiles, layoutDimensions, regionFree, tileIndexAt, tileSpan, type Layout, } from "./types.ts" describe("layoutDimensions", () => { test("parses COLSxROWS", () => { expect(layoutDimensions({ name: "a", grid: "4x3" })).toEqual([4, 3]) }) test("tolerates whitespace and case", () => { expect(layoutDimensions({ name: "a", grid: " 2X2 " })).toEqual([2, 2]) }) // Go rejects these on save; the editor must stay usable rather than crash. test.each(["", "4", "4x", "ax3", "0x3", "-1x2", "4x3x2"])("falls back to 1x1 on %p", (grid) => { expect(layoutDimensions({ name: "a", grid })).toEqual([1, 1]) }) }) describe("tileSpan", () => { test("normalizes absent and zero spans to 1", () => { expect(tileSpan({ camera: "a", col: 0, row: 0 })).toEqual([1, 1]) expect(tileSpan({ camera: "a", col: 0, row: 0, colspan: 0, rowspan: 0 })).toEqual([1, 1]) expect(tileSpan({ camera: "a", col: 0, row: 0, colspan: 3, rowspan: 2 })).toEqual([3, 2]) }) }) describe("effectiveTiles", () => { test("normalizes spans without mutating the input", () => { const l: Layout = { name: "a", grid: "2x2", tiles: [{ camera: "cam", col: 0, row: 0 }] } expect(effectiveTiles(l)).toEqual([{ camera: "cam", col: 0, row: 0, colspan: 1, rowspan: 1 }]) expect(l.tiles![0]!.colspan).toBeUndefined() }) test("upgrades legacy row-major slots, skipping blanks", () => { const l: Layout = { name: "a", grid: "2x2", slots: ["one", "", "three"] } expect(effectiveTiles(l)).toEqual([ { camera: "one", col: 0, row: 0, colspan: 1, rowspan: 1 }, { camera: "three", col: 0, row: 1, colspan: 1, rowspan: 1 }, ]) }) test("prefers tiles over slots when both are present", () => { const l: Layout = { name: "a", grid: "2x2", tiles: [{ camera: "fromTiles", col: 1, row: 1 }], slots: ["fromSlots"], } expect(effectiveTiles(l).map((t) => t.camera)).toEqual(["fromTiles"]) }) }) describe("tileIndexAt", () => { const tiles = [ { camera: "big", col: 0, row: 0, colspan: 3, rowspan: 2 }, { camera: "small", col: 3, row: 0 }, ] test("finds a tile through the whole span, not just its origin", () => { expect(tileIndexAt(tiles, 0, 0)).toBe(0) expect(tileIndexAt(tiles, 2, 1)).toBe(0) expect(tileIndexAt(tiles, 3, 0)).toBe(1) }) test("returns -1 on an empty cell", () => { expect(tileIndexAt(tiles, 3, 1)).toBe(-1) expect(tileIndexAt(tiles, 0, 2)).toBe(-1) }) }) describe("regionFree", () => { const tiles = [ { camera: "a", col: 0, row: 0, colspan: 2, rowspan: 1 }, { camera: "b", col: 2, row: 0, colspan: 1, rowspan: 1 }, ] test("rejects regions leaving the grid", () => { expect(regionFree(tiles, 4, 2, -1, 3, 0, 2, 1)).toBe(false) expect(regionFree(tiles, 4, 2, -1, 0, 1, 1, 3)).toBe(false) expect(regionFree(tiles, 4, 2, -1, -1, 0, 1, 1)).toBe(false) }) test("rejects regions overlapping another tile", () => { expect(regionFree(tiles, 4, 2, -1, 1, 0, 1, 1)).toBe(false) }) test("ignores the excluded tile, so a tile can grow in place", () => { // Tile "a" growing from 2x1 to 2x2 overlaps only itself. expect(regionFree(tiles, 4, 2, 0, 0, 0, 2, 2)).toBe(true) // ...but it cannot grow into "b". expect(regionFree(tiles, 4, 2, 0, 0, 0, 3, 1)).toBe(false) }) test("accepts a free region", () => { expect(regionFree(tiles, 4, 2, -1, 3, 0, 1, 2)).toBe(true) }) }) describe("availableQualities", () => { const qualities = ["high", "medium", "low"] test("lists only present streams, best first", () => { expect(availableQualities({ name: "c", streams: { low: "u", high: "u" } }, qualities)).toEqual(["high", "low"]) }) test("is empty for a camera with no streams map", () => { expect(availableQualities({ name: "c", rtsp: "rtsp://x" }, qualities)).toEqual([]) expect(availableQualities(undefined, qualities)).toEqual([]) }) })