2

I wrote a tiny library that manipulates DOM directly, using window and document to search and change the DOM. And now I'm trying to figure out how to test it.

So far I have tried a combination of jest + jsdom:

const jsdom = require('jsdom');
const {JSDOM} = jsdom;

const dom = new JSDOM('<!DOCTYPE html><p>Hello world</p>');

// these two lines do not seem to do anything useful:
global.window = dom.window;
global.document = dom.window.document;

test('can globally access and search the DOM', () => {
    // this doesn't work
    expect(global.document.querySelectorAll('p').length).toBe(1);
});

Am I doing something wrong here, or am I entirely wrong with the testing approach? And if so, what would be the right way to approach such testing?

I feel a bit lost here, between what's called e2e testing and Unit testing, because in my case this seems to be something right in between.

Ideally, I would want to end up with this thing tested against multiple browsers, and to get some sort of test coverage. But that's more like the next step. Right now I cannot get it to work at all.

I know how fast things are moving in the Web world, so if I'm wrong completely, I appreciate if you can point me at what's the right way to do it today.


UPDATE

Here's my complete code, after a few suggestions from @duxfox, since with this approach my library is now missing event DOMContentLoaded which is supposed to trigger its DOM processing.

const jsdom = require('jsdom');
const {JSDOM} = jsdom;

const { document } = (new JSDOM('<!DOCTYPE html><p>Hello world</p>')).window;
global.document = document;

global.window = document.defaultView;
window.console = global.console;

Object.keys(document.defaultView).forEach((property) => {
    if (typeof global[property] === 'undefined') {
        global[property] = document.defaultView[property];
    }
});

global.navigator = {
    userAgent: 'node.js'
};

// Here I'm trying to follow a suggestion from Luis:
// https://stackoverflow.com/questions/36803733/jsdom-dispatchevent-addeventlistener-doesnt-seem-to-work
window.addEventListener('DOMContentLoaded', function (ev) {
    console.log('DOMContentLoaded called!'); // this is not called.
    /*
    console.log('window click', ev.target.constructor.name,
        ev.currentTarget.constructor.name);*/
});

// loading my library here:
// const root = require('../src');

test('something', () => {

    // This now works, but my library is missing
    // event DOMContentLoaded to start processing the DOM

    expect(document.querySelectorAll('p').length).toBe(1);
});
skyboyer
  • 22,209
  • 7
  • 57
  • 64
vitaly-t
  • 24,279
  • 15
  • 116
  • 138
  • would something like firing the domContentLoaded event manually work? https://stackoverflow.com/questions/9153314/manually-dispatchevent-domcontentloaded wait sorry are you doing that already – Abdul Ahmad Jul 26 '18 at 16:57
  • or this: https://gist.github.com/chad3814/5059671 – Abdul Ahmad Jul 26 '18 at 16:58
  • @duxfox-- I don't get it, using that piece of code I get error `TypeError: jsdom.env is not a function`. I'm using `jsdom` version `11.11.0`. – vitaly-t Jul 26 '18 at 17:06
  • @duxfox-- LOL, just saw the last comment there: `Would you please redo the example using the new API? jsdom.env is deprecated as of jsdom v10.`. So, this doesn't work. What is the correct approach with the latest jsdom then? :) – vitaly-t Jul 26 '18 at 17:08
  • lol idk, I saw that too, was just looking it up – Abdul Ahmad Jul 26 '18 at 17:09
  • what about this thing: https://www.npmjs.com/package/node-jsdom sorry that's an old unmaintained project... – Abdul Ahmad Jul 26 '18 at 17:10
  • or this... https://stackoverflow.com/questions/45068932/jsdom-env-not-working-on-node-js-c9 lol sorry for all the links – Abdul Ahmad Jul 26 '18 at 17:15
  • did you try this code: `event = new window.Event("DOMContentLoaded", {bubbles: true}); document.dispatchEvent(event);` after the eventListener? – Abdul Ahmad Jul 26 '18 at 17:18
  • @duxfox-- Yes, that didn't work, as it works only inside browser, and not under Node.js, which overrides the `Event` object. – vitaly-t Jul 26 '18 at 17:20

1 Answers1

3

Below is a jsdom setup script that I use with mocha/chai/jsdom/enzyme

looks like the global.window should be set to

dom.window.document.defaultView

rather than

dom.window

Full script, which adds other global properties:

var jsdom = require('jsdom');
const { JSDOM } = jsdom;

const { document } = (new JSDOM('')).window;
global.document = document;

global.window = document.defaultView;
window.console = global.console;

Object.keys(document.defaultView).forEach((property) => {
  if (typeof global[property] === 'undefined') {
    global[property] = document.defaultView[property];
  }
});

global.navigator = {
  userAgent: 'node.js'
};
Abdul Ahmad
  • 9,673
  • 16
  • 64
  • 127
  • This seems to work on the surface. Right now it fails for me because inside the library it parses DOM only after initialization: `document.addEventListener('DOMContentLoaded', function () { /* process the DOM */});`. Any suggestion how solve this? – vitaly-t Jul 26 '18 at 16:34
  • not sure what you mean - what's the significance of the `addEventListener` code you pasted? is that a test case or something? – Abdul Ahmad Jul 26 '18 at 16:37
  • see this question: https://stackoverflow.com/questions/36803733/jsdom-dispatchevent-addeventlistener-doesnt-seem-to-work – Abdul Ahmad Jul 26 '18 at 16:38
  • `document.addEventListener('DOMContentLoaded', function () {` is to start processing the DOM after the document has finished loading. But in our case that happens before the library is included. And If I include the library first instead, then it won't work because the global variables are not set up yet. So it's like a race thing. – vitaly-t Jul 26 '18 at 16:38
  • ah I see what you're saying - can you post more of your code in the question so I can see it in context with the event listener etc.. maybe we can think of something – Abdul Ahmad Jul 26 '18 at 16:43
  • 1
    I am beginng to get the feeling of what should be the right approach, but I am yet to try and manage it work. I think, the best way is probably to use `JSDOM.fromFile` to load an HTML file that has all the library use cases inside it, but most importantly - include my library from inside it. Then, in theory, everything should just work. That's what I am trying to do right now... – vitaly-t Jul 26 '18 at 17:22