diff options
| author | Rob Spies | 2010-06-22 17:09:55 -0700 |
|---|---|---|
| committer | Rob Spies | 2010-06-22 17:09:55 -0700 |
| commit | 1500e91defa4020bfe9608749b25e585cd1d8e3d (patch) | |
| tree | 8c2872252b62567dc4eb00f7d7547661d5674c55 /test | |
| parent | eaa397c76b7d28343cde9f3a0338b9b0e79197c8 (diff) | |
| parent | b129a1094e6b42ed82c3ccecc2f40daaa0a6cb6a (diff) | |
| download | angular.js-1500e91defa4020bfe9608749b25e585cd1d8e3d.tar.bz2 | |
Merge http://github.com/angular/angular.js into angular
Conflicts:
.gitignore
Diffstat (limited to 'test')
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 = ' '; + 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>) ' + + '<<a href="http://a/">http://a/</a>> ' + + '<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'); + }); + }); +}); + |
