1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
// @ts-nocheck
import { describe, expect, it } from 'vitest';
// Live 375px browser overflow regression (artifacts/qa/supervisor-live/
// mobile-overflow-probe.json): document scrollWidth measured 532 → 552 at a
// 375px viewport, sidebar/main 552px, cards 523px; DOM authoritative (a
// full-page screenshot expands to the overflow width, so source/DOM checks
// are the honest regression). Root causes are real CSS: grid min-content
// blow-up (layout/grid3), nowrap table cells, and unbreakable provider error
// URLs. CSS `?raw` imports are stubbed empty by vitest, so the real source
// files are read from disk instead.
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
const srcRoot = path.join(fileURLToPath('file://' + process.cwd()), 'src');
const appCss = readFileSync(path.join(srcRoot, 'app.css'), 'utf8');
const layoutSource = readFileSync(path.join(srcRoot, 'components', 'Layout.svelte'), 'utf8');
const datasetCard = readFileSync(path.join(srcRoot, 'components', 'DatasetCard.svelte'), 'utf8');
describe('375px responsive sizing (no global content hiding)', () => {
it('layout columns shrink below min-content (no document-level overflow)', () => {
// the mobile layout must be allowed to shrink (minmax(0,1fr), not bare 1fr)
expect(layoutSource).toMatch(/@media\s*\(max-width:\s*900px\)[\s\S]*?\.layout\s*\{[^}]*grid-template-columns:\s*minmax\(0,\s*1fr\)/);
// the content column opts out of min-content propagation
expect(layoutSource).toMatch(/\.content\s*\{[^}]*min-width:\s*0/);
// project tab context wraps instead of forcing one long line
expect(layoutSource).toMatch(/\.projctx\s*\{[^}]*flex-wrap:\s*wrap/);
});
it('multi-column forms and tables collapse/wrap on narrow view', () => {
expect(appCss).toContain('@media (max-width: 900px)');
expect(appCss).toContain('.grid2, .grid3 { grid-template-columns: minmax(0, 1fr); }');
expect(appCss).toContain('table.data th, table.data td {');
expect(appCss).toContain('white-space: normal');
});
it('long real provider errors wrap instead of inflating width', () => {
// `overflow-wrap: anywhere` wraps even long URL payloads; NOT
// overflow-x:hidden on body/document (that would hide content).
expect(appCss).toMatch(/\.banner\s*\{[^}]*overflow-wrap:\s*anywhere/);
expect(appCss.includes('body { overflow-x: hidden')).toBe(false);
expect(appCss.includes('html { overflow-x: hidden')).toBe(false);
});
it('dataset card keeps a local scroll container for tabular data', () => {
expect(datasetCard).toContain('scrollx');
});
});
function getMediaBlock(css, query) {
const idx = css.indexOf(`@media ${query}`);
if (idx < 0) return null;
const open = css.indexOf('{', idx);
let depth = 1;
for (let i = open + 1; i < css.length; i++) {
if (css[i] === '{') depth++;
else if (css[i] === '}') {
depth--;
if (depth === 0) return css.slice(idx, i + 1);
}
}
return null;
}
|