The warnings added in 6398542 were wrong. UG1, UG2 and LD1 were failing
because this branch predated e17a5dc, not because the behaviour they assert
was missing: merging user/main brought the FilePond upload gate and the
oriented-derivative slide dims, and all three pass with no product change.
Headers now point at e17a5dc for both mechanisms. Also corrects the plan's
.env note — the env layering is intentional (.env global, .env.<ENV> per
environment via the generated remote-*-<env> targets); the actual fault is
just that `-include .env` additionally requires makefile-valid syntax and
line 6 is not, which breaks make in both non-worktree clones.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
81 lines
4.1 KiB
JavaScript
81 lines
4.1 KiB
JavaScript
// @ts-check
|
||
// Tests: UG1–UG2 — the create form must never submit while a photo is not
|
||
// fully uploaded (BUG 2026-07-09: a fast save after adding a picture posted a
|
||
// text-only entry; the photo was silently dropped).
|
||
//
|
||
// The form plugin's own submit guard (filepond-handler.js) only blocks the
|
||
// PROCESSING / PROCESSING_QUEUED states. Two states slip through it:
|
||
// - UG1: LOADING — the moment between picking a file and it entering the
|
||
// upload queue (the "too quick" click). Guarded here with a slowed upload.
|
||
// - UG2: PROCESSING_ERROR — a failed upload keeps its thumbnail, passes the
|
||
// ≥1-photo validation, and the form posts without the file. This is the
|
||
// silent-data-loss path.
|
||
// post-form.js owns the complete gate (theme code; the form plugin is
|
||
// GPM-managed and not patchable in-repo).
|
||
//
|
||
// The gate lives in e17a5dc: submit is blocked unless EVERY FilePond item is
|
||
// processing-complete, with distinct messages for the failed and still-uploading
|
||
// cases. Both assert on .photo-convert-status, which post-form.js's setStatus()
|
||
// creates via photoStatusEl() — so a passing expectation here proves the THEME
|
||
// gate fired, not the form plugin's, whose own guard only raises alert().
|
||
const { test, expect } = require('@playwright/test');
|
||
const { fillEditor, findEntry, cleanupEntry, TEST_PHOTO } = require('../helpers');
|
||
|
||
// FilePond uploads go to the form route with .json + the file-upload task
|
||
// (Form.php:1183: withExtension('json')->withGravParam('task','file-upload')),
|
||
// i.e. /post.json/task:file-upload — the task is a PATH segment, so a glob
|
||
// with a non-slash-crossing `*` misses it; match by regex instead.
|
||
const UPLOAD_URL = /\/post\.json\//;
|
||
|
||
const created = [];
|
||
test.afterAll(() => created.forEach(cleanupEntry));
|
||
|
||
async function fillCreateForm(page, tag) {
|
||
await page.goto('/post');
|
||
await page.fill('input[name="data[title]"]', `UI Test ${tag}`);
|
||
await fillEditor(page, `Upload-gate fixture ${tag}. Safe to delete.`);
|
||
}
|
||
|
||
// ── UG1: submit while the upload is still in flight is blocked ────────────────
|
||
test('UG1: submitting while a photo upload is in flight is blocked with a message', async ({ page }) => {
|
||
const tag = `ug1-${Date.now()}`;
|
||
|
||
// Slow the upload down so the submit click lands mid-flight.
|
||
await page.route(UPLOAD_URL, async (route) => {
|
||
await new Promise((r) => setTimeout(r, 6000));
|
||
await route.continue();
|
||
});
|
||
|
||
await fillCreateForm(page, tag);
|
||
await page.locator('input.filepond--browser').setInputFiles(TEST_PHOTO);
|
||
// The item exists but cannot have finished uploading (route is held).
|
||
await page.waitForSelector('.filepond--item');
|
||
await page.locator('.btn-post').evaluate((el) => el.click());
|
||
created.push(tag);
|
||
|
||
// Blocked: visible feedback, no success notice, nothing written to disk.
|
||
await expect(page.locator('.photo-convert-status')).toContainText(/uploading/i);
|
||
await expect(page.locator('.notices.success')).toHaveCount(0);
|
||
expect(findEntry(tag), 'no entry may be created mid-upload').toBeNull();
|
||
});
|
||
|
||
// ── UG2: submit with a FAILED upload is blocked, not silently posted ──────────
|
||
test('UG2: submitting after a photo upload failed is blocked with an error', async ({ page }) => {
|
||
const tag = `ug2-${Date.now()}`;
|
||
|
||
// Make the upload fail server-side (transient network/limit failure).
|
||
await page.route(UPLOAD_URL, (route) => route.fulfill({ status: 500, body: 'nope' }));
|
||
|
||
await fillCreateForm(page, tag);
|
||
await page.locator('input.filepond--browser').setInputFiles(TEST_PHOTO);
|
||
// Wait for FilePond to mark the item as failed.
|
||
await page.waitForSelector('.filepond--item[data-filepond-item-state*="error"]', { timeout: 20_000 });
|
||
await page.locator('.btn-post').evaluate((el) => el.click());
|
||
created.push(tag);
|
||
|
||
// Blocked: the error is surfaced, the form did not post, no disk write.
|
||
await expect(page.locator('.photo-convert-status')).toContainText(/failed/i);
|
||
await expect(page.locator('.notices.success')).toHaveCount(0);
|
||
expect(findEntry(tag), 'a failed upload must never produce a photo-less entry').toBeNull();
|
||
});
|