Hoist the duplicated per-spec createEntry photo-fixture into a single createPhotoEntry() in helpers.js (used by delete-flow, edit-mode, and the anon-view draft). Register the tag for cleanup BEFORE the awaited 15s success-toast assertion, so a create that lands on disk but whose toast assertion times out no longer leaks an untracked entry. Add AE3b covering the disclosure deviation branch (a non-default toggle auto-expands More options). Code review F2 (leak), F3 (duplication), F6 (coverage). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
67 lines
3.2 KiB
JavaScript
67 lines
3.2 KiB
JavaScript
// @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 { createPhotoEntry, cleanupEntry, findEntry, ACTIVE_TRIP_URL } = require('../helpers');
|
||
|
||
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 createPhotoEntry(op, tag, {
|
||
created,
|
||
publish: false,
|
||
content: `Draft body ${tag}. Safe to delete.`,
|
||
});
|
||
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);
|
||
});
|