-
-
Notifications
You must be signed in to change notification settings - Fork 11.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
♻️ refactor: refactor the auth action and common action
- Loading branch information
Showing
4 changed files
with
181 additions
and
139 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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({}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,81 @@ | ||
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<void>; | ||
/** | ||
* universal logout method | ||
*/ | ||
logout: () => Promise<void>; | ||
refreshUserConfig: () => Promise<void>; | ||
|
||
useFetchUserConfig: (initServer: boolean) => SWRResponse<UserConfig | undefined>; | ||
} | ||
|
||
export const createAuthSlice: StateCreator< | ||
UserStore, | ||
[['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<UserConfig | undefined>( | ||
[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, | ||
}, | ||
), | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.