Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add parent in create-instance #586

Merged
merged 18 commits into from
Jun 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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