Skip to content

Commit

Permalink
feat(select): sort exact and startsWith match to first (#18856)
Browse files Browse the repository at this point in the history
  • Loading branch information
ktmud authored Mar 8, 2022
1 parent 04a36d5 commit c75f233
Show file tree
Hide file tree
Showing 7 changed files with 316 additions and 234 deletions.
43 changes: 39 additions & 4 deletions superset-frontend/src/components/Select/Select.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/
import React, { ReactNode, useState, useCallback } from 'react';
import ControlHeader from 'src/explore/components/ControlHeader';
import Select, { SelectProps, OptionsTypePage } from './Select';
import Select, { SelectProps, OptionsTypePage, OptionsType } from './Select';

export default {
title: 'Select',
Expand All @@ -27,7 +27,7 @@ export default {

const DEFAULT_WIDTH = 200;

const options = [
const options: OptionsType = [
{
label: 'Such an incredibly awesome long long label',
value: 'Such an incredibly awesome long long label',
Expand Down Expand Up @@ -147,13 +147,42 @@ const mountHeader = (type: String) => {
return header;
};

export const InteractiveSelect = (args: SelectProps & { header: string }) => (
const generateOptions = (opts: OptionsType, count: number) => {
let generated = opts.slice();
let iteration = 0;
while (generated.length < count) {
iteration += 1;
generated = generated.concat(
// eslint-disable-next-line no-loop-func
generated.map(({ label, value }) => ({
label: `${label} ${iteration}`,
value: `${value} ${iteration}`,
})),
);
}
return generated.slice(0, count);
};

export const InteractiveSelect = ({
header,
options,
optionsCount,
...args
}: SelectProps & { header: string; optionsCount: number }) => (
<div
style={{
width: DEFAULT_WIDTH,
}}
>
<Select {...args} header={mountHeader(args.header)} />
<Select
{...args}
options={
Array.isArray(options)
? generateOptions(options, optionsCount)
: options
}
header={mountHeader(header)}
/>
</div>
);

Expand All @@ -170,6 +199,12 @@ InteractiveSelect.args = {

InteractiveSelect.argTypes = {
...ARG_TYPES,
optionsCount: {
defaultValue: options.length,
control: {
type: 'number',
},
},
header: {
defaultValue: 'none',
description: `It adds a header on top of the Select. Can be any ReactNode.`,
Expand Down
64 changes: 56 additions & 8 deletions superset-frontend/src/components/Select/Select.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ const OPTIONS = [
{ label: 'Irfan', value: 18, gender: 'Male' },
{ label: 'George', value: 19, gender: 'Male' },
{ label: 'Ashfaq', value: 20, gender: 'Male' },
{ label: 'Herme', value: 21, gender: 'Male' },
{ label: 'Cher', value: 22, gender: 'Female' },
{ label: 'Her', value: 23, gender: 'Male' },
].sort((option1, option2) => option1.label.localeCompare(option2.label));

const loadOptions = async (search: string, page: number, pageSize: number) => {
Expand Down Expand Up @@ -111,9 +114,21 @@ test('displays a header', async () => {
});

test('adds a new option if the value is not in the options', async () => {
render(<Select {...defaultProps} options={[]} value={OPTIONS[0]} />);
const { rerender } = render(
<Select {...defaultProps} options={[]} value={OPTIONS[0]} />,
);
await open();
expect(await findSelectOption(OPTIONS[0].label)).toBeInTheDocument();

rerender(
<Select {...defaultProps} options={[OPTIONS[1]]} value={OPTIONS[0]} />,
);
await open();
const options = await findAllSelectOptions();
expect(options).toHaveLength(2);
options.forEach((option, i) =>
expect(option).toHaveTextContent(OPTIONS[i].label),
);
});

test('inverts the selection', async () => {
Expand Down Expand Up @@ -149,9 +164,9 @@ test('sort the options using a custom sort comparator', async () => {
const options = await findAllSelectOptions();
const optionsPage = OPTIONS.slice(0, defaultProps.pageSize);
const sortedOptions = optionsPage.sort(sortComparator);
options.forEach((option, key) =>
expect(option).toHaveTextContent(sortedOptions[key].label),
);
options.forEach((option, key) => {
expect(option).toHaveTextContent(sortedOptions[key].label);
});
});

test('displays the selected values first', async () => {
Expand Down Expand Up @@ -194,12 +209,44 @@ test('searches for label or value', async () => {
expect(options[0]).toHaveTextContent(option.label);
});

test('search order exact and startWith match first', async () => {
render(<Select {...defaultProps} />);
await type('Her');
const options = await findAllSelectOptions();
expect(options.length).toBe(4);
expect(options[0]?.textContent).toEqual('Her');
expect(options[1]?.textContent).toEqual('Herme');
expect(options[2]?.textContent).toEqual('Cher');
expect(options[3]?.textContent).toEqual('Guilherme');
});

test('ignores case when searching', async () => {
render(<Select {...defaultProps} />);
await type('george');
expect(await findSelectOption('George')).toBeInTheDocument();
});

test('same case should be ranked to the top', async () => {
render(
<Select
{...defaultProps}
options={[
{ value: 'Cac' },
{ value: 'abac' },
{ value: 'acbc' },
{ value: 'CAc' },
]}
/>,
);
await type('Ac');
const options = await findAllSelectOptions();
expect(options.length).toBe(4);
expect(options[0]?.textContent).toEqual('acbc');
expect(options[1]?.textContent).toEqual('CAc');
expect(options[2]?.textContent).toEqual('abac');
expect(options[3]?.textContent).toEqual('Cac');
});

test('ignores special keys when searching', async () => {
render(<Select {...defaultProps} />);
await type('{shift}');
Expand All @@ -214,12 +261,13 @@ test('searches for custom fields', async () => {
expect(options[0]).toHaveTextContent('Liam');
await type('Female');
options = await findAllSelectOptions();
expect(options.length).toBe(5);
expect(options.length).toBe(6);
expect(options[0]).toHaveTextContent('Ava');
expect(options[1]).toHaveTextContent('Charlotte');
expect(options[2]).toHaveTextContent('Emma');
expect(options[3]).toHaveTextContent('Nikole');
expect(options[4]).toHaveTextContent('Olivia');
expect(options[2]).toHaveTextContent('Cher');
expect(options[3]).toHaveTextContent('Emma');
expect(options[4]).toHaveTextContent('Nikole');
expect(options[5]).toHaveTextContent('Olivia');
await type('1');
expect(screen.getByText(NO_DATA)).toBeInTheDocument();
});
Expand Down
Loading

0 comments on commit c75f233

Please sign in to comment.