test: 补齐步骤3-b及多字段组合场景的债券三字段测试覆盖
文档(互换债券三字段互算踩坑总结与测试指南.md)列出的步骤3-b (不敲回车连续编辑多字段)此前无任何测试断言最终落标状态。 新增 7 个用例(35->42,总计72->79): - Step3-b: 回车CP->改DP->改YD(均不回车) -> 仅YD留REV - Step3-b变体: 连续编辑CP->DP->YD -> 仅最后赢 - 三字段轮换(CP->DP->YD->CP) -> 仅最终CP留REV - 步骤4-1: 回车CP->回车DP->改YD -> 多源降级为单REV - 步骤4-1变体: 三字段都回车后改CP -> 仅CP留REV - 数值保留守卫: 多次applyBondManualEdit不联动修改任何价格值 核心发现:applyBondManualEdit每次重置全部bondRev再只标当前字段, 故多字段连续编辑时只有最后一个字段保留REV是确定性行为, 现已用测试钉死,防止未来重构破坏此语义。
This commit is contained in:
@@ -671,6 +671,121 @@ describe('约定1/2/3 状态机:端到端转换', () => {
|
||||
expect(item.bondAuto).toEqual({ CP: false, DP: false, YD: false });
|
||||
expect(item.bondRev).toEqual({ CP: false, DP: false, YD: false });
|
||||
});
|
||||
|
||||
// ---- 以下为步骤3-b 及多字段组合场景(文档明确列出但此前缺失) ----
|
||||
|
||||
test('Step3-b 回车CP→改DP→改YD(均不回车) → 仅最后字段(YD)留REV,前面REV被覆盖', () => {
|
||||
const item = makeBondItem();
|
||||
// 步骤1:回车净价(CP)
|
||||
SwapCalc.applyBondCalcSuccess(item, 'CP');
|
||||
expect(item.bondDriverType).toBe('CP');
|
||||
expect(item.bondRev).toEqual({ CP: false, DP: false, YD: false });
|
||||
|
||||
// 步骤2:编辑全价(DP)未回车 → REV={DP:true}
|
||||
SwapCalc.applyBondManualEdit(item, 'DP');
|
||||
expect(item.bondRev).toEqual({ CP: false, DP: true, YD: false });
|
||||
|
||||
// ★ 步骤3-b:不回车,又改收益率(YD) → DP的REV被覆盖,仅YD留REV
|
||||
SwapCalc.applyBondManualEdit(item, 'YD');
|
||||
expect(item.bondDriverType).toBeNull(); // 源已被清
|
||||
expect(item.bondAuto).toEqual({ CP: false, DP: false, YD: false }); // AUTO 已清
|
||||
// ⚠️ 核心断言:applyBondManualEdit 每次重置全部 REV 再只标当前字段
|
||||
// 故多字段连续编辑时"最后赢",中间字段的 REV 静默丢失
|
||||
expect(item.bondRev).toEqual({ CP: false, DP: false, YD: true });
|
||||
// 数值保持不变(约定3:编辑不联动)
|
||||
expect(item.PosiNetNoFeePrice).toBe(0.995); // 净价保留
|
||||
expect(item.PosiGrossPrice).toBe(1.0); // 全价保留
|
||||
expect(item.InitYtm).toBe(0.026); // 收益率保留
|
||||
});
|
||||
|
||||
test('Step3-b 变体:回车CP后连续编辑三字段(CP→DP→YD) → 仅最后(YD)留REV', () => {
|
||||
const item = makeBondItem();
|
||||
SwapCalc.applyBondCalcSuccess(item, 'CP');
|
||||
|
||||
// 连续编辑三个字段,都不回车
|
||||
SwapCalc.applyBondManualEdit(item, 'CP'); // 先改源字段本身
|
||||
expect(item.bondRev).toEqual({ CP: true, DP: false, YD: false });
|
||||
|
||||
SwapCalc.applyBondManualEdit(item, 'DP'); // 再改第二个 → CP的REV被覆盖
|
||||
expect(item.bondRev).toEqual({ CP: false, DP: true, YD: false });
|
||||
|
||||
SwapCalc.applyBondManualEdit(item, 'YD'); // 最后改第三个 → DP的REV被覆盖
|
||||
expect(item.bondRev).toEqual({ CP: false, DP: false, YD: true }); // 仅最后赢
|
||||
});
|
||||
|
||||
test('Step3-b 三字段轮换编辑(CP→DP→YD→CP) → 仅最终(CP)留REV', () => {
|
||||
const item = makeBondItem();
|
||||
SwapCalc.applyBondCalcSuccess(item, 'CP');
|
||||
|
||||
// 四次连续手工编辑,模拟用户在三个输入框之间来回跳转
|
||||
SwapCalc.applyBondManualEdit(item, 'CP');
|
||||
SwapCalc.applyBondManualEdit(item, 'DP');
|
||||
SwapCalc.applyBondManualEdit(item, 'YD');
|
||||
SwapCalc.applyBondManualEdit(item, 'CP'); // 回到第一个
|
||||
|
||||
// 无论轮换多少次,只有最后一次 applyBondManualEdit 的字段留 REV
|
||||
expect(item.bondRev).toEqual({ CP: true, DP: false, YD: false });
|
||||
expect(item.bondDriverType).toBeNull();
|
||||
expect(item.bondAuto).toEqual({ CP: false, DP: false, YD: false });
|
||||
});
|
||||
|
||||
test('步骤4-1 回车CP→回车DP→改YD(不回车) → 从多源状态降级为单REV(YD)', () => {
|
||||
const item = makeBondItem();
|
||||
// 连续回车两个字段 → 多源状态
|
||||
SwapCalc.applyBondCalcSuccess(item, 'CP');
|
||||
SwapCalc.applyBondCalcSuccess(item, 'DP');
|
||||
expect(item.bondDriverType).toBe('DP'); // 最后回车者为源
|
||||
expect(item.bondAuto).toEqual({ CP: true, DP: false, YD: true }); // CP降级AUTO
|
||||
|
||||
// 编辑第三个字段但不回车 → 降级为单字段REV
|
||||
SwapCalc.applyBondManualEdit(item, 'YD');
|
||||
expect(item.bondDriverType).toBeNull(); // 源被清
|
||||
expect(item.bondAuto).toEqual({ CP: false, DP: false, YD: false }); // AUTO全清
|
||||
expect(item.bondRev).toEqual({ CP: false, DP: false, YD: true }); // 仅YD留REV
|
||||
});
|
||||
|
||||
test('步骤4-1 变体:回车CP→回车DP→回车YD→改CP(不回车) → 最终仅CP留REV', () => {
|
||||
const item = makeBondItem();
|
||||
// 三字段都回车过 → YD是最终源
|
||||
SwapCalc.applyBondCalcSuccess(item, 'CP');
|
||||
SwapCalc.applyBondCalcSuccess(item, 'DP');
|
||||
SwapCalc.applyBondCalcSuccess(item, 'YD');
|
||||
expect(item.bondDriverType).toBe('YD');
|
||||
|
||||
// 手工编辑CP(不回车)→ 全部标识降级,仅CP留REV
|
||||
SwapCalc.applyBondManualEdit(item, 'CP');
|
||||
expect(item.bondDriverType).toBeNull();
|
||||
expect(item.bondAuto).toEqual({ CP: false, DP: false, YD: false });
|
||||
expect(item.bondRev).toEqual({ CP: true, DP: false, YD: false });
|
||||
});
|
||||
|
||||
test('多字段连续编辑时数值始终保留、不联动(约定3核心语义)', () => {
|
||||
const item = makeBondItem();
|
||||
// 设定初始值
|
||||
item.PosiNetNoFeePrice = 0.995; // 净价 99.5
|
||||
item.PosiGrossPrice = 1.0; // 全价 100
|
||||
item.InitYtm = 0.03; // 收益率 3%
|
||||
|
||||
// 回车CP触发计算(模拟成功——数值会被覆盖,但这里只看标识)
|
||||
SwapCalc.applyBondCalcSuccess(item, 'CP');
|
||||
|
||||
// 连续编辑另两字段(不回车)→ 数值不应被联动修改
|
||||
SwapCalc.applyBondManualEdit(item, 'DP');
|
||||
expect(item.PosiNetNoFeePrice).toBe(0.995);
|
||||
expect(item.PosiGrossPrice).toBe(1.0);
|
||||
expect(item.InitYtm).toBe(0.03);
|
||||
|
||||
SwapCalc.applyBondManualEdit(item, 'YD');
|
||||
expect(item.PosiNetNoFeePrice).toBe(0.995); // 净价不变
|
||||
expect(item.PosiGrossPrice).toBe(1.0); // 全价不变
|
||||
expect(item.InitYtm).toBe(0.03); // 收益率不变
|
||||
|
||||
// 再编辑回CP本身
|
||||
SwapCalc.applyBondManualEdit(item, 'CP');
|
||||
expect(item.PosiNetNoFeePrice).toBe(0.995); // 仍然不变
|
||||
expect(item.PosiGrossPrice).toBe(1.0);
|
||||
expect(item.InitYtm).toBe(0.03);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user