Skip to content

Commit 0ab5a75

Browse files
authored
feat: add parent in create-instance (#586)
breaking change: removes templates from slots
1 parent 6a40f8a commit 0ab5a75

38 files changed

+290
-550
lines changed

flow/options.flow.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,10 @@ declare type Options = { // eslint-disable-line no-undef
1111
context?: Object,
1212
attrs?: Object,
1313
listeners?: Object,
14-
logModifiedComponents?: Boolean
14+
logModifiedComponents?: boolean,
15+
sync?: boolean
1516
}
17+
18+
declare type SlotValue = Component | string | Array<Component | string>
19+
20+
declare type SlotsObject = {[name: string]: SlotValue}

flow/vue.flow.js

-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,3 @@
44

55
declare type Component = Object | Function // eslint-disable-line no-undef
66
declare type VNode = Object // eslint-disable-line no-undef
7-
declare type SlotValue = Component | string | Array<Component> | Array<string>

flow/wrapper.flow.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,5 @@ declare interface BaseWrapper { // eslint-disable-line no-undef
3939

4040
declare type WrapperOptions = { // eslint-disable-line no-undef
4141
attachedToDocument: boolean,
42-
sync: boolean
42+
sync?: boolean
4343
}

package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,12 @@
6363
"rollup": "^0.58.2",
6464
"sinon": "^2.3.2",
6565
"sinon-chai": "^2.10.0",
66-
"vue": "2.5.13",
66+
"vue": "^2.5.16",
6767
"vue-class-component": "^6.1.2",
6868
"vue-loader": "^13.6.2",
6969
"vue-router": "^3.0.1",
70-
"vue-server-renderer": "2.5.13",
71-
"vue-template-compiler": "2.5.13",
70+
"vue-server-renderer": "^2.5.16",
71+
"vue-template-compiler": "^2.5.16",
7272
"vuepress": "^0.10.0",
7373
"vuepress-theme-vue": "^1.0.3",
7474
"vuetify": "^0.16.9",

packages/create-instance/add-attrs.js

-12
This file was deleted.

packages/create-instance/add-listeners.js

-12
This file was deleted.

packages/create-instance/add-provide.js

-13
This file was deleted.

packages/create-instance/add-scoped-slots.js

-17
This file was deleted.

packages/create-instance/add-slots.js

+24-50
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,34 @@
11
// @flow
22

33
import { compileToFunctions } from 'vue-template-compiler'
4-
import { throwError } from 'shared/util'
5-
import { validateSlots } from './validate-slots'
64

7-
// see https://github.com/vuejs/vue-test-utils/pull/274
8-
function createVNodes (vm: Component, slotValue: string) {
9-
const compiledResult = compileToFunctions(`<div>${slotValue}{{ }}</div>`)
10-
const _staticRenderFns = vm._renderProxy.$options.staticRenderFns
11-
vm._renderProxy.$options.staticRenderFns = compiledResult.staticRenderFns
12-
const elem = compiledResult.render.call(vm._renderProxy, vm.$createElement).children
13-
vm._renderProxy.$options.staticRenderFns = _staticRenderFns
14-
return elem
15-
}
16-
17-
function validateEnvironment (): void {
18-
if (!compileToFunctions) {
19-
throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined')
20-
}
21-
if (typeof window === 'undefined') {
22-
throwError('the slots string option does not support strings in server-test-uitls.')
23-
}
24-
}
5+
function createVNodesForSlot (
6+
h: Function,
7+
slotValue: SlotValue,
8+
name: string
9+
): Array<VNode> {
10+
const el = typeof slotValue === 'string'
11+
? compileToFunctions(slotValue)
12+
: slotValue
2513

26-
function addSlotToVm (vm: Component, slotName: string, slotValue: SlotValue): void {
27-
let elem
28-
if (typeof slotValue === 'string') {
29-
validateEnvironment()
30-
elem = createVNodes(vm, slotValue)
31-
} else {
32-
elem = vm.$createElement(slotValue)
33-
}
34-
if (Array.isArray(elem)) {
35-
if (Array.isArray(vm.$slots[slotName])) {
36-
vm.$slots[slotName] = [...vm.$slots[slotName], ...elem]
37-
} else {
38-
vm.$slots[slotName] = [...elem]
39-
}
40-
} else {
41-
if (Array.isArray(vm.$slots[slotName])) {
42-
vm.$slots[slotName].push(elem)
43-
} else {
44-
vm.$slots[slotName] = [elem]
45-
}
46-
}
14+
const vnode = h(el)
15+
vnode.data.slot = name
16+
return vnode
4717
}
4818

49-
export function addSlots (vm: Component, slots: Object): void {
50-
validateSlots(slots)
51-
Object.keys(slots).forEach((key) => {
52-
if (Array.isArray(slots[key])) {
53-
slots[key].forEach((slotValue) => {
54-
addSlotToVm(vm, key, slotValue)
55-
})
19+
export function createSlotVNodes (
20+
h: Function,
21+
slots: SlotsObject
22+
): Array<VNode> {
23+
return Object.keys(slots).reduce((acc, key) => {
24+
const content = slots[key]
25+
if (Array.isArray(content)) {
26+
const nodes = content.reduce((accInner, slotDef) => {
27+
return accInner.concat(createVNodesForSlot(h, slotDef, key))
28+
}, [])
29+
return acc.concat(nodes)
5630
} else {
57-
addSlotToVm(vm, key, slots[key])
31+
return acc.concat(createVNodesForSlot(h, content, key))
5832
}
59-
})
33+
}, [])
6034
}
+55-75
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,29 @@
11
// @flow
22

33
import Vue from 'vue'
4-
import { addSlots } from './add-slots'
5-
import { addScopedSlots } from './add-scoped-slots'
4+
import { createSlotVNodes } from './add-slots'
65
import addMocks from './add-mocks'
7-
import addAttrs from './add-attrs'
8-
import addListeners from './add-listeners'
9-
import addProvide from './add-provide'
106
import { addEventLogger } from './log-events'
117
import { createComponentStubs } from 'shared/stub-components'
12-
import { throwError, warn } from 'shared/util'
8+
import { throwError, warn, vueVersion } from 'shared/util'
139
import { compileTemplate } from 'shared/compile-template'
14-
import deleteoptions from './delete-mounting-options'
10+
import deleteMountingOptions from './delete-mounting-options'
1511
import createFunctionalComponent from './create-functional-component'
1612
import { componentNeedsCompiling } from 'shared/validators'
17-
18-
function isDestructuringSlotScope (slotScope: string): boolean {
19-
return slotScope[0] === '{' && slotScope[slotScope.length - 1] === '}'
20-
}
21-
22-
function getVueTemplateCompilerHelpers (proxy: Object): Object {
23-
const helpers = {}
24-
const names = ['_c', '_o', '_n', '_s', '_l', '_t', '_q', '_i', '_m', '_f', '_k', '_b', '_v', '_e', '_u', '_g']
25-
names.forEach((name) => {
26-
helpers[name] = proxy[name]
27-
})
28-
return helpers
29-
}
13+
import { validateSlots } from './validate-slots'
3014

3115
export default function createInstance (
3216
component: Component,
3317
options: Options,
34-
vue: Component
18+
_Vue: Component,
19+
elm?: Element
3520
): Component {
21+
// Remove cached constructor
22+
delete component._Ctor
23+
3624
if (options.mocks) {
37-
addMocks(options.mocks, vue)
25+
addMocks(options.mocks, _Vue)
3826
}
39-
4027
if ((component.options && component.options.functional) || component.functional) {
4128
component = createFunctionalComponent(component, options)
4229
} else if (options.context) {
@@ -45,23 +32,23 @@ export default function createInstance (
4532
)
4633
}
4734

48-
if (options.provide) {
49-
addProvide(component, options.provide, options)
50-
}
51-
5235
if (componentNeedsCompiling(component)) {
5336
compileTemplate(component)
5437
}
5538

56-
addEventLogger(vue)
39+
addEventLogger(_Vue)
40+
41+
const instanceOptions = {
42+
...options,
43+
propsData: {
44+
...options.propsData
45+
}
46+
}
5747

58-
const Constructor = (typeof component === 'function' && component.prototype instanceof Vue) ? component : vue.extend(component)
48+
deleteMountingOptions(instanceOptions)
5949

60-
const instanceOptions = { ...options, propsData: { ...options.propsData }}
61-
deleteoptions(instanceOptions)
6250
// $FlowIgnore
6351
const stubComponents = createComponentStubs(component.components, options.stubs)
64-
6552
if (options.stubs) {
6653
instanceOptions.components = {
6754
...instanceOptions.components,
@@ -76,60 +63,53 @@ export default function createInstance (
7663
if (options.logModifiedComponents) {
7764
warn(`an extended child component ${c} has been modified to ensure it has the correct instance properties. This means it is not possible to find the component with a component selector. To find the component, you must stub it manually using the stubs mounting option.`)
7865
}
79-
instanceOptions.components[c] = vue.extend(component.components[c])
66+
instanceOptions.components[c] = _Vue.extend(component.components[c])
8067
}
8168
})
8269

8370
Object.keys(stubComponents).forEach(c => {
84-
vue.component(c, stubComponents[c])
71+
_Vue.component(c, stubComponents[c])
8572
})
8673

87-
const vm = new Constructor(instanceOptions)
88-
89-
// Workaround for Vue < 2.5
90-
vm._staticTrees = []
74+
const Constructor = (typeof component === 'function' && component.prototype instanceof Vue)
75+
? component.extend(instanceOptions)
76+
: _Vue.extend(component).extend(instanceOptions)
9177

92-
addAttrs(vm, options.attrs)
93-
addListeners(vm, options.listeners)
78+
// const Constructor = _Vue.extend(component).extend(instanceOptions)
9479

95-
if (options.scopedSlots) {
96-
if (window.navigator.userAgent.match(/PhantomJS/i)) {
97-
throwError('the scopedSlots option does not support PhantomJS. Please use Puppeteer, or pass a component.')
98-
}
99-
const vueVersion = Number(`${Vue.version.split('.')[0]}.${Vue.version.split('.')[1]}`)
100-
if (vueVersion >= 2.5) {
101-
vm.$_vueTestUtils_scopedSlots = {}
102-
vm.$_vueTestUtils_slotScopes = {}
103-
const renderSlot = vm._renderProxy._t
104-
105-
vm._renderProxy._t = function (name, feedback, props, bindObject) {
106-
const scopedSlotFn = vm.$_vueTestUtils_scopedSlots[name]
107-
const slotScope = vm.$_vueTestUtils_slotScopes[name]
108-
if (scopedSlotFn) {
109-
props = { ...bindObject, ...props }
110-
const helpers = getVueTemplateCompilerHelpers(vm._renderProxy)
111-
let proxy = { ...helpers }
112-
if (isDestructuringSlotScope(slotScope)) {
113-
proxy = { ...helpers, ...props }
114-
} else {
115-
proxy[slotScope] = props
116-
}
117-
return scopedSlotFn.call(proxy)
118-
} else {
119-
return renderSlot.call(vm._renderProxy, name, feedback, props, bindObject)
120-
}
121-
}
80+
Object.keys(instanceOptions.components || {}).forEach(key => {
81+
Constructor.component(key, instanceOptions.components[key])
82+
_Vue.component(key, instanceOptions.components[key])
83+
})
12284

123-
// $FlowIgnore
124-
addScopedSlots(vm, options.scopedSlots)
125-
} else {
126-
throwError('the scopedSlots option is only supported in [email protected]+.')
127-
}
85+
if (options.slots) {
86+
validateSlots(options.slots)
12887
}
12988

130-
if (options.slots) {
131-
addSlots(vm, options.slots)
89+
// Objects are not resolved in extended components in Vue < 2.5
90+
// https://github.com/vuejs/vue/issues/6436
91+
if (options.provide &&
92+
typeof options.provide === 'object' &&
93+
vueVersion < 2.5
94+
) {
95+
const obj = { ...options.provide }
96+
options.provide = () => obj
13297
}
13398

134-
return vm
99+
const Parent = _Vue.extend({
100+
provide: options.provide,
101+
render (h) {
102+
const slots = options.slots
103+
? createSlotVNodes(h, options.slots)
104+
: undefined
105+
return h(Constructor, {
106+
ref: 'vm',
107+
props: options.propsData,
108+
on: options.listeners,
109+
attrs: options.attrs
110+
}, slots)
111+
}
112+
})
113+
114+
return new Parent()
135115
}

packages/create-instance/delete-mounting-options.js

+1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ export default function deleteMountingOptions (options) {
88
delete options.clone
99
delete options.attrs
1010
delete options.listeners
11+
delete options.propsData
1112
}

0 commit comments

Comments
 (0)