From ae02eb7d0d323a82d8fbfdb87787ba7d4b9a9e3b Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Sun, 1 Nov 2026 09:18:29 -0800 Subject: [PATCH] test(site-builder): parity harness neutralises documented editor-only differences (empty drop zone, empty-image placeholder) Container marks the applied drop zone with data-wsb-dropzone. Co-Authored-By: Claude Sonnet 5.5 --- craft/scripts/e2e-canvas-parity.mjs | 13 +++++++++++++ .../components/layout/Container.dropZone.test.tsx | 8 ++++++++ craft/src/components/layout/Container.tsx | 1 + 3 files changed, 22 insertions(+) diff --git a/craft/scripts/e2e-canvas-parity.mjs b/craft/scripts/e2e-canvas-parity.mjs index b67ab4e..276d45c 100644 --- a/craft/scripts/e2e-canvas-parity.mjs +++ b/craft/scripts/e2e-canvas-parity.mjs @@ -8,6 +8,10 @@ * same page's published HTML rendered in a plain page whose viewport equals * the canvas content width. * + * Stored state is loaded VERBATIM (blank style values included). Allowed editor-only + * differences (empty-container drop zone, empty-image placeholder) are neutralised in the + * canvas before measuring; see the ALLOWED block in run(). + * * Compared per matched element: bounding box (x/y/w/h, within 1px) and the * computed font-size / line-height / margin / padding / colour / font-family * / font-weight / display. Editor-only chrome (the dashed header/footer zone @@ -240,6 +244,15 @@ async function run(name, launcher, exe) { await ed.waitForSelector('.canvas-device-frame [data-craft-container]', { timeout: 20000 }); await ed.waitForTimeout(1500); // header/footer zone previews + layout settle const canvasW = await ed.evaluate(() => document.querySelector('.canvas-device-frame').clientWidth); + // ALLOWED editor-only differences (deliberate UX with no published counterpart), neutralised + // before measuring so they don't mask real diffs: + // - the 40px drop zone on EMPTY containers (Container sets data-wsb-dropzone only then) + // - the placeholder shown for an image node with no src (toHtml emits nothing) + await ed.evaluate(() => { + const f = document.querySelector('.canvas-device-frame'); + f.querySelectorAll('[data-wsb-dropzone]').forEach((el) => el.style.setProperty('min-height', '0', 'important')); + f.querySelectorAll('img[src^="data:image/svg"]').forEach((el) => el.style.setProperty('display', 'none', 'important')); + }); const canvas = await ed.evaluate(measure, { rootSel: '.canvas-device-frame', isCanvas: true, skipNav: idx > 0 }); if (shotDir) await ed.screenshot({ path: path.join(shotDir, `${label.replace('/', '_')}.${name}.canvas.png`) }); if (process.env.CANVAS_CHROME_DUMP && idx === 0 && spec === specs[0]) { diff --git a/craft/src/components/layout/Container.dropZone.test.tsx b/craft/src/components/layout/Container.dropZone.test.tsx index 3621b4e..8d75e75 100644 --- a/craft/src/components/layout/Container.dropZone.test.tsx +++ b/craft/src/components/layout/Container.dropZone.test.tsx @@ -31,3 +31,11 @@ describe('Container empty-only drop zone', () => { expect(mount({ style: { minHeight: '100px' } }, false).style.minHeight).toBe('100px'); }); }); + +describe('data-wsb-dropzone marker (harness hook)', () => { + test('set only while the drop-zone min-height is the one applied', () => { + expect(mount({ style: {} }, false).hasAttribute('data-wsb-dropzone')).toBe(true); + expect(mount({ style: {} }, true).hasAttribute('data-wsb-dropzone')).toBe(false); + expect(mount({ style: { minHeight: '100px' } }, false).hasAttribute('data-wsb-dropzone')).toBe(false); + }); +}); diff --git a/craft/src/components/layout/Container.tsx b/craft/src/components/layout/Container.tsx index 4797f83..3c59b1d 100644 --- a/craft/src/components/layout/Container.tsx +++ b/craft/src/components/layout/Container.tsx @@ -117,6 +117,7 @@ export const Container: UserComponent = ({ style: outerStyle, 'data-craft-container': 'true', 'data-fullwidth': fullWidth ? '' : undefined, + 'data-wsb-dropzone': isEmpty && !style.minHeight ? '' : undefined, id: idValue, className: cssClass || undefined, },