Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
import React from "react";
import { rest } from "msw";
import { setupServer } from "msw/node";
import { render, fireEvent, waitFor, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import Fetch from "../fetch";
const server = setupServer(
rest.get("/greeting", (req, res, ctx) => {
return res(ctx.json({ greeting: "hello there" }));
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
test("loads and displays greeting", async () => {
render(<Fetch url="/greeting" />);
fireEvent.click(screen.getByText("Load Greeting"));
await waitFor(() => screen.getByRole("heading"));
expect(screen.getByRole("heading")).toHaveTextContent("hello there");
expect(screen.getByRole("button")).toBeDisabled();
});
test("handles server error", async () => {
server.use(
rest.get("/greeting", (req, res, ctx) => {
return res(ctx.status(500));
})
);
render(<Fetch url="/greeting" />);
fireEvent.click(screen.getByText("Load Greeting"));
await waitFor(() => screen.getByRole("alert"));
expect(screen.getByRole("alert")).toHaveTextContent("Oops, failed to fetch!");
expect(screen.getByRole("button")).not.toBeDisabled();
});// import dependencies
import React from "react";
// import API mocking utilities from Mock Service Worker
import { rest } from "msw";
import { setupServer } from "msw/node";
// import react-testing methods
import { render, fireEvent, waitFor, screen } from "@testing-library/react";
// add custom jest matchers from jest-dom
import "@testing-library/jest-dom";
// the component to test
import Fetch from "../fetch";test("loads and displays greeting", async () => {
// Arrange
// Act
// Assert
});// declare which API requests to mock
const server = setupServer(
// capture "GET /greeting" requests
rest.get("/greeting", (req, res, ctx) => {
// respond using a mocked JSON body
return res(ctx.json({ greeting: "hello there" }));
})
);
// establish API mocking before all tests
beforeAll(() => server.listen());
// reset any request handlers that are declared as a part of our tests
// (i.e. for testing one-time error scenarios)
afterEach(() => server.resetHandlers());
// clean up once the tests are done
afterAll(() => server.close());
// ...
test("handles server error", async () => {
server.use(
// override the initial "GET /greeting" request handler
// to return a 500 Server Error
rest.get("/greeting", (req, res, ctx) => {
return res(ctx.status(500));
})
);
// ...
});render(<Fetch url="/greeting" />);fireEvent.click(screen.getByText("Load Greeting"));
// wait until the `get` request promise resolves and
// the component calls setState and re-renders.
// `waitFor` waits until the callback doesn't throw an error
await waitFor(() =>
// getByRole throws an error if it cannot find an element
screen.getByRole("heading")
);// assert that the alert message is correct using
// toHaveTextContent, a custom matcher from jest-dom.
expect(screen.getByRole("alert")).toHaveTextContent("Oops, failed to fetch!");
// assert that the button is not disabled using
// toBeDisabled, a custom matcher from jest-dom.
expect(screen.getByRole("button")).not.toBeDisabled();import React, { useState, useReducer } from "react";
import axios from "axios";
const initialState = {
error: null,
greeting: null,
};
function greetingReducer(state, action) {
switch (action.type) {
case "SUCCESS": {
return {
error: null,
greeting: action.greeting,
};
}
case "ERROR": {
return {
error: action.error,
greeting: null,
};
}
default: {
return state;
}
}
}
export default function Fetch({ url }) {
const [{ error, greeting }, dispatch] = useReducer(
greetingReducer,
initialState
);
const [buttonClicked, setButtonClicked] = useState(false);
const fetchGreeting = async (url) =>
axios
.get(url)
.then((response) => {
const { data } = response;
const { greeting } = data;
dispatch({ type: "SUCCESS", greeting });
setButtonClicked(true);
})
.catch((error) => {
dispatch({ type: "ERROR", error });
});
const buttonText = buttonClicked ? "Ok" : "Load Greeting";
return (
<div>
<button onClick={() => fetchGreeting(url)} disabled={buttonClicked}>
{buttonText}
</button>
{greeting && <h1>{greeting}</h1>}
{error && <p role="alert">Oops, failed to fetch!</p>}
</div>
);
}fireEvent.click(node)afterEach to reset DOM between tests)import { render, fireEvent, screen } from "@testing-library/react";
test("loads items eventually", async () => {
render(<Page />);
// Click button
fireEvent.click(screen.getByText("Load"));
// Wait for page to update with query text
const items = await screen.findAllByText(/Item #[0-9]: /);
expect(items).toHaveLength(10);
});<div>Hello World</div>// Matching a string:
getByText("Hello World"); // full string match
getByText("llo Worl", { exact: false }); // substring match
getByText("hello world", { exact: false }); // ignore case
// Matching a regex:
getByText(/World/); // substring match
getByText(/world/i); // substring match, ignore case
getByText(/^hello world$/i); // full string match, ignore case
getByText(/Hello W?oRlD/i); // advanced regex
// Matching with a custom function:
getByText((content, element) => content.startsWith("Hello"));import React, {
useState
} from 'react'
import {
render,
fireEvent
} from '@testing-library/react'
function CostInput() {
const [ value, setValue ] = useState( '' ) removeDollarSign = value => ( value[ 0 ] === '$' ? value.slice( 1 ) : value ) getReturnValue = value => ( value === '' ? '' : `$${value}` ) handleChange = ev => {
ev.preventDefault()
const inputtedValue = ev.currentTarget.value
const noDollarSign = removeDollarSign( inputtedValue ) if ( isNaN( noDollarSign ) ) return setValue( getReturnValue( noDollarSign ) )
}
return <input value = {
value
}
aria - label = "cost-input"
onChange = {
handleChange
}
/>}
const setup = () => {
const utils = render(<CostInput / > )
const input = utils.getByLabelText( 'cost-input' ) return {
input,
...utils,
}
}
test( 'It should keep a $ in front of the input', () => {
const {
input
} = setup() fireEvent.change( input, {
target: {
value: '23'
}
} ) expect( input.value ).toBe( '$23' )
} ) test( 'It should allow a $ to be in the input when the value is changed', () => {
const {
input
} = setup() fireEvent.change( input, {
target: {
value: '$23.0'
}
} ) expect( input.value ).toBe( '$23.0' )
} ) test( 'It should not allow letters to be inputted', () => {
const {
input
} = setup() expect( input.value ).toBe( '' )
// empty before fireEvent.change(input, {target: {value: 'Good Day'}}) expect(input.value).toBe('')
//empty after})test('It should allow the $ to be deleted', () => {
const {input} = setup() fireEvent.change(input, {target: {value: '23'}}) expect(input.value).toBe('$23')
// need to make a change so React registers "" as a change fireEvent.change(input, {target: {value: ''}}) expect(input.value).toBe('')})npm run storybookexport const NoIcons = Template.bind();
NoIcons.args = {
items: itemsOptions["3"],
};export const Icons = Template.bind({});
Icons.args = {
items: itemsOptions["5"],
};export default {
title: "Components/Accordion",
component: AccordionComponent,
argTypes: {
theme: {
name: "Theme",
},
closeOthers: {
name: "One Panel Open At A Time",
},
},
};
const Template = (args) => <AccordionComponent {...args} />;
export const Primary = Template.bind({});
Primary.args = {
...props,
};argTypes: {
backgroundColor: {
control: {
type: 'radio',
options: ['white', 'gray'],
},
defaultValue: 'white',
},
},argTypes: {
items: {
name: 'How many items?',
control: {
type: 'select',
options: {
...itemsPrimary,
},
},
},
},argTypes: {
title: {
control: {
type: 'text',
},
},
subtitle: {
control: {
type: 'text',
},
},
link: {
control: 'boolean',
},
ctaType: {
control: {
type: 'inline-radio',
options: ['ImageSlide', 'VideoSlide'],
},
},
},import React from "react";
import MyComponent from "./index";
import { Data } from "./data";
import { MyComponent as MyComponentComposition } from "../../lib/composition";
const props = MyComponentComposition({ fields: Data });
export default {
title: "Components/MyComponent",
component: MyComponent,
argTypes: {
backgroundColor: {
control: {
type: "radio",
options: ["white", "gray"],
},
defaultValue: "white",
},
},
};
const Template = (args) => <MyComponent {...args} />;
export const Primary = Template.bind({});
Primary.args = {
...props,
};import React from 'react';
import MyComponent from './index';
import { Data } from './data';
......
import { MyComponent as MyComponentComposition } from '../../lib/composition';
const props = MyComponentComposition({ fields: Data });
......
export default {
title: 'Components/MyComponent',
component: MyComponent,
argTypes: {
backgroundColor: {
control: {
type: 'radio',
options: ['white', 'gray'],
},
defaultValue: 'white',
},
},
};
......
const Template = args => <MyComponent {...args} />;
export const Primary = Template.bind({});
Primary.args = {
...props,
};...
import { storybookCustomArgs, createStoryOptions } from 'src/lib/helpers';
const props = BulletedOverviewComposition(Data);
const itemsToShow = createStoryOptions(props.items);
export default {
title: 'Components/Bulleted Overview',
component: BulletedOverview,
argTypes: {
...storybookCustomArgs(props, BulletedOverview, [], false),
items: {
name: 'How many items?',
control: {
type: 'select',
options: {
...itemsToShow,
},
},
},
},
};
...getByLabelText(
// If you're using `screen`, then skip the container argument:
container: HTMLElement,
text: TextMatch,
options?: {
selector?: string = '*',
exact?: boolean = true,
normalizer?: NormalizerFn,
}): HTMLElementgetByTitle(
// If you're using `screen`, then skip the container argument:
container: HTMLElement,
title: TextMatch,
options?: {
exact?: boolean = true,
normalizer?: NormalizerFn,
}): HTMLElement<span title="Delete" id="2"></span>
<svg>
<title>Close</title>
<g><path /></g>
</svg>// for/htmlFor relationship between label and form element id
<label for="username-input">Username</label>
<input id="username-input" />
// The aria-labelledby attribute with form elements
<label id="username-label">Username</label>
<input aria-labelledby="username-label" />
// Wrapper labels
<label>Username <input /></label>
// Wrapper labels where the label text is in another child element
<label>
<span>Username</span>
<input />
</label>
// aria-label attributes
// Take care because this is not a label that users can see on the page,
// so the purpose of your input must be obvious to visual users.
<input aria-label="Username" />import { screen } from "@testing-library/dom";
const inputNode = screen.getByLabelText("Username");import { render, screen } from "@testing-library/react";
render(<Login />);
const inputNode = screen.getByLabelText("Username");cy.findByLabelText("Username").should("exist");// Multiple elements labelled via aria-labelledby
<label id="username">Username</label>
<input aria-labelledby="username" />
<span aria-labelledby="username">Please enter your username</span>
// Multiple labels with the same text
<label>
Username
<input />
</label>
<label>
Username
<textarea></textarea>
</label>const inputNode = screen.getByLabelText("Username", { selector: "input" });// This case is not valid
// for/htmlFor between label and an element that is not a form element
<section id="photos-section">
<label for="photos-section">Photos</label>
</section>import { screen } from "@testing-library/dom";
const deleteElement = screen.getByTitle("Delete");
const closeElement = screen.getByTitle("Close");import { render, screen } from "@testing-library/react";
render(<MyComponent />);
const deleteElement = screen.getByTitle("Delete");
const closeElement = screen.getByTitle("Close");cy.findByTitle("Delete").should("exist");
cy.findByTitle("Close").should("exist");// ❌
const wrapper = render(<Example prop="1" />)
wrapper.rerender(<Example prop="2" />)
// ✅
const {rerender} = render(<Example prop="1" />)
rerender(<Example prop="2" />)// ❌
import {render, screen, cleanup} from '@testing-library/react'
afterEach(cleanup)
// ✅
import {render, screen} from '@testing-library/react'// ❌
const {getByRole} = render(<Example />)
const errorMessageNode = getByRole('alert')
// ✅
render(<Example />)
const errorMessageNode = screen.getByRole('alert')import {render, screen} from '@testing-library/react'const button = screen.getByRole('button', {name: /disabled button/i})
// ❌
expect(button.disabled).toBe(true)
// error message:
// expect(received).toBe(expected) // Object.is equality
//
// Expected: true
// Received: false
// ✅
expect(button).toBeDisabled()
// error message:
// Received element is not disabled:
// <button />// ❌
act(() => {
render(<Example />)
})
const input = screen.getByRole('textbox', {name: /choose a fruit/i})
act(() => {
fireEvent.keyDown(input, {key: 'ArrowDown'})
})
// ✅
render(<Example />)
const input = screen.getByRole('textbox', {name: /choose a fruit/i})
fireEvent.keyDown(input, {key: 'ArrowDown'})// ❌
// assuming you've got this DOM to work with:
// <label>Username</label><input data-testid="username" />
screen.getByTestId('username')
// ✅
// change the DOM to be accessible by associating the label and setting the type
// <label for="username">Username</label><input id="username" type="text" />
screen.getByRole('textbox', {name: /username/i})// ❌
const {container} = render(<Example />)
const button = container.querySelector('.btn-primary')
expect(button).toHaveTextContent(/click me/i)
// ✅
render(<Example />)
screen.getByRole('button', {name: /click me/i})// ❌
screen.getByTestId('submit-button')
// ✅
screen.getByRole('button', {name: /submit/i})// assuming we've got this DOM structure to work with
// <button><span>Hello</span> <span>World</span></button>
screen.getByText(/hello world/i)
// ❌ fails with the following error:
// Unable to find an element with the text: /hello world/i. This could be
// because the text is broken up by multiple elements. In this case, you can
// provide a function for your text matcher to make your matcher more flexible.
screen.getByRole('button', {name: /hello world/i})
// ✅ works!// assuming we've got this DOM structure to work with
// <button><span>Hello</span> <span>World</span></button>
screen.getByRole('blah')TestingLibraryElementError: Unable to find an accessible element with the role "blah"
Here are the accessible roles:
button:
Name "Hello World":
<button />
--------------------------------------------------
<body>
<div>
<button>
<span>
Hello
</span>
<span>
World
</span>
</button>
</div>
</body>// ❌
render(<button role="button">Click me</button>)
// ✅
render(<button>Click me</button>)// ❌
fireEvent.change(input, {target: {value: 'hello world'}})
// ✅
userEvent.type(input, 'hello world')// ❌
expect(screen.queryByRole('alert')).toBeInTheDocument()
// ✅
expect(screen.getByRole('alert')).toBeInTheDocument()
expect(screen.queryByRole('alert')).not.toBeInTheDocument()// ❌
const submitButton = await waitFor(() =>
screen.getByRole('button', {name: /submit/i}),
)
// ✅
const submitButton = await screen.findByRole('button', {name: /submit/i})// ❌
await waitFor(() => {})
expect(window.fetch).toHaveBeenCalledWith('foo')
expect(window.fetch).toHaveBeenCalledTimes(1)
// ✅
await waitFor(() => expect(window.fetch).toHaveBeenCalledWith('foo'))
expect(window.fetch).toHaveBeenCalledTimes(1)// ❌
await waitFor(() => {
expect(window.fetch).toHaveBeenCalledWith('foo')
expect(window.fetch).toHaveBeenCalledTimes(1)
})
// ✅
await waitFor(() => expect(window.fetch).toHaveBeenCalledWith('foo'))
expect(window.fetch).toHaveBeenCalledTimes(1)// ❌
await waitFor(() => {
fireEvent.keyDown(input, {key: 'ArrowDown'})
expect(screen.getAllByRole('listitem')).toHaveLength(3)
})
// ✅
fireEvent.keyDown(input, {key: 'ArrowDown'})
await waitFor(() => {
expect(screen.getAllByRole('listitem')).toHaveLength(3)
})// ❌
screen.getByRole('alert', {name: /error/i})
// ✅
expect(screen.getByRole('alert', {name: /error/i})).toBeInTheDocument()h1 value is correctwrapper.instance())shallow render a component?<input placeholder="Username" />import { screen } from "@testing-library/dom";
const inputNode = screen.getByPlaceholderText("Username");<button />buttonroleimport { render, screen } from "@testing-library/react";
render(<MyComponent />);
const inputNode = screen.getByPlaceholderText("Username");cy.findByPlaceholderText("Username").should("exist");npm install --save-dev @testing-library/react @testing-library/jest-dom// import React so you can use JSX (React.createElement) in your test
import React from "react";
/**
* render: lets us render the component as React would
* screen: a utility for finding elements the same way the user does
*/
import { render, screen } from "@testing-library/react";test("test title", () => {
// Your tests come here...
});const Welcome = (props) => {
const [values, setValues] = useState({
firstName: props.firstName,
lastName: props.lastName,
});
const handleChange = (event) => {
setValues({ ...values, [event.target.name]: event.target.value });
};
return (
<div>
<h1>
Welcome, {values.firstName} {values.lastName}
</h1>
<form name="userName">
<label>
First Name
<input
value={values.firstName}
name="firstName"
onChange={handleChange}
/>
</label>
<label>
Last Name
<input
value={values.lastName}
name="lastName"
onChange={handleChange}
/>
</label>
</form>
</div>
);
};
export default Welcome;test("has correct welcome text", () => {
const wrapper = shallow(<Welcome firstName="John" lastName="Doe" />);
expect(wrapper.find("h1").text()).toEqual("Welcome, John Doe");
});test("has correct welcome text", () => {
render(<Welcome firstName="John" lastName="Doe" />);
expect(screen.getByRole("heading")).toHaveTextContent("Welcome, John Doe");
});test("has correct input value", () => {
const wrapper = shallow(<Welcome firstName="John" lastName="Doe" />);
expect(wrapper.find('input[name="firstName"]').value).toEqual("John");
expect(wrapper.find('input[name="lastName"]').value).toEqual("Doe");
});test("has correct input value", () => {
render(<Welcome firstName="John" lastName="Doe" />);
expect(screen.getByRole("form")).toHaveFormValues({
firstName: "John",
lastName: "Doe",
});
});npm install --save-dev @testing-library/user-event @testing-library/domimport userEvent from "@testing-library/user-event";import React from "react";
const Checkbox = () => {
return (
<div>
<label htmlFor="checkbox">Check</label>
<input id="checkbox" type="checkbox" />
</div>
);
};
export default Checkbox;test("handles click correctly", async () => {
render(<Checkbox />);
const user = userEvent.setup();
// You can also call this method directly on userEvent,
// but using the methods from `.setup()` is recommended.
await user.click(screen.getByText("Check"));
expect(screen.getByLabelText("Check")).toBeChecked();
});getByRole(
// If you're using `screen`, then skip the container argument:
container: HTMLElement,
role: TextMatch,
options?: {
exact?: boolean = true,
hidden?: boolean = false,
name?: TextMatch,
description?: TextMatch,
normalizer?: NormalizerFn,
selected?: boolean,
checked?: boolean,
pressed?: boolean,
current?: boolean | string,
expanded?: boolean,
queryFallbacks?: boolean,
level?: number,
}): HTMLElement<body>
<main aria-hidden="true">
<button>Open dialog</button>
</main>
<div role="dialog">
<button>Close dialog</button>
</div>
</body><body>
<div role="tablist">
<button role="tab" aria-selected="true">Native</button>
<button role="tab" aria-selected="false">React</button>
<button role="tab" aria-selected="false">Cypress</button>
</div>
</body><body>
<section>
<button role="checkbox" aria-checked="true">Sugar</button>
<button role="checkbox" aria-checked="false">Gummy bears</button>
<button role="checkbox" aria-checked="false">Whipped cream</button>
</section>
</body><body>
<nav>
<a href="current/page" aria-current="true">👍</a>
<a href="another/page">👎</a>
</nav>
</body><body>
<section>
<button aria-pressed="true">👍</button>
<button aria-pressed="false">👎</button>
</section>
</body><body>
<nav>
<ul>
<li>
<a aria-expanded="false" aria-haspopup="true" href="..."
>Expandable Menu Item</a
>
<ul>
<li><a href="#">Submenu Item 1</a></li>
<li><a href="#">Submenu Item 1</a></li>
</ul>
</li>
<li><a href="#">Regular Menu Item</a></li>
</ul>
</nav>
</body><div role="dialog">...</div>import { screen } from "@testing-library/dom";
const dialogContainer = screen.getByRole("dialog");import { render, screen } from "@testing-library/react";
render(<MyComponent />);
const dialogContainer = screen.getByRole("dialog");cy.findByRole("dialog").should("exist");<body>
<section>
<h1>Heading Level One</h1>
<h2>First Heading Level Two</h2>
<h3>Heading Level Three</h3>
<div role="heading" aria-level="2">Second Heading Level Two</div>
</section>
</body>getByRole("heading", { level: 1 });
// <h1>Heading Level One</h1>
getAllByRole("heading", { level: 2 });
// [
// <h2>First Heading Level Two</h2>,
// <div role="heading" aria-level="2">Second Heading Level Two</div>
// ]<body>
<ul>
<li role="alertdialog" aria-describedby="notification-id-1">
<div><button>Close</button></div>
<div id="notification-id-1">You have unread emails</div>
</li>
<li role="alertdialog" aria-describedby="notification-id-2">
<div><button>Close</button></div>
<div id="notification-id-2">Your session is about to expire</div>
</li>
</ul>
</body>getByRole("alertdialog", { description: "Your session is about to expire" });const rtl = require("@testing-library/react");
const customRender = (ui, options) =>
rtl.render(ui, {
myDefaultOption: "something",
...options,
});
module.exports = {
...rtl,
render: customRender,
};- import { render, fireEvent } from '@testing-library/react';
+ import { render, fireEvent } from '../test-utils';import React from "react";
import { render } from "@testing-library/react";
import { ThemeProvider } from "my-ui-lib";
import { TranslationProvider } from "my-i18n-lib";
import defaultStrings from "i18n/en-x-default";
const AllTheProviders = ({ children }) => {
return (
<ThemeProvider theme="light">
<TranslationProvider messages={defaultStrings}>
{children}
</TranslationProvider>
</ThemeProvider>
);
};
const customRender = (ui, options) =>
render(ui, { wrapper: AllTheProviders, ...options });
// re-export everything
export * from "@testing-library/react";
// override render method
export { customRender as render };- import { render, fireEvent } from '@testing-library/react';
+ import { render, fireEvent } from '../test-utils';import React, { FC, ReactElement } from "react";
import { render, RenderOptions } from "@testing-library/react";
import { ThemeProvider } from "my-ui-lib";
import { TranslationProvider } from "my-i18n-lib";
import defaultStrings from "i18n/en-x-default";
const AllTheProviders: FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<ThemeProvider theme="light">
<TranslationProvider messages={defaultStrings}>
{children}
</TranslationProvider>
</ThemeProvider>
);
};
const customRender = (
ui: ReactElement,
options?: Omit<RenderOptions, "wrapper">
) => render(ui, { wrapper: AllTheProviders, ...options });
export * from "@testing-library/react";
export { customRender as render };import { queryHelpers, buildQueries } from "@testing-library/react";
// The queryAllByAttribute is a shortcut for attribute-based matchers
// You can also use document.querySelector or a combination of existing
// testing library utilities to find matching nodes for your query
const queryAllByDataCy = (...args) =>
queryHelpers.queryAllByAttribute("data-cy", ...args);
const getMultipleError = (c, dataCyValue) =>
`Found multiple elements with the data-cy attribute of: ${dataCyValue}`;
const getMissingError = (c, dataCyValue) =>
`Unable to find an element with the data-cy attribute of: ${dataCyValue}`;
const [
queryByDataCy,
getAllByDataCy,
getByDataCy,
findAllByDataCy,
findByDataCy,
] = buildQueries(queryAllByDataCy, getMultipleError, getMissingError);
export {
queryByDataCy,
queryAllByDataCy,
getByDataCy,
getAllByDataCy,
findAllByDataCy,
findByDataCy,
};import { render, queries } from "@testing-library/react";
import * as customQueries from "./custom-queries";
const customRender = (ui, options) =>
render(ui, { queries: { ...queries, ...customQueries }, ...options });
// re-export everything
export * from "@testing-library/react";
// override render method
export { customRender as render };import { render, queries, RenderOptions } from "@testing-library/react";
import * as customQueries from "./custom-queries";
import { ReactElement } from "react";
const customRender = (
ui: ReactElement,
options?: Omit<RenderOptions, "queries">
) => render(ui, { queries: { ...queries, ...customQueries }, ...options });
export * from "@testing-library/react";
export { customRender as render };const { getByDataCy } = render(<Component />);
expect(getByDataCy("my-component")).toHaveTextContent("Hello");- import { render, fireEvent } from '../test-utils';
+ import { render, fireEvent } from 'test-utils';module.exports = {
moduleDirectories: [
'node_modules',
+ // add the directory with the test-utils.js file, for example:
+ 'utils', // a utility folder
+ __dirname, // the root directory
],
// ... other options ...
}{
"compilerOptions": {
"baseUrl": "src",
"paths": {
"test-utils": ["./utils/test-utils"]
}
}
}npm install --save-dev jest-environment-jsdom module.exports = {
+ testEnvironment: 'jsdom',
// ... other options ...
}/**
* @jest-environment jsdom
*/ module.exports = {
+ testEnvironment: 'jest-environment-jsdom',
// ... other options ...
}/**
* @jest-environment jsdom
*/npm install --save-dev jest-environment-jsdom-fifteen module.exports = {
+ testEnvironment: 'jest-environment-jsdom-fifteen',
// ... other options ...
}npm install --save-dev jsdom global-jsdommocha --require global-jsdom/registercross-env RTL_SKIP_AUTO_CLEANUP=true jest{
// ... other jest config
setupFiles: ["@testing-library/react/dont-cleanup-after-each"];
}mocha -r @testing-library/react/dont-cleanup-after-eachconst { cleanup } = require("@testing-library/react");
exports.mochaHooks = {
afterEach() {
cleanup();
},
};mocha -r ./mocha-watch-cleanup-after-each.jstoBeDisabledtoBeEnabledtoBeEmptyDOMElementtoBeInTheDocumenttoBeInvalidtoBeRequiredtoBeValidtoBeVisibletoContainElementtoContainHTMLtoHaveAccessibleDescriptiontoHaveAccessibleNametoHaveAttributetoHaveClasstoHaveFocustoHaveFormValuestoHaveStyletoHaveTextContenttoHaveValuetoHaveDisplayValuetoBeCheckedtoBePartiallyCheckedtoHaveErrorMessagetoBeEmptytoBeInTheDOMtoHaveDescriptionhiddencollapsename attribute, it is treated as a boolean, returning true if the checkbox is checked, false if unchecked.npm install --save-dev @testing-library/jest-domyarn add --dev @testing-library/jest-dom// In your own jest-setup.js (or any other name)
import "@testing-library/jest-dom";
// In jest.config.js add (if you haven't already)
setupFilesAfterEnv: ["<rootDir>/jest-setup.js"]; // In tsconfig.json
"include": [
...
"./jest-setup.ts"
],toBeDisabled();<button data-testid="button" type="submit" disabled>submit</button>
<fieldset disabled><input type="text" data-testid="input" /></fieldset>
<a href="..." disabled>link</a>expect(getByTestId("button")).toBeDisabled();
expect(getByTestId("input")).toBeDisabled();
expect(getByText("link")).not.toBeDisabled();toBeEnabled();toBeEmptyDOMElement();<span data-testid="not-empty"><span data-testid="empty"></span></span>
<span data-testid="with-whitespace"> </span>
<span data-testid="with-comment"><!-- comment --></span>expect(getByTestId("empty")).toBeEmptyDOMElement();
expect(getByTestId("not-empty")).not.toBeEmptyDOMElement();
expect(getByTestId("with-whitespace")).not.toBeEmptyDOMElement();toBeInTheDocument();<span data-testid="html-element"><span>Html Element</span></span>
<svg data-testid="svg-element"></svg>expect(
getByTestId(document.documentElement, "html-element")
).toBeInTheDocument();
expect(
getByTestId(document.documentElement, "svg-element")
).toBeInTheDocument();
expect(
queryByTestId(document.documentElement, "does-not-exist")
).not.toBeInTheDocument();toBeInvalid();<input data-testid="no-aria-invalid" />
<input data-testid="aria-invalid" aria-invalid />
<input data-testid="aria-invalid-value" aria-invalid="true" />
<input data-testid="aria-invalid-false" aria-invalid="false" />
<form data-testid="valid-form">
<input />
</form>
<form data-testid="invalid-form">
<input required />
</form>expect(getByTestId("no-aria-invalid")).not.toBeInvalid();
expect(getByTestId("aria-invalid")).toBeInvalid();
expect(getByTestId("aria-invalid-value")).toBeInvalid();
expect(getByTestId("aria-invalid-false")).not.toBeInvalid();
expect(getByTestId("valid-form")).not.toBeInvalid();
expect(getByTestId("invalid-form")).toBeInvalid();toBeRequired();<input data-testid="required-input" required />
<input data-testid="aria-required-input" aria-required="true" />
<input data-testid="conflicted-input" required aria-required="false" />
<input data-testid="aria-not-required-input" aria-required="false" />
<input data-testid="optional-input" />
<input data-testid="unsupported-type" type="image" required />
<select data-testid="select" required></select>
<textarea data-testid="textarea" required></textarea>
<div data-testid="supported-role" role="tree" required></div>
<div data-testid="supported-role-aria" role="tree" aria-required="true"></div>expect(getByTestId("required-input")).toBeRequired();
expect(getByTestId("aria-required-input")).toBeRequired();
expect(getByTestId("conflicted-input")).toBeRequired();
expect(getByTestId("aria-not-required-input")).not.toBeRequired();
expect(getByTestId("optional-input")).not.toBeRequired();
expect(getByTestId("unsupported-type")).not.toBeRequired();
expect(getByTestId("select")).toBeRequired();
expect(getByTestId("textarea")).toBeRequired();
expect(getByTestId("supported-role")).not.toBeRequired();
expect(getByTestId("supported-role-aria")).toBeRequired();toBeValid();<input data-testid="no-aria-invalid" />
<input data-testid="aria-invalid" aria-invalid />
<input data-testid="aria-invalid-value" aria-invalid="true" />
<input data-testid="aria-invalid-false" aria-invalid="false" />
<form data-testid="valid-form">
<input />
</form>
<form data-testid="invalid-form">
<input required />
</form>expect(getByTestId("no-aria-invalid")).toBeValid();
expect(getByTestId("aria-invalid")).not.toBeValid();
expect(getByTestId("aria-invalid-value")).not.toBeValid();
expect(getByTestId("aria-invalid-false")).toBeValid();
expect(getByTestId("valid-form")).toBeValid();
expect(getByTestId("invalid-form")).not.toBeValid();toBeVisible();<div data-testid="zero-opacity" style="opacity: 0">Zero Opacity Example</div>
<div data-testid="visibility-hidden" style="visibility: hidden">
Visibility Hidden Example
</div>
<div data-testid="display-none" style="display: none">Display None Example</div>
<div style="opacity: 0">
<span data-testid="hidden-parent">Hidden Parent Example</span>
</div>
<div data-testid="visible">Visible Example</div>
<div data-testid="hidden-attribute" hidden>Hidden Attribute Example</div>expect(getByText("Zero Opacity Example")).not.toBeVisible();
expect(getByText("Visibility Hidden Example")).not.toBeVisible();
expect(getByText("Display None Example")).not.toBeVisible();
expect(getByText("Hidden Parent Example")).not.toBeVisible();
expect(getByText("Visible Example")).toBeVisible();
expect(getByText("Hidden Attribute Example")).not.toBeVisible();toContainElement(element: HTMLElement | SVGElement | null)<span data-testid="ancestor"><span data-testid="descendant"></span></span>const ancestor = getByTestId("ancestor");
const descendant = getByTestId("descendant");
const nonExistantElement = getByTestId("does-not-exist");
expect(ancestor).toContainElement(descendant);
expect(descendant).not.toContainElement(ancestor);
expect(ancestor).not.toContainElement(nonExistantElement);toContainHTML(htmlText: string)<span data-testid="parent"><span data-testid="child"></span></span>// These are valid uses
expect(getByTestId("parent")).toContainHTML(
'<span data-testid="child"></span>'
);
expect(getByTestId("parent")).toContainHTML('<span data-testid="child" />');
expect(getByTestId("parent")).not.toContainHTML("<br />");
// These won't work
expect(getByTestId("parent")).toContainHTML('data-testid="child"');
expect(getByTestId("parent")).toContainHTML("data-testid");
expect(getByTestId("parent")).toContainHTML("</span>");toHaveAccessibleDescription(expectedAccessibleDescription?: string | RegExp)<a
data-testid="link"
href="/"
aria-label="Home page"
title="A link to start over"
>Start</a
>
<a data-testid="extra-link" href="/about" aria-label="About page">About</a>
<img src="avatar.jpg" data-testid="avatar" alt="User profile pic" />
<img
src="logo.jpg"
data-testid="logo"
alt="Company logo"
aria-describedby="t1"
/>
<span id="t1" role="presentation">The logo of Our Company</span>expect(getByTestId("link")).toHaveAccessibleDescription();
expect(getByTestId("link")).toHaveAccessibleDescription("A link to start over");
expect(getByTestId("link")).not.toHaveAccessibleDescription("Home page");
expect(getByTestId("extra-link")).not.toHaveAccessibleDescription();
expect(getByTestId("avatar")).not.toHaveAccessibleDescription();
expect(getByTestId("logo")).not.toHaveAccessibleDescription("Company logo");
expect(getByTestId("logo")).toHaveAccessibleDescription(
"The logo of Our Company"
);toHaveAccessibleName(expectedAccessibleName?: string | RegExp)<img data-testid="img-alt" src="" alt="Test alt" />
<img data-testid="img-empty-alt" src="" alt="" />
<svg data-testid="svg-title"><title>Test title</title></svg>
<button data-testid="button-img-alt"><img src="" alt="Test" /></button>
<p><img data-testid="img-paragraph" src="" alt="" /> Test content</p>
<button data-testid="svg-button"><svg><title>Test</title></svg></p>
<div><svg data-testid="svg-without-title"></svg></div>
<input data-testid="input-title" title="test" />expect(getByTestId("img-alt")).toHaveAccessibleName("Test alt");
expect(getByTestId("img-empty-alt")).not.toHaveAccessibleName();
expect(getByTestId("svg-title")).toHaveAccessibleName("Test title");
expect(getByTestId("button-img-alt")).toHaveAccessibleName();
expect(getByTestId("img-paragraph")).not.toHaveAccessibleName();
expect(getByTestId("svg-button")).toHaveAccessibleName();
expect(getByTestId("svg-without-title")).not.toHaveAccessibleName();
expect(getByTestId("input-title")).toHaveAccessibleName();toHaveAttribute(attr: string, value?: any)<button data-testid="ok-button" type="submit" disabled>ok</button>const button = getByTestId("ok-button");
expect(button).toHaveAttribute("disabled");
expect(button).toHaveAttribute("type", "submit");
expect(button).not.toHaveAttribute("type", "button");
expect(button).toHaveAttribute("type", expect.stringContaining("sub"));
expect(button).toHaveAttribute("type", expect.not.stringContaining("but"));toHaveClass(...classNames: string[], options?: {exact: boolean})<button data-testid="delete-button" class="btn extra btn-danger">
Delete item
</button>
<button data-testid="no-classes">No Classes</button>const deleteButton = getByTestId("delete-button");
const noClasses = getByTestId("no-classes");
expect(deleteButton).toHaveClass("extra");
expect(deleteButton).toHaveClass("btn-danger btn");
expect(deleteButton).toHaveClass("btn-danger", "btn");
expect(deleteButton).not.toHaveClass("btn-link");
expect(deleteButton).toHaveClass("btn-danger extra btn", { exact: true }); // to check if the element has EXACTLY a set of classes
expect(deleteButton).not.toHaveClass("btn-danger extra", { exact: true }); // if it has more than expected it is going to fail
expect(noClasses).not.toHaveClass();toHaveFocus();<div><input type="text" data-testid="element-to-focus" /></div>const input = getByTestId("element-to-focus");
input.focus();
expect(input).toHaveFocus();
input.blur();
expect(input).not.toHaveFocus();toHaveFormValues(expectedValues: {
[name: string]: any
})<form data-testid="login-form">
<input type="text" name="username" value="jane.doe" />
<input type="password" name="password" value="12345678" />
<input type="checkbox" name="rememberMe" checked />
<button type="submit">Sign in</button>
</form>expect(getByTestId("login-form")).toHaveFormValues({
username: "jane.doe",
rememberMe: true,
});toHaveStyle(css: string | object)<button
data-testid="delete-button"
style="display: none; background-color: red"
>
Delete item
</button>const button = getByTestId("delete-button");
expect(button).toHaveStyle("display: none");
expect(button).toHaveStyle({ display: "none" });
expect(button).toHaveStyle(`
background-color: red;
display: none;
`);
expect(button).toHaveStyle({
backgroundColor: "red",
display: "none",
});
expect(button).not.toHaveStyle(`
background-color: blue;
display: none;
`);
expect(button).not.toHaveStyle({
backgroundColor: "blue",
display: "none",
});toHaveTextContent(text: string | RegExp, options?: {normalizeWhitespace: boolean})<span data-testid="text-content">Text Content</span>const element = getByTestId("text-content");
expect(element).toHaveTextContent("Content");
expect(element).toHaveTextContent(/^Text Content$/); // to match the whole content
expect(element).toHaveTextContent(/content$/i); // to use case-insensitive match
expect(element).not.toHaveTextContent("content");toHaveValue(value: string | string[] | number)<input type="text" value="text" data-testid="input-text" />
<input type="number" value="5" data-testid="input-number" />
<input type="text" data-testid="input-empty" />
<select multiple data-testid="select-number">
<option value="first">First Value</option>
<option value="second" selected>Second Value</option>
<option value="third" selected>Third Value</option>
</select>const textInput = getByTestId("input-text");
const numberInput = getByTestId("input-number");
const emptyInput = getByTestId("input-empty");
const selectInput = getByTestId("select-number");
expect(textInput).toHaveValue("text");
expect(numberInput).toHaveValue(5);
expect(emptyInput).not.toHaveValue();
expect(selectInput).toHaveValue(["second", "third"]);toHaveDisplayValue(value: string | RegExp | (string|RegExp)[])<label for="input-example">First name</label>
<input type="text" id="input-example" value="Luca" />
<label for="textarea-example">Description</label>
<textarea id="textarea-example">An example description here.</textarea>
<label for="single-select-example">Fruit</label>
<select id="single-select-example">
<option value="">Select a fruit...</option>
<option value="banana">Banana</option>
<option value="ananas">Ananas</option>
<option value="avocado">Avocado</option>
</select>
<label for="multiple-select-example">Fruits</label>
<select id="multiple-select-example" multiple>
<option value="">Select a fruit...</option>
<option value="banana" selected>Banana</option>
<option value="ananas">Ananas</option>
<option value="avocado" selected>Avocado</option>
</select>const input = screen.getByLabelText("First name");
const textarea = screen.getByLabelText("Description");
const selectSingle = screen.getByLabelText("Fruit");
const selectMultiple = screen.getByLabelText("Fruits");
expect(input).toHaveDisplayValue("Luca");
expect(input).toHaveDisplayValue(/Luc/);
expect(textarea).toHaveDisplayValue("An example description here.");
expect(textarea).toHaveDisplayValue(/example/);
expect(selectSingle).toHaveDisplayValue("Select a fruit...");
expect(selectSingle).toHaveDisplayValue(/Select/);
expect(selectMultiple).toHaveDisplayValue([/Avocado/, "Banana"]);toBeChecked();<input type="checkbox" checked data-testid="input-checkbox-checked" />
<input type="checkbox" data-testid="input-checkbox-unchecked" />
<div role="checkbox" aria-checked="true" data-testid="aria-checkbox-checked" />
<div
role="checkbox"
aria-checked="false"
data-testid="aria-checkbox-unchecked"
/>
<input type="radio" checked value="foo" data-testid="input-radio-checked" />
<input type="radio" value="foo" data-testid="input-radio-unchecked" />
<div role="radio" aria-checked="true" data-testid="aria-radio-checked" />
<div role="radio" aria-checked="false" data-testid="aria-radio-unchecked" />
<div role="switch" aria-checked="true" data-testid="aria-switch-checked" />
<div role="switch" aria-checked="false" data-testid="aria-switch-unchecked" />const inputCheckboxChecked = getByTestId("input-checkbox-checked");
const inputCheckboxUnchecked = getByTestId("input-checkbox-unchecked");
const ariaCheckboxChecked = getByTestId("aria-checkbox-checked");
const ariaCheckboxUnchecked = getByTestId("aria-checkbox-unchecked");
expect(inputCheckboxChecked).toBeChecked();
expect(inputCheckboxUnchecked).not.toBeChecked();
expect(ariaCheckboxChecked).toBeChecked();
expect(ariaCheckboxUnchecked).not.toBeChecked();
const inputRadioChecked = getByTestId("input-radio-checked");
const inputRadioUnchecked = getByTestId("input-radio-unchecked");
const ariaRadioChecked = getByTestId("aria-radio-checked");
const ariaRadioUnchecked = getByTestId("aria-radio-unchecked");
expect(inputRadioChecked).toBeChecked();
expect(inputRadioUnchecked).not.toBeChecked();
expect(ariaRadioChecked).toBeChecked();
expect(ariaRadioUnchecked).not.toBeChecked();
const ariaSwitchChecked = getByTestId("aria-switch-checked");
const ariaSwitchUnchecked = getByTestId("aria-switch-unchecked");
expect(ariaSwitchChecked).toBeChecked();
expect(ariaSwitchUnchecked).not.toBeChecked();toBePartiallyChecked();<input type="checkbox" aria-checked="mixed" data-testid="aria-checkbox-mixed" />
<input type="checkbox" checked data-testid="input-checkbox-checked" />
<input type="checkbox" data-testid="input-checkbox-unchecked" />
<div role="checkbox" aria-checked="true" data-testid="aria-checkbox-checked" />
<div
role="checkbox"
aria-checked="false"
data-testid="aria-checkbox-unchecked"
/>
<input type="checkbox" data-testid="input-checkbox-indeterminate" />const ariaCheckboxMixed = getByTestId("aria-checkbox-mixed");
const inputCheckboxChecked = getByTestId("input-checkbox-checked");
const inputCheckboxUnchecked = getByTestId("input-checkbox-unchecked");
const ariaCheckboxChecked = getByTestId("aria-checkbox-checked");
const ariaCheckboxUnchecked = getByTestId("aria-checkbox-unchecked");
const inputCheckboxIndeterminate = getByTestId("input-checkbox-indeterminate");
expect(ariaCheckboxMixed).toBePartiallyChecked();
expect(inputCheckboxChecked).not.toBePartiallyChecked();
expect(inputCheckboxUnchecked).not.toBePartiallyChecked();
expect(ariaCheckboxChecked).not.toBePartiallyChecked();
expect(ariaCheckboxUnchecked).not.toBePartiallyChecked();
inputCheckboxIndeterminate.indeterminate = true;
expect(inputCheckboxIndeterminate).toBePartiallyChecked();toHaveErrorMessage(text: string | RegExp)<label for="startTime"> Please enter a start time for the meeting: </label>
<input
id="startTime"
type="text"
aria-errormessage="msgID"
aria-invalid="true"
value="11:30 PM"
/>
<span id="msgID" aria-live="assertive" style="visibility:visible">
Invalid time: the time must be between 9:00 AM and 5:00 PM
</span>const timeInput = getByLabel("startTime");
expect(timeInput).toHaveErrorMessage(
"Invalid time: the time must be between 9:00 AM and 5:00 PM"
);
expect(timeInput).toHaveErrorMessage(/invalid time/i); // to partially match
expect(timeInput).toHaveErrorMessage(expect.stringContaining("Invalid time")); // to partially match
expect(timeInput).not.toHaveErrorMessage("Pikachu!");toBeEmpty();<span data-testid="not-empty"><span data-testid="empty"></span></span>expect(getByTestId("empty")).toBeEmpty();
expect(getByTestId("not-empty")).not.toBeEmpty();toBeInTheDOM();expect(document.querySelector(".ok-button")).toBeInstanceOf(HTMLElement);
expect(document.querySelector(".cancel-button")).toBeTruthy();toHaveDescription(text: string | RegExp)<button aria-label="Close" aria-describedby="description-close">X</button>
<div id="description-close">Closing will discard any changes</div>
<button>Delete</button>const closeButton = getByRole("button", { name: "Close" });
expect(closeButton).toHaveDescription("Closing will discard any changes");
expect(closeButton).toHaveDescription(/will discard/); // to partially match
expect(closeButton).toHaveDescription(expect.stringContaining("will discard")); // to partially match
expect(closeButton).toHaveDescription(/^closing/i); // to use case-insensitive match
expect(closeButton).not.toHaveDescription("Other description");
const deleteButton = getByRole("button", { name: "Delete" });
expect(deleteButton).not.toHaveDescription();
expect(deleteButton).toHaveDescription(""); // Missing or empty description always becomes a blank string