/** * 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(''); 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%'); }); });