diff --git a/superset-frontend/src/components/Select/Select.stories.tsx b/superset-frontend/src/components/Select/Select.stories.tsx
index 33f1ce6d50..b258232672 100644
--- a/superset-frontend/src/components/Select/Select.stories.tsx
+++ b/superset-frontend/src/components/Select/Select.stories.tsx
@@ -79,6 +79,12 @@ const ARG_TYPES = {
disable: true,
},
},
+ labelInValue: {
+ defaultValue: true,
+ table: {
+ disable: true,
+ },
+ },
name: {
table: {
disable: true,
@@ -171,7 +177,11 @@ export const AtEveryCorner = () => (
position: 'absolute',
}}
>
-
+
))}
@@ -206,7 +216,7 @@ export const PageScroll = () => (
right: 30,
}}
>
-
+
{
- const ariaLabel = 'test';
- render(
- ,
+const ARIA_LABEL = 'Test';
+const NEW_OPTION = 'Kyle';
+const NO_DATA = 'No Data';
+const LOADING = 'Loading...';
+const OPTIONS = [
+ { label: 'John', value: 1 },
+ { label: 'Liam', value: 2 },
+ { label: 'Olivia', value: 3 },
+ { label: 'Emma', value: 4 },
+ { label: 'Noah', value: 5 },
+ { label: 'Ava', value: 6 },
+ { label: 'Oliver', value: 7 },
+ { label: 'ElijahH', value: 8 },
+ { label: 'Charlotte', value: 9 },
+ { label: 'Giovanni', value: 10 },
+ { label: 'Franco', value: 11 },
+ { label: 'Sandro', value: 12 },
+ { label: 'Alehandro', value: 13 },
+ { label: 'Johnny', value: 14 },
+ { label: 'Nikole', value: 15 },
+ { label: 'Igor', value: 16 },
+ { label: 'Guilherme', value: 17 },
+ { label: 'Irfan', value: 18 },
+ { label: 'George', value: 19 },
+ { label: 'Ashfaq', value: 20 },
+];
+
+const loadOptions = async (search: string, page: number, pageSize: number) => {
+ const totalCount = OPTIONS.length;
+ const start = page * pageSize;
+ const deleteCount =
+ start + pageSize < totalCount ? pageSize : totalCount - start;
+ const data = OPTIONS.filter(option => option.label.match(search)).splice(
+ start,
+ deleteCount,
+ );
+ return {
+ data,
+ totalCount: OPTIONS.length,
+ };
+};
+
+const defaultProps = {
+ allowClear: true,
+ ariaLabel: ARIA_LABEL,
+ labelInValue: true,
+ options: OPTIONS,
+ pageSize: 10,
+ showSearch: true,
+};
+
+const getElementByClassName = (className: string) =>
+ document.querySelector(className)! as HTMLElement;
+
+const getElementsByClassName = (className: string) =>
+ document.querySelectorAll(className)! as NodeListOf;
+
+const getSelect = () => screen.getByRole('combobox', { name: ARIA_LABEL });
+
+const findSelectOption = (text: string) =>
+ waitFor(() =>
+ within(getElementByClassName('.rc-virtual-list')).getByText(text),
);
- expect(screen.getByRole('combobox', { name: ariaLabel })).toBeInTheDocument();
+const findAllSelectOptions = () =>
+ waitFor(() => getElementsByClassName('.ant-select-item-option-content'));
+
+const findSelectValue = () =>
+ waitFor(() => getElementByClassName('.ant-select-selection-item'));
+
+const findAllSelectValues = () =>
+ waitFor(() => getElementsByClassName('.ant-select-selection-item'));
+
+const type = (text: string) => userEvent.type(getSelect(), text, { delay: 10 });
+
+const open = () => waitFor(() => userEvent.click(getSelect()));
+
+test('displays a header', async () => {
+ const headerText = 'Header';
+ render();
+ expect(screen.getByText(headerText)).toBeInTheDocument();
+});
+
+test('inverts the selection', async () => {
+ render();
+ await open();
+ userEvent.click(await findSelectOption(OPTIONS[0].label));
+ expect(await screen.findByLabelText('stop')).toBeInTheDocument();
+});
+
+test('displays the selected values first', async () => {
+ render();
+ const option3 = OPTIONS[2].label;
+ const option8 = OPTIONS[7].label;
+ await open();
+ userEvent.click(await findSelectOption(option3));
+ userEvent.click(await findSelectOption(option8));
+ await type('{esc}');
+ await open();
+ const sortedOptions = await findAllSelectOptions();
+ expect(sortedOptions[0]).toHaveTextContent(option3);
+ expect(sortedOptions[1]).toHaveTextContent(option8);
+});
+
+test('searches for label or value', async () => {
+ const option = OPTIONS[11];
+ render();
+ const search = option.value;
+ await type(search.toString());
+ const options = await findAllSelectOptions();
+ expect(options.length).toBe(1);
+ expect(options[0]).toHaveTextContent(option.label);
+});
+
+test('clear all the values', async () => {
+ const onClear = jest.fn();
+ render(
+ ,
+ );
+ userEvent.click(screen.getByLabelText('close-circle'));
+ expect(onClear).toHaveBeenCalled();
+ const values = await findAllSelectValues();
+ expect(values.length).toBe(0);
+});
+
+test('does not add a new option if allowNewValue is false', async () => {
+ render();
+ await open();
+ await type(NEW_OPTION);
+ expect(await screen.findByText(NO_DATA)).toBeInTheDocument();
+});
+
+test('static - renders the select with default props', () => {
+ render();
+ expect(getSelect()).toBeInTheDocument();
+});
+
+test('static - opens the select without any data', async () => {
+ render();
+ await open();
+ expect(screen.getByText(NO_DATA)).toBeInTheDocument();
+});
+
+test('static - makes a selection in single mode', async () => {
+ render();
+ const optionText = 'Emma';
+ await open();
+ userEvent.click(await findSelectOption(optionText));
+ expect(await findSelectValue()).toHaveTextContent(optionText);
+});
+
+test('static - multiple selections in multiple mode', async () => {
+ render();
+ await open();
+ const [firstOption, secondOption] = OPTIONS;
+ userEvent.click(await findSelectOption(firstOption.label));
+ userEvent.click(await findSelectOption(secondOption.label));
+ const values = await findAllSelectValues();
+ expect(values[0]).toHaveTextContent(firstOption.label);
+ expect(values[1]).toHaveTextContent(secondOption.label);
+});
+
+test('static - changes the selected item in single mode', async () => {
+ const onChange = jest.fn();
+ render();
+ await open();
+ const [firstOption, secondOption] = OPTIONS;
+ userEvent.click(await findSelectOption(firstOption.label));
+ expect(await findSelectValue()).toHaveTextContent(firstOption.label);
+ expect(onChange).toHaveBeenCalledWith(
+ expect.objectContaining(firstOption),
+ firstOption,
+ );
+ userEvent.click(await findSelectOption(secondOption.label));
+ expect(onChange).toHaveBeenCalledWith(
+ expect.objectContaining(secondOption),
+ secondOption,
+ );
+ expect(await findSelectValue()).toHaveTextContent(secondOption.label);
+});
+
+test('static - deselects an item in multiple mode', async () => {
+ render();
+ await open();
+ const [firstOption, secondOption] = OPTIONS;
+ userEvent.click(await findSelectOption(firstOption.label));
+ userEvent.click(await findSelectOption(secondOption.label));
+ let values = await findAllSelectValues();
+ expect(values.length).toBe(2);
+ expect(values[0]).toHaveTextContent(firstOption.label);
+ expect(values[1]).toHaveTextContent(secondOption.label);
+ userEvent.click(await findSelectOption(firstOption.label));
+ values = await findAllSelectValues();
+ expect(values.length).toBe(1);
+ expect(values[0]).toHaveTextContent(secondOption.label);
+});
+
+test('static - adds a new option if none is available and allowNewOptions is true', async () => {
+ render();
+ await open();
+ await type(NEW_OPTION);
+ expect(await findSelectOption(NEW_OPTION)).toBeInTheDocument();
+});
+
+test('static - does not add a new option if the option already exists', async () => {
+ render();
+ const option = OPTIONS[0].label;
+ await open();
+ await type(option);
+ expect(await findSelectOption(option)).toBeInTheDocument();
+});
+
+test('static - sets a initial value in single mode', async () => {
+ render();
+ expect(await findSelectValue()).toHaveTextContent(OPTIONS[0].label);
+});
+
+test('static - sets a initial value in multiple mode', async () => {
+ render(
+ ,
+ );
+ const values = await findAllSelectValues();
+ expect(values[0]).toHaveTextContent(OPTIONS[0].label);
+ expect(values[1]).toHaveTextContent(OPTIONS[1].label);
+});
+
+test('static - searches for an item', async () => {
+ render();
+ const search = 'Oli';
+ await type(search);
+ const options = await findAllSelectOptions();
+ expect(options.length).toBe(2);
+ expect(options[0]).toHaveTextContent('Olivia');
+ expect(options[1]).toHaveTextContent('Oliver');
+});
+
+test('async - renders the select with default props', () => {
+ render();
+ expect(getSelect()).toBeInTheDocument();
+});
+
+test('async - opens the select without any data', async () => {
+ render(
+