You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
senzapaura_es/e2e/taller.e2e.ts

400 lines
16 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import { expect, test } from '@playwright/test';
test.describe('Taller de letras', () => {
test('el índice separa los principios de las fichas', async ({ page }) => {
await page.goto('/taller');
await expect(page.getByRole('heading', { level: 1 })).toHaveText('Taller de letras');
await expect(page.getByRole('heading', { level: 2, name: 'Los principios' })).toBeVisible();
await expect(page.getByRole('heading', { level: 2, name: 'Fichas de creación' })).toBeVisible();
// El índice lateral rotula sus bloques, pero no como encabezados: si no,
// el esquema de la página diría dos veces lo mismo.
await expect(page.getByRole('heading', { level: 2 })).toHaveCount(2);
});
test('se llega a un principio y se encadena con el siguiente', async ({ page }) => {
await page.goto('/taller');
// El listado de la página, no el índice lateral, que repite los enlaces.
await page
.locator('section.bloque')
.getByRole('link', { name: /Lo que hace que una letra funcione/ })
.click();
await expect(page).toHaveURL('/taller/lo-que-hace-que-una-letra-funcione');
await expect(page.getByRole('heading', { level: 1 })).toHaveText(
'Lo que hace que una letra funcione'
);
// Los capítulos están pensados para seguirse en orden.
await page.getByRole('link', { name: /Siguiente/ }).click();
await expect(page.getByRole('heading', { level: 1 })).toHaveText('La imagen concreta');
});
test('una ficha de género trae sus datos de creación', async ({ page }) => {
await page.goto('/taller/bolero');
await expect(page.getByRole('heading', { level: 1 })).toHaveText('Bolero');
await expect(page.getByText('Santiago de Cuba, 1883')).toBeVisible();
/*
* Los datos comparables van en una franja a lo ancho, no en una barra
* lateral: el compás y la métrica de un género se leen juntos o no se leen.
* Antes era una lista de definiciones en una columna estrecha a la derecha,
* con los datos en letra pequeña y el texto escorado.
*/
const franja = page.locator('.datos .dato');
await expect(franja).toHaveCount(4);
await expect(franja.filter({ hasText: 'Compás' })).toContainText('2/4');
await expect(franja.filter({ hasText: 'Metro' })).toContainText('Octosílabo');
await expect(page.locator('aside.lateral')).toHaveCount(0);
await expect(page.getByRole('heading', { level: 2, name: 'De qué se habla' })).toBeVisible();
await expect(
page.getByRole('heading', { level: 2, name: 'Rasgos de escritura' })
).toBeVisible();
await expect(
page.getByRole('heading', { level: 2, name: 'Estructura de las canciones' })
).toBeVisible();
});
test('la franja de datos ocupa el ancho, y el texto su medida de lectura', async ({ page }) => {
// Es la diferencia con la barra lateral de antes, y lo que hace que los
// datos se puedan comparar de un vistazo.
await page.setViewportSize({ width: 1440, height: 900 });
await page.goto('/taller/bolero');
const datos = (await page.locator('.datos').boundingBox())!;
const texto = (await page.locator('.lectura .texto').boundingBox())!;
expect(datos.width).toBeGreaterThan(texto.width * 1.4);
expect(Math.abs(datos.x - texto.x)).toBeLessThan(2);
});
test('nada se queda corto por la derecha', async ({ page }) => {
/*
* La queja recurrente, convertida en prueba: la franja de arriba, el índice
* del capítulo y los bloques de abajo tienen que terminar en el mismo
* borde. Un texto no puede llenar novecientos píxeles —a menos de letra de
* veinticuatro puntos salen ciento veinte caracteres por línea—, así que lo
* que cubre ese ancho es lo que va a su lado, no el texto estirado.
*/
await page.setViewportSize({ width: 1440, height: 900 });
await page.goto('/taller/bolero');
const derecha = async (selector: string) => {
const caja = (await page.locator(selector).first().boundingBox())!;
return caja.x + caja.width;
};
const franja = await derecha('.datos');
const indice = await derecha('.apartados');
const bloque = await derecha('.bloque');
expect(Math.abs(franja - indice)).toBeLessThan(2);
expect(Math.abs(franja - bloque)).toBeLessThan(2);
});
test('la portada del taller también lleva el índice al lado', async ({ page }) => {
// El índice está en el layout de la sección, así que acompaña a todas sus
// páginas, no solo a los capítulos.
await page.goto('/taller');
const indice = page.getByRole('navigation', { name: 'Contenido del taller' });
await expect(indice).toBeVisible();
await expect(indice.getByRole('link', { name: 'Bolero' })).toBeVisible();
// En la portada no hay capítulo abierto que marcar.
await expect(indice.locator('a[aria-current="page"]')).toHaveCount(0);
});
test('cada capítulo lleva el índice del taller al lado', async ({ page }) => {
await page.goto('/taller/bolero');
const indice = page.getByRole('navigation', { name: 'Contenido del taller' });
await expect(indice).toBeVisible();
// Marca dónde estás dentro de la estructura.
await expect(indice.getByRole('link', { name: 'Bolero' })).toHaveAttribute(
'aria-current',
'page'
);
// Y enseña los dos bloques enteros, no solo el actual.
await expect(indice.getByRole('link', { name: 'El estribillo' })).toBeVisible();
});
test('un principio no enseña ficha de creación', async ({ page }) => {
// La ficha es de los estilos; en un principio serían bloques vacíos.
await page.goto('/taller/el-estribillo');
await expect(page.locator('.datos')).toHaveCount(0);
await expect(page.locator('.instrumentos')).toHaveCount(0);
await expect(page.getByRole('heading', { level: 2, name: 'Linaje' })).toHaveCount(0);
});
/*
* Lo que sigue solo existe desde que el taller lee de la base: en un archivo
* de Markdown no cabía la instrumentación con el peso de cada instrumento,
* ni los letristas con sus fechas, ni de qué géneros viene este.
*/
test('la ficha dice qué país es la cuna y cuáles lo adoptaron', async ({ page }) => {
await page.goto('/taller/bolero');
const paises = page.locator('.paises li');
await expect(paises).toHaveCount(6);
// La cuna va primero aunque alfabéticamente no le toque.
await expect(paises.first()).toContainText('Cuba');
await expect(paises.first()).toContainText('cuna');
await expect(paises.filter({ hasText: 'México' })).not.toContainText('cuna');
});
test('la instrumentación distingue lo esencial de lo ocasional', async ({ page }) => {
await page.goto('/taller/bolero');
const filas = page.locator('.instrumentos li');
await expect(filas.filter({ hasText: 'Guitarra' })).toContainText('esencial');
await expect(filas.filter({ hasText: 'Piano' })).toContainText('ocasional');
// El papel es lo que hace útil el dato: no basta con enumerar instrumentos.
await expect(filas.filter({ hasText: 'Requinto' })).toContainText('adornos');
});
test('los letristas traen país, años y papel', async ({ page }) => {
await page.goto('/taller/tango');
const gardel = page.locator('.personas li').filter({ hasText: 'Carlos Gardel' });
await expect(gardel).toContainText('Argentina · 1890–1935');
// «interprete» se guarda sin tilde, pero se lee con ella.
await expect(gardel).toContainText('intérprete');
});
test('las obras de referencia se fechan y se acreditan', async ({ page }) => {
await page.goto('/taller/bolero');
const primera = page.locator('.obras li').first();
await expect(primera).toContainText('1883');
await expect(primera).toContainText('Tristezas');
await expect(primera).toContainText('José «Pepe» Sánchez');
// Un fragmento de letra ajena solo se sostiene si se dice por qué está.
await expect(page.locator('.obras')).toHaveCount(1);
await expect(page.getByText('derecho de cita')).toBeVisible();
});
test('el linaje no enlaza a un género sin capítulo', async ({ page }) => {
/*
* El bolero-son existe en la base porque el bolero y el son confluyen en
* él, pero nadie ha escrito su ficha. Enlazarlo mandaba a un 404.
*/
await page.goto('/taller/bolero');
const linaje = page.locator('.rama');
await expect(linaje.getByRole('link', { name: 'Bachata' })).toBeVisible();
await expect(linaje.getByText('Bolero-son')).toBeVisible();
await expect(linaje.getByRole('link', { name: 'Bolero-son' })).toHaveCount(0);
});
test('la ficha enseña versos, no solo los describe', async ({ page }) => {
/*
* Se podía leer que el bolero va en octosílabo y endecasílabo alternados sin
* ver nunca uno. Un taller que describe la métrica y no la enseña está
* pidiendo que se la imaginen.
*/
await page.goto('/taller/bolero');
const ejemplos = page.locator('.ejemplo');
await expect(ejemplos).toHaveCount(2);
const primero = ejemplos.first();
await expect(primero).toContainText('Dejaste la taza a medias');
await expect(primero).toContainText('métrica');
// Cuatro versos, cada uno con su cuenta.
await expect(primero.locator('.estrofa li')).toHaveCount(4);
await expect(primero.locator('.cuenta').first()).toHaveText('8');
});
test('la escansión marca la sinalefa y el acento', async ({ page }) => {
await page.goto('/taller/bolero');
// Dos palabras en una casilla, unidas por el arco: al cantarlas se dice
// una sola sílaba. Es lo que en un texto corrido no se ve.
const casillas = page.locator('.ejemplo').first().locator('.escansion').first();
await expect(casillas.locator('.silaba')).toHaveCount(8);
await expect(casillas).toContainText('za⁀a');
// Y el octosílabo lleva siempre acento en la séptima.
await expect(casillas.locator('.silaba.tonica').last()).toHaveText('me');
});
test('el octosílabo agudo mide ocho con siete casillas', async ({ page }) => {
// La ley del acento final, que es la primera regla que hay que tener
// automatizada para escribir sobre clave.
await page.goto('/taller/son-cubano');
const segundo = page.locator('.estrofa li').nth(1);
await expect(segundo.locator('.verso')).toHaveText('yo respondo con el son;');
await expect(segundo.locator('.silaba')).toHaveCount(7);
await expect(segundo.locator('.cuenta')).toHaveText('8');
});
test('los ejemplos van antes que la documentación del género', async ({ page }) => {
// Es un taller: quien abre esta página viene a escribir una letra, no a
// documentarse. Primero lo que se usa para escribir.
await page.goto('/taller/bolero');
const titulos = await page.getByRole('heading', { level: 2 }).allTextContents();
expect(titulos.indexOf('Ejemplos de versos')).toBeLessThan(titulos.indexOf('Instrumentación'));
expect(titulos.indexOf('Rasgos de escritura')).toBeLessThan(
titulos.indexOf('Obras de referencia')
);
});
test('cada obra de referencia dice dónde escucharla', async ({ page }) => {
await page.goto('/taller/tango');
const cambalache = page.locator('.obras li').filter({ hasText: 'Cambalache' });
const spotify = cambalache.getByRole('link', { name: 'Spotify' });
await expect(spotify).toBeVisible();
await expect(cambalache.getByRole('link', { name: 'YouTube' })).toBeVisible();
// En otra pestaña: irse del taller a mitad de una ficha es perder el sitio.
await expect(spotify).toHaveAttribute('target', '_blank');
await expect(spotify).toHaveAttribute('rel', /noopener/);
});
test('se avisa de lo que aún no ha revisado nadie', async ({ page }) => {
// La musicología la reunió un modelo y la ficha se publica con el nombre
// del titular. Callarlo sería lo cómodo.
await page.goto('/taller/bolero');
const aviso = page.locator('.sin-revisar');
await expect(aviso).toContainText('pendiente de revisión');
// Y enumera todo lo que sale de ahí, ejemplos de versos incluidos.
await expect(aviso).toContainText('versos');
});
/* --- El glosario ------------------------------------------------------- */
test('los términos del glosario salen marcados en el texto', async ({ page }) => {
// Lo que se pidió: «cada vez que aparezca un término del glosario en el
// texto, estará marcado para visualizarse con un popup».
await page.goto('/taller/son-cubano');
const marcados = page.locator('.texto .termino');
await expect(marcados.first()).toBeVisible();
// Cada término una sola vez por apartado, no en cada aparición: un texto
// con nueve «compás» subrayados no hay quien lo lea.
const slugs = await marcados.evaluateAll((bs) =>
bs.map((b) => (b as HTMLElement).dataset.termino)
);
expect(new Set(slugs).size).toBeGreaterThan(1);
});
test('al pulsar un término se abre su definición', async ({ page }) => {
await page.goto('/taller/son-cubano');
const montuno = page.locator('.texto .termino[data-termino="montuno"]').first();
await montuno.click();
const globo = page.getByRole('dialog', { name: /Definición de montuno/ });
await expect(globo).toBeVisible();
await expect(globo).toContainText('llamada y respuesta');
// Y se cierra con Escape, devolviendo el foco a donde estaba.
await page.keyboard.press('Escape');
await expect(globo).toBeHidden();
await expect(montuno).toBeFocused();
});
test('el globo no tapa el término que se acaba de pulsar', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 900 });
await page.goto('/taller/son-cubano');
const termino = page.locator('.texto .termino').first();
await termino.click();
const suyo = (await termino.boundingBox())!;
const globo = (await page.locator('.globo').boundingBox())!;
const seSolapan = globo.y < suyo.y + suyo.height && suyo.y < globo.y + globo.height;
expect(seSolapan).toBe(false);
});
test('no se marca dentro de un encabezado', async ({ page }) => {
// Ensuciaría el índice del capítulo, que sale de los encabezados.
await page.goto('/taller/son-cubano');
await expect(page.locator('.texto h2 .termino')).toHaveCount(0);
});
test('el glosario tiene su propia página, con las acepciones numeradas', async ({ page }) => {
await page.goto('/taller/glosario');
await expect(page.getByRole('heading', { level: 1 })).toHaveText('Glosario');
await expect(page.getByRole('heading', { level: 2, name: 'sinalefa' })).toBeVisible();
/*
* «Clave» tiene dos: patrón rítmico e instrumento. Con una sola
* definición por palabra, una de las dos sería falsa, y por eso las
* acepciones van numeradas.
*/
const clave = page.locator('#clave');
await expect(clave.locator('.acepciones > li')).toHaveCount(2);
await expect(clave).toContainText('patrón rítmico');
await expect(clave).toContainText('percusión');
});
test('se llega al glosario desde el índice del taller', async ({ page }) => {
await page.goto('/taller/bolero');
await page
.getByRole('navigation', { name: 'Contenido del taller' })
.getByRole('link', { name: 'Glosario' })
.click();
await expect(page).toHaveURL('/taller/glosario');
});
test('el taller está en la navegación principal', async ({ page }) => {
await page.goto('/');
await page
.getByRole('navigation', { name: 'Principal' })
.getByRole('link', { name: 'Taller' })
.click();
await expect(page).toHaveURL('/taller');
});
});
test.describe('Tema claro y oscuro', () => {
test('sin elegir nada no hay atributo, y manda el marino', async ({ browser }) => {
// Aunque el sistema pida el claro. El color es de la marca y no lo decide
// una preferencia que se configuró pensando en otra cosa.
const claro = await browser.newContext({ colorScheme: 'light' });
const pagina = await claro.newPage();
await pagina.goto('/taller');
await expect(pagina.locator('html')).not.toHaveAttribute('data-tema', /.+/);
await expect(pagina.getByRole('button', { name: 'Cambiar al tema claro' })).toBeVisible();
await claro.close();
});
test('la elección manual manda y se recuerda', async ({ browser }) => {
const claro = await browser.newContext({ colorScheme: 'light' });
const pagina = await claro.newPage();
await pagina.goto('/taller');
await pagina.getByRole('button', { name: 'Cambiar al tema claro' }).click();
await expect(pagina.locator('html')).toHaveAttribute('data-tema', 'claro');
// Sobrevive a la recarga y, sobre todo, se aplica antes de pintar: lo hace
// un script en línea, no la hidratación.
await pagina.reload();
await expect(pagina.locator('html')).toHaveAttribute('data-tema', 'claro');
// Y se puede volver.
await pagina.getByRole('button', { name: 'Cambiar al tema oscuro' }).click();
await expect(pagina.locator('html')).toHaveAttribute('data-tema', 'oscuro');
await claro.close();
});
});

Powered by TurnKey Linux.