fix(fast): 重建 bundle.js 纳入 ENTER 事件改动 + 新增 enter 链路单测

- 根因:页面(_MainLayout)加载的是 bundle.js(由 bundleconfig.json 打包 fastVue.base.js 等生成),
  而非原始 fastVue.base.js;源码树 bundle.js 是改动前的陈旧构建产物(tracked in git),
  导致测试环境部署拿到缺 _enterFired/$emit('enter') 的版本,回车不触发计算器($emit('enter') 未被发出)。
- 修复:按 bundleconfig.json 输入顺序重建 bundle.js(minify:false=顺序拼接),diff 仅 10 行,
  重建后含完整 enter 链路(_enterFired/isEnter/$emit('enter')),与源码 fastVue.base.js 一致。
- 新增 fastVue.enter.test.js(jsdom+jQuery):覆盖 numberInput 底层 Enter→_enterFired→onchange(isEnter) 与
  vueNumberInput 组件 $emit('input')+$emit('enter') 分发,9 项全过;定位期单测证明代码逻辑本身正确,
  问题在构建/部署产物陈旧。
This commit is contained in:
hjhan
2026-07-29 15:22:06 +08:00
parent b9f2bd14f6
commit 7fc4babc28
2 changed files with 237 additions and 8 deletions
+224
View File
@@ -0,0 +1,224 @@
/**
* fastVue.enter.test.js — FastVue.numberInput 回车(enter)事件链路验证
* ============================================================================
* 目的:验证 __keyHandle Enter → _enterFired=true → blur() → __change 读 isEnter
* → onchange(f, text, isEnter=true) 完整链路。
*
* 背景:债券三字段交互改造新增 enter 事件区分回车/失焦;
* 生产环境回车不触发计算(REV 显示正常说明 input 事件通路 OK),
* 需要脱离浏览器独立验证控件改动本身是否正确。
*
* 运行:cd YLErpWeb/fe-tests && npx jest fastVue.enter
*/
const { JSDOM } = require('jsdom');
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
global.window = dom.window;
global.document = dom.window.document;
global.navigator = dom.window.navigator;
Object.defineProperty(global, 'FastVue', {
get() { return global.window.FastVue; },
set(value) { global.window.FastVue = value; },
configurable: true
});
const $ = require('jquery');
global.$ = $;
global.jQuery = $;
global.main = {
formatNumber: function (number, precision, options) {
const n = Number(number);
if (Number.isNaN(n)) return '0';
const useGrouping = options && options.grouping;
if (useGrouping) {
return n.toLocaleString('en-US', { minimumFractionDigits: precision, maximumFractionDigits: precision });
}
return n.toFixed(precision);
}
};
require('../wwwroot/Scripts/fast/fastVue.base.js');
function createInput() {
return document.createElement('input');
}
/**
* 模拟用户在 input 中输入值后敲回车的完整流程:
* 1. 设置 value
* 2. 触发 keydown(13=Enter) → __keyHandle 应设置 _enterFired=true 并 blur()
* 3. 触发 change 事件 → __change 应读取 _enterFired 并传 isEnter=true 给 onchange
*/
function simulateEnter(el, inputValue) {
el.value = inputValue;
// Step 1: keydown Enter (keyCode 13)
const keydown = new window.KeyboardEvent('keydown', { keyCode: 13, bubbles: true });
el.dispatchEvent(keydown);
// Step 2: change 事件(blur 后浏览器触发;jsdom 不自动触发,需手动)
const change = new window.Event('change', { bubbles: true });
el.dispatchEvent(change);
}
/**
* 模拟用户在 input 中输入值后点击别处失焦(非回车)的流程:
* keydown 非 Enter → _enterFired 保持 false → change → isEnter=false
*/
function simulateBlurOnly(el, inputValue) {
el.value = inputValue;
// 先随便按个键(如 End 键 keyCode 35),让 __keyHandle 跑一遍但 _enterFired=false
const keydown = new window.KeyboardEvent('keydown', { keyCode: 35, bubbles: true });
el.dispatchEvent(keydown);
// 再触发 change(模拟失焦)
const change = new window.Event('change', { bubbles: true });
el.dispatchEvent(change);
}
describe('FastVue.numberInput enter 事件链路', () => {
test('回车(Enter)时 onchange 应收到 isEnter=true(三参数签名)', () => {
const el = createInput();
const calls = [];
FastVue.numberInput(el, { precision: 4, onchange: (f, text, isEnter) => calls.push({ f, text, isEnter }) });
simulateEnter(el, '100');
// 核心断言:onchange 被调用且 isEnter === true
expect(calls.length).toBeGreaterThanOrEqual(1);
const lastCall = calls[calls.length - 1];
expect(lastCall.isEnter).toBe(true);
expect(lastCall.f).toBeCloseTo(100, 6);
expect(lastCall.text).toBe('100');
});
test('失焦(非回车)时 onchange 应收到 isEnter=false', () => {
const el = createInput();
const calls = [];
FastVue.numberInput(el, { precision: 4, onchange: (f, text, isEnter) => calls.push({ f, text, isEnter }) });
simulateBlurOnly(el, '99.5');
expect(calls.length).toBeGreaterThanOrEqual(1);
const lastCall = calls[calls.length - 1];
expect(lastCall.isEnter).toBe(false);
});
test('连续两次回车:每次都应 isEnter=true', () => {
const el = createInput();
const calls = [];
FastVue.numberInput(el, { precision: 2, onchange: (f, text, isEnter) => calls.push({ isEnter }) });
simulateEnter(el, '100');
simulateEnter(el, '101');
// 至少有 2 次 onchange 调用(每次 Enter 各一次)
expect(calls.length).toBeGreaterThanOrEqual(2);
calls.forEach((call, i) => {
expect(call.isEnter).toBe(true); // 每次 Enter 都应 isEnter=true
});
});
test('先失焦再回车:第一次 isEnter=false,第二次 isEnter=true', () => {
const el = createInput();
const calls = [];
FastVue.numberInput(el, { precision: 2, onchange: (f, text, isEnter) => calls.push({ isEnter }) });
simulateBlurOnly(el, '99'); // 失焦
simulateEnter(el, '100'); // 回车
expect(calls.length).toBeGreaterThanOrEqual(2);
expect(calls[0].isEnter).toBe(false); // 失焦
expect(calls[calls.length - 1].isEnter).toBe(true); // 回车
});
test('percent:true 格式下回车:isEnter=true 且值正确 ÷100', () => {
const el = createInput();
const calls = [];
FastVue.numberInput(el, { precision: 4, percent: true, append: '', onchange: (f, text, isEnter) => calls.push({ f, text, isEnter }) });
simulateEnter(el, '99.5');
const lastCall = calls[calls.length - 1];
expect(lastCall.isEnter).toBe(true);
// percent:true 下原始值 99.5 → 解析为 0.995
expect(lastCall.f).toBeCloseTo(0.995, 8);
expect(lastCall.text).toBe('99.5');
});
test('append:"%" 格式下回车:isEnter=true 且值正确 ÷100', () => {
const el = createInput();
const calls = [];
FastVue.numberInput(el, { precision: 2, append: '%', onchange: (f, text, isEnter) => calls.push({ f, text, isEnter }) });
simulateEnter(el, '99.50%');
const lastCall = calls[calls.length - 1];
expect(lastCall.isEnter).toBe(true);
expect(lastCall.f).toBeCloseTo(0.995, 6); // "99.50%" → 99.50 / 100 = 0.995
});
});
describe('FastVue.vueNumberInput 组件 onchange 方法($emit enter/input 分发)', () => {
/**
* 创建一个模拟的 Vue 组件实例,包含 $emit 收集 + vueNumberInput 的 onchange 方法。
* 用 Function.prototype.bind 确保 this 指向 vm(模拟 Vue 1.x 的方法绑定)。
*/
function createMockVm() {
const emitted = [];
const vm = {
$emit(event, val) { emitted.push({ event, val }); },
ret_type: 0,
init: true,
value: null
};
const ctor = FastVue.vueNumberInput();
// 用 bind 固定 this 为 vm,模拟 Vue 组件的方法绑定
vm.onchange = ctor.methods.onchange.bind(vm);
return { vm, emitted };
}
test('回车(isEnter=true)时:$emit("input") 和 $emit("enter") 都应触发', () => {
const { vm, emitted } = createMockVm();
// 直接调用 onchange(模拟 __change 回调),传入 isEnter=true
vm.onchange(100, '100', true);
expect(emitted.some(e => e.event === 'input')).toBe(true);
expect(emitted.some(e => e.event === 'enter')).toBe(true);
const inputEvt = emitted.find(e => e.event === 'input');
const enterEvt = emitted.find(e => e.event === 'enter');
expect(inputEvt.val).toBe(100);
expect(enterEvt.val).toBe(100);
});
test('失焦(isEnter=false)时:只 $emit("input"),不 $emit("enter")', () => {
const { vm, emitted } = createMockVm();
vm.onchange(99.5, '99.5', false);
expect(emitted.some(e => e.event === 'input')).toBe(true);
expect(emitted.every(e => e.event !== 'enter')).toBe(true);
});
test('ret_type=1 时:$emit 传 text 而非 number', () => {
const emitted = [];
const vm = {
$emit(event, val) { emitted.push({ event, val }); },
ret_type: 1, // ret_type=1 → emit text 原值
init: true,
value: null
};
const ctor = FastVue.vueNumberInput();
vm.onchange = ctor.methods.onchange.bind(vm);
vm.onchange(99.5, '99.50%', true);
expect(emitted.some(e => e.event === 'enter')).toBe(true);
const enterEvt = emitted.find(e => e.event === 'enter');
// ret_type=1 → emit text ('99.50%') 而非 parsed number
expect(enterEvt.val).toBe('99.50%');
});
});
File diff suppressed because one or more lines are too long