├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── compats │ ├── wrapper-attributes.js │ ├── wrapper-find-all.js │ └── wrapper-find.js ├── index.js └── utils │ ├── create-local-vue.js │ └── normalize-mount-args.js └── tests ├── flags ├── export │ ├── export-create-local-vue.test.js │ └── export-create-wrapper.test.js ├── global-stubs.test.js ├── mount-args │ ├── context │ │ ├── mount-args-context-attrs.test.js │ │ ├── mount-args-context-children.test.js │ │ ├── mount-args-context-class.test.js │ │ ├── mount-args-context-on.test.js │ │ └── mount-args-context-props.test.js │ ├── mount-args-components.test.js │ ├── mount-args-directives.test.js │ ├── mount-args-listeners.test.js │ ├── mount-args-mocks.test.js │ ├── mount-args-provide.test.js │ ├── mount-args-scoped-slots-this.test.js │ ├── mount-args-scoped-slots.test.js │ ├── mount-args-stubs.test.js │ └── mount-args.test.js └── wrapper │ ├── wrapper-attributes-disabled.test.js │ ├── wrapper-attributes-value.test.js │ ├── wrapper-destroy.test.js │ ├── wrapper-do-not-include-native-events-in-emitted.test.js │ ├── wrapper-find-all.test.js │ ├── wrapper-find-by-css-selector-returns-components.test.js │ ├── wrapper-find-component-by-ref-returns-dom.test.js │ ├── wrapper-set-value-does-not-trigger-change.test.js │ └── wrapper-vue-set-value-uses-dom.test.js ├── global-behavior.test.js └── helpers.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.0.5 2 | 3 | - Prevent crash if directives is not defined 4 | 5 | # 0.0.4 6 | 7 | - Altered behavior of MOUNT_ARGS_DIRECTIVES to pass directives also to stubs 8 | See https://github.com/vuejs/test-utils/pull/1804 for details 9 | 10 | # 0.0.3 11 | 12 | - Added flags: 13 | - MOUNT_ARGS_COMPONENTS 14 | - MOUNT_ARGS_DIRECTIVES 15 | - WRAPPER_DO_NOT_INCLUDE_HOOK_EVENTS_IN_EMITTED 16 | - WRAPPER_FIND_COMPONENT_BY_REF_RETURNS_DOM 17 | - WRAPPER_SET_VALUE_DOES_NOT_TRIGGER_CHANGE 18 | - WRAPPER_VUE_SET_VALUE_USES_DOM 19 | 20 | # 0.0.2 21 | 22 | - Added flags: 23 | - FIND_BY_CSS_SELECTOR_RETURNS_COMPONENTS 24 | 25 | ## 0.0.1 26 | 27 | - Initial release. 28 | - Flags available: 29 | - EXPORT_CREATE_LOCAL_VUE 30 | - EXPORT_CREATE_WRAPPER 31 | - GLOBAL_STUBS 32 | - MOUNT_ARGS_CONTEXT_ATTRS 33 | - MOUNT_ARGS_CONTEXT_CHILDREN 34 | - MOUNT_ARGS_CONTEXT_CLASS 35 | - MOUNT_ARGS_CONTEXT_ON 36 | - MOUNT_ARGS_CONTEXT_PROPS 37 | - MOUNT_ARGS_LISTENERS 38 | - MOUNT_ARGS_MOCKS 39 | - MOUNT_ARGS_PROVIDE 40 | - MOUNT_ARGS_SCOPED_SLOTS 41 | - MOUNT_ARGS_SCOPED_SLOTS_THIS 42 | - MOUNT_ARGS_STUBS 43 | - WRAPPER_ATTRIBUTES_DISABLED 44 | - WRAPPER_ATTRIBUTES_VALUE 45 | - WRAPPER_DESTROY 46 | - WRAPPER_DO_NOT_INCLUDE_NATIVE_EVENTS_IN_EMITTED 47 | - WRAPPER_FIND_ALL 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Illya Klymov and others 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-test-utils-compat [![npm badge](https://img.shields.io/npm/v/vue-test-utils-compat)](https://npmjs.com/vue-test-utils-compat) 2 | 3 | Upgrade your Vue components first, deal with tests later 4 | 5 | --- 6 | 7 | A migration tool to help you with migration from Vue 2 to Vue3 8 | 9 | Vue 3 introduced [migration build](https://v3.vuejs.org/guide/migration/introduction.html#migration-build) to help teams with gradual migration. [vue-test-utils-next](https://github.com/vuejs/vue-test-utils-next) is playing well with migration build, but there are many differences between [v1 vue-test-utils](https://github.com/vuejs/vue-test-utils) and [v2](https://github.com/vuejs/vue-test-utils-next). 10 | 11 | This package provides a compatibility layer, which allows you to run old v1 test suites on vue-test-utils v2 and Vue 3 12 | 13 | ## Table of Contents 14 | 15 | - ⏩ [Quickstart](#-quickstart) 16 | - ✔️ [Upgrade workflow](#%EF%B8%8F-upgrade-workflow) 17 | - 🌐 [Global API](#-global-api) 18 | - 🏁 Compatibility flags 19 | 20 | - [EXPORT_CREATE_LOCAL_VUE](#export_create_local_vue) 21 | - [EXPORT_CREATE_WRAPPER](#export_create_wrapper) 22 | - [MOUNT_ARGS_COMPONENTS](#mount_args_components-added-in-v003) 23 | - [MOUNT_ARGS_CONTEXT\_\*](#mount_args_context_) 24 | - [MOUNT_ARGS_DIRECTIVES](#mount_args_directives-added-in-v003) 25 | - [MOUNT_ARGS_LISTENERS](#mount_args_listeners) 26 | - [MOUNT_ARGS_MOCKS](#mount_args_mocks) 27 | - [MOUNT_ARGS_PROVIDE](#mount_args_provide) 28 | - [MOUNT_ARGS_SCOPED_SLOTS](#mount_args_scoped_slots) 29 | - [MOUNT_ARGS_SCOPED_SLOTS_THIS](#mount_args_scoped_slots_this) 30 | - [MOUNT_ARGS_STUBS](#mount_args_stubs) 31 | - [WRAPPER_ATTRIBUTES_DISABLED](#wrapper_attributes_disabled) 32 | - [WRAPPER_ATTRIBUTES_VALUE](#wrapper_attributes_value) 33 | - [WRAPPER_DESTROY](#wrapper_destroy) 34 | - [WRAPPER_DO_NOT_INCLUDE_NATIVE_EVENTS_IN_EMITTED](#wrapper_do_not_include_native_events_in_emitted) 35 | - [WRAPPER_DO_NOT_INCLUDE_HOOK_EVENTS_IN_EMITTED](#wrapper_do_not_include_hook_events_in_emitted-added-in-v003) 36 | - [WRAPPER_FIND_ALL](#wrapper_find_all) 37 | - [WRAPPER_FIND_BY_CSS_SELECTOR_RETURNS_COMPONENTS](#wrapper_find_by_css_selector_returns_components-added-in-v002) 38 | - [WRAPPER_FIND_COMPONENT_BY_REF_RETURNS_DOM](#wrapper_find_component_by_ref_returns_dom-added-in-v003) 39 | - [WRAPPER_SET_VALUE_DOES_NOT_TRIGGER_CHANGE](#wrapper_set_value_does_not_trigger_change-added-in-v003) 40 | - [WRAPPER_VUE_SET_VALUE_USES_DOM](#wrapper_vue_set_value_uses_dom-added-in-v003) 41 | 42 | - ⚠️ [Known issues](#known-issues) 43 | 44 | ## ⏩ Quickstart 45 | 46 | ```bash 47 | npm install --save-dev vue-test-utils-compat 48 | ``` 49 | 50 | **Vue 3**: 51 | 52 | ```js 53 | const VueTestUtils = require('vue@/test-utils'); 54 | const { h } = require('vue'); 55 | const { 56 | installCompat as installVTUCompat, 57 | fullCompatConfig 58 | } = require('vue-test-utils-compat'); 59 | 60 | installVTUCompat(VueTestUtils, fullCompatConfig, h) 61 | ``` 62 | 63 | **Vue 3 migration build** (in Vue 2 mode): 64 | 65 | ```js 66 | const VueTestUtils = require("vue@/test-utils"); 67 | const Vue = require("vue"); 68 | let compatH; 69 | Vue.createApp({ 70 | compatConfig: { 71 | MODE: 3, 72 | RENDER_FUNCTION: "suppress-warning", 73 | }, 74 | render(h) { 75 | compatH = h; 76 | }, 77 | }).mount(document.createElement("div")); 78 | installVTUCompat(VueTestUtils, fullCompatConfig, compatH); 79 | ``` 80 | 81 | ## ✔️ Upgrade workflow 82 | 83 | This upgrade workflow is demonstrated in [bootstrap-vue](https://github.com/bootstrap-vue/bootstrap-vue/compare/dev...xanf:vue3-compat-build) migration to Vue 3. 84 | 85 | 0. **Before you start** make sure you are using **latest** version of `@vue/test-utils` v1 in your project and fix all deprecations reported 86 | 1. Follow [Vue3 migration build upgrade workflow](https://v3.vuejs.org/guide/migration/migration-build.html#upgrade-workflow) to set up Vue build infrastructure [[example commit]](https://github.com/bootstrap-vue/bootstrap-vue/commit/b7a350c270da1e51f6288e7ffaec425a80c790ff) 87 | 1. Make sure your test infrastructure uses `@vue/compat` as `vue` alias. 88 | Example (using jest - `jest.config.js`): 89 | 90 | ```js 91 | module.exports = { 92 | // ... 93 | moduleNameMapper: { 94 | "^vue$": "@vue/compat", 95 | }, 96 | // ... 97 | }; 98 | ``` 99 | 100 | > Hint: it might be a good idea to set up your environment to use Vue 2 or Vue 3 conditionally. It greatly simplifies the migration process. 101 | 102 | [[example commit]](https://github.com/bootstrap-vue/bootstrap-vue/commit/ff82fd173c7b7d3939ee479e50112f689cf31a8f) 103 | 104 | 3. Install `vue-test-utils-compat`. Please take a note that your test environment might reset modules between files (`jest` do this), so make sure to do this in the proper place (we're using `setupFilesAfterEnv` in jest): 105 | 106 | ```js 107 | const compatH = new Vue({}).$createElement; 108 | installVTUCompat(VTU, fullCompatConfig, compatH); 109 | ``` 110 | 111 | 4. Run your tests and fix failing ones. Typical failures usually include: 112 | 113 | - using private Vue API (like `__vue__`) [[example commit]](https://github.com/bootstrap-vue/bootstrap-vue/commit/f068bed68c99ee5f633059e8f098ed4ffced72d2) 114 | - wrong usage of `find` vs. `findComponent` [[example commit]](https://github.com/bootstrap-vue/bootstrap-vue/commit/5a6de07225b8963e0b8d5fdd7f3cf08123240cb0) 115 | - snapshots (they might differ between Vue 2 and Vue 3) 116 | 117 | 5. At this point, you (theoretically) have a green suite and can start working on upgrading your code to Vue 3 118 | 6. Replace `fullCompatConfig` from step 3 with the detailed list of compat flags. You can copy-paste the full list of flags below or take a look at the source code to figure all flags: 119 | 120 | ```js 121 | const compatConfig = { 122 | EXPORT_CREATE_LOCAL_VUE: true, 123 | EXPORT_CREATE_WRAPPER: true, 124 | 125 | GLOBAL_STUBS: true, 126 | 127 | MOUNT_ARGS_CONTEXT_ATTRS: true, 128 | MOUNT_ARGS_CONTEXT_CHILDREN: true, 129 | MOUNT_ARGS_CONTEXT_CLASS: true, 130 | MOUNT_ARGS_CONTEXT_ON: true, 131 | MOUNT_ARGS_CONTEXT_PROPS: true, 132 | MOUNT_ARGS_LISTENERS: true, 133 | MOUNT_ARGS_MOCKS: true, 134 | MOUNT_ARGS_PROVIDE: true, 135 | MOUNT_ARGS_SCOPED_SLOTS: true, 136 | MOUNT_ARGS_SCOPED_SLOTS_THIS: true, 137 | MOUNT_ARGS_STUBS: true, 138 | 139 | WRAPPER_ATTRIBUTES_DISABLED: true, 140 | WRAPPER_ATTRIBUTES_VALUE: true, 141 | WRAPPER_DESTROY: true, 142 | WRAPPER_DO_NOT_INCLUDE_NATIVE_EVENTS_IN_EMITTED: true, 143 | WRAPPER_FIND_ALL: true, 144 | }; 145 | ``` 146 | 147 | 7. 🔁 Turn off one compatibility flag. Fix failing tests. Repeat. 148 | 8. As soon as you turn off the last compatibility flag - throw away and uninstall this package. You are awesome! 🎉 149 | 150 | ## 🌐 Global API 151 | 152 | - `installCompat(VueTestUtilsModule, compatConfig, vueH)` 153 | - `VueTestUtilsModule` - module, which will be patched 154 | - `compatConfig: Record` - list of compatibility flags 155 | - `vueH` - function which will be used to create Vue VNodes. Required only if `MOUNT_ARGS_SCOPED_SLOTS_THIS` compatibility flag is used, could be omitted otherwise 156 | - `compatFlags` - object with all available compatibility 157 | - `fullCompatConfig` - config object with all compatibility flags enabled 158 | 159 | ## 🏁 Compatibility flags 160 | 161 | Tests cover all compatibility flags. If the flag description is unclear, check the relevant test in `tests` folder. 162 | 163 | ### EXPORT_CREATE_LOCAL_VUE 164 | 165 | Adds `createLocalVue` to `@vue/test-utils` module and support for `{ localVue }` mount option. 166 | 167 | > ⚠️ `localVue` provides `.extend`, which is no-op operation. It is sufficient for most of the code but might require special handling 168 | 169 | ➡️ Migration strategy: [available in @vue/test-utils v2 docs](https://next.vue-test-utils.vuejs.org/migration/#no-more-createlocalvue) 170 | 171 | ### EXPORT_CREATE_WRAPPER 172 | 173 | Adds `createWrapper` to `@vue/test-utils` module 174 | 175 | ➡️ Migration strategy: replace `createWrapper` with `new DOMWrapper()`, `new VueWrapper()` which are available as exports in `@vue/test-utils` v2 176 | 177 | ### MOUNT_ARGS_COMPONENTS (added in v0.0.3) 178 | 179 | Enable support for `components` field in `mount` args of `@vue/test-utils` 180 | 181 | ➡️ Migration strategy: Move `components` mount arg to `global.components` 182 | 183 | ### MOUNT_ARGS_CONTEXT\_\* 184 | 185 | Flags: 186 | 187 | - `MOUNT_ARGS_CONTEXT_ATTRS` 188 | - `MOUNT_ARGS_CONTEXT_CHILDREN` 189 | - `MOUNT_ARGS_CONTEXT_CLASS` 190 | - `MOUNT_ARGS_CONTEXT_ON` 191 | - `MOUNT_ARGS_CONTEXT_PROPS` 192 | 193 | Enable support for `context` field in `mount` args of `@vue/test-utils` (used to test functional components) 194 | 195 | > ⚠️ `MOUNT_ARGS_CONTEXT_CHILDREN` converts `context.children` to the default slot of the component. It is not a complete implementation of old `context.children` behavior but should be sufficient for most cases. 196 | 197 | ➡️ Migration strategy: rewrite your mount args as follows: 198 | 199 | - `context.props`, `context.attrs`, and `context.class` go directly to `props` 200 | - `children` are replaced with `slots.default` 201 | - `context.on` become corresponding `props`: (`click` → `onClick`, etc.) 202 | 203 | ### MOUNT_ARGS_DIRECTIVES (added in v0.0.3) 204 | 205 | Enable support for `components` field in `mount` args of `@vue/test-utils` 206 | 207 | ➡️ Migration strategy: Move `directives` mount arg to `global.directives` 208 | 209 | ### MOUNT_ARGS_LISTENERS 210 | 211 | Allow passing `{ listeners }` field in `mount` arguments 212 | 213 | ➡️ Migration strategy: replace `listeners` with `props`: (`click` → `onClick`, etc.) 214 | 215 | ### MOUNT_ARGS_MOCKS 216 | 217 | Enable passing `mocks` to the component from `mount` arguments 218 | 219 | ➡️ Migration strategy: move `mocks` mount arg to `global.mocks` 220 | 221 | ### MOUNT_ARGS_PROVIDE 222 | 223 | Allow passing relevant `provide` to the component 224 | 225 | > ⚠️ `@vue/test-utils` v2 does not support passing `provide` as function. It means that your `provide()` function might be invoked earlier than you think 226 | 227 | ➡️ Migration strategy: move `provide` mount arg to `global.provide`. If your `provide` is a function - replace it with an object. 228 | 229 | ### MOUNT_ARGS_SCOPED_SLOTS 230 | 231 | Enable `scopedSlots` support in mount args 232 | 233 | ➡️ Migration strategy: merge `scopedSlots` mount arg to `slots`. If your scoped slot is using raw string - wrap it with `` 234 | 235 | ### MOUNT_ARGS_SCOPED_SLOTS_THIS 236 | 237 | Allows `scopedSlots` declared as functions to receive `this` which contains `$createElement` and `$set` 238 | 239 | > ⚠️⚠️⚠️ Requires `MOUNT_ARGS_SCOPED_SLOTS` to be enabled and third argument (`vueH` ) a for `installCompat` call 240 | 241 | > ️⚠️`$createElement` provided by this flag is not context-aware and will not be able to render components as a string. Refer to [Vue docs](https://v3.vuejs.org/guide/migration/render-function-api.html#registered-component) for details 242 | 243 | ➡️ Migration strategy: ❌ rewrite such slots in your tests 244 | 245 | ### MOUNT_ARGS_STUBS 246 | 247 | Enable `stubs` to be passed to mount arguments 248 | 249 | ➡️ Migration strategy: move `stubs` mount arg to `global.stubs` 250 | 251 | ### WRAPPER_ATTRIBUTES_DISABLED 252 | 253 | Adds special handling when retrieving the `disabled` attribute on `wrapper`. Previously Vue always normalized such values ([Vue 3 migration guide](https://v3.vuejs.org/guide/migration/attribute-coercion.html) has more details on this) 254 | 255 | ➡️ Migration strategy: update your `.attributes("disabled")` assertions to relevant values 256 | 257 | ### WRAPPER_ATTRIBUTES_VALUE 258 | 259 | Adds special handling when retrieving the `value` attribute on `wrapper`. Previously Vue always set `value` as DOM node attribute, which is no more the case 260 | 261 | ➡️ Migration strategy: ❌ rewrite your value retrieval in another way 262 | 263 | ### WRAPPER_DESTROY 264 | 265 | Enables `wrapper.destroy` calls and `enableAutoDestroy` calls 266 | 267 | ➡️ Migration strategy: replace all `wrapper.destroy` calls with `wrapper.unmount` and `enableAutoDestroy` with `enableAutoUnmount 268 | 269 | ### WRAPPER_DO_NOT_INCLUDE_NATIVE_EVENTS_IN_EMITTED 270 | 271 | Makes sure that native events will not be captured in `.emitted()` 272 | 273 | ➡️ Migration strategy: rewrite your event-related assertions to take into account that native events are also captured, or (preferred) use [emits](https://v3.vuejs.org/guide/migration/emits-option.html#overview) option on your components 274 | 275 | ### WRAPPER_DO_NOT_INCLUDE_HOOK_EVENTS_IN_EMITTED (added in v0.0.3) 276 | 277 | Makes sure that `hook:` events (which happen when using `@vue/compat`) will not be captured in `.emitted()` 278 | 279 | ➡️ Migration strategy: rewrite your event-related assertions to take into account that such events are also captured, or just upgrade to Vue 3 build without compat 280 | 281 | ### WRAPPER_FIND_ALL 282 | 283 | Implements old behavior of `.findAll` / `.findAllComponents` when results were wrapped with special object with `.wrappers` field and various methods (`.at`, `.filter`, `.trigger`, etc.) 284 | 285 | ➡️ Migration strategy: rewrite your tests, assuming that `.findAll` and `.findAllComponents` return a simple array instead 286 | 287 | ### WRAPPER_FIND_BY_CSS_SELECTOR_RETURNS_COMPONENTS (added in v0.0.2) 288 | 289 | Implements old behavior of `.find` / `.findAll` when results will be Vue components if they matches. So potentially, you can receive mixed array of DOM/Vue wrappers when using `.findAll` with this compat flag 290 | 291 | ➡️ Migration strategy: replace `.find` with `.findComponent` and `.findAll` with `.findAllComponents` where appropriate. Please take a note that if your tests rely on having a mixed array of DOM/Vue wrappers - you need to rewrite them 292 | 293 | ### WRAPPER_FIND_COMPONENT_BY_REF_RETURNS_DOM (added in v0.0.3) 294 | 295 | Implements old behavior when using `.findComponent` with `ref` will return DOM wrapper if ref is pointing to one. 296 | 297 | ➡️ Migration strategy: replace `.findComponent` with `.find` by ref (when https://github.com/vuejs/vue-test-utils-next/pull/1110 will be merged) 298 | 299 | ### WRAPPER_SET_VALUE_DOES_NOT_TRIGGER_CHANGE (added in v0.0.3) 300 | 301 | Implements old behavior when using `.trigger` on DOM Wrapper did not trigger `change` event, so you should trigger it manually (important for lazy v-models) 302 | 303 | ➡️ Migration strategy: rewrite relevant tests 304 | 305 | ### WRAPPER_VUE_SET_VALUE_USES_DOM (added in v0.0.3) 306 | 307 | Implements old VTU v1 behavior when using `.setValue` on Vue component actually used same logic, as setting value on DOM node (checking element type of Vue component, etc.) 308 | 309 | ➡️ Migration strategy: fix your components to use new `setValue` (which respects `v-model`) or rewrite relevant tests 310 | 311 | ## Known issues 312 | 313 | This package monkey-patches `@vue/test-utils` package. Depending on your setup this might not work (for example you are using real imports). In that case you can create a mutable wrapper around VTU and replace all your imports from `@vue/test-utils` to this helper module: 314 | 315 | ```js 316 | import * as VueTestUtils from '@vue/test-utils'; 317 | import { h } from 'vue'; 318 | import { installCompat, fullCompatConfig } from `@vue/test-utils/compat` 319 | 320 | const PatchedVTU = { ...VueTestUtils }; 321 | installCompat(PatchedVTU, fullCompatConfig, h); 322 | export PatchedVTU; 323 | ``` 324 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | clearMocks: true, 3 | testEnvironment: "jsdom", 4 | transform: {}, 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-test-utils-compat", 3 | "version": "0.0.5", 4 | "description": "Compat layer for @vue/test-utils@2.x", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest" 8 | }, 9 | "type": "module", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/xanf/vue-test-utils-compat.git" 13 | }, 14 | "keywords": [ 15 | "vue-test-utils", 16 | "vue-compat", 17 | "vue" 18 | ], 19 | "eslintConfig": { 20 | "env": { 21 | "node": true, 22 | "browser": true 23 | }, 24 | "extends": [ 25 | "airbnb-base", 26 | "prettier" 27 | ], 28 | "parserOptions": { 29 | "ecmaVersion": 2021, 30 | "sourceType": "module" 31 | }, 32 | "rules": { 33 | "import/prefer-default-export": 0, 34 | "import/extensions": [ 35 | "error", 36 | "always" 37 | ] 38 | } 39 | }, 40 | "prettier": { 41 | "printWidth": 120 42 | }, 43 | "author": "Illya Klymov ", 44 | "license": "MIT", 45 | "simple-git-hooks": { 46 | "pre-commit": "npx lint-staged" 47 | }, 48 | "lint-staged": { 49 | "**/*.js": [ 50 | "eslint", 51 | "prettier -c" 52 | ], 53 | "**/*.md": [ 54 | "prettier -c" 55 | ] 56 | }, 57 | "devDependencies": { 58 | "@jest/globals": "^27.3.1", 59 | "@vue/compiler-dom": "^3.2.23", 60 | "@vue/test-utils": "^2.0.0-rc.17", 61 | "cross-env": "^7.0.3", 62 | "eslint": "^8.2.0", 63 | "eslint-config-airbnb-base": "^15.0.0", 64 | "eslint-config-prettier": "^8.3.0", 65 | "eslint-plugin-import": "^2.25.3", 66 | "jest": "^27.3.1", 67 | "lint-staged": "^12.0.2", 68 | "prettier": "^2.5.0", 69 | "simple-git-hooks": "^2.7.0", 70 | "vue": "^3.2.23" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/compats/wrapper-attributes.js: -------------------------------------------------------------------------------- 1 | export function install(VTU, compatConfig) { 2 | [VTU.config.plugins.DOMWrapper, VTU.config.plugins.VueWrapper].forEach((pluginHost) => 3 | pluginHost.install((wrapper) => { 4 | const { attributes } = wrapper; 5 | return { 6 | attributes(attr) { 7 | const originalAttributes = attributes.call(wrapper, attr); 8 | 9 | if (attr === "value" && compatConfig.WRAPPER_ATTRIBUTES_VALUE) { 10 | return wrapper.element.value; 11 | } 12 | 13 | if (attr === "disabled" && compatConfig.WRAPPER_ATTRIBUTES_DISABLED) { 14 | return typeof originalAttributes === "string" ? "disabled" : originalAttributes; 15 | } 16 | 17 | if (attr) { 18 | return originalAttributes; 19 | } 20 | 21 | const normalizedDisabled = originalAttributes.disabled === "" ? "disabled" : originalAttributes.disabled; 22 | const normalizedAttributes = { ...originalAttributes }; 23 | if (compatConfig.WRAPPER_ATTRIBUTES_VALUE && "value" in wrapper.element) { 24 | normalizedAttributes.value = wrapper.element.value; 25 | } 26 | if (compatConfig.WRAPPER_ATTRIBUTES_DISABLED && "disabled" in originalAttributes) { 27 | normalizedAttributes.disabled = normalizedDisabled; 28 | } 29 | 30 | return normalizedAttributes; 31 | }, 32 | }; 33 | }) 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/compats/wrapper-find-all.js: -------------------------------------------------------------------------------- 1 | function wrapResults(results) { 2 | return { 3 | ...results, 4 | wrappers: results, 5 | at(i) { 6 | return results[i]; 7 | }, 8 | 9 | filter(condition) { 10 | return wrapResults(results.filter(condition)); 11 | }, 12 | 13 | trigger(...params) { 14 | results.forEach((w) => w.trigger(...params)); 15 | }, 16 | 17 | exists() { 18 | return results.length > 0 && results.every((x) => x.exists()); 19 | }, 20 | 21 | get length() { 22 | return results.length; 23 | }, 24 | }; 25 | } 26 | 27 | export function install(VTU, config) { 28 | [VTU.config.plugins.DOMWrapper, VTU.config.plugins.VueWrapper].forEach((pluginHost) => 29 | pluginHost.install((wrapper) => { 30 | const { findAll, findAllComponents } = wrapper; 31 | return { 32 | findAll: (...args) => { 33 | const results = findAll.call(wrapper, ...args); 34 | 35 | if (config.WRAPPER_FIND_BY_CSS_SELECTOR_RETURNS_COMPONENTS) { 36 | const componentResults = findAllComponents.call(wrapper, ...args); 37 | results.forEach((v, i) => { 38 | const matchingComponent = componentResults.find((w) => w.element === v.element); 39 | if (matchingComponent) { 40 | results[i] = matchingComponent; 41 | } 42 | }); 43 | } 44 | 45 | return config.WRAPPER_FIND_ALL ? wrapResults(results) : results; 46 | }, 47 | findAllComponents: (...args) => { 48 | const results = findAllComponents.call(wrapper, ...args); 49 | return config.WRAPPER_FIND_ALL ? wrapResults(results) : results; 50 | }, 51 | }; 52 | }) 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /src/compats/wrapper-find.js: -------------------------------------------------------------------------------- 1 | export function install(VTU, config) { 2 | [VTU.config.plugins.DOMWrapper, VTU.config.plugins.VueWrapper].forEach((pluginHost) => 3 | pluginHost.install((wrapper) => { 4 | const { find, findAllComponents, findComponent } = wrapper; 5 | return { 6 | find: (...args) => { 7 | const result = find.call(wrapper, ...args); 8 | if (!result.exists()) { 9 | return result; 10 | } 11 | 12 | if (config.WRAPPER_FIND_BY_CSS_SELECTOR_RETURNS_COMPONENTS) { 13 | const componentResults = args[0].ref 14 | ? [findComponent.call(wrapper, ...args)] 15 | : findAllComponents.call(wrapper, ...args); 16 | 17 | const matchingComponent = componentResults.find((w) => w.exists() && w.element === result.element); 18 | if (matchingComponent) { 19 | return matchingComponent; 20 | } 21 | } 22 | return result; 23 | }, 24 | findComponent(selector) { 25 | const result = findComponent.call(wrapper, selector); 26 | if ( 27 | !result.exists() && 28 | selector.ref && 29 | config.WRAPPER_FIND_COMPONENT_BY_REF_RETURNS_DOM && 30 | wrapper.vm.$refs?.[selector.ref] 31 | ) { 32 | return new VTU.DOMWrapper(wrapper.vm.$refs?.[selector.ref]); 33 | } 34 | return result; 35 | }, 36 | }; 37 | }) 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { install as installWrapperAttributesCompat } from "./compats/wrapper-attributes.js"; 2 | import { install as installWrapperFindAllCompat } from "./compats/wrapper-find-all.js"; 3 | import { install as installWrapperFindCompat } from "./compats/wrapper-find.js"; 4 | import { createLocalVue } from "./utils/create-local-vue.js"; 5 | import { normalizeMountArgs } from "./utils/normalize-mount-args.js"; 6 | 7 | const installedConfigs = new WeakMap(); 8 | 9 | function needsNormalization(config) { 10 | return ( 11 | config.EXPORT_CREATE_LOCAL_VUE || Object.entries(config).some(([k, v]) => k.startsWith("MOUNT_ARGS") && Boolean(v)) 12 | ); 13 | } 14 | 15 | export const compatFlags = Object.freeze({ 16 | EXPORT_CREATE_LOCAL_VUE: "EXPORT_CREATE_LOCAL_VUE", 17 | EXPORT_CREATE_WRAPPER: "EXPORT_CREATE_WRAPPER", 18 | 19 | GLOBAL_STUBS: "GLOBAL_STUBS", 20 | 21 | MOUNT_ARGS_COMPONENTS: "MOUNT_ARGS_COMPONENTS", 22 | MOUNT_ARGS_CONTEXT_ATTRS: "MOUNT_ARGS_CONTEXT_ATTRS", 23 | MOUNT_ARGS_CONTEXT_CHILDREN: "MOUNT_ARGS_CONTEXT_CHILDREN", 24 | MOUNT_ARGS_CONTEXT_CLASS: "MOUNT_ARGS_CONTEXT_CLASS", 25 | MOUNT_ARGS_CONTEXT_ON: "MOUNT_ARGS_CONTEXT_ON", 26 | MOUNT_ARGS_CONTEXT_PROPS: "MOUNT_ARGS_CONTEXT_PROPS", 27 | MOUNT_ARGS_DIRECTIVES: "MOUNT_ARGS_DIRECTIVES", 28 | MOUNT_ARGS_LISTENERS: "MOUNT_ARGS_LISTENERS", 29 | MOUNT_ARGS_MOCKS: "MOUNT_ARGS_MOCKS", 30 | MOUNT_ARGS_PROVIDE: "MOUNT_ARGS_PROVIDE", 31 | MOUNT_ARGS_SCOPED_SLOTS: "MOUNT_ARGS_SCOPED_SLOTS", 32 | MOUNT_ARGS_SCOPED_SLOTS_THIS: "MOUNT_ARGS_SCOPED_SLOTS_THIS", 33 | MOUNT_ARGS_STUBS: "MOUNT_ARGS_STUBS", 34 | 35 | WRAPPER_ATTRIBUTES_DISABLED: "WRAPPER_ATTRIBUTES_DISABLED", 36 | WRAPPER_ATTRIBUTES_VALUE: "WRAPPER_ATTRIBUTES_VALUE", 37 | WRAPPER_DESTROY: "WRAPPER_DESTROY", 38 | WRAPPER_DO_NOT_INCLUDE_NATIVE_EVENTS_IN_EMITTED: "WRAPPER_DO_NOT_INCLUDE_NATIVE_EVENTS_IN_EMITTED", 39 | WRAPPER_DO_NOT_INCLUDE_HOOK_EVENTS_IN_EMITTED: "WRAPPER_DO_NOT_INCLUDE_HOOK_EVENTS_IN_EMITTED", 40 | WRAPPER_FIND_ALL: "WRAPPER_FIND_ALL", 41 | WRAPPER_FIND_BY_CSS_SELECTOR_RETURNS_COMPONENTS: "WRAPPER_FIND_BY_CSS_SELECTOR_RETURNS_COMPONENTS", 42 | WRAPPER_FIND_COMPONENT_BY_REF_RETURNS_DOM: "WRAPPER_FIND_COMPONENT_BY_REF_RETURNS_DOM", 43 | WRAPPER_SET_VALUE_DOES_NOT_TRIGGER_CHANGE: "WRAPPER_SET_VALUE_DOES_NOT_TRIGGER_CHANGE", 44 | WRAPPER_VUE_SET_VALUE_USES_DOM: "WRAPPER_VUE_SET_VALUE_USES_DOM", 45 | }); 46 | 47 | export const fullCompatConfig = Object.freeze({ 48 | ...Object.fromEntries(Object.keys(compatFlags).map((option) => [option, true])), 49 | }); 50 | 51 | export function installCompat(VTU, compatConfig, vueH = null) { 52 | if (!VTU) { 53 | throw new Error("Unknown module for installation"); 54 | } 55 | 56 | if (!compatConfig) { 57 | throw new Error("compatConfig is required"); 58 | } 59 | 60 | const invalidOptions = Object.keys(compatConfig).filter((option) => !Object.keys(compatFlags).includes(option)); 61 | if (invalidOptions.length > 0) { 62 | throw new Error(`Got unknown compat options: ${invalidOptions.join(", ")}`); 63 | } 64 | 65 | if (compatConfig.MOUNT_ARGS_SCOPED_SLOTS_THIS && !compatConfig.MOUNT_ARGS_SCOPED_SLOTS) { 66 | throw new Error("MOUNT_ARGS_SCOPED_SLOTS_THIS require MOUNT_ARGS_SCOPED_SLOTS compat to function"); 67 | } 68 | 69 | if (compatConfig.MOUNT_ARGS_SCOPED_SLOTS_THIS && !vueH) { 70 | throw new Error("vueH (third parameter) is required when MOUNT_ARGS_SCOPED_SLOTS_THIS is used"); 71 | } 72 | 73 | const installedCompatConfig = installedConfigs.get(VTU); 74 | if (installedCompatConfig && installedCompatConfig !== JSON.stringify(compatConfig)) { 75 | throw new Error( 76 | "You are trying to install compat layer to vue-test-utils, but it was already installed with different config" 77 | ); 78 | } 79 | installedConfigs.set(VTU, JSON.stringify(compatConfig)); 80 | 81 | if (compatConfig.GLOBAL_STUBS) { 82 | // eslint-disable-next-line no-param-reassign 83 | VTU.config.stubs = VTU.config.global.stubs; 84 | } 85 | 86 | if (compatConfig.EXPORT_CREATE_WRAPPER) { 87 | // eslint-disable-next-line no-param-reassign 88 | VTU.createWrapper = (vm) => (vm instanceof HTMLElement ? new VTU.DOMWrapper(vm) : new VTU.VueWrapper(null, vm)); 89 | } 90 | 91 | if (compatConfig.EXPORT_CREATE_LOCAL_VUE) { 92 | // eslint-disable-next-line no-param-reassign 93 | VTU.createLocalVue = createLocalVue; 94 | } 95 | 96 | if (compatConfig.WRAPPER_DESTROY) { 97 | VTU.config.plugins.VueWrapper.install((wrapper) => ({ 98 | destroy: () => wrapper.unmount(), 99 | })); 100 | // eslint-disable-next-line no-param-reassign 101 | VTU.enableAutoDestroy = VTU.enableAutoUnmount; 102 | // eslint-disable-next-line no-param-reassign 103 | VTU.disableAutoDestroy = VTU.disableAutoUnmount; 104 | } 105 | 106 | if (compatConfig.WRAPPER_FIND_ALL || compatConfig.WRAPPER_FIND_BY_CSS_SELECTOR_RETURNS_COMPONENTS) { 107 | installWrapperFindAllCompat(VTU, compatConfig); 108 | } 109 | 110 | if ( 111 | compatConfig.WRAPPER_FIND_BY_CSS_SELECTOR_RETURNS_COMPONENTS || 112 | compatConfig.WRAPPER_FIND_COMPONENT_BY_REF_RETURNS_DOM 113 | ) { 114 | installWrapperFindCompat(VTU, compatConfig); 115 | } 116 | 117 | if (compatConfig.WRAPPER_DO_NOT_INCLUDE_NATIVE_EVENTS_IN_EMITTED) { 118 | // eslint-disable-next-line no-param-reassign 119 | VTU.VueWrapper.prototype.attachNativeEventListener = () => {}; 120 | } 121 | 122 | if (compatConfig.WRAPPER_DO_NOT_INCLUDE_HOOK_EVENTS_IN_EMITTED) { 123 | VTU.config.plugins.VueWrapper.install((wrapper) => { 124 | const { emitted } = wrapper; 125 | return { 126 | emitted(event) { 127 | const result = emitted.call(this, event); 128 | if (arguments.length === 1) { 129 | return result; 130 | } 131 | 132 | Object.keys(result).forEach((k) => { 133 | if (k.startsWith("hook:")) { 134 | delete result[k]; 135 | } 136 | }); 137 | return result; 138 | }, 139 | }; 140 | }); 141 | } 142 | 143 | if (needsNormalization(compatConfig)) { 144 | const originalMount = VTU.mount; 145 | // eslint-disable-next-line no-param-reassign 146 | VTU.mount = function patchedMount(component, args) { 147 | return originalMount.call(this, component, normalizeMountArgs(args, compatConfig, vueH)); 148 | }; 149 | 150 | const originalShallowMount = VTU.shallowMount; 151 | // eslint-disable-next-line no-param-reassign 152 | VTU.shallowMount = function patchedMount(component, args) { 153 | return originalShallowMount.call(this, component, normalizeMountArgs(args, compatConfig, vueH)); 154 | }; 155 | } 156 | 157 | if (compatConfig.WRAPPER_ATTRIBUTES_VALUE || compatConfig.WRAPPER_ATTRIBUTES_DISABLED) { 158 | installWrapperAttributesCompat(VTU, compatConfig); 159 | } 160 | 161 | if (compatConfig.WRAPPER_VUE_SET_VALUE_USES_DOM) { 162 | VTU.config.plugins.VueWrapper.install((wrapper) => ({ 163 | setValue(value) { 164 | return new VTU.DOMWrapper(wrapper.element).setValue(value); 165 | }, 166 | })); 167 | } 168 | 169 | if (compatConfig.WRAPPER_SET_VALUE_DOES_NOT_TRIGGER_CHANGE) { 170 | let blockTriggerringChange = false; 171 | VTU.config.plugins.DOMWrapper.install((wrapper) => { 172 | const { trigger, setValue } = wrapper; 173 | return { 174 | trigger(event, ...args) { 175 | if (blockTriggerringChange && event === "change") { 176 | return null; 177 | } 178 | 179 | return trigger.call(wrapper, event, ...args); 180 | }, 181 | setValue(...args) { 182 | blockTriggerringChange = true; 183 | try { 184 | return setValue.call(wrapper, ...args); 185 | } finally { 186 | blockTriggerringChange = false; 187 | } 188 | }, 189 | }; 190 | }); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/utils/create-local-vue.js: -------------------------------------------------------------------------------- 1 | function makeGetOrSetStorageFn(storage) { 2 | return function getOrSet(id, definition) { 3 | if (arguments.length === 1) { 4 | return storage[id]; 5 | } 6 | // eslint-disable-next-line no-param-reassign 7 | storage[id] = definition; 8 | return undefined; 9 | }; 10 | } 11 | 12 | export function createLocalVue() { 13 | const plugins = []; 14 | const directives = {}; 15 | const components = {}; 16 | const mixins = []; 17 | 18 | return { 19 | extend(options) { 20 | return options; 21 | }, 22 | use(...args) { 23 | plugins.push(args); 24 | }, 25 | component: makeGetOrSetStorageFn(components), 26 | directive: makeGetOrSetStorageFn(directives), 27 | mixin(entry) { 28 | mixins.push(entry); 29 | }, 30 | getLocalVueConfig() { 31 | return { 32 | ...(plugins.length ? { plugins } : {}), 33 | ...(Object.keys(directives).length ? { directives } : {}), 34 | ...(Object.keys(components).length ? { components } : {}), 35 | ...(mixins.length ? { mixins } : {}), 36 | }; 37 | }, 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /src/utils/normalize-mount-args.js: -------------------------------------------------------------------------------- 1 | const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1); 2 | 3 | function smartMerge(...args) { 4 | const filteredArgs = args.filter((a) => a != null); 5 | if (filteredArgs.length === 0) { 6 | return args[0]; 7 | } 8 | 9 | return Object.assign({}, ...filteredArgs); 10 | } 11 | 12 | function listenersToProps(listeners) { 13 | if (listeners == null) { 14 | return listeners; 15 | } 16 | 17 | return Object.fromEntries( 18 | Object.keys(listeners).map((key) => { 19 | const fixedKey = `on${capitalize(key)}`; 20 | return [fixedKey, listeners[key]]; 21 | }) 22 | ); 23 | } 24 | 25 | function normalizeStubs(stubs) { 26 | if (stubs == null) { 27 | return stubs; 28 | } 29 | 30 | const stubsAsObj = Array.isArray(stubs) ? Object.fromEntries(stubs.map((key) => [key, true])) : stubs; 31 | return Object.fromEntries( 32 | Object.entries(stubsAsObj).map(([key, stub]) => { 33 | if (typeof stub === "string") { 34 | return [key, { name: key, template: stub }]; 35 | } 36 | 37 | return [key, stub]; 38 | }) 39 | ); 40 | } 41 | 42 | function mergeGlobal(newGlobal, oldGlobal = {}) { 43 | return { 44 | components: smartMerge(newGlobal.components, oldGlobal.components), 45 | config: smartMerge(newGlobal.config, oldGlobal.config), 46 | directives: smartMerge(newGlobal.directives, oldGlobal.directives), 47 | mixins: 48 | /* istanbul ignore next */ 49 | newGlobal.mixins || oldGlobal.mixins ? [...(newGlobal.mixins ?? []), ...(oldGlobal.mixins ?? [])] : undefined, 50 | mocks: smartMerge(newGlobal.mocks, oldGlobal.mocks), 51 | plugins: 52 | /* istanbul ignore next */ 53 | newGlobal.plugins || oldGlobal.plugins ? [...(newGlobal.plugins ?? []), ...(oldGlobal.plugins ?? [])] : undefined, 54 | provide: smartMerge(newGlobal.provide, oldGlobal.provide), 55 | renderStubDefaultSlot: oldGlobal.renderStubDefaultSlot, 56 | stubs: smartMerge(newGlobal.stubs, normalizeStubs(oldGlobal.stubs)), 57 | }; 58 | } 59 | 60 | function normalizeConfigOption(obj) { 61 | const resultObj = Object.fromEntries(Object.entries(obj).filter(([, v]) => v != null)); 62 | return Object.keys(resultObj).length > 0 ? resultObj : null; 63 | } 64 | 65 | function normalizeScopedSlots(scopedSlots, scopedSlotsThis) { 66 | if (scopedSlots == null) { 67 | return scopedSlots; 68 | } 69 | 70 | return Object.fromEntries( 71 | Object.entries(scopedSlots).map(([k, v]) => { 72 | let normalizedValue = v; 73 | if (typeof v === "string" && !v.startsWith("${v}`; 75 | } else if (typeof v === "function") { 76 | normalizedValue = (...args) => v.call(scopedSlotsThis, ...args); 77 | } else { 78 | throw new Error("Unknown slot type"); 79 | } 80 | 81 | return [k, normalizedValue]; 82 | }) 83 | ); 84 | } 85 | 86 | function contextOnToProps(on) { 87 | if (on == null) { 88 | return on; 89 | } 90 | return Object.fromEntries( 91 | Object.keys(on).map((key) => { 92 | const capitalized = capitalize(key); 93 | return [`on${capitalized}`, on[key]]; 94 | }) 95 | ); 96 | } 97 | 98 | function normalizeProvide(provide) { 99 | return typeof provide === "function" ? provide() : provide; 100 | } 101 | 102 | export function normalizeMountArgs(args, config, vueH) { 103 | if (args == null) { 104 | return args; 105 | } 106 | 107 | const { 108 | // VTU v1 props 109 | localVue, 110 | 111 | context, 112 | mocks, 113 | provide, 114 | listeners, 115 | scopedSlots, 116 | stubs, 117 | components, 118 | directives, 119 | 120 | // VTU v2 props 121 | props, 122 | propsData, 123 | slots, 124 | global, 125 | ...otherArgsOptions 126 | } = args; 127 | 128 | const scopedSlotsThis = config.MOUNT_ARGS_SCOPED_SLOTS_THIS 129 | ? { 130 | $createElement: vueH, 131 | $set: (obj, path, value) => { 132 | // eslint-disable-next-line no-param-reassign 133 | obj[path] = value; 134 | }, 135 | } 136 | : {}; 137 | 138 | const { 139 | plugins, 140 | directives: localVueDirectives, 141 | components: localVueComponents, 142 | mixins, 143 | } = localVue?.getLocalVueConfig() ?? {}; 144 | const computedArgs = normalizeConfigOption({ 145 | props: smartMerge( 146 | config.MOUNT_ARGS_CONTEXT_ATTRS ? context?.attrs : null, 147 | config.MOUNT_ARGS_CONTEXT_PROPS ? context?.props : null, 148 | config.MOUNT_ARGS_CONTEXT_CLASS && context?.class ? { class: context?.class } : null, 149 | config.MOUNT_ARGS_CONTEXT_ON ? contextOnToProps(context?.on) : null, 150 | config.MOUNT_ARGS_LISTENERS ? listenersToProps(listeners) : null, 151 | propsData, 152 | props 153 | ), 154 | slots: smartMerge( 155 | config.MOUNT_ARGS_SCOPED_SLOTS ? normalizeScopedSlots(scopedSlots, scopedSlotsThis) : null, 156 | config.MOUNT_ARGS_CONTEXT_CHILDREN && context?.children ? { default: context.children } : null, 157 | slots 158 | ), 159 | global: normalizeConfigOption( 160 | mergeGlobal( 161 | { 162 | stubs: config.MOUNT_ARGS_STUBS 163 | ? { 164 | ...normalizeStubs(stubs), 165 | ...(config.MOUNT_ARGS_DIRECTIVES && directives 166 | ? Object.fromEntries(Object.entries(directives).map(([k, v]) => [`v${capitalize(k)}`, v])) 167 | : {}), 168 | } 169 | : null, 170 | mocks: config.MOUNT_ARGS_MOCKS ? mocks : null, 171 | provide: config.MOUNT_ARGS_PROVIDE ? normalizeProvide(provide) : null, 172 | plugins, 173 | mixins, 174 | components: smartMerge(localVueComponents, config.MOUNT_ARGS_COMPONENTS ? components : null), 175 | directives: smartMerge(localVueDirectives, config.MOUNT_ARGS_DIRECTIVES ? directives : null), 176 | }, 177 | global 178 | ) 179 | ), 180 | }); 181 | return smartMerge(computedArgs, otherArgsOptions); 182 | } 183 | -------------------------------------------------------------------------------- /tests/flags/export/export-create-local-vue.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, beforeEach, it, expect } from "@jest/globals"; 2 | import { installCompat, compatFlags } from "../../../src/index.js"; 3 | import { describeOption } from "../../helpers.js"; 4 | 5 | describeOption(compatFlags.EXPORT_CREATE_LOCAL_VUE, () => { 6 | let VTU; 7 | 8 | beforeEach(async () => { 9 | jest.resetModules(); 10 | VTU = { ...(await import("@vue/test-utils")) }; 11 | }); 12 | 13 | describe("when enabled", () => { 14 | beforeEach(() => { 15 | installCompat(VTU, { [compatFlags.EXPORT_CREATE_LOCAL_VUE]: true }); 16 | }); 17 | 18 | it("exposes createLocalVue on VTU module", () => { 19 | expect(VTU.createLocalVue).toBeInstanceOf(Function); 20 | }); 21 | 22 | it.each(["component", "directive"])("registers %s on mounted app", (type) => { 23 | const fakeEntry = {}; 24 | const localVue = VTU.createLocalVue(); 25 | localVue[type]("fake-entry", fakeEntry); 26 | const wrapper = VTU.mount(() => "test", { 27 | localVue, 28 | }); 29 | expect(localVue[type]("fake-entry")).toBe(fakeEntry); 30 | expect(wrapper.vm.$.appContext.app[type]("fake-entry")).toBe(fakeEntry); 31 | }); 32 | 33 | it("registers mixin on mounted app", () => { 34 | const fakeMixin = { 35 | created() { 36 | this.mixinInstalled = true; 37 | }, 38 | }; 39 | const localVue = VTU.createLocalVue(); 40 | localVue.mixin(fakeMixin); 41 | const wrapper = VTU.mount({ template: "test" }, { localVue }); 42 | expect(wrapper.vm.mixinInstalled).toBe(true); 43 | }); 44 | 45 | it("registers plugin on mounted app", () => { 46 | const fakeOptions = []; 47 | const fakePlugin = { 48 | install(app) { 49 | // eslint-disable-next-line no-param-reassign 50 | app.config.globalProperties.$pluginInstalled = fakeOptions; 51 | }, 52 | }; 53 | const localVue = VTU.createLocalVue(); 54 | localVue.use(fakePlugin, fakeOptions); 55 | const wrapper = VTU.mount({ template: "test" }, { localVue }); 56 | expect(wrapper.vm.$pluginInstalled).toBe(fakeOptions); 57 | }); 58 | 59 | it("localVue.extend works for normal component", () => { 60 | const localVue = VTU.createLocalVue(); 61 | const fakeComponent = localVue.extend({ template: "test" }); 62 | const wrapper = VTU.mount(fakeComponent, { localVue }); 63 | expect(wrapper.html()).toBe("test"); 64 | }); 65 | 66 | it("localVue.extend works for functional component", () => { 67 | const localVue = VTU.createLocalVue(); 68 | const fakeComponent = localVue.extend(() => "test"); 69 | const wrapper = VTU.mount(fakeComponent, { localVue }); 70 | expect(wrapper.html()).toBe("test"); 71 | }); 72 | }); 73 | 74 | describe("when disabled", () => { 75 | beforeEach(() => { 76 | installCompat(VTU, { [compatFlags.EXPORT_CREATE_LOCAL_VUE]: false }); 77 | }); 78 | 79 | it("does not expose createLocalVue", () => { 80 | expect(VTU.createLocalVue).toBeUndefined(); 81 | }); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /tests/flags/export/export-create-wrapper.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, beforeEach, it, expect } from "@jest/globals"; 2 | import { defineComponent } from "vue"; 3 | import { installCompat, compatFlags } from "../../../src/index.js"; 4 | import { describeOption } from "../../helpers.js"; 5 | 6 | describeOption(compatFlags.EXPORT_CREATE_WRAPPER, () => { 7 | let VTU; 8 | 9 | const FakeComponent = defineComponent({ template: "
hello
" }); 10 | 11 | beforeEach(async () => { 12 | jest.resetModules(); 13 | VTU = { ...(await import("@vue/test-utils")) }; 14 | }); 15 | 16 | describe("when enabled", () => { 17 | beforeEach(() => { 18 | installCompat(VTU, { [compatFlags.EXPORT_CREATE_WRAPPER]: true }); 19 | }); 20 | 21 | it("exposes createWrapper on VTU module", () => { 22 | expect(VTU.createWrapper).toBeInstanceOf(Function); 23 | }); 24 | 25 | it("returns DOM wrapper when called on a DOM node", () => { 26 | const domNode = document.createElement("div"); // 27 | const wrapper = VTU.createWrapper(domNode); 28 | expect(wrapper).toBeInstanceOf(VTU.DOMWrapper); 29 | }); 30 | 31 | it("returns Vue wrapper when called on a Vue vm", () => { 32 | const otherWrapper = VTU.mount(FakeComponent); 33 | const wrapper = VTU.createWrapper(otherWrapper.vm); 34 | expect(wrapper).toBeInstanceOf(VTU.VueWrapper); 35 | }); 36 | }); 37 | 38 | describe("when disabled", () => { 39 | beforeEach(() => { 40 | installCompat(VTU, { [compatFlags.EXPORT_CREATE_WRAPPER]: false }); 41 | }); 42 | 43 | it("does not expose createWrapper", () => { 44 | expect(VTU.createWrapper).toBeUndefined(); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /tests/flags/global-stubs.test.js: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, jest, beforeEach } from "@jest/globals"; 2 | import { installCompat, compatFlags } from "../../src/index.js"; 3 | import { describeOption } from "../helpers.js"; 4 | 5 | describeOption(compatFlags.GLOBAL_STUBS, () => { 6 | let VTU; 7 | beforeEach(async () => { 8 | jest.resetModules(); 9 | VTU = await import("@vue/test-utils"); 10 | }); 11 | 12 | describe("when enabled", () => { 13 | beforeEach(() => { 14 | installCompat(VTU, { [compatFlags.GLOBAL_STUBS]: true }); 15 | }); 16 | 17 | it("should mirror stubs", () => { 18 | expect(VTU.config.global.stubs).toBe(VTU.config.stubs); 19 | }); 20 | }); 21 | 22 | describe("when disabled", () => { 23 | beforeEach(() => { 24 | installCompat(VTU, { [compatFlags.GLOBAL_STUBS]: false }); 25 | }); 26 | 27 | it("should not mirror stubs", () => { 28 | expect(VTU.config.global.stubs).not.toBe(VTU.config.stubs); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /tests/flags/mount-args/context/mount-args-context-attrs.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, afterEach, beforeEach, it, expect } from "@jest/globals"; 2 | import { h } from "vue"; 3 | import { installCompat, compatFlags } from "../../../../src/index.js"; 4 | import { describeOption } from "../../../helpers.js"; 5 | 6 | describeOption(compatFlags.MOUNT_ARGS_CONTEXT_ATTRS, () => { 7 | let VTU; 8 | 9 | const FakeComponent = (props) => h("div", props, "hello"); 10 | 11 | beforeEach(async () => { 12 | jest.resetModules(); 13 | VTU = { ...(await import("@vue/test-utils")) }; 14 | }); 15 | 16 | let wrapper; 17 | const makeWrapperWithCompat = (compatMode) => { 18 | installCompat(VTU, { [compatFlags.MOUNT_ARGS_CONTEXT_ATTRS]: compatMode }); 19 | wrapper = VTU.mount(FakeComponent, { 20 | context: { attrs: { "aria-label": "test" } }, 21 | }); 22 | }; 23 | 24 | afterEach(() => { 25 | wrapper.unmount(); 26 | }); 27 | 28 | describe("when enabled", () => { 29 | beforeEach(() => makeWrapperWithCompat(true)); 30 | 31 | it("attrs from context.attrs are passed to props", async () => { 32 | expect(wrapper.html()).toBe(`
hello
`); 33 | }); 34 | }); 35 | 36 | describe("when disabled", () => { 37 | beforeEach(() => makeWrapperWithCompat(false)); 38 | 39 | it("attrs from context.attrs are ignored", async () => { 40 | expect(wrapper.html()).toBe(`
hello
`); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /tests/flags/mount-args/context/mount-args-context-children.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, afterEach, beforeEach, it, expect } from "@jest/globals"; 2 | import { h } from "vue"; 3 | import { installCompat, compatFlags } from "../../../../src/index.js"; 4 | import { describeOption } from "../../../helpers.js"; 5 | 6 | describeOption(compatFlags.MOUNT_ARGS_CONTEXT_CHILDREN, () => { 7 | let VTU; 8 | 9 | const FakeComponent = (props, context) => h("div", context.slots.default?.()); 10 | 11 | beforeEach(async () => { 12 | jest.resetModules(); 13 | VTU = { ...(await import("@vue/test-utils")) }; 14 | }); 15 | 16 | let wrapper; 17 | const makeWrapperWithCompat = (compatMode) => { 18 | installCompat(VTU, { [compatFlags.MOUNT_ARGS_CONTEXT_CHILDREN]: compatMode }); 19 | wrapper = VTU.mount(FakeComponent, { 20 | context: { 21 | children: ["foo", "bar"], 22 | }, 23 | }); 24 | }; 25 | 26 | afterEach(() => { 27 | wrapper.unmount(); 28 | }); 29 | 30 | describe("when enabled", () => { 31 | beforeEach(() => makeWrapperWithCompat(true)); 32 | 33 | it("should render children passed as context.children as default slot", async () => { 34 | expect(wrapper.html()).toBe("
foobar
"); 35 | }); 36 | }); 37 | 38 | describe("when disabled", () => { 39 | beforeEach(() => makeWrapperWithCompat(false)); 40 | 41 | it("should not render children passed as context.children", async () => { 42 | expect(wrapper.html()).toBe("
"); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /tests/flags/mount-args/context/mount-args-context-class.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, afterEach, beforeEach, it, expect } from "@jest/globals"; 2 | import { h } from "vue"; 3 | import { installCompat, compatFlags } from "../../../../src/index.js"; 4 | import { describeOption } from "../../../helpers.js"; 5 | 6 | describeOption(compatFlags.MOUNT_ARGS_CONTEXT_CLASS, () => { 7 | let VTU; 8 | 9 | const FakeComponent = (props) => h("div", { class: props.class }, "hello"); 10 | 11 | beforeEach(async () => { 12 | jest.resetModules(); 13 | VTU = { ...(await import("@vue/test-utils")) }; 14 | }); 15 | 16 | let wrapper; 17 | const makeWrapperWithCompat = (compatMode) => { 18 | installCompat(VTU, { [compatFlags.MOUNT_ARGS_CONTEXT_CLASS]: compatMode }); 19 | wrapper = VTU.mount(FakeComponent, { 20 | context: { class: "foo" }, 21 | }); 22 | }; 23 | 24 | afterEach(() => { 25 | wrapper.unmount(); 26 | }); 27 | 28 | describe("when enabled", () => { 29 | beforeEach(() => makeWrapperWithCompat(true)); 30 | 31 | it("should render class passed as context.class", async () => { 32 | expect(wrapper.html()).toBe('
hello
'); 33 | }); 34 | }); 35 | 36 | describe("when disabled", () => { 37 | beforeEach(() => makeWrapperWithCompat(false)); 38 | 39 | it("should not render class passed as context.children", async () => { 40 | expect(wrapper.html()).toBe("
hello
"); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /tests/flags/mount-args/context/mount-args-context-on.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, afterEach, beforeEach, it, expect } from "@jest/globals"; 2 | import { h } from "vue"; 3 | import { installCompat, compatFlags } from "../../../../src/index.js"; 4 | import { describeOption } from "../../../helpers.js"; 5 | 6 | describeOption(compatFlags.MOUNT_ARGS_CONTEXT_ON, () => { 7 | let VTU; 8 | 9 | const FakeComponent = (props) => h("div", props, "hello"); 10 | 11 | beforeEach(async () => { 12 | jest.resetModules(); 13 | VTU = { ...(await import("@vue/test-utils")) }; 14 | }); 15 | 16 | let wrapper; 17 | const clickHandler = jest.fn(); 18 | const makeWrapperWithCompat = (compatMode) => { 19 | installCompat(VTU, { [compatFlags.MOUNT_ARGS_CONTEXT_ON]: compatMode }); 20 | wrapper = VTU.mount(FakeComponent, { 21 | context: { on: { click: clickHandler } }, 22 | }); 23 | }; 24 | 25 | afterEach(() => { 26 | wrapper.unmount(); 27 | }); 28 | 29 | describe("when enabled", () => { 30 | beforeEach(() => makeWrapperWithCompat(true)); 31 | 32 | it("handler passed in context.on should be triggered", async () => { 33 | await wrapper.trigger("click"); 34 | expect(clickHandler).toHaveBeenCalled(); 35 | }); 36 | 37 | it("does not throw when context.on is null", () => { 38 | expect(() => VTU.mount(FakeComponent, { context: { on: null } })).not.toThrow(); 39 | }); 40 | }); 41 | 42 | describe("when disabled", () => { 43 | beforeEach(() => makeWrapperWithCompat(false)); 44 | 45 | it("handler passed in context.on is ignored", async () => { 46 | await wrapper.trigger("click"); 47 | expect(clickHandler).not.toHaveBeenCalled(); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /tests/flags/mount-args/context/mount-args-context-props.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, afterEach, beforeEach, it, expect } from "@jest/globals"; 2 | import { h } from "vue"; 3 | import { installCompat, compatFlags } from "../../../../src/index.js"; 4 | import { describeOption } from "../../../helpers.js"; 5 | 6 | describeOption(compatFlags.MOUNT_ARGS_CONTEXT_PROPS, () => { 7 | let VTU; 8 | 9 | const FakeComponent = (props) => h("div", props, "hello"); 10 | 11 | beforeEach(async () => { 12 | jest.resetModules(); 13 | VTU = { ...(await import("@vue/test-utils")) }; 14 | }); 15 | 16 | let wrapper; 17 | const makeWrapperWithCompat = (compatMode) => { 18 | installCompat(VTU, { [compatFlags.MOUNT_ARGS_CONTEXT_PROPS]: compatMode }); 19 | wrapper = VTU.mount(FakeComponent, { 20 | context: { props: { "aria-label": "test" } }, 21 | }); 22 | }; 23 | 24 | afterEach(() => { 25 | wrapper.unmount(); 26 | }); 27 | 28 | describe("when enabled", () => { 29 | beforeEach(() => makeWrapperWithCompat(true)); 30 | 31 | it("props from context.props are passed to props", async () => { 32 | expect(wrapper.html()).toBe(`
hello
`); 33 | }); 34 | }); 35 | 36 | describe("when disabled", () => { 37 | beforeEach(() => makeWrapperWithCompat(false)); 38 | 39 | it("props from context.props are ignored", async () => { 40 | expect(wrapper.html()).toBe(`
hello
`); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /tests/flags/mount-args/mount-args-components.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, afterEach, beforeEach, it, expect } from "@jest/globals"; 2 | import { installCompat, compatFlags } from "../../../src/index.js"; 3 | import { describeOption } from "../../helpers.js"; 4 | 5 | describeOption(compatFlags.MOUNT_ARGS_COMPONENTS, () => { 6 | let VTU; 7 | 8 | beforeEach(async () => { 9 | jest.resetModules(); 10 | VTU = { ...(await import("@vue/test-utils")) }; 11 | }); 12 | 13 | let wrapper; 14 | const makeWrapperWithCompat = (compatMode) => { 15 | const ChildComponent = { 16 | name: "ChildComponent", 17 | template: "
i-am-child
", 18 | }; 19 | 20 | const FakeComponent = { 21 | components: { ChildComponent }, 22 | template: "
i-am-root
", 23 | }; 24 | 25 | installCompat(VTU, { [compatFlags.MOUNT_ARGS_COMPONENTS]: compatMode }); 26 | wrapper = VTU.mount(FakeComponent, { 27 | global: { 28 | config: { 29 | warnHandler: () => {}, 30 | }, 31 | }, 32 | components: { 33 | SomeGlobalComponent: { template: "
i-am-global
" }, 34 | }, 35 | }); 36 | }; 37 | 38 | afterEach(() => { 39 | wrapper.unmount(); 40 | }); 41 | 42 | describe("when enabled", () => { 43 | beforeEach(() => makeWrapperWithCompat(true)); 44 | 45 | it("components option is respected", async () => { 46 | expect(wrapper.text()).toBe("i-am-root i-am-child i-am-global"); 47 | }); 48 | }); 49 | 50 | describe("when disabled", () => { 51 | beforeEach(() => makeWrapperWithCompat(false)); 52 | 53 | it("components option is ignored", async () => { 54 | expect(wrapper.text()).toBe("i-am-root i-am-child"); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /tests/flags/mount-args/mount-args-directives.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, afterEach, beforeEach, it, expect } from "@jest/globals"; 2 | import { installCompat, compatFlags } from "../../../src/index.js"; 3 | import { describeOption } from "../../helpers.js"; 4 | 5 | describeOption(compatFlags.MOUNT_ARGS_DIRECTIVES, () => { 6 | let VTU; 7 | 8 | beforeEach(async () => { 9 | jest.resetModules(); 10 | VTU = { ...(await import("@vue/test-utils")) }; 11 | }); 12 | 13 | const directiveCreated = jest.fn(); 14 | let wrapper; 15 | const makeWrapperWithCompat = (compatMode) => { 16 | const FakeComponent = { 17 | template: "
i-am-root
", 18 | }; 19 | 20 | installCompat(VTU, { [compatFlags.MOUNT_ARGS_DIRECTIVES]: compatMode, [compatFlags.MOUNT_ARGS_STUBS]: compatMode }); 21 | wrapper = VTU.mount(FakeComponent, { 22 | global: { 23 | config: { 24 | warnHandler: () => {}, 25 | }, 26 | }, 27 | directives: { 28 | CustomDir: { created: directiveCreated }, 29 | }, 30 | }); 31 | }; 32 | 33 | afterEach(() => { 34 | wrapper.unmount(); 35 | }); 36 | 37 | describe("when enabled", () => { 38 | it("directives option is respected", async () => { 39 | makeWrapperWithCompat(true); 40 | expect(wrapper.html()).toBe("
i-am-root
"); 41 | expect(directiveCreated).toHaveBeenCalled(); 42 | }); 43 | }); 44 | 45 | describe("when disabled", () => { 46 | it("directives option is ignored", async () => { 47 | expect(() => makeWrapperWithCompat(false)).toThrow(); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /tests/flags/mount-args/mount-args-listeners.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, afterEach, beforeEach, it, expect } from "@jest/globals"; 2 | import { h } from "vue"; 3 | import { installCompat, compatFlags } from "../../../src/index.js"; 4 | import { describeOption } from "../../helpers.js"; 5 | 6 | describeOption(compatFlags.MOUNT_ARGS_LISTENERS, () => { 7 | let VTU; 8 | 9 | const FakeComponent = (props) => h("div", props, "hello"); 10 | 11 | beforeEach(async () => { 12 | jest.resetModules(); 13 | VTU = { ...(await import("@vue/test-utils")) }; 14 | }); 15 | 16 | let wrapper; 17 | const clickHandler = jest.fn(); 18 | const makeWrapperWithCompat = (compatMode) => { 19 | installCompat(VTU, { [compatFlags.MOUNT_ARGS_LISTENERS]: compatMode }); 20 | wrapper = VTU.mount(FakeComponent, { 21 | listeners: { click: clickHandler }, 22 | }); 23 | }; 24 | 25 | afterEach(() => { 26 | wrapper.unmount(); 27 | }); 28 | 29 | describe("when enabled", () => { 30 | beforeEach(() => makeWrapperWithCompat(true)); 31 | 32 | it("handler passed in listeners should be triggered", async () => { 33 | await wrapper.trigger("click"); 34 | expect(clickHandler).toHaveBeenCalled(); 35 | }); 36 | 37 | it("does not throw when listeners is null", () => { 38 | expect(() => VTU.mount(FakeComponent, { listeners: null })).not.toThrow(); 39 | }); 40 | }); 41 | 42 | describe("when disabled", () => { 43 | beforeEach(() => makeWrapperWithCompat(false)); 44 | 45 | it("handler passed in listeners is ignored", async () => { 46 | await wrapper.trigger("click"); 47 | expect(clickHandler).not.toHaveBeenCalled(); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /tests/flags/mount-args/mount-args-mocks.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, afterEach, beforeEach, it, expect } from "@jest/globals"; 2 | import { defineComponent } from "vue"; 3 | import { installCompat, compatFlags } from "../../../src/index.js"; 4 | import { describeOption } from "../../helpers.js"; 5 | 6 | describeOption(compatFlags.MOUNT_ARGS_MOCKS, () => { 7 | let VTU; 8 | 9 | const FakeComponent = defineComponent({ 10 | template: "
hello
", 11 | }); 12 | 13 | beforeEach(async () => { 14 | jest.resetModules(); 15 | VTU = { ...(await import("@vue/test-utils")) }; 16 | }); 17 | 18 | let wrapper; 19 | const makeWrapperWithCompat = (compatMode) => { 20 | installCompat(VTU, { [compatFlags.MOUNT_ARGS_MOCKS]: compatMode }); 21 | wrapper = VTU.mount(FakeComponent, { 22 | mocks: { $mockValue: "something" }, 23 | }); 24 | }; 25 | 26 | afterEach(() => { 27 | wrapper.unmount(); 28 | }); 29 | 30 | describe("when enabled", () => { 31 | beforeEach(() => makeWrapperWithCompat(true)); 32 | 33 | it("mocks option is respected", async () => { 34 | expect(wrapper.vm.$mockValue).toBe("something"); 35 | }); 36 | }); 37 | 38 | describe("when disabled", () => { 39 | beforeEach(() => makeWrapperWithCompat(false)); 40 | 41 | it("mocks option is ignored", async () => { 42 | expect(wrapper.vm.$mockValue).toBeUndefined(); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /tests/flags/mount-args/mount-args-provide.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, afterEach, beforeEach, it, expect } from "@jest/globals"; 2 | import { defineComponent } from "vue"; 3 | import { installCompat, compatFlags } from "../../../src/index.js"; 4 | import { describeOption } from "../../helpers.js"; 5 | 6 | describeOption(compatFlags.MOUNT_ARGS_PROVIDE, () => { 7 | let VTU; 8 | 9 | const FakeComponent = defineComponent({ 10 | inject: { 11 | providedValue: { 12 | default: "not-provided", 13 | }, 14 | }, 15 | template: "
{{ providedValue }}
", 16 | }); 17 | 18 | beforeEach(async () => { 19 | jest.resetModules(); 20 | VTU = { ...(await import("@vue/test-utils")) }; 21 | }); 22 | 23 | let wrapper; 24 | const makeWrapperWithCompat = (compatMode) => { 25 | installCompat(VTU, { [compatFlags.MOUNT_ARGS_PROVIDE]: compatMode }); 26 | wrapper = VTU.mount(FakeComponent, { 27 | provide: { providedValue: "ok-provided" }, 28 | }); 29 | }; 30 | 31 | afterEach(() => { 32 | wrapper.unmount(); 33 | }); 34 | 35 | describe("when enabled", () => { 36 | beforeEach(() => makeWrapperWithCompat(true)); 37 | 38 | it("provide option is respected", async () => { 39 | expect(wrapper.html()).toBe("
ok-provided
"); 40 | }); 41 | 42 | it("correctly handles provide as a function", async () => { 43 | const wrapperWithProvideFn = VTU.mount(FakeComponent, { 44 | provide() { 45 | return { providedValue: "ok-provided-from-fn" }; 46 | }, 47 | }); 48 | expect(wrapperWithProvideFn.html()).toBe("
ok-provided-from-fn
"); 49 | }); 50 | }); 51 | 52 | describe("when disabled", () => { 53 | beforeEach(() => makeWrapperWithCompat(false)); 54 | 55 | it("provide option is ignored", async () => { 56 | expect(wrapper.html()).toBe("
not-provided
"); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /tests/flags/mount-args/mount-args-scoped-slots-this.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, afterEach, beforeEach, it, expect } from "@jest/globals"; 2 | import { h } from "vue"; 3 | import { installCompat, compatFlags } from "../../../src/index.js"; 4 | import { describeOption } from "../../helpers.js"; 5 | 6 | describeOption(compatFlags.MOUNT_ARGS_SCOPED_SLOTS_THIS, () => { 7 | let VTU; 8 | 9 | const FakeComponent = (props, context) => 10 | h("div", ["hello", " ", context.slots?.default?.(), " ", context.slots?.scoped?.({ foo: "bar" })]); 11 | 12 | beforeEach(async () => { 13 | jest.resetModules(); 14 | VTU = { ...(await import("@vue/test-utils")) }; 15 | }); 16 | 17 | let wrapper; 18 | const mountComponent = () => { 19 | wrapper = VTU.mount(FakeComponent, { 20 | global: { 21 | config: { 22 | warnHandler: () => {}, 23 | }, 24 | }, 25 | slots: { 26 | default: "
default
", 27 | }, 28 | scopedSlots: { 29 | scoped(scope) { 30 | this.$set(scope, "baz", "$set"); 31 | return this.$createElement("div", [scope.foo, " ", scope.baz]); 32 | }, 33 | }, 34 | }); 35 | }; 36 | 37 | afterEach(() => { 38 | wrapper.unmount(); 39 | }); 40 | 41 | describe("when enabled", () => { 42 | beforeEach(() => { 43 | installCompat( 44 | VTU, 45 | { 46 | [compatFlags.MOUNT_ARGS_SCOPED_SLOTS]: true, 47 | [compatFlags.MOUNT_ARGS_SCOPED_SLOTS_THIS]: true, 48 | }, 49 | h 50 | ); 51 | mountComponent(); 52 | }); 53 | 54 | it("scoped slots are passed this with $createElement and $set", async () => { 55 | expect(wrapper.text()).toBe("hello default bar $set"); 56 | }); 57 | }); 58 | 59 | describe("when disabled", () => { 60 | beforeEach(() => { 61 | installCompat( 62 | VTU, 63 | { 64 | [compatFlags.MOUNT_ARGS_SCOPED_SLOTS]: true, 65 | [compatFlags.MOUNT_ARGS_SCOPED_SLOTS_THIS]: false, 66 | }, 67 | h 68 | ); 69 | }); 70 | 71 | it("throws when trying to use $set or $createElement", async () => { 72 | expect(mountComponent).toThrow(); 73 | }); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /tests/flags/mount-args/mount-args-scoped-slots.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, afterEach, beforeEach, it, expect } from "@jest/globals"; 2 | import { h } from "vue"; 3 | import { installCompat, compatFlags } from "../../../src/index.js"; 4 | import { describeOption } from "../../helpers.js"; 5 | 6 | describeOption(compatFlags.MOUNT_ARGS_SCOPED_SLOTS, () => { 7 | let VTU; 8 | 9 | const FakeComponent = (props, context) => 10 | h("div", ["hello", " ", context.slots?.default?.(), " ", context.slots?.scoped?.({ foo: "bar" })]); 11 | 12 | beforeEach(async () => { 13 | jest.resetModules(); 14 | VTU = { ...(await import("@vue/test-utils")) }; 15 | }); 16 | 17 | let wrapper; 18 | const makeWrapperWithCompat = (compatMode) => { 19 | installCompat(VTU, { [compatFlags.MOUNT_ARGS_SCOPED_SLOTS]: compatMode }); 20 | wrapper = VTU.mount(FakeComponent, { 21 | slots: { 22 | default: "
default
", 23 | }, 24 | scopedSlots: { 25 | scoped(scope) { 26 | return scope.foo; 27 | }, 28 | }, 29 | }); 30 | }; 31 | 32 | afterEach(() => { 33 | wrapper.unmount(); 34 | }); 35 | 36 | describe("when enabled", () => { 37 | beforeEach(() => makeWrapperWithCompat(true)); 38 | 39 | it("scoped slots are rendered", async () => { 40 | expect(wrapper.text()).toBe("hello default bar"); 41 | }); 42 | 43 | it("correctly renders scoped slot when it is presented as a string", async () => { 44 | expect( 45 | VTU.mount(FakeComponent, { 46 | slots: { 47 | default: "
default
", 48 | }, 49 | scopedSlots: { 50 | scoped: "{{ props.foo }} string", 51 | }, 52 | }).text() 53 | ).toBe("hello default bar string"); 54 | }); 55 | 56 | it("does not throw when scopedSlots is null", () => { 57 | expect(() => VTU.mount(FakeComponent, { scopedSlots: null })).not.toThrow(); 58 | }); 59 | 60 | it("throws when something weird is passed to scopedSlots", () => { 61 | expect(() => VTU.mount(FakeComponent, { scopedSlots: { scoped: 3 } })).toThrow(); 62 | }); 63 | }); 64 | 65 | describe("when disabled", () => { 66 | beforeEach(() => makeWrapperWithCompat(false)); 67 | 68 | it("scoped slots are ignored", async () => { 69 | expect(wrapper.text()).toBe("hello default"); 70 | }); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /tests/flags/mount-args/mount-args-stubs.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, afterEach, beforeEach, it, expect } from "@jest/globals"; 2 | import { installCompat, compatFlags } from "../../../src/index.js"; 3 | import { describeOption } from "../../helpers.js"; 4 | 5 | describeOption(compatFlags.MOUNT_ARGS_STUBS, () => { 6 | let VTU; 7 | 8 | const ChildComponent = { 9 | name: "ChildComponent", 10 | template: "i-am-child", 11 | }; 12 | 13 | const FakeComponent = { 14 | components: { ChildComponent }, 15 | template: "
i-am-root
", 16 | }; 17 | 18 | beforeEach(async () => { 19 | jest.resetModules(); 20 | VTU = { ...(await import("@vue/test-utils")) }; 21 | }); 22 | 23 | let wrapper; 24 | const makeWrapperWithCompat = (compatMode) => { 25 | installCompat(VTU, { [compatFlags.MOUNT_ARGS_STUBS]: compatMode }); 26 | wrapper = VTU.mount(FakeComponent, { 27 | stubs: { 28 | ChildComponent: { template: "stubbed-child" }, 29 | }, 30 | }); 31 | }; 32 | 33 | afterEach(() => { 34 | wrapper.unmount(); 35 | }); 36 | 37 | describe("when enabled", () => { 38 | beforeEach(() => makeWrapperWithCompat(true)); 39 | 40 | it("stubs option is respected", async () => { 41 | expect(wrapper.text()).toBe("i-am-root stubbed-child"); 42 | }); 43 | 44 | it("handle stubs passed as string", () => { 45 | expect(VTU.mount(FakeComponent, { stubs: { ChildComponent: "string-stub" } }).text()).toBe( 46 | "i-am-root string-stub" 47 | ); 48 | }); 49 | 50 | it("handle stubs passed as array", () => { 51 | expect( 52 | VTU.mount(FakeComponent, { stubs: ["ChildComponent"] }) 53 | .findComponent(ChildComponent) 54 | .html() 55 | ).toBe(""); 56 | }); 57 | 58 | it("does not throw when stubs is null", () => { 59 | expect(() => VTU.mount(FakeComponent, { stubs: null })).not.toThrow(); 60 | }); 61 | }); 62 | 63 | describe("when disabled", () => { 64 | beforeEach(() => makeWrapperWithCompat(false)); 65 | 66 | it("stubs option is ignored", async () => { 67 | expect(wrapper.text()).toBe("i-am-root i-am-child"); 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /tests/flags/mount-args/mount-args.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, beforeEach, it, expect } from "@jest/globals"; 2 | import { installCompat, fullCompatConfig } from "../../../src/index.js"; 3 | 4 | describe("mount args", () => { 5 | let VTU; 6 | 7 | const FakeComponent = () => "test"; 8 | 9 | beforeEach(async () => { 10 | jest.resetModules(); 11 | VTU = { ...(await import("@vue/test-utils")) }; 12 | }); 13 | 14 | it("patches mount & shallowMount when needed", async () => { 15 | const mountSpy = jest.spyOn(VTU, "mount"); 16 | const shallowMountSpy = jest.spyOn(VTU, "shallowMount"); 17 | 18 | installCompat(VTU, fullCompatConfig, () => {}); 19 | 20 | VTU.mount(FakeComponent); 21 | expect(mountSpy).toHaveBeenCalled(); 22 | 23 | VTU.shallowMount(FakeComponent); 24 | expect(shallowMountSpy).toHaveBeenCalled(); 25 | }); 26 | 27 | it("does not patch mount & shallowMount when not needed", () => { 28 | const mountSpy = jest.spyOn(VTU, "mount"); 29 | const shallowMountSpy = jest.spyOn(VTU, "shallowMount"); 30 | 31 | installCompat(VTU, {}); 32 | 33 | expect(VTU.mount).toBe(mountSpy); 34 | expect(VTU.shallowMount).toBe(shallowMountSpy); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /tests/flags/wrapper/wrapper-attributes-disabled.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, afterEach, beforeEach, it, expect } from "@jest/globals"; 2 | import { h } from "vue"; 3 | import { installCompat, compatFlags } from "../../../src/index.js"; 4 | import { describeOption } from "../../helpers.js"; 5 | 6 | describeOption(compatFlags.WRAPPER_ATTRIBUTES_DISABLED, () => { 7 | let VTU; 8 | 9 | const FakeComponent = () => h("input", { type: "text", disabled: "" }); 10 | const FakeComponentWithoutDisabled = () => h("input", { type: "text" }); 11 | 12 | beforeEach(async () => { 13 | jest.resetModules(); 14 | VTU = await import("@vue/test-utils"); 15 | }); 16 | 17 | let wrapper; 18 | let wrapperWithoutDisabled; 19 | 20 | afterEach(() => { 21 | wrapper.unmount(); 22 | }); 23 | 24 | describe("when enabled", () => { 25 | beforeEach(() => { 26 | installCompat(VTU, { [compatFlags.WRAPPER_ATTRIBUTES_DISABLED]: true }); 27 | wrapper = VTU.mount(FakeComponent); 28 | wrapperWithoutDisabled = VTU.mount(FakeComponentWithoutDisabled); 29 | }); 30 | 31 | it("should return normalized value for disabled from attributes call", async () => { 32 | expect(wrapper.attributes().disabled).toBe("disabled"); 33 | expect(wrapper.attributes("disabled")).toBe("disabled"); 34 | expect(wrapper.attributes().type).toBe("text"); 35 | expect(wrapper.attributes("type")).toBe("text"); 36 | expect(wrapperWithoutDisabled.attributes().disabled).toBe(undefined); 37 | expect(wrapperWithoutDisabled.attributes("disabled")).toBe(undefined); 38 | expect(wrapperWithoutDisabled.attributes().type).toBe("text"); 39 | expect(wrapperWithoutDisabled.attributes("type")).toBe("text"); 40 | }); 41 | }); 42 | 43 | describe("when disabled", () => { 44 | beforeEach(() => { 45 | installCompat(VTU, { [compatFlags.WRAPPER_ATTRIBUTES_VALUE]: false }); 46 | wrapper = VTU.mount(FakeComponent); 47 | wrapperWithoutDisabled = VTU.mount(FakeComponentWithoutDisabled); 48 | }); 49 | 50 | it("should not modify disabled value from attributes call", async () => { 51 | expect(wrapper.attributes().disabled).toBe(""); 52 | expect(wrapper.attributes("disabled")).toBe(""); 53 | expect(wrapper.attributes().type).toBe("text"); 54 | expect(wrapper.attributes("type")).toBe("text"); 55 | expect(wrapperWithoutDisabled.attributes().disabled).toBe(undefined); 56 | expect(wrapperWithoutDisabled.attributes("disabled")).toBe(undefined); 57 | expect(wrapperWithoutDisabled.attributes().type).toBe("text"); 58 | expect(wrapperWithoutDisabled.attributes("type")).toBe("text"); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /tests/flags/wrapper/wrapper-attributes-value.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, afterEach, beforeEach, it, expect } from "@jest/globals"; 2 | import { defineComponent, h } from "vue"; 3 | import { installCompat, compatFlags } from "../../../src/index.js"; 4 | import { describeOption } from "../../helpers.js"; 5 | 6 | describeOption(compatFlags.WRAPPER_ATTRIBUTES_VALUE, () => { 7 | let VTU; 8 | 9 | const FakeComponent = defineComponent({ 10 | render() { 11 | const value = "correct"; 12 | return h("input", { type: "text", value }); 13 | }, 14 | }); 15 | 16 | beforeEach(async () => { 17 | jest.resetModules(); 18 | VTU = await import("@vue/test-utils"); 19 | }); 20 | 21 | let wrapper; 22 | 23 | afterEach(() => { 24 | wrapper.unmount(); 25 | }); 26 | 27 | describe("when enabled", () => { 28 | beforeEach(() => { 29 | installCompat(VTU, { [compatFlags.WRAPPER_ATTRIBUTES_VALUE]: true }); 30 | wrapper = VTU.mount(FakeComponent); 31 | }); 32 | 33 | it("should return correct value from attributes call", async () => { 34 | expect(wrapper.attributes().value).toBe("correct"); 35 | expect(wrapper.attributes("value")).toBe("correct"); 36 | expect(wrapper.attributes().type).toBe("text"); 37 | expect(wrapper.attributes("type")).toBe("text"); 38 | }); 39 | }); 40 | 41 | describe("when disabled", () => { 42 | beforeEach(() => { 43 | installCompat(VTU, { [compatFlags.WRAPPER_ATTRIBUTES_VALUE]: false }); 44 | wrapper = VTU.mount(FakeComponent); 45 | }); 46 | 47 | it("should not return correct value from attributes call", async () => { 48 | // value is not actually managed as attribute in Vue3 49 | expect(wrapper.attributes().value).toBeUndefined(); 50 | expect(wrapper.attributes("value")).toBeUndefined(); 51 | expect(wrapper.attributes().type).toBe("text"); 52 | expect(wrapper.attributes("type")).toBe("text"); 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /tests/flags/wrapper/wrapper-destroy.test.js: -------------------------------------------------------------------------------- 1 | import { jest, it, beforeEach, expect, describe } from "@jest/globals"; 2 | import { installCompat, compatFlags } from "../../../src/index.js"; 3 | import { describeOption } from "../../helpers.js"; 4 | 5 | describeOption(compatFlags.WRAPPER_DESTROY, () => { 6 | let VTU; 7 | beforeEach(async () => { 8 | jest.resetModules(); 9 | VTU = { ...(await import("@vue/test-utils")) }; 10 | }); 11 | 12 | describe("when enabled", () => { 13 | beforeEach(() => { 14 | installCompat(VTU, { [compatFlags.WRAPPER_DESTROY]: true }); 15 | }); 16 | 17 | it("should call unmount instead of destroy when enabled", async () => { 18 | const unmounted = jest.fn(); 19 | const FakeComponent = { 20 | template: "
", 21 | unmounted, 22 | }; 23 | const wrapper = VTU.mount(FakeComponent); 24 | wrapper.destroy(); 25 | expect(unmounted).toHaveBeenCalled(); 26 | }); 27 | }); 28 | 29 | describe("when disabled", () => { 30 | beforeEach(() => { 31 | installCompat(VTU, { [compatFlags.WRAPPER_DESTROY]: false }); 32 | }); 33 | 34 | it("should not provide destroy method on wrapper when disabled", async () => { 35 | const unmounted = jest.fn(); 36 | const FakeComponent = { 37 | template: "
", 38 | unmounted, 39 | }; 40 | 41 | const wrapper = VTU.mount(FakeComponent); 42 | expect(wrapper.destroy).toBeUndefined(); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /tests/flags/wrapper/wrapper-do-not-include-native-events-in-emitted.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, afterEach, beforeEach, it, expect } from "@jest/globals"; 2 | import { defineComponent } from "vue"; 3 | import { installCompat, compatFlags } from "../../../src/index.js"; 4 | import { describeOption } from "../../helpers.js"; 5 | 6 | describeOption(compatFlags.WRAPPER_DO_NOT_INCLUDE_NATIVE_EVENTS_IN_EMITTED, () => { 7 | let VTU; 8 | 9 | const FakeComponent = defineComponent({ 10 | template: `
clicker
`, 11 | }); 12 | 13 | beforeEach(async () => { 14 | jest.resetModules(); 15 | VTU = await import("@vue/test-utils"); 16 | }); 17 | 18 | let wrapper; 19 | 20 | afterEach(() => { 21 | wrapper.unmount(); 22 | }); 23 | 24 | describe("when enabled", () => { 25 | beforeEach(() => { 26 | installCompat(VTU, { [compatFlags.WRAPPER_DO_NOT_INCLUDE_NATIVE_EVENTS_IN_EMITTED]: true }); 27 | wrapper = VTU.mount(FakeComponent); 28 | }); 29 | 30 | it("should ignore native event in emitted", async () => { 31 | await wrapper.trigger("click"); 32 | expect(Object.keys(wrapper.emitted())).toHaveLength(1); 33 | expect(wrapper.emitted()["click-event"]).toBeDefined(); 34 | }); 35 | }); 36 | 37 | describe("when disabled", () => { 38 | beforeEach(() => { 39 | installCompat(VTU, { [compatFlags.WRAPPER_DO_NOT_INCLUDE_NATIVE_EVENTS_IN_EMITTED]: false }); 40 | wrapper = VTU.mount(FakeComponent); 41 | }); 42 | 43 | it("should ignore native event in emitted", async () => { 44 | await wrapper.trigger("click"); 45 | expect(Object.keys(wrapper.emitted())).toHaveLength(2); 46 | expect(wrapper.emitted()["click-event"]).toBeDefined(); 47 | expect(wrapper.emitted().click).toBeDefined(); 48 | expect(wrapper.emitted().click[0][0]).toBeInstanceOf(Event); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /tests/flags/wrapper/wrapper-find-all.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, afterEach, beforeEach, it, expect } from "@jest/globals"; 2 | import { defineComponent } from "vue"; 3 | import { installCompat, compatFlags } from "../../../src/index.js"; 4 | import { describeOption } from "../../helpers.js"; 5 | 6 | describeOption(compatFlags.WRAPPER_FIND_ALL, () => { 7 | let VTU; 8 | 9 | const NestedCmp = defineComponent({ 10 | props: ["text"], 11 | template: "

{{ text }}

", 12 | }); 13 | 14 | const handleClick = jest.fn(); 15 | const FakeComponent = defineComponent({ 16 | components: { NestedCmp }, 17 | methods: { 18 | handleClick, 19 | }, 20 | template: [ 21 | "
", 22 | "

1

", 23 | "

2

", 24 | "

3

", 25 | "", 26 | "", 27 | "
", 28 | ].join(""), 29 | }); 30 | 31 | beforeEach(async () => { 32 | jest.resetModules(); 33 | VTU = await import("@vue/test-utils"); 34 | }); 35 | 36 | let wrapper; 37 | let results; 38 | 39 | describe("when enabled", () => { 40 | describe.each` 41 | method | selector | length 42 | ${"findAll"} | ${"p"} | ${3} 43 | ${"findAllComponents"} | ${NestedCmp} | ${2} 44 | `("wrapping $method", ({ method, selector, length }) => { 45 | beforeEach(() => { 46 | installCompat(VTU, { [compatFlags.WRAPPER_FIND_ALL]: true }); 47 | wrapper = VTU.mount(FakeComponent); 48 | results = wrapper[method](selector); 49 | }); 50 | 51 | afterEach(() => { 52 | wrapper.unmount(); 53 | }); 54 | 55 | it("should wrap results in wrapper", () => { 56 | expect(results).toHaveLength(length); 57 | expect(results.wrappers).toBeInstanceOf(Array); 58 | expect(results.at).toBeInstanceOf(Function); 59 | expect(results.at(0)).toBe(results.wrappers[0]); 60 | }); 61 | 62 | it("should filter results based on condition", () => { 63 | const filteredResults = results.filter((w) => w.text().includes("1")); 64 | expect(filteredResults).toHaveLength(1); 65 | }); 66 | 67 | it("should trigger event on all wrappers", () => { 68 | results.trigger("click"); 69 | expect(handleClick).toHaveBeenCalledTimes(results.length); 70 | }); 71 | 72 | it("should report exists as false for empty wrapper", () => { 73 | expect(wrapper[method]("some-weird-selector").exists()).toBe(false); 74 | }); 75 | 76 | it("should report exists as true for non-empty wrapper", () => { 77 | expect(results.exists()).toBe(true); 78 | }); 79 | }); 80 | }); 81 | 82 | describe("when disabled", () => { 83 | beforeEach(() => { 84 | installCompat(VTU, { [compatFlags.WRAPPER_FIND_ALL]: false }); 85 | wrapper = VTU.mount(FakeComponent); 86 | }); 87 | 88 | it.each(["findAll", "findAllComponents"])("%s returns an array", (method) => { 89 | results = wrapper[method]("something"); 90 | expect(results).toBeInstanceOf(Array); 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /tests/flags/wrapper/wrapper-find-by-css-selector-returns-components.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, beforeEach, it, expect } from "@jest/globals"; 2 | import { defineComponent } from "vue"; 3 | import { installCompat, compatFlags } from "../../../src/index.js"; 4 | import { describeOption } from "../../helpers.js"; 5 | 6 | describeOption(compatFlags.WRAPPER_FIND_BY_CSS_SELECTOR_RETURNS_COMPONENTS, () => { 7 | let VTU; 8 | 9 | const NestedCmp = defineComponent({ 10 | props: ["text"], 11 | template: "

{{ text }}

", 12 | }); 13 | 14 | const FakeComponent = defineComponent({ 15 | components: { NestedCmp }, 16 | template: [ 17 | "
", 18 | "", 19 | "

1

", 20 | "

2

", 21 | "", 22 | "
", 23 | ].join(""), 24 | }); 25 | 26 | beforeEach(async () => { 27 | jest.resetModules(); 28 | VTU = await import("@vue/test-utils"); 29 | }); 30 | 31 | let wrapper; 32 | let results; 33 | let result; 34 | 35 | describe("when enabled", () => { 36 | it("find/findAll returns components", () => { 37 | installCompat(VTU, { [compatFlags.WRAPPER_FIND_BY_CSS_SELECTOR_RETURNS_COMPONENTS]: true }); 38 | wrapper = VTU.mount(FakeComponent); 39 | results = wrapper.findAll(".foo"); 40 | result = wrapper.find(".foo"); 41 | 42 | expect(results).toHaveLength(4); 43 | expect(results[0]).toBeInstanceOf(VTU.VueWrapper); 44 | expect(results[1]).toBeInstanceOf(VTU.DOMWrapper); 45 | expect(results[2]).toBeInstanceOf(VTU.DOMWrapper); 46 | expect(results[3]).toBeInstanceOf(VTU.VueWrapper); 47 | expect(result).toBeInstanceOf(VTU.VueWrapper); 48 | }); 49 | }); 50 | 51 | describe("when disabled", () => { 52 | it("find/findAll returns components", () => { 53 | installCompat(VTU, { [compatFlags.WRAPPER_FIND_BY_CSS_SELECTOR_RETURNS_COMPONENTS]: false }); 54 | wrapper = VTU.mount(FakeComponent); 55 | results = wrapper.findAll(".foo"); 56 | result = wrapper.find(".foo"); 57 | 58 | expect(results).toHaveLength(4); 59 | expect(results.every((x) => x instanceof VTU.DOMWrapper)).toBe(true); 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /tests/flags/wrapper/wrapper-find-component-by-ref-returns-dom.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, beforeEach, it, expect } from "@jest/globals"; 2 | import { defineComponent } from "vue"; 3 | import { installCompat, compatFlags } from "../../../src/index.js"; 4 | import { describeOption } from "../../helpers.js"; 5 | 6 | describeOption(compatFlags.WRAPPER_FIND_COMPONENT_BY_REF_RETURNS_DOM, () => { 7 | let VTU; 8 | 9 | const FakeComponent = defineComponent({ 10 | template: `
Hello
`, 11 | }); 12 | 13 | beforeEach(async () => { 14 | jest.resetModules(); 15 | VTU = await import("@vue/test-utils"); 16 | }); 17 | 18 | let wrapper; 19 | 20 | describe("when enabled", () => { 21 | it("findComponent by ref finds relevant DOM nodes", () => { 22 | installCompat(VTU, { [compatFlags.WRAPPER_FIND_COMPONENT_BY_REF_RETURNS_DOM]: true }); 23 | wrapper = VTU.mount(FakeComponent); 24 | 25 | expect(wrapper.findComponent({ ref: "demo" }).text()).toBe("Hello"); 26 | }); 27 | }); 28 | 29 | describe("when disabled", () => { 30 | it("find/findAll returns components", () => { 31 | installCompat(VTU, { [compatFlags.WRAPPER_FIND_COMPONENT_BY_REF_RETURNS_DOM]: false }); 32 | wrapper = VTU.mount(FakeComponent); 33 | 34 | expect(wrapper.findComponent({ ref: "demo" }).exists()).toBe(false); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /tests/flags/wrapper/wrapper-set-value-does-not-trigger-change.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, beforeEach, it, expect } from "@jest/globals"; 2 | import { defineComponent } from "vue"; 3 | import { installCompat, compatFlags } from "../../../src/index.js"; 4 | import { describeOption } from "../../helpers.js"; 5 | 6 | describeOption(compatFlags.WRAPPER_SET_VALUE_DOES_NOT_TRIGGER_CHANGE, () => { 7 | let VTU; 8 | 9 | const onInput = jest.fn(); 10 | const onChange = jest.fn(); 11 | const FakeComponent = defineComponent({ 12 | methods: { onInput, onChange }, 13 | template: `
`, 14 | }); 15 | 16 | beforeEach(async () => { 17 | jest.resetModules(); 18 | VTU = await import("@vue/test-utils"); 19 | }); 20 | 21 | let wrapper; 22 | 23 | describe("when enabled", () => { 24 | it("only input event is triggered", async () => { 25 | installCompat(VTU, { [compatFlags.WRAPPER_SET_VALUE_DOES_NOT_TRIGGER_CHANGE]: true }); 26 | wrapper = VTU.mount(FakeComponent); 27 | 28 | await wrapper.find("input").setValue("test"); 29 | 30 | expect(onInput).toHaveBeenCalled(); 31 | expect(onChange).not.toHaveBeenCalled(); 32 | }); 33 | }); 34 | 35 | describe("when disabled", () => { 36 | it("both input and change events are triggered", async () => { 37 | installCompat(VTU, { [compatFlags.WRAPPER_SET_VALUE_DOES_NOT_TRIGGER_CHANGE]: false }); 38 | wrapper = VTU.mount(FakeComponent); 39 | 40 | await wrapper.find("input").setValue("test"); 41 | 42 | expect(onInput).toHaveBeenCalled(); 43 | expect(onChange).toHaveBeenCalled(); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /tests/flags/wrapper/wrapper-vue-set-value-uses-dom.test.js: -------------------------------------------------------------------------------- 1 | import { jest, describe, beforeEach, it, expect } from "@jest/globals"; 2 | import { defineComponent, h } from "vue"; 3 | import { installCompat, compatFlags } from "../../../src/index.js"; 4 | import { describeOption } from "../../helpers.js"; 5 | 6 | describeOption(compatFlags.WRAPPER_VUE_SET_VALUE_USES_DOM, () => { 7 | let VTU; 8 | 9 | const InputComponent = defineComponent({ 10 | name: "InputComponent", 11 | template: ``, 12 | }); 13 | 14 | const FakeComponent = () => h(InputComponent); 15 | 16 | beforeEach(async () => { 17 | jest.resetModules(); 18 | VTU = await import("@vue/test-utils"); 19 | }); 20 | 21 | let wrapper; 22 | 23 | describe("when enabled", () => { 24 | it("model event is triggered", async () => { 25 | installCompat(VTU, { [compatFlags.WRAPPER_VUE_SET_VALUE_USES_DOM]: true }); 26 | wrapper = VTU.mount(FakeComponent); 27 | 28 | await wrapper.findComponent(InputComponent).setValue("test"); 29 | 30 | expect(wrapper.findComponent(InputComponent).emitted().input).toBeDefined(); 31 | expect(wrapper.findComponent(InputComponent).emitted().change).toBeDefined(); 32 | expect(wrapper.findComponent(InputComponent).emitted()["update:modelValue"]).toBeUndefined(); 33 | }); 34 | }); 35 | 36 | describe("when disabled", () => { 37 | it("both input and change events are triggered", async () => { 38 | installCompat(VTU, { [compatFlags.WRAPPER_VUE_SET_VALUE_USES_DOM]: false }); 39 | wrapper = VTU.mount(FakeComponent); 40 | 41 | await wrapper.findComponent(InputComponent).setValue("test"); 42 | 43 | expect(wrapper.findComponent(InputComponent).emitted()).toStrictEqual({ "update:modelValue": [["test"]] }); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /tests/global-behavior.test.js: -------------------------------------------------------------------------------- 1 | import { describe, beforeEach, it, expect } from "@jest/globals"; 2 | import { makeVTUStub } from "./helpers.js"; 3 | 4 | describe("global behavior", () => { 5 | let VTUCompat; 6 | 7 | beforeEach(async () => { 8 | VTUCompat = await import("../src/index.js"); 9 | }); 10 | 11 | it("throws if target module is not supplied", () => { 12 | expect(() => VTUCompat.installCompat(null, {})).toThrow({ 13 | message: "Unknown module for installation", 14 | }); 15 | }); 16 | 17 | it("throws if installed with different configs multiple times", () => { 18 | const targetModule = makeVTUStub(); 19 | const fakeConfig = { GLOBAL_STUBS: true }; 20 | const fakeConfig2 = { GLOBAL_STUBS: false }; 21 | 22 | VTUCompat.installCompat(targetModule, fakeConfig); 23 | expect(() => VTUCompat.installCompat(targetModule, fakeConfig2)).toThrow({ 24 | message: 25 | "You are trying to install compat layer to vue-test-utils, but it was already installed with different config", 26 | }); 27 | }); 28 | 29 | it("should not throw if installed with same config multiple times", () => { 30 | const targetModule = makeVTUStub(); 31 | const fakeConfig = { GLOBAL_STUBS: true }; 32 | const fakeConfig2 = { GLOBAL_STUBS: true }; // structurally same 33 | 34 | VTUCompat.installCompat(targetModule, fakeConfig); 35 | VTUCompat.installCompat(targetModule, fakeConfig2); 36 | }); 37 | 38 | it("throws error if unknown option is supplied", () => { 39 | const targetModule = makeVTUStub(); 40 | expect(() => VTUCompat.installCompat(targetModule, { UNKNOWN_OPTION: true })).toThrow(); 41 | }); 42 | 43 | it("throws error if compat config is missing", () => { 44 | const targetModule = makeVTUStub(); 45 | expect(() => VTUCompat.installCompat(targetModule)).toThrow(); 46 | }); 47 | 48 | it("throws error if MOUNT_ARGS_SCOPED_SLOTS_THIS is used but vueH is not passed", () => { 49 | const targetModule = makeVTUStub(); 50 | expect(() => 51 | VTUCompat.installCompat(targetModule, { 52 | MOUNT_ARGS_SCOPED_SLOTS: true, 53 | MOUNT_ARGS_SCOPED_SLOTS_THIS: true, 54 | }) 55 | ).toThrow(); 56 | }); 57 | 58 | it("throws error if MOUNT_ARGS_SCOPED_SLOTS_THIS is used without MOUNT_ARGS_SCOPED_SLOTS", () => { 59 | const targetModule = makeVTUStub(); 60 | expect(() => 61 | VTUCompat.installCompat( 62 | targetModule, 63 | { 64 | MOUNT_ARGS_SCOPED_SLOTS: false, 65 | MOUNT_ARGS_SCOPED_SLOTS_THIS: true, 66 | }, 67 | () => {} 68 | ) 69 | ).toThrow(); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /tests/helpers.js: -------------------------------------------------------------------------------- 1 | import { defineComponent } from "vue"; 2 | import { describe, jest } from "@jest/globals"; 3 | 4 | export const makeVTUStub = () => ({ 5 | config: { 6 | global: { 7 | stubs: {}, 8 | }, 9 | }, 10 | mount: jest.fn(), 11 | shallowMount: jest.fn(), 12 | enableAutoUnmount: jest.fn(), 13 | disableAutoUnmount: jest.fn(), 14 | RouterLinkStub: defineComponent({ template: "
stub { 22 | /* istanbul ignore if */ 23 | if (!option) { 24 | throw new Error("Invalid option"); 25 | } 26 | 27 | return describe(`option ${option}`, fn); 28 | }; 29 | --------------------------------------------------------------------------------