How to properly mount b-table component in vue-test-utils - javascript

I am using buefy css lib along with vue.js framework.
I am trying to unit test my vue component (Foo) which has b-table component from buefy:
<b-table :data="foo" class="container" style="width: 50%">
<b-table-column v-slot="props">
<b-icon
pack="fas"
icon="times"
class="is-clickable"
#click.native="doSomething(props.row)"
></b-icon>
</b-table-column>
</b-table>
note the embedded b-icon
In Foo.spec.js test file I am trying to mount the component using shallowMount from vue-test-utils:
import Buefy from "buefy";
import { shallowMount, createLocalVue } from "#vue/test-utils";
import Foo from './Foo.vue'
const localVue = createLocalVue();
localVue.use(Buefy);
const wrapper = shallowMount(Foo, {
localVue,
});
Now I would like to use the returned wrapper to perform some actions on b-icon which should be embedded in the table column.
const icon = wrapper.find('.is-clickable')
icon.vm.$emit('click')
But I get the error:
TypeError: Cannot read property '$emit' of undefined
The thing is, that this b-icon is in fact missing in the wrapper. It can be confirmed via wrapper.html():
<b-table-stub data="[object Object],[object Object]" columns="" headercheckable="true" checkboxposition="left" isrowselectable="[Function]" isrowcheckable="[Function]" checkedrows="" mobilecards="true" defaultsortdirection="asc" sorticon="arrow-up" sorticonsize="is-small" sortmultipledata="" currentpage="1" perpage="20" showdetailicon="true" paginationposition="bottom" rowclass="[Function]" openeddetailed="" hasdetailedvisible="[Function]" detailkey="" detailtransition="" total="0" filtersevent="" showheader="true" class="container" style="width: 50%;">
<b-table-column-stub visible="true" thattrs="[Function]" tdattrs="[Function]"></b-table-column-stub>
</b-table-stub>
b-icon has magically disappeared.
Fully mounting via mount is not helpfull either:
<div class="b-table container" style="width: 50%;">
<!---->
<!---->
<!---->
<div class="table-wrapper has-mobile-cards">
<table class="table">
<!---->
<tbody>
<tr draggable="false" class="">
<!---->
<!---->
<!---->
</tr>
<transition-stub name="">
<!---->
</transition-stub>
<!---->
<tr draggable="false" class="">
<!---->
<!---->
<!---->
</tr>
<transition-stub name="">
<!---->
</transition-stub>
<!---->
<!---->
</tbody>
<!---->
</table>
<!---->
</div>
<!---->
</div>
How can I access data / components embedded in the table columns?
b-table documentation: link
b-table source code: link

Try to use shallowMount but stub b-table-column yourself like this:
const BTableColumnStub = {
template: <span class="b-table-column-super-stub"><slot name="props"></slot></span>
}
const wrapper = shallowMount(Foo, {
localVue,
stubs: {
BTableColumn: BTableColumnStub
}
});
BIcon in your component is passed as named slot props. Default Jest`s stubbing just stubs component not taking slots into account. I think that should help to render BIcon.
And btw, if you want to trigger click event, you should call
await icon.trigger('click') not icon.vm.$emit('click'). But to test event listener you also can call wrapper.vm.$emit('your-event') , I mean you should call $emit on the component that is listening to that event, not on the one that would emit that event.

b-table has child components.
You may try to use mount instead of shallowMount.
That will render b-table.

Related

Mount a vue component to an element in Nuxt 3

I'm trying to mount a Vue component to an element that is rendered by a v-html directive. The parent Vue component is a table. Every table cell has richtext content (including images).
If there is an image in the richtext, I need to add an existing copyright component, that opens an overlay. So it can't be plain HTML.
The component looks as follows (simplified):
How do I do this?
<script lang="ts" setup>
import { onMounted, ref } from '#imports'
const tableEl = ref<Array<HTMLTableElement>>([])
const imageEls = ref<Array<HTMLImageElement>>([])
onMounted(() => {
const els = tableEl.value.querySelectorAll('p > img')
imageEls.value = Array.from(els) as Array<HTMLImageElement>
imageEls.value.forEach((imageEl) => {
const parent: HTMLParagraphElement = imageEl.parentElement as HTMLParagraphElement
parent.style.position = 'relative' // Up to this point, everything works...
// How do I add my "<CopyrightNotice/>" component here?
})
})
</script>
<template>
<div>
<table>
<tr v-for="row in rows" :key="row.id">
<td ref="tdEls" v-for="col in row.cols" v-html="col.content" :key="col.id" />
</tr>
</table>
</div>
</template>
Rendered, it looks like this:
<div>
<table>
<tr>
<td>
<p>Hello World!</p>
</td>
<td>
<p>Content</p>
<p>
<img src="/link/to/src.jpg" alt="a cat">
</p>
</td>
</tr>
</table>
</div>
Instead of working with DOM using JS it is better idea to render something called vnode.
Your solution can break Vuejs reactivity / virtual DOM.
Follow documentation here: Render Functions & JSX
There is an example with combining HTML elements and Vuejs components.

[Vue warn]: Error in callback for watcher "childItems": "TypeError: Cannot read property 'tag' of undefined"

I am currently working on the Store page of a webapp I am building using Axios to fetch product data and then display it in a vue template. All the backend works and the front end renders successfully as well, however Vue gives a Warning twice in the console stating:
Error in callback for watcher "childItems": "TypeError: Cannot read property 'tag' of undefined"
To combat this, I have tried wrapping the v-for for iterating products in a v-if that is set to true by the async getProducts function which sends a get request for the product data to display. I have tried moving around where the getter function is, placing it in Mounted, beforeMount, created, used vue-async-computed and all have rendered fine but none have gotten rid of the error.
Here is my store.vue file:
<template>
<div id="store">
<section>
<b-tabs>
<div class="container">
<div v-if="fetchComplete">
<b-tab-item v-for="(product, index) in products" :label="product.categories[0].title" :key="index">
<div class="card">
<div class="card-image">
<figure class="image">
<img :src="'http://localhost:1339'+ product.product_color_imgs[0].img[0].url" :alt="product.title">
</figure>
</div>
<div class="card-content">
<p class="title is-4">{{product.title}}</p>
</div>
</div>
</b-tab-item>
</div>
</div>
</b-tabs>
</section>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
products: [],
fetchComplete: false
}
},
async beforeCreate() {
await axios({
method:'get',
url:'http://localhost:1339/products'
}).then(res => {
this.products = res.data;
this.fetchComplete = true;
}).catch(err => {
console.log(err);
})
}
}
</script>
In this version I used beforeCreate but I have tested it with the other life-cycle functions. My guess is that the promise from beforeCreate returns after the page has rendered the first time.
Note: I am relatively new to Vue.js so it is possible I may have overlooked something
Thank you in advance!
This is caused by placing the <div> tags inside of the <b-tabs> component but ouside of a <b-tab-item>. It seems that <b-tabs> requires that no element other than a <b-tab-item> be placed in the slot root.
Try moving the divs outside of that component like:
<div class="container">
<div v-if="fetchComplete">
<b-tabs>
<b-tab-item v-for...>
...
</b-tab-item>
</b-tabs>
</div>
</div>

Create my own component vuejs, with two diferents parts

I have a component with an input that when clicking opens a modal.
When I use this component, and insert it inside a div with relative position, the modal that opens, it does not display well. I would need the html of the modal to be outside the div position relative.
It is important that my component contains both the input and the modal, since this component itself will be used several times within another component.
<div class="position-relative">
<MyOwnComponet />
</div>
<div class="position-relative">
<MyOwnComponet />
</div>
My component would be something like this, more or less:
<template>
<input #click="showModal = true" />
<div class="modal" v-if="showModal">
...
</div>
</template>
I am not sure what's your point for this kind of requirement but anyway there is a workaround you can do with props.
You can have props in "MyOwnComponet" like this. And use that prop value to render accordingly.
Your main component
<div class="position-relative">
<MyOwnComponet :isInput="true" />
</div>
<div class="position-relative">
<MyOwnComponet :isInput="false" />
</div>
Your (MyOwnComponent)
<template>
<div v-if="isInput">div-1</div>
<div v-else>div-2</div>
</template>
<script>
export default {
props: {
isInput : Boolean,
},
data() {
}
};
</script>
You can replace div-1 with your input and div-2 with modal.

Vue TypeError: Cannot read property '_wrapper' of undefined when attempting to update Object property

I am trying to use a button to update a property of an Object in Vue. The Object is returned by an AJAX query to a database, and the isFetching boolean is then set to false, which attaches the containing div to the DOM. When I try and update the property, I get the following error:
TypeError: Cannot read property '_wrapper' of undefined
Below is my AJAX code:
axios.post("/api/myEndpoint", { id: router.currentRoute.query.id })
.then((response) => {
this.activities = response.data.activities;
this.isFetching = false;
})
.catch((errors) => {
console.log(errors);
router.push("/");
});
Here is my HTML:
<div v-if="isFetching">
<h2>Loading data...</h2>
</div>
<div v-else>
<div class="row no-gutters">
<div class="col">
<div class="d-flex flex-row justify-content-center">
<h4>Activities</h4>
</div>
<div class="custom-card p-2">
<div class="row no-gutters pb-4" v-for="(activity, index) in activities"
:key="activity.stage_id">
<button v-if="!activity.is_removed" class="btn custom-btn" :class="{'hov':
index == 0}" :disabled="index != 0"
#click="markRemoved(activity)">Remove</button>
</div>
</div>
</div>
</div>
</div>
Finally, here is markRemoved() as called by the button's click listener:
markRemoved(a) {
a.is_removed = true;
}
When I try and log a in markRemoved() the Object is logged to the console fine, exactly as expected. Having stepped through it in the debugger, the exception is thrown at the point I try and update the is_removed property of the Object.
Does anyone know what I'm doing wrong here?
Note: the id I pass to the AJAX query is a query parameter of Vue Router. This is also set correctly and passed as expected.
Here is an example activity Object:
{date_time_due: "2020-12-09T11:43:07.740Z"
date_time_reached_stage: "2020-12-02T11:43:07.740Z"
is_complete: true
is_removed: false
selected_option: "Some text"
stage_id: 1
stage_name: "Some text"}
The exception is only thrown on the first click of the button.
Posting in case anybody else comes across this error in the future.
Vue requires exactly one root element within a single-file component's <template> tags. I had forgotten about this and in my case had two <div> elements, shown one at a time, conditionally using v-if:
<template>
<div v-if="fetchingData">
<h2>Loading data...</h2>
</div>
<div v-else>
<!-- The rest of the component -->
</div>
</template>
This caused problems with Vue's reactivity, throwing the error whenever I tried to update some part of the component. After realising my mistake, I wrapped everything in a root <div>. This solved the issue for me:
<template>
<div id="fixedComponent">
<div v-if="fetchingData">
<h2>Loading data...</h2>
</div>
<div v-else>
<!-- The rest of the component -->
</div>
</div>
</template>

Change route with vue- <router-link>

I have a <router-link to = "/ endreco / test">, but I need to perform the same behavior on a vue-material tab, something like this: <md-tab md- icon = "books"> .. to change my route, same as the href = ""
What should I do?
I'm using vue.js, the vue-router to control routes and style with vue-material
You can add a #click.native handler to push to a route manually (the .native modifier is needed since the md-tab component does not have a click event):
<md-tab #click.native="$router.push('/endreco/test')">
Here's the documentation on Programmatic Navigation with Vue Router.
See my code I have implemented a method which traverses to router to see routes of mentioned name of component, you can easily get idea!
<template>
<div>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--3-col mdl-cell mdl-cell--1-col-tablet mdl-cell--hide-phone"></div>
<div class="mdl-cell mdl-cell--6-col mdl-cell--4-col-phone">
<div class="image-card" v-for="picture in this.pictures" #click="displaydetails(picture.id) ">
<div class="image-card__picture">
<img :src="picture.url" />
</div>
<div class="image-card__comment mdl-card__actions">
<span>{{ picture.comment }}</span>
</div>
</div>
</div>
</div>
<router-link class="add-picture-button mdl-button mdl-js-button mdl-button--fab mdl-button--colored" to="/postview">
<i class="material-icons">add</i>
</router-link>
</div>
</template>
<script>
import data from '../data'
export default {
data() {
return{
'pictures' : data.pictures
}
},
methods :{
displaydetails (id){
this.$router.push({name:'detailview', params:{id:id}});
console.log("helo");
}
}
}
</script>
Hope get something resourceful out of it!

Categories

Resources