UNPKG

tabulator-tables

Version:

Interactive table generation JavaScript library

730 lines (605 loc) 25.3 kB
import MoveColumns from "../../../src/js/modules/MoveColumns/MoveColumns"; import Helpers from "../../../src/js/core/tools/Helpers"; describe("MoveColumns module", () => { /** @type {MoveColumns} */ let moveColumns; let mockTable; let mockColumnManager; beforeEach(() => { // Mock DOM methods document.createElement = jest.fn().mockImplementation((tagName) => { const element = { tagName: tagName.toUpperCase(), classList: { add: jest.fn(), remove: jest.fn(), contains: jest.fn() }, style: {}, parentNode: { insertBefore: jest.fn(), removeChild: jest.fn() }, appendChild: jest.fn(), cloneNode: jest.fn().mockImplementation(() => ({ classList: { add: jest.fn(), remove: jest.fn() }, style: {} })), addEventListener: jest.fn(), removeEventListener: jest.fn(), nextSibling: null, }; return element; }); // Mock column elements const createMockColumnElement = () => ({ style: {}, parentNode: { insertBefore: jest.fn(), removeChild: jest.fn() }, appendChild: jest.fn(), classList: { add: jest.fn(), remove: jest.fn() }, addEventListener: jest.fn(), removeEventListener: jest.fn(), nextSibling: null }); // Create mock columns with modules.moveColumn initialized const mockColumn1 = { getElement: jest.fn().mockReturnValue(createMockColumnElement()), getCells: jest.fn().mockReturnValue([]), getWidth: jest.fn().mockReturnValue(100), getHeight: jest.fn().mockReturnValue(30), parent: "row", // For parent comparison in column movement isGroup: false, isRowHeader: false, nextColumn: jest.fn(), prevColumn: jest.fn(), modules: { moveColumn: {} // Initialize the moveColumn property } }; const mockColumn2 = { getElement: jest.fn().mockReturnValue(createMockColumnElement()), getCells: jest.fn().mockReturnValue([]), getWidth: jest.fn().mockReturnValue(150), getHeight: jest.fn().mockReturnValue(30), parent: "row", // For parent comparison in column movement isGroup: false, isRowHeader: false, nextColumn: jest.fn(), prevColumn: jest.fn(), modules: { moveColumn: {} // Initialize the moveColumn property } }; // Mock frozen column const mockFrozenColumn = { getElement: jest.fn().mockReturnValue(createMockColumnElement()), getCells: jest.fn().mockReturnValue([]), parent: "row", isGroup: false, isRowHeader: false, modules: { frozen: {}, moveColumn: {} // Initialize the moveColumn property } }; // Create mock contents element const mockContentsElement = { scrollLeft: 0, clientWidth: 1000, appendChild: jest.fn(), clientHeight: 50 }; // Create mock headers element const mockHeadersElement = { offsetHeight: 40 }; // Create mock columnManager mockColumnManager = { columnsByIndex: [mockColumn1, mockColumn2, mockFrozenColumn], moveColumnActual: jest.fn(), getContentsElement: jest.fn().mockReturnValue(mockContentsElement), getHeadersElement: jest.fn().mockReturnValue(mockHeadersElement) }; // Create mock row manager const mockRowManager = { getElement: jest.fn().mockReturnValue({ scrollLeft: 0 }), element: { scrollLeft: 0 } }; // Create mock element const mockElement = { classList: { add: jest.fn(), remove: jest.fn() } }; // Create mock eventBus const mockEventBus = { subscribe: jest.fn(), dispatch: jest.fn() }; // Create a simplified mock of the table mockTable = { modExists: jest.fn((name) => name === "selectRange" ? true : false), modules: { selectRange: { columnSelection: false, mousedown: false, selecting: null } }, columnManager: mockColumnManager, rowManager: mockRowManager, element: mockElement, options: { movableColumns: true }, eventBus: mockEventBus }; // Mock Helpers.elOffset jest.spyOn(Helpers, 'elOffset').mockImplementation(() => ({ left: 50, top: 20 })); // Mock methods in the MoveColumns prototype jest.spyOn(MoveColumns.prototype, 'registerTableOption').mockImplementation(function(key, value) { this.table.options[key] = this.table.options[key] || value; }); jest.spyOn(MoveColumns.prototype, 'subscribe').mockImplementation(function(key, callback) { return this.table.eventBus.subscribe(key, callback); }); jest.spyOn(MoveColumns.prototype, 'dispatch').mockImplementation(function(event, ...args) { return this.table.eventBus.dispatch(event, ...args); }); // Create an instance of the MoveColumns module with the mock table moveColumns = new MoveColumns(mockTable); // Mock setTimeout jest.useFakeTimers(); }); afterEach(() => { jest.clearAllMocks(); jest.restoreAllMocks(); jest.useRealTimers(); }); it("should register movableColumns table option during construction", () => { // Verify table option is registered expect(mockTable.options.movableColumns).toBe(true); // Verify placeholder element is created expect(document.createElement).toHaveBeenCalledWith("div"); expect(moveColumns.placeholderElement.classList.add).toHaveBeenCalledWith("tabulator-col"); expect(moveColumns.placeholderElement.classList.add).toHaveBeenCalledWith("tabulator-col-placeholder"); }); it("should initialize and subscribe to events if movableColumns is enabled", () => { // Set movableColumns option mockTable.options.movableColumns = true; // Run initialize moveColumns.initialize(); // Verify event subscriptions expect(mockTable.eventBus.subscribe).toHaveBeenCalledWith("column-init", expect.any(Function)); expect(mockTable.eventBus.subscribe).toHaveBeenCalledWith("alert-show", expect.any(Function)); }); it("should not subscribe to events if movableColumns is disabled", () => { // Set movableColumns option to false mockTable.options.movableColumns = false; // Run initialize moveColumns.initialize(); // Verify no subscriptions were made expect(mockTable.eventBus.subscribe).not.toHaveBeenCalled(); }); it("should abort any move operation when alert is shown", () => { // Mock clearTimeout jest.spyOn(global, 'clearTimeout'); // Set timeout moveColumns.checkTimeout = setTimeout(() => {}, 1000); // Call abortMove method moveColumns.abortMove(); // Verify timeout was cleared expect(clearTimeout).toHaveBeenCalledWith(moveColumns.checkTimeout); }); it("should initialize a regular column for movement", () => { // Create mock column const mockColumn = { parent: null, modules: {}, isGroup: false, isRowHeader: false, getElement: jest.fn().mockReturnValue({ addEventListener: jest.fn(), parentNode: { insertBefore: jest.fn() } }), nextColumn: jest.fn(), prevColumn: jest.fn() }; // Spy on bindTouchEvents jest.spyOn(moveColumns, 'bindTouchEvents'); // Initialize column moveColumns.initializeColumn(mockColumn); // Verify event listeners were added expect(mockColumn.getElement().addEventListener).toHaveBeenCalledWith("mousedown", expect.any(Function)); expect(mockColumn.getElement().addEventListener).toHaveBeenCalledWith("mouseup", expect.any(Function)); // Verify touch events were bound expect(moveColumns.bindTouchEvents).toHaveBeenCalledWith(mockColumn); // Verify the column has mousemove configuration expect(mockColumn.modules.moveColumn).toBeDefined(); expect(mockColumn.modules.moveColumn.mousemove).toBeDefined(); }); it("should not initialize a frozen column for movement", () => { // Create mock frozen column const mockColumn = { parent: null, modules: { frozen: {} }, isGroup: false, isRowHeader: false, getElement: jest.fn() }; // Spy on bindTouchEvents jest.spyOn(moveColumns, 'bindTouchEvents'); // Initialize column moveColumns.initializeColumn(mockColumn); // Verify no event listeners were added expect(mockColumn.getElement).not.toHaveBeenCalled(); // Verify touch events were not bound expect(moveColumns.bindTouchEvents).not.toHaveBeenCalled(); // Verify the column has a moveColumn configuration expect(mockColumn.modules.moveColumn).toBeDefined(); }); it("should not initialize a group column for movement", () => { // Create mock group column const mockColumn = { parent: null, modules: {}, isGroup: true, isRowHeader: false, getElement: jest.fn() }; // Initialize column moveColumns.initializeColumn(mockColumn); // Verify no event listeners were added expect(mockColumn.getElement).not.toHaveBeenCalled(); }); it("should not initialize a row header column for movement", () => { // Create mock row header column const mockColumn = { parent: null, modules: {}, isGroup: false, isRowHeader: true, getElement: jest.fn() }; // Initialize column moveColumns.initializeColumn(mockColumn); // Verify no event listeners were added expect(mockColumn.getElement).not.toHaveBeenCalled(); }); it("should start a move operation on mouse down after timeout", () => { // Create mock column const mockColumn = { parent: null, modules: {}, isGroup: false, isRowHeader: false, getElement: jest.fn().mockReturnValue({ addEventListener: jest.fn(), parentNode: { insertBefore: jest.fn(), removeChild: jest.fn() }, cloneNode: jest.fn().mockReturnValue({ classList: { add: jest.fn() }, style: {} }) }), getWidth: jest.fn().mockReturnValue(100), getHeight: jest.fn().mockReturnValue(30), getCells: jest.fn().mockReturnValue([]) }; // Mock event const mockEvent = { which: 1, pageX: 100 }; // Initialize the column first to add event listeners moveColumns.initializeColumn(mockColumn); // Get the mousedown handler const mousedownHandler = mockColumn.getElement().addEventListener.mock.calls.find(call => call[0] === "mousedown")[1]; // Spy on startMove jest.spyOn(moveColumns, 'startMove').mockImplementation(() => {}); // Call mousedown handler mousedownHandler(mockEvent); // Fast forward time to trigger the timeout jest.advanceTimersByTime(moveColumns.checkPeriod + 10); // Verify startMove was called expect(moveColumns.startMove).toHaveBeenCalledWith(mockEvent, mockColumn); }); it("should bind touch events to a column", () => { // Create mock column element const mockColEl = { addEventListener: jest.fn() }; // Create mock column const mockColumn = { getElement: jest.fn().mockReturnValue(mockColEl), nextColumn: jest.fn(), prevColumn: jest.fn() }; // Bind touch events moveColumns.bindTouchEvents(mockColumn); // Verify touch event listeners were added expect(mockColEl.addEventListener).toHaveBeenCalledWith("touchstart", expect.any(Function), {passive: true}); expect(mockColEl.addEventListener).toHaveBeenCalledWith("touchmove", expect.any(Function), {passive: true}); expect(mockColEl.addEventListener).toHaveBeenCalledWith("touchend", expect.any(Function)); }); it("should not start move if range selection is active", () => { // Set up selectRange module to be active mockTable.modules.selectRange.mousedown = true; mockTable.modules.selectRange.selecting = "column"; mockTable.modules.selectRange.columnSelection = true; // Create mock column const mockColumn = { getElement: jest.fn() }; // Create mock event const mockEvent = { pageX: 100 }; // Spy on methods that should not be called jest.spyOn(moveColumns, '_bindMouseMove'); // Start move operation moveColumns.startMove(mockEvent, mockColumn); // Verify that early return happened and no move operation started expect(moveColumns._bindMouseMove).not.toHaveBeenCalled(); expect(moveColumns.moving).toBeFalsy(); }); it("should bind mouse move handlers to all columns", () => { // Update mock columnsByIndex to have proper modules.moveColumn structure mockTable.columnManager.columnsByIndex.forEach(column => { column.modules.moveColumn = { mousemove: jest.fn() }; }); // Bind mouse move handlers moveColumns._bindMouseMove(); // Verify event listeners were added to all columns mockTable.columnManager.columnsByIndex.forEach(column => { expect(column.getElement().addEventListener).toHaveBeenCalledWith( "mousemove", column.modules.moveColumn.mousemove ); }); }); it("should unbind mouse move handlers from all columns", () => { // Update mock columnsByIndex to have proper modules.moveColumn structure mockTable.columnManager.columnsByIndex.forEach(column => { column.modules.moveColumn = { mousemove: jest.fn() }; }); // Unbind mouse move handlers moveColumns._unbindMouseMove(); // Verify event listeners were removed from all columns mockTable.columnManager.columnsByIndex.forEach(column => { expect(column.getElement().removeEventListener).toHaveBeenCalledWith( "mousemove", column.modules.moveColumn.mousemove ); }); }); it("should move column cells after target column", () => { // Create mock cells const mockCellEl1 = { parentNode: { insertBefore: jest.fn() }, nextSibling: "nextSibling1" }; const mockCellEl2 = { parentNode: { insertBefore: jest.fn() }, nextSibling: "nextSibling2" }; // Create mock source and target columns const mockSourceCell1 = { getElement: jest.fn().mockReturnValue("sourceCell1") }; const mockSourceCell2 = { getElement: jest.fn().mockReturnValue("sourceCell2") }; const mockSourceColumn = { getCells: jest.fn().mockReturnValue([mockSourceCell1, mockSourceCell2]) }; const mockTargetCell1 = { getElement: jest.fn(true).mockReturnValue(mockCellEl1) }; const mockTargetCell2 = { getElement: jest.fn(true).mockReturnValue(mockCellEl2) }; const mockTargetColumn = { getCells: jest.fn().mockReturnValue([mockTargetCell1, mockTargetCell2]) }; // Set up the moving column moveColumns.moving = mockSourceColumn; // Move column after target moveColumns.moveColumn(mockTargetColumn, true); // Verify cells are moved correctly expect(moveColumns.toCol).toBe(mockTargetColumn); expect(moveColumns.toColAfter).toBe(true); expect(mockCellEl1.parentNode.insertBefore).toHaveBeenCalledWith("sourceCell1", "nextSibling1"); expect(mockCellEl2.parentNode.insertBefore).toHaveBeenCalledWith("sourceCell2", "nextSibling2"); }); it("should move column cells before target column", () => { // Create mock cells const mockCellEl1 = { parentNode: { insertBefore: jest.fn() } }; const mockCellEl2 = { parentNode: { insertBefore: jest.fn() } }; // Create mock source and target columns const mockSourceCell1 = { getElement: jest.fn().mockReturnValue("sourceCell1") }; const mockSourceCell2 = { getElement: jest.fn().mockReturnValue("sourceCell2") }; const mockSourceColumn = { getCells: jest.fn().mockReturnValue([mockSourceCell1, mockSourceCell2]) }; const mockTargetCell1 = { getElement: jest.fn(true).mockReturnValue(mockCellEl1) }; const mockTargetCell2 = { getElement: jest.fn(true).mockReturnValue(mockCellEl2) }; const mockTargetColumn = { getCells: jest.fn().mockReturnValue([mockTargetCell1, mockTargetCell2]) }; // Set up the moving column moveColumns.moving = mockSourceColumn; // Move column before target moveColumns.moveColumn(mockTargetColumn, false); // Verify cells are moved correctly expect(moveColumns.toCol).toBe(mockTargetColumn); expect(moveColumns.toColAfter).toBe(false); expect(mockCellEl1.parentNode.insertBefore).toHaveBeenCalledWith("sourceCell1", mockCellEl1); expect(mockCellEl2.parentNode.insertBefore).toHaveBeenCalledWith("sourceCell2", mockCellEl2); }); it("should finalize column move on mouse up", () => { // Create mock column elements const mockElement = { parentNode: { insertBefore: jest.fn(), removeChild: jest.fn() }, nextSibling: "nextSibling" }; // Set up the moving column state moveColumns.moving = { getElement: jest.fn().mockReturnValue("columnElement") }; moveColumns.placeholderElement = { parentNode: { insertBefore: jest.fn(), removeChild: jest.fn() }, nextSibling: "nextSibling" }; moveColumns.hoverElement = { parentNode: { removeChild: jest.fn() } }; moveColumns.toCol = "targetColumn"; moveColumns.toColAfter = true; // Create mock event const mockEvent = { which: 1 }; // Mock _unbindMouseMove to avoid the issues with column.modules.moveColumn jest.spyOn(moveColumns, '_unbindMouseMove').mockImplementation(() => {}); // Mock document body event listener removal jest.spyOn(document.body, 'removeEventListener').mockImplementation(jest.fn()); // Call endMove moveColumns.endMove(mockEvent); // Verify DOM cleanup expect(moveColumns._unbindMouseMove).toHaveBeenCalled(); expect(moveColumns.placeholderElement.parentNode.insertBefore).toHaveBeenCalledWith( "columnElement", "nextSibling" ); expect(moveColumns.placeholderElement.parentNode.removeChild).toHaveBeenCalledWith( moveColumns.placeholderElement ); expect(moveColumns.hoverElement.parentNode.removeChild).toHaveBeenCalledWith( moveColumns.hoverElement ); // Verify table class cleanup expect(mockTable.element.classList.remove).toHaveBeenCalledWith("tabulator-block-select"); // Verify column manager was called to move the column expect(mockTable.columnManager.moveColumnActual).toHaveBeenCalledWith( expect.anything(), // The moving column (just checking it was passed) "targetColumn", true ); // Verify state reset expect(moveColumns.moving).toBe(false); expect(moveColumns.toCol).toBe(false); expect(moveColumns.toColAfter).toBe(false); }); it("should update hover element position on moveHover", () => { // Set up the hoverElement moveColumns.hoverElement = { style: {} }; moveColumns.startX = 20; // Create mock event and column holder const mockEvent = { pageX: 100 }; // Call moveHover moveColumns.moveHover(mockEvent); // Verify hover element position is updated expect(moveColumns.hoverElement.style.left).toBe("30px"); }); it("should trigger auto scroll when near left edge", () => { // Set up the hoverElement moveColumns.hoverElement = { style: {} }; moveColumns.startX = 20; // Create mock event near left edge const mockEvent = { pageX: 70 // Will calculate to xPos=20, which is less than autoScrollMargin (40) }; // Mock rowManager getElement to return an object we can directly update const scrollObj = { scrollLeft: 0 }; mockTable.rowManager.getElement.mockReturnValue(scrollObj); // Call moveHover moveColumns.moveHover(mockEvent); // Fast forward to trigger auto scroll jest.advanceTimersByTime(5); // Verify auto scroll was triggered (scrollLeft should remain at 0 since we're at the edge) expect(scrollObj.scrollLeft).toBe(0); }); it("should attempt to set auto scroll when near right edge", () => { // Mock setTimeout to track if it's called const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); // Set up the hoverElement moveColumns.hoverElement = { style: {} }; moveColumns.startX = 20; // Create mock event near right edge const mockEvent = { pageX: 1000 // Will calculate to xPos=950, which is within autoScrollMargin of right edge }; // Mock columnHolder with larger scrollLeft value const mockContentsElement = { scrollLeft: 10, clientWidth: 50 // Small width to ensure we're near the right edge }; mockTable.columnManager.getContentsElement.mockReturnValue(mockContentsElement); // Call moveHover moveColumns.moveHover(mockEvent); // Verify setTimeout was called (indicating the auto scroll code was executed) expect(setTimeoutSpy).toHaveBeenCalled(); }); });