I am trying to do some unit testing by using findRenderedComponentWithType and findRenderedComponentWithType to find components. However, I am having a bit of trouble.
I have a shallow rendered component <Layout /> and within it, I want to find <UserMenu />.
var Layout = React.createClass({
render: function(){
console.log(this.props);
return (
<div id="data-trader">
<header className="header">
<UserMenu user={this.props.user} />
</header>
<SideMenu user={this.props.user}/>
</div>
);
}
});
module.exports = Layout;
Within my test file, I tried this:
describe('Layout', function() {
beforeEach(function(){
fakeDOM = TestUtils.createRenderer();
it('should exist as a component', function(done) {
expect(<Layout/>).to.exist;
done();
});
fakeDOM.render(<Layout />);
renderedFakeDOMOutput = fakeDOM.getRenderOutput();
});
it('should have login button when props.user is undefined', function(done) {
renderedFakeDOMOutput.props.user = undefined;
let UserMenuComponent = TestUtils.scryRenderedComponentsWithType(renderedFakeDOMOutput, UserMenu);
done();
});
});
However, scryRenderedComponentsWithType and findRenderedComponentWithType cannot find anything of the components with the type UserMenu.
I also tried to create another file where it exports the UserMenu component but I get the same output or 0 found (Array length 0) or error when no components are found (Error: Did not find exactly one match for componentType:function UserMenu()).
I know it's not a direct solution to the question you're asking, but when I tried shallow rendering, I struggled with it just as you are. React's own docs describe this feature as an early release with "some limitations".
Shallow testing currently has some limitations, namely not supporting
refs. We're releasing this feature early and would appreciate the
React community's feedback on how it should evolve.
I got tired of banging my head against the wall and went with a more straight-forward solution that I found elsewhere.
http://substantial.com/blog/2014/11/11/test-driven-react-how-to-manually-mock-components/
In case that link ever goes bad: Basically, it says to use Rewire to swap out your sub-components with something you create inline in your test. Here is exactly what I do:
rewire-module.js (copied from the blog post)
module.exports = function rewireModule(rewiredModule, varValues) {
var rewiredReverts = [];
beforeEach(function () {
var key, value, revert;
for (key in varValues) {
if (varValues.hasOwnProperty(key)) {
value = varValues[key];
revert = rewiredModule.__set__(key, value);
rewiredReverts.push(revert);
}
}
});
afterEach(function () {
rewiredReverts.forEach(function (revert) {
revert();
});
});
return rewiredModule;
};
MyList.jsx (snippet)
<div>
{ items.map(item => <MyListItem key={item.id} item={item}/>) }
</div>
MyList-test.jsx (snippet)
let rewireModule = require('../utils/rewire-module');
let rewire = require('rewire');
let MyList = rewire('./MyList.jsx');
rewireModule(MyList, {
MyListItem: React.createClass({
propTypes: {
item: React.PropTypes.object.isRequired
},
render: function () {
return <div className="MyListItem"/>;
}
})
});
it('should render data', function () {
// init
MyStore.onLoadCompleted([
{id: 1, name: 'Test 1'},
{id: 2, name: 'Test 2'}
]);
let listComponent = TestUtils.renderIntoDocument(<MyList/>);
let listItems = TestUtils.scryRenderedDOMComponentsWithClass(listComponent, 'MyListItem');
expect(listItems.length).to.equal(2);
// cleanup
React.unmountComponentAtNode(listComponent.getDOMNode());
});
As you may have figured, I basically use "className" as my element identifiers to find them later.
Sometimes if I want to search for specific sub-components with specific data, I'll build something unique into the className.
rewireModule(MyList, {
MyListItem: React.createClass({
propTypes: {
item: React.PropTypes.object.isRequired
},
render: function () {
return <div className={'MyListItem_' + item.id}/>;
}
})
});
it('should render data', function () {
// init
MyStore.onLoadCompleted([
{id: 1, name: 'Test 1'},
{id: 2, name: 'Test 2'}
]);
let listComponent = TestUtils.renderIntoDocument(<MyList/>);
TestUtils.findRenderedDOMComponentWithClass(listComponent, 'MyListItem_1');
TestUtils.findRenderedDOMComponentWithClass(listComponent, 'MyListItem_2');
// cleanup
React.unmountComponentAtNode(listComponent.getDOMNode());
});
Related
I am new in ReactJS and "reactive programming". I tried to create a dispatcher, action and store according to this project, but I don't know how to pass data to component.
In this example it doesn't work.
var data = [1, 2, 3, 4, 5];
var AppDispatcher = Kefir.emitter();
function DataActions() {
this.getAllData = function () {
AppDispatcher.emit({
actionType: "GET_ALL"
});
};
}
var Actions = new DataActions();
var getAllDataActionsStream = AppDispatcher.filter(function (action) {
return action.actionType === "GET_ALL";
}).map(function (action) {
return function (data) {
return data;
};
});
var dataStream = Kefir.merge([getAllDataActionsStream]).scan(function (prevData, modificationFunc) {
return modificationFunc(prevData);
}, {});
var Content = React.createClass({
getInitialState: function() {
this.onDataChange = this.onDataChange.bind(this);
return {componentData: []};
},
componentDidMount: function() {
dataStream.onValue(this.onDataChange);
},
componentWillMount: function(){
dataStream.offValue(this.onDataChange);
console.log(Actions.getAllData());
},
onDataChange(newData) {
this.setState({componentData: newData});
},
render: function() {
console.log(this.state);
var list = this.state.componentData.map(function (item, i) {
return (
<li key={i}>{item}</li>
);
});
return <ul>{list}</ul>;
}
});
React.render(<Content />, document.getElementById('container'));
Before I begin to answer in length I want to answer this part up front:
but I don't know how to pass data to component.
In the example you linked the author passes in the Todos into the main component using React's props, not with an action. So that is the approach I take in my example as well.
Now here is my example. I highly reccommend looking at the example and reading along to what I've written below.
var data = [ 1, 2, 3, 4, 5 ];
// This will now log all events of the AppDispatcher in the console with the prefix 'Kefer: '
var AppDispatcher = Kefir.emitter().log("Kefir: ");
function DataActions() {
// Our application has an action of emitting a random number.
this.emitNumber = function() {
AppDispatcher.emit({
actionType: "EMIT_NUMBER"
})
};
}
var Actions = new DataActions();
var emitNumberActionStream = AppDispatcher
.filter(function(action) {
return action.actionType === "EMIT_NUMBER";
})
.map(function(action) {
console.log("EMIT_NUMBER ACTION OCCURRED!!");
return Math.floor(Math.random() * (10)) + 1;
});
// Only one stream, no need to merge right now.
//var dataStream = Kefir.merge([ getAllDataActionsStream ]);
var Content = React.createClass({
getInitialState: function() {
// Set initial componentData using the data passed into this component's via props
return { componentData: this.props.data };
},
componentDidMount: function() {
// On each emitted value run the this.onDataChange function
emitNumberActionStream.onValue(this.onDataChange);
// Every second emit a number using the Actions we created earlier
setInterval(function() {
Actions.emitNumber();
}, 1000);
},
onDataChange: function(emittedNumber) {
console.log('state on change:', this.state);
// Update the state by appending the emitted number to the current state's componentData
this.setState({ componentData: this.state.componentData.concat([emittedNumber])});
console.log('updated state: ', this.state);
console.log('-----------------');
},
render: function() {
console.log('RENDER AGAIN!');
var list = this.state.componentData.map(function(item, i) {
return (
<li key={i}>{item}</li>
);
});
return <ul>{list}</ul>;
}
})
;
// Pass in initial data using props 'data={data}'
React.render(<Content data={data}/>, document.getElementById('container'));
I modified the example you gave that wasn't working so that it works and makes a little more sense (hopefully).
The Actions and Stores work like this:
Actions:
Request a number be emitted
Stores
Listen for "EMIT_NUMBER" actions and emit a random number
And the actual component runs like this:
It gets the initial 5 numbers passed into the component via props.
Once mounted it begins listening to the store and creates a setInterval that calls the action dispatcher's emitNumber() action. The interval is to show the reactivity at work, you could imagine that there was a button to press that would call emitNumber() instead.
The store observes the action dispatcher emit "EMIT_NUMBER" and emits a number.
The component observes the store emitted a number and updates the component's state.
The component observes that its state has changed and it rerenders.
I believe the issue is that you're using ES6 syntax (which is what the example was written in... notice the Readme). You'll need to either use a transpiler like Babel or convert your method(param => console.log(param)) syntax into normal JS (ie, method(function(param) { console.log(param) });).
What's the best way to set state based on the data received from observe()?
It seems setting state via componentWillMount() won't work as observe() runs after this and the data isn't available to set state.
I'm using the observe function as advised when using Parse
E.g.:
var DragApp = React.createClass({
getInitialState: function () {
return {
activeCollection : ''
};
},
observe: function() {
return {
collections: (collectionsQuery.equalTo("createdBy", currentUser))
};
},
_setactiveCollection: function(collection) {
this.setState({
activeCollection : collection
});
},
componentWillMount: function () {
var collection = this.data.collections[0];
this._setActiveCollection(collection);
},
)}
I went the wrong way about this.
I shouldn't be storing this.data into state. I can pass it into components via render.
To get round this.data not being ready before rendering, I make use of the ParseReact function pendingQueries() inside render. E.g.
if (this.pendingQueries().length > 0) {
content = 'loading...'
} else {
content = 'hello world I am' + this.data.name
}
Try:
var DragApp = React.createClass({
observe: function() {
var collections = collectionsQuery.equalTo("createdBy", currentUser);
return {
collections: collections,
activeCollection: collections[0]
};
},
render: function () {
// do something with this.data.collections and/or this.data.activeCollection
},
)}
I want to return a single document with the fields joined together. That is, a result like as follows
{
_id: "someid",
name: "Odin",
profile: {
game: {
_id: "gameid",
name: "World of Warcraft"
}
}
}
I have a route controller which is fairly simple.
UserController = RouteController.extend({
waitOn: function () {
return Meteor.subscribe('users');
},
showAllUsers: function () {
this.render('userList', {
data: Meteor.users.find()
})
}
});
I've tried changing my data like so:
this.render('userList', {
data: Meteor.users.find().map(function (doc) {
doc.profile.game = Games.findOne();
return doc;
})
});
However, this does not have the intended effect of adding "game" to the user. (and yes, Games.findOne() has a result)
How can you transform the results of a cursor in meteor and iron:router?
Try defining your data as a function so it can be dynamically re-executed when needed.
UserController = RouteController.extend({
waitOn: function () {
return Meteor.subscribe('users');
},
showAllUsers: function () {
this.render('userList', {
data: function(){
return Meteor.users.find().map(function (doc) {
doc.profile.game = Games.findOne();
return doc;
});
}
});
}
});
Given your use of easy search, what might be simpler is just to define a template helper for profile
Template.userList.helpers({
profile: function(){
var game = Games.findOne({_id: this.gameId});
return { game: { _id: game._id, name: game.name }};
}
});
This assumes a single game per user. If you have more than one then you can iterate over a cursor of Games instead.
This is my component class (Part of it).
updateStore: function() {
console.log("Updating state in the Calendar.js");
this.setState(this.getInitialState());
},
componentDidMount: function() {
EventsStore.addChangeListener(this.updateStore, 'CHANGE');
},
componentDidUnmount: function() {
EventStore.removeChangeListener(this.updateStore);
},
This is my action (Tuxx)
var Actions = require('tuxx/Actions');
var eventsStore = require('../Stores/EventsStore');
var jQ = require('jquery');
var eventsActions = Actions.createActionCategory({
category: 'events',
source: 'standard',
actions: ['create', 'get']
});
eventsActions.register(eventsStore, {
create: eventsStore.onCreate,
get: eventsStore.onGet
});
eventsActions.before('get', function (nextCallback, actionBody) {
jQ.get('http://127.0.0.1:8181/events').done(function(resp) {
nextCallback(resp);
});
});
module.exports = eventsActions;
And this is part of my store
onGet: function(resp) {
resp = JSON.parse(resp);
this._events = resp;
console.log(this._events);
console.log("Emiting change")
this.emitChange('CHANGE');
},
And last, this is my init code:
eventsAction.get();
var App = React.createClass({
render: function() {
return (
<div>
<RouteHandler />
</div>
)
}
});
var routes = (
<Route name="app" path="/" handler={App}>
<DefaultRoute handler={Calendar} />
<Route name="event.edit" path="/event/:eventId" handler= {EventEditForm} />
</Route>
);
Router.run(routes, function(Handler) {
React.render(<Handler />, document.getElementById("main"));
});
As far as I understand, it should re render my component when emitChange is run.
This is my console output:
I think it should hit the
console.log("Updating state in the Calendar.js");
part, but it doesn't.
I am far from being competent in JS world, so I need help.
Thank you in advance.
This is how store is required:
var EventsStore = require('./Stores/EventsStore');
store is saved as follows:
Store is defined as:
var Stores = require('tuxx/Stores')
var eventsStore = Stores.createStore({
_events: [],
getAll: function () {
return Object.keys(this._events);
},
(...)
(...)
onGet: function(resp) {
resp = JSON.parse(resp);
this._events = resp;
console.log(this._events);
console.log("Emiting change")
this.emitChange();
},
register: function () {
return {
events: {
create: this.onCreate,
get: this.onGet
}
};
}
});
module.exports = eventsStore;
In the component I use it using EventsStore variable which was created from:
var EventsStore = require('./Stores/EventsStore');
Second edit.
I was still digging and I found out this:
componentDidMount: function() {
EventsStore.addChangeListener(this.updateStore);
console.log('Calendar::componentDidMount');
console.log(EventsStore.listeners());
console.log('----')
},
And the result in the console is:
[Log] Calendar::componentDidMount (app.js, line 36083)
[Log] [] (app.js, line 36084)
Having looked at the Tuxx source code, I believe that should work. You can also omit that second "CHANGE" parameter to emitChange and addChangeListener and it'll use a default. I assume it's the same instance of the store you're using everywhere?
Looking at Tuxx, they use the createOwnerClass and connectOwnerToStore combo to make all of this happen automatically - see the initial guide on the homepage. Perhaps using that approach would help you track down the bug?
I'm having an issue with creating a component using react and martyjs. I'm sure it is a typo or something but I just can't seem to find it. Although I have a state mixin in the component the state is not being populated and it doesn't look like getState is even being called in the mixin.
Mixin.es6
var StateMixin = Marty.createStateMixin({
listenTo: VideoStore,
getState: function() {
return {
items: VideoStore.list(),
currentItem: VideoStore.select(),
}
}
});
State.es6
var VideoStore = Marty.createStore({
displayName: "Store",
handlers: {
list: Events.List,
render: Events.Render
},
getInitialState: function(){
return { };
},
list: function(){
return this.fetch({
id: 'list',
locally: function(){
if(this.hasAlreadyFetched('list') )
return this.state.items;
},
remotely: function(){
return DissolveStateSource.list();
}
});
},
select: function(){},
render: function(){}
});
Component.es6
$( ()=>
React.render(
<VideosTable/>,
$("#container")[0]
));
var VideosTable = React.createClass(
{
mixins: StateMixin,
render: function() {
var body = this.state.list.when({ //state is null here
pending: function(){
return <span className="ball"></span>;
},
failed: function(error){
return <div className="error">error.message</div>;
},
done: function(videos){
return <div>Videos</div>;
}
});
return <h2>hello</h2>;
}
});
Any idea what I'm doing wrong?
Edit: I've added a js bin thing here
http://jsbin.com/lekegicumo/2/edit?html,js,console,output
Looks like a typo in Mixin.es6 to me.
Change getState to getInitialState.
Also, in Component.es6:
Change mixins: StateMixin to mixins: [StateMixin].
The problem ended up being that the order of inclusion of JavaScript files was incorrect. Swapping some around fixed the issue.
are u using react v0.1.13.0
this is new way to initial your state using 'construct'
constructor(props) {
super(props);
this.state = {count: props.initialCount};
}
https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html