Closed panganibanpj closed 5 years ago
What is benefit for supporting JSX $scopedSlots?
This feature would be useful for making renderless components such as outlined by Adam here: https://adamwathan.me/renderless-components-in-vuejs/
It would be helpful to test the component api. Currently I get the above error in jest in my unit test.
Hi, please can someone help me with how I can test scoped slots?
Currently, my render function looks like this
render() {
return this.$scopedSlots.default({ ...this.computedStateAndHelpers })
},
In my test (with Jest), I'm trying to mount like this:
const wrapper = shallowMount(Component, {
scopedSlots: {
default: '<p></p>'
}
})
expect(wrapper).toBeAViewComponent()
But no luck.
I've also tried making default property a method and no luck there either. I believe I followed the docs properly. How do I do this? Thanks!
I've worked around it by wrapping the component like
shallowMount({
render() {
return (
<Component
scopedSlots={{
default: <p></p>
}}
/>
);
}
})
hi @panganibanpj I still get
TypeError: this.$scopedSlots.default is not a function
Also, won't I lose direct access to the components vm?
@kayandrae07 You should be able to call find
to get the nested component (may have to mount instead of shallowMount)
Something like this:
const wrapper = mount({
render() {
return (
<Component
scopedSlots={{
default: <p></p>
}}
/>
);
}
});
const componentWrapper = wrapper.find(Component);
@davestaab thank you. This was what worked for me
const wrapper = mount({
render() {
return <Component scopedSlots={{ default: () => <p></p> }}/>
}
})
const componentWrapper = wrapper.find(Component)
expect(componentWrapper.isVueInstance()).toBeTruthy()
I had to make the scoped slot parameter a function otherwise it failed for me
So that method works until I need to mock dependencies.
When I pass options to mount/shallowMount they are set on the outer component and not on the true component I'm trying to test. (Component in the above examples)
Looking forward to a first class solution to this.
@davestaab 😄 I created a helper function that sends all the options to the Component
import { mount } from '@vue/test-utils'
export default (component, options = {}) => {
return mount({
render(h) {
return h(component, {
scopedSlots: { default: () => <p /> },
...options,
})
},
}).find(component)
}
I'm also looking for a solution and will love to contribute in my spare time. Also, do you know how I can access the values exported from a scoped slot?
I'm able to do this:
const wrapper = shallowMount(Component, {
scopedSlots: {
default:
`<div slot-scope="props" class="content">
slot content {{ props.message }}
</div>`
}
});
expect(wrapper.text()).toContain("slot content hi");
where "hi" is bound to the slot as message inside Component
.
So message
is passed from Component to the scoped slot. Is that what you mean?
Yeah, it's what I'm also doing. But assuming I'm exposing a method from the slot-scope. How do I access it?
@kayandrae07 I've ended up using a template based approach like in this project: https://github.com/posva/vue-promised
Basically I make a test helper component that uses the component in question. To test methods, I use them and assert they had the desired results.
Hope this helps.
@davestaab Thank you! Taking a look at Vue-promised and its tests and I found a suitable solution to my problem. Since it uses scoped-slots too, I can just mirror their implementation.
This feature would be useful for making renderless components such as outlined by Adam here: https://adamwathan.me/renderless-components-in-vuejs/
It would be helpful to test the component api. Currently I get the above error in jest in my unit test.
It seems that this example does not use createElement()
.
I think the API as below using createElement()
is not absolutely necessary.
scopedSlots: {
foo: (createElement, props) => { return createElement('div', props); },
},
I was encountering similar scoped slot testing issues when using a functional
component or component with a render
function. After #808 landed, the final key for me ended up being to ensure that the scoped slot in a test component returns a VNode
as that is what render
functions ultimately need to return. The following sample code demonstrates returning a VNode
from a scoped slot in a test mounted component. Hope someone else finds value in it!
// component
const MyComponent = {
functional: true,
props: {
someComponentProp: { type: String },
}
render(createElement, context) {
const { someComponentProp } = context.props;
if (context.data.scopedSlots && context.data.scopedSlots.default) {
// scoped slot should return a VNode
return context.data.scopedSlots.default(someComponentProp);
}
return createElement('div', {}, 'default value if slot isn't defined');
}
};
// test
const scopedComponent = {
props: { someProp: { type: String } },
render(createElement) {
return createElement('em', {}, this.someProp);
},
};
const props = { someComponentProp: 'somePropValue' };
const rendered = mount(MyComponent, {
context: {
props,
scopedSlots: {
default: (someProp) => {
const scoped = mount(scopedComponent, { propsData: { someProp } });
// NOTE: `render` function needs VNode
return scoped.vnode;
},
},
},
});
expect(rendered.html()).toBe(`<em>${props.someComponentProp}</em>`);
What problem does this feature solve?
Being able to use $scopedSlots with render function (specifically for JSX usage)
It will complain that
$scopedSlots.foo is not a function
, but if you make it a function, it will say$scopedSlots.foo.trim() is not a function
What does the proposed API look like?
I prefer if
scopedSlots
can be passed function values like:Otherwise, at least make it so the above syntax would still work (with string
scopedSlot.foo
values but thethis.$scopedSlots.foo()
in the render function still work)