import { expect, it } from "bun:test"; import { toExtendedPathKey, toSimplePath } from "../extended"; import { OperationManager, StateManager } from "../stateManager"; it("path with array of objects uses ids", () => { const obj = { foo: [{ id: "bar" }] }; const path = ["foo", 0]; const extendedPath = toExtendedPathKey(obj, path); expect(extendedPath).toEqual(["foo", { id: "bar" }]); const simplePath = toSimplePath(obj, extendedPath); expect(simplePath).toEqual(path); }); it("handles paths with string indexes", () => { const obj = { foo: [{ id: "bar" }] }; const path = ["foo", "0"]; const extendedPath = toExtendedPathKey(obj, path); expect(extendedPath).toEqual(["foo", { id: "bar" }]); const simplePath = toSimplePath(obj, extendedPath); expect(simplePath).toEqual(["foo", 0]); }); it("path with array of objects without ids uses index", () => { const obj = { foo: [{ test: "bar" }] }; const path = ["foo", 0]; const extendedPath = toExtendedPathKey(obj, path); expect(extendedPath).toEqual(path); const simplePath = toSimplePath(obj, extendedPath); expect(simplePath).toEqual(path); }); it("path with multiple arrays of objects uses ids", () => { const obj = { foo: [{ id: "bar", nested: [{ id: "baz" }] }] }; const path = ["foo", 0, "nested", 0]; const extendedPath = toExtendedPathKey(obj, path); expect(extendedPath).toEqual(["foo", { id: "bar" }, "nested", { id: "baz" }]); const simplePath = toSimplePath(obj, extendedPath); expect(simplePath).toEqual(path); }); it("path that targets id specifically uses index", () => { const obj = { foo: [{ id: "bar" }] }; const path = ["foo", 0, "id"]; const extendedPath = toExtendedPathKey(obj, path); expect(extendedPath).toEqual(path); const simplePath = toSimplePath(obj, extendedPath); expect(simplePath).toEqual(path); }); it("path that adds object into array uses index", () => { const obj = { foo: [{ id: "bar" }] }; const path = ["foo", 0]; const extendedPath = toExtendedPathKey(obj, path, "insert"); expect(extendedPath).toEqual(path); }); it("creates state manager with extended path", () => { type S = { foo: { id: string; count: number }[] }; const sm = new StateManager({ foo: [{ id: "a", count: 0 }], }); sm.edit((draft) => { draft.foo[0].count++; }); expect(sm.state.foo[0].count).toEqual(1); expect(sm.history[0].redoPatches).toEqual([ { op: "replace", path: ["foo", { id: "a" }, "count"], value: 1, }, ]); expect(sm.history[0].undoPatches).toEqual([ { op: "replace", path: ["foo", { id: "a" }, "count"], value: 0, }, ]); sm.undo(); expect(sm.state.foo[0].count).toEqual(0); }); it("removes item from array", () => { type Item = { id: string; name: string }; const a = { id: "a", name: "item" }; const b = { id: "b", name: "item" }; const sm = new StateManager<{ items: Item[] }>({ items: [a], }); sm.operate(({ add }) => { add("items/-", b); }); expect(sm.state.items).toEqual([a, b]); expect(sm.history[0].redoPatches).toEqual([ { op: "add", path: ["items", "-"], value: b, }, ]); expect(sm.history[0].undoPatches).toEqual([ { op: "remove", path: ["items", { id: "b" }], }, ]); }); const adjustState = { items: [ { id: "a" }, { id: "b" }, { id: "group", items: [{ id: "ga" }, { id: "gb" }] }, { id: "c" }, { id: "d" }, ], }; it("adjusts indexes when moving item out of group (before)", () => { const { invFrom, invTo } = OperationManager.getInverseMoveIndexes({ state: adjustState, opFrom: ["items", 2, "items", 1], opTo: ["items", 0], }); expect(invFrom).toEqual(["items", 0]); expect(invTo).toEqual(["items", 3, "items", 1]); }); it("adjusts indexes when using string indexes", () => { const { invFrom, invTo } = OperationManager.getInverseMoveIndexes({ state: adjustState, opFrom: ["items", "2", "items", "1"], opTo: ["items", "0"], }); expect(invFrom).toEqual(["items", "0"]); expect(invTo).toEqual(["items", 3, "items", "1"]); }); it("adjusts indexes when moving item out of group (after)", () => { const { invFrom, invTo } = OperationManager.getInverseMoveIndexes({ state: adjustState, opFrom: ["items", 2, "items", 1], opTo: ["items", 4], }); expect(invFrom).toEqual(["items", 4]); expect(invTo).toEqual(["items", 2, "items", 1]); }); it("adjusts indexes when moving item into group (before)", () => { const { invFrom, invTo } = OperationManager.getInverseMoveIndexes({ state: adjustState, opFrom: ["items", 0], opTo: ["items", 2, "items", 1], }); expect(invFrom).toEqual(["items", 1, "items", 1]); expect(invTo).toEqual(["items", 0]); }); it("adjusts indexes when moving item into group (after)", () => { const { invFrom, invTo } = OperationManager.getInverseMoveIndexes({ state: adjustState, opFrom: ["items", 4], opTo: ["items", 2, "items", 1], }); expect(invFrom).toEqual(["items", 2, "items", 1]); expect(invTo).toEqual(["items", 4]); }); it("moves item by id", () => { const sm = new StateManager({ items: [{ id: "a" }, { id: "b" }, { id: "c" }], }); sm.operate(({ move }) => { move(["items", 2], ["items", 0]); }); expect(sm.state.items).toEqual([{ id: "c" }, { id: "a" }, { id: "b" }]); expect(sm.history[0].redoPatches).toEqual([ { op: "move", path: ["items", 0], from: ["items", { id: "c" }], }, ]); expect(sm.history[0].undoPatches).toEqual([ { op: "move", path: ["items", 2], from: ["items", { id: "c" }], }, ]); }); it("moves item by id out of nested array", () => { const value = { items: [ { id: "a" }, { id: "b" }, { id: "group", items: [{ id: "c" }, { id: "d" }] }, ], }; const sm = new StateManager(value); sm.operate(({ move }) => { move(["items", 2, "items", 1], ["items", 0]); }); expect(sm.state.items).toEqual([ { id: "d" }, { id: "a" }, { id: "b" }, { id: "group", items: [{ id: "c" }] }, ]); sm.undo(); expect(sm.state).toEqual(value); }); it("moves item by id out of nested array to same index as original group", () => { const value = { items: [ { id: "a" }, { id: "b" }, { id: "group", items: [{ id: "c" }, { id: "d" }] }, ], }; const sm = new StateManager(value); sm.operate(({ move }) => { move(["items", 2, "items", 1], ["items", 2]); }); expect(sm.state.items).toEqual([ { id: "a" }, { id: "b" }, { id: "d" }, { id: "group", items: [{ id: "c" }] }, ]); sm.undo(); expect(sm.state).toEqual(value); }); it("moves item by id in to nested array", () => { const value = { items: [ { id: "a" }, { id: "b" }, { id: "group", items: [{ id: "c" }, { id: "d" }] }, ], }; const sm = new StateManager(value); sm.operate(({ move }) => { move(["items", 0], ["items", 2, "items", 1]); }); expect(sm.state).toEqual({ items: [ { id: "b" }, { id: "group", items: [{ id: "c" }, { id: "a" }, { id: "d" }] }, ], }); sm.undo(); expect(sm.state).toEqual(value); }); it("replaces array length", () => { const sm = new StateManager({ items: [{ id: "a" }, { id: "b" }, { id: "c" }], }); sm.edit((draft) => { draft.items.length = 0; }); expect(sm.state.items).toEqual([]); });