From 96e2505ee429a7c840763deec3d69d99fc7f1d0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Thu, 27 Jun 2024 09:22:59 -0300 Subject: [PATCH 01/12] feat(design system): rename select multiple to advanced everywhere --- .../components/form/form-group/FormGroup.tsx | 4 +- .../form-element/FormSelectAdvanced.tsx | 15 +++++++ .../form-element/FormSelectMultiple.tsx | 15 ------- .../SelectAdvanced.module.scss} | 8 ++-- .../SelectAdvanced.tsx} | 28 ++++++------ .../SelectAdvancedMenu.tsx} | 10 ++--- .../SelectAdvancedToggle.tsx} | 16 +++---- .../selectAdvancedReducer.ts} | 22 +++++----- .../useIsFirstRender.ts | 0 .../utils.ts | 0 packages/design-system/src/lib/index.ts | 2 +- .../src/lib/stories/form/Form.stories.tsx | 8 ++-- .../SelectAdvanced.stories.tsx} | 22 +++++----- .../FormGroupSelectMultiple.spec.tsx | 6 +-- .../SelectAdvanced.spec.tsx} | 44 +++++++++---------- .../selectAdvancedReducer.spec.tsx} | 0 16 files changed, 100 insertions(+), 100 deletions(-) create mode 100644 packages/design-system/src/lib/components/form/form-group/form-element/FormSelectAdvanced.tsx delete mode 100644 packages/design-system/src/lib/components/form/form-group/form-element/FormSelectMultiple.tsx rename packages/design-system/src/lib/components/{select-multiple/SelectMultiple.module.scss => select-advanced/SelectAdvanced.module.scss} (95%) rename packages/design-system/src/lib/components/{select-multiple/SelectMultiple.tsx => select-advanced/SelectAdvanced.tsx} (83%) rename packages/design-system/src/lib/components/{select-multiple/SelectMultipleMenu.tsx => select-advanced/SelectAdvancedMenu.tsx} (93%) rename packages/design-system/src/lib/components/{select-multiple/SelectMultipleToggle.tsx => select-advanced/SelectAdvancedToggle.tsx} (82%) rename packages/design-system/src/lib/components/{select-multiple/selectMultipleReducer.ts => select-advanced/selectAdvancedReducer.ts} (84%) rename packages/design-system/src/lib/components/{select-multiple => select-advanced}/useIsFirstRender.ts (100%) rename packages/design-system/src/lib/components/{select-multiple => select-advanced}/utils.ts (100%) rename packages/design-system/src/lib/stories/{select-multiple/SelectMultiple.stories.tsx => select-advanced/SelectAdvanced.stories.tsx} (58%) rename packages/design-system/tests/component/{select-multiple/SelectMultiple.spec.tsx => select-advanced/SelectAdvanced.spec.tsx} (95%) rename packages/design-system/tests/component/{select-multiple/selectMultipleReducer.spec.tsx => select-advanced/selectAdvancedReducer.spec.tsx} (100%) diff --git a/packages/design-system/src/lib/components/form/form-group/FormGroup.tsx b/packages/design-system/src/lib/components/form/form-group/FormGroup.tsx index 5248609b0..a4261d913 100644 --- a/packages/design-system/src/lib/components/form/form-group/FormGroup.tsx +++ b/packages/design-system/src/lib/components/form/form-group/FormGroup.tsx @@ -9,7 +9,7 @@ import { Col, ColProps } from '../../grid/Col' import { Row } from '../../grid/Row' import { FormCheckbox } from './form-element/FormCheckbox' import { FormFeedback } from './form-element/FormFeedback' -import { FormSelectMultiple } from './form-element/FormSelectMultiple' +import { FormSelectAdvanced } from './form-element/FormSelectAdvanced' interface FormGroupProps extends ColProps { as?: typeof Col | typeof Row @@ -27,7 +27,7 @@ function FormGroup({ as = Row, controlId, children, ...props }: PropsWithChildre FormGroup.Label = FormLabel FormGroup.Input = FormInput FormGroup.Select = FormSelect -FormGroup.SelectMultiple = FormSelectMultiple +FormGroup.SelectAdvanced = FormSelectAdvanced FormGroup.TextArea = FormTextArea FormGroup.Text = FormText FormGroup.Checkbox = FormCheckbox diff --git a/packages/design-system/src/lib/components/form/form-group/form-element/FormSelectAdvanced.tsx b/packages/design-system/src/lib/components/form/form-group/form-element/FormSelectAdvanced.tsx new file mode 100644 index 000000000..e4c0ba87a --- /dev/null +++ b/packages/design-system/src/lib/components/form/form-group/form-element/FormSelectAdvanced.tsx @@ -0,0 +1,15 @@ +import { PropsWithChildren, forwardRef } from 'react' +import { SelectAdvanced, SelectAdvancedProps } from '../../../select-advanced/SelectAdvanced' + +export interface FormSelectAdvancedProps extends SelectAdvancedProps { + inputButtonId: string + isInvalid?: boolean +} + +export const FormSelectAdvanced = forwardRef( + ({ ...props }: PropsWithChildren, ref) => { + return } {...props} /> + } +) + +FormSelectAdvanced.displayName = 'FormSelectAdvanced' diff --git a/packages/design-system/src/lib/components/form/form-group/form-element/FormSelectMultiple.tsx b/packages/design-system/src/lib/components/form/form-group/form-element/FormSelectMultiple.tsx deleted file mode 100644 index b8296f6b2..000000000 --- a/packages/design-system/src/lib/components/form/form-group/form-element/FormSelectMultiple.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { PropsWithChildren, forwardRef } from 'react' -import { SelectMultiple, SelectMultipleProps } from '../../../select-multiple/SelectMultiple' - -export interface FormSelectMultipleProps extends SelectMultipleProps { - inputButtonId: string - isInvalid?: boolean -} - -export const FormSelectMultiple = forwardRef( - ({ ...props }: PropsWithChildren, ref) => { - return } {...props} /> - } -) - -FormSelectMultiple.displayName = 'FormSelectMultiple' diff --git a/packages/design-system/src/lib/components/select-multiple/SelectMultiple.module.scss b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.module.scss similarity index 95% rename from packages/design-system/src/lib/components/select-multiple/SelectMultiple.module.scss rename to packages/design-system/src/lib/components/select-advanced/SelectAdvanced.module.scss index 75913ce33..49d8b3610 100644 --- a/packages/design-system/src/lib/components/select-multiple/SelectMultiple.module.scss +++ b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.module.scss @@ -3,11 +3,11 @@ @import 'src/lib/assets/styles/design-tokens/typography.module'; :root { - --select-multiple-menu-max-height: 300px; + --select-advanced-menu-max-height: 300px; --toggle-padding: 6px 36px 6px 12px; } -.select-multiple-toggle { +.select-advanced-toggle { position: relative; display: grid; background-color: #fff; @@ -108,10 +108,10 @@ } } -.select-multiple-menu { +.select-advanced-menu { width: 100%; max-width: 100%; - max-height: var(--select-multiple-menu-max-height); + max-height: var(--select-advanced-menu-max-height); padding-top: 0; overflow-x: hidden; overflow-y: auto; diff --git a/packages/design-system/src/lib/components/select-multiple/SelectMultiple.tsx b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx similarity index 83% rename from packages/design-system/src/lib/components/select-multiple/SelectMultiple.tsx rename to packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx index bf1c021ae..86a851bb6 100644 --- a/packages/design-system/src/lib/components/select-multiple/SelectMultiple.tsx +++ b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx @@ -1,22 +1,22 @@ import { ForwardedRef, forwardRef, useEffect, useId, useReducer } from 'react' import { Dropdown as DropdownBS } from 'react-bootstrap' import { - selectMultipleInitialState, - selectMultipleReducer, + selectAdvancedInitialState, + selectAdvancedReducer, selectOption, removeOption, selectAllOptions, deselectAllOptions, searchOptions -} from './selectMultipleReducer' -import { SelectMultipleToggle } from './SelectMultipleToggle' -import { SelectMultipleMenu } from './SelectMultipleMenu' +} from './selectAdvancedReducer' +import { SelectAdvancedToggle } from './SelectAdvancedToggle' +import { SelectAdvancedMenu } from './SelectAdvancedMenu' import { debounce } from './utils' import { useIsFirstRender } from './useIsFirstRender' export const SELECT_MENU_SEARCH_DEBOUNCE_TIME = 400 -export interface SelectMultipleProps { +export interface SelectAdvancedProps { options: string[] onChange?: (selectedOptions: string[]) => void defaultValue?: string[] @@ -26,7 +26,7 @@ export interface SelectMultipleProps { inputButtonId?: string } -export const SelectMultiple = forwardRef( +export const SelectAdvanced = forwardRef( ( { options, @@ -36,13 +36,13 @@ export const SelectMultiple = forwardRef( isDisabled = false, isInvalid = false, inputButtonId - }: SelectMultipleProps, + }: SelectAdvancedProps, ref: ForwardedRef ) => { const [{ selectedOptions, filteredOptions, searchValue }, dispatch] = useReducer( - selectMultipleReducer, + selectAdvancedReducer, { - ...selectMultipleInitialState, + ...selectAdvancedInitialState, options: options, selectedOptions: defaultValue || [] } @@ -54,7 +54,7 @@ export const SelectMultiple = forwardRef( if (!isFirstRender && onChange) { onChange(selectedOptions) } - }, [selectedOptions]) + }, [selectedOptions, isFirstRender, onChange]) const handleSearch = debounce((e: React.ChangeEvent): void => { const { value } = e.target @@ -83,7 +83,7 @@ export const SelectMultiple = forwardRef( return ( - - { +}: SelectAdvancedMenuProps) => { const searchInputControlID = useId() const toggleAllControlID = useId() @@ -42,7 +42,7 @@ export const SelectMultipleMenu = ({ void isInvalid?: boolean @@ -12,7 +12,7 @@ interface SelectMultipleToggleProps { menuId: string } -export const SelectMultipleToggle = forwardRef( +export const SelectAdvancedToggle = forwardRef( ( { selectedOptions, @@ -21,12 +21,12 @@ export const SelectMultipleToggle = forwardRef( isDisabled, inputButtonId, menuId - }: SelectMultipleToggleProps, + }: SelectAdvancedToggleProps, ref: ForwardedRef ) => { return (
+ className={`${styles['select-advanced-toggle']} ${isDisabled ? styles['disabled'] : ''}`}> -
+
{selectedOptions.length > 0 ? (
{ switch (action.type) { case 'SELECT_OPTION': @@ -80,25 +80,25 @@ export const selectMultipleReducer = ( } } -export const selectOption = /* istanbul ignore next */ (option: string): SelectMultipleActions => ({ +export const selectOption = /* istanbul ignore next */ (option: string): SelectAdvancedActions => ({ type: 'SELECT_OPTION', payload: option }) -export const removeOption = /* istanbul ignore next */ (option: string): SelectMultipleActions => ({ +export const removeOption = /* istanbul ignore next */ (option: string): SelectAdvancedActions => ({ type: 'REMOVE_OPTION', payload: option }) -export const selectAllOptions = /* istanbul ignore next */ (): SelectMultipleActions => ({ +export const selectAllOptions = /* istanbul ignore next */ (): SelectAdvancedActions => ({ type: 'SELECT_ALL_OPTIONS' }) -export const deselectAllOptions = /* istanbul ignore next */ (): SelectMultipleActions => ({ +export const deselectAllOptions = /* istanbul ignore next */ (): SelectAdvancedActions => ({ type: 'DESELECT_ALL_OPTIONS' }) -export const searchOptions = /* istanbul ignore next */ (value: string): SelectMultipleActions => ({ +export const searchOptions = /* istanbul ignore next */ (value: string): SelectAdvancedActions => ({ type: 'SEARCH', payload: value }) diff --git a/packages/design-system/src/lib/components/select-multiple/useIsFirstRender.ts b/packages/design-system/src/lib/components/select-advanced/useIsFirstRender.ts similarity index 100% rename from packages/design-system/src/lib/components/select-multiple/useIsFirstRender.ts rename to packages/design-system/src/lib/components/select-advanced/useIsFirstRender.ts diff --git a/packages/design-system/src/lib/components/select-multiple/utils.ts b/packages/design-system/src/lib/components/select-advanced/utils.ts similarity index 100% rename from packages/design-system/src/lib/components/select-multiple/utils.ts rename to packages/design-system/src/lib/components/select-advanced/utils.ts diff --git a/packages/design-system/src/lib/index.ts b/packages/design-system/src/lib/index.ts index fbac72d65..d61a22c79 100644 --- a/packages/design-system/src/lib/index.ts +++ b/packages/design-system/src/lib/index.ts @@ -24,6 +24,6 @@ export { IconName } from './components/icon/IconName' export { Tooltip } from './components/tooltip/Tooltip' export { Pagination } from './components/pagination/Pagination' export { RequiredInputSymbol } from './components/form/required-input-symbol/RequiredInputSymbol' -export { SelectMultiple } from './components/select-multiple/SelectMultiple' +export { SelectAdvanced } from './components/select-advanced/SelectAdvanced' export { Card } from './components/card/Card' export { ProgressBar } from './components/progress-bar/ProgressBar' diff --git a/packages/design-system/src/lib/stories/form/Form.stories.tsx b/packages/design-system/src/lib/stories/form/Form.stories.tsx index 813f23581..8dc683701 100644 --- a/packages/design-system/src/lib/stories/form/Form.stories.tsx +++ b/packages/design-system/src/lib/stories/form/Form.stories.tsx @@ -212,17 +212,17 @@ export const Select: Story = { ) } -export const SelectMultiple: Story = { +export const SelectAdvanced: Story = { render: () => (
- + Hobbies - diff --git a/packages/design-system/src/lib/stories/select-multiple/SelectMultiple.stories.tsx b/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx similarity index 58% rename from packages/design-system/src/lib/stories/select-multiple/SelectMultiple.stories.tsx rename to packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx index 94d5c3120..30c469c03 100644 --- a/packages/design-system/src/lib/stories/select-multiple/SelectMultiple.stories.tsx +++ b/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx @@ -1,19 +1,19 @@ import type { Meta, StoryObj } from '@storybook/react' -import { SelectMultiple } from '../../components/select-multiple/SelectMultiple' +import { SelectAdvanced } from '../../components/select-advanced/SelectAdvanced' /** * ## Description - * The select multiple component is a user interface element that allows users to select multiple options from a list of items. + * The select advanced component is a user interface element that allows users to select one or multiple options from a list of items. * They can also search for items in the list, select all items, and clear the selection. */ -const meta: Meta = { - title: 'Select Multiple', - component: SelectMultiple, +const meta: Meta = { + title: 'Select Advanced', + component: SelectAdvanced, tags: ['autodocs'] } export default meta -type Story = StoryObj +type Story = StoryObj const exampleOptions = [ 'Agricultural Sciences', @@ -33,11 +33,11 @@ const exampleOptions = [ ] export const Default: Story = { - render: () => + render: () => console.log(a)} /> } export const WithDefaultValues: Story = { render: () => ( - @@ -45,13 +45,13 @@ export const WithDefaultValues: Story = { } export const NotSearchable: Story = { - render: () => + render: () => } export const Invalid: Story = { - render: () => + render: () => } export const Disabled: Story = { - render: () => + render: () => } diff --git a/packages/design-system/tests/component/form/form-group/FormGroupSelectMultiple.spec.tsx b/packages/design-system/tests/component/form/form-group/FormGroupSelectMultiple.spec.tsx index e54c5d6a0..94008d3c6 100644 --- a/packages/design-system/tests/component/form/form-group/FormGroupSelectMultiple.spec.tsx +++ b/packages/design-system/tests/component/form/form-group/FormGroupSelectMultiple.spec.tsx @@ -1,11 +1,11 @@ import { FormGroup } from '../../../../src/lib/components/form/form-group/FormGroup' -describe('FormSelectMultiple', () => { +describe('FormSelectAdvanced', () => { it('renders without error', () => { cy.mount( Hobbies - @@ -19,7 +19,7 @@ describe('FormSelectMultiple', () => { cy.mount( Hobbies - diff --git a/packages/design-system/tests/component/select-multiple/SelectMultiple.spec.tsx b/packages/design-system/tests/component/select-advanced/SelectAdvanced.spec.tsx similarity index 95% rename from packages/design-system/tests/component/select-multiple/SelectMultiple.spec.tsx rename to packages/design-system/tests/component/select-advanced/SelectAdvanced.spec.tsx index e8363f8c3..a8974a802 100644 --- a/packages/design-system/tests/component/select-multiple/SelectMultiple.spec.tsx +++ b/packages/design-system/tests/component/select-advanced/SelectAdvanced.spec.tsx @@ -1,12 +1,12 @@ import { - SELECT_MENU_SEARCH_DEBOUNCE_TIME, - SelectMultiple -} from '../../../src/lib/components/select-multiple/SelectMultiple' + SelectAdvanced, + SELECT_MENU_SEARCH_DEBOUNCE_TIME +} from '../../../src/lib/components/select-advanced/SelectAdvanced' -describe('SelectMultiple', () => { +describe('SelectAdvanced', () => { it('should render correctly', () => { cy.mount( - ) @@ -16,7 +16,7 @@ describe('SelectMultiple', () => { it('should render correct options', () => { cy.mount( - ) @@ -33,7 +33,7 @@ describe('SelectMultiple', () => { it('should render with default values', () => { cy.mount( - @@ -47,7 +47,7 @@ describe('SelectMultiple', () => { const onChange = cy.stub().as('onChange') cy.mount( - @@ -63,7 +63,7 @@ describe('SelectMultiple', () => { const onChange = cy.stub().as('onChange') cy.mount( - { const onChange = cy.stub().as('onChange') cy.mount( - { it('should select an option and be shown as selected both in the menu as well as in the selected options', () => { cy.mount( - ) @@ -110,7 +110,7 @@ describe('SelectMultiple', () => { it('should remove a selected option by clicking on an item in the selected options', () => { cy.mount( - ) @@ -126,7 +126,7 @@ describe('SelectMultiple', () => { it('selects all options', () => { cy.mount( - ) @@ -156,7 +156,7 @@ describe('SelectMultiple', () => { it('deselects all options', () => { cy.mount( - ) @@ -178,7 +178,7 @@ describe('SelectMultiple', () => { it('should select all filtered options', () => { cy.mount( - ) @@ -213,7 +213,7 @@ describe('SelectMultiple', () => { it('should unselect only filtered options', () => { cy.mount( - ) @@ -256,7 +256,7 @@ describe('SelectMultiple', () => { it('should show correct filtered options when searching for a value', () => { cy.mount( - ) @@ -274,7 +274,7 @@ describe('SelectMultiple', () => { it('should debounce the search input correctly', () => { cy.mount( - ) @@ -306,7 +306,7 @@ describe('SelectMultiple', () => { it('should show count of selected options when isSearchable is false', () => { cy.mount( - @@ -321,7 +321,7 @@ describe('SelectMultiple', () => { it('should show No Options Found and toggle all chebox be disabled when search does not match any option', () => { cy.mount( - ) @@ -335,7 +335,7 @@ describe('SelectMultiple', () => { it('should be disabled when isDisabled is true', () => { cy.mount( - @@ -346,7 +346,7 @@ describe('SelectMultiple', () => { it('should be invalid when isInvalid is true', () => { cy.mount( - diff --git a/packages/design-system/tests/component/select-multiple/selectMultipleReducer.spec.tsx b/packages/design-system/tests/component/select-advanced/selectAdvancedReducer.spec.tsx similarity index 100% rename from packages/design-system/tests/component/select-multiple/selectMultipleReducer.spec.tsx rename to packages/design-system/tests/component/select-advanced/selectAdvancedReducer.spec.tsx From 6dfb0dd00120accdcdf10744b2208ea9bb267aa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Thu, 27 Jun 2024 15:53:19 -0300 Subject: [PATCH 02/12] feat(design system): Select Advanced single or multiple --- .../SelectAdvanced.module.scss | 12 +- .../select-advanced/SelectAdvanced.tsx | 84 ++++++++---- .../select-advanced/SelectAdvancedMenu.tsx | 106 +++++++++------ .../select-advanced/SelectAdvancedToggle.tsx | 53 +++++--- .../select-advanced/selectAdvancedReducer.ts | 124 ++++++++++++++---- .../SelectAdvanced.stories.tsx | 34 +++-- 6 files changed, 299 insertions(+), 114 deletions(-) diff --git a/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.module.scss b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.module.scss index 49d8b3610..b7d766616 100644 --- a/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.module.scss +++ b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.module.scss @@ -77,7 +77,7 @@ padding: var(--toggle-padding); pointer-events: none; - .selected-options-container { + .multiple-selected-options-container { display: inline-flex; flex: 1; flex-wrap: wrap; @@ -105,6 +105,10 @@ } } } + + .single-selected-option { + margin: 0; + } } } @@ -156,4 +160,10 @@ } } } + + .option-item-not-multiple { + margin-bottom: 0.125rem; + cursor: pointer; + transition: background-color 0.1s ease-in-out; + } } diff --git a/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx index 86a851bb6..b3b845a7a 100644 --- a/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx +++ b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx @@ -1,63 +1,89 @@ import { ForwardedRef, forwardRef, useEffect, useId, useReducer } from 'react' import { Dropdown as DropdownBS } from 'react-bootstrap' import { - selectAdvancedInitialState, selectAdvancedReducer, selectOption, removeOption, selectAllOptions, deselectAllOptions, - searchOptions + searchOptions, + getSelectAdvancedInitialState } from './selectAdvancedReducer' import { SelectAdvancedToggle } from './SelectAdvancedToggle' import { SelectAdvancedMenu } from './SelectAdvancedMenu' import { debounce } from './utils' import { useIsFirstRender } from './useIsFirstRender' +export const DEFAULT_LOCALES = { + select: 'Select...' +} + export const SELECT_MENU_SEARCH_DEBOUNCE_TIME = 400 -export interface SelectAdvancedProps { - options: string[] - onChange?: (selectedOptions: string[]) => void - defaultValue?: string[] - isSearchable?: boolean - isDisabled?: boolean - isInvalid?: boolean - inputButtonId?: string -} +export type SelectAdvancedProps = + | { + isMultiple?: false + initialOptions: string[] + onChange?: (selected: string) => void + defaultValue?: string + isSearchable?: boolean + isDisabled?: boolean + isInvalid?: boolean + inputButtonId?: string + locales?: { + select?: string + } + } + | { + isMultiple: true + initialOptions: string[] + onChange?: (selected: string[]) => void + defaultValue?: string[] + isSearchable?: boolean + isDisabled?: boolean + isInvalid?: boolean + inputButtonId?: string + locales?: { + select?: string + } + } export const SelectAdvanced = forwardRef( ( { - options, + initialOptions, onChange, defaultValue, + isMultiple, isSearchable = true, isDisabled = false, isInvalid = false, - inputButtonId + inputButtonId, + locales }: SelectAdvancedProps, ref: ForwardedRef ) => { - const [{ selectedOptions, filteredOptions, searchValue }, dispatch] = useReducer( + const dynamicInitialOptions = isMultiple + ? initialOptions + : [locales?.select ?? DEFAULT_LOCALES.select, ...initialOptions] + + const [{ selected, filteredOptions, searchValue, options }, dispatch] = useReducer( selectAdvancedReducer, - { - ...selectAdvancedInitialState, - options: options, - selectedOptions: defaultValue || [] - } + getSelectAdvancedInitialState(Boolean(isMultiple), dynamicInitialOptions, defaultValue) ) + const isFirstRender = useIsFirstRender() const menuId = useId() useEffect(() => { if (!isFirstRender && onChange) { - onChange(selectedOptions) + isMultiple ? onChange(selected as string[]) : onChange(selected as string) } - }, [selectedOptions, isFirstRender, onChange]) + }, [isMultiple, selected, isFirstRender, onChange]) const handleSearch = debounce((e: React.ChangeEvent): void => { const { value } = e.target + console.log({ value }) dispatch(searchOptions(value)) }, SELECT_MENU_SEARCH_DEBOUNCE_TIME) @@ -71,6 +97,13 @@ export const SelectAdvanced = forwardRef( } } + const handleClickOption = (option: string): void => { + if ((selected as string) === option) { + return + } + dispatch(selectOption(option)) + } + const handleRemoveSelectedOption = (option: string): void => dispatch(removeOption(option)) const handleToggleAllOptions = (e: React.ChangeEvent): void => { @@ -84,24 +117,29 @@ export const SelectAdvanced = forwardRef( return ( ) diff --git a/packages/design-system/src/lib/components/select-advanced/SelectAdvancedMenu.tsx b/packages/design-system/src/lib/components/select-advanced/SelectAdvancedMenu.tsx index b05bf423a..e9774d259 100644 --- a/packages/design-system/src/lib/components/select-advanced/SelectAdvancedMenu.tsx +++ b/packages/design-system/src/lib/components/select-advanced/SelectAdvancedMenu.tsx @@ -3,27 +3,33 @@ import { Dropdown as DropdownBS, Form as FormBS } from 'react-bootstrap' import styles from './SelectAdvanced.module.scss' interface SelectAdvancedMenuProps { + isMultiple: boolean options: string[] - selectedOptions: string[] + selected: string | string[] filteredOptions: string[] searchValue: string handleToggleAllOptions: (e: React.ChangeEvent) => void handleSearch: (e: React.ChangeEvent) => void handleCheck: (e: React.ChangeEvent) => void + handleClickOption: (option: string) => void isSearchable: boolean menuId: string + selectWord: string } export const SelectAdvancedMenu = ({ + isMultiple, options, - selectedOptions, + selected, filteredOptions, searchValue, handleToggleAllOptions, handleSearch, handleCheck, + handleClickOption, isSearchable, - menuId + menuId, + selectWord }: SelectAdvancedMenuProps) => { const searchInputControlID = useId() const toggleAllControlID = useId() @@ -34,8 +40,8 @@ export const SelectAdvancedMenu = ({ const allOptionsShownAreSelected = !noOptionsFound ? filteredOptions.length > 0 - ? filteredOptions.every((option) => selectedOptions.includes(option)) - : options.every((option) => selectedOptions.includes(option)) + ? filteredOptions.every((option) => selected.includes(option)) + : options.every((option) => selected.includes(option)) : false return ( @@ -53,43 +59,65 @@ export const SelectAdvancedMenu = ({ } ] }}> - - - {isSearchable ? ( - - ) : ( -

{selectedOptions.length} selected

- )} -
- - {!noOptionsFound && - menuOptions.map((option) => ( - + {(isMultiple || isSearchable) && ( + + {isMultiple && ( - - ))} + )} + + {isSearchable && ( + + )} + {isMultiple && !isSearchable && ( +

{selected.length} selected

+ )} + + )} + + {!noOptionsFound && + menuOptions.map((option) => { + if (!isMultiple) { + return ( + handleClickOption(option === selectWord ? '' : option)} + active={option !== selectWord ? selected === option : selected === ''} + id={`check-item-${option}`}> + {option} + + ) + } + + return ( + + + + ) + })} {noOptionsFound && ( diff --git a/packages/design-system/src/lib/components/select-advanced/SelectAdvancedToggle.tsx b/packages/design-system/src/lib/components/select-advanced/SelectAdvancedToggle.tsx index 590bd7e70..a81524b4c 100644 --- a/packages/design-system/src/lib/components/select-advanced/SelectAdvancedToggle.tsx +++ b/packages/design-system/src/lib/components/select-advanced/SelectAdvancedToggle.tsx @@ -3,24 +3,28 @@ import { Dropdown as DropdownBS, Button as ButtonBS } from 'react-bootstrap' import { X as CloseIcon } from 'react-bootstrap-icons' import styles from './SelectAdvanced.module.scss' -interface SelectAdvancedToggleProps { - selectedOptions: string[] +type SelectAdvancedToggleProps = { + isMultiple: boolean + selected: string | string[] handleRemoveSelectedOption: (option: string) => void isInvalid?: boolean isDisabled?: boolean inputButtonId?: string menuId: string + selectWord: string } export const SelectAdvancedToggle = forwardRef( ( { - selectedOptions, + isMultiple, + selected, handleRemoveSelectedOption, isInvalid, isDisabled, inputButtonId, - menuId + menuId, + selectWord }: SelectAdvancedToggleProps, ref: ForwardedRef ) => { @@ -42,27 +46,36 @@ export const SelectAdvancedToggle = forwardRef( }`} />
- {selectedOptions.length > 0 ? ( + {selected.length > 0 ? (
- {selectedOptions.map((selectedOption) => ( -
( +
e.stopPropagation()} + key={`selected-option-${selectedValue}`}> + {selectedValue} + handleRemoveSelectedOption(selectedValue)}> + + +
+ )) + ) : ( +

e.stopPropagation()} - key={`selected-option-${selectedOption}`}> - {selectedOption} - handleRemoveSelectedOption(selectedOption)}> - - -

- ))} + key={`selected-option-${selected as string}`}> + {selected} +

+ )}
) : ( - 'Select...' + selectWord )}
diff --git a/packages/design-system/src/lib/components/select-advanced/selectAdvancedReducer.ts b/packages/design-system/src/lib/components/select-advanced/selectAdvancedReducer.ts index 05278842d..ba12530fc 100644 --- a/packages/design-system/src/lib/components/select-advanced/selectAdvancedReducer.ts +++ b/packages/design-system/src/lib/components/select-advanced/selectAdvancedReducer.ts @@ -1,13 +1,21 @@ -export const selectAdvancedInitialState: SelectAdvancedState = { - options: [], - selectedOptions: [], +export const getSelectAdvancedInitialState = ( + isMultiple: boolean, + initialOptions: string[], + defaultValue?: string | string[] +): SelectAdvancedState => ({ + options: initialOptions, + selected: isMultiple + ? (defaultValue as string[] | undefined) ?? [] + : (defaultValue as string | undefined) ?? '', filteredOptions: [], - searchValue: '' -} + searchValue: '', + isMultiple +}) interface SelectAdvancedState { + isMultiple: boolean + selected: string | string[] options: string[] - selectedOptions: string[] filteredOptions: string[] searchValue: string } @@ -38,32 +46,48 @@ export const selectAdvancedReducer = ( ) => { switch (action.type) { case 'SELECT_OPTION': - return { - ...state, - selectedOptions: [...state.selectedOptions, action.payload] + if (state.isMultiple) { + return { + ...state, + selected: Array.isArray(state.selected) + ? [...state.selected, action.payload] + : [action.payload] + } + } else { + return { + ...state, + selected: action.payload + } } case 'REMOVE_OPTION': - return { - ...state, - selectedOptions: state.selectedOptions.filter((option) => option !== action.payload) + if (state.isMultiple && Array.isArray(state.selected)) { + return { + ...state, + selected: state.selected.filter((option) => option !== action.payload) + } + } else { + return { + ...state, + selected: '' + } } case 'SELECT_ALL_OPTIONS': - return { - ...state, - selectedOptions: - state.filteredOptions.length > 0 - ? Array.from(new Set([...state.selectedOptions, ...state.filteredOptions])) - : state.options + if (state.isMultiple) { + return { + ...state, + selected: + state.filteredOptions.length > 0 + ? Array.from(new Set([...state.selected, ...state.filteredOptions])) + : state.options + } + } else { + return state } case 'DESELECT_ALL_OPTIONS': return { ...state, - selectedOptions: - state.filteredOptions.length > 0 - ? state.selectedOptions.filter((option) => !state.filteredOptions.includes(option)) - : [] + selected: state.isMultiple ? [] : '' } - case 'SEARCH': return { ...state, @@ -75,11 +99,65 @@ export const selectAdvancedReducer = ( : [], searchValue: action.payload } + // case 'TOGGLE_SELECTION_MODE': + // return { + // ...state, + // isMultiple: action.payload, + // selected: action.payload ? [] : '' + // } default: return state } } +// export const selectAdvancedReducer = ( +// state: SelectAdvancedState, +// action: SelectAdvancedActions +// ) => { +// switch (action.type) { +// case 'SELECT_OPTION': +// return { +// ...state, +// selected: [...state.selected, action.payload] +// } +// case 'REMOVE_OPTION': +// return { +// ...state, +// selected: state.selected.filter((option) => option !== action.payload) +// } +// case 'SELECT_ALL_OPTIONS': +// return { +// ...state, +// selected: +// state.filteredOptions.length > 0 +// ? Array.from(new Set([...state.selected, ...state.filteredOptions])) +// : state.options +// } +// case 'DESELECT_ALL_OPTIONS': +// return { +// ...state, +// selected: +// state.filteredOptions.length > 0 +// ? state.selected.filter((option) => !state.filteredOptions.includes(option)) +// : [] +// } + +// case 'SEARCH': +// return { +// ...state, +// filteredOptions: +// action.payload !== '' +// ? state.options.filter((option) => +// option.toLowerCase().includes(action.payload.toLowerCase()) +// ) +// : [], +// searchValue: action.payload +// } +// default: +// return state +// } +// } + export const selectOption = /* istanbul ignore next */ (option: string): SelectAdvancedActions => ({ type: 'SELECT_OPTION', payload: option diff --git a/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx b/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx index 30c469c03..318ee107d 100644 --- a/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx +++ b/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx @@ -32,26 +32,44 @@ const exampleOptions = [ 'Other' ] -export const Default: Story = { - render: () => console.log(a)} /> +export const Single: Story = { + render: () => } -export const WithDefaultValues: Story = { +export const Multiple: Story = { + render: () => +} + +export const SingleWithDefaultValues: Story = { + render: () => +} +export const MultipleWithDefaultValues: Story = { render: () => ( ) } -export const NotSearchable: Story = { - render: () => +export const SingleNotSearchable: Story = { + render: () => +} + +export const MultipleNotSearchable: Story = { + render: () => } export const Invalid: Story = { - render: () => + render: () => } export const Disabled: Story = { - render: () => + render: () => +} + +export const WithDifferentSelectWord: Story = { + render: () => ( + + ) } From a8b95059a9125ec08b3fa157865e0c54188bb5d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Thu, 27 Jun 2024 16:17:02 -0300 Subject: [PATCH 03/12] feat(design system): form stories --- .../form-element/FormSelectAdvanced.tsx | 2 +- .../select-advanced/SelectAdvanced.tsx | 1 - .../src/lib/stories/form/Form.stories.tsx | 21 ++++- .../SelectAdvanced.stories.tsx | 80 ++++++++++++------- 4 files changed, 70 insertions(+), 34 deletions(-) diff --git a/packages/design-system/src/lib/components/form/form-group/form-element/FormSelectAdvanced.tsx b/packages/design-system/src/lib/components/form/form-group/form-element/FormSelectAdvanced.tsx index e4c0ba87a..7dd1068be 100644 --- a/packages/design-system/src/lib/components/form/form-group/form-element/FormSelectAdvanced.tsx +++ b/packages/design-system/src/lib/components/form/form-group/form-element/FormSelectAdvanced.tsx @@ -1,7 +1,7 @@ import { PropsWithChildren, forwardRef } from 'react' import { SelectAdvanced, SelectAdvancedProps } from '../../../select-advanced/SelectAdvanced' -export interface FormSelectAdvancedProps extends SelectAdvancedProps { +export type FormSelectAdvancedProps = SelectAdvancedProps & { inputButtonId: string isInvalid?: boolean } diff --git a/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx index b3b845a7a..baba37be8 100644 --- a/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx +++ b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx @@ -83,7 +83,6 @@ export const SelectAdvanced = forwardRef( const handleSearch = debounce((e: React.ChangeEvent): void => { const { value } = e.target - console.log({ value }) dispatch(searchOptions(value)) }, SELECT_MENU_SEARCH_DEBOUNCE_TIME) diff --git a/packages/design-system/src/lib/stories/form/Form.stories.tsx b/packages/design-system/src/lib/stories/form/Form.stories.tsx index 8dc683701..73758d5d9 100644 --- a/packages/design-system/src/lib/stories/form/Form.stories.tsx +++ b/packages/design-system/src/lib/stories/form/Form.stories.tsx @@ -221,7 +221,26 @@ export const SelectAdvanced: Story = { + + + + ) +} + +export const SelectAdvancedMultiple: Story = { + render: () => ( +
+ + + Hobbies + + + diff --git a/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx b/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx index 318ee107d..8fc9aeb64 100644 --- a/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx +++ b/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx @@ -1,10 +1,11 @@ import type { Meta, StoryObj } from '@storybook/react' import { SelectAdvanced } from '../../components/select-advanced/SelectAdvanced' +import { CanvasFixedHeight } from '../CanvasFixedHeight' /** * ## Description * The select advanced component is a user interface element that allows users to select one or multiple options from a list of items. - * They can also search for items in the list, select all items, and clear the selection. + * They can also search for items in the list, select all items and clear the selection (last two on multiple selection mode). */ const meta: Meta = { title: 'Select Advanced', @@ -15,61 +16,78 @@ const meta: Meta = { export default meta type Story = StoryObj -const exampleOptions = [ - 'Agricultural Sciences', - 'Arts and Humanities', - 'Astronomy and Astrophysics', - 'Business and Management', - 'Chemistry', - 'Computer and Information Science', - 'Earth and Environmental Sciences', - 'Engineering', - 'Law', - 'Mathematical Sciences', - 'Medicine, Health and Life Sciences', - 'Physics', - 'Social Sciences', - 'Other' -] +const exampleOptions = ['Option 1', 'Option 2', 'Option 3', 'Option 4'] export const Single: Story = { - render: () => + render: () => ( + + + + ) } export const Multiple: Story = { - render: () => + render: () => ( + + + + ) } -export const SingleWithDefaultValues: Story = { - render: () => +export const SingleWithDefaultValue: Story = { + render: () => ( + + + + ) } export const MultipleWithDefaultValues: Story = { render: () => ( - + + + ) } export const SingleNotSearchable: Story = { - render: () => + render: () => ( + + + + ) } export const MultipleNotSearchable: Story = { - render: () => + render: () => ( + + + + ) } export const Invalid: Story = { - render: () => + render: () => ( + + + + ) } export const Disabled: Story = { - render: () => + render: () => ( + + + + ) } export const WithDifferentSelectWord: Story = { render: () => ( - + + + ) } From 9767b4e530fc02ab54a8e7bd87db5d6ec4208eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Thu, 27 Jun 2024 17:04:14 -0300 Subject: [PATCH 04/12] feat(design system): improve click on checbox list and avoid search on select word --- .../SelectAdvanced.module.scss | 12 ++- .../select-advanced/SelectAdvanced.tsx | 7 +- .../select-advanced/selectAdvancedReducer.ts | 78 +++++-------------- 3 files changed, 36 insertions(+), 61 deletions(-) diff --git a/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.module.scss b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.module.scss index b7d766616..957e076d4 100644 --- a/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.module.scss +++ b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.module.scss @@ -153,10 +153,20 @@ } &__checkbox-input { - padding-block: 0.25rem; + display: flex; + align-items: center; + padding-left: 0; + + input[type='checkbox'] { + float: unset; + margin-top: 0; + margin-left: 0; + } label { width: 100%; + padding-left: 0.5rem; + padding-block: 0.25rem; } } } diff --git a/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx index baba37be8..c126f1a9c 100644 --- a/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx +++ b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx @@ -69,7 +69,12 @@ export const SelectAdvanced = forwardRef( const [{ selected, filteredOptions, searchValue, options }, dispatch] = useReducer( selectAdvancedReducer, - getSelectAdvancedInitialState(Boolean(isMultiple), dynamicInitialOptions, defaultValue) + getSelectAdvancedInitialState( + Boolean(isMultiple), + dynamicInitialOptions, + locales?.select ?? DEFAULT_LOCALES.select, + defaultValue + ) ) const isFirstRender = useIsFirstRender() diff --git a/packages/design-system/src/lib/components/select-advanced/selectAdvancedReducer.ts b/packages/design-system/src/lib/components/select-advanced/selectAdvancedReducer.ts index ba12530fc..ee4a13eb0 100644 --- a/packages/design-system/src/lib/components/select-advanced/selectAdvancedReducer.ts +++ b/packages/design-system/src/lib/components/select-advanced/selectAdvancedReducer.ts @@ -1,6 +1,7 @@ export const getSelectAdvancedInitialState = ( isMultiple: boolean, initialOptions: string[], + selectWord: string, defaultValue?: string | string[] ): SelectAdvancedState => ({ options: initialOptions, @@ -9,7 +10,8 @@ export const getSelectAdvancedInitialState = ( : (defaultValue as string | undefined) ?? '', filteredOptions: [], searchValue: '', - isMultiple + isMultiple, + selectWord }) interface SelectAdvancedState { @@ -18,6 +20,7 @@ interface SelectAdvancedState { options: string[] filteredOptions: string[] searchValue: string + selectWord: string } type SelectAdvancedActions = @@ -91,72 +94,29 @@ export const selectAdvancedReducer = ( case 'SEARCH': return { ...state, - filteredOptions: - action.payload !== '' - ? state.options.filter((option) => - option.toLowerCase().includes(action.payload.toLowerCase()) - ) - : [], + filteredOptions: filterOptions(state, action), searchValue: action.payload } - // case 'TOGGLE_SELECTION_MODE': - // return { - // ...state, - // isMultiple: action.payload, - // selected: action.payload ? [] : '' - // } default: return state } } -// export const selectAdvancedReducer = ( -// state: SelectAdvancedState, -// action: SelectAdvancedActions -// ) => { -// switch (action.type) { -// case 'SELECT_OPTION': -// return { -// ...state, -// selected: [...state.selected, action.payload] -// } -// case 'REMOVE_OPTION': -// return { -// ...state, -// selected: state.selected.filter((option) => option !== action.payload) -// } -// case 'SELECT_ALL_OPTIONS': -// return { -// ...state, -// selected: -// state.filteredOptions.length > 0 -// ? Array.from(new Set([...state.selected, ...state.filteredOptions])) -// : state.options -// } -// case 'DESELECT_ALL_OPTIONS': -// return { -// ...state, -// selected: -// state.filteredOptions.length > 0 -// ? state.selected.filter((option) => !state.filteredOptions.includes(option)) -// : [] -// } +const filterOptions = ( + state: SelectAdvancedState, + action: { + type: 'SEARCH' + payload: string + } +) => { + if (action.payload === '') return [] -// case 'SEARCH': -// return { -// ...state, -// filteredOptions: -// action.payload !== '' -// ? state.options.filter((option) => -// option.toLowerCase().includes(action.payload.toLowerCase()) -// ) -// : [], -// searchValue: action.payload -// } -// default: -// return state -// } -// } + const optionsWithoutSelectWord = state.options.filter((option) => option !== state.selectWord) + + return optionsWithoutSelectWord.filter((option) => + option.toLowerCase().includes(action.payload.toLowerCase()) + ) +} export const selectOption = /* istanbul ignore next */ (option: string): SelectAdvancedActions => ({ type: 'SELECT_OPTION', From ef92eb083b0d12198f2d2290140b673e9b549082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Fri, 28 Jun 2024 09:58:16 -0300 Subject: [PATCH 05/12] fix: issue about not unique label on checkbox --- .../lib/components/select-advanced/SelectAdvancedMenu.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/design-system/src/lib/components/select-advanced/SelectAdvancedMenu.tsx b/packages/design-system/src/lib/components/select-advanced/SelectAdvancedMenu.tsx index e9774d259..74fe615ec 100644 --- a/packages/design-system/src/lib/components/select-advanced/SelectAdvancedMenu.tsx +++ b/packages/design-system/src/lib/components/select-advanced/SelectAdvancedMenu.tsx @@ -33,6 +33,7 @@ export const SelectAdvancedMenu = ({ }: SelectAdvancedMenuProps) => { const searchInputControlID = useId() const toggleAllControlID = useId() + const optionLabelId = useId() const menuOptions = filteredOptions.length > 0 ? filteredOptions : options @@ -98,7 +99,7 @@ export const SelectAdvancedMenu = ({ key={option} onClick={() => handleClickOption(option === selectWord ? '' : option)} active={option !== selectWord ? selected === option : selected === ''} - id={`check-item-${option}`}> + id={`${optionLabelId}-${option}`}> {option} ) @@ -111,7 +112,7 @@ export const SelectAdvancedMenu = ({ value={option} label={option} onChange={handleCheck} - id={`check-item-${option}`} + id={`${optionLabelId}-${option}`} checked={selected.includes(option)} className={styles['option-item__checkbox-input']} /> From 09afbbedf21e9b834b51cf0ffeb288fc00e97bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Fri, 28 Jun 2024 13:55:44 -0300 Subject: [PATCH 06/12] fix(design system): update available options if they change --- .../select-advanced/SelectAdvanced.tsx | 55 ++++++++++++++---- .../select-advanced/selectAdvancedReducer.ts | 16 ++++++ .../lib/components/select-advanced/utils.ts | 21 +++++++ .../src/lib/stories/form/Form.stories.tsx | 4 +- .../SelectAdvanced.stories.tsx | 57 ++++++++++++++++--- 5 files changed, 132 insertions(+), 21 deletions(-) diff --git a/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx index c126f1a9c..afcedcb82 100644 --- a/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx +++ b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx @@ -1,4 +1,4 @@ -import { ForwardedRef, forwardRef, useEffect, useId, useReducer } from 'react' +import { useState, useEffect, useMemo, useId, useReducer, forwardRef, ForwardedRef } from 'react' import { Dropdown as DropdownBS } from 'react-bootstrap' import { selectAdvancedReducer, @@ -7,11 +7,12 @@ import { selectAllOptions, deselectAllOptions, searchOptions, - getSelectAdvancedInitialState + getSelectAdvancedInitialState, + updateOptions } from './selectAdvancedReducer' import { SelectAdvancedToggle } from './SelectAdvancedToggle' import { SelectAdvancedMenu } from './SelectAdvancedMenu' -import { debounce } from './utils' +import { areArraysEqual, debounce } from './utils' import { useIsFirstRender } from './useIsFirstRender' export const DEFAULT_LOCALES = { @@ -23,7 +24,7 @@ export const SELECT_MENU_SEARCH_DEBOUNCE_TIME = 400 export type SelectAdvancedProps = | { isMultiple?: false - initialOptions: string[] + options: string[] onChange?: (selected: string) => void defaultValue?: string isSearchable?: boolean @@ -36,7 +37,7 @@ export type SelectAdvancedProps = } | { isMultiple: true - initialOptions: string[] + options: string[] onChange?: (selected: string[]) => void defaultValue?: string[] isSearchable?: boolean @@ -51,7 +52,7 @@ export type SelectAdvancedProps = export const SelectAdvanced = forwardRef( ( { - initialOptions, + options: propsOption, onChange, defaultValue, isMultiple, @@ -63,9 +64,9 @@ export const SelectAdvanced = forwardRef( }: SelectAdvancedProps, ref: ForwardedRef ) => { - const dynamicInitialOptions = isMultiple - ? initialOptions - : [locales?.select ?? DEFAULT_LOCALES.select, ...initialOptions] + const dynamicInitialOptions = useMemo(() => { + return isMultiple ? propsOption : [locales?.select ?? DEFAULT_LOCALES.select, ...propsOption] + }, [isMultiple, propsOption, locales]) const [{ selected, filteredOptions, searchValue, options }, dispatch] = useReducer( selectAdvancedReducer, @@ -79,12 +80,46 @@ export const SelectAdvanced = forwardRef( const isFirstRender = useIsFirstRender() const menuId = useId() + const [lastOnChangeValue, setLastOnChangeValue] = useState( + isMultiple ? [] : '' + ) useEffect(() => { if (!isFirstRender && onChange) { + // Dont call onChange if the selected options (string[]) remain the same + if (isMultiple) { + const selectedOptionsRemainTheSame = areArraysEqual( + selected as string[], + defaultValue && (lastOnChangeValue as string[])?.length === 0 + ? defaultValue + : (lastOnChangeValue as string[]) + ) + + if (selectedOptionsRemainTheSame) return + } + // Dont call onChange if the selected option (string) remain the same + if (!isMultiple) { + const compareAgainst = + defaultValue && (lastOnChangeValue as string) === '' + ? defaultValue + : (lastOnChangeValue as string) + const selectedOptionRemainTheSame = selected === compareAgainst + + if (selectedOptionRemainTheSame) return + } + isMultiple ? onChange(selected as string[]) : onChange(selected as string) + setLastOnChangeValue(selected) } - }, [isMultiple, selected, isFirstRender, onChange]) + }, [isMultiple, selected, isFirstRender, onChange, lastOnChangeValue, defaultValue]) + + useEffect(() => { + const optionsRemainTheSame = propsOption.every((option) => options.includes(option)) + + if (optionsRemainTheSame) return + + dispatch(updateOptions(dynamicInitialOptions)) + }, [dynamicInitialOptions, propsOption, options, isFirstRender, dispatch]) const handleSearch = debounce((e: React.ChangeEvent): void => { const { value } = e.target diff --git a/packages/design-system/src/lib/components/select-advanced/selectAdvancedReducer.ts b/packages/design-system/src/lib/components/select-advanced/selectAdvancedReducer.ts index ee4a13eb0..31e17cd2e 100644 --- a/packages/design-system/src/lib/components/select-advanced/selectAdvancedReducer.ts +++ b/packages/design-system/src/lib/components/select-advanced/selectAdvancedReducer.ts @@ -42,6 +42,10 @@ type SelectAdvancedActions = type: 'SEARCH' payload: string } + | { + type: 'UPDATE_OPTIONS' + payload: string[] + } export const selectAdvancedReducer = ( state: SelectAdvancedState, @@ -97,6 +101,11 @@ export const selectAdvancedReducer = ( filteredOptions: filterOptions(state, action), searchValue: action.payload } + case 'UPDATE_OPTIONS': + return { + ...state, + options: action.payload + } default: return state } @@ -140,3 +149,10 @@ export const searchOptions = /* istanbul ignore next */ (value: string): SelectA type: 'SEARCH', payload: value }) + +export const updateOptions = /* istanbul ignore next */ ( + options: string[] +): SelectAdvancedActions => ({ + type: 'UPDATE_OPTIONS', + payload: options +}) diff --git a/packages/design-system/src/lib/components/select-advanced/utils.ts b/packages/design-system/src/lib/components/select-advanced/utils.ts index 0c0ad99cb..709120843 100644 --- a/packages/design-system/src/lib/components/select-advanced/utils.ts +++ b/packages/design-system/src/lib/components/select-advanced/utils.ts @@ -11,3 +11,24 @@ export function debounce unknown>( timeoutId = setTimeout(() => fn(...args), delay) } } + +export function areArraysEqual(arr1: string[], arr2: string[]): boolean { + if (arr1.length === 0 && arr2.length === 0) { + return true + } + + if (arr1.length !== arr2.length) { + return false + } + + const sortedArr1 = arr1.slice().sort() + const sortedArr2 = arr2.slice().sort() + + for (let i = 0; i < sortedArr1.length; i++) { + if (sortedArr1[i] !== sortedArr2[i]) { + return false + } + } + + return true +} diff --git a/packages/design-system/src/lib/stories/form/Form.stories.tsx b/packages/design-system/src/lib/stories/form/Form.stories.tsx index 73758d5d9..d4ff0b9ac 100644 --- a/packages/design-system/src/lib/stories/form/Form.stories.tsx +++ b/packages/design-system/src/lib/stories/form/Form.stories.tsx @@ -221,7 +221,7 @@ export const SelectAdvanced: Story = { @@ -240,7 +240,7 @@ export const SelectAdvancedMultiple: Story = { diff --git a/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx b/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx index 8fc9aeb64..c7ef62359 100644 --- a/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx +++ b/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx @@ -1,6 +1,8 @@ import type { Meta, StoryObj } from '@storybook/react' import { SelectAdvanced } from '../../components/select-advanced/SelectAdvanced' +import { Button } from '../../components/button/Button' import { CanvasFixedHeight } from '../CanvasFixedHeight' +import { useState } from 'react' /** * ## Description @@ -21,14 +23,14 @@ const exampleOptions = ['Option 1', 'Option 2', 'Option 3', 'Option 4'] export const Single: Story = { render: () => ( - + ) } export const Multiple: Story = { render: () => ( - + ) } @@ -36,7 +38,7 @@ export const Multiple: Story = { export const SingleWithDefaultValue: Story = { render: () => ( - + ) } @@ -45,7 +47,7 @@ export const MultipleWithDefaultValues: Story = { @@ -55,7 +57,7 @@ export const MultipleWithDefaultValues: Story = { export const SingleNotSearchable: Story = { render: () => ( - + ) } @@ -63,7 +65,7 @@ export const SingleNotSearchable: Story = { export const MultipleNotSearchable: Story = { render: () => ( - + ) } @@ -71,7 +73,7 @@ export const MultipleNotSearchable: Story = { export const Invalid: Story = { render: () => ( - + ) } @@ -79,7 +81,7 @@ export const Invalid: Story = { export const Disabled: Story = { render: () => ( - + ) } @@ -87,7 +89,44 @@ export const Disabled: Story = { export const WithDifferentSelectWord: Story = { render: () => ( - + + + ) +} + +const SimulateChangeOfAvailableOptions = () => { + const [availableOptions, setAvailableOptions] = useState(['Tag 1', 'Tag 2', 'Tag 3']) + + return ( + <> + +
+ console.log(selected)} + /> + console.log(selected)} + /> +
+ + ) +} + +export const ChangeOfAvailablesOptionsCase: Story = { + render: () => ( + + ) } From 9851d6f8cd205d042eb84df989e6cc2e5aee4873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Fri, 28 Jun 2024 17:22:43 -0300 Subject: [PATCH 07/12] feat(design system): adding tests --- .../select-advanced/SelectAdvancedMenu.tsx | 5 +- .../select-advanced/SelectAdvancedToggle.tsx | 5 +- .../select-advanced/selectAdvancedReducer.ts | 46 +- .../select-advanced/SelectAdvanced.spec.tsx | 666 +++++++++++++++--- .../selectAdvancedReducer.spec.tsx | 121 +++- .../component/select-advanced/utils.spec.ts | 37 + 6 files changed, 705 insertions(+), 175 deletions(-) create mode 100644 packages/design-system/tests/component/select-advanced/utils.spec.ts diff --git a/packages/design-system/src/lib/components/select-advanced/SelectAdvancedMenu.tsx b/packages/design-system/src/lib/components/select-advanced/SelectAdvancedMenu.tsx index 74fe615ec..074567b3e 100644 --- a/packages/design-system/src/lib/components/select-advanced/SelectAdvancedMenu.tsx +++ b/packages/design-system/src/lib/components/select-advanced/SelectAdvancedMenu.tsx @@ -81,10 +81,13 @@ export const SelectAdvancedMenu = ({ aria-label="Search for an option" size="sm" onChange={handleSearch} + data-testid="select-advanced-searchable-input" /> )} {isMultiple && !isSearchable && ( -

{selected.length} selected

+

+ {selected.length} selected +

)} )} diff --git a/packages/design-system/src/lib/components/select-advanced/SelectAdvancedToggle.tsx b/packages/design-system/src/lib/components/select-advanced/SelectAdvancedToggle.tsx index a81524b4c..81878ba6a 100644 --- a/packages/design-system/src/lib/components/select-advanced/SelectAdvancedToggle.tsx +++ b/packages/design-system/src/lib/components/select-advanced/SelectAdvancedToggle.tsx @@ -45,7 +45,9 @@ export const SelectAdvancedToggle = forwardRef( isInvalid ? styles['invalid'] : '' }`} /> -
+
{selected.length > 0 ? (
e.stopPropagation()} key={`selected-option-${selected as string}`}> {selected}

diff --git a/packages/design-system/src/lib/components/select-advanced/selectAdvancedReducer.ts b/packages/design-system/src/lib/components/select-advanced/selectAdvancedReducer.ts index 31e17cd2e..6f95f7dc9 100644 --- a/packages/design-system/src/lib/components/select-advanced/selectAdvancedReducer.ts +++ b/packages/design-system/src/lib/components/select-advanced/selectAdvancedReducer.ts @@ -14,7 +14,7 @@ export const getSelectAdvancedInitialState = ( selectWord }) -interface SelectAdvancedState { +export interface SelectAdvancedState { isMultiple: boolean selected: string | string[] options: string[] @@ -56,9 +56,7 @@ export const selectAdvancedReducer = ( if (state.isMultiple) { return { ...state, - selected: Array.isArray(state.selected) - ? [...state.selected, action.payload] - : [action.payload] + selected: [...state.selected, action.payload] } } else { return { @@ -66,34 +64,32 @@ export const selectAdvancedReducer = ( selected: action.payload } } + // ONLY FOR MULTIPLE SELECT 👇 case 'REMOVE_OPTION': - if (state.isMultiple && Array.isArray(state.selected)) { - return { - ...state, - selected: state.selected.filter((option) => option !== action.payload) - } - } else { - return { - ...state, - selected: '' - } + return { + ...state, + selected: (state.selected as string[]).filter((option) => option !== action.payload) } + + // ONLY FOR MULTIPLE SELECT 👇 case 'SELECT_ALL_OPTIONS': - if (state.isMultiple) { - return { - ...state, - selected: - state.filteredOptions.length > 0 - ? Array.from(new Set([...state.selected, ...state.filteredOptions])) - : state.options - } - } else { - return state + return { + ...state, + selected: + state.filteredOptions.length > 0 + ? Array.from(new Set([...(state.selected as string[]), ...state.filteredOptions])) + : state.options } + // ONLY FOR MULTIPLE SELECT 👇 case 'DESELECT_ALL_OPTIONS': return { ...state, - selected: state.isMultiple ? [] : '' + selected: + state.filteredOptions.length > 0 + ? (state.selected as string[]).filter( + (option) => !state.filteredOptions.includes(option) + ) + : [] } case 'SEARCH': return { diff --git a/packages/design-system/tests/component/select-advanced/SelectAdvanced.spec.tsx b/packages/design-system/tests/component/select-advanced/SelectAdvanced.spec.tsx index a8974a802..7697387de 100644 --- a/packages/design-system/tests/component/select-advanced/SelectAdvanced.spec.tsx +++ b/packages/design-system/tests/component/select-advanced/SelectAdvanced.spec.tsx @@ -1,96 +1,240 @@ +import { useState } from 'react' import { SelectAdvanced, SELECT_MENU_SEARCH_DEBOUNCE_TIME } from '../../../src/lib/components/select-advanced/SelectAdvanced' describe('SelectAdvanced', () => { - it('should render correctly', () => { - cy.mount( - - ) - - cy.findByText('Select...').should('exist') + describe('should render correctly', () => { + it('on single selection', () => { + cy.mount( + + ) + + cy.findByText('Select...').should('exist') + }) + it('on multiple selection', () => { + cy.mount( + + ) + cy.findByText('Select...').should('exist') + }) }) - it('should render correct options', () => { - cy.mount( - - ) - - cy.findByLabelText('Toggle options menu').click() - - cy.findByText('Reading').should('exist') - cy.findByText('Swimming').should('exist') - cy.findByText('Running').should('exist') - cy.findByText('Cycling').should('exist') - cy.findByText('Cooking').should('exist') - cy.findByText('Gardening').should('exist') + describe('should render correct options', () => { + it('on single selection', () => { + cy.mount( + + ) + + cy.findByLabelText('Toggle options menu').click() + + cy.findByText('Reading').should('exist') + cy.findByText('Swimming').should('exist') + cy.findByText('Running').should('exist') + cy.findByText('Cycling').should('exist') + cy.findByText('Cooking').should('exist') + cy.findByText('Gardening').should('exist') + }) + it('on multiple selection', () => { + cy.mount( + + ) + cy.findByLabelText('Toggle options menu').click() + + cy.findByText('Reading').should('exist') + cy.findByText('Swimming').should('exist') + cy.findByText('Running').should('exist') + cy.findByText('Cycling').should('exist') + cy.findByText('Cooking').should('exist') + cy.findByText('Gardening').should('exist') + }) }) - it('should render with default values', () => { - cy.mount( - - ) + describe('should render with default values', () => { + it('on single selection', () => { + cy.mount( + + ) - cy.findByText('Reading').should('exist') - cy.findByText('Running').should('exist') + cy.findByText('Running').should('exist') + }) + it('on multiple selection', () => { + cy.mount( + + ) + cy.findByText('Reading').should('exist') + cy.findByText('Running').should('exist') + }) }) - it('should call onChange when an option is selected', () => { - const onChange = cy.stub().as('onChange') + describe('should call onChange when an option is selected', () => { + it('on single selection', () => { + const onChange = cy.stub().as('onChange') - cy.mount( - - ) + cy.mount( + + ) - cy.findByLabelText('Toggle options menu').click() - cy.findByLabelText('Reading').click() + cy.findByLabelText('Toggle options menu').click() + cy.findByText('Reading').click() - cy.get('@onChange').should('have.been.calledOnce') + cy.get('@onChange').should('have.been.calledOnce') + }) + it('on multiple selection', () => { + const onChange = cy.stub().as('onChange') + + cy.mount( + + ) + cy.findByLabelText('Toggle options menu').click() + cy.findByLabelText('Reading').click() + + cy.get('@onChange').should('have.been.calledOnce') + }) }) - it('should call onChange when an option is deselected', () => { - const onChange = cy.stub().as('onChange') + describe('should call onChange when an option is deselected', () => { + // Only on multiple selection as in single selection mode we can't deselect an option just change it to another + it('on multiple selection', () => { + const onChange = cy.stub().as('onChange') + + cy.mount( + + ) + cy.findByLabelText('Toggle options menu').click() + cy.findByLabelText('Reading').click() + + cy.get('@onChange').should('have.been.calledOnce') + }) + }) - cy.mount( - - ) + describe('should not call onChange when passing defaultValues and rendering for first time', () => { + it('on single selection', () => { + const onChange = cy.stub().as('onChange') - cy.findByLabelText('Toggle options menu').click() - cy.findByLabelText('Reading').click() + cy.mount( + + ) - cy.get('@onChange').should('have.been.calledOnce') + cy.get('@onChange').should('not.have.been.called') + }) + it('on multiple selection', () => { + const onChange = cy.stub().as('onChange') + + cy.mount( + + ) + cy.get('@onChange').should('not.have.been.called') + }) }) - it('should not call onChange when passing defaultValues and rendering for first time', () => { - const onChange = cy.stub().as('onChange') + describe('should select an option and be shown as selected both in the menu as well as in the selected options', () => { + it('on single selection', () => { + cy.mount( + + ) + cy.findByLabelText('Toggle options menu').click() + cy.findByText('Reading').click() + cy.findAllByText('Reading').spread((_selectedItem, selectedListOption) => { + const element = cy.get(selectedListOption) + element.should('have.class', 'active') + }) + + cy.findByTestId('toggle-inner-content') + .should('exist') + .within(() => { + cy.findByText('Reading').should('exist') + }) + }) + it('on multiple selection', () => { + cy.mount( + + ) + + cy.findByLabelText('Toggle options menu').click() + cy.findByLabelText('Reading').click() + cy.findByLabelText('Reading').should('be.checked') + + cy.findByTestId('toggle-inner-content') + .should('exist') + .within(() => { + cy.findByText('Reading').should('exist') + }) + }) + }) + it('should change the selected option when selecting another option in single selection mode', () => { cy.mount( ) - cy.get('@onChange').should('not.have.been.called') + cy.findByLabelText('Toggle options menu').click() + cy.findByText('Reading').click() + cy.findAllByText('Reading') + .should('have.length', 2) + .spread((_selectedItem, selectedListOption) => { + const element = cy.get(selectedListOption) + element.should('have.class', 'active') + }) + + cy.findByText('Swimming').click() + + cy.findAllByText('Swimming') + .should('have.length', 2) + .spread((_selectedItem, selectedListOption) => { + const element = cy.get(selectedListOption) + element.should('have.class', 'active') + }) + cy.findByText('Reading').should('not.have.class', 'active') }) - it('should select an option and be shown as selected both in the menu as well as in the selected options', () => { + it('should not change the selected option when clicking on the selected option in single selection mode', () => { cy.mount( { ) cy.findByLabelText('Toggle options menu').click() - cy.findByLabelText('Reading').click() - cy.findByLabelText('Reading').should('be.checked') + cy.findByText('Reading').click() + cy.findAllByText('Reading') + .should('have.length', 2) + .spread((_selectedItem, selectedListOption) => { + const element = cy.get(selectedListOption) + element.should('have.class', 'active') + }) - cy.findByLabelText('List of selected options') - .should('exist') - .within(() => { - cy.findByText('Reading').should('exist') + cy.findAllByText('Reading').spread((_selectedItem, selectedListOption) => { + const element = cy.get(selectedListOption) + element.click() + }) + + cy.findAllByText('Reading') + .should('have.length', 2) + .spread((_selectedItem, selectedListOption) => { + const element = cy.get(selectedListOption) + element.should('have.class', 'active') }) }) - it('should remove a selected option by clicking on an item in the selected options', () => { + it('should remove a selected option by clicking on an X icon of an item in the selected options in multiple selection mode', () => { cy.mount( ) @@ -118,15 +274,16 @@ describe('SelectAdvanced', () => { cy.findByLabelText('Toggle options menu').click() cy.findByLabelText('Reading').click() - cy.findByLabelText('List of selected options').within(() => { + cy.findByTestId('toggle-inner-content').within(() => { cy.findByLabelText('Remove Reading option').click() cy.findByText('Reading').should('not.exist') }) }) - it('selects all options', () => { + it('selects all options on multiple selection mode', () => { cy.mount( ) @@ -141,7 +298,7 @@ describe('SelectAdvanced', () => { cy.findByLabelText('Cooking').should('be.checked') cy.findByLabelText('Gardening').should('be.checked') - cy.findByLabelText('List of selected options') + cy.findByTestId('toggle-inner-content') .should('exist') .within(() => { cy.findByText('Reading').should('exist') @@ -154,9 +311,10 @@ describe('SelectAdvanced', () => { cy.findByText('Select...').should('not.exist') }) - it('deselects all options', () => { + it('deselects all options on multiple selection mode', () => { cy.mount( ) @@ -172,13 +330,23 @@ describe('SelectAdvanced', () => { cy.findByLabelText('Cooking').should('not.be.checked') cy.findByLabelText('Gardening').should('not.be.checked') - cy.findByLabelText('List of selected options').should('not.exist') + cy.findByTestId('toggle-inner-content') + .should('exist') + .within(() => { + cy.findByText('Reading').should('not.exist') + cy.findByText('Swimming').should('not.exist') + cy.findByText('Running').should('not.exist') + cy.findByText('Cycling').should('not.exist') + cy.findByText('Cooking').should('not.exist') + cy.findByText('Gardening').should('not.exist') + }) cy.findByText('Select...').should('exist') }) - it('should select all filtered options', () => { + it('should select all filtered options on multiple selection mode', () => { cy.mount( ) @@ -198,7 +366,7 @@ describe('SelectAdvanced', () => { cy.findByLabelText('Toggle all options').click() - cy.findByLabelText('List of selected options') + cy.findByTestId('toggle-inner-content') .should('exist') .within(() => { cy.findByText('Reading').should('exist') @@ -211,9 +379,10 @@ describe('SelectAdvanced', () => { cy.findByText('Select...').should('not.exist') }) - it('should unselect only filtered options', () => { + it('should unselect only filtered options on multiple selection mode', () => { cy.mount( ) @@ -242,7 +411,7 @@ describe('SelectAdvanced', () => { cy.findByLabelText('Toggle all options').click() - cy.findByLabelText('List of selected options') + cy.findByTestId('toggle-inner-content') .should('exist') .within(() => { cy.findByText('Reading').should('not.exist') @@ -254,27 +423,48 @@ describe('SelectAdvanced', () => { }) }) - it('should show correct filtered options when searching for a value', () => { - cy.mount( - - ) - - cy.findByLabelText('Toggle options menu').click() - cy.findByPlaceholderText('Search...').type('Read') - - cy.findByLabelText('Reading').should('exist') - cy.findByLabelText('Swimming').should('not.exist') - cy.findByLabelText('Running').should('not.exist') - cy.findByLabelText('Cycling').should('not.exist') - cy.findByLabelText('Cooking').should('not.exist') - cy.findByLabelText('Gardening').should('not.exist') + describe('should show correct filtered options when searching for a value', () => { + it('on single selection', () => { + cy.mount( + + ) + + cy.findByLabelText('Toggle options menu').click() + cy.findByPlaceholderText('Search...').type('Read') + + cy.findByText('Reading').should('exist') + cy.findByText('Swimming').should('not.exist') + cy.findByText('Running').should('not.exist') + cy.findByText('Cycling').should('not.exist') + cy.findByText('Cooking').should('not.exist') + cy.findByText('Gardening').should('not.exist') + }) + it('on multiple selection', () => { + cy.mount( + + ) + + cy.findByLabelText('Toggle options menu').click() + cy.findByPlaceholderText('Search...').type('Read') + + cy.findByLabelText('Reading').should('exist') + cy.findByLabelText('Swimming').should('not.exist') + cy.findByLabelText('Running').should('not.exist') + cy.findByLabelText('Cycling').should('not.exist') + cy.findByLabelText('Cooking').should('not.exist') + cy.findByLabelText('Gardening').should('not.exist') + }) }) it('should debounce the search input correctly', () => { cy.mount( ) @@ -304,11 +494,12 @@ describe('SelectAdvanced', () => { cy.clock().then((clock) => clock.restore()) }) - it('should show count of selected options when isSearchable is false', () => { + it('should show count of selected options when isSearchable is false on multiple selection mode', () => { cy.mount( ) @@ -319,39 +510,294 @@ describe('SelectAdvanced', () => { cy.findByText('2 selected').should('exist') }) - it('should show No Options Found and toggle all chebox be disabled when search does not match any option', () => { + describe('should not show search when isSearchable is false', () => { + it('on single selection', () => { + cy.mount( + + ) + + cy.findByLabelText('Toggle options menu').click() + + cy.findByTestId('select-advanced-searchable-input').should('not.exist') + }) + + it('on multiple selection', () => { + cy.mount( + + ) + + cy.findByLabelText('Toggle options menu').click() + + cy.findByTestId('select-advanced-searchable-input').should('not.exist') + }) + }) + + it('should not show Selection Count on single selection when isSearchable is false', () => { cy.mount( ) cy.findByLabelText('Toggle options menu').click() - cy.findByPlaceholderText('Search...').type('Yoga') + cy.findByText('Reading').click() - cy.findByText('No options found').should('exist') - cy.findByLabelText('Toggle all options').should('be.disabled') + cy.findByTestId('select-advanced-searchable-input').should('not.exist') + cy.findByTestId('select-advanced-selected-count').should('not.exist') }) - it('should be disabled when isDisabled is true', () => { + it('should show Selection Count on multiple selection when isSearchable is false', () => { cy.mount( ) - cy.findByLabelText('Toggle options menu').should('be.disabled') + cy.findByLabelText('Toggle options menu').click() + cy.findByLabelText('Reading').click() + cy.findByLabelText('Swimming').click() + + cy.findByTestId('select-advanced-searchable-input').should('not.exist') + cy.findByTestId('select-advanced-selected-count') + .should('exist') + .should('have.text', '2 selected') }) - it('should be invalid when isInvalid is true', () => { - cy.mount( - - ) + describe('should show No Options Found when search does not match any option', () => { + it('on single selection', () => { + cy.mount( + + ) + + cy.findByLabelText('Toggle options menu').click() + cy.findByPlaceholderText('Search...').type('Yoga') + + cy.findByText('No options found').should('exist') + }) + it('on multiple selection', () => { + cy.mount( + + ) + cy.findByLabelText('Toggle options menu').click() + cy.findByPlaceholderText('Search...').type('Yoga') + + cy.findByText('No options found').should('exist') + cy.findByLabelText('Toggle all options').should('be.disabled') + }) + }) - cy.findByLabelText('Toggle options menu').should('have.attr', 'aria-invalid', 'true') + describe('should be disabled when isDisabled is true', () => { + it('on single selection', () => { + cy.mount( + + ) + + cy.findByLabelText('Toggle options menu').should('be.disabled') + }) + it('on multiple selection', () => { + cy.mount( + + ) + + cy.findByLabelText('Toggle options menu').should('be.disabled') + }) + }) + + describe('should be invalid when isInvalid is true', () => { + it('on single selection', () => { + cy.mount( + + ) + + cy.findByLabelText('Toggle options menu').should('have.attr', 'aria-invalid', 'true') + }) + it('on multiple selection', () => { + cy.mount( + + ) + + cy.findByLabelText('Toggle options menu').should('have.attr', 'aria-invalid', 'true') + }) + }) + + // TODO:ME What happen if available options change to less? + describe('should update available options when options prop changes', () => { + const SelectSingleExample = ({ withDefaultValues }: { withDefaultValues: boolean }) => { + const [availableOptions, setAvailableOptions] = useState(['Reading', 'Swimming', 'Running']) + + return ( + <> + + + + ) + } + + const SelectMultipleExample = ({ withDefaultValues }: { withDefaultValues: boolean }) => { + const [availableOptions, setAvailableOptions] = useState(['Reading', 'Swimming', 'Running']) + + return ( + <> + + + + ) + } + + it('on single selection', () => { + cy.mount() + + cy.findByLabelText('Toggle options menu').click() + cy.findByText('Reading').should('exist') + cy.findByText('Swimming').should('exist') + cy.findByText('Running').should('exist') + cy.findByText('Gardening').should('not.exist') + + cy.findByTestId('add-option-button').click() + + cy.findByText('Reading').should('exist') + cy.findByText('Swimming').should('exist') + cy.findByText('Running').should('exist') + cy.findByText('Gardening').should('exist') + }) + + it('on multiple selection', () => { + cy.mount() + + cy.findByLabelText('Toggle options menu').click() + cy.findByText('Reading').should('exist') + cy.findByLabelText('Swimming').should('exist') + cy.findByLabelText('Running').should('exist') + cy.findByLabelText('Gardening').should('not.exist') + + cy.findByTestId('add-option-button').click() + + cy.findByLabelText('Reading').should('exist') + cy.findByLabelText('Swimming').should('exist') + cy.findByLabelText('Running').should('exist') + cy.findByLabelText('Gardening').should('exist') + }) + + it('on multiple selection after selecting and deselecting all', () => { + cy.mount() + + cy.findByLabelText('Toggle options menu').click() + cy.findByLabelText('Toggle all options').click() + + cy.findByLabelText('Reading').should('be.checked') + cy.findByLabelText('Swimming').should('be.checked') + cy.findByLabelText('Running').should('be.checked') + + cy.findByLabelText('Toggle all options').click() + + cy.findByLabelText('Reading').should('not.be.checked') + cy.findByLabelText('Swimming').should('not.be.checked') + cy.findByLabelText('Running').should('not.be.checked') + + cy.findByTestId('add-option-button').click() + + cy.findByLabelText('Reading').should('exist') + cy.findByLabelText('Swimming').should('exist') + cy.findByLabelText('Running').should('exist') + cy.findByLabelText('Gardening').should('exist') + }) + + it('on multiple selection after selecting all', () => { + cy.mount() + + cy.findByLabelText('Toggle options menu').click() + cy.findByLabelText('Toggle all options').click() + + cy.findByLabelText('Reading').should('be.checked') + cy.findByLabelText('Swimming').should('be.checked') + cy.findByLabelText('Running').should('be.checked') + + cy.findByTestId('add-option-button').click() + + cy.findByLabelText('Toggle options menu').click({ force: true }) + + cy.findByLabelText('Reading').should('be.checked') + cy.findByLabelText('Swimming').should('be.checked') + cy.findByLabelText('Running').should('be.checked') + cy.findByLabelText('Gardening').should('exist').should('not.be.checked') + }) + + it('on single selection and with default value', () => { + cy.mount() + + cy.findByLabelText('Toggle options menu').click() + cy.findAllByText('Reading').should('exist').should('have.length', 2) + cy.findByText('Swimming').should('exist') + cy.findByText('Running').should('exist') + cy.findByText('Gardening').should('not.exist') + + cy.findByTestId('add-option-button').click() + + cy.findAllByText('Reading').should('exist').should('have.length', 2) + cy.findByText('Swimming').should('exist') + cy.findByText('Running').should('exist') + cy.findByText('Gardening').should('exist') + }) + + it('on multiple selection and with default values', () => { + cy.mount() + + cy.findByLabelText('Toggle options menu').click() + cy.findAllByText('Reading').should('exist').should('have.length', 2) + cy.findByLabelText('Swimming').should('exist') + cy.findByLabelText('Running').should('exist') + cy.findByLabelText('Gardening').should('not.exist') + + cy.findByTestId('add-option-button').click() + + cy.findAllByText('Reading').should('exist').should('have.length', 2) + cy.findByLabelText('Swimming').should('exist') + cy.findByLabelText('Running').should('exist') + cy.findByLabelText('Gardening').should('exist') + }) }) }) diff --git a/packages/design-system/tests/component/select-advanced/selectAdvancedReducer.spec.tsx b/packages/design-system/tests/component/select-advanced/selectAdvancedReducer.spec.tsx index b8a93852d..a405df864 100644 --- a/packages/design-system/tests/component/select-advanced/selectAdvancedReducer.spec.tsx +++ b/packages/design-system/tests/component/select-advanced/selectAdvancedReducer.spec.tsx @@ -1,69 +1,101 @@ import { - selectMultipleInitialState, - selectMultipleReducer -} from '../../../src/lib/components/select-multiple/selectMultipleReducer' + SelectAdvancedState, + getSelectAdvancedInitialState, + selectAdvancedReducer +} from '../../../src/lib/components/select-advanced/selectAdvancedReducer' -describe('selectMultipleReducer', () => { +const options = ['Reading', 'Swimming', 'Running'] +const selectWord = 'Select...' + +describe('selectAdvancedReducer', () => { it('should return state if bad action type is passed', () => { - const state = selectMultipleReducer(selectMultipleInitialState, { + const expectedInitialState: SelectAdvancedState = { + options: options, + selected: '', + filteredOptions: [], + searchValue: '', + isMultiple: false, + selectWord + } + + const state = selectAdvancedReducer(getSelectAdvancedInitialState(false, options, selectWord), { // @ts-expect-error - Testing bad action type type: 'BAD_ACTION' }) - expect(state).deep.equal(selectMultipleInitialState) + expect(state).deep.equal(expectedInitialState) }) - it('should select an option', () => { - const state = selectMultipleReducer(selectMultipleInitialState, { - type: 'SELECT_OPTION', - payload: 'Reading' + describe('should select an option', () => { + it('on single select mode', () => { + const state = selectAdvancedReducer( + getSelectAdvancedInitialState(false, options, selectWord), + { + type: 'SELECT_OPTION', + payload: 'Reading' + } + ) + + expect(state.selected).to.include('Reading') + }) + it('on multiple select mode', () => { + const state = selectAdvancedReducer( + getSelectAdvancedInitialState(true, options, selectWord), + { + type: 'SELECT_OPTION', + payload: 'Reading' + } + ) + + expect(state.selected).to.include('Reading') }) - - expect(state.selectedOptions).to.include('Reading') }) it('should remove an option', () => { - const state = selectMultipleReducer( - { ...selectMultipleInitialState, selectedOptions: ['Reading'] }, + const state = selectAdvancedReducer( + { ...getSelectAdvancedInitialState(true, options, selectWord), selected: ['Reading'] }, { type: 'REMOVE_OPTION', payload: 'Reading' } ) - expect(state.selectedOptions).to.not.include('Reading') + expect(state.selected).to.not.include('Reading') }) it('should select all available options when there are no current filtered options', () => { - const state = selectMultipleReducer( - { ...selectMultipleInitialState, options: ['Reading', 'Swimming'] }, + const state = selectAdvancedReducer( + { + ...getSelectAdvancedInitialState(true, options, selectWord), + options: ['Reading', 'Swimming'] + }, { type: 'SELECT_ALL_OPTIONS' } ) - expect(state.selectedOptions).to.deep.equal(['Reading', 'Swimming']) + expect(state.selected).to.deep.equal(['Reading', 'Swimming']) }) it('should deselect all available options when there are no current filtered options', () => { - const state = selectMultipleReducer( + const state = selectAdvancedReducer( { - ...selectMultipleInitialState, + ...getSelectAdvancedInitialState(true, options, selectWord), options: ['Reading', 'Swimming'], - selectedOptions: ['Reading', 'Swimming'] + selected: ['Reading', 'Swimming'] }, { type: 'DESELECT_ALL_OPTIONS' } ) - expect(state.selectedOptions).to.be.empty + expect(state.selected).to.be.empty }) it('should select all filtered options', () => { - const state = selectMultipleReducer( + const state = selectAdvancedReducer( { - ...selectMultipleInitialState, + ...getSelectAdvancedInitialState(true, options, selectWord), options: ['Reading', 'Swimming', 'Running'], filteredOptions: ['Reading', 'Swimming'] }, @@ -72,15 +104,15 @@ describe('selectMultipleReducer', () => { } ) - expect(state.selectedOptions).to.deep.equal(['Reading', 'Swimming']) + expect(state.selected).to.deep.equal(['Reading', 'Swimming']) }) it('should deselect all filtered options', () => { - const state = selectMultipleReducer( + const state = selectAdvancedReducer( { - ...selectMultipleInitialState, + ...getSelectAdvancedInitialState(true, options, selectWord), options: ['Reading', 'Swimming', 'Running'], - selectedOptions: ['Reading', 'Swimming'], + selected: ['Reading', 'Swimming'], filteredOptions: ['Reading', 'Swimming'] }, { @@ -88,15 +120,15 @@ describe('selectMultipleReducer', () => { } ) - expect(state.selectedOptions).to.be.empty + expect(state.selected).to.be.empty }) it('should add filtered options to selected options when selecting all if filtered options are present', () => { - const state = selectMultipleReducer( + const state = selectAdvancedReducer( { - ...selectMultipleInitialState, + ...getSelectAdvancedInitialState(true, options, selectWord), options: ['Reading', 'Swimming', 'Running'], - selectedOptions: ['Reading', 'Swimming'], + selected: ['Reading', 'Swimming'], filteredOptions: ['Running'] }, { @@ -104,12 +136,15 @@ describe('selectMultipleReducer', () => { } ) - expect(state.selectedOptions).to.deep.equal(['Reading', 'Swimming', 'Running']) + expect(state.selected).to.deep.equal(['Reading', 'Swimming', 'Running']) }) it('should filter options', () => { - const state = selectMultipleReducer( - { ...selectMultipleInitialState, options: ['Reading', 'Swimming', 'Running'] }, + const state = selectAdvancedReducer( + { + ...getSelectAdvancedInitialState(true, options, selectWord), + options: ['Reading', 'Swimming', 'Running'] + }, { type: 'SEARCH', payload: 'read' @@ -121,8 +156,8 @@ describe('selectMultipleReducer', () => { }) it('should reset search value when empty string is passed', () => { - const state = selectMultipleReducer( - { ...selectMultipleInitialState, searchValue: 'read' }, + const state = selectAdvancedReducer( + { ...getSelectAdvancedInitialState(true, options, selectWord), searchValue: 'read' }, { type: 'SEARCH', payload: '' @@ -131,4 +166,16 @@ describe('selectMultipleReducer', () => { expect(state.searchValue).to.equal('') }) + + it('should update options', () => { + const state = selectAdvancedReducer( + { ...getSelectAdvancedInitialState(true, options, selectWord), options: ['Reading'] }, + { + type: 'UPDATE_OPTIONS', + payload: ['Reading', 'Swimming'] + } + ) + + expect(state.options).to.deep.equal(['Reading', 'Swimming']) + }) }) diff --git a/packages/design-system/tests/component/select-advanced/utils.spec.ts b/packages/design-system/tests/component/select-advanced/utils.spec.ts new file mode 100644 index 000000000..3d411f5bc --- /dev/null +++ b/packages/design-system/tests/component/select-advanced/utils.spec.ts @@ -0,0 +1,37 @@ +import chai from 'chai' +import chaiAsPromised from 'chai-as-promised' +import { areArraysEqual } from '../../../src/lib/components/select-advanced/utils' + +chai.use(chaiAsPromised) +const expect = chai.expect + +describe('utils', () => { + describe('areArraysEqual', () => { + it('should return true if arrays are equal', () => { + const case1 = areArraysEqual([], []) + const case2 = areArraysEqual( + ['Option 1', 'Option 2', 'Option 3'], + ['Option 1', 'Option 2', 'Option 3'] + ) + const case3 = areArraysEqual( + ['Option 1', 'Option 2', 'Option 3'], + ['Option 1', 'Option 3', 'Option 2'] + ) + const case4 = areArraysEqual(['0', '1', '2', '10'], ['10', '1', '0', '2']) + + expect(case1).to.be.equal(true) + expect(case2).to.be.equal(true) + expect(case3).to.be.equal(true) + expect(case4).to.be.equal(true) + }) + it('should return false if arrays are not equal', () => { + const case1 = areArraysEqual(['Option 1'], ['Option 1', 'Option 2']) + const case2 = areArraysEqual( + ['Option 1', 'Option 2', 'Option 3'], + ['Option 1', 'Option 2', 'Option 4'] + ) + expect(case1).to.be.equal(false) + expect(case2).to.be.equal(false) + }) + }) +}) From 9da184c6622f93c6bb6b7929172bea274cefa9e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Fri, 28 Jun 2024 20:19:28 -0300 Subject: [PATCH 08/12] feat(design system): adding more tests --- .../select-advanced/SelectAdvanced.tsx | 50 +- .../SelectAdvanced.stories.tsx | 14 +- .../select-advanced/SelectAdvanced.spec.tsx | 462 ++++++++++++++---- 3 files changed, 421 insertions(+), 105 deletions(-) diff --git a/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx index afcedcb82..6908aeda6 100644 --- a/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx +++ b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx @@ -107,19 +107,65 @@ export const SelectAdvanced = forwardRef( if (selectedOptionRemainTheSame) return } - + // console.log('%cOn Change', 'background: green; color: white; padding: 4px;') isMultiple ? onChange(selected as string[]) : onChange(selected as string) setLastOnChangeValue(selected) } }, [isMultiple, selected, isFirstRender, onChange, lastOnChangeValue, defaultValue]) + useEffect(() => { + console.log('%cselected: ', 'background: green; color: white; padding: 4px;') + console.log({ selected }) + }, [selected]) + useEffect(() => { const optionsRemainTheSame = propsOption.every((option) => options.includes(option)) + // If the options remain the same, do nothing if (optionsRemainTheSame) return + const selectedOptionsThatAreNotInNewOptions = isMultiple + ? (selected as string[]).filter((option) => !propsOption.includes(option)) + : [] + + // If there are selected options that are not in the new options, remove them + if (isMultiple && selectedOptionsThatAreNotInNewOptions.length > 0) { + selectedOptionsThatAreNotInNewOptions.forEach((option) => dispatch(removeOption(option))) + const newSelected = (selected as string[]).filter((option) => propsOption.includes(option)) + + if (onChange) { + onChange(newSelected) + // console.log('%cOn Change new selected', 'background: green; color: white; padding: 4px;') + setLastOnChangeValue(newSelected) + } + } + // If the selected option is not in the new options replace it with the default empty value + + if ( + !isMultiple && + selected !== '' && + !propsOption.some((option) => option === (selected as string)) + ) { + dispatch(selectOption('')) + + if (onChange) { + onChange('') + // console.log('%cOn Change to " " ', 'background: green; color: white; padding: 4px;') + setLastOnChangeValue('') + } + } + // Update the options dispatch(updateOptions(dynamicInitialOptions)) - }, [dynamicInitialOptions, propsOption, options, isFirstRender, dispatch]) + }, [ + dynamicInitialOptions, + propsOption, + options, + isFirstRender, + dispatch, + selected, + isMultiple, + onChange + ]) const handleSearch = debounce((e: React.ChangeEvent): void => { const { value } = e.target diff --git a/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx b/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx index c7ef62359..0a2e20f49 100644 --- a/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx +++ b/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx @@ -97,6 +97,9 @@ export const WithDifferentSelectWord: Story = { const SimulateChangeOfAvailableOptions = () => { const [availableOptions, setAvailableOptions] = useState(['Tag 1', 'Tag 2', 'Tag 3']) + const newOptions = ['Foo', 'Bar', 'Ron', 'Hermione'] + const newOptions2 = ['Foo', 'Tag 2', 'Ron', 'Hermione', 'Harry'] + return ( <> + +
console.log(selected)} + onChange={(selectedFromIsMultiple1) => console.log({ selectedFromIsMultiple1 })} /> console.log(selected)} + onChange={(selectedFromIsMultiple2) => console.log({ selectedFromIsMultiple2 })} + /> + console.log({ selected1 })} />
diff --git a/packages/design-system/tests/component/select-advanced/SelectAdvanced.spec.tsx b/packages/design-system/tests/component/select-advanced/SelectAdvanced.spec.tsx index 7697387de..5bda6c73c 100644 --- a/packages/design-system/tests/component/select-advanced/SelectAdvanced.spec.tsx +++ b/packages/design-system/tests/component/select-advanced/SelectAdvanced.spec.tsx @@ -649,155 +649,415 @@ describe('SelectAdvanced', () => { }) }) - // TODO:ME What happen if available options change to less? - describe('should update available options when options prop changes', () => { - const SelectSingleExample = ({ withDefaultValues }: { withDefaultValues: boolean }) => { + describe('when options props changes', () => { + const ADD_OPTION_BUTTON_TEST_ID = 'add-option-button' + const CHANGE_ALL_OPTIONS_BUTTON_TEST_ID = 'change-all-options-button' + const CHANGE_ALL_ONE_KEEP_OPTION_BUTTON_TEST_ID = 'chage-all-one-keep-option-button' + const ALL_NEW_OPTIONS = ['Foo', 'Bar', 'Ron', 'Hermione'] + const NEW_OPTIONS_BUT_ONE_REMAIN_THE_SAME = ['Foo', 'Reading', 'Ron', 'Hermione', 'Harry'] + + const SelectWithButtonsToChangeOptions = ({ + isMultiple, + withDefaultValues, + onChange + }: { + isMultiple: boolean + withDefaultValues: boolean + onChange?: (value: string | string[]) => void + }) => { const [availableOptions, setAvailableOptions] = useState(['Reading', 'Swimming', 'Running']) return ( <> - - - ) - } - const SelectMultipleExample = ({ withDefaultValues }: { withDefaultValues: boolean }) => { - const [availableOptions, setAvailableOptions] = useState(['Reading', 'Swimming', 'Running']) - - return ( - <> + + {/* @ts-expect-error type boolean is not assignable to type true */} ) } + describe('adds one option correctly', () => { + it('on single selection', () => { + cy.mount() - it('on single selection', () => { - cy.mount() + cy.findByLabelText('Toggle options menu').click() + cy.findByText('Reading').should('exist') + cy.findByText('Swimming').should('exist') + cy.findByText('Running').should('exist') + cy.findByText('Gardening').should('not.exist') - cy.findByLabelText('Toggle options menu').click() - cy.findByText('Reading').should('exist') - cy.findByText('Swimming').should('exist') - cy.findByText('Running').should('exist') - cy.findByText('Gardening').should('not.exist') + cy.findByTestId(ADD_OPTION_BUTTON_TEST_ID).click() - cy.findByTestId('add-option-button').click() + cy.findByText('Reading').should('exist') + cy.findByText('Swimming').should('exist') + cy.findByText('Running').should('exist') + cy.findByText('Gardening').should('exist') + }) - cy.findByText('Reading').should('exist') - cy.findByText('Swimming').should('exist') - cy.findByText('Running').should('exist') - cy.findByText('Gardening').should('exist') - }) + it('on single selection with default value', () => { + cy.mount() - it('on multiple selection', () => { - cy.mount() + cy.findByLabelText('Toggle options menu').click() + cy.findAllByText('Reading').should('exist').should('have.length', 2) + cy.findByText('Swimming').should('exist') + cy.findByText('Running').should('exist') + cy.findByText('Gardening').should('not.exist') - cy.findByLabelText('Toggle options menu').click() - cy.findByText('Reading').should('exist') - cy.findByLabelText('Swimming').should('exist') - cy.findByLabelText('Running').should('exist') - cy.findByLabelText('Gardening').should('not.exist') + cy.findByTestId(ADD_OPTION_BUTTON_TEST_ID).click() - cy.findByTestId('add-option-button').click() + cy.findAllByText('Reading').should('exist').should('have.length', 2) + cy.findByText('Swimming').should('exist') + cy.findByText('Running').should('exist') + cy.findByText('Gardening').should('exist') + }) - cy.findByLabelText('Reading').should('exist') - cy.findByLabelText('Swimming').should('exist') - cy.findByLabelText('Running').should('exist') - cy.findByLabelText('Gardening').should('exist') + it('on multiple selection', () => { + cy.mount() + + cy.findByLabelText('Toggle options menu').click() + cy.findByLabelText('Reading').should('exist') + cy.findByLabelText('Swimming').should('exist') + cy.findByLabelText('Running').should('exist') + cy.findByLabelText('Gardening').should('not.exist') + + cy.findByTestId(ADD_OPTION_BUTTON_TEST_ID).click() + + cy.findByLabelText('Reading').should('exist') + cy.findByLabelText('Swimming').should('exist') + cy.findByLabelText('Running').should('exist') + cy.findByLabelText('Gardening').should('exist') + }) + it('on multiple selection with default values', () => { + cy.mount() + + cy.findByLabelText('Toggle options menu').click() + cy.findByLabelText('Reading').should('exist') + cy.findByLabelText('Swimming').should('exist') + cy.findByLabelText('Running').should('exist') + cy.findByLabelText('Gardening').should('not.exist') + + cy.findByTestId(ADD_OPTION_BUTTON_TEST_ID).click() + + cy.findByLabelText('Reading').should('exist') + cy.findByLabelText('Swimming').should('exist') + cy.findByLabelText('Running').should('exist') + cy.findByLabelText('Gardening').should('exist') + }) }) - it('on multiple selection after selecting and deselecting all', () => { - cy.mount() + describe('adds completely new options correctly', () => { + it('on single selection', () => { + cy.mount() - cy.findByLabelText('Toggle options menu').click() - cy.findByLabelText('Toggle all options').click() + cy.findByLabelText('Toggle options menu').click() + cy.findByText('Reading').should('exist') + cy.findByText('Swimming').should('exist') + cy.findByText('Running').should('exist') - cy.findByLabelText('Reading').should('be.checked') - cy.findByLabelText('Swimming').should('be.checked') - cy.findByLabelText('Running').should('be.checked') + cy.findByTestId(CHANGE_ALL_OPTIONS_BUTTON_TEST_ID).click() - cy.findByLabelText('Toggle all options').click() + cy.findByText('Reading').should('not.exist') + cy.findByText('Swimming').should('not.exist') + cy.findByText('Running').should('not.exist') - cy.findByLabelText('Reading').should('not.be.checked') - cy.findByLabelText('Swimming').should('not.be.checked') - cy.findByLabelText('Running').should('not.be.checked') + ALL_NEW_OPTIONS.forEach((option) => { + cy.findByText(option).should('exist') + }) + }) - cy.findByTestId('add-option-button').click() + it('on single selection with default value', () => { + cy.mount() - cy.findByLabelText('Reading').should('exist') - cy.findByLabelText('Swimming').should('exist') - cy.findByLabelText('Running').should('exist') - cy.findByLabelText('Gardening').should('exist') - }) + cy.findByLabelText('Toggle options menu').click() + cy.findAllByText('Reading').should('exist').should('have.length', 2) + cy.findByText('Swimming').should('exist') + cy.findByText('Running').should('exist') - it('on multiple selection after selecting all', () => { - cy.mount() + cy.findByTestId(CHANGE_ALL_OPTIONS_BUTTON_TEST_ID).click() - cy.findByLabelText('Toggle options menu').click() - cy.findByLabelText('Toggle all options').click() + cy.findByText('Reading').should('not.exist') + cy.findByText('Swimming').should('not.exist') + cy.findByText('Running').should('not.exist') - cy.findByLabelText('Reading').should('be.checked') - cy.findByLabelText('Swimming').should('be.checked') - cy.findByLabelText('Running').should('be.checked') + ALL_NEW_OPTIONS.forEach((option) => { + cy.findByText(option).should('exist') + }) + }) - cy.findByTestId('add-option-button').click() + it('on multiple selection', () => { + cy.mount() - cy.findByLabelText('Toggle options menu').click({ force: true }) + cy.findByLabelText('Toggle options menu').click() + cy.findByLabelText('Reading').should('exist') + cy.findByLabelText('Reading').should('exist') + cy.findByLabelText('Running').should('exist') - cy.findByLabelText('Reading').should('be.checked') - cy.findByLabelText('Swimming').should('be.checked') - cy.findByLabelText('Running').should('be.checked') - cy.findByLabelText('Gardening').should('exist').should('not.be.checked') - }) + cy.findByTestId(CHANGE_ALL_OPTIONS_BUTTON_TEST_ID).click() - it('on single selection and with default value', () => { - cy.mount() + cy.findByText('Reading').should('not.exist') + cy.findByText('Swimming').should('not.exist') + cy.findByText('Running').should('not.exist') - cy.findByLabelText('Toggle options menu').click() - cy.findAllByText('Reading').should('exist').should('have.length', 2) - cy.findByText('Swimming').should('exist') - cy.findByText('Running').should('exist') - cy.findByText('Gardening').should('not.exist') + ALL_NEW_OPTIONS.forEach((option) => { + cy.findByText(option).should('exist') + }) + }) - cy.findByTestId('add-option-button').click() + it('on multiple selection with default values', () => { + cy.mount() - cy.findAllByText('Reading').should('exist').should('have.length', 2) - cy.findByText('Swimming').should('exist') - cy.findByText('Running').should('exist') - cy.findByText('Gardening').should('exist') + cy.findByLabelText('Toggle options menu').click() + cy.findAllByText('Reading').should('exist').should('have.length', 2) + cy.findAllByText('Reading').should('exist').should('have.length', 2) + cy.findByLabelText('Running').should('exist') + + cy.findByTestId(CHANGE_ALL_OPTIONS_BUTTON_TEST_ID).click() + + cy.findByText('Reading').should('not.exist') + cy.findByText('Swimming').should('not.exist') + cy.findByText('Running').should('not.exist') + + ALL_NEW_OPTIONS.forEach((option) => { + cy.findByText(option).should('exist') + }) + }) }) - it('on multiple selection and with default values', () => { - cy.mount() + describe('adds new options but keeps one option the same correctly', () => { + it('on single selection', () => { + cy.mount() - cy.findByLabelText('Toggle options menu').click() - cy.findAllByText('Reading').should('exist').should('have.length', 2) - cy.findByLabelText('Swimming').should('exist') - cy.findByLabelText('Running').should('exist') - cy.findByLabelText('Gardening').should('not.exist') + cy.findByLabelText('Toggle options menu').click() + cy.findByText('Reading').should('exist') + cy.findByText('Swimming').should('exist') + cy.findByText('Running').should('exist') + + cy.findByTestId(CHANGE_ALL_ONE_KEEP_OPTION_BUTTON_TEST_ID).click() + // This option remains + cy.findByText('Reading').should('exist') + cy.findByText('Swimming').should('not.exist') + cy.findByText('Running').should('not.exist') + + NEW_OPTIONS_BUT_ONE_REMAIN_THE_SAME.filter((option) => option !== 'Reading').forEach( + (option) => { + cy.findByText(option).should('exist') + } + ) + }) + + it('on single selection with default value', () => { + cy.mount() + + cy.findByLabelText('Toggle options menu').click() + cy.findAllByText('Reading').should('exist').should('have.length', 2) + cy.findByText('Swimming').should('exist') + cy.findByText('Running').should('exist') + + cy.findByTestId(CHANGE_ALL_ONE_KEEP_OPTION_BUTTON_TEST_ID).click() + + // This option remains + cy.findAllByText('Reading').should('exist').should('have.length', 2) + cy.findByText('Swimming').should('not.exist') + cy.findByText('Running').should('not.exist') + + NEW_OPTIONS_BUT_ONE_REMAIN_THE_SAME.filter((option) => option !== 'Reading').forEach( + (option) => { + cy.findByText(option).should('exist') + } + ) + }) + + it('on multiple selection', () => { + cy.mount() + + cy.findByLabelText('Toggle options menu').click() + cy.findByText('Reading').should('exist') + cy.findByText('Running').should('exist') + cy.findByText('Swimming').should('exist') + + cy.findByTestId(CHANGE_ALL_ONE_KEEP_OPTION_BUTTON_TEST_ID).click() - cy.findByTestId('add-option-button').click() + // This option remains + cy.findByText('Reading').should('exist') + cy.findByText('Swimming').should('not.exist') + cy.findByText('Running').should('not.exist') - cy.findAllByText('Reading').should('exist').should('have.length', 2) - cy.findByLabelText('Swimming').should('exist') - cy.findByLabelText('Running').should('exist') - cy.findByLabelText('Gardening').should('exist') + NEW_OPTIONS_BUT_ONE_REMAIN_THE_SAME.filter((option) => option !== 'Reading').forEach( + (option) => { + cy.findByLabelText(option).should('exist') + } + ) + }) + it('on multiple selection with default values', () => { + cy.mount() + + cy.findByLabelText('Toggle options menu').click() + cy.findAllByText('Reading').should('exist').should('have.length', 2) + cy.findByLabelText('Running').should('exist') + cy.findByLabelText('Swimming').should('exist') + + cy.findByTestId(CHANGE_ALL_ONE_KEEP_OPTION_BUTTON_TEST_ID).click() + + // This option remains + cy.findAllByText('Reading').should('exist').should('have.length', 2) + cy.findByText('Swimming').should('not.exist') + cy.findByText('Running').should('not.exist') + + NEW_OPTIONS_BUT_ONE_REMAIN_THE_SAME.filter((option) => option !== 'Reading').forEach( + (option) => { + cy.findByLabelText(option).should('exist') + } + ) + }) }) + + // it('on multiple selection', () => { + // cy.mount() + + // cy.findByLabelText('Toggle options menu').click() + // cy.findByText('Reading').should('exist') + // cy.findByLabelText('Swimming').should('exist') + // cy.findByLabelText('Running').should('exist') + // cy.findByLabelText('Gardening').should('not.exist') + + // cy.findByTestId('add-option-button').click() + + // cy.findByLabelText('Reading').should('exist') + // cy.findByLabelText('Swimming').should('exist') + // cy.findByLabelText('Running').should('exist') + // cy.findByLabelText('Gardening').should('exist') + // }) + + // it('on multiple selection after selecting and deselecting all', () => { + // cy.mount() + + // cy.findByLabelText('Toggle options menu').click() + // cy.findByLabelText('Toggle all options').click() + + // cy.findByLabelText('Reading').should('be.checked') + // cy.findByLabelText('Swimming').should('be.checked') + // cy.findByLabelText('Running').should('be.checked') + + // cy.findByLabelText('Toggle all options').click() + + // cy.findByLabelText('Reading').should('not.be.checked') + // cy.findByLabelText('Swimming').should('not.be.checked') + // cy.findByLabelText('Running').should('not.be.checked') + + // cy.findByTestId('add-option-button').click() + + // cy.findByLabelText('Reading').should('exist') + // cy.findByLabelText('Swimming').should('exist') + // cy.findByLabelText('Running').should('exist') + // cy.findByLabelText('Gardening').should('exist') + // }) + + // it('on multiple selection after selecting all', () => { + // cy.mount() + + // cy.findByLabelText('Toggle options menu').click() + // cy.findByLabelText('Toggle all options').click() + + // cy.findByLabelText('Reading').should('be.checked') + // cy.findByLabelText('Swimming').should('be.checked') + // cy.findByLabelText('Running').should('be.checked') + + // cy.findByTestId('add-option-button').click() + + // cy.findByLabelText('Toggle options menu').click({ force: true }) + + // cy.findByLabelText('Reading').should('be.checked') + // cy.findByLabelText('Swimming').should('be.checked') + // cy.findByLabelText('Running').should('be.checked') + // cy.findByLabelText('Gardening').should('exist').should('not.be.checked') + // }) + + // it('on single selection and with default value', () => { + // cy.mount() + + // cy.findByLabelText('Toggle options menu').click() + // cy.findAllByText('Reading').should('exist').should('have.length', 2) + // cy.findByText('Swimming').should('exist') + // cy.findByText('Running').should('exist') + // cy.findByText('Gardening').should('not.exist') + + // cy.findByTestId('add-option-button').click() + + // cy.findAllByText('Reading').should('exist').should('have.length', 2) + // cy.findByText('Swimming').should('exist') + // cy.findByText('Running').should('exist') + // cy.findByText('Gardening').should('exist') + // }) + + // it('on multiple selection and with default values', () => { + // cy.mount() + + // cy.findByLabelText('Toggle options menu').click() + // cy.findAllByText('Reading').should('exist').should('have.length', 2) + // cy.findByLabelText('Swimming').should('exist') + // cy.findByLabelText('Running').should('exist') + // cy.findByLabelText('Gardening').should('not.exist') + + // cy.findByTestId('add-option-button').click() + + // cy.findAllByText('Reading').should('exist').should('have.length', 2) + // cy.findByLabelText('Swimming').should('exist') + // cy.findByLabelText('Running').should('exist') + // cy.findByLabelText('Gardening').should('exist') + // }) + }) + + it('selects the "Select..." option correctly', () => { + cy.mount( + + ) + + cy.findByLabelText('Toggle options menu').click() + cy.findByText('Swimming').click() + cy.findAllByText('Swimming').should('have.length', 2) + + cy.findByText('Select...').click() + + cy.findAllByText('Select...').should('have.length', 2) + }) + + it('selects the custom Select word option correctly', () => { + cy.mount( + + ) + + cy.findByLabelText('Toggle options menu').click() + cy.findByText('Swimming').click() + cy.findAllByText('Swimming').should('have.length', 2) + + cy.findByText('Selezionare...').click() + + cy.findAllByText('Selezionare...').should('have.length', 2) }) }) From a134edaee4a6617f9768cc066e81526d30bca061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Sat, 29 Jun 2024 15:46:26 -0300 Subject: [PATCH 09/12] feat(design system): all working correctly --- .../select-advanced/SelectAdvanced.tsx | 117 ++++--- .../SelectAdvanced.stories.tsx | 52 +--- .../select-advanced/SelectAdvanced.spec.tsx | 285 +++++++++++------- 3 files changed, 236 insertions(+), 218 deletions(-) diff --git a/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx index 6908aeda6..7426d553a 100644 --- a/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx +++ b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useMemo, useId, useReducer, forwardRef, ForwardedRef } from 'react' +import { useEffect, useMemo, useId, useReducer, forwardRef, ForwardedRef, useCallback } from 'react' import { Dropdown as DropdownBS } from 'react-bootstrap' import { selectAdvancedReducer, @@ -80,121 +80,108 @@ export const SelectAdvanced = forwardRef( const isFirstRender = useIsFirstRender() const menuId = useId() - const [lastOnChangeValue, setLastOnChangeValue] = useState( - isMultiple ? [] : '' - ) - useEffect(() => { - if (!isFirstRender && onChange) { - // Dont call onChange if the selected options (string[]) remain the same - if (isMultiple) { - const selectedOptionsRemainTheSame = areArraysEqual( - selected as string[], - defaultValue && (lastOnChangeValue as string[])?.length === 0 - ? defaultValue - : (lastOnChangeValue as string[]) - ) - - if (selectedOptionsRemainTheSame) return - } - // Dont call onChange if the selected option (string) remain the same - if (!isMultiple) { - const compareAgainst = - defaultValue && (lastOnChangeValue as string) === '' - ? defaultValue - : (lastOnChangeValue as string) - const selectedOptionRemainTheSame = selected === compareAgainst - - if (selectedOptionRemainTheSame) return - } - // console.log('%cOn Change', 'background: green; color: white; padding: 4px;') - isMultiple ? onChange(selected as string[]) : onChange(selected as string) - setLastOnChangeValue(selected) - } - }, [isMultiple, selected, isFirstRender, onChange, lastOnChangeValue, defaultValue]) - - useEffect(() => { - console.log('%cselected: ', 'background: green; color: white; padding: 4px;') - console.log({ selected }) - }, [selected]) + const callOnChage = useCallback( + (newSelected: string | string[]): void => { + if (!onChange) return + //@ts-expect-error - types differs + onChange(newSelected) + }, + [onChange] + ) useEffect(() => { - const optionsRemainTheSame = propsOption.every((option) => options.includes(option)) + const optionsRemainTheSame = areArraysEqual(dynamicInitialOptions, options) // If the options remain the same, do nothing if (optionsRemainTheSame) return const selectedOptionsThatAreNotInNewOptions = isMultiple - ? (selected as string[]).filter((option) => !propsOption.includes(option)) + ? (selected as string[]).filter((option) => !dynamicInitialOptions.includes(option)) : [] // If there are selected options that are not in the new options, remove them if (isMultiple && selectedOptionsThatAreNotInNewOptions.length > 0) { selectedOptionsThatAreNotInNewOptions.forEach((option) => dispatch(removeOption(option))) - const newSelected = (selected as string[]).filter((option) => propsOption.includes(option)) - if (onChange) { - onChange(newSelected) - // console.log('%cOn Change new selected', 'background: green; color: white; padding: 4px;') - setLastOnChangeValue(newSelected) - } + const newSelected = (selected as string[]).filter((option) => + dynamicInitialOptions.includes(option) + ) + + callOnChage(newSelected) } - // If the selected option is not in the new options replace it with the default empty value + // If the selected option is not in the new options replace it with the default empty value if ( !isMultiple && selected !== '' && - !propsOption.some((option) => option === (selected as string)) + !dynamicInitialOptions.some((option) => option === (selected as string)) ) { dispatch(selectOption('')) - - if (onChange) { - onChange('') - // console.log('%cOn Change to " " ', 'background: green; color: white; padding: 4px;') - setLastOnChangeValue('') - } + callOnChage('') } - // Update the options dispatch(updateOptions(dynamicInitialOptions)) - }, [ - dynamicInitialOptions, - propsOption, - options, - isFirstRender, - dispatch, - selected, - isMultiple, - onChange - ]) + }, [dynamicInitialOptions, options, selected, isFirstRender, dispatch, callOnChage, isMultiple]) const handleSearch = debounce((e: React.ChangeEvent): void => { const { value } = e.target dispatch(searchOptions(value)) }, SELECT_MENU_SEARCH_DEBOUNCE_TIME) + // ONLY FOR MULTIPLE SELECT 👇 const handleCheck = (e: React.ChangeEvent): void => { const { value, checked } = e.target if (checked) { + const newSelected = [...(selected as string[]), value] + callOnChage(newSelected) + dispatch(selectOption(value)) } else { + const newSelected = (selected as string[]).filter((option) => option !== value) + callOnChage(newSelected) + dispatch(removeOption(value)) } } + // ONLY FOR SINGLE SELECT 👇 const handleClickOption = (option: string): void => { if ((selected as string) === option) { return } + callOnChage(option) + dispatch(selectOption(option)) } - const handleRemoveSelectedOption = (option: string): void => dispatch(removeOption(option)) + // ONLY FOR MULTIPLE SELECT 👇 + const handleRemoveSelectedOption = (option: string): void => { + const newSelected = (selected as string[]).filter((selected) => selected !== option) + callOnChage(newSelected) + + dispatch(removeOption(option)) + } + // ONLY FOR MULTIPLE SELECT 👇 const handleToggleAllOptions = (e: React.ChangeEvent): void => { if (e.target.checked) { + const newSelected = + filteredOptions.length > 0 + ? Array.from(new Set([...(selected as string[]), ...filteredOptions])) + : options + + callOnChage(newSelected) + dispatch(selectAllOptions()) } else { + const newSelected = + filteredOptions.length > 0 + ? (selected as string[]).filter((option) => !filteredOptions.includes(option)) + : [] + + callOnChage(newSelected) + dispatch(deselectAllOptions()) } } diff --git a/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx b/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx index 0a2e20f49..e71f30593 100644 --- a/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx +++ b/packages/design-system/src/lib/stories/select-advanced/SelectAdvanced.stories.tsx @@ -1,12 +1,11 @@ import type { Meta, StoryObj } from '@storybook/react' import { SelectAdvanced } from '../../components/select-advanced/SelectAdvanced' -import { Button } from '../../components/button/Button' + import { CanvasFixedHeight } from '../CanvasFixedHeight' -import { useState } from 'react' /** * ## Description - * The select advanced component is a user interface element that allows users to select one or multiple options from a list of items. + * The select advanced component is an element that allows users to select one or multiple options from a list of items. * They can also search for items in the list, select all items and clear the selection (last two on multiple selection mode). */ const meta: Meta = { @@ -93,50 +92,3 @@ export const WithDifferentSelectWord: Story = { ) } - -const SimulateChangeOfAvailableOptions = () => { - const [availableOptions, setAvailableOptions] = useState(['Tag 1', 'Tag 2', 'Tag 3']) - - const newOptions = ['Foo', 'Bar', 'Ron', 'Hermione'] - const newOptions2 = ['Foo', 'Tag 2', 'Ron', 'Hermione', 'Harry'] - - return ( - <> - - - -
- console.log({ selectedFromIsMultiple1 })} - /> - console.log({ selectedFromIsMultiple2 })} - /> - console.log({ selected1 })} - /> -
- - ) -} - -export const ChangeOfAvailablesOptionsCase: Story = { - render: () => ( - - - - ) -} diff --git a/packages/design-system/tests/component/select-advanced/SelectAdvanced.spec.tsx b/packages/design-system/tests/component/select-advanced/SelectAdvanced.spec.tsx index 5bda6c73c..751c00f01 100644 --- a/packages/design-system/tests/component/select-advanced/SelectAdvanced.spec.tsx +++ b/packages/design-system/tests/component/select-advanced/SelectAdvanced.spec.tsx @@ -100,7 +100,9 @@ describe('SelectAdvanced', () => { cy.findByText('Reading').click() cy.get('@onChange').should('have.been.calledOnce') + cy.get('@onChange').should('have.been.calledWith', 'Reading') }) + it('on multiple selection', () => { const onChange = cy.stub().as('onChange') @@ -115,6 +117,7 @@ describe('SelectAdvanced', () => { cy.findByLabelText('Reading').click() cy.get('@onChange').should('have.been.calledOnce') + cy.get('@onChange').should('have.been.calledWith', ['Reading']) }) }) @@ -123,6 +126,25 @@ describe('SelectAdvanced', () => { it('on multiple selection', () => { const onChange = cy.stub().as('onChange') + cy.mount( + + ) + cy.findByLabelText('Toggle options menu').click() + cy.findByLabelText('Reading').click() + cy.get('@onChange').should('have.been.calledWith', ['Reading']) + + cy.findByLabelText('Reading').click() + cy.get('@onChange').should('have.been.calledWith', []) + + cy.get('@onChange').should('have.been.calledTwice') + }) + it('on multiple selection with default value', () => { + const onChange = cy.stub().as('onChange') + cy.mount( { cy.findByLabelText('Reading').click() cy.get('@onChange').should('have.been.calledOnce') + cy.get('@onChange').should('have.been.calledWith', ['Running']) }) }) @@ -167,6 +190,89 @@ describe('SelectAdvanced', () => { }) }) + describe('should call onChange correct times after multiple types of selections', () => { + it('on single selection', () => { + const onChange = cy.stub().as('onChange') + + cy.mount( + + ) + + cy.findByLabelText('Toggle options menu').click() + + // 6 different individual selections + cy.findByText('Reading').click() + cy.findByText('Swimming').click() + cy.findByText('Running').click() + cy.findByText('Cycling').click() + cy.findByText('Reading').click() + cy.findByText('Swimming').click() + + cy.get('@onChange').should('have.callCount', 6) + cy.get('@onChange').should('have.been.calledWith', 'Reading') + cy.get('@onChange').should('have.been.calledWith', 'Swimming') + cy.get('@onChange').should('have.been.calledWith', 'Running') + cy.get('@onChange').should('have.been.calledWith', 'Cycling') + }) + + it('on multiple selection', () => { + const onChange = cy.stub().as('onChange') + + cy.mount( + + ) + + cy.findByLabelText('Toggle options menu').click() + + // 4 individual selections + cy.findByLabelText('Reading').click() + cy.findByLabelText('Swimming').click() + cy.findByLabelText('Running').click() + cy.findByLabelText('Cycling').click() + + // Select all options + cy.findByLabelText('Toggle all options').click() + + // Now deselect 1 option individually + cy.findByLabelText('Cooking').click() + + cy.get('@onChange').should('have.callCount', 6) + cy.get('@onChange').should('have.been.calledWith', ['Reading']) + cy.get('@onChange').should('have.been.calledWith', ['Reading', 'Swimming']) + cy.get('@onChange').should('have.been.calledWith', ['Reading', 'Swimming', 'Running']) + cy.get('@onChange').should('have.been.calledWith', [ + 'Reading', + 'Swimming', + 'Running', + 'Cycling' + ]) + // When selecting all + cy.get('@onChange').should('have.been.calledWith', [ + 'Reading', + 'Swimming', + 'Running', + 'Cycling', + 'Cooking', + 'Gardening' + ]) + // When deselecting Cooking + cy.get('@onChange').should('have.been.calledWith', [ + 'Reading', + 'Swimming', + 'Running', + 'Cycling', + 'Gardening' + ]) + }) + }) + describe('should select an option and be shown as selected both in the menu as well as in the selected options', () => { it('on single selection', () => { cy.mount( @@ -787,7 +893,15 @@ describe('SelectAdvanced', () => { }) it('on single selection with default value', () => { - cy.mount() + const onChange = cy.stub().as('onChange') + + cy.mount( + + ) cy.findByLabelText('Toggle options menu').click() cy.findAllByText('Reading').should('exist').should('have.length', 2) @@ -803,10 +917,20 @@ describe('SelectAdvanced', () => { ALL_NEW_OPTIONS.forEach((option) => { cy.findByText(option).should('exist') }) + + cy.get('@onChange').should('have.been.calledOnce') }) it('on multiple selection', () => { - cy.mount() + const onChange = cy.stub().as('onChange') + + cy.mount( + + ) cy.findByLabelText('Toggle options menu').click() cy.findByLabelText('Reading').should('exist') @@ -822,10 +946,19 @@ describe('SelectAdvanced', () => { ALL_NEW_OPTIONS.forEach((option) => { cy.findByText(option).should('exist') }) + + cy.get('@onChange').should('have.been.not.called') }) it('on multiple selection with default values', () => { - cy.mount() + const onChange = cy.stub().as('onChange') + cy.mount( + + ) cy.findByLabelText('Toggle options menu').click() cy.findAllByText('Reading').should('exist').should('have.length', 2) @@ -841,12 +974,22 @@ describe('SelectAdvanced', () => { ALL_NEW_OPTIONS.forEach((option) => { cy.findByText(option).should('exist') }) + + cy.get('@onChange').should('have.been.calledOnce') }) }) describe('adds new options but keeps one option the same correctly', () => { it('on single selection', () => { - cy.mount() + const onChange = cy.stub().as('onChange') + + cy.mount( + + ) cy.findByLabelText('Toggle options menu').click() cy.findByText('Reading').should('exist') @@ -864,10 +1007,20 @@ describe('SelectAdvanced', () => { cy.findByText(option).should('exist') } ) + + cy.get('@onChange').should('have.been.not.called') }) it('on single selection with default value', () => { - cy.mount() + const onChange = cy.stub().as('onChange') + + cy.mount( + + ) cy.findByLabelText('Toggle options menu').click() cy.findAllByText('Reading').should('exist').should('have.length', 2) @@ -886,10 +1039,20 @@ describe('SelectAdvanced', () => { cy.findByText(option).should('exist') } ) + + cy.get('@onChange').should('have.been.not.called') }) it('on multiple selection', () => { - cy.mount() + const onChange = cy.stub().as('onChange') + + cy.mount( + + ) cy.findByLabelText('Toggle options menu').click() cy.findByText('Reading').should('exist') @@ -908,13 +1071,23 @@ describe('SelectAdvanced', () => { cy.findByLabelText(option).should('exist') } ) + + cy.get('@onChange').should('have.been.not.called') }) it('on multiple selection with default values', () => { - cy.mount() + const onChange = cy.stub().as('onChange') + + cy.mount( + + ) cy.findByLabelText('Toggle options menu').click() cy.findAllByText('Reading').should('exist').should('have.length', 2) - cy.findByLabelText('Running').should('exist') + cy.findAllByText('Running').should('exist').should('have.length', 2) cy.findByLabelText('Swimming').should('exist') cy.findByTestId(CHANGE_ALL_ONE_KEEP_OPTION_BUTTON_TEST_ID).click() @@ -929,103 +1102,9 @@ describe('SelectAdvanced', () => { cy.findByLabelText(option).should('exist') } ) + cy.get('@onChange').should('have.been.calledOnce') }) }) - - // it('on multiple selection', () => { - // cy.mount() - - // cy.findByLabelText('Toggle options menu').click() - // cy.findByText('Reading').should('exist') - // cy.findByLabelText('Swimming').should('exist') - // cy.findByLabelText('Running').should('exist') - // cy.findByLabelText('Gardening').should('not.exist') - - // cy.findByTestId('add-option-button').click() - - // cy.findByLabelText('Reading').should('exist') - // cy.findByLabelText('Swimming').should('exist') - // cy.findByLabelText('Running').should('exist') - // cy.findByLabelText('Gardening').should('exist') - // }) - - // it('on multiple selection after selecting and deselecting all', () => { - // cy.mount() - - // cy.findByLabelText('Toggle options menu').click() - // cy.findByLabelText('Toggle all options').click() - - // cy.findByLabelText('Reading').should('be.checked') - // cy.findByLabelText('Swimming').should('be.checked') - // cy.findByLabelText('Running').should('be.checked') - - // cy.findByLabelText('Toggle all options').click() - - // cy.findByLabelText('Reading').should('not.be.checked') - // cy.findByLabelText('Swimming').should('not.be.checked') - // cy.findByLabelText('Running').should('not.be.checked') - - // cy.findByTestId('add-option-button').click() - - // cy.findByLabelText('Reading').should('exist') - // cy.findByLabelText('Swimming').should('exist') - // cy.findByLabelText('Running').should('exist') - // cy.findByLabelText('Gardening').should('exist') - // }) - - // it('on multiple selection after selecting all', () => { - // cy.mount() - - // cy.findByLabelText('Toggle options menu').click() - // cy.findByLabelText('Toggle all options').click() - - // cy.findByLabelText('Reading').should('be.checked') - // cy.findByLabelText('Swimming').should('be.checked') - // cy.findByLabelText('Running').should('be.checked') - - // cy.findByTestId('add-option-button').click() - - // cy.findByLabelText('Toggle options menu').click({ force: true }) - - // cy.findByLabelText('Reading').should('be.checked') - // cy.findByLabelText('Swimming').should('be.checked') - // cy.findByLabelText('Running').should('be.checked') - // cy.findByLabelText('Gardening').should('exist').should('not.be.checked') - // }) - - // it('on single selection and with default value', () => { - // cy.mount() - - // cy.findByLabelText('Toggle options menu').click() - // cy.findAllByText('Reading').should('exist').should('have.length', 2) - // cy.findByText('Swimming').should('exist') - // cy.findByText('Running').should('exist') - // cy.findByText('Gardening').should('not.exist') - - // cy.findByTestId('add-option-button').click() - - // cy.findAllByText('Reading').should('exist').should('have.length', 2) - // cy.findByText('Swimming').should('exist') - // cy.findByText('Running').should('exist') - // cy.findByText('Gardening').should('exist') - // }) - - // it('on multiple selection and with default values', () => { - // cy.mount() - - // cy.findByLabelText('Toggle options menu').click() - // cy.findAllByText('Reading').should('exist').should('have.length', 2) - // cy.findByLabelText('Swimming').should('exist') - // cy.findByLabelText('Running').should('exist') - // cy.findByLabelText('Gardening').should('not.exist') - - // cy.findByTestId('add-option-button').click() - - // cy.findAllByText('Reading').should('exist').should('have.length', 2) - // cy.findByLabelText('Swimming').should('exist') - // cy.findByLabelText('Running').should('exist') - // cy.findByLabelText('Gardening').should('exist') - // }) }) it('selects the "Select..." option correctly', () => { From 1fad4ec58f4b90b93f96396960e0b21e9ab19a99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Sat, 29 Jun 2024 17:02:21 -0300 Subject: [PATCH 10/12] feat: integration in dataset metadata form --- .../MetadataFormField/Fields/Vocabulary.tsx | 38 ++++++++----- .../Fields/VocabularyMultiple.tsx | 3 +- .../DatasetMetadataForm.spec.tsx | 53 ++++++++----------- 3 files changed, 49 insertions(+), 45 deletions(-) diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Vocabulary.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Vocabulary.tsx index b0b02ad1d..5fae08fa3 100644 --- a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Vocabulary.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/Vocabulary.tsx @@ -87,19 +87,31 @@ export const Vocabulary = ({ - - - {options.map((option) => ( - - ))} - + {options.length > 10 ? ( + + ) : ( + + + {options.map((option) => ( + + ))} + + )} + {error?.message} diff --git a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/VocabularyMultiple.tsx b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/VocabularyMultiple.tsx index 6f4c9a51b..39c9bb9f4 100644 --- a/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/VocabularyMultiple.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/MetadataForm/MetadataBlockFormFields/MetadataFormField/Fields/VocabularyMultiple.tsx @@ -53,7 +53,8 @@ export const VocabularyMultiple = ({ - { .should('exist') .closest('.row') .within(() => { + // This is a Select Advanced single, it is shown as a Select with more than 10 options cy.findByLabelText(/Country \/ Nation/) .should('exist') - .select('Argentina', { force: true }) + .click({ force: true }) + cy.findByLabelText(/Country \/ Nation/) + .closest('.dropdown') + .within(() => { + cy.findByText('Argentina').click({ force: true }) + }) }) } @@ -394,12 +400,10 @@ describe('DatasetMetadataForm', () => { .should('have.attr', 'aria-required', 'false') .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Textbox) - cy.findByLabelText('Identifier Type', { exact: true }) - .should('exist') - .should('have.attr', 'aria-required', 'false') - .should('have.prop', 'tagName', 'SELECT') - .children('option') - .should('have.length', 20) + cy.get('[id="citation.publication.0.publicationIDType"]').as( + 'identifierTypeInputButton' + ) + cy.get('@identifierTypeInputButton').should('exist') cy.findByLabelText('Identifier', { exact: true }) .should('exist') @@ -443,12 +447,7 @@ describe('DatasetMetadataForm', () => { .should('exist') .closest('.row') .within(() => { - cy.findByLabelText(/Country \/ Nation/) - .should('exist') - .should('have.attr', 'aria-required', 'true') - .should('have.prop', 'tagName', 'SELECT') - .children('option') - .should('have.length', 250) + cy.findByLabelText(/Country \/ Nation/).should('exist') cy.findByLabelText(/State \/ Province/) .should('exist') @@ -750,12 +749,7 @@ describe('DatasetMetadataForm', () => { .should('have.attr', 'aria-required', 'false') .should('have.data', 'fieldtype', TypeMetadataFieldOptions.Textbox) - cy.findByLabelText('Identifier Type', { exact: true }) - .should('exist') - .should('have.attr', 'aria-required', 'false') - .should('have.prop', 'tagName', 'SELECT') - .children('option') - .should('have.length', 20) + cy.findByLabelText('Identifier Type', { exact: true }).should('exist') cy.findByLabelText('Identifier', { exact: true }) .should('exist') @@ -857,13 +851,7 @@ describe('DatasetMetadataForm', () => { .should('exist') .closest('.row') .within(() => { - cy.findByLabelText('Type', { exact: true }) - .should('exist') - .should('have.attr', 'aria-required', 'false') - .should('have.value', '') - .should('have.prop', 'tagName', 'SELECT') - .children('option') - .should('have.length', 18) + cy.findByLabelText('Type', { exact: true }).should('exist') cy.findByLabelText('Name', { exact: true }) .should('exist') @@ -1143,13 +1131,16 @@ describe('DatasetMetadataForm', () => { .should('exist') .closest('.row') .within(() => { + // This is a Select Advanced single, it is shown as a Select with more than 10 options cy.findByLabelText(/Country \/ Nation/) .should('exist') - .should('have.attr', 'aria-required', 'true') - .should('have.value', 'United States') - .should('have.prop', 'tagName', 'SELECT') - .children('option') - .should('have.length', 250) + .click({ force: true }) + + cy.findByLabelText(/Country \/ Nation/) + .closest('.dropdown') + .within(() => { + cy.findAllByText('United States').should('exist').should('have.length', 2) + }) cy.findByLabelText(/State \/ Province/) .should('exist') From 5e8b5e1f224a906ddd9153279ad7fb2ccfb24aed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Mon, 1 Jul 2024 09:41:18 -0300 Subject: [PATCH 11/12] feat: improve accesibility and tests --- packages/design-system/CHANGELOG.md | 4 +--- .../select-advanced/SelectAdvancedMenu.tsx | 13 ++++++++++--- .../select-advanced/SelectAdvancedToggle.tsx | 3 ++- ...le.spec.tsx => FormGroupSelectAdvanced.spec.tsx} | 8 ++++---- .../select-advanced/SelectAdvanced.spec.tsx | 12 +++++++++++- .../DatasetMetadataForm.spec.tsx | 8 +++++++- 6 files changed, 35 insertions(+), 13 deletions(-) rename packages/design-system/tests/component/form/form-group/{FormGroupSelectMultiple.spec.tsx => FormGroupSelectAdvanced.spec.tsx} (82%) diff --git a/packages/design-system/CHANGELOG.md b/packages/design-system/CHANGELOG.md index 80b0b57b0..6853566bb 100644 --- a/packages/design-system/CHANGELOG.md +++ b/packages/design-system/CHANGELOG.md @@ -19,7 +19,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline - **FormFeedback:** remove `span: 9` from styles. - **FormGroup:** controlId is now optional. - **FormLabel:** extend Props Interface to accept `htmlFor` prop. -- **SelectMultiple:** NEW multiple selector for occasions when you can choose more than one option. - **FormSelectMultiple:** The new multiple selector is added to the "FormGroup" components. - **DropdownButton:** extend Props Interface to accept `ariaLabel` prop. - **DropdownButtonItem:** extend Props Interface to accept `as` prop. @@ -31,14 +30,13 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline - **FormInput:** remove withinMultipleFieldsGroup prop. - **FormLabel:** remove withinMultipleFieldsGroup prop extend interface to accept ColProps. - **FormSelect:** remove withinMultipleFieldsGroup prop. -- **FormSelectMultiple:** remove withinMultipleFieldsGroup prop. - **FormText:** remove withinMultipleFieldsGroup prop. - **FormTextArea:** remove withinMultipleFieldsGroup prop. - **FormInputGroup:** remove hasVisibleLabel prop. - **FormInputGroupText:** refactor type. -- **Select Multiple:** add is-invalid classname if isInvalid prop is true. - **Card:** NEW card element to show header and body. - **ProgressBar:** NEW progress bar element to show progress. +- **SelectAdvanced:** NEW ehanced select to search across options, and perform both single and multiple selections. # [1.1.0](https://github.com/IQSS/dataverse-frontend/compare/@iqss/dataverse-design-system@1.0.1...@iqss/dataverse-design-system@1.1.0) (2024-03-12) diff --git a/packages/design-system/src/lib/components/select-advanced/SelectAdvancedMenu.tsx b/packages/design-system/src/lib/components/select-advanced/SelectAdvancedMenu.tsx index 074567b3e..574fa7dea 100644 --- a/packages/design-system/src/lib/components/select-advanced/SelectAdvancedMenu.tsx +++ b/packages/design-system/src/lib/components/select-advanced/SelectAdvancedMenu.tsx @@ -98,18 +98,25 @@ export const SelectAdvancedMenu = ({ return ( handleClickOption(option === selectWord ? '' : option)} active={option !== selectWord ? selected === option : selected === ''} - id={`${optionLabelId}-${option}`}> + key={option}> {option} ) } return ( - + 0 ? (
+ aria-label="List of selected options" + role="region"> {isMultiple ? ( (selected as string[]).map((selectedValue) => (
{ it('renders without error', () => { cy.mount( - - Hobbies + + Hobbies { it('should focus on the input button when the label is clicked', () => { cy.mount( - - Hobbies + + Hobbies { cy.findByLabelText('Toggle options menu').click() + cy.findByText('Reading').should('exist') cy.findByText('Reading').should('exist') cy.findByText('Swimming').should('exist') cy.findByText('Running').should('exist') cy.findByText('Cycling').should('exist') cy.findByText('Cooking').should('exist') cy.findByText('Gardening').should('exist') + + // 6 Options + 1 Select... option + cy.findAllByRole('option').should('have.length', 7) }) it('on multiple selection', () => { cy.mount( @@ -58,6 +62,8 @@ describe('SelectAdvanced', () => { cy.findByText('Cycling').should('exist') cy.findByText('Cooking').should('exist') cy.findByText('Gardening').should('exist') + + cy.findAllByRole('option').should('have.length', 6) }) }) @@ -327,6 +333,7 @@ describe('SelectAdvanced', () => { .spread((_selectedItem, selectedListOption) => { const element = cy.get(selectedListOption) element.should('have.class', 'active') + element.should('have.attr', 'aria-selected', 'true') }) cy.findByText('Swimming').click() @@ -336,8 +343,11 @@ describe('SelectAdvanced', () => { .spread((_selectedItem, selectedListOption) => { const element = cy.get(selectedListOption) element.should('have.class', 'active') + element.should('have.attr', 'aria-selected', 'true') }) - cy.findByText('Reading').should('not.have.class', 'active') + cy.findByText('Reading') + .should('not.have.class', 'active') + .should('have.attr', 'aria-selected', 'false') }) it('should not change the selected option when clicking on the selected option in single selection mode', () => { diff --git a/tests/component/sections/shared/dataset-metadata-form/DatasetMetadataForm.spec.tsx b/tests/component/sections/shared/dataset-metadata-form/DatasetMetadataForm.spec.tsx index ed3a4409d..19c00f7e3 100644 --- a/tests/component/sections/shared/dataset-metadata-form/DatasetMetadataForm.spec.tsx +++ b/tests/component/sections/shared/dataset-metadata-form/DatasetMetadataForm.spec.tsx @@ -447,7 +447,13 @@ describe('DatasetMetadataForm', () => { .should('exist') .closest('.row') .within(() => { - cy.findByLabelText(/Country \/ Nation/).should('exist') + cy.findByLabelText(/Country \/ Nation/) + .should('exist') + .closest('.dropdown') + .within(() => { + cy.findByLabelText('Toggle options menu').click() + cy.findAllByRole('option').should('have.length', 250) + }) cy.findByLabelText(/State \/ Province/) .should('exist') From 61535683990578b0aafd0d55aa889a1e782fc0f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Thu, 18 Jul 2024 15:21:28 -0300 Subject: [PATCH 12/12] feat: close options menu when selecting an option on single selection mode --- .../select-advanced/SelectAdvanced.tsx | 4 +- .../select-advanced/SelectAdvanced.spec.tsx | 109 +++++++++++------- 2 files changed, 71 insertions(+), 42 deletions(-) diff --git a/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx index 7426d553a..641430b5d 100644 --- a/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx +++ b/packages/design-system/src/lib/components/select-advanced/SelectAdvanced.tsx @@ -187,7 +187,9 @@ export const SelectAdvanced = forwardRef( } return ( - + { describe('should render correctly', () => { it('on single selection', () => { @@ -34,7 +38,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByText('Reading').should('exist') cy.findByText('Reading').should('exist') @@ -54,7 +58,7 @@ describe('SelectAdvanced', () => { options={['Reading', 'Swimming', 'Running', 'Cycling', 'Cooking', 'Gardening']} /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByText('Reading').should('exist') cy.findByText('Swimming').should('exist') @@ -102,7 +106,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByText('Reading').click() cy.get('@onChange').should('have.been.calledOnce') @@ -119,7 +123,7 @@ describe('SelectAdvanced', () => { onChange={onChange} /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByLabelText('Reading').click() cy.get('@onChange').should('have.been.calledOnce') @@ -139,7 +143,7 @@ describe('SelectAdvanced', () => { onChange={onChange} /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByLabelText('Reading').click() cy.get('@onChange').should('have.been.calledWith', ['Reading']) @@ -159,7 +163,7 @@ describe('SelectAdvanced', () => { onChange={onChange} /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByLabelText('Reading').click() cy.get('@onChange').should('have.been.calledOnce') @@ -207,14 +211,24 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() // 6 different individual selections cy.findByText('Reading').click() + + toggleOptionsMenu() cy.findByText('Swimming').click() + + toggleOptionsMenu() cy.findByText('Running').click() + + toggleOptionsMenu() cy.findByText('Cycling').click() + + toggleOptionsMenu() cy.findByText('Reading').click() + + toggleOptionsMenu() cy.findByText('Swimming').click() cy.get('@onChange').should('have.callCount', 6) @@ -235,7 +249,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() // 4 individual selections cy.findByLabelText('Reading').click() @@ -286,7 +300,7 @@ describe('SelectAdvanced', () => { options={['Reading', 'Swimming', 'Running', 'Cycling', 'Cooking', 'Gardening']} /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByText('Reading').click() cy.findAllByText('Reading').spread((_selectedItem, selectedListOption) => { const element = cy.get(selectedListOption) @@ -307,7 +321,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByLabelText('Reading').click() cy.findByLabelText('Reading').should('be.checked') @@ -326,7 +340,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByText('Reading').click() cy.findAllByText('Reading') .should('have.length', 2) @@ -336,6 +350,8 @@ describe('SelectAdvanced', () => { element.should('have.attr', 'aria-selected', 'true') }) + toggleOptionsMenu() + cy.findByText('Swimming').click() cy.findAllByText('Swimming') @@ -357,8 +373,11 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByText('Reading').click() + + toggleOptionsMenu() + cy.findAllByText('Reading') .should('have.length', 2) .spread((_selectedItem, selectedListOption) => { @@ -371,6 +390,8 @@ describe('SelectAdvanced', () => { element.click() }) + toggleOptionsMenu() + cy.findAllByText('Reading') .should('have.length', 2) .spread((_selectedItem, selectedListOption) => { @@ -387,7 +408,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByLabelText('Reading').click() cy.findByTestId('toggle-inner-content').within(() => { @@ -404,7 +425,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByLabelText('Toggle all options').click() cy.findByLabelText('Reading').should('be.checked') @@ -435,7 +456,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByLabelText('Toggle all options').click() cy.findByLabelText('Toggle all options').click() @@ -468,7 +489,7 @@ describe('SelectAdvanced', () => { ) cy.clock() - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByPlaceholderText('Search...').type('Read') cy.tick(SELECT_MENU_SEARCH_DEBOUNCE_TIME) @@ -504,7 +525,7 @@ describe('SelectAdvanced', () => { ) cy.clock() - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByLabelText('Toggle all options').click() cy.findByLabelText('Reading').should('be.checked') @@ -547,7 +568,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByPlaceholderText('Search...').type('Read') cy.findByText('Reading').should('exist') @@ -565,7 +586,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByPlaceholderText('Search...').type('Read') cy.findByLabelText('Reading').should('exist') @@ -587,7 +608,7 @@ describe('SelectAdvanced', () => { cy.clock() - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByPlaceholderText('Search...').type('Read') cy.findByLabelText('Swimming').should('exist') @@ -619,7 +640,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByLabelText('Reading').click() cy.findByLabelText('Swimming').click() @@ -635,7 +656,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByTestId('select-advanced-searchable-input').should('not.exist') }) @@ -649,7 +670,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByTestId('select-advanced-searchable-input').should('not.exist') }) @@ -663,7 +684,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByText('Reading').click() cy.findByTestId('select-advanced-searchable-input').should('not.exist') @@ -679,7 +700,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByLabelText('Reading').click() cy.findByLabelText('Swimming').click() @@ -697,7 +718,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByPlaceholderText('Search...').type('Yoga') cy.findByText('No options found').should('exist') @@ -709,7 +730,7 @@ describe('SelectAdvanced', () => { options={['Reading', 'Swimming', 'Running', 'Cycling', 'Cooking', 'Gardening']} /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByPlaceholderText('Search...').type('Yoga') cy.findByText('No options found').should('exist') @@ -817,7 +838,7 @@ describe('SelectAdvanced', () => { it('on single selection', () => { cy.mount() - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByText('Reading').should('exist') cy.findByText('Swimming').should('exist') cy.findByText('Running').should('exist') @@ -834,7 +855,7 @@ describe('SelectAdvanced', () => { it('on single selection with default value', () => { cy.mount() - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findAllByText('Reading').should('exist').should('have.length', 2) cy.findByText('Swimming').should('exist') cy.findByText('Running').should('exist') @@ -851,7 +872,7 @@ describe('SelectAdvanced', () => { it('on multiple selection', () => { cy.mount() - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByLabelText('Reading').should('exist') cy.findByLabelText('Swimming').should('exist') cy.findByLabelText('Running').should('exist') @@ -867,7 +888,7 @@ describe('SelectAdvanced', () => { it('on multiple selection with default values', () => { cy.mount() - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByLabelText('Reading').should('exist') cy.findByLabelText('Swimming').should('exist') cy.findByLabelText('Running').should('exist') @@ -886,7 +907,7 @@ describe('SelectAdvanced', () => { it('on single selection', () => { cy.mount() - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByText('Reading').should('exist') cy.findByText('Swimming').should('exist') cy.findByText('Running').should('exist') @@ -913,7 +934,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findAllByText('Reading').should('exist').should('have.length', 2) cy.findByText('Swimming').should('exist') cy.findByText('Running').should('exist') @@ -942,7 +963,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByLabelText('Reading').should('exist') cy.findByLabelText('Reading').should('exist') cy.findByLabelText('Running').should('exist') @@ -970,7 +991,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findAllByText('Reading').should('exist').should('have.length', 2) cy.findAllByText('Reading').should('exist').should('have.length', 2) cy.findByLabelText('Running').should('exist') @@ -1001,7 +1022,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByText('Reading').should('exist') cy.findByText('Swimming').should('exist') cy.findByText('Running').should('exist') @@ -1032,7 +1053,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findAllByText('Reading').should('exist').should('have.length', 2) cy.findByText('Swimming').should('exist') cy.findByText('Running').should('exist') @@ -1064,7 +1085,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByText('Reading').should('exist') cy.findByText('Running').should('exist') cy.findByText('Swimming').should('exist') @@ -1095,7 +1116,7 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findAllByText('Reading').should('exist').should('have.length', 2) cy.findAllByText('Running').should('exist').should('have.length', 2) cy.findByLabelText('Swimming').should('exist') @@ -1124,12 +1145,15 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByText('Swimming').click() + + toggleOptionsMenu() cy.findAllByText('Swimming').should('have.length', 2) cy.findByText('Select...').click() + toggleOptionsMenu() cy.findAllByText('Select...').should('have.length', 2) }) @@ -1141,12 +1165,15 @@ describe('SelectAdvanced', () => { /> ) - cy.findByLabelText('Toggle options menu').click() + toggleOptionsMenu() cy.findByText('Swimming').click() + + toggleOptionsMenu() cy.findAllByText('Swimming').should('have.length', 2) cy.findByText('Selezionare...').click() + toggleOptionsMenu() cy.findAllByText('Selezionare...').should('have.length', 2) }) })