Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 5 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}