From 3ef7112c1cbf2cd7d3d1de87321aabd6dd2857dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Thu, 3 Dec 2020 08:13:14 +0100 Subject: [PATCH] feat: Add types for Vue 3 (#180) * Add basic types * Rename main file to properly get types * Fix import statement * Fix compiler options lib * Extract temporary-defined types to its own file * Remove duplicate test * Fix link * Improve tests * Remove unnecessary chars * Add support for file inputs * Update deps * Fix rerender typing test * Remove typecheck step until we figure out how to overcome ts-ignore in node_modules. lol * Update deps --- .eslintrc.js | 3 + README.md | 7 +- jest.config.js | 3 - package.json | 28 ++++--- src/__tests__/auto-cleanup-skip.js | 4 +- src/__tests__/auto-cleanup.js | 2 +- src/__tests__/axios-mock.js | 2 +- src/__tests__/cleanup-throw.js | 2 +- src/__tests__/components/Translations.vue | 10 +-- src/__tests__/components/VueI18n.vue | 21 ----- src/__tests__/debug.js | 2 +- src/__tests__/directive.js | 2 +- src/__tests__/disappearance.js | 2 +- src/__tests__/fire-event.js | 19 ++++- src/__tests__/form.js | 2 +- src/__tests__/functional.js | 4 +- src/__tests__/render.js | 2 +- src/__tests__/rerender.js | 2 +- src/__tests__/select.js | 10 +-- src/__tests__/simple-button.js | 2 +- src/__tests__/slots.js | 2 +- src/__tests__/stopwatch.js | 2 +- src/__tests__/stubs.js | 2 +- src/__tests__/user-event.js | 40 +++++----- src/__tests__/validate-plugin.js | 2 +- src/__tests__/visibility.js | 2 +- src/__tests__/vue-apollo.js | 2 +- src/__tests__/vue-i18n.js | 4 +- src/__tests__/vue-portal.js | 2 +- src/__tests__/vue-router-mocha.js | 2 +- src/__tests__/vue-router.js | 2 +- src/__tests__/vueI18n.js | 40 ---------- src/__tests__/vuetify.js | 2 +- src/__tests__/vuex.js | 2 +- src/__tests__/within.js | 2 +- src/{vue-testing-library.js => index.js} | 2 + types/index.d.ts | 65 +++++++++++++++ types/test.ts | 97 +++++++++++++++++++++++ types/tsconfig.json | 17 ++++ types/tslint.json | 7 ++ 40 files changed, 286 insertions(+), 139 deletions(-) delete mode 100644 src/__tests__/components/VueI18n.vue delete mode 100644 src/__tests__/vueI18n.js rename src/{vue-testing-library.js => index.js} (98%) 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/.eslintrc.js b/.eslintrc.js index 356e7ed8..4e9e131e 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/vue3-recommended', diff --git a/README.md b/README.md index 077ed5d3..dade3ebb 100644 --- a/README.md +++ b/README.md @@ -171,8 +171,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 @@ -247,7 +246,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 @@ -261,10 +259,11 @@ 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 [vee-validate-example]: https://github.com/testing-library/vue-testing-library/blob/master/src/__tests__/validate-plugin.js -[vue-i18n-example]: https://github.com/testing-library/vue-testing-library/blob/master/src/__tests__/vueI18n.js +[vue-i18n-example]: https://github.com/testing-library/vue-testing-library/blob/master/src/__tests__/vue-i18n.js [vuetify-example]: https://github.com/testing-library/vue-testing-library/blob/master/src/__tests__/vuetify.js diff --git a/jest.config.js b/jest.config.js index c68ee838..09a45c1d 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,9 +4,6 @@ const config = require('kcd-scripts/jest') module.exports = merge(config, { testEnvironment: 'jsdom', moduleFileExtensions: ['js', 'vue'], - moduleNameMapper: { - '@testing-library/vue': '/src/vue-testing-library.js', - }, coverageDirectory: './coverage', collectCoverageFrom: ['**/src/**/*.js', '!**/src/__tests__/**'], transform: { diff --git a/package.json b/package.json index 317791f7..8029eee1 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name": "@testing-library/vue", "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", + "main": "dist/index.js", + "types": "types/index.d.ts", "scripts": { "format": "kcd-scripts format", "build": "kcd-scripts build", @@ -16,6 +17,7 @@ "node": ">10.18" }, "files": [ + "types", "dist", "cleanup-after-each.js" ], @@ -45,36 +47,38 @@ "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.1", - "@testing-library/dom": "^7.26.3", - "@vue/test-utils": "^2.0.0-beta.10", + "@testing-library/dom": "^7.28.1", + "@vue/test-utils": "^2.0.0-beta.12", "lodash.merge": "^4.6.2" }, "devDependencies": { "@babel/plugin-transform-runtime": "^7.12.1", "@testing-library/jest-dom": "^5.11.5", - "@testing-library/user-event": "^12.2.2", - "@vue/compiler-sfc": "^3.0.2", + "@testing-library/user-event": "^12.4.0", + "@types/estree": "0.0.45", + "@vue/compiler-sfc": "^3.0.4", "apollo-boost": "^0.4.9", "apollo-cache-inmemory": "^1.6.6", "apollo-client": "^2.6.10", "axios": "^0.20.0", + "dtslint": "^4.0.6", "eslint-plugin-vue": "^7.1.0", "graphql": "^15.4.0", "graphql-tag": "^2.11.0", "isomorphic-unfetch": "^3.1.0", "jest-serializer-vue": "^2.0.2", - "kcd-scripts": "^7.0.3", + "kcd-scripts": "^7.5.1", "msw": "^0.21.3", "portal-vue": "^2.1.7", "typescript": "^4.1.2", - "vee-validate": "^4.0.0-beta.16", - "vue": "^3.0.2", + "vee-validate": "^4.0.2", + "vue": "^3.0.4", "vue-apollo": "^3.0.5", "vue-i18n": "^9.0.0-beta.6", - "vue-jest": "^5.0.0-alpha.5", - "vue-router": "^4.0.0-rc.1", - "vuetify": "^2.3.16", - "vuex": "^4.0.0-rc.1" + "vue-jest": "^5.0.0-alpha.7", + "vue-router": "^4.0.0-rc.6", + "vuetify": "^2.3.19", + "vuex": "^4.0.0-rc.2" }, "peerDependencies": { "vue": ">= 3", diff --git a/src/__tests__/auto-cleanup-skip.js b/src/__tests__/auto-cleanup-skip.js index bcd4595a..0806864b 100644 --- a/src/__tests__/auto-cleanup-skip.js +++ b/src/__tests__/auto-cleanup-skip.js @@ -1,7 +1,7 @@ let render -beforeAll(async () => { +beforeAll(() => { process.env.VTL_SKIP_AUTO_CLEANUP = 'true' - const vtl = await require('@testing-library/vue') + const vtl = require('..') render = vtl.render }) diff --git a/src/__tests__/auto-cleanup.js b/src/__tests__/auto-cleanup.js index 8255c083..805d4303 100644 --- a/src/__tests__/auto-cleanup.js +++ b/src/__tests__/auto-cleanup.js @@ -1,4 +1,4 @@ -import {render} from '@testing-library/vue' +import {render} from '..' import '@testing-library/jest-dom' // This verifies that by importing VTL in an environment which supports diff --git a/src/__tests__/axios-mock.js b/src/__tests__/axios-mock.js index c4f066d8..8daeb14a 100644 --- a/src/__tests__/axios-mock.js +++ b/src/__tests__/axios-mock.js @@ -1,6 +1,6 @@ import '@testing-library/jest-dom' import axiosMock from 'axios' -import {render, fireEvent} from '@testing-library/vue' +import {render, fireEvent} from '..' import Component from './components/Fetch.vue' test('mocks an API call when load-greeting is clicked', async () => { diff --git a/src/__tests__/cleanup-throw.js b/src/__tests__/cleanup-throw.js index 828820f2..d424d59f 100644 --- a/src/__tests__/cleanup-throw.js +++ b/src/__tests__/cleanup-throw.js @@ -1,6 +1,6 @@ test.todo('check if this test still makes sense') -// import {render, cleanup} from '@testing-library/vue' +// import {render, cleanup} from '..' // import {nextTick} from 'vue' // test('cleanup re-throws errors from async lifecycle hooks', async () => { diff --git a/src/__tests__/components/Translations.vue b/src/__tests__/components/Translations.vue index 82fcbbb8..f431c86c 100644 --- a/src/__tests__/components/Translations.vue +++ b/src/__tests__/components/Translations.vue @@ -1,9 +1,9 @@ diff --git a/src/__tests__/debug.js b/src/__tests__/debug.js index c17d4cd2..554feb04 100644 --- a/src/__tests__/debug.js +++ b/src/__tests__/debug.js @@ -1,5 +1,5 @@ /* eslint-disable testing-library/no-debug */ -import {render} from '@testing-library/vue' +import {render} from '..' import HelloWorld from './components/HelloWorld' beforeEach(() => { diff --git a/src/__tests__/directive.js b/src/__tests__/directive.js index 5cf4981f..f003f0fc 100644 --- a/src/__tests__/directive.js +++ b/src/__tests__/directive.js @@ -1,4 +1,4 @@ -import {render} from '@testing-library/vue' +import {render} from '..' import '@testing-library/jest-dom' import {uppercaseDirective} from './directives/uppercase-directive' import ComponentUsingDirective from './components/Directive' diff --git a/src/__tests__/disappearance.js b/src/__tests__/disappearance.js index 040848fc..8746f9ed 100644 --- a/src/__tests__/disappearance.js +++ b/src/__tests__/disappearance.js @@ -1,4 +1,4 @@ -import {render, waitForElementToBeRemoved} from '@testing-library/vue' +import {render, waitForElementToBeRemoved} from '..' import Disappearance from './components/Disappearance' import '@testing-library/jest-dom' diff --git a/src/__tests__/fire-event.js b/src/__tests__/fire-event.js index 7e18a512..19cef6b2 100644 --- a/src/__tests__/fire-event.js +++ b/src/__tests__/fire-event.js @@ -1,5 +1,5 @@ import {h} from 'vue' -import {render, fireEvent} from '@testing-library/vue' +import {render, fireEvent} from '..' import Button from './components/Button' const eventTypes = [ @@ -231,3 +231,20 @@ test('fireEvent.update does not crash if non-input element is passed in', async expect(console.warn).not.toHaveBeenCalled() }) + +test('fireEvent.update handles input file', async () => { + const {getByTestId} = render({ + template: ``, + }) + + const file = new File(['(⌐□_□)'], 'chucknorris.png', {type: 'image/png'}) + + const inputEl = getByTestId('test-update') + + // You could replace the lines below with + // userEvent.upload(inputEl, file) + Object.defineProperty(inputEl, 'files', {value: [file]}) + await fireEvent.update(inputEl) + + expect(console.warn).not.toHaveBeenCalled() +}) diff --git a/src/__tests__/form.js b/src/__tests__/form.js index 58493bed..5011b359 100644 --- a/src/__tests__/form.js +++ b/src/__tests__/form.js @@ -1,4 +1,4 @@ -import {render, fireEvent} from '@testing-library/vue' +import {render, fireEvent} from '..' import '@testing-library/jest-dom' import Form from './components/Form' diff --git a/src/__tests__/functional.js b/src/__tests__/functional.js index 22b8fee4..323a6ff2 100644 --- a/src/__tests__/functional.js +++ b/src/__tests__/functional.js @@ -1,6 +1,6 @@ -import {render} from '@testing-library/vue' -import '@testing-library/jest-dom' import {h} from 'vue' +import {render} from '..' +import '@testing-library/jest-dom' // From docs: Performance gains from 2.x for functional components are now // negligible in 3.x, so we recommend just using stateful components. diff --git a/src/__tests__/render.js b/src/__tests__/render.js index 6d8d4d69..151bd8bc 100644 --- a/src/__tests__/render.js +++ b/src/__tests__/render.js @@ -1,4 +1,4 @@ -import {render} from '@testing-library/vue' +import {render} from '..' import '@testing-library/jest-dom' test('baseElement defaults to document.body', () => { diff --git a/src/__tests__/rerender.js b/src/__tests__/rerender.js index 5951870c..d72dc1b6 100644 --- a/src/__tests__/rerender.js +++ b/src/__tests__/rerender.js @@ -1,6 +1,6 @@ import '@testing-library/jest-dom' import {defineComponent, h, computed} from 'vue' -import {render} from '@testing-library/vue' +import {render} from '..' import NumberDisplay from './components/NumberDisplay' // It'd probably be better if you test the component that's doing the rerendering diff --git a/src/__tests__/select.js b/src/__tests__/select.js index 304d7bd0..c8e79750 100644 --- a/src/__tests__/select.js +++ b/src/__tests__/select.js @@ -1,4 +1,4 @@ -import {render, fireEvent} from '@testing-library/vue' +import {render, fireEvent} from '..' import '@testing-library/jest-dom' import Select from './components/Select' @@ -9,19 +9,19 @@ test('Select component', async () => { // Get the Select element by using the initially displayed value. const select = getByDisplayValue('Tyrannosaurus') - expect(select.value).toBe('dino1') + expect(select).toHaveValue('dino1') // Update it by manually sending a valid option value. await fireEvent.update(select, 'dino2') - expect(select.value).toBe('dino2') + expect(select).toHaveValue('dino2') // We can trigger an update event by directly getting the . optionElement = getByText('Diplodocus') await fireEvent.update(optionElement) - expect(select.value).toBe('dino4') + expect(select).toHaveValue('dino4') }) diff --git a/src/__tests__/simple-button.js b/src/__tests__/simple-button.js index a63d91ea..29698a4a 100644 --- a/src/__tests__/simple-button.js +++ b/src/__tests__/simple-button.js @@ -1,4 +1,4 @@ -import {render, fireEvent} from '@testing-library/vue' +import {render, fireEvent} from '..' import Button from './components/Button' import '@testing-library/jest-dom' diff --git a/src/__tests__/slots.js b/src/__tests__/slots.js index 45ffb0fb..6fc643d6 100644 --- a/src/__tests__/slots.js +++ b/src/__tests__/slots.js @@ -1,5 +1,5 @@ import '@testing-library/jest-dom' -import {render} from '@testing-library/vue' +import {render} from '..' import Card from './components/Card' // Usage is the same as Vue Test Utils, since slots values are passed using the `slots` diff --git a/src/__tests__/stopwatch.js b/src/__tests__/stopwatch.js index 996a7e9e..b0e9737f 100644 --- a/src/__tests__/stopwatch.js +++ b/src/__tests__/stopwatch.js @@ -1,5 +1,5 @@ import '@testing-library/jest-dom' -import {render, waitFor, fireEvent} from '@testing-library/vue' +import {render, waitFor, fireEvent} from '..' import StopWatch from './components/StopWatch.vue' const sleep = ms => diff --git a/src/__tests__/stubs.js b/src/__tests__/stubs.js index f255b15c..38d8ddbd 100644 --- a/src/__tests__/stubs.js +++ b/src/__tests__/stubs.js @@ -1,4 +1,4 @@ -import {render} from '@testing-library/vue' +import {render} from '..' import '@testing-library/jest-dom' import Stubs from './components/Stubs' diff --git a/src/__tests__/user-event.js b/src/__tests__/user-event.js index 21e123e6..4db8c3e6 100644 --- a/src/__tests__/user-event.js +++ b/src/__tests__/user-event.js @@ -1,6 +1,6 @@ import '@testing-library/jest-dom' -import {render} from '@testing-library/vue' import userEvent from '@testing-library/user-event' +import {render, waitFor} from '..' import Form from './components/Form' import Select from './components/Select' @@ -24,49 +24,49 @@ test('User events in a form', async () => { expect(submitButton).toBeDisabled() const titleInput = getByLabelText(/title of the movie/i) - await userEvent.type(titleInput, fakeReview.title) - expect(titleInput.value).toEqual(fakeReview.title) + userEvent.type(titleInput, fakeReview.title) + expect(titleInput).toHaveValue(fakeReview.title) const textArea = getByLabelText(/Your review/i) - await userEvent.type(textArea, 'The t-rex went insane!') - expect(textArea.value).toEqual('The t-rex went insane!') + userEvent.type(textArea, 'The t-rex went insane!') + expect(textArea).toHaveValue('The t-rex went insane!') - await userEvent.clear(textArea) - expect(textArea.value).toEqual('') - await userEvent.type(textArea, fakeReview.review) - expect(textArea.value).toEqual(fakeReview.review) + userEvent.clear(textArea) + expect(textArea).toHaveValue('') + userEvent.type(textArea, fakeReview.review) + expect(textArea).toHaveValue(fakeReview.review) const initialSelectedRating = getByLabelText(/Awful/i) const wonderfulRadioInput = getByLabelText(/Wonderful/i) expect(initialSelectedRating).toBeChecked() expect(wonderfulRadioInput).not.toBeChecked() - await userEvent.click(wonderfulRadioInput) + userEvent.click(wonderfulRadioInput) expect(wonderfulRadioInput).toBeChecked() - expect(initialSelectedRating).not.toBeChecked() + await waitFor(() => expect(initialSelectedRating).not.toBeChecked()) const recommendInput = getByLabelText(/Would you recommend this movie?/i) - await userEvent.click(recommendInput) + userEvent.click(recommendInput) expect(recommendInput).toBeChecked() userEvent.tab() expect(submitButton).toHaveFocus() expect(submitButton).toBeEnabled() - await userEvent.type(submitButton, '{enter}') + userEvent.type(submitButton, '{enter}') expect(emitted().submit[0][0]).toMatchObject(fakeReview) expect(console.warn).not.toHaveBeenCalled() }) -test('selecting option with user events', async () => { +test('selecting option with user events', () => { const {getByDisplayValue} = render(Select) const select = getByDisplayValue('Tyrannosaurus') - expect(select.value).toBe('dino1') + expect(select).toHaveValue('dino1') - await userEvent.selectOptions(select, 'dino2') - expect(select.value).toBe('dino2') + userEvent.selectOptions(select, 'dino2') + expect(select).toHaveValue('dino2') - await userEvent.selectOptions(select, 'dino3') - expect(select.value).not.toBe('dino2') - expect(select.value).toBe('dino3') + userEvent.selectOptions(select, 'dino3') + expect(select).not.toHaveValue('dino2') + expect(select).toHaveValue('dino3') }) diff --git a/src/__tests__/validate-plugin.js b/src/__tests__/validate-plugin.js index 22cd80e6..12d58981 100644 --- a/src/__tests__/validate-plugin.js +++ b/src/__tests__/validate-plugin.js @@ -1,6 +1,6 @@ import '@testing-library/jest-dom' -import {render, fireEvent} from '@testing-library/vue' +import {render, fireEvent} from '..' import VeeValidate from './components/Validate' test('can validate using plugin', async () => { diff --git a/src/__tests__/visibility.js b/src/__tests__/visibility.js index 0e9436a7..3cda56e4 100644 --- a/src/__tests__/visibility.js +++ b/src/__tests__/visibility.js @@ -1,4 +1,4 @@ -import {render, fireEvent} from '@testing-library/vue' +import {render, fireEvent} from '..' import '@testing-library/jest-dom' import Collapsible from './components/Collapsible' diff --git a/src/__tests__/vue-apollo.js b/src/__tests__/vue-apollo.js index 2c8cd316..d7728ed8 100644 --- a/src/__tests__/vue-apollo.js +++ b/src/__tests__/vue-apollo.js @@ -1,7 +1,7 @@ test.todo('Your test suite must contain at least one test.') // import '@testing-library/jest-dom' // import fetch from 'isomorphic-unfetch' -// import {render, fireEvent, screen} from '@testing-library/vue' +// import {render, fireEvent, screen} from '..' // import VueApollo from 'vue-apollo' // import {InMemoryCache} from 'apollo-cache-inmemory' // import ApolloClient from 'apollo-boost' diff --git a/src/__tests__/vue-i18n.js b/src/__tests__/vue-i18n.js index 24ede3b9..4dd1d2bd 100644 --- a/src/__tests__/vue-i18n.js +++ b/src/__tests__/vue-i18n.js @@ -1,6 +1,6 @@ import '@testing-library/jest-dom' -import {render, fireEvent} from '@testing-library/vue' import {createI18n} from 'vue-i18n' +import {render, fireEvent} from '..' import Translations from './components/Translations' const i18n = createI18n({ @@ -29,5 +29,5 @@ test('renders translations', async () => { expect(getByText('こんにちは')).toBeInTheDocument() - expect(queryByText('Hello')).toBeNull() + expect(queryByText('Hello')).not.toBeInTheDocument() }) diff --git a/src/__tests__/vue-portal.js b/src/__tests__/vue-portal.js index 9128d62c..7aa87c82 100644 --- a/src/__tests__/vue-portal.js +++ b/src/__tests__/vue-portal.js @@ -1,6 +1,6 @@ test.todo('Your test suite must contain at least one test.') -// import {render, waitFor} from '@testing-library/vue' +// import {render, waitFor} from '..' // import '@testing-library/jest-dom/extend-expect' // import PortalVue from 'portal-vue' diff --git a/src/__tests__/vue-router-mocha.js b/src/__tests__/vue-router-mocha.js index cec76851..2f7553d1 100644 --- a/src/__tests__/vue-router-mocha.js +++ b/src/__tests__/vue-router-mocha.js @@ -1,7 +1,7 @@ test.todo('Your test suite must contain at least one test.') // import '@testing-library/jest-dom' -// import {render} from '@testing-library/vue' +// import {render} from '..' // import About from './components/Router/About.vue' diff --git a/src/__tests__/vue-router.js b/src/__tests__/vue-router.js index 67cbb23a..d2e098d3 100644 --- a/src/__tests__/vue-router.js +++ b/src/__tests__/vue-router.js @@ -5,7 +5,7 @@ test.todo('Your test suite must contain at least one test.') // // Related issue on Vue Test Utils: https://github.com/vuejs/vue-test-utils-next/issues/152 // import '@testing-library/jest-dom' -// import {render, fireEvent} from '@testing-library/vue' +// import {render, fireEvent} from '..' // import App from './components/Router/App.vue' // import Home from './components/Router/Home.vue' // import About from './components/Router/About.vue' diff --git a/src/__tests__/vueI18n.js b/src/__tests__/vueI18n.js deleted file mode 100644 index 2ac7ad94..00000000 --- a/src/__tests__/vueI18n.js +++ /dev/null @@ -1,40 +0,0 @@ -test.todo('Your test suite must contain at least one test.') - -// import '@testing-library/jest-dom' -// import {render, fireEvent} from '@testing-library/vue' -// import Vuei18n from 'vue-i18n' -// import VueI18n from './components/VueI18n' - -// const messages = { -// en: { -// Hello: 'Hello', -// }, -// ja: { -// Hello: 'こんにちは', -// }, -// } - -// test('renders translations', async () => { -// const {queryByText, getByText} = render(VueI18n, {}, vue => { -// // Let's register Vuei18n normally -// vue.use(Vuei18n) - -// const i18n = new Vuei18n({ -// locale: 'en', -// fallbackLocale: 'en', -// messages, -// }) - -// // Notice how we return an object from the callback function. It will be -// // available as an additional option on the created Vue instance. -// return {i18n} -// }) - -// expect(getByText('Hello')).toBeInTheDocument() - -// await fireEvent.click(getByText('Japanese')) - -// expect(getByText('こんにちは')).toBeInTheDocument() - -// expect(queryByText('Hello')).toBeNull() -// }) diff --git a/src/__tests__/vuetify.js b/src/__tests__/vuetify.js index 5a7261ed..a933e8ef 100644 --- a/src/__tests__/vuetify.js +++ b/src/__tests__/vuetify.js @@ -1,7 +1,7 @@ test.todo('Your test suite must contain at least one test.') // import '@testing-library/jest-dom' // import Vue from 'vue' -// import {render, fireEvent} from '@testing-library/vue' +// import {render, fireEvent} from '..' // import Vuetify from 'vuetify' // import VuetifyDemoComponent from './components/Vuetify' diff --git a/src/__tests__/vuex.js b/src/__tests__/vuex.js index 233e4533..7873f5bf 100644 --- a/src/__tests__/vuex.js +++ b/src/__tests__/vuex.js @@ -1,5 +1,5 @@ import '@testing-library/jest-dom' -import {render, fireEvent} from '@testing-library/vue' +import {render, fireEvent} from '..' import VuexTest from './components/Store/VuexTest' import {store} from './components/Store/store' diff --git a/src/__tests__/within.js b/src/__tests__/within.js index 245e4ed7..3a85664f 100644 --- a/src/__tests__/within.js +++ b/src/__tests__/within.js @@ -1,4 +1,4 @@ -import {render, within} from '@testing-library/vue' +import {render, within} from '..' test('within() returns an object with all queries bound to the DOM node', () => { const {getByTestId, getByText} = render({ diff --git a/src/vue-testing-library.js b/src/index.js similarity index 98% rename from src/vue-testing-library.js rename to src/index.js index b4e2eaf0..4fbdf6e0 100644 --- a/src/vue-testing-library.js +++ b/src/index.js @@ -170,6 +170,8 @@ fireEvent.update = (elem, value) => { if (['checkbox', 'radio'].includes(type)) { elem.checked = true return fireEvent.change(elem) + } else if (type === 'file') { + return fireEvent.change(elem) } else { elem.value = value return fireEvent.input(elem) diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 00000000..5dec7fa4 --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,65 @@ +// Minimum TypeScript Version: 4.0 +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import {EmitsOptions} from 'vue' +import {MountingOptions} from '@vue/test-utils' +import {StoreOptions} from 'vuex' +import {queries, EventType, BoundFunctions} from '@testing-library/dom' +// eslint-disable-next-line import/no-extraneous-dependencies +import {OptionsReceived as PrettyFormatOptions} from 'pretty-format' + +// NOTE: fireEvent is overridden below +export * from '@testing-library/dom' + +type Debug = ( + baseElement?: Element | DocumentFragment | Array, + maxLength?: number, + options?: PrettyFormatOptions, +) => void + +export interface RenderResult extends BoundFunctions { + container: Element + baseElement: Element + debug: Debug + unmount(): void + html(): string + emitted(): EmitsOptions + rerender(props: object): Promise +} + +type VueTestUtilsRenderOptions = Omit< + MountingOptions>, + 'attachTo' | 'shallow' | 'propsData' +> +type VueTestingLibraryRenderOptions = { + store?: StoreOptions<{}> + // router?: ¿¿¿??? + container?: Element + baseElement?: Element +} +type RenderOptions = VueTestUtilsRenderOptions & VueTestingLibraryRenderOptions + +export function render( + TestComponent: any, // this makes me sad :sob: + options?: RenderOptions, +): 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: Element, value?: string): Promise +} + +export const fireEvent: VueFireEventObject diff --git a/types/test.ts b/types/test.ts new file mode 100644 index 00000000..b11b466b --- /dev/null +++ b/types/test.ts @@ -0,0 +1,97 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions */ +import {defineComponent} from 'vue' +import {render, fireEvent, screen, waitFor} from '@testing-library/vue' + +declare const elem: Element + +const SomeComponent = defineComponent({ + name: 'SomeComponent', + props: { + foo: {type: Number, default: 0}, + bar: {type: String, default: '0'}, + }, +}) + +export 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, baseElement, unmount, debug, rerender} = page + + // eslint-disable-next-line @typescript-eslint/no-floating-promises + rerender({a: 1}) // $ExpectType Promise + + debug() // $ExpectType void + debug(container) // $ExpectType void + debug([elem, elem], 100, {highlight: false}) // $ExpectType void + + unmount() // $ExpectType void + + container // $ExpectType Element + baseElement // $ExpectType Element +} + +export function testRenderOptions() { + const container = document.createElement('div') + const baseElement = document.createElement('div') + const options = {container, baseElement} + render({template: 'div'}, options) +} + +export async function testFireEvent() { + const {container} = render({template: 'button'}) + await fireEvent.click(container) // $ExpectType Promise + await fireEvent.touch(elem) // $ExpectType Promise +} + +export async function testScreen() { + render({template: 'button'}) + + await screen.findByRole('button') // $ExpectType Promise +} + +export async function testWaitFor() { + const {container} = render({template: 'button'}) + await fireEvent.update(container) // $ExpectType Promise + await waitFor(() => {}) +} + +export function testOptions() { + render(SomeComponent, { + attrs: {a: 1}, + props: {c: 1}, // ideally it would fail because `c` is not an existing prop… + data: () => ({b: 2}), + slots: { + default: '
', + footer: '
', + }, + global: { + config: {isCustomElement: _ => true}, + }, + store: { + state: {count: 3}, + strict: true, + }, + baseElement: document.createElement('div'), + container: document.createElement('div'), + }) +} + +/* +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", +*/ diff --git a/types/tsconfig.json b/types/tsconfig.json new file mode 100644 index 00000000..d822978b --- /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": ["ES2020", "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..70c4494b --- /dev/null +++ b/types/tslint.json @@ -0,0 +1,7 @@ +{ + "extends": "dtslint/dtslint.json", + "rules": { + "semicolon": false, + "whitespace": false + } +}