preferences.test.ts 7.1 KB

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