Titandocs

Testing

A correctness-first priority order for a financial app, and what CI actually gates.

Stack: Vitest + @testing-library/react + jsdom. globals: true, so describe / it / expect are ambient. Setup in vitest.setup.ts loads @testing-library/jest-dom/vitest.

Priority order

This is a financial app, so the order is correctness-first, not coverage-first.

Pure functions — always

Formatters, trading math (math.ts, order-math.ts, orderbook.ts), slug helpers, mappers, address truncation. These are where a wrong answer becomes a wrong trade.

Stores

Zustand transitions and selectors. setState to seed, getState to assert, reset in beforeEach.

Critical hooks

The ones combining stores with side effects: use-resize, use-format, use-pip, use-trades-pagination, use-stream-multi. Test the public output, not the internals.

Components — selectively

Only where there is non-trivial logic: form validation, multi-branch rendering. Do not test "renders without crashing".

E2E — not yet

Playwright golden paths (connect → place → see in positions) land once the write flow is stable.

Round-trip over point assertions

For anything invertible, assert the property rather than a handful of examples:

it('parse(format(x)) === x across the grid', () => {
  for (const x of SAMPLES) {
    expect(parseSize(formatSize(x, szDecimals), szDecimals)).toBe(x);
  }
});

Point assertions on formatters pass right up until someone adds locale grouping. A round-trip catches it, because grouping breaks the inverse.

Grid tests are the high-value ones

The engine rejects rather than rounds, so the client-side grid is the last line of defence against a rejected order that looked valid in the form:

it('floors size onto the grid before deriving margin', () => {
  const snapped = snapSize('0.123456789', 5);   // szDecimals = 5
  expect(snapped).toBe('0.12345');
  expect(marginFor(snapped, price, leverage)).toMatchInlineSnapshot();
});

it('exempts reduce-only from minNotional', () => {
  expect(validate({ reduceOnly: true, notional: 1 })).toEqual({ ok: true });
});

Layout

Tests are co-located with a .test.ts(x) suffix; some ui/ primitives use a __tests__/ folder.

CI and hooks

GateRuns
.github/workflows/ci.ymllint → test:run → build on push to develop / next / main, and every PR
Husky pre-commitlint-staged — ESLint --fix on staged .ts(x)
Husky pre-pushtsc -b && npm run test:run

CI does not gate Cloudflare Pages deploys — CF watches the same branches independently. A red build does not stop a deploy. Treat the pre-push hook as the real gate, and --no-verify as something you justify out loud.

On this page