From 50386f1978ce42af081c2e8934cd3c2d7fe5ff27 Mon Sep 17 00:00:00 2001 From: gulcinuras Date: Mon, 28 Nov 2022 13:28:00 +0300 Subject: [PATCH 01/12] fix(select): Create a generic Id type for Option interface - Add title as an interface prop and set type as ReactNode - Create an enum for Language options and use it in Select and TypeaheadSelect components --- .../typeahead/util/typeaheadSelectUtils.ts | 3 ++- src/select/util/selectTypes.ts | 16 +++++++++----- stories/11-Typeahead.stories.tsx | 9 ++++---- .../constants/select/selectStoryConstants.tsx | 22 +++++++++++-------- stories/utils/selectStoryUtils.ts | 5 +++-- 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/select/typeahead/util/typeaheadSelectUtils.ts b/src/select/typeahead/util/typeaheadSelectUtils.ts index 129bba9..de5813d 100644 --- a/src/select/typeahead/util/typeaheadSelectUtils.ts +++ b/src/select/typeahead/util/typeaheadSelectUtils.ts @@ -9,7 +9,8 @@ function filterOptionsByKeyword - !option.title || option.title.toLowerCase().includes(keyword.toLowerCase()) + typeof option.title === "string" && + option.title.toLowerCase().includes(keyword.toLowerCase()) ); } diff --git a/src/select/util/selectTypes.ts b/src/select/util/selectTypes.ts index 561a75c..26ff452 100644 --- a/src/select/util/selectTypes.ts +++ b/src/select/util/selectTypes.ts @@ -1,13 +1,19 @@ import React from "react"; -interface Option { - id: string; +interface Option { + id: Id; + title: React.ReactNode; isDisabled?: boolean; } -interface TypeaheadSelectOption extends Option { - title: string; -} +// TypeaheadSelectOption is intentionally empty. It happens not +// to have more properties than Option, but this may +// change in the future, and it helps to have a TypeaheadSelectOption +// interface that people can use. Therefore the no-empty-interface is disabled +// rule for this declaration: + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface TypeaheadSelectOption extends Option {} type SelectItemElement = HTMLLIElement | HTMLDivElement; diff --git a/stories/11-Typeahead.stories.tsx b/stories/11-Typeahead.stories.tsx index 0067c5e..619cc23 100644 --- a/stories/11-Typeahead.stories.tsx +++ b/stories/11-Typeahead.stories.tsx @@ -7,6 +7,7 @@ import StoryFragment from "./utils/StoryFragment"; import FormField from "../src/form/field/FormField"; import TypeaheadSelect from "../src/select/typeahead/TypeaheadSelect"; import {TypeaheadSelectOption} from "../src/select/util/selectTypes"; +import {Language} from "./utils/constants/select/selectStoryConstants"; const simulateAPICall = (timeout = 1000) => new Promise((resolve) => setTimeout(resolve, timeout)); @@ -26,11 +27,11 @@ storiesOf("Typeahead", module).add("Typeahead", () => { id: "spanish", title: "Spanish" } - ], + ] as TypeaheadSelectOption[], thirdOptions: [], - selectedOptions: [] as TypeaheadSelectOption[], - secondSelectedOptions: [] as TypeaheadSelectOption[], - thirdSelectedOptions: [] as TypeaheadSelectOption[], + selectedOptions: [] as TypeaheadSelectOption[], + secondSelectedOptions: [] as TypeaheadSelectOption[], + thirdSelectedOptions: [] as TypeaheadSelectOption[], areOptionsFetching: false, keyword: "" }; diff --git a/stories/utils/constants/select/selectStoryConstants.tsx b/stories/utils/constants/select/selectStoryConstants.tsx index a8ed0c5..5e15760 100644 --- a/stories/utils/constants/select/selectStoryConstants.tsx +++ b/stories/utils/constants/select/selectStoryConstants.tsx @@ -1,4 +1,5 @@ import React from "react"; +import {Option} from "../../../../src/select/util/selectTypes"; function CoinDropdownOptionCustomContent({ id, @@ -21,6 +22,13 @@ function CoinDropdownOptionCustomContent({ ); } +export enum Language { + TURKISH = "turkish", + ENGLISH = "english", + SPANISH = "spanish", + FRENCH = "french" +} + const initialState = { basic: { options: [ @@ -42,7 +50,7 @@ const initialState = { isDisabled: true } ], - selectedOption: null as {id: string; isDisabled?: boolean; title: string} | null + selectedOption: null as Option | null }, multiSelect: { options: [ @@ -63,8 +71,8 @@ const initialState = { title: "French - Disabled", isDisabled: true } - ] as {id: string; isDisabled?: boolean; title: string}[], - value: [] as {id: string; isDisabled?: boolean; title: string}[] + ] as Option[], + value: [] as Option[] }, withSubtitle: { options: [ @@ -84,7 +92,7 @@ const initialState = { subtitle: "JavaScript" } ], - selectedOption: null as {id: string; isDisabled?: boolean; subtitle: string} | null + selectedOption: null as Option | null }, withCustomContent: { options: [ @@ -127,11 +135,7 @@ const initialState = { ) } ], - selectedOption: null as { - id: string; - title?: string; - CustomContent: JSX.Element; - } | null + selectedOption: null as Option | null } }; diff --git a/stories/utils/selectStoryUtils.ts b/stories/utils/selectStoryUtils.ts index 7d36a32..81bb726 100644 --- a/stories/utils/selectStoryUtils.ts +++ b/stories/utils/selectStoryUtils.ts @@ -1,9 +1,10 @@ -import {initialState} from "./constants/select/selectStoryConstants"; +import {Option} from "../../src/select/util/selectTypes"; +import {initialState, Language} from "./constants/select/selectStoryConstants"; function handleMultiSelect( state: typeof initialState.multiSelect, setState: React.Dispatch>, - option: {id: string; isDisabled?: boolean; title: string} + option: Option ) { const isSelected = state.value.findIndex((opt) => opt.id === option.id) > -1; From c622ed96e90ba8a5c5f23d551899318709dddaf0 Mon Sep 17 00:00:00 2001 From: gulcinuras Date: Thu, 1 Dec 2022 13:15:23 +0300 Subject: [PATCH 02/12] fix(select-types): make TypeaheadSelectOption generic --- src/select/util/selectTypes.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/select/util/selectTypes.ts b/src/select/util/selectTypes.ts index 26ff452..7e28a2a 100644 --- a/src/select/util/selectTypes.ts +++ b/src/select/util/selectTypes.ts @@ -6,14 +6,9 @@ interface Option { isDisabled?: boolean; } -// TypeaheadSelectOption is intentionally empty. It happens not -// to have more properties than Option, but this may -// change in the future, and it helps to have a TypeaheadSelectOption -// interface that people can use. Therefore the no-empty-interface is disabled -// rule for this declaration: - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -interface TypeaheadSelectOption extends Option {} +type TypeaheadSelectOption = Omit & { + id: Id; +}; type SelectItemElement = HTMLLIElement | HTMLDivElement; From cb18789ddd80f33dfe94bcad0ced737b93c07884 Mon Sep 17 00:00:00 2001 From: gulcinuras Date: Tue, 10 Jan 2023 20:57:45 +0300 Subject: [PATCH 03/12] fix(typeahead-select): Remove title from TypeaheadSelectOption props - Use contentRenderer as Select.Item content - Use contentRenderer for tag content as we removed the title prop - Instead of handling the keyword change inside TypeaheadSelect by filtering titles, handle it outside of the component --- src/select/typeahead/TypeaheadSelect.tsx | 32 ++------ .../typeahead/typeahead-select.test.tsx | 8 +- .../typeahead/util/typeaheadSelectUtils.ts | 20 ----- src/select/util/selectTypes.ts | 5 +- src/tag/util/tagUtils.ts | 12 ++- stories/11-Typeahead.stories.tsx | 80 ++++++++++++++----- .../constants/select/selectStoryConstants.tsx | 10 +-- stories/utils/selectStoryUtils.ts | 2 +- stories/utils/typeaheadSelectStoryUtils.ts | 21 +++++ tsconfig.json | 2 +- 10 files changed, 111 insertions(+), 81 deletions(-) delete mode 100644 src/select/typeahead/util/typeaheadSelectUtils.ts create mode 100644 stories/utils/typeaheadSelectStoryUtils.ts diff --git a/src/select/typeahead/TypeaheadSelect.tsx b/src/select/typeahead/TypeaheadSelect.tsx index 3b1e132..2e68e37 100644 --- a/src/select/typeahead/TypeaheadSelect.tsx +++ b/src/select/typeahead/TypeaheadSelect.tsx @@ -8,7 +8,6 @@ import TypeaheadInput, { } from "../../form/input/typeahead/TypeaheadInput"; import {mapOptionsToTagShapes} from "../../tag/util/tagUtils"; import {TagShape} from "../../tag/Tag"; -import {filterOptionsByKeyword} from "./util/typeaheadSelectUtils"; import {filterOutItemsByKey} from "../../core/utils/array/arrayUtils"; import Spinner from "../../spinner/Spinner"; import {KEYBOARD_EVENT_KEY} from "../../core/utils/keyboard/keyboardEventConstants"; @@ -32,15 +31,15 @@ export interface TypeaheadSelectProps< TypeaheadInputProps, "id" | "placeholder" | "name" | "onFocus" | "type" >; + contentRenderer: (option: T) => React.ReactNode; + onKeywordChange: (value: string) => void; testid?: string; - onKeywordChange?: (value: string) => void; initialKeyword?: string; controlledKeyword?: string; onTagRemove?: (option: Option) => void; selectedOptionLimit?: number; customClassName?: string; shouldDisplaySelectedOptions?: boolean; - shouldFilterOptionsByKeyword?: boolean; isDisabled?: boolean; customSpinner?: React.ReactNode; shouldShowEmptyOptions?: boolean; @@ -57,10 +56,10 @@ function TypeaheadSelect= selectedOptionLimit ); @@ -144,7 +143,7 @@ function TypeaheadSelect @@ -159,9 +158,10 @@ function TypeaheadSelect {computedDropdownOptions.map((option) => ( - {option.title} + {contentRenderer(option)} ))} + {shouldShowEmptyOptions && !computedDropdownOptions.length && (

selectedOptions.indexOf(option) < 0 - ); - - setComputedDropdownOptions(filterOptionsByKeyword(unselectedOptions, value)); - } - - if (onKeywordChange) { - onKeywordChange(value); - } - - if (typeof controlledKeyword === "undefined") { - setKeyword(value); - } - } - function handleKeyDown(event: React.KeyboardEvent) { const {key} = event; diff --git a/src/select/typeahead/typeahead-select.test.tsx b/src/select/typeahead/typeahead-select.test.tsx index 4fbe2d1..18e30bb 100644 --- a/src/select/typeahead/typeahead-select.test.tsx +++ b/src/select/typeahead/typeahead-select.test.tsx @@ -5,9 +5,12 @@ import "@testing-library/jest-dom"; import TypeaheadSelect, {TypeaheadSelectProps} from "./TypeaheadSelect"; import {testA11y} from "../../core/utils/test/testUtils"; +import {TypeaheadSelectOption} from "../util/selectTypes"; describe("", () => { - const defaultTypeaheadSelectProps: TypeaheadSelectProps = { + const defaultTypeaheadSelectProps: TypeaheadSelectProps< + TypeaheadSelectOption & {title: string} + > = { testid: "typeahead-select", options: [ {id: "1", title: "first-dropdown-option"}, @@ -21,7 +24,8 @@ describe("", () => { typeaheadProps: { placeholder: "test placeholder", name: "test typeahead" - } + }, + contentRenderer: (option) => option.title }; it("should render correctly", () => { diff --git a/src/select/typeahead/util/typeaheadSelectUtils.ts b/src/select/typeahead/util/typeaheadSelectUtils.ts deleted file mode 100644 index de5813d..0000000 --- a/src/select/typeahead/util/typeaheadSelectUtils.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {TypeaheadSelectOption} from "../../util/selectTypes"; - -function filterOptionsByKeyword( - options: T[], - keyword: string -): T[] { - let filteredOptions = options; - - if (keyword) { - filteredOptions = options.filter( - (option) => - typeof option.title === "string" && - option.title.toLowerCase().includes(keyword.toLowerCase()) - ); - } - - return filteredOptions; -} - -export {filterOptionsByKeyword}; diff --git a/src/select/util/selectTypes.ts b/src/select/util/selectTypes.ts index 7e28a2a..290808a 100644 --- a/src/select/util/selectTypes.ts +++ b/src/select/util/selectTypes.ts @@ -2,13 +2,10 @@ import React from "react"; interface Option { id: Id; - title: React.ReactNode; isDisabled?: boolean; } -type TypeaheadSelectOption = Omit & { - id: Id; -}; +type TypeaheadSelectOption = Option; type SelectItemElement = HTMLLIElement | HTMLDivElement; diff --git a/src/tag/util/tagUtils.ts b/src/tag/util/tagUtils.ts index 124cb7d..8853262 100644 --- a/src/tag/util/tagUtils.ts +++ b/src/tag/util/tagUtils.ts @@ -1,20 +1,24 @@ +import React from "react"; + import {TypeaheadSelectOption} from "../../select/util/selectTypes"; import {TagShape} from "../Tag"; function mapOptionToTagShape( - option: T + option: T, + content: React.ReactNode ): TagShape { return { id: option.id, - content: option.title, + content, context: option }; } function mapOptionsToTagShapes( - options: T[] + options: T[], + contentRenderer: (option: T) => React.ReactNode ) { - return options.map(mapOptionToTagShape); + return options.map((option) => mapOptionToTagShape(option, contentRenderer(option))); } export {mapOptionsToTagShapes}; diff --git a/stories/11-Typeahead.stories.tsx b/stories/11-Typeahead.stories.tsx index 619cc23..c388575 100644 --- a/stories/11-Typeahead.stories.tsx +++ b/stories/11-Typeahead.stories.tsx @@ -8,6 +8,7 @@ import FormField from "../src/form/field/FormField"; import TypeaheadSelect from "../src/select/typeahead/TypeaheadSelect"; import {TypeaheadSelectOption} from "../src/select/util/selectTypes"; import {Language} from "./utils/constants/select/selectStoryConstants"; +import {filterOptionsByKeyword} from "./utils/typeaheadSelectStoryUtils"; const simulateAPICall = (timeout = 1000) => new Promise((resolve) => setTimeout(resolve, timeout)); @@ -27,11 +28,11 @@ storiesOf("Typeahead", module).add("Typeahead", () => { id: "spanish", title: "Spanish" } - ] as TypeaheadSelectOption[], + ] as (TypeaheadSelectOption & {title: string})[], thirdOptions: [], - selectedOptions: [] as TypeaheadSelectOption[], - secondSelectedOptions: [] as TypeaheadSelectOption[], - thirdSelectedOptions: [] as TypeaheadSelectOption[], + selectedOptions: [] as (TypeaheadSelectOption & {title: string})[], + secondSelectedOptions: [] as (TypeaheadSelectOption & {title: string})[], + thirdSelectedOptions: [] as (TypeaheadSelectOption & {title: string})[], areOptionsFetching: false, keyword: "" }; @@ -39,16 +40,13 @@ storiesOf("Typeahead", module).add("Typeahead", () => { const modelInitialState = { options: [ { - id: "2005", - title: "2005" + id: "2005" }, { - id: "2015", - title: "2015" + id: "2015" }, { - id: "2021", - title: "2021" + id: "2021" } ], thirdOptions: [] as TypeaheadSelectOption[], @@ -66,14 +64,25 @@ storiesOf("Typeahead", module).add("Typeahead", () => { {(state, setState) => ( option.title} onSelect={(option) => setState({ ...state, selectedOptions: [...state.selectedOptions, option] }) } + onKeywordChange={(keyword) => + setState({ + ...state, + options: filterOptionsByKeyword( + initialState.options, + keyword, + "title" + ) + }) + } onTagRemove={handleRemoveTag(state, setState)} typeaheadProps={{ placeholder: "Select Languages", @@ -89,7 +98,8 @@ storiesOf("Typeahead", module).add("Typeahead", () => { option.title} selectedOptions={state.secondSelectedOptions} onSelect={(option) => setState({ @@ -97,6 +107,16 @@ storiesOf("Typeahead", module).add("Typeahead", () => { secondSelectedOptions: [...state.secondSelectedOptions, option] }) } + onKeywordChange={(keyword) => + setState({ + ...state, + options: filterOptionsByKeyword( + initialState.options, + keyword, + "title" + ) + }) + } onTagRemove={handleRemoveTag(state, setState, "secondSelectedOptions")} typeaheadProps={{ placeholder: "Select Languages", @@ -113,8 +133,19 @@ storiesOf("Typeahead", module).add("Typeahead", () => { option.title} selectedOptions={state.secondSelectedOptions} + onKeywordChange={(keyword) => + setState({ + ...state, + options: filterOptionsByKeyword( + initialState.options, + keyword, + "title" + ) + }) + } onSelect={(option) => setState({ ...state, @@ -138,9 +169,9 @@ storiesOf("Typeahead", module).add("Typeahead", () => { "Select Languages (API Fetch Simulation) - with keyword by TypeaheadSelect" }> option.title} selectedOptions={state.thirdSelectedOptions} onSelect={(option) => setState({ @@ -148,7 +179,7 @@ storiesOf("Typeahead", module).add("Typeahead", () => { thirdSelectedOptions: [...state.thirdSelectedOptions, option] }) } - onKeywordChange={handleKeywordChange(setState)} + onKeywordChange={handleAsyncKeywordChange(setState)} onTagRemove={handleRemoveTag(state, setState, "thirdSelectedOptions")} typeaheadProps={{ placeholder: "Select Languages", @@ -164,9 +195,9 @@ storiesOf("Typeahead", module).add("Typeahead", () => { option.title} selectedOptions={state.thirdSelectedOptions} onSelect={(option) => setState({ @@ -175,7 +206,7 @@ storiesOf("Typeahead", module).add("Typeahead", () => { keyword: "" }) } - onKeywordChange={handleKeywordChange(setState)} + onKeywordChange={handleAsyncKeywordChange(setState)} controlledKeyword={state.keyword} onTagRemove={handleRemoveTag(state, setState, "thirdSelectedOptions")} typeaheadProps={{ @@ -191,7 +222,7 @@ storiesOf("Typeahead", module).add("Typeahead", () => { {(state, setState) => ( setState({ @@ -199,6 +230,17 @@ storiesOf("Typeahead", module).add("Typeahead", () => { selectedOptions: [...state.selectedOptions, option] }) } + contentRenderer={(option) => option.id} + onKeywordChange={(keyword) => + setState({ + ...state, + options: filterOptionsByKeyword( + modelInitialState.options, + keyword, + "id" + ) + }) + } onTagRemove={handleRemoveTag(state, setState)} typeaheadProps={{ placeholder: "Select Model", @@ -223,7 +265,7 @@ storiesOf("Typeahead", module).add("Typeahead", () => { }); } - function handleKeywordChange(setState) { + function handleAsyncKeywordChange(setState) { return async (keyword) => { if (keyword) { setState((prevState) => ({ diff --git a/stories/utils/constants/select/selectStoryConstants.tsx b/stories/utils/constants/select/selectStoryConstants.tsx index 5e15760..7daeb05 100644 --- a/stories/utils/constants/select/selectStoryConstants.tsx +++ b/stories/utils/constants/select/selectStoryConstants.tsx @@ -50,7 +50,7 @@ const initialState = { isDisabled: true } ], - selectedOption: null as Option | null + selectedOption: null as (Option & {title: string}) | null }, multiSelect: { options: [ @@ -71,8 +71,8 @@ const initialState = { title: "French - Disabled", isDisabled: true } - ] as Option[], - value: [] as Option[] + ] as (Option & {title: string})[], + value: [] as (Option & {title: string})[] }, withSubtitle: { options: [ @@ -92,7 +92,7 @@ const initialState = { subtitle: "JavaScript" } ], - selectedOption: null as Option | null + selectedOption: null as (Option & {title: string}) | null }, withCustomContent: { options: [ @@ -135,7 +135,7 @@ const initialState = { ) } ], - selectedOption: null as Option | null + selectedOption: null as (Option & {title: string}) | null } }; diff --git a/stories/utils/selectStoryUtils.ts b/stories/utils/selectStoryUtils.ts index 81bb726..a7c20d7 100644 --- a/stories/utils/selectStoryUtils.ts +++ b/stories/utils/selectStoryUtils.ts @@ -4,7 +4,7 @@ import {initialState, Language} from "./constants/select/selectStoryConstants"; function handleMultiSelect( state: typeof initialState.multiSelect, setState: React.Dispatch>, - option: Option + option: Option & {title: string} ) { const isSelected = state.value.findIndex((opt) => opt.id === option.id) > -1; diff --git a/stories/utils/typeaheadSelectStoryUtils.ts b/stories/utils/typeaheadSelectStoryUtils.ts new file mode 100644 index 0000000..0d1fd5e --- /dev/null +++ b/stories/utils/typeaheadSelectStoryUtils.ts @@ -0,0 +1,21 @@ +import {TypeaheadSelectOption} from "../../src/select/util/selectTypes"; + +function filterOptionsByKeyword( + options: T[], + keyword: string, + filterBy: keyof Pick | "title" +): T[] { + let filteredOptions = options; + + if (keyword) { + filteredOptions = options.filter((option) => { + const optionFilterValue = option[filterBy] as string; + + return optionFilterValue.toLowerCase().includes(keyword.toLowerCase()); + }); + } + + return filteredOptions; +} + +export {filterOptionsByKeyword}; diff --git a/tsconfig.json b/tsconfig.json index 83d746a..38b2479 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,6 +23,6 @@ "noUnusedParameters": true, "downlevelIteration": true }, - "include": ["src"], + "include": ["src", "stories/utils/typeaheadSelectStoryUtils.ts"], "exclude": ["node_modules", "build"] } From a07ff689a7fc43dd2f19dd0f7784ba2937951957 Mon Sep 17 00:00:00 2001 From: gulcinuras Date: Tue, 10 Jan 2023 21:05:15 +0300 Subject: [PATCH 04/12] Remove typeaheadSelectStoryUtils from tsconfig --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 38b2479..83d746a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,6 +23,6 @@ "noUnusedParameters": true, "downlevelIteration": true }, - "include": ["src", "stories/utils/typeaheadSelectStoryUtils.ts"], + "include": ["src"], "exclude": ["node_modules", "build"] } From 80df9c71b6a01eae0697e9b2997dae625d991ece Mon Sep 17 00:00:00 2001 From: gulcinuras Date: Wed, 25 Jan 2023 00:31:15 +0300 Subject: [PATCH 05/12] fix(typeahead-select): Remove initialKeyword and use controlledKeyword to set initial and changeable value - Set canSelectMultiple to false if options.length is not bigger than 1 - Add testid to use in typeahead-select.test - Add onClick optional prop to TypeheadSelectTrigger to set the correct classname when the menu is open - setMenuVisibility to false if shouldCloseOnSelect is true, so that the classnames are correct - Remove input styles when TypeaheadSelectInput is used inside of the trigger - Add testid to TypeheadSelectTrigger to test it easily - Fix most of the test cases for typeahead-select - Fix classnames - I used screen.findByText function to find the option but I can update it Note:- In my opinion, having two classnames for typeahead-select and select is a bit unnecessary. We can just add typeahead-select as main classname and override select classnames if necessary. - I couldn't fix a11y test case for the typeahead-select. --- src/select/Select.tsx | 1 + src/select/item/SelectItem.tsx | 11 ++- src/select/typeahead/TypeaheadSelect.tsx | 36 ++++++--- src/select/typeahead/_typeahead-select.scss | 10 --- .../trigger/TypeheadSelectTrigger.tsx | 10 ++- .../trigger/_typehead-select-trigger.scss | 8 +- .../typeahead/typeahead-select.test.tsx | 77 +++++++++---------- src/select/util/selectTypes.ts | 1 + 8 files changed, 86 insertions(+), 68 deletions(-) diff --git a/src/select/Select.tsx b/src/select/Select.tsx index efda77a..b9f4a0d 100644 --- a/src/select/Select.tsx +++ b/src/select/Select.tsx @@ -47,6 +47,7 @@ function SelectComponent( return (

( ) { const selectState = useSelectContext(); const dispatchSelectStateAction = useSelectDispatchContext(); - const {onSelect, value, focusedOptionIndex, shouldCloseOnSelect, options} = selectState; + const { + onSelect, + value, + focusedOptionIndex, + shouldCloseOnSelect, + options, + isMenuOpen + } = selectState; const optionIndex = options.findIndex((opt) => opt?.id === option?.id); const isSelected = Array.isArray(value) ? Boolean(value.find((currentOption) => currentOption.id === option?.id)) @@ -83,7 +90,7 @@ function SelectItemComponent( payload: optionIndex }); - if (shouldCloseOnSelect) { + if (shouldCloseOnSelect && isMenuOpen) { dispatchSelectStateAction({type: "TOGGLE_MENU_VISIBILITY"}); } } diff --git a/src/select/typeahead/TypeaheadSelect.tsx b/src/select/typeahead/TypeaheadSelect.tsx index 2e68e37..d3e61e8 100644 --- a/src/select/typeahead/TypeaheadSelect.tsx +++ b/src/select/typeahead/TypeaheadSelect.tsx @@ -34,7 +34,6 @@ export interface TypeaheadSelectProps< contentRenderer: (option: T) => React.ReactNode; onKeywordChange: (value: string) => void; testid?: string; - initialKeyword?: string; controlledKeyword?: string; onTagRemove?: (option: Option) => void; selectedOptionLimit?: number; @@ -47,7 +46,6 @@ export interface TypeaheadSelectProps< areOptionsFetching?: boolean; } -/* eslint-disable complexity */ function TypeaheadSelect({ testid, options, @@ -65,23 +63,23 @@ function TypeaheadSelect) { const typeaheadInputRef = useRef(null); const [isMenuOpen, setMenuVisibility] = useState(false); const [computedDropdownOptions, setComputedDropdownOptions] = useState(options); const [shouldFocusOnInput, setShouldFocusOnInput] = useState(false); - const [keyword, setKeyword] = useState(initialKeyword); - const inputValue = typeof controlledKeyword === "string" ? controlledKeyword : keyword; + const [keyword, setKeyword] = useState(controlledKeyword); const tags = mapOptionsToTagShapes(selectedOptions, contentRenderer); const shouldDisplayOnlyTags = Boolean( selectedOptionLimit && selectedOptions.length >= selectedOptionLimit ); - const canSelectMultiple = !selectedOptionLimit || selectedOptionLimit > 1; + const canSelectMultiple = + options.length > 1 && (!selectedOptionLimit || selectedOptionLimit > 1); + const shouldCloseOnSelect = !canSelectMultiple || Boolean(selectedOptionLimit && selectedOptions.length >= selectedOptionLimit - 1); @@ -123,6 +121,7 @@ function TypeaheadSelect @@ -192,7 +192,12 @@ function TypeaheadSelect void; customClassName?: string; input?: React.ReactNode; + onClick?: VoidFunction; } function TypeheadSelectTrigger({ handleTagRemove, tags, customClassName, - input + input, + onClick }: TypeheadSelectTriggerProps) { return ( - + )} + {input} ); diff --git a/src/select/typeahead/trigger/_typehead-select-trigger.scss b/src/select/typeahead/trigger/_typehead-select-trigger.scss index b755350..c522d95 100644 --- a/src/select/typeahead/trigger/_typehead-select-trigger.scss +++ b/src/select/typeahead/trigger/_typehead-select-trigger.scss @@ -8,8 +8,12 @@ padding: 0; - border: 1px solid var(--default-border-color); - border-radius: var(--small-border-radius); + .typeahead-select__input { + .input { + border: none; + border-radius: 8px; + } + } } .typeahead-select-trigger__tag-list { diff --git a/src/select/typeahead/typeahead-select.test.tsx b/src/select/typeahead/typeahead-select.test.tsx index 18e30bb..37af5f9 100644 --- a/src/select/typeahead/typeahead-select.test.tsx +++ b/src/select/typeahead/typeahead-select.test.tsx @@ -1,5 +1,5 @@ import React from "react"; -import {fireEvent, render, screen, within} from "@testing-library/react"; +import {fireEvent, render, screen} from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import "@testing-library/jest-dom"; @@ -65,18 +65,20 @@ describe("", () => { it("should set initialValue and remove when set new value", () => { render( - + ); - const typeaheadSelect = screen.getByRole("textbox") as HTMLInputElement; + const typeaheadSelectInput = screen.getByTestId( + `${defaultTypeaheadSelectProps.testid}.search` + ).firstElementChild as HTMLInputElement; - expect(typeaheadSelect).toHaveValue("initial"); + expect(typeaheadSelectInput).toHaveValue("initial"); + + typeaheadSelectInput.setSelectionRange(0, typeaheadSelectInput.value.length); - typeaheadSelect.setSelectionRange(0, typeaheadSelect.value.length); + userEvent.type(typeaheadSelectInput, "test"); - userEvent.type(typeaheadSelect, "test"); - - expect(typeaheadSelect).toHaveValue("test"); + expect(typeaheadSelectInput).toHaveValue("test"); }); it("should render custom spinner correctly", () => { @@ -105,15 +107,14 @@ describe("", () => { const dropdownList = screen.getByTestId("test-dropdown-visibility"); - expect(dropdownList).not.toHaveClass("dropdown-list--is-visible"); + expect(dropdownList).not.toHaveClass("typeahead-select--is-dropdown-menu-open"); - // fireEvent.focus(screen.getByRole("listbox")); - userEvent.click(screen.getByRole("listbox")); + userEvent.click(screen.getByTestId("TypeaheadSelectTrigger")); - expect(dropdownList).toHaveClass("dropdown-list--is-visible"); + expect(dropdownList).toHaveClass("typeahead-select--is-dropdown-menu-open"); }); - it("should run click event handle when option is selected", () => { + it("should run click event handler when option is selected", async () => { render( ", () => { /> ); - const selectedOptionList = screen.getByRole("list"); - - const dropdownList = screen.getByTestId("test-dropdown-visibility"); - - const firstOption = within(dropdownList).getByTestId( - "test-dropdown-visibility.item-0" + const firstOption = await screen.findByText( + defaultTypeaheadSelectProps.options[0].title ); userEvent.click(firstOption); expect(defaultTypeaheadSelectProps.onSelect).toHaveBeenCalledTimes(1); - const secondOption = within(dropdownList).getByTestId( - "test-dropdown-visibility.item-1" + const secondOption = await screen.findByText( + defaultTypeaheadSelectProps.options[1].title ); userEvent.click(secondOption); - expect(selectedOptionList).not.toContainElement(secondOption); + expect(defaultTypeaheadSelectProps.onSelect).toHaveBeenCalledTimes(2); }); - it("should not render option menu when selectedOptionLimit is reached", () => { + it("should not render option menu when selectedOptionLimit is reached", async () => { render( ", () => { const selectedOptionList = screen.getByRole("list"); - const dropdownList = screen.getByTestId("test-dropdown-visibility"); - - const secondOption = within(dropdownList).getByTestId( - "test-dropdown-visibility.item-1" + const secondOption = await screen.findByText( + defaultTypeaheadSelectProps.options[1].title ); userEvent.click(secondOption); @@ -165,7 +160,7 @@ describe("", () => { expect(selectedOptionList).not.toContainElement(secondOption); }); - it("should render when select an option flow correctly", () => { + it("should render when select an option flow correctly", async () => { render( ", () => { /> ); - userEvent.click(screen.getByRole("listbox")); + userEvent.click(screen.getByTestId("TypeaheadSelectTrigger")); const dropdownList = screen.getByTestId("test-dropdown-visibility"); - expect(dropdownList).toHaveClass("dropdown-list--is-visible"); + expect(dropdownList).toHaveClass("typeahead-select--is-dropdown-menu-open"); const typeaheadInput = screen.getByRole("textbox"); userEvent.type(typeaheadInput, "second-dropdown"); - const searchedOption = screen.getByTestId("test-dropdown-visibility.item-1"); + const searchedOption = await screen.findByText( + defaultTypeaheadSelectProps.options[0].title + ); expect(dropdownList).toContainElement(searchedOption); @@ -193,12 +190,12 @@ describe("", () => { userEvent.click(searchedOption); - expect(dropdownList).not.toHaveClass("dropdown-list--is-visible"); + expect(dropdownList).not.toHaveClass("typeahead-select--is-dropdown-menu-open"); expect(defaultTypeaheadSelectProps.onSelect).toHaveBeenCalledTimes(1); }); - it("should not render selected option on dropdown list", () => { + it("should not render selected option on dropdown list", async () => { const {rerender} = render( ", () => { /> ); - userEvent.click(screen.getByRole("listbox")); + userEvent.click(screen.getByTestId("TypeaheadSelectTrigger")); const dropdownList = screen.getByTestId("test-dropdown-visibility"); - expect(dropdownList).toHaveClass("dropdown-list--is-visible"); + expect(dropdownList).toHaveClass("typeahead-select--is-dropdown-menu-open"); const typeaheadInput = screen.getByRole("textbox"); userEvent.type(typeaheadInput, "second-dropdown"); - const searchedOption = screen.getByTestId("test-dropdown-visibility.item-1"); + const searchedOption = await screen.findByText( + defaultTypeaheadSelectProps.options[1].title + ); expect(dropdownList).toContainElement(searchedOption); @@ -227,8 +226,7 @@ describe("", () => { userEvent.click(searchedOption); expect(defaultTypeaheadSelectProps.onSelect).toHaveBeenCalledTimes(1); - - expect(dropdownList).toHaveClass("dropdown-list--is-visible"); + expect(dropdownList).not.toHaveClass("select--is-visible"); rerender( ", () => { expect(dropdownList.children.length).toBe(2); - // One of items is the input another one is selected option - expect(selectedOptionList.children.length).toBe(2); + expect(selectedOptionList.children.length).toBe(1); }); }); /* eslint diff --git a/src/select/util/selectTypes.ts b/src/select/util/selectTypes.ts index 290808a..4e777c0 100644 --- a/src/select/util/selectTypes.ts +++ b/src/select/util/selectTypes.ts @@ -30,6 +30,7 @@ interface SelectProps { isDisabled?: boolean; shouldCloseOnSelect?: boolean; isMenuOpen?: boolean; + testid?: string; } type SelectContextValue = Pick< From ccc9698adf832333c28d5203b6a08f8f60bf93e3 Mon Sep 17 00:00:00 2001 From: gulcinuras Date: Mon, 20 Feb 2023 17:07:29 +0300 Subject: [PATCH 06/12] fix(select/options): Set options to the state if the length is not equal --- package.json | 4 +--- src/select/Select.tsx | 10 ++++++++++ src/select/util/context/SelectContext.reducer.ts | 8 +++++++- src/select/util/selectTypes.ts | 3 ++- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 6e9a1bc..95664c9 100644 --- a/package.json +++ b/package.json @@ -96,9 +96,7 @@ "stylelint-scss": "3.19.0", "ts-jest": "26.1.0", "ts-loader": "7.0.5", - "typescript": "3.9.5", - "react": "17.0.2", - "react-dom": "17.0.2" + "typescript": "3.9.5" }, "dependencies": { "classnames": "2.3.1", diff --git a/src/select/Select.tsx b/src/select/Select.tsx index b9f4a0d..8a790df 100644 --- a/src/select/Select.tsx +++ b/src/select/Select.tsx @@ -4,6 +4,7 @@ import React, { ForwardedRef, forwardRef, Ref, + useEffect, useImperativeHandle, useReducer, useRef @@ -44,6 +45,15 @@ function SelectComponent( () => selectRef.current ); + useEffect(() => { + if (props.options.length !== selectOwnState.options.length) { + dispatchSelectStateAction({ + type: "SET_OPTIONS", + options: props.options + }); + } + }, [props.options, selectOwnState.options.length]); + return (
= T | T[] | null; type SelectStateAction = | {type: "TOGGLE_MENU_VISIBILITY"} - | {type: "SET_FOCUSED_OPTION_INDEX"; payload: number}; + | {type: "SET_FOCUSED_OPTION_INDEX"; payload: number} + | {type: "SET_OPTIONS"; options: (Option | null)[]}; export type { SelectContextValue, From ed9dddb1037854639e2fbb22e700dd53ff615b21 Mon Sep 17 00:00:00 2001 From: gulcinuras Date: Mon, 20 Feb 2023 17:10:10 +0300 Subject: [PATCH 07/12] chore(package-json): Add react and react-dom back to the devDependencies --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 95664c9..6e9a1bc 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,9 @@ "stylelint-scss": "3.19.0", "ts-jest": "26.1.0", "ts-loader": "7.0.5", - "typescript": "3.9.5" + "typescript": "3.9.5", + "react": "17.0.2", + "react-dom": "17.0.2" }, "dependencies": { "classnames": "2.3.1", From 8cef8db36e25e5d0324d0faf5abcf52e0b4c7302 Mon Sep 17 00:00:00 2001 From: gulcinuras Date: Wed, 13 Sep 2023 12:27:55 +0300 Subject: [PATCH 08/12] Remove the options field from selectOwnState and directly set it in the context state from the props with generateSelectState function --- src/select/Select.tsx | 13 +------------ src/select/util/context/SelectContext.reducer.ts | 7 ------- src/select/util/selectTypes.ts | 14 +++++++++----- src/select/util/selectUtils.ts | 4 +++- 4 files changed, 13 insertions(+), 25 deletions(-) diff --git a/src/select/Select.tsx b/src/select/Select.tsx index 8a790df..11d0d57 100644 --- a/src/select/Select.tsx +++ b/src/select/Select.tsx @@ -4,7 +4,6 @@ import React, { ForwardedRef, forwardRef, Ref, - useEffect, useImperativeHandle, useReducer, useRef @@ -30,8 +29,7 @@ function SelectComponent( const {children, role = "listbox", customClassName, value} = props; const [selectOwnState, dispatchSelectStateAction] = useReducer(selectStateReducer, { focusedOptionIndex: -1, - isMenuOpen: false, - options: props.options || [] + isMenuOpen: false }); const selectRef = useRef(null); const selectState = generateSelectState(selectOwnState, props); @@ -45,15 +43,6 @@ function SelectComponent( () => selectRef.current ); - useEffect(() => { - if (props.options.length !== selectOwnState.options.length) { - dispatchSelectStateAction({ - type: "SET_OPTIONS", - options: props.options - }); - } - }, [props.options, selectOwnState.options.length]); - return (
{ children: React.ReactNode; - options: (Option | null)[]; + options: Option[]; value: SelectValue; onSelect: OptionSelectHandler; role?: SelectRole; @@ -35,22 +35,26 @@ interface SelectProps { type SelectContextValue = Pick< SelectProps, - "hasError" | "isDisabled" | "onSelect" | "shouldCloseOnSelect" | "value" | "role" + | "hasError" + | "isDisabled" + | "onSelect" + | "shouldCloseOnSelect" + | "value" + | "role" + | "options" > & SelectOwnState; interface SelectOwnState { isMenuOpen: boolean; focusedOptionIndex: number; - options: (Option | null)[]; } type SelectValue = T | T[] | null; type SelectStateAction = | {type: "TOGGLE_MENU_VISIBILITY"} - | {type: "SET_FOCUSED_OPTION_INDEX"; payload: number} - | {type: "SET_OPTIONS"; options: (Option | null)[]}; + | {type: "SET_FOCUSED_OPTION_INDEX"; payload: number}; export type { SelectContextValue, diff --git a/src/select/util/selectUtils.ts b/src/select/util/selectUtils.ts index 08c267e..84358f2 100644 --- a/src/select/util/selectUtils.ts +++ b/src/select/util/selectUtils.ts @@ -11,13 +11,15 @@ function generateSelectState( props: SelectProps ): SelectContextValue { const selectState: SelectContextValue = { + ...state, isDisabled: props.isDisabled ?? false, hasError: props.hasError ?? false, value: props.value || null, onSelect: props.onSelect, shouldCloseOnSelect: props.shouldCloseOnSelect ?? true, role: props.role ?? "listbox", - ...state + isMenuOpen: props.isMenuOpen ?? state.isMenuOpen, + options: props.options }; return selectState; From 59f745dbb866ed23c6f9610b1d055ec5f95cf530 Mon Sep 17 00:00:00 2001 From: gulcinuras Date: Mon, 1 Apr 2024 22:25:27 +0300 Subject: [PATCH 09/12] feat(typeahead-select): Remove useState hook for setting keyword, since we are handling the keyword state on our projects - controlledKeyword is the typeaheadProps.value with initial value - Update typeahead-select.test - Update stories --- src/select/item/SelectItem.tsx | 10 +- src/select/typeahead/TypeaheadSelect.tsx | 30 ++-- .../trigger/TypeheadSelectTrigger.tsx | 5 +- .../typeahead/typeahead-select.test.tsx | 45 ++--- src/tab/Tab.tsx | 20 +-- stories/11-Typeahead.stories.tsx | 169 ++++++++---------- stories/12-Tab.stories.tsx | 5 +- 7 files changed, 130 insertions(+), 154 deletions(-) diff --git a/src/select/item/SelectItem.tsx b/src/select/item/SelectItem.tsx index 8baa3d8..ac18dca 100644 --- a/src/select/item/SelectItem.tsx +++ b/src/select/item/SelectItem.tsx @@ -35,14 +35,8 @@ function SelectItemComponent( ) { const selectState = useSelectContext(); const dispatchSelectStateAction = useSelectDispatchContext(); - const { - onSelect, - value, - focusedOptionIndex, - shouldCloseOnSelect, - options, - isMenuOpen - } = selectState; + const {onSelect, value, focusedOptionIndex, shouldCloseOnSelect, options, isMenuOpen} = + selectState; const optionIndex = options.findIndex((opt) => opt?.id === option?.id); const isSelected = Array.isArray(value) ? Boolean(value.find((currentOption) => currentOption.id === option?.id)) diff --git a/src/select/typeahead/TypeaheadSelect.tsx b/src/select/typeahead/TypeaheadSelect.tsx index d3e61e8..d806616 100644 --- a/src/select/typeahead/TypeaheadSelect.tsx +++ b/src/select/typeahead/TypeaheadSelect.tsx @@ -29,12 +29,10 @@ export interface TypeaheadSelectProps< onSelect: TypeaheadSelectOptionSelectHandler; typeaheadProps: Pick< TypeaheadInputProps, - "id" | "placeholder" | "name" | "onFocus" | "type" + "id" | "placeholder" | "name" | "onFocus" | "type" | "value" | "onQueryChange" >; contentRenderer: (option: T) => React.ReactNode; - onKeywordChange: (value: string) => void; testid?: string; - controlledKeyword?: string; onTagRemove?: (option: Option) => void; selectedOptionLimit?: number; customClassName?: string; @@ -52,7 +50,6 @@ function TypeaheadSelect) { const typeaheadInputRef = useRef(null); const [isMenuOpen, setMenuVisibility] = useState(false); const [computedDropdownOptions, setComputedDropdownOptions] = useState(options); const [shouldFocusOnInput, setShouldFocusOnInput] = useState(false); - const [keyword, setKeyword] = useState(controlledKeyword); const tags = mapOptionsToTagShapes(selectedOptions, contentRenderer); + const shouldDisplayOnlyTags = Boolean( selectedOptionLimit && selectedOptions.length >= selectedOptionLimit ); @@ -142,8 +138,8 @@ function TypeaheadSelect @@ -191,7 +187,7 @@ function TypeaheadSelect) { - if (onTagRemove) { - onTagRemove(tag.context!); + if (onTagRemove && tag.context) { + onTagRemove(tag.context); setShouldFocusOnInput(true); setMenuVisibility(false); - setKeyword(""); + typeaheadProps.onQueryChange(""); } } @@ -215,7 +211,7 @@ function TypeaheadSelect + ", () => { ], selectedOptions: [{id: "1", title: "test"}], onSelect: jest.fn(), - onKeywordChange: jest.fn(), onTagRemove: jest.fn(), typeaheadProps: { placeholder: "test placeholder", - name: "test typeahead" + name: "test typeahead", + value: "", + onQueryChange: jest.fn() }, contentRenderer: (option) => option.title }; @@ -45,12 +46,12 @@ describe("", () => { }); }); - it("should update value on change", () => { + it("should update value on change", async () => { render(); const typeaheadSelect = screen.getByRole("textbox"); - userEvent.type(typeaheadSelect, "test"); + await userEvent.type(typeaheadSelect, "test"); expect(typeaheadSelect).toHaveValue("test"); }); @@ -63,9 +64,12 @@ describe("", () => { expect(typeaheadSelect).toBeDisabled(); }); - it("should set initialValue and remove when set new value", () => { + it("should set initialValue and remove when set new value", async () => { render( - + ); const typeaheadSelectInput = screen.getByTestId( @@ -73,10 +77,9 @@ describe("", () => { ).firstElementChild as HTMLInputElement; expect(typeaheadSelectInput).toHaveValue("initial"); - - typeaheadSelectInput.setSelectionRange(0, typeaheadSelectInput.value.length); - userEvent.type(typeaheadSelectInput, "test"); + await userEvent.clear(typeaheadSelectInput); + await userEvent.type(typeaheadSelectInput, "test"); expect(typeaheadSelectInput).toHaveValue("test"); }); @@ -97,19 +100,19 @@ describe("", () => { expect(container).toContainElement(spinner); }); - it("should render option menu when focused", () => { + it("should render option menu when focused", async () => { render( ); - const dropdownList = screen.getByTestId("test-dropdown-visibility"); + const dropdownList = screen.getByTestId("test-dropdown-visibility-on-focus"); expect(dropdownList).not.toHaveClass("typeahead-select--is-dropdown-menu-open"); - userEvent.click(screen.getByTestId("TypeaheadSelectTrigger")); + await userEvent.click(screen.getAllByRole("button")[0]); expect(dropdownList).toHaveClass("typeahead-select--is-dropdown-menu-open"); }); @@ -127,7 +130,7 @@ describe("", () => { defaultTypeaheadSelectProps.options[0].title ); - userEvent.click(firstOption); + await userEvent.click(firstOption); expect(defaultTypeaheadSelectProps.onSelect).toHaveBeenCalledTimes(1); @@ -135,7 +138,7 @@ describe("", () => { defaultTypeaheadSelectProps.options[1].title ); - userEvent.click(secondOption); + await userEvent.click(secondOption); expect(defaultTypeaheadSelectProps.onSelect).toHaveBeenCalledTimes(2); }); @@ -155,7 +158,7 @@ describe("", () => { defaultTypeaheadSelectProps.options[1].title ); - userEvent.click(secondOption); + await userEvent.click(secondOption); expect(selectedOptionList).not.toContainElement(secondOption); }); @@ -170,7 +173,7 @@ describe("", () => { /> ); - userEvent.click(screen.getByTestId("TypeaheadSelectTrigger")); + await userEvent.click(screen.getByRole("button")); const dropdownList = screen.getByTestId("test-dropdown-visibility"); @@ -178,7 +181,7 @@ describe("", () => { const typeaheadInput = screen.getByRole("textbox"); - userEvent.type(typeaheadInput, "second-dropdown"); + await userEvent.type(typeaheadInput, "second-dropdown"); const searchedOption = await screen.findByText( defaultTypeaheadSelectProps.options[0].title @@ -188,7 +191,7 @@ describe("", () => { fireEvent.focus(searchedOption); - userEvent.click(searchedOption); + await userEvent.click(searchedOption); expect(dropdownList).not.toHaveClass("typeahead-select--is-dropdown-menu-open"); @@ -205,7 +208,7 @@ describe("", () => { /> ); - userEvent.click(screen.getByTestId("TypeaheadSelectTrigger")); + await userEvent.click(screen.getByRole("button")); const dropdownList = screen.getByTestId("test-dropdown-visibility"); @@ -223,7 +226,7 @@ describe("", () => { fireEvent.focus(searchedOption); - userEvent.click(searchedOption); + await userEvent.click(searchedOption); expect(defaultTypeaheadSelectProps.onSelect).toHaveBeenCalledTimes(1); expect(dropdownList).not.toHaveClass("select--is-visible"); diff --git a/src/tab/Tab.tsx b/src/tab/Tab.tsx index 75c1afa..6bb66fa 100644 --- a/src/tab/Tab.tsx +++ b/src/tab/Tab.tsx @@ -27,14 +27,14 @@ interface UncontrolledTabProps { // and initialActiveTabIndex should be undefined type ControlledTabProps = | { - activeTabIndex: number; - onTabChange: (index: number) => void; - initialActiveTabIndex?: number; - } + activeTabIndex: number; + onTabChange: (index: number) => void; + initialActiveTabIndex?: number; + } | { - activeTabIndex?: number; - onTabChange?: (index: number) => void; - }; + activeTabIndex?: number; + onTabChange?: (index: number) => void; + }; export type TabProps = ControlledTabProps & UncontrolledTabProps; @@ -72,9 +72,9 @@ function Tab({
{ children[ - activeTabIndexFromProps === undefined - ? activeTabIndex - : activeTabIndexFromProps + activeTabIndexFromProps === undefined + ? activeTabIndex + : activeTabIndexFromProps ] }
diff --git a/stories/11-Typeahead.stories.tsx b/stories/11-Typeahead.stories.tsx index c388575..e89da6c 100644 --- a/stories/11-Typeahead.stories.tsx +++ b/stories/11-Typeahead.stories.tsx @@ -67,26 +67,22 @@ storiesOf("Typeahead", module).add("Typeahead", () => { options={state.options} selectedOptions={state.selectedOptions} contentRenderer={(option) => option.title} - onSelect={(option) => - setState({ - ...state, - selectedOptions: [...state.selectedOptions, option] - }) - } - onKeywordChange={(keyword) => - setState({ - ...state, - options: filterOptionsByKeyword( - initialState.options, - keyword, - "title" - ) - }) - } + onSelect={handleSelect(state, setState)} onTagRemove={handleRemoveTag(state, setState)} typeaheadProps={{ placeholder: "Select Languages", - name: "language" + name: "language", + value: state.keyword, + onQueryChange: (keyword) => + setState({ + ...state, + options: filterOptionsByKeyword( + initialState.options, + keyword, + "title" + ), + keyword + }) }} /> @@ -101,26 +97,22 @@ storiesOf("Typeahead", module).add("Typeahead", () => { options={state.options} contentRenderer={(option) => option.title} selectedOptions={state.secondSelectedOptions} - onSelect={(option) => - setState({ - ...state, - secondSelectedOptions: [...state.secondSelectedOptions, option] - }) - } - onKeywordChange={(keyword) => - setState({ - ...state, - options: filterOptionsByKeyword( - initialState.options, - keyword, - "title" - ) - }) - } + onSelect={handleSelect(state, setState, "secondSelectedOptions")} onTagRemove={handleRemoveTag(state, setState, "secondSelectedOptions")} typeaheadProps={{ placeholder: "Select Languages", - name: "language" + name: "language", + value: state.keyword, + onQueryChange: (keyword) => + setState({ + ...state, + options: filterOptionsByKeyword( + initialState.options, + keyword, + "title" + ), + keyword + }) }} /> @@ -136,26 +128,22 @@ storiesOf("Typeahead", module).add("Typeahead", () => { options={state.options} contentRenderer={(option) => option.title} selectedOptions={state.secondSelectedOptions} - onKeywordChange={(keyword) => - setState({ - ...state, - options: filterOptionsByKeyword( - initialState.options, - keyword, - "title" - ) - }) - } - onSelect={(option) => - setState({ - ...state, - secondSelectedOptions: [...state.secondSelectedOptions, option] - }) - } + onSelect={handleSelect(state, setState)} onTagRemove={handleRemoveTag(state, setState)} typeaheadProps={{ placeholder: "Select Languages", - name: "language" + name: "language", + onQueryChange: (keyword) => + setState({ + ...state, + options: filterOptionsByKeyword( + initialState.options, + keyword, + "title" + ), + keyword + }), + value: state.keyword }} /> @@ -173,17 +161,13 @@ storiesOf("Typeahead", module).add("Typeahead", () => { options={state.thirdOptions} contentRenderer={(option) => option.title} selectedOptions={state.thirdSelectedOptions} - onSelect={(option) => - setState({ - ...state, - thirdSelectedOptions: [...state.thirdSelectedOptions, option] - }) - } - onKeywordChange={handleAsyncKeywordChange(setState)} + onSelect={handleSelect(state, setState, "thirdSelectedOptions")} onTagRemove={handleRemoveTag(state, setState, "thirdSelectedOptions")} typeaheadProps={{ placeholder: "Select Languages", - name: "language-test" + name: "language-test", + value: state.keyword, + onQueryChange: handleAsyncKeywordChange(setState) }} /> @@ -199,19 +183,13 @@ storiesOf("Typeahead", module).add("Typeahead", () => { options={state.thirdOptions} contentRenderer={(option) => option.title} selectedOptions={state.thirdSelectedOptions} - onSelect={(option) => - setState({ - ...state, - thirdSelectedOptions: [...state.thirdSelectedOptions, option], - keyword: "" - }) - } - onKeywordChange={handleAsyncKeywordChange(setState)} - controlledKeyword={state.keyword} + onSelect={handleSelect(state, setState, "thirdSelectedOptions")} onTagRemove={handleRemoveTag(state, setState, "thirdSelectedOptions")} typeaheadProps={{ placeholder: "Select Languages", - name: "language-test" + name: "language-test", + value: state.keyword, + onQueryChange: handleAsyncKeywordChange(setState) }} /> @@ -224,28 +202,24 @@ storiesOf("Typeahead", module).add("Typeahead", () => { - setState({ - ...state, - selectedOptions: [...state.selectedOptions, option] - }) - } + onSelect={handleSelect(state, setState)} contentRenderer={(option) => option.id} - onKeywordChange={(keyword) => - setState({ - ...state, - options: filterOptionsByKeyword( - modelInitialState.options, - keyword, - "id" - ) - }) - } onTagRemove={handleRemoveTag(state, setState)} typeaheadProps={{ placeholder: "Select Model", name: "model", - type: "number" + type: "number", + value: state.keyword, + onQueryChange: (keyword) => + setState({ + ...state, + options: filterOptionsByKeyword( + modelInitialState.options, + keyword, + "id" + ), + keyword + }) }} /> @@ -255,16 +229,31 @@ storiesOf("Typeahead", module).add("Typeahead", () => { ); - function handleRemoveTag(state, setState, optionsArrayName = "selectedOptions") { - return (tag) => + function handleSelect(state, setState, optionsArrayName = "selectedOptions") { + return (option) => setState({ ...state, - [optionsArrayName]: state[optionsArrayName].filter( - (options) => options.id !== tag.id + [optionsArrayName]: state[optionsArrayName].some( + (selected) => selected.id === option.id ) + ? state[optionsArrayName] + : state[optionsArrayName].push(option) }); } + function handleRemoveTag(state, setState, optionsArrayName = "selectedOptions") { + return (tag) => { + const tagIndex = state[optionsArrayName].findIndex( + (option) => option.id === tag.id + ); + + setState({ + ...state, + [optionsArrayName]: state[optionsArrayName].splice(tagIndex, 1) + }); + }; + } + function handleAsyncKeywordChange(setState) { return async (keyword) => { if (keyword) { diff --git a/stories/12-Tab.stories.tsx b/stories/12-Tab.stories.tsx index 5969e6a..0706010 100644 --- a/stories/12-Tab.stories.tsx +++ b/stories/12-Tab.stories.tsx @@ -49,7 +49,10 @@ storiesOf("Tab", module).add("Tab", () => ( isDisabled={state.index === 0}> Previous Tab -
From 60b77120e2e42ef62cd425bb0813e994e65acc0b Mon Sep 17 00:00:00 2001 From: gulcinuras Date: Tue, 14 May 2024 21:39:57 +0200 Subject: [PATCH 10/12] fix(typeahead-select): Revert changes --- src/select/typeahead/TypeaheadSelect.tsx | 49 ++++-- .../typeahead/typeahead-select.test.tsx | 15 +- .../typeahead/util/typeaheadSelectUtils.ts | 19 ++ src/select/util/selectTypes.ts | 4 +- src/tag/util/tagUtils.ts | 12 +- stories/11-Typeahead.stories.tsx | 165 +++++++----------- stories/utils/typeaheadSelectStoryUtils.ts | 21 --- 7 files changed, 135 insertions(+), 150 deletions(-) create mode 100644 src/select/typeahead/util/typeaheadSelectUtils.ts delete mode 100644 stories/utils/typeaheadSelectStoryUtils.ts diff --git a/src/select/typeahead/TypeaheadSelect.tsx b/src/select/typeahead/TypeaheadSelect.tsx index d806616..849562e 100644 --- a/src/select/typeahead/TypeaheadSelect.tsx +++ b/src/select/typeahead/TypeaheadSelect.tsx @@ -18,6 +18,7 @@ import { } from "../util/selectTypes"; import Select from "../Select"; import TypeheadSelectTrigger from "./trigger/TypeheadSelectTrigger"; +import {filterOptionsByKeyword} from "./util/typeaheadSelectUtils"; import "./_typeahead-select.scss"; @@ -29,13 +30,16 @@ export interface TypeaheadSelectProps< onSelect: TypeaheadSelectOptionSelectHandler; typeaheadProps: Pick< TypeaheadInputProps, - "id" | "placeholder" | "name" | "onFocus" | "type" | "value" | "onQueryChange" + "id" | "placeholder" | "name" | "onFocus" | "type" >; - contentRenderer: (option: T) => React.ReactNode; testid?: string; + onKeywordChange?: (value: string) => void; + initialKeyword?: string; + controlledKeyword?: string; onTagRemove?: (option: Option) => void; selectedOptionLimit?: number; customClassName?: string; + shouldFilterOptionsByKeyword?: boolean; shouldDisplaySelectedOptions?: boolean; isDisabled?: boolean; customSpinner?: React.ReactNode; @@ -51,23 +55,28 @@ function TypeaheadSelect) { const typeaheadInputRef = useRef(null); const [isMenuOpen, setMenuVisibility] = useState(false); const [computedDropdownOptions, setComputedDropdownOptions] = useState(options); const [shouldFocusOnInput, setShouldFocusOnInput] = useState(false); + const [keyword, setKeyword] = useState(initialKeyword); + const inputValue = typeof controlledKeyword === "string" ? controlledKeyword : keyword; - const tags = mapOptionsToTagShapes(selectedOptions, contentRenderer); + const tags = mapOptionsToTagShapes(selectedOptions); const shouldDisplayOnlyTags = Boolean( selectedOptionLimit && selectedOptions.length >= selectedOptionLimit @@ -138,8 +147,8 @@ function TypeaheadSelect @@ -154,7 +163,7 @@ function TypeaheadSelect {computedDropdownOptions.map((option) => ( - {contentRenderer(option)} + {option.title} ))} @@ -187,7 +196,7 @@ function TypeaheadSelect selectedOptions.indexOf(option) < 0 + ); + + setComputedDropdownOptions(filterOptionsByKeyword(unselectedOptions, value)); + } + + if (onKeywordChange) { + onKeywordChange(value); + } + + if (typeof controlledKeyword === "undefined") { + setKeyword(value); } } @@ -211,7 +238,7 @@ function TypeaheadSelect", () => { {id: "2", title: "second-dropdown-option"}, {id: "3", title: "third-dropdown-option"} ], + onKeywordChange: jest.fn(), selectedOptions: [{id: "1", title: "test"}], onSelect: jest.fn(), onTagRemove: jest.fn(), typeaheadProps: { placeholder: "test placeholder", - name: "test typeahead", - value: "", - onQueryChange: jest.fn() - }, - contentRenderer: (option) => option.title + name: "test typeahead" + } }; it("should render correctly", () => { @@ -65,12 +63,7 @@ describe("", () => { }); it("should set initialValue and remove when set new value", async () => { - render( - - ); + render(); const typeaheadSelectInput = screen.getByTestId( `${defaultTypeaheadSelectProps.testid}.search` diff --git a/src/select/typeahead/util/typeaheadSelectUtils.ts b/src/select/typeahead/util/typeaheadSelectUtils.ts new file mode 100644 index 0000000..129bba9 --- /dev/null +++ b/src/select/typeahead/util/typeaheadSelectUtils.ts @@ -0,0 +1,19 @@ +import {TypeaheadSelectOption} from "../../util/selectTypes"; + +function filterOptionsByKeyword( + options: T[], + keyword: string +): T[] { + let filteredOptions = options; + + if (keyword) { + filteredOptions = options.filter( + (option) => + !option.title || option.title.toLowerCase().includes(keyword.toLowerCase()) + ); + } + + return filteredOptions; +} + +export {filterOptionsByKeyword}; diff --git a/src/select/util/selectTypes.ts b/src/select/util/selectTypes.ts index 61edcc1..45dfa5b 100644 --- a/src/select/util/selectTypes.ts +++ b/src/select/util/selectTypes.ts @@ -5,7 +5,9 @@ interface Option { isDisabled?: boolean; } -type TypeaheadSelectOption = Option; +type TypeaheadSelectOption = Option & { + title: string; +}; type SelectItemElement = HTMLLIElement | HTMLDivElement; diff --git a/src/tag/util/tagUtils.ts b/src/tag/util/tagUtils.ts index 8853262..124cb7d 100644 --- a/src/tag/util/tagUtils.ts +++ b/src/tag/util/tagUtils.ts @@ -1,24 +1,20 @@ -import React from "react"; - import {TypeaheadSelectOption} from "../../select/util/selectTypes"; import {TagShape} from "../Tag"; function mapOptionToTagShape( - option: T, - content: React.ReactNode + option: T ): TagShape { return { id: option.id, - content, + content: option.title, context: option }; } function mapOptionsToTagShapes( - options: T[], - contentRenderer: (option: T) => React.ReactNode + options: T[] ) { - return options.map((option) => mapOptionToTagShape(option, contentRenderer(option))); + return options.map(mapOptionToTagShape); } export {mapOptionsToTagShapes}; diff --git a/stories/11-Typeahead.stories.tsx b/stories/11-Typeahead.stories.tsx index e89da6c..619cc23 100644 --- a/stories/11-Typeahead.stories.tsx +++ b/stories/11-Typeahead.stories.tsx @@ -8,7 +8,6 @@ import FormField from "../src/form/field/FormField"; import TypeaheadSelect from "../src/select/typeahead/TypeaheadSelect"; import {TypeaheadSelectOption} from "../src/select/util/selectTypes"; import {Language} from "./utils/constants/select/selectStoryConstants"; -import {filterOptionsByKeyword} from "./utils/typeaheadSelectStoryUtils"; const simulateAPICall = (timeout = 1000) => new Promise((resolve) => setTimeout(resolve, timeout)); @@ -28,11 +27,11 @@ storiesOf("Typeahead", module).add("Typeahead", () => { id: "spanish", title: "Spanish" } - ] as (TypeaheadSelectOption & {title: string})[], + ] as TypeaheadSelectOption[], thirdOptions: [], - selectedOptions: [] as (TypeaheadSelectOption & {title: string})[], - secondSelectedOptions: [] as (TypeaheadSelectOption & {title: string})[], - thirdSelectedOptions: [] as (TypeaheadSelectOption & {title: string})[], + selectedOptions: [] as TypeaheadSelectOption[], + secondSelectedOptions: [] as TypeaheadSelectOption[], + thirdSelectedOptions: [] as TypeaheadSelectOption[], areOptionsFetching: false, keyword: "" }; @@ -40,13 +39,16 @@ storiesOf("Typeahead", module).add("Typeahead", () => { const modelInitialState = { options: [ { - id: "2005" + id: "2005", + title: "2005" }, { - id: "2015" + id: "2015", + title: "2015" }, { - id: "2021" + id: "2021", + title: "2021" } ], thirdOptions: [] as TypeaheadSelectOption[], @@ -64,25 +66,18 @@ storiesOf("Typeahead", module).add("Typeahead", () => { {(state, setState) => ( option.title} - onSelect={handleSelect(state, setState)} + onSelect={(option) => + setState({ + ...state, + selectedOptions: [...state.selectedOptions, option] + }) + } onTagRemove={handleRemoveTag(state, setState)} typeaheadProps={{ placeholder: "Select Languages", - name: "language", - value: state.keyword, - onQueryChange: (keyword) => - setState({ - ...state, - options: filterOptionsByKeyword( - initialState.options, - keyword, - "title" - ), - keyword - }) + name: "language" }} /> @@ -94,25 +89,18 @@ storiesOf("Typeahead", module).add("Typeahead", () => { option.title} + options={initialState.options} selectedOptions={state.secondSelectedOptions} - onSelect={handleSelect(state, setState, "secondSelectedOptions")} + onSelect={(option) => + setState({ + ...state, + secondSelectedOptions: [...state.secondSelectedOptions, option] + }) + } onTagRemove={handleRemoveTag(state, setState, "secondSelectedOptions")} typeaheadProps={{ placeholder: "Select Languages", - name: "language", - value: state.keyword, - onQueryChange: (keyword) => - setState({ - ...state, - options: filterOptionsByKeyword( - initialState.options, - keyword, - "title" - ), - keyword - }) + name: "language" }} /> @@ -125,25 +113,18 @@ storiesOf("Typeahead", module).add("Typeahead", () => { option.title} + options={initialState.options} selectedOptions={state.secondSelectedOptions} - onSelect={handleSelect(state, setState)} + onSelect={(option) => + setState({ + ...state, + secondSelectedOptions: [...state.secondSelectedOptions, option] + }) + } onTagRemove={handleRemoveTag(state, setState)} typeaheadProps={{ placeholder: "Select Languages", - name: "language", - onQueryChange: (keyword) => - setState({ - ...state, - options: filterOptionsByKeyword( - initialState.options, - keyword, - "title" - ), - keyword - }), - value: state.keyword + name: "language" }} /> @@ -157,17 +138,21 @@ storiesOf("Typeahead", module).add("Typeahead", () => { "Select Languages (API Fetch Simulation) - with keyword by TypeaheadSelect" }> option.title} selectedOptions={state.thirdSelectedOptions} - onSelect={handleSelect(state, setState, "thirdSelectedOptions")} + onSelect={(option) => + setState({ + ...state, + thirdSelectedOptions: [...state.thirdSelectedOptions, option] + }) + } + onKeywordChange={handleKeywordChange(setState)} onTagRemove={handleRemoveTag(state, setState, "thirdSelectedOptions")} typeaheadProps={{ placeholder: "Select Languages", - name: "language-test", - value: state.keyword, - onQueryChange: handleAsyncKeywordChange(setState) + name: "language-test" }} /> @@ -179,17 +164,23 @@ storiesOf("Typeahead", module).add("Typeahead", () => { option.title} selectedOptions={state.thirdSelectedOptions} - onSelect={handleSelect(state, setState, "thirdSelectedOptions")} + onSelect={(option) => + setState({ + ...state, + thirdSelectedOptions: [...state.thirdSelectedOptions, option], + keyword: "" + }) + } + onKeywordChange={handleKeywordChange(setState)} + controlledKeyword={state.keyword} onTagRemove={handleRemoveTag(state, setState, "thirdSelectedOptions")} typeaheadProps={{ placeholder: "Select Languages", - name: "language-test", - value: state.keyword, - onQueryChange: handleAsyncKeywordChange(setState) + name: "language-test" }} /> @@ -200,26 +191,19 @@ storiesOf("Typeahead", module).add("Typeahead", () => { {(state, setState) => ( option.id} + onSelect={(option) => + setState({ + ...state, + selectedOptions: [...state.selectedOptions, option] + }) + } onTagRemove={handleRemoveTag(state, setState)} typeaheadProps={{ placeholder: "Select Model", name: "model", - type: "number", - value: state.keyword, - onQueryChange: (keyword) => - setState({ - ...state, - options: filterOptionsByKeyword( - modelInitialState.options, - keyword, - "id" - ), - keyword - }) + type: "number" }} /> @@ -229,32 +213,17 @@ storiesOf("Typeahead", module).add("Typeahead", () => { ); - function handleSelect(state, setState, optionsArrayName = "selectedOptions") { - return (option) => - setState({ - ...state, - [optionsArrayName]: state[optionsArrayName].some( - (selected) => selected.id === option.id - ) - ? state[optionsArrayName] - : state[optionsArrayName].push(option) - }); - } - function handleRemoveTag(state, setState, optionsArrayName = "selectedOptions") { - return (tag) => { - const tagIndex = state[optionsArrayName].findIndex( - (option) => option.id === tag.id - ); - + return (tag) => setState({ ...state, - [optionsArrayName]: state[optionsArrayName].splice(tagIndex, 1) + [optionsArrayName]: state[optionsArrayName].filter( + (options) => options.id !== tag.id + ) }); - }; } - function handleAsyncKeywordChange(setState) { + function handleKeywordChange(setState) { return async (keyword) => { if (keyword) { setState((prevState) => ({ diff --git a/stories/utils/typeaheadSelectStoryUtils.ts b/stories/utils/typeaheadSelectStoryUtils.ts deleted file mode 100644 index 0d1fd5e..0000000 --- a/stories/utils/typeaheadSelectStoryUtils.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {TypeaheadSelectOption} from "../../src/select/util/selectTypes"; - -function filterOptionsByKeyword( - options: T[], - keyword: string, - filterBy: keyof Pick | "title" -): T[] { - let filteredOptions = options; - - if (keyword) { - filteredOptions = options.filter((option) => { - const optionFilterValue = option[filterBy] as string; - - return optionFilterValue.toLowerCase().includes(keyword.toLowerCase()); - }); - } - - return filteredOptions; -} - -export {filterOptionsByKeyword}; From a4a3b7536172a328061d1c699dbe746403e2342b Mon Sep 17 00:00:00 2001 From: gulcinuras Date: Tue, 14 May 2024 21:41:01 +0200 Subject: [PATCH 11/12] fix(typeahead-select/test): Fix initialKeyword test case --- src/select/typeahead/typeahead-select.test.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/select/typeahead/typeahead-select.test.tsx b/src/select/typeahead/typeahead-select.test.tsx index 314661f..a51113d 100644 --- a/src/select/typeahead/typeahead-select.test.tsx +++ b/src/select/typeahead/typeahead-select.test.tsx @@ -63,7 +63,9 @@ describe("", () => { }); it("should set initialValue and remove when set new value", async () => { - render(); + render( + + ); const typeaheadSelectInput = screen.getByTestId( `${defaultTypeaheadSelectProps.testid}.search` From 878206f093cc0b1f78d47b224bcca3da092534ef Mon Sep 17 00:00:00 2001 From: gulcinuras Date: Wed, 15 May 2024 17:49:48 +0200 Subject: [PATCH 12/12] fix(typeahead-select/test): Follow order of priority as much as possible when calling queries --- .../typeahead/typeahead-select.test.tsx | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/src/select/typeahead/typeahead-select.test.tsx b/src/select/typeahead/typeahead-select.test.tsx index a51113d..c8b1f28 100644 --- a/src/select/typeahead/typeahead-select.test.tsx +++ b/src/select/typeahead/typeahead-select.test.tsx @@ -67,9 +67,9 @@ describe("", () => { ); - const typeaheadSelectInput = screen.getByTestId( - `${defaultTypeaheadSelectProps.testid}.search` - ).firstElementChild as HTMLInputElement; + const typeaheadSelectInput = screen.getByPlaceholderText( + defaultTypeaheadSelectProps.typeaheadProps.placeholder! + ); expect(typeaheadSelectInput).toHaveValue("initial"); @@ -121,17 +121,13 @@ describe("", () => { /> ); - const firstOption = await screen.findByText( - defaultTypeaheadSelectProps.options[0].title - ); + const firstOption = screen.getByText(defaultTypeaheadSelectProps.options[0].title); await userEvent.click(firstOption); expect(defaultTypeaheadSelectProps.onSelect).toHaveBeenCalledTimes(1); - const secondOption = await screen.findByText( - defaultTypeaheadSelectProps.options[1].title - ); + const secondOption = screen.getByText(defaultTypeaheadSelectProps.options[1].title); await userEvent.click(secondOption); @@ -149,9 +145,7 @@ describe("", () => { const selectedOptionList = screen.getByRole("list"); - const secondOption = await screen.findByText( - defaultTypeaheadSelectProps.options[1].title - ); + const secondOption = screen.getByText(defaultTypeaheadSelectProps.options[1].title); await userEvent.click(secondOption); @@ -170,7 +164,7 @@ describe("", () => { await userEvent.click(screen.getByRole("button")); - const dropdownList = screen.getByTestId("test-dropdown-visibility"); + const dropdownList = screen.getByRole("listbox"); expect(dropdownList).toHaveClass("typeahead-select--is-dropdown-menu-open"); @@ -178,9 +172,7 @@ describe("", () => { await userEvent.type(typeaheadInput, "second-dropdown"); - const searchedOption = await screen.findByText( - defaultTypeaheadSelectProps.options[0].title - ); + const searchedOption = screen.getByText(defaultTypeaheadSelectProps.options[0].title); expect(dropdownList).toContainElement(searchedOption); @@ -205,7 +197,7 @@ describe("", () => { await userEvent.click(screen.getByRole("button")); - const dropdownList = screen.getByTestId("test-dropdown-visibility"); + const dropdownList = screen.getByRole("listbox"); expect(dropdownList).toHaveClass("typeahead-select--is-dropdown-menu-open"); @@ -213,9 +205,7 @@ describe("", () => { userEvent.type(typeaheadInput, "second-dropdown"); - const searchedOption = await screen.findByText( - defaultTypeaheadSelectProps.options[1].title - ); + const searchedOption = screen.getByText(defaultTypeaheadSelectProps.options[1].title); expect(dropdownList).toContainElement(searchedOption);