From 032f560c2cdfda81c5238e1901b01b0263d81288 Mon Sep 17 00:00:00 2001 From: Jesse Yang Date: Tue, 8 Mar 2022 07:08:40 -0800 Subject: [PATCH] feat(select): sort exact and startsWith match to first (#18856) (cherry picked from commit c75f233109008eee60cce6fea4f7f2d9256d53ef) --- .../src/components/Select/Select.stories.tsx | 43 ++- .../src/components/Select/Select.test.tsx | 64 +++- .../src/components/Select/Select.tsx | 294 +++++++----------- .../src/components/Select/utils.ts | 41 ++- .../controls/SelectControl.test.jsx | 22 +- .../src/utils/rankedSearchCompare.test.ts | 48 +++ .../src/utils/rankedSearchCompare.ts | 38 +++ 7 files changed, 316 insertions(+), 234 deletions(-) create mode 100644 superset-frontend/src/utils/rankedSearchCompare.test.ts create mode 100644 superset-frontend/src/utils/rankedSearchCompare.ts diff --git a/superset-frontend/src/components/Select/Select.stories.tsx b/superset-frontend/src/components/Select/Select.stories.tsx index d64eb78a4c3ad..204ebff807c8a 100644 --- a/superset-frontend/src/components/Select/Select.stories.tsx +++ b/superset-frontend/src/components/Select/Select.stories.tsx @@ -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', @@ -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', @@ -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 }) => (
-
); @@ -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.`, diff --git a/superset-frontend/src/components/Select/Select.test.tsx b/superset-frontend/src/components/Select/Select.test.tsx index c23f57d523d36..15489c14e1212 100644 --- a/superset-frontend/src/components/Select/Select.test.tsx +++ b/superset-frontend/src/components/Select/Select.test.tsx @@ -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) => { @@ -111,9 +114,21 @@ test('displays a header', async () => { }); test('adds a new option if the value is not in the options', async () => { - render(, + ); await open(); expect(await findSelectOption(OPTIONS[0].label)).toBeInTheDocument(); + + rerender( + ); + 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(, + ); + 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(