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>
120 lines
5.5 KiB
TypeScript
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,
|
|
);
|
|
});
|
|
});
|