Test Writer
Write and maintain tests for the Raamattu Nyt monorepo.
Monorepo Test Architecture
Determine workspace first. Before writing or running tests, identify which workspace the code belongs to:
| Workspace | Vitest Config | Test Command | Path Aliases |
|-----------|--------------|--------------|--------------|
| apps/raamattu-nyt | vite.config.ts (test section) | cd apps/raamattu-nyt && npx vitest run | @/ → ./src, @shared-auth, @ui, etc. |
| packages/shared-i18n | vitest.config.ts | cd packages/shared-i18n && npx vitest run | @/integrations/supabase/client → raamattu-nyt client |
| packages/* (others) | None yet | Must create vitest.config.ts first | Varies per package |
Root npm test only runs apps/raamattu-nyt tests.
Running Tests
# ALWAYS cd into the workspace directory first
cd apps/raamattu-nyt && npx vitest run src/hooks/useMyHook.test.ts
# For packages with vitest config
cd packages/shared-i18n && npx vitest run src/myUtil.test.ts
# Run all raamattu-nyt tests with coverage
cd apps/raamattu-nyt && npx vitest run --coverage
NEVER run from monorepo root with a path — include patterns are relative to config location. See references/learnings.md.
Test File Conventions
- Place tests adjacent to source:
useHook.ts→useHook.test.ts - Use
.test.tsfor pure logic,.test.tsxfor React components/hooks - Name:
describe("ComponentName")ordescribe("hookName")
Standard Test Structure
import { describe, expect, it, vi, beforeEach } from "vitest";
// Mocks BEFORE imports (hoisted)
vi.mock("@/integrations/supabase/client", () => ({
supabase: { rpc: vi.fn() }
}));
// Import module under test AFTER mocks
import { myFunction } from "./myModule";
describe("myFunction", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("does expected behavior", () => {
expect(myFunction()).toBe(expected);
});
});
Hook Testing Pattern
import { renderHook, waitFor } from "@testing-library/react";
const wrapper = ({ children }) => (
<AuthProvider>{children}</AuthProvider>
);
it("returns initial state", () => {
const { result } = renderHook(() => useMyHook(), { wrapper });
expect(result.current.loading).toBe(true);
});
it("updates on async action", async () => {
const { result } = renderHook(() => useMyHook(), { wrapper });
await waitFor(() => {
expect(result.current.data).toBeDefined();
});
});
Common Mocks
See references/mocks.md for reusable mock patterns:
- Supabase client (RPC, auth, schema queries)
- useAuth hook
- React Query
- localStorage
Adding Tests to a New Package
When a package (e.g. packages/shared-recording) needs tests for the first time:
- Create
vitest.config.tsin the package root:
/// <reference types="vitest" />
import path from "node:path";
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
environment: "jsdom",
include: ["src/**/*.{test,spec}.{ts,tsx}"],
},
resolve: {
alias: {
// Add aliases matching the package's imports
"@/integrations/supabase/client": path.resolve(
__dirname, "../../apps/raamattu-nyt/src/integrations/supabase/client.ts"
),
},
},
});
- Add test script to package's
package.json:
{ "scripts": { "test": "vitest run" } }
- Add test setup if using React Testing Library — create
src/test/setup.ts:
import "@testing-library/jest-dom/vitest";
And add setupFiles: ["./src/test/setup.ts"] to the vitest config.
Workflow
- Identify workspace — determine which app/package the code belongs to
- Check existing tests —
Glob pattern: <workspace>/**/*.test.{ts,tsx} - Check vitest config — ensure the workspace has one; create if not
- Write tests — happy path, errors, edge cases, loading states
- Run from workspace dir —
cd <workspace> && npx vitest run src/path/to/file.test.ts
Test Quality Checklist
- [ ] Tests are independent (no shared state between tests)
- [ ] Mocks are cleared in
beforeEach - [ ] Async operations use
waitFornot arbitrary delays - [ ] Error scenarios are tested
- [ ] Edge cases covered (null, empty, boundary values)
- [ ] Tests run from correct workspace directory
IdeaMachina Evolution Tests
For IdeaMachina evolution features (sparks, cores, direction, force), write migration-safe tests that survive the Zustand→Supabase migration.
See references/idea-machina-migration-safe.md for:
- Data Port Pattern — abstract the store behind an interface so tests swap Zustand↔Supabase with one import change
- Test fixtures —
makeSpark(),makeCore(),makeDirection()factories - Component tests — test rendered UI, not store internals
- Pure logic tests — stage computation, core health, spark filtering (always migration-proof)
- What NOT to test — avoid persist config, localStorage keys, store version migrations
Quick rule: If a test imports useProjectEvolutionStore directly, consider whether it can test through rendered UI or the data port instead.
Related Skills
| Situation | Delegate To |
|-----------|-------------|
| Find code to test | code-wizard |
| Debug test failures | systematic-debugging |
| CI test failures | ci-doctor |
| Lint errors in tests | lint-fixer |