Merge pull request #3 from codex/coin-consume

# Conflicts:
#	packages/shared/src/contracts/runtime.ts
#	server-rs/crates/module-runtime/src/lib.rs
#	server-rs/crates/shared-contracts/src/runtime.rs
#	server-rs/crates/spacetime-client/src/module_bindings/runtime_profile_wallet_ledger_source_type_type.rs
#	src/components/rpg-entry/RpgEntryHomeView.tsx
This commit is contained in:
2026-04-28 15:43:48 +08:00
16 changed files with 779 additions and 684 deletions

View File

@@ -1,6 +1,6 @@
/* @vitest-environment jsdom */
import { render, screen, waitFor } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { afterEach, expect, test, vi } from 'vitest';
@@ -11,7 +11,29 @@ import {
} from './RpgEntryHomeView';
import type { PlatformPublicGalleryCard } from './rpgEntryWorldPresentation';
const { mockGetRpgProfileWalletLedger } = vi.hoisted(() => ({
mockGetRpgProfileWalletLedger: vi.fn(async () => ({
entries: [
{
id: 'ledger-1',
amountDelta: -1,
balanceAfter: 29,
sourceType: 'asset_operation_consume',
createdAt: '2026-04-28T10:00:00Z',
},
{
id: 'ledger-2',
amountDelta: 30,
balanceAfter: 30,
sourceType: 'invite_invitee_reward',
createdAt: '2026-04-28T09:00:00Z',
},
],
})),
}));
vi.mock('../../services/rpg-entry/rpgProfileClient', () => ({
getRpgProfileWalletLedger: mockGetRpgProfileWalletLedger,
getRpgProfileRechargeCenter: vi.fn(async () => ({
walletBalance: 0,
membership: {
@@ -269,20 +291,34 @@ afterEach(() => {
});
});
test('opens recharge modal and submits points product', async () => {
test('opens wallet ledger modal from narrative coin card', async () => {
const user = userEvent.setup();
const onRechargeSuccess = vi.fn();
renderProfileView(onRechargeSuccess);
await user.click(screen.getByText('会员充值'));
renderProfileView();
await user.click(screen.getByText('剩余叙世币'));
expect(await screen.findByText('账户充值')).toBeTruthy();
expect(await screen.findByText('叙世币充值')).toBeTruthy();
expect(await screen.findByText('60叙世币')).toBeTruthy();
expect(await screen.findByText('叙世币账单')).toBeTruthy();
expect(mockGetRpgProfileWalletLedger).toHaveBeenCalledTimes(1);
expect(screen.getByText('资产操作消耗')).toBeTruthy();
expect(screen.getByText('-1')).toBeTruthy();
expect(screen.getByText('填写邀请码奖励')).toBeTruthy();
expect(screen.getByText('+30')).toBeTruthy();
});
await user.click(screen.getByText('首充送60叙世币'));
test('wallet ledger modal shows empty and error states', async () => {
const user = userEvent.setup();
mockGetRpgProfileWalletLedger.mockResolvedValueOnce({ entries: [] });
await waitFor(() => expect(onRechargeSuccess).toHaveBeenCalledTimes(1));
renderProfileView();
await user.click(screen.getByText('剩余叙世币'));
expect(await screen.findByText('暂无账单记录')).toBeTruthy();
await user.click(screen.getByLabelText('关闭叙世币账单'));
mockGetRpgProfileWalletLedger.mockRejectedValueOnce(new Error('加载失败'));
await user.click(screen.getByText('剩余叙世币'));
expect(await screen.findByText('加载失败')).toBeTruthy();
expect(screen.getByText('重新加载')).toBeTruthy();
});
test('shows a reachable login entry in logged out mobile shell', async () => {