diff --git a/build/webpack.test.config.js b/build/webpack.test.config.js index b3f4a24fa2d..e694b4971e2 100644 --- a/build/webpack.test.config.js +++ b/build/webpack.test.config.js @@ -17,7 +17,12 @@ module.exports = { { test: /\.js$/, loader: 'babel', - exclude: /test\/unit|node_modules/ + // NOTE: use absolute path to make sure + // running tests is OK even if it is in node_modules of other project + exclude: [ + path.resolve(__dirname, '../test/unit'), + path.resolve(__dirname, '../node_modules') + ] } ] }, diff --git a/src/watcher.js b/src/watcher.js index 576bf472b2e..c72919cde61 100644 --- a/src/watcher.js +++ b/src/watcher.js @@ -331,14 +331,25 @@ Watcher.prototype.teardown = function () { * @param {*} val */ -function traverse (val) { +function traverse (val, walkedObjs) { var i, keys + + walkedObjs = walkedObjs || {} if (isArray(val)) { i = val.length - while (i--) traverse(val[i]) + while (i--) traverse(val[i], walkedObjs) } else if (isObject(val)) { + if (val.__ob__) { + var depId = val.__ob__.dep.id + if (walkedObjs[depId]) { + return + } else { + walkedObjs[depId] = true + } + } + keys = Object.keys(val) i = keys.length - while (i--) traverse(val[keys[i]]) + while (i--) traverse(val[keys[i]], walkedObjs) } } diff --git a/test/unit/specs/watcher_spec.js b/test/unit/specs/watcher_spec.js index 5e9c1a601ed..2450417ce90 100644 --- a/test/unit/specs/watcher_spec.js +++ b/test/unit/specs/watcher_spec.js @@ -286,6 +286,23 @@ describe('Watcher', function () { }) }) + it('deep watch with circular references', function (done) { + new Watcher(vm, 'b', spy, { + deep: true + }) + Vue.set(vm.b, '_', vm.b) + nextTick(function () { + expect(spy).toHaveBeenCalledWith(vm.b, vm.b) + expect(spy.calls.count()).toBe(1) + vm.b._.c = 1 + nextTick(function () { + expect(spy).toHaveBeenCalledWith(vm.b, vm.b) + expect(spy.calls.count()).toBe(2) + done() + }) + }) + }) + it('fire change for prop addition/deletion in non-deep mode', function (done) { new Watcher(vm, 'b', spy) Vue.set(vm.b, 'e', 123)