import { describe, expect, it } from 'vitest'; import { pressKey, evaluateExpression } from '../src/components/numpadExpression'; import { parsePostingsToLegs } from '../src/domain/postingLegs'; import { buildPostings } from '../src/domain/transactionBuilder'; describe('pressKey 表达式编辑', () => { it('数字键追加', () => { expect(pressKey('', '5')).toBe('5'); expect(pressKey('5', '3')).toBe('53'); }); it('前导零被替换', () => { expect(pressKey('0', '5')).toBe('5'); }); it('小数点:空段补 0,同段不重复', () => { expect(pressKey('', '.')).toBe('0.'); expect(pressKey('5', '.')).toBe('5.'); expect(pressKey('5.5', '.')).toBe('5.5'); }); it('小数最多两位', () => { expect(pressKey('5.55', '5')).toBe('5.55'); expect(pressKey('5.5', '5')).toBe('5.55'); }); it('整数位最多 9 位', () => { expect(pressKey('123456789', '0')).toBe('123456789'); expect(pressKey('12345678', '9')).toBe('123456789'); }); it('9 位整数后仍可输入小数', () => { expect(pressKey('123456789.', '5')).toBe('123456789.5'); expect(pressKey('123456789.5', '5')).toBe('123456789.55'); }); it('运算符:数字后可加,连续运算符替换,空表达式不可加', () => { expect(pressKey('5', '+')).toBe('5+'); expect(pressKey('5+', '-')).toBe('5-'); expect(pressKey('', '+')).toBe(''); expect(pressKey('', '-')).toBe(''); }); it('运算符后开启新数字段(小数规则独立)', () => { expect(pressKey('5.5+', '.')).toBe('5.5+0.'); expect(pressKey('5.55+3', '.')).toBe('5.55+3.'); }); it('backspace 删除末字符', () => { expect(pressKey('5.5', 'backspace')).toBe('5.'); expect(pressKey('5', 'backspace')).toBe(''); expect(pressKey('', 'backspace')).toBe(''); }); }); describe('evaluateExpression 求值', () => { it('单值', () => { expect(evaluateExpression('35')).toBe('35'); }); it('加减混合(字符串十进制,无浮点误差)', () => { expect(evaluateExpression('20+15')).toBe('35'); expect(evaluateExpression('20+15-5.5')).toBe('29.5'); expect(evaluateExpression('0.1+0.2')).toBe('0.3'); }); it('尾随运算符被忽略', () => { expect(evaluateExpression('20+')).toBe('20'); expect(evaluateExpression('20+15-')).toBe('35'); }); it('空表达式返回空串', () => { expect(evaluateExpression('')).toBe(''); }); it('段尾小数点不崩溃(键盘可达状态)', () => { expect(evaluateExpression('5.')).toBe('5'); expect(evaluateExpression('5.+3')).toBe('8'); }); it('净额可为负', () => { expect(evaluateExpression('5-10')).toBe('-5'); }); }); describe('parsePostingsToLegs 双腿解析', () => { it('与 buildPostings 往返一致:支出', () => { const legs = parsePostingsToLegs(buildPostings('expense', 'Assets:招行', 'Expenses:餐饮', '35', 'CNY')); expect(legs).toEqual({ direction: 'expense', sourceAccount: 'Assets:招行', targetAccount: 'Expenses:餐饮', amount: '35', currency: 'CNY', }); }); it('与 buildPostings 往返一致:收入', () => { const legs = parsePostingsToLegs(buildPostings('income', 'Assets:招行', 'Income:工资', '12000', 'CNY')); expect(legs).toEqual({ direction: 'income', sourceAccount: 'Assets:招行', targetAccount: 'Income:工资', amount: '12000', currency: 'CNY', }); }); it('与 buildPostings 往返一致:转账', () => { const legs = parsePostingsToLegs(buildPostings('transfer', 'Assets:招行', 'Liabilities:花呗', '2000', 'CNY')); expect(legs).toEqual({ direction: 'transfer', sourceAccount: 'Assets:招行', targetAccount: 'Liabilities:花呗', amount: '2000', currency: 'CNY', }); }); it('金额取绝对值(去掉负号)', () => { const legs = parsePostingsToLegs([ { account: 'Assets:招行', amount: '-35', currency: 'CNY' }, { account: 'Expenses:餐饮', amount: '35', currency: 'CNY' }, ]); expect(legs?.amount).toBe('35'); }); it('无法识别时返回 null', () => { expect(parsePostingsToLegs([])).toBeNull(); expect(parsePostingsToLegs([{ account: 'Assets:招行', amount: '35', currency: 'CNY' }])).toBeNull(); }); it('拆分交易(3 条分录)返回 null,走高级模式', () => { expect( parsePostingsToLegs([ { account: 'Assets:招行', amount: '-35', currency: 'CNY' }, { account: 'Expenses:餐饮', amount: '30', currency: 'CNY' }, { account: 'Expenses:小费', amount: '5', currency: 'CNY' }, ]) ).toBeNull(); }); it('识别腿缺金额时回退用对侧资金腿', () => { const legs = parsePostingsToLegs([ { account: 'Assets:招行', amount: '-35', currency: 'CNY' }, { account: 'Expenses:餐饮' }, ]); expect(legs?.amount).toBe('35'); }); it('转账正腿在前时按负号判定转出方', () => { const legs = parsePostingsToLegs([ { account: 'Assets:A', amount: '100', currency: 'CNY' }, { account: 'Assets:B', amount: '-100', currency: 'CNY' }, ]); expect(legs?.sourceAccount).toBe('Assets:B'); expect(legs?.targetAccount).toBe('Assets:A'); }); });