// @ts-check // Tests: DEL1–DEL3 — the owner entry-delete flow (feed-actions.js + the // entry-actions `deleteEntry` route). Delete is a two-step inline confirm on a // feed card: Delete → Cancel / Confirm delete → DELETE /api/v1/entry/. // // This flow — a destructive, owner-only action — had zero automated coverage. // - DEL1: full happy path — the card vanishes AND the folder leaves disk. // - DEL2: Cancel is a real escape hatch — nothing is deleted. // - DEL3: a failed DELETE keeps the card and surfaces the inline error. const { test, expect } = require('@playwright/test'); const { createPhotoEntry, cleanupEntry, findEntry, ACTIVE_TRIP_URL, } = require('../helpers'); const created = []; // cleanupEntry is a no-op when the entry was already deleted by the test. test.afterAll(() => created.forEach(cleanupEntry)); // ── DEL1: happy delete removes the card and the folder ──────────────────────── test('DEL1: owner deletes an entry — the card disappears and the folder is removed', async ({ page }) => { const tag = `del1-${Date.now()}`; await createPhotoEntry(page, tag, { created, content: `Delete-flow fixture ${tag}. Safe to delete.` }); await page.goto(ACTIVE_TRIP_URL); const card = page.locator('.journal-post', { hasText: tag }); await expect(card).toHaveCount(1); // The owner-only controls must be present — this also asserts the auth gate. await card.locator('[data-delete-start]').click(); await card.locator('[data-delete-confirm]').click(); await expect(page.locator('.journal-post', { hasText: tag })) .toHaveCount(0, { timeout: 15_000 }); await expect.poll(() => findEntry(tag), { timeout: 15_000 }).toBeNull(); }); // ── DEL2: Cancel keeps the entry ────────────────────────────────────────────── test('DEL2: cancelling the confirm step keeps the entry on the page and on disk', async ({ page }) => { const tag = `del2-${Date.now()}`; await createPhotoEntry(page, tag, { created, content: `Delete-flow fixture ${tag}. Safe to delete.` }); await page.goto(ACTIVE_TRIP_URL); const card = page.locator('.journal-post', { hasText: tag }); await expect(card).toHaveCount(1); await card.locator('[data-delete-start]').click(); await expect(card.locator('.entry-delete-confirm')).toBeVisible(); await card.locator('[data-delete-cancel]').click(); await expect(card.locator('.entry-delete-confirm')).toBeHidden(); await expect(card).toHaveCount(1); expect(findEntry(tag), 'a cancelled delete must not remove the folder').not.toBeNull(); }); // ── DEL3: a failed DELETE keeps the card and shows the inline error ─────────── test('DEL3: a failed delete keeps the card and surfaces the inline error', async ({ page }) => { const tag = `del3-${Date.now()}`; await createPhotoEntry(page, tag, { created, content: `Delete-flow fixture ${tag}. Safe to delete.` }); await page.goto(ACTIVE_TRIP_URL); // Force the delete request to fail after the confirm. await page.route('**/api/v1/entry/**', (route) => { if (route.request().method() === 'DELETE') return route.fulfill({ status: 500, body: '' }); return route.continue(); }); const card = page.locator('.journal-post', { hasText: tag }); await expect(card).toHaveCount(1); await card.locator('[data-delete-start]').click(); await card.locator('[data-delete-confirm]').click(); await expect(card.locator('.entry-delete-msg')) .toContainText('Could not delete', { timeout: 15_000 }); await expect(card).toHaveCount(1); // the card survives a failed delete expect(findEntry(tag), 'a failed delete must not remove the folder').not.toBeNull(); });