Files
intotheeast-com/tests/ui/post/upload-gate.spec.js
T
m038andClaude Opus 5 6398542845 test(post): resolve USER_DIR via helpers; record why UG1/UG2/LD1 are red
lightbox-dims.spec.js hardcoded ../../../user, so a run against a checkout
detached from the served tree planted its fixture in a different user/ than
Grav renders and LD1 failed as an opaque "card never appeared" timeout.
Take USER_DIR from helpers instead, which honours GRAV_USER_DIR.

The three specs in this folder that fail do so for real, pre-existing
reasons, and both files' headers implied otherwise:

- UG1/UG2 specify a submit gate that is not implemented. post-form.js's
  only create-form guard is `converting > 0` (pre-FilePond HEIC
  conversion); it never inspects FilePond item state at submit time, and
  .photo-convert-status is created lazily by photoStatusEl() only from the
  HEIC paths — so for a plain JPEG the element never exists and both
  expectations fail as "element(s) not found". UG2 is the one that matters:
  a failed upload keeping its thumbnail is unguarded silent data loss.

- LD1's header described its root cause in the past tense, reading as
  fixed. entry-journal.html.twig:48-49 still emits {{ img.width }} /
  {{ img.height }}, so EXIF-rotated photos still declare pre-rotation dims
  and PhotoSwipe still squeezes them.

Left failing rather than skipped, per retries:0 — a red test here is a real
defect, and hiding these would lose both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-24 23:28:18 +02:00

89 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// @ts-check
// Tests: UG1UG2 — 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).
//
// ⚠️ BOTH CASES CURRENTLY FAIL — the gate they specify is NOT implemented.
// post-form.js's only create-form submit guard is `converting > 0` (the
// pre-FilePond HEIC conversion, "Hang on — a photo is still converting."). It
// never inspects FilePond's item state at submit time. `.photo-convert-status`
// is created lazily by photoStatusEl(), which only runs from setStatus() on the
// HEIC paths — so for a plain JPEG the element never exists and both
// expectations below fail as "element(s) not found", not as a wrong message.
// refreshCollapse() does read data-filepond-item-state, but only to word the
// <summary> ("Uploading N photos…"); it gates nothing.
// These are therefore red specs describing intended behaviour. UG2 is the one
// that matters: a failed upload keeping its thumbnail is a silent-data-loss
// path with no guard. Left failing rather than skipped so the gap stays visible
// — see the plan's open items.
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();
});