provisional/phy-editor-feedback.md の合意事項と phy-editor-keybindings.md の割り当て案に沿って、PHY 画面を一から書き直した。 App.vue は共通のシェル(入出力・履歴・タブ)だけにし、PHY は src/ui/phy/ に、コンポーネント・composable・純粋モジュール(logic.ts, viewport.ts)を分けて置いた。 - 画面確認で見つかった不具合(読み込み時の全体表示、Shift+クリック、削除の確認の大きさ、Alt+↑、フォーカス中の二重描画、長押し中の選択など)を直し、キャンバス上の実際の操作で確かめる e2e を足した - 階層パネルでクラスターの子を余分な折りたたみで包まず、件数を配下のキー・部品の数にそろえた - 実装は Codex(Luna)。検証: vue-tsc 0件、Vitest 64件、Playwright 30件、prettier --check Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VbfgHT848s3JbuFw8MKFF8
106 lines
3.5 KiB
TypeScript
106 lines
3.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { PhyFile } from "../src/domain/phy";
|
|
import {
|
|
addComponentToParent,
|
|
addKeyToParent,
|
|
cloneNodesWithNewIds,
|
|
duplicateNode,
|
|
insertNodes,
|
|
} from "../src/domain/phyCommands";
|
|
const ids = (() => {
|
|
let n = 0;
|
|
return () => `0000000${++n}-0000-4000-8000-000000000000`;
|
|
})();
|
|
const file: PhyFile = {
|
|
version: "0.1.0",
|
|
metadata: {},
|
|
profiles: [{ id: "p", name: "P", unitSize: { x: 1, y: 1 } }],
|
|
root: [
|
|
{ type: "cluster", id: "cl-parent", name: "P", transform: { x: 0, y: 0, rotation: 0 }, children: [] },
|
|
],
|
|
};
|
|
describe("PHY parent insertion and duplication", () => {
|
|
it("adds keys and components to root, clusters, or variants", () => {
|
|
let p = addKeyToParent(file, "cl-parent", { x: 2, y: 3, rotation: 0 }, { w: 1, h: 1 }, "p", ids);
|
|
expect(
|
|
p.root[0]!.type === "cluster" &&
|
|
(p.root[0]! as import("../src/domain/phy").ClusterNode).children[0]?.type,
|
|
).toBe("key");
|
|
p = addComponentToParent(p, "cl-parent", { x: 0, y: 0, rotation: 0 }, "@led", ids);
|
|
expect(
|
|
p.root[0]!.type === "cluster" &&
|
|
(p.root[0]! as import("../src/domain/phy").ClusterNode).children[1]?.type,
|
|
).toBe("component");
|
|
});
|
|
it("inserts a copied node batch into a selected container", () => {
|
|
const node = {
|
|
type: "key" as const,
|
|
id: "key-source",
|
|
profile: "p",
|
|
transform: { x: 1, y: 1, rotation: 0 },
|
|
size: { w: 1, h: 1 },
|
|
};
|
|
const p = insertNodes(file, "cl-parent", [node]);
|
|
expect(
|
|
p.root[0]!.type === "cluster" && (p.root[0]! as import("../src/domain/phy").ClusterNode).children,
|
|
).toHaveLength(1);
|
|
});
|
|
it("renews IDs and remaps a duplicated group's internal default", () => {
|
|
const base: PhyFile = {
|
|
...file,
|
|
root: [
|
|
{
|
|
type: "variantGroup",
|
|
id: "vg-source",
|
|
name: "VG",
|
|
transform: { x: 0, y: 0, rotation: 0 },
|
|
default: "v-one",
|
|
children: [
|
|
{ type: "variant", id: "v-one", name: "One", children: [] },
|
|
{ type: "variant", id: "v-two", name: "Two", children: [] },
|
|
],
|
|
},
|
|
],
|
|
};
|
|
const q = duplicateNode(base, "vg-source", ids);
|
|
const copy = q.root[1];
|
|
expect(copy?.type).toBe("variantGroup");
|
|
if (copy?.type === "variantGroup") {
|
|
expect(copy.id).not.toBe("vg-source");
|
|
expect(copy.default).not.toBe("v-one");
|
|
expect(copy.children.map((v) => v.id)).not.toContain("v-one");
|
|
expect(copy.default).toBe(copy.children[0]!.id);
|
|
}
|
|
});
|
|
it("remaps defaults for variant groups nested inside a copied cluster", () => {
|
|
const nested: PhyFile["root"][number] = {
|
|
type: "cluster",
|
|
id: "outer",
|
|
transform: { x: 0, y: 0, rotation: 0 },
|
|
children: [
|
|
{
|
|
type: "variantGroup",
|
|
id: "vg-inner",
|
|
name: "Inner",
|
|
transform: { x: 1, y: 2, rotation: 0 },
|
|
default: "v-inner-a",
|
|
children: [
|
|
{ type: "variant", id: "v-inner-a", name: "A", children: [] },
|
|
{ type: "variant", id: "v-inner-b", name: "B", children: [] },
|
|
],
|
|
},
|
|
],
|
|
};
|
|
const copy = cloneNodesWithNewIds([nested], ids)[0];
|
|
expect(copy?.type).toBe("cluster");
|
|
if (copy?.type === "cluster") {
|
|
const group = copy.children[0];
|
|
expect(group?.type).toBe("variantGroup");
|
|
if (group?.type === "variantGroup") {
|
|
expect(group.default).toBe(group.children[0]!.id);
|
|
expect(group.default).not.toBe("v-inner-a");
|
|
}
|
|
}
|
|
});
|
|
});
|