Jest-dom: Cannot run in Node using jsdom fragment due to missing global window object

Created on 11 Oct 2019  路  11Comments  路  Source: testing-library/jest-dom

  • @testing-library/jest-dom version: 4.1.2
  • node version: 10.15.0
  • npm (or yarn) version: yarn 1.13.0

Relevant code or config:

expect(getByTestId('testid')).toHaveAttribute('disabled')

What you did:

Running a test in a node environment using a JSDOM fragment rather than a full JSDOM container. (This was produced by https://github.com/marko-js/testing-library)

What happened:

Got the following error:

received value must be an HTMLElement or an SVGElement.
Received has type:  object

I tracked it down to this line:
https://github.com/testing-library/jest-dom/blob/master/src/utils.js#L47

There is no defaultView object defined when using a JSDOM fragement.
This is also mentioned in https://github.com/testing-library/dom-testing-library/pull/377, where they question the need to have a window object:

It seems like a valid question. Does jest-dom need to require a global window object?

Reproduction:

Problem description:

Cannot use jest-dom within node when using a JSDOM fragment

Suggested solution:

Remove the requirement for having a defaultView. Or get dom-testing-library to add one.

All 11 comments

I'm thinking that the issue here is that the DOM element is not attached to the document which is why this is a problem. I hadn't considered that issue coming up, but it makes sense.

I'm not sure what to do about this. I think this would be solved if we just required that everyone using these libraries have a globally accessible window and document so we didn't have to retrieve that from the DOM elements.

cc @eps1lon thoughts?

@kentcdodds another option would be to walk the constructors prototype chain and check for the name "HTMLElement" or "SVGElement" like so:

function checkHtmlElement(htmlElement, ...args) {
  if (
    !instanceOf(htmlElement, 'HTMLElement') &&
    !instanceOf(htmlElement, 'SVGElement')
  ) {
    throw new HtmlElementTypeError(htmlElement, ...args)
  }
}

function instanceOf(obj, name) {
  if (obj) {
    let constructor = obj.constructor
    while (constructor) {
      if (constructor.name === name) {
        return true
      }

      constructor = Object.getPrototypeOf(constructor)
    }
  }

  return false
}

But this issue would also present itself in toBeVisible and toHaveStyle.
I think the simplest approach for us from the @marko/testing-library side of things would just be to not use JSDOM.fragment for testing the server rendered markup. This is somewhat lame since In my opinion server side tests shouldn't be able to interact with the window or trigger events (those should happen in a browser context, not on the server side), but at least it would work for the basic cases and require less work on the side of jest-dom.

I would prefer the window and document not to be required globally as this is going to cause the tests in our case to expose these globals even though we are only using jsdom to do some validation on html strings.

I'm fine with that personally.

That works for me too. Thanks @DylanPiercey .
I'll let @kentcdodds close the issue, if you think its not a blocker for other cases where someone may use a JSDOM fragment.

Released @marko/[email protected] which ensures there is a defaultView. So it's all good on our side of things 馃槃.

Looks ok 馃憤

This is my current stance:

  1. Please follow installation instructions of the library you use. In particular with jsdom
    > Note that we strongly advise against trying to "execute scripts" by mashing together the jsdom and Node global environments (e.g. by doing global.window = dom.window), and then executing scripts or test code inside the Node global environment. Instead, you should treat jsdom like you would a browser, and run all scripts and tests that need access to a DOM inside the jsdom environment, using window.eval or runScripts: "dangerously". This might require, for example, creating a browserify bundle to execute as a