@vue/test-utils
Advanced tools
Comparing version 2.0.0-alpha.1 to 2.0.0-alpha.2
export declare const MOUNT_ELEMENT_ID = "app"; | ||
export declare const MOUNT_COMPONENT_REF = "VTU_COMPONENT"; | ||
export declare const MOUNT_PARENT_NAME = "VTU_ROOT"; |
import { nextTick } from 'vue'; | ||
import { WrapperAPI } from './types'; | ||
import { ErrorWrapper } from './error-wrapper'; | ||
import { TriggerOptions } from './create-dom-event'; | ||
export declare class DOMWrapper<ElementType extends Element> implements WrapperAPI { | ||
@@ -12,3 +13,10 @@ element: ElementType; | ||
html(): string; | ||
find<K extends keyof HTMLElementTagNameMap>(selector: K): DOMWrapper<HTMLElementTagNameMap[K]> | ErrorWrapper; | ||
find<K extends keyof SVGElementTagNameMap>(selector: K): DOMWrapper<SVGElementTagNameMap[K]> | ErrorWrapper; | ||
find<T extends Element>(selector: string): DOMWrapper<T> | ErrorWrapper; | ||
get<K extends keyof HTMLElementTagNameMap>(selector: K): DOMWrapper<HTMLElementTagNameMap[K]>; | ||
get<K extends keyof SVGElementTagNameMap>(selector: K): DOMWrapper<SVGElementTagNameMap[K]>; | ||
get<T extends Element>(selector: string): DOMWrapper<T>; | ||
findAll<K extends keyof HTMLElementTagNameMap>(selector: K): DOMWrapper<HTMLElementTagNameMap[K]>[]; | ||
findAll<K extends keyof SVGElementTagNameMap>(selector: K): DOMWrapper<SVGElementTagNameMap[K]>[]; | ||
findAll<T extends Element>(selector: string): DOMWrapper<T>[]; | ||
@@ -18,3 +26,3 @@ private setChecked; | ||
private setSelected; | ||
trigger(eventString: string): Promise<typeof nextTick>; | ||
trigger(eventString: string, options?: TriggerOptions): Promise<typeof nextTick>; | ||
} |
@@ -1,6 +0,3 @@ | ||
export declare const createEmitMixin: () => { | ||
events: Record<string, unknown[]>; | ||
emitMixin: { | ||
beforeCreate(): void; | ||
}; | ||
export declare const attachEmitListener: () => { | ||
beforeCreate(): void; | ||
}; |
@@ -0,9 +1,11 @@ | ||
import { FindComponentSelector } from './types'; | ||
interface Options { | ||
selector: string; | ||
selector: FindComponentSelector; | ||
} | ||
export declare class ErrorWrapper { | ||
selector: string; | ||
selector: FindComponentSelector; | ||
element: null; | ||
constructor({ selector }: Options); | ||
wrapperError(method: string): Error; | ||
vm(): Error; | ||
attributes(): void; | ||
@@ -13,8 +15,10 @@ classes(): void; | ||
find(): never; | ||
get(): never; | ||
findAll(): never; | ||
setChecked(): void; | ||
setProps(): void; | ||
setValue(): void; | ||
text(): void; | ||
trigger(): void; | ||
unmount(): void; | ||
} | ||
export {}; |
import { mount } from './mount'; | ||
import { RouterLinkStub } from './components/RouterLinkStub'; | ||
export { mount, RouterLinkStub }; | ||
import { config } from './config'; | ||
export { mount, RouterLinkStub, config }; |
@@ -1,8 +0,10 @@ | ||
import { VNode, ComponentOptions, Plugin, Directive, Component } from 'vue'; | ||
import { VNode, ComponentPublicInstance, ComponentOptionsWithObjectProps, ComponentOptionsWithArrayProps, ComponentOptionsWithoutProps, ExtractPropTypes } from 'vue'; | ||
import { GlobalMountOptions } from './types'; | ||
import { VueWrapper } from './vue-wrapper'; | ||
declare type Slot = VNode | string | { | ||
render: Function; | ||
}; | ||
interface MountingOptions { | ||
interface MountingOptions<Props> { | ||
data?: () => Record<string, unknown>; | ||
props?: Record<string, any>; | ||
props?: Props; | ||
slots?: { | ||
@@ -12,13 +14,8 @@ default?: Slot; | ||
}; | ||
global?: { | ||
plugins?: Plugin[]; | ||
mixins?: ComponentOptions[]; | ||
mocks?: Record<string, any>; | ||
provide?: Record<any, any>; | ||
components?: Record<string, Component | object>; | ||
directives?: Record<string, Directive>; | ||
}; | ||
stubs?: Record<string, any>; | ||
global?: GlobalMountOptions; | ||
} | ||
export declare function mount(originalComponent: any, options?: MountingOptions): import("./vue-wrapper").VueWrapper; | ||
export declare function mount<TestedComponent extends ComponentPublicInstance, PublicProps extends TestedComponent['$props']>(originalComponent: new () => TestedComponent, options?: MountingOptions<PublicProps>): VueWrapper<TestedComponent>; | ||
export declare function mount<TestedComponent extends ComponentOptionsWithObjectProps, PublicProps extends ExtractPropTypes<TestedComponent['props']>>(originalComponent: TestedComponent, options?: MountingOptions<PublicProps>): VueWrapper<any>; | ||
export declare function mount<TestedComponent extends ComponentOptionsWithArrayProps, PublicProps extends Record<string, any>>(originalComponent: TestedComponent, options?: MountingOptions<PublicProps>): VueWrapper<any>; | ||
export declare function mount<TestedComponent extends ComponentOptionsWithoutProps, PublicProps extends Record<string, any>>(originalComponent: TestedComponent, options?: MountingOptions<PublicProps>): VueWrapper<any>; | ||
export {}; |
@@ -0,1 +1,2 @@ | ||
import { Component, ComponentOptions, Directive, Plugin } from 'vue'; | ||
import { DOMWrapper } from './dom-wrapper'; | ||
@@ -8,2 +9,3 @@ import { ErrorWrapper } from './error-wrapper'; | ||
exists: () => boolean; | ||
get<T extends Element>(selector: string): DOMWrapper<T>; | ||
find<T extends Element>(selector: string): DOMWrapper<T> | ErrorWrapper; | ||
@@ -13,3 +15,21 @@ findAll<T extends Element>(selector: string): DOMWrapper<T>[]; | ||
text: () => string; | ||
trigger: (eventString: string) => Promise<(fn?: () => void) => Promise<void>>; | ||
trigger: (eventString: string, options?: Object) => Promise<(fn?: () => void) => Promise<void>>; | ||
} | ||
interface RefSelector { | ||
ref: string; | ||
} | ||
interface NameSelector { | ||
name: string; | ||
} | ||
export declare type FindComponentSelector = RefSelector | NameSelector | string; | ||
export declare type FindAllComponentsSelector = NameSelector | string; | ||
export declare type GlobalMountOptions = { | ||
plugins?: Plugin[]; | ||
mixins?: ComponentOptions[]; | ||
mocks?: Record<string, any>; | ||
provide?: Record<any, any>; | ||
components?: Record<string, Component | object>; | ||
directives?: Record<string, Directive>; | ||
stubs?: Record<any, any>; | ||
}; | ||
export {}; |
/** | ||
* @vue/test-utils v2.0.0-alpha.1 | ||
* @vue/test-utils v2.0.0-alpha.2 | ||
* (c) 2020 Lachlan Miller | ||
@@ -12,3 +12,11 @@ * Released under the MIT License | ||
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
var vue = require('vue'); | ||
var camelCase = _interopDefault(require('lodash/camelCase')); | ||
var upperFirst = _interopDefault(require('lodash/upperFirst')); | ||
var kebabCase = _interopDefault(require('lodash/kebabCase')); | ||
var flow = _interopDefault(require('lodash/flow')); | ||
var mergeWith = _interopDefault(require('lodash/mergeWith')); | ||
var eventTypes = _interopDefault(require('dom-event-types')); | ||
@@ -86,2 +94,24 @@ /*! ***************************************************************************** | ||
var config = { | ||
global: {} | ||
}; | ||
var pascalCase = flow(camelCase, upperFirst); | ||
function mergeGlobalProperties(configGlobal, mountGlobal) { | ||
if (configGlobal === void 0) { configGlobal = {}; } | ||
if (mountGlobal === void 0) { mountGlobal = {}; } | ||
return mergeWith({}, configGlobal, mountGlobal, function (objValue, srcValue, key) { | ||
switch (key) { | ||
case 'mocks': | ||
case 'provide': | ||
case 'components': | ||
case 'directives': | ||
return __assign(__assign({}, objValue), srcValue); | ||
case 'plugins': | ||
case 'mixins': | ||
return __spreadArrays((objValue || []), (srcValue || [])).filter(Boolean); | ||
} | ||
}); | ||
} | ||
var ErrorWrapper = /** @class */ (function () { | ||
@@ -95,2 +125,5 @@ function ErrorWrapper(_a) { | ||
}; | ||
ErrorWrapper.prototype.vm = function () { | ||
throw this.wrapperError('vm'); | ||
}; | ||
ErrorWrapper.prototype.attributes = function () { | ||
@@ -108,7 +141,10 @@ throw this.wrapperError('attributes'); | ||
}; | ||
ErrorWrapper.prototype.get = function () { | ||
throw this.wrapperError('get'); | ||
}; | ||
ErrorWrapper.prototype.findAll = function () { | ||
throw this.wrapperError('findAll'); | ||
}; | ||
ErrorWrapper.prototype.setChecked = function () { | ||
throw this.wrapperError('setChecked'); | ||
ErrorWrapper.prototype.setProps = function () { | ||
throw this.wrapperError('setProps'); | ||
}; | ||
@@ -124,5 +160,65 @@ ErrorWrapper.prototype.setValue = function () { | ||
}; | ||
ErrorWrapper.prototype.unmount = function () { | ||
throw this.wrapperError('unmount'); | ||
}; | ||
return ErrorWrapper; | ||
}()); | ||
var keyCodesByKeyName = { | ||
backspace: 8, | ||
tab: 9, | ||
enter: 13, | ||
esc: 27, | ||
space: 32, | ||
pageup: 33, | ||
pagedown: 34, | ||
end: 35, | ||
home: 36, | ||
left: 37, | ||
up: 38, | ||
right: 39, | ||
down: 40, | ||
insert: 45, | ||
delete: 46 | ||
}; | ||
function getEventProperties(eventParams) { | ||
var modifier = eventParams.modifier, meta = eventParams.meta, options = eventParams.options; | ||
var keyCode = keyCodesByKeyName[modifier] || | ||
(options && (options.keyCode || options.code)); | ||
return __assign(__assign({}, options), { bubbles: meta.bubbles, meta: meta.cancelable, | ||
// Any derived options should go here | ||
keyCode: keyCode, code: keyCode }); | ||
} | ||
function createEvent(eventParams) { | ||
var eventType = eventParams.eventType, meta = eventParams.meta; | ||
var metaEventInterface = window[meta.eventInterface]; | ||
var SupportedEventInterface = typeof metaEventInterface === 'function' ? metaEventInterface : window.Event; | ||
var eventProperties = getEventProperties(eventParams); | ||
var event = new SupportedEventInterface(eventType, | ||
// event properties can only be added when the event is instantiated | ||
// custom properties must be added after the event has been instantiated | ||
eventProperties); | ||
return event; | ||
} | ||
function createDOMEvent(eventString, options) { | ||
var _a = eventString.split('.'), eventType = _a[0], modifier = _a[1]; | ||
var meta = eventTypes[eventType] || { | ||
eventInterface: 'Event', | ||
cancelable: true, | ||
bubbles: true | ||
}; | ||
var eventParams = { eventType: eventType, modifier: modifier, meta: meta, options: options }; | ||
var event = createEvent(eventParams); | ||
var eventPrototype = Object.getPrototypeOf(event); | ||
options && | ||
Object.keys(options).forEach(function (key) { | ||
var propertyDescriptor = Object.getOwnPropertyDescriptor(eventPrototype, key); | ||
var canSetProperty = !(propertyDescriptor && propertyDescriptor.set === undefined); | ||
if (canSetProperty) { | ||
event[key] = options[key]; | ||
} | ||
}); | ||
return event; | ||
} | ||
var DOMWrapper = /** @class */ (function () { | ||
@@ -164,2 +260,9 @@ function DOMWrapper(element) { | ||
}; | ||
DOMWrapper.prototype.get = function (selector) { | ||
var result = this.find(selector); | ||
if (result instanceof ErrorWrapper) { | ||
throw new Error("Unable to find " + selector + " within: " + this.html()); | ||
} | ||
return result; | ||
}; | ||
DOMWrapper.prototype.findAll = function (selector) { | ||
@@ -180,3 +283,3 @@ return Array.from(this.element.querySelectorAll(selector)).map(function (x) { return new DOMWrapper(x); }); | ||
// attempting set the same value twice | ||
// this is beacuse in a browser setting checked = true when it is | ||
// this is because in a browser setting checked = true when it is | ||
// already true is a no-op; no change event is triggered | ||
@@ -231,11 +334,32 @@ if (checked === element.checked) { | ||
}; | ||
DOMWrapper.prototype.trigger = function (eventString) { | ||
DOMWrapper.prototype.trigger = function (eventString, options) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var evt; | ||
var isDisabled, event_1; | ||
var _this = this; | ||
return __generator(this, function (_a) { | ||
evt = document.createEvent('Event'); | ||
evt.initEvent(eventString); | ||
if (this.element) { | ||
this.element.dispatchEvent(evt); | ||
if (options && options['target']) { | ||
throw Error("[vue-test-utils]: you cannot set the target value of an event. See the notes section " + | ||
"of the docs for more details\u2014" + | ||
"https://vue-test-utils.vuejs.org/api/wrapper/trigger.html"); | ||
} | ||
isDisabled = function () { | ||
var validTagsToBeDisabled = [ | ||
'BUTTON', | ||
'COMMAND', | ||
'FIELDSET', | ||
'KEYGEN', | ||
'OPTGROUP', | ||
'OPTION', | ||
'SELECT', | ||
'TEXTAREA', | ||
'INPUT' | ||
]; | ||
var hasDisabledAttribute = _this.attributes().disabled !== undefined; | ||
var elementCanBeDisabled = validTagsToBeDisabled.includes(_this.element.tagName); | ||
return hasDisabledAttribute && elementCanBeDisabled; | ||
}; | ||
if (this.element && !isDisabled()) { | ||
event_1 = createDOMEvent(eventString, options); | ||
this.element.dispatchEvent(event_1); | ||
} | ||
return [2 /*return*/, vue.nextTick]; | ||
@@ -248,23 +372,95 @@ }); | ||
var MOUNT_ELEMENT_ID = 'app'; | ||
// Make a map and return a function for checking if a key | ||
const EMPTY_OBJ = (process.env.NODE_ENV !== 'production') | ||
? Object.freeze({}) | ||
: {}; | ||
const cacheStringFunction = (fn) => { | ||
const cache = Object.create(null); | ||
return ((str) => { | ||
const hit = cache[str]; | ||
return hit || (cache[str] = fn(str)); | ||
}); | ||
}; | ||
const camelizeRE = /-(\w)/g; | ||
const camelize = cacheStringFunction((str) => { | ||
return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : '')); | ||
}); | ||
const capitalize = cacheStringFunction((str) => { | ||
return str.charAt(0).toUpperCase() + str.slice(1); | ||
}); | ||
function matchName(target, sourceName) { | ||
var camelized = camelize(target); | ||
var capitalized = capitalize(camelized); | ||
return (sourceName && | ||
(sourceName === target || | ||
sourceName === camelized || | ||
sourceName === capitalized || | ||
capitalize(camelize(sourceName)) === capitalized)); | ||
} | ||
/** | ||
* Detect whether a selector matches a VNode | ||
* @param node | ||
* @param selector | ||
* @return {boolean | ((value: any) => boolean)} | ||
*/ | ||
function matches(node, selector) { | ||
var _a, _b, _c; | ||
// do not return none Vue components | ||
if (!node.component) | ||
return false; | ||
if (typeof selector === 'string') { | ||
return (_c = (_a = node.el) === null || _a === void 0 ? void 0 : (_b = _a).matches) === null || _c === void 0 ? void 0 : _c.call(_b, selector); | ||
} | ||
if (typeof selector === 'object' && typeof node.type === 'object') { | ||
if (selector.name && ('name' in node.type || 'displayName' in node.type)) { | ||
// match normal component definitions or functional components | ||
return matchName(selector.name, node.type.name || node.type.displayName); | ||
} | ||
} | ||
return false; | ||
} | ||
/** | ||
* Collect all children | ||
* @param nodes | ||
* @param children | ||
*/ | ||
function aggregateChildren(nodes, children) { | ||
if (children && Array.isArray(children)) { | ||
__spreadArrays(children).reverse().forEach(function (n) { | ||
nodes.unshift(n); | ||
}); | ||
} | ||
} | ||
function findAllVNodes(vnode, selector) { | ||
var _a; | ||
var matchingNodes = []; | ||
var nodes = [vnode]; | ||
while (nodes.length) { | ||
var node = nodes.shift(); | ||
aggregateChildren(nodes, node.children); | ||
aggregateChildren(nodes, (_a = node.component) === null || _a === void 0 ? void 0 : _a.subTree.children); | ||
if (matches(node, selector)) { | ||
matchingNodes.push(node); | ||
} | ||
} | ||
return matchingNodes; | ||
} | ||
function find(root, selector) { | ||
return findAllVNodes(root, selector).map(function (vnode) { return vnode.component.proxy; }); | ||
} | ||
var VueWrapper = /** @class */ (function () { | ||
function VueWrapper(vm, events, setProps) { | ||
this.__emitted = {}; | ||
this.__vm = vm; | ||
function VueWrapper(app, vm, setProps) { | ||
this.__app = app; | ||
this.rootVM = vm.$root; | ||
this.componentVM = vm; | ||
this.__setProps = setProps; | ||
this.componentVM = this.vm.$refs['VTU_COMPONENT']; | ||
this.__emitted = events; | ||
} | ||
Object.defineProperty(VueWrapper.prototype, "appRootNode", { | ||
get: function () { | ||
return document.getElementById(MOUNT_ELEMENT_ID); | ||
}, | ||
enumerable: true, | ||
configurable: true | ||
}); | ||
Object.defineProperty(VueWrapper.prototype, "hasMultipleRoots", { | ||
get: function () { | ||
// if the subtree is an array of children, we have multiple root nodes | ||
return this.componentVM.$.subTree.shapeFlag === 16 /* ARRAY_CHILDREN */; | ||
return this.vm.$.subTree.shapeFlag === 16 /* ARRAY_CHILDREN */; | ||
}, | ||
@@ -276,3 +472,3 @@ enumerable: true, | ||
get: function () { | ||
return this.componentVM.$el.parentElement; | ||
return this.vm.$el.parentElement; | ||
}, | ||
@@ -285,3 +481,3 @@ enumerable: true, | ||
// if the component has multiple root elements, we use the parent's element | ||
return this.hasMultipleRoots ? this.parentElement : this.componentVM.$el; | ||
return this.hasMultipleRoots ? this.parentElement : this.vm.$el; | ||
}, | ||
@@ -293,3 +489,3 @@ enumerable: true, | ||
get: function () { | ||
return this.__vm; | ||
return this.componentVM; | ||
}, | ||
@@ -299,2 +495,7 @@ enumerable: true, | ||
}); | ||
VueWrapper.prototype.props = function (selector) { | ||
return selector | ||
? this.componentVM.$props[selector] | ||
: this.componentVM.$props; | ||
}; | ||
VueWrapper.prototype.classes = function (className) { | ||
@@ -310,6 +511,8 @@ return new DOMWrapper(this.element).classes(className); | ||
VueWrapper.prototype.emitted = function () { | ||
return this.__emitted; | ||
// TODO Should we define this? | ||
// @ts-ignore | ||
return this.vm.__emitted; | ||
}; | ||
VueWrapper.prototype.html = function () { | ||
return this.appRootNode.innerHTML; | ||
return this.parentElement.innerHTML; | ||
}; | ||
@@ -328,25 +531,58 @@ VueWrapper.prototype.text = function () { | ||
}; | ||
VueWrapper.prototype.get = function (selector) { | ||
var result = this.find(selector); | ||
if (result instanceof ErrorWrapper) { | ||
throw new Error("Unable to find " + selector + " within: " + this.html()); | ||
} | ||
return result; | ||
}; | ||
VueWrapper.prototype.findComponent = function (selector) { | ||
if (typeof selector === 'object' && 'ref' in selector) { | ||
return createWrapper(null, this.vm.$refs[selector.ref]); | ||
} | ||
var result = find(this.vm.$.subTree, selector); | ||
if (!result.length) | ||
return new ErrorWrapper({ selector: selector }); | ||
return createWrapper(null, result[0]); | ||
}; | ||
VueWrapper.prototype.findAllComponents = function (selector) { | ||
return find(this.vm.$.subTree, selector).map(function (c) { return createWrapper(null, c); }); | ||
}; | ||
VueWrapper.prototype.findAll = function (selector) { | ||
var results = this.appRootNode.querySelectorAll(selector); | ||
return Array.from(results).map(function (x) { return new DOMWrapper(x); }); | ||
var results = this.parentElement.querySelectorAll(selector); | ||
return Array.from(results).map(function (element) { return new DOMWrapper(element); }); | ||
}; | ||
VueWrapper.prototype.setProps = function (props) { | ||
// if this VM's parent is not the root, error out | ||
if (this.vm.$parent !== this.rootVM) { | ||
throw Error('You can only use setProps on your mounted component'); | ||
} | ||
this.__setProps(props); | ||
return vue.nextTick(); | ||
}; | ||
VueWrapper.prototype.trigger = function (eventString) { | ||
VueWrapper.prototype.trigger = function (eventString, options) { | ||
var rootElementWrapper = new DOMWrapper(this.element); | ||
return rootElementWrapper.trigger(eventString); | ||
return rootElementWrapper.trigger(eventString, options); | ||
}; | ||
VueWrapper.prototype.unmount = function () { | ||
// preventing dispose of child component | ||
if (!this.__app) { | ||
throw new Error("wrapper.unmount() can only be called by the root wrapper"); | ||
} | ||
if (this.parentElement) { | ||
this.parentElement.removeChild(this.element); | ||
} | ||
this.__app.unmount(this.element); | ||
}; | ||
return VueWrapper; | ||
}()); | ||
function createWrapper(vm, events, setProps) { | ||
return new VueWrapper(vm, events, setProps); | ||
function createWrapper(app, vm, setProps) { | ||
return new VueWrapper(app, vm, setProps); | ||
} | ||
var createEmitMixin = function () { | ||
var events = {}; | ||
var emitMixin = { | ||
var attachEmitListener = function () { | ||
return { | ||
beforeCreate: function () { | ||
var originalEmit = vue.getCurrentInstance().emit; | ||
var events = {}; | ||
this.__emitted = events; | ||
vue.getCurrentInstance().emit = function (event) { | ||
@@ -360,10 +596,6 @@ var args = []; | ||
: (events[event] = [__spreadArrays(args)]); | ||
return originalEmit.call.apply(originalEmit, __spreadArrays([vue.getCurrentInstance(), event], args)); | ||
return __spreadArrays([event], args); | ||
}; | ||
} | ||
}; | ||
return { | ||
events: events, | ||
emitMixin: emitMixin | ||
}; | ||
}; | ||
@@ -383,4 +615,70 @@ | ||
var MOUNT_ELEMENT_ID = 'app'; | ||
var MOUNT_COMPONENT_REF = 'VTU_COMPONENT'; | ||
var MOUNT_PARENT_NAME = 'VTU_ROOT'; | ||
var createStub = function (options) { | ||
var tag = options.name ? options.name + "-stub" : 'anonymous-stub'; | ||
var render = function () { return vue.h(tag); }; | ||
return { name: tag, render: render }; | ||
}; | ||
var resolveComponentStubByName = function (componentName, stubs) { | ||
var componentPascalName = pascalCase(componentName); | ||
var componentKebabName = kebabCase(componentName); | ||
if (Array.isArray(stubs) && stubs.length) { | ||
// ['Foo', 'Bar'] => { Foo: true, Bar: true } | ||
stubs = stubs.reduce(function (acc, current) { | ||
acc[current] = true; | ||
return acc; | ||
}, {}); | ||
} | ||
for (var _i = 0, _a = Object.entries(stubs); _i < _a.length; _i++) { | ||
var _b = _a[_i], stubKey = _b[0], value = _b[1]; | ||
if (stubKey === componentPascalName || | ||
stubKey === componentKebabName || | ||
stubKey === componentName) { | ||
return value; | ||
} | ||
} | ||
}; | ||
var isHTMLElement = function (args) { | ||
return Array.isArray(args) && typeof args[0] === 'string'; | ||
}; | ||
var isCommentOrFragment = function (args) { return typeof args[0] === 'symbol'; }; | ||
var isParent = function (args) { | ||
return typeof args[0] === 'object' && args[0]['name'] === 'VTU_COMPONENT'; | ||
}; | ||
var isComponent = function (args) { return typeof args[0] === 'object'; }; | ||
function stubComponents(stubs) { | ||
vue.transformVNodeArgs(function (args) { | ||
// args[0] can either be: | ||
// 1. a HTML tag (div, span...) | ||
// 2. An object of component options, such as { name: 'foo', render: [Function], props: {...} } | ||
// Depending what it is, we do different things. | ||
if (isHTMLElement(args) || isCommentOrFragment(args) || isParent(args)) { | ||
return args; | ||
} | ||
if (isComponent(args)) { | ||
var name_1 = args[0]['name']; | ||
if (!name_1) { | ||
return args; | ||
} | ||
var stub = resolveComponentStubByName(name_1, stubs); | ||
// we return a stub by matching Vue's `h` function | ||
// where the signature is h(Component, props) | ||
// case 1: default stub | ||
if (stub === true) { | ||
return [createStub({ name: name_1 }), {}]; | ||
} | ||
// case 2: custom implementation | ||
if (typeof stub === 'object') { | ||
return [stubs[name_1], {}]; | ||
} | ||
} | ||
return args; | ||
}); | ||
} | ||
function mount(originalComponent, options) { | ||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y; | ||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m; | ||
var component = __assign({}, originalComponent); | ||
@@ -407,9 +705,12 @@ // Reset the document.body | ||
var dataMixin = createDataMixin(options.data()); | ||
component.mixins = __spreadArrays((component.mixins || []), [dataMixin]); | ||
component.mixins = __spreadArrays((component.mixins || []), [ | ||
dataMixin | ||
]); | ||
} | ||
// we define props as reactive so that way when we update them with `setProps` | ||
// Vue's reactivity system will cause a rerender. | ||
var props = vue.reactive(__assign(__assign({}, (_c = options) === null || _c === void 0 ? void 0 : _c.props), { ref: 'VTU_COMPONENT' })); | ||
var props = vue.reactive(__assign(__assign({}, (_c = options) === null || _c === void 0 ? void 0 : _c.props), { ref: MOUNT_COMPONENT_REF })); | ||
// create the wrapper component | ||
var Parent = vue.defineComponent({ | ||
name: MOUNT_PARENT_NAME, | ||
render: function () { | ||
@@ -424,13 +725,13 @@ return vue.h(component, props, slots); | ||
} | ||
return app.$nextTick(); | ||
return vm.$nextTick(); | ||
}; | ||
// create the vm | ||
var vm = vue.createApp(Parent); | ||
// create the app | ||
var app = vue.createApp(Parent); | ||
var global = mergeGlobalProperties(config.global, (_d = options) === null || _d === void 0 ? void 0 : _d.global); | ||
// global mocks mixin | ||
if ((_e = (_d = options) === null || _d === void 0 ? void 0 : _d.global) === null || _e === void 0 ? void 0 : _e.mocks) { | ||
if ((_e = global) === null || _e === void 0 ? void 0 : _e.mocks) { | ||
var mixin = { | ||
beforeCreate: function () { | ||
var _a; | ||
for (var _i = 0, _b = Object.entries((_a = options.global) === null || _a === void 0 ? void 0 : _a.mocks); _i < _b.length; _i++) { | ||
var _c = _b[_i], k = _c[0], v = _c[1]; | ||
for (var _i = 0, _a = Object.entries(global.mocks); _i < _a.length; _i++) { | ||
var _b = _a[_i], k = _b[0], v = _b[1]; | ||
this[k] = v; | ||
@@ -440,44 +741,51 @@ } | ||
}; | ||
vm.mixin(mixin); | ||
app.mixin(mixin); | ||
} | ||
// use and plugins from mounting options | ||
if ((_g = (_f = options) === null || _f === void 0 ? void 0 : _f.global) === null || _g === void 0 ? void 0 : _g.plugins) { | ||
for (var _i = 0, _z = (_j = (_h = options) === null || _h === void 0 ? void 0 : _h.global) === null || _j === void 0 ? void 0 : _j.plugins; _i < _z.length; _i++) { | ||
var use = _z[_i]; | ||
vm.use(use); | ||
if ((_f = global) === null || _f === void 0 ? void 0 : _f.plugins) { | ||
for (var _i = 0, _o = global.plugins; _i < _o.length; _i++) { | ||
var use = _o[_i]; | ||
app.use(use); | ||
} | ||
} | ||
// use any mixins from mounting options | ||
if ((_l = (_k = options) === null || _k === void 0 ? void 0 : _k.global) === null || _l === void 0 ? void 0 : _l.mixins) { | ||
for (var _0 = 0, _1 = (_o = (_m = options) === null || _m === void 0 ? void 0 : _m.global) === null || _o === void 0 ? void 0 : _o.mixins; _0 < _1.length; _0++) { | ||
var mixin = _1[_0]; | ||
vm.mixin(mixin); | ||
if ((_g = global) === null || _g === void 0 ? void 0 : _g.mixins) { | ||
for (var _p = 0, _q = global.mixins; _p < _q.length; _p++) { | ||
var mixin = _q[_p]; | ||
app.mixin(mixin); | ||
} | ||
} | ||
if ((_q = (_p = options) === null || _p === void 0 ? void 0 : _p.global) === null || _q === void 0 ? void 0 : _q.components) { | ||
for (var _2 = 0, _3 = Object.keys((_s = (_r = options) === null || _r === void 0 ? void 0 : _r.global) === null || _s === void 0 ? void 0 : _s.components); _2 < _3.length; _2++) { | ||
var key = _3[_2]; | ||
vm.component(key, options.global.components[key]); | ||
if ((_h = global) === null || _h === void 0 ? void 0 : _h.components) { | ||
for (var _r = 0, _s = Object.keys(global.components); _r < _s.length; _r++) { | ||
var key = _s[_r]; | ||
app.component(key, global.components[key]); | ||
} | ||
} | ||
if ((_u = (_t = options) === null || _t === void 0 ? void 0 : _t.global) === null || _u === void 0 ? void 0 : _u.directives) { | ||
for (var _4 = 0, _5 = Object.keys((_w = (_v = options) === null || _v === void 0 ? void 0 : _v.global) === null || _w === void 0 ? void 0 : _w.directives); _4 < _5.length; _4++) { | ||
var key = _5[_4]; | ||
vm.directive(key, options.global.directives[key]); | ||
if ((_j = global) === null || _j === void 0 ? void 0 : _j.directives) { | ||
for (var _t = 0, _u = Object.keys(global.directives); _t < _u.length; _t++) { | ||
var key = _u[_t]; | ||
app.directive(key, global.directives[key]); | ||
} | ||
} | ||
// provide any values passed via provides mounting option | ||
if ((_y = (_x = options) === null || _x === void 0 ? void 0 : _x.global) === null || _y === void 0 ? void 0 : _y.provide) { | ||
for (var _6 = 0, _7 = Reflect.ownKeys(options.global.provide); _6 < _7.length; _6++) { | ||
var key = _7[_6]; | ||
if ((_k = global) === null || _k === void 0 ? void 0 : _k.provide) { | ||
for (var _v = 0, _w = Reflect.ownKeys(global.provide); _v < _w.length; _v++) { | ||
var key = _w[_v]; | ||
// @ts-ignore: https://github.com/microsoft/TypeScript/issues/1863 | ||
vm.provide(key, options.global.provide[key]); | ||
app.provide(key, global.provide[key]); | ||
} | ||
} | ||
// add tracking for emitted events | ||
var _8 = createEmitMixin(), emitMixin = _8.emitMixin, events = _8.events; | ||
vm.mixin(emitMixin); | ||
app.mixin(attachEmitListener()); | ||
// stubs | ||
if ((_m = (_l = options) === null || _l === void 0 ? void 0 : _l.global) === null || _m === void 0 ? void 0 : _m.stubs) { | ||
stubComponents(options.global.stubs); | ||
} | ||
else { | ||
vue.transformVNodeArgs(); | ||
} | ||
// mount the app! | ||
var app = vm.mount(el); | ||
return createWrapper(app, events, setProps); | ||
var vm = app.mount(el); | ||
var App = vm.$refs[MOUNT_COMPONENT_REF]; | ||
return createWrapper(app, App, setProps); | ||
} | ||
@@ -500,2 +808,3 @@ | ||
exports.RouterLinkStub = RouterLinkStub; | ||
exports.config = config; | ||
exports.mount = mount; |
/** | ||
* @vue/test-utils v2.0.0-alpha.1 | ||
* @vue/test-utils v2.0.0-alpha.2 | ||
* (c) 2020 Lachlan Miller | ||
@@ -8,3 +8,9 @@ * Released under the MIT License | ||
import { nextTick, getCurrentInstance, reactive, defineComponent, h, createApp } from 'vue'; | ||
import { nextTick, getCurrentInstance, transformVNodeArgs, h, reactive, defineComponent, createApp } from 'vue'; | ||
import camelCase from 'lodash/camelCase'; | ||
import upperFirst from 'lodash/upperFirst'; | ||
import kebabCase from 'lodash/kebabCase'; | ||
import flow from 'lodash/flow'; | ||
import mergeWith from 'lodash/mergeWith'; | ||
import eventTypes from 'dom-event-types'; | ||
@@ -82,2 +88,24 @@ /*! ***************************************************************************** | ||
var config = { | ||
global: {} | ||
}; | ||
var pascalCase = flow(camelCase, upperFirst); | ||
function mergeGlobalProperties(configGlobal, mountGlobal) { | ||
if (configGlobal === void 0) { configGlobal = {}; } | ||
if (mountGlobal === void 0) { mountGlobal = {}; } | ||
return mergeWith({}, configGlobal, mountGlobal, function (objValue, srcValue, key) { | ||
switch (key) { | ||
case 'mocks': | ||
case 'provide': | ||
case 'components': | ||
case 'directives': | ||
return __assign(__assign({}, objValue), srcValue); | ||
case 'plugins': | ||
case 'mixins': | ||
return __spreadArrays((objValue || []), (srcValue || [])).filter(Boolean); | ||
} | ||
}); | ||
} | ||
var ErrorWrapper = /** @class */ (function () { | ||
@@ -91,2 +119,5 @@ function ErrorWrapper(_a) { | ||
}; | ||
ErrorWrapper.prototype.vm = function () { | ||
throw this.wrapperError('vm'); | ||
}; | ||
ErrorWrapper.prototype.attributes = function () { | ||
@@ -104,7 +135,10 @@ throw this.wrapperError('attributes'); | ||
}; | ||
ErrorWrapper.prototype.get = function () { | ||
throw this.wrapperError('get'); | ||
}; | ||
ErrorWrapper.prototype.findAll = function () { | ||
throw this.wrapperError('findAll'); | ||
}; | ||
ErrorWrapper.prototype.setChecked = function () { | ||
throw this.wrapperError('setChecked'); | ||
ErrorWrapper.prototype.setProps = function () { | ||
throw this.wrapperError('setProps'); | ||
}; | ||
@@ -120,5 +154,65 @@ ErrorWrapper.prototype.setValue = function () { | ||
}; | ||
ErrorWrapper.prototype.unmount = function () { | ||
throw this.wrapperError('unmount'); | ||
}; | ||
return ErrorWrapper; | ||
}()); | ||
var keyCodesByKeyName = { | ||
backspace: 8, | ||
tab: 9, | ||
enter: 13, | ||
esc: 27, | ||
space: 32, | ||
pageup: 33, | ||
pagedown: 34, | ||
end: 35, | ||
home: 36, | ||
left: 37, | ||
up: 38, | ||
right: 39, | ||
down: 40, | ||
insert: 45, | ||
delete: 46 | ||
}; | ||
function getEventProperties(eventParams) { | ||
var modifier = eventParams.modifier, meta = eventParams.meta, options = eventParams.options; | ||
var keyCode = keyCodesByKeyName[modifier] || | ||
(options && (options.keyCode || options.code)); | ||
return __assign(__assign({}, options), { bubbles: meta.bubbles, meta: meta.cancelable, | ||
// Any derived options should go here | ||
keyCode: keyCode, code: keyCode }); | ||
} | ||
function createEvent(eventParams) { | ||
var eventType = eventParams.eventType, meta = eventParams.meta; | ||
var metaEventInterface = window[meta.eventInterface]; | ||
var SupportedEventInterface = typeof metaEventInterface === 'function' ? metaEventInterface : window.Event; | ||
var eventProperties = getEventProperties(eventParams); | ||
var event = new SupportedEventInterface(eventType, | ||
// event properties can only be added when the event is instantiated | ||
// custom properties must be added after the event has been instantiated | ||
eventProperties); | ||
return event; | ||
} | ||
function createDOMEvent(eventString, options) { | ||
var _a = eventString.split('.'), eventType = _a[0], modifier = _a[1]; | ||
var meta = eventTypes[eventType] || { | ||
eventInterface: 'Event', | ||
cancelable: true, | ||
bubbles: true | ||
}; | ||
var eventParams = { eventType: eventType, modifier: modifier, meta: meta, options: options }; | ||
var event = createEvent(eventParams); | ||
var eventPrototype = Object.getPrototypeOf(event); | ||
options && | ||
Object.keys(options).forEach(function (key) { | ||
var propertyDescriptor = Object.getOwnPropertyDescriptor(eventPrototype, key); | ||
var canSetProperty = !(propertyDescriptor && propertyDescriptor.set === undefined); | ||
if (canSetProperty) { | ||
event[key] = options[key]; | ||
} | ||
}); | ||
return event; | ||
} | ||
var DOMWrapper = /** @class */ (function () { | ||
@@ -160,2 +254,9 @@ function DOMWrapper(element) { | ||
}; | ||
DOMWrapper.prototype.get = function (selector) { | ||
var result = this.find(selector); | ||
if (result instanceof ErrorWrapper) { | ||
throw new Error("Unable to find " + selector + " within: " + this.html()); | ||
} | ||
return result; | ||
}; | ||
DOMWrapper.prototype.findAll = function (selector) { | ||
@@ -176,3 +277,3 @@ return Array.from(this.element.querySelectorAll(selector)).map(function (x) { return new DOMWrapper(x); }); | ||
// attempting set the same value twice | ||
// this is beacuse in a browser setting checked = true when it is | ||
// this is because in a browser setting checked = true when it is | ||
// already true is a no-op; no change event is triggered | ||
@@ -227,11 +328,32 @@ if (checked === element.checked) { | ||
}; | ||
DOMWrapper.prototype.trigger = function (eventString) { | ||
DOMWrapper.prototype.trigger = function (eventString, options) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var evt; | ||
var isDisabled, event_1; | ||
var _this = this; | ||
return __generator(this, function (_a) { | ||
evt = document.createEvent('Event'); | ||
evt.initEvent(eventString); | ||
if (this.element) { | ||
this.element.dispatchEvent(evt); | ||
if (options && options['target']) { | ||
throw Error("[vue-test-utils]: you cannot set the target value of an event. See the notes section " + | ||
"of the docs for more details\u2014" + | ||
"https://vue-test-utils.vuejs.org/api/wrapper/trigger.html"); | ||
} | ||
isDisabled = function () { | ||
var validTagsToBeDisabled = [ | ||
'BUTTON', | ||
'COMMAND', | ||
'FIELDSET', | ||
'KEYGEN', | ||
'OPTGROUP', | ||
'OPTION', | ||
'SELECT', | ||
'TEXTAREA', | ||
'INPUT' | ||
]; | ||
var hasDisabledAttribute = _this.attributes().disabled !== undefined; | ||
var elementCanBeDisabled = validTagsToBeDisabled.includes(_this.element.tagName); | ||
return hasDisabledAttribute && elementCanBeDisabled; | ||
}; | ||
if (this.element && !isDisabled()) { | ||
event_1 = createDOMEvent(eventString, options); | ||
this.element.dispatchEvent(event_1); | ||
} | ||
return [2 /*return*/, nextTick]; | ||
@@ -244,23 +366,95 @@ }); | ||
var MOUNT_ELEMENT_ID = 'app'; | ||
// Make a map and return a function for checking if a key | ||
const EMPTY_OBJ = (process.env.NODE_ENV !== 'production') | ||
? Object.freeze({}) | ||
: {}; | ||
const cacheStringFunction = (fn) => { | ||
const cache = Object.create(null); | ||
return ((str) => { | ||
const hit = cache[str]; | ||
return hit || (cache[str] = fn(str)); | ||
}); | ||
}; | ||
const camelizeRE = /-(\w)/g; | ||
const camelize = cacheStringFunction((str) => { | ||
return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : '')); | ||
}); | ||
const capitalize = cacheStringFunction((str) => { | ||
return str.charAt(0).toUpperCase() + str.slice(1); | ||
}); | ||
function matchName(target, sourceName) { | ||
var camelized = camelize(target); | ||
var capitalized = capitalize(camelized); | ||
return (sourceName && | ||
(sourceName === target || | ||
sourceName === camelized || | ||
sourceName === capitalized || | ||
capitalize(camelize(sourceName)) === capitalized)); | ||
} | ||
/** | ||
* Detect whether a selector matches a VNode | ||
* @param node | ||
* @param selector | ||
* @return {boolean | ((value: any) => boolean)} | ||
*/ | ||
function matches(node, selector) { | ||
var _a, _b, _c; | ||
// do not return none Vue components | ||
if (!node.component) | ||
return false; | ||
if (typeof selector === 'string') { | ||
return (_c = (_a = node.el) === null || _a === void 0 ? void 0 : (_b = _a).matches) === null || _c === void 0 ? void 0 : _c.call(_b, selector); | ||
} | ||
if (typeof selector === 'object' && typeof node.type === 'object') { | ||
if (selector.name && ('name' in node.type || 'displayName' in node.type)) { | ||
// match normal component definitions or functional components | ||
return matchName(selector.name, node.type.name || node.type.displayName); | ||
} | ||
} | ||
return false; | ||
} | ||
/** | ||
* Collect all children | ||
* @param nodes | ||
* @param children | ||
*/ | ||
function aggregateChildren(nodes, children) { | ||
if (children && Array.isArray(children)) { | ||
__spreadArrays(children).reverse().forEach(function (n) { | ||
nodes.unshift(n); | ||
}); | ||
} | ||
} | ||
function findAllVNodes(vnode, selector) { | ||
var _a; | ||
var matchingNodes = []; | ||
var nodes = [vnode]; | ||
while (nodes.length) { | ||
var node = nodes.shift(); | ||
aggregateChildren(nodes, node.children); | ||
aggregateChildren(nodes, (_a = node.component) === null || _a === void 0 ? void 0 : _a.subTree.children); | ||
if (matches(node, selector)) { | ||
matchingNodes.push(node); | ||
} | ||
} | ||
return matchingNodes; | ||
} | ||
function find(root, selector) { | ||
return findAllVNodes(root, selector).map(function (vnode) { return vnode.component.proxy; }); | ||
} | ||
var VueWrapper = /** @class */ (function () { | ||
function VueWrapper(vm, events, setProps) { | ||
this.__emitted = {}; | ||
this.__vm = vm; | ||
function VueWrapper(app, vm, setProps) { | ||
this.__app = app; | ||
this.rootVM = vm.$root; | ||
this.componentVM = vm; | ||
this.__setProps = setProps; | ||
this.componentVM = this.vm.$refs['VTU_COMPONENT']; | ||
this.__emitted = events; | ||
} | ||
Object.defineProperty(VueWrapper.prototype, "appRootNode", { | ||
get: function () { | ||
return document.getElementById(MOUNT_ELEMENT_ID); | ||
}, | ||
enumerable: true, | ||
configurable: true | ||
}); | ||
Object.defineProperty(VueWrapper.prototype, "hasMultipleRoots", { | ||
get: function () { | ||
// if the subtree is an array of children, we have multiple root nodes | ||
return this.componentVM.$.subTree.shapeFlag === 16 /* ARRAY_CHILDREN */; | ||
return this.vm.$.subTree.shapeFlag === 16 /* ARRAY_CHILDREN */; | ||
}, | ||
@@ -272,3 +466,3 @@ enumerable: true, | ||
get: function () { | ||
return this.componentVM.$el.parentElement; | ||
return this.vm.$el.parentElement; | ||
}, | ||
@@ -281,3 +475,3 @@ enumerable: true, | ||
// if the component has multiple root elements, we use the parent's element | ||
return this.hasMultipleRoots ? this.parentElement : this.componentVM.$el; | ||
return this.hasMultipleRoots ? this.parentElement : this.vm.$el; | ||
}, | ||
@@ -289,3 +483,3 @@ enumerable: true, | ||
get: function () { | ||
return this.__vm; | ||
return this.componentVM; | ||
}, | ||
@@ -295,2 +489,7 @@ enumerable: true, | ||
}); | ||
VueWrapper.prototype.props = function (selector) { | ||
return selector | ||
? this.componentVM.$props[selector] | ||
: this.componentVM.$props; | ||
}; | ||
VueWrapper.prototype.classes = function (className) { | ||
@@ -306,6 +505,8 @@ return new DOMWrapper(this.element).classes(className); | ||
VueWrapper.prototype.emitted = function () { | ||
return this.__emitted; | ||
// TODO Should we define this? | ||
// @ts-ignore | ||
return this.vm.__emitted; | ||
}; | ||
VueWrapper.prototype.html = function () { | ||
return this.appRootNode.innerHTML; | ||
return this.parentElement.innerHTML; | ||
}; | ||
@@ -324,25 +525,58 @@ VueWrapper.prototype.text = function () { | ||
}; | ||
VueWrapper.prototype.get = function (selector) { | ||
var result = this.find(selector); | ||
if (result instanceof ErrorWrapper) { | ||
throw new Error("Unable to find " + selector + " within: " + this.html()); | ||
} | ||
return result; | ||
}; | ||
VueWrapper.prototype.findComponent = function (selector) { | ||
if (typeof selector === 'object' && 'ref' in selector) { | ||
return createWrapper(null, this.vm.$refs[selector.ref]); | ||
} | ||
var result = find(this.vm.$.subTree, selector); | ||
if (!result.length) | ||
return new ErrorWrapper({ selector: selector }); | ||
return createWrapper(null, result[0]); | ||
}; | ||
VueWrapper.prototype.findAllComponents = function (selector) { | ||
return find(this.vm.$.subTree, selector).map(function (c) { return createWrapper(null, c); }); | ||
}; | ||
VueWrapper.prototype.findAll = function (selector) { | ||
var results = this.appRootNode.querySelectorAll(selector); | ||
return Array.from(results).map(function (x) { return new DOMWrapper(x); }); | ||
var results = this.parentElement.querySelectorAll(selector); | ||
return Array.from(results).map(function (element) { return new DOMWrapper(element); }); | ||
}; | ||
VueWrapper.prototype.setProps = function (props) { | ||
// if this VM's parent is not the root, error out | ||
if (this.vm.$parent !== this.rootVM) { | ||
throw Error('You can only use setProps on your mounted component'); | ||
} | ||
this.__setProps(props); | ||
return nextTick(); | ||
}; | ||
VueWrapper.prototype.trigger = function (eventString) { | ||
VueWrapper.prototype.trigger = function (eventString, options) { | ||
var rootElementWrapper = new DOMWrapper(this.element); | ||
return rootElementWrapper.trigger(eventString); | ||
return rootElementWrapper.trigger(eventString, options); | ||
}; | ||
VueWrapper.prototype.unmount = function () { | ||
// preventing dispose of child component | ||
if (!this.__app) { | ||
throw new Error("wrapper.unmount() can only be called by the root wrapper"); | ||
} | ||
if (this.parentElement) { | ||
this.parentElement.removeChild(this.element); | ||
} | ||
this.__app.unmount(this.element); | ||
}; | ||
return VueWrapper; | ||
}()); | ||
function createWrapper(vm, events, setProps) { | ||
return new VueWrapper(vm, events, setProps); | ||
function createWrapper(app, vm, setProps) { | ||
return new VueWrapper(app, vm, setProps); | ||
} | ||
var createEmitMixin = function () { | ||
var events = {}; | ||
var emitMixin = { | ||
var attachEmitListener = function () { | ||
return { | ||
beforeCreate: function () { | ||
var originalEmit = getCurrentInstance().emit; | ||
var events = {}; | ||
this.__emitted = events; | ||
getCurrentInstance().emit = function (event) { | ||
@@ -356,10 +590,6 @@ var args = []; | ||
: (events[event] = [__spreadArrays(args)]); | ||
return originalEmit.call.apply(originalEmit, __spreadArrays([getCurrentInstance(), event], args)); | ||
return __spreadArrays([event], args); | ||
}; | ||
} | ||
}; | ||
return { | ||
events: events, | ||
emitMixin: emitMixin | ||
}; | ||
}; | ||
@@ -379,4 +609,70 @@ | ||
var MOUNT_ELEMENT_ID = 'app'; | ||
var MOUNT_COMPONENT_REF = 'VTU_COMPONENT'; | ||
var MOUNT_PARENT_NAME = 'VTU_ROOT'; | ||
var createStub = function (options) { | ||
var tag = options.name ? options.name + "-stub" : 'anonymous-stub'; | ||
var render = function () { return h(tag); }; | ||
return { name: tag, render: render }; | ||
}; | ||
var resolveComponentStubByName = function (componentName, stubs) { | ||
var componentPascalName = pascalCase(componentName); | ||
var componentKebabName = kebabCase(componentName); | ||
if (Array.isArray(stubs) && stubs.length) { | ||
// ['Foo', 'Bar'] => { Foo: true, Bar: true } | ||
stubs = stubs.reduce(function (acc, current) { | ||
acc[current] = true; | ||
return acc; | ||
}, {}); | ||
} | ||
for (var _i = 0, _a = Object.entries(stubs); _i < _a.length; _i++) { | ||
var _b = _a[_i], stubKey = _b[0], value = _b[1]; | ||
if (stubKey === componentPascalName || | ||
stubKey === componentKebabName || | ||
stubKey === componentName) { | ||
return value; | ||
} | ||
} | ||
}; | ||
var isHTMLElement = function (args) { | ||
return Array.isArray(args) && typeof args[0] === 'string'; | ||
}; | ||
var isCommentOrFragment = function (args) { return typeof args[0] === 'symbol'; }; | ||
var isParent = function (args) { | ||
return typeof args[0] === 'object' && args[0]['name'] === 'VTU_COMPONENT'; | ||
}; | ||
var isComponent = function (args) { return typeof args[0] === 'object'; }; | ||
function stubComponents(stubs) { | ||
transformVNodeArgs(function (args) { | ||
// args[0] can either be: | ||
// 1. a HTML tag (div, span...) | ||
// 2. An object of component options, such as { name: 'foo', render: [Function], props: {...} } | ||
// Depending what it is, we do different things. | ||
if (isHTMLElement(args) || isCommentOrFragment(args) || isParent(args)) { | ||
return args; | ||
} | ||
if (isComponent(args)) { | ||
var name_1 = args[0]['name']; | ||
if (!name_1) { | ||
return args; | ||
} | ||
var stub = resolveComponentStubByName(name_1, stubs); | ||
// we return a stub by matching Vue's `h` function | ||
// where the signature is h(Component, props) | ||
// case 1: default stub | ||
if (stub === true) { | ||
return [createStub({ name: name_1 }), {}]; | ||
} | ||
// case 2: custom implementation | ||
if (typeof stub === 'object') { | ||
return [stubs[name_1], {}]; | ||
} | ||
} | ||
return args; | ||
}); | ||
} | ||
function mount(originalComponent, options) { | ||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y; | ||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m; | ||
var component = __assign({}, originalComponent); | ||
@@ -403,9 +699,12 @@ // Reset the document.body | ||
var dataMixin = createDataMixin(options.data()); | ||
component.mixins = __spreadArrays((component.mixins || []), [dataMixin]); | ||
component.mixins = __spreadArrays((component.mixins || []), [ | ||
dataMixin | ||
]); | ||
} | ||
// we define props as reactive so that way when we update them with `setProps` | ||
// Vue's reactivity system will cause a rerender. | ||
var props = reactive(__assign(__assign({}, (_c = options) === null || _c === void 0 ? void 0 : _c.props), { ref: 'VTU_COMPONENT' })); | ||
var props = reactive(__assign(__assign({}, (_c = options) === null || _c === void 0 ? void 0 : _c.props), { ref: MOUNT_COMPONENT_REF })); | ||
// create the wrapper component | ||
var Parent = defineComponent({ | ||
name: MOUNT_PARENT_NAME, | ||
render: function () { | ||
@@ -420,13 +719,13 @@ return h(component, props, slots); | ||
} | ||
return app.$nextTick(); | ||
return vm.$nextTick(); | ||
}; | ||
// create the vm | ||
var vm = createApp(Parent); | ||
// create the app | ||
var app = createApp(Parent); | ||
var global = mergeGlobalProperties(config.global, (_d = options) === null || _d === void 0 ? void 0 : _d.global); | ||
// global mocks mixin | ||
if ((_e = (_d = options) === null || _d === void 0 ? void 0 : _d.global) === null || _e === void 0 ? void 0 : _e.mocks) { | ||
if ((_e = global) === null || _e === void 0 ? void 0 : _e.mocks) { | ||
var mixin = { | ||
beforeCreate: function () { | ||
var _a; | ||
for (var _i = 0, _b = Object.entries((_a = options.global) === null || _a === void 0 ? void 0 : _a.mocks); _i < _b.length; _i++) { | ||
var _c = _b[_i], k = _c[0], v = _c[1]; | ||
for (var _i = 0, _a = Object.entries(global.mocks); _i < _a.length; _i++) { | ||
var _b = _a[_i], k = _b[0], v = _b[1]; | ||
this[k] = v; | ||
@@ -436,44 +735,51 @@ } | ||
}; | ||
vm.mixin(mixin); | ||
app.mixin(mixin); | ||
} | ||
// use and plugins from mounting options | ||
if ((_g = (_f = options) === null || _f === void 0 ? void 0 : _f.global) === null || _g === void 0 ? void 0 : _g.plugins) { | ||
for (var _i = 0, _z = (_j = (_h = options) === null || _h === void 0 ? void 0 : _h.global) === null || _j === void 0 ? void 0 : _j.plugins; _i < _z.length; _i++) { | ||
var use = _z[_i]; | ||
vm.use(use); | ||
if ((_f = global) === null || _f === void 0 ? void 0 : _f.plugins) { | ||
for (var _i = 0, _o = global.plugins; _i < _o.length; _i++) { | ||
var use = _o[_i]; | ||
app.use(use); | ||
} | ||
} | ||
// use any mixins from mounting options | ||
if ((_l = (_k = options) === null || _k === void 0 ? void 0 : _k.global) === null || _l === void 0 ? void 0 : _l.mixins) { | ||
for (var _0 = 0, _1 = (_o = (_m = options) === null || _m === void 0 ? void 0 : _m.global) === null || _o === void 0 ? void 0 : _o.mixins; _0 < _1.length; _0++) { | ||
var mixin = _1[_0]; | ||
vm.mixin(mixin); | ||
if ((_g = global) === null || _g === void 0 ? void 0 : _g.mixins) { | ||
for (var _p = 0, _q = global.mixins; _p < _q.length; _p++) { | ||
var mixin = _q[_p]; | ||
app.mixin(mixin); | ||
} | ||
} | ||
if ((_q = (_p = options) === null || _p === void 0 ? void 0 : _p.global) === null || _q === void 0 ? void 0 : _q.components) { | ||
for (var _2 = 0, _3 = Object.keys((_s = (_r = options) === null || _r === void 0 ? void 0 : _r.global) === null || _s === void 0 ? void 0 : _s.components); _2 < _3.length; _2++) { | ||
var key = _3[_2]; | ||
vm.component(key, options.global.components[key]); | ||
if ((_h = global) === null || _h === void 0 ? void 0 : _h.components) { | ||
for (var _r = 0, _s = Object.keys(global.components); _r < _s.length; _r++) { | ||
var key = _s[_r]; | ||
app.component(key, global.components[key]); | ||
} | ||
} | ||
if ((_u = (_t = options) === null || _t === void 0 ? void 0 : _t.global) === null || _u === void 0 ? void 0 : _u.directives) { | ||
for (var _4 = 0, _5 = Object.keys((_w = (_v = options) === null || _v === void 0 ? void 0 : _v.global) === null || _w === void 0 ? void 0 : _w.directives); _4 < _5.length; _4++) { | ||
var key = _5[_4]; | ||
vm.directive(key, options.global.directives[key]); | ||
if ((_j = global) === null || _j === void 0 ? void 0 : _j.directives) { | ||
for (var _t = 0, _u = Object.keys(global.directives); _t < _u.length; _t++) { | ||
var key = _u[_t]; | ||
app.directive(key, global.directives[key]); | ||
} | ||
} | ||
// provide any values passed via provides mounting option | ||
if ((_y = (_x = options) === null || _x === void 0 ? void 0 : _x.global) === null || _y === void 0 ? void 0 : _y.provide) { | ||
for (var _6 = 0, _7 = Reflect.ownKeys(options.global.provide); _6 < _7.length; _6++) { | ||
var key = _7[_6]; | ||
if ((_k = global) === null || _k === void 0 ? void 0 : _k.provide) { | ||
for (var _v = 0, _w = Reflect.ownKeys(global.provide); _v < _w.length; _v++) { | ||
var key = _w[_v]; | ||
// @ts-ignore: https://github.com/microsoft/TypeScript/issues/1863 | ||
vm.provide(key, options.global.provide[key]); | ||
app.provide(key, global.provide[key]); | ||
} | ||
} | ||
// add tracking for emitted events | ||
var _8 = createEmitMixin(), emitMixin = _8.emitMixin, events = _8.events; | ||
vm.mixin(emitMixin); | ||
app.mixin(attachEmitListener()); | ||
// stubs | ||
if ((_m = (_l = options) === null || _l === void 0 ? void 0 : _l.global) === null || _m === void 0 ? void 0 : _m.stubs) { | ||
stubComponents(options.global.stubs); | ||
} | ||
else { | ||
transformVNodeArgs(); | ||
} | ||
// mount the app! | ||
var app = vm.mount(el); | ||
return createWrapper(app, events, setProps); | ||
var vm = app.mount(el); | ||
var App = vm.$refs[MOUNT_COMPONENT_REF]; | ||
return createWrapper(app, App, setProps); | ||
} | ||
@@ -495,2 +801,2 @@ | ||
export { RouterLinkStub, mount }; | ||
export { RouterLinkStub, config, mount }; |
/** | ||
* @vue/test-utils v2.0.0-alpha.1 | ||
* @vue/test-utils v2.0.0-alpha.2 | ||
* (c) 2020 Lachlan Miller | ||
@@ -8,3 +8,9 @@ * Released under the MIT License | ||
import { nextTick, getCurrentInstance, reactive, defineComponent, h, createApp } from 'vue'; | ||
import { nextTick, getCurrentInstance, transformVNodeArgs, h, reactive, defineComponent, createApp } from 'vue'; | ||
import camelCase from 'lodash/camelCase'; | ||
import upperFirst from 'lodash/upperFirst'; | ||
import kebabCase from 'lodash/kebabCase'; | ||
import flow from 'lodash/flow'; | ||
import mergeWith from 'lodash/mergeWith'; | ||
import eventTypes from 'dom-event-types'; | ||
@@ -82,2 +88,24 @@ /*! ***************************************************************************** | ||
var config = { | ||
global: {} | ||
}; | ||
var pascalCase = flow(camelCase, upperFirst); | ||
function mergeGlobalProperties(configGlobal, mountGlobal) { | ||
if (configGlobal === void 0) { configGlobal = {}; } | ||
if (mountGlobal === void 0) { mountGlobal = {}; } | ||
return mergeWith({}, configGlobal, mountGlobal, function (objValue, srcValue, key) { | ||
switch (key) { | ||
case 'mocks': | ||
case 'provide': | ||
case 'components': | ||
case 'directives': | ||
return __assign(__assign({}, objValue), srcValue); | ||
case 'plugins': | ||
case 'mixins': | ||
return __spreadArrays((objValue || []), (srcValue || [])).filter(Boolean); | ||
} | ||
}); | ||
} | ||
var ErrorWrapper = /** @class */ (function () { | ||
@@ -91,2 +119,5 @@ function ErrorWrapper(_a) { | ||
}; | ||
ErrorWrapper.prototype.vm = function () { | ||
throw this.wrapperError('vm'); | ||
}; | ||
ErrorWrapper.prototype.attributes = function () { | ||
@@ -104,7 +135,10 @@ throw this.wrapperError('attributes'); | ||
}; | ||
ErrorWrapper.prototype.get = function () { | ||
throw this.wrapperError('get'); | ||
}; | ||
ErrorWrapper.prototype.findAll = function () { | ||
throw this.wrapperError('findAll'); | ||
}; | ||
ErrorWrapper.prototype.setChecked = function () { | ||
throw this.wrapperError('setChecked'); | ||
ErrorWrapper.prototype.setProps = function () { | ||
throw this.wrapperError('setProps'); | ||
}; | ||
@@ -120,5 +154,65 @@ ErrorWrapper.prototype.setValue = function () { | ||
}; | ||
ErrorWrapper.prototype.unmount = function () { | ||
throw this.wrapperError('unmount'); | ||
}; | ||
return ErrorWrapper; | ||
}()); | ||
var keyCodesByKeyName = { | ||
backspace: 8, | ||
tab: 9, | ||
enter: 13, | ||
esc: 27, | ||
space: 32, | ||
pageup: 33, | ||
pagedown: 34, | ||
end: 35, | ||
home: 36, | ||
left: 37, | ||
up: 38, | ||
right: 39, | ||
down: 40, | ||
insert: 45, | ||
delete: 46 | ||
}; | ||
function getEventProperties(eventParams) { | ||
var modifier = eventParams.modifier, meta = eventParams.meta, options = eventParams.options; | ||
var keyCode = keyCodesByKeyName[modifier] || | ||
(options && (options.keyCode || options.code)); | ||
return __assign(__assign({}, options), { bubbles: meta.bubbles, meta: meta.cancelable, | ||
// Any derived options should go here | ||
keyCode: keyCode, code: keyCode }); | ||
} | ||
function createEvent(eventParams) { | ||
var eventType = eventParams.eventType, meta = eventParams.meta; | ||
var metaEventInterface = window[meta.eventInterface]; | ||
var SupportedEventInterface = typeof metaEventInterface === 'function' ? metaEventInterface : window.Event; | ||
var eventProperties = getEventProperties(eventParams); | ||
var event = new SupportedEventInterface(eventType, | ||
// event properties can only be added when the event is instantiated | ||
// custom properties must be added after the event has been instantiated | ||
eventProperties); | ||
return event; | ||
} | ||
function createDOMEvent(eventString, options) { | ||
var _a = eventString.split('.'), eventType = _a[0], modifier = _a[1]; | ||
var meta = eventTypes[eventType] || { | ||
eventInterface: 'Event', | ||
cancelable: true, | ||
bubbles: true | ||
}; | ||
var eventParams = { eventType: eventType, modifier: modifier, meta: meta, options: options }; | ||
var event = createEvent(eventParams); | ||
var eventPrototype = Object.getPrototypeOf(event); | ||
options && | ||
Object.keys(options).forEach(function (key) { | ||
var propertyDescriptor = Object.getOwnPropertyDescriptor(eventPrototype, key); | ||
var canSetProperty = !(propertyDescriptor && propertyDescriptor.set === undefined); | ||
if (canSetProperty) { | ||
event[key] = options[key]; | ||
} | ||
}); | ||
return event; | ||
} | ||
var DOMWrapper = /** @class */ (function () { | ||
@@ -160,2 +254,9 @@ function DOMWrapper(element) { | ||
}; | ||
DOMWrapper.prototype.get = function (selector) { | ||
var result = this.find(selector); | ||
if (result instanceof ErrorWrapper) { | ||
throw new Error("Unable to find " + selector + " within: " + this.html()); | ||
} | ||
return result; | ||
}; | ||
DOMWrapper.prototype.findAll = function (selector) { | ||
@@ -176,3 +277,3 @@ return Array.from(this.element.querySelectorAll(selector)).map(function (x) { return new DOMWrapper(x); }); | ||
// attempting set the same value twice | ||
// this is beacuse in a browser setting checked = true when it is | ||
// this is because in a browser setting checked = true when it is | ||
// already true is a no-op; no change event is triggered | ||
@@ -227,11 +328,32 @@ if (checked === element.checked) { | ||
}; | ||
DOMWrapper.prototype.trigger = function (eventString) { | ||
DOMWrapper.prototype.trigger = function (eventString, options) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var evt; | ||
var isDisabled, event_1; | ||
var _this = this; | ||
return __generator(this, function (_a) { | ||
evt = document.createEvent('Event'); | ||
evt.initEvent(eventString); | ||
if (this.element) { | ||
this.element.dispatchEvent(evt); | ||
if (options && options['target']) { | ||
throw Error("[vue-test-utils]: you cannot set the target value of an event. See the notes section " + | ||
"of the docs for more details\u2014" + | ||
"https://vue-test-utils.vuejs.org/api/wrapper/trigger.html"); | ||
} | ||
isDisabled = function () { | ||
var validTagsToBeDisabled = [ | ||
'BUTTON', | ||
'COMMAND', | ||
'FIELDSET', | ||
'KEYGEN', | ||
'OPTGROUP', | ||
'OPTION', | ||
'SELECT', | ||
'TEXTAREA', | ||
'INPUT' | ||
]; | ||
var hasDisabledAttribute = _this.attributes().disabled !== undefined; | ||
var elementCanBeDisabled = validTagsToBeDisabled.includes(_this.element.tagName); | ||
return hasDisabledAttribute && elementCanBeDisabled; | ||
}; | ||
if (this.element && !isDisabled()) { | ||
event_1 = createDOMEvent(eventString, options); | ||
this.element.dispatchEvent(event_1); | ||
} | ||
return [2 /*return*/, nextTick]; | ||
@@ -244,23 +366,95 @@ }); | ||
var MOUNT_ELEMENT_ID = 'app'; | ||
// Make a map and return a function for checking if a key | ||
const EMPTY_OBJ = (process.env.NODE_ENV !== 'production') | ||
? Object.freeze({}) | ||
: {}; | ||
const cacheStringFunction = (fn) => { | ||
const cache = Object.create(null); | ||
return ((str) => { | ||
const hit = cache[str]; | ||
return hit || (cache[str] = fn(str)); | ||
}); | ||
}; | ||
const camelizeRE = /-(\w)/g; | ||
const camelize = cacheStringFunction((str) => { | ||
return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : '')); | ||
}); | ||
const capitalize = cacheStringFunction((str) => { | ||
return str.charAt(0).toUpperCase() + str.slice(1); | ||
}); | ||
function matchName(target, sourceName) { | ||
var camelized = camelize(target); | ||
var capitalized = capitalize(camelized); | ||
return (sourceName && | ||
(sourceName === target || | ||
sourceName === camelized || | ||
sourceName === capitalized || | ||
capitalize(camelize(sourceName)) === capitalized)); | ||
} | ||
/** | ||
* Detect whether a selector matches a VNode | ||
* @param node | ||
* @param selector | ||
* @return {boolean | ((value: any) => boolean)} | ||
*/ | ||
function matches(node, selector) { | ||
var _a, _b, _c; | ||
// do not return none Vue components | ||
if (!node.component) | ||
return false; | ||
if (typeof selector === 'string') { | ||
return (_c = (_a = node.el) === null || _a === void 0 ? void 0 : (_b = _a).matches) === null || _c === void 0 ? void 0 : _c.call(_b, selector); | ||
} | ||
if (typeof selector === 'object' && typeof node.type === 'object') { | ||
if (selector.name && ('name' in node.type || 'displayName' in node.type)) { | ||
// match normal component definitions or functional components | ||
return matchName(selector.name, node.type.name || node.type.displayName); | ||
} | ||
} | ||
return false; | ||
} | ||
/** | ||
* Collect all children | ||
* @param nodes | ||
* @param children | ||
*/ | ||
function aggregateChildren(nodes, children) { | ||
if (children && Array.isArray(children)) { | ||
__spreadArrays(children).reverse().forEach(function (n) { | ||
nodes.unshift(n); | ||
}); | ||
} | ||
} | ||
function findAllVNodes(vnode, selector) { | ||
var _a; | ||
var matchingNodes = []; | ||
var nodes = [vnode]; | ||
while (nodes.length) { | ||
var node = nodes.shift(); | ||
aggregateChildren(nodes, node.children); | ||
aggregateChildren(nodes, (_a = node.component) === null || _a === void 0 ? void 0 : _a.subTree.children); | ||
if (matches(node, selector)) { | ||
matchingNodes.push(node); | ||
} | ||
} | ||
return matchingNodes; | ||
} | ||
function find(root, selector) { | ||
return findAllVNodes(root, selector).map(function (vnode) { return vnode.component.proxy; }); | ||
} | ||
var VueWrapper = /** @class */ (function () { | ||
function VueWrapper(vm, events, setProps) { | ||
this.__emitted = {}; | ||
this.__vm = vm; | ||
function VueWrapper(app, vm, setProps) { | ||
this.__app = app; | ||
this.rootVM = vm.$root; | ||
this.componentVM = vm; | ||
this.__setProps = setProps; | ||
this.componentVM = this.vm.$refs['VTU_COMPONENT']; | ||
this.__emitted = events; | ||
} | ||
Object.defineProperty(VueWrapper.prototype, "appRootNode", { | ||
get: function () { | ||
return document.getElementById(MOUNT_ELEMENT_ID); | ||
}, | ||
enumerable: true, | ||
configurable: true | ||
}); | ||
Object.defineProperty(VueWrapper.prototype, "hasMultipleRoots", { | ||
get: function () { | ||
// if the subtree is an array of children, we have multiple root nodes | ||
return this.componentVM.$.subTree.shapeFlag === 16 /* ARRAY_CHILDREN */; | ||
return this.vm.$.subTree.shapeFlag === 16 /* ARRAY_CHILDREN */; | ||
}, | ||
@@ -272,3 +466,3 @@ enumerable: true, | ||
get: function () { | ||
return this.componentVM.$el.parentElement; | ||
return this.vm.$el.parentElement; | ||
}, | ||
@@ -281,3 +475,3 @@ enumerable: true, | ||
// if the component has multiple root elements, we use the parent's element | ||
return this.hasMultipleRoots ? this.parentElement : this.componentVM.$el; | ||
return this.hasMultipleRoots ? this.parentElement : this.vm.$el; | ||
}, | ||
@@ -289,3 +483,3 @@ enumerable: true, | ||
get: function () { | ||
return this.__vm; | ||
return this.componentVM; | ||
}, | ||
@@ -295,2 +489,7 @@ enumerable: true, | ||
}); | ||
VueWrapper.prototype.props = function (selector) { | ||
return selector | ||
? this.componentVM.$props[selector] | ||
: this.componentVM.$props; | ||
}; | ||
VueWrapper.prototype.classes = function (className) { | ||
@@ -306,6 +505,8 @@ return new DOMWrapper(this.element).classes(className); | ||
VueWrapper.prototype.emitted = function () { | ||
return this.__emitted; | ||
// TODO Should we define this? | ||
// @ts-ignore | ||
return this.vm.__emitted; | ||
}; | ||
VueWrapper.prototype.html = function () { | ||
return this.appRootNode.innerHTML; | ||
return this.parentElement.innerHTML; | ||
}; | ||
@@ -324,25 +525,58 @@ VueWrapper.prototype.text = function () { | ||
}; | ||
VueWrapper.prototype.get = function (selector) { | ||
var result = this.find(selector); | ||
if (result instanceof ErrorWrapper) { | ||
throw new Error("Unable to find " + selector + " within: " + this.html()); | ||
} | ||
return result; | ||
}; | ||
VueWrapper.prototype.findComponent = function (selector) { | ||
if (typeof selector === 'object' && 'ref' in selector) { | ||
return createWrapper(null, this.vm.$refs[selector.ref]); | ||
} | ||
var result = find(this.vm.$.subTree, selector); | ||
if (!result.length) | ||
return new ErrorWrapper({ selector: selector }); | ||
return createWrapper(null, result[0]); | ||
}; | ||
VueWrapper.prototype.findAllComponents = function (selector) { | ||
return find(this.vm.$.subTree, selector).map(function (c) { return createWrapper(null, c); }); | ||
}; | ||
VueWrapper.prototype.findAll = function (selector) { | ||
var results = this.appRootNode.querySelectorAll(selector); | ||
return Array.from(results).map(function (x) { return new DOMWrapper(x); }); | ||
var results = this.parentElement.querySelectorAll(selector); | ||
return Array.from(results).map(function (element) { return new DOMWrapper(element); }); | ||
}; | ||
VueWrapper.prototype.setProps = function (props) { | ||
// if this VM's parent is not the root, error out | ||
if (this.vm.$parent !== this.rootVM) { | ||
throw Error('You can only use setProps on your mounted component'); | ||
} | ||
this.__setProps(props); | ||
return nextTick(); | ||
}; | ||
VueWrapper.prototype.trigger = function (eventString) { | ||
VueWrapper.prototype.trigger = function (eventString, options) { | ||
var rootElementWrapper = new DOMWrapper(this.element); | ||
return rootElementWrapper.trigger(eventString); | ||
return rootElementWrapper.trigger(eventString, options); | ||
}; | ||
VueWrapper.prototype.unmount = function () { | ||
// preventing dispose of child component | ||
if (!this.__app) { | ||
throw new Error("wrapper.unmount() can only be called by the root wrapper"); | ||
} | ||
if (this.parentElement) { | ||
this.parentElement.removeChild(this.element); | ||
} | ||
this.__app.unmount(this.element); | ||
}; | ||
return VueWrapper; | ||
}()); | ||
function createWrapper(vm, events, setProps) { | ||
return new VueWrapper(vm, events, setProps); | ||
function createWrapper(app, vm, setProps) { | ||
return new VueWrapper(app, vm, setProps); | ||
} | ||
var createEmitMixin = function () { | ||
var events = {}; | ||
var emitMixin = { | ||
var attachEmitListener = function () { | ||
return { | ||
beforeCreate: function () { | ||
var originalEmit = getCurrentInstance().emit; | ||
var events = {}; | ||
this.__emitted = events; | ||
getCurrentInstance().emit = function (event) { | ||
@@ -356,10 +590,6 @@ var args = []; | ||
: (events[event] = [__spreadArrays(args)]); | ||
return originalEmit.call.apply(originalEmit, __spreadArrays([getCurrentInstance(), event], args)); | ||
return __spreadArrays([event], args); | ||
}; | ||
} | ||
}; | ||
return { | ||
events: events, | ||
emitMixin: emitMixin | ||
}; | ||
}; | ||
@@ -379,4 +609,70 @@ | ||
var MOUNT_ELEMENT_ID = 'app'; | ||
var MOUNT_COMPONENT_REF = 'VTU_COMPONENT'; | ||
var MOUNT_PARENT_NAME = 'VTU_ROOT'; | ||
var createStub = function (options) { | ||
var tag = options.name ? options.name + "-stub" : 'anonymous-stub'; | ||
var render = function () { return h(tag); }; | ||
return { name: tag, render: render }; | ||
}; | ||
var resolveComponentStubByName = function (componentName, stubs) { | ||
var componentPascalName = pascalCase(componentName); | ||
var componentKebabName = kebabCase(componentName); | ||
if (Array.isArray(stubs) && stubs.length) { | ||
// ['Foo', 'Bar'] => { Foo: true, Bar: true } | ||
stubs = stubs.reduce(function (acc, current) { | ||
acc[current] = true; | ||
return acc; | ||
}, {}); | ||
} | ||
for (var _i = 0, _a = Object.entries(stubs); _i < _a.length; _i++) { | ||
var _b = _a[_i], stubKey = _b[0], value = _b[1]; | ||
if (stubKey === componentPascalName || | ||
stubKey === componentKebabName || | ||
stubKey === componentName) { | ||
return value; | ||
} | ||
} | ||
}; | ||
var isHTMLElement = function (args) { | ||
return Array.isArray(args) && typeof args[0] === 'string'; | ||
}; | ||
var isCommentOrFragment = function (args) { return typeof args[0] === 'symbol'; }; | ||
var isParent = function (args) { | ||
return typeof args[0] === 'object' && args[0]['name'] === 'VTU_COMPONENT'; | ||
}; | ||
var isComponent = function (args) { return typeof args[0] === 'object'; }; | ||
function stubComponents(stubs) { | ||
transformVNodeArgs(function (args) { | ||
// args[0] can either be: | ||
// 1. a HTML tag (div, span...) | ||
// 2. An object of component options, such as { name: 'foo', render: [Function], props: {...} } | ||
// Depending what it is, we do different things. | ||
if (isHTMLElement(args) || isCommentOrFragment(args) || isParent(args)) { | ||
return args; | ||
} | ||
if (isComponent(args)) { | ||
var name_1 = args[0]['name']; | ||
if (!name_1) { | ||
return args; | ||
} | ||
var stub = resolveComponentStubByName(name_1, stubs); | ||
// we return a stub by matching Vue's `h` function | ||
// where the signature is h(Component, props) | ||
// case 1: default stub | ||
if (stub === true) { | ||
return [createStub({ name: name_1 }), {}]; | ||
} | ||
// case 2: custom implementation | ||
if (typeof stub === 'object') { | ||
return [stubs[name_1], {}]; | ||
} | ||
} | ||
return args; | ||
}); | ||
} | ||
function mount(originalComponent, options) { | ||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y; | ||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m; | ||
var component = __assign({}, originalComponent); | ||
@@ -403,9 +699,12 @@ // Reset the document.body | ||
var dataMixin = createDataMixin(options.data()); | ||
component.mixins = __spreadArrays((component.mixins || []), [dataMixin]); | ||
component.mixins = __spreadArrays((component.mixins || []), [ | ||
dataMixin | ||
]); | ||
} | ||
// we define props as reactive so that way when we update them with `setProps` | ||
// Vue's reactivity system will cause a rerender. | ||
var props = reactive(__assign(__assign({}, (_c = options) === null || _c === void 0 ? void 0 : _c.props), { ref: 'VTU_COMPONENT' })); | ||
var props = reactive(__assign(__assign({}, (_c = options) === null || _c === void 0 ? void 0 : _c.props), { ref: MOUNT_COMPONENT_REF })); | ||
// create the wrapper component | ||
var Parent = defineComponent({ | ||
name: MOUNT_PARENT_NAME, | ||
render: function () { | ||
@@ -420,13 +719,13 @@ return h(component, props, slots); | ||
} | ||
return app.$nextTick(); | ||
return vm.$nextTick(); | ||
}; | ||
// create the vm | ||
var vm = createApp(Parent); | ||
// create the app | ||
var app = createApp(Parent); | ||
var global = mergeGlobalProperties(config.global, (_d = options) === null || _d === void 0 ? void 0 : _d.global); | ||
// global mocks mixin | ||
if ((_e = (_d = options) === null || _d === void 0 ? void 0 : _d.global) === null || _e === void 0 ? void 0 : _e.mocks) { | ||
if ((_e = global) === null || _e === void 0 ? void 0 : _e.mocks) { | ||
var mixin = { | ||
beforeCreate: function () { | ||
var _a; | ||
for (var _i = 0, _b = Object.entries((_a = options.global) === null || _a === void 0 ? void 0 : _a.mocks); _i < _b.length; _i++) { | ||
var _c = _b[_i], k = _c[0], v = _c[1]; | ||
for (var _i = 0, _a = Object.entries(global.mocks); _i < _a.length; _i++) { | ||
var _b = _a[_i], k = _b[0], v = _b[1]; | ||
this[k] = v; | ||
@@ -436,44 +735,51 @@ } | ||
}; | ||
vm.mixin(mixin); | ||
app.mixin(mixin); | ||
} | ||
// use and plugins from mounting options | ||
if ((_g = (_f = options) === null || _f === void 0 ? void 0 : _f.global) === null || _g === void 0 ? void 0 : _g.plugins) { | ||
for (var _i = 0, _z = (_j = (_h = options) === null || _h === void 0 ? void 0 : _h.global) === null || _j === void 0 ? void 0 : _j.plugins; _i < _z.length; _i++) { | ||
var use = _z[_i]; | ||
vm.use(use); | ||
if ((_f = global) === null || _f === void 0 ? void 0 : _f.plugins) { | ||
for (var _i = 0, _o = global.plugins; _i < _o.length; _i++) { | ||
var use = _o[_i]; | ||
app.use(use); | ||
} | ||
} | ||
// use any mixins from mounting options | ||
if ((_l = (_k = options) === null || _k === void 0 ? void 0 : _k.global) === null || _l === void 0 ? void 0 : _l.mixins) { | ||
for (var _0 = 0, _1 = (_o = (_m = options) === null || _m === void 0 ? void 0 : _m.global) === null || _o === void 0 ? void 0 : _o.mixins; _0 < _1.length; _0++) { | ||
var mixin = _1[_0]; | ||
vm.mixin(mixin); | ||
if ((_g = global) === null || _g === void 0 ? void 0 : _g.mixins) { | ||
for (var _p = 0, _q = global.mixins; _p < _q.length; _p++) { | ||
var mixin = _q[_p]; | ||
app.mixin(mixin); | ||
} | ||
} | ||
if ((_q = (_p = options) === null || _p === void 0 ? void 0 : _p.global) === null || _q === void 0 ? void 0 : _q.components) { | ||
for (var _2 = 0, _3 = Object.keys((_s = (_r = options) === null || _r === void 0 ? void 0 : _r.global) === null || _s === void 0 ? void 0 : _s.components); _2 < _3.length; _2++) { | ||
var key = _3[_2]; | ||
vm.component(key, options.global.components[key]); | ||
if ((_h = global) === null || _h === void 0 ? void 0 : _h.components) { | ||
for (var _r = 0, _s = Object.keys(global.components); _r < _s.length; _r++) { | ||
var key = _s[_r]; | ||
app.component(key, global.components[key]); | ||
} | ||
} | ||
if ((_u = (_t = options) === null || _t === void 0 ? void 0 : _t.global) === null || _u === void 0 ? void 0 : _u.directives) { | ||
for (var _4 = 0, _5 = Object.keys((_w = (_v = options) === null || _v === void 0 ? void 0 : _v.global) === null || _w === void 0 ? void 0 : _w.directives); _4 < _5.length; _4++) { | ||
var key = _5[_4]; | ||
vm.directive(key, options.global.directives[key]); | ||
if ((_j = global) === null || _j === void 0 ? void 0 : _j.directives) { | ||
for (var _t = 0, _u = Object.keys(global.directives); _t < _u.length; _t++) { | ||
var key = _u[_t]; | ||
app.directive(key, global.directives[key]); | ||
} | ||
} | ||
// provide any values passed via provides mounting option | ||
if ((_y = (_x = options) === null || _x === void 0 ? void 0 : _x.global) === null || _y === void 0 ? void 0 : _y.provide) { | ||
for (var _6 = 0, _7 = Reflect.ownKeys(options.global.provide); _6 < _7.length; _6++) { | ||
var key = _7[_6]; | ||
if ((_k = global) === null || _k === void 0 ? void 0 : _k.provide) { | ||
for (var _v = 0, _w = Reflect.ownKeys(global.provide); _v < _w.length; _v++) { | ||
var key = _w[_v]; | ||
// @ts-ignore: https://github.com/microsoft/TypeScript/issues/1863 | ||
vm.provide(key, options.global.provide[key]); | ||
app.provide(key, global.provide[key]); | ||
} | ||
} | ||
// add tracking for emitted events | ||
var _8 = createEmitMixin(), emitMixin = _8.emitMixin, events = _8.events; | ||
vm.mixin(emitMixin); | ||
app.mixin(attachEmitListener()); | ||
// stubs | ||
if ((_m = (_l = options) === null || _l === void 0 ? void 0 : _l.global) === null || _m === void 0 ? void 0 : _m.stubs) { | ||
stubComponents(options.global.stubs); | ||
} | ||
else { | ||
transformVNodeArgs(); | ||
} | ||
// mount the app! | ||
var app = vm.mount(el); | ||
return createWrapper(app, events, setProps); | ||
var vm = app.mount(el); | ||
var App = vm.$refs[MOUNT_COMPONENT_REF]; | ||
return createWrapper(app, App, setProps); | ||
} | ||
@@ -495,2 +801,2 @@ | ||
export { RouterLinkStub, mount }; | ||
export { RouterLinkStub, config, mount }; |
@@ -1,16 +0,17 @@ | ||
import { ComponentPublicInstance, nextTick } from 'vue'; | ||
import { ComponentPublicInstance, nextTick, App } from 'vue'; | ||
import { DOMWrapper } from './dom-wrapper'; | ||
import { WrapperAPI } from './types'; | ||
import { FindAllComponentsSelector, FindComponentSelector, WrapperAPI } from './types'; | ||
import { ErrorWrapper } from './error-wrapper'; | ||
export declare class VueWrapper implements WrapperAPI { | ||
import { TriggerOptions } from './create-dom-event'; | ||
export declare class VueWrapper<T extends ComponentPublicInstance> implements WrapperAPI { | ||
private componentVM; | ||
private __emitted; | ||
private __vm; | ||
private rootVM; | ||
private __app; | ||
private __setProps; | ||
constructor(vm: ComponentPublicInstance, events: Record<string, unknown[]>, setProps: (props: Record<string, any>) => void); | ||
private get appRootNode(); | ||
constructor(app: App | null, vm: ComponentPublicInstance, setProps?: (props: Record<string, any>) => void); | ||
private get hasMultipleRoots(); | ||
private get parentElement(); | ||
get element(): Element; | ||
get vm(): ComponentPublicInstance; | ||
get vm(): T; | ||
props(selector?: string): any; | ||
classes(className?: string): boolean | string[]; | ||
@@ -22,7 +23,17 @@ attributes(key?: string): any; | ||
text(): string; | ||
find<K extends keyof HTMLElementTagNameMap>(selector: K): DOMWrapper<HTMLElementTagNameMap[K]> | ErrorWrapper; | ||
find<K extends keyof SVGElementTagNameMap>(selector: K): DOMWrapper<SVGElementTagNameMap[K]> | ErrorWrapper; | ||
find<T extends Element>(selector: string): DOMWrapper<T> | ErrorWrapper; | ||
get<K extends keyof HTMLElementTagNameMap>(selector: K): DOMWrapper<HTMLElementTagNameMap[K]>; | ||
get<K extends keyof SVGElementTagNameMap>(selector: K): DOMWrapper<SVGElementTagNameMap[K]>; | ||
get<T extends Element>(selector: string): DOMWrapper<T>; | ||
findComponent(selector: FindComponentSelector): VueWrapper<T> | ErrorWrapper; | ||
findAllComponents(selector: FindAllComponentsSelector): VueWrapper<T>[]; | ||
findAll<K extends keyof HTMLElementTagNameMap>(selector: K): DOMWrapper<HTMLElementTagNameMap[K]>[]; | ||
findAll<K extends keyof SVGElementTagNameMap>(selector: K): DOMWrapper<SVGElementTagNameMap[K]>[]; | ||
findAll<T extends Element>(selector: string): DOMWrapper<T>[]; | ||
setProps(props: Record<string, any>): Promise<void>; | ||
trigger(eventString: string): Promise<typeof nextTick>; | ||
trigger(eventString: string, options?: TriggerOptions): Promise<typeof nextTick>; | ||
unmount(): void; | ||
} | ||
export declare function createWrapper(vm: ComponentPublicInstance, events: Record<string, unknown[]>, setProps: (props: Record<string, any>) => void): VueWrapper; | ||
export declare function createWrapper<T extends ComponentPublicInstance>(app: App, vm: ComponentPublicInstance, setProps?: (props: Record<string, any>) => void): VueWrapper<T>; |
{ | ||
"name": "@vue/test-utils", | ||
"version": "2.0.0-alpha.1", | ||
"version": "2.0.0-alpha.2", | ||
"license": "MIT", | ||
@@ -12,4 +12,9 @@ "main": "dist/vue-test-utils.cjs.js", | ||
"dist", | ||
"README.md" | ||
"README.md", | ||
"dist/index.d.ts" | ||
], | ||
"dependencies": { | ||
"dom-event-types": "^1.0.0", | ||
"lodash": "^4.17.15" | ||
}, | ||
"devDependencies": { | ||
@@ -19,5 +24,8 @@ "@babel/core": "^7.9.0", | ||
"@babel/types": "^7.8.3", | ||
"@rollup/plugin-node-resolve": "^7.1.3", | ||
"@types/estree": "^0.0.42", | ||
"@types/jest": "^24.9.1", | ||
"@vue/compiler-sfc": "^3.0.0-alpha.12", | ||
"@types/node": "12.12.35", | ||
"@types/lodash": "^4.14.149", | ||
"@vue/compiler-sfc": "^3.0.0-beta.2", | ||
"babel-jest": "^25.2.3", | ||
@@ -33,4 +41,5 @@ "babel-preset-jest": "^25.2.1", | ||
"ts-jest": "^25.0.0", | ||
"tsd": "0.11.0", | ||
"typescript": "^3.7.5", | ||
"vue": "^3.0.0-alpha.12", | ||
"vue": "^3.0.0-beta.2", | ||
"vue-jest": "vuejs/vue-jest#next", | ||
@@ -49,2 +58,3 @@ "vuex": "^4.0.0-alpha.1" | ||
"test": "yarn jest --runInBand tests", | ||
"tsd": "tsd", | ||
"build": "yarn rollup -c rollup.config.js", | ||
@@ -63,3 +73,10 @@ "lint": "prettier -c --parser typescript \"(src|tests)/**/*.ts?(x)\"", | ||
] | ||
}, | ||
"tsd": { | ||
"directory": "test-dts", | ||
"compilerOptions": { | ||
"strict": false, | ||
"lib": ["esnext", "dom"] | ||
} | ||
} | ||
} |
@@ -62,3 +62,3 @@ # vue-test-utils-next | ||
directives | ✅ | (new!) nested in [`global`](https://vuejs.github.io/vue-test-utils-next-docs/api/#global) | ||
stubs | ❌ | ||
stubs | ✅ | ||
attachToDocument | ❌| will rename to `attachTo`. See [here](https://github.com/vuejs/vue-test-utils/pull/1492) | ||
@@ -83,2 +83,3 @@ attrs | ❌ | | ||
findAll | ✅ | see above. `.vm` is different to Vue 2. We are exploring options. | ||
get | ✅ | ||
html | ✅ | ||
@@ -89,6 +90,5 @@ setValue | ✅ | works for select, checkbox, radio button, input, textarea. Returns `nextTick`. | ||
setProps | ✅ | | ||
props | ✅ | ||
setData | ❌ | has PR | ||
destroy | ❌ | ||
get | ❌ | ||
isVisible | ❌ | use matchers such as [this](https://github.com/testing-library/jest-dom#tobeempty) | ||
destroy | ✅ | renamed to `unmount` to match Vue 3 lifecycle hook name. | ||
props | ❌ | ||
@@ -101,4 +101,5 @@ contains | ⚰️| use `find` | ||
isEmpty | ⚰️ | use matchers such as [this](https://github.com/testing-library/jest-dom#tobeempty) | ||
isVisible | ⚰️ | use matchers such as [this](https://github.com/testing-library/jest-dom#tobevisible) | ||
isVueInstance | ⚰️ | ||
name | ⚰️ | | ||
setMethods | ⚰️ | | ||
setMethods | ⚰️ | |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
108666
21
2476
102
4
24
4
+ Addeddom-event-types@^1.0.0
+ Addedlodash@^4.17.15
+ Addeddom-event-types@1.1.0(transitive)
+ Addedlodash@4.17.21(transitive)