sidebar-styles.client.spec.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. /** Sidebar shell style contracts shared with its slot-owned controls. */
  2. import { readFileSync } from 'node:fs'
  3. import { fileURLToPath } from 'node:url'
  4. import { describe, expect, it } from 'vitest'
  5. const css = readFileSync(fileURLToPath(new URL('../src/client/SidebarRoot.module.css', import.meta.url)), 'utf8')
  6. /**
  7. * Declarations of one exact selector, keyed by property.
  8. * @param selector - exact selector text.
  9. * @returns the normalized declarations, or undefined when absent.
  10. */
  11. function declarations(selector: string): Map<string, string> | undefined {
  12. const withoutComments = css.replace(/\/\*[\s\S]*?\*\//g, ' ')
  13. for (const [, selectorList = '', body = ''] of withoutComments.matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
  14. if (!selectorList.split(',').map(value => value.trim()).includes(selector)) continue
  15. const found = new Map<string, string>()
  16. for (const part of body.split(';')) {
  17. const colon = part.indexOf(':')
  18. if (colon === -1) continue
  19. found.set(part.slice(0, colon).trim(), part.slice(colon + 1).trim().replace(/\s+/g, ' '))
  20. }
  21. return found
  22. }
  23. return undefined
  24. }
  25. describe('SidebarRoot.module.css', () => {
  26. it('shares and cancels the wide shell trailing padding structurally', () => {
  27. const root = declarations('.root')
  28. expect(root?.get('--dsh-sidebar-inline-padding')).toBe('12px')
  29. expect(root?.get('padding')).toBe('6px var(--dsh-sidebar-inline-padding)')
  30. expect(declarations('.regionArea')?.get('margin-left')).toBe('-4px')
  31. expect(declarations('.regionArea')?.get('padding-left')).toBe('4px')
  32. expect(declarations('.regionArea')?.get('margin-right')).toBe(
  33. 'calc(-1 * var(--dsh-sidebar-inline-padding))',
  34. )
  35. expect(declarations('.collapsed .regionArea')?.get('margin-left')).toBe('0')
  36. expect(declarations('.collapsed .regionArea')?.get('padding-left')).toBe('0')
  37. expect(declarations('.collapsed .regionArea')?.get('margin-right')).toBe('0')
  38. })
  39. it('moves the four upper controls while the settings seat only fades', () => {
  40. const animation = 'rail-in 150ms var(--ds-ease-in-out) backwards'
  41. for (const selector of [
  42. '.railIn .iconButton',
  43. '.railIn .newSession',
  44. '.railIn .regionArea',
  45. ]) {
  46. expect(declarations(selector)?.get('animation')).toBe(animation)
  47. }
  48. expect(declarations('.railIn .footArea')?.get('animation')).toBe(
  49. 'rail-fade-in 150ms var(--ds-ease-in-out) backwards',
  50. )
  51. expect(css).toMatch(
  52. /@keyframes rail-in\s*\{\s*from\s*\{\s*opacity: 0;\s*transform: translateX\(49px\);\s*}\s*}/,
  53. )
  54. expect(css).toMatch(/@keyframes rail-fade-in\s*\{\s*from\s*\{\s*opacity: 0;\s*}\s*}/)
  55. })
  56. it('gives shell rail controls the same base anchor for their shared translation', () => {
  57. expect(declarations('.collapsed .logoRow')?.get('justify-content')).toBe('flex-start')
  58. expect(declarations('.collapsed .newSession')?.get('align-self')).toBe('flex-start')
  59. expect(declarations('.collapsed .newSession')?.get('width')).toBe('36px')
  60. })
  61. it('keeps the slotted brand row at the full artwork height', () => {
  62. expect(declarations('.brandIdentity')?.get('height')).toBe('24px')
  63. expect(declarations('.brandName')?.get('height')).toBe('24px')
  64. expect(declarations('.brandName')?.get('line-height')).toBe('24px')
  65. expect(declarations('.brandName')?.get('font-size')).toBe('18px')
  66. expect(declarations('.fallbackBrandName')?.get('font-size')).toBe('17px')
  67. expect(declarations('.fallbackBrandName')?.get('white-space')).toBe('nowrap')
  68. })
  69. })