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(); }); });