Skip to content

Commit

Permalink
Add controlled selected logic
Browse files Browse the repository at this point in the history
  • Loading branch information
cee-chen committed Oct 26, 2023
1 parent e8d333e commit 7e9e3fa
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 34 deletions.
86 changes: 68 additions & 18 deletions src/components/basic_table/basic_table.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import React from 'react';
import { render } from '../../test/rtl';
import { render, screen } from '../../test/rtl';
import { requiredProps } from '../../test';
import { shouldRenderCustomStyles } from '../../test/internal';

Expand Down Expand Up @@ -391,24 +391,74 @@ describe('EuiBasicTable', () => {
expect(container.querySelector('[aria-sort]')).toBeTruthy();
});

test('with initial selection', () => {
const props: EuiBasicTableProps<BasicItem> = {
items: basicItems,
columns: basicColumns,
itemId: 'id',
selection: {
onSelectionChange: () => {},
initialSelected: [basicItems[0]],
},
};
const { getByTestSubject } = render(<EuiBasicTable {...props} />);
describe('selection', () => {
const getCheckboxAt = (index: number) =>
screen.getByTestSubject(`checkboxSelectRow-${index}`) as HTMLInputElement;

expect(
(getByTestSubject('checkboxSelectRow-1') as HTMLInputElement).checked
).toBeTruthy();
expect(
(getByTestSubject('checkboxSelectRow-2') as HTMLInputElement).checked
).toBeFalsy();
test('initialSelected', () => {
const props: EuiBasicTableProps<BasicItem> = {
items: basicItems,
columns: basicColumns,
itemId: 'id',
selection: {
onSelectionChange: () => {},
initialSelected: [basicItems[0]],
},
};
render(<EuiBasicTable {...props} />);

expect(getCheckboxAt(1).checked).toBeTruthy();
expect(getCheckboxAt(2).checked).toBeFalsy();
});

test('selected', () => {
const props: EuiBasicTableProps<BasicItem> = {
items: basicItems,
columns: basicColumns,
itemId: 'id',
selection: {
onSelectionChange: () => {},
selected: [basicItems[1]],
},
};
render(<EuiBasicTable {...props} />);

expect(getCheckboxAt(1).checked).toBeFalsy();
expect(getCheckboxAt(2).checked).toBeTruthy();
});

it('ignores initialSelected if selected is passed', () => {
const props: EuiBasicTableProps<BasicItem> = {
items: basicItems,
columns: basicColumns,
itemId: 'id',
selection: {
onSelectionChange: () => {},
selected: [],
},
};
render(<EuiBasicTable {...props} />);

expect(getCheckboxAt(1).checked).toBeFalsy();
expect(getCheckboxAt(2).checked).toBeFalsy();
});

it("checks for selections that don't exist within `items`", () => {
const onSelectionChange = jest.fn();
const props: EuiBasicTableProps<BasicItem> = {
items: basicItems,
columns: basicColumns,
itemId: 'id',
selection: {
onSelectionChange: onSelectionChange,
selected: [{ id: 'notvalid', name: 'notInItems' }],
},
};
const { container } = render(<EuiBasicTable {...props} />);

expect(onSelectionChange).toHaveBeenCalledWith([]);
expect(container.querySelectorAll('[checked]')).toHaveLength(0);
});
});

test('footers', () => {
Expand Down
49 changes: 33 additions & 16 deletions src/components/basic_table/basic_table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -340,20 +340,28 @@ export class EuiBasicTable<T = any> extends Component<
return { selection: [] };
}

const { itemId } = nextProps;
const selection = prevState.selection.filter(
const controlledSelection = nextProps.selection.selected;
const unfilteredSelection = controlledSelection ?? prevState.selection;

// Ensure we're not including selections that aren't in the
// current `items` array (affected by pagination)
const { itemId, items } = nextProps;
const selection = unfilteredSelection.filter(
(selectedItem: T) =>
nextProps.items.findIndex(
items.findIndex(
(item: T) =>
getItemId(item, itemId) === getItemId(selectedItem, itemId)
) !== -1
);

if (selection.length !== prevState.selection.length) {
if (nextProps.selection.onSelectionChange) {
nextProps.selection.onSelectionChange(selection);
}
// If some selected items were filtered out, update state and callback
if (selection.length !== unfilteredSelection.length) {
nextProps.selection.onSelectionChange?.(selection);
return { selection };
}

// Always update selection state from props if controlled
if (controlledSelection) {
return { selection };
}

Expand Down Expand Up @@ -385,15 +393,23 @@ export class EuiBasicTable<T = any> extends Component<
);
}

get isSelectionControlled() {
return !!this.props.selection?.selected;
}

getInitialSelection() {
if (this.isSelectionControlled) return;

if (
this.props.selection &&
this.props.selection.initialSelected &&
!this.state.initialSelectionRendered &&
this.props.items.length > 0
) {
this.setState({ selection: this.props.selection.initialSelected });
this.setState({ initialSelectionRendered: true });
this.setState({
selection: this.props.selection.initialSelected,
initialSelectionRendered: true,
});
}
}

Expand All @@ -418,13 +434,14 @@ export class EuiBasicTable<T = any> extends Component<
return criteria;
}

changeSelection(selection: T[]) {
if (!this.props.selection) {
return;
}
this.setState({ selection });
if (this.props.selection.onSelectionChange) {
this.props.selection.onSelectionChange(selection);
changeSelection(changedSelection: T[]) {
const { selection } = this.props;
if (!selection) return;

selection.onSelectionChange?.(changedSelection);

if (!this.isSelectionControlled) {
this.setState({ selection: changedSelection });
}
}

Expand Down

0 comments on commit 7e9e3fa

Please sign in to comment.