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