166 lines
5.6 KiB
JavaScript
166 lines
5.6 KiB
JavaScript
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);
|
|
});
|
|
|
|
test("compile lifecycle state transitions are deterministic", () => {
|
|
const storeA = createSchemetaStore();
|
|
const storeB = createSchemetaStore();
|
|
|
|
storeA.actions.beginCompile("Compile");
|
|
storeA.actions.failCompile("Compile", "Bad payload");
|
|
storeA.actions.beginCompile("Analyze");
|
|
storeA.actions.completeCompile("Analyze");
|
|
|
|
storeB.actions.beginCompile("Compile");
|
|
storeB.actions.failCompile("Compile", "Bad payload");
|
|
storeB.actions.beginCompile("Analyze");
|
|
storeB.actions.completeCompile("Analyze");
|
|
|
|
assert.deepEqual(storeA.getState().lifecycle, storeB.getState().lifecycle);
|
|
assert.equal(storeA.getState().lifecycle.isCompiling, false);
|
|
assert.equal(storeA.getState().lifecycle.lastError, null);
|
|
assert.equal(storeA.getState().lifecycle.lastAction, "Analyze");
|
|
});
|
|
|
|
test("compile lifecycle updates do not alter undo/redo history", () => {
|
|
const store = createSchemetaStore();
|
|
store.actions.setModel(baseModel());
|
|
store.actions.moveComponent("U1", { x: 90, y: 70 });
|
|
|
|
const beforeUndo = store.getState();
|
|
const pastBeforeLifecycle = beforeUndo.history.past.length;
|
|
|
|
store.actions.beginCompile("Compile");
|
|
store.actions.failCompile("Compile", "Synthetic error");
|
|
store.actions.beginCompile("Compile");
|
|
store.actions.completeCompile("Compile");
|
|
|
|
assert.equal(store.getState().history.past.length, pastBeforeLifecycle);
|
|
assert.equal(store.getState().lifecycle.lastAction, "Compile");
|
|
assert.equal(store.getState().lifecycle.lastError, null);
|
|
|
|
store.actions.undo();
|
|
const u1 = store.getState().model.instances.find((x) => x.ref === "U1");
|
|
assert.equal(u1.placement.x, null);
|
|
assert.equal(u1.placement.y, null);
|
|
});
|