theme.spec.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. // mounted makes store to reinitiate on each test
  3. import { mounted } from '#tests/support/components/mounted.ts'
  4. import { mockAccount } from '#tests/support/mock-account.ts'
  5. import { nextTick } from 'vue'
  6. import { useAppTheme } from '../theme.ts'
  7. const haveUserPreference = (theme: string | undefined) => {
  8. mockAccount({
  9. preferences: {
  10. theme,
  11. },
  12. })
  13. }
  14. const haveDOMTheme = (theme: string | undefined) => {
  15. const root = document.querySelector(':root') as HTMLElement
  16. if (!theme) {
  17. root.removeAttribute('data-theme')
  18. } else {
  19. root.dataset.theme = theme
  20. }
  21. }
  22. const getDOMTheme = () => {
  23. const root = document.querySelector(':root') as HTMLElement
  24. return root.dataset.theme
  25. }
  26. const addEventListener = vi.fn()
  27. const haveMediaTheme = (theme: string) => {
  28. window.matchMedia = (rule) =>
  29. ({
  30. matches: !!(rule === '(prefers-color-scheme: dark)' && theme === 'dark'),
  31. addEventListener,
  32. }) as any
  33. }
  34. describe('theme is initiated correctly', () => {
  35. beforeEach(() => {
  36. haveDOMTheme(undefined)
  37. haveMediaTheme('light')
  38. })
  39. describe("when user doesn't have a preference", () => {
  40. beforeEach(() => {
  41. haveUserPreference(undefined)
  42. })
  43. it('when DOM is present, takes from DOM', () => {
  44. haveMediaTheme('light')
  45. haveDOMTheme('dark')
  46. const appTheme = mounted(() => useAppTheme())
  47. expect(appTheme.theme).toBe('dark')
  48. expect(getDOMTheme()).toBe('dark')
  49. })
  50. it('when DOM is not present, takes from media if light', () => {
  51. haveMediaTheme('light')
  52. haveDOMTheme(undefined)
  53. const appTheme = mounted(() => useAppTheme())
  54. expect(appTheme.theme).toBe('light')
  55. expect(getDOMTheme()).toBe('light')
  56. })
  57. it('when DOM is not present, takes from media if dark', () => {
  58. haveMediaTheme('dark')
  59. haveDOMTheme(undefined)
  60. const appTheme = mounted(() => useAppTheme())
  61. expect(appTheme.theme).toBe('dark')
  62. expect(getDOMTheme()).toBe('dark')
  63. })
  64. it('when media changes, update theme', async () => {
  65. haveMediaTheme('dark')
  66. const appTheme = mounted(() => useAppTheme())
  67. expect(appTheme.theme).toBe('dark')
  68. expect(getDOMTheme()).toBe('dark')
  69. haveMediaTheme('light')
  70. addEventListener.mock.calls[0][1]()
  71. expect(appTheme.theme).toBe('light')
  72. expect(getDOMTheme()).toBe('light')
  73. })
  74. })
  75. describe('when user has a preference', () => {
  76. it('when user has dark theme enabled, but DOM and media are light, takes from user preference', () => {
  77. haveUserPreference('dark')
  78. haveMediaTheme('light')
  79. haveDOMTheme('light')
  80. const appTheme = mounted(() => useAppTheme())
  81. expect(appTheme.theme).toBe('dark')
  82. expect(getDOMTheme()).toBe('dark')
  83. })
  84. it('when user has light theme enabled, but DOM and media are dark, takes from user preference', () => {
  85. haveUserPreference('light')
  86. haveMediaTheme('dark')
  87. haveDOMTheme('dark')
  88. const appTheme = mounted(() => useAppTheme())
  89. expect(appTheme.theme).toBe('light')
  90. expect(getDOMTheme()).toBe('light')
  91. })
  92. it('updates theme when account is changed', async () => {
  93. haveUserPreference('light')
  94. const appTheme = mounted(() => useAppTheme())
  95. expect(appTheme.theme).toBe('light')
  96. expect(getDOMTheme()).toBe('light')
  97. haveUserPreference('dark')
  98. await nextTick()
  99. expect(appTheme.theme).toBe('dark')
  100. expect(getDOMTheme()).toBe('dark')
  101. })
  102. it('can toggle them from the outside', () => {
  103. haveUserPreference('light')
  104. const appTheme = mounted(() => useAppTheme())
  105. expect(appTheme.theme).toBe('light')
  106. expect(getDOMTheme()).toBe('light')
  107. appTheme.toggleTheme(false)
  108. expect(appTheme.theme).toBe('dark')
  109. expect(getDOMTheme()).toBe('dark')
  110. })
  111. it("changes in media don't affect theme", async () => {
  112. haveUserPreference('dark')
  113. haveMediaTheme('dark')
  114. const appTheme = mounted(() => useAppTheme())
  115. expect(appTheme.theme).toBe('dark')
  116. expect(getDOMTheme()).toBe('dark')
  117. haveMediaTheme('light')
  118. addEventListener.mock.calls[0][1]()
  119. expect(appTheme.theme).toBe('dark')
  120. expect(getDOMTheme()).toBe('dark')
  121. })
  122. // TODO: when saveTheme implements API call
  123. it.todo('stored the new value in user preferences when theme is toggled')
  124. })
  125. })