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>
This commit was merged in pull request #86.
This commit is contained in:
2026-07-20 15:03:23 +08:00
committed by 段舒康
parent 0ee8e664fa
commit 125927e0e8
49 changed files with 4194 additions and 1362 deletions
@@ -3,31 +3,14 @@ import { expect, test } from 'vitest';
import type {
ProfileMembership,
ProfileRechargeProduct,
ProfileWalletLedgerEntry,
} from '../../../packages/shared/src/contracts/runtime';
import {
buildMembershipCycleLabel,
buildMembershipLabel,
buildRechargeProductValueLabel,
buildWalletLedgerPresentation,
formatRechargePrice,
formatWalletLedgerAmount,
getWalletLedgerSourceLabel,
} from './rpgEntryProfileFundsViewModel';
function buildLedgerEntry(
overrides: Partial<ProfileWalletLedgerEntry> = {},
): ProfileWalletLedgerEntry {
return {
id: 'ledger-1',
amountDelta: 30,
balanceAfter: 80,
sourceType: 'invite_invitee_reward',
createdAt: '2026-06-03T00:00:00.000Z',
...overrides,
};
}
function buildRechargeProduct(
overrides: Partial<ProfileRechargeProduct> = {},
): ProfileRechargeProduct {
@@ -68,89 +51,6 @@ function buildMembership(
};
}
test('profile funds ViewModel formats ledger amount labels', () => {
expect(formatWalletLedgerAmount(-1)).toBe('-1');
expect(formatWalletLedgerAmount(0)).toBe('0');
expect(formatWalletLedgerAmount(30)).toBe('+30');
});
test('profile funds ViewModel resolves ledger source labels with raw fallback', () => {
expect(getWalletLedgerSourceLabel('asset_operation_consume')).toBe(
'资产操作消耗',
);
expect(getWalletLedgerSourceLabel('puzzle_author_incentive_claim')).toBe(
'拼图作者奖励',
);
expect(getWalletLedgerSourceLabel('recharge_refund_recovery')).toBe(
'充值退款追回',
);
expect(getWalletLedgerSourceLabel('future_source')).toBe('future_source');
expect(getWalletLedgerSourceLabel('')).toBe('未知来源');
});
test('profile funds ViewModel builds wallet ledger presentation', () => {
const incomeEntry = buildLedgerEntry({
id: 'ledger-income',
amountDelta: 30,
balanceAfter: 80,
sourceType: 'puzzle_author_incentive_claim',
});
const outcomeEntry = buildLedgerEntry({
id: 'ledger-outcome',
amountDelta: -1,
balanceAfter: 79,
sourceType: 'asset_operation_consume',
});
const recoveryEntry = buildLedgerEntry({
id: 'ledger-recovery',
amountDelta: -30,
balanceAfter: 49,
sourceType: 'recharge_refund_recovery',
});
expect(
buildWalletLedgerPresentation(
{ entries: [incomeEntry, outcomeEntry, recoveryEntry] },
12,
),
).toEqual({
balance: 80,
balanceLabel: '80泥点',
entries: [
{
amountLabel: '+30',
balanceLabel: '余额 80',
createdAt: '2026-06-03T00:00:00.000Z',
id: 'ledger-income',
isIncome: true,
sourceLabel: '拼图作者奖励',
},
{
amountLabel: '-1',
balanceLabel: '余额 79',
createdAt: '2026-06-03T00:00:00.000Z',
id: 'ledger-outcome',
isIncome: false,
sourceLabel: '资产操作消耗',
},
{
amountLabel: '-30',
balanceLabel: '余额 49',
createdAt: '2026-06-03T00:00:00.000Z',
id: 'ledger-recovery',
isIncome: false,
sourceLabel: '充值退款追回',
},
],
});
expect(buildWalletLedgerPresentation({ entries: [] }, 12)).toEqual({
balance: 12,
balanceLabel: '12泥点',
entries: [],
});
});
test('profile funds ViewModel formats recharge product and membership labels', () => {
expect(formatRechargePrice(600)).toBe('¥6');
expect(formatRechargePrice(650)).toBe('¥6.50');