aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorRob Spies2010-06-22 17:09:55 -0700
committerRob Spies2010-06-22 17:09:55 -0700
commit1500e91defa4020bfe9608749b25e585cd1d8e3d (patch)
tree8c2872252b62567dc4eb00f7d7547661d5674c55 /test
parenteaa397c76b7d28343cde9f3a0338b9b0e79197c8 (diff)
parentb129a1094e6b42ed82c3ccecc2f40daaa0a6cb6a (diff)
downloadangular.js-1500e91defa4020bfe9608749b25e585cd1d8e3d.tar.bz2
Merge http://github.com/angular/angular.js into angular
Conflicts: .gitignore
Diffstat (limited to 'test')
-rw-r--r--test/AngularSpec.js52
-rw-r--r--test/ApiTest.js256
-rw-r--r--test/BinderTest.js676
-rw-r--r--test/BrowserSpecs.js48
-rw-r--r--test/CompilerSpec.js137
-rw-r--r--test/ConsoleTest.js12
-rw-r--r--test/FiltersTest.js143
-rw-r--r--test/FormattersTest.js37
-rw-r--r--test/JsonTest.js84
-rw-r--r--test/ParserTest.js465
-rw-r--r--test/ResourceSpec.js159
-rw-r--r--test/ScenarioSpec.js51
-rw-r--r--test/ScopeSpec.js181
-rw-r--r--test/ValidatorsTest.js169
-rw-r--r--test/angular-mocks.js101
-rw-r--r--test/delete/ScopeTest.js145
-rw-r--r--test/delete/WidgetsTest.js268
-rw-r--r--test/directivesSpec.js226
-rw-r--r--test/jquery_alias.js1
-rw-r--r--test/jquery_remove.js1
-rw-r--r--test/markupSpec.js138
-rw-r--r--test/moveToAngularCom/Base64Test.js5
-rw-r--r--test/moveToAngularCom/DataStoreTest.js616
-rw-r--r--test/moveToAngularCom/EntityDeclarationTest.js50
-rw-r--r--test/moveToAngularCom/FileControllerTest.js98
-rw-r--r--test/moveToAngularCom/MiscTest.js35
-rw-r--r--test/moveToAngularCom/ModelTest.js84
-rw-r--r--test/moveToAngularCom/ServerTest.js42
-rw-r--r--test/moveToAngularCom/UsersTest.js26
-rw-r--r--test/moveToAngularCom/miscTest.js35
-rw-r--r--test/scenario/DSLSpec.js55
-rw-r--r--test/scenario/RunnerSpec.js190
-rw-r--r--test/servicesSpec.js362
-rw-r--r--test/testabilityPatch.js180
-rw-r--r--test/widgetsSpec.js441
35 files changed, 5569 insertions, 0 deletions
diff --git a/test/AngularSpec.js b/test/AngularSpec.js
new file mode 100644
index 00000000..de724f03
--- /dev/null
+++ b/test/AngularSpec.js
@@ -0,0 +1,52 @@
+describe('Angular', function(){
+ xit('should fire on updateEvents', function(){
+ var onUpdateView = jasmine.createSpy();
+ var scope = angular.compile("<div></div>", { onUpdateView: onUpdateView });
+ expect(onUpdateView).wasNotCalled();
+ scope.$init();
+ scope.$eval();
+ expect(onUpdateView).wasCalled();
+ });
+});
+
+describe("copy", function(){
+ it("should return same object", function (){
+ var obj = {};
+ var arr = [];
+ assertSame(obj, copy({}, obj));
+ assertSame(arr, copy([], arr));
+ });
+
+ it("should copy array", function(){
+ var src = [1, {name:"value"}];
+ var dst = [{key:"v"}];
+ assertSame(dst, copy(src, dst));
+ assertEquals([1, {name:"value"}], dst);
+ assertEquals({name:"value"}, dst[1]);
+ assertNotSame(src[1], dst[1]);
+ });
+
+ it('should copy empty array', function() {
+ var src = [];
+ var dst = [{key: "v"}];
+ assertEquals([], copy(src, dst));
+ assertEquals([], dst);
+ });
+
+ it("should copy object", function(){
+ var src = {a:{name:"value"}};
+ var dst = {b:{key:"v"}};
+ assertSame(dst, copy(src, dst));
+ assertEquals({a:{name:"value"}}, dst);
+ assertEquals(src.a, dst.a);
+ assertNotSame(src.a, dst.a);
+ });
+
+ it("should copy primitives", function(){
+ expect(copy(null)).toEqual(null);
+ expect(copy('')).toEqual('');
+ expect(copy(123)).toEqual(123);
+ expect(copy([{key:null}])).toEqual([{key:null}]);
+ });
+
+});
diff --git a/test/ApiTest.js b/test/ApiTest.js
new file mode 100644
index 00000000..4035cdbb
--- /dev/null
+++ b/test/ApiTest.js
@@ -0,0 +1,256 @@
+ApiTest = TestCase("ApiTest");
+
+ApiTest.prototype.testItShouldReturnTypeOf = function (){
+ assertEquals("undefined", angular.Object.typeOf(undefined));
+ assertEquals("null", angular.Object.typeOf(null));
+ assertEquals("object", angular.Collection.typeOf({}));
+ assertEquals("array", angular.Array.typeOf([]));
+ assertEquals("string", angular.Object.typeOf(""));
+ assertEquals("date", angular.Object.typeOf(new Date()));
+ assertEquals("element", angular.Object.typeOf(document.body));
+ assertEquals("function", angular.Object.typeOf(function(){}));
+};
+
+ApiTest.prototype.testItShouldReturnSize = function(){
+ assertEquals(0, angular.Collection.size({}));
+ assertEquals(1, angular.Collection.size({a:"b"}));
+ assertEquals(0, angular.Object.size({}));
+ assertEquals(1, angular.Array.size([0]));
+};
+
+ApiTest.prototype.testIncludeIf = function() {
+ var array = [];
+ var obj = {};
+
+ angular.Array.includeIf(array, obj, true);
+ angular.Array.includeIf(array, obj, true);
+ assertTrue(includes(array, obj));
+ assertEquals(1, array.length);
+
+ angular.Array.includeIf(array, obj, false);
+ assertFalse(includes(array, obj));
+ assertEquals(0, array.length);
+
+ angular.Array.includeIf(array, obj, 'x');
+ assertTrue(includes(array, obj));
+ assertEquals(1, array.length);
+ angular.Array.includeIf(array, obj, '');
+ assertFalse(includes(array, obj));
+ assertEquals(0, array.length);
+};
+
+ApiTest.prototype.testSum = function(){
+ assertEquals(3, angular.Array.sum([{a:"1"}, {a:"2"}], 'a'));
+};
+
+ApiTest.prototype.testSumContainingNaN = function(){
+ assertEquals(1, angular.Array.sum([{a:1}, {a:Number.NaN}], 'a'));
+ assertEquals(1, angular.Array.sum([{a:1}, {a:Number.NaN}], function($){return $.a;}));
+};
+
+ApiTest.prototype.testInclude = function(){
+ assertTrue(angular.Array.include(['a'], 'a'));
+ assertTrue(angular.Array.include(['a', 'b'], 'a'));
+ assertTrue(!angular.Array.include(['c'], 'a'));
+ assertTrue(!angular.Array.include(['c', 'b'], 'a'));
+};
+
+ApiTest.prototype.testIndex = function(){
+ assertEquals(angular.Array.indexOf(['a'], 'a'), 0);
+ assertEquals(angular.Array.indexOf(['a', 'b'], 'a'), 0);
+ assertEquals(angular.Array.indexOf(['b', 'a'], 'a'), 1);
+ assertEquals(angular.Array.indexOf(['b', 'b'],'x'), -1);
+};
+
+ApiTest.prototype.testRemove = function(){
+ var items = ['a', 'b', 'c'];
+ assertEquals(angular.Array.remove(items, 'q'), 'q');
+ assertEquals(items.length, 3);
+
+ assertEquals(angular.Array.remove(items, 'b'), 'b');
+ assertEquals(items.length, 2);
+
+ assertEquals(angular.Array.remove(items, 'a'), 'a');
+ assertEquals(items.length, 1);
+
+ assertEquals(angular.Array.remove(items, 'c'), 'c');
+ assertEquals(items.length, 0);
+
+ assertEquals(angular.Array.remove(items, 'q'), 'q');
+ assertEquals(items.length, 0);
+};
+
+ApiTest.prototype.testFindById = function() {
+ var items = [{$id:1}, {$id:2}, {$id:3}];
+ assertNull(angular.Array.findById(items, 0));
+ assertEquals(items[0], angular.Array.findById(items, 1));
+ assertEquals(items[1], angular.Array.findById(items, 2));
+ assertEquals(items[2], angular.Array.findById(items, 3));
+};
+
+ApiTest.prototype.testFilter = function() {
+ var items = ["MIsKO", {name:"shyam"}, ["adam"], 1234];
+ assertEquals(4, angular.Array.filter(items, "").length);
+ assertEquals(4, angular.Array.filter(items, undefined).length);
+
+ assertEquals(1, angular.Array.filter(items, 'iSk').length);
+ assertEquals("MIsKO", angular.Array.filter(items, 'isk')[0]);
+
+ assertEquals(1, angular.Array.filter(items, 'yam').length);
+ assertEquals(items[1], angular.Array.filter(items, 'yam')[0]);
+
+ assertEquals(1, angular.Array.filter(items, 'da').length);
+ assertEquals(items[2], angular.Array.filter(items, 'da')[0]);
+
+ assertEquals(1, angular.Array.filter(items, '34').length);
+ assertEquals(1234, angular.Array.filter(items, '34')[0]);
+
+ assertEquals(0, angular.Array.filter(items, "I don't exist").length);
+};
+
+ApiTest.prototype.testShouldNotFilterOnSystemData = function() {
+ assertEquals("", "".charAt(0)); // assumption
+ var items = [{$name:"misko"}];
+ assertEquals(0, angular.Array.filter(items, "misko").length);
+};
+
+ApiTest.prototype.testFilterOnSpecificProperty = function() {
+ var items = [{ignore:"a", name:"a"}, {ignore:"a", name:"abc"}];
+ assertEquals(2, angular.Array.filter(items, {}).length);
+
+ assertEquals(2, angular.Array.filter(items, {name:'a'}).length);
+
+ assertEquals(1, angular.Array.filter(items, {name:'b'}).length);
+ assertEquals("abc", angular.Array.filter(items, {name:'b'})[0].name);
+};
+
+ApiTest.prototype.testFilterOnFunction = function() {
+ var items = [{name:"a"}, {name:"abc", done:true}];
+ assertEquals(1, angular.Array.filter(items, function(i){return i.done;}).length);
+};
+
+ApiTest.prototype.testFilterIsAndFunction = function() {
+ var items = [{first:"misko", last:"hevery"},
+ {first:"adam", last:"abrons"}];
+
+ assertEquals(2, angular.Array.filter(items, {first:'', last:''}).length);
+ assertEquals(1, angular.Array.filter(items, {first:'', last:'hevery'}).length);
+ assertEquals(0, angular.Array.filter(items, {first:'adam', last:'hevery'}).length);
+ assertEquals(1, angular.Array.filter(items, {first:'misko', last:'hevery'}).length);
+ assertEquals(items[0], angular.Array.filter(items, {first:'misko', last:'hevery'})[0]);
+};
+
+ApiTest.prototype.testFilterNot = function() {
+ var items = ["misko", "adam"];
+
+ assertEquals(1, angular.Array.filter(items, '!isk').length);
+ assertEquals(items[1], angular.Array.filter(items, '!isk')[0]);
+};
+
+ApiTest.prototype.testAdd = function() {
+ var add = angular.Array.add;
+ assertJsonEquals([{}, "a"], add(add([]),"a"));
+};
+
+ApiTest.prototype.testCount = function() {
+ var array = [{name:'a'},{name:'b'},{name:''}];
+ var obj = {};
+
+ assertEquals(3, angular.Array.count(array));
+ assertEquals(2, angular.Array.count(array, 'name'));
+ assertEquals(1, angular.Array.count(array, 'name=="a"'));
+};
+
+ApiTest.prototype.testFind = function() {
+ var array = [{name:'a'},{name:'b'},{name:''}];
+ var obj = {};
+
+ assertEquals(undefined, angular.Array.find(array, 'false'));
+ assertEquals('default', angular.Array.find(array, 'false', 'default'));
+ assertEquals('a', angular.Array.find(array, 'name == "a"').name);
+ assertEquals('', angular.Array.find(array, 'name == ""').name);
+};
+
+ApiTest.prototype.testItShouldSortArray = function() {
+ assertEquals([2,15], angular.Array.orderBy([15,2]));
+ assertEquals(["a","B", "c"], angular.Array.orderBy(["c","B", "a"]));
+ assertEquals([15,"2"], angular.Array.orderBy([15,"2"]));
+ assertEquals(["15","2"], angular.Array.orderBy(["15","2"]));
+ assertJsonEquals([{a:2},{a:15}], angular.Array.orderBy([{a:15},{a:2}], 'a'));
+ assertJsonEquals([{a:2},{a:15}], angular.Array.orderBy([{a:15},{a:2}], 'a', "F"));
+};
+
+ApiTest.prototype.testItShouldSortArrayInReverse = function() {
+ assertJsonEquals([{a:15},{a:2}], angular.Array.orderBy([{a:15},{a:2}], 'a', true));
+ assertJsonEquals([{a:15},{a:2}], angular.Array.orderBy([{a:15},{a:2}], 'a', "T"));
+ assertJsonEquals([{a:15},{a:2}], angular.Array.orderBy([{a:15},{a:2}], 'a', "reverse"));
+};
+
+ApiTest.prototype.testItShouldSortArrayByPredicate = function() {
+ assertJsonEquals([{a:2, b:1},{a:15, b:1}],
+ angular.Array.orderBy([{a:15, b:1},{a:2, b:1}], ['a', 'b']));
+ assertJsonEquals([{a:2, b:1},{a:15, b:1}],
+ angular.Array.orderBy([{a:15, b:1},{a:2, b:1}], ['b', 'a']));
+ assertJsonEquals([{a:15, b:1},{a:2, b:1}],
+ angular.Array.orderBy([{a:15, b:1},{a:2, b:1}], ['+b', '-a']));
+};
+
+ApiTest.prototype.testQuoteString = function(){
+ assertEquals(angular.String.quote('a'), '"a"');
+ assertEquals(angular.String.quote('\\'), '"\\\\"');
+ assertEquals(angular.String.quote("'a'"), '"\'a\'"');
+ assertEquals(angular.String.quote('"a"'), '"\\"a\\""');
+ assertEquals(angular.String.quote('\n\f\r\t'), '"\\n\\f\\r\\t"');
+};
+
+ApiTest.prototype.testQuoteStringBug = function(){
+ assertEquals('"7\\\\\\\"7"', angular.String.quote("7\\\"7"));
+};
+
+ApiTest.prototype.testQuoteUnicode = function(){
+ assertEquals('"abc\\u00a0def"', angular.String.quoteUnicode('abc\u00A0def'));
+};
+
+ApiTest.prototype.testMerge = function() {
+ var array = [{name:"misko"}];
+ angular.Array.merge(array, 0, {name:"", email:"email1"});
+ angular.Array.merge(array, 1, {name:"adam", email:"email2"});
+ assertJsonEquals([{"email":"email1","name":"misko"},{"email":"email2","name":"adam"}], array);
+};
+
+ApiTest.prototype.testOrderByToggle = function() {
+ var orderByToggle = angular.Array.orderByToggle;
+ var predicate = [];
+ assertEquals(['+a'], orderByToggle(predicate, 'a'));
+ assertEquals(['-a'], orderByToggle(predicate, 'a'));
+
+ assertEquals(['-a', '-b'], orderByToggle(['-b', 'a'], 'a'));
+};
+
+ApiTest.prototype.testOrderByToggle = function() {
+ var orderByDirection = angular.Array.orderByDirection;
+ assertEquals("", orderByDirection(['+a','b'], 'x'));
+ assertEquals("", orderByDirection(['+a','b'], 'b'));
+ assertEquals('ng-ascend', orderByDirection(['a','b'], 'a'));
+ assertEquals('ng-ascend', orderByDirection(['+a','b'], 'a'));
+ assertEquals('ng-descend', orderByDirection(['-a','b'], 'a'));
+ assertEquals('up', orderByDirection(['+a','b'], 'a', 'up', 'down'));
+ assertEquals('down', orderByDirection(['-a','b'], 'a', 'up', 'down'));
+};
+
+ApiTest.prototype.testDateToUTC = function(){
+ var date = new Date("Sep 10 2003 13:02:03 GMT");
+ assertEquals("date", angular.Object.typeOf(date));
+ assertEquals("2003-09-10T13:02:03Z", angular.Date.toString(date));
+};
+
+ApiTest.prototype.testStringFromUTC = function(){
+ var date = angular.String.toDate("2003-09-10T13:02:03Z");
+ assertEquals("date", angular.Object.typeOf(date));
+ assertEquals("2003-09-10T13:02:03Z", angular.Date.toString(date));
+ assertEquals("str", angular.String.toDate("str"));
+};
+
+ApiTest.prototype.testObjectShouldHaveExtend = function(){
+ assertEquals({a:1, b:2}, angular.Object.extend({a:1}, {b:2}));
+};
diff --git a/test/BinderTest.js b/test/BinderTest.js
new file mode 100644
index 00000000..ecdd506f
--- /dev/null
+++ b/test/BinderTest.js
@@ -0,0 +1,676 @@
+BinderTest = TestCase('BinderTest');
+
+BinderTest.prototype.setUp = function(){
+ var self = this;
+ this.compile = function(html, initialScope, config) {
+ var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget);
+ var element = self.element = jqLite(html);
+ var scope = compiler.compile(element)(element);
+ extend(scope, initialScope);
+ scope.$init();
+ return {node:element, scope:scope};
+ };
+ this.compileToHtml = function (content) {
+ return sortedHtml(this.compile(content).node);
+ };
+};
+
+BinderTest.prototype.tearDown = function(){
+ if (this.element && this.element.dealoc) {
+ this.element.dealoc();
+ }
+};
+
+
+BinderTest.prototype.testChangingTextfieldUpdatesModel = function(){
+ var state = this.compile('<input type="text" name="model.price" value="abc">', {model:{}});
+ state.scope.$eval();
+ assertEquals('abc', state.scope.model.price);
+};
+
+BinderTest.prototype.testChangingTextareaUpdatesModel = function(){
+ var c = this.compile('<textarea name="model.note">abc</textarea>');
+ c.scope.$eval();
+ assertEquals(c.scope.model.note, 'abc');
+};
+
+BinderTest.prototype.testChangingRadioUpdatesModel = function(){
+ var c = this.compile('<input type="radio" name="model.price" value="A" checked>' +
+ '<input type="radio" name="model.price" value="B">');
+ c.scope.$eval();
+ assertEquals(c.scope.model.price, 'A');
+};
+
+BinderTest.prototype.testChangingCheckboxUpdatesModel = function(){
+ var form = this.compile('<input type="checkbox" name="model.price" value="true" checked ng-format="boolean"/>');
+ assertEquals(true, form.scope.model.price);
+};
+
+BinderTest.prototype.testBindUpdate = function() {
+ var c = this.compile('<div ng-eval="a=123"/>');
+ assertEquals(123, c.scope.$get('a'));
+};
+
+BinderTest.prototype.testChangingSelectNonSelectedUpdatesModel = function(){
+ var form = this.compile('<select name="model.price"><option value="A">A</option><option value="B">B</option></select>');
+ assertEquals('A', form.scope.model.price);
+};
+
+BinderTest.prototype.testChangingMultiselectUpdatesModel = function(){
+ var form = this.compile('<select name="Invoice.options" multiple="multiple">' +
+ '<option value="A" selected>Gift wrap</option>' +
+ '<option value="B" selected>Extra padding</option>' +
+ '<option value="C">Expedite</option>' +
+ '</select>');
+ assertJsonEquals(["A", "B"], form.scope.$get('Invoice').options);
+};
+
+BinderTest.prototype.testChangingSelectSelectedUpdatesModel = function(){
+ var form = this.compile('<select name="model.price"><option>A</option><option selected value="b">B</option></select>');
+ assertEquals(form.scope.model.price, 'b');
+};
+
+BinderTest.prototype.testExecuteInitialization = function() {
+ var c = this.compile('<div ng-init="a=123">');
+ assertEquals(c.scope.$get('a'), 123);
+};
+
+BinderTest.prototype.testExecuteInitializationStatements = function() {
+ var c = this.compile('<div ng-init="a=123;b=345">');
+ assertEquals(c.scope.$get('a'), 123);
+ assertEquals(c.scope.$get('b'), 345);
+};
+
+BinderTest.prototype.testApplyTextBindings = function(){
+ var form = this.compile('<div ng-bind="model.a">x</div>');
+ form.scope.$set('model', {a:123});
+ form.scope.$eval();
+ assertEquals('123', form.node.text());
+};
+
+BinderTest.prototype.testReplaceBindingInTextWithSpan = function() {
+ assertEquals(this.compileToHtml("<b>a{{b}}c</b>"), '<b>a<span ng-bind="b"></span>c</b>');
+ assertEquals(this.compileToHtml("<b>{{b}}</b>"), '<b><span ng-bind="b"></span></b>');
+};
+
+BinderTest.prototype.testBindingSpaceConfusesIE = function() {
+ if (!msie) return;
+ var span = document.createElement("span");
+ span.innerHTML = '&nbsp;';
+ var nbsp = span.firstChild.nodeValue;
+ assertEquals(
+ '<b><span ng-bind="a"></span><span>'+nbsp+'</span><span ng-bind="b"></span></b>',
+ this.compileToHtml("<b>{{a}} {{b}}</b>"));
+ assertEquals(
+ '<b><span ng-bind="A"></span><span>'+nbsp+'x </span><span ng-bind="B"></span><span>'+nbsp+'(</span><span ng-bind="C"></span>)</b>',
+ this.compileToHtml("<b>{{A}} x {{B}} ({{C}})</b>"));
+};
+
+BinderTest.prototype.testBindingOfAttributes = function() {
+ var c = this.compile("<a href='http://s/a{{b}}c' foo='x'></a>");
+ var attrbinding = c.node.attr("ng-bind-attr");
+ var bindings = fromJson(attrbinding);
+ assertEquals("http://s/a{{b}}c", decodeURI(bindings.href));
+ assertTrue(!bindings.foo);
+};
+
+BinderTest.prototype.testMarkMultipleAttributes = function() {
+ var c = this.compile('<a href="http://s/a{{b}}c" foo="{{d}}"></a>');
+ var attrbinding = c.node.attr("ng-bind-attr");
+ var bindings = fromJson(attrbinding);
+ assertEquals(bindings.foo, "{{d}}");
+ assertEquals(decodeURI(bindings.href), "http://s/a{{b}}c");
+};
+
+BinderTest.prototype.testAttributesNoneBound = function() {
+ var c = this.compile("<a href='abc' foo='def'></a>");
+ var a = c.node;
+ assertEquals(a[0].nodeName, "A");
+ assertTrue(!a.attr("ng-bind-attr"));
+};
+
+BinderTest.prototype.testExistingAttrbindingIsAppended = function() {
+ var c = this.compile("<a href='http://s/{{abc}}' ng-bind-attr='{\"b\":\"{{def}}\"}'></a>");
+ var a = c.node;
+ assertEquals('{"b":"{{def}}","href":"http://s/{{abc}}"}', a.attr('ng-bind-attr'));
+};
+
+BinderTest.prototype.testAttributesAreEvaluated = function(){
+ var c = this.compile('<a ng-bind-attr=\'{"a":"a", "b":"a+b={{a+b}}"}\'></a>');
+ var binder = c.binder, form = c.node;
+ c.scope.$eval('a=1;b=2');
+ c.scope.$eval();
+ var a = c.node;
+ assertEquals(a.attr('a'), 'a');
+ assertEquals(a.attr('b'), 'a+b=3');
+};
+
+BinderTest.prototype.testInputTypeButtonActionExecutesInScope = function(){
+ var savedCalled = false;
+ var c = this.compile('<input type="button" ng-click="person.save()" value="Apply">');
+ c.scope.$set("person.save", function(){
+ savedCalled = true;
+ });
+ c.node.trigger('click');
+ assertTrue(savedCalled);
+};
+
+BinderTest.prototype.testInputTypeButtonActionExecutesInScope2 = function(){
+ var log = "";
+ var c = this.compile('<input type="image" ng-click="action()">');
+ c.scope.$set("action", function(){
+ log += 'click;';
+ });
+ expect(log).toEqual('');
+ c.node.trigger('click');
+ expect(log).toEqual('click;');
+};
+
+BinderTest.prototype.testButtonElementActionExecutesInScope = function(){
+ var savedCalled = false;
+ var c = this.compile('<button ng-click="person.save()">Apply</button>');
+ c.scope.$set("person.save", function(){
+ savedCalled = true;
+ });
+ c.node.trigger('click');
+ assertTrue(savedCalled);
+};
+
+BinderTest.prototype.testRepeaterUpdateBindings = function(){
+ var a = this.compile('<ul><LI ng-repeat="item in model.items" ng-bind="item.a"/></ul>');
+ var form = a.node;
+ var items = [{a:"A"}, {a:"B"}];
+ a.scope.$set('model', {items:items});
+
+ a.scope.$eval();
+ assertEquals('<ul>' +
+ '<#comment></#comment>' +
+ '<li ng-bind="item.a" ng-repeat-index="0">A</li>' +
+ '<li ng-bind="item.a" ng-repeat-index="1">B</li>' +
+ '</ul>', sortedHtml(form));
+
+ items.unshift({a:'C'});
+ a.scope.$eval();
+ assertEquals('<ul>' +
+ '<#comment></#comment>' +
+ '<li ng-bind="item.a" ng-repeat-index="0">C</li>' +
+ '<li ng-bind="item.a" ng-repeat-index="1">A</li>' +
+ '<li ng-bind="item.a" ng-repeat-index="2">B</li>' +
+ '</ul>', sortedHtml(form));
+
+ items.shift();
+ a.scope.$eval();
+ assertEquals('<ul>' +
+ '<#comment></#comment>' +
+ '<li ng-bind="item.a" ng-repeat-index="0">A</li>' +
+ '<li ng-bind="item.a" ng-repeat-index="1">B</li>' +
+ '</ul>', sortedHtml(form));
+
+ items.shift();
+ items.shift();
+ a.scope.$eval();
+};
+
+BinderTest.prototype.testRepeaterContentDoesNotBind = function(){
+ var a = this.compile('<ul><LI ng-repeat="item in model.items"><span ng-bind="item.a"></span></li></ul>');
+ a.scope.$set('model', {items:[{a:"A"}]});
+ a.scope.$eval();
+ assertEquals('<ul>' +
+ '<#comment></#comment>' +
+ '<li ng-repeat-index="0"><span ng-bind="item.a">A</span></li>' +
+ '</ul>', sortedHtml(a.node));
+};
+
+BinderTest.prototype.testExpandEntityTag = function(){
+ assertEquals(
+ '<div ng-entity="Person" ng-watch="$anchor.a:1"></div>',
+ this.compileToHtml('<div ng-entity="Person" ng-watch="$anchor.a:1"/>'));
+};
+
+BinderTest.prototype.testDoNotOverwriteCustomAction = function(){
+ var html = this.compileToHtml('<input type="submit" value="Save" action="foo();">');
+ assertTrue(html.indexOf('action="foo();"') > 0 );
+};
+
+BinderTest.prototype.testRepeaterAdd = function(){
+ var c = this.compile('<div><input type="text" name="item.x" ng-repeat="item in items"></div>');
+ var doc = c.node;
+ c.scope.$set('items', [{x:'a'}, {x:'b'}]);
+ c.scope.$eval();
+ var first = childNode(c.node, 1);
+ var second = childNode(c.node, 2);
+ assertEquals('a', first.val());
+ assertEquals('b', second.val());
+
+ first.val('ABC');
+ first.trigger('keyup');
+ assertEquals(c.scope.items[0].x, 'ABC');
+};
+
+BinderTest.prototype.testItShouldRemoveExtraChildrenWhenIteratingOverHash = function(){
+ var c = this.compile('<div><div ng-repeat="i in items">{{i}}</div></div>');
+ var items = {};
+ c.scope.$set("items", items);
+
+ c.scope.$eval();
+ expect(c.node[0].childNodes.length - 1).toEqual(0);
+
+ items.name = "misko";
+ c.scope.$eval();
+ expect(c.node[0].childNodes.length - 1).toEqual(1);
+
+ delete items.name;
+ c.scope.$eval();
+ expect(c.node[0].childNodes.length - 1).toEqual(0);
+};
+
+BinderTest.prototype.testIfTextBindingThrowsErrorDecorateTheSpan = function(){
+ var a = this.compile('<div>{{error.throw()}}</div>');
+ var doc = a.node;
+
+ a.scope.$set('error.throw', function(){throw "ErrorMsg1";});
+ a.scope.$eval();
+ var span = childNode(doc, 0);
+ assertTrue(span.hasClass('ng-exception'));
+ assertEquals('ErrorMsg1', fromJson(span.text()));
+ assertEquals('"ErrorMsg1"', span.attr('ng-exception'));
+
+ a.scope.$set('error.throw', function(){throw "MyError";});
+ a.scope.$eval();
+ span = childNode(doc, 0);
+ assertTrue(span.hasClass('ng-exception'));
+ assertTrue(span.text(), span.text().match('MyError') !== null);
+ assertEquals('"MyError"', span.attr('ng-exception'));
+
+ a.scope.$set('error.throw', function(){return "ok";});
+ a.scope.$eval();
+ assertFalse(span.hasClass('ng-exception'));
+ assertEquals('ok', span.text());
+ assertEquals(null, span.attr('ng-exception'));
+};
+
+BinderTest.prototype.testIfAttrBindingThrowsErrorDecorateTheAttribute = function(){
+ var a = this.compile('<div attr="before {{error.throw()}} after"></div>');
+ var doc = a.node;
+
+ a.scope.$set('error.throw', function(){throw "ErrorMsg";});
+ a.scope.$eval();
+ assertTrue('ng-exception', doc.hasClass('ng-exception'));
+ assertEquals('"ErrorMsg"', doc.attr('ng-exception'));
+ assertEquals('before "ErrorMsg" after', doc.attr('attr'));
+
+ a.scope.$set('error.throw', function(){ return 'X';});
+ a.scope.$eval();
+ assertFalse('!ng-exception', doc.hasClass('ng-exception'));
+ assertEquals('before X after', doc.attr('attr'));
+ assertEquals(null, doc.attr('ng-exception'));
+
+};
+
+BinderTest.prototype.testNestedRepeater = function() {
+ var a = this.compile('<div><div ng-repeat="m in model" name="{{m.name}}">' +
+ '<ul name="{{i}}" ng-repeat="i in m.item"></ul>' +
+ '</div></div>');
+
+ a.scope.$set('model', [{name:'a', item:['a1', 'a2']}, {name:'b', item:['b1', 'b2']}]);
+ a.scope.$eval();
+
+ assertEquals('<div>'+
+ '<#comment></#comment>'+
+ '<div name="a" ng-bind-attr="{"name":"{{m.name}}"}" ng-repeat-index="0">'+
+ '<#comment></#comment>'+
+ '<ul name="a1" ng-bind-attr="{"name":"{{i}}"}" ng-repeat-index="0"></ul>'+
+ '<ul name="a2" ng-bind-attr="{"name":"{{i}}"}" ng-repeat-index="1"></ul>'+
+ '</div>'+
+ '<div name="b" ng-bind-attr="{"name":"{{m.name}}"}" ng-repeat-index="1">'+
+ '<#comment></#comment>'+
+ '<ul name="b1" ng-bind-attr="{"name":"{{i}}"}" ng-repeat-index="0"></ul>'+
+ '<ul name="b2" ng-bind-attr="{"name":"{{i}}"}" ng-repeat-index="1"></ul>'+
+ '</div></div>', sortedHtml(a.node));
+};
+
+BinderTest.prototype.testHideBindingExpression = function() {
+ var a = this.compile('<div ng-hide="hidden == 3"/>');
+
+ a.scope.$set('hidden', 3);
+ a.scope.$eval();
+
+ assertHidden(a.node);
+
+ a.scope.$set('hidden', 2);
+ a.scope.$eval();
+
+ assertVisible(a.node);
+};
+
+BinderTest.prototype.testHideBinding = function() {
+ var c = this.compile('<div ng-hide="hidden"/>');
+
+ c.scope.$set('hidden', 'true');
+ c.scope.$eval();
+
+ assertHidden(c.node);
+
+ c.scope.$set('hidden', 'false');
+ c.scope.$eval();
+
+ assertVisible(c.node);
+
+ c.scope.$set('hidden', '');
+ c.scope.$eval();
+
+ assertVisible(c.node);
+};
+
+BinderTest.prototype.testShowBinding = function() {
+ var c = this.compile('<div ng-show="show"/>');
+
+ c.scope.$set('show', 'true');
+ c.scope.$eval();
+
+ assertVisible(c.node);
+
+ c.scope.$set('show', 'false');
+ c.scope.$eval();
+
+ assertHidden(c.node);
+
+ c.scope.$set('show', '');
+ c.scope.$eval();
+
+ assertHidden(c.node);
+};
+
+BinderTest.prototype.testBindClassUndefined = function() {
+ var doc = this.compile('<div ng-class="undefined"/>');
+ doc.scope.$eval();
+
+ assertEquals(
+ '<div class="undefined" ng-class="undefined"></div>',
+ sortedHtml(doc.node));
+};
+
+BinderTest.prototype.testBindClass = function() {
+ var c = this.compile('<div ng-class="class"/>');
+
+ c.scope.$set('class', 'testClass');
+ c.scope.$eval();
+
+ assertEquals(sortedHtml(c.node),
+ '<div class="testClass" ng-class="class"></div>');
+
+ c.scope.$set('class', ['a', 'b']);
+ c.scope.$eval();
+
+ assertEquals(sortedHtml(c.node),
+ '<div class="a b" ng-class="class"></div>');
+};
+
+BinderTest.prototype.testBindClassEvenOdd = function() {
+ var x = this.compile('<div><div ng-repeat="i in [0,1]" ng-class-even="\'e\'" ng-class-odd="\'o\'"/></div>');
+ x.scope.$eval();
+ assertEquals(
+ '<div><#comment></#comment>' +
+ '<div class="o" ng-class-even="\'e\'" ng-class-odd="\'o\'" ng-repeat-index="0"></div>' +
+ '<div class="e" ng-class-even="\'e\'" ng-class-odd="\'o\'" ng-repeat-index="1"></div></div>',
+ sortedHtml(x.node));
+};
+
+BinderTest.prototype.testBindStyle = function() {
+ var c = this.compile('<div ng-style="style"/>');
+
+ c.scope.$eval('style={color:"red"}');
+ c.scope.$eval();
+
+ assertEquals("red", c.node.css('color'));
+
+ c.scope.$eval('style={}');
+ c.scope.$eval();
+};
+
+BinderTest.prototype.testActionOnAHrefThrowsError = function(){
+ var model = {books:[]};
+ var c = this.compile('<a ng-click="action()">Add Phone</a>', model);
+ c.scope.action = function(){
+ throw {a:'abc', b:2};
+ };
+ var input = c.node;
+ input.trigger('click');
+ var error = fromJson(input.attr('ng-exception'));
+ assertEquals("abc", error.a);
+ assertEquals(2, error.b);
+ assertTrue("should have an error class", input.hasClass('ng-exception'));
+
+ // TODO: I think that exception should never get cleared so this portion of test makes no sense
+ //c.scope.action = noop;
+ //input.trigger('click');
+ //dump(input.attr('ng-error'));
+ //assertFalse('error class should be cleared', input.hasClass('ng-exception'));
+};
+
+BinderTest.prototype.testShoulIgnoreVbNonBindable = function(){
+ var c = this.compile("<div>{{a}}" +
+ "<div ng-non-bindable>{{a}}</div>" +
+ "<div ng-non-bindable=''>{{b}}</div>" +
+ "<div ng-non-bindable='true'>{{c}}</div></div>");
+ c.scope.$set('a', 123);
+ c.scope.$eval();
+ assertEquals('123{{a}}{{b}}{{c}}', c.node.text());
+};
+
+BinderTest.prototype.testOptionShouldUpdateParentToGetProperBinding = function() {
+ var c = this.compile('<select name="s"><option ng-repeat="i in [0,1]" value="{{i}}" ng-bind="i"></option></select>');
+ c.scope.$set('s', 1);
+ c.scope.$eval();
+ assertEquals(1, c.node[0].selectedIndex);
+};
+
+BinderTest.prototype.testRepeaterShouldBindInputsDefaults = function () {
+ var c = this.compile('<div><input value="123" name="item.name" ng-repeat="item in items"></div>');
+ c.scope.$set('items', [{}, {name:'misko'}]);
+ c.scope.$eval();
+
+ assertEquals("123", c.scope.$eval('items[0].name'));
+ assertEquals("misko", c.scope.$eval('items[1].name'));
+};
+
+BinderTest.prototype.testRepeaterShouldCreateArray = function () {
+ var c = this.compile('<input value="123" name="item.name" ng-repeat="item in items">');
+ c.scope.$eval();
+
+ assertEquals(0, c.scope.$get('items').length);
+};
+
+BinderTest.prototype.testShouldTemplateBindPreElements = function () {
+ var c = this.compile('<pre>Hello {{name}}!</pre>');
+ c.scope.$set("name", "World");
+ c.scope.$eval();
+
+ assertEquals('<pre ng-bind-template="Hello {{name}}!">Hello World!</pre>', sortedHtml(c.node));
+};
+
+BinderTest.prototype.testFillInOptionValueWhenMissing = function() {
+ var c = this.compile(
+ '<select><option selected="true">{{a}}</option><option value="">{{b}}</option><option>C</option></select>');
+ c.scope.$set('a', 'A');
+ c.scope.$set('b', 'B');
+ c.scope.$eval();
+ var optionA = childNode(c.node, 0);
+ var optionB = childNode(c.node, 1);
+ var optionC = childNode(c.node, 2);
+
+ expect(optionA.attr('value')).toEqual('A');
+ expect(optionA.text()).toEqual('A');
+
+ expect(optionB.attr('value')).toEqual('');
+ expect(optionB.text()).toEqual('B');
+
+ expect(optionC.attr('value')).toEqual('C');
+ expect(optionC.text()).toEqual('C');
+};
+
+BinderTest.prototype.testValidateForm = function() {
+ var c = this.compile('<div><input name="name" ng-required>' +
+ '<div ng-repeat="item in items"><input name="item.name" ng-required/></div></div>');
+ var items = [{}, {}];
+ c.scope.$set("items", items);
+ c.scope.$eval();
+ assertEquals(3, c.scope.$get("$invalidWidgets.length"));
+
+ c.scope.$set('name', '');
+ c.scope.$eval();
+ assertEquals(3, c.scope.$get("$invalidWidgets.length"));
+
+ c.scope.$set('name', ' ');
+ c.scope.$eval();
+ assertEquals(3, c.scope.$get("$invalidWidgets.length"));
+
+ c.scope.$set('name', 'abc');
+ c.scope.$eval();
+ assertEquals(2, c.scope.$get("$invalidWidgets.length"));
+
+ items[0].name = 'abc';
+ c.scope.$eval();
+ assertEquals(1, c.scope.$get("$invalidWidgets.length"));
+
+ items[1].name = 'abc';
+ c.scope.$eval();
+ assertEquals(0, c.scope.$get("$invalidWidgets.length"));
+};
+
+BinderTest.prototype.testValidateOnlyVisibleItems = function(){
+ var c = this.compile('<div><input name="name" ng-required><input ng-show="show" name="name" ng-required></div>');
+ jqLite(document.body).append(c.node);
+ c.scope.$set("show", true);
+ c.scope.$eval();
+ assertEquals(2, c.scope.$get("$invalidWidgets.length"));
+
+ c.scope.$set("show", false);
+ c.scope.$eval();
+ assertEquals(1, c.scope.$invalidWidgets.visible());
+};
+
+BinderTest.prototype.testDeleteAttributeIfEvaluatesFalse = function() {
+ var c = this.compile('<div>' +
+ '<input name="a0" ng-bind-attr="{disabled:\'{{true}}\'}"><input name="a1" ng-bind-attr="{disabled:\'{{false}}\'}">' +
+ '<input name="b0" ng-bind-attr="{disabled:\'{{1}}\'}"><input name="b1" ng-bind-attr="{disabled:\'{{0}}\'}">' +
+ '<input name="c0" ng-bind-attr="{disabled:\'{{[0]}}\'}"><input name="c1" ng-bind-attr="{disabled:\'{{[]}}\'}"></div>');
+ c.scope.$eval();
+ function assertChild(index, disabled) {
+ var child = childNode(c.node, index);
+ assertEquals(sortedHtml(child), disabled, !!child.attr('disabled'));
+ }
+
+ assertChild(0, true);
+ assertChild(1, false);
+ assertChild(2, true);
+ assertChild(3, false);
+ assertChild(4, true);
+ assertChild(5, false);
+};
+
+BinderTest.prototype.testItShouldDisplayErrorWhenActionIsSyntacticlyIncorect = function(){
+ var c = this.compile('<div>' +
+ '<input type="button" ng-click="greeting=\'ABC\'"/>' +
+ '<input type="button" ng-click=":garbage:"/></div>');
+ var first = jqLite(c.node[0].childNodes[0]);
+ var second = jqLite(c.node[0].childNodes[1]);
+
+ first.trigger('click');
+ assertEquals("ABC", c.scope.greeting);
+
+ second.trigger('click');
+ assertTrue(second.hasClass("ng-exception"));
+};
+
+BinderTest.prototype.testItShouldSelectTheCorrectRadioBox = function() {
+ var c = this.compile('<div>' +
+ '<input type="radio" name="sex" value="female"/>' +
+ '<input type="radio" name="sex" value="male"/></div>');
+ var female = jqLite(c.node[0].childNodes[0]);
+ var male = jqLite(c.node[0].childNodes[1]);
+
+ click(female);
+ assertEquals("female", c.scope.sex);
+ assertEquals(true, female[0].checked);
+ assertEquals(false, male[0].checked);
+ assertEquals("female", female.val());
+
+ click(male);
+ assertEquals("male", c.scope.sex);
+ assertEquals(false, female[0].checked);
+ assertEquals(true, male[0].checked);
+ assertEquals("male", male.val());
+};
+
+BinderTest.prototype.testItShouldListenOnRightScope = function() {
+ var c = this.compile(
+ '<ul ng-init="counter=0; gCounter=0" ng-watch="w:counter=counter+1">' +
+ '<li ng-repeat="n in [1,2,4]" ng-watch="w:counter=counter+1;w:$root.gCounter=$root.gCounter+n"/></ul>');
+ c.scope.$eval();
+ assertEquals(0, c.scope.$get("counter"));
+ assertEquals(0, c.scope.$get("gCounter"));
+
+ c.scope.$set("w", "something");
+ c.scope.$eval();
+ assertEquals(1, c.scope.$get("counter"));
+ assertEquals(7, c.scope.$get("gCounter"));
+};
+
+BinderTest.prototype.testItShouldRepeatOnHashes = function() {
+ var x = this.compile('<ul><li ng-repeat="(k,v) in {a:0,b:1}" ng-bind=\"k + v\"></li></ul>');
+ x.scope.$eval();
+ assertEquals('<ul>' +
+ '<#comment></#comment>' +
+ '<li ng-bind=\"k + v\" ng-repeat-index="0">a0</li>' +
+ '<li ng-bind=\"k + v\" ng-repeat-index="1">b1</li>' +
+ '</ul>',
+ sortedHtml(x.node));
+};
+
+BinderTest.prototype.testItShouldFireChangeListenersBeforeUpdate = function(){
+ var x = this.compile('<div ng-bind="name"></div>');
+ x.scope.$set("name", "");
+ x.scope.$watch("watched", "name=123");
+ x.scope.$set("watched", "change");
+ x.scope.$eval();
+ assertEquals(123, x.scope.$get("name"));
+ assertEquals(
+ '<div ng-bind="name">123</div>',
+ sortedHtml(x.node));
+};
+
+BinderTest.prototype.testItShouldHandleMultilineBindings = function(){
+ var x = this.compile('<div>{{\n 1 \n + \n 2 \n}}</div>');
+ x.scope.$eval();
+ assertEquals("3", x.node.text());
+};
+
+BinderTest.prototype.testItBindHiddenInputFields = function(){
+ var x = this.compile('<input type="hidden" name="myName" value="abc" />');
+ x.scope.$eval();
+ assertEquals("abc", x.scope.$get("myName"));
+};
+
+BinderTest.prototype.XtestItShouldRenderMultiRootHtmlInBinding = function() {
+ var x = this.compile('<div>before {{a|html}}after</div>');
+ x.scope.a = "a<b>c</b>d";
+ x.scope.$eval();
+ assertEquals(
+ '<div>before <span ng-bind="a|html">a<b>c</b>d</span>after</div>',
+ sortedHtml(x.node));
+};
+
+BinderTest.prototype.testItShouldUseFormaterForText = function() {
+ var x = this.compile('<input name="a" ng-format="list" value="a,b">');
+ x.scope.$eval();
+ assertEquals(['a','b'], x.scope.$get('a'));
+ var input = x.node;
+ input[0].value = ' x,,yz';
+ input.trigger('change');
+ assertEquals(['x','yz'], x.scope.$get('a'));
+ x.scope.$set('a', [1 ,2, 3]);
+ x.scope.$eval();
+ assertEquals('1, 2, 3', input[0].value);
+};
+
diff --git a/test/BrowserSpecs.js b/test/BrowserSpecs.js
new file mode 100644
index 00000000..3ce158b4
--- /dev/null
+++ b/test/BrowserSpecs.js
@@ -0,0 +1,48 @@
+describe('browser', function(){
+
+ var browser, location;
+
+ beforeEach(function(){
+ location = {href:"http://server", hash:""};
+ browser = new Browser(location, {});
+ browser.setTimeout = noop;
+ });
+
+ it('should watch url', function(){
+ browser.delay = 1;
+ expectAsserts(2);
+ browser.watchUrl(function(url){
+ assertEquals('http://getangular.test', url);
+ });
+ browser.setTimeout = function(fn, delay){
+ assertEquals(1, delay);
+ location.href = "http://getangular.test";
+ browser.setTimeout = function(fn, delay) {};
+ fn();
+ };
+ browser.startUrlWatcher();
+ });
+
+ describe('outstading requests', function(){
+ it('should process callbacks immedietly with no outstanding requests', function(){
+ var callback = jasmine.createSpy('callback');
+ browser.notifyWhenNoOutstandingRequests(callback);
+ expect(callback).wasCalled();
+ });
+
+ it('should queue callbacks with outstanding requests', function(){
+ var callback = jasmine.createSpy('callback');
+ browser.outstandingRequests.count = 1;
+ browser.notifyWhenNoOutstandingRequests(callback);
+ expect(callback).not.wasCalled();
+
+ browser.processRequestCallbacks();
+ expect(callback).not.wasCalled();
+
+ browser.outstandingRequests.count = 0;
+ browser.processRequestCallbacks();
+ expect(callback).wasCalled();
+ });
+ });
+
+});
diff --git a/test/CompilerSpec.js b/test/CompilerSpec.js
new file mode 100644
index 00000000..da354ea5
--- /dev/null
+++ b/test/CompilerSpec.js
@@ -0,0 +1,137 @@
+describe('compiler', function(){
+ var compiler, textMarkup, directives, widgets, compile, log;
+
+ beforeEach(function(){
+ log = "";
+ directives = {
+ hello: function(expression, element){
+ log += "hello ";
+ return function() {
+ log += expression;
+ };
+ },
+
+ watch: function(expression, element){
+ return function() {
+ this.$watch(expression, function(val){
+ log += ":" + val;
+ });
+ };
+ }
+
+ };
+ textMarkup = [];
+ attrMarkup = [];
+ widgets = {};
+ compiler = new Compiler(textMarkup, attrMarkup, directives, widgets);
+ compile = function(html){
+ var e = jqLite("<div>" + html + "</div>");
+ var scope = compiler.compile(e)(e);
+ scope.$init();
+ return scope;
+ };
+ });
+
+ it('should recognize a directive', function(){
+ var e = jqLite('<div directive="expr" ignore="me"></div>');
+ directives.directive = function(expression, element){
+ log += "found";
+ expect(expression).toEqual("expr");
+ expect(element).toEqual(e);
+ return function initFn() {
+ log += ":init";
+ };
+ };
+ var template = compiler.compile(e);
+ var init = template(e).$init;
+ expect(log).toEqual("found");
+ init();
+ expect(log).toEqual("found:init");
+ });
+
+ it('should recurse to children', function(){
+ var scope = compile('<div><span hello="misko"/></div>');
+ expect(log).toEqual("hello misko");
+ });
+
+ it('should watch scope', function(){
+ var scope = compile('<span watch="name"/>');
+ expect(log).toEqual("");
+ scope.$eval();
+ scope.$set('name', 'misko');
+ scope.$eval();
+ scope.$eval();
+ scope.$set('name', 'adam');
+ scope.$eval();
+ scope.$eval();
+ expect(log).toEqual(":misko:adam");
+ });
+
+ it('should prevent descend', function(){
+ directives.stop = function(){ this.descend(false); };
+ var scope = compile('<span hello="misko" stop="true"><span hello="adam"/></span>');
+ expect(log).toEqual("hello misko");
+ });
+
+ it('should allow creation of templates', function(){
+ directives.duplicate = function(expr, element){
+ var parent = element.parent();
+ element.replaceWith(document.createComment("marker"));
+ element.removeAttr("duplicate");
+ var template = this.compile(element);
+ return function(marker) {
+ this.$onEval(function() {
+ marker.after(template(element.clone()).$element);
+ });
+ };
+ };
+ var 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>');
+ scope.$eval();
+ expect(sortedHtml(scope.$element)).toEqual('<div>before<#comment></#comment><span>x</span><span>x</span><span>x</span>after</div>');
+ });
+
+ it('should process markup before directives', function(){
+ textMarkup.push(function(text, textNode, parentNode) {
+ if (text == 'middle') {
+ expect(textNode.text()).toEqual(text);
+ parentNode.attr('hello', text);
+ textNode[0].nodeValue = 'replaced';
+ }
+ });
+ var scope = compile('before<span>middle</span>after');
+ expect(lowercase(scope.$element[0].innerHTML)).toEqual('before<span hello="middle">replaced</span>after');
+ expect(log).toEqual("hello middle");
+ });
+
+ it('should replace widgets', function(){
+ widgets['NG:BUTTON'] = function(element) {
+ element.replaceWith('<div>button</div>');
+ return function(element) {
+ log += 'init';
+ };
+ };
+ var scope = compile('<ng:button>push me</ng:button>');
+ expect(lowercase(scope.$element[0].innerHTML)).toEqual('<div>button</div>');
+ expect(log).toEqual('init');
+ });
+
+ it('should use the replaced element after calling widget', function(){
+ widgets['H1'] = function(element) {
+ var span = angular.element('<span>{{1+2}}</span>');
+ element.replaceWith(span);
+ this.descend(true);
+ this.directives(true);
+ return noop;
+ };
+ textMarkup.push(function(text, textNode, parent){
+ if (text == '{{1+2}}')
+ parent.text('3');
+ });
+ var scope = compile('<div><h1>ignore me</h1></div>');
+ expect(scope.$element.text()).toEqual('3');
+ });
+
+});
diff --git a/test/ConsoleTest.js b/test/ConsoleTest.js
new file mode 100644
index 00000000..3e09267b
--- /dev/null
+++ b/test/ConsoleTest.js
@@ -0,0 +1,12 @@
+ConsoleTest = TestCase('ConsoleTest');
+
+ConsoleTest.prototype.XtestConsoleWrite = function(){
+ var consoleNode = jqLite("<div></div>")[0];
+ consoleLog("error", ["Hello", "world"]);
+ assertEquals(jqLite(consoleNode)[0].nodeName, 'DIV');
+ assertEquals(jqLite(consoleNode).text(), 'Hello world');
+ assertEquals(jqLite(consoleNode.childNodes[0])[0].className, 'error');
+ consoleLog("error",["Bye"]);
+ assertEquals(jqLite(consoleNode).text(), 'Hello worldBye');
+ consoleNode = null;
+};
diff --git a/test/FiltersTest.js b/test/FiltersTest.js
new file mode 100644
index 00000000..f839bb51
--- /dev/null
+++ b/test/FiltersTest.js
@@ -0,0 +1,143 @@
+FiltersTest = TestCase('FiltersTest');
+
+FiltersTest.prototype.testCurrency = function(){
+ var html = jqLite('<span/>');
+ var context = {$element:html};
+ var currency = bind(context, angular.filter.currency);
+
+ assertEquals(currency(0), '$0.00');
+ assertEquals(html.hasClass('ng-format-negative'), false);
+ assertEquals(currency(-999), '$-999.00');
+ assertEquals(html.hasClass('ng-format-negative'), true);
+ assertEquals(currency(1234.5678), '$1,234.57');
+ assertEquals(html.hasClass('ng-format-negative'), false);
+};
+
+FiltersTest.prototype.testFilterThisIsContext = function(){
+ expectAsserts(1);
+ var scope = createScope();
+ scope.name = 'misko';
+ angular.filter.testFn = function () {
+ assertEquals('scope not equal', 'misko', this.name);
+ };
+ scope.$eval("0|testFn");
+ delete angular.filter['testFn'];
+};
+
+FiltersTest.prototype.testNumberFormat = function(){
+ var context = {jqElement:jqLite('<span/>')};
+ var number = bind(context, angular.filter.number);
+
+ assertEquals('0', number(0, 0));
+ assertEquals('0.00', number(0));
+ assertEquals('-999.00', number(-999));
+ assertEquals('1,234.57', number(1234.5678));
+ assertEquals('', number(Number.NaN));
+ assertEquals('1,234.57', number("1234.5678"));
+ assertEquals("", number(1/0));
+};
+
+FiltersTest.prototype.testJson = function () {
+ assertEquals(toJson({a:"b"}, true), angular.filter.json.call({$element:jqLite('<div></div>')}, {a:"b"}));
+};
+
+FiltersTest.prototype.testPackageTracking = function () {
+ var assert = function(title, trackingNo) {
+ var val = angular.filter.trackPackage(trackingNo, title);
+ assertNotNull("Did Not Match: " + trackingNo, val);
+ assertEquals(title + ": " + trim(trackingNo), val.text());
+ assertNotNull(val.attr('href'));
+ };
+ assert('UPS', ' 1Z 999 999 99 9999 999 9 ');
+ assert('UPS', '1ZW5w5220379084747');
+
+ assert('FedEx', '418822131061812');
+ assert('FedEx', '9612019 5935 3267 2473 738');
+ assert('FedEx', '9612019593532672473738');
+ assert('FedEx', '235354667129449');
+ assert('FedEx', '915368880571');
+ assert('FedEx', '901712142390');
+ assert('FedEx', '297391510063413');
+
+ assert('USPS', '9101 8052 1390 7402 4335 49');
+ assert('USPS', '9101010521297963339560');
+ assert('USPS', '9102901001301038667029');
+ assert('USPS', '910 27974 4490 3000 8916 56');
+ assert('USPS', '9102801438635051633253');
+};
+
+FiltersTest.prototype.testLink = function() {
+ var assert = function(text, url, obj){
+ var val = angular.filter.link(obj);
+ assertEquals('<a href="' + url + '">' + text + '</a>', sortedHtml(val));
+ };
+ assert("url", "url", "url");
+ assert("hello", "url", {text:"hello", url:"url"});
+ assert("a@b.com", "mailto:a@b.com", "a@b.com");
+};
+
+FiltersTest.prototype.testImage = function(){
+ assertEquals(null, angular.filter.image());
+ assertEquals(null, angular.filter.image({}));
+ assertEquals(null, angular.filter.image(""));
+ assertEquals('http://localhost/abc', angular.filter.image({url:"http://localhost/abc"}).attr('src'));
+};
+
+FiltersTest.prototype.testQRcode = function() {
+ assertEquals(
+ 'http://chart.apis.google.com/chart?chl=Hello%20world&chs=200x200&cht=qr',
+ angular.filter.qrcode('Hello world').attr('src'));
+};
+
+FiltersTest.prototype.testLowercase = function() {
+ assertEquals('abc', angular.filter.lowercase('AbC'));
+ assertEquals(null, angular.filter.lowercase(null));
+};
+
+FiltersTest.prototype.testUppercase = function() {
+ assertEquals('ABC', angular.filter.uppercase('AbC'));
+ assertEquals(null, angular.filter.uppercase(null));
+};
+
+FiltersTest.prototype.testLineCount = function() {
+ assertEquals(1, angular.filter.linecount(null));
+ assertEquals(1, angular.filter.linecount(''));
+ assertEquals(1, angular.filter.linecount('a'));
+ assertEquals(2, angular.filter.linecount('a\nb'));
+ assertEquals(3, angular.filter.linecount('a\nb\nc'));
+};
+
+FiltersTest.prototype.testIf = function() {
+ assertEquals('A', angular.filter['if']('A', true));
+ assertEquals(undefined, angular.filter['if']('A', false));
+};
+
+FiltersTest.prototype.testUnless = function() {
+ assertEquals('A', angular.filter.unless('A', false));
+ assertEquals(undefined, angular.filter.unless('A', true));
+};
+
+FiltersTest.prototype.testGoogleChartApiEncode = function() {
+ assertEquals(
+ 'http://chart.apis.google.com/chart?chl=Hello world&chs=200x200&cht=qr',
+ angular.filter.googleChartApi.encode({cht:"qr", chl:"Hello world"}).attr('src'));
+};
+
+FiltersTest.prototype.testHtml = function() {
+ var html = angular.filter.html("a<b>c</b>d");
+ expect(html instanceof HTML).toBeTruthy();
+ expect(html.html).toEqual("a<b>c</b>d");
+};
+
+FiltersTest.prototype.testLinky = function() {
+ var linky = angular.filter.linky;
+ assertEquals(
+ '<a href="http://ab/">http://ab/</a> ' +
+ '(<a href="http://a/">http://a/</a>) ' +
+ '&lt;<a href="http://a/">http://a/</a>&gt; ' +
+ '<a href="http://1.2/v:~-123">http://1.2/v:~-123</a>. c',
+ linky("http://ab/ (http://a/) <http://a/> http://1.2/v:~-123. c").html);
+ assertEquals(undefined, linky(undefined));
+};
+
+
diff --git a/test/FormattersTest.js b/test/FormattersTest.js
new file mode 100644
index 00000000..b520faf9
--- /dev/null
+++ b/test/FormattersTest.js
@@ -0,0 +1,37 @@
+TestCase("formatterTest", {
+ testNoop: function(){
+ assertEquals("abc", angular.formatter.noop.format("abc"));
+ assertEquals("xyz", angular.formatter.noop.parse("xyz"));
+ assertEquals(null, angular.formatter.noop.parse(null));
+ },
+
+ testList: function() {
+ assertEquals('a, b', angular.formatter.list.format(['a', 'b']));
+ assertEquals('', angular.formatter.list.format([]));
+ assertEquals(['abc', 'c'], angular.formatter.list.parse(" , abc , c ,,"));
+ assertEquals([], angular.formatter.list.parse(""));
+ assertEquals([], angular.formatter.list.parse(null));
+ },
+
+ testBoolean: function() {
+ assertEquals('true', angular.formatter['boolean'].format(true));
+ assertEquals('false', angular.formatter['boolean'].format(false));
+ assertEquals(true, angular.formatter['boolean'].parse("true"));
+ assertEquals(false, angular.formatter['boolean'].parse(""));
+ assertEquals(false, angular.formatter['boolean'].parse("false"));
+ assertEquals(null, angular.formatter['boolean'].parse(null));
+ },
+
+ testNumber: function() {
+ assertEquals('1', angular.formatter.number.format(1));
+ assertEquals(1, angular.formatter.number.format('1'));
+ },
+
+ testTrim: function() {
+ assertEquals('', angular.formatter.trim.format(null));
+ assertEquals('', angular.formatter.trim.format(""));
+ assertEquals('a', angular.formatter.trim.format(" a "));
+ assertEquals('a', angular.formatter.trim.parse(' a '));
+ }
+
+});
diff --git a/test/JsonTest.js b/test/JsonTest.js
new file mode 100644
index 00000000..1ed56da8
--- /dev/null
+++ b/test/JsonTest.js
@@ -0,0 +1,84 @@
+JsonTest = TestCase("JsonTest");
+
+JsonTest.prototype.testPrimitives = function () {
+ assertEquals("null", toJson(0/0));
+ assertEquals("null", toJson(null));
+ assertEquals("true", toJson(true));
+ assertEquals("false", toJson(false));
+ assertEquals("123.45", toJson(123.45));
+ assertEquals('"abc"', toJson("abc"));
+ assertEquals('"a \\t \\n \\r b \\\\"', toJson("a \t \n \r b \\"));
+};
+
+JsonTest.prototype.testEscaping = function () {
+ assertEquals("\"7\\\\\\\"7\"", toJson("7\\\"7"));
+};
+
+JsonTest.prototype.testObjects = function () {
+ assertEquals('{"a":1,"b":2}', toJson({a:1,b:2}));
+ assertEquals('{"a":{"b":2}}', toJson({a:{b:2}}));
+ assertEquals('{"a":{"b":{"c":0}}}', toJson({a:{b:{c:0}}}));
+ assertEquals('{"a":{"b":null}}', toJson({a:{b:0/0}}));
+};
+
+JsonTest.prototype.testObjectPretty = function () {
+ assertEquals('{\n "a":1,\n "b":2}', toJson({a:1,b:2}, true));
+ assertEquals('{\n "a":{\n "b":2}}', toJson({a:{b:2}}, true));
+};
+
+JsonTest.prototype.testArray = function () {
+ assertEquals('[]', toJson([]));
+ assertEquals('[1,"b"]', toJson([1,"b"]));
+};
+
+JsonTest.prototype.testIgnoreFunctions = function () {
+ assertEquals('[null,1]', toJson([function(){},1]));
+ assertEquals('{}', toJson({a:function(){}}));
+};
+
+JsonTest.prototype.testParseNull = function () {
+ assertNull(fromJson("null"));
+};
+
+JsonTest.prototype.testParseBoolean = function () {
+ assertTrue(fromJson("true"));
+ assertFalse(fromJson("false"));
+};
+
+JsonTest.prototype.test$$isIgnored = function () {
+ assertEquals("{}", toJson({$$:0}));
+};
+
+JsonTest.prototype.testArrayWithEmptyItems = function () {
+ var a = [];
+ a[1] = "X";
+ assertEquals('[null,"X"]', toJson(a));
+};
+
+JsonTest.prototype.testItShouldEscapeUnicode = function () {
+ assertEquals(1, "\u00a0".length);
+ assertEquals(8, toJson("\u00a0").length);
+ assertEquals(1, fromJson(toJson("\u00a0")).length);
+};
+
+JsonTest.prototype.testItShouldUTCDates = function() {
+ var date = angular.String.toDate("2009-10-09T01:02:03Z");
+ assertEquals('"2009-10-09T01:02:03Z"', toJson(date));
+ assertEquals(date.getTime(),
+ fromJson('"2009-10-09T01:02:03Z"').getTime());
+};
+
+JsonTest.prototype.testItShouldPreventRecursion = function () {
+ var obj = {a:'b'};
+ obj.recursion = obj;
+ assertEquals('{"a":"b","recursion":RECURSION}', angular.toJson(obj));
+};
+
+JsonTest.prototype.testItShouldSerializeSameObjectsMultipleTimes = function () {
+ var obj = {a:'b'};
+ assertEquals('{"A":{"a":"b"},"B":{"a":"b"}}', angular.toJson({A:obj, B:obj}));
+};
+
+JsonTest.prototype.testItShouldNotSerializeUndefinedValues = function () {
+ assertEquals('{}', angular.toJson({A:undefined}));
+};
diff --git a/test/ParserTest.js b/test/ParserTest.js
new file mode 100644
index 00000000..7ba65f18
--- /dev/null
+++ b/test/ParserTest.js
@@ -0,0 +1,465 @@
+LexerTest = TestCase('LexerTest');
+
+LexerTest.prototype.testTokenizeAString = function(){
+ var lexer = new Lexer("a.bc[22]+1.3|f:'a\\\'c':\"d\\\"e\"");
+ var tokens = lexer.parse();
+ var i = 0;
+ assertEquals(tokens[i].index, 0);
+ assertEquals(tokens[i].text, 'a.bc');
+
+ i++;
+ assertEquals(tokens[i].index, 4);
+ assertEquals(tokens[i].text, '[');
+
+ i++;
+ assertEquals(tokens[i].index, 5);
+ assertEquals(tokens[i].text, 22);
+
+ i++;
+ assertEquals(tokens[i].index, 7);
+ assertEquals(tokens[i].text, ']');
+
+ i++;
+ assertEquals(tokens[i].index, 8);
+ assertEquals(tokens[i].text, '+');
+
+ i++;
+ assertEquals(tokens[i].index, 9);
+ assertEquals(tokens[i].text, 1.3);
+
+ i++;
+ assertEquals(tokens[i].index, 12);
+ assertEquals(tokens[i].text, '|');
+
+ i++;
+ assertEquals(tokens[i].index, 13);
+ assertEquals(tokens[i].text, 'f');
+
+ i++;
+ assertEquals(tokens[i].index, 14);
+ assertEquals(tokens[i].text, ':');
+
+ i++;
+ assertEquals(tokens[i].index, 15);
+ assertEquals(tokens[i].string, "a'c");
+
+ i++;
+ assertEquals(tokens[i].index, 21);
+ assertEquals(tokens[i].text, ':');
+
+ i++;
+ assertEquals(tokens[i].index, 22);
+ assertEquals(tokens[i].string, 'd"e');
+};
+
+LexerTest.prototype.testTokenizeUndefined = function(){
+ var lexer = new Lexer("undefined");
+ var tokens = lexer.parse();
+ var i = 0;
+ assertEquals(tokens[i].index, 0);
+ assertEquals(tokens[i].text, 'undefined');
+ assertEquals(undefined, tokens[i].fn());
+};
+
+
+
+LexerTest.prototype.testTokenizeRegExp = function(){
+ var lexer = new Lexer("/r 1/");
+ var tokens = lexer.parse();
+ var i = 0;
+ assertEquals(tokens[i].index, 0);
+ assertEquals(tokens[i].text, 'r 1');
+ assertEquals("r 1".match(tokens[i].fn())[0], 'r 1');
+};
+
+LexerTest.prototype.testQuotedString = function(){
+ var str = "['\\'', \"\\\"\"]";
+ var lexer = new Lexer(str);
+ var tokens = lexer.parse();
+
+ assertEquals(1, tokens[1].index);
+ assertEquals("'", tokens[1].string);
+
+ assertEquals(7, tokens[3].index);
+ assertEquals('"', tokens[3].string);
+
+};
+
+LexerTest.prototype.testQuotedStringEscape = function(){
+ var str = '"\\"\\n\\f\\r\\t\\v\\u00A0"';
+ var lexer = new Lexer(str);
+ var tokens = lexer.parse();
+
+ assertEquals('"\n\f\r\t\v\u00A0', tokens[0].string);
+};
+
+LexerTest.prototype.testTokenizeUnicode = function(){
+ var lexer = new Lexer('"\\u00A0"');
+ var tokens = lexer.parse();
+ assertEquals(1, tokens.length);
+ assertEquals('\u00a0', tokens[0].string);
+};
+
+LexerTest.prototype.testTokenizeRegExpWithOptions = function(){
+ var lexer = new Lexer("/r/g");
+ var tokens = lexer.parse();
+ var i = 0;
+ assertEquals(tokens[i].index, 0);
+ assertEquals(tokens[i].text, 'r');
+ assertEquals(tokens[i].flags, 'g');
+ assertEquals("rr".match(tokens[i].fn()).length, 2);
+};
+
+LexerTest.prototype.testTokenizeRegExpWithEscape = function(){
+ var lexer = new Lexer("/\\/\\d/");
+ var tokens = lexer.parse();
+ var i = 0;
+ assertEquals(tokens[i].index, 0);
+ assertEquals(tokens[i].text, '\\/\\d');
+ assertEquals("/1".match(tokens[i].fn())[0], '/1');
+};
+
+LexerTest.prototype.testIgnoreWhitespace = function(){
+ var lexer = new Lexer("a \t \n \r b");
+ var tokens = lexer.parse();
+ assertEquals(tokens[0].text, 'a');
+ assertEquals(tokens[1].text, 'b');
+};
+
+LexerTest.prototype.testRelation = function(){
+ var lexer = new Lexer("! == != < > <= >=");
+ var tokens = lexer.parse();
+ assertEquals(tokens[0].text, '!');
+ assertEquals(tokens[1].text, '==');
+ assertEquals(tokens[2].text, '!=');
+ assertEquals(tokens[3].text, '<');
+ assertEquals(tokens[4].text, '>');
+ assertEquals(tokens[5].text, '<=');
+ assertEquals(tokens[6].text, '>=');
+};
+
+LexerTest.prototype.testStatements = function(){
+ var lexer = new Lexer("a;b;");
+ var tokens = lexer.parse();
+ assertEquals(tokens[0].text, 'a');
+ assertEquals(tokens[1].text, ';');
+ assertEquals(tokens[2].text, 'b');
+ assertEquals(tokens[3].text, ';');
+};
+
+ParserTest = TestCase('ParserTest');
+
+ParserTest.prototype.testExpressions = function(){
+ var scope = createScope();
+ assertEquals(scope.$eval("-1"), -1);
+ assertEquals(scope.$eval("1 + 2.5"), 3.5);
+ assertEquals(scope.$eval("1 + -2.5"), -1.5);
+ assertEquals(scope.$eval("1+2*3/4"), 1+2*3/4);
+ assertEquals(scope.$eval("0--1+1.5"), 0- -1 + 1.5);
+ assertEquals(scope.$eval("-0--1++2*-3/-4"), -0- -1+ +2*-3/-4);
+ assertEquals(scope.$eval("1/2*3"), 1/2*3);
+};
+
+ParserTest.prototype.testComparison = function(){
+ var scope = createScope();
+ assertEquals(scope.$eval("false"), false);
+ assertEquals(scope.$eval("!true"), false);
+ assertEquals(scope.$eval("1==1"), true);
+ assertEquals(scope.$eval("1!=2"), true);
+ assertEquals(scope.$eval("1<2"), true);
+ assertEquals(scope.$eval("1<=1"), true);
+ assertEquals(scope.$eval("1>2"), 1>2);
+ assertEquals(scope.$eval("2>=1"), 2>=1);
+
+ assertEquals(true === 2<3, scope.$eval("true==2<3"));
+
+};
+
+ParserTest.prototype.testLogical = function(){
+ var scope = createScope();
+ assertEquals(scope.$eval("0&&2"), 0&&2);
+ assertEquals(scope.$eval("0||2"), 0||2);
+ assertEquals(scope.$eval("0||1&&2"), 0||1&&2);
+};
+
+ParserTest.prototype.testString = function(){
+ var scope = createScope();
+ assertEquals(scope.$eval("'a' + 'b c'"), "ab c");
+};
+
+ParserTest.prototype.testFilters = function(){
+ angular.filter.substring = function(input, start, end) {
+ return input.substring(start, end);
+ };
+
+ angular.filter.upper = {_case:function(input) {
+ return input.toUpperCase();
+ }};
+ var scope = createScope();
+ try {
+ scope.$eval("1|nonExistant");
+ fail();
+ } catch (e) {
+ assertEquals(e, "Function 'nonExistant' at column '3' in '1|nonExistant' is not defined.");
+ }
+ scope.$set('offset', 3);
+ assertEquals(scope.$eval("'abcd'|upper._case"), "ABCD");
+ assertEquals(scope.$eval("'abcd'|substring:1:offset"), "bc");
+ assertEquals(scope.$eval("'abcd'|substring:1:3|upper._case"), "BC");
+};
+
+ParserTest.prototype.testScopeAccess = function(){
+ var scope = createScope();
+ scope.$set('a', 123);
+ scope.$set('b.c', 456);
+ assertEquals(scope.$eval("a", scope), 123);
+ assertEquals(scope.$eval("b.c", scope), 456);
+ assertEquals(scope.$eval("x.y.z", scope), undefined);
+};
+
+ParserTest.prototype.testGrouping = function(){
+ var scope = createScope();
+ assertEquals(scope.$eval("(1+2)*3"), (1+2)*3);
+};
+
+ParserTest.prototype.testAssignments = function(){
+ var scope = createScope();
+ assertEquals(scope.$eval("a=12"), 12);
+ assertEquals(scope.$get("a"), 12);
+
+ scope = createScope();
+ assertEquals(scope.$eval("x.y.z=123;"), 123);
+ assertEquals(scope.$get("x.y.z"), 123);
+
+ assertEquals(234, scope.$eval("a=123; b=234"));
+ assertEquals(123, scope.$get("a"));
+ assertEquals(234, scope.$get("b"));
+};
+
+ParserTest.prototype.testFunctionCallsNoArgs = function(){
+ var scope = createScope();
+ scope.$set('const', function(a,b){return 123;});
+ assertEquals(scope.$eval("const()"), 123);
+};
+
+ParserTest.prototype.testFunctionCalls = function(){
+ var scope = createScope();
+ scope.$set('add', function(a,b){
+ return a+b;
+ });
+ assertEquals(3, scope.$eval("add(1,2)"));
+};
+
+ParserTest.prototype.testCalculationBug = function(){
+ var scope = createScope();
+ scope.$set('taxRate', 8);
+ scope.$set('subTotal', 100);
+ assertEquals(scope.$eval("taxRate / 100 * subTotal"), 8);
+ assertEquals(scope.$eval("subTotal * taxRate / 100"), 8);
+};
+
+ParserTest.prototype.testArray = function(){
+ var scope = createScope();
+ assertEquals(scope.$eval("[]").length, 0);
+ assertEquals(scope.$eval("[1, 2]").length, 2);
+ assertEquals(scope.$eval("[1, 2]")[0], 1);
+ assertEquals(scope.$eval("[1, 2]")[1], 2);
+};
+
+ParserTest.prototype.testArrayAccess = function(){
+ var scope = createScope();
+ assertEquals(scope.$eval("[1][0]"), 1);
+ assertEquals(scope.$eval("[[1]][0][0]"), 1);
+ assertEquals(scope.$eval("[].length"), 0);
+ assertEquals(scope.$eval("[1, 2].length"), 2);
+};
+
+ParserTest.prototype.testObject = function(){
+ var scope = createScope();
+ assertEquals(toJson(scope.$eval("{}")), "{}");
+ assertEquals(toJson(scope.$eval("{a:'b'}")), '{"a":"b"}');
+ assertEquals(toJson(scope.$eval("{'a':'b'}")), '{"a":"b"}');
+ assertEquals(toJson(scope.$eval("{\"a\":'b'}")), '{"a":"b"}');
+};
+
+ParserTest.prototype.testObjectAccess = function(){
+ var scope = createScope();
+ assertEquals("WC", scope.$eval("{false:'WC', true:'CC'}[false]"));
+};
+
+ParserTest.prototype.testJSON = function(){
+ var scope = createScope();
+ assertEquals(toJson(scope.$eval("[{}]")), "[{}]");
+ assertEquals(toJson(scope.$eval("[{a:[]}, {b:1}]")), '[{"a":[]},{"b":1}]');
+};
+
+ParserTest.prototype.testMultippleStatements = function(){
+ var scope = createScope();
+ assertEquals(scope.$eval("a=1;b=3;a+b"), 4);
+ assertEquals(scope.$eval(";;1;;"), 1);
+};
+
+ParserTest.prototype.testParseThrow = function(){
+ expectAsserts(1);
+ var scope = createScope();
+ scope.$set('e', 'abc');
+ try {
+ scope.$eval("throw e");
+ } catch(e) {
+ assertEquals(e, 'abc');
+ }
+};
+
+ParserTest.prototype.testMethodsGetDispatchedWithCorrectThis = function(){
+ var scope = createScope();
+ var C = function (){
+ this.a=123;
+ };
+ C.prototype.getA = function(){
+ return this.a;
+ };
+
+ scope.$set("obj", new C());
+ assertEquals(123, scope.$eval("obj.getA()"));
+};
+ParserTest.prototype.testMethodsArgumentsGetCorrectThis = function(){
+ var scope = createScope();
+ var C = function (){
+ this.a=123;
+ };
+ C.prototype.sum = function(value){
+ return this.a + value;
+ };
+ C.prototype.getA = function(){
+ return this.a;
+ };
+
+ scope.$set("obj", new C());
+ assertEquals(246, scope.$eval("obj.sum(obj.getA())"));
+};
+
+ParserTest.prototype.testObjectPointsToScopeValue = function(){
+ var scope = createScope();
+ scope.$set('a', "abc");
+ assertEquals("abc", scope.$eval("{a:a}").a);
+};
+
+ParserTest.prototype.testFieldAccess = function(){
+ var scope = createScope();
+ var fn = function(){
+ return {name:'misko'};
+ };
+ scope.$set('a', fn);
+ assertEquals("misko", scope.$eval("a().name"));
+};
+
+ParserTest.prototype.testArrayIndexBug = function () {
+ var scope = createScope();
+ scope.$set('items', [{}, {name:'misko'}]);
+
+ assertEquals("misko", scope.$eval('items[1].name'));
+};
+
+ParserTest.prototype.testArrayAssignment = function () {
+ var scope = createScope();
+ scope.$set('items', []);
+
+ assertEquals("abc", scope.$eval('items[1] = "abc"'));
+ assertEquals("abc", scope.$eval('items[1]'));
+// Dont know how to make this work....
+// assertEquals("moby", scope.$eval('books[1] = "moby"'));
+// assertEquals("moby", scope.$eval('books[1]'));
+};
+
+ParserTest.prototype.testFiltersCanBeGrouped = function () {
+ var scope = createScope({name:'MISKO'});
+ assertEquals('misko', scope.$eval('n = (name|lowercase)'));
+ assertEquals('misko', scope.$eval('n'));
+};
+
+ParserTest.prototype.testFiltersCanBeGrouped = function () {
+ var scope = createScope({name:'MISKO'});
+ assertEquals('misko', scope.$eval('n = (name|lowercase)'));
+ assertEquals('misko', scope.$eval('n'));
+};
+
+ParserTest.prototype.testRemainder = function () {
+ var scope = createScope();
+ assertEquals(1, scope.$eval('1%2'));
+};
+
+ParserTest.prototype.testSumOfUndefinedIsNotUndefined = function () {
+ var scope = createScope();
+ assertEquals(1, scope.$eval('1+undefined'));
+ assertEquals(1, scope.$eval('undefined+1'));
+};
+
+ParserTest.prototype.testMissingThrowsError = function() {
+ var scope = createScope();
+ try {
+ scope.$eval('[].count(');
+ fail();
+ } catch (e) {
+ assertEquals('Unexpected end of expression: [].count(', e);
+ }
+};
+
+ParserTest.prototype.testItShouldCreateClosureFunctionWithNoArguments = function () {
+ var scope = createScope();
+ var fn = scope.$eval("{:value}");
+ scope.$set("value", 1);
+ assertEquals(1, fn());
+ scope.$set("value", 2);
+ assertEquals(2, fn());
+ fn = scope.$eval("{():value}");
+ assertEquals(2, fn());
+};
+
+ParserTest.prototype.testItShouldCreateClosureFunctionWithArguments = function () {
+ var scope = createScope();
+ scope.$set("value", 1);
+ var fn = scope.$eval("{(a):value+a}");
+ assertEquals(11, fn(10));
+ scope.$set("value", 2);
+ assertEquals(12, fn(10));
+ fn = scope.$eval("{(a,b):value+a+b}");
+ assertEquals(112, fn(10, 100));
+};
+
+ParserTest.prototype.testItShouldHaveDefaultArugument = function(){
+ var scope = createScope();
+ var fn = scope.$eval("{:$*2}");
+ assertEquals(4, fn(2));
+};
+
+ParserTest.prototype.testDoubleNegationBug = function (){
+ var scope = createScope();
+ assertEquals(true, scope.$eval('true'));
+ assertEquals(false, scope.$eval('!true'));
+ assertEquals(true, scope.$eval('!!true'));
+ assertEquals('a', scope.$eval('{true:"a", false:"b"}[!!true]'));
+};
+
+ParserTest.prototype.testNegationBug = function () {
+ var scope = createScope();
+ assertEquals(!false || true, scope.$eval("!false || true"));
+ assertEquals(!11 == 10, scope.$eval("!11 == 10"));
+ assertEquals(12/6/2, scope.$eval("12/6/2"));
+};
+
+ParserTest.prototype.testBugStringConfusesParser = function() {
+ var scope = createScope();
+ assertEquals('!', scope.$eval('suffix = "!"'));
+};
+
+ParserTest.prototype.testParsingBug = function () {
+ var scope = createScope();
+ assertEquals({a: "-"}, scope.$eval("{a:'-'}"));
+};
+
+ParserTest.prototype.testUndefined = function () {
+ var scope = createScope();
+ assertEquals(undefined, scope.$eval("undefined"));
+ assertEquals(undefined, scope.$eval("a=undefined"));
+ assertEquals(undefined, scope.$get("a"));
+};
diff --git a/test/ResourceSpec.js b/test/ResourceSpec.js
new file mode 100644
index 00000000..d11c3e08
--- /dev/null
+++ b/test/ResourceSpec.js
@@ -0,0 +1,159 @@
+describe("resource", function() {
+ var xhr, resource, CreditCard, callback;
+
+ beforeEach(function(){
+ var browser = new MockBrowser();
+ xhr = browser.xhr;
+ resource = new ResourceFactory(xhr);
+ CreditCard = resource.route('/CreditCard/:id:verb', {id:'@id.key'}, {
+ charge:{
+ method:'POST',
+ params:{verb:'!charge'}
+ }
+ });
+ callback = jasmine.createSpy();
+ });
+
+ it("should build resource", function(){
+ expect(typeof CreditCard).toBe('function');
+ expect(typeof CreditCard.get).toBe('function');
+ expect(typeof CreditCard.save).toBe('function');
+ expect(typeof CreditCard.remove).toBe('function');
+ expect(typeof CreditCard['delete']).toBe('function');
+ expect(typeof CreditCard.query).toBe('function');
+ });
+
+ it('should default to empty parameters', function(){
+ xhr.expectGET('URL').respond({});
+ resource.route('URL').query();
+ });
+
+ it("should build resource with default param", function(){
+ xhr.expectGET('/Order/123/Line/456.visa?minimum=0.05').respond({id:'abc'});
+ xhr.expectGET('/Order/123/Line/456.visa?minimum=0.05').respond({id:'ddd'});
+ var LineItem = resource.route('/Order/:orderId/Line/:id:verb', {orderId: '123', id: '@id.key', verb:'.visa', minimum:0.05});
+ var item = LineItem.get({id:456});
+ xhr.flush();
+ nakedExpect(item).toEqual({id:'abc'});
+
+ item = LineItem.get({id:456});
+ xhr.flush();
+ nakedExpect(item).toEqual({id:'abc'});
+
+ });
+
+ it("should create resource", function(){
+ xhr.expectPOST('/CreditCard', {name:'misko'}).respond({id:123, name:'misko'});
+
+ var cc = CreditCard.save({name:'misko'}, callback);
+ nakedExpect(cc).toEqual({name:'misko'});
+ expect(callback).wasNotCalled();
+ xhr.flush();
+ nakedExpect(cc).toEqual({id:123, name:'misko'});
+ expect(callback).wasCalledWith(cc);
+ });
+
+ it("should read resource", function(){
+ xhr.expectGET("/CreditCard/123").respond({id:123, number:'9876'});
+ var cc = CreditCard.get({id:123}, callback);
+ expect(cc instanceof CreditCard).toBeTruthy();
+ nakedExpect(cc).toEqual({});
+ expect(callback).wasNotCalled();
+ xhr.flush();
+ nakedExpect(cc).toEqual({id:123, number:'9876'});
+ expect(callback).wasCalledWith(cc);
+ });
+
+ it("should update resource", function(){
+ xhr.expectPOST('/CreditCard/123', {id:{key:123}, name:'misko'}).respond({id:{key:123}, name:'rama'});
+
+ var cc = CreditCard.save({id:{key:123}, name:'misko'}, callback);
+ nakedExpect(cc).toEqual({id:{key:123}, name:'misko'});
+ expect(callback).wasNotCalled();
+ xhr.flush();
+ });
+
+ it("should query resource", function(){
+ xhr.expectGET("/CreditCard?key=value").respond([{id:1}, {id:2}]);
+
+ var ccs = CreditCard.query({key:'value'}, callback);
+ expect(ccs).toEqual([]);
+ expect(callback).wasNotCalled();
+ xhr.flush();
+ nakedExpect(ccs).toEqual([{id:1}, {id:2}]);
+ expect(callback).wasCalledWith(ccs);
+ });
+
+ it("should have all arguments optional", function(){
+ xhr.expectGET('/CreditCard').respond([{id:1}]);
+ var log = '';
+ var ccs = CreditCard.query(function(){ log += 'cb;'; });
+ xhr.flush();
+ nakedExpect(ccs).toEqual([{id:1}]);
+ expect(log).toEqual('cb;');
+ });
+
+ it('should delete resource', function(){
+ xhr.expectDELETE("/CreditCard/123").respond({});
+
+ CreditCard.remove({id:123}, callback);
+ expect(callback).wasNotCalled();
+ xhr.flush();
+ nakedExpect(callback.mostRecentCall.args).toEqual([{}]);
+ });
+
+ it('should post charge verb', function(){
+ xhr.expectPOST('/CreditCard/123!charge?amount=10', {auth:'abc'}).respond({success:'ok'});
+
+ CreditCard.charge({id:123, amount:10},{auth:'abc'}, callback);
+ });
+
+ it('should create on save', function(){
+ xhr.expectPOST('/CreditCard', {name:'misko'}).respond({id:123});
+ var cc = new CreditCard();
+ expect(cc.$get).not.toBeDefined();
+ expect(cc.$query).not.toBeDefined();
+ expect(cc.$remove).toBeDefined();
+ expect(cc.$save).toBeDefined();
+
+ cc.name = 'misko';
+ cc.$save(callback);
+ nakedExpect(cc).toEqual({name:'misko'});
+ xhr.flush();
+ nakedExpect(cc).toEqual({id:123});
+ expect(callback).wasCalledWith(cc);
+ });
+
+ it('should bind default parameters', function(){
+ xhr.expectGET('/CreditCard/123.visa?minimum=0.05').respond({id:123});
+ var Visa = CreditCard.bind({verb:'.visa', minimum:0.05});
+ var visa = Visa.get({id:123});
+ xhr.flush();
+ nakedExpect(visa).toEqual({id:123});
+ });
+
+ it('should excersize full stack', function(){
+ var scope = angular.compile('<div></div>');
+ var Person = scope.$resource('/Person/:id');
+ scope.$browser.xhr.expectGET('/Person/123').respond('\n{\nname:\n"misko"\n}\n');
+ var person = Person.get({id:123});
+ scope.$browser.xhr.flush();
+ expect(person.name).toEqual('misko');
+ });
+
+ describe('failure mode', function(){
+ it('should report error when non 200', function(){
+ xhr.expectGET('/CreditCard/123').respond(500, "Server Error");
+ var cc = CreditCard.get({id:123});
+ try {
+ xhr.flush();
+ fail('expected exception, non thrown');
+ } catch (e) {
+ expect(e.status).toEqual(500);
+ expect(e.response).toEqual('Server Error');
+ expect(e.message).toEqual('500: Server Error');
+ }
+ });
+ });
+
+});
diff --git a/test/ScenarioSpec.js b/test/ScenarioSpec.js
new file mode 100644
index 00000000..9afe8e95
--- /dev/null
+++ b/test/ScenarioSpec.js
@@ -0,0 +1,51 @@
+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);
+ });
+
+ 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');
+ });
+
+ 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("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');
+ });
+
+ 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("ScenarioSpec: configuration", function(){
+ it("should take location object", function(){
+ var url = "http://server/#?book=moby";
+ var scope = compile("<div>{{$location}}</div>");
+ var $location = scope.$get('$location');
+ expect($location.hashSearch.book).toBeUndefined();
+ scope.$browser.setUrl(url);
+ scope.$browser.fireUrlWatchers();
+ expect($location.hashSearch.book).toEqual('moby');
+ });
+});
diff --git a/test/ScopeSpec.js b/test/ScopeSpec.js
new file mode 100644
index 00000000..d93400e5
--- /dev/null
+++ b/test/ScopeSpec.js
@@ -0,0 +1,181 @@
+describe('scope/model', function(){
+
+ it('should create a scope with parent', function(){
+ var model = createScope({name:'Misko'});
+ expect(model.name).toEqual('Misko');
+ });
+
+ it('should have $get/set$/parent$', function(){
+ var parent = {};
+ var model = createScope(parent);
+ model.$set('name', 'adam');
+ expect(model.name).toEqual('adam');
+ expect(model.$get('name')).toEqual('adam');
+ expect(model.$parent).toEqual(model);
+ expect(model.$root).toEqual(model);
+ });
+
+ describe('$eval', function(){
+ it('should eval function with correct this and pass arguments', function(){
+ var model = createScope();
+ model.$eval(function(name){
+ this.name = name;
+ }, 'works');
+ expect(model.name).toEqual('works');
+ });
+
+ it('should eval expression with correct this', function(){
+ var model = createScope();
+ model.$eval('name="works"');
+ expect(model.name).toEqual('works');
+ });
+
+ it('should do nothing on empty string and not update view', function(){
+ var model = createScope();
+ var onEval = jasmine.createSpy('onEval');
+ model.$onEval(onEval);
+ model.$eval('');
+ expect(onEval).wasNotCalled();
+ });
+ });
+
+ describe('$watch', function(){
+ it('should watch an expression for change', function(){
+ var model = createScope();
+ model.oldValue = "";
+ var nameCount = 0, evalCount = 0;
+ model.name = 'adam';
+ model.$watch('name', function(){ nameCount ++; });
+ model.$watch(function(){return model.name;}, function(newValue, oldValue){
+ this.newValue = newValue;
+ this.oldValue = oldValue;
+ });
+ model.$onEval(function(){evalCount ++;});
+ model.name = 'misko';
+ model.$eval();
+ expect(nameCount).toEqual(2);
+ expect(evalCount).toEqual(1);
+ expect(model.newValue).toEqual('misko');
+ expect(model.oldValue).toEqual('adam');
+ });
+
+ it('should eval with no arguments', function(){
+ var model = createScope();
+ var count = 0;
+ model.$onEval(function(){count++;});
+ model.$eval();
+ expect(count).toEqual(1);
+ });
+ });
+
+ describe('$bind', function(){
+ it('should curry a function with respect to scope', function(){
+ var model = createScope();
+ model.name = 'misko';
+ expect(model.$bind(function(){return this.name;})()).toEqual('misko');
+ });
+ });
+
+ describe('$tryEval', function(){
+ it('should report error on element', function(){
+ var scope = createScope();
+ scope.$tryEval('throw "myerror";', function(error){
+ scope.error = error;
+ });
+ expect(scope.error).toEqual('myerror');
+ });
+
+ it('should report error on visible element', function(){
+ var element = jqLite('<div></div>');
+ var scope = createScope();
+ scope.$tryEval('throw "myError"', element);
+ expect(element.attr('ng-exception')).toEqual('"myError"'); // errors are jsonified
+ expect(element.hasClass('ng-exception')).toBeTruthy();
+ });
+
+ it('should report error on $excetionHandler', function(){
+ var element = jqLite('<div></div>');
+ var scope = createScope();
+ scope.$exceptionHandler = function(e){
+ this.error = e;
+ };
+ scope.$tryEval('throw "myError"');
+ expect(scope.error).toEqual("myError");
+ });
+ });
+
+ // $onEval
+ describe('$onEval', function(){
+ it("should eval using priority", function(){
+ var scope = createScope();
+ scope.log = "";
+ scope.$onEval('log = log + "middle;"');
+ scope.$onEval(-1, 'log = log + "first;"');
+ scope.$onEval(1, 'log = log + "last;"');
+ scope.$eval();
+ expect(scope.log).toEqual('first;middle;last;');
+ });
+
+ it("should have $root and $parent", function(){
+ var parent = createScope();
+ var scope = createScope(parent);
+ expect(scope.$root).toEqual(parent);
+ expect(scope.$parent).toEqual(parent);
+ });
+ });
+
+ describe('service injection', function(){
+ it('should inject services', function(){
+ var scope = createScope(null, {
+ service:function(){
+ return "ABC";
+ }
+ });
+ expect(scope.service).toEqual("ABC");
+ });
+
+ it('should inject arugments', function(){
+ var scope = createScope(null, {
+ name:function(){
+ return "misko";
+ },
+ greet: extend(function(name) {
+ return 'hello ' + name;
+ }, {inject:['name']})
+ });
+ expect(scope.greet).toEqual("hello misko");
+ });
+
+ it('should throw error on missing dependency', function(){
+ try {
+ createScope(null, {
+ greet: extend(function(name) {
+ }, {inject:['name']})
+ });
+ } catch(e) {
+ expect(e).toEqual("Don't know how to inject 'name'.");
+ }
+ });
+ });
+
+ describe('getterFn', function(){
+ it('should get chain', function(){
+ expect(getterFn('a.b')(undefined)).toEqual(undefined);
+ expect(getterFn('a.b')({})).toEqual(undefined);
+ expect(getterFn('a.b')({a:null})).toEqual(undefined);
+ expect(getterFn('a.b')({a:{}})).toEqual(undefined);
+ expect(getterFn('a.b')({a:{b:null}})).toEqual(null);
+ expect(getterFn('a.b')({a:{b:0}})).toEqual(0);
+ expect(getterFn('a.b')({a:{b:'abc'}})).toEqual('abc');
+ });
+
+ it('should map type method on top of expression', function(){
+ expect(getterFn('a.$filter')({a:[]})('')).toEqual([]);
+ });
+
+ it('should bind function this', function(){
+ expect(getterFn('a')({a:function($){return this.b + $;}, b:1})(2)).toEqual(3);
+
+ });
+ });
+});
diff --git a/test/ValidatorsTest.js b/test/ValidatorsTest.js
new file mode 100644
index 00000000..573c340d
--- /dev/null
+++ b/test/ValidatorsTest.js
@@ -0,0 +1,169 @@
+ValidatorTest = TestCase('ValidatorTest');
+
+ValidatorTest.prototype.testItShouldHaveThisSet = function() {
+ var validator = {};
+ angular.validator.myValidator = function(first, last){
+ validator.first = first;
+ validator.last = last;
+ validator._this = this;
+ };
+ var scope = compile('<input name="name" ng-validate="myValidator:\'hevery\'"/>');
+ scope.name = 'misko';
+ scope.$init();
+ assertEquals('misko', validator.first);
+ assertEquals('hevery', validator.last);
+ expect(validator._this.$id).toEqual(scope.$id);
+ delete angular.validator.myValidator;
+ scope.$element.remove();
+};
+
+ValidatorTest.prototype.testRegexp = function() {
+ assertEquals(angular.validator.regexp("abc", /x/, "E1"), "E1");
+ assertEquals(angular.validator.regexp("abc", '/x/'),
+ "Value does not match expected format /x/.");
+ assertEquals(angular.validator.regexp("ab", '^ab$'), null);
+ assertEquals(angular.validator.regexp("ab", '^axb$', "E3"), "E3");
+};
+
+ValidatorTest.prototype.testNumber = function() {
+ assertEquals(angular.validator.number("ab"), "Not a number");
+ assertEquals(angular.validator.number("-0.1",0), "Value can not be less than 0.");
+ assertEquals(angular.validator.number("10.1",0,10), "Value can not be greater than 10.");
+ assertEquals(angular.validator.number("1.2"), null);
+ assertEquals(angular.validator.number(" 1 ", 1, 1), null);
+};
+
+ValidatorTest.prototype.testInteger = function() {
+ assertEquals(angular.validator.integer("ab"), "Not a number");
+ assertEquals(angular.validator.integer("1.1"), "Not a whole number");
+ assertEquals(angular.validator.integer("1.0"), "Not a whole number");
+ assertEquals(angular.validator.integer("1."), "Not a whole number");
+ assertEquals(angular.validator.integer("-1",0), "Value can not be less than 0.");
+ assertEquals(angular.validator.integer("11",0,10), "Value can not be greater than 10.");
+ assertEquals(angular.validator.integer("1"), null);
+ assertEquals(angular.validator.integer(" 1 ", 1, 1), null);
+};
+
+ValidatorTest.prototype.testDate = function() {
+ var error = "Value is not a date. (Expecting format: 12/31/2009).";
+ assertEquals(angular.validator.date("ab"), error);
+ assertEquals(angular.validator.date("12/31/2009"), null);
+};
+
+ValidatorTest.prototype.testPhone = function() {
+ var error = "Phone number needs to be in 1(987)654-3210 format in North America or +999 (123) 45678 906 internationaly.";
+ assertEquals(angular.validator.phone("ab"), error);
+ assertEquals(null, angular.validator.phone("1(408)757-3023"));
+ assertEquals(null, angular.validator.phone("+421 (0905) 933 297"));
+ assertEquals(null, angular.validator.phone("+421 0905 933 297"));
+};
+
+ValidatorTest.prototype.testSSN = function() {
+ var error = "SSN needs to be in 999-99-9999 format.";
+ assertEquals(angular.validator.ssn("ab"), error);
+ assertEquals(angular.validator.ssn("123-45-6789"), null);
+};
+
+ValidatorTest.prototype.testURL = function() {
+ var error = "URL needs to be in http://server[:port]/path format.";
+ assertEquals(angular.validator.url("ab"), error);
+ assertEquals(angular.validator.url("http://server:123/path"), null);
+};
+
+ValidatorTest.prototype.testEmail = function() {
+ var error = "Email needs to be in username@host.com format.";
+ assertEquals(error, angular.validator.email("ab"));
+ assertEquals(null, angular.validator.email("misko@hevery.com"));
+};
+
+ValidatorTest.prototype.testJson = function() {
+ assertNotNull(angular.validator.json("'"));
+ assertNotNull(angular.validator.json("''X"));
+ assertNull(angular.validator.json("{}"));
+};
+
+describe('Validator:asynchronous', function(){
+ var asynchronous = angular.validator.asynchronous;
+ var self;
+ var value, fn;
+
+ beforeEach(function(){
+ var invalidWidgets = angularService('$invalidWidgets')();
+ value = null;
+ fn = null;
+ self = {
+ $element:jqLite('<input />'),
+ $invalidWidgets:invalidWidgets,
+ $eval: noop
+ };
+ self.$element.data('$validate', noop);
+ self.$root = self;
+ });
+
+ afterEach(function(){
+ if (self.$element) self.$element.remove();
+ var oldCache = jqCache;
+ jqCache = {};
+ expect(size(oldCache)).toEqual(0);
+ });
+
+ it('should make a request and show spinner', function(){
+ var value, fn;
+ var scope = compile('<input type="text" name="name" ng-validate="asynchronous:asyncFn"/>');
+ scope.$init();
+ var input = scope.$element;
+ scope.asyncFn = function(v,f){
+ value=v; fn=f;
+ };
+ scope.name = "misko";
+ scope.$eval();
+ expect(value).toEqual('misko');
+ expect(input.hasClass('ng-input-indicator-wait')).toBeTruthy();
+ fn("myError");
+ expect(input.hasClass('ng-input-indicator-wait')).toBeFalsy();
+ expect(input.attr(NG_VALIDATION_ERROR)).toEqual("myError");
+ scope.$element.remove();
+ });
+
+ it("should not make second request to same value", function(){
+ asynchronous.call(self, "kai", function(v,f){value=v; fn=f;});
+ expect(value).toEqual('kai');
+ expect(self.$invalidWidgets[0]).toEqual(self.$element);
+
+ var spy = jasmine.createSpy();
+ asynchronous.call(self, "kai", spy);
+ expect(spy).wasNotCalled();
+
+ asynchronous.call(self, "misko", spy);
+ expect(spy).wasCalled();
+ });
+
+ it("should ignore old callbacks, and not remove spinner", function(){
+ var firstCb, secondCb;
+ asynchronous.call(self, "first", function(v,f){value=v; firstCb=f;});
+ asynchronous.call(self, "second", function(v,f){value=v; secondCb=f;});
+
+ firstCb();
+ expect(self.$element.hasClass('ng-input-indicator-wait')).toBeTruthy();
+
+ secondCb();
+ expect(self.$element.hasClass('ng-input-indicator-wait')).toBeFalsy();
+ });
+
+ it("should handle update function", function(){
+ var scope = angular.compile('<input name="name" ng-validate="asynchronous:asyncFn:updateFn"/>');
+ scope.asyncFn = jasmine.createSpy();
+ scope.updateFn = jasmine.createSpy();
+ scope.name = 'misko';
+ scope.$init();
+ scope.$eval();
+ expect(scope.asyncFn).wasCalledWith('misko', scope.asyncFn.mostRecentCall.args[1]);
+ assertTrue(scope.$element.hasClass('ng-input-indicator-wait'));
+ scope.asyncFn.mostRecentCall.args[1]('myError', {id: 1234, data:'data'});
+ assertFalse(scope.$element.hasClass('ng-input-indicator-wait'));
+ assertEquals('myError', scope.$element.attr('ng-validation-error'));
+ expect(scope.updateFn.mostRecentCall.args[0]).toEqual({id: 1234, data:'data'});
+ scope.$element.remove();
+ });
+
+});
diff --git a/test/angular-mocks.js b/test/angular-mocks.js
new file mode 100644
index 00000000..8838b2cd
--- /dev/null
+++ b/test/angular-mocks.js
@@ -0,0 +1,101 @@
+/**
+ * The MIT License
+ *
+ * Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+function MockBrowser() {
+ var self = this,
+ expectations = {},
+ requests = [];
+ this.isMock = true;
+ self.url = "http://server";
+ self.watches = [];
+
+ self.xhr = function(method, url, data, callback) {
+ if (angular.isFunction(data)) {
+ callback = data;
+ data = null;
+ }
+ if (data && angular.isObject(data)) data = angular.toJson(data);
+ if (data && angular.isString(data)) url += "|" + data;
+ var expect = expectations[method] || {};
+ var response = expect[url];
+ if (!response) {
+ throw "Unexepected request for method '" + method + "' and url '" + url + "'.";
+ }
+ requests.push(function(){
+ callback(response.code, response.response);
+ });
+ };
+ self.xhr.expectations = expectations;
+ self.xhr.requests = requests;
+ self.xhr.expect = function(method, url, data) {
+ if (data && angular.isObject(data)) data = angular.toJson(data);
+ if (data && angular.isString(data)) url += "|" + data;
+ var expect = expectations[method] || (expectations[method] = {});
+ return {
+ respond: function(code, response) {
+ if (!angular.isNumber(code)) {
+ response = code;
+ code = 200;
+ }
+ expect[url] = {code:code, response:response};
+ }
+ };
+ };
+ self.xhr.expectGET = angular.bind(self, self.xhr.expect, 'GET');
+ self.xhr.expectPOST = angular.bind(self, self.xhr.expect, 'POST');
+ self.xhr.expectDELETE = angular.bind(self, self.xhr.expect, 'DELETE');
+ self.xhr.expectPUT = angular.bind(self, self.xhr.expect, 'PUT');
+ self.xhr.flush = function() {
+ while(requests.length) {
+ requests.pop()();
+ }
+ };
+}
+MockBrowser.prototype = {
+
+ hover: function(onHover) {
+ },
+
+ getUrl: function(){
+ return this.url;
+ },
+
+ setUrl: function(url){
+ this.url = url;
+ },
+
+ watchUrl: function(fn) {
+ this.watches.push(fn);
+ },
+
+ fireUrlWatchers: function() {
+ for(var i=0; i<this.watches.length; i++) {
+ this.watches[i](this.url);
+ }
+ }
+};
+
+angular.service('$browser', function(){
+ return new MockBrowser();
+});
diff --git a/test/delete/ScopeTest.js b/test/delete/ScopeTest.js
new file mode 100644
index 00000000..24febf19
--- /dev/null
+++ b/test/delete/ScopeTest.js
@@ -0,0 +1,145 @@
+ScopeTest = TestCase('ScopeTest');
+
+ScopeTest.prototype.testGetScopeRetrieval = function(){
+ var scope = {};
+ var form = jQuery("<a><b><c></c></b></a>");
+ form.data('scope', scope);
+ var c = form.find('c');
+ assertTrue(scope === c.scope());
+};
+
+ScopeTest.prototype.testGetScopeRetrievalIntermediateNode = function(){
+ var scope = {};
+ var form = jQuery("<a><b><c></c></b></a>");
+ form.find("b").data('scope', scope);
+ var b = form.find('b');
+ assertTrue(scope === b.scope());
+};
+
+ScopeTest.prototype.testNoScopeDoesNotCauseInfiniteRecursion = function(){
+ var form = jQuery("<a><b><c></c></b></a>");
+ var c = form.find('c');
+ assertTrue(!c.scope());
+};
+
+ScopeTest.prototype.testScopeEval = function(){
+ var scope = new Scope({b:345});
+ assertEquals(scope.eval('b = 123'), 123);
+ assertEquals(scope.get('b'), 123);
+};
+
+ScopeTest.prototype.testScopeFromPrototype = function(){
+ var scope = new Scope({b:123});
+ scope.eval('a = b');
+ scope.eval('b = 456');
+ assertEquals(scope.get('a'), 123);
+ assertEquals(scope.get('b'), 456);
+};
+
+ScopeTest.prototype.testSetScopeGet = function(){
+ var scope = new Scope();
+ assertEquals(987, scope.set('a', 987));
+ assertEquals(scope.get('a'), 987);
+ assertEquals(scope.eval('a'), 987);
+};
+
+ScopeTest.prototype.testGetChain = function(){
+ var scope = new Scope({a:{b:987}});
+ assertEquals(scope.get('a.b'), 987);
+ assertEquals(scope.eval('a.b'), 987);
+};
+
+ScopeTest.prototype.testGetUndefinedChain = function(){
+ var scope = new Scope();
+ assertEquals(typeof scope.get('a.b'), 'undefined');
+};
+
+ScopeTest.prototype.testSetChain = function(){
+ var scope = new Scope({a:{}});
+ scope.set('a.b', 987);
+ assertEquals(scope.get('a.b'), 987);
+ assertEquals(scope.eval('a.b'), 987);
+};
+
+ScopeTest.prototype.testSetGetOnChain = function(){
+ var scope = new Scope();
+ scope.set('a.b', 987);
+ assertEquals(scope.get('a.b'), 987);
+ assertEquals(scope.eval('a.b'), 987);
+};
+
+ScopeTest.prototype.testGlobalFunctionAccess =function(){
+ window['scopeAddTest'] = function (a, b) {return a+b;};
+ var scope = new Scope({window:window});
+ assertEquals(scope.eval('window.scopeAddTest(1,2)'), 3);
+
+ scope.set('add', function (a, b) {return a+b;});
+ assertEquals(scope.eval('add(1,2)'), 3);
+
+ scope.set('math.add', function (a, b) {return a+b;});
+ assertEquals(scope.eval('math.add(1,2)'), 3);
+};
+
+ScopeTest.prototype.testValidationEval = function(){
+ expectAsserts(4);
+ var scope = new Scope();
+ scope.set("name", "misko");
+ angular.validator.testValidator = function(value, expect){
+ assertEquals("misko", this.name);
+ return value == expect ? null : "Error text";
+ };
+
+ assertEquals("Error text", scope.validate("testValidator:'abc'", 'x'));
+ assertEquals(null, scope.validate("testValidator:'abc'", 'abc'));
+
+ delete angular.validator['testValidator'];
+};
+
+ScopeTest.prototype.testCallingNonExistantMethodShouldProduceFriendlyException = function() {
+ expectAsserts(1);
+ var scope = new Scope({obj:{}});
+ try {
+ scope.eval("obj.iDontExist()");
+ fail();
+ } catch (e) {
+ assertEquals("Expression 'obj.iDontExist' is not a function.", e);
+ }
+};
+
+ScopeTest.prototype.testAccessingWithInvalidPathShouldThrowError = function() {
+ var scope = new Scope();
+ try {
+ scope.get('a.{{b}}');
+ fail();
+ } catch (e) {
+ assertEquals("Expression 'a.{{b}}' is not a valid expression for accesing variables.", e);
+ }
+};
+
+ScopeTest.prototype.testItShouldHave$parent = function() {
+ var parent = new Scope({}, "ROOT");
+ var child = new Scope(parent.state);
+ assertSame("parent", child.state.$parent, parent.state);
+ assertSame("root", child.state.$root, parent.state);
+};
+
+ScopeTest.prototype.testItShouldHave$root = function() {
+ var scope = new Scope({}, "ROOT");
+ assertSame(scope.state.$root, scope.state);
+};
+
+ScopeTest.prototype.testItShouldBuildPathOnUndefined = function(){
+ var scope = new Scope({}, "ROOT");
+ scope.setEval("a.$b.c", 1);
+ assertJsonEquals({$b:{c:1}}, scope.get("a"));
+};
+
+ScopeTest.prototype.testItShouldMapUnderscoreFunctions = function(){
+ var scope = new Scope({}, "ROOT");
+ scope.set("a", [1,2,3]);
+ assertEquals('function', typeof scope.get("a.$size"));
+ scope.eval("a.$includeIf(4,true)");
+ assertEquals(4, scope.get("a.$size")());
+ assertEquals(4, scope.eval("a.$size()"));
+ assertEquals('undefined', typeof scope.get("a.dontExist"));
+};
diff --git a/test/delete/WidgetsTest.js b/test/delete/WidgetsTest.js
new file mode 100644
index 00000000..313d7372
--- /dev/null
+++ b/test/delete/WidgetsTest.js
@@ -0,0 +1,268 @@
+WidgetTest = TestCase('WidgetTest');
+
+WidgetTest.prototype.testRequired = function () {
+ var view = $('<input name="a" ng-required>');
+ var scope = new Scope({$invalidWidgets:[]});
+ var cntl = new TextController(view[0], 'a', angularFormatter.noop);
+ cntl.updateView(scope);
+ assertTrue(view.hasClass('ng-validation-error'));
+ assertEquals("Required Value", view.attr('ng-error'));
+ scope.set('a', 'A');
+ cntl.updateView(scope);
+ assertFalse(view.hasClass('ng-validation-error'));
+ assertEquals("undefined", typeof view.attr('ng-error'));
+};
+
+WidgetTest.prototype.testValidator = function () {
+ var view = $('<input name="a" ng-validate="testValidator:\'ABC\'">');
+ var scope = new Scope({$invalidWidgets:[]});
+ var cntl = new TextController(view[0], 'a', angularFormatter.noop);
+ angular.validator.testValidator = function(value, expect){
+ return value == expect ? false : "Error text";
+ };
+
+ scope.set('a', '');
+ cntl.updateView(scope);
+ assertEquals(view.hasClass('ng-validation-error'), false);
+ assertEquals(null, view.attr('ng-error'));
+
+ scope.set('a', 'X');
+ cntl.updateView(scope);
+ assertEquals(view.hasClass('ng-validation-error'), true);
+ assertEquals(view.attr('ng-error'), "Error text");
+ assertEquals("Error text", view.attr('ng-error'));
+
+ scope.set('a', 'ABC');
+ cntl.updateView(scope);
+ assertEquals(view.hasClass('ng-validation-error'), false);
+ assertEquals(view.attr('ng-error'), null);
+ assertEquals(null, view.attr('ng-error'));
+
+ delete angular.validator['testValidator'];
+};
+
+WidgetTest.prototype.testRequiredValidator = function () {
+ var view = $('<input name="a" ng-required ng-validate="testValidator:\'ABC\'">');
+ var scope = new Scope({$invalidWidgets:[]});
+ var cntl = new TextController(view[0], 'a', angularFormatter.noop);
+ angular.validator.testValidator = function(value, expect){
+ return value == expect ? null : "Error text";
+ };
+
+ scope.set('a', '');
+ cntl.updateView(scope);
+ assertEquals(view.hasClass('ng-validation-error'), true);
+ assertEquals("Required Value", view.attr('ng-error'));
+
+ scope.set('a', 'X');
+ cntl.updateView(scope);
+ assertEquals(view.hasClass('ng-validation-error'), true);
+ assertEquals("Error text", view.attr('ng-error'));
+
+ scope.set('a', 'ABC');
+ cntl.updateView(scope);
+ assertEquals(view.hasClass('ng-validation-error'), false);
+ assertEquals(null, view.attr('ng-error'));
+
+ delete angular.validator['testValidator'];
+};
+
+TextControllerTest = TestCase("TextControllerTest");
+
+TextControllerTest.prototype.testDatePicker = function() {
+ var input = $('<input type="text" ng-widget="datepicker">');
+ input.data('scope', new Scope());
+ var body = $(document.body);
+ body.append(input);
+ var binder = new Binder(input[0], new WidgetFactory());
+ assertTrue('before', input.data('datepicker') === undefined);
+ binder.compile();
+ assertTrue('after', input.data('datepicker') !== null);
+ assertTrue(body.html(), input.hasClass('hasDatepicker'));
+};
+
+RepeaterUpdaterTest = TestCase("RepeaterUpdaterTest");
+
+RepeaterUpdaterTest.prototype.testRemoveThenAdd = function() {
+ var view = $("<div><span/></div>");
+ var template = function () {
+ return $("<li/>");
+ };
+ var repeater = new RepeaterUpdater(view.find("span"), "a in b", template, "");
+ var scope = new Scope();
+ scope.set('b', [1,2]);
+
+ repeater.updateView(scope);
+
+ scope.set('b', []);
+ repeater.updateView(scope);
+
+ scope.set('b', [1]);
+ repeater.updateView(scope);
+ assertEquals(1, view.find("li").size());
+};
+
+RepeaterUpdaterTest.prototype.testShouldBindWidgetOnRepeaterClone = function(){
+ //fail();
+};
+
+RepeaterUpdaterTest.prototype.testShouldThrowInformativeSyntaxError= function(){
+ expectAsserts(1);
+ try {
+ var repeater = new RepeaterUpdater(null, "a=b");
+ } catch (e) {
+ assertEquals("Expected ng-repeat in form of 'item in collection' but got 'a=b'.", e);
+ }
+};
+
+SelectControllerTest = TestCase("SelectControllerTest");
+
+SelectControllerTest.prototype.testShouldUpdateModelNullOnNothingSelected = function(){
+ var scope = new Scope();
+ var view = {selectedIndex:-1, options:[]};
+ var cntl = new SelectController(view, 'abc');
+ cntl.updateModel(scope);
+ assertNull(scope.get('abc'));
+};
+
+SelectControllerTest.prototype.testShouldUpdateModelWhenNothingSelected = function(){
+ var scope = new Scope();
+ var view = {value:'123'};
+ var cntl = new SelectController(view, 'abc');
+ cntl.updateView(scope);
+ assertEquals("123", scope.get('abc'));
+};
+
+BindUpdaterTest = TestCase("BindUpdaterTest");
+
+BindUpdaterTest.prototype.testShouldDisplayNothingForUndefined = function () {
+ var view = $('<span />');
+ var controller = new BindUpdater(view[0], "{{a}}");
+ var scope = new Scope();
+
+ scope.set('a', undefined);
+ controller.updateView(scope);
+ assertEquals("", view.text());
+
+ scope.set('a', null);
+ controller.updateView(scope);
+ assertEquals("", view.text());
+};
+
+BindUpdaterTest.prototype.testShouldDisplayJsonForNonStrings = function () {
+ var view = $('<span />');
+ var controller = new BindUpdater(view[0], "{{obj}}");
+
+ controller.updateView(new Scope({obj:[]}));
+ assertEquals("[]", view.text());
+
+ controller.updateView(new Scope({obj:{text:'abc'}}));
+ assertEquals('abc', fromJson(view.text()).text);
+};
+
+
+BindUpdaterTest.prototype.testShouldInsertHtmlNode = function () {
+ var view = $('<span />');
+ var controller = new BindUpdater(view[0], "<fake>&{{obj}}</fake>");
+ var scope = new Scope();
+
+ scope.set("obj", $('<div>myDiv</div>')[0]);
+ controller.updateView(scope);
+ assertEquals("<fake>&myDiv</fake>", view.text());
+};
+
+
+BindUpdaterTest.prototype.testShouldDisplayTextMethod = function () {
+ var view = $('<div />');
+ var controller = new BindUpdater(view[0], "{{obj}}");
+ var scope = new Scope();
+
+ scope.set("obj", new angular.filter.Meta({text:function(){return "abc";}}));
+ controller.updateView(scope);
+ assertEquals("abc", view.text());
+
+ scope.set("obj", new angular.filter.Meta({text:"123"}));
+ controller.updateView(scope);
+ assertEquals("123", view.text());
+
+ scope.set("obj", {text:"123"});
+ controller.updateView(scope);
+ assertEquals("123", fromJson(view.text()).text);
+};
+
+BindUpdaterTest.prototype.testShouldDisplayHtmlMethod = function () {
+ var view = $('<div />');
+ var controller = new BindUpdater(view[0], "{{obj}}");
+ var scope = new Scope();
+
+ scope.set("obj", new angular.filter.Meta({html:function(){return "a<div>b</div>c";}}));
+ controller.updateView(scope);
+ assertEquals("abc", view.text());
+
+ scope.set("obj", new angular.filter.Meta({html:"1<div>2</div>3"}));
+ controller.updateView(scope);
+ assertEquals("123", view.text());
+
+ scope.set("obj", {html:"123"});
+ controller.updateView(scope);
+ assertEquals("123", fromJson(view.text()).html);
+};
+
+BindUpdaterTest.prototype.testUdateBoolean = function() {
+ var view = $('<div />');
+ var controller = new BindUpdater(view[0], "{{true}}, {{false}}");
+ controller.updateView(new Scope());
+ assertEquals('true, false', view.text());
+};
+
+BindAttrUpdaterTest = TestCase("BindAttrUpdaterTest");
+
+BindAttrUpdaterTest.prototype.testShouldLoadBlankImageWhenBindingIsUndefined = function () {
+ var view = $('<img />');
+ var controller = new BindAttrUpdater(view[0], {src: '{{imageUrl}}'});
+
+ var scope = new Scope();
+ scope.set('imageUrl', undefined);
+ scope.set('$config.blankImage', 'http://server/blank.gif');
+
+ controller.updateView(scope);
+ assertEquals("http://server/blank.gif", view.attr('src'));
+};
+
+RepeaterUpdaterTest.prototype.testShouldNotDieWhenRepeatExpressionIsNull = function() {
+ var rep = new RepeaterUpdater(null, "$item in items", null, null);
+ var scope = new Scope();
+ scope.set('items', undefined);
+ rep.updateView(scope);
+};
+
+RepeaterUpdaterTest.prototype.testShouldIterateOverKeys = function() {
+ var rep = new RepeaterUpdater(null, "($k,_v) in items", null, null);
+ assertEquals("items", rep.iteratorExp);
+ assertEquals("_v", rep.valueExp);
+ assertEquals("$k", rep.keyExp);
+};
+
+EvalUpdaterTest = TestCase("EvalUpdaterTest");
+EvalUpdaterTest.prototype.testEvalThrowsException = function(){
+ var view = $('<div/>');
+ var eval = new EvalUpdater(view[0], 'undefined()');
+
+ eval.updateView(new Scope());
+ assertTrue(!!view.attr('ng-error'));
+ assertTrue(view.hasClass('ng-exception'));
+
+ eval.exp = "1";
+ eval.updateView(new Scope());
+ assertFalse(!!view.attr('ng-error'));
+ assertFalse(view.hasClass('ng-exception'));
+};
+
+RadioControllerTest = TestCase("RadioController");
+RadioControllerTest.prototype.testItShouldTreatTrueStringAsBoolean = function () {
+ var view = $('<input type="radio" name="select" value="true"/>');
+ var radio = new RadioController(view[0], 'select');
+ var scope = new Scope({select:true});
+ radio.updateView(scope);
+ assertTrue(view[0].checked);
+};
diff --git a/test/directivesSpec.js b/test/directivesSpec.js
new file mode 100644
index 00000000..42869a05
--- /dev/null
+++ b/test/directivesSpec.js
@@ -0,0 +1,226 @@
+describe("directives", function(){
+
+ var compile, model, element;
+
+ beforeEach(function() {
+ var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget);
+ compile = function(html) {
+ element = jqLite(html);
+ model = compiler.compile(element)(element);
+ model.$init();
+ return model;
+ };
+ });
+
+ afterEach(function() {
+ if (model && model.$element) model.$element.remove();
+ expect(size(jqCache)).toEqual(0);
+ });
+
+ it("should ng-init", function() {
+ var scope = compile('<div ng-init="a=123"></div>');
+ expect(scope.a).toEqual(123);
+ });
+
+ it("should ng-eval", function() {
+ var scope = compile('<div ng-init="a=0" ng-eval="a = a + 1"></div>');
+ expect(scope.a).toEqual(1);
+ scope.$eval();
+ expect(scope.a).toEqual(2);
+ });
+
+ it('should ng-bind', function() {
+ var scope = compile('<div ng-bind="a"></div>');
+ expect(element.text()).toEqual('');
+ scope.a = 'misko';
+ scope.$eval();
+ expect(element.text()).toEqual('misko');
+ });
+
+ it('should ng-bind html', function() {
+ var scope = compile('<div ng-bind="html|html"></div>');
+ scope.html = '<div>hello</div>';
+ scope.$eval();
+ expect(lowercase(element.html())).toEqual('<div>hello</div>');
+ });
+
+ it('should ng-bind element', function() {
+ angularFilter.myElement = function() {
+ return jqLite('<a>hello</a>');
+ };
+ var scope = compile('<div ng-bind="0|myElement"></div>');
+ scope.$eval();
+ expect(lowercase(element.html())).toEqual('<a>hello</a>');
+ });
+
+ it('should ng-bind-template', function() {
+ var scope = compile('<div ng-bind-template="Hello {{name}}!"></div>');
+ scope.$set('name', 'Misko');
+ scope.$eval();
+ expect(element.text()).toEqual('Hello Misko!');
+ });
+
+ it('should ng-bind-attr', function(){
+ var scope = compile('<img ng-bind-attr="{src:\'http://localhost/mysrc\', alt:\'myalt\'}"/>');
+ expect(element.attr('src')).toEqual('http://localhost/mysrc');
+ expect(element.attr('alt')).toEqual('myalt');
+ });
+
+ it('should remove special attributes on false', function(){
+ var scope = compile('<input ng-bind-attr="{disabled:\'{{disabled}}\', readonly:\'{{readonly}}\', checked:\'{{checked}}\'}"/>');
+ var input = scope.$element[0];
+ expect(input.disabled).toEqual(false);
+ expect(input.readOnly).toEqual(false);
+ expect(input.checked).toEqual(false);
+
+ scope.disabled = true;
+ scope.readonly = true;
+ scope.checked = true;
+ scope.$eval();
+
+ expect(input.disabled).toEqual(true);
+ expect(input.readOnly).toEqual(true);
+ expect(input.checked).toEqual(true);
+ });
+
+ it('should ng-non-bindable', function(){
+ var scope = compile('<div ng-non-bindable><span ng-bind="name"></span></div>');
+ scope.$set('name', 'misko');
+ scope.$eval();
+ expect(element.text()).toEqual('');
+ });
+
+ it('should ng-repeat over array', function(){
+ var scope = compile('<ul><li ng-repeat="item in items" ng-init="suffix = \';\'" ng-bind="item + suffix"></li></ul>');
+
+ scope.$set('items', ['misko', 'shyam']);
+ scope.$eval();
+ expect(element.text()).toEqual('misko;shyam;');
+
+ scope.$set('items', ['adam', 'kai', 'brad']);
+ scope.$eval();
+ expect(element.text()).toEqual('adam;kai;brad;');
+
+ scope.$set('items', ['brad']);
+ scope.$eval();
+ expect(element.text()).toEqual('brad;');
+ });
+
+ it('should ng-repeat over object', function(){
+ var scope = compile('<ul><li ng-repeat="(key, value) in items" ng-bind="key + \':\' + value + \';\' "></li></ul>');
+ scope.$set('items', {misko:'swe', shyam:'set'});
+ scope.$eval();
+ expect(element.text()).toEqual('misko:swe;shyam:set;');
+ });
+
+ it('should set ng-repeat to [] if undefinde', function(){
+ var scope = compile('<ul><li ng-repeat="item in items"></li></ul>');
+ expect(scope.items).toEqual([]);
+ });
+
+ it('should error on wrong parsing of ng-repeat', function(){
+ var scope = compile('<ul><li ng-repeat="i dont parse"></li></ul>');
+ var log = "";
+ log += element.attr('ng-exception') + ';';
+ log += element.hasClass('ng-exception') + ';';
+ expect(log).toEqual("\"Expected ng-repeat in form of 'item in collection' but got 'i dont parse'.\";true;");
+ });
+
+ it('should ng-watch', function(){
+ var scope = compile('<div ng-watch="i: count = count + 1" ng-init="count = 0">');
+ scope.$eval();
+ scope.$eval();
+ expect(scope.$get('count')).toEqual(0);
+
+ scope.$set('i', 0);
+ scope.$eval();
+ scope.$eval();
+ expect(scope.$get('count')).toEqual(1);
+ });
+
+ it('should ng-click', function(){
+ var scope = compile('<div ng-click="clicked = true"></div>');
+ scope.$eval();
+ expect(scope.$get('clicked')).toBeFalsy();
+
+ element.trigger('click');
+ expect(scope.$get('clicked')).toEqual(true);
+ });
+
+ it('should ng-class', function(){
+ var scope = compile('<div class="existing" ng-class="[\'A\', \'B\']"></div>');
+ scope.$eval();
+ expect(element.hasClass('existing')).toBeTruthy();
+ expect(element.hasClass('A')).toBeTruthy();
+ expect(element.hasClass('B')).toBeTruthy();
+ });
+
+ it('should ng-class odd/even', function(){
+ var scope = compile('<ul><li ng-repeat="i in [0,1]" class="existing" ng-class-odd="\'odd\'" ng-class-even="\'even\'"></li><ul>');
+ scope.$eval();
+ var e1 = jqLite(element[0].childNodes[1]);
+ var e2 = jqLite(element[0].childNodes[2]);
+ expect(e1.hasClass('existing')).toBeTruthy();
+ expect(e1.hasClass('odd')).toBeTruthy();
+ expect(e2.hasClass('existing')).toBeTruthy();
+ expect(e2.hasClass('even')).toBeTruthy();
+ });
+
+ it('should ng-style', function(){
+ var scope = compile('<div ng-style="{color:\'red\'}"></div>');
+ scope.$eval();
+ expect(element.css('color')).toEqual('red');
+ });
+
+ it('should ng-show', function(){
+ var scope = compile('<div ng-hide="hide"></div>');
+ scope.$eval();
+ expect(isCssVisible(scope.$element)).toEqual(true);
+ scope.$set('hide', true);
+ scope.$eval();
+ expect(isCssVisible(scope.$element)).toEqual(false);
+ });
+
+ it('should ng-hide', function(){
+ var scope = compile('<div ng-show="show"></div>');
+ scope.$eval();
+ expect(isCssVisible(scope.$element)).toEqual(false);
+ scope.$set('show', true);
+ scope.$eval();
+ expect(isCssVisible(scope.$element)).toEqual(true);
+ });
+
+ describe('ng-controller', function(){
+ it('should bind', function(){
+ window.Greeter = function(){
+ this.greeting = 'hello';
+ };
+ window.Greeter.prototype = {
+ init: function(){
+ this.suffix = '!';
+ },
+ greet: function(name) {
+ return this.greeting + ' ' + name + this.suffix;
+ }
+ };
+ var scope = compile('<div ng-controller="Greeter"></div>');
+ expect(scope.greeting).toEqual('hello');
+ expect(scope.greet('misko')).toEqual('hello misko!');
+ window.Greeter = undefined;
+ });
+ });
+
+ it('should eval things according to ng-eval-order', function(){
+ var scope = compile(
+ '<div ng-init="log=\'\'">' +
+ '{{log = log + \'e\'}}' +
+ '<span ng-eval-order="first" ng-eval="log = log + \'a\'">' +
+ '{{log = log + \'b\'}}' +
+ '<span src="{{log = log + \'c\'}}"></span>' +
+ '<span bind-template="{{log = log + \'d\'}}"></span>' +
+ '</span>' +
+ '</div>');
+ expect(scope.log).toEqual('abcde');
+ });
+
+});
diff --git a/test/jquery_alias.js b/test/jquery_alias.js
new file mode 100644
index 00000000..4b3fad00
--- /dev/null
+++ b/test/jquery_alias.js
@@ -0,0 +1 @@
+var _jQuery = jQuery; \ No newline at end of file
diff --git a/test/jquery_remove.js b/test/jquery_remove.js
new file mode 100644
index 00000000..5283c340
--- /dev/null
+++ b/test/jquery_remove.js
@@ -0,0 +1 @@
+var _jQuery = jQuery.noConflict(true); \ No newline at end of file
diff --git a/test/markupSpec.js b/test/markupSpec.js
new file mode 100644
index 00000000..8358b673
--- /dev/null
+++ b/test/markupSpec.js
@@ -0,0 +1,138 @@
+describe("markups", function(){
+
+ var compile, element, scope;
+
+ beforeEach(function() {
+ scope = null;
+ element = null;
+ var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget);
+ compile = function(html) {
+ element = jqLite(html);
+ scope = compiler.compile(element)(element);
+ scope.$init();
+ };
+ });
+
+ afterEach(function(){
+ if (element) element.remove();
+ expect(size(jqCache)).toEqual(0);
+ });
+
+ it('should translate {{}} in text', function(){
+ compile('<div>hello {{name}}!</div>');
+ expect(sortedHtml(element)).toEqual('<div>hello <span ng-bind="name"></span>!</div>');
+ scope.$set('name', 'Misko');
+ scope.$eval();
+ expect(sortedHtml(element)).toEqual('<div>hello <span ng-bind="name">Misko</span>!</div>');
+ });
+
+ it('should translate {{}} in terminal nodes', function(){
+ compile('<select name="x"><option value="">Greet {{name}}!</option></select>');
+ expect(sortedHtml(element).replace(' selected="true"', '')).toEqual('<select name="x"><option ng-bind-template="Greet {{name}}!">Greet !</option></select>');
+ scope.$set('name', 'Misko');
+ scope.$eval();
+ expect(sortedHtml(element).replace(' selected="true"', '')).toEqual('<select name="x"><option ng-bind-template="Greet {{name}}!">Greet Misko!</option></select>');
+ });
+
+ it('should translate {{}} in attributes', function(){
+ compile('<img src="http://server/{{path}}.png"/>');
+ expect(element.attr('ng-bind-attr')).toEqual('{"src":"http://server/{{path}}.png"}');
+ scope.$set('path', 'a/b');
+ scope.$eval();
+ expect(element.attr('src')).toEqual("http://server/a/b.png");
+ });
+
+ it('should populate value attribute on OPTION', function(){
+ compile('<select name="x"><option>a</option></select>');
+ expect(sortedHtml(element).replace(' selected="true"', '')).toEqual('<select name="x"><option value="a">a</option></select>');
+ });
+
+});
+
+
+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/moveToAngularCom/Base64Test.js b/test/moveToAngularCom/Base64Test.js
new file mode 100644
index 00000000..a9353186
--- /dev/null
+++ b/test/moveToAngularCom/Base64Test.js
@@ -0,0 +1,5 @@
+Base64Test = TestCase('Base64Test');
+
+Base64Test.prototype.testEncodeDecode = function(){
+ assertEquals(Base64.decode(Base64.encode('hello')), 'hello');
+};
diff --git a/test/moveToAngularCom/DataStoreTest.js b/test/moveToAngularCom/DataStoreTest.js
new file mode 100644
index 00000000..87c5be2e
--- /dev/null
+++ b/test/moveToAngularCom/DataStoreTest.js
@@ -0,0 +1,616 @@
+DataStoreTest = TestCase('DataStoreTest');
+
+DataStoreTest.prototype.testSavePostsToServer = function(){
+ expectAsserts(10);
+ var response;
+ var post = function(data, callback){
+ var method = data[0][0];
+ var posted = data[0][2];
+ assertEquals("POST", method);
+ assertEquals("abc", posted.$entity);
+ assertEquals("123", posted.$id);
+ assertEquals("1", posted.$version);
+ assertFalse('function' == typeof posted.save);
+ response = fromJson(toJson(posted));
+ response.$entity = "abc";
+ response.$id = "123";
+ response.$version = "2";
+ callback(200, [response]);
+ };
+ var datastore = new DataStore(post);
+ var model = datastore.entity('abc', {name: "value"})();
+ model.$id = "123";
+ model.$version = "1";
+
+ datastore.save(model, function(obj){
+ assertTrue(obj === model);
+ assertEquals(obj.$entity, "abc");
+ assertEquals(obj.$id, "123");
+ assertEquals(obj.$version, "2");
+ assertEquals(obj.name, "value");
+ obj.after = true;
+ });
+ datastore.flush();
+};
+
+DataStoreTest.prototype.testLoadGetsFromServer = function(){
+ expectAsserts(12);
+ var post = function(data, callback){
+ var method = data[0][0];
+ var path = data[0][1];
+ assertEquals("GET", method);
+ assertEquals("abc/1", path);
+ response = [{$entity:'abc', $id:'1', $version:'2', key:"value"}];
+ callback(200, response);
+ };
+ var datastore = new DataStore(post);
+
+ var model = datastore.entity("abc", {merge:true})();
+ assertEquals(datastore.load(model, '1', function(obj){
+ assertEquals(obj.$entity, "abc");
+ assertEquals(obj.$id, "1");
+ assertEquals(obj.$version, "2");
+ assertEquals(obj.key, "value");
+ }), model);
+ datastore.flush();
+ assertEquals(model.$entity, "abc");
+ assertEquals(model.$id, "1");
+ assertEquals(model.$version, "2");
+ assertEquals(model.key, "value");
+ assertEquals(model.merge, true);
+};
+
+DataStoreTest.prototype.testRemove = function(){
+ expectAsserts(8);
+ var response;
+ var post = function(data, callback){
+ var method = data[0][0];
+ var posted = data[0][2];
+ assertEquals("DELETE", method);
+ assertEquals("abc", posted.$entity);
+ assertEquals("123", posted.$id);
+ assertEquals("1", posted.$version);
+ assertFalse('function' == typeof posted.save);
+ response = fromJson(toJson(posted));
+ response.$entity = "abc";
+ response.$id = "123";
+ response.$version = "2";
+ callback(200, [response]);
+ };
+ var model;
+ var datastore = new DataStore(post);
+ model = datastore.entity('abc', {name: "value"})();
+ model.$id = "123";
+ model.$version = "1";
+
+ datastore.remove(model, function(obj){
+ assertEquals(obj.$id, "123");
+ assertEquals(obj.$version, "2");
+ assertEquals(obj.name, "value");
+ obj.after = true;
+ });
+ datastore.flush();
+
+};
+
+
+DataStoreTest.prototype.test401ResponseDoesNotCallCallback = function(){
+ expectAsserts(1);
+ var post = function(data, callback) {
+ callback(200, {$status_code: 401});
+ };
+
+ var datastore = new DataStore(post, {login:function(){
+ assertTrue(true);
+ }});
+
+ var onLoadAll = function(){
+ assertTrue(false, "onLoadAll should not be called when response is status 401");
+ };
+ datastore.bulkRequest.push({});
+ datastore.flush();
+ datastore.loadAll({type: "A"}, onLoadAll);
+};
+
+DataStoreTest.prototype.test403ResponseDoesNotCallCallback = function(){
+ expectAsserts(1);
+ var post = function(data, callback) {
+ callback(200, [{$status_code: 403}]);
+ };
+
+ var datastore = new DataStore(post, {notAuthorized:function(){
+ assertTrue(true);
+ }});
+
+ var onLoadAll = function(){
+ assertTrue(false, "onLoadAll should not be called when response is status 403");
+ };
+ datastore.bulkRequest.push({});
+ datastore.flush();
+ datastore.loadAll({type: "A"}, onLoadAll);
+};
+
+DataStoreTest.prototype.testLoadCalledWithoutIdShouldBeNoop = function(){
+ expectAsserts(2);
+ var post = function(url, callback){
+ assertTrue(false);
+ };
+ var datastore = new DataStore(post);
+ var model = datastore.entity("abc")();
+ assertEquals(datastore.load(model, undefined), model);
+ assertEquals(model.$entity, "abc");
+};
+
+DataStoreTest.prototype.testEntityFactory = function(){
+ var ds = new DataStore();
+ var Recipe = ds.entity("Recipe", {a:1, b:2});
+ assertEquals(Recipe.title, "Recipe");
+ assertEquals(Recipe.defaults.a, 1);
+ assertEquals(Recipe.defaults.b, 2);
+
+ var recipe = Recipe();
+ assertEquals(recipe.$entity, "Recipe");
+ assertEquals(recipe.a, 1);
+ assertEquals(recipe.b, 2);
+
+ recipe = new Recipe();
+ assertEquals(recipe.$entity, "Recipe");
+ assertEquals(recipe.a, 1);
+ assertEquals(recipe.b, 2);
+};
+
+DataStoreTest.prototype.testEntityFactoryNoDefaults = function(){
+ var ds = new DataStore();
+ var Recipe = ds.entity("Recipe");
+ assertEquals(Recipe.title, "Recipe");
+
+ recipe = new Recipe();
+ assertEquals(recipe.$entity, "Recipe");
+};
+
+DataStoreTest.prototype.testEntityFactoryWithInitialValues = function(){
+ var ds = new DataStore();
+ var Recipe = ds.entity("Recipe");
+
+ var recipe = Recipe({name: "name"});
+ assertEquals("name", recipe.name);
+};
+
+DataStoreTest.prototype.testEntityLoad = function(){
+ var ds = new DataStore();
+ var Recipe = ds.entity("Recipe", {a:1, b:2});
+ ds.load = function(instance, id, callback){
+ callback.apply(instance);
+ return instance;
+ };
+ var instance = null;
+ var recipe2 = Recipe.load("ID", function(){
+ instance = this;
+ });
+ assertTrue(recipe2 === instance);
+};
+
+DataStoreTest.prototype.testSaveScope = function(){
+ var ds = new DataStore();
+ var log = "";
+ var Person = ds.entity("Person");
+ var person1 = Person({name:"A", $entity:"Person", $id:"1", $version:"1"}, ds);
+ person1.$$anchor = "A";
+ var person2 = Person({name:"B", $entity:"Person", $id:"2", $version:"2"}, ds);
+ person2.$$anchor = "B";
+ var anchor = {};
+ ds.anchor = anchor;
+ ds._jsonRequest = function(request, callback){
+ log += "save(" + request[2].$id + ");";
+ callback({$id:request[2].$id});
+ };
+ ds.saveScope({person1:person1, person2:person2,
+ ignoreMe:{name: "ignore", save:function(callback){callback();}}}, function(){
+ log += "done();";
+ });
+ assertEquals("save(1);save(2);done();", log);
+ assertEquals(1, anchor.A);
+ assertEquals(2, anchor.B);
+};
+
+DataStoreTest.prototype.testEntityLoadAllRows = function(){
+ var ds = new DataStore();
+ var Recipe = ds.entity("Recipe");
+ var list = [];
+ ds.loadAll = function(entity, callback){
+ assertTrue(Recipe === entity);
+ callback.apply(list);
+ return list;
+ };
+ var items = Recipe.all(function(){
+ assertTrue(list === this);
+ });
+ assertTrue(items === list);
+};
+
+DataStoreTest.prototype.testLoadAll = function(){
+ expectAsserts(8);
+ var post = function(data, callback){
+ assertEquals("GET", data[0][0]);
+ assertEquals("A", data[0][1]);
+ callback(200, [[{$entity:'A', $id:'1'},{$entity:'A', $id:'2'}]]);
+ };
+ var datastore = new DataStore(post);
+ var list = datastore.entity("A").all(function(){
+ assertTrue(true);
+ });
+ datastore.flush();
+ assertEquals(list.length, 2);
+ assertEquals(list[0].$entity, "A");
+ assertEquals(list[0].$id, "1");
+ assertEquals(list[1].$entity, "A");
+ assertEquals(list[1].$id, "2");
+};
+
+DataStoreTest.prototype.testQuery = function(){
+ expectAsserts(5);
+ var post = function(data, callback) {
+ assertEquals("GET", data[0][0]);
+ assertEquals("Employee/managerId=123abc", data[0][1]);
+ callback(200, [[{$entity:"Employee", $id: "456", managerId: "123ABC"}]]);
+
+ };
+ var datastore = new DataStore(post);
+ var Employee = datastore.entity("Employee");
+ var list = Employee.query('managerId', "123abc", function(){
+ assertTrue(true);
+ });
+ datastore.flush();
+ assertJsonEquals([[{$entity:"Employee", $id: "456", managerId: "123ABC"}]], datastore._cache.$collections);
+ assertEquals(list[0].$id, "456");
+};
+
+DataStoreTest.prototype.testLoadingDocumentRefreshesExistingArrays = function() {
+ expectAsserts(12);
+ var post;
+ var datastore = new DataStore(function(r, c){post(r,c);});
+ var Book = datastore.entity('Book');
+ post = function(req, callback) {
+ callback(200, [[{$id:1, $entity:"Book", name:"Moby"},
+ {$id:2, $entity:"Book", name:"Dick"}]]);
+ };
+ var allBooks = Book.all();
+ datastore.flush();
+ var queryBooks = Book.query("a", "b");
+ datastore.flush();
+ assertEquals("Moby", allBooks[0].name);
+ assertEquals("Dick", allBooks[1].name);
+ assertEquals("Moby", queryBooks[0].name);
+ assertEquals("Dick", queryBooks[1].name);
+
+ post = function(req, callback) {
+ assertEquals('[["GET","Book/1"]]', toJson(req));
+ callback(200, [{$id:1, $entity:"Book", name:"Moby Dick"}]);
+ };
+ var book = Book.load(1);
+ datastore.flush();
+ assertEquals("Moby Dick", book.name);
+ assertEquals("Moby Dick", allBooks[0].name);
+ assertEquals("Moby Dick", queryBooks[0].name);
+
+ post = function(req, callback) {
+ assertEquals('POST', req[0][0]);
+ callback(200, [{$id:1, $entity:"Book", name:"The Big Fish"}]);
+ };
+ book.$save();
+ datastore.flush();
+ assertEquals("The Big Fish", book.name);
+ assertEquals("The Big Fish", allBooks[0].name);
+ assertEquals("The Big Fish", queryBooks[0].name);
+};
+
+DataStoreTest.prototype.testEntityProperties = function() {
+ expectAsserts(2);
+ var datastore = new DataStore();
+ var callback = {};
+
+ datastore._jsonRequest = function(request, callbackFn) {
+ assertJsonEquals(["GET", "Cheese/$properties"], request);
+ assertEquals(callback, callbackFn);
+ };
+
+ var Cheese = datastore.entity("Cheese");
+ Cheese.properties(callback);
+
+};
+
+DataStoreTest.prototype.testLoadInstanceIsNotFromCache = function() {
+ var post;
+ var datastore = new DataStore(function(r, c){post(r,c);});
+ var Book = datastore.entity('Book');
+
+ post = function(req, callback) {
+ assertEquals('[["GET","Book/1"]]', toJson(req));
+ callback(200, [{$id:1, $entity:"Book", name:"Moby Dick"}]);
+ };
+ var book = Book.load(1);
+ datastore.flush();
+ assertEquals("Moby Dick", book.name);
+ assertFalse(book === datastore._cache['Book/1']);
+};
+
+DataStoreTest.prototype.testLoadStarsIsNewDocument = function() {
+ var datastore = new DataStore();
+ var Book = datastore.entity('Book');
+ var book = Book.load('*');
+ assertEquals('Book', book.$entity);
+};
+
+DataStoreTest.prototype.testUndefinedEntityReturnsNullValueObject = function() {
+ var datastore = new DataStore();
+ var Entity = datastore.entity(undefined);
+ var all = Entity.all();
+ assertEquals(0, all.length);
+};
+
+DataStoreTest.prototype.testFetchEntities = function(){
+ expectAsserts(6);
+ var post = function(data, callback){
+ assertJsonEquals(["GET", "$entities"], data[0]);
+ callback(200, [{A:0, B:0}]);
+ };
+ var datastore = new DataStore(post);
+ var entities = datastore.entities(function(){
+ assertTrue(true);
+ });
+ datastore.flush();
+ assertJsonEquals([], datastore.bulkRequest);
+ assertEquals(2, entities.length);
+ assertEquals("A", entities[0].title);
+ assertEquals("B", entities[1].title);
+};
+
+DataStoreTest.prototype.testItShouldMigrateSchema = function() {
+ var datastore = new DataStore();
+ var Entity = datastore.entity("Entity", {a:[], user:{name:"Misko", email:""}});
+ var doc = Entity().$loadFrom({b:'abc', user:{email:"misko@hevery.com"}});
+ assertFalse(
+ toJson({a:[], b:'abc', user:{name:"Misko", email:"misko@hevery.com"}}) ==
+ toJson(doc));
+ doc.$migrate();
+ assertEquals(
+ toJson({a:[], b:'abc', user:{name:"Misko", email:"misko@hevery.com"}}),
+ toJson(doc));
+};
+
+DataStoreTest.prototype.testItShouldCollectRequestsForBulk = function() {
+ var ds = new DataStore();
+ var Book = ds.entity("Book");
+ var Library = ds.entity("Library");
+ Book.all();
+ Library.load("123");
+ assertEquals(2, ds.bulkRequest.length);
+ assertJsonEquals(["GET", "Book"], ds.bulkRequest[0]);
+ assertJsonEquals(["GET", "Library/123"], ds.bulkRequest[1]);
+};
+
+DataStoreTest.prototype.testEmptyFlushShouldDoNothing = function () {
+ var ds = new DataStore(function(){
+ fail("expecting noop");
+ });
+ ds.flush();
+};
+
+DataStoreTest.prototype.testFlushShouldCallAllCallbacks = function() {
+ var log = "";
+ function post(request, callback){
+ log += 'BulkRequest:' + toJson(request) + ';';
+ callback(200, [[{$id:'ABC'}], {$id:'XYZ'}]);
+ }
+ var ds = new DataStore(post);
+ var Book = ds.entity("Book");
+ var Library = ds.entity("Library");
+ Book.all(function(instance){
+ log += toJson(instance) + ';';
+ });
+ Library.load("123", function(instance){
+ log += toJson(instance) + ';';
+ });
+ assertEquals("", log);
+ ds.flush();
+ assertJsonEquals([], ds.bulkRequest);
+ assertEquals('BulkRequest:[["GET","Book"],["GET","Library/123"]];[{"$id":"ABC"}];{"$id":"XYZ"};', log);
+};
+
+DataStoreTest.prototype.testSaveOnNotLoggedInRetriesAfterLoggin = function(){
+ var log = "";
+ var book;
+ var ds = new DataStore(null, {login:function(c){c();}});
+ ds.post = function (request, callback){
+ assertJsonEquals([["POST", "", book]], request);
+ ds.post = function(request, callback){
+ assertJsonEquals([["POST", "", book]], request);
+ ds.post = function(){fail("too much recursion");};
+ callback(200, [{saved:"ok"}]);
+ };
+ callback(200, {$status_code:401});
+ };
+ book = ds.entity("Book")({name:"misko"});
+ book.$save();
+ ds.flush();
+ assertJsonEquals({saved:"ok"}, book);
+};
+
+DataStoreTest.prototype.testItShouldRemoveItemFromCollectionWhenDeleted = function() {
+ expectAsserts(6);
+ var ds = new DataStore();
+ ds.post = function(request, callback){
+ assertJsonEquals([["GET", "Book"]], request);
+ callback(200, [[{name:"Moby Dick", $id:123, $entity:'Book'}]]);
+ };
+ var Book = ds.entity("Book");
+ var books = Book.all();
+ ds.flush();
+ assertJsonEquals([[{name:"Moby Dick", $id:123, $entity:'Book'}]], ds._cache.$collections);
+ assertDefined(ds._cache['Book/123']);
+ var book = Book({$id:123});
+ ds.post = function(request, callback){
+ assertJsonEquals([["DELETE", "", book]], request);
+ callback(200, [book]);
+ };
+ ds.remove(book);
+ ds.flush();
+ assertUndefined(ds._cache['Book/123']);
+ assertJsonEquals([[]],ds._cache.$collections);
+};
+
+DataStoreTest.prototype.testItShouldAddToAll = function() {
+ expectAsserts(8);
+ var ds = new DataStore();
+ ds.post = function(request, callback){
+ assertJsonEquals([["GET", "Book"]], request);
+ callback(200, [[]]);
+ };
+ var Book = ds.entity("Book");
+ var books = Book.all();
+ assertEquals(0, books.length);
+ ds.flush();
+ var moby = Book({name:'moby'});
+ moby.$save();
+ ds.post = function(request, callback){
+ assertJsonEquals([["POST", "", moby]], request);
+ moby.$id = '123';
+ callback(200, [moby]);
+ };
+ ds.flush();
+ assertEquals(1, books.length);
+ assertEquals(moby, books[0]);
+
+ moby.$save();
+ ds.flush();
+ assertEquals(1, books.length);
+ assertEquals(moby, books[0]);
+};
+
+DataStoreTest.prototype.testItShouldReturnCreatedDocumentCountByUser = function(){
+ expectAsserts(2);
+ var datastore = new DataStore(
+ function(request, callback){
+ assertJsonEquals([["GET", "$users"]], request);
+ callback(200, [{misko:1, adam:1}]);
+ });
+ var users = datastore.documentCountsByUser();
+ assertJsonEquals({misko:1, adam:1}, users);
+};
+
+
+DataStoreTest.prototype.testItShouldReturnDocumentIdsForUeserByEntity = function(){
+ expectAsserts(2);
+ var datastore = new DataStore(
+ function(request, callback){
+ assertJsonEquals([["GET", "$users/misko@hevery.com"]], request);
+ callback(200, [{Book:["1"], Library:["2"]}]);
+ });
+ var users = datastore.userDocumentIdsByEntity("misko@hevery.com");
+ assertJsonEquals({Book:["1"], Library:["2"]}, users);
+};
+
+DataStoreTest.prototype.testItShouldReturnNewInstanceOn404 = function(){
+ expectAsserts(7);
+ var log = "";
+ var datastore = new DataStore(
+ function(request, callback){
+ assertJsonEquals([["GET", "User/misko"]], request);
+ callback(200, [{$status_code:404}]);
+ });
+ var User = datastore.entity("User", {admin:false});
+ var user = User.loadOrCreate('misko', function(i){log+="cb "+i.$id+";";});
+ datastore.flush();
+ assertEquals("misko", user.$id);
+ assertEquals("User", user.$entity);
+ assertEquals(false, user.admin);
+ assertEquals("undefined", typeof user.$secret);
+ assertEquals("undefined", typeof user.$version);
+ assertEquals("cb misko;", log);
+};
+
+DataStoreTest.prototype.testItShouldReturnNewInstanceOn404 = function(){
+ var log = "";
+ var datastore = new DataStore(
+ function(request, callback){
+ assertJsonEquals([["GET", "User/misko"],["GET", "User/adam"]], request);
+ callback(200, [{$id:'misko'},{$id:'adam'}]);
+ });
+ var User = datastore.entity("User");
+ var users = User.loadMany(['misko', 'adam'], function(i){log+="cb "+toJson(i)+";";});
+ datastore.flush();
+ assertEquals("misko", users[0].$id);
+ assertEquals("adam", users[1].$id);
+ assertEquals('cb [{"$id":"misko"},{"$id":"adam"}];', log);
+};
+
+DataStoreTest.prototype.testItShouldCreateJoinAndQuery = function() {
+ var datastore = new DataStore();
+ var Invoice = datastore.entity("Invoice");
+ var Customer = datastore.entity("Customer");
+ var InvoiceWithCustomer = datastore.join({
+ invoice:{join:Invoice},
+ customer:{join:Customer, on:"invoice.customer"}
+ });
+ var invoiceWithCustomer = InvoiceWithCustomer.query("invoice.month", 1);
+ assertEquals([], invoiceWithCustomer);
+ assertJsonEquals([["GET", "Invoice/month=1"]], datastore.bulkRequest);
+ var request = datastore.bulkRequest.shift();
+ request.$$callback([{$id:1, customer:1},{$id:2, customer:1},{$id:3, customer:3}]);
+ assertJsonEquals([["GET","Customer/1"],["GET","Customer/3"]], datastore.bulkRequest);
+ datastore.bulkRequest.shift().$$callback({$id:1});
+ datastore.bulkRequest.shift().$$callback({$id:3});
+ assertJsonEquals([
+ {invoice:{$id:1,customer:1},customer:{$id:1}},
+ {invoice:{$id:2,customer:1},customer:{$id:1}},
+ {invoice:{$id:3,customer:3},customer:{$id:3}}], invoiceWithCustomer);
+};
+
+DataStoreTest.prototype.testItShouldThrowIfMoreThanOneEntityIsPrimary = function() {
+ var datastore = new DataStore();
+ var Invoice = datastore.entity("Invoice");
+ var Customer = datastore.entity("Customer");
+ assertThrows("Exactly one entity needs to be primary.", function(){
+ datastore.join({
+ invoice:{join:Invoice},
+ customer:{join:Customer}
+ });
+ });
+};
+
+DataStoreTest.prototype.testItShouldThrowIfLoopInReferences = function() {
+ var datastore = new DataStore();
+ var Invoice = datastore.entity("Invoice");
+ var Customer = datastore.entity("Customer");
+ assertThrows("Infinite loop in join: invoice -> customer", function(){
+ datastore.join({
+ invoice:{join:Invoice, on:"customer.invoice"},
+ customer:{join:Customer, on:"invoice.customer"}
+ });
+ });
+};
+
+DataStoreTest.prototype.testItShouldThrowIfReferenceToNonExistantJoin = function() {
+ var datastore = new DataStore();
+ var Invoice = datastore.entity("Invoice");
+ var Customer = datastore.entity("Customer");
+ assertThrows("Named entity 'x' is undefined.", function(){
+ datastore.join({
+ invoice:{join:Invoice, on:"x.invoice"},
+ customer:{join:Customer, on:"invoice.customer"}
+ });
+ });
+};
+
+DataStoreTest.prototype.testItShouldThrowIfQueryOnNonPrimary = function() {
+ var datastore = new DataStore();
+ var Invoice = datastore.entity("Invoice");
+ var Customer = datastore.entity("Customer");
+ var InvoiceWithCustomer = datastore.join({
+ invoice:{join:Invoice},
+ customer:{join:Customer, on:"invoice.customer"}
+ });
+ assertThrows("Named entity 'customer' is not a primary entity.", function(){
+ InvoiceWithCustomer.query("customer.month", 1);
+ });
+};
diff --git a/test/moveToAngularCom/EntityDeclarationTest.js b/test/moveToAngularCom/EntityDeclarationTest.js
new file mode 100644
index 00000000..28986ea8
--- /dev/null
+++ b/test/moveToAngularCom/EntityDeclarationTest.js
@@ -0,0 +1,50 @@
+EntityDeclarationTest = TestCase('EntityDeclarationTest');
+
+EntityDeclarationTest.prototype.testEntityTypeOnly = function(){
+ expectAsserts(2);
+ var datastore = {entity:function(name){
+ assertEquals("Person", name);
+ }};
+ var scope = new Scope();
+ var init = scope.entity("Person", datastore);
+ assertEquals("", init);
+};
+
+EntityDeclarationTest.prototype.testWithDefaults = function(){
+ expectAsserts(4);
+ var datastore = {entity:function(name, init){
+ assertEquals("Person", name);
+ assertEquals("=a:", init.a);
+ assertEquals(0, init.b.length);
+ }};
+ var scope = new Scope();
+ var init = scope.entity('Person:{a:"=a:", b:[]}', datastore);
+ assertEquals("", init);
+};
+
+EntityDeclarationTest.prototype.testWithName = function(){
+ expectAsserts(2);
+ var datastore = {entity:function(name, init){
+ assertEquals("Person", name);
+ return function (){ return {}; };
+ }};
+ var scope = new Scope();
+ var init = scope.entity('friend=Person', datastore);
+ assertEquals("$anchor.friend:{friend=Person.load($anchor.friend);friend.$$anchor=\"friend\";};", init);
+};
+
+EntityDeclarationTest.prototype.testMultipleEntities = function(){
+ expectAsserts(3);
+ var expect = ['Person', 'Book'];
+ var i=0;
+ var datastore = {entity:function(name, init){
+ assertEquals(expect[i], name);
+ i++;
+ return function (){ return {}; };
+ }};
+ var scope = new Scope();
+ var init = scope.entity('friend=Person;book=Book;', datastore);
+ assertEquals("$anchor.friend:{friend=Person.load($anchor.friend);friend.$$anchor=\"friend\";};" +
+ "$anchor.book:{book=Book.load($anchor.book);book.$$anchor=\"book\";};",
+ init);
+};
diff --git a/test/moveToAngularCom/FileControllerTest.js b/test/moveToAngularCom/FileControllerTest.js
new file mode 100644
index 00000000..75c924e6
--- /dev/null
+++ b/test/moveToAngularCom/FileControllerTest.js
@@ -0,0 +1,98 @@
+FileControllerTest = TestCase('FileControllerTest');
+
+FileControllerTest.prototype.XtestOnSelectUpdateView = function(){
+ var view = jQuery('<span><a/><span/></span>');
+ var swf = {};
+ var controller = new FileController(view, null, swf);
+ swf.uploadFile = function(path){};
+ controller.select('A', 9, '9 bytes');
+ assertEquals(view.find('a').text(), "A");
+ assertEquals(view.find('span').text(), "9 bytes");
+};
+
+FileControllerTest.prototype.XtestUpdateModelView = function(){
+ var view = FileController.template('');
+ var input = $('<input name="value.input">');
+ var controller;
+ var scope = new Scope({value:{}, $binder:{updateView:function(){
+ controller.updateView(scope);
+ }}});
+ view.data('scope', scope);
+ controller = new FileController(view, 'value.input', null, "http://server_base");
+ var value = '{"text":"A", "size":123, "id":"890"}';
+ controller.uploadCompleteData(value);
+ controller.updateView(scope);
+ assertEquals(scope.get('value.input.text'), 'A');
+ assertEquals(scope.get('value.input.size'), 123);
+ assertEquals(scope.get('value.input.id'), '890');
+ assertEquals(scope.get('value.input.url'), 'http://server_base/_attachments/890/A');
+ assertEquals(view.find('a').text(), "A");
+ assertEquals(view.find('a').attr('href'), "http://server_base/_attachments/890/A");
+ assertEquals(view.find('span').text(), "123 bytes");
+};
+
+FileControllerTest.prototype.XtestFileUpload = function(){
+ expectAsserts(1);
+ var swf = {};
+ var controller = new FileController(null, null, swf, "http://server_base");
+ swf.uploadFile = function(path){
+ assertEquals("http://server_base/_attachments", path);
+ };
+ controller.name = "Name";
+ controller.upload();
+};
+
+FileControllerTest.prototype.XtestFileUploadNoFileIsNoop = function(){
+ expectAsserts(0);
+ var swf = {uploadFile:function(path){
+ fail();
+ }};
+ var controller = new FileController(null, swf);
+ controller.upload("basePath", null);
+};
+
+FileControllerTest.prototype.XtestRemoveAttachment = function(){
+ var doc = FileController.template();
+ var input = $('<input name="file">');
+ var scope = new Scope();
+ input.data('scope', scope);
+ var controller = new FileController(doc, 'file', null, null);
+ controller.updateView(scope);
+ assertEquals(false, doc.find('input').attr('checked'));
+
+ scope.set('file', {url:'url', size:123});
+ controller.updateView(scope);
+ assertEquals(true, doc.find('input').attr('checked'));
+
+ doc.find('input').attr('checked', false);
+ controller.updateModel(scope);
+ assertNull(scope.get('file'));
+
+ doc.find('input').attr('checked', true);
+ controller.updateModel(scope);
+ assertEquals('url', scope.get('file.url'));
+ assertEquals(123, scope.get('file.size'));
+};
+
+FileControllerTest.prototype.XtestShouldEmptyOutOnUndefined = function () {
+ var view = FileController.template('hello');
+ var controller = new FileController(view, 'abc', null, null);
+
+ var scope = new Scope();
+ scope.set('abc', {text: 'myname', url: 'myurl', size: 1234});
+
+ controller.updateView(scope);
+ assertEquals("myurl", view.find('a').attr('href'));
+ assertEquals("myname", view.find('a').text());
+ assertEquals(true, view.find('input').is(':checked'));
+ assertEquals("1.2 KB", view.find('span').text());
+
+ scope.set('abc', undefined);
+ controller.updateView(scope);
+ assertEquals("myurl", view.find('a').attr('href'));
+ assertEquals("myname", view.find('a').text());
+ assertEquals(false, view.find('input').is(':checked'));
+ assertEquals("1.2 KB", view.find('span').text());
+};
+
+
diff --git a/test/moveToAngularCom/MiscTest.js b/test/moveToAngularCom/MiscTest.js
new file mode 100644
index 00000000..aa0e1186
--- /dev/null
+++ b/test/moveToAngularCom/MiscTest.js
@@ -0,0 +1,35 @@
+BinderTest.prototype.testExpandEntityTagWithName = function(){
+ var c = this.compile('<div ng-entity="friend=Person"/>');
+ assertEquals(
+ '<div ng-entity="friend=Person" ng-watch="$anchor.friend:{friend=Person.load($anchor.friend);friend.$$anchor=\"friend\";};"></div>',
+ sortedHtml(c.node));
+ assertEquals("Person", c.scope.$get("friend.$entity"));
+ assertEquals("friend", c.scope.$get("friend.$$anchor"));
+};
+
+BinderTest.prototype.testExpandSubmitButtonToAction = function(){
+ var html = this.compileToHtml('<input type="submit" value="Save">');
+ assertTrue(html, html.indexOf('ng-action="$save()"') > 0 );
+ assertTrue(html, html.indexOf('ng-bind-attr="{"disabled":"{{$invalidWidgets}}"}"') > 0 );
+};
+
+BinderTest.prototype.testReplaceFileUploadWithSwf = function(){
+ expectAsserts(1);
+ var form = jQuery("body").append('<div id="testTag"><input type="file"></div>');
+ form.data('scope', new Scope());
+ var factory = {};
+ var binder = new Binder(form.get(0), factory, new MockLocation());
+ factory.createController = function(node){
+ assertEquals(node.attr('type'), 'file');
+ return {updateModel:function(){}};
+ };
+ binder.compile();
+ jQuery("#testTag").remove();
+};
+
+BinderTest.prototype.testExpandEntityTagWithDefaults = function(){
+ assertEquals(
+ '<div ng-entity="Person:{a:\"a\"}" ng-watch=""></div>',
+ this.compileToHtml('<div ng-entity=\'Person:{a:"a"}\'/>'));
+};
+
diff --git a/test/moveToAngularCom/ModelTest.js b/test/moveToAngularCom/ModelTest.js
new file mode 100644
index 00000000..dbd97778
--- /dev/null
+++ b/test/moveToAngularCom/ModelTest.js
@@ -0,0 +1,84 @@
+ModelTest = TestCase('ModelTest');
+
+ModelTest.prototype.testLoadSaveOperations = function(){
+ var m1 = new DataStore().entity('A')();
+ m1.a = 1;
+
+ var m2 = {b:1};
+
+ m1.$loadFrom(m2);
+
+ assertTrue(!m1.a);
+ assertEquals(m1.b, 1);
+};
+
+ModelTest.prototype.testLoadfromDoesNotClobberFunctions = function(){
+ var m1 = new DataStore().entity('A')();
+ m1.id = function(){return 'OK';};
+ m1.$loadFrom({id:null});
+ assertEquals(m1.id(), 'OK');
+
+ m1.b = 'OK';
+ m1.$loadFrom({b:function(){}});
+ assertEquals(m1.b, 'OK');
+};
+
+ModelTest.prototype.testDataStoreDoesNotGetClobbered = function(){
+ var ds = new DataStore();
+ var m = ds.entity('A')();
+ assertTrue(m.$$entity.datastore === ds);
+ m.$loadFrom({});
+ assertTrue(m.$$entity.datastore === ds);
+};
+
+ModelTest.prototype.testManagedModelDelegatesMethodsToDataStore = function(){
+ expectAsserts(7);
+ var datastore = new DataStore();
+ var model = datastore.entity("A", {a:1})();
+ var fn = {};
+ datastore.save = function(instance, callback) {
+ assertTrue(model === instance);
+ assertTrue(callback === fn);
+ };
+ datastore.remove = function(instance, callback) {
+ assertTrue(model === instance);
+ assertTrue(callback === fn);
+ };
+ datastore.load = function(instance, id, callback) {
+ assertTrue(model === instance);
+ assertTrue(id === "123");
+ assertTrue(callback === fn);
+ };
+ model.$save(fn);
+ model.$delete(fn);
+ model.$loadById("123", fn);
+};
+
+ModelTest.prototype.testManagedModelCanBeForcedToFlush = function(){
+ expectAsserts(6);
+ var datastore = new DataStore();
+ var model = datastore.entity("A", {a:1})();
+
+ datastore.save = function(instance, callback) {
+ assertTrue(model === instance);
+ assertTrue(callback === undefined);
+ };
+ datastore.remove = function(instance, callback) {
+ assertTrue(model === instance);
+ assertTrue(callback === undefined);
+ };
+ datastore.flush = function(){
+ assertTrue(true);
+ };
+ model.$save(true);
+ model.$delete(true);
+};
+
+
+ModelTest.prototype.testItShouldMakeDeepCopyOfInitialValues = function (){
+ var initial = {a:[]};
+ var entity = new DataStore().entity("A", initial);
+ var model = entity();
+ model.a.push(1);
+ assertEquals(0, entity().a.length);
+};
diff --git a/test/moveToAngularCom/ServerTest.js b/test/moveToAngularCom/ServerTest.js
new file mode 100644
index 00000000..02fab84c
--- /dev/null
+++ b/test/moveToAngularCom/ServerTest.js
@@ -0,0 +1,42 @@
+ServerTest = TestCase("ServerTest");
+ServerTest.prototype.testBreakLargeRequestIntoPackets = function() {
+ var log = "";
+ var server = new Server("http://server", function(url){
+ log += "|" + url;
+ });
+ server.maxSize = 30;
+ server.uuid = "uuid";
+ server.request("POST", "/data/database", {}, function(code, r){
+ assertEquals(200, code);
+ assertEquals("response", r);
+ });
+ angularCallbacks.uuid0("response");
+ assertEquals(
+ "|http://server/$/uuid0/2/1?h=eyJtIjoiUE9TVCIsInAiOnt9LCJ1Ij" +
+ "|http://server/$/uuid0/2/2?h=oiL2RhdGEvZGF0YWJhc2UifQ==",
+ log);
+};
+
+ServerTest.prototype.testItShouldEncodeUsingUrlRules = function() {
+ var server = new Server("http://server");
+ assertEquals("fn5-fn5-", server.base64url("~~~~~~"));
+ assertEquals("fn5_fn5_", server.base64url("~~\u007f~~\u007f"));
+};
+
+FrameServerTest = TestCase("FrameServerTest");
+
+FrameServerTest.prototype = {
+ testRead:function(){
+ var window = {name:'$DATASET:"MyData"'};
+ var server = new FrameServer(window);
+ server.read();
+ assertEquals("MyData", server.data);
+ },
+ testWrite:function(){
+ var window = {};
+ var server = new FrameServer(window);
+ server.data = "TestData";
+ server.write();
+ assertEquals('$DATASET:"TestData"', window.name);
+ }
+};
diff --git a/test/moveToAngularCom/UsersTest.js b/test/moveToAngularCom/UsersTest.js
new file mode 100644
index 00000000..f0ff545a
--- /dev/null
+++ b/test/moveToAngularCom/UsersTest.js
@@ -0,0 +1,26 @@
+// Copyright (C) 2008,2009 BRAT Tech LLC
+
+UsersTest = TestCase("UsersTest");
+
+UsersTest.prototype = {
+ setUp:function(){},
+
+ tearDown:function(){},
+
+ testItShouldFetchCurrentUser:function(){
+ expectAsserts(5);
+ var user;
+ var users = new Users({request:function(method, url, request, callback){
+ assertEquals("GET", method);
+ assertEquals("/account.json", url);
+ assertEquals("{}", toJson(request));
+ callback(200, {$status_code:200, user:{name:'misko'}});
+ }});
+ users.fetchCurrentUser(function(u){
+ user = u;
+ assertEquals("misko", u.name);
+ assertEquals("misko", users.current.name);
+ });
+ }
+
+};
diff --git a/test/moveToAngularCom/miscTest.js b/test/moveToAngularCom/miscTest.js
new file mode 100644
index 00000000..aa0e1186
--- /dev/null
+++ b/test/moveToAngularCom/miscTest.js
@@ -0,0 +1,35 @@
+BinderTest.prototype.testExpandEntityTagWithName = function(){
+ var c = this.compile('<div ng-entity="friend=Person"/>');
+ assertEquals(
+ '<div ng-entity="friend=Person" ng-watch="$anchor.friend:{friend=Person.load($anchor.friend);friend.$$anchor=\"friend\";};"></div>',
+ sortedHtml(c.node));
+ assertEquals("Person", c.scope.$get("friend.$entity"));
+ assertEquals("friend", c.scope.$get("friend.$$anchor"));
+};
+
+BinderTest.prototype.testExpandSubmitButtonToAction = function(){
+ var html = this.compileToHtml('<input type="submit" value="Save">');
+ assertTrue(html, html.indexOf('ng-action="$save()"') > 0 );
+ assertTrue(html, html.indexOf('ng-bind-attr="{"disabled":"{{$invalidWidgets}}"}"') > 0 );
+};
+
+BinderTest.prototype.testReplaceFileUploadWithSwf = function(){
+ expectAsserts(1);
+ var form = jQuery("body").append('<div id="testTag"><input type="file"></div>');
+ form.data('scope', new Scope());
+ var factory = {};
+ var binder = new Binder(form.get(0), factory, new MockLocation());
+ factory.createController = function(node){
+ assertEquals(node.attr('type'), 'file');
+ return {updateModel:function(){}};
+ };
+ binder.compile();
+ jQuery("#testTag").remove();
+};
+
+BinderTest.prototype.testExpandEntityTagWithDefaults = function(){
+ assertEquals(
+ '<div ng-entity="Person:{a:\"a\"}" ng-watch=""></div>',
+ this.compileToHtml('<div ng-entity=\'Person:{a:"a"}\'/>'));
+};
+
diff --git a/test/scenario/DSLSpec.js b/test/scenario/DSLSpec.js
new file mode 100644
index 00000000..5aac9752
--- /dev/null
+++ b/test/scenario/DSLSpec.js
@@ -0,0 +1,55 @@
+describe("DSL", function() {
+
+ var lastStep, executeStep, lastDocument;
+
+ beforeEach(function() {
+ lastStep = null;
+ $scenario = {
+ addStep: function(name, stepFunction) {
+ lastStep = { name:name, fn: stepFunction};
+ }
+ };
+ executeStep = function(step, html, callback) {
+ lastDocument =_jQuery('<div>' + html + '</div>');
+ _jQuery(document.body).append(lastDocument);
+ var specThis = {
+ testWindow: window,
+ testDocument: lastDocument
+ };
+ step.fn.call(specThis, callback || noop);
+ };
+ });
+
+ describe("input", function() {
+
+ var input = angular.scenario.dsl.input;
+ it('should enter', function() {
+ input('name').enter('John');
+ expect(lastStep.name).toEqual("Set input text of 'name' to 'John'");
+ executeStep(lastStep, '<input type="text" name="name" />');
+ expect(lastDocument.find('input').val()).toEqual('John');
+ });
+
+ it('should select', function() {
+ input('gender').select('female');
+ expect(lastStep.name).toEqual("Select radio 'gender' to 'female'");
+ executeStep(lastStep,
+ '<input type="radio" name="0@gender" value="male" checked/>' +
+ '<input type="radio" name="0@gender" value="female"/>');
+ expect(lastDocument.find(':radio:checked').length).toEqual(1);
+ expect(lastDocument.find(':radio:checked').val()).toEqual('female');
+ });
+ });
+
+ describe('expect', function() {
+ var dslExpect = angular.scenario.dsl.expect;
+ describe('repeater', function() {
+ it('should check the count of repeated elements', function() {
+ dslExpect.repeater('.repeater-row').count.toEqual(2);
+ expect(lastStep.name).toEqual("Expect that there are 2 items in Repeater with selector '.repeater-row'");
+ var html = "<div class='repeater-row'>a</div><div class='repeater-row'>b</div>";
+ executeStep(lastStep, html);
+ });
+ });
+ });
+});
diff --git a/test/scenario/RunnerSpec.js b/test/scenario/RunnerSpec.js
new file mode 100644
index 00000000..ca6e8eb2
--- /dev/null
+++ b/test/scenario/RunnerSpec.js
@@ -0,0 +1,190 @@
+describe('Runner', function(){
+ var scenario, runner, log, Describe, It, $scenario, body;
+
+ function logger(text) {
+ return function(done){
+ log += text;
+ (done||noop)();
+ };
+ }
+
+ beforeEach(function(){
+ log = '';
+ scenario = {};
+ body = _jQuery('<div></div>');
+ runner = new angular.scenario.Runner(scenario, _jQuery);
+ Describe = scenario.describe;
+ BeforeEach = scenario.beforeEach;
+ AfterEach = scenario.afterEach;
+ It = scenario.it;
+ $scenario = scenario.$scenario;
+ });
+
+ describe('describe', function(){
+ it('should consume the describe functions', function(){
+ Describe('describe name', logger('body'));
+
+ expect(log).toEqual('body');
+ });
+
+ describe('it', function(){
+ it('should consume it', function(){
+ Describe('describe name', function(){
+ It('should text', logger('body'));
+ });
+ expect(log).toEqual('body');
+ var spec = $scenario.specs['describe name: it should text'];
+ expect(spec.steps).toEqual([]);
+ expect(spec.name).toEqual('describe name: it should text');
+ });
+
+ it('should complain on duplicate it', function() {
+ // WRITE ME!!!!
+ });
+
+ it('should create a failing step if there is a javascript error', function(){
+ var spec;
+ Describe('D1', function(){
+ It('I1', function(){
+ spec = $scenario.currentSpec;
+ throw {message: 'blah'};
+ });
+ });
+ var step = spec.steps[0];
+ expect(step.name).toEqual('blah');
+ try {
+ step.fn();
+ fail();
+ } catch (e) {
+ expect(e.message).toEqual('blah');
+ };
+ });
+ });
+
+ describe('beforeEach', function() {
+ it('should execute beforeEach before every it', function() {
+ Describe('describe name', function(){
+ BeforeEach(logger('before;'));
+ It('should text', logger('body;'));
+ It('should text2', logger('body2;'));
+ });
+ expect(log).toEqual('before;body;before;body2;');
+ });
+ });
+ describe('afterEach', function() {
+ it('should execute afterEach after every it', function() {
+ Describe('describe name', function(){
+ AfterEach(logger('after;'));
+ It('should text', logger('body;'));
+ It('should text2', logger('body2;'));
+ });
+ expect(log).toEqual('body;after;body2;after;');
+ });
+
+ it('should always execute afterEach after every it', function() {
+ Describe('describe name', function(){
+ AfterEach(logger('after;'));
+ It('should text', function() {
+ log = 'body;';
+ throw "MyError";
+ });
+ It('should text2', logger('body2;'));
+ });
+ expect(log).toEqual('body;after;body2;after;');
+ });
+ });
+ });
+
+ describe('steps building', function(){
+ it('should queue steps', function(){
+ function step(){};
+ Describe('name', function(){
+ It('should', function(){
+ $scenario.addStep('stepname', step);
+ });
+ });
+ expect($scenario.specs['name: it should'].steps).toEqual([{name:'stepname', fn:step}]);
+ });
+ });
+
+ describe('execution', function(){
+ it('should execute the queued steps', function(){
+ var next, firstThis, secondThis, doneThis, spec;
+ $scenario.specs['spec'] = {
+ steps: [
+ {name:'step1', fn: function(done) {
+ next = done;
+ log += 'first;';
+ firstThis = this;
+ }},
+ {name:'step2', fn:function(done){
+ next = done;
+ log += 'second;';
+ secondThis = this;
+ }}
+ ]
+ };
+
+ spec = $scenario.execute('spec', function(done){
+ log += 'done;';
+ doneThis = this;
+ });
+ expect(log).toEqual('first;');
+ next();
+ expect(log).toEqual('first;second;');
+ next();
+ expect(log).toEqual('first;second;done;');
+ expect(spec).not.toEqual(window);
+ expect(spec).toEqual(firstThis);
+ expect(spec).toEqual(secondThis);
+ expect(spec).toEqual(doneThis);
+
+ expect(spec.result.failed).toEqual(false);
+ expect(spec.result.finished).toEqual(true);
+ expect(spec.result.error).toBeUndefined();
+ expect(spec.result.passed).toEqual(true);
+ });
+
+ it('should handle exceptions in a step', function(){
+ $scenario.specs['spec'] = {
+ steps: [
+ {name:'error', fn:function(done) {
+ throw "MyError";
+ }}
+ ]
+ };
+
+ var spec = $scenario.execute('spec');
+
+ expect(spec.result.passed).toEqual(false);
+ expect(spec.result.failed).toEqual(true);
+ expect(spec.result.finished).toEqual(true);
+ expect(spec.result.error).toEqual("MyError");
+ });
+ });
+
+ describe('run', function(){
+ var next;
+ it('should execute all specs', function(){
+ Describe('d1', function(){
+ It('it1', function(){ $scenario.addStep('s1', logger('s1,')); });
+ It('it2', function(){
+ $scenario.addStep('s2', logger('s2,'));
+ $scenario.addStep('s2.2', function(done){ next = done; });
+ });
+ });
+ Describe('d2', function(){
+ It('it3', function(){ $scenario.addStep('s3', logger('s3,')); });
+ It('it4', function(){ $scenario.addStep('s4', logger('s4,')); });
+ });
+
+ $scenario.run(body);
+
+ expect(log).toEqual('s1,s2,');
+ next();
+ expect(log).toEqual('s1,s2,s3,s4,');
+
+ });
+ });
+
+}); \ No newline at end of file
diff --git a/test/servicesSpec.js b/test/servicesSpec.js
new file mode 100644
index 00000000..f679a39b
--- /dev/null
+++ b/test/servicesSpec.js
@@ -0,0 +1,362 @@
+describe("service", function(){
+ var scope, $xhrError, $log;
+
+ beforeEach(function(){
+ $xhrError = jasmine.createSpy('$xhr.error');
+ $log = {};
+ scope = createScope(null, angularService, {
+ '$xhr.error': $xhrError,
+ '$log': $log
+ });
+ });
+
+ afterEach(function(){
+ if (scope && scope.$element)
+ scope.$element.remove();
+ });
+
+
+
+ it("should inject $window", function(){
+ expect(scope.$window).toEqual(window);
+ });
+
+ xit('should add stylesheets', function(){
+ scope.$document = {
+ getElementsByTagName: function(name){
+ expect(name).toEqual('LINK');
+ return [];
+ }
+ };
+ scope.$document.addStyleSheet('css/angular.css');
+ });
+
+ describe("$log", function(){
+ it('should use console if present', function(){
+ function log(){};
+ function warn(){};
+ function info(){};
+ function error(){};
+ var scope = createScope(null, angularService, {$window: {console:{log:log, warn:warn, info:info, error:error}}});
+ expect(scope.$log.log).toEqual(log);
+ expect(scope.$log.warn).toEqual(warn);
+ expect(scope.$log.info).toEqual(info);
+ expect(scope.$log.error).toEqual(error);
+ });
+
+ it('should use console.log if other not present', function(){
+ function log(){};
+ var scope = createScope(null, angularService, {$window: {console:{log:log}}});
+ expect(scope.$log.log).toEqual(log);
+ expect(scope.$log.warn).toEqual(log);
+ expect(scope.$log.info).toEqual(log);
+ expect(scope.$log.error).toEqual(log);
+ });
+
+ it('should use noop if no console', function(){
+ var scope = createScope(null, angularService, {$window: {}});
+ expect(scope.$log.log).toEqual(noop);
+ expect(scope.$log.warn).toEqual(noop);
+ expect(scope.$log.info).toEqual(noop);
+ expect(scope.$log.error).toEqual(noop);
+ });
+ });
+
+ describe("$location", function(){
+ it("should inject $location", function(){
+ scope.$location.parse('http://host:123/p/a/t/h.html?query=value#path?key=value');
+ expect(scope.$location.href).toEqual("http://host:123/p/a/t/h.html?query=value#path?key=value");
+ expect(scope.$location.protocol).toEqual("http");
+ expect(scope.$location.host).toEqual("host");
+ expect(scope.$location.port).toEqual("123");
+ expect(scope.$location.path).toEqual("/p/a/t/h.html");
+ expect(scope.$location.search).toEqual({query:'value'});
+ expect(scope.$location.hash).toEqual('path?key=value');
+ expect(scope.$location.hashPath).toEqual('path');
+ expect(scope.$location.hashSearch).toEqual({key:'value'});
+
+ scope.$location.hashPath = 'page=http://path';
+ scope.$location.hashSearch = {k:'a=b'};
+
+ expect(scope.$location.toString()).toEqual('http://host:123/p/a/t/h.html?query=value#page=http://path?k=a%3Db');
+ });
+
+ it('should parse file://', function(){
+ scope.$location.parse('file:///Users/Shared/misko/work/angular.js/scenario/widgets.html');
+ expect(scope.$location.href).toEqual("file:///Users/Shared/misko/work/angular.js/scenario/widgets.html");
+ expect(scope.$location.protocol).toEqual("file");
+ expect(scope.$location.host).toEqual("");
+ expect(scope.$location.port).toEqual(null);
+ expect(scope.$location.path).toEqual("/Users/Shared/misko/work/angular.js/scenario/widgets.html");
+ expect(scope.$location.search).toEqual({});
+ expect(scope.$location.hash).toEqual('');
+ expect(scope.$location.hashPath).toEqual('');
+ expect(scope.$location.hashSearch).toEqual({});
+
+ expect(scope.$location.toString()).toEqual('file:///Users/Shared/misko/work/angular.js/scenario/widgets.html#');
+ });
+
+ it('should update url on hash change', function(){
+ scope.$location.parse('http://server/#path?a=b');
+ scope.$location.hash = '';
+ expect(scope.$location.toString()).toEqual('http://server/#');
+ expect(scope.$location.hashPath).toEqual('');
+ });
+
+ it('should update url on hashPath change', function(){
+ scope.$location.parse('http://server/#path?a=b');
+ scope.$location.hashPath = '';
+ expect(scope.$location.toString()).toEqual('http://server/#?a=b');
+ expect(scope.$location.hash).toEqual('?a=b');
+ });
+
+ it('should update hash before any processing', function(){
+ var scope = compile('<div>');
+ var log = '';
+ scope.$watch('$location.hash', function(){
+ log += this.$location.hashPath + ';';
+ });
+ expect(log).toEqual(';');
+
+ log = '';
+ scope.$location.hash = '/abc';
+ scope.$eval();
+ expect(log).toEqual('/abc;');
+ });
+
+ it("should parse url which contains - in host", function(){
+ scope.$location.parse('http://a-b1.c-d.09/path');
+ expect(scope.$location.href).toEqual('http://a-b1.c-d.09/path');
+ expect(scope.$location.protocol).toEqual('http');
+ expect(scope.$location.host).toEqual('a-b1.c-d.09');
+ expect(scope.$location.path).toEqual('/path');
+ });
+
+ });
+
+ describe("$invalidWidgets", function(){
+ it("should count number of invalid widgets", function(){
+ var scope = compile('<input name="price" ng-required ng-validate="number"></input>').$init();
+ expect(scope.$invalidWidgets.length).toEqual(1);
+ scope.price = 123;
+ scope.$eval();
+ expect(scope.$invalidWidgets.length).toEqual(0);
+ scope.$element.remove();
+ scope.price = 'abc';
+ scope.$eval();
+ expect(scope.$invalidWidgets.length).toEqual(1);
+
+ jqLite(document.body).append(scope.$element);
+ scope.$invalidWidgets.clearOrphans();
+ expect(scope.$invalidWidgets.length).toEqual(1);
+
+ jqLite(document.body).html('');
+ scope.$invalidWidgets.clearOrphans();
+ expect(scope.$invalidWidgets.length).toEqual(0);
+ });
+ });
+
+
+ describe("$route", function(){
+ it('should route and fire change event', function(){
+ var log = '';
+ function BookChapter() {
+ this.log = '<init>';
+ }
+ BookChapter.prototype.init = function(){
+ log += 'init();';
+ };
+ var scope = compile('<div></div>').$init();
+ scope.$route.when('/Book/:book/Chapter/:chapter', {controller: BookChapter, template:'Chapter.html'});
+ scope.$route.when('/Blank');
+ scope.$route.onChange(function(){
+ log += 'onChange();';
+ });
+ scope.$location.parse('http://server#/Book/Moby/Chapter/Intro?p=123');
+ scope.$eval();
+ expect(log).toEqual('onChange();init();');
+ expect(scope.$route.current.params).toEqual({book:'Moby', chapter:'Intro', p:'123'});
+ expect(scope.$route.current.scope.log).toEqual('<init>');
+ var lastId = scope.$route.current.scope.$id;
+
+ log = '';
+ scope.$location.parse('http://server#/Blank?ignore');
+ scope.$eval();
+ expect(log).toEqual('onChange();');
+ expect(scope.$route.current.params).toEqual({ignore:true});
+ expect(scope.$route.current.scope.$id).not.toEqual(lastId);
+
+ log = '';
+ scope.$location.parse('http://server#/NONE');
+ scope.$eval();
+ expect(log).toEqual('onChange();');
+ expect(scope.$route.current).toEqual(null);
+
+ scope.$route.when('/NONE', {template:'instant update'});
+ scope.$eval();
+ expect(scope.$route.current.template).toEqual('instant update');
+ });
+ });
+
+ describe('$resource', function(){
+ it('should publish to root scope', function(){
+ expect(scope.$resource).toBeTruthy();
+ });
+ });
+
+ describe('$xhr', function(){
+ var log, xhr;
+ function callback(code, response) {
+ expect(code).toEqual(200);
+ log = log + toJson(response) + ';';
+ }
+
+ beforeEach(function(){
+ log = '';
+ xhr = scope.$browser.xhr;
+ });
+
+ it('should forward the request to $browser and decode JSON', function(){
+ xhr.expectGET('/reqGET').respond('first');
+ xhr.expectGET('/reqGETjson').respond('["second"]');
+ xhr.expectPOST('/reqPOST', {post:'data'}).respond('third');
+
+ scope.$xhr('GET', '/reqGET', null, callback);
+ scope.$xhr('GET', '/reqGETjson', null, callback);
+ scope.$xhr('POST', '/reqPOST', {post:'data'}, callback);
+
+ xhr.flush();
+
+ expect(log).toEqual('"third";["second"];"first";');
+ });
+
+ it('should handle non 200 status codes by forwarding to error handler', function(){
+ xhr.expectPOST('/req', 'MyData').respond(500, 'MyError');
+ scope.$xhr('POST', '/req', 'MyData', callback);
+ xhr.flush();
+ var cb = $xhrError.mostRecentCall.args[0].callback;
+ expect(typeof cb).toEqual('function');
+ expect($xhrError).wasCalledWith(
+ {url:'/req', method:'POST', data:'MyData', callback:cb},
+ {status:500, body:'MyError'});
+ });
+
+ it('should handle exceptions in callback', function(){
+ $log.error = jasmine.createSpy('$log.error');
+ xhr.expectGET('/reqGET').respond('first');
+ scope.$xhr('GET', '/reqGET', null, function(){ throw "MyException"; });
+ xhr.flush();
+
+ expect($log.error).wasCalledWith("MyException");
+ });
+
+ describe('bulk', function(){
+ it('should collect requests', function(){
+ scope.$xhr.bulk.urls["/"] = {match:/.*/};
+ scope.$xhr.bulk('GET', '/req1', null, callback);
+ scope.$xhr.bulk('POST', '/req2', {post:'data'}, callback);
+
+ xhr.expectPOST('/', {
+ requests:[{method:'GET', url:'/req1', data: null},
+ {method:'POST', url:'/req2', data:{post:'data'} }]
+ }).respond([
+ {status:200, response:'first'},
+ {status:200, response:'second'}
+ ]);
+ scope.$xhr.bulk.flush(function(){ log += 'DONE';});
+ xhr.flush();
+ expect(log).toEqual('"first";"second";DONE');
+ });
+
+ it('should handle non 200 status code by forwarding to error handler', function(){
+ scope.$xhr.bulk.urls['/'] = {match:/.*/};
+ scope.$xhr.bulk('GET', '/req1', null, callback);
+ scope.$xhr.bulk('POST', '/req2', {post:'data'}, callback);
+
+ xhr.expectPOST('/', {
+ requests:[{method:'GET', url:'/req1', data: null},
+ {method:'POST', url:'/req2', data:{post:'data'} }]
+ }).respond([
+ {status:404, response:'NotFound'},
+ {status:200, response:'second'}
+ ]);
+ scope.$xhr.bulk.flush(function(){ log += 'DONE';});
+ xhr.flush();
+
+ expect($xhrError).wasCalled();
+ var cb = $xhrError.mostRecentCall.args[0].callback;
+ expect(typeof cb).toEqual('function');
+ expect($xhrError).wasCalledWith(
+ {url:'/req1', method:'GET', data:null, callback:cb},
+ {status:404, response:'NotFound'});
+
+ expect(log).toEqual('"second";DONE');
+ });
+ });
+
+ describe('cache', function(){
+ var cache;
+ beforeEach(function(){ cache = scope.$xhr.cache; });
+
+ it('should cache requests', function(){
+ xhr.expectGET('/url').respond('first');
+ cache('GET', '/url', null, callback);
+ xhr.flush();
+ xhr.expectGET('/url').respond('ERROR');
+ cache('GET', '/url', null, callback);
+ xhr.flush();
+ expect(log).toEqual('"first";"first";');
+ cache('GET', '/url', null, callback, false);
+ xhr.flush();
+ expect(log).toEqual('"first";"first";"first";');
+ });
+
+ it('should first return cache request, then return server request', function(){
+ xhr.expectGET('/url').respond('first');
+ cache('GET', '/url', null, callback, true);
+ xhr.flush();
+ xhr.expectGET('/url').respond('ERROR');
+ cache('GET', '/url', null, callback, true);
+ expect(log).toEqual('"first";"first";');
+ xhr.flush();
+ expect(log).toEqual('"first";"first";"ERROR";');
+ });
+
+ it('should serve requests from cache', function(){
+ cache.data.url = {value:'123'};
+ cache('GET', 'url', null, callback);
+ expect(log).toEqual('"123";');
+ cache('GET', 'url', null, callback, false);
+ expect(log).toEqual('"123";"123";');
+ });
+
+ it('should keep track of in flight requests and request only once', function(){
+ scope.$xhr.bulk.urls['/bulk'] = {
+ match:function(url){
+ return url == '/url';
+ }
+ };
+ xhr.expectPOST('/bulk', {
+ requests:[{method:'GET', url:'/url', data: null}]
+ }).respond([
+ {status:200, response:'123'}
+ ]);
+ cache('GET', '/url', null, callback);
+ cache('GET', '/url', null, callback);
+ cache.delegate.flush();
+ xhr.flush();
+ expect(log).toEqual('"123";"123";');
+ });
+
+ it('should clear cache on non GET', function(){
+ xhr.expectPOST('abc', {}).respond({});
+ cache.data.url = {value:123};
+ cache('POST', 'abc', {});
+ expect(cache.data.url).toBeUndefined();
+ });
+ });
+
+ });
+
+
+});
diff --git a/test/testabilityPatch.js b/test/testabilityPatch.js
new file mode 100644
index 00000000..d621b1f1
--- /dev/null
+++ b/test/testabilityPatch.js
@@ -0,0 +1,180 @@
+jstd = jstestdriver;
+dump = bind(jstd.console, jstd.console.log);
+
+beforeEach(function(){
+ this.addMatchers({
+ toBeInvalid: function(){
+ var element = jqLite(this.actual);
+ var hasClass = element.hasClass('ng-validation-error');
+ var validationError = element.attr('ng-validation-error');
+ this.message = function(){
+ if (!hasClass)
+ return "Expected class 'ng-validation-error' not found.";
+ return "Expected an error message, but none was found.";
+ };
+ return hasClass && validationError;
+ },
+
+ toBeValid: function(){
+ var element = jqLite(this.actual);
+ var hasClass = element.hasClass('ng-validation-error');
+ this.message = function(){
+ return "Expected to not have class 'ng-validation-error' but found.";
+ };
+ return !hasClass;
+ }
+ });
+});
+
+function nakedExpect(obj) {
+ return expect(angular.fromJson(angular.toJson(obj)));
+}
+
+function childNode(element, index) {
+ return jqLite(element[0].childNodes[index]);
+}
+
+extend(angular, {
+ 'bind': bind,
+ 'compile': compile,
+ 'copy': copy,
+ 'element': jqLite,
+ 'extend': extend,
+ 'foreach': foreach,
+ 'identity':identity,
+ 'isUndefined': isUndefined,
+ 'isDefined': isDefined,
+ 'isObject': isObject,
+ 'isString': isString,
+ 'isFunction': isFunction,
+ 'isNumber': isNumber,
+ 'isArray': isArray,
+ 'noop':noop,
+ 'scope': createScope
+});
+
+
+function sortedHtml(element) {
+ var html = "";
+ foreach(element, function toString(node) {
+ if (node.nodeName == "#text") {
+ html += escapeHtml(node.nodeValue);
+ } else {
+ html += '<' + node.nodeName.toLowerCase();
+ var attributes = node.attributes || [];
+ var attrs = [];
+ for(var i=0; i<attributes.length; i++) {
+ var attr = attributes[i];
+ if(attr.name.match(/^ng-/) ||
+ attr.value &&
+ attr.value !='null' &&
+ attr.value !='auto' &&
+ attr.value !='false' &&
+ attr.value !='inherit' &&
+ attr.value !='0' &&
+ attr.name !='loop' &&
+ attr.name !='complete' &&
+ attr.name !='maxLength' &&
+ attr.name !='size' &&
+ attr.name !='start' &&
+ attr.name !='tabIndex' &&
+ attr.name !='style' &&
+ attr.name.substr(0, 6) != 'jQuery') {
+ // in IE we need to check for all of these.
+ if (!/ng-\d+/.exec(attr.name))
+ attrs.push(' ' + attr.name + '="' + attr.value + '"');
+ }
+ }
+ attrs.sort();
+ html += attrs.join('');
+ if (node.style) {
+ var style = [];
+ if (node.style.cssText) {
+ foreach(node.style.cssText.split(';'), function(value){
+ value = trim(value);
+ if (value) {
+ style.push(lowercase(value));
+ }
+ });
+ }
+ for(var css in node.style){
+ var value = node.style[css];
+ if (isString(value) && isString(css) && css != 'cssText' && value && (1*css != css)) {
+ var text = lowercase(css + ': ' + value);
+ if (value != 'false' && indexOf(style, text) == -1) {
+ style.push(text);
+ }
+ }
+ }
+ style.sort();
+ var tmp = style;
+ style = [];
+ foreach(tmp, function(value){
+ if (!value.match(/^max[^\-]/))
+ style.push(value);
+ });
+ if (style.length) {
+ html += ' style="' + style.join('; ') + ';"';
+ }
+ }
+ html += '>';
+ var children = node.childNodes;
+ for(var j=0; j<children.length; j++) {
+ toString(children[j]);
+ }
+ html += '</' + node.nodeName.toLowerCase() + '>';
+ }
+ });
+ return html;
+}
+
+function isCssVisible(node) {
+ var display = node.css('display');
+ if (display == 'block') display = "";
+ return display != 'none';
+}
+
+function assertHidden(node) {
+ assertFalse("Node should be hidden but vas visible: " + sortedHtml(node), isCssVisible(node));
+}
+
+function assertVisible(node) {
+ assertTrue("Node should be visible but vas hidden: " + sortedHtml(node), isCssVisible(node));
+}
+
+function assertJsonEquals(expected, actual) {
+ assertEquals(toJson(expected), toJson(actual));
+}
+
+function assertUndefined(value) {
+ assertEquals('undefined', typeof value);
+}
+
+function assertDefined(value) {
+ assertTrue(toJson(value), !!value);
+}
+
+function assertThrows(error, fn){
+ var exception = null;
+ try {
+ fn();
+ } catch(e) {
+ exception = e;
+ }
+ if (!exception) {
+ fail("Expecting exception, none thrown");
+ }
+ assertEquals(error, exception);
+}
+
+log = noop;
+error = noop;
+
+function click(element) {
+ element = jqLite(element);
+ if ( msie &&
+ nodeName(element) == 'INPUT' && (lowercase(element.attr('type')) == 'radio' || lowercase(element.attr('type')) == 'checkbox')) {
+ element[0].checked = ! element[0].checked;
+ }
+ JQLite.prototype.trigger.call(element, 'click');
+}
diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js
new file mode 100644
index 00000000..a053090e
--- /dev/null
+++ b/test/widgetsSpec.js
@@ -0,0 +1,441 @@
+describe("widget", function(){
+ var compile, element, scope;
+
+ beforeEach(function() {
+ scope = null;
+ element = null;
+ var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget);
+ compile = function(html, before) {
+ element = jqLite(html);
+ scope = compiler.compile(element)(element);
+ (before||noop).apply(scope);
+ scope.$init();
+ };
+ });
+
+ afterEach(function(){
+ if (element && element.dealoc) element.dealoc();
+ expect(size(jqCache)).toEqual(0);
+ });
+
+ describe("input", function(){
+
+ describe("text", function(){
+ it('should input-text auto init and handle keyup/change events', function(){
+ compile('<input type="Text" name="name" value="Misko" ng-change="count = count + 1" ng-init="count=0"/>');
+ expect(scope.$get('name')).toEqual("Misko");
+ expect(scope.$get('count')).toEqual(0);
+
+ scope.$set('name', 'Adam');
+ scope.$eval();
+ expect(element.val()).toEqual("Adam");
+
+ element.val('Shyam');
+ element.trigger('keyup');
+ expect(scope.$get('name')).toEqual('Shyam');
+ expect(scope.$get('count')).toEqual(1);
+
+ element.val('Kai');
+ element.trigger('change');
+ expect(scope.$get('name')).toEqual('Kai');
+ expect(scope.$get('count')).toEqual(2);
+ });
+
+ describe("ng-format", function(){
+
+ it("should format text", function(){
+ compile('<input type="Text" name="list" value="a,b,c" ng-format="list"/>');
+ expect(scope.$get('list')).toEqual(['a', 'b', 'c']);
+
+ scope.$set('list', ['x', 'y', 'z']);
+ scope.$eval();
+ expect(element.val()).toEqual("x, y, z");
+
+ element.val('1, 2, 3');
+ element.trigger('keyup');
+ expect(scope.$get('list')).toEqual(['1', '2', '3']);
+ });
+
+ it("should come up blank if null", function(){
+ compile('<input type="text" name="age" ng-format="number"/>', function(){
+ scope.age = null;
+ });
+ expect(scope.age).toBeNull();
+ expect(scope.$element[0].value).toEqual('');
+ });
+
+ it("should show incorect text while number does not parse", function(){
+ compile('<input type="text" name="age" ng-format="number"/>');
+ scope.age = 123;
+ scope.$eval();
+ scope.$element.val('123X');
+ scope.$element.trigger('change');
+ expect(scope.$element.val()).toEqual('123X');
+ expect(scope.age).toEqual(123);
+ expect(scope.$element).toBeInvalid();
+ });
+
+ it("should clober incorect text if model changes", function(){
+ compile('<input type="text" name="age" ng-format="number" value="123X"/>');
+ scope.age = 456;
+ scope.$eval();
+ expect(scope.$element.val()).toEqual('456');
+ });
+
+ it("should not clober text if model changes doe to itself", function(){
+ compile('<input type="text" name="list" ng-format="list" value="a"/>');
+
+ scope.$element.val('a ');
+ scope.$element.trigger('change');
+ expect(scope.$element.val()).toEqual('a ');
+ expect(scope.list).toEqual(['a']);
+
+ scope.$element.val('a ,');
+ scope.$element.trigger('change');
+ expect(scope.$element.val()).toEqual('a ,');
+ expect(scope.list).toEqual(['a']);
+
+ scope.$element.val('a , ');
+ scope.$element.trigger('change');
+ expect(scope.$element.val()).toEqual('a , ');
+ expect(scope.list).toEqual(['a']);
+
+ scope.$element.val('a , b');
+ scope.$element.trigger('change');
+ expect(scope.$element.val()).toEqual('a , b');
+ expect(scope.list).toEqual(['a', 'b']);
+ });
+
+ it("should come up blank when no value specifiend", function(){
+ compile('<input type="text" name="age" ng-format="number"/>');
+ scope.$eval();
+ expect(scope.$element.val()).toEqual('');
+ expect(scope.age).toEqual(null);
+ });
+
+ });
+
+ describe("checkbox", function(){
+ it("should format booleans", function(){
+ compile('<input type="checkbox" name="name"/>', function(){
+ scope.name = false;
+ });
+ expect(scope.name).toEqual(false);
+ expect(scope.$element[0].checked).toEqual(false);
+ });
+
+ it('should support type="checkbox"', function(){
+ compile('<input type="checkBox" name="checkbox" checked ng-change="action = true"/>');
+ expect(scope.checkbox).toEqual(true);
+ click(element);
+ expect(scope.checkbox).toEqual(false);
+ expect(scope.action).toEqual(true);
+ click(element);
+ expect(scope.checkbox).toEqual(true);
+ });
+
+ it("should use ng-format", function(){
+ angularFormatter('testFormat', {
+ parse: function(value){
+ return value ? "Worked" : "Failed";
+ },
+
+ format: function(value) {
+ if (value == undefined) return value;
+ return value == "Worked";
+ }
+
+ });
+ compile('<input type="checkbox" name="state" ng-format="testFormat" checked/>');
+ expect(scope.state).toEqual("Worked");
+ expect(scope.$element[0].checked).toEqual(true);
+
+ click(scope.$element);
+ expect(scope.state).toEqual("Failed");
+ expect(scope.$element[0].checked).toEqual(false);
+
+ scope.state = "Worked";
+ scope.$eval();
+ expect(scope.state).toEqual("Worked");
+ expect(scope.$element[0].checked).toEqual(true);
+ });
+ });
+
+ describe("ng-validate", function(){
+ it("should process ng-validate", function(){
+ compile('<input type="text" name="price" value="abc" ng-validate="number"/>');
+ expect(element.hasClass('ng-validation-error')).toBeTruthy();
+ expect(element.attr('ng-validation-error')).toEqual('Not a number');
+
+ scope.$set('price', '123');
+ scope.$eval();
+ expect(element.hasClass('ng-validation-error')).toBeFalsy();
+ expect(element.attr('ng-validation-error')).toBeFalsy();
+
+ element.val('x');
+ element.trigger('keyup');
+ expect(element.hasClass('ng-validation-error')).toBeTruthy();
+ expect(element.attr('ng-validation-error')).toEqual('Not a number');
+ });
+
+ it('should not blow up for validation with bound attributes', function() {
+ compile('<input type="text" name="price" boo="{{abc}}" ng-required/>');
+ expect(element.hasClass('ng-validation-error')).toBeTruthy();
+ expect(element.attr('ng-validation-error')).toEqual('Required');
+
+ scope.$set('price', '123');
+ scope.$eval();
+ expect(element.hasClass('ng-validation-error')).toBeFalsy();
+ expect(element.attr('ng-validation-error')).toBeFalsy();
+ });
+
+ it("should not call validator if undefined/empty", function(){
+ var lastValue = "NOT_CALLED";
+ angularValidator.myValidator = function(value){lastValue = value;};
+ compile('<input type="text" name="url" ng-validate="myValidator"/>');
+ expect(lastValue).toEqual("NOT_CALLED");
+
+ scope.url = 'http://server';
+ scope.$eval();
+ expect(lastValue).toEqual("http://server");
+
+ delete angularValidator.myValidator;
+ });
+ });
+ });
+
+ it("should ignore disabled widgets", function(){
+ compile('<input type="text" name="price" ng-required disabled/>');
+ expect(element.hasClass('ng-validation-error')).toBeFalsy();
+ expect(element.attr('ng-validation-error')).toBeFalsy();
+ });
+
+ it("should ignore readonly widgets", function(){
+ compile('<input type="text" name="price" ng-required readonly/>');
+ expect(element.hasClass('ng-validation-error')).toBeFalsy();
+ expect(element.attr('ng-validation-error')).toBeFalsy();
+ });
+
+ it("should process ng-required", function(){
+ compile('<input type="text" name="price" ng-required/>');
+ expect(element.hasClass('ng-validation-error')).toBeTruthy();
+ expect(element.attr('ng-validation-error')).toEqual('Required');
+
+ scope.$set('price', 'xxx');
+ scope.$eval();
+ expect(element.hasClass('ng-validation-error')).toBeFalsy();
+ expect(element.attr('ng-validation-error')).toBeFalsy();
+
+ element.val('');
+ element.trigger('keyup');
+ expect(element.hasClass('ng-validation-error')).toBeTruthy();
+ expect(element.attr('ng-validation-error')).toEqual('Required');
+ });
+
+ it('should allow conditions on ng-required', function() {
+ compile('<input type="text" name="price" ng-required="ineedz"/>');
+ scope.$set('ineedz', false);
+ scope.$eval();
+ expect(element.hasClass('ng-validation-error')).toBeFalsy();
+ expect(element.attr('ng-validation-error')).toBeFalsy();
+
+ scope.$set('price', 'xxx');
+ scope.$eval();
+ expect(element.hasClass('ng-validation-error')).toBeFalsy();
+ expect(element.attr('ng-validation-error')).toBeFalsy();
+
+ scope.$set('price', '');
+ scope.$set('ineedz', true);
+ scope.$eval();
+ expect(element.hasClass('ng-validation-error')).toBeTruthy();
+ expect(element.attr('ng-validation-error')).toEqual('Required');
+
+ element.val('abc');
+ element.trigger('keyup');
+ expect(element.hasClass('ng-validation-error')).toBeFalsy();
+ expect(element.attr('ng-validation-error')).toBeFalsy();
+ });
+
+ it("should process ng-required2", function() {
+ compile('<textarea name="name">Misko</textarea>');
+ expect(scope.$get('name')).toEqual("Misko");
+
+ scope.$set('name', 'Adam');
+ scope.$eval();
+ expect(element.val()).toEqual("Adam");
+
+ element.val('Shyam');
+ element.trigger('keyup');
+ expect(scope.$get('name')).toEqual('Shyam');
+
+ element.val('Kai');
+ element.trigger('change');
+ expect(scope.$get('name')).toEqual('Kai');
+ });
+
+ it('should call ng-change on button click', function(){
+ compile('<input type="button" value="Click Me" ng-change="clicked = true"/>');
+ click(element);
+ expect(scope.$get('clicked')).toEqual(true);
+ });
+
+ it('should support button alias', function(){
+ compile('<button ng-change="clicked = true">Click Me</button>');
+ click(element);
+ expect(scope.$get('clicked')).toEqual(true);
+ });
+
+ describe('radio', function(){
+
+ it('should support type="radio"', function(){
+ compile('<div>' +
+ '<input type="radio" name="chose" value="A" ng-change="clicked = 1"/>' +
+ '<input type="radio" name="chose" value="B" checked ng-change="clicked = 2"/>' +
+ '<input type="radio" name="chose" value="C" ng-change="clicked = 3"/>' +
+ '</div>');
+ var a = element[0].childNodes[0];
+ var b = element[0].childNodes[1];
+ expect(b.name.split('@')[1]).toEqual('chose');
+ expect(scope.chose).toEqual('B');
+ scope.chose = 'A';
+ scope.$eval();
+ expect(a.checked).toEqual(true);
+
+ scope.chose = 'B';
+ scope.$eval();
+ expect(a.checked).toEqual(false);
+ expect(b.checked).toEqual(true);
+ expect(scope.clicked).not.toBeDefined();
+
+ click(a);
+ expect(scope.chose).toEqual('A');
+ expect(scope.clicked).toEqual(1);
+ });
+
+ it('should honor model over html checked keyword after', function(){
+ compile('<div>' +
+ '<input type="radio" name="choose" value="A""/>' +
+ '<input type="radio" name="choose" value="B" checked/>' +
+ '<input type="radio" name="choose" value="C"/>' +
+ '</div>', function(){
+ this.choose = 'C';
+ });
+
+ expect(scope.choose).toEqual('C');
+ });
+
+ it('should honor model over html checked keyword before', function(){
+ compile('<div>' +
+ '<input type="radio" name="choose" value="A""/>' +
+ '<input type="radio" name="choose" value="B" checked/>' +
+ '<input type="radio" name="choose" value="C"/>' +
+ '</div>', function(){
+ this.choose = 'A';
+ });
+
+ expect(scope.choose).toEqual('A');
+ });
+
+ });
+
+ it('should support type="select-one"', function(){
+ compile(
+ '<select name="selection">' +
+ '<option>A</option>' +
+ '<option selected>B</option>' +
+ '</select>');
+ expect(scope.selection).toEqual('B');
+ scope.selection = 'A';
+ scope.$eval();
+ expect(scope.selection).toEqual('A');
+ expect(element[0].childNodes[0].selected).toEqual(true);
+ });
+
+ it('should support type="select-multiple"', function(){
+ compile(
+ '<select name="selection" multiple>' +
+ '<option>A</option>' +
+ '<option selected>B</option>' +
+ '</select>');
+ expect(scope.selection).toEqual(['B']);
+ scope.selection = ['A'];
+ scope.$eval();
+ expect(element[0].childNodes[0].selected).toEqual(true);
+ });
+
+ it('should report error on missing field', function(){
+ compile('<input type="text"/>');
+ expect(element.hasClass('ng-exception')).toBeTruthy();
+ });
+
+ it('should report error on assignment error', function(){
+ compile('<input type="text" name="throw \'\'" value="x"/>');
+ expect(element.hasClass('ng-exception')).toBeTruthy();
+ });
+
+ it('should report error on ng-change exception', function(){
+ compile('<button ng-change="a-2=x">click</button>');
+ click(element);
+ expect(element.hasClass('ng-exception')).toBeTruthy();
+ });
+ });
+
+ describe('ng:switch', function(){
+ it('should switch on value change', function(){
+ compile('<ng:switch on="select"><div ng-switch-when="1">first:{{name}}</div><div ng-switch-when="2">second:{{name}}</div></ng:switch>');
+ expect(element.html()).toEqual('');
+ scope.select = 1;
+ scope.$eval();
+ expect(element.text()).toEqual('first:');
+ scope.name="shyam";
+ scope.$eval();
+ expect(element.text()).toEqual('first:shyam');
+ scope.select = 2;
+ scope.$eval();
+ expect(element.text()).toEqual('second:shyam');
+ scope.name = 'misko';
+ scope.$eval();
+ expect(element.text()).toEqual('second:misko');
+ });
+
+ it("should match urls", function(){
+ var scope = angular.compile('<ng:switch on="url" using="route:params"><div ng-switch-when="/Book/:name">{{params.name}}</div></ng:switch>');
+ scope.url = '/Book/Moby';
+ scope.$init();
+ expect(scope.$element.text()).toEqual('Moby');
+ });
+
+ it("should match sandwich ids", function(){
+ var scope = {};
+ var match = angular.widget['NG:SWITCH'].route.call(scope, '/a/123/b', '/a/:id');
+ expect(match).toBeFalsy();
+ });
+
+ it('should call init on switch', function(){
+ var scope = angular.compile('<ng:switch on="url" change="name=\'works\'"><div ng-switch-when="a">{{name}}</div></ng:switch>');
+ var cleared = false;
+ scope.url = 'a';
+ scope.$invalidWidgets = {clearOrphans: function(){
+ cleared = true;
+ }};
+ scope.$init();
+ expect(scope.name).toEqual(undefined);
+ expect(scope.$element.text()).toEqual('works');
+ expect(cleared).toEqual(true);
+ });
+ });
+
+ describe('ng:include', function(){
+ it('should include on external file', function() {
+ var element = jqLite('<ng:include src="url" scope="childScope"></ng:include>');
+ var scope = angular.compile(element);
+ scope.childScope = createScope();
+ scope.childScope.name = 'misko';
+ scope.url = 'myUrl';
+ scope.$xhr.cache.data.myUrl = {value:'{{name}}'};
+ scope.$init();
+ expect(element.text()).toEqual('misko');
+ });
+ });
+});
+