Files
Genarrative/src/components/platform-entry/PlatformActiveProfileView.test.tsx
T
k88936 fc0dcb0782
Project CI / Repository checks (push) Successful in 50s
Project CI / Frontend tests (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
修复泥点消耗刷新不及时 (#138)
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>
2026-08-08 10:25:02 +08:00

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();
}
});
});