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
98 lines
4.7 KiB
JavaScript
98 lines
4.7 KiB
JavaScript
// @ts-check
|
||
// Tests: ES1–ES3 — 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('');
|
||
});
|