/** * WorkspaceBrowser spacing contract, asserted against the CSS text on disk: * row fills share the shell's trailing inset, the stable scrollbar counts * inside it, and flat, grouped, and search views keep their intended rhythm. */ import { readFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import { describe, expect, it } from 'vitest' const css = readFileSync(fileURLToPath(new URL('../src/client/WorkspaceBrowser.module.css', import.meta.url)), 'utf8') /** * Declarations of one selector rule, keyed by property with whitespace collapsed. * Declaration order and trailing semicolons are normalized away. * @param selector - one exact selector, including a leading dot for local classes. * @returns the rule's declarations, or undefined when no such rule exists. */ function declarations(selector: string): Map | undefined { const withoutComments = css.replace(/\/\*[\s\S]*?\*\//g, ' ') for (const [, selectorList = '', body = ''] of withoutComments.matchAll(/([^{}]+)\{([^{}]*)\}/g)) { if (!selectorList.split(',').map(value => value.trim()).includes(selector)) continue const found = new Map() for (const part of body.split(';')) { const colon = part.indexOf(':') if (colon === -1) continue found.set(part.slice(0, colon).trim(), part.slice(colon + 1).trim().replace(/\s+/g, ' ')) } return found } return undefined } describe('WorkspaceBrowser.module.css list', () => { const root = declarations('.root') const listArea = declarations('.listArea') const list = declarations('.list') it('is the scrolling region', () => { expect(list).toBeDefined() expect(list!.get('overflow-y')).toBe('auto') }) it('counts the themed scrollbar inside the shell trailing inset', () => { expect(root?.get('--dsh-session-list-edge-inset')).toBe('var(--dsh-sidebar-inline-padding)') expect(root?.get('--dsh-session-list-scrollbar-width')).toBe('8px') expect(root?.get('--dsh-session-list-scrollbar-offset')).toBe('2px') expect(root?.get('padding-right')).toBe('var(--dsh-session-list-edge-inset)') expect(listArea?.get('margin-right')).toBe('calc(-1 * var(--dsh-session-list-edge-inset))') expect(declarations('.fade')?.get('right')).toBe('var(--dsh-session-list-edge-inset)') expect(list?.get('margin-right')).toBe('var(--dsh-session-list-scrollbar-offset)') expect(list?.get('padding-right')).toBe([ 'calc(', 'var(--dsh-session-list-edge-inset)', '- var(--dsh-session-list-scrollbar-width)', '- var(--dsh-session-list-scrollbar-offset)', ')', ].join(' ')) expect(declarations('.list::-webkit-scrollbar')).toBeUndefined() }) it('reserves the scrollbar whether or not the list overflows', () => { expect(list!.get('scrollbar-gutter')).toBe('stable') }) it('keeps 2px between rows and 4px between workspace groups', () => { expect(declarations('.flatList > * + *')?.get('margin-top')).toBe('2px') expect(declarations(".searchTree > [role='treeitem'] + [role='treeitem']")?.get('margin-top')).toBe('2px') expect(declarations('.groupSection > * + *')?.get('margin-top')).toBe('2px') expect(declarations('.groupSection + .groupSection')?.get('margin-top')).toBe('4px') }) })