Skip to content

Commit

Permalink
fix: create stubs in render (#1038)
Browse files Browse the repository at this point in the history
Fixes #973
Fixes #994
Fixes #995


BREAKING CHANGE: The tag name rendered by snapshots will use the rendered component tag, rather than the registered component name
  • Loading branch information
eddyerburgh committed Nov 25, 2018
1 parent 7a1a49e commit e1fd705
Show file tree
Hide file tree
Showing 19 changed files with 327 additions and 309 deletions.
4 changes: 2 additions & 2 deletions package.json
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
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
@@ -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
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
@@ -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)
}

0 comments on commit e1fd705

Please sign in to comment.