I want to test this FooComponent
:
<div>
<slot :fn="internalFn" />
</div>
It's used like that (e.g. in ParentComponent
):
<FooComponent>
<template slot-scope="slotProps">
<BarComponent @some-event="slotProps.fn" />
</template>
</FooComponent>
So I want to test how my component reacts on calling this "fn" from slot props. The easiest way I see is to take method itself and call it, like that:
cosnt wrapper = shallowMount(FooComponent, /* ... */)
wrapper.vm.methods.internalFn(/* test payload */)
expect(wrapper.emitted()).toBe(/* some expectation */)
But this is well known as anti-pattern about testing internal implementation. So instead I would like to test it via prop fn
passed into slot, because it's also some sort of component interface, like component own props.
But how to to test props passed in slot? I can imagine it's working only in case if I test that ParentComponent
something like that:
const wrapper = shallowMount(ParentComponent, /* ... */)
const foo = wrapper.find(FooComponent)
wrapper.find(BarComponent).vm.$emit('some-event', /*...*/)
/* write expectations against foo */
But that feels like tests for FooComponent
inside tests for ParentComponent
Maybe there is a better way to do it?
Scoped slots To pass a property in Vue, you would define the data in the parent component and assign a value to the data. Then, you'd pass the value of the property to the child component so the data becomes a property in the child component.
Slot props allow us to turn slots into reusable templates that can render different content based on input props. This is most useful when you are designing a reusable component that encapsulates data logic while allowing the consuming parent component to customize part of its layout.
v-slot is used on an HTML element. v-slot can only be used on a <template> element or the component receiving the slot. Otherwise, Vue throws a compile error. See Example 1 below. v-slot is used on a component, but the component has another <template v-slot> as a child.
I'm a bit late to the party but I just hit the same problem. I took a few cues from the actual Vue tests themselves, while they're more abstract in what they're testing compared to us, it did help.
Here's what I came up with:
import { shallowMount } from '@vue/test-utils';
import Component from 'xyz/Component';
let wrapperSlotted;
describe('xyz/Component.vue', () => {
beforeAll(() => {
wrapperSlotted = shallowMount(Component, {
data() {
return { myProp: 'small' }
},
scopedSlots: {
default: function (props) {
return this.$createElement('div', [props.myProp]);
}
},
});
});
it('slot prop passed to a scoped slot', () => {
let text = wrapperSlotted.find('div').text();
expect(text).toBe('small'); // the value of `myProp`, which has been set as the text node in the slotted <div />
});
});
So the main thing was that I used the render function for scopedSlots
.
Hope that helps someone :)
UPD: below the line is the original answer that I gave years ago. For today I would say that one of these approaches would be good enough:
Don't test the slot method, but test the user-facing feature that relies on it. Try to test it manually first, notice how do you do it, then try to write a similar test. E.g. with testing-library/vue
If the first option is too hard to do, try to come up with some fake testing component. The idea is very similar to what I described in the question
But that feels like tests for FooComponent inside tests for ParentComponent
But instead of using the ParentComponent
just create some very simple inline component (right in your FooComponent.spec.js
file), that uses the component with a slot.
Original answer that was given in 2020:
Since there are no answers so I just share what I ended up with.
I decided to test the internal method. It's not cool, but at least, because I use typescript, I have a type-safe test, which will fail with a clear type error if I rename or modify the method which I test. Looks like this:
import Foo from '@/components/foo/Foo.ts'
import FooComponent from '@/components/foo/Foo.vue'
/*...*/
cosnt wrapper = <Foo>shallowMount(FooComponent, /* ... */)
// notice that because I passed `Foo` which is a class-component,
// I have autocomplete and type checks for vm.*
wrapper.vm.internalFn(/* test payload */)
expect(wrapper.emitted()).toBe(/* some expectation */)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With