Skip to content

Commit

Permalink
fix: getIsSomeRowsSelected should ignore unselectable rows (fix #5060) (
Browse files Browse the repository at this point in the history
#5061)

* Row selection should ignore unselectable rows (fix #5060)

* `getIsSomeRowsSelected()` and `isSubRowSelected()` now ignore rows
  that cannot be selected.
* `toggleSelected()` now doesn't skip propagating toggle on rows that
  cannot be selected.

* Check row selection of nested subrows

* add unit tests for isSubRowSelected

---------

Co-authored-by: Kevin Van Cott <[email protected]>
  • Loading branch information
itsmejoeeey and KevinVandy authored Sep 15, 2023
1 parent 2f4b211 commit 6c8fb5e
Show file tree
Hide file tree
Showing 2 changed files with 223 additions and 14 deletions.
196 changes: 196 additions & 0 deletions packages/table-core/__tests__/RowSelection.test.ts
Original file line number Diff line number Diff line change
@@ -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<Person, string | number | Person[] | undefined>

function generateColumns(people: Person[]): PersonColumn[] {
const columnHelper = createColumnHelper<Person>()
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<Person>({
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<Person>({
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<Person>({
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<Person>({
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<Person>({
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<Person>({
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')
})
})
})
41 changes: 27 additions & 14 deletions packages/table-core/src/features/RowSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -496,25 +496,38 @@ export function isSubRowSelected<TData extends RowData>(
selection: Record<string, boolean>,
table: Table<TData>
): 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
}

0 comments on commit 6c8fb5e

Please sign in to comment.