├── .idea ├── .name ├── copyright │ └── profiles_settings.xml ├── scopes │ └── scope_settings.xml ├── encodings.xml ├── vcs.xml ├── modules.xml ├── libraries │ └── KotlinJavaScript.xml ├── misc.xml ├── compiler.xml ├── uiDesigner.xml └── workspace.xml ├── lib └── kotlin-jslib.jar ├── bower_components ├── todomvc-common │ ├── bg.png │ ├── base.js │ └── base.css └── angular-mocks │ └── angular-mocks.js ├── test ├── readme.md ├── config │ └── testacular.conf.js ├── package.json └── unit │ ├── directivesSpec.js │ └── todoCtrlSpec.js ├── js ├── app.js ├── directives │ ├── todoBlur.js │ └── todoFocus.js ├── services │ └── todoStorage.js └── controllers │ └── todoCtrl.js ├── bower.json ├── src ├── js_ext.kt ├── services.kt ├── app.kt ├── directives.kt ├── angular.kt └── controllers.kt ├── kotlin-angularjs.iml ├── README.md ├── index.html └── script └── kotlinEcma3.js /.idea/.name: -------------------------------------------------------------------------------- 1 | kotlin-angularjs -------------------------------------------------------------------------------- /lib/kotlin-jslib.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freewind/kotlin-angularjs/HEAD/lib/kotlin-jslib.jar -------------------------------------------------------------------------------- /bower_components/todomvc-common/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freewind/kotlin-angularjs/HEAD/bower_components/todomvc-common/bg.png -------------------------------------------------------------------------------- /test/readme.md: -------------------------------------------------------------------------------- 1 | Angular Unit Tests 2 | ================== 3 | 4 | To run the test suite, run these commands: 5 | 6 | npm install 7 | npm test 8 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | /*global angular */ 2 | /*jshint unused:false */ 3 | 'use strict'; 4 | 5 | /** 6 | * The main TodoMVC app module 7 | * 8 | * @type {angular.Module} 9 | */ 10 | var todomvc = angular.module('todomvc', []); 11 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-angular", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "~1.0.5", 6 | "todomvc-common": "~0.1.4" 7 | }, 8 | "devDependencies": { 9 | "angular-mocks": "~1.0.5" 10 | } 11 | } -------------------------------------------------------------------------------- /test/config/testacular.conf.js: -------------------------------------------------------------------------------- 1 | basePath = '../../'; 2 | 3 | files = [ 4 | JASMINE, 5 | JASMINE_ADAPTER, 6 | 'components/angular/angular.js', 7 | 'components/angular-mocks/angular-mocks.js', 8 | 'js/**/*.js', 9 | 'test/unit/**/*.js' 10 | ]; 11 | 12 | autoWatch = true; 13 | 14 | browsers = ['Chrome']; 15 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/libraries/KotlinJavaScript.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/js_ext.kt: -------------------------------------------------------------------------------- 1 | package js_ext 2 | 3 | native fun Array.push(x: T) = js.noImpl 4 | native fun Array.splice(i1: Int, i2: Int) = js.noImpl 5 | native fun Array.indexOf(x: T) = js.noImpl 6 | native fun Array.filter(x: (T)->Boolean) = js.noImpl 7 | native fun Array.forEach(x: (T)->Unit) = js.noImpl 8 | native val Array.length: Int = js.noImpl -------------------------------------------------------------------------------- /js/directives/todoBlur.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | 'use strict'; 3 | 4 | /** 5 | * Directive that executes an expression when the element it is applied to loses focus 6 | */ 7 | todomvc.directive('todoBlur', function () { 8 | return function (scope, elem, attrs) { 9 | elem.bind('blur', function () { 10 | scope.$apply(attrs.todoBlur); 11 | }); 12 | }; 13 | }); 14 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-angular-tests", 3 | "description": "Unit tests for the AngularJS example of TodoMVC", 4 | "author": "Pascal Hartig ", 5 | "version": "1.0.0", 6 | "devDependencies": { 7 | "testacular": "~ 0.4.0" 8 | }, 9 | "scripts": { 10 | "test": "testacular start config/testacular.conf.js" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /js/services/todoStorage.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | 'use strict'; 3 | 4 | /** 5 | * Services that persists and retrieves TODOs from localStorage 6 | */ 7 | todomvc.factory('todoStorage', function () { 8 | var STORAGE_ID = 'todos-angularjs'; 9 | 10 | return { 11 | get: function () { 12 | return JSON.parse(localStorage.getItem(STORAGE_ID) || '[]'); 13 | }, 14 | 15 | put: function (todos) { 16 | localStorage.setItem(STORAGE_ID, JSON.stringify(todos)); 17 | } 18 | }; 19 | }); 20 | -------------------------------------------------------------------------------- /kotlin-angularjs.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /js/directives/todoFocus.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | 'use strict'; 3 | 4 | /** 5 | * Directive that places focus on the element it is applied to when the expression it binds to evaluates to true 6 | */ 7 | todomvc.directive('todoFocus', function todoFocus($timeout) { 8 | return function (scope, elem, attrs) { 9 | scope.$watch(attrs.todoFocus, function (newVal) { 10 | if (newVal) { 11 | $timeout(function () { 12 | elem[0].focus(); 13 | }, 0, false); 14 | } 15 | }); 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /src/services.kt: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import controllers.* 4 | import angular.LocalStorage 5 | 6 | native("localStorage") val localStorage: LocalStorage = js.noImpl 7 | 8 | class TodoStorage { 9 | private val STORAGE_ID = "TODOS-angularjs" 10 | fun get(): Array { 11 | var data = localStorage.getItem(STORAGE_ID) 12 | if(data == null) { 13 | data = "[]" 14 | } 15 | return JSON.parse(data!!) as Array 16 | } 17 | fun put(data: Array) { 18 | localStorage.setItem(STORAGE_ID, JSON.stringify(data)) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app.kt: -------------------------------------------------------------------------------- 1 | import angular.* 2 | import controllers.* 3 | import services.* 4 | import directives.* 5 | 6 | native trait FilterFilter { 7 | fun invoke(data: Array, completed: Boolean): Array 8 | } 9 | 10 | fun main() { 11 | var todomvc = angular.module("todomvc", array()) 12 | todomvc.factory("todoStorage", array({ TodoStorage() })) 13 | todomvc.directive("todoFocus", array("\$timeout", {(x: Timeout) -> todoFocus(x) })) 14 | todomvc.controller("TodoCtrl", array("\$scope", "\$location", "todoStorage", "filterFilter", { 15 | (a: TodoScope, b: Location, c: TodoStorage, d: FilterFilter) -> 16 | TodoCtrl(a, b, c, d) 17 | })) 18 | } 19 | 20 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/directives.kt: -------------------------------------------------------------------------------- 1 | package directives 2 | 3 | import js_ext.* 4 | import angular.* 5 | 6 | native trait Attrs { 7 | val todoBlur: Any 8 | val todoFocus: Any 9 | } 10 | 11 | fun todoFocus(timeout: Timeout): Directive { 12 | val directive = Directive() 13 | directive.link = { scope, elem, _attrs -> 14 | val attrs = _attrs as Attrs 15 | scope.`$watch`(attrs.todoFocus, { newVal -> 16 | if(newVal as Boolean) { 17 | timeout({ elem[0].focus() }, 0, false) 18 | } 19 | }) 20 | } 21 | return directive 22 | } 23 | 24 | fun todoBlur(): Directive { 25 | val directive = Directive() 26 | directive.link = { scope, elem, _attrs -> 27 | val attrs = _attrs as Attrs 28 | scope.`$apply`(attrs.todoBlur) 29 | } 30 | return directive 31 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Try to use Kotlin to write the angularjs TODO MVC demo. 2 | 3 | You can see there are several .kt files in the "src" directory, which are kotlin files and will be compiled to Javascript code. 4 | 5 | Finally, it works ~ 6 | 7 | To run this project, you should use IDEA + Kotlin plugin, and configure it carefully to make it compiling. 8 | After compiling, there will be a "kotlin-angularjs.js" file under "out/production/kotlin-angularjs/". 9 | 10 | You can use browser to open the "index.html" file to view the demo. If you use chrome, 11 | please make sure it can visit local js files. 12 | 13 | Although there are some issue to use Kotlin to work with Angularjs, but it works and I'm feeling good. 14 | I think it will be better and better in the near future. 15 | 16 | I'm new to Kotlin, the code is not very good. Please help me to improve it, thank you ~ 17 | 18 | -------------------------------------------------------------------------------- /src/angular.kt: -------------------------------------------------------------------------------- 1 | package angular 2 | 3 | native trait Angular { 4 | fun module(name: String, deps: Array): Module 5 | } 6 | 7 | native trait Module { 8 | fun directive(name: String, injectsAndDef: Array): Unit 9 | fun factory (name: String, injectsAndDef: Array): Unit 10 | fun controller(name: String, injectsAndDef: Array): Unit 11 | } 12 | 13 | native trait ElemNode { 14 | fun focus() 15 | } 16 | 17 | native trait Elem { 18 | fun bind(name: String, func: () -> Unit) 19 | fun get(index: Int): ElemNode 20 | } 21 | 22 | native("Object") 23 | class Directive { 24 | var link: (scope: Scope, elem: Elem, attrs: Any) -> Unit = js.noImpl 25 | } 26 | 27 | native trait Timeout { 28 | fun invoke(func: () -> Unit, x: Int, y: Boolean) 29 | } 30 | 31 | native trait Location { 32 | fun path(): String 33 | fun path(p: String) 34 | } 35 | 36 | native trait Scope { 37 | fun `$watch`(exp: Any, todo: (Any?) -> Unit, deepWatch: Boolean) 38 | fun `$watch`(exp: Any, todo: (Any?) -> Unit) 39 | fun `$apply`(func: Any): Unit 40 | } 41 | 42 | native trait LocalStorage { 43 | fun getItem(id: String): String? 44 | fun setItem(id: String, data: String) 45 | } 46 | 47 | native("angular") val angular: Angular = js.noImpl 48 | -------------------------------------------------------------------------------- /bower_components/todomvc-common/base.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | if (location.hostname === 'todomvc.com') { 5 | window._gaq = [['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script')); 6 | } 7 | 8 | function getSourcePath() { 9 | // If accessed via tastejs.github.io/todomvc/, strip the project path. 10 | if (location.hostname.indexOf('github.io') > 0) { 11 | return location.pathname.replace(/todomvc\//, ''); 12 | } 13 | return location.pathname; 14 | } 15 | 16 | function appendSourceLink() { 17 | var sourceLink = document.createElement('a'); 18 | var paragraph = document.createElement('p'); 19 | var footer = document.getElementById('info'); 20 | var urlBase = 'https://github.com/tastejs/todomvc/tree/gh-pages'; 21 | 22 | if (footer) { 23 | sourceLink.href = urlBase + getSourcePath(); 24 | sourceLink.appendChild(document.createTextNode('Check out the source')); 25 | paragraph.appendChild(sourceLink); 26 | footer.appendChild(paragraph); 27 | } 28 | } 29 | 30 | function redirect() { 31 | if (location.hostname === 'tastejs.github.io') { 32 | location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); 33 | } 34 | } 35 | 36 | appendSourceLink(); 37 | redirect(); 38 | })(); 39 | -------------------------------------------------------------------------------- /test/unit/directivesSpec.js: -------------------------------------------------------------------------------- 1 | /*global describe, it, beforeEach, inject, expect, angular*/ 2 | (function () { 3 | 'use strict'; 4 | 5 | beforeEach(module('todomvc')); 6 | 7 | describe('todoBlur directive', function () { 8 | var scope, compile; 9 | 10 | beforeEach(inject(function ($rootScope, $compile) { 11 | scope = $rootScope.$new(); 12 | compile = $compile; 13 | })); 14 | 15 | it('should $apply on blur', function () { 16 | var el, 17 | mock = { 18 | called: false, 19 | call: function () { this.called = true; } 20 | }; 21 | 22 | scope.mock = mock; 23 | el = angular.element(''); 24 | compile(el)(scope); 25 | 26 | el.triggerHandler('blur'); 27 | scope.$digest(); 28 | 29 | expect(mock.called).toBeTruthy(); 30 | }); 31 | }); 32 | 33 | describe('todoFocus directive', function () { 34 | var scope, compile, browser; 35 | 36 | beforeEach(inject(function ($rootScope, $compile, $browser) { 37 | scope = $rootScope.$new(); 38 | compile = $compile; 39 | browser = $browser; 40 | })); 41 | 42 | it('should focus on truthy expression', function () { 43 | var el = angular.element(''); 44 | scope.focus = false; 45 | 46 | compile(el)(scope); 47 | expect(browser.deferredFns.length).toBe(0); 48 | 49 | scope.$apply(function () { 50 | scope.focus = true; 51 | }); 52 | 53 | expect(browser.deferredFns.length).toBe(1); 54 | }); 55 | }); 56 | }()); 57 | -------------------------------------------------------------------------------- /js/controllers/todoCtrl.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | 'use strict'; 3 | 4 | /** 5 | * The main controller for the app. The controller: 6 | * - retrieves and persists the model via the todoStorage service 7 | * - exposes the model to the template and provides event handlers 8 | */ 9 | todomvc.controller('TodoCtrl', function TodoCtrl($scope, $location, todoStorage, filterFilter) { 10 | var todos = $scope.todos = todoStorage.get(); 11 | 12 | $scope.newTodo = ''; 13 | $scope.editedTodo = null; 14 | 15 | $scope.$watch('todos', function () { 16 | $scope.remainingCount = filterFilter(todos, {completed: false}).length; 17 | $scope.completedCount = todos.length - $scope.remainingCount; 18 | $scope.allChecked = !$scope.remainingCount; 19 | todoStorage.put(todos); 20 | }, true); 21 | 22 | if ($location.path() === '') { 23 | $location.path('/'); 24 | } 25 | 26 | $scope.location = $location; 27 | 28 | $scope.$watch('location.path()', function (path) { 29 | $scope.statusFilter = (path === '/active') ? 30 | { completed: false } : (path === '/completed') ? 31 | { completed: true } : null; 32 | }); 33 | 34 | $scope.addTodo = function () { 35 | if (!$scope.newTodo.length) { 36 | return; 37 | } 38 | 39 | todos.push({ 40 | title: $scope.newTodo, 41 | completed: false 42 | }); 43 | 44 | $scope.newTodo = ''; 45 | }; 46 | 47 | $scope.editTodo = function (todo) { 48 | $scope.editedTodo = todo; 49 | }; 50 | 51 | $scope.doneEditing = function (todo) { 52 | $scope.editedTodo = null; 53 | if (!todo.title) { 54 | $scope.removeTodo(todo); 55 | } 56 | }; 57 | 58 | $scope.removeTodo = function (todo) { 59 | todos.splice(todos.indexOf(todo), 1); 60 | }; 61 | 62 | $scope.clearCompletedTodos = function () { 63 | $scope.todos = todos = todos.filter(function (val) { 64 | return !val.completed; 65 | }); 66 | }; 67 | 68 | $scope.markAll = function (completed) { 69 | todos.forEach(function (todo) { 70 | todo.completed = completed; 71 | }); 72 | }; 73 | }); 74 | -------------------------------------------------------------------------------- /src/controllers.kt: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import js_ext.* 4 | import angular.* 5 | import services.TodoStorage 6 | 7 | native("Object") 8 | class Todo { 9 | var title: String? = js.noImpl 10 | var completed: Boolean = js.noImpl 11 | } 12 | 13 | native trait TodoScope: Scope { 14 | var todos: Array 15 | var newTodo: String 16 | var editedTodo: Todo? 17 | var remainingCount: Int 18 | var completedCount: Int 19 | var allChecked: Boolean 20 | var statusFilter: Boolean? 21 | var location: Location 22 | var addTodo: () -> Unit 23 | var editTodo: (Todo) -> Unit 24 | var doneEditing: (Todo) -> Unit 25 | var removeTodo: (Todo) -> Unit 26 | var clearCompletedTodos: () -> Unit 27 | var markAll: (Boolean) -> Unit 28 | } 29 | 30 | fun TodoCtrl(scope: TodoScope, location: Location, todoStorage: TodoStorage, filterFilter: FilterFilter) { 31 | 32 | scope.todos = todoStorage.get() 33 | scope.newTodo = "" 34 | scope.editedTodo = null 35 | 36 | scope.`$watch`("todos", { 37 | scope.remainingCount = filterFilter(scope.todos, false).length 38 | scope.completedCount = scope.todos.length - scope.remainingCount 39 | scope.completedCount = scope.todos.length - scope.remainingCount 40 | scope.allChecked = scope.remainingCount == 0 41 | }, true) 42 | 43 | if(location.path() == "") { 44 | location.path("/") 45 | } 46 | 47 | scope.location = location 48 | 49 | scope.`$watch`("location.path()", { path -> 50 | scope.statusFilter = when(path) { 51 | "/active" -> false 52 | "/completed" -> true 53 | else -> null 54 | } 55 | }) 56 | 57 | scope.addTodo = { 58 | if(scope.newTodo.length > 0) { 59 | val todo = Todo() 60 | todo.title = scope.newTodo 61 | todo.completed = false 62 | scope.todos.push(todo) 63 | scope.newTodo = "" 64 | } 65 | } 66 | 67 | scope.editTodo = { todo -> 68 | scope.editedTodo = todo 69 | } 70 | 71 | scope.doneEditing = { todo -> 72 | scope.editedTodo = null 73 | if(todo.title != null) { 74 | scope.removeTodo(todo) 75 | } 76 | } 77 | 78 | scope.removeTodo = { todo -> 79 | scope.todos.splice(scope.todos.indexOf(todo), 1) 80 | } 81 | 82 | scope.clearCompletedTodos = { 83 | scope.todos = scope.todos.filter({ !it.completed }) 84 | } 85 | 86 | scope.markAll = { completed -> 87 | scope.todos.forEach({ it.completed = completed }) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AngularJS • TodoMVC 7 | 8 | 9 | 10 | 11 |
12 | 18 |
19 | 20 | 21 |
    22 |
  • 23 |
    24 | 25 | 26 | 27 |
    28 |
    29 | 30 |
    31 |
  • 32 |
33 |
34 |
35 | {{remainingCount}} 36 | 37 | 38 | 49 | 50 |
51 |
52 | 62 | 63 | 64 | 65 | 66 | 69 | 70 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /test/unit/todoCtrlSpec.js: -------------------------------------------------------------------------------- 1 | /*global describe, it, beforeEach, inject, expect*/ 2 | (function () { 3 | 'use strict'; 4 | 5 | describe('Todo Controller', function () { 6 | var ctrl, scope; 7 | 8 | // Load the module containing the app, only 'ng' is loaded by default. 9 | beforeEach(module('todomvc')); 10 | 11 | beforeEach(inject(function ($controller, $rootScope) { 12 | scope = $rootScope.$new(); 13 | ctrl = $controller('TodoCtrl', {$scope: scope}); 14 | })); 15 | 16 | it('should not have an edited Todo on start', function () { 17 | expect(scope.editedTodo).toBeNull(); 18 | }); 19 | 20 | it('should not have any Todos on start', function () { 21 | expect(scope.todos.length).toBe(0); 22 | }); 23 | 24 | it('should have all Todos completed', function () { 25 | scope.$digest(); 26 | expect(scope.allChecked).toBeTruthy(); 27 | }); 28 | 29 | describe('the path', function () { 30 | it('should default to "/"', function () { 31 | expect(scope.location.path()).toBe('/'); 32 | }); 33 | 34 | describe('being at /active', function () { 35 | it('should filter non-completed', inject(function ($controller) { 36 | ctrl = $controller('TodoCtrl', { 37 | $scope: scope, 38 | $location: { 39 | path: function () { return '/active'; } 40 | } 41 | }); 42 | 43 | scope.$digest(); 44 | expect(scope.statusFilter.completed).toBeFalsy(); 45 | })); 46 | }); 47 | 48 | describe('being at /completed', function () { 49 | it('should filter completed', inject(function ($controller) { 50 | ctrl = $controller('TodoCtrl', { 51 | $scope: scope, 52 | $location: { 53 | path: function () { return '/completed'; } 54 | } 55 | }); 56 | 57 | scope.$digest(); 58 | expect(scope.statusFilter.completed).toBeTruthy(); 59 | })); 60 | }); 61 | }); 62 | 63 | describe('having some saved Todos', function () { 64 | var todoList, 65 | todoStorage = { 66 | storage: {}, 67 | get: function () { 68 | return this.storage; 69 | }, 70 | put: function (value) { 71 | this.storage = value; 72 | } 73 | }; 74 | 75 | beforeEach(inject(function ($controller) { 76 | todoList = [{ 77 | 'title': 'Uncompleted Item 0', 78 | 'completed': false 79 | }, { 80 | 'title': 'Uncompleted Item 1', 81 | 'completed': false 82 | }, { 83 | 'title': 'Uncompleted Item 2', 84 | 'completed': false 85 | }, { 86 | 'title': 'Completed Item 0', 87 | 'completed': true 88 | }, { 89 | 'title': 'Completed Item 1', 90 | 'completed': true 91 | }]; 92 | 93 | todoStorage.storage = todoList; 94 | ctrl = $controller('TodoCtrl', { 95 | $scope: scope, 96 | todoStorage: todoStorage 97 | }); 98 | scope.$digest(); 99 | })); 100 | 101 | it('should count Todos correctly', function () { 102 | expect(scope.todos.length).toBe(5); 103 | expect(scope.remainingCount).toBe(3); 104 | expect(scope.completedCount).toBe(2); 105 | expect(scope.allChecked).toBeFalsy(); 106 | }); 107 | 108 | it('should save Todos to local storage', function () { 109 | expect(todoStorage.storage.length).toBe(5); 110 | }); 111 | 112 | it('should remove Todos w/o title on saving', function () { 113 | var todo = todoList[2]; 114 | todo.title = ''; 115 | 116 | scope.doneEditing(todo); 117 | expect(scope.todos.length).toBe(4); 118 | }); 119 | 120 | it('clearCompletedTodos() should clear completed Todos', function () { 121 | scope.clearCompletedTodos(); 122 | expect(scope.todos.length).toBe(3); 123 | }); 124 | 125 | it('markAll() should mark all Todos completed', function () { 126 | scope.markAll(); 127 | scope.$digest(); 128 | expect(scope.completedCount).toBe(5); 129 | }); 130 | }); 131 | }); 132 | }()); 133 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /bower_components/todomvc-common/base.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | color: inherit; 16 | -webkit-appearance: none; 17 | /*-moz-appearance: none;*/ 18 | -ms-appearance: none; 19 | -o-appearance: none; 20 | appearance: none; 21 | } 22 | 23 | body { 24 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 25 | line-height: 1.4em; 26 | background: #eaeaea url('bg.png'); 27 | color: #4d4d4d; 28 | width: 550px; 29 | margin: 0 auto; 30 | -webkit-font-smoothing: antialiased; 31 | -moz-font-smoothing: antialiased; 32 | -ms-font-smoothing: antialiased; 33 | -o-font-smoothing: antialiased; 34 | font-smoothing: antialiased; 35 | } 36 | 37 | #todoapp { 38 | background: #fff; 39 | background: rgba(255, 255, 255, 0.9); 40 | margin: 130px 0 40px 0; 41 | border: 1px solid #ccc; 42 | position: relative; 43 | border-top-left-radius: 2px; 44 | border-top-right-radius: 2px; 45 | box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2), 46 | 0 25px 50px 0 rgba(0, 0, 0, 0.15); 47 | } 48 | 49 | #todoapp:before { 50 | content: ''; 51 | border-left: 1px solid #f5d6d6; 52 | border-right: 1px solid #f5d6d6; 53 | width: 2px; 54 | position: absolute; 55 | top: 0; 56 | left: 40px; 57 | height: 100%; 58 | } 59 | 60 | #todoapp input::-webkit-input-placeholder { 61 | font-style: italic; 62 | } 63 | 64 | #todoapp input:-moz-placeholder { 65 | font-style: italic; 66 | color: #a9a9a9; 67 | } 68 | 69 | #todoapp h1 { 70 | position: absolute; 71 | top: -120px; 72 | width: 100%; 73 | font-size: 70px; 74 | font-weight: bold; 75 | text-align: center; 76 | color: #b3b3b3; 77 | color: rgba(255, 255, 255, 0.3); 78 | text-shadow: -1px -1px rgba(0, 0, 0, 0.2); 79 | -webkit-text-rendering: optimizeLegibility; 80 | -moz-text-rendering: optimizeLegibility; 81 | -ms-text-rendering: optimizeLegibility; 82 | -o-text-rendering: optimizeLegibility; 83 | text-rendering: optimizeLegibility; 84 | } 85 | 86 | #header { 87 | padding-top: 15px; 88 | border-radius: inherit; 89 | } 90 | 91 | #header:before { 92 | content: ''; 93 | position: absolute; 94 | top: 0; 95 | right: 0; 96 | left: 0; 97 | height: 15px; 98 | z-index: 2; 99 | border-bottom: 1px solid #6c615c; 100 | background: #8d7d77; 101 | background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8))); 102 | background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 103 | background: -moz-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 104 | background: -o-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 105 | background: -ms-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 106 | background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 107 | filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670'); 108 | border-top-left-radius: 1px; 109 | border-top-right-radius: 1px; 110 | } 111 | 112 | #new-todo, 113 | .edit { 114 | position: relative; 115 | margin: 0; 116 | width: 100%; 117 | font-size: 24px; 118 | font-family: inherit; 119 | line-height: 1.4em; 120 | border: 0; 121 | outline: none; 122 | color: inherit; 123 | padding: 6px; 124 | border: 1px solid #999; 125 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 126 | -webkit-box-sizing: border-box; 127 | -moz-box-sizing: border-box; 128 | -ms-box-sizing: border-box; 129 | -o-box-sizing: border-box; 130 | box-sizing: border-box; 131 | -webkit-font-smoothing: antialiased; 132 | -moz-font-smoothing: antialiased; 133 | -ms-font-smoothing: antialiased; 134 | -o-font-smoothing: antialiased; 135 | font-smoothing: antialiased; 136 | } 137 | 138 | #new-todo { 139 | padding: 16px 16px 16px 60px; 140 | border: none; 141 | background: rgba(0, 0, 0, 0.02); 142 | z-index: 2; 143 | box-shadow: none; 144 | } 145 | 146 | #main { 147 | position: relative; 148 | z-index: 2; 149 | border-top: 1px dotted #adadad; 150 | } 151 | 152 | label[for='toggle-all'] { 153 | display: none; 154 | } 155 | 156 | #toggle-all { 157 | position: absolute; 158 | top: -42px; 159 | left: -4px; 160 | width: 40px; 161 | text-align: center; 162 | border: none; /* Mobile Safari */ 163 | } 164 | 165 | #toggle-all:before { 166 | content: '»'; 167 | font-size: 28px; 168 | color: #d9d9d9; 169 | padding: 0 25px 7px; 170 | } 171 | 172 | #toggle-all:checked:before { 173 | color: #737373; 174 | } 175 | 176 | #todo-list { 177 | margin: 0; 178 | padding: 0; 179 | list-style: none; 180 | } 181 | 182 | #todo-list li { 183 | position: relative; 184 | font-size: 24px; 185 | border-bottom: 1px dotted #ccc; 186 | } 187 | 188 | #todo-list li:last-child { 189 | border-bottom: none; 190 | } 191 | 192 | #todo-list li.editing { 193 | border-bottom: none; 194 | padding: 0; 195 | } 196 | 197 | #todo-list li.editing .edit { 198 | display: block; 199 | width: 506px; 200 | padding: 13px 17px 12px 17px; 201 | margin: 0 0 0 43px; 202 | } 203 | 204 | #todo-list li.editing .view { 205 | display: none; 206 | } 207 | 208 | #todo-list li .toggle { 209 | text-align: center; 210 | width: 40px; 211 | /* auto, since non-WebKit browsers doesn't support input styling */ 212 | height: auto; 213 | position: absolute; 214 | top: 0; 215 | bottom: 0; 216 | margin: auto 0; 217 | border: none; /* Mobile Safari */ 218 | -webkit-appearance: none; 219 | /*-moz-appearance: none;*/ 220 | -ms-appearance: none; 221 | -o-appearance: none; 222 | appearance: none; 223 | } 224 | 225 | #todo-list li .toggle:after { 226 | content: '✔'; 227 | line-height: 43px; /* 40 + a couple of pixels visual adjustment */ 228 | font-size: 20px; 229 | color: #d9d9d9; 230 | text-shadow: 0 -1px 0 #bfbfbf; 231 | } 232 | 233 | #todo-list li .toggle:checked:after { 234 | color: #85ada7; 235 | text-shadow: 0 1px 0 #669991; 236 | bottom: 1px; 237 | position: relative; 238 | } 239 | 240 | #todo-list li label { 241 | word-break: break-word; 242 | padding: 15px; 243 | margin-left: 45px; 244 | display: block; 245 | line-height: 1.2; 246 | -webkit-transition: color 0.4s; 247 | -moz-transition: color 0.4s; 248 | -ms-transition: color 0.4s; 249 | -o-transition: color 0.4s; 250 | transition: color 0.4s; 251 | } 252 | 253 | #todo-list li.completed label { 254 | color: #a9a9a9; 255 | text-decoration: line-through; 256 | } 257 | 258 | #todo-list li .destroy { 259 | display: none; 260 | position: absolute; 261 | top: 0; 262 | right: 10px; 263 | bottom: 0; 264 | width: 40px; 265 | height: 40px; 266 | margin: auto 0; 267 | font-size: 22px; 268 | color: #a88a8a; 269 | -webkit-transition: all 0.2s; 270 | -moz-transition: all 0.2s; 271 | -ms-transition: all 0.2s; 272 | -o-transition: all 0.2s; 273 | transition: all 0.2s; 274 | } 275 | 276 | #todo-list li .destroy:hover { 277 | text-shadow: 0 0 1px #000, 278 | 0 0 10px rgba(199, 107, 107, 0.8); 279 | -webkit-transform: scale(1.3); 280 | -moz-transform: scale(1.3); 281 | -ms-transform: scale(1.3); 282 | -o-transform: scale(1.3); 283 | transform: scale(1.3); 284 | } 285 | 286 | #todo-list li .destroy:after { 287 | content: '✖'; 288 | } 289 | 290 | #todo-list li:hover .destroy { 291 | display: block; 292 | } 293 | 294 | #todo-list li .edit { 295 | display: none; 296 | } 297 | 298 | #todo-list li.editing:last-child { 299 | margin-bottom: -1px; 300 | } 301 | 302 | #footer { 303 | color: #777; 304 | padding: 0 15px; 305 | position: absolute; 306 | right: 0; 307 | bottom: -31px; 308 | left: 0; 309 | height: 20px; 310 | z-index: 1; 311 | text-align: center; 312 | } 313 | 314 | #footer:before { 315 | content: ''; 316 | position: absolute; 317 | right: 0; 318 | bottom: 31px; 319 | left: 0; 320 | height: 50px; 321 | z-index: -1; 322 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), 323 | 0 6px 0 -3px rgba(255, 255, 255, 0.8), 324 | 0 7px 1px -3px rgba(0, 0, 0, 0.3), 325 | 0 43px 0 -6px rgba(255, 255, 255, 0.8), 326 | 0 44px 2px -6px rgba(0, 0, 0, 0.2); 327 | } 328 | 329 | #todo-count { 330 | float: left; 331 | text-align: left; 332 | } 333 | 334 | #filters { 335 | margin: 0; 336 | padding: 0; 337 | list-style: none; 338 | position: absolute; 339 | right: 0; 340 | left: 0; 341 | } 342 | 343 | #filters li { 344 | display: inline; 345 | } 346 | 347 | #filters li a { 348 | color: #83756f; 349 | margin: 2px; 350 | text-decoration: none; 351 | } 352 | 353 | #filters li a.selected { 354 | font-weight: bold; 355 | } 356 | 357 | #clear-completed { 358 | float: right; 359 | position: relative; 360 | line-height: 20px; 361 | text-decoration: none; 362 | background: rgba(0, 0, 0, 0.1); 363 | font-size: 11px; 364 | padding: 0 10px; 365 | border-radius: 3px; 366 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2); 367 | } 368 | 369 | #clear-completed:hover { 370 | background: rgba(0, 0, 0, 0.15); 371 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3); 372 | } 373 | 374 | #info { 375 | margin: 65px auto 0; 376 | color: #a6a6a6; 377 | font-size: 12px; 378 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); 379 | text-align: center; 380 | } 381 | 382 | #info a { 383 | color: inherit; 384 | } 385 | 386 | /* 387 | Hack to remove background from Mobile Safari. 388 | Can't use it globally since it destroys checkboxes in Firefox and Opera 389 | */ 390 | @media screen and (-webkit-min-device-pixel-ratio:0) { 391 | #toggle-all, 392 | #todo-list li .toggle { 393 | background: none; 394 | } 395 | 396 | #todo-list li .toggle { 397 | height: 40px; 398 | } 399 | 400 | #toggle-all { 401 | top: -56px; 402 | left: -15px; 403 | width: 65px; 404 | height: 41px; 405 | -webkit-transform: rotate(90deg); 406 | transform: rotate(90deg); 407 | -webkit-appearance: none; 408 | appearance: none; 409 | } 410 | } 411 | 412 | .hidden{ 413 | display:none; 414 | } 415 | -------------------------------------------------------------------------------- /script/kotlinEcma3.js: -------------------------------------------------------------------------------- 1 | var Kotlin = {}; 2 | (function() { 3 | function e(c, d) { 4 | for(var a in d) { 5 | d.hasOwnProperty(a) && (c[a] = d[a]) 6 | } 7 | } 8 | var g = function() { 9 | }; 10 | Function.prototype.bind || (Function.prototype.bind = function(c) { 11 | if("function" !== typeof this) { 12 | throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); 13 | } 14 | var d = Array.prototype.slice.call(arguments, 1), a = this, b = function() { 15 | }, f = function() { 16 | return a.apply(this instanceof b && c ? this : c, d.concat(Array.prototype.slice.call(arguments))) 17 | }; 18 | b.prototype = this.prototype; 19 | f.prototype = new b; 20 | return f 21 | }); 22 | Kotlin.keys = Object.keys || function(c) { 23 | var d = [], a = 0, b; 24 | for(b in c) { 25 | c.hasOwnProperty(b) && (d[a++] = b) 26 | } 27 | return d 28 | }; 29 | Kotlin.argumentsToArrayLike = function(c) { 30 | for(var d = c.length, a = Array(d);d--;) { 31 | a[d] = c[d] 32 | } 33 | return a 34 | }; 35 | Kotlin.isType = function(c, d) { 36 | if(c === null || c === void 0) { 37 | return false 38 | } 39 | for(var a = c.get_class();a !== d;) { 40 | if(a === null) { 41 | return false 42 | } 43 | a = a.superclass 44 | } 45 | return true 46 | }; 47 | Kotlin.createTrait = function() { 48 | return function() { 49 | for(var c = arguments[0], d = 1, a = arguments.length;d < a;d++) { 50 | e(c, arguments[d]) 51 | } 52 | return c 53 | } 54 | }(); 55 | Kotlin.definePackage = function(c) { 56 | return c === null ? {} : c 57 | }; 58 | Kotlin.createClass = function() { 59 | function c() { 60 | } 61 | function d(a) { 62 | e(this.prototype, a); 63 | return this 64 | } 65 | return function() { 66 | function a() { 67 | this.initializing = a; 68 | this.initialize && this.initialize.apply(this, arguments) 69 | } 70 | var b = null, f = Kotlin.argumentsToArrayLike(arguments); 71 | typeof f[0] == "function" && (b = f.shift()); 72 | a.addMethods = d; 73 | a.superclass = b; 74 | a.subclasses = []; 75 | if(b) { 76 | c.prototype = b.prototype; 77 | a.prototype = new c; 78 | b.subclasses.push(a) 79 | } 80 | a.addMethods({get_class:function() { 81 | return a 82 | }}); 83 | b !== null && a.addMethods({super_init:function() { 84 | this.initializing = this.initializing.superclass; 85 | this.initializing.prototype.initialize.apply(this, arguments) 86 | }}); 87 | for(var b = 0, h = f.length;b < h;b++) { 88 | a.addMethods(f[b]) 89 | } 90 | if(!a.prototype.initialize) { 91 | a.prototype.initialize = g 92 | } 93 | return a.prototype.constructor = a 94 | } 95 | }(); 96 | Kotlin.$createClass = Kotlin.createClass; 97 | Kotlin.createObjectWithPrototype = function(c) { 98 | function d() { 99 | } 100 | d.prototype = c; 101 | return new d 102 | }; 103 | Kotlin.$new = function(c) { 104 | var d = Kotlin.createObjectWithPrototype(c.prototype); 105 | return function() { 106 | c.apply(d, arguments); 107 | return d 108 | } 109 | }; 110 | Kotlin.createObject = function() { 111 | return new (Kotlin.createClass.apply(null, arguments)) 112 | }; 113 | Kotlin.defineModule = function(c, d) { 114 | if(c in Kotlin.modules) { 115 | throw Kotlin.$new(Kotlin.IllegalArgumentException)(); 116 | } 117 | Kotlin.modules[c] = d 118 | } 119 | })(); 120 | String.prototype.startsWith = function(e) { 121 | return 0 === this.indexOf(e) 122 | }; 123 | String.prototype.endsWith = function(e) { 124 | return-1 !== this.indexOf(e, this.length - e.length) 125 | }; 126 | String.prototype.contains = function(e) { 127 | return-1 !== this.indexOf(e) 128 | }; 129 | var kotlin = {set:function(e, g, c) { 130 | return e.put(g, c) 131 | }}; 132 | (function() { 133 | function e(a) { 134 | return function() { 135 | throw new TypeError(void 0 !== a ? "Function " + a + " is abstract" : "Function is abstract"); 136 | } 137 | } 138 | Kotlin.equals = function(a, b) { 139 | if(null === a || void 0 === a) { 140 | return null === b 141 | } 142 | if(a instanceof Array) { 143 | if(!(b instanceof Array) || a.length != b.length) { 144 | return!1 145 | } 146 | for(var f = 0;f < a.length;f++) { 147 | if(!Kotlin.equals(a[f], b[f])) { 148 | return!1 149 | } 150 | } 151 | return!0 152 | } 153 | return"object" == typeof a && void 0 !== a.equals ? a.equals(b) : a === b 154 | }; 155 | Kotlin.array = function(a) { 156 | return null === a || void 0 === a ? [] : a.slice() 157 | }; 158 | Kotlin.intUpto = function(a, b) { 159 | return Kotlin.$new(Kotlin.NumberRange)(a, b) 160 | }; 161 | Kotlin.intDownto = function(a, b) { 162 | return Kotlin.$new(Kotlin.Progression)(a, b, -1) 163 | }; 164 | Kotlin.modules = {}; 165 | Kotlin.Exception = Kotlin.$createClass(); 166 | Kotlin.RuntimeException = Kotlin.$createClass(Kotlin.Exception); 167 | Kotlin.NullPointerException = Kotlin.$createClass(Kotlin.Exception); 168 | Kotlin.NoSuchElementException = Kotlin.$createClass(Kotlin.Exception); 169 | Kotlin.IllegalArgumentException = Kotlin.$createClass(Kotlin.Exception); 170 | Kotlin.IllegalStateException = Kotlin.$createClass(Kotlin.Exception); 171 | Kotlin.IndexOutOfBoundsException = Kotlin.$createClass(Kotlin.Exception); 172 | Kotlin.UnsupportedOperationException = Kotlin.$createClass(Kotlin.Exception); 173 | Kotlin.IOException = Kotlin.$createClass(Kotlin.Exception); 174 | Kotlin.throwNPE = function() { 175 | throw Kotlin.$new(Kotlin.NullPointerException)(); 176 | }; 177 | Kotlin.Iterator = Kotlin.$createClass({initialize:function() { 178 | }, next:e("Iterator#next"), hasNext:e("Iterator#hasNext")}); 179 | var g = Kotlin.$createClass(Kotlin.Iterator, {initialize:function(a) { 180 | this.array = a; 181 | this.size = a.length; 182 | this.index = 0 183 | }, next:function() { 184 | return this.array[this.index++] 185 | }, get_hasNext:function() { 186 | return this.index < this.size 187 | }, hasNext:function() { 188 | return this.index < this.size 189 | }}), c = Kotlin.$createClass(g, {initialize:function(a) { 190 | this.list = a; 191 | this.size = a.size(); 192 | this.index = 0 193 | }, next:function() { 194 | return this.list.get(this.index++) 195 | }, get_hasNext:function() { 196 | return this.index < this.size 197 | }}); 198 | Kotlin.Collection = Kotlin.$createClass(); 199 | Kotlin.AbstractList = Kotlin.$createClass(Kotlin.Collection, {iterator:function() { 200 | return Kotlin.$new(c)(this) 201 | }, isEmpty:function() { 202 | return 0 === this.size() 203 | }, addAll:function(a) { 204 | for(var a = a.iterator(), b = this.$size;0 < b--;) { 205 | this.add(a.next()) 206 | } 207 | }, remove:function(a) { 208 | a = this.indexOf(a); 209 | -1 !== a && this.removeAt(a) 210 | }, contains:function(a) { 211 | return-1 !== this.indexOf(a) 212 | }, equals:function(a) { 213 | if(this.$size === a.$size) { 214 | for(var b = this.iterator(), a = a.iterator(), f = this.$size;0 < f--;) { 215 | if(!Kotlin.equals(b.next(), a.next())) { 216 | return!1 217 | } 218 | } 219 | } 220 | return!0 221 | }, toString:function() { 222 | for(var a = "[", b = this.iterator(), f = !0, c = this.$size;0 < c--;) { 223 | f ? f = !1 : a += ", ", a += b.next() 224 | } 225 | return a + "]" 226 | }, toJSON:function() { 227 | return this.toArray() 228 | }}); 229 | Kotlin.ArrayList = Kotlin.$createClass(Kotlin.AbstractList, {initialize:function() { 230 | this.array = []; 231 | this.$size = 0 232 | }, get:function(a) { 233 | this.checkRange(a); 234 | return this.array[a] 235 | }, set:function(a, b) { 236 | this.checkRange(a); 237 | this.array[a] = b 238 | }, toArray:function() { 239 | return this.array.slice(0, this.$size) 240 | }, size:function() { 241 | return this.$size 242 | }, iterator:function() { 243 | return Kotlin.arrayIterator(this.array) 244 | }, add:function(a) { 245 | this.array[this.$size++] = a 246 | }, addAt:function(a, b) { 247 | this.array.splice(a, 0, b); 248 | this.$size++ 249 | }, addAll:function(a) { 250 | for(var b = a.iterator(), f = this.$size, c = a.size();0 < c--;) { 251 | this.array[f++] = b.next() 252 | } 253 | this.$size += a.size() 254 | }, removeAt:function(a) { 255 | this.checkRange(a); 256 | this.$size--; 257 | return this.array.splice(a, 1)[0] 258 | }, clear:function() { 259 | this.$size = this.array.length = 0 260 | }, indexOf:function(a) { 261 | for(var b = 0, f = this.$size;b < f;++b) { 262 | if(Kotlin.equals(this.array[b], a)) { 263 | return b 264 | } 265 | } 266 | return-1 267 | }, toString:function() { 268 | return"[" + this.array.join(", ") + "]" 269 | }, toJSON:function() { 270 | return this.array 271 | }, checkRange:function(a) { 272 | if(0 > a || a >= this.$size) { 273 | throw new Kotlin.IndexOutOfBoundsException; 274 | } 275 | }}); 276 | Kotlin.Runnable = Kotlin.$createClass({initialize:function() { 277 | }, run:e("Runnable#run")}); 278 | Kotlin.Comparable = Kotlin.$createClass({initialize:function() { 279 | }, compareTo:e("Comparable#compareTo")}); 280 | Kotlin.Appendable = Kotlin.$createClass({initialize:function() { 281 | }, append:e("Appendable#append")}); 282 | Kotlin.Closeable = Kotlin.$createClass({initialize:function() { 283 | }, close:e("Closeable#close")}); 284 | Kotlin.parseInt = function(a) { 285 | return parseInt(a, 10) 286 | }; 287 | Kotlin.safeParseInt = function(a) { 288 | a = parseInt(a, 10); 289 | return isNaN(a) ? null : a 290 | }; 291 | Kotlin.safeParseDouble = function(a) { 292 | a = parseFloat(a); 293 | return isNaN(a) ? null : a 294 | }; 295 | Kotlin.System = function() { 296 | var a = "", b = function(b) { 297 | void 0 !== b && (a = null === b || "object" !== typeof b ? a + b : a + b.toString()) 298 | }, f = function(b) { 299 | this.print(b); 300 | a += "\n" 301 | }; 302 | return{out:function() { 303 | return{print:b, println:f} 304 | }, output:function() { 305 | return a 306 | }, flush:function() { 307 | a = "" 308 | }} 309 | }(); 310 | Kotlin.println = function(a) { 311 | Kotlin.System.out().println(a) 312 | }; 313 | Kotlin.print = function(a) { 314 | Kotlin.System.out().print(a) 315 | }; 316 | Kotlin.RangeIterator = Kotlin.$createClass(Kotlin.Iterator, {initialize:function(a, b, f) { 317 | this.$start = a; 318 | this.$end = b; 319 | this.$increment = f; 320 | this.$i = a 321 | }, get_start:function() { 322 | return this.$start 323 | }, get_end:function() { 324 | return this.$end 325 | }, get_i:function() { 326 | return this.$i 327 | }, set_i:function(a) { 328 | this.$i = a 329 | }, next:function() { 330 | var a = this.$i; 331 | this.set_i(this.$i + this.$increment); 332 | return a 333 | }, get_hasNext:function() { 334 | return 0 < this.$increment ? this.$next <= this.$end : this.$next >= this.$end 335 | }}); 336 | Kotlin.NumberRange = Kotlin.$createClass({initialize:function(a, b) { 337 | this.$start = a; 338 | this.$end = b 339 | }, get_start:function() { 340 | return this.$start 341 | }, get_end:function() { 342 | return this.$end 343 | }, get_increment:function() { 344 | return 1 345 | }, contains:function(a) { 346 | return this.$start <= a && a <= this.$end 347 | }, iterator:function() { 348 | return Kotlin.$new(Kotlin.RangeIterator)(this.get_start(), this.get_end()) 349 | }}); 350 | Kotlin.Progression = Kotlin.$createClass({initialize:function(a, b, f) { 351 | this.$start = a; 352 | this.$end = b; 353 | this.$increment = f 354 | }, get_start:function() { 355 | return this.$start 356 | }, get_end:function() { 357 | return this.$end 358 | }, get_increment:function() { 359 | return this.$increment 360 | }, iterator:function() { 361 | return Kotlin.$new(Kotlin.RangeIterator)(this.get_start(), this.get_end(), this.get_increment()) 362 | }}); 363 | Kotlin.Comparator = Kotlin.$createClass({initialize:function() { 364 | }, compare:e("Comparator#compare")}); 365 | var d = Kotlin.$createClass(Kotlin.Comparator, {initialize:function(a) { 366 | this.compare = a 367 | }}); 368 | Kotlin.comparator = function(a) { 369 | return Kotlin.$new(d)(a) 370 | }; 371 | Kotlin.collectionsMax = function(a, b) { 372 | var f = a.iterator(); 373 | if(a.isEmpty()) { 374 | throw Kotlin.Exception(); 375 | } 376 | for(var c = f.next();f.get_hasNext();) { 377 | var d = f.next(); 378 | 0 > b.compare(c, d) && (c = d) 379 | } 380 | return c 381 | }; 382 | Kotlin.collectionsSort = function(a, b) { 383 | var c = void 0; 384 | void 0 !== b && (c = b.compare.bind(b)); 385 | a instanceof Array && a.sort(c); 386 | for(var d = [], e = a.iterator();e.hasNext();) { 387 | d.push(e.next()) 388 | } 389 | d.sort(c); 390 | c = 0; 391 | for(e = d.length;c < e;c++) { 392 | a.set(c, d[c]) 393 | } 394 | }; 395 | Kotlin.StringBuilder = Kotlin.$createClass({initialize:function() { 396 | this.string = "" 397 | }, append:function(a) { 398 | this.string += a.toString() 399 | }, toString:function() { 400 | return this.string 401 | }}); 402 | Kotlin.splitString = function(a, b) { 403 | return a.split(b) 404 | }; 405 | Kotlin.nullArray = function(a) { 406 | for(var b = [];0 < a;) { 407 | b[--a] = null 408 | } 409 | return b 410 | }; 411 | Kotlin.numberArrayOfSize = function(a) { 412 | return Kotlin.arrayFromFun(a, function() { 413 | return 0 414 | }) 415 | }; 416 | Kotlin.charArrayOfSize = function(a) { 417 | return Kotlin.arrayFromFun(a, function() { 418 | return"\x00" 419 | }) 420 | }; 421 | Kotlin.booleanArrayOfSize = function(a) { 422 | return Kotlin.arrayFromFun(a, function() { 423 | return!1 424 | }) 425 | }; 426 | Kotlin.arrayFromFun = function(a, b) { 427 | for(var c = Array(a), d = 0;d < a;d++) { 428 | c[d] = b(d) 429 | } 430 | return c 431 | }; 432 | Kotlin.arrayIndices = function(a) { 433 | return Kotlin.$new(Kotlin.NumberRange)(0, a.length - 1) 434 | }; 435 | Kotlin.arrayIterator = function(a) { 436 | return Kotlin.$new(g)(a) 437 | }; 438 | Kotlin.toString = function(a) { 439 | return a.toString() 440 | }; 441 | Kotlin.jsonFromPairs = function(a) { 442 | for(var b = a.length, c = {};0 < b;) { 443 | --b, c[a[b][0]] = a[b][1] 444 | } 445 | return c 446 | }; 447 | Kotlin.jsonSet = function(a, b, c) { 448 | a[b] = c 449 | }; 450 | Kotlin.jsonGet = function(a, b) { 451 | return a[b] 452 | }; 453 | Kotlin.jsonAddProperties = function(a, b) { 454 | for(var c in b) { 455 | b.hasOwnProperty(c) && (a[c] = b[c]) 456 | } 457 | return a 458 | }; 459 | Kotlin.sure = function(a) { 460 | return a 461 | }; 462 | Kotlin.concat = function(a, b) { 463 | for(var c = Array(a.length + b.length), d = 0, e = a.length;d < e;d++) { 464 | c[d] = a[d] 465 | } 466 | for(var e = b.length, g = 0;g < e;) { 467 | c[d++] = b[g++] 468 | } 469 | return c 470 | } 471 | })(); 472 | Kotlin.assignOwner = function(e, g) { 473 | e.o = g; 474 | return e 475 | }; 476 | Kotlin.b0 = function(e, g, c) { 477 | return function() { 478 | return e.call(null !== g ? g : this, c) 479 | } 480 | }; 481 | Kotlin.b1 = function(e, g, c) { 482 | return function() { 483 | return e.apply(null !== g ? g : this, c) 484 | } 485 | }; 486 | Kotlin.b2 = function(e, g, c) { 487 | return function() { 488 | return e.apply(null !== g ? g : this, Kotlin.concat(c, arguments)) 489 | } 490 | }; 491 | Kotlin.b3 = function(e, g) { 492 | return function() { 493 | return e.call(g) 494 | } 495 | }; 496 | Kotlin.b4 = function(e, g) { 497 | return function() { 498 | return e.apply(g, Kotlin.argumentsToArrayLike(arguments)) 499 | } 500 | }; 501 | (function() { 502 | function e(a) { 503 | return"string" == typeof a ? a : typeof a.hashCode == i ? (a = a.hashCode(), "string" == typeof a ? a : e(a)) : typeof a.toString == i ? a.toString() : "" + a 504 | } 505 | function g(a, b) { 506 | return a.equals(b) 507 | } 508 | function c(a, b) { 509 | return typeof b.equals == i ? b.equals(a) : a === b 510 | } 511 | function d(a) { 512 | return function(b) { 513 | if(null === b) { 514 | throw Error("null is not a valid " + a); 515 | } 516 | if("undefined" == typeof b) { 517 | throw Error(a + " must not be undefined"); 518 | } 519 | } 520 | } 521 | function a(a, b, c, d) { 522 | this[0] = a; 523 | this.entries = []; 524 | this.addEntry(b, c); 525 | null !== d && (this.getEqualityFunction = function() { 526 | return d 527 | }) 528 | } 529 | function b(a) { 530 | return function(b) { 531 | for(var c = this.entries.length, d, e = this.getEqualityFunction(b);c--;) { 532 | if(d = this.entries[c], e(b, d[0])) { 533 | switch(a) { 534 | case j: 535 | return!0; 536 | case o: 537 | return d; 538 | case p: 539 | return[c, d[1]] 540 | } 541 | } 542 | } 543 | return!1 544 | } 545 | } 546 | function f(a) { 547 | return function(b) { 548 | for(var c = b.length, d = 0, e = this.entries.length;d < e;++d) { 549 | b[c + d] = this.entries[d][a] 550 | } 551 | } 552 | } 553 | function h(b, c) { 554 | var d = b[c]; 555 | return d && d instanceof a ? d : null 556 | } 557 | var i = "function", n = typeof Array.prototype.splice == i ? function(a, b) { 558 | a.splice(b, 1) 559 | } : function(a, b) { 560 | var c, d, e; 561 | if(b === a.length - 1) { 562 | a.length = b 563 | }else { 564 | c = a.slice(b + 1); 565 | a.length = b; 566 | d = 0; 567 | for(e = c.length;d < e;++d) { 568 | a[b + d] = c[d] 569 | } 570 | } 571 | }, k = d("key"), q = d("value"), j = 0, o = 1, p = 2; 572 | a.prototype = {getEqualityFunction:function(a) { 573 | return typeof a.equals == i ? g : c 574 | }, getEntryForKey:b(o), getEntryAndIndexForKey:b(p), removeEntryForKey:function(a) { 575 | return(a = this.getEntryAndIndexForKey(a)) ? (n(this.entries, a[0]), a[1]) : null 576 | }, addEntry:function(a, b) { 577 | this.entries[this.entries.length] = [a, b] 578 | }, keys:f(0), values:f(1), getEntries:function(a) { 579 | for(var b = a.length, c = 0, d = this.entries.length;c < d;++c) { 580 | a[b + c] = this.entries[c].slice(0) 581 | } 582 | }, containsKey:b(j), containsValue:function(a) { 583 | for(var b = this.entries.length;b--;) { 584 | if(a === this.entries[b][1]) { 585 | return!0 586 | } 587 | } 588 | return!1 589 | }}; 590 | var r = function(b, c) { 591 | var d = this, f = [], g = {}, l = typeof b == i ? b : e, j = typeof c == i ? c : null; 592 | this.put = function(b, c) { 593 | k(b); 594 | q(c); 595 | var d = l(b), e, i = null; 596 | (e = h(g, d)) ? (d = e.getEntryForKey(b)) ? (i = d[1], d[1] = c) : e.addEntry(b, c) : (e = new a(d, b, c, j), f[f.length] = e, g[d] = e); 597 | return i 598 | }; 599 | this.get = function(a) { 600 | k(a); 601 | var b = l(a); 602 | if(b = h(g, b)) { 603 | if(a = b.getEntryForKey(a)) { 604 | return a[1] 605 | } 606 | } 607 | return null 608 | }; 609 | this.containsKey = function(a) { 610 | k(a); 611 | var b = l(a); 612 | return(b = h(g, b)) ? b.containsKey(a) : !1 613 | }; 614 | this.containsValue = function(a) { 615 | q(a); 616 | for(var b = f.length;b--;) { 617 | if(f[b].containsValue(a)) { 618 | return!0 619 | } 620 | } 621 | return!1 622 | }; 623 | this.clear = function() { 624 | f.length = 0; 625 | g = {} 626 | }; 627 | this.isEmpty = function() { 628 | return!f.length 629 | }; 630 | var m = function(a) { 631 | return function() { 632 | for(var b = [], c = f.length;c--;) { 633 | f[c][a](b) 634 | } 635 | return b 636 | } 637 | }; 638 | this._keys = m("keys"); 639 | this._values = m("values"); 640 | this._entries = m("getEntries"); 641 | this.values = function() { 642 | for(var a = this._values(), b = a.length, c = Kotlin.$new(Kotlin.ArrayList)();b--;) { 643 | c.add(a[b]) 644 | } 645 | return c 646 | }; 647 | this.remove = function(a) { 648 | k(a); 649 | var b = l(a), c = null, d = h(g, b); 650 | if(d && (c = d.removeEntryForKey(a), null !== c && !d.entries.length)) { 651 | a: { 652 | for(a = f.length;a--;) { 653 | if(d = f[a], b === d[0]) { 654 | break a 655 | } 656 | } 657 | a = null 658 | } 659 | n(f, a); 660 | delete g[b] 661 | } 662 | return c 663 | }; 664 | this.size = function() { 665 | for(var a = 0, b = f.length;b--;) { 666 | a += f[b].entries.length 667 | } 668 | return a 669 | }; 670 | this.each = function(a) { 671 | for(var b = d._entries(), c = b.length, e;c--;) { 672 | e = b[c], a(e[0], e[1]) 673 | } 674 | }; 675 | this.putAll = function(a, b) { 676 | for(var c = a._entries(), e, f, g, h = c.length, j = typeof b == i;h--;) { 677 | e = c[h]; 678 | f = e[0]; 679 | e = e[1]; 680 | if(j && (g = d.get(f))) { 681 | e = b(f, g, e) 682 | } 683 | d.put(f, e) 684 | } 685 | }; 686 | this.clone = function() { 687 | var a = new r(b, c); 688 | a.putAll(d); 689 | return a 690 | }; 691 | this.keySet = function() { 692 | for(var a = Kotlin.$new(Kotlin.HashSet)(), b = this._keys(), c = b.length;c--;) { 693 | a.add(b[c]) 694 | } 695 | return a 696 | } 697 | }; 698 | Kotlin.HashTable = r 699 | })(); 700 | Kotlin.Map = Kotlin.$createClass(); 701 | Kotlin.HashMap = Kotlin.$createClass(Kotlin.Map, {initialize:function() { 702 | Kotlin.HashTable.call(this) 703 | }}); 704 | Kotlin.ComplexHashMap = Kotlin.HashMap; 705 | (function() { 706 | var e = Kotlin.$createClass(Kotlin.Iterator, {initialize:function(c, d) { 707 | this.map = c; 708 | this.keys = d; 709 | this.size = d.length; 710 | this.index = 0 711 | }, next:function() { 712 | return this.map[this.keys[this.index++]] 713 | }, get_hasNext:function() { 714 | return this.index < this.size 715 | }}), g = Kotlin.$createClass(Kotlin.Collection, {initialize:function(c) { 716 | this.map = c 717 | }, iterator:function() { 718 | return Kotlin.$new(e)(this.map.map, Kotlin.keys(this.map.map)) 719 | }, isEmpty:function() { 720 | return 0 === this.map.$size 721 | }, contains:function(c) { 722 | return this.map.containsValue(c) 723 | }}); 724 | Kotlin.PrimitiveHashMap = Kotlin.$createClass(Kotlin.Map, {initialize:function() { 725 | this.$size = 0; 726 | this.map = {} 727 | }, size:function() { 728 | return this.$size 729 | }, isEmpty:function() { 730 | return 0 === this.$size 731 | }, containsKey:function(c) { 732 | return void 0 !== this.map[c] 733 | }, containsValue:function(c) { 734 | var d = this.map, a; 735 | for(a in d) { 736 | if(d.hasOwnProperty(a) && d[a] === c) { 737 | return!0 738 | } 739 | } 740 | return!1 741 | }, get:function(c) { 742 | return this.map[c] 743 | }, put:function(c, d) { 744 | var a = this.map[c]; 745 | this.map[c] = void 0 === d ? null : d; 746 | void 0 === a && this.$size++; 747 | return a 748 | }, remove:function(c) { 749 | var d = this.map[c]; 750 | void 0 !== d && (delete this.map[c], this.$size--); 751 | return d 752 | }, clear:function() { 753 | this.$size = 0; 754 | this.map = {} 755 | }, putAll:function() { 756 | throw Kotlin.$new(Kotlin.UnsupportedOperationException)(); 757 | }, keySet:function() { 758 | var c = Kotlin.$new(Kotlin.HashSet)(), d = this.map, a; 759 | for(a in d) { 760 | d.hasOwnProperty(a) && c.add(a) 761 | } 762 | return c 763 | }, values:function() { 764 | return Kotlin.$new(g)(this) 765 | }, toJSON:function() { 766 | return this.map 767 | }}) 768 | })(); 769 | (function() { 770 | function e(g, c) { 771 | var d = new Kotlin.HashTable(g, c); 772 | this.add = function(a) { 773 | d.put(a, !0) 774 | }; 775 | this.addAll = function(a) { 776 | for(var b = a.length;b--;) { 777 | d.put(a[b], !0) 778 | } 779 | }; 780 | this.values = function() { 781 | return d._keys() 782 | }; 783 | this.iterator = function() { 784 | return Kotlin.arrayIterator(this.values()) 785 | }; 786 | this.remove = function(a) { 787 | return d.remove(a) ? a : null 788 | }; 789 | this.contains = function(a) { 790 | return d.containsKey(a) 791 | }; 792 | this.clear = function() { 793 | d.clear() 794 | }; 795 | this.size = function() { 796 | return d.size() 797 | }; 798 | this.isEmpty = function() { 799 | return d.isEmpty() 800 | }; 801 | this.clone = function() { 802 | var a = new e(g, c); 803 | a.addAll(d.keys()); 804 | return a 805 | }; 806 | this.equals = function(a) { 807 | if(null === a || void 0 === a) { 808 | return!1 809 | } 810 | if(this.size() === a.size()) { 811 | for(var b = this.iterator(), a = a.iterator();;) { 812 | var c = b.get_hasNext(), d = a.get_hasNext(); 813 | if(c != d) { 814 | break 815 | } 816 | if(d) { 817 | if(c = b.next(), d = a.next(), !Kotlin.equals(c, d)) { 818 | break 819 | } 820 | }else { 821 | return!0 822 | } 823 | } 824 | } 825 | return!1 826 | }; 827 | this.toString = function() { 828 | for(var a = "[", b = this.iterator(), c = !0;b.get_hasNext();) { 829 | c ? c = !1 : a += ", ", a += b.next() 830 | } 831 | return a + "]" 832 | }; 833 | this.intersection = function(a) { 834 | for(var b = new e(g, c), a = a.values(), f = a.length, h;f--;) { 835 | h = a[f], d.containsKey(h) && b.add(h) 836 | } 837 | return b 838 | }; 839 | this.union = function(a) { 840 | for(var b = this.clone(), a = a.values(), c = a.length, e;c--;) { 841 | e = a[c], d.containsKey(e) || b.add(e) 842 | } 843 | return b 844 | }; 845 | this.isSubsetOf = function(a) { 846 | for(var b = d.keys(), c = b.length;c--;) { 847 | if(!a.contains(b[c])) { 848 | return!1 849 | } 850 | } 851 | return!0 852 | } 853 | } 854 | Kotlin.HashSet = Kotlin.$createClass({initialize:function() { 855 | e.call(this) 856 | }}) 857 | })(); 858 | 859 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 68 | 69 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 202 | 203 | 204 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 244 | 245 | 246 | 247 | 250 | 251 | 254 | 255 | 256 | 257 | 260 | 261 | 264 | 265 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 303 | 304 | 311 | 312 | 313 | 324 | 325 | 326 | 327 | 345 | 352 | 353 | 354 | 367 | 368 | 369 | 370 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 393 | 394 | 395 | 396 | 397 | localhost 398 | 5050 399 | 400 | 401 | 402 | 403 | 419 | 420 | 421 | 422 | 1368530192267 423 | 1368530192267 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 462 | 463 | 474 | 516 | 517 | 518 | 519 | 520 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 645 | 646 | 647 | 648 | 649 | 650 | No facets are configured 651 | 652 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 668 | 669 | 670 | 671 | 672 | 673 | 1.7 674 | 675 | 680 | 681 | 682 | 683 | 684 | 685 | kotlin-angularjs 686 | 687 | 692 | 693 | 694 | 695 | 696 | 697 | KotlinJavaScript 698 | 699 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | -------------------------------------------------------------------------------- /bower_components/angular-mocks/angular-mocks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.4 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 throw, 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 constroctor 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 is 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 | * *NOTE*: This is function is also published on window for easy access.
1691 | * *NOTE*: Only available with {@link http://pivotal.github.com/jasmine/ jasmine}. 1692 | * 1693 | * The inject function wraps a function into an injectable function. The inject() creates new 1694 | * instance of {@link AUTO.$injector $injector} per test, which is then used for 1695 | * resolving references. 1696 | * 1697 | * See also {@link angular.mock.module module} 1698 | * 1699 | * Example of what a typical jasmine tests looks like with the inject method. 1700 | *
1701 |    *
1702 |    *   angular.module('myApplicationModule', [])
1703 |    *       .value('mode', 'app')
1704 |    *       .value('version', 'v1.0.1');
1705 |    *
1706 |    *
1707 |    *   describe('MyApp', function() {
1708 |    *
1709 |    *     // You need to load modules that you want to test,
1710 |    *     // it loads only the "ng" module by default.
1711 |    *     beforeEach(module('myApplicationModule'));
1712 |    *
1713 |    *
1714 |    *     // inject() is used to inject arguments of all given functions
1715 |    *     it('should provide a version', inject(function(mode, version) {
1716 |    *       expect(version).toEqual('v1.0.1');
1717 |    *       expect(mode).toEqual('app');
1718 |    *     }));
1719 |    *
1720 |    *
1721 |    *     // The inject and module method can also be used inside of the it or beforeEach
1722 |    *     it('should override a version and test the new version is injected', function() {
1723 |    *       // module() takes functions or strings (module aliases)
1724 |    *       module(function($provide) {
1725 |    *         $provide.value('version', 'overridden'); // override version here
1726 |    *       });
1727 |    *
1728 |    *       inject(function(version) {
1729 |    *         expect(version).toEqual('overridden');
1730 |    *       });
1731 |    *     ));
1732 |    *   });
1733 |    *
1734 |    * 
1735 | * 1736 | * @param {...Function} fns any number of functions which will be injected using the injector. 1737 | */ 1738 | window.inject = angular.mock.inject = function() { 1739 | var blockFns = Array.prototype.slice.call(arguments, 0); 1740 | var errorForStack = new Error('Declaration Location'); 1741 | return isSpecRunning() ? workFn() : workFn; 1742 | ///////////////////// 1743 | function workFn() { 1744 | var spec = getCurrentSpec(); 1745 | var modules = spec.$modules || []; 1746 | modules.unshift('ngMock'); 1747 | modules.unshift('ng'); 1748 | var injector = spec.$injector; 1749 | if (!injector) { 1750 | injector = spec.$injector = angular.injector(modules); 1751 | } 1752 | for(var i = 0, ii = blockFns.length; i < ii; i++) { 1753 | try { 1754 | injector.invoke(blockFns[i] || angular.noop, this); 1755 | } catch (e) { 1756 | if(e.stack) e.stack += '\n' + errorForStack.stack; 1757 | throw e; 1758 | } finally { 1759 | errorForStack = null; 1760 | } 1761 | } 1762 | } 1763 | }; 1764 | })(window); 1765 | --------------------------------------------------------------------------------