STOP. Read this before touching anything else in this repo.
This is the single blocker between main and green CI. Every other PR you open right now inherits a red build through no fault of its own — this issue is what un-inherits it. Fix this, merge this, then everything else.
TL;DR
Why this is priority zero
Right now, if any other contributor opens a PR — even a docs typo — their CI turns red. Not because of their change, because of these failing tests on main. That makes it impossible for reviewers or authors to distinguish "you broke the build" from "the build was already broken." Every PR under this state is noise for everyone involved.
Branch protection on main requires Verify TypeScript Frontend & SDK to pass. It currently doesn't. So:
- No other PR can merge without admin bypass while these tests are red.
- No contributor can trust their own CI signal while these tests are red.
- You (the reviewer) can't tell a broken contribution from an inherited failure.
Fixing this issue is therefore not just "a test cleanup" — it is the unlock for the entire contribution pipeline.
Where (file:line)
| File |
Failing tests |
Root cause |
apps/web/hooks/useAuth.test.ts |
7 of 9 |
initApiClientWithToken side effect unmocked |
apps/web/hooks/useBalances.test.ts |
7 of 7 |
Missing QueryClientProvider + useBalances throws instead of returning nulls when disconnected |
apps/web/components/invoice/InvoiceForm.test.tsx |
2 of 2 |
Missing QueryClientProvider |
apps/web/hooks/useWallet.test.ts |
1 of 9 |
Related mock issue |
apps/web/lib/api.test.ts |
1 of 18 |
initApiClientWithToken singleton state leak |
apps/web/lib/toast.test.tsx |
Entire file |
Empty file — vitest treats zero tests as failure |
Total: 18 failing tests across 6 files. Every one has a root cause you can see in the CI log for run 29341922753.
Current Behavior
Root cause 1 — useAuth tests: unmocked initApiClientWithToken side effect
hooks/useAuth.ts:75,80,86 calls initApiClientWithToken(), which mutates a module-level singleton in lib/api.ts. useAuth.test.ts never mocks this, so the singleton mutation leaks between tests and throws in isolation.
FAIL hooks/useAuth.test.ts > useAuth > successful login flow
❯ Object.login hooks/useAuth.ts:75:7
❯ hooks/useAuth.test.ts:89:7
Root cause 2 — hook and component tests missing QueryClientProvider
useBalances, InvoiceForm, and anything that indirectly renders useInvoices or usePool calls useQueryClient() from @tanstack/react-query. The tests render without wrapping in a provider.
Error: No QueryClient set, use QueryClientProvider to set one
❯ useQueryClient node_modules/@tanstack/react-query/src/QueryClientProvider.tsx:18:10
❯ useInvoices hooks/useInvoices.ts:67:23
❯ InvoiceForm components/invoice/InvoiceForm.tsx:26:54
Root cause 3 — useBalances.ts throws when disconnected instead of returning nulls
Test "returns nulls if not connected" fails because useBalances.ts:65 throws. The test correctly expects nulls. This is a leftover of the wave-merge repair — the branch that handles connected === false was accidentally converted to a throw.
Root cause 4 — lib/toast.test.tsx is empty
Vitest treats a zero-test file as a failure. Either populate it with a real assertion (even a smoke test importing toast and checking it's defined) or delete the file.
Expected Behavior
pnpm test (which runs pnpm --filter web test under the hood) exits 0 on main and on this PR.
The CI check Verify TypeScript Frontend & SDK > Run Unit Tests reports green.
Proposed Approach
Do each step in this order — later steps depend on earlier ones being in place.
1. Add a test setup file that mocks initApiClientWithToken globally
Create apps/web/vitest.setup.ts (or extend the existing one if present):
import { vi, beforeEach } from 'vitest';
vi.mock('@/lib/api', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/lib/api')>();
return {
...actual,
initApiClientWithToken: vi.fn(),
};
});
beforeEach(() => {
vi.clearAllMocks();
});
Wire it into apps/web/vitest.config.ts:
export default defineConfig({
test: {
// ...
setupFiles: ['./vitest.setup.ts'],
},
});
2. Add a shared renderWithProviders helper
Create apps/web/test-utils/renderWithProviders.tsx:
import { render, RenderOptions } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactElement, ReactNode } from 'react';
export function createTestQueryClient() {
return new QueryClient({
defaultOptions: { queries: { retry: false, gcTime: 0 } },
});
}
export function renderWithProviders(
ui: ReactElement,
options?: Omit<RenderOptions, 'wrapper'>,
) {
const client = createTestQueryClient();
const wrapper = ({ children }: { children: ReactNode }) => (
<QueryClientProvider client={client}>{children}</QueryClientProvider>
);
return render(ui, { wrapper, ...options });
}
Update useBalances.test.ts, InvoiceForm.test.tsx, useInvoices.test.ts, usePool.test.ts, useTxHistory.test.ts to import renderWithProviders and use renderHook(..., { wrapper }) from @testing-library/react where appropriate.
3. Fix hooks/useBalances.ts early-return behavior
Line 65-ish currently throws when the wallet isn't connected. Change it to return { xlm: null, usdc: null, isLoading: false, error: null } (or whatever shape the test "returns nulls if not connected" asserts — read the assertion first, mirror it).
This is the only product-code change in this issue. Everything else is test infrastructure.
4. Populate or delete apps/web/lib/toast.test.tsx
Simplest passing version:
import { describe, it, expect } from 'vitest';
import { toast } from './toast';
describe('toast', () => {
it('exports the toast helpers', () => {
expect(typeof toast).toBe('object');
});
});
Or delete the file if there's no meaningful surface to test.
5. Run locally until green
Repeat until zero failures. Then push and let CI confirm.
Acceptance Criteria
Definition of Done
- CI green on this PR (all steps: Build, Test, Lint, Format, Go).
- PR reviewed and admin-merged.
- The moment this lands,
main goes green for the first time since the wave merges. The next contributor who opens a PR sees a clean baseline and their red/green is 100% about their own change.
Contributor Tips
- Read the vitest config first —
apps/web/vitest.config.ts may already have a setup file. Extend it, don't overwrite.
- The
initApiClientWithToken mock is the single biggest lift. It unblocks 7 tests in one shot.
- Look at existing passing tests (there are ~76 passing already) for the patterns this repo uses. Match those patterns; don't invent new ones.
- Retry logic must be off in tests.
new QueryClient({ defaultOptions: { queries: { retry: false } } }) — without this, React Query's default 3 retries make every failing test 3× slower.
gcTime: 0 prevents state leaking between tests.
- Don't touch
packages/sdk tests. They're independent (base.test.ts etc.). Only apps/web tests fail.
- Don't add snapshot tests. Snapshots grow stale fast.
- Run
pnpm test at each step, not just at the end. Fixing 1 root cause at a time lets you see progress.
Tech Stack
- Test runner: Vitest 4.x
- Renderer: React 18 +
@testing-library/react
- State:
@tanstack/react-query 5.x, Zustand
- Location: All test files in
apps/web/**/*.test.{ts,tsx} — SDK tests in packages/sdk are separate and already passing.
How to Claim
Comment "I'll take this" on the issue. Read CONTRIBUTING.md for the general flow. Do not open a PR without claiming first — this is the priority-zero issue and the maintainer needs to know exactly one person is working on it at a time to avoid duplicate work.
Difficulty
[URGENT / Hard] — the work itself is Medium-effort test plumbing, but it's tagged Hard because:
- It must land first before any other PR against
main.
- It requires you to run the full test suite iteratively and understand the mock/provider patterns.
- It's the single unlock for the entire repo's CI signal.
TL;DR
mainnow builds and lints cleanly.QueryClientProvider, one hook returning wrong shape when disconnected.maingoes green for the first time since the wave merges. Every other contributor stops inheriting a red baseline. That is the entire point of this issue.Why this is priority zero
Right now, if any other contributor opens a PR — even a docs typo — their CI turns red. Not because of their change, because of these failing tests on
main. That makes it impossible for reviewers or authors to distinguish "you broke the build" from "the build was already broken." Every PR under this state is noise for everyone involved.Branch protection on
mainrequiresVerify TypeScript Frontend & SDKto pass. It currently doesn't. So:Fixing this issue is therefore not just "a test cleanup" — it is the unlock for the entire contribution pipeline.
Where (file:line)
apps/web/hooks/useAuth.test.tsinitApiClientWithTokenside effect unmockedapps/web/hooks/useBalances.test.tsQueryClientProvider+useBalancesthrows instead of returning nulls when disconnectedapps/web/components/invoice/InvoiceForm.test.tsxQueryClientProviderapps/web/hooks/useWallet.test.tsapps/web/lib/api.test.tsinitApiClientWithTokensingleton state leakapps/web/lib/toast.test.tsxTotal: 18 failing tests across 6 files. Every one has a root cause you can see in the CI log for run
29341922753.Current Behavior
Root cause 1 —
useAuthtests: unmockedinitApiClientWithTokenside effecthooks/useAuth.ts:75,80,86callsinitApiClientWithToken(), which mutates a module-level singleton inlib/api.ts.useAuth.test.tsnever mocks this, so the singleton mutation leaks between tests and throws in isolation.Root cause 2 — hook and component tests missing
QueryClientProvideruseBalances,InvoiceForm, and anything that indirectly rendersuseInvoicesorusePoolcallsuseQueryClient()from@tanstack/react-query. The tests render without wrapping in a provider.Root cause 3 —
useBalances.tsthrows when disconnected instead of returning nullsTest
"returns nulls if not connected"fails becauseuseBalances.ts:65throws. The test correctly expects nulls. This is a leftover of the wave-merge repair — the branch that handlesconnected === falsewas accidentally converted to a throw.Root cause 4 —
lib/toast.test.tsxis emptyVitest treats a zero-test file as a failure. Either populate it with a real assertion (even a smoke test importing
toastand checking it's defined) or delete the file.Expected Behavior
pnpm test(which runspnpm --filter web testunder the hood) exits 0 onmainand on this PR.The CI check
Verify TypeScript Frontend & SDK > Run Unit Testsreports green.Proposed Approach
Do each step in this order — later steps depend on earlier ones being in place.
1. Add a test setup file that mocks
initApiClientWithTokengloballyCreate
apps/web/vitest.setup.ts(or extend the existing one if present):Wire it into
apps/web/vitest.config.ts:2. Add a shared
renderWithProvidershelperCreate
apps/web/test-utils/renderWithProviders.tsx:Update
useBalances.test.ts,InvoiceForm.test.tsx,useInvoices.test.ts,usePool.test.ts,useTxHistory.test.tsto importrenderWithProvidersand userenderHook(..., { wrapper })from@testing-library/reactwhere appropriate.3. Fix
hooks/useBalances.tsearly-return behaviorLine 65-ish currently throws when the wallet isn't connected. Change it to return
{ xlm: null, usdc: null, isLoading: false, error: null }(or whatever shape the test"returns nulls if not connected"asserts — read the assertion first, mirror it).This is the only product-code change in this issue. Everything else is test infrastructure.
4. Populate or delete
apps/web/lib/toast.test.tsxSimplest passing version:
Or delete the file if there's no meaningful surface to test.
5. Run locally until green
Repeat until zero failures. Then push and let CI confirm.
Acceptance Criteria
pnpm --filter web testexits 0 locally, no failing suites.Verify TypeScript Frontend & SDK > Run Unit Testsreports green on this PR's CI.useBalances.tsearly-return fix.apps/web/vitest.setup.tsexists and is wired into vitest config.apps/web/test-utils/renderWithProviders.tsx(or equivalent) exists and is used by every failing hook/component test.apps/web/lib/toast.test.tsxeither has a real test or is deleted.Definition of Done
maingoes green for the first time since the wave merges. The next contributor who opens a PR sees a clean baseline and their red/green is 100% about their own change.Contributor Tips
apps/web/vitest.config.tsmay already have a setup file. Extend it, don't overwrite.initApiClientWithTokenmock is the single biggest lift. It unblocks 7 tests in one shot.new QueryClient({ defaultOptions: { queries: { retry: false } } })— without this, React Query's default 3 retries make every failing test 3× slower.gcTime: 0prevents state leaking between tests.packages/sdktests. They're independent (base.test.tsetc.). Onlyapps/webtests fail.pnpm testat each step, not just at the end. Fixing 1 root cause at a time lets you see progress.Tech Stack
@testing-library/react@tanstack/react-query5.x, Zustandapps/web/**/*.test.{ts,tsx}— SDK tests inpackages/sdkare separate and already passing.How to Claim
Comment "I'll take this" on the issue. Read
CONTRIBUTING.mdfor the general flow. Do not open a PR without claiming first — this is the priority-zero issue and the maintainer needs to know exactly one person is working on it at a time to avoid duplicate work.Difficulty
[URGENT / Hard]— the work itself is Medium-effort test plumbing, but it's tagged Hard because:main.