mock: PHY Editorで異形キーを描き・編集し・追加できるようにする(T-32)
- 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
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
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$/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user