How to capture object props in Vue snapshot tests - unit-testing

I always get [object Object] in place of object props due to object to string coercion when I use snapshot testing. How can I fix it? I've tried wrapping element into JSON.stringify(), but it causes "Converting circular structure to JSON" Error.
The example of a resulting snapshot:
exports[`SalesList.vue Снапшот десктоп 1`] = `
<magic-grid-stub
class="sales-list"
cols="[object Object]"
gaps="[object Object]"
usemin="true"
>
<sales-item-stub
class="item"
sale="[object Object]"
/>
<sales-item-stub
class="item"
sale="[object Object]"
/>
<sales-item-stub
class="item"
sale="[object Object]"
/>
<sales-item-stub
class="item"
sale="[object Object]"
/>
<sales-info-stub
class="item"
content="additionalInfo"
/>
</magic-grid-stub>
`;
I have the simple corresponding snapshot tests, like this one:
import { createLocalVue, shallowMount } from '#vue/test-utils'
import SalesList from '#/components/sales/SalesList.vue'
let localVue
const fakeSale = {
code: 'code',
description: 'description',
title: 'title',
image: 'image',
archive: false,
visible: true,
date_to: '2020/08/01',
short_description: 'short_description',
slug: 'slug',
date_from: '2020/06/01',
seo: {
seo_description: 'seo_description',
seo_title: 'seo_title',
seo_keywords: 'seo_keywords',
},
}
function createWrapper(component, options) {
return shallowMount(component, {
localVue,
...options,
})
}
beforeAll(() => {
localVue = createLocalVue()
})
describe('SalesList.vue', () => {
it('Снапшот десктоп', async () => {
expect.assertions(1)
const wrapper = createWrapper(SalesList, {
propsData: {
sales: Array.from({ length: 4 }, (_, index) => ({
...fakeSale,
slug: `slug-${index}`,
})),
additionalInfo: 'additionalInfo',
},
mocks: {
$device: { isDesktop: true },
},
})
expect(wrapper.element).toMatchSnapshot()
})
})
And the component in question itself:
<script lang="ts">
import SalesItem from '#/components/sales/SalesItem.vue'
import MagicGrid from '#/components/MagicGrid.vue'
import SalesInfo from '#/components/sales/SalesInfo.vue'
import Vue from 'vue'
export default Vue.extend({
name: 'SalesList',
components: {
SalesItem,
MagicGrid,
SalesInfo,
},
props: {
sales: {
type: Array,
required: true,
},
additionalInfo: {
type: String,
default: null,
},
},
computed: {
colsAndGaps(): {
cols: { 0: number }
gaps: { 0: number }
} {
return this.$device.isDesktopOrTablet
? {
cols: {0: 2},
gaps: {0: 30},
}
: {
cols: {0: 1},
gaps: {0: 16},
}
},
},
})
</script>
<template>
<magic-grid v-bind="colsAndGaps" class="sales-list">
<sales-item
v-for="sale in sales"
:key="sale.slug"
:sale="sale"
class="item"
/>
<sales-info v-if="additionalInfo" :content="additionalInfo" class="item"/>
</magic-grid>
</template>

You could use a custom jest snapshot serializer.
For VueJs 2 you could use https://github.com/tjw-lint/jest-serializer-vue-tjw - but it doesn't work for VueJs 3 (https://github.com/tjw-lint/jest-serializer-vue-tjw/pull/64).
Example configuration for VueJs 2:
npm install jest-serializer-vue-tjw
// package.json
{
...
"jest": {
"snapshotSerializers": ["jest-serializer-vue-tjw"]
}
}

Related

vue3 filtered data on computed with chartjs

im learning about vuejs 3 and for example i want to show a chartjs with a combo to filter data. (Without the combo, everything works correctly)
i have a parent component chartjs.vue where import the chartjs and the combo. when i select a year, i change the variable and show it on the template
<script setup>
import ChartJsBarChart from '#/views/charts/chartjs/ChartJsBarChart.vue'
import SelectYear from '#/componentes/selectYear.vue'
const api = ref('exp')
const year = ref([])
function selectYear(x) {
year.value = x
}
</script>
<template>
<!-- 👉 Latest Statistics -->
<VCol cols="24" md="12">
<VCard>
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
<VCardTitle>Choose a year</VCardTitle>
</VCardItem>
<VCardText>
<SelectAno #input="selectAYear" />
</VCardText>
</VCard>
</VCol>
<VCol cols="24" md="12">
<VCard>
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
<VCardTitle>Total</VCardTitle>
</VCardItem>
<VCardText>
<ChartJsBarChart :yearSelected="year" :url="api" />
</VCardText>
</VCard>
</VCol>
</template>
one child component with a select (to try props and emit) selectYear.vue
<script setup>
import { ref, defineEmits, watch } from 'vue'
const emit = defineEmits(['input'])
let selected = ref([])
const years = [
'2005',
'2008',
'2009',
'2010',
'2011',
'2012',
'2014',
'2015',
'2016',
'2017',
'2018',
'2019',
'2020',
'2021',
'2022',
'2023',
]
watch(
() => selected.value,
(newValue, oldValue) => {
change(newValue)
},
)
const change = val => emit('input', val)
</script>
<template>
<VSelect
v-model="selected"
:items="years"
label="choose a year"
/>
</template>
and last child component with the chartjs ChartJsBarChart.vue
<script setup>
import BarChart from '#/libs/chartjs/components/BarChart'
import axios from '#axios'
import { ref, onMounted, computed } from "vue"
import { useStatesStore } from '#/store/states'
const props = defineProps({
url: {
type: null,
required: true,
},
yearSelected: {
type: null,
required: true,
},
})
const apiData = ref([]) //data from API
const data = ref({}) // data parsed to chartjs
const store = useStatesStore()
const states = ref(store.states) // legend
const filterByYear = computed(() => {
return apiData.value.filter(item => item.year === '2005')
})
onMounted(async () => {
await axios
.get('/api/dashboard/expProvActiv')
.then(res => {
apiData.value = res.data['hydra:member']
})
})
</script>
<template>
{{ props.yearSelected }}
<hr>
{{ apiData }}
<BarChart
:height="400"
:chart-data="data"
/>
</template>
the result (shortened) of {{ apiData }} with hundred of items is:
[ { "state": "state 1", "activity": "string", "subactivity": "string", "year": 2015, "surface": "3.5500", "total": 3, "rcs": 3 }, { "state": "state 2", "activity": "string", "subactivity": "string", "year": 2016, "surface": "10.9400", "total": 13, "rcs": 13 }]
if i try to show filterByYear, the first time i have not data and how can i do to call filterByYear when i change the year?
[answer myself] the year was string instead number
how can i update the child component BarChart to update the information?
thank you!

TypeError: null is not an object (evaluating 'RNFusedLocation.getCurrentPosition')

Please, am new to react native. Am trying to get the current location through google places autocomplete for android in expo but an getting the error 'TypeError: null is not an object (evaluating 'RNFusedLocation.getCurrentPosition')'
below is my code
Please, am new to react native. Am trying to get the current location through google places autocomplete for android in expo but an getting the error 'TypeError: null is not an object (evaluating 'RNFusedLocation.getCurrentPosition')'
below is my code
App.js
import { StyleSheet, Text, View, StatusBar, PermissionsAndroid, Platform,} from 'react-native';
import HomeScreen from './src/screens/HomeScreen/HomeScreen';
import DestinationSearch from './src/screens/DestinationSearch/DestinationSearch';
import SearchResults from './src/screens/SearchResults/SearchResults';
import { useEffect, useState } from 'react';
//import * as Location from 'expo-location';
import Geolocation, { getCurrentPosition } from 'react-native-geolocation-service';
navigator.geolocation = require('react-native-geolocation-service');
export default function App() {
const androidPermission = async () => {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{
title: "Uber App location Permission",
message:
"Uber App needs access to your location " +
"so you can take awesome rides.",
buttonNeutral: "Ask Me Later",
buttonNegative: "Cancel",
buttonPositive: "OK"
}
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
console.log("You can use the location");
} else {
console.log("Location permission denied");
}
} catch (err) {
console.warn(err);
}
};
useEffect(() => {
if (androidPermission) {
Geolocation.getCurrentPosition(
(position) => {
console.log(position);
},
(error) => {
// See error code charts below.
console.log(error.code, error.message);
},
{ enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 }
);
}
}, [])
useEffect(() => {
if(Platform.OS == 'android') {
androidPermission()
} else{
//IOS
Geolocation.requestAuthorization();
}
}, [])
return (
<View>
{/* <HomeScreen /> */}
<DestinationSearch />
{/* <SearchResults /> */}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
marginTop:StatusBar.currentHeight
},
});
`
DestinationSearch.jsx
import { View, Text, StyleSheet, SafeAreaView, StatusBar } from 'react-native'
import React, {useEffect, useState,} from 'react'
import { GooglePlacesAutocomplete } from 'react-native-google-places-autocomplete';
import {GOOGLE_MAPS_APIKEY} from '#env'
import PlaceRow from './PlaceRows';
const DestinationSearch = () => {
const [originPlace,setOriginPlace] = useState(null)
const [destinationPlace, setDestinationPlace] = useState(null)
useEffect(() => {
console.log('useEffect is called')
if (originPlace && destinationPlace) {
console.warn('Redirect to results')
}
}, [originPlace, destinationPlace])
return (
<SafeAreaView>
<View style={styles.container}>
<GooglePlacesAutocomplete
nearbyPlacesApi = 'GooglePlacesSearch'
placeholder = 'From...'
listViewDisplayed = 'auto'
debounce = {400}
currentLocation = {true}
currentLocationLabel='Current location'
minLenght = {2}
enabledPoweredByContainer = {true}
fetchDetails = {true}
autoFoccus = {true}
renderRow={(data)=> <PlaceRow data={data}/>}
query ={{
key: GOOGLE_MAPS_APIKEY ,
language :'en'
}}
styles={{
container: styles.autocompleteContainer,
textInput: styles.textInput,
listView: styles.listView,
seperator: styles.separator
}}
onPress = {(data, details = null)=> {
setOriginPlace({data, details})
console.log(currentLocation)
}}
/>
<GooglePlacesAutocomplete
nearbyPlacesApi = 'GooglePlacesSearch'
placeholder = 'To...'
listViewDisplayed = 'auto'
debounce = {400}
minLenght = {2}
enabledPoweredByContainer = {true}
fetchDetails = {true}
autoFoccus = {true}
query ={{
key: GOOGLE_MAPS_APIKEY ,
language :'en'
}}
renderRow={(data)=> <PlaceRow data={data}/>}
styles={{
container: {
...styles.autocompleteContainer,
top: 70
},
textInput: styles.textInput,
seperator: styles.separator
}}
onPress = {(data, details = null)=> {
setDestinationPlace({data, details})
}}
/>

TypeError: Cannot read property 'getters' of undefined on Vue Jest Test

This is my test file
import { shallowMount, createLocalVue } from '#vue/test-utils';
import Home from '#/views/Home.vue';
import Vuex from 'vuex';
import Vuetify from 'vuetify';
import movies from '#/store/modules/movies';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('Home.vue', () => {
let vuetify;
let store;
let state;
let actions;
beforeEach(() => {
state = {
movieList: [],
movieDetails: {},
};
actions = {
getMovieList: jest.fn(),
getMovieDetails: jest.fn(),
};
store = new Vuex.Store({
modules: {
movies: {
namespaced: true,
state,
actions,
},
},
});
vuetify = new Vuetify();
});
const wrapper = shallowMount(Home, {
store,
vuetify,
localVue,
});
it('should call submitForm() function when button is clicked', () => {
wrapper.find('button').trigger('click');
expect(wrapper.vm.submitForm).toHaveBeenCalled();
});
});
This is my view file
<script>
import Tooltip from '#/components/Tooltip.vue';
import { moviesComputed, moviesMethods } from '#/store/helpers/movies';
export default {
components: {
Tooltip,
},
methods: {
...moviesMethods,
submitForm() {
const validate = this.$refs.form.validate();
if (!validate) return;
this.currentPage = 1;
this.getMoviesList({ s: this.searchQuery });
},
async goToDetails(moveId) {
this.$router.push({ name: 'MovieDetails', params: { id: moveId } });
},
onScroll() {
const isOnBottom = document.documentElement.scrollTop + window.innerHeight
=== document.documentElement.offsetHeight;
if (isOnBottom) {
this.currentPage += 1;
this.nextMoviePage({ s: this.searchQuery, page: this.currentPage });
}
},
},
computed: {
...moviesComputed,
pagedMovieList() {
const listNumber = this.maxList * this.currentPage;
const list = this.movieList.filter((movie, index) => index < listNumber);
return list;
},
},
data() {
return {
searchQuery: '',
queryRules: [(v) => !!v || 'Antes de pesquisar, forneça um nome'],
mouseOverIndex: '',
maxList: 6,
currentPage: 1,
};
},
async mounted() {
document.addEventListener('scroll', this.onScroll);
},
beforeDestroy() {
document.removeEventListener('scroll', this.onScroll);
},
};
</script>
<template>
<div id="home">
<v-row class="justify-center align-center">
<v-col class="home-form" :class="{ 'to-top': movieList.length }" sm="12" md="6" xl="4">
<v-form ref="form" color="secondary" #submit="submitForm">
<v-text-field
label="Digite sua busca"
append-icon="mdi-magnify"
:rules="queryRules"
v-model="searchQuery"
required
/>
</v-form>
<v-btn color="secondary" class="mt-1" #click="submitForm">Enviar</v-btn>
</v-col>
</v-row>
<v-row class="mt-5">
<v-col
cols="12"
sm="6"
md="4"
v-for="(movie, index) in pagedMovieList"
:key="`${movie.Title}-${index}`"
style="position: relative"
>
<v-card elevation="10" #mouseover="mouseOverIndex = index" #mouseout="mouseOverIndex = ''">
<p class="mt-1 text-center" v-if="movie.Poster === 'N/A'">{{ movie.Title }}</p>
<v-img v-if="movie.Poster !== 'N/A'" :src="movie.Poster" />
<v-img v-else src="#/assets/not-found.png" />
<Tooltip #clicked="goToDetails" :active="index === mouseOverIndex" :movie="movie" />
</v-card>
</v-col>
</v-row>
</div>
</template>
This is my store module file
import { GetList, GetDetails } from '#/services/movies';
export const state = {
movieList: [],
movieDetails: {},
};
export const mutations = {
SET_LIST(state, newList) {
state.movieList = newList;
},
SET_MOVIE_DETAILS(state, newValue) {
state.movieDetails = newValue;
},
ADD_TO_LIST(state, list) {
state.movieList.push(...list);
},
};
export const actions = {
async getMoviesList({ commit }, query) {
const resp = await GetList(query);
if (resp.status === 200) commit('SET_LIST', resp.data.Search);
},
async getMovieDetails({ commit }, movieId) {
const resp = await GetDetails({ i: movieId });
if (!resp.status === 200) return;
commit('SET_MOVIE_DETAILS', resp.data);
},
async nextMoviePage({ commit }, query) {
const resp = await GetList(query);
if (resp.status === 200 && resp.data.Search) commit('ADD_TO_LIST', resp.data.Search);
},
};
const movies = {
namespaced: true,
mutations,
state,
actions,
};
export default movies;
This is my base.js module file
export const state = {
isLoading: false,
};
export const mutations = {
SET_LOADING(state, newValue) {
state.isLoading = newValue;
},
};
export const actions = {
setLoading({ commit }, value) {
commit('SET_LOADING', value);
},
};
const base = {
namespaced: true,
mutations,
state,
actions,
};
export default base;
And this is my store.js
import Vue from 'vue';
import Vuex from 'vuex';
// Modules
import Base from './modules/base';
import Movies from './modules/movies';
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
base: Base,
movies: Movies,
},
strict: process.env.NODE_ENV !== 'production',
});
This is the error
TypeError: Cannot read property 'getters' of undefined
8 | Vue.use(Vuex);
9 |
> 10 | export default new Vuex.Store({
| ^
11 | modules: {
12 | base: Base,
13 | movies: Movies,
I have no idea why I'm getting an error on new Vuex.Store on tests. This application runs normally, without errors on Vuex. I cant even post what I already did to solve this because I'm completely lost

Unit Test using Jest. TypeError: Cannot read property 'getters' of undefined

I have a component that I want to test that uses Vuex.
components/Main/Header.vue
<template>
<v-container fluid grid-list-xl>
<v-card flat class="header" color="transparent">
<v-layout align-center justify-start row fill-height class="content">
<v-flex xs5>
<v-img :src="avatar" class="avatar" aspect-ratio="1" contain></v-img>
</v-flex>
<v-flex xs7>
<div class="main-text font-weight-black">
WELCOME
</div>
<div class="sub-text">
{{currentLocation.description}}
</div>
<div #click="showStory" class="show-more font-weight-bold">
Explore More
</div>
</v-flex>
</v-layout>
</v-card>
</v-container>
</template>
<script>
import avatar from '../../assets/images/home/avatar.png';
export default {
name: 'Header',
computed: {
currentLocation() {
return this.$store.getters.getCurrentLocation;
},
avatar() {
return avatar;
}
},
methods: {
showStory() {
this.$router.push( { name: 'Stories', params: { name: 'Our Story' } });
}
}
}
</script>
and from /test/unit/components/Main/Header.spec.js
import Vuex from 'vuex'
import { shallowMount, createLocalVue } from "#vue/test-utils"
import Header from "#/components/Main/Header.vue"
const localVue = createLocalVue()
localVue.use(Vuex)
const store = new Vuex.Store({
state: {
currentLocation: {
name: 'this is a name',
description: "lorem ipsum",
latitude: '1.123123',
longitude: '103.123123',
radius: '5000'
}
},
getters: {
getCurrentLocation: (state) => state.currentLocation
}
})
describe('Header', () => {
const wrapper = shallowMount(Header, {
store,
localVue
})
it('should render a computed property currentLocation', () => {
expect(Header.computed.currentLocation()).toBe(store.getters.getCurrentLocation)
});
});
The error I'm getting is from the computed property currentLocation
TypeError: Cannot read property 'getters' of undefined

Vue.js + Avoriaz : how to test a watcher?

I am trying to test the following component w Avoriaz, but upon props change , the action in watch: {} is not triggered
ItemComponent.vue
switch checkbox
✗ calls store action updateList when item checkbox is switched
AssertionError: expected false to equal true
at Context.<anonymous> (webpack:///test/unit/specs/components/ItemComponent.spec.js:35:47 <- index.js:25510:48)
thanks for feedback
ItemComponent.vue
<template>
<li :class="{ 'removed': item.checked }">
<div class="checkbox">
<label>
<input type="checkbox" v-model="item.checked"> {{ item.text }}
</label>
</div>
</li>
</template>
<script>
import { mapActions } from 'vuex'
export default {
props: ['item', 'id'],
methods: mapActions(['updateList']),
watch: {
'item.checked': function () {
this.updateList(this.id)
}
}
}
</script>
here is my component test
ItemComponent.spec.js
import Vue from 'vue'
import ItemComponent from '#/components/ItemComponent'
import Vuex from 'vuex'
import sinon from 'sinon'
import { mount } from 'avoriaz'
Vue.use(Vuex)
describe('ItemComponent.vue', () => {
let actions
let store
beforeEach(() => {
actions = {
updateList: sinon.stub()
}
store = new Vuex.Store({
state: {},
actions
})
})
describe('switch checkbox', () => {
it('calls store action updateList when item checkbox is switched', () => {
const id = '3'
const item = { text: 'Bananas', checked: true }
const wrapper = mount(ItemComponent, { propsData: { item, id }, store })
// switch item checked to false
wrapper.setProps({ item: { text: 'Bananas', checked: false } })
expect(wrapper.vm.$props.item.checked).to.equal(false)
expect(actions.updateList.calledOnce).to.equal(true)
})
})
})
U mistaked the prop,use :checked instead
I should write my expect(actions.updateList() . within a $nextTick block
describe('switch checkbox', () => {
it('calls store action updateList when item checkbox is switched', (done) => {
const id = '3'
const item = { text: 'Bananas', checked: true }
const wrapper = mount(ItemComponent, { propsData: { item, id }, store })
// switch item.checked to false
wrapper.setProps({ item: { text: 'Bananas', checked: false } })
expect(wrapper.vm.$props.item.checked).to.equal(false)
wrapper.find('input')[0].trigger('input')
wrapper.vm.$nextTick(() => {
expect(actions.updateList.calledOnce).to.equal(true)
done()
})
})
})
then my test is OK
ItemComponent.vue
switch checkbox
✓ calls store action updateList when item checkbox is switched