import { describe, expect, it } from "vitest"; import { IRREGULAR_KEY_PRESETS, hasSecondRect, keyOrigin, keyOutline, keyRects, } from "../src/domain/keyShape"; import { keyPath } from "../src/ui/phy/logic"; const isoEnter = IRREGULAR_KEY_PRESETS.find((p) => p.id === "iso-enter")!.size; const asSet = (loop: { x: number; y: number }[]) => loop.map((p) => `${p.x},${p.y}`).sort(); describe("key shape", () => { it("fills missing second-rectangle fields with KLE's defaults", () => { expect(hasSecondRect({ w: 1, h: 1 })).toBe(false); expect(keyRects({ w: 1, h: 1 })).toEqual([{ x: 0, y: 0, w: 1, h: 1 }]); expect(keyRects({ w: 1.25, h: 1, w2: 1.75 })).toEqual([ { x: 0, y: 0, w: 1.25, h: 1 }, { x: 0, y: 0, w: 1.75, h: 1 }, ]); }); it("traces the ISO Enter as one clockwise loop of six corners", () => { const loops = keyOutline(isoEnter); expect(loops).toHaveLength(1); expect(asSet(loops[0]!)).toEqual( asSet([ { x: -0.25, y: 0 }, { x: 1.25, y: 0 }, { x: 1.25, y: 2 }, { x: 0, y: 2 }, { x: 0, y: 1 }, { x: -0.25, y: 1 }, ]), ); // Shoelace area is positive for a clockwise loop on a y-down screen. const loop = loops[0]!; const area = loop.reduce((sum, p, i) => { const q = loop[(i + 1) % loop.length]!; return sum + (p.x * q.y - q.x * p.y) / 2; }, 0); expect(area).toBeCloseTo(1.25 * 2 + 0.25); }); it("traces a rectangle-in-rectangle as the outer rectangle", () => { expect(keyOutline({ w: 1, h: 1, w2: 2 }).map(asSet)).toEqual([ asSet([ { x: 0, y: 0 }, { x: 2, y: 0 }, { x: 2, y: 1 }, { x: 0, y: 1 }, ]), ]); }); it("puts the bounding box's top-left where the key is placed", () => { expect(keyOrigin(isoEnter)).toEqual({ x: -0.25, y: 0 }); expect(keyOrigin({ w: 1, h: 1 })).toEqual({ x: 0, y: 0 }); }); it("draws plain keys as rects and irregular ones as a path", () => { expect(keyPath({ w: 1, h: 1 })).toBeUndefined(); expect(keyPath(isoEnter)).toMatch(/^M .* Z$/); }); });