Skip to content

Commit d27becb

Browse files
committed
fix: support named functional slots
1 parent d9e368b commit d27becb

File tree

5 files changed

+114
-58
lines changed

5 files changed

+114
-58
lines changed

src/lib/create-instance.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ import extractOptions from '../options/extract-options'
1414
import deleteMountingOptions from '../options/delete-mounting-options'
1515
import { compileToFunctions } from 'vue-template-compiler'
1616

17+
function isValidSlot (slot: any): boolean {
18+
return Array.isArray(slot) || (slot !== null && typeof slot === 'object') || typeof slot === 'string'
19+
}
20+
1721
function createFunctionalSlots (slots = {}, h) {
1822
if (Array.isArray(slots.default)) {
1923
return slots.default.map(h)
@@ -26,12 +30,18 @@ function createFunctionalSlots (slots = {}, h) {
2630
Object.keys(slots).forEach(slotType => {
2731
if (Array.isArray(slots[slotType])) {
2832
slots[slotType].forEach(slot => {
33+
if (!isValidSlot(slot)) {
34+
throwError('slots[key] must be a Component, string or an array of Components')
35+
}
2936
const component = typeof slot === 'string' ? compileToFunctions(slot) : slot
3037
const newSlot = h(component)
3138
newSlot.data.slot = slotType
3239
children.push(newSlot)
3340
})
3441
} else {
42+
if (!isValidSlot(slots[slotType])) {
43+
throwError('slots[key] must be a Component, string or an array of Components')
44+
}
3545
const component = typeof slots[slotType] === 'string' ? compileToFunctions(slots[slotType]) : slots[slotType]
3646
const slot = h(component)
3747
slot.data.slot = slotType

src/lib/error-handler.js

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,8 @@
1-
function errorMessage (message, info) {
2-
if (info) {
3-
return `${message} : additional info ${info}`
4-
}
5-
6-
return message
7-
}
8-
9-
export default function errorHandler (errorOrString, vm, info) {
1+
export default function errorHandler (errorOrString, vm) {
102
const error = (typeof errorOrString === 'object')
113
? errorOrString
124
: new Error(errorOrString)
135

14-
error.message = errorMessage(error.message, info)
156
vm._error = error
167

178
throw error

test/resources/components/functional-component-with-slots.vue

Lines changed: 0 additions & 9 deletions
This file was deleted.

test/unit/specs/lib/error-handler.spec.js

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,34 @@
11
import errorHandler from '../../../../src/lib/error-handler'
22

3+
const errorString = 'errorString'
4+
const errorObject = new Error(errorString)
5+
36
describe('errorHandler', () => {
4-
const errorString = 'errorString'
5-
const info = 'additional info provided by vue'
6-
const errorObject = new Error(errorString)
7-
it('when error object: rethrows error', () => {
7+
it('throws error', () => {
88
expect(() => errorHandler(errorObject, {})).to.throw().with.property('message', errorString)
99
})
1010

11-
it('when error object: rethrown error contains vue info when provided', () => {
12-
expect(() => errorHandler(errorObject, {}, info)).to.throw().that.satisfies(function (err) {
13-
const errorMessage = err.message
14-
15-
return errorMessage.includes(errorString) && errorMessage.includes(info)
11+
it('throws error with vue info when provided', () => {
12+
expect(() => errorHandler(errorObject, {})).to.throw().that.satisfies(function (err) {
13+
return err.message.includes(errorString)
1614
})
1715
})
1816

19-
it('when error object: sets vm_error to the error that is thrown', () => {
17+
it('sets vm_error to the error that is thrown', () => {
2018
const vm = {}
21-
expect(() => errorHandler(errorObject, vm, info)).to.throw().that.satisfies(function (err) {
19+
expect(() => errorHandler(errorObject, vm)).to.throw().that.satisfies(function (err) {
2220
return err === vm._error
2321
})
2422
})
2523

26-
it('when error string: throws error with string', () => {
24+
it('throws error with string', () => {
2725
expect(() => errorHandler(errorString, {})).to.throw().with.property('message', errorString)
2826
})
2927

30-
it('throws error with string and appends info when provided', () => {
31-
expect(() => errorHandler(errorString, {}, info)).to.throw().that.satisfies(function (err) {
32-
const errorMessage = err.message
33-
34-
return errorMessage.includes(errorString) && errorMessage.includes(info)
35-
})
36-
})
37-
38-
it('when error string: sets vm_error to the error that is thrown', () => {
28+
it('sets vm_error to the error that is thrown', () => {
3929
const vm = {}
4030

41-
expect(() => errorHandler(errorObject, vm, info)).to.throw().that.satisfies(function (err) {
31+
expect(() => errorHandler(errorObject, vm)).to.throw().that.satisfies(function (err) {
4232
return err === vm._error
4333
})
4434
})

test/unit/specs/mount/options/slots.spec.js

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { compileToFunctions } from 'vue-template-compiler'
22
import mount from '~src/mount'
33
import Component from '~resources/components/component.vue'
44
import ComponentWithSlots from '~resources/components/component-with-slots.vue'
5-
import FunctionalComponentWithSlots from '~resources/components/functional-component-with-slots.vue'
65

76
describe('mount.slots', () => {
87
it('mounts component with default slot if passed component in slot object', () => {
@@ -84,32 +83,50 @@ describe('mount.slots', () => {
8483
})
8584

8685
it('mounts functional component with default slot if passed component in slot object', () => {
87-
const wrapper = mount(FunctionalComponentWithSlots, { slots: { default: Component }})
86+
const TestComponent = {
87+
name: 'component-with-slots',
88+
functional: true,
89+
render: (h, ctx) => h('div', ctx.data, ctx.slots().default)
90+
}
91+
const wrapper = mount(TestComponent, { slots: { default: Component }})
8892
expect(wrapper.contains(Component)).to.equal(true)
8993
})
9094

9195
it('mounts component with default slot if passed component in slot object', () => {
92-
const wrapper = mount(FunctionalComponentWithSlots, { slots: { default: [Component] }})
96+
const TestComponent = {
97+
name: 'component-with-slots',
98+
functional: true,
99+
render: (h, ctx) => h('div', ctx.data, ctx.slots().default)
100+
}
101+
const wrapper = mount(TestComponent, { slots: { default: [Component] }})
93102
expect(wrapper.contains(Component)).to.equal(true)
94103
})
95104

96105
it('mounts component with default slot if passed object with template prop in slot object', () => {
106+
const TestComponent = {
107+
name: 'component-with-slots',
108+
functional: true,
109+
render: (h, ctx) => h('div', ctx.data, ctx.slots().default)
110+
}
97111
const compiled = compileToFunctions('<div id="div" />')
98-
const wrapper = mount(FunctionalComponentWithSlots, { slots: { default: [compiled] }})
112+
const wrapper = mount(TestComponent, { slots: { default: [compiled] }})
99113
expect(wrapper.contains('#div')).to.equal(true)
100114
})
101115

102116
it('mounts component with default slot if passed string in slot object', () => {
103-
const wrapper = mount(FunctionalComponentWithSlots, { slots: { default: '<span />' }})
117+
const TestComponent = {
118+
name: 'component-with-slots',
119+
functional: true,
120+
render: (h, ctx) => h('div', ctx.data, ctx.slots().default)
121+
}
122+
const wrapper = mount(TestComponent, { slots: { default: '<span />' }})
104123
expect(wrapper.contains('span')).to.equal(true)
105124
})
106125

107126
it('mounts component with named slot if passed string in slot object', () => {
108127
const TestComponent = {
109128
functional: true,
110-
render (h, ctx) {
111-
return h('div', {}, ctx.slots().named)
112-
}
129+
render: (h, ctx) => h('div', {}, ctx.slots().named)
113130
}
114131
const wrapper = mount(TestComponent, { slots: { named: Component }})
115132
expect(wrapper.contains(Component)).to.equal(true)
@@ -118,9 +135,7 @@ describe('mount.slots', () => {
118135
it('mounts component with named slot if passed string in slot object in array', () => {
119136
const TestComponent = {
120137
functional: true,
121-
render (h, ctx) {
122-
return h('div', {}, ctx.slots().named)
123-
}
138+
render: (h, ctx) => h('div', {}, ctx.slots().named)
124139
}
125140
const wrapper = mount(TestComponent, { slots: { named: [Component] }})
126141
expect(wrapper.contains(Component)).to.equal(true)
@@ -129,9 +144,7 @@ describe('mount.slots', () => {
129144
it('mounts component with named slot if passed string in slot object in array', () => {
130145
const TestComponent = {
131146
functional: true,
132-
render (h, ctx) {
133-
return h('div', {}, ctx.slots().named)
134-
}
147+
render: (h, ctx) => h('div', {}, ctx.slots().named)
135148
}
136149
const wrapper = mount(TestComponent, { slots: { named: '<span />' }})
137150
expect(wrapper.contains('span')).to.equal(true)
@@ -140,11 +153,72 @@ describe('mount.slots', () => {
140153
it('mounts component with named slot if passed string in slot object in array', () => {
141154
const TestComponent = {
142155
functional: true,
143-
render (h, ctx) {
144-
return h('div', {}, ctx.slots().named)
145-
}
156+
render: (h, ctx) => h('div', {}, ctx.slots().named)
146157
}
147158
const wrapper = mount(TestComponent, { slots: { named: ['<span />'] }})
148159
expect(wrapper.contains('span')).to.equal(true)
149160
})
161+
162+
it('throws error if passed false for named slots', () => {
163+
const TestComponent = {
164+
name: 'component-with-slots',
165+
functional: true,
166+
render: (h, ctx) => h('div', ctx.data, ctx.slots().default)
167+
}
168+
const fn = () => mount(TestComponent, { slots: { named: [false] }})
169+
const message = '[vue-test-utils]: slots[key] must be a Component, string or an array of Components'
170+
expect(fn).to.throw().with.property('message', message)
171+
})
172+
173+
it('throws error if passed a number for named slots', () => {
174+
const TestComponent = {
175+
name: 'component-with-slots',
176+
functional: true,
177+
render: (h, ctx) => h('div', ctx.data, ctx.slots().default)
178+
}
179+
const fn = () => mount(TestComponent, { slots: { named: [1] }})
180+
const message = '[vue-test-utils]: slots[key] must be a Component, string or an array of Components'
181+
expect(fn).to.throw().with.property('message', message)
182+
})
183+
it('throws error if passed false for named slots', () => {
184+
const TestComponent = {
185+
name: 'component-with-slots',
186+
functional: true,
187+
render: (h, ctx) => h('div', ctx.data, ctx.slots().default)
188+
}
189+
const fn = () => mount(TestComponent, { slots: { named: false }})
190+
const message = '[vue-test-utils]: slots[key] must be a Component, string or an array of Components'
191+
expect(fn).to.throw().with.property('message', message)
192+
})
193+
194+
it('throws error if passed a number for named slots', () => {
195+
const TestComponent = {
196+
name: 'component-with-slots',
197+
functional: true,
198+
render: (h, ctx) => h('div', ctx.data, ctx.slots().default)
199+
}
200+
const fn = () => mount(TestComponent, { slots: { named: 1 }})
201+
const message = '[vue-test-utils]: slots[key] must be a Component, string or an array of Components'
202+
expect(fn).to.throw().with.property('message', message)
203+
})
204+
it('throws error if passed string in default slot array when vue-template-compiler is undefined', () => {
205+
const TestComponent = {
206+
name: 'component-with-slots',
207+
functional: true,
208+
render: (h, ctx) => h('div', ctx.data, ctx.slots().default)
209+
}
210+
const compilerSave = require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions
211+
require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions = undefined
212+
delete require.cache[require.resolve('../../../../../src/mount')]
213+
const mountFresh = require('../../../../../src/mount').default
214+
const message = '[vue-test-utils]: vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined'
215+
const fn = () => mountFresh(TestComponent, { slots: { default: ['<span />'] }})
216+
try {
217+
expect(fn).to.throw().with.property('message', message)
218+
} catch (err) {
219+
require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions = compilerSave
220+
throw err
221+
}
222+
require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions = compilerSave
223+
})
150224
})

0 commit comments

Comments
 (0)