import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { Proceso } from '../Proceso';
import { proceso, creditosFotos } from '@/lib/content';
describe('', () => {
it('renders the three approved phases as a strip', () => {
render();
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();
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();
expect(screen.getAllByText(proceso.etiquetaEntregables)).toHaveLength(8);
for (const entregable of proceso.etapas.flatMap((e) => e.entregables)) {
expect(screen.getByText(entregable)).toBeInTheDocument();
}
// One
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();
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();
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();
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();
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();
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();
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();
expect(container.textContent).not.toMatch(
/cápsulas|gomitas|tabletas|planta|sell in|sell out|mercado mexicano|líderes de la categoría/i,
);
});
});