theme.spec.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. import { flushPromises } from '@vue/test-utils'
  3. import { createPinia, setActivePinia, storeToRefs } from 'pinia'
  4. import { mockMediaTheme } from '#tests/support/mock-mediaTheme.ts'
  5. import { mockUserCurrent } from '#tests/support/mock-userCurrent.ts'
  6. import { EnumAppearanceTheme } from '#shared/graphql/types.ts'
  7. import { mockUserCurrentAppearanceMutation } from '#desktop/pages/personal-setting/graphql/mutations/userCurrentAppearance.mocks.ts'
  8. import { useThemeStore } from '#desktop/stores/theme.ts'
  9. // :TODO mock media theme does not update preferredColorScheme preferable without module mocking
  10. // vi.mock('@vueuse/core', async () => {
  11. // const mod =
  12. // await vi.importActual<typeof import('@vueuse/core')>('@vueuse/core')
  13. //
  14. // return {
  15. // ...mod,
  16. // usePreferredColorScheme: () => currentTheme,
  17. // }
  18. // })
  19. const mockUserTheme = (theme: string | undefined) => {
  20. mockUserCurrent({
  21. preferences: {
  22. theme,
  23. },
  24. })
  25. }
  26. const getRoot = () => document.querySelector(':root') as HTMLElement
  27. const haveDOMTheme = (theme: string | undefined) => {
  28. const root = getRoot()
  29. if (!theme) {
  30. root.removeAttribute('data-theme')
  31. root.style.colorScheme = 'normal'
  32. } else {
  33. root.dataset.theme = theme
  34. root.style.colorScheme = theme
  35. }
  36. }
  37. const getDOMTheme = () => getRoot().dataset.theme
  38. const getDOMColorScheme = () => getRoot().style.colorScheme
  39. describe('useThemeStore', () => {
  40. beforeEach(() => {
  41. setActivePinia(createPinia())
  42. mockUserCurrent({
  43. lastname: 'Doe',
  44. firstname: 'John',
  45. preferences: {},
  46. })
  47. const { syncTheme } = useThemeStore()
  48. syncTheme()
  49. haveDOMTheme(undefined)
  50. mockMediaTheme(EnumAppearanceTheme.Light)
  51. })
  52. it('should fallback to auto when no theme present', () => {
  53. const { currentTheme } = useThemeStore()
  54. expect(currentTheme).toBe(EnumAppearanceTheme.Auto)
  55. })
  56. it('changes app theme', async () => {
  57. const mockerUserCurrentAppearanceUpdate = mockUserCurrentAppearanceMutation(
  58. {
  59. userCurrentAppearance: {
  60. success: true,
  61. },
  62. },
  63. )
  64. const themeStore = useThemeStore()
  65. const { updateTheme } = themeStore
  66. const { currentTheme, savingTheme } = storeToRefs(themeStore)
  67. await updateTheme(EnumAppearanceTheme.Dark)
  68. expect(currentTheme.value).toBe(EnumAppearanceTheme.Dark)
  69. const mockCalls = await mockerUserCurrentAppearanceUpdate.waitForCalls()
  70. expect(mockCalls).toHaveLength(1)
  71. await flushPromises()
  72. expect(savingTheme.value).toBe(false)
  73. expect(currentTheme.value).toBe(EnumAppearanceTheme.Dark)
  74. })
  75. it('should change theme value back to old value when update fails', async () => {
  76. const mockerUserCurrentAppearanceUpdate = mockUserCurrentAppearanceMutation(
  77. {
  78. userCurrentAppearance: {
  79. errors: [
  80. {
  81. message: 'Failed to update.',
  82. },
  83. ],
  84. },
  85. },
  86. )
  87. const themeStore = useThemeStore()
  88. const { updateTheme } = themeStore
  89. const { currentTheme, savingTheme } = storeToRefs(themeStore)
  90. await updateTheme(EnumAppearanceTheme.Dark)
  91. expect(currentTheme.value).toBe(EnumAppearanceTheme.Auto)
  92. const mockCalls = await mockerUserCurrentAppearanceUpdate.waitForCalls()
  93. expect(mockCalls).toHaveLength(1)
  94. await flushPromises()
  95. expect(savingTheme.value).toBe(false)
  96. expect(currentTheme.value).toBe(EnumAppearanceTheme.Auto)
  97. })
  98. it('when user has no theme preference, takes from media', () => {
  99. const { syncTheme } = useThemeStore()
  100. syncTheme()
  101. const { currentTheme } = useThemeStore()
  102. expect(currentTheme).toBe('auto')
  103. })
  104. it("changes in media don't affect theme", async () => {
  105. mockUserTheme(EnumAppearanceTheme.Dark)
  106. mockMediaTheme(EnumAppearanceTheme.Dark)
  107. const { syncTheme, currentTheme } = useThemeStore()
  108. syncTheme()
  109. expect(currentTheme).toBe(EnumAppearanceTheme.Dark)
  110. expect(getDOMTheme()).toBe(EnumAppearanceTheme.Dark)
  111. expect(getDOMColorScheme()).toBe(EnumAppearanceTheme.Dark)
  112. mockMediaTheme(EnumAppearanceTheme.Light)
  113. // addEventListener.mock?.calls?.[0][1]()
  114. expect(currentTheme).toBe(EnumAppearanceTheme.Dark)
  115. expect(getDOMTheme()).toBe(EnumAppearanceTheme.Dark)
  116. expect(getDOMColorScheme()).toBe(EnumAppearanceTheme.Dark)
  117. })
  118. describe('isDarkMode', () => {
  119. it.todo('returns true when user prefers dark media theme', async () => {
  120. // :TODO mock media theme does not update preferredColorScheme
  121. mockMediaTheme(EnumAppearanceTheme.Dark)
  122. const { isDarkMode } = useThemeStore()
  123. expect(isDarkMode).toBe(true)
  124. })
  125. it('returns false when user prefers light media theme', async () => {
  126. mockMediaTheme(EnumAppearanceTheme.Light)
  127. const { isDarkMode } = useThemeStore()
  128. expect(isDarkMode).toBe(false)
  129. })
  130. it('returns true when user has dark theme active', async () => {
  131. mockUserTheme(EnumAppearanceTheme.Dark) // has precedence
  132. mockMediaTheme(EnumAppearanceTheme.Light)
  133. const { isDarkMode } = useThemeStore()
  134. expect(isDarkMode).toBe(true)
  135. })
  136. it('returns false when user prefers light media theme', async () => {
  137. mockUserTheme(EnumAppearanceTheme.Light) // has precedence
  138. mockMediaTheme(EnumAppearanceTheme.Dark)
  139. const { isDarkMode } = useThemeStore()
  140. expect(isDarkMode).toBe(false)
  141. })
  142. })
  143. })