Files
intotheeast-com/tests/ui/trip/trip-header.spec.js
T
m038andClaude Opus 4.8 af07ef403c test(trip): add AE4 no-banner coverage + record deferred review findings
- trip-header.spec.js: new U4/R9/AE4 test — a trip with no cover_image and no
  entry images renders its text header with no .trip-header-banner strip.
- Makefile: demo-load/demo-reset now also (un)load the no-photos-demo fixture,
  guarded with `|| true` so runs whose user/ predates the fixture skip it
  instead of failing globalSetup.
- plan: post-review follow-up notes the applied fixes and records the two
  intentionally-deferred findings (macro-owned resolution per KTD2; inline
  toggle script per U4's Approach) so they are not re-flagged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDS6t8wcpbwKvvrxykVQ5K
2026-07-07 09:13:59 +02:00

94 lines
5.0 KiB
JavaScript

// @ts-check
// Tests: U4 — trip-page in-column header extras (R8, R9, R10, R13)
const { test, expect } = require('@playwright/test');
const TRIP_URL = '/trips/italy-2026-demo';
const NO_PHOTOS_URL = '/trips/no-photos-demo'; // no cover_image, one image-less entry
const topOf = async (locator) => (await locator.boundingBox()).y;
// ── R8/R9: extras render in HTD order, banner sits above the filter bar ────────
test('U4/R8+R9: one-liner, description and banner stack in order above the filter bar', async ({ page }) => {
await page.goto(TRIP_URL);
const header = page.locator('.home-trip-header');
await expect(header.locator('.home-trip-tagline')).toHaveText(/southern Tuscany by bike/);
const yTitle = await topOf(page.locator('.home-trip-name'));
const yTag = await topOf(page.locator('.home-trip-tagline'));
const yCounts = await topOf(page.locator('.home-trip-counts'));
const yDesc = await topOf(page.locator('.trip-header-desc'));
const yBanner = await topOf(page.locator('.trip-header-banner'));
const yFilter = await topOf(page.locator('.trip-filter-bar'));
expect(yTitle).toBeLessThan(yTag); // one-liner directly below the title
expect(yTag).toBeLessThan(yCounts);
expect(yCounts).toBeLessThan(yDesc); // description below the counts
expect(yDesc).toBeLessThan(yBanner); // banner below the description
expect(yBanner).toBeLessThan(yFilter); // ...and above the filter bar (R9)
});
// ── R8/R13: description shows a collapsed preview and expands on demand ────────
test('U4/R13: description is clamped to a preview and expands to full text', async ({ page }) => {
await page.goto(TRIP_URL);
const desc = page.locator('.trip-header-desc');
const body = page.locator('.trip-header-desc-body');
const btn = page.locator('.trip-header-desc-toggle');
await expect(desc).toHaveAttribute('data-collapsed', 'true');
await expect(btn).toBeVisible();
// Collapsed: the body is genuinely clamped — visible height is shorter than
// its full content (the max-height:4.8em preview actually hides overflow).
const clampedWhenCollapsed = await body.evaluate((el) => el.clientHeight < el.scrollHeight);
expect(clampedWhenCollapsed).toBe(true);
const collapsedH = (await body.boundingBox()).height;
await btn.click();
await expect(desc).toHaveAttribute('data-collapsed', 'false');
await expect(btn).toHaveText('Show less');
const expandedH = (await body.boundingBox()).height;
expect(expandedH).toBeGreaterThan(collapsedH);
// Expanded: the clamp is gone — the full text is now actually visible, not
// merely present in the DOM (which it was even while collapsed).
const unclampedWhenExpanded = await body.evaluate((el) => el.clientHeight >= el.scrollHeight - 1);
expect(unclampedWhenExpanded).toBe(true);
});
// ── R9/AE3: banner uses the first journal image (no cover_image set) ───────────
test('U4/R9/AE3: banner falls back to the first journal entry image', async ({ page }) => {
await page.goto(TRIP_URL);
const img = page.locator('.trip-header-banner img');
await expect(img).toBeVisible();
const srcset = await img.getAttribute('srcset');
expect(srcset).toContain('720w');
expect(srcset).toContain('1440w');
await expect(img).toHaveAttribute('alt', 'Tuscany 2026');
});
// ── R9/AE4: no cover_image and no entry images → text-only header, no banner ───
test('U4/R9/AE4: trip with no cover and no entry images renders no banner strip', async ({ page }) => {
await page.goto(NO_PHOTOS_URL);
// The text extras still render (the header is not blank)...
await expect(page.locator('.home-trip-name')).toHaveText('Notes from the Road 2027');
await expect(page.locator('.home-trip-tagline')).toBeVisible();
// ...and there is a journal entry, so the empty-cover path is the macro's
// fallback loop finding no image — not simply an empty trip.
await expect(page.locator('.feed .journal-post')).toHaveCount(1);
// With no resolvable cover, the banner strip is omitted entirely (AE4).
await expect(page.locator('.trip-header-banner')).toHaveCount(0);
});
// ── R10/AE6: the map+journal split is intact with no header above it ───────────
test('U4/R10/AE6: map+journal split renders with the extras inside the feed column', async ({ page }) => {
await page.goto(TRIP_URL);
await expect(page.locator('.home-layout')).toBeVisible();
await expect(page.locator('.home-layout > .home-map-col')).toBeVisible();
await expect(page.locator('.home-layout > .home-feed-col')).toBeVisible();
// extras live inside the feed column, not as a new header above the split
await expect(page.locator('.home-feed-col .trip-header-banner')).toHaveCount(1);
await expect(page.locator('.home-feed-col .home-trip-tagline')).toHaveCount(1);
// the header extras never appear outside the two-column layout
await expect(page.locator('body > .trip-header-banner, .home-layout ~ .trip-header-banner')).toHaveCount(0);
});