I can't get my click-event test to work.
I'm using a Vuetify component: v-btn, but my click-event doesn't seem to dispatch. I tried using a normal button tag, but that had the same outcome. I'm new at testing, this is actually my first run, so tips and pointer are welcome.
This is my component being tested:
<template>
<div>
<div class="marked">
<h2>Clicks: {{ counter }}</h2>
</div>
<v-btn #click="addOne" :style="buttonStyle">Counter</v-btn>
</div>
</template>
<script>
export default {
name: "UnitTesting",
data() {
return {
counter: 0
};
},
computed: {
count() {
return this.counter;
},
buttonStyle() {
return {
border: "solid 2px blue",
background: "black",
padding: "5px 12px",
color: "white",
borderRadius: "8px"
};
}
},
methods: {
addOne() {
this.counter++;
}
}
};
</script>
I have a simple test here to see if the component does mount, checks the content of the title, and tries to dispatch an event for the button, failing:
// Library
import Vue from "vue"
import Vuetify from "vuetify";
// Utils
import UnitTesting from "../UnitTesting.vue";
import { mount, createLocalVue } from '#vue/test-utils'
const localVue = createLocalVue()
Vue.use(Vuetify);
describe("UnitTesting.vue", () => {
let vuetify;
beforeEach(() => {
vuetify = new Vuetify()
})
it("Testing UnitTesting Component", () => {
const wrapper = mount(UnitTesting, {
localVue,
vuetify,
});
expect(wrapper).toBeTruthy()
});
it("Testing button", () => {
const wrapper = mount(UnitTesting, {
localVue, vuetify
});
const event = jest.fn();
const button = wrapper.find(".v-btn");
expect(button.text()).toContain("Counter")
const title = wrapper.find(".marked");
expect(title.text()).toContain("Clicks: 0");
button.vm.$on("action-btn:clicked", event);
expect(event).toHaveBeenCalledTimes(0);
button.trigger("click");
expect(event).toHaveBeenCalledTimes(1);
})
})
As you can see, my test breaks when it expects the click-event to have been dispatched:
FAIL src/views/__tests__/UnitTesting.spec.js
● UnitTesting.vue › Testing button
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
37 | expect(event).toHaveBeenCalledTimes(0);
38 | button.trigger("click");
> 39 | expect(event).toHaveBeenCalledTimes(1);
| ^
40 | })
41 | })
at Object.<anonymous> (src/views/__tests__/UnitTesting.spec.js:39:19)
Test Suites: 1 failed, 1 passed, 2 total
Tests: 1 failed, 2 passed, 3 total
Snapshots: 0 total
Time: 2.249 s
Did I do something wrong?
The problem seems to be the event name you're listening to. There is no action-btn:clicked event from v-btn. However, there is a click event. Changing your event name resolves the issue:
//button.vm.$on("action-btn:clicked", event); // DON'T DO THIS
button.vm.$on("click", event);
Try mocking like addOne instead of event
You need to mock the actual event instead you are creating new one which component is not aware of
wrapper.vm.addOne = jest.fn();
expect(wrapper.vm.addOne).toHaveBeenCalledTimes(1);
Related
I have a VueJS where I have created a component for rendering the contents from a WYSIWYG component (tiptap).
I have the following content being returned from the backend
let x = 0;
enum A {}
function Baa() {}
I'm using highlight.js to highlight this code snippet in the following manner:
import { defineComponent, h, nextTick, onMounted, onUpdated, ref, watch } from 'vue';
// No need to use a third-party component to highlight code
// since the `#tiptap/extension-code-block-lowlight` library has highlight as a dependency
import highlight from 'highlight.js'
export const WYSIWYG = defineComponent({
name: 'WYSIWYG',
props: {
content: { type: String, required: true },
},
setup(props) {
const root = ref<HTMLElement>(null);
const highlightClass = 'hljs';
const hightlightCodes = async () => {
console.log(root.value?.querySelectorAll('pre code')[0]);
setTimeout(() => {
root.value?.querySelectorAll('pre code').forEach((el: HTMLElement) => {
highlight.highlightElement(el as HTMLElement);
});
}, 2000);
}
onMounted(hightlightCodes);
watch(() => props.content, hightlightCodes);
return function render() {
return h('div', {
class: 'WYSIWYG',
ref: root,
innerHTML: props.content
});
};
},
});
Now, when I visit the page by typing the URL in the browser, it highlights the typescript code
Whenever I visit a different page and click on my browser's "Go back" button, it makes the code completely vanishes
What I have tried
I can see that the line root.value?.querySelectorAll('pre code') is returning the correct items and the correct code is present but the code vanishes after the 2 seconds passes - due to setTimeout.
How can I make highlight.js highlight the code parts whenever props.content changes?
Option 1
Use Highlight.js Vue integration (you need to setup the plugin first, check the link):
<script setup>
const props = defineProps({
content: { type: String, required: true },
})
</script>
<template>
<highlightjs :code="content" language="ts" />
</template>
Option 2
Use computed to reactively compute highlighted HTML of props.content
Use sync highlight(code, options) function to get the highlighted HTML
Use HTML as-is via innerHTML prop or v-html directive
<script setup>
import { computed } from 'vue'
import highlight from 'highlight.js'
const props = defineProps({
content: { type: String, required: true },
})
const html = computed(() => {
const { value } = highlight.highlight(props.content, { lang: 'ts' })
return value
})
</script>
<template>
<div v-html="html" />
</template>
I'm really confused when using Vue services to test. My test issue is related to vue, vuetify, vuex, vuei18n.
I will summary my set-up test here (I also noted the failed step in it) :
import Vue from "vue";
import Vuetify from "vuetify";
import VueI18n from "vue-i18n";
import store from "#/store/index";
import Login from "#/views/Login.vue";
...
beforeEach(() => {
Vue.use(Vuetify);
Vue.use(VueI18n);
i18n = new VueI18n({
messages: { fr, en, vi }
});
Constructor = Vue.extend(Login);
});
describe("Login.vue", () => {
...
test("should translate helper message when failing in login", () => {
i18n.locale = "en";
vm = new Constructor({ store, i18n }).$mount();
vm.$el.querySelector("input[type='email']").value = "incorrect user";
vm.$el.querySelector("input[type='password']").value = "incorrect password";
vm.$el.querySelector("button[type='submit']").click();
const message = vm.$el.querySelector(".v-snack__content").textContent;
console.log(111, message); <-- I can't get any string here
expect(message).toEqual("Login error, please retry");
});
}
The button I click run this function
** Login.vue
<template>
...
<v-btn type="submit" :disabled="logMeIn || !valid" :loading="logMeIn" #click="login">{{$t("Login")}}</v-btn>
...
</template>
<script>
methods: {
login: async function() {
...
} catch (e) {
this.$store.dispatch("ui/displaySnackbar", {
message: this.$i18n.t("Login error, please retry")
});
...
}
}
</script>
We use Vuex to render this component:
** Snackbar.vue
<template>
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="snackbar.timeout">
{{ snackbar.message }}
<v-btn dark flat #click="snackbar.show = false">{{ $t("Close") }}</v-btn>
</v-snackbar>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "Snackbar",
computed: {
...mapState("ui", ["snackbar"])
}
};
</script>
My question is: Why vm.$el.querySelector can't access element in
Snackbar.vue. I need it to test some texts inside
It has just not done async login my guess is here. Test if login has been called and test the login method in its own unit - currently its a weird approach to test the login method.
E.g.:
describe("clicked submit", function clickedSubmit(){
it("should call vm.login", function(){
...
expect(vm.login).toBeCalledTimes(1)
expect(vm.login).toBeCalledWith("args")
})
})
describe("vm.login", function(){
describe("called with invalid userdata & invalid pass", function(){
it("should invalidate", async function(){
await vm.login(...args)
await vm.$nextTick()
...
expect(vm.message).toBe("target value")
//expect(vm.$el...)
})
})
})
I have created a project in vuetify and now trying to unit test it using jest.
I have button that I have created using v-btn, which toggles the value of a boolean variable.
To check whether the button has been clicked, I tried to get hold of the button using the class name I gave to v-btn, but its not working
I have tried using shallow mounting and plain mounting. I tried to capture the tag (v-btn) itself, but it didn't work
some.vue
<template>
<div>
<v-btn class="profile-button" #click="isProfile = !isProfile">Profile</v-btn>
<v-btn icon color="#fff" class="expand" small fab absolute right #click="mini = !mini">
<v-icon color="#fcbc00">chevron_right</v-icon>
</v-btn>
</div>
</template>
<script>
export default {
data() {
return {
isProfile: true,
mini: true
}
}
}
</script>
some.spec.js
import { shallowMount } from '#vue/test-utils';
import Profile from '#/components/Profile.vue';
import Vue from 'vue'
import Vuetify from 'vuetify'
describe('Profile.vue', () => {
Vue.use(Vuetify)
it('Profile is a vue instance', () => {
const wrapper = shallowMount(Profile);
expect(wrapper.isVueInstance()).toBeTruthy()
});
it('Profile Button exists', () => {
const wrapper = shallowMount(Profile);
expect(wrapper.contains('.profile-button')).toBe(true)
});
it('Profile Button is clicked', () => {
const wrapper = shallowMount(Profile);
expect(wrapper.contains('.profile-button')).trigger('click')
});
it('Has an expand button', () => {
const wrapper = shallowMount(Profile)
wrapper.find('expand').trigger('click')
})
});
All the tests should be passed. But I receive the following error:
expect(received).toBe(expected) // Object.is equality
Expected: true
Received: false
19 |
20 | it('Profile Button exists', () => {
> 21 | expect(wrapper.contains('.profile-button')).toBe(true)
| ^
22 | })
[vue-test-utils]: find did not return expand, cannot call trigger()
on empty Wrapper
16 | it('Has an expand button', () => {
17 | const wrapper = shallowMount(SideDrawer)
> 18 | wrapper.find('expand').trigger('click')
| ^
19 | })
20 | });
What should I do? I have many such buttons that I need to test and I am stuck, as I tried everything.
Got the solution!
I was asserting the value of wrapper.find('.profile-button') with true.
It should be changed to:
(wrapper.find('.profile-button').exists()).toBe(true)
(wrapper.find('.profile-button').exists()) returns a boolean value, which is what we want to assert.
I have a vuejs modal component that shows the modal of team members (avatar, full_name & description). I need to test it.
teamMembers.js looks like :
<template lang="pug">
.col-lg-4.col-md-6.team_member_wrapper
a(href="javascript:;" data-toggle="modal" data-target="#TeamMemberModal" #click="$emit('clicked', item)" )
.team_member
.team_member_image
img(:src="item.avatar")
.team_member_name {{item.full_name}}
.team_member_status {{item.status}}
.team_member_shadow
</template>git status
<script>
export default {
name: "TeamMember",
props: {
item: {},
}
};
</script>
my test code is :
import Vue from 'vue'
import TeamMember from '#/components/TeamMember.vue'
import { mount } from '#vue/test-utils'
const wrapper = mount(TeamMember, {
context: {
props : {
item : {
avatar : 'path/to_image.png',
full_name: "Robocop"
}
}
}
});
I need to validate that the template generated the correct html
- while the Wrapper must contain a Vue instance. I did this :
wrapper.setProps({ avatar: 'path/to_image.png' }),
expect(wrapper.vm.avatar).toBe('path/to_image.png'),
wrapper.setProps({ avatar: 'Robocop' }),
expect(wrapper.vm.full_name).toBe('Robocop')
I run my test, got the following result :
FAIL tests/unit/specs/TeamMember.spec.js
● Test suite failed to run
[vue-test-utils]: mount.context can only be used when mounting a functional component
9 | item : {
10 | avatar : 'path/to_image.png',
> 11 | full_name: "Robocop"
| ^
12 | }
13 | }
14 | }
at throwError (node_modules/#vue/test-utils/dist/vue-test-utils.js:11:9)
at createInstance (node_modules/#vue/test-utils/dist/vue-test-utils.js:2847:5)
at mount (node_modules/#vue/test-utils/dist/vue-test-utils.js:5639:18)
at Object.<anonymous> (tests/unit/specs/TeamMember.spec.js:11:36)
what mistake in my code, & how can I correct it ?
Thanks
The options you are passing should be on the root level of the object, and props data is set with the propsData option:
const wrapper = mount(TeamMember, {
propsData: {
item: {
avatar: 'path/to_image.png',
full_name: 'Robocop'
}
}
})
I am trying to integrate Phaser 3 with Vue.js 2.
My goal is to create a Vue.js component associated with a game canvas.
My initial solution was:
<template>
<div :id="id">
</div>
</template>
<script>
import Phaser from 'phaser'
export default {
data () {
return {
id: null,
game: null
}
},
mounted () {
this.id = 'game' + this._uid
var config = {
parent: this.id,
type: Phaser.CANVAS
}
this.game = new Phaser.Game(config)
....
}
}
</script>
This code attach the game canvas to my template. But to my surprise it only worked 'sometimes'.
I figured out, after hours of debugging, that my div element in the DOM wasn't updated with the id when I was instantiating my new Game.
So I came up with the solution of instantiating the id in the beforeMount () method as follow:
<template>
<div :id="id">
</div>
</template>
<script>
import Phaser from 'phaser'
export default {
data () {
return {
id: null,
game: null
}
},
beforeMount () {
this.id = 'game' + this._uid
},
mounted () {
var config = {
parent: this.id,
type: Phaser.CANVAS
}
this.game = new Phaser.Game(config)
....
}
}
</script>
It is working, but I would like to know if there is a more simple and elegant solution ?
One better solution for integrating Phaser.Game into the application is directly passing the config the HTML element, a configuration supported by Phaser.Game.
To get a reference to a HTML element in vue, you can use refs, these are basically id's, but local to the component itself, so there is not risk in creating conflicts.
<template>
<div ref="myDiv">
</div>
</template>
<script>
import Phaser from 'phaser'
export default {
data () {
return {
game: null
}
},
mounted () {
var config = {
parent: this.$refs.myDiv,
type: Phaser.CANVAS
}
this.game = new Phaser.Game(config)
....
}
}
</script>
Vue3 sample:
<script setup>
import { ref,onMounted } from 'vue';
import Phaser from 'phaser'
const myDiv = ref(null)
let canvasWidth = 750;
let canvasHeight = 1450;
onMounted(() => {
const config = {
type: Phaser.AUTO,
parent: popWrap.value,
width: canvasWidth,
height: canvasHeight,
scene: {
preload: preload,
create: create,
update: update
}
};
const game = new Phaser.Game(config);
})
</script>
<template>
<div ref="myDiv">
</div>
</template>