Jest: Quasar table is not rendering rows - vue.js

I'm using Jest for testing a quasar table in VueJs 3. The table I have is like this:
<template>
<q-table id="myTable"
:rows="rows"
:columns="columns"
virtual-scroll
:rows-per-page-options="[0]"
hide-bottom
/>
</template>
<script lang="ts">
import {defineComponent, PropType} from 'vue'
export default defineComponent({
name: 'TableDemo',
props: {
rows: {
type: [] as PropType<[{ name: string, surname: string }]>,
default: []
},
},
setup() {
const columns = [
{
name: 'name',
required: true,
align: 'left',
label: 'name',
field: 'name',
sortable: true
},
{
name: 'surname',
label: 'surname',
align: 'left',
field: 'surname',
sortable: true
},
]
return {columns}
}
})
</script>
I'm trying to write a very simple test to understand how this works:
it('Should contain the word "John"', async () => {
)
const wrapper = mount(TableDemo, {
props: {rows: [{name: 'John', surname: 'Marston'}]},
}
)
expect(wrapper.find('#myTable').text()).toContain('John')
})
My problem is that it doesn't render the rows, but only the columns.
Here what Jest renders:
<div class="q-table__container q-table--horizontal-separator column no-wrap q-table__card q-table--no-wrap"
id="myTable"><!---->
<div class="q-table__middle q-virtual-scroll q-virtual-scroll--vertical scroll">
<table class="q-table">
<thead>
<tr>
<th class="text-left sortable">name<i aria-hidden="true"
class="notranslate material-icons q-icon q-table__sort-icon q-table__sort-icon--left"
role="presentation">arrow_upward</i></th>
<th class="text-left sortable">surname<i aria-hidden="true"
class="notranslate material-icons q-icon q-table__sort-icon q-table__sort-icon--left"
role="presentation">arrow_upward</i></th>
</tr>
</thead>
<tbody class="q-virtual-scroll__padding">
<tr>
<td colspan="2" style="height: 0px; --q-virtual-scroll-item-height: 48px;"/>
</tr>
</tbody>
<tbody class="q-virtual-scroll__content" id="qvs_1" tabindex="-1"/>
<tbody class="q-virtual-scroll__padding">
<tr>
<td colspan="2" style="height: 48px; --q-virtual-scroll-item-height: 48px;"/>
</tr>
</tbody>
</table>
</div><!----></div>
Where is my mistake? Why doesn't the table render the row?
UPDATE
I actually realized that it works without "virtual-scroll" option, so the problem is bound to that. Does anyone have experience with it?

It seems they have used some timer function inside virtual scroll implementation, this is my fix:
call jest.useFakeTimers(); method before describe block, then inside test call jest.runAllTimers(); and await wrapper.vm.$nextTick(); before assertion
it('renders correctly', async () => {
const wrapper: any = wrapperFactory();
jest.runAllTimers();
await wrapper.vm.$nextTick();
expect(wrapper.html()).toMatchSnapshot();
});

Related

how to display 2 lists filtered by another field from one model

Model: Article.
id.
name.
type: ['code', 'design']
API gets all articles
How can I display two lists:
all articles with type ='Code',
all articles with type = 'Design'
In other words, is it possible to filter the API query
Or is it better to do it on the API side?
Extra: same as above but in a nested environment (ie Articles belong to Category. How to do it on the category detail page.
You can use computed properties. I built a sample component:
EDIT: Took some time to DRY it up.
Parent.vue
<template>
<div class="parent">
<div class="row">
<div class="col-md-6">
<article-list title="Code Articles" :articles="codeArticles" />
</div>
<div class="col-md-6">
<article-list title="Design Articles" :articles="designArticles" />
</div>
</div>
</div>
</template>
<script>
import ArticleList from './ArticleList.vue'
export default {
components: {
ArticleList
},
data() {
return {
articles: [
{
id: 1,
name: 'Article1',
type: 'Code'
},
{
id: 2,
name: 'Article2',
type: 'Design'
},
{
id: 3,
name: 'Article3',
type: 'Code'
},
{
id: 4,
name: 'Article4',
type: 'Design'
},
]
}
},
computed: {
codeArticles() {
return this.articles.filter(article => article.type === 'Code');
},
designArticles() {
return this.articles.filter(article => article.type === 'Design');
}
}
}
</script>
ArticleList.vue
<template>
<div class="two-filtered-lists">
<h5>{{ title }}</h5>
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>NAME</th>
<th>TYPE</th>
</tr>
</thead>
<tbody>
<tr v-for="article in articles" :key="article.id">
<td>{{ article.id }}</td>
<td>{{ article.name }}</td>
<td>{{ article.type }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
required: true
},
articles: {
type: Array,
required: true
}
}
}
</script>

How to store array to Vuex in VueJs

Can someone tell me how to write an array of objects in Vuex
I will explain the essence
In this situation, I need to create an order management system
I have an array of products with different data
This array I get from the server using axios request
Via v-for, I displayed this array in the select option html tag
Next, by clicking on the html option tag, I need to add the product to the data table
Also, with the addition of a specific product to the table, it is necessary that the product for which a click is made is recorded in the Vuex store, but not with rewriting, but with addition to existing data
Next, in synchronization with the addition to the Vuex store, information is output from the Vuex store to the table
this is my code in Vue Component
<div class="row">
<div class="col-md-8">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>QTY</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-4">
<form>
<div class="form-group">
<label for="products">Select Products</label>
<select v-model="orderData" #change="addOrderData" id="products" class="form-control">
<option v-for="product in products" multiple v-bind:value="{productId: product.id, name: product.name, price: product.price}">{{product.name}}</option>
</select>
<pre>{{orderData}}</pre>
</div>
</form>
</div>
</div>
data() {
return {
selected: '',
formData: {
userId: ''
},
orderData: {
productId: [],
price: [],
total: [],
quantity: []
}
}
},
methods: {
addOrderData()
{
let data = {
productId: this.orderData.productId,
name: this.orderData.name,
price: this.orderData.price,
}
this.$store.commit('orders/setOrderProduct', data)
}
},
this is my code in Vuex store
function initialState () {
const orderProducts = [];
return {
orderProducts
}}
const getters = {
orderProducts(state)
{
return state.orderProduct;
},};
const mutations = {
setOrderProduct(state, orderProduct)
{
state.orderProduct = orderProduct;
}};
If I understand you correctly, please check the following:
template:
<select v-model="orderData" #change="addOrderData">
<option v-for="(product) in products" :key="product.productId" :value="{productId: product.productId, name: product.name, price: product.price}">
{{product.productId}} - {{product.name}}</option>
</select>
<br /><br />
<table>
<thead>
<tr>
<th>name</th>
<th>price</th>
</tr>
</thead>
<tbody v-if="orderProducts.length > 0">
<tr v-for="(item, index) in orderProducts" :key="index">
<td>{{item.name}}</td>
<td>{{item.price}}</td>
</tr>
</tbody>
<tbody v-else>
<tr>
<td colspan="2">No products to display</td>
</tr>
</tbody>
</table>
code:
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
orderProducts: []
},
mutations: {
addProduct(state, payload) {
state.orderProducts.push(payload);
}
}
})
new Vue({
store,
el: '#app',
data() {
return {
products: [{
productId: 1,
name: 'Product One',
price: 10
},
{
productId: 2,
name: 'Product Two',
price: 15
}
],
orderData: {
productId: [],
price: [],
total: [],
quantity: []
}
};
},
computed: {
orderProducts() {
return this.$store.state.orderProducts;
}
},
methods: {
addOrderData() {
this.$store.commit('addProduct', this.orderData);
}
}
});
The idea is when change event is triggered by selecting an option, you commit mutation to the store with the selected product as payload. The orderProducts computed property will refresh, and the table will get the latest data. You can check this jsfiddle.

dynamically call an object property in a v-for loop

so i ran into a problem again,
i want to make a table component where you can send a array to the component, and it will render a table for you
we set it up like this
<template>
<section class="container">
<Apptable :search="true" :loader="true" title="User data" :data="users"/>
</section>
</template>
<script>
import Apptable from "~/components/table.vue";
export default {
components: {
Apptable
},
data() {
return {
users: [
{
id: 1,
name: "Lars",
Adres: "hondenstraat 21",
phone: "06555965"
},
{
id: 1,
name: "John",
Adres: "verwelstraat 35",
phone: "06555965"
}
]
};
}
};
</script>
i send data to the component and loop it from there like this
<template>
<section class="container">
<h2 v-if="title">{{title}}</h2>
<input v-if="search" class="search" placeholder="Search">
<button v-if="loader" class="update" #click="dialog = true">Update</button>
<table class="table">
<thead>
<tr class="tableheader">
<th v-for="(item, index) in Object.keys(data[0])" :key="index">{{item}}</th>
</tr>
</thead>
<tbody>
<tr class="userdata" v-for="(item, index) in data" :key="index">
<td v-for="(name, index) in Object.keys(data[index])" :key="index">{{//TODO: I WANT TO SELECT THE ITEM.DYNAMIC PROPERTY}}</td>
</tr>
</tbody>
</table>
<loader v-if="loader" :trigger="dialog"/>
</section>
</template>
<script>
import loader from "~/components/loader.vue";
export default {
components: {
loader
},
data() {
return {
dialog: false
};
},
watch: {
dialog(val) {
if (!val) return;
setTimeout(() => (this.dialog = false), 1500);
}
},
props: {
data: {
type: Array,
required: true
},
title: {
type: String,
required: false,
default: false
},
loader: {
type: Boolean,
required: false,
default: false
},
search: {
required: false,
type: Boolean,
default: true
}
}
};
</script>
so if you look at the table. were i left the todo, if i put in the {{item}} in the todo place. i will get this in my column
enter image description here
but i want to select the key of the object dynamically. but if i put {{item.name}} in the todo place it will not select the key dynamically.
so the point is that i want to dynamically call a property from the object in the v-for so the columns will get the data in the cells.
You should use item[name] instead of item.name
<tbody>
<tr class="userdata" v-for="(item, index) in data" :key="index">
<td v-for="(name, nIndex) in Object.keys(data[index])" :key="nIndex">
{{ item[name] }}
</td>
</tr>
</tbody>

Looping a component in another component Vue.js

The question here is how can I get this Task.vue file to loop in my tasklist.vue file knowing I'm willing to pass a json file so I can get the list of all the task to do.
Task.vue
<template>
<table :id="id" class="task_box">
<tr>
<td class="task_user">{{name}}</td>
<td class="task_date">{{date}}</td>
<td class="task_time">{{time}}</td>
</tr>
<tr>
<td colspan="3" class="task_description">
<div class="toto">{{description}}</div>
</td>
</tr>
</table>
</template>
<script>
export default {
name: "task",
data() {
return {
id: 1,
name: "Test",
date: new Date(),
time: "9:30 ",
description: "whatever"
};
}
};
</script>
So this task.vue is meant to be a container that I can use in the tasklist.vue.
tasklist.vue
<template>
<div>
<task v-for="task in tasks" :key="task.id"></task>
</div>
</template>
<script>
import Task from "./Task.vue";
export default {
name: "tasklist",
data() {
return {
tasks: []
};
},
components: {
Task
}
};
</script>
You need to use Properties to pass your task from your task list to your task. Code is untested.
#Task
<template>
<table :id="task.id" class="task_box">
<tr>
<td class="task_user">{{task.name}}</td>
<td class="task_date">{{task.date}}</td>
<td class="task_time">{{task.time}}</td>
</tr>
<tr>
<td colspan="3" class="task_description">
<div class="toto">{{description}}</div>
</td>
</tr>
</table>
</template>
<script>
export default {
name: "task",
props: ["task"],
};
</script>
#TaskList
<template>
<div>
<task v-for="task in tasks" :task="task" :key="task.id"></task>
</div>
</template>
<script>
import Task from "./Task.vue";
export default {
name: "tasklist",
data() {
return {
tasks: [{
id: 1,
name: "Test",
date: new Date(),
time: "9:30 ",
description: "whatever"
}]
};
},
components: {
Task
}
};
</script>
If task-component is reapeating, you should insert it's tag inside table tag.
Use props to pass data to task-component from tasklist-component
When tasklist-component is creating, you can load tasks via Ajax from json.
Full working example of code you can find here
TaskList.vue
<script>
import Task from "./Task.vue";
export default {
components: { Task },
data() {
return {
tasks: [],
isLoading: false,
doShowNewTaskAddingDialog: false,
};
},
created(){
// this.isLoading = true;
// $.ajax({
// url: '/some/tasks/url',
// method: 'GET',
// dataType: 'json',
// success: (tasks) => {
// this.isLoading = false;
// this.tasks = tasks;
// }
// });
this.tasks = [
{id: 1, name: "task 1", date: new Date(), time: "9:31", description: "descr 1" },
{id: 1, name: "task 2", date: new Date(), time: "9:32", description: "descr 2" },
{id: 1, name: "task 3", date: new Date(), time: "9:33", description: "descr 3" },
{id: 1, name: "task 4", date: new Date(), time: "9:34", description: "descr 4" },
]
},
methods:{
addButtonHandler(){
this.doShowNewTaskAddingDialog = true;
}
}
};
</script>
<template>
<div>
<div v-if="isLoading">Please wait, loading tasks...</div>
<table v-if="!isLoading">
<task
v-for="task in tasks"
:key="task.id"
:task="task"
:isNew="false"
/>
<task
v-if="doShowNewTaskAddingDialog"
:isNew="true"
/>
</table>
Add new?
</div>
</template>
<style>
table, td{
border-collapse: collapse;
border: 1px solid black;
}
</style>
Task.vue
<template>
<!--
I'd prefer use bootstrap row and col- divs here instead
of table and tbody-hack. See discussion here: https://github.com/vuejs/Discussion/issues/295
-->
<tbody>
<!-- display only -->
<tr v-if="!isNew">
<td class="task_user">{{name}}</td>
<td class="task_date">{{date}}</td>
<td class="task_time">{{time}}</td>
</tr>
<tr v-if="!isNew">
<td colspan="3" class="task_description">
<div class="toto">{{description}}</div>
</td>
</tr>
<!-- edit -->
<tr v-if="isNew">
<td class="task_user"><input type="text" v-model="name"></td>
<td class="task_date"><input type="text" v-model="date"></td>
<td class="task_time"><input type="text" v-model="time"></td>
</tr>
<tr v-if="isNew">
<td colspan="3" class="task_description">
<div class="toto"><input type="text" v-model="description"></div>
</td>
</tr>
</tbody>
</template>
<script>
export default {
props:{
task: { type: Object, required: false },
isNew: { type: Boolean, required: true },
},
created(){
if(!this.isNew){
this.id = this.task.id;
this.name = this.task.name;
this.date = this.task.date;
this.time = this.task.time;
this.description = this.task.description;
}
},
data() {
return {
id: 1,
name: "",
date: new Date(),
time: "0:00 ",
description: ""
};
}
};
</script>

Computed property on child component props

I'm having this setup where I have child component props that have datetime format inside it and I want to change it to more human readable format in my table, so i use moment js to change the format and to do those kinds of task it will be made more sense if I use computed property. Like this in my index.vue
<div class="page-container">
<div class="page-content">
<div class="content-wrapper">
<data-viewer :source="source" :thead="thead">
<template scope="props">
<tr>
<td>{{props.item.name}}</td>
<td>{{props.item.creator}}</td>
<td>
<i class="icon-checkmark5" v-if="props.item.publish === '0'"></i>
<i class="icon-cancel-circle2" v-else></i>
{{props.item.publish}} //for testing purpose to see returned value
</td>
<td>{{publishDate}}</td> //this is where i put my computed to change created_at format
</tr>
</template>
</data-viewer>
</div>
</div>
</div>
<script type="text/javascript">
import DataViewer from '../../components/dataviewer.vue'
import moment from 'moment'
export default{
components:{
DataViewer
},
data(){
return{
source: '/api/article',
thead: [
{title: 'Name', key: 'name', sort: true},
{title: 'Creator', key: 'creator_id', sort: true},
{title: 'Publish', key: 'publish', sort: true},
{title: 'Created', key: 'created_at', sort: true}
],
}
},
computed: {
publishDate: function(){
return moment(props.item.created_at).format('YYYY-MM-DD')
}
}
}
</script>
and here is what inside my dataviewer file
<template>
<table class="table">
<thead class="bg-primary">
<tr>
<th v-for="item in thead">
<span>{{item.title}}</span>
</th>
</tr>
</thead>
<tbody>
<slot v-for="item in model.data" :item="item"></slot>
</tbody>
</table>
</template>
<script>
import Vue from 'vue'
import axios from 'axios'
export default {
props: ['source', 'thead'],
data() {
return {
model: {
data: []
},
}
},
beforeMount() {
this.fetchData()
},
methods: {
fetchData() {
var vm = this
axios.get(this.source)
.then(function(response) {
Vue.set(vm.$data, 'model', response.data.model)
})
.catch(function(error) {
console.log(error)
})
}
}
}
</script>
but it just won't work, it can't find props.item.created_at, so how I can change created_at or any other property item to change from my index.vue?
Seems like using a filter will work here:
Instead of using computed props:
filters: {
publishDate: function(value){
return moment(value).format('YYYY-MM-DD')
}
}
In place of {{publishDate}}
<td>{{props.item.created_at | publishedDate }}</td>