/** * T-series (UI) — Fase 3.1 * * Task lists overview at /tasks; per-list detail at /tasks/[id]. Members can * create, complete, edit, delete, and reorder tasks. Guests see them but * cannot mutate. Completed tasks sink to the bottom and show "Completed by …". */ import { test, expect, type Page } from '@playwright/test'; import { USERS } from '../fixtures/users.js'; import { loginAs } from '../fixtures/login.js'; const NEW_LIST_PLACEHOLDER = /new task list|nueva lista de tareas/i; const NEW_TASK_PLACEHOLDER = /add task|añadir tarea/i; async function gotoTasksClean(page: Page) { await page.goto('/tasks'); await expect( page.getByRole('button', { name: /new list|nueva lista/i }) ).toBeVisible({ timeout: 15_000 }); } async function createTaskList(page: Page, name: string) { // Masthead "New list" button → creates empty list and navigates to the detail. await page.getByRole('button', { name: /new list|nueva lista/i }).click(); await expect(page).toHaveURL(/\/tasks\/[0-9a-f-]+$/, { timeout: 10_000 }); const titleInput = page.getByPlaceholder(NEW_LIST_PLACEHOLDER); await expect(titleInput).toBeVisible({ timeout: 10_000 }); await titleInput.fill(name); await titleInput.blur(); await page.waitForTimeout(900); // autosave debounce } test.describe('Tasks — member (Borja)', () => { test.beforeEach(async ({ page }) => { await loginAs(page, USERS.borja); }); test('T-UI-01: create list, add task, complete it, see "Completed by …" tooltip', async ({ page }) => { await gotoTasksClean(page); const listName = `Tasks ${Date.now()}`; await createTaskList(page, listName); const taskInput = page.getByPlaceholder(NEW_TASK_PLACEHOLDER); await expect(taskInput).toBeVisible({ timeout: 5_000 }); const taskTitle = `Buy milk ${Date.now()}`; await taskInput.fill(taskTitle); await taskInput.press('Enter'); const row = page.locator('[role="listitem"]').filter({ hasText: taskTitle }); await expect(row).toBeVisible({ timeout: 5_000 }); // Toggle complete await row.getByRole('button', { name: /toggle task|alternar tarea/i }).click(); // Title is decorated as completed (line-through). Check via aria. await expect( page .locator('[role="listitem"]') .filter({ hasText: taskTitle }) .getByRole('button', { name: /uncheck task|desmarcar tarea/i }) ).toBeVisible({ timeout: 5_000 }); // "Completed by Borja" tooltip / metadata visible somewhere on the row. await expect( page.locator('[role="listitem"]').filter({ hasText: taskTitle }).getByText(/borja/i) ).toBeVisible({ timeout: 5_000 }); }); test('T-UI-02: delete a task removes it from the list', async ({ page }) => { await gotoTasksClean(page); const listName = `Tasks-del ${Date.now()}`; await createTaskList(page, listName); const taskInput = page.getByPlaceholder(NEW_TASK_PLACEHOLDER); const taskTitle = `Delete me ${Date.now()}`; await taskInput.fill(taskTitle); await taskInput.press('Enter'); const row = page.locator('[role="listitem"]').filter({ hasText: taskTitle }); await expect(row).toBeVisible({ timeout: 5_000 }); await row.getByRole('button', { name: /delete task|eliminar tarea/i }).click(); await expect( page.locator('[role="listitem"]').filter({ hasText: taskTitle }) ).toHaveCount(0, { timeout: 5_000 }); }); }); test.describe('Tasks — guest (David, read-only)', () => { test.beforeEach(async ({ page }) => { await loginAs(page, USERS.david); }); test('T-UI-03: guest can navigate to /tasks but cannot create lists', async ({ page }) => { await page.goto('/tasks'); // Either the create input is hidden, or it exists but typing into it // must NOT produce a committed list (RLS blocks the insert). const input = page.getByPlaceholder(NEW_LIST_PLACEHOLDER); if (await input.isVisible({ timeout: 2_000 }).catch(() => false)) { const name = `David sneaky ${Date.now()}`; await input.fill(name); await input.press('Enter'); await page.waitForTimeout(1_500); await expect( page.getByRole('heading', { name: new RegExp(`^${name}$`) }) ).not.toBeVisible({ timeout: 2_000 }); } }); });