-
Notifications
You must be signed in to change notification settings - Fork 27.4k
ngClass[Odd/Even] overhaul #15228
ngClass[Odd/Even] overhaul #15228
Changes from 4 commits
8822006
0409589
2705085
0ba4be6
89268af
2871a11
a7e69f7
37cec7a
5708424
739f723
181c51b
16e666b
dfe4307
f766698
a9742f9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
'use strict'; | ||
|
||
describe('ngClass', function() { | ||
fdescribe('ngClass', function() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Naught naughty There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I already have a fixup commit at the end 😛 |
||
var element; | ||
|
||
beforeEach(module(function($compileProvider) { | ||
|
@@ -244,31 +244,42 @@ describe('ngClass', function() { | |
})); | ||
|
||
|
||
it('should allow ngClassOdd/Even on the same element with overlapping classes', inject(function($rootScope, $compile, $animate) { | ||
var className; | ||
|
||
element = $compile('<ul><li ng-repeat="i in [0,1,2]" ng-class-odd="\'same odd\'" ng-class-even="\'same even\'"></li><ul>')($rootScope); | ||
it('should allow ngClassOdd/Even on the same element with overlapping classes', | ||
inject(function($compile, $rootScope) { | ||
element = $compile( | ||
'<ul>' + | ||
'<li ng-repeat="i in [0,1,2]" ' + | ||
'ng-class-odd="\'same odd\'" ' + | ||
'ng-class-even="\'same even\'">' + | ||
'</li>' + | ||
'<ul>')($rootScope); | ||
$rootScope.$digest(); | ||
var e1 = jqLite(element[0].childNodes[1]); | ||
var e2 = jqLite(element[0].childNodes[5]); | ||
expect(e1.hasClass('same')).toBeTruthy(); | ||
expect(e1.hasClass('odd')).toBeTruthy(); | ||
expect(e2.hasClass('same')).toBeTruthy(); | ||
expect(e2.hasClass('odd')).toBeTruthy(); | ||
|
||
var e1 = element.children().eq(0); | ||
var e2 = element.children().eq(1); | ||
var e3 = element.children().eq(2); | ||
|
||
expect(e1).toHaveClass('same'); | ||
expect(e1).toHaveClass('odd'); | ||
expect(e1).not.toHaveClass('even'); | ||
expect(e2).toHaveClass('same'); | ||
expect(e2).not.toHaveClass('odd'); | ||
expect(e2).toHaveClass('even'); | ||
expect(e3).toHaveClass('same'); | ||
expect(e3).toHaveClass('odd'); | ||
expect(e3).not.toHaveClass('even'); | ||
}) | ||
); | ||
|
||
it('should allow ngClass with overlapping classes', inject(function($rootScope, $compile, $animate) { | ||
it('should allow ngClass with overlapping classes', inject(function($rootScope, $compile) { | ||
element = $compile('<div ng-class="{\'same yes\': test, \'same no\': !test}"></div>')($rootScope); | ||
$rootScope.$digest(); | ||
|
||
expect(element).toHaveClass('same'); | ||
expect(element).not.toHaveClass('yes'); | ||
expect(element).toHaveClass('no'); | ||
|
||
$rootScope.$apply(function() { | ||
$rootScope.test = true; | ||
}); | ||
$rootScope.$apply('test = true'); | ||
|
||
expect(element).toHaveClass('same'); | ||
expect(element).toHaveClass('yes'); | ||
|
@@ -300,37 +311,79 @@ describe('ngClass', function() { | |
})); | ||
|
||
|
||
it('should reapply ngClass when interpolated class attribute changes', inject(function($rootScope, $compile) { | ||
element = $compile('<div class="one {{cls}} three" ng-class="{four: four}"></div>')($rootScope); | ||
|
||
$rootScope.$apply(function() { | ||
$rootScope.cls = 'two'; | ||
$rootScope.four = true; | ||
}); | ||
expect(element).toHaveClass('one'); | ||
expect(element).toHaveClass('two'); // interpolated | ||
expect(element).toHaveClass('three'); | ||
expect(element).toHaveClass('four'); | ||
|
||
$rootScope.$apply(function() { | ||
$rootScope.cls = 'too'; | ||
}); | ||
expect(element).toHaveClass('one'); | ||
expect(element).toHaveClass('too'); // interpolated | ||
expect(element).toHaveClass('three'); | ||
expect(element).toHaveClass('four'); // should still be there | ||
expect(element.hasClass('two')).toBeFalsy(); | ||
|
||
$rootScope.$apply(function() { | ||
$rootScope.cls = 'to'; | ||
}); | ||
expect(element).toHaveClass('one'); | ||
expect(element).toHaveClass('to'); // interpolated | ||
expect(element).toHaveClass('three'); | ||
expect(element).toHaveClass('four'); // should still be there | ||
expect(element.hasClass('two')).toBeFalsy(); | ||
expect(element.hasClass('too')).toBeFalsy(); | ||
})); | ||
it('should reapply ngClass when interpolated class attribute changes', | ||
inject(function($compile, $rootScope) { | ||
element = $compile( | ||
'<div>' + | ||
'<div class="one {{two}} three" ng-class="{five: five}"></div>' + | ||
'<div class="one {{two}} three {{four}}" ng-class="{five: five}"></div>' + | ||
'</div>')($rootScope); | ||
var e1 = element.children().eq(0); | ||
var e2 = element.children().eq(1); | ||
|
||
$rootScope.$apply('two = "two"; five = true'); | ||
|
||
expect(e1).toHaveClass('one'); | ||
expect(e1).toHaveClass('two'); | ||
expect(e1).toHaveClass('three'); | ||
expect(e1).not.toHaveClass('four'); | ||
expect(e1).toHaveClass('five'); | ||
expect(e2).toHaveClass('one'); | ||
expect(e2).toHaveClass('two'); | ||
expect(e2).toHaveClass('three'); | ||
expect(e2).not.toHaveClass('four'); | ||
expect(e2).toHaveClass('five'); | ||
|
||
$rootScope.$apply('two = "too"'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. although this is very clever "two" and "too" are very similar and actually make the test harder to follow. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The cleverness credits go to the original test 😃 |
||
|
||
expect(e1).toHaveClass('one'); | ||
expect(e1).not.toHaveClass('two'); | ||
expect(e1).toHaveClass('too'); | ||
expect(e1).toHaveClass('three'); | ||
expect(e1).not.toHaveClass('four'); | ||
expect(e1).toHaveClass('five'); | ||
expect(e2).toHaveClass('one'); | ||
expect(e2).not.toHaveClass('two'); | ||
expect(e2).toHaveClass('too'); | ||
expect(e2).toHaveClass('three'); | ||
expect(e2).not.toHaveClass('four'); | ||
expect(e2).toHaveClass('five'); | ||
|
||
$rootScope.$apply('two = "to"; four = "four"'); | ||
|
||
expect(e1).toHaveClass('one'); | ||
expect(e1).not.toHaveClass('two'); | ||
expect(e1).not.toHaveClass('too'); | ||
expect(e1).toHaveClass('to'); | ||
expect(e1).toHaveClass('three'); | ||
expect(e1).not.toHaveClass('four'); | ||
expect(e1).toHaveClass('five'); | ||
expect(e2).toHaveClass('one'); | ||
expect(e2).not.toHaveClass('two'); | ||
expect(e2).not.toHaveClass('too'); | ||
expect(e2).toHaveClass('to'); | ||
expect(e2).toHaveClass('three'); | ||
expect(e2).toHaveClass('four'); | ||
expect(e2).toHaveClass('five'); | ||
|
||
$rootScope.$apply('five = false'); | ||
|
||
expect(e1).toHaveClass('one'); | ||
expect(e1).not.toHaveClass('two'); | ||
expect(e1).not.toHaveClass('too'); | ||
expect(e1).toHaveClass('to'); | ||
expect(e1).toHaveClass('three'); | ||
expect(e1).not.toHaveClass('four'); | ||
expect(e1).not.toHaveClass('five'); | ||
expect(e2).toHaveClass('one'); | ||
expect(e2).not.toHaveClass('two'); | ||
expect(e2).not.toHaveClass('too'); | ||
expect(e2).toHaveClass('to'); | ||
expect(e2).toHaveClass('three'); | ||
expect(e2).toHaveClass('four'); | ||
expect(e2).not.toHaveClass('five'); | ||
}) | ||
); | ||
|
||
|
||
it('should not mess up class value due to observing an interpolated class attribute', inject(function($rootScope, $compile) { | ||
|
@@ -409,6 +462,47 @@ describe('ngClass', function() { | |
expect(e2.hasClass('odd')).toBeFalsy(); | ||
})); | ||
|
||
|
||
it('should keep track of old classes even if odd/even does not match `$index` atm', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This description makes hard to understand what is actually being tested. Are you saying that ngClass is not removing old classes when both the $index and the expression value changes? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be because I was trying to solve it differently at first. Here is what was happening before this change:
Basically, the problem is that both watch-actions check against the current value of each other to determine what to do, which fails when both change in the same digest. The commit fxes it by:
This way, when the I hope this makes sense 😃 How about:
|
||
inject(function($compile, $rootScope) { | ||
element = $compile( | ||
'<div>' + | ||
'<div ng-class-odd="foo"></div>' + | ||
'<div ng-class-even="foo"></div>' + | ||
'</div>')($rootScope); | ||
var odd = element.children().eq(0); | ||
var even = element.children().eq(1); | ||
|
||
$rootScope.$apply('$index = 0; foo = "class1"'); | ||
|
||
expect(odd).toHaveClass('class1'); | ||
expect(odd).not.toHaveClass('class2'); | ||
expect(even).not.toHaveClass('class1'); | ||
expect(even).not.toHaveClass('class2'); | ||
|
||
$rootScope.$apply('$index = 1; foo = "class2"'); | ||
|
||
expect(odd).not.toHaveClass('class1'); | ||
expect(odd).not.toHaveClass('class2'); | ||
expect(even).not.toHaveClass('class1'); | ||
expect(even).toHaveClass('class2'); | ||
|
||
$rootScope.$apply('foo = "class1"'); | ||
|
||
expect(odd).not.toHaveClass('class1'); | ||
expect(odd).not.toHaveClass('class2'); | ||
expect(even).toHaveClass('class1'); | ||
expect(even).not.toHaveClass('class2'); | ||
|
||
$rootScope.$apply('$index = 2'); | ||
|
||
expect(odd).toHaveClass('class1'); | ||
expect(odd).not.toHaveClass('class2'); | ||
expect(even).not.toHaveClass('class1'); | ||
expect(even).not.toHaveClass('class2'); | ||
}) | ||
); | ||
|
||
it('should support mixed array/object variable with a mutating object', | ||
inject(function($rootScope, $compile) { | ||
element = $compile('<div ng-class="classVar"></div>')($rootScope); | ||
|
@@ -424,6 +518,62 @@ describe('ngClass', function() { | |
}) | ||
); | ||
|
||
it('should do value stabilization as expected when one-time binding', | ||
inject(function($rootScope, $compile) { | ||
element = $compile('<div ng-class="::className"></div>')($rootScope); | ||
$rootScope.$digest(); | ||
|
||
$rootScope.className = 'foo'; | ||
$rootScope.$digest(); | ||
expect(element).toHaveClass('foo'); | ||
|
||
$rootScope.className = 'bar'; | ||
$rootScope.$digest(); | ||
expect(element).toHaveClass('foo'); | ||
}) | ||
); | ||
|
||
it('should remove the watcher when static array one-time binding', | ||
inject(function($rootScope, $compile) { | ||
element = $compile('<div ng-class="::[className]"></div>')($rootScope); | ||
$rootScope.$digest(); | ||
|
||
$rootScope.$apply('className = "foo"'); | ||
expect(element).toHaveClass('foo'); | ||
|
||
$rootScope.$apply('className = "bar"'); | ||
expect(element).toHaveClass('foo'); | ||
expect(element).not.toHaveClass('bar'); | ||
}) | ||
); | ||
|
||
it('should do value remove the watcher when static map one-time binding', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the test description here seems like a hybrid of the two tests above :-) |
||
inject(function($rootScope, $compile) { | ||
element = $compile('<div ng-class="::{foo: fooPresent}"></div>')($rootScope); | ||
$rootScope.$digest(); | ||
|
||
$rootScope.fooPresent = true; | ||
$rootScope.$digest(); | ||
expect(element).toHaveClass('foo'); | ||
|
||
$rootScope.fooPresent = false; | ||
$rootScope.$digest(); | ||
expect(element).toHaveClass('foo'); | ||
}) | ||
); | ||
|
||
it('should track changes of mutating object inside an array', | ||
inject(function($rootScope, $compile) { | ||
$rootScope.classVar = [{orange: true}]; | ||
element = $compile('<div ng-class="classVar"></div>')($rootScope); | ||
$rootScope.$digest(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it worth adding an expectation that it should have the class "orange" here? |
||
|
||
$rootScope.classVar[0].orange = false; | ||
$rootScope.$digest(); | ||
|
||
expect(element).not.toHaveClass('orange'); | ||
}) | ||
); | ||
}); | ||
|
||
describe('ngClass animations', function() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I never knew about these helpers!!