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
+22 -17
View File
@@ -15,36 +15,31 @@ test.afterAll(() => {
created.forEach(cleanupEntry);
});
// ── P1: Post without photo ─────────────────────────────────────────────────────
test('P1: post text-only entry → created on disk and visible in trip feed', async ({ page }) => {
// ── P1: A photo is required — a text-only submit is blocked, writing nothing ───
// Create mode requires ≥1 photo (post-form.js gate). The UX suite asserts the
// inline error + suppressed notice; P1 is the complementary DISK-level guarantee
// that a blocked submit never lands an entry on disk.
test('P1: text-only submit is blocked by the photo gate and creates no entry', async ({ page }) => {
const tag = `p1-${Date.now()}`;
const title = `UI Test ${tag}`;
await page.goto('/post');
await page.fill('input[name="data[title]"]', title);
await fillEditor(page, 'Text-only test entry. Safe to delete.');
await fillEditor(page, 'Text-only entry. Should be rejected — no photo.');
await page.fill('input[name="data[location_city]"]', 'Testville');
await page.fill('input[name="data[location_country]"]', 'Testland');
await page.locator('.btn-post').evaluate(el => el.click());
await page.waitForSelector('.form-messages, .notices', { timeout: 15_000 });
const entryDir = findEntry(tag);
expect(entryDir, 'Entry folder should exist on disk').toBeTruthy();
created.push(tag);
// The photo gate fires an inline error and suppresses the success notice.
await expect(page.locator('.photos-collapse .field-error')).toContainText('at least one photo');
await expect(page.locator('.notices.success, .notices.green')).toHaveCount(0);
const md = readEntryMd(entryDir);
expect(md).toContain(tag);
// No photo expected
const photos = fs.readdirSync(entryDir).filter(f => /\.(jpg|jpeg|png|webp|heic)$/i.test(f));
expect(photos.length, 'Text-only entry should have no photos').toBe(0);
await page.goto(ACTIVE_TRIP_URL);
await expect(page.locator('body')).toContainText(tag);
// The crucial guarantee: nothing was written to disk.
expect(findEntry(tag), 'a blocked submit must not create an entry').toBeNull();
});
// ── P2: Post with photo ────────────────────────────────────────────────────────
test.skip('P2: post entry with photo → photo saved in entry folder and visible in trip feed', async ({ page }) => {
test('P2: post entry with photo → photo saved in entry folder and visible in trip feed', async ({ page }) => {
const tag = `p2-${Date.now()}`;
const title = `UI Test ${tag}`;
@@ -84,6 +79,8 @@ test('P3: post entry with city/country → frontmatter contains location', async
await fillEditor(page, 'Location test. Safe to delete.');
await page.fill('input[name="data[location_city]"]', 'Kyoto');
await page.fill('input[name="data[location_country]"]', 'Japan');
await page.locator('input.filepond--browser').setInputFiles(TEST_PHOTO);
await waitForPhotoUpload(page);
await page.locator('.btn-post').evaluate(el => el.click());
await page.waitForSelector('.form-messages, .notices', { timeout: 15_000 });
@@ -110,6 +107,8 @@ test('P4: post entry with lat/lng → coordinates saved in frontmatter', async (
document.querySelector('input[name="data[lat]"]').value = '35.6762';
document.querySelector('input[name="data[lng]"]').value = '139.6503';
});
await page.locator('input.filepond--browser').setInputFiles(TEST_PHOTO);
await waitForPhotoUpload(page);
await page.locator('.btn-post').evaluate(el => el.click());
await page.waitForSelector('.form-messages, .notices', { timeout: 15_000 });
@@ -143,6 +142,8 @@ test('P6: successful submit shows "Entry posted successfully!" message', async (
await page.goto('/post');
await page.fill('input[name="data[title]"]', `UI Test ${tag}`);
await fillEditor(page, 'P6 test. 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 }
@@ -159,6 +160,8 @@ test('P7: submitted entry is saved with a date within 5 minutes of now', async (
await page.goto('/post');
await page.fill('input[name="data[title]"]', `UI Test ${tag}`);
await fillEditor(page, 'P7 date test. 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 }
@@ -185,6 +188,8 @@ test('P8: title and content fields are empty after a successful submit', async (
await page.goto('/post');
await page.fill('input[name="data[title]"]', `UI Test ${tag}`);
await fillEditor(page, 'P8 reset test. 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 }