feat(web): EQD-6953 平仓页DP↔YD互算纯函数库unwindBondCalc.js+17个jest单测——冻结三条易错约定:发计算器展示态直传不×100/ExitYtm四位四舍五入AwayFromZero(确认书导出固定4位不去零)/失败只清对方字段;修复roundExitYtm(null)会被取整成0覆盖手输的隐患(单测拦截);jest覆盖率清单纳入新文件;纯新增零接线,不影响生产
This commit is contained in:
@@ -25,6 +25,7 @@ module.exports = {
|
||||
// 覆盖率配置(--coverage 时生效)
|
||||
collectCoverageFrom: [
|
||||
'../wwwroot/Scripts/app/swaptrade/swapCalc.js',
|
||||
'../wwwroot/Scripts/app/swaptrade/unwindBondCalc.js',
|
||||
'../wwwroot/Scripts/fast/fastVue.base.js',
|
||||
// 逐步加入更多文件
|
||||
],
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* 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('roundExitYtm:4 位小数、四舍五入(远离零)', () => {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
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 为 false,0 为 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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user