├── logs └── .gitkeep ├── app ├── css │ ├── .gitkeep │ └── app.css ├── img │ └── .gitkeep ├── partials │ ├── .gitkeep │ ├── partial1.html │ └── partial2.html ├── lib │ └── angular │ │ ├── version.txt │ │ ├── angular-cookies.min.js │ │ ├── angular-loader.min.js │ │ ├── angular-resource.min.js │ │ ├── angular-sanitize.min.js │ │ ├── angular-cookies.js │ │ ├── angular-loader.js │ │ ├── angular-resource.js │ │ └── angular-sanitize.js ├── js │ ├── controllers.js │ ├── services.js │ ├── directives.js │ ├── filters.js │ └── app.js ├── index.html └── index-async.html ├── test ├── lib │ └── angular │ │ ├── version.txt │ │ └── angular-mocks.js ├── e2e │ ├── runner.html │ └── scenarios.js └── unit │ ├── controllersSpec.js │ ├── servicesSpec.js │ ├── filtersSpec.js │ └── directivesSpec.js ├── .gitignore ├── scripts ├── test.sh ├── e2e-test.sh ├── e2e-test.bat ├── test.bat ├── watchr.rb └── web-server.js ├── config ├── karma-e2e.conf.js └── karma.conf.js ├── .travis.yml └── README.md /logs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/css/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/img/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/partials/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/lib/angular/version.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/lib/angular/version.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs/* 2 | !.gitkeep 3 | -------------------------------------------------------------------------------- /app/partials/partial1.html: -------------------------------------------------------------------------------- 1 |

This is the partial for view 1.

2 | -------------------------------------------------------------------------------- /app/partials/partial2.html: -------------------------------------------------------------------------------- 1 |

This is the partial for view 2.

2 |

3 | Showing of 'interpolate' filter: 4 | {{ 'Current version is v%VERSION%.' | interpolate }} 5 |

6 | -------------------------------------------------------------------------------- /app/js/controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Controllers */ 4 | 5 | angular.module('myApp.controllers', []). 6 | controller('MyCtrl1', [function() { 7 | 8 | }]) 9 | .controller('MyCtrl2', [function() { 10 | 11 | }]); -------------------------------------------------------------------------------- /app/js/services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Services */ 4 | 5 | 6 | // Demonstrate how to register services 7 | // In this case it is a simple value service. 8 | angular.module('myApp.services', []). 9 | value('version', '0.1'); 10 | -------------------------------------------------------------------------------- /app/js/directives.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Directives */ 4 | 5 | 6 | angular.module('myApp.directives', []). 7 | directive('appVersion', ['version', function(version) { 8 | return function(scope, elm, attrs) { 9 | elm.text(version); 10 | }; 11 | }]); 12 | -------------------------------------------------------------------------------- /app/js/filters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Filters */ 4 | 5 | angular.module('myApp.filters', []). 6 | filter('interpolate', ['version', function(version) { 7 | return function(text) { 8 | return String(text).replace(/\%VERSION\%/mg, version); 9 | } 10 | }]); 11 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASE_DIR=`dirname $0` 4 | 5 | echo "" 6 | echo "Starting Karma Server (http://karma-runner.github.io)" 7 | echo "-------------------------------------------------------------------" 8 | 9 | karma start $BASE_DIR/../config/karma.conf.js $* 10 | -------------------------------------------------------------------------------- /scripts/e2e-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASE_DIR=`dirname $0` 4 | 5 | echo "" 6 | echo "Starting Karma Server (http://karma-runner.github.io)" 7 | echo "-------------------------------------------------------------------" 8 | 9 | karma start $BASE_DIR/../config/karma-e2e.conf.js $* 10 | -------------------------------------------------------------------------------- /test/e2e/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | End2end Test Runner 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /scripts/e2e-test.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM Windows script for running e2e tests 4 | REM You have to run server and capture some browser first 5 | REM 6 | REM Requirements: 7 | REM - NodeJS (http://nodejs.org/) 8 | REM - Karma (npm install -g karma) 9 | 10 | set BASE_DIR=%~dp0 11 | karma start "%BASE_DIR%\..\config\karma-e2e.conf.js" %* 12 | -------------------------------------------------------------------------------- /scripts/test.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM Windows script for running unit tests 4 | REM You have to run server and capture some browser first 5 | REM 6 | REM Requirements: 7 | REM - NodeJS (http://nodejs.org/) 8 | REM - Karma (npm install -g karma) 9 | 10 | set BASE_DIR=%~dp0 11 | karma start "%BASE_DIR%\..\config\karma.conf.js" %* 12 | -------------------------------------------------------------------------------- /test/unit/controllersSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jasmine specs for controllers go here */ 4 | 5 | describe('controllers', function(){ 6 | beforeEach(module('myApp.controllers')); 7 | 8 | 9 | it('should ....', inject(function() { 10 | //spec body 11 | })); 12 | 13 | it('should ....', inject(function() { 14 | //spec body 15 | })); 16 | }); 17 | -------------------------------------------------------------------------------- /test/unit/servicesSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jasmine specs for services go here */ 4 | 5 | describe('service', function() { 6 | beforeEach(module('myApp.services')); 7 | 8 | 9 | describe('version', function() { 10 | it('should return current version', inject(function(version) { 11 | expect(version).toEqual('0.1'); 12 | })); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /config/karma-e2e.conf.js: -------------------------------------------------------------------------------- 1 | basePath = '../'; 2 | 3 | files = [ 4 | ANGULAR_SCENARIO, 5 | ANGULAR_SCENARIO_ADAPTER, 6 | 'test/e2e/**/*.js' 7 | ]; 8 | 9 | autoWatch = false; 10 | 11 | browsers = ['Chrome']; 12 | 13 | singleRun = true; 14 | 15 | proxies = { 16 | '/': 'http://localhost:8000/' 17 | }; 18 | 19 | junitReporter = { 20 | outputFile: 'test_out/e2e.xml', 21 | suite: 'e2e' 22 | }; 23 | -------------------------------------------------------------------------------- /config/karma.conf.js: -------------------------------------------------------------------------------- 1 | basePath = '../'; 2 | 3 | files = [ 4 | JASMINE, 5 | JASMINE_ADAPTER, 6 | 'app/lib/angular/angular.js', 7 | 'app/lib/angular/angular-*.js', 8 | 'test/lib/angular/angular-mocks.js', 9 | 'app/js/**/*.js', 10 | 'test/unit/**/*.js' 11 | ]; 12 | 13 | autoWatch = true; 14 | 15 | browsers = ['Chrome']; 16 | 17 | junitReporter = { 18 | outputFile: 'test_out/unit.xml', 19 | suite: 'unit' 20 | }; 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 4 | 5 | before_script: 6 | - export DISPLAY=:99.0 7 | - sh -e /etc/init.d/xvfb start 8 | - npm install --quiet -g karma 9 | - ./scripts/web-server.js > /dev/null & 10 | - sleep 1 # give server time to start 11 | 12 | script: 13 | - karma start config/karma.conf.js --no-auto-watch --single-run --reporters=dots --browsers=Firefox 14 | - karma start config/karma-e2e.conf.js --reporters=dots --browsers=Firefox 15 | -------------------------------------------------------------------------------- /app/css/app.css: -------------------------------------------------------------------------------- 1 | /* app css stylesheet */ 2 | 3 | .menu { 4 | list-style: none; 5 | border-bottom: 0.1em solid black; 6 | margin-bottom: 2em; 7 | padding: 0 0 0.5em; 8 | } 9 | 10 | .menu:before { 11 | content: "["; 12 | } 13 | 14 | .menu:after { 15 | content: "]"; 16 | } 17 | 18 | .menu > li { 19 | display: inline; 20 | } 21 | 22 | .menu > li:before { 23 | content: "|"; 24 | padding-right: 0.3em; 25 | } 26 | 27 | .menu > li:nth-child(1):before { 28 | content: ""; 29 | padding: 0; 30 | } 31 | -------------------------------------------------------------------------------- /app/js/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | // Declare app level module which depends on filters, and services 5 | angular.module('myApp', ['myApp.filters', 'myApp.services', 'myApp.directives', 'myApp.controllers']). 6 | config(['$routeProvider', function($routeProvider) { 7 | $routeProvider.when('/view1', {templateUrl: 'partials/partial1.html', controller: 'MyCtrl1'}); 8 | $routeProvider.when('/view2', {templateUrl: 'partials/partial2.html', controller: 'MyCtrl2'}); 9 | $routeProvider.otherwise({redirectTo: '/view1'}); 10 | }]); 11 | -------------------------------------------------------------------------------- /test/unit/filtersSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jasmine specs for filters go here */ 4 | 5 | describe('filter', function() { 6 | beforeEach(module('myApp.filters')); 7 | 8 | 9 | describe('interpolate', function() { 10 | beforeEach(module(function($provide) { 11 | $provide.value('version', 'TEST_VER'); 12 | })); 13 | 14 | 15 | it('should replace VERSION', inject(function(interpolateFilter) { 16 | expect(interpolateFilter('before %VERSION% after')).toEqual('before TEST_VER after'); 17 | })); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /scripts/watchr.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env watchr 2 | 3 | # config file for watchr http://github.com/mynyml/watchr 4 | # install: gem install watchr 5 | # run: watch watchr.rb 6 | # note: make sure that you have jstd server running (server.sh) and a browser captured 7 | 8 | log_file = File.expand_path(File.dirname(__FILE__) + '/../logs/jstd.log') 9 | 10 | `cd ..` 11 | `touch #{log_file}` 12 | 13 | puts "String watchr... log file: #{log_file}" 14 | 15 | watch( '(app/js|test/unit)' ) do 16 | `echo "\n\ntest run started @ \`date\`" > #{log_file}` 17 | `scripts/test.sh &> #{log_file}` 18 | end 19 | 20 | -------------------------------------------------------------------------------- /test/unit/directivesSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jasmine specs for directives go here */ 4 | 5 | describe('directives', function() { 6 | beforeEach(module('myApp.directives')); 7 | 8 | describe('app-version', function() { 9 | it('should print current version', function() { 10 | module(function($provide) { 11 | $provide.value('version', 'TEST_VER'); 12 | }); 13 | inject(function($compile, $rootScope) { 14 | var element = $compile('')($rootScope); 15 | expect(element.text()).toEqual('TEST_VER'); 16 | }); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /app/lib/angular/angular-cookies.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.6 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(m,f,l){'use strict';f.module("ngCookies",["ng"]).factory("$cookies",["$rootScope","$browser",function(d,c){var b={},g={},h,i=!1,j=f.copy,k=f.isUndefined;c.addPollFn(function(){var a=c.cookies();h!=a&&(h=a,j(a,g),j(a,b),i&&d.$apply())})();i=!0;d.$watch(function(){var a,e,d;for(a in g)k(b[a])&&c.cookies(a,l);for(a in b)e=b[a],f.isString(e)?e!==g[a]&&(c.cookies(a,e),d=!0):f.isDefined(g[a])?b[a]=g[a]:delete b[a];if(d)for(a in e=c.cookies(),b)b[a]!==e[a]&&(k(e[a])?delete b[a]:b[a]=e[a])});return b}]).factory("$cookieStore", 7 | ["$cookies",function(d){return{get:function(c){return f.fromJson(d[c])},put:function(c,b){d[c]=f.toJson(b)},remove:function(c){delete d[c]}}}])})(window,window.angular); 8 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My AngularJS App 6 | 7 | 8 | 9 | 13 | 14 |
15 | 16 |
Angular seed app: v
17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/lib/angular/angular-loader.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.6 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(i){'use strict';function d(c,b,e){return c[b]||(c[b]=e())}return d(d(i,"angular",Object),"module",function(){var c={};return function(b,e,f){e&&c.hasOwnProperty(b)&&(c[b]=null);return d(c,b,function(){function a(a,b,d){return function(){c[d||"push"]([a,b,arguments]);return g}}if(!e)throw Error("No module: "+b);var c=[],d=[],h=a("$injector","invoke"),g={_invokeQueue:c,_runBlocks:d,requires:e,name:b,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"), 7 | value:a("$provide","value"),constant:a("$provide","constant","unshift"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:h,run:function(a){d.push(a);return this}};f&&h(f);return g})}})})(window); 8 | -------------------------------------------------------------------------------- /test/e2e/scenarios.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* http://docs.angularjs.org/guide/dev_guide.e2e-testing */ 4 | 5 | describe('my app', function() { 6 | 7 | beforeEach(function() { 8 | browser().navigateTo('../../app/index.html'); 9 | }); 10 | 11 | 12 | it('should automatically redirect to /view1 when location hash/fragment is empty', function() { 13 | expect(browser().location().url()).toBe("/view1"); 14 | }); 15 | 16 | 17 | describe('view1', function() { 18 | 19 | beforeEach(function() { 20 | browser().navigateTo('#/view1'); 21 | }); 22 | 23 | 24 | it('should render view1 when user navigates to /view1', function() { 25 | expect(element('[ng-view] p:first').text()). 26 | toMatch(/partial for view 1/); 27 | }); 28 | 29 | }); 30 | 31 | 32 | describe('view2', function() { 33 | 34 | beforeEach(function() { 35 | browser().navigateTo('#/view2'); 36 | }); 37 | 38 | 39 | it('should render view2 when user navigates to /view2', function() { 40 | expect(element('[ng-view] p:first').text()). 41 | toMatch(/partial for view 2/); 42 | }); 43 | 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /app/lib/angular/angular-resource.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.6 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(C,d,w){'use strict';d.module("ngResource",["ng"]).factory("$resource",["$http","$parse",function(x,y){function s(b,e){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,e?"%20":"+")}function t(b,e){this.template=b+="#";this.defaults=e||{};var a=this.urlParams={};h(b.split(/\W/),function(f){f&&RegExp("(^|[^\\\\]):"+f+"\\W").test(b)&&(a[f]=!0)});this.template=b.replace(/\\:/g,":")}function u(b,e,a){function f(m,a){var b= 7 | {},a=o({},e,a);h(a,function(a,z){var c;a.charAt&&a.charAt(0)=="@"?(c=a.substr(1),c=y(c)(m)):c=a;b[z]=c});return b}function g(a){v(a||{},this)}var k=new t(b),a=o({},A,a);h(a,function(a,b){a.method=d.uppercase(a.method);var e=a.method=="POST"||a.method=="PUT"||a.method=="PATCH";g[b]=function(b,c,d,B){var j={},i,l=p,q=null;switch(arguments.length){case 4:q=B,l=d;case 3:case 2:if(r(c)){if(r(b)){l=b;q=c;break}l=c;q=d}else{j=b;i=c;l=d;break}case 1:r(b)?l=b:e?i=b:j=b;break;case 0:break;default:throw"Expected between 0-4 arguments [params, data, success, error], got "+ 8 | arguments.length+" arguments.";}var n=this instanceof g?this:a.isArray?[]:new g(i);x({method:a.method,url:k.url(o({},f(i,a.params||{}),j)),data:i}).then(function(b){var c=b.data;if(c)a.isArray?(n.length=0,h(c,function(a){n.push(new g(a))})):v(c,n);(l||p)(n,b.headers)},q);return n};g.prototype["$"+b]=function(a,d,h){var m=f(this),j=p,i;switch(arguments.length){case 3:m=a;j=d;i=h;break;case 2:case 1:r(a)?(j=a,i=d):(m=a,j=d||p);case 0:break;default:throw"Expected between 1-3 arguments [params, success, error], got "+ 9 | arguments.length+" arguments.";}g[b].call(this,m,e?this:w,j,i)}});g.bind=function(d){return u(b,o({},e,d),a)};return g}var A={get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}},p=d.noop,h=d.forEach,o=d.extend,v=d.copy,r=d.isFunction;t.prototype={url:function(b){var e=this,a=this.template,f,g,b=b||{};h(this.urlParams,function(h,c){f=b.hasOwnProperty(c)?b[c]:e.defaults[c];d.isDefined(f)&&f!==null?(g=s(f,!0).replace(/%26/gi,"&").replace(/%3D/gi, 10 | "=").replace(/%2B/gi,"+"),a=a.replace(RegExp(":"+c+"(\\W)","g"),g+"$1")):a=a.replace(RegExp("(/?):"+c+"(\\W)","g"),function(a,b,c){return c.charAt(0)=="/"?c:b+c})});var a=a.replace(/\/?#$/,""),k=[];h(b,function(a,b){e.urlParams[b]||k.push(s(b)+"="+s(a))});k.sort();a=a.replace(/\/*$/,"");return a+(k.length?"?"+k.join("&"):"")}};return u}])})(window,window.angular); 11 | -------------------------------------------------------------------------------- /app/index-async.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 43 | My AngularJS App 44 | 45 | 46 | 47 | 51 | 52 |
53 | 54 |
Angular seed app: v
55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/lib/angular/angular-sanitize.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.6 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(I,g){'use strict';function i(a){var d={},a=a.split(","),b;for(b=0;b=0;e--)if(f[e]==b)break;if(e>=0){for(c=f.length-1;c>=e;c--)d.end&&d.end(f[c]);f.length= 7 | e}}var c,h,f=[],j=a;for(f.last=function(){return f[f.length-1]};a;){h=!0;if(!f.last()||!q[f.last()]){if(a.indexOf("<\!--")===0)c=a.indexOf("--\>"),c>=0&&(d.comment&&d.comment(a.substring(4,c)),a=a.substring(c+3),h=!1);else if(B.test(a)){if(c=a.match(r))a=a.substring(c[0].length),c[0].replace(r,e),h=!1}else if(C.test(a)&&(c=a.match(s)))a=a.substring(c[0].length),c[0].replace(s,b),h=!1;h&&(c=a.indexOf("<"),h=c<0?a:a.substring(0,c),a=c<0?"":a.substring(c),d.chars&&d.chars(k(h)))}else a=a.replace(RegExp("(.*)<\\s*\\/\\s*"+ 8 | f.last()+"[^>]*>","i"),function(b,a){a=a.replace(D,"$1").replace(E,"$1");d.chars&&d.chars(k(a));return""}),e("",f.last());if(a==j)throw"Parse Error: "+a;j=a}e()}function k(a){l.innerHTML=a.replace(//g,">")}function u(a){var d=!1,b=g.bind(a,a.push);return{start:function(a,c,h){a=g.lowercase(a);!d&&q[a]&&(d=a);!d&&v[a]== 9 | !0&&(b("<"),b(a),g.forEach(c,function(a,c){var e=g.lowercase(c);if(G[e]==!0&&(w[e]!==!0||a.match(H)))b(" "),b(c),b('="'),b(t(a)),b('"')}),b(h?"/>":">"))},end:function(a){a=g.lowercase(a);!d&&v[a]==!0&&(b(""));a==d&&(d=!1)},chars:function(a){d||b(t(a))}}}var s=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,r=/^<\s*\/\s*([\w:-]+)[^>]*>/,A=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,C=/^/g, 10 | E=//g,H=/^((ftp|https?):\/\/|mailto:|#)/,F=/([^\#-~| |!])/g,p=i("area,br,col,hr,img,wbr"),x=i("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),y=i("rp,rt"),o=g.extend({},y,x),m=g.extend({},x,i("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),n=g.extend({},y,i("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")), 11 | q=i("script,style"),v=g.extend({},p,m,n,o),w=i("background,cite,href,longdesc,src,usemap"),G=g.extend({},w,i("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,span,start,summary,target,title,type,valign,value,vspace,width")),l=document.createElement("pre");g.module("ngSanitize",[]).value("$sanitize",function(a){var d=[]; 12 | z(a,u(d));return d.join("")});g.module("ngSanitize").directive("ngBindHtml",["$sanitize",function(a){return function(d,b,e){b.addClass("ng-binding").data("$binding",e.ngBindHtml);d.$watch(e.ngBindHtml,function(c){c=a(c);b.html(c||"")})}}]);g.module("ngSanitize").filter("linky",function(){var a=/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,d=/^mailto:/;return function(b){if(!b)return b;for(var e=b,c=[],h=u(c),f,g;b=e.match(a);)f=b[0],b[2]==b[3]&&(f="mailto:"+f),g=b.index, 13 | h.chars(e.substr(0,g)),h.start("a",{href:f}),h.chars(b[0].replace(d,"")),h.end("a"),e=e.substring(g+b[0].length);h.chars(e);return c.join("")}})})(window,window.angular); 14 | -------------------------------------------------------------------------------- /app/lib/angular/angular-cookies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.6 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) { 7 | 'use strict'; 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name ngCookies 12 | */ 13 | 14 | 15 | angular.module('ngCookies', ['ng']). 16 | /** 17 | * @ngdoc object 18 | * @name ngCookies.$cookies 19 | * @requires $browser 20 | * 21 | * @description 22 | * Provides read/write access to browser's cookies. 23 | * 24 | * Only a simple Object is exposed and by adding or removing properties to/from 25 | * this object, new cookies are created/deleted at the end of current $eval. 26 | * 27 | * @example 28 | 29 | 30 | 38 | 39 | 40 | */ 41 | factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) { 42 | var cookies = {}, 43 | lastCookies = {}, 44 | lastBrowserCookies, 45 | runEval = false, 46 | copy = angular.copy, 47 | isUndefined = angular.isUndefined; 48 | 49 | //creates a poller fn that copies all cookies from the $browser to service & inits the service 50 | $browser.addPollFn(function() { 51 | var currentCookies = $browser.cookies(); 52 | if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl 53 | lastBrowserCookies = currentCookies; 54 | copy(currentCookies, lastCookies); 55 | copy(currentCookies, cookies); 56 | if (runEval) $rootScope.$apply(); 57 | } 58 | })(); 59 | 60 | runEval = true; 61 | 62 | //at the end of each eval, push cookies 63 | //TODO: this should happen before the "delayed" watches fire, because if some cookies are not 64 | // strings or browser refuses to store some cookies, we update the model in the push fn. 65 | $rootScope.$watch(push); 66 | 67 | return cookies; 68 | 69 | 70 | /** 71 | * Pushes all the cookies from the service to the browser and verifies if all cookies were stored. 72 | */ 73 | function push() { 74 | var name, 75 | value, 76 | browserCookies, 77 | updated; 78 | 79 | //delete any cookies deleted in $cookies 80 | for (name in lastCookies) { 81 | if (isUndefined(cookies[name])) { 82 | $browser.cookies(name, undefined); 83 | } 84 | } 85 | 86 | //update all cookies updated in $cookies 87 | for(name in cookies) { 88 | value = cookies[name]; 89 | if (!angular.isString(value)) { 90 | if (angular.isDefined(lastCookies[name])) { 91 | cookies[name] = lastCookies[name]; 92 | } else { 93 | delete cookies[name]; 94 | } 95 | } else if (value !== lastCookies[name]) { 96 | $browser.cookies(name, value); 97 | updated = true; 98 | } 99 | } 100 | 101 | //verify what was actually stored 102 | if (updated){ 103 | updated = false; 104 | browserCookies = $browser.cookies(); 105 | 106 | for (name in cookies) { 107 | if (cookies[name] !== browserCookies[name]) { 108 | //delete or reset all cookies that the browser dropped from $cookies 109 | if (isUndefined(browserCookies[name])) { 110 | delete cookies[name]; 111 | } else { 112 | cookies[name] = browserCookies[name]; 113 | } 114 | updated = true; 115 | } 116 | } 117 | } 118 | } 119 | }]). 120 | 121 | 122 | /** 123 | * @ngdoc object 124 | * @name ngCookies.$cookieStore 125 | * @requires $cookies 126 | * 127 | * @description 128 | * Provides a key-value (string-object) storage, that is backed by session cookies. 129 | * Objects put or retrieved from this storage are automatically serialized or 130 | * deserialized by angular's toJson/fromJson. 131 | * @example 132 | */ 133 | factory('$cookieStore', ['$cookies', function($cookies) { 134 | 135 | return { 136 | /** 137 | * @ngdoc method 138 | * @name ngCookies.$cookieStore#get 139 | * @methodOf ngCookies.$cookieStore 140 | * 141 | * @description 142 | * Returns the value of given cookie key 143 | * 144 | * @param {string} key Id to use for lookup. 145 | * @returns {Object} Deserialized cookie value. 146 | */ 147 | get: function(key) { 148 | return angular.fromJson($cookies[key]); 149 | }, 150 | 151 | /** 152 | * @ngdoc method 153 | * @name ngCookies.$cookieStore#put 154 | * @methodOf ngCookies.$cookieStore 155 | * 156 | * @description 157 | * Sets a value for given cookie key 158 | * 159 | * @param {string} key Id for the `value`. 160 | * @param {Object} value Value to be stored. 161 | */ 162 | put: function(key, value) { 163 | $cookies[key] = angular.toJson(value); 164 | }, 165 | 166 | /** 167 | * @ngdoc method 168 | * @name ngCookies.$cookieStore#remove 169 | * @methodOf ngCookies.$cookieStore 170 | * 171 | * @description 172 | * Remove given cookie 173 | * 174 | * @param {string} key Id of the key-value pair to delete. 175 | */ 176 | remove: function(key) { 177 | delete $cookies[key]; 178 | } 179 | }; 180 | 181 | }]); 182 | 183 | 184 | })(window, window.angular); 185 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-seed — the seed for AngularJS apps 2 | 3 | This project is an application skeleton for a typical [AngularJS](http://angularjs.org/) web app. 4 | You can use it to quickly bootstrap your angular webapp projects and dev environment for these 5 | projects. 6 | 7 | The seed contains AngularJS libraries, test libraries and a bunch of scripts all preconfigured for 8 | instant web development gratification. Just clone the repo (or download the zip/tarball), start up 9 | our (or yours) webserver and you are ready to develop and test your application. 10 | 11 | The seed app doesn't do much, just shows how to wire two controllers and views together. You can 12 | check it out by opening app/index.html in your browser (might not work file `file://` scheme in 13 | certain browsers, see note below). 14 | 15 | _Note: While angular is client-side-only technology and it's possible to create angular webapps that 16 | don't require a backend server at all, we recommend hosting the project files using a local 17 | webserver during development to avoid issues with security restrictions (sandbox) in browsers. The 18 | sandbox implementation varies between browsers, but quite often prevents things like cookies, xhr, 19 | etc to function properly when an html page is opened via `file://` scheme instead of `http://`._ 20 | 21 | 22 | ## How to use angular-seed 23 | 24 | Clone the angular-seed repository and start hacking... 25 | 26 | 27 | ### Running the app during development 28 | 29 | You can pick one of these options: 30 | 31 | * serve this repository with your webserver 32 | * install node.js and run `scripts/web-server.js` 33 | 34 | Then navigate your browser to `http://localhost:/app/index.html` to see the app running in 35 | your browser. 36 | 37 | 38 | ### Running the app in production 39 | 40 | This really depends on how complex is your app and the overall infrastructure of your system, but 41 | the general rule is that all you need in production are all the files under the `app/` directory. 42 | Everything else should be omitted. 43 | 44 | Angular apps are really just a bunch of static html, css and js files that just need to be hosted 45 | somewhere, where they can be accessed by browsers. 46 | 47 | If your Angular app is talking to the backend server via xhr or other means, you need to figure 48 | out what is the best way to host the static files to comply with the same origin policy if 49 | applicable. Usually this is done by hosting the files by the backend server or through 50 | reverse-proxying the backend server(s) and a webserver(s). 51 | 52 | 53 | ### Running unit tests 54 | 55 | We recommend using [jasmine](http://pivotal.github.com/jasmine/) and 56 | [Karma](http://karma-runner.github.io) for your unit tests/specs, but you are free 57 | to use whatever works for you. 58 | 59 | Requires [node.js](http://nodejs.org/), Karma (`sudo npm install -g karma`) and a local 60 | or remote browser. 61 | 62 | * start `scripts/test.sh` (on windows: `scripts\test.bat`) 63 | * a browser will start and connect to the Karma server (Chrome is default browser, others can be captured by loading the same url as the one in Chrome or by changing the `config/karma.conf.js` file) 64 | * to run or re-run tests just change any of your source or test javascript files 65 | 66 | 67 | ### End to end testing 68 | 69 | Angular ships with a baked-in end-to-end test runner that understands angular, your app and allows 70 | you to write your tests with jasmine-like BDD syntax. 71 | 72 | Requires a webserver, node.js + `./scripts/web-server.js` or your backend server that hosts the angular static files. 73 | 74 | Check out the 75 | [end-to-end runner's documentation](http://docs.angularjs.org/guide/dev_guide.e2e-testing) for more 76 | info. 77 | 78 | * create your end-to-end tests in `test/e2e/scenarios.js` 79 | * serve your project directory with your http/backend server or node.js + `scripts/web-server.js` 80 | * to run do one of: 81 | * open `http://localhost:port/test/e2e/runner.html` in your browser 82 | * run the tests from console with [Karma](http://karma-runner.github.io) via 83 | `scripts/e2e-test.sh` or `script/e2e-test.bat` 84 | 85 | ### Continuous Integration 86 | 87 | CloudBees have provided a CI/deployment setup: 88 | 89 | 90 | 91 | If you run this, you will get a cloned version of this repo to start working on in a private git repo, 92 | along with a CI service (in Jenkins) hosted that will run unit and end to end tests in both Firefox and Chrome. 93 | 94 | ### Receiving updates from upstream 95 | 96 | When we upgrade angular-seed's repo with newer angular or testing library code, you can just 97 | fetch the changes and merge them into your project with git. 98 | 99 | 100 | ## Directory Layout 101 | 102 | app/ --> all of the files to be used in production 103 | css/ --> css files 104 | app.css --> default stylesheet 105 | img/ --> image files 106 | index.html --> app layout file (the main html template file of the app) 107 | index-async.html --> just like index.html, but loads js files asynchronously 108 | js/ --> javascript files 109 | app.js --> application 110 | controllers.js --> application controllers 111 | directives.js --> application directives 112 | filters.js --> custom angular filters 113 | services.js --> custom angular services 114 | lib/ --> angular and 3rd party javascript libraries 115 | angular/ 116 | angular.js --> the latest angular js 117 | angular.min.js --> the latest minified angular js 118 | angular-*.js --> angular add-on modules 119 | version.txt --> version number 120 | partials/ --> angular view partials (partial html templates) 121 | partial1.html 122 | partial2.html 123 | 124 | config/karma.conf.js --> config file for running unit tests with Karma 125 | config/karma-e2e.conf.js --> config file for running e2e tests with Karma 126 | 127 | scripts/ --> handy shell/js/ruby scripts 128 | e2e-test.sh --> runs end-to-end tests with Karma (*nix) 129 | e2e-test.bat --> runs end-to-end tests with Karma (windows) 130 | test.bat --> autotests unit tests with Karma (windows) 131 | test.sh --> autotests unit tests with Karma (*nix) 132 | web-server.js --> simple development webserver based on node.js 133 | 134 | test/ --> test source files and libraries 135 | e2e/ --> 136 | runner.html --> end-to-end test runner (open in your browser to run) 137 | scenarios.js --> end-to-end specs 138 | lib/ 139 | angular/ --> angular testing libraries 140 | angular-mocks.js --> mocks that replace certain angular services in tests 141 | angular-scenario.js --> angular's scenario (end-to-end) test runner library 142 | version.txt --> version file 143 | unit/ --> unit level specs/tests 144 | controllersSpec.js --> specs for controllers 145 | directivessSpec.js --> specs for directives 146 | filtersSpec.js --> specs for filters 147 | servicesSpec.js --> specs for services 148 | 149 | ## Contact 150 | 151 | For more information on AngularJS please check out http://angularjs.org/ 152 | -------------------------------------------------------------------------------- /scripts/web-server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var util = require('util'), 4 | http = require('http'), 5 | fs = require('fs'), 6 | url = require('url'), 7 | events = require('events'); 8 | 9 | var DEFAULT_PORT = 8000; 10 | 11 | function main(argv) { 12 | new HttpServer({ 13 | 'GET': createServlet(StaticServlet), 14 | 'HEAD': createServlet(StaticServlet) 15 | }).start(Number(argv[2]) || DEFAULT_PORT); 16 | } 17 | 18 | function escapeHtml(value) { 19 | return value.toString(). 20 | replace('<', '<'). 21 | replace('>', '>'). 22 | replace('"', '"'); 23 | } 24 | 25 | function createServlet(Class) { 26 | var servlet = new Class(); 27 | return servlet.handleRequest.bind(servlet); 28 | } 29 | 30 | /** 31 | * An Http server implementation that uses a map of methods to decide 32 | * action routing. 33 | * 34 | * @param {Object} Map of method => Handler function 35 | */ 36 | function HttpServer(handlers) { 37 | this.handlers = handlers; 38 | this.server = http.createServer(this.handleRequest_.bind(this)); 39 | } 40 | 41 | HttpServer.prototype.start = function(port) { 42 | this.port = port; 43 | this.server.listen(port); 44 | util.puts('Http Server running at http://localhost:' + port + '/'); 45 | }; 46 | 47 | HttpServer.prototype.parseUrl_ = function(urlString) { 48 | var parsed = url.parse(urlString); 49 | parsed.pathname = url.resolve('/', parsed.pathname); 50 | return url.parse(url.format(parsed), true); 51 | }; 52 | 53 | HttpServer.prototype.handleRequest_ = function(req, res) { 54 | var logEntry = req.method + ' ' + req.url; 55 | if (req.headers['user-agent']) { 56 | logEntry += ' ' + req.headers['user-agent']; 57 | } 58 | util.puts(logEntry); 59 | req.url = this.parseUrl_(req.url); 60 | var handler = this.handlers[req.method]; 61 | if (!handler) { 62 | res.writeHead(501); 63 | res.end(); 64 | } else { 65 | handler.call(this, req, res); 66 | } 67 | }; 68 | 69 | /** 70 | * Handles static content. 71 | */ 72 | function StaticServlet() {} 73 | 74 | StaticServlet.MimeMap = { 75 | 'txt': 'text/plain', 76 | 'html': 'text/html', 77 | 'css': 'text/css', 78 | 'xml': 'application/xml', 79 | 'json': 'application/json', 80 | 'js': 'application/javascript', 81 | 'jpg': 'image/jpeg', 82 | 'jpeg': 'image/jpeg', 83 | 'gif': 'image/gif', 84 | 'png': 'image/png', 85 |   'svg': 'image/svg+xml' 86 | }; 87 | 88 | StaticServlet.prototype.handleRequest = function(req, res) { 89 | var self = this; 90 | var path = ('./' + req.url.pathname).replace('//','/').replace(/%(..)/g, function(match, hex){ 91 | return String.fromCharCode(parseInt(hex, 16)); 92 | }); 93 | var parts = path.split('/'); 94 | if (parts[parts.length-1].charAt(0) === '.') 95 | return self.sendForbidden_(req, res, path); 96 | fs.stat(path, function(err, stat) { 97 | if (err) 98 | return self.sendMissing_(req, res, path); 99 | if (stat.isDirectory()) 100 | return self.sendDirectory_(req, res, path); 101 | return self.sendFile_(req, res, path); 102 | }); 103 | } 104 | 105 | StaticServlet.prototype.sendError_ = function(req, res, error) { 106 | res.writeHead(500, { 107 | 'Content-Type': 'text/html' 108 | }); 109 | res.write('\n'); 110 | res.write('Internal Server Error\n'); 111 | res.write('

Internal Server Error

'); 112 | res.write('
' + escapeHtml(util.inspect(error)) + '
'); 113 | util.puts('500 Internal Server Error'); 114 | util.puts(util.inspect(error)); 115 | }; 116 | 117 | StaticServlet.prototype.sendMissing_ = function(req, res, path) { 118 | path = path.substring(1); 119 | res.writeHead(404, { 120 | 'Content-Type': 'text/html' 121 | }); 122 | res.write('\n'); 123 | res.write('404 Not Found\n'); 124 | res.write('

Not Found

'); 125 | res.write( 126 | '

The requested URL ' + 127 | escapeHtml(path) + 128 | ' was not found on this server.

' 129 | ); 130 | res.end(); 131 | util.puts('404 Not Found: ' + path); 132 | }; 133 | 134 | StaticServlet.prototype.sendForbidden_ = function(req, res, path) { 135 | path = path.substring(1); 136 | res.writeHead(403, { 137 | 'Content-Type': 'text/html' 138 | }); 139 | res.write('\n'); 140 | res.write('403 Forbidden\n'); 141 | res.write('

Forbidden

'); 142 | res.write( 143 | '

You do not have permission to access ' + 144 | escapeHtml(path) + ' on this server.

' 145 | ); 146 | res.end(); 147 | util.puts('403 Forbidden: ' + path); 148 | }; 149 | 150 | StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) { 151 | res.writeHead(301, { 152 | 'Content-Type': 'text/html', 153 | 'Location': redirectUrl 154 | }); 155 | res.write('\n'); 156 | res.write('301 Moved Permanently\n'); 157 | res.write('

Moved Permanently

'); 158 | res.write( 159 | '

The document has moved here.

' 162 | ); 163 | res.end(); 164 | util.puts('301 Moved Permanently: ' + redirectUrl); 165 | }; 166 | 167 | StaticServlet.prototype.sendFile_ = function(req, res, path) { 168 | var self = this; 169 | var file = fs.createReadStream(path); 170 | res.writeHead(200, { 171 | 'Content-Type': StaticServlet. 172 | MimeMap[path.split('.').pop()] || 'text/plain' 173 | }); 174 | if (req.method === 'HEAD') { 175 | res.end(); 176 | } else { 177 | file.on('data', res.write.bind(res)); 178 | file.on('close', function() { 179 | res.end(); 180 | }); 181 | file.on('error', function(error) { 182 | self.sendError_(req, res, error); 183 | }); 184 | } 185 | }; 186 | 187 | StaticServlet.prototype.sendDirectory_ = function(req, res, path) { 188 | var self = this; 189 | if (path.match(/[^\/]$/)) { 190 | req.url.pathname += '/'; 191 | var redirectUrl = url.format(url.parse(url.format(req.url))); 192 | return self.sendRedirect_(req, res, redirectUrl); 193 | } 194 | fs.readdir(path, function(err, files) { 195 | if (err) 196 | return self.sendError_(req, res, error); 197 | 198 | if (!files.length) 199 | return self.writeDirectoryIndex_(req, res, path, []); 200 | 201 | var remaining = files.length; 202 | files.forEach(function(fileName, index) { 203 | fs.stat(path + '/' + fileName, function(err, stat) { 204 | if (err) 205 | return self.sendError_(req, res, err); 206 | if (stat.isDirectory()) { 207 | files[index] = fileName + '/'; 208 | } 209 | if (!(--remaining)) 210 | return self.writeDirectoryIndex_(req, res, path, files); 211 | }); 212 | }); 213 | }); 214 | }; 215 | 216 | StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) { 217 | path = path.substring(1); 218 | res.writeHead(200, { 219 | 'Content-Type': 'text/html' 220 | }); 221 | if (req.method === 'HEAD') { 222 | res.end(); 223 | return; 224 | } 225 | res.write('\n'); 226 | res.write('' + escapeHtml(path) + '\n'); 227 | res.write('\n'); 230 | res.write('

Directory: ' + escapeHtml(path) + '

'); 231 | res.write('
    '); 232 | files.forEach(function(fileName) { 233 | if (fileName.charAt(0) !== '.') { 234 | res.write('
  1. ' + 236 | escapeHtml(fileName) + '
  2. '); 237 | } 238 | }); 239 | res.write('
'); 240 | res.end(); 241 | }; 242 | 243 | // Must be last, 244 | main(process.argv); 245 | -------------------------------------------------------------------------------- /app/lib/angular/angular-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.6 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | 7 | ( 8 | 9 | /** 10 | * @ngdoc interface 11 | * @name angular.Module 12 | * @description 13 | * 14 | * Interface for configuring angular {@link angular.module modules}. 15 | */ 16 | 17 | function setupModuleLoader(window) { 18 | 19 | function ensure(obj, name, factory) { 20 | return obj[name] || (obj[name] = factory()); 21 | } 22 | 23 | return ensure(ensure(window, 'angular', Object), 'module', function() { 24 | /** @type {Object.} */ 25 | var modules = {}; 26 | 27 | /** 28 | * @ngdoc function 29 | * @name angular.module 30 | * @description 31 | * 32 | * The `angular.module` is a global place for creating and registering Angular modules. All 33 | * modules (angular core or 3rd party) that should be available to an application must be 34 | * registered using this mechanism. 35 | * 36 | * 37 | * # Module 38 | * 39 | * A module is a collocation of services, directives, filters, and configuration information. Module 40 | * is used to configure the {@link AUTO.$injector $injector}. 41 | * 42 | *
 43 |      * // Create a new module
 44 |      * var myModule = angular.module('myModule', []);
 45 |      *
 46 |      * // register a new service
 47 |      * myModule.value('appName', 'MyCoolApp');
 48 |      *
 49 |      * // configure existing services inside initialization blocks.
 50 |      * myModule.config(function($locationProvider) {
 51 | 'use strict';
 52 |      *   // Configure existing providers
 53 |      *   $locationProvider.hashPrefix('!');
 54 |      * });
 55 |      * 
56 | * 57 | * Then you can create an injector and load your modules like this: 58 | * 59 | *
 60 |      * var injector = angular.injector(['ng', 'MyModule'])
 61 |      * 
62 | * 63 | * However it's more likely that you'll just use 64 | * {@link ng.directive:ngApp ngApp} or 65 | * {@link angular.bootstrap} to simplify this process for you. 66 | * 67 | * @param {!string} name The name of the module to create or retrieve. 68 | * @param {Array.=} requires If specified then new module is being created. If unspecified then the 69 | * the module is being retrieved for further configuration. 70 | * @param {Function} configFn Optional configuration function for the module. Same as 71 | * {@link angular.Module#config Module#config()}. 72 | * @returns {module} new module with the {@link angular.Module} api. 73 | */ 74 | return function module(name, requires, configFn) { 75 | if (requires && modules.hasOwnProperty(name)) { 76 | modules[name] = null; 77 | } 78 | return ensure(modules, name, function() { 79 | if (!requires) { 80 | throw Error('No module: ' + name); 81 | } 82 | 83 | /** @type {!Array.>} */ 84 | var invokeQueue = []; 85 | 86 | /** @type {!Array.} */ 87 | var runBlocks = []; 88 | 89 | var config = invokeLater('$injector', 'invoke'); 90 | 91 | /** @type {angular.Module} */ 92 | var moduleInstance = { 93 | // Private state 94 | _invokeQueue: invokeQueue, 95 | _runBlocks: runBlocks, 96 | 97 | /** 98 | * @ngdoc property 99 | * @name angular.Module#requires 100 | * @propertyOf angular.Module 101 | * @returns {Array.} List of module names which must be loaded before this module. 102 | * @description 103 | * Holds the list of modules which the injector will load before the current module is loaded. 104 | */ 105 | requires: requires, 106 | 107 | /** 108 | * @ngdoc property 109 | * @name angular.Module#name 110 | * @propertyOf angular.Module 111 | * @returns {string} Name of the module. 112 | * @description 113 | */ 114 | name: name, 115 | 116 | 117 | /** 118 | * @ngdoc method 119 | * @name angular.Module#provider 120 | * @methodOf angular.Module 121 | * @param {string} name service name 122 | * @param {Function} providerType Construction function for creating new instance of the service. 123 | * @description 124 | * See {@link AUTO.$provide#provider $provide.provider()}. 125 | */ 126 | provider: invokeLater('$provide', 'provider'), 127 | 128 | /** 129 | * @ngdoc method 130 | * @name angular.Module#factory 131 | * @methodOf angular.Module 132 | * @param {string} name service name 133 | * @param {Function} providerFunction Function for creating new instance of the service. 134 | * @description 135 | * See {@link AUTO.$provide#factory $provide.factory()}. 136 | */ 137 | factory: invokeLater('$provide', 'factory'), 138 | 139 | /** 140 | * @ngdoc method 141 | * @name angular.Module#service 142 | * @methodOf angular.Module 143 | * @param {string} name service name 144 | * @param {Function} constructor A constructor function that will be instantiated. 145 | * @description 146 | * See {@link AUTO.$provide#service $provide.service()}. 147 | */ 148 | service: invokeLater('$provide', 'service'), 149 | 150 | /** 151 | * @ngdoc method 152 | * @name angular.Module#value 153 | * @methodOf angular.Module 154 | * @param {string} name service name 155 | * @param {*} object Service instance object. 156 | * @description 157 | * See {@link AUTO.$provide#value $provide.value()}. 158 | */ 159 | value: invokeLater('$provide', 'value'), 160 | 161 | /** 162 | * @ngdoc method 163 | * @name angular.Module#constant 164 | * @methodOf angular.Module 165 | * @param {string} name constant name 166 | * @param {*} object Constant value. 167 | * @description 168 | * Because the constant are fixed, they get applied before other provide methods. 169 | * See {@link AUTO.$provide#constant $provide.constant()}. 170 | */ 171 | constant: invokeLater('$provide', 'constant', 'unshift'), 172 | 173 | /** 174 | * @ngdoc method 175 | * @name angular.Module#filter 176 | * @methodOf angular.Module 177 | * @param {string} name Filter name. 178 | * @param {Function} filterFactory Factory function for creating new instance of filter. 179 | * @description 180 | * See {@link ng.$filterProvider#register $filterProvider.register()}. 181 | */ 182 | filter: invokeLater('$filterProvider', 'register'), 183 | 184 | /** 185 | * @ngdoc method 186 | * @name angular.Module#controller 187 | * @methodOf angular.Module 188 | * @param {string} name Controller name. 189 | * @param {Function} constructor Controller constructor function. 190 | * @description 191 | * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 192 | */ 193 | controller: invokeLater('$controllerProvider', 'register'), 194 | 195 | /** 196 | * @ngdoc method 197 | * @name angular.Module#directive 198 | * @methodOf angular.Module 199 | * @param {string} name directive name 200 | * @param {Function} directiveFactory Factory function for creating new instance of 201 | * directives. 202 | * @description 203 | * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 204 | */ 205 | directive: invokeLater('$compileProvider', 'directive'), 206 | 207 | /** 208 | * @ngdoc method 209 | * @name angular.Module#config 210 | * @methodOf angular.Module 211 | * @param {Function} configFn Execute this function on module load. Useful for service 212 | * configuration. 213 | * @description 214 | * Use this method to register work which needs to be performed on module loading. 215 | */ 216 | config: config, 217 | 218 | /** 219 | * @ngdoc method 220 | * @name angular.Module#run 221 | * @methodOf angular.Module 222 | * @param {Function} initializationFn Execute this function after injector creation. 223 | * Useful for application initialization. 224 | * @description 225 | * Use this method to register work which should be performed when the injector is done 226 | * loading all modules. 227 | */ 228 | run: function(block) { 229 | runBlocks.push(block); 230 | return this; 231 | } 232 | }; 233 | 234 | if (configFn) { 235 | config(configFn); 236 | } 237 | 238 | return moduleInstance; 239 | 240 | /** 241 | * @param {string} provider 242 | * @param {string} method 243 | * @param {String=} insertMethod 244 | * @returns {angular.Module} 245 | */ 246 | function invokeLater(provider, method, insertMethod) { 247 | return function() { 248 | invokeQueue[insertMethod || 'push']([provider, method, arguments]); 249 | return moduleInstance; 250 | } 251 | } 252 | }); 253 | }; 254 | }); 255 | 256 | } 257 | 258 | )(window); 259 | 260 | /** 261 | * Closure compiler type information 262 | * 263 | * @typedef { { 264 | * requires: !Array., 265 | * invokeQueue: !Array.>, 266 | * 267 | * service: function(string, Function):angular.Module, 268 | * factory: function(string, Function):angular.Module, 269 | * value: function(string, *):angular.Module, 270 | * 271 | * filter: function(string, Function):angular.Module, 272 | * 273 | * init: function(Function):angular.Module 274 | * } } 275 | */ 276 | angular.Module; 277 | 278 | -------------------------------------------------------------------------------- /app/lib/angular/angular-resource.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.6 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) { 7 | 'use strict'; 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name ngResource 12 | * @description 13 | */ 14 | 15 | /** 16 | * @ngdoc object 17 | * @name ngResource.$resource 18 | * @requires $http 19 | * 20 | * @description 21 | * A factory which creates a resource object that lets you interact with 22 | * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. 23 | * 24 | * The returned resource object has action methods which provide high-level behaviors without 25 | * the need to interact with the low level {@link ng.$http $http} service. 26 | * 27 | * # Installation 28 | * To use $resource make sure you have included the `angular-resource.js` that comes in Angular 29 | * package. You can also find this file on Google CDN, bower as well as at 30 | * {@link http://code.angularjs.org/ code.angularjs.org}. 31 | * 32 | * Finally load the module in your application: 33 | * 34 | * angular.module('app', ['ngResource']); 35 | * 36 | * and you are ready to get started! 37 | * 38 | * @param {string} url A parameterized URL template with parameters prefixed by `:` as in 39 | * `/user/:username`. If you are using a URL with a port number (e.g. 40 | * `http://example.com:8080/api`), you'll need to escape the colon character before the port 41 | * number, like this: `$resource('http://example.com\\:8080/api')`. 42 | * 43 | * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in 44 | * `actions` methods. 45 | * 46 | * Each key value in the parameter object is first bound to url template if present and then any 47 | * excess keys are appended to the url search query after the `?`. 48 | * 49 | * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in 50 | * URL `/path/greet?salutation=Hello`. 51 | * 52 | * If the parameter value is prefixed with `@` then the value of that parameter is extracted from 53 | * the data object (useful for non-GET operations). 54 | * 55 | * @param {Object.=} actions Hash with declaration of custom action that should extend the 56 | * default set of resource actions. The declaration should be created in the following format: 57 | * 58 | * {action1: {method:?, params:?, isArray:?}, 59 | * action2: {method:?, params:?, isArray:?}, 60 | * ...} 61 | * 62 | * Where: 63 | * 64 | * - `action` – {string} – The name of action. This name becomes the name of the method on your 65 | * resource object. 66 | * - `method` – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`, 67 | * and `JSONP` 68 | * - `params` – {object=} – Optional set of pre-bound parameters for this action. 69 | * - isArray – {boolean=} – If true then the returned object for this action is an array, see 70 | * `returns` section. 71 | * 72 | * @returns {Object} A resource "class" object with methods for the default set of resource actions 73 | * optionally extended with custom `actions`. The default set contains these actions: 74 | * 75 | * { 'get': {method:'GET'}, 76 | * 'save': {method:'POST'}, 77 | * 'query': {method:'GET', isArray:true}, 78 | * 'remove': {method:'DELETE'}, 79 | * 'delete': {method:'DELETE'} }; 80 | * 81 | * Calling these methods invoke an {@link ng.$http} with the specified http method, 82 | * destination and parameters. When the data is returned from the server then the object is an 83 | * instance of the resource class. The actions `save`, `remove` and `delete` are available on it 84 | * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, 85 | * read, update, delete) on server-side data like this: 86 | *
 87 |         var User = $resource('/user/:userId', {userId:'@id'});
 88 |         var user = User.get({userId:123}, function() {
 89 |           user.abc = true;
 90 |           user.$save();
 91 |         });
 92 |      
93 | * 94 | * It is important to realize that invoking a $resource object method immediately returns an 95 | * empty reference (object or array depending on `isArray`). Once the data is returned from the 96 | * server the existing reference is populated with the actual data. This is a useful trick since 97 | * usually the resource is assigned to a model which is then rendered by the view. Having an empty 98 | * object results in no rendering, once the data arrives from the server then the object is 99 | * populated with the data and the view automatically re-renders itself showing the new data. This 100 | * means that in most case one never has to write a callback function for the action methods. 101 | * 102 | * The action methods on the class object or instance object can be invoked with the following 103 | * parameters: 104 | * 105 | * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` 106 | * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` 107 | * - non-GET instance actions: `instance.$action([parameters], [success], [error])` 108 | * 109 | * 110 | * @example 111 | * 112 | * # Credit card resource 113 | * 114 | *
115 |      // Define CreditCard class
116 |      var CreditCard = $resource('/user/:userId/card/:cardId',
117 |       {userId:123, cardId:'@id'}, {
118 |        charge: {method:'POST', params:{charge:true}}
119 |       });
120 | 
121 |      // We can retrieve a collection from the server
122 |      var cards = CreditCard.query(function() {
123 |        // GET: /user/123/card
124 |        // server returns: [ {id:456, number:'1234', name:'Smith'} ];
125 | 
126 |        var card = cards[0];
127 |        // each item is an instance of CreditCard
128 |        expect(card instanceof CreditCard).toEqual(true);
129 |        card.name = "J. Smith";
130 |        // non GET methods are mapped onto the instances
131 |        card.$save();
132 |        // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
133 |        // server returns: {id:456, number:'1234', name: 'J. Smith'};
134 | 
135 |        // our custom method is mapped as well.
136 |        card.$charge({amount:9.99});
137 |        // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
138 |      });
139 | 
140 |      // we can create an instance as well
141 |      var newCard = new CreditCard({number:'0123'});
142 |      newCard.name = "Mike Smith";
143 |      newCard.$save();
144 |      // POST: /user/123/card {number:'0123', name:'Mike Smith'}
145 |      // server returns: {id:789, number:'01234', name: 'Mike Smith'};
146 |      expect(newCard.id).toEqual(789);
147 |  * 
148 | * 149 | * The object returned from this function execution is a resource "class" which has "static" method 150 | * for each action in the definition. 151 | * 152 | * Calling these methods invoke `$http` on the `url` template with the given `method` and `params`. 153 | * When the data is returned from the server then the object is an instance of the resource type and 154 | * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD 155 | * operations (create, read, update, delete) on server-side data. 156 | 157 |
158 |      var User = $resource('/user/:userId', {userId:'@id'});
159 |      var user = User.get({userId:123}, function() {
160 |        user.abc = true;
161 |        user.$save();
162 |      });
163 |    
164 | * 165 | * It's worth noting that the success callback for `get`, `query` and other method gets passed 166 | * in the response that came from the server as well as $http header getter function, so one 167 | * could rewrite the above example and get access to http headers as: 168 | * 169 |
170 |      var User = $resource('/user/:userId', {userId:'@id'});
171 |      User.get({userId:123}, function(u, getResponseHeaders){
172 |        u.abc = true;
173 |        u.$save(function(u, putResponseHeaders) {
174 |          //u => saved user object
175 |          //putResponseHeaders => $http header getter
176 |        });
177 |      });
178 |    
179 | 180 | * # Buzz client 181 | 182 | Let's look at what a buzz client created with the `$resource` service looks like: 183 | 184 | 185 | 205 | 206 |
207 | 208 | 209 |
210 |
211 |

212 | 213 | {{item.actor.name}} 214 | Expand replies: {{item.links.replies[0].count}} 215 |

216 | {{item.object.content | html}} 217 |
218 | 219 | {{reply.actor.name}}: {{reply.content | html}} 220 |
221 |
222 |
223 |
224 | 225 | 226 |
227 | */ 228 | angular.module('ngResource', ['ng']). 229 | factory('$resource', ['$http', '$parse', function($http, $parse) { 230 | var DEFAULT_ACTIONS = { 231 | 'get': {method:'GET'}, 232 | 'save': {method:'POST'}, 233 | 'query': {method:'GET', isArray:true}, 234 | 'remove': {method:'DELETE'}, 235 | 'delete': {method:'DELETE'} 236 | }; 237 | var noop = angular.noop, 238 | forEach = angular.forEach, 239 | extend = angular.extend, 240 | copy = angular.copy, 241 | isFunction = angular.isFunction, 242 | getter = function(obj, path) { 243 | return $parse(path)(obj); 244 | }; 245 | 246 | /** 247 | * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 248 | * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 249 | * segments: 250 | * segment = *pchar 251 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 252 | * pct-encoded = "%" HEXDIG HEXDIG 253 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 254 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 255 | * / "*" / "+" / "," / ";" / "=" 256 | */ 257 | function encodeUriSegment(val) { 258 | return encodeUriQuery(val, true). 259 | replace(/%26/gi, '&'). 260 | replace(/%3D/gi, '='). 261 | replace(/%2B/gi, '+'); 262 | } 263 | 264 | 265 | /** 266 | * This method is intended for encoding *key* or *value* parts of query component. We need a custom 267 | * method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be 268 | * encoded per http://tools.ietf.org/html/rfc3986: 269 | * query = *( pchar / "/" / "?" ) 270 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 271 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 272 | * pct-encoded = "%" HEXDIG HEXDIG 273 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 274 | * / "*" / "+" / "," / ";" / "=" 275 | */ 276 | function encodeUriQuery(val, pctEncodeSpaces) { 277 | return encodeURIComponent(val). 278 | replace(/%40/gi, '@'). 279 | replace(/%3A/gi, ':'). 280 | replace(/%24/g, '$'). 281 | replace(/%2C/gi, ','). 282 | replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 283 | } 284 | 285 | function Route(template, defaults) { 286 | this.template = template = template + '#'; 287 | this.defaults = defaults || {}; 288 | var urlParams = this.urlParams = {}; 289 | forEach(template.split(/\W/), function(param){ 290 | if (param && (new RegExp("(^|[^\\\\]):" + param + "\\W").test(template))) { 291 | urlParams[param] = true; 292 | } 293 | }); 294 | this.template = template.replace(/\\:/g, ':'); 295 | } 296 | 297 | Route.prototype = { 298 | url: function(params) { 299 | var self = this, 300 | url = this.template, 301 | val, 302 | encodedVal; 303 | 304 | params = params || {}; 305 | forEach(this.urlParams, function(_, urlParam){ 306 | val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; 307 | if (angular.isDefined(val) && val !== null) { 308 | encodedVal = encodeUriSegment(val); 309 | url = url.replace(new RegExp(":" + urlParam + "(\\W)", "g"), encodedVal + "$1"); 310 | } else { 311 | url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W)", "g"), function(match, 312 | leadingSlashes, tail) { 313 | if (tail.charAt(0) == '/') { 314 | return tail; 315 | } else { 316 | return leadingSlashes + tail; 317 | } 318 | }); 319 | } 320 | }); 321 | url = url.replace(/\/?#$/, ''); 322 | var query = []; 323 | forEach(params, function(value, key){ 324 | if (!self.urlParams[key]) { 325 | query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value)); 326 | } 327 | }); 328 | query.sort(); 329 | url = url.replace(/\/*$/, ''); 330 | return url + (query.length ? '?' + query.join('&') : ''); 331 | } 332 | }; 333 | 334 | 335 | function ResourceFactory(url, paramDefaults, actions) { 336 | var route = new Route(url); 337 | 338 | actions = extend({}, DEFAULT_ACTIONS, actions); 339 | 340 | function extractParams(data, actionParams){ 341 | var ids = {}; 342 | actionParams = extend({}, paramDefaults, actionParams); 343 | forEach(actionParams, function(value, key){ 344 | ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value; 345 | }); 346 | return ids; 347 | } 348 | 349 | function Resource(value){ 350 | copy(value || {}, this); 351 | } 352 | 353 | forEach(actions, function(action, name) { 354 | action.method = angular.uppercase(action.method); 355 | var hasBody = action.method == 'POST' || action.method == 'PUT' || action.method == 'PATCH'; 356 | Resource[name] = function(a1, a2, a3, a4) { 357 | var params = {}; 358 | var data; 359 | var success = noop; 360 | var error = null; 361 | switch(arguments.length) { 362 | case 4: 363 | error = a4; 364 | success = a3; 365 | //fallthrough 366 | case 3: 367 | case 2: 368 | if (isFunction(a2)) { 369 | if (isFunction(a1)) { 370 | success = a1; 371 | error = a2; 372 | break; 373 | } 374 | 375 | success = a2; 376 | error = a3; 377 | //fallthrough 378 | } else { 379 | params = a1; 380 | data = a2; 381 | success = a3; 382 | break; 383 | } 384 | case 1: 385 | if (isFunction(a1)) success = a1; 386 | else if (hasBody) data = a1; 387 | else params = a1; 388 | break; 389 | case 0: break; 390 | default: 391 | throw "Expected between 0-4 arguments [params, data, success, error], got " + 392 | arguments.length + " arguments."; 393 | } 394 | 395 | var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data)); 396 | $http({ 397 | method: action.method, 398 | url: route.url(extend({}, extractParams(data, action.params || {}), params)), 399 | data: data 400 | }).then(function(response) { 401 | var data = response.data; 402 | 403 | if (data) { 404 | if (action.isArray) { 405 | value.length = 0; 406 | forEach(data, function(item) { 407 | value.push(new Resource(item)); 408 | }); 409 | } else { 410 | copy(data, value); 411 | } 412 | } 413 | (success||noop)(value, response.headers); 414 | }, error); 415 | 416 | return value; 417 | }; 418 | 419 | 420 | Resource.prototype['$' + name] = function(a1, a2, a3) { 421 | var params = extractParams(this), 422 | success = noop, 423 | error; 424 | 425 | switch(arguments.length) { 426 | case 3: params = a1; success = a2; error = a3; break; 427 | case 2: 428 | case 1: 429 | if (isFunction(a1)) { 430 | success = a1; 431 | error = a2; 432 | } else { 433 | params = a1; 434 | success = a2 || noop; 435 | } 436 | case 0: break; 437 | default: 438 | throw "Expected between 1-3 arguments [params, success, error], got " + 439 | arguments.length + " arguments."; 440 | } 441 | var data = hasBody ? this : undefined; 442 | Resource[name].call(this, params, data, success, error); 443 | }; 444 | }); 445 | 446 | Resource.bind = function(additionalParamDefaults){ 447 | return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); 448 | }; 449 | 450 | return Resource; 451 | } 452 | 453 | return ResourceFactory; 454 | }]); 455 | 456 | 457 | })(window, window.angular); 458 | -------------------------------------------------------------------------------- /app/lib/angular/angular-sanitize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.6 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) { 7 | 'use strict'; 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name ngSanitize 12 | * @description 13 | */ 14 | 15 | /* 16 | * HTML Parser By Misko Hevery (misko@hevery.com) 17 | * based on: HTML Parser By John Resig (ejohn.org) 18 | * Original code by Erik Arvidsson, Mozilla Public License 19 | * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js 20 | * 21 | * // Use like so: 22 | * htmlParser(htmlString, { 23 | * start: function(tag, attrs, unary) {}, 24 | * end: function(tag) {}, 25 | * chars: function(text) {}, 26 | * comment: function(text) {} 27 | * }); 28 | * 29 | */ 30 | 31 | 32 | /** 33 | * @ngdoc service 34 | * @name ngSanitize.$sanitize 35 | * @function 36 | * 37 | * @description 38 | * The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are 39 | * then serialized back to properly escaped html string. This means that no unsafe input can make 40 | * it into the returned string, however, since our parser is more strict than a typical browser 41 | * parser, it's possible that some obscure input, which would be recognized as valid HTML by a 42 | * browser, won't make it through the sanitizer. 43 | * 44 | * @param {string} html Html input. 45 | * @returns {string} Sanitized html. 46 | * 47 | * @example 48 | 49 | 50 | 58 |
59 | Snippet: 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 71 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
FilterSourceRendered
html filter 69 |
<div ng-bind-html="snippet">
</div>
70 |
72 |
73 |
no filter
<div ng-bind="snippet">
</div>
unsafe html filter
<div ng-bind-html-unsafe="snippet">
</div>
86 |
87 |
88 | 89 | it('should sanitize the html snippet ', function() { 90 | expect(using('#html-filter').element('div').html()). 91 | toBe('

an html\nclick here\nsnippet

'); 92 | }); 93 | 94 | it('should escape snippet without any filter', function() { 95 | expect(using('#escaped-html').element('div').html()). 96 | toBe("<p style=\"color:blue\">an html\n" + 97 | "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + 98 | "snippet</p>"); 99 | }); 100 | 101 | it('should inline raw snippet if filtered as unsafe', function() { 102 | expect(using('#html-unsafe-filter').element("div").html()). 103 | toBe("

an html\n" + 104 | "click here\n" + 105 | "snippet

"); 106 | }); 107 | 108 | it('should update', function() { 109 | input('snippet').enter('new text'); 110 | expect(using('#html-filter').binding('snippet')).toBe('new text'); 111 | expect(using('#escaped-html').element('div').html()).toBe("new <b>text</b>"); 112 | expect(using('#html-unsafe-filter').binding("snippet")).toBe('new text'); 113 | }); 114 |
115 |
116 | */ 117 | var $sanitize = function(html) { 118 | var buf = []; 119 | htmlParser(html, htmlSanitizeWriter(buf)); 120 | return buf.join(''); 121 | }; 122 | 123 | 124 | // Regular Expressions for parsing tags and attributes 125 | var START_TAG_REGEXP = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/, 126 | END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/, 127 | ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, 128 | BEGIN_TAG_REGEXP = /^/g, 131 | CDATA_REGEXP = //g, 132 | URI_REGEXP = /^((ftp|https?):\/\/|mailto:|#)/, 133 | NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; // Match everything outside of normal chars and " (quote character) 134 | 135 | 136 | // Good source of info about elements and attributes 137 | // http://dev.w3.org/html5/spec/Overview.html#semantics 138 | // http://simon.html5.org/html-elements 139 | 140 | // Safe Void Elements - HTML5 141 | // http://dev.w3.org/html5/spec/Overview.html#void-elements 142 | var voidElements = makeMap("area,br,col,hr,img,wbr"); 143 | 144 | // Elements that you can, intentionally, leave open (and which close themselves) 145 | // http://dev.w3.org/html5/spec/Overview.html#optional-tags 146 | var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), 147 | optionalEndTagInlineElements = makeMap("rp,rt"), 148 | optionalEndTagElements = angular.extend({}, optionalEndTagInlineElements, optionalEndTagBlockElements); 149 | 150 | // Safe Block Elements - HTML5 151 | var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article,aside," + 152 | "blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6," + 153 | "header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")); 154 | 155 | // Inline Elements - HTML5 156 | var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b,bdi,bdo," + 157 | "big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small," + 158 | "span,strike,strong,sub,sup,time,tt,u,var")); 159 | 160 | 161 | // Special Elements (can contain anything) 162 | var specialElements = makeMap("script,style"); 163 | 164 | var validElements = angular.extend({}, voidElements, blockElements, inlineElements, optionalEndTagElements); 165 | 166 | //Attributes that have href and hence need to be sanitized 167 | var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap"); 168 | var validAttrs = angular.extend({}, uriAttrs, makeMap( 169 | 'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+ 170 | 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+ 171 | 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+ 172 | 'scope,scrolling,shape,span,start,summary,target,title,type,'+ 173 | 'valign,value,vspace,width')); 174 | 175 | function makeMap(str) { 176 | var obj = {}, items = str.split(','), i; 177 | for (i = 0; i < items.length; i++) obj[items[i]] = true; 178 | return obj; 179 | } 180 | 181 | 182 | /** 183 | * @example 184 | * htmlParser(htmlString, { 185 | * start: function(tag, attrs, unary) {}, 186 | * end: function(tag) {}, 187 | * chars: function(text) {}, 188 | * comment: function(text) {} 189 | * }); 190 | * 191 | * @param {string} html string 192 | * @param {object} handler 193 | */ 194 | function htmlParser( html, handler ) { 195 | var index, chars, match, stack = [], last = html; 196 | stack.last = function() { return stack[ stack.length - 1 ]; }; 197 | 198 | while ( html ) { 199 | chars = true; 200 | 201 | // Make sure we're not in a script or style element 202 | if ( !stack.last() || !specialElements[ stack.last() ] ) { 203 | 204 | // Comment 205 | if ( html.indexOf(""); 207 | 208 | if ( index >= 0 ) { 209 | if (handler.comment) handler.comment( html.substring( 4, index ) ); 210 | html = html.substring( index + 3 ); 211 | chars = false; 212 | } 213 | 214 | // end tag 215 | } else if ( BEGING_END_TAGE_REGEXP.test(html) ) { 216 | match = html.match( END_TAG_REGEXP ); 217 | 218 | if ( match ) { 219 | html = html.substring( match[0].length ); 220 | match[0].replace( END_TAG_REGEXP, parseEndTag ); 221 | chars = false; 222 | } 223 | 224 | // start tag 225 | } else if ( BEGIN_TAG_REGEXP.test(html) ) { 226 | match = html.match( START_TAG_REGEXP ); 227 | 228 | if ( match ) { 229 | html = html.substring( match[0].length ); 230 | match[0].replace( START_TAG_REGEXP, parseStartTag ); 231 | chars = false; 232 | } 233 | } 234 | 235 | if ( chars ) { 236 | index = html.indexOf("<"); 237 | 238 | var text = index < 0 ? html : html.substring( 0, index ); 239 | html = index < 0 ? "" : html.substring( index ); 240 | 241 | if (handler.chars) handler.chars( decodeEntities(text) ); 242 | } 243 | 244 | } else { 245 | html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), function(all, text){ 246 | text = text. 247 | replace(COMMENT_REGEXP, "$1"). 248 | replace(CDATA_REGEXP, "$1"); 249 | 250 | if (handler.chars) handler.chars( decodeEntities(text) ); 251 | 252 | return ""; 253 | }); 254 | 255 | parseEndTag( "", stack.last() ); 256 | } 257 | 258 | if ( html == last ) { 259 | throw "Parse Error: " + html; 260 | } 261 | last = html; 262 | } 263 | 264 | // Clean up any remaining tags 265 | parseEndTag(); 266 | 267 | function parseStartTag( tag, tagName, rest, unary ) { 268 | tagName = angular.lowercase(tagName); 269 | if ( blockElements[ tagName ] ) { 270 | while ( stack.last() && inlineElements[ stack.last() ] ) { 271 | parseEndTag( "", stack.last() ); 272 | } 273 | } 274 | 275 | if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) { 276 | parseEndTag( "", tagName ); 277 | } 278 | 279 | unary = voidElements[ tagName ] || !!unary; 280 | 281 | if ( !unary ) 282 | stack.push( tagName ); 283 | 284 | var attrs = {}; 285 | 286 | rest.replace(ATTR_REGEXP, function(match, name, doubleQuotedValue, singleQoutedValue, unqoutedValue) { 287 | var value = doubleQuotedValue 288 | || singleQoutedValue 289 | || unqoutedValue 290 | || ''; 291 | 292 | attrs[name] = decodeEntities(value); 293 | }); 294 | if (handler.start) handler.start( tagName, attrs, unary ); 295 | } 296 | 297 | function parseEndTag( tag, tagName ) { 298 | var pos = 0, i; 299 | tagName = angular.lowercase(tagName); 300 | if ( tagName ) 301 | // Find the closest opened tag of the same type 302 | for ( pos = stack.length - 1; pos >= 0; pos-- ) 303 | if ( stack[ pos ] == tagName ) 304 | break; 305 | 306 | if ( pos >= 0 ) { 307 | // Close all the open elements, up the stack 308 | for ( i = stack.length - 1; i >= pos; i-- ) 309 | if (handler.end) handler.end( stack[ i ] ); 310 | 311 | // Remove the open elements from the stack 312 | stack.length = pos; 313 | } 314 | } 315 | } 316 | 317 | /** 318 | * decodes all entities into regular string 319 | * @param value 320 | * @returns {string} A string with decoded entities. 321 | */ 322 | var hiddenPre=document.createElement("pre"); 323 | function decodeEntities(value) { 324 | hiddenPre.innerHTML=value.replace(//g, '>'); 343 | } 344 | 345 | /** 346 | * create an HTML/XML writer which writes to buffer 347 | * @param {Array} buf use buf.jain('') to get out sanitized html string 348 | * @returns {object} in the form of { 349 | * start: function(tag, attrs, unary) {}, 350 | * end: function(tag) {}, 351 | * chars: function(text) {}, 352 | * comment: function(text) {} 353 | * } 354 | */ 355 | function htmlSanitizeWriter(buf){ 356 | var ignore = false; 357 | var out = angular.bind(buf, buf.push); 358 | return { 359 | start: function(tag, attrs, unary){ 360 | tag = angular.lowercase(tag); 361 | if (!ignore && specialElements[tag]) { 362 | ignore = tag; 363 | } 364 | if (!ignore && validElements[tag] == true) { 365 | out('<'); 366 | out(tag); 367 | angular.forEach(attrs, function(value, key){ 368 | var lkey=angular.lowercase(key); 369 | if (validAttrs[lkey]==true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) { 370 | out(' '); 371 | out(key); 372 | out('="'); 373 | out(encodeEntities(value)); 374 | out('"'); 375 | } 376 | }); 377 | out(unary ? '/>' : '>'); 378 | } 379 | }, 380 | end: function(tag){ 381 | tag = angular.lowercase(tag); 382 | if (!ignore && validElements[tag] == true) { 383 | out(''); 386 | } 387 | if (tag == ignore) { 388 | ignore = false; 389 | } 390 | }, 391 | chars: function(chars){ 392 | if (!ignore) { 393 | out(encodeEntities(chars)); 394 | } 395 | } 396 | }; 397 | } 398 | 399 | 400 | // define ngSanitize module and register $sanitize service 401 | angular.module('ngSanitize', []).value('$sanitize', $sanitize); 402 | 403 | /** 404 | * @ngdoc directive 405 | * @name ngSanitize.directive:ngBindHtml 406 | * 407 | * @description 408 | * Creates a binding that will sanitize the result of evaluating the `expression` with the 409 | * {@link ngSanitize.$sanitize $sanitize} service and innerHTML the result into the current element. 410 | * 411 | * See {@link ngSanitize.$sanitize $sanitize} docs for examples. 412 | * 413 | * @element ANY 414 | * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 415 | */ 416 | angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($sanitize) { 417 | return function(scope, element, attr) { 418 | element.addClass('ng-binding').data('$binding', attr.ngBindHtml); 419 | scope.$watch(attr.ngBindHtml, function ngBindHtmlWatchAction(value) { 420 | value = $sanitize(value); 421 | element.html(value || ''); 422 | }); 423 | }; 424 | }]); 425 | 426 | /** 427 | * @ngdoc filter 428 | * @name ngSanitize.filter:linky 429 | * @function 430 | * 431 | * @description 432 | * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and 433 | * plain email address links. 434 | * 435 | * @param {string} text Input text. 436 | * @returns {string} Html-linkified text. 437 | * 438 | * @usage 439 | 440 | * 441 | * @example 442 | 443 | 444 | 454 |
455 | Snippet: 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 467 | 470 | 471 | 472 | 473 | 474 | 475 | 476 |
FilterSourceRendered
linky filter 465 |
<div ng-bind-html="snippet | linky">
</div>
466 |
468 |
469 |
no filter
<div ng-bind="snippet">
</div>
477 | 478 | 479 | it('should linkify the snippet with urls', function() { 480 | expect(using('#linky-filter').binding('snippet | linky')). 481 | toBe('Pretty text with some links: ' + 482 | 'http://angularjs.org/, ' + 483 | 'us@somewhere.org, ' + 484 | 'another@somewhere.org, ' + 485 | 'and one more: ftp://127.0.0.1/.'); 486 | }); 487 | 488 | it ('should not linkify snippet without the linky filter', function() { 489 | expect(using('#escaped-html').binding('snippet')). 490 | toBe("Pretty text with some links:\n" + 491 | "http://angularjs.org/,\n" + 492 | "mailto:us@somewhere.org,\n" + 493 | "another@somewhere.org,\n" + 494 | "and one more: ftp://127.0.0.1/."); 495 | }); 496 | 497 | it('should update', function() { 498 | input('snippet').enter('new http://link.'); 499 | expect(using('#linky-filter').binding('snippet | linky')). 500 | toBe('new http://link.'); 501 | expect(using('#escaped-html').binding('snippet')).toBe('new http://link.'); 502 | }); 503 | 504 | 505 | */ 506 | angular.module('ngSanitize').filter('linky', function() { 507 | var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/, 508 | MAILTO_REGEXP = /^mailto:/; 509 | 510 | return function(text) { 511 | if (!text) return text; 512 | var match; 513 | var raw = text; 514 | var html = []; 515 | // TODO(vojta): use $sanitize instead 516 | var writer = htmlSanitizeWriter(html); 517 | var url; 518 | var i; 519 | while ((match = raw.match(LINKY_URL_REGEXP))) { 520 | // We can not end in these as they are sometimes found at the end of the sentence 521 | url = match[0]; 522 | // if we did not match ftp/http/mailto then assume mailto 523 | if (match[2] == match[3]) url = 'mailto:' + url; 524 | i = match.index; 525 | writer.chars(raw.substr(0, i)); 526 | writer.start('a', {href:url}); 527 | writer.chars(match[0].replace(MAILTO_REGEXP, '')); 528 | writer.end('a'); 529 | raw = raw.substring(i + match[0].length); 530 | } 531 | writer.chars(raw); 532 | return html.join(''); 533 | }; 534 | }); 535 | 536 | 537 | })(window, window.angular); 538 | -------------------------------------------------------------------------------- /test/lib/angular/angular-mocks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.6 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | * 6 | * TODO(vojta): wrap whole file into closure during build 7 | */ 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name angular.mock 12 | * @description 13 | * 14 | * Namespace from 'angular-mocks.js' which contains testing related code. 15 | */ 16 | angular.mock = {}; 17 | 18 | /** 19 | * ! This is a private undocumented service ! 20 | * 21 | * @name ngMock.$browser 22 | * 23 | * @description 24 | * This service is a mock implementation of {@link ng.$browser}. It provides fake 25 | * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr, 26 | * cookies, etc... 27 | * 28 | * The api of this service is the same as that of the real {@link ng.$browser $browser}, except 29 | * that there are several helper methods available which can be used in tests. 30 | */ 31 | angular.mock.$BrowserProvider = function() { 32 | this.$get = function(){ 33 | return new angular.mock.$Browser(); 34 | }; 35 | }; 36 | 37 | angular.mock.$Browser = function() { 38 | var self = this; 39 | 40 | this.isMock = true; 41 | self.$$url = "http://server/"; 42 | self.$$lastUrl = self.$$url; // used by url polling fn 43 | self.pollFns = []; 44 | 45 | // TODO(vojta): remove this temporary api 46 | self.$$completeOutstandingRequest = angular.noop; 47 | self.$$incOutstandingRequestCount = angular.noop; 48 | 49 | 50 | // register url polling fn 51 | 52 | self.onUrlChange = function(listener) { 53 | self.pollFns.push( 54 | function() { 55 | if (self.$$lastUrl != self.$$url) { 56 | self.$$lastUrl = self.$$url; 57 | listener(self.$$url); 58 | } 59 | } 60 | ); 61 | 62 | return listener; 63 | }; 64 | 65 | self.cookieHash = {}; 66 | self.lastCookieHash = {}; 67 | self.deferredFns = []; 68 | self.deferredNextId = 0; 69 | 70 | self.defer = function(fn, delay) { 71 | delay = delay || 0; 72 | self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId}); 73 | self.deferredFns.sort(function(a,b){ return a.time - b.time;}); 74 | return self.deferredNextId++; 75 | }; 76 | 77 | 78 | self.defer.now = 0; 79 | 80 | 81 | self.defer.cancel = function(deferId) { 82 | var fnIndex; 83 | 84 | angular.forEach(self.deferredFns, function(fn, index) { 85 | if (fn.id === deferId) fnIndex = index; 86 | }); 87 | 88 | if (fnIndex !== undefined) { 89 | self.deferredFns.splice(fnIndex, 1); 90 | return true; 91 | } 92 | 93 | return false; 94 | }; 95 | 96 | 97 | /** 98 | * @name ngMock.$browser#defer.flush 99 | * @methodOf ngMock.$browser 100 | * 101 | * @description 102 | * Flushes all pending requests and executes the defer callbacks. 103 | * 104 | * @param {number=} number of milliseconds to flush. See {@link #defer.now} 105 | */ 106 | self.defer.flush = function(delay) { 107 | if (angular.isDefined(delay)) { 108 | self.defer.now += delay; 109 | } else { 110 | if (self.deferredFns.length) { 111 | self.defer.now = self.deferredFns[self.deferredFns.length-1].time; 112 | } else { 113 | throw Error('No deferred tasks to be flushed'); 114 | } 115 | } 116 | 117 | while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) { 118 | self.deferredFns.shift().fn(); 119 | } 120 | }; 121 | /** 122 | * @name ngMock.$browser#defer.now 123 | * @propertyOf ngMock.$browser 124 | * 125 | * @description 126 | * Current milliseconds mock time. 127 | */ 128 | 129 | self.$$baseHref = ''; 130 | self.baseHref = function() { 131 | return this.$$baseHref; 132 | }; 133 | }; 134 | angular.mock.$Browser.prototype = { 135 | 136 | /** 137 | * @name ngMock.$browser#poll 138 | * @methodOf ngMock.$browser 139 | * 140 | * @description 141 | * run all fns in pollFns 142 | */ 143 | poll: function poll() { 144 | angular.forEach(this.pollFns, function(pollFn){ 145 | pollFn(); 146 | }); 147 | }, 148 | 149 | addPollFn: function(pollFn) { 150 | this.pollFns.push(pollFn); 151 | return pollFn; 152 | }, 153 | 154 | url: function(url, replace) { 155 | if (url) { 156 | this.$$url = url; 157 | return this; 158 | } 159 | 160 | return this.$$url; 161 | }, 162 | 163 | cookies: function(name, value) { 164 | if (name) { 165 | if (value == undefined) { 166 | delete this.cookieHash[name]; 167 | } else { 168 | if (angular.isString(value) && //strings only 169 | value.length <= 4096) { //strict cookie storage limits 170 | this.cookieHash[name] = value; 171 | } 172 | } 173 | } else { 174 | if (!angular.equals(this.cookieHash, this.lastCookieHash)) { 175 | this.lastCookieHash = angular.copy(this.cookieHash); 176 | this.cookieHash = angular.copy(this.cookieHash); 177 | } 178 | return this.cookieHash; 179 | } 180 | }, 181 | 182 | notifyWhenNoOutstandingRequests: function(fn) { 183 | fn(); 184 | } 185 | }; 186 | 187 | 188 | /** 189 | * @ngdoc object 190 | * @name ngMock.$exceptionHandlerProvider 191 | * 192 | * @description 193 | * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors passed 194 | * into the `$exceptionHandler`. 195 | */ 196 | 197 | /** 198 | * @ngdoc object 199 | * @name ngMock.$exceptionHandler 200 | * 201 | * @description 202 | * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed 203 | * into it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration 204 | * information. 205 | * 206 | * 207 | *
 208 |  *   describe('$exceptionHandlerProvider', function() {
 209 |  *
 210 |  *     it('should capture log messages and exceptions', function() {
 211 |  *
 212 |  *       module(function($exceptionHandlerProvider) {
 213 |  *         $exceptionHandlerProvider.mode('log');
 214 |  *       });
 215 |  *
 216 |  *       inject(function($log, $exceptionHandler, $timeout) {
 217 |  *         $timeout(function() { $log.log(1); });
 218 |  *         $timeout(function() { $log.log(2); throw 'banana peel'; });
 219 |  *         $timeout(function() { $log.log(3); });
 220 |  *         expect($exceptionHandler.errors).toEqual([]);
 221 |  *         expect($log.assertEmpty());
 222 |  *         $timeout.flush();
 223 |  *         expect($exceptionHandler.errors).toEqual(['banana peel']);
 224 |  *         expect($log.log.logs).toEqual([[1], [2], [3]]);
 225 |  *       });
 226 |  *     });
 227 |  *   });
 228 |  * 
229 | */ 230 | 231 | angular.mock.$ExceptionHandlerProvider = function() { 232 | var handler; 233 | 234 | /** 235 | * @ngdoc method 236 | * @name ngMock.$exceptionHandlerProvider#mode 237 | * @methodOf ngMock.$exceptionHandlerProvider 238 | * 239 | * @description 240 | * Sets the logging mode. 241 | * 242 | * @param {string} mode Mode of operation, defaults to `rethrow`. 243 | * 244 | * - `rethrow`: If any errors are are passed into the handler in tests, it typically 245 | * means that there is a bug in the application or test, so this mock will 246 | * make these tests fail. 247 | * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` mode stores an 248 | * array of errors in `$exceptionHandler.errors`, to allow later assertion of them. 249 | * See {@link ngMock.$log#assertEmpty assertEmpty()} and 250 | * {@link ngMock.$log#reset reset()} 251 | */ 252 | this.mode = function(mode) { 253 | switch(mode) { 254 | case 'rethrow': 255 | handler = function(e) { 256 | throw e; 257 | }; 258 | break; 259 | case 'log': 260 | var errors = []; 261 | 262 | handler = function(e) { 263 | if (arguments.length == 1) { 264 | errors.push(e); 265 | } else { 266 | errors.push([].slice.call(arguments, 0)); 267 | } 268 | }; 269 | 270 | handler.errors = errors; 271 | break; 272 | default: 273 | throw Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); 274 | } 275 | }; 276 | 277 | this.$get = function() { 278 | return handler; 279 | }; 280 | 281 | this.mode('rethrow'); 282 | }; 283 | 284 | 285 | /** 286 | * @ngdoc service 287 | * @name ngMock.$log 288 | * 289 | * @description 290 | * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays 291 | * (one array per logging level). These arrays are exposed as `logs` property of each of the 292 | * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`. 293 | * 294 | */ 295 | angular.mock.$LogProvider = function() { 296 | 297 | function concat(array1, array2, index) { 298 | return array1.concat(Array.prototype.slice.call(array2, index)); 299 | } 300 | 301 | 302 | this.$get = function () { 303 | var $log = { 304 | log: function() { $log.log.logs.push(concat([], arguments, 0)); }, 305 | warn: function() { $log.warn.logs.push(concat([], arguments, 0)); }, 306 | info: function() { $log.info.logs.push(concat([], arguments, 0)); }, 307 | error: function() { $log.error.logs.push(concat([], arguments, 0)); } 308 | }; 309 | 310 | /** 311 | * @ngdoc method 312 | * @name ngMock.$log#reset 313 | * @methodOf ngMock.$log 314 | * 315 | * @description 316 | * Reset all of the logging arrays to empty. 317 | */ 318 | $log.reset = function () { 319 | /** 320 | * @ngdoc property 321 | * @name ngMock.$log#log.logs 322 | * @propertyOf ngMock.$log 323 | * 324 | * @description 325 | * Array of logged messages. 326 | */ 327 | $log.log.logs = []; 328 | /** 329 | * @ngdoc property 330 | * @name ngMock.$log#warn.logs 331 | * @propertyOf ngMock.$log 332 | * 333 | * @description 334 | * Array of logged messages. 335 | */ 336 | $log.warn.logs = []; 337 | /** 338 | * @ngdoc property 339 | * @name ngMock.$log#info.logs 340 | * @propertyOf ngMock.$log 341 | * 342 | * @description 343 | * Array of logged messages. 344 | */ 345 | $log.info.logs = []; 346 | /** 347 | * @ngdoc property 348 | * @name ngMock.$log#error.logs 349 | * @propertyOf ngMock.$log 350 | * 351 | * @description 352 | * Array of logged messages. 353 | */ 354 | $log.error.logs = []; 355 | }; 356 | 357 | /** 358 | * @ngdoc method 359 | * @name ngMock.$log#assertEmpty 360 | * @methodOf ngMock.$log 361 | * 362 | * @description 363 | * Assert that the all of the logging methods have no logged messages. If messages present, an exception is thrown. 364 | */ 365 | $log.assertEmpty = function() { 366 | var errors = []; 367 | angular.forEach(['error', 'warn', 'info', 'log'], function(logLevel) { 368 | angular.forEach($log[logLevel].logs, function(log) { 369 | angular.forEach(log, function (logItem) { 370 | errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + (logItem.stack || '')); 371 | }); 372 | }); 373 | }); 374 | if (errors.length) { 375 | errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or an expected " + 376 | "log message was not checked and removed:"); 377 | errors.push(''); 378 | throw new Error(errors.join('\n---------\n')); 379 | } 380 | }; 381 | 382 | $log.reset(); 383 | return $log; 384 | }; 385 | }; 386 | 387 | 388 | (function() { 389 | var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; 390 | 391 | function jsonStringToDate(string){ 392 | var match; 393 | if (match = string.match(R_ISO8061_STR)) { 394 | var date = new Date(0), 395 | tzHour = 0, 396 | tzMin = 0; 397 | if (match[9]) { 398 | tzHour = int(match[9] + match[10]); 399 | tzMin = int(match[9] + match[11]); 400 | } 401 | date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3])); 402 | date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0)); 403 | return date; 404 | } 405 | return string; 406 | } 407 | 408 | function int(str) { 409 | return parseInt(str, 10); 410 | } 411 | 412 | function padNumber(num, digits, trim) { 413 | var neg = ''; 414 | if (num < 0) { 415 | neg = '-'; 416 | num = -num; 417 | } 418 | num = '' + num; 419 | while(num.length < digits) num = '0' + num; 420 | if (trim) 421 | num = num.substr(num.length - digits); 422 | return neg + num; 423 | } 424 | 425 | 426 | /** 427 | * @ngdoc object 428 | * @name angular.mock.TzDate 429 | * @description 430 | * 431 | * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`. 432 | * 433 | * Mock of the Date type which has its timezone specified via constructor arg. 434 | * 435 | * The main purpose is to create Date-like instances with timezone fixed to the specified timezone 436 | * offset, so that we can test code that depends on local timezone settings without dependency on 437 | * the time zone settings of the machine where the code is running. 438 | * 439 | * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored) 440 | * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC* 441 | * 442 | * @example 443 | * !!!! WARNING !!!!! 444 | * This is not a complete Date object so only methods that were implemented can be called safely. 445 | * To make matters worse, TzDate instances inherit stuff from Date via a prototype. 446 | * 447 | * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is 448 | * incomplete we might be missing some non-standard methods. This can result in errors like: 449 | * "Date.prototype.foo called on incompatible Object". 450 | * 451 | *
 452 |    * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
 453 |    * newYearInBratislava.getTimezoneOffset() => -60;
 454 |    * newYearInBratislava.getFullYear() => 2010;
 455 |    * newYearInBratislava.getMonth() => 0;
 456 |    * newYearInBratislava.getDate() => 1;
 457 |    * newYearInBratislava.getHours() => 0;
 458 |    * newYearInBratislava.getMinutes() => 0;
 459 |    * 
460 | * 461 | */ 462 | angular.mock.TzDate = function (offset, timestamp) { 463 | var self = new Date(0); 464 | if (angular.isString(timestamp)) { 465 | var tsStr = timestamp; 466 | 467 | self.origDate = jsonStringToDate(timestamp); 468 | 469 | timestamp = self.origDate.getTime(); 470 | if (isNaN(timestamp)) 471 | throw { 472 | name: "Illegal Argument", 473 | message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string" 474 | }; 475 | } else { 476 | self.origDate = new Date(timestamp); 477 | } 478 | 479 | var localOffset = new Date(timestamp).getTimezoneOffset(); 480 | self.offsetDiff = localOffset*60*1000 - offset*1000*60*60; 481 | self.date = new Date(timestamp + self.offsetDiff); 482 | 483 | self.getTime = function() { 484 | return self.date.getTime() - self.offsetDiff; 485 | }; 486 | 487 | self.toLocaleDateString = function() { 488 | return self.date.toLocaleDateString(); 489 | }; 490 | 491 | self.getFullYear = function() { 492 | return self.date.getFullYear(); 493 | }; 494 | 495 | self.getMonth = function() { 496 | return self.date.getMonth(); 497 | }; 498 | 499 | self.getDate = function() { 500 | return self.date.getDate(); 501 | }; 502 | 503 | self.getHours = function() { 504 | return self.date.getHours(); 505 | }; 506 | 507 | self.getMinutes = function() { 508 | return self.date.getMinutes(); 509 | }; 510 | 511 | self.getSeconds = function() { 512 | return self.date.getSeconds(); 513 | }; 514 | 515 | self.getTimezoneOffset = function() { 516 | return offset * 60; 517 | }; 518 | 519 | self.getUTCFullYear = function() { 520 | return self.origDate.getUTCFullYear(); 521 | }; 522 | 523 | self.getUTCMonth = function() { 524 | return self.origDate.getUTCMonth(); 525 | }; 526 | 527 | self.getUTCDate = function() { 528 | return self.origDate.getUTCDate(); 529 | }; 530 | 531 | self.getUTCHours = function() { 532 | return self.origDate.getUTCHours(); 533 | }; 534 | 535 | self.getUTCMinutes = function() { 536 | return self.origDate.getUTCMinutes(); 537 | }; 538 | 539 | self.getUTCSeconds = function() { 540 | return self.origDate.getUTCSeconds(); 541 | }; 542 | 543 | self.getUTCMilliseconds = function() { 544 | return self.origDate.getUTCMilliseconds(); 545 | }; 546 | 547 | self.getDay = function() { 548 | return self.date.getDay(); 549 | }; 550 | 551 | // provide this method only on browsers that already have it 552 | if (self.toISOString) { 553 | self.toISOString = function() { 554 | return padNumber(self.origDate.getUTCFullYear(), 4) + '-' + 555 | padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' + 556 | padNumber(self.origDate.getUTCDate(), 2) + 'T' + 557 | padNumber(self.origDate.getUTCHours(), 2) + ':' + 558 | padNumber(self.origDate.getUTCMinutes(), 2) + ':' + 559 | padNumber(self.origDate.getUTCSeconds(), 2) + '.' + 560 | padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z' 561 | } 562 | } 563 | 564 | //hide all methods not implemented in this mock that the Date prototype exposes 565 | var unimplementedMethods = ['getMilliseconds', 'getUTCDay', 566 | 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', 567 | 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear', 568 | 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', 569 | 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString', 570 | 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; 571 | 572 | angular.forEach(unimplementedMethods, function(methodName) { 573 | self[methodName] = function() { 574 | throw Error("Method '" + methodName + "' is not implemented in the TzDate mock"); 575 | }; 576 | }); 577 | 578 | return self; 579 | }; 580 | 581 | //make "tzDateInstance instanceof Date" return true 582 | angular.mock.TzDate.prototype = Date.prototype; 583 | })(); 584 | 585 | 586 | /** 587 | * @ngdoc function 588 | * @name angular.mock.dump 589 | * @description 590 | * 591 | * *NOTE*: this is not an injectable instance, just a globally available function. 592 | * 593 | * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for debugging. 594 | * 595 | * This method is also available on window, where it can be used to display objects on debug console. 596 | * 597 | * @param {*} object - any object to turn into string. 598 | * @return {string} a serialized string of the argument 599 | */ 600 | angular.mock.dump = function(object) { 601 | return serialize(object); 602 | 603 | function serialize(object) { 604 | var out; 605 | 606 | if (angular.isElement(object)) { 607 | object = angular.element(object); 608 | out = angular.element('
'); 609 | angular.forEach(object, function(element) { 610 | out.append(angular.element(element).clone()); 611 | }); 612 | out = out.html(); 613 | } else if (angular.isArray(object)) { 614 | out = []; 615 | angular.forEach(object, function(o) { 616 | out.push(serialize(o)); 617 | }); 618 | out = '[ ' + out.join(', ') + ' ]'; 619 | } else if (angular.isObject(object)) { 620 | if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) { 621 | out = serializeScope(object); 622 | } else if (object instanceof Error) { 623 | out = object.stack || ('' + object.name + ': ' + object.message); 624 | } else { 625 | out = angular.toJson(object, true); 626 | } 627 | } else { 628 | out = String(object); 629 | } 630 | 631 | return out; 632 | } 633 | 634 | function serializeScope(scope, offset) { 635 | offset = offset || ' '; 636 | var log = [offset + 'Scope(' + scope.$id + '): {']; 637 | for ( var key in scope ) { 638 | if (scope.hasOwnProperty(key) && !key.match(/^(\$|this)/)) { 639 | log.push(' ' + key + ': ' + angular.toJson(scope[key])); 640 | } 641 | } 642 | var child = scope.$$childHead; 643 | while(child) { 644 | log.push(serializeScope(child, offset + ' ')); 645 | child = child.$$nextSibling; 646 | } 647 | log.push('}'); 648 | return log.join('\n' + offset); 649 | } 650 | }; 651 | 652 | /** 653 | * @ngdoc object 654 | * @name ngMock.$httpBackend 655 | * @description 656 | * Fake HTTP backend implementation suitable for unit testing application that use the 657 | * {@link ng.$http $http service}. 658 | * 659 | * *Note*: For fake http backend implementation suitable for end-to-end testing or backend-less 660 | * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}. 661 | * 662 | * During unit testing, we want our unit tests to run quickly and have no external dependencies so 663 | * we don’t want to send {@link https://developer.mozilla.org/en/xmlhttprequest XHR} or 664 | * {@link http://en.wikipedia.org/wiki/JSONP JSONP} requests to a real server. All we really need is 665 | * to verify whether a certain request has been sent or not, or alternatively just let the 666 | * application make requests, respond with pre-trained responses and assert that the end result is 667 | * what we expect it to be. 668 | * 669 | * This mock implementation can be used to respond with static or dynamic responses via the 670 | * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc). 671 | * 672 | * When an Angular application needs some data from a server, it calls the $http service, which 673 | * sends the request to a real server using $httpBackend service. With dependency injection, it is 674 | * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify 675 | * the requests and respond with some testing data without sending a request to real server. 676 | * 677 | * There are two ways to specify what test data should be returned as http responses by the mock 678 | * backend when the code under test makes http requests: 679 | * 680 | * - `$httpBackend.expect` - specifies a request expectation 681 | * - `$httpBackend.when` - specifies a backend definition 682 | * 683 | * 684 | * # Request Expectations vs Backend Definitions 685 | * 686 | * Request expectations provide a way to make assertions about requests made by the application and 687 | * to define responses for those requests. The test will fail if the expected requests are not made 688 | * or they are made in the wrong order. 689 | * 690 | * Backend definitions allow you to define a fake backend for your application which doesn't assert 691 | * if a particular request was made or not, it just returns a trained response if a request is made. 692 | * The test will pass whether or not the request gets made during testing. 693 | * 694 | * 695 | * 696 | * 697 | * 698 | * 699 | * 700 | * 701 | * 702 | * 703 | * 704 | * 705 | * 706 | * 707 | * 708 | * 709 | * 710 | * 711 | * 712 | * 713 | * 714 | * 715 | * 716 | * 717 | * 718 | * 719 | * 720 | * 721 | * 722 | * 723 | * 724 | * 725 | * 726 | * 727 | *
Request expectationsBackend definitions
Syntax.expect(...).respond(...).when(...).respond(...)
Typical usagestrict unit testsloose (black-box) unit testing
Fulfills multiple requestsNOYES
Order of requests mattersYESNO
Request requiredYESNO
Response requiredoptional (see below)YES
728 | * 729 | * In cases where both backend definitions and request expectations are specified during unit 730 | * testing, the request expectations are evaluated first. 731 | * 732 | * If a request expectation has no response specified, the algorithm will search your backend 733 | * definitions for an appropriate response. 734 | * 735 | * If a request didn't match any expectation or if the expectation doesn't have the response 736 | * defined, the backend definitions are evaluated in sequential order to see if any of them match 737 | * the request. The response from the first matched definition is returned. 738 | * 739 | * 740 | * # Flushing HTTP requests 741 | * 742 | * The $httpBackend used in production, always responds to requests with responses asynchronously. 743 | * If we preserved this behavior in unit testing, we'd have to create async unit tests, which are 744 | * hard to write, follow and maintain. At the same time the testing mock, can't respond 745 | * synchronously because that would change the execution of the code under test. For this reason the 746 | * mock $httpBackend has a `flush()` method, which allows the test to explicitly flush pending 747 | * requests and thus preserving the async api of the backend, while allowing the test to execute 748 | * synchronously. 749 | * 750 | * 751 | * # Unit testing with mock $httpBackend 752 | * 753 | *
 754 |    // controller
 755 |    function MyController($scope, $http) {
 756 |      $http.get('/auth.py').success(function(data) {
 757 |        $scope.user = data;
 758 |      });
 759 | 
 760 |      this.saveMessage = function(message) {
 761 |        $scope.status = 'Saving...';
 762 |        $http.post('/add-msg.py', message).success(function(response) {
 763 |          $scope.status = '';
 764 |        }).error(function() {
 765 |          $scope.status = 'ERROR!';
 766 |        });
 767 |      };
 768 |    }
 769 | 
 770 |    // testing controller
 771 |    var $httpBackend;
 772 | 
 773 |    beforeEach(inject(function($injector) {
 774 |      $httpBackend = $injector.get('$httpBackend');
 775 | 
 776 |      // backend definition common for all tests
 777 |      $httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'});
 778 |    }));
 779 | 
 780 | 
 781 |    afterEach(function() {
 782 |      $httpBackend.verifyNoOutstandingExpectation();
 783 |      $httpBackend.verifyNoOutstandingRequest();
 784 |    });
 785 | 
 786 | 
 787 |    it('should fetch authentication token', function() {
 788 |      $httpBackend.expectGET('/auth.py');
 789 |      var controller = scope.$new(MyController);
 790 |      $httpBackend.flush();
 791 |    });
 792 | 
 793 | 
 794 |    it('should send msg to server', function() {
 795 |      // now you don’t care about the authentication, but
 796 |      // the controller will still send the request and
 797 |      // $httpBackend will respond without you having to
 798 |      // specify the expectation and response for this request
 799 |      $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, '');
 800 | 
 801 |      var controller = scope.$new(MyController);
 802 |      $httpBackend.flush();
 803 |      controller.saveMessage('message content');
 804 |      expect(controller.status).toBe('Saving...');
 805 |      $httpBackend.flush();
 806 |      expect(controller.status).toBe('');
 807 |    });
 808 | 
 809 | 
 810 |    it('should send auth header', function() {
 811 |      $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
 812 |        // check if the header was send, if it wasn't the expectation won't
 813 |        // match the request and the test will fail
 814 |        return headers['Authorization'] == 'xxx';
 815 |      }).respond(201, '');
 816 | 
 817 |      var controller = scope.$new(MyController);
 818 |      controller.saveMessage('whatever');
 819 |      $httpBackend.flush();
 820 |    });
 821 |    
822 | */ 823 | angular.mock.$HttpBackendProvider = function() { 824 | this.$get = [createHttpBackendMock]; 825 | }; 826 | 827 | /** 828 | * General factory function for $httpBackend mock. 829 | * Returns instance for unit testing (when no arguments specified): 830 | * - passing through is disabled 831 | * - auto flushing is disabled 832 | * 833 | * Returns instance for e2e testing (when `$delegate` and `$browser` specified): 834 | * - passing through (delegating request to real backend) is enabled 835 | * - auto flushing is enabled 836 | * 837 | * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified) 838 | * @param {Object=} $browser Auto-flushing enabled if specified 839 | * @return {Object} Instance of $httpBackend mock 840 | */ 841 | function createHttpBackendMock($delegate, $browser) { 842 | var definitions = [], 843 | expectations = [], 844 | responses = [], 845 | responsesPush = angular.bind(responses, responses.push); 846 | 847 | function createResponse(status, data, headers) { 848 | if (angular.isFunction(status)) return status; 849 | 850 | return function() { 851 | return angular.isNumber(status) 852 | ? [status, data, headers] 853 | : [200, status, data]; 854 | }; 855 | } 856 | 857 | // TODO(vojta): change params to: method, url, data, headers, callback 858 | function $httpBackend(method, url, data, callback, headers) { 859 | var xhr = new MockXhr(), 860 | expectation = expectations[0], 861 | wasExpected = false; 862 | 863 | function prettyPrint(data) { 864 | return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp) 865 | ? data 866 | : angular.toJson(data); 867 | } 868 | 869 | if (expectation && expectation.match(method, url)) { 870 | if (!expectation.matchData(data)) 871 | throw Error('Expected ' + expectation + ' with different data\n' + 872 | 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data); 873 | 874 | if (!expectation.matchHeaders(headers)) 875 | throw Error('Expected ' + expectation + ' with different headers\n' + 876 | 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + 877 | prettyPrint(headers)); 878 | 879 | expectations.shift(); 880 | 881 | if (expectation.response) { 882 | responses.push(function() { 883 | var response = expectation.response(method, url, data, headers); 884 | xhr.$$respHeaders = response[2]; 885 | callback(response[0], response[1], xhr.getAllResponseHeaders()); 886 | }); 887 | return; 888 | } 889 | wasExpected = true; 890 | } 891 | 892 | var i = -1, definition; 893 | while ((definition = definitions[++i])) { 894 | if (definition.match(method, url, data, headers || {})) { 895 | if (definition.response) { 896 | // if $browser specified, we do auto flush all requests 897 | ($browser ? $browser.defer : responsesPush)(function() { 898 | var response = definition.response(method, url, data, headers); 899 | xhr.$$respHeaders = response[2]; 900 | callback(response[0], response[1], xhr.getAllResponseHeaders()); 901 | }); 902 | } else if (definition.passThrough) { 903 | $delegate(method, url, data, callback, headers); 904 | } else throw Error('No response defined !'); 905 | return; 906 | } 907 | } 908 | throw wasExpected ? 909 | Error('No response defined !') : 910 | Error('Unexpected request: ' + method + ' ' + url + '\n' + 911 | (expectation ? 'Expected ' + expectation : 'No more request expected')); 912 | } 913 | 914 | /** 915 | * @ngdoc method 916 | * @name ngMock.$httpBackend#when 917 | * @methodOf ngMock.$httpBackend 918 | * @description 919 | * Creates a new backend definition. 920 | * 921 | * @param {string} method HTTP method. 922 | * @param {string|RegExp} url HTTP url. 923 | * @param {(string|RegExp)=} data HTTP request body. 924 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header 925 | * object and returns true if the headers match the current definition. 926 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 927 | * request is handled. 928 | * 929 | * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` 930 | * – The respond method takes a set of static data to be returned or a function that can return 931 | * an array containing response status (number), response data (string) and response headers 932 | * (Object). 933 | */ 934 | $httpBackend.when = function(method, url, data, headers) { 935 | var definition = new MockHttpExpectation(method, url, data, headers), 936 | chain = { 937 | respond: function(status, data, headers) { 938 | definition.response = createResponse(status, data, headers); 939 | } 940 | }; 941 | 942 | if ($browser) { 943 | chain.passThrough = function() { 944 | definition.passThrough = true; 945 | }; 946 | } 947 | 948 | definitions.push(definition); 949 | return chain; 950 | }; 951 | 952 | /** 953 | * @ngdoc method 954 | * @name ngMock.$httpBackend#whenGET 955 | * @methodOf ngMock.$httpBackend 956 | * @description 957 | * Creates a new backend definition for GET requests. For more info see `when()`. 958 | * 959 | * @param {string|RegExp} url HTTP url. 960 | * @param {(Object|function(Object))=} headers HTTP headers. 961 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 962 | * request is handled. 963 | */ 964 | 965 | /** 966 | * @ngdoc method 967 | * @name ngMock.$httpBackend#whenHEAD 968 | * @methodOf ngMock.$httpBackend 969 | * @description 970 | * Creates a new backend definition for HEAD requests. For more info see `when()`. 971 | * 972 | * @param {string|RegExp} url HTTP url. 973 | * @param {(Object|function(Object))=} headers HTTP headers. 974 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 975 | * request is handled. 976 | */ 977 | 978 | /** 979 | * @ngdoc method 980 | * @name ngMock.$httpBackend#whenDELETE 981 | * @methodOf ngMock.$httpBackend 982 | * @description 983 | * Creates a new backend definition for DELETE requests. For more info see `when()`. 984 | * 985 | * @param {string|RegExp} url HTTP url. 986 | * @param {(Object|function(Object))=} headers HTTP headers. 987 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 988 | * request is handled. 989 | */ 990 | 991 | /** 992 | * @ngdoc method 993 | * @name ngMock.$httpBackend#whenPOST 994 | * @methodOf ngMock.$httpBackend 995 | * @description 996 | * Creates a new backend definition for POST requests. For more info see `when()`. 997 | * 998 | * @param {string|RegExp} url HTTP url. 999 | * @param {(string|RegExp)=} data HTTP request body. 1000 | * @param {(Object|function(Object))=} headers HTTP headers. 1001 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1002 | * request is handled. 1003 | */ 1004 | 1005 | /** 1006 | * @ngdoc method 1007 | * @name ngMock.$httpBackend#whenPUT 1008 | * @methodOf ngMock.$httpBackend 1009 | * @description 1010 | * Creates a new backend definition for PUT requests. For more info see `when()`. 1011 | * 1012 | * @param {string|RegExp} url HTTP url. 1013 | * @param {(string|RegExp)=} data HTTP request body. 1014 | * @param {(Object|function(Object))=} headers HTTP headers. 1015 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1016 | * request is handled. 1017 | */ 1018 | 1019 | /** 1020 | * @ngdoc method 1021 | * @name ngMock.$httpBackend#whenJSONP 1022 | * @methodOf ngMock.$httpBackend 1023 | * @description 1024 | * Creates a new backend definition for JSONP requests. For more info see `when()`. 1025 | * 1026 | * @param {string|RegExp} url HTTP url. 1027 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1028 | * request is handled. 1029 | */ 1030 | createShortMethods('when'); 1031 | 1032 | 1033 | /** 1034 | * @ngdoc method 1035 | * @name ngMock.$httpBackend#expect 1036 | * @methodOf ngMock.$httpBackend 1037 | * @description 1038 | * Creates a new request expectation. 1039 | * 1040 | * @param {string} method HTTP method. 1041 | * @param {string|RegExp} url HTTP url. 1042 | * @param {(string|RegExp)=} data HTTP request body. 1043 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header 1044 | * object and returns true if the headers match the current expectation. 1045 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1046 | * request is handled. 1047 | * 1048 | * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` 1049 | * – The respond method takes a set of static data to be returned or a function that can return 1050 | * an array containing response status (number), response data (string) and response headers 1051 | * (Object). 1052 | */ 1053 | $httpBackend.expect = function(method, url, data, headers) { 1054 | var expectation = new MockHttpExpectation(method, url, data, headers); 1055 | expectations.push(expectation); 1056 | return { 1057 | respond: function(status, data, headers) { 1058 | expectation.response = createResponse(status, data, headers); 1059 | } 1060 | }; 1061 | }; 1062 | 1063 | 1064 | /** 1065 | * @ngdoc method 1066 | * @name ngMock.$httpBackend#expectGET 1067 | * @methodOf ngMock.$httpBackend 1068 | * @description 1069 | * Creates a new request expectation for GET requests. For more info see `expect()`. 1070 | * 1071 | * @param {string|RegExp} url HTTP url. 1072 | * @param {Object=} headers HTTP headers. 1073 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1074 | * request is handled. See #expect for more info. 1075 | */ 1076 | 1077 | /** 1078 | * @ngdoc method 1079 | * @name ngMock.$httpBackend#expectHEAD 1080 | * @methodOf ngMock.$httpBackend 1081 | * @description 1082 | * Creates a new request expectation for HEAD requests. For more info see `expect()`. 1083 | * 1084 | * @param {string|RegExp} url HTTP url. 1085 | * @param {Object=} headers HTTP headers. 1086 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1087 | * request is handled. 1088 | */ 1089 | 1090 | /** 1091 | * @ngdoc method 1092 | * @name ngMock.$httpBackend#expectDELETE 1093 | * @methodOf ngMock.$httpBackend 1094 | * @description 1095 | * Creates a new request expectation for DELETE requests. For more info see `expect()`. 1096 | * 1097 | * @param {string|RegExp} url HTTP url. 1098 | * @param {Object=} headers HTTP headers. 1099 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1100 | * request is handled. 1101 | */ 1102 | 1103 | /** 1104 | * @ngdoc method 1105 | * @name ngMock.$httpBackend#expectPOST 1106 | * @methodOf ngMock.$httpBackend 1107 | * @description 1108 | * Creates a new request expectation for POST requests. For more info see `expect()`. 1109 | * 1110 | * @param {string|RegExp} url HTTP url. 1111 | * @param {(string|RegExp)=} data HTTP request body. 1112 | * @param {Object=} headers HTTP headers. 1113 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1114 | * request is handled. 1115 | */ 1116 | 1117 | /** 1118 | * @ngdoc method 1119 | * @name ngMock.$httpBackend#expectPUT 1120 | * @methodOf ngMock.$httpBackend 1121 | * @description 1122 | * Creates a new request expectation for PUT requests. For more info see `expect()`. 1123 | * 1124 | * @param {string|RegExp} url HTTP url. 1125 | * @param {(string|RegExp)=} data HTTP request body. 1126 | * @param {Object=} headers HTTP headers. 1127 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1128 | * request is handled. 1129 | */ 1130 | 1131 | /** 1132 | * @ngdoc method 1133 | * @name ngMock.$httpBackend#expectPATCH 1134 | * @methodOf ngMock.$httpBackend 1135 | * @description 1136 | * Creates a new request expectation for PATCH requests. For more info see `expect()`. 1137 | * 1138 | * @param {string|RegExp} url HTTP url. 1139 | * @param {(string|RegExp)=} data HTTP request body. 1140 | * @param {Object=} headers HTTP headers. 1141 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1142 | * request is handled. 1143 | */ 1144 | 1145 | /** 1146 | * @ngdoc method 1147 | * @name ngMock.$httpBackend#expectJSONP 1148 | * @methodOf ngMock.$httpBackend 1149 | * @description 1150 | * Creates a new request expectation for JSONP requests. For more info see `expect()`. 1151 | * 1152 | * @param {string|RegExp} url HTTP url. 1153 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1154 | * request is handled. 1155 | */ 1156 | createShortMethods('expect'); 1157 | 1158 | 1159 | /** 1160 | * @ngdoc method 1161 | * @name ngMock.$httpBackend#flush 1162 | * @methodOf ngMock.$httpBackend 1163 | * @description 1164 | * Flushes all pending requests using the trained responses. 1165 | * 1166 | * @param {number=} count Number of responses to flush (in the order they arrived). If undefined, 1167 | * all pending requests will be flushed. If there are no pending requests when the flush method 1168 | * is called an exception is thrown (as this typically a sign of programming error). 1169 | */ 1170 | $httpBackend.flush = function(count) { 1171 | if (!responses.length) throw Error('No pending request to flush !'); 1172 | 1173 | if (angular.isDefined(count)) { 1174 | while (count--) { 1175 | if (!responses.length) throw Error('No more pending request to flush !'); 1176 | responses.shift()(); 1177 | } 1178 | } else { 1179 | while (responses.length) { 1180 | responses.shift()(); 1181 | } 1182 | } 1183 | $httpBackend.verifyNoOutstandingExpectation(); 1184 | }; 1185 | 1186 | 1187 | /** 1188 | * @ngdoc method 1189 | * @name ngMock.$httpBackend#verifyNoOutstandingExpectation 1190 | * @methodOf ngMock.$httpBackend 1191 | * @description 1192 | * Verifies that all of the requests defined via the `expect` api were made. If any of the 1193 | * requests were not made, verifyNoOutstandingExpectation throws an exception. 1194 | * 1195 | * Typically, you would call this method following each test case that asserts requests using an 1196 | * "afterEach" clause. 1197 | * 1198 | *
1199 |    *   afterEach($httpBackend.verifyExpectations);
1200 |    * 
1201 | */ 1202 | $httpBackend.verifyNoOutstandingExpectation = function() { 1203 | if (expectations.length) { 1204 | throw Error('Unsatisfied requests: ' + expectations.join(', ')); 1205 | } 1206 | }; 1207 | 1208 | 1209 | /** 1210 | * @ngdoc method 1211 | * @name ngMock.$httpBackend#verifyNoOutstandingRequest 1212 | * @methodOf ngMock.$httpBackend 1213 | * @description 1214 | * Verifies that there are no outstanding requests that need to be flushed. 1215 | * 1216 | * Typically, you would call this method following each test case that asserts requests using an 1217 | * "afterEach" clause. 1218 | * 1219 | *
1220 |    *   afterEach($httpBackend.verifyNoOutstandingRequest);
1221 |    * 
1222 | */ 1223 | $httpBackend.verifyNoOutstandingRequest = function() { 1224 | if (responses.length) { 1225 | throw Error('Unflushed requests: ' + responses.length); 1226 | } 1227 | }; 1228 | 1229 | 1230 | /** 1231 | * @ngdoc method 1232 | * @name ngMock.$httpBackend#resetExpectations 1233 | * @methodOf ngMock.$httpBackend 1234 | * @description 1235 | * Resets all request expectations, but preserves all backend definitions. Typically, you would 1236 | * call resetExpectations during a multiple-phase test when you want to reuse the same instance of 1237 | * $httpBackend mock. 1238 | */ 1239 | $httpBackend.resetExpectations = function() { 1240 | expectations.length = 0; 1241 | responses.length = 0; 1242 | }; 1243 | 1244 | return $httpBackend; 1245 | 1246 | 1247 | function createShortMethods(prefix) { 1248 | angular.forEach(['GET', 'DELETE', 'JSONP'], function(method) { 1249 | $httpBackend[prefix + method] = function(url, headers) { 1250 | return $httpBackend[prefix](method, url, undefined, headers) 1251 | } 1252 | }); 1253 | 1254 | angular.forEach(['PUT', 'POST', 'PATCH'], function(method) { 1255 | $httpBackend[prefix + method] = function(url, data, headers) { 1256 | return $httpBackend[prefix](method, url, data, headers) 1257 | } 1258 | }); 1259 | } 1260 | } 1261 | 1262 | function MockHttpExpectation(method, url, data, headers) { 1263 | 1264 | this.data = data; 1265 | this.headers = headers; 1266 | 1267 | this.match = function(m, u, d, h) { 1268 | if (method != m) return false; 1269 | if (!this.matchUrl(u)) return false; 1270 | if (angular.isDefined(d) && !this.matchData(d)) return false; 1271 | if (angular.isDefined(h) && !this.matchHeaders(h)) return false; 1272 | return true; 1273 | }; 1274 | 1275 | this.matchUrl = function(u) { 1276 | if (!url) return true; 1277 | if (angular.isFunction(url.test)) return url.test(u); 1278 | return url == u; 1279 | }; 1280 | 1281 | this.matchHeaders = function(h) { 1282 | if (angular.isUndefined(headers)) return true; 1283 | if (angular.isFunction(headers)) return headers(h); 1284 | return angular.equals(headers, h); 1285 | }; 1286 | 1287 | this.matchData = function(d) { 1288 | if (angular.isUndefined(data)) return true; 1289 | if (data && angular.isFunction(data.test)) return data.test(d); 1290 | if (data && !angular.isString(data)) return angular.toJson(data) == d; 1291 | return data == d; 1292 | }; 1293 | 1294 | this.toString = function() { 1295 | return method + ' ' + url; 1296 | }; 1297 | } 1298 | 1299 | function MockXhr() { 1300 | 1301 | // hack for testing $http, $httpBackend 1302 | MockXhr.$$lastInstance = this; 1303 | 1304 | this.open = function(method, url, async) { 1305 | this.$$method = method; 1306 | this.$$url = url; 1307 | this.$$async = async; 1308 | this.$$reqHeaders = {}; 1309 | this.$$respHeaders = {}; 1310 | }; 1311 | 1312 | this.send = function(data) { 1313 | this.$$data = data; 1314 | }; 1315 | 1316 | this.setRequestHeader = function(key, value) { 1317 | this.$$reqHeaders[key] = value; 1318 | }; 1319 | 1320 | this.getResponseHeader = function(name) { 1321 | // the lookup must be case insensitive, that's why we try two quick lookups and full scan at last 1322 | var header = this.$$respHeaders[name]; 1323 | if (header) return header; 1324 | 1325 | name = angular.lowercase(name); 1326 | header = this.$$respHeaders[name]; 1327 | if (header) return header; 1328 | 1329 | header = undefined; 1330 | angular.forEach(this.$$respHeaders, function(headerVal, headerName) { 1331 | if (!header && angular.lowercase(headerName) == name) header = headerVal; 1332 | }); 1333 | return header; 1334 | }; 1335 | 1336 | this.getAllResponseHeaders = function() { 1337 | var lines = []; 1338 | 1339 | angular.forEach(this.$$respHeaders, function(value, key) { 1340 | lines.push(key + ': ' + value); 1341 | }); 1342 | return lines.join('\n'); 1343 | }; 1344 | 1345 | this.abort = angular.noop; 1346 | } 1347 | 1348 | 1349 | /** 1350 | * @ngdoc function 1351 | * @name ngMock.$timeout 1352 | * @description 1353 | * 1354 | * This service is just a simple decorator for {@link ng.$timeout $timeout} service 1355 | * that adds a "flush" method. 1356 | */ 1357 | 1358 | /** 1359 | * @ngdoc method 1360 | * @name ngMock.$timeout#flush 1361 | * @methodOf ngMock.$timeout 1362 | * @description 1363 | * 1364 | * Flushes the queue of pending tasks. 1365 | */ 1366 | 1367 | /** 1368 | * 1369 | */ 1370 | angular.mock.$RootElementProvider = function() { 1371 | this.$get = function() { 1372 | return angular.element('
'); 1373 | } 1374 | }; 1375 | 1376 | /** 1377 | * @ngdoc overview 1378 | * @name ngMock 1379 | * @description 1380 | * 1381 | * The `ngMock` is an angular module which is used with `ng` module and adds unit-test configuration as well as useful 1382 | * mocks to the {@link AUTO.$injector $injector}. 1383 | */ 1384 | angular.module('ngMock', ['ng']).provider({ 1385 | $browser: angular.mock.$BrowserProvider, 1386 | $exceptionHandler: angular.mock.$ExceptionHandlerProvider, 1387 | $log: angular.mock.$LogProvider, 1388 | $httpBackend: angular.mock.$HttpBackendProvider, 1389 | $rootElement: angular.mock.$RootElementProvider 1390 | }).config(function($provide) { 1391 | $provide.decorator('$timeout', function($delegate, $browser) { 1392 | $delegate.flush = function() { 1393 | $browser.defer.flush(); 1394 | }; 1395 | return $delegate; 1396 | }); 1397 | }); 1398 | 1399 | 1400 | /** 1401 | * @ngdoc overview 1402 | * @name ngMockE2E 1403 | * @description 1404 | * 1405 | * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing. 1406 | * Currently there is only one mock present in this module - 1407 | * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock. 1408 | */ 1409 | angular.module('ngMockE2E', ['ng']).config(function($provide) { 1410 | $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); 1411 | }); 1412 | 1413 | /** 1414 | * @ngdoc object 1415 | * @name ngMockE2E.$httpBackend 1416 | * @description 1417 | * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of 1418 | * applications that use the {@link ng.$http $http service}. 1419 | * 1420 | * *Note*: For fake http backend implementation suitable for unit testing please see 1421 | * {@link ngMock.$httpBackend unit-testing $httpBackend mock}. 1422 | * 1423 | * This implementation can be used to respond with static or dynamic responses via the `when` api 1424 | * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the 1425 | * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch 1426 | * templates from a webserver). 1427 | * 1428 | * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application 1429 | * is being developed with the real backend api replaced with a mock, it is often desirable for 1430 | * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch 1431 | * templates or static files from the webserver). To configure the backend with this behavior 1432 | * use the `passThrough` request handler of `when` instead of `respond`. 1433 | * 1434 | * Additionally, we don't want to manually have to flush mocked out requests like we do during unit 1435 | * testing. For this reason the e2e $httpBackend automatically flushes mocked out requests 1436 | * automatically, closely simulating the behavior of the XMLHttpRequest object. 1437 | * 1438 | * To setup the application to run with this http backend, you have to create a module that depends 1439 | * on the `ngMockE2E` and your application modules and defines the fake backend: 1440 | * 1441 | *
1442 |  *   myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']);
1443 |  *   myAppDev.run(function($httpBackend) {
1444 |  *     phones = [{name: 'phone1'}, {name: 'phone2'}];
1445 |  *
1446 |  *     // returns the current list of phones
1447 |  *     $httpBackend.whenGET('/phones').respond(phones);
1448 |  *
1449 |  *     // adds a new phone to the phones array
1450 |  *     $httpBackend.whenPOST('/phones').respond(function(method, url, data) {
1451 |  *       phones.push(angular.fromJSON(data));
1452 |  *     });
1453 |  *     $httpBackend.whenGET(/^\/templates\//).passThrough();
1454 |  *     //...
1455 |  *   });
1456 |  * 
1457 | * 1458 | * Afterwards, bootstrap your app with this new module. 1459 | */ 1460 | 1461 | /** 1462 | * @ngdoc method 1463 | * @name ngMockE2E.$httpBackend#when 1464 | * @methodOf ngMockE2E.$httpBackend 1465 | * @description 1466 | * Creates a new backend definition. 1467 | * 1468 | * @param {string} method HTTP method. 1469 | * @param {string|RegExp} url HTTP url. 1470 | * @param {(string|RegExp)=} data HTTP request body. 1471 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header 1472 | * object and returns true if the headers match the current definition. 1473 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1474 | * control how a matched request is handled. 1475 | * 1476 | * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` 1477 | * – The respond method takes a set of static data to be returned or a function that can return 1478 | * an array containing response status (number), response data (string) and response headers 1479 | * (Object). 1480 | * - passThrough – `{function()}` – Any request matching a backend definition with `passThrough` 1481 | * handler, will be pass through to the real backend (an XHR request will be made to the 1482 | * server. 1483 | */ 1484 | 1485 | /** 1486 | * @ngdoc method 1487 | * @name ngMockE2E.$httpBackend#whenGET 1488 | * @methodOf ngMockE2E.$httpBackend 1489 | * @description 1490 | * Creates a new backend definition for GET requests. For more info see `when()`. 1491 | * 1492 | * @param {string|RegExp} url HTTP url. 1493 | * @param {(Object|function(Object))=} headers HTTP headers. 1494 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1495 | * control how a matched request is handled. 1496 | */ 1497 | 1498 | /** 1499 | * @ngdoc method 1500 | * @name ngMockE2E.$httpBackend#whenHEAD 1501 | * @methodOf ngMockE2E.$httpBackend 1502 | * @description 1503 | * Creates a new backend definition for HEAD requests. For more info see `when()`. 1504 | * 1505 | * @param {string|RegExp} url HTTP url. 1506 | * @param {(Object|function(Object))=} headers HTTP headers. 1507 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1508 | * control how a matched request is handled. 1509 | */ 1510 | 1511 | /** 1512 | * @ngdoc method 1513 | * @name ngMockE2E.$httpBackend#whenDELETE 1514 | * @methodOf ngMockE2E.$httpBackend 1515 | * @description 1516 | * Creates a new backend definition for DELETE requests. For more info see `when()`. 1517 | * 1518 | * @param {string|RegExp} url HTTP url. 1519 | * @param {(Object|function(Object))=} headers HTTP headers. 1520 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1521 | * control how a matched request is handled. 1522 | */ 1523 | 1524 | /** 1525 | * @ngdoc method 1526 | * @name ngMockE2E.$httpBackend#whenPOST 1527 | * @methodOf ngMockE2E.$httpBackend 1528 | * @description 1529 | * Creates a new backend definition for POST requests. For more info see `when()`. 1530 | * 1531 | * @param {string|RegExp} url HTTP url. 1532 | * @param {(string|RegExp)=} data HTTP request body. 1533 | * @param {(Object|function(Object))=} headers HTTP headers. 1534 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1535 | * control how a matched request is handled. 1536 | */ 1537 | 1538 | /** 1539 | * @ngdoc method 1540 | * @name ngMockE2E.$httpBackend#whenPUT 1541 | * @methodOf ngMockE2E.$httpBackend 1542 | * @description 1543 | * Creates a new backend definition for PUT requests. For more info see `when()`. 1544 | * 1545 | * @param {string|RegExp} url HTTP url. 1546 | * @param {(string|RegExp)=} data HTTP request body. 1547 | * @param {(Object|function(Object))=} headers HTTP headers. 1548 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1549 | * control how a matched request is handled. 1550 | */ 1551 | 1552 | /** 1553 | * @ngdoc method 1554 | * @name ngMockE2E.$httpBackend#whenPATCH 1555 | * @methodOf ngMockE2E.$httpBackend 1556 | * @description 1557 | * Creates a new backend definition for PATCH requests. For more info see `when()`. 1558 | * 1559 | * @param {string|RegExp} url HTTP url. 1560 | * @param {(string|RegExp)=} data HTTP request body. 1561 | * @param {(Object|function(Object))=} headers HTTP headers. 1562 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1563 | * control how a matched request is handled. 1564 | */ 1565 | 1566 | /** 1567 | * @ngdoc method 1568 | * @name ngMockE2E.$httpBackend#whenJSONP 1569 | * @methodOf ngMockE2E.$httpBackend 1570 | * @description 1571 | * Creates a new backend definition for JSONP requests. For more info see `when()`. 1572 | * 1573 | * @param {string|RegExp} url HTTP url. 1574 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1575 | * control how a matched request is handled. 1576 | */ 1577 | angular.mock.e2e = {}; 1578 | angular.mock.e2e.$httpBackendDecorator = ['$delegate', '$browser', createHttpBackendMock]; 1579 | 1580 | 1581 | angular.mock.clearDataCache = function() { 1582 | var key, 1583 | cache = angular.element.cache; 1584 | 1585 | for(key in cache) { 1586 | if (cache.hasOwnProperty(key)) { 1587 | var handle = cache[key].handle; 1588 | 1589 | handle && angular.element(handle.elem).unbind(); 1590 | delete cache[key]; 1591 | } 1592 | } 1593 | }; 1594 | 1595 | 1596 | window.jstestdriver && (function(window) { 1597 | /** 1598 | * Global method to output any number of objects into JSTD console. Useful for debugging. 1599 | */ 1600 | window.dump = function() { 1601 | var args = []; 1602 | angular.forEach(arguments, function(arg) { 1603 | args.push(angular.mock.dump(arg)); 1604 | }); 1605 | jstestdriver.console.log.apply(jstestdriver.console, args); 1606 | if (window.console) { 1607 | window.console.log.apply(window.console, args); 1608 | } 1609 | }; 1610 | })(window); 1611 | 1612 | 1613 | window.jasmine && (function(window) { 1614 | 1615 | afterEach(function() { 1616 | var spec = getCurrentSpec(); 1617 | var injector = spec.$injector; 1618 | 1619 | spec.$injector = null; 1620 | spec.$modules = null; 1621 | 1622 | if (injector) { 1623 | injector.get('$rootElement').unbind(); 1624 | injector.get('$browser').pollFns.length = 0; 1625 | } 1626 | 1627 | angular.mock.clearDataCache(); 1628 | 1629 | // clean up jquery's fragment cache 1630 | angular.forEach(angular.element.fragments, function(val, key) { 1631 | delete angular.element.fragments[key]; 1632 | }); 1633 | 1634 | MockXhr.$$lastInstance = null; 1635 | 1636 | angular.forEach(angular.callbacks, function(val, key) { 1637 | delete angular.callbacks[key]; 1638 | }); 1639 | angular.callbacks.counter = 0; 1640 | }); 1641 | 1642 | function getCurrentSpec() { 1643 | return jasmine.getEnv().currentSpec; 1644 | } 1645 | 1646 | function isSpecRunning() { 1647 | var spec = getCurrentSpec(); 1648 | return spec && spec.queue.running; 1649 | } 1650 | 1651 | /** 1652 | * @ngdoc function 1653 | * @name angular.mock.module 1654 | * @description 1655 | * 1656 | * *NOTE*: This function is also published on window for easy access.
1657 | * *NOTE*: Only available with {@link http://pivotal.github.com/jasmine/ jasmine}. 1658 | * 1659 | * This function registers a module configuration code. It collects the configuration information 1660 | * which will be used when the injector is created by {@link angular.mock.inject inject}. 1661 | * 1662 | * See {@link angular.mock.inject inject} for usage example 1663 | * 1664 | * @param {...(string|Function)} fns any number of modules which are represented as string 1665 | * aliases or as anonymous module initialization functions. The modules are used to 1666 | * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. 1667 | */ 1668 | window.module = angular.mock.module = function() { 1669 | var moduleFns = Array.prototype.slice.call(arguments, 0); 1670 | return isSpecRunning() ? workFn() : workFn; 1671 | ///////////////////// 1672 | function workFn() { 1673 | var spec = getCurrentSpec(); 1674 | if (spec.$injector) { 1675 | throw Error('Injector already created, can not register a module!'); 1676 | } else { 1677 | var modules = spec.$modules || (spec.$modules = []); 1678 | angular.forEach(moduleFns, function(module) { 1679 | modules.push(module); 1680 | }); 1681 | } 1682 | } 1683 | }; 1684 | 1685 | /** 1686 | * @ngdoc function 1687 | * @name angular.mock.inject 1688 | * @description 1689 | * 1690 | <<<<<<< HEAD 1691 | * *NOTE*: This is function is also published on window for easy access.
1692 | * *NOTE*: Only available with {@link http://pivotal.github.com/jasmine/ jasmine}. 1693 | ======= 1694 | * *NOTE*: This function is also published on window for easy access.
1695 | >>>>>>> 8dca056... docs(mocks): fix typos 1696 | * 1697 | * The inject function wraps a function into an injectable function. The inject() creates new 1698 | * instance of {@link AUTO.$injector $injector} per test, which is then used for 1699 | * resolving references. 1700 | * 1701 | * See also {@link angular.mock.module module} 1702 | * 1703 | * Example of what a typical jasmine tests looks like with the inject method. 1704 | *
1705 |    *
1706 |    *   angular.module('myApplicationModule', [])
1707 |    *       .value('mode', 'app')
1708 |    *       .value('version', 'v1.0.1');
1709 |    *
1710 |    *
1711 |    *   describe('MyApp', function() {
1712 |    *
1713 |    *     // You need to load modules that you want to test,
1714 |    *     // it loads only the "ng" module by default.
1715 |    *     beforeEach(module('myApplicationModule'));
1716 |    *
1717 |    *
1718 |    *     // inject() is used to inject arguments of all given functions
1719 |    *     it('should provide a version', inject(function(mode, version) {
1720 |    *       expect(version).toEqual('v1.0.1');
1721 |    *       expect(mode).toEqual('app');
1722 |    *     }));
1723 |    *
1724 |    *
1725 |    *     // The inject and module method can also be used inside of the it or beforeEach
1726 |    *     it('should override a version and test the new version is injected', function() {
1727 |    *       // module() takes functions or strings (module aliases)
1728 |    *       module(function($provide) {
1729 |    *         $provide.value('version', 'overridden'); // override version here
1730 |    *       });
1731 |    *
1732 |    *       inject(function(version) {
1733 |    *         expect(version).toEqual('overridden');
1734 |    *       });
1735 |    *     ));
1736 |    *   });
1737 |    *
1738 |    * 
1739 | * 1740 | * @param {...Function} fns any number of functions which will be injected using the injector. 1741 | */ 1742 | window.inject = angular.mock.inject = function() { 1743 | var blockFns = Array.prototype.slice.call(arguments, 0); 1744 | var errorForStack = new Error('Declaration Location'); 1745 | return isSpecRunning() ? workFn() : workFn; 1746 | ///////////////////// 1747 | function workFn() { 1748 | var spec = getCurrentSpec(); 1749 | var modules = spec.$modules || []; 1750 | modules.unshift('ngMock'); 1751 | modules.unshift('ng'); 1752 | var injector = spec.$injector; 1753 | if (!injector) { 1754 | injector = spec.$injector = angular.injector(modules); 1755 | } 1756 | for(var i = 0, ii = blockFns.length; i < ii; i++) { 1757 | try { 1758 | injector.invoke(blockFns[i] || angular.noop, this); 1759 | } catch (e) { 1760 | if(e.stack && errorForStack) e.stack += '\n' + errorForStack.stack; 1761 | throw e; 1762 | } finally { 1763 | errorForStack = null; 1764 | } 1765 | } 1766 | } 1767 | }; 1768 | })(window); 1769 | --------------------------------------------------------------------------------