Skip to content

Commit e877338

Browse files
committed
fix: refactor Vue initialization in app frame
Refactors how the Vue instance within the component app frame is initialized, and fully fixes Vue integration. - fixes lingering issues after pull request cypress-io#13 - fully fixes cypress-io#6 - fixes #1 - README docs have been updated to reflect refactoring - 'vue' option has been deprecated (with warning to user) as it's no longer necessary - spread operator support has been added for upcoming Vuex example
1 parent 45e5276 commit e877338

File tree

4 files changed

+92
-95
lines changed

4 files changed

+92
-95
lines changed

README.md

+17-6
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,11 @@ See examples below for details.
4545
See [cypress/integration/options-spec.js](cypress/integration/options-spec.js)
4646
for examples of options.
4747

48-
* `vue` - path or URL to the Vue library to load. By default, will
49-
try to load `../node_modules/vue/dist/vue.js`, but you can pass your
50-
own path or URL.
48+
* `mountId` - specify root Vue app mount element ID. Defaults to `app`.
5149

5250
```js
5351
const options = {
54-
vue: 'https://unpkg.com/vue'
52+
mountId: 'rootApp' // div#rootApp
5553
}
5654
beforeEach(mountVue(/* my Vue code */, options))
5755
```
@@ -71,12 +69,25 @@ beforeEach(mountVue(/* my Vue code */, options))
7169
place to load additional libraries, polyfills and styles.
7270

7371
```js
74-
const vue = '../node_modules/vue/dist/vue.js'
72+
const polyfill = '../node_modules/mypolyfill/dist/polyfill.js'
7573
const options = {
76-
html: `<div id="app"></div><script src="${vue}"></script>`
74+
html: `<div id="app"></div><script src="${polyfill}"></script>`
75+
}
76+
beforeEach(mountVue(/* my Vue code */, options))
77+
```
78+
79+
* `vue` **[DEPRECATED]** - path or URL to the Vue library to load. By default, will
80+
try to load `../node_modules/vue/dist/vue.js`, but you can pass your
81+
own path or URL.
82+
83+
```js
84+
const options = {
85+
vue: 'https://unpkg.com/vue'
7786
}
7887
beforeEach(mountVue(/* my Vue code */, options))
7988
```
89+
> #### Deprecation Warning
90+
> `vue` option has been deprecated. `node_modules/vue/dist/vue` is always used.
8091
8192
### Global Vue extensions
8293

components/.babelrc

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"plugins": [
3+
"transform-object-rest-spread"
4+
]
5+
}

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"generateNotes": "github-post-release"
7272
},
7373
"devDependencies": {
74+
"babel-plugin-transform-object-rest-spread": "6.26.0",
7475
"ban-sensitive-files": "1.9.2",
7576
"css-loader": "0.28.7",
7677
"cypress": "1.4.1",
@@ -86,7 +87,6 @@
8687
"semantic-action": "1.1.0",
8788
"simple-commit-message": "3.3.2",
8889
"standard": "10.0.3",
89-
"vue": "2.5.13",
9090
"vue-loader": "13.6.1",
9191
"vue-template-compiler": "2.5.13",
9292
"vuex": "3.0.1"
@@ -100,6 +100,7 @@
100100
},
101101
"dependencies": {
102102
"@cypress/webpack-preprocessor": "1.1.2",
103-
"common-tags": "1.6.0"
103+
"common-tags": "1.6.0",
104+
"vue": "2.5.13"
104105
}
105106
}

src/index.js

+67-87
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1+
const Vue = require('vue/dist/vue')
12
const { stripIndent } = require('common-tags')
23

4+
// mountVue options
5+
const defaultOptions = ['html', 'vue', 'base', 'mountId', 'extensions']
6+
7+
// default mount point element ID for root Vue instance
8+
const defaultMountId = 'app'
9+
10+
const parentDocument = window.parent.document
11+
const projectName = Cypress.config('projectName')
12+
const appIframeId = `Your App: '${projectName}'`
13+
const appIframe = parentDocument.getElementById(appIframeId)
14+
315
// having weak reference to styles prevents garbage collection
416
// and "losing" styles when the next test starts
517
const stylesCache = new Map()
@@ -23,10 +35,6 @@ const copyStyles = component => {
2335
return
2436
}
2537

26-
const parentDocument = window.parent.document
27-
const projectName = Cypress.config('projectName')
28-
const appIframeId = `Your App: '${projectName}'`
29-
const appIframe = parentDocument.getElementById(appIframeId)
3038
const head = appIframe.contentDocument.querySelector('head')
3139
styles.forEach(style => {
3240
head.appendChild(style)
@@ -42,59 +50,20 @@ const deleteCachedConstructors = component => {
4250
Cypress._.values(component.components).forEach(deleteConstructor)
4351
}
4452

45-
const getVuePath = options =>
46-
options.vue || options.vuePath || '../node_modules/vue/dist/vue.js'
47-
48-
const getVuexPath = options => options.vuex || options.vuexPath
49-
5053
const getPageHTML = options => {
51-
if (options.html) {
52-
return options.html
53-
}
54-
const vue = getVuePath(options)
55-
let vuex = getVuexPath(options)
56-
if (vuex) {
57-
vuex = `<script src="${vuex}"></script>`
58-
}
59-
60-
// note: add "base" tag to force loading static assets
61-
// from the server, not from the "spec" file URL
62-
if (options.base) {
63-
if (vue.startsWith('.')) {
64-
console.error(
65-
'You are using base tag %s and relative Vue path %s',
66-
options.base,
67-
vue
68-
)
69-
console.error('the relative path might NOT work')
70-
console.error(
71-
'maybe pass Vue url using "https://unpkg.com/vue/dist/vue.js"'
72-
)
73-
}
74-
return stripIndent`
75-
<html>
76-
<head>
77-
<base href="${options.base}" />
78-
</head>
79-
<body>
80-
<div id="app"></div>
81-
<script src="${vue}"></script>
82-
</body>
83-
</html>
84-
`
85-
}
86-
87-
const vueHtml = stripIndent`
54+
return (
55+
options.html ||
56+
stripIndent`
8857
<html>
89-
<head></head>
58+
<head>
59+
${options.base ? `<base href="${options.base}" />` : ''}
60+
</head>
9061
<body>
91-
<div id="app"></div>
92-
<script src="${vue}"></script>
93-
${vuex || ''}
62+
<div id="${options.mountId || defaultMountId}"></div>
9463
</body>
9564
</html>
9665
`
97-
return vueHtml
66+
)
9867
}
9968

10069
const registerGlobalComponents = (Vue, options) => {
@@ -128,18 +97,16 @@ const installMixins = (Vue, options) => {
12897
}
12998
}
13099

131-
const isOptionName = name =>
132-
['vue', 'html', 'vuePath', 'base', 'extensions'].includes(name)
100+
const isOptionName = name => defaultOptions.includes(name)
133101

134102
const isOptions = object => Object.keys(object).every(isOptionName)
135103

136104
const isConstructor = object => object && object._compiled
137105

138106
const hasStore = ({ store }) => store && store._vm
139107

140-
const forEachValue = (obj, fn) => {
108+
const forEachValue = (obj, fn) =>
141109
Object.keys(obj).forEach(key => fn(obj[key], key))
142-
}
143110

144111
const resetStoreVM = (Vue, { store }) => {
145112
// bind store public getters
@@ -177,38 +144,51 @@ const mountVue = (component, optionsOrProps = {}) => () => {
177144
props = optionsOrProps
178145
}
179146

180-
const vueHtml = getPageHTML(options)
181-
const document = cy.state('document')
182-
document.write(vueHtml)
183-
document.close()
184-
185-
// TODO: do not log out "its(Vue)" command
186-
// but it currently does not support it
187-
return cy
188-
.window({ log: false })
189-
.its('Vue')
190-
.then(Vue => {
191-
// refresh inner Vue instance of Vuex store
192-
if (hasStore(component)) {
193-
component.store = resetStoreVM(Vue, component)
194-
}
195-
installMixins(Vue, options)
196-
installPlugins(Vue, options)
197-
registerGlobalComponents(Vue, options)
198-
deleteCachedConstructors(component)
199-
200-
if (isConstructor(component)) {
201-
const Cmp = Vue.extend(component)
202-
Cypress.vue = new Cmp(props).$mount('#app')
203-
copyStyles(Cmp)
204-
} else {
205-
const allOptions = Object.assign({}, component, { el: '#app' })
206-
Cypress.vue = new Vue(allOptions)
207-
copyStyles(component)
208-
}
209-
210-
return Cypress.vue
211-
})
147+
// display deprecation warnings
148+
if (options.vue) {
149+
console.warn(stripIndent`
150+
[DEPRECATION]: 'vue' option has been deprecated.
151+
'node_modules/vue/dis/vue' is always used.
152+
Please remove it from your 'mountVue' options.`)
153+
}
154+
155+
// insert base app template
156+
const doc = appIframe.contentDocument
157+
doc.write(getPageHTML(options))
158+
doc.close()
159+
160+
// get root Vue mount element
161+
const mountId = options.mountId || defaultMountId
162+
const el = doc.getElementById(mountId)
163+
164+
// set global Vue instance:
165+
// 1. convenience for debugging in DevTools
166+
// 2. some libraries might check for this global
167+
appIframe.contentWindow.Vue = Vue
168+
169+
// refresh inner Vue instance of Vuex store
170+
if (hasStore(component)) {
171+
component.store = resetStoreVM(Vue, component)
172+
}
173+
174+
// setup Vue instance
175+
installMixins(Vue, options)
176+
installPlugins(Vue, options)
177+
registerGlobalComponents(Vue, options)
178+
deleteCachedConstructors(component)
179+
180+
// create root Vue component
181+
// and make it accessible via Cypress.vue
182+
if (isConstructor(component)) {
183+
const Cmp = Vue.extend(component)
184+
Cypress.vue = new Cmp(props).$mount(el)
185+
copyStyles(Cmp)
186+
} else {
187+
Cypress.vue = new Vue(component).$mount(el)
188+
copyStyles(component)
189+
}
190+
191+
return cy.wrap(Cypress.vue)
212192
}
213193

214194
module.exports = mountVue

0 commit comments

Comments
 (0)