diff --git a/docs/api/config.md b/docs/api/config.md index 87eec15fb..b5b72e022 100644 --- a/docs/api/config.md +++ b/docs/api/config.md @@ -6,7 +6,7 @@ Vue Test Utils includes a config object to defined options used by Vue Test Util ### `stubs` -- type: `Object` +- type: `{ [name: string]: Component | boolean | string }` - default: `{ transition: TransitionStub, 'transition-group': TransitionGroupStub @@ -46,7 +46,7 @@ VueTestUtils.config.mocks['$store'] = { ### `methods` -- type: `Object` +- type: `{ [name: string]: Function }` - default: `{}` You can configure default methods using the `config` object. This can be useful for plugins that inject methods to components, like [VeeValidate](https://vee-validate.logaretm.com/). You can override methods set in `config` by passing `methods` in the mounting options. diff --git a/flow/config.flow.js b/flow/config.flow.js new file mode 100644 index 000000000..c94e79325 --- /dev/null +++ b/flow/config.flow.js @@ -0,0 +1,8 @@ +declare type Config = { + stubs?: { [name: string]: Component | boolean | string }, + mocks?: Object, + methods?: { [name: string]: Function }, + provide?: Object, + logModifiedComponents?: boolean, + silent?: boolean +}; diff --git a/flow/options.flow.js b/flow/options.flow.js index 9765cd612..e88ddc9c6 100644 --- a/flow/options.flow.js +++ b/flow/options.flow.js @@ -3,15 +3,15 @@ declare type Options = { attachToDocument?: boolean, propsData?: Object, mocks?: Object, - methods?: Object, + methods?: { [key: string]: Function }, slots?: SlotsObject, - scopedSlots?: Object, + scopedSlots?: { [key: string]: string }, localVue?: Component, provide?: Object, - stubs?: Object, + stubs?: Stubs, context?: Object, - attrs?: Object, - listeners?: Object, + attrs?: { [key: string]: string }, + listeners?: { [key: string]: Function | Array }, logModifiedComponents?: boolean, sync?: boolean }; @@ -19,3 +19,7 @@ declare type Options = { declare type SlotValue = Component | string | Array; declare type SlotsObject = { [name: string]: SlotValue }; + +declare type Stubs = { + [name: string]: Component | true | string +} | Array diff --git a/flow/wrapper.flow.js b/flow/wrapper.flow.js index 75a22fe0a..e677685a9 100644 --- a/flow/wrapper.flow.js +++ b/flow/wrapper.flow.js @@ -4,6 +4,7 @@ import type Wrapper from '~src/Wrapper' import type WrapperArray from '~src/WrapperArray' declare type Selector = any; +declare type Components = { [name: string]: Component }; declare interface BaseWrapper { // eslint-disable-line no-undef diff --git a/packages/shared/merge-options.js b/packages/shared/merge-options.js index 9bdb58977..9d9fd9bc4 100644 --- a/packages/shared/merge-options.js +++ b/packages/shared/merge-options.js @@ -1,30 +1,34 @@ // @flow -function getOptions (key, options, config) { - if (options || (config[key] && Object.keys(config[key]).length > 0)) { - if (options instanceof Function) { - return options - } else if (Array.isArray(options)) { - return [...options, ...Object.keys(config[key] || {})] - } else if (!(config[key] instanceof Function)) { +function getOption (option, config?: Object): any { + if (option || (config && Object.keys(config).length > 0)) { + if (option instanceof Function) { + return option + } else if (Array.isArray(option)) { + return [...option, ...Object.keys(config || {})] + } else if (config instanceof Function) { + throw new Error(`Config can't be a Function.`) + } else { return { - ...config[key], - ...options + ...config, + ...option } - } else { - throw new Error(`Config can't be a Function.`) } } } -export function mergeOptions (options: Options, config: Options): Options { +export function mergeOptions (options: Options, config: Config): Options { + const mocks = (getOption(options.mocks, config.mocks): Object) + const methods = ( + (getOption(options.methods, config.methods)): { [key: string]: Function }) + const provide = ((getOption(options.provide, config.provide)): Object) return { ...options, logModifiedComponents: config.logModifiedComponents, - stubs: getOptions('stubs', options.stubs, config), - mocks: getOptions('mocks', options.mocks, config), - methods: getOptions('methods', options.methods, config), - provide: getOptions('provide', options.provide, config), + stubs: getOption(options.stubs, config.stubs), + mocks, + methods, + provide, sync: !!(options.sync || options.sync === undefined) } } diff --git a/packages/shared/stub-components.js b/packages/shared/stub-components.js index 57220755b..dd526cde8 100644 --- a/packages/shared/stub-components.js +++ b/packages/shared/stub-components.js @@ -14,11 +14,11 @@ import { } from './validators' import { compileTemplate } from './compile-template' -function isVueComponent (comp) { +function isVueComponent (comp): boolean { return comp && (comp.render || comp.template || comp.options) } -function isValidStub (stub: any) { +function isValidStub (stub: any): boolean { return ( (!!stub && typeof stub === 'string') || stub === true || @@ -26,7 +26,7 @@ function isValidStub (stub: any) { ) } -function resolveComponent (obj, component) { +function resolveComponent (obj: Object, component: string): Object { return obj[component] || obj[hyphenate(component)] || obj[camelize(component)] || @@ -35,7 +35,7 @@ function resolveComponent (obj, component) { {} } -function isRequiredComponent (name) { +function isRequiredComponent (name): boolean { return ( name === 'KeepAlive' || name === 'Transition' || name === 'TransitionGroup' ) @@ -59,11 +59,12 @@ function getCoreProperties (componentOptions: Component): Object { functional: componentOptions.functional } } + function createStubFromString ( templateString: string, originalComponent: Component, name: string -): Object { +): Component { if (!compileToFunctions) { throwError( `vueTemplateCompiler is undefined, you must pass ` + @@ -86,7 +87,10 @@ function createStubFromString ( } } -function createBlankStub (originalComponent: Component, name: string) { +function createBlankStub ( + originalComponent: Component, + name: string +): Component { const componentOptions = typeof originalComponent === 'function' ? originalComponent.extendOptions : originalComponent @@ -107,8 +111,8 @@ function createBlankStub (originalComponent: Component, name: string) { export function createComponentStubs ( originalComponents: Object = {}, - stubs: Object -): Object { + stubs: Stubs +): Components { const components = {} if (!stubs) { return components @@ -127,42 +131,47 @@ export function createComponentStubs ( components[stub] = createBlankStub(component, stub) }) } else { - Object.keys(stubs).forEach(stub => { - if (stubs[stub] === false) { + const stubsObject = (stubs: { [name: string]: Component | string | true }) + Object.keys(stubsObject).forEach(stubName => { + const stub = stubsObject[stubName] + if (stub === false) { return } - if (!isValidStub(stubs[stub])) { + + if (!isValidStub(stub)) { throwError( `options.stub values must be passed a string or ` + `component` ) } - if (stubs[stub] === true) { - const component = resolveComponent(originalComponents, stub) - components[stub] = createBlankStub(component, stub) + + if (stub === true) { + const component = resolveComponent(originalComponents, stubName) + components[stubName] = createBlankStub(component, stubName) return } - if (componentNeedsCompiling(stubs[stub])) { - compileTemplate(stubs[stub]) + if (typeof stub !== 'string' && componentNeedsCompiling(stub)) { + compileTemplate(stub) } - if (originalComponents[stub]) { + if (originalComponents[stubName]) { // Remove cached constructor - delete originalComponents[stub]._Ctor - if (typeof stubs[stub] === 'string') { - components[stub] = createStubFromString( - stubs[stub], - originalComponents[stub], - stub + delete originalComponents[stubName]._Ctor + if (typeof stub === 'string') { + components[stubName] = createStubFromString( + stub, + originalComponents[stubName], + stubName ) } else { - components[stub] = { - ...stubs[stub], - name: originalComponents[stub].name + const stubObject = (stub: Object) + components[stubName] = { + ...stubObject, + name: originalComponents[stubName].name } } } else { - if (typeof stubs[stub] === 'string') { + if (typeof stub === 'string') { if (!compileToFunctions) { throwError( `vueTemplateCompiler is undefined, you must pass ` + @@ -170,12 +179,13 @@ export function createComponentStubs ( `undefined` ) } - components[stub] = { - ...compileToFunctions(stubs[stub]) + components[stubName] = { + ...compileToFunctions(stub) } } else { - components[stub] = { - ...stubs[stub] + const stubObject = (stub: Object) + components[stubName] = { + ...stubObject } } } @@ -184,7 +194,10 @@ export function createComponentStubs ( return components } -function stubComponents (components: Object, stubbedComponents: Object) { +function stubComponents ( + components: Components, + stubbedComponents: Components +): void { Object.keys(components).forEach(component => { const cmp = components[component] const componentOptions = typeof cmp === 'function' @@ -199,7 +212,7 @@ function stubComponents (components: Object, stubbedComponents: Object) { }) } -export function createComponentStubsForAll (component: Component): Object { +export function createComponentStubsForAll (component: Component): Components { const stubbedComponents = {} if (component.components) { @@ -225,7 +238,9 @@ export function createComponentStubsForAll (component: Component): Object { return stubbedComponents } -export function createComponentStubsForGlobals (instance: Component): Object { +export function createComponentStubsForGlobals ( + instance: Component +): Components { const components = {} Object.keys(instance.options.components).forEach(c => { if (isRequiredComponent(c)) { diff --git a/packages/test-utils/src/wrapper.js b/packages/test-utils/src/wrapper.js index 53d822d69..d77ab40ac 100644 --- a/packages/test-utils/src/wrapper.js +++ b/packages/test-utils/src/wrapper.js @@ -76,7 +76,7 @@ export default class Wrapper implements BaseWrapper { ) } - at () { + at (): void { throwError('at() must be called on a WrapperArray') } @@ -122,7 +122,7 @@ export default class Wrapper implements BaseWrapper { /** * Checks if wrapper contains provided selector. */ - contains (selector: Selector) { + contains (selector: Selector): boolean { const selectorType = getSelectorTypeOrThrow(selector, 'contains') const nodes = findAll(this.vm, this.vnode, this.element, selector) const is = selectorType === REF_SELECTOR ? false : this.is(selector) @@ -132,7 +132,9 @@ export default class Wrapper implements BaseWrapper { /** * Returns an object containing custom events emitted by the Wrapper vm */ - emitted (event?: string) { + emitted ( + event?: string + ): Array> | { [name: string]: Array> } { if (!this._emitted && !this.vm) { throwError(`wrapper.emitted() can only be called on a Vue instance`) } @@ -145,7 +147,7 @@ export default class Wrapper implements BaseWrapper { /** * Returns an Array containing custom events emitted by the Wrapper vm */ - emittedByOrder () { + emittedByOrder (): Array<{ name: string, args: Array }> { if (!this._emittedByOrder && !this.vm) { throwError( `wrapper.emittedByOrder() can only be called on a Vue instance` @@ -195,7 +197,7 @@ export default class Wrapper implements BaseWrapper { /** * Checks if wrapper has an attribute with matching value */ - hasAttribute (attribute: string, value: string) { + hasAttribute (attribute: string, value: string): boolean { warn( `hasAttribute() has been deprecated and will be ` + `removed in version 1.0.0. Use attributes() ` + @@ -220,7 +222,7 @@ export default class Wrapper implements BaseWrapper { /** * Asserts wrapper has a class name */ - hasClass (className: string) { + hasClass (className: string): boolean { warn( `hasClass() has been deprecated and will be removed ` + `in version 1.0.0. Use classes() ` + @@ -247,7 +249,7 @@ export default class Wrapper implements BaseWrapper { /** * Asserts wrapper has a prop name */ - hasProp (prop: string, value: string) { + hasProp (prop: string, value: string): boolean { warn( `hasProp() has been deprecated and will be removed ` + `in version 1.0.0. Use props() ` + @@ -278,7 +280,7 @@ export default class Wrapper implements BaseWrapper { /** * Checks if wrapper has a style with value */ - hasStyle (style: string, value: string) { + hasStyle (style: string, value: string): boolean { warn( `hasStyle() has been deprecated and will be removed ` + `in version 1.0.0. Use wrapper.element.style ` + @@ -494,7 +496,7 @@ export default class Wrapper implements BaseWrapper { /** * Sets vm data */ - setData (data: Object) { + setData (data: Object): void { if (this.isFunctionalComponent) { throwError( `wrapper.setData() cannot be called on a functional ` + @@ -535,7 +537,7 @@ export default class Wrapper implements BaseWrapper { /** * Sets vm computed */ - setComputed (computed: Object) { + setComputed (computed: Object): void { if (!this.isVueInstance()) { throwError( `wrapper.setComputed() can only be called on a Vue ` + @@ -608,7 +610,7 @@ export default class Wrapper implements BaseWrapper { /** * Sets vm methods */ - setMethods (methods: Object) { + setMethods (methods: Object): void { if (!this.isVueInstance()) { throwError( `wrapper.setMethods() can only be called on a Vue ` + @@ -631,7 +633,7 @@ export default class Wrapper implements BaseWrapper { /** * Sets vm props */ - setProps (data: Object) { + setProps (data: Object): void { const originalConfig = Vue.config.silent Vue.config.silent = config.silent if (this.isFunctionalComponent) { @@ -678,7 +680,7 @@ export default class Wrapper implements BaseWrapper { /** * Sets element value and triggers input event */ - setValue (value: any) { + setValue (value: any): void { const tagName = this.element.tagName const type = this.attributes().type @@ -711,7 +713,7 @@ export default class Wrapper implements BaseWrapper { /** * Checks radio button or checkbox element */ - setChecked (checked: boolean = true) { + setChecked (checked: boolean = true): void { if (typeof checked !== 'boolean') { throwError('wrapper.setChecked() must be passed a boolean') } @@ -761,7 +763,7 @@ export default class Wrapper implements BaseWrapper { /** * Selects element */ - setSelected () { + setSelected (): void { const tagName = this.element.tagName const type = this.attributes().type @@ -815,7 +817,7 @@ export default class Wrapper implements BaseWrapper { /** * Calls destroy on vm */ - destroy () { + destroy (): void { if (!this.isVueInstance()) { throwError(`wrapper.destroy() can only be called on a Vue instance`) } @@ -899,7 +901,7 @@ export default class Wrapper implements BaseWrapper { } } - update () { + update (): void { warn( `update has been removed from vue-test-utils. All ` + `updates are now synchronous by default` diff --git a/packages/test-utils/types/index.d.ts b/packages/test-utils/types/index.d.ts index 87b0cec66..7ca0fd7a7 100644 --- a/packages/test-utils/types/index.d.ts +++ b/packages/test-utils/types/index.d.ts @@ -141,7 +141,7 @@ type ShallowMountOptions = MountOptions type ThisTypedShallowMountOptions = ShallowMountOptions & ThisType interface VueTestUtilsConfigOptions { - stubs?: Stubs + stubs?: Record mocks?: object methods?: Record provide?: object, diff --git a/packages/test-utils/types/test/mount.ts b/packages/test-utils/types/test/mount.ts index f085f9e59..cb847a483 100644 --- a/packages/test-utils/types/test/mount.ts +++ b/packages/test-utils/types/test/mount.ts @@ -75,7 +75,6 @@ mount(ClassComponent, { /** * Test for config */ -config.stubs = ['a'] config.stubs = { foo: normalOptions, bar: functionalOptions,