This document explains the frontend testing setup for the @app/client package and details each test suite already in place.
- Vitest: same test runner as the server but configured for DOM.
- @vitest/coverage-istanbul: coverage reporting.
- React Testing Library (
@testing-library/react,@testing-library/jest-dom,@testing-library/user-event): component rendering and user interactions. - jsdom: provides a browser-like DOM for tests.
packages/client/vitest.config.ts specifies:
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react() as any], // cast avoids vitest/vite type mismatch
test: {
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{ts,tsx}'],
setupFiles: './src/setupTests.ts',
coverage: { provider: 'istanbul', reporter: ['text', 'html'] },
},
});Notice globals: true enables describe, it, expect, and vi without imports.
import '@testing-library/jest-dom';
vi.mock('./firebase', () => ({
auth: {},
googleProvider: {},
}));The Firebase module is stubbed globally to prevent real initialization.
- Objective: smoke‑test the login screen and ensure click handler works.
- Key techniques:
vi.spyOn(AuthContext, 'useAuth')to fake authentication state,MemoryRouterwith v7 future flags,userEventfor interaction. - Assertions:
- login prompt renders when
useris null - clicking the sign‑in button calls
signInWithGoogle
- login prompt renders when
- Objective: verify
ErrorBoundaryshows fallback UI and suppress console/jsdom noise. - Technique: create a throwing
Bombcomponent, mockconsole.error, and prevent default onwindow.errorevents to silence jsdom.
- Objective: exercise all three rendering branches of
ProtectedRoute. - Technique: stub
useAuth()return value with loading, unauthenticated, and authenticated states; wrap inMemoryRouter.
- Objective: validate interactive dashboard behaviour now that users can edit their name/picture and view a list of all users.
- Additions:
- form inputs pre‑populate from
useMe()and send updates viauseUpdateProfile(). - a secondary query hook
useUsers()fetches/api/users; tests mock it and assert that the list renders when data is returned.
- form inputs pre‑populate from
- Technique: in addition to existing mocks for auth and API hooks,
useUsersis also stubbed;userEventtypes into inputs and clicks the save button, and the mutation is inspected for correct arguments.
- Objective: unit‑test HTTP helper functions without network.
- Technique: mock the shared
axiosinstance and assert correct URL, headers, and payloads.
- Objective: test
useMehook's interaction with React Query and auth. - Technique:
renderHookwith aQueryClientProviderwrapper, mockgetIdToken, and driveuserService.getMeto success, loading, and error states.
- Objective: component behaviour including user info, button states, API call, and sign‑out.
- Technique: mock
useAuth,useMe, anduseMutationHook; useuserEventto trigger button clicks.
cd packages/client
npm install # after adding deps
npm run test # run once
npm run test:watch # watch mode
npm run test -- --coverage- Isolation: use
vi.mock()andvi.spyOn()so tests run deterministically without network or Firebase. - Typing mocks: add explicit generics to
vi.fn<() => Promise<void>>()for correctness. - Noise suppression: error boundary tests mock
console.errorand prevent jsdom from writing uncaught exceptions. - Router & context wrappers:
MemoryRouterwith future flags and custom hook spies keep tests lightweight. - Query client setup:
new QueryClient({ defaultOptions: { queries: { retry: false } } })prevents automatic retries during test failures.
Add new component or hook tests next to their implementation; use the patterns above (mocks, wrappers, interaction). For any new async service call, prefer mocking the underlying module instead of real HTTP requests.