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

feat: Select all for synchronous select #22084

Merged
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0dfab9d
added select all button and a few tests
cccs-RyanK Oct 19, 2022
32e966d
Merge branch 'master' of github.com:CybercentreCanada/superset into s…
cccs-RyanK Nov 8, 2022
66af8c9
removed commented tests
cccs-RyanK Nov 8, 2022
6e7ac16
prettier fixes
cccs-RyanK Nov 8, 2022
4c599fd
lint fixes
cccs-RyanK Nov 10, 2022
06b77b0
added tests and feature for custom tag renderer
cccs-RyanK Nov 18, 2022
1efc58b
fixed onchange
cccs-RyanK Nov 23, 2022
42b45fd
Merge branch 'master' of github.com:CybercentreCanada/superset into s…
cccs-RyanK Nov 28, 2022
14129d0
fixed value format
cccs-RyanK Nov 29, 2022
3b596c6
Merge branch 'master' of github.com:CybercentreCanada/superset into s…
cccs-RyanK Nov 29, 2022
16e3c66
fixing wrong type issues
cccs-RyanK Nov 30, 2022
c69b7af
select test fixes
cccs-RyanK Nov 30, 2022
3d120d1
SqlEditor test changed with new select all behavior
cccs-RyanK Nov 30, 2022
6ca115e
fixed some lint errors
cccs-RyanK Dec 1, 2022
5702dd4
Merge branch 'master' of github.com:CybercentreCanada/superset into s…
cccs-RyanK Dec 14, 2022
c6c5f55
feedback suggestions
cccs-RyanK Dec 20, 2022
e36ceed
Merge branch 'master' of github.com:CybercentreCanada/superset into s…
cccs-RyanK Dec 20, 2022
426f57c
added tag renderer to async select
cccs-RyanK Dec 20, 2022
88f125e
fixed hook dependencies and ts error
Jan 12, 2023
807f51c
Merge branch 'master' of github.com:CybercentreCanada/superset into s…
Jan 12, 2023
618161f
removed custom max tag placeholder from select filter plugin
Jan 13, 2023
6b9b428
prettier fixes
Jan 16, 2023
0d66466
fixed bug in maxTagPlaceholder and value filter plugin tests
Jan 17, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ test('table should be visible when expanded is true', async () => {
expect(dbSelect).toBeInTheDocument();
expect(schemaSelect).toBeInTheDocument();
expect(dropdown).toBeInTheDocument();
expect(abUser).toHaveLength(2);
expect(abUser).toHaveLength(1);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test adds one item into a 'multi' select component and then selects it. With the new behaviour, that counts as selecting all which renders a custom tag with the total number of items selected. This test would fail because it expected the single tag to be rendered in the select component when that is no longer the expected behaviour, so I changed the test.

Another option is to make it so that the select all functionality is disabled in the case where there is only one item in the options. Please let me know your thoughts

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I like the idea of disabling the "select all" when only 1 option is available.

expect(
container.querySelector('.ant-collapse-content-active'),
).toBeInTheDocument();
Expand All @@ -105,7 +105,7 @@ test('should toggle the table when the header is clicked', async () => {
const store = mockStore(initialState);
await renderAndWait(mockedProps, store);

const header = (await screen.findAllByText(/ab_user/))[1];
const header = (await screen.findAllByText(/ab_user/))[0];
expect(header).toBeInTheDocument();
userEvent.click(header);

Expand Down
147 changes: 137 additions & 10 deletions superset-frontend/src/components/Select/Select.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
import React from 'react';
import { render, screen, waitFor, within } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
import { Select } from 'src/components';
import Select from 'src/components/Select/Select';
import { SELECT_ALL_VALUE } from './utils';

const ARIA_LABEL = 'Test';
const NEW_OPTION = 'Kyle';
Expand Down Expand Up @@ -64,6 +65,11 @@ const defaultProps = {
showSearch: true,
};

const selectAllTagLabel = (numOptions: number) => `${numOptions} selected`;

const selectAllOptionLabel = (numOptions: number) =>
`${String(SELECT_ALL_VALUE)} (${numOptions})`;

const getElementByClassName = (className: string) =>
document.querySelector(className)! as HTMLElement;

Expand All @@ -89,7 +95,10 @@ const findSelectValue = () =>
waitFor(() => getElementByClassName('.ant-select-selection-item'));

const findAllSelectValues = () =>
waitFor(() => getElementsByClassName('.ant-select-selection-item'));
waitFor(() => [
...getElementsByClassName('.ant-tag'),
...getElementsByClassName('.ant-select-selection-item'),
]);

const clearAll = () => userEvent.click(screen.getByLabelText('close-circle'));

Expand Down Expand Up @@ -209,26 +218,37 @@ test('should sort selected to the top when in multi mode', async () => {
let labels = originalLabels.slice();

await open();
userEvent.click(await findSelectOption(labels[1]));
expect(await matchOrder(labels)).toBe(true);
userEvent.click(await findSelectOption(labels[2]));
expect(
await matchOrder([selectAllOptionLabel(originalLabels.length), ...labels]),
).toBe(true);

await type('{esc}');
await open();
labels = labels.splice(1, 1).concat(labels);
expect(await matchOrder(labels)).toBe(true);
labels = labels.splice(2, 1).concat(labels);
expect(
await matchOrder([selectAllOptionLabel(originalLabels.length), ...labels]),
).toBe(true);

await open();
userEvent.click(await findSelectOption(labels[5]));
await type('{esc}');
await open();
labels = [labels.splice(0, 1)[0], labels.splice(4, 1)[0]].concat(labels);
expect(await matchOrder(labels)).toBe(true);
expect(
await matchOrder([selectAllOptionLabel(originalLabels.length), ...labels]),
).toBe(true);

// should revert to original order
clearAll();
await type('{esc}');
await open();
expect(await matchOrder(originalLabels)).toBe(true);
expect(
await matchOrder([
selectAllOptionLabel(originalLabels.length),
...originalLabels,
]),
).toBe(true);
});

test('searches for label or value', async () => {
Expand Down Expand Up @@ -440,15 +460,15 @@ test('changes the selected item in single mode', async () => {
label: firstOption.label,
value: firstOption.value,
}),
firstOption,
expect.objectContaining(firstOption),
);
userEvent.click(await findSelectOption(secondOption.label));
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({
label: secondOption.label,
value: secondOption.value,
}),
secondOption,
expect.objectContaining(secondOption),
);
expect(await findSelectValue()).toHaveTextContent(secondOption.label);
});
Expand Down Expand Up @@ -566,6 +586,113 @@ test('finds an element with a numeric value and does not duplicate the options',
expect(await querySelectOption('11')).not.toBeInTheDocument();
});

test('render "Select all" for multi select', async () => {
render(<Select {...defaultProps} mode="multiple" options={OPTIONS} />);
await open();
const options = await findAllSelectOptions();
expect(options[0]).toHaveTextContent(selectAllOptionLabel(OPTIONS.length));
});

test('does not render "Select all" for single select', async () => {
render(<Select {...defaultProps} options={OPTIONS} mode="single" />);
await open();
expect(
screen.queryByText(selectAllOptionLabel(OPTIONS.length)),
).not.toBeInTheDocument();
});

test('does not render "Select all" for an empty multiple select', async () => {
render(<Select {...defaultProps} options={[]} mode="multiple" />);
await open();
expect(
screen.queryByText(selectAllOptionLabel(OPTIONS.length)),
).not.toBeInTheDocument();
});

test('does not render "Select all" when searching', async () => {
render(<Select {...defaultProps} options={OPTIONS} mode="multiple" />);
await open();
await type('Select');
expect(
screen.queryByText(selectAllOptionLabel(OPTIONS.length)),
).not.toBeInTheDocument();
});

test('does not render "Select all" as one of the tags after selection', async () => {
render(<Select {...defaultProps} options={OPTIONS} mode="multiple" />);
await open();
userEvent.click(await findSelectOption(selectAllOptionLabel(OPTIONS.length)));
const values = await findAllSelectValues();
expect(values[0]).not.toHaveTextContent(selectAllOptionLabel(OPTIONS.length));
});

test('keeps "Select all" at the top after a selection', async () => {
const selected = OPTIONS[2];
render(
<Select
{...defaultProps}
options={OPTIONS.slice(0, 10)}
mode="multiple"
value={[selected]}
/>,
);
await open();
const options = await findAllSelectOptions();
expect(options[0]).toHaveTextContent(selectAllOptionLabel(10));
expect(options[1]).toHaveTextContent(selected.label);
});

test('selects all values', async () => {
render(<Select {...defaultProps} options={OPTIONS} mode="multiple" />);
await open();
userEvent.click(await findSelectOption(selectAllOptionLabel(OPTIONS.length)));
const values = await findAllSelectValues();
expect(values.length).toBe(2);
expect(values[0]).toHaveTextContent(selectAllTagLabel(OPTIONS.length));
});

test('unselects all values', async () => {
render(<Select {...defaultProps} options={OPTIONS} mode="multiple" />);
await open();
userEvent.click(await findSelectOption(selectAllOptionLabel(OPTIONS.length)));
let values = await findAllSelectValues();
expect(values.length).toBe(2);
expect(values[0]).toHaveTextContent(selectAllTagLabel(OPTIONS.length));
// const options = await findAllSelectOptions();
cccs-RyanK marked this conversation as resolved.
Show resolved Hide resolved
userEvent.click(await findSelectOption(selectAllOptionLabel(OPTIONS.length)));
values = await findAllSelectValues();
expect(values.length).toBe(0);
});

test('deselecting a value also deselects "Select all"', async () => {
render(
<Select {...defaultProps} options={OPTIONS.slice(0, 10)} mode="multiple" />,
);
await open();
userEvent.click(await findSelectOption(selectAllOptionLabel(10)));
let values = await findAllSelectValues();
expect(values[0]).toHaveTextContent(selectAllTagLabel(10));
userEvent.click(await findSelectOption(OPTIONS[0].label));
values = await findAllSelectValues();
expect(values[0]).toHaveTextContent(OPTIONS[1].label);
});

test('selecting all values also selects "Select all"', async () => {
render(
<Select {...defaultProps} options={OPTIONS.slice(0, 10)} mode="multiple" />,
);
await open();
const options = await findAllSelectOptions();
options.forEach((option, index) => {
// skip select all
if (index > 0) {
userEvent.click(option);
}
});
const values = await findAllSelectValues();
expect(values[0]).toHaveTextContent(selectAllTagLabel(10));
});

/*
TODO: Add tests that require scroll interaction. Needs further investigation.
- Fetches more data when scrolling and more data is available
Expand Down
Loading