import { expect, test } from '@playwright/test'; test.describe('Fichas de contenido', () => { test('la ficha de un tema publica la letra y sus datos', async ({ page }) => { await page.goto('/canciones/prometiste'); await expect(page.getByRole('heading', { level: 1 })).toHaveText('Prometiste'); await expect(page.getByRole('tab', { name: 'Letra' })).toHaveAttribute('aria-selected', 'true'); await expect(page.getByRole('tabpanel', { name: 'Letra' }).locator('p').first()).toContainText( 'Me prometiste besos' ); await expect(page.getByRole('heading', { level: 2, name: 'Ficha' })).toBeVisible(); await expect(page.getByText('Acceso de escucha')).toBeVisible(); }); test('los versos conservan sus saltos de línea', async ({ page }) => { // Sin remark-breaks, Markdown juntaría la estrofa en un único párrafo. await page.goto('/canciones/prometiste'); const saltos = await page.locator('.texto br').count(); expect(saltos).toBeGreaterThan(5); }); test('la letra bloquea selección, copia y menú contextual', async ({ page }) => { await page.goto('/canciones/cuando-tus-manos-me-tocan'); await page.locator('html[data-hidratado]').waitFor(); const letra = page.locator('.letra.protegida'); await expect(letra).toBeVisible(); expect(await letra.evaluate((elemento) => getComputedStyle(elemento).userSelect)).toBe('none'); const eventosCancelados = await letra.evaluate((elemento) => ['copy', 'cut', 'contextmenu', 'selectstart', 'dragstart'].map( (tipo) => !elemento.dispatchEvent(new Event(tipo, { bubbles: true, cancelable: true })) ) ); expect(eventosCancelados).toEqual([true, true, true, true, true]); }); test('la bio se publica desde el Markdown de contenido', async ({ page }) => { await page.goto('/bio'); await expect(page.getByRole('heading', { level: 1 })).toHaveText('Biografía'); await expect(page.getByRole('heading', { level: 2, name: 'Las baladas' })).toBeVisible(); }); }); test.describe('Tarjetas de canción', () => { test('la tarjeta sitúa el tema: disco, año y estilo', async ({ page }) => { await page.goto('/musica/baladas-romanticas'); const tarjeta = page .locator('article') .filter({ has: page.locator('a[href="/canciones/prometiste"]') }); await expect(tarjeta.getByRole('heading', { name: 'Prometiste' })).toBeVisible(); await expect(tarjeta.getByText(/Cupido sin flechas/)).toBeVisible(); await expect(tarjeta.getByText(/2026/)).toBeVisible(); await expect(tarjeta.getByText(/Baladas románticas/)).toBeVisible(); }); test('la tarjeta no enseña la letra', async ({ page }) => { await page.goto('/musica/baladas-romanticas'); await expect(page.getByText('Me prometiste besos')).toHaveCount(0); }); test('la portada no cambia de tamaño al interactuar con la tarjeta', async ({ page }) => { await page.goto('/musica/baladas-romanticas'); const tarjeta = page .locator('article') .filter({ has: page.locator('a[href="/canciones/prometiste"]') }); const portada = tarjeta.locator('.tarjeta__medio img'); await expect(portada).toBeVisible(); expect( await portada.evaluate((imagen) => ({ transformacion: getComputedStyle(imagen).transform, transiciones: getComputedStyle(imagen).transitionProperty })) ).toEqual({ transformacion: 'none', transiciones: 'none' }); await tarjeta.hover(); expect(await portada.evaluate((imagen) => getComputedStyle(imagen).transform)).toBe('none'); }); test('desde la tarjeta se llega a la letra completa', async ({ page }) => { await page.goto('/musica/baladas-romanticas'); const tarjeta = page .locator('article') .filter({ has: page.locator('a[href="/canciones/prometiste"]') }); await tarjeta.getByRole('link').first().click(); await expect(page).toHaveURL(/\/canciones\/prometiste$/); await expect(page.getByRole('tab', { name: 'Letra' })).toHaveAttribute('aria-selected', 'true'); }); test('la tarjeta permite añadir la canción a una lista de reproducción', async ({ page }) => { await page.goto('/musica/baladas-romanticas'); await page.locator('html[data-hidratado]').waitFor(); const tarjeta = page .locator('article') .filter({ has: page.locator('a[href="/canciones/prometiste"]') }); await tarjeta .getByRole('link', { name: 'Entra con tu cuenta para añadir a una lista de reproducción' }) .click(); await expect(page.getByRole('dialog', { name: 'Entrar o crear cuenta' })).toBeVisible(); await expect(page).toHaveURL(/\/musica\/baladas-romanticas$/); }); test('la tarjeta muestra el número de likes', async ({ page }) => { await page.goto('/musica/baladas-romanticas'); const tarjeta = page .locator('article') .filter({ has: page.locator('a[href="/canciones/prometiste"]') }); await expect( tarjeta.getByRole('link', { name: /Me gusta: \d+ likes?\. Inicia sesión/ }) ).toContainText(/^\d+$/); }); }); test.describe('Pestañas de la ficha', () => { test('la letra se abre por defecto', async ({ page }) => { await page.goto('/canciones/dorina'); await expect(page.getByRole('tab', { name: 'Letra' })).toHaveAttribute('aria-selected', 'true'); await expect(page.getByRole('tabpanel', { name: 'Letra' })).toBeVisible(); await expect(page.getByRole('tabpanel', { name: 'Vídeo' })).toBeHidden(); }); test('al pulsar una pestaña se cambia de panel', async ({ page }) => { await page.goto('/canciones/dorina'); await page.locator('html[data-hidratado]').waitFor(); await page.getByRole('tab', { name: 'Vídeo' }).click(); await expect(page).toHaveURL('/canciones/dorina#panel-videos'); await expect(page.getByRole('tabpanel', { name: 'Vídeo' })).toBeVisible(); await expect(page.getByRole('tabpanel', { name: 'Letra' })).toBeHidden(); await expect(page.getByText(/El vídeo se carga solo al pulsarlo/)).toBeVisible(); }); test('Atrás y Adelante restauran la pestaña de la canción', async ({ page }) => { await page.goto('/canciones/dorina'); await page.locator('html[data-hidratado]').waitFor(); await page.getByRole('tab', { name: 'Versiones' }).click(); await expect(page.getByRole('tabpanel', { name: 'Versiones' })).toBeVisible(); await page.getByRole('tab', { name: 'Vídeo' }).click(); await expect(page.getByRole('tabpanel', { name: 'Vídeo' })).toBeVisible(); await page.goBack(); await expect(page).toHaveURL('/canciones/dorina#panel-versiones'); await expect(page.getByRole('tabpanel', { name: 'Versiones' })).toBeVisible(); await page.goBack(); await expect(page).toHaveURL('/canciones/dorina'); await expect(page.getByRole('tabpanel', { name: 'Letra' })).toBeVisible(); await page.goForward(); await expect(page.getByRole('tabpanel', { name: 'Versiones' })).toBeVisible(); }); test('las flechas mueven entre pestañas', async ({ page }) => { await page.goto('/canciones/dorina'); await page.locator('html[data-hidratado]').waitFor(); await page.getByRole('tab', { name: 'Letra' }).focus(); await page.keyboard.press('ArrowRight'); await expect(page.getByRole('tab', { name: 'Versiones' })).toBeFocused(); await expect(page.getByRole('tabpanel', { name: 'Versiones' })).toBeVisible(); await page.keyboard.press('ArrowRight'); await expect(page.getByRole('tab', { name: 'Vídeo' })).toBeFocused(); const pestanas = page.getByRole('tab'); await page.keyboard.press('End'); await expect(pestanas.last()).toBeFocused(); await page.keyboard.press('ArrowRight'); await expect(pestanas.first()).toBeFocused(); }); test('versiones siempre incluye la principal y el vídeo solo aparece cuando existe', async ({ page }) => { await page.goto('/canciones/prometiste'); await expect(page.getByRole('tab', { name: 'Versiones' })).toBeVisible(); await expect(page.getByRole('tab', { name: 'Vídeo' })).toHaveCount(0); await expect(page.getByRole('tab', { name: 'Letra' })).toBeVisible(); }); test('se puede enlazar una pestaña concreta', async ({ page }) => { await page.goto('/canciones/dorina#panel-videos'); await expect(page.getByRole('tab', { name: 'Vídeo' })).toHaveAttribute('aria-selected', 'true'); await expect(page.getByRole('tabpanel', { name: 'Vídeo' })).toBeVisible(); }); test('un ancla que no existe deja la pestaña de siempre', async ({ page }) => { await page.goto('/canciones/dorina#panel-inventado'); await expect(page.getByRole('tab', { name: 'Letra' })).toHaveAttribute('aria-selected', 'true'); }); test('el contenido de las pestañas cerradas está en el HTML', async ({ page }) => { await page.goto('/canciones/dorina'); await expect(page.getByRole('tabpanel', { name: 'Vídeo' })).toBeHidden(); await expect(page.getByText(/El vídeo se carga solo al pulsarlo/)).toBeAttached(); }); }); test.describe('Idioma de la letra', () => { test('una letra francesa declara su idioma', async ({ page }) => { await page.goto('/canciones/ton-silence-me-libere'); await expect(page.locator('.letra > .texto')).toHaveAttribute('lang', 'fr'); await expect(page.getByText('francés', { exact: true })).toBeVisible(); await expect(page.getByText('Traducción al español')).toHaveCount(0); }); test('la letra francesa conserva el corte de los versos', async ({ page }) => { await page.goto('/canciones/ton-silence-me-libere'); const saltos = await page.locator('.letra > .texto br').count(); expect(saltos).toBeGreaterThan(4); }); test('un tema en español se queda en una sola columna', async ({ page }) => { await page.goto('/canciones/prometiste'); await expect(page.locator('.letra.pareada')).toHaveCount(0); await expect(page.getByText('Traducción al español')).toHaveCount(0); }); });