diff --git a/apps/web/src/routes/(app)/lists/[id]/+page.svelte b/apps/web/src/routes/(app)/lists/[id]/+page.svelte index 0c8547e..58296e7 100644 --- a/apps/web/src/routes/(app)/lists/[id]/+page.svelte +++ b/apps/web/src/routes/(app)/lists/[id]/+page.svelte @@ -690,6 +690,22 @@ > {m.list_status_archived()} + {:else if list?.status === 'completed'} + + {/if} diff --git a/apps/web/tests/e2e/reset-from-detail.test.ts b/apps/web/tests/e2e/reset-from-detail.test.ts new file mode 100644 index 0000000..791f062 --- /dev/null +++ b/apps/web/tests/e2e/reset-from-detail.test.ts @@ -0,0 +1,72 @@ +/** + * RST-series: reset list from /lists/[id] (Fase 10.7). + * + * The completed-list detail view exposes a prominent "Reset list" button + * next to the title. Clicking it brings the list back to status='active' + * without leaving the page. + */ +import { test, expect } from '@playwright/test'; +import { USERS } from '../fixtures/users.js'; +import { loginAs } from '../fixtures/login.js'; +import { closePool, sql, COLLECTIVE_ID, BORJA_ID } from '@colectivo/test-utils'; + +test.describe.configure({ mode: 'serial' }); + +const created: { id: string | null } = { id: null }; + +test.describe('Reset list from detail view (10.7)', () => { + test.beforeEach(async () => { + // Seed a completed list with one checked item. + const r = await sql( + `INSERT INTO public.shopping_lists (collective_id, name, status, completed_at, created_by) + VALUES ($1, 'Reset-from-detail', 'completed', now(), $2) + RETURNING id`, + [COLLECTIVE_ID, BORJA_ID] + ); + created.id = r.rows[0].id as string; + await sql( + `INSERT INTO public.shopping_items (list_id, name, sort_order, created_by, is_checked, checked_by, checked_at) + VALUES ($1, 'Milk', 1, $2, true, $2, now())`, + [created.id, BORJA_ID] + ); + }); + + test.afterEach(async () => { + if (created.id) { + await sql('DELETE FROM public.shopping_lists WHERE id = $1', [created.id]); + created.id = null; + } + }); + + test.afterAll(async () => { + await closePool(); + }); + + test('RST-01: completed list shows a Reset button that brings it back to active', async ({ + page + }) => { + await loginAs(page, USERS.borja); + await page.goto(`/lists/${created.id}`); + + const resetBtn = page.getByTestId('detail-reset-button'); + await expect(resetBtn).toBeVisible({ timeout: 15_000 }); + await resetBtn.click(); + + // Button should disappear once the list is back to active. + await expect(resetBtn).toHaveCount(0, { timeout: 5_000 }); + + const row = await sql( + 'SELECT status::text AS status, completed_at FROM public.shopping_lists WHERE id = $1', + [created.id] + ); + expect(row.rows[0].status).toBe('active'); + expect(row.rows[0].completed_at).toBeNull(); + + // Items uncheked. + const items = await sql( + 'SELECT bool_and(NOT is_checked) AS all_unchecked FROM public.shopping_items WHERE list_id = $1', + [created.id] + ); + expect(items.rows[0].all_unchecked).toBe(true); + }); +});