feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { vi, describe, it, expect, beforeEach, type Mock } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import {
handleAPIErrorEvent,
removeAPIErrorEvent,
APIErrorEvent,
} from '@coze-arch/bot-api';
import { useCheckLoginBase } from '../factory';
import { useUserStore } from '../../store/user';
vi.mock('@coze-arch/bot-api', () => ({
handleAPIErrorEvent: vi.fn(),
removeAPIErrorEvent: vi.fn(),
APIErrorEvent: { UNAUTHORIZED: 'UNAUTHORIZED' },
}));
const mockCheckLoginImpl = vi
.fn()
.mockResolvedValue({ userInfo: null, hasError: false });
const mockGoLogin = vi.fn();
const mockReset = vi.fn();
const originalUserStore = {
...useUserStore.getState(),
reset: mockReset,
};
beforeEach(() => {
useUserStore.setState(originalUserStore);
vi.clearAllMocks();
mockGoLogin.mockReset();
});
describe('useCheckLoginBase', () => {
it('should call checkLoginBase when isSettled is false', () => {
useUserStore.setState({ isSettled: false });
renderHook(() => useCheckLoginBase(true, mockCheckLoginImpl, mockGoLogin));
expect(mockCheckLoginImpl).toHaveBeenCalledTimes(1);
});
it('should call checkLoginBase when isSettled is false and require auth is false', () => {
useUserStore.setState({ isSettled: false });
renderHook(() => useCheckLoginBase(false, mockCheckLoginImpl, mockGoLogin));
expect(mockCheckLoginImpl).toHaveBeenCalledTimes(1);
});
it('should redirect to login when needLogin is true and user is not logged in', () => {
useUserStore.setState({ isSettled: true, userInfo: null });
renderHook(() => useCheckLoginBase(true, mockCheckLoginImpl, mockGoLogin));
expect(mockGoLogin).toHaveBeenCalled();
});
it('should not redirect when user is logged in', () => {
useUserStore.setState({
isSettled: true,
userInfo: { user_id_str: '123' },
});
renderHook(() => useCheckLoginBase(true, mockCheckLoginImpl, mockGoLogin));
expect(mockGoLogin).not.toHaveBeenCalled();
});
it('should handle UNAUTHORIZED event and redirect', () => {
const { unmount } = renderHook(() =>
useCheckLoginBase(true, mockCheckLoginImpl, mockGoLogin),
);
const handleUnauthorized = (handleAPIErrorEvent as Mock).mock.calls[0][1];
act(() => handleUnauthorized());
expect(mockReset).toHaveBeenCalled();
expect(mockGoLogin).toHaveBeenCalled();
unmount();
expect(removeAPIErrorEvent).toHaveBeenCalledWith(
APIErrorEvent.UNAUTHORIZED,
handleUnauthorized,
);
});
it('should not redirect on UNAUTHORIZED if needLogin is false', () => {
renderHook(() => useCheckLoginBase(false, mockCheckLoginImpl, mockGoLogin));
const handleUnauthorized = (handleAPIErrorEvent as Mock).mock.calls[0][1];
act(() => handleUnauthorized());
expect(mockGoLogin).not.toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,194 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
import { useDocumentVisibility } from 'ahooks';
import { renderHook, act } from '@testing-library/react';
import { useLoginStatus, useAlterOnLogout } from '../index';
import { useUserStore } from '../../store/user';
// Mock ahooks
vi.mock('ahooks', async importOriginal => {
const original = await importOriginal();
return {
...original,
useDocumentVisibility: vi.fn(),
};
});
// Mock useUserStore
vi.mock('../../store/user', () => ({
useUserStore: vi.fn(),
}));
const UID_KEY = 'coze_current_uid';
const localStorageMock = (() => {
let store: Record<string, string> = {};
return {
getItem: (key: string) => store[key] || null,
setItem: (key: string, value: string) => {
store[key] = value.toString();
},
removeItem: (key: string) => {
delete store[key];
},
clear: () => {
store = {};
},
length: 0,
key: (index: number) => null,
};
})();
Object.defineProperty(window, 'localStorage', {
value: localStorageMock,
});
Object.defineProperty(window, 'addEventListener', {
value: vi.fn(),
});
Object.defineProperty(window, 'removeEventListener', {
value: vi.fn(),
});
describe('Account Hooks from index.ts', () => {
beforeEach(() => {
vi.clearAllMocks();
localStorageMock.clear();
// Default mock for useUserStore to return a function that can be called with a selector
(useUserStore as unknown as Mock).mockImplementation(selector =>
selector(mockUserState),
);
});
let mockUserState: any;
describe('useLoginStatus', () => {
it('should return "settling" if store is not settled', () => {
mockUserState = { isSettled: false, userInfo: null };
const { result } = renderHook(() => useLoginStatus());
expect(result.current).toBe('settling');
});
it('should return "not_login" if store is settled and no userInfo', () => {
mockUserState = { isSettled: true, userInfo: null };
const { result } = renderHook(() => useLoginStatus());
expect(result.current).toBe('not_login');
});
it('should return "not_login" if store is settled and userInfo has no user_id_str', () => {
mockUserState = { isSettled: true, userInfo: { name: 'Test' } };
const { result } = renderHook(() => useLoginStatus());
expect(result.current).toBe('not_login');
});
it('should return "logined" if store is settled and userInfo has user_id_str', () => {
mockUserState = { isSettled: true, userInfo: { user_id_str: '123' } };
const { result } = renderHook(() => useLoginStatus());
expect(result.current).toBe('logined');
});
});
describe('useAlterOnLogout', () => {
let alertMock: Mock;
beforeEach(() => {
alertMock = vi.fn();
(useDocumentVisibility as Mock).mockReturnValue('visible');
// Mock getState for the effect cleanup function
(useUserStore as any).getState = vi.fn(() => mockUserState);
});
it('should not call alert if document is visible or user is not logged in', () => {
mockUserState = { isSettled: true, userInfo: null }; // Not logged in
renderHook(() => useAlterOnLogout(alertMock));
// Simulate visibility change to hidden and back to visible (triggering cleanup and re-run)
act(() => {
(useDocumentVisibility as Mock).mockReturnValue('hidden');
});
act(() => {
(useDocumentVisibility as Mock).mockReturnValue('visible');
});
expect(alertMock).not.toHaveBeenCalled();
});
it('should call alert if user was logged in, document becomes visible, and UID in localStorage is different or null', () => {
const currentUserId = 'user123';
mockUserState = {
isSettled: true,
userInfo: { user_id_str: currentUserId },
}; // Logged in
localStorageMock.setItem(UID_KEY, currentUserId); // UID in localStorage matches
(useDocumentVisibility as Mock).mockReturnValue('hidden'); // Start hidden
const { rerender } = renderHook(() => useAlterOnLogout(alertMock));
// Simulate user logging out in another tab (localStorage UID changes)
localStorageMock.removeItem(UID_KEY);
// Simulate tab becoming visible
act(() => {
(useDocumentVisibility as Mock).mockReturnValue('visible');
});
rerender(); // Rerender to trigger useEffect with new visibility
expect(alertMock).toHaveBeenCalledTimes(1);
});
it('should call alert if user was logged in, document becomes visible, and UID in localStorage is different', () => {
const currentUserId = 'user123';
const otherTabUserId = 'user456';
mockUserState = {
isSettled: true,
userInfo: { user_id_str: currentUserId },
}; // Logged in
localStorageMock.setItem(UID_KEY, currentUserId); // UID in localStorage matches
(useDocumentVisibility as Mock).mockReturnValue('hidden'); // Start hidden
const { rerender } = renderHook(() => useAlterOnLogout(alertMock));
localStorageMock.setItem(UID_KEY, otherTabUserId); // UID changes in another tab
act(() => {
(useDocumentVisibility as Mock).mockReturnValue('visible');
});
rerender();
expect(alertMock).toHaveBeenCalledTimes(1);
});
it('should NOT call alert if user was logged in, document becomes visible, and UID in localStorage matches', () => {
const currentUserId = 'user123';
mockUserState = {
isSettled: true,
userInfo: { user_id_str: currentUserId },
}; // Logged in
localStorageMock.setItem(UID_KEY, currentUserId);
(useDocumentVisibility as Mock).mockReturnValue('hidden');
const { rerender } = renderHook(() => useAlterOnLogout(alertMock));
act(() => {
(useDocumentVisibility as Mock).mockReturnValue('visible');
});
rerender();
expect(alertMock).not.toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest';
import { renderHook } from '@testing-library/react';
import { localStorageService } from '@coze-foundation/local-storage';
import { useSyncLocalStorageUid } from '../use-sync-local-storage-uid';
import { useLoginStatus, useUserInfo } from '../index';
// Mock hooks and services
vi.mock('../index', () => ({
useLoginStatus: vi.fn(),
useUserInfo: vi.fn(),
}));
vi.mock('@coze-foundation/local-storage', () => ({
localStorageService: {
setUserId: vi.fn(),
},
}));
describe('useSyncLocalStorageUid', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('update uid when login status changes', () => {
const mockUserInfo = { user_id_str: '123456' };
const { rerender } = renderHook(() => useSyncLocalStorageUid(), {
initialProps: {},
});
// 初始状态:未登录
(useLoginStatus as Mock).mockReturnValue('not_login');
(useUserInfo as Mock).mockReturnValue(null);
rerender();
expect(localStorageService.setUserId).toHaveBeenCalledWith();
// 切换到登录状态
(useLoginStatus as Mock).mockReturnValue('logined');
(useUserInfo as Mock).mockReturnValue(mockUserInfo);
rerender();
expect(localStorageService.setUserId).toHaveBeenCalledWith(
mockUserInfo.user_id_str,
);
});
});