From 27a35a1db8f2bccdc1e978025fec85245382e57b Mon Sep 17 00:00:00 2001 From: Mischa Date: Sat, 4 Jul 2026 17:52:09 +0200 Subject: [PATCH] test(post-form): realign photo specs to FilePond after U4 rework waitForPhotoUpload waits on FilePond processing-complete; AE1/AE4 use input.filepond--browser + the .photo-convert-status error status; V3/V4 back to FilePond limit + non-image rejection; reauth hint -> .photo-reauth-hint; test-form-config asserts type:filepond. Verified against a live server via a browser smoke run (HEIC->JPEG attach, corrupt fail-closed, draft restore). --- scripts/test-form-config.sh | 6 ++--- tests/ui/helpers.js | 12 ++++++--- tests/ui/post/post-form-ux.spec.js | 13 +++++----- tests/ui/post/post.spec.js | 2 +- tests/ui/post/validation.spec.js | 40 +++++++++++++++++------------- 5 files changed, 42 insertions(+), 31 deletions(-) diff --git a/scripts/test-form-config.sh b/scripts/test-form-config.sh index 9fc89d5..c8badfd 100755 --- a/scripts/test-form-config.sh +++ b/scripts/test-form-config.sh @@ -63,9 +63,9 @@ check_grep "hero_image field present" "name: hero_image" check_grep "force_connect field present" "name: force_connect" check_grep "featured field present" "name: featured" -# Photos must use the custom controlled picker (U4), not filepond/file — that is -# what lets post-form.js convert HEIC before upload. -check_grep "photos field uses the custom 'photos' type (HEIC pipeline)" "type: photos" +# Photos use Grav's filepond field; post-form.js hooks its beforeAddFile to +# convert HEIC->JPEG before FilePond uploads (U4). +check_grep "photos field uses the filepond type" "type: filepond" echo "────────────────────────────────────────" echo " $PASS passed, $FAIL failed" diff --git a/tests/ui/helpers.js b/tests/ui/helpers.js index 04e6f07..2fc172f 100644 --- a/tests/ui/helpers.js +++ b/tests/ui/helpers.js @@ -93,14 +93,18 @@ async function fillEditor(page, text) { } /** - * Wait for the custom photo picker to finish converting + uploading photos - * (each successfully attached photo gets a `.photo-card.is-done`). + * Wait for photos to finish uploading. post-form.js converts HEIC->JPEG and + * hands files to FilePond via pond.addFile(); FilePond then uploads each, and a + * finished item reaches data-filepond-item-state="processing-complete". */ async function waitForPhotoUpload(page, count = 1) { await page.waitForFunction( - (n) => document.querySelectorAll('.photo-card.is-done').length >= n, + (n) => { + const items = document.querySelectorAll('.filepond--item[data-filepond-item-state]'); + return [...items].filter(el => el.getAttribute('data-filepond-item-state') === 'processing-complete').length >= n; + }, count, - { timeout: 30_000 } + { timeout: 40_000 } ); } diff --git a/tests/ui/post/post-form-ux.spec.js b/tests/ui/post/post-form-ux.spec.js index 1390e83..38c7f2b 100644 --- a/tests/ui/post/post-form-ux.spec.js +++ b/tests/ui/post/post-form-ux.spec.js @@ -31,9 +31,9 @@ test('AE1: a HEIC photo is converted to JPEG client-side and posted', async ({ p await page.fill('input[name="data[title]"]', `UI Test ${tag}`); await fillEditor(page, 'HEIC conversion test. Safe to delete.'); - await page.locator('input.photo-picker__input').setInputFiles(TEST_HEIC); - await waitForPhotoUpload(page, 1); // is-done == converted + uploaded - await expect(page.locator('.photo-card.is-done .photo-card__thumb')).toBeVisible(); + await page.locator('input.filepond--browser').setInputFiles(TEST_HEIC); + await waitForPhotoUpload(page, 1); // converted (beforeAddFile) + uploaded via FilePond + await expect(page.locator('.filepond--item[data-filepond-item-state="processing-complete"]')).toBeVisible(); await page.locator('.btn-post').evaluate(el => el.click()); await expect(page.locator('.notices')).toContainText('Entry posted successfully!', { timeout: 15_000 }); @@ -53,8 +53,9 @@ test('AE4: a corrupt HEIC is blocked (fail-closed) and never posted', async ({ p await page.fill('input[name="data[title]"]', `UI Test ${tag}`); await fillEditor(page, 'HEIC failure test. Safe to delete.'); - await page.locator('input.photo-picker__input').setInputFiles(TEST_CORRUPT_HEIC); - await expect(page.locator('.photo-card.is-error')).toBeVisible({ timeout: 15_000 }); + await page.locator('input.filepond--browser').setInputFiles(TEST_CORRUPT_HEIC); + // Conversion fails → inline error status, original HEIC never added to FilePond. + await expect(page.locator('.photo-convert-status.form-status--err')).toBeVisible({ timeout: 15_000 }); await expect(page.locator('.btn-post')).toBeEnabled(); // Submit still usable await page.locator('.btn-post').evaluate(el => el.click()); @@ -96,7 +97,7 @@ test('R20: in-progress text is restored after a reload, with a photos hint', asy await expect(page.locator('input[name="data[title]"]')).toHaveValue(marker); expect(await page.evaluate(() => window.postFormEditor.value())).toContain(marker); - await expect(page.locator('.photo-picker__reauth-hint')).toBeVisible(); + await expect(page.locator('.photo-reauth-hint')).toBeVisible(); await page.evaluate((k) => localStorage.removeItem(k), DRAFT_KEY); }); diff --git a/tests/ui/post/post.spec.js b/tests/ui/post/post.spec.js index fcab156..c61fa0e 100644 --- a/tests/ui/post/post.spec.js +++ b/tests/ui/post/post.spec.js @@ -54,7 +54,7 @@ test.skip('P2: post entry with photo → photo saved in entry folder and visible await page.fill('input[name="data[location_city]"]', 'Testville'); await page.fill('input[name="data[location_country]"]', 'Testland'); - await page.locator('input.photo-picker__input').setInputFiles(TEST_PHOTO); + await page.locator('input.filepond--browser').setInputFiles(TEST_PHOTO); await waitForPhotoUpload(page); await page.locator('.btn-post').evaluate(el => el.click()); diff --git a/tests/ui/post/validation.spec.js b/tests/ui/post/validation.spec.js index bb9e4f9..f5a8d39 100644 --- a/tests/ui/post/validation.spec.js +++ b/tests/ui/post/validation.spec.js @@ -2,7 +2,7 @@ // Tests: V1–V4 — form validation and input constraints const { test, expect } = require('@playwright/test'); const path = require('path'); -const { fillEditor, waitForPhotoUpload } = require('../helpers'); +const { fillEditor } = require('../helpers'); const TEST_PHOTO = path.join(__dirname, '../../fixtures/test-photo.jpg'); const TEST_NONIMAGE = path.join(__dirname, '../../fixtures/test-nonimage.txt'); @@ -34,31 +34,37 @@ test('V2: submit without content shows a validation error or stays on /post', as }); // ── V3: Photo limit (max 4) ─────────────────────────────────────────────────── -test('V3: photo picker caps attachments at 4', async ({ page }) => { +test('V3: FilePond caps attachments at the limit (4)', async ({ page }) => { await page.goto('/post'); - const input = page.locator('input.photo-picker__input'); + const browser = page.locator('input.filepond--browser'); // Attach 4 photos (same fixture — we only need four items). - await input.setInputFiles([TEST_PHOTO, TEST_PHOTO, TEST_PHOTO, TEST_PHOTO]); - await waitForPhotoUpload(page, 4); + await browser.setInputFiles([TEST_PHOTO, TEST_PHOTO, TEST_PHOTO, TEST_PHOTO]); + await page.waitForFunction(() => + document.querySelectorAll('.filepond--item').length === 4, { timeout: 10_000 }); - // A 5th is rejected once the limit is reached — no new card, and a hint shows. - await input.setInputFiles([TEST_PHOTO]); + // A 5th is ignored once the limit is reached. + await browser.setInputFiles([TEST_PHOTO]); await page.waitForTimeout(500); - await expect(page.locator('.photo-card')).toHaveCount(4); - await expect(page.locator('.photo-picker__hint')).toContainText(/up to 4/i); + expect(await page.locator('.filepond--item').count()).toBe(4); }); -// ── V4: Non-image file rejected (fail-closed) ───────────────────────────────── -test('V4: a non-image upload fails closed and is not attached', async ({ page }) => { +// ── V4: Non-image file rejected ─────────────────────────────────────────────── +test('V4: FilePond rejects a non-image file', async ({ page }) => { await page.goto('/post'); - // The picker accepts anything the OS dialog allows; the server's uploadFiles - // accept-check (image/*) rejects a .txt, so the card ends in the error state - // and never reaches "done" — the original is never posted. - await page.locator('input.photo-picker__input').setInputFiles(TEST_NONIMAGE); + await page.locator('input.filepond--browser').setInputFiles(TEST_NONIMAGE); + await page.waitForTimeout(1_000); - await expect(page.locator('.photo-card.is-error')).toBeVisible({ timeout: 15_000 }); - await expect(page.locator('.photo-card.is-done')).toHaveCount(0); + const items = page.locator('.filepond--item'); + const count = await items.count(); + if (count > 0) { + // If added, it must not reach processing-complete. + const state = await items.first().getAttribute('data-filepond-item-state'); + expect(state).not.toBe('processing-complete'); + } else { + // Silently rejected before adding — also a pass. + expect(count).toBe(0); + } });