UNPKG

@gravity-ui/graph

Version:

Modern graph editor component

213 lines (212 loc) 10.1 kB
import { MultipleSelectionBucket } from "./MultipleSelectionBucket"; import { ESelectionStrategy } from "./types"; describe("MultipleSelectionBucket", () => { let bucket; let lastPayload = null; let defaultActionCalled = false; beforeEach(() => { lastPayload = null; defaultActionCalled = false; bucket = new MultipleSelectionBucket("block", (payload, defaultAction) => { lastPayload = payload; defaultAction(); defaultActionCalled = true; return true; }); }); it("selects and deselects with REPLACE", () => { bucket.updateSelection(["a", "b"], true, ESelectionStrategy.REPLACE); expect(bucket.$selected.value).toEqual(new Set(["a", "b"])); expect(lastPayload).toEqual({ list: ["a", "b"], changes: { add: ["a", "b"], removed: [] }, }); bucket.updateSelection(["a"], false, ESelectionStrategy.SUBTRACT); expect(bucket.$selected.value).toEqual(new Set(["b"])); expect(lastPayload).toEqual({ list: ["b"], changes: { add: [], removed: ["a"] }, }); }); it("appends and toggles selection", () => { bucket.updateSelection(["a"], true, ESelectionStrategy.REPLACE); expect(lastPayload).toEqual({ list: ["a"], changes: { add: ["a"], removed: [] }, }); bucket.updateSelection(["b"], true, ESelectionStrategy.APPEND); expect(lastPayload).toEqual({ list: ["a", "b"], changes: { add: ["b"], removed: [] }, }); bucket.updateSelection(["a"], true, ESelectionStrategy.TOGGLE); expect(lastPayload).toEqual({ list: ["b"], changes: { add: [], removed: ["a"] }, }); }); it("reset clears all selection", () => { bucket.updateSelection(["a", "b"], true, ESelectionStrategy.REPLACE); bucket.reset(); expect(bucket.$selected.value.size).toBe(0); expect(lastPayload).toEqual({ list: [], changes: { add: [], removed: ["a", "b"] }, }); }); it("isSelected returns correct state", () => { bucket.updateSelection(["a"], true, ESelectionStrategy.REPLACE); expect(bucket.isSelected("a")).toBe(true); expect(bucket.isSelected("b")).toBe(false); }); it("does nothing if ids is empty", () => { bucket.updateSelection([], true, ESelectionStrategy.APPEND); expect(bucket.$selected.value.size).toBe(0); expect(lastPayload).toBeNull(); }); it("handles duplicate ids gracefully", () => { bucket.updateSelection(["a", "a", "b"], true, ESelectionStrategy.APPEND); expect(bucket.$selected.value).toEqual(new Set(["a", "b"])); expect(lastPayload).toEqual({ list: ["a", "b"], changes: { add: ["a", "b"], removed: [] }, }); }); it("SUBTRACT does nothing if id is not selected", () => { bucket.updateSelection(["a"], false, ESelectionStrategy.SUBTRACT); expect(bucket.$selected.value.size).toBe(0); expect(lastPayload).toBeNull(); }); it("APPEND with already selected ids does not emit event", () => { bucket.updateSelection(["a"], true, ESelectionStrategy.APPEND); lastPayload = null; bucket.updateSelection(["a"], true, ESelectionStrategy.APPEND); expect(lastPayload).toBeNull(); }); it("REPLACE with empty ids clears selection", () => { bucket.updateSelection(["a"], true, ESelectionStrategy.APPEND); bucket.updateSelection([], true, ESelectionStrategy.REPLACE); expect(bucket.$selected.value.size).toBe(0); }); it("does not update selection if onSelectionChange returns false", () => { const bucket2 = new MultipleSelectionBucket("block", () => false); bucket2.updateSelection(["a"], true, ESelectionStrategy.APPEND); expect(bucket2.$selected.value.size).toBe(0); }); it("treats '1' and 1 as different ids", () => { // @ts-expect-error: for test purposes bucket.updateSelection(["1", 1], true, ESelectionStrategy.APPEND); expect(bucket.$selected.value).toEqual(new Set(["1", 1])); }); it("reset does nothing if selection is already empty", () => { bucket.reset(); expect(bucket.$selected.value.size).toBe(0); expect(lastPayload).toBeNull(); }); describe("TOGGLE strategy comprehensive tests", () => { it("toggles selection with mixed existing and new IDs", () => { // Initial selection bucket.updateSelection(["a", "b"], true, ESelectionStrategy.REPLACE); expect(bucket.$selected.value).toEqual(new Set(["a", "b"])); // Toggle: remove 'a', add 'c' bucket.updateSelection(["a", "c"], true, ESelectionStrategy.TOGGLE); expect(bucket.$selected.value).toEqual(new Set(["b", "c"])); expect(lastPayload).toEqual({ list: ["b", "c"], changes: { add: ["c"], removed: ["a"] }, }); }); it("toggle with select=false removes existing IDs", () => { bucket.updateSelection(["a"], true, ESelectionStrategy.REPLACE); bucket.updateSelection(["a", "b"], false, ESelectionStrategy.TOGGLE); // With select=false, TOGGLE should remove 'a' but not add 'b' expect(bucket.$selected.value).toEqual(new Set()); }); }); describe("Edge cases and error conditions", () => { it("handles null and undefined IDs gracefully", () => { bucket.updateSelection([null, undefined], true, ESelectionStrategy.APPEND); expect(bucket.$selected.value).toEqual(new Set([null, undefined])); }); }); describe("Strategy combinations", () => { it("APPEND after REPLACE maintains correct state", () => { bucket.updateSelection(["a", "b"], true, ESelectionStrategy.REPLACE); bucket.updateSelection(["c", "d"], true, ESelectionStrategy.APPEND); expect(bucket.$selected.value).toEqual(new Set(["a", "b", "c", "d"])); }); it("SUBTRACT after APPEND removes correct items", () => { bucket.updateSelection(["a", "b", "c"], true, ESelectionStrategy.REPLACE); bucket.updateSelection(["b", "d"], true, ESelectionStrategy.SUBTRACT); expect(bucket.$selected.value).toEqual(new Set(["a", "c"])); }); it("REPLACE with empty array after complex selection", () => { bucket.updateSelection(["a", "b"], true, ESelectionStrategy.REPLACE); bucket.updateSelection(["c"], true, ESelectionStrategy.APPEND); bucket.updateSelection([], true, ESelectionStrategy.REPLACE); expect(bucket.$selected.value.size).toBe(0); }); }); describe("Callback behavior", () => { it("prevents selection when callback returns false", () => { const preventBucket = new MultipleSelectionBucket("test", () => false); preventBucket.updateSelection(["a"], true, ESelectionStrategy.REPLACE); expect(preventBucket.$selected.value.size).toBe(0); }); it("supports callback that modifies selection", () => { const modifyBucket = new MultipleSelectionBucket("test", (payload, defaultAction) => { // Modify the selection to always include 'x' const modifiedIds = new Set([...payload.list, "x"]); defaultAction(modifiedIds); return true; }); modifyBucket.updateSelection(["a"], true, ESelectionStrategy.REPLACE); // Note: This test shows the callback pattern, but actual modification would need bucket internals access expect(modifyBucket.$selected.value).toEqual(new Set(["a", "x"])); }); }); describe("Reactive behavior", () => { it("$selected signal updates reactively", () => { const values = []; // Subscribe to changes const unsubscribe = bucket.$selected.subscribe((value) => { values.push(new Set(value)); }); bucket.updateSelection(["a"], true, ESelectionStrategy.REPLACE); bucket.updateSelection(["b"], true, ESelectionStrategy.APPEND); bucket.reset(); unsubscribe(); expect(values).toHaveLength(4); // Initial + 3 changes expect(values[1]).toEqual(new Set(["a"])); expect(values[2]).toEqual(new Set(["a", "b"])); expect(values[3]).toEqual(new Set()); }); }); describe("Silent mode", () => { it("silent=true prevents callback execution", () => { bucket.updateSelection(["a"], true, ESelectionStrategy.REPLACE, true); expect(lastPayload).toBeNull(); expect(defaultActionCalled).toBe(false); expect(bucket.$selected.value).toEqual(new Set(["a"])); }); it("silent=false executes callback normally", () => { bucket.updateSelection(["a"], true, ESelectionStrategy.REPLACE, false); expect(lastPayload).not.toBeNull(); expect(defaultActionCalled).toBe(true); }); }); describe("Type safety", () => { it("works with number IDs", () => { const numberBucket = new MultipleSelectionBucket("numbers"); numberBucket.updateSelection([1, 2, 3], true, ESelectionStrategy.REPLACE); expect(numberBucket.$selected.value).toEqual(new Set([1, 2, 3])); expect(numberBucket.isSelected(2)).toBe(true); expect(numberBucket.isSelected(4)).toBe(false); }); it("works with mixed string/number types", () => { const mixedBucket = new MultipleSelectionBucket("mixed"); mixedBucket.updateSelection(["a", 1, "b", 2], true, ESelectionStrategy.REPLACE); expect(mixedBucket.$selected.value).toEqual(new Set(["a", 1, "b", 2])); }); }); });