From 232958cd135deb15b80da0f23886d7c632c62a88 Mon Sep 17 00:00:00 2001 From: surtic86 Date: Thu, 15 Oct 2026 09:24:04 +0200 Subject: [PATCH] test: SIZE option for screenshots and form alignment; scale filter rows (T9) Query filter rows kept a fixed 40px height, so in Compact their labels sat 3px below the operator select. Co-Authored-By: Claude Opus 5.5 --- e2e/check-form-alignment.mjs | 5 +++++ e2e/screenshots.mjs | 9 ++++++++- tonalmine/stylesheets/tables.css | 4 ++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/e2e/check-form-alignment.mjs b/e2e/check-form-alignment.mjs index d090a1f..1077d70 100644 --- a/e2e/check-form-alignment.mjs +++ b/e2e/check-form-alignment.mjs @@ -4,9 +4,12 @@ // to 480px, compact fields (numbers, dates) are 140px, long-text fields fill the column; // on phones every field fills its row // Usage: node check-form-alignment.mjs (exits 1 if any row is off by more than 2px) +// SIZE=compact|large node check-form-alignment.mjs (in that size mode; needs the plugin) import { chromium } from 'playwright'; +import { setSize } from './size.mjs'; const BASE = process.env.REDMINE_URL ?? 'http://127.0.0.1:3070'; +const SIZE = process.env.SIZE; const settingsTabs = ['general', 'display', 'authentication', 'api', 'projects', 'users', 'issues', 'timelog', 'attachments', 'mail_handler', 'repositories']; const pages = [ '/projects?display_type=list', '/projects/apollo/issues', '/projects/apollo/time_entries', '/projects/apollo/issues/new', '/issues/13/edit', '/issues/bulk_edit?ids[]=13&ids[]=17', @@ -26,6 +29,7 @@ for (const width of [1440, 1024, 390]) { await page.fill('#password', 'admin'); await page.click('#login-submit'); await page.waitForURL((url) => !url.pathname.startsWith('/login')); + if (SIZE) await setSize(page, SIZE); for (const path of pages) { await page.goto(`${BASE}${path}`, { waitUntil: 'networkidle' }); @@ -77,6 +81,7 @@ for (const width of [1440, 1024, 390]) { failures += bad.length; console.log(`${bad.length ? 'FAIL' : 'ok '} ${width} ${path} (${rows.length} checks)${bad.map((r) => `\n ${r.label}: ${r.offset}px`).join('')}`); } + if (SIZE) await setSize(page); await page.close(); } diff --git a/e2e/screenshots.mjs b/e2e/screenshots.mjs index 50ce7c9..f9f8791 100644 --- a/e2e/screenshots.mjs +++ b/e2e/screenshots.mjs @@ -1,12 +1,15 @@ // Captures the page checklist in light/dark × desktop/mobile into ../screenshots/. // Usage: node screenshots.mjs [filter] e.g. `node screenshots.mjs issue` for pages whose name contains "issue". +// SIZE=compact|large node screenshots.mjs (in that size mode, files end in -compact/-large; needs the plugin) // Needs the dev server with seed data (see README). import { chromium } from 'playwright'; import { mkdir } from 'node:fs/promises'; +import { setSize } from './size.mjs'; const BASE = process.env.REDMINE_URL ?? 'http://127.0.0.1:3070'; const OUT = new URL('../screenshots/', import.meta.url).pathname; const filter = process.argv[2] ?? ''; +const SIZE = process.env.SIZE; const pages = [ ['my-page', '/my/page'], @@ -111,9 +114,11 @@ for (const scheme of ['light', 'dark']) { for (const [vpName, vp] of Object.entries(viewports)) { const { width, height, ...rest } = vp; const context = await browser.newContext({ viewport: { width, height }, colorScheme: scheme, ...rest }); + // Logged out, the size comes from localStorage; logged in, from the account (set below) + if (SIZE) await context.addInitScript((size) => localStorage.setItem('tonalmine', JSON.stringify({ size })), SIZE); const page = await context.newPage(); const shot = (name, fullPage = true) => - page.screenshot({ path: `${OUT}${name}-${scheme}-${vpName}.png`, fullPage }); + page.screenshot({ path: `${OUT}${name}-${scheme}-${vpName}${SIZE ? `-${SIZE}` : ''}.png`, fullPage }); for (const [name, path] of publicPages.filter(([n]) => n.includes(filter))) { await page.goto(`${BASE}${path}`, { waitUntil: 'networkidle' }); @@ -124,6 +129,7 @@ for (const scheme of ['light', 'dark']) { await page.fill('#password', 'admin'); await page.click('#login-submit'); await page.waitForURL((url) => !url.pathname.startsWith('/login')); + if (SIZE) await setSize(page, SIZE); for (const [name, path] of pages.filter(([n]) => n.includes(filter))) { await page.goto(`${BASE}${path}`, { waitUntil: 'networkidle' }); @@ -135,6 +141,7 @@ for (const scheme of ['light', 'dark']) { await act(page); await shot(name, !opts.viewportOnly); } + if (SIZE) await setSize(page); await context.close(); } } diff --git a/tonalmine/stylesheets/tables.css b/tonalmine/stylesheets/tables.css index 0be96c6..4bab8d9 100644 --- a/tonalmine/stylesheets/tables.css +++ b/tonalmine/stylesheets/tables.css @@ -146,9 +146,9 @@ a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: var(--md-sy 40px high and centers its content; the row stays top-aligned so an expanded multi-select grows downwards without dragging the label to its middle. */ #filters-table .filter { align-items: flex-start; margin-block: 4px 0; } -#filters-table .filter .field { display: flex; align-items: center; gap: 8px; min-block-size: 40px; } +#filters-table .filter .field { display: flex; align-items: center; gap: 8px; min-block-size: calc(40px + var(--nm-density)); } #filters-table .filter .field input[type="checkbox"] { margin: 0; } -#filters-table .filter .values { display: flex; align-items: center; min-block-size: 40px; flex-wrap: wrap; gap: 4px 8px; } +#filters-table .filter .values { display: flex; align-items: center; min-block-size: calc(40px + var(--nm-density)); flex-wrap: wrap; gap: 4px 8px; } /* "Add filter" is an M3 tonal button, not one more outlined field. A select has no placeholder, so the label lies over it in the same grid cell and lets taps through. A zero width with a 100% minimum keeps the longest option from widening the button. */