Testing with React Testing Library
Testing with React Testing Library
About React Testing Library
getByText()
to facilitate that.
Shadow DOM Testing Library
It is not possible to exclusively use RTL to test
@ukic/react
components as they are React-wrapped web components, which use the
<ShadowRoot>
). RTL does not provide utility functions that traverse beyond the shadow DOM. With the addition of
getByShadowText()
).
// DOM tree for IcAlert.
<ic-alert class="dark hydrated" message="Foo" role="alert"><ShadowRoot>
...
<ic-typography class="ic-typography-body hydrated"><ShadowRoot><slot /></ShadowRoot>
Foo
</ic-typography>
...
</ShadowRoot></ic-alert>
import { IcAlert } from "@ukic/react";
import { render, waitFor } from "@testing-library/react";
import { screen } from "shadow-dom-testing-library";
it("renders IcAlert", async () => {
const { getByRole /* container */ } = render(<IcAlert message="Foo" />);
// Wait for IcAlert to hydrate before continuing to view the full IcAlert DOM tree.
await waitFor(() => {
expect(getByRole("alert")).toHaveClass("hydrated");
// 'container' can also be used with querySelector to select ic-alert.
// expect(container.querySelector('ic-alert')).toHaveClass('hydrated');
});
expect(await screen.findByShadowText("Foo")).toBeInTheDocument();
});
Examples with IcSelect and IcTextField
Below are examples on testing@ukic/react
components using RTL and Shadow DOM Testing Library withIcSelect
andIcTextField
.
IcSelect
// Select.tsx
import { useState } from "react";
import { IcSelect } from "@ukic/react";
import { IcMenuOption } from "@ukic/web-components";
interface IcMenuOptions {
options: IcMenuOption[];
}
const Select = ({ options }: IcMenuOptions) => {
const [selectedValue, setSelectedValue] =
(useState < string) | (undefined > undefined);
return (
<IcSelect
label="Title"
onIcChange={(e) => setSelectedValue(e.detail.value)}
options={options}
value={selectedValue}
/>
);
};
export default Select;
// Select.test.tsx
import { fireEvent, render, waitFor } from '@testing-library/react';
import { screen } from 'shadow-dom-testing-library';
import Select from './components/Select';
import userEvent from '@testing-library/user-event';
const options = [
{ label: 'Americano', value: 'americano' },
{ label: 'Espresso', value: 'espresso' },
]
describe('IcSelect', () => {
it('renders', async () => {
const { container } = render(<Select options={options} />);
// Using 'container' to query the render and select 'ic-select'.
const select = container.querySelector('ic-select');
// Make sure component is hydrated before moving to the next action.
await waitFor(() => {
expect(select).toHaveClass('hydrated');
});
/**
* 'Shallow' sets the selector to go one <ShadowRoot> level deep.
* There are multiple instances where 'Title' is selected due to aria properties
* so one level deep is enough to select the label.
*/
expect(screen.getByShadowText('Title')).toBeInTheDocument();
})
it("should render the correct number of options in the menu", async () => {
const { container } = render(<Select options={options} />);
const select = container.querySelector('ic-select');
await waitFor(() => {
expect(select).toHaveClass('hydrated');
});
const allOptions = await screen.findAllByShadowRole('option');
expect(await (allOptions).length).toBe(2);
});
it('should change select value using userEvent', async () => {
// Using testing-library/user-events to select an option from ic-select.
const user = userEvent.setup();
const { container } = render(<Select options={options} />);
const select = container.querySelector('ic-select') as HTMLIcSelectElement;
await waitFor(() => {
expect(select).toHaveClass('hydrated');
});
// ic-select uses a button element to trigger the dropdown so using the button role as the selector.
const btn = await screen.findByShadowRole('button');
await user.click(btn);
// Selecting 'Espresso' from the option list.
const espressoOption = await screen.getByShadowLabelText('Espresso');
await user.click(espressoOption);
expect(select).toHaveValue('espresso');
})
it('should change select value using fireEvent', async () => {
const { container } = render(<Select options={options} />);
const select = container.querySelector('ic-select') as HTMLIcSelectElement;
await waitFor(() => {
expect(select).toHaveClass('hydrated');
});
// Invoking icChange method to select an item from the option list.
fireEvent(select, new CustomEvent('icChange', { detail: { value: 'americano' }}));
expect(select).toHaveValue('americano');
});
})
IcTextField
// TextField.tsx
import { IcTextField } from "@ukic/react";
import { useState } from "react";
interface NameProps {
name: string;
}
const TextField = ({ name }: NameProps) => {
const [value, setTextFieldValue] = useState < string > "";
const handleInput = (event: CustomEvent) => {
setTextFieldValue(event?.detail?.value);
};
return <IcTextField label={name} onIcInput={handleInput} value={value} />;
};
export default TextField;
// TextField.test.tsx
import { render, waitFor } from '@testing-library/react';
import { screen, findByShadowLabelText } from 'shadow-dom-testing-library';
import TextField from '../components/TextField';
import userEvent from '@testing-library/user-event';
describe('IcTextField', () => {
it('should change textField value while typing', async () => {
const user = userEvent.setup();
const { container } = render(<TextField name='Foo' />);
const textField = container.querySelector('ic-text-field') as HTMLIcTextFieldElement;
await waitFor(() => {
expect(textField).toHaveClass('hydrated');
});
const input = await findByShadowLabelText(textField, 'Foo');
// Using the userEvent 'type' method to input a value into IcTextField.
await user.type(input, 'Bar');
expect(textField).toHaveValue('Bar');
});
})