test(ui): lock responsive shell breakpoints
This commit is contained in:
parent
a65d7261ca
commit
c51544aa29
75
web/src/shellResponsiveCss.test.ts
Normal file
75
web/src/shellResponsiveCss.test.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
const css = readFileSync(resolve(__dirname, 'index.css'), 'utf8');
|
||||
|
||||
const mediaBlocks = (() => {
|
||||
const blocks: Array<{ query: string; body: string }> = [];
|
||||
const mediaPattern = /@media\s*([^{]+)\{/g;
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
while ((match = mediaPattern.exec(css))) {
|
||||
let depth = 1;
|
||||
let cursor = mediaPattern.lastIndex;
|
||||
for (; cursor < css.length; cursor += 1) {
|
||||
if (css[cursor] === '{') depth += 1;
|
||||
if (css[cursor] === '}') depth -= 1;
|
||||
if (depth === 0) break;
|
||||
}
|
||||
blocks.push({
|
||||
query: match[1].trim(),
|
||||
body: css.slice(mediaPattern.lastIndex, cursor),
|
||||
});
|
||||
mediaPattern.lastIndex = cursor + 1;
|
||||
}
|
||||
|
||||
return blocks;
|
||||
})();
|
||||
|
||||
const blocksFor = (queryPart: string) =>
|
||||
mediaBlocks.filter((block) => block.query.includes(queryPart));
|
||||
|
||||
const ruleBodies = (source: string, selector: string) => {
|
||||
const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
return [...source.matchAll(new RegExp(`${escapedSelector}\\s*\\{([^}]*)\\}`, 'g'))]
|
||||
.map((match) => match[1]);
|
||||
};
|
||||
|
||||
describe('responsive shell CSS contract', () => {
|
||||
it('keeps desktop navigation as a left rail by default', () => {
|
||||
expect(css).toMatch(/\.dashboard-main\s*\{[\s\S]*?margin-left:\s*248px;/);
|
||||
expect(css).toMatch(/\.trading-sidebar\s*\{[\s\S]*?width:\s*248px;/);
|
||||
expect(css).toMatch(/\.dashboard-right-panel\s*\{[\s\S]*?width:\s*340px;/);
|
||||
});
|
||||
|
||||
it('keeps tablet navigation on the side instead of switching to footer nav', () => {
|
||||
const tabletBlocks = blocksFor('min-width: 561px');
|
||||
expect(tabletBlocks.length).toBeGreaterThan(0);
|
||||
|
||||
const tabletCss = tabletBlocks.map((block) => block.body).join('\n');
|
||||
const tabletSidebarRules = ruleBodies(tabletCss, '.trading-sidebar').join('\n');
|
||||
expect(tabletCss).toContain('margin-left: 88px');
|
||||
expect(tabletSidebarRules).toContain('width: 88px');
|
||||
expect(tabletSidebarRules).toContain('flex-direction: column');
|
||||
expect(tabletSidebarRules).toContain('border-right: 1px solid var(--border)');
|
||||
expect(tabletSidebarRules).not.toContain('width: 100%');
|
||||
expect(tabletSidebarRules).not.toContain('border-top: 1px solid var(--border)');
|
||||
});
|
||||
|
||||
it('uses bottom navigation only at phone width', () => {
|
||||
const phoneCss = blocksFor('max-width: 560px').map((block) => block.body).join('\n');
|
||||
const phoneSidebarRules = ruleBodies(phoneCss, '.trading-sidebar').join('\n');
|
||||
expect(phoneCss).toContain('margin-left: 0');
|
||||
expect(phoneSidebarRules).toContain('width: 100%');
|
||||
expect(phoneSidebarRules).toContain('flex-direction: row');
|
||||
expect(phoneSidebarRules).toContain('border-top: 1px solid var(--border)');
|
||||
|
||||
const wideResponsiveCss = mediaBlocks
|
||||
.filter((block) => !block.query.includes('max-width: 560px'))
|
||||
.map((block) => block.body)
|
||||
.join('\n');
|
||||
const wideSidebarRules = ruleBodies(wideResponsiveCss, '.trading-sidebar').join('\n');
|
||||
expect(wideSidebarRules).not.toContain('width: 100%');
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user