Search code examples
javascriptsystemjs

Browser test setup: How to make already global SystemJS module available to require('systemjs') of tested code?


I have code that is fairly system independent and with few system dependent lines of code runs on node.js or the browser. I already managed to setup mocha-based testing so that it works in both those environments, using the same tests and the same code modules. I also managed the browser testing environment to automatically swap calls for those very few system-dependent packages that always call the node.js version for the browser version.

So everything runs, including mocha and sinon and all tests.

Until I decided to use SystemJS not just for running the tests but also within the code itself.

The node.js part still works including the tests. The problem arises when I run the browser tests.

For those I use a index.html file that runs the tests, below it is in its entirety.

Note that this has nothing to do with any later production browser environment, at this early stage all I want is to test the code (that is purely network and storage based, no GUI stuff whatsoever, using IndexedDB on the browser and websockets on both node.js and browser) that I already have against the browser.

Key is the line - the only line that is new, everything else has worked for months

SystemJS.set('systemjs', SystemJS.newModule(SystemJS));

A similar one for sinon has worked fine for months, I hoped I could use the exact same method to make the already available SystemJS available when a module uses require('systemjs'). But it does not work.

This is what the module sees after its require:

This is what the module sees after its <code>require</code>

The actual SystemJS module contents is under that Symbol(): tt.

I'm sure the answer is extremely simple, but the documentation of SystemJS was of no help to me, which of course may very well be entirely my own fault but that's just how it is, I have to ask after two hours without a result. It took me a while to get the current setup going too, including having to read SystemJS source code, and I figured it all out by myself, but I think this tiny and very specific question might be justifiable for an SO question?

Just to complete the description of the setup (for those curious): I run it all from WebStorm, directory setup is a usual node.js project with ./test/, ./src/, ./lib/, and of course ./node_modules/. The html file is ./test/index.html and I tell WebStorm to "run" it, so WS acts as the webserver for the tests.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Browser Tests</title>
    <link href="https://cdn.rawgit.com/mochajs/mocha/v3.4.1/mocha.css" rel="stylesheet"/>
    <script src="https://cdn.rawgit.com/mochajs/mocha/v3.4.1/mocha.js"></script>
    <script src="http://sinonjs.org/releases/sinon-2.2.0.js"></script>
    <script src="../node_modules/systemjs/dist/system.js"></script>
    <style>
        #errors pre {
            width: 50em;
            margin: 2em 4em;
            padding: 1em;
            border: 1px solid red;
        }
    </style>
</head>
<body>
<div id="errors"></div>
<div id="mocha"></div>
<script>
    /*global SystemJS */
    mocha.setup('bdd');

    // This hack switches the system-dependent components so that require()
    // statements for modules with node.js specific code are exchanged for their
    // browser versions.
    const resolveOrig = SystemJS.constructor.prototype.resolve;
    SystemJS.constructor.prototype.resolve = function (key, parents) {
        return resolveOrig.call(SystemJS, key.replace('-nodejs.js', '-browser.js'), parents);
    };

    // ======================================================
    // RELEVANT SECTION (what I tried)
    // THIS WORKS:
    SystemJS.set('sinon', SystemJS.newModule(sinon));
    // THIS DOES NOT WORK:
    SystemJS.set('systemjs', SystemJS.newModule(SystemJS));
    // ======================================================

    // These are the test scripts in ./test/ that I want to run in the browser
    const mochaTestScripts = [
        // ... ABRIDGED LIST ....
        'crypto-helpers',
        'map-query',
        'object-helpers',
        'storage'
    ];

    SystemJS.config({
        map: {
            'chai': '../node_modules/chai/chai.js',
            'chai-as-promised': '../node_modules/chai-as-promised/lib/chai-as-promised.js',
            // dependency of chai-as-promised
            'check-error': '../node_modules/check-error/check-error.js',
            'js-sha256': '../node_modules/js-sha256/build/sha256.min.js'
        }
    });

    Promise.all(
        mochaTestScripts.map(testScript =>
            SystemJS.import('./' + testScript + '-test.js')
            .catch(err => {
                const div = document.getElementById('errors');
                const pre = document.createElement('pre');
                pre.appendChild(document.createTextNode('Test: ' + testScript + '\n\n' + err));
                div.appendChild(pre);
            })
        )
    )
    .then(() => {
        mocha.checkLeaks();
        mocha.globals([]);
        mocha.run();
    })
    .catch(err => {
        const div = document.getElementById('errors');
        const pre = document.createElement('pre');
        pre.appendChild(document.createTextNode('GLOBAL ERROR\n' + err));
        div.appendChild(pre);
    });
</script>
</body>
</html>


Solution

  • I solved it myself:

    I had to go to the source code of SystemJS to find the answer. The problem seems to be that the SystemJS symbol wasn't just a plain object like it normally is, where the methods are properties directly inside this object. Instead, SystemJS is an instance and the methods are on the prototype.

    What finally worked was taken right from how SystemJS used newModule internally, and the final command that worked was

    SystemJS.registry.set(
        'systemjs',
        SystemJS.newModule({default: SystemJS, __useDefault: true})
    );
    

    This replaces the line under // THIS DOES NOT WORK: in the above index.html test runner file.