1
0

preferences.test.ts 6.8 KB

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