Skip to content

feat: create stubs in createElement #1038

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

Merged
merged 17 commits into from
Nov 25, 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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"lint:fix": "npm run lint -- --fix",
"release": "npm run build && npm run test:unit:only && lerna publish --conventional-commits -m \"chore(release): publish %s\" --cd-version prerelease",
"test": "npm run lint && npm run lint:docs && npm run flow && npm run test:types && npm run test:unit && npm run test:unit:karma && npm run test:unit:node",
"test:compat": "scripts/test-compat.sh",
"test:compat": "scripts/test-compat-all.sh",
"test:unit": "npm run build:test && npm run test:unit:only",
"test:unit:only": "mocha-webpack --webpack-config test/setup/webpack.test.config.js test/specs --recursive --require test/setup/mocha.setup.js",
"test:unit:debug": "npm run build:test && node --inspect-brk node_modules/.bin/mocha-webpack --webpack-config test/setup/webpack.test.config.js test/specs --recursive --require test/setup/mocha.setup.js",
Expand Down Expand Up @@ -64,7 +64,7 @@
"sinon": "^2.3.2",
"sinon-chai": "^2.10.0",
"typescript": "^3.0.1",
"vee-validate": "2.1.0-beta.5",
"vee-validate": "^2.1.3",
"vue": "2.5.16",
"vue-class-component": "^6.1.2",
"vue-loader": "^13.6.2",
Expand Down
8 changes: 4 additions & 4 deletions packages/create-instance/add-mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import $$Vue from 'vue'
import { warn } from 'shared/util'

export default function addMocks (
mockedProperties: Object | false = {},
Vue: Component
_Vue: Component,
mockedProperties: Object | false = {}
): void {
if (mockedProperties === false) {
return
}
Object.keys(mockedProperties).forEach(key => {
try {
// $FlowIgnore
Vue.prototype[key] = mockedProperties[key]
_Vue.prototype[key] = mockedProperties[key]
} catch (e) {
warn(
`could not overwrite property ${key}, this is ` +
Expand All @@ -21,6 +21,6 @@ export default function addMocks (
)
}
// $FlowIgnore
$$Vue.util.defineReactive(Vue, key, mockedProperties[key])
$$Vue.util.defineReactive(_Vue, key, mockedProperties[key])
})
}
26 changes: 2 additions & 24 deletions packages/create-instance/add-stubs.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,8 @@
import {
createStubsFromStubsObject,
createStubFromComponent
} from 'shared/create-component-stubs'
import { addHook } from './add-hook'

export function addStubs (component, stubs, _Vue, shouldProxy) {
const stubComponents = createStubsFromStubsObject(
component.components,
stubs
)

export function addStubs (_Vue, stubComponents) {
function addStubComponentsMixin () {
Object.assign(
this.$options.components,
stubComponents
)
if (typeof Proxy !== 'undefined' && shouldProxy) {
this.$options.components = new Proxy(this.$options.components, {
set (target, prop, value) {
if (!target[prop]) {
target[prop] = createStubFromComponent(value, prop)
}
return true
}
})
}
Object.assign(this.$options.components, stubComponents)
}

addHook(_Vue.options, 'beforeMount', addStubComponentsMixin)
Expand Down
40 changes: 13 additions & 27 deletions packages/create-instance/create-instance.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import {
compileTemplate,
compileTemplateForSlots
} from 'shared/compile-template'
import { isRequiredComponent } from 'shared/validators'
import extractInstanceOptions from './extract-instance-options'
import createFunctionalComponent from './create-functional-component'
import { componentNeedsCompiling, isPlainObject } from 'shared/validators'
import { validateSlots } from './validate-slots'
import createScopedSlots from './create-scoped-slots'
import { extendExtendedComponents } from './extend-extended-components'
import { createStubsFromStubsObject } from 'shared/create-component-stubs'
import { patchRender } from './patch-render'

function vueExtendUnsupportedOption (option: string) {
return `options.${option} is not supported for ` +
Expand Down Expand Up @@ -56,10 +56,16 @@ export default function createInstance (
// instance options are options that are passed to the
// root instance when it's instantiated
const instanceOptions = extractInstanceOptions(options)
const stubComponentsObject = createStubsFromStubsObject(
component.components,
// $FlowIgnore
options.stubs
)

addEventLogger(_Vue)
addMocks(options.mocks, _Vue)
addStubs(component, options.stubs, _Vue, options.shouldProxy)
addMocks(_Vue, options.mocks)
addStubs(_Vue, stubComponentsObject)
patchRender(_Vue, stubComponentsObject, options.shouldProxy)

if (
(component.options && component.options.functional) ||
Expand All @@ -77,29 +83,6 @@ export default function createInstance (
compileTemplate(component)
}

// Replace globally registered components with components extended
// from localVue.
// Vue version must be 2.3 or greater, because of a bug resolving
// extended constructor options (https://github.com/vuejs/vue/issues/4976)
if (vueVersion > 2.2) {
for (const c in _Vue.options.components) {
if (!isRequiredComponent(c)) {
const comp = _Vue.options.components[c]
const options = comp.options ? comp.options : comp
const extendedComponent = _Vue.extend(options)
extendedComponent.options.$_vueTestUtils_original = comp
_Vue.component(c, extendedComponent)
}
}
}

extendExtendedComponents(
component,
_Vue,
options.logModifiedComponents,
instanceOptions.components
)

if (component.options) {
component.options._base = _Vue
}
Expand All @@ -112,6 +95,7 @@ export default function createInstance (

// used to identify extended component using constructor
Constructor.options.$_vueTestUtils_original = component

if (options.slots) {
compileTemplateForSlots(options.slots)
// validate slots outside of the createSlots function so
Expand Down Expand Up @@ -143,6 +127,8 @@ export default function createInstance (

const parentComponentOptions = options.parentComponent || {}
parentComponentOptions.provide = options.provide
parentComponentOptions.$_doNotStubChildren = true

parentComponentOptions.render = function (h) {
const slots = options.slots
? createSlotVNodes(this, options.slots)
Expand Down
95 changes: 0 additions & 95 deletions packages/create-instance/extend-extended-components.js

This file was deleted.

121 changes: 121 additions & 0 deletions packages/create-instance/patch-render.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { createStubFromComponent } from 'shared/create-component-stubs'
import { resolveComponent, semVerGreaterThan } from 'shared/util'
import { isReservedTag } from 'shared/validators'
import { addHook } from './add-hook'
import Vue from 'vue'

const isWhitelisted = (el, whitelist) => resolveComponent(el, whitelist)
const isAlreadyStubbed = (el, stubs) => stubs.has(el)
const isDynamicComponent = cmp => typeof cmp === 'function' && !cmp.cid

const CREATE_ELEMENT_ALIAS = semVerGreaterThan(Vue.version, '2.1.5')
? '_c'
: '_h'
const LIFECYCLE_HOOK = semVerGreaterThan(Vue.version, '2.1.8')
? 'beforeCreate'
: 'beforeMount'

function shouldExtend (component, _Vue) {
return (
(typeof component === 'function' && !isDynamicComponent(component)) ||
(component && component.extends)
)
}

function extend (component, _Vue) {
const stub = _Vue.extend(component.options)
stub.options.$_vueTestUtils_original = component
return stub
}

function createStubIfNeeded (shouldStub, component, _Vue, el) {
if (shouldStub) {
return createStubFromComponent(component || {}, el)
}

if (shouldExtend(component, _Vue)) {
return extend(component, _Vue)
}
}

function shouldNotBeStubbed (el, whitelist, modifiedComponents) {
return (
(typeof el === 'string' && isReservedTag(el)) ||
isWhitelisted(el, whitelist) ||
isAlreadyStubbed(el, modifiedComponents)
)
}

function isConstructor (el) {
return typeof el === 'function'
}

export function patchRender (_Vue, stubs, stubAllComponents) {
// This mixin patches vm.$createElement so that we can stub all components
// before they are rendered in shallow mode. We also need to ensure that
// component constructors were created from the _Vue constructor. If not,
// we must replace them with components created from the _Vue constructor
// before calling the original $createElement. This ensures that components
// have the correct instance properties and stubs when they are rendered.
function patchRenderMixin () {
const vm = this

if (vm.$options.$_doNotStubChildren || vm._isFunctionalContainer) {
return
}

const modifiedComponents = new Set()
const originalCreateElement = vm.$createElement
const originalComponents = vm.$options.components

const createElement = (el, ...args) => {
if (shouldNotBeStubbed(el, stubs, modifiedComponents)) {
return originalCreateElement(el, ...args)
}

if (isConstructor(el)) {
if (stubAllComponents) {
const stub = createStubFromComponent(el, el.name || 'anonymous')
return originalCreateElement(stub, ...args)
}

const Constructor = shouldExtend(el, _Vue) ? extend(el, _Vue) : el

return originalCreateElement(Constructor, ...args)
}

if (typeof el === 'string') {
let original = resolveComponent(el, originalComponents)

if (
original &&
original.options &&
original.options.$_vueTestUtils_original
) {
original = original.options.$_vueTestUtils_original
}

if (isDynamicComponent(original)) {
return originalCreateElement(el, ...args)
}

const stub = createStubIfNeeded(stubAllComponents, original, _Vue, el)

if (stub) {
vm.$options.components = {
...vm.$options.components,
[el]: stub
}
modifiedComponents.add(el)
}
}

return originalCreateElement(el, ...args)
}

vm[CREATE_ELEMENT_ALIAS] = createElement
vm.$createElement = createElement
}

addHook(_Vue.options, LIFECYCLE_HOOK, patchRenderMixin)
}
Loading