diff --git a/cypress/integration/components-tab.js b/cypress/integration/components-tab.js
index 100fe2319..fc66237a4 100644
--- a/cypress/integration/components-tab.js
+++ b/cypress/integration/components-tab.js
@@ -1,6 +1,6 @@
import { suite } from '../utils/suite'
-const baseInstanceCount = 9
+const baseInstanceCount = 10
suite('components tab', () => {
it('should detect instances inside shadow DOM', () => {
diff --git a/cypress/integration/vuex-edit.js b/cypress/integration/vuex-edit.js
index d64a89eae..5f3fd7f06 100644
--- a/cypress/integration/vuex-edit.js
+++ b/cypress/integration/vuex-edit.js
@@ -3,8 +3,10 @@ import { suite } from '../utils/suite'
suite('vuex edit', () => {
it('should edit state using the decrease button', () => {
cy.get('.vuex-tab').click()
+ cy.get('[data-id="load-vuex-state"]').click()
+
// using the decrease button
- cy.get('.data-field').eq(0)
+ cy.get('.state .data-field').eq(0)
.find('.actions .vue-ui-button').eq(1)
.click({ force: true })
.click({ force: true })
@@ -17,7 +19,7 @@ suite('vuex edit', () => {
it('should edit state using the increase button', () => {
// using the increase button
- cy.get('.data-field').eq(0).click()
+ cy.get('.state .data-field').eq(0).click()
.find('.actions .vue-ui-button').eq(2)
.click({ force: true })
.click({ force: true })
@@ -30,7 +32,7 @@ suite('vuex edit', () => {
it('should edit state using the edit input', () => {
// using the edit input
- cy.get('.data-field').eq(0).click()
+ cy.get('.state .data-field').eq(0).click()
.find('.actions .vue-ui-button').eq(0).click({ force: true })
cy.get('.edit-input').type('12')
cy.get('.edit-overlay > .actions > :nth-child(2) > .content > .vue-ui-icon').click()
@@ -40,8 +42,8 @@ suite('vuex edit', () => {
get('#counter p').contains('12')
})
- // change count back to 0
- cy.get('.data-field').eq(0).click()
+ // change count back to 1
+ cy.get('.state .data-field').eq(0).click()
.find('.actions .vue-ui-button').eq(0).click({ force: true })
cy.get('.edit-input').type('0')
cy.get('.edit-overlay > .actions > :nth-child(2) > .content > .vue-ui-icon').click()
diff --git a/cypress/integration/vuex-tab.js b/cypress/integration/vuex-tab.js
index ee7d2418c..a222fa1a5 100644
--- a/cypress/integration/vuex-tab.js
+++ b/cypress/integration/vuex-tab.js
@@ -10,7 +10,7 @@ suite('vuex tab', () => {
get('#counter p').contains('1')
})
cy.get('.vuex-tab').click()
- cy.get('.history .entry').should('have.length', 4)
+ cy.get('.history .entry').should('have.length', 5)
cy.get('[data-id="load-vuex-state"]').click()
cy.get('.recording-vuex-state').should('not.be.visible')
cy.get('.loading-vuex-state').should('not.be.visible')
@@ -18,7 +18,7 @@ suite('vuex tab', () => {
expect(el.text()).to.include('type:"DECREMENT"')
expect(el.text()).to.include('count:1')
})
- cy.get('.history .entry').eq(3).should('have.class', 'inspected').should('have.class', 'active')
+ cy.get('.history .entry').eq(4).should('have.class', 'inspected').should('have.class', 'active')
})
it('should filter state & getters', () => {
@@ -41,7 +41,7 @@ suite('vuex tab', () => {
cy.get('.history .entry[data-active="true"].active').should('have.length', 0)
cy.get('.left .search input').clear().type('/dec)/i')
- cy.get('.history .entry[data-active="true"]').should('have.length', 3)
+ cy.get('.history .entry[data-active="true"]').should('have.length', 4)
cy.get('.history .entry[data-active="true"].inspected').should('have.length', 0)
cy.get('.history .entry[data-active="true"].active').should('have.length', 1)
@@ -65,19 +65,19 @@ suite('vuex tab', () => {
})
it('should time-travel', () => {
- cy.get('.history .entry[data-index="2"] .entry-actions .action-time-travel').click({ force: true })
- cy.get('.history .entry[data-index="2"]')
+ cy.get('.history .entry[data-index="3"] .entry-actions .action-time-travel').click({ force: true })
+ cy.get('.history .entry[data-index="3"]')
.should('have.class', 'inspected')
.should('have.class', 'active')
cy.get('#target').iframe().then(({ get }) => {
get('#counter p').contains('2')
})
- cy.get('.history .entry[data-index="1"] .mutation-type').click({ force: true })
- cy.get('.history .entry[data-index="1"]')
+ cy.get('.history .entry[data-index="2"] .mutation-type').click({ force: true })
+ cy.get('.history .entry[data-index="2"]')
.should('have.class', 'inspected')
.should('not.have.class', 'active')
- cy.get('.history .entry[data-index="2"]')
+ cy.get('.history .entry[data-index="3"]')
.should('not.have.class', 'inspected')
.should('have.class', 'active')
cy.get('.recording-vuex-state').should('not.be.visible')
@@ -90,11 +90,11 @@ suite('vuex tab', () => {
cy.get('#target').iframe().then(({ get }) => {
get('#counter p').contains('2')
})
- cy.get('.history .entry[data-index="1"] .entry-actions .action-time-travel').click({ force: true })
- cy.get('.history .entry[data-index="1"]')
+ cy.get('.history .entry[data-index="2"] .entry-actions .action-time-travel').click({ force: true })
+ cy.get('.history .entry[data-index="2"]')
.should('have.class', 'inspected')
.should('have.class', 'active')
- cy.get('.history .entry[data-index="2"]')
+ cy.get('.history .entry[data-index="3"]')
.should('not.have.class', 'inspected')
.should('not.have.class', 'active')
cy.get('#target').iframe().then(({ get }) => {
@@ -112,8 +112,8 @@ suite('vuex tab', () => {
cy.get('#target').iframe().then(({ get }) => {
get('#counter p').contains('1')
})
- cy.get('.history .entry[data-index="0"] .entry-actions .action-time-travel').click({ force: true })
- cy.get('.history .entry[data-index="0"]')
+ cy.get('.history .entry[data-index="1"] .entry-actions .action-time-travel').click({ force: true })
+ cy.get('.history .entry[data-index="1"]')
.should('have.class', 'inspected')
.should('have.class', 'active')
cy.get('#target').iframe().then(({ get }) => {
@@ -122,10 +122,10 @@ suite('vuex tab', () => {
})
it('should revert', () => {
- cy.get('.history .entry[data-index="3"] .mutation-type').click({ force: true })
- cy.get('.history .entry[data-index="3"]').find('.action-revert').click({ force: true })
- cy.get('.history .entry[data-active="true"]').should('have.length', 3)
- cy.get('.history .entry[data-index="2"]')
+ cy.get('.history .entry[data-index="4"] .mutation-type').click({ force: true })
+ cy.get('.history .entry[data-index="4"]').find('.action-revert').click({ force: true })
+ cy.get('.history .entry[data-active="true"]').should('have.length', 4)
+ cy.get('.history .entry[data-index="3"]')
.should('have.class', 'inspected')
.should('have.class', 'active')
cy.get('.vuex-state-inspector').then(el => {
@@ -137,8 +137,7 @@ suite('vuex tab', () => {
})
it('should commit', () => {
- cy.get('.history .entry[data-index="2"] .mutation-type').click({ force: true })
- cy.get('.history .entry[data-index="2"] .action-commit').click({ force: true })
+ cy.get('.history .entry[data-index="3"] .action-commit').click({ force: true })
cy.get('.history .entry[data-active="true"]').should('have.length', 1)
cy.get('.history .entry[data-index="0"]')
.should('have.class', 'inspected')
@@ -152,20 +151,23 @@ suite('vuex tab', () => {
})
it('should display getters', () => {
- cy.get('.vuex-state-inspector').then(el => {
- expect(el.text()).to.include('isPositive:true')
+ cy.get('.vuex-state-inspector').within(() => {
+ cy.get('.key').contains('count').parent().contains('2')
+ cy.get('.key').contains('isPositive').parent().contains('true')
})
cy.get('#target').iframe().then(({ get }) => {
get('.decrement')
.click({ force: true })
.click({ force: true })
.click({ force: true })
+ get('#counter p').contains('-1')
})
cy.get('.history .entry[data-index="3"]').click({ force: true })
cy.get('.recording-vuex-state').should('not.be.visible')
cy.get('.loading-vuex-state').should('not.be.visible')
- cy.get('.vuex-state-inspector').then(el => {
- expect(el.text()).to.include('isPositive:false')
+ cy.get('.vuex-state-inspector').within(() => {
+ cy.get('.key').contains('count').parent().contains('-1')
+ cy.get('.key').contains('isPositive').parent().contains('false')
})
})
@@ -178,7 +180,7 @@ suite('vuex tab', () => {
cy.get('#target').iframe().then(({ get }) => {
get('.increment').click({ force: true })
})
- cy.get('.history .entry').should('have.length', 4)
+ cy.get('.history .entry').should('have.length', 5)
})
it('should copy vuex state', () => {
diff --git a/shells/dev/target/Init.vue b/shells/dev/target/Init.vue
new file mode 100644
index 000000000..e4f1a58b1
--- /dev/null
+++ b/shells/dev/target/Init.vue
@@ -0,0 +1,20 @@
+
+
+
+
+
diff --git a/shells/dev/target/index.js b/shells/dev/target/index.js
index 71832abba..09e312349 100644
--- a/shells/dev/target/index.js
+++ b/shells/dev/target/index.js
@@ -2,6 +2,7 @@ import Vue from 'vue'
import store from './store'
import Target from './Target.vue'
import Other from './Other.vue'
+import Init from './Init.vue'
import Counter from './Counter.vue'
import VuexObject from './VuexObject.vue'
import NativeTypes from './NativeTypes.vue'
@@ -39,7 +40,8 @@ new Vue({
h(Events, { key: 'foo' }),
h(NativeTypes, { key: new Date() }),
h(Router, { key: [] }),
- h(VuexObject)
+ h(VuexObject),
+ h(Init)
])
}
}).$mount('#app')
diff --git a/shells/dev/target/store.js b/shells/dev/target/store.js
index f4eab0516..34a68a52f 100644
--- a/shells/dev/target/store.js
+++ b/shells/dev/target/store.js
@@ -5,6 +5,7 @@ Vue.use(Vuex)
export default new Vuex.Store({
state: {
+ inited: 0,
count: 0,
date: new Date(),
set: new Set(),
@@ -21,6 +22,7 @@ export default new Vuex.Store({
}
},
mutations: {
+ TEST_INIT: state => state.inited++,
INCREMENT: state => state.count++,
DECREMENT: state => state.count--,
UPDATE_DATE: state => {
diff --git a/src/backend/hook.js b/src/backend/hook.js
index d7dc95b0f..6f23ce232 100644
--- a/src/backend/hook.js
+++ b/src/backend/hook.js
@@ -17,19 +17,36 @@ export function installHook (window) {
const hook = {
Vue: null,
+ _buffer: [],
+
+ _replayBuffer (event) {
+ let buffer = this._buffer
+ this._buffer = []
+
+ for (let i = 0, l = buffer.length; i < l; i++) {
+ let allArgs = buffer[i]
+ allArgs[0] === event
+ ? this.emit.apply(this, allArgs)
+ : this._buffer.push(allArgs)
+ }
+ },
+
on (event, fn) {
- event = '$' + event
- ;(listeners[event] || (listeners[event] = [])).push(fn)
+ const $event = '$' + event
+ if (listeners[$event]) {
+ listeners[$event].push(fn)
+ } else {
+ listeners[$event] = [fn]
+ this._replayBuffer(event)
+ }
},
once (event, fn) {
- const eventAlias = event
- event = '$' + event
function on () {
- this.off(eventAlias, on)
+ this.off(event, on)
fn.apply(this, arguments)
}
- ;(listeners[event] || (listeners[event] = [])).push(on)
+ this.on(event, on)
},
off (event, fn) {
@@ -55,14 +72,17 @@ export function installHook (window) {
},
emit (event) {
- event = '$' + event
- let cbs = listeners[event]
+ const $event = '$' + event
+ let cbs = listeners[$event]
if (cbs) {
- const args = [].slice.call(arguments, 1)
+ const eventArgs = [].slice.call(arguments, 1)
cbs = cbs.slice()
for (let i = 0, l = cbs.length; i < l; i++) {
- cbs[i].apply(this, args)
+ cbs[i].apply(this, eventArgs)
}
+ } else {
+ const allArgs = [].slice.call(arguments)
+ this._buffer.push(allArgs)
}
}
}
@@ -78,6 +98,7 @@ export function installHook (window) {
hook.once('vuex:init', store => {
hook.store = store
+ hook.initialStore = clone(store)
})
Object.defineProperty(window, '__VUE_DEVTOOLS_GLOBAL_HOOK__', {
@@ -85,4 +106,164 @@ export function installHook (window) {
return hook
}
})
+
+ // Clone deep utility for cloning initial state of the store
+ // REFERENCE: https://github.com/buunguyen/node-clone/commit/63afda9de9d94b9332586e34a646a13e8d719244
+
+ function clone (parent, circular, depth, prototype) {
+ if (typeof circular === 'object') {
+ depth = circular.depth
+ prototype = circular.prototype
+ circular = circular.circular
+ }
+ // maintain two arrays for circular references, where corresponding parents
+ // and children have the same index
+ var allParents = []
+ var allChildren = []
+
+ var useBuffer = typeof Buffer !== 'undefined'
+
+ if (typeof circular === 'undefined') { circular = true }
+
+ if (typeof depth === 'undefined') { depth = Infinity }
+
+ // recurse this function so we don't reset allParents and allChildren
+ function _clone (parent, depth) {
+ // cloning null always returns null
+ if (parent === null) { return null }
+
+ if (depth === 0) { return parent }
+
+ var child
+ var proto
+ if (typeof parent !== 'object') {
+ return parent
+ }
+
+ if (parent instanceof Map) {
+ child = new Map()
+ } else if (parent instanceof Set) {
+ child = new Set()
+ } else if (parent instanceof Promise) {
+ child = new Promise(function (resolve, reject) {
+ parent.then(function (value) {
+ resolve(_clone(value, depth - 1))
+ }, function (err) {
+ reject(_clone(err, depth - 1))
+ })
+ })
+ } else if (_isArray(parent)) {
+ child = []
+ } else if (_isRegExp(parent)) {
+ child = new RegExp(parent.source, _getRegExpFlags(parent))
+ if (parent.lastIndex) child.lastIndex = parent.lastIndex
+ } else if (_isDate(parent)) {
+ child = new Date(parent.getTime())
+ } else if (useBuffer && Buffer.isBuffer(parent)) {
+ child = Buffer.alloc(parent.length)
+ parent.copy(child)
+ return child
+ } else if (parent instanceof Error) {
+ child = Object.create(parent)
+ } else {
+ if (typeof prototype === 'undefined') {
+ proto = Object.getPrototypeOf(parent)
+ child = Object.create(proto)
+ } else {
+ child = Object.create(prototype)
+ proto = prototype
+ }
+ }
+
+ if (circular) {
+ var index = allParents.indexOf(parent)
+
+ if (index !== -1) {
+ return allChildren[index]
+ }
+ allParents.push(parent)
+ allChildren.push(child)
+ }
+
+ if (parent instanceof Map) {
+ var keyIterator = parent.keys()
+ while (true) {
+ let next = keyIterator.next()
+ if (next.done) {
+ break
+ }
+ var keyChild = _clone(next.value, depth - 1)
+ var valueChild = _clone(parent.get(next.value), depth - 1)
+ child.set(keyChild, valueChild)
+ }
+ }
+ if (parent instanceof Set) {
+ var iterator = parent.keys()
+ while (true) {
+ let next = iterator.next()
+ if (next.done) {
+ break
+ }
+ var entryChild = _clone(next.value, depth - 1)
+ child.add(entryChild)
+ }
+ }
+
+ for (let i in parent) {
+ var attrs
+ if (proto) {
+ attrs = Object.getOwnPropertyDescriptor(proto, i)
+ }
+
+ if (attrs && attrs.set == null) {
+ continue
+ }
+ child[i] = _clone(parent[i], depth - 1)
+ }
+
+ if (Object.getOwnPropertySymbols) {
+ var symbols = Object.getOwnPropertySymbols(parent)
+ for (let i = 0; i < symbols.length; i++) {
+ // Don't need to worry about cloning a symbol because it is a primitive,
+ // like a number or string.
+ var symbol = symbols[i]
+ var descriptor = Object.getOwnPropertyDescriptor(parent, symbol)
+ if (descriptor && !descriptor.enumerable) {
+ continue
+ }
+ child[symbol] = _clone(parent[symbol], depth - 1)
+ }
+ }
+
+ return child
+ }
+
+ return _clone(parent, depth)
+ }
+
+ // private utility functions
+
+ function _objToStr (o) {
+ return Object.prototype.toString.call(o)
+ }
+
+ function _isDate (o) {
+ return typeof o === 'object' && _objToStr(o) === '[object Date]'
+ }
+
+ function _isArray (o) {
+ return typeof o === 'object' && _objToStr(o) === '[object Array]'
+ }
+
+ function _isRegExp (o) {
+ return typeof o === 'object' && _objToStr(o) === '[object RegExp]'
+ }
+
+ function _getRegExpFlags (re) {
+ var flags = ''
+ if (re.global) flags += 'g'
+ if (re.ignoreCase) flags += 'i'
+ if (re.multiline) flags += 'm'
+ return flags
+ }
}
diff --git a/src/backend/vuex.js b/src/backend/vuex.js
index b0ec4e711..08fec18cf 100644
--- a/src/backend/vuex.js
+++ b/src/backend/vuex.js
@@ -14,15 +14,16 @@ export function initVuexBackend (hook, bridge) {
computed: originalVm.$options.computed
})
- const getSnapshot = () => stringify({
- state: store.state,
- getters: store.getters || {}
+ const getSnapshot = (_store = store) => stringify({
+ state: _store.state,
+ getters: _store.getters || {}
})
let baseSnapshot, snapshots, mutations, lastState
function reset () {
- baseSnapshot = getSnapshot()
+ baseSnapshot = getSnapshot(hook.initialStore)
+ hook.initialStore = undefined
mutations = []
resetSnapshotCache()
}
@@ -72,7 +73,6 @@ export function initVuexBackend (hook, bridge) {
snapshot
})
if (apply) {
- console.log('vuex:travel-to-state', state)
hook.emit('vuex:travel-to-state', state)
}
})
diff --git a/src/devtools/index.js b/src/devtools/index.js
index cdd71b573..3d764fbad 100644
--- a/src/devtools/index.js
+++ b/src/devtools/index.js
@@ -8,7 +8,6 @@ import { parse } from '../util'
import { isChrome, initEnv } from './env'
import SharedData, { init as initSharedData, destroy as destroySharedData } from 'src/shared-data'
import storage from './storage'
-import { snapshotsCache } from './views/vuex/cache'
import VuexResolve from './views/vuex/resolve'
for (const key in filters) {
@@ -144,8 +143,7 @@ function initApp (shell) {
})
bridge.on('vuex:inspected-state', ({ index, snapshot }) => {
- snapshotsCache.set(index, snapshot)
- store.commit('vuex/RECEIVE_STATE', snapshot)
+ store.commit('vuex/RECEIVE_STATE', { index, snapshot })
if (index === -1) {
store.commit('vuex/UPDATE_BASE_STATE', snapshot)
diff --git a/src/devtools/views/vuex/VuexHistory.vue b/src/devtools/views/vuex/VuexHistory.vue
index 9818b6a9c..92ac84fa2 100644
--- a/src/devtools/views/vuex/VuexHistory.vue
+++ b/src/devtools/views/vuex/VuexHistory.vue
@@ -104,7 +104,7 @@