// @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); });