Files
m038andClaude Opus 4.8 2e96106c84 test(post): DEL4 — a deleted entry stays gone after a page reload
Guards the page-tree-index staleness fix. DEL1 only checked optimistic DOM
removal + disk; DEL4 reloads and asserts the server no longer renders the card.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
2026-07-08 10:29:21 +02:00

101 lines
5.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// @ts-check
// Tests: DEL1DEL3 — 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/<slug>.
//
// 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();
});
// ── DEL4: a deleted entry stays gone after a full page reload ─────────────────
// Regression for the page-tree-index staleness bug: deleteEntry did
// cache.deleteAll() but not Cache::invalidateCache(), so with
// cache.check.method: folder the deleted child lingered in the pages index and
// the SERVER re-rendered the (now image-less) card on the next load — even
// though its folder was gone from disk. DEL1 only checks the optimistic DOM
// removal + disk, so it missed this. Here we reload and assert the server no
// longer emits the card.
test('DEL4: a deleted entry is absent from the feed after a fresh page load', async ({ page }) => {
const tag = `del4-${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 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();
// The real test: a fresh server render must not resurrect the entry.
await page.goto(ACTIVE_TRIP_URL);
await expect(page.locator('.journal-post', { hasText: tag })).toHaveCount(0);
});
// ── 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();
});