Testing framework for offline testing of index.html and script.js

Took me a while but I finally cobbled something together to unit test my web page based setup process (index.html and script.js files). It’s been a massive pain point for me having to test something device specific like RPC functionality on my device itself, so hopefully this helps someone out.

My setup page is pretty basic, there are some elements and as you click/tick elements things are supposed to happen. There is also some async RPC commands happening to get and save config to the device.

I’m using the JavaScript testing framework Jest. You’ll need to follow the instructions out there to get npm and jest installed - here is the config I’m using for local and CI testing along with some of the most useful tests I’ve managed to get going (like XMLHttpRequest mocking):

Example index.html:

<!DOCTYPE html>
<html>

<head>
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <link rel="icon" type="image/png" href="/favicon.ico" />
  <link rel="stylesheet" href="layout.css" type="text/css" />
  <script type="text/javascript" src="script.js"></script>
</head>

<body onload="pageLoad()">
  <lots of stuff>
  <lots more stuff>
</body>
</html>

Example script.js:

function setWifiElementState(request) {
    if (request.status === 200) {
        var staState = JSON.parse(request.responseText);

        if (staState.enable) {
            wifiIsSet = true;
            collapseElement(document.getElementById("wifi"));
        }
        else {
            expandElement(document.getElementById("wifi"));
        }
    }
    else {
        log('Error fetching WiFi state');
    }
}

function getRemoteWifiState() {
    var request = new XMLHttpRequest();
    var data = JSON.stringify({ "key": "wifi.sta.enable" });

    request.open('POST', '/rpc/Config.Get', true);
    request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
    request.onload = function () { setWifiElementState(request) };
    request.send(data);
}

module.exports = {
    setWifiElementState,
    getRemoteWifiState,
}

Example index.spec.js:

var script = require('../fs/script.js');
const fs = require('fs');
const path = require('path');
const html = fs.readFileSync(path.resolve(__dirname, '../fs/index.html'), 'utf8');

const createMockXHR = responseJSON => {
    const mockXHR = {
        open: jest.fn(),
        send: jest.fn(),
        setRequestHeader: jest.fn(),
        status: 200,
        readyState: 4,
        responseText: JSON.stringify(responseJSON || {})
    };
    return mockXHR;
};

describe('WiFi', function () {
    const oldXMLHttpRequest = window.XMLHttpRequest;
    let mockXHR = null;

    beforeEach(() => {
        document.documentElement.innerHTML = html.toString();
        mockXHR = createMockXHR();
    });

    afterEach(() => {
        jest.resetModules();
    });

    it('is set, section is collapsed', function () {
        mockXHR.responseText = JSON.stringify({
            enable: true
        });

        var wifiElement = document.getElementById("wifi");

        expect(wifiElement.style.display).toBe("block");
        script.setWifiElementState(mockXHR);
        expect(wifiElement.style.display).toBe("none");
    });
    it('is not set, section is not collapsed', function () {
        mockXHR.responseText = JSON.stringify({
            enable: false
        });

        var wifiElement = document.getElementById("wifi");

        expect(wifiElement.style.display).toBe("block");
        script.setWifiElementState(mockXHR);
        expect(wifiElement.style.display).toBe("block");
    });
});

Example package.json:

{
    "scripts": {
        "test": "CI=true jest --watch=all",
    },
    "devDependencies": {
        "jest": "^23.6.0"
    }
}

Example output: npm run test

Test Suites: 1 passed, 1 total
Tests:       35 passed, 35 total
Snapshots:   0 total
Time:        0.69s, estimated 1s
Ran all test suites.

It’s been pretty annoying getting it all working for vanilla JS as most of the examples out there are for typescript or react or something.

Would love to add to my tests if someone out there has some good mocking tricks working for them.