fc0dcb0782
refactor: - 主站复用game agent的zustand store fix: - 修复旧请求覆盖问题 - 修复账号切换问题 - 主站原本已经每 4 秒轮询 external generation 任务状态, 在这里补充触发余额刷新的时机 --------- Co-authored-by: 段舒康 <kdletters@qq.com> Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/138 Co-authored-by: 王德宇 <kvtodev@outlook.com> Co-committed-by: 王德宇 <kvtodev@outlook.com>
239 lines
7.5 KiB
TypeScript
239 lines
7.5 KiB
TypeScript
/* @vitest-environment jsdom */
|
|
|
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import type { AuthUser } from '../../../packages/shared/src/contracts/auth';
|
|
import * as hostBridgeServices from '../../services/host-bridge/hostBridge';
|
|
import { PlatformActiveProfileView } from './PlatformActiveProfileView';
|
|
|
|
const updateAuthProfileMock = vi.hoisted(() => vi.fn());
|
|
|
|
vi.mock('../../services/authService', () => ({
|
|
updateAuthProfile: updateAuthProfileMock,
|
|
}));
|
|
|
|
const callbacks = {
|
|
onLogin: vi.fn(),
|
|
onOpenApiKeys: vi.fn(),
|
|
onOpenCommunity: vi.fn(),
|
|
onOpenFeedback: vi.fn(),
|
|
onOpenRecharge: vi.fn(),
|
|
onOpenRewardCode: vi.fn(),
|
|
onOpenSettings: vi.fn(),
|
|
onOpenWalletLedger: vi.fn(),
|
|
onUserUpdated: vi.fn(),
|
|
};
|
|
|
|
const authenticatedUser: AuthUser = {
|
|
id: 'user-1',
|
|
publicUserCode: '100001',
|
|
displayName: '测试玩家',
|
|
avatarUrl: null,
|
|
phoneNumberMasked: null,
|
|
loginMethod: 'password' as const,
|
|
bindingStatus: 'active',
|
|
wechatBound: false,
|
|
};
|
|
|
|
describe('PlatformActiveProfileView', () => {
|
|
beforeEach(() => {
|
|
Object.values(callbacks).forEach((callback) => callback.mockReset());
|
|
updateAuthProfileMock.mockReset();
|
|
});
|
|
|
|
it('shows the login entry for guests', () => {
|
|
render(
|
|
<PlatformActiveProfileView
|
|
{...callbacks}
|
|
dashboard={null}
|
|
isLoadingDashboard={false}
|
|
isLoadingWalletBalance={false}
|
|
legacyWalletBalance={null}
|
|
mudPointBalance={null}
|
|
user={null}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByRole('main', { name: '我的' })).toBeTruthy();
|
|
expect(screen.getByText('尚未登录')).toBeTruthy();
|
|
expect(screen.getByRole('button', { name: '登录' })).toBeTruthy();
|
|
});
|
|
|
|
it('renders active account, wallet, and settings capabilities', () => {
|
|
render(
|
|
<PlatformActiveProfileView
|
|
{...callbacks}
|
|
dashboard={{
|
|
walletBalance: 108,
|
|
totalPlayTimeMs: 5_400_000,
|
|
playedWorldCount: 7,
|
|
updatedAt: '2026-07-18T00:00:00.000Z',
|
|
}}
|
|
isLoadingDashboard={false}
|
|
isLoadingWalletBalance={false}
|
|
legacyWalletBalance={null}
|
|
mudPointBalance={{
|
|
totalPoints: 108,
|
|
permanentPoints: 88,
|
|
limitedPoints: 0,
|
|
limitedExpiresAt: null,
|
|
dailyFreePoints: 20,
|
|
dailyFreeResetPoints: 20,
|
|
dailyFreeResetsAt: '2026-08-04T00:00:00+08:00',
|
|
}}
|
|
user={authenticatedUser}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByText('测试玩家')).toBeTruthy();
|
|
expect(screen.getByText(/陶泥号:\s*100001/u)).toBeTruthy();
|
|
expect(screen.getByRole('button', { name: '泥点余额 108' })).toBeTruthy();
|
|
expect(screen.getByRole('button', { name: /泥点充值/u })).toBeTruthy();
|
|
expect(screen.getByRole('button', { name: /兑换码/u })).toBeTruthy();
|
|
expect(screen.getByRole('button', { name: /玩家社区/u })).toBeTruthy();
|
|
expect(screen.getByRole('button', { name: /反馈与建议/u })).toBeTruthy();
|
|
expect(screen.getByRole('button', { name: /通用设置/u })).toBeTruthy();
|
|
expect(
|
|
screen.getByRole('button', { name: /开发者 API Key/u }),
|
|
).toBeTruthy();
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: '用户协议' }));
|
|
const legalDialog = screen.getByRole('dialog', { name: '用户协议' });
|
|
expect(legalDialog.parentElement?.className).toContain(
|
|
'platform-theme--light',
|
|
);
|
|
});
|
|
|
|
it('edits the nickname through the profile identity action', async () => {
|
|
updateAuthProfileMock.mockResolvedValueOnce({
|
|
...authenticatedUser,
|
|
displayName: '新昵称',
|
|
});
|
|
render(
|
|
<PlatformActiveProfileView
|
|
{...callbacks}
|
|
dashboard={null}
|
|
isLoadingDashboard={false}
|
|
isLoadingWalletBalance={false}
|
|
legacyWalletBalance={null}
|
|
mudPointBalance={null}
|
|
user={authenticatedUser}
|
|
/>,
|
|
);
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: '修改昵称' }));
|
|
const input = screen.getByRole('textbox', { name: '新昵称' });
|
|
fireEvent.change(input, { target: { value: 'a' } });
|
|
fireEvent.click(screen.getByRole('button', { name: '保存' }));
|
|
|
|
expect(screen.getByText('昵称需要 2 到 20 位')).toBeTruthy();
|
|
expect(updateAuthProfileMock).not.toHaveBeenCalled();
|
|
|
|
fireEvent.change(input, { target: { value: '新昵称' } });
|
|
fireEvent.click(screen.getByRole('button', { name: '保存' }));
|
|
|
|
await waitFor(() => {
|
|
expect(updateAuthProfileMock).toHaveBeenCalledWith({
|
|
displayName: '新昵称',
|
|
});
|
|
expect(callbacks.onUserUpdated).toHaveBeenCalledWith({
|
|
...authenticatedUser,
|
|
displayName: '新昵称',
|
|
});
|
|
});
|
|
});
|
|
|
|
it('keeps avatar validation on the direct camera action', () => {
|
|
render(
|
|
<PlatformActiveProfileView
|
|
{...callbacks}
|
|
dashboard={null}
|
|
isLoadingDashboard={false}
|
|
isLoadingWalletBalance={false}
|
|
legacyWalletBalance={null}
|
|
mudPointBalance={null}
|
|
user={authenticatedUser}
|
|
/>,
|
|
);
|
|
|
|
const avatarInput = screen
|
|
.getAllByLabelText('上传头像')
|
|
.find((element) => element instanceof HTMLInputElement);
|
|
expect(avatarInput).toBeTruthy();
|
|
fireEvent.change(avatarInput as HTMLInputElement, {
|
|
target: {
|
|
files: [new File(['avatar'], 'avatar.txt', { type: 'text/plain' })],
|
|
},
|
|
});
|
|
|
|
expect(screen.getByText('头像仅支持 jpg、png、webp')).toBeTruthy();
|
|
expect(updateAuthProfileMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('imports avatar images through the native HostBridge', async () => {
|
|
class MockFileReader {
|
|
result: string | null = null;
|
|
onload: null | (() => void) = null;
|
|
onerror: null | (() => void) = null;
|
|
|
|
readAsDataURL() {
|
|
this.result = 'data:image/webp;base64,YXZhdGFy';
|
|
this.onload?.();
|
|
}
|
|
}
|
|
class MockImage {
|
|
onload: null | (() => void) = null;
|
|
onerror: null | (() => void) = null;
|
|
naturalWidth = 720;
|
|
naturalHeight = 720;
|
|
|
|
set src(_value: string) {
|
|
this.onload?.();
|
|
}
|
|
}
|
|
vi.stubGlobal('FileReader', MockFileReader as unknown as typeof FileReader);
|
|
vi.stubGlobal('Image', MockImage as unknown as typeof Image);
|
|
const inputClickSpy = vi
|
|
.spyOn(HTMLInputElement.prototype, 'click')
|
|
.mockImplementation(() => undefined);
|
|
vi.spyOn(
|
|
hostBridgeServices,
|
|
'canUseNativeHostCapability',
|
|
).mockImplementation((capability) => capability === 'file.importImage');
|
|
vi.spyOn(hostBridgeServices, 'importHostImageFile').mockResolvedValue({
|
|
action: 'selected',
|
|
fileName: 'native-avatar.webp',
|
|
base64Data: 'YXZhdGFy',
|
|
mimeType: 'image/webp',
|
|
bytes: 6,
|
|
});
|
|
|
|
try {
|
|
render(
|
|
<PlatformActiveProfileView
|
|
{...callbacks}
|
|
dashboard={null}
|
|
isLoadingDashboard={false}
|
|
isLoadingWalletBalance={false}
|
|
legacyWalletBalance={null}
|
|
mudPointBalance={null}
|
|
user={authenticatedUser}
|
|
/>,
|
|
);
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: '上传头像' }));
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByRole('dialog', { name: '裁剪头像' })).toBeTruthy();
|
|
});
|
|
expect(hostBridgeServices.importHostImageFile).toHaveBeenCalledTimes(1);
|
|
expect(inputClickSpy).not.toHaveBeenCalled();
|
|
expect(screen.getByLabelText('头像裁剪操作区')).toBeTruthy();
|
|
} finally {
|
|
vi.restoreAllMocks();
|
|
vi.unstubAllGlobals();
|
|
}
|
|
});
|
|
});
|