Skip to content

Commit

Permalink
♻️ refactor: refactor the auth action and common action
Browse files Browse the repository at this point in the history
  • Loading branch information
arvinxx committed May 2, 2024
1 parent 5280cb7 commit 6b476fe
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 139 deletions.
118 changes: 118 additions & 0 deletions 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({});
});
});
});
61 changes: 60 additions & 1 deletion src/store/user/slices/auth/action.ts
@@ -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'));
},

Check warning on line 37 in src/store/user/slices/auth/action.ts

View check run for this annotation

Codecov / codecov/patch

src/store/user/slices/auth/action.ts#L36-L37

Added lines #L36 - L37 were not covered by tests
login: async () => {
// TODO: 针对开启 next-auth 的场景,需要在这里调用登录方法
console.log(n('login'));
},

Check warning on line 41 in src/store/user/slices/auth/action.ts

View check run for this annotation

Codecov / codecov/patch

src/store/user/slices/auth/action.ts#L39-L41

Added lines #L39 - L41 were not covered by tests
logout: async () => {
// TODO: 针对开启 next-auth 的场景,需要在这里调用登录方法
console.log(n('logout'));
},

Check warning on line 45 in src/store/user/slices/auth/action.ts

View check run for this annotation

Codecov / codecov/patch

src/store/user/slices/auth/action.ts#L43-L45

Added lines #L43 - L45 were not covered by tests
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,
},
),
});
95 changes: 0 additions & 95 deletions src/store/user/slices/common/action.test.ts
Expand Up @@ -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 {
Expand All @@ -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());
Expand Down Expand Up @@ -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), {
Expand Down

0 comments on commit 6b476fe

Please sign in to comment.