- domain/keyShape.ts に、キーの矩形・外接矩形・和の外形の計算と、異形キーのプリセット表(今はISO Enterだけ)を置いた - キャンバスと選択枠は、2つ目の矩形を持つキーを和の外形のpathで描く。範囲選択と囲み矩形も両方の矩形で取る - プロパティ欄に「異形キー」のチェックと、2つ目の矩形の欄(X2・Y2・Width2・Height2)を置いた - ツールバーに「異形キーを足す」selectを置いた。1Uと同じ規則の位置に、外接矩形の左上を合わせて足す - 検証: vue-tsc 0件、Vitest 106件(うち新規5件)、Playwright PHY 31件(うち新規1件)、prettier --check Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xmqw9x7EoryY6zf116i3yM
63 lines
2.1 KiB
TypeScript
63 lines
2.1 KiB
TypeScript
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$/);
|
|
});
|
|
});
|