- trip-header/trips-list specs now assert the cropZoom wide-strip aspect and the all-or-nothing retina rule (1x-only for sub-2w sources), and a new regression on us-canada-mex-2024 covers the reported portrait-blur - remove the orphaned AE4 test (no-photos-demo fixture was deleted) - backlog: full-res pixelfed reimport (luxury quality item) - plan: record the cover-quality follow-up + AE4 fixture removal - bump user pin -> 536ca20 (trip-description-hero cover fix) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RDS6t8wcpbwKvvrxykVQ5K
110 lines
5.9 KiB
JavaScript
110 lines
5.9 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 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');
|
|
// The 1200px landscape source can't supply a non-upscaled 2x (needs ≥1440),
|
|
// so the retina descriptor is omitted entirely — 1x only, no blurry upscale
|
|
// and no odd intermediate width.
|
|
expect(srcset).not.toContain('1440w');
|
|
expect(srcset).not.toContain('1200w');
|
|
// cropZoom hands over a wide cover strip (~3.27:1); the old cropResize gave
|
|
// a fit-inside image the object-fit:cover box then upscaled into a blur.
|
|
const ratio = await img.evaluate((el) => new Promise((res) => {
|
|
const done = () => res(el.naturalWidth / el.naturalHeight);
|
|
el.complete && el.naturalWidth ? done() : el.addEventListener('load', done, { once: true });
|
|
}));
|
|
expect(ratio).toBeGreaterThan(3);
|
|
await expect(img).toHaveAttribute('alt', 'Tuscany 2026');
|
|
});
|
|
|
|
// ── Regression: a portrait fallback source (the reported us-canada-mex-2024
|
|
// banner) must render a sharp wide cover strip, not a blurry upscaled sliver ─
|
|
test('U4/R9: a portrait fallback source renders a wide cover strip, not a sliver', async ({ page }) => {
|
|
await page.goto('/trips/us-canada-mex-2024');
|
|
const img = page.locator('.trip-header-banner img');
|
|
await expect(img).toBeVisible();
|
|
const srcset = await img.getAttribute('srcset');
|
|
expect(srcset).toContain('720w');
|
|
// 1013px portrait source — nowhere near ≥1440 — so no upscaled 2x candidate.
|
|
expect(srcset).not.toContain('1440w');
|
|
// cropZoom crops the tall portrait into a wide cover strip (~3.27:1); the old
|
|
// cropResize fit it inside as a ~165px sliver the box then upscaled to a blur.
|
|
const ratio = await img.evaluate((el) => new Promise((res) => {
|
|
const done = () => res(el.naturalWidth / el.naturalHeight);
|
|
el.complete && el.naturalWidth ? done() : el.addEventListener('load', done, { once: true });
|
|
}));
|
|
expect(ratio).toBeGreaterThan(3);
|
|
});
|
|
|
|
// ── 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);
|
|
});
|