import React, { useState } from 'react';
import { waitFor, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import renderWithTheme from '../../../testUtils/renderWithTheme';
import SingleSelect from '..';
import Icon from '../../Icon';
describe('rendering', () => {
it('renders input and not render list when disabled', async () => {
const options = [
{ value: 1, text: 'Item 1' },
{ value: 2, text: 'Item 2' },
];
const { getByText, getByPlaceholderText } = renderWithTheme(
);
await waitFor(() => {
expect(getByPlaceholderText('Select an item')).toBeInTheDocument();
expect(getByText('Item 1')).not.toBeVisible();
expect(getByText('Item 2')).not.toBeVisible();
});
fireEvent.click(getByPlaceholderText('Select an item'));
await waitFor(() => {
expect(getByText('Item 1')).not.toBeVisible();
expect(getByText('Item 2')).not.toBeVisible();
});
});
it('renders input and option list', async () => {
const options = [
{ value: 1, text: 'Item 1' },
{ value: 2, text: 'Item 2' },
];
const { getByText, getByPlaceholderText } = renderWithTheme(
);
await waitFor(() => {
expect(getByPlaceholderText('Select an item')).toBeInTheDocument();
expect(getByText('Item 1')).not.toBeVisible();
expect(getByText('Item 2')).not.toBeVisible();
});
fireEvent.click(getByPlaceholderText('Select an item'));
await waitFor(() => {
expect(getByText('Item 1')).toBeVisible();
expect(getByText('Item 2')).toBeVisible();
});
});
it('renders input and option list with category names', async () => {
const options = [
{
category: 'Teams',
options: [{ value: 'team-1', text: 'Team 1', helpText: '5 members' }],
},
{
category: 'Locations',
options: [{ value: 'location-1', text: 'Location 1' }],
},
{
category: 'Individual',
options: [{ value: 'person-1', text: 'Person 1' }],
},
];
const { getByText, getByPlaceholderText } = renderWithTheme(
);
await waitFor(() => {
expect(getByPlaceholderText('Select an item')).toBeInTheDocument();
expect(getByText('Teams')).not.toBeVisible();
expect(getByText('Locations')).not.toBeVisible();
expect(getByText('Individual')).not.toBeVisible();
expect(getByText('Team 1')).not.toBeVisible();
expect(getByText('Location 1')).not.toBeVisible();
expect(getByText('Person 1')).not.toBeVisible();
expect(getByText('5 members')).not.toBeVisible();
});
fireEvent.click(getByPlaceholderText('Select an item'));
await waitFor(() => {
expect(getByText('Teams')).toBeVisible();
expect(getByText('Locations')).toBeVisible();
expect(getByText('Individual')).toBeVisible();
expect(getByText('Team 1')).toBeVisible();
expect(getByText('Location 1')).toBeVisible();
expect(getByText('Person 1')).toBeVisible();
expect(getByText('5 members')).toBeVisible();
});
});
it('renders custom option renderer with additional option props', async () => {
const options = [
{ value: 'item-1', text: 'Item 1', icon: 'add-person' } as const,
{ value: 'item-2', text: 'Item 2', icon: 'alignment' } as const,
];
const { getByText, getByPlaceholderText } = renderWithTheme(
(
<>
{`${
index + 1
}: ${text}`}
>
)}
onChange={jest.fn()}
placeholder="Select an item"
/>
);
await waitFor(() => {
expect(getByPlaceholderText('Select an item')).toBeInTheDocument();
});
fireEvent.click(getByPlaceholderText('Select an item'));
await waitFor(() => {
expect(getByText('1: Item 1')).toBeVisible();
expect(getByText('2: Item 2')).toBeVisible();
});
expect(
getByText('1: Item 1').parentElement?.querySelector('i')
).toHaveClass('hero-icon-add-person');
expect(
getByText('2: Item 2').parentElement?.querySelector('i')
).toHaveClass('hero-icon-alignment');
});
});
describe('interaction', () => {
it('allows to select an item', async () => {
const options = [
{ value: 1, text: 'Item 1' },
{ value: 2, text: 'Item 2' },
];
const onChange = jest.fn();
const { getByText, getByPlaceholderText } = renderWithTheme(
);
fireEvent.click(getByPlaceholderText('Select an item'));
fireEvent.click(getByText('Item 1'));
await waitFor(() => {
expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange).toHaveBeenCalledWith(1);
});
});
it('allows to select an item from grouped options', async () => {
const options = [
{
category: 'Teams',
options: [
{ value: 'team-1', text: 'Team 1', helpText: '5 members' },
{ value: 'team-2', text: 'Team 2', helpText: '3 members' },
],
},
{
category: 'Locations',
options: [{ value: 'location-1', text: 'Location 1' }],
},
{
category: 'Individual',
options: [{ value: 'person-1', text: 'Person 1' }],
},
];
const onChange = jest.fn();
const { getByText, getByPlaceholderText } = renderWithTheme(
);
fireEvent.click(getByPlaceholderText('Select an item'));
fireEvent.click(getByText('Location 1'));
await waitFor(() => {
expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange).toHaveBeenCalledWith('location-1');
});
});
it('allows to search for an item', async () => {
const options = [
{ value: 1, text: 'Item 1' },
{ value: 2, text: 'Item 2' },
];
const onChange = jest.fn();
const onQueryChange = jest.fn();
const { getByText, getByPlaceholderText, queryByText } = renderWithTheme(
);
await waitFor(() => {
expect(getByText('Item 1')).toBeInTheDocument();
expect(queryByText('Item 2')).not.toBeInTheDocument();
});
fireEvent.change(getByPlaceholderText('Select an item'), {
target: { value: '2' },
});
await waitFor(() => {
expect(onQueryChange).toHaveBeenCalledTimes(1);
expect(onQueryChange).toHaveBeenCalledWith('2');
});
});
it('allows to create new item', async () => {
const options = [
{ value: 1, text: 'Item 1' },
{ value: 2, text: 'Item 2' },
];
const onChange = jest.fn();
const onQueryChange = jest.fn();
const onCreateNewOption = jest.fn();
const { getByText, getByPlaceholderText, queryByText } = renderWithTheme(
);
await waitFor(() => {
expect(queryByText('Item 1')).not.toBeInTheDocument();
expect(queryByText('Item 2')).not.toBeInTheDocument();
expect(getByText('New Item')).not.toBeVisible();
});
fireEvent.click(getByPlaceholderText('Select an item'));
await waitFor(() => {
expect(getByText('New Item')).toBeVisible();
});
fireEvent.click(getByText('New Item'));
await waitFor(() => {
expect(onCreateNewOption).toHaveBeenCalledTimes(1);
expect(onCreateNewOption).toHaveBeenCalledWith('New Item');
});
});
it('allows to call callback when scrolling to bottom of the list', async () => {
const options = [
{ value: 1, text: 'Item 1' },
{ value: 2, text: 'Item 2' },
{ value: 3, text: 'Item 3' },
{ value: 4, text: 'Item 4' },
{ value: 5, text: 'Item 5' },
{ value: 6, text: 'Item 6' },
{ value: 7, text: 'Item 7' },
{ value: 8, text: 'Item 8' },
{ value: 9, text: 'Item 9' },
{ value: 10, text: 'Item 10' },
];
const onChange = jest.fn();
const onScrollListToBottom = jest.fn();
const { getByText, getByRole, getByPlaceholderText } = renderWithTheme(
);
fireEvent.click(getByPlaceholderText('Select an item'));
await waitFor(() => {
expect(getByText('Item 10')).toBeVisible();
expect(onScrollListToBottom).not.toHaveBeenCalled();
});
fireEvent.scroll(getByRole('listbox'), { y: 200 });
await waitFor(() => {
expect(onScrollListToBottom).toHaveBeenCalledTimes(1);
});
});
it('allows to clear selected item', async () => {
const options = [
{ value: 1, text: 'Item 1' },
{ value: 2, text: 'Item 2' },
];
const onChange = jest.fn();
const { getByTestId } = renderWithTheme(
);
userEvent.hover(getByTestId('query-input'));
userEvent.click(getByTestId('remove-icon'));
await waitFor(() => {
expect(onChange).toHaveBeenCalledWith(undefined);
});
});
it('only calls onQueryChange when query is not undefined', async () => {
const options = [
{ value: 1, text: 'Item 1' },
{ value: 2, text: 'Item 2' },
];
const onChange = jest.fn();
const onQueryChange = jest.fn();
const { getByText, findByText, getByTestId } = renderWithTheme(
option.text.substring(4)}
onChange={onChange}
onQueryChange={onQueryChange}
clearable
/>
);
const input = getByTestId('query-input');
// open select dropdown
userEvent.click(input);
expect(onQueryChange).not.toHaveBeenCalled();
// select item 1
const itemOne = await findByText('Item 1');
expect(itemOne).toBeVisible();
userEvent.click(itemOne);
expect(onQueryChange).not.toHaveBeenCalled();
// open select dropdown again
userEvent.click(input);
expect(onQueryChange).not.toHaveBeenCalled();
// select item 2
const itemTwo = await findByText('Item 2');
expect(itemTwo).toBeVisible();
userEvent.click(itemTwo);
expect(onQueryChange).not.toHaveBeenCalled();
// open select dropdown again
userEvent.click(input);
// close dropdown by clicking outside Select
userEvent.click(document.body);
expect(getByText('Item 1')).not.toBeVisible();
expect(getByText('Item 2')).not.toBeVisible();
expect(onQueryChange).not.toHaveBeenCalled();
// Change query
await waitFor(() => {
userEvent.type(input, 'Item');
});
expect(onQueryChange).toHaveBeenCalled();
expect(onQueryChange).not.toHaveBeenCalledWith(undefined);
});
it('makes onQueryChange to have been called with undefined when onBlur is undefined', async () => {
const options = [{ value: 1, text: 'Item 1' }];
const onChange = jest.fn();
const onFocus = jest.fn();
const onQueryChange = jest.fn();
const { getByPlaceholderText } = renderWithTheme(
);
const input = getByPlaceholderText('Select an item');
// click on select input
userEvent.click(input);
await waitFor(() => {
expect(onFocus).toHaveBeenCalledTimes(1);
});
// click outside
userEvent.click(document.body);
expect(onQueryChange).toHaveBeenCalledWith(undefined);
});
it('calls onBlur and onFocus when they are not undefined', async () => {
const options = [{ value: 1, text: 'Item 1' }];
const onChange = jest.fn();
const onBlur = jest.fn();
const onFocus = jest.fn();
const onQueryChange = jest.fn();
const { getByTestId } = renderWithTheme(
);
const input = getByTestId('query-input');
// click on select input
userEvent.click(input);
await waitFor(() => {
expect(onFocus).toHaveBeenCalledTimes(1);
expect(onBlur).not.toHaveBeenCalled();
});
// click outside
userEvent.click(document.body);
expect(onBlur).toHaveBeenCalledTimes(1);
expect(onQueryChange).not.toHaveBeenCalledWith(undefined);
});
it('shows noResult text even when loading', async () => {
const onChange = jest.fn();
const { getByPlaceholderText, getByText } = renderWithTheme(
);
const input = getByPlaceholderText('Select items');
// click on select input
userEvent.click(input);
await waitFor(() => {
expect(getByText('Not found')).toBeInTheDocument();
});
});
it('works well when value got changed outside', () => {
const options = [
{ value: 'item-1', text: 'Item 1' },
{ value: 'item-2', text: 'Item 2' },
];
const Example = () => {
const [value, setValue] = useState();
return (
<>
setValue(e.target.value)}
data-test-id="input"
/>
>
);
};
const { getByTestId, getByText } = renderWithTheme();
const select = getByTestId('select');
userEvent.click(select);
userEvent.click(getByText('Item 1'));
expect(select).toSelectItem('Item 1');
const input = getByTestId('input');
userEvent.click(input);
userEvent.clear(input);
expect(select).toSelectItem('');
userEvent.click(select);
userEvent.click(getByText('Item 1'));
expect(select).toSelectItem('Item 1');
});
it('shows spinner when loading', () => {
const options = [
{ value: 'item-1', text: 'Item 1' },
{ value: 'item-2', text: 'Item 2' },
];
const { getByTestId } = renderWithTheme(
);
expect(getByTestId('loading-icon')).toBeVisible();
});
});