Files
Creeer-Web/components/sections/__tests__/Proceso.test.tsx
T
erikghandClaude Opus 5.5 89e54a6e84 feat: rediseño «El documento» en curso, contacto real y enlaces externos
Primitivas y contenido del rediseño (escala tipográfica, Revelar,
MarcaSeccion, Seccion, Antetitulo, SimboloMarca, Resplandor, ClaridadHero,
sección Proceso de ocho etapas, lib/content.ts reestructurado en seis
secciones con alias para las viejas). Mecanismo y FormaTrabajar se retiran.

Encima, lo de esta sesión:
- recorrido: creeer vuelve a responder cada situación en vez de repetirla
  (el alias apuntaba respuesta a situacion); respuestas en plural. Créditos
  de las seis fotos de México.
- contacto: WhatsApp, email (con mailto:) y LinkedIn reales; iconos de las
  redes en el cierre y el footer (IconoRed, trazos de Simple Icons, CC0).
- enlaces: lo externo abre en otra pestaña con noopener y aviso para
  lectores de pantalla (lib/enlaces.ts); mailto y anclas se quedan.
- Proceso: las ocho tarjetas no aparecían nunca en móvil (amount 0.3 sobre
  una lista de ~3000px); ahora cada una se revela sola. El ≡ de la 08 ya no
  tapa un entregable, título equilibrado, etiqueta a 12px, sin elevar al
  hover lo que no es clicable.
- Disciplinas: la frase «No son la promesa — son los medios» gana peso y
  se agrupa con título e ilustración; sin cortes huérfanos en móvil.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:27:20 -06:00

120 lines
5.5 KiB
TypeScript

import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { Proceso } from '../Proceso';
import { proceso, creditosFotos } from '@/lib/content';
describe('<Proceso />', () => {
it('renders the three approved phases as a strip', () => {
render(<Proceso />);
expect(proceso.fases).toHaveLength(3);
for (const fase of proceso.fases) {
expect(screen.getByText(fase)).toBeInTheDocument();
}
});
it('renders all eight stages, numbered 01 through 08', () => {
render(<Proceso />);
expect(proceso.etapas).toHaveLength(8);
for (const etapa of proceso.etapas) {
expect(screen.getByText(etapa.name)).toBeInTheDocument();
expect(screen.getByText(etapa.n)).toBeInTheDocument();
}
});
// The argument of the section: "claridad estratégica" is abstract until it
// is a document you can hold. The lámina labels this block on all eight of
// its cards and marks every line — losing either turns the deliverables back
// into a list of service names.
it('labels the deliverables block on every card and marks every line', () => {
const { container } = render(<Proceso />);
expect(screen.getAllByText(proceso.etiquetaEntregables)).toHaveLength(8);
for (const entregable of proceso.etapas.flatMap((e) => e.entregables)) {
expect(screen.getByText(entregable)).toBeInTheDocument();
}
// One <ul> of deliverables per stage, every line of it carrying an icon.
const listas = container.querySelectorAll('ul');
expect(listas).toHaveLength(proceso.etapas.length);
listas.forEach((lista, i) => {
const esperados = proceso.etapas[i].entregables.length;
expect(lista.querySelectorAll(':scope > li')).toHaveLength(esperados);
expect(lista.querySelectorAll(':scope > li > svg')).toHaveLength(esperados);
});
});
// The icon map is keyed by the copy itself, so rewording a deliverable in
// lib/content.ts without updating the map would render a blank space where
// its mark should be. Nothing else would fail.
it('gives every deliverable its own icon, none of them empty', () => {
const { container } = render(<Proceso />);
const items = Array.from(container.querySelectorAll('ul > li'));
expect(items).toHaveLength(proceso.etapas.reduce((n, e) => n + e.entregables.length, 0));
for (const item of items) {
const svg = item.querySelector(':scope > svg');
expect(svg, `«${item.textContent}» se quedó sin ícono`).not.toBeNull();
expect(svg!.children.length, `el ícono de «${item.textContent}» está vacío`).toBeGreaterThan(0);
}
});
// Stage 01's medallion was the magnifier, and so was its own "Diagnóstico
// ejecutivo" line — the same glyph twice in one card, which reads as a
// mistake. Nothing but the eye catches this, so the eye gets help.
it('never repeats a glyph inside the same card', () => {
const { container } = render(<Proceso />);
const tarjetas = Array.from(container.querySelectorAll('ol'))[1].querySelectorAll(':scope > li');
tarjetas.forEach((tarjeta, i) => {
const glifos = Array.from(tarjeta.querySelectorAll('svg')).map((s) => s.innerHTML);
expect(new Set(glifos).size, `la etapa ${proceso.etapas[i].n} repite un ícono`).toBe(glifos.length);
});
});
it('closes on the One Year Plan, the payoff the other seven point at', () => {
render(<Proceso />);
expect(screen.getByText('One Year Plan')).toBeInTheDocument();
expect(screen.getByText('Roadmap a 12 meses')).toBeInTheDocument();
expect(screen.getByText('Gobernanza del crecimiento')).toBeInTheDocument();
});
// The stages are a sequence, so the order has to reach assistive tech
// through the markup — otherwise it lives only in the drawn number, which is
// decorative and never announced.
it('carries the order in the markup, not just in the drawn number', () => {
const { container } = render(<Proceso />);
const listas = container.querySelectorAll('ol');
expect(listas).toHaveLength(2); // the phase strip and the eight stages
expect(listas[1].querySelectorAll(':scope > li')).toHaveLength(8);
});
// The footer is the only place in the project where the photo licences are
// honoured, and it renders `creditosFotos` verbatim. An image added here
// without an entry there would go uncredited and nothing else would notice.
it('credits every photograph it shows', () => {
const { container } = render(<Proceso />);
const fotos = Array.from(container.querySelectorAll('img')).map((i) => i.getAttribute('src'));
expect(fotos).toHaveLength(proceso.etapas.length);
for (const src of fotos) {
expect(
creditosFotos.some((c) => 'archivo' in c && c.archivo === src),
`«${src}» no tiene crédito en creditosFotos`,
).toBe(true);
}
});
it('loads the photographs lazily, since none of them is above the fold', () => {
const { container } = render(<Proceso />);
for (const img of Array.from(container.querySelectorAll('img'))) {
expect(img).toHaveAttribute('loading', 'lazy');
}
});
// Everything here is adapted from the dueña's working deck for a real
// client. None of that client's detail may reach a public page.
it('leaks nothing that identifies the client the deck was built for', () => {
const { container } = render(<Proceso />);
expect(container.textContent).not.toMatch(
/cápsulas|gomitas|tabletas|planta|sell in|sell out|mercado mexicano|líderes de la categoría/i,
);
});
});