From 3a476b3b10748407d4a0a80fed958868c91543d3 Mon Sep 17 00:00:00 2001
From: eddyerburgh
Date: Sat, 21 Jul 2018 06:36:57 +0100
Subject: [PATCH 01/13] Extend all extended components
---
packages/create-instance/create-instance.js | 49 +++++----------
.../extend-extended-components.js | 62 +++++++++++++++++++
.../extract-instance-options.js | 9 ++-
packages/test-utils/src/shallow-mount.js | 4 +-
test/specs/mounting-options/localVue.spec.js | 3 +-
test/specs/mounting-options/slots.spec.js | 12 ++--
test/specs/mounting-options/stubs.spec.js | 3 +-
test/specs/wrapper/emitted.spec.js | 56 +++++++++++++++++
8 files changed, 150 insertions(+), 48 deletions(-)
create mode 100644 packages/create-instance/extend-extended-components.js
diff --git a/packages/create-instance/create-instance.js b/packages/create-instance/create-instance.js
index 515ddeff9..f7e901579 100644
--- a/packages/create-instance/create-instance.js
+++ b/packages/create-instance/create-instance.js
@@ -4,13 +4,14 @@ import { createSlotVNodes } from './add-slots'
import addMocks from './add-mocks'
import { addEventLogger } from './log-events'
import { createComponentStubs } from 'shared/stub-components'
-import { throwError, warn, vueVersion } from 'shared/util'
+import { throwError, vueVersion } from 'shared/util'
import { compileTemplate } from 'shared/compile-template'
import extractInstanceOptions from './extract-instance-options'
import createFunctionalComponent from './create-functional-component'
import { componentNeedsCompiling } from 'shared/validators'
import { validateSlots } from './validate-slots'
import createScopedSlots from './create-scoped-slots'
+import { extendExtendedComponents } from './extend-extended-components'
export default function createInstance (
component: Component,
@@ -27,9 +28,6 @@ export default function createInstance (
// root instance when it's instantiated
//
// component options are the root components options
- const componentOptions = typeof component === 'function'
- ? component.extendOptions
- : component
const instanceOptions = extractInstanceOptions(options)
@@ -59,13 +57,15 @@ export default function createInstance (
// $FlowIgnore
options.stubs
)
- if (options.stubs) {
- instanceOptions.components = {
- ...instanceOptions.components,
- // $FlowIgnore
- ...stubComponents
- }
- }
+
+ extendExtendedComponents(
+ component,
+ _Vue,
+ options.logModifiedComponents,
+ instanceOptions.components
+ )
+
+ // stub components should override every component defined in a component
_Vue.mixin({
created () {
Object.assign(
@@ -74,26 +74,6 @@ export default function createInstance (
)
}
})
- Object.keys(componentOptions.components || {}).forEach(c => {
- if (
- componentOptions.components[c].extendOptions &&
- !instanceOptions.components[c]
- ) {
- 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(
- componentOptions.components[c]
- )
- }
- })
if (component.options) {
component.options._base = _Vue
@@ -103,9 +83,10 @@ export default function createInstance (
? component.extend(instanceOptions)
: _Vue.extend(component).extend(instanceOptions)
- Object.keys(instanceOptions.components || {}).forEach(key => {
- Constructor.component(key, instanceOptions.components[key])
- _Vue.component(key, instanceOptions.components[key])
+ Constructor._vueTestUtilsRoot = component
+
+ Object.keys(instanceOptions.components || {}).forEach(c => {
+ Constructor.component(c, instanceOptions.components[c])
})
if (options.slots) {
diff --git a/packages/create-instance/extend-extended-components.js b/packages/create-instance/extend-extended-components.js
new file mode 100644
index 000000000..1de7b5a39
--- /dev/null
+++ b/packages/create-instance/extend-extended-components.js
@@ -0,0 +1,62 @@
+import { warn } from 'shared/util'
+
+function createdFrom (extendOptions, componentOptions) {
+ while (extendOptions) {
+ if (extendOptions === componentOptions) {
+ return true
+ }
+ if (extendOptions._vueTestUtilsRoot === componentOptions) {
+ return true
+ }
+ extendOptions = extendOptions.extendOptions
+ }
+}
+
+export function extendExtendedComponents (
+ component,
+ _Vue,
+ logModifiedComponents,
+ excludedComponents = { }
+) {
+ const componentOptions = typeof component === 'function'
+ ? component.extendOptions
+ : component
+ const extendedComponents = Object.create(null)
+ Object.keys(componentOptions.components || {}).forEach(c => {
+ if (
+ componentOptions.components[c].extendOptions &&
+ !excludedComponents[c]
+ ) {
+ if (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.`
+ )
+ }
+ extendedComponents[c] = _Vue.extend(
+ componentOptions.components[c]
+ )
+ }
+ extendExtendedComponents(
+ componentOptions.components[c],
+ _Vue,
+ logModifiedComponents
+ )
+ })
+ if (extendedComponents) {
+ _Vue.mixin({
+ created () {
+ if (createdFrom(this.constructor, component)) {
+ Object.assign(
+ this.$options.components,
+ extendedComponents
+ )
+ }
+ }
+ })
+ }
+}
diff --git a/packages/create-instance/extract-instance-options.js b/packages/create-instance/extract-instance-options.js
index ea6afad72..05756612c 100644
--- a/packages/create-instance/extract-instance-options.js
+++ b/packages/create-instance/extract-instance-options.js
@@ -10,13 +10,18 @@ const MOUNTING_OPTIONS = [
'clone',
'attrs',
'listeners',
- 'propsData'
+ 'propsData',
+ 'logModifiedComponents',
+ 'sync'
]
export default function extractInstanceOptions (
options: Object
): Object {
- const instanceOptions = { ...options }
+ const instanceOptions = {
+ ...options,
+ _vueTestUtilsRootExtendOptions: true
+ }
MOUNTING_OPTIONS.forEach(mountingOption => {
delete instanceOptions[mountingOption]
})
diff --git a/packages/test-utils/src/shallow-mount.js b/packages/test-utils/src/shallow-mount.js
index d07869074..4ea41445e 100644
--- a/packages/test-utils/src/shallow-mount.js
+++ b/packages/test-utils/src/shallow-mount.js
@@ -14,7 +14,7 @@ export default function shallowMount (
component: Component,
options: Options = {}
): VueWrapper {
- const vue = options.localVue || Vue
+ const _Vue = options.localVue || Vue
// remove any recursive components added to the constructor
// in vm._init from previous tests
@@ -26,7 +26,7 @@ export default function shallowMount (
return mount(component, {
...options,
components: {
- ...createComponentStubsForGlobals(vue),
+ ...createComponentStubsForGlobals(_Vue),
...createComponentStubsForAll(component)
}
})
diff --git a/test/specs/mounting-options/localVue.spec.js b/test/specs/mounting-options/localVue.spec.js
index a64011d1a..d7d8c5e4e 100644
--- a/test/specs/mounting-options/localVue.spec.js
+++ b/test/specs/mounting-options/localVue.spec.js
@@ -61,7 +61,7 @@ describeWithMountingMethods('options.localVue', mountingMethod => {
}
})
- it('is applies to child extended components', () => {
+ it('is applied to child extended components', () => {
const ChildComponent = Vue.extend({
template: '{{$route.params}}
'
})
@@ -71,6 +71,7 @@ describeWithMountingMethods('options.localVue', mountingMethod => {
})
const localVue = createLocalVue()
localVue.prototype.$route = {}
+
mountingMethod(TestComponent, {
localVue
})
diff --git a/test/specs/mounting-options/slots.spec.js b/test/specs/mounting-options/slots.spec.js
index 19e338b05..87511334b 100644
--- a/test/specs/mounting-options/slots.spec.js
+++ b/test/specs/mounting-options/slots.spec.js
@@ -4,6 +4,7 @@ import ComponentWithSlots from '~resources/components/component-with-slots.vue'
import ComponentAsAClass from '~resources/components/component-as-a-class.vue'
import { describeWithMountingMethods, vueVersion } from '~resources/utils'
import { itSkipIf, itDoNotRunIf } from 'conditional-specs'
+import { createLocalVue } from '~vue/test-utils'
describeWithMountingMethods('options.slots', mountingMethod => {
it('mounts component with default slot if passed component in slot object', () => {
@@ -24,19 +25,16 @@ describeWithMountingMethods('options.slots', mountingMethod => {
const CustomComponent = {
render: h => h('time')
}
+ const localVue = createLocalVue()
+ localVue.component('custom-component', CustomComponent)
const TestComponent = {
- template: '
',
- components: {
- 'custom-component': CustomComponent
- }
+ template: '
'
}
const wrapper = mountingMethod(TestComponent, {
slots: {
default: ''
},
- components: {
- 'custom-component': CustomComponent
- }
+ localVue
})
if (mountingMethod.name === 'renderToString') {
expect(wrapper).contains('
')
})
it('handles components without a render function', () => {
From 9f60e66a1a41b8c60844715b7d2308f668ab19a8 Mon Sep 17 00:00:00 2001
From: eddyerburgh
Date: Sun, 5 Aug 2018 13:03:13 +0100
Subject: [PATCH 10/13] fix: do not extend component in Vue < 2.3
---
packages/create-instance/add-stubs.js | 2 --
packages/create-instance/create-instance.js | 6 +++++-
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/packages/create-instance/add-stubs.js b/packages/create-instance/add-stubs.js
index da4a0adf6..07c67057d 100644
--- a/packages/create-instance/add-stubs.js
+++ b/packages/create-instance/add-stubs.js
@@ -1,10 +1,8 @@
import { createComponentStubs } from 'shared/stub-components'
export function addStubs (component, stubs, _Vue) {
- // if the component has a name, add a stub
const stubComponents = createComponentStubs(
component.components,
- // $FlowIgnore
stubs
)
diff --git a/packages/create-instance/create-instance.js b/packages/create-instance/create-instance.js
index 516b9bbe9..71cdf3eb4 100644
--- a/packages/create-instance/create-instance.js
+++ b/packages/create-instance/create-instance.js
@@ -82,8 +82,12 @@ export default function createInstance (
}
// extend component from _Vue to add properties and mixins
- const Constructor = _Vue.extend(component).extend(instanceOptions)
+ // extend does not work correctly for sub class components in Vue < 2.2
+ const Constructor = typeof component === 'function' && vueVersion > 2.3
+ ? component.extend(instanceOptions)
+ : _Vue.extend(component).extend(instanceOptions)
+ // Keep reference to component mount was called with
Constructor._vueTestUtilsRoot = component
if (options.slots) {
From fe8f3390362650ad411b76b604dac67b66a0d466 Mon Sep 17 00:00:00 2001
From: eddyerburgh
Date: Sun, 5 Aug 2018 13:17:05 +0100
Subject: [PATCH 11/13] test: skip tests that rely on extended inheritance in
Vue < 2.3
---
test/specs/mount.spec.js | 19 ++++++++++---------
test/specs/mounting-options/stubs.spec.js | 2 +-
2 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/test/specs/mount.spec.js b/test/specs/mount.spec.js
index 25b24c235..db4fa2e53 100644
--- a/test/specs/mount.spec.js
+++ b/test/specs/mount.spec.js
@@ -58,16 +58,17 @@ describeRunIf(process.env.TEST_ENV !== 'node', 'mount', () => {
}
})
- it('handles propsData for extended components', () => {
- const prop1 = 'test'
- const TestComponent = Vue.extend(ComponentWithProps)
- const wrapper = mount(TestComponent, {
- propsData: {
- prop1
- }
+ itDoNotRunIf(vueVersion < 2.3,
+ 'handles propsData for extended components', () => {
+ const prop1 = 'test'
+ const TestComponent = Vue.extend(ComponentWithProps)
+ const wrapper = mount(TestComponent, {
+ propsData: {
+ prop1
+ }
+ })
+ expect(wrapper.text()).to.contain(prop1)
})
- expect(wrapper.text()).to.contain(prop1)
- })
it('handles uncompiled extended Vue component', () => {
const BaseComponent = {
diff --git a/test/specs/mounting-options/stubs.spec.js b/test/specs/mounting-options/stubs.spec.js
index a0dd9cf81..fb43f4b13 100644
--- a/test/specs/mounting-options/stubs.spec.js
+++ b/test/specs/mounting-options/stubs.spec.js
@@ -182,7 +182,7 @@ describeWithMountingMethods('options.stub', mountingMethod => {
})
itDoNotRunIf(
- mountingMethod.name === 'shallowMount',
+ mountingMethod.name === 'shallowMount' || vueVersion < 2.3,
'stubs nested components on extended components', () => {
const GrandChildComponent = {
template: ''
From a0d02522a5b6bd27587028fc96af462b1f64bd97 Mon Sep 17 00:00:00 2001
From: eddyerburgh
Date: Sun, 5 Aug 2018 15:53:37 +0100
Subject: [PATCH 12/13] fix: use correct check for vue version
---
packages/create-instance/create-instance.js | 2 +-
packages/shared/stub-components.js | 114 +++++++++-----------
2 files changed, 50 insertions(+), 66 deletions(-)
diff --git a/packages/create-instance/create-instance.js b/packages/create-instance/create-instance.js
index 71cdf3eb4..52d7c361f 100644
--- a/packages/create-instance/create-instance.js
+++ b/packages/create-instance/create-instance.js
@@ -83,7 +83,7 @@ export default function createInstance (
// extend component from _Vue to add properties and mixins
// extend does not work correctly for sub class components in Vue < 2.2
- const Constructor = typeof component === 'function' && vueVersion > 2.3
+ const Constructor = typeof component === 'function' && vueVersion < 2.3
? component.extend(instanceOptions)
: _Vue.extend(component).extend(instanceOptions)
diff --git a/packages/shared/stub-components.js b/packages/shared/stub-components.js
index ef2457a74..11f46f045 100644
--- a/packages/shared/stub-components.js
+++ b/packages/shared/stub-components.js
@@ -129,86 +129,70 @@ export function createBlankStub (
export function createComponentStubs (
originalComponents: Object = {},
- stubs: Stubs
+ stubs: Object
): Components {
const components = {}
if (!stubs) {
return components
}
- if (Array.isArray(stubs)) {
- stubs.forEach(stub => {
- if (stub === false) {
- return
- }
-
- if (typeof stub !== 'string') {
- throwError(`each item in an options.stubs array must be a ` + `string`)
- }
- const component = resolveComponent(originalComponents, stub)
+ Object.keys(stubs).forEach(stubName => {
+ const stub = stubs[stubName]
+ if (stub === false) {
+ return
+ }
- components[stub] = createBlankStub(component, stub)
- })
- } else {
- const stubsObject = (stubs: { [name: string]: Component | string | true })
- Object.keys(stubsObject).forEach(stubName => {
- const stub = stubsObject[stubName]
- if (stub === false) {
- return
- }
+ if (!isValidStub(stub)) {
+ throwError(
+ `options.stub values must be passed a string or ` + `component`
+ )
+ }
- if (!isValidStub(stub)) {
- throwError(
- `options.stub values must be passed a string or ` + `component`
- )
- }
+ if (stub === true) {
+ const component = resolveComponent(originalComponents, stubName)
+ components[stubName] = createBlankStub(component, stubName)
+ return
+ }
- if (stub === true) {
- const component = resolveComponent(originalComponents, stubName)
- components[stubName] = createBlankStub(component, stubName)
- return
- }
+ if (typeof stub !== 'string' && componentNeedsCompiling(stub)) {
+ compileTemplate(stub)
+ }
- if (typeof stub !== 'string' && componentNeedsCompiling(stub)) {
- compileTemplate(stub)
+ if (originalComponents[stubName]) {
+ // Remove cached constructor
+ delete originalComponents[stubName]._Ctor
+ if (typeof stub === 'string') {
+ components[stubName] = createStubFromString(
+ stub,
+ originalComponents[stubName],
+ stubName
+ )
+ } else {
+ const stubObject = (stub: Object)
+ components[stubName] = {
+ ...stubObject,
+ name: originalComponents[stubName].name
+ }
}
-
- if (originalComponents[stubName]) {
- // Remove cached constructor
- delete originalComponents[stubName]._Ctor
- if (typeof stub === 'string') {
- components[stubName] = createStubFromString(
- stub,
- originalComponents[stubName],
- stubName
+ } else {
+ if (typeof stub === 'string') {
+ if (!compileToFunctions) {
+ throwError(
+ `vueTemplateCompiler is undefined, you must pass ` +
+ `precompiled components if vue-template-compiler is ` +
+ `undefined`
)
- } else {
- const stubObject = (stub: Object)
- components[stubName] = {
- ...stubObject,
- name: originalComponents[stubName].name
- }
+ }
+ components[stubName] = {
+ ...compileToFunctions(stub)
}
} else {
- if (typeof stub === 'string') {
- if (!compileToFunctions) {
- throwError(
- `vueTemplateCompiler is undefined, you must pass ` +
- `precompiled components if vue-template-compiler is ` +
- `undefined`
- )
- }
- components[stubName] = {
- ...compileToFunctions(stub)
- }
- } else {
- const stubObject = (stub: Object)
- components[stubName] = {
- ...stubObject
- }
+ const stubObject = (stub: Object)
+ components[stubName] = {
+ ...stubObject
}
}
- })
- }
+ }
+ })
return components
}
From a30565b5bec106b2f43be835f24bc60d99e9d004 Mon Sep 17 00:00:00 2001
From: eddyerburgh
Date: Sun, 5 Aug 2018 15:59:27 +0100
Subject: [PATCH 13/13] refactor: extract compile from string logic
---
packages/shared/stub-components.js | 30 +++++++++++++-----------------
1 file changed, 13 insertions(+), 17 deletions(-)
diff --git a/packages/shared/stub-components.js b/packages/shared/stub-components.js
index 11f46f045..b203971b3 100644
--- a/packages/shared/stub-components.js
+++ b/packages/shared/stub-components.js
@@ -16,6 +16,17 @@ import {
} from './validators'
import { compileTemplate } from './compile-template'
+function compileFromString (str) {
+ if (!compileToFunctions) {
+ throwError(
+ `vueTemplateCompiler is undefined, you must pass ` +
+ `precompiled components if vue-template-compiler is ` +
+ `undefined`
+ )
+ }
+ return compileToFunctions(str)
+}
+
function isVueComponentStub (comp): boolean {
return comp && comp.template || isVueComponent(comp)
}
@@ -61,14 +72,6 @@ function createStubFromString (
originalComponent: Component,
name: string
): Component {
- if (!compileToFunctions) {
- throwError(
- `vueTemplateCompiler is undefined, you must pass ` +
- `precompiled components if vue-template-compiler is ` +
- `undefined`
- )
- }
-
if (templateContainsComponent(templateString, name)) {
throwError('options.stub cannot contain a circular reference')
}
@@ -79,7 +82,7 @@ function createStubFromString (
return {
...getCoreProperties(componentOptions),
- ...compileToFunctions(templateString)
+ ...compileFromString(templateString)
}
}
@@ -175,15 +178,8 @@ export function createComponentStubs (
}
} else {
if (typeof stub === 'string') {
- if (!compileToFunctions) {
- throwError(
- `vueTemplateCompiler is undefined, you must pass ` +
- `precompiled components if vue-template-compiler is ` +
- `undefined`
- )
- }
components[stubName] = {
- ...compileToFunctions(stub)
+ ...compileFromString(stub)
}
} else {
const stubObject = (stub: Object)