I'm writting some test using enzyme but I have some weird behavior. Here is my test :
import React from 'react'
import { TypeTable } from 'routes/Type/components/TypeTable'
import { shallow } from 'enzyme'
import { Table } from 'react-toolbox'
// ...
let _props, _wrapper
beforeEach(() => {
_props = {
getTypes: sinon.spy(),
types: [
{ name: 'type 1'},
{ name: 'type 2'}
]
}
_wrapper = shallow(<TypeTable {..._props} />)
})
it('Should render a <Table>', () => {
expect(_wrapper.is(Table)).to.be.true
})
it('should render 2 rows', () => {
expect(_wrapper.find('tbody').find('tr')).to.have.length(2)
})
The first test is working. The second one is not working (the assertion is failing : expected { Object (root, unrendered, ...) } to have a length of 2 but got 0)
But in my second test, if I print the content of my _wrapper using console.log(_wrapper.html()) I get
'<table data-react-toolbox="table">
<thead>
<tr>
<th>name</th>
</tr>
</thead>
<tbody>
<tr data-react-toolbox-table="row">
<td>type 1</td>
</tr>
<tr data-react-toolbox-table="row">
<td>type 2</td>
</tr>
</tbody>
</table>'
Which seems to be ok and which contains several tr.
Did I missed something ?
shallow will not "render" subcomponents. It is used to test a single component and not its children. I think that using mount instead of shallow will let you test what you want.
Shallow rendering is useful to constrain yourself to testing a component as a unit, and to ensure that your tests aren't indirectly asserting on behavior of child components.
Related
I have a reactjs project that uses react route version 4 and react16.3 . I can navigate to page http://localhost:8000/#/my-list where it will take you to MyList Component , on my list component there is a link to view a single list which behaves like a SPA by changing the state as below .
import React,{Component} from 'react'
class MyList extends Component{
constructor(props){
super(props)
this.state={
canViewItem:false
}
this.viewItem=this.viewItem.bind(this)
}
viewItem(){
this.setState({canViewItem:true})
}
renderDisplay() {
const {canViewItem}=this.state
if(canViewOrder){
return <canViewItem cancel={this.cancel} item={item} />
}
else {
return this.renderMyList()
}
}
renderMyList(){
return(
<table>
<thead>
<tr>
<th>Iten Name</th>
<th>Quantity</th>
<th>More Details</th>
</tr>
</thead>
<tbody>
<tr>
<td>Item 1</td>
<td>10</td>
<td>onClick={() => this.viewItem(item1Obj)}</td>
</tr>
<tr>
<td>Item 2</td>
<td>30</td>
<td>onClick={() => this.viewItem(item2Obj)}</td>
</tr>
</tbody>
</table>
}
render(){
return this.renderDisplay()
}
}
How do i share a link so as it takes me direct to an item page ?
Inside of componentWillMount() evaluate what is being passed in this.props.location.pathname. You should be able to grab the ID # from your URL and update your state.
I don't see how you are determining in your existing code which item they are viewing.
componentWillMount() {
let id = '';
console.log(this.props.location.pathname);
// write a JS function to parse the ID from the URL here and save to 'id'
// then if you have an ID, save whatever you need to state.
if(id !== '') {
this.setState({
canViewItem: true
});
}
}
As a side note, renderDisplay and renderMyList should be included in your constructor.
I've got code like this. I would like to split deals array into 3 separate arrays: dealsCol1, dealsCol2, dealsCol2 and I would like to have it done after the object is created. I'm a beginner in JavaScript. I previously programmed in C++ and the thing that I guess I need is something like a constructor. I've found out that created() function works as a constructor (it's called on object creation). I put the part of the code that splits the array in that function, but I get an error:
vue.esm.js:591 [Vue warn]: Error in created hook: "ReferenceError: deals is not defined"
I have no idea why it's not defined there, because I guess that it should be. Could you give me some hints how can I solve the problem?
<script>
export default {
props: {
deals: Array
},
data() {
return {
dealsCol1: [],
dealsCol2: [],
dealsCol3: []
};
},
created() { // why it doesn't work??
this.dealsCol1 = this.deals.slice(0, this.deals.length/3),
this.dealsCol2 = this.deals.slice(this.deals.length/3, 2*this.deals.length/3),
this.dealsCol3 = this.deals.slice(2*this.deals.length/3, this.deals.length-1)
}
};
</script>
EDIT:
If I use the trick with computed() from one of the answers everything works good. But I'm wondering why the deals are visible in every other method beyond constructed(). It is also visible in the template part. Why is that?
The parent component code looks like this:
<template>
<div>
<editDealsModal ref="editDealsModal" :deals="deals" #editDeals="editDeals" />
<table class="table table-sm color mb-2">
<caption class="caption table-caption">Users</caption>
<thead>
<th class="text-left text-nowrap">Deals</th>
<th></th>
<th></th>
<th></th>
<th></th>
</thead>
<tbody>
<tr v-for="user in users" :key=user.Id v-bind:class="[{ disabled: user.IsEnabled == false }]">
<td class="text-left align-middle">{{user.Username}}</td>
<td class="text-left align-middle">
<div v-for="role in user.Roles" :key=role>{{role}}</div>
</td>
<td class="text-left align-middle">
<div v-for="deal in user.Deals" :key=deal>{{deal}}</div>
</td>
<td class="align-middle">
<b-btn variant="link" #click="showEditDealsModal(user)" v-bind:disabled="!user.IsEnabled">Edit deals</b-btn>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import service from '../../services/HttpRequestService'
import EditDealsModal from './EditDealsModal'
export default {
props: {
users: Array,
deals: Array
},
methods: {
showEditDealsModal(user) {
this.$refs.editDealsModal.showModal(user, user.Deals || [])
},
async editDeals(user, data) {
try {
await service.editDeals(user.Id, data);
this.$emit("userEdited", { type: "success", msg: "Updated deals for " + user.Username })
} catch (err) {
this.$emit("userEdited", { type: "danger", msg: "Failed to edit deals for " + user.Username })
}
},
},
components: {
EditDealsModal
}
}
</script>
enter code here
Try to defined a default value for your myData array, like this
props: {
myData: {
default: [],
type: Array,
},
},
By the way props are used to pass data from parent to child, I don't think this is the best way to do this.
myDataCol1,2,3 should be computed properties :
props: {
myData: {
default: [],
type: Array,
},
},
computed: {
myDataCol1(){
return this.myData.slice(0, this.myData.length/3)
}
.
.
.
}
I'm using Jest with vue-test-utils trying to test if a child component reacts to an $emit event in the parent component.
VueJS test-utils library provides a parentComponent option to be passed when mounting/shallow mounting the component.
Everything is working fine except that even though I instantiate the component with a mocked Vuex store, the parent component throws a
TypeError: Cannot read property 'state' of undefined
on a this.$store.state.something.here piece of code in the parent component.
How can I mock the Vuex store there?
The component mount looks like this:
const wrapper = shallowMount(ChildComponent, {
store,
localVue,
parentComponent: ParentComponent,
mocks: {
$t: msg => msg,
},
});
Any ideas on how to solve this?
it may not be the complete answer to OP questions, but since I had been debugging around for the last 2h and finally found MY problem, I would like to posted here to help someone in the future.
I was trying to mock and mount the following component:
<template>
<div test="object-list-div">
<h1 test="component-title">{{ objectName }}</h1>
<table class="table">
<thead>
<tr test="table-row-title">
<th scope="col" test="table-column-title" v-for="(value, name, index) in objectData[0]" :key="index">{{ name }}</th>
</tr>
</thead>
<tbody>
<tr test="table-row-data" v-for="(ivalue, iname, i) in objectData" :key="i">
<td test="table-cell-data" v-for="(jvalue, jname, j) in ivalue" :key="j">{{ jvalue }}</td>
</tr>
</tbody>
</table>
</div>
export default {
props: [
'objectName',
'objectData'
],
computed: {
visibleColums() {
return this.$store.state.Config_ShowColumn;
}
}
}
with the following wrapper code
wrapper = shallowMount(ObjectList, {
mocks: {
$store: {
state: {
Config_ShowColumn: [
"Field1",
"Field2",
"Field3",
"Field4",
"Field5",
]
}
}
}
});
I got OP error, but in my case the component was expecting two Props at the moment of creation. Since it did not receive this, it got stuck.
This is working now:
import { shallowMount } from "#vue/test-utils";
import { expect } from "chai";
import ObjectList from "#/components/Object-List.vue";
wrapper = shallowMount(ObjectList, {
propsData: {
objectName: "Ticket",
objectData: [
{
Field1: "Field1",
Field2: "Field2",
Field3: "Field3",
Field4: "Field4",
Field5: "Field5",
},
]
},
mocks: {
$store: {
state: {
Config_ShowColumn: [
"Field1",
"Field2",
"Field3",
"Field4",
"Field5",
]
}
}
}
});
Hope it helps someone.
Tried the solution Richard proposed but without much success, even though his guess was right.
The solution was far simnpler than I envisioned, I just stopped instantiating the Vuex.Store and just have the mocked $store in vue-test-utils config like so:
import { createLocalVue, shallowMount, config } from '#vue/test-utils';
config.mocks.$store = {
state: {
user: {
sexy: true
},
},
};
I had no need to use an actual instance of Vuex as I only needed to mock the actual data so this worked perfectly.
How are you creating the mock store? It should be something like
const storeOptions = {
state: {...},
getters: {...},
mutations: {...}
}
const mockStore = new Vuex.Store(storeOptions)
Since this.$store is undefined, I suspect you might just be passing the options object to shallowMount.
tl;dr:
Given a VueJS VNode object, how do I get the HTML element that would be generated if it were rendered?
e.g.:
> temp1
VNode {tag: "h1", data: undefined, children: Array(1), text: undefined, elm: undefined, …}
> temp1.children[0]
VNode {tag: undefined, data: undefined, children: undefined, text: "Test", elm: undefined, …}
> doSomething(temp1)
<h1>Test</h1>
Goal
I'm attempting to build a small VueJS wrapper around the DataTables.net library.
To mimic the behavior of HTML tables in my markup, I want something like the following:
<datatable>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th>Salary</th>
</tr>
</thead>
<tbody>
<datatable-row v-for="person in people">
<td>{{ person.name }}</td>
<td>{{ person.age }}</td>
<td>{{ person.salary }}</td>
</datatable-row>
</tbody>
</datatable>
What I've done so far
I've started to implement this as follows:
DataTable.vue
<template>
<table ref="table" class="display table table-striped" cellspacing="0" width="100%">
<slot></slot>
</table>
</template>
<script>
/* global $ */
export default {
data: () => ({
instance: null
}),
mounted() {
this.instance = $(this.$refs.table).dataTable();
this.$el.addEventListener("dt.row_added", function(e) {
this.addRow(e.detail);
});
},
methods: {
addRow(row) {
// TODO <-----
console.log(row);
}
}
};
</script>
DataTableRow.vue
<script>
/* global CustomEvent */
export default {
mounted() {
this.$nextTick(() => {
this.$el.dispatchEvent(new CustomEvent("dt.row_added", {
bubbles: true,
detail: this.$slots.default.filter(col => col.tag === "td")
}));
});
},
render() { return ""; }
};
What this currently does:
When the page loads, the DataTable is initialized. So the column headers are properly formatted and I see "Showing 0 to 0 of 0 entries" in the bottom left
The CustomEvent is able to bubble up past the <tbody> and be caught by the DataTable element successfully (circumventing the limitation in VueJS that you can't listen to events on slots)
What this does not do:
Actually add the row
My event is giving me an array of VNode objects. There's one VNode per column in my row. The DataTables API has an addRow function which can be called like so:
this.instance.row.add(["col1", "col2", "col3"]);
In my case, I want the resultant element from the rendering of the VNode to be the elements in this array.
var elems = [];
for (var i = 0; i < row.length; i++)
elems[i] = compile(row[i]);
this.instance.row.add(elems);
Unfortunately this compile method eludes me. I tried skimming the VueJS documentation and I tried Googling it, but no dice. I tried manually passing the createElement function (the parameter passed to the render method) but this threw an error. How can I ask VueJS to render a VNode without injecting the result into the DOM?
I ran into the same issue wanting to do basically the same thing with a row details template for DataTables.net.
One solution could be to create a generic component that renders out a VNode and instantiate that programmatically. Here is how my setup for a dynamic detail row that I insert using datatable's row.child() API.
RenderNode.js
export default {
props: ['node'],
render(h, context) {
return this.node ? this.node : ''
}
}
Datatables.vue
Include the renderer component from above
import Vue from 'vue'
import nodeRenderer from './RenderNode'
Instantiate and mount the renderer to get the compiled HTML
// Assume we have `myVNode` and want its compiled HTML
const DetailConstructor = Vue.extend(nodeRenderer)
const detailRenderer = new DetailConstructor({
propsData: {
node: myVNode
}
})
detailRenderer.$mount()
// detailRenderer.$el is now a compiled DOM element
row.child(detailRenderer.$el).show()
You should define your components like with:
import {createApp} from 'vue';
import {defineAsyncComponent} from "vue";
createApp({
components: {
'top-bar': defineAsyncComponent(() => import('#Partials/top-bar'))
}
}).mount("#app")
This question already has answers here:
React 'cannot read property of undefined' when using map
(4 answers)
Closed 5 years ago.
I've just started looking basics of ReactJs. Following is my component to show list of buses. What I want exactly is I want to perform edit/delete operations over buses. But not able to pass busId of corresponding bus to my edit/delete methods.
Following is component code
import React, {Component} from "react";
import { withRouter } from 'react-router-dom'
import {Table,Badge, Label,FormGroup,Container, Row, Col, CardGroup, Card, CardBlock,CardHeader, Button, Input, InputGroup, InputGroupAddon} from "reactstrap";
import {appSettings} from '../../../../Utils/Util.js';
import Pagination from "react-js-pagination";
var axios = require('axios');
class BusList extends React.Component {
constructor(props) {
super(props);
this.state = {
busList:[]
};
this.loadBuses = this.loadBuses.bind(this);
}
componentWillMount() {
this.loadBuses();
}
loadBuses() {
var url = ‘my-api-complete-url-here’;
axios.get(url)
.then((result) => {
var key = 0;
var buses = result.data.map(function(bus,i){
return <tr key={key++}>
<td key={key++}>{bus.id}</td>
<td key={(key++)}>{bus.number}</td>
<td key={(key++)}>
<Button onClick={(e)=>this.editBus(e, bus.id)}>Edit</Button>
<Button onClick={(e)=>this.deleteBus(e, bus.id)}>Delete</Button>
</td>
</tr>
});
this.setState({busList: buses});
})
.catch(function (error) {
console.log(error);
});
}
render() {
return (
<div className="animated fadeIn">
<Table hover responsive striped>
<thead>
<tr>
<th>Sr #</th>
<th>Bus Number</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{this.state.busList}
</tbody>
</Table>
</div>
);
}
editBus(id, e) {
console.log(‘Edit - Bus Id = ' +id);
}
deleteBus(id, e) {
console.log('Delete - Bus Id = ' +id);
}
}
export default BusList;
But when tapped on edit button, I receive this error(
Screenshot)
you're accessing it in wrong order, you're passing (e)=>this.editBus(e, bus.id) and in function you've defined editBus(id, e)
moreover you need to bind(this) at the end of map function
var buses = result.data.map(function(bus,i){
return <tr key={key++}>
<td key={key++}>{bus.id}</td>
<td key={(key++)}>{bus.number}</td>
<td key={(key++)}>
<Button onClick={(e)=>this.editBus(e, bus.id)}>Edit</Button>
<Button onClick={(e)=>this.deleteBus(e, bus.id)}>Delete</Button>
</td>
</tr>
});
Also, you don't need to define key variable. Instead, use second argument i of map function as it is the index of array element.
updated
you need to change your map code with
var buses = result.data.map(function(bus,i){
return <tr key={key++}>
<td key={key++}>{bus.id}</td>
<td key={(key++)}>{bus.number}</td>
<td key={(key++)}>
<Button onClick={(e)=>this.editBus(e, bus.id)}>Edit</Button>
<Button onClick={(e)=>this.deleteBus(e, bus.id)}>Delete</Button>
</td>
</tr>
}.bind(this));
The .bind(this) in the last line does the trick.