// @ts-check // Tests: P1–P5 — form submission happy paths // Replaces post-with-photo.spec.js const { test, expect } = require('@playwright/test'); const path = require('path'); const fs = require('fs'); const { waitForFilePondUpload, cleanupEntry, findEntry, readEntryMd, TRACKER_DIR } = require('./helpers'); const TEST_PHOTO = path.join(__dirname, '../fixtures/test-photo.jpg'); // Track slugs created per test for cleanup const created = []; test.afterAll(() => { created.forEach(cleanupEntry); }); // ── P1: Post without photo ───────────────────────────────────────────────────── test('P1: post text-only entry → created on disk and visible on /tracker', async ({ page }) => { const tag = `p1-${Date.now()}`; const title = `UI Test ${tag}`; await page.goto('/post'); await page.fill('input[name="data[title]"]', title); await page.fill('textarea[name="data[content]"]', 'Text-only test entry. Safe to delete.'); await page.fill('input[name="data[location_city]"]', 'Testville'); await page.fill('input[name="data[location_country]"]', 'Testland'); await page.locator('.btn-post').evaluate(el => el.click()); await page.waitForSelector('.form-messages, .notices', { timeout: 15_000 }); const entryDir = findEntry(tag); expect(entryDir, 'Entry folder should exist on disk').toBeTruthy(); created.push(tag); const md = readEntryMd(entryDir); expect(md).toContain(tag); // No photo expected const photos = fs.readdirSync(entryDir).filter(f => /\.(jpg|jpeg|png|webp|heic)$/i.test(f)); expect(photos.length, 'Text-only entry should have no photos').toBe(0); await page.goto('/tracker'); await expect(page.locator('body')).toContainText(tag); }); // ── P2: Post with photo ──────────────────────────────────────────────────────── test('P2: post entry with photo → photo saved in entry folder and visible on /tracker', async ({ page }) => { const tag = `p2-${Date.now()}`; const title = `UI Test ${tag}`; await page.goto('/post'); await page.fill('input[name="data[title]"]', title); await page.fill('textarea[name="data[content]"]', 'Photo test entry. Safe to delete.'); await page.fill('input[name="data[location_city]"]', 'Testville'); await page.fill('input[name="data[location_country]"]', 'Testland'); await page.locator('input.filepond--browser').setInputFiles(TEST_PHOTO); await waitForFilePondUpload(page); await page.locator('.btn-post').evaluate(el => el.click()); await page.waitForSelector('.form-messages, .notices', { timeout: 15_000 }); const entryDir = findEntry(tag); expect(entryDir, 'Entry folder should exist on disk').toBeTruthy(); created.push(tag); const md = readEntryMd(entryDir); expect(md).toContain(tag); const photos = fs.readdirSync(entryDir).filter(f => /\.(jpg|jpeg|png|webp|heic)$/i.test(f)); expect(photos.length, 'At least one photo should be saved').toBeGreaterThan(0); await page.goto('/tracker'); await expect(page.locator('body')).toContainText(tag); }); // ── P3: Post with city + country ────────────────────────────────────────────── test('P3: post entry with city/country → frontmatter contains location', async ({ page }) => { const tag = `p3-${Date.now()}`; const title = `UI Test ${tag}`; await page.goto('/post'); await page.fill('input[name="data[title]"]', title); await page.fill('textarea[name="data[content]"]', 'Location test. Safe to delete.'); await page.fill('input[name="data[location_city]"]', 'Kyoto'); await page.fill('input[name="data[location_country]"]', 'Japan'); await page.locator('.btn-post').evaluate(el => el.click()); await page.waitForSelector('.form-messages, .notices', { timeout: 15_000 }); const entryDir = findEntry(tag); expect(entryDir, 'Entry folder should exist on disk').toBeTruthy(); created.push(tag); const md = readEntryMd(entryDir); expect(md).toContain('Kyoto'); expect(md).toContain('Japan'); }); // ── P4: Post with lat/lng ───────────────────────────────────────────────────── test('P4: post entry with lat/lng → coordinates saved in frontmatter', async ({ page }) => { const tag = `p4-${Date.now()}`; const title = `UI Test ${tag}`; await page.goto('/post'); await page.fill('input[name="data[title]"]', title); await page.fill('textarea[name="data[content]"]', 'GPS test. Safe to delete.'); // lat/lng fields are CSS-hidden (designed to be filled by the Get Location button); // set values directly via JS to simulate what the button would do. await page.evaluate(() => { document.querySelector('input[name="data[lat]"]').value = '35.6762'; document.querySelector('input[name="data[lng]"]').value = '139.6503'; }); await page.locator('.btn-post').evaluate(el => el.click()); await page.waitForSelector('.form-messages, .notices', { timeout: 15_000 }); const entryDir = findEntry(tag); expect(entryDir, 'Entry folder should exist on disk').toBeTruthy(); created.push(tag); const md = readEntryMd(entryDir); expect(md).toContain('35.6762'); expect(md).toContain('139.6503'); }); // ── P5: "Get Location" button fills lat/lng (TDD — fails until feature built) ── test('P5: Get Location button fills lat/lng from browser geolocation', async ({ page, context }) => { await context.grantPermissions(['geolocation']); await context.setGeolocation({ latitude: 35.6762, longitude: 139.6503 }); await page.goto('/post'); await page.click('#get-location'); // Allow a moment for the geolocation callback to fire await page.waitForTimeout(500); await expect(page.locator('input[name="data[lat]"]')).toHaveValue(/35\.67/); await expect(page.locator('input[name="data[lng]"]')).toHaveValue(/139\.65/); });