Detailed testing reference. Top-level rule of thumb lives in AGENTS.md.
pnpm testRun unit tests across all packagespnpm --filter code testRun code unit tests onlypnpm test:e2eRun Playwright E2E tests
Unit tests (Vitest) Fast, isolated, run frequently:
- Zustand store logic and state transitions
- Pure utility functions and helpers
- Service methods with mocked dependencies
- Complex business logic in isolation
- Data transformations and validators
E2E tests (Playwright) Slower, test real user flows:
- Critical user journeys (auth, task creation, workspace setup)
- IPC communication between main and renderer
- Features requiring real Electron APIs (file system, shell)
- Multi-step workflows spanning multiple components
- Regression tests for reported bugs
Rule of thumb: If it can be tested without Electron running, use a unit test. If it requires the full app context or tests user-facing behavior, use E2E.
Tests are colocated with source code using .test.ts or .test.tsx extension. E2E tests live in tests/e2e/.
describe("store", () => {
beforeEach(() => {
localStorage.clear();
useStore.setState({ /* reset state */ });
});
it("action changes state", () => {
useStore.getState().action();
expect(useStore.getState().property).toBe(expectedValue);
});
it("persists to localStorage", () => {
useStore.getState().action();
const persisted = localStorage.getItem("store-key");
expect(JSON.parse(persisted).state).toEqual(expectedState);
});
});Hoisted mocks for complex modules:
const mockPty = vi.hoisted(() => ({ spawn: vi.fn() }));
vi.mock("node-pty", () => mockPty);Simple module mocks:
vi.mock("@utils/analytics", () => ({ track: vi.fn() }));Global fetch stubbing:
const mockFetch = vi.fn();
vi.stubGlobal("fetch", mockFetch);
mockFetch.mockResolvedValueOnce(ok());Test utilities are in src/test/:
setup.tsGlobal test setup with localStorage mockutils.tsxrenderWithProviders()for component testsfixtures.tsMock data factoriespanelTestHelpers.tsDomain-specific assertions