Repository navigation
Testing and CI
English · Español
What is tested, how, and which gate catches what: Vitest for the frontend and Node scripts, cargo test for Rust (including image-comparison golden tests for the card designer), the custom check scripts, Knip, the git hook, and the GitHub Actions workflows.
The local gates and CI run the same commands, so a green pnpm check plus the three Rust commands predicts a green CI. Setup is in Development Setup; conventions that the gates enforce are in Conventions.
flowchart TD
C["pnpm check"] --> T["check:tipos: tsc --noEmit"]
C --> V["test: vitest run"]
C --> B["check:biome: biome check ."]
C --> TI["check:title: sin-title-nativo.mjs"]
C --> I["idiomas: idiomas.mjs"]
C --> K["check:knip: knip"]
R1["check:rust-fmt: cargo fmt --check"]
R2["check:clippy: clippy -D warnings"]
R3["test:rust: cargo test --workspace"]
O["no OpenSSL: cargo tree -i openssl is empty"]
CI["CI job 'checks'"] --> C
CI --> R1
CI --> R2
CI --> R3
CI --> O
| Gate | Command | Fails when |
|---|---|---|
| Types |
pnpm check:tipos (tsc --noEmit) |
TypeScript errors, unused locals/parameters (strict, noUnusedLocals, noUnusedParameters in tsconfig.json) |
| Unit tests |
pnpm test (vitest run) |
A test fails |
| Lint + format |
pnpm check:biome (biome check .) |
Lint error or wrong formatting (tabs, quotes, line width 100; see biome.json) |
| Native tooltips | pnpm check:title |
A .tsx puts title= on an HTML element or a pass-through component |
| Translations | pnpm idiomas |
A string passed to t() has no English entry, or one string is in two dictionary files |
| Dead code |
pnpm check:knip (knip) |
Unused files or dependencies |
| Rust format | pnpm check:rust-fmt |
cargo fmt --check reports differences |
| Rust lints | pnpm check:clippy |
Any clippy warning (-D warnings, all targets, whole workspace), including unwrap/expect outside tests |
| Rust tests | pnpm test:rust |
Any test fails (this also runs the ts-rs export tests, see Development Setup) |
| No OpenSSL | CI step only |
cargo tree -i openssl finds the crate |
pnpm check runs its parts in the order tipos, test, biome, title, idiomas, knip (package.json scripts.check); title and idiomas run with --silent so only their own messages show.
Config: vitest.config.ts uses the node environment (no DOM), alias @ to src, restoreMocks: true, and picks up src/**/*.test.ts, src/**/*.test.tsx and scripts/**/*.test.mjs. happy-dom is a dev dependency but the default environment is node.
Philosophy: React components are not rendered in tests. The tested code is the logic that was deliberately pulled out of components into plain modules and stores, which are given a mocked @/lib/ipc. Typical pattern (see src/stores/__tests__/actualizacion-store.test.ts): vi.mock("@/lib/ipc", ...) fakes call and on with a switch on the command name and throws on an unexpected command, then asserts the sequence of commands the store issued.
| Folder | Tests (files) |
|---|---|
src/stores/__tests__/ |
deck-store, deck-store-abrir, ui-store, plan-store, trabajo-store, actualizacion-store
|
src/lib/__tests__/ |
cartas, deck-ops, dialogos, errores, image-url, imagen-viva, tema-inicial, temas-instalados, trabajos (the listen-before-launch job helper) |
src/features/decklist/__tests__/ |
acciones |
src/features/export/__tests__/ |
acciones |
src/features/library/__tests__/ |
organizar (filter, sort, group, selection) |
src/features/preview/__tests__/ |
acciones-carta, arrastrar-ficheros
|
src/features/variants/__tests__/ |
vincular |
src/features/card-editor/__tests__/ |
propagar (apply to card / copies / all) |
src/features/disenar/__tests__/ |
estilos, encuadre, grupos, renumerar, textos
|
src/features/stats/__tests__/ |
textos |
scripts/__tests__/ |
release-notas.test.mjs (argument parsing, release notes template and cleaning, latest.json notes rewriting) |
Gotcha: the scroll and animation behaviour of the real UI is not covered by any automated test; it has to be tried by hand in WebKitGTK (see Development Setup).
pnpm test:rust runs cargo test --workspace for two crates: the app (proxy-builder, library proxy_builder_lib) and the pure core proxy-core. As of this version there are several hundred #[test]/#[tokio::test] functions spread over roughly 90 modules (grep the tree for exact numbers). Dev-dependencies: tempfile, lopdf (inspects generated PDFs), and tokio with test-util.
Rules visible in the code:
-
No network in the default run. Anything that needs the live internet (Scryfall, GitHub, Google Fonts, MTGJSON, Cockatrice, mtg.io) is
#[ignore]and meant for manual runs. Parsers are tested against saved fixtures:src-tauri/src/fixtures/(archidekt-1.json,cockatrice-tokens.xml,mtgio-muro-de-algas.json,tweakcn-amethyst-haze.json) andsrc-tauri/src/scryfall/fixtures/. -
Temp state. Tests that need an
AppStatebuild one over atempfile::tempdir()(data and cache paths), never the real app folders. -
Tests may unwrap.
#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]only applies outside tests; integration test files add#![allow(clippy::unwrap_used, clippy::expect_used)]. -
Where the volume is. Most tests sit in
proxy-core: the designer (diseno/render.rs,componer.rs,cuerpos.rs,texto.rs,pack.rs,pintar.rs,estructura.rs), layout (layout/plan.rs,grid.rs), decklist parsing and export (decklist/), imaging (imaging/frame.rs,corners.rs,adjust.rs) andstats. In the app crate:scryfall/resolve.rs,commands/cards.rs,commands/stats.rs,render.rs,store/decks.rs,package.rs,commands/export.rs,commands/identify.rs.
src-tauri/src/actualizar.rs has a test, la_configuracion_del_actualizador_es_valida, that reads tauri.conf.json and asserts createUpdaterArtifacts is true, requireSignedVersion is true, the endpoint is a GitHub releases/latest/download/latest.json URL, and the public key decodes to a minisign key with the expected key id. Reason: breaking any of these silently leaves every installed copy without updates. See Releases and Auto-Updates.
Location: src-tauri/crates/proxy-core/tests/.
| File | What |
|---|---|
dorados.rs |
Base cases per style, each rendered with the real engine and compared to a stored PNG in tests/dorados/
|
layouts.rs |
One golden per special Magic layout in tests/dorados/layouts/ (planeswalker, saga, class, case, split, flip, transform, MDFC, battle, meld, dungeon, plane, vanguard...) using real card data saved in tests/dorados/layouts/datos/*.json
|
comun/mod.rs |
Shared helpers: synthetic art (arte(seed)), emblem, symbols drawn locally, and diferencia()
|
Design points:
-
Test assets are generated in code (gradient-and-circles art, a blue oval emblem, coloured-circle symbols) so no copyrighted art is committed. Fonts and the house frame pack come from the real
assets/(the engine constructed byMotor::de_fabrica()). -
Comparison:
diferencia(a, b)returns the percentage of pixels where any channel differs by more than 8/255; sizes that differ count as 100 %. A case fails at 0.5 % or more (dorados_de_los_casos_base). On failure the new render is written to the OS temp directory as<case>-nuevo.png(nuevo-<name>inlayouts.rs) for inspection, and the test lists all failing cases together. - First run creates, later runs compare. If a golden file does not exist it is written and the case passes.
-
Updating goldens:
PB_ACTUALIZAR_DORADOS=1 cargo test -p proxy-core --test dorados(or--test layouts) rewrites them. Only do this when the visual change is intended and approved; review the diff in the PNGs before committing. -
Determinism tests:
dos_renders_iguales_dan_los_mismos_bytesasserts two renders of one case are byte-identical (the PDF and preview rely on determinism);cambiar_un_color_del_pack_cambia_el_doradoproves a pack palette edit changes the output (guards against the pack colours being ignored). -
las_cartas_grandes_salen_a_su_medidachecks large cards (planes, schemes, Vanguard) render at their own size.
Run with cargo test <path> -- --ignored --nocapture. They need local data or the network and are never part of CI.
| Variable | Used by | Meaning |
|---|---|---|
PB_ACTUALIZAR_DORADOS |
tests/dorados.rs, tests/layouts.rs
|
Rewrite goldens instead of comparing |
PB_BALATRO |
tests/comun pack_balatro(), layouts::balatro_matriz, dorados::balatro_*
|
Folder of the Balatro frame pack (the separate packs repository) |
PB_SALIDA |
same | Output folder for contact sheets (defaults to a temp dir) |
PB_ORIGINAL |
dorados::original_con_sus_imagenes, original_colores
|
Folder with the downloaded "Original" frame images |
PB_GF |
dorados::letras_libres_a_ojo, diseno/letras_libres.rs
|
Folder of the free Google Fonts files |
PB_AUTOR |
dorados::balatro_contra_el_autor |
The pack author's reference renders to compare against |
PB_OCR_DATOS, PB_OCR_IMG
|
ocr.rs, originales.rs
|
OCR model folder and a test image |
PB_ID_DIR |
commands/identify.rs |
Folder of images for batch identification |
PB_IMG |
imaging/corners.rs en_vivo
|
An image to measure the corner fill |
PB_DB, PB_DECK
|
scryfall/fichas_auto.rs en_vivo
|
A copy of a library DB and a deck name for token detection |
PB_PDF_DE_PRUEBA |
commands/export.rs de_punta_a_punta
|
Where to leave the end-to-end PDF |
PB_PACKS_LOCAL |
diseno/descargables.rs |
Debug builds only: load downloadable packs from a local folder instead of GitHub (not just a test variable) |
Other #[ignore] examples: live downloads (diseno/original.rs en_vivo, descargables.rs, fuentes/mtgio.rs, fuentes/cockatrice.rs, fuentes/mtgjson.rs, scryfall/client.rs, scryfall/nombres.rs, temas.rs, diseno/traduccion.rs, diseno/borrador.rs).
src-tauri/crates/proxy-core/examples/ (disenar.rs, prototipo.rs) are runnable viewers, not tests: cargo run --release -p proxy-core --example disenar -- <symbols> <arts> <out> [width].
Guards the i18n contract: UI text is written in Spanish inside t("..."), and the English dictionary (src/i18n/en/*.ts, merged in index.ts) is keyed by the Spanish source string. Because t() silently falls back to Spanish when a key is missing, a missing translation would compile and render fine and would only be visible with the app in English; this script is the way to notice. It:
- Reads every
src/i18n/en/*.ts(exceptindex.ts) and collects keys, reporting any key present in two files (the merge would silently let the last one win). - Walks
src/**/*.ts(x)(skippingbindings,node_modules, tests and the dictionary itself) and extracts every string literal int("...")andparaTraducir("..."). - Fails listing each requested string with no English key and the file that asked for it.
Gotchas: only double-quoted literals directly inside t(...) are seen; a t( with a template literal or a variable is not checked. paraTraducir("...") (identity function, src/components/language-provider.tsx) exists to mark texts that live in data tables and are translated elsewhere (for example TEXTOS in src/lib/errores.ts). See Internationalization.
Fails if a .tsx under src/ sets title= on a lowercase HTML tag (except iframe/frame, where it is the accessible name) or on a component that forwards its props to the DOM (PASAN_AL_DOM: Table*, Badge, Label, Button). Reason: on Linux the native title tooltip is drawn by GTK with system colours and looks like a foreign window; swept clean by hand it came back within a day. The right tool is Tooltip (src/components/ui/tooltip.tsx) or WithTooltip. A runtime backstop, TooltipsNativos (src/components/tooltips-nativos.tsx), converts any that slip through. Own components with a title or titulo prop (for example SettingCard) are not flagged because they do not render a native attribute.
Config knip.json: entries are scripts/** and src/**/*.test.{ts,tsx}; project files src/** and scripts/**. Unused exports, types and enum members are off (rules), so Knip is about unused files and dependencies. src/bindings/** is ignored; tw-animate-css and tailwindcss are whitelisted dependencies (consumed from CSS), and the rustfmt binary is whitelisted for the lint-staged config.
.husky/pre-commit runs lint-staged (auto-fix staged files), then, depending on what is staged, tsc --noEmit + idiomas + check:title for TypeScript and check:clippy for Rust. It is intentionally fast; vitest, knip, cargo test and check:biome over the whole repo are left to pnpm check and CI. Details in Development Setup.
Triggers: push to main and every pull_request. One job, checks, on ubuntu-22.04:
-
actions/checkout@v4. - Install Tauri's Linux libraries (
libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf). -
pnpm/action-setup@v4(version frompackageManager),actions/setup-node@v4(Node 22, pnpm cache). -
dtolnay/rust-toolchain@stablewithclippyandrustfmt, andswatinem/rust-cache@v2(workspacesrc-tauri -> target). -
pnpm install --frozen-lockfile. -
pnpm check,pnpm check:rust-fmt,pnpm check:clippy,pnpm test:rust. - Step "Sin OpenSSL en el arbol" (the workflow text is Spanish):
! cargo tree --manifest-path src-tauri/Cargo.toml -i openssl 2>/dev/nullpasses only when the crate is absent from the tree. Why: networking isreqwestwithrustls(default-features = false); OpenSSL would complicate cross-platform builds and installers.
Because the toolchain is latest stable (not pinned), a new Rust release can introduce new clippy warnings and fail CI without any code change; the maintainer runs the latest-stable clippy before sending changes.
Manual only (workflow_dispatch), deliberately not triggered by tags: publishing the draft creates the tag, and a tag trigger rebuilt everything again. Permissions contents: write. A matrix with fail-fast: false:
| Platform | args |
|---|---|
ubuntu-22.04 |
none |
windows-latest |
none |
macos-latest |
--target aarch64-apple-darwin |
macos-latest |
--target x86_64-apple-darwin |
Each leg: checkout, Linux libs (Linux only), pnpm, Node 22, Rust stable (with both Apple targets on macOS), cache, pnpm install --frozen-lockfile, then tauri-apps/tauri-action@v0 with tagName: v__VERSION__, releaseDraft: true, and the signing secrets. It does not run the test gates: scripts/release.mjs waits for the CI run of the release commit before launching it. Full detail in Releases and Auto-Updates.
| Changing | Add a test in |
|---|---|
| Pure logic (parsing, geometry, imaging, stats, designer) |
src-tauri/crates/proxy-core/src/<module>.rs (#[cfg(test)]), or a golden if it is visual |
| A visual change in card rendering | Update goldens deliberately (see above) |
| A Tauri command or store/DB behaviour | The module's #[cfg(test)] in src-tauri/src/..., using a temp AppState
|
| Network parsers | A saved fixture next to the module, plus an #[ignore] live test |
| Frontend store/lib logic |
__tests__/ next to the code, mocking @/lib/ipc
|
| A new UI string | An entry in the matching src/i18n/en/<area>.ts (the gate will tell you) |
Use and build · Uso y desarrollo
- User-Guide · Guía de usuario
- Development-Setup · Preparación del entorno de desarrollo
- Testing-and-CI · Tests y CI
- Releases-and-Auto-Updates · Versiones y actualizaciones automáticas
- Conventions · Convenciones
Architecture · Arquitectura
- Architecture · Arquitectura
- IPC-and-Error-Handling · IPC y manejo de errores
- Rust-App-Layer · Capa de aplicación Rust
Data · Datos
- Deck-Model · Modelo del mazo
- Storage-and-Blobs · Almacenamiento y blobs
- Deck-Package-Format · Formato del paquete de mazo
- Deck-Library · Biblioteca de mazos
Getting cards · Conseguir cartas
- Decklist-Parsing · Parseo de la lista del mazo
- Scryfall-Integration · Integración con Scryfall
- External-Sources · Fuentes externas
- Card-Identification-and-OCR · Identificación de cartas y OCR
Layout, images, PDF · Maquetación, imagen y PDF
- Layout-Engine · Motor de maquetación
- Imaging-and-PDF · Imagen y PDF
- Export · Exportación
- Live-Preview · Vista previa en vivo
- Statistics · Estadísticas
Card designer · Diseñador de cartas
- Designer-Overview · Visión general del diseñador
- Designer-Engine · Motor del diseñador
- Designer-Composition · Composición del diseñador
- Frame-Packs · Packs de marcos
- Designer-App-Layer · Capa de aplicación del diseñador
- Designer-UI · Interfaz del diseñador
- Card-Editor-Dialog · Diálogo Editar carta
Game table · Mesa de juego
Frontend · Frontend
- Frontend-Architecture · Arquitectura del frontend
- State-Stores · Stores de estado
- Internationalization · Internacionalización
- Themes-and-Skins · Temas y skins
- Icons · Iconos
- Settings-and-Shell · Ajustes y shell
- Tutorials · Tutoriales
- Decklist-Panel · Panel de la lista del mazo
- Add-Card-and-Variants · Añadir carta y variantes
- Set-Browser · Explorador de sets
Reference · Referencia