Skip to content

Commit e1fd705

Browse files
authored
fix: create stubs in render (#1038)
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
1 parent 7a1a49e commit e1fd705

19 files changed

+327
-309
lines changed

Diff for: package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"lint:fix": "npm run lint -- --fix",
1919
"release": "npm run build && npm run test:unit:only && lerna publish --conventional-commits -m \"chore(release): publish %s\" --cd-version prerelease",
2020
"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",
21-
"test:compat": "scripts/test-compat.sh",
21+
"test:compat": "scripts/test-compat-all.sh",
2222
"test:unit": "npm run build:test && npm run test:unit:only",
2323
"test:unit:only": "mocha-webpack --webpack-config test/setup/webpack.test.config.js test/specs --recursive --require test/setup/mocha.setup.js",
2424
"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",
@@ -64,7 +64,7 @@
6464
"sinon": "^2.3.2",
6565
"sinon-chai": "^2.10.0",
6666
"typescript": "^3.0.1",
67-
"vee-validate": "2.1.0-beta.5",
67+
"vee-validate": "^2.1.3",
6868
"vue": "2.5.16",
6969
"vue-class-component": "^6.1.2",
7070
"vue-loader": "^13.6.2",

Diff for: packages/create-instance/add-mocks.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ import $$Vue from 'vue'
33
import { warn } from 'shared/util'
44

55
export default function addMocks (
6-
mockedProperties: Object | false = {},
7-
Vue: Component
6+
_Vue: Component,
7+
mockedProperties: Object | false = {}
88
): void {
99
if (mockedProperties === false) {
1010
return
1111
}
1212
Object.keys(mockedProperties).forEach(key => {
1313
try {
1414
// $FlowIgnore
15-
Vue.prototype[key] = mockedProperties[key]
15+
_Vue.prototype[key] = mockedProperties[key]
1616
} catch (e) {
1717
warn(
1818
`could not overwrite property ${key}, this is ` +
@@ -21,6 +21,6 @@ export default function addMocks (
2121
)
2222
}
2323
// $FlowIgnore
24-
$$Vue.util.defineReactive(Vue, key, mockedProperties[key])
24+
$$Vue.util.defineReactive(_Vue, key, mockedProperties[key])
2525
})
2626
}

Diff for: packages/create-instance/add-stubs.js

+2-24
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,8 @@
1-
import {
2-
createStubsFromStubsObject,
3-
createStubFromComponent
4-
} from 'shared/create-component-stubs'
51
import { addHook } from './add-hook'
62

7-
export function addStubs (component, stubs, _Vue, shouldProxy) {
8-
const stubComponents = createStubsFromStubsObject(
9-
component.components,
10-
stubs
11-
)
12-
3+
export function addStubs (_Vue, stubComponents) {
134
function addStubComponentsMixin () {
14-
Object.assign(
15-
this.$options.components,
16-
stubComponents
17-
)
18-
if (typeof Proxy !== 'undefined' && shouldProxy) {
19-
this.$options.components = new Proxy(this.$options.components, {
20-
set (target, prop, value) {
21-
if (!target[prop]) {
22-
target[prop] = createStubFromComponent(value, prop)
23-
}
24-
return true
25-
}
26-
})
27-
}
5+
Object.assign(this.$options.components, stubComponents)
286
}
297

308
addHook(_Vue.options, 'beforeMount', addStubComponentsMixin)

Diff for: packages/create-instance/create-instance.js

+13-27
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ import {
99
compileTemplate,
1010
compileTemplateForSlots
1111
} from 'shared/compile-template'
12-
import { isRequiredComponent } from 'shared/validators'
1312
import extractInstanceOptions from './extract-instance-options'
1413
import createFunctionalComponent from './create-functional-component'
1514
import { componentNeedsCompiling, isPlainObject } from 'shared/validators'
1615
import { validateSlots } from './validate-slots'
1716
import createScopedSlots from './create-scoped-slots'
18-
import { extendExtendedComponents } from './extend-extended-components'
17+
import { createStubsFromStubsObject } from 'shared/create-component-stubs'
18+
import { patchRender } from './patch-render'
1919

2020
function vueExtendUnsupportedOption (option: string) {
2121
return `options.${option} is not supported for ` +
@@ -56,10 +56,16 @@ export default function createInstance (
5656
// instance options are options that are passed to the
5757
// root instance when it's instantiated
5858
const instanceOptions = extractInstanceOptions(options)
59+
const stubComponentsObject = createStubsFromStubsObject(
60+
component.components,
61+
// $FlowIgnore
62+
options.stubs
63+
)
5964

6065
addEventLogger(_Vue)
61-
addMocks(options.mocks, _Vue)
62-
addStubs(component, options.stubs, _Vue, options.shouldProxy)
66+
addMocks(_Vue, options.mocks)
67+
addStubs(_Vue, stubComponentsObject)
68+
patchRender(_Vue, stubComponentsObject, options.shouldProxy)
6369

6470
if (
6571
(component.options && component.options.functional) ||
@@ -77,29 +83,6 @@ export default function createInstance (
7783
compileTemplate(component)
7884
}
7985

80-
// Replace globally registered components with components extended
81-
// from localVue.
82-
// Vue version must be 2.3 or greater, because of a bug resolving
83-
// extended constructor options (https://github.com/vuejs/vue/issues/4976)
84-
if (vueVersion > 2.2) {
85-
for (const c in _Vue.options.components) {
86-
if (!isRequiredComponent(c)) {
87-
const comp = _Vue.options.components[c]
88-
const options = comp.options ? comp.options : comp
89-
const extendedComponent = _Vue.extend(options)
90-
extendedComponent.options.$_vueTestUtils_original = comp
91-
_Vue.component(c, extendedComponent)
92-
}
93-
}
94-
}
95-
96-
extendExtendedComponents(
97-
component,
98-
_Vue,
99-
options.logModifiedComponents,
100-
instanceOptions.components
101-
)
102-
10386
if (component.options) {
10487
component.options._base = _Vue
10588
}
@@ -112,6 +95,7 @@ export default function createInstance (
11295

11396
// used to identify extended component using constructor
11497
Constructor.options.$_vueTestUtils_original = component
98+
11599
if (options.slots) {
116100
compileTemplateForSlots(options.slots)
117101
// validate slots outside of the createSlots function so
@@ -143,6 +127,8 @@ export default function createInstance (
143127

144128
const parentComponentOptions = options.parentComponent || {}
145129
parentComponentOptions.provide = options.provide
130+
parentComponentOptions.$_doNotStubChildren = true
131+
146132
parentComponentOptions.render = function (h) {
147133
const slots = options.slots
148134
? createSlotVNodes(this, options.slots)

Diff for: packages/create-instance/extend-extended-components.js

-95
This file was deleted.

Diff for: packages/create-instance/patch-render.js

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { createStubFromComponent } from 'shared/create-component-stubs'
2+
import { resolveComponent, semVerGreaterThan } from 'shared/util'
3+
import { isReservedTag } from 'shared/validators'
4+
import { addHook } from './add-hook'
5+
import Vue from 'vue'
6+
7+
const isWhitelisted = (el, whitelist) => resolveComponent(el, whitelist)
8+
const isAlreadyStubbed = (el, stubs) => stubs.has(el)
9+
const isDynamicComponent = cmp => typeof cmp === 'function' && !cmp.cid
10+
11+
const CREATE_ELEMENT_ALIAS = semVerGreaterThan(Vue.version, '2.1.5')
12+
? '_c'
13+
: '_h'
14+
const LIFECYCLE_HOOK = semVerGreaterThan(Vue.version, '2.1.8')
15+
? 'beforeCreate'
16+
: 'beforeMount'
17+
18+
function shouldExtend (component, _Vue) {
19+
return (
20+
(typeof component === 'function' && !isDynamicComponent(component)) ||
21+
(component && component.extends)
22+
)
23+
}
24+
25+
function extend (component, _Vue) {
26+
const stub = _Vue.extend(component.options)
27+
stub.options.$_vueTestUtils_original = component
28+
return stub
29+
}
30+
31+
function createStubIfNeeded (shouldStub, component, _Vue, el) {
32+
if (shouldStub) {
33+
return createStubFromComponent(component || {}, el)
34+
}
35+
36+
if (shouldExtend(component, _Vue)) {
37+
return extend(component, _Vue)
38+
}
39+
}
40+
41+
function shouldNotBeStubbed (el, whitelist, modifiedComponents) {
42+
return (
43+
(typeof el === 'string' && isReservedTag(el)) ||
44+
isWhitelisted(el, whitelist) ||
45+
isAlreadyStubbed(el, modifiedComponents)
46+
)
47+
}
48+
49+
function isConstructor (el) {
50+
return typeof el === 'function'
51+
}
52+
53+
export function patchRender (_Vue, stubs, stubAllComponents) {
54+
// This mixin patches vm.$createElement so that we can stub all components
55+
// before they are rendered in shallow mode. We also need to ensure that
56+
// component constructors were created from the _Vue constructor. If not,
57+
// we must replace them with components created from the _Vue constructor
58+
// before calling the original $createElement. This ensures that components
59+
// have the correct instance properties and stubs when they are rendered.
60+
function patchRenderMixin () {
61+
const vm = this
62+
63+
if (vm.$options.$_doNotStubChildren || vm._isFunctionalContainer) {
64+
return
65+
}
66+
67+
const modifiedComponents = new Set()
68+
const originalCreateElement = vm.$createElement
69+
const originalComponents = vm.$options.components
70+
71+
const createElement = (el, ...args) => {
72+
if (shouldNotBeStubbed(el, stubs, modifiedComponents)) {
73+
return originalCreateElement(el, ...args)
74+
}
75+
76+
if (isConstructor(el)) {
77+
if (stubAllComponents) {
78+
const stub = createStubFromComponent(el, el.name || 'anonymous')
79+
return originalCreateElement(stub, ...args)
80+
}
81+
82+
const Constructor = shouldExtend(el, _Vue) ? extend(el, _Vue) : el
83+
84+
return originalCreateElement(Constructor, ...args)
85+
}
86+
87+
if (typeof el === 'string') {
88+
let original = resolveComponent(el, originalComponents)
89+
90+
if (
91+
original &&
92+
original.options &&
93+
original.options.$_vueTestUtils_original
94+
) {
95+
original = original.options.$_vueTestUtils_original
96+
}
97+
98+
if (isDynamicComponent(original)) {
99+
return originalCreateElement(el, ...args)
100+
}
101+
102+
const stub = createStubIfNeeded(stubAllComponents, original, _Vue, el)
103+
104+
if (stub) {
105+
vm.$options.components = {
106+
...vm.$options.components,
107+
[el]: stub
108+
}
109+
modifiedComponents.add(el)
110+
}
111+
}
112+
113+
return originalCreateElement(el, ...args)
114+
}
115+
116+
vm[CREATE_ELEMENT_ALIAS] = createElement
117+
vm.$createElement = createElement
118+
}
119+
120+
addHook(_Vue.options, LIFECYCLE_HOOK, patchRenderMixin)
121+
}

0 commit comments

Comments
 (0)