Covers the reorder pipeline: two uploads produce photo-1.jpg/photo-2.jpg on the posted entry, and the top-level photo_order POST key never appears in the entry frontmatter. Adds a second image fixture so both slots are exercised. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
171 lines
8.5 KiB
JavaScript
171 lines
8.5 KiB
JavaScript
// @ts-check
|
|
// Tests: post-form redesign UX — disclosure, HEIC conversion + failure,
|
|
// weather-button gating, draft restore. Covers AE1, AE3, AE4 and R18/R20.
|
|
const { test, expect } = require('@playwright/test');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const { fillEditor, waitForPhotoUpload, cleanupEntry, findEntry } = require('../helpers');
|
|
|
|
const TEST_HEIC = path.join(__dirname, '../../fixtures/test-photo.heic');
|
|
const TEST_CORRUPT_HEIC = path.join(__dirname, '../../fixtures/test-corrupt.heic');
|
|
const TEST_JPG = path.join(__dirname, '../../fixtures/test-photo.jpg');
|
|
const TEST_JPG_B = path.join(__dirname, '../../fixtures/test-photo-b.jpg');
|
|
const DRAFT_KEY = 'intotheeast:new-entry-draft';
|
|
|
|
const created = [];
|
|
test.afterAll(() => { created.forEach(cleanupEntry); });
|
|
|
|
// ── AE3: advanced fields sit behind "More options" ────────────────────────────
|
|
test('AE3: advanced fields are hidden until "More options" is expanded', async ({ page }) => {
|
|
await page.goto('/post');
|
|
const hero = page.locator('input[name="data[hero_image]"]');
|
|
|
|
await expect(page.locator('details.more-options')).toBeAttached();
|
|
await expect(hero).toBeHidden(); // collapsed <details> hides content
|
|
await page.locator('.more-options__summary').click();
|
|
await expect(hero).toBeVisible();
|
|
});
|
|
|
|
// ── AE1: HEIC → JPEG conversion, posted with a working thumbnail ───────────────
|
|
test('AE1: a HEIC photo is converted to JPEG client-side and posted', async ({ page }) => {
|
|
const tag = `heic-${Date.now()}`;
|
|
await page.goto('/post');
|
|
await page.fill('input[name="data[title]"]', `UI Test ${tag}`);
|
|
await fillEditor(page, 'HEIC conversion test. Safe to delete.');
|
|
|
|
await page.locator('input.filepond--browser').setInputFiles(TEST_HEIC);
|
|
await waitForPhotoUpload(page, 1); // converted (beforeAddFile) + uploaded via FilePond
|
|
// The photo section auto-collapses to a summary bar once the upload settles.
|
|
await expect(page.locator('.photos-collapse__summary')).toContainText('1 photo ready');
|
|
|
|
await page.locator('.btn-post').evaluate(el => el.click());
|
|
await expect(page.locator('.notices')).toContainText('Entry posted successfully!', { timeout: 15_000 });
|
|
created.push(tag);
|
|
|
|
const dir = findEntry(tag);
|
|
expect(dir, 'Entry folder should exist on disk').toBeTruthy();
|
|
const files = fs.readdirSync(dir);
|
|
expect(files.some(f => /\.jpe?g$/i.test(f)), 'a JPEG should be posted').toBe(true);
|
|
expect(files.some(f => /\.heic$/i.test(f)), 'the original HEIC must NOT be posted').toBe(false);
|
|
});
|
|
|
|
// ── AE4: corrupt HEIC fails closed; Submit + other photos stay usable ─────────
|
|
test('AE4: a corrupt HEIC is blocked (fail-closed) and never posted', async ({ page }) => {
|
|
const tag = `heicfail-${Date.now()}`;
|
|
await page.goto('/post');
|
|
await page.fill('input[name="data[title]"]', `UI Test ${tag}`);
|
|
await fillEditor(page, 'HEIC failure test. Safe to delete.');
|
|
|
|
await page.locator('input.filepond--browser').setInputFiles(TEST_CORRUPT_HEIC);
|
|
// 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 page.locator('.btn-post').evaluate(el => el.click());
|
|
await expect(page.locator('.notices')).toContainText('Entry posted successfully!', { timeout: 15_000 });
|
|
created.push(tag);
|
|
|
|
const dir = findEntry(tag);
|
|
expect(dir).toBeTruthy();
|
|
const files = fs.readdirSync(dir);
|
|
expect(files.some(f => /\.heic$/i.test(f)), 'the corrupt HEIC must never be posted').toBe(false);
|
|
});
|
|
|
|
// ── Photo section collapses to a summary after upload, re-expands on tap ──────
|
|
test('photo section auto-collapses to a summary after upload and re-expands on tap', async ({ page }) => {
|
|
await page.goto('/post');
|
|
const details = page.locator('details.photos-collapse');
|
|
await expect(details).toHaveJSProperty('open', true); // open while empty
|
|
|
|
await page.locator('input.filepond--browser').setInputFiles(TEST_HEIC);
|
|
await waitForPhotoUpload(page, 1);
|
|
|
|
// Settled → auto-collapsed, summary reflects the ready count.
|
|
await expect(details).toHaveJSProperty('open', false);
|
|
await expect(page.locator('.photos-collapse__summary')).toContainText('1 photo ready');
|
|
|
|
// Native <details>: clicking the summary re-expands for review.
|
|
await page.locator('.photos-collapse__summary').click();
|
|
await expect(details).toHaveJSProperty('open', true);
|
|
});
|
|
|
|
// ── Reorder: uploaded photos are renamed photo-1..N; no order field leaks ──────
|
|
test('uploaded photos are renamed photo-1..N and the order field never hits frontmatter', async ({ page }) => {
|
|
const tag = `rename-${Date.now()}`;
|
|
await page.goto('/post');
|
|
await page.fill('input[name="data[title]"]', `UI Test ${tag}`);
|
|
await fillEditor(page, `photo rename test ${tag}`);
|
|
|
|
await page.locator('input.filepond--browser').setInputFiles([TEST_JPG, TEST_JPG_B]);
|
|
await waitForPhotoUpload(page, 2);
|
|
|
|
await page.locator('.btn-post').evaluate(el => el.click());
|
|
await expect(page.locator('.notices')).toContainText('Entry posted successfully!', { timeout: 15_000 });
|
|
created.push(tag);
|
|
|
|
const dir = findEntry(tag);
|
|
expect(dir, 'Entry folder should exist').toBeTruthy();
|
|
const files = fs.readdirSync(dir);
|
|
// Server renamed both uploads to the deterministic photo-N scheme (drag order).
|
|
expect(files).toContain('photo-1.jpg');
|
|
expect(files).toContain('photo-2.jpg');
|
|
|
|
// The order is sent as a top-level POST key, so it must not appear in frontmatter.
|
|
const mdName = files.find(f => /\.md$/.test(f));
|
|
const md = fs.readFileSync(path.join(dir, mdName), 'utf-8');
|
|
expect(md).not.toContain('photo_order');
|
|
});
|
|
|
|
// ── R18: Get Weather is gated on coordinates ──────────────────────────────────
|
|
test('R18: Get Weather is disabled until Get Location provides coordinates', async ({ page, context }) => {
|
|
await context.grantPermissions(['geolocation']);
|
|
await context.setGeolocation({ latitude: 35.6812, longitude: 139.7671 });
|
|
|
|
await page.goto('/post');
|
|
await expect(page.locator('#get-weather')).toBeDisabled();
|
|
|
|
await page.click('#get-location');
|
|
await expect(page.locator('input[name="data[lat]"]')).toHaveValue(/35\.68/, { timeout: 5_000 });
|
|
await expect(page.locator('#get-weather')).toBeEnabled();
|
|
});
|
|
|
|
// ── R20: text draft survives a reload; photos need re-selecting ────────────────
|
|
test('R20: in-progress text is restored after a reload, with a photos hint', async ({ page }) => {
|
|
await page.goto('/post');
|
|
await page.evaluate((k) => localStorage.removeItem(k), DRAFT_KEY);
|
|
|
|
const marker = `draft-${Date.now()}`;
|
|
await page.fill('input[name="data[title]"]', marker);
|
|
await fillEditor(page, `Draft body ${marker}`);
|
|
// Nudge an input event so the draft is written, then let it flush.
|
|
await page.locator('input[name="data[title]"]').press('End');
|
|
await page.waitForTimeout(300);
|
|
|
|
await page.reload();
|
|
|
|
await expect(page.locator('input[name="data[title]"]')).toHaveValue(marker);
|
|
expect(await page.evaluate(() => window.postFormEditor.value())).toContain(marker);
|
|
await expect(page.locator('.photo-reauth-hint')).toBeVisible();
|
|
|
|
await page.evaluate((k) => localStorage.removeItem(k), DRAFT_KEY);
|
|
});
|
|
|
|
// ── Success confirmation: after a post, show a clear CTA the owner can act on ──
|
|
test('post success shows a confirmation with a working "View your journal" link', async ({ page }) => {
|
|
const tag = `success-${Date.now()}`;
|
|
await page.goto('/post');
|
|
await page.fill('input[name="data[title]"]', `UI Test ${tag}`);
|
|
await fillEditor(page, `success cta test ${tag}`);
|
|
await page.locator('.btn-post').evaluate(el => el.click());
|
|
await expect(page.locator('.notices')).toContainText('Entry posted successfully!', { timeout: 15_000 });
|
|
created.push(tag);
|
|
|
|
const panel = page.locator('.post-success');
|
|
await expect(panel).toBeVisible();
|
|
const view = panel.locator('.post-success__view');
|
|
await expect(view).toBeVisible();
|
|
// links into the active trip's journal (resolved from site.active_trip)
|
|
await expect(view).toHaveAttribute('href', /\/trips\//);
|
|
await expect(panel.locator('.post-success__again')).toBeVisible();
|
|
});
|