Files
zszq-trs/YLErpWeb/fe-tests/unwindBondCalc.test.js
T

146 lines
7.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* unwindBondCalc.test.js — 平仓页 DP↔YD 两字段互算纯函数(EQD-6953
* ============================================================================
* 冻结三条与录入页不同、极易被"顺手统一"改坏的约定:
* 1. 发计算器的价格是【展示态直传】,不 ×100(录入页是存储态 ×100);
* 2. ExitYtm 保留 4 位、四舍五入(AwayFromZero)——结算确认书导出固定 4 位不去零,
* 界面精度不得低于导出要求;
* 3. 失败只清【对方】字段(平仓页无净价列,仅 DP/YD 两字段)。
*
* 运行:cd YLErpWeb/fe-tests && npx jest unwindBondCalc
*/
const UnwindBondCalc = require('../wwwroot/Scripts/app/swaptrade/unwindBondCalc.js');
const CALC = { cleanPrice: 98.5, dirtyPrice: 99.5, ytm: 6.3721 };
describe('getCalcRequest:展示态直传,不做录入页的 ×100 换算', () => {
test('DP 回车:price 原样 99.5(若误加 ×100 会变成 9950),priceType=DP,估值日=平仓日', () => {
const req = UnwindBondCalc.getCalcRequest('240004.IB', 99.5, 'DP', '2026-08-20');
expect(req.price).toBe(99.5);
expect(req.priceType).toBe('DP');
expect(req.targetDate).toBe('2026-08-20');
expect(req.underlyingCode).toBe('240004.IB');
expect(req.source).toBe('unwind');
});
test('YD 回车:收益率展示态直传(6.3721 而非 0.063721', () => {
const req = UnwindBondCalc.getCalcRequest('240004.IB', 6.3721, 'YD', '2026-08-20');
expect(req.price).toBe(6.3721);
expect(req.priceType).toBe('YD');
});
test('估值日为空时 targetDate 为 null(兜底,调用方应先校验)', () => {
const req = UnwindBondCalc.getCalcRequest('240004.IB', 99.5, 'DP', null);
expect(req.targetDate).toBeNull();
});
});
describe('roundExitYtm4 位小数、四舍五入(远离零)', () => {
test('第 5 位进位', () => {
expect(UnwindBondCalc.roundExitYtm(6.37215)).toBe(6.3722);
});
test('第 5 位舍去', () => {
expect(UnwindBondCalc.roundExitYtm(6.37214)).toBe(6.3721);
});
test('负收益率同样远离零(负利率债)', () => {
expect(UnwindBondCalc.roundExitYtm(-0.00005)).toBe(-0.0001);
expect(UnwindBondCalc.roundExitYtm(-1.23445)).toBe(-1.2345);
});
});
describe('applyCalcResult:源字段保持、仅回写对方字段', () => {
test('DP 为源:TradingAmountAvg 保持手输值,ExitYtm 被反算覆盖(4位)', () => {
const state = { TradingAmountAvg: 100.123456, ExitYtm: null };
UnwindBondCalc.applyCalcResult(state, { ytm: 6.37215 }, 'DP');
expect(state.TradingAmountAvg).toBe(100.123456); // 源:保持
expect(state.ExitYtm).toBe(6.3722); // 对方:反算+4位取整
});
test('YD 为源:ExitYtm 保持手输值,TradingAmountAvg 被反算覆盖(展示态直写)', () => {
const state = { TradingAmountAvg: null, ExitYtm: 6.3721 };
UnwindBondCalc.applyCalcResult(state, { dirtyPrice: 99.5023 }, 'YD');
expect(state.ExitYtm).toBe(6.3721); // 源:保持
expect(state.TradingAmountAvg).toBe(99.5023); // 对方:直写(不 ÷100
});
test('计算器未返回对方值时不覆盖(保留手工输入)', () => {
const state = { TradingAmountAvg: 100.5, ExitYtm: 6.3 };
UnwindBondCalc.applyCalcResult(state, { ytm: null }, 'DP');
expect(state.ExitYtm).toBe(6.3);
UnwindBondCalc.applyCalcResult(state, { dirtyPrice: undefined }, 'YD');
expect(state.TradingAmountAvg).toBe(100.5);
});
test('回写值与现值相同(<1e-9)时不写,避免光标跳动', () => {
const state = { TradingAmountAvg: 100, ExitYtm: 6.3721 };
UnwindBondCalc.applyCalcResult(state, { ytm: 6.3721 }, 'DP');
expect(state.ExitYtm).toBe(6.3721);
});
});
describe('applyCalcFailure:保留源字段、只清对方字段、标识全清', () => {
test('DP 为源失败:ExitYtm 清空、全价保留', () => {
const state = { TradingAmountAvg: 100.5, ExitYtm: 6.3, bondDriverType: 'DP', bondAuto: { DP: true, YD: false } };
UnwindBondCalc.applyCalcFailure(state, 'DP');
expect(state.TradingAmountAvg).toBe(100.5);
expect(state.ExitYtm).toBeNull();
expect(state.bondDriverType).toBeNull();
expect(state.bondAuto).toEqual({ DP: false, YD: false });
expect(state.bondRev).toEqual({ DP: false, YD: false });
});
test('YD 为源失败:TradingAmountAvg 清空、收益率保留', () => {
const state = { TradingAmountAvg: 100.5, ExitYtm: 6.3, bondDriverType: 'YD' };
UnwindBondCalc.applyCalcFailure(state, 'YD');
expect(state.ExitYtm).toBe(6.3);
expect(state.TradingAmountAvg).toBeNull();
});
test('driver 非法(null):仅清标识、不动任何数值(setValueDate 清陈旧标识场景的防误清守卫)', () => {
const state = { TradingAmountAvg: 100.5, ExitYtm: 6.3, bondDriverType: 'DP', bondAuto: { DP: false, YD: true } };
UnwindBondCalc.applyCalcFailure(state, null);
expect(state.TradingAmountAvg).toBe(100.5); // 绝不能被清
expect(state.ExitYtm).toBe(6.3);
expect(state.bondDriverType).toBeNull();
expect(state.bondAuto).toEqual({ DP: false, YD: false });
});
});
describe('标识状态机(交互约定2/3,对齐录入页)', () => {
test('成功:源字段标源、对方标 AUTO、REV 清空', () => {
const state = { TradingAmountAvg: 100.5, ExitYtm: null };
UnwindBondCalc.applyCalcSuccess(state, 'DP');
expect(state.bondDriverType).toBe('DP');
expect(state.bondAuto).toEqual({ DP: false, YD: true });
expect(state.bondRev).toEqual({ DP: false, YD: false });
});
test('手动编辑未回车:清 源/AUTO、本字段累计 REV、不清对方 REV', () => {
const state = { TradingAmountAvg: 100.5, ExitYtm: 6.3, bondRev: { DP: true, YD: false } };
UnwindBondCalc.applyManualEdit(state, 'YD');
expect(state.bondDriverType).toBeNull();
expect(state.bondAuto).toEqual({ DP: false, YD: false });
expect(state.bondRev).toEqual({ DP: true, YD: true }); // 累计:DP 的 REV 保留
});
test('bondRev 缺失时自动初始化', () => {
const state = {};
UnwindBondCalc.applyManualEdit(state, 'DP');
expect(state.bondRev).toEqual({ DP: true, YD: false });
});
});
describe('FIELDS 映射与 hasValue 守卫', () => {
test('字段映射', () => {
expect(UnwindBondCalc.FIELDS).toEqual({ DP: 'TradingAmountAvg', YD: 'ExitYtm' });
});
test('hasValue:空串/null/undefined/NaN 为 false0 为 true', () => {
expect(UnwindBondCalc.hasValue('')).toBe(false);
expect(UnwindBondCalc.hasValue(null)).toBe(false);
expect(UnwindBondCalc.hasValue(undefined)).toBe(false);
expect(UnwindBondCalc.hasValue(NaN)).toBe(false);
expect(UnwindBondCalc.hasValue(0)).toBe(true);
expect(UnwindBondCalc.hasValue('6.37')).toBe(true);
});
});