Testing Lab 4: Nested Components
Objectives
- Test Setup
- Testing that Projects Display
- Testing Form Display
- Testing Form Cancel
Steps
Test Setup
Upgrade the
@testing-library/user-event
library to version 14.npm install @testing-library/user-event@14
Create the file
src\projects\MockProjects.ts
.Add the mock data below to the file.
src\projects\MockProjects.ts
import { Project } from './Project';
export const MOCK_PROJECTS = [
new Project({
id: 1,
name: 'Johnson - Kutch',
description:
'Fully-configurable intermediate framework. Ullam occaecati libero laudantium nihil voluptas omnis.',
imageUrl: '/assets/placeimg_500_300_arch4.jpg',
contractTypeId: 3,
contractSignedOn: '2013-08-04T22:39:41.473Z',
budget: 54637,
isActive: false,
}),
new Project({
id: 2,
name: 'Wisozk Group',
description:
'Centralized interactive application. Exercitationem nulla ut ipsam vero quasi enim quos doloribus voluptatibus.',
imageUrl: '/assets/placeimg_500_300_arch1.jpg',
contractTypeId: 4,
contractSignedOn: '2012-08-06T21:21:31.419Z',
budget: 91638,
isActive: true,
}),
new Project({
id: 3,
name: 'Denesik LLC',
description:
'Re-contextualized dynamic moratorium. Aut nulla soluta numquam qui dolor architecto et facere dolores.',
imageUrl: '/assets/placeimg_500_300_arch12.jpg',
contractTypeId: 6,
contractSignedOn: '2016-06-26T18:24:01.706Z',
budget: 29729,
isActive: true,
}),
new Project({
id: 4,
name: 'Purdy, Keeling and Smitham',
description:
'Innovative 6th generation model. Perferendis libero qui iusto et ullam cum sint molestias vel.',
imageUrl: '/assets/placeimg_500_300_arch5.jpg',
contractTypeId: 4,
contractSignedOn: '2013-05-26T01:10:42.344Z',
budget: 45660,
isActive: true,
}),
new Project({
id: 5,
name: 'Kreiger - Waelchi',
description:
'Managed logistical migration. Qui quod praesentium accusamus eos hic non error modi et.',
imageUrl: '/assets/placeimg_500_300_arch12.jpg',
contractTypeId: 2,
contractSignedOn: '2009-12-18T21:46:47.944Z',
budget: 81188,
isActive: true,
}),
new Project({
id: 6,
name: 'Lesch - Waelchi',
description:
'Profound mobile project. Rem consequatur laborum explicabo sint odit et illo voluptas expedita.',
imageUrl: '/assets/placeimg_500_300_arch1.jpg',
contractTypeId: 3,
contractSignedOn: '2016-09-23T21:27:25.035Z',
budget: 53407,
isActive: false,
}),
];Create the file
src\projects\__tests__\ProjectList-test.tsx
.Add the setup code below to test the component.
src\projects\__tests__\ProjectList-test.tsx
import { render, screen } from '@testing-library/react';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import ProjectList from '../ProjectList';
import { MOCK_PROJECTS } from '../MockProjects';
import userEvent from '@testing-library/user-event';
import { Provider } from 'react-redux';
import { store } from '../../state';
describe('<ProjectList />', () => {
beforeEach(() => {
render(
<Provider store={store}>
<MemoryRouter>
<ProjectList projects={MOCK_PROJECTS} />
</MemoryRouter>
</Provider>
);
});
test('should render without crashing', () => {
expect(screen).toBeDefined();
});
});Verify the initial test passes.
PASS src/projects/__tests__/ProjectList-test.tsx
Testing that Projects Display
Test that the
projects
display correctly.src\projects\__tests__\ProjectList-test.tsx
...
describe('<ProjectList />', () => {
const setup = () =>
render(
<Provider store={store}>
<MemoryRouter>
<ProjectList projects={MOCK_PROJECTS} />
</MemoryRouter>
</Provider>
);
beforeEach(() => {});
test('should render without crashing', () => {
setup();
expect(screen).toBeDefined();
});
+ test('should display list', () => {
+ setup();
+ expect(screen.getAllByRole('heading')).toHaveLength(MOCK_PROJECTS.length);
+ expect(screen.getAllByRole('img')).toHaveLength(MOCK_PROJECTS.length);
+ expect(screen.getAllByRole('link')).toHaveLength(MOCK_PROJECTS.length);
+ expect(screen.getAllByRole('button')).toHaveLength(MOCK_PROJECTS.length);
+ });
});
1. Verify the test passes.
```shell
PASS src/projects/__tests__/ProjectList-test.tsx
Testing Form Display
Modify the card component to add an
aria-label
so we can access the button in the test.src\projects\ProjectCard.tsx
...
function ProjectCard(props: ProjectCardProps) {
...
return (
<div className="card">
<img src={project.imageUrl} alt={project.name} />
<section className="section dark">
<Link to={'/projects/' + project.id}>
<h5 className="strong">
<strong>{project.name}</strong>
</h5>
<p>{formatDescription(project.description)}</p>
<p>Budget : {project.budget.toLocaleString()}</p>
</Link>
<button
+ aria-label={`edit ${project.name}`}
className=" bordered"
onClick={() => {
handleEditClick(project);
}}
>
<span className="icon-edit "></span>
Edit
</button>
</section>
</div>
);
}
export default ProjectCard;Modify the form component to add an
aria-label
and aname
(which gives an implicit role of form) so we can access the form in the test.src\projects\ProjectForm.tsx
...
function ProjectForm({ project: initialProject, onCancel }: ProjectFormProps) {
...
return (
<form
+ aria-label="Edit a Project"
+ name="projectForm"
className="input-group vertical"
onSubmit={handleSubmit}
>
...
</form>
);
}
export default ProjectForm;Test that the form is displayed when edit is clicked.
src\projects\__tests__\ProjectList-test.tsx
...
describe('<ProjectList />', () => {
...
test('should display list', () => {
...
});
+ test('should display form when edit clicked', async () => {
+ setup();
+ // eslint-disable-next-line testing-library/render-result-naming-convention
+ const user = userEvent.setup();
+ await user.click(
+ screen.getByRole('button', { name: /edit Wisozk Group/i })
+ );
+ expect(
+ screen.getByRole('form', {
+ name: /edit a project/i,
+ })
+ ).toBeInTheDocument();
+ });
});Verify the test passes.
PASS src/projects/__tests__/ProjectList-test.tsx
Testing Form Cancel
- Test that the form is removed after clicking cancel.
src\projects\__tests__\ProjectList-test.tsx
...
describe('<ProjectList />', () => {
...
+ test('should display image and remove form when cancel clicked', async () => {
+ setup();
+ // eslint-disable-next-line testing-library/render-result-naming-convention
+ const user = userEvent.setup();
+ await user.click(
+ screen.getByRole('button', { name: /edit Wisozk Group/i })
+ );
+ await user.click(
+ screen.getByRole('button', {
+ name: /cancel/i,
+ })
+ );
+ expect(
+ screen.getByRole('img', {
+ name: /wisozk group/i,
+ })
+ ).toBeInTheDocument();
+ expect(
+ screen.queryByRole('form', {
+ name: /edit a project/i,
+ })
+ ).not.toBeInTheDocument();
+ });
});
Verify the test passes.
PASS src/projects/__tests__/ProjectList-test.tsx