// @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'); }); }); describe('round3 shadcn-svelte 主题 tokens(b1D0dv72 preset 精神 + 截图重配)', () => { it('radius 上调至 0.75rem(Stripe 风格大圆角),shadcn radius-lg 由此派生', () => { expect(appCss).toMatch(/--radius:\s*0\.75rem/); expect(appCss).toMatch(/--radius-lg:\s*var\(--radius\)/); }); it('mint 默认 + amber 双套变量组(用户 create 截图色值)', () => { // mint ~#70DAB6 / 深一档 #58D3AA;amber ~#F0AE00 expect(appCss).toContain('--brand: #58d3aa'); expect(appCss).toContain('--ring: #70dab6'); expect(appCss).toMatch(/\.theme-amber\s*\{/); expect(appCss).toContain('--brand: #f0ae00'); // 默认 mint、amber 仅通过 .theme-amber 切换(src/lib/theme.ts 挂类) expect(appCss).not.toMatch(/NAVER/); }); it('数字排印:tabular-nums 大字重(mvalue 1.5rem/700),保留 .num/.metric-grid', () => { expect(appCss).toMatch(/\.mvalue\s*\{[^}]*font-weight:\s*700/); expect(appCss).toMatch(/\.mvalue\s*\{[^}]*font-variant-numeric:\s*tabular-nums/); expect(appCss).toContain('.metric-grid {'); }); it('legacy 组件选择器已从 app.css 删除(.btn/.banner 变体/.badge/.tabs/table.data 组件样式)', () => { expect(appCss).not.toMatch(/\.btn\s*\{|\.\btn\.|\.btn\./); expect(appCss).not.toContain('.btn.primary {'); expect(appCss).not.toContain('.badge.teal {'); expect(appCss).not.toContain('.tabs button {'); expect(appCss).not.toContain('table.data th {'); expect(appCss).not.toContain('.metric {'); expect(appCss).not.toContain('.metric-card {'); // .banner 仅剩窄视口防溢出工具(无颜色组件样式) expect(appCss).not.toContain('.banner.error {'); }); }); 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; }