Testing
sh
pnpm test:all # type-check, then frontend tests, then Rust tests| Command | What it runs |
|---|---|
pnpm lint | tsc --noEmit, the type check. It is clean, so keep it clean |
pnpm test | Vitest and React Testing Library in jsdom |
pnpm test:watch | Vitest in watch mode |
pnpm test:rust | cargo test for src-tauri |
pnpm test:all | All of the above, in order |
cd src-tauri && cargo test -- --ignored | The opt-in test that uses the real OS credential store |
Disk space. A Rust debug build is several GB. If cargo test fails with "not enough space on the disk", point the build output at another drive: CARGO_TARGET_DIR=E:/cargo-targets/localmind pnpm test:rust. In PowerShell, set $env:CARGO_TARGET_DIR first.
Frontend tests
- Configuration is in
vitest.config.ts: jsdom,src/test/setup.ts, and the@alias pointing at the repository root. Tests sit next to the code as*.test.ts(x). src/test/setup.tsclearslocalStorage, unmounts after every test and adds amatchMediashim.- The Rust backend is mocked at
src/lib/backend(or at@tauri-apps/api/corefor the wrapper tests). A test that needs to be "inside the desktop app" setswindow.__TAURI_INTERNALS__. Without it, the test exercises the plain-browser path. - App-level tests render
<App />with the boot animation and welcome dialog switched off throughlocalStorage, then drive the real interface:App.test.tsx,App.keys.test.tsx,App.backup.test.tsx,App.shortcuts.test.tsxandApp.advisor.test.tsx.
Some tests guard the project's rules rather than a single feature:
src/copy.test.tsreads the source of every component and fails if a banned marketing claim returns.src/data/releases.test.tsfails if the version differs betweenpackage.json,tauri.conf.json,Cargo.tomlandCargo.lock, or if the in-app release list drifts fromCHANGELOG.md.src/lib/feed.test.tsfails iffeed/providers.jsondrifts fromsrc/data/providers.ts.bootWatchdog.test.tsextracts the real script fromindex.htmland runs it with fake timers.
Writing a frontend test
- Query by role or label (
getByRole('button', { name: ... })), not by class name. - Use the real seed data (
INITIAL_AI_TOOLS,DEFAULT_BOOKMARKS) and derive expected counts from it, so a data change does not need a test change. - If a test finds a bug, fix the bug and keep the test. Several tests here began as regressions.
Rust tests
Unit tests live in each module under #[cfg(test)]. Pure logic (validation, request building, response parsing, crypto) is separated from I/O so it can be tested without a network or an OS keychain:
- The key store is a trait with an in-memory test double.
- The probe tests use real loopback sockets.
- The
feed.rstests runfetch_feeditself against a throwaway local server: success, HTTP error, oversized body, redirect off HTTPS, non-text body, unreachable server. - The
setup.rstests check recipe data and detection without installing anything.
What is not covered
- The real boundary between the webview and Rust (command names, argument shapes and permissions) is covered only by mocks. A smoke suite against the running app would close that gap.
- Anything that needs a real machine. The packaged app has been run by hand on Windows and macOS, including Setup, but not every feature on each. Linux has not been run. Still worth checking on each system: file downloads and the file picker in the packaged window, the OS credential store, and how long the backup key derivation takes on a slow computer.