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 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 enlaza los temas de los que habla', async ({ page }) => { await page.goto('/blog/lo-que-no-cabe-en-una-cancion'); await expect(page.getByRole('heading', { name: 'De lo que habla' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Como si nada' })).toBeVisible(); }); test('las etiquetas de una entrada llevan al mismo sitio que las de un tema', 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('/etiquetas/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 la media y una invitación', async ({ page }) => { await page.goto('/canciones/como-si-nada'); const bloque = page.getByRole('region', { name: 'Valoración' }); await expect(bloque).toBeVisible(); await expect(bloque.getByRole('link', { name: 'Entra con tu cuenta' })).toBeVisible(); // Sin sesión no se puede votar: no hay estrellas que pulsar. await expect(bloque.getByRole('radio')).toHaveCount(0); }); test('la invitación vuelve al tema tras entrar', async ({ page }) => { await page.goto('/canciones/como-si-nada'); await expect( page .getByRole('region', { name: 'Valoración' }) .getByRole('link', { name: 'Entra con tu cuenta' }) ).toHaveAttribute('href', /volverA=%2Fcanciones%2Fcomo-si-nada/); }); test('un POST sin origen lo para el CSRF, antes de llegar a la acción', async ({ request }) => { const respuesta = await request.post('/canciones/como-si-nada?/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/como-si-nada?/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/como-si-nada?/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, pero lleva a entrar', async ({ page }) => { await page.goto('/canciones/como-si-nada'); // No se esconde: la interfaz no cambia de forma según quién mira. const enlace = page.getByRole('link', { name: 'Entra con tu cuenta para guardar en favoritos' }); await expect(enlace).toBeVisible(); await expect(enlace).toHaveAttribute('href', /volverA=%2Fcanciones%2Fcomo-si-nada/); }); 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: 'como-si-nada', 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'); }); });