- test-form-config.sh: assert parent is NOT hardcoded (injected server-side), active_trip set in site.yaml, and the new fields incl. custom 'photos' type. - helpers.js: resolve active trip from site.active_trip (parent coupling gone); fillEditor() drives EasyMDE via window.postFormEditor; waitForPhotoUpload() waits on the new picker. - post.spec / validation.spec: content via the editor, filepond selectors -> the photo picker, P8 checks editor value, V3/V4 exercise the picker cap + fail-closed non-image. - post-form-ux.spec.js (new): AE3 disclosure, AE1 HEIC->JPEG, AE4 corrupt-HEIC fail-closed, R18 weather gating, R20 draft restore. - fixtures: real + corrupt .heic. - test-post.sh: resolve dailies dir from active_trip. Refs AE1-AE4, R18, R20, U7.
65 lines
3.4 KiB
JavaScript
65 lines
3.4 KiB
JavaScript
// @ts-check
|
||
// Tests: V1–V4 — form validation and input constraints
|
||
const { test, expect } = require('@playwright/test');
|
||
const path = require('path');
|
||
const { fillEditor, waitForPhotoUpload } = require('../helpers');
|
||
|
||
const TEST_PHOTO = path.join(__dirname, '../../fixtures/test-photo.jpg');
|
||
const TEST_NONIMAGE = path.join(__dirname, '../../fixtures/test-nonimage.txt');
|
||
|
||
// ── V1: Missing title ─────────────────────────────────────────────────────────
|
||
test('V1: submit without title shows a validation error or stays on /post', async ({ page }) => {
|
||
await page.goto('/post');
|
||
// Leave title empty, fill only content
|
||
await fillEditor(page, 'Content without a title.');
|
||
await page.locator('.btn-post').evaluate(el => el.click());
|
||
|
||
// Grav either shows an error message OR re-renders the form (stays on /post).
|
||
// Either outcome is acceptable — what should NOT happen is a success message.
|
||
await page.waitForTimeout(2_000);
|
||
const bodyText = await page.locator('body').textContent();
|
||
expect(bodyText).not.toContain('Entry posted successfully');
|
||
});
|
||
|
||
// ── V2: Missing content ───────────────────────────────────────────────────────
|
||
test('V2: submit without content shows a validation error or stays on /post', async ({ page }) => {
|
||
await page.goto('/post');
|
||
await page.fill('input[name="data[title]"]', 'V2 title no content');
|
||
// Leave content (editor) empty
|
||
await page.locator('.btn-post').evaluate(el => el.click());
|
||
|
||
await page.waitForTimeout(2_000);
|
||
const bodyText = await page.locator('body').textContent();
|
||
expect(bodyText).not.toContain('Entry posted successfully');
|
||
});
|
||
|
||
// ── V3: Photo limit (max 4) ───────────────────────────────────────────────────
|
||
test('V3: photo picker caps attachments at 4', async ({ page }) => {
|
||
await page.goto('/post');
|
||
const input = page.locator('input.photo-picker__input');
|
||
|
||
// 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);
|
||
|
||
// A 5th is rejected once the limit is reached — no new card, and a hint shows.
|
||
await input.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);
|
||
});
|
||
|
||
// ── V4: Non-image file rejected (fail-closed) ─────────────────────────────────
|
||
test('V4: a non-image upload fails closed and is not attached', 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 expect(page.locator('.photo-card.is-error')).toBeVisible({ timeout: 15_000 });
|
||
await expect(page.locator('.photo-card.is-done')).toHaveCount(0);
|
||
});
|