I made unit test code with test utils by React.
But encountered a problem
My environment is:
Rails 4
Jasmine 2.0.0
Backbone 1.1.2
describe("cNotice", function () {
it("lol", function () {
console.log(Notice); // present
console.log(<Notice message="show me the message" />); // return Constructor
var instance = <Notice message="show me the message" />;
var component = React.addons.TestUtils.renderIntoDocument(instance);
expect(component.getDOMNode().childNodes[0].className).toBe('notice');
});
});
Error message is:
Error: Invariant Violation: addComponentAsRefTo(...): Only a ReactOwner can have refs. This usually means that you're trying to add a ref to a component that doesn't have an owner (that is, was not created inside of another component's render method). Try rendering this component inside of a new top-level component which will hold the ref.
UPDATE
This code is no problem:
describe("cNotice", function () {
var Notice = null;
beforeEach(function () { Notice = React.createClass({...}); });
it("lol", function () {
var instance = <Notice message="show me the message" />;
var component = React.addons.TestUtils.renderIntoDocument(instance);
expect(component.getDOMNode().childNodes[0].className).toBe('notice');
});
});
But I want to import Notice component from external file.
SOLVED
i used window namespace.
imported Notice component from external file
describe("cNotice", function () {
it("lol", function () {
var component = React.addons.TestUtils.renderIntoDocument(window.Notice({ message: "show me the message" }));
expect(component.getDOMNode().childNodes[0].className).toBe('notice');
});
});
Related
I need to render React components on the server for SEO. My component fetches data in ComponentWillMount, based on the query parameters - but on the server (Node 4.0.0), SetState fails in the request's callback. The error can be reproduced with a simpler setTimeout too, as in the code example below.
I have found numerous discussion on the web relating to complications between React and server-side rendering. I'm working on two work-around approaches:
removing all ajax requests from the server, instead rendering the result of the request directly into a global variable embedded in the first-serve HTML
moving the ajax request prior to initialization of the React components, on the server only (the request would still have to live in ComponentWillMount (or ComponentDidMount) for the client version.
Please let me know if there is an alternative or recommended approach instead.
var React = require('react');
// Reproduced in React 0.13.3 and 0.14.0-beta1
var ReactDOMServer = require("react-dom/server");
var A = React.createClass({
componentWillMount: function() {
var _this = this;
// for example an ajax call to fetch data based on request parameters:
setTimeout(function(err, res) {
// state is set based on results
_this.setState({ a: 1 });
}, 100);
},
render: function() {
return React.createElement('div', null);
}
});
ReactDOMServer.renderToString(React.createElement(A, null));
Error:
$ node index.js
/app/node_modules/react/lib/getActiveElement.js:25
return document.body;
^
ReferenceError: document is not defined
at getActiveElement (/app/node_modules/react/lib/getActiveElement.js:25:12)
at ReactReconcileTransaction.ReactInputSelection.getSelectionInformation (/app/node_modules/react/lib/ReactInputSelection.js:38:23)
at ReactReconcileTransaction.Mixin.initializeAll (/app/node_modules/react/lib/Transaction.js:168:75)
at ReactReconcileTransaction.Mixin.perform (/app/node_modules/react/lib/Transaction.js:135:12)
at ReactUpdatesFlushTransaction.Mixin.perform (/app/node_modules/react/lib/Transaction.js:136:20)
at ReactUpdatesFlushTransaction.assign.perform (/app/node_modules/react/lib/ReactUpdates.js:86:38)
at Object.flushBatchedUpdates (/app/node_modules/react/lib/ReactUpdates.js:147:19)
at Object.wrapper [as flushBatchedUpdates] (/app/node_modules/react/lib/ReactPerf.js:66:21)
at ReactDefaultBatchingStrategyTransaction.Mixin.closeAll (/app/node_modules/react/lib/Transaction.js:202:25)
at ReactDefaultBatchingStrategyTransaction.Mixin.perform (/app/node_modules/react/lib/Transaction.js:149:16)
Issue opened at https://github.com/facebook/react/issues/4873
Try moving the setState function in another method:
var React = require('react');
// Reproduced in React 0.13.3 and 0.14.0-beta1
var ReactDOMServer = require("react-dom/server");
var A = React.createClass({
stateChange: function( obj ){
setTimeout( this.setState( obj ), 100 );
},
componentWillMount: function() {
this.stateChange( {a: 1} );
},
render: function() {
console.log( this.state.a )
return React.createElement('div', null);
}
});
ReactDOMServer.renderToString(React.createElement(A, null));
React Tests Fails after set State causes second render
Up until now testing has been going well with JSDOM and Mocha. So far have not had to test any components that change their state. I found my first issue testing a component that changes it's state.
The Error
1) Reduced Test Case - #current Tests that Fail when Component changes state and renders "before each" hook:
Error: Invariant Violation: dangerouslyRenderMarkup(...): Cannot render markup in a worker thread. Make sure `window` and `document` are available globally before requiring React when unit testing or use React.renderToString for server rendering.
at Context.<anonymous> (test/react-reflux/parts/Reduced-spec.js:47:32)
The Component : Reduced.js
var React = require('react');
var Reduced = React.createClass({
getInitialState() {
console.log("start off with editing as false");
return {editing: false};
},
edit() {
console.log("Setting State to Edit");
this.setState({editing: true});
},
render() {
console.log("Rendering");
return (
<span onClick={this.edit}>
{(this.state.editing) ? "Editing" : "Click To Edit"}
</span>
);
}
});
module.exports = Reduced;
The Tests : 1-pass, 1-fail
var React, TestUtils, jsdom, Reduced, expect;
describe('Reduced Test Case', function () {
before(function () {
jsdom = require('jsdom');
global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.window = global.document.parentWindow;
React = require('react/addons');
TestUtils = React.addons.TestUtils;
Reduced = require('./Reduced');
expect = require('chai').expect;
this.component = TestUtils.renderIntoDocument(
<Reduced />
);
var root = TestUtils.findRenderedDOMComponentWithTag(this.component, 'span');
this.el = root.getDOMNode();
});
describe("Tests Pass without simulate", function () {
it("Root Element Reads 'Click To Edit'", function () {
expect(this.el.innerHTML).to.equal('Click To Edit');
});
});
describe("Tests that Fail when Component changes state and renders", function () {
beforeEach(function () {
//
// Simulate invokes edit, invokes set state, invokes render, then error occures
//
TestUtils.Simulate.click(this.el);
});
it("Root Element Reads 'Editing'", function () {
expect(this.el.innerHTML).to.equal('Editing');
});
});
});
The Results
> mocha --compilers js:babel/register
Reduced Test Case - #current
start off with editing as false
Rendering
Tests Pass without simulate
✓ Root Element Reads 'Click To Edit'
Tests that Fail when Component changes state and renders
Setting State to Edit
Rendering
1) "before each" hook
1 passing (379ms)
1 failing
1) Reduced Test Case Tests that Fail when Component changes state and renders "before each" hook:
Error: Invariant Violation: dangerouslyRenderMarkup(...): Cannot render markup in a worker thread. Make sure `window` and `document` are available globally before requiring React when unit testing or use React.renderToString for server rendering.
at Context.<anonymous> (test/Reduced-spec.js:47:32)
I've been going crazy
Everything is loaded after global.window and global.document
The Simulate Event invokes edit(), then render() before error
All React Mocha JSDOM tests have been working well until this state change issue
Please help ???
The setup JSDOM setup was missing global.navigator.
global.navigator = {
userAgent: 'node.js'
};
Insert your global object modifying(passing window and document objects to global) before React is required.
Because React creates its ExecutionEnvironment object while required and don't modify it while works.
Let's say I have 2 components. A parent that contains a child.
The child component is a button like so:
var React = require('react');
var ChildButton = React.createClass({
onSubmitAnswer: function(e) {
this.props.onClick(this);
},
render: function() {
return (
<div className={this.props.visibility}>
<button onClick={this.onSubmitAnswer}>Click Me</button>
</div>
)
}
});
module.exports = ChildButton;
It lives within it's parent, which looks like this:
var React = require('react'),
ChildButton = require('./face-submit-button');
var ParentComponent = React.createClass({
onButtonSubmit: function() {
//Something happens here
},
render: function() {
return (
<div>
//Some more components
<ChildButton text="Submit" onClick={this.onButtonSubmit} />
</div>
)
}
});
module.exports = ParentComponent;
So far so good. Everything works as expected in the UI. But I've encountered some issues in the Jest tests using TestUtils.Simulate.click().
My test for the ChildButton component is straightforward and behaves as I would expect.
jest.dontMock('./child-button');
describe('ChildButton', function() {
var React = require('react/addons'),
ChildButton = require('./child-button'),
TestUtils = React.addons.TestUtils;
describe('events', function() {
var button,
onClickStub;
beforeEach(function() {
onClickStub = jest.genMockFn();
button = TestUtils.renderIntoDocument(
<ChildButton onClick={onClickStub} />
);
});
it('should call onSubmitAnswer when the button is clicked', function() {
var buttonTag = TestUtils.findRenderedDOMComponentWithTag(button, 'button');
TestUtils.Simulate.click(buttonTag);
expect(onClickStub).toBeCalled();
});
});
});
My test for the parent component started out looking the same:
jest.dontMock('./parent-component');
describe('ParentComponent', function() {
var React = require('react/addons'),
ParentComponent = require('./parent-component'),
ChildButton = require('./child-button'),
TestUtils = React.addons.TestUtils;
describe('events', function() {
var parent,
onClickStub;
beforeEach(function() {
onClickStub = jest.genMockFn();
parent = TestUtils.renderIntoDocument(
<ParentComponent onClick={onClickStub} />
);
});
it('should call onButtonSubmit when a click is triggered', function() {
var childButton = TestUtils.findRenderedComponentWithType(parent, ChildButton);
TestUtils.Simulate.click(childButton);
expect(onClickStub).toBeCalled();
});
});
});
But this test fails. The only difference I can see between these two tests is that one uses an HTML tag directly and clicks on it, while the other triggers a click on a React component. Can I not use the click event on React components directly? Is my assumption correct?
And if so, is there a way to trigger a click on React components differently in the tests? I tried using SimulateNative but that had the same effect, the onClickStub doesn't get called on click.
There is currently an open bug for this issue: Let ReactTestUtils.Simulate.click work on non-dom components. So the answer is that due to bugs, you can only use Simulate.click on an actual DOM node. So you can workaround the bug by getting the DOM node until it is fixed.
I have a React component that toggles a className when the component is clicked
var Foo = React.createClass({
getInitialState: function() {
return {className: ''}
},
render: function(){
var className = 'bar ' + this.state.className
return React.createElement('div', {className: className, onClick: this.onClick})
},
onClick: function() {
this.setState({className: 'baz'})
}
});
It works fine, but when I am rendering the app server side, I get the following error
Warning: getInitialState was defined on a component, a plain JavaScript class.
This is only supported for classes created using React.createClass.
Did you mean to define a state property instead?
My build step is setup like so
var Foo = require('./Foo');
var factory = React.createFactory(Foo);
module.exports = React.renderToString(factory({}));
Why is what I am doing wrong, and how should it be done?
I am not sure if this helps, but while using fluxible, this is the syntax i used with JSX as part of require component
var app = new Fluxible({
component: React.createFactory(require('./Components/startup.react.jsx'))
});
I'm trying to test a component that has a value from a state object displayed in it's render().
I've simplified the component in question in to this simple Test component for reproduction. I'm using React 0.12.2.
I am populating my "report" in getIntitialState's call to getStateFromStores(). In testing though this value is empty and is what I think is leading to the error.
Certainly a conditional checking to see if this.state.report is defined would work, but it seems a bit much to have to put conditionals on all variables printed in a render() that are populated via state.
Test Component
var React = require('react');
var AppStore = require('../stores/AppStore');
function getStateFromStores() {
return {
report: AppStore.getCurrentReport(),
};
}
var Test = React.createClass({
getInitialState: function() {
return getStateFromStores();
},
render: function(){
return (
<div>
// This call to the report.id on state seems to be the issue
{this.state.report.id}
</div>
);
}
});
module.exports = Test;
The Test
jest.dontMock('../Test');
var React = require('react/addons');
var TestUtils = React.addons.TestUtils;
var Test = require('../Test');
describe("Test", function() {
it("should render Test", function() {
var test = TestUtils.renderIntoDocument(<Test />);
expect(test).toBeDefined();
});
});
Ideally, I'd like to pre-populate the state of the component before renderIntoDocument() is called in the test as that is where it is failing.
I receive this failure:
● Test › it should render Test
- TypeError: Cannot read property 'id' of undefined
at React.createClass.render (/Users/kevinold/_development/app/assets/javascripts/_app/components/Test.jsx:20:26)
at ReactCompositeComponentMixin._renderValidatedComponent (/Users/kevinold/_development/node_modules/react/lib/ReactCompositeComponent.js:1260:34)
at wrapper [as _renderValidatedComponent] (/Users/kevinold/_development/node_modules/react/lib/ReactPerf.js:50:21)
at ReactCompositeComponentMixin.mountComponent (/Users/kevinold/_development/node_modules/react/lib/ReactCompositeComponent.js:802:14)
at wrapper [as mountComponent] (/Users/kevinold/_development/node_modules/react/lib/ReactPerf.js:50:21)
at ReactComponent.Mixin._mountComponentIntoNode (/Users/kevinold/_development/node_modules/react/lib/ReactComponent.js:405:25)
at ReactReconcileTransaction.Mixin.perform (/Users/kevinold/_development/node_modules/react/lib/Transaction.js:134:20)
at ReactComponent.Mixin.mountComponentIntoNode (/Users/kevinold/_development/node_modules/react/lib/ReactComponent.js:381:19)
at Object.ReactMount._renderNewRootComponent (/Users/kevinold/_development/node_modules/react/lib/ReactMount.js:312:25)
at Object.wrapper [as _renderNewRootComponent] (/Users/kevinold/_development/node_modules/react/lib/ReactPerf.js:50:21)
at Object.ReactMount.render (/Users/kevinold/_development/node_modules/react/lib/ReactMount.js:381:32)
at Object.wrapper [as render] (/Users/kevinold/_development/node_modules/react/lib/ReactPerf.js:50:21)
at Object.ReactTestUtils.renderIntoDocument (/Users/kevinold/_development/node_modules/react/lib/ReactTestUtils.js:48:18)
at Spec.<anonymous> (/Users/kevinold/_development/app/assets/javascripts/_app/components/__tests__/Test-test.js:9:26)
at jasmine.Block.execute (/Users/kevinold/_development/node_modules/jest-cli/vendor/jasmine/jasmine-1.3.0.js:1065:17)
at jasmine.Queue.next_ (/Users/kevinold/_development/node_modules/jest-cli/vendor/jasmine/jasmine-1.3.0.js:2098:31)
at null._onTimeout (/Users/kevinold/_development/node_modules/jest-cli/vendor/jasmine/jasmine-1.3.0.js:2088:18)
I am not sure how to preload state for this component, which should solve the issue, prior to the renderIntoDocument() call in my test.
I've also considered trying to mock getIntitialState() for this component, but there has to be a better way.
Any ideas on how to test this?
I ended up solving this by mocking out the AppStore.getCurrentReport() method like so:
jest.dontMock('../Test');
var React = require('react/addons');
var TestUtils = React.addons.TestUtils;
var Test = require('../Test');
var AppStore = require('../stores/AppStore');
describe("Test", function() {
it("should render Test", function() {
// Mock the return value from the method in the store that populates the value in getInitialState()
AppStore.getCurrentReport.mockReturnValue({id: 1, title: 'Test Rpt'});
var test = TestUtils.renderIntoDocument(<Test />);
expect(test).toBeDefined();
});
});