A component test should confirm what a user can see after an action. It does not need to know every internal state. A repository test instead checks persistence, relationships and rollback. House Log uses real SQLite databases for those contracts. The small browser example checks normalised form submission from tutorial 11.
01Set up the test
Use the React project from tutorial 10 with App.tsx from tutorial 11. package.json contains Vitest, Testing Library and jsdom. Save the test as src/App.test.tsx. The environment comment enables a browser DOM for this test.
// @vitest-environment jsdom
import React from 'react';
import { afterEach, expect, test } from 'vitest';
import { cleanup, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { App } from './App';
afterEach(cleanup);
test('confirmed input is normalised and displayed', async () => {
render(<App />);
const user = userEvent.setup();
await user.type(screen.getByLabelText('Title'), ' Roof ');
await user.click(screen.getByRole('button', { name: 'Add' }));
expect(screen.getByRole('listitem').textContent).toBe('Roof');
});
test('blank input creates no record', async () => {
render(<App />);
await userEvent.click(screen.getByRole('button', { name: 'Add' }));
expect(screen.queryAllByRole('listitem')).toHaveLength(0);
expect(screen.getByRole('alert').textContent).toBe('Title required');
});
Run / check
npm test02Test through accessible roles
getByLabelText finds the field through its accessible label. getByRole finds the actual button. userEvent reproduces typing and clicking. We then check for a listitem containing normalised text.
03Check empty input
The second test clicks without input. No entry may be created. The error message must appear instead. cleanup separates runs so a previous screen cannot affect the next.
04Add the appropriate boundaries
For the real repository, test foreign keys, migrations, transaction rollback and reopening. Camera and scanner require native tests or a documented device workflow. A jsdom test proves neither SQLite persistence nor Android permissions.
Both tests pass. Removing trim or bypassing blank-input validation must fail at least one test.
Your exercise
Test that successful submission clears the input and a failure retains it.
Show the solution and explanation
Read the field as HTMLInputElement and check value. Type only spaces for the failure case. After failed submission they must remain in the field. This protects the draft against unintended loss.
Project context and sources
The example is simplified for learning. The linked source version shows its use in the project and the checks applied there.