From 2924b9432924f4d14683364b34c8577c38ba95fc Mon Sep 17 00:00:00 2001 From: Oier Bravo Urtasun Date: Mon, 18 May 2026 02:26:41 +0200 Subject: [PATCH] feat(fase-10): reset list button on detail view (10.7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A list shown at status=completed used to have its only Reset action buried in the 3-dot menu, forcing the user back to /lists to start over. Now the title row of /lists/[id] renders a prominent "Reset list" pill button when status === 'completed' — reusing the existing handleReset() handler, no new logic. The 3-dot menu entry stays for the active and archived views. RST-01 seeds a completed list with a checked item, clicks the new button, verifies status flips back to active + the item gets unchecked. Co-Authored-By: Claude Opus 4.7 --- .../src/routes/(app)/lists/[id]/+page.svelte | 16 +++++ apps/web/tests/e2e/reset-from-detail.test.ts | 72 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 apps/web/tests/e2e/reset-from-detail.test.ts 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); + }); +});