From 037a643627ae3e21a56e39113e700670eddda789 Mon Sep 17 00:00:00 2001 From: Joel Denning Date: Mon, 13 Apr 2020 16:29:41 -0600 Subject: [PATCH 1/7] feat(history): Remove event listeners when all apps are destroyed. --- examples/basic/app.js | 29 +++++++++++++++++++++++++++ examples/hash-mode/app.js | 33 ++++++++++++++++++++++++++++++- src/history/base.js | 15 ++++++++++++++ src/history/hash.js | 41 ++++++++++++++++++++++++--------------- src/history/html5.js | 25 ++++++++++++++++++++---- src/index.js | 20 +++++++++---------- src/util/scroll.js | 17 ++++++++++------ 7 files changed, 142 insertions(+), 38 deletions(-) diff --git a/examples/basic/app.js b/examples/basic/app.js index 6033475de..96fab9ab1 100644 --- a/examples/basic/app.js +++ b/examples/basic/app.js @@ -1,6 +1,30 @@ import Vue from 'vue' import VueRouter from 'vue-router' +// track number of popstate listeners +// This should be replaced with something better +let numPopstateListeners = 0 +const listenerCountDiv = document.createElement('div') +listenerCountDiv.textContent = numPopstateListeners + ' popstate listeners' +document.body.appendChild(listenerCountDiv) + +const originalAddEventListener = window.addEventListener +const originalRemoveEventListener = window.removeEventListener +window.addEventListener = function (name, handler) { + if (name === 'popstate') { + listenerCountDiv.textContent = + ++numPopstateListeners + ' popstate listeners' + } + return originalAddEventListener.apply(this, arguments) +} +window.removeEventListener = function (name, handler) { + if (name === 'popstate') { + listenerCountDiv.textContent = + --numPopstateListeners + ' popstate listeners' + } + return originalRemoveEventListener.apply(this, arguments) +} + // 1. Use plugin. // This installs and , // and injects $router and $route to all router-enabled child components @@ -55,6 +79,7 @@ new Vue({
{{ $route.query.t }}
{{ $route.hash }}
+ `, @@ -66,6 +91,10 @@ new Vue({ } else { this.$router.push('/', increment) } + }, + teardown () { + this.$destroy() + this.$el.innerHTML = '' } } }).$mount('#app') diff --git a/examples/hash-mode/app.js b/examples/hash-mode/app.js index c8081cf2a..035d55f51 100644 --- a/examples/hash-mode/app.js +++ b/examples/hash-mode/app.js @@ -1,6 +1,30 @@ import Vue from 'vue' import VueRouter from 'vue-router' +// track number of popstate listeners +// This should be replaced with something better +let numPopstateListeners = 0 +const listenerCountDiv = document.createElement('div') +listenerCountDiv.textContent = numPopstateListeners + ' popstate listeners' +document.body.appendChild(listenerCountDiv) + +const originalAddEventListener = window.addEventListener +const originalRemoveEventListener = window.removeEventListener +window.addEventListener = function (name, handler) { + if (name === 'popstate') { + listenerCountDiv.textContent = + ++numPopstateListeners + ' popstate listeners' + } + return originalAddEventListener.apply(this, arguments) +} +window.removeEventListener = function (name, handler) { + if (name === 'popstate') { + listenerCountDiv.textContent = + --numPopstateListeners + ' popstate listeners' + } + return originalRemoveEventListener.apply(this, arguments) +} + // 1. Use plugin. // This installs and , // and injects $router and $route to all router-enabled child components @@ -46,6 +70,13 @@ new Vue({
{{ $route.query.t }}
{{ $route.hash }}
+ - ` + `, + methods: { + teardown () { + this.$destroy() + this.$el.innerHTML = '' + } + } }).$mount('#app') diff --git a/src/history/base.js b/src/history/base.js index 2b12e1967..7680edb6d 100644 --- a/src/history/base.js +++ b/src/history/base.js @@ -23,6 +23,8 @@ export class History { readyCbs: Array readyErrorCbs: Array errorCbs: Array + listeners: Array + cleanupListeners: Function // implemented by sub-classes +go: (n: number) => void @@ -30,6 +32,7 @@ export class History { +replace: (loc: RawLocation) => void +ensureURL: (push?: boolean) => void +getCurrentLocation: () => string + +setupListeners: Function constructor (router: Router, base: ?string) { this.router = router @@ -41,6 +44,7 @@ export class History { this.readyCbs = [] this.readyErrorCbs = [] this.errorCbs = [] + this.listeners = [] } listen (cb: Function) { @@ -208,6 +212,17 @@ export class History { hook && hook(route, prev) }) } + + setupListeners () { + // Default implementation is empty + } + + teardownListeners () { + this.listeners.forEach(cleanupListener => { + cleanupListener() + }) + this.listeners = [] + } } function normalizeBase (base: ?string): string { diff --git a/src/history/hash.js b/src/history/hash.js index 62b9a3472..b3372e10d 100644 --- a/src/history/hash.js +++ b/src/history/hash.js @@ -20,31 +20,40 @@ export class HashHistory extends History { // this is delayed until the app mounts // to avoid the hashchange listener being fired too early setupListeners () { + if (this.listeners.length > 0) { + return + } + const router = this.router const expectScroll = router.options.scrollBehavior const supportsScroll = supportsPushState && expectScroll if (supportsScroll) { - setupScroll() + this.listeners.push(setupScroll()) } - window.addEventListener( - supportsPushState ? 'popstate' : 'hashchange', - () => { - const current = this.current - if (!ensureSlash()) { - return - } - this.transitionTo(getHash(), route => { - if (supportsScroll) { - handleScroll(this.router, route, current, true) - } - if (!supportsPushState) { - replaceHash(route.fullPath) - } - }) + const handleRoutingEvent = () => { + const current = this.current + if (!ensureSlash()) { + return } + this.transitionTo(getHash(), route => { + if (supportsScroll) { + handleScroll(this.router, route, current, true) + } + if (!supportsPushState) { + replaceHash(route.fullPath) + } + }) + } + const eventType = supportsPushState ? 'popstate' : 'hashchange' + window.addEventListener( + eventType, + handleRoutingEvent ) + this.listeners.push(() => { + window.removeEventListener(eventType, handleRoutingEvent) + }) } push (location: RawLocation, onComplete?: Function, onAbort?: Function) { diff --git a/src/history/html5.js b/src/history/html5.js index e1cdba97d..590238b86 100644 --- a/src/history/html5.js +++ b/src/history/html5.js @@ -8,24 +8,37 @@ import { setupScroll, handleScroll } from '../util/scroll' import { pushState, replaceState, supportsPushState } from '../util/push-state' export class HTML5History extends History { + +initLocation: string + constructor (router: Router, base: ?string) { super(router, base) + this.initLocation = getLocation(this.base) + } + + setupListeners () { + if (this.listeners.length > 0) { + return + } + + const router = this.router const expectScroll = router.options.scrollBehavior const supportsScroll = supportsPushState && expectScroll if (supportsScroll) { - setupScroll() + this.listeners.push(setupScroll()) } - const initLocation = getLocation(this.base) - window.addEventListener('popstate', e => { + const handleRoutingEvent = () => { + const expectScroll = router.options.scrollBehavior + const supportsScroll = supportsPushState && expectScroll + const current = this.current // Avoiding first `popstate` event dispatched in some browsers but first // history route not updated since async guard at the same time. const location = getLocation(this.base) - if (this.current === START && location === initLocation) { + if (this.current === START && location === this.initLocation) { return } @@ -34,6 +47,10 @@ export class HTML5History extends History { handleScroll(router, route, current, true) } }) + } + window.addEventListener('popstate', handleRoutingEvent) + this.listeners.push(() => { + window.removeEventListener('popstate', handleRoutingEvent) }) } diff --git a/src/index.js b/src/index.js index e95ace6d1..f512d31fe 100644 --- a/src/index.js +++ b/src/index.js @@ -98,6 +98,12 @@ export default class VueRouter { // ensure we still have a main app or null if no apps // we do not release the router so it can be reused if (this.app === app) this.app = this.apps[0] || null + + if (this.apps.length === 0) { + // clean up event listeners + // https://github.com/vuejs/vue-router/issues/2341 + this.history.teardownListeners() + } }) // main app previously initialized @@ -110,18 +116,10 @@ export default class VueRouter { const history = this.history - if (history instanceof HTML5History) { - history.transitionTo(history.getCurrentLocation()) - } else if (history instanceof HashHistory) { - const setupHashListener = () => { - history.setupListeners() - } - history.transitionTo( - history.getCurrentLocation(), - setupHashListener, - setupHashListener - ) + const setupListeners = () => { + history.setupListeners() } + history.transitionTo(history.getCurrentLocation(), setupListeners, setupListeners) history.listen(route => { this.apps.forEach((app) => { diff --git a/src/util/scroll.js b/src/util/scroll.js index c6c055a97..5693c17ee 100644 --- a/src/util/scroll.js +++ b/src/util/scroll.js @@ -19,12 +19,10 @@ export function setupScroll () { const stateCopy = extend({}, window.history.state) stateCopy.key = getStateKey() window.history.replaceState(stateCopy, '', absolutePath) - window.addEventListener('popstate', e => { - saveScrollPosition() - if (e.state && e.state.key) { - setStateKey(e.state.key) - } - }) + window.addEventListener('popstate', handlePopState) + return () => { + window.removeEventListener('popstate', handlePopState) + } } export function handleScroll ( @@ -86,6 +84,13 @@ export function saveScrollPosition () { } } +function handlePopState (e) { + saveScrollPosition() + if (e.state && e.state.key) { + setStateKey(e.state.key) + } +} + function getScrollPosition (): ?Object { const key = getStateKey() if (key) { From f923d37324ecd7097cd3970b035a264fc0f83277 Mon Sep 17 00:00:00 2001 From: Joel Denning Date: Mon, 13 Apr 2020 16:51:20 -0600 Subject: [PATCH 2/7] fix(tests): Adding test assertions --- examples/basic/app.js | 4 ++-- examples/hash-mode/app.js | 4 ++-- test/e2e/specs/basic.js | 5 +++++ test/e2e/specs/hash-mode.js | 6 ++++++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/examples/basic/app.js b/examples/basic/app.js index 96fab9ab1..34fea91db 100644 --- a/examples/basic/app.js +++ b/examples/basic/app.js @@ -2,9 +2,9 @@ import Vue from 'vue' import VueRouter from 'vue-router' // track number of popstate listeners -// This should be replaced with something better let numPopstateListeners = 0 const listenerCountDiv = document.createElement('div') +listenerCountDiv.id = 'popstate-count' listenerCountDiv.textContent = numPopstateListeners + ' popstate listeners' document.body.appendChild(listenerCountDiv) @@ -79,7 +79,7 @@ new Vue({
{{ $route.query.t }}
{{ $route.hash }}
- + `, diff --git a/examples/hash-mode/app.js b/examples/hash-mode/app.js index 035d55f51..15d7acb34 100644 --- a/examples/hash-mode/app.js +++ b/examples/hash-mode/app.js @@ -2,9 +2,9 @@ import Vue from 'vue' import VueRouter from 'vue-router' // track number of popstate listeners -// This should be replaced with something better let numPopstateListeners = 0 const listenerCountDiv = document.createElement('div') +listenerCountDiv.id = 'popstate-count' listenerCountDiv.textContent = numPopstateListeners + ' popstate listeners' document.body.appendChild(listenerCountDiv) @@ -70,7 +70,7 @@ new Vue({
{{ $route.query.t }}
{{ $route.hash }}
- + `, methods: { diff --git a/test/e2e/specs/basic.js b/test/e2e/specs/basic.js index f5e6364eb..23fe93312 100644 --- a/test/e2e/specs/basic.js +++ b/test/e2e/specs/basic.js @@ -70,6 +70,11 @@ module.exports = { .assert.cssClassPresent('li:nth-child(8)', 'exact-active') .assert.attributeEquals('li:nth-child(8) a', 'class', '') + // Listener cleanup + .assert.containsText('#popstate-count', '1 popstate listeners') + .click('#teardown-app') + .assert.containsText('#popstate-count', '0 popstate listeners') + .end() } } diff --git a/test/e2e/specs/hash-mode.js b/test/e2e/specs/hash-mode.js index be5df7d14..01994305f 100644 --- a/test/e2e/specs/hash-mode.js +++ b/test/e2e/specs/hash-mode.js @@ -57,6 +57,12 @@ module.exports = { .waitForElementVisible('#app', 1000) .assert.containsText('.view', 'unicode: ñ') .assert.containsText('#query-t', '%') + + // Listener cleanup + .assert.containsText('#popstate-count', '1 popstate listeners') + .click('#teardown-app') + .assert.containsText('#popstate-count', '0 popstate listeners') + .end() } } From 6fdde38080a05cfd282e7df616dc595fc63f14f7 Mon Sep 17 00:00:00 2001 From: Joel Denning Date: Tue, 12 May 2020 11:50:36 -0600 Subject: [PATCH 3/7] fix(feedback): addressing @posva's feedback --- src/index.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index f512d31fe..d8cc27582 100644 --- a/src/index.js +++ b/src/index.js @@ -116,10 +116,12 @@ export default class VueRouter { const history = this.history - const setupListeners = () => { - history.setupListeners() + if (history instanceof HTML5History || history instanceof HashHistory) { + const setupListeners = () => { + history.setupListeners() + } + history.transitionTo(history.getCurrentLocation(), setupListeners, setupListeners) } - history.transitionTo(history.getCurrentLocation(), setupListeners, setupListeners) history.listen(route => { this.apps.forEach((app) => { From db3e3476cebe06c40c676cc77a6b47c484e1a5e7 Mon Sep 17 00:00:00 2001 From: Joel Denning Date: Fri, 15 May 2020 09:06:51 -0600 Subject: [PATCH 4/7] fix(index): posva's feedback --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index d8cc27582..d0fb34b7b 100644 --- a/src/index.js +++ b/src/index.js @@ -99,7 +99,7 @@ export default class VueRouter { // we do not release the router so it can be reused if (this.app === app) this.app = this.apps[0] || null - if (this.apps.length === 0) { + if (!this.app) { // clean up event listeners // https://github.com/vuejs/vue-router/issues/2341 this.history.teardownListeners() From 9d0ba028559e3939a82bb9ce29c1fe6e6ebbe830 Mon Sep 17 00:00:00 2001 From: Joel Denning Date: Fri, 15 May 2020 10:11:06 -0600 Subject: [PATCH 5/7] feat(tests): adding multi-app test --- examples/index.html | 1 + examples/multi-app/app.js | 76 +++++++++++++++++++++++++++++++++++ examples/multi-app/index.html | 18 +++++++++ src/history/html5.js | 6 +-- test/e2e/specs/multi-app.js | 49 ++++++++++++++++++++++ 5 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 examples/multi-app/app.js create mode 100644 examples/multi-app/index.html create mode 100644 test/e2e/specs/multi-app.js diff --git a/examples/index.html b/examples/index.html index c0ee5bc16..864beeb3b 100644 --- a/examples/index.html +++ b/examples/index.html @@ -27,6 +27,7 @@

Vue Router Examples

  • Discrete Components
  • Nested Routers
  • Keepalive View
  • +
  • Multiple Apps
  • diff --git a/examples/multi-app/app.js b/examples/multi-app/app.js new file mode 100644 index 000000000..d6898e2f1 --- /dev/null +++ b/examples/multi-app/app.js @@ -0,0 +1,76 @@ +import Vue from 'vue' +import VueRouter from 'vue-router' + +// track number of popstate listeners +let numPopstateListeners = 0 +const listenerCountDiv = document.getElementById('popcount') +listenerCountDiv.textContent = 0 + +const originalAddEventListener = window.addEventListener +const originalRemoveEventListener = window.removeEventListener +window.addEventListener = function (name, handler) { + if (name === 'popstate') { + listenerCountDiv.textContent = + ++numPopstateListeners + } + return originalAddEventListener.apply(this, arguments) +} +window.removeEventListener = function (name, handler) { + if (name === 'popstate') { + listenerCountDiv.textContent = + --numPopstateListeners + } + return originalRemoveEventListener.apply(this, arguments) +} + +// 1. Use plugin. +// This installs and , +// and injects $router and $route to all router-enabled child components +Vue.use(VueRouter) + +const looper = [1, 2, 3] + +looper.forEach((n) => { + const mountEl = document.getElementById('mount' + n) + + mountEl.addEventListener('click', () => { + // 2. Define route components + const Home = { template: '
    home
    ' } + const Foo = { template: '
    foo
    ' } + + // 3. Create the router + const router = new VueRouter({ + mode: 'history', + base: __dirname, + routes: [ + { path: '/', component: Home }, + { path: '/foo', component: Foo } + ] + }) + + // 4. Create and mount root instance. + // Make sure to inject the router. + // Route components will be rendered inside . + new Vue({ + router, + template: ` +
    +

    Basic

    +
      +
    • /
    • +
    • /foo
    • +
    + + +
    + `, + + methods: { + teardown () { + this.$destroy() + this.$el.innerHTML = '' + } + } + }).$mount('#app-' + n) + }) +}) diff --git a/examples/multi-app/index.html b/examples/multi-app/index.html new file mode 100644 index 000000000..35a34b140 --- /dev/null +++ b/examples/multi-app/index.html @@ -0,0 +1,18 @@ + + +← Examples index + + + + + +
    + +popstate count: + +
    +
    +
    + + + \ No newline at end of file diff --git a/src/history/html5.js b/src/history/html5.js index 590238b86..008030612 100644 --- a/src/history/html5.js +++ b/src/history/html5.js @@ -8,12 +8,12 @@ import { setupScroll, handleScroll } from '../util/scroll' import { pushState, replaceState, supportsPushState } from '../util/push-state' export class HTML5History extends History { - +initLocation: string + _startLocation: string constructor (router: Router, base: ?string) { super(router, base) - this.initLocation = getLocation(this.base) + this._startLocation = getLocation(this.base) } setupListeners () { @@ -38,7 +38,7 @@ export class HTML5History extends History { // Avoiding first `popstate` event dispatched in some browsers but first // history route not updated since async guard at the same time. const location = getLocation(this.base) - if (this.current === START && location === this.initLocation) { + if (this.current === START && location === this._startLocation) { return } diff --git a/test/e2e/specs/multi-app.js b/test/e2e/specs/multi-app.js new file mode 100644 index 000000000..a6b0a2af1 --- /dev/null +++ b/test/e2e/specs/multi-app.js @@ -0,0 +1,49 @@ +const bsStatus = require('../browserstack-send-status') + +module.exports = { + ...bsStatus(), + + '@tags': ['history'], + + basic: function (browser) { + browser + .url('http://localhost:8080/multi-app/') + .waitForElementVisible('#mount1', 1000) + .assert.containsText('#popcount', '0') + .click('#mount1') + .waitForElementVisible('#app-1 > *', 1000) + .assert.containsText('#popcount', '1') + .click('#mount2') + .waitForElementVisible('#app-2 > *', 1000) + .assert.containsText('#popcount', '2') + .click('#mount3') + .waitForElementVisible('#app-3 > *', 1000) + .assert.containsText('#popcount', '3') + + // They should all be displaying the home page + .assert.containsText('#app-1', 'home') + .assert.containsText('#app-2', 'home') + .assert.containsText('#app-3', 'home') + + // Navigate to foo route + .click('#app-1 li:nth-child(2) a') + .assert.containsText('#app-1', 'foo') + + .click('#app-2 li:nth-child(2) a') + .assert.containsText('#app-2', 'foo') + + .click('#app-3 li:nth-child(2) a') + .assert.containsText('#app-3', 'foo') + + // Unmount all apps + .assert.containsText('#popcount', '3') + .click('#unmount1') + .assert.containsText('#popcount', '2') + .click('#unmount2') + .assert.containsText('#popcount', '1') + .click('#unmount3') + .assert.containsText('#popcount', '0') + + .end() + } +} From 97470df3b6466cc36db3ea99efe9b6125fb3da61 Mon Sep 17 00:00:00 2001 From: Joel Denning Date: Mon, 18 May 2020 09:06:27 -0600 Subject: [PATCH 6/7] fix(feedback): posva's feedback --- examples/multi-app/app.js | 19 +++++++++---------- examples/multi-app/index.html | 6 ++++++ src/history/html5.js | 3 --- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/examples/multi-app/app.js b/examples/multi-app/app.js index d6898e2f1..c9d28a0c7 100644 --- a/examples/multi-app/app.js +++ b/examples/multi-app/app.js @@ -31,7 +31,9 @@ Vue.use(VueRouter) const looper = [1, 2, 3] looper.forEach((n) => { + let vueInstance const mountEl = document.getElementById('mount' + n) + const unmountEl = document.getElementById('unmount' + n) mountEl.addEventListener('click', () => { // 2. Define route components @@ -51,7 +53,7 @@ looper.forEach((n) => { // 4. Create and mount root instance. // Make sure to inject the router. // Route components will be rendered inside . - new Vue({ + vueInstance = new Vue({ router, template: `
    @@ -61,16 +63,13 @@ looper.forEach((n) => {
  • /foo
  • -
    - `, - - methods: { - teardown () { - this.$destroy() - this.$el.innerHTML = '' - } - } + ` }).$mount('#app-' + n) }) + + unmountEl.addEventListener('click', () => { + vueInstance.$destroy() + vueInstance.$el.innerHTML = '' + }) }) diff --git a/examples/multi-app/index.html b/examples/multi-app/index.html index 35a34b140..96f094114 100644 --- a/examples/multi-app/index.html +++ b/examples/multi-app/index.html @@ -8,6 +8,12 @@
    + + + + +
    + popstate count:
    diff --git a/src/history/html5.js b/src/history/html5.js index 008030612..b41fb5cde 100644 --- a/src/history/html5.js +++ b/src/history/html5.js @@ -30,9 +30,6 @@ export class HTML5History extends History { } const handleRoutingEvent = () => { - const expectScroll = router.options.scrollBehavior - const supportsScroll = supportsPushState && expectScroll - const current = this.current // Avoiding first `popstate` event dispatched in some browsers but first From 1c90043463a4a4f624d405e25e44415078f72f8c Mon Sep 17 00:00:00 2001 From: Joel Denning Date: Mon, 18 May 2020 09:14:01 -0600 Subject: [PATCH 7/7] fix(feedback): unmounting apps with buttons outside of the app --- examples/basic/app.js | 12 ++++++------ examples/basic/index.html | 2 ++ examples/hash-mode/app.js | 12 ++++++------ examples/hash-mode/index.html | 2 ++ test/e2e/specs/basic.js | 2 +- test/e2e/specs/hash-mode.js | 2 +- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/examples/basic/app.js b/examples/basic/app.js index 34fea91db..ba13679f2 100644 --- a/examples/basic/app.js +++ b/examples/basic/app.js @@ -51,7 +51,7 @@ const router = new VueRouter({ // 4. Create and mount root instance. // Make sure to inject the router. // Route components will be rendered inside . -new Vue({ +const vueInstance = new Vue({ router, data: () => ({ n: 0 }), template: ` @@ -79,7 +79,6 @@ new Vue({
    {{ $route.query.t }}
    {{ $route.hash }}
    - `, @@ -91,10 +90,11 @@ new Vue({ } else { this.$router.push('/', increment) } - }, - teardown () { - this.$destroy() - this.$el.innerHTML = '' } } }).$mount('#app') + +document.getElementById('unmount').addEventListener('click', () => { + vueInstance.$destroy() + vueInstance.$el.innerHTML = '' +}) diff --git a/examples/basic/index.html b/examples/basic/index.html index 78a0c040f..695d668f5 100644 --- a/examples/basic/index.html +++ b/examples/basic/index.html @@ -1,6 +1,8 @@ ← Examples index + +
    diff --git a/examples/hash-mode/app.js b/examples/hash-mode/app.js index 15d7acb34..48b9ab1d6 100644 --- a/examples/hash-mode/app.js +++ b/examples/hash-mode/app.js @@ -52,7 +52,7 @@ const router = new VueRouter({ // 4. Create and mount root instance. // Make sure to inject the router. // Route components will be rendered inside . -new Vue({ +const vueInstance = new Vue({ router, template: `
    @@ -70,13 +70,13 @@ new Vue({
    {{ $route.query.t }}
    {{ $route.hash }}
    -
    `, methods: { - teardown () { - this.$destroy() - this.$el.innerHTML = '' - } } }).$mount('#app') + +document.getElementById('unmount').addEventListener('click', () => { + vueInstance.$destroy() + vueInstance.$el.innerHTML = '' +}) diff --git a/examples/hash-mode/index.html b/examples/hash-mode/index.html index 68e93063a..5789d784f 100644 --- a/examples/hash-mode/index.html +++ b/examples/hash-mode/index.html @@ -1,6 +1,8 @@ ← Examples index + +
    diff --git a/test/e2e/specs/basic.js b/test/e2e/specs/basic.js index 23fe93312..f4d7d6f51 100644 --- a/test/e2e/specs/basic.js +++ b/test/e2e/specs/basic.js @@ -72,7 +72,7 @@ module.exports = { // Listener cleanup .assert.containsText('#popstate-count', '1 popstate listeners') - .click('#teardown-app') + .click('#unmount') .assert.containsText('#popstate-count', '0 popstate listeners') .end() diff --git a/test/e2e/specs/hash-mode.js b/test/e2e/specs/hash-mode.js index 01994305f..569285a23 100644 --- a/test/e2e/specs/hash-mode.js +++ b/test/e2e/specs/hash-mode.js @@ -60,7 +60,7 @@ module.exports = { // Listener cleanup .assert.containsText('#popstate-count', '1 popstate listeners') - .click('#teardown-app') + .click('#unmount') .assert.containsText('#popstate-count', '0 popstate listeners') .end()