Cannot find data-testid attribute in Vue Component with Jest - vue.js

I am trying to build a test that will target an element with the data-testid attribute. I have a BaseTile component that looks like this:
<template>
<div
data-testid="base-tile-icon"
v-if="!!this.$slots.icon"
>
<slot name="icon"></slot>
</div>
</template>
<script>
export default {};
</script>
<style></style>
And my test looks like this:
import { mount } from '#vue/test-utils';
import BaseTile from '#/components/BaseTile';
const factory = (slot = 'default') => {
return mount(BaseTile, {
slots: {
[slot]: '<div class="test-msg"></div>'
}
});
};
it('has an icon slot if an icon is provided', () => {
let wrapper = factory({ slot: 'icon' });
const input = wrapper.find('[data-testid="base-tile-icon"]');
expect(input.findAll('.test-msg').length).toBe(1);
});
How do I appropriately target the data-testid attribute with this test?

The named parameter of the factory is implemented incorrectly. The correct method is described in this post: Is there a way to provide named parameters in a function call in JavaScript?
The correct way to implement this is as follows:
import { mount } from '#vue/test-utils';
import BaseTile from '#/components/BaseTile';
const factory = ({ slot = 'default' } = {}) => {
return mount(BaseTile, {
slots: {
[slot]: '<div class="test-msg"></div>'
}
});
};
it('has an icon slot if an icon is provided', () => {
let wrapper = factory({ slot: 'icon' });
const input = wrapper.find('[data-testid="base-tile-icon"]');
expect(input.findAll('.test-msg').length).toBe(1);
});

Related

Testing Vuetify with Vue-test-utils - Cannot set value to v-text-field

I want to test simple login form with two v-text-fields and one v-btn. I'm testing with Jest and Vue test utils.
Code looks like that:
<v-text-field
v-model="form.email"
test-id="input-email">
</v-text-field>
My test looks like that:
import Login from './login.vue'
import { shallowMount } from '#vue/test-utils'
describe('login', () => {
it('set value', () => {
const wrapper = shallowMount(Login)
const emailInput = wrapper.findComponent('[test-id="input-email"]')
emailInput.setValue('john#example.com')
})
})
And I'm getting error:
[vue-test-utils]: wrapper.setValue() cannot be called on this element
and points here:
emailInput.setValue('john#example.com')
How can I set value to v-text-field?
I tried to repeat your example and it works
Component:
<template>
<v-text-field
v-model="form.email"
test-id="input-email"
/>
</template>
<script>
export default {
data () {
return {
form: { email: '' },
}
},
}
</script>
Test:
import Input from './input.vue'
import { mount } from '#vue/test-utils'
describe('input', () => {
it('should set v-model', () => {
const wrapper = mount(Input)
const input = wrapper.findComponent('[test-id="input-email"]')
input.setValue('test')
})
})

vue-test-utils: Unable to detect method call when using #click attribute on child component

Vue-test-utils is unable to detect method call when using #click attribute on child component, but is able to detect it when using the #click attribute on a native HTML-element, e.g. a button. Let me demonstrate:
This works:
// Test.vue
<template>
<form #submit.prevent>
<button name="button" type="button" #click="click">Test</button>
</form>
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
name: 'Test',
setup() {
const click = () => {
console.log('Click')
}
return { click }
}
})
</script>
// Test.spec.js
import { mount } from '#vue/test-utils'
import Test from './src/components/Test.vue'
describe('Test.vue', () => {
const wrapper = mount(Test)
if ('detects that method was called', () => {
const spy = spyOn(wrapper.vm, 'click')
wrapper.find('button').trigger('click')
expect(wrapper.vm.click).toHaveBeenCalled() // SUCCESS. Called once
})
})
This does NOT work:
// Test.vue
<template>
<form #submit.prevent>
<ButtonItem #click="click" />
</form>
</template>
<script>
import { defineComponent } from 'vue'
import ButtonItem from './src/components/ButtonItem.vue'
export default defineComponent({
name: 'Test',
components: { ButtonItem },
setup() {
const click = () => {
console.log('Click')
}
return { click }
}
})
</script>
// ButtonItem.vue
<template>
<button type="button">Click</button>
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
name: 'ButtonItem',
})
</script>
// Test.spec.js
import { mount } from '#vue/test-utils'
import Test from './src/components/Test.vue'
import ButtonItem from './src/components/ButtonItem.vue'
describe('Test.vue', () => {
const wrapper = mount(Test)
if ('detects that method was called', () => {
const spy = spyOn(wrapper.vm, 'click')
wrapper.findComponent(ButtonItem).trigger('click')
expect(wrapper.vm.click).toHaveBeenCalled() // FAIL. Called 0 times
})
})
This issue stumbles me. I am not sure what I do wrong. I would be grateful if someone could describe the fault and show me the solution. Thanks!
I haven't run your code to test it yet, but why won't you use a more straightforward solution?
Look out for when the component emits click.
describe('Test.vue', () => {
const wrapper = mount(Test)
it('when clicked on ButtonItem it should be called one time', () => {
const button = wrapper.findComponent(ButtonItem)
button.trigger('click')
expect(wrapper.emitted().click).toBeTruthy()
expect(wrapper.emitted().click.length).toBe(1)
})
})

Vuex + Jest + Composition API: How to check if an action has been called

I am working on a project built on Vue3 and composition API and writing test cases.
The component I want to test is like below.
Home.vue
<template>
<div>
<Child #onChangeValue="onChangeValue" />
</div>
</template>
<script lang="ts>
...
const onChangeValue = (value: string) => {
store.dispatch("changeValueAction", {
value: value,
});
};
</scirpt>
Now I want to test if changeValueAction has been called.
Home.spec.ts
...
import { key, store } from '#/store';
describe("Test Home component", () => {
const wrapper = mount(Home, {
global: {
plugins: [[store, key]],
},
});
it("Test onChangeValue", () => {
const child = wrapper.findComponent(Child);
child.vm.$emit("onChangeValue", "Hello, world");
// I want to check changeValueAction has been called.
expect(wrapper.vm.store.state.moduleA.value).toBe("Hello, world");
});
});
I can confirm the state has actually been updated successfully in the test case above but I am wondering how I can mock action and check if it has been called.
How can I do it?
I have sort of a similar setup.
I don't want to test the actual store just that the method within the component is calling dispatch with a certain value.
This is what I've done.
favorite.spec.ts
import {key} from '#/store';
let storeMock: any;
beforeEach(async () => {
storeMock = createStore({});
});
test(`Should remove favorite`, async () => {
const wrapper = mount(Component, {
propsData: {
item: mockItemObj
},
global: {
plugins: [[storeMock, key]],
}
});
const spyDispatch = jest.spyOn(storeMock, 'dispatch').mockImplementation();
await wrapper.find('.remove-favorite-item').trigger('click');
expect(spyDispatch).toHaveBeenCalledTimes(1);
expect(spyDispatch).toHaveBeenCalledWith("favoritesState/deleteFavorite", favoriteId);
});
This is the Component method:
setup(props) {
const store = useStore();
function removeFavorite() {
store.dispatch("favoritesState/deleteFavorite", favoriteId);
}
return {
removeFavorite
}
}
Hope this will help you further :)

Vue 3 access child component from slots

I am currently working on a custom validation and would like to, if possible, access a child components and call a method in there.
Form wrapper
<template>
<form #submit.prevent="handleSubmit">
<slot></slot>
</form>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
setup(props, { slots }) {
const validate = (): boolean => {
if (slots.default) {
slots.default().forEach((vNode) => {
if (vNode.props && vNode.props.rules) {
if (vNode.component) {
vNode.component.emit('validate');
}
}
});
}
return false;
};
const handleSubmit = (ev: any): void => {
validate();
};
return {
handleSubmit,
};
},
});
</script>
When I call slot.default() I get proper list of child components and can see their props. However, vNode.component is always null
My code is based from this example but it is for vue 2.
If someone can help me that would be great, or is this even possible to do.
I found another solution, inspired by quasar framework.
Form component provide() bind and unbind function.
bind() push validate function to an array and store in Form component.
Input component inject the bind and unbind function from parent Form component.
run bind() with self validate() function and uid
Form listen submit event from submit button.
run through all those validate() array, if no problem then emit('submit')
Form Component
import {
defineComponent,
onBeforeUnmount,
onMounted,
reactive,
toRefs,
provide
} from "vue";
export default defineComponent({
name: "Form",
emits: ["submit"],
setup(props, { emit }) {
const state = reactive({
validateComponents: []
});
provide("form", {
bind,
unbind
});
onMounted(() => {
state.form.addEventListener("submit", onSubmit);
});
onBeforeUnmount(() => {
state.form.removeEventListener("submit", onSubmit);
});
function bind(component) {
state.validateComponents.push(component);
}
function unbind(uid) {
const index = state.validateComponents.findIndex(c => c.uid === uid);
if (index > -1) {
state.validateComponents.splice(index, 1);
}
}
function validate() {
let valid = true;
for (const component of state.validateComponents) {
const result = component.validate();
if (!result) {
valid = false;
}
}
return valid;
}
function onSubmit() {
const valid = validate();
if (valid) {
emit("submit");
}
}
}
});
Input Component
import { defineComponent } from "vue";
export default defineComponent({
name: "Input",
props: {
rules: {
default: () => [],
type: Array
},
modelValue: {
default: null,
type: String
}
}
setup(props) {
const form = inject("form");
const uid = getCurrentInstance().uid;
onMounted(() => {
form.bind({ validate, uid });
});
onBeforeUnmount(() => {
form.unbind(uid);
});
function validate() {
// validate logic here
let result = true;
props.rules.forEach(rule => {
const value = rule(props.modelValue);
if(!value) result = value;
})
return result;
}
}
});
Usage
<template>
<form #submit="onSubmit">
<!-- rules function -->
<input :rules="[(v) => true]">
<button label="submit form" type="submit">
</form>
</template>
In the link you provided, Linus mentions using $on and $off to do this. These have been removed in Vue 3, but you could use the recommended mitt library.
One way would be to dispatch a submit event to the child components and have them emit a validate event when they receive a submit. But maybe you don't have access to add this to the child components?
JSFiddle Example
<div id="app">
<form-component>
<one></one>
<two></two>
<three></three>
</form-component>
</div>
const emitter = mitt();
const ChildComponent = {
setup(props, { emit }) {
emitter.on('submit', () => {
console.log('Child submit event handler!');
if (props && props.rules) {
emit('validate');
}
});
},
};
function makeChild(name) {
return {
...ChildComponent,
template: `<input value="${name}" />`,
};
}
const formComponent = {
template: `
<form #submit.prevent="handleSubmit">
<slot></slot>
<button type="submit">Submit</button>
</form>
`,
setup() {
const handleSubmit = () => emitter.emit('submit');
return { handleSubmit };
},
};
const app = Vue.createApp({
components: {
formComponent,
one: makeChild('one'),
two: makeChild('two'),
three: makeChild('three'),
}
});
app.mount('#app');

test Vue js props component with Vue-test-utils

I'm new on testing Vue apps, I'm trying to test props in one Vue component, using Vue-test-utils package. I'm wondering if I'm creating the propsData is the proper way or there is another approach which in that case it's better to test this component successfully
Template.spec.js
import { mount } from '#vue/test-utils';
import Template from '~/components/Template.vue';
describe('Template', () => {
const wrapper = mount(Template, {
propsData: {
image: 'https://loremflickr.com/640/480/dog',
location: 'London',
jobPosition: 'Developer',
hashtags: ['es6', 'vue']
}
});
it('should render member props', () => {
expect(wrapper.props().image).toMatch('https://loremflickr.com/640/480/dog');
expect(wrapper.props().location).toMatch('London');
expect(wrapper.props().jobPosition).toMatch('Developer');
expect(wrapper.props().hashtags).toEqual(['es6', 'vue']);
});
});
Template.vue
<template>
<div class="template">
<img
:src="image"
>
<span>{{ location }}</span>
<span>{{ jobPosition }}</span>
</div>
</template>
<script>
export default {
name: 'template',
props: {
image: {
type: String,
required: true
},
location: {
type: String,
required: true
},
jobPosition: {
type: String,
required: true
},
}
};
</script>
You can test not props value, but component's behavior depending on props set. For example, your component can set some classes or show some element if some prop is set
e.g.
describe( 'BaseButton.vue icon rendering', () => {
const icon = 'laptop';
const wrapper = shallowMount( BaseButton, {
propsData : {
icon : icon,
},
} );
const wrapperWithoutIcon = shallowMount( BaseButton );
it( 'renders icon component', () => {
expect( wrapper.contains( FontAwesomeIcon ) ).toBe( true );
} );
it( 'sets a right classname', () => {
expect( wrapper.classes() ).toContain('--is-iconed');
} );
it( 'doesn\'t render an icon when icon prop is not passed', () => {
expect( wrapperWithoutIcon.contains( FontAwesomeIcon ) ).toBe( false );
} );
it( 'sets right prop for icon component', () => {
const iconComponent = wrapper.find( FontAwesomeIcon );
expect( iconComponent.attributes('icon') ).toMatch( icon );
} );
} );
Your Template.spec.js is fine, in which you set up your fake props.
You can check if those fake props are rendered out into the HTML.
Here is an example:
it('title render properly thru passed prop', () => {
const wrapper = shallowMount(app, {
propsData: {
data: {
title: 'here are the title'
}
},
})
expect(wrapper.text()).toContain('here are the title')
})
This way, you are checking if your code can render your props to HTML