/** * N-series (UI) — Fase 3.2 * * Notes board at /notes; editor at /notes/[id]. Create, change color, pin, * archive, trash, restore. Guest is read-only. */ import { test, expect, type Page } from '@playwright/test'; import { USERS } from '../fixtures/users.js'; import { loginAs } from '../fixtures/login.js'; async function gotoNotes(page: Page) { await page.goto('/notes'); await expect( page.getByRole('button', { name: /new note|nueva nota/i }) ).toBeVisible({ timeout: 15_000 }); } async function createNote(page: Page, title: string, content: string) { await page.getByRole('button', { name: /new note|nueva nota/i }).click(); await expect(page).toHaveURL(/\/notes\/[0-9a-f-]+$/, { timeout: 10_000 }); const titleInput = page.getByPlaceholder(/title|título/i); await titleInput.fill(title); const contentInput = page.getByRole('textbox', { name: /content|contenido/i }); await contentInput.fill(content); // Wait beyond the 500ms autosave debounce + buffer. await page.waitForTimeout(900); } test.describe('Notes — member (Borja)', () => { test.beforeEach(async ({ page }) => { await loginAs(page, USERS.borja); }); test('N-UI-01: create + autosave + back to board shows the note', async ({ page }) => { await gotoNotes(page); const title = `Note ${Date.now()}`; await createNote(page, title, 'autosave content'); await page.goto('/notes'); await expect( page.getByRole('heading', { name: new RegExp(`^${title}$`) }) ).toBeVisible({ timeout: 5_000 }); }); test('N-UI-02: pin a note → it appears in the pinned section', async ({ page }) => { await gotoNotes(page); const title = `Pin ${Date.now()}`; await createNote(page, title, 'pin me'); await page.getByRole('button', { name: /pin note|fijar nota/i }).click(); await page.waitForTimeout(800); await page.goto('/notes'); const pinnedSection = page.getByTestId('notes-pinned'); await expect(pinnedSection.getByText(title)).toBeVisible({ timeout: 5_000 }); }); test('N-UI-03: trash → restore from /notes/trash', async ({ page }) => { await gotoNotes(page); const title = `Trash ${Date.now()}`; await createNote(page, title, 'trash me'); await page.getByRole('button', { name: /move to trash|enviar a la papelera/i }).click(); await page.waitForTimeout(800); await page.goto('/notes'); await expect( page.getByRole('heading', { name: new RegExp(`^${title}$`) }) ).not.toBeVisible({ timeout: 3_000 }); await page.goto('/notes/trash'); const trashedRow = page .locator('[role="listitem"], [data-testid="note-card"]') .filter({ hasText: title }) .first(); await expect(trashedRow).toBeVisible({ timeout: 5_000 }); await trashedRow.getByRole('button', { name: /restore|restaurar/i }).click(); await page.waitForTimeout(800); await page.goto('/notes'); await expect( page.getByRole('heading', { name: new RegExp(`^${title}$`) }) ).toBeVisible({ timeout: 5_000 }); }); }); test.describe('Notes — guest (David, read-only)', () => { test.beforeEach(async ({ page }) => { await loginAs(page, USERS.david); }); test('N-UI-04: guest can read the board, create button is hidden or no-op', async ({ page }) => { await page.goto('/notes'); // The "new note" button must either be absent, or clicking it must not // land us on a /notes/[id] route (RLS blocks the insert). const newBtn = page.getByRole('button', { name: /new note|nueva nota/i }); if (await newBtn.isVisible({ timeout: 2_000 }).catch(() => false)) { await newBtn.click(); await page.waitForTimeout(1_500); await expect(page).not.toHaveURL(/\/notes\/[0-9a-f-]+$/); } }); });