New specs: edit-mode (ES1 save round-trip + ES2/ES3 prefill 404/500 states), delete-flow (DEL1-3 happy/cancel/failed), anon-view (AN1 no owner controls, AN2 draft hidden from anon), photo-editor (live add/delete/reorder). Existing: P3-P8 now attach a photo to satisfy the create photo-gate; V3 picker cap 4->6. Full post suite 38/38, stable across parallel (3-worker) runs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
73 lines
3.6 KiB
JavaScript
73 lines
3.6 KiB
JavaScript
// @ts-check
|
||
// Tests: V1–V4 — form validation and input constraints
|
||
const { test, expect } = require('@playwright/test');
|
||
const path = require('path');
|
||
const { fillEditor } = 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 6) ───────────────────────────────────────────────────
|
||
// The blueprint limit is 6 (asserted directly as maxFiles===6 in the UX suite);
|
||
// this is the behavioral counterpart — the picker must refuse a 7th attachment.
|
||
test('V3: FilePond caps attachments at the limit (6)', async ({ page }) => {
|
||
await page.goto('/post');
|
||
const browser = page.locator('input.filepond--browser');
|
||
|
||
// Attach 6 photos (same fixture — we only need six items).
|
||
await browser.setInputFiles([TEST_PHOTO, TEST_PHOTO, TEST_PHOTO, TEST_PHOTO, TEST_PHOTO, TEST_PHOTO]);
|
||
await page.waitForFunction(() =>
|
||
document.querySelectorAll('.filepond--item').length === 6, { timeout: 10_000 });
|
||
|
||
// A 7th is ignored once the limit is reached.
|
||
await browser.setInputFiles([TEST_PHOTO]);
|
||
await page.waitForTimeout(500);
|
||
|
||
expect(await page.locator('.filepond--item').count()).toBe(6);
|
||
});
|
||
|
||
// ── V4: Non-image file rejected ───────────────────────────────────────────────
|
||
test('V4: FilePond rejects a non-image file', async ({ page }) => {
|
||
await page.goto('/post');
|
||
|
||
await page.locator('input.filepond--browser').setInputFiles(TEST_NONIMAGE);
|
||
await page.waitForTimeout(1_000);
|
||
|
||
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);
|
||
}
|
||
});
|