In Vue.js, a functional component can return multiple root nodes by using a render
function that returns an array of createdElements.
export default {
functional: true,
props: ['cellData'],
render: function (h, context) {
return [
h('td', context.props.cellData.category),
h('td', context.props.cellData.description)
]
}
}
This works great but I'm having trouble trying to create a unit test for such a component. Using shallowMount
on the component results in [Vue warn]: Multiple root nodes returned from render function. Render function should return a single root node.
import { shallowMount } from '@vue/test-utils'
import Cell from '@/components/Cell'
wrapper = shallowMount(Cell, {
context: {
props: {
cellData {
category: 'foo',
description: 'bar'
}
}
}
});
This github issue suggests that the component needs to be wrapped in a single root node to actually render it, but trying that results in [vue-test-utils]: mount.context can only be used when mounting a functional component
import { shallowMount } from '@vue/test-utils'
import Cell from '@/components/Cell'
wrapper = shallowMount('<div><Cell></div>', {
context: {
props: {
cellData {
category: 'foo',
description: 'bar'
}
}
}
});
So how do I test a functional component that returns multiple root nodes?
A render function returns a virtual DOM node, commonly named VNode in the Vue ecosystem, which is an interface that allows Vue to write these objects in your browser DOM. They contain all the information necessary to work with Vue.
createLocalVue returns a Vue class for you to add components, mixins and install plugins without polluting the global Vue class. The errorHandler option can be used to handle uncaught errors during component render function and watchers.
js. Love it or hate it, JSX is a popular extension to JavaScript that allows XML tokens in your scripts. If you want to create templates in your script files and you find Vue's render() function to be difficult to work with, JSX may be just what you need.
You could create a higher order, transparent wrapper component that passes all props and event listeners to the inner Cell
component using v-bind="$attrs"
[1] and v-on="$listeners"
[2]. Then you can use propsData
to pass props to the wrapper component ..
import { mount } from '@vue/test-utils'
import Cell from '@/components/Cell'
const WrappedCell = {
components: { Cell },
template: `
<div>
<Cell v-bind="$attrs" v-on="$listeners" />
</div>
`
}
const wrapper = mount(WrappedCell, {
propsData: {
cellData: {
category: 'foo',
description: 'bar'
}
}
});
You can create a fragment_wrapper
for wrapping your Components with Fragments (multiple root elements).
//File: fragment_wrapper.js
exports.fragment_wrapper = function(FragmentComponent){
const wrapper = {
components: { FragmentComponent },
props: FragmentComponent.props,
template: `<div><FragmentComponent v-bind="$props" v-on="$listeners"/></div>`
}
return wrapper;
}
Then you can use this to test all your Fragmented Components as follows:
import { mount } from '@vue/test-utils'
import { fragment_wrapper } from './fragment_wrapper'
import Cell from './components/Cell'
describe('Test Cell', () => {
let WrappedCell = fragment_wrapper(Cell);
const wrapper = mount(WrappedCell, {
propsData: {
cellData: {
category: 'foo',
description: 'bar'
}
}
});
it('renders the correct markup', () => {
expect(wrapper.html()).toContain('<td>foo</td>')
});
});
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