Skip to content

Commit 8feec8c

Browse files
authored
fix validation for Vue render function (#310)
We didn't take functional components into account, therefore we would receive errors because a Transition component is a functional component in production and we didn't take that into account.
1 parent 035f9b0 commit 8feec8c

File tree

2 files changed

+99
-0
lines changed

2 files changed

+99
-0
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { defineComponent, h } from 'vue'
2+
import { render as testRender } from '../test-utils/vue-testing-library'
3+
4+
import { render } from './render'
5+
import { html } from '../test-utils/html'
6+
7+
let Dummy = defineComponent({
8+
props: {
9+
as: { type: [Object, String], default: 'div' },
10+
},
11+
setup(props, { attrs, slots }) {
12+
return () => render({ props, slots, attrs, slot: {}, name: 'Dummy' })
13+
},
14+
})
15+
16+
function renderTemplate(input: string | Partial<Parameters<typeof defineComponent>[0]>) {
17+
let defaultComponents = { Dummy }
18+
19+
if (typeof input === 'string') {
20+
return testRender(defineComponent({ template: input, components: defaultComponents }))
21+
}
22+
23+
return testRender(
24+
defineComponent(
25+
Object.assign({}, input, {
26+
components: { ...defaultComponents, ...input.components },
27+
}) as Parameters<typeof defineComponent>[0]
28+
)
29+
)
30+
}
31+
32+
describe('Validation', () => {
33+
it('should error when using an as="template" with additional props', () => {
34+
expect.hasAssertions()
35+
36+
renderTemplate({
37+
template: html`
38+
<Dummy as="template" class="abc">Contents</Dummy>
39+
`,
40+
errorCaptured(err) {
41+
expect(err as Error).toEqual(
42+
new Error(
43+
[
44+
'Passing props on "template"!',
45+
'',
46+
'The current component <Dummy /> is rendering a "template".',
47+
'However we need to passthrough the following props:',
48+
' - class',
49+
'',
50+
'You can apply a few solutions:',
51+
' - Add an `as="..."` prop, to ensure that we render an actual element instead of a "template".',
52+
' - Render a single element as the child so that we can forward the props onto that element.',
53+
].join('\n')
54+
)
55+
)
56+
return false
57+
},
58+
})
59+
})
60+
61+
it('should forward the props to the first child', () => {
62+
renderTemplate({
63+
template: html`
64+
<Dummy as="template" class="abc">
65+
<div id="result">Contents</div>
66+
</Dummy>
67+
`,
68+
})
69+
70+
expect(document.getElementById('result')).toHaveClass('abc')
71+
})
72+
73+
it('should forward the props via Functional Components', () => {
74+
renderTemplate({
75+
components: {
76+
PassThrough(props, context) {
77+
props.as = props.as ?? 'template'
78+
return render({
79+
props,
80+
attrs: context.attrs,
81+
slots: context.slots,
82+
slot: {},
83+
name: 'PassThrough',
84+
})
85+
},
86+
},
87+
template: html`
88+
<Dummy as="template" class="abc" data-test="123">
89+
<PassThrough>
90+
<div id="result">Contents</div>
91+
</PassThrough>
92+
</Dummy>
93+
`,
94+
})
95+
96+
expect(document.getElementById('result')).toHaveClass('abc')
97+
})
98+
})

packages/@headlessui-vue/src/utils/render.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,5 +133,6 @@ function isValidElement(input: any): boolean {
133133
if (input == null) return false // No children
134134
if (typeof input.type === 'string') return true // 'div', 'span', ...
135135
if (typeof input.type === 'object') return true // Other components
136+
if (typeof input.type === 'function') return true // Built-ins like Transition
136137
return false // Comments, strings, ...
137138
}

0 commit comments

Comments
 (0)