├── .gitignore ├── version.json ├── tools ├── jsmake │ ├── js.jar │ ├── jsmake.cmd │ ├── jsmaked.cmd │ ├── bootstrap.js │ └── jsmake.js ├── jasmine │ ├── jasmine_favicon.png │ ├── MIT.LICENSE │ ├── jasmine.css │ ├── jasmine-html.js │ └── jasmine.js ├── uglifyjs │ ├── package.json │ ├── uglify-js.js │ └── lib │ │ ├── squeeze-more.js │ │ └── parse-js.js └── jsmake.javascript.JavascriptUtils.js ├── LICENSE ├── src ├── jsonrpc.Observable.js ├── jsonrpc.CallStack.js ├── jsonrpc.DelayedTask.js └── jsonrpc.JsonRpc.js ├── test ├── jsonrpc.ObservableTest.js ├── jsonrpc.CallStackTest.js ├── jsonrpc.DelayedTaskTest.js └── jsonrpc.JsonRpcTest.js ├── SpecRunner.html ├── README.markdown └── specrunner.js /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /.idea/ 3 | /*.iml -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | {"major":0,"minor":1,"patch":8} -------------------------------------------------------------------------------- /tools/jsmake/js.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gimmi/jsonrpcjs/HEAD/tools/jsmake/js.jar -------------------------------------------------------------------------------- /tools/jasmine/jasmine_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gimmi/jsonrpcjs/HEAD/tools/jasmine/jasmine_favicon.png -------------------------------------------------------------------------------- /tools/jsmake/jsmake.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | java -cp "%~dp0js.jar" org.mozilla.javascript.tools.shell.Main -modules "." "%~dp0bootstrap.js" "%~dp0jsmake.js" %* -------------------------------------------------------------------------------- /tools/jsmake/jsmaked.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | java -cp "%~dp0js.jar" org.mozilla.javascript.tools.debugger.Main -modules "." "%~dp0bootstrap.js" "%~dp0jsmake.js" %* 3 | -------------------------------------------------------------------------------- /tools/jsmake/bootstrap.js: -------------------------------------------------------------------------------- 1 | (function (global, args) { 2 | load(args.shift()); 3 | var main = new jsmake.Main(); 4 | main.init(global); 5 | load('build.js'); 6 | main.runTask(args.shift() || 'default', args); 7 | }(this, arguments)); 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012 Gian Marco Gherardi 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /src/jsonrpc.Observable.js: -------------------------------------------------------------------------------- 1 | jsonrpc.Observable = function () { 2 | this._listeners = []; 3 | }; 4 | 5 | jsonrpc.Observable.prototype = { 6 | bind: function (fn, scope) { 7 | var token = { fn: fn, scope: scope || this }; 8 | this._listeners.push(token); 9 | return token; 10 | }, 11 | 12 | unbind: function (token) { 13 | var idx = this._listeners.indexOf(token); 14 | if (idx !== -1) { 15 | this._listeners.splice(idx, 1); 16 | } 17 | }, 18 | 19 | trigger: function () { 20 | var i; 21 | for (i = 0; i < this._listeners.length; i += 1) { 22 | this._listeners[i].fn.apply(this._listeners[i].scope, arguments); 23 | } 24 | } 25 | }; -------------------------------------------------------------------------------- /test/jsonrpc.ObservableTest.js: -------------------------------------------------------------------------------- 1 | describe('jsonrpc.Observable', function () { 2 | var target; 3 | 4 | beforeEach(function () { 5 | target = new jsonrpc.Observable(); 6 | }); 7 | 8 | it('should trigger events', function () { 9 | var scope = {}, 10 | h1 = jasmine.createSpy(), 11 | s1 = {}, 12 | h2 = jasmine.createSpy(); 13 | 14 | target.bind(h1, s1); 15 | target.unbind(target.bind(h2)); 16 | 17 | target.trigger(1, 2, 3); 18 | 19 | expect(h1).toHaveBeenCalledWith(1, 2, 3); 20 | expect(h1.callCount).toEqual(1); 21 | expect(h1.mostRecentCall.object).toEqual(s1); 22 | expect(h2).not.toHaveBeenCalled(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/jsonrpc.CallStack.js: -------------------------------------------------------------------------------- 1 | jsonrpc.CallStack = function (enterFn, enterScope, exitFn, exitScope) { 2 | this._counter = 0; 3 | this._enterFn = enterFn; 4 | this._exitFn = exitFn; 5 | this._enterScope = enterScope; 6 | this._exitScope = exitScope; 7 | }; 8 | 9 | jsonrpc.CallStack.prototype = { 10 | enter: function () { 11 | this._counter = (this._counter < 0 ? 1 : this._counter + 1); 12 | if (this._counter === 1) { 13 | this._enterFn.apply(this._enterScope, arguments); 14 | } 15 | }, 16 | 17 | exit: function (fn) { 18 | this._counter -= 1; 19 | if (this._counter === 0) { 20 | this._exitFn.apply(this._exitScope, arguments); 21 | } 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /tools/uglifyjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "uglify-js", 3 | 4 | "description" : "JavaScript parser and compressor/beautifier toolkit", 5 | 6 | "author" : { 7 | "name" : "Mihai Bazon", 8 | "email" : "mihai.bazon@gmail.com", 9 | "url" : "http://mihai.bazon.net/blog" 10 | }, 11 | 12 | "version" : "1.2.3", 13 | 14 | "main" : "./uglify-js.js", 15 | 16 | "bin" : { 17 | "uglifyjs" : "./bin/uglifyjs" 18 | }, 19 | 20 | "repository": { 21 | "type": "git", 22 | "url": "git@github.com:mishoo/UglifyJS.git" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tools/uglifyjs/uglify-js.js: -------------------------------------------------------------------------------- 1 | //convienence function(src, [options]); 2 | function uglify(orig_code, options){ 3 | options || (options = {}); 4 | var jsp = uglify.parser; 5 | var pro = uglify.uglify; 6 | 7 | var ast = jsp.parse(orig_code, options.strict_semicolons); // parse code and get the initial AST 8 | ast = pro.ast_mangle(ast, options.mangle_options); // get a new AST with mangled names 9 | ast = pro.ast_squeeze(ast, options.squeeze_options); // get an AST with compression optimizations 10 | var final_code = pro.gen_code(ast, options.gen_options); // compressed code here 11 | return final_code; 12 | }; 13 | 14 | uglify.parser = require("./lib/parse-js"); 15 | uglify.uglify = require("./lib/process"); 16 | 17 | module.exports = uglify -------------------------------------------------------------------------------- /src/jsonrpc.DelayedTask.js: -------------------------------------------------------------------------------- 1 | jsonrpc.DelayedTask = function (fn, scope, args) { 2 | this._fn = fn || function () {}; 3 | this._scope = scope || undefined; 4 | this._args = args || []; 5 | this._id = null; 6 | }; 7 | 8 | jsonrpc.DelayedTask.prototype = { 9 | delay: function (delay, fn, scope, args) { 10 | var me = this; 11 | 12 | this._fn = fn || this._fn; 13 | this._scope = scope || this._scope; 14 | this._args = args || this._args; 15 | this.cancel(); 16 | this._id = window.setInterval(function () { 17 | window.clearInterval(me._id); 18 | me._id = null; 19 | me._fn.apply(me._scope, me._args); 20 | }, delay); 21 | }, 22 | 23 | cancel: function () { 24 | if (this._id) { 25 | window.clearInterval(this._id); 26 | this._id = null; 27 | } 28 | } 29 | }; -------------------------------------------------------------------------------- /tools/jsmake.javascript.JavascriptUtils.js: -------------------------------------------------------------------------------- 1 | jsmake.javascript = {}; 2 | 3 | jsmake.javascript.JavascriptUtils = function () { 4 | }; 5 | jsmake.javascript.JavascriptUtils.prototype = { 6 | jslint: function (files, options, globals) { 7 | if (!JSLINT) { 8 | throw 'JSLint library must be loaded. Download JSLint and include it in build script'; 9 | } 10 | globals = '/*global ' + jsmake.Utils.map(globals, function (global, key) { 11 | return key + ': ' + !!global; 12 | }, this).join(', ') + ' */\n'; 13 | var errors = []; 14 | jsmake.Utils.each(files, function (file) { 15 | var content = globals + fs.readFile(file); 16 | JSLINT(content, options); 17 | jsmake.Utils.each(JSLINT.errors, function (error) { 18 | if (error) { 19 | errors.push(file + ':' + (error.line - 1) + ',' + error.character + ': ' + error.reason); 20 | } 21 | }); 22 | }); 23 | 24 | if (errors.length) { 25 | sys.log('JSLint found ' + errors.length + ' errors'); 26 | sys.log(errors.join('\n')); 27 | throw 'Fatal error, see previous messages.'; 28 | } 29 | } 30 | }; -------------------------------------------------------------------------------- /tools/jasmine/MIT.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2011 Pivotal Labs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /SpecRunner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Jasmine Test Runner 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/jsonrpc.CallStackTest.js: -------------------------------------------------------------------------------- 1 | describe('jsonrpc.CallStack', function () { 2 | var target, enterFn, exitFn, scope; 3 | 4 | beforeEach(function () { 5 | enterFn = jasmine.createSpy(); 6 | exitFn = jasmine.createSpy(); 7 | scope = {}; 8 | target = new jsonrpc.CallStack(enterFn, exitFn, scope); 9 | }); 10 | 11 | it('should keep scope and parameters', function () { 12 | var enterScope = {}, 13 | exitScope = {}, 14 | enterFn = jasmine.createSpy(), 15 | exitFn = jasmine.createSpy(), 16 | target = new jsonrpc.CallStack(enterFn, enterScope, exitFn, exitScope); 17 | 18 | target.enter(1, 2, 3); 19 | target.exit(4, 5, 6); 20 | 21 | expect(enterFn).toHaveBeenCalledWith(1, 2, 3); 22 | expect(enterFn.mostRecentCall.object).toBe(enterScope); 23 | 24 | expect(exitFn).toHaveBeenCalledWith(4, 5, 6); 25 | expect(exitFn.mostRecentCall.object).toBe(exitScope); 26 | }); 27 | 28 | it('should simmetrically invoke enter and exit fn', function () { 29 | var count = 0, 30 | enter = [], 31 | exit = [], 32 | target; 33 | target = new jsonrpc.CallStack(function () { count += 1; enter.push(count); }, null, function () { count += 1; exit.push(count); }, null); 34 | 35 | target.exit(); 36 | target.enter(); 37 | target.enter(); 38 | target.exit(); 39 | target.exit(); 40 | target.exit(); 41 | target.enter(); 42 | 43 | expect(enter).toEqual([1, 3]); 44 | expect(exit).toEqual([2]); 45 | expect(count).toEqual(3); 46 | }); 47 | }); -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | JSON-RPC 2.0 client library for Javascript 2 | ------------------------------------------ 3 | 4 | Blah blah blah 5 | 6 | Sample code 7 | ----------- 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /test/jsonrpc.DelayedTaskTest.js: -------------------------------------------------------------------------------- 1 | describe('jsonrpc.DelayedTask', function () { 2 | it('should invoke after delay', function () { 3 | var fn = jasmine.createSpy(), 4 | scope = {}, 5 | target = new jsonrpc.DelayedTask(fn, scope, [ 1, 2, 3 ]); 6 | 7 | runs(function () { 8 | target.delay(100); 9 | expect(fn).not.toHaveBeenCalled(); 10 | }); 11 | 12 | waits(110); 13 | 14 | runs(function () { 15 | expect(fn).toHaveBeenCalledWith(1, 2, 3); 16 | expect(fn.mostRecentCall.object).toBe(scope); 17 | }); 18 | }); 19 | 20 | it('should cancel call', function () { 21 | var fn = jasmine.createSpy(), 22 | target = new jsonrpc.DelayedTask(fn); 23 | 24 | runs(function () { 25 | target.delay(100); 26 | }); 27 | 28 | waits(50); 29 | 30 | runs(function () { 31 | target.cancel(); 32 | }); 33 | 34 | waits(60); 35 | 36 | runs(function () { 37 | expect(fn).not.toHaveBeenCalled(); 38 | }); 39 | }); 40 | 41 | it('should call the last specified function', function () { 42 | var fn = jasmine.createSpy(), 43 | target = new jsonrpc.DelayedTask(fn), 44 | newFn = jasmine.createSpy(), 45 | newScope = {}; 46 | 47 | runs(function () { 48 | target.delay(30, newFn, newScope, [1, 2, 3]); 49 | }); 50 | 51 | waits(50); 52 | 53 | runs(function () { 54 | expect(fn).not.toHaveBeenCalled(); 55 | expect(newFn).toHaveBeenCalledWith(1, 2, 3); 56 | expect(newFn.mostRecentCall.object).toBe(newScope); 57 | }); 58 | }); 59 | 60 | it('should restart timer on each delay call', function () { 61 | var stop = null, 62 | fn = function () { stop = new Date().getTime(); }, 63 | target = new jsonrpc.DelayedTask(fn), 64 | start; 65 | 66 | runs(function () { 67 | start = new Date().getTime(); 68 | target.delay(50); 69 | }); 70 | 71 | waits(30); 72 | 73 | runs(function () { 74 | target.delay(50); 75 | }); 76 | 77 | waits(30); 78 | 79 | runs(function () { 80 | target.delay(50); 81 | }); 82 | 83 | waitsFor(function () { 84 | return !!stop; 85 | }); 86 | 87 | runs(function () { 88 | expect(stop - start).toBeGreaterThan(100); 89 | expect(stop - start).toBeLessThan(120); 90 | }); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /specrunner.js: -------------------------------------------------------------------------------- 1 | // From http://stackoverflow.com/questions/2261705/how-to-run-a-javascript-function-asynchronously-without-using-settimeout/5767884#5767884 2 | (function (global) { 3 | var timer = new java.util.Timer(); 4 | var counter = 1; 5 | var ids = {}; 6 | 7 | global.setTimeout = function (fn, delay) { 8 | var id = counter++; 9 | ids[id] = new JavaAdapter(java.util.TimerTask, { run: fn }); 10 | timer.schedule(ids[id], delay); 11 | return id; 12 | }; 13 | 14 | global.clearTimeout = function (id) { 15 | ids[id].cancel(); 16 | timer.purge(); 17 | delete ids[id]; 18 | }; 19 | 20 | global.setInterval = function (fn, delay) { 21 | var id = counter++; 22 | ids[id] = new JavaAdapter(java.util.TimerTask, { run: fn }); 23 | timer.schedule(ids[id], delay, delay); 24 | return id; 25 | }; 26 | 27 | global.clearInterval = global.clearTimeout; 28 | })(this); 29 | 30 | this.window = this; // jasmine uses 'window' global variable to detect commonjs environment. We want Jasmine to behave like when run in browser 31 | load('tools/jasmine/jasmine.js'); 32 | 33 | //var jasmine = require('tools/jasmine/jasmine').jasmine; 34 | 35 | (function (args) { 36 | for (var i = 0; i < args.length; i += 1) { 37 | load(args[i]); 38 | } 39 | }(arguments)); 40 | 41 | jasmine.RhinoReporter = function() { 42 | }; 43 | jasmine.RhinoReporter.prototype = { 44 | reportRunnerStarting: function(runner) { 45 | this._results = ''; 46 | }, 47 | reportRunnerResults: function(runner) { 48 | var failedCount = runner.results().failedCount; 49 | 50 | this.log(this._results); 51 | this.log("Passed: " + runner.results().passedCount); 52 | this.log("Failed: " + failedCount); 53 | this.log("Total : " + runner.results().totalCount); 54 | 55 | java.lang.System.exit(failedCount); 56 | }, 57 | reportSuiteResults: function(suite) { 58 | }, 59 | reportSpecStarting: function(spec) { 60 | }, 61 | reportSpecResults: function(spec) { 62 | var i, specResults = spec.results().getItems(); 63 | 64 | if (spec.results().passed()) { 65 | java.lang.System.out.print("."); 66 | } else { 67 | java.lang.System.out.print("F"); 68 | this._results += "FAILED\n"; 69 | this._results += "Suite: " + spec.suite.description + "\n"; 70 | this._results += "Spec : " + spec.description + "\n"; 71 | for (i = 0; i < specResults.length; i += 1) { 72 | this._results += specResults[i].trace + "\n"; 73 | } 74 | } 75 | }, 76 | log: function(str) { 77 | print(str); 78 | } 79 | }; 80 | jasmine.getEnv().addReporter(new jasmine.RhinoReporter()); 81 | jasmine.getEnv().execute(); -------------------------------------------------------------------------------- /tools/jasmine/jasmine.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; 3 | } 4 | 5 | 6 | .jasmine_reporter a:visited, .jasmine_reporter a { 7 | color: #303; 8 | } 9 | 10 | .jasmine_reporter a:hover, .jasmine_reporter a:active { 11 | color: blue; 12 | } 13 | 14 | .run_spec { 15 | float:right; 16 | padding-right: 5px; 17 | font-size: .8em; 18 | text-decoration: none; 19 | } 20 | 21 | .jasmine_reporter { 22 | margin: 0 5px; 23 | } 24 | 25 | .banner { 26 | color: #303; 27 | background-color: #fef; 28 | padding: 5px; 29 | } 30 | 31 | .logo { 32 | float: left; 33 | font-size: 1.1em; 34 | padding-left: 5px; 35 | } 36 | 37 | .logo .version { 38 | font-size: .6em; 39 | padding-left: 1em; 40 | } 41 | 42 | .runner.running { 43 | background-color: yellow; 44 | } 45 | 46 | 47 | .options { 48 | text-align: right; 49 | font-size: .8em; 50 | } 51 | 52 | 53 | 54 | 55 | .suite { 56 | border: 1px outset gray; 57 | margin: 5px 0; 58 | padding-left: 1em; 59 | } 60 | 61 | .suite .suite { 62 | margin: 5px; 63 | } 64 | 65 | .suite.passed { 66 | background-color: #dfd; 67 | } 68 | 69 | .suite.failed { 70 | background-color: #fdd; 71 | } 72 | 73 | .spec { 74 | margin: 5px; 75 | padding-left: 1em; 76 | clear: both; 77 | } 78 | 79 | .spec.failed, .spec.passed, .spec.skipped { 80 | padding-bottom: 5px; 81 | border: 1px solid gray; 82 | } 83 | 84 | .spec.failed { 85 | background-color: #fbb; 86 | border-color: red; 87 | } 88 | 89 | .spec.passed { 90 | background-color: #bfb; 91 | border-color: green; 92 | } 93 | 94 | .spec.skipped { 95 | background-color: #bbb; 96 | } 97 | 98 | .messages { 99 | border-left: 1px dashed gray; 100 | padding-left: 1em; 101 | padding-right: 1em; 102 | } 103 | 104 | .passed { 105 | background-color: #cfc; 106 | display: none; 107 | } 108 | 109 | .failed { 110 | background-color: #fbb; 111 | } 112 | 113 | .skipped { 114 | color: #777; 115 | background-color: #eee; 116 | display: none; 117 | } 118 | 119 | 120 | /*.resultMessage {*/ 121 | /*white-space: pre;*/ 122 | /*}*/ 123 | 124 | .resultMessage span.result { 125 | display: block; 126 | line-height: 2em; 127 | color: black; 128 | } 129 | 130 | .resultMessage .mismatch { 131 | color: black; 132 | } 133 | 134 | .stackTrace { 135 | white-space: pre; 136 | font-size: .8em; 137 | margin-left: 10px; 138 | max-height: 5em; 139 | overflow: auto; 140 | border: 1px inset red; 141 | padding: 1em; 142 | background: #eef; 143 | } 144 | 145 | .finished-at { 146 | padding-left: 1em; 147 | font-size: .6em; 148 | } 149 | 150 | .show-passed .passed, 151 | .show-skipped .skipped { 152 | display: block; 153 | } 154 | 155 | 156 | #jasmine_content { 157 | position:fixed; 158 | right: 100%; 159 | } 160 | 161 | .runner { 162 | border: 1px solid gray; 163 | display: block; 164 | margin: 5px 0; 165 | padding: 2px 0 2px 10px; 166 | } 167 | -------------------------------------------------------------------------------- /tools/uglifyjs/lib/squeeze-more.js: -------------------------------------------------------------------------------- 1 | var jsp = require("./parse-js"), 2 | pro = require("./process"), 3 | slice = jsp.slice, 4 | member = jsp.member, 5 | curry = jsp.curry, 6 | MAP = pro.MAP, 7 | PRECEDENCE = jsp.PRECEDENCE, 8 | OPERATORS = jsp.OPERATORS; 9 | 10 | function ast_squeeze_more(ast) { 11 | var w = pro.ast_walker(), walk = w.walk, scope; 12 | function with_scope(s, cont) { 13 | var save = scope, ret; 14 | scope = s; 15 | ret = cont(); 16 | scope = save; 17 | return ret; 18 | }; 19 | function _lambda(name, args, body) { 20 | return [ this[0], name, args, with_scope(body.scope, curry(MAP, body, walk)) ]; 21 | }; 22 | return w.with_walkers({ 23 | "toplevel": function(body) { 24 | return [ this[0], with_scope(this.scope, curry(MAP, body, walk)) ]; 25 | }, 26 | "function": _lambda, 27 | "defun": _lambda, 28 | "new": function(ctor, args) { 29 | if (ctor[0] == "name") { 30 | if (ctor[1] == "Array" && !scope.has("Array")) { 31 | if (args.length != 1) { 32 | return [ "array", args ]; 33 | } else { 34 | return walk([ "call", [ "name", "Array" ], args ]); 35 | } 36 | } else if (ctor[1] == "Object" && !scope.has("Object")) { 37 | if (!args.length) { 38 | return [ "object", [] ]; 39 | } else { 40 | return walk([ "call", [ "name", "Object" ], args ]); 41 | } 42 | } else if ((ctor[1] == "RegExp" || ctor[1] == "Function" || ctor[1] == "Error") && !scope.has(ctor[1])) { 43 | return walk([ "call", [ "name", ctor[1] ], args]); 44 | } 45 | } 46 | }, 47 | "call": function(expr, args) { 48 | if (expr[0] == "dot" && expr[2] == "toString" && args.length == 0) { 49 | // foo.toString() ==> foo+"" 50 | return [ "binary", "+", expr[1], [ "string", "" ]]; 51 | } 52 | if (expr[0] == "name") { 53 | if (expr[1] == "Array" && args.length != 1 && !scope.has("Array")) { 54 | return [ "array", args ]; 55 | } 56 | if (expr[1] == "Object" && !args.length && !scope.has("Object")) { 57 | return [ "object", [] ]; 58 | } 59 | if (expr[1] == "String" && !scope.has("String")) { 60 | return [ "binary", "+", args[0], [ "string", "" ]]; 61 | } 62 | } 63 | } 64 | }, function() { 65 | return walk(pro.ast_add_scope(ast)); 66 | }); 67 | }; 68 | 69 | exports.ast_squeeze_more = ast_squeeze_more; 70 | -------------------------------------------------------------------------------- /src/jsonrpc.JsonRpc.js: -------------------------------------------------------------------------------- 1 | jsonrpc.JsonRpc = function (url) { 2 | this._url = url; 3 | this.loading = new jsonrpc.Observable(); 4 | this.loaded = new jsonrpc.Observable(); 5 | this.unhandledFailure = new jsonrpc.Observable(); 6 | this._loadingState = new jsonrpc.CallStack(this.loading.trigger, this.loading, this.loaded.trigger, this.loaded); 7 | this._requests = []; 8 | this._batchingMilliseconds = 10; 9 | this._delayedTask = new jsonrpc.DelayedTask(); 10 | }; 11 | 12 | jsonrpc.JsonRpc.prototype = { 13 | setBatchingMilliseconds: function (value) { 14 | this._batchingMilliseconds = value; 15 | }, 16 | 17 | call: function () { 18 | var args = this._getParams.apply(this, arguments); 19 | 20 | this._loadingState.enter(); 21 | this._requests.push(args); 22 | 23 | if (this._batchingMilliseconds) { 24 | this._delayedTask.delay(this._batchingMilliseconds, this._sendRequests, this); 25 | } else { 26 | this._sendRequests(); 27 | } 28 | }, 29 | 30 | _sendRequests: function () { 31 | var me = this, 32 | requests = this._requests, 33 | data = [], 34 | i; 35 | 36 | this._requests = []; 37 | 38 | for (i = 0; i < requests.length; i += 1) { 39 | requests[i].request.id = i; 40 | data.push(requests[i].request); 41 | } 42 | 43 | if (data.length === 1) { 44 | data = data[0]; 45 | } 46 | 47 | me._doJsonPost(me._url, data, function (htmlSuccess, htmlResponse) { 48 | var responses; 49 | if (htmlSuccess) { 50 | responses = (me._isArray(htmlResponse) ? htmlResponse : [htmlResponse]); 51 | } else { 52 | responses = []; 53 | for (i = 0; i < requests.length; i += 1) { 54 | responses[i] = { id: i, error: { message: htmlResponse } }; 55 | } 56 | } 57 | me._handleResponses(requests, responses); 58 | }); 59 | }, 60 | 61 | _handleResponses: function (requests, responses) { 62 | var i, response, request; 63 | for (i = 0; i < responses.length; i += 1) { 64 | response = responses[i]; 65 | request = requests[response.id]; 66 | this._handleResponse(request, response); 67 | } 68 | }, 69 | 70 | _handleResponse: function (request, response) { 71 | var success = !response.error, 72 | ret = (success ? response.result : response.error.message); 73 | 74 | this._loadingState.exit(); 75 | 76 | if (success) { 77 | request.success.call(request.scope, ret); 78 | } else { 79 | request.failure.call(request.scope, ret); 80 | } 81 | request.callback.call(request.scope, success, ret); 82 | }, 83 | 84 | _getParams: function () { 85 | var me = this, 86 | args = Array.prototype.slice.call(arguments), 87 | ret = { 88 | request: { 89 | jsonrpc: '2.0', 90 | method: args.shift() 91 | } 92 | }; 93 | 94 | ret.request.params = []; 95 | while (args.length > 1 && !this._isFunction(args[0])) { 96 | ret.request.params.push(args.shift()); 97 | } 98 | 99 | if (this._isFunction(args[0])) { 100 | ret.success = args[0]; 101 | ret.scope = args[1]; 102 | } else { 103 | ret.success = args[0].success; 104 | ret.failure = args[0].failure; 105 | ret.callback = args[0].callback; 106 | ret.scope = args[0].scope; 107 | } 108 | ret.success = ret.success || function () { return; }; 109 | ret.failure = ret.failure || function () { me.unhandledFailure.trigger.apply(me.unhandledFailure, arguments); }; 110 | ret.callback = ret.callback || function () { return; }; 111 | 112 | return ret; 113 | }, 114 | 115 | _isArray: function (v) { 116 | return Object.prototype.toString.apply(v) === '[object Array]'; 117 | }, 118 | 119 | _isFunction: function (v) { 120 | return Object.prototype.toString.apply(v) === '[object Function]'; 121 | }, 122 | 123 | _doJsonPost: function (url, data, callback) { 124 | var xhr = new XMLHttpRequest(); 125 | xhr.open("POST", url, true); 126 | xhr.setRequestHeader('Content-Type', 'application/json'); 127 | xhr.onreadystatechange = function () { 128 | if (xhr.readyState !== 4) { 129 | return; 130 | } 131 | 132 | var contentType = xhr.getResponseHeader('Content-Type'); 133 | 134 | if (xhr.status !== 200) { 135 | callback(false, 'Expected HTTP response "200 OK", found "' + xhr.status + ' ' + xhr.statusText + '"'); 136 | } else if (contentType.indexOf('application/json') !== 0) { 137 | callback(false, 'Expected JSON encoded response, found "' + contentType + '"'); 138 | } else { 139 | callback(true, JSON.parse(this.responseText)); 140 | } 141 | }; 142 | xhr.send(JSON.stringify(data)); 143 | } 144 | }; -------------------------------------------------------------------------------- /tools/jasmine/jasmine-html.js: -------------------------------------------------------------------------------- 1 | jasmine.TrivialReporter = function(doc) { 2 | this.document = doc || document; 3 | this.suiteDivs = {}; 4 | this.logRunningSpecs = false; 5 | }; 6 | 7 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { 8 | var el = document.createElement(type); 9 | 10 | for (var i = 2; i < arguments.length; i++) { 11 | var child = arguments[i]; 12 | 13 | if (typeof child === 'string') { 14 | el.appendChild(document.createTextNode(child)); 15 | } else { 16 | if (child) { el.appendChild(child); } 17 | } 18 | } 19 | 20 | for (var attr in attrs) { 21 | if (attr == "className") { 22 | el[attr] = attrs[attr]; 23 | } else { 24 | el.setAttribute(attr, attrs[attr]); 25 | } 26 | } 27 | 28 | return el; 29 | }; 30 | 31 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { 32 | var showPassed, showSkipped; 33 | 34 | this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' }, 35 | this.createDom('div', { className: 'banner' }, 36 | this.createDom('div', { className: 'logo' }, 37 | this.createDom('span', { className: 'title' }, "Jasmine"), 38 | this.createDom('span', { className: 'version' }, runner.env.versionString())), 39 | this.createDom('div', { className: 'options' }, 40 | "Show ", 41 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), 42 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), 43 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), 44 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") 45 | ) 46 | ), 47 | 48 | this.runnerDiv = this.createDom('div', { className: 'runner running' }, 49 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), 50 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."), 51 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) 52 | ); 53 | 54 | this.document.body.appendChild(this.outerDiv); 55 | 56 | var suites = runner.suites(); 57 | for (var i = 0; i < suites.length; i++) { 58 | var suite = suites[i]; 59 | var suiteDiv = this.createDom('div', { className: 'suite' }, 60 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), 61 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); 62 | this.suiteDivs[suite.id] = suiteDiv; 63 | var parentDiv = this.outerDiv; 64 | if (suite.parentSuite) { 65 | parentDiv = this.suiteDivs[suite.parentSuite.id]; 66 | } 67 | parentDiv.appendChild(suiteDiv); 68 | } 69 | 70 | this.startedAt = new Date(); 71 | 72 | var self = this; 73 | showPassed.onclick = function(evt) { 74 | if (showPassed.checked) { 75 | self.outerDiv.className += ' show-passed'; 76 | } else { 77 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); 78 | } 79 | }; 80 | 81 | showSkipped.onclick = function(evt) { 82 | if (showSkipped.checked) { 83 | self.outerDiv.className += ' show-skipped'; 84 | } else { 85 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); 86 | } 87 | }; 88 | }; 89 | 90 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { 91 | var results = runner.results(); 92 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; 93 | this.runnerDiv.setAttribute("class", className); 94 | //do it twice for IE 95 | this.runnerDiv.setAttribute("className", className); 96 | var specs = runner.specs(); 97 | var specCount = 0; 98 | for (var i = 0; i < specs.length; i++) { 99 | if (this.specFilter(specs[i])) { 100 | specCount++; 101 | } 102 | } 103 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); 104 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; 105 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); 106 | 107 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); 108 | }; 109 | 110 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { 111 | var results = suite.results(); 112 | var status = results.passed() ? 'passed' : 'failed'; 113 | if (results.totalCount === 0) { // todo: change this to check results.skipped 114 | status = 'skipped'; 115 | } 116 | this.suiteDivs[suite.id].className += " " + status; 117 | }; 118 | 119 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { 120 | if (this.logRunningSpecs) { 121 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 122 | } 123 | }; 124 | 125 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { 126 | var results = spec.results(); 127 | var status = results.passed() ? 'passed' : 'failed'; 128 | if (results.skipped) { 129 | status = 'skipped'; 130 | } 131 | var specDiv = this.createDom('div', { className: 'spec ' + status }, 132 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), 133 | this.createDom('a', { 134 | className: 'description', 135 | href: '?spec=' + encodeURIComponent(spec.getFullName()), 136 | title: spec.getFullName() 137 | }, spec.description)); 138 | 139 | 140 | var resultItems = results.getItems(); 141 | var messagesDiv = this.createDom('div', { className: 'messages' }); 142 | for (var i = 0; i < resultItems.length; i++) { 143 | var result = resultItems[i]; 144 | 145 | if (result.type == 'log') { 146 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 147 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 148 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 149 | 150 | if (result.trace.stack) { 151 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 152 | } 153 | } 154 | } 155 | 156 | if (messagesDiv.childNodes.length > 0) { 157 | specDiv.appendChild(messagesDiv); 158 | } 159 | 160 | this.suiteDivs[spec.suite.id].appendChild(specDiv); 161 | }; 162 | 163 | jasmine.TrivialReporter.prototype.log = function() { 164 | var console = jasmine.getGlobal().console; 165 | if (console && console.log) { 166 | if (console.log.apply) { 167 | console.log.apply(console, arguments); 168 | } else { 169 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 170 | } 171 | } 172 | }; 173 | 174 | jasmine.TrivialReporter.prototype.getLocation = function() { 175 | return this.document.location; 176 | }; 177 | 178 | jasmine.TrivialReporter.prototype.specFilter = function(spec) { 179 | var paramMap = {}; 180 | var params = this.getLocation().search.substring(1).split('&'); 181 | for (var i = 0; i < params.length; i++) { 182 | var p = params[i].split('='); 183 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 184 | } 185 | 186 | if (!paramMap.spec) { 187 | return true; 188 | } 189 | return spec.getFullName().indexOf(paramMap.spec) === 0; 190 | }; 191 | -------------------------------------------------------------------------------- /test/jsonrpc.JsonRpcTest.js: -------------------------------------------------------------------------------- 1 | describe("jsonrpc.JsonRpc", function () { 2 | var target, fakeSuccess, fakeResponse; 3 | 4 | beforeEach(function () { 5 | target = new jsonrpc.JsonRpc('rpc'); 6 | target.setBatchingMilliseconds(0); 7 | spyOn(target, '_doJsonPost').andCallFake(function (url, data, callback) { 8 | callback(fakeSuccess, fakeResponse); 9 | }); 10 | }); 11 | 12 | it("isFunction", function () { 13 | expect(target._isFunction(1)).toBe(false); 14 | expect(target._isFunction('a')).toBe(false); 15 | expect(target._isFunction(undefined)).toBe(false); 16 | expect(target._isFunction(null)).toBe(false); 17 | expect(target._isFunction(function () { 18 | })).toBe(true); 19 | }); 20 | 21 | it("isArray", function () { 22 | expect(target._isArray()).toBe(false); 23 | expect(target._isArray([])).toBe(true); 24 | expect(target._isArray({})).toBe(false); 25 | expect(target._isArray(123)).toBe(false); 26 | expect(target._isArray('')).toBe(false); 27 | expect(target._isArray('a string')).toBe(false); 28 | expect(target._isArray(null)).toBe(false); 29 | expect(target._isArray(arguments)).toBe(false); 30 | }); 31 | 32 | it('should interpret call with succes function and scope', function () { 33 | var scope = {}, 34 | successFn = jasmine.createSpy(), 35 | actual; 36 | 37 | actual = target._getParams('method', 1, 2, 3, successFn, scope); 38 | 39 | expect(actual.request.method).toEqual('method'); 40 | expect(actual.request.params).toEqual([1, 2, 3]); 41 | expect(actual.success).toBe(successFn); 42 | expect(actual.scope).toBe(scope); 43 | }); 44 | 45 | it('should batch calls within timeout', function () { 46 | target.setBatchingMilliseconds(10); 47 | 48 | runs(function () { 49 | target.call('method1', 'par1', jasmine.createSpy(), {}); 50 | target.call('method2', 'par2', jasmine.createSpy(), {}); 51 | expect(target._doJsonPost).not.toHaveBeenCalled(); 52 | }); 53 | 54 | waits(15); 55 | 56 | runs(function () { 57 | target.call('method3', 'par3', jasmine.createSpy(), {}); 58 | }); 59 | 60 | waits(15); 61 | 62 | runs(function () { 63 | expect(target._doJsonPost).toHaveBeenCalledWith('rpc', [{ 64 | jsonrpc: '2.0', 65 | id: 0, 66 | method: 'method1', 67 | params: ['par1'] 68 | }, { 69 | jsonrpc: '2.0', 70 | id: 1, 71 | method: 'method2', 72 | params: ['par2'] 73 | }], jasmine.any(Function)); 74 | expect(target._doJsonPost).toHaveBeenCalledWith('rpc', { 75 | jsonrpc: '2.0', 76 | id: 0, 77 | method: 'method3', 78 | params: ['par3'] 79 | }, jasmine.any(Function)); 80 | }); 81 | }); 82 | 83 | it('should wrap batched call in single loading/loaded events', function () { 84 | var loadingFn = jasmine.createSpy(), 85 | loadedFn = jasmine.createSpy(); 86 | target.setBatchingMilliseconds(10); 87 | target.loading.bind(loadingFn); 88 | target.loaded.bind(loadedFn); 89 | 90 | runs(function () { 91 | expect(loadingFn.callCount).toEqual(0); 92 | expect(loadedFn.callCount).toEqual(0); 93 | target.call('method1', 'par1', jasmine.createSpy(), {}); 94 | target.call('method2', 'par2', jasmine.createSpy(), {}); 95 | expect(loadingFn.callCount).toEqual(1); 96 | expect(loadedFn.callCount).toEqual(0); 97 | }); 98 | 99 | waits(15); 100 | 101 | runs(function () { 102 | expect(loadingFn.callCount).toEqual(1); 103 | expect(loadedFn.callCount).toEqual(1); 104 | }); 105 | }); 106 | 107 | it('should assign progressive ids', function () { 108 | target.setBatchingMilliseconds(10); 109 | 110 | runs(function () { 111 | target.call('method1', {}); 112 | target.call('method2', {}); 113 | target.call('method3', {}); 114 | target.call('method4', {}); 115 | }); 116 | 117 | waits(15); 118 | 119 | runs(function () { 120 | expect(target._doJsonPost).toHaveBeenCalledWith('rpc', [{ 121 | jsonrpc: '2.0', 122 | id: 0, 123 | method: 'method1', 124 | params: [] 125 | }, { 126 | jsonrpc: '2.0', 127 | id: 1, 128 | method: 'method2', 129 | params: [] 130 | }, { 131 | jsonrpc: '2.0', 132 | id: 2, 133 | method: 'method3', 134 | params: [] 135 | }, { 136 | jsonrpc: '2.0', 137 | id: 3, 138 | method: 'method4', 139 | params: [] 140 | }], jasmine.any(Function)); 141 | }); 142 | }); 143 | 144 | it('should interpret call with options', function () { 145 | var scope = {}, 146 | successFn = jasmine.createSpy(), 147 | failureFn = jasmine.createSpy(), 148 | callbackFn = jasmine.createSpy(), 149 | actual; 150 | 151 | actual = target._getParams('method', 1, 2, 3, { 152 | success: successFn, 153 | failure: failureFn, 154 | callback: callbackFn, 155 | scope: scope 156 | }); 157 | 158 | expect(actual.request.method).toEqual('method'); 159 | expect(actual.request.params).toEqual([1, 2, 3]); 160 | expect(actual.success).toBe(successFn); 161 | expect(actual.failure).toBe(failureFn); 162 | expect(actual.callback).toBe(callbackFn); 163 | expect(actual.scope).toBe(scope); 164 | }); 165 | 166 | it('should do json post with expected parameters', function () { 167 | var scope = {}, 168 | successFn = jasmine.createSpy(); 169 | 170 | target.call('method', 1, 2, 3, successFn, scope); 171 | 172 | expect(target._doJsonPost).toHaveBeenCalledWith('rpc', { 173 | jsonrpc: '2.0', 174 | id: 0, 175 | method: 'method', 176 | params: [1, 2, 3] 177 | }, jasmine.any(Function)); 178 | }); 179 | 180 | it('should invoke expected callbacks on succesful call', function () { 181 | var scope = {}, 182 | successFn = jasmine.createSpy(), 183 | failureFn = jasmine.createSpy(), 184 | callbackFn = jasmine.createSpy(); 185 | 186 | fakeSuccess = true; 187 | fakeResponse = { id: 0, result: 'return val' }; 188 | 189 | target.call('method', 1, 2, 3, { 190 | success: successFn, 191 | failure: failureFn, 192 | callback: callbackFn, 193 | scope: scope 194 | }); 195 | 196 | expect(failureFn).not.toHaveBeenCalled(); 197 | expect(successFn).toHaveBeenCalledWith('return val'); 198 | expect(successFn.callCount).toBe(1); 199 | expect(successFn.mostRecentCall.object).toBe(scope); 200 | expect(callbackFn).toHaveBeenCalledWith(true, 'return val'); 201 | expect(callbackFn.callCount).toBe(1); 202 | expect(callbackFn.mostRecentCall.object).toBe(scope); 203 | }); 204 | 205 | it('should invoke expected callbacks on transport error', function () { 206 | var scope = {}, 207 | successFn = jasmine.createSpy(), 208 | failureFn = jasmine.createSpy(), 209 | callbackFn = jasmine.createSpy(); 210 | 211 | fakeSuccess = false; 212 | fakeResponse = 'error msg'; 213 | 214 | target.call('method', 1, 2, 3, { 215 | success: successFn, 216 | failure: failureFn, 217 | callback: callbackFn, 218 | scope: scope 219 | }); 220 | 221 | expect(failureFn).toHaveBeenCalledWith('error msg'); 222 | expect(failureFn.callCount).toBe(1); 223 | expect(successFn).not.toHaveBeenCalled(); 224 | expect(callbackFn).toHaveBeenCalledWith(false, 'error msg'); 225 | expect(callbackFn.callCount).toBe(1); 226 | }); 227 | 228 | it('should invoke expected callbacks on rpc error', function () { 229 | var scope = {}, 230 | successFn = jasmine.createSpy(), 231 | failureFn = jasmine.createSpy(), 232 | callbackFn = jasmine.createSpy(); 233 | 234 | fakeSuccess = true; 235 | fakeResponse = { id: 0, error: { message: 'rpc error' } }; 236 | 237 | target.call('method', 1, 2, 3, { 238 | success: successFn, 239 | failure: failureFn, 240 | callback: callbackFn, 241 | scope: scope 242 | }); 243 | 244 | expect(failureFn).toHaveBeenCalledWith('rpc error'); 245 | expect(failureFn.callCount).toBe(1); 246 | expect(successFn).not.toHaveBeenCalled(); 247 | expect(callbackFn).toHaveBeenCalledWith(false, 'rpc error'); 248 | expect(callbackFn.callCount).toBe(1); 249 | }); 250 | 251 | it('should trigger unhandledFailure event when call fail and failure callback not defined', function () { 252 | var unhandledFailureFn = jasmine.createSpy(); 253 | 254 | target.unhandledFailure.bind(unhandledFailureFn); 255 | 256 | fakeSuccess = false; 257 | fakeResponse = 'error msg'; 258 | 259 | target.call('method', jasmine.createSpy()); 260 | target.call('method', { 261 | failure: jasmine.createSpy() 262 | }); 263 | 264 | expect(unhandledFailureFn.callCount).toBe(1); 265 | expect(unhandledFailureFn).toHaveBeenCalledWith('error msg'); 266 | }); 267 | 268 | it('should trigger loading and loaded events', function () { 269 | var loadingFn = jasmine.createSpy(), 270 | loadedFn = jasmine.createSpy(); 271 | 272 | target.loading.bind(loadingFn); 273 | target.loaded.bind(loadedFn); 274 | fakeSuccess = true; 275 | fakeResponse = { id: 0, result: 'return val' }; 276 | 277 | target.call('method', 1, 2, 3, function () {}); 278 | 279 | expect(loadingFn.callCount).toBe(1); 280 | expect(loadedFn.callCount).toBe(1); 281 | }); 282 | 283 | it('should support responses in different order than requests', function () { 284 | // TODO 285 | }); 286 | }); 287 | -------------------------------------------------------------------------------- /tools/jsmake/jsmake.js: -------------------------------------------------------------------------------- 1 | /* 2 | JSMake version 0.8.37 3 | 4 | http://gimmi.github.com/jsmake/ 5 | 6 | Copyright 2011 Gian Marco Gherardi 7 | 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and 18 | limitations under the License. 19 | */ 20 | /** @namespace Top level namespace for JSMake */ 21 | jsmake = this.jsmake || {}; 22 | 23 | jsmake.Rhino = { 24 | translateJavaString: function (javaString) { 25 | if (javaString === null) { 26 | return null; 27 | } 28 | if (javaString === undefined) { 29 | return undefined; 30 | } 31 | return String(javaString); 32 | } 33 | }; 34 | /** @class Various helper methods to make working with Javascript easier */ 35 | jsmake.Utils = { 36 | /** 37 | * Return the same string with escaped regex chars, in order to be safely included as part of regex 38 | * @param {String} str string to escape 39 | * @returns {String} escaped string 40 | */ 41 | escapeForRegex: function (str) { 42 | return str.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); 43 | }, 44 | /** 45 | * @param v 46 | * @returns {Boolean} true if passed value is an array 47 | */ 48 | isArray: function (v) { 49 | // Check ignored test 'isArray should show strange behavior on Firefox' 50 | return Object.prototype.toString.apply(v) === '[object Array]'; 51 | }, 52 | /** 53 | * @param v 54 | * @returns {Boolean} true if passed value is an argument object 55 | */ 56 | isArguments: function (v) { 57 | return !!(v && Object.prototype.hasOwnProperty.call(v, 'callee')); 58 | }, 59 | /** 60 | * @param v 61 | * @returns {Array} passed value, converted to array 62 | */ 63 | toArray: function (v) { 64 | if (this.isEmpty(v)) { 65 | return []; 66 | } else if (this.isArray(v)) { 67 | return v; 68 | } else if (this.isArguments(v)) { 69 | return Array.prototype.slice.call(v); 70 | } else { 71 | return [ v ]; 72 | } 73 | }, 74 | /** 75 | * @param v 76 | * @returns {Boolean} true if passed value is an object 77 | */ 78 | isObject : function (v) { 79 | return !!v && Object.prototype.toString.call(v) === '[object Object]'; 80 | }, 81 | /** 82 | * @param v 83 | * @returns {Boolean} true if passed value is a number 84 | */ 85 | isNumber: function (v) { 86 | return typeof v === 'number' && isFinite(v); 87 | }, 88 | /** 89 | * @param v 90 | * @returns {Boolean} true if passed value is null, undefined or empty array 91 | */ 92 | isEmpty : function (v) { 93 | return v === null || v === undefined || ((this.isArray(v) && !v.length)); 94 | }, 95 | /** 96 | * @param v 97 | * @returns {Boolean} true if passed value is a function 98 | */ 99 | isFunction : function (v) { 100 | return Object.prototype.toString.apply(v) === '[object Function]'; 101 | }, 102 | /** 103 | * @param v 104 | * @returns {Boolean} true if passed value is a string 105 | */ 106 | isString: function (value) { 107 | return typeof value === 'string'; 108 | }, 109 | /** 110 | * @param {String} str string to trim 111 | * @returns {String} passed value with head and tail spaces removed 112 | */ 113 | trim: function (str) { 114 | return str.replace(/(?:^\s+)|(?:\s+$)/g, ''); 115 | }, 116 | /** 117 | * A function that does nothing, useful to pass around as null value 118 | */ 119 | EMPTY_FN: function () { 120 | }, 121 | /** 122 | * Iterate over each element of items. 123 | * @param items the collection on which iterate, can be anything 124 | * @param {Function} fn the funcion to call for each element in items. 125 | * Will be called with the following parameters: currentItem, itemIndex, items. 126 | * If function returns truthy value then iteration will stop 127 | * @param {Object} [scope] 'this' binding for function 128 | * @example 129 | * // Array iteration: the following code logs 130 | * // item=a, index=0, items=[a,b] 131 | * // item=b, index=1, items=[a,b] 132 | * jsmake.Utils.each([ 'a', 'b'], function (item, index, items) { 133 | * jsmake.Sys.log('item=' + item + ', index=' + index + ', items=' + items); 134 | * }, this); 135 | * // Object iteration: the following code logs 136 | * // item=1, index=a, items=[object] 137 | * // item=2, index=b, items=[object] 138 | * jsmake.Utils.each({ a: 1, b: 2 }, function (item, index, items) { 139 | * jsmake.Sys.log('item=' + item + ', index=' + index + ', items=' + items); 140 | * }, this); 141 | */ 142 | each: function (items, fn, scope) { 143 | var key; 144 | if (this.isObject(items)) { 145 | for (key in items) { 146 | if (items.hasOwnProperty(key)) { 147 | if (fn.call(scope, items[key], key, items)) { 148 | return; 149 | } 150 | } 151 | } 152 | } else { 153 | items = this.toArray(items); 154 | for (key = 0; key < items.length; key += 1) { 155 | if (fn.call(scope, items[key], key, items)) { 156 | return; 157 | } 158 | } 159 | } 160 | }, 161 | /** 162 | * Filter collection, returning elements that satisfy passed criteria 163 | * @param items can be anything, see {@link jsmake.Utils.each} 164 | * @param {Function} fn filter criteria, will be called for each element in items, passing current element as parameter. 165 | * Must return falsy value to indicate that the element should be filtered out 166 | * @param {Object} [scope] 'this' binding for function 167 | * @returns {Array} filtered values 168 | * @example 169 | * // returns [ 1, 2 ] 170 | * jsmake.Utils.filter([ 1, 2, 3 ], function (item) { 171 | * return item < 3; 172 | * }); 173 | */ 174 | filter: function (items, fn, scope) { 175 | var ret = []; 176 | this.each(items, function (item) { 177 | if (fn.call(scope, item)) { 178 | ret.push(item); 179 | } 180 | }, this); 181 | return ret; 182 | }, 183 | /** 184 | * Transform each item in passed collection, returning a new array with transformed items 185 | * @param items can be anything, see {@link jsmake.Utils.each} 186 | * @param {Function} fn transformation function, will be called for each element in items. 187 | * Will be called with the following parameters: currentItem, itemIndex, items. 188 | * If function returns truthy value then iteration will stop 189 | * Must return the transformed item 190 | * @param {Object} [scope] 'this' binding for function 191 | * @returns {Array} new array with transformed items 192 | * @example 193 | * // returns [ 4, 9 ] 194 | * jsmake.Utils.map([ 2, 3 ], function (item, index, items) { 195 | * return item * item; 196 | * }); 197 | */ 198 | map: function (items, fn, scope) { 199 | var ret = []; 200 | this.each(items, function (item, index, items) { 201 | ret.push(fn.call(scope, item, index, items)); 202 | }, this); 203 | return ret; 204 | }, 205 | /** 206 | * @example 207 | * // returns 'items are: 2 3 ' 208 | * jsmake.Utils.reduce([ 2, 3 ], function (memo, item, index, items) { 209 | * return memo + item + ' '; 210 | * }, 'items are: '); 211 | */ 212 | reduce: function (items, fn, memo, scope) { 213 | this.each(items, function (item, index, items) { 214 | memo = fn.call(scope, memo, item, index, items); 215 | }, this); 216 | return memo; 217 | }, 218 | /** 219 | * @example 220 | * jsmake.Utils.contains([ 2, 3 ], 3); // returns true 221 | * jsmake.Utils.contains([ 2, 3 ], 4); // returns false 222 | */ 223 | contains: function (items, item) { 224 | var ret = false; 225 | this.each(items, function (it) { 226 | ret = (it === item); 227 | return ret; 228 | }, this); 229 | return ret; 230 | }, 231 | /** 232 | * @example 233 | * jsmake.Utils.distinct([ 2, 3, 2, 3 ]); // returns [ 2, 3 ] 234 | */ 235 | distinct: function (items) { 236 | var ret = []; 237 | this.each(items, function (item) { 238 | if (!this.contains(ret, item)) { 239 | ret.push(item); 240 | } 241 | }, this); 242 | return ret; 243 | }, 244 | /** 245 | * @example 246 | * jsmake.Utils.flatten([ 1, [ 2, 3 ], [ 4, [ 5, 6 ] ] ]); // returns [ 1, 2, 3, 4, 5, 6 ] 247 | */ 248 | flatten: function (items) { 249 | return this.reduce(items, function (memo, item) { 250 | if (this.isArray(item)) { 251 | memo = memo.concat(this.flatten(item)); 252 | } else { 253 | memo.push(item); 254 | } 255 | return memo; 256 | }, [], this); 257 | } 258 | }; 259 | 260 | jsmake.Project = function (logger) { 261 | this._tasks = {}; 262 | this._logger = logger; 263 | }; 264 | jsmake.Project.prototype = { 265 | addTask: function (task) { 266 | this._tasks[task.getName()] = task; 267 | }, 268 | getTask: function (name) { 269 | var task = this._tasks[name]; 270 | if (!task) { 271 | throw "Task '" + name + "' not defined"; 272 | } 273 | return task; 274 | }, 275 | getTasks: function (name) { 276 | var tasks = []; 277 | this._fillDependencies(this.getTask(name), tasks, new jsmake.RecursionChecker('Task recursion found')); 278 | return jsmake.Utils.distinct(tasks); 279 | }, 280 | runTask: function (name, args) { 281 | var tasks, taskNames; 282 | tasks = this.getTasks(name); 283 | taskNames = jsmake.Utils.map(tasks, function (task) { 284 | return task.getName(); 285 | }, this); 286 | this._logger.log('Task execution order: ' + taskNames.join(', ')); 287 | jsmake.Utils.each(tasks, function (task) { 288 | task.run(task.getName() === name ? args : []); 289 | }, this); 290 | }, 291 | _fillDependencies: function (task, tasks, recursionChecker) { 292 | recursionChecker.wrap(task.getName(), function () { 293 | jsmake.Utils.each(task.getTaskNames(), function (taskName) { 294 | var task = this.getTask(taskName); 295 | this._fillDependencies(task, tasks, recursionChecker); 296 | }, this); 297 | tasks.push(task); 298 | }, this); 299 | } 300 | }; 301 | 302 | jsmake.Task = function (name, taskNames, body, logger) { 303 | this._name = name; 304 | this._taskNames = taskNames; 305 | this._body = body; 306 | this._logger = logger; 307 | }; 308 | jsmake.Task.prototype = { 309 | getName: function () { 310 | return this._name; 311 | }, 312 | getTaskNames: function () { 313 | return this._taskNames; 314 | }, 315 | run: function (args) { 316 | this._logger.log('Executing task ' + this._name); 317 | this._body.apply({}, args); 318 | } 319 | }; 320 | 321 | jsmake.RecursionChecker = function (message) { 322 | this._message = message; 323 | this._stack = []; 324 | }; 325 | jsmake.RecursionChecker.prototype = { 326 | enter: function (id) { 327 | this._check(id); 328 | this._stack.push(id); 329 | }, 330 | exit: function () { 331 | this._stack.pop(); 332 | }, 333 | wrap: function (id, fn, scope) { 334 | this.enter(id); 335 | try { 336 | fn.call(scope); 337 | } finally { 338 | this.exit(); 339 | } 340 | }, 341 | _check: function (id) { 342 | if (jsmake.Utils.contains(this._stack, id)) { 343 | this._stack.push(id); 344 | throw this._message + ': ' + this._stack.join(' => '); 345 | } 346 | } 347 | }; 348 | 349 | jsmake.AntPathMatcher = function (pattern, caseSensitive) { 350 | this._pattern = pattern; 351 | this._caseSensitive = caseSensitive; 352 | }; 353 | jsmake.AntPathMatcher.prototype = { 354 | match: function (path) { 355 | var patternTokens, pathTokens; 356 | patternTokens = this._tokenize(this._pattern); 357 | pathTokens = this._tokenize(path); 358 | return this._matchTokens(patternTokens, pathTokens); 359 | }, 360 | _matchTokens: function (patternTokens, pathTokens) { 361 | var patternToken, pathToken; 362 | while (true) { 363 | patternToken = patternTokens.shift(); 364 | if (patternToken === '**') { 365 | pathTokens = pathTokens.slice(-patternTokens.length).reverse(); 366 | patternTokens = patternTokens.reverse(); 367 | return this._matchTokens(patternTokens, pathTokens); 368 | } 369 | pathToken = pathTokens.shift(); 370 | if (patternToken && pathToken) { 371 | if (!this._matchToken(patternToken, pathToken)) { 372 | return false; 373 | } 374 | } else if (patternToken && !pathToken) { 375 | return false; 376 | } else if (!patternToken && pathToken) { 377 | return false; 378 | } else { 379 | return true; 380 | } 381 | } 382 | }, 383 | _matchToken: function (patternToken, pathToken) { 384 | var regex = '', i, ch; 385 | for (i = 0; i < patternToken.length; i += 1) { 386 | ch = patternToken.charAt(i); 387 | if (ch === '*') { 388 | regex += '.*'; 389 | } else if (ch === '?') { 390 | regex += '.{1}'; 391 | } else { 392 | regex += jsmake.Utils.escapeForRegex(ch); 393 | } 394 | } 395 | return new RegExp('^' + regex + '$', (this._caseSensitive ? '' : 'i')).test(pathToken); 396 | }, 397 | _tokenize: function (pattern) { 398 | var tokens = pattern.split(/\\+|\/+/); 399 | tokens = jsmake.Utils.map(tokens, function (token) { 400 | return jsmake.Utils.trim(token); 401 | }, this); 402 | tokens = jsmake.Utils.filter(tokens, function (token) { 403 | return !/^[\s\.]*$/.test(token); 404 | }, this); 405 | if (tokens[tokens.length - 1] === '**') { 406 | throw 'Invalid ** wildcard at end pattern, use **/* instead'; // TODO maybe useless 407 | } 408 | // TODO invalid more then one ** 409 | return tokens; 410 | } 411 | }; 412 | 413 | /** 414 | * @class Contains methods for system interaciont and informations 415 | */ 416 | jsmake.Sys = { 417 | /** 418 | * Returns if OS is Windows 419 | * @returns true if running on Windows 420 | */ 421 | isWindowsOs: function () { 422 | return jsmake.Fs.getPathSeparator() === '\\'; 423 | }, 424 | runCommand: function (command, opts) { 425 | return runCommand(command, opts); 426 | }, 427 | /** 428 | * Create a runner object, used to define and invoke an external program 429 | * @param {String} command the path of the command executable 430 | * @return {jsmake.CommandRunner} CommandRunner instance to fluently configure and run command 431 | * @see jsmake.CommandRunner 432 | * @example 433 | * // runs '/path/to/cmd.exe par1 par2 par3 par4' 434 | * jsmake.Sys.createRunner('/path/to/cmd.exe') 435 | * .args('par1', 'par2') 436 | * .args([ 'par3', 'par4' ]) 437 | * .run(); 438 | */ 439 | createRunner: function (command) { 440 | return new jsmake.CommandRunner(command); 441 | }, 442 | /** 443 | * Returns environment variable value 444 | * @param {String} name name of the environment variable 445 | * @param {String} [def] default value to return if environment variable not defined. 446 | * @returns {String} environment variable value if found, or default value. 447 | * @throws {Error} if environment variable is not found and no default value passed. 448 | */ 449 | getEnvVar: function (name, def) { 450 | var val = jsmake.Rhino.translateJavaString(java.lang.System.getenv(name)); 451 | return this._getEnvVar(name, val, def); 452 | }, 453 | /** 454 | * Log message to the console 455 | * @param {String} msg the message to log 456 | */ 457 | log: function (msg) { 458 | print(msg); 459 | }, 460 | _getEnvVar: function (name, val, def) { 461 | if (val !== null) { 462 | return val; 463 | } 464 | if (def !== undefined) { 465 | return def; 466 | } 467 | throw 'Environment variable "' + name + '" not defined.'; 468 | } 469 | }; 470 | 471 | /** @class Contains methods for working with filesystem */ 472 | jsmake.Fs = { 473 | /** 474 | * Create a zip file containing specified file/directory 475 | * @param {String} srcPath file/directory to zip 476 | * @param {String} destFile zip file name 477 | */ 478 | zipPath: function (srcPath, destFile) { 479 | jsmake.PathZipper.zip(srcPath, destFile); 480 | }, 481 | /** 482 | * Create a filesystem scanner 483 | * @param {String} basePath the path to scan for children tha match criteria 484 | * @returns {jsmake.FsScanner} FsScanner instance to fluently configure and run scanner 485 | * @see jsmake.FsScanner 486 | * @example 487 | * // returns all js and java files in \home folder, including subfolders, excluding .git folders 488 | * jsmake.Fs.createScanner('\home') 489 | * .include('**\*.js') 490 | * .include('**\*.java') 491 | * .exclude('**\.git') 492 | * .scan(); 493 | */ 494 | createScanner: function (basePath) { 495 | return new jsmake.FsScanner(basePath, this.isCaseSensitive()); 496 | }, 497 | /** 498 | * Return default OS character encoding 499 | * @returns {String} Character encoding, e.g. 'UTF-8' or 'Cp1252' 500 | */ 501 | getCharacterEncoding: function () { 502 | return java.lang.System.getProperty("file.encoding", "UTF-8"); // Windows default is "Cp1252" 503 | }, 504 | /** 505 | * Return OS path separator 506 | * @returns {String} path separator, e.g. '/' or '\' 507 | */ 508 | getPathSeparator: function () { 509 | return jsmake.Rhino.translateJavaString(java.io.File.separator); 510 | }, 511 | /** 512 | * Returns true if OS has case sensitive filesystem 513 | * @returns {Boolean} true if OS has case sensitive filesystem 514 | */ 515 | isCaseSensitive: function () { 516 | return !jsmake.Sys.isWindowsOs(); 517 | }, 518 | /** 519 | * Read text file content 520 | * @param {String} path path of the file to read 521 | * @param {String} [characterEncoding=OS default] 522 | * @returns {String} text content 523 | */ 524 | readFile: function (path, characterEncoding) { 525 | characterEncoding = characterEncoding || this.getCharacterEncoding(); 526 | if (!this.fileExists(path)) { 527 | throw "File '" + path + "' not found"; 528 | } 529 | return readFile(path, characterEncoding); 530 | }, 531 | /** 532 | * Write String to file, creating all necessary parent directories and overwriting if file already exists 533 | * @param {String} path path of the file to write 534 | * @param {String} content file content 535 | * @param {String} [characterEncoding=OS default] 536 | */ 537 | writeFile: function (path, content, characterEncoding) { 538 | characterEncoding = characterEncoding || this.getCharacterEncoding(); 539 | this.createDirectory(this.getParentDirectory(path)); 540 | var out = new java.io.FileOutputStream(new java.io.File(path)); 541 | content = new java.lang.String(content || ''); 542 | try { 543 | out.write(content.getBytes(characterEncoding)); 544 | } finally { 545 | out.close(); 546 | } 547 | }, 548 | /** 549 | * Extract last element from a path 550 | * @param {String} path the source path 551 | * @returns {String} the name of the last element in the path 552 | * @example 553 | * jsmake.Fs.getName('/users/gimmi/file.txt'); // returns 'file.txt' 554 | */ 555 | getName: function (path) { 556 | return jsmake.Rhino.translateJavaString(new java.io.File(path).getName()); 557 | }, 558 | /** 559 | * Copy file or directory to another directory 560 | * @param {String} srcPath source file/directory. Must exists 561 | * @param {String} destDirectory destination directory 562 | */ 563 | copyPath: function (srcPath, destDirectory) { 564 | if (this.fileExists(srcPath)) { 565 | this._copyFile(srcPath, destDirectory); 566 | } else if (this.directoryExists(srcPath)) { 567 | this._copyDirectory(srcPath, destDirectory); 568 | } else { 569 | throw "Cannot copy source path '" + srcPath + "', it does not exists"; 570 | } 571 | }, 572 | /** 573 | * @param {String} path file or directory path 574 | * @returns {Boolean} true if file or directory exists 575 | */ 576 | pathExists: function (path) { 577 | return new java.io.File(path).exists(); 578 | }, 579 | /** 580 | * @param {String} path directory path 581 | * @returns {Boolean} true if path exists and is a directory 582 | */ 583 | directoryExists: function (path) { 584 | var file = new java.io.File(path); 585 | return file.exists() && file.isDirectory(); 586 | }, 587 | /** 588 | * @param {String} path file path 589 | * @returns {Boolean} true if path exists and is a file 590 | */ 591 | fileExists: function (path) { 592 | var file = new java.io.File(path); 593 | return file.exists() && file.isFile(); 594 | }, 595 | /** 596 | * Create directory and all necessary parents 597 | * @param {String} path directory to create 598 | */ 599 | createDirectory: function (path) { 600 | var file = new java.io.File(path); 601 | if (file.exists() && file.isDirectory()) { 602 | return; 603 | } 604 | if (!file.mkdirs()) { 605 | throw "Failed to create directories for path '" + path + "'"; 606 | } 607 | }, 608 | /** 609 | * Delete file or directory, with all cild elements 610 | * @param {String} path to delete 611 | */ 612 | deletePath: function (path) { 613 | if (!this.pathExists(path)) { 614 | return; 615 | } 616 | jsmake.Utils.each(jsmake.Fs.getChildPathNames(path), function (name) { 617 | this.deletePath(this.combinePaths(path, name)); 618 | }, this); 619 | if (!new java.io.File(path)['delete']()) { 620 | throw "'Unable to delete path '" + path + "'"; 621 | } 622 | }, 623 | /** 624 | * Transform a path to absolute, removing '.' and '..' references 625 | * @param {String} path path to translate 626 | * @returns {String} path in canonical form 627 | * @example 628 | * jsmake.Fs.getCanonicalPath('../file.txt'); // returns '/users/file.txt' 629 | */ 630 | getCanonicalPath: function (path) { 631 | return jsmake.Rhino.translateJavaString(new java.io.File(path).getCanonicalPath()); 632 | }, 633 | /** 634 | * Returns parent path 635 | * @param {String} path 636 | * @returns {String} parent path 637 | */ 638 | getParentDirectory: function (path) { 639 | return jsmake.Rhino.translateJavaString(new java.io.File(path).getCanonicalFile().getParent()); 640 | }, 641 | /** 642 | * Combine all passed path fragments into one, using OS path separator. Supports any number of parameters. 643 | * @example 644 | * jsmake.Fs.combinePaths('home', 'gimmi', [ 'dir/subdir', 'file.txt' ]); 645 | * // returns 'home/gimmi/dir/subdir/file.txt' 646 | */ 647 | combinePaths: function () { 648 | var paths = jsmake.Utils.flatten(arguments); 649 | return jsmake.Utils.reduce(paths, function (memo, path) { 650 | return (memo ? this._javaCombine(memo, path) : path); 651 | }, null, this); 652 | }, 653 | getChildPathNames: function (basePath) { 654 | return this._listFilesWithFilter(basePath, function () { 655 | return true; 656 | }); 657 | }, 658 | getChildFileNames: function (basePath) { 659 | return this._listFilesWithFilter(basePath, function (fileName) { 660 | return new java.io.File(fileName).isFile(); 661 | }); 662 | }, 663 | getChildDirectoryNames: function (basePath) { 664 | return this._listFilesWithFilter(basePath, function (fileName) { 665 | return new java.io.File(fileName).isDirectory(); 666 | }); 667 | }, 668 | _javaCombine: function (path1, path2) { 669 | return jsmake.Rhino.translateJavaString(new java.io.File(path1, path2).getPath()); 670 | }, 671 | _copyDirectory: function (srcDirectory, destDirectory) { 672 | this.deletePath(destDirectory); 673 | this.createDirectory(destDirectory); 674 | jsmake.Utils.each(this.getChildFileNames(srcDirectory), function (path) { 675 | this.copyPath(this.combinePaths(srcDirectory, path), destDirectory); 676 | }, this); 677 | jsmake.Utils.each(this.getChildDirectoryNames(srcDirectory), function (path) { 678 | this.copyPath(this.combinePaths(srcDirectory, path), this.combinePaths(destDirectory, path)); 679 | }, this); 680 | }, 681 | _copyFile: function (srcFile, destDirectory) { 682 | var destFile = this.combinePaths(destDirectory, this.getName(srcFile)); 683 | this.deletePath(destFile); 684 | this.createDirectory(destDirectory); 685 | this._copyFileToFile(srcFile, destFile); 686 | }, 687 | _copyFileToFile: function (srcFile, destFile) { 688 | var input, output, buffer, n; 689 | input = new java.io.FileInputStream(srcFile); 690 | try { 691 | output = new java.io.FileOutputStream(destFile); 692 | try { 693 | buffer = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, 1024 * 4); 694 | while (-1 !== (n = input.read(buffer))) { 695 | output.write(buffer, 0, n); 696 | } 697 | } finally { 698 | output.close(); 699 | } 700 | } finally { 701 | input.close(); 702 | } 703 | }, 704 | _listFilesWithFilter: function (basePath, filter) { 705 | var fileFilter, files; 706 | fileFilter = new java.io.FileFilter({ accept: filter }); 707 | files = this._translateJavaArray(new java.io.File(basePath).listFiles(fileFilter)); 708 | return jsmake.Utils.map(files, function (file) { 709 | return jsmake.Rhino.translateJavaString(file.getName()); 710 | }, this); 711 | }, 712 | _translateJavaArray: function (javaArray) { 713 | var ary = [], i; 714 | if (javaArray === null) { 715 | return null; 716 | } 717 | for (i = 0; i < javaArray.length; i += 1) { 718 | ary.push(javaArray[i]); 719 | } 720 | return ary; 721 | } 722 | }; 723 | /** 724 | * Don't instantiate it directly, use {@link jsmake.Fs.createScanner} 725 | * @constructor 726 | */ 727 | jsmake.FsScanner = function (basePath, caseSensitive) { 728 | this._basePath = basePath; 729 | this._includeMatchers = []; 730 | this._excludeMatchers = []; 731 | this._caseSensitive = caseSensitive; 732 | }; 733 | jsmake.FsScanner.prototype = { 734 | /** 735 | * Add a criteria for path inclusion. If no inclusion path are specified, '**\*' is assumed 736 | * @param {String} pattern 737 | * @returns {jsmake.FsScanner} this instance, for chaining calls 738 | * @example 739 | * jsmake.Fs.createScanner('\home').include('**\*.js').scan(); 740 | */ 741 | include: function (pattern) { 742 | this._includeMatchers.push(new jsmake.AntPathMatcher(pattern, this._caseSensitive)); 743 | return this; 744 | }, 745 | /** 746 | * Add a criteria for path exclusion 747 | * @param {String} pattern 748 | * @returns {jsmake.FsScanner} this instance, for chaining calls 749 | * @example 750 | * jsmake.Fs.createScanner('\home').exclude('**\.git').scan(); 751 | */ 752 | exclude: function (pattern) { 753 | this._excludeMatchers.push(new jsmake.AntPathMatcher(pattern, this._caseSensitive)); 754 | return this; 755 | }, 756 | /** 757 | * Execute filesystem scanning with defined criterias 758 | * @returns {String[]} all mathing paths 759 | * @example 760 | * // returns the path of all files in /home directory 761 | * jsmake.Fs.createScanner('/home').scan(); 762 | */ 763 | scan: function () { 764 | var fileNames = []; 765 | if (this._includeMatchers.length === 0) { 766 | this.include('**/*'); 767 | } 768 | this._scan('.', fileNames); 769 | return fileNames; 770 | }, 771 | _scan: function (relativePath, fileNames) { 772 | var fullPath = jsmake.Fs.combinePaths(this._basePath, relativePath); 773 | jsmake.Utils.each(jsmake.Fs.getChildFileNames(fullPath), function (fileName) { 774 | fileName = jsmake.Fs.combinePaths(relativePath, fileName); 775 | if (this._evaluatePath(fileName, false)) { 776 | fileNames.push(jsmake.Fs.combinePaths(this._basePath, fileName)); 777 | } 778 | }, this); 779 | jsmake.Utils.each(jsmake.Fs.getChildDirectoryNames(fullPath), function (dir) { 780 | dir = jsmake.Fs.combinePaths(relativePath, dir); 781 | if (this._evaluatePath(dir, true)) { 782 | this._scan(dir, fileNames); 783 | } 784 | }, this); 785 | }, 786 | _evaluatePath: function (path, def) { 787 | if (this._runMatchers(this._excludeMatchers, path)) { 788 | return false; 789 | } 790 | if (this._runMatchers(this._includeMatchers, path)) { 791 | return true; 792 | } 793 | return def; 794 | }, 795 | _runMatchers: function (matchers, value) { 796 | var match = false; 797 | jsmake.Utils.each(matchers, function (matcher) { 798 | match = match || matcher.match(value); 799 | }, this); 800 | return match; 801 | } 802 | }; 803 | 804 | /** 805 | * Don't instantiate it directly, use {@link jsmake.Sys.createRunner} 806 | * @constructor 807 | */ 808 | jsmake.CommandRunner = function (command) { 809 | this._command = command; 810 | this._arguments = []; 811 | this._logger = jsmake.Sys; 812 | }; 813 | jsmake.CommandRunner.prototype = { 814 | /** 815 | * Add all passed arguments. Supports any number of parameters. 816 | * @returns {jsmake.CommandRunner} this instance, for chaining calls 817 | * @example 818 | * jsmake.Sys.createRunner('cmd.exe').args('par1', 'par2', [ 'par3', 'par4' ]).run(); 819 | */ 820 | args: function () { 821 | this._arguments = this._arguments.concat(jsmake.Utils.flatten(arguments)); 822 | return this; 823 | }, 824 | /** 825 | * Run configured command. if exitstatus of the command is 0 then execution is considered succesful, otherwise an exception is thrown 826 | */ 827 | run: function () { 828 | this._logger.log(this._command + ' ' + this._arguments.join(' ')); 829 | var exitStatus = jsmake.Sys.runCommand(this._command, { args: this._arguments }); 830 | if (exitStatus !== 0) { 831 | throw 'Command failed with exit status ' + exitStatus; 832 | } 833 | } 834 | }; 835 | jsmake.PathZipper = { 836 | zip: function (srcPath, destFile) { 837 | var zipOutputStream = new java.util.zip.ZipOutputStream(new java.io.FileOutputStream(destFile)); 838 | try { 839 | this._zip(jsmake.Fs.getParentDirectory(srcPath), jsmake.Fs.getName(srcPath), zipOutputStream); 840 | } finally { 841 | zipOutputStream.close(); // This raise exception "java.util.zip.ZipException: ZIP file must have at least one entry" 842 | } 843 | }, 844 | _zip: function (basePath, relativePath, zipOutputStream) { 845 | var names, path; 846 | path = jsmake.Fs.combinePaths(basePath, relativePath); 847 | if (jsmake.Fs.fileExists(path)) { 848 | this._addFile(basePath, relativePath, zipOutputStream); 849 | } else if (jsmake.Fs.directoryExists(path)) { 850 | jsmake.Utils.each(jsmake.Fs.getChildPathNames(path), function (name) { 851 | this._zip(basePath, jsmake.Fs.combinePaths(relativePath, name), zipOutputStream); 852 | }, this); 853 | } else { 854 | throw "Cannot zip source path '" + path + "', it does not exists"; 855 | } 856 | }, 857 | _addFile: function (basePath, relativePath, zipOutputStream) { 858 | var fileInputStream, buffer, n; 859 | zipOutputStream.putNextEntry(new java.util.zip.ZipEntry(relativePath)); 860 | buffer = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, 1024 * 4); 861 | fileInputStream = new java.io.FileInputStream(jsmake.Fs.combinePaths(basePath, relativePath)); 862 | try { 863 | while (-1 !== (n = fileInputStream.read(buffer))) { 864 | zipOutputStream.write(buffer, 0, n); 865 | } 866 | } finally { 867 | fileInputStream.close(); 868 | } 869 | zipOutputStream.closeEntry(); 870 | } 871 | }; 872 | /** @class Various helper methods for manipulating XML files */ 873 | jsmake.Xml = { 874 | /** 875 | * Search nodes that match XPath in XML file. 876 | * @param {String} file XML file path 877 | * @param {String} xpath XPath query to search for 878 | * @returns {String[]} an array of values of matching nodes 879 | * @example 880 | * var values = jsmake.Xml.getValues('temp/file.xml', '//series/season/episode/text()'); 881 | */ 882 | getValues: function (file, xpath) { 883 | var i, ret = [], nodeList; 884 | nodeList = this._getNodeList(this._loadDocument(file), xpath); 885 | for (i = 0; i < nodeList.getLength(); i += 1) { 886 | ret.push(jsmake.Rhino.translateJavaString(nodeList.item(i).getNodeValue())); 887 | } 888 | return ret; 889 | }, 890 | /** 891 | * Like {@link jsmake.Xml.getValues}, but expect a single match, throwing exception otherwise. 892 | * @param {String} file XML file path 893 | * @param {String} xpath XPath query to search for 894 | * @returns {String} value of matching node 895 | * @example 896 | * var episode = jsmake.Xml.getValue('temp/file.xml', '//series/season[@id="1"]/episode/text()'); 897 | */ 898 | getValue: function (file, xpath) { 899 | var values = this.getValues(file, xpath); 900 | if (values.length !== 1) { 901 | throw "Unable to find a single element for xpath '" + xpath + "' in file '" + file + "'"; 902 | } 903 | return values[0]; 904 | }, 905 | /** 906 | * Set value of matching node in XML file. throw exception if multiple nodes match XPath. 907 | * @param {String} file XML file path 908 | * @param {String} xpath XPath query to search for 909 | * @param {String} value value to set 910 | * @example 911 | * jsmake.Xml.setValue('temp/file.xml', '//series/season[@id="1"]/episode', 'new episode value'); 912 | */ 913 | setValue: function (file, xpath, value) { 914 | var nodeList, document; 915 | document = this._loadDocument(file); 916 | nodeList = this._getNodeList(document, xpath); 917 | if (nodeList.getLength() !== 1) { 918 | throw "Unable to find a single element for xpath '" + xpath + "' in file '" + file + "'"; 919 | } 920 | nodeList.item(0).setTextContent(value); 921 | this._saveDocument(document, file); 922 | }, 923 | _getNodeList: function (document, xpath) { 924 | return javax.xml.xpath.XPathFactory.newInstance().newXPath().evaluate(xpath, document, javax.xml.xpath.XPathConstants.NODESET); 925 | }, 926 | _loadDocument: function (file) { 927 | var documentBuilderFactory, document; 928 | documentBuilderFactory = javax.xml.parsers.DocumentBuilderFactory.newInstance(); 929 | documentBuilderFactory.setNamespaceAware(true); 930 | document = documentBuilderFactory.newDocumentBuilder().parse(file); 931 | return document; 932 | }, 933 | _saveDocument: function (document, file) { 934 | var transformer; 935 | transformer = javax.xml.transform.TransformerFactory.newInstance().newTransformer(); 936 | transformer.transform(new javax.xml.transform.dom.DOMSource(document), new javax.xml.transform.stream.StreamResult(new java.io.File(file))); 937 | } 938 | }; 939 | 940 | jsmake.Main = function () { 941 | this._project = null; 942 | this._logger = jsmake.Sys; 943 | }; 944 | jsmake.Main.prototype = { 945 | init: function (global) { 946 | this._project = new jsmake.Project(this._logger); 947 | global.task = this._bind(this._task, this); 948 | }, 949 | runTask: function (name, args) { 950 | this._project.runTask(name, args); 951 | }, 952 | // TODO document it with JSDoc 953 | _task: function () { 954 | var args = this._getTaskParameters(jsmake.Utils.toArray(arguments)); 955 | this._project.addTask(new jsmake.Task(args[0], args[1], args[2], this._logger)); 956 | }, 957 | _getTaskParameters: function (args) { 958 | return [ 959 | args.shift(), 960 | jsmake.Utils.isFunction(args[0]) ? [] : jsmake.Utils.toArray(args.shift()), 961 | args.shift() || jsmake.Utils.EMPTY_FN 962 | ]; 963 | }, 964 | _bind: function (fn, scope) { 965 | return function () { 966 | fn.apply(scope, arguments); 967 | }; 968 | } 969 | }; 970 | -------------------------------------------------------------------------------- /tools/uglifyjs/lib/parse-js.js: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | 3 | A JavaScript tokenizer / parser / beautifier / compressor. 4 | 5 | This version is suitable for Node.js. With minimal changes (the 6 | exports stuff) it should work on any JS platform. 7 | 8 | This file contains the tokenizer/parser. It is a port to JavaScript 9 | of parse-js [1], a JavaScript parser library written in Common Lisp 10 | by Marijn Haverbeke. Thank you Marijn! 11 | 12 | [1] http://marijn.haverbeke.nl/parse-js/ 13 | 14 | Exported functions: 15 | 16 | - tokenizer(code) -- returns a function. Call the returned 17 | function to fetch the next token. 18 | 19 | - parse(code) -- returns an AST of the given JavaScript code. 20 | 21 | -------------------------------- (C) --------------------------------- 22 | 23 | Author: Mihai Bazon 24 | 25 | http://mihai.bazon.net/blog 26 | 27 | Distributed under the BSD license: 28 | 29 | Copyright 2010 (c) Mihai Bazon 30 | Based on parse-js (http://marijn.haverbeke.nl/parse-js/). 31 | 32 | Redistribution and use in source and binary forms, with or without 33 | modification, are permitted provided that the following conditions 34 | are met: 35 | 36 | * Redistributions of source code must retain the above 37 | copyright notice, this list of conditions and the following 38 | disclaimer. 39 | 40 | * Redistributions in binary form must reproduce the above 41 | copyright notice, this list of conditions and the following 42 | disclaimer in the documentation and/or other materials 43 | provided with the distribution. 44 | 45 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY 46 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 47 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 48 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE 49 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 50 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 51 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 52 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 53 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 54 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 55 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 56 | SUCH DAMAGE. 57 | 58 | ***********************************************************************/ 59 | 60 | /* -----[ Tokenizer (constants) ]----- */ 61 | 62 | var KEYWORDS = array_to_hash([ 63 | "break", 64 | "case", 65 | "catch", 66 | "const", 67 | "continue", 68 | "debugger", 69 | "default", 70 | "delete", 71 | "do", 72 | "else", 73 | "finally", 74 | "for", 75 | "function", 76 | "if", 77 | "in", 78 | "instanceof", 79 | "new", 80 | "return", 81 | "switch", 82 | "throw", 83 | "try", 84 | "typeof", 85 | "var", 86 | "void", 87 | "while", 88 | "with" 89 | ]); 90 | 91 | var RESERVED_WORDS = array_to_hash([ 92 | "abstract", 93 | "boolean", 94 | "byte", 95 | "char", 96 | "class", 97 | "double", 98 | "enum", 99 | "export", 100 | "extends", 101 | "final", 102 | "float", 103 | "goto", 104 | "implements", 105 | "import", 106 | "int", 107 | "interface", 108 | "long", 109 | "native", 110 | "package", 111 | "private", 112 | "protected", 113 | "public", 114 | "short", 115 | "static", 116 | "super", 117 | "synchronized", 118 | "throws", 119 | "transient", 120 | "volatile" 121 | ]); 122 | 123 | var KEYWORDS_BEFORE_EXPRESSION = array_to_hash([ 124 | "return", 125 | "new", 126 | "delete", 127 | "throw", 128 | "else", 129 | "case" 130 | ]); 131 | 132 | var KEYWORDS_ATOM = array_to_hash([ 133 | "false", 134 | "null", 135 | "true", 136 | "undefined" 137 | ]); 138 | 139 | var OPERATOR_CHARS = array_to_hash(characters("+-*&%=<>!?|~^")); 140 | 141 | var RE_HEX_NUMBER = /^0x[0-9a-f]+$/i; 142 | var RE_OCT_NUMBER = /^0[0-7]+$/; 143 | var RE_DEC_NUMBER = /^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i; 144 | 145 | var OPERATORS = array_to_hash([ 146 | "in", 147 | "instanceof", 148 | "typeof", 149 | "new", 150 | "void", 151 | "delete", 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 | ">>=", 183 | "<<=", 184 | ">>>=", 185 | "|=", 186 | "^=", 187 | "&=", 188 | "&&", 189 | "||" 190 | ]); 191 | 192 | var WHITESPACE_CHARS = array_to_hash(characters(" \u00a0\n\r\t\f\u000b\u200b\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000")); 193 | 194 | var PUNC_BEFORE_EXPRESSION = array_to_hash(characters("[{}(,.;:")); 195 | 196 | var PUNC_CHARS = array_to_hash(characters("[]{}(),;:")); 197 | 198 | var REGEXP_MODIFIERS = array_to_hash(characters("gmsiy")); 199 | 200 | /* -----[ Tokenizer ]----- */ 201 | 202 | // regexps adapted from http://xregexp.com/plugins/#unicode 203 | var UNICODE = { 204 | letter: new RegExp("[\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0523\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0621-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971\\u0972\\u097B-\\u097F\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D28\\u0D2A-\\u0D39\\u0D3D\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC\\u0EDD\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8B\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10D0-\\u10FA\\u10FC\\u1100-\\u1159\\u115F-\\u11A2\\u11A8-\\u11F9\\u1200-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u1676\\u1681-\\u169A\\u16A0-\\u16EA\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19A9\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u2094\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183\\u2184\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2C6F\\u2C71-\\u2C7D\\u2C80-\\u2CE4\\u2D00-\\u2D25\\u2D30-\\u2D65\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005\\u3006\\u3031-\\u3035\\u303B\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31B7\\u31F0-\\u31FF\\u3400\\u4DB5\\u4E00\\u9FC3\\uA000-\\uA48C\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA65F\\uA662-\\uA66E\\uA67F-\\uA697\\uA717-\\uA71F\\uA722-\\uA788\\uA78B\\uA78C\\uA7FB-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA90A-\\uA925\\uA930-\\uA946\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAC00\\uD7A3\\uF900-\\uFA2D\\uFA30-\\uFA6A\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC]"), 205 | non_spacing_mark: new RegExp("[\\u0300-\\u036F\\u0483-\\u0487\\u0591-\\u05BD\\u05BF\\u05C1\\u05C2\\u05C4\\u05C5\\u05C7\\u0610-\\u061A\\u064B-\\u065E\\u0670\\u06D6-\\u06DC\\u06DF-\\u06E4\\u06E7\\u06E8\\u06EA-\\u06ED\\u0711\\u0730-\\u074A\\u07A6-\\u07B0\\u07EB-\\u07F3\\u0816-\\u0819\\u081B-\\u0823\\u0825-\\u0827\\u0829-\\u082D\\u0900-\\u0902\\u093C\\u0941-\\u0948\\u094D\\u0951-\\u0955\\u0962\\u0963\\u0981\\u09BC\\u09C1-\\u09C4\\u09CD\\u09E2\\u09E3\\u0A01\\u0A02\\u0A3C\\u0A41\\u0A42\\u0A47\\u0A48\\u0A4B-\\u0A4D\\u0A51\\u0A70\\u0A71\\u0A75\\u0A81\\u0A82\\u0ABC\\u0AC1-\\u0AC5\\u0AC7\\u0AC8\\u0ACD\\u0AE2\\u0AE3\\u0B01\\u0B3C\\u0B3F\\u0B41-\\u0B44\\u0B4D\\u0B56\\u0B62\\u0B63\\u0B82\\u0BC0\\u0BCD\\u0C3E-\\u0C40\\u0C46-\\u0C48\\u0C4A-\\u0C4D\\u0C55\\u0C56\\u0C62\\u0C63\\u0CBC\\u0CBF\\u0CC6\\u0CCC\\u0CCD\\u0CE2\\u0CE3\\u0D41-\\u0D44\\u0D4D\\u0D62\\u0D63\\u0DCA\\u0DD2-\\u0DD4\\u0DD6\\u0E31\\u0E34-\\u0E3A\\u0E47-\\u0E4E\\u0EB1\\u0EB4-\\u0EB9\\u0EBB\\u0EBC\\u0EC8-\\u0ECD\\u0F18\\u0F19\\u0F35\\u0F37\\u0F39\\u0F71-\\u0F7E\\u0F80-\\u0F84\\u0F86\\u0F87\\u0F90-\\u0F97\\u0F99-\\u0FBC\\u0FC6\\u102D-\\u1030\\u1032-\\u1037\\u1039\\u103A\\u103D\\u103E\\u1058\\u1059\\u105E-\\u1060\\u1071-\\u1074\\u1082\\u1085\\u1086\\u108D\\u109D\\u135F\\u1712-\\u1714\\u1732-\\u1734\\u1752\\u1753\\u1772\\u1773\\u17B7-\\u17BD\\u17C6\\u17C9-\\u17D3\\u17DD\\u180B-\\u180D\\u18A9\\u1920-\\u1922\\u1927\\u1928\\u1932\\u1939-\\u193B\\u1A17\\u1A18\\u1A56\\u1A58-\\u1A5E\\u1A60\\u1A62\\u1A65-\\u1A6C\\u1A73-\\u1A7C\\u1A7F\\u1B00-\\u1B03\\u1B34\\u1B36-\\u1B3A\\u1B3C\\u1B42\\u1B6B-\\u1B73\\u1B80\\u1B81\\u1BA2-\\u1BA5\\u1BA8\\u1BA9\\u1C2C-\\u1C33\\u1C36\\u1C37\\u1CD0-\\u1CD2\\u1CD4-\\u1CE0\\u1CE2-\\u1CE8\\u1CED\\u1DC0-\\u1DE6\\u1DFD-\\u1DFF\\u20D0-\\u20DC\\u20E1\\u20E5-\\u20F0\\u2CEF-\\u2CF1\\u2DE0-\\u2DFF\\u302A-\\u302F\\u3099\\u309A\\uA66F\\uA67C\\uA67D\\uA6F0\\uA6F1\\uA802\\uA806\\uA80B\\uA825\\uA826\\uA8C4\\uA8E0-\\uA8F1\\uA926-\\uA92D\\uA947-\\uA951\\uA980-\\uA982\\uA9B3\\uA9B6-\\uA9B9\\uA9BC\\uAA29-\\uAA2E\\uAA31\\uAA32\\uAA35\\uAA36\\uAA43\\uAA4C\\uAAB0\\uAAB2-\\uAAB4\\uAAB7\\uAAB8\\uAABE\\uAABF\\uAAC1\\uABE5\\uABE8\\uABED\\uFB1E\\uFE00-\\uFE0F\\uFE20-\\uFE26]"), 206 | space_combining_mark: new RegExp("[\\u0903\\u093E-\\u0940\\u0949-\\u094C\\u094E\\u0982\\u0983\\u09BE-\\u09C0\\u09C7\\u09C8\\u09CB\\u09CC\\u09D7\\u0A03\\u0A3E-\\u0A40\\u0A83\\u0ABE-\\u0AC0\\u0AC9\\u0ACB\\u0ACC\\u0B02\\u0B03\\u0B3E\\u0B40\\u0B47\\u0B48\\u0B4B\\u0B4C\\u0B57\\u0BBE\\u0BBF\\u0BC1\\u0BC2\\u0BC6-\\u0BC8\\u0BCA-\\u0BCC\\u0BD7\\u0C01-\\u0C03\\u0C41-\\u0C44\\u0C82\\u0C83\\u0CBE\\u0CC0-\\u0CC4\\u0CC7\\u0CC8\\u0CCA\\u0CCB\\u0CD5\\u0CD6\\u0D02\\u0D03\\u0D3E-\\u0D40\\u0D46-\\u0D48\\u0D4A-\\u0D4C\\u0D57\\u0D82\\u0D83\\u0DCF-\\u0DD1\\u0DD8-\\u0DDF\\u0DF2\\u0DF3\\u0F3E\\u0F3F\\u0F7F\\u102B\\u102C\\u1031\\u1038\\u103B\\u103C\\u1056\\u1057\\u1062-\\u1064\\u1067-\\u106D\\u1083\\u1084\\u1087-\\u108C\\u108F\\u109A-\\u109C\\u17B6\\u17BE-\\u17C5\\u17C7\\u17C8\\u1923-\\u1926\\u1929-\\u192B\\u1930\\u1931\\u1933-\\u1938\\u19B0-\\u19C0\\u19C8\\u19C9\\u1A19-\\u1A1B\\u1A55\\u1A57\\u1A61\\u1A63\\u1A64\\u1A6D-\\u1A72\\u1B04\\u1B35\\u1B3B\\u1B3D-\\u1B41\\u1B43\\u1B44\\u1B82\\u1BA1\\u1BA6\\u1BA7\\u1BAA\\u1C24-\\u1C2B\\u1C34\\u1C35\\u1CE1\\u1CF2\\uA823\\uA824\\uA827\\uA880\\uA881\\uA8B4-\\uA8C3\\uA952\\uA953\\uA983\\uA9B4\\uA9B5\\uA9BA\\uA9BB\\uA9BD-\\uA9C0\\uAA2F\\uAA30\\uAA33\\uAA34\\uAA4D\\uAA7B\\uABE3\\uABE4\\uABE6\\uABE7\\uABE9\\uABEA\\uABEC]"), 207 | connector_punctuation: new RegExp("[\\u005F\\u203F\\u2040\\u2054\\uFE33\\uFE34\\uFE4D-\\uFE4F\\uFF3F]") 208 | }; 209 | 210 | function is_letter(ch) { 211 | return UNICODE.letter.test(ch); 212 | }; 213 | 214 | function is_digit(ch) { 215 | ch = ch.charCodeAt(0); 216 | return ch >= 48 && ch <= 57; //XXX: find out if "UnicodeDigit" means something else than 0..9 217 | }; 218 | 219 | function is_alphanumeric_char(ch) { 220 | return is_digit(ch) || is_letter(ch); 221 | }; 222 | 223 | function is_unicode_combining_mark(ch) { 224 | return UNICODE.non_spacing_mark.test(ch) || UNICODE.space_combining_mark.test(ch); 225 | }; 226 | 227 | function is_unicode_connector_punctuation(ch) { 228 | return UNICODE.connector_punctuation.test(ch); 229 | }; 230 | 231 | function is_identifier_start(ch) { 232 | return ch == "$" || ch == "_" || is_letter(ch); 233 | }; 234 | 235 | function is_identifier_char(ch) { 236 | return is_identifier_start(ch) 237 | || is_unicode_combining_mark(ch) 238 | || is_digit(ch) 239 | || is_unicode_connector_punctuation(ch) 240 | || ch == "\u200c" // zero-width non-joiner 241 | || ch == "\u200d" // zero-width joiner (in my ECMA-262 PDF, this is also 200c) 242 | ; 243 | }; 244 | 245 | function parse_js_number(num) { 246 | if (RE_HEX_NUMBER.test(num)) { 247 | return parseInt(num.substr(2), 16); 248 | } else if (RE_OCT_NUMBER.test(num)) { 249 | return parseInt(num.substr(1), 8); 250 | } else if (RE_DEC_NUMBER.test(num)) { 251 | return parseFloat(num); 252 | } 253 | }; 254 | 255 | function JS_Parse_Error(message, line, col, pos) { 256 | this.message = message; 257 | this.line = line + 1; 258 | this.col = col + 1; 259 | this.pos = pos + 1; 260 | this.stack = new Error().stack; 261 | }; 262 | 263 | JS_Parse_Error.prototype.toString = function() { 264 | return this.message + " (line: " + this.line + ", col: " + this.col + ", pos: " + this.pos + ")" + "\n\n" + this.stack; 265 | }; 266 | 267 | function js_error(message, line, col, pos) { 268 | throw new JS_Parse_Error(message, line, col, pos); 269 | }; 270 | 271 | function is_token(token, type, val) { 272 | return token.type == type && (val == null || token.value == val); 273 | }; 274 | 275 | var EX_EOF = {}; 276 | 277 | function tokenizer($TEXT) { 278 | 279 | var S = { 280 | text : $TEXT.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, ''), 281 | pos : 0, 282 | tokpos : 0, 283 | line : 0, 284 | tokline : 0, 285 | col : 0, 286 | tokcol : 0, 287 | newline_before : false, 288 | regex_allowed : false, 289 | comments_before : [] 290 | }; 291 | 292 | function peek() { return S.text.charAt(S.pos); }; 293 | 294 | function next(signal_eof, in_string) { 295 | var ch = S.text.charAt(S.pos++); 296 | if (signal_eof && !ch) 297 | throw EX_EOF; 298 | if (ch == "\n") { 299 | S.newline_before = S.newline_before || !in_string; 300 | ++S.line; 301 | S.col = 0; 302 | } else { 303 | ++S.col; 304 | } 305 | return ch; 306 | }; 307 | 308 | function eof() { 309 | return !S.peek(); 310 | }; 311 | 312 | function find(what, signal_eof) { 313 | var pos = S.text.indexOf(what, S.pos); 314 | if (signal_eof && pos == -1) throw EX_EOF; 315 | return pos; 316 | }; 317 | 318 | function start_token() { 319 | S.tokline = S.line; 320 | S.tokcol = S.col; 321 | S.tokpos = S.pos; 322 | }; 323 | 324 | function token(type, value, is_comment) { 325 | S.regex_allowed = ((type == "operator" && !HOP(UNARY_POSTFIX, value)) || 326 | (type == "keyword" && HOP(KEYWORDS_BEFORE_EXPRESSION, value)) || 327 | (type == "punc" && HOP(PUNC_BEFORE_EXPRESSION, value))); 328 | var ret = { 329 | type : type, 330 | value : value, 331 | line : S.tokline, 332 | col : S.tokcol, 333 | pos : S.tokpos, 334 | endpos : S.pos, 335 | nlb : S.newline_before 336 | }; 337 | if (!is_comment) { 338 | ret.comments_before = S.comments_before; 339 | S.comments_before = []; 340 | } 341 | S.newline_before = false; 342 | return ret; 343 | }; 344 | 345 | function skip_whitespace() { 346 | while (HOP(WHITESPACE_CHARS, peek())) 347 | next(); 348 | }; 349 | 350 | function read_while(pred) { 351 | var ret = "", ch = peek(), i = 0; 352 | while (ch && pred(ch, i++)) { 353 | ret += next(); 354 | ch = peek(); 355 | } 356 | return ret; 357 | }; 358 | 359 | function parse_error(err) { 360 | js_error(err, S.tokline, S.tokcol, S.tokpos); 361 | }; 362 | 363 | function read_num(prefix) { 364 | var has_e = false, after_e = false, has_x = false, has_dot = prefix == "."; 365 | var num = read_while(function(ch, i){ 366 | if (ch == "x" || ch == "X") { 367 | if (has_x) return false; 368 | return has_x = true; 369 | } 370 | if (!has_x && (ch == "E" || ch == "e")) { 371 | if (has_e) return false; 372 | return has_e = after_e = true; 373 | } 374 | if (ch == "-") { 375 | if (after_e || (i == 0 && !prefix)) return true; 376 | return false; 377 | } 378 | if (ch == "+") return after_e; 379 | after_e = false; 380 | if (ch == ".") { 381 | if (!has_dot && !has_x) 382 | return has_dot = true; 383 | return false; 384 | } 385 | return is_alphanumeric_char(ch); 386 | }); 387 | if (prefix) 388 | num = prefix + num; 389 | var valid = parse_js_number(num); 390 | if (!isNaN(valid)) { 391 | return token("num", valid); 392 | } else { 393 | parse_error("Invalid syntax: " + num); 394 | } 395 | }; 396 | 397 | function read_escaped_char(in_string) { 398 | var ch = next(true, in_string); 399 | switch (ch) { 400 | case "n" : return "\n"; 401 | case "r" : return "\r"; 402 | case "t" : return "\t"; 403 | case "b" : return "\b"; 404 | case "v" : return "\u000b"; 405 | case "f" : return "\f"; 406 | case "0" : return "\0"; 407 | case "x" : return String.fromCharCode(hex_bytes(2)); 408 | case "u" : return String.fromCharCode(hex_bytes(4)); 409 | case "\n": return ""; 410 | default : return ch; 411 | } 412 | }; 413 | 414 | function hex_bytes(n) { 415 | var num = 0; 416 | for (; n > 0; --n) { 417 | var digit = parseInt(next(true), 16); 418 | if (isNaN(digit)) 419 | parse_error("Invalid hex-character pattern in string"); 420 | num = (num << 4) | digit; 421 | } 422 | return num; 423 | }; 424 | 425 | function read_string() { 426 | return with_eof_error("Unterminated string constant", function(){ 427 | var quote = next(), ret = ""; 428 | for (;;) { 429 | var ch = next(true); 430 | if (ch == "\\") { 431 | // read OctalEscapeSequence (XXX: deprecated if "strict mode") 432 | // https://github.com/mishoo/UglifyJS/issues/178 433 | var octal_len = 0, first = null; 434 | ch = read_while(function(ch){ 435 | if (ch >= "0" && ch <= "7") { 436 | if (!first) { 437 | first = ch; 438 | return ++octal_len; 439 | } 440 | else if (first <= "3" && octal_len <= 2) return ++octal_len; 441 | else if (first >= "4" && octal_len <= 1) return ++octal_len; 442 | } 443 | return false; 444 | }); 445 | if (octal_len > 0) ch = String.fromCharCode(parseInt(ch, 8)); 446 | else ch = read_escaped_char(true); 447 | } 448 | else if (ch == quote) break; 449 | ret += ch; 450 | } 451 | return token("string", ret); 452 | }); 453 | }; 454 | 455 | function read_line_comment() { 456 | next(); 457 | var i = find("\n"), ret; 458 | if (i == -1) { 459 | ret = S.text.substr(S.pos); 460 | S.pos = S.text.length; 461 | } else { 462 | ret = S.text.substring(S.pos, i); 463 | S.pos = i; 464 | } 465 | return token("comment1", ret, true); 466 | }; 467 | 468 | function read_multiline_comment() { 469 | next(); 470 | return with_eof_error("Unterminated multiline comment", function(){ 471 | var i = find("*/", true), 472 | text = S.text.substring(S.pos, i); 473 | S.pos = i + 2; 474 | S.line += text.split("\n").length - 1; 475 | S.newline_before = text.indexOf("\n") >= 0; 476 | 477 | // https://github.com/mishoo/UglifyJS/issues/#issue/100 478 | if (/^@cc_on/i.test(text)) { 479 | warn("WARNING: at line " + S.line); 480 | warn("*** Found \"conditional comment\": " + text); 481 | warn("*** UglifyJS DISCARDS ALL COMMENTS. This means your code might no longer work properly in Internet Explorer."); 482 | } 483 | 484 | return token("comment2", text, true); 485 | }); 486 | }; 487 | 488 | function read_name() { 489 | var backslash = false, name = "", ch; 490 | while ((ch = peek()) != null) { 491 | if (!backslash) { 492 | if (ch == "\\") backslash = true, next(); 493 | else if (is_identifier_char(ch)) name += next(); 494 | else break; 495 | } 496 | else { 497 | if (ch != "u") parse_error("Expecting UnicodeEscapeSequence -- uXXXX"); 498 | ch = read_escaped_char(); 499 | if (!is_identifier_char(ch)) parse_error("Unicode char: " + ch.charCodeAt(0) + " is not valid in identifier"); 500 | name += ch; 501 | backslash = false; 502 | } 503 | } 504 | return name; 505 | }; 506 | 507 | function read_regexp(regexp) { 508 | return with_eof_error("Unterminated regular expression", function(){ 509 | var prev_backslash = false, ch, in_class = false; 510 | while ((ch = next(true))) if (prev_backslash) { 511 | regexp += "\\" + ch; 512 | prev_backslash = false; 513 | } else if (ch == "[") { 514 | in_class = true; 515 | regexp += ch; 516 | } else if (ch == "]" && in_class) { 517 | in_class = false; 518 | regexp += ch; 519 | } else if (ch == "/" && !in_class) { 520 | break; 521 | } else if (ch == "\\") { 522 | prev_backslash = true; 523 | } else { 524 | regexp += ch; 525 | } 526 | var mods = read_name(); 527 | return token("regexp", [ regexp, mods ]); 528 | }); 529 | }; 530 | 531 | function read_operator(prefix) { 532 | function grow(op) { 533 | if (!peek()) return op; 534 | var bigger = op + peek(); 535 | if (HOP(OPERATORS, bigger)) { 536 | next(); 537 | return grow(bigger); 538 | } else { 539 | return op; 540 | } 541 | }; 542 | return token("operator", grow(prefix || next())); 543 | }; 544 | 545 | function handle_slash() { 546 | next(); 547 | var regex_allowed = S.regex_allowed; 548 | switch (peek()) { 549 | case "/": 550 | S.comments_before.push(read_line_comment()); 551 | S.regex_allowed = regex_allowed; 552 | return next_token(); 553 | case "*": 554 | S.comments_before.push(read_multiline_comment()); 555 | S.regex_allowed = regex_allowed; 556 | return next_token(); 557 | } 558 | return S.regex_allowed ? read_regexp("") : read_operator("/"); 559 | }; 560 | 561 | function handle_dot() { 562 | next(); 563 | return is_digit(peek()) 564 | ? read_num(".") 565 | : token("punc", "."); 566 | }; 567 | 568 | function read_word() { 569 | var word = read_name(); 570 | return !HOP(KEYWORDS, word) 571 | ? token("name", word) 572 | : HOP(OPERATORS, word) 573 | ? token("operator", word) 574 | : HOP(KEYWORDS_ATOM, word) 575 | ? token("atom", word) 576 | : token("keyword", word); 577 | }; 578 | 579 | function with_eof_error(eof_error, cont) { 580 | try { 581 | return cont(); 582 | } catch(ex) { 583 | if (ex === EX_EOF) parse_error(eof_error); 584 | else throw ex; 585 | } 586 | }; 587 | 588 | function next_token(force_regexp) { 589 | if (force_regexp != null) 590 | return read_regexp(force_regexp); 591 | skip_whitespace(); 592 | start_token(); 593 | var ch = peek(); 594 | if (!ch) return token("eof"); 595 | if (is_digit(ch)) return read_num(); 596 | if (ch == '"' || ch == "'") return read_string(); 597 | if (HOP(PUNC_CHARS, ch)) return token("punc", next()); 598 | if (ch == ".") return handle_dot(); 599 | if (ch == "/") return handle_slash(); 600 | if (HOP(OPERATOR_CHARS, ch)) return read_operator(); 601 | if (ch == "\\" || is_identifier_start(ch)) return read_word(); 602 | parse_error("Unexpected character '" + ch + "'"); 603 | }; 604 | 605 | next_token.context = function(nc) { 606 | if (nc) S = nc; 607 | return S; 608 | }; 609 | 610 | return next_token; 611 | 612 | }; 613 | 614 | /* -----[ Parser (constants) ]----- */ 615 | 616 | var UNARY_PREFIX = array_to_hash([ 617 | "typeof", 618 | "void", 619 | "delete", 620 | "--", 621 | "++", 622 | "!", 623 | "~", 624 | "-", 625 | "+" 626 | ]); 627 | 628 | var UNARY_POSTFIX = array_to_hash([ "--", "++" ]); 629 | 630 | var ASSIGNMENT = (function(a, ret, i){ 631 | while (i < a.length) { 632 | ret[a[i]] = a[i].substr(0, a[i].length - 1); 633 | i++; 634 | } 635 | return ret; 636 | })( 637 | ["+=", "-=", "/=", "*=", "%=", ">>=", "<<=", ">>>=", "|=", "^=", "&="], 638 | { "=": true }, 639 | 0 640 | ); 641 | 642 | var PRECEDENCE = (function(a, ret){ 643 | for (var i = 0, n = 1; i < a.length; ++i, ++n) { 644 | var b = a[i]; 645 | for (var j = 0; j < b.length; ++j) { 646 | ret[b[j]] = n; 647 | } 648 | } 649 | return ret; 650 | })( 651 | [ 652 | ["||"], 653 | ["&&"], 654 | ["|"], 655 | ["^"], 656 | ["&"], 657 | ["==", "===", "!=", "!=="], 658 | ["<", ">", "<=", ">=", "in", "instanceof"], 659 | [">>", "<<", ">>>"], 660 | ["+", "-"], 661 | ["*", "/", "%"] 662 | ], 663 | {} 664 | ); 665 | 666 | var STATEMENTS_WITH_LABELS = array_to_hash([ "for", "do", "while", "switch" ]); 667 | 668 | var ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "string", "regexp", "name" ]); 669 | 670 | /* -----[ Parser ]----- */ 671 | 672 | function NodeWithToken(str, start, end) { 673 | this.name = str; 674 | this.start = start; 675 | this.end = end; 676 | }; 677 | 678 | NodeWithToken.prototype.toString = function() { return this.name; }; 679 | 680 | function parse($TEXT, exigent_mode, embed_tokens) { 681 | 682 | var S = { 683 | input : typeof $TEXT == "string" ? tokenizer($TEXT, true) : $TEXT, 684 | token : null, 685 | prev : null, 686 | peeked : null, 687 | in_function : 0, 688 | in_loop : 0, 689 | labels : [] 690 | }; 691 | 692 | S.token = next(); 693 | 694 | function is(type, value) { 695 | return is_token(S.token, type, value); 696 | }; 697 | 698 | function peek() { return S.peeked || (S.peeked = S.input()); }; 699 | 700 | function next() { 701 | S.prev = S.token; 702 | if (S.peeked) { 703 | S.token = S.peeked; 704 | S.peeked = null; 705 | } else { 706 | S.token = S.input(); 707 | } 708 | return S.token; 709 | }; 710 | 711 | function prev() { 712 | return S.prev; 713 | }; 714 | 715 | function croak(msg, line, col, pos) { 716 | var ctx = S.input.context(); 717 | js_error(msg, 718 | line != null ? line : ctx.tokline, 719 | col != null ? col : ctx.tokcol, 720 | pos != null ? pos : ctx.tokpos); 721 | }; 722 | 723 | function token_error(token, msg) { 724 | croak(msg, token.line, token.col); 725 | }; 726 | 727 | function unexpected(token) { 728 | if (token == null) 729 | token = S.token; 730 | token_error(token, "Unexpected token: " + token.type + " (" + token.value + ")"); 731 | }; 732 | 733 | function expect_token(type, val) { 734 | if (is(type, val)) { 735 | return next(); 736 | } 737 | token_error(S.token, "Unexpected token " + S.token.type + ", expected " + type); 738 | }; 739 | 740 | function expect(punc) { return expect_token("punc", punc); }; 741 | 742 | function can_insert_semicolon() { 743 | return !exigent_mode && ( 744 | S.token.nlb || is("eof") || is("punc", "}") 745 | ); 746 | }; 747 | 748 | function semicolon() { 749 | if (is("punc", ";")) next(); 750 | else if (!can_insert_semicolon()) unexpected(); 751 | }; 752 | 753 | function as() { 754 | return slice(arguments); 755 | }; 756 | 757 | function parenthesised() { 758 | expect("("); 759 | var ex = expression(); 760 | expect(")"); 761 | return ex; 762 | }; 763 | 764 | function add_tokens(str, start, end) { 765 | return str instanceof NodeWithToken ? str : new NodeWithToken(str, start, end); 766 | }; 767 | 768 | function maybe_embed_tokens(parser) { 769 | if (embed_tokens) return function() { 770 | var start = S.token; 771 | var ast = parser.apply(this, arguments); 772 | ast[0] = add_tokens(ast[0], start, prev()); 773 | return ast; 774 | }; 775 | else return parser; 776 | }; 777 | 778 | var statement = maybe_embed_tokens(function() { 779 | if (is("operator", "/") || is("operator", "/=")) { 780 | S.peeked = null; 781 | S.token = S.input(S.token.value.substr(1)); // force regexp 782 | } 783 | switch (S.token.type) { 784 | case "num": 785 | case "string": 786 | case "regexp": 787 | case "operator": 788 | case "atom": 789 | return simple_statement(); 790 | 791 | case "name": 792 | return is_token(peek(), "punc", ":") 793 | ? labeled_statement(prog1(S.token.value, next, next)) 794 | : simple_statement(); 795 | 796 | case "punc": 797 | switch (S.token.value) { 798 | case "{": 799 | return as("block", block_()); 800 | case "[": 801 | case "(": 802 | return simple_statement(); 803 | case ";": 804 | next(); 805 | return as("block"); 806 | default: 807 | unexpected(); 808 | } 809 | 810 | case "keyword": 811 | switch (prog1(S.token.value, next)) { 812 | case "break": 813 | return break_cont("break"); 814 | 815 | case "continue": 816 | return break_cont("continue"); 817 | 818 | case "debugger": 819 | semicolon(); 820 | return as("debugger"); 821 | 822 | case "do": 823 | return (function(body){ 824 | expect_token("keyword", "while"); 825 | return as("do", prog1(parenthesised, semicolon), body); 826 | })(in_loop(statement)); 827 | 828 | case "for": 829 | return for_(); 830 | 831 | case "function": 832 | return function_(true); 833 | 834 | case "if": 835 | return if_(); 836 | 837 | case "return": 838 | if (S.in_function == 0) 839 | croak("'return' outside of function"); 840 | return as("return", 841 | is("punc", ";") 842 | ? (next(), null) 843 | : can_insert_semicolon() 844 | ? null 845 | : prog1(expression, semicolon)); 846 | 847 | case "switch": 848 | return as("switch", parenthesised(), switch_block_()); 849 | 850 | case "throw": 851 | if (S.token.nlb) 852 | croak("Illegal newline after 'throw'"); 853 | return as("throw", prog1(expression, semicolon)); 854 | 855 | case "try": 856 | return try_(); 857 | 858 | case "var": 859 | return prog1(var_, semicolon); 860 | 861 | case "const": 862 | return prog1(const_, semicolon); 863 | 864 | case "while": 865 | return as("while", parenthesised(), in_loop(statement)); 866 | 867 | case "with": 868 | return as("with", parenthesised(), statement()); 869 | 870 | default: 871 | unexpected(); 872 | } 873 | } 874 | }); 875 | 876 | function labeled_statement(label) { 877 | S.labels.push(label); 878 | var start = S.token, stat = statement(); 879 | if (exigent_mode && !HOP(STATEMENTS_WITH_LABELS, stat[0])) 880 | unexpected(start); 881 | S.labels.pop(); 882 | return as("label", label, stat); 883 | }; 884 | 885 | function simple_statement() { 886 | return as("stat", prog1(expression, semicolon)); 887 | }; 888 | 889 | function break_cont(type) { 890 | var name; 891 | if (!can_insert_semicolon()) { 892 | name = is("name") ? S.token.value : null; 893 | } 894 | if (name != null) { 895 | next(); 896 | if (!member(name, S.labels)) 897 | croak("Label " + name + " without matching loop or statement"); 898 | } 899 | else if (S.in_loop == 0) 900 | croak(type + " not inside a loop or switch"); 901 | semicolon(); 902 | return as(type, name); 903 | }; 904 | 905 | function for_() { 906 | expect("("); 907 | var init = null; 908 | if (!is("punc", ";")) { 909 | init = is("keyword", "var") 910 | ? (next(), var_(true)) 911 | : expression(true, true); 912 | if (is("operator", "in")) 913 | return for_in(init); 914 | } 915 | return regular_for(init); 916 | }; 917 | 918 | function regular_for(init) { 919 | expect(";"); 920 | var test = is("punc", ";") ? null : expression(); 921 | expect(";"); 922 | var step = is("punc", ")") ? null : expression(); 923 | expect(")"); 924 | return as("for", init, test, step, in_loop(statement)); 925 | }; 926 | 927 | function for_in(init) { 928 | var lhs = init[0] == "var" ? as("name", init[1][0]) : init; 929 | next(); 930 | var obj = expression(); 931 | expect(")"); 932 | return as("for-in", init, lhs, obj, in_loop(statement)); 933 | }; 934 | 935 | var function_ = function(in_statement) { 936 | var name = is("name") ? prog1(S.token.value, next) : null; 937 | if (in_statement && !name) 938 | unexpected(); 939 | expect("("); 940 | return as(in_statement ? "defun" : "function", 941 | name, 942 | // arguments 943 | (function(first, a){ 944 | while (!is("punc", ")")) { 945 | if (first) first = false; else expect(","); 946 | if (!is("name")) unexpected(); 947 | a.push(S.token.value); 948 | next(); 949 | } 950 | next(); 951 | return a; 952 | })(true, []), 953 | // body 954 | (function(){ 955 | ++S.in_function; 956 | var loop = S.in_loop; 957 | S.in_loop = 0; 958 | var a = block_(); 959 | --S.in_function; 960 | S.in_loop = loop; 961 | return a; 962 | })()); 963 | }; 964 | 965 | function if_() { 966 | var cond = parenthesised(), body = statement(), belse; 967 | if (is("keyword", "else")) { 968 | next(); 969 | belse = statement(); 970 | } 971 | return as("if", cond, body, belse); 972 | }; 973 | 974 | function block_() { 975 | expect("{"); 976 | var a = []; 977 | while (!is("punc", "}")) { 978 | if (is("eof")) unexpected(); 979 | a.push(statement()); 980 | } 981 | next(); 982 | return a; 983 | }; 984 | 985 | var switch_block_ = curry(in_loop, function(){ 986 | expect("{"); 987 | var a = [], cur = null; 988 | while (!is("punc", "}")) { 989 | if (is("eof")) unexpected(); 990 | if (is("keyword", "case")) { 991 | next(); 992 | cur = []; 993 | a.push([ expression(), cur ]); 994 | expect(":"); 995 | } 996 | else if (is("keyword", "default")) { 997 | next(); 998 | expect(":"); 999 | cur = []; 1000 | a.push([ null, cur ]); 1001 | } 1002 | else { 1003 | if (!cur) unexpected(); 1004 | cur.push(statement()); 1005 | } 1006 | } 1007 | next(); 1008 | return a; 1009 | }); 1010 | 1011 | function try_() { 1012 | var body = block_(), bcatch, bfinally; 1013 | if (is("keyword", "catch")) { 1014 | next(); 1015 | expect("("); 1016 | if (!is("name")) 1017 | croak("Name expected"); 1018 | var name = S.token.value; 1019 | next(); 1020 | expect(")"); 1021 | bcatch = [ name, block_() ]; 1022 | } 1023 | if (is("keyword", "finally")) { 1024 | next(); 1025 | bfinally = block_(); 1026 | } 1027 | if (!bcatch && !bfinally) 1028 | croak("Missing catch/finally blocks"); 1029 | return as("try", body, bcatch, bfinally); 1030 | }; 1031 | 1032 | function vardefs(no_in) { 1033 | var a = []; 1034 | for (;;) { 1035 | if (!is("name")) 1036 | unexpected(); 1037 | var name = S.token.value; 1038 | next(); 1039 | if (is("operator", "=")) { 1040 | next(); 1041 | a.push([ name, expression(false, no_in) ]); 1042 | } else { 1043 | a.push([ name ]); 1044 | } 1045 | if (!is("punc", ",")) 1046 | break; 1047 | next(); 1048 | } 1049 | return a; 1050 | }; 1051 | 1052 | function var_(no_in) { 1053 | return as("var", vardefs(no_in)); 1054 | }; 1055 | 1056 | function const_() { 1057 | return as("const", vardefs()); 1058 | }; 1059 | 1060 | function new_() { 1061 | var newexp = expr_atom(false), args; 1062 | if (is("punc", "(")) { 1063 | next(); 1064 | args = expr_list(")"); 1065 | } else { 1066 | args = []; 1067 | } 1068 | return subscripts(as("new", newexp, args), true); 1069 | }; 1070 | 1071 | var expr_atom = maybe_embed_tokens(function(allow_calls) { 1072 | if (is("operator", "new")) { 1073 | next(); 1074 | return new_(); 1075 | } 1076 | if (is("punc")) { 1077 | switch (S.token.value) { 1078 | case "(": 1079 | next(); 1080 | return subscripts(prog1(expression, curry(expect, ")")), allow_calls); 1081 | case "[": 1082 | next(); 1083 | return subscripts(array_(), allow_calls); 1084 | case "{": 1085 | next(); 1086 | return subscripts(object_(), allow_calls); 1087 | } 1088 | unexpected(); 1089 | } 1090 | if (is("keyword", "function")) { 1091 | next(); 1092 | return subscripts(function_(false), allow_calls); 1093 | } 1094 | if (HOP(ATOMIC_START_TOKEN, S.token.type)) { 1095 | var atom = S.token.type == "regexp" 1096 | ? as("regexp", S.token.value[0], S.token.value[1]) 1097 | : as(S.token.type, S.token.value); 1098 | return subscripts(prog1(atom, next), allow_calls); 1099 | } 1100 | unexpected(); 1101 | }); 1102 | 1103 | function expr_list(closing, allow_trailing_comma, allow_empty) { 1104 | var first = true, a = []; 1105 | while (!is("punc", closing)) { 1106 | if (first) first = false; else expect(","); 1107 | if (allow_trailing_comma && is("punc", closing)) break; 1108 | if (is("punc", ",") && allow_empty) { 1109 | a.push([ "atom", "undefined" ]); 1110 | } else { 1111 | a.push(expression(false)); 1112 | } 1113 | } 1114 | next(); 1115 | return a; 1116 | }; 1117 | 1118 | function array_() { 1119 | return as("array", expr_list("]", !exigent_mode, true)); 1120 | }; 1121 | 1122 | function object_() { 1123 | var first = true, a = []; 1124 | while (!is("punc", "}")) { 1125 | if (first) first = false; else expect(","); 1126 | if (!exigent_mode && is("punc", "}")) 1127 | // allow trailing comma 1128 | break; 1129 | var type = S.token.type; 1130 | var name = as_property_name(); 1131 | if (type == "name" && (name == "get" || name == "set") && !is("punc", ":")) { 1132 | a.push([ as_name(), function_(false), name ]); 1133 | } else { 1134 | expect(":"); 1135 | a.push([ name, expression(false) ]); 1136 | } 1137 | } 1138 | next(); 1139 | return as("object", a); 1140 | }; 1141 | 1142 | function as_property_name() { 1143 | switch (S.token.type) { 1144 | case "num": 1145 | case "string": 1146 | return prog1(S.token.value, next); 1147 | } 1148 | return as_name(); 1149 | }; 1150 | 1151 | function as_name() { 1152 | switch (S.token.type) { 1153 | case "name": 1154 | case "operator": 1155 | case "keyword": 1156 | case "atom": 1157 | return prog1(S.token.value, next); 1158 | default: 1159 | unexpected(); 1160 | } 1161 | }; 1162 | 1163 | function subscripts(expr, allow_calls) { 1164 | if (is("punc", ".")) { 1165 | next(); 1166 | return subscripts(as("dot", expr, as_name()), allow_calls); 1167 | } 1168 | if (is("punc", "[")) { 1169 | next(); 1170 | return subscripts(as("sub", expr, prog1(expression, curry(expect, "]"))), allow_calls); 1171 | } 1172 | if (allow_calls && is("punc", "(")) { 1173 | next(); 1174 | return subscripts(as("call", expr, expr_list(")")), true); 1175 | } 1176 | return expr; 1177 | }; 1178 | 1179 | function maybe_unary(allow_calls) { 1180 | if (is("operator") && HOP(UNARY_PREFIX, S.token.value)) { 1181 | return make_unary("unary-prefix", 1182 | prog1(S.token.value, next), 1183 | maybe_unary(allow_calls)); 1184 | } 1185 | var val = expr_atom(allow_calls); 1186 | while (is("operator") && HOP(UNARY_POSTFIX, S.token.value) && !S.token.nlb) { 1187 | val = make_unary("unary-postfix", S.token.value, val); 1188 | next(); 1189 | } 1190 | return val; 1191 | }; 1192 | 1193 | function make_unary(tag, op, expr) { 1194 | if ((op == "++" || op == "--") && !is_assignable(expr)) 1195 | croak("Invalid use of " + op + " operator"); 1196 | return as(tag, op, expr); 1197 | }; 1198 | 1199 | function expr_op(left, min_prec, no_in) { 1200 | var op = is("operator") ? S.token.value : null; 1201 | if (op && op == "in" && no_in) op = null; 1202 | var prec = op != null ? PRECEDENCE[op] : null; 1203 | if (prec != null && prec > min_prec) { 1204 | next(); 1205 | var right = expr_op(maybe_unary(true), prec, no_in); 1206 | return expr_op(as("binary", op, left, right), min_prec, no_in); 1207 | } 1208 | return left; 1209 | }; 1210 | 1211 | function expr_ops(no_in) { 1212 | return expr_op(maybe_unary(true), 0, no_in); 1213 | }; 1214 | 1215 | function maybe_conditional(no_in) { 1216 | var expr = expr_ops(no_in); 1217 | if (is("operator", "?")) { 1218 | next(); 1219 | var yes = expression(false); 1220 | expect(":"); 1221 | return as("conditional", expr, yes, expression(false, no_in)); 1222 | } 1223 | return expr; 1224 | }; 1225 | 1226 | function is_assignable(expr) { 1227 | if (!exigent_mode) return true; 1228 | switch (expr[0]+"") { 1229 | case "dot": 1230 | case "sub": 1231 | case "new": 1232 | case "call": 1233 | return true; 1234 | case "name": 1235 | return expr[1] != "this"; 1236 | } 1237 | }; 1238 | 1239 | function maybe_assign(no_in) { 1240 | var left = maybe_conditional(no_in), val = S.token.value; 1241 | if (is("operator") && HOP(ASSIGNMENT, val)) { 1242 | if (is_assignable(left)) { 1243 | next(); 1244 | return as("assign", ASSIGNMENT[val], left, maybe_assign(no_in)); 1245 | } 1246 | croak("Invalid assignment"); 1247 | } 1248 | return left; 1249 | }; 1250 | 1251 | var expression = maybe_embed_tokens(function(commas, no_in) { 1252 | if (arguments.length == 0) 1253 | commas = true; 1254 | var expr = maybe_assign(no_in); 1255 | if (commas && is("punc", ",")) { 1256 | next(); 1257 | return as("seq", expr, expression(true, no_in)); 1258 | } 1259 | return expr; 1260 | }); 1261 | 1262 | function in_loop(cont) { 1263 | try { 1264 | ++S.in_loop; 1265 | return cont(); 1266 | } finally { 1267 | --S.in_loop; 1268 | } 1269 | }; 1270 | 1271 | return as("toplevel", (function(a){ 1272 | while (!is("eof")) 1273 | a.push(statement()); 1274 | return a; 1275 | })([])); 1276 | 1277 | }; 1278 | 1279 | /* -----[ Utilities ]----- */ 1280 | 1281 | function curry(f) { 1282 | var args = slice(arguments, 1); 1283 | return function() { return f.apply(this, args.concat(slice(arguments))); }; 1284 | }; 1285 | 1286 | function prog1(ret) { 1287 | if (ret instanceof Function) 1288 | ret = ret(); 1289 | for (var i = 1, n = arguments.length; --n > 0; ++i) 1290 | arguments[i](); 1291 | return ret; 1292 | }; 1293 | 1294 | function array_to_hash(a) { 1295 | var ret = {}; 1296 | for (var i = 0; i < a.length; ++i) 1297 | ret[a[i]] = true; 1298 | return ret; 1299 | }; 1300 | 1301 | function slice(a, start) { 1302 | return Array.prototype.slice.call(a, start || 0); 1303 | }; 1304 | 1305 | function characters(str) { 1306 | return str.split(""); 1307 | }; 1308 | 1309 | function member(name, array) { 1310 | for (var i = array.length; --i >= 0;) 1311 | if (array[i] == name) 1312 | return true; 1313 | return false; 1314 | }; 1315 | 1316 | function HOP(obj, prop) { 1317 | return Object.prototype.hasOwnProperty.call(obj, prop); 1318 | }; 1319 | 1320 | var warn = function() {}; 1321 | 1322 | /* -----[ Exports ]----- */ 1323 | 1324 | exports.tokenizer = tokenizer; 1325 | exports.parse = parse; 1326 | exports.slice = slice; 1327 | exports.curry = curry; 1328 | exports.member = member; 1329 | exports.array_to_hash = array_to_hash; 1330 | exports.PRECEDENCE = PRECEDENCE; 1331 | exports.KEYWORDS_ATOM = KEYWORDS_ATOM; 1332 | exports.RESERVED_WORDS = RESERVED_WORDS; 1333 | exports.KEYWORDS = KEYWORDS; 1334 | exports.ATOMIC_START_TOKEN = ATOMIC_START_TOKEN; 1335 | exports.OPERATORS = OPERATORS; 1336 | exports.is_alphanumeric_char = is_alphanumeric_char; 1337 | exports.set_logger = function(logger) { 1338 | warn = logger; 1339 | }; 1340 | -------------------------------------------------------------------------------- /tools/jasmine/jasmine.js: -------------------------------------------------------------------------------- 1 | var isCommonJS = typeof window == "undefined"; 2 | 3 | /** 4 | * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. 5 | * 6 | * @namespace 7 | */ 8 | var jasmine = {}; 9 | if (isCommonJS) exports.jasmine = jasmine; 10 | /** 11 | * @private 12 | */ 13 | jasmine.unimplementedMethod_ = function() { 14 | throw new Error("unimplemented method"); 15 | }; 16 | 17 | /** 18 | * Use jasmine.undefined instead of undefined, since undefined is just 19 | * a plain old variable and may be redefined by somebody else. 20 | * 21 | * @private 22 | */ 23 | jasmine.undefined = jasmine.___undefined___; 24 | 25 | /** 26 | * Show diagnostic messages in the console if set to true 27 | * 28 | */ 29 | jasmine.VERBOSE = false; 30 | 31 | /** 32 | * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. 33 | * 34 | */ 35 | jasmine.DEFAULT_UPDATE_INTERVAL = 250; 36 | 37 | /** 38 | * Default timeout interval in milliseconds for waitsFor() blocks. 39 | */ 40 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; 41 | 42 | jasmine.getGlobal = function() { 43 | function getGlobal() { 44 | return this; 45 | } 46 | 47 | return getGlobal(); 48 | }; 49 | 50 | /** 51 | * Allows for bound functions to be compared. Internal use only. 52 | * 53 | * @ignore 54 | * @private 55 | * @param base {Object} bound 'this' for the function 56 | * @param name {Function} function to find 57 | */ 58 | jasmine.bindOriginal_ = function(base, name) { 59 | var original = base[name]; 60 | if (original.apply) { 61 | return function() { 62 | return original.apply(base, arguments); 63 | }; 64 | } else { 65 | // IE support 66 | return jasmine.getGlobal()[name]; 67 | } 68 | }; 69 | 70 | jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); 71 | jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); 72 | jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); 73 | jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); 74 | 75 | jasmine.MessageResult = function(values) { 76 | this.type = 'log'; 77 | this.values = values; 78 | this.trace = new Error(); // todo: test better 79 | }; 80 | 81 | jasmine.MessageResult.prototype.toString = function() { 82 | var text = ""; 83 | for (var i = 0; i < this.values.length; i++) { 84 | if (i > 0) text += " "; 85 | if (jasmine.isString_(this.values[i])) { 86 | text += this.values[i]; 87 | } else { 88 | text += jasmine.pp(this.values[i]); 89 | } 90 | } 91 | return text; 92 | }; 93 | 94 | jasmine.ExpectationResult = function(params) { 95 | this.type = 'expect'; 96 | this.matcherName = params.matcherName; 97 | this.passed_ = params.passed; 98 | this.expected = params.expected; 99 | this.actual = params.actual; 100 | this.message = this.passed_ ? 'Passed.' : params.message; 101 | 102 | var trace = (params.trace || new Error(this.message)); 103 | this.trace = this.passed_ ? '' : trace; 104 | }; 105 | 106 | jasmine.ExpectationResult.prototype.toString = function () { 107 | return this.message; 108 | }; 109 | 110 | jasmine.ExpectationResult.prototype.passed = function () { 111 | return this.passed_; 112 | }; 113 | 114 | /** 115 | * Getter for the Jasmine environment. Ensures one gets created 116 | */ 117 | jasmine.getEnv = function() { 118 | var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); 119 | return env; 120 | }; 121 | 122 | /** 123 | * @ignore 124 | * @private 125 | * @param value 126 | * @returns {Boolean} 127 | */ 128 | jasmine.isArray_ = function(value) { 129 | return jasmine.isA_("Array", value); 130 | }; 131 | 132 | /** 133 | * @ignore 134 | * @private 135 | * @param value 136 | * @returns {Boolean} 137 | */ 138 | jasmine.isString_ = function(value) { 139 | return jasmine.isA_("String", value); 140 | }; 141 | 142 | /** 143 | * @ignore 144 | * @private 145 | * @param value 146 | * @returns {Boolean} 147 | */ 148 | jasmine.isNumber_ = function(value) { 149 | return jasmine.isA_("Number", value); 150 | }; 151 | 152 | /** 153 | * @ignore 154 | * @private 155 | * @param {String} typeName 156 | * @param value 157 | * @returns {Boolean} 158 | */ 159 | jasmine.isA_ = function(typeName, value) { 160 | return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; 161 | }; 162 | 163 | /** 164 | * Pretty printer for expecations. Takes any object and turns it into a human-readable string. 165 | * 166 | * @param value {Object} an object to be outputted 167 | * @returns {String} 168 | */ 169 | jasmine.pp = function(value) { 170 | var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); 171 | stringPrettyPrinter.format(value); 172 | return stringPrettyPrinter.string; 173 | }; 174 | 175 | /** 176 | * Returns true if the object is a DOM Node. 177 | * 178 | * @param {Object} obj object to check 179 | * @returns {Boolean} 180 | */ 181 | jasmine.isDomNode = function(obj) { 182 | return obj.nodeType > 0; 183 | }; 184 | 185 | /** 186 | * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. 187 | * 188 | * @example 189 | * // don't care about which function is passed in, as long as it's a function 190 | * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); 191 | * 192 | * @param {Class} clazz 193 | * @returns matchable object of the type clazz 194 | */ 195 | jasmine.any = function(clazz) { 196 | return new jasmine.Matchers.Any(clazz); 197 | }; 198 | 199 | /** 200 | * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. 201 | * 202 | * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine 203 | * expectation syntax. Spies can be checked if they were called or not and what the calling params were. 204 | * 205 | * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). 206 | * 207 | * Spies are torn down at the end of every spec. 208 | * 209 | * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. 210 | * 211 | * @example 212 | * // a stub 213 | * var myStub = jasmine.createSpy('myStub'); // can be used anywhere 214 | * 215 | * // spy example 216 | * var foo = { 217 | * not: function(bool) { return !bool; } 218 | * } 219 | * 220 | * // actual foo.not will not be called, execution stops 221 | * spyOn(foo, 'not'); 222 | 223 | // foo.not spied upon, execution will continue to implementation 224 | * spyOn(foo, 'not').andCallThrough(); 225 | * 226 | * // fake example 227 | * var foo = { 228 | * not: function(bool) { return !bool; } 229 | * } 230 | * 231 | * // foo.not(val) will return val 232 | * spyOn(foo, 'not').andCallFake(function(value) {return value;}); 233 | * 234 | * // mock example 235 | * foo.not(7 == 7); 236 | * expect(foo.not).toHaveBeenCalled(); 237 | * expect(foo.not).toHaveBeenCalledWith(true); 238 | * 239 | * @constructor 240 | * @see spyOn, jasmine.createSpy, jasmine.createSpyObj 241 | * @param {String} name 242 | */ 243 | jasmine.Spy = function(name) { 244 | /** 245 | * The name of the spy, if provided. 246 | */ 247 | this.identity = name || 'unknown'; 248 | /** 249 | * Is this Object a spy? 250 | */ 251 | this.isSpy = true; 252 | /** 253 | * The actual function this spy stubs. 254 | */ 255 | this.plan = function() { 256 | }; 257 | /** 258 | * Tracking of the most recent call to the spy. 259 | * @example 260 | * var mySpy = jasmine.createSpy('foo'); 261 | * mySpy(1, 2); 262 | * mySpy.mostRecentCall.args = [1, 2]; 263 | */ 264 | this.mostRecentCall = {}; 265 | 266 | /** 267 | * Holds arguments for each call to the spy, indexed by call count 268 | * @example 269 | * var mySpy = jasmine.createSpy('foo'); 270 | * mySpy(1, 2); 271 | * mySpy(7, 8); 272 | * mySpy.mostRecentCall.args = [7, 8]; 273 | * mySpy.argsForCall[0] = [1, 2]; 274 | * mySpy.argsForCall[1] = [7, 8]; 275 | */ 276 | this.argsForCall = []; 277 | this.calls = []; 278 | }; 279 | 280 | /** 281 | * Tells a spy to call through to the actual implemenatation. 282 | * 283 | * @example 284 | * var foo = { 285 | * bar: function() { // do some stuff } 286 | * } 287 | * 288 | * // defining a spy on an existing property: foo.bar 289 | * spyOn(foo, 'bar').andCallThrough(); 290 | */ 291 | jasmine.Spy.prototype.andCallThrough = function() { 292 | this.plan = this.originalValue; 293 | return this; 294 | }; 295 | 296 | /** 297 | * For setting the return value of a spy. 298 | * 299 | * @example 300 | * // defining a spy from scratch: foo() returns 'baz' 301 | * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); 302 | * 303 | * // defining a spy on an existing property: foo.bar() returns 'baz' 304 | * spyOn(foo, 'bar').andReturn('baz'); 305 | * 306 | * @param {Object} value 307 | */ 308 | jasmine.Spy.prototype.andReturn = function(value) { 309 | this.plan = function() { 310 | return value; 311 | }; 312 | return this; 313 | }; 314 | 315 | /** 316 | * For throwing an exception when a spy is called. 317 | * 318 | * @example 319 | * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' 320 | * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); 321 | * 322 | * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' 323 | * spyOn(foo, 'bar').andThrow('baz'); 324 | * 325 | * @param {String} exceptionMsg 326 | */ 327 | jasmine.Spy.prototype.andThrow = function(exceptionMsg) { 328 | this.plan = function() { 329 | throw exceptionMsg; 330 | }; 331 | return this; 332 | }; 333 | 334 | /** 335 | * Calls an alternate implementation when a spy is called. 336 | * 337 | * @example 338 | * var baz = function() { 339 | * // do some stuff, return something 340 | * } 341 | * // defining a spy from scratch: foo() calls the function baz 342 | * var foo = jasmine.createSpy('spy on foo').andCall(baz); 343 | * 344 | * // defining a spy on an existing property: foo.bar() calls an anonymnous function 345 | * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); 346 | * 347 | * @param {Function} fakeFunc 348 | */ 349 | jasmine.Spy.prototype.andCallFake = function(fakeFunc) { 350 | this.plan = fakeFunc; 351 | return this; 352 | }; 353 | 354 | /** 355 | * Resets all of a spy's the tracking variables so that it can be used again. 356 | * 357 | * @example 358 | * spyOn(foo, 'bar'); 359 | * 360 | * foo.bar(); 361 | * 362 | * expect(foo.bar.callCount).toEqual(1); 363 | * 364 | * foo.bar.reset(); 365 | * 366 | * expect(foo.bar.callCount).toEqual(0); 367 | */ 368 | jasmine.Spy.prototype.reset = function() { 369 | this.wasCalled = false; 370 | this.callCount = 0; 371 | this.argsForCall = []; 372 | this.calls = []; 373 | this.mostRecentCall = {}; 374 | }; 375 | 376 | jasmine.createSpy = function(name) { 377 | 378 | var spyObj = function() { 379 | spyObj.wasCalled = true; 380 | spyObj.callCount++; 381 | var args = jasmine.util.argsToArray(arguments); 382 | spyObj.mostRecentCall.object = this; 383 | spyObj.mostRecentCall.args = args; 384 | spyObj.argsForCall.push(args); 385 | spyObj.calls.push({object: this, args: args}); 386 | return spyObj.plan.apply(this, arguments); 387 | }; 388 | 389 | var spy = new jasmine.Spy(name); 390 | 391 | for (var prop in spy) { 392 | spyObj[prop] = spy[prop]; 393 | } 394 | 395 | spyObj.reset(); 396 | 397 | return spyObj; 398 | }; 399 | 400 | /** 401 | * Determines whether an object is a spy. 402 | * 403 | * @param {jasmine.Spy|Object} putativeSpy 404 | * @returns {Boolean} 405 | */ 406 | jasmine.isSpy = function(putativeSpy) { 407 | return putativeSpy && putativeSpy.isSpy; 408 | }; 409 | 410 | /** 411 | * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something 412 | * large in one call. 413 | * 414 | * @param {String} baseName name of spy class 415 | * @param {Array} methodNames array of names of methods to make spies 416 | */ 417 | jasmine.createSpyObj = function(baseName, methodNames) { 418 | if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { 419 | throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); 420 | } 421 | var obj = {}; 422 | for (var i = 0; i < methodNames.length; i++) { 423 | obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); 424 | } 425 | return obj; 426 | }; 427 | 428 | /** 429 | * All parameters are pretty-printed and concatenated together, then written to the current spec's output. 430 | * 431 | * Be careful not to leave calls to jasmine.log in production code. 432 | */ 433 | jasmine.log = function() { 434 | var spec = jasmine.getEnv().currentSpec; 435 | spec.log.apply(spec, arguments); 436 | }; 437 | 438 | /** 439 | * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. 440 | * 441 | * @example 442 | * // spy example 443 | * var foo = { 444 | * not: function(bool) { return !bool; } 445 | * } 446 | * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops 447 | * 448 | * @see jasmine.createSpy 449 | * @param obj 450 | * @param methodName 451 | * @returns a Jasmine spy that can be chained with all spy methods 452 | */ 453 | var spyOn = function(obj, methodName) { 454 | return jasmine.getEnv().currentSpec.spyOn(obj, methodName); 455 | }; 456 | if (isCommonJS) exports.spyOn = spyOn; 457 | 458 | /** 459 | * Creates a Jasmine spec that will be added to the current suite. 460 | * 461 | * // TODO: pending tests 462 | * 463 | * @example 464 | * it('should be true', function() { 465 | * expect(true).toEqual(true); 466 | * }); 467 | * 468 | * @param {String} desc description of this specification 469 | * @param {Function} func defines the preconditions and expectations of the spec 470 | */ 471 | var it = function(desc, func) { 472 | return jasmine.getEnv().it(desc, func); 473 | }; 474 | if (isCommonJS) exports.it = it; 475 | 476 | /** 477 | * Creates a disabled Jasmine spec. 478 | * 479 | * A convenience method that allows existing specs to be disabled temporarily during development. 480 | * 481 | * @param {String} desc description of this specification 482 | * @param {Function} func defines the preconditions and expectations of the spec 483 | */ 484 | var xit = function(desc, func) { 485 | return jasmine.getEnv().xit(desc, func); 486 | }; 487 | if (isCommonJS) exports.xit = xit; 488 | 489 | /** 490 | * Starts a chain for a Jasmine expectation. 491 | * 492 | * It is passed an Object that is the actual value and should chain to one of the many 493 | * jasmine.Matchers functions. 494 | * 495 | * @param {Object} actual Actual value to test against and expected value 496 | */ 497 | var expect = function(actual) { 498 | return jasmine.getEnv().currentSpec.expect(actual); 499 | }; 500 | if (isCommonJS) exports.expect = expect; 501 | 502 | /** 503 | * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. 504 | * 505 | * @param {Function} func Function that defines part of a jasmine spec. 506 | */ 507 | var runs = function(func) { 508 | jasmine.getEnv().currentSpec.runs(func); 509 | }; 510 | if (isCommonJS) exports.runs = runs; 511 | 512 | /** 513 | * Waits a fixed time period before moving to the next block. 514 | * 515 | * @deprecated Use waitsFor() instead 516 | * @param {Number} timeout milliseconds to wait 517 | */ 518 | var waits = function(timeout) { 519 | jasmine.getEnv().currentSpec.waits(timeout); 520 | }; 521 | if (isCommonJS) exports.waits = waits; 522 | 523 | /** 524 | * Waits for the latchFunction to return true before proceeding to the next block. 525 | * 526 | * @param {Function} latchFunction 527 | * @param {String} optional_timeoutMessage 528 | * @param {Number} optional_timeout 529 | */ 530 | var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { 531 | jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); 532 | }; 533 | if (isCommonJS) exports.waitsFor = waitsFor; 534 | 535 | /** 536 | * A function that is called before each spec in a suite. 537 | * 538 | * Used for spec setup, including validating assumptions. 539 | * 540 | * @param {Function} beforeEachFunction 541 | */ 542 | var beforeEach = function(beforeEachFunction) { 543 | jasmine.getEnv().beforeEach(beforeEachFunction); 544 | }; 545 | if (isCommonJS) exports.beforeEach = beforeEach; 546 | 547 | /** 548 | * A function that is called after each spec in a suite. 549 | * 550 | * Used for restoring any state that is hijacked during spec execution. 551 | * 552 | * @param {Function} afterEachFunction 553 | */ 554 | var afterEach = function(afterEachFunction) { 555 | jasmine.getEnv().afterEach(afterEachFunction); 556 | }; 557 | if (isCommonJS) exports.afterEach = afterEach; 558 | 559 | /** 560 | * Defines a suite of specifications. 561 | * 562 | * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared 563 | * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization 564 | * of setup in some tests. 565 | * 566 | * @example 567 | * // TODO: a simple suite 568 | * 569 | * // TODO: a simple suite with a nested describe block 570 | * 571 | * @param {String} description A string, usually the class under test. 572 | * @param {Function} specDefinitions function that defines several specs. 573 | */ 574 | var describe = function(description, specDefinitions) { 575 | return jasmine.getEnv().describe(description, specDefinitions); 576 | }; 577 | if (isCommonJS) exports.describe = describe; 578 | 579 | /** 580 | * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. 581 | * 582 | * @param {String} description A string, usually the class under test. 583 | * @param {Function} specDefinitions function that defines several specs. 584 | */ 585 | var xdescribe = function(description, specDefinitions) { 586 | return jasmine.getEnv().xdescribe(description, specDefinitions); 587 | }; 588 | if (isCommonJS) exports.xdescribe = xdescribe; 589 | 590 | 591 | // Provide the XMLHttpRequest class for IE 5.x-6.x: 592 | jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { 593 | function tryIt(f) { 594 | try { 595 | return f(); 596 | } catch(e) { 597 | } 598 | return null; 599 | } 600 | 601 | var xhr = tryIt(function() { 602 | return new ActiveXObject("Msxml2.XMLHTTP.6.0"); 603 | }) || 604 | tryIt(function() { 605 | return new ActiveXObject("Msxml2.XMLHTTP.3.0"); 606 | }) || 607 | tryIt(function() { 608 | return new ActiveXObject("Msxml2.XMLHTTP"); 609 | }) || 610 | tryIt(function() { 611 | return new ActiveXObject("Microsoft.XMLHTTP"); 612 | }); 613 | 614 | if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); 615 | 616 | return xhr; 617 | } : XMLHttpRequest; 618 | /** 619 | * @namespace 620 | */ 621 | jasmine.util = {}; 622 | 623 | /** 624 | * Declare that a child class inherit it's prototype from the parent class. 625 | * 626 | * @private 627 | * @param {Function} childClass 628 | * @param {Function} parentClass 629 | */ 630 | jasmine.util.inherit = function(childClass, parentClass) { 631 | /** 632 | * @private 633 | */ 634 | var subclass = function() { 635 | }; 636 | subclass.prototype = parentClass.prototype; 637 | childClass.prototype = new subclass(); 638 | }; 639 | 640 | jasmine.util.formatException = function(e) { 641 | var lineNumber; 642 | if (e.line) { 643 | lineNumber = e.line; 644 | } 645 | else if (e.lineNumber) { 646 | lineNumber = e.lineNumber; 647 | } 648 | 649 | var file; 650 | 651 | if (e.sourceURL) { 652 | file = e.sourceURL; 653 | } 654 | else if (e.fileName) { 655 | file = e.fileName; 656 | } 657 | 658 | var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); 659 | 660 | if (file && lineNumber) { 661 | message += ' in ' + file + ' (line ' + lineNumber + ')'; 662 | } 663 | 664 | return message; 665 | }; 666 | 667 | jasmine.util.htmlEscape = function(str) { 668 | if (!str) return str; 669 | return str.replace(/&/g, '&') 670 | .replace(//g, '>'); 672 | }; 673 | 674 | jasmine.util.argsToArray = function(args) { 675 | var arrayOfArgs = []; 676 | for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); 677 | return arrayOfArgs; 678 | }; 679 | 680 | jasmine.util.extend = function(destination, source) { 681 | for (var property in source) destination[property] = source[property]; 682 | return destination; 683 | }; 684 | 685 | /** 686 | * Environment for Jasmine 687 | * 688 | * @constructor 689 | */ 690 | jasmine.Env = function() { 691 | this.currentSpec = null; 692 | this.currentSuite = null; 693 | this.currentRunner_ = new jasmine.Runner(this); 694 | 695 | this.reporter = new jasmine.MultiReporter(); 696 | 697 | this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; 698 | this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; 699 | this.lastUpdate = 0; 700 | this.specFilter = function() { 701 | return true; 702 | }; 703 | 704 | this.nextSpecId_ = 0; 705 | this.nextSuiteId_ = 0; 706 | this.equalityTesters_ = []; 707 | 708 | // wrap matchers 709 | this.matchersClass = function() { 710 | jasmine.Matchers.apply(this, arguments); 711 | }; 712 | jasmine.util.inherit(this.matchersClass, jasmine.Matchers); 713 | 714 | jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); 715 | }; 716 | 717 | 718 | jasmine.Env.prototype.setTimeout = jasmine.setTimeout; 719 | jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; 720 | jasmine.Env.prototype.setInterval = jasmine.setInterval; 721 | jasmine.Env.prototype.clearInterval = jasmine.clearInterval; 722 | 723 | /** 724 | * @returns an object containing jasmine version build info, if set. 725 | */ 726 | jasmine.Env.prototype.version = function () { 727 | if (jasmine.version_) { 728 | return jasmine.version_; 729 | } else { 730 | throw new Error('Version not set'); 731 | } 732 | }; 733 | 734 | /** 735 | * @returns string containing jasmine version build info, if set. 736 | */ 737 | jasmine.Env.prototype.versionString = function() { 738 | if (!jasmine.version_) { 739 | return "version unknown"; 740 | } 741 | 742 | var version = this.version(); 743 | var versionString = version.major + "." + version.minor + "." + version.build; 744 | if (version.release_candidate) { 745 | versionString += ".rc" + version.release_candidate; 746 | } 747 | versionString += " revision " + version.revision; 748 | return versionString; 749 | }; 750 | 751 | /** 752 | * @returns a sequential integer starting at 0 753 | */ 754 | jasmine.Env.prototype.nextSpecId = function () { 755 | return this.nextSpecId_++; 756 | }; 757 | 758 | /** 759 | * @returns a sequential integer starting at 0 760 | */ 761 | jasmine.Env.prototype.nextSuiteId = function () { 762 | return this.nextSuiteId_++; 763 | }; 764 | 765 | /** 766 | * Register a reporter to receive status updates from Jasmine. 767 | * @param {jasmine.Reporter} reporter An object which will receive status updates. 768 | */ 769 | jasmine.Env.prototype.addReporter = function(reporter) { 770 | this.reporter.addReporter(reporter); 771 | }; 772 | 773 | jasmine.Env.prototype.execute = function() { 774 | this.currentRunner_.execute(); 775 | }; 776 | 777 | jasmine.Env.prototype.describe = function(description, specDefinitions) { 778 | var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); 779 | 780 | var parentSuite = this.currentSuite; 781 | if (parentSuite) { 782 | parentSuite.add(suite); 783 | } else { 784 | this.currentRunner_.add(suite); 785 | } 786 | 787 | this.currentSuite = suite; 788 | 789 | var declarationError = null; 790 | try { 791 | specDefinitions.call(suite); 792 | } catch(e) { 793 | declarationError = e; 794 | } 795 | 796 | if (declarationError) { 797 | this.it("encountered a declaration exception", function() { 798 | throw declarationError; 799 | }); 800 | } 801 | 802 | this.currentSuite = parentSuite; 803 | 804 | return suite; 805 | }; 806 | 807 | jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { 808 | if (this.currentSuite) { 809 | this.currentSuite.beforeEach(beforeEachFunction); 810 | } else { 811 | this.currentRunner_.beforeEach(beforeEachFunction); 812 | } 813 | }; 814 | 815 | jasmine.Env.prototype.currentRunner = function () { 816 | return this.currentRunner_; 817 | }; 818 | 819 | jasmine.Env.prototype.afterEach = function(afterEachFunction) { 820 | if (this.currentSuite) { 821 | this.currentSuite.afterEach(afterEachFunction); 822 | } else { 823 | this.currentRunner_.afterEach(afterEachFunction); 824 | } 825 | 826 | }; 827 | 828 | jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { 829 | return { 830 | execute: function() { 831 | } 832 | }; 833 | }; 834 | 835 | jasmine.Env.prototype.it = function(description, func) { 836 | var spec = new jasmine.Spec(this, this.currentSuite, description); 837 | this.currentSuite.add(spec); 838 | this.currentSpec = spec; 839 | 840 | if (func) { 841 | spec.runs(func); 842 | } 843 | 844 | return spec; 845 | }; 846 | 847 | jasmine.Env.prototype.xit = function(desc, func) { 848 | return { 849 | id: this.nextSpecId(), 850 | runs: function() { 851 | } 852 | }; 853 | }; 854 | 855 | jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { 856 | if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { 857 | return true; 858 | } 859 | 860 | a.__Jasmine_been_here_before__ = b; 861 | b.__Jasmine_been_here_before__ = a; 862 | 863 | var hasKey = function(obj, keyName) { 864 | return obj !== null && obj[keyName] !== jasmine.undefined; 865 | }; 866 | 867 | for (var property in b) { 868 | if (!hasKey(a, property) && hasKey(b, property)) { 869 | mismatchKeys.push("expected has key '" + property + "', but missing from actual."); 870 | } 871 | } 872 | for (property in a) { 873 | if (!hasKey(b, property) && hasKey(a, property)) { 874 | mismatchKeys.push("expected missing key '" + property + "', but present in actual."); 875 | } 876 | } 877 | for (property in b) { 878 | if (property == '__Jasmine_been_here_before__') continue; 879 | if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { 880 | mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); 881 | } 882 | } 883 | 884 | if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { 885 | mismatchValues.push("arrays were not the same length"); 886 | } 887 | 888 | delete a.__Jasmine_been_here_before__; 889 | delete b.__Jasmine_been_here_before__; 890 | return (mismatchKeys.length === 0 && mismatchValues.length === 0); 891 | }; 892 | 893 | jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { 894 | mismatchKeys = mismatchKeys || []; 895 | mismatchValues = mismatchValues || []; 896 | 897 | for (var i = 0; i < this.equalityTesters_.length; i++) { 898 | var equalityTester = this.equalityTesters_[i]; 899 | var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); 900 | if (result !== jasmine.undefined) return result; 901 | } 902 | 903 | if (a === b) return true; 904 | 905 | if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { 906 | return (a == jasmine.undefined && b == jasmine.undefined); 907 | } 908 | 909 | if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { 910 | return a === b; 911 | } 912 | 913 | if (a instanceof Date && b instanceof Date) { 914 | return a.getTime() == b.getTime(); 915 | } 916 | 917 | if (a instanceof jasmine.Matchers.Any) { 918 | return a.matches(b); 919 | } 920 | 921 | if (b instanceof jasmine.Matchers.Any) { 922 | return b.matches(a); 923 | } 924 | 925 | if (jasmine.isString_(a) && jasmine.isString_(b)) { 926 | return (a == b); 927 | } 928 | 929 | if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { 930 | return (a == b); 931 | } 932 | 933 | if (typeof a === "object" && typeof b === "object") { 934 | return this.compareObjects_(a, b, mismatchKeys, mismatchValues); 935 | } 936 | 937 | //Straight check 938 | return (a === b); 939 | }; 940 | 941 | jasmine.Env.prototype.contains_ = function(haystack, needle) { 942 | if (jasmine.isArray_(haystack)) { 943 | for (var i = 0; i < haystack.length; i++) { 944 | if (this.equals_(haystack[i], needle)) return true; 945 | } 946 | return false; 947 | } 948 | return haystack.indexOf(needle) >= 0; 949 | }; 950 | 951 | jasmine.Env.prototype.addEqualityTester = function(equalityTester) { 952 | this.equalityTesters_.push(equalityTester); 953 | }; 954 | /** No-op base class for Jasmine reporters. 955 | * 956 | * @constructor 957 | */ 958 | jasmine.Reporter = function() { 959 | }; 960 | 961 | //noinspection JSUnusedLocalSymbols 962 | jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { 963 | }; 964 | 965 | //noinspection JSUnusedLocalSymbols 966 | jasmine.Reporter.prototype.reportRunnerResults = function(runner) { 967 | }; 968 | 969 | //noinspection JSUnusedLocalSymbols 970 | jasmine.Reporter.prototype.reportSuiteResults = function(suite) { 971 | }; 972 | 973 | //noinspection JSUnusedLocalSymbols 974 | jasmine.Reporter.prototype.reportSpecStarting = function(spec) { 975 | }; 976 | 977 | //noinspection JSUnusedLocalSymbols 978 | jasmine.Reporter.prototype.reportSpecResults = function(spec) { 979 | }; 980 | 981 | //noinspection JSUnusedLocalSymbols 982 | jasmine.Reporter.prototype.log = function(str) { 983 | }; 984 | 985 | /** 986 | * Blocks are functions with executable code that make up a spec. 987 | * 988 | * @constructor 989 | * @param {jasmine.Env} env 990 | * @param {Function} func 991 | * @param {jasmine.Spec} spec 992 | */ 993 | jasmine.Block = function(env, func, spec) { 994 | this.env = env; 995 | this.func = func; 996 | this.spec = spec; 997 | }; 998 | 999 | jasmine.Block.prototype.execute = function(onComplete) { 1000 | try { 1001 | this.func.apply(this.spec); 1002 | } catch (e) { 1003 | this.spec.fail(e); 1004 | } 1005 | onComplete(); 1006 | }; 1007 | /** JavaScript API reporter. 1008 | * 1009 | * @constructor 1010 | */ 1011 | jasmine.JsApiReporter = function() { 1012 | this.started = false; 1013 | this.finished = false; 1014 | this.suites_ = []; 1015 | this.results_ = {}; 1016 | }; 1017 | 1018 | jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { 1019 | this.started = true; 1020 | var suites = runner.topLevelSuites(); 1021 | for (var i = 0; i < suites.length; i++) { 1022 | var suite = suites[i]; 1023 | this.suites_.push(this.summarize_(suite)); 1024 | } 1025 | }; 1026 | 1027 | jasmine.JsApiReporter.prototype.suites = function() { 1028 | return this.suites_; 1029 | }; 1030 | 1031 | jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { 1032 | var isSuite = suiteOrSpec instanceof jasmine.Suite; 1033 | var summary = { 1034 | id: suiteOrSpec.id, 1035 | name: suiteOrSpec.description, 1036 | type: isSuite ? 'suite' : 'spec', 1037 | children: [] 1038 | }; 1039 | 1040 | if (isSuite) { 1041 | var children = suiteOrSpec.children(); 1042 | for (var i = 0; i < children.length; i++) { 1043 | summary.children.push(this.summarize_(children[i])); 1044 | } 1045 | } 1046 | return summary; 1047 | }; 1048 | 1049 | jasmine.JsApiReporter.prototype.results = function() { 1050 | return this.results_; 1051 | }; 1052 | 1053 | jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { 1054 | return this.results_[specId]; 1055 | }; 1056 | 1057 | //noinspection JSUnusedLocalSymbols 1058 | jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { 1059 | this.finished = true; 1060 | }; 1061 | 1062 | //noinspection JSUnusedLocalSymbols 1063 | jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { 1064 | }; 1065 | 1066 | //noinspection JSUnusedLocalSymbols 1067 | jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { 1068 | this.results_[spec.id] = { 1069 | messages: spec.results().getItems(), 1070 | result: spec.results().failedCount > 0 ? "failed" : "passed" 1071 | }; 1072 | }; 1073 | 1074 | //noinspection JSUnusedLocalSymbols 1075 | jasmine.JsApiReporter.prototype.log = function(str) { 1076 | }; 1077 | 1078 | jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ 1079 | var results = {}; 1080 | for (var i = 0; i < specIds.length; i++) { 1081 | var specId = specIds[i]; 1082 | results[specId] = this.summarizeResult_(this.results_[specId]); 1083 | } 1084 | return results; 1085 | }; 1086 | 1087 | jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ 1088 | var summaryMessages = []; 1089 | var messagesLength = result.messages.length; 1090 | for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { 1091 | var resultMessage = result.messages[messageIndex]; 1092 | summaryMessages.push({ 1093 | text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, 1094 | passed: resultMessage.passed ? resultMessage.passed() : true, 1095 | type: resultMessage.type, 1096 | message: resultMessage.message, 1097 | trace: { 1098 | stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined 1099 | } 1100 | }); 1101 | } 1102 | 1103 | return { 1104 | result : result.result, 1105 | messages : summaryMessages 1106 | }; 1107 | }; 1108 | 1109 | /** 1110 | * @constructor 1111 | * @param {jasmine.Env} env 1112 | * @param actual 1113 | * @param {jasmine.Spec} spec 1114 | */ 1115 | jasmine.Matchers = function(env, actual, spec, opt_isNot) { 1116 | this.env = env; 1117 | this.actual = actual; 1118 | this.spec = spec; 1119 | this.isNot = opt_isNot || false; 1120 | this.reportWasCalled_ = false; 1121 | }; 1122 | 1123 | // todo: @deprecated as of Jasmine 0.11, remove soon [xw] 1124 | jasmine.Matchers.pp = function(str) { 1125 | throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); 1126 | }; 1127 | 1128 | // todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] 1129 | jasmine.Matchers.prototype.report = function(result, failing_message, details) { 1130 | throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); 1131 | }; 1132 | 1133 | jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { 1134 | for (var methodName in prototype) { 1135 | if (methodName == 'report') continue; 1136 | var orig = prototype[methodName]; 1137 | matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); 1138 | } 1139 | }; 1140 | 1141 | jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { 1142 | return function() { 1143 | var matcherArgs = jasmine.util.argsToArray(arguments); 1144 | var result = matcherFunction.apply(this, arguments); 1145 | 1146 | if (this.isNot) { 1147 | result = !result; 1148 | } 1149 | 1150 | if (this.reportWasCalled_) return result; 1151 | 1152 | var message; 1153 | if (!result) { 1154 | if (this.message) { 1155 | message = this.message.apply(this, arguments); 1156 | if (jasmine.isArray_(message)) { 1157 | message = message[this.isNot ? 1 : 0]; 1158 | } 1159 | } else { 1160 | var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); 1161 | message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; 1162 | if (matcherArgs.length > 0) { 1163 | for (var i = 0; i < matcherArgs.length; i++) { 1164 | if (i > 0) message += ","; 1165 | message += " " + jasmine.pp(matcherArgs[i]); 1166 | } 1167 | } 1168 | message += "."; 1169 | } 1170 | } 1171 | var expectationResult = new jasmine.ExpectationResult({ 1172 | matcherName: matcherName, 1173 | passed: result, 1174 | expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], 1175 | actual: this.actual, 1176 | message: message 1177 | }); 1178 | this.spec.addMatcherResult(expectationResult); 1179 | return jasmine.undefined; 1180 | }; 1181 | }; 1182 | 1183 | 1184 | 1185 | 1186 | /** 1187 | * toBe: compares the actual to the expected using === 1188 | * @param expected 1189 | */ 1190 | jasmine.Matchers.prototype.toBe = function(expected) { 1191 | return this.actual === expected; 1192 | }; 1193 | 1194 | /** 1195 | * toNotBe: compares the actual to the expected using !== 1196 | * @param expected 1197 | * @deprecated as of 1.0. Use not.toBe() instead. 1198 | */ 1199 | jasmine.Matchers.prototype.toNotBe = function(expected) { 1200 | return this.actual !== expected; 1201 | }; 1202 | 1203 | /** 1204 | * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. 1205 | * 1206 | * @param expected 1207 | */ 1208 | jasmine.Matchers.prototype.toEqual = function(expected) { 1209 | return this.env.equals_(this.actual, expected); 1210 | }; 1211 | 1212 | /** 1213 | * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual 1214 | * @param expected 1215 | * @deprecated as of 1.0. Use not.toNotEqual() instead. 1216 | */ 1217 | jasmine.Matchers.prototype.toNotEqual = function(expected) { 1218 | return !this.env.equals_(this.actual, expected); 1219 | }; 1220 | 1221 | /** 1222 | * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes 1223 | * a pattern or a String. 1224 | * 1225 | * @param expected 1226 | */ 1227 | jasmine.Matchers.prototype.toMatch = function(expected) { 1228 | return new RegExp(expected).test(this.actual); 1229 | }; 1230 | 1231 | /** 1232 | * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch 1233 | * @param expected 1234 | * @deprecated as of 1.0. Use not.toMatch() instead. 1235 | */ 1236 | jasmine.Matchers.prototype.toNotMatch = function(expected) { 1237 | return !(new RegExp(expected).test(this.actual)); 1238 | }; 1239 | 1240 | /** 1241 | * Matcher that compares the actual to jasmine.undefined. 1242 | */ 1243 | jasmine.Matchers.prototype.toBeDefined = function() { 1244 | return (this.actual !== jasmine.undefined); 1245 | }; 1246 | 1247 | /** 1248 | * Matcher that compares the actual to jasmine.undefined. 1249 | */ 1250 | jasmine.Matchers.prototype.toBeUndefined = function() { 1251 | return (this.actual === jasmine.undefined); 1252 | }; 1253 | 1254 | /** 1255 | * Matcher that compares the actual to null. 1256 | */ 1257 | jasmine.Matchers.prototype.toBeNull = function() { 1258 | return (this.actual === null); 1259 | }; 1260 | 1261 | /** 1262 | * Matcher that boolean not-nots the actual. 1263 | */ 1264 | jasmine.Matchers.prototype.toBeTruthy = function() { 1265 | return !!this.actual; 1266 | }; 1267 | 1268 | 1269 | /** 1270 | * Matcher that boolean nots the actual. 1271 | */ 1272 | jasmine.Matchers.prototype.toBeFalsy = function() { 1273 | return !this.actual; 1274 | }; 1275 | 1276 | 1277 | /** 1278 | * Matcher that checks to see if the actual, a Jasmine spy, was called. 1279 | */ 1280 | jasmine.Matchers.prototype.toHaveBeenCalled = function() { 1281 | if (arguments.length > 0) { 1282 | throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); 1283 | } 1284 | 1285 | if (!jasmine.isSpy(this.actual)) { 1286 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1287 | } 1288 | 1289 | this.message = function() { 1290 | return [ 1291 | "Expected spy " + this.actual.identity + " to have been called.", 1292 | "Expected spy " + this.actual.identity + " not to have been called." 1293 | ]; 1294 | }; 1295 | 1296 | return this.actual.wasCalled; 1297 | }; 1298 | 1299 | /** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ 1300 | jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; 1301 | 1302 | /** 1303 | * Matcher that checks to see if the actual, a Jasmine spy, was not called. 1304 | * 1305 | * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead 1306 | */ 1307 | jasmine.Matchers.prototype.wasNotCalled = function() { 1308 | if (arguments.length > 0) { 1309 | throw new Error('wasNotCalled does not take arguments'); 1310 | } 1311 | 1312 | if (!jasmine.isSpy(this.actual)) { 1313 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1314 | } 1315 | 1316 | this.message = function() { 1317 | return [ 1318 | "Expected spy " + this.actual.identity + " to not have been called.", 1319 | "Expected spy " + this.actual.identity + " to have been called." 1320 | ]; 1321 | }; 1322 | 1323 | return !this.actual.wasCalled; 1324 | }; 1325 | 1326 | /** 1327 | * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. 1328 | * 1329 | * @example 1330 | * 1331 | */ 1332 | jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { 1333 | var expectedArgs = jasmine.util.argsToArray(arguments); 1334 | if (!jasmine.isSpy(this.actual)) { 1335 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1336 | } 1337 | this.message = function() { 1338 | if (this.actual.callCount === 0) { 1339 | // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw] 1340 | return [ 1341 | "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.", 1342 | "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was." 1343 | ]; 1344 | } else { 1345 | return [ 1346 | "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall), 1347 | "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall) 1348 | ]; 1349 | } 1350 | }; 1351 | 1352 | return this.env.contains_(this.actual.argsForCall, expectedArgs); 1353 | }; 1354 | 1355 | /** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ 1356 | jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; 1357 | 1358 | /** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ 1359 | jasmine.Matchers.prototype.wasNotCalledWith = function() { 1360 | var expectedArgs = jasmine.util.argsToArray(arguments); 1361 | if (!jasmine.isSpy(this.actual)) { 1362 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1363 | } 1364 | 1365 | this.message = function() { 1366 | return [ 1367 | "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", 1368 | "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" 1369 | ]; 1370 | }; 1371 | 1372 | return !this.env.contains_(this.actual.argsForCall, expectedArgs); 1373 | }; 1374 | 1375 | /** 1376 | * Matcher that checks that the expected item is an element in the actual Array. 1377 | * 1378 | * @param {Object} expected 1379 | */ 1380 | jasmine.Matchers.prototype.toContain = function(expected) { 1381 | return this.env.contains_(this.actual, expected); 1382 | }; 1383 | 1384 | /** 1385 | * Matcher that checks that the expected item is NOT an element in the actual Array. 1386 | * 1387 | * @param {Object} expected 1388 | * @deprecated as of 1.0. Use not.toNotContain() instead. 1389 | */ 1390 | jasmine.Matchers.prototype.toNotContain = function(expected) { 1391 | return !this.env.contains_(this.actual, expected); 1392 | }; 1393 | 1394 | jasmine.Matchers.prototype.toBeLessThan = function(expected) { 1395 | return this.actual < expected; 1396 | }; 1397 | 1398 | jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { 1399 | return this.actual > expected; 1400 | }; 1401 | 1402 | /** 1403 | * Matcher that checks that the expected item is equal to the actual item 1404 | * up to a given level of decimal precision (default 2). 1405 | * 1406 | * @param {Number} expected 1407 | * @param {Number} precision 1408 | */ 1409 | jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { 1410 | if (!(precision === 0)) { 1411 | precision = precision || 2; 1412 | } 1413 | var multiplier = Math.pow(10, precision); 1414 | var actual = Math.round(this.actual * multiplier); 1415 | expected = Math.round(expected * multiplier); 1416 | return expected == actual; 1417 | }; 1418 | 1419 | /** 1420 | * Matcher that checks that the expected exception was thrown by the actual. 1421 | * 1422 | * @param {String} expected 1423 | */ 1424 | jasmine.Matchers.prototype.toThrow = function(expected) { 1425 | var result = false; 1426 | var exception; 1427 | if (typeof this.actual != 'function') { 1428 | throw new Error('Actual is not a function'); 1429 | } 1430 | try { 1431 | this.actual(); 1432 | } catch (e) { 1433 | exception = e; 1434 | } 1435 | if (exception) { 1436 | result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); 1437 | } 1438 | 1439 | var not = this.isNot ? "not " : ""; 1440 | 1441 | this.message = function() { 1442 | if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { 1443 | return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); 1444 | } else { 1445 | return "Expected function to throw an exception."; 1446 | } 1447 | }; 1448 | 1449 | return result; 1450 | }; 1451 | 1452 | jasmine.Matchers.Any = function(expectedClass) { 1453 | this.expectedClass = expectedClass; 1454 | }; 1455 | 1456 | jasmine.Matchers.Any.prototype.matches = function(other) { 1457 | if (this.expectedClass == String) { 1458 | return typeof other == 'string' || other instanceof String; 1459 | } 1460 | 1461 | if (this.expectedClass == Number) { 1462 | return typeof other == 'number' || other instanceof Number; 1463 | } 1464 | 1465 | if (this.expectedClass == Function) { 1466 | return typeof other == 'function' || other instanceof Function; 1467 | } 1468 | 1469 | if (this.expectedClass == Object) { 1470 | return typeof other == 'object'; 1471 | } 1472 | 1473 | return other instanceof this.expectedClass; 1474 | }; 1475 | 1476 | jasmine.Matchers.Any.prototype.toString = function() { 1477 | return ''; 1478 | }; 1479 | 1480 | /** 1481 | * @constructor 1482 | */ 1483 | jasmine.MultiReporter = function() { 1484 | this.subReporters_ = []; 1485 | }; 1486 | jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); 1487 | 1488 | jasmine.MultiReporter.prototype.addReporter = function(reporter) { 1489 | this.subReporters_.push(reporter); 1490 | }; 1491 | 1492 | (function() { 1493 | var functionNames = [ 1494 | "reportRunnerStarting", 1495 | "reportRunnerResults", 1496 | "reportSuiteResults", 1497 | "reportSpecStarting", 1498 | "reportSpecResults", 1499 | "log" 1500 | ]; 1501 | for (var i = 0; i < functionNames.length; i++) { 1502 | var functionName = functionNames[i]; 1503 | jasmine.MultiReporter.prototype[functionName] = (function(functionName) { 1504 | return function() { 1505 | for (var j = 0; j < this.subReporters_.length; j++) { 1506 | var subReporter = this.subReporters_[j]; 1507 | if (subReporter[functionName]) { 1508 | subReporter[functionName].apply(subReporter, arguments); 1509 | } 1510 | } 1511 | }; 1512 | })(functionName); 1513 | } 1514 | })(); 1515 | /** 1516 | * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults 1517 | * 1518 | * @constructor 1519 | */ 1520 | jasmine.NestedResults = function() { 1521 | /** 1522 | * The total count of results 1523 | */ 1524 | this.totalCount = 0; 1525 | /** 1526 | * Number of passed results 1527 | */ 1528 | this.passedCount = 0; 1529 | /** 1530 | * Number of failed results 1531 | */ 1532 | this.failedCount = 0; 1533 | /** 1534 | * Was this suite/spec skipped? 1535 | */ 1536 | this.skipped = false; 1537 | /** 1538 | * @ignore 1539 | */ 1540 | this.items_ = []; 1541 | }; 1542 | 1543 | /** 1544 | * Roll up the result counts. 1545 | * 1546 | * @param result 1547 | */ 1548 | jasmine.NestedResults.prototype.rollupCounts = function(result) { 1549 | this.totalCount += result.totalCount; 1550 | this.passedCount += result.passedCount; 1551 | this.failedCount += result.failedCount; 1552 | }; 1553 | 1554 | /** 1555 | * Adds a log message. 1556 | * @param values Array of message parts which will be concatenated later. 1557 | */ 1558 | jasmine.NestedResults.prototype.log = function(values) { 1559 | this.items_.push(new jasmine.MessageResult(values)); 1560 | }; 1561 | 1562 | /** 1563 | * Getter for the results: message & results. 1564 | */ 1565 | jasmine.NestedResults.prototype.getItems = function() { 1566 | return this.items_; 1567 | }; 1568 | 1569 | /** 1570 | * Adds a result, tracking counts (total, passed, & failed) 1571 | * @param {jasmine.ExpectationResult|jasmine.NestedResults} result 1572 | */ 1573 | jasmine.NestedResults.prototype.addResult = function(result) { 1574 | if (result.type != 'log') { 1575 | if (result.items_) { 1576 | this.rollupCounts(result); 1577 | } else { 1578 | this.totalCount++; 1579 | if (result.passed()) { 1580 | this.passedCount++; 1581 | } else { 1582 | this.failedCount++; 1583 | } 1584 | } 1585 | } 1586 | this.items_.push(result); 1587 | }; 1588 | 1589 | /** 1590 | * @returns {Boolean} True if everything below passed 1591 | */ 1592 | jasmine.NestedResults.prototype.passed = function() { 1593 | return this.passedCount === this.totalCount; 1594 | }; 1595 | /** 1596 | * Base class for pretty printing for expectation results. 1597 | */ 1598 | jasmine.PrettyPrinter = function() { 1599 | this.ppNestLevel_ = 0; 1600 | }; 1601 | 1602 | /** 1603 | * Formats a value in a nice, human-readable string. 1604 | * 1605 | * @param value 1606 | */ 1607 | jasmine.PrettyPrinter.prototype.format = function(value) { 1608 | if (this.ppNestLevel_ > 40) { 1609 | throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); 1610 | } 1611 | 1612 | this.ppNestLevel_++; 1613 | try { 1614 | if (value === jasmine.undefined) { 1615 | this.emitScalar('undefined'); 1616 | } else if (value === null) { 1617 | this.emitScalar('null'); 1618 | } else if (value === jasmine.getGlobal()) { 1619 | this.emitScalar(''); 1620 | } else if (value instanceof jasmine.Matchers.Any) { 1621 | this.emitScalar(value.toString()); 1622 | } else if (typeof value === 'string') { 1623 | this.emitString(value); 1624 | } else if (jasmine.isSpy(value)) { 1625 | this.emitScalar("spy on " + value.identity); 1626 | } else if (value instanceof RegExp) { 1627 | this.emitScalar(value.toString()); 1628 | } else if (typeof value === 'function') { 1629 | this.emitScalar('Function'); 1630 | } else if (typeof value.nodeType === 'number') { 1631 | this.emitScalar('HTMLNode'); 1632 | } else if (value instanceof Date) { 1633 | this.emitScalar('Date(' + value + ')'); 1634 | } else if (value.__Jasmine_been_here_before__) { 1635 | this.emitScalar(''); 1636 | } else if (jasmine.isArray_(value) || typeof value == 'object') { 1637 | value.__Jasmine_been_here_before__ = true; 1638 | if (jasmine.isArray_(value)) { 1639 | this.emitArray(value); 1640 | } else { 1641 | this.emitObject(value); 1642 | } 1643 | delete value.__Jasmine_been_here_before__; 1644 | } else { 1645 | this.emitScalar(value.toString()); 1646 | } 1647 | } finally { 1648 | this.ppNestLevel_--; 1649 | } 1650 | }; 1651 | 1652 | jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { 1653 | for (var property in obj) { 1654 | if (property == '__Jasmine_been_here_before__') continue; 1655 | fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && 1656 | obj.__lookupGetter__(property) !== null) : false); 1657 | } 1658 | }; 1659 | 1660 | jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; 1661 | jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; 1662 | jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; 1663 | jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; 1664 | 1665 | jasmine.StringPrettyPrinter = function() { 1666 | jasmine.PrettyPrinter.call(this); 1667 | 1668 | this.string = ''; 1669 | }; 1670 | jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); 1671 | 1672 | jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { 1673 | this.append(value); 1674 | }; 1675 | 1676 | jasmine.StringPrettyPrinter.prototype.emitString = function(value) { 1677 | this.append("'" + value + "'"); 1678 | }; 1679 | 1680 | jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { 1681 | this.append('[ '); 1682 | for (var i = 0; i < array.length; i++) { 1683 | if (i > 0) { 1684 | this.append(', '); 1685 | } 1686 | this.format(array[i]); 1687 | } 1688 | this.append(' ]'); 1689 | }; 1690 | 1691 | jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { 1692 | var self = this; 1693 | this.append('{ '); 1694 | var first = true; 1695 | 1696 | this.iterateObject(obj, function(property, isGetter) { 1697 | if (first) { 1698 | first = false; 1699 | } else { 1700 | self.append(', '); 1701 | } 1702 | 1703 | self.append(property); 1704 | self.append(' : '); 1705 | if (isGetter) { 1706 | self.append(''); 1707 | } else { 1708 | self.format(obj[property]); 1709 | } 1710 | }); 1711 | 1712 | this.append(' }'); 1713 | }; 1714 | 1715 | jasmine.StringPrettyPrinter.prototype.append = function(value) { 1716 | this.string += value; 1717 | }; 1718 | jasmine.Queue = function(env) { 1719 | this.env = env; 1720 | this.blocks = []; 1721 | this.running = false; 1722 | this.index = 0; 1723 | this.offset = 0; 1724 | this.abort = false; 1725 | }; 1726 | 1727 | jasmine.Queue.prototype.addBefore = function(block) { 1728 | this.blocks.unshift(block); 1729 | }; 1730 | 1731 | jasmine.Queue.prototype.add = function(block) { 1732 | this.blocks.push(block); 1733 | }; 1734 | 1735 | jasmine.Queue.prototype.insertNext = function(block) { 1736 | this.blocks.splice((this.index + this.offset + 1), 0, block); 1737 | this.offset++; 1738 | }; 1739 | 1740 | jasmine.Queue.prototype.start = function(onComplete) { 1741 | this.running = true; 1742 | this.onComplete = onComplete; 1743 | this.next_(); 1744 | }; 1745 | 1746 | jasmine.Queue.prototype.isRunning = function() { 1747 | return this.running; 1748 | }; 1749 | 1750 | jasmine.Queue.LOOP_DONT_RECURSE = true; 1751 | 1752 | jasmine.Queue.prototype.next_ = function() { 1753 | var self = this; 1754 | var goAgain = true; 1755 | 1756 | while (goAgain) { 1757 | goAgain = false; 1758 | 1759 | if (self.index < self.blocks.length && !this.abort) { 1760 | var calledSynchronously = true; 1761 | var completedSynchronously = false; 1762 | 1763 | var onComplete = function () { 1764 | if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { 1765 | completedSynchronously = true; 1766 | return; 1767 | } 1768 | 1769 | if (self.blocks[self.index].abort) { 1770 | self.abort = true; 1771 | } 1772 | 1773 | self.offset = 0; 1774 | self.index++; 1775 | 1776 | var now = new Date().getTime(); 1777 | if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { 1778 | self.env.lastUpdate = now; 1779 | self.env.setTimeout(function() { 1780 | self.next_(); 1781 | }, 0); 1782 | } else { 1783 | if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { 1784 | goAgain = true; 1785 | } else { 1786 | self.next_(); 1787 | } 1788 | } 1789 | }; 1790 | self.blocks[self.index].execute(onComplete); 1791 | 1792 | calledSynchronously = false; 1793 | if (completedSynchronously) { 1794 | onComplete(); 1795 | } 1796 | 1797 | } else { 1798 | self.running = false; 1799 | if (self.onComplete) { 1800 | self.onComplete(); 1801 | } 1802 | } 1803 | } 1804 | }; 1805 | 1806 | jasmine.Queue.prototype.results = function() { 1807 | var results = new jasmine.NestedResults(); 1808 | for (var i = 0; i < this.blocks.length; i++) { 1809 | if (this.blocks[i].results) { 1810 | results.addResult(this.blocks[i].results()); 1811 | } 1812 | } 1813 | return results; 1814 | }; 1815 | 1816 | 1817 | /** 1818 | * Runner 1819 | * 1820 | * @constructor 1821 | * @param {jasmine.Env} env 1822 | */ 1823 | jasmine.Runner = function(env) { 1824 | var self = this; 1825 | self.env = env; 1826 | self.queue = new jasmine.Queue(env); 1827 | self.before_ = []; 1828 | self.after_ = []; 1829 | self.suites_ = []; 1830 | }; 1831 | 1832 | jasmine.Runner.prototype.execute = function() { 1833 | var self = this; 1834 | if (self.env.reporter.reportRunnerStarting) { 1835 | self.env.reporter.reportRunnerStarting(this); 1836 | } 1837 | self.queue.start(function () { 1838 | self.finishCallback(); 1839 | }); 1840 | }; 1841 | 1842 | jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { 1843 | beforeEachFunction.typeName = 'beforeEach'; 1844 | this.before_.splice(0,0,beforeEachFunction); 1845 | }; 1846 | 1847 | jasmine.Runner.prototype.afterEach = function(afterEachFunction) { 1848 | afterEachFunction.typeName = 'afterEach'; 1849 | this.after_.splice(0,0,afterEachFunction); 1850 | }; 1851 | 1852 | 1853 | jasmine.Runner.prototype.finishCallback = function() { 1854 | this.env.reporter.reportRunnerResults(this); 1855 | }; 1856 | 1857 | jasmine.Runner.prototype.addSuite = function(suite) { 1858 | this.suites_.push(suite); 1859 | }; 1860 | 1861 | jasmine.Runner.prototype.add = function(block) { 1862 | if (block instanceof jasmine.Suite) { 1863 | this.addSuite(block); 1864 | } 1865 | this.queue.add(block); 1866 | }; 1867 | 1868 | jasmine.Runner.prototype.specs = function () { 1869 | var suites = this.suites(); 1870 | var specs = []; 1871 | for (var i = 0; i < suites.length; i++) { 1872 | specs = specs.concat(suites[i].specs()); 1873 | } 1874 | return specs; 1875 | }; 1876 | 1877 | jasmine.Runner.prototype.suites = function() { 1878 | return this.suites_; 1879 | }; 1880 | 1881 | jasmine.Runner.prototype.topLevelSuites = function() { 1882 | var topLevelSuites = []; 1883 | for (var i = 0; i < this.suites_.length; i++) { 1884 | if (!this.suites_[i].parentSuite) { 1885 | topLevelSuites.push(this.suites_[i]); 1886 | } 1887 | } 1888 | return topLevelSuites; 1889 | }; 1890 | 1891 | jasmine.Runner.prototype.results = function() { 1892 | return this.queue.results(); 1893 | }; 1894 | /** 1895 | * Internal representation of a Jasmine specification, or test. 1896 | * 1897 | * @constructor 1898 | * @param {jasmine.Env} env 1899 | * @param {jasmine.Suite} suite 1900 | * @param {String} description 1901 | */ 1902 | jasmine.Spec = function(env, suite, description) { 1903 | if (!env) { 1904 | throw new Error('jasmine.Env() required'); 1905 | } 1906 | if (!suite) { 1907 | throw new Error('jasmine.Suite() required'); 1908 | } 1909 | var spec = this; 1910 | spec.id = env.nextSpecId ? env.nextSpecId() : null; 1911 | spec.env = env; 1912 | spec.suite = suite; 1913 | spec.description = description; 1914 | spec.queue = new jasmine.Queue(env); 1915 | 1916 | spec.afterCallbacks = []; 1917 | spec.spies_ = []; 1918 | 1919 | spec.results_ = new jasmine.NestedResults(); 1920 | spec.results_.description = description; 1921 | spec.matchersClass = null; 1922 | }; 1923 | 1924 | jasmine.Spec.prototype.getFullName = function() { 1925 | return this.suite.getFullName() + ' ' + this.description + '.'; 1926 | }; 1927 | 1928 | 1929 | jasmine.Spec.prototype.results = function() { 1930 | return this.results_; 1931 | }; 1932 | 1933 | /** 1934 | * All parameters are pretty-printed and concatenated together, then written to the spec's output. 1935 | * 1936 | * Be careful not to leave calls to jasmine.log in production code. 1937 | */ 1938 | jasmine.Spec.prototype.log = function() { 1939 | return this.results_.log(arguments); 1940 | }; 1941 | 1942 | jasmine.Spec.prototype.runs = function (func) { 1943 | var block = new jasmine.Block(this.env, func, this); 1944 | this.addToQueue(block); 1945 | return this; 1946 | }; 1947 | 1948 | jasmine.Spec.prototype.addToQueue = function (block) { 1949 | if (this.queue.isRunning()) { 1950 | this.queue.insertNext(block); 1951 | } else { 1952 | this.queue.add(block); 1953 | } 1954 | }; 1955 | 1956 | /** 1957 | * @param {jasmine.ExpectationResult} result 1958 | */ 1959 | jasmine.Spec.prototype.addMatcherResult = function(result) { 1960 | this.results_.addResult(result); 1961 | }; 1962 | 1963 | jasmine.Spec.prototype.expect = function(actual) { 1964 | var positive = new (this.getMatchersClass_())(this.env, actual, this); 1965 | positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); 1966 | return positive; 1967 | }; 1968 | 1969 | /** 1970 | * Waits a fixed time period before moving to the next block. 1971 | * 1972 | * @deprecated Use waitsFor() instead 1973 | * @param {Number} timeout milliseconds to wait 1974 | */ 1975 | jasmine.Spec.prototype.waits = function(timeout) { 1976 | var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); 1977 | this.addToQueue(waitsFunc); 1978 | return this; 1979 | }; 1980 | 1981 | /** 1982 | * Waits for the latchFunction to return true before proceeding to the next block. 1983 | * 1984 | * @param {Function} latchFunction 1985 | * @param {String} optional_timeoutMessage 1986 | * @param {Number} optional_timeout 1987 | */ 1988 | jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { 1989 | var latchFunction_ = null; 1990 | var optional_timeoutMessage_ = null; 1991 | var optional_timeout_ = null; 1992 | 1993 | for (var i = 0; i < arguments.length; i++) { 1994 | var arg = arguments[i]; 1995 | switch (typeof arg) { 1996 | case 'function': 1997 | latchFunction_ = arg; 1998 | break; 1999 | case 'string': 2000 | optional_timeoutMessage_ = arg; 2001 | break; 2002 | case 'number': 2003 | optional_timeout_ = arg; 2004 | break; 2005 | } 2006 | } 2007 | 2008 | var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); 2009 | this.addToQueue(waitsForFunc); 2010 | return this; 2011 | }; 2012 | 2013 | jasmine.Spec.prototype.fail = function (e) { 2014 | var expectationResult = new jasmine.ExpectationResult({ 2015 | passed: false, 2016 | message: e ? jasmine.util.formatException(e) : 'Exception', 2017 | trace: { stack: e.stack } 2018 | }); 2019 | this.results_.addResult(expectationResult); 2020 | }; 2021 | 2022 | jasmine.Spec.prototype.getMatchersClass_ = function() { 2023 | return this.matchersClass || this.env.matchersClass; 2024 | }; 2025 | 2026 | jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { 2027 | var parent = this.getMatchersClass_(); 2028 | var newMatchersClass = function() { 2029 | parent.apply(this, arguments); 2030 | }; 2031 | jasmine.util.inherit(newMatchersClass, parent); 2032 | jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); 2033 | this.matchersClass = newMatchersClass; 2034 | }; 2035 | 2036 | jasmine.Spec.prototype.finishCallback = function() { 2037 | this.env.reporter.reportSpecResults(this); 2038 | }; 2039 | 2040 | jasmine.Spec.prototype.finish = function(onComplete) { 2041 | this.removeAllSpies(); 2042 | this.finishCallback(); 2043 | if (onComplete) { 2044 | onComplete(); 2045 | } 2046 | }; 2047 | 2048 | jasmine.Spec.prototype.after = function(doAfter) { 2049 | if (this.queue.isRunning()) { 2050 | this.queue.add(new jasmine.Block(this.env, doAfter, this)); 2051 | } else { 2052 | this.afterCallbacks.unshift(doAfter); 2053 | } 2054 | }; 2055 | 2056 | jasmine.Spec.prototype.execute = function(onComplete) { 2057 | var spec = this; 2058 | if (!spec.env.specFilter(spec)) { 2059 | spec.results_.skipped = true; 2060 | spec.finish(onComplete); 2061 | return; 2062 | } 2063 | 2064 | this.env.reporter.reportSpecStarting(this); 2065 | 2066 | spec.env.currentSpec = spec; 2067 | 2068 | spec.addBeforesAndAftersToQueue(); 2069 | 2070 | spec.queue.start(function () { 2071 | spec.finish(onComplete); 2072 | }); 2073 | }; 2074 | 2075 | jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { 2076 | var runner = this.env.currentRunner(); 2077 | var i; 2078 | 2079 | for (var suite = this.suite; suite; suite = suite.parentSuite) { 2080 | for (i = 0; i < suite.before_.length; i++) { 2081 | this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); 2082 | } 2083 | } 2084 | for (i = 0; i < runner.before_.length; i++) { 2085 | this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); 2086 | } 2087 | for (i = 0; i < this.afterCallbacks.length; i++) { 2088 | this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); 2089 | } 2090 | for (suite = this.suite; suite; suite = suite.parentSuite) { 2091 | for (i = 0; i < suite.after_.length; i++) { 2092 | this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); 2093 | } 2094 | } 2095 | for (i = 0; i < runner.after_.length; i++) { 2096 | this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); 2097 | } 2098 | }; 2099 | 2100 | jasmine.Spec.prototype.explodes = function() { 2101 | throw 'explodes function should not have been called'; 2102 | }; 2103 | 2104 | jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { 2105 | if (obj == jasmine.undefined) { 2106 | throw "spyOn could not find an object to spy upon for " + methodName + "()"; 2107 | } 2108 | 2109 | if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { 2110 | throw methodName + '() method does not exist'; 2111 | } 2112 | 2113 | if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { 2114 | throw new Error(methodName + ' has already been spied upon'); 2115 | } 2116 | 2117 | var spyObj = jasmine.createSpy(methodName); 2118 | 2119 | this.spies_.push(spyObj); 2120 | spyObj.baseObj = obj; 2121 | spyObj.methodName = methodName; 2122 | spyObj.originalValue = obj[methodName]; 2123 | 2124 | obj[methodName] = spyObj; 2125 | 2126 | return spyObj; 2127 | }; 2128 | 2129 | jasmine.Spec.prototype.removeAllSpies = function() { 2130 | for (var i = 0; i < this.spies_.length; i++) { 2131 | var spy = this.spies_[i]; 2132 | spy.baseObj[spy.methodName] = spy.originalValue; 2133 | } 2134 | this.spies_ = []; 2135 | }; 2136 | 2137 | /** 2138 | * Internal representation of a Jasmine suite. 2139 | * 2140 | * @constructor 2141 | * @param {jasmine.Env} env 2142 | * @param {String} description 2143 | * @param {Function} specDefinitions 2144 | * @param {jasmine.Suite} parentSuite 2145 | */ 2146 | jasmine.Suite = function(env, description, specDefinitions, parentSuite) { 2147 | var self = this; 2148 | self.id = env.nextSuiteId ? env.nextSuiteId() : null; 2149 | self.description = description; 2150 | self.queue = new jasmine.Queue(env); 2151 | self.parentSuite = parentSuite; 2152 | self.env = env; 2153 | self.before_ = []; 2154 | self.after_ = []; 2155 | self.children_ = []; 2156 | self.suites_ = []; 2157 | self.specs_ = []; 2158 | }; 2159 | 2160 | jasmine.Suite.prototype.getFullName = function() { 2161 | var fullName = this.description; 2162 | for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { 2163 | fullName = parentSuite.description + ' ' + fullName; 2164 | } 2165 | return fullName; 2166 | }; 2167 | 2168 | jasmine.Suite.prototype.finish = function(onComplete) { 2169 | this.env.reporter.reportSuiteResults(this); 2170 | this.finished = true; 2171 | if (typeof(onComplete) == 'function') { 2172 | onComplete(); 2173 | } 2174 | }; 2175 | 2176 | jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { 2177 | beforeEachFunction.typeName = 'beforeEach'; 2178 | this.before_.unshift(beforeEachFunction); 2179 | }; 2180 | 2181 | jasmine.Suite.prototype.afterEach = function(afterEachFunction) { 2182 | afterEachFunction.typeName = 'afterEach'; 2183 | this.after_.unshift(afterEachFunction); 2184 | }; 2185 | 2186 | jasmine.Suite.prototype.results = function() { 2187 | return this.queue.results(); 2188 | }; 2189 | 2190 | jasmine.Suite.prototype.add = function(suiteOrSpec) { 2191 | this.children_.push(suiteOrSpec); 2192 | if (suiteOrSpec instanceof jasmine.Suite) { 2193 | this.suites_.push(suiteOrSpec); 2194 | this.env.currentRunner().addSuite(suiteOrSpec); 2195 | } else { 2196 | this.specs_.push(suiteOrSpec); 2197 | } 2198 | this.queue.add(suiteOrSpec); 2199 | }; 2200 | 2201 | jasmine.Suite.prototype.specs = function() { 2202 | return this.specs_; 2203 | }; 2204 | 2205 | jasmine.Suite.prototype.suites = function() { 2206 | return this.suites_; 2207 | }; 2208 | 2209 | jasmine.Suite.prototype.children = function() { 2210 | return this.children_; 2211 | }; 2212 | 2213 | jasmine.Suite.prototype.execute = function(onComplete) { 2214 | var self = this; 2215 | this.queue.start(function () { 2216 | self.finish(onComplete); 2217 | }); 2218 | }; 2219 | jasmine.WaitsBlock = function(env, timeout, spec) { 2220 | this.timeout = timeout; 2221 | jasmine.Block.call(this, env, null, spec); 2222 | }; 2223 | 2224 | jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); 2225 | 2226 | jasmine.WaitsBlock.prototype.execute = function (onComplete) { 2227 | if (jasmine.VERBOSE) { 2228 | this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); 2229 | } 2230 | this.env.setTimeout(function () { 2231 | onComplete(); 2232 | }, this.timeout); 2233 | }; 2234 | /** 2235 | * A block which waits for some condition to become true, with timeout. 2236 | * 2237 | * @constructor 2238 | * @extends jasmine.Block 2239 | * @param {jasmine.Env} env The Jasmine environment. 2240 | * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. 2241 | * @param {Function} latchFunction A function which returns true when the desired condition has been met. 2242 | * @param {String} message The message to display if the desired condition hasn't been met within the given time period. 2243 | * @param {jasmine.Spec} spec The Jasmine spec. 2244 | */ 2245 | jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { 2246 | this.timeout = timeout || env.defaultTimeoutInterval; 2247 | this.latchFunction = latchFunction; 2248 | this.message = message; 2249 | this.totalTimeSpentWaitingForLatch = 0; 2250 | jasmine.Block.call(this, env, null, spec); 2251 | }; 2252 | jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); 2253 | 2254 | jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; 2255 | 2256 | jasmine.WaitsForBlock.prototype.execute = function(onComplete) { 2257 | if (jasmine.VERBOSE) { 2258 | this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); 2259 | } 2260 | var latchFunctionResult; 2261 | try { 2262 | latchFunctionResult = this.latchFunction.apply(this.spec); 2263 | } catch (e) { 2264 | this.spec.fail(e); 2265 | onComplete(); 2266 | return; 2267 | } 2268 | 2269 | if (latchFunctionResult) { 2270 | onComplete(); 2271 | } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { 2272 | var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); 2273 | this.spec.fail({ 2274 | name: 'timeout', 2275 | message: message 2276 | }); 2277 | 2278 | this.abort = true; 2279 | onComplete(); 2280 | } else { 2281 | this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; 2282 | var self = this; 2283 | this.env.setTimeout(function() { 2284 | self.execute(onComplete); 2285 | }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); 2286 | } 2287 | }; 2288 | // Mock setTimeout, clearTimeout 2289 | // Contributed by Pivotal Computer Systems, www.pivotalsf.com 2290 | 2291 | jasmine.FakeTimer = function() { 2292 | this.reset(); 2293 | 2294 | var self = this; 2295 | self.setTimeout = function(funcToCall, millis) { 2296 | self.timeoutsMade++; 2297 | self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); 2298 | return self.timeoutsMade; 2299 | }; 2300 | 2301 | self.setInterval = function(funcToCall, millis) { 2302 | self.timeoutsMade++; 2303 | self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); 2304 | return self.timeoutsMade; 2305 | }; 2306 | 2307 | self.clearTimeout = function(timeoutKey) { 2308 | self.scheduledFunctions[timeoutKey] = jasmine.undefined; 2309 | }; 2310 | 2311 | self.clearInterval = function(timeoutKey) { 2312 | self.scheduledFunctions[timeoutKey] = jasmine.undefined; 2313 | }; 2314 | 2315 | }; 2316 | 2317 | jasmine.FakeTimer.prototype.reset = function() { 2318 | this.timeoutsMade = 0; 2319 | this.scheduledFunctions = {}; 2320 | this.nowMillis = 0; 2321 | }; 2322 | 2323 | jasmine.FakeTimer.prototype.tick = function(millis) { 2324 | var oldMillis = this.nowMillis; 2325 | var newMillis = oldMillis + millis; 2326 | this.runFunctionsWithinRange(oldMillis, newMillis); 2327 | this.nowMillis = newMillis; 2328 | }; 2329 | 2330 | jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { 2331 | var scheduledFunc; 2332 | var funcsToRun = []; 2333 | for (var timeoutKey in this.scheduledFunctions) { 2334 | scheduledFunc = this.scheduledFunctions[timeoutKey]; 2335 | if (scheduledFunc != jasmine.undefined && 2336 | scheduledFunc.runAtMillis >= oldMillis && 2337 | scheduledFunc.runAtMillis <= nowMillis) { 2338 | funcsToRun.push(scheduledFunc); 2339 | this.scheduledFunctions[timeoutKey] = jasmine.undefined; 2340 | } 2341 | } 2342 | 2343 | if (funcsToRun.length > 0) { 2344 | funcsToRun.sort(function(a, b) { 2345 | return a.runAtMillis - b.runAtMillis; 2346 | }); 2347 | for (var i = 0; i < funcsToRun.length; ++i) { 2348 | try { 2349 | var funcToRun = funcsToRun[i]; 2350 | this.nowMillis = funcToRun.runAtMillis; 2351 | funcToRun.funcToCall(); 2352 | if (funcToRun.recurring) { 2353 | this.scheduleFunction(funcToRun.timeoutKey, 2354 | funcToRun.funcToCall, 2355 | funcToRun.millis, 2356 | true); 2357 | } 2358 | } catch(e) { 2359 | } 2360 | } 2361 | this.runFunctionsWithinRange(oldMillis, nowMillis); 2362 | } 2363 | }; 2364 | 2365 | jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { 2366 | this.scheduledFunctions[timeoutKey] = { 2367 | runAtMillis: this.nowMillis + millis, 2368 | funcToCall: funcToCall, 2369 | recurring: recurring, 2370 | timeoutKey: timeoutKey, 2371 | millis: millis 2372 | }; 2373 | }; 2374 | 2375 | /** 2376 | * @namespace 2377 | */ 2378 | jasmine.Clock = { 2379 | defaultFakeTimer: new jasmine.FakeTimer(), 2380 | 2381 | reset: function() { 2382 | jasmine.Clock.assertInstalled(); 2383 | jasmine.Clock.defaultFakeTimer.reset(); 2384 | }, 2385 | 2386 | tick: function(millis) { 2387 | jasmine.Clock.assertInstalled(); 2388 | jasmine.Clock.defaultFakeTimer.tick(millis); 2389 | }, 2390 | 2391 | runFunctionsWithinRange: function(oldMillis, nowMillis) { 2392 | jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); 2393 | }, 2394 | 2395 | scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { 2396 | jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); 2397 | }, 2398 | 2399 | useMock: function() { 2400 | if (!jasmine.Clock.isInstalled()) { 2401 | var spec = jasmine.getEnv().currentSpec; 2402 | spec.after(jasmine.Clock.uninstallMock); 2403 | 2404 | jasmine.Clock.installMock(); 2405 | } 2406 | }, 2407 | 2408 | installMock: function() { 2409 | jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; 2410 | }, 2411 | 2412 | uninstallMock: function() { 2413 | jasmine.Clock.assertInstalled(); 2414 | jasmine.Clock.installed = jasmine.Clock.real; 2415 | }, 2416 | 2417 | real: { 2418 | setTimeout: jasmine.getGlobal().setTimeout, 2419 | clearTimeout: jasmine.getGlobal().clearTimeout, 2420 | setInterval: jasmine.getGlobal().setInterval, 2421 | clearInterval: jasmine.getGlobal().clearInterval 2422 | }, 2423 | 2424 | assertInstalled: function() { 2425 | if (!jasmine.Clock.isInstalled()) { 2426 | throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); 2427 | } 2428 | }, 2429 | 2430 | isInstalled: function() { 2431 | return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; 2432 | }, 2433 | 2434 | installed: null 2435 | }; 2436 | jasmine.Clock.installed = jasmine.Clock.real; 2437 | 2438 | //else for IE support 2439 | jasmine.getGlobal().setTimeout = function(funcToCall, millis) { 2440 | if (jasmine.Clock.installed.setTimeout.apply) { 2441 | return jasmine.Clock.installed.setTimeout.apply(this, arguments); 2442 | } else { 2443 | return jasmine.Clock.installed.setTimeout(funcToCall, millis); 2444 | } 2445 | }; 2446 | 2447 | jasmine.getGlobal().setInterval = function(funcToCall, millis) { 2448 | if (jasmine.Clock.installed.setInterval.apply) { 2449 | return jasmine.Clock.installed.setInterval.apply(this, arguments); 2450 | } else { 2451 | return jasmine.Clock.installed.setInterval(funcToCall, millis); 2452 | } 2453 | }; 2454 | 2455 | jasmine.getGlobal().clearTimeout = function(timeoutKey) { 2456 | if (jasmine.Clock.installed.clearTimeout.apply) { 2457 | return jasmine.Clock.installed.clearTimeout.apply(this, arguments); 2458 | } else { 2459 | return jasmine.Clock.installed.clearTimeout(timeoutKey); 2460 | } 2461 | }; 2462 | 2463 | jasmine.getGlobal().clearInterval = function(timeoutKey) { 2464 | if (jasmine.Clock.installed.clearTimeout.apply) { 2465 | return jasmine.Clock.installed.clearInterval.apply(this, arguments); 2466 | } else { 2467 | return jasmine.Clock.installed.clearInterval(timeoutKey); 2468 | } 2469 | }; 2470 | 2471 | jasmine.version_= { 2472 | "major": 1, 2473 | "minor": 1, 2474 | "build": 0, 2475 | "revision": 1315677058 2476 | }; 2477 | --------------------------------------------------------------------------------