Skip to content

Commit 1f160df

Browse files
authored
refactor: extract validation logic (#1108)
1 parent b461900 commit 1f160df

File tree

10 files changed

+148
-162
lines changed

10 files changed

+148
-162
lines changed

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

-51
This file was deleted.

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

+40-92
Original file line numberDiff line numberDiff line change
@@ -4,59 +4,58 @@ import { createSlotVNodes } from './create-slot-vnodes'
44
import addMocks from './add-mocks'
55
import { addEventLogger } from './log-events'
66
import { addStubs } from './add-stubs'
7-
import { throwError } from 'shared/util'
8-
import { VUE_VERSION } from 'shared/consts'
9-
import {
10-
compileTemplate,
11-
compileTemplateForSlots
12-
} from 'shared/compile-template'
7+
import { compileTemplate } from 'shared/compile-template'
138
import extractInstanceOptions from './extract-instance-options'
14-
import createFunctionalComponent from './create-functional-component'
15-
import { componentNeedsCompiling, isPlainObject } from 'shared/validators'
16-
import { validateSlots } from './validate-slots'
9+
import {
10+
componentNeedsCompiling,
11+
isConstructor
12+
} from 'shared/validators'
1713
import createScopedSlots from './create-scoped-slots'
1814
import { createStubsFromStubsObject } from './create-component-stubs'
1915
import { patchCreateElement } from './patch-create-element'
20-
import { isConstructor } from 'shared/validators'
2116

22-
function vueExtendUnsupportedOption (option: string) {
23-
return `options.${option} is not supported for ` +
24-
`components created with Vue.extend in Vue < 2.3. ` +
25-
`You can set ${option} to false to mount the component.`
17+
function createContext (options, scopedSlots) {
18+
const on = {
19+
...(options.context && options.context.on),
20+
...options.listeners
21+
}
22+
return {
23+
attrs: {
24+
...options.attrs,
25+
// pass as attrs so that inheritAttrs works correctly
26+
// propsData should take precedence over attrs
27+
...options.propsData
28+
},
29+
...(options.context || {}),
30+
on,
31+
scopedSlots
32+
}
2633
}
2734

28-
// these options aren't supported if Vue is version < 2.3
29-
// for components using Vue.extend. This is due to a bug
30-
// that means the mixins we use to add properties are not applied
31-
// correctly
32-
const UNSUPPORTED_VERSION_OPTIONS = [
33-
'mocks',
34-
'stubs',
35-
'localVue'
36-
]
35+
function createChildren (vm, h, { slots, context }) {
36+
const slotVNodes = slots
37+
? createSlotVNodes(vm, slots)
38+
: undefined
39+
return (
40+
context &&
41+
context.children &&
42+
context.children.map(x => (typeof x === 'function' ? x(h) : x))
43+
) || slotVNodes
44+
}
3745

3846
export default function createInstance (
3947
component: Component,
4048
options: Options,
4149
_Vue: Component
4250
): Component {
43-
if (
44-
VUE_VERSION < 2.3 && isConstructor(component)
45-
) {
46-
UNSUPPORTED_VERSION_OPTIONS.forEach((option) => {
47-
if (options[option]) {
48-
throwError(vueExtendUnsupportedOption(option))
49-
}
50-
})
51-
}
52-
53-
let componentOptions = isConstructor(component)
51+
const componentOptions = isConstructor(component)
5452
? component.options
5553
: component
5654

5755
// instance options are options that are passed to the
5856
// root instance when it's instantiated
5957
const instanceOptions = extractInstanceOptions(options)
58+
6059
const stubComponentsObject = createStubsFromStubsObject(
6160
componentOptions.components,
6261
// $FlowIgnore
@@ -69,81 +68,30 @@ export default function createInstance (
6968
addStubs(_Vue, stubComponentsObject)
7069
patchCreateElement(_Vue, stubComponentsObject, options.shouldProxy)
7170

72-
if (componentOptions.functional) {
73-
componentOptions = createFunctionalComponent(
74-
componentOptions,
75-
options,
76-
_Vue
77-
)
78-
} else if (options.context) {
79-
throwError(
80-
`mount.context can only be used when mounting a ` +
81-
`functional component`
82-
)
83-
}
84-
8571
if (componentNeedsCompiling(componentOptions)) {
8672
compileTemplate(componentOptions)
8773
}
8874

75+
// used to identify extended component using constructor
76+
componentOptions.$_vueTestUtils_original = component
77+
8978
// make sure all extends are based on this instance
9079
componentOptions._base = _Vue
9180

9281
const Constructor = _Vue.extend(componentOptions).extend(instanceOptions)
9382

94-
// used to identify extended component using constructor
95-
Constructor.options.$_vueTestUtils_original = component
96-
97-
if (options.slots) {
98-
compileTemplateForSlots(options.slots)
99-
// validate slots outside of the createSlots function so
100-
// that we can throw an error without it being caught by
101-
// the Vue error handler
102-
// $FlowIgnore
103-
validateSlots(options.slots)
104-
}
105-
106-
// Objects are not resolved in extended components in Vue < 2.5
107-
// https://github.com/vuejs/vue/issues/6436
108-
if (
109-
options.provide &&
110-
typeof options.provide === 'object' &&
111-
VUE_VERSION < 2.5
112-
) {
113-
const obj = { ...options.provide }
114-
options.provide = () => obj
115-
}
116-
11783
const scopedSlots = createScopedSlots(options.scopedSlots, _Vue)
11884

119-
if (options.parentComponent && !isPlainObject(options.parentComponent)) {
120-
throwError(
121-
`options.parentComponent should be a valid Vue component options object`
122-
)
123-
}
124-
12585
const parentComponentOptions = options.parentComponent || {}
86+
12687
parentComponentOptions.provide = options.provide
12788
parentComponentOptions.$_doNotStubChildren = true
128-
89+
parentComponentOptions._isFunctionalContainer = componentOptions.functional
12990
parentComponentOptions.render = function (h) {
130-
const slots = options.slots
131-
? createSlotVNodes(this, options.slots)
132-
: undefined
13391
return h(
13492
Constructor,
135-
{
136-
ref: 'vm',
137-
on: options.listeners,
138-
attrs: {
139-
...options.attrs,
140-
// pass as attrs so that inheritAttrs works correctly
141-
// propsData should take precedence over attrs
142-
...options.propsData
143-
},
144-
scopedSlots
145-
},
146-
slots
93+
createContext(options, scopedSlots),
94+
createChildren(this, h, options)
14795
)
14896
}
14997
const Parent = _Vue.extend(parentComponentOptions)

Diff for: packages/server-test-utils/src/renderToString.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { createRenderer } from 'vue-server-renderer'
77
import { mergeOptions } from 'shared/merge-options'
88
import config from './config'
99
import testUtils from '@vue/test-utils'
10+
import { validateOptions } from 'shared/validate-options'
1011

1112
Vue.config.productionTip = false
1213
Vue.config.devtools = false
@@ -27,9 +28,12 @@ export default function renderToString (
2728
throwError(`you cannot use attachToDocument with ` + `renderToString`)
2829
}
2930

31+
const mergedOptions = mergeOptions(options, config)
32+
validateOptions(mergedOptions, component)
33+
3034
const vm = createInstance(
3135
component,
32-
mergeOptions(options, config),
36+
mergedOptions,
3337
testUtils.createLocalVue(options.localVue)
3438
)
3539
let renderedString = ''

Diff for: packages/shared/merge-options.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// @flow
2-
import { normalizeStubs } from './normalize'
2+
import { normalizeStubs, normalizeProvide } from './normalize'
33

44
function getOption (option, config?: Object): any {
55
if (option === false) {
@@ -26,11 +26,11 @@ export function mergeOptions (options: Options, config: Config): Options {
2626
const provide = ((getOption(options.provide, config.provide)): Object)
2727
return {
2828
...options,
29+
provide: normalizeProvide(provide),
2930
logModifiedComponents: config.logModifiedComponents,
3031
stubs: getOption(normalizeStubs(options.stubs), config.stubs),
3132
mocks,
3233
methods,
33-
provide,
3434
sync: !!(options.sync || options.sync === undefined)
3535
}
3636
}

Diff for: packages/shared/normalize.js

+14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { isPlainObject } from './validators'
22
import { throwError } from './util'
3+
import { VUE_VERSION } from './consts'
34

45
export function normalizeStubs (stubs = {}) {
56
if (stubs === false) {
@@ -19,3 +20,16 @@ export function normalizeStubs (stubs = {}) {
1920
}
2021
throwError('options.stubs must be an object or an Array')
2122
}
23+
24+
export function normalizeProvide (provide) {
25+
// Objects are not resolved in extended components in Vue < 2.5
26+
// https://github.com/vuejs/vue/issues/6436
27+
if (
28+
typeof provide === 'object' &&
29+
VUE_VERSION < 2.5
30+
) {
31+
const obj = { ...provide }
32+
return () => obj
33+
}
34+
return provide
35+
}

Diff for: packages/shared/validate-options.js

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import {
2+
isPlainObject,
3+
isFunctionalComponent,
4+
isConstructor
5+
} from './validators'
6+
import { VUE_VERSION } from './consts'
7+
import { compileTemplateForSlots } from './compile-template'
8+
import { throwError } from './util'
9+
import { validateSlots } from './validate-slots'
10+
11+
function vueExtendUnsupportedOption (option) {
12+
return `options.${option} is not supported for ` +
13+
`components created with Vue.extend in Vue < 2.3. ` +
14+
`You can set ${option} to false to mount the component.`
15+
}
16+
// these options aren't supported if Vue is version < 2.3
17+
// for components using Vue.extend. This is due to a bug
18+
// that means the mixins we use to add properties are not applied
19+
// correctly
20+
const UNSUPPORTED_VERSION_OPTIONS = [
21+
'mocks',
22+
'stubs',
23+
'localVue'
24+
]
25+
26+
export function validateOptions (options, component) {
27+
if (options.parentComponent && !isPlainObject(options.parentComponent)) {
28+
throwError(
29+
`options.parentComponent should be a valid Vue component options object`
30+
)
31+
}
32+
33+
if (!isFunctionalComponent(component) && options.context) {
34+
throwError(
35+
`mount.context can only be used when mounting a functional component`
36+
)
37+
}
38+
39+
if (options.context && !isPlainObject(options.context)) {
40+
throwError('mount.context must be an object')
41+
}
42+
43+
if (
44+
VUE_VERSION < 2.3 && isConstructor(component)
45+
) {
46+
UNSUPPORTED_VERSION_OPTIONS.forEach((option) => {
47+
if (options[option]) {
48+
throwError(vueExtendUnsupportedOption(option))
49+
}
50+
})
51+
}
52+
53+
if (options.slots) {
54+
compileTemplateForSlots(options.slots)
55+
// validate slots outside of the createSlots function so
56+
// that we can throw an error without it being caught by
57+
// the Vue error handler
58+
// $FlowIgnore
59+
validateSlots(options.slots)
60+
}
61+
}

Diff for: packages/create-instance/validate-slots.js renamed to packages/shared/validate-slots.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { throwError } from 'shared/util'
44
import { compileToFunctions } from 'vue-template-compiler'
5-
import { isVueComponent } from '../shared/validators'
5+
import { isVueComponent } from './validators'
66

77
function isValidSlot (slot: any): boolean {
88
return (

Diff for: packages/shared/validators.js

+10
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,16 @@ export function isComponentOptions (c: any) {
8888
return typeof c === 'object' && (c.template || c.render)
8989
}
9090

91+
export function isFunctionalComponent (c: any) {
92+
if (!isVueComponent(c)) {
93+
return false
94+
}
95+
if (isConstructor(c)) {
96+
return c.options.functional
97+
}
98+
return c.functional
99+
}
100+
91101
export function templateContainsComponent (
92102
template: string,
93103
name: string

0 commit comments

Comments
 (0)