Files
Creeer-Web/components/sections/__tests__/Dolores.test.tsx
T
erikghandClaude Opus 5 d4bff33e47 feat(dolores): cada frase se resuelve al llegar, palabra a palabra
Prometí en la vista previa que entrarían «de una en una al bajar» y lo
que monté las traía a las cuatro a la vez: un stagger de contenedor se
dispara con la sección al 30% en pantalla, y la sección mide 1.200px, así
que el lector veía aparecer todas las frases mucho antes de llegar a
ellas.

Ahora cada cita usa TextoDesenfoque, que lleva su propio observador: cada
una espera su turno y entonces se resuelve desde un desenfoque, palabra a
palabra. Es el dolor entrando en foco, y es el mismo recurso que ya usan
el hero y la frase ancla — vocabulario del proyecto, no un invento nuevo.

Verificado midiendo la opacidad de la palabra animada de cada cita: al
asomar la sección, las dos primeras están resueltas y las dos últimas a
cero; a media sección, las cuatro. Antes de corregir el selector medía el
span envoltorio y daba unos en todas, que no significaba nada.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 16:07:00 -06:00

58 lines
2.7 KiB
TypeScript

import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { Dolores } from '../Dolores';
import { dolores } from '@/lib/content';
describe('<Dolores />', () => {
it('renders the four pains verbatim, in the client\'s first person', () => {
const { container } = render(<Dolores />);
// The quotes resolve word by word, so each word is its own span and no
// single element holds the whole sentence as its own text — getByText
// would never find it. The blockquote's textContent is what the reader
// ends up with, and that is what has to be verbatim.
const citas = Array.from(container.querySelectorAll('blockquote')).map((b) =>
(b.textContent ?? '').replace(/\s+/g, ' '),
);
for (const dolor of dolores.quotes) {
expect(citas.some((c) => c.includes(dolor))).toBe(true);
}
});
it('says each pain exactly once', () => {
const { container } = render(<Dolores />);
// A marquee needed the four sentences painted four times over — twice per
// row for a seamless loop, twice again for the second row — and three of
// those runs had to be hidden from assistive tech or a screen reader would
// recite the whole list twice. Stacked, there is nothing to hide: one
// sentence, once, said once.
for (const dolor of dolores.quotes) {
const apariciones = Array.from(container.querySelectorAll('blockquote')).filter(
(el) => el.textContent?.includes(dolor),
);
expect(apariciones).toHaveLength(1);
expect(apariciones[0].closest('[aria-hidden="true"]')).toBeNull();
}
expect(container.querySelectorAll('li')).toHaveLength(dolores.quotes.length);
});
it('closes with the resolution, not with more effort', () => {
const { container } = render(<Dolores />);
expect(container.textContent).toContain('Esto no se resuelve con más esfuerzo.');
expect(container.textContent).toContain('Se resuelve con una estrategia clara.');
});
it('centres the knot so the conductor thread runs through it', () => {
const { container } = render(<Dolores />);
// The knot is the centred decorative svg of the section; the conductor
// thread runs down the centre, so only a centred knot gets traversed by
// it — stitching the pain to the resolution below.
const centrados = Array.from(container.querySelectorAll('svg')).filter((s) => {
const cls = s.getAttribute('class') ?? '';
return cls.includes('left-1/2') && cls.includes('-translate-x-1/2');
});
expect(centrados.length).toBeGreaterThanOrEqual(1);
});
it('does NOT introduce scarcity', () => {
const { container } = render(<Dolores />);
expect(container.textContent).not.toMatch(/cupos|cohorte|generación fundadora/i);
});
});