I am facing an issue of how to test a component in Mobx that we are passing props to.
I am using Jest and I just want to make a simple snapshot test.
Next to default export I am also using named export of just the component so #inject and #observer don't influence it. I should just pass my own 'expenses' and 'filters' as a prop but it is not working.
So this is my component. I am passing RootStore as a prop to that component.
ExpenseList Component
#inject('RootStore')
#observer
export class ExpenseList extends Component {
render() {
const {expenses} = this.props.RootStore.ExpensesStore
const {filters} = this.props.RootStore.FiltersStore
const expensesFilter = selectExpense(expenses, filters)
return (
<div>
{
expenses.length === 0 ? (
<p>No expenses</p>
) : (
expensesFilter.map((expense) => {
return <ExpenseListItem key={expense.id} {...expense} />
})
)
}
</div>
)
}
}
ExpenseList.test.js
import React from 'react'
import { shallow } from 'enzyme'
import { ExpenseList } from '../../components/ExpenseList'
import expenses from '../fixtures/expenses'
test('should render ExpenseList with expenses', () => {
// const wrapper = shallow(<ExpenseList RootStore={{'ExpensesStore':{'expenses':expenses}}}/>)
const wrapper = shallow(<ExpenseList
RootStore={
{
'ExpensesStore':{
'expenses':expenses
},
'FiltersStore': {
'filters': {
text: 'e',
sortBy: 'date',
startDate: '11.2017.',
endDate: '12.2017.'
}
}
}
}
/>)
expect(wrapper).toMatchSnapshot();
})
This is my RootStore
import ExpensesStore from './ExpensesStore'
import FiltersStore from './FiltersStore'
class RootStore {
ExpensesStore = new ExpensesStore(this)
FiltersStore = new FiltersStore(this)
}
const rootStore = new RootStore()
export default rootStore
ExpensesStore
class ExpensesStore {
constructor(rootStore) {
this.rootStore = rootStore
}
#observable expenses = [];
findExpense(paramsId) {
return computed(() => {
return this.expenses.find((expense) => expense.id === paramsId)
}).get()
}
}
export default ExpensesStore
FiltersStore
class FiltersStore {
constructor(rootStore) {
this.rootStore = rootStore
}
#observable filters = {
text: '',
sortBy: 'date',
startDate: moment().startOf('month'),
endDate: moment().endOf('month')
}
}
export default FiltersStore
Ok. The problem were decorators at the top of the component.
Using shallow rendering won't provide any useful results when testing injected components; only the injector will be rendered. To test with shallow rendering, instantiate the wrappedComponent
test('should render ExpenseList with expenses', () => {
const wrapper = shallow(<ExpenseList.wrappedComponent
RootStore = {
{
ExpensesStore: {
expenses
},
FiltersStore: {
filters: {
text: 'e',
sortBy: 'date',
startDate: '',
endDate: ''
}
}
}
}
/>)
expect(wrapper).toMatchSnapshot();
})
Related
I'm using reactjs with redux for state management. I want to change state in a component with redux. but when I send props to the component and I inspect it with console.log(), returned undefined to me.
please guide me to solve problem...
thanks
Svg Viewer Component
import React, { useEffect, useState, useContext } from "react";
import * as d3 from "d3";
import store from "../../redux/store";
const SvgViewer = ({ nodesData, svgFilePath, props }) => {
//const { visible, invisible } = props;
const [svgContainer, setSvgContainer] = useState(undefined);
const showNodesOnSvg = nodes => {
let svgDoc = svgContainer.contentDocument;
let gTags = svgDoc.querySelectorAll("svg > g");
let container = null;
if (gTags.length > 1) container = svgDoc.querySelector("g:nth-of-type(2)");
else container = svgDoc.querySelector("g:nth-of-type(1)");
let node = d3.select(container);
nodesData.forEach(nodeData => {
node
.append("text")
.attr("id", "node" + nodeData["id"])
.attr("fill", "white")
.attr("text-anchor", "middle")
.attr("x", nodeData["positionX"])
.attr("y", nodeData["positionY"])
.attr("class", "clickable-node")
.style("font-size", "8px")
.style("position", "absolute")
.style("cursor", "pointer")
.style("display", "inline-block")
.on("click", function() {
clickHandler(nodeData["id"]);
})
.text("N/A" + " " + nodeData["symbol"]);
let nodeCreated = d3.select(
svgDoc.getElementById("node" + nodeData["id"])
);
nodeCreated
.append("title")
.attr("id", "title" + nodeData["id"])
.text(" " + nodeData["tagCode"]);
});
};
const clickHandler = nodeID => {
console.log(props); //not show props
};
useEffect(() => {
const svg = document.querySelector("#svgobject");
setSvgContainer(svg);
svg.onload = () => {
if (nodesData != null) {
showNodesOnSvg();
}
};
});
return (
<div className="unit-schema-container1" key={svgFilePath}>
{/* <Spin indicator={objectLoading} spinning={this.state.objectLoading}> */}
<object id="svgobject" type="image/svg+xml" data={svgFilePath}></object>
{/* </Spin> */}
</div>
);
};
export default SvgViewer;
store
import { createStore, combineReducers } from "redux";
import modalReducer from "./reducers/modalReducer";
const store = createStore(modalReducer);
export default store;
Reducer:
function modalReducer(state = initialState, action) {
const initialState = false;
switch (action.type) {
case "VISIBALE":
return (state = true);
case "INVISIBALE":
return (state = false);
default:
return state;
}
}
export default modalReducer;
Action
export function visible() {
return {
type: "VISIBLE"
};
}
export function invisible() {
return {
type: "INVISIBLE"
};
}
Svg Container
import { visible, invisible } from "../redux/actions/modalAction";
import { connect } from "react-redux";
import svgViewer from "../pages/unit-monitor/svg-viewer";
const mapStateToProps = state => ({
visibale: state.value
});
const mapDispatchToProps = dispatch => {
return {
visible: () => dispatch(visible()),
invisible: () => dispatch(invisible())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(svgViewer);
Svg Component
import React, { PureComponent } from "react";
import { Row, Col, Spin, Icon } from "antd";
import axios from "axios";
import "./tree-select.scss";
import History from "./history";
import SchemaTreeSelect from "./schema-tree-select";
import SvgViewer from "../../container/svgViewerContainer";
class UnitMonitor extends PureComponent {
constructor() {
super();
}
state = {
nodes: undefined,
nodeId: 25,
valueSignalR: [],
searchText: "",
selectedChart: "Line",
tsSchemaLoading: false,
objectLoading: false,
svgFilePath: ""
};
onChangeShcema = schemaID => {
axios.get("/api/schemata/get-schemata-nodes/" + schemaID).then(response => {
this.setState({ nodes: response.data });
let path = response.data[0].file;
let svgFile = require("./images/" + path);
this.setState({ svgFilePath: svgFile });
});
};
render() {
return (
<Row type="flex" className="">
<Col span={25}>
<SchemaTreeSelect handleChange={this.onChangeShcema} />
<History nodeId={this.state.nodeId} />
<SvgViewer
svgFilePath={this.state.svgFilePath}
nodesData={this.state.nodes}
/>
</Col>
</Row>
);
}
}
export default UnitMonitor;
You are trying to read the value property on state, but there is no such property on the state returned by your reducer... it is just true or false so replace the state.value with the state itself in your mapStateToProps.
const mapStateToProps = state => ({
visibale: state
});
Also there is an inconsistency between your types used for dispatching the actions on the redux state.
"VISIBALE"/ "UNVISIBALE" is used in reducer while "VISIBLE"/ "INVISIBLE" is used in action dispatcher.`
Trying to test this component, and im getting this
error
TypeError: this.props.onItemAdded is not a function
I've referenced this but this solution doesn't really apply to my problem
Enzyme test: TypeError: expect(...).find is not a function
How would i test the button functionality being that the button is a prop ?
todo-add-item.test.js
import React from "react";
import { shallow } from "enzyme";
import TodoAddItem from './todo-add-item';
describe('Should render add item component', ()=> {
it('should render add item component', () => {
const wrapper = shallow(<TodoAddItem/>)
})
})
describe('Should simulate button click', ()=> {
it('should simulate button click', () => {
const wrapper =shallow(<TodoAddItem/>)
wrapper.find('button').simulate('click') // getting the type error here.
})
})
todo-add-item.js
import React, { Component } from 'react';
import './todo-add-item.css';
export default class TodoAddItem extends Component {
render() {
return (
<div className="todo-add-item">
<button
className="test-button btn btn-outline-secondary float-left"
onClick={() => this.props.onItemAdded('Hello world')}>
Add Item
</button>
</div>
);
}
}
app.js
import React, { Component } from 'react';
import AppHeader from '../app-header';
import SearchPanel from '../search-panel';
import TodoList from '../todo-list';
import ItemStatusFilter from '../item-status-filter';
import TodoAddItem from '../todo-add-item';
import './app.css';
export default class App extends Component {
constructor() {
super();
this.createTodoItem = (label) => {
return {
label,
important: false,
done: false,
id: this.maxId++
}
};
this.maxId = 100;
this.state = {
todoData: [
this.createTodoItem('Drink Coffee'),
this.createTodoItem('Make Awesome App'),
this.createTodoItem('Have a lunch')
]
};
this.deleteItem = (id) => {
this.setState(({ todoData }) => {
const idx = todoData.findIndex((el) => el.id === id);
const newArray = [
...todoData.slice(0, idx),
...todoData.slice(idx + 1)
];
return {
todoData: newArray
};
});
};
this.addItem = (text) => {
const newItem = this.createTodoItem(text);
this.setState(({ todoData }) => {
const newArray = [
...todoData,
newItem
];
return {
todoData: newArray
};
});
};
this.onToggleImportant = (id) => {
console.log('toggle important', id);
};
this.onToggleDone = (id) => {
console.log('toggle done', id);
};
};
render() {
return (
<div className="todo-app">
<AppHeader toDo={ 1 } done={ 3 } />
<div className="top-panel d-flex">
<SearchPanel />
<ItemStatusFilter />
</div>
<TodoList
todos={ this.state.todoData }
onDeleted={ this.deleteItem }
onToggleImportant={ this.onToggleImportant }
onToggleDone={ this.onToggleDone } />
<TodoAddItem onItemAdded={ this.addItem } />
</div>
);
};
};
You don't pass any props to your component.
const wrapper =shallow(<TodoAddItem onItemAdded={() => jest.fn()}/>)
You can check props with .props()
Eg:
console.log('props',wrapper.find('button').props());
As we know, the structure of a class component can be simplified as the following:
// Blank 1
class Books extends Component {
// Blank 2
render(){
// Blank 3
return()
}
export default Books;
So just for example:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { updateFilters } from '../../../services/filters/actions';
import Checkbox from '../../Checkbox';
import GithubStarButton from '../../github/StarButton';
import './style.scss';
const availableSizes = ['XS', 'S', 'M', 'ML', 'L', 'XL', 'XXL'];
class Filter extends Component {
static propTypes = {
updateFilters: PropTypes.func.isRequired,
filters: PropTypes.array
};
componentWillMount() {
this.selectedCheckboxes = new Set();
}
toggleCheckbox = label => {
if (this.selectedCheckboxes.has(label)) {
this.selectedCheckboxes.delete(label);
} else {
this.selectedCheckboxes.add(label);
}
this.props.updateFilters(Array.from(this.selectedCheckboxes));
};
createCheckbox = label => (
<Checkbox
classes="filters-available-size"
label={label}
handleCheckboxChange={this.toggleCheckbox}
key={label}
/>
);
createCheckboxes = () => availableSizes.map(this.createCheckbox);
render() {
return (
<div className="filters">
<h4 className="title">Sizes:</h4>
{this.createCheckboxes()}
<GithubStarButton />
</div>
);
}
}
const mapStateToProps = state => ({
filters: state.filters.items
});
export default connect(
mapStateToProps,
{ updateFilters }
)(Filter);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { fetchProducts } from '../../services/shelf/actions';
import { addProduct } from '../../services/cart/actions';
import Product from './Product';
import Filter from './Filter';
import ShelfHeader from './ShelfHeader';
import Clearfix from '../Clearfix';
import Spinner from '../Spinner';
import './style.scss';
class Shelf extends Component {
static propTypes = {
fetchProducts: PropTypes.func.isRequired,
products: PropTypes.array.isRequired,
addProduct: PropTypes.func.isRequired,
filters: PropTypes.array,
sort: PropTypes.string
};
state = {
loading: false
};
componentWillMount() {
const { filters, sort } = this.props;
this.handleFetchProducts(filters, sort);
}
componentWillReceiveProps(nextProps) {
const { filters: nextFilters, sort: nextSort } = nextProps;
if (nextFilters !== this.props.filters) {
this.handleFetchProducts(nextFilters, undefined);
}
if (nextSort !== this.props.sort) {
this.handleFetchProducts(undefined, nextSort);
}
}
handleFetchProducts = (
filters = this.props.filters,
sort = this.props.sort
) => {
this.setState({ loading: true });
this.props.fetchProducts(filters, sort, () => {
this.setState({ loading: false });
});
};
render() {
const { products } = this.props;
const p = products.map(p => {
return (
<Product product={p} addProduct={this.props.addProduct} key=
{p.id} />
);
});
return (
<React.Fragment>
{this.state.loading && <Spinner />}
<Filter />
<div className="shelf-container">
<ShelfHeader productsLength={products.length} />
{p}
<Clearfix />
</div>
<Clearfix />
</React.Fragment>
);
}
}
const mapStateToProps = state => ({
products: state.shelf.products,
filters: state.filters.items,
sort: state.sort.type
});
export default connect(
mapStateToProps,
{ fetchProducts, addProduct }
)(Shelf);
Except for state and life cycle methods, sometimes we define other types of attributes and functions in Blank 1, sometimes in Blank 2, sometimes in Blank 3. So I am wondering when we are going to define attributes and functions, which part should we choose? Is there a convention or something like that?
Block 1 is for defining variables and functions which are not depended on component ,these are general variables and functions which could be used in the component and can even be exported in another files.
Block 2 is for defining component specific variables and methods, define lifecycle methods.variables and methods defined in block 2 could be accessed using this keyword.
Block 3 is used when we want to execute certain piece of code,every time when render method is executed.Apart from initial render, render method is executed every time when setState is performed,so avoid writing code in block 3 as it's excessive.
Hope this helps,
Cheers !!
import { Mongo } from 'meteor/mongo';
import { Meteor } from 'meteor/meteor';
import React, {Component} from 'react';
import {check} from 'meteor/check';
export const Adressen = new Mongo.Collection('Phonebook');
if (Meteor.isServer) {
Meteor.publish('ArrayToExport', function(branches) {
check(branches, [Match.Any]);
if(branches.length > 10){
return this.ready()
};
return Adressen.find(
{branche: {$in: branches}}, {fields: {firmenname:1, plz:1}}
);
});
}
.
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import {Adressen} from "../api/MongoDB";
class ExportArray extends Component{
constructor(props){
super(props);
this.state = {
branches: this.props.filteredBranches
};
}
render(){
return(
<div>
<button onClick={this.exportArrays}></button>+
</div>
);
}
}
export default withTracker( (branches) => {
Meteor.subscribe('ArrayToExport', branches);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
this.props.filteredBranche is a pure array,generated through controlled input field. this.props.filteredBranches changes as Input changes, in parent Component.
I thought I was sending my this.props.filteredBranches as an argument through withTracker function. But nothing is passed to the publish function.
if (Meteor.isServer) {
arrayExfct = function (array){
return {
find: {branche:{$in: array }},
fields: {firmenname:1, plz:1}
};
}
Meteor.publish('ArrayToExport', function (array) {
return Adressen.find(
arrayExfct(array).find, arrayExfct(array).fields);
});
}
.
export default withTracker( () => {
arrayExfct = function(array) {
return {
find: {branche: {$in: array}},
fields: {firmenname:1, plz:1}
}
}
var array = ['10555'];
Meteor.subscribe('ArrayToExport', array );
var arrayExfct = Adressen.find(arrayExfct(array).find, arrayExfct(array).fields);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
It would help if you also added an example of where you used this component and how you pass props to it, but I think I see your problem.
You expect the local state in your rendering component to get into the withTracker container, but that would be the other way around. When you make the withTracker container, you are really making another react component that renders your display component (ExportArray) and passes the data (ArrayToExport) down into it.
So, props go like this currently:
external render -> withTracker component -> ExportArray
What you need to do it to get the filteredBranches (which you pass from a parent component?) from the props argument in withTracker and pass that to the subscribtion,
class ExportArray extends Component{
exportArrays () {
const { ArrayToExport } = this.props;
}
render(){
const { ArrayToExport } = this.props;
return(
<div>
<button onClick={this.exportArrays}></button>+
</div>
);
}
}
export default withTracker(propsFromParent => {
const { filteredBranches } = propsFromParent;
Meteor.subscribe('ArrayToExport', filteredBranches);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
Hi the issue is with the code below. The parameter called branches is the props so branches.branches is the array you passed in.
export default withTracker( (branches) => {
Meteor.subscribe('ArrayToExport', branches);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
Try the following.
export default withTracker( ({branches}) => {
Meteor.subscribe('ArrayToExport', branches);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
Notice all that changed was
(branches)
became
({branches})
I solved my problem with a combination of Session Variables and State.
//Client
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import {Adressen} from "../api/MongoDB";
import {Meteor} from 'meteor/meteor';
import { Session } from 'meteor/session';
class ExportArray extends Component{
constructor(){
super();
this.state = {
x: [],
y: []
};
this.exportArrays = this.exportArrays.bind(this);
}
exportArrays(e){
e.preventDefault();
this.setState({x: this.props.filteredBranches});
this.setState({y: this.props.filteredPostleitzahlen});
}
render(){
var selector = {branche: {$in: this.state.x},plz: {$in: this.state.y}};
Session.set('selector', selector);
return(
<div>
<button onClick={this.exportArrays}> Commit </button>
</div>
);
}
}
export default withTracker( () => {
const ArrayfürExport = Meteor.subscribe('ArrayToExport', Session.get('selector') );
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
//Server
Meteor.publish('ArrayToExport', function (selector) {
console.log('von mongodb', selector);
return Adressen.find(
selector
, {
fields: {firmenname:1, plz:1}
});
});
}
Builder Action positionRComponent not called. Am I doing something wrong? Check out the commentLine inside moveBox function in the BuildView.js
Expecting output: to be printed in console.
Position R Component
Below are the code snippets of BuildView.js and builder-actions.js.
BuildView.js
import React, {PropTypes} from 'react';
import BuilderStore from '../stores/builder-store';
import BuilderActions from '../actions/builder-actions'
import update from 'react/lib/update';
import ItemTypes from './ItemTypes';
import RComponent from './RComponent';
import { DropTarget } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
function getViewRComponents(){
return( {components: BuilderStore.getViewRComponents()})
}
const rComponentTarget = {
drop(props, monitor, component) {
const item = monitor.getItem();
const delta = monitor.getDifferenceFromInitialOffset();
const left = Math.round(item.left + delta.x);
const top = Math.round(item.top + delta.y);
component.moveBox(item.id, left, top);
}
};
const wrapper = {
border: '1px solid grey'
}
function collect(connect, monitor){
return ({
connectDropTarget: connect.dropTarget()
})
}
class BuildView extends React.Component{
constructor(props){
super(props);
this.state = getViewRComponents();
this._onChange = this._onChange.bind(this);
}
moveBox(id, left, top) {
this.setState(update(this.state, {
components: {
[id]: {
$merge: {
left: left,
top: top
}
}
}
}));
//CALLING HERE>>> Not getting called
BuilderActions.positionRComponent.bind(null, this.state.components[id]);
}
componentWillMount(){
BuilderStore.addChangeListener(this._onChange)
}
render(){
const { hideComponentOnDrag, connectDropTarget } = this.props;
let components = this.state.components.map( (component, index) => {
return(<RComponent
key={index}
id={index}
left={component.left}
top={component.top}
hideComponentOnDrag={hideComponentOnDrag}>
{component.name}
</RComponent>);
})
return connectDropTarget(
<div>
{components}
</div>
);
}
_onChange(){
this.setState(getViewRComponents());
}
componentWillUnMount(){
BuilderStore.removeChangeListener(this._onChange())
}
}
BuildView.propTypes = {
hideComponentOnDrag: PropTypes.bool.isRequired,
connectDropTarget: PropTypes.func.isRequired
};
export default DropTarget(ItemTypes.RCOMPONENT,rComponentTarget, collect )(BuildView);
builder-actions.js
import BuilderConstants from '../constants/builder-constants';
import {dispatch, register} from '../dispatchers/builder-dispatcher';
export default {
addRComponent(component) {
console.log("Add R Component")
dispatch({
actionType: BuilderConstants.ADD_RCOMPONENT, component
})
},
removeRComponent(component){
dispatch({
actionType: BuilderConstants.REMOVE_RCOMPONENT, component
})
},
positionRComponent(component){
console.log("Position R Component");
dispatch({
actionType: BuilderConstants.POSITION_RCOMPONENT, component
})
}
}
Use call or execute the returned function from bind:
var f = BuilderActions.positionRComponent.bind(null, this.state.components[id])
f()
or:
BuilderActions.positionRComponent.call(null, this.state.components[id]);
The difference is bind doesn't execute but returns a new function with the argument list passed into the new function.
call basically does a bind then executes, apply is similar but takes an array of arguments.
Hope it helps.