From d1ab9ffe55f152680b3227900f1788cc41c3d1b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Sun, 1 Nov 2020 10:25:09 +0100 Subject: [PATCH 1/5] Add required dependencies --- package.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index d579d2f3..de573e11 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.0.0-semantically-released", "description": "Simple and complete Vue DOM testing utilities that encourage good testing practices.", "main": "dist/vue-testing-library.js", + "types": "types/index.d.ts", "scripts": { "format": "kcd-scripts format", "build": "kcd-scripts build", @@ -10,12 +11,14 @@ "test": "kcd-scripts test", "test:update": "npm test -- --updateSnapshot --coverage", "validate": "kcd-scripts validate", + "typecheck": "dtslint ./types/", "setup": "npm install && npm run validate -s" }, "engines": { "node": ">10.18" }, "files": [ + "types", "dist", "cleanup-after-each.js" ], @@ -42,7 +45,6 @@ "dependencies": { "@babel/runtime": "^7.11.2", "@testing-library/dom": "^7.24.3", - "@types/testing-library__vue": "^5.0.0", "@vue/test-utils": "^1.1.0" }, "devDependencies": { @@ -51,6 +53,7 @@ "apollo-boost": "^0.4.9", "apollo-cache-inmemory": "^1.6.6", "axios": "^0.20.0", + "dtslint": "^4.0.4", "eslint-plugin-vue": "^6.2.2", "graphql": "^15.3.0", "graphql-tag": "^2.11.0", @@ -60,6 +63,7 @@ "lodash.merge": "^4.6.2", "msw": "^0.21.2", "portal-vue": "^2.1.7", + "typescript": "^4.0.5", "vee-validate": "^2.2.15", "vue": "^2.6.12", "vue-apollo": "^3.0.4", From 6e2ce45bd4bd27ec9fe67ae812638c465c4db9f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Sun, 1 Nov 2020 10:25:32 +0100 Subject: [PATCH 2/5] Add types --- types/index.d.ts | 71 +++++++++++++++++++++++++ types/test.ts | 124 ++++++++++++++++++++++++++++++++++++++++++++ types/tsconfig.json | 17 ++++++ types/tslint.json | 7 +++ 4 files changed, 219 insertions(+) create mode 100644 types/index.d.ts create mode 100644 types/test.ts create mode 100644 types/tsconfig.json create mode 100644 types/tslint.json diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 00000000..8441b13d --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,71 @@ +// TypeScript Version: 3.8 + +import Vue, {ComponentOptions} from 'vue' +import {ThisTypedMountOptions, VueClass} from '@vue/test-utils' +import {Store, StoreOptions} from 'vuex' +import Router, {RouteConfig} from 'vue-router' +import {OptionsReceived as PrettyFormatOptions} from 'pretty-format' +import {queries, EventType, BoundFunctions} from '@testing-library/dom' + +// NOTE: fireEvent is overridden below +export * from '@testing-library/dom' + +export interface RenderResult extends BoundFunctions { + container: HTMLElement + baseElement: HTMLElement + debug: ( + baseElement?: + | HTMLElement + | DocumentFragment + | Array, + maxLength?: number, + options?: PrettyFormatOptions, + ) => void + unmount(): void + isUnmounted(): boolean + html(): string + emitted(): {[name: string]: any[][]} + updateProps(props: object): Promise +} + +export interface RenderOptions + // The props and store options special-cased by Vue Testing Library and NOT passed to mount(). + extends Omit, 'store' | 'props'> { + props?: object + store?: StoreOptions + routes?: RouteConfig[] + container?: HTMLElement + baseElement?: HTMLElement +} + +export type ConfigurationCallback = ( + localVue: typeof Vue, + store: Store, + router: Router, +) => Partial> | void + +export function render( + TestComponent: VueClass | ComponentOptions, + options?: RenderOptions, + configure?: ConfigurationCallback, +): RenderResult + +export type AsyncFireObject = { + [K in EventType]: ( + element: Document | Element | Window, + options?: {}, + ) => Promise +} + +export interface VueFireEventObject extends AsyncFireObject { + (element: Document | Element | Window, event: Event): Promise + touch(element: Document | Element | Window): Promise + update(element: HTMLOptionElement): Promise + update( + element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement, + value: string, + ): Promise + update(element: HTMLElement, value?: string): Promise +} + +export const fireEvent: VueFireEventObject diff --git a/types/test.ts b/types/test.ts new file mode 100644 index 00000000..dd2a5ff2 --- /dev/null +++ b/types/test.ts @@ -0,0 +1,124 @@ +import Vue from 'vue' +import {render, fireEvent, screen, waitFor} from '@testing-library/vue' + +declare const elem: HTMLElement + +const SomeComponent = Vue.extend({ + name: 'SomeComponent', + props: { + foo: Number, + bar: String, + }, +}) + +async function testRender() { + const page = render({template: '
'}) + + // single queries + page.getByText('foo') + page.queryByText('foo') + await page.findByText('foo') + + // multiple queries + page.getAllByText('bar') + page.queryAllByText('bar') + await page.findAllByText('bar') + + // helpers + const {container, unmount, debug} = page + + debug(elem) // $ExpectType void + debug([elem, elem], 100, {highlight: false}) // $ExpectType void +} + +async function testRenderOptions() { + const container = document.createElement('div') + const options = {container} + render({template: 'div'}, options) +} + +async function testFireEvent() { + const {container} = render({template: 'button'}) + await fireEvent.click(container) +} + +async function testDebug() { + const {debug, getAllByTestId} = render({ + render(h) { + return h('div', [ + h('h1', {attrs: {'data-testId': 'testid'}}, 'hello world'), + h('h2', {attrs: {'data-testId': 'testid'}}, 'hello world'), + ]) + }, + }) + + debug(getAllByTestId('testid')) +} + +async function testScreen() { + render({template: 'button'}) + + await screen.findByRole('button') +} + +async function testWaitFor() { + const {container} = render({template: 'button'}) + fireEvent.click(container) + await waitFor(() => {}) +} + +async function testOptions() { + render(SomeComponent, { + // options for new Vue() + name: 'SomeComponent', + methods: { + glorb() { + return 42 + }, + }, + // options for vue-test-utils mount() + slots: { + quux: '

Baz

', + }, + mocks: { + isThisFake() { + return true + }, + }, + // options for Vue Testing Library render() + container: elem, + baseElement: elem, + props: { + foo: 9, + bar: 'x', + }, + store: { + state: { + foos: [4, 5], + bars: ['a', 'b'], + }, + getters: { + fooCount() { + return this.foos.length + }, + }, + }, + routes: [ + {path: '/', name: 'home', component: SomeComponent}, + { + path: '/about', + name: 'about', + component: () => Promise.resolve(SomeComponent), + }, + ], + }) +} + +function testConfigCallback() { + const ExamplePlugin: Vue.PluginFunction = () => {} + render(SomeComponent, {}, (localVue, store, router) => { + localVue.use(ExamplePlugin) + store.replaceState({foo: 'bar'}) + router.onError(error => console.log(error.message)) + }) +} diff --git a/types/tsconfig.json b/types/tsconfig.json new file mode 100644 index 00000000..4c48bf28 --- /dev/null +++ b/types/tsconfig.json @@ -0,0 +1,17 @@ +// this additional tsconfig is required by dtslint +// see: https://github.com/Microsoft/dtslint#typestsconfigjson +{ + "compilerOptions": { + "module": "commonjs", + "lib": ["es6", "dom"], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "noEmit": true, + "baseUrl": ".", + "paths": { + "@testing-library/vue": ["."] + } + } +} diff --git a/types/tslint.json b/types/tslint.json new file mode 100644 index 00000000..c7b428f3 --- /dev/null +++ b/types/tslint.json @@ -0,0 +1,7 @@ +{ + "extends": ["dtslint/dtslint.json"], + "rules": { + "semicolon": false, + "whitespace": false + } +} From ad510b2706d150337d1387f04b1cd965853b3ba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Sun, 1 Nov 2020 10:28:07 +0100 Subject: [PATCH 3/5] Update readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ab3bcab2..4382388c 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ + - [Installation](#installation) - [A basic example](#a-basic-example) - [More examples](#more-examples) @@ -173,8 +174,7 @@ light-weight, simple, and understandable. ## Typings -The TypeScript type definitions are in the [DefinitelyTyped repo][types] and -bundled with Vue Testing Library. +The TypeScript type definitions are in the [types][types-directory] directory. ## ESLint support @@ -248,7 +248,6 @@ instead of filing an issue on GitHub. [license]: https://github.com/testing-library/vue-testing-library/blob/master/LICENSE [discord]: https://testing-library.com/discord [discord-badge]: https://img.shields.io/discord/723559267868737556.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2&style=flat-square -[types]: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/testing-library__vue [jest-dom]: https://github.com/testing-library/jest-dom [which-query]: https://testing-library.com/docs/guide-which-query [guiding-principle]: https://twitter.com/kentcdodds/status/977018512689455106 @@ -262,6 +261,7 @@ instead of filing an issue on GitHub. [add-issue-bug]: https://github.com/testing-library/vue-testing-library/issues/new?assignees=&labels=bug&template=bug_report.md&title= [add-issue]: (https://github.com/testing-library/vue-testing-library/issues/new) +[types-directory]: https://github.com/testing-library/vue-testing-library/blob/master/types [test-directory]: https://github.com/testing-library/vue-testing-library/blob/master/src/__tests__ [vuex-example]: https://github.com/testing-library/vue-testing-library/blob/master/src/__tests__/vuex.js [vue-router-example]: https://github.com/testing-library/vue-testing-library/blob/master/src/__tests__/vue-router.js From cc07d23ab54e1b1981d480ddd73b7e37e76ba852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Sat, 14 Nov 2020 20:09:03 +0100 Subject: [PATCH 4/5] Bump deps and fix build --- .eslintrc.js | 3 +++ package.json | 16 +++++++++------- types/index.d.ts | 5 ++++- types/test.ts | 35 ++++++++++++++++++++++++----------- 4 files changed, 40 insertions(+), 19 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 612dd456..ab4c94dd 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,4 +1,7 @@ module.exports = { + parserOptions: { + parser: '@typescript-eslint/parser', + }, extends: [ './node_modules/kcd-scripts/eslint.js', 'plugin:vue/recommended', diff --git a/package.json b/package.json index de573e11..2d9b84c3 100644 --- a/package.json +++ b/package.json @@ -43,23 +43,25 @@ "author": "Daniel Cook", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.11.2", - "@testing-library/dom": "^7.24.3", + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^7.26.6", "@vue/test-utils": "^1.1.0" }, "devDependencies": { "@babel/plugin-transform-runtime": "^7.11.5", - "@testing-library/jest-dom": "^5.11.4", + "@testing-library/jest-dom": "^5.11.6", + "@types/estree": "0.0.45", "apollo-boost": "^0.4.9", "apollo-cache-inmemory": "^1.6.6", "axios": "^0.20.0", - "dtslint": "^4.0.4", + "dtslint": "^4.0.5", + "eslint": "^7.13.0", "eslint-plugin-vue": "^6.2.2", "graphql": "^15.3.0", "graphql-tag": "^2.11.0", "isomorphic-unfetch": "^3.0.0", "jest-serializer-vue": "^2.0.2", - "kcd-scripts": "^6.5.1", + "kcd-scripts": "^7.0.3", "lodash.merge": "^4.6.2", "msw": "^0.21.2", "portal-vue": "^2.1.7", @@ -69,9 +71,9 @@ "vue-apollo": "^3.0.4", "vue-i18n": "^8.21.1", "vue-jest": "^4.0.0-rc.0", - "vue-router": "^3.4.5", + "vue-router": "^3.4.9", "vue-template-compiler": "^2.6.12", - "vuetify": "^2.3.10", + "vuetify": "^2.3.17", "vuex": "^3.5.1" }, "peerDependencies": { diff --git a/types/index.d.ts b/types/index.d.ts index 8441b13d..f50ac952 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,9 +1,10 @@ -// TypeScript Version: 3.8 +// TypeScript Version: 4.0 import Vue, {ComponentOptions} from 'vue' import {ThisTypedMountOptions, VueClass} from '@vue/test-utils' import {Store, StoreOptions} from 'vuex' import Router, {RouteConfig} from 'vue-router' +// eslint-disable-next-line import/no-extraneous-dependencies import {OptionsReceived as PrettyFormatOptions} from 'pretty-format' import {queries, EventType, BoundFunctions} from '@testing-library/dom' @@ -24,6 +25,7 @@ export interface RenderResult extends BoundFunctions { unmount(): void isUnmounted(): boolean html(): string + // eslint-disable-next-line @typescript-eslint/no-explicit-any emitted(): {[name: string]: any[][]} updateProps(props: object): Promise } @@ -40,6 +42,7 @@ export interface RenderOptions export type ConfigurationCallback = ( localVue: typeof Vue, + // eslint-disable-next-line @typescript-eslint/no-explicit-any store: Store, router: Router, ) => Partial> | void diff --git a/types/test.ts b/types/test.ts index dd2a5ff2..6663d3c7 100644 --- a/types/test.ts +++ b/types/test.ts @@ -6,12 +6,12 @@ declare const elem: HTMLElement const SomeComponent = Vue.extend({ name: 'SomeComponent', props: { - foo: Number, - bar: String, + foo: {type: Number, default: 0}, + bar: {type: String, default: '0'}, }, }) -async function testRender() { +export async function testRender() { const page = render({template: '
'}) // single queries @@ -27,22 +27,26 @@ async function testRender() { // helpers const {container, unmount, debug} = page + debug(container) + debug(elem) // $ExpectType void debug([elem, elem], 100, {highlight: false}) // $ExpectType void + + unmount() } -async function testRenderOptions() { +export function testRenderOptions() { const container = document.createElement('div') const options = {container} render({template: 'div'}, options) } -async function testFireEvent() { +export async function testFireEvent() { const {container} = render({template: 'button'}) await fireEvent.click(container) } -async function testDebug() { +export function testDebug() { const {debug, getAllByTestId} = render({ render(h) { return h('div', [ @@ -55,19 +59,19 @@ async function testDebug() { debug(getAllByTestId('testid')) } -async function testScreen() { +export async function testScreen() { render({template: 'button'}) await screen.findByRole('button') } -async function testWaitFor() { +export async function testWaitFor() { const {container} = render({template: 'button'}) - fireEvent.click(container) + await fireEvent.click(container) await waitFor(() => {}) } -async function testOptions() { +export function testOptions() { render(SomeComponent, { // options for new Vue() name: 'SomeComponent', @@ -114,7 +118,7 @@ async function testOptions() { }) } -function testConfigCallback() { +export function testConfigCallback() { const ExamplePlugin: Vue.PluginFunction = () => {} render(SomeComponent, {}, (localVue, store, router) => { localVue.use(ExamplePlugin) @@ -122,3 +126,12 @@ function testConfigCallback() { router.onError(error => console.log(error.message)) }) } + +/* +eslint + testing-library/prefer-explicit-assert: "off", + testing-library/no-wait-for-empty-callback: "off", + testing-library/no-debug: "off", + testing-library/prefer-screen-queries: "off", + @typescript-eslint/unbound-method: "off", +*/ From 34594ebc2b200716e970845d636cc46ad3ad902b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Sat, 14 Nov 2020 20:16:06 +0100 Subject: [PATCH 5/5] Bump eslint to fix node 15 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2d9b84c3..6c977fd9 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "axios": "^0.20.0", "dtslint": "^4.0.5", "eslint": "^7.13.0", - "eslint-plugin-vue": "^6.2.2", + "eslint-plugin-vue": "^7.1.0", "graphql": "^15.3.0", "graphql-tag": "^2.11.0", "isomorphic-unfetch": "^3.0.0",