/** * main.postSafe.test.js — main.postSafe() 错误守卫封装测试 * ============================================================================ * 目的:验证 postSafe 正确将 jQuery Deferred 的 .done/.fail 映射到 Promise 的 resolve/reject, * 防止「业务错误走 reject、.done 不触发」陷阱复发。 * * 覆盖场景: * 1. 成功请求 → Promise resolve * 2. 业务错误(resp.success === false)→ Promise reject * 3. 网络异常 → Promise reject * 4. async/await 语法兼容性 * 5. 与 main.post 行为对比(陷阱复现) * * 运行:cd YLErpWeb/fe-tests && npx jest main.postSafe * ============================================================================ */ // 搭建最小 DOM 环境 const { JSDOM } = require('jsdom'); const dom = new JSDOM(''); global.window = dom.window; global.document = dom.window.document; global.navigator = dom.window.navigator; // Mock main 对象(与 main.js 中的逻辑等价) var main = {}; // main.post: 返回 jQuery Deferred promise,测试中通过 _lastDeferred 控制结果 main.post = function (url, data, options) { var doneCb = [], failCb = []; var promise = { done: function (cb) { doneCb.push(cb); return promise; }, fail: function (cb) { failCb.push(cb); return promise; }, }; main._lastDeferred = { resolve: function (resp) { doneCb.forEach(function (cb) { cb(resp); }); }, reject: function (resp) { failCb.forEach(function (cb) { cb(resp); }); }, promise: function () { return promise; }, }; return main._lastDeferred.promise(); }; // main.postSafe: 与 main.js 中完全相同的实现 main.postSafe = function (url, data, options) { return new Promise(function (resolve, reject) { main.post(url, data, options) .done(function (resp) { resolve(resp); }) .fail(function (resp) { reject(resp); }); }); }; describe('main.postSafe — Promise 封装守卫', function () { test('成功请求 → Promise resolve(resp)', function () { var p = main.postSafe('/api/test', { foo: 1 }); // postSafe 内部已调用 main.post,_lastDeferred 已就绪 main._lastDeferred.resolve({ success: true, data: 'ok' }); return p.then(function (resp) { expect(resp.success).toBe(true); expect(resp.data).toBe('ok'); }); }); test('业务错误(resp.success===false)→ Promise reject(resp)', function () { var p = main.postSafe('/api/test', { foo: 1 }); main._lastDeferred.reject({ success: false, msg: '算不出来' }); return p.catch(function (resp) { expect(resp.success).toBe(false); expect(resp.msg).toBe('算不出来'); }); }); test('网络异常 → Promise reject', function () { var p = main.postSafe('/api/test', { foo: 1 }); main._lastDeferred.reject({ errcode: 500, msg: '请求失败' }); return p.catch(function (resp) { expect(resp.errcode).toBe(500); }); }); test('async/await 语法兼容 — 成功路径', async function () { var p = main.postSafe('/api/test', { foo: 1 }); main._lastDeferred.resolve({ success: true, value: 42 }); var resp = await p; expect(resp.success).toBe(true); expect(resp.value).toBe(42); }); test('async/await 语法兼容 — 失败路径', async function () { var p = main.postSafe('/api/test', { foo: 1 }); main._lastDeferred.reject({ success: false, msg: '计算失败' }); var caught = null; try { await p; } catch (resp) { caught = resp; } expect(caught).not.toBeNull(); expect(caught.success).toBe(false); expect(caught.msg).toBe('计算失败'); }); }); describe('main.postSafe — 与 main.post 行为对比(陷阱复现)', function () { test('main.post 的 .done 在业务错误时不触发(陷阱复现)', function () { var doneCalled = false; var failCalled = false; main.post('/api/test', {}).done(function () { doneCalled = true; }).fail(function () { failCalled = true; }); // 业务错误 → reject main._lastDeferred.reject({ success: false }); expect(doneCalled).toBe(false); // .done 没触发!这就是陷阱 expect(failCalled).toBe(true); // .fail 触发了 }); test('main.postSafe 的 .then 在业务错误时不触发(安全)', function () { var thenCalled = false; var caught = null; var p = main.postSafe('/api/test', {}).then(function () { thenCalled = true; }).catch(function (resp) { caught = resp; }); // 业务错误 → reject main._lastDeferred.reject({ success: false, msg: 'biz error' }); // Promise 是微任务,需要等一轮 return p.then(function () { expect(thenCalled).toBe(false); // .then 没触发 expect(caught).not.toBeNull(); // .catch 触发了 expect(caught.msg).toBe('biz error'); }); }); });