test(post): share createPhotoEntry helper and fix cleanup leak

Hoist the duplicated per-spec createEntry photo-fixture into a single
createPhotoEntry() in helpers.js (used by delete-flow, edit-mode, and the
anon-view draft). Register the tag for cleanup BEFORE the awaited 15s
success-toast assertion, so a create that lands on disk but whose toast
assertion times out no longer leaks an untracked entry. Add AE3b covering the
disclosure deviation branch (a non-default toggle auto-expands More options).

Code review F2 (leak), F3 (duplication), F6 (coverage).

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-07 08:32:09 +02:00
co-authored by Claude Opus 4.8
parent f4dbac6fc2
commit d576487886
5 changed files with 74 additions and 59 deletions
+37 -1
View File
@@ -2,6 +2,11 @@
const path = require('path');
const fs = require('fs');
const { execSync } = require('child_process');
const { expect } = require('@playwright/test');
// The shared photo fixture every create goes through (the post form gates submit
// on at least one uploaded photo).
const TEST_PHOTO = path.join(__dirname, '../fixtures/test-photo.jpg');
/**
* Resolve the Grav user directory.
@@ -134,6 +139,37 @@ async function postEntry(page, { titleTag, content = 'Automated test. Safe to de
return titleTag;
}
/**
* Create a fresh journal entry through the /post create form, with a photo
* attached so the submit gate is satisfied. Shared by the specs that need a
* disposable feed card to act on (delete-flow, edit-mode, anon-view draft).
*
* Pass the spec's `created` array so the tag is registered for cleanup BEFORE
* the (slow, 15s) success-toast assertion — a create that lands on disk but
* whose toast assertion times out would otherwise leak an entry the afterAll
* hook never sees. `publish:false` flips the Published toggle off to make a
* draft (the toggle is a visually-hidden radio pair behind "More options", so
* set state + fire `change` rather than fighting the visibility gate).
*/
async function createPhotoEntry(page, tag, { content, publish = true, created } = {}) {
await page.goto('/post');
await page.fill('input[name="data[title]"]', `UI Test ${tag}`);
await fillEditor(page, content || `Fixture for ${tag}. Safe to delete.`);
await page.locator('input.filepond--browser').setInputFiles(TEST_PHOTO);
await waitForPhotoUpload(page);
if (!publish) {
await page.evaluate(() => {
const off = document.querySelector('input[name="data[published]"][value="0"]');
off.checked = true;
off.dispatchEvent(new Event('change', { bubbles: true }));
});
}
await page.locator('.btn-post').evaluate(el => el.click());
if (created) created.push(tag);
await expect(page.locator('.form-messages, .notices')).toContainText(
'Entry posted successfully!', { timeout: 15_000 });
}
/**
* Find a tracker entry folder by a unique slug fragment, then delete it.
*/
@@ -166,4 +202,4 @@ function readEntryMd(entryDir) {
return fs.readFileSync(path.join(entryDir, name), 'utf-8');
}
module.exports = { fillEditor, waitForPhotoUpload, postEntry, cleanupEntry, findEntry, readEntryMd, TRACKER_DIR, ACTIVE_TRIP_URL };
module.exports = { fillEditor, waitForPhotoUpload, postEntry, createPhotoEntry, cleanupEntry, findEntry, readEntryMd, TEST_PHOTO, TRACKER_DIR, ACTIVE_TRIP_URL };