aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMisko Hevery2010-12-01 20:29:54 -0800
committerMisko Hevery2010-12-02 22:45:57 -0800
commit5a8ad8fe329fc09898ff43a060710265d38393be (patch)
tree95058036d40b1dd993e2a9c4094ebd34b2751707
parent41d5938883a3d06ffe8a88a51efd8d1896f7d747 (diff)
downloadangular.js-5a8ad8fe329fc09898ff43a060710265d38393be.tar.bz2
Closes #170. Corrected the behavior of select when options are ng:repeated
- Delete $postEval method, as it was a hack
-rw-r--r--regression/issue-170.html28
-rw-r--r--src/Angular.js5
-rw-r--r--src/Compiler.js13
-rw-r--r--src/Scope.js27
-rw-r--r--src/directives.js21
-rw-r--r--src/validators.js2
-rw-r--r--src/widgets.js49
-rw-r--r--test/AngularSpec.js1
-rw-r--r--test/BinderTest.js1
-rw-r--r--test/CompilerSpec.js25
-rw-r--r--test/ResourceSpec.js2
-rw-r--r--test/ScenarioSpec.js104
-rw-r--r--test/ScopeSpec.js22
-rw-r--r--test/ValidatorsTest.js6
-rw-r--r--test/directivesSpec.js3
-rw-r--r--test/markupSpec.js176
-rw-r--r--test/servicesSpec.js11
-rw-r--r--test/testabilityPatch.js24
-rw-r--r--test/widgetsSpec.js37
19 files changed, 328 insertions, 229 deletions
diff --git a/regression/issue-170.html b/regression/issue-170.html
new file mode 100644
index 00000000..392abf81
--- /dev/null
+++ b/regression/issue-170.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html xmlns:ng="http://angularjs.org">
+<head>
+ <script type="text/javascript" src="../lib/jquery/jquery-1.4.2.js"></script>
+ <script type="text/javascript" src="../src/angular-bootstrap.js" ng:autobind></script>
+<head>
+<body>
+ <select name='selection0' style="display:block;">
+ <option ng:repeat='value in ["FOO","BAR"]'">{{value}}</option>
+ </select>
+ {{selection0}} &lt;-- FOO should be shown here
+
+ <hr/>
+
+ <select ng:init="selection1='ignore'" name='selection1' style="display:block;">
+ <option ng:repeat='value in ["FOO","BAR"]' ng:bind-attr="{selected:'{{value==\'BAR\'}}'}">{{value}}</option>
+ </select>
+ {{selection1}} &lt;-- BAR should be shown here
+
+ <hr/>
+
+ <select ng:init="selection2=1" name="selection2" style="display:block;">
+ <option value="{{$index}}" ng:repeat="opt in ['zero', 'one']">{{opt}}</option>
+ </select>
+ {{selection2}} &lt;-- 1 should be shown here
+
+</body>
+</html> \ No newline at end of file
diff --git a/src/Angular.js b/src/Angular.js
index 4d3c360f..97792868 100644
--- a/src/Angular.js
+++ b/src/Angular.js
@@ -53,6 +53,9 @@ function fromCharCode(code) { return String.fromCharCode(code); }
var _undefined = undefined,
_null = null,
$$element = '$element',
+ $$update = '$update',
+ $$scope = '$scope',
+ $$validate = '$validate',
$angular = 'angular',
$array = 'array',
$boolean = 'boolean',
@@ -69,6 +72,8 @@ var _undefined = undefined,
$number = 'number',
$object = 'object',
$string = 'string',
+ $value = 'value',
+ $selected = 'selected',
$undefined = 'undefined',
NG_EXCEPTION = 'ng-exception',
NG_VALIDATION_ERROR = 'ng-validation-error',
diff --git a/src/Compiler.js b/src/Compiler.js
index 10d19ea8..a98bd502 100644
--- a/src/Compiler.js
+++ b/src/Compiler.js
@@ -30,6 +30,7 @@ Template.prototype = {
if (this.newScope) {
childScope = createScope(scope);
scope.$onEval(childScope.$eval);
+ element.data($$scope, childScope);
}
foreach(this.inits, function(fn) {
queue.push(function() {
@@ -68,6 +69,17 @@ Template.prototype = {
}
};
+/*
+ * Function walks up the element chain looking for the scope associated with the give element.
+ */
+function retrieveScope(element) {
+ var scope;
+ while (element && !(scope = element.data($$scope))) {
+ element = element.parent();
+ }
+ return scope;
+}
+
///////////////////////////////////
//Compiler
//////////////////////////////////
@@ -97,6 +109,7 @@ Compiler.prototype = {
element = jqLite(element);
var scope = parentScope && parentScope.$eval ?
parentScope : createScope(parentScope);
+ element.data($$scope, scope);
return extend(scope, {
$element:element,
$init: function() {
diff --git a/src/Scope.js b/src/Scope.js
index 09779453..203507a3 100644
--- a/src/Scope.js
+++ b/src/Scope.js
@@ -243,7 +243,6 @@ function createScope(parent, providers, instanceCache) {
parent = Parent.prototype = (parent || {});
var instance = new Parent();
var evalLists = {sorted:[]};
- var postList = [], postHash = {}, postId = 0;
extend(instance, {
'this': instance,
@@ -371,11 +370,6 @@ function createScope(parent, providers, instanceCache) {
instance.$tryEval(queue[j].fn, queue[j].handler);
}
}
- while(postList.length) {
- fn = postList.shift();
- delete postHash[fn.$postEvalId];
- instance.$tryEval(fn);
- }
} else if (type === $function) {
return exp.call(instance);
} else if (type === 'string') {
@@ -552,27 +546,6 @@ function createScope(parent, providers, instanceCache) {
/**
* @workInProgress
* @ngdoc function
- * @name angular.scope.$postEval
- * @function
- */
- $postEval: function(expr) {
- if (expr) {
- var fn = expressionCompile(expr);
- var id = fn.$postEvalId;
- if (!id) {
- id = '$' + instance.$id + "_" + (postId++);
- fn.$postEvalId = id;
- }
- if (!postHash[id]) {
- postList.push(postHash[id] = fn);
- }
- }
- },
-
-
- /**
- * @workInProgress
- * @ngdoc function
* @name angular.scope.$become
* @function
* @deprecated This method will be removed before 1.0
diff --git a/src/directives.js b/src/directives.js
index 8a76f17a..d40d6120 100644
--- a/src/directives.js
+++ b/src/directives.js
@@ -304,7 +304,8 @@ angularDirective("ng:bind-template", function(expression, element){
var REMOVE_ATTRIBUTES = {
'disabled':'disabled',
'readonly':'readOnly',
- 'checked':'checked'
+ 'checked':'checked',
+ 'selected':'selected'
};
/**
* @workInProgress
@@ -359,27 +360,31 @@ var REMOVE_ATTRIBUTES = {
angularDirective("ng:bind-attr", function(expression){
return function(element){
var lastValue = {};
- var updateFn = element.parent().data('$update');
+ var updateFn = element.data($$update) || noop;
this.$onEval(function(){
- var values = this.$eval(expression);
+ var values = this.$eval(expression),
+ dirty = noop;
for(var key in values) {
var value = compileBindTemplate(values[key]).call(this, element),
specialName = REMOVE_ATTRIBUTES[lowercase(key)];
if (lastValue[key] !== value) {
lastValue[key] = value;
if (specialName) {
- if (element[specialName] = toBoolean(value)) {
- element.attr(specialName, value);
+ if (toBoolean(value)) {
+ element.attr(specialName, specialName);
+ element.attr('ng-' + specialName, value);
} else {
- element.removeAttr(key);
+ element.removeAttr(specialName);
+ element.removeAttr('ng-' + specialName);
}
- (element.data('$validate')||noop)();
+ (element.data($$validate)||noop)();
} else {
element.attr(key, value);
}
- this.$postEval(updateFn);
+ dirty = updateFn;
}
}
+ dirty();
}, element);
};
});
diff --git a/src/validators.js b/src/validators.js
index ea35558e..7318333a 100644
--- a/src/validators.js
+++ b/src/validators.js
@@ -394,7 +394,7 @@ extend(angularValidator, {
element.removeClass('ng-input-indicator-wait');
scope.$invalidWidgets.markValid(element);
}
- element.data('$validate')();
+ element.data($$validate)();
scope.$root.$eval();
});
} else if (inputState.inFlight) {
diff --git a/src/widgets.js b/src/widgets.js
index f34ff05c..04353bd5 100644
--- a/src/widgets.js
+++ b/src/widgets.js
@@ -282,7 +282,7 @@ function valueAccessor(scope, element) {
required = requiredExpr === '';
}
- element.data('$validate', validate);
+ element.data($$validate, validate);
return {
get: function(){
if (lastError)
@@ -391,6 +391,7 @@ var textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, initW
// 'file': fileWidget???
};
+
function initWidgetValue(initValue) {
return function (model, view) {
var value = view.get();
@@ -461,18 +462,13 @@ function inputWidget(events, modelAccessor, viewAccessor, initFn) {
this.$eval(element.attr('ng:init')||'');
// Don't register a handler if we are a button (noopAccessor) and there is no action
if (action || modelAccessor !== noopAccessor) {
- element.bind(events, function(event){
+ element.bind(events, function (){
model.set(view.get());
lastValue = model.get();
scope.$tryEval(action, element);
scope.$root.$eval();
});
}
- function updateView(){
- view.set(lastValue = model.get());
- }
- updateView();
- element.data('$update', updateView);
scope.$watch(model.get, function(value){
if (lastValue !== value) {
view.set(lastValue = value);
@@ -494,15 +490,50 @@ angularWidget('select', function(element){
return inputWidgetSelector.call(this, element);
});
+
+/*
+ * Consider this:
+ * <select name="selection">
+ * <option ng:repeat="x in [1,2]">{{x}}</option>
+ * </select>
+ *
+ * The issue is that the select gets evaluated before option is unrolled.
+ * This means that the selection is undefined, but the browser
+ * default behavior is to show the top selection in the list.
+ * To fix that we register a $update function on the select element
+ * and the option creation then calls the $update function when it is
+ * unrolled. The $update function then calls this update function, which
+ * then tries to determine if the model is unassigned, and if so it tries to
+ * chose one of the options from the list.
+ */
angularWidget('option', function(){
this.descend(true);
this.directives(true);
return function(element) {
- this.$postEval(element.parent().data('$update'));
+ var select = element.parent();
+ var scope = retrieveScope(select);
+ var model = modelFormattedAccessor(scope, select);
+ var view = valueAccessor(scope, select);
+ var option = element;
+ var lastValue = option.attr($value);
+ var lastSelected = option.attr('ng-' + $selected);
+ element.data($$update, function(){
+ var value = option.attr($value);
+ var selected = option.attr('ng-' + $selected);
+ var modelValue = model.get();
+ if (lastSelected != selected || lastValue != value) {
+ lastSelected = selected;
+ lastValue = value;
+ if (selected || modelValue == _null || modelValue == _undefined)
+ model.set(value);
+ if (value == modelValue) {
+ view.set(lastValue);
+ }
+ }
+ });
};
});
-
/**
* @workInProgress
* @ngdoc widget
diff --git a/test/AngularSpec.js b/test/AngularSpec.js
index 6733a7ab..f5a202fe 100644
--- a/test/AngularSpec.js
+++ b/test/AngularSpec.js
@@ -10,6 +10,7 @@ describe('Angular', function(){
scope.$init();
scope.$eval();
expect(onUpdateView).wasCalled();
+ dealoc(scope);
});
});
diff --git a/test/BinderTest.js b/test/BinderTest.js
index d35d46f4..58081f25 100644
--- a/test/BinderTest.js
+++ b/test/BinderTest.js
@@ -5,6 +5,7 @@ BinderTest.prototype.setUp = function(){
this.compile = function(html, initialScope, parent) {
var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget);
+ if (self.element) dealoc(self.element);
var element = self.element = jqLite(html);
var scope = compiler.compile(element)(element);
diff --git a/test/CompilerSpec.js b/test/CompilerSpec.js
index fa63ab77..d8c7c1b8 100644
--- a/test/CompilerSpec.js
+++ b/test/CompilerSpec.js
@@ -1,5 +1,5 @@
describe('compiler', function(){
- var compiler, markup, directives, widgets, compile, log;
+ var compiler, markup, directives, widgets, compile, log, scope;
beforeEach(function(){
log = "";
@@ -32,6 +32,10 @@ describe('compiler', function(){
return scope;
};
});
+
+ afterEach(function(){
+ dealoc(scope);
+ });
it('should recognize a directive', function(){
var e = jqLite('<div directive="expr" ignore="me"></div>');
@@ -44,7 +48,8 @@ describe('compiler', function(){
};
};
var template = compiler.compile(e);
- var init = template(e).$init;
+ scope = template(e);
+ var init = scope.$init;
expect(log).toEqual("found");
init();
expect(e.hasClass('ng-directive')).toEqual(true);
@@ -52,12 +57,12 @@ describe('compiler', function(){
});
it('should recurse to children', function(){
- var scope = compile('<div><span hello="misko"/></div>');
+ scope = compile('<div><span hello="misko"/></div>');
expect(log).toEqual("hello misko");
});
it('should watch scope', function(){
- var scope = compile('<span watch="name"/>');
+ scope = compile('<span watch="name"/>');
expect(log).toEqual("");
scope.$eval();
scope.$set('name', 'misko');
@@ -71,7 +76,7 @@ describe('compiler', function(){
it('should prevent descend', function(){
directives.stop = function(){ this.descend(false); };
- var scope = compile('<span hello="misko" stop="true"><span hello="adam"/></span>');
+ scope = compile('<span hello="misko" stop="true"><span hello="adam"/></span>');
expect(log).toEqual("hello misko");
});
@@ -87,7 +92,7 @@ describe('compiler', function(){
});
};
};
- var scope = compile('before<span duplicate="expr">x</span>after');
+ scope = compile('before<span duplicate="expr">x</span>after');
expect(sortedHtml(scope.$element)).toEqual('<div>before<#comment></#comment><span>x</span>after</div>');
scope.$eval();
expect(sortedHtml(scope.$element)).toEqual('<div>before<#comment></#comment><span>x</span><span>x</span>after</div>');
@@ -103,7 +108,7 @@ describe('compiler', function(){
textNode[0].nodeValue = 'replaced';
}
});
- var scope = compile('before<span>middle</span>after');
+ scope = compile('before<span>middle</span>after');
expect(sortedHtml(scope.$element[0], true)).toEqual('<div>before<span class="ng-directive" hello="middle">replaced</span>after</div>');
expect(log).toEqual("hello middle");
});
@@ -116,7 +121,7 @@ describe('compiler', function(){
log += 'init';
};
};
- var scope = compile('<ng:button>push me</ng:button>');
+ scope = compile('<ng:button>push me</ng:button>');
expect(lowercase(scope.$element[0].innerHTML)).toEqual('<div>button</div>');
expect(log).toEqual('init');
});
@@ -135,7 +140,7 @@ describe('compiler', function(){
if (text == '{{1+2}}')
parent.text('3');
});
- var scope = compile('<div><h1>ignore me</h1></div>');
+ scope = compile('<div><h1>ignore me</h1></div>');
expect(scope.$element.text()).toEqual('3');
});
@@ -158,7 +163,7 @@ describe('compiler', function(){
textNode.remove();
}
});
- var scope = compile('A---B---C===D');
+ scope = compile('A---B---C===D');
expect(sortedHtml(scope.$element)).toEqual('<div>A<hr></hr>B<hr></hr>C<p></p>D</div>');
});
diff --git a/test/ResourceSpec.js b/test/ResourceSpec.js
index e258d5a3..a026263b 100644
--- a/test/ResourceSpec.js
+++ b/test/ResourceSpec.js
@@ -169,6 +169,7 @@ describe("resource", function() {
var person = Person.get({id:123});
$browser.xhr.flush();
expect(person.name).toEqual('misko');
+ dealoc(scope);
});
it('should return the same object when verifying the cache', function(){
@@ -188,6 +189,7 @@ describe("resource", function() {
$browser.xhr.flush();
expect(person2Cache).toEqual(person2);
expect(person2[0].name).toEqual('rob');
+ dealoc(scope);
});
describe('failure mode', function(){
diff --git a/test/ScenarioSpec.js b/test/ScenarioSpec.js
index 730019a2..4a8b5e69 100644
--- a/test/ScenarioSpec.js
+++ b/test/ScenarioSpec.js
@@ -1,52 +1,64 @@
describe("ScenarioSpec: Compilation", function(){
- it("should compile dom node and return scope", function(){
- var node = jqLite('<div ng:init="a=1">{{b=a+1}}</div>')[0];
- var scope = compile(node);
- scope.$init();
- expect(scope.a).toEqual(1);
- expect(scope.b).toEqual(2);
+ var scope;
+
+ beforeEach(function(){
+ scope = null;
});
-
- it("should compile jQuery node and return scope", function(){
- var scope = compile(jqLite('<div>{{a=123}}</div>')).$init();
- expect(jqLite(scope.$element).text()).toEqual('123');
+
+ afterEach(function(){
+ dealoc(scope);
});
-
- it("should compile text node and return scope", function(){
- var scope = compile('<div>{{a=123}}</div>').$init();
- expect(jqLite(scope.$element).text()).toEqual('123');
+
+ describe('compilation', function(){
+ it("should compile dom node and return scope", function(){
+ var node = jqLite('<div ng:init="a=1">{{b=a+1}}</div>')[0];
+ scope = compile(node);
+ scope.$init();
+ expect(scope.a).toEqual(1);
+ expect(scope.b).toEqual(2);
+ });
+
+ it("should compile jQuery node and return scope", function(){
+ scope = compile(jqLite('<div>{{a=123}}</div>')).$init();
+ expect(jqLite(scope.$element).text()).toEqual('123');
+ });
+
+ it("should compile text node and return scope", function(){
+ scope = compile('<div>{{a=123}}</div>').$init();
+ expect(jqLite(scope.$element).text()).toEqual('123');
+ });
});
-});
-
-describe("ScenarioSpec: Scope", function(){
- it("should have set, get, eval, $init, updateView methods", function(){
- var scope = compile('<div>{{a}}</div>').$init();
- scope.$eval("$invalidWidgets.push({})");
- expect(scope.$set("a", 2)).toEqual(2);
- expect(scope.$get("a")).toEqual(2);
- expect(scope.$eval("a=3")).toEqual(3);
- scope.$eval();
- expect(jqLite(scope.$element).text()).toEqual('3');
+
+ describe('scope', function(){
+ it("should have set, get, eval, $init, updateView methods", function(){
+ scope = compile('<div>{{a}}</div>').$init();
+ scope.$eval("$invalidWidgets.push({})");
+ expect(scope.$set("a", 2)).toEqual(2);
+ expect(scope.$get("a")).toEqual(2);
+ expect(scope.$eval("a=3")).toEqual(3);
+ scope.$eval();
+ expect(jqLite(scope.$element).text()).toEqual('3');
+ });
+
+ it("should have $ objects", function(){
+ scope = compile('<div></div>', {$config: {a:"b"}});
+ expect(scope.$get('$location')).toBeDefined();
+ expect(scope.$get('$eval')).toBeDefined();
+ expect(scope.$get('$config')).toBeDefined();
+ expect(scope.$get('$config.a')).toEqual("b");
+ });
});
-
- it("should have $ objects", function(){
- var scope = compile('<div></div>', {$config: {a:"b"}});
- expect(scope.$get('$location')).toBeDefined();
- expect(scope.$get('$eval')).toBeDefined();
- expect(scope.$get('$config')).toBeDefined();
- expect(scope.$get('$config.a')).toEqual("b");
+
+ describe("configuration", function(){
+ it("should take location object", function(){
+ var url = "http://server/#?book=moby";
+ scope = compile("<div>{{$location}}</div>");
+ var $location = scope.$location;
+ var $browser = scope.$inject('$browser');
+ expect($location.hashSearch.book).toBeUndefined();
+ $browser.setUrl(url);
+ $browser.poll();
+ expect($location.hashSearch.book).toEqual('moby');
+ });
});
-});
-
-describe("ScenarioSpec: configuration", function(){
- it("should take location object", function(){
- var url = "http://server/#?book=moby";
- var scope = compile("<div>{{$location}}</div>");
- var $location = scope.$location;
- var $browser = scope.$inject('$browser');
- expect($location.hashSearch.book).toBeUndefined();
- $browser.setUrl(url);
- $browser.poll();
- expect($location.hashSearch.book).toEqual('moby');
- });
-});
+}); \ No newline at end of file
diff --git a/test/ScopeSpec.js b/test/ScopeSpec.js
index 38350b17..acded34b 100644
--- a/test/ScopeSpec.js
+++ b/test/ScopeSpec.js
@@ -209,28 +209,6 @@ describe('scope/model', function(){
});
});
- describe('$postEval', function(){
- it('should eval function once and last', function(){
- var log = '';
- var scope = createScope();
- function onceOnly(){log+= '@';}
- scope.$onEval(function(){log+= '.';});
- scope.$postEval(function(){log+= '!';});
- scope.$postEval(onceOnly);
- scope.$postEval(onceOnly);
- scope.$postEval(); // ignore
- scope.$eval();
- expect(log).toEqual('.!@');
- scope.$eval();
- expect(log).toEqual('.!@.');
-
- scope.$postEval(onceOnly);
- scope.$postEval(onceOnly);
- scope.$eval();
- expect(log).toEqual('.!@..@');
- });
- });
-
describe('$new', function(){
it('should $new should create new child scope and $become controller', function(){
var parent = createScope(null, {exampleService: function(){return 'Example Service';}});
diff --git a/test/ValidatorsTest.js b/test/ValidatorsTest.js
index cc77b6da..f740c7b2 100644
--- a/test/ValidatorsTest.js
+++ b/test/ValidatorsTest.js
@@ -104,12 +104,6 @@ describe('Validator:asynchronous', function(){
afterEach(function(){
if (self.$element) self.$element.remove();
- var oldCache = jqCache;
- jqCache = {};
- if (size(oldCache)) {
- dump(oldCache);
- }
- expect(size(oldCache)).toEqual(0);
});
it('should make a request and show spinner', function(){
diff --git a/test/directivesSpec.js b/test/directivesSpec.js
index b70067ba..ab1813c3 100644
--- a/test/directivesSpec.js
+++ b/test/directivesSpec.js
@@ -13,8 +13,7 @@ describe("directive", function(){
});
afterEach(function() {
- if (model && model.$element) model.$element.remove();
- expect(size(jqCache)).toEqual(0);
+ dealoc(model);
});
it("should ng:init", function() {
diff --git a/test/markupSpec.js b/test/markupSpec.js
index cb8ee23f..3234bf2f 100644
--- a/test/markupSpec.js
+++ b/test/markupSpec.js
@@ -14,8 +14,7 @@ describe("markups", function(){
});
afterEach(function(){
- if (element) element.remove();
- expect(size(jqCache)).toEqual(0);
+ dealoc(element);
});
it('should translate {{}} in text', function(){
@@ -63,92 +62,91 @@ describe("markups", function(){
compile('<a ng:href="{{url}}" rel="{{rel}}"></a>');
expect(sortedHtml(element)).toEqual('<a ng:bind-attr="{"href":"{{url}}","rel":"{{rel}}"}"></a>');
});
-});
+ it('should Parse Text With No Bindings', function(){
+ var parts = parseBindings("a");
+ assertEquals(parts.length, 1);
+ assertEquals(parts[0], "a");
+ assertTrue(!binding(parts[0]));
+ });
+
+ it('should Parse Empty Text', function(){
+ var parts = parseBindings("");
+ assertEquals(parts.length, 1);
+ assertEquals(parts[0], "");
+ assertTrue(!binding(parts[0]));
+ });
+
+ it('should Parse Inner Binding', function(){
+ var parts = parseBindings("a{{b}}C");
+ assertEquals(parts.length, 3);
+ assertEquals(parts[0], "a");
+ assertTrue(!binding(parts[0]));
+ assertEquals(parts[1], "{{b}}");
+ assertEquals(binding(parts[1]), "b");
+ assertEquals(parts[2], "C");
+ assertTrue(!binding(parts[2]));
+ });
+
+ it('should Parse Ending Binding', function(){
+ var parts = parseBindings("a{{b}}");
+ assertEquals(parts.length, 2);
+ assertEquals(parts[0], "a");
+ assertTrue(!binding(parts[0]));
+ assertEquals(parts[1], "{{b}}");
+ assertEquals(binding(parts[1]), "b");
+ });
+
+ it('should Parse Begging Binding', function(){
+ var parts = parseBindings("{{b}}c");
+ assertEquals(parts.length, 2);
+ assertEquals(parts[0], "{{b}}");
+ assertEquals(binding(parts[0]), "b");
+ assertEquals(parts[1], "c");
+ assertTrue(!binding(parts[1]));
+ });
+
+ it('should Parse Loan Binding', function(){
+ var parts = parseBindings("{{b}}");
+ assertEquals(parts.length, 1);
+ assertEquals(parts[0], "{{b}}");
+ assertEquals(binding(parts[0]), "b");
+ });
+
+ it('should Parse Two Bindings', function(){
+ var parts = parseBindings("{{b}}{{c}}");
+ assertEquals(parts.length, 2);
+ assertEquals(parts[0], "{{b}}");
+ assertEquals(binding(parts[0]), "b");
+ assertEquals(parts[1], "{{c}}");
+ assertEquals(binding(parts[1]), "c");
+ });
+
+ it('should Parse Two Bindings With Text In Middle', function(){
+ var parts = parseBindings("{{b}}x{{c}}");
+ assertEquals(parts.length, 3);
+ assertEquals(parts[0], "{{b}}");
+ assertEquals(binding(parts[0]), "b");
+ assertEquals(parts[1], "x");
+ assertTrue(!binding(parts[1]));
+ assertEquals(parts[2], "{{c}}");
+ assertEquals(binding(parts[2]), "c");
+ });
+
+ it('should Parse Multiline', function(){
+ var parts = parseBindings('"X\nY{{A\nB}}C\nD"');
+ assertTrue(!!binding('{{A\nB}}'));
+ assertEquals(parts.length, 3);
+ assertEquals(parts[0], '"X\nY');
+ assertEquals(parts[1], '{{A\nB}}');
+ assertEquals(parts[2], 'C\nD"');
+ });
+
+ it('should Has Binding', function(){
+ assertTrue(hasBindings(parseBindings("{{a}}")));
+ assertTrue(!hasBindings(parseBindings("a")));
+ assertTrue(hasBindings(parseBindings("{{b}}x{{c}}")));
+ });
+
+});
-var BindingMarkupTest = TestCase("BindingMarkupTest");
-
-BindingMarkupTest.prototype.testParseTextWithNoBindings = function(){
- var parts = parseBindings("a");
- assertEquals(parts.length, 1);
- assertEquals(parts[0], "a");
- assertTrue(!binding(parts[0]));
-};
-
-BindingMarkupTest.prototype.testParseEmptyText = function(){
- var parts = parseBindings("");
- assertEquals(parts.length, 1);
- assertEquals(parts[0], "");
- assertTrue(!binding(parts[0]));
-};
-
-BindingMarkupTest.prototype.testParseInnerBinding = function(){
- var parts = parseBindings("a{{b}}c");
- assertEquals(parts.length, 3);
- assertEquals(parts[0], "a");
- assertTrue(!binding(parts[0]));
- assertEquals(parts[1], "{{b}}");
- assertEquals(binding(parts[1]), "b");
- assertEquals(parts[2], "c");
- assertTrue(!binding(parts[2]));
-};
-
-BindingMarkupTest.prototype.testParseEndingBinding = function(){
- var parts = parseBindings("a{{b}}");
- assertEquals(parts.length, 2);
- assertEquals(parts[0], "a");
- assertTrue(!binding(parts[0]));
- assertEquals(parts[1], "{{b}}");
- assertEquals(binding(parts[1]), "b");
-};
-
-BindingMarkupTest.prototype.testParseBeggingBinding = function(){
- var parts = parseBindings("{{b}}c");
- assertEquals(parts.length, 2);
- assertEquals(parts[0], "{{b}}");
- assertEquals(binding(parts[0]), "b");
- assertEquals(parts[1], "c");
- assertTrue(!binding(parts[1]));
-};
-
-BindingMarkupTest.prototype.testParseLoanBinding = function(){
- var parts = parseBindings("{{b}}");
- assertEquals(parts.length, 1);
- assertEquals(parts[0], "{{b}}");
- assertEquals(binding(parts[0]), "b");
-};
-
-BindingMarkupTest.prototype.testParseTwoBindings = function(){
- var parts = parseBindings("{{b}}{{c}}");
- assertEquals(parts.length, 2);
- assertEquals(parts[0], "{{b}}");
- assertEquals(binding(parts[0]), "b");
- assertEquals(parts[1], "{{c}}");
- assertEquals(binding(parts[1]), "c");
-};
-
-BindingMarkupTest.prototype.testParseTwoBindingsWithTextInMiddle = function(){
- var parts = parseBindings("{{b}}x{{c}}");
- assertEquals(parts.length, 3);
- assertEquals(parts[0], "{{b}}");
- assertEquals(binding(parts[0]), "b");
- assertEquals(parts[1], "x");
- assertTrue(!binding(parts[1]));
- assertEquals(parts[2], "{{c}}");
- assertEquals(binding(parts[2]), "c");
-};
-
-BindingMarkupTest.prototype.testParseMultiline = function(){
- var parts = parseBindings('"X\nY{{A\nB}}C\nD"');
- assertTrue(!!binding('{{A\nB}}'));
- assertEquals(parts.length, 3);
- assertEquals(parts[0], '"X\nY');
- assertEquals(parts[1], '{{A\nB}}');
- assertEquals(parts[2], 'C\nD"');
-};
-
-BindingMarkupTest.prototype.testHasBinding = function(){
- assertTrue(hasBindings(parseBindings("{{a}}")));
- assertTrue(!hasBindings(parseBindings("a")));
- assertTrue(hasBindings(parseBindings("{{b}}x{{c}}")));
-};
diff --git a/test/servicesSpec.js b/test/servicesSpec.js
index 13e61b18..ff90e0a1 100644
--- a/test/servicesSpec.js
+++ b/test/servicesSpec.js
@@ -17,8 +17,7 @@ describe("service", function(){
});
afterEach(function(){
- if (scope && scope.$element)
- scope.$element.remove();
+ dealoc(scope);
});
@@ -202,7 +201,7 @@ describe("service", function(){
});
it('should update hash before any processing', function(){
- var scope = compile('<div>');
+ scope = compile('<div>');
var log = '';
scope.$watch('$location.hash', function(){
log += this.$location.hashPath + ';';
@@ -259,7 +258,7 @@ describe("service", function(){
describe("$invalidWidgets", function(){
it("should count number of invalid widgets", function(){
- var scope = compile('<input name="price" ng:required ng:validate="number"></input>');
+ scope = compile('<input name="price" ng:required ng:validate="number"></input>');
jqLite(document.body).append(scope.$element);
scope.$init();
expect(scope.$invalidWidgets.length).toEqual(1);
@@ -291,8 +290,8 @@ describe("service", function(){
function BookChapter() {
this.log = '<init>';
}
- var scope = compile('<div></div>').$init();
- var $route = scope.$inject('$route');
+ scope = compile('<div></div>').$init();
+ $route = scope.$inject('$route');
$route.when('/Book/:book/Chapter/:chapter', {controller: BookChapter, template:'Chapter.html'});
$route.when('/Blank');
$route.onChange(function(){
diff --git a/test/testabilityPatch.js b/test/testabilityPatch.js
index e8041ac7..ea5c8ab7 100644
--- a/test/testabilityPatch.js
+++ b/test/testabilityPatch.js
@@ -50,6 +50,25 @@ beforeEach(function(){
});
});
+afterEach(clearJqCache);
+
+function clearJqCache(){
+ var count = 0;
+ foreachSorted(jqCache, function(value, key){
+ count ++;
+ delete jqCache[key];
+ foreach(value, function(value, key){
+ if (value.$element)
+ dump(key, sortedHtml(value.$element));
+ else
+ dump(key, toJson(value));
+ });
+ });
+ if (count) {
+ fail('Found jqCache references that were not deallocated!');
+ }
+}
+
function nakedExpect(obj) {
return expect(angular.fromJson(angular.toJson(obj)));
}
@@ -58,6 +77,11 @@ function childNode(element, index) {
return jqLite(element[0].childNodes[index]);
}
+function dealoc(obj) {
+ var element = (obj||{}).$element || obj;
+ if (element && element.dealoc) element.dealoc();
+}
+
extend(angular, {
'element': jqLite,
'compile': compile,
diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js
index d3957e66..cb3b76a1 100644
--- a/test/widgetsSpec.js
+++ b/test/widgetsSpec.js
@@ -16,8 +16,7 @@ describe("widget", function(){
});
afterEach(function(){
- if (element && element.dealoc) element.dealoc();
- expect(size(jqCache)).toEqual(0);
+ dealoc(element);
});
describe("input", function(){
@@ -362,7 +361,7 @@ describe("widget", function(){
'<option value="{{$index}}" ng:repeat="name in [\'A\', \'B\', \'C\']">{{name}}</option>' +
'</select>');
// childNodes[0] is repeater comment
- expect(scope.selection).toEqual(undefined);
+ expect(scope.selection).toEqual(0);
browserTrigger(element[0].childNodes[2], 'change');
expect(scope.selection).toEqual(1);
@@ -398,6 +397,32 @@ describe("widget", function(){
scope.$eval();
expect(element[0].childNodes[1].selected).toEqual(true);
});
+
+ it('should select default option on repeater', function(){
+ compile(
+ '<select name="selection">' +
+ '<option ng:repeat="no in [1,2]">{{no}}</option>' +
+ '</select>');
+ expect(scope.selection).toEqual('1');
+ });
+
+ it('should select selected option on repeater', function(){
+ compile(
+ '<select name="selection">' +
+ '<option ng:repeat="no in [1,2]">{{no}}</option>' +
+ '<option selected>ABC</option>' +
+ '</select>');
+ expect(scope.selection).toEqual('ABC');
+ });
+
+ it('should select dynamically selected option on repeater', function(){
+ compile(
+ '<select name="selection">' +
+ '<option ng:repeat="no in [1,2]" ng:bind-attr="{selected:\'{{no==2}}\'}">{{no}}</option>' +
+ '</select>');
+ expect(scope.selection).toEqual('2');
+ });
+
});
it('should support type="select-multiple"', function(){
@@ -476,6 +501,7 @@ describe("widget", function(){
scope.url = '/Book/Moby';
scope.$init();
expect(scope.$element.text()).toEqual('Moby');
+ dealoc(scope);
});
it("should match sandwich ids", function(){
@@ -491,6 +517,7 @@ describe("widget", function(){
scope.$init();
expect(scope.name).toEqual(undefined);
expect(scope.$element.text()).toEqual('works');
+ dealoc(scope);
});
});
@@ -504,6 +531,7 @@ describe("widget", function(){
scope.$inject('$xhr.cache').data.myUrl = {value:'{{name}}'};
scope.$init();
expect(element.text()).toEqual('misko');
+ dealoc(scope);
});
it('should remove previously included text if a falsy value is bound to src', function() {
@@ -521,6 +549,7 @@ describe("widget", function(){
scope.$eval();
expect(element.text()).toEqual('');
+ dealoc(scope);
});
it('should allow this for scope', function(){
@@ -532,6 +561,7 @@ describe("widget", function(){
// This should not be 4, but to fix this properly
// we need to have real events on the scopes.
expect(element.text()).toEqual('4');
+ dealoc(scope);
});
it('should evaluate onload expression when a partial is loaded', function() {
@@ -545,6 +575,7 @@ describe("widget", function(){
scope.$init();
expect(element.text()).toEqual('my partial');
expect(scope.loaded).toBe(true);
+ dealoc(scope);
});
});