在 QUnit 测试中,仅当在测试运行之前获得引用时才会触发 click 事件
Within a QUnit test the click event will only fire if the reference is obtained before the test runs
我想用 Qunit.
测试以下代码
// my code under test
document.getElementById('saveButton').addEventListener('click',save);
function save() {
console.log('save clicked');
}
我的 QUnit 测试获取按钮的引用并调用点击函数:
(function () {
"use strict";
// HACK: with this line here click works
//var btn = document.getElementById('saveButton');
// the test
QUnit.test("click save", function (assert) {
// with this line no click
var btn = document.getElementById('saveButton');
btn.click(); // will it click?
assert.ok(btn !== null , 'button found');
});
}());
奇怪的是,如果我在 Qunit 的测试方法中调用 getElementById
,则不会调用附加到按钮的事件处理程序。但是,如果我将调用移至测试函数之外的 getElementById
,该事件会触发点击事件处理程序。
QUnit 在那里做了什么阻止我的测试按预期工作?proper/recommended 解决我面临的问题的方法是什么?
这是一个演示非工作版本的 MCVE(也在 JSFiddle 上)。我已经评论了要更改什么才能使点击正常工作(此处的工作方式:将文本输出到控制台)
// code under test
document.getElementById('saveButton').addEventListener('click',save);
function save() {
console.log('save clicked');
}
// QUnit tests
(function () {
"use strict";
// with this line here click works
//var btn = document.getElementById('saveButton');
QUnit.test("click save", function (assert) {
// with this line no click, comment it to test the working variant
var btn = document.getElementById('saveButton');
btn.click(); // will it click?
assert.ok(btn !== null , 'button found');
});
}());
<script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script>
<div id="qunit"></div>
<div id="qunit-fixture">
<button id="saveButton">test</button>
</div>
值得注意的是,我在这里明确不使用 jQuery,因为我正在为其编写测试的代码也没有使用 jQuery。我还没有达到可以或愿意更改被测代码的阶段。
当您执行 QUnit.test()
时,似乎 <div id="qunit-fixture">
下面的 DOM 中的所有内容都被克隆和替换。这意味着您在该调用之前添加的事件侦听器与现在存在于 DOM 中的 <button>
不同。 btn.click();
起作用是因为它在原始 <button>
上触发事件,您已将引用保存到原始 var btn =
.
如果 btn
是在 QUnit.test()
中定义的,那么它会引用新的 <button>
,它没有分配给它的事件侦听器。 QUnit 预计主要与 jQuery 一起使用,因此它可能 jQuery .clone()
, which can be set to copy the jQuery based event listeners, but not the vanilla JavaScript listeners, because the underlying Node.cloneNode()
没有那种能力。
您可以确认原来的btn
在QUnit.test()
中被console.log(btn.parentNode.parentNode);
与DOM断开了连接。原始 btn
输出 null
,但 QUnit.test()
中存在的输出 <body>
。为了证明这一点,在 运行 测试之前确定的 btn
在下面的代码中被分配给 btnBeforeQUnit
。
QUnit 克隆 HTML 内容是处理使测试彼此独立的好方法,但应记录在案。通常希望单元测试是独立的。毕竟,可能会有更改 DOM 结构的测试,应该在测试之间恢复。
但是,由于某种原因,QUnit 没有恢复 QUnit.test()
末尾的原始 DOM 个元素。文档表明它应该在下一次测试之前重新克隆原始文件,但在测试完成后它不会恢复原始文件。
除了btnBeforeQUnit
和btn
之外,btnAfterQUnit
和btnDelayedAfterQUnit
的二级父级也会输出到控制台,以更准确地演示何时DOM 替换发生在异步执行提供给 QUnit.test()
.
的回调中
// code under test
document.getElementById('saveButton').addEventListener('click',save);
function save() {
console.log('save clicked');
}
// QUnit tests
(function () {
"use strict";
// with this line here click works
var btnBeforeQUnit = document.getElementById('saveButton');
QUnit.test("click save", function (assert) {
// with this line no click, comment it to test the working variant
var btn = document.getElementById('saveButton');
var btnInsideQUnit = btn;
btn.click(); // will it click?
console.log('btnBeforeQUnit.parentNode.parentNode', btnBeforeQUnit.parentNode.parentNode);
console.log('btnInsideQUnit.parentNode.parentNode', btnInsideQUnit.parentNode.parentNode)
assert.ok(btn !== null , 'buttin found');
});
var btnAfterQUnit = document.getElementById('saveButton');
console.log('btnAfterQUnit.parentNode.parentNode', btnAfterQUnit.parentNode.parentNode);
setTimeout(function() {
var btnDelayedAfterQUnit = document.getElementById('saveButton');
console.log('btnDelayedAfterQUnit.parentNode.parentNode', btnAfterQUnit.parentNode.parentNode);
}, 1000);
}());
<script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script>
<div id="qunit"></div>
<div id="qunit-fixture">
<button id="saveButton">test</button>
</div>
调整此行为
您可以通过将 QUnit.config.fixture
设置为 null
来获得您期望的行为:
QUnit.config.fixture = null;
QUnit.config.fixture (string) | default: undefined
Defines the HTML content to use in the fixture container which is reset at the start of each test.
By default QUnit will use whatever the starting content of #qunit-fixture is as the fixture reset. If you do not want the fixture to be reset in between tests, set the value to null.
但是,将此选项设置为 null
时应小心。这将意味着 DOM 对于所有设置为 null
的 运行 测试不是独立的。换句话说,您在一个测试中对 DOM 所做的更改将影响其他测试中的更改。
IMO,QUnit.test()
在克隆 DOM 中的整体行为没有明确记录。肯定应该在主文档中提到一些行为。特别是副作用。应该明确提及现有的事件侦听器,但我没有在 QUinit 文档中找到任何明确描述此过程及其影响的内容。
以下是相同的代码,但添加了 QUnit.config.fixture = null;
。如您所见,"save clicked" 是从测试输出到控制台的,而它不是原始输出。
// code under test
document.getElementById('saveButton').addEventListener('click',save);
function save() {
console.log('save clicked');
}
// QUnit tests
(function () {
"use strict";
QUnit.config.fixture = null;
// with this line here click works
var btnBeforeQUnit = document.getElementById('saveButton');
QUnit.test("click save", function (assert) {
// with this line no click, comment it to test the working variant
var btn = document.getElementById('saveButton');
var btnInsideQUnit = btn;
btn.click(); // will it click?
console.log('btnBeforeQUnit.parentNode.parentNode', btnBeforeQUnit.parentNode.parentNode);
console.log('btnInsideQUnit.parentNode.parentNode', btnInsideQUnit.parentNode.parentNode)
assert.ok(btn !== null , 'buttin found');
});
var btnAfterQUnit = document.getElementById('saveButton');
console.log('btnAfterQUnit.parentNode.parentNode', btnAfterQUnit.parentNode.parentNode);
setTimeout(function() {
var btnDelayedAfterQUnit = document.getElementById('saveButton');
console.log('btnDelayedAfterQUnit.parentNode.parentNode', btnAfterQUnit.parentNode.parentNode);
}, 1000);
}());
<script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script>
<div id="qunit"></div>
<div id="qunit-fixture">
<button id="saveButton">test</button>
</div>
或者,为您的预定义事件侦听器使用 HTML 属性
对于您遇到的特定问题,即希望在测试之前设置事件侦听器,您可以使用 HTML 属性来定义侦听器(例如,在您的案例中为 onclick="save()"
.
// code under test
document.getElementById('saveButton').addEventListener('click',save);
function save() {
console.log('save clicked');
}
// QUnit tests
(function () {
"use strict";
// with this line here click works
var btnBeforeQUnit = document.getElementById('saveButton');
QUnit.test("click save", function (assert) {
// with this line no click, comment it to test the working variant
var btn = document.getElementById('saveButton');
var btnInsideQUnit = btn;
btn.click(); // will it click?
console.log('btnBeforeQUnit.parentNode.parentNode', btnBeforeQUnit.parentNode.parentNode);
console.log('btnInsideQUnit.parentNode.parentNode', btnInsideQUnit.parentNode.parentNode)
assert.ok(btn !== null , 'buttin found');
});
var btnAfterQUnit = document.getElementById('saveButton');
console.log('btnAfterQUnit.parentNode.parentNode', btnAfterQUnit.parentNode.parentNode);
setTimeout(function() {
var btnDelayedAfterQUnit = document.getElementById('saveButton');
console.log('btnDelayedAfterQUnit.parentNode.parentNode', btnAfterQUnit.parentNode.parentNode);
}, 1000);
}());
<script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script>
<div id="qunit"></div>
<div id="qunit-fixture">
<button id="saveButton" onclick="save()">test</button>
</div>
我想用 Qunit.
测试以下代码// my code under test
document.getElementById('saveButton').addEventListener('click',save);
function save() {
console.log('save clicked');
}
我的 QUnit 测试获取按钮的引用并调用点击函数:
(function () {
"use strict";
// HACK: with this line here click works
//var btn = document.getElementById('saveButton');
// the test
QUnit.test("click save", function (assert) {
// with this line no click
var btn = document.getElementById('saveButton');
btn.click(); // will it click?
assert.ok(btn !== null , 'button found');
});
}());
奇怪的是,如果我在 Qunit 的测试方法中调用 getElementById
,则不会调用附加到按钮的事件处理程序。但是,如果我将调用移至测试函数之外的 getElementById
,该事件会触发点击事件处理程序。
QUnit 在那里做了什么阻止我的测试按预期工作?proper/recommended 解决我面临的问题的方法是什么?
这是一个演示非工作版本的 MCVE(也在 JSFiddle 上)。我已经评论了要更改什么才能使点击正常工作(此处的工作方式:将文本输出到控制台)
// code under test
document.getElementById('saveButton').addEventListener('click',save);
function save() {
console.log('save clicked');
}
// QUnit tests
(function () {
"use strict";
// with this line here click works
//var btn = document.getElementById('saveButton');
QUnit.test("click save", function (assert) {
// with this line no click, comment it to test the working variant
var btn = document.getElementById('saveButton');
btn.click(); // will it click?
assert.ok(btn !== null , 'button found');
});
}());
<script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script>
<div id="qunit"></div>
<div id="qunit-fixture">
<button id="saveButton">test</button>
</div>
值得注意的是,我在这里明确不使用 jQuery,因为我正在为其编写测试的代码也没有使用 jQuery。我还没有达到可以或愿意更改被测代码的阶段。
当您执行 QUnit.test()
时,似乎 <div id="qunit-fixture">
下面的 DOM 中的所有内容都被克隆和替换。这意味着您在该调用之前添加的事件侦听器与现在存在于 DOM 中的 <button>
不同。 btn.click();
起作用是因为它在原始 <button>
上触发事件,您已将引用保存到原始 var btn =
.
如果 btn
是在 QUnit.test()
中定义的,那么它会引用新的 <button>
,它没有分配给它的事件侦听器。 QUnit 预计主要与 jQuery 一起使用,因此它可能 jQuery .clone()
, which can be set to copy the jQuery based event listeners, but not the vanilla JavaScript listeners, because the underlying Node.cloneNode()
没有那种能力。
您可以确认原来的btn
在QUnit.test()
中被console.log(btn.parentNode.parentNode);
与DOM断开了连接。原始 btn
输出 null
,但 QUnit.test()
中存在的输出 <body>
。为了证明这一点,在 运行 测试之前确定的 btn
在下面的代码中被分配给 btnBeforeQUnit
。
QUnit 克隆 HTML 内容是处理使测试彼此独立的好方法,但应记录在案。通常希望单元测试是独立的。毕竟,可能会有更改 DOM 结构的测试,应该在测试之间恢复。
但是,由于某种原因,QUnit 没有恢复 QUnit.test()
末尾的原始 DOM 个元素。文档表明它应该在下一次测试之前重新克隆原始文件,但在测试完成后它不会恢复原始文件。
除了btnBeforeQUnit
和btn
之外,btnAfterQUnit
和btnDelayedAfterQUnit
的二级父级也会输出到控制台,以更准确地演示何时DOM 替换发生在异步执行提供给 QUnit.test()
.
// code under test
document.getElementById('saveButton').addEventListener('click',save);
function save() {
console.log('save clicked');
}
// QUnit tests
(function () {
"use strict";
// with this line here click works
var btnBeforeQUnit = document.getElementById('saveButton');
QUnit.test("click save", function (assert) {
// with this line no click, comment it to test the working variant
var btn = document.getElementById('saveButton');
var btnInsideQUnit = btn;
btn.click(); // will it click?
console.log('btnBeforeQUnit.parentNode.parentNode', btnBeforeQUnit.parentNode.parentNode);
console.log('btnInsideQUnit.parentNode.parentNode', btnInsideQUnit.parentNode.parentNode)
assert.ok(btn !== null , 'buttin found');
});
var btnAfterQUnit = document.getElementById('saveButton');
console.log('btnAfterQUnit.parentNode.parentNode', btnAfterQUnit.parentNode.parentNode);
setTimeout(function() {
var btnDelayedAfterQUnit = document.getElementById('saveButton');
console.log('btnDelayedAfterQUnit.parentNode.parentNode', btnAfterQUnit.parentNode.parentNode);
}, 1000);
}());
<script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script>
<div id="qunit"></div>
<div id="qunit-fixture">
<button id="saveButton">test</button>
</div>
调整此行为
您可以通过将 QUnit.config.fixture
设置为 null
来获得您期望的行为:
QUnit.config.fixture = null;
QUnit.config.fixture (string) | default: undefined
Defines the HTML content to use in the fixture container which is reset at the start of each test.
By default QUnit will use whatever the starting content of #qunit-fixture is as the fixture reset. If you do not want the fixture to be reset in between tests, set the value to null.
但是,将此选项设置为 null
时应小心。这将意味着 DOM 对于所有设置为 null
的 运行 测试不是独立的。换句话说,您在一个测试中对 DOM 所做的更改将影响其他测试中的更改。
IMO,QUnit.test()
在克隆 DOM 中的整体行为没有明确记录。肯定应该在主文档中提到一些行为。特别是副作用。应该明确提及现有的事件侦听器,但我没有在 QUinit 文档中找到任何明确描述此过程及其影响的内容。
以下是相同的代码,但添加了 QUnit.config.fixture = null;
。如您所见,"save clicked" 是从测试输出到控制台的,而它不是原始输出。
// code under test
document.getElementById('saveButton').addEventListener('click',save);
function save() {
console.log('save clicked');
}
// QUnit tests
(function () {
"use strict";
QUnit.config.fixture = null;
// with this line here click works
var btnBeforeQUnit = document.getElementById('saveButton');
QUnit.test("click save", function (assert) {
// with this line no click, comment it to test the working variant
var btn = document.getElementById('saveButton');
var btnInsideQUnit = btn;
btn.click(); // will it click?
console.log('btnBeforeQUnit.parentNode.parentNode', btnBeforeQUnit.parentNode.parentNode);
console.log('btnInsideQUnit.parentNode.parentNode', btnInsideQUnit.parentNode.parentNode)
assert.ok(btn !== null , 'buttin found');
});
var btnAfterQUnit = document.getElementById('saveButton');
console.log('btnAfterQUnit.parentNode.parentNode', btnAfterQUnit.parentNode.parentNode);
setTimeout(function() {
var btnDelayedAfterQUnit = document.getElementById('saveButton');
console.log('btnDelayedAfterQUnit.parentNode.parentNode', btnAfterQUnit.parentNode.parentNode);
}, 1000);
}());
<script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script>
<div id="qunit"></div>
<div id="qunit-fixture">
<button id="saveButton">test</button>
</div>
或者,为您的预定义事件侦听器使用 HTML 属性
对于您遇到的特定问题,即希望在测试之前设置事件侦听器,您可以使用 HTML 属性来定义侦听器(例如,在您的案例中为 onclick="save()"
.
// code under test
document.getElementById('saveButton').addEventListener('click',save);
function save() {
console.log('save clicked');
}
// QUnit tests
(function () {
"use strict";
// with this line here click works
var btnBeforeQUnit = document.getElementById('saveButton');
QUnit.test("click save", function (assert) {
// with this line no click, comment it to test the working variant
var btn = document.getElementById('saveButton');
var btnInsideQUnit = btn;
btn.click(); // will it click?
console.log('btnBeforeQUnit.parentNode.parentNode', btnBeforeQUnit.parentNode.parentNode);
console.log('btnInsideQUnit.parentNode.parentNode', btnInsideQUnit.parentNode.parentNode)
assert.ok(btn !== null , 'buttin found');
});
var btnAfterQUnit = document.getElementById('saveButton');
console.log('btnAfterQUnit.parentNode.parentNode', btnAfterQUnit.parentNode.parentNode);
setTimeout(function() {
var btnDelayedAfterQUnit = document.getElementById('saveButton');
console.log('btnDelayedAfterQUnit.parentNode.parentNode', btnAfterQUnit.parentNode.parentNode);
}, 1000);
}());
<script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script>
<div id="qunit"></div>
<div id="qunit-fixture">
<button id="saveButton" onclick="save()">test</button>
</div>