import fs from 'node:fs'; import path from 'node:path'; import { describe, expect, test } from 'vitest'; const stylesheet = fs.readFileSync( path.resolve(process.cwd(), 'apps/admin-web/src/styles/admin.css'), 'utf8', ); describe('admin shell scrolling contract', () => { test('desktop shell keeps sidebar separate from the scrollable content', () => { expect(ruleFor('.admin-shell')).toContain('\n height: 100dvh;'); expect(ruleFor('.admin-shell')).toContain('overflow: hidden'); expect(ruleFor('.admin-sidebar')).toContain('min-height: 0'); expect(ruleFor('.admin-sidebar')).toContain('overflow-y: auto'); expect(ruleFor('.admin-main')).toContain('min-height: 0'); expect(ruleFor('.admin-main')).toContain('overflow: hidden'); expect(ruleFor('.admin-content')).toContain('min-height: 0'); expect(ruleFor('.admin-content')).toContain('overflow: auto'); }); test('mobile navigation stays in one horizontally scrollable row', () => { const mobileStyles = stylesheet.slice( stylesheet.indexOf('@media (max-width: 980px)'), ); expect(mobileStyles).toContain('.admin-bottom-nav {'); expect(mobileStyles).toContain('display: flex'); expect(mobileStyles).toContain('overflow-x: auto'); expect(mobileStyles).not.toContain( 'grid-template-columns: repeat(auto-fit, minmax(64px, 1fr))', ); expect(mobileStyles).toContain('flex: 0 0 76px'); }); test('database preview headers stay on one line and expose ellipsis', () => { const sortButton = ruleFor('.admin-table-sort-button'); const sortLabel = ruleFor('.admin-table-sort-button span'); expect(sortButton).toContain('white-space: nowrap'); expect(sortButton).toContain('overflow: hidden'); expect(sortLabel).toContain('text-overflow: ellipsis'); expect(sortLabel).toContain('white-space: nowrap'); }); test('copy feedback uses an auto-dismissing toast surface', () => { const toast = ruleFor('.admin-toast'); expect(toast).toContain('position: fixed'); expect(toast).toContain('z-index: 120'); expect(toast).toContain('pointer-events: none'); expect(toast).toContain('animation: admin-toast-in'); expect(stylesheet).toContain(".admin-toast[data-tone='error']"); }); test('detail JSON keeps only a slim scrollbar thumb', () => { const json = ruleFor('.admin-json-preview'); expect(json).toContain('scrollbar-width: thin'); expect(json).toContain('scrollbar-color: #d1b09a transparent'); expect(stylesheet).toContain( '.admin-json-preview::-webkit-scrollbar-track', ); expect(stylesheet).toContain('background: transparent'); expect(stylesheet).toContain( '.admin-json-preview::-webkit-scrollbar-thumb', ); }); }); function ruleFor(selector: string) { const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const match = stylesheet.match( new RegExp(`${escapedSelector}\\s*\\{([^}]*)\\}`), ); if (!match) { throw new Error(`Missing CSS rule for ${selector}`); } return match[1]; }