From cb832a74cb87b4f3513cdf088f364cff147f05d5 Mon Sep 17 00:00:00 2001 From: AlekseyManetov Date: Tue, 28 Jan 2025 16:04:01 +0100 Subject: [PATCH] refactor current PickerInput tests structure --- .../pickers/__tests__/PickerInput.test.tsx | 2279 ++++++++--------- 1 file changed, 1074 insertions(+), 1205 deletions(-) diff --git a/uui/components/pickers/__tests__/PickerInput.test.tsx b/uui/components/pickers/__tests__/PickerInput.test.tsx index c0711012c0..42ce27c2f5 100644 --- a/uui/components/pickers/__tests__/PickerInput.test.tsx +++ b/uui/components/pickers/__tests__/PickerInput.test.tsx @@ -2,12 +2,10 @@ import React, { ReactNode } from 'react'; import { ArrayDataSource, CascadeSelection, LazyDataSource } from '@epam/uui-core'; import { renderSnapshotWithContextAsync, setupComponentForTest, screen, within, fireEvent, waitFor, userEvent, PickerInputTestObject, act, - delayAct, } from '@epam/uui-test-utils'; import { Modals, PickerToggler } from '@epam/uui-components'; import { DataPickerRow, FlexCell, PickerItem, Text, Button } from '../../'; import { PickerInput, PickerInputProps } from '../PickerInput'; -import { IHasEditMode } from '../../types'; import { Item, TestItemType, TestTreeItem, mockDataSource, mockDataSourceAsync, mockSmallDataSourceAsync, mockTreeLikeDataSourceAsync } from './mocks'; type PickerInputComponentProps = PickerInputProps; @@ -154,250 +152,211 @@ describe('PickerInput', () => { jest.clearAllMocks(); }); - it('should render with minimum props', async () => { - const tree = await renderSnapshotWithContextAsync( - , - ); - expect(tree).toMatchSnapshot(); - }); - - it('should render with maximum props', async () => { - const tree = await renderSnapshotWithContextAsync( - item?.level ?? '' } - autoFocus - placeholder="Test placeholder" - filter={ (item: any) => item.level === 'A1' } - sorting={ { direction: 'desc', field: 'level' } } - searchPosition="body" - minBodyWidth={ 900 } - renderNotFound={ () => null } - renderFooter={ (props) =>
{ props as unknown as ReactNode }
} - cascadeSelection - dropdownHeight={ 48 } - minCharsToSearch={ 4 } - />, - ); - expect(tree).toMatchSnapshot(); - }); - - it('should open body', async () => { - const { dom, result } = await setupPickerInputForTest({ - value: undefined, - selectionMode: 'single', - dataSource: mockSmallDataSourceAsync, - getName: ({ name }) => name, + describe('Rendering', () => { + it('should render with minimum props', async () => { + const tree = await renderSnapshotWithContextAsync( + , + ); + expect(tree).toMatchSnapshot(); + }); + + it('should render with maximum props', async () => { + const tree = await renderSnapshotWithContextAsync( + item?.level ?? '' } + autoFocus + placeholder="Test placeholder" + filter={ (item: any) => item.level === 'A1' } + sorting={ { direction: 'desc', field: 'level' } } + searchPosition="body" + minBodyWidth={ 900 } + renderNotFound={ () => null } + renderFooter={ (props) =>
{ props as unknown as ReactNode }
} + cascadeSelection + dropdownHeight={ 48 } + minCharsToSearch={ 4 } + />, + ); + expect(tree).toMatchSnapshot(); + }); + + it('should render custom placeholder', async () => { + const { dom } = await setupPickerInputForTest({ + value: undefined, + selectionMode: 'multi', + placeholder: 'Custom placeholder', + }); + expect(await PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Custom placeholder'); }); - fireEvent.click(dom.input); - - await PickerInputTestObject.waitForOptionsToBeReady(); - await PickerInputTestObject.waitForLoadingComplete(); - - expect(result.baseElement).toMatchSnapshot(); - }); - - describe('[selectionMode single]', () => { - it('[valueType id] should select & clear option', async () => { - const { dom, mocks } = await setupPickerInputForTest({ + it('should define minBodyWidth', async () => { + const { dom } = await setupPickerInputForTest({ value: undefined, - selectionMode: 'single', - }); - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); - fireEvent.click(dom.input); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - const optionC2 = await screen.findByText('C2'); - fireEvent.click(optionC2); - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith(12); + selectionMode: 'multi', + minBodyWidth: 300, }); - fireEvent.click(window.document.body); - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - expect(screen.getByPlaceholderText('C2')).toBeInTheDocument(); - const clear = screen.getByRole('button'); - fireEvent.click(clear); - await waitFor(() => { - expect(screen.queryByText('C2')).not.toBeInTheDocument(); - }); + fireEvent.click(dom.input); - // double click should be performed to check, if on blur selection is still present - fireEvent.click(document.body); - await waitFor(() => { - expect(screen.queryByRole('dialog')).toBeNull(); - }); - expect(screen.queryByText('C2')).not.toBeInTheDocument(); + const dialog = await screen.findByRole('dialog'); + expect(dialog).toBeInTheDocument(); - fireEvent.click(document.body); - await waitFor(() => { - expect(screen.queryByRole('dialog')).toBeNull(); - }); - expect(screen.queryByText('C2')).not.toBeInTheDocument(); + const dialogBody = dialog.getElementsByClassName('uui-dropdown-body')[0]; + expect(dialogBody).toHaveStyle('min-width: 300px'); }); - it('[valueType id] should listen to value change', async () => { - const { dom, mocks } = await setupPickerInputForTestWithFirstValueChangeRewriting({ - selectionMode: 'single', - }); - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); - fireEvent.click(dom.input); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - const optionC2 = await screen.findByText('C2'); - fireEvent.click(optionC2); - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith(12); - }); - - fireEvent.click(window.document.body); - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - - await waitFor(() => { - expect(screen.queryByText('C2')).not.toBeInTheDocument(); + it('should define dropdownHeight', async () => { + const { dom } = await setupPickerInputForTest({ + value: undefined, + selectionMode: 'multi', + dropdownHeight: 100, }); fireEvent.click(dom.input); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - const option2C2 = await screen.findByText('C2'); - fireEvent.click(option2C2); - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith(12); - }); - await waitFor(() => { - expect(screen.getByPlaceholderText('C2')).toBeInTheDocument(); - }); + const dialog = await screen.findByRole('dialog'); + expect(dialog).toBeInTheDocument(); + + const dialogBody = dialog.firstElementChild?.firstElementChild; + expect(dialogBody).toHaveStyle('max-height: 100px'); }); - it('should close body on click outside', async () => { - const { dom } = await setupPickerInputForTest({ - value: undefined, - selectionMode: 'single', - }); - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); - fireEvent.click(dom.input); - await waitFor(() => { - expect(screen.getByRole('dialog')).toBeInTheDocument(); + it('should render custom not found', async () => { + const mockEmptyDS = new ArrayDataSource({ + items: [], + getId: ({ id }) => id, }); - fireEvent.click(document.body); + const mockConsoleError = jest.fn(); + const prevConsoleError = console.error; + console.error = mockConsoleError; - await waitFor(() => { - expect(screen.queryByRole('dialog')).toBeNull(); - }); - }); + const customText = 'Custom Text or Component'; - it('should keep selection on close body', async () => { - const { dom, mocks } = await setupPickerInputForTest({ + const { dom } = await setupPickerInputForTest({ value: undefined, - selectionMode: 'single', + selectionMode: 'multi', + dataSource: mockEmptyDS, + renderNotFound: () => ( + + {customText} + + ), }); - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); + fireEvent.click(dom.input); - await waitFor(() => { - expect(screen.getByRole('dialog')).toBeInTheDocument(); - }); - - const optionC2 = await screen.findByText('C2'); - fireEvent.click(optionC2); - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith(12); - }); + const notFound = within(await screen.findByRole('dialog')).getByTestId('test-custom-not-found'); + expect(notFound).toHaveTextContent(customText); + expect(mockConsoleError).toBeCalled(); - // double click should be performed to check, if on blur selection is still present - fireEvent.click(document.body); - await waitFor(() => { - expect(screen.queryByRole('dialog')).toBeNull(); - }); - expect(screen.getByPlaceholderText('C2')).toBeInTheDocument(); + console.error = prevConsoleError; + }); - fireEvent.click(document.body); - await waitFor(() => { - expect(screen.queryByRole('dialog')).toBeNull(); + it('should render custom renderEmpty', async () => { + const mockEmptyDS = new ArrayDataSource({ + items: [], + getId: ({ id }) => id, }); - expect(screen.getByPlaceholderText('C2')).toBeInTheDocument(); - }); - it('[valueType entity] should select & clear option', async () => { - const { dom, mocks } = await setupPickerInputForTest({ + const customTextForNotFound = 'Custom Text For Not Found'; + const customTextForNotFoundId = 'test-custom-not-found-from-empty'; + const customTextForNotEnoughCharsInSearch = 'Custom Text For Not Enough Chars In Search'; + const customTextForNotEnoughCharsInSearchId = 'test-custom-not-enough-chars-from-empty'; + + const { dom } = await setupPickerInputForTest({ value: undefined, - selectionMode: 'single', - valueType: 'entity', + selectionMode: 'multi', + minCharsToSearch: 3, + searchPosition: 'body', + dataSource: mockEmptyDS, + renderEmpty: ({ minCharsToSearch, search }) => { + if (search.length < minCharsToSearch) { + return ( + + {customTextForNotEnoughCharsInSearch} + + ); + } + + return ( + + {customTextForNotFound} + + ); + }, + getSearchFields: (item) => [item!.level], }); - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); + + expect(dom.input.hasAttribute('readonly')).toBeTruthy(); fireEvent.click(dom.input); - await waitFor(() => { - expect(screen.getByRole('dialog')).toBeInTheDocument(); - }); - const optionC2 = await screen.findByText('C2'); - fireEvent.click(optionC2); - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith({ id: 12, level: 'C2', name: 'Proficiency' }); - }); - fireEvent.click(window.document.body); - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - await waitFor(() => { - expect(screen.getByPlaceholderText('C2')).toBeInTheDocument(); + const dialog = await screen.findByRole('dialog'); + expect(dialog).toBeInTheDocument(); + + await waitFor(async () => { + const notFound = within(await screen.findByRole('dialog')).getByTestId(customTextForNotEnoughCharsInSearchId); + expect(notFound).toHaveTextContent(customTextForNotEnoughCharsInSearch); }); - const clear = screen.getByRole('button'); - fireEvent.click(clear); - await waitFor(() => { - expect(screen.queryByText('C2')).not.toBeInTheDocument(); + const bodyInput = within(dialog).getByPlaceholderText('Search'); + fireEvent.change(bodyInput, { target: { value: 'A11' } }); + + await waitFor(async () => { + const notFound = within(await screen.findByRole('dialog')).getByTestId(customTextForNotFoundId); + expect(notFound).toHaveTextContent(customTextForNotFound); }); }); - it('[valueType entity] should listen to value change', async () => { - const { dom, mocks } = await setupPickerInputForTestWithFirstValueChangeRewriting({ + it('should render custom row', async () => { + const { dom } = await setupPickerInputForTest({ value: undefined, - selectionMode: 'single', - valueType: 'entity', - }); - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); - fireEvent.click(dom.input); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - const optionC2 = await screen.findByText('C2'); - fireEvent.click(optionC2); - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith({ id: 12, level: 'C2', name: 'Proficiency' }); - }); - - fireEvent.click(window.document.body); - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - - await waitFor(() => { - expect(screen.queryByText('C2')).not.toBeInTheDocument(); + selectionMode: 'multi', + renderRow: (props) => ( + } + /> + ), }); fireEvent.click(dom.input); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - const option2C2 = await screen.findByText('C2'); - fireEvent.click(option2C2); + expect(await screen.findByRole('dialog')).toBeInTheDocument(); - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith({ id: 12, level: 'C2', name: 'Proficiency' }); - }); - await waitFor(() => { - expect(screen.getByPlaceholderText('C2')).toBeInTheDocument(); - }); + await PickerInputTestObject.waitForOptionsToBeReady(); + + expect(await PickerInputTestObject.findOptionsText({ busy: false })).toEqual([ + 'Elementary', + 'Elementary+', + 'Pre-Intermediate', + 'Pre-Intermediate+', + 'Intermediate', + 'Intermediate+', + 'Upper-Intermediate', + 'Upper-Intermediate+', + 'Advanced', + 'Advanced+', + 'Proficiency', + ]); }); - it('should render names of items by getName', async () => { - const { mocks, dom } = await setupPickerInputForTest({ - value: 3, - selectionMode: 'single', + it('should render names according to getName prop', async () => { + const { dom, mocks } = await setupPickerInputForTest({ + value: [3, 4], + selectionMode: 'multi', getName: ({ name }) => name, }); - - await waitFor(async () => expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Elementary+')); + expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); + await waitFor(() => expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['Elementary+', 'Pre-Intermediate'])); fireEvent.click(dom.input); expect(screen.getByRole('dialog')).toBeInTheDocument(); @@ -405,1193 +364,1023 @@ describe('PickerInput', () => { const optionC2 = await screen.findByText('Proficiency'); fireEvent.click(optionC2); await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith(12); + expect(mocks.onValueChange).toHaveBeenLastCalledWith([3, 4, 12]); }); }); + }); - it('should render entity name in placeholder', async () => { - const { dom } = await setupPickerInputForTest({ + describe('Body Open/Close', () => { + it('should open body', async () => { + const { dom, result } = await setupPickerInputForTest({ value: undefined, selectionMode: 'single', - entityName: 'Language Level', + dataSource: mockSmallDataSourceAsync, + getName: ({ name }) => name, }); - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select Language Level'); + fireEvent.click(dom.input); + + await PickerInputTestObject.waitForOptionsToBeReady(); + await PickerInputTestObject.waitForLoadingComplete(); + + expect(result.baseElement).toMatchSnapshot(); }); - it('should ignore plural entity name in placeholder', async () => { + it('should open dropdown when start typing with selectionMode single', async () => { const { dom } = await setupPickerInputForTest({ value: undefined, selectionMode: 'single', - entityName: 'Language Level', - entityPluralName: 'Multiple Language Levels', }); - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select Language Level'); + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + + fireEvent.change(dom.input, { target: { value: 'A' } }); + + expect(await screen.findByRole('dialog')).toBeInTheDocument(); }); - it.each<[CascadeSelection]>( - [[false], [true], ['implicit'], ['explicit']], - ) - ('should pick single element with cascadeSelection = %s', async (cascadeSelection) => { - const { mocks, dom } = await setupPickerInputForTest({ + it('should focus first founded item after search', async () => { // TODO: move + const { dom } = await setupPickerInputForTest({ value: undefined, - getName: ({ name }) => name, selectionMode: 'single', - cascadeSelection, - dataSource: mockTreeLikeDataSourceAsync, }); + fireEvent.click(dom.input); + const dialog = await screen.findByRole('dialog'); + + fireEvent.change(dom.input, { target: { value: 'A' } }); + await PickerInputTestObject.waitForOptionsToBeReady(); - // Check parent - await PickerInputTestObject.clickOptionByText('Parent 2'); - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith(2); + const focusedItem = dialog.querySelector('.uui-focus'); + expect(focusedItem?.getAttribute('aria-posinset')).toBe('1'); }); - - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Parent 2'); }); - it('should work with maxItems properly', async () => { - const { mocks, dom } = await setupPickerInputForTest({ + it('should open body in modal if editMode= modal', async () => { + const { dom } = await setupPickerInputForTest({ value: undefined, - maxItems: 1, selectionMode: 'single', + editMode: 'modal', }); - - fireEvent.click(dom.input); - - await PickerInputTestObject.waitForOptionsToBeReady(); - - // Check parent - await PickerInputTestObject.clickOptionByText('A1'); fireEvent.click(dom.input); - await PickerInputTestObject.clickOptionByText('A1+'); - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith(3); - }); + expect(screen.getByAria('modal', 'true')).toBeInTheDocument(); - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('A1+'); + expect( + await PickerInputTestObject.findOptionsText({ busy: false, editMode: 'modal' }), + ).toEqual( + ['A1', 'A1+', 'A2', 'A2+', 'B1', 'B1+', 'B2', 'B2+', 'C1', 'C1+', 'C2'], + ); }); - it('should disable clear', async () => { - const { dom, setProps } = await setupPickerInputForTest({ - value: 2, + it('should close body on click outside', async () => { + const { dom } = await setupPickerInputForTest({ + value: undefined, selectionMode: 'single', - disableClear: false, }); - - await waitFor(() => expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('A1')); - - const clearButton = within(dom.container).getByRole('button', { name: 'Clear' }); - expect(clearButton).toBeInTheDocument(); - fireEvent.click(clearButton); + expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); + fireEvent.click(dom.input); await waitFor(() => { - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); + expect(screen.getByRole('dialog')).toBeInTheDocument(); }); - // After clearing of all the items, debounced version of clear search is called. - // If update items just after clearing, handleDataSourceValueChange with old value will be called. - // So, to wait for all debounced events execution, delayAct should be called. - await delayAct(); + fireEvent.click(document.body); - setProps({ disableClear: true, value: 2 }); await waitFor(() => { - expect(within(dom.container).queryByRole('button', { name: 'Clear' })).not.toBeInTheDocument(); + expect(screen.queryByRole('dialog')).toBeNull(); }); - - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('A1'); }); + }); - it('should clear selected item', async () => { + describe('Search Functionality', () => { + it('should render search in input', async () => { const { dom } = await setupPickerInputForTest({ value: undefined, - selectionMode: 'single', - maxItems: 100, + selectionMode: 'multi', + searchPosition: 'input', }); - fireEvent.click(dom.input); - expect(await PickerInputTestObject.hasOptions()).toBeTruthy(); + expect(dom.input.getAttribute('readonly')).toBeNull(); + fireEvent.click(dom.input); + const dialog = await screen.findByRole('dialog'); + expect(dialog).toBeInTheDocument(); - const clearButton = within(screen.getByRole('dialog')).getByRole('button', { name: 'CLEAR' }); - expect(clearButton).toBeInTheDocument(); - expect(clearButton).toHaveAttribute('aria-disabled', 'true'); + const bodyInput = within(dialog).queryByPlaceholderText('Search'); + expect(bodyInput).not.toBeInTheDocument(); + }); - await PickerInputTestObject.clickOptionByText('A1'); - await waitFor(() => { - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('A1'); + it('should render search in body', async () => { + const { dom } = await setupPickerInputForTest({ + value: undefined, + selectionMode: 'multi', + searchPosition: 'body', }); + expect(dom.input.hasAttribute('readonly')).toBeTruthy(); fireEvent.click(dom.input); - const clearButton2 = within(screen.getByRole('dialog')).getByRole('button', { name: 'CLEAR' }); - expect(clearButton2).toHaveAttribute('aria-disabled', 'false'); + const dialog = await screen.findByRole('dialog'); + expect(dialog).toBeInTheDocument(); + const bodyInput = within(dialog).getByPlaceholderText('Search'); + expect(bodyInput).toBeInTheDocument(); + expect(bodyInput.hasAttribute('readonly')).toBeFalsy(); + }); - fireEvent.click(clearButton2); - await waitFor(() => { - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); + it('should not render search in none mode', async () => { + const { dom } = await setupPickerInputForTest({ + value: undefined, + selectionMode: 'multi', + searchPosition: 'none', }); - const clearButton3 = within(screen.getByRole('dialog')).getByRole('button', { name: 'CLEAR' }); - expect(clearButton3).toHaveAttribute('aria-disabled', 'true'); + expect(dom.input.hasAttribute('readonly')).toBeTruthy(); + fireEvent.click(dom.input); + + const dialog = await screen.findByRole('dialog'); + expect(dialog).toBeInTheDocument(); + expect(within(dialog).queryByPlaceholderText('Search')).not.toBeInTheDocument(); }); - }); - describe('[selectionMode multi]', () => { - it('[valueType id] should select & clear several options', async () => { - const { dom, mocks } = await setupPickerInputForTest({ + it('should search items', async () => { + const { dom } = await setupPickerInputForTest({ value: undefined, selectionMode: 'multi', + searchPosition: 'body', + getSearchFields: (item) => [item!.level], }); - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); + + expect(dom.input.hasAttribute('readonly')).toBeTruthy(); fireEvent.click(dom.input); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - await PickerInputTestObject.clickOptionCheckbox('A1'); - - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith([2]); - }); + const dialog = await screen.findByRole('dialog'); + expect(dialog).toBeInTheDocument(); - await PickerInputTestObject.clickOptionCheckbox('A1+'); - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith([2, 3]); - }); - expect(await PickerInputTestObject.findCheckedOptions()).toEqual(['A1', 'A1+']); + await PickerInputTestObject.waitForOptionsToBeReady(); - fireEvent.click(window.document.body); - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A1', 'A1+']); + expect(await PickerInputTestObject.findOptionsText({ busy: false })).toEqual([ + 'A1', + 'A1+', + 'A2', + 'A2+', + 'B1', + 'B1+', + 'B2', + 'B2+', + 'C1', + 'C1+', + 'C2', + ]); - PickerInputTestObject.removeSelectedTagByText(dom.target, 'A1+'); - await waitFor(() => { - expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A1']); - }); - - PickerInputTestObject.removeSelectedTagByText(dom.target, 'A1'); - await waitFor(() => { - expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual([]); - }); - }); + const bodyInput = within(dialog).getByPlaceholderText('Search'); + fireEvent.change(bodyInput, { target: { value: 'A' } }); - it('[valueType id] should select & clear all', async () => { - const { dom, mocks } = await setupPickerInputForTest({ - value: undefined, - selectionMode: 'multi', - }); - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); - fireEvent.click(dom.input); - expect(screen.getByRole('dialog')).toBeInTheDocument(); + await waitFor(() => expect(PickerInputTestObject.getOptions({ busy: false }).length).toBe(4)); - await PickerInputTestObject.clickOptionCheckbox('A1'); - - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith([2]); - }); + expect(await PickerInputTestObject.findOptionsText({ busy: false })).toEqual([ + 'A1', + 'A1+', + 'A2', + 'A2+', + ]); + }); - await PickerInputTestObject.clickOptionCheckbox('A1+'); - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith([2, 3]); + it('should open dialog only when minCharsToSearch is reached if search in input', async () => { + const { dom } = await setupPickerInputForTest({ + value: undefined, + minCharsToSearch: 1, + selectionMode: 'single', + searchPosition: 'input', }); - expect(await PickerInputTestObject.findCheckedOptions()).toEqual(['A1', 'A1+']); - await PickerInputTestObject.clickClearAllOptions(); - await waitFor(() => { - expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual([]); - }); + fireEvent.click(dom.input); - fireEvent.click(window.document.body); expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual([]); - fireEvent.click(window.document.body); - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual([]); - }); - it('should close body on click outside', async () => { - const { dom, mocks } = await setupPickerInputForTest({ - value: undefined, - selectionMode: 'multi', + jest.useFakeTimers(); + fireEvent.change(dom.input, { target: { value: 'A' } }); + act(() => { + jest.runAllTimers(); }); - fireEvent.click(dom.input); - expect(screen.getByRole('dialog')).toBeInTheDocument(); + jest.useRealTimers(); + const pickerBody = await PickerInputTestObject.findDialog(); + return expect(pickerBody).toBeInTheDocument(); + }); - await PickerInputTestObject.clickOptionCheckbox('A1'); - - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith([2]); - }); + it('should not load items while search less than minCharsToSearch', async () => { + const apiMock = jest.fn().mockResolvedValue([]); - await PickerInputTestObject.clickOptionCheckbox('A1+'); - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith([2, 3]); + const mockEmptyDS = new LazyDataSource({ + api: apiMock, + getId: ({ id }) => id, }); - expect(await PickerInputTestObject.findCheckedOptions()).toEqual(['A1', 'A1+']); - fireEvent.click(window.document.body); - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - }); - - it('should keep selection on close body', async () => { - const { dom, mocks } = await setupPickerInputForTest({ + const { dom } = await setupPickerInputForTest({ value: undefined, selectionMode: 'multi', + minCharsToSearch: 3, + searchPosition: 'body', + dataSource: mockEmptyDS, + getSearchFields: (item) => [item!.level], }); + + expect(dom.input.hasAttribute('readonly')).toBeTruthy(); fireEvent.click(dom.input); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - await PickerInputTestObject.clickOptionCheckbox('A1'); - - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith([2]); - }); + const dialog = await screen.findByRole('dialog'); + expect(dialog).toBeInTheDocument(); - await PickerInputTestObject.clickOptionCheckbox('A1+'); - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith([2, 3]); + await waitFor(async () => { + const notFound = within(await screen.findByRole('dialog')); + expect(notFound.getByText('Type search to load items')).toBeInTheDocument(); }); - expect(await PickerInputTestObject.findCheckedOptions()).toEqual(['A1', 'A1+']); - expect(await PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A1', 'A1+']); - fireEvent.click(window.document.body); - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - - // double click should be performed to check, if on blur selection is still present - fireEvent.click(document.body); - await waitFor(() => { - expect(screen.queryByRole('dialog')).toBeNull(); - }); - expect(await PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A1', 'A1+']); + expect(apiMock).toBeCalledTimes(0); - fireEvent.click(document.body); - await waitFor(() => { - expect(screen.queryByRole('dialog')).toBeNull(); - }); - expect(await PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A1', 'A1+']); - }); + const bodyInput = within(dialog).getByPlaceholderText('Search'); - it('[valueType id] should listen to value change', async () => { - const { dom, mocks } = await setupPickerInputForTestWithFirstValueChangeRewriting({ - valueForFirstUpdate: [4], - value: undefined, - selectionMode: 'multi', - valueType: 'id', + jest.useFakeTimers(); + fireEvent.change(bodyInput, { target: { value: '1234' } }); + act(() => { + jest.runAllTimers(); }); + jest.useRealTimers(); - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); - fireEvent.click(dom.input); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - - await PickerInputTestObject.clickOptionCheckbox('A1'); - - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith([2]); + await waitFor(async () => { + const notFound = within(await screen.findByRole('dialog')); + expect(notFound.getByText('No records found')).toBeInTheDocument(); }); - expect(await PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A2']); - fireEvent.click(window.document.body); - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - - fireEvent.click(dom.input); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - await PickerInputTestObject.clickOptionCheckbox('A1'); - - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith([4, 2]); - }); - expect(await PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A2', 'A1']); + expect(apiMock).toBeCalledTimes(1); }); + }); - it('[valueType entity] should listen to value change', async () => { - const { dom, mocks } = await setupPickerInputForTestWithFirstValueChangeRewriting({ - valueForFirstUpdate: [{ id: 4, level: 'A2', name: 'Pre-Intermediate' }], - value: undefined, - selectionMode: 'multi', - valueType: 'entity', - }); - - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); - fireEvent.click(dom.input); - expect(screen.getByRole('dialog')).toBeInTheDocument(); + describe('Selection', () => { + describe('Single Mode', () => { + it('[valueType id] should select & clear option', async () => { + const { dom, mocks } = await setupPickerInputForTest({ + value: undefined, + selectionMode: 'single', + }); + expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); + fireEvent.click(dom.input); + expect(screen.getByRole('dialog')).toBeInTheDocument(); + const optionC2 = await screen.findByText('C2'); + fireEvent.click(optionC2); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith(12); + }); - await PickerInputTestObject.clickOptionCheckbox('A1'); - - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith([{ - id: 2, - level: 'A1', - name: 'Elementary', - }]); - }); - expect(await PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A2']); + fireEvent.click(window.document.body); + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + expect(screen.getByPlaceholderText('C2')).toBeInTheDocument(); + const clear = screen.getByRole('button'); + fireEvent.click(clear); + await waitFor(() => { + expect(screen.queryByText('C2')).not.toBeInTheDocument(); + }); - fireEvent.click(window.document.body); - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - - fireEvent.click(dom.input); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - await act(async () => { - await PickerInputTestObject.clickOptionCheckbox('A1'); - }); + // double click should be performed to check, if on blur selection is still present + fireEvent.click(document.body); + await waitFor(() => { + expect(screen.queryByRole('dialog')).toBeNull(); + }); + expect(screen.queryByText('C2')).not.toBeInTheDocument(); - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith([{ - id: 4, - level: 'A2', - name: 'Pre-Intermediate', - }, - { - id: 2, - level: 'A1', - name: 'Elementary', - }]); + fireEvent.click(document.body); + await waitFor(() => { + expect(screen.queryByRole('dialog')).toBeNull(); + }); + expect(screen.queryByText('C2')).not.toBeInTheDocument(); }); - expect(await PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A2', 'A1']); - }); - it('[valueType entity] should select & clear several options', async () => { - const { dom, mocks } = await setupPickerInputForTest({ - value: undefined, - selectionMode: 'multi', - valueType: 'entity', - }); - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); - fireEvent.click(dom.input); - expect(screen.getByRole('dialog')).toBeInTheDocument(); + it('[valueType id] should listen to external value change', async () => { + const { dom, mocks } = await setupPickerInputForTestWithFirstValueChangeRewriting({ + selectionMode: 'single', + }); - await PickerInputTestObject.clickOptionCheckbox('A1'); - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith([{ id: 2, level: 'A1', name: 'Elementary' }]); - }); + expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); + fireEvent.click(dom.input); + expect(screen.getByRole('dialog')).toBeInTheDocument(); - await PickerInputTestObject.clickOptionCheckbox('A1+'); - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith([ - { id: 2, level: 'A1', name: 'Elementary' }, - { id: 3, level: 'A1+', name: 'Elementary+' }, - ]); - }); - expect(await PickerInputTestObject.findCheckedOptions()).toEqual(['A1', 'A1+']); + const optionC2 = await screen.findByText('C2'); + fireEvent.click(optionC2); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith(12); + }); - fireEvent.click(window.document.body); - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A1', 'A1+']); + fireEvent.click(window.document.body); + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - PickerInputTestObject.removeSelectedTagByText(dom.target, 'A1+'); - await waitFor(() => { - expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A1']); - }); - PickerInputTestObject.removeSelectedTagByText(dom.target, 'A1'); - await waitFor(() => { - expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual([]); - }); - }); + await waitFor(() => { + expect(screen.queryByText('C2')).not.toBeInTheDocument(); // wait for value change + }); - it('should render names of items by getName', async () => { - const { dom } = await setupPickerInputForTest({ - value: [3, 4], - selectionMode: 'multi', - getName: ({ name }) => name, - }); - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); - await waitFor(() => expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['Elementary+', 'Pre-Intermediate'])); - }); + fireEvent.click(dom.input); + expect(screen.getByRole('dialog')).toBeInTheDocument(); + const option2C2 = await screen.findByText('C2'); + fireEvent.click(option2C2); - it('should render entity name with \'s\' in placeholder', async () => { - const { dom } = await setupPickerInputForTest({ - value: undefined, - selectionMode: 'multi', - entityName: 'Language Level', + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith(12); + }); + await waitFor(() => { + expect(screen.getByPlaceholderText('C2')).toBeInTheDocument(); + }); }); - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select Language Levels'); - }); + it('should keep selection on body close', async () => { + const { dom, mocks } = await setupPickerInputForTest({ + value: undefined, + selectionMode: 'single', + }); + expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); + fireEvent.click(dom.input); + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); - it('should render plural entity name in placeholder', async () => { - const { dom } = await setupPickerInputForTest({ - value: undefined, - selectionMode: 'multi', - entityName: 'Language Level', - entityPluralName: 'Multiple Language Levels', - }); + const optionC2 = await screen.findByText('C2'); + fireEvent.click(optionC2); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith(12); + }); - expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select Multiple Language Levels'); - }); + // double click should be performed to check, if on blur selection is still present + fireEvent.click(document.body); + await waitFor(() => { + expect(screen.queryByRole('dialog')).toBeNull(); + }); + expect(screen.getByPlaceholderText('C2')).toBeInTheDocument(); - it('should pick single element with cascadeSelection = false', async () => { - const { mocks, dom } = await setupPickerInputForTest({ - value: undefined, - getName: ({ name }) => name, - selectionMode: 'multi', - cascadeSelection: false, - dataSource: mockTreeLikeDataSourceAsync, - }); - fireEvent.click(dom.input); - expect(await PickerInputTestObject.hasOptions()).toBeTruthy(); - await PickerInputTestObject.clickOptionCheckbox('Parent 2'); - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith([2]); + fireEvent.click(document.body); + await waitFor(() => { + expect(screen.queryByRole('dialog')).toBeNull(); + }); + expect(screen.getByPlaceholderText('C2')).toBeInTheDocument(); }); - expect(await PickerInputTestObject.findCheckedOptions()).toEqual(['Parent 2']); - expect(await PickerInputTestObject.findUncheckedOptions()).toEqual(['Parent 1', 'Parent 3']); - expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['Parent 2']); - }); + it('[valueType entity] should select & clear option', async () => { + const { dom, mocks } = await setupPickerInputForTest({ + value: undefined, + selectionMode: 'single', + valueType: 'entity', + }); + expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); + fireEvent.click(dom.input); + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); - it.each<[CascadeSelection]>( - [[true], ['explicit']], - ) - ('should pick multiple elements with cascadeSelection = %s', async (cascadeSelection) => { - const { mocks, dom } = await setupPickerInputForTest({ - value: undefined, - getName: ({ name }) => name, - selectionMode: 'multi', - cascadeSelection, - dataSource: mockTreeLikeDataSourceAsync, - }); + const optionC2 = await screen.findByText('C2'); + fireEvent.click(optionC2); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith({ id: 12, level: 'C2', name: 'Proficiency' }); + }); + fireEvent.click(window.document.body); + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByPlaceholderText('C2')).toBeInTheDocument(); + }); - fireEvent.click(dom.input); - expect(await PickerInputTestObject.hasOptions()).toBeTruthy(); - // Check parent - await PickerInputTestObject.clickOptionCheckbox('Parent 2'); - // Unfold parent - await PickerInputTestObject.clickOptionUnfold('Parent 2'); - await waitFor(() => { - // Test if checkboxes are checked/unchecked - expect(mocks.onValueChange).toHaveBeenLastCalledWith([2, 2.1, 2.2, 2.3]); + const clear = screen.getByRole('button'); + fireEvent.click(clear); + await waitFor(() => { + expect(screen.queryByText('C2')).not.toBeInTheDocument(); + }); }); - expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['Parent 2', 'Child 2.1', 'Child 2.2', 'Child 2.3']); - expect(await PickerInputTestObject.findCheckedOptions()).toEqual(['Parent 2', 'Child 2.1', 'Child 2.2', 'Child 2.3']); - expect(await PickerInputTestObject.findUncheckedOptions()).toEqual(['Parent 1', 'Parent 3']); + it('[valueType entity] should listen to value change', async () => { + const { dom, mocks } = await setupPickerInputForTestWithFirstValueChangeRewriting({ + value: undefined, + selectionMode: 'single', + valueType: 'entity', + }); + expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); + fireEvent.click(dom.input); + expect(screen.getByRole('dialog')).toBeInTheDocument(); + const optionC2 = await screen.findByText('C2'); + fireEvent.click(optionC2); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith({ id: 12, level: 'C2', name: 'Proficiency' }); + }); - // Check child - await PickerInputTestObject.clickOptionCheckbox('Child 2.2'); - await waitFor(() => { - // Test if checkboxes are checked/unchecked - expect(mocks.onValueChange).toHaveBeenLastCalledWith([2.1, 2.3]); - }); - - expect(await PickerInputTestObject.findCheckedOptions()).toEqual(['Child 2.1', 'Child 2.3']); - expect(await PickerInputTestObject.findUncheckedOptions()).toEqual(['Parent 1', 'Parent 2', 'Child 2.2', 'Parent 3']); - }); + fireEvent.click(window.document.body); + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - it('should pick single element with cascadeSelection = implicit', async () => { - const { mocks, dom } = await setupPickerInputForTest({ - value: undefined, - getName: ({ name }) => name, - selectionMode: 'multi', - cascadeSelection: 'implicit', - dataSource: mockTreeLikeDataSourceAsync, - }); + await waitFor(() => { + expect(screen.queryByText('C2')).not.toBeInTheDocument(); + }); - fireEvent.click(dom.input); - await waitFor(async () => { - expect(await PickerInputTestObject.hasOptions()).toBeTruthy(); - }); + fireEvent.click(dom.input); + expect(screen.getByRole('dialog')).toBeInTheDocument(); + const option2C2 = await screen.findByText('C2'); + fireEvent.click(option2C2); - // Check parent - await PickerInputTestObject.clickOptionCheckbox('Parent 2'); - // Unfold parent - await PickerInputTestObject.clickOptionUnfold('Parent 2'); - - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith([2]); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith({ id: 12, level: 'C2', name: 'Proficiency' }); + }); + await waitFor(() => { + expect(screen.getByPlaceholderText('C2')).toBeInTheDocument(); + }); }); - expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['Parent 2']); + it.each<[CascadeSelection]>( + [[false], [true], ['implicit'], ['explicit']], + ) + ('should pick single element with cascadeSelection = %s', async (cascadeSelection) => { + const { mocks, dom } = await setupPickerInputForTest({ + value: undefined, + getName: ({ name }) => name, + selectionMode: 'single', + cascadeSelection, + dataSource: mockTreeLikeDataSourceAsync, + }); + fireEvent.click(dom.input); - expect(await PickerInputTestObject.findCheckedOptions()).toEqual(['Parent 2', 'Child 2.1', 'Child 2.2', 'Child 2.3']); - expect(await PickerInputTestObject.findUncheckedOptions()).toEqual(['Parent 1', 'Parent 3']); + await PickerInputTestObject.waitForOptionsToBeReady(); - // Check child - await PickerInputTestObject.clickOptionCheckbox('Child 2.2'); - await waitFor(() => { - // Test if checkboxes are checked/unchecked - expect(mocks.onValueChange).toHaveBeenLastCalledWith([2.1, 2.3]); - }); - expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['Child 2.1', 'Child 2.3']); - expect(await PickerInputTestObject.findCheckedOptions()).toEqual(['Child 2.1', 'Child 2.3']); - expect(await PickerInputTestObject.findUncheckedOptions()).toEqual(['Parent 1', 'Parent 2', 'Child 2.2', 'Parent 3']); - }); + // Check parent + await PickerInputTestObject.clickOptionByText('Parent 2'); - it('should wrap up extra items if number of elements is greater than maxItems', async () => { - const { mocks, dom } = await setupPickerInputForTest({ - value: undefined, - maxItems: 2, - entityPluralName: 'languages', - selectionMode: 'multi', - }); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith(2); + }); - fireEvent.click(dom.input); - await waitFor(async () => { - expect(await PickerInputTestObject.hasOptions()).toBeTruthy(); + expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Parent 2'); }); - // Check parent - await PickerInputTestObject.clickOptionByText('A1'); - await PickerInputTestObject.clickOptionByText('A1+'); - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith([2, 3]); - }); - expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A1', 'A1+']); + it('should work with maxItems properly(maxItems should not affect single select)', async () => { + const { mocks, dom } = await setupPickerInputForTest({ + value: undefined, + maxItems: 1, + selectionMode: 'single', + }); - await PickerInputTestObject.clickOptionByText('A2'); - await PickerInputTestObject.clickOptionByText('A2+'); - - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith([2, 3, 4, 5]); - }); + fireEvent.click(dom.input); - expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A1', 'A1+', '+ 2']); - }); + await PickerInputTestObject.waitForOptionsToBeReady(); - it('should disable clear', async () => { - const { setProps, dom, result } = await setupPickerInputForTest({ - value: [2, 3], - selectionMode: 'multi', - disableClear: false, - }); - await waitFor(() => expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A1', 'A1+'])); - PickerInputTestObject.clearInput(result.container); - await waitFor(() => { - expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual([]); - }); + // Check parent + await PickerInputTestObject.clickOptionByText('A1'); + fireEvent.click(dom.input); + await PickerInputTestObject.clickOptionByText('A1+'); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith(3); + }); - setProps({ disableClear: true, value: [2, 3] }); - await waitFor(() => { - expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A1', 'A1+']); + expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('A1+'); }); - expect(PickerInputTestObject.hasClearInputButton(result.container)).toBeFalsy(); - }); - it('should select all', async () => { - const { dom, mocks } = await setupPickerInputForTest({ - value: undefined, - selectionMode: 'multi', - maxItems: 100, - }); + it('should clear selected item', async () => { + const { dom } = await setupPickerInputForTest({ + value: undefined, + selectionMode: 'single', + maxItems: 100, + }); + fireEvent.click(dom.input); - fireEvent.click(dom.input); - await waitFor(async () => { expect(await PickerInputTestObject.hasOptions()).toBeTruthy(); - }); - await PickerInputTestObject.clickSelectAllOptions(); + const clearButton = within(screen.getByRole('dialog')).getByRole('button', { name: 'CLEAR' }); + expect(clearButton).toBeInTheDocument(); + expect(clearButton).toHaveAttribute('aria-disabled', 'true'); + + await PickerInputTestObject.clickOptionByText('A1'); + await waitFor(() => { + expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('A1'); + }); - await waitFor(() => { - expect(mocks.onValueChange).toHaveBeenLastCalledWith([2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); - // expect(within(dialog).getByText('CLEAR ALL')).toBeInTheDocument(); - }); + fireEvent.click(dom.input); - await waitFor(() => { - const result = PickerInputTestObject.getSelectedTagsText(dom.target); - return expect(result).toEqual(['A1', 'A1+', 'A2', 'A2+', 'B1', 'B1+', 'B2', 'B2+', 'C1', 'C1+', 'C2']); - }); + const clearButton2 = within(screen.getByRole('dialog')).getByRole('button', { name: 'CLEAR' }); + expect(clearButton2).toHaveAttribute('aria-disabled', 'false'); - await PickerInputTestObject.clickClearAllOptions(); - await waitFor(() => { - expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual([]); + fireEvent.click(clearButton2); + await waitFor(() => { + expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); + }); + + const clearButton3 = within(screen.getByRole('dialog')).getByRole('button', { name: 'CLEAR' }); + expect(clearButton3).toHaveAttribute('aria-disabled', 'true'); }); }); - describe('show only selected', () => { - it('should show only selected items', async () => { - const { dom } = await setupPickerInputForTest({ - value: [4, 2, 6, 8], + describe('Multi Mode', () => { + it('[valueType id] should check & uncheck options', async () => { + const { dom, mocks } = await setupPickerInputForTest({ + value: undefined, selectionMode: 'multi', }); - + expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); fireEvent.click(dom.input); + expect(screen.getByRole('dialog')).toBeInTheDocument(); - const dialog = await screen.findByRole('dialog'); - expect(dialog).toBeInTheDocument(); + await PickerInputTestObject.clickOptionCheckbox('A1'); - await waitFor(async () => { - expect(await PickerInputTestObject.findCheckedOptions()).toEqual(['A1', 'A2', 'B1', 'B2']); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith([2]); }); - expect(await PickerInputTestObject.findUncheckedOptions()).toEqual(['A1+', 'A2+', 'B1+', 'B2+', 'C1', 'C1+', 'C2']); - - await PickerInputTestObject.clickShowOnlySelected(); + await PickerInputTestObject.clickOptionCheckbox('A1+'); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith([2, 3]); + }); + await PickerInputTestObject.clickOptionCheckbox('A1+'); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith([2]); + }); + expect(await PickerInputTestObject.findCheckedOptions()).toEqual(['A1']); - expect(await PickerInputTestObject.findCheckedOptions()).toEqual(['A2', 'A1', 'B1', 'B2']); - expect(await PickerInputTestObject.findUncheckedOptions()).toEqual([]); - }); - }); + fireEvent.click(window.document.body); + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - it('should show selected items by \'Show only selected\' click, and reset \'Show only selected\' mode by search change', async () => { - const { dom } = await setupPickerInputForTest({ - value: undefined, - selectionMode: 'multi', - searchPosition: 'body', - getSearchFields: (item) => [item!.level], + PickerInputTestObject.removeSelectedTagByText(dom.target, 'A1'); // Remove by tags cross in toggler + await waitFor(() => { + expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual([]); + }); }); - expect(dom.input.hasAttribute('readonly')).toBeTruthy(); - fireEvent.click(dom.input); - - const dialog = await screen.findByRole('dialog'); - expect(dialog).toBeInTheDocument(); - - await PickerInputTestObject.waitForOptionsToBeReady(); - - // Verify that all expected options are displayed. - expect(await PickerInputTestObject.findOptionsText({ busy: false })).toEqual([ - 'A1', - 'A1+', - 'A2', - 'A2+', - 'B1', - 'B1+', - 'B2', - 'B2+', - 'C1', - 'C1+', - 'C2', - ]); - - // Click on options 'A1' and 'B1' to select them. - await PickerInputTestObject.clickOptionByText('A1'); - await PickerInputTestObject.clickOptionByText('B1'); + it('[valueType id] should select & clear all', async () => { + const { dom, mocks } = await setupPickerInputForTest({ + value: undefined, + selectionMode: 'multi', + }); + expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); + fireEvent.click(dom.input); + expect(screen.getByRole('dialog')).toBeInTheDocument(); - await PickerInputTestObject.clickShowOnlySelected(); + await PickerInputTestObject.clickOptionCheckbox('A1'); - // Expect that only 'A1' and 'B1' are visible after the filter is applied. - await waitFor(async () => expect(await PickerInputTestObject.findOptionsText({ busy: false })).toEqual(['A1', 'B1'])); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith([2]); + }); - // Type 'A' into the search input and trigger the search. - const bodyInput = within(dialog).getByPlaceholderText('Search'); - fireEvent.change(bodyInput, { target: { value: 'A' } }); + await PickerInputTestObject.clickOptionCheckbox('A1+'); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith([2, 3]); + }); + expect(await PickerInputTestObject.findCheckedOptions()).toEqual(['A1', 'A1+']); - // Expect that only options containing 'A' are shown after the search. - await waitFor(() => expect(PickerInputTestObject.getOptions({ busy: false }).length).toBe(4)); - expect(await PickerInputTestObject.findOptionsText({ busy: false })).toEqual(['A1', 'A1+', 'A2', 'A2+']); + await PickerInputTestObject.clickClearAllOptions(); + await waitFor(() => { + expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual([]); + }); - // Clear the search input and verify that all options are visible again. - fireEvent.change(bodyInput, { target: { value: '' } }); - await waitFor(async () => expect(await PickerInputTestObject.findOptionsText({ busy: false })).toEqual([ - 'A1', - 'A1+', - 'A2', - 'A2+', - 'B1', - 'B1+', - 'B2', - 'B2+', - 'C1', - 'C1+', - 'C2', - ])); - }); - }); + fireEvent.click(window.document.body); + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual([]); - it('should disable input', async () => { - const { dom } = await setupPickerInputForTest({ - value: undefined, - selectionMode: 'single', - isDisabled: true, - }); + fireEvent.click(window.document.body); + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + expect(PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual([]); + }); - expect(dom.input.hasAttribute('disabled')).toBeTruthy(); - expect(dom.input.getAttribute('aria-disabled')?.trim()).toEqual('true'); + it('should keep selection on close body', async () => { + const { dom, mocks } = await setupPickerInputForTest({ + value: undefined, + selectionMode: 'multi', + }); + fireEvent.click(dom.input); + expect(screen.getByRole('dialog')).toBeInTheDocument(); - fireEvent.click(dom.input); - expect(screen.queryByRole('dialog')).toBeNull(); - }); + await PickerInputTestObject.clickOptionCheckbox('A1'); - it('should make an input readonly', async () => { - const { dom } = await setupPickerInputForTest({ - value: undefined, - selectionMode: 'single', - isReadonly: true, - }); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith([2]); + }); - expect(dom.input.hasAttribute('readonly')).toBeTruthy(); - expect(dom.input.getAttribute('aria-readonly')?.trim()).toEqual('true'); + await PickerInputTestObject.clickOptionCheckbox('A1+'); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith([2, 3]); + }); + expect(await PickerInputTestObject.findCheckedOptions()).toEqual(['A1', 'A1+']); + expect(await PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A1', 'A1+']); - fireEvent.click(dom.input); - expect(screen.queryByRole('dialog')).toBeNull(); - }); + fireEvent.click(window.document.body); + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - it.each<[IHasEditMode['mode'] | undefined]>( - [[undefined], ['form'], ['cell'], ['inline']], - )('should render with mode = %s', async (mode) => { - const props: PickerInputComponentProps = { - value: [], - onValueChange: () => {}, - valueType: 'id', - dataSource: mockDataSourceAsync, - disableClear: false, - searchPosition: 'input', - getName: (item: TestItemType) => item.level, - selectionMode: 'multi', - mode, - }; - expect(await renderSnapshotWithContextAsync()).toMatchSnapshot(); - }); + // double click should be performed to check, if on blur selection is still present + fireEvent.click(document.body); + await waitFor(() => { + expect(screen.queryByRole('dialog')).toBeNull(); + }); + expect(await PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A1', 'A1+']); - it.each<['left' | 'right' | undefined]>( - [[undefined], ['left'], ['right']], - )('should render icon at specific position', async (iconPosition) => { - const props: PickerInputComponentProps = { - value: [], - onValueChange: () => {}, - valueType: 'id', - dataSource: mockDataSourceAsync, - disableClear: false, - searchPosition: 'input', - getName: (item: TestItemType) => item.level, - selectionMode: 'multi', - icon: () =>
, - iconPosition, - }; - expect(await renderSnapshotWithContextAsync()).toMatchSnapshot(); - }); + fireEvent.click(document.body); + await waitFor(() => { + expect(screen.queryByRole('dialog')).toBeNull(); + }); + expect(await PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A1', 'A1+']); + }); - it('should pass onClick to the icon', async () => { - const { mocks } = await setupPickerInputForTest({ - value: undefined, - onIconClick: jest.fn(), - icon: () =>
, - }); + it('[valueType id] should listen to value change', async () => { + const { dom, mocks } = await setupPickerInputForTestWithFirstValueChangeRewriting({ + valueForFirstUpdate: [4], + value: undefined, + selectionMode: 'multi', + valueType: 'id', + }); - const iconContainer = screen.getByTestId('test-icon').parentElement as Element; - fireEvent.click(iconContainer); - expect(mocks.onIconClick).toBeCalledTimes(1); - }); + expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); + fireEvent.click(dom.input); + expect(screen.getByRole('dialog')).toBeInTheDocument(); - it('should open dialog only when minCharsToSearch is reached', async () => { - const { dom } = await setupPickerInputForTest({ - value: undefined, - minCharsToSearch: 1, - }); + await PickerInputTestObject.clickOptionCheckbox('A1'); - fireEvent.click(dom.input); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith([2]); + }); + expect(await PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A2']); - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + fireEvent.click(window.document.body); + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - jest.useFakeTimers(); - fireEvent.change(dom.input, { target: { value: 'A' } }); - act(() => { - jest.runAllTimers(); - }); - jest.useRealTimers(); - const pickerBody = await PickerInputTestObject.findDialog(); - return expect(pickerBody).toBeInTheDocument(); - }); + fireEvent.click(dom.input); + expect(screen.getByRole('dialog')).toBeInTheDocument(); + await PickerInputTestObject.clickOptionCheckbox('A1'); - it('should use modal edit mode', async () => { - const { dom } = await setupPickerInputForTest({ - value: undefined, - selectionMode: 'single', - editMode: 'modal', - }); - fireEvent.click(dom.input); - expect(screen.getByAria('modal', 'true')).toBeInTheDocument(); - - expect( - await PickerInputTestObject.findOptionsText({ busy: false, editMode: 'modal' }), - ).toEqual( - ['A1', 'A1+', 'A2', 'A2+', 'B1', 'B1+', 'B2', 'B2+', 'C1', 'C1+', 'C2'], - ); - }); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith([4, 2]); + }); + expect(await PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A2', 'A1']); + }); - it('should render input as invalid', async () => { - const props: PickerInputComponentProps = { - value: undefined, - onValueChange: () => {}, - valueType: 'id', - dataSource: mockDataSourceAsync, - disableClear: false, - searchPosition: 'input', - getName: (item: TestItemType) => item.level, - selectionMode: 'single', - isInvalid: true, - }; - expect(await renderSnapshotWithContextAsync()).toMatchSnapshot(); - }); + it('[valueType entity] should listen to value change', async () => { + const { dom, mocks } = await setupPickerInputForTestWithFirstValueChangeRewriting({ + valueForFirstUpdate: [{ id: 4, level: 'A2', name: 'Pre-Intermediate' }], + value: undefined, + selectionMode: 'multi', + valueType: 'entity', + }); - it('should support single line', async () => { - const props: PickerInputComponentProps = { - value: [], - onValueChange: () => {}, - dataSource: mockDataSourceAsync, - disableClear: false, - searchPosition: 'input', - getName: (item: TestItemType) => item.level, - selectionMode: 'multi', - isSingleLine: true, - }; - expect(await renderSnapshotWithContextAsync()).toMatchSnapshot(); - }); + expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); + fireEvent.click(dom.input); + expect(screen.getByRole('dialog')).toBeInTheDocument(); - it('should provide custom placeholder', async () => { - const { dom } = await setupPickerInputForTest({ - value: undefined, - selectionMode: 'multi', - placeholder: 'Custom placeholder', - }); - expect(await PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Custom placeholder'); - }); + await PickerInputTestObject.clickOptionCheckbox('A1'); - it('should define minBodyWidth', async () => { - const { dom } = await setupPickerInputForTest({ - value: undefined, - selectionMode: 'multi', - minBodyWidth: 300, - }); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith([{ + id: 2, + level: 'A1', + name: 'Elementary', + }]); + }); + expect(await PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A2']); - fireEvent.click(dom.input); + fireEvent.click(window.document.body); + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - const dialog = await screen.findByRole('dialog'); - expect(dialog).toBeInTheDocument(); + fireEvent.click(dom.input); + expect(screen.getByRole('dialog')).toBeInTheDocument(); + await act(async () => { + await PickerInputTestObject.clickOptionCheckbox('A1'); + }); - const dialogBody = dialog.getElementsByClassName('uui-dropdown-body')[0]; - expect(dialogBody).toHaveStyle('min-width: 300px'); - }); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith([{ + id: 4, + level: 'A2', + name: 'Pre-Intermediate', + }, + { + id: 2, + level: 'A1', + name: 'Elementary', + }]); + }); + expect(await PickerInputTestObject.getSelectedTagsText(dom.target)).toEqual(['A2', 'A1']); + }); - it('should define dropdownHeight', async () => { - const { dom } = await setupPickerInputForTest({ - value: undefined, - selectionMode: 'multi', - dropdownHeight: 100, - }); + it('[valueType entity] should check & uncheck several options', async () => { + const { dom, mocks } = await setupPickerInputForTest({ + value: undefined, + selectionMode: 'multi', + valueType: 'entity', + }); + expect(PickerInputTestObject.getPlaceholderText(dom.input)).toEqual('Please select'); + fireEvent.click(dom.input); + expect(screen.getByRole('dialog')).toBeInTheDocument(); - fireEvent.click(dom.input); + await PickerInputTestObject.clickOptionCheckbox('A1'); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith([{ id: 2, level: 'A1', name: 'Elementary' }]); + }); - const dialog = await screen.findByRole('dialog'); - expect(dialog).toBeInTheDocument(); + await PickerInputTestObject.clickOptionCheckbox('A1+'); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith([ + { id: 2, level: 'A1', name: 'Elementary' }, + { id: 3, level: 'A1+', name: 'Elementary+' }, + ]); + }); - const dialogBody = dialog.firstElementChild?.firstElementChild; - expect(dialogBody).toHaveStyle('max-height: 100px'); - }); + await PickerInputTestObject.clickOptionCheckbox('A1+'); + await waitFor(() => { + expect(mocks.onValueChange).toHaveBeenLastCalledWith([ + { id: 2, level: 'A1', name: 'Elementary' }, + ]); + }); + expect(await PickerInputTestObject.findCheckedOptions()).toEqual(['A1']); - it('should render custom toggler', async () => { - const { mocks, dom } = await setupPickerInputForTest({ - value: undefined, - selectionMode: 'multi', - renderToggler: (props) => ( -