Skip to content

Commit

Permalink
feat: add parent in create-instance (#586)
Browse files Browse the repository at this point in the history
breaking change: removes templates from slots
  • Loading branch information
eddyerburgh authored Jun 1, 2018
1 parent 6a40f8a commit 0ab5a75
Show file tree
Hide file tree
Showing 38 changed files with 290 additions and 550 deletions.
7 changes: 6 additions & 1 deletion flow/options.flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,10 @@ declare type Options = { // eslint-disable-line no-undef
context?: Object,
attrs?: Object,
listeners?: Object,
logModifiedComponents?: Boolean
logModifiedComponents?: boolean,
sync?: boolean
}

declare type SlotValue = Component | string | Array<Component | string>

declare type SlotsObject = {[name: string]: SlotValue}
1 change: 0 additions & 1 deletion flow/vue.flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,3 @@

declare type Component = Object | Function // eslint-disable-line no-undef
declare type VNode = Object // eslint-disable-line no-undef
declare type SlotValue = Component | string | Array<Component> | Array<string>
2 changes: 1 addition & 1 deletion flow/wrapper.flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@ declare interface BaseWrapper { // eslint-disable-line no-undef

declare type WrapperOptions = { // eslint-disable-line no-undef
attachedToDocument: boolean,
sync: boolean
sync?: boolean
}
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@
"rollup": "^0.58.2",
"sinon": "^2.3.2",
"sinon-chai": "^2.10.0",
"vue": "2.5.13",
"vue": "^2.5.16",
"vue-class-component": "^6.1.2",
"vue-loader": "^13.6.2",
"vue-router": "^3.0.1",
"vue-server-renderer": "2.5.13",
"vue-template-compiler": "2.5.13",
"vue-server-renderer": "^2.5.16",
"vue-template-compiler": "^2.5.16",
"vuepress": "^0.10.0",
"vuepress-theme-vue": "^1.0.3",
"vuetify": "^0.16.9",
Expand Down
12 changes: 0 additions & 12 deletions packages/create-instance/add-attrs.js

This file was deleted.

12 changes: 0 additions & 12 deletions packages/create-instance/add-listeners.js

This file was deleted.

13 changes: 0 additions & 13 deletions packages/create-instance/add-provide.js

This file was deleted.

17 changes: 0 additions & 17 deletions packages/create-instance/add-scoped-slots.js

This file was deleted.

74 changes: 24 additions & 50 deletions packages/create-instance/add-slots.js
Original file line number Diff line number Diff line change
@@ -1,60 +1,34 @@
// @flow

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

// see https://github.com/vuejs/vue-test-utils/pull/274
function createVNodes (vm: Component, slotValue: string) {
const compiledResult = compileToFunctions(`<div>${slotValue}{{ }}</div>`)
const _staticRenderFns = vm._renderProxy.$options.staticRenderFns
vm._renderProxy.$options.staticRenderFns = compiledResult.staticRenderFns
const elem = compiledResult.render.call(vm._renderProxy, vm.$createElement).children
vm._renderProxy.$options.staticRenderFns = _staticRenderFns
return elem
}

function validateEnvironment (): void {
if (!compileToFunctions) {
throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined')
}
if (typeof window === 'undefined') {
throwError('the slots string option does not support strings in server-test-uitls.')
}
}
function createVNodesForSlot (
h: Function,
slotValue: SlotValue,
name: string
): Array<VNode> {
const el = typeof slotValue === 'string'
? compileToFunctions(slotValue)
: slotValue

function addSlotToVm (vm: Component, slotName: string, slotValue: SlotValue): void {
let elem
if (typeof slotValue === 'string') {
validateEnvironment()
elem = createVNodes(vm, slotValue)
} else {
elem = vm.$createElement(slotValue)
}
if (Array.isArray(elem)) {
if (Array.isArray(vm.$slots[slotName])) {
vm.$slots[slotName] = [...vm.$slots[slotName], ...elem]
} else {
vm.$slots[slotName] = [...elem]
}
} else {
if (Array.isArray(vm.$slots[slotName])) {
vm.$slots[slotName].push(elem)
} else {
vm.$slots[slotName] = [elem]
}
}
const vnode = h(el)
vnode.data.slot = name
return vnode
}

export function addSlots (vm: Component, slots: Object): void {
validateSlots(slots)
Object.keys(slots).forEach((key) => {
if (Array.isArray(slots[key])) {
slots[key].forEach((slotValue) => {
addSlotToVm(vm, key, slotValue)
})
export function createSlotVNodes (
h: Function,
slots: SlotsObject
): Array<VNode> {
return Object.keys(slots).reduce((acc, key) => {
const content = slots[key]
if (Array.isArray(content)) {
const nodes = content.reduce((accInner, slotDef) => {
return accInner.concat(createVNodesForSlot(h, slotDef, key))
}, [])
return acc.concat(nodes)
} else {
addSlotToVm(vm, key, slots[key])
return acc.concat(createVNodesForSlot(h, content, key))
}
})
}, [])
}
130 changes: 55 additions & 75 deletions packages/create-instance/create-instance.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,29 @@
// @flow

import Vue from 'vue'
import { addSlots } from './add-slots'
import { addScopedSlots } from './add-scoped-slots'
import { createSlotVNodes } from './add-slots'
import addMocks from './add-mocks'
import addAttrs from './add-attrs'
import addListeners from './add-listeners'
import addProvide from './add-provide'
import { addEventLogger } from './log-events'
import { createComponentStubs } from 'shared/stub-components'
import { throwError, warn } from 'shared/util'
import { throwError, warn, vueVersion } from 'shared/util'
import { compileTemplate } from 'shared/compile-template'
import deleteoptions from './delete-mounting-options'
import deleteMountingOptions from './delete-mounting-options'
import createFunctionalComponent from './create-functional-component'
import { componentNeedsCompiling } from 'shared/validators'

function isDestructuringSlotScope (slotScope: string): boolean {
return slotScope[0] === '{' && slotScope[slotScope.length - 1] === '}'
}

function getVueTemplateCompilerHelpers (proxy: Object): Object {
const helpers = {}
const names = ['_c', '_o', '_n', '_s', '_l', '_t', '_q', '_i', '_m', '_f', '_k', '_b', '_v', '_e', '_u', '_g']
names.forEach((name) => {
helpers[name] = proxy[name]
})
return helpers
}
import { validateSlots } from './validate-slots'

export default function createInstance (
component: Component,
options: Options,
vue: Component
_Vue: Component,
elm?: Element
): Component {
// Remove cached constructor
delete component._Ctor

if (options.mocks) {
addMocks(options.mocks, vue)
addMocks(options.mocks, _Vue)
}

if ((component.options && component.options.functional) || component.functional) {
component = createFunctionalComponent(component, options)
} else if (options.context) {
Expand All @@ -45,23 +32,23 @@ export default function createInstance (
)
}

if (options.provide) {
addProvide(component, options.provide, options)
}

if (componentNeedsCompiling(component)) {
compileTemplate(component)
}

addEventLogger(vue)
addEventLogger(_Vue)

const instanceOptions = {
...options,
propsData: {
...options.propsData
}
}

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

const instanceOptions = { ...options, propsData: { ...options.propsData }}
deleteoptions(instanceOptions)
// $FlowIgnore
const stubComponents = createComponentStubs(component.components, options.stubs)

if (options.stubs) {
instanceOptions.components = {
...instanceOptions.components,
Expand All @@ -76,60 +63,53 @@ export default function createInstance (
if (options.logModifiedComponents) {
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.`)
}
instanceOptions.components[c] = vue.extend(component.components[c])
instanceOptions.components[c] = _Vue.extend(component.components[c])
}
})

Object.keys(stubComponents).forEach(c => {
vue.component(c, stubComponents[c])
_Vue.component(c, stubComponents[c])
})

const vm = new Constructor(instanceOptions)

// Workaround for Vue < 2.5
vm._staticTrees = []
const Constructor = (typeof component === 'function' && component.prototype instanceof Vue)
? component.extend(instanceOptions)
: _Vue.extend(component).extend(instanceOptions)

addAttrs(vm, options.attrs)
addListeners(vm, options.listeners)
// const Constructor = _Vue.extend(component).extend(instanceOptions)

if (options.scopedSlots) {
if (window.navigator.userAgent.match(/PhantomJS/i)) {
throwError('the scopedSlots option does not support PhantomJS. Please use Puppeteer, or pass a component.')
}
const vueVersion = Number(`${Vue.version.split('.')[0]}.${Vue.version.split('.')[1]}`)
if (vueVersion >= 2.5) {
vm.$_vueTestUtils_scopedSlots = {}
vm.$_vueTestUtils_slotScopes = {}
const renderSlot = vm._renderProxy._t

vm._renderProxy._t = function (name, feedback, props, bindObject) {
const scopedSlotFn = vm.$_vueTestUtils_scopedSlots[name]
const slotScope = vm.$_vueTestUtils_slotScopes[name]
if (scopedSlotFn) {
props = { ...bindObject, ...props }
const helpers = getVueTemplateCompilerHelpers(vm._renderProxy)
let proxy = { ...helpers }
if (isDestructuringSlotScope(slotScope)) {
proxy = { ...helpers, ...props }
} else {
proxy[slotScope] = props
}
return scopedSlotFn.call(proxy)
} else {
return renderSlot.call(vm._renderProxy, name, feedback, props, bindObject)
}
}
Object.keys(instanceOptions.components || {}).forEach(key => {
Constructor.component(key, instanceOptions.components[key])
_Vue.component(key, instanceOptions.components[key])
})

// $FlowIgnore
addScopedSlots(vm, options.scopedSlots)
} else {
throwError('the scopedSlots option is only supported in [email protected]+.')
}
if (options.slots) {
validateSlots(options.slots)
}

if (options.slots) {
addSlots(vm, options.slots)
// Objects are not resolved in extended components in Vue < 2.5
// https://github.com/vuejs/vue/issues/6436
if (options.provide &&
typeof options.provide === 'object' &&
vueVersion < 2.5
) {
const obj = { ...options.provide }
options.provide = () => obj
}

return vm
const Parent = _Vue.extend({
provide: options.provide,
render (h) {
const slots = options.slots
? createSlotVNodes(h, options.slots)
: undefined
return h(Constructor, {
ref: 'vm',
props: options.propsData,
on: options.listeners,
attrs: options.attrs
}, slots)
}
})

return new Parent()
}
1 change: 1 addition & 0 deletions packages/create-instance/delete-mounting-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export default function deleteMountingOptions (options) {
delete options.clone
delete options.attrs
delete options.listeners
delete options.propsData
}
Loading

0 comments on commit 0ab5a75

Please sign in to comment.