From c231627a2d53e704d639b622f9f84ebf50d0359a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Sun, 22 Nov 2020 10:59:57 +0100 Subject: [PATCH 01/14] Add basic types --- src/index.js | 207 ++++++++++++++++++++++++++++++++++++++++++++ types/index.d.ts | 124 ++++++++++++++++++++++++++ types/test.ts | 105 ++++++++++++++++++++++ types/tsconfig.json | 17 ++++ types/tslint.json | 7 ++ 5 files changed, 460 insertions(+) create mode 100644 src/index.js 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/src/index.js b/src/index.js new file mode 100644 index 00000000..b4e2eaf0 --- /dev/null +++ b/src/index.js @@ -0,0 +1,207 @@ +/* eslint-disable testing-library/no-wait-for-empty-callback */ +import {mount} from '@vue/test-utils' +import merge from 'lodash.merge' + +import { + getQueriesForElement, + prettyDOM, + waitFor, + fireEvent as dtlFireEvent, +} from '@testing-library/dom' + +const mountedWrappers = new Set() + +function render( + TestComponent, + { + store = null, + // routes = null, + container: customContainer, + baseElement: customBaseElement, + ...mountOptions + } = {}, + // configurationCb, +) { + const div = document.createElement('div') + const baseElement = customBaseElement || customContainer || document.body + const container = customContainer || baseElement.appendChild(div) + + // let additionalOptions = {} + + const plugins = [] + + if (store) { + const {createStore} = require('vuex') + plugins.push(createStore(store)) + } + + // if (routes) { + // const requiredRouter = require('vue-router') + // const {createRouter, createWebHistory} = + // requiredRouter.default || requiredRouter + // plugins.push(createRouter({history: createWebHistory(), routes})) + // } + + // Should we expose vue 3 app? if so, how? + // if (configurationCb && typeof configurationCb === 'function') { + // additionalOptions = configurationCb(router) + // } + + const mountComponent = (Component, newProps) => { + const wrapper = mount( + Component, + merge({ + attachTo: container, + global: { + plugins, + }, + ...mountOptions, + props: newProps || mountOptions.props, + // ...additionalOptions, + }), + ) + + // this removes the additional "data-v-app" div node from VTU: + // https://github.com/vuejs/vue-test-utils-next/blob/master/src/mount.ts#L196-L213 + unwrapNode(wrapper.parentElement) + + mountedWrappers.add(wrapper) + return wrapper + } + + let wrapper = mountComponent(TestComponent) + + return { + container, + baseElement, + debug: (el = baseElement, maxLength, options) => + Array.isArray(el) + ? el.forEach(e => console.log(prettyDOM(e, maxLength, options))) + : console.log(prettyDOM(el, maxLength, options)), + unmount: () => wrapper.unmount(), + html: () => wrapper.html(), + emitted: () => wrapper.emitted(), + rerender: ({props}) => { + wrapper.unmount() + mountedWrappers.delete(wrapper) + + wrapper = mountComponent(TestComponent, props) + }, + ...getQueriesForElement(baseElement), + } +} + +function unwrapNode(node) { + node.replaceWith(...node.childNodes) +} + +function cleanup() { + mountedWrappers.forEach(cleanupAtWrapper) +} + +function cleanupAtWrapper(wrapper) { + if ( + wrapper.element.parentNode && + wrapper.element.parentNode.parentNode === document.body + ) { + document.body.removeChild(wrapper.element.parentNode) + } + + try { + wrapper.unmount() + } finally { + mountedWrappers.delete(wrapper) + } +} + +// Vue Testing Library's version of fireEvent will call DOM Testing Library's +// version of fireEvent plus wait for one tick of the event loop to allow Vue +// to asynchronously handle the event. +// More info: https://vuejs.org/v2/guide/reactivity.html#Async-Update-Queue +async function fireEvent(...args) { + dtlFireEvent(...args) + await waitFor(() => {}) +} + +function suggestUpdateIfNecessary(eventValue, eventKey) { + const changeOrInputEventCalledDirectly = + eventValue && (eventKey === 'change' || eventKey === 'input') + + if (changeOrInputEventCalledDirectly) { + console.warn( + `Using fireEvent.${eventKey}() may lead to unexpected results. Please use fireEvent.update() instead.`, + ) + } +} + +Object.keys(dtlFireEvent).forEach(key => { + fireEvent[key] = async (...args) => { + suggestUpdateIfNecessary(args[1], key) + dtlFireEvent[key](...args) + await waitFor(() => {}) + } +}) + +fireEvent.touch = async elem => { + await fireEvent.focus(elem) + await fireEvent.blur(elem) +} + +// Small utility to provide a better experience when working with v-model. +// Related upstream issue: https://github.com/vuejs/vue-test-utils/issues/345#issuecomment-380588199 +// Examples: https://github.com/testing-library/vue-testing-library/blob/master/src/__tests__/form.js +fireEvent.update = (elem, value) => { + const tagName = elem.tagName + const type = elem.type + + switch (tagName) { + case 'OPTION': { + elem.selected = true + + const parentSelectElement = + elem.parentElement.tagName === 'OPTGROUP' + ? elem.parentElement.parentElement + : elem.parentElement + + return fireEvent.change(parentSelectElement) + } + + case 'INPUT': { + if (['checkbox', 'radio'].includes(type)) { + elem.checked = true + return fireEvent.change(elem) + } else { + elem.value = value + return fireEvent.input(elem) + } + } + + case 'TEXTAREA': { + elem.value = value + return fireEvent.input(elem) + } + + case 'SELECT': { + elem.value = value + return fireEvent.change(elem) + } + + default: + // do nothing + } + + return null +} + +// If we're running in a test runner that supports afterEach then we'll +// automatically run cleanup after each test. This ensures that tests run in +// isolation from each other. +// If you don't like this, set the VTL_SKIP_AUTO_CLEANUP variable to 'true'. +if (typeof afterEach === 'function' && !process.env.VTL_SKIP_AUTO_CLEANUP) { + afterEach(() => { + cleanup() + }) +} + +export * from '@testing-library/dom' +export {cleanup, render, fireEvent} diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 00000000..3b0b03a2 --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,124 @@ +// TypeScript Version: 4.1 +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { + VNode, + Component, + Directive, + Plugin, + AppConfig, + EmitsOptions, + ComponentOptions, +} from 'vue' +import {} 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' + +/** vvvvvvvvv THIS SHOULD COME FROM VUE TEST UTILS vvvvvvvvvvvvv */ +/** vvvvvvvvv THIS SHOULD COME FROM VUE TEST UTILS vvvvvvvvvvvvv */ +/** vvvvvvvvv THIS SHOULD COME FROM VUE TEST UTILS vvvvvvvvvvvvv */ +/** vvvvvvvvv THIS SHOULD COME FROM VUE TEST UTILS vvvvvvvvvvvvv */ +/** vvvvvvvvv THIS SHOULD COME FROM VUE TEST UTILS vvvvvvvvvvvvv */ +/** vvvvvvvvv THIS SHOULD COME FROM VUE TEST UTILS vvvvvvvvvvvvv */ +/** vvvvvvvvv THIS SHOULD COME FROM VUE TEST UTILS vvvvvvvvvvvvv */ +/** vvvvvvvvv THIS SHOULD COME FROM VUE TEST UTILS vvvvvvvvvvvvv */ +/** vvvvvvvvv THIS SHOULD COME FROM VUE TEST UTILS vvvvvvvvvvvvv */ + +type GlobalMountOptions = { + plugins?: (Plugin | [Plugin, ...any[]])[] + config?: Partial> // isNativeTag is readonly, so we omit it + mixins?: ComponentOptions[] + mocks?: Record + provide?: Record + components?: Record + directives?: Record + stubs?: Record + renderStubDefaultSlot?: boolean +} + +type Slot = VNode | string | {render: Function} | Function | Component + +type SlotDictionary = { + [key: string]: Slot +} + +interface MountingOptions { + data?: () => {} extends Data ? any : Data extends object ? Partial : any + props?: Props + attrs?: Record + slots?: SlotDictionary & { + default?: Slot + } + global?: GlobalMountOptions + attachTo?: HTMLElement | string + shallow?: boolean +} + +/** ^^^^^^^^^^^^ THIS SHOULD COME FROM VUE TEST UTILS ^^^^^^^^^^^^ */ +/** ^^^^^^^^^^^^ THIS SHOULD COME FROM VUE TEST UTILS ^^^^^^^^^^^^ */ +/** ^^^^^^^^^^^^ THIS SHOULD COME FROM VUE TEST UTILS ^^^^^^^^^^^^ */ +/** ^^^^^^^^^^^^ THIS SHOULD COME FROM VUE TEST UTILS ^^^^^^^^^^^^ */ +/** ^^^^^^^^^^^^ THIS SHOULD COME FROM VUE TEST UTILS ^^^^^^^^^^^^ */ +/** ^^^^^^^^^^^^ THIS SHOULD COME FROM VUE TEST UTILS ^^^^^^^^^^^^ */ +/** ^^^^^^^^^^^^ THIS SHOULD COME FROM VUE TEST UTILS ^^^^^^^^^^^^ */ +/** ^^^^^^^^^^^^ THIS SHOULD COME FROM VUE TEST UTILS ^^^^^^^^^^^^ */ +/** ^^^^^^^^^^^^ THIS SHOULD COME FROM VUE TEST UTILS ^^^^^^^^^^^^ */ +/** ^^^^^^^^^^^^ THIS SHOULD COME FROM VUE TEST UTILS ^^^^^^^^^^^^ */ + +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..30aac5ab --- /dev/null +++ b/types/test.ts @@ -0,0 +1,105 @@ +import {defineComponent, h} 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, unmount, debug, rerender} = page + + debug(container) + + await rerender({a: 1}) + + debug(elem) // $ExpectType void + debug([elem, elem], 100, {highlight: false}) // $ExpectType void + + unmount() // $ExpectType void +} + +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) +} + +export function testDebug() { + const {debug, getAllByTestId} = render({ + render() { + return h('div', [ + h('h1', {attrs: {'data-testid': 'testid'}}, 'hello world'), + h('h2', {attrs: {'data-testid': 'testid'}}, 'hello world'), + ]) + }, + }) + + debug(getAllByTestId('testid')) +} + +export async function testScreen() { + render({template: 'button'}) + + await screen.findByRole('button') +} + +export async function testWaitFor() { + const {container} = render({template: 'button'}) + await fireEvent.click(container) + 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..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 daa82e4b7ad128e302f20eacafac8a186f0bc9aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Sun, 22 Nov 2020 11:00:17 +0100 Subject: [PATCH 02/14] Rename main file to properly get types --- .eslintrc.js | 3 + README.md | 5 +- jest.config.js | 3 - package.json | 7 +- src/__tests__/auto-cleanup.js | 2 +- src/__tests__/axios-mock.js | 2 +- src/__tests__/cleanup-throw.js | 2 +- src/__tests__/debug.js | 2 +- src/__tests__/directive.js | 2 +- src/__tests__/disappearance.js | 2 +- src/__tests__/fire-event.js | 2 +- src/__tests__/form.js | 2 +- src/__tests__/functional.js | 2 +- src/__tests__/render.js | 2 +- src/__tests__/rerender.js | 2 +- src/__tests__/select.js | 2 +- 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 | 2 +- src/__tests__/validate-plugin.js | 2 +- src/__tests__/visibility.js | 2 +- src/__tests__/vue-apollo.js | 2 +- src/__tests__/vue-i18n.js | 2 +- src/__tests__/vue-portal.js | 2 +- src/__tests__/vue-router-mocha.js | 2 +- src/__tests__/vue-router.js | 2 +- src/__tests__/vueI18n.js | 2 +- src/__tests__/vuetify.js | 2 +- src/__tests__/vuex.js | 2 +- src/__tests__/within.js | 2 +- src/vue-testing-library.js | 207 ------------------------------ 33 files changed, 39 insertions(+), 242 deletions(-) delete mode 100644 src/vue-testing-library.js 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..ec6ff7df 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,6 +259,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 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..5a3682af 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", @@ -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" ], @@ -53,11 +56,13 @@ "@babel/plugin-transform-runtime": "^7.12.1", "@testing-library/jest-dom": "^5.11.5", "@testing-library/user-event": "^12.2.2", + "@types/estree": "0.0.45", "@vue/compiler-sfc": "^3.0.2", "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", 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__/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..ef6e11dc 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 = [ 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..290528e7 100644 --- a/src/__tests__/functional.js +++ b/src/__tests__/functional.js @@ -1,4 +1,4 @@ -import {render} from '@testing-library/vue' +import {render} from '..' import '@testing-library/jest-dom' import {h} from 'vue' 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..876c6ca9 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' 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..4bf7827d 100644 --- a/src/__tests__/user-event.js +++ b/src/__tests__/user-event.js @@ -1,5 +1,5 @@ import '@testing-library/jest-dom' -import {render} from '@testing-library/vue' +import {render} from '..' import userEvent from '@testing-library/user-event' import Form from './components/Form' import Select from './components/Select' 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..d833629f 100644 --- a/src/__tests__/vue-i18n.js +++ b/src/__tests__/vue-i18n.js @@ -1,5 +1,5 @@ import '@testing-library/jest-dom' -import {render, fireEvent} from '@testing-library/vue' +import {render, fireEvent} from '..' import {createI18n} from 'vue-i18n' import Translations from './components/Translations' 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 index 2ac7ad94..470c5816 100644 --- a/src/__tests__/vueI18n.js +++ b/src/__tests__/vueI18n.js @@ -1,7 +1,7 @@ test.todo('Your test suite must contain at least one test.') // import '@testing-library/jest-dom' -// import {render, fireEvent} from '@testing-library/vue' +// import {render, fireEvent} from '..' // import Vuei18n from 'vue-i18n' // import VueI18n from './components/VueI18n' 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/vue-testing-library.js deleted file mode 100644 index b4e2eaf0..00000000 --- a/src/vue-testing-library.js +++ /dev/null @@ -1,207 +0,0 @@ -/* eslint-disable testing-library/no-wait-for-empty-callback */ -import {mount} from '@vue/test-utils' -import merge from 'lodash.merge' - -import { - getQueriesForElement, - prettyDOM, - waitFor, - fireEvent as dtlFireEvent, -} from '@testing-library/dom' - -const mountedWrappers = new Set() - -function render( - TestComponent, - { - store = null, - // routes = null, - container: customContainer, - baseElement: customBaseElement, - ...mountOptions - } = {}, - // configurationCb, -) { - const div = document.createElement('div') - const baseElement = customBaseElement || customContainer || document.body - const container = customContainer || baseElement.appendChild(div) - - // let additionalOptions = {} - - const plugins = [] - - if (store) { - const {createStore} = require('vuex') - plugins.push(createStore(store)) - } - - // if (routes) { - // const requiredRouter = require('vue-router') - // const {createRouter, createWebHistory} = - // requiredRouter.default || requiredRouter - // plugins.push(createRouter({history: createWebHistory(), routes})) - // } - - // Should we expose vue 3 app? if so, how? - // if (configurationCb && typeof configurationCb === 'function') { - // additionalOptions = configurationCb(router) - // } - - const mountComponent = (Component, newProps) => { - const wrapper = mount( - Component, - merge({ - attachTo: container, - global: { - plugins, - }, - ...mountOptions, - props: newProps || mountOptions.props, - // ...additionalOptions, - }), - ) - - // this removes the additional "data-v-app" div node from VTU: - // https://github.com/vuejs/vue-test-utils-next/blob/master/src/mount.ts#L196-L213 - unwrapNode(wrapper.parentElement) - - mountedWrappers.add(wrapper) - return wrapper - } - - let wrapper = mountComponent(TestComponent) - - return { - container, - baseElement, - debug: (el = baseElement, maxLength, options) => - Array.isArray(el) - ? el.forEach(e => console.log(prettyDOM(e, maxLength, options))) - : console.log(prettyDOM(el, maxLength, options)), - unmount: () => wrapper.unmount(), - html: () => wrapper.html(), - emitted: () => wrapper.emitted(), - rerender: ({props}) => { - wrapper.unmount() - mountedWrappers.delete(wrapper) - - wrapper = mountComponent(TestComponent, props) - }, - ...getQueriesForElement(baseElement), - } -} - -function unwrapNode(node) { - node.replaceWith(...node.childNodes) -} - -function cleanup() { - mountedWrappers.forEach(cleanupAtWrapper) -} - -function cleanupAtWrapper(wrapper) { - if ( - wrapper.element.parentNode && - wrapper.element.parentNode.parentNode === document.body - ) { - document.body.removeChild(wrapper.element.parentNode) - } - - try { - wrapper.unmount() - } finally { - mountedWrappers.delete(wrapper) - } -} - -// Vue Testing Library's version of fireEvent will call DOM Testing Library's -// version of fireEvent plus wait for one tick of the event loop to allow Vue -// to asynchronously handle the event. -// More info: https://vuejs.org/v2/guide/reactivity.html#Async-Update-Queue -async function fireEvent(...args) { - dtlFireEvent(...args) - await waitFor(() => {}) -} - -function suggestUpdateIfNecessary(eventValue, eventKey) { - const changeOrInputEventCalledDirectly = - eventValue && (eventKey === 'change' || eventKey === 'input') - - if (changeOrInputEventCalledDirectly) { - console.warn( - `Using fireEvent.${eventKey}() may lead to unexpected results. Please use fireEvent.update() instead.`, - ) - } -} - -Object.keys(dtlFireEvent).forEach(key => { - fireEvent[key] = async (...args) => { - suggestUpdateIfNecessary(args[1], key) - dtlFireEvent[key](...args) - await waitFor(() => {}) - } -}) - -fireEvent.touch = async elem => { - await fireEvent.focus(elem) - await fireEvent.blur(elem) -} - -// Small utility to provide a better experience when working with v-model. -// Related upstream issue: https://github.com/vuejs/vue-test-utils/issues/345#issuecomment-380588199 -// Examples: https://github.com/testing-library/vue-testing-library/blob/master/src/__tests__/form.js -fireEvent.update = (elem, value) => { - const tagName = elem.tagName - const type = elem.type - - switch (tagName) { - case 'OPTION': { - elem.selected = true - - const parentSelectElement = - elem.parentElement.tagName === 'OPTGROUP' - ? elem.parentElement.parentElement - : elem.parentElement - - return fireEvent.change(parentSelectElement) - } - - case 'INPUT': { - if (['checkbox', 'radio'].includes(type)) { - elem.checked = true - return fireEvent.change(elem) - } else { - elem.value = value - return fireEvent.input(elem) - } - } - - case 'TEXTAREA': { - elem.value = value - return fireEvent.input(elem) - } - - case 'SELECT': { - elem.value = value - return fireEvent.change(elem) - } - - default: - // do nothing - } - - return null -} - -// If we're running in a test runner that supports afterEach then we'll -// automatically run cleanup after each test. This ensures that tests run in -// isolation from each other. -// If you don't like this, set the VTL_SKIP_AUTO_CLEANUP variable to 'true'. -if (typeof afterEach === 'function' && !process.env.VTL_SKIP_AUTO_CLEANUP) { - afterEach(() => { - cleanup() - }) -} - -export * from '@testing-library/dom' -export {cleanup, render, fireEvent} From 0e01b23906cd55f86ba3f76ba6e0d95276123b22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Sun, 22 Nov 2020 11:01:34 +0100 Subject: [PATCH 03/14] Fix import statement --- src/__tests__/auto-cleanup-skip.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 }) From d333aa881d5755401cbb0984139a265be6817a04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Sun, 22 Nov 2020 11:03:13 +0100 Subject: [PATCH 04/14] Fix compiler options lib --- types/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/tsconfig.json b/types/tsconfig.json index 4c48bf28..d822978b 100644 --- a/types/tsconfig.json +++ b/types/tsconfig.json @@ -3,7 +3,7 @@ { "compilerOptions": { "module": "commonjs", - "lib": ["es6", "dom"], + "lib": ["ES2020", "DOM"], "noImplicitAny": true, "noImplicitThis": true, "strictNullChecks": true, From a025fa0eb87a1cded526bf594d551cb28a4678be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Sun, 22 Nov 2020 11:22:12 +0100 Subject: [PATCH 05/14] Extract temporary-defined types to its own file --- types/index.d.ts | 65 ++++------------------------------------------- types/vtu-next.ts | 39 ++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 60 deletions(-) create mode 100644 types/vtu-next.ts diff --git a/types/index.d.ts b/types/index.d.ts index 3b0b03a2..f7bdff93 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,75 +1,20 @@ // TypeScript Version: 4.1 /* eslint-disable @typescript-eslint/no-explicit-any */ -import { - VNode, - Component, - Directive, - Plugin, - AppConfig, - EmitsOptions, - ComponentOptions, -} from 'vue' +import {EmitsOptions} from 'vue' import {} 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' +// Here until https://github.com/vuejs/vue-test-utils-next/pull/252 +// is released +import {MountingOptions} from 'vtu-next' + // NOTE: fireEvent is overridden below export * from '@testing-library/dom' -/** vvvvvvvvv THIS SHOULD COME FROM VUE TEST UTILS vvvvvvvvvvvvv */ -/** vvvvvvvvv THIS SHOULD COME FROM VUE TEST UTILS vvvvvvvvvvvvv */ -/** vvvvvvvvv THIS SHOULD COME FROM VUE TEST UTILS vvvvvvvvvvvvv */ -/** vvvvvvvvv THIS SHOULD COME FROM VUE TEST UTILS vvvvvvvvvvvvv */ -/** vvvvvvvvv THIS SHOULD COME FROM VUE TEST UTILS vvvvvvvvvvvvv */ -/** vvvvvvvvv THIS SHOULD COME FROM VUE TEST UTILS vvvvvvvvvvvvv */ -/** vvvvvvvvv THIS SHOULD COME FROM VUE TEST UTILS vvvvvvvvvvvvv */ -/** vvvvvvvvv THIS SHOULD COME FROM VUE TEST UTILS vvvvvvvvvvvvv */ -/** vvvvvvvvv THIS SHOULD COME FROM VUE TEST UTILS vvvvvvvvvvvvv */ - -type GlobalMountOptions = { - plugins?: (Plugin | [Plugin, ...any[]])[] - config?: Partial> // isNativeTag is readonly, so we omit it - mixins?: ComponentOptions[] - mocks?: Record - provide?: Record - components?: Record - directives?: Record - stubs?: Record - renderStubDefaultSlot?: boolean -} - -type Slot = VNode | string | {render: Function} | Function | Component - -type SlotDictionary = { - [key: string]: Slot -} - -interface MountingOptions { - data?: () => {} extends Data ? any : Data extends object ? Partial : any - props?: Props - attrs?: Record - slots?: SlotDictionary & { - default?: Slot - } - global?: GlobalMountOptions - attachTo?: HTMLElement | string - shallow?: boolean -} - -/** ^^^^^^^^^^^^ THIS SHOULD COME FROM VUE TEST UTILS ^^^^^^^^^^^^ */ -/** ^^^^^^^^^^^^ THIS SHOULD COME FROM VUE TEST UTILS ^^^^^^^^^^^^ */ -/** ^^^^^^^^^^^^ THIS SHOULD COME FROM VUE TEST UTILS ^^^^^^^^^^^^ */ -/** ^^^^^^^^^^^^ THIS SHOULD COME FROM VUE TEST UTILS ^^^^^^^^^^^^ */ -/** ^^^^^^^^^^^^ THIS SHOULD COME FROM VUE TEST UTILS ^^^^^^^^^^^^ */ -/** ^^^^^^^^^^^^ THIS SHOULD COME FROM VUE TEST UTILS ^^^^^^^^^^^^ */ -/** ^^^^^^^^^^^^ THIS SHOULD COME FROM VUE TEST UTILS ^^^^^^^^^^^^ */ -/** ^^^^^^^^^^^^ THIS SHOULD COME FROM VUE TEST UTILS ^^^^^^^^^^^^ */ -/** ^^^^^^^^^^^^ THIS SHOULD COME FROM VUE TEST UTILS ^^^^^^^^^^^^ */ -/** ^^^^^^^^^^^^ THIS SHOULD COME FROM VUE TEST UTILS ^^^^^^^^^^^^ */ - type Debug = ( baseElement?: Element | DocumentFragment | Array, maxLength?: number, diff --git a/types/vtu-next.ts b/types/vtu-next.ts new file mode 100644 index 00000000..279877d8 --- /dev/null +++ b/types/vtu-next.ts @@ -0,0 +1,39 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + VNode, + Component, + Directive, + Plugin, + AppConfig, + ComponentOptions, +} from 'vue' + +type GlobalMountOptions = { + plugins?: (Plugin | [Plugin, ...any[]])[] + config?: Partial> // isNativeTag is readonly, so we omit it + mixins?: ComponentOptions[] + mocks?: Record + provide?: Record + components?: Record + directives?: Record + stubs?: Record + renderStubDefaultSlot?: boolean +} + +type Slot = VNode | string | {render: Function} | Function | Component + +type SlotDictionary = { + [key: string]: Slot +} + +export interface MountingOptions { + data?: () => {} extends Data ? any : Data extends object ? Partial : any + props?: Props + attrs?: Record + slots?: SlotDictionary & { + default?: Slot + } + global?: GlobalMountOptions + attachTo?: HTMLElement | string + shallow?: boolean +} From ff111657d9430db8a4c58d2d4481dfb9345e6e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Sun, 22 Nov 2020 12:10:33 +0100 Subject: [PATCH 06/14] Remove duplicate test --- src/__tests__/vue-i18n.js | 4 ++-- src/__tests__/vueI18n.js | 40 --------------------------------------- 2 files changed, 2 insertions(+), 42 deletions(-) delete mode 100644 src/__tests__/vueI18n.js diff --git a/src/__tests__/vue-i18n.js b/src/__tests__/vue-i18n.js index d833629f..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 '..' 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__/vueI18n.js b/src/__tests__/vueI18n.js deleted file mode 100644 index 470c5816..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 '..' -// 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() -// }) From 4c05acfb94e9589decf40559678c2e2d5f31b9af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Sun, 22 Nov 2020 12:13:34 +0100 Subject: [PATCH 07/14] Fix link --- README.md | 2 +- src/__tests__/components/Translations.vue | 10 +++++----- src/__tests__/components/VueI18n.vue | 21 --------------------- 3 files changed, 6 insertions(+), 27 deletions(-) delete mode 100644 src/__tests__/components/VueI18n.vue diff --git a/README.md b/README.md index ec6ff7df..dade3ebb 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,6 @@ instead of filing an issue on GitHub. [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/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 @@ From 8052e3b85de1764a723e2a15fd11bda3c833335e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Sun, 22 Nov 2020 19:47:02 +0100 Subject: [PATCH 08/14] Improve tests --- types/index.d.ts | 2 +- types/test.ts | 35 +++++++++++++---------------------- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/types/index.d.ts b/types/index.d.ts index f7bdff93..8a5bbc50 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,4 +1,4 @@ -// TypeScript Version: 4.1 +// Minimum TypeScript Version: 4.0 /* eslint-disable @typescript-eslint/no-explicit-any */ import {EmitsOptions} from 'vue' diff --git a/types/test.ts b/types/test.ts index 30aac5ab..b6941c97 100644 --- a/types/test.ts +++ b/types/test.ts @@ -1,4 +1,5 @@ -import {defineComponent, h} from 'vue' +/* eslint-disable @typescript-eslint/no-unused-expressions */ +import {defineComponent} from 'vue' import {render, fireEvent, screen, waitFor} from '@testing-library/vue' declare const elem: Element @@ -25,16 +26,18 @@ export async function testRender() { await page.findAllByText('bar') // helpers - const {container, unmount, debug, rerender} = page + const {container, baseElement, unmount, debug, rerender} = page - debug(container) + await rerender({a: 1}) // $ExpectType Promise - await rerender({a: 1}) - - debug(elem) // $ExpectType void + 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() { @@ -46,31 +49,19 @@ export function testRenderOptions() { export async function testFireEvent() { const {container} = render({template: 'button'}) - await fireEvent.click(container) -} - -export function testDebug() { - const {debug, getAllByTestId} = render({ - render() { - return h('div', [ - h('h1', {attrs: {'data-testid': 'testid'}}, 'hello world'), - h('h2', {attrs: {'data-testid': 'testid'}}, 'hello world'), - ]) - }, - }) - - debug(getAllByTestId('testid')) + await fireEvent.click(container) // $ExpectType Promise + await fireEvent.touch(elem) // $ExpectType Promise } export async function testScreen() { render({template: 'button'}) - await screen.findByRole('button') + await screen.findByRole('button') // $ExpectType Promise } export async function testWaitFor() { const {container} = render({template: 'button'}) - await fireEvent.click(container) + await fireEvent.update(container) // $ExpectType Promise await waitFor(() => {}) } From 65bdf07e7b995b9b843204f08546c0346ae07f04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Sun, 22 Nov 2020 19:48:19 +0100 Subject: [PATCH 09/14] Remove unnecessary chars --- package.json | 2 +- types/tslint.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5a3682af..b018b26a 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "test": "kcd-scripts test", "test:update": "npm test -- --updateSnapshot --coverage", "validate": "kcd-scripts validate", - "typecheck": "dtslint ./types/", + "typecheck": "dtslint types", "setup": "npm install && npm run validate -s" }, "engines": { diff --git a/types/tslint.json b/types/tslint.json index c7b428f3..70c4494b 100644 --- a/types/tslint.json +++ b/types/tslint.json @@ -1,5 +1,5 @@ { - "extends": ["dtslint/dtslint.json"], + "extends": "dtslint/dtslint.json", "rules": { "semicolon": false, "whitespace": false From 99a42c13ceed2dadb641a4bd13a4f5ac1d6a243f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Sat, 28 Nov 2020 14:53:38 +0100 Subject: [PATCH 10/14] Add support for file inputs --- src/__tests__/fire-event.js | 17 +++++++++++++++++ src/index.js | 2 ++ 2 files changed, 19 insertions(+) diff --git a/src/__tests__/fire-event.js b/src/__tests__/fire-event.js index ef6e11dc..19cef6b2 100644 --- a/src/__tests__/fire-event.js +++ b/src/__tests__/fire-event.js @@ -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/index.js b/src/index.js index b4e2eaf0..4fbdf6e0 100644 --- a/src/index.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) From b107a9cf08555aa736d918e945635a9022ed4ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Sat, 28 Nov 2020 15:10:11 +0100 Subject: [PATCH 11/14] Update deps --- package.json | 16 +++++++-------- src/__tests__/functional.js | 2 +- src/__tests__/user-event.js | 2 +- types/index.d.ts | 6 +----- types/vtu-next.ts | 39 ------------------------------------- 5 files changed, 11 insertions(+), 54 deletions(-) delete mode 100644 types/vtu-next.ts diff --git a/package.json b/package.json index b018b26a..8f83b19f 100644 --- a/package.json +++ b/package.json @@ -48,8 +48,8 @@ "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.11", "lodash.merge": "^4.6.2" }, "devDependencies": { @@ -57,7 +57,7 @@ "@testing-library/jest-dom": "^5.11.5", "@testing-library/user-event": "^12.2.2", "@types/estree": "0.0.45", - "@vue/compiler-sfc": "^3.0.2", + "@vue/compiler-sfc": "^3.0.3", "apollo-boost": "^0.4.9", "apollo-cache-inmemory": "^1.6.6", "apollo-client": "^2.6.10", @@ -68,18 +68,18 @@ "graphql-tag": "^2.11.0", "isomorphic-unfetch": "^3.1.0", "jest-serializer-vue": "^2.0.2", - "kcd-scripts": "^7.0.3", + "kcd-scripts": "^7.3.0", "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.3", "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" + "vuetify": "^2.3.19", + "vuex": "^4.0.0-rc.2" }, "peerDependencies": { "vue": ">= 3", diff --git a/src/__tests__/functional.js b/src/__tests__/functional.js index 290528e7..323a6ff2 100644 --- a/src/__tests__/functional.js +++ b/src/__tests__/functional.js @@ -1,6 +1,6 @@ +import {h} from 'vue' import {render} from '..' import '@testing-library/jest-dom' -import {h} from 'vue' // 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__/user-event.js b/src/__tests__/user-event.js index 4bf7827d..220a0f23 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 '..' import userEvent from '@testing-library/user-event' +import {render} from '..' import Form from './components/Form' import Select from './components/Select' diff --git a/types/index.d.ts b/types/index.d.ts index 8a5bbc50..5dec7fa4 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -2,16 +2,12 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import {EmitsOptions} from 'vue' -import {} from '@vue/test-utils' +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' -// Here until https://github.com/vuejs/vue-test-utils-next/pull/252 -// is released -import {MountingOptions} from 'vtu-next' - // NOTE: fireEvent is overridden below export * from '@testing-library/dom' diff --git a/types/vtu-next.ts b/types/vtu-next.ts deleted file mode 100644 index 279877d8..00000000 --- a/types/vtu-next.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { - VNode, - Component, - Directive, - Plugin, - AppConfig, - ComponentOptions, -} from 'vue' - -type GlobalMountOptions = { - plugins?: (Plugin | [Plugin, ...any[]])[] - config?: Partial> // isNativeTag is readonly, so we omit it - mixins?: ComponentOptions[] - mocks?: Record - provide?: Record - components?: Record - directives?: Record - stubs?: Record - renderStubDefaultSlot?: boolean -} - -type Slot = VNode | string | {render: Function} | Function | Component - -type SlotDictionary = { - [key: string]: Slot -} - -export interface MountingOptions { - data?: () => {} extends Data ? any : Data extends object ? Partial : any - props?: Props - attrs?: Record - slots?: SlotDictionary & { - default?: Slot - } - global?: GlobalMountOptions - attachTo?: HTMLElement | string - shallow?: boolean -} From 9390e80688cda2a8fbb90304180b196e71b700e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Thu, 3 Dec 2020 08:04:56 +0100 Subject: [PATCH 12/14] Fix rerender typing test --- types/test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/types/test.ts b/types/test.ts index b6941c97..b11b466b 100644 --- a/types/test.ts +++ b/types/test.ts @@ -28,7 +28,8 @@ export async function testRender() { // helpers const {container, baseElement, unmount, debug, rerender} = page - await rerender({a: 1}) // $ExpectType Promise + // eslint-disable-next-line @typescript-eslint/no-floating-promises + rerender({a: 1}) // $ExpectType Promise debug() // $ExpectType void debug(container) // $ExpectType void From 75d4084caece96ded44c24f59d9ce23010c9fee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Thu, 3 Dec 2020 08:05:35 +0100 Subject: [PATCH 13/14] Remove typecheck step until we figure out how to overcome ts-ignore in node_modules. lol --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 8f83b19f..b37b6c02 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "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": { From f629d168e80ca00e9ce063a03acb65c3e234384d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Thu, 3 Dec 2020 08:10:07 +0100 Subject: [PATCH 14/14] Update deps --- package.json | 14 ++++++------- src/__tests__/select.js | 8 ++++---- src/__tests__/user-event.js | 40 ++++++++++++++++++------------------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index b37b6c02..8029eee1 100644 --- a/package.json +++ b/package.json @@ -48,15 +48,15 @@ "dependencies": { "@babel/runtime": "^7.12.1", "@testing-library/dom": "^7.28.1", - "@vue/test-utils": "^2.0.0-beta.11", + "@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", + "@testing-library/user-event": "^12.4.0", "@types/estree": "0.0.45", - "@vue/compiler-sfc": "^3.0.3", + "@vue/compiler-sfc": "^3.0.4", "apollo-boost": "^0.4.9", "apollo-cache-inmemory": "^1.6.6", "apollo-client": "^2.6.10", @@ -67,16 +67,16 @@ "graphql-tag": "^2.11.0", "isomorphic-unfetch": "^3.1.0", "jest-serializer-vue": "^2.0.2", - "kcd-scripts": "^7.3.0", + "kcd-scripts": "^7.5.1", "msw": "^0.21.3", "portal-vue": "^2.1.7", "typescript": "^4.1.2", "vee-validate": "^4.0.2", - "vue": "^3.0.3", + "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", + "vue-jest": "^5.0.0-alpha.7", + "vue-router": "^4.0.0-rc.6", "vuetify": "^2.3.19", "vuex": "^4.0.0-rc.2" }, diff --git a/src/__tests__/select.js b/src/__tests__/select.js index 876c6ca9..c8e79750 100644 --- a/src/__tests__/select.js +++ b/src/__tests__/select.js @@ -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__/user-event.js b/src/__tests__/user-event.js index 220a0f23..4db8c3e6 100644 --- a/src/__tests__/user-event.js +++ b/src/__tests__/user-event.js @@ -1,6 +1,6 @@ import '@testing-library/jest-dom' import userEvent from '@testing-library/user-event' -import {render} from '..' +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') })