Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Testing Guide

This repository uses **Vitest** (with Jest-style APIs: `describe`, `it`, `expect`, `vi`).

## Test structure

- **Unit tests**: `src/**/*.test.{ts,tsx}`
- **Main unit environment**: `jsdom` with setup in `vitest.setup.ts`
- **Storybook tests**: separate Vitest project (`storybook`)

Current layout examples:
- Pure logic tests in `src/lib/*.test.ts`
- Feature/component logic tests near source (for example `src/components/send/*.test.ts`)
- Hook tests near hooks (for example `src/hooks/useFeatures.test.ts`)

## Run tests and coverage

- Run all tests (all projects):

```bash
npm test
```

- Run unit tests only (recommended for local development):

```bash
npm run test:unit
```

- Run one file:

```bash
npm test -- src/hooks/useFeatures.test.ts
```

- Coverage (unit-focused, practical default):

```bash
npm run test:unit -- --coverage
```

- Coverage (all projects):

```bash
npm test -- --coverage
```

## Mocking APIs

Prefer module-level mocks with `vi.mock(...)` and keep tests deterministic.

Typical patterns in this repo:

```ts
vi.mock('@tanstack/react-query', () => ({ useQuery: vi.fn() }))
vi.mock('@/lib/api/logs', () => ({ fetchFeatures: vi.fn() }))
```

```ts
vi.mock('@joinmarket-webui/joinmarket-api-ts', () => ({
createClient: vi.fn(() => ({ interceptors: { request: { use: vi.fn() } } })),
}))
```

Guidelines:
- Mock network boundaries (`fetch`, API clients, websocket hooks), not business logic.
- Reset mocks in `beforeEach` with `vi.clearAllMocks()`.
- Assert behavior (return values/state transitions), not implementation details.

## WebSocket behavior in tests

`useJmWebsocket` depends on timers, reconnect backoff, and heartbeat scheduling.

Recommended approach:
- Mock `react-use-websocket` and control `readyState` + `sendMessage` explicitly.
- Use fake timers for auth refresh and heartbeat intervals:

```ts
vi.useFakeTimers()
// trigger effects/timeouts
vi.advanceTimersByTime(ms)
```

- Verify:
- auth token is sent after connection becomes healthy
- `isAuthenticated` toggles on open/close paths
- heartbeat sends are scheduled/cancelled correctly
- Always clean up timers/mocks in `afterEach`.

## Why Send/Earn flows require stricter testing

Send and Earn paths directly affect wallet safety and user funds.

High-risk areas include:
- amount/address validation
- collaborator/coinjoin preconditions
- fee and scheduling logic
- state transitions around confirmations and execution

Testing expectations for these flows:
- Cover boundary and invalid inputs (null/undefined/malformed/edge values)
- Prefer explicit, deterministic unit tests for validation and request-building logic
- Raise coverage thresholds for `src/components/send/` and `src/components/earn/` above global defaults
261 changes: 261 additions & 0 deletions docs/baseline-coverage-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
# Baseline Coverage Notes

## Test setup facts
- package.json script test runs vitest run.
- package.json also defines test:unit and test:storybook projects.
- vitest.config.ts sets test env LC_ALL to en_US.UTF-8.
- vitest.config.ts configures two projects: storybook (browser chromium via Playwright) and unit (jsdom, globals, setupFiles ./vitest.setup.ts).
- vitest.config.ts has no explicit coverage output path override.

## Command simulation results
- npm test: FAIL (exit code 1). Summary: 19 test files passed, 191 tests passed, but Vitest reported an unhandled EACCES listen error on ::1:63315 and process close timeout.
- npm test -- --coverage: FAIL (exit code 1). Summary: coverage mode enabled, 18 test files passed, 1 failed; 190 tests passed, 1 failed (hashPassword timeout), plus the same unhandled EACCES listen error and close timeout.
- Coverage artifact status: missing. No coverage directory was produced. Likely cause is non-zero test run termination (failed test plus unhandled runtime error), so coverage report files were not emitted.

Covered:
- src/components/earn/report/hooks/earnReportParser.ts (Risk: Low)
- src/components/send/collaborativeSend.ts (Risk: Low)
- src/components/send/feeEstimate.ts (Risk: Low)
- src/components/sweep/destinationValidation.ts (Risk: Low)
- src/components/sweep/preconditions.ts (Risk: Low)
- src/components/sweep/scheduleUtils.ts (Risk: Low)
- src/components/ui/jam/Balance.tsx (Risk: Low)
- src/constants/jm.ts (Risk: Low)
- src/context/JamDisplayContextProvider.tsx (Risk: Low)
- src/lib/balanceSummary.ts (Risk: Low)
- src/lib/bip21.ts (Risk: Low)
- src/lib/bip32.ts (Risk: Low)
- src/lib/config.ts (Risk: Low)
- src/lib/errorReason.ts (Risk: Low)
- src/lib/feeConfig.ts (Risk: Low)
- src/lib/fidelityBondUtils.ts (Risk: Low)
- src/lib/hash.ts (Risk: Low)
- src/lib/tags.ts (Risk: Low)
- src/lib/utils.ts (Risk: Low)
- src/lib/xpubs.ts (Risk: Low)
- src/store/authStore.ts (Risk: Low)

Partially covered:
- src/components/send/types.d.ts (Risk: Medium)
- src/context/JamWalletInfoContext.ts (Risk: Medium)
- src/hooks/useFeeConfigValidation.ts (Risk: Medium)
- src/hooks/useQueryUtxos.ts (Risk: Medium)

Not covered:
- src/App.tsx (Risk: High)
- src/components/create/CreateStepConfirm.tsx (Risk: High)
- src/components/create/CreateStepVerifyMnemonic.tsx (Risk: High)
- src/components/create/CreateStepWalletDetails.tsx (Risk: High)
- src/components/create/CreateWalletForm.tsx (Risk: High)
- src/components/create/CreateWalletPage.tsx (Risk: High)
- src/components/create/OtherWalletActiveAlert.tsx (Risk: High)
- src/components/dev/DevBadge.tsx (Risk: High)
- src/components/dev/DevErrorThrowingComponent.tsx (Risk: High)
- src/components/dev/DevPage.tsx (Risk: High)
- src/components/dev/DevSetupPage.tsx (Risk: High)
- src/components/dev/FeeConfigTestComponent.tsx (Risk: High)
- src/components/earn/CreateFidelityBondDialog/CreateFidelityBondDialog.tsx (Risk: High)
- src/components/earn/CreateFidelityBondDialog/CreateFidelityBondDialogSteps.tsx (Risk: High)
- src/components/earn/CreateFidelityBondDialog/index.ts (Risk: High)
- src/components/earn/CreateFidelityBondDialog/StepProgress.tsx (Risk: High)
- src/components/earn/CreateFidelityBondDialog/types.ts (Risk: High)
- src/components/earn/CreateFidelityBondDialog/useCreateFidelityBondWizard.ts (Risk: High)
- src/components/earn/EarnForm.tsx (Risk: High)
- src/components/earn/EarnPage.tsx (Risk: High)
- src/components/earn/FidelityBondCard.tsx (Risk: High)
- src/components/earn/MoveToJarDialog.tsx (Risk: High)
- src/components/earn/OfferCard.tsx (Risk: High)
- src/components/earn/RenewBondDialog.tsx (Risk: High)
- src/components/earn/report/EarnReportChart.tsx (Risk: High)
- src/components/earn/report/EarnReportContent.tsx (Risk: High)
- src/components/earn/report/EarnReportOverlay.tsx (Risk: High)
- src/components/earn/report/EarnReportPage.tsx (Risk: High)
- src/components/earn/report/hooks/useQueryYieldgenReport.ts (Risk: High)
- src/components/error/ErrorPage.tsx (Risk: High)
- src/components/import/ImportDetailsForm.tsx (Risk: High)
- src/components/import/ImportStepConfirm.tsx (Risk: High)
- src/components/import/ImportStepImportDetails.tsx (Risk: High)
- src/components/import/ImportStepWalletDetails.tsx (Risk: High)
- src/components/import/ImportWalletPage.tsx (Risk: High)
- src/components/import/RescanActiveAlert.tsx (Risk: High)
- src/components/layout/AppFooter.tsx (Risk: High)
- src/components/layout/AppNavbar.tsx (Risk: High)
- src/components/layout/AppSidebar.tsx (Risk: High)
- src/components/layout/AuthPageShell.tsx (Risk: High)
- src/components/layout/footer/BetaWarningDialog.tsx (Risk: High)
- src/components/layout/Layout.tsx (Risk: High)
- src/components/layout/PostLoginOnboardingTour.tsx (Risk: High)
- src/components/logging/LogViewer.tsx (Risk: High)
- src/components/logging/useJmwalletdStdoutLog.ts (Risk: High)
- src/components/login/LoginCard.tsx (Risk: High)
- src/components/login/LoginForm.tsx (Risk: High)
- src/components/login/LoginPage.tsx (Risk: High)
- src/components/login/OnboardingDialog.tsx (Risk: High)
- src/components/LogsContent.tsx (Risk: High)
- src/components/LogsOverlay.tsx (Risk: High)
- src/components/LogsPage.tsx (Risk: High)
- src/components/MainWalletPage.tsx (Risk: High)
- src/components/orderbook/OrderbookChart.tsx (Risk: High)
- src/components/orderbook/OrderbookContent.tsx (Risk: High)
- src/components/orderbook/OrderbookOverlay.tsx (Risk: High)
- src/components/orderbook/OrderbookPage.tsx (Risk: High)
- src/components/orderbook/OrderbookTable.tsx (Risk: High)
- src/components/receive/BitcoinAmountInput.tsx (Risk: High)
- src/components/receive/BitcoinQR.tsx (Risk: High)
- src/components/receive/ReceiveForm.tsx (Risk: High)
- src/components/receive/ReceivePage.tsx (Risk: High)
- src/components/receive/types.d.ts (Risk: High)
- src/components/send/JarSelectorDialog.tsx (Risk: High)
- src/components/send/PaymentConfirmDialog.tsx (Risk: High)
- src/components/send/SendCoinjoinPreconditionAlert.tsx (Risk: High)
- src/components/send/SendForm.tsx (Risk: High)
- src/components/send/SendPage.tsx (Risk: High)
- src/components/send/UtxoSelectionDialog.tsx (Risk: High)
- src/components/settings/AccountXpubsDialog.tsx (Risk: High)
- src/components/settings/CollaboratorFeesForm.tsx (Risk: High)
- src/components/settings/FeeLimitDialog.tsx (Risk: High)
- src/components/settings/LanguageSelector.tsx (Risk: High)
- src/components/settings/MiningFeesForm.tsx (Risk: High)
- src/components/settings/RescanChainPage.tsx (Risk: High)
- src/components/settings/SeedPhraseDialog.tsx (Risk: High)
- src/components/settings/SettingsItem.tsx (Risk: High)
- src/components/settings/SettingsPage.tsx (Risk: High)
- src/components/settings/TxFeeInputField.tsx (Risk: High)
- src/components/sweep/SweepDestinationInputs.tsx (Risk: High)
- src/components/sweep/SweepPage.tsx (Risk: High)
- src/components/sweep/SweepPreconditionAlert.tsx (Risk: High)
- src/components/sweep/SweepScheduleProgress.tsx (Risk: High)
- src/components/sweep/SweepStartConfirmDialog.tsx (Risk: High)
- src/components/SwitchWalletPage.tsx (Risk: High)
- src/components/ui/accordion.tsx (Risk: High)
- src/components/ui/alert.tsx (Risk: High)
- src/components/ui/avatar.tsx (Risk: High)
- src/components/ui/badge.tsx (Risk: High)
- src/components/ui/badge-variants.ts (Risk: High)
- src/components/ui/button.tsx (Risk: High)
- src/components/ui/button-group.tsx (Risk: High)
- src/components/ui/button-variants.ts (Risk: High)
- src/components/ui/card.tsx (Risk: High)
- src/components/ui/checkbox.tsx (Risk: High)
- src/components/ui/dialog.tsx (Risk: High)
- src/components/ui/dropdown-menu.tsx (Risk: High)
- src/components/ui/field.tsx (Risk: High)
- src/components/ui/input.tsx (Risk: High)
- src/components/ui/input-group.tsx (Risk: High)
- src/components/ui/input-variants.ts (Risk: High)
- src/components/ui/jam/Address.tsx (Risk: High)
- src/components/ui/jam/Cheatsheet.tsx (Risk: High)
- src/components/ui/jam/ClickableJar.tsx (Risk: High)
- src/components/ui/jam/CopyButton.tsx (Risk: High)
- src/components/ui/jam/CurrencySymbol.tsx (Risk: High)
- src/components/ui/jam/FeeConfigErrorAlert.tsx (Risk: High)
- src/components/ui/jam/Jar.tsx (Risk: High)
- src/components/ui/jam/JarIcon.tsx (Risk: High)
- src/components/ui/jam/JmWebsocketInfo.tsx (Risk: High)
- src/components/ui/jam/LockWalletConfirmDialog.tsx (Risk: High)
- src/components/ui/jam/MaskedText.tsx (Risk: High)
- src/components/ui/jam/PageLoading.tsx (Risk: High)
- src/components/ui/jam/PageTitle.tsx (Risk: High)
- src/components/ui/jam/SeedPhraseGrid.tsx (Risk: High)
- src/components/ui/jam/SelectableJar.tsx (Risk: High)
- src/components/ui/jam/SortIcon.tsx (Risk: High)
- src/components/ui/jam/StatusBadge.tsx (Risk: High)
- src/components/ui/jam/StatusBadge-variants.ts (Risk: High)
- src/components/ui/jam/TablePagination.tsx (Risk: High)
- src/components/ui/jam/ThemeToggleButton.tsx (Risk: High)
- src/components/ui/jam/WalletLoadErrorAlert.tsx (Risk: High)
- src/components/ui/label.tsx (Risk: High)
- src/components/ui/pagination.tsx (Risk: High)
- src/components/ui/popover.tsx (Risk: High)
- src/components/ui/QrScannerDialog.tsx (Risk: High)
- src/components/ui/radio-group.tsx (Risk: High)
- src/components/ui/select.tsx (Risk: High)
- src/components/ui/separator.tsx (Risk: High)
- src/components/ui/sheet.tsx (Risk: High)
- src/components/ui/sidebar.tsx (Risk: High)
- src/components/ui/skeleton.tsx (Risk: High)
- src/components/ui/sonner.tsx (Risk: High)
- src/components/ui/spinner.tsx (Risk: High)
- src/components/ui/switch.tsx (Risk: High)
- src/components/ui/table.tsx (Risk: High)
- src/components/ui/tabs.tsx (Risk: High)
- src/components/ui/textarea.tsx (Risk: High)
- src/components/ui/tooltip.tsx (Risk: High)
- src/components/ui/use-sidebar.tsx (Risk: High)
- src/components/utils/PasswordVerificationForm.tsx (Risk: High)
- src/components/utils/PreventLeavingPageByMistake.tsx (Risk: High)
- src/components/wallet/AccountDetailsTabContent.tsx (Risk: High)
- src/components/wallet/BranchEntryTable.tsx (Risk: High)
- src/components/wallet/JarUtxosTable.tsx (Risk: High)
- src/components/wallet/WalletJarsDetailsContent.tsx (Risk: High)
- src/components/wallet/WalletJarsDetailsOverlay.tsx (Risk: High)
- src/components/wallet/WalletJarsDetailsPage.tsx (Risk: High)
- src/constants/debugFeatures.ts (Risk: High)
- src/constants/jam.ts (Risk: High)
- src/constants/meta-env-utils.ts (Risk: High)
- src/constants/onboarding.ts (Risk: High)
- src/constants/routes.ts (Risk: High)
- src/context/JamDisplayContext.ts (Risk: High)
- src/context/JamSessionInfoContext.ts (Risk: High)
- src/context/JamSessionInfoContextProvider.tsx (Risk: High)
- src/context/JamWalletInfoContextProvider.tsx (Risk: High)
- src/hooks/useApiClient.ts (Risk: High)
- src/hooks/useCheatsheet.ts (Risk: High)
- src/hooks/useDisplaySettings.ts (Risk: High)
- src/hooks/useFeatures.ts (Risk: High)
- src/hooks/useJmConfig.ts (Risk: High)
- src/hooks/useJmWebsocket.ts (Risk: High)
- src/hooks/use-mobile.ts (Risk: High)
- src/hooks/useQueryDisplayWallet.ts (Risk: High)
- src/hooks/useQueryJmInfo.ts (Risk: High)
- src/hooks/useRefreshSession.ts (Risk: High)
- src/hooks/useUtxoSelectionDialog.ts (Risk: High)
- src/i18n/config.ts (Risk: High)
- src/i18n/languages.ts (Risk: High)
- src/i18n/testConfig.ts (Risk: High)
- src/lib/api/logs.ts (Risk: High)
- src/lib/api/orderbook.ts (Risk: High)
- src/lib/queryClient.ts (Risk: High)
- src/main.tsx (Risk: High)
- src/store/jamSettingsStore.ts (Risk: High)
- src/store/jmConfigStore.ts (Risk: High)
- src/store/jmSessionStore.ts (Risk: High)
- src/store/jmTxStore.ts (Risk: High)
- src/stories/Accordion.stories.tsx (Risk: High)
- src/stories/Alert.stories.tsx (Risk: High)
- src/stories/Avatar.stories.tsx (Risk: High)
- src/stories/Badge.stories.tsx (Risk: High)
- src/stories/Button.stories.tsx (Risk: High)
- src/stories/Card.stories.tsx (Risk: High)
- src/stories/Input.stories.tsx (Risk: High)
- src/stories/jam/Address.stories.tsx (Risk: High)
- src/stories/jam/AppFooter.stories.tsx (Risk: High)
- src/stories/jam/AppNavbar.stories.tsx (Risk: High)
- src/stories/jam/Balance.stories.tsx (Risk: High)
- src/stories/jam/BitcoinAmountInput.stories.tsx (Risk: High)
- src/stories/jam/dialogs/LockWalletConfirmDialog.stories.tsx (Risk: High)
- src/stories/jam/Jar.stories.tsx (Risk: High)
- src/stories/jam/JmWebsocketInfo.stories.tsx (Risk: High)
- src/stories/jam/pages/create/CreateStepConfirm.stories.tsx (Risk: High)
- src/stories/jam/pages/create/CreateStepVerifyMnemonic.stories.tsx (Risk: High)
- src/stories/jam/pages/create/CreateStepWalletDetails.stories.tsx (Risk: High)
- src/stories/jam/pages/import/ImportStepConfirm.stories.tsx (Risk: High)
- src/stories/jam/pages/import/ImportStepImportDetails.stories.tsx (Risk: High)
- src/stories/jam/pages/import/ImportStepWalletDetails.stories.tsx (Risk: High)
- src/stories/jam/pages/login/LoginCard.stories.tsx (Risk: High)
- src/stories/jam/pages/login/LoginForm.stories.tsx (Risk: High)
- src/stories/jam/pages/login/OnboardingDialog.stories.tsx (Risk: High)
- src/stories/jam/SeedPhraseGrid.stories.tsx (Risk: High)
- src/stories/jam/SelectableJar.stories.tsx (Risk: High)
- src/stories/jam/StatusBadge.stories.tsx (Risk: High)
- src/stories/jam/ThemeToggleButton.stories.tsx (Risk: High)
- src/stories/Label.stories.tsx (Risk: High)
- src/stories/Sekeleton.stories.tsx (Risk: High)
- src/stories/Select.stories.tsx (Risk: High)
- src/stories/Separator.stories.tsx (Risk: High)
- src/stories/Sonner.stories.tsx (Risk: High)
- src/stories/Spinner.stories.tsx (Risk: High)
- src/stories/Switch.stories.tsx (Risk: High)
- src/stories/Tooltip.stories.tsx (Risk: High)
- src/types/global.d.ts (Risk: High)
- src/vite-env.d.ts (Risk: High)
Loading