theme-presenter.client.spec.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. // @vitest-environment jsdom
  2. import { afterEach, beforeEach, describe, expect, it } from 'vitest'
  3. import type { ThemeSnapshot } from '@deepseek-ai/dsh-client-ui-theme/client'
  4. import { DARK_ATTRIBUTE, ThemePresenter } from '@deepseek-ai/dsh-client-ui-layout/src/client/theme-presenter.ts'
  5. const LIGHT_THEME_COLOR = 'rgb(255, 255, 255)'
  6. const DARK_THEME_COLOR = 'rgb(21, 21, 23)'
  7. function snapshot(colorScheme: 'light' | 'dark', tokens: Record<string, string> = {}, fontSize = 14): ThemeSnapshot {
  8. // The presenter must key off colorScheme, not the id — keep them distinct.
  9. const active = { id: `${colorScheme}-test`, colorScheme, tokens }
  10. return { preference: colorScheme, fontSize, active, themes: [active], revision: 1 }
  11. }
  12. function clearThemePresentation(): void {
  13. document.head.querySelectorAll('meta[name="theme-color"], style[data-theme-presenter-test]').forEach((node) => { node.remove() })
  14. }
  15. function themeColorMeta(): HTMLMetaElement | null {
  16. return document.head.querySelector<HTMLMetaElement>('meta[name="theme-color"]')
  17. }
  18. beforeEach(() => {
  19. clearThemePresentation()
  20. document.documentElement.style.removeProperty('color-scheme')
  21. document.body.removeAttribute(DARK_ATTRIBUTE)
  22. document.body.removeAttribute('style')
  23. const style = document.createElement('style')
  24. style.dataset.themePresenterTest = ''
  25. style.textContent = `
  26. body { background-color: ${LIGHT_THEME_COLOR}; }
  27. body[${DARK_ATTRIBUTE}] { background-color: ${DARK_THEME_COLOR}; }
  28. `
  29. document.head.append(style)
  30. })
  31. afterEach(clearThemePresentation)
  32. describe('ThemePresenter', () => {
  33. it('light scheme sets root color-scheme and leaves the dark attribute absent', () => {
  34. const presenter = new ThemePresenter()
  35. presenter.apply(snapshot('light'))
  36. expect(document.documentElement.style.colorScheme).toBe('light')
  37. expect(document.body.hasAttribute(DARK_ATTRIBUTE)).toBe(false)
  38. expect(themeColorMeta()?.content).toBe(LIGHT_THEME_COLOR)
  39. })
  40. it('dark scheme sets root color-scheme, the attribute, and metadata; switching to light updates one node', () => {
  41. const presenter = new ThemePresenter()
  42. presenter.apply(snapshot('dark'))
  43. const meta = themeColorMeta()
  44. expect(document.documentElement.style.colorScheme).toBe('dark')
  45. expect(document.body.hasAttribute(DARK_ATTRIBUTE)).toBe(true)
  46. expect(meta?.content).toBe(DARK_THEME_COLOR)
  47. presenter.apply(snapshot('light'))
  48. expect(document.documentElement.style.colorScheme).toBe('light')
  49. expect(document.body.hasAttribute(DARK_ATTRIBUTE)).toBe(false)
  50. expect(themeColorMeta()).toBe(meta)
  51. expect(meta?.content).toBe(LIGHT_THEME_COLOR)
  52. expect(document.head.querySelectorAll('meta[name="theme-color"]')).toHaveLength(1)
  53. })
  54. it('applies tokens as inline variables and clears the previous set on theme change', () => {
  55. const presenter = new ThemePresenter()
  56. presenter.apply(snapshot('dark', { '--dsw-alias-bg': '#111', '--dsw-alias-fg': '#eee' }))
  57. expect(document.body.style.getPropertyValue('--dsw-alias-bg')).toBe('#111')
  58. expect(document.body.style.getPropertyValue('--dsw-alias-fg')).toBe('#eee')
  59. presenter.apply(snapshot('light', { '--dsw-alias-bg': '#fff' }))
  60. expect(document.body.style.getPropertyValue('--dsw-alias-bg')).toBe('#fff')
  61. // The old theme's extra variable is gone, not merged.
  62. expect(document.body.style.getPropertyValue('--dsw-alias-fg')).toBe('')
  63. })
  64. it('publishes the content font size and follows changes', () => {
  65. const presenter = new ThemePresenter()
  66. presenter.apply(snapshot('light'))
  67. expect(document.body.style.getPropertyValue('--dsh-content-font-size')).toBe('14px')
  68. presenter.apply(snapshot('light', {}, 17))
  69. expect(document.body.style.getPropertyValue('--dsh-content-font-size')).toBe('17px')
  70. })
  71. it('dispose removes color-scheme, the attribute, the font-size axis, and every applied variable, sparing foreign inline styles', () => {
  72. document.body.style.setProperty('--foreign', 'kept')
  73. const presenter = new ThemePresenter()
  74. presenter.apply(snapshot('dark', { '--dsw-alias-bg': '#111' }))
  75. const meta = themeColorMeta()
  76. presenter.dispose()
  77. expect(document.documentElement.style.colorScheme).toBe('')
  78. expect(document.body.hasAttribute(DARK_ATTRIBUTE)).toBe(false)
  79. expect(document.body.style.getPropertyValue('--dsw-alias-bg')).toBe('')
  80. expect(document.body.style.getPropertyValue('--dsh-content-font-size')).toBe('')
  81. expect(document.body.style.getPropertyValue('--foreign')).toBe('kept')
  82. expect(meta?.isConnected).toBe(false)
  83. })
  84. })