theme.spec.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  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 { mockUserCurrent } from '#tests/support/mock-userCurrent.ts'
  5. import { EnumAppearanceTheme } from '#shared/graphql/types.ts'
  6. import { mockUserCurrentAppearanceMutation } from '#desktop/pages/personal-setting/graphql/mutations/userCurrentAppearance.mocks.ts'
  7. import { useThemeStore } from '#desktop/stores/theme.ts'
  8. const mockUserTheme = (theme: string | undefined) => {
  9. mockUserCurrent({
  10. preferences: {
  11. theme,
  12. },
  13. })
  14. }
  15. const haveDOMTheme = (theme: string | undefined) => {
  16. const root = document.querySelector(':root') as HTMLElement
  17. if (!theme) {
  18. root.removeAttribute('data-theme')
  19. } else {
  20. root.dataset.theme = theme
  21. }
  22. }
  23. const getDOMTheme = () => {
  24. const root = document.querySelector(':root') as HTMLElement
  25. return root.dataset.theme
  26. }
  27. const addEventListener = vi.fn()
  28. const haveMediaTheme = (theme: string) => {
  29. window.matchMedia = (rule) =>
  30. ({
  31. matches: rule === '(prefers-color-scheme: dark)' && theme === 'dark',
  32. addEventListener,
  33. }) as any
  34. }
  35. describe('useThemeStore', () => {
  36. beforeEach(() => {
  37. setActivePinia(createPinia())
  38. mockUserCurrent({
  39. lastname: 'Doe',
  40. firstname: 'John',
  41. preferences: {},
  42. })
  43. const { syncTheme } = useThemeStore()
  44. syncTheme()
  45. haveDOMTheme(undefined)
  46. haveMediaTheme('light')
  47. })
  48. it('should fallback to auto when no theme present', () => {
  49. const { currentTheme } = useThemeStore()
  50. expect(currentTheme).toBe(EnumAppearanceTheme.Auto)
  51. })
  52. it('changes app theme', async () => {
  53. const mockerUserCurrentAppearanceUpdate = mockUserCurrentAppearanceMutation(
  54. {
  55. userCurrentAppearance: {
  56. success: true,
  57. },
  58. },
  59. )
  60. const themeStore = useThemeStore()
  61. const { updateTheme } = themeStore
  62. const { currentTheme, savingTheme } = storeToRefs(themeStore)
  63. await updateTheme(EnumAppearanceTheme.Dark)
  64. expect(currentTheme.value).toBe(EnumAppearanceTheme.Dark)
  65. const mockCalls = await mockerUserCurrentAppearanceUpdate.waitForCalls()
  66. expect(mockCalls).toHaveLength(1)
  67. await flushPromises()
  68. expect(savingTheme.value).toBe(false)
  69. expect(currentTheme.value).toBe(EnumAppearanceTheme.Dark)
  70. })
  71. it('should change theme value back to old value when update fails', async () => {
  72. const mockerUserCurrentAppearanceUpdate = mockUserCurrentAppearanceMutation(
  73. {
  74. userCurrentAppearance: {
  75. errors: [
  76. {
  77. message: 'Failed to update.',
  78. },
  79. ],
  80. },
  81. },
  82. )
  83. const themStore = useThemeStore()
  84. const { updateTheme } = themStore
  85. const { currentTheme, savingTheme } = storeToRefs(themStore)
  86. await updateTheme(EnumAppearanceTheme.Dark)
  87. expect(currentTheme.value).toBe(EnumAppearanceTheme.Auto)
  88. const mockCalls = await mockerUserCurrentAppearanceUpdate.waitForCalls()
  89. expect(mockCalls).toHaveLength(1)
  90. await flushPromises()
  91. expect(savingTheme.value).toBe(false)
  92. expect(currentTheme.value).toBe(EnumAppearanceTheme.Auto)
  93. })
  94. // describe('when user has a preference', () => {
  95. it('when user has no theme preference, takes from media', () => {
  96. const { syncTheme } = useThemeStore()
  97. syncTheme()
  98. const { currentTheme } = useThemeStore()
  99. expect(currentTheme).toBe('auto')
  100. })
  101. it("changes in media don't affect theme", async () => {
  102. mockUserTheme(EnumAppearanceTheme.Dark)
  103. haveMediaTheme(EnumAppearanceTheme.Dark)
  104. const { syncTheme, currentTheme } = useThemeStore()
  105. syncTheme()
  106. expect(currentTheme).toBe(EnumAppearanceTheme.Dark)
  107. expect(getDOMTheme()).toBe(EnumAppearanceTheme.Dark)
  108. haveMediaTheme(EnumAppearanceTheme.Light)
  109. addEventListener.mock.calls[0][1]()
  110. expect(currentTheme).toBe(EnumAppearanceTheme.Dark)
  111. expect(getDOMTheme()).toBe(EnumAppearanceTheme.Dark)
  112. })
  113. })