From 5d5f4110e3c107f2652169daefd357ba267bb981 Mon Sep 17 00:00:00 2001 From: Jason Bedard Date: Sun, 18 Mar 2018 02:15:58 -0700 Subject: [PATCH] test($rootScope): add tests clarifying $watchGroup oldValues behavior Closes #16024 BREAKING CHANGE: (caused by c2b8fab0a480204374d561d6b9b3d47347ac5570) Previously when using `$watchGroup` the entries in `newValues` and `oldValues` represented the *most recent change of each entry*. Now the entries in `oldValues` will always equal the `newValues` of the previous call of the listener. This means comparing the entries in `newValues` and `oldValues` can be used to determine which individual expressions changed. For example `$scope.$watchGroup(['a', 'b'], fn)` would previously: | Action | newValue | oldValue | |----------|------------|------------| | (init) | [undefined, undefined] | [undefined, undefined] | | `a=1` | [1, undefined] | [undefined, undefined] | | `a=2` | [2, undefined] | [1, undefined] | | `b=3` | [2, 3] | [1, undefined] | Now the `oldValue` will always equal the previous `newValue`: | Action | newValue | oldValue | |----------|------------|------------| | (init) | [undefined, undefined] | [undefined, undefined] | | `a=1` | [1, undefined] | [undefined, undefined] | | `a=2` | [2, undefined] | [1, undefined] | | `b=3` | [2, 3] | [2, undefined] | Note the last call now shows `a === 2` in the `oldValues` array. This also makes the `oldValue` of one-time watchers more clear. Previously the `oldValue` of a one-time watcher would remain `undefined` forever. For example `$scope.$watchGroup(['a', '::b'], fn)` would previously: | Action | newValue | oldValue | |----------|------------|------------| | (init) | [undefined, undefined] | [undefined, undefined] | | `a=1` | [1, undefined] | [undefined, undefined] | | `b=2` | [1, 2] | [undefined, undefined] | | `a=b=3` | [3, 2] | [1, undefined] | Where now the `oldValue` will always equal the previous `newValue`: | Action | newValue | oldValue | |----------|------------|------------| | (init) | [undefined, undefined] | [undefined, undefined] | | `a=1` | [1, undefined] | [undefined, undefined] | | `b=2` | [1, 2] | [1, undefined] | | `a=b=3` | [3, 2] | [1, 2] | --- test/ng/rootScopeSpec.js | 109 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/test/ng/rootScopeSpec.js b/test/ng/rootScopeSpec.js index eea9e824ae66..e2f33ee80bba 100644 --- a/test/ng/rootScopeSpec.js +++ b/test/ng/rootScopeSpec.js @@ -1462,6 +1462,49 @@ describe('Scope', function() { })); + it('should pass same group instance on first call (no expressions)', function() { + var newValues; + var oldValues; + scope.$watchGroup([], function(n, o) { + newValues = n; + oldValues = o; + }); + + scope.$apply(); + expect(newValues).toBe(oldValues); + }); + + + it('should pass same group instance on first call (single expression)', function() { + var newValues; + var oldValues; + scope.$watchGroup(['a'], function(n, o) { + newValues = n; + oldValues = o; + }); + + scope.$apply(); + expect(newValues).toBe(oldValues); + + scope.$apply('a = 1'); + expect(newValues).not.toBe(oldValues); + }); + + it('should pass same group instance on first call (multiple expressions)', function() { + var newValues; + var oldValues; + scope.$watchGroup(['a', 'b'], function(n, o) { + newValues = n; + oldValues = o; + }); + + scope.$apply(); + expect(newValues).toBe(oldValues); + + scope.$apply('a = 1'); + expect(newValues).not.toBe(oldValues); + }); + it('should detect a change to any one expression in the group', function() { scope.$watchGroup(['a', 'b'], function(values, oldValues, s) { expect(s).toBe(scope); @@ -1542,6 +1585,72 @@ describe('Scope', function() { expect(log).toEqual(''); }); + it('should have each individual old value equal to new values of previous watcher invocation', function() { + var newValues; + var oldValues; + scope.$watchGroup(['a', 'b'], function(n, o) { + newValues = n.slice(); + oldValues = o.slice(); + }); + + scope.$apply(); //skip the initial invocation + + scope.$apply('a = 1'); + expect(newValues).toEqual([1, undefined]); + expect(oldValues).toEqual([undefined, undefined]); + + scope.$apply('a = 2'); + expect(newValues).toEqual([2, undefined]); + expect(oldValues).toEqual([1, undefined]); + + scope.$apply('b = 3'); + expect(newValues).toEqual([2, 3]); + expect(oldValues).toEqual([2, undefined]); + + scope.$apply('a = b = 4'); + expect(newValues).toEqual([4, 4]); + expect(oldValues).toEqual([2, 3]); + + scope.$apply('a = 5'); + expect(newValues).toEqual([5, 4]); + expect(oldValues).toEqual([4, 4]); + + scope.$apply('b = 6'); + expect(newValues).toEqual([5, 6]); + expect(oldValues).toEqual([5, 4]); + }); + + + it('should have each individual old value equal to new values of previous watcher invocation, with modifications from other watchers', function() { + scope.$watch('a', function() { scope.b++; }); + scope.$watch('b', function() { scope.c++; }); + + var newValues; + var oldValues; + scope.$watchGroup(['a', 'b', 'c'], function(n, o) { + newValues = n.slice(); + oldValues = o.slice(); + }); + + scope.$apply(); //skip the initial invocation + + scope.$apply('a = b = c = 1'); + expect(newValues).toEqual([1, 2, 2]); + expect(oldValues).toEqual([undefined, NaN, NaN]); + + scope.$apply('a = 3'); + expect(newValues).toEqual([3, 3, 3]); + expect(oldValues).toEqual([1, 2, 2]); + + scope.$apply('b = 5'); + expect(newValues).toEqual([3, 5, 4]); + expect(oldValues).toEqual([3, 3, 3]); + + scope.$apply('c = 7'); + expect(newValues).toEqual([3, 5, 7]); + expect(oldValues).toEqual([3, 5, 4]); + }); + it('should remove all watchers once one-time/constant bindings are stable', function() { //empty scope.$watchGroup([], noop);