import { expect, it } from "bun:test"; import { StateManager } from ".."; it("gets from object", () => { type S = { foo?: number }; const sm = new StateManager({ foo: 1 }); let foo: number | undefined; sm.operate(({ get }) => { foo = get("foo"); }); expect(foo).toEqual(1); }); it("gets from array", () => { type S = { foo: number[] }; const sm = new StateManager({ foo: [1, 2, 3] }); let foo: number | undefined; sm.operate(({ get }) => { foo = get("foo/1") as number | undefined; }); expect(foo).toEqual(2); }); it("add to object", () => { type S = { foo?: number }; const sm = new StateManager({}); sm.operate(({ set, get }) => { set("foo", 1); // State is updated immediately expect(get("foo")).toEqual(1); }); expect(sm.state.foo).toEqual(1); sm.undo(); expect(sm.state.foo).toBeUndefined(); }); it("replaces in object", () => { type S = { foo: number }; const sm = new StateManager({ foo: 0 }); sm.operate(({ set }) => { set("foo", 1); }); expect(sm.state.foo).toEqual(1); sm.undo(); expect(sm.state.foo).toEqual(0); }); it("replaces in array", () => { type S = { foo: number[] }; const sm = new StateManager({ foo: [0, 1, 2] }); sm.operate(({ replace }) => { replace("foo/1", 3); }); expect(sm.state.foo).toEqual([0, 3, 2]); sm.undo(); expect(sm.state.foo).toEqual([0, 1, 2]); }); it("deletes from object", () => { type S = { foo: number }; const sm = new StateManager({ foo: 0 }); sm.operate(({ remove }) => { remove("foo"); }); expect(sm.state.foo).toBeUndefined(); sm.undo(); expect(sm.state.foo).toEqual(0); }); it("moves within object", () => { type S = { foo?: { num: number }; bar?: { num: number } }; const state = { foo: { num: 0 } }; const sm = new StateManager(state); sm.operate(({ move }) => { move("foo", "bar"); }); expect(sm.state.foo).toBeUndefined(); expect(sm.state.bar).toEqual({ num: 0 }); // Preserves reference identity expect(sm.state.bar).toEqual(state.foo); sm.undo(); expect(sm.state.foo).toEqual({ num: 0 }); expect(sm.state.bar).toBeUndefined(); }); it("moves to different object", () => { type S = { foo?: { num: number }; bar: { baz?: { num: number } } }; const state = { foo: { num: 0 }, bar: {} }; const sm = new StateManager(state); sm.operate(({ move }) => { move(["foo"], ["bar", "baz"]); }); expect(sm.state.foo).toBeUndefined(); expect(sm.state.bar).toEqual({ baz: { num: 0 } }); // Preserves reference identity expect(sm.state.bar.baz).toEqual(state.foo); sm.undo(); expect(sm.state.foo).toEqual({ num: 0 }); expect(sm.state.bar.baz).toBeUndefined(); }); it("add to array", () => { type S = { foo: number[] }; const sm = new StateManager({ foo: [1] }); sm.operate(({ add }) => { add("foo/-", 2); }); expect(sm.state.foo).toEqual([1, 2]); sm.operate(({ add }) => { add("foo/0", 3); }); expect(sm.state.foo).toEqual([3, 1, 2]); sm.undo(); expect(sm.state.foo).toEqual([1, 2]); sm.undo(); expect(sm.state.foo).toEqual([1]); }); it("removes from array", () => { type S = { foo: { id: string }[] }; const sm = new StateManager({ foo: [{ id: "1" }, { id: "2" }, { id: "3" }], }); sm.operate((op) => { op.delete("foo/1"); }); expect(sm.state.foo).toEqual([{ id: "1" }, { id: "3" }]); sm.undo(); expect(sm.state.foo).toEqual([{ id: "1" }, { id: "2" }, { id: "3" }]); }); it("moves within array", () => { type S = { foo: string[] }; const sm = new StateManager({ foo: ["all", "grass", "cows", "eat"] }); sm.operate(({ move }) => { move("foo/1", "foo/3"); }); expect(sm.state.foo).toEqual(["all", "cows", "eat", "grass"]); sm.undo(); expect(sm.state.foo).toEqual(["all", "grass", "cows", "eat"]); }); it("edits in operation", () => { type S = { foo: number }; const sm = new StateManager({ foo: 0 }); sm.operate(({ set, editDraft: edit }) => { set("foo", 1); edit((draft) => { draft.foo++; }); }); expect(sm.state.foo).toEqual(2); sm.undo(); expect(sm.state.foo).toEqual(0); });