mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-17 07:40:22 +00:00
- added angular.injector(scope, services, instanceCache) which returns inject
- inject method can return, instance, or call function which have $inject
property
- initialize services with $creation=[eager|eager-publish] this means that
only some of the services are now globally accessible
- upgraded $become on scope to use injector hence respect the $inject property
for injection
- $become should not be run multiple times and will most likely be removed
in future version
- added $new on scope to create a child scope
- $inject is respected on constructor function
- simplified scopes so that they no longer have separate __proto__ for
parent, api, behavior and instance this should speed up execution since
scope will now create one __proto__ chain per scope (not three).
BACKWARD COMPATIBILITY WARNING:
- services now need to have $inject instead of inject property for proper
injection this breaks backward compatibility
- not all services are now published into root scope
(only: $location, $cookie, $window)
- if you have widget/directive which uses services on scope
(such as this.$xhr), you will now have to inject that service in
(as it is not published on the root scope anymore)
509 lines
17 KiB
JavaScript
509 lines
17 KiB
JavaScript
describe("service", function(){
|
|
var scope, $xhrError, $log, mockServices, inject, $browser, $browserXhr, $xhrBulk, $xhr, $route;
|
|
|
|
beforeEach(function(){
|
|
$xhrError = jasmine.createSpy('$xhr.error');
|
|
$log = {};
|
|
scope = createScope({}, angularService, {
|
|
'$xhr.error': $xhrError,
|
|
'$log': $log
|
|
});
|
|
inject = scope.$inject;
|
|
$browser = inject('$browser');
|
|
$browserXhr = $browser.xhr;
|
|
$xhrBulk = scope.$inject('$xhr.bulk');
|
|
$xhr = scope.$inject('$xhr');
|
|
$route = scope.$inject('$route');
|
|
});
|
|
|
|
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(){
|
|
var logger = "";
|
|
function log(){ logger+= 'log;'; }
|
|
function warn(){ logger+= 'warn;'; }
|
|
function info(){ logger+= 'info;'; }
|
|
function error(){ logger+= 'error;'; }
|
|
var scope = createScope({}, angularService, {$window: {console:{log:log, warn:warn, info:info, error:error}}, $document:[{cookie:''}]});
|
|
scope.$log.log();
|
|
scope.$log.warn();
|
|
scope.$log.info();
|
|
scope.$log.error();
|
|
expect(logger).toEqual('log;warn;info;error;');
|
|
});
|
|
|
|
it('should use console.log if other not present', function(){
|
|
var logger = "";
|
|
function log(){ logger+= 'log;'; }
|
|
var scope = createScope({}, angularService, {$window: {console:{log:log}}, $document:[{cookie:''}]});
|
|
scope.$log.log();
|
|
scope.$log.warn();
|
|
scope.$log.info();
|
|
scope.$log.error();
|
|
expect(logger).toEqual('log;log;log;log;');
|
|
});
|
|
|
|
it('should use noop if no console', function(){
|
|
var scope = createScope({}, angularService, {$window: {}, $document:[{cookie:''}]});
|
|
scope.$log.log();
|
|
scope.$log.warn();
|
|
scope.$log.info();
|
|
scope.$log.error();
|
|
});
|
|
});
|
|
|
|
describe("$exceptionHandler", function(){
|
|
it('should log errors', function(){
|
|
var error = '';
|
|
$log.error = function(m) { error += m; };
|
|
scope.$exceptionHandler('myError');
|
|
expect(error).toEqual('myError');
|
|
});
|
|
});
|
|
|
|
describe("$location", function(){
|
|
it("should inject $location", function(){
|
|
scope.$location.parse('http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2=');
|
|
expect(scope.$location.href).
|
|
toEqual("http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2=");
|
|
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&flag&key2=');
|
|
expect(scope.$location.hashPath).toEqual('path');
|
|
expect(scope.$location.hashSearch).toEqual({key: 'value', flag: true, key2: ''});
|
|
|
|
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%3Dhttp%3A//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 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');
|
|
});
|
|
|
|
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;');
|
|
});
|
|
});
|
|
|
|
describe("$invalidWidgets", function(){
|
|
it("should count number of invalid widgets", function(){
|
|
var scope = compile('<input name="price" ng:required ng:validate="number"></input>');
|
|
jqLite(document.body).append(scope.$element);
|
|
scope.$init();
|
|
expect(scope.$invalidWidgets.length).toEqual(1);
|
|
|
|
scope.price = 123;
|
|
scope.$eval();
|
|
expect(scope.$invalidWidgets.length).toEqual(0);
|
|
|
|
scope.$element.remove();
|
|
scope.price = 'abc';
|
|
scope.$eval();
|
|
expect(scope.$invalidWidgets.length).toEqual(0);
|
|
|
|
jqLite(document.body).append(scope.$element);
|
|
scope.price = 'abcd'; //force revalidation, maybe this should be done automatically?
|
|
scope.$eval();
|
|
expect(scope.$invalidWidgets.length).toEqual(1);
|
|
|
|
jqLite(document.body).html('');
|
|
scope.$eval();
|
|
expect(scope.$invalidWidgets.length).toEqual(0);
|
|
});
|
|
});
|
|
|
|
|
|
describe("$route", function(){
|
|
it('should route and fire change event', function(){
|
|
var log = '';
|
|
function BookChapter() {
|
|
this.log = '<init>';
|
|
}
|
|
var scope = compile('<div></div>').$init();
|
|
var $route = scope.$inject('$route');
|
|
$route.when('/Book/:book/Chapter/:chapter', {controller: BookChapter, template:'Chapter.html'});
|
|
$route.when('/Blank');
|
|
$route.onChange(function(){
|
|
log += 'onChange();';
|
|
});
|
|
scope.$location.parse('http://server#/Book/Moby/Chapter/Intro?p=123');
|
|
scope.$eval();
|
|
expect(log).toEqual('onChange();');
|
|
expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', p:'123'});
|
|
expect($route.current.scope.log).toEqual('<init>');
|
|
var lastId = $route.current.scope.$id;
|
|
|
|
log = '';
|
|
scope.$location.parse('http://server#/Blank?ignore');
|
|
scope.$eval();
|
|
expect(log).toEqual('onChange();');
|
|
expect($route.current.params).toEqual({ignore:true});
|
|
expect($route.current.scope.$id).not.toEqual(lastId);
|
|
|
|
log = '';
|
|
scope.$location.parse('http://server#/NONE');
|
|
scope.$eval();
|
|
expect(log).toEqual('onChange();');
|
|
expect($route.current).toEqual(null);
|
|
|
|
$route.when('/NONE', {template:'instant update'});
|
|
scope.$eval();
|
|
expect($route.current.template).toEqual('instant update');
|
|
});
|
|
});
|
|
|
|
describe('$resource', function(){
|
|
it('should publish to root scope', function(){
|
|
expect(scope.$inject('$resource')).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
describe('$xhr', function(){
|
|
var log;
|
|
function callback(code, response) {
|
|
expect(code).toEqual(200);
|
|
log = log + toJson(response) + ';';
|
|
}
|
|
|
|
beforeEach(function(){
|
|
log = '';
|
|
});
|
|
|
|
it('should forward the request to $browser and decode JSON', function(){
|
|
$browserXhr.expectGET('/reqGET').respond('first');
|
|
$browserXhr.expectGET('/reqGETjson').respond('["second"]');
|
|
$browserXhr.expectPOST('/reqPOST', {post:'data'}).respond('third');
|
|
|
|
$xhr('GET', '/reqGET', null, callback);
|
|
$xhr('GET', '/reqGETjson', null, callback);
|
|
$xhr('POST', '/reqPOST', {post:'data'}, callback);
|
|
|
|
$browserXhr.flush();
|
|
|
|
expect(log).toEqual('"third";["second"];"first";');
|
|
});
|
|
|
|
it('should handle non 200 status codes by forwarding to error handler', function(){
|
|
$browserXhr.expectPOST('/req', 'MyData').respond(500, 'MyError');
|
|
$xhr('POST', '/req', 'MyData', callback);
|
|
$browserXhr.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');
|
|
$browserXhr.expectGET('/reqGET').respond('first');
|
|
$xhr('GET', '/reqGET', null, function(){ throw "MyException"; });
|
|
$browserXhr.flush();
|
|
|
|
expect($log.error).wasCalledWith("MyException");
|
|
});
|
|
|
|
describe('bulk', function(){
|
|
it('should collect requests', function(){
|
|
$xhrBulk.urls["/"] = {match:/.*/};
|
|
$xhrBulk('GET', '/req1', null, callback);
|
|
$xhrBulk('POST', '/req2', {post:'data'}, callback);
|
|
|
|
$browserXhr.expectPOST('/', {
|
|
requests:[{method:'GET', url:'/req1', data: null},
|
|
{method:'POST', url:'/req2', data:{post:'data'} }]
|
|
}).respond([
|
|
{status:200, response:'first'},
|
|
{status:200, response:'second'}
|
|
]);
|
|
$xhrBulk.flush(function(){ log += 'DONE';});
|
|
$browserXhr.flush();
|
|
expect(log).toEqual('"first";"second";DONE');
|
|
});
|
|
|
|
it('should handle non 200 status code by forwarding to error handler', function(){
|
|
$xhrBulk.urls['/'] = {match:/.*/};
|
|
$xhrBulk('GET', '/req1', null, callback);
|
|
$xhrBulk('POST', '/req2', {post:'data'}, callback);
|
|
|
|
$browserXhr.expectPOST('/', {
|
|
requests:[{method:'GET', url:'/req1', data: null},
|
|
{method:'POST', url:'/req2', data:{post:'data'} }]
|
|
}).respond([
|
|
{status:404, response:'NotFound'},
|
|
{status:200, response:'second'}
|
|
]);
|
|
$xhrBulk.flush(function(){ log += 'DONE';});
|
|
$browserXhr.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.$inject('$xhr.cache'); });
|
|
|
|
it('should cache requests', function(){
|
|
$browserXhr.expectGET('/url').respond('first');
|
|
cache('GET', '/url', null, callback);
|
|
$browserXhr.flush();
|
|
$browserXhr.expectGET('/url').respond('ERROR');
|
|
cache('GET', '/url', null, callback);
|
|
$browserXhr.flush();
|
|
expect(log).toEqual('"first";"first";');
|
|
cache('GET', '/url', null, callback, false);
|
|
$browserXhr.flush();
|
|
expect(log).toEqual('"first";"first";"first";');
|
|
});
|
|
|
|
it('should first return cache request, then return server request', function(){
|
|
$browserXhr.expectGET('/url').respond('first');
|
|
cache('GET', '/url', null, callback, true);
|
|
$browserXhr.flush();
|
|
$browserXhr.expectGET('/url').respond('ERROR');
|
|
cache('GET', '/url', null, callback, true);
|
|
expect(log).toEqual('"first";"first";');
|
|
$browserXhr.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.$inject('$xhr.bulk').urls['/bulk'] = {
|
|
match:function(url){
|
|
return url == '/url';
|
|
}
|
|
};
|
|
$browserXhr.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();
|
|
$browserXhr.flush();
|
|
expect(log).toEqual('"123";"123";');
|
|
});
|
|
|
|
it('should clear cache on non GET', function(){
|
|
$browserXhr.expectPOST('abc', {}).respond({});
|
|
cache.data.url = {value:123};
|
|
cache('POST', 'abc', {});
|
|
expect(cache.data.url).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
|
|
describe('$cookies', function() {
|
|
|
|
var scope, $browser;
|
|
|
|
beforeEach(function() {
|
|
$browser = new MockBrowser();
|
|
$browser.cookieHash['preexisting'] = 'oldCookie';
|
|
scope = createScope(null, angularService, {$browser: $browser});
|
|
});
|
|
|
|
|
|
it('should provide access to existing cookies via object properties and keep them in sync',
|
|
function(){
|
|
expect(scope.$cookies).toEqual({'preexisting': 'oldCookie'});
|
|
|
|
// access internal cookie storage of the browser mock directly to simulate behavior of
|
|
// document.cookie
|
|
$browser.cookieHash['brandNew'] = 'cookie';
|
|
$browser.poll();
|
|
|
|
expect(scope.$cookies).toEqual({'preexisting': 'oldCookie', 'brandNew':'cookie'});
|
|
|
|
$browser.cookieHash['brandNew'] = 'cookie2';
|
|
$browser.poll();
|
|
expect(scope.$cookies).toEqual({'preexisting': 'oldCookie', 'brandNew':'cookie2'});
|
|
|
|
delete $browser.cookieHash['brandNew'];
|
|
$browser.poll();
|
|
expect(scope.$cookies).toEqual({'preexisting': 'oldCookie'});
|
|
});
|
|
|
|
|
|
it('should create or update a cookie when a value is assigned to a property', function() {
|
|
scope.$cookies.oatmealCookie = 'nom nom';
|
|
scope.$eval();
|
|
|
|
expect($browser.cookies()).
|
|
toEqual({'preexisting': 'oldCookie', 'oatmealCookie':'nom nom'});
|
|
|
|
scope.$cookies.oatmealCookie = 'gone';
|
|
scope.$eval();
|
|
|
|
expect($browser.cookies()).
|
|
toEqual({'preexisting': 'oldCookie', 'oatmealCookie': 'gone'});
|
|
});
|
|
|
|
|
|
it('should ignore non-string values when asked to create a cookie', function() {
|
|
scope.$cookies.nonString = [1, 2, 3];
|
|
scope.$eval();
|
|
expect($browser.cookies()).toEqual({'preexisting': 'oldCookie'});
|
|
expect(scope.$cookies).toEqual({'preexisting': 'oldCookie'});
|
|
});
|
|
|
|
|
|
it('should drop any null or undefined properties', function() {
|
|
scope.$cookies.nullVal = null;
|
|
scope.$cookies.undefVal = undefined;
|
|
scope.$eval();
|
|
|
|
expect($browser.cookies()).toEqual({'preexisting': 'oldCookie'});
|
|
});
|
|
|
|
|
|
it('should remove a cookie when a $cookies property is deleted', function() {
|
|
scope.$cookies.oatmealCookie = 'nom nom';
|
|
scope.$eval();
|
|
$browser.poll();
|
|
expect($browser.cookies()).
|
|
toEqual({'preexisting': 'oldCookie', 'oatmealCookie':'nom nom'});
|
|
|
|
delete scope.$cookies.oatmealCookie;
|
|
scope.$eval();
|
|
|
|
expect($browser.cookies()).toEqual({'preexisting': 'oldCookie'});
|
|
});
|
|
|
|
|
|
it('should drop or reset cookies that browser refused to store', function() {
|
|
var i, longVal;
|
|
|
|
for (i=0; i<5000; i++) {
|
|
longVal += '*';
|
|
}
|
|
|
|
//drop if no previous value
|
|
scope.$cookies.longCookie = longVal;
|
|
scope.$eval();
|
|
expect(scope.$cookies).toEqual({'preexisting': 'oldCookie'});
|
|
|
|
|
|
//reset if previous value existed
|
|
scope.$cookies.longCookie = 'shortVal';
|
|
scope.$eval();
|
|
expect(scope.$cookies).toEqual({'preexisting': 'oldCookie', 'longCookie': 'shortVal'});
|
|
scope.$cookies.longCookie = longVal;
|
|
scope.$eval();
|
|
expect(scope.$cookies).toEqual({'preexisting': 'oldCookie', 'longCookie': 'shortVal'});
|
|
});
|
|
});
|
|
|
|
|
|
describe('$cookieStore', function() {
|
|
|
|
it('should serialize objects to json', function() {
|
|
scope.$cookieStore.put('objectCookie', {id: 123, name: 'blah'});
|
|
scope.$eval(); //force eval in test
|
|
expect($browser.cookies()).toEqual({'objectCookie': '{"id":123,"name":"blah"}'});
|
|
});
|
|
|
|
|
|
it('should deserialize json to object', function() {
|
|
$browser.cookies('objectCookie', '{"id":123,"name":"blah"}');
|
|
$browser.poll();
|
|
expect(scope.$cookieStore.get('objectCookie')).toEqual({id: 123, name: 'blah'});
|
|
});
|
|
|
|
|
|
it('should delete objects from the store when remove is called', function() {
|
|
scope.$cookieStore.put('gonner', { "I'll":"Be Back"});
|
|
scope.$eval(); //force eval in test
|
|
expect($browser.cookies()).toEqual({'gonner': '{"I\'ll":"Be Back"}'});
|
|
});
|
|
|
|
});
|
|
});
|