test(post-form): add edit/delete/anon coverage and fix stale photo-gate assumptions

New specs: edit-mode (ES1 save round-trip + ES2/ES3 prefill 404/500 states),
delete-flow (DEL1-3 happy/cancel/failed), anon-view (AN1 no owner controls,
AN2 draft hidden from anon), photo-editor (live add/delete/reorder). Existing:
P3-P8 now attach a photo to satisfy the create photo-gate; V3 picker cap 4->6.
Full post suite 38/38, stable across parallel (3-worker) runs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
This commit is contained in:
2026-07-05 23:42:45 +02:00
co-authored by Claude Opus 4.8
parent 7534d7d178
commit d3c17791b7
7 changed files with 565 additions and 27 deletions
+97
View File
@@ -0,0 +1,97 @@
// @ts-check
// Tests: ES1ES3 — edit mode field SAVE round-trip + prefill error states.
//
// Complements photo-editor.spec.js (which covers the live photo add/delete/
// reorder inside edit mode). Here we cover the *text* side of edit mode:
// - ES1 drives a real end-to-end save: create → open the feed card's Edit link
// → change title + body → Save → assert the new values land back on disk.
// - ES2/ES3 mock the prefill fetch to force the two D7 failure branches
// (404 "no longer exists" vs a transient "couldn't be loaded") — the copy
// that tells the owner whether a retry is worthwhile. These had zero coverage.
const { test, expect } = require('@playwright/test');
const path = require('path');
const {
fillEditor, waitForPhotoUpload, cleanupEntry, findEntry, readEntryMd, ACTIVE_TRIP_URL,
} = require('../helpers');
const TEST_PHOTO = path.join(__dirname, '../../fixtures/test-photo.jpg');
// Synthetic route for the mocked error tests — never has to exist on disk.
const MISSING_ROUTE = '/trips/italy-2026-demo/dailies/does-not-exist';
const created = [];
test.afterAll(() => created.forEach(cleanupEntry));
/** Post a fresh entry through the create form (photo included, gate satisfied). */
async function createEntry(page, tag) {
await page.goto('/post');
await page.fill('input[name="data[title]"]', `UI Test ${tag}`);
await fillEditor(page, `Fixture for ${tag}. Safe to delete.`);
await page.locator('input.filepond--browser').setInputFiles(TEST_PHOTO);
await waitForPhotoUpload(page);
await page.locator('.btn-post').evaluate(el => el.click());
await expect(page.locator('.form-messages, .notices')).toContainText(
'Entry posted successfully!', { timeout: 15_000 });
}
// ── ES1: edit-mode save writes the changed title + body back to disk ──────────
test('ES1: editing an entry saves the changed title and body back in place', async ({ page }) => {
const tag = `es1-${Date.now()}`;
await createEntry(page, tag);
created.push(tag);
// Reach edit mode the way the owner does: via the feed card's Edit link.
await page.goto(ACTIVE_TRIP_URL);
const card = page.locator('.journal-post', { hasText: tag });
await expect(card).toHaveCount(1);
const editHref = await card.locator('.entry-action--edit').getAttribute('href');
expect(editHref).toContain('/post?edit=');
await page.goto(editHref);
// Prefill is async (GET /api/v1/pages{route}); wait until it populates.
await expect(page.locator('input[name="data[title]"]'))
.toHaveValue(`UI Test ${tag}`, { timeout: 15_000 });
await page.fill('input[name="data[title]"]', `UI Test ${tag} EDITED`);
await fillEditor(page, `Edited body for ${tag}.`);
await page.locator('.btn-post').evaluate(el => el.click());
// overwrite_mode:edit writes back in place — assert both changes on disk.
await expect.poll(() => {
const dir = findEntry(tag);
return dir ? (readEntryMd(dir) || '') : '';
}, { timeout: 15_000 }).toContain('EDITED');
const md = readEntryMd(findEntry(tag));
expect(md, 'edited body should persist').toContain(`Edited body for ${tag}.`);
});
// ── ES2: prefill 404 → the "no longer exists" (deleted) branch ────────────────
test('ES2: opening a deleted entry for editing shows the "no longer exists" notice', async ({ page }) => {
await page.route('**/api/v1/pages/**', (route) => {
if (route.request().method() === 'GET') return route.fulfill({ status: 404, body: '' });
return route.continue();
});
await page.goto('/post?edit=' + encodeURIComponent(MISSING_ROUTE));
const banner = page.locator('.post-edit-error');
await expect(banner).toContainText('no longer exists', { timeout: 15_000 });
await expect(banner).toHaveAttribute('role', 'alert');
// D7: the form is left empty rather than half-filled.
await expect(page.locator('input[name="data[title]"]')).toHaveValue('');
});
// ── ES3: prefill 500 → the transient "couldn't be loaded" (retry) branch ──────
test('ES3: a transient prefill failure shows the retry-able "be loaded" notice', async ({ page }) => {
await page.route('**/api/v1/pages/**', (route) => {
if (route.request().method() === 'GET') return route.fulfill({ status: 500, body: '' });
return route.continue();
});
await page.goto('/post?edit=' + encodeURIComponent(MISSING_ROUTE));
// Copy differs from the 404 case so the owner knows a retry is worthwhile.
await expect(page.locator('.post-edit-error'))
.toContainText('be loaded for editing', { timeout: 15_000 });
await expect(page.locator('input[name="data[title]"]')).toHaveValue('');
});