boot-theme.client.spec.ts 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
  1. // @vitest-environment jsdom
  2. /** The theme bootstrap injection row and the resulting pre-plugin browser theme. */
  3. import { runInNewContext } from 'node:vm'
  4. import { afterEach, describe, expect, it, vi } from 'vitest'
  5. import { bootThemeInjection } from '../src/boot-theme.ts'
  6. import type { ThemePreference } from '../src/theme-settings.ts'
  7. const DARK_ATTRIBUTE = 'data-ds-dark-theme'
  8. function mockSystemDark(matches: boolean): void {
  9. vi.stubGlobal('matchMedia', vi.fn(() => ({ matches }) as MediaQueryList))
  10. }
  11. function executeBootstrap(preference?: ThemePreference): void {
  12. const row = bootThemeInjection(preference)
  13. if (row.kind !== 'script') throw new Error('theme bootstrap row is not a script')
  14. runInNewContext(row.text, { document, matchMedia: globalThis.matchMedia })
  15. }
  16. afterEach(() => {
  17. vi.restoreAllMocks()
  18. vi.unstubAllGlobals()
  19. document.documentElement.style.removeProperty('color-scheme')
  20. document.body.removeAttribute(DARK_ATTRIBUTE)
  21. })
  22. describe('theme bootstrap row', () => {
  23. it('is a body script row, so it runs before the shell mount', () => {
  24. mockSystemDark(false)
  25. const row = bootThemeInjection('dark')
  26. expect(row).toMatchObject({ kind: 'script', placement: 'body' })
  27. executeBootstrap('dark')
  28. expect(document.documentElement.style.colorScheme).toBe('dark')
  29. expect(document.body.hasAttribute(DARK_ATTRIBUTE)).toBe(true)
  30. })
  31. it('lets durable light override a dark OS and clears stale dark state', () => {
  32. document.body.setAttribute(DARK_ATTRIBUTE, '')
  33. mockSystemDark(true)
  34. executeBootstrap('light')
  35. expect(document.documentElement.style.colorScheme).toBe('light')
  36. expect(document.body.hasAttribute(DARK_ATTRIBUTE)).toBe(false)
  37. })
  38. it.each([
  39. [true, 'dark', true],
  40. [false, 'light', false],
  41. ] as const)('resolves system=%s to %s', (matches, colorScheme, dark) => {
  42. mockSystemDark(matches)
  43. executeBootstrap('system')
  44. expect(document.documentElement.style.colorScheme).toBe(colorScheme)
  45. expect(document.body.hasAttribute(DARK_ATTRIBUTE)).toBe(dark)
  46. })
  47. it('defaults to system and falls back to light when matchMedia is unavailable', () => {
  48. vi.stubGlobal('matchMedia', undefined)
  49. executeBootstrap()
  50. expect(document.documentElement.style.colorScheme).toBe('light')
  51. expect(document.body.hasAttribute(DARK_ATTRIBUTE)).toBe(false)
  52. })
  53. })