Andrew Brown Andrew Brown - 11 months ago 63
Javascript Question

mocha: can't seem to call module's methods that use jquery from inside mocha tests

So, I am using Mocha/Chai for some testing and the item I am testing depends on jQuery. I am using jsdom to help with this. As you can see from code below I have no issue using jquery from WITHIN the mocha test itself. What I can't seem to get situated is just calling the code that uses jquery. When running this in the browser, jQuery is included in a script tag before the ACE_GA module is included so jQuery fully works there.

In examining the code snippet below, there are two lines inside the done() function that have comments with them. The first line that has a comment, simply calls the code from the ACE_GA module, and it does not work. The second line that has a comment, is re-creating some of the code from inside the ACE_GA module (that I was trying to call in the first line), and it works just fine.

To be clear, the line that works just fine

var productItems = jQuery("[data-" + ACE_GA.VALID_PRODUCT_FIELDS.prefix + "]");

is simply copying a line from the module's method I'm trying to call, which is giving me problems

var productItems = ACE_GA.HELPERS.validateOptions(ACE_GA.VALID_PRODUCT_FIELDS.prefix);

What do I need to do to be able to simply call a client library/module and have jquery work with this, instead of copying/pasting code snippets from the module into the mocha tests?

var chai = require("chai");
var jsdom = require("jsdom");
var jQuery = require("jquery");
var fs = require("fs");
var ACE_GA = require("../main");

var bodyContent = fs.readFileSync(__dirname + "/../test/test.html", "utf8", "r");
global.document = jsdom.jsdom(bodyContent);
global.window = document.defaultView;
global.jQuery = jQuery;

describe("ACE_GA.HELPERS.validateOptions", function() {

it("should select all items with a data-attribute of 'ace-ga-{something}'", function(done) {

html: bodyContent,
src: jQuery,
scripts: "",
userAgent: "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
done: function(err, window) {

console.log("err", err);
var jQuery = window.jQuery;

//var productItems = ACE_GA.HELPERS.validateOptions(ACE_GA.VALID_PRODUCT_FIELDS.prefix);
/** ^ that won't work and says jQuery requires a window with a document */

var productItems = jQuery("[data-" + ACE_GA.VALID_PRODUCT_FIELDS.prefix + "]");
/** ^ that works just fine and is copy/pasted from the non working method I'm trying to call above */




For anyone's reference, here is the exact output of the failure error I'm getting

1) ACE_GA.HELPERS.validateOptions should select all items with a data-attribute of 'ace-ga-{something}':
Uncaught Error: jQuery requires a window with a document
at module.exports (C:\Path\To\Project\dev-files\node_modules\jquery\dist\jquery.js:29:12)
at Object.validateOptions (main.js:144:15)
at Object.done (test\gaecTest.js:89:39)
at process.nextTick (C:\Path\To\Project\dev-files\node_modules\jsdom\lib\jsdom.js:320:18)
at _combinedTickCallback (internal/process/next_tick.js:67:7)
at process._tickCallback (internal/process/next_tick.js:98:9)

From what I can see, in the included module itself (ACE_GA), it's having a problem with jQuery working as I want it to from that context by itself (like defining then calling jQuery() inside mocha is okay but defining jQuery() in mocha and then calling it from inside ACE_GA module does not work the same). As stated earlier, when running this in the browser, I'm able to define/include jQuery before including the ACE_GA module. I'm attempting to do something similar here only without running it in the browser and instead running it from node with mocha.

Answer Source

The immediate reason you're running into trouble is that you're not initializing properly the jQuery instance that is loaded directly in node (by opposition to the one you load in the jsdom instance).

When you do require('jquery'), jQuery checks whether there's a document in the global scope. If so, it installs itself in the global scope. In a browser, the upshot is that it installs itself on window, as $ and jQuery. In Node.js, there is no document in the global scope by default. This is also the case in the code you show. When there is no document defined, what you get from require('jquery') is a factory from which you can build a jQuery instance. You have to pass a Window instance to that factory.

You could change your initial code to:

var chai = require("chai");
var jsdom = require("jsdom");
var makeJQ = require("jquery"); // Load the factory.
var fs = require("fs");

var bodyContent = fs.readFileSync(__dirname + "/../test/test.html", "utf8", "r");
global.document = jsdom.jsdom(bodyContent);
global.window = document.defaultView;
global.$ = global.jQuery = makeJQ(window); // Actually make a jQuery instance.

// Load this after, since it depends on jQuery being in the global space.
var ACE_GA = require("../main");

You should then ditch the second window you create with jsdom within the test and just use the one set up at the start of your test file. You can reuse it from test to test by cleaning the DOM tree between tests.