Increase VectorEngine timeouts and add image UI

Add VectorEngine image generation config and raise request timeouts (env + scripts) from 180000 to 1000000ms. Introduce a reusable CreativeImageInputPanel component with tests and wire up mobile keyboard-focus helpers; update generation views and related tests (CustomWorldGenerationView, BarkBattle editor, Match3D, Puzzle flows). Improve API error handling / VectorEngine request guidance (packages/shared http.ts and docs), and apply multiple backend/frontend fixes for puzzle/match3d/prompt handling. Also include extensive docs and decision-log updates describing UI/UX decisions and verification steps.
This commit is contained in:
2026-05-15 02:40:59 +08:00
parent 4642855fd0
commit 74fd9a33ac
87 changed files with 5508 additions and 1261 deletions

View File

@@ -7,6 +7,10 @@ import { afterEach, beforeEach, expect, test, vi } from 'vitest';
import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary';
import type { CreativeAgentSessionSnapshot } from '../../../packages/shared/src/contracts/creativeAgent';
import type {
BabyObjectMatchDraft,
CreateBabyObjectMatchDraftRequest,
} from '../../../packages/shared/src/contracts/edutainmentBabyObject';
import type {
CustomWorldAgentSessionSnapshot,
CustomWorldWorkSummary,
@@ -37,6 +41,14 @@ import {
} from '../../routing/appPageRoutes';
import { ApiClientError } from '../../services/apiClient';
import type { AuthUser } from '../../services/authService';
import {
createBabyObjectMatchDraft,
deleteLocalBabyObjectMatchDraft,
listLocalBabyObjectMatchDrafts,
publishBabyObjectMatchWork,
regenerateBabyObjectMatchDraftAssets,
saveBabyObjectMatchDraft,
} from '../../services/edutainment-baby-object';
import {
createBigFishCreationSession,
getBigFishCreationSession,
@@ -48,6 +60,10 @@ import {
submitBigFishInput,
} from '../../services/big-fish-runtime';
import { listBigFishWorks } from '../../services/big-fish-works';
import {
createBarkBattleDraft,
publishBarkBattleWork,
} from '../../services/bark-battle-creation';
import {
type CreationEntryConfig,
fetchCreationEntryConfig,
@@ -193,9 +209,9 @@ async function openDraftHub(user: ReturnType<typeof userEvent.setup>) {
async function expectDraftHubGeneratingBadgeCountAtLeast(count: number) {
const panel = getPlatformTabPanel('saves');
await waitFor(() => {
expect(within(panel).getAllByText('生成中').length).toBeGreaterThanOrEqual(
count,
);
expect(
within(panel).getAllByLabelText('生成中').length,
).toBeGreaterThanOrEqual(count);
});
}
@@ -277,6 +293,17 @@ const testCreationEntryConfig = {
sortOrder: 40,
updatedAtMicros: 1,
},
{
id: 'bark-battle',
title: '汪汪声浪',
subtitle: '声控狗狗对战',
badge: '可创建',
imageSrc: '/creation-type-references/bark-battle.webp',
visible: true,
open: true,
sortOrder: 45,
updatedAtMicros: 1,
},
{
id: 'square-hole',
title: '方洞挑战',
@@ -446,6 +473,21 @@ vi.mock('../../services/big-fish-runtime', () => ({
submitBigFishInput: vi.fn(),
}));
vi.mock('../../services/bark-battle-creation', () => ({
createBarkBattleDraft: vi.fn(),
publishBarkBattleWork: vi.fn(),
}));
vi.mock('../../services/edutainment-baby-object', () => ({
createBabyObjectMatchDraft: vi.fn(),
deleteLocalBabyObjectMatchDraft: vi.fn(),
hasBabyObjectMatchPlaceholderAssets: vi.fn(() => false),
listLocalBabyObjectMatchDrafts: vi.fn(),
publishBabyObjectMatchWork: vi.fn(),
regenerateBabyObjectMatchDraftAssets: vi.fn(),
saveBabyObjectMatchDraft: vi.fn(),
}));
vi.mock('../../services/match3d-creation', () => ({
match3dCreationClient: {
createSession: vi.fn(),
@@ -806,10 +848,12 @@ vi.mock('../match3d-creation/Match3DAgentWorkspace', () => ({
Match3DAgentWorkspace: ({
session,
isBusy,
error,
onCreateFromForm,
}: {
session: { sessionId: string; messages: Array<{ text: string }> } | null;
isBusy?: boolean;
error?: string | null;
onCreateFromForm?: (payload: {
seedText: string;
themeText: string;
@@ -827,6 +871,7 @@ vi.mock('../match3d-creation/Match3DAgentWorkspace', () => ({
<div data-testid="match3d-workspace-busy-state">
{isBusy ? 'busy' : 'idle'}
</div>
{error ? <div>{error}</div> : null}
<button
type="button"
disabled={isBusy}
@@ -938,6 +983,125 @@ vi.mock('../match3d-runtime/Match3DRuntimeShell', () => ({
),
}));
vi.mock('../bark-battle-creation/BarkBattleConfigEditor', () => ({
BarkBattleConfigEditor: ({
error,
isBusy,
showBackButton,
title,
onPublish,
}: {
error?: string | null;
isBusy?: boolean;
showBackButton?: boolean;
title?: string | null;
onPublish: (payload: {
title: string;
description: string;
themePreset: string;
playerDogSkinPreset: string;
opponentDogSkinPreset: string;
difficultyPreset: 'normal';
leaderboardEnabled: boolean;
}) => void;
}) => (
<div className="bark-battle-config-editor-mock">
<div></div>
<div data-testid="bark-battle-editor-back-state">
{showBackButton ? 'back-visible' : 'back-hidden'}
</div>
<div data-testid="bark-battle-editor-title-state">
{title === null ? 'title-hidden' : title}
</div>
<div data-testid="bark-battle-editor-busy-state">
{isBusy ? 'busy' : 'idle'}
</div>
{error ? <div>{error}</div> : null}
<button
type="button"
disabled={isBusy}
onClick={() => {
onPublish({
title: '汪汪测试杯',
description: '',
themePreset: 'sunny-yard',
playerDogSkinPreset: 'corgi',
opponentDogSkinPreset: 'husky',
difficultyPreset: 'normal',
leaderboardEnabled: true,
});
}}
>
</button>
</div>
),
}));
vi.mock('../edutainment-result/BabyObjectMatchResultView', () => ({
BabyObjectMatchResultView: ({
draft,
onBack,
onStartTestRun,
}: {
draft: BabyObjectMatchDraft;
onBack: () => void;
onStartTestRun?: (draft: BabyObjectMatchDraft) => void;
}) => (
<div className="baby-object-match-result-view-mock">
<div></div>
<div>{draft.workTitle}</div>
<button
type="button"
onClick={() => {
onStartTestRun?.(draft);
}}
>
</button>
<button type="button" onClick={onBack}>
</button>
</div>
),
}));
vi.mock('../edutainment-runtime/BabyObjectMatchRuntimeShell', () => ({
BabyObjectMatchRuntimeShell: ({
draft,
onBack,
}: {
draft: BabyObjectMatchDraft;
onBack?: () => void;
}) => (
<div className="baby-object-match-runtime-shell-mock">
<div>{draft.profileId}</div>
<button type="button" onClick={onBack}>
</button>
</div>
),
}));
vi.mock('../../games/bark-battle/ui/BarkBattleRuntimeShell', () => ({
BarkBattleRuntimeShell: ({
title,
workId,
onExit,
}: {
title?: string;
workId?: string;
onExit?: () => void;
}) => (
<div className="bark-battle-runtime-shell-mock">
<div>{title ?? '未命名'} / {workId ?? 'missing-work'}</div>
<button type="button" onClick={onExit}>
</button>
</div>
),
}));
vi.mock('../custom-world-agent/CustomWorldAgentWorkspace', () => ({
CustomWorldAgentWorkspace: ({
session,
@@ -1070,6 +1234,48 @@ function buildMockCreativeAgentSession(
};
}
function buildMockBabyObjectMatchDraft(
overrides: Partial<BabyObjectMatchDraft> = {},
): BabyObjectMatchDraft {
const itemNames = overrides.itemNames ?? ['苹果', '香蕉'];
const now = '2026-05-14T10:00:00.000Z';
return {
draftId: 'baby-object-draft-red-dot',
profileId: 'baby-object-profile-red-dot',
templateId: 'baby-object-match',
templateName: '宝贝识物',
workTitle: '宝贝识物红点草稿',
workDescription: `${itemNames[0]}${itemNames[1]}识物分类`,
itemNames,
itemAssets: [
{
itemId: 'baby-object-item-a',
itemName: itemNames[0],
imageSrc: '/baby-object/apple.png',
assetObjectId: null,
generationProvider: 'vector-engine-gpt-image-2',
prompt: itemNames[0],
},
{
itemId: 'baby-object-item-b',
itemName: itemNames[1],
imageSrc: '/baby-object/banana.png',
assetObjectId: null,
generationProvider: 'vector-engine-gpt-image-2',
prompt: itemNames[1],
},
],
visualPackage: null,
themeTags: ['寓教于乐', '宝贝识物'],
publicationStatus: 'draft',
createdAt: now,
updatedAt: now,
publishedAt: null,
...overrides,
};
}
function buildMockSquareHoleAgentSession(
overrides: Partial<
Parameters<typeof buildMockSquareHoleAgentSessionImpl>[0]
@@ -1908,7 +2114,7 @@ beforeEach(() => {
testCreationEntryConfig,
);
vi.mocked(getProfileDashboard).mockResolvedValue({
walletBalance: 0,
walletBalance: 20,
totalPlayTimeMs: 0,
playedWorldCount: 0,
updatedAt: '2026-04-16T12:00:00.000Z',
@@ -2006,6 +2212,22 @@ beforeEach(() => {
vi.mocked(deleteRpgEntryWorldProfile).mockResolvedValue([]);
vi.mocked(listVisualNovelGallery).mockResolvedValue({ works: [] });
vi.mocked(listVisualNovelWorks).mockResolvedValue({ works: [] });
vi.mocked(listLocalBabyObjectMatchDrafts).mockResolvedValue([]);
vi.mocked(deleteLocalBabyObjectMatchDraft).mockResolvedValue([]);
vi.mocked(saveBabyObjectMatchDraft).mockImplementation(async (payload) => ({
draft: payload.draft,
}));
vi.mocked(regenerateBabyObjectMatchDraftAssets).mockImplementation(
async (draft) => ({ draft }),
);
vi.mocked(publishBabyObjectMatchWork).mockImplementation(async (payload) => ({
draft: {
...payload.draft,
publicationStatus: 'published',
publishedAt: '2026-05-14T10:10:00.000Z',
},
publicWorkCode: `BO-${payload.draft.profileId}`,
}));
vi.mocked(recordBigFishPlay).mockResolvedValue({ items: [] });
vi.mocked(recordRpgEntryWorldGalleryPlay).mockImplementation(
async (ownerUserId, profileId) => ({
@@ -2502,6 +2724,36 @@ beforeEach(() => {
},
}));
vi.mocked(recordBigFishPlay).mockResolvedValue({ items: [] });
vi.mocked(createBarkBattleDraft).mockResolvedValue({
draftId: 'bark-battle-draft-1',
workId: 'bark-battle-work-1',
title: '汪汪测试杯',
description: '',
themePreset: 'sunny-yard',
playerDogSkinPreset: 'corgi',
opponentDogSkinPreset: 'husky',
difficultyPreset: 'normal',
leaderboardEnabled: true,
configVersion: 1,
rulesetVersion: 'bark-battle-ruleset-v1',
updatedAt: '2026-05-14T10:00:00.000Z',
});
vi.mocked(publishBarkBattleWork).mockResolvedValue({
workId: 'bark-battle-work-1',
draftId: 'bark-battle-draft-1',
configVersion: 1,
rulesetVersion: 'bark-battle-ruleset-v1',
playTypeId: 'bark-battle',
title: '汪汪测试杯',
description: '',
themePreset: 'sunny-yard',
playerDogSkinPreset: 'corgi',
opponentDogSkinPreset: 'husky',
difficultyPreset: 'normal',
leaderboardEnabled: true,
updatedAt: '2026-05-14T10:00:00.000Z',
publishedAt: '2026-05-14T10:00:00.000Z',
});
vi.mocked(match3dCreationClient.createSession).mockResolvedValue({
session: buildMockMatch3DAgentSession(),
});
@@ -2885,6 +3137,9 @@ test('create tab shows template tabs and embeds puzzle form by default', async (
expect(
screen.getByRole('tab', { name: '抓大鹅' }).querySelector('img')?.src,
).toContain('/creation-type-references/match3d.webp');
expect(
screen.getByRole('tab', { name: '汪汪声浪' }).querySelector('img')?.src,
).toContain('/creation-type-references/bark-battle.webp');
expect(
screen.getByRole('tab', { name: '宝贝识物' }).querySelector('img')?.src,
).toContain('/child-motion-demo/picture-book-grass-stage.png');
@@ -2900,6 +3155,7 @@ test('create tab shows template tabs and embeds puzzle form by default', async (
expect(screen.queryByRole('tab', { name: /方洞挑战/u })).toBeNull();
expect(screen.queryByRole('tab', { name: '视觉小说' })).toBeNull();
expect(screen.getByRole('tab', { name: /抓大鹅/u })).toBeTruthy();
expect(screen.getByRole('tab', { name: /汪汪声浪/u })).toBeTruthy();
expect(screen.getByRole('tab', { name: /宝贝识物/u })).toBeTruthy();
expect(createRpgCreationSession).not.toHaveBeenCalled();
expect(match3dCreationClient.createSession).not.toHaveBeenCalled();
@@ -2922,6 +3178,57 @@ test('create tab switches match3d into the embedded entry form', async () => {
expect(match3dCreationClient.createSession).not.toHaveBeenCalled();
});
test('create tab switches bark battle into the embedded config form', async () => {
const user = userEvent.setup();
render(<TestWrapper withAuth />);
await openCreateTemplateHub(user);
await user.click(screen.getByRole('tab', { name: '汪汪声浪' }));
expect(
screen.getByRole('tab', { name: '汪汪声浪' }).getAttribute('aria-selected'),
).toBe('true');
expect(await screen.findByText('汪汪声浪配置表单')).toBeTruthy();
expect(screen.getByTestId('bark-battle-editor-back-state').textContent).toBe(
'back-hidden',
);
expect(screen.getByTestId('bark-battle-editor-title-state').textContent).toBe(
'title-hidden',
);
expect(screen.queryByText('汪汪声浪运行态')).toBeNull();
expect(createBarkBattleDraft).not.toHaveBeenCalled();
expect(publishBarkBattleWork).not.toHaveBeenCalled();
});
test('bark battle publish preview returns to the embedded config form', async () => {
const user = userEvent.setup();
render(<TestWrapper withAuth />);
await openCreateTemplateHub(user);
await user.click(screen.getByRole('tab', { name: '汪汪声浪' }));
await user.click(await screen.findByRole('button', { name: '发布并试玩' }));
expect(createBarkBattleDraft).toHaveBeenCalledWith({
title: '汪汪测试杯',
description: '',
themePreset: 'sunny-yard',
playerDogSkinPreset: 'corgi',
opponentDogSkinPreset: 'husky',
difficultyPreset: 'normal',
leaderboardEnabled: true,
});
expect(await screen.findByText(/汪汪声浪运行态:汪汪测试杯/u)).toBeTruthy();
await user.click(screen.getByRole('button', { name: '返回配置' }));
expect(await screen.findByText('汪汪声浪配置表单')).toBeTruthy();
expect(
screen.getByRole('tab', { name: '汪汪声浪' }).getAttribute('aria-selected'),
).toBe('true');
});
test('running match3d form generation can return to draft tab and reopen progress', async () => {
const user = userEvent.setup();
const runningSession = buildMockMatch3DAgentSession({
@@ -3394,6 +3701,116 @@ test('running puzzle form generation creates a new puzzle draft on same template
});
});
test('running puzzle draft opens generation progress from draft tab', async () => {
const user = userEvent.setup();
const runningSession = buildMockPuzzleAgentSession({
sessionId: 'puzzle-running-session',
draft: null,
stage: 'collecting_anchors',
progressPercent: 20,
});
let resolveCompile!: (value: {
operation: {
operationId: string;
type: 'compile_puzzle_draft';
status: 'completed';
phaseLabel: string;
phaseDetail: string;
progress: number;
};
session: PuzzleAgentSessionSnapshot;
}) => void;
vi.mocked(createPuzzleAgentSession).mockResolvedValueOnce({
session: runningSession,
});
vi.mocked(executePuzzleAgentAction).mockReturnValueOnce(
new Promise((resolve) => {
resolveCompile = resolve;
}),
);
vi.mocked(getPuzzleAgentSession).mockResolvedValue({
session: runningSession,
});
render(<TestWrapper withAuth />);
await openCreateTemplateHub(user);
await user.click(await screen.findByRole('button', { name: '生成草稿' }));
expect(await screen.findByText('拼图草稿生成进度')).toBeTruthy();
await user.click(screen.getByRole('button', { name: '返回创作中心' }));
await openDraftHub(user);
await expectDraftHubGeneratingBadgeCountAtLeast(1);
await user.click(
screen.getByRole('button', { name: /继续创作《拼图草稿》/u }),
);
expect(await screen.findByText('拼图草稿生成进度')).toBeTruthy();
expect(screen.queryByText('拼图结果页')).toBeNull();
await act(async () => {
resolveCompile({
operation: {
operationId: 'compile-puzzle-running',
type: 'compile_puzzle_draft',
status: 'completed',
phaseLabel: '已完成',
phaseDetail: '草稿已生成',
progress: 1,
},
session: buildMockPuzzleAgentSession({
sessionId: 'puzzle-running-session',
}),
});
});
});
test('puzzle form checks mud points before creating a draft', async () => {
const user = userEvent.setup();
vi.mocked(getProfileDashboard).mockResolvedValue({
walletBalance: 1,
totalPlayTimeMs: 0,
playedWorldCount: 0,
updatedAt: '2026-05-14T10:00:00.000Z',
});
render(<TestWrapper withAuth />);
await openCreateTemplateHub(user);
await user.click(await screen.findByRole('button', { name: '生成草稿' }));
expect(
await screen.findByText('泥点不足,本次需要 2 泥点,当前 1 泥点。'),
).toBeTruthy();
expect(createPuzzleAgentSession).not.toHaveBeenCalled();
expect(executePuzzleAgentAction).not.toHaveBeenCalled();
});
test('match3d form checks mud points before creating a draft', async () => {
const user = userEvent.setup();
vi.mocked(getProfileDashboard).mockResolvedValue({
walletBalance: 9,
totalPlayTimeMs: 0,
playedWorldCount: 0,
updatedAt: '2026-05-14T10:00:00.000Z',
});
render(<TestWrapper withAuth />);
await openCreateTemplateHub(user);
await user.click(screen.getByRole('tab', { name: '抓大鹅' }));
await user.click(
await screen.findByRole('button', { name: '生成抓大鹅草稿' }),
);
expect(
await screen.findByText('泥点不足,本次需要 10 泥点,当前 9 泥点。'),
).toBeTruthy();
expect(match3dCreationClient.createSession).not.toHaveBeenCalled();
expect(match3dCreationClient.executeAction).not.toHaveBeenCalled();
});
test('match3d result trial passes generated models into first runtime mount', async () => {
const user = userEvent.setup();
const generatedItemAssets: Match3DWorkSummary['generatedItemAssets'] = [
@@ -3857,6 +4274,113 @@ test('completed match3d draft notice first opens trial then reopens result', asy
expect(match3dServerRuntimeAdapterMock.startRun).toHaveBeenCalledTimes(1);
});
test('completed baby object match draft viewed immediately does not keep unread marker', async () => {
const user = userEvent.setup();
const generatedDraft = buildMockBabyObjectMatchDraft();
vi.mocked(createBabyObjectMatchDraft).mockImplementation(
async (payload: CreateBabyObjectMatchDraftRequest) => ({
draft: buildMockBabyObjectMatchDraft({
itemNames: [payload.itemAName, payload.itemBName],
}),
}),
);
vi.mocked(listLocalBabyObjectMatchDrafts).mockResolvedValue([generatedDraft]);
render(<TestWrapper withAuth />);
await openCreateTemplateHub(user);
await user.click(screen.getByRole('tab', { name: '宝贝识物' }));
await waitFor(() => {
expect(
screen.getByRole('tab', { name: '宝贝识物' }).getAttribute(
'aria-selected',
),
).toBe('true');
});
await user.type(await screen.findByLabelText('物品 A'), '苹果');
await user.type(await screen.findByLabelText('物品 B'), '香蕉');
await user.click(screen.getByRole('button', { name: '生成宝贝识物草稿' }));
expect(await screen.findByText('宝贝识物结果页')).toBeTruthy();
await user.click(screen.getByRole('button', { name: '返回' }));
await waitFor(() => {
expect(screen.queryByText('宝贝识物结果页')).toBeNull();
});
expect(await screen.findByLabelText('物品 A')).toBeTruthy();
await user.click(screen.getByRole('button', { name: '返回' }));
await openDraftHub(user);
expect(
await screen.findByRole('button', {
name: /继续创作《宝贝识物红点草稿》/u,
}),
).toBeTruthy();
expect(screen.queryByLabelText('新生成完成')).toBeNull();
await user.click(
screen.getByRole('button', {
name: /继续创作《宝贝识物红点草稿》/u,
}),
);
expect(await screen.findByText('宝贝识物结果页')).toBeTruthy();
await user.click(screen.getByRole('button', { name: '返回' }));
await waitFor(() => {
expect(screen.queryByText('宝贝识物结果页')).toBeNull();
});
await user.click(await screen.findByRole('button', { name: '返回' }));
await openDraftHub(user);
expect(screen.queryByLabelText('新生成完成')).toBeNull();
});
test('completed baby object match draft shows unread marker after leaving generation page', async () => {
const user = userEvent.setup();
const generatedDraft = buildMockBabyObjectMatchDraft();
let resolveCreateDraft!: (value: { draft: BabyObjectMatchDraft }) => void;
vi.mocked(createBabyObjectMatchDraft).mockReturnValue(
new Promise((resolve) => {
resolveCreateDraft = resolve;
}),
);
vi.mocked(listLocalBabyObjectMatchDrafts).mockResolvedValue([generatedDraft]);
render(<TestWrapper withAuth />);
await openCreateTemplateHub(user);
await user.click(screen.getByRole('tab', { name: '宝贝识物' }));
await user.type(await screen.findByLabelText('物品 A'), '苹果');
await user.type(await screen.findByLabelText('物品 B'), '香蕉');
await user.click(screen.getByRole('button', { name: '生成宝贝识物草稿' }));
expect(await screen.findByText('宝贝识物草稿生成进度')).toBeTruthy();
await user.click(screen.getByRole('button', { name: '返回创作中心' }));
await openDraftHub(user);
await act(async () => {
resolveCreateDraft({ draft: generatedDraft });
});
expect(await screen.findByLabelText('新生成完成')).toBeTruthy();
expect(
await screen.findByRole('button', { name: '草稿,有新草稿' }),
).toBeTruthy();
await user.click(
await screen.findByRole('button', {
name: /继续创作《宝贝识物红点草稿》/u,
}),
);
expect(await screen.findByText('宝贝识物结果页')).toBeTruthy();
await user.click(screen.getByRole('button', { name: '返回' }));
await waitFor(() => {
expect(screen.queryByText('宝贝识物结果页')).toBeNull();
});
await user.click(await screen.findByRole('button', { name: '返回' }));
await openDraftHub(user);
expect(screen.queryByLabelText('新生成完成')).toBeNull();
});
test('puzzle draft generation auto starts trial and runtime back opens draft result', async () => {
const user = userEvent.setup();
const generatedDraft: PuzzleResultDraft = {
@@ -7943,7 +8467,7 @@ test('creation hub published work experience button enters world directly', asyn
expect(handleCustomWorldSelect).toHaveBeenCalledTimes(1);
});
test('creation hub published work card keeps delete action guarded by detail flow', async () => {
test('creation hub published work card reveals delete action after card action reveal', async () => {
const user = userEvent.setup();
const publishedWork = {
@@ -8014,7 +8538,13 @@ test('creation hub published work card keeps delete action guarded by detail flo
await openDraftHub(user);
expect(await screen.findByRole('button', { name: /查看详情/u })).toBeTruthy();
const publishedCard = await screen.findByRole('button', {
name: /查看详情《潮雾列岛》/u,
});
publishedCard.focus();
await user.keyboard('{ArrowLeft}');
expect(screen.getByRole('button', { name: '删除' })).toBeTruthy();
await user.click(screen.getByRole('button', { name: '删除' }));
const dialog = await screen.findByRole('dialog', { name: '删除作品' });

View File

@@ -949,10 +949,7 @@ test('profile recharge modal buys points through mock channel outside mini progr
const onRechargeSuccess = vi.fn();
renderProfileView(onRechargeSuccess);
const shortcutRegion = screen.getByRole('region', { name: '常用功能' });
await user.click(
within(shortcutRegion).getByRole('button', { name: //u }),
);
await user.click(screen.getByRole('button', { name: /\s*\//u }));
expect(await screen.findByText('账户充值')).toBeTruthy();
expect(mockGetRpgProfileRechargeCenter).toHaveBeenCalledTimes(1);
@@ -1022,10 +1019,7 @@ test('profile recharge modal posts requestPayment params in mini program web-vie
});
renderProfileView();
const shortcutRegion = screen.getByRole('region', { name: '常用功能' });
await user.click(
within(shortcutRegion).getByRole('button', { name: //u }),
);
await user.click(screen.getByRole('button', { name: /\s*\//u }));
await user.click(await screen.findByRole('button', { name: /60/u }));
await waitFor(() => {
@@ -1302,6 +1296,9 @@ test('profile page shows legal entries and ICP record link', async () => {
expect(
within(shortcutRegion).getByRole('button', { name: //u }),
).toBeTruthy();
expect(
within(shortcutRegion).queryByRole('button', { name: //u }),
).toBeNull();
expect(
within(shortcutRegion).getByRole('button', { name: //u }),
).toBeTruthy();

View File

@@ -5196,12 +5196,6 @@ export function RpgEntryHomeView({
icon={Star}
onClick={openTaskCenterPanel}
/>
<ProfileShortcutButton
label="充值"
subLabel="泥点/会员"
icon={Coins}
onClick={openRechargeModal}
/>
<ProfileShortcutButton
label="兑换码"
subLabel="福利奖励"