From b72e0fcaf33a5f8f29e71a8894e66d3d0fd3c33a Mon Sep 17 00:00:00 2001 From: tommy0103 Date: Mon, 3 Aug 2026 02:12:38 +0800 Subject: [PATCH] test(renderer): run every Electron suite from one command The Electron suites were only reachable one npm script at a time, so a new one was covered by whoever remembered it existed. test:electron:all builds once and runs all five, reporting a single summary and a non-zero exit. The runner lives under tests/ rather than app/scripts/, which .gitignore excludes entirely. The image row spacing assertion also stops hard-coding the current gap -- it had 0.59px of headroom -- and calibrates against the spacing the rest of the timeline is using. Co-Authored-By: Claude Opus 5 (1M context) --- app/package.json | 3 ++- app/tests/electron-session-images.mjs | 15 ++++++++++++- app/tests/run-electron-suites.mjs | 32 +++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 app/tests/run-electron-suites.mjs diff --git a/app/package.json b/app/package.json index 333d25c..8926bef 100644 --- a/app/package.json +++ b/app/package.json @@ -23,7 +23,8 @@ "test:electron:images": "electron-vite build && electron --no-sandbox tests/electron-session-images.mjs", "test:electron:timeline": "electron-vite build && electron --no-sandbox tests/electron-session-virtualization.mjs", "test:electron:reader-state": "electron-vite build && electron --no-sandbox tests/electron-session-reader-state.mjs", - "test:electron:file-refs": "electron-vite build && electron --no-sandbox tests/electron-file-references.mjs" + "test:electron:file-refs": "electron-vite build && electron --no-sandbox tests/electron-file-references.mjs", + "test:electron:all": "electron-vite build && node tests/run-electron-suites.mjs" }, "build": { "appId": "com.obelisk.app", diff --git a/app/tests/electron-session-images.mjs b/app/tests/electron-session-images.mjs index 514c2ec..e342edf 100644 --- a/app/tests/electron-session-images.mjs +++ b/app/tests/electron-session-images.mjs @@ -300,6 +300,16 @@ async function run() { const smallImageRect = smallImage.getBoundingClientRect(); const wideRowRect = wideRow.getBoundingClientRect(); const nextRowRect = nextRow.getBoundingClientRect(); + // Row spacing is applied by the virtualizer, not by CSS on the row, so + // calibrate against the spacing the rest of this timeline is using rather + // than hard-coding the current value. + const mountedRows = [...document.querySelectorAll('.virtual-timeline-row')] + .map(row => ({ index: Number(row.dataset.index), rect: row.getBoundingClientRect() })) + .sort((left, right) => left.index - right.index); + const gaps = mountedRows + .slice(1) + .map((row, offset) => row.rect.top - mountedRows[offset].rect.bottom) + .sort((left, right) => left - right); return { wrapClientWidth: wrap.clientWidth, wrapScrollWidth: wrap.scrollWidth, @@ -312,6 +322,7 @@ async function run() { smallNaturalWidth: smallImage.naturalWidth, rowHeight: wideRowRect.height, rowGap: nextRowRect.top - wideRowRect.bottom, + typicalRowGap: gaps[Math.floor(gaps.length / 2)] ?? null, brokenText: brokenHost.shadowRoot.querySelector('figcaption')?.textContent || '', }; })()`, true); @@ -337,7 +348,9 @@ async function run() { `small images keep their intrinsic width (${JSON.stringify(layout)})`, ); assert( - layout.rowHeight > layout.wideImageHeight && layout.rowGap >= 13, + layout.rowHeight > layout.wideImageHeight + && layout.typicalRowGap !== null + && Math.abs(layout.rowGap - layout.typicalRowGap) <= 1, `measured image rows do not overlap the following row (${JSON.stringify(layout)})`, ); assert( diff --git a/app/tests/run-electron-suites.mjs b/app/tests/run-electron-suites.mjs new file mode 100644 index 0000000..2e1ac61 --- /dev/null +++ b/app/tests/run-electron-suites.mjs @@ -0,0 +1,32 @@ +// Runs every Electron regression suite against one build and reports a single +// summary. Each suite already prints its own PASS/FAIL lines; this exists so a +// new suite is covered by one command instead of only being run by whoever +// remembers it is there. + +import { spawnSync } from 'node:child_process' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import electron from 'electron' + +const appRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') +const suites = [ + 'electron-concurrency.mjs', + 'electron-session-images.mjs', + 'electron-session-virtualization.mjs', + 'electron-session-reader-state.mjs', + 'electron-file-references.mjs', +] + +const failed = [] +for (const suite of suites) { + console.log(`\n=== ${suite} ===`) + const result = spawnSync(electron, ['--no-sandbox', path.join('tests', suite)], { + cwd: appRoot, + stdio: 'inherit', + }) + if (result.status !== 0) failed.push(`${suite} (exit ${result.status ?? 'signal'})`) +} + +console.log(`\n${suites.length - failed.length}/${suites.length} Electron suites passed`) +for (const failure of failed) console.error(`FAILED: ${failure}`) +process.exit(failed.length ? 1 : 0)