I have a component called External.vue which contains a button that has props and also has a Google Analytic event fired every time it is clicked.
<template>
<div>
<button
ref="ctaButtonExternalElement"
type="submit"
#click="fireEvent"
>
<a
ref="ctaButtonExternalLinkElement"
:class="ctaColour"
class="block rounded-md border border-transparent px-4 py-3 font-medium shadow focus:outline-none focus:ring-2 focus:ring-offset-2 sm:px-6"
:href="ctaLink"
>
{{ ctaTitle }}
</a>
</button>
</div>
</template>
<script>
export default {
props: {
ctaTitle: {
required: true,
type: String,
},
ctaLink: {
required: true,
type: String,
},
ctaColour: {
required: false,
type: String,
default: 'bg-blue-500 hover:bg-blue-700 focus:ring-blue-500',
},
event: {
required: false,
type: Object,
default: function() {
return {};
},
},
},
methods: {
fireEvent() {
if (this.event != null) {
return this.$ga.event({
eventCategory: this.event.event_category,
eventAction: this.event.event_action,
eventLabel: this.event.event_label,
eventValue: this.event.event_value,
});
}
},
},
};
</script>
As you can see this.$ga is injected here by nuxt automatically and in our test we are wanting to load the component so we want to inject the $ga or rather have a mocked version of it.
import {mount} from '#vue/test-utils';
import External from './External';
import ga from '#nuxtjs/google-analytics';
import {jest} from '#jest/globals';
describe('Test External Button', () => {
test('should mock google analytics', async () => {
const $ga = {ga: {
event: jest.fn(),
}};
const wrapper = mount(External, {
propsData: props,
mocks: {
$ga,
},
});
const button = wrapper.getComponent({ref: 'ctaButtonExternalElement'});
button.trigger('click');
expect($ga).toHaveBeenCalled();
});
});
when I run this test I get this error:
Test External Button › should mock google analytics
expect(received).toHaveBeenCalled()
Matcher error: received value must be a mock or spy function
Received has type: object
Received has value: {"ga": {"event": [Function mockConstructor]}}
Error in v-on handler: "TypeError: this.$ga.event is not a function"
Is there anyway of mocking $ga?
Your mock looks incorrect. $ga has no ga property, so that should be removed from the mock.
//const $ga = {ga: {
// event: jest.fn(),
//}};
const $ga = {
event: jest.fn(),
};
And you'd verify $ga.event is called, not $ga. Also the click-event trigger should be awaited, as the click-handler isn't called until the next tick:
//button.trigger('click');
//expect($ga).toHaveBeenCalled();
await button.trigger('click');
expect($ga.event).toHaveBeenCalled();
Related
I am trying to create a test for a BaseInput with a v-model prop. The expectation is the component will emit the changed input. When I update the input in the Vitest framework, there does not seem to be an emit triggered.
Component
<template>
<label v-if="label">{{ label }}</label>
<input
v-bind="$attrs"
:value="modelValue"
:placeholder="label"
#input="$emit('update:modelValue', $event.target.value)"
class="field"
/>
</template>
<script>
export default {
props: {
label: {
type: String,
default: "",
},
modelValue: {
type: [String, Number],
default: "",
},
},
};
</script>
Test
import { describe, it, expect, beforeEach } from "vitest";
import { mount } from "#vue/test-utils";
import BaseInput from "#/components/base/BaseInput.vue";
describe("BaseInput", () => {
let wrapper
beforeEach(() => {
wrapper = mount(BaseInput, {
propsData: {
label: 'Test Label',
modelValue: 'Test Value'
}
})
})
it('emits input value when changed', async () => {
// Assert input value gets the new value
await wrapper.find('input').setValue('New Test Value')
expect(wrapper.find('input').element.value).toBe('New Test Value')
// Assert input event is emitted
await wrapper.vm.$nextTick()
expect(wrapper.emitted().input).toBeTruthy() //this fails
})
});
Result: there is nothing emitted from the input when the value is set.
How can the input be set to prove the component emits the new value of the input component?
This is actually discussed as an example in the Vue Test Utils examples: https://test-utils.vuejs.org/guide/advanced/v-model.html#a-simple-example
Here is how you test v-model in Vue 3
import { describe, it, expect, beforeEach } from "vitest";
import { mount } from "#vue/test-utils";
import BaseInput from "#/components/base/BaseInput.vue";
describe("BaseInput", () => {
let wrapper;
beforeEach(() => {
wrapper = mount(BaseInput, {
propsData: {
label: "Test Label",
modelValue: "Test Value",
"onUpdate:modelValue": (e) => wrapper.setProps({ modelValue: e }),
},
});
});
it("emits input value when changed", async () => {
// Assert input value gets the new value
await wrapper.find("input").setValue("New Test Value");
expect(wrapper.props("modelValue")).toBe("New Test Value");
});
});
I'm new to testing vue components and right now I'm trying to test that clicking a b-button component trigger an event and calls a method.
The test seems to fail because the 'click' event doesn't get triggered.
Also note that i'm using Nuxt.js
The component containing b-button is the following:
<template>
<div>
<b-form-input name="username" type="text" v-model="username"></b-form-input>
<b-form-input name="password" type="password" v-model="password"></b-form-input>
<b-button #click="login">Login</b-button>
</div>
</template>
<script>
import { mapActions } from "vuex";
export default {
data() {
return {
username: "",
password: ""
};
},
methods: {
...mapActions({
dispatchLogin: "auth/login"
}),
login: () => {
const response = this.dispatchLogin(this.username, this.password);
response.then(this.$router.push("/dashboard"));
}
}
};
</script>
And the test is written like that:
test('call login when Login button gets clicked', async () => {
expect.assertions(2);
moxios.stubRequest('/api/auth/login', {
response: 200
});
const store = new Vuex.Store(storeConfig);
const mocks = {
login: jest.fn()
}
const wrapper = shallowMount(login, { localVue, store, mocks });
debugger;
// Note that b-button is stubbed as <button></button>
wrapper.find('button').trigger('click');
await flushPromises()
expect(wrapper.emitted('click')).toHaveLength(1);
expect(auth.actions.login).toHaveBeenCalledTimes(1);
});
The problem is that I can't find any click event in wrapper.emitted().
I have a dropdown list called login. The list is shown when I load the page, but I can’t select an option from the list. Why is that?
I have searched the internet but didn’t find a solution.
This is the code from the component:
<template>
<div>
<label for="login" class="control-label">Logins anlegen für</label>
<select2 class="select2_tag form-control" name="login"
multiple="multiple" :options="login">
</select2>
</div>
</template>
<script>
import JQuery from 'jquery'
let $ = JQuery
import Select2 from '#/components/Select2.vue'
export default {
name: 'Login',
data: function () {
return {
login: [],
loginUebertrag: ''
}
},
components: {
Select2
},
methods: {
lade_login: function () {
let vm = this;
$.getJSON("/api/get_login.php", function (result) {
vm.login = result;
console.log("zeile 41: ", vm.login);
});
},
},
mounted()
{
this.lade_login();
}
}
</script>
And this is the code from the imported component (called select2):
<template>
<select class="form-control" >
<slot></slot>
</select>
</template>
<script>
import JQuery from 'jquery'
let $ = JQuery
export default {
name: 'select2',
props: ['options', 'value'],
mounted: function () {
var vm = this
$(this.$el)
// init select2
.select2({
data: this.options,
tags:true
})
//.val(this.value)
//.trigger('change')
// emit event on change.
.on('change', function () {
vm.$emit('input', this.value)
});
console.log( $(this.$el))
},
watch: {
value: function (value) {
// update value
//console.log(value)
$(this.$el)
.val(value)
.trigger('change')
},
options: function (options) {
// update options
$(this.$el).empty().select2({data: options,tags: true})
}
},
destroyed: function () {
$(this.$el).off().select2('destroy')
}
}
</script>
Any help is appreciated!
I’m having a hard time figuring out how to test single file Vue components that need to react to child-sourced events.
I have a Gallery component, which has any number of Card components as children, and must keep track of the Cards that are selected. When a card is clicked, it emits a custom event (card-click) that the Gallery listens for and reacts to by calling a select() function. Select() has some logic based on whether or not the Shift or Meta keys are pressed. I’ve created simplified versions of these components below:
Gallery Component
<template>
<div class="gallery">
<card v-for="(item, index) in items"
:id="item.id"
:key="item.id"
class="galleryCard"
#card-click="select(item.id, $event)">
<h2>{{ item.title }}</h2>
</card>
</div>
</template>
<script>
export default {
name: "Gallery",
props: {
items: {
required: true,
type: Array,
},
},
methods: {
select: function(id, event) {
if(event.metaKey) {
console.log('meta + click')
} else {
if (event.shiftKey) {
console.log('shift + click')
} else {
console.log('click')
}
}
},
},
}
</script>
Card Component
<template>
<article :id="id" #click="handleSelect($event)" class="card">
<slot/>
</article>
</template>
<script>
export default {
name: "Card",
props: {
id: {
type: String,
default: "",
},
},
methods: {
handleSelect: function(event) {
this.$emit("card-click", event)
},
},
}
</script>
Using Jest, I’m trying to test the Gallery component’s select() function, which I’m mocking (selectStub), to make sure the correct Cards are selected when clicked. When I trigger clicks on two of the cards, I expect to see something in the selectStub.mock.calls array, but there is nothing. I have also tried to emit the event How can I capture events that children emit in my test?
Gallery Test
import { createLocalVue, mount } from "#vue/test-utils"
import Gallery from "#/Gallery.vue"
import Card from "#/Card.vue"
const localVue = createLocalVue()
let wrapper
let items = [
{ id: "1", title: "First" },
{ id: "2", title: "Second" },
{ id: "3", title: "Third" },
]
describe("Gallery.vue", () => {
beforeEach(() => {
wrapper = mount(Gallery, {
localVue,
propsData: {
items: items,
},
stubs: ["card"],
})
})
const selectStub = jest.fn();
it("selects the correct items", () => {
wrapper.setMethods({ select: selectStub })
const card1 = wrapper.findAll(".galleryCard").at(0)
const card2 = wrapper.findAll(".galleryCard").at(1)
card1.trigger('click')
card2.trigger('click', {
shiftKey: true
})
console.log(selectStub.mock)
})
})
All properties (calls, instances, and timestamps) of selectStub.mock are empty. I have also tried emitting the card-click event via vue-test-utils wrapper.vm.$emit() function but it doesn't trigger the select.
How can I test the Gallery select() method to make sure it's responding correctly to a child-sourced event?
I'm trying to write Mocha/Chai tests for a Vue component which utilizes Chart.js. All tests pass, but I then get this error:
C:\repos\security-center-frontend-2\security-center-frontend\node_modules\mocha\lib\runner.js:726
err.uncaught = true;
^
TypeError: Cannot create property 'uncaught' on string
'window.matchMedia not found! Make sure you're using a polyfill.'
I think that this has to do with the way Vue test utils represent the window object, and that I should somehow stub it, but am not sure of the proper syntax.
My component:
<template>
<card>
<template slot="header">
<h4 v-if="$slots.title || title" class="card-title">
<slot name="title">
{{title}}
</slot>
</h4>
<p class="card-category">
<slot name="subTitle">
{{subTitle}}
</slot>
</p>
</template>
<div>
<div :id="chartId" class="ct-chart"></div>
<div class="footer">
<div class="chart-legend">
<slot name="legend"></slot>
</div>
<hr>
<div class="stats">
<slot name="footer"></slot>
</div>
<div class="pull-right">
</div>
</div>
</div>
</card>
</template>
<script>
import Card from './Card.vue';
export default {
name: 'chart-card',
components: {
Card
},
props: {
footerText: {
type: String,
default: ''
},
title: {
type: String,
default: ''
},
subTitle: {
type: String,
default: ''
},
chartType: {
type: String,
default: 'Line' // Line | Pie | Bar
},
chartOptions: {
type: Object,
default: () => {
return {};
}
},
chartData: {
type: Object,
default: () => {
return {
labels: [],
series: []
};
}
}
},
data () {
return {
chartId: 'no-id'
};
},
methods: {
/***
* Initializes the chart by merging the chart options sent via props and the default chart options
*/
initChart (Chartist) {
const chartIdQuery = `#${this.chartId}`;
Chartist[this.chartType](
chartIdQuery,
this.chartData,
this.chartOptions
);
},
/***
* Assigns a random id to the chart
*/
updateChartId () {
const currentTime = new Date().getTime().toString();
const randomInt = this.getRandomInt(0, currentTime);
this.chartId = `div_${randomInt}`;
},
getRandomInt (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
},
mounted () {
this.updateChartId();
import('chartist').then((Chartist) => {
let ChartistLib = Chartist.default || Chartist;
this.$nextTick(() => {
this.initChart(ChartistLib);
});
});
}
};
</script>
<style>
</style>
My tests:
import { expect } from 'chai';
import { mount } from '#vue/test-utils';
import ChartCard from '#/components/Cards/ChartCard.vue';
describe('ChartCard.vue', () => {
it('Has a title', () => {
const wrapper = mount(ChartCard);
wrapper.setProps({title: 'test title'});
expect(wrapper.contains('.card-title')).to.be.true;
});
it('Displays passed title', () => {
const wrapper = mount(ChartCard);
wrapper.setProps({title: 'test title'});
expect(wrapper.text()).to.include('test title');
});
});