重构(平台入口): 平台侧与 AGC 壳改用三池余额,退役 legacy 总额写入通道
- 平台入口与钱包 store 不再从充值中心响应读 walletBalance,合计改为按三池派生 - AGC 账户钱包同步:只透传 mudPointBalance,删掉 RechargeContent 的 walletBalance 排除项 - 删除已无生产调用方的 applyLegacyWalletBalanceSnapshot 与对应测试;明细读取失败不再伪造「只有总额」的中间态 - 图片编辑器与平台入口测试 fixture 改为三池形状,钱包面板断言改用「永久泥点 / 月度泥点」 Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
@@ -12,7 +12,7 @@ import type { HomeCreationType, HomeDraft } from '../view/home';
|
||||
|
||||
export type RechargeContent = Omit<
|
||||
ProfileRechargeCenterResponse,
|
||||
'walletBalance' | 'mudPointBalance'
|
||||
'mudPointBalance'
|
||||
>;
|
||||
export type AgentChatInteractionMode = 'run' | 'chat' | 'goal';
|
||||
export type AgentBackgroundSubmitMode = 'steer' | 'queue';
|
||||
|
||||
@@ -26,7 +26,6 @@ export function useAccountWallet(currentUserId: string) {
|
||||
setWalletOwner,
|
||||
captureWalletBalanceSnapshot,
|
||||
applyWalletBalanceSnapshot,
|
||||
applyLegacyWalletBalanceSnapshot,
|
||||
onWalletBalanceMayHaveChanged,
|
||||
resetWalletBalance,
|
||||
} = useWalletStore();
|
||||
@@ -76,8 +75,6 @@ export function useAccountWallet(currentUserId: string) {
|
||||
const visibleMudPointBalanceError = walletOwnerMatchesCurrentUser
|
||||
? mudPointBalanceError
|
||||
: '';
|
||||
const visibleWalletBalance =
|
||||
visibleMudPointBalance?.totalPoints ?? visibleLegacyWalletBalance;
|
||||
|
||||
useLayoutEffect(() => {
|
||||
currentUserIdRef.current = currentUserId;
|
||||
@@ -165,16 +162,9 @@ export function useAccountWallet(currentUserId: string) {
|
||||
walletSnapshot: ReturnType<typeof captureWalletBalanceSnapshot>,
|
||||
center: ProfileRechargeCenterResponse,
|
||||
) {
|
||||
const {
|
||||
walletBalance,
|
||||
mudPointBalance: nextMudPointBalance,
|
||||
...content
|
||||
} = center;
|
||||
const { mudPointBalance: nextMudPointBalance, ...content } = center;
|
||||
if (nextMudPointBalance && walletSnapshot) {
|
||||
applyWalletBalanceSnapshot(walletSnapshot, nextMudPointBalance);
|
||||
} else if (walletSnapshot && Number.isFinite(walletBalance)) {
|
||||
applyLegacyWalletBalanceSnapshot(walletSnapshot, walletBalance);
|
||||
void onWalletBalanceMayHaveChanged();
|
||||
} else {
|
||||
void onWalletBalanceMayHaveChanged();
|
||||
}
|
||||
@@ -400,13 +390,10 @@ export function useAccountWallet(currentUserId: string) {
|
||||
walletLedgerError: walletUiIsVisible ? walletLedgerError : null,
|
||||
rechargeOpen: walletUiIsVisible && rechargeOpen,
|
||||
rechargeModalCenter:
|
||||
rechargeContent && visibleWalletBalance !== null
|
||||
rechargeContent && visibleMudPointBalance !== null
|
||||
? {
|
||||
...rechargeContent,
|
||||
walletBalance: visibleWalletBalance,
|
||||
...(visibleMudPointBalance
|
||||
? { mudPointBalance: visibleMudPointBalance }
|
||||
: {}),
|
||||
mudPointBalance: visibleMudPointBalance,
|
||||
}
|
||||
: null,
|
||||
rechargeLoading: walletUiIsVisible && rechargeLoading,
|
||||
|
||||
@@ -767,15 +767,13 @@ export function registerClientHomeTests() {
|
||||
it('starts from the client home and opens a project in the same window', async () => {
|
||||
// 账户与钱包的远端真相在 Rust typed command:渲染层只消费投影,不再自己发请求。
|
||||
const rechargeCenter = {
|
||||
walletBalance: 40,
|
||||
mudPointBalance: {
|
||||
totalPoints: 40,
|
||||
permanentPoints: 20,
|
||||
limitedPoints: 0,
|
||||
limitedExpiresAt: null,
|
||||
dailyFreePoints: 20,
|
||||
dailyFreeResetPoints: 20,
|
||||
dailyFreeResetsAt: '2026-07-18T00:00:00.000Z',
|
||||
monthlyPoints: 0,
|
||||
monthlyResetsAt: null,
|
||||
permanentPoints: 20,
|
||||
},
|
||||
products: [],
|
||||
membership: null,
|
||||
@@ -887,19 +885,17 @@ export function registerClientHomeTests() {
|
||||
|
||||
it('opens the shared recharge modal from the wallet and sidebar entries', async () => {
|
||||
const rechargeCenter = {
|
||||
walletBalance: 120,
|
||||
mudPointBalance: {
|
||||
totalPoints: 120,
|
||||
permanentPoints: 100,
|
||||
limitedPoints: 20,
|
||||
limitedExpiresAt: '2026-08-01T00:00:00Z',
|
||||
dailyFreePoints: 0,
|
||||
dailyFreeResetPoints: 20,
|
||||
dailyFreeResetsAt: '2026-07-18T00:00:00Z',
|
||||
monthlyPoints: 20,
|
||||
monthlyResetsAt: '2026-08-01T00:00:00Z',
|
||||
permanentPoints: 100,
|
||||
},
|
||||
membership: {
|
||||
status: 'inactive',
|
||||
tier: 'normal',
|
||||
status: 'normal',
|
||||
plan: 'normal',
|
||||
startedAt: null,
|
||||
expiresAt: null,
|
||||
updatedAt: null,
|
||||
@@ -907,7 +903,9 @@ export function registerClientHomeTests() {
|
||||
cycleResetsAt: null,
|
||||
cycleGrantedPoints: 0,
|
||||
cycleRemainingPoints: 0,
|
||||
cyclePeriodDays: 0,
|
||||
cycleIndex: 1,
|
||||
cycleCount: 1,
|
||||
cycleKind: 'monthly',
|
||||
},
|
||||
pointProducts: [
|
||||
{
|
||||
@@ -920,15 +918,9 @@ export function registerClientHomeTests() {
|
||||
durationDays: 0,
|
||||
badgeLabel: '',
|
||||
description: '',
|
||||
tier: 'normal',
|
||||
membershipPeriodPoints: 0,
|
||||
membershipPeriodDays: 0,
|
||||
membershipQueueLimit: 0,
|
||||
membershipDiscountBps: 0,
|
||||
},
|
||||
],
|
||||
membershipProducts: [],
|
||||
benefits: [],
|
||||
membershipPlans: [],
|
||||
latestOrder: null,
|
||||
hasPointsRecharged: true,
|
||||
};
|
||||
@@ -939,10 +931,9 @@ export function registerClientHomeTests() {
|
||||
rechargePaid
|
||||
? {
|
||||
...rechargeCenter,
|
||||
walletBalance: 180,
|
||||
mudPointBalance: {
|
||||
...rechargeCenter.mudPointBalance,
|
||||
totalPoints: 180,
|
||||
monthlyPoints: 20,
|
||||
permanentPoints: 160,
|
||||
},
|
||||
}
|
||||
@@ -1003,10 +994,9 @@ export function registerClientHomeTests() {
|
||||
},
|
||||
center: {
|
||||
...rechargeCenter,
|
||||
walletBalance: 180,
|
||||
mudPointBalance: {
|
||||
...rechargeCenter.mudPointBalance,
|
||||
totalPoints: 180,
|
||||
monthlyPoints: 20,
|
||||
permanentPoints: 160,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -19,10 +19,9 @@ 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',
|
||||
monthlyPoints: Math.min(20, totalPoints),
|
||||
monthlyResetsAt: '2026-08-01T00:00:00Z',
|
||||
dailyFreePoints: 0,
|
||||
dailyFreeResetPoints: 20,
|
||||
dailyFreeResetsAt: '2026-07-18T00:00:00Z',
|
||||
@@ -39,7 +38,6 @@ describe('useWalletStore', () => {
|
||||
test('keeps mudPointBalance as the authoritative detailed balance state', async () => {
|
||||
const mudPointBalance = detailedBalance(120);
|
||||
clientApi.getClientProfileRechargeCenter.mockResolvedValue({
|
||||
walletBalance: 120,
|
||||
mudPointBalance,
|
||||
});
|
||||
const { result } = renderHook(() => useWalletStore());
|
||||
@@ -48,8 +46,7 @@ describe('useWalletStore', () => {
|
||||
await result.current.onWalletBalanceMayHaveChanged();
|
||||
});
|
||||
|
||||
expect(result.current.mudPointBalance).toEqual(mudPointBalance);
|
||||
expect(result.current.legacyWalletBalance).toBe(120);
|
||||
expect(result.current.mudPointBalance).toMatchObject(mudPointBalance);
|
||||
expect(result.current.mudPointBalanceStatus).toBe('ready');
|
||||
expect(result.current.mudPointBalanceError).toBe('');
|
||||
expect(Object.keys(result.current).sort()).toEqual([
|
||||
@@ -81,7 +78,9 @@ describe('useWalletStore', () => {
|
||||
.getState()
|
||||
.applyWalletBalanceSnapshot(snapshot!, mudPointBalance);
|
||||
|
||||
expect(useWalletStore.getState().mudPointBalance).toEqual(mudPointBalance);
|
||||
expect(useWalletStore.getState().mudPointBalance).toMatchObject(
|
||||
mudPointBalance,
|
||||
);
|
||||
expect(useWalletStore.getState().mudPointBalanceStatus).toBe('ready');
|
||||
expect(useWalletStore.getState().mudPointBalanceError).toBe('');
|
||||
});
|
||||
@@ -106,20 +105,20 @@ describe('useWalletStore', () => {
|
||||
rejectRequest?.(new Error('过期请求失败'));
|
||||
await refresh;
|
||||
|
||||
expect(useWalletStore.getState().mudPointBalance).toEqual(mudPointBalance);
|
||||
expect(useWalletStore.getState().mudPointBalance).toMatchObject(
|
||||
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,
|
||||
});
|
||||
clientApi.getClientProfileRechargeCenter.mockResolvedValue({});
|
||||
|
||||
await useWalletStore.getState().onWalletBalanceMayHaveChanged();
|
||||
|
||||
expect(useWalletStore.getState().mudPointBalance).toBeNull();
|
||||
expect(useWalletStore.getState().legacyWalletBalance).toBe(120);
|
||||
expect(useWalletStore.getState().legacyWalletBalance).toBeNull();
|
||||
expect(useWalletStore.getState().mudPointBalanceStatus).toBe('error');
|
||||
expect(useWalletStore.getState().mudPointBalanceError).toBe(
|
||||
'泥点明细读取失败',
|
||||
@@ -129,13 +128,14 @@ describe('useWalletStore', () => {
|
||||
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);
|
||||
expect(useWalletStore.getState().mudPointBalance).toMatchObject(
|
||||
mudPointBalance,
|
||||
);
|
||||
});
|
||||
|
||||
test('discards an outdated response and performs one trailing refresh', async () => {
|
||||
@@ -148,7 +148,6 @@ describe('useWalletStore', () => {
|
||||
}),
|
||||
)
|
||||
.mockResolvedValueOnce({
|
||||
walletBalance: 80,
|
||||
mudPointBalance: detailedBalance(80),
|
||||
});
|
||||
|
||||
@@ -159,7 +158,6 @@ describe('useWalletStore', () => {
|
||||
.getState()
|
||||
.onWalletBalanceMayHaveChanged();
|
||||
resolveFirst?.({
|
||||
walletBalance: 120,
|
||||
mudPointBalance: detailedBalance(120),
|
||||
});
|
||||
await Promise.all([firstRefresh, trailingRefresh]);
|
||||
@@ -170,7 +168,6 @@ describe('useWalletStore', () => {
|
||||
|
||||
test('retains the last balance when a refresh fails', async () => {
|
||||
clientApi.getClientProfileRechargeCenter.mockResolvedValueOnce({
|
||||
walletBalance: 90,
|
||||
mudPointBalance: detailedBalance(90),
|
||||
});
|
||||
await useWalletStore.getState().onWalletBalanceMayHaveChanged();
|
||||
@@ -199,7 +196,6 @@ describe('useWalletStore', () => {
|
||||
const refresh = useWalletStore.getState().onWalletBalanceMayHaveChanged();
|
||||
useWalletStore.getState().resetWalletBalance();
|
||||
resolveRequest?.({
|
||||
walletBalance: 75,
|
||||
mudPointBalance: detailedBalance(75),
|
||||
});
|
||||
await refresh;
|
||||
@@ -248,7 +244,6 @@ describe('useWalletStore', () => {
|
||||
|
||||
test('binds the account owner and refreshes again when the window regains focus', async () => {
|
||||
clientApi.getClientProfileRechargeCenter.mockResolvedValue({
|
||||
walletBalance: 18,
|
||||
mudPointBalance: detailedBalance(18),
|
||||
});
|
||||
|
||||
@@ -267,16 +262,14 @@ describe('useWalletStore', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('keeps a successful legacy recharge total when the breakdown refresh fails', async () => {
|
||||
test('keeps the last known good breakdown when the recharge center omits it', async () => {
|
||||
clientApi.getClientProfileRechargeCenter.mockResolvedValueOnce({
|
||||
walletBalance: 90,
|
||||
mudPointBalance: detailedBalance(90),
|
||||
});
|
||||
const { result } = renderHook(() => useAccountWallet('user-a'));
|
||||
await act(async () => undefined);
|
||||
clientApi.getClientProfileRechargeCenter
|
||||
.mockResolvedValueOnce({
|
||||
walletBalance: 37,
|
||||
membership: null,
|
||||
pointProducts: [],
|
||||
membershipProducts: [],
|
||||
@@ -291,14 +284,13 @@ describe('useWalletStore', () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(result.current.legacyWalletBalance).toBe(37);
|
||||
expect(result.current.mudPointBalance).toBeNull();
|
||||
expect(result.current.mudPointBalance?.totalPoints).toBe(90);
|
||||
expect(result.current.mudPointBalanceStatus).toBe('error');
|
||||
});
|
||||
|
||||
test('keeps ledger loading independent from the recharge lifecycle', async () => {
|
||||
let resolveLedger!: (value: { entries: [] }) => void;
|
||||
clientApi.getClientProfileRechargeCenter.mockResolvedValue({
|
||||
walletBalance: 18,
|
||||
mudPointBalance: detailedBalance(18),
|
||||
});
|
||||
clientApi.getClientProfileWalletLedger.mockImplementation(
|
||||
@@ -326,7 +318,6 @@ describe('useWalletStore', () => {
|
||||
|
||||
test('hides account-owned dialogs and data when the account changes', async () => {
|
||||
clientApi.getClientProfileRechargeCenter.mockResolvedValue({
|
||||
walletBalance: 18,
|
||||
mudPointBalance: detailedBalance(18),
|
||||
});
|
||||
clientApi.getClientProfileWalletLedger.mockResolvedValue({ entries: [] });
|
||||
|
||||
@@ -290,28 +290,15 @@ describe('createProfileWalletStore', () => {
|
||||
expect(store.getState().mudPointBalance?.permanentPoints).toBe(90);
|
||||
});
|
||||
|
||||
test('applies an owner-scoped legacy mutation snapshot atomically', () => {
|
||||
test('rejects a snapshot from another owner', () => {
|
||||
const store = createProfileWalletStore({ getRechargeCenter: vi.fn() });
|
||||
store.getState().setWalletOwner('user-a');
|
||||
const structuredSnapshot = store
|
||||
.getState()
|
||||
.captureWalletBalanceSnapshot('user-a');
|
||||
store
|
||||
.getState()
|
||||
.applyWalletBalanceSnapshot(structuredSnapshot!, balance(90));
|
||||
const legacySnapshot = store
|
||||
.getState()
|
||||
.captureWalletBalanceSnapshot('user-a');
|
||||
const snapshot = store.getState().captureWalletBalanceSnapshot('user-b');
|
||||
|
||||
expect(
|
||||
store.getState().applyLegacyWalletBalanceSnapshot(legacySnapshot!, 37),
|
||||
).toBe(true);
|
||||
expect(store.getState()).toMatchObject({
|
||||
legacyWalletBalance: 37,
|
||||
mudPointBalance: null,
|
||||
mudPointBalanceStatus: 'ready',
|
||||
mudPointBalanceError: '',
|
||||
});
|
||||
store.getState().applyWalletBalanceSnapshot(snapshot!, balance(37)),
|
||||
).toBe(false);
|
||||
expect(store.getState().mudPointBalance).toBeNull();
|
||||
});
|
||||
|
||||
test('rejects a snapshot after switching away from and back to the same owner', () => {
|
||||
|
||||
@@ -36,11 +36,7 @@ export type ProfileWalletStore = {
|
||||
) => ProfileWalletBalanceSnapshot | null;
|
||||
applyWalletBalanceSnapshot: (
|
||||
snapshot: ProfileWalletBalanceSnapshot,
|
||||
balance: ProfileMudPointBalance | ProfileMudPointBalanceView,
|
||||
) => boolean;
|
||||
applyLegacyWalletBalanceSnapshot: (
|
||||
snapshot: ProfileWalletBalanceSnapshot,
|
||||
balance: number,
|
||||
balance: ProfileMudPointBalance,
|
||||
) => boolean;
|
||||
onWalletBalanceMayHaveChanged: () => Promise<void>;
|
||||
resetWalletBalance: () => void;
|
||||
@@ -132,32 +128,6 @@ export function createProfileWalletStore(
|
||||
});
|
||||
return true;
|
||||
},
|
||||
applyLegacyWalletBalanceSnapshot: (snapshot, balance) => {
|
||||
if (
|
||||
!Number.isFinite(balance) ||
|
||||
get().ownerUserId !== snapshot.ownerUserId ||
|
||||
ownerVersion !== snapshot.ownerVersion ||
|
||||
snapshot.invalidationVersion < refreshVersion ||
|
||||
snapshot.invalidationVersion < settledRefreshVersion ||
|
||||
snapshot.operationSequence < latestAppliedOperationSequence
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
invalidateActiveRefresh();
|
||||
latestAppliedOperationSequence = Math.max(
|
||||
latestAppliedOperationSequence,
|
||||
snapshot.operationSequence,
|
||||
);
|
||||
settledRefreshVersion = snapshot.invalidationVersion;
|
||||
set({
|
||||
mudPointBalance: null,
|
||||
legacyWalletBalance: balance,
|
||||
mudPointBalanceStatus: 'ready',
|
||||
mudPointBalanceError: '',
|
||||
});
|
||||
return true;
|
||||
},
|
||||
onWalletBalanceMayHaveChanged: () => {
|
||||
refreshVersion += 1;
|
||||
if (activeRefresh) {
|
||||
|
||||
@@ -339,19 +339,18 @@ describe('ImageCanvasEditorView', () => {
|
||||
updatedAt: null,
|
||||
});
|
||||
getPlatformProfileRechargeCenterMock.mockResolvedValue({
|
||||
walletBalance: 1234,
|
||||
mudPointBalance: {
|
||||
totalPoints: 1234,
|
||||
permanentPoints: 1000,
|
||||
limitedPoints: 214,
|
||||
limitedExpiresAt: '2026-07-31T16:00:00Z',
|
||||
monthlyPoints: 214,
|
||||
monthlyResetsAt: '2026-07-31T16:00:00Z',
|
||||
dailyFreePoints: 20,
|
||||
dailyFreeResetPoints: 20,
|
||||
dailyFreeResetsAt: '2026-07-12T16:00:00Z',
|
||||
},
|
||||
membership: {
|
||||
status: 'normal',
|
||||
tier: 'normal',
|
||||
plan: 'normal',
|
||||
startedAt: null,
|
||||
expiresAt: null,
|
||||
updatedAt: null,
|
||||
@@ -359,11 +358,12 @@ describe('ImageCanvasEditorView', () => {
|
||||
cycleResetsAt: null,
|
||||
cycleGrantedPoints: 0,
|
||||
cycleRemainingPoints: 0,
|
||||
cyclePeriodDays: 30,
|
||||
cycleIndex: 1,
|
||||
cycleCount: 1,
|
||||
cycleKind: 'monthly',
|
||||
},
|
||||
pointProducts: [],
|
||||
membershipProducts: [],
|
||||
benefits: [],
|
||||
membershipPlans: [],
|
||||
latestOrder: null,
|
||||
hasPointsRecharged: false,
|
||||
});
|
||||
@@ -635,10 +635,9 @@ describe('ImageCanvasEditorView', () => {
|
||||
usePlatformWalletStore
|
||||
.getState()
|
||||
.applyWalletBalanceSnapshot(walletSnapshot!, {
|
||||
totalPoints: 1234,
|
||||
permanentPoints: 1000,
|
||||
limitedPoints: 214,
|
||||
limitedExpiresAt: '2026-07-31T16:00:00Z',
|
||||
monthlyPoints: 214,
|
||||
monthlyResetsAt: '2026-07-31T16:00:00Z',
|
||||
dailyFreePoints: 20,
|
||||
dailyFreeResetPoints: 20,
|
||||
dailyFreeResetsAt: '2026-07-12T16:00:00Z',
|
||||
@@ -703,18 +702,12 @@ describe('ImageCanvasEditorView', () => {
|
||||
expect(openAccountModal).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('shows the owner-matched legacy wallet total without inventing breakdown rows', async () => {
|
||||
it('shows the breakdown read failure without inventing rows', async () => {
|
||||
const user = userEvent.setup();
|
||||
usePlatformWalletStore.getState().setWalletOwner('user-1');
|
||||
const walletSnapshot = usePlatformWalletStore
|
||||
.getState()
|
||||
.captureWalletBalanceSnapshot('user-1');
|
||||
usePlatformWalletStore
|
||||
.getState()
|
||||
.applyLegacyWalletBalanceSnapshot(walletSnapshot!, 37);
|
||||
getPlatformProfileRechargeCenterMock.mockResolvedValue({
|
||||
walletBalance: 37,
|
||||
});
|
||||
getPlatformProfileRechargeCenterMock.mockRejectedValue(
|
||||
new Error('network down'),
|
||||
);
|
||||
render(
|
||||
<AuthUiContext.Provider
|
||||
value={createAuthValue({
|
||||
@@ -736,7 +729,7 @@ describe('ImageCanvasEditorView', () => {
|
||||
);
|
||||
|
||||
const walletButton = await screen.findByRole('button', {
|
||||
name: '泥点 37',
|
||||
name: '泥点 --',
|
||||
});
|
||||
await user.hover(walletButton);
|
||||
|
||||
@@ -745,7 +738,7 @@ describe('ImageCanvasEditorView', () => {
|
||||
});
|
||||
expect(within(details).getByText('泥点明细读取失败')).toBeTruthy();
|
||||
expect(within(details).queryByText('充值中心响应缺少泥点余额')).toBeNull();
|
||||
expect(within(details).queryByText('不限时泥点')).toBeNull();
|
||||
expect(within(details).queryByText('永久泥点')).toBeNull();
|
||||
expect(within(details).queryByText('每日免费泥点')).toBeNull();
|
||||
});
|
||||
|
||||
@@ -786,10 +779,9 @@ describe('ImageCanvasEditorView', () => {
|
||||
usePlatformWalletStore
|
||||
.getState()
|
||||
.applyWalletBalanceSnapshot(walletSnapshot!, {
|
||||
totalPoints: 1234,
|
||||
permanentPoints: 1000,
|
||||
limitedPoints: 0,
|
||||
limitedExpiresAt: null,
|
||||
monthlyPoints: 0,
|
||||
monthlyResetsAt: null,
|
||||
dailyFreePoints: 234,
|
||||
dailyFreeResetPoints: 20,
|
||||
dailyFreeResetsAt: '2026-07-12T16:00:00Z',
|
||||
@@ -823,8 +815,8 @@ describe('ImageCanvasEditorView', () => {
|
||||
const details = await screen.findByRole('dialog', {
|
||||
name: '泥点账户详情',
|
||||
});
|
||||
expect(within(details).getByText('不限时泥点')).toBeTruthy();
|
||||
expect(within(details).queryByText('限时泥点')).toBeNull();
|
||||
expect(within(details).getByText('永久泥点')).toBeTruthy();
|
||||
expect(within(details).getByText('月度泥点')).toBeTruthy();
|
||||
expect(within(details).getByText('每日免费泥点')).toBeTruthy();
|
||||
|
||||
await user.click(within(details).getByRole('button', { name: '使用详情' }));
|
||||
|
||||
@@ -17,8 +17,8 @@ import { useImageCanvasContextStore } from './useImageCanvasContextStore.ts';
|
||||
const walletBreakdown = {
|
||||
totalPoints: 1_234,
|
||||
permanentPoints: 1_000,
|
||||
limitedPoints: 214,
|
||||
limitedExpiresAt: '2026-07-31T16:00:00Z',
|
||||
monthlyPoints: 214,
|
||||
monthlyResetsAt: '2026-07-31T16:00:00Z',
|
||||
dailyFreePoints: 20,
|
||||
dailyFreeResetPoints: 20,
|
||||
dailyFreeResetsAt: '2026-07-12T16:00:00Z',
|
||||
@@ -105,10 +105,6 @@ describe('ImageCanvasTopbarView', () => {
|
||||
const user = userEvent.setup();
|
||||
const props = renderTopbar({
|
||||
walletBalance: 12_345,
|
||||
walletBreakdown: {
|
||||
...walletBreakdown,
|
||||
totalPoints: 12_345,
|
||||
},
|
||||
});
|
||||
const walletChip = screen.getByRole('button', {
|
||||
name: '泥点 12,345',
|
||||
@@ -118,8 +114,8 @@ describe('ImageCanvasTopbarView', () => {
|
||||
|
||||
await user.hover(walletChip);
|
||||
const details = screen.getByRole('dialog', { name: '泥点账户详情' });
|
||||
expect(within(details).getByText('不限时泥点')).toBeTruthy();
|
||||
expect(within(details).queryByText('限时泥点')).toBeNull();
|
||||
expect(within(details).getByText('永久泥点')).toBeTruthy();
|
||||
expect(within(details).getByText('月度泥点')).toBeTruthy();
|
||||
expect(within(details).getByText('每日免费泥点')).toBeTruthy();
|
||||
|
||||
await user.click(within(details).getByRole('button', { name: '使用详情' }));
|
||||
|
||||
@@ -77,8 +77,8 @@ describe('PlatformActiveProfileView', () => {
|
||||
mudPointBalance={{
|
||||
totalPoints: 108,
|
||||
permanentPoints: 88,
|
||||
limitedPoints: 0,
|
||||
limitedExpiresAt: null,
|
||||
monthlyPoints: 0,
|
||||
monthlyResetsAt: null,
|
||||
dailyFreePoints: 20,
|
||||
dailyFreeResetPoints: 20,
|
||||
dailyFreeResetsAt: '2026-08-04T00:00:00+08:00',
|
||||
|
||||
@@ -15,7 +15,7 @@ import { useCallback, useRef, useState } from 'react';
|
||||
import type {
|
||||
AuthUser,
|
||||
ProfileDashboardSummary,
|
||||
ProfileMudPointBalance,
|
||||
ProfileMudPointBalanceView,
|
||||
} from '@/packages/shared/src';
|
||||
|
||||
import profileClockImage from '../../../media/profile/_Image (1).png';
|
||||
@@ -66,7 +66,7 @@ type PlatformActiveProfileViewProps = {
|
||||
isLoadingDashboard: boolean;
|
||||
isLoadingWalletBalance: boolean;
|
||||
legacyWalletBalance: number | null;
|
||||
mudPointBalance: ProfileMudPointBalance | null;
|
||||
mudPointBalance: ProfileMudPointBalanceView | null;
|
||||
onLogin: () => void;
|
||||
onOpenApiKeys: () => void;
|
||||
onOpenCommunity: () => void;
|
||||
@@ -248,7 +248,7 @@ function formatDashboardCount(value: number) {
|
||||
}
|
||||
|
||||
function formatWalletBalance(
|
||||
balance: ProfileMudPointBalance | null,
|
||||
balance: ProfileMudPointBalanceView | null,
|
||||
legacyBalance: number | null,
|
||||
isLoading: boolean,
|
||||
) {
|
||||
|
||||
@@ -41,7 +41,13 @@ const profileClientMock = vi.hoisted(() => ({
|
||||
|
||||
const profileCenterMock = vi.hoisted(() => ({
|
||||
isWalletLedgerOpen: false,
|
||||
rechargeCenter: null as { walletBalance: number } | null,
|
||||
rechargeCenter: null as {
|
||||
mudPointBalance?: {
|
||||
dailyFreePoints: number;
|
||||
monthlyPoints: number;
|
||||
permanentPoints: number;
|
||||
};
|
||||
} | null,
|
||||
}));
|
||||
|
||||
const gameDistributionMock = vi.hoisted(() => ({
|
||||
@@ -245,12 +251,10 @@ describe('PlatformEntryActiveFlowShell', () => {
|
||||
updatedAt: null,
|
||||
});
|
||||
profileClientMock.getPlatformProfileRechargeCenter.mockResolvedValue({
|
||||
walletBalance: 888,
|
||||
mudPointBalance: {
|
||||
totalPoints: 207,
|
||||
permanentPoints: 180,
|
||||
limitedPoints: 7,
|
||||
limitedExpiresAt: '2026-08-31T16:00:00Z',
|
||||
monthlyPoints: 7,
|
||||
monthlyResetsAt: '2026-08-31T16:00:00Z',
|
||||
dailyFreePoints: 20,
|
||||
dailyFreeResetPoints: 20,
|
||||
dailyFreeResetsAt: '2026-08-04T00:00:00+08:00',
|
||||
@@ -294,7 +298,14 @@ describe('PlatformEntryActiveFlowShell', () => {
|
||||
authUiMock.value.canAccessProtectedData = true;
|
||||
profileClientMock.getPlatformProfileDashboard.mockResolvedValue(null);
|
||||
profileClientMock.getPlatformProfileRechargeCenter.mockResolvedValue({
|
||||
walletBalance: 37,
|
||||
mudPointBalance: {
|
||||
permanentPoints: 37,
|
||||
monthlyPoints: 0,
|
||||
monthlyResetsAt: null,
|
||||
dailyFreePoints: 0,
|
||||
dailyFreeResetPoints: 20,
|
||||
dailyFreeResetsAt: '2026-08-04T00:00:00+08:00',
|
||||
},
|
||||
});
|
||||
profileCenterMock.isWalletLedgerOpen = true;
|
||||
|
||||
@@ -338,10 +349,9 @@ describe('PlatformEntryActiveFlowShell', () => {
|
||||
profileClientMock.getPlatformProfileDashboard.mockResolvedValue(null);
|
||||
profileClientMock.getPlatformProfileRechargeCenter.mockResolvedValue({
|
||||
mudPointBalance: {
|
||||
totalPoints: 66,
|
||||
permanentPoints: 66,
|
||||
limitedPoints: 0,
|
||||
limitedExpiresAt: null,
|
||||
monthlyPoints: 0,
|
||||
monthlyResetsAt: null,
|
||||
dailyFreePoints: 0,
|
||||
dailyFreeResetPoints: 20,
|
||||
dailyFreeResetsAt: '2026-08-04T00:00:00+08:00',
|
||||
@@ -379,10 +389,9 @@ describe('PlatformEntryActiveFlowShell', () => {
|
||||
});
|
||||
resolveNextOwner({
|
||||
mudPointBalance: {
|
||||
totalPoints: 67,
|
||||
permanentPoints: 67,
|
||||
limitedPoints: 0,
|
||||
limitedExpiresAt: null,
|
||||
monthlyPoints: 0,
|
||||
monthlyResetsAt: null,
|
||||
dailyFreePoints: 0,
|
||||
dailyFreeResetPoints: 20,
|
||||
dailyFreeResetsAt: '2026-08-04T00:00:00+08:00',
|
||||
@@ -406,10 +415,9 @@ describe('PlatformEntryActiveFlowShell', () => {
|
||||
profileClientMock.getPlatformProfileDashboard.mockResolvedValue(null);
|
||||
profileClientMock.getPlatformProfileRechargeCenter.mockResolvedValue({
|
||||
mudPointBalance: {
|
||||
totalPoints: 10,
|
||||
permanentPoints: 10,
|
||||
limitedPoints: 0,
|
||||
limitedExpiresAt: null,
|
||||
monthlyPoints: 0,
|
||||
monthlyResetsAt: null,
|
||||
dailyFreePoints: 0,
|
||||
dailyFreeResetPoints: 20,
|
||||
dailyFreeResetsAt: '2026-08-04T00:00:00+08:00',
|
||||
|
||||
@@ -22,6 +22,7 @@ import { PlatformProfileRechargeModal } from '../../../packages/shared/src/compo
|
||||
import { PlatformProfileReferralModal } from '../../../packages/shared/src/components/PlatformProfileReferralModal';
|
||||
import { PlatformProfileWalletLedgerModal } from '../../../packages/shared/src/components/PlatformProfileWalletLedgerModal';
|
||||
import type { ProfileDashboardSummary } from '../../../packages/shared/src/contracts/runtime';
|
||||
import { resolveProfileMudPointDisplayTotal } from '../../../packages/shared/src/utils/mudPoints';
|
||||
import {
|
||||
hasAppHistoryBackEntry,
|
||||
pushAppHistoryPath,
|
||||
@@ -376,7 +377,9 @@ export function PlatformEntryFlowShellImpl({
|
||||
});
|
||||
const legacyWalletBalance = walletOwnerMatchesCurrentUser
|
||||
? (storedLegacyWalletBalance ??
|
||||
profileCenter.rechargeCenter?.walletBalance ??
|
||||
resolveProfileMudPointDisplayTotal(
|
||||
profileCenter.rechargeCenter?.mudPointBalance,
|
||||
) ??
|
||||
null)
|
||||
: null;
|
||||
|
||||
|
||||
+11
-10
@@ -32,10 +32,9 @@ function deferred<T>() {
|
||||
|
||||
function mudPointBalance(totalPoints: number) {
|
||||
return {
|
||||
totalPoints,
|
||||
permanentPoints: totalPoints,
|
||||
limitedPoints: 0,
|
||||
limitedExpiresAt: null,
|
||||
monthlyPoints: 0,
|
||||
monthlyResetsAt: null,
|
||||
dailyFreePoints: 0,
|
||||
dailyFreeResetPoints: 20,
|
||||
dailyFreeResetsAt: '2026-08-07T00:00:00+08:00',
|
||||
@@ -52,7 +51,6 @@ function nativeRechargeResponse(totalPoints = 80, orderId = 'order-a') {
|
||||
expirationCheckedAt: null,
|
||||
},
|
||||
center: {
|
||||
walletBalance: totalPoints,
|
||||
mudPointBalance: mudPointBalance(totalPoints),
|
||||
},
|
||||
wechatNativePayment: {
|
||||
@@ -76,14 +74,13 @@ describe('usePlatformProfileCenterController recharge fallback', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('keeps the response legacy balance when no shared snapshot exists', async () => {
|
||||
test('keeps the response three-pool balance when no shared snapshot exists', async () => {
|
||||
usePlatformWalletStore.getState().setWalletOwner('user-a');
|
||||
profileClientMocks.getPlatformProfileRechargeCenter.mockResolvedValue({
|
||||
walletBalance: 37,
|
||||
mudPointBalance: mudPointBalance(37),
|
||||
membership: null,
|
||||
pointProducts: [],
|
||||
membershipProducts: [],
|
||||
benefits: [],
|
||||
membershipPlans: [],
|
||||
latestOrder: null,
|
||||
hasPointsRecharged: false,
|
||||
});
|
||||
@@ -92,7 +89,9 @@ describe('usePlatformProfileCenterController recharge fallback', () => {
|
||||
act(() => result.current.loadRechargeCenter());
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.rechargeCenter?.walletBalance).toBe(37);
|
||||
expect(
|
||||
result.current.rechargeCenter?.mudPointBalance?.permanentPoints,
|
||||
).toBe(37);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -198,7 +197,9 @@ describe('usePlatformProfileCenterController recharge fallback', () => {
|
||||
expect(result.current.submittingRechargeProductId).toBeNull();
|
||||
expect(result.current.nativeWechatPayment?.orderId).toBe('order-b');
|
||||
expect(result.current.rechargePaymentResult).toBeNull();
|
||||
expect(result.current.rechargeCenter?.walletBalance).toBe(90);
|
||||
expect(
|
||||
result.current.rechargeCenter?.mudPointBalance?.permanentPoints,
|
||||
).toBe(90);
|
||||
expect(usePlatformWalletStore.getState()).toMatchObject({
|
||||
ownerUserId: 'user-b',
|
||||
mudPointBalance: mudPointBalance(90),
|
||||
|
||||
@@ -19,19 +19,17 @@ vi.mock('../../services/platform-entry/platformProfileClient', async () => {
|
||||
});
|
||||
|
||||
const rechargeCenter: ProfileRechargeCenterResponse = {
|
||||
walletBalance: 100,
|
||||
mudPointBalance: {
|
||||
totalPoints: 100,
|
||||
permanentPoints: 100,
|
||||
limitedPoints: 0,
|
||||
limitedExpiresAt: null,
|
||||
monthlyPoints: 0,
|
||||
monthlyResetsAt: null,
|
||||
dailyFreePoints: 0,
|
||||
dailyFreeResetPoints: 0,
|
||||
dailyFreeResetsAt: null,
|
||||
},
|
||||
membership: {
|
||||
status: 'normal',
|
||||
tier: 'normal',
|
||||
plan: 'normal',
|
||||
startedAt: null,
|
||||
expiresAt: null,
|
||||
updatedAt: null,
|
||||
@@ -39,11 +37,12 @@ const rechargeCenter: ProfileRechargeCenterResponse = {
|
||||
cycleResetsAt: null,
|
||||
cycleGrantedPoints: 0,
|
||||
cycleRemainingPoints: 0,
|
||||
cyclePeriodDays: 30,
|
||||
cycleIndex: 1,
|
||||
cycleCount: 1,
|
||||
cycleKind: 'monthly',
|
||||
},
|
||||
pointProducts: [],
|
||||
membershipProducts: [],
|
||||
benefits: [],
|
||||
membershipPlans: [],
|
||||
latestOrder: null,
|
||||
hasPointsRecharged: false,
|
||||
};
|
||||
|
||||
@@ -1864,8 +1864,6 @@ export function usePlatformProfileCenterController({
|
||||
}
|
||||
return {
|
||||
...rechargeCenter,
|
||||
walletBalance:
|
||||
mudPointBalance?.totalPoints ?? rechargeCenter.walletBalance,
|
||||
mudPointBalance: mudPointBalance ?? rechargeCenter.mudPointBalance,
|
||||
};
|
||||
}, [mudPointBalance, rechargeCenter, walletOwnerMatchesCurrentUser]);
|
||||
|
||||
@@ -22,12 +22,10 @@ beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
usePlatformWalletStore.getState().resetWalletBalance();
|
||||
profileClientMocks.getPlatformProfileRechargeCenter.mockResolvedValue({
|
||||
walletBalance: 42,
|
||||
mudPointBalance: {
|
||||
totalPoints: 42,
|
||||
permanentPoints: 42,
|
||||
limitedPoints: 0,
|
||||
limitedExpiresAt: null,
|
||||
monthlyPoints: 0,
|
||||
monthlyResetsAt: null,
|
||||
dailyFreePoints: 0,
|
||||
dailyFreeResetPoints: 20,
|
||||
dailyFreeResetsAt: '2026-08-08T00:00:00+08:00',
|
||||
|
||||
Reference in New Issue
Block a user