diff --git a/packages/table-core/__tests__/RowSelection.test.ts b/packages/table-core/__tests__/RowSelection.test.ts new file mode 100644 index 0000000000..a1ee96d02f --- /dev/null +++ b/packages/table-core/__tests__/RowSelection.test.ts @@ -0,0 +1,196 @@ +import { + ColumnDef, + createColumnHelper, + createTable, + getCoreRowModel, +} from '../src' +import * as RowSelection from '../src/features/RowSelection' +import { makeData, Person } from './makeTestData' + +type personKeys = keyof Person +type PersonColumn = ColumnDef + +function generateColumns(people: Person[]): PersonColumn[] { + const columnHelper = createColumnHelper() + const person = people[0] + return Object.keys(person).map(key => { + const typedKey = key as personKeys + return columnHelper.accessor(typedKey, { id: typedKey }) + }) +} + +describe('RowSelection', () => { + describe('isSubRowSelected', () => { + it('should return false if there are no sub-rows', () => { + const data = makeData(3) + const columns = generateColumns(data) + + const table = createTable({ + enableRowSelection: true, + onStateChange() {}, + renderFallbackValue: '', + data, + state: {}, + columns, + getCoreRowModel: getCoreRowModel(), + }) + + const firstRow = table.getCoreRowModel().rows[0] + + const result = RowSelection.isSubRowSelected( + firstRow, + table.getState().rowSelection, + table + ) + + expect(result).toEqual(false) + }) + + it('should return false if no sub-rows are selected', () => { + const data = makeData(3, 2) + const columns = generateColumns(data) + + const table = createTable({ + enableRowSelection: true, + onStateChange() {}, + renderFallbackValue: '', + data, + getSubRows: row => row.subRows, + state: { + rowSelection: {}, + }, + columns, + getCoreRowModel: getCoreRowModel(), + }) + + const firstRow = table.getCoreRowModel().rows[0] + + const result = RowSelection.isSubRowSelected( + firstRow, + table.getState().rowSelection, + table + ) + + expect(result).toEqual(false) + }) + + it('should return some if some sub-rows are selected', () => { + const data = makeData(3, 2) + const columns = generateColumns(data) + + const table = createTable({ + enableRowSelection: true, + onStateChange() {}, + renderFallbackValue: '', + data, + getSubRows: row => row.subRows, + state: { + rowSelection: { + '0.0': true, + }, + }, + columns, + getCoreRowModel: getCoreRowModel(), + }) + + const firstRow = table.getCoreRowModel().rows[0] + + const result = RowSelection.isSubRowSelected( + firstRow, + table.getState().rowSelection, + table + ) + + expect(result).toEqual('some') + }) + + it('should return all if all sub-rows are selected', () => { + const data = makeData(3, 2) + const columns = generateColumns(data) + + const table = createTable({ + enableRowSelection: true, + onStateChange() {}, + renderFallbackValue: '', + data, + getSubRows: row => row.subRows, + state: { + rowSelection: { + '0.0': true, + '0.1': true, + }, + }, + columns, + getCoreRowModel: getCoreRowModel(), + }) + + const firstRow = table.getCoreRowModel().rows[0] + + const result = RowSelection.isSubRowSelected( + firstRow, + table.getState().rowSelection, + table + ) + + expect(result).toEqual('all') + }) + it('should return all if all selectable sub-rows are selected', () => { + const data = makeData(3, 2) + const columns = generateColumns(data) + + const table = createTable({ + enableRowSelection: row => row.index === 0, // only first row is selectable (of 2 sub-rows) + onStateChange() {}, + renderFallbackValue: '', + data, + getSubRows: row => row.subRows, + state: { + rowSelection: { + '0.0': true, // first sub-row + }, + }, + columns, + getCoreRowModel: getCoreRowModel(), + }) + + const firstRow = table.getCoreRowModel().rows[0] + + const result = RowSelection.isSubRowSelected( + firstRow, + table.getState().rowSelection, + table + ) + + expect(result).toEqual('all') + }) + it('should return some when some nested sub-rows are selected', () => { + const data = makeData(3, 2, 2) + const columns = generateColumns(data) + + const table = createTable({ + enableRowSelection: true, + onStateChange() {}, + renderFallbackValue: '', + data, + getSubRows: row => row.subRows, + state: { + rowSelection: { + '0.0.0': true, // first nested sub-row + }, + }, + columns, + getCoreRowModel: getCoreRowModel(), + }) + + const firstRow = table.getCoreRowModel().rows[0] + + const result = RowSelection.isSubRowSelected( + firstRow, + table.getState().rowSelection, + table + ) + + expect(result).toEqual('some') + }) + }) +}) diff --git a/packages/table-core/src/features/RowSelection.ts b/packages/table-core/src/features/RowSelection.ts index d3ea0fd094..4fcf693ddc 100644 --- a/packages/table-core/src/features/RowSelection.ts +++ b/packages/table-core/src/features/RowSelection.ts @@ -348,7 +348,7 @@ export const RowSelection: TableFeature = { table.setRowSelection(old => { value = typeof value !== 'undefined' ? value : !isSelected - if (isSelected === value) { + if (row.getCanSelect() && isSelected === value) { return old } @@ -496,25 +496,38 @@ export function isSubRowSelected( selection: Record, table: Table ): boolean | 'some' | 'all' { - if (row.subRows && row.subRows.length) { - let allChildrenSelected = true - let someSelected = false - - row.subRows.forEach(subRow => { - // Bail out early if we know both of these - if (someSelected && !allChildrenSelected) { - return - } + if (!row.subRows?.length) return false + + let allChildrenSelected = true + let someSelected = false + + row.subRows.forEach(subRow => { + // Bail out early if we know both of these + if (someSelected && !allChildrenSelected) { + return + } + if (subRow.getCanSelect()) { if (isRowSelected(subRow, selection)) { someSelected = true } else { allChildrenSelected = false } - }) + } - return allChildrenSelected ? 'all' : someSelected ? 'some' : false - } + // Check row selection of nested subrows + if (subRow.subRows && subRow.subRows.length) { + const subRowChildrenSelected = isSubRowSelected(subRow, selection, table) + if (subRowChildrenSelected === 'all') { + someSelected = true + } else if (subRowChildrenSelected === 'some') { + someSelected = true + allChildrenSelected = false + } else { + allChildrenSelected = false + } + } + }) - return false + return allChildrenSelected ? 'all' : someSelected ? 'some' : false }