UNPKG

tabulator-tables

Version:

Interactive table generation JavaScript library

561 lines (444 loc) 19.3 kB
import ReactiveData from "../../../src/js/modules/ReactiveData/ReactiveData"; describe("ReactiveData module", () => { /** @type {ReactiveData} */ let reactiveData; let mockTable; beforeEach(() => { // Create mock rowManager const mockRowManager = { addRowActual: jest.fn(), getRowFromDataObject: jest.fn(), reRenderInPosition: jest.fn(), refreshActiveData: jest.fn() }; // Create mock dataTree module const mockDataTree = { initializeRow: jest.fn(), layoutRow: jest.fn() }; // Create mock eventBus const mockEventBus = { subscribe: jest.fn() }; // Create a simplified mock of the table mockTable = { rowManager: mockRowManager, modules: { dataTree: mockDataTree }, options: { reactiveData: false, dataTree: false, dataTreeChildField: "children" }, eventBus: mockEventBus }; // Mock methods in the ReactiveData prototype jest.spyOn(ReactiveData.prototype, 'registerTableOption').mockImplementation(function(key, value) { this.table.options[key] = this.table.options[key] || value; }); jest.spyOn(ReactiveData.prototype, 'subscribe').mockImplementation(function(key, callback) { return this.table.eventBus.subscribe(key, callback); }); // Create an instance of the ReactiveData module with the mock table reactiveData = new ReactiveData(mockTable); }); afterEach(() => { jest.clearAllMocks(); jest.restoreAllMocks(); }); it("should register reactiveData table option during construction", () => { // Verify table option is registered expect(mockTable.options.reactiveData).toBe(false); }); it("should not subscribe to events if reactiveData is disabled", () => { // Initialize module with reactiveData = false mockTable.options.reactiveData = false; reactiveData.initialize(); // Verify no events are subscribed expect(mockTable.eventBus.subscribe).not.toHaveBeenCalled(); }); it("should subscribe to events when reactiveData is enabled", () => { // Initialize module with reactiveData = true mockTable.options.reactiveData = true; reactiveData.initialize(); // Verify events are subscribed expect(mockTable.eventBus.subscribe).toHaveBeenCalledWith("cell-value-save-before", expect.any(Function)); expect(mockTable.eventBus.subscribe).toHaveBeenCalledWith("cell-value-save-after", expect.any(Function)); expect(mockTable.eventBus.subscribe).toHaveBeenCalledWith("row-data-save-before", expect.any(Function)); expect(mockTable.eventBus.subscribe).toHaveBeenCalledWith("row-data-save-after", expect.any(Function)); expect(mockTable.eventBus.subscribe).toHaveBeenCalledWith("row-data-init-after", expect.any(Function)); expect(mockTable.eventBus.subscribe).toHaveBeenCalledWith("data-processing", expect.any(Function)); expect(mockTable.eventBus.subscribe).toHaveBeenCalledWith("table-destroy", expect.any(Function)); }); it("should block and unblock reactivity", () => { // Initially not blocked expect(reactiveData.blocked).toBe(false); // Block reactivity reactiveData.block("test"); expect(reactiveData.blocked).toBe("test"); // Try to block with another key reactiveData.block("another"); expect(reactiveData.blocked).toBe("test"); // Still blocked by first key // Unblock with wrong key reactiveData.unblock("wrong"); expect(reactiveData.blocked).toBe("test"); // Still blocked // Unblock with correct key reactiveData.unblock("test"); expect(reactiveData.blocked).toBe(false); // Unblocked }); it("should watch a row and its data properties", () => { // Create a mock row const mockRow = { getData: jest.fn().mockReturnValue({ id: 1, name: "John", age: 30 }), updateData: jest.fn() }; // Spy on watchKey jest.spyOn(reactiveData, 'watchKey'); // Watch row reactiveData.watchRow(mockRow); // Verify watchKey was called for each property expect(reactiveData.watchKey).toHaveBeenCalledWith(mockRow, mockRow.getData(), "id"); expect(reactiveData.watchKey).toHaveBeenCalledWith(mockRow, mockRow.getData(), "name"); expect(reactiveData.watchKey).toHaveBeenCalledWith(mockRow, mockRow.getData(), "age"); }); it("should watch tree children if dataTree is enabled", () => { // Enable dataTree mockTable.options.dataTree = true; // Create a mock row with children const mockRow = { getData: jest.fn().mockReturnValue({ id: 1, name: "John", children: [ { id: 2, name: "Jane" } ] }), updateData: jest.fn() }; // Spy on watchTreeChildren jest.spyOn(reactiveData, 'watchTreeChildren'); // Watch row reactiveData.watchRow(mockRow); // Verify watchTreeChildren was called expect(reactiveData.watchTreeChildren).toHaveBeenCalledWith(mockRow); }); it("should watch a data property and trigger updates when it changes", () => { // Create a mock row const mockRow = { getData: jest.fn().mockReturnValue({ id: 1, name: "John" }), updateData: jest.fn() }; // Watch the name property reactiveData.watchKey(mockRow, mockRow.getData(), "name"); // Change the property value mockRow.getData().name = "Jane"; // Verify updateData was called with the new value expect(mockRow.updateData).toHaveBeenCalledWith({ name: "Jane" }); }); it("should not trigger updates when property changes if reactivity is blocked", () => { // Create a mock row const mockRow = { getData: jest.fn().mockReturnValue({ id: 1, name: "John" }), updateData: jest.fn() }; // Watch the name property reactiveData.watchKey(mockRow, mockRow.getData(), "name"); // Block reactivity reactiveData.block("test"); // Change the property value mockRow.getData().name = "Jane"; // Verify updateData was not called expect(mockRow.updateData).not.toHaveBeenCalled(); }); it("should watch and react to tree children array manipulations", () => { // Create a mock row with children array const children = [ { id: 2, name: "Jane" }, { id: 3, name: "Bob" } ]; const mockRow = { getData: jest.fn().mockReturnValue({ id: 1, name: "John", children: children }) }; // Spy on rebuildTree jest.spyOn(reactiveData, 'rebuildTree'); // Watch children array reactiveData.watchTreeChildren(mockRow); // Test push children.push({ id: 4, name: "Alice" }); expect(reactiveData.rebuildTree).toHaveBeenCalledWith(mockRow); // Reset spy reactiveData.rebuildTree.mockClear(); // Test unshift children.unshift({ id: 5, name: "Tom" }); expect(reactiveData.rebuildTree).toHaveBeenCalledWith(mockRow); // Reset spy reactiveData.rebuildTree.mockClear(); // Test pop children.pop(); expect(reactiveData.rebuildTree).toHaveBeenCalledWith(mockRow); // Reset spy reactiveData.rebuildTree.mockClear(); // Test shift children.shift(); expect(reactiveData.rebuildTree).toHaveBeenCalledWith(mockRow); // Reset spy reactiveData.rebuildTree.mockClear(); // Test splice children.splice(0, 1, { id: 6, name: "Sam" }); expect(reactiveData.rebuildTree).toHaveBeenCalledWith(mockRow); }); it("should not trigger tree rebuilds when array manipulations happen while blocked", () => { // Create a mock row with children array const children = [ { id: 2, name: "Jane" }, { id: 3, name: "Bob" } ]; const mockRow = { getData: jest.fn().mockReturnValue({ id: 1, name: "John", children: children }) }; // Spy on rebuildTree jest.spyOn(reactiveData, 'rebuildTree'); // Watch children array reactiveData.watchTreeChildren(mockRow); // Block reactivity reactiveData.block("test"); // Test various operations children.push({ id: 4, name: "Alice" }); children.unshift({ id: 5, name: "Tom" }); children.pop(); children.shift(); children.splice(0, 1, { id: 6, name: "Sam" }); // Verify rebuildTree was not called expect(reactiveData.rebuildTree).not.toHaveBeenCalled(); }); it("should invoke required methods when rebuilding a tree", () => { // Create a mock row const mockRow = { id: 1, name: "John" }; // Call rebuildTree reactiveData.rebuildTree(mockRow); // Verify dataTree methods were called expect(mockTable.modules.dataTree.initializeRow).toHaveBeenCalledWith(mockRow); expect(mockTable.modules.dataTree.layoutRow).toHaveBeenCalledWith(mockRow); expect(mockTable.rowManager.refreshActiveData).toHaveBeenCalledWith("tree", false, true); }); it("should watch data array and override array methods", () => { // Create a test data array const testData = [ { id: 1, name: "John" }, { id: 2, name: "Jane" } ]; // Store original array methods const origPush = testData.push; const origUnshift = testData.unshift; const origShift = testData.shift; const origPop = testData.pop; const origSplice = testData.splice; // Watch data reactiveData.watchData(testData); // Verify methods were overridden expect(testData.push).not.toBe(origPush); expect(testData.unshift).not.toBe(origUnshift); expect(testData.shift).not.toBe(origShift); expect(testData.pop).not.toBe(origPop); expect(testData.splice).not.toBe(origSplice); // Verify data was stored expect(reactiveData.data).toBe(testData); expect(reactiveData.origFuncs.push).toBe(origPush); expect(reactiveData.origFuncs.unshift).toBe(origUnshift); expect(reactiveData.origFuncs.shift).toBe(origShift); expect(reactiveData.origFuncs.pop).toBe(origPop); expect(reactiveData.origFuncs.splice).toBe(origSplice); }); it("should handle push operations on the data array", () => { // Create a test data array const testData = [ { id: 1, name: "John" }, { id: 2, name: "Jane" } ]; // Create a new row object const newRow = { id: 3, name: "Bob" }; // Watch data reactiveData.watchData(testData); // Call push testData.push(newRow); // Verify row was added expect(mockTable.rowManager.addRowActual).toHaveBeenCalledWith(newRow, false); }); it("should handle unshift operations on the data array", () => { // Create a test data array const testData = [ { id: 1, name: "John" }, { id: 2, name: "Jane" } ]; // Create a new row object const newRow = { id: 3, name: "Bob" }; // Watch data reactiveData.watchData(testData); // Call unshift testData.unshift(newRow); // Verify row was added expect(mockTable.rowManager.addRowActual).toHaveBeenCalledWith(newRow, true); }); it("should handle shift operations on the data array", () => { // Create a test data array const testData = [ { id: 1, name: "John" }, { id: 2, name: "Jane" } ]; // Create a mock row with a delete method const mockRow = { deleteActual: jest.fn() }; // Store reference to first item before shift const firstItem = testData[0]; // Configure rowManager to return the mock row mockTable.rowManager.getRowFromDataObject.mockReturnValue(mockRow); // Watch data reactiveData.watchData(testData); // Call shift testData.shift(); // Verify the row was retrieved and deleted expect(mockTable.rowManager.getRowFromDataObject).toHaveBeenCalledWith(firstItem); expect(mockRow.deleteActual).toHaveBeenCalled(); }); it("should handle pop operations on the data array", () => { // Create a test data array const testData = [ { id: 1, name: "John" }, { id: 2, name: "Jane" } ]; // Create a mock row with a delete method const mockRow = { deleteActual: jest.fn() }; // Store reference to last item before pop const lastItem = testData[testData.length - 1]; // Configure rowManager to return the mock row mockTable.rowManager.getRowFromDataObject.mockReturnValue(mockRow); // Watch data reactiveData.watchData(testData); // Call pop testData.pop(); // Verify the row was retrieved and deleted expect(mockTable.rowManager.getRowFromDataObject).toHaveBeenCalledWith(lastItem); expect(mockRow.deleteActual).toHaveBeenCalled(); }); it("should handle splice operations on the data array", () => { // Create a test data array const testData = [ { id: 1, name: "John" }, { id: 2, name: "Jane" }, { id: 3, name: "Bob" } ]; // Create a new row and a mock row const newRow = { id: 4, name: "Alice" }; const mockRow = { deleteActual: jest.fn() }; // Configure rowManager to return the mock row for the removed row mockTable.rowManager.getRowFromDataObject.mockReturnValue(mockRow); // Watch data reactiveData.watchData(testData); // Call splice to remove one row and add a new one testData.splice(1, 1, newRow); // Verify rows were added and removed appropriately expect(mockTable.rowManager.addRowActual).toHaveBeenCalled(); expect(mockTable.rowManager.getRowFromDataObject).toHaveBeenCalled(); expect(mockRow.deleteActual).toHaveBeenCalled(); expect(mockTable.rowManager.reRenderInPosition).toHaveBeenCalled(); }); it("should not trigger data operations when reactivity is blocked", () => { // Create a test data array const testData = [ { id: 1, name: "John" }, { id: 2, name: "Jane" } ]; // Watch data reactiveData.watchData(testData); // Block reactivity reactiveData.block("test"); // Call various operations testData.push({ id: 3, name: "Bob" }); testData.unshift({ id: 4, name: "Alice" }); testData.pop(); testData.shift(); testData.splice(0, 1, { id: 5, name: "Sam" }); // Verify no changes were processed expect(mockTable.rowManager.addRowActual).not.toHaveBeenCalled(); expect(mockTable.rowManager.getRowFromDataObject).not.toHaveBeenCalled(); expect(mockTable.rowManager.reRenderInPosition).not.toHaveBeenCalled(); }); it("should unwatchData and restore original array methods", () => { // Create a test data array const testData = [ { id: 1, name: "John" }, { id: 2, name: "Jane" } ]; // Store original methods const origPush = testData.push; // Watch data reactiveData.watchData(testData); // Verify methods were changed expect(testData.push).not.toBe(origPush); // Spy on Object.defineProperty jest.spyOn(Object, 'defineProperty'); // Unwatch data reactiveData.unwatchData(); // Verify Object.defineProperty was called to restore methods expect(Object.defineProperty).toHaveBeenCalledWith(testData, "push", expect.any(Object)); expect(Object.defineProperty).toHaveBeenCalledWith(testData, "unshift", expect.any(Object)); expect(Object.defineProperty).toHaveBeenCalledWith(testData, "shift", expect.any(Object)); expect(Object.defineProperty).toHaveBeenCalledWith(testData, "pop", expect.any(Object)); expect(Object.defineProperty).toHaveBeenCalledWith(testData, "splice", expect.any(Object)); }); it("should unwatchRow and restore original property definitions", () => { // Create a mock row const mockRow = { getData: jest.fn().mockReturnValue({ id: 1, name: "John" }) }; // Spy on Object.defineProperty jest.spyOn(Object, 'defineProperty'); // Unwatch row reactiveData.unwatchRow(mockRow); // Verify Object.defineProperty was called for each property expect(Object.defineProperty).toHaveBeenCalledWith(mockRow.getData(), "id", expect.any(Object)); expect(Object.defineProperty).toHaveBeenCalledWith(mockRow.getData(), "name", expect.any(Object)); }); it("should update currentVersion when watching new data", () => { // Initial version should be 0 expect(reactiveData.currentVersion).toBe(0); // Watch data reactiveData.watchData([]); // Version should be incremented expect(reactiveData.currentVersion).toBe(1); // Watch another data array reactiveData.watchData([]); // Version should be incremented again expect(reactiveData.currentVersion).toBe(2); }); });