preferences.test.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. import { beforeEach, describe, expect, it, vi } from 'vitest';
  2. import { defaultPreferences } from './config';
  3. import { PreferenceManager, isDarkTheme } from './preferences';
  4. describe('preferences', () => {
  5. let preferenceManager: PreferenceManager;
  6. vi.mock('@vben-core/cache', () => {
  7. return {
  8. StorageManager: vi.fn().mockImplementation(() => {
  9. return {
  10. getItem: vi.fn(),
  11. removeItem: vi.fn(),
  12. setItem: vi.fn(),
  13. };
  14. }),
  15. };
  16. });
  17. // 模拟 window.matchMedia 方法
  18. vi.stubGlobal(
  19. 'matchMedia',
  20. vi.fn().mockImplementation((query) => ({
  21. addEventListener: vi.fn(),
  22. addListener: vi.fn(), // Deprecated
  23. dispatchEvent: vi.fn(),
  24. matches: query === '(prefers-color-scheme: dark)',
  25. media: query,
  26. onchange: null,
  27. removeEventListener: vi.fn(),
  28. removeListener: vi.fn(), // Deprecated
  29. })),
  30. );
  31. beforeEach(() => {
  32. preferenceManager = new PreferenceManager();
  33. });
  34. it('initPreferences should initialize preferences with overrides and namespace', async () => {
  35. const overrides = { theme: { colorPrimary: 'hsl(211 91% 39%)' } };
  36. const namespace = 'testNamespace';
  37. await preferenceManager.initPreferences({ namespace, overrides });
  38. expect(preferenceManager.getPreferences().theme.colorPrimary).toBe(
  39. overrides.theme.colorPrimary,
  40. );
  41. });
  42. it('loads default preferences if no saved preferences found', () => {
  43. const preferences = preferenceManager.getPreferences();
  44. expect(preferences).toEqual(defaultPreferences);
  45. });
  46. it('initializes preferences with overrides', async () => {
  47. const overrides: any = {
  48. app: {
  49. locale: 'en-US',
  50. themeMode: 'light',
  51. },
  52. };
  53. await preferenceManager.initPreferences({
  54. namespace: 'testNamespace',
  55. overrides,
  56. });
  57. // 等待防抖动操作完成
  58. // await new Promise((resolve) => setTimeout(resolve, 300)); // 等待100毫秒
  59. const expected = {
  60. ...defaultPreferences,
  61. app: {
  62. ...defaultPreferences.app,
  63. ...overrides.app,
  64. },
  65. };
  66. expect(preferenceManager.getPreferences()).toEqual(expected);
  67. });
  68. it('updates theme mode correctly', () => {
  69. preferenceManager.updatePreferences({
  70. app: { themeMode: 'light' },
  71. });
  72. expect(preferenceManager.getPreferences().app.themeMode).toBe('light');
  73. });
  74. it('updates color modes correctly', () => {
  75. preferenceManager.updatePreferences({
  76. app: { colorGrayMode: true, colorWeakMode: true },
  77. });
  78. expect(preferenceManager.getPreferences().app.colorGrayMode).toBe(true);
  79. expect(preferenceManager.getPreferences().app.colorWeakMode).toBe(true);
  80. });
  81. it('resets preferences to default', () => {
  82. // 先更新一些偏好设置
  83. preferenceManager.updatePreferences({
  84. app: { themeMode: 'light' },
  85. });
  86. // 然后重置偏好设置
  87. preferenceManager.resetPreferences();
  88. expect(preferenceManager.getPreferences()).toEqual(defaultPreferences);
  89. });
  90. it('updates isMobile correctly', () => {
  91. // 模拟移动端状态
  92. vi.stubGlobal(
  93. 'matchMedia',
  94. vi.fn().mockImplementation((query) => ({
  95. addEventListener: vi.fn(),
  96. addListener: vi.fn(),
  97. dispatchEvent: vi.fn(),
  98. matches: query === '(max-width: 768px)',
  99. media: query,
  100. onchange: null,
  101. removeEventListener: vi.fn(),
  102. removeListener: vi.fn(),
  103. })),
  104. );
  105. preferenceManager.updatePreferences({
  106. app: { isMobile: true },
  107. });
  108. expect(preferenceManager.getPreferences().app.isMobile).toBe(true);
  109. });
  110. it('updates the locale preference correctly', () => {
  111. preferenceManager.updatePreferences({
  112. app: { locale: 'en-US' },
  113. });
  114. expect(preferenceManager.getPreferences().app.locale).toBe('en-US');
  115. });
  116. it('updates the sidebar width correctly', () => {
  117. preferenceManager.updatePreferences({
  118. sidebar: { width: 200 },
  119. });
  120. expect(preferenceManager.getPreferences().sidebar.width).toBe(200);
  121. });
  122. it('updates the sidebar collapse state correctly', () => {
  123. preferenceManager.updatePreferences({
  124. sidebar: { collapse: true },
  125. });
  126. expect(preferenceManager.getPreferences().sidebar.collapse).toBe(true);
  127. });
  128. it('updates the navigation style type correctly', () => {
  129. preferenceManager.updatePreferences({
  130. navigation: { styleType: 'flat' },
  131. } as any);
  132. expect(preferenceManager.getPreferences().navigation.styleType).toBe(
  133. 'flat',
  134. );
  135. });
  136. it('resets preferences to default correctly', () => {
  137. // 先更新一些偏好设置
  138. preferenceManager.updatePreferences({
  139. app: { locale: 'en-US', themeMode: 'light' },
  140. sidebar: { collapse: true, width: 200 },
  141. });
  142. // 然后重置偏好设置
  143. preferenceManager.resetPreferences();
  144. expect(preferenceManager.getPreferences()).toEqual(defaultPreferences);
  145. });
  146. it('does not update undefined preferences', () => {
  147. const originalPreferences = preferenceManager.getPreferences();
  148. preferenceManager.updatePreferences({
  149. app: { nonexistentField: 'value' },
  150. } as any);
  151. expect(preferenceManager.getPreferences()).toEqual(originalPreferences);
  152. });
  153. it('reverts to default when a preference field is deleted', () => {
  154. preferenceManager.updatePreferences({
  155. app: { locale: 'en-US' },
  156. });
  157. preferenceManager.updatePreferences({
  158. app: { locale: undefined },
  159. });
  160. expect(preferenceManager.getPreferences().app.locale).toBe('en-US');
  161. });
  162. it('ignores updates with invalid preference value types', () => {
  163. const originalPreferences = preferenceManager.getPreferences();
  164. preferenceManager.updatePreferences({
  165. app: { isMobile: 'true' as unknown as boolean }, // 错误类型
  166. });
  167. expect(preferenceManager.getPreferences()).toEqual(originalPreferences);
  168. });
  169. it('merges nested preference objects correctly', () => {
  170. preferenceManager.updatePreferences({
  171. app: { name: 'New App Name' },
  172. });
  173. const expected = {
  174. ...defaultPreferences,
  175. app: {
  176. ...defaultPreferences.app,
  177. name: 'New App Name',
  178. },
  179. };
  180. expect(preferenceManager.getPreferences()).toEqual(expected);
  181. });
  182. it('applies updates immediately after initialization', async () => {
  183. const overrides: any = {
  184. app: {
  185. locale: 'en-US',
  186. },
  187. };
  188. await preferenceManager.initPreferences(overrides);
  189. preferenceManager.updatePreferences({
  190. app: { themeMode: 'light' },
  191. });
  192. expect(preferenceManager.getPreferences().app.themeMode).toBe('light');
  193. });
  194. });
  195. describe('isDarkTheme', () => {
  196. it('should return true for dark theme', () => {
  197. expect(isDarkTheme('dark')).toBe(true);
  198. });
  199. it('should return false for light theme', () => {
  200. expect(isDarkTheme('light')).toBe(false);
  201. });
  202. it('should return system preference for auto theme', () => {
  203. vi.spyOn(window, 'matchMedia').mockImplementation((query) => ({
  204. addEventListener: vi.fn(),
  205. addListener: vi.fn(), // Deprecated
  206. dispatchEvent: vi.fn(),
  207. matches: query === '(prefers-color-scheme: dark)',
  208. media: query,
  209. onchange: null,
  210. removeEventListener: vi.fn(),
  211. removeListener: vi.fn(), // Deprecated
  212. }));
  213. expect(isDarkTheme('auto')).toBe(true);
  214. expect(window.matchMedia).toHaveBeenCalledWith(
  215. '(prefers-color-scheme: dark)',
  216. );
  217. });
  218. });