From 5cd8d593f86bdb804f291f8a51ef462eb6e7e88e Mon Sep 17 00:00:00 2001 From: Jason Rial Date: Thu, 10 Sep 2020 03:44:58 -0500 Subject: [PATCH 01/19] Update using-with-vuex.md to include `namespaced:true` in using modules section (#1636) * Update using-with-vuex.md * chore: lint Co-authored-by: Lachlan --- docs/guides/using-with-vuex.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/guides/using-with-vuex.md b/docs/guides/using-with-vuex.md index d33a94f45..039a998f8 100644 --- a/docs/guides/using-with-vuex.md +++ b/docs/guides/using-with-vuex.md @@ -233,7 +233,8 @@ describe('MyComponent.vue', () => { myModule: { state, actions, - getters: myModule.getters + getters: myModule.getters, + namespaced: true } } }) From e0cbb16e962b58fc08758264239a7ff2143c0a5a Mon Sep 17 00:00:00 2001 From: Th3Un1q3 <19877114+Th3Un1q3@users.noreply.github.com> Date: Sat, 12 Sep 2020 11:24:26 +0300 Subject: [PATCH 02/19] Update find to findComponent in documentation example for mount options. (#1683) * Update find -> find components in options doc It was older name of the method used for examples. * Option doc update find -> findComponent in example zn version --- docs/api/options.md | 2 +- docs/zh/api/options.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/options.md b/docs/api/options.md index dddf6366e..fcd76acd7 100644 --- a/docs/api/options.md +++ b/docs/api/options.md @@ -206,7 +206,7 @@ const WrapperComp = { ComponentUnderTest } } -const wrapper = mount(WrapperComp).find(ComponentUnderTest) +const wrapper = mount(WrapperComp).findComponent(ComponentUnderTest) ``` ## stubs diff --git a/docs/zh/api/options.md b/docs/zh/api/options.md index 65df63f6a..3613caa42 100644 --- a/docs/zh/api/options.md +++ b/docs/zh/api/options.md @@ -204,7 +204,7 @@ const WrapperComp = { ComponentUnderTest } } -const wrapper = mount(WrapperComp).find(ComponentUnderTest) +const wrapper = mount(WrapperComp).findComponent(ComponentUnderTest) ``` ## stubs From a696325af9173df258ed6e0ed833f547d8ff9436 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 21 Sep 2020 07:48:44 +0200 Subject: [PATCH 03/19] fix(test-utils/wrapper): clarify deprecation note for wrong "get" usage (#1688) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If `wrapper.get` was called with a vue component instance, a misleading deprecation note was shown to the user. This fix includes the `get` method into the deprecation message fix #1687 Co-authored-by: Valentin Palkovič --- packages/test-utils/src/wrapper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/test-utils/src/wrapper.js b/packages/test-utils/src/wrapper.js index 625ce686f..25e4867fe 100644 --- a/packages/test-utils/src/wrapper.js +++ b/packages/test-utils/src/wrapper.js @@ -224,7 +224,7 @@ export default class Wrapper implements BaseWrapper { const selector = getSelector(rawSelector, 'find') if (selector.type !== DOM_SELECTOR) { warnDeprecated( - 'finding components with `find`', + 'finding components with `find` or `get`', 'Use `findComponent` instead' ) } From 312fde4f7db1ccc8f94042570324e512d58c5faf Mon Sep 17 00:00:00 2001 From: Jessica Sachs Date: Wed, 23 Sep 2020 15:24:25 -0400 Subject: [PATCH 04/19] docs: nesting VTU docs under /v2/ --- netlify.toml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 netlify.toml diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 000000000..a6ea095e3 --- /dev/null +++ b/netlify.toml @@ -0,0 +1,6 @@ +[[redirects]] + from = "/v2/*" + to = "https://vue-test-utils-next-docs.netlify.app/:splat" + status = 200 + force = true # COMMENT: ensure that we always redirect + headers = {X-From = "Netlify"} From c4c940d291b38459827870f1b66a5491f938d25c Mon Sep 17 00:00:00 2001 From: Jessica Sachs Date: Wed, 23 Sep 2020 15:29:43 -0400 Subject: [PATCH 05/19] docs: redirects update --- netlify.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netlify.toml b/netlify.toml index a6ea095e3..b0d1ca3f5 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,6 +1,6 @@ [[redirects]] from = "/v2/*" - to = "https://vue-test-utils-next-docs.netlify.app/:splat" + to = "https://vue-test-utils-next-docs.netlify.app/v2/:splat" status = 200 force = true # COMMENT: ensure that we always redirect headers = {X-From = "Netlify"} From bb949a1c83d5f2109a1f1f610f6350cf410e3fa0 Mon Sep 17 00:00:00 2001 From: jeremy-cassou Date: Fri, 25 Sep 2020 11:23:33 +0200 Subject: [PATCH 06/19] fix: support v-text on child functional components with shallowMount (#1697) a child functional component must display content of v-text directive when it is mounted with shallowMount fix #1693 Co-authored-by: Jeremy Cassou --- packages/create-instance/create-component-stubs.js | 3 +++ .../resources/components/component-with-functional-child.vue | 4 +++- test/resources/components/functional-component.vue | 4 +++- test/specs/shallow-mount.spec.js | 5 +++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/create-instance/create-component-stubs.js b/packages/create-instance/create-component-stubs.js index 2a6f81941..4fc6cd8d8 100644 --- a/packages/create-instance/create-component-stubs.js +++ b/packages/create-instance/create-component-stubs.js @@ -135,6 +135,9 @@ export function createStubFromComponent( tagName, { ref: componentOptions.functional ? context.data.ref : undefined, + domProps: componentOptions.functional + ? context.data.domProps + : undefined, attrs: componentOptions.functional ? { ...context.props, diff --git a/test/resources/components/component-with-functional-child.vue b/test/resources/components/component-with-functional-child.vue index 5d18f7272..16d638a02 100644 --- a/test/resources/components/component-with-functional-child.vue +++ b/test/resources/components/component-with-functional-child.vue @@ -1,6 +1,7 @@ @@ -15,7 +16,8 @@ export default { data() { return { a: 1, - b: 1 + b: 1, + something: 'value' } } } diff --git a/test/resources/components/functional-component.vue b/test/resources/components/functional-component.vue index 06a8d69bb..f9cc14c83 100644 --- a/test/resources/components/functional-component.vue +++ b/test/resources/components/functional-component.vue @@ -1,3 +1,5 @@ diff --git a/test/specs/shallow-mount.spec.js b/test/specs/shallow-mount.spec.js index 5251873f0..4d668a39d 100644 --- a/test/specs/shallow-mount.spec.js +++ b/test/specs/shallow-mount.spec.js @@ -41,6 +41,11 @@ describeRunIf(process.env.TEST_ENV !== 'node', 'shallowMount', () => { ) }) + it('renders v-text content of functional child', () => { + const wrapper = shallowMount(ComponentWithFunctionalChild) + expect(wrapper.find('functional-component-stub').text()).toBe('value') + }) + it('returns new VueWrapper of Vue localVue if no options are passed', () => { const compiled = compileToFunctions('
') const wrapper = shallowMount(compiled) From 7740e90ba6a92474f2706f98221a91ca19bb2415 Mon Sep 17 00:00:00 2001 From: Illya Klymov Date: Thu, 1 Oct 2020 10:50:31 +0300 Subject: [PATCH 07/19] feat(config): introduce deprecation warning handler, fix #1672 (#1700) Allow passing custom handler for deprecation warnings allowing fine-grained control how these are reported to a user --- docs/api/config.md | 23 +++++++++++++++++++++++ packages/shared/util.js | 6 +++++- packages/test-utils/types/index.d.ts | 1 + test/specs/config.spec.js | 12 ++++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/docs/api/config.md b/docs/api/config.md index 5403273e2..a737b80c5 100644 --- a/docs/api/config.md +++ b/docs/api/config.md @@ -19,6 +19,29 @@ import { config } from '@vue/test-utils' config.showDeprecationWarnings = false ``` +### `deprecationWarningHandler` + +- type: `Function` + +Allows fine-grained control on deprecation warnings. When `showDeprecationWarnings` is set to `true`, all deprecation warnings will be passed to this handler with method name as first argument and original message as second. + +::: tip +This could be useful to log deprecation messages to separate location or to help in gradual upgrade of codebase to latest version of test utils by ignoring certain deprecated functions warnings +::: + +Example: + +```js +import { config } from '@vue/test-utils' + +config.showDeprecationWarnings = true +config.deprecationWarningHandler = (method, message) => { + if (method === 'emittedByOrder') return + + console.error(msg) +} +``` + ### `stubs` - type: `{ [name: string]: Component | boolean | string }` diff --git a/packages/shared/util.js b/packages/shared/util.js index 41ccc2cb7..679315f63 100644 --- a/packages/shared/util.js +++ b/packages/shared/util.js @@ -105,5 +105,9 @@ export function warnDeprecated(method: string, fallback: string = '') { if (!config.showDeprecationWarnings) return let msg = `${method} is deprecated and will be removed in the next major version.` if (fallback) msg += ` ${fallback}.` - warn(msg) + if (config.deprecationWarningHandler) { + config.deprecationWarningHandler(method, msg) + } else { + warn(msg) + } } diff --git a/packages/test-utils/types/index.d.ts b/packages/test-utils/types/index.d.ts index f0fcedbb2..b5c91e9b1 100644 --- a/packages/test-utils/types/index.d.ts +++ b/packages/test-utils/types/index.d.ts @@ -166,6 +166,7 @@ interface VueTestUtilsConfigOptions { provide?: Record, silent?: Boolean, showDeprecationWarnings?: boolean + deprecationWarningHandler?: Function } export declare function createLocalVue (): typeof Vue diff --git a/test/specs/config.spec.js b/test/specs/config.spec.js index 4202d4150..e77d31f6d 100644 --- a/test/specs/config.spec.js +++ b/test/specs/config.spec.js @@ -19,6 +19,7 @@ describeWithShallowAndMount('config', mountingMethod => { config.stubs = configStubsSave config.silent = configSilentSave config.methods = {} + config.deprecationWarningHandler = null console.error = consoleErrorSave }) @@ -103,6 +104,17 @@ describeWithShallowAndMount('config', mountingMethod => { expect(wrapper.find('[data-testid="expanded"]').exists()).toEqual(false) }) + it('invokes custom deprecation warning handler if specified in config', () => { + config.showDeprecationWarnings = true + config.deprecationWarningHandler = jest.fn() + + const TestComponent = { template: `
Test
` } + mountingMethod(TestComponent, { attachToDocument: true }) + + expect(config.deprecationWarningHandler).toHaveBeenCalledTimes(1) + expect(console.error).not.toHaveBeenCalled() + }) + it('allows control deprecation warnings visibility for name method', () => { config.showDeprecationWarnings = true const Component = { From a8219083602b9e4d144777147938648ef99d1cd8 Mon Sep 17 00:00:00 2001 From: Illya Klymov Date: Thu, 1 Oct 2020 10:51:36 +0300 Subject: [PATCH 08/19] feat: treat document.body in attachTo in special way (#1699) When using attachTo with document.body as a target do not replace original content of body but append a new div instead See #1578 for details and discussion --- docs/api/options.md | 4 ++++ packages/test-utils/src/mount.js | 4 +++- test/specs/mounting-options/attachTo.spec.js | 13 +++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/api/options.md b/docs/api/options.md index fcd76acd7..1bbdd2ae0 100644 --- a/docs/api/options.md +++ b/docs/api/options.md @@ -302,6 +302,10 @@ HTMLElement, to which your component will be fully mounted in the document. When attaching to the DOM, you should call `wrapper.destroy()` at the end of your test to remove the rendered elements from the document and destroy the component instance. +::: tip +When using `attachTo: document.body` new `div` instead of replacing entire body new `
` will be appended. This is designed to mimic Vue3 behavior and simplify future migration. See [this comment](https://github.com/vuejs/vue-test-utils/issues/1578#issuecomment-674652747) for details +::: + ```js const div = document.createElement('div') div.id = 'root' diff --git a/packages/test-utils/src/mount.js b/packages/test-utils/src/mount.js index 4d3b8a6ce..4398e51b8 100644 --- a/packages/test-utils/src/mount.js +++ b/packages/test-utils/src/mount.js @@ -32,7 +32,9 @@ export default function mount(component, options = {}) { const parentVm = createInstance(component, mergedOptions, _Vue) const el = - options.attachTo || (options.attachToDocument ? createElement() : undefined) + options.attachToDocument || options.attachTo instanceof HTMLBodyElement + ? createElement() + : options.attachTo const vm = parentVm.$mount(el) component._Ctor = {} diff --git a/test/specs/mounting-options/attachTo.spec.js b/test/specs/mounting-options/attachTo.spec.js index 65852724c..7df27d7f7 100644 --- a/test/specs/mounting-options/attachTo.spec.js +++ b/test/specs/mounting-options/attachTo.spec.js @@ -32,6 +32,19 @@ describeWithShallowAndMount('options.attachTo', mountingMethod => { wrapper.destroy() expect(document.getElementById('attach-to')).toBeNull() }) + it('appends new node when attached to document.body', () => { + const unrelatedDiv = document.createElement('div') + unrelatedDiv.id = 'unrelated' + document.body.appendChild(unrelatedDiv) + const wrapper = mountingMethod(TestComponent, { + attachTo: document.body + }) + expect(document.body.contains(unrelatedDiv)).toBe(true) + expect(wrapper.vm.$el.parentNode).toBe(document.body) + expect(wrapper.options.attachedToDocument).toEqual(true) + wrapper.destroy() + unrelatedDiv.remove() + }) it('attaches to a provided CSS selector string', () => { const div = document.createElement('div') div.id = 'root' From 002eb3e06a1bb9e395a5c4bab560cd955aa74acc Mon Sep 17 00:00:00 2001 From: Blake Newman Date: Mon, 5 Oct 2020 13:47:37 +0100 Subject: [PATCH 09/19] docs: update vue-router documentation (#1701) Update documentation to explain differences between using `localVue` and `mocks` and provide examples of the use case for each. A common gotcha with integration testing (which don't stub children) is when using the `mocks` approach the router instance is not available on child components. --- docs/guides/using-with-vue-router.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/guides/using-with-vue-router.md b/docs/guides/using-with-vue-router.md index 36ecde098..9851c99dc 100644 --- a/docs/guides/using-with-vue-router.md +++ b/docs/guides/using-with-vue-router.md @@ -41,17 +41,20 @@ shallowMount(Component, { ### Installing Vue Router with localVue ```js -import { shallowMount, createLocalVue } from '@vue/test-utils' +import { mount, createLocalVue } from '@vue/test-utils' import VueRouter from 'vue-router' const localVue = createLocalVue() localVue.use(VueRouter) -shallowMount(Component, { - localVue +mount(Component, { + localVue, + router }) ``` +The router instance is available to all children components, this is useful for integration level testing. + ### Mocking `$route` and `$router` Sometimes you want to test that a component does something with parameters from the `$route` and `$router` objects. To do that, you can pass custom mocks to the Vue instance. @@ -72,6 +75,8 @@ const wrapper = shallowMount(Component, { wrapper.vm.$route.path // /some/path ``` +> **Note:** the mocked `$route` and `$router` values are not available to children components, either stub this components or use the `localVue` method. + ### Common gotchas Installing Vue Router adds `$route` and `$router` as read-only properties on Vue prototype. From 993b29398a4e19373dd47f0767ff294349d9a64e Mon Sep 17 00:00:00 2001 From: Haroen Viaene Date: Wed, 7 Oct 2020 13:21:17 +0200 Subject: [PATCH 10/19] fix(setData): allow empty objects to be set fix #1704 (#1705) * fix(setData): allow empty objects to be set fixes #1704 This is a tentative fix to show one possible solution, I can clean it up if you think it makes sense * fix formatting * test --- .../test-utils/src/recursively-set-data.js | 6 ++++- test/specs/wrapper/setData.spec.js | 25 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/test-utils/src/recursively-set-data.js b/packages/test-utils/src/recursively-set-data.js index ea03cb62b..99cf741e2 100644 --- a/packages/test-utils/src/recursively-set-data.js +++ b/packages/test-utils/src/recursively-set-data.js @@ -5,7 +5,11 @@ export function recursivelySetData(vm, target, data) { const val = data[key] const targetVal = target[key] - if (isPlainObject(val) && isPlainObject(targetVal)) { + if ( + isPlainObject(val) && + isPlainObject(targetVal) && + Object.keys(val).length > 0 + ) { recursivelySetData(vm, targetVal, val) } else { vm.$set(target, key, val) diff --git a/test/specs/wrapper/setData.spec.js b/test/specs/wrapper/setData.spec.js index dfedba3ea..a7ac5681a 100644 --- a/test/specs/wrapper/setData.spec.js +++ b/test/specs/wrapper/setData.spec.js @@ -320,4 +320,29 @@ describeWithShallowAndMount('setData', mountingMethod => { await wrapper.setData({ selectedDate: testDate }) expect(wrapper.vm.selectedDate).toEqual(testDate) }) + + it('allows empty objects to be set', () => { + const TestComponent = { + data() { + return { + someKey: { someValue: true } + } + }, + render(h) { + return h('span') + } + } + + const wrapper = mountingMethod(TestComponent) + + expect(wrapper.vm.$data).toEqual({ someKey: { someValue: true } }) + + wrapper.setData({ someKey: {} }) + + expect(wrapper.vm.$data).toEqual({ someKey: {} }) + + wrapper.setData({ someKey: { someValue: false } }) + + expect(wrapper.vm.$data).toEqual({ someKey: { someValue: false } }) + }) }) From fcfc63ac74b4eefc6cbe8e1701e978779a8b7106 Mon Sep 17 00:00:00 2001 From: Nikolay Akinshin <49105276+karrambol@users.noreply.github.com> Date: Wed, 7 Oct 2020 16:21:42 +0500 Subject: [PATCH 11/19] docs(ru): change mocha-webpack to mochapack (#1708) based on en docs changes --- ...ting-single-file-components-with-mocha-webpack.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/ru/installation/testing-single-file-components-with-mocha-webpack.md b/docs/ru/installation/testing-single-file-components-with-mocha-webpack.md index 681406493..42be8b1c2 100644 --- a/docs/ru/installation/testing-single-file-components-with-mocha-webpack.md +++ b/docs/ru/installation/testing-single-file-components-with-mocha-webpack.md @@ -4,7 +4,7 @@ Другая стратегия тестирования однофайловых компонентов заключается в компиляции всех наших тестов с помощью webpack, а затем программой для запуска тестов. Преимущество такого подхода заключается в том, что он даёт нам полную поддержку всех функций webpack и `vue-loader`, поэтому нам не нужно идти на компромиссы в нашем исходном коде. -Технически, вы можете использовать любую программу для запуска тестов, которая вам нравится, и вручную соединять вещи, но мы нашли [`mocha-webpack`](https://github.com/zinserjan/mocha-webpack) как очень удобный способ для реализации этой задачи. +Технически, вы можете использовать любую программу для запуска тестов, которая вам нравится, и вручную соединять вещи, но мы нашли [`mochapack`](https://github.com/sysgears/mochapack) как очень удобный способ для реализации этой задачи. ### Настройка `mocha-webpack` @@ -13,7 +13,7 @@ Первое, что нужно сделать, это установить тестовые зависимости: ```bash -npm install --save-dev @vue/test-utils mocha mocha-webpack +npm install --save-dev @vue/test-utils mocha mochapack ``` Затем мы должны указать скрипт test в нашем `package.json`. @@ -22,7 +22,7 @@ npm install --save-dev @vue/test-utils mocha mocha-webpack // package.json { "scripts": { - "test": "mocha-webpack --webpack-config webpack.config.js --require test/setup.js test/**/*.spec.js" + "test": "mochapack --webpack-config webpack.config.js --require test/setup.js test/**/*.spec.js" } } ``` @@ -53,7 +53,7 @@ module.exports = { #### Source Maps -Source maps должны быть встроены для использования в `mocha-webpack`. Рекомендуемая конфигурация: +Source maps должны быть встроены для использования в `mochapack`. Рекомендуемая конфигурация: ```js module.exports = { @@ -172,13 +172,13 @@ npm run test ### Покрытие кода (Coverage) -Для настройки покрытия кода в mocha-webpack, следуйте [инструкции по настройке покрытия кода mocha-webpack](https://github.com/zinserjan/mocha-webpack/blob/master/docs/guides/code-coverage.md). +Для настройки покрытия кода в mochapack, следуйте [инструкции по настройке покрытия кода mochapack](https://github.com/sysgears/mochapack/blob/master/docs/guides/code-coverage.md). ### Ресурсы - [Пример проекта для этой настройки](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-unit-mocha) - [Mocha](https://mochajs.org/) -- [mocha-webpack](http://zinserjan.github.io/mocha-webpack/) +- [mochapack](https://github.com/sysgears/mochapack) - [Chai](http://chaijs.com/) - [Sinon](http://sinonjs.org/) - [jest/expect](http://facebook.github.io/jest/docs/en/expect.html#content) From 16790b9e08e097e55599dff7293cc27d5edae358 Mon Sep 17 00:00:00 2001 From: Illya Klymov Date: Wed, 7 Oct 2020 14:22:50 +0300 Subject: [PATCH 12/19] feat: warn when operating on destroyed Vue component (#1706) --- packages/test-utils/src/wrapper.js | 56 +++++++++++++++++++++++++++++ test/specs/wrapper/destroy.spec.js | 58 ++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/packages/test-utils/src/wrapper.js b/packages/test-utils/src/wrapper.js index 25e4867fe..c56a899c5 100644 --- a/packages/test-utils/src/wrapper.js +++ b/packages/test-utils/src/wrapper.js @@ -17,6 +17,7 @@ import { getCheckedEvent, isPhantomJS, nextTick, + warn, warnDeprecated } from 'shared/util' import { isElementVisible } from 'shared/is-visible' @@ -82,7 +83,18 @@ export default class Wrapper implements BaseWrapper { } } + /** + * Prints warning if component is destroyed + */ + __warnIfDestroyed() { + if (!this.exists()) { + warn('Operations on destroyed component are discouraged') + } + } + at(): void { + this.__warnIfDestroyed() + throwError('at() must be called on a WrapperArray') } @@ -90,6 +102,8 @@ export default class Wrapper implements BaseWrapper { * Returns an Object containing all the attribute/value pairs on the element. */ attributes(key?: string): { [name: string]: string } | string { + this.__warnIfDestroyed() + const attributes = this.element.attributes const attributeMap = {} for (let i = 0; i < attributes.length; i++) { @@ -104,6 +118,8 @@ export default class Wrapper implements BaseWrapper { * Returns an Array containing all the classes on the element */ classes(className?: string): Array | boolean { + this.__warnIfDestroyed() + const classAttribute = this.element.getAttribute('class') let classes = classAttribute ? classAttribute.split(' ') : [] // Handle converting cssmodules identifiers back to the original class name @@ -134,6 +150,9 @@ export default class Wrapper implements BaseWrapper { 'contains', 'Use `wrapper.find`, `wrapper.findComponent` or `wrapper.get` instead' ) + + this.__warnIfDestroyed() + const selector = getSelector(rawSelector, 'contains') const nodes = find(this.rootNode, this.vm, selector) return nodes.length > 0 @@ -209,6 +228,8 @@ export default class Wrapper implements BaseWrapper { * matches the provided selector. */ get(rawSelector: Selector): Wrapper { + this.__warnIfDestroyed() + const found = this.find(rawSelector) if (found instanceof ErrorWrapper) { throw new Error(`Unable to find ${rawSelector} within: ${this.html()}`) @@ -221,6 +242,8 @@ export default class Wrapper implements BaseWrapper { * matches the provided selector. */ find(rawSelector: Selector): Wrapper | ErrorWrapper { + this.__warnIfDestroyed() + const selector = getSelector(rawSelector, 'find') if (selector.type !== DOM_SELECTOR) { warnDeprecated( @@ -237,6 +260,8 @@ export default class Wrapper implements BaseWrapper { * matches the provided selector. */ findComponent(rawSelector: Selector): Wrapper | ErrorWrapper { + this.__warnIfDestroyed() + const selector = getSelector(rawSelector, 'findComponent') if (!this.vm && !this.isFunctionalComponent) { throwError( @@ -270,6 +295,8 @@ export default class Wrapper implements BaseWrapper { * the provided selector. */ findAll(rawSelector: Selector): WrapperArray { + this.__warnIfDestroyed() + const selector = getSelector(rawSelector, 'findAll') if (selector.type !== DOM_SELECTOR) { warnDeprecated( @@ -285,6 +312,8 @@ export default class Wrapper implements BaseWrapper { * the provided selector. */ findAllComponents(rawSelector: Selector): WrapperArray { + this.__warnIfDestroyed() + const selector = getSelector(rawSelector, 'findAll') if (!this.vm) { throwError( @@ -318,6 +347,8 @@ export default class Wrapper implements BaseWrapper { * Returns HTML of element as a string */ html(): string { + this.__warnIfDestroyed() + return pretty(this.element.outerHTML) } @@ -325,6 +356,8 @@ export default class Wrapper implements BaseWrapper { * Checks if node matches selector or component definition */ is(rawSelector: Selector): boolean { + this.__warnIfDestroyed() + const selector = getSelector(rawSelector, 'is') if (selector.type === DOM_SELECTOR) { @@ -351,6 +384,8 @@ export default class Wrapper implements BaseWrapper { 'Consider a custom matcher such as those provided in jest-dom: https://github.com/testing-library/jest-dom#tobeempty. ' + 'When using with findComponent, access the DOM element with findComponent(Comp).element' ) + this.__warnIfDestroyed() + if (!this.vnode) { return this.element.innerHTML === '' } @@ -375,6 +410,8 @@ export default class Wrapper implements BaseWrapper { * Checks if node is visible */ isVisible(): boolean { + this.__warnIfDestroyed() + return isElementVisible(this.element) } @@ -384,6 +421,8 @@ export default class Wrapper implements BaseWrapper { */ isVueInstance(): boolean { warnDeprecated(`isVueInstance`) + this.__warnIfDestroyed() + return !!this.vm } @@ -393,6 +432,7 @@ export default class Wrapper implements BaseWrapper { */ name(): string { warnDeprecated(`name`) + this.__warnIfDestroyed() if (this.vm) { return ( @@ -416,6 +456,7 @@ export default class Wrapper implements BaseWrapper { */ overview(): void { warnDeprecated(`overview`) + this.__warnIfDestroyed() if (!this.vm) { throwError(`wrapper.overview() can only be called on a Vue instance`) @@ -495,6 +536,7 @@ export default class Wrapper implements BaseWrapper { if (!this.vm) { throwError('wrapper.props() must be called on a Vue instance') } + this.__warnIfDestroyed() const props = {} const keys = this.vm && this.vm.$options._propKeys @@ -519,6 +561,8 @@ export default class Wrapper implements BaseWrapper { * @deprecated */ setChecked(checked: boolean = true): Promise<*> { + this.__warnIfDestroyed() + if (typeof checked !== 'boolean') { throwError('wrapper.setChecked() must be passed a boolean') } @@ -568,6 +612,8 @@ export default class Wrapper implements BaseWrapper { * @deprecated */ setSelected(): Promise { + this.__warnIfDestroyed() + const tagName = this.element.tagName if (tagName === 'SELECT') { @@ -613,6 +659,8 @@ export default class Wrapper implements BaseWrapper { throwError(`wrapper.setData() can only be called on a Vue instance`) } + this.__warnIfDestroyed() + recursivelySetData(this.vm, this.vm, data) return nextTick() } @@ -630,6 +678,8 @@ export default class Wrapper implements BaseWrapper { if (!this.vm) { throwError(`wrapper.setMethods() can only be called on a Vue instance`) } + this.__warnIfDestroyed() + Object.keys(methods).forEach(key => { // $FlowIgnore : Problem with possibly null this.vm this.vm[key] = methods[key] @@ -657,6 +707,7 @@ export default class Wrapper implements BaseWrapper { if (!this.vm) { throwError(`wrapper.setProps() can only be called on a Vue instance`) } + this.__warnIfDestroyed() // Save the original "silent" config so that we can directly mutate props const originalConfig = Vue.config.silent @@ -730,6 +781,7 @@ export default class Wrapper implements BaseWrapper { const tagName = this.element.tagName // $FlowIgnore const type = this.attributes().type + this.__warnIfDestroyed() if (tagName === 'OPTION') { throwError( @@ -782,6 +834,8 @@ export default class Wrapper implements BaseWrapper { * Return text of wrapper element */ text(): string { + this.__warnIfDestroyed() + return this.element.textContent.trim() } @@ -789,6 +843,8 @@ export default class Wrapper implements BaseWrapper { * Dispatches a DOM event on wrapper */ trigger(type: string, options: Object = {}): Promise { + this.__warnIfDestroyed() + if (typeof type !== 'string') { throwError('wrapper.trigger() must be passed a string') } diff --git a/test/specs/wrapper/destroy.spec.js b/test/specs/wrapper/destroy.spec.js index 479617053..c25045c56 100644 --- a/test/specs/wrapper/destroy.spec.js +++ b/test/specs/wrapper/destroy.spec.js @@ -1,6 +1,19 @@ import { describeWithShallowAndMount } from '~resources/utils' +import { config } from 'packages/test-utils/src' describeWithShallowAndMount('destroy', mountingMethod => { + let originalConsoleError + + beforeEach(() => { + config.showDeprecationWarnings = true + originalConsoleError = console.error + console.error = jest.fn() + }) + + afterEach(() => { + console.error = originalConsoleError + }) + it('triggers beforeDestroy ', () => { const stub = jest.fn() mountingMethod({ @@ -61,4 +74,49 @@ describeWithShallowAndMount('destroy', mountingMethod => { const wrapper = mountingMethod(TestComponent) expect(() => wrapper.destroy()).toThrow() }) + + const StubComponent = { props: ['a'], template: '

' } + + ;[ + ['attributes'], + ['classes'], + ['isEmpty'], + ['isVisible'], + ['isVueInstance'], + ['name'], + ['overview'], + ['props'], + ['text'], + ['html'], + ['contains', ['p']], + ['get', ['p']], + ['find', ['p']], + ['findComponent', [StubComponent]], + ['findAll', [StubComponent]], + ['findAllComponents', [StubComponent]], + ['is', [StubComponent]], + ['setProps', [{ a: 1 }]], + ['setData', [{}]], + ['setMethods', [{}]], + ['trigger', ['test-event']] + ].forEach(([method, args = []]) => { + it(`displays warning when ${method} is called on destroyed wrapper`, () => { + config.showDeprecationWarnings = false + const wrapper = mountingMethod(StubComponent) + wrapper.destroy() + wrapper[method](...args) + + expect(console.error).toHaveBeenCalled() + }) + }) + ;['emitted', 'emittedByOrder', 'exists'].forEach(method => { + it(`does not display warning when ${method} is called on destroyed wrapper`, () => { + config.showDeprecationWarnings = false + const wrapper = mountingMethod(StubComponent) + wrapper.destroy() + wrapper[method]() + + expect(console.error).not.toHaveBeenCalled() + }) + }) }) From c90f597168c94c8b48785eb5dce130b3c533a752 Mon Sep 17 00:00:00 2001 From: TinyWisp <616244978@qq.com> Date: Wed, 14 Oct 2020 09:10:01 +0800 Subject: [PATCH 13/19] add the wrapper.getComponent method and corresponding documents. (#1714) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add the wrapper.getComponent method docs: create the /docs/api/wrapper/getComponent.md, and add the deprecation message to the /docs/api/wrapper/get.md docs(zh): create the /docs/zh/api/wrapper/getComponent.md, and add the deprecation message to the /docs/zh/api/wrapper/get.md * Update packages/test-utils/src/wrapper.js Co-authored-by: Adrià Fontcuberta * Update docs/api/wrapper/getComponent.md Co-authored-by: Adrià Fontcuberta * Update docs/zh/api/wrapper/getComponent.md Co-authored-by: Adrià Fontcuberta * Update docs/zh/api/wrapper/getComponent.md Co-authored-by: Adrià Fontcuberta * Update docs/api/wrapper/getComponent.md Co-authored-by: Adrià Fontcuberta Co-authored-by: yinquan Co-authored-by: Adrià Fontcuberta --- docs/api/wrapper/get.md | 4 ++++ docs/api/wrapper/getComponent.md | 27 +++++++++++++++++++++++++++ docs/zh/api/wrapper/get.md | 4 ++++ docs/zh/api/wrapper/getComponent.md | 24 ++++++++++++++++++++++++ packages/test-utils/src/wrapper.js | 16 +++++++++++++++- 5 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 docs/api/wrapper/getComponent.md create mode 100644 docs/zh/api/wrapper/getComponent.md diff --git a/docs/api/wrapper/get.md b/docs/api/wrapper/get.md index 5462c668d..fa3c679d4 100644 --- a/docs/api/wrapper/get.md +++ b/docs/api/wrapper/get.md @@ -1,5 +1,9 @@ ## get +::: warning Deprecation warning +Using `get` to search for a Component is deprecated and will be removed. Use [`getComponent`](./getComponent.md) instead. +::: + Works just like [find](./find.md) but will throw an error if nothing matching the given selector is found. You should use `find` when searching for an element that may not exist. You should use this method when getting an element that should diff --git a/docs/api/wrapper/getComponent.md b/docs/api/wrapper/getComponent.md new file mode 100644 index 000000000..b354e9d2b --- /dev/null +++ b/docs/api/wrapper/getComponent.md @@ -0,0 +1,27 @@ +## getComponent + +Works just like [findComponent](./findComponent.md) but will throw an error if nothing matching +the given selector is found. You should use `findComponent` when searching for an element +that may not exist. You should use this method when getting an element that should +exist and it will provide a nice error message if that is not the case. + +```js +import { mount } from '@vue/test-utils' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) + +// similar to `wrapper.findComponent`. +// `getComponent` will throw an error if an element is not found. `findComponent` will do nothing. +expect(wrapper.getComponent(Bar)) // => gets Bar by component instance +expect(wrapper.getComponent({ name: 'bar' })) // => gets Bar by `name` +expect(wrapper.getComponent({ ref: 'bar' })) // => gets Bar by `ref` + +expect(() => wrapper.getComponent({ name: 'does-not-exist' })) + .to.throw() + .with.property( + 'message', + "Unable to get a component named 'does-not-exist' within:
the actual DOM here...
" + ) +``` diff --git a/docs/zh/api/wrapper/get.md b/docs/zh/api/wrapper/get.md index fca5c1316..317fa2aa7 100644 --- a/docs/zh/api/wrapper/get.md +++ b/docs/zh/api/wrapper/get.md @@ -1,5 +1,9 @@ ## get +::: warning 废弃警告 +使用 `get` 搜索组件的方式已经被废弃并会被移除。请换用 [`getComponent`](./getComponent.md)。 +::: + 和 [find](./find.md) 工作起来一样,但是如果未匹配到给定的选择器时会抛出错误。当搜索一个可能不存在的元素时你应该使用 `find`。当获取一个应该存在的元素时你应该使用这个方法,并且如果没有找到的话它会提供一则友好的错误信息。 ```js diff --git a/docs/zh/api/wrapper/getComponent.md b/docs/zh/api/wrapper/getComponent.md new file mode 100644 index 000000000..be3507f31 --- /dev/null +++ b/docs/zh/api/wrapper/getComponent.md @@ -0,0 +1,24 @@ +## getComponent + +和 [findComponent](./findComponent.md) 工作起来一样,但是如果未匹配到给定的选择器时会抛出错误。当搜索一个可能不存在的元素时你应该使用 `findComponent`。当获取一个应该存在的元素时你应该使用这个方法,并且如果没有找到的话它会提供一则友好的错误信息。 + +```js +import { mount } from '@vue/test-utils' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) + +// 和 `wrapper.findComponent` 相似。 +// 如果 `getComponent` 没有找到任何元素将会抛出一个而错误。`findComponent` 则不会做任何事。 +expect(wrapper.getComponent(Bar)) // => gets Bar by component instance +expect(wrapper.getComponent({ name: 'bar' })) // => gets Bar by `name` +expect(wrapper.getComponent({ ref: 'bar' })) // => gets Bar by `ref` + +expect(() => wrapper.getComponent({ name: 'does-not-exist' })) + .to.throw() + .with.property( + 'message', + "Unable to get a component named 'does-not-exist' within:
the actual DOM here...
" + ) +``` diff --git a/packages/test-utils/src/wrapper.js b/packages/test-utils/src/wrapper.js index c56a899c5..3b3160949 100644 --- a/packages/test-utils/src/wrapper.js +++ b/packages/test-utils/src/wrapper.js @@ -237,6 +237,20 @@ export default class Wrapper implements BaseWrapper { return found } + /** + * Gets first node in tree of the current wrapper that + * matches the provided selector. + */ + getComponent(rawSelector: Selector): Wrapper { + this.__warnIfDestroyed() + + const found = this.findComponent(rawSelector) + if (found instanceof ErrorWrapper) { + throw new Error(`Unable to get ${rawSelector} within: ${this.html()}`) + } + return found + } + /** * Finds first DOM node in tree of the current wrapper that * matches the provided selector. @@ -248,7 +262,7 @@ export default class Wrapper implements BaseWrapper { if (selector.type !== DOM_SELECTOR) { warnDeprecated( 'finding components with `find` or `get`', - 'Use `findComponent` instead' + 'Use `findComponent` and `getComponent` instead' ) } From 8c56db6345312871bd02fb9fdd40dbf2c383b781 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Thu, 29 Oct 2020 06:37:28 +0800 Subject: [PATCH 14/19] feat: add abstract property to the core property of the component (#1716) --- packages/create-instance/create-component-stubs.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/create-instance/create-component-stubs.js b/packages/create-instance/create-component-stubs.js index 4fc6cd8d8..8c0749f26 100644 --- a/packages/create-instance/create-component-stubs.js +++ b/packages/create-instance/create-component-stubs.js @@ -56,7 +56,8 @@ function getCoreProperties(componentOptions: Component): Object { style: componentOptions.style, normalizedStyle: componentOptions.normalizedStyle, nativeOn: componentOptions.nativeOn, - functional: componentOptions.functional + functional: componentOptions.functional, + abstract: componentOptions.abstract } } From e1c252699ad3ba5cba5ff2d4666386a05fedfd02 Mon Sep 17 00:00:00 2001 From: Blake Newman Date: Fri, 30 Oct 2020 04:16:42 +0000 Subject: [PATCH 15/19] feat(test-utils): add types for auto destroy methods (#1724) Add types for auto destroy methods, the hook type is typed loosely to be agnostic to testing frameworks. --- packages/test-utils/types/index.d.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/test-utils/types/index.d.ts b/packages/test-utils/types/index.d.ts index b5c91e9b1..27d0573ac 100644 --- a/packages/test-utils/types/index.d.ts +++ b/packages/test-utils/types/index.d.ts @@ -183,4 +183,7 @@ export declare function shallowMount export declare function createWrapper(node: HTMLElement, options?: WrapperOptions): Wrapper +export declare function enableAutoDestroy(hook: (...args: any[]) => any): void +export declare function resetAutoDestroyState(hook: (...args: any[]) => any): void + export declare let RouterLinkStub: VueClass From c198d19de032b307173ce1f1b933df50a3871d77 Mon Sep 17 00:00:00 2001 From: Pieter De Decker Date: Sat, 31 Oct 2020 13:25:12 +0100 Subject: [PATCH 16/19] fix: ignore non-vue wrapper for auto-destroy (#1723) Co-authored-by: Joeri Hendrickx --- packages/test-utils/src/auto-destroy.js | 6 +++--- test/specs/wrapper.spec.js | 14 +++++++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/test-utils/src/auto-destroy.js b/packages/test-utils/src/auto-destroy.js index 8f9073077..15f27c500 100644 --- a/packages/test-utils/src/auto-destroy.js +++ b/packages/test-utils/src/auto-destroy.js @@ -21,9 +21,9 @@ export function enableAutoDestroy(hook: (() => void) => void) { hook(() => { wrapperInstances.forEach((wrapper: Wrapper) => { // skip child wrappers created by wrapper.find() - if (wrapper.selector) return - - wrapper.destroy() + if (wrapper.vm || wrapper.isFunctionalComponent) { + wrapper.destroy() + } }) wrapperInstances.length = 0 diff --git a/test/specs/wrapper.spec.js b/test/specs/wrapper.spec.js index 95934dac3..b1855b196 100644 --- a/test/specs/wrapper.spec.js +++ b/test/specs/wrapper.spec.js @@ -1,7 +1,8 @@ import { describeWithShallowAndMount } from '~resources/utils' import { enableAutoDestroy, - resetAutoDestroyState + resetAutoDestroyState, + createWrapper } from 'packages/test-utils/src' describeWithShallowAndMount('Wrapper', mountingMethod => { @@ -51,5 +52,16 @@ describeWithShallowAndMount('Wrapper', mountingMethod => { expect(() => enableAutoDestroy(noop)).toThrow() }) + + it('does not fail when non-Vue wrappers exist', async () => { + let hookCallback + enableAutoDestroy(callback => { + hookCallback = callback + }) + + createWrapper(document.createElement('div')) + + expect(hookCallback).not.toThrow() + }) }) }) From 3cbc2cc46627f98f4e0044d9e896b7652fcf1397 Mon Sep 17 00:00:00 2001 From: Lachlan Miller Date: Sun, 1 Nov 2020 10:35:03 +1000 Subject: [PATCH 17/19] chore: build 1.1.1 --- lerna.json | 2 +- .../dist/vue-server-test-utils.js | 91 +++++++++++++++- .../test-utils/dist/vue-test-utils.iife.js | 101 ++++++++++++++++-- packages/test-utils/dist/vue-test-utils.js | 101 ++++++++++++++++-- .../test-utils/dist/vue-test-utils.umd.js | 101 ++++++++++++++++-- 5 files changed, 363 insertions(+), 33 deletions(-) diff --git a/lerna.json b/lerna.json index 0c839ade0..9821206c6 100644 --- a/lerna.json +++ b/lerna.json @@ -5,5 +5,5 @@ ], "npmClient": "yarn", "useWorkspaces": true, - "version": "1.1.0" + "version": "1.1.1" } diff --git a/packages/server-test-utils/dist/vue-server-test-utils.js b/packages/server-test-utils/dist/vue-server-test-utils.js index cd96874e0..b3f56111f 100644 --- a/packages/server-test-utils/dist/vue-server-test-utils.js +++ b/packages/server-test-utils/dist/vue-server-test-utils.js @@ -8116,7 +8116,11 @@ function recursivelySetData(vm, target, data) { var val = data[key]; var targetVal = target[key]; - if (isPlainObject(val) && isPlainObject(targetVal)) { + if ( + isPlainObject(val) && + isPlainObject(targetVal) && + Object.keys(val).length > 0 + ) { recursivelySetData(vm, targetVal, val); } else { vm.$set(target, key, val); @@ -9481,7 +9485,18 @@ var Wrapper = function Wrapper( } }; +/** + * Prints warning if component is destroyed + */ +Wrapper.prototype.__warnIfDestroyed = function __warnIfDestroyed () { + if (!this.exists()) { + warn('Operations on destroyed component are discouraged'); + } +}; + Wrapper.prototype.at = function at () { + this.__warnIfDestroyed(); + throwError('at() must be called on a WrapperArray'); }; @@ -9489,6 +9504,8 @@ Wrapper.prototype.at = function at () { * Returns an Object containing all the attribute/value pairs on the element. */ Wrapper.prototype.attributes = function attributes (key) { + this.__warnIfDestroyed(); + var attributes = this.element.attributes; var attributeMap = {}; for (var i = 0; i < attributes.length; i++) { @@ -9505,6 +9522,8 @@ Wrapper.prototype.attributes = function attributes (key) { Wrapper.prototype.classes = function classes (className) { var this$1 = this; + this.__warnIfDestroyed(); + var classAttribute = this.element.getAttribute('class'); var classes = classAttribute ? classAttribute.split(' ') : []; // Handle converting cssmodules identifiers back to the original class name @@ -9535,6 +9554,9 @@ Wrapper.prototype.contains = function contains (rawSelector) { 'contains', 'Use `wrapper.find`, `wrapper.findComponent` or `wrapper.get` instead' ); + + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'contains'); var nodes = find(this.rootNode, this.vm, selector); return nodes.length > 0 @@ -9610,6 +9632,8 @@ Wrapper.prototype.filter = function filter () { * matches the provided selector. */ Wrapper.prototype.get = function get (rawSelector) { + this.__warnIfDestroyed(); + var found = this.find(rawSelector); if (found instanceof ErrorWrapper) { throw new Error(("Unable to find " + rawSelector + " within: " + (this.html()))) @@ -9617,16 +9641,32 @@ Wrapper.prototype.get = function get (rawSelector) { return found }; +/** + * Gets first node in tree of the current wrapper that + * matches the provided selector. + */ +Wrapper.prototype.getComponent = function getComponent (rawSelector) { + this.__warnIfDestroyed(); + + var found = this.findComponent(rawSelector); + if (found instanceof ErrorWrapper) { + throw new Error(("Unable to get " + rawSelector + " within: " + (this.html()))) + } + return found +}; + /** * Finds first DOM node in tree of the current wrapper that * matches the provided selector. */ Wrapper.prototype.find = function find (rawSelector) { + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'find'); if (selector.type !== DOM_SELECTOR) { warnDeprecated( - 'finding components with `find`', - 'Use `findComponent` instead' + 'finding components with `find` or `get`', + 'Use `findComponent` and `getComponent` instead' ); } @@ -9638,6 +9678,8 @@ Wrapper.prototype.find = function find (rawSelector) { * matches the provided selector. */ Wrapper.prototype.findComponent = function findComponent (rawSelector) { + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'findComponent'); if (!this.vm && !this.isFunctionalComponent) { throwError( @@ -9671,6 +9713,8 @@ Wrapper.prototype.__find = function __find (rawSelector, selector) { * the provided selector. */ Wrapper.prototype.findAll = function findAll (rawSelector) { + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'findAll'); if (selector.type !== DOM_SELECTOR) { warnDeprecated( @@ -9686,6 +9730,8 @@ Wrapper.prototype.findAll = function findAll (rawSelector) { * the provided selector. */ Wrapper.prototype.findAllComponents = function findAllComponents (rawSelector) { + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'findAll'); if (!this.vm) { throwError( @@ -9721,6 +9767,8 @@ Wrapper.prototype.__findAll = function __findAll (rawSelector, selector) { * Returns HTML of element as a string */ Wrapper.prototype.html = function html () { + this.__warnIfDestroyed(); + return pretty(this.element.outerHTML) }; @@ -9728,6 +9776,8 @@ Wrapper.prototype.html = function html () { * Checks if node matches selector or component definition */ Wrapper.prototype.is = function is (rawSelector) { + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'is'); if (selector.type === DOM_SELECTOR) { @@ -9754,6 +9804,8 @@ Wrapper.prototype.isEmpty = function isEmpty () { 'Consider a custom matcher such as those provided in jest-dom: https://github.com/testing-library/jest-dom#tobeempty. ' + 'When using with findComponent, access the DOM element with findComponent(Comp).element' ); + this.__warnIfDestroyed(); + if (!this.vnode) { return this.element.innerHTML === '' } @@ -9778,6 +9830,8 @@ Wrapper.prototype.isEmpty = function isEmpty () { * Checks if node is visible */ Wrapper.prototype.isVisible = function isVisible () { + this.__warnIfDestroyed(); + return isElementVisible(this.element) }; @@ -9787,6 +9841,8 @@ Wrapper.prototype.isVisible = function isVisible () { */ Wrapper.prototype.isVueInstance = function isVueInstance () { warnDeprecated("isVueInstance"); + this.__warnIfDestroyed(); + return !!this.vm }; @@ -9796,6 +9852,7 @@ Wrapper.prototype.isVueInstance = function isVueInstance () { */ Wrapper.prototype.name = function name () { warnDeprecated("name"); + this.__warnIfDestroyed(); if (this.vm) { return ( @@ -9821,6 +9878,7 @@ Wrapper.prototype.overview = function overview () { var this$1 = this; warnDeprecated("overview"); + this.__warnIfDestroyed(); if (!this.vm) { throwError("wrapper.overview() can only be called on a Vue instance"); @@ -9907,6 +9965,7 @@ Wrapper.prototype.props = function props (key) { if (!this.vm) { throwError('wrapper.props() must be called on a Vue instance'); } + this.__warnIfDestroyed(); var props = {}; var keys = this.vm && this.vm.$options._propKeys; @@ -9933,6 +9992,8 @@ Wrapper.prototype.props = function props (key) { Wrapper.prototype.setChecked = function setChecked (checked) { if ( checked === void 0 ) checked = true; + this.__warnIfDestroyed(); + if (typeof checked !== 'boolean') { throwError('wrapper.setChecked() must be passed a boolean'); } @@ -9982,6 +10043,8 @@ Wrapper.prototype.setChecked = function setChecked (checked) { * @deprecated */ Wrapper.prototype.setSelected = function setSelected () { + this.__warnIfDestroyed(); + var tagName = this.element.tagName; if (tagName === 'SELECT') { @@ -10027,6 +10090,8 @@ Wrapper.prototype.setData = function setData (data) { throwError("wrapper.setData() can only be called on a Vue instance"); } + this.__warnIfDestroyed(); + recursivelySetData(this.vm, this.vm, data); return nextTick() }; @@ -10046,6 +10111,8 @@ Wrapper.prototype.setMethods = function setMethods (methods) { if (!this.vm) { throwError("wrapper.setMethods() can only be called on a Vue instance"); } + this.__warnIfDestroyed(); + Object.keys(methods).forEach(function (key) { // $FlowIgnore : Problem with possibly null this.vm this$1.vm[key] = methods[key]; @@ -10075,6 +10142,7 @@ Wrapper.prototype.setProps = function setProps (data) { if (!this.vm) { throwError("wrapper.setProps() can only be called on a Vue instance"); } + this.__warnIfDestroyed(); // Save the original "silent" config so that we can directly mutate props var originalConfig = Vue__default['default'].config.silent; @@ -10148,6 +10216,7 @@ Wrapper.prototype.setValue = function setValue (value) { var tagName = this.element.tagName; // $FlowIgnore var type = this.attributes().type; + this.__warnIfDestroyed(); if (tagName === 'OPTION') { throwError( @@ -10200,6 +10269,8 @@ Wrapper.prototype.setValue = function setValue (value) { * Return text of wrapper element */ Wrapper.prototype.text = function text () { + this.__warnIfDestroyed(); + return this.element.textContent.trim() }; @@ -10209,6 +10280,8 @@ Wrapper.prototype.text = function text () { Wrapper.prototype.trigger = function trigger (type, options) { if ( options === void 0 ) options = {}; + this.__warnIfDestroyed(); + if (typeof type !== 'string') { throwError('wrapper.trigger() must be passed a string'); } @@ -13112,7 +13185,11 @@ function warnDeprecated(method, fallback) { if (!config.showDeprecationWarnings) { return } var msg = method + " is deprecated and will be removed in the next major version."; if (fallback) { msg += " " + fallback + "."; } - warn(msg); + if (config.deprecationWarningHandler) { + config.deprecationWarningHandler(method, msg); + } else { + warn(msg); + } } // @@ -13372,7 +13449,8 @@ function getCoreProperties(componentOptions) { style: componentOptions.style, normalizedStyle: componentOptions.normalizedStyle, nativeOn: componentOptions.nativeOn, - functional: componentOptions.functional + functional: componentOptions.functional, + abstract: componentOptions.abstract } } @@ -13452,6 +13530,9 @@ function createStubFromComponent( tagName, { ref: componentOptions.functional ? context.data.ref : undefined, + domProps: componentOptions.functional + ? context.data.domProps + : undefined, attrs: componentOptions.functional ? Object.assign({}, context.props, context.data.attrs, diff --git a/packages/test-utils/dist/vue-test-utils.iife.js b/packages/test-utils/dist/vue-test-utils.iife.js index 3368df4bd..c76162cbb 100644 --- a/packages/test-utils/dist/vue-test-utils.iife.js +++ b/packages/test-utils/dist/vue-test-utils.iife.js @@ -1792,7 +1792,11 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { if (!config.showDeprecationWarnings) { return } var msg = method + " is deprecated and will be removed in the next major version."; if (fallback) { msg += " " + fallback + "."; } - warn(msg); + if (config.deprecationWarningHandler) { + config.deprecationWarningHandler(method, msg); + } else { + warn(msg); + } } // @@ -2272,7 +2276,8 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { style: componentOptions.style, normalizedStyle: componentOptions.normalizedStyle, nativeOn: componentOptions.nativeOn, - functional: componentOptions.functional + functional: componentOptions.functional, + abstract: componentOptions.abstract } } @@ -2352,6 +2357,9 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { tagName, { ref: componentOptions.functional ? context.data.ref : undefined, + domProps: componentOptions.functional + ? context.data.domProps + : undefined, attrs: componentOptions.functional ? Object.assign({}, context.props, context.data.attrs, @@ -9030,7 +9038,11 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { var val = data[key]; var targetVal = target[key]; - if (isPlainObject(val) && isPlainObject(targetVal)) { + if ( + isPlainObject(val) && + isPlainObject(targetVal) && + Object.keys(val).length > 0 + ) { recursivelySetData(vm, targetVal, val); } else { vm.$set(target, key, val); @@ -10395,7 +10407,18 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { } }; + /** + * Prints warning if component is destroyed + */ + Wrapper.prototype.__warnIfDestroyed = function __warnIfDestroyed () { + if (!this.exists()) { + warn('Operations on destroyed component are discouraged'); + } + }; + Wrapper.prototype.at = function at () { + this.__warnIfDestroyed(); + throwError('at() must be called on a WrapperArray'); }; @@ -10403,6 +10426,8 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { * Returns an Object containing all the attribute/value pairs on the element. */ Wrapper.prototype.attributes = function attributes (key) { + this.__warnIfDestroyed(); + var attributes = this.element.attributes; var attributeMap = {}; for (var i = 0; i < attributes.length; i++) { @@ -10419,6 +10444,8 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { Wrapper.prototype.classes = function classes (className) { var this$1 = this; + this.__warnIfDestroyed(); + var classAttribute = this.element.getAttribute('class'); var classes = classAttribute ? classAttribute.split(' ') : []; // Handle converting cssmodules identifiers back to the original class name @@ -10449,6 +10476,9 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { 'contains', 'Use `wrapper.find`, `wrapper.findComponent` or `wrapper.get` instead' ); + + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'contains'); var nodes = find(this.rootNode, this.vm, selector); return nodes.length > 0 @@ -10524,6 +10554,8 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { * matches the provided selector. */ Wrapper.prototype.get = function get (rawSelector) { + this.__warnIfDestroyed(); + var found = this.find(rawSelector); if (found instanceof ErrorWrapper) { throw new Error(("Unable to find " + rawSelector + " within: " + (this.html()))) @@ -10531,16 +10563,32 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { return found }; + /** + * Gets first node in tree of the current wrapper that + * matches the provided selector. + */ + Wrapper.prototype.getComponent = function getComponent (rawSelector) { + this.__warnIfDestroyed(); + + var found = this.findComponent(rawSelector); + if (found instanceof ErrorWrapper) { + throw new Error(("Unable to get " + rawSelector + " within: " + (this.html()))) + } + return found + }; + /** * Finds first DOM node in tree of the current wrapper that * matches the provided selector. */ Wrapper.prototype.find = function find (rawSelector) { + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'find'); if (selector.type !== DOM_SELECTOR) { warnDeprecated( - 'finding components with `find`', - 'Use `findComponent` instead' + 'finding components with `find` or `get`', + 'Use `findComponent` and `getComponent` instead' ); } @@ -10552,6 +10600,8 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { * matches the provided selector. */ Wrapper.prototype.findComponent = function findComponent (rawSelector) { + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'findComponent'); if (!this.vm && !this.isFunctionalComponent) { throwError( @@ -10585,6 +10635,8 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { * the provided selector. */ Wrapper.prototype.findAll = function findAll (rawSelector) { + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'findAll'); if (selector.type !== DOM_SELECTOR) { warnDeprecated( @@ -10600,6 +10652,8 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { * the provided selector. */ Wrapper.prototype.findAllComponents = function findAllComponents (rawSelector) { + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'findAll'); if (!this.vm) { throwError( @@ -10635,6 +10689,8 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { * Returns HTML of element as a string */ Wrapper.prototype.html = function html () { + this.__warnIfDestroyed(); + return pretty(this.element.outerHTML) }; @@ -10642,6 +10698,8 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { * Checks if node matches selector or component definition */ Wrapper.prototype.is = function is (rawSelector) { + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'is'); if (selector.type === DOM_SELECTOR) { @@ -10668,6 +10726,8 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { 'Consider a custom matcher such as those provided in jest-dom: https://github.com/testing-library/jest-dom#tobeempty. ' + 'When using with findComponent, access the DOM element with findComponent(Comp).element' ); + this.__warnIfDestroyed(); + if (!this.vnode) { return this.element.innerHTML === '' } @@ -10692,6 +10752,8 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { * Checks if node is visible */ Wrapper.prototype.isVisible = function isVisible () { + this.__warnIfDestroyed(); + return isElementVisible(this.element) }; @@ -10701,6 +10763,8 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { */ Wrapper.prototype.isVueInstance = function isVueInstance () { warnDeprecated("isVueInstance"); + this.__warnIfDestroyed(); + return !!this.vm }; @@ -10710,6 +10774,7 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { */ Wrapper.prototype.name = function name () { warnDeprecated("name"); + this.__warnIfDestroyed(); if (this.vm) { return ( @@ -10735,6 +10800,7 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { var this$1 = this; warnDeprecated("overview"); + this.__warnIfDestroyed(); if (!this.vm) { throwError("wrapper.overview() can only be called on a Vue instance"); @@ -10821,6 +10887,7 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { if (!this.vm) { throwError('wrapper.props() must be called on a Vue instance'); } + this.__warnIfDestroyed(); var props = {}; var keys = this.vm && this.vm.$options._propKeys; @@ -10847,6 +10914,8 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { Wrapper.prototype.setChecked = function setChecked (checked) { if ( checked === void 0 ) checked = true; + this.__warnIfDestroyed(); + if (typeof checked !== 'boolean') { throwError('wrapper.setChecked() must be passed a boolean'); } @@ -10896,6 +10965,8 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { * @deprecated */ Wrapper.prototype.setSelected = function setSelected () { + this.__warnIfDestroyed(); + var tagName = this.element.tagName; if (tagName === 'SELECT') { @@ -10941,6 +11012,8 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { throwError("wrapper.setData() can only be called on a Vue instance"); } + this.__warnIfDestroyed(); + recursivelySetData(this.vm, this.vm, data); return nextTick() }; @@ -10960,6 +11033,8 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { if (!this.vm) { throwError("wrapper.setMethods() can only be called on a Vue instance"); } + this.__warnIfDestroyed(); + Object.keys(methods).forEach(function (key) { // $FlowIgnore : Problem with possibly null this.vm this$1.vm[key] = methods[key]; @@ -10989,6 +11064,7 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { if (!this.vm) { throwError("wrapper.setProps() can only be called on a Vue instance"); } + this.__warnIfDestroyed(); // Save the original "silent" config so that we can directly mutate props var originalConfig = Vue__default['default'].config.silent; @@ -11062,6 +11138,7 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { var tagName = this.element.tagName; // $FlowIgnore var type = this.attributes().type; + this.__warnIfDestroyed(); if (tagName === 'OPTION') { throwError( @@ -11114,6 +11191,8 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { * Return text of wrapper element */ Wrapper.prototype.text = function text () { + this.__warnIfDestroyed(); + return this.element.textContent.trim() }; @@ -11123,6 +11202,8 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { Wrapper.prototype.trigger = function trigger (type, options) { if ( options === void 0 ) options = {}; + this.__warnIfDestroyed(); + if (typeof type !== 'string') { throwError('wrapper.trigger() must be passed a string'); } @@ -11221,9 +11302,9 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { hook(function () { wrapperInstances.forEach(function (wrapper) { // skip child wrappers created by wrapper.find() - if (wrapper.selector) { return } - - wrapper.destroy(); + if (wrapper.vm || wrapper.isFunctionalComponent) { + wrapper.destroy(); + } }); wrapperInstances.length = 0; @@ -13927,7 +14008,9 @@ var VueTestUtils = (function (exports, Vue, vueTemplateCompiler) { var parentVm = createInstance(component, mergedOptions, _Vue); var el = - options.attachTo || (options.attachToDocument ? createElement() : undefined); + options.attachToDocument || options.attachTo instanceof HTMLBodyElement + ? createElement() + : options.attachTo; var vm = parentVm.$mount(el); component._Ctor = {}; diff --git a/packages/test-utils/dist/vue-test-utils.js b/packages/test-utils/dist/vue-test-utils.js index f558740da..5843ecc50 100644 --- a/packages/test-utils/dist/vue-test-utils.js +++ b/packages/test-utils/dist/vue-test-utils.js @@ -1796,7 +1796,11 @@ function warnDeprecated(method, fallback) { if (!config.showDeprecationWarnings) { return } var msg = method + " is deprecated and will be removed in the next major version."; if (fallback) { msg += " " + fallback + "."; } - warn(msg); + if (config.deprecationWarningHandler) { + config.deprecationWarningHandler(method, msg); + } else { + warn(msg); + } } // @@ -2276,7 +2280,8 @@ function getCoreProperties(componentOptions) { style: componentOptions.style, normalizedStyle: componentOptions.normalizedStyle, nativeOn: componentOptions.nativeOn, - functional: componentOptions.functional + functional: componentOptions.functional, + abstract: componentOptions.abstract } } @@ -2356,6 +2361,9 @@ function createStubFromComponent( tagName, { ref: componentOptions.functional ? context.data.ref : undefined, + domProps: componentOptions.functional + ? context.data.domProps + : undefined, attrs: componentOptions.functional ? Object.assign({}, context.props, context.data.attrs, @@ -9034,7 +9042,11 @@ function recursivelySetData(vm, target, data) { var val = data[key]; var targetVal = target[key]; - if (isPlainObject(val) && isPlainObject(targetVal)) { + if ( + isPlainObject(val) && + isPlainObject(targetVal) && + Object.keys(val).length > 0 + ) { recursivelySetData(vm, targetVal, val); } else { vm.$set(target, key, val); @@ -10399,7 +10411,18 @@ var Wrapper = function Wrapper( } }; +/** + * Prints warning if component is destroyed + */ +Wrapper.prototype.__warnIfDestroyed = function __warnIfDestroyed () { + if (!this.exists()) { + warn('Operations on destroyed component are discouraged'); + } +}; + Wrapper.prototype.at = function at () { + this.__warnIfDestroyed(); + throwError('at() must be called on a WrapperArray'); }; @@ -10407,6 +10430,8 @@ Wrapper.prototype.at = function at () { * Returns an Object containing all the attribute/value pairs on the element. */ Wrapper.prototype.attributes = function attributes (key) { + this.__warnIfDestroyed(); + var attributes = this.element.attributes; var attributeMap = {}; for (var i = 0; i < attributes.length; i++) { @@ -10423,6 +10448,8 @@ Wrapper.prototype.attributes = function attributes (key) { Wrapper.prototype.classes = function classes (className) { var this$1 = this; + this.__warnIfDestroyed(); + var classAttribute = this.element.getAttribute('class'); var classes = classAttribute ? classAttribute.split(' ') : []; // Handle converting cssmodules identifiers back to the original class name @@ -10453,6 +10480,9 @@ Wrapper.prototype.contains = function contains (rawSelector) { 'contains', 'Use `wrapper.find`, `wrapper.findComponent` or `wrapper.get` instead' ); + + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'contains'); var nodes = find(this.rootNode, this.vm, selector); return nodes.length > 0 @@ -10528,6 +10558,8 @@ Wrapper.prototype.filter = function filter () { * matches the provided selector. */ Wrapper.prototype.get = function get (rawSelector) { + this.__warnIfDestroyed(); + var found = this.find(rawSelector); if (found instanceof ErrorWrapper) { throw new Error(("Unable to find " + rawSelector + " within: " + (this.html()))) @@ -10535,16 +10567,32 @@ Wrapper.prototype.get = function get (rawSelector) { return found }; +/** + * Gets first node in tree of the current wrapper that + * matches the provided selector. + */ +Wrapper.prototype.getComponent = function getComponent (rawSelector) { + this.__warnIfDestroyed(); + + var found = this.findComponent(rawSelector); + if (found instanceof ErrorWrapper) { + throw new Error(("Unable to get " + rawSelector + " within: " + (this.html()))) + } + return found +}; + /** * Finds first DOM node in tree of the current wrapper that * matches the provided selector. */ Wrapper.prototype.find = function find (rawSelector) { + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'find'); if (selector.type !== DOM_SELECTOR) { warnDeprecated( - 'finding components with `find`', - 'Use `findComponent` instead' + 'finding components with `find` or `get`', + 'Use `findComponent` and `getComponent` instead' ); } @@ -10556,6 +10604,8 @@ Wrapper.prototype.find = function find (rawSelector) { * matches the provided selector. */ Wrapper.prototype.findComponent = function findComponent (rawSelector) { + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'findComponent'); if (!this.vm && !this.isFunctionalComponent) { throwError( @@ -10589,6 +10639,8 @@ Wrapper.prototype.__find = function __find (rawSelector, selector) { * the provided selector. */ Wrapper.prototype.findAll = function findAll (rawSelector) { + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'findAll'); if (selector.type !== DOM_SELECTOR) { warnDeprecated( @@ -10604,6 +10656,8 @@ Wrapper.prototype.findAll = function findAll (rawSelector) { * the provided selector. */ Wrapper.prototype.findAllComponents = function findAllComponents (rawSelector) { + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'findAll'); if (!this.vm) { throwError( @@ -10639,6 +10693,8 @@ Wrapper.prototype.__findAll = function __findAll (rawSelector, selector) { * Returns HTML of element as a string */ Wrapper.prototype.html = function html () { + this.__warnIfDestroyed(); + return pretty(this.element.outerHTML) }; @@ -10646,6 +10702,8 @@ Wrapper.prototype.html = function html () { * Checks if node matches selector or component definition */ Wrapper.prototype.is = function is (rawSelector) { + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'is'); if (selector.type === DOM_SELECTOR) { @@ -10672,6 +10730,8 @@ Wrapper.prototype.isEmpty = function isEmpty () { 'Consider a custom matcher such as those provided in jest-dom: https://github.com/testing-library/jest-dom#tobeempty. ' + 'When using with findComponent, access the DOM element with findComponent(Comp).element' ); + this.__warnIfDestroyed(); + if (!this.vnode) { return this.element.innerHTML === '' } @@ -10696,6 +10756,8 @@ Wrapper.prototype.isEmpty = function isEmpty () { * Checks if node is visible */ Wrapper.prototype.isVisible = function isVisible () { + this.__warnIfDestroyed(); + return isElementVisible(this.element) }; @@ -10705,6 +10767,8 @@ Wrapper.prototype.isVisible = function isVisible () { */ Wrapper.prototype.isVueInstance = function isVueInstance () { warnDeprecated("isVueInstance"); + this.__warnIfDestroyed(); + return !!this.vm }; @@ -10714,6 +10778,7 @@ Wrapper.prototype.isVueInstance = function isVueInstance () { */ Wrapper.prototype.name = function name () { warnDeprecated("name"); + this.__warnIfDestroyed(); if (this.vm) { return ( @@ -10739,6 +10804,7 @@ Wrapper.prototype.overview = function overview () { var this$1 = this; warnDeprecated("overview"); + this.__warnIfDestroyed(); if (!this.vm) { throwError("wrapper.overview() can only be called on a Vue instance"); @@ -10825,6 +10891,7 @@ Wrapper.prototype.props = function props (key) { if (!this.vm) { throwError('wrapper.props() must be called on a Vue instance'); } + this.__warnIfDestroyed(); var props = {}; var keys = this.vm && this.vm.$options._propKeys; @@ -10851,6 +10918,8 @@ Wrapper.prototype.props = function props (key) { Wrapper.prototype.setChecked = function setChecked (checked) { if ( checked === void 0 ) checked = true; + this.__warnIfDestroyed(); + if (typeof checked !== 'boolean') { throwError('wrapper.setChecked() must be passed a boolean'); } @@ -10900,6 +10969,8 @@ Wrapper.prototype.setChecked = function setChecked (checked) { * @deprecated */ Wrapper.prototype.setSelected = function setSelected () { + this.__warnIfDestroyed(); + var tagName = this.element.tagName; if (tagName === 'SELECT') { @@ -10945,6 +11016,8 @@ Wrapper.prototype.setData = function setData (data) { throwError("wrapper.setData() can only be called on a Vue instance"); } + this.__warnIfDestroyed(); + recursivelySetData(this.vm, this.vm, data); return nextTick() }; @@ -10964,6 +11037,8 @@ Wrapper.prototype.setMethods = function setMethods (methods) { if (!this.vm) { throwError("wrapper.setMethods() can only be called on a Vue instance"); } + this.__warnIfDestroyed(); + Object.keys(methods).forEach(function (key) { // $FlowIgnore : Problem with possibly null this.vm this$1.vm[key] = methods[key]; @@ -10993,6 +11068,7 @@ Wrapper.prototype.setProps = function setProps (data) { if (!this.vm) { throwError("wrapper.setProps() can only be called on a Vue instance"); } + this.__warnIfDestroyed(); // Save the original "silent" config so that we can directly mutate props var originalConfig = Vue__default['default'].config.silent; @@ -11066,6 +11142,7 @@ Wrapper.prototype.setValue = function setValue (value) { var tagName = this.element.tagName; // $FlowIgnore var type = this.attributes().type; + this.__warnIfDestroyed(); if (tagName === 'OPTION') { throwError( @@ -11118,6 +11195,8 @@ Wrapper.prototype.setValue = function setValue (value) { * Return text of wrapper element */ Wrapper.prototype.text = function text () { + this.__warnIfDestroyed(); + return this.element.textContent.trim() }; @@ -11127,6 +11206,8 @@ Wrapper.prototype.text = function text () { Wrapper.prototype.trigger = function trigger (type, options) { if ( options === void 0 ) options = {}; + this.__warnIfDestroyed(); + if (typeof type !== 'string') { throwError('wrapper.trigger() must be passed a string'); } @@ -11225,9 +11306,9 @@ function enableAutoDestroy(hook) { hook(function () { wrapperInstances.forEach(function (wrapper) { // skip child wrappers created by wrapper.find() - if (wrapper.selector) { return } - - wrapper.destroy(); + if (wrapper.vm || wrapper.isFunctionalComponent) { + wrapper.destroy(); + } }); wrapperInstances.length = 0; @@ -13931,7 +14012,9 @@ function mount(component, options) { var parentVm = createInstance(component, mergedOptions, _Vue); var el = - options.attachTo || (options.attachToDocument ? createElement() : undefined); + options.attachToDocument || options.attachTo instanceof HTMLBodyElement + ? createElement() + : options.attachTo; var vm = parentVm.$mount(el); component._Ctor = {}; diff --git a/packages/test-utils/dist/vue-test-utils.umd.js b/packages/test-utils/dist/vue-test-utils.umd.js index db3607e90..7a5d06e6d 100644 --- a/packages/test-utils/dist/vue-test-utils.umd.js +++ b/packages/test-utils/dist/vue-test-utils.umd.js @@ -1795,7 +1795,11 @@ if (!config.showDeprecationWarnings) { return } var msg = method + " is deprecated and will be removed in the next major version."; if (fallback) { msg += " " + fallback + "."; } - warn(msg); + if (config.deprecationWarningHandler) { + config.deprecationWarningHandler(method, msg); + } else { + warn(msg); + } } // @@ -2275,7 +2279,8 @@ style: componentOptions.style, normalizedStyle: componentOptions.normalizedStyle, nativeOn: componentOptions.nativeOn, - functional: componentOptions.functional + functional: componentOptions.functional, + abstract: componentOptions.abstract } } @@ -2355,6 +2360,9 @@ tagName, { ref: componentOptions.functional ? context.data.ref : undefined, + domProps: componentOptions.functional + ? context.data.domProps + : undefined, attrs: componentOptions.functional ? Object.assign({}, context.props, context.data.attrs, @@ -9033,7 +9041,11 @@ var val = data[key]; var targetVal = target[key]; - if (isPlainObject(val) && isPlainObject(targetVal)) { + if ( + isPlainObject(val) && + isPlainObject(targetVal) && + Object.keys(val).length > 0 + ) { recursivelySetData(vm, targetVal, val); } else { vm.$set(target, key, val); @@ -10398,7 +10410,18 @@ } }; + /** + * Prints warning if component is destroyed + */ + Wrapper.prototype.__warnIfDestroyed = function __warnIfDestroyed () { + if (!this.exists()) { + warn('Operations on destroyed component are discouraged'); + } + }; + Wrapper.prototype.at = function at () { + this.__warnIfDestroyed(); + throwError('at() must be called on a WrapperArray'); }; @@ -10406,6 +10429,8 @@ * Returns an Object containing all the attribute/value pairs on the element. */ Wrapper.prototype.attributes = function attributes (key) { + this.__warnIfDestroyed(); + var attributes = this.element.attributes; var attributeMap = {}; for (var i = 0; i < attributes.length; i++) { @@ -10422,6 +10447,8 @@ Wrapper.prototype.classes = function classes (className) { var this$1 = this; + this.__warnIfDestroyed(); + var classAttribute = this.element.getAttribute('class'); var classes = classAttribute ? classAttribute.split(' ') : []; // Handle converting cssmodules identifiers back to the original class name @@ -10452,6 +10479,9 @@ 'contains', 'Use `wrapper.find`, `wrapper.findComponent` or `wrapper.get` instead' ); + + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'contains'); var nodes = find(this.rootNode, this.vm, selector); return nodes.length > 0 @@ -10527,6 +10557,8 @@ * matches the provided selector. */ Wrapper.prototype.get = function get (rawSelector) { + this.__warnIfDestroyed(); + var found = this.find(rawSelector); if (found instanceof ErrorWrapper) { throw new Error(("Unable to find " + rawSelector + " within: " + (this.html()))) @@ -10534,16 +10566,32 @@ return found }; + /** + * Gets first node in tree of the current wrapper that + * matches the provided selector. + */ + Wrapper.prototype.getComponent = function getComponent (rawSelector) { + this.__warnIfDestroyed(); + + var found = this.findComponent(rawSelector); + if (found instanceof ErrorWrapper) { + throw new Error(("Unable to get " + rawSelector + " within: " + (this.html()))) + } + return found + }; + /** * Finds first DOM node in tree of the current wrapper that * matches the provided selector. */ Wrapper.prototype.find = function find (rawSelector) { + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'find'); if (selector.type !== DOM_SELECTOR) { warnDeprecated( - 'finding components with `find`', - 'Use `findComponent` instead' + 'finding components with `find` or `get`', + 'Use `findComponent` and `getComponent` instead' ); } @@ -10555,6 +10603,8 @@ * matches the provided selector. */ Wrapper.prototype.findComponent = function findComponent (rawSelector) { + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'findComponent'); if (!this.vm && !this.isFunctionalComponent) { throwError( @@ -10588,6 +10638,8 @@ * the provided selector. */ Wrapper.prototype.findAll = function findAll (rawSelector) { + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'findAll'); if (selector.type !== DOM_SELECTOR) { warnDeprecated( @@ -10603,6 +10655,8 @@ * the provided selector. */ Wrapper.prototype.findAllComponents = function findAllComponents (rawSelector) { + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'findAll'); if (!this.vm) { throwError( @@ -10638,6 +10692,8 @@ * Returns HTML of element as a string */ Wrapper.prototype.html = function html () { + this.__warnIfDestroyed(); + return pretty(this.element.outerHTML) }; @@ -10645,6 +10701,8 @@ * Checks if node matches selector or component definition */ Wrapper.prototype.is = function is (rawSelector) { + this.__warnIfDestroyed(); + var selector = getSelector(rawSelector, 'is'); if (selector.type === DOM_SELECTOR) { @@ -10671,6 +10729,8 @@ 'Consider a custom matcher such as those provided in jest-dom: https://github.com/testing-library/jest-dom#tobeempty. ' + 'When using with findComponent, access the DOM element with findComponent(Comp).element' ); + this.__warnIfDestroyed(); + if (!this.vnode) { return this.element.innerHTML === '' } @@ -10695,6 +10755,8 @@ * Checks if node is visible */ Wrapper.prototype.isVisible = function isVisible () { + this.__warnIfDestroyed(); + return isElementVisible(this.element) }; @@ -10704,6 +10766,8 @@ */ Wrapper.prototype.isVueInstance = function isVueInstance () { warnDeprecated("isVueInstance"); + this.__warnIfDestroyed(); + return !!this.vm }; @@ -10713,6 +10777,7 @@ */ Wrapper.prototype.name = function name () { warnDeprecated("name"); + this.__warnIfDestroyed(); if (this.vm) { return ( @@ -10738,6 +10803,7 @@ var this$1 = this; warnDeprecated("overview"); + this.__warnIfDestroyed(); if (!this.vm) { throwError("wrapper.overview() can only be called on a Vue instance"); @@ -10824,6 +10890,7 @@ if (!this.vm) { throwError('wrapper.props() must be called on a Vue instance'); } + this.__warnIfDestroyed(); var props = {}; var keys = this.vm && this.vm.$options._propKeys; @@ -10850,6 +10917,8 @@ Wrapper.prototype.setChecked = function setChecked (checked) { if ( checked === void 0 ) checked = true; + this.__warnIfDestroyed(); + if (typeof checked !== 'boolean') { throwError('wrapper.setChecked() must be passed a boolean'); } @@ -10899,6 +10968,8 @@ * @deprecated */ Wrapper.prototype.setSelected = function setSelected () { + this.__warnIfDestroyed(); + var tagName = this.element.tagName; if (tagName === 'SELECT') { @@ -10944,6 +11015,8 @@ throwError("wrapper.setData() can only be called on a Vue instance"); } + this.__warnIfDestroyed(); + recursivelySetData(this.vm, this.vm, data); return nextTick() }; @@ -10963,6 +11036,8 @@ if (!this.vm) { throwError("wrapper.setMethods() can only be called on a Vue instance"); } + this.__warnIfDestroyed(); + Object.keys(methods).forEach(function (key) { // $FlowIgnore : Problem with possibly null this.vm this$1.vm[key] = methods[key]; @@ -10992,6 +11067,7 @@ if (!this.vm) { throwError("wrapper.setProps() can only be called on a Vue instance"); } + this.__warnIfDestroyed(); // Save the original "silent" config so that we can directly mutate props var originalConfig = Vue__default['default'].config.silent; @@ -11065,6 +11141,7 @@ var tagName = this.element.tagName; // $FlowIgnore var type = this.attributes().type; + this.__warnIfDestroyed(); if (tagName === 'OPTION') { throwError( @@ -11117,6 +11194,8 @@ * Return text of wrapper element */ Wrapper.prototype.text = function text () { + this.__warnIfDestroyed(); + return this.element.textContent.trim() }; @@ -11126,6 +11205,8 @@ Wrapper.prototype.trigger = function trigger (type, options) { if ( options === void 0 ) options = {}; + this.__warnIfDestroyed(); + if (typeof type !== 'string') { throwError('wrapper.trigger() must be passed a string'); } @@ -11224,9 +11305,9 @@ hook(function () { wrapperInstances.forEach(function (wrapper) { // skip child wrappers created by wrapper.find() - if (wrapper.selector) { return } - - wrapper.destroy(); + if (wrapper.vm || wrapper.isFunctionalComponent) { + wrapper.destroy(); + } }); wrapperInstances.length = 0; @@ -13930,7 +14011,9 @@ var parentVm = createInstance(component, mergedOptions, _Vue); var el = - options.attachTo || (options.attachToDocument ? createElement() : undefined); + options.attachToDocument || options.attachTo instanceof HTMLBodyElement + ? createElement() + : options.attachTo; var vm = parentVm.$mount(el); component._Ctor = {}; From 27b9bfd3d5a295acc74efb2cc6b1e57e416d2ae7 Mon Sep 17 00:00:00 2001 From: Lachlan Miller Date: Sun, 1 Nov 2020 10:36:15 +1000 Subject: [PATCH 18/19] chore: revert version so lerna can handle it --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 9821206c6..0c839ade0 100644 --- a/lerna.json +++ b/lerna.json @@ -5,5 +5,5 @@ ], "npmClient": "yarn", "useWorkspaces": true, - "version": "1.1.1" + "version": "1.1.0" } From d7916ce82062dd0c920c8a0aebaaaa527e3b98ff Mon Sep 17 00:00:00 2001 From: Lachlan Miller Date: Sun, 1 Nov 2020 10:36:34 +1000 Subject: [PATCH 19/19] v1.1.1 --- lerna.json | 2 +- packages/create-instance/package.json | 2 +- packages/server-test-utils/package.json | 2 +- packages/shared/package.json | 2 +- packages/test-utils/package.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lerna.json b/lerna.json index 0c839ade0..9821206c6 100644 --- a/lerna.json +++ b/lerna.json @@ -5,5 +5,5 @@ ], "npmClient": "yarn", "useWorkspaces": true, - "version": "1.1.0" + "version": "1.1.1" } diff --git a/packages/create-instance/package.json b/packages/create-instance/package.json index 6454a4866..9a8d8aefb 100644 --- a/packages/create-instance/package.json +++ b/packages/create-instance/package.json @@ -1,6 +1,6 @@ { "name": "create-instance", - "version": "1.0.5", + "version": "1.1.1", "main": "create-instance.js", "private": true } diff --git a/packages/server-test-utils/package.json b/packages/server-test-utils/package.json index 814a4f9a2..42282b9ba 100644 --- a/packages/server-test-utils/package.json +++ b/packages/server-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@vue/server-test-utils", - "version": "1.1.0", + "version": "1.1.1", "description": "Utilities for testing Vue components.", "main": "dist/vue-server-test-utils.js", "types": "types/index.d.ts", diff --git a/packages/shared/package.json b/packages/shared/package.json index 804f4eb78..7506372f7 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "shared", - "version": "1.1.0", + "version": "1.1.1", "private": true, "peerDependencies": { "vue": "2.x", diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index e2ac2a520..f1efa64b1 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@vue/test-utils", - "version": "1.1.0", + "version": "1.1.1", "description": "Utilities for testing Vue components.", "main": "dist/vue-test-utils.js", "types": "types/index.d.ts",