How to find component, passed as slot into child component, in jest? - vue.js

I have a component, that uses another 'wrapper' component and passing all of it's body as slots into it.
Something like that:
<template>
<WrapperComponent>
<Card />
</WrapperComponent>
</template>
Question is - how do I find this 'Card' component while testing parent component in jest?
Have tried like that, but failed:
describe('RegistrationModal', () => {
const wrapper = mount(RegistrationModal, {
localVue
});
test('Registration of private company', async () => {
const PrivateCard = wrapper.findAllComponents(Card).at(0);
await PrivateCard.trigger('click', ['private'] );
expect(wrapper.vm.action).toBe('private');
expect(wrapper.vm.step).toBe(1);
});
});

Related

Get data from a vue component child and bind to an object in parent component

<user-data #change="setUserInfo"></user-data">
this is the child component where have used emits to pass data.
here is the method of parent component.
setUserInfo(data) {
this.obj.payment_details = data;
},
is it possible to bind data from the above method?
export default {
data: () => ({
dialog: false,
obj: new Expense(),
saveLoader: false,
}),
}
Here you have an example on how to emit data from child component to parent (using Vue3 Composition API script setup):
Parent:
<template>
<Comp #my-var="callback" />
{{ test }}
</template>
<script setup>
import { ref } from 'vue'
import Comp from './Comp.vue'
const test = ref('')
const callback = data => test.value = data
</script>
Child:
<template>
<button
v-text="'click'"
#click="doEmit()"
/>
</template>
<script setup>
const emits = defineEmits(['myVar'])
const doEmit = () => emits('myVar', 'emiting this data')
</script>
Check out the Playground

How can I test nested slots in Vue

I am trying to pass nested slots into my mounted component and I can;'t figure out the syntax, couldn't find anything in docs.
What I am trying to do is to add html element into drop-down slot
This is my test what I've tried but it looks to be wrong.
it('should render drop down', async () => {
const wrapper = mount(MyComponent, {
slots: {
utilities: `
<template slot="drop-down">
<input placeholder="Search bar" class="header-bar-mobile-drop-down-utility__example">
</template>
`
}
});
expect(wrapper.find(".header-bar-mobile-drop-down-utility").exists()).to.equal(true)
});

Template ref do not trigger watchers

I'm facing a weird issue about watchers and template refs.
I have the following component in which I used an slot.
<template>
<div
class="state.widgetClasses"
ref="widgetElement"
>
<slot />
</div>
</template>
The content in the <slot /> could be large and overflow the component, so I need to add behavior using widgetClasses. That property is being created using reactive as follow:
const state = reactive<State>({
'widgetClasses': '',
...
});
The overflowed state could be detected using scrollHeigth and scrollClient provided by the ref widgetElement, I used the following watchers to check if overflowed.
watch(
() => widgetElement.value?.scrollHeigth,
() => checkOverflow(),
{ 'flush': 'post' }
);
and
watchEffect(
() => checkOverflow(),
{ 'flush': 'post' }
);
Both do not work, they only be triggered when the component is mounted (widgetElement is not undefined) and do not detect when widgetElement.vale.scrollHeigth changes after mounted.

How to prevent double invoking of the hook created?

So I have a problem with my vue3 project. The gist: I need to support different layouts for some use cases: authorization, user profile's layout, group's layout, etc. I've got the opportunity by this way:
Create a component AppLayout.vue for managing layouts
<template>
<component :is="layout">
<slot />
</component>
</template>
<script>
import AppLayoutDefault from "#/layouts/EmptyLayout";
import { shallowRef, watch } from "vue";
import { useRoute } from "vue-router";
export default {
name: "AppLayout",
setup() {
const layout = shallowRef(AppLayoutDefault);
const route = useRoute();
watch(
() => route.meta,
async (meta) => {
try {
const component =
await require(`#/layouts/${meta.layout}.vue`);
layout.value = component?.default || AppLayoutDefault;
} catch (e) {
layout.value = AppLayoutDefault;
}
}
);
return { layout };
},
};
</script>
So my App.vue started to look so
<template>
<AppLayout>
<router-view />
</AppLayout>
</template>
To render a specific layout, I've added to router's index.js special tag meta
{
path: '/login',
name: 'LoginPage',
component: () => import('../views/auth/LoginPage.vue')
},
{
path: '/me',
name: 'MePage',
component: () => import('../views/user/MePage.vue'),
meta: {
layout: 'ProfileLayout'
},
},
Now I can create some layouts. For example, I've made ProfileLayout.vue with some nested components: Header and Footer. I use slots to render dynamic page content.
<template>
<div>
<div class="container">
<Header />
<slot />
<Footer />
</div>
</div>
</template>
So, when I type the URL http://example.com/profile, I see the content of Profile page based on ProfileLayout. And here the problem is: Profile page invokes hooks twice.
I put console.log() into created() hook and I see the following
That's problem because I have some requests inside of hooks, and they execute twice too. I'm a newbie in vuejs and I don't understand deeply how vue renders components. I suggest that someting inside of the code invokes re-rendering and Profile Page creates again. How to prevent it?
Your profile page loaded twice because it's literally... have to load twice.
This is the render flow, not accurate but for you to get the idea:
Your layout.value=AppDefaultLayout. The dynamic component <component :is="layout"> will render it first since meta.layout is undefined on initial. ProfilePage was also rendered at this point.
meta.layout now had value & watcher made the change to layout.value => <component :is="layout"> re-render 2nd times, also for ProfilePage
So to resolve this problem I simply remove the default value, the dynamic component is no longer need to render default layout. If it has no value so it should not render anything.
<keep-alive>
<component :is="layout">
<slot />
</component>
</keep-alive>
import { markRaw, shallowRef, watch } from "vue";
import { useRoute } from "vue-router";
export default {
name: "AppLayout",
setup() {
console.debug("Loaded DynamicLayout");
const layout = shallowRef()
const route = useRoute()
const getLayout = async (lyt) => {
const c = await import(`#/components/${lyt}.vue`);
return c.default;
};
watch(
() => route.meta,
async (meta) => {
console.log('...', meta.layout);
try {
layout.value = markRaw(await getLayout(meta.layout));
} catch (e) {
console.warn('%c Use AppLayoutDefault instead.\n', 'color: darkturquoise', e);
layout.value = markRaw(await getLayout('EmptyLayout'));
}
}
);
return { layout }
},
};

Cannot spyOn on a primitive value; undefined given . Vue JS, Jest, Utils

I try to use spyOn to spy the functions and it's implementation. However, i got this error. "Cannot spyOn on a primitive value; undefined given".
I already read the documentation of jest.spyOn in https://jestjs.io/docs/en/jest-object . But it keeps showing the same errror... is there anything that i should add and improve?
below is the code
<template>
<div>
<form #submit.prevent="onSubmit(inputValue)">
<input type="text" v-model="inputValue">
<span class="reversed">{{ reversedInput }}</span>
</form>
</div>
</template>
<script>
import axios from 'axios';
export default {
props: ['reversed'],
data: () => ({
inputValue: '',
results: [],
}),
methods: {
onSubmit(value) {
const getPromise = axios.get(
'https://jsonplaceholder.typicode.com/posts?q=' + value,
);
getPromise.then(results => {
this.results = results.data;
});
return getPromise;
},
},
};
</script>
while the test code is
import axios from 'axios'; // axios here is the mock from above!
import { shallowMount } from '#vue/test-utils';
import Form from '#/components/Form.vue';
describe('Form.test.js', () => {
const wrapper;
describe('Testing Submit events', () => {
wrapper = shallowMount(Form);
it('calls submit event', () => {
const onSubmit = jest.spyOn(Form.prototype, 'onSubmit') // mock function
// updating method with mock function
wrapper.setMethods({ onSubmit });
//find the button and trigger click event
wrapper.findAll('form').trigger('submit');
expect(onSubmit).toBeCalled();
})
});
})
Can you also vrief me what and how to use spyOn to test the method?
Thank you so much
Best regards
Lughni
Component definition suggests that Form is an object. Form.prototype === undefined because Form is not a function. Since Vue class components aren't in use, nothing suggests the opposite.
It can be spied as:
jest.spyOn(Form.methods, 'onSubmit')
This should be done prior to component instantiation. And spyOn with no implementation provided creates a spy, not a mock.