/** * O-series: /onboarding — collective creation (Fase 7.1). * * Eva is the seeded no-collective user; the app layout redirects her to * /onboarding automatically after login. Each test resets her state in * beforeEach so tests don't leak into each other. */ import { test, expect } from '@playwright/test'; import { USERS } from '../fixtures/users.js'; import { loginAs } from '../fixtures/login.js'; import { resetEva } from '../fixtures/db.js'; import { closePool, sql } from '@colectivo/test-utils'; test.describe.configure({ mode: 'serial' }); test.describe('Onboarding', () => { test.beforeEach(async () => { await resetEva(); }); test.afterAll(async () => { await resetEva(); await closePool(); }); test('O-01: non-member Eva is auto-redirected to /onboarding after login', async ({ page }) => { await loginAs(page, USERS.eva, { waitForCollective: false }); // The root +layout.svelte sends users with zero collectives to /onboarding. await expect(page).toHaveURL(/\/onboarding$/, { timeout: 15_000 }); // Create-tab form should be visible by default. await expect(page.getByTestId('collective-name-input')).toBeVisible(); await expect(page.getByTestId('collective-submit')).toBeVisible(); }); test('O-02: happy path — name + emoji → redirect to /lists + sidebar updates', async ({ page }) => { await loginAs(page, USERS.eva, { waitForCollective: false }); await expect(page).toHaveURL(/\/onboarding$/, { timeout: 15_000 }); const name = `Eva Home ${Date.now()}`; await page.getByTestId('collective-name-input').fill(name); await page.getByRole('button', { name: '🏡', exact: true }).click(); await page.getByTestId('collective-submit').click(); // Landing page after creation. await expect(page).toHaveURL(/\/lists$/, { timeout: 15_000 }); // Sidebar (desktop) shows the new name with the chosen emoji. const sidebar = page.getByTestId('desktop-sidebar'); await expect(sidebar.getByText(name, { exact: false })).toBeVisible({ timeout: 10_000 }); await expect(sidebar.getByText('🏡', { exact: false })).toBeVisible(); // localStorage cached the active collective id matching the DB row. const activeId = await page.evaluate(() => localStorage.getItem('activeCollectiveId')); expect(activeId).toBeTruthy(); const res = await sql('SELECT id, name, emoji FROM public.collectives WHERE id = $1', [ activeId ]); expect(res.rowCount).toBe(1); expect(res.rows[0].name).toBe(name); expect(res.rows[0].emoji).toBe('🏡'); // Eva is admin of the new collective. const m = await sql( 'SELECT role FROM public.collective_members WHERE collective_id = $1 AND user_id = $2', [activeId, USERS.eva.id] ); expect(m.rowCount).toBe(1); expect(m.rows[0].role).toBe('admin'); }); test('O-03: empty name → submit disabled, no POST fires', async ({ page }) => { await loginAs(page, USERS.eva, { waitForCollective: false }); await expect(page).toHaveURL(/\/onboarding$/, { timeout: 15_000 }); // With an empty name, the submit button is disabled by `disabled={!collectiveName.trim()}`. const submit = page.getByTestId('collective-submit'); await expect(submit).toBeDisabled(); // A whitespace-only name should still count as empty. await page.getByTestId('collective-name-input').fill(' '); await expect(submit).toBeDisabled(); // No collective rows created for Eva. const res = await sql('SELECT count(*)::int AS n FROM public.collectives WHERE created_by = $1', [ USERS.eva.id ]); expect(res.rows[0].n).toBe(0); }); });