diff --git a/package.json b/package.json index 6882af111..fb6aff99f 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,11 @@ "version": "0.1.0", "description": "Utilities for testing Vue components.", "main": "dist/vue-test-utils.js", + "types": "types/index.d.ts", "files": [ "src", - "dist/*.js" + "dist/*.js", + "types/index.d.ts" ], "scripts": { "build": "node build/build.js", @@ -16,10 +18,11 @@ "lint:docs": "eslint --ext js,vue,md docs --ignore-path .gitignore", "lint:fix": "npm run lint -- --fix", "postinstall": "node build/install-hooks.js", - "test": "npm run lint && npm run lint:docs && npm run flow && npm run test:unit && npm run test:integration && npm run test:integration:karma", + "test": "npm run lint && npm run lint:docs && npm run flow && npm run test:types && npm run test:unit && npm run test:integration && npm run test:integration:karma", "test:integration": "cross-env BABEL_ENV=test mocha-webpack --webpack-config build/webpack.test.config.js test/integration/specs --recursive --require test/integration/setup/mocha.setup.js", "test:integration:karma": "cross-env BABEL_ENV=test TARGET=browser karma start test/integration/setup/karma.conf.js --single-run", "test:unit": "cross-env BABEL_ENV=test mocha-webpack --webpack-config build/webpack.test.config.js test/unit/specs --recursive --require test/unit/setup/mocha.setup.js", + "test:types": "tsc -p types", "release": "bash build/release.sh", "release:note": "node build/gen-release-note.js" }, @@ -72,6 +75,7 @@ "shelljs": "^0.7.8", "sinon": "^2.3.2", "sinon-chai": "^2.10.0", + "typescript": "^2.4.1", "vue": "^2.3.3", "vue-loader": "^12.2.1", "vue-router": "^2.6.0", diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 000000000..7a9959fff --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,101 @@ +import Vue, { VNodeData, Component, ComponentOptions, FunctionalComponentOptions } from 'vue' + +/** + * Utility type to declare an extended Vue constructor + */ +type VueClass = (new (...args: any[]) => V) & typeof Vue + +/** + * Utility type for a selector + */ +type Selector = string | Component + +/** + * Utility type for slots + */ +type Slots = { + [key: string]: (Component | string)[] | Component | string +} + +/** + * Utility type for stubs which can be a string of template as a shorthand + * If it is an array of string, the specified children are replaced by blank components + */ +type Stubs = { + [key: string]: Component | string | true +} | string[] + +/** + * Base class of Wrapper and WrapperArray + * It has common methods on both Wrapper and WrapperArray + */ +interface BaseWrapper { + contains (selector: Selector): boolean + exists (): boolean + + hasAttribute (attribute: string, value: string): boolean + hasClass (className: string): boolean + hasProp (prop: string, value: any): boolean + hasStyle (style: string, value: string): boolean + + is (selector: Selector): boolean + isEmpty (): boolean + isVueInstance (): boolean + + update (): void + setData (data: object): void + setProps (props: object): void + trigger (eventName: string, options?: object): void +} + +interface Wrapper extends BaseWrapper { + readonly vm: V + readonly element: HTMLElement + readonly options: WrapperOptions + + find = VueClass> (selector: Ctor): Wrapper + find (selector: ComponentOptions): Wrapper + find (selector: FunctionalComponentOptions): Wrapper + find (selector: string): Wrapper + + findAll = VueClass> (selector: Ctor): WrapperArray + findAll (selector: ComponentOptions): WrapperArray + findAll (selector: FunctionalComponentOptions): WrapperArray + findAll (selector: string): WrapperArray + + html (): string + text (): string + name (): string +} + +interface WrapperArray extends BaseWrapper { + readonly length: number + + at (index: number): Wrapper +} + +interface WrapperOptions { + attachedToDocument: boolean +} + +interface MountOptions extends ComponentOptions { + attachToDocument?: boolean + clone?: boolean + context?: VNodeData + localVue?: typeof Vue + intercept?: object + slots?: Slots + stubs?: Stubs +} + +type ShallowOptions = MountOptions + +export declare function createLocalVue (): typeof Vue + +export declare function mount = VueClass> (component: Ctor, options?: MountOptions): Wrapper +export declare function mount (component: ComponentOptions, options?: MountOptions): Wrapper +export declare function mount (component: FunctionalComponentOptions, options?: MountOptions): Wrapper + +export declare function shallow = VueClass> (component: Ctor, options?: ShallowOptions): Wrapper +export declare function shallow (component: ComponentOptions, options?: ShallowOptions): Wrapper +export declare function shallow (component: FunctionalComponentOptions, options?: ShallowOptions): Wrapper diff --git a/types/test/mount.ts b/types/test/mount.ts new file mode 100644 index 000000000..f57da5948 --- /dev/null +++ b/types/test/mount.ts @@ -0,0 +1,62 @@ +import Vuex from 'vuex' +import { mount, createLocalVue } from '../' +import { normalOptions, functionalOptions, Normal, ClassComponent } from './resources' + +/** + * Should create wrapper vm based on (function) component options or constructors + * The users can specify component type via the type parameter + */ +const normalWrapper = mount(normalOptions) +const normalFoo: string = normalWrapper.vm.foo + +const classWrapper = mount(ClassComponent) +const classFoo: string = classWrapper.vm.bar + +const functinalWrapper = mount(functionalOptions) + +/** + * Test for mount options + */ +const localVue = createLocalVue() +localVue.use(Vuex) + +const store = new Vuex.Store({}) + +mount(ClassComponent, { + attachToDocument: true, + clone: true, + localVue, + intercept: { + $store: store + }, + slots: { + default: `
Foo
`, + foo: [normalOptions, functionalOptions], + bar: ClassComponent + }, + stubs: { + foo: normalOptions, + bar: functionalOptions, + baz: ClassComponent, + qux: `
Test
` + } +}) + +mount(functionalOptions, { + context: { + props: { foo: 'test' } + }, + stubs: ['child'] +}) + +/** + * MountOptions should receive Vue's component options + */ +mount(ClassComponent, { + propsData: { + test: 'test' + }, + created () { + this.bar + } +}) diff --git a/types/test/resources.ts b/types/test/resources.ts new file mode 100644 index 000000000..4195c5c8d --- /dev/null +++ b/types/test/resources.ts @@ -0,0 +1,33 @@ +import Vue, { ComponentOptions, FunctionalComponentOptions } from 'vue' + +/** + * Normal component options + */ +export interface Normal extends Vue { + foo: string +} +export const normalOptions: ComponentOptions = { + name: 'normal', + data () { + return { + foo: 'bar' + } + } +} + +/** + * Functional component options + */ +export const functionalOptions: FunctionalComponentOptions = { + functional: true, + render (h) { + return h('div') + } +} + +/** + * Component constructor declared with vue-class-component etc. + */ +export class ClassComponent extends Vue { + bar = 'bar' +} diff --git a/types/test/shallow.ts b/types/test/shallow.ts new file mode 100644 index 000000000..a1537d366 --- /dev/null +++ b/types/test/shallow.ts @@ -0,0 +1,62 @@ +import Vuex from 'vuex' +import { shallow, createLocalVue } from '../' +import { normalOptions, functionalOptions, Normal, ClassComponent } from './resources' + +/** + * Should create wrapper vm based on (function) component options or constructors + * The users can specify component type via the type parameter + */ +const normalWrapper = shallow(normalOptions) +const normalFoo: string = normalWrapper.vm.foo + +const classWrapper = shallow(ClassComponent) +const classFoo: string = classWrapper.vm.bar + +const functinalWrapper = shallow(functionalOptions) + +/** + * Test for shallow options + */ +const localVue = createLocalVue() +localVue.use(Vuex) + +const store = new Vuex.Store({}) + +shallow(ClassComponent, { + attachToDocument: true, + clone: true, + localVue, + intercept: { + $store: store + }, + slots: { + default: `
Foo
`, + foo: [normalOptions, functionalOptions], + baz: ClassComponent + }, + stubs: { + foo: normalOptions, + bar: functionalOptions, + baz: ClassComponent, + qux: `
Test
` + } +}) + +shallow(functionalOptions, { + context: { + props: { foo: 'test' } + }, + stubs: ['child'] +}) + +/** + * ShallowOptions should receive Vue's component options + */ +shallow(ClassComponent, { + propsData: { + test: 'test' + }, + created () { + this.bar + } +}) diff --git a/types/test/wrapper.ts b/types/test/wrapper.ts new file mode 100644 index 000000000..412423dff --- /dev/null +++ b/types/test/wrapper.ts @@ -0,0 +1,59 @@ +import { mount } from '../' +import { normalOptions, functionalOptions, Normal, ClassComponent } from './resources' + +/** + * Tests for BaseWrapper API + */ +let wrapper = mount(normalOptions) + +let bool: boolean = wrapper.contains('.foo') +bool = wrapper.contains(normalOptions) +bool = wrapper.contains(ClassComponent) + +bool = wrapper.exists() + +bool = wrapper.hasAttribute('foo', 'bar') +bool = wrapper.hasClass('foo-class') +bool = wrapper.hasProp('checked', true) +bool = wrapper.hasStyle('color', 'red') + +bool = wrapper.is(normalOptions) +bool = wrapper.isEmpty() +bool = wrapper.isVueInstance() + +wrapper.update() +wrapper.setData({ foo: 'bar' }) +wrapper.setProps({ checked: true }) +wrapper.trigger('mousedown.enter', { + preventDefault: true +}) + +/** + * Tests for Wrapper API + */ +wrapper.vm.foo +wrapper.vm.$emit('event', 'arg') + +let el: HTMLElement = wrapper.element + +bool = wrapper.options.attachedToDocument + +let found = wrapper.find('.foo') +found = wrapper.find(normalOptions) +found = wrapper.find(functionalOptions) +found = wrapper.find(ClassComponent) + +let array = wrapper.findAll('.bar') +array = wrapper.findAll(normalOptions) +array = wrapper.findAll(functionalOptions) +array = wrapper.findAll(ClassComponent) + +let str: string = wrapper.html() +str = wrapper.text() +str = wrapper.name() + +/** + * Tests for WrapperArray API + */ +let num: number = array.length +found = array.at(1) diff --git a/types/tsconfig.json b/types/tsconfig.json new file mode 100644 index 000000000..c0e76be17 --- /dev/null +++ b/types/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "module": "es2015", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "strict": true, + "noEmit": true + }, + "include": [ + "**/*.ts" + ] +} diff --git a/yarn.lock b/yarn.lock index 049856692..636ffdd5a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6342,6 +6342,10 @@ typedarray@^0.0.6, typedarray@~0.0.5: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" +typescript@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.1.tgz#c3ccb16ddaa0b2314de031e7e6fee89e5ba346bc" + uglify-js@^2.6, uglify-js@^2.8.27: version "2.8.27" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.27.tgz#47787f912b0f242e5b984343be8e35e95f694c9c"