Run mocha + chai + chai-dom tests on node.js using a virtual DOM.
npm install -g run-dom-tests
run-dom-tests test.json
JSON file format: { html, tests }
npm test
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 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);
});
});
You can even stub inputs in plain scripts like this...
<html>
<head>
<title>events</title>
</head>
<body>
<script>
let message = prompt("insert an alert message");
alert(message || "Initial Alert")
</script>
</body>
</html>
...by using _resetDocument_()
right after calling _stubXxxResponse_()
:
describe("User Interactions", () => {
beforeEach(() => {
_resetUserInteractions_();
});
it("allows stubbing script interactions and re-run document", () => {
_stubPromptResponse_("A Stubbed Alert Message");
_resetDocument_();
_alertMessagesCount_().should.eql(1);
"A Stubbed Alert Message".should.eql(_shiftAlertMessage_());
});
})
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();
});
});
});
- _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
- WAIT_FOR_TIMEOUT :
number
Polling period of
_waitFor_
_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 object for mocking http interactions
Kind: global variable See: https://github.com/nock/nock
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 |
Deprecated
The original HTML document, before any JavaScript actions are executed
Polling period of _waitFor_
Deprecated
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 |
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 |
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 |
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
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
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
Answers the number of the pending alert message to check
Kind: global function
Returns: number
- the pending messages count to check
Answers the number of the pending confirm message to check
Kind: global function
Returns: number
- the pending messages count to check
Answers the number of the pending prompt message to check
Kind: global function
Returns: number
- the pending messages count to check
Reset all stubs and messages
Reset nock state
Resets the document
to its original state,
discarding every document polyfill
and then runs its scripts again.
window
is not cleared.
Resets the document, interactions and nock state
Kind: global function