You have a fully-featured TaskFlow application with Redux Toolkit, TanStack Query, MSW, routing, performance optimisations, and error boundaries. Now it is time to write unit tests that verify the application actually works.
This challenge introduces Vitest and React Testing Library — the standard testing stack for modern React applications. You will write tests that exercise real user behaviour: rendering lists, clicking filter buttons, submitting forms, and changing task statuses.
- Configure Vitest with jsdom for component testing
- Use
renderWithProvidersto mount components with all required context - Query the DOM with
screen.getByRole,getByText,getByLabelText - Simulate user interactions with
@testing-library/user-event - Understand
getByvsqueryByvsfindBy - Write tests that describe behaviour, not implementation details
React Testing Library operates on a single principle: test the way your users use the application. That means:
- Query by visible text, role, or label — not by CSS class or data-testid
- Simulate real pointer / keyboard events — not synthetic DOM events
- Assert on what appears in the DOM — not on internal component state
Open src/test/setup.ts. It imports @testing-library/jest-dom/vitest, which
adds matchers like toBeInTheDocument(), toBeDisabled(), and toHaveValue()
to Vitest's expect.
Open src/test/utils.tsx. This file exports renderWithProviders, a helper
that wraps any component in the full provider tree:
QueryClientProvider > Provider (Redux) > ThemeProvider > AuthProvider > MemoryRouter.
This is necessary because most components in TaskFlow consume at least one of these contexts. Without the wrapper they would throw on first render.
Create src/components/__tests__/ProjectListItem.test.tsx.
A ProjectListItem displays a project name, status badge, and task count.
Write tests for:
- The project name is rendered as a link
- The correct status badge text appears
- The task count is shown
Hint: screen.getByRole('link', { name: /website redesign/i }) matches the
anchor element whose accessible name contains that text.
Create src/components/__tests__/TaskStatusButton.test.tsx.
A TaskStatusButton renders the valid next-status transitions for a task.
A Todo task shows an "In Progress" button; a Done task shows nothing.
Write tests for:
- A Todo task renders a button to move it to In Progress
- A Done task renders no status buttons
- Clicking the button calls
onStatusChangewith the correct arguments
Create src/components/__tests__/AddTaskForm.test.tsx.
AddTaskForm renders a toggle button when closed and a form when open.
Write tests for:
- Initially only the "+ Add task" toggle button is visible
- Clicking the toggle opens the form (title input appears)
- Submitting with a title shorter than 2 characters shows a validation error
- Submitting a valid title calls the mutation (check that the input clears or success state is reached)
Note: AddTaskForm calls useCreateTask which makes network requests. In
tests these will be intercepted by MSW if you start the MSW server in setup.
For simplicity in this challenge, you can mock the module or observe that the
optimistic UI updates immediately.
Create src/components/__tests__/TaskItem.test.tsx.
A TaskItem displays the task title, status badge, optional assignee name,
delete button, and status transition buttons. Write tests for:
- The task title is displayed
- The status badge shows the correct status text
- Clicking the delete button calls
onDeletewith the task id - A Todo task shows the "In Progress" status button
| Query | Returns | Throws if not found | Use when |
|---|---|---|---|
getBy… |
element | yes | element must be in DOM right now |
queryBy… |
element or null | no | asserting element is NOT present |
findBy… |
Promise<element> | yes (after timeout) | element appears asynchronously |
getAllBy… |
element[] | yes if empty | multiple matching elements |
Prefer userEvent from @testing-library/user-event:
const user = userEvent.setup();
await user.click(button);
await user.type(input, 'hello');
await user.keyboard('{Enter}');userEvent fires the full browser event sequence (pointerdown, mousedown,
focus, pointerup, mouseup, click) rather than a single synthetic event. This
means your tests catch bugs that only appear with real user input.
Use fireEvent only when you need direct control over a single low-level event
or when userEvent is unavailable.
npm run test # watch mode
npm run test -- --run # run once and exit (useful in CI)