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.
305 lines
12 KiB
305 lines
12 KiB
import { expect, test } from '@playwright/test';
|
|
|
|
test.describe('Blog', () => {
|
|
test('está en la navegación principal', async ({ page }) => {
|
|
await page.goto('/');
|
|
await page
|
|
.getByRole('navigation', { name: 'Principal' })
|
|
.getByRole('link', { name: 'Blog' })
|
|
.click();
|
|
|
|
await expect(page).toHaveURL('/blog');
|
|
await expect(page.getByRole('heading', { level: 1 })).toHaveText('Blog');
|
|
});
|
|
|
|
test('las entradas van de la más reciente a la más antigua', async ({ page }) => {
|
|
await page.goto('/blog');
|
|
|
|
const fechas = await page
|
|
.locator('time')
|
|
.evaluateAll((ts) => ts.map((t) => t.getAttribute('datetime') ?? ''));
|
|
expect(fechas.length).toBeGreaterThan(1);
|
|
expect(fechas).toEqual([...fechas].sort().reverse());
|
|
});
|
|
|
|
test('buscar y cambiar de vista comparten el mismo control', async ({ page }) => {
|
|
await page.goto('/blog');
|
|
await page.locator('html[data-hidratado]').waitFor();
|
|
|
|
await page.getByRole('searchbox', { name: 'Buscar una entrada' }).fill('no cabe');
|
|
await expect(page.getByRole('status')).toContainText('1 entrada');
|
|
await expect(page.getByRole('link', { name: 'Lo que no cabe en una canción' })).toBeVisible();
|
|
|
|
const tabla = page.getByRole('button', { name: 'Tabla' });
|
|
await tabla.click();
|
|
await expect(tabla).toHaveAttribute('aria-pressed', 'true');
|
|
await expect(page.getByRole('table')).toBeVisible();
|
|
});
|
|
|
|
test('las etiquetas no tienen una opción ficticia y admiten selección múltiple', async ({
|
|
page
|
|
}) => {
|
|
await page.goto('/blog');
|
|
await page.locator('html[data-hidratado]').waitFor();
|
|
|
|
const grupo = page.getByRole('group', { name: 'Filtrar por etiqueta' });
|
|
await expect(grupo.getByRole('button', { name: 'Todas', exact: true })).toHaveCount(0);
|
|
const colores = await grupo
|
|
.getByRole('button')
|
|
.evaluateAll((botones) => botones.map((boton) => getComputedStyle(boton).color));
|
|
expect(new Set(colores).size).toBeGreaterThan(1);
|
|
const primera = grupo.getByRole('button').nth(0);
|
|
const segunda = grupo.getByRole('button').nth(1);
|
|
|
|
await primera.click();
|
|
await segunda.click();
|
|
await expect(primera).toHaveAttribute('aria-pressed', 'true');
|
|
await expect(segunda).toHaveAttribute('aria-pressed', 'true');
|
|
|
|
await primera.click();
|
|
await expect(primera).toHaveAttribute('aria-pressed', 'false');
|
|
await expect(segunda).toHaveAttribute('aria-pressed', 'true');
|
|
});
|
|
|
|
test('Atrás y Adelante restauran la presentación y las etiquetas', async ({ page }) => {
|
|
await page.goto('/blog');
|
|
await page.locator('html[data-hidratado]').waitFor();
|
|
|
|
const tabla = page.getByRole('button', { name: 'Tabla' });
|
|
const tarjetas = page.getByRole('button', { name: 'Tarjetas' });
|
|
const primeraEtiqueta = page
|
|
.getByRole('group', { name: 'Filtrar por etiqueta' })
|
|
.getByRole('button')
|
|
.first();
|
|
|
|
await tabla.click();
|
|
await expect(page).toHaveURL('/blog?presentacion=tabla');
|
|
await expect(page.getByRole('table')).toBeVisible();
|
|
await primeraEtiqueta.click();
|
|
await expect(page).toHaveURL(/\/blog\?presentacion=tabla&etiqueta=.+/);
|
|
await expect(primeraEtiqueta).toHaveAttribute('aria-pressed', 'true');
|
|
|
|
await page.goBack();
|
|
await expect(page).toHaveURL('/blog?presentacion=tabla');
|
|
await expect(primeraEtiqueta).toHaveAttribute('aria-pressed', 'false');
|
|
await expect(tabla).toHaveAttribute('aria-pressed', 'true');
|
|
|
|
await page.goBack();
|
|
await expect(page).toHaveURL('/blog');
|
|
await expect(tarjetas).toHaveAttribute('aria-pressed', 'true');
|
|
await expect(page.locator('ul.entradas')).toBeVisible();
|
|
|
|
await page.goForward();
|
|
await expect(page).toHaveURL('/blog?presentacion=tabla');
|
|
await expect(tabla).toHaveAttribute('aria-pressed', 'true');
|
|
await page.goForward();
|
|
await expect(page).toHaveURL(/\/blog\?presentacion=tabla&etiqueta=.+/);
|
|
await expect(primeraEtiqueta).toHaveAttribute('aria-pressed', 'true');
|
|
});
|
|
|
|
test('Atrás recupera la búsqueda después de abrir una entrada', async ({ page }) => {
|
|
await page.goto('/blog');
|
|
const buscador = page.getByRole('searchbox', { name: 'Buscar una entrada' });
|
|
await buscador.fill('no cabe');
|
|
await expect(page.getByRole('status')).toContainText('1 entrada');
|
|
await page.getByRole('link', { name: 'Lo que no cabe en una canción' }).click();
|
|
await expect(page).toHaveURL('/blog/lo-que-no-cabe-en-una-cancion');
|
|
|
|
await page.goBack();
|
|
await expect(page).toHaveURL('/blog');
|
|
await expect(buscador).toHaveValue('no cabe');
|
|
await expect(page.getByRole('status')).toContainText('1 entrada');
|
|
});
|
|
|
|
test('una entrada se lee entera y encadena con la anterior', async ({ page }) => {
|
|
await page.goto('/blog');
|
|
await page.getByRole('link', { name: 'Escribir en un estilo que no es el tuyo' }).click();
|
|
|
|
await expect(page).toHaveURL('/blog/escribir-en-un-estilo-ajeno');
|
|
await expect(
|
|
page.getByRole('heading', { level: 2, name: 'No es el ritmo, es el registro' })
|
|
).toBeVisible();
|
|
await expect(page.locator('.documento .bloque--texto')).toHaveCount(1);
|
|
|
|
await page.getByRole('link', { name: /Anterior/ }).click();
|
|
await expect(page.getByRole('heading', { level: 1 })).toHaveText(
|
|
'Lo que no cabe en una canción'
|
|
);
|
|
});
|
|
|
|
test('una entrada no conserva enlaces a canciones eliminadas', async ({ page }) => {
|
|
await page.goto('/blog/lo-que-no-cabe-en-una-cancion');
|
|
|
|
await expect(page.getByRole('heading', { name: 'De lo que habla' })).toHaveCount(0);
|
|
await expect(page.getByRole('heading', { name: 'Como si nada' })).toHaveCount(0);
|
|
});
|
|
|
|
test('el detalle distingue una portada real de la reserva del listado', async ({ page }) => {
|
|
await page.setViewportSize({ width: 1000, height: 900 });
|
|
await page.goto('/blog/lo-que-no-cabe-en-una-cancion');
|
|
|
|
const titulo = page.getByRole('heading', { level: 1 });
|
|
await expect(titulo).toHaveText('Lo que no cabe en una canción');
|
|
await expect(page.locator('article img.portada-entrada')).toHaveCount(0);
|
|
await expect(page.locator('article')).not.toContainText('Sin portada');
|
|
await expect(page.getByLabel('Datos del artículo')).toContainText('Publicado');
|
|
await expect(page.getByLabel('Datos del artículo')).toContainText('Lectura');
|
|
const indice = page.getByRole('complementary', { name: 'Más en el cuaderno' });
|
|
await expect(indice).toBeVisible();
|
|
await expect(
|
|
indice.getByRole('link', { name: /Lo que no cabe en una canción/ })
|
|
).toHaveAttribute('aria-current', 'page');
|
|
expect(await indice.getByRole('link').count()).toBeGreaterThan(1);
|
|
const estructura = await page.evaluate(() => {
|
|
const cabecera = document.querySelector('.intro')?.getBoundingClientRect();
|
|
const lateral = document.querySelector('.indice-blog')?.getBoundingClientRect();
|
|
const interior = document.querySelector('.indice-interior');
|
|
return {
|
|
diferenciaVertical: Math.abs((cabecera?.top ?? 0) - (lateral?.top ?? 0)),
|
|
posicion: interior ? getComputedStyle(interior).position : ''
|
|
};
|
|
});
|
|
expect(estructura.diferenciaVertical).toBeLessThan(1);
|
|
expect(estructura.posicion).toBe('sticky');
|
|
|
|
const medidas = await titulo.evaluate((elemento) => {
|
|
const caja = elemento.getBoundingClientRect();
|
|
return {
|
|
ancho: caja.width,
|
|
alto: caja.height,
|
|
tamano: parseFloat(getComputedStyle(elemento).fontSize)
|
|
};
|
|
});
|
|
expect(medidas.ancho).toBeGreaterThan(540);
|
|
expect(medidas.ancho).toBeLessThan(600);
|
|
expect(medidas.alto).toBeGreaterThan(100);
|
|
expect(medidas.alto).toBeLessThan(125);
|
|
expect(medidas.tamano).toBeGreaterThanOrEqual(56);
|
|
|
|
const cuerpo = page.locator('.contenido .prosa').first();
|
|
await expect(cuerpo).toBeVisible();
|
|
const lectura = await cuerpo.evaluate((elemento) => {
|
|
const caja = elemento.getBoundingClientRect();
|
|
return { ancho: caja.width, tamano: parseFloat(getComputedStyle(elemento).fontSize) };
|
|
});
|
|
expect(lectura.ancho).toBeGreaterThanOrEqual(570);
|
|
expect(lectura.ancho).toBeLessThanOrEqual(680);
|
|
expect(lectura.tamano).toBeGreaterThanOrEqual(18);
|
|
|
|
const primerParrafo = cuerpo.locator('p').first();
|
|
const capitular = await primerParrafo.evaluate((elemento) => {
|
|
const estilo = getComputedStyle(elemento, '::first-letter');
|
|
return { tamano: parseFloat(estilo.fontSize), flotante: estilo.float };
|
|
});
|
|
expect(capitular.tamano).toBeGreaterThan(70);
|
|
expect(capitular.flotante).toBe('left');
|
|
});
|
|
|
|
test('las migas conservan el eje izquierdo de la cabecera en móvil', async ({ page }) => {
|
|
await page.setViewportSize({ width: 336, height: 800 });
|
|
await page.goto('/blog/lo-que-no-cabe-en-una-cancion');
|
|
|
|
const alineacion = await page.evaluate(() => {
|
|
const marca = document.querySelector('header .marca')!.getBoundingClientRect();
|
|
const ruta = document
|
|
.querySelector<HTMLElement>("nav[aria-label='Ruta']")!
|
|
.getBoundingClientRect();
|
|
return {
|
|
diferenciaIzquierda: Math.abs(marca.left - ruta.left),
|
|
espacioDerecho: innerWidth - ruta.right,
|
|
ancho: ruta.width
|
|
};
|
|
});
|
|
|
|
expect(alineacion.diferenciaIzquierda).toBeLessThanOrEqual(1);
|
|
expect(alineacion.espacioDerecho).toBeGreaterThanOrEqual(16);
|
|
expect(alineacion.ancho).toBeGreaterThanOrEqual(300);
|
|
});
|
|
|
|
test('las etiquetas de una entrada filtran la música relacionada', async ({ page }) => {
|
|
await page.goto('/blog/lo-que-no-cabe-en-una-cancion');
|
|
await page.getByRole('link', { name: 'Desamor', exact: true }).click();
|
|
|
|
await expect(page).toHaveURL('/musica?etiqueta=desamor');
|
|
});
|
|
|
|
test('una entrada que no existe da 404', async ({ page }) => {
|
|
const respuesta = await page.goto('/blog/no-existe');
|
|
expect(respuesta?.status()).toBe(404);
|
|
});
|
|
});
|
|
|
|
test.describe('Me gusta', () => {
|
|
test('muestra el pulgar y el contador, sin estrellas', async ({ page }) => {
|
|
await page.goto('/canciones/prometiste');
|
|
const bloque = page.locator('header.ficha .me-gusta');
|
|
await expect(
|
|
bloque.getByRole('link', { name: /Me gusta: \d+ likes?\. Inicia sesión/ })
|
|
).toBeVisible();
|
|
await expect(bloque.locator('.contador')).toHaveText(/^\d+$/);
|
|
await expect(page.locator('.estrellas')).toHaveCount(0);
|
|
});
|
|
test('conserva la canción como destino al entrar', async ({ page }) => {
|
|
await page.goto('/canciones/prometiste');
|
|
await expect(page.locator('header.ficha .me-gusta a')).toHaveAttribute(
|
|
'href',
|
|
/volverA=%2Fcanciones%2Fprometiste/
|
|
);
|
|
});
|
|
test('rechaza un POST sin origen', async ({ request }) => {
|
|
const r = await request.post('/canciones/prometiste?/meGusta', { form: { activo: '1' } });
|
|
expect(r.status()).toBe(403);
|
|
});
|
|
test('rechaza likes sin sesión', async ({ request, baseURL }) => {
|
|
const r = await request.post('/canciones/prometiste?/meGusta', {
|
|
headers: { origin: baseURL ?? '', accept: 'application/json', 'x-sveltekit-action': 'true' },
|
|
form: { activo: '1' }
|
|
});
|
|
expect(JSON.parse(await r.text())).toMatchObject({ type: 'failure', status: 401 });
|
|
});
|
|
});
|
|
|
|
test.describe('Favoritos', () => {
|
|
test('quien no ha entrado ve el corazón y abre el acceso en la ficha', async ({ page }) => {
|
|
await page.goto('/canciones/prometiste');
|
|
await expect(page.locator('html')).toHaveAttribute('data-hidratado', '');
|
|
|
|
// No se esconde: la interfaz no cambia de forma según quién mira.
|
|
const enlace = page.locator('header.ficha').getByRole('link', {
|
|
name: 'Entra con tu cuenta para añadir a favoritos'
|
|
});
|
|
await expect(enlace).toBeVisible();
|
|
await expect(enlace).toHaveAttribute('href', /volverA=%2Fcanciones%2Fprometiste/);
|
|
await enlace.click();
|
|
await expect(page.getByRole('dialog', { name: 'Entrar o crear cuenta' })).toBeVisible();
|
|
await expect(page).toHaveURL(/\/canciones\/prometiste$/);
|
|
});
|
|
|
|
test('la lista invita a entrar si no hay sesión', async ({ page }) => {
|
|
await page.goto('/favoritos');
|
|
|
|
await expect(page.getByRole('heading', { level: 1 })).toHaveText('Favoritos');
|
|
await expect(page.getByRole('link', { name: 'Entra con tu cuenta' })).toBeVisible();
|
|
});
|
|
|
|
test('sin sesión, marcar un favorito se rechaza', async ({ request, baseURL }) => {
|
|
const respuesta = await request.post('/favoritos?/marcar', {
|
|
headers: { origin: baseURL ?? '' },
|
|
form: { cancion: 'prometiste', volverA: '/favoritos' }
|
|
});
|
|
|
|
const resultado = JSON.parse(await respuesta.text());
|
|
expect(resultado.type).toBe('failure');
|
|
expect(resultado.status).toBe(401);
|
|
});
|
|
|
|
test('un tema inventado no se puede guardar', async ({ request, baseURL }) => {
|
|
const respuesta = await request.post('/favoritos?/marcar', {
|
|
headers: { origin: baseURL ?? '' },
|
|
form: { cancion: 'no-existe', volverA: '/favoritos' }
|
|
});
|
|
|
|
expect(JSON.parse(await respuesta.text()).type).toBe('failure');
|
|
});
|
|
});
|