lightbox-dims.spec.js hardcoded ../../../user, so a run against a checkout
detached from the served tree planted its fixture in a different user/ than
Grav renders and LD1 failed as an opaque "card never appeared" timeout.
Take USER_DIR from helpers instead, which honours GRAV_USER_DIR.
The three specs in this folder that fail do so for real, pre-existing
reasons, and both files' headers implied otherwise:
- UG1/UG2 specify a submit gate that is not implemented. post-form.js's
only create-form guard is `converting > 0` (pre-FilePond HEIC
conversion); it never inspects FilePond item state at submit time, and
.photo-convert-status is created lazily by photoStatusEl() only from the
HEIC paths — so for a plain JPEG the element never exists and both
expectations fail as "element(s) not found". UG2 is the one that matters:
a failed upload keeping its thumbnail is unguarded silent data loss.
- LD1's header described its root cause in the past tense, reading as
fixed. entry-journal.html.twig:48-49 still emits {{ img.width }} /
{{ img.height }}, so EXIF-rotated photos still declare pre-rotation dims
and PhotoSwipe still squeezes them.
Left failing rather than skipped, per retries:0 — a red test here is a real
defect, and hiding these would lose both.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
105 lines
4.9 KiB
JavaScript
105 lines
4.9 KiB
JavaScript
// @ts-check
|
|
// Test: LD1 — the PhotoSwipe slide's declared dimensions must match what the
|
|
// browser actually renders for the linked image (BUG 2026-07-09: portrait
|
|
// iPhone JPEGs squeezed to landscape in the fullscreen lightbox).
|
|
//
|
|
// Root cause: entry-journal.html.twig feeds `img.width`/`img.height` (raw
|
|
// getimagesize() of the ORIGINAL file — EXIF orientation ignored) into
|
|
// data-pswp-*, while the slide href points at that original, which browsers
|
|
// display EXIF-rotated. For a stored-landscape portrait photo the attrs say
|
|
// landscape while the pixels render portrait → PhotoSwipe squeezes them.
|
|
//
|
|
// ⚠️ THIS SPEC CURRENTLY FAILS — the root cause above is still live.
|
|
// entry-journal.html.twig:48-49 remains `{{ img.width }}` / `{{ img.height }}`,
|
|
// so the fixture (stored 800x600, EXIF Orientation=6) reports 800 while the
|
|
// browser renders 600. Fixing it means sourcing the dimensions from a medium
|
|
// Grav has already oriented rather than the raw original — which cannot be
|
|
// verified locally, since the dev container has no php-exif and so never
|
|
// applies auto_fix_orientation. Left failing so the squeeze stays visible.
|
|
//
|
|
// The invariant tested here is environment-proof: whatever file the slide
|
|
// links to, its browser-rendered natural size must equal the data-pswp-*
|
|
// attrs. (Whether the photo ALSO displays upright depends on the server's
|
|
// php-exif extension feeding auto_fix_orientation — present on prod, absent
|
|
// in the local dev container — so upright-ness is deliberately not asserted.)
|
|
//
|
|
// The fixture entry is planted straight on disk in the DEMO trip (the active
|
|
// trip is whatever site.yaml says and may be an unpublished draft that 404s;
|
|
// this spec exercises template rendering, not the posting pipeline — that is
|
|
// upload-gate.spec.js's job). touch(system.yaml) bumps the config checksum so
|
|
// the page-tree index rebuilds — the same invalidation cache-on-save uses.
|
|
const { test, expect } = require('@playwright/test');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const { execSync } = require('child_process');
|
|
// USER_DIR comes from helpers so GRAV_USER_DIR is honoured — without it a run
|
|
// against a checkout detached from the served tree plants the fixture in a
|
|
// different user/ than Grav renders, and LD1 fails as an opaque "card never
|
|
// appeared" timeout.
|
|
const { USER_DIR } = require('../helpers');
|
|
|
|
// Stored 800x600 with EXIF Orientation=6: browsers render it 600x800 portrait.
|
|
const EXIF_PORTRAIT = path.join(__dirname, '../../fixtures/test-photo-exif-portrait.jpg');
|
|
const DEMO_DAILIES = path.join(USER_DIR, 'pages/01.trips/italy-2026-demo/01.dailies');
|
|
const DEMO_TRIP_URL = '/trips/italy-2026-demo';
|
|
|
|
const TAG = `ld1-fixture-${Date.now()}`;
|
|
const ENTRY_DIR = path.join(DEMO_DAILIES, `2026-09-30-1200-${TAG}.entry`);
|
|
|
|
function bumpPageTreeIndex() {
|
|
// mtime bump on system.yaml changes config->checksum(), which keys the
|
|
// pages index — next request rebuilds the tree from disk.
|
|
execSync(`touch "${path.join(USER_DIR, 'config/system.yaml')}"`);
|
|
}
|
|
|
|
test.beforeAll(() => {
|
|
fs.mkdirSync(ENTRY_DIR, { recursive: true });
|
|
fs.copyFileSync(EXIF_PORTRAIT, path.join(ENTRY_DIR, 'photo-01.jpg'));
|
|
fs.writeFileSync(path.join(ENTRY_DIR, 'entry.md'), [
|
|
'---',
|
|
`title: 'UI Test ${TAG}'`,
|
|
"date: '2026-09-30 12:00'",
|
|
'template: entry',
|
|
'published: true',
|
|
'---',
|
|
'',
|
|
`Lightbox dims fixture ${TAG}. Safe to delete.`,
|
|
'',
|
|
].join('\n'));
|
|
bumpPageTreeIndex();
|
|
});
|
|
|
|
test.afterAll(() => {
|
|
fs.rmSync(ENTRY_DIR, { recursive: true, force: true });
|
|
bumpPageTreeIndex();
|
|
});
|
|
|
|
test('LD1: lightbox slide dims match the rendered size of the linked image', async ({ page }) => {
|
|
const card = page.locator('.journal-post', { hasText: TAG });
|
|
const slide = card.locator('a.journal-photo-slide').first();
|
|
|
|
// The config-checksum bump has second-granularity mtimes; a goto in the
|
|
// same second can still be served the stale cached page. Reload until the
|
|
// planted card is in the rendered feed.
|
|
await expect(async () => {
|
|
await page.goto(DEMO_TRIP_URL);
|
|
await expect(slide).toBeAttached({ timeout: 1000 });
|
|
}).toPass({ timeout: 20_000 });
|
|
|
|
const attrW = Number(await slide.getAttribute('data-pswp-width'));
|
|
const attrH = Number(await slide.getAttribute('data-pswp-height'));
|
|
const href = await slide.getAttribute('href');
|
|
expect(attrW).toBeGreaterThan(0);
|
|
expect(attrH).toBeGreaterThan(0);
|
|
|
|
const natural = await page.evaluate((src) => new Promise((resolve, reject) => {
|
|
const i = new Image();
|
|
i.onload = () => resolve({ w: i.naturalWidth, h: i.naturalHeight });
|
|
i.onerror = () => reject(new Error('image failed to load: ' + src));
|
|
i.src = src;
|
|
}), href);
|
|
|
|
expect(natural.w, `data-pswp-width vs rendered width of ${href}`).toBe(attrW);
|
|
expect(natural.h, `data-pswp-height vs rendered height of ${href}`).toBe(attrH);
|
|
});
|