import * as React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ComponentClassNames } from '../../shared'; import { SelectField } from '../SelectField'; import { testFlexProps, expectFlexContainerStyleProps, } from '../../Flex/__tests__/Flex.test'; import { AUTO_GENERATED_ID_PREFIX } from '../../utils/useStableId'; describe('SelectField', () => { const className = 'my-select'; const descriptiveText = 'This is a descriptive text'; const id = 'my-select'; const label = 'Number'; const role = 'combobox'; const testId = 'test-select'; const errorMessage = 'This is an error message'; describe('Flex wrapper', () => { it('should render default and custom classname', async () => { render( ); const selectField = await screen.findByTestId(testId); expect(selectField).toHaveClass(className); expect(selectField).toHaveClass(ComponentClassNames.Field); expect(selectField).toHaveClass(ComponentClassNames.SelectField); }); it('should render all flex style props', async () => { render( ); const selectField = await screen.findByTestId(testId); expectFlexContainerStyleProps(selectField); }); }); describe('Label', () => { it('should render expected field classname', async () => { render( ); const labelElelment = await screen.findByText(label); expect(labelElelment).toHaveClass(ComponentClassNames.Label); }); it('should match select id', async () => { render( ); const labelElelment = await screen.findByText(label); const select = await screen.findByRole(role); expect(labelElelment).toHaveAttribute('for', select.id); }); it('should have `amplify-visually-hidden` class when labelHidden is true', async () => { render( ); const labelElelment = await screen.findByText(label); expect(labelElelment).toHaveClass('amplify-visually-hidden'); }); }); describe('Select control', () => { it('should render expected id for select control', async () => { render( ); const select = await screen.findByRole(role); expect(select).toHaveAttribute('id', id); }); it('should forward ref to DOM element', async () => { const ref = React.createRef(); render( ); await screen.findByRole(role); expect(ref.current?.nodeName).toBe('SELECT'); }); it('should render labeled select field when id is provided', async () => { render( ); const field = await screen.findByLabelText(label); expect(field).toHaveClass(ComponentClassNames.Select); expect(field.id).toBe(id); }); it('should render labeled select when id is not provided, and is autogenerated', async () => { render( ); const field = await screen.findByLabelText(label); expect(field.id.startsWith(AUTO_GENERATED_ID_PREFIX)).toBe(true); expect(field).toHaveClass(ComponentClassNames.Select); }); }); it('should render the state attributes', async () => { render( ); const select = await screen.findByRole(role); expect(select).toBeDisabled(); expect(select).toBeRequired(); }); it('should set size and variation data attributes', async () => { render( ); const selectField = await screen.findByTestId(testId); const select = await screen.findByRole(role); expect(selectField).toHaveAttribute('data-size', 'small'); expect(select).toHaveAttribute('data-variation', 'quiet'); }); it('should render size classes for SelectField', async () => { render(
); const small = await screen.findByTestId('small'); const large = await screen.findByTestId('large'); expect(small.classList).toContain(`${ComponentClassNames['Field']}--small`); expect(large.classList).toContain(`${ComponentClassNames['Field']}--large`); }); it('can set defaultValue', async () => { render( ); const select = await screen.findByRole(role); expect(select).toHaveValue('1'); }); it('has aria-invalid attribute when hasError is true', async () => { render( ); const select = await screen.findByRole(role); expect(select).toHaveAttribute('aria-invalid'); }); it('should fire event handlers', async () => { const onChange = jest.fn(); render( ); const select = await screen.findByRole(role); userEvent.selectOptions(select, '2'); expect(onChange).toHaveBeenCalled(); }); describe('Descriptive message', () => { it('renders when descriptiveText is provided', () => { render( ); const descriptiveField = screen.queryByText(descriptiveText); expect(descriptiveField).toContainHTML(descriptiveText); }); it('should map to descriptive text correctly', async () => { render( ); const select = await screen.findByRole(role); expect(select).toHaveAccessibleDescription(descriptiveText); }); }); describe('Error messages', () => { it("don't show when hasError is false", () => { render( ); const errorText = screen.queryByText(errorMessage); expect(errorText).not.toBeInTheDocument(); }); it('show when hasError and errorMessage', () => { render( ); const errorText = screen.queryByText(errorMessage); expect(errorText?.innerHTML).toContain(errorMessage); }); }); describe('Options', () => { it('correctly maps the options prop to corresponding option tags with matching value, label and children', async () => { const optionStrings = ['lions', 'tigers', 'bears']; render(); const selectFieldOptions = await screen.findAllByRole('option'); expect(selectFieldOptions.length).toBe(optionStrings.length); selectFieldOptions.forEach((option, index) => { const optionString = optionStrings[index]; expect(option).toHaveAttribute('value', optionString); expect(option).toHaveAttribute('label', optionString); expect(option.innerHTML).toBe(optionString); }); }); it('logs a warning to the console if the customer passes both children and options', () => { const warningMessage = 'Amplify UI: component defaults to rendering children over `options`. When using the `options` prop, omit children.'; // add empty mockImplementation to prevent logging output to the console const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); const optionStrings = ['lions', 'tigers', 'bears']; render( ); expect(consoleWarnSpy).toHaveBeenCalledWith(warningMessage); }); }); });