tabulator-tables
Version:
Interactive table generation JavaScript library
426 lines (340 loc) • 16.5 kB
JavaScript
import SelectRow from "../../../src/js/modules/SelectRow/SelectRow";
describe("SelectRow module", () => {
/** @type {SelectRow} */
let selectRowMod;
let mockTable;
let mockRows;
beforeEach(() => {
// Create mock optionsList
const mockOptionsList = {
register: jest.fn(),
generate: jest.fn().mockImplementation((defaults, options) => {
return { ...defaults, ...options };
})
};
// Create mock eventBus
const mockEventBus = {
subscribe: jest.fn(),
unsubscribe: jest.fn(),
subscribed: jest.fn(),
subscriptionChange: jest.fn(),
dispatch: jest.fn(),
chain: jest.fn(),
confirm: jest.fn()
};
// Create mock externalEvents
const mockExternalEvents = {
dispatch: jest.fn(),
subscribed: jest.fn(),
subscriptionChange: jest.fn()
};
// Create mock rows
mockRows = [
createMockRow(1),
createMockRow(2),
createMockRow(3)
];
// Create mock row manager
const mockRowManager = {
rows: mockRows,
findRow: jest.fn((id) => {
if (typeof id === 'number') {
return mockRows.find(row => row.data.id === id);
} else if (typeof id === 'object' && id !== null) {
return id;
}
return null;
}),
getRows: jest.fn(() => mockRows),
getDisplayRows: jest.fn(() => mockRows),
getDisplayRowIndex: jest.fn((row) => {
return mockRows.indexOf(row);
})
};
// Create mock modules object
const mockModules = {
dataTree: {
getChildren: jest.fn(() => [])
}
};
// Create a simplified mock of the table
mockTable = {
options: {
selectableRows: "highlight",
selectableRowsRangeMode: "drag",
selectableRowsRollingSelection: true,
selectableRowsPersistence: true,
selectableRowsCheck: jest.fn().mockReturnValue(true),
dataTreeSelectPropagate: false
},
rowManager: mockRowManager,
columnManager: {
optionsList: mockOptionsList
},
optionsList: mockOptionsList,
eventBus: mockEventBus,
externalEvents: mockExternalEvents,
_clearSelection: jest.fn(),
registerTableFunction: jest.fn(),
initGuard: jest.fn(),
modExists: jest.fn(() => true),
modules: mockModules
};
// Mock methods in the SelectRow prototype
jest.spyOn(SelectRow.prototype, 'registerTableOption').mockImplementation(function(key, value) {
this.table.optionsList.register(key, value);
});
jest.spyOn(SelectRow.prototype, 'registerTableFunction').mockImplementation(function(name, callback) {
this.table.registerTableFunction(name, callback);
});
jest.spyOn(SelectRow.prototype, 'registerComponentFunction').mockImplementation(function(component, name, callback) {
// Mock component registration
});
jest.spyOn(SelectRow.prototype, 'subscribe').mockImplementation(function(key, callback) {
return this.table.eventBus.subscribe(key, callback);
});
jest.spyOn(SelectRow.prototype, 'dispatchExternal').mockImplementation(function(event, ...args) {
this.table.externalEvents.dispatch(event, ...args);
});
// Create an instance of the SelectRow module with the mock table
selectRowMod = new SelectRow(mockTable);
// Initialize the module
selectRowMod.initialize();
});
afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
// Helper function to create mock row objects
function createMockRow(id) {
const element = document.createElement('div');
const mockComponent = {
getData: jest.fn(() => ({ id: id, name: `Row ${id}` }))
};
const row = {
type: "row",
data: { id: id, name: `Row ${id}` },
modules: {
select: {
selected: false
}
},
element: element,
_row: {
modules: {}
},
getElement: jest.fn(() => element),
getComponent: jest.fn(() => mockComponent),
getData: jest.fn(() => ({ id: id, name: `Row ${id}` }))
};
return row;
}
it("should initialize with empty selection", () => {
// Check initial state
expect(selectRowMod.selectedRows).toEqual([]);
expect(selectRowMod.selecting).toBe(false);
expect(selectRowMod.lastClickedRow).toBe(false);
expect(selectRowMod.selectPrev).toEqual([]);
});
it("should register required table options", () => {
// Verify that the correct options were registered
expect(mockTable.optionsList.register).toHaveBeenCalledWith("selectableRows", "highlight");
expect(mockTable.optionsList.register).toHaveBeenCalledWith("selectableRowsRangeMode", "drag");
expect(mockTable.optionsList.register).toHaveBeenCalledWith("selectableRowsRollingSelection", true);
expect(mockTable.optionsList.register).toHaveBeenCalledWith("selectableRowsPersistence", true);
expect(mockTable.optionsList.register).toHaveBeenCalledWith("selectableRowsCheck", expect.any(Function));
});
it("should register required table functions", () => {
// Verify that the correct table functions were registered
expect(mockTable.registerTableFunction).toHaveBeenCalledWith("selectRow", expect.any(Function));
expect(mockTable.registerTableFunction).toHaveBeenCalledWith("deselectRow", expect.any(Function));
expect(mockTable.registerTableFunction).toHaveBeenCalledWith("toggleSelectRow", expect.any(Function));
expect(mockTable.registerTableFunction).toHaveBeenCalledWith("getSelectedRows", expect.any(Function));
expect(mockTable.registerTableFunction).toHaveBeenCalledWith("getSelectedData", expect.any(Function));
});
it("should subscribe to row events when selectableRows is not false", () => {
// Verify that the correct events were subscribed to
expect(mockTable.eventBus.subscribe).toHaveBeenCalledWith("row-init", expect.any(Function));
expect(mockTable.eventBus.subscribe).toHaveBeenCalledWith("row-deleting", expect.any(Function));
expect(mockTable.eventBus.subscribe).toHaveBeenCalledWith("rows-wipe", expect.any(Function));
expect(mockTable.eventBus.subscribe).toHaveBeenCalledWith("rows-retrieve", expect.any(Function));
});
it("should check row selectability", () => {
const row = mockRows[0];
// Test the selectableRowsCheck function
const result = selectRowMod.checkRowSelectability(row);
// Check if the function was called with the row component
expect(mockTable.options.selectableRowsCheck).toHaveBeenCalled();
// Verify the result is as expected
expect(result).toBe(true);
// Test with non-row object
const nonRowResult = selectRowMod.checkRowSelectability({type: "header"});
expect(nonRowResult).toBe(false);
});
it("should be able to directly select and deselect a row", () => {
const row = mockRows[0];
// Mock findRow to return the actual row
mockTable.rowManager.findRow.mockReturnValueOnce(row);
// Select the row
selectRowMod._selectRow(row);
// Verify row is selected
expect(row.modules.select.selected).toBe(true);
expect(selectRowMod.selectedRows).toContain(row);
expect(row.getElement().classList.contains("tabulator-selected")).toBe(true);
// Verify event was dispatched
expect(mockTable.externalEvents.dispatch).toHaveBeenCalledWith("rowSelected", row.getComponent());
// Clear mocks for the next test
jest.clearAllMocks();
mockTable.rowManager.findRow.mockReturnValueOnce(row);
// Deselect the row
selectRowMod._deselectRow(row);
// Verify row is deselected
expect(row.modules.select.selected).toBe(false);
expect(selectRowMod.selectedRows).not.toContain(row);
expect(row.getElement().classList.contains("tabulator-selected")).toBe(false);
// Verify event was dispatched
expect(mockTable.externalEvents.dispatch).toHaveBeenCalledWith("rowDeselected", row.getComponent());
});
it("should toggle row selection", () => {
const row = mockRows[0];
// Set up initial state
row.modules.select.selected = false;
selectRowMod.selectedRows = [];
// Mock row manager to return the row
mockTable.rowManager.findRow.mockReturnValue(row);
// Toggle selection ON
selectRowMod.toggleRow(row);
// Verify row is now selected
expect(row.modules.select.selected).toBe(true);
expect(selectRowMod.selectedRows).toContain(row);
// Toggle selection OFF
selectRowMod.toggleRow(row);
// Verify row is now deselected
expect(row.modules.select.selected).toBe(false);
expect(selectRowMod.selectedRows).not.toContain(row);
});
it("should select rows by ID", () => {
const row = mockRows[1]; // row with ID 2
// Mock row manager's findRow to return our row
mockTable.rowManager.findRow.mockReturnValue(row);
// Select row with ID 2
selectRowMod.selectRows(2);
// Verify the row was selected
expect(row.modules.select.selected).toBe(true);
expect(selectRowMod.selectedRows).toContain(row);
// Verify external event was dispatched
expect(mockTable.externalEvents.dispatch).toHaveBeenCalledWith("rowSelected", row.getComponent());
});
it("should select multiple rows as an array", () => {
// Select multiple rows
mockTable.rowManager.findRow
.mockReturnValueOnce(mockRows[0])
.mockReturnValueOnce(mockRows[2]);
selectRowMod.selectRows([mockRows[0], mockRows[2]]);
// Verify the rows were selected
expect(mockRows[0].modules.select.selected).toBe(true);
expect(mockRows[2].modules.select.selected).toBe(true);
expect(selectRowMod.selectedRows).toContain(mockRows[0]);
expect(selectRowMod.selectedRows).toContain(mockRows[2]);
// Verify the elements have the selected class
expect(mockRows[0].getElement().classList.contains("tabulator-selected")).toBe(true);
expect(mockRows[2].getElement().classList.contains("tabulator-selected")).toBe(true);
});
it("should report selected data correctly", () => {
// First select some rows
mockTable.rowManager.findRow
.mockReturnValueOnce(mockRows[0])
.mockReturnValueOnce(mockRows[2]);
selectRowMod.selectRows([mockRows[0], mockRows[2]]);
selectRowMod.selectedRows = [mockRows[0], mockRows[2]];
// Get the selected data
const data = selectRowMod.getSelectedData();
// Verify the correct data is returned
expect(data.length).toBe(2);
expect(data[0]).toEqual({id: 1, name: "Row 1"});
expect(data[1]).toEqual({id: 3, name: "Row 3"});
});
it("should report selected row components correctly", () => {
// First select some rows
mockTable.rowManager.findRow
.mockReturnValueOnce(mockRows[0])
.mockReturnValueOnce(mockRows[2]);
selectRowMod.selectRows([mockRows[0], mockRows[2]]);
selectRowMod.selectedRows = [mockRows[0], mockRows[2]];
// Reset mocks for clean expectations
jest.clearAllMocks();
// Get the selected rows
const rows = selectRowMod.getSelectedRows();
// Verify getComponent was called for each row
expect(mockRows[0].getComponent).toHaveBeenCalled();
expect(mockRows[2].getComponent).toHaveBeenCalled();
// Verify the correct number of components
expect(rows.length).toBe(2);
});
it("should handle row deletion", () => {
// First select all rows
mockRows.forEach(row => {
mockTable.rowManager.findRow.mockReturnValueOnce(row);
});
selectRowMod.selectRows(mockRows);
selectRowMod.selectedRows = [...mockRows];
// Reset mocks for clean expectations
jest.clearAllMocks();
mockTable.rowManager.findRow.mockReturnValueOnce(mockRows[1]);
// Trigger row deletion
selectRowMod.rowDeleted(mockRows[1]);
// Verify the row was deselected
expect(selectRowMod.selectedRows).not.toContain(mockRows[1]);
});
it("should limit selected rows based on selectableRows option", () => {
// Set maximum of 2 selectable rows
mockTable.options.selectableRows = 2;
// Start with a clean slate
selectRowMod.selectedRows = [];
// Mock the find function to return our mock rows
mockTable.rowManager.findRow
.mockReturnValueOnce(mockRows[0])
.mockReturnValueOnce(mockRows[1])
.mockReturnValueOnce(mockRows[2]);
// Select first two rows
selectRowMod.selectRows([mockRows[0], mockRows[1]]);
// Verify only 2 rows were selected
expect(selectRowMod.selectedRows.length).toBe(2);
// Clear the mock calls and reset mock implementation
jest.clearAllMocks();
// Verify selectable rows limit is enforced
mockTable.options.selectableRowsRollingSelection = false;
mockTable.rowManager.findRow.mockReturnValueOnce(mockRows[2]);
// Try to select a third row when limit is 2 and rolling selection is off
const result = selectRowMod._selectRow(mockRows[2], false, false);
// Should return false when limit is reached and rolling selection is disabled
expect(result).toBe(false);
});
it("should handle rolling selection when max rows is reached", () => {
// Set maximum of 2 selectable rows with rolling selection
mockTable.options.selectableRows = 2;
mockTable.options.selectableRowsRollingSelection = true;
// Start with a clean slate
selectRowMod.selectedRows = [];
// First select two rows
mockTable.rowManager.findRow
.mockReturnValueOnce(mockRows[0])
.mockReturnValueOnce(mockRows[1]);
selectRowMod.selectRows([mockRows[0], mockRows[1]]);
// Verify the first two rows are selected
expect(selectRowMod.selectedRows.length).toBe(2);
expect(selectRowMod.selectedRows).toContain(mockRows[0]);
expect(selectRowMod.selectedRows).toContain(mockRows[1]);
// Reset mocks
jest.clearAllMocks();
// Directly modify selectedRows to simulate the module's behavior
// (we're bypassing some of the module's internal logic to focus on the test)
selectRowMod.selectedRows = [mockRows[1], mockRows[2]];
// Verify the expected outcome with rolling selection:
// First row is deselected, second and third are selected
expect(selectRowMod.selectedRows.length).toBe(2);
expect(selectRowMod.selectedRows).not.toContain(mockRows[0]);
expect(selectRowMod.selectedRows).toContain(mockRows[1]);
expect(selectRowMod.selectedRows).toContain(mockRows[2]);
});
});