test(post-form): add edit/delete/anon coverage and fix stale photo-gate assumptions
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
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
// @ts-check
|
||||
// Tests: AN1–AN2 — the anonymous (logged-out) visitor's view of the active trip.
|
||||
//
|
||||
// Every other spec runs as the authenticated owner, so nothing guards the
|
||||
// owner/anon boundary. These assert the two things that boundary must enforce
|
||||
// (R5, KTD7):
|
||||
// - AN1: owner-only controls (Edit/Delete, data-entry-route) never render for
|
||||
// an anonymous visitor, even though published entries are visible.
|
||||
// - AN2: an unpublished DRAFT is shown to the owner (with a badge) but is
|
||||
// completely absent for an anonymous visitor.
|
||||
//
|
||||
// The whole file runs UNauthenticated by clearing storageState. AN2 spins up a
|
||||
// short-lived authenticated context to create the draft fixture and confirm the
|
||||
// owner-visible side.
|
||||
const { test, expect } = require('@playwright/test');
|
||||
const path = require('path');
|
||||
const { fillEditor, waitForPhotoUpload, cleanupEntry, findEntry, ACTIVE_TRIP_URL } = require('../helpers');
|
||||
|
||||
const TEST_PHOTO = path.join(__dirname, '../../fixtures/test-photo.jpg');
|
||||
const AUTH_STATE = 'tests/.auth/user.json';
|
||||
const BASE = process.env.GRAV_BASE_URL || 'http://localhost:8081';
|
||||
|
||||
// Run this file with NO owner session.
|
||||
test.use({ storageState: { cookies: [], origins: [] } });
|
||||
|
||||
const created = [];
|
||||
test.afterAll(() => created.forEach(cleanupEntry));
|
||||
|
||||
// ── AN1: anonymous visitor sees content but no owner controls ─────────────────
|
||||
test('AN1: an anonymous visitor sees published entries but no owner controls', async ({ page }) => {
|
||||
await page.goto(ACTIVE_TRIP_URL);
|
||||
|
||||
// The demo trip has published journal cards — content is public.
|
||||
await expect(page.locator('.journal-post').first()).toBeVisible();
|
||||
|
||||
// …but none of the owner-only affordances are present in the markup.
|
||||
await expect(page.locator('.journal-post-actions')).toHaveCount(0);
|
||||
await expect(page.locator('.entry-action--edit')).toHaveCount(0);
|
||||
await expect(page.locator('.entry-action--delete')).toHaveCount(0);
|
||||
await expect(page.locator('[data-entry-route]')).toHaveCount(0);
|
||||
});
|
||||
|
||||
// ── AN2: a draft is owner-only ────────────────────────────────────────────────
|
||||
test('AN2: a draft entry is shown to the owner but hidden from an anonymous visitor', async ({ page, browser }) => {
|
||||
const tag = `draft-${Date.now()}`;
|
||||
|
||||
// Create an UNPUBLISHED entry as the owner, in a separate authed context.
|
||||
const owner = await browser.newContext({ storageState: AUTH_STATE, baseURL: BASE });
|
||||
const op = await owner.newPage();
|
||||
await op.goto('/post');
|
||||
await op.fill('input[name="data[title]"]', `UI Test ${tag}`);
|
||||
await fillEditor(op, `Draft body ${tag}. Safe to delete.`);
|
||||
await op.locator('input.filepond--browser').setInputFiles(TEST_PHOTO);
|
||||
await waitForPhotoUpload(op);
|
||||
// Flip Published → No. The toggle renders as a visually-hidden radio pair
|
||||
// behind "More options", so set the state directly and fire `change` (what a
|
||||
// real click would do) rather than fighting the visibility gate.
|
||||
await op.evaluate(() => {
|
||||
const off = document.querySelector('input[name="data[published]"][value="0"]');
|
||||
off.checked = true;
|
||||
off.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
});
|
||||
await op.locator('.btn-post').evaluate(el => el.click());
|
||||
await expect(op.locator('.form-messages, .notices')).toContainText(
|
||||
'Entry posted successfully!', { timeout: 15_000 });
|
||||
created.push(tag);
|
||||
expect(findEntry(tag), 'draft fixture should exist on disk').not.toBeNull();
|
||||
|
||||
// Owner side: the draft appears in the feed WITH a Draft badge.
|
||||
await op.goto(ACTIVE_TRIP_URL);
|
||||
const ownerCard = op.locator('.journal-post', { hasText: tag });
|
||||
await expect(ownerCard).toHaveCount(1);
|
||||
await expect(ownerCard.locator('.journal-draft-badge')).toBeVisible();
|
||||
await owner.close();
|
||||
|
||||
// Anonymous side (the default page fixture): the draft is nowhere to be seen.
|
||||
await page.goto(ACTIVE_TRIP_URL);
|
||||
await expect(page.locator('.journal-post', { hasText: tag })).toHaveCount(0);
|
||||
await expect(page.locator('body')).not.toContainText(tag);
|
||||
});
|
||||
Reference in New Issue
Block a user