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.
248 lines
9.8 KiB
248 lines
9.8 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('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('Valoraciones', () => {
|
|
test('quien no ha entrado ve solo las estrellas y la puntuación', async ({ page }) => {
|
|
await page.goto('/canciones/prometiste');
|
|
|
|
const bloque = page.getByRole('region', { name: 'Valoración de la canción' });
|
|
await expect(bloque).toBeVisible();
|
|
await expect(
|
|
bloque.getByRole('link', { name: 'Inicia sesión para valorar esta canción' })
|
|
).toBeVisible();
|
|
await expect(bloque.locator('.estrellas span')).toHaveCount(5);
|
|
await expect(bloque.locator('.media')).toBeVisible();
|
|
await expect(bloque).not.toContainText(/valoración|de 5|votos?|quitar mi voto/i);
|
|
// Sin sesión no se puede votar: no hay estrellas que pulsar.
|
|
await expect(bloque.getByRole('radio')).toHaveCount(0);
|
|
});
|
|
|
|
test('las estrellas vuelven al tema tras entrar', async ({ page }) => {
|
|
await page.goto('/canciones/prometiste');
|
|
|
|
await expect(
|
|
page
|
|
.getByRole('region', { name: 'Valoración de la canción' })
|
|
.getByRole('link', { name: 'Inicia sesión para valorar esta canción' })
|
|
).toHaveAttribute('href', /volverA=%2Fcanciones%2Fprometiste/);
|
|
});
|
|
|
|
test('un POST sin origen lo para el CSRF, antes de llegar a la acción', async ({ request }) => {
|
|
const respuesta = await request.post('/canciones/prometiste?/valorar', {
|
|
form: { puntuacion: '5' }
|
|
});
|
|
|
|
expect(respuesta.status()).toBe(403);
|
|
});
|
|
|
|
test('sin sesión, la acción de valorar se rechaza', async ({ request, baseURL }) => {
|
|
// Con el origen correcto la petición llega a la acción, así que lo que se
|
|
// comprueba aquí es su guardia y no la del framework. La interfaz no
|
|
// enseña estrellas sin sesión, pero el formulario se puede enviar a mano.
|
|
//
|
|
// La respuesta es el resultado de la acción en JSON, no la página: el
|
|
// código de `fail` viaja dentro, y el de la respuesta HTTP es 200.
|
|
const respuesta = await request.post('/canciones/prometiste?/valorar', {
|
|
headers: { origin: baseURL ?? '' },
|
|
form: { puntuacion: '5' }
|
|
});
|
|
|
|
const resultado = JSON.parse(await respuesta.text());
|
|
expect(resultado.type).toBe('failure');
|
|
expect(resultado.status).toBe(401);
|
|
});
|
|
|
|
test('una puntuación fuera de rango tampoco pasa', async ({ request, baseURL }) => {
|
|
// Sin sesión gana la guardia de la cuenta; que el rango se comprueba está
|
|
// cubierto aparte, en las pruebas de `aPuntuacion`.
|
|
const respuesta = await request.post('/canciones/prometiste?/valorar', {
|
|
headers: { origin: baseURL ?? '' },
|
|
form: { puntuacion: '9' }
|
|
});
|
|
|
|
expect(JSON.parse(await respuesta.text()).type).toBe('failure');
|
|
});
|
|
});
|
|
|
|
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');
|
|
|
|
// 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');
|
|
});
|
|
});
|