run-dom-tests
Run mocha + chai + chai-dom tests on node.js using a virtual DOM.
Install
npm install -g run-dom-tests
run-dom-tests test.json
JSON file format: { html, tests }
Test
npm test
Usage sample
Document testing
You can make assertions about the document
, both based on its plain, static structure - _originalDocument_
- or its dynamic structure document
. For example, given the following HTML...
<html>
<head>
<script>
function insertSection() {
const secondDiv = document.getElementById("second");
const section = document.createElement("section");
document.body.insertBefore(section, secondDiv);
}
document.getElementById("insert-section").addEventListener("click", insertSection);
insertSection();
</script>
</head>
<body>
<p id="first">
This is a paragraph
</p>
<button id="insert-section">Insert a new section</button>
<p id="second">
This is another paragraph
</p>
</body>
</html>
...you can write the following tests:
describe("DOM API", function() {
// you can validate the static structure of the DOM...
context("static dom", () => {
// ...both using plain chai https://www.chaijs.com/ assertions...
it("works with plain assertions", () => {
_originalDocument_.getElementsByTagName("p").length.should.eql(2);
document.getElementsByTagName("p").length.should.eql(2);
})
// ...or chai-dom https://www.chaijs.com/plugins/chai-dom/ assertions:
it("works with mocha-chai assertions", () => {
_originalDocument_.body.should.contain("p");
_originalDocument_.body.should.not.contain("section");
document.getElementsByTagName("p").should.have.length(2);
document.getElementById("first").should.exist;
document.getElementById("first").should.have.trimmed.text("This is a paragraph");
})
})
// you can also validate the
// dynamic structure of the DOM just after the initial script tags execution
describe("script tags", () => {
it("inserts dom elements", () => {
_originalDocument_.getElementsByTagName("section").should.have.length(0);
document.getElementsByTagName("section").should.have.length(1);
});
})
// finally you can interact with the dynamic dom
// through events and make assertions about them
describe("event handlers", () => {
beforeEach(() => {
// if your test mutates the DOM
// you will need to reset document to a fresh state
// before each test
_resetDocument_();
});
it("inserts dom elements after a single event", () => {
const button = document.getElementById("insert-section");
_dispatch_("click", button);
document.getElementsByTagName("section").should.have.length(2);
});
it("inserts dom elements after multiple events", () => {
const button = document.getElementById("insert-section");
_dispatch_("click", button);
_dispatch_("click", button);
_dispatch_("click", button);
document.getElementsByTagName("section").should.have.length(4);
});
});
});
User interactions testing
User interactions like alert
and prompt
can be tested by stubbing user responses using _stubXxxResponse_
and checking prompted messages with _shiftXxxMessage_
.
For example given the following HTML document...
<html>
<head>
<title>events</title>
</head>
<body>
<button id="prompt-and-alert">Prompt and Alert</button>
<button id="confirm-and-alert">Confirm and Alert</button>
<script>
document.querySelector("#prompt-and-alert").addEventListener("click", () => {
let name = prompt("Please tell us who you are");
alert("Hello " + name);
});
document.querySelector("#confirm-and-alert").addEventListener("click", () => {
let result = confirm("Are you really sure?");
if (result) {
alert("Yay");
} else {
alert("Boo");
}
});
</script>
</body>
</html>
...you can write the following user interaction tests:
describe("User interactions", () => {
beforeEach(() => {
// call before each test in
// order to clear user interactions registered and tested
// through _stubXxxResponse_ and _shiftXxxMessage_
_resetUserInteractions_();
// if you also need to reset the document state
// you can call `_resetDocument_()`
})
it("allows stubbing sequential confirm interaction", function() {
// stub responses before calling the code
_stubConfirmResponse_(false);
_stubConfirmResponse_(true);
// fire the actual code
// by simulating events
_dispatch_("click", document.querySelector("#confirm-and-alert"));
_dispatch_("click", document.querySelector("#confirm-and-alert"));
// run assertions
_alertMessagesCount_().should.eql(2);
_confirmMessagesCount_().should.eql(2);
"Are you really sure?".should.eql(_shiftConfirmMessage_());
"Boo".should.eql(_shiftAlertMessage_());
"Are you really sure?".should.eql(_shiftConfirmMessage_());
"Yay".should.eql(_shiftAlertMessage_());
_alertMessagesCount_().should.eql(0);
_confirmMessagesCount_().should.eql(0);
});
it("allows stubbing sequential prompt interaction", function() {
// stub responses before calling the code
_stubPromptResponse_("Node");
_stubPromptResponse_("Mumuki");
_stubPromptResponse_("JS");
// fire the actual code
// by simulating events
_dispatch_("click", document.querySelector("#prompt-and-alert"));
_dispatch_("click", document.querySelector("#prompt-and-alert"));
_dispatch_("click", document.querySelector("#prompt-and-alert"));
// run assertions
_alertMessagesCount_().should.eql(3);
_promptMessagesCount_().should.eql(3);
"Hello Node".should.eql(_shiftAlertMessage_());
"Hello Mumuki".should.eql(_shiftAlertMessage_());
"Hello JS".should.eql(_shiftAlertMessage_());
_alertMessagesCount_().should.eql(0);
_promptMessagesCount_().should.eql(3);
});
});
HTTP interactions testing
HTTP Interaction tests are are built on top of nock, using the _nock_
object.
Given the following HTML document...
<html>
<head>
<title>ajax</title>
<script>
document.addEventListener("DOMContentLoaded", () => {
document.querySelector("#get-data").addEventListener("click", () => {
fetch("https://some-domain.com/some-data.json")
.then((response) => {
return response.json();
})
.then((data) => {
document.querySelector("#data").innerHTML = data.content;
});
});
});
</script>
</head>
<body>
<div>
<button id="get-data">GET DATA NOW!</button>
</div>
<h1>Remote data:</h1>
<pre id="data">Nothing yet...</pre>
</body>
</html>
...you can write the following HTTP interaction tests:
describe("HTTP Interactions", function() {
beforeEach(() => {
// resets all user interactions,
// dom state and http interactions
_resetAll_();
// if only http interactions need to be reseted,
// call _resetHttpInteractions_() instead
});
it("shows the downloaded content when the button is clicked", function(done) {
document.querySelector("#data").innerHTML.should.eql("Nothing yet...");
const mockedGet = _nock_("https://some-domain.com/")
.get("/some-data.json")
.reply(200, { content: "Some remote data" });
_dispatch_('click', document.querySelector("#get-data"));
_waitFor_(() => mockedGet.isDone(), () => {
document.querySelector("#data").innerHTML.should.eql("Some remote data");
done();
});
});
});
Reference
Members
- _nock_
-
nock object for mocking http interactions
- _waitFor_
-
Waits for a condition to occur, and then executes an action. This function will check for the condition with a period of
WAIT_FOR_TIMEOUT
. oldDocument-
_originalDocument_ :
HTMLDocument
-
The original HTML document, before any JavaScript actions are executed
Constants
-
WAIT_FOR_TIMEOUT :
number
-
Polling period of
_waitFor_
Functions
_wait_for_()- _dispatch_(type, node)
-
Simulates the dispatch of an event of the given type to the given node
- _stubConfirmResponse_(response)
-
Enqueues an stubbed confirm window response message.
This function must be called in order, before the first alert
confirm
is performed - _stubPromptResponse_(response)
-
Enqueues an stubbed a prompt window response message
This function must be called in order, before the first alert
prompt
is performed -
_shiftAlertMessage_() ⇒
string
-
Dequeues the first pending alert message to check.
Subsequent calls to this function will produce different results. When there are no more alert messages to dequeue, undefined is returned
-
_shiftConfirmMessage_() ⇒
boolean
-
Dequeues the first pending confirm message to check.
Subsequent calls to this function will produce different results. When there are no more confirm messages to dequeue, undefined is returned
-
_shiftPromptMessage_() ⇒
string
-
Dequeues the first pending prompt message to check.
Subsequent calls to this function will produce different results. When there are no more prompt messages to dequeue, undefined is returned
-
_alertMessagesCount_() ⇒
number
-
Answers the number of the pending alert message to check
-
_confirmMessagesCount_() ⇒
number
-
Answers the number of the pending confirm message to check
-
_promptMessagesCount_() ⇒
number
-
Answers the number of the pending prompt message to check
- _resetUserInteractions_()
-
Reset all stubs and messages
- _resetHttpInteractions_()
-
Reset nock state
- _resetDocument_()
-
Resets the
document
to its original state, discarding every document polyfill and then runs its scripts again.window
is not cleared. - _resetAll_()
-
Resets the document, interactions and nock state
_nock_
nock object for mocking http interactions
Kind: global variable See: https://github.com/nock/nock
_waitFor_
Waits for a condition to occur, and then executes an action.
This function will check for the condition with a period of WAIT_FOR_TIMEOUT
.
Kind: global variable See: WAIT_FOR_TIMEOUT
Param | Type | Description |
---|---|---|
condition | function |
the condition to wait |
action | function |
the action to execute |
oldDocument
Deprecated
HTMLDocument
_originalDocument_ : The original HTML document, before any JavaScript actions are executed
number
WAIT_FOR_TIMEOUT : Polling period of _waitFor_
_wait_for_()
Deprecated
_dispatch_(type, node)
Simulates the dispatch of an event of the given type to the given node
Kind: global function
Param | Type | Description |
---|---|---|
type | string |
the event type, such as click or DOMContentLoaded
|
node | HTMLElement |
the simulated event target. document by default |
_stubConfirmResponse_(response)
Enqueues an stubbed confirm window response message.
This function must be called in order, before the first alert confirm
is performed
Kind: global function
Param | Type | Description |
---|---|---|
response | string |
the stubbed response of the confirm window |
_stubPromptResponse_(response)
Enqueues an stubbed a prompt window response message
This function must be called in order, before the first alert prompt
is performed
Kind: global function
Param | Type | Description |
---|---|---|
response | string |
the stubbed response of the prompt window |
string
_shiftAlertMessage_() ⇒ Dequeues the first pending alert message to check.
Subsequent calls to this function will produce different results. When there are no more alert messages to dequeue, undefined is returned
Kind: global function
Returns: string
- the first pending alert message
boolean
_shiftConfirmMessage_() ⇒ Dequeues the first pending confirm message to check.
Subsequent calls to this function will produce different results. When there are no more confirm messages to dequeue, undefined is returned
Kind: global function
Returns: boolean
- the first pending confirm message
string
_shiftPromptMessage_() ⇒ Dequeues the first pending prompt message to check.
Subsequent calls to this function will produce different results. When there are no more prompt messages to dequeue, undefined is returned
Kind: global function
Returns: string
- the first pending prompt message
number
_alertMessagesCount_() ⇒ Answers the number of the pending alert message to check
Kind: global function
Returns: number
- the pending messages count to check
number
_confirmMessagesCount_() ⇒ Answers the number of the pending confirm message to check
Kind: global function
Returns: number
- the pending messages count to check
number
_promptMessagesCount_() ⇒ Answers the number of the pending prompt message to check
Kind: global function
Returns: number
- the pending messages count to check
_resetUserInteractions_()
Reset all stubs and messages
_resetHttpInteractions_()
Reset nock state
_resetDocument_()
Resets the document
to its original state,
discarding every document polyfill
and then runs its scripts again.
window
is not cleared.
_resetAll_()
Resets the document, interactions and nock state
Kind: global function