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).
This commit is contained in:
@@ -63,9 +63,9 @@ check_grep "hero_image field present" "name: hero_image"
|
|||||||
check_grep "force_connect field present" "name: force_connect"
|
check_grep "force_connect field present" "name: force_connect"
|
||||||
check_grep "featured field present" "name: featured"
|
check_grep "featured field present" "name: featured"
|
||||||
|
|
||||||
# Photos must use the custom controlled picker (U4), not filepond/file — that is
|
# Photos use Grav's filepond field; post-form.js hooks its beforeAddFile to
|
||||||
# what lets post-form.js convert HEIC before upload.
|
# convert HEIC->JPEG before FilePond uploads (U4).
|
||||||
check_grep "photos field uses the custom 'photos' type (HEIC pipeline)" "type: photos"
|
check_grep "photos field uses the filepond type" "type: filepond"
|
||||||
|
|
||||||
echo "────────────────────────────────────────"
|
echo "────────────────────────────────────────"
|
||||||
echo " $PASS passed, $FAIL failed"
|
echo " $PASS passed, $FAIL failed"
|
||||||
|
|||||||
+8
-4
@@ -93,14 +93,18 @@ async function fillEditor(page, text) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait for the custom photo picker to finish converting + uploading photos
|
* Wait for photos to finish uploading. post-form.js converts HEIC->JPEG and
|
||||||
* (each successfully attached photo gets a `.photo-card.is-done`).
|
* 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) {
|
async function waitForPhotoUpload(page, count = 1) {
|
||||||
await page.waitForFunction(
|
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,
|
count,
|
||||||
{ timeout: 30_000 }
|
{ timeout: 40_000 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 page.fill('input[name="data[title]"]', `UI Test ${tag}`);
|
||||||
await fillEditor(page, 'HEIC conversion test. Safe to delete.');
|
await fillEditor(page, 'HEIC conversion test. Safe to delete.');
|
||||||
|
|
||||||
await page.locator('input.photo-picker__input').setInputFiles(TEST_HEIC);
|
await page.locator('input.filepond--browser').setInputFiles(TEST_HEIC);
|
||||||
await waitForPhotoUpload(page, 1); // is-done == converted + uploaded
|
await waitForPhotoUpload(page, 1); // converted (beforeAddFile) + uploaded via FilePond
|
||||||
await expect(page.locator('.photo-card.is-done .photo-card__thumb')).toBeVisible();
|
await expect(page.locator('.filepond--item[data-filepond-item-state="processing-complete"]')).toBeVisible();
|
||||||
|
|
||||||
await page.locator('.btn-post').evaluate(el => el.click());
|
await page.locator('.btn-post').evaluate(el => el.click());
|
||||||
await expect(page.locator('.notices')).toContainText('Entry posted successfully!', { timeout: 15_000 });
|
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 page.fill('input[name="data[title]"]', `UI Test ${tag}`);
|
||||||
await fillEditor(page, 'HEIC failure test. Safe to delete.');
|
await fillEditor(page, 'HEIC failure test. Safe to delete.');
|
||||||
|
|
||||||
await page.locator('input.photo-picker__input').setInputFiles(TEST_CORRUPT_HEIC);
|
await page.locator('input.filepond--browser').setInputFiles(TEST_CORRUPT_HEIC);
|
||||||
await expect(page.locator('.photo-card.is-error')).toBeVisible({ timeout: 15_000 });
|
// 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 expect(page.locator('.btn-post')).toBeEnabled(); // Submit still usable
|
||||||
|
|
||||||
await page.locator('.btn-post').evaluate(el => el.click());
|
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);
|
await expect(page.locator('input[name="data[title]"]')).toHaveValue(marker);
|
||||||
expect(await page.evaluate(() => window.postFormEditor.value())).toContain(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);
|
await page.evaluate((k) => localStorage.removeItem(k), DRAFT_KEY);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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_city]"]', 'Testville');
|
||||||
await page.fill('input[name="data[location_country]"]', 'Testland');
|
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 waitForPhotoUpload(page);
|
||||||
|
|
||||||
await page.locator('.btn-post').evaluate(el => el.click());
|
await page.locator('.btn-post').evaluate(el => el.click());
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// Tests: V1–V4 — form validation and input constraints
|
// Tests: V1–V4 — form validation and input constraints
|
||||||
const { test, expect } = require('@playwright/test');
|
const { test, expect } = require('@playwright/test');
|
||||||
const path = require('path');
|
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_PHOTO = path.join(__dirname, '../../fixtures/test-photo.jpg');
|
||||||
const TEST_NONIMAGE = path.join(__dirname, '../../fixtures/test-nonimage.txt');
|
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) ───────────────────────────────────────────────────
|
// ── 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');
|
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).
|
// Attach 4 photos (same fixture — we only need four items).
|
||||||
await input.setInputFiles([TEST_PHOTO, TEST_PHOTO, TEST_PHOTO, TEST_PHOTO]);
|
await browser.setInputFiles([TEST_PHOTO, TEST_PHOTO, TEST_PHOTO, TEST_PHOTO]);
|
||||||
await waitForPhotoUpload(page, 4);
|
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.
|
// A 5th is ignored once the limit is reached.
|
||||||
await input.setInputFiles([TEST_PHOTO]);
|
await browser.setInputFiles([TEST_PHOTO]);
|
||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
|
|
||||||
await expect(page.locator('.photo-card')).toHaveCount(4);
|
expect(await page.locator('.filepond--item').count()).toBe(4);
|
||||||
await expect(page.locator('.photo-picker__hint')).toContainText(/up to 4/i);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── V4: Non-image file rejected (fail-closed) ─────────────────────────────────
|
// ── V4: Non-image file rejected ───────────────────────────────────────────────
|
||||||
test('V4: a non-image upload fails closed and is not attached', async ({ page }) => {
|
test('V4: FilePond rejects a non-image file', async ({ page }) => {
|
||||||
await page.goto('/post');
|
await page.goto('/post');
|
||||||
|
|
||||||
// The picker accepts anything the OS dialog allows; the server's uploadFiles
|
await page.locator('input.filepond--browser').setInputFiles(TEST_NONIMAGE);
|
||||||
// accept-check (image/*) rejects a .txt, so the card ends in the error state
|
await page.waitForTimeout(1_000);
|
||||||
// and never reaches "done" — the original is never posted.
|
|
||||||
await page.locator('input.photo-picker__input').setInputFiles(TEST_NONIMAGE);
|
|
||||||
|
|
||||||
await expect(page.locator('.photo-card.is-error')).toBeVisible({ timeout: 15_000 });
|
const items = page.locator('.filepond--item');
|
||||||
await expect(page.locator('.photo-card.is-done')).toHaveCount(0);
|
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);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user