Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ReactWrapper.unmount() throws Cannot render markup in a worker thread. #395

Closed
tleunen opened this issue May 15, 2016 · 22 comments
Closed
Labels

Comments

@tleunen
Copy link

tleunen commented May 15, 2016

For some reasons, when I call unmount on a react wrapper node, React throws an error saying I don't have a DOM.

jsdom is initialized, before importing React and enzyme, but I still get this error on unmount.

I even checked if the dom was really initialized properly, with the same check React does, and it's evaluated as true so everything looks ok.

!!(
  typeof window !== 'undefined' &&
  window.document &&
  window.document.createElement
)

Any idea what could be the problem?

@aweary
Copy link
Collaborator

aweary commented May 15, 2016

Can you share the code for the test that is throwing this error? Thanks!

@tleunen
Copy link
Author

tleunen commented May 16, 2016

I tried to find the root cause of this and I think I found it.
ES6 imports were used in the setup file for jsdom, and inside it React was imported as well and it seems that it was causing the issue.
Not sure why es6 imports were used in this file instead of commonjs :) I'm surprised it even worked before with es6 imports. The file is probably also run with the babel compilers, but then it seems there's an issue with React.

Anyway, problem solved when modifying in simple commonjs require.

@tleunen tleunen closed this as completed May 16, 2016
@aweary
Copy link
Collaborator

aweary commented May 16, 2016

Thanks for the update @tleunen!

@aweary aweary added the invalid label May 16, 2016
@tomkis
Copy link

tomkis commented May 30, 2016

@tleunen Can you elaborate on this? I don't understand what was the fix.

Anyway, having the same issue basically, calling unount on componet throws this invariant.

it('doesnt work', () => {
  const dispatchSpy = spy();
  const globalDispatchSpy = spy(mockStore, 'dispatch');
  const WrappedView = dfView(syncMockFetch, MOCK_SCHEMA, MockView);

  const cmp = mount(
    <WrappedView
      model={{}}
      store={mockStore}
      dispatch={dispatchSpy}
    />
  );

  cmp.unmount();
});

Of course neither document nor window is empty (using jsdom).

@tleunen
Copy link
Author

tleunen commented May 30, 2016

I had import X from 'Y' in the jsdom setup file and after replacing it with const X = require('Y'), everything worked

@tomkis
Copy link

tomkis commented May 30, 2016

that's weird

  1. I don't see how these two are different
  2. Doesn't work for me anyway...

@aweary
Copy link
Collaborator

aweary commented May 30, 2016

@tomkis1 can you share a small example reproducing your issue (including the content of the mounted component)? I'm in Paris for ReactEurope this week but I can look at this when I get back if no one else does.

@tomkis
Copy link

tomkis commented May 30, 2016

import React from 'react';
import jsdom from 'jsdom';
import { mount } from 'enzyme';

const doc = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.document = doc;
global.window = doc.defaultView;

describe('foo', () => {
  it('works', () => {
    mount(<div>Foo Bar</div>);
  });

  it('doesnt work', () => {
    const mounted = mount(<div>Foo Bar</div>);
    mounted.unmount();
  });
});

stacktrace:

  1) foo doesnt work:
     Invariant Violation: dangerouslyReplaceNodeWithMarkup(...): Cannot render markup in a worker thread. Make sure `window` and `document` are available globally before requiring React when unit testing or use ReactDOMServer.renderToString() for server rendering.
      at invariant (node_modules/fbjs/lib/invariant.js:38:15)
      at Object.Danger.dangerouslyReplaceNodeWithMarkup (node_modules/react/lib/Danger.js:130:79)
      at Object.wrapper [as replaceNodeWithMarkup] (node_modules/react/lib/ReactPerf.js:66:21)
      at [object Object].ReactCompositeComponentMixin._replaceNodeWithMarkup (node_modules/react/lib/ReactCompositeComponent.js:679:31)
      at [object Object].ReactCompositeComponentMixin._updateRenderedComponent (node_modules/react/lib/ReactCompositeComponent.js:669:12)
      at [object Object].ReactCompositeComponentMixin._performComponentUpdate (node_modules/react/lib/ReactCompositeComponent.js:643:10)
      at [object Object].ReactCompositeComponentMixin.updateComponent (node_modules/react/lib/ReactCompositeComponent.js:572:12)
      at [object Object].wrapper [as updateComponent] (node_modules/react/lib/ReactPerf.js:66:21)
      at [object Object].ReactCompositeComponentMixin.performUpdateIfNecessary (node_modules/react/lib/ReactCompositeComponent.js:511:12)
      at Object.ReactReconciler.performUpdateIfNecessary (node_modules/react/lib/ReactReconciler.js:122:22)
      at runBatchedUpdates (node_modules/react/lib/ReactUpdates.js:143:21)
      at ReactReconcileTransaction.Mixin.perform (node_modules/react/lib/Transaction.js:136:20)
      at ReactUpdatesFlushTransaction.Mixin.perform (node_modules/react/lib/Transaction.js:136:20)
      at ReactUpdatesFlushTransaction._assign.perform (node_modules/react/lib/ReactUpdates.js:89:38)
      at Object.flushBatchedUpdates (node_modules/react/lib/ReactUpdates.js:165:19)
      at Object.wrapper [as flushBatchedUpdates] (node_modules/react/lib/ReactPerf.js:66:21)
      at ReactDefaultBatchingStrategyTransaction.Mixin.closeAll (node_modules/react/lib/Transaction.js:202:25)
      at ReactDefaultBatchingStrategyTransaction.Mixin.perform (node_modules/react/lib/Transaction.js:149:16)
      at Object.ReactDefaultBatchingStrategy.batchedUpdates (node_modules/react/lib/ReactDefaultBatchingStrategy.js:63:19)
      at Object.enqueueUpdate (node_modules/react/lib/ReactUpdates.js:194:22)
      at enqueueUpdate (node_modules/react/lib/ReactUpdateQueue.js:22:16)
      at Object.ReactUpdateQueue.enqueueSetState (node_modules/react/lib/ReactUpdateQueue.js:201:5)
      at [object Object].ReactComponent.setState (node_modules/react/lib/ReactComponent.js:67:16)
      at ReactWrapper.<anonymous> (node_modules/enzyme/build/ReactWrapper.js:215:28)
      at ReactWrapper.single (node_modules/enzyme/build/ReactWrapper.js:1297:19)
      at ReactWrapper.unmount (node_modules/enzyme/build/ReactWrapper.js:214:14)
      at Context.<anonymous> (foo.spec.js:11:1)

@matheusrocha89
Copy link

Somebody found a solution for this problem?

@tomkis
Copy link

tomkis commented Jun 28, 2016

Yes, @matthewgertner found a workaround. Can you please elaborate?

@matheusrocha89
Copy link

I am having the same problem you had @tomkis1 , when I try to unmount the component I receive the same error message about the Invariant Violation

@nfcampos
Copy link
Collaborator

Sorry about the delay in posting a solution, here goes:

  • react-dom needs navigator to be a global (enzyme imports react-dom)
  • both react and enzyme imports need to happen after the globals are attached (so they need to be require calls, because imports happen before anything else irrespective of where they appear in the file
import jsdom from 'jsdom';

const doc = jsdom.jsdom('');
global.document = doc;
global.window = doc.defaultView;
global.navigator = {
  userAgent: 'node.js',
}

const React = require('react')
const mount = require('enzyme').mount

describe('foo', () => {
  it('works', () => {
    mount(<div>Foo Bar</div>);
  });

  it('doesnt work', () => {
    const mounted = mount(<div>Foo Bar</div>);
    mounted.unmount();
  });
});

@matheusrocha89
Copy link

@nfcampos The import happens even before the configs I put on setup.js when I run the tests?

@nfcampos
Copy link
Collaborator

it's something like this:

import React from 'react'
console.log('hey')
import jsdom from 'jsdom'
console.log('boo')
  1. react is imported
  2. jsdom is imported
  3. hey is logged
  4. boo is logged
    So all imports run in the order you wrote them, but they all run before anything else in this file.

as for setup.js, are you talking about running mocha --require setup.js test.js? if yes, then anything in setup.js will run before anything in test.js (which is why we recommend jsdom setup happens in a separate file, to avoid these situations

@matheusrocha89
Copy link

That's my scenario, I put the jsdom config on setup.js and just import the mount and React on the test files. So I don't know why still happens.

@tp
Copy link

tp commented Jul 6, 2016

Running into the same issue with TypeScript transpiled files, that make it hard to require/order the imports. Is there any other known workaround? Really surprised this only happens when unmounting, since everything else works fine.

@kentcdodds
Copy link
Contributor

kentcdodds commented Aug 12, 2016

I figured out what my issue was:

Here's my setup-test-env.js file now:

/**
 * This is used to set up the environment that's needed for most
 * of the unit tests for the project which includes polyfilling,
 * chai setup, and initializing the DOM with jsdom
 */
import 'babel-polyfill'
import chai from 'chai'
import sinonChai from 'sinon-chai'
import {jsdom} from 'jsdom'

chai.use(sinonChai)

global.document = jsdom('<body></body>')
global.window = document.defaultView
global.navigator = window.navigator
global.expect = chai.expect

// this has to happen after the globals are set up because `chai-enzyme`
// will require `enzyme`, which requires `react`, which ultimately
// requires `fbjs/lib/ExecutionEnvironment` which (at require time) will
// attempt to determine the current environment (this is where it checks
// for whether the globals are present). Hence, the globals need to be
// initialized before requiring `chai-enzyme`.
chai.use(require('chai-enzyme')())

@j-funk
Copy link

j-funk commented Sep 11, 2016

I don't think this issue should be closed.

@butlermd
Copy link

butlermd commented Oct 3, 2016

I agree with @j-funk, something appears to be wrong here. If nothing else, this seems like it ought to be addressed within the documentation?

@jackbrown
Copy link
Contributor

jackbrown commented Feb 13, 2017

In addition to @kentcdodds fixes, I managed to fix this issue by changing the order of my test-setup.js to the following:

/**
 * This is used to set up the environment that's needed for most
 * of the unit tests for the project which includes polyfilling,
 * chai setup, and initializing the DOM with jsdom
 */
import 'babel-polyfill'
import chai from 'chai'
import sinonChai from 'sinon-chai'
import {jsdom} from 'jsdom'

chai.use(sinonChai)

global.document = jsdom('<body></body>')
global.window = document.defaultView
global.navigator = window.navigator
global.expect = chai.expect

// this has to go after the window, document and navigator are set up
global.React = require('React');
global.TestUtils = require('react-addons-test-utils');

// this has to happen after the globals are set up because `chai-enzyme`
// will require `enzyme`, which requires `react`, which ultimately
// requires `fbjs/lib/ExecutionEnvironment` which (at require time) will
// attempt to determine the current environment (this is where it checks
// for whether the globals are present). Hence, the globals need to be
// initialized before requiring `chai-enzyme`.
chai.use(require('chai-enzyme')())

@peter-mouland
Copy link
Contributor

thanks @jackbrown . the last point was essential for it to work for me

@nabati
Copy link

nabati commented Feb 28, 2018

To possibly help future debuggers.

The equivalent of the line below should also be placed after jsdom.
const Adapter = require('enzyme-adapter-react-15');

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests