Skip to content

Testing

FORGE Studio uses Vitest for unit and integration tests, and Playwright for end-to-end tests.


Test Layout

test/
├── unit/
│   ├── main/           # Main process unit tests
│   │   ├── jsonl-parser.test.ts
│   │   ├── workflow-loader.test.ts
│   │   ├── forge-cli.test.ts
│   │   ├── compute-backend.test.ts
│   │   ├── stage-cache.test.ts
│   │   ├── twix-reader.test.ts
│   │   ├── ismrmrd-reader.test.ts
│   │   ├── yarra-converter.test.ts
│   │   ├── file-watcher.test.ts
│   │   ├── interpreter-session.test.ts
│   │   └── backend-dispatcher.test.ts
│   └── renderer/
│       ├── slices/     # Redux slice tests
│       └── keyboard/
├── integration/
│   └── workflow/
│       └── workflow-roundtrip.test.ts
├── e2e/                # Playwright tests (requires built app)
├── fixtures/           # Sample data
│   ├── workflows/      # Workflow YAML fixtures
│   ├── jsonl/          # FORGE stderr JSONL samples
│   ├── yarra/          # Yarra .mode fixtures
│   └── ismrmrd/        # ISMRMRD fixture data
└── helpers/
    ├── factories.ts    # Test data factories
    ├── ipc-mock.ts     # window.forgeAPI mock
    └── redux-helpers.ts

Running Tests

bash
# All tests (unit + integration)
npm run test

# Unit tests only
npm run test:unit

# Single file
npx vitest run test/unit/main/jsonl-parser.test.ts

# Watch mode
npx vitest

# E2E tests (requires npm run build first)
npm run test:e2e

Vitest Configurations

There are two Vitest configs because the main and renderer processes have different environments:

ConfigEnvironmentCovers
vitest.config.tsNodeMain process: parsers, CLI, cache, dispatch
vitest.config.renderer.tsjsdomRenderer: Redux slices, component logic

Both are run by npm run test. To run only one:

bash
npx vitest run --config vitest.config.ts
npx vitest run --config vitest.config.renderer.ts

Test Priorities

The highest-value tests are correctness-critical paths where a wrong result is silent:

Test fileWhy critical
jsonl-parser.test.tsWrong event parsing produces silent bad metrics
forge-cli.test.tsWrong CLI flags produce silent wrong reconstructions
workflow-loader.test.tsInvalid configs crash pipelines
stage-cache.test.tsWrong hashing produces stale results
twix-reader.test.tsWrong header values mislead the user

Writing Main Process Tests

Main process tests use real filesystem operations with temp directories. Avoid mocking filesystem calls where possible — use tmp directories instead:

typescript
import { mkdtemp, rm } from 'fs/promises';
import { join } from 'path';
import { tmpdir } from 'os';

let tmpDir: string;
beforeEach(async () => {
  tmpDir = await mkdtemp(join(tmpdir(), 'forge-test-'));
});
afterEach(async () => {
  await rm(tmpDir, { recursive: true });
});

Mock spawn and fs only when testing the protocol layer (e.g. backend-dispatcher.test.ts).


Writing Renderer Tests

Redux slice tests use the real store setup:

typescript
import { configureStore } from '@reduxjs/toolkit';
import jobReducer from '../../../src/renderer/store/jobSlice';

const store = configureStore({ reducer: { job: jobReducer } });
store.dispatch(someAction(payload));
expect(store.getState().job.someField).toBe(expectedValue);

For tests that need the full store (with all slices), import from test/helpers/redux-helpers.ts.


Test Factories

test/helpers/factories.ts provides factory functions for common test objects:

typescript
import { makeJobSummary, makeWorkflowDef, makeTWIXHeader } from '../../helpers/factories';

const job = makeJobSummary({ status: 'failed' });
const wf  = makeWorkflowDef({ stages: [...] });

IPC Mock

Renderer tests that interact with window.forgeAPI use test/helpers/ipc-mock.ts:

typescript
import { setupIpcMock } from '../../helpers/ipc-mock';

beforeEach(() => setupIpcMock());

The mock provides vi.fn() stubs for all ForgeAPI methods and push event subscriptions.


Fixture Data

Real-world data fixtures live in test/fixtures/:

DirectoryContents
fixtures/jsonl/Captured FORGE stderr JSONL event streams
fixtures/workflows/Valid and invalid workflow YAML files
fixtures/yarra/Yarra .mode files

Use fixtures for snapshot tests and round-trip validation:

typescript
import { readFileSync } from 'fs';
import { join } from 'path';

const jsonl = readFileSync(join(__dirname, '../../fixtures/jsonl/cs_sense_run.jsonl'), 'utf8');

GPU Tests

Tests that require real GPU hardware must be tagged so CI can skip them:

typescript
it.skipIf(process.env.CI)('uses Metal backend on Apple Silicon', async () => {
  ...
});

Type Checking

bash
npm run typecheck   # tsc --noEmit

Always run typecheck before committing. All code must pass with zero errors — any types are prohibited.


Linting

bash
npm run lint   # ESLint

FORGE Studio