Overview
Every user-data feature in the High IQ mobile app is covered by automated, simulator-free tests organized into three tiers. Each tier proves something the others structurally cannot see, and each costs a different amount to run. Cannabis and strain data (Supabase) is out of scope except where a user feature depends on it. User data in Convex is the focus —apps/mobile/convex/schema.ts
is the source of truth for what exists.
This platform is separate from the Revyl cloud-device suite. Revyl drives a real
iOS simulator through the UI; the tiers below never boot a simulator and run in
seconds to minutes instead.
The three tiers
Tiers A and B are free, fast, and hermetic — they never touch a deployment.
Tier C hits real infrastructure: the shared staging Convex deployment, the
production Hono API, and Trigger.dev’s prod environment. It writes real rows and
spends real credits, which is why it is invoked explicitly and never runs on push.
Running each tier
All commands run fromapps/mobile (or with pnpm --filter @highiq/mobile from
anywhere in the monorepo).
- Tier A + B (free)
- Tier C (billed)
Adding a test for a new feature
1
Pick the tier by what you need to prove
- Function logic, a validator, an auth check, a scheduled effect → Tier A
- The arguments a hook sends, or how a screen renders a backend state → Tier B
- “Does this actually work on the deployed backend? Does the pipeline complete?” → Tier C
2
Name the file
New Tier A/B suites use the
*.featuretests.test.ts(x) suffix in a new
file, so parallel authors never collide with the existing suites.3
Tag it (Tier C)
Vitest
-t matches the concatenated describe/test title, so tags live in
titles: @smoke, @slow, @ai, @trigger, @shared-account, and
@P0–@P3. A case with no tag runs in every lane.4
Add the matrix row
Give the case an area-prefixed ID (
ORD-231, NB-118, SUB-115, …) and add
it to the master matrix with tier, priority, status, and implementing file.
Do not leave a test out of the matrix — the matrix is what the weekly
regression run is read against.5
Scope every Tier C assertion to your own test user
Staging is shared by every non-production build plus a daily cron. Never
assert on a global count, a “latest N” listing, or a cross-user aggregate —
those pass or fail depending on what someone else did five minutes ago.
Interpreting results
Per-area builder results and run evidence live in the repo underdocs/testing/feature-audit/. Statuses have fixed meanings:
Two lessons worth keeping
- A pin must assert behaviour, never a spelling.
it.failsonly self-flips when the assertion observes the effect of the defect. A negative source scan (not.toContain('…')) fails open — it goes green when the literal is merely reworded while the defect remains. Invert unavoidable source assertions into a positive binding. - The in-memory fake and a real deployment differ on time. On a real Convex
deployment
Date.now()is frozen for the duration of a mutation, so every row written in one handler shares a timestamp; the in-memory fake advances per call. A Tier A test asserting timestamp equality across rows can flake in the harness while being correct in production — hoistconst now = Date.now()in the handler rather than quarantining the test.
Related
Tier C — Staging Integration
Identity model, production guard, tags, and cost controls.
Subscription-State Testing
How free, active-Pro, and lapsed-Pro states are proven now that staging enforces the gate.
Weekly Feature Regression
Lanes, local reproduction, and why the schedule is still off.
Launch Testing Plan
Where this platform sits among Revyl, Argent, and manual QA.
