Repository navigation
Tests y CI
English · Español
Qué se prueba, cómo, y qué puerta atrapa cada cosa: Vitest para el frontend y los scripts de Node, cargo test para Rust (incluidos los tests dorados por comparación de imágenes del diseñador de cartas y el crate del motor de la mesa, mesa), los tests propios del servidor de salas y el contrato entre la app y el servidor, los scripts de comprobación propios, Knip, el git hook y los workflows de GitHub Actions.
Las puertas locales y CI ejecutan los mismos comandos, así que un pnpm check en verde más los tres comandos de Rust predicen un CI en verde. La preparación está en Preparación del entorno de desarrollo; las convenciones que hacen cumplir las puertas están en Convenciones.
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["sin OpenSSL: cargo tree -i openssl está vacío"]
CI["job de CI 'checks'"] --> C
CI --> R1
CI --> R2
CI --> R3
CI --> O
| Puerta | Comando | Falla cuando |
|---|---|---|
| Tipos |
pnpm check:tipos (tsc --noEmit) |
Errores de TypeScript, variables locales o parámetros sin usar (strict, noUnusedLocals, noUnusedParameters en tsconfig.json) |
| Tests unitarios |
pnpm test (vitest run) |
Falla un test |
| Lint + formato |
pnpm check:biome (biome check .) |
Error de lint o formato incorrecto (tabuladores, comillas, ancho de línea 100; véase biome.json) |
| Tooltips nativos | pnpm check:title |
Un .tsx pone title= en un elemento HTML o en un componente que lo pasa al DOM |
| Traducciones | pnpm idiomas |
Un idioma de src/lib/idiomas.ts no tiene diccionario, a uno le falta una cadena pasada a t(), una cadena está en dos ficheros de área, o no cuadran los marcadores o las formas de plural (véase abajo) |
| Código muerto |
pnpm check:knip (knip) |
Ficheros o dependencias sin usar |
| Formato de Rust | pnpm check:rust-fmt |
cargo fmt --check informa de diferencias |
| Lints de Rust | pnpm check:clippy |
Cualquier aviso de clippy (-D warnings, todos los targets, todo el workspace), incluidos unwrap/expect fuera de tests |
| Tests de Rust | pnpm test:rust |
Falla cualquier test (esto también ejecuta los tests de exportación de ts-rs, véase Preparación del entorno de desarrollo) |
| Sin OpenSSL | Solo un paso de CI |
cargo tree -i openssl encuentra el crate |
pnpm check ejecuta sus partes en el orden tipos, test, biome, title, idiomas, knip (scripts.check de package.json); title e idiomas se ejecutan con --silent para que solo se vean sus propios mensajes.
Configuración: vitest.config.ts usa el entorno node (sin DOM), el alias @ a src, restoreMocks: true, y recoge src/**/*.test.ts, src/**/*.test.tsx y scripts/**/*.test.mjs. happy-dom es una dependencia de desarrollo, pero el entorno por defecto es node.
Filosofía: los componentes de React no se renderizan en los tests. El código probado es la lógica que se sacó a propósito de los componentes a módulos y stores planos, a los que se da un @/lib/ipc simulado. Patrón típico (véase src/stores/__tests__/actualizacion-store.test.ts): vi.mock("@/lib/ipc", ...) falsea call y on con un switch sobre el nombre del comando y lanza una excepción ante un comando inesperado, y luego comprueba la secuencia de comandos que emitió el store.
| Carpeta | Tests (ficheros) |
|---|---|
src/stores/__tests__/ |
deck-store, deck-store-abrir, ui-store, plan-store, trabajo-store, actualizacion-store, originales-store (conOriginales), vivo-store (cursores y fantasmas de los demás jugadores), mesa-store (aplica los parches que escribe crates/mesa, véase abajo) |
src/lib/__tests__/ |
cartas, deck-ops, dialogos, errores, idiomas (la tabla de idiomas), image-url, imagen-viva, tema-inicial, temas-instalados, trabajos (el ayudante de trabajos que escucha antes de lanzar), cierre (la guarda de cierre: pendientes / hayPendientes con cada razón, altas y bajas de diálogos en el registro, varios diálogos a la vez, un mazo que no se pudo guardar) |
src/i18n/__tests__/ |
traducir (respaldo idioma, inglés, español; formas del plural ruso por n; un n que no es número usa other; números con el formato del idioma de la interfaz; cargarDiccionario) |
src/components/__tests__/ |
emoji (la bandera de cada idioma tiene imagen; pintado a texto con react-dom/server, sin DOM) |
src/features/decklist/__tests__/ |
acciones |
src/features/export/__tests__/ |
acciones |
src/features/library/__tests__/ |
organizar (filtrar, ordenar, agrupar, selección), partidas-deck (porcentaje de victorias) |
src/features/mesa/__tests__/, mesa/dados/__tests__/, mesa/listas/__tests__/
|
La lógica de la pantalla de la mesa de juego (arrastre, atajos, búsqueda, chat, cuentas, daño de comandante, equipos, disposición, sonidos, física y geometría de los dados, elección de lista…); véase Mesa de juego |
src/features/salas/__tests__/ |
enlaces-entrantes (un enlace de invitación que llega se guarda para «Jugar con amigos», o se rechaza con una partida en marcha) |
src/features/preview/__tests__/ |
acciones-carta, arrastrar-ficheros
|
src/features/variants/__tests__/ |
vincular |
src/features/card-editor/__tests__/ |
propagar (aplicar a la carta / copias / todas) |
src/features/disenar/__tests__/ |
estilos, encuadre, grupos, por-cara (estilo y ajustes por cara), renumerar, textos
|
src/features/stats/__tests__/ |
textos |
scripts/__tests__/ |
release-notas.test.mjs (análisis de argumentos, plantilla y limpieza de las notas de la versión, reescritura de las notas de latest.json), idiomas.test.mjs (comprobar: claves que faltan, marcadores cambiados, categorías de plural que faltan o son de otro idioma, plural sin {n}, avisos, una clave repetida en un fichero, un fichero de área que falta) |
Trampa conocida: el comportamiento del scroll y de las animaciones de la interfaz real no lo cubre ningún test automático; hay que probarlo a mano en WebKitGTK (véase Preparación del entorno de desarrollo).
pnpm test:rust ejecuta cargo test --workspace para tres crates (members en src-tauri/Cargo.toml): la app (proxy-builder, biblioteca proxy_builder_lib), el núcleo puro proxy-core y el motor puro de la mesa, mesa. En esta versión hay unas 750 funciones #[test]/#[tokio::test] repartidas en unos 140 ficheros (busca en el árbol para cifras exactas). Dev-dependencies de la app: tempfile, lopdf (inspecciona los PDF generados) y tokio con test-util; proxy-core tiene lopdf.
Reglas visibles en el código:
-
Sin red en la ejecución por defecto. Todo lo que necesite internet real (Scryfall, GitHub, Google Fonts, MTGJSON, Cockatrice, mtg.io) está en
#[ignore]y pensado para ejecuciones manuales. Los parsers se prueban contra fixtures guardados:src-tauri/src/fixtures/(archidekt-1.json,cockatrice-tokens.xml,mtgio-muro-de-algas.json,tweakcn-amethyst-haze.json) ysrc-tauri/src/scryfall/fixtures/. -
Estado temporal. Los tests que necesitan un
AppStatelo construyen sobre untempfile::tempdir()(rutas de datos y de caché), nunca sobre las carpetas reales de la app. -
Los tests pueden usar unwrap.
#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]solo se aplica fuera de los tests; los ficheros de tests de integración añaden#![allow(clippy::unwrap_used, clippy::expect_used)]. -
Dónde está el volumen. La mayoría de los tests están en
proxy-core: el diseñador (diseno/render.rs,componer.rs,cuerpos.rs,texto.rs,pack.rs,pintar.rs,estructura.rs), layout (layout/plan.rs,grid.rs), análisis y exportación de la lista del mazo (decklist/), tratamiento de imagen (imaging/frame.rs,corners.rs,adjust.rs) ystats. En el crate de la app: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 tiene un test, la_configuracion_del_actualizador_es_valida, que lee tauri.conf.json y comprueba que createUpdaterArtifacts es true, que requireSignedVersion es true, que el endpoint es una URL releases/latest/download/latest.json de GitHub y que la clave pública se decodifica como una clave minisign con el id de clave esperado. Motivo: romper cualquiera de estas cosas deja en silencio a todas las copias instaladas sin actualizaciones. Véase Versiones y actualizaciones automáticas.
Ubicación: src-tauri/crates/proxy-core/tests/.
| Fichero | Qué es |
|---|---|
dorados.rs |
Casos base por estilo, cada uno renderizado con el motor real y comparado con un PNG guardado en tests/dorados/; además dorados_en_otros_idiomas (idioma-fr.png, idioma-ru.png, idioma-ro.png: latín con diacríticos, cirílico y un idioma sin cartas impresas; comprueba que no sale FaltanLetras y que SinTextoOficial solo sale en rumano) |
layouts.rs |
Un dorado por cada maquetación especial de Magic en tests/dorados/layouts/ (planeswalker, saga, clase, case, split, flip, transform, MDFC, batalla, meld, mazmorra, plano, vanguard...) usando datos reales de cartas guardados en tests/dorados/layouts/datos/*.json
|
comun/mod.rs |
Ayudantes compartidos: ilustración sintética (arte(seed)), emblema, símbolos dibujados en local y diferencia()
|
compatibilidad/diseno-2.5.9.json |
Un diseño tal como lo guardaba la versión 2.5.9, para los tests de compatibilidad de abajo |
Puntos de diseño:
-
Los recursos de los tests se generan en código (ilustración de degradado y círculos, un emblema ovalado azul, símbolos de círculos de colores) para no commitear arte con derechos de autor. Las letras y el pack de marcos de la casa salen de los
assets/reales (el motor se construye conMotor::de_fabrica()). -
Comparación:
diferencia(a, b)devuelve el porcentaje de píxeles en los que algún canal difiere en más de 8/255; los tamaños distintos cuentan como 100 %. Un caso falla con 0,5 % o más (dorados_de_los_casos_base). Si falla, el nuevo renderizado se escribe en el directorio temporal del sistema como<caso>-nuevo.png(nuevo-<nombre>enlayouts.rs) para inspeccionarlo, y el test lista juntos todos los casos fallidos. - La primera ejecución crea, las siguientes comparan. Si no existe un fichero dorado, se escribe y el caso pasa.
-
Actualizar los dorados:
PB_ACTUALIZAR_DORADOS=1 cargo test -p proxy-core --test dorados(o--test layouts) los reescribe. Hazlo solo cuando el cambio visual sea intencionado y esté aprobado; revisa la diferencia en los PNG antes de commitear. -
Tests de determinismo:
dos_renders_iguales_dan_los_mismos_bytescomprueba que dos renderizados de un mismo caso son idénticos byte a byte (el PDF y la vista previa dependen del determinismo);cambiar_un_color_del_pack_cambia_el_doradodemuestra que editar la paleta de un pack cambia el resultado (protege contra que se ignoren los colores del pack). -
las_cartas_grandes_salen_a_su_medidacomprueba que las cartas grandes (planos, planes, Vanguard) se renderizan a su propio tamaño.
Añadir idiomas no puede cambiar lo que ya son los diseños en español e inglés, ni en disco ni en pantalla. Lo fijan tres tests:
-
diseno::efectivo::tests::las_huellas_en_espanol_e_ingles_son_las_de_antes: huellas de render (huella) fijas, sacadas de la 2.5.9, en español e inglés. Si cambiaran, se volverían a pintar todas las cartas diseñadas. -
diseno::modelo::tests::un_diseno_de_antes_se_reescribe_igual:tests/compatibilidad/diseno-2.5.9.jsonse lee y se vuelve a escribir idéntico byte a byte (guardar un deck en esta versión no reescribe los diseños de antes). - En el mismo módulo,
otro_idioma_se_guarda_tras_es_y_en_y_lo_lee_la_version_de_antes: un diseño con otro idioma dejaesyenprimero, una estructura antigua de dos campos lo sigue leyendo y una clave de un idioma desconocido se ignora.
Véase Motor del diseñador para ByLang y la huella.
Se ejecutan con cargo test <ruta> -- --ignored --nocapture. Necesitan datos locales o la red y nunca forman parte de CI.
| Variable | Usada por | Significado |
|---|---|---|
PB_ACTUALIZAR_DORADOS |
tests/dorados.rs, tests/layouts.rs
|
Reescribir los dorados en vez de compararlos |
PB_CJK |
dorados::dorado_en_japones |
Ruta de un fichero de Noto Sans JP (la app la baja aparte, así que no está en el repo); compara con idioma-ja.png. Sin la letra, el render lleva FaltanLetras. |
PB_BALATRO |
tests/comun pack_balatro(), layouts::balatro_matriz, dorados::balatro_*
|
Carpeta del pack de marcos Balatro (el repositorio de packs aparte) |
PB_SALIDA |
igual | Carpeta de salida de las hojas de contacto (por defecto, un directorio temporal) |
PB_ORIGINAL |
dorados::original_con_sus_imagenes, original_colores
|
Carpeta con las imágenes de marcos «Original» descargadas |
PB_GF |
dorados::letras_libres_a_ojo, diseno/letras_libres.rs
|
Carpeta de los ficheros de Google Fonts libres |
PB_AUTOR |
dorados::balatro_contra_el_autor |
Los renderizados de referencia del autor del pack con los que comparar |
PB_OCR_DATOS, PB_OCR_IMG
|
ocr.rs, originales.rs
|
Carpeta de modelos de OCR y una imagen de prueba |
PB_ID_DIR |
commands/identify.rs |
Carpeta de imágenes para la identificación en lote |
PB_IMG |
imaging/corners.rs en_vivo
|
Una imagen con la que medir el relleno de esquinas |
PB_DB, PB_DECK
|
scryfall/fichas_auto.rs en_vivo
|
Una copia de una base de datos de la biblioteca y un nombre de mazo para la detección de fichas |
PB_PDF_DE_PRUEBA |
commands/export.rs de_punta_a_punta
|
Dónde dejar el PDF de punta a punta |
PB_PACKS_LOCAL |
diseno/descargables.rs |
Solo compilaciones de depuración: carga los packs descargables desde una carpeta local en vez de GitHub (no es solo una variable de test) |
PB_DATOS, PB_CACHE
|
mesa/ejemplo.rs deck_de_ejemplo, deck_grande_de_ejemplo
|
Las carpetas de datos y de caché de la app: escribe en esa biblioteca un deck de ejemplo con cartas diseñadas (sin Scryfall), para probar la mesa de juego |
PB_PRUEBA_SERVIDOR, PB_PRUEBA_RELE
|
red/servidor/cliente.rs |
Un servidor de salas desplegado (wss://…/v1/ws) y su relé (véase «Tests del juego en línea», abajo) |
Otras dos variables no son de tests ignorados, sino que reescriben datos guardados en una ejecución normal: PB_FIXTURES=1 (los fixtures del protocolo y del servidor de src-tauri/src/red/ y los parches de crates/mesa) y PB_CATALOGO_ESCRIBIR=1 (la instantánea del catálogo de crates/mesa). Las dos se explican más abajo.
Otros ejemplos de #[ignore]: descargas en vivo (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) son visores ejecutables, no tests: cargo run --release -p proxy-core --example disenar -- <symbols> <arts> <out> [width].
Ubicación: src-tauri/crates/mesa/tests/, con cargo test -p mesa (también entra en pnpm test:rust). El crate se compila con opt-level = 2 en desarrollo para que los tests de propiedad tarden segundos.
| Fichero | Qué es |
|---|---|
acciones.rs |
Cada acción del catálogo (antes → evento → después). Un match exhaustivo (nombre) y la lista TODAS fallan si una variante nueva de Accion no tiene test |
propiedad.rs |
1.000 intenciones al azar (misma semilla → misma huella, las rechazadas no cambian nada), la visibilidad para cada mirador tras cada acción con los parches iguales a la vista entera, 2.000 eventos arbitrarios que nunca hacen panic
|
registro.rs |
Deshacer, rehacer, deshacer hasta una línea, permisos de deshacer en línea, invitados que entran tarde, repetición en cualquier seq, mulligan, huella; además busca en el código HashMap<, SystemTime, Instant::, rand::
|
preparar.rs |
Secciones del deck para la mesa y el reparto |
turnos.rs |
El orden de los turnos por asientos desde el primer jugador, saltando a los eliminados; la ronda sube al pasar por el primero |
equipos.rs |
Equipos: quién cuenta como oponente, la vida compartida |
formatos.rs |
Formatos multijugador: los valores de fábrica, Archenemy, Planechase, el daño de comandante |
muerte.rs |
Quién está fuera de juego (vida, veneno, daño de comandante, con la vida compartida de los equipos) y que la vista lo cuente |
encuentro.rs |
Encuentros: el marcador y el cambio de cartas con el banquillo |
resumen.rs |
El resumen de la partida: puestos por orden de eliminación, empates, concesiones, equipos |
sparring.rs |
El sparring con partidas simuladas (un guion de jugadas: una tierra por ronda, los hechizos que dé el maná, pasar) con cada intensidad: cada una cae en su banda, apagado nunca reacciona, la misma semilla da las mismas reacciones, resolver y deshacer lo devuelve todo, nunca quita tierras básicas |
catalogo.rs |
Las instantáneas del catálogo (véase abajo) |
comun/mod.rs |
Decks y partidas de ejemplo, un generador de intenciones al azar y sin_fugas (las reglas de visibilidad escritas otra vez a mano) |
fixtures.rs |
Juega una partida de ejemplo, comprueba en Rust que los parches sobre la primera vista dan la vista final y, con PB_FIXTURES=1 cargo test -p mesa --test fixtures, escribe src/stores/__tests__/fixtures/mesa-parches.json (una primera vista, 60 parches y la vista final). src/stores/__tests__/mesa-store.test.ts los aplica en TypeScript y tiene que llegar a la vista final, así Rust y el store de la pantalla coinciden en los parches. Nada compara el fichero guardado con lo que sale ahora de Rust, así que hay que reescribirlo después de cambiar la vista o los parches |
Algunos módulos del motor tienen además tests unitarios en src/ (azar.rs, lista.rs, formato.rs, ids.rs, sparring/amenaza.rs, sparring/cartas.rs).
Los eventos (Evento, con su Cambio) son lo que guarda el registro de la partida, lo que se manda a los demás jugadores y lo que se repite, así que una repetición o una partida guardada tiene que seguir leyéndose en versiones posteriores. La regla, escrita en crates/mesa/src/accion.rs: las variantes de Accion y Cambio nunca se cambian ni se quitan, y una nueva o cambiada sube VERSION_CATALOGO (ahora 1), porque un enum de serde rechaza una etiqueta que no conoce y la red y las repeticiones guardadas comparan la versión.
tests/catalogo.rs (los_eventos_de_cada_version_se_siguen_leyendo) lee cada tests/catalogo/v1.json … v<VERSION_CATALOGO>.json y falla si falta uno o si ya no se puede leer. Así que, tras subir la versión, se escribe la instantánea nueva una sola vez con PB_CATALOGO_ESCRIBIR=1 cargo test -p mesa --test catalogo (unos 120 eventos al azar de una partida de dos jugadores más una línea de chat, sin las restauraciones de la partida entera) y no se vuelve a tocar. Sin ese fichero el test falla, y ese es el recordatorio.
tests/catalogo/ queda fuera de Biome (biome.json), como proxy-core/tests/compatibilidad, y también src/stores/__tests__/fixtures.
La capa de la app y la pantalla de la mesa de juego tienen sus propios tests (src-tauri/src/mesa/, protocol.rs::las_rutas_de_la_mesa, src/features/mesa/__tests__/): véase Mesa de juego.
-
src-tauri/src/red/pruebas.rs: partidas en el mismo proceso sobre iroh sin relés (direcciones locales), cada jugador con su sesión en memoria: tres jugadores y 200 jugadas al azar acaban con la misma huella; un invitado que se corta vuelve con la misma partida; un código malo, otra versión o (como jugador) una partida empezada se rechazan; si el host se cae, el siguiente jugador por asiento sigue solo y los demás lo encuentran; con dos jugadores, el que queda espera sin jugar; un host que pierde la red se para y luego se une al nuevo como invitado; y un espectador sigue mirando con el host nuevo (traspaso del anfitrión sin servidor,red/sucesion.rs, con la unidad de tiempo a 40 ms); los espectadores nunca ven la identidad de lo oculto, y solo les llega el movimiento de lo que ven; las imágenes propias llegan a un invitado por el host y una huella cambiada se tira; cada jugador sabe su latencia y si va directo; el reloj del host se calcula aunque el de aquí vaya atrasado; el fantasma de un invitado llega al host y a los demás con su nombre; quien se va aparece como ausente y deja de estarlo al volver; el host se queda la mano de un ausente que no decide; un expulsado queda eliminado, se le corta y no puede volver. -
red/protocolo.rs: topes de las tramas, elHolaprimero, cada mensaje de ida y vuelta, JSON y decks rotos, datagramas grandes o rotos, y los ejemplos para otros clientes ensrc-tauri/src/red/fixtures/(al-host.json,del-host.json,datagramas.json), que reescribePB_FIXTURES=1 cargo test --lib escribir_fixtures.
El servidor de salas (playgroups, invitaciones, salas, elección del host; la partida sigue siendo P2P) es un repositorio aparte, clonado dentro del checkout de la app en server/ e ignorado por el git de la app (.gitignore), así que el Biome de la app tampoco lo mira (vcs.useIgnoreFile). Su protocolo v1 está en server/docs/protocolo.md. Los dos lados guardan mensajes de ejemplo y cada uno comprueba los del otro:
flowchart LR
A["app: red/servidor/mensajes.rs"] -->|"escribe (PB_FIXTURES=1)"| F["src-tauri/src/red/servidor/fixtures/al-servidor.json"]
F -->|"server/test/contrato.test.ts"| S["esquemas zod del servidor (src/protocolo.ts)"]
E["server/test/ejemplos/al-servidor.json, del-servidor.json"] -->|"contrato.test.ts"| S
E -->|"los_ejemplos_del_servidor_se_entienden"| A
| Lado | Test | Comprueba |
|---|---|---|
| App |
red/servidor/mensajes.rs los_mensajes_de_la_app_son_los_de_los_fixtures
|
Un ejemplo de cada mensaje que manda la app es igual a fixtures/al-servidor.json y se lee de vuelta igual. Tras cambiar un mensaje, se reescribe con PB_FIXTURES=1 cargo test --lib los_mensajes_de_la_app
|
| App | los_ejemplos_del_servidor_se_entienden |
Cada mensaje de server/test/ejemplos/del-servidor.json se lee, se escribe de nuevo idéntico (no se pierde ningún campo) y hay 11 tipos de mensaje. Sin server/ avisa y pasa |
| App |
red/servidor/cliente.rs crea_un_playgroup_invita_y_otra_identidad_entra
|
Arranca el servidor de verdad con server/node_modules/.bin/tsx src/index.ts en un puerto libre (PB_PUERTO) con la base de datos en memoria (PB_DB=:memory:); dos identidades se conectan, una crea un playgroup y una invitación de un solo uso, la otra entra (un segundo uso falla con caducada), se crea una sala y se entra en ella, y llega el aviso del playgroup. Sin server/node_modules se salta |
| App, ignorado | el_rele_solo_deja_pasar_a_quien_esta_en_el_servidor |
Contra un despliegue (PB_PRUEBA_SERVIDOR, PB_PRUEBA_RELE): wss:// a través de nginx, y el relé propio solo deja entrar a una clave conectada al servidor |
| Servidor | test/contrato.test.ts |
Los ejemplos de test/ejemplos/ pasan los esquemas zod y hay uno de cada tipo; el al-servidor.json de la app también pasa y cubre todos los tipos (se salta cuando el servidor está clonado solo). pnpm protocolo:comprobar ejecuta solo este fichero |
Como el CI solo hace checkout del repositorio de la app, allí no hay server/ y los tests de la app contra el servidor pasan saltándose: el contrato solo se comprueba de verdad en una máquina con el servidor clonado (y con pnpm install hecho dentro, para cliente.rs).
El servidor tiene sus propios scripts y ningún workflow de CI: pnpm check es tsc --noEmit && vitest run && biome check ., y pnpm test es vitest run (test/**/*.test.ts y src/**/*.test.ts, entorno Node). Necesita Node 22.13 o posterior (node:sqlite). Ficheros:
| Fichero | Qué es |
|---|---|
identidad.test.ts |
Entrada por firma: con la firma buena, bienvenido; la firma de otro reto, una clave mal escrita o un apodo largo no entran; otra versión del protocolo se rechaza y tres mensajes malos cierran; como mucho tres conexiones por clave (la cuarta cierra la más antigua) |
playgroups.test.ts |
El dueño invita, se entra por el código, los permisos; las invitaciones caducan y tienen tope de usos (reloj falso); los límites de 20 invitaciones vivas y 100 miembros; los códigos se leen sin importar mayúsculas, guiones, I/L ni O |
salas.test.ts |
Con sockets de verdad: tres se juntan por un código y ven las direcciones, el undécimo no cabe; tras reiniciar el servidor, la sala vuelve con el epoch que traen los clientes; el callout del relé solo deja pasar a una clave conectada o en una sala; los playgroups avisan de sus salas y de quién está en línea; quien creó la sala expulsa a otro, que sale y no vuelve |
presencia.test.ts |
Migración del host (reloj falso): un host cortado se cambia por otro con epoch 2 antes de 10 s; un corte breve dentro de la gracia no cambia nada; un solo aviso con el host vivo no cambia nada; un host que no contesta al sondeo se cambia; dos avisos durante 10 s lo cambian |
eleccion.test.ts |
Elección del host: gana el de menor peor RTT, un camino por relé cuenta ×1,5, un móvil con 30 ms pierde frente a un ordenador con 60 ms, las filas que faltan cuentan 1.000 ms, el empate lo decide la media y luego el orden |
contrato.test.ts |
El contrato de arriba |
Vigila el contrato de i18n: el texto de la interfaz se escribe en español dentro de t("..."), y cada diccionario (src/i18n/<código>/<área>.json, fundido por cargar.ts) se indexa por la cadena fuente en español. Como t() cae en silencio a un respaldo (el idioma, luego el inglés, luego la fuente en español), una traducción que falta compila y se pinta bien y solo se vería con la app en ese idioma; este script es la forma de enterarse. Hace esto:
- Reúne las cadenas pedidas: cada literal entre comillas dobles de
t("...")yparaTraducir("...")ensrc/**/*.ts(x)(saltabindings,node_modules, las carpetas que empiezan por punto, los*.test.ts(x)y el propiosrc/i18n/). - Lee la tabla de idiomas de
src/lib/idiomas.ts, que es código y por eso se transpila contypescript. Luego carga cada carpeta de diccionario parseando sus.jsonconJSON.parse(los plurales llegan como datos reales) y revisa su texto en bruto por si una clave se repite en el mismo fichero, cosa queJSON.parseesconde. Las carpetas que empiezan por_(como__tests__) se saltan. - En cada idioma salvo el español, falla si: falta su carpeta; una cadena pedida no tiene entrada; una clave está en dos áreas (al fundir ganaría la última en silencio) o repetida en un mismo fichero; falta un fichero de área del diccionario inglés; los marcadores
{x}no son los de la clave española (también en cada forma de un plural); hay un plural en una clave sin{n}; o a un plural le falta, o le sobra, una categoría deIntl.PluralRules(locale)de su idioma. Una carpeta que no es de ningún idioma de la tabla también es un error. El español (la fuente,es/) se comprueba con su propia regla: tiene las mismas áreas y claves que el inglés, y cada valor debe ser igual a su clave. -
Avisa (sin fallar) de las entradas que nadie pide y de los valores idénticos al inglés (sin contar los nombres de marca de
IDENTICAS_PERMITIDASdel script ni las claves de unsrc/i18n/<código>/identicas.jsonopcional).--detallelista cada aviso; si no, solo sale cuántos hay.
Sus partes puras (leerPedidas, leerTabla, leerDiccionarios, comprobar) se exportan y se prueban en scripts/__tests__/idiomas.test.mjs.
Trampas: solo se ven los literales entre comillas dobles directamente dentro de t(...); un t( con una plantilla literal o una variable no se comprueba. paraTraducir("...") (función identidad, src/components/language-provider.tsx) existe para marcar textos que viven en tablas de datos y se traducen en otro sitio (por ejemplo TEXTOS en src/lib/errores.ts). Véase Internacionalización.
Falla si un .tsx bajo src/ pone title= en una etiqueta HTML en minúsculas (salvo iframe/frame, donde es el nombre accesible) o en un componente que reenvía sus props al DOM (PASAN_AL_DOM: Table*, Badge, Label, Button). Motivo: en Linux el tooltip nativo de title lo dibuja GTK con los colores del sistema y parece una ventana ajena; barridos a mano, volvieron en un día. La herramienta correcta es Tooltip (src/components/ui/tooltip.tsx) o WithTooltip. Una red de seguridad en ejecución, TooltipsNativos (src/components/tooltips-nativos.tsx), convierte los que se cuelen. Los componentes propios con una prop title o titulo (por ejemplo SettingCard) no se señalan porque no renderizan un atributo nativo.
Configuración en knip.json: las entradas son scripts/**/*.{ts,mjs,js} y src/**/*.test.{ts,tsx}; los ficheros del proyecto, src/**/*.{ts,tsx} y scripts/**/*.{ts,mjs,js}. Los exports, tipos, miembros de enum y duplicados sin usar están desactivados (rules), así que Knip va de ficheros y dependencias sin usar. src/bindings/** se ignora; tw-animate-css y tailwindcss son dependencias en la lista blanca (se consumen desde CSS), y los binarios rustfmt (configuración de lint-staged) e inkscape (scripts/emoji-noto.mjs) están en la lista blanca.
.husky/pre-commit ejecuta lint-staged (arregla automáticamente los ficheros en staged) y luego, según lo que haya en staged, tsc --noEmit + idiomas + check:title para TypeScript y check:clippy para Rust. Es rápido a propósito; vitest, knip, cargo test y check:biome sobre todo el repo se dejan para pnpm check y CI. Detalles en Preparación del entorno de desarrollo.
Disparadores: push a main y cada pull_request. Un solo job, checks, en ubuntu-22.04:
-
actions/checkout@v4. - Instalar las bibliotecas de Linux de Tauri (
libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf) conawalsh128/cache-apt-pkgs-action@v1, que guarda en caché los.deb: el mirror de Ubuntu a veces baja a unos 50 kB/s y esos 55 MB tardaban hasta 20 minutos, cuando el resto del CI tarda unos 3. (release.ymlsigue usando unapt-get installnormal.) -
pnpm/action-setup@v4(versión tomada depackageManager),actions/setup-node@v4(Node 22, caché de pnpm). -
dtolnay/rust-toolchain@stableconclippyyrustfmt, yswatinem/rust-cache@v2(workspacesrc-tauri -> target). -
pnpm install --frozen-lockfile. -
pnpm check,pnpm check:rust-fmt,pnpm check:clippy,pnpm test:rust. - Paso «Sin OpenSSL en el árbol» (el texto del workflow está en español):
! cargo tree --manifest-path src-tauri/Cargo.toml -i openssl 2>/dev/nullsolo pasa cuando el crate no está en el árbol. Por qué: la red esreqwestconrustls(default-features = false); OpenSSL complicaría las compilaciones multiplataforma y los instaladores.
Como el toolchain es la última stable (no fijada), una nueva versión de Rust puede introducir avisos nuevos de clippy y hacer fallar CI sin ningún cambio de código; el mantenedor ejecuta el clippy de la última stable antes de enviar cambios.
Solo manual (workflow_dispatch), a propósito sin disparar por etiquetas: publicar el borrador crea la etiqueta, y un disparador por etiqueta lo reconstruía todo otra vez. Permisos contents: write. Una matriz con fail-fast: false:
| Plataforma | args |
|---|---|
ubuntu-22.04 |
ninguno |
windows-latest |
ninguno |
macos-latest |
--target aarch64-apple-darwin |
macos-latest |
--target x86_64-apple-darwin |
Cada entrada de la matriz: checkout, bibliotecas de Linux (solo en Linux), pnpm, Node 22, Rust stable (con los dos targets de Apple en macOS), caché, pnpm install --frozen-lockfile, y después tauri-apps/tauri-action@v0 con tagName: v__VERSION__, releaseDraft: true y los secretos de firma. No ejecuta las puertas de tests: scripts/release.mjs espera a la ejecución de CI del commit de la versión antes de lanzarlo. Todo el detalle en Versiones y actualizaciones automáticas.
| Si cambias | Añade un test en |
|---|---|
| Lógica pura (análisis, geometría, imagen, estadísticas, diseñador) |
src-tauri/crates/proxy-core/src/<módulo>.rs (#[cfg(test)]), o un dorado si es visual |
| Un cambio visual en el renderizado de cartas | Actualiza los dorados de forma deliberada (véase arriba) |
| Un comando de Tauri o el comportamiento del store/BD | El #[cfg(test)] del módulo en src-tauri/src/..., usando un AppState temporal |
| Parsers de red | Un fixture guardado junto al módulo, más un test en vivo con #[ignore]
|
Una acción de la mesa de juego (variante de Accion/Cambio) |
Su caso en crates/mesa/tests/acciones.rs (sin él el match exhaustivo no compila), más subir VERSION_CATALOGO y su tests/catalogo/v<N>.json
|
| Un mensaje del servidor de salas | Los ejemplos() de la app en red/servidor/mensajes.rs y PB_FIXTURES=1 para reescribir al-servidor.json; en el servidor, test/ejemplos/ y su esquema (contrato.test.ts comprueba que hay un ejemplo de cada tipo) |
| Lógica de stores/lib del frontend |
__tests__/ junto al código, simulando @/lib/ipc
|
| Una cadena nueva de la interfaz | Una entrada en el src/i18n/<código>/<área>.json correspondiente de cada idioma (la puerta te lo dirá) |
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
- 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