在Jest测试中使用jQuery检测元素可见性的问题排查
问题根源分析
你遇到的问题主要来自三个核心原因:
JSDOM不支持CSS动画执行
Jest默认使用JSDOM作为DOM测试环境,它仅模拟DOM结构,不会像真实浏览器那样运行CSS动画或过渡效果。jQuery的toggle("fast")是带渐进式动画的方法,在JSDOM中动画完全不会被执行,元素样式永远停留在初始状态,导致is(":visible")始终返回初始值。动画的异步性未被处理
即使在真实浏览器中,toggle("fast")也是异步执行的,你在点击后立即断言时,动画还未完成,元素状态尚未改变,断言自然会失败。JSDOM跳过动画的特性让这个问题被进一步放大。jQuery
:visible选择器的环境差异
jQuery的:visible判断依赖于元素的offsetWidth、offsetHeight等布局属性,JSDOM对这些属性的计算逻辑和真实浏览器存在差异,尤其是在动画未执行的情况下,这些属性不会被正确更新。
解决方案
下面提供几种靠谱的解决方法,按推荐程度排序:
方案1:禁用jQuery动画(最推荐)
jQuery提供了全局开关$.fx.off,设置为true后,所有动画方法(toggle、show、hide、animate)都会跳过动画,直接将元素设置到最终状态。这样在测试中,元素状态会立即更新,is(":visible")就能正确判断了。
修改你的测试代码,在beforeEach中添加这个开关:
jest.dontMock('jquery'); jest.dontMock('../toggle.js'); $ = jQuery = require('jquery'); describe('toggle.js', function() { var sut = require('../toggle.js'); // 在所有测试前禁用动画,让状态立即生效 beforeEach(() => { $.fx.off = true; }); function givenFields(formFields) { document.documentElement.innerHTML = ` <html><body><form> <div id="field">hi</div> ${formFields} </form></body></html> `; sut.initialize(); } it('toggles the field', function() { givenFields(`<input type='checkbox' id="toggle">toggle</input>`); var initiallyVisible = $("#field").is(":visible"); $('#toggle').click(); expect($("#field").is(":visible")).toEqual(!initiallyVisible); }); // 注意:你的toggle.js中需要补充show按钮的事件绑定,否则这个测试还是会失败 // 比如在toggle.js中添加: // $('form').on('change', 'input#show[type="checkbox"]', function() { // $('#field').show(); // }); it('shows the field', function() { givenFields(`<input type='checkbox' id="show">show</input>`); $('#show').click(); expect($("#field").is(":visible")).toEqual(true); }); });
方案2:使用Jest定时器模拟动画完成
如果需要保留动画逻辑的测试,可以利用Jest的假定时器——因为jQuery的动画是基于setTimeout实现的,我们可以快进所有定时器来模拟动画完成:
it('toggles the field with animation simulation', function() { givenFields(`<input type='checkbox' id="toggle">toggle</input>`); jest.useFakeTimers(); // 启用假定时器 var initiallyVisible = $("#field").is(":visible"); $('#toggle').click(); jest.runAllTimers(); // 快进所有定时器,让动画"完成" expect($("#field").is(":visible")).toEqual(!initiallyVisible); jest.useRealTimers(); // 还原真实定时器 });
不过这个方案不如禁用动画稳定,因为不同版本的jQuery动画使用的定时器逻辑可能有变化。
方案3:直接检查元素样式属性(替代:visible)
如果你不想修改动画行为,可以直接检查元素的display属性或布局属性,避免依赖jQuery的:visible选择器:
// 检查display属性 expect($("#field").css("display")).not.toBe("none"); // 或者检查offsetHeight(元素可见时offsetHeight>0) expect($("#field")[0].offsetHeight).toBeGreaterThan(0);
这些方法更直接,在JSDOM环境下的兼容性更好。
额外注意点
你的shows the field测试失败,还有一个可能的原因是toggle.js中没有绑定#show复选框的change事件,你需要在toggle.js中补充对应的逻辑:
// 在toggle.js中添加show按钮的处理 $('form').on('change', 'input#show[type="checkbox"]', function() { $('#field').show(); }); // 同理,hide按钮的处理 $('form').on('change', 'input#hide[type="checkbox"]', function() { $('#field').hide(); });
内容的提问来源于stack exchange,提问作者Roman-Stop RU aggression in UA

