learning-front

Nivel 8 · Calidad: que no se rompa en producción

Proyecto: blindar el Team Builder

Mega-tarea de cierre: coge tu Team Builder de React y dótalo de toda la red de seguridad del nivel —tests de componentes y hooks, integración con MSW, accesibilidad, rendimiento y seguridad— corriendo en CI. No hay concepto nuevo: es juntar todo el nivel sobre tu propia app.

Llegaste al final del Nivel 8. Toca el encargo que lo junta todo: coger tu Team Builder de React —el que construiste en el Nivel 7— y blindarlo para producción. No hay nada nuevo que aprender aquí; es síntesis: aplicar, de una vez y sobre tu propia app, toda la red de seguridad del nivel.

No cabe en el playground de esta página: una suite de tests de la app de verdad, su accesibilidad, su rendimiento y su seguridad viven en tu proyecto y en tu CI. El código que verás en las soluciones es el espinazo de la suite; el blindaje completo lo montas en local (está en el ejercicio).

Lo que vas a montar#

  • Una suite real de la app. Tests de componente con Testing Library (queries por rol, user-event con await) y un test del hook con renderHook y act.
  • Integración con MSW de los caminos críticos: cargar, fichar, ver subir la cuenta, y el camino de error —la API que falla—, esperando lo asíncrono con findBy, sin sleeps.
  • Accesibilidad como test con axe (un suelo, no un techo), más queries por rol para lo que axe no juzga.
  • Un paso de rendimiento: lazy/code-splitting de una ruta pesada y memoización donde paga, medido antes y después.
  • Una revisión de seguridad: ningún secreto en el cliente, ningún HTML de usuario sin sanear.
  • Una story de Storybook con una play function, y todo corriendo en CI como portería.

El reparto: el trofeo, hecho tu suite#

La pregunta de fondo del nivel era cuántos tests de cada tipo. Aplícalo aquí: la base son los tipos y el linter (gratis, en cada guardado); el grueso va a la integración de features (fichar toca lista, estado y red, y eso se prueba junto con MSW); los unitarios cubren la lógica pura del motor (Nivel 6); y arriba, muy pocos e2e sobre el camino crítico. Ese reparto es el que da más confianza sin que la suite se vuelva lenta y flaky.

Y recuerda que este proyecto se construye sobre el anterior: la app de React del Nivel 7 sigue intacta, y tú le añades la red de calidad encima. Acumulas una capa más —HTML, JS, tipos, React, y ahora calidad— sin descartar nada. Es exactamente como crece un proyecto de verdad.

Comprueba lo que sabes#

Este es el examen del Nivel 8: repasa de una sola pasada todo lo que has montado en el nivel, del testing de componentes a la cobertura en CI, pasando por la seguridad, el rendimiento, la accesibilidad y el sistema de diseño. Es más largo y más exigente que los quizzes intermedios; tómatelo como el examen que es.

Pregunta 1 de 19

Quieres comprobar que existe el botón "Fichar". ¿Por qué se prefiere getByRole("button", { name: "Fichar" }) antes que getByTestId("boton-fichar")?

Tu turno#

Este es un proyecto en local, sobre tu Team Builder de React. Clona la carpeta del ejercicio, monta la suite y las capas de blindaje, y déjalo en verde con pnpm test y pnpm coverage, corriendo en CI. Cuando lo tengas (o si te atascas), despliega las soluciones y fíjate en el salto de un tier al siguiente: de una suite de piezas aisladas, a la integración con MSW, a las capas transversales con la honestidad del trofeo.

Ejercicio · hazlo en local

Blinda el Team Builder

Coge tu Team Builder de React (Nivel 7) y dótalo de toda la red de seguridad del nivel, en local: una suite de tests de la app que corra en verde, integración con MSW de los caminos críticos, accesibilidad como test, un paso de rendimiento y una revisión de seguridad, todo corriendo en CI. No hay concepto nuevo: es juntar el nivel entero sobre tu propia app, sin descartar nada de lo anterior.

Paso 1: Una suite real de la app, en verde y en CI

  • Tests de componente con Testing Library sobre TarjetaHeroe: localizas por rol y nombre accesible (getByRole), usas user-event con await y compruebas el dato derivado (el winrate), no solo que pinta.
  • Un test del hook useFavoritos con renderHook y act, comprobando que alternar añade y quita.
  • La suite corre en verde con pnpm test, configuras la cobertura (pnpm coverage) y todo se ejecuta en CI en cada PR.

Cómo hacerlo en local

Clona el repositorio del curso, entra en la carpeta del ejercicio y abre el index.html en tu navegador. Toda tu solución va en solucion.js.

git clone <repo>
cd exercises/nivel-8/proyecto-blindar-el-team-builder
# abre index.html en el navegador y edita solucion.js
Ver soluciones
// El espinazo de la suite, tier OK: tests de componente (Testing Library) y un test del
// hook (renderHook). Es la base que protege la app cuando la toques dentro de seis meses.
import { describe, it, expect } from "vitest";
import { render, screen, renderHook, act } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

import { TarjetaHeroe } from "./TarjetaHeroe";
import { useFavoritos } from "./useFavoritos";

// El héroe de prueba que reutilizan los tests de componente.
const tracer = {
  nombre: "Tracer",
  rol: "Daño",
  partidas: 200,
  victorias: 130,
} as const;

describe("TarjetaHeroe", () => {
  it("muestra el nombre, el rol y el winrate calculado", () => {
    // Arrange + Act: montamos la tarjeta con un héroe.
    render(<TarjetaHeroe heroe={tracer} />);

    // Assert: el nombre va en un encabezado (rol accesible "heading").
    expect(screen.getByRole("heading", { name: "Tracer" })).toBeInTheDocument();

    // 130 de 200 = 65 %: comprobamos el dato DERIVADO, no solo que pinta algo.
    expect(screen.getByText("Winrate: 65%")).toBeInTheDocument();
  });

  it("alterna el botón de favorito al pulsarlo", async () => {
    const user = userEvent.setup();
    render(<TarjetaHeroe heroe={tracer} />);

    // Arranca sin marcar: el botón ofrece "Marcar favorito".
    const boton = screen.getByRole("button", { name: "Marcar favorito" });

    // El usuario lo pulsa (await: user-event simula la interacción real, es asíncrono).
    await user.click(boton);

    // El mismo botón pasa a "Quitar de favoritos": el toggle funcionó.
    expect(
      screen.getByRole("button", { name: "Quitar de favoritos" }),
    ).toBeInTheDocument();
  });
});

describe("useFavoritos", () => {
  it("añade y quita un héroe de la lista", () => {
    // renderHook monta el hook aislado; result.current es lo que devuelve.
    const { result } = renderHook(() => useFavoritos());

    // act envuelve el cambio de estado para que React lo procese antes de afirmar.
    act(() => result.current.alternar("Tracer"));
    expect(result.current.esFavorito("Tracer")).toBe(true);

    // Volver a alternar el mismo nombre lo quita.
    act(() => result.current.alternar("Tracer"));
    expect(result.current.esFavorito("Tracer")).toBe(false);
  });
});

Por qué este nivel

  • El espinazo de la suite: tests de componente de TarjetaHeroe (por rol y nombre accesible, con user-event y await) y un test del hook useFavoritos con renderHook y act. Si alguien rompe el winrate o el toggle, salta.
  • Su límite: prueba piezas aisladas. Que la feature de fichajes funcione entera —lista, estado y red colaborando— se queda fuera. Eso es integración: el siguiente tier.