browser-styles.spec.ts 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
  1. /**
  2. * WorkspaceBrowser scroll-region style contract, asserted against the CSS text
  3. * on disk: the session list reserves its scrollbar gutter so the scrollbar
  4. * cannot overlay row trailing content, and reserves it whether or not the list
  5. * currently overflows so expanding a group does not shift rows sideways.
  6. */
  7. import { readFileSync } from 'node:fs'
  8. import { fileURLToPath } from 'node:url'
  9. import { describe, expect, it } from 'vitest'
  10. const css = readFileSync(fileURLToPath(new URL('../src/client/WorkspaceBrowser.module.css', import.meta.url)), 'utf8')
  11. /**
  12. * Declarations of one class rule, keyed by property with whitespace collapsed.
  13. * Declaration order and trailing semicolons are normalized away.
  14. * @param className - local class name, without the leading dot.
  15. * @returns the rule's declarations, or undefined when no such rule exists.
  16. */
  17. function declarations(className: string): Map<string, string> | undefined {
  18. const withoutComments = css.replace(/\/\*[\s\S]*?\*\//g, ' ')
  19. const match = new RegExp(String.raw`(^|[\s,}])\.${className}\s*\{([^{}]*)\}`).exec(withoutComments)
  20. if (match === null) return undefined
  21. const found = new Map<string, string>()
  22. // The body group is unconditional in the pattern; the fallback only satisfies
  23. // noUncheckedIndexedAccess.
  24. for (const part of (match[2] ?? '').split(';')) {
  25. const colon = part.indexOf(':')
  26. if (colon === -1) continue
  27. found.set(part.slice(0, colon).trim(), part.slice(colon + 1).trim().replace(/\s+/g, ' '))
  28. }
  29. return found
  30. }
  31. describe('WorkspaceBrowser.module.css list', () => {
  32. const list = declarations('list')
  33. it('is the scrolling region', () => {
  34. expect(list).toBeDefined()
  35. expect(list!.get('overflow-y')).toBe('auto')
  36. })
  37. it('reserves the scrollbar gutter unconditionally', () => {
  38. // Row trailing content sits flush against the row's right padding, so an
  39. // overlay scrollbar covers it. `stable` keeps the reservation when the list
  40. // is short enough not to scroll, so expanding a group does not shift rows.
  41. expect(list!.get('scrollbar-gutter')).toBe('stable')
  42. })
  43. })