/** * swapPriceInput.component.test.js — vue-swap-price-input 事件派发回归守卫 * ============================================================================ * 目的:锁定债券三字段输入组件「粘贴 / 重输原值 不派发 input/enter」的 bug。 * 症状:粘贴或重输原值后,点空白(标REV/清其他标识)与回车(触发计算)均无反应。 * 根因:组件把 input/enter 全部锁在原生 change 事件里,而 change 仅在「值相对聚焦时 * 变动」才触发;onPaste 甚至完全不派发任何事件。 * * 做法:直接实例化组件 options 对象(createVueInputComponent),用 mock this + $emit 间谍 * 驱动 onPaste/onKeydown/onChange,断言事件派发。不依赖 Vue 运行时挂载、不改动源文件。 * * 运行:cd YLErpWeb/fe-tests && npx jest swapPriceInput.component * 设 SWAP_PRICE_HELPER 指向其它版本文件可做对照(如改之前的旧代码应使用例变红)。 */ const fs = require('fs'); const path = require('path'); function loadComponent() { const rel = process.env.SWAP_PRICE_HELPER || '../wwwroot/Scripts/app/swaptrade/swapPricePrecisionHelper.js'; const file = path.resolve(__dirname, rel); const code = fs.readFileSync(file, 'utf8'); // swapPricePrecisionHelper.js: var swapPricePrecision = (function(global){...})(window); // 在受控作用域求值并取出 createVueInputComponent(IIFE 不挂 window,故用 new Function 取返回值)。 const factory = new Function('window', 'global', 'console', code + '\n;return swapPricePrecision;'); const sandbox = {}; const swapPricePrecision = factory(sandbox, sandbox, console); return swapPricePrecision.createVueInputComponent(); } function makeCtx(component, opts) { opts = opts || {}; const emitted = []; const ctx = { text: opts.text != null ? String(opts.text) : '', enterPressed: false, formatSnapshot: '{}', format: opts.format || { integerDigits: 2, precision: 4, percent: true }, $emit: function (name, payload) { emitted.push({ name: name, payload: payload }); }, }; Object.keys(component.methods).forEach(function (m) { ctx[m] = component.methods[m].bind(ctx); }); return { ctx: ctx, emitted: emitted }; } describe('vue-swap-price-input 事件派发(粘贴 / 重输原值场景)', () => { const component = loadComponent(); test('粘贴原值应派发 input(点空白标REV / 清其他标识)', () => { const { ctx, emitted } = makeCtx(component, { text: '3.5', format: { integerDigits: 2, precision: 4, percent: true } }); const evt = { preventDefault() {}, clipboardData: { getData: () => '3.5' }, target: { value: '' } }; ctx.onPaste(evt); const inputs = emitted.filter(e => e.name === 'input'); expect(inputs.length).toBe(1); expect(inputs[0].payload).toBe('0.035'); // percent:true → ÷100 }); test('回车(原值未变)应派发 enter(触发自动计算)', () => { const { ctx, emitted } = makeCtx(component, { text: '3.5', format: { integerDigits: 2, precision: 4, percent: true } }); const evt = { which: 13, keyCode: 13, key: 'Enter', target: { blur() {}, value: '3.5' } }; ctx.onKeydown(evt); const enters = emitted.filter(e => e.name === 'enter'); expect(enters.length).toBe(1); expect(enters[0].payload).toBe('0.035'); }); test('回车(值已改)应派发 input+enter 各一次,且不重复派发 enter', () => { const { ctx, emitted } = makeCtx(component, { text: '3.6', format: { percent: true } }); const evt = { which: 13, keyCode: 13, key: 'Enter', target: { blur() {}, value: '3.6' } }; ctx.onKeydown(evt); expect(emitted.filter(e => e.name === 'input').length).toBe(1); expect(emitted.filter(e => e.name === 'enter').length).toBe(1); }); test('非 Enter 按键不应派发 enter / input', () => { const { ctx, emitted } = makeCtx(component, { text: '3.5', format: { percent: true } }); const evt = { which: 65, keyCode: 65, key: 'a', target: { blur() {} } }; ctx.onKeydown(evt); expect(emitted.filter(e => e.name === 'enter').length).toBe(0); expect(emitted.filter(e => e.name === 'input').length).toBe(0); }); test('失焦(onChange)即使值未变也应派发 input(点空白标REV),且不再派发 enter', () => { const { ctx, emitted } = makeCtx(component, { text: '3.5', format: { percent: true } }); const evt = { target: { value: '3.5' } }; ctx.onChange(evt); expect(emitted.filter(e => e.name === 'input').length).toBe(1); expect(emitted.filter(e => e.name === 'enter').length).toBe(0); }); test('粘贴但格式为 percent:false 也应派发 input(原值透传)', () => { const { ctx, emitted } = makeCtx(component, { text: '3.5', format: { integerDigits: 2, precision: 4, percent: false } }); const evt = { preventDefault() {}, clipboardData: { getData: () => '3.5' }, target: { value: '' } }; ctx.onPaste(evt); expect(emitted.filter(e => e.name === 'input').length).toBe(1); expect(emitted.filter(e => e.name === 'input')[0].payload).toBe('3.5'); }); });