Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit bdfc9c0

Browse files
mgolpetebacondarwin
authored andcommitted
fix(core): drop the toBoolean function
So far Angular have used the toBoolean function to decide if the parsed value is truthy. The function made more values falsy than regular JavaScript would, e.g. strings 'f' and 'no' were both treated as falsy. This creates suble bugs when backend sends a non-empty string with one of these values and something suddenly hides in the application Thanks to lgalfaso for test ideas. BREAKING CHANGE: values 'f', '0', 'false', 'no', 'n', '[]' are no longer treated as falsy. Only JavaScript falsy values are now treated as falsy by the expression parser; there are six of them: false, null, undefined, NaN, 0 and "". Closes #3969 Closes #4277 Closes #7960
1 parent e970df8 commit bdfc9c0

File tree

10 files changed

+121
-62
lines changed

10 files changed

+121
-62
lines changed

src/.jshintrc

-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@
8585
"toJsonReplacer": false,
8686
"toJson": false,
8787
"fromJson": false,
88-
"toBoolean": false,
8988
"startingTag": false,
9089
"tryDecodeURIComponent": false,
9190
"parseKeyValue": false,

src/Angular.js

-13
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@
6767
-toJsonReplacer,
6868
-toJson,
6969
-fromJson,
70-
-toBoolean,
7170
-startingTag,
7271
-tryDecodeURIComponent,
7372
-parseKeyValue,
@@ -1033,18 +1032,6 @@ function fromJson(json) {
10331032
}
10341033

10351034

1036-
function toBoolean(value) {
1037-
if (typeof value === 'function') {
1038-
value = true;
1039-
} else if (value && value.length !== 0) {
1040-
var v = lowercase("" + value);
1041-
value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]');
1042-
} else {
1043-
value = false;
1044-
}
1045-
return value;
1046-
}
1047-
10481035
/**
10491036
* @returns {string} Returns the string representation of the element.
10501037
*/

src/ng/directive/input.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -932,7 +932,7 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
932932
// By default we will trim the value
933933
// If the attribute ng-trim exists we will avoid trimming
934934
// e.g. <input ng-model="foo" ng-trim="false">
935-
if (toBoolean(attr.ngTrim || 'T')) {
935+
if (!attr.ngTrim || attr.ngTrim !== 'false') {
936936
value = trim(value);
937937
}
938938

src/ng/directive/ngIf.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ var ngIfDirective = ['$animate', function($animate) {
8787
var block, childScope, previousElements;
8888
$scope.$watch($attr.ngIf, function ngIfWatchAction(value) {
8989

90-
if (toBoolean(value)) {
90+
if (value) {
9191
if (!childScope) {
9292
$transclude(function (clone, newScope) {
9393
childScope = newScope;

src/ng/directive/ngShowHide.js

+6-16
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,10 @@
1919
* <div ng-show="myValue" class="ng-hide"></div>
2020
* ```
2121
*
22-
* When the ngShow expression evaluates to false then the ng-hide CSS class is added to the class attribute
23-
* on the element causing it to become hidden. When true, the ng-hide CSS class is removed
22+
* When the ngShow expression evaluates to a falsy value then the ng-hide CSS class is added to the class
23+
* attribute on the element causing it to become hidden. When truthy, the ng-hide CSS class is removed
2424
* from the element causing the element not to appear hidden.
2525
*
26-
* <div class="alert alert-warning">
27-
* **Note:** Here is a list of values that ngShow will consider as a falsy value (case insensitive):<br />
28-
* "f" / "0" / "false" / "no" / "n" / "[]"
29-
* </div>
30-
*
3126
* ## Why is !important used?
3227
*
3328
* You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector
@@ -163,7 +158,7 @@
163158
var ngShowDirective = ['$animate', function($animate) {
164159
return function(scope, element, attr) {
165160
scope.$watch(attr.ngShow, function ngShowWatchAction(value){
166-
$animate[toBoolean(value) ? 'removeClass' : 'addClass'](element, 'ng-hide');
161+
$animate[value ? 'removeClass' : 'addClass'](element, 'ng-hide');
167162
});
168163
};
169164
}];
@@ -188,15 +183,10 @@ var ngShowDirective = ['$animate', function($animate) {
188183
* <div ng-hide="myValue"></div>
189184
* ```
190185
*
191-
* When the ngHide expression evaluates to true then the .ng-hide CSS class is added to the class attribute
192-
* on the element causing it to become hidden. When false, the ng-hide CSS class is removed
186+
* When the ngHide expression evaluates to a truthy value then the .ng-hide CSS class is added to the class
187+
* attribute on the element causing it to become hidden. When falsy, the ng-hide CSS class is removed
193188
* from the element causing the element not to appear hidden.
194189
*
195-
* <div class="alert alert-warning">
196-
* **Note:** Here is a list of values that ngHide will consider as a falsy value (case insensitive):<br />
197-
* "f" / "0" / "false" / "no" / "n" / "[]"
198-
* </div>
199-
*
200190
* ## Why is !important used?
201191
*
202192
* You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector
@@ -319,7 +309,7 @@ var ngShowDirective = ['$animate', function($animate) {
319309
var ngHideDirective = ['$animate', function($animate) {
320310
return function(scope, element, attr) {
321311
scope.$watch(attr.ngHide, function ngHideWatchAction(value){
322-
$animate[toBoolean(value) ? 'addClass' : 'removeClass'](element, 'ng-hide');
312+
$animate[value ? 'addClass' : 'removeClass'](element, 'ng-hide');
323313
});
324314
};
325315
}];

src/ng/filter/orderBy.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ function orderByFilter($parse){
144144
return 0;
145145
}
146146
function reverseComparator(comp, descending) {
147-
return toBoolean(descending)
147+
return descending
148148
? function(a,b){return comp(b,a);}
149149
: comp;
150150
}

test/.jshintrc

-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@
8585
"toJsonReplacer": false,
8686
"toJson": false,
8787
"fromJson": false,
88-
"toBoolean": false,
8988
"startingTag": false,
9089
"tryDecodeURIComponent": false,
9190
"parseKeyValue": false,

test/BinderSpec.js

+20
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,16 @@ describe('Binder', function() {
248248
$rootScope.hidden = 'false';
249249
$rootScope.$apply();
250250

251+
assertHidden(element);
252+
253+
$rootScope.hidden = 0;
254+
$rootScope.$apply();
255+
256+
assertVisible(element);
257+
258+
$rootScope.hidden = false;
259+
$rootScope.$apply();
260+
251261
assertVisible(element);
252262

253263
$rootScope.hidden = '';
@@ -267,6 +277,16 @@ describe('Binder', function() {
267277
$rootScope.show = 'false';
268278
$rootScope.$apply();
269279

280+
assertVisible(element);
281+
282+
$rootScope.show = false;
283+
$rootScope.$apply();
284+
285+
assertHidden(element);
286+
287+
$rootScope.show = false;
288+
$rootScope.$apply();
289+
270290
assertHidden(element);
271291

272292
$rootScope.show = '';

test/ng/directive/ngIfSpec.js

+16-4
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ describe('ngIf', function () {
1616
dealoc(element);
1717
});
1818

19-
function makeIf(expr) {
20-
element.append($compile('<div class="my-class" ng-if="' + expr + '"><div>Hi</div></div>')($scope));
19+
function makeIf() {
20+
forEach(arguments, function (expr) {
21+
element.append($compile('<div class="my-class" ng-if="' + expr + '"><div>Hi</div></div>')($scope));
22+
});
2123
$scope.$apply();
2224
}
2325

24-
it('should immediately remove element if condition is false', function () {
25-
makeIf('false');
26+
it('should immediately remove the element if condition is falsy', function () {
27+
makeIf('false', 'undefined', 'null', 'NaN', '\'\'', '0');
2628
expect(element.children().length).toBe(0);
2729
});
2830

@@ -31,6 +33,16 @@ describe('ngIf', function () {
3133
expect(element.children().length).toBe(1);
3234
});
3335

36+
it('should leave the element if the condition is a non-empty string', function () {
37+
makeIf('\'f\'', '\'0\'', '\'false\'', '\'no\'', '\'n\'', '\'[]\'');
38+
expect(element.children().length).toBe(6);
39+
});
40+
41+
it('should leave the element if the condition is an object', function () {
42+
makeIf('[]', '{}');
43+
expect(element.children().length).toBe(2);
44+
});
45+
3446
it('should not add the element twice if the condition goes from true to true', function () {
3547
$scope.hello = 'true1';
3648
makeIf('hello');

test/ng/directive/ngShowHideSpec.js

+76-24
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,106 @@
11
'use strict';
22

33
describe('ngShow / ngHide', function() {
4-
var element;
4+
var $scope, $compile, element;
55

6+
function expectVisibility(exprs, ngShowOrNgHide, shownOrHidden) {
7+
element = $compile('<div></div>')($scope);
8+
forEach(exprs, function (expr) {
9+
var childElem = $compile('<div ' + ngShowOrNgHide + '="' + expr + '"></div>')($scope);
10+
element.append(childElem);
11+
$scope.$digest();
12+
expect(childElem)[shownOrHidden === 'shown' ? 'toBeShown' : 'toBeHidden']();
13+
});
14+
}
15+
16+
beforeEach(inject(function ($rootScope, _$compile_) {
17+
$scope = $rootScope.$new();
18+
$compile = _$compile_;
19+
}));
620

721
afterEach(function() {
822
dealoc(element);
923
});
1024

1125
describe('ngShow', function() {
12-
it('should show and hide an element', inject(function($rootScope, $compile) {
26+
function expectShown() {
27+
expectVisibility(arguments, 'ng-show', 'shown');
28+
}
29+
30+
function expectHidden() {
31+
expectVisibility(arguments, 'ng-show', 'hidden');
32+
}
33+
34+
it('should show and hide an element', function() {
1335
element = jqLite('<div ng-show="exp"></div>');
14-
element = $compile(element)($rootScope);
15-
$rootScope.$digest();
36+
element = $compile(element)($scope);
37+
$scope.$digest();
1638
expect(element).toBeHidden();
17-
$rootScope.exp = true;
18-
$rootScope.$digest();
39+
$scope.exp = true;
40+
$scope.$digest();
1941
expect(element).toBeShown();
20-
}));
21-
42+
});
2243

2344
// https://github.com/angular/angular.js/issues/5414
24-
it('should show if the expression is a function with a no arguments', inject(function($rootScope, $compile) {
45+
it('should show if the expression is a function with a no arguments', function() {
2546
element = jqLite('<div ng-show="exp"></div>');
26-
element = $compile(element)($rootScope);
27-
$rootScope.exp = function(){};
28-
$rootScope.$digest();
47+
element = $compile(element)($scope);
48+
$scope.exp = function(){};
49+
$scope.$digest();
2950
expect(element).toBeShown();
30-
}));
31-
51+
});
3252

33-
it('should make hidden element visible', inject(function($rootScope, $compile) {
53+
it('should make hidden element visible', function() {
3454
element = jqLite('<div class="ng-hide" ng-show="exp"></div>');
35-
element = $compile(element)($rootScope);
55+
element = $compile(element)($scope);
3656
expect(element).toBeHidden();
37-
$rootScope.exp = true;
38-
$rootScope.$digest();
57+
$scope.exp = true;
58+
$scope.$digest();
3959
expect(element).toBeShown();
40-
}));
60+
});
61+
62+
it('should hide the element if condition is falsy', function() {
63+
expectHidden('false', 'undefined', 'null', 'NaN', '\'\'', '0');
64+
});
65+
66+
it('should show the element if condition is a non-empty string', function() {
67+
expectShown('\'f\'', '\'0\'', '\'false\'', '\'no\'', '\'n\'', '\'[]\'');
68+
});
69+
70+
it('should show the element if condition is an object', function() {
71+
expectShown('[]', '{}');
72+
});
4173
});
4274

4375
describe('ngHide', function() {
44-
it('should hide an element', inject(function($rootScope, $compile) {
76+
function expectShown() {
77+
expectVisibility(arguments, 'ng-hide', 'shown');
78+
}
79+
80+
function expectHidden() {
81+
expectVisibility(arguments, 'ng-hide', 'hidden');
82+
}
83+
84+
it('should hide an element', function() {
4585
element = jqLite('<div ng-hide="exp"></div>');
46-
element = $compile(element)($rootScope);
86+
element = $compile(element)($scope);
4787
expect(element).toBeShown();
48-
$rootScope.exp = true;
49-
$rootScope.$digest();
88+
$scope.exp = true;
89+
$scope.$digest();
5090
expect(element).toBeHidden();
51-
}));
91+
});
92+
93+
it('should show the element if condition is falsy', function() {
94+
expectShown('false', 'undefined', 'null', 'NaN', '\'\'', '0');
95+
});
96+
97+
it('should hide the element if condition is a non-empty string', function() {
98+
expectHidden('\'f\'', '\'0\'', '\'false\'', '\'no\'', '\'n\'', '\'[]\'');
99+
});
100+
101+
it('should hide the element if condition is an object', function() {
102+
expectHidden('[]', '{}');
103+
});
52104
});
53105
});
54106

0 commit comments

Comments
 (0)