import { describe, expect, it, beforeEach } from 'vitest'; import { useLedgerStore } from '../src/store/ledgerStore'; import { MemoryBackend } from '../src/domain/mobileStore'; import type { LedgerFile } from '../src/domain/types'; const sampleLedger: LedgerFile = { path: 'main.bean', content: `option "operating_currency" "CNY" 2026-01-01 open Assets:Alipay CNY 2026-01-01 open Expenses:Food CNY 2026-01-01 open Expenses:Uncategorized CNY 2026-01-01 open Income:Uncategorized CNY include "mobile.bean" `, }; const usdLedger: LedgerFile = { path: 'main.bean', content: `option "operating_currency" "USD" 2026-01-01 open Assets:Bank USD 2026-01-01 open Expenses:Food USD include "mobile.bean" `, }; beforeEach(() => { useLedgerStore.getState().reset(); }); describe('ledgerStore 写操作', () => { it('autoOpenAccounts 跳过已 open 的账户(去重)', async () => { await useLedgerStore.getState().loadLedger([sampleLedger], new MemoryBackend()); // Assets:Alipay 已存在 → 应跳过 await useLedgerStore.getState().autoOpenAccounts(['Assets:Alipay', 'Assets:NewAccount']); const after = useLedgerStore.getState().mobileBean; // 只新增 Assets:NewAccount,不含 Assets:Alipay 的重复 open expect(after).toContain('Assets:NewAccount'); // 确认 mobileBean 中不含重复的 Assets:Alipay open(它在 main.bean 里声明,不应追加到 mobile.bean) expect(after).not.toContain('open Assets:Alipay'); }); it('autoOpenAccounts 对已全部 open 的列表是 no-op', async () => { await useLedgerStore.getState().loadLedger([sampleLedger], new MemoryBackend()); const before = useLedgerStore.getState().mobileBean; await useLedgerStore.getState().autoOpenAccounts(['Assets:Alipay']); const after = useLedgerStore.getState().mobileBean; expect(after).toBe(before); // 无变化 }); it('autoOpenAccounts 使用 ledger 的 operating_currency', async () => { await useLedgerStore.getState().loadLedger([usdLedger], new MemoryBackend()); await useLedgerStore.getState().autoOpenAccounts(['Assets:NewUSD']); const after = useLedgerStore.getState().mobileBean; // 应写 USD 而非 CNY expect(after).toContain('open Assets:NewUSD USD'); expect(after).not.toContain('open Assets:NewUSD CNY'); }); it('adjustAccountBalance 使用 ledger 的 operating_currency', async () => { await useLedgerStore.getState().loadLedger([usdLedger], new MemoryBackend()); await useLedgerStore.getState().adjustAccountBalance('Assets:Bank', '5000', '2026-07-15'); const after = useLedgerStore.getState().mobileBean; // balance 断言应使用 USD expect(after).toContain('balance Assets:Bank 5000 USD'); }); it('appendTransactionsBatch 在锁内拼接多笔交易', async () => { await useLedgerStore.getState().loadLedger([sampleLedger], new MemoryBackend()); const before = useLedgerStore.getState().mobileBean; await useLedgerStore.getState().appendTransactionsBatch([ { date: '2026-07-10', narration: '午餐', postings: [{ account: 'Assets:Alipay', amount: '-20', currency: 'CNY' }, { account: 'Expenses:Food', amount: '20', currency: 'CNY' }] }, { date: '2026-07-11', narration: '晚餐', postings: [{ account: 'Assets:Alipay', amount: '-30', currency: 'CNY' }, { account: 'Expenses:Food', amount: '30', currency: 'CNY' }] }, ]); const after = useLedgerStore.getState().mobileBean; expect(after).toContain('午餐'); expect(after).toContain('晚餐'); expect(after.length).toBeGreaterThan(before.length); }); it('并发 addTransaction + autoOpenAccounts 不丢失数据', async () => { await useLedgerStore.getState().loadLedger([sampleLedger], new MemoryBackend()); // 并发执行:添加交易 + 自动开户 await Promise.all([ useLedgerStore.getState().addTransaction({ date: '2026-07-10', narration: '交易A', postings: [{ account: 'Assets:Alipay', amount: '-10', currency: 'CNY' }, { account: 'Expenses:Food', amount: '10', currency: 'CNY' }], }), useLedgerStore.getState().autoOpenAccounts(['Assets:NewAcct']), ]); const after = useLedgerStore.getState().mobileBean; // 两者都应存在(互斥锁保证了不丢失) expect(after).toContain('交易A'); expect(after).toContain('Assets:NewAcct'); }); it('并发两笔 addTransaction 都保留', async () => { await useLedgerStore.getState().loadLedger([sampleLedger], new MemoryBackend()); await Promise.all([ useLedgerStore.getState().addTransaction({ date: '2026-07-10', narration: '交易1', postings: [{ account: 'Assets:Alipay', amount: '-5', currency: 'CNY' }, { account: 'Expenses:Food', amount: '5', currency: 'CNY' }], }), useLedgerStore.getState().addTransaction({ date: '2026-07-10', narration: '交易2', postings: [{ account: 'Assets:Alipay', amount: '-8', currency: 'CNY' }, { account: 'Expenses:Food', amount: '8', currency: 'CNY' }], }), ]); const txns = useLedgerStore.getState().ledger!.transactions; expect(txns).toHaveLength(2); expect(txns.map(t => t.narration).sort()).toEqual(['交易1', '交易2']); }); });