React testing strategy¶
Stack¶
- Runner — Jest 30 with
ts-jest(ESM preset). - DOM —
jsdomenabled per-file via the@jest-environment jsdomdocblock. The global default staysnodeso existing server-side tests keep their lightweight environment. - Library —
@testing-library/reactfor rendering and queries,@testing-library/user-eventfor interactions,@testing-library/jest-domfor extended matchers (registered viasetupFilesAfterEnv).
Conventions¶
- Colocation —
Foo.test.tsxlives next toFoo.tsx. Thetest/folder at the repo root is reserved for shared helpers. - Queries — prefer
getByRole→getByLabelText→getByText.getByTestIdis the last resort. - User interactions — always
userEvent, neverfireEvent(user-event simulates the full event sequence). - No snapshots — they drift silently. Assert the specific behaviour you care about.
- No type tests — TypeScript handles that.
- AAA — Arrange → Act → Assert. One behaviour per
it.
Providers¶
Components that rely on i18n or the MUI theme must be rendered through
renderWithProviders from @test/renderWithProviders. The test i18n instance
is a pass-through: t('foo.bar') === 'foo.bar', which makes assertions
deterministic without loading real translation files.
import { screen } from '@testing-library/react'
import { renderWithProviders } from '@test/renderWithProviders'
import { CoreButton } from './CoreButton'
renderWithProviders(<CoreButton text='save' />)
expect(screen.getByRole('button', { name: 'save' })).toBeInTheDocument()
Hooks¶
Use renderHook + act from @testing-library/react. If the hook depends on
a provider, pass a wrapper:
const { result } = renderHook(() => useLocalStorage('k', 0))
act(() => result.current[1](1))
expect(result.current[0]).toBe(1)
Coverage¶
Coverage is collected automatically by npm test (v8 provider, reports:
text, lcov, cobertura). Barrels (index.ts) and type-only files are
excluded.
Raise coverageThreshold in jest.config.ts as modules gain tests. Suggested
targets:
common/utilities: 80 % lines / 70 % branchesclient/hooks/: 80 % linesclient/components/: 60 % lines (pixel-level tests are not worth the churn)
Reference tests¶
See @platform/core for working examples:
modules/platform/core/src/client/hooks/useLocalStorage.test.tsmodules/platform/core/src/client/hooks/useT.test.tsxmodules/platform/core/src/client/components/CoreButton.test.tsxmodules/platform/core/src/client/components/Typo.test.tsx