feat(fase-10): reset list button on detail view (10.7)
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 <noreply@anthropic.com>
This commit is contained in:
@@ -690,6 +690,22 @@
|
||||
>
|
||||
{m.list_status_archived()}
|
||||
</span>
|
||||
{:else if list?.status === 'completed'}
|
||||
<!-- Fase 10.7: prominent reset CTA on the completed-list view.
|
||||
The 3-dot menu already exposes the same action, but landing
|
||||
on a list you just finished and not seeing a way to start
|
||||
over without bouncing back to /lists is the UX trap. -->
|
||||
<button
|
||||
type="button"
|
||||
data-testid="detail-reset-button"
|
||||
onclick={handleReset}
|
||||
class="shrink-0 inline-flex items-center gap-1.5 rounded-full bg-slate-900 px-3 py-1
|
||||
text-xs font-semibold text-white hover:bg-slate-800
|
||||
dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-200"
|
||||
>
|
||||
<RotateCcw size={12} strokeWidth={2} />
|
||||
{m.list_reset()}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
72
apps/web/tests/e2e/reset-from-detail.test.ts
Normal file
72
apps/web/tests/e2e/reset-from-detail.test.ts
Normal file
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user