summaryrefslogtreecommitdiff
path: root/frontend/src/lib/mobileCss.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/lib/mobileCss.test.ts')
-rw-r--r--frontend/src/lib/mobileCss.test.ts63
1 files changed, 63 insertions, 0 deletions
diff --git a/frontend/src/lib/mobileCss.test.ts b/frontend/src/lib/mobileCss.test.ts
new file mode 100644
index 0000000..c4d1efb
--- /dev/null
+++ b/frontend/src/lib/mobileCss.test.ts
@@ -0,0 +1,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;
+}