From 6b476fe820c988384da16f97be22231308e49444 Mon Sep 17 00:00:00 2001 From: arvinxx Date: Thu, 2 May 2024 23:51:36 +0800 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20refactor=20the?= =?UTF-8?q?=20auth=20action=20and=20common=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/store/user/slices/auth/action.test.ts | 118 ++++++++++++++++++++ src/store/user/slices/auth/action.ts | 61 +++++++++- src/store/user/slices/common/action.test.ts | 95 ---------------- src/store/user/slices/common/action.ts | 46 +------- 4 files changed, 181 insertions(+), 139 deletions(-) create mode 100644 src/store/user/slices/auth/action.test.ts diff --git a/src/store/user/slices/auth/action.test.ts b/src/store/user/slices/auth/action.test.ts new file mode 100644 index 000000000000..6e0add0cb3d5 --- /dev/null +++ b/src/store/user/slices/auth/action.test.ts @@ -0,0 +1,118 @@ +import { act, renderHook, waitFor } from '@testing-library/react'; +import { mutate } from 'swr'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { withSWR } from '~test-utils'; + +import { userService } from '@/services/user'; +import { useUserStore } from '@/store/user'; +import { switchLang } from '@/utils/client/switchLang'; + +vi.mock('zustand/traditional'); + +vi.mock('@/utils/client/switchLang', () => ({ + switchLang: vi.fn(), +})); + +vi.mock('swr', async (importOriginal) => { + const modules = await importOriginal(); + return { + ...(modules as any), + mutate: vi.fn(), + }; +}); + +afterEach(() => { + vi.restoreAllMocks(); +}); + +describe('createAuthSlice', () => { + describe('refreshUserConfig', () => { + it('should refresh user config', async () => { + const { result } = renderHook(() => useUserStore()); + + await act(async () => { + await result.current.refreshUserConfig(); + }); + + expect(mutate).toHaveBeenCalledWith(['fetchUserConfig', true]); + }); + }); + + describe('useFetchUserConfig', () => { + it('should not fetch user config if initServer is false', async () => { + const mockUserConfig: any = undefined; // 模拟未初始化服务器的情况 + vi.spyOn(userService, 'getUserConfig').mockResolvedValueOnce(mockUserConfig); + + const { result } = renderHook(() => useUserStore().useFetchUserConfig(false), { + wrapper: withSWR, + }); + + // 因为 initServer 为 false,所以不会触发 getUserConfig 的调用 + expect(userService.getUserConfig).not.toHaveBeenCalled(); + // 确保状态未改变 + expect(result.current.data).toBeUndefined(); + }); + + it('should fetch user config correctly when initServer is true', async () => { + const mockUserConfig: any = { + avatar: 'new-avatar-url', + settings: { + language: 'en', + }, + }; + vi.spyOn(userService, 'getUserConfig').mockResolvedValueOnce(mockUserConfig); + + const { result } = renderHook(() => useUserStore().useFetchUserConfig(true), { + wrapper: withSWR, + }); + + // 等待 SWR 完成数据获取 + await waitFor(() => expect(result.current.data).toEqual(mockUserConfig)); + + // 验证状态是否正确更新 + expect(useUserStore.getState().avatar).toBe(mockUserConfig.avatar); + expect(useUserStore.getState().settings).toEqual(mockUserConfig.settings); + + // 验证是否正确处理了语言设置 + expect(switchLang).not.toHaveBeenCalledWith('auto'); + }); + it('should call switch language when language is auto', async () => { + const mockUserConfig: any = { + avatar: 'new-avatar-url', + settings: { + language: 'auto', + }, + }; + vi.spyOn(userService, 'getUserConfig').mockResolvedValueOnce(mockUserConfig); + + const { result } = renderHook(() => useUserStore().useFetchUserConfig(true), { + wrapper: withSWR, + }); + + // 等待 SWR 完成数据获取 + await waitFor(() => expect(result.current.data).toEqual(mockUserConfig)); + + // 验证状态是否正确更新 + expect(useUserStore.getState().avatar).toBe(mockUserConfig.avatar); + expect(useUserStore.getState().settings).toEqual(mockUserConfig.settings); + + // 验证是否正确处理了语言设置 + expect(switchLang).toHaveBeenCalledWith('auto'); + }); + + it('should handle the case when user config is null', async () => { + vi.spyOn(userService, 'getUserConfig').mockResolvedValueOnce(null as any); + + const { result } = renderHook(() => useUserStore().useFetchUserConfig(true), { + wrapper: withSWR, + }); + + // 等待 SWR 完成数据获取 + await waitFor(() => expect(result.current.data).toBeNull()); + + // 验证状态未被错误更新 + expect(useUserStore.getState().avatar).toBeUndefined(); + expect(useUserStore.getState().settings).toEqual({}); + }); + }); +}); diff --git a/src/store/user/slices/auth/action.ts b/src/store/user/slices/auth/action.ts index 4404f3019a92..ebd458d44263 100644 --- a/src/store/user/slices/auth/action.ts +++ b/src/store/user/slices/auth/action.ts @@ -1,13 +1,29 @@ +import useSWR, { SWRResponse, mutate } from 'swr'; import { StateCreator } from 'zustand/vanilla'; +import { UserConfig, userService } from '@/services/user'; +import { switchLang } from '@/utils/client/switchLang'; import { setNamespace } from '@/utils/storeDebug'; import { UserStore } from '../../store'; +import { settingsSelectors } from '../settings/selectors'; const n = setNamespace('auth'); +const USER_CONFIG_FETCH_KEY = 'fetchUserConfig'; export interface UserAuthAction { getUserConfig: () => void; + /** + * universal login method + */ + login: () => Promise; + /** + * universal logout method + */ + logout: () => Promise; + refreshUserConfig: () => Promise; + + useFetchUserConfig: (initServer: boolean) => SWRResponse; } export const createAuthSlice: StateCreator< @@ -15,8 +31,51 @@ export const createAuthSlice: StateCreator< [['zustand/devtools', never]], [], UserAuthAction -> = () => ({ +> = (set, get) => ({ getUserConfig: () => { console.log(n('userconfig')); }, + login: async () => { + // TODO: 针对开启 next-auth 的场景,需要在这里调用登录方法 + console.log(n('login')); + }, + logout: async () => { + // TODO: 针对开启 next-auth 的场景,需要在这里调用登录方法 + console.log(n('logout')); + }, + refreshUserConfig: async () => { + await mutate([USER_CONFIG_FETCH_KEY, true]); + + // when get the user config ,refresh the model provider list to the latest + get().refreshModelProviderList(); + }, + + useFetchUserConfig: (initServer) => + useSWR( + [USER_CONFIG_FETCH_KEY, initServer], + async () => { + if (!initServer) return; + return userService.getUserConfig(); + }, + { + onSuccess: (data) => { + if (!data) return; + + set( + { avatar: data.avatar, settings: data.settings, userId: data.uuid }, + false, + n('fetchUserConfig', data), + ); + + // when get the user config ,refresh the model provider list to the latest + get().refreshDefaultModelProviderList({ trigger: 'fetchUserConfig' }); + + const { language } = settingsSelectors.currentSettings(get()); + if (language === 'auto') { + switchLang('auto'); + } + }, + revalidateOnFocus: false, + }, + ), }); diff --git a/src/store/user/slices/common/action.test.ts b/src/store/user/slices/common/action.test.ts index 46ac7f5e20fb..d8f3463f4a1f 100644 --- a/src/store/user/slices/common/action.test.ts +++ b/src/store/user/slices/common/action.test.ts @@ -9,14 +9,9 @@ import { userService } from '@/services/user'; import { useUserStore } from '@/store/user'; import { preferenceSelectors } from '@/store/user/selectors'; import { GlobalServerConfig } from '@/types/serverConfig'; -import { switchLang } from '@/utils/client/switchLang'; vi.mock('zustand/traditional'); -vi.mock('@/utils/client/switchLang', () => ({ - switchLang: vi.fn(), -})); - vi.mock('swr', async (importOriginal) => { const modules = await importOriginal(); return { @@ -30,18 +25,6 @@ afterEach(() => { }); describe('createCommonSlice', () => { - describe('refreshUserConfig', () => { - it('should refresh user config', async () => { - const { result } = renderHook(() => useUserStore()); - - await act(async () => { - await result.current.refreshUserConfig(); - }); - - expect(mutate).toHaveBeenCalledWith(['fetchUserConfig', true]); - }); - }); - describe('updateAvatar', () => { it('should update avatar', async () => { const { result } = renderHook(() => useUserStore()); @@ -74,84 +57,6 @@ describe('createCommonSlice', () => { }); }); - describe('useFetchUserConfig', () => { - it('should not fetch user config if initServer is false', async () => { - const mockUserConfig: any = undefined; // 模拟未初始化服务器的情况 - vi.spyOn(userService, 'getUserConfig').mockResolvedValueOnce(mockUserConfig); - - const { result } = renderHook(() => useUserStore().useFetchUserConfig(false), { - wrapper: withSWR, - }); - - // 因为 initServer 为 false,所以不会触发 getUserConfig 的调用 - expect(userService.getUserConfig).not.toHaveBeenCalled(); - // 确保状态未改变 - expect(result.current.data).toBeUndefined(); - }); - - it('should fetch user config correctly when initServer is true', async () => { - const mockUserConfig: any = { - avatar: 'new-avatar-url', - settings: { - language: 'en', - }, - }; - vi.spyOn(userService, 'getUserConfig').mockResolvedValueOnce(mockUserConfig); - - const { result } = renderHook(() => useUserStore().useFetchUserConfig(true), { - wrapper: withSWR, - }); - - // 等待 SWR 完成数据获取 - await waitFor(() => expect(result.current.data).toEqual(mockUserConfig)); - - // 验证状态是否正确更新 - expect(useUserStore.getState().avatar).toBe(mockUserConfig.avatar); - expect(useUserStore.getState().settings).toEqual(mockUserConfig.settings); - - // 验证是否正确处理了语言设置 - expect(switchLang).not.toHaveBeenCalledWith('auto'); - }); - it('should call switch language when language is auto', async () => { - const mockUserConfig: any = { - avatar: 'new-avatar-url', - settings: { - language: 'auto', - }, - }; - vi.spyOn(userService, 'getUserConfig').mockResolvedValueOnce(mockUserConfig); - - const { result } = renderHook(() => useUserStore().useFetchUserConfig(true), { - wrapper: withSWR, - }); - - // 等待 SWR 完成数据获取 - await waitFor(() => expect(result.current.data).toEqual(mockUserConfig)); - - // 验证状态是否正确更新 - expect(useUserStore.getState().avatar).toBe(mockUserConfig.avatar); - expect(useUserStore.getState().settings).toEqual(mockUserConfig.settings); - - // 验证是否正确处理了语言设置 - expect(switchLang).toHaveBeenCalledWith('auto'); - }); - - it('should handle the case when user config is null', async () => { - vi.spyOn(userService, 'getUserConfig').mockResolvedValueOnce(null as any); - - const { result } = renderHook(() => useUserStore().useFetchUserConfig(true), { - wrapper: withSWR, - }); - - // 等待 SWR 完成数据获取 - await waitFor(() => expect(result.current.data).toBeNull()); - - // 验证状态未被错误更新 - expect(useUserStore.getState().avatar).toBeUndefined(); - expect(useUserStore.getState().settings).toEqual({}); - }); - }); - describe('useCheckTrace', () => { it('should return false when shouldFetch is false', async () => { const { result } = renderHook(() => useUserStore().useCheckTrace(false), { diff --git a/src/store/user/slices/common/action.ts b/src/store/user/slices/common/action.ts index 30d4adbed3a1..d281d6fc5c93 100644 --- a/src/store/user/slices/common/action.ts +++ b/src/store/user/slices/common/action.ts @@ -1,19 +1,17 @@ -import useSWR, { SWRResponse, mutate } from 'swr'; +import useSWR, { SWRResponse } from 'swr'; import { DeepPartial } from 'utility-types'; import type { StateCreator } from 'zustand/vanilla'; import { globalService } from '@/services/global'; import { messageService } from '@/services/message'; -import { UserConfig, userService } from '@/services/user'; +import { userService } from '@/services/user'; import type { UserStore } from '@/store/user'; import type { GlobalServerConfig } from '@/types/serverConfig'; import type { GlobalSettings } from '@/types/settings'; -import { switchLang } from '@/utils/client/switchLang'; import { merge } from '@/utils/merge'; import { setNamespace } from '@/utils/storeDebug'; import { preferenceSelectors } from '../preference/selectors'; -import { settingsSelectors } from '../settings/selectors'; const n = setNamespace('common'); @@ -21,32 +19,22 @@ const n = setNamespace('common'); * 设置操作 */ export interface CommonAction { - refreshUserConfig: () => Promise; updateAvatar: (avatar: string) => Promise; useCheckTrace: (shouldFetch: boolean) => SWRResponse; useFetchServerConfig: () => SWRResponse; - useFetchUserConfig: (initServer: boolean) => SWRResponse; } -const USER_CONFIG_FETCH_KEY = 'fetchUserConfig'; - export const createCommonSlice: StateCreator< UserStore, [['zustand/devtools', never]], [], CommonAction > = (set, get) => ({ - refreshUserConfig: async () => { - await mutate([USER_CONFIG_FETCH_KEY, true]); - - // when get the user config ,refresh the model provider list to the latest - get().refreshModelProviderList(); - }, - updateAvatar: async (avatar) => { await userService.updateAvatar(avatar); await get().refreshUserConfig(); }, + useCheckTrace: (shouldFetch) => useSWR( ['checkTrace', shouldFetch], @@ -83,32 +71,4 @@ export const createCommonSlice: StateCreator< }, revalidateOnFocus: false, }), - useFetchUserConfig: (initServer) => - useSWR( - [USER_CONFIG_FETCH_KEY, initServer], - async () => { - if (!initServer) return; - return userService.getUserConfig(); - }, - { - onSuccess: (data) => { - if (!data) return; - - set( - { avatar: data.avatar, settings: data.settings, userId: data.uuid }, - false, - n('fetchUserConfig', data), - ); - - // when get the user config ,refresh the model provider list to the latest - get().refreshDefaultModelProviderList({ trigger: 'fetchUserConfig' }); - - const { language } = settingsSelectors.currentSettings(get()); - if (language === 'auto') { - switchLang('auto'); - } - }, - revalidateOnFocus: false, - }, - ), });