import React, { useState } from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { axe } from 'jest-axe';
import 'jest-axe/extend-expect';
import 'regenerator-runtime/runtime';
import 'jest-styled-components';
import '@testing-library/jest-dom';
import { createPortal, expectPortal } from '../../../utils/portal';
import { Grommet } from '../..';
import { Box } from '../../Box';
import { Text } from '../../Text';
import { SelectMultiple } from '..';
describe('SelectMultiple', () => {
window.scrollTo = jest.fn();
beforeEach(createPortal);
test('should not have accessibility violations', async () => {
const { container } = render(
,
);
const results = await axe(container, {
rules: {
/* This rule is flagged because Select is built using a
TextInput within a DropButton. According to Dequeue and
WCAG 4.1.2 "interactive controls must not have focusable
descendants". Jest-axe is assuming that the input is focusable
and since the input is a descendant of the button the rule is
flagged. However, the TextInput is built so that it is read
only and cannot receive focus. Select is accessible
according to the WCAG specification, but jest-axe is flagging
it so we are disabling this rule. */
'nested-interactive': { enabled: false },
},
});
expect(container.firstChild).toMatchSnapshot();
expect(results).toHaveNoViolations();
});
test('defaultValue', () => {
const { container } = render(
,
,
);
expect(container.firstChild).toMatchSnapshot();
});
test('children', () => {
const { container } = render(
{(option) => {option.test}}
,
);
expect(container.firstChild).toMatchSnapshot();
});
test('placeholder', () => {
const { container } = render(
help text}
options={[{ test: 'one' }, { test: 'two' }]}
placeholder="placeholder text"
/>
,
);
expect(container.firstChild).toMatchSnapshot();
});
test('disabled', async () => {
const { container } = render(
,
);
expect(container.firstChild).toMatchSnapshot();
});
test('disabled option', async () => {
window.HTMLElement.prototype.scrollIntoView = jest.fn();
const user = userEvent.setup();
render(
,
);
// open SelectMultiple
await user.click(screen.getByRole('button', { name: /Open Drop/i }));
// try to click all the options
await user.click(screen.getByRole('option', { name: /0/i }));
await user.click(screen.getByRole('option', { name: /1/i }));
await user.click(screen.getByRole('option', { name: /2/i }));
// only 2 options should be selected (0 and 2)
expectPortal('test-select__drop').toMatchSnapshot();
});
test('limit', async () => {
window.HTMLElement.prototype.scrollIntoView = jest.fn();
const user = userEvent.setup();
render(
,
);
// open SelectMultiple
await user.click(screen.getByRole('button', { name: /Open Drop/i }));
// select 2 options
await user.click(screen.getByRole('option', { name: /0/i }));
await user.click(screen.getByRole('option', { name: /1/i }));
await user.click(screen.getByRole('option', { name: /2/i }));
// option 2 should be disabled
expectPortal('test-select__drop').toMatchSnapshot();
});
test('showSelectionInline', async () => {
// Mock scrollIntoView since JSDOM doesn't do layout.
// https://github.com/jsdom/jsdom/issues/1695#issuecomment-449931788
window.HTMLElement.prototype.scrollIntoView = jest.fn();
const user = userEvent.setup();
const open = jest.fn();
const close = jest.fn();
const { container } = render(
,
);
// open SelectMultiple
await user.click(screen.getByRole('button', { name: /Open Drop/i }));
expect(open).toHaveBeenCalled();
// click all the options
await user.click(screen.getByRole('option', { name: /0/i }));
await user.click(screen.getByRole('option', { name: /1/i }));
await user.click(screen.getByRole('option', { name: /2/i }));
// close SelectMultiple
await user.click(screen.getByRole('button', { name: /Close Select/i }));
expect(close).toHaveBeenCalled();
// all options should be visible when drop is closed
expect(container.firstChild).toMatchSnapshot();
});
test('showSelectionInline with children', async () => {
// Mock scrollIntoView since JSDOM doesn't do layout.
// https://github.com/jsdom/jsdom/issues/1695#issuecomment-449931788
window.HTMLElement.prototype.scrollIntoView = jest.fn();
const user = userEvent.setup();
const { container } = render(
{(option, state) => (
{option}
)}
,
);
// open SelectMultiple
await user.click(screen.getByRole('button', { name: /Open Drop/i }));
// click all the options
await user.click(screen.getByRole('option', { name: /0/i }));
await user.click(screen.getByRole('option', { name: /1/i }));
await user.click(screen.getByRole('option', { name: /2/i }));
// close SelectMultiple
await user.click(screen.getByRole('button', { name: /Close Select/i }));
// all options should be visible when drop is closed
expect(container.firstChild).toMatchSnapshot();
// unselect option at input level
await user.click(screen.getByRole('option', { name: /0/i }));
await user.click(screen.getByRole('option', { name: /2/i }));
await user.click(screen.getByRole('option', { name: /1/i }));
// options should no longer be visible after unselecting them
expect(container.firstChild).toMatchSnapshot();
});
test('showSelectionInline with disabled options', () => {
// Mock scrollIntoView since JSDOM doesn't do layout.
// https://github.com/jsdom/jsdom/issues/1695#issuecomment-449931788
window.HTMLElement.prototype.scrollIntoView = jest.fn();
const { container } = render(
,
);
expect(container.firstChild).toMatchSnapshot();
});
test('keyboard interactions', async () => {
// Mock scrollIntoView since JSDOM doesn't do layout.
// https://github.com/jsdom/jsdom/issues/1695#issuecomment-449931788
window.HTMLElement.prototype.scrollIntoView = jest.fn();
const user = userEvent.setup();
const { container } = render(
,
);
await user.click(screen.getByRole('button', { name: /Open Drop/i }));
const input = screen.getByRole('listbox');
fireEvent.keyDown(input, { keyCode: 40 }); // down
fireEvent.keyDown(input, { keyCode: 13 }); // enter
fireEvent.keyDown(input, { keyCode: 40 }); // down
fireEvent.keyDown(input, { keyCode: 40 }); // down
fireEvent.keyDown(input, { keyCode: 38 }); // up
fireEvent.keyDown(input, { keyCode: 13 }); // enter
expect(container.firstChild).toMatchSnapshot();
});
test('valueKey and labelKey', () => {
const { container } = render(
,
);
expect(container.firstChild).toMatchSnapshot();
});
test('test inline click on object', async () => {
const TestComp = () => {
const [value, setValue] = useState([
{ value: 'one', label: 'a' },
{ value: 'two', label: 'b' },
{ value: 'tree', label: 'c' },
]);
return (
setValue(selected)}
/>
);
};
render();
const user = userEvent.setup();
expect(screen.getByRole('option', { name: /a selected/ })).toBeVisible();
expect(screen.getByRole('option', { name: /b selected/ })).toBeVisible();
expect(screen.getByRole('option', { name: /c selected/ })).toBeVisible();
await user.click(screen.getByRole('option', { name: /c selected/ }));
expect(screen.getByRole('option', { name: /a selected/ })).toBeVisible();
expect(screen.getByRole('option', { name: /b selected/ })).toBeVisible();
expect(screen.queryByRole('option', { name: /c selected/ })).toBeNull();
});
test('search', async () => {
const user = userEvent.setup();
const onSearch = jest.fn();
render(
,
);
await user.click(screen.getByRole('button', { name: /Open Drop/i }));
const input = screen.getByRole('searchbox');
await user.type(input, 'th');
expect(onSearch).toBeCalledWith(expect.stringMatching(/^th/));
});
test('select all and clear', async () => {
const user = userEvent.setup();
render(
,
);
await user.click(screen.getByRole('button', { name: /Open Drop/i }));
await user.click(screen.getByRole('button', { name: /Select All/i }));
expectPortal('test-select__drop').toMatchSnapshot();
await user.click(screen.getByRole('button', { name: /Clear All/i }));
expectPortal('test-select__drop').toMatchSnapshot();
});
test('null value', () => {
const { asFragment } = render(
{/* @ts-ignore */}
,
);
expect(asFragment()).toMatchSnapshot();
});
test('object value', () => {
render(
,
);
expect(screen.getByRole('option', { name: /a selected/ })).toBeVisible();
});
test('search with select and clear', async () => {
const user = userEvent.setup();
const defaultOptions = [
'Apple',
'Orange',
'Banana',
'Grape',
'Melon',
'Strawberry',
'Kiwi',
'Mango',
'Raspberry',
'Rhubarb',
];
const Test = () => {
const [options, setOptions] = useState(defaultOptions);
const [valueMultiple, setValueMultiple] = useState([]);
return (
{
const escapedText = text.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
const exp = new RegExp(escapedText, 'i');
setOptions(defaultOptions.filter((o) => exp.test(o)));
}}
onClose={() => setOptions(defaultOptions)}
onChange={({ value }) => {
setValueMultiple(value);
}}
/>
);
};
render();
// open drop
await user.click(screen.getByRole('button', { name: /Select/ }));
// search
await user.type(screen.getByRole('searchbox', { name: /Search/ }), 'p');
// select all
await user.click(screen.getByRole('button', { name: /Select all/ }));
expect(
screen.queryByRole('option', { name: /Apple selected/ }),
).not.toBeNull();
expect(
screen.queryByRole('option', { name: /Grape selected/ }),
).not.toBeNull();
expect(
screen.queryByRole('option', { name: /Raspberry selected/ }),
).not.toBeNull();
// search
await user.type(
screen.getByRole('searchbox', { name: /Search/ }),
'{backspace}w',
);
// select all
await user.click(screen.getByRole('button', { name: /Select all/ }));
expect(
screen.queryByRole('option', { name: /Strawberry selected/ }),
).not.toBeNull();
expect(
screen.queryByRole('option', { name: /Kiwi selected/ }),
).not.toBeNull();
// search
await user.type(
screen.getByRole('searchbox', { name: /Search/ }),
'{backspace}b',
);
// Clear
await user.click(screen.getByRole('button', { name: /Clear all/ }));
// clear search
await user.type(
screen.getByRole('searchbox', { name: /Search/ }),
'{backspace}',
);
expect(
screen.queryByRole('option', { name: /Grape selected/ }),
).not.toBeNull();
expect(
screen.queryByRole('option', { name: /Strawberry selected/ }),
).toBeNull();
});
test('empty options', async () => {
window.HTMLElement.prototype.scrollIntoView = jest.fn();
const user = userEvent.setup();
render(
,
);
// open SelectMultiple
await user.click(screen.getByRole('button', { name: /Open Drop/i }));
expectPortal('test-select__drop').toMatchSnapshot();
});
});