import test from "node:test"; import assert from "node:assert/strict"; import fixture from "../frontend/sample.schemeta.json" with { type: "json" }; import { createSchemetaStore } from "../frontend-react/src/state/store.js"; function baseModel() { return { ...fixture, instances: fixture.instances.map((instance) => ({ ...instance, placement: { ...instance.placement } })) }; } test("setSelection normalizes and sorts refs deterministically", () => { const store = createSchemetaStore(); store.actions.setSelection({ selectedRefs: ["U7", "U1", "U2", "U1", "U3"], selectedNet: "I2C_SCL", selectedPin: { ref: "U1", pin: "GPIO9" } }); const { selection } = store.getState(); assert.deepEqual(selection.selectedRefs, ["U1", "U2", "U3", "U7"]); assert.equal(selection.selectedNet, "I2C_SCL"); assert.deepEqual(selection.selectedPin, { ref: "U1", pin: "GPIO9" }); }); test("moveComponent only updates targeted instance and is deterministic", () => { const model = baseModel(); const storeA = createSchemetaStore(); const storeB = createSchemetaStore(); storeA.actions.setModel(model); storeA.actions.moveComponent("U1", { x: 121.7, y: 333.2 }); storeB.actions.setModel(model); storeB.actions.moveComponent("U1", { x: 121.7, y: 333.2 }); const stateA = storeA.getState(); const stateB = storeB.getState(); assert.deepEqual(stateA.model, stateB.model); const moved = stateA.model.instances.find((x) => x.ref === "U1"); assert.equal(moved.placement.x, 122); assert.equal(moved.placement.y, 333); const untouched = stateA.model.instances.find((x) => x.ref === "U2"); assert.equal(untouched.placement.x, null); assert.equal(untouched.placement.y, null); }); test("applyJsonText updates model on valid JSON and clears jsonError", () => { const store = createSchemetaStore({ uiFlags: { jsonError: "Old error" } }); const payload = JSON.stringify(baseModel()); const result = store.actions.applyJsonText(payload); assert.equal(result.ok, true); assert.equal(store.getState().uiFlags.jsonError, null); assert.equal(store.getState().model.instances.length > 0, true); }); test("applyJsonText reports parse errors without mutating model", () => { const store = createSchemetaStore(); store.actions.setModel(baseModel()); const before = store.getState().model; const result = store.actions.applyJsonText("{ bad json }"); assert.equal(result.ok, false); assert.equal(typeof result.error, "string"); assert.equal(store.getState().uiFlags.jsonError.length > 0, true); assert.deepEqual(store.getState().model, before); }); test("undo/redo restores deterministic snapshots", () => { const store = createSchemetaStore(); store.actions.setModel(baseModel()); store.actions.setSelection({ selectedRefs: ["U3", "U1"] }); const beforeMove = store.getState().model; store.actions.moveComponent("U1", { x: 80, y: 60 }); assert.equal(store.getState().history.past.length >= 3, true); store.actions.undo(); assert.deepEqual(store.getState().model, beforeMove); store.actions.redo(); const moved = store.getState().model.instances.find((x) => x.ref === "U1"); assert.equal(moved.placement.x, 80); assert.equal(moved.placement.y, 60); }); test("transaction scaffold groups multiple actions into one undo step", () => { const store = createSchemetaStore(); store.actions.setModel(baseModel()); const pastBefore = store.getState().history.past.length; store.actions.beginTransaction("drag-and-select"); store.actions.moveComponent("U1", { x: 240, y: 140 }); store.actions.setSelection({ selectedRefs: ["U2", "U1"] }); store.actions.commitTransaction(); assert.equal(store.getState().history.past.length, pastBefore + 1); assert.deepEqual(store.getState().selection.selectedRefs, ["U1", "U2"]); store.actions.undo(); assert.deepEqual(store.getState().selection.selectedRefs, []); const u1 = store.getState().model.instances.find((x) => x.ref === "U1"); assert.equal(u1.placement.x, null); assert.equal(u1.placement.y, null); });