Skip to content

[URGENT / CRITICAL — MERGE FIRST] test(web): 18 failing tests block main CI; this PR is the harbinger of green CI #392

Description

@K1NGD4VID

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:

  1. No other PR can merge without admin bypass while these tests are red.
  2. No contributor can trust their own CI signal while these tests are red.
  3. 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

cd apps/web
pnpm test

Repeat until zero failures. Then push and let CI confirm.


Acceptance Criteria

  • pnpm --filter web test exits 0 locally, no failing suites.
  • Verify TypeScript Frontend & SDK > Run Unit Tests reports green on this PR's CI.
  • No new failing suites introduced.
  • No product code refactors beyond the useBalances.ts early-return fix.
  • apps/web/vitest.setup.ts exists 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.tsx either has a real test or is deleted.

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 firstapps/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:

  1. It must land first before any other PR against main.
  2. It requires you to run the full test suite iteratively and understand the mock/provider patterns.
  3. It's the single unlock for the entire repo's CI signal.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Stellar WaveIssues in the Stellar wave programci-blockerFailure of this issue turns main CI redcomplexity:highgood first issueGood for newcomerspriorityBlocks main / urgenttestTest coverageurgentBlocks all other work — must be resolved first

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions