Files
Genarrative/apps/ai-game-creator-shell/tests/walletStore.test.ts
T
k88936 125927e0e8 Ai game creator app home sidebar (#86)
![shotmd-1784186268.jpg](/attachments/c828773c-4f1a-4654-b76d-c944b82ec023)
![shotmd-1784186877.jpg](/attachments/f1782ba3-245a-40c1-8caa-cbd7e4cce74b)
![shotmd-1784186948-compressed.webp](/attachments/f3d02ac3-db7a-439b-9a84-7aceefad9048)
TODO: 补充说明书链接

Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/86
Co-authored-by: 王德宇 <kvtodev@outlook.com>
Co-committed-by: 王德宇 <kvtodev@outlook.com>
2026-07-20 15:03:23 +08:00

183 lines
6.3 KiB
TypeScript

/** @vitest-environment jsdom */
import { act, renderHook } from '@testing-library/react';
import { beforeEach, describe, expect, test, vi } from 'vitest';
const clientApi = vi.hoisted(() => ({
getClientProfileRechargeCenter: vi.fn(),
}));
vi.mock('../src/services/clientApi', () => clientApi);
import { useWalletStore } from '../src/stores/useWalletStore';
function detailedBalance(totalPoints: number) {
return {
totalPoints,
permanentPoints: Math.max(0, totalPoints - 20),
limitedPoints: 20,
limitedExpiresAt: '2026-08-01T00:00:00Z',
dailyFreePoints: 0,
dailyFreeResetPoints: 20,
dailyFreeResetsAt: '2026-07-18T00:00:00Z',
};
}
describe('useWalletStore', () => {
beforeEach(() => {
useWalletStore.getState().resetWalletBalance();
vi.clearAllMocks();
});
test('keeps mudPointBalance as the only balance state', async () => {
const mudPointBalance = detailedBalance(120);
clientApi.getClientProfileRechargeCenter.mockResolvedValue({
walletBalance: 120,
mudPointBalance,
});
const { result } = renderHook(() => useWalletStore());
await act(async () => {
await result.current.onWalletBalanceMayHaveChanged();
});
expect(result.current.mudPointBalance).toEqual(mudPointBalance);
expect(result.current.mudPointBalanceStatus).toBe('ready');
expect(result.current.mudPointBalanceError).toBe('');
expect(Object.keys(result.current).sort()).toEqual([
'applyWalletBalanceSnapshot',
'mudPointBalance',
'mudPointBalanceError',
'mudPointBalanceStatus',
'onWalletBalanceMayHaveChanged',
'resetWalletBalance',
]);
});
test('recovers from a failed refresh with a recharge-center snapshot', async () => {
clientApi.getClientProfileRechargeCenter.mockRejectedValueOnce(
new Error('首次读取失败'),
);
await useWalletStore.getState().onWalletBalanceMayHaveChanged();
const mudPointBalance = detailedBalance(120);
useWalletStore.getState().applyWalletBalanceSnapshot(mudPointBalance);
expect(useWalletStore.getState().mudPointBalance).toEqual(mudPointBalance);
expect(useWalletStore.getState().mudPointBalanceStatus).toBe('ready');
expect(useWalletStore.getState().mudPointBalanceError).toBe('');
});
test('does not let an older refresh overwrite an applied snapshot', async () => {
let rejectRequest: ((reason: Error) => void) | undefined;
clientApi.getClientProfileRechargeCenter.mockImplementationOnce(
() =>
new Promise((_, reject) => {
rejectRequest = reject;
}),
);
const refresh = useWalletStore.getState().onWalletBalanceMayHaveChanged();
const mudPointBalance = detailedBalance(160);
useWalletStore.getState().applyWalletBalanceSnapshot(mudPointBalance);
rejectRequest?.(new Error('过期请求失败'));
await refresh;
expect(useWalletStore.getState().mudPointBalance).toEqual(mudPointBalance);
expect(useWalletStore.getState().mudPointBalanceStatus).toBe('ready');
expect(useWalletStore.getState().mudPointBalanceError).toBe('');
});
test('rejects a response that omits mudPointBalance instead of inventing a breakdown', async () => {
clientApi.getClientProfileRechargeCenter.mockResolvedValue({
walletBalance: 120,
});
await useWalletStore.getState().onWalletBalanceMayHaveChanged();
expect(useWalletStore.getState().mudPointBalance).toBeNull();
expect(useWalletStore.getState().mudPointBalanceStatus).toBe('error');
expect(useWalletStore.getState().mudPointBalanceError).toBe(
'充值中心响应缺少泥点余额',
);
});
test('uses mudPointBalance from recharge-center as the authoritative snapshot', async () => {
const mudPointBalance = detailedBalance(180);
clientApi.getClientProfileRechargeCenter.mockResolvedValue({
walletBalance: 999,
mudPointBalance,
});
await useWalletStore.getState().onWalletBalanceMayHaveChanged();
expect(useWalletStore.getState().mudPointBalance).toEqual(mudPointBalance);
});
test('discards an outdated response and performs one trailing refresh', async () => {
let resolveFirst: ((value: unknown) => void) | undefined;
clientApi.getClientProfileRechargeCenter
.mockImplementationOnce(
() =>
new Promise((resolve) => {
resolveFirst = resolve;
}),
)
.mockResolvedValueOnce({
walletBalance: 80,
mudPointBalance: detailedBalance(80),
});
const firstRefresh =
useWalletStore.getState().onWalletBalanceMayHaveChanged();
const trailingRefresh =
useWalletStore.getState().onWalletBalanceMayHaveChanged();
resolveFirst?.({
walletBalance: 120,
mudPointBalance: detailedBalance(120),
});
await Promise.all([firstRefresh, trailingRefresh]);
expect(clientApi.getClientProfileRechargeCenter).toHaveBeenCalledTimes(2);
expect(useWalletStore.getState().mudPointBalance?.totalPoints).toBe(80);
});
test('retains the last balance when a refresh fails', async () => {
clientApi.getClientProfileRechargeCenter.mockResolvedValueOnce({
walletBalance: 90,
mudPointBalance: detailedBalance(90),
});
await useWalletStore.getState().onWalletBalanceMayHaveChanged();
clientApi.getClientProfileRechargeCenter.mockRejectedValueOnce(
new Error('刷新失败'),
);
await useWalletStore.getState().onWalletBalanceMayHaveChanged();
expect(useWalletStore.getState().mudPointBalance?.totalPoints).toBe(90);
expect(useWalletStore.getState().mudPointBalanceStatus).toBe('error');
expect(useWalletStore.getState().mudPointBalanceError).toBe('刷新失败');
});
test('does not restore a late response after reset', async () => {
let resolveRequest: ((value: unknown) => void) | undefined;
clientApi.getClientProfileRechargeCenter.mockImplementationOnce(
() =>
new Promise((resolve) => {
resolveRequest = resolve;
}),
);
const refresh = useWalletStore.getState().onWalletBalanceMayHaveChanged();
useWalletStore.getState().resetWalletBalance();
resolveRequest?.({
walletBalance: 75,
mudPointBalance: detailedBalance(75),
});
await refresh;
expect(useWalletStore.getState().mudPointBalance).toBeNull();
expect(useWalletStore.getState().mudPointBalanceStatus).toBe('idle');
});
});