diff --git a/docs/api/mount.md b/docs/api/mount.md
index 240c15b97..e52129576 100644
--- a/docs/api/mount.md
+++ b/docs/api/mount.md
@@ -55,10 +55,13 @@ import Foo from './Foo.vue'
describe('Foo', () => {
it('renders a div', () => {
+ const div = document.createElement('div')
+ document.body.appendChild(div)
const wrapper = mount(Foo, {
- attachToDocument: true
+ attachTo: div
})
expect(wrapper.contains('div')).toBe(true)
+ wrapper.destroy()
})
})
```
diff --git a/docs/api/options.md b/docs/api/options.md
index f3b13fce5..2b8f85904 100644
--- a/docs/api/options.md
+++ b/docs/api/options.md
@@ -16,6 +16,7 @@ These options will be merged with the component's existing options when mounted
- [`stubs`](#stubs)
- [`mocks`](#mocks)
- [`localVue`](#localvue)
+- [`attachTo`](#attachto)
- [`attachToDocument`](#attachtodocument)
- [`propsData`](#propsdata)
- [`attrs`](#attrs)
@@ -288,12 +289,41 @@ const wrapper = mount(Component, {
expect(wrapper.vm.$route).toBeInstanceOf(Object)
```
+## attachTo
+
+- type: `HTMLElement | string`
+- default: `null`
+
+This either specifies a specific HTMLElement or CSS selector string targeting an
+HTMLElement, to which your component will be fully mounted in the document.
+
+When attaching to the DOM, you should call `wrapper.destroy()` at the end of your test to
+remove the rendered elements from the document and destroy the component instance.
+
+```js
+const Component = {
+ template: '
ABC
',
+ props: ['msg']
+}
+let wrapper = mount(Component, {
+ attachTo: '#root'
+})
+expect(wrapper.vm.$el.parentNode).to.not.be.null
+wrapper.destroy()
+
+wrapper = mount(Component, {
+ attachTo: document.getElementById('root')
+})
+expect(wrapper.vm.$el.parentNode).to.not.be.null
+wrapper.destroy()
+```
+
## attachToDocument
- type: `boolean`
- default: `false`
-Component will be attached to DOM when rendered if set to `true`.
+Like [`attachTo`](#attachto), but automatically creates a new `div` element for you and inserts it into the body. This is deprecated in favor of [`attachTo`](#attachto).
When attaching to the DOM, you should call `wrapper.destroy()` at the end of your test to
remove the rendered elements from the document and destroy the component instance.
diff --git a/docs/api/shallowMount.md b/docs/api/shallowMount.md
index a9d74fa70..74c3be5e2 100644
--- a/docs/api/shallowMount.md
+++ b/docs/api/shallowMount.md
@@ -4,6 +4,7 @@
- `{Component} component`
- `{Object} options`
+ - `{HTMLElement|string} string`
- `{boolean} attachToDocument`
- `{Object} context`
- `{Array|Component} children`
@@ -64,10 +65,13 @@ import Foo from './Foo.vue'
describe('Foo', () => {
it('renders a div', () => {
+ const div = document.createElement('div')
+ document.body.appendChild(div)
const wrapper = shallowMount(Foo, {
- attachToDocument: true
+ attachTo: div
})
expect(wrapper.contains('div')).toBe(true)
+ wrapper.destroy()
})
})
```
diff --git a/docs/api/wrapper/destroy.md b/docs/api/wrapper/destroy.md
index 5857c1867..1808d7a2d 100644
--- a/docs/api/wrapper/destroy.md
+++ b/docs/api/wrapper/destroy.md
@@ -18,7 +18,7 @@ mount({
expect(spy.calledOnce).toBe(true)
```
-if `attachToDocument` was set to `true` when mounted, the component DOM elements will
+if either the `attachTo` or `attachToDocument` option caused the component to mount to the document, the component DOM elements will
also be removed from the document.
For functional components, `destroy` only removes the rendered DOM elements from the document.
diff --git a/flow/options.flow.js b/flow/options.flow.js
index 92f1a7231..992cf3d8b 100644
--- a/flow/options.flow.js
+++ b/flow/options.flow.js
@@ -1,6 +1,7 @@
declare type Options = {
// eslint-disable-line no-undef
attachToDocument?: boolean,
+ attachTo?: HTMLElement | string,
propsData?: Object,
mocks?: Object,
methods?: { [key: string]: Function },
@@ -17,6 +18,7 @@ declare type Options = {
}
declare type NormalizedOptions = {
+ attachTo?: HTMLElement | string,
attachToDocument?: boolean,
propsData?: Object,
mocks: Object,
diff --git a/packages/shared/validate-options.js b/packages/shared/validate-options.js
index 1ad9c90ac..452d64eb4 100644
--- a/packages/shared/validate-options.js
+++ b/packages/shared/validate-options.js
@@ -1,11 +1,13 @@
import {
isPlainObject,
isFunctionalComponent,
- isConstructor
+ isConstructor,
+ isDomSelector,
+ isHTMLElement
} from './validators'
import { VUE_VERSION } from './consts'
import { compileTemplateForSlots } from './compile-template'
-import { throwError } from './util'
+import { throwError, warn } from './util'
import { validateSlots } from './validate-slots'
function vueExtendUnsupportedOption(option) {
@@ -22,6 +24,20 @@ function vueExtendUnsupportedOption(option) {
const UNSUPPORTED_VERSION_OPTIONS = ['mocks', 'stubs', 'localVue']
export function validateOptions(options, component) {
+ if (
+ options.attachTo &&
+ !isHTMLElement(options.attachTo) &&
+ !isDomSelector(options.attachTo)
+ ) {
+ throwError(
+ `options.attachTo should be a valid HTMLElement or CSS selector string`
+ )
+ }
+ if ('attachToDocument' in options) {
+ warn(
+ `options.attachToDocument is deprecated in favor of options.attachTo and will be removed in a future release`
+ )
+ }
if (options.parentComponent && !isPlainObject(options.parentComponent)) {
throwError(
`options.parentComponent should be a valid Vue component options object`
diff --git a/packages/shared/validators.js b/packages/shared/validators.js
index 09fcb384b..55da960d6 100644
--- a/packages/shared/validators.js
+++ b/packages/shared/validators.js
@@ -112,6 +112,14 @@ export function isPlainObject(c: any): boolean {
return Object.prototype.toString.call(c) === '[object Object]'
}
+export function isHTMLElement(c: any): boolean {
+ if (typeof HTMLElement === 'undefined') {
+ return false
+ }
+ // eslint-disable-next-line no-undef
+ return c instanceof HTMLElement
+}
+
export function isRequiredComponent(name: string): boolean {
return (
name === 'KeepAlive' || name === 'Transition' || name === 'TransitionGroup'
diff --git a/packages/test-utils/src/mount.js b/packages/test-utils/src/mount.js
index c6dab106d..0a0ef4128 100644
--- a/packages/test-utils/src/mount.js
+++ b/packages/test-utils/src/mount.js
@@ -28,7 +28,8 @@ export default function mount(component, options = {}) {
const parentVm = createInstance(component, mergedOptions, _Vue)
- const el = options.attachToDocument ? createElement() : undefined
+ const el =
+ options.attachTo || (options.attachToDocument ? createElement() : undefined)
const vm = parentVm.$mount(el)
component._Ctor = {}
@@ -36,7 +37,7 @@ export default function mount(component, options = {}) {
throwIfInstancesThrew(vm)
const wrapperOptions = {
- attachedToDocument: !!mergedOptions.attachToDocument
+ attachedToDocument: !!el
}
const root = parentVm.$options._isFunctionalContainer
diff --git a/test/specs/mount.spec.js b/test/specs/mount.spec.js
index 279f9224a..d9869c512 100644
--- a/test/specs/mount.spec.js
+++ b/test/specs/mount.spec.js
@@ -293,6 +293,7 @@ describeRunIf(process.env.TEST_ENV !== 'node', 'mount', () => {
expect(wrapper.vm.$options.context).to.equal(undefined)
expect(wrapper.vm.$options.attrs).to.equal(undefined)
expect(wrapper.vm.$options.listeners).to.equal(undefined)
+ wrapper.destroy()
})
itDoNotRunIf(vueVersion < 2.3, 'injects store correctly', () => {
@@ -366,6 +367,7 @@ describeRunIf(process.env.TEST_ENV !== 'node', 'mount', () => {
' \n' +
''
)
+ wrapper.destroy()
})
it('overwrites the component options with the instance options', () => {
diff --git a/test/specs/mounting-options/attachTo.spec.js b/test/specs/mounting-options/attachTo.spec.js
new file mode 100644
index 000000000..ff519812a
--- /dev/null
+++ b/test/specs/mounting-options/attachTo.spec.js
@@ -0,0 +1,77 @@
+import { describeWithShallowAndMount } from '~resources/utils'
+
+const innerHTML = 'Hello world'
+const outerHTML = `