Skip to content

Commit

Permalink
fix(data-table): select all behavior (#4430)
Browse files Browse the repository at this point in the history
* fix(data-table): select all behavior

* fix(data-table): minor changes

* test(data-table): test cases

* test(data-table): update snapshot
  • Loading branch information
hshockley authored and abbeyhrt committed Oct 28, 2019
1 parent d8187e6 commit 0521c13
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 28 deletions.
42 changes: 32 additions & 10 deletions packages/react/src/components/DataTable/DataTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,6 @@ export default class DataTable extends React.Component {
const checked = rowCount > 0 && selectedRowCount === rowCount;
const indeterminate =
rowCount > 0 && selectedRowCount > 0 && selectedRowCount !== rowCount;

const translationKey = checked
? translationKeys.unselectAll
: translationKeys.selectAll;
Expand Down Expand Up @@ -378,6 +377,26 @@ export default class DataTable extends React.Component {
return row.isSelected;
});

/**
* Helper utility to get all of the available rows after applying the filter
* @returns {Array<string>} the array of rowIds that are currently included through the filter
* */
getFilteredRowIds = () => {
const filteredRowIds =
typeof this.state.filterInputValue === 'string'
? this.props.filterRows({
rowIds: this.state.rowIds,
headers: this.props.headers,
cellsById: this.state.cellsById,
inputValue: this.state.filterInputValue,
})
: this.state.rowIds;
if (filteredRowIds.length == 0) {
return this.state.rowIds;
}
return filteredRowIds;
};

/**
* Helper for getting the table prefix for elements that require an
* `id` attribute that is unique.
Expand All @@ -392,15 +411,18 @@ export default class DataTable extends React.Component {
* @param {object} initialState
* @returns {object} object to put into this.setState (use spread operator)
*/
setAllSelectedState = (initialState, isSelected) => {
setAllSelectedState = (initialState, isSelected, filteredRowIds) => {
const { rowIds } = initialState;
return {
rowsById: rowIds.reduce(
(acc, id) => ({
...acc,
[id]: {
...initialState.rowsById[id],
isSelected: initialState.rowsById[id].disabled ? false : isSelected,
isSelected:
!initialState.rowsById[id].disabled &&
filteredRowIds.includes(id) &&
isSelected,
},
}),
{}
Expand All @@ -416,7 +438,7 @@ export default class DataTable extends React.Component {
this.setState(state => {
return {
shouldShowBatchActions: false,
...this.setAllSelectedState(state, false),
...this.setAllSelectedState(state, false, this.getFilteredRowIds()),
};
});
};
Expand All @@ -426,14 +448,14 @@ export default class DataTable extends React.Component {
*/
handleSelectAll = () => {
this.setState(state => {
const { rowIds, rowsById } = state;
const selectableRows = rowIds.reduce((acc, rowId) => {
return (acc += rowsById[rowId].disabled ? 0 : 1);
}, 0);
const isSelected = this.getSelectedRows().length !== selectableRows;
const filteredRowIds = this.getFilteredRowIds();
const { rowsById } = state;
const isSelected = !(
Object.values(rowsById).filter(row => row.isSelected == true).length > 0
);
return {
shouldShowBatchActions: isSelected,
...this.setAllSelectedState(state, isSelected),
...this.setAllSelectedState(state, isSelected, filteredRowIds),
};
});
};
Expand Down
175 changes: 175 additions & 0 deletions packages/react/src/components/DataTable/__tests__/DataTable-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,181 @@ describe('DataTable', () => {
});
});

describe('selection with filtering', () => {
let mockProps;

beforeEach(() => {
mockProps = {
rows: [
{
id: 'b',
fieldA: 'Field 2:A',
fieldB: 'Field 2:B',
},
{
id: 'a',
fieldA: 'Field 1:A',
fieldB: 'Field 1:B',
},
{
id: 'c',
fieldA: 'Field 3:A',
fieldB: 'Field 3:B',
},
],
headers: [
{
key: 'fieldA',
header: 'Field A',
},
{
key: 'fieldB',
header: 'Field B',
},
],
locale: 'en',
render: jest.fn(
({
rows,
headers,
getHeaderProps,
getSelectionProps,
onInputChange,
}) => (
<TableContainer title="DataTable with selection">
<TableToolbar>
<TableToolbarContent>
<TableToolbarSearch
persistent
onChange={onInputChange}
id="custom-id"
/>
<TableToolbarMenu>
<TableToolbarAction primaryFocus onClick={jest.fn()}>
Action 1
</TableToolbarAction>
<TableToolbarAction onClick={jest.fn()}>
Action 2
</TableToolbarAction>
<TableToolbarAction onClick={jest.fn()}>
Action 3
</TableToolbarAction>
</TableToolbarMenu>
<Button onClick={jest.fn()} small kind="primary">
Add new
</Button>
</TableToolbarContent>
</TableToolbar>
<Table>
<TableHead>
<TableRow>
<TableSelectAll {...getSelectionProps()} />
{headers.map(header => (
<TableHeader {...getHeaderProps({ header })}>
{header.header}
</TableHeader>
))}
</TableRow>
</TableHead>
<TableBody>
{rows.map(row => (
<TableRow key={row.id}>
<TableSelectRow {...getSelectionProps({ row })} />
{row.cells.map(cell => (
<TableCell key={cell.id}>{cell.value}</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)
),
};
});

it('should only select all from filtered items', () => {
const wrapper = mount(<DataTable {...mockProps} />);

expect(getSelectAll(wrapper).prop('checked')).toBe(false);

const filterInput = getFilterInput(wrapper);

filterInput.getDOMNode().value = 'Field 1';
filterInput.simulate('change');

getInputAtIndex({ wrapper, index: 0, inputType: 'checkbox' }).simulate(
'click'
);

filterInput.getDOMNode().value = '';
filterInput.simulate('change');

expect(wrapper.find('TableSelectAll').prop('indeterminate')).toBe(true);

let { selectedRows } = getLastCallFor(mockProps.render)[0];
expect(selectedRows.length).toBe(1);

getSelectAll(wrapper).simulate('click');

selectedRows = getLastCallFor(mockProps.render)[0].selectedRows;
expect(selectedRows.length).toBe(0);
});

it('should only select rows that are not disabled even when filtered', () => {
const wrapper = mount(<DataTable {...mockProps} />);

const nextRows = [
...mockProps.rows.map(row => ({ ...row })),
{
id: 'd',
fieldA: 'Field 3:A',
fieldB: 'Field 3:B',
disabled: true,
},
];

wrapper.setProps({ rows: nextRows });

const filterInput = getFilterInput(wrapper);

filterInput.getDOMNode().value = 'Field 3';
filterInput.simulate('change');

getSelectAll(wrapper).simulate('click');

const { selectedRows } = getLastCallFor(mockProps.render)[0];
expect(selectedRows.length).toBe(1);

expect(wrapper.find('TableSelectAll').prop('indeterminate')).toBe(true);
});

it('does not select a row if they are all disabled', () => {
const wrapper = mount(<DataTable {...mockProps} />);

const nextRows = [
...mockProps.rows.map(row => ({ ...row, disabled: true })),
];

wrapper.setProps({ rows: nextRows });

getSelectAll(wrapper).simulate('click');

expect(wrapper.find('TableSelectAll').prop('indeterminate')).toBe(false);
expect(wrapper.find('TableSelectAll').prop('checked')).toBe(false);

const filterInput = getFilterInput(wrapper);

filterInput.getDOMNode().value = 'Field 3';
filterInput.simulate('change');

getSelectAll(wrapper).simulate('click');

const { selectedRows } = getLastCallFor(mockProps.render)[0];
expect(selectedRows.length).toBe(0);
});
});

describe('selection -- radio buttons', () => {
let mockProps;

Expand Down
Loading

0 comments on commit 0521c13

Please sign in to comment.