Documentación completa del plan de pruebas, cobertura y bugs detectados para el
proyecto github-profile-header-generator v2.1.0.
- Stack de pruebas
- Estructura de archivos
- Cómo ejecutar las pruebas
- Resultados de cobertura
- Bugs encontrados
- Generación de datos de prueba y oráculos
- Pruebas E2E (Playwright)
- Análisis estático (ESLint)
- Pruebas de mutación (Stryker)
- Limitaciones conocidas del entorno
| Herramienta | Versión | Propósito |
|---|---|---|
| Vitest | ^3.2 | Runner de tests unitarios e integración |
| jsdom | ^26.1 | Simulación de DOM para Vitest |
| @vitest/coverage-v8 | ^3.2 | Cobertura de código (V8 provider) |
| @playwright/test | ^1.52 | Pruebas E2E en navegadores reales |
| @axe-core/playwright | ^4.10 | Accesibilidad automática (E2E) |
| @stryker-mutator/core | ^8.7 | Pruebas de mutación |
| ESLint | ^9.28 | Análisis estático |
| eslint-plugin-security | — | Reglas de seguridad OWASP |
| @faker-js/faker | ^9.8 | Generación de datos de prueba |
| Zod | ^3.24 | Oráculos / validación de esquemas |
tests/
├── setup.js # Fixture DOM completo para Vitest+jsdom
├── utils/
│ ├── oracle.js # Oráculos Zod: PresetSchema, ThemeSchema, etc.
│ └── testData.js # Generadores Faker: colores, presets, patrones
├── unit/
│ ├── getPattern.test.js # 70 tests — función getPattern() y variantes
│ └── presets.test.js # 21 tests — carga y validación de presets.json
├── integration/
│ ├── banner.test.js # 27 tests — updateBanner, applyTheme, updateUIOptions
│ └── localStorage.test.js # 16 tests — saveTheme, getSavedThemeProp, ciclos
└── e2e/
├── banner-customization.spec.ts # Flujos de personalización de banner
├── accessibility.spec.ts # Accesibilidad con axe-core
└── performance.spec.ts # Métricas de rendimiento (CLS, LCP, FCP)
.github/prompts/
└── playwright-qa-engineer.prompt.md # Skill de Copilot para pruebas E2E
vitest.config.js # Configuración Vitest + cobertura
playwright.config.ts # Configuración Playwright (4 browsers, URL de producción)
stryker.config.mjs # Configuración Stryker (runner: vitest)
eslint.config.js # Configuración ESLint con eslint-plugin-security
# Instalar todas las dependencias (incluye devDependencies de prueba)
npm install
# ── Pruebas unitarias e integración ──────────────────────────────────────────
npm test # Ejecutar una vez y ver resultados
npm run test:watch # Modo observador (re-ejecuta al guardar)
npm run test:coverage # Ejecutar con reporte de cobertura HTML + texto
# ── Pruebas E2E (requiere navegadores instalados) ────────────────────────────
npx playwright install # Instalar browsers (solo la primera vez)
npm run test:e2e # Ejecutar E2E en todos los browsers configurados
npm run test:e2e:ui # Abrir interfaz visual de Playwright
npm run test:e2e:report # Abrir reporte HTML de última ejecución E2E
# ── Análisis estático ────────────────────────────────────────────────────────
npm run lint # ESLint con reglas de seguridad
# ── Pruebas de mutación ──────────────────────────────────────────────────────
npm run test:mutation # Stryker sobre js/presets.js y js/helpers/helpers.jsCobertura sobre archivos con lógica de negocio testeable
(los módulos de UI pura —main.js, toolbox-*.js— se excluyen porque
requieren interacción real con el navegador y se cubren con E2E).
| Archivo | Sentencias | Ramas | Funciones | Líneas |
|---|---|---|---|---|
js/banner.js |
69% | 70% | 100% | 69% |
js/presets.js |
100% | 100% | 100% | 100% |
js/helpers/helpers.js |
100% | 50% | 100% | 100% |
js/helpers/elements.js |
100% | 100% | 100% | 100% |
| Total | 74% | 72% | 100% | 74% |
Umbrales configurados (en vitest.config.js): líneas ≥ 70%, ramas ≥ 60%, funciones ≥ 80%.
Líneas no cubiertas en banner.js: ramas de updateUIOptions para patternSize,
patternColor/patternOpacity y titleFont/subtitleFont (líneas ~220–249).
Estas ramas requieren elementos del DOM de la toolbox que no están en el fixture de tests
unitarios/integración.
Los tests de esta suite documentan y detectan los siguientes bugs en el
código fuente. Los tests que los documentan pasan intencionalmente verificando
el comportamiento incorrecto actual y están marcados con [BUG #N].
| Campo | Detalle |
|---|---|
| Archivo | js/banner.js — función applyTheme() |
| Línea aprox. | 110 |
| Severidad | 🔴 CRÍTICO |
Descripción:
La condición if (pattern || patternColor || patternOpacity || patternSize) activa
el bloque cuando cualquiera de los cuatro es truthy. Dentro del bloque se llama
incondicionalmente a patternColor.replace('#', ''), lo que lanza un TypeError
si patternColor es undefined (p.ej., cuando solo se actualiza patternSize).
// Reproducción del bug:
updateBanner({ patternSize: 20 });
// → TypeError: Cannot read properties of undefined (reading 'replace')Corrección sugerida:
bannerImage.style.backgroundImage = getPattern(
pattern,
patternColor ? patternColor.replace("#", "") : "",
patternOpacity,
);| Campo | Detalle |
|---|---|
| Archivo | js/banner.js — función getSavedThemeProp() |
| Línea aprox. | 258 |
| Severidad | 🟡 MEDIO |
Descripción:
Cuando no existe un tema guardado en localStorage, la función retorna la cadena
hardcodeada 'bubbles' para cualquier propiedad solicitada, incluyendo
borderSize, titleColor, borderRadius, etc.
// Comportamiento actual (con localStorage vacío):
getSavedThemeProp("borderSize"); // → 'bubbles' ❌
getSavedThemeProp("titleColor"); // → 'bubbles' ❌
getSavedThemeProp("background"); // → 'bubbles' ❌ (esperado: null o undefined)Corrección sugerida:
if (!theme) return null; // o retornar un mapa de defaults por propiedad| Campo | Detalle |
|---|---|
| Archivo | js/banner.js — función updateUIOptions() |
| Línea aprox. | 187–205 |
| Severidad | 🟢 BAJO |
Descripción:
Los bloques if (textAlign), if (decoration) y if (pattern) están vacíos
en updateUIOptions. Cuando se restaura un tema guardado, los controles del
toolbox para alineación de texto, decoración y patrón no se actualizan visualmente.
| Campo | Detalle |
|---|---|
| Archivo | js/banner.js — función applyTheme() |
| Línea aprox. | 96 |
| Severidad | 🟢 BAJO |
Descripción:
La condición if (borderColor && borderSize && borderRadius) falla cuando
borderRadius es 0 (borde sin redondeo), porque 0 es falsy en JavaScript.
Resultado: un tema con borderRadius: 0 (esquinas cuadradas) nunca aplica
el borde completo.
applyTheme({ borderColor: "#000", borderSize: 2, borderRadius: 0 });
// → bannerImage.style.border === '' ❌ (borde no aplicado)Corrección sugerida:
if (borderColor && borderSize && borderRadius !== undefined && borderRadius !== null) { ... }| Campo | Detalle |
|---|---|
| Archivo | js/banner.js — función applyTheme() |
| Línea aprox. | 129 |
| Severidad | 🟢 BAJO |
Descripción:
La condición if (title || subtitle) impide limpiar el título o subtítulo
enviando una cadena vacía (''), ya que '' es falsy.
updateBanner({ title: "" });
// → bannerTitle.innerText sin cambios ❌Corrección sugerida:
if (title !== undefined || subtitle !== undefined) { ... }| Campo | Detalle |
|---|---|
| Archivo | js/data/presets.json — índice 22 |
| Línea aprox. | ~230 |
| Severidad | 🟡 MEDIO |
Descripción:
El preset en el índice 22 tiene los campos decorationSize, titleFontSize y
subtitleFontSize como strings en lugar de números. Esto puede causar errores
al usarlos en cálculos o interpolaciones:
// Incorrecto (preset #22):
"decorationSize": "77",
"titleFontSize": "44",
"subtitleFontSize": "18"
// Correcto (resto de presets):
"decorationSize": 77,
"titleFontSize": 44,
"subtitleFontSize": 18Detectado por: Oráculo PresetSchema (Zod) en tests/unit/presets.test.js.
| Campo | Detalle |
|---|---|
| Archivo | js/banner.js — función getSavedThemeProp() |
| Línea aprox. | 257 |
| Severidad | 🟢 BAJO |
Descripción:
JSON.parse(localStorage.getItem('theme')) se ejecuta sin bloque try/catch.
Si el contenido de localStorage está corrupto o fue modificado por un script
externo, la aplicación lanzará un SyntaxError no manejado.
Corrección sugerida:
function getSavedThemeProp(prop) {
if (!prop) return null;
try {
const theme = JSON.parse(localStorage.getItem("theme"));
if (!theme) return null;
return theme[prop];
} catch {
return null;
}
}Utilizan @faker-js/faker para crear datos realistas y reproducibles:
| Función | Descripción |
|---|---|
generateHexColor() |
Color hexadecimal aleatorio |
generatePatternName() |
Nombre de patrón válido del catálogo |
generatePreset() |
Preset completo con todos los campos |
generatePresets(n) |
Lista de n presets aleatorios |
generatePatternTestCases() |
Casos de prueba para getPattern() |
generatePartialTheme() |
Tema parcial con campos aleatorios |
Utilizan Zod para validar contratos de datos:
| Esquema | Valida |
|---|---|
PresetSchema |
Estructura completa de un preset (tipos, rangos) |
ThemeSchema |
Estructura de un tema guardado en localStorage |
PatternResultSchema |
Resultado de getPattern() (string o 'none') |
validateAllPresets(presets) |
Valida todos los presets y devuelve errores |
El oráculo detectó BUG #5 automáticamente al validar todos los presets contra el esquema esperado.
Las pruebas E2E se ejecutan contra la URL de producción:
https://leviarista.github.io/github-profile-header-generator/
- Browsers: Chromium, Firefox, WebKit (Safari), Mobile Chrome
- Timeout: 30 000 ms por prueba
- Reintentos: 2 en CI, 0 en local
| Archivo | Descripción |
|---|---|
banner-customization.spec.ts |
Carga de la app, cambio de colores, selección de presets, descarga de imagen |
accessibility.spec.ts |
Escaneo de accesibilidad con axe-core en todas las secciones |
performance.spec.ts |
CLS, LCP, FCP usando Performance Observer nativo |
npx playwright install # Solo la primera vez
npm run test:e2e # Todos los browsers
npm run test:e2e -- --project=chromium # Solo Chromium
npm run test:e2e:ui # Interfaz visual interactiva
npm run test:e2e:report # Abrir último reporte HTMLnpm run lintConfiguración en eslint.config.js:
- Base:
@eslint/jsrecommended - Plugin:
eslint-plugin-security(reglas OWASP para JS) - Reglas clave:
security/detect-object-injection,security/detect-non-literal-regexp
npm run test:mutationConfiguración en stryker.config.mjs:
- Runner: Vitest
- Mutantes en:
js/presets.js,js/helpers/helpers.js - Reporter: HTML + texto
Las pruebas de mutación verifican que los tests unitarios sean capaces de detectar cambios incorrectos en la lógica (operadores lógicos, valores de retorno, condiciones).
jsdom no implementa completamente el setter de element.innerText en todos los
contextos. En consecuencia:
- Los tests que verifican cambios en
innerTextusanObject.definePropertypara interceptar el setter y capturar el valor asignado. - En un navegador real,
bannerTitle.innerText = 'value'funciona correctamente.
jsdom puede rechazar URLs SVG complejas asignadas a element.style.backgroundImage
a través del parser CSS interno. Los tests relacionados verifican el tipo del valor
retornado (typeof string) en lugar del contenido exacto.
Los archivos js/main.js, js/toolbox-*.js y similares inicializan listeners de
eventos directamente al cargar el módulo. No son testables en jsdom sin refactorizar
para inyección de dependencias. Se cubren exclusivamente mediante pruebas E2E.
| Métrica | Resultado |
|---|---|
| Tests unitarios + integración | 145 / 145 pasan ✅ |
| Cobertura de líneas | 74% (umbral: 70%) ✅ |
| Cobertura de funciones | 100% (umbral: 80%) ✅ |
| Bugs detectados | 7 bugs (1 crítico, 2 medios, 4 bajos) |
| Bugs en datos detectados por oráculo | 1 (BUG #5, presets.json) |