"use strict";
// Loads the compiled bundle (html/js/app.js) into a jsdom window so the
// global-script UI code can be driven from Node's test runner.
const fs = require("fs");
const path = require("path");
const vm = require("vm");
const {JSDOM, VirtualConsole} = require("jsdom");
const BUNDLE_PATH = path.join(__dirname, "..", "html", "js", "app.js");
// A trimmed copy of index.html: only the ids the bundle looks up.
const SHELL_HTML = `
xTeVe
`;
function readBundle() {
return fs.readFileSync(BUNDLE_PATH, "utf8");
}
class FakeWebSocket {
constructor(url) {
this.url = url;
this.readyState = FakeWebSocket.CONNECTING;
this.sent = [];
this.onopen = null;
this.onmessage = null;
this.onerror = null;
this.onclose = null;
FakeWebSocket.instances.push(this);
}
send(data) {
this.sent.push(data);
}
close() {
this.readyState = FakeWebSocket.CLOSED;
}
}
FakeWebSocket.CONNECTING = 0;
FakeWebSocket.OPEN = 1;
FakeWebSocket.CLOSING = 2;
FakeWebSocket.CLOSED = 3;
FakeWebSocket.instances = [];
// loadApp evaluates the bundle in a fresh window. Options:
// server - object assigned to the global SERVER after the bundle runs
// html - alternative document markup
function loadApp(options) {
options = options || {};
// The UI logs freely; keep that out of the test output but keep jsdom's
// own errors (unimplemented APIs, uncaught handler exceptions) visible.
const jsdomErrors = [];
const virtualConsole = new VirtualConsole();
virtualConsole.on("jsdomError", err => { jsdomErrors.push(err); });
const dom = new JSDOM(options.html || SHELL_HTML, {
url: "http://localhost:34400/",
runScripts: "dangerously",
pretendToBeVisual: true,
virtualConsole,
});
const window = dom.window;
// jsdom does not implement innerText; the UI uses it as a textContent alias.
Object.defineProperty(window.HTMLElement.prototype, "innerText", {
configurable: true,
get() { return this.textContent; },
set(value) { this.textContent = value; },
});
const alerts = [];
const confirms = [];
window.alert = function(message) { alerts.push(message); };
window.confirm = function(message) { confirms.push(message); return true; };
FakeWebSocket.instances = [];
window.WebSocket = FakeWebSocket;
const context = dom.getInternalVMContext();
vm.runInContext(readBundle(), context, {filename: "app.js"});
// evalIn runs code inside the window, where top-level class declarations
// from the bundle are visible (they are lexical bindings, not window props).
function evalIn(code) {
return vm.runInContext(code, context);
}
if (options.server) {
window.SERVER = options.server;
}
return {dom, window, document: window.document, evalIn, alerts, confirms, jsdomErrors, sockets: FakeWebSocket.instances};
}
module.exports = {loadApp, readBundle, SHELL_HTML, BUNDLE_PATH};