├── .jscsrc
├── .gitignore
├── test
├── jasmine-2.3.4
│ ├── jasmine_favicon.png
│ ├── boot.js
│ ├── console.js
│ ├── jasmine-html.js
│ ├── jasmine.css
│ └── jasmine.js
├── SpecRunner.html
├── factorySpec.js
├── helpersSpec.js
├── jobSpec.js
├── workercodeSpec.js
└── messagesSpec.js
├── .npmignore
├── .travis.yml
├── karma.conf.js
├── package.json
├── src
├── threadify.js
├── workercode.js
├── helpers.js
└── job.js
├── LICENSE
├── Gruntfile.js
├── dist
├── threadify.min.js
└── threadify.js
└── README.md
/.jscsrc:
--------------------------------------------------------------------------------
1 | {
2 | "preset": "loris",
3 | "validateQuoteMarks": "\""
4 | }
5 |
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.log
3 | _api.js
4 | test/_tmp
5 | .grunt
6 | _SpecRunner.html
7 |
--------------------------------------------------------------------------------
/test/jasmine-2.3.4/jasmine_favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flozz/threadify/HEAD/test/jasmine-2.3.4/jasmine_favicon.png
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | dist
2 | Gruntfile.js
3 | .travis.yml
4 | test
5 | .jscsrc
6 | _api.js
7 | karma.conf.js
8 | .grunt
9 | _SpecRunner.html
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: true
2 | language: node_js
3 | node_js:
4 | - "stable"
5 | before_install:
6 | - sudo apt-get update -qq
7 | - sudo apt-get install chromium-browser firefox
8 | install:
9 | - npm install -g grunt-cli
10 | - npm install karma karma-firefox-launcher karma-chrome-launcher karma-jasmine
11 | - npm install
12 | before_script:
13 | - export CHROME_BIN=chromium-browser
14 | - export DISPLAY=:99.0
15 | - sh -e /etc/init.d/xvfb start
16 | script:
17 | - grunt
18 | - grunt test
19 | - ./node_modules/.bin/karma start
20 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = function(config) {
2 | var cfg = {
3 | frameworks: ['jasmine'],
4 | files: [
5 | 'dist/threadify.js',
6 | 'test/_tmp/helpers.js',
7 | 'test/*Spec.js'
8 | ],
9 | reporters: ['progress'],
10 | port: 9876,
11 | colors: true,
12 | logLevel: config.LOG_INFO,
13 | autoWatch: false,
14 | browsers: ['Firefox', 'Chrome'],
15 | captureTimeout: 60000,
16 | singleRun: true,
17 |
18 | customLaunchers: {
19 | Chrome_travis_ci: {
20 | base: 'Chrome',
21 | flags: ['--no-sandbox']
22 | }
23 | },
24 | };
25 |
26 | if (process.env.TRAVIS) {
27 | cfg.browsers = ['Firefox', 'Chrome_travis_ci'];
28 | }
29 |
30 | config.set(cfg);
31 | };
32 |
--------------------------------------------------------------------------------
/test/SpecRunner.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Jasmine Spec Runner v2.3.4
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "threadify",
3 | "version": "0.2.4",
4 | "description": "Simply transforms a javascript function into a web worker",
5 | "main": "src/threadify.js",
6 | "scripts": {
7 | "test": "grunt test"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git://github.com/flozz/threadify"
12 | },
13 | "keywords": [
14 | "webworker",
15 | "worker",
16 | "thread",
17 | "parallele"
18 | ],
19 | "author": "Fabien LOISON ",
20 | "license": "BSD-3-Clause",
21 | "bugs": {
22 | "url": "https://github.com/flozz/threadify/issues"
23 | },
24 | "devDependencies": {
25 | "grunt": "^1.0.2",
26 | "grunt-browserify": "^5.3.0",
27 | "grunt-cli": "^1.2.0",
28 | "grunt-contrib-jasmine": "^2.0.0",
29 | "grunt-contrib-jshint": "^1.1.0",
30 | "grunt-contrib-uglify": "^3.3.0",
31 | "grunt-jscs": "^3.0.1",
32 | "jscs-preset-loris": "^1.2.0"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/threadify.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var helpers = require("./helpers.js");
4 | var Job = require("./job.js");
5 | var workerCode = require("./workercode.js");
6 |
7 | function factory(workerFunction) {
8 | var workerBlob = new Blob(
9 | [
10 | "var window=this;var global=this;(",
11 | workerCode.toString(),
12 | ")(",
13 | workerFunction.toString(),
14 | ",",
15 | helpers.serializeArgs.toString(),
16 | ",",
17 | helpers.unserializeArgs.toString(),
18 | ");"
19 | ],
20 | {
21 | type: "application/javascript"
22 | }
23 | );
24 | var workerUrl = URL.createObjectURL(workerBlob);
25 |
26 | return function () {
27 | var args = [];
28 | for (var i = 0 ; i < arguments.length ; i++) {
29 | args.push(arguments[i]);
30 | }
31 | return new Job(workerUrl, args);
32 | };
33 | }
34 |
35 | module.exports = factory;
36 |
--------------------------------------------------------------------------------
/test/factorySpec.js:
--------------------------------------------------------------------------------
1 | describe("Threadify factory", function () {
2 |
3 | it("can returns a job factory function", function () {
4 | var fn = threadify(function () {});
5 |
6 | expect(fn instanceof Function).toBeTruthy();
7 | // expect(fn.toString()).toMatch(/return\s+new\s+Job/); // FIXME this cannot work once minified
8 | });
9 |
10 | });
11 |
12 | describe("Job factory", function () {
13 |
14 | it("can spawns a worker", function (done) {
15 | var callbacks = {
16 | done: function () {
17 | expect(callbacks.done).toHaveBeenCalledWith("ok");
18 | done();
19 | }
20 | };
21 |
22 | spyOn(callbacks, "done").and.callThrough();
23 |
24 | var fn = threadify(function () {
25 | return "ok";
26 | });
27 |
28 | var job = fn();
29 |
30 | job.done = callbacks.done;
31 | });
32 |
33 | it("can sends arguments to the worker", function (done) {
34 | var callbacks = {
35 | done: function () {
36 | expect(callbacks.done).toHaveBeenCalledWith("foobar");
37 | done();
38 | }
39 | };
40 |
41 | spyOn(callbacks, "done").and.callThrough();
42 |
43 | var fn = threadify(function (a, b) {
44 | return a + b;
45 | });
46 |
47 | var job = fn("foo", "bar");
48 |
49 | job.done = callbacks.done;
50 | });
51 |
52 | });
53 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, Fabien LOISON
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 | * Redistributions of source code must retain the above copyright
7 | notice, this list of conditions and the following disclaimer.
8 | * Redistributions in binary form must reproduce the above copyright
9 | notice, this list of conditions and the following disclaimer in the
10 | documentation and/or other materials provided with the distribution.
11 | * Neither the name of Fabien LOISON nor the
12 | names of its contributors may be used to endorse or promote products
13 | derived from this software without specific prior written permission.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | DISCLAIMED. IN NO EVENT SHALL FABIEN LOISON BE LIABLE FOR ANY
19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
--------------------------------------------------------------------------------
/src/workercode.js:
--------------------------------------------------------------------------------
1 | //
2 | // This file contains the code that will be injected inside the web worker
3 | //
4 |
5 | module.exports = function (workerFunction, serializeArgs, unserializeArgs) {
6 | "use strict";
7 |
8 | function _postMessage(name, args) {
9 | var serialized = serializeArgs(args || []);
10 |
11 | var data = {
12 | name: name,
13 | args: serialized.args
14 | };
15 |
16 | postMessage(data, serialized.transferable);
17 | }
18 |
19 | var thread = {
20 | terminate: function () {
21 | _postMessage("threadify-terminated", []);
22 | close();
23 | },
24 |
25 | error: function () {
26 | _postMessage("threadify-error", arguments);
27 | },
28 |
29 | return: function () {
30 | _postMessage("threadify-return", arguments);
31 | thread.terminate();
32 | }
33 | };
34 |
35 | function _onMessage(event) {
36 | var data = event.data || {};
37 | var args = unserializeArgs(data.args || []);
38 |
39 | switch (data.name) {
40 | case "threadify-start":
41 | var result;
42 | try {
43 | result = workerFunction.apply(thread, args);
44 | } catch (error) {
45 | thread.error(error);
46 | thread.terminate();
47 | }
48 | if (result !== undefined) {
49 | _postMessage("threadify-return", [result]);
50 | thread.terminate();
51 | }
52 | }
53 | }
54 |
55 | addEventListener("message", _onMessage, false);
56 | };
57 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 |
3 | // Project configuration.
4 | grunt.initConfig({
5 | pkg: grunt.file.readJSON('package.json'),
6 |
7 | browserify: {
8 | dist: {
9 | files: {
10 | 'dist/<%= pkg.name %>.js': ['src/<%= pkg.name %>.js'],
11 | },
12 | options: {
13 | browserifyOptions: {
14 | 'standalone': '<%= pkg.name %>'
15 | }
16 | }
17 | },
18 | testHelpers: {
19 | files: {
20 | 'test/_tmp/helpers.js': ['src/helpers.js'],
21 | },
22 | options: {
23 | browserifyOptions: {
24 | 'standalone': 'testHelpers'
25 | }
26 | }
27 | }
28 | },
29 |
30 | uglify: {
31 | dist: {
32 | files: {
33 | 'dist/<%= pkg.name %>.min.js': ['dist/<%= pkg.name %>.js']
34 | }
35 | }
36 | },
37 |
38 | jshint: {
39 | all: ['src/*.js'],
40 | options: {
41 | futurehostile: true,
42 | freeze: true,
43 | latedef: true,
44 | noarg: true,
45 | nocomma: true,
46 | nonbsp: true,
47 | nonew: true,
48 | undef: true,
49 | node: true,
50 | browser: true,
51 | globals: {
52 | ImageData: false,
53 | postMessage: false
54 | }
55 | }
56 | },
57 |
58 | jscs: {
59 | src: ['src/*.js', 'test/*Spec.js'],
60 | options: {
61 | config: ".jscsrc"
62 | }
63 | },
64 |
65 | jasmine: {
66 | pivotal: {
67 | src: [
68 | 'dist/<%= pkg.name %>.min.js',
69 | 'test/_tmp/*.js'
70 | ],
71 | options: {
72 | specs: 'test/*Spec.js'
73 | }
74 | }
75 | }
76 |
77 | });
78 |
79 | // Load the plugins.
80 | grunt.loadNpmTasks('grunt-browserify');
81 | grunt.loadNpmTasks('grunt-contrib-uglify');
82 | grunt.loadNpmTasks('grunt-contrib-jshint');
83 | grunt.loadNpmTasks('grunt-contrib-jasmine');
84 | grunt.loadNpmTasks("grunt-jscs");
85 |
86 | // Default task(s).
87 | grunt.registerTask('default', ['browserify:dist', 'uglify']);
88 | grunt.registerTask('test', ['jshint', 'jscs', 'browserify:testHelpers', 'browserify:dist' /*, 'jasmine'*/ ]);
89 |
90 | };
91 |
--------------------------------------------------------------------------------
/src/helpers.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |
3 | serializeArgs: function (args) {
4 | "use strict";
5 |
6 | var typedArray = [
7 | "Int8Array",
8 | "Uint8Array",
9 | "Uint8ClampedArray",
10 | "Int16Array",
11 | "Uint16Array",
12 | "Int32Array",
13 | "Uint32Array",
14 | "Float32Array",
15 | "Float64Array"
16 | ];
17 | var serializedArgs = [];
18 | var transferable = [];
19 |
20 | for (var i = 0 ; i < args.length ; i++) {
21 | if (args[i] instanceof Error) {
22 | var obj = {
23 | type: "Error",
24 | value: {name: args[i].name}
25 | };
26 | var keys = Object.getOwnPropertyNames(args[i]);
27 | for (var k = 0 ; k < keys.length ; k++) {
28 | obj.value[keys[k]] = args[i][keys[k]];
29 | }
30 | serializedArgs.push(obj);
31 | } else if (args[i] instanceof DataView) {
32 | transferable.push(args[i].buffer);
33 | serializedArgs.push({
34 | type: "DataView",
35 | value: args[i].buffer
36 | });
37 | } else {
38 | // transferable: ArrayBuffer
39 | if (args[i] instanceof ArrayBuffer) {
40 | transferable.push(args[i]);
41 |
42 | // tranferable: ImageData
43 | } else if ("ImageData" in window && args[i] instanceof ImageData) {
44 | transferable.push(args[i].data.buffer);
45 |
46 | // tranferable: TypedArray
47 | } else {
48 | for (var t = 0 ; t < typedArray.length ; t++) {
49 | if (args[i] instanceof window[typedArray[t]]) {
50 | transferable.push(args[i].buffer);
51 | break;
52 | }
53 | }
54 | }
55 |
56 | serializedArgs.push({
57 | type: "arg",
58 | value: args[i]
59 | });
60 | }
61 | }
62 |
63 | return {
64 | args: serializedArgs,
65 | transferable: transferable
66 | };
67 | },
68 |
69 | unserializeArgs: function (serializedArgs) {
70 | "use strict";
71 |
72 | var args = [];
73 |
74 | for (var i = 0 ; i < serializedArgs.length ; i++) {
75 |
76 | switch (serializedArgs[i].type) {
77 | case "arg":
78 | args.push(serializedArgs[i].value);
79 | break;
80 | case "Error":
81 | var obj = new Error();
82 | for (var key in serializedArgs[i].value) {
83 | obj[key] = serializedArgs[i].value[key];
84 | }
85 | args.push(obj);
86 | break;
87 | case "DataView":
88 | args.push(new DataView(serializedArgs[i].value));
89 | }
90 | }
91 |
92 | return args;
93 | }
94 | };
95 |
--------------------------------------------------------------------------------
/src/job.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var helpers = require("./helpers.js");
4 |
5 | function Job(workerUrl, args) {
6 |
7 | var _this = this;
8 | var _worker = new Worker(workerUrl);
9 |
10 | var callbacks = {
11 | done: null,
12 | failed: null,
13 | terminated: null
14 | };
15 |
16 | var results = {
17 | done: null,
18 | failed: null,
19 | terminated: null
20 | };
21 |
22 | function _postMessage(name, args) {
23 | var serialized = helpers.serializeArgs(args || []);
24 |
25 | var data = {
26 | name: name,
27 | args: serialized.args
28 | };
29 |
30 | _worker.postMessage(data, serialized.transferable);
31 | }
32 |
33 | function _callCallbacks() {
34 | for (var cb in callbacks) {
35 | if (callbacks[cb] && results[cb]) {
36 | callbacks[cb].apply(_this, results[cb]);
37 | results[cb] = null;
38 | }
39 | }
40 | }
41 |
42 | function _onMessage(event) {
43 | var data = event.data || {};
44 | var args = helpers.unserializeArgs(data.args || []);
45 |
46 | switch (data.name) {
47 | case "threadify-return":
48 | results.done = args;
49 | break;
50 | case "threadify-error":
51 | results.failed = args;
52 | break;
53 | case "threadify-terminated":
54 | results.terminated = [];
55 | }
56 | _callCallbacks();
57 | }
58 |
59 | function terminate() {
60 | _worker.terminate();
61 | results.terminated = [];
62 | _callCallbacks();
63 | }
64 |
65 | function _onError(error) {
66 | results.failed = [error];
67 | _callCallbacks();
68 | terminate();
69 | }
70 |
71 | Object.defineProperty(this, "done", {
72 | get: function () {
73 | return callbacks.done;
74 | },
75 | set: function (fn) {
76 | callbacks.done = fn;
77 | _callCallbacks();
78 | },
79 | enumerable: true,
80 | configurable: false
81 | });
82 |
83 | Object.defineProperty(this, "failed", {
84 | get: function () {
85 | return callbacks.failed;
86 | },
87 | set: function (fn) {
88 | callbacks.failed = fn;
89 | _callCallbacks();
90 | },
91 | enumerable: true,
92 | configurable: false
93 | });
94 |
95 | Object.defineProperty(this, "terminated", {
96 | get: function () {
97 | return callbacks.terminated;
98 | },
99 | set: function (fn) {
100 | callbacks.terminated = fn;
101 | _callCallbacks();
102 | },
103 | enumerable: true,
104 | configurable: false
105 | });
106 |
107 | this.terminate = terminate;
108 |
109 | _worker.addEventListener("message", _onMessage.bind(this), false);
110 | _worker.addEventListener("error", _onError.bind(this), false);
111 |
112 | _postMessage("threadify-start", args);
113 | }
114 |
115 | module.exports = Job;
116 |
--------------------------------------------------------------------------------
/dist/threadify.min.js:
--------------------------------------------------------------------------------
1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).threadify=e()}}(function(){return function i(o,s,f){function u(r,e){if(!s[r]){if(!o[r]){var t="function"==typeof require&&require;if(!e&&t)return t(r,!0);if(l)return l(r,!0);var n=new Error("Cannot find module '"+r+"'");throw n.code="MODULE_NOT_FOUND",n}var a=s[r]={exports:{}};o[r][0].call(a.exports,function(e){return u(o[r][1][e]||e)},a,a.exports,i,o,s,f)}return s[r].exports}for(var l="function"==typeof require&&require,e=0;e 0) {
78 | printNewline();
79 |
80 | var specCounts = specCount + ' ' + plural('spec', specCount) + ', ' +
81 | failureCount + ' ' + plural('failure', failureCount);
82 |
83 | if (pendingCount) {
84 | specCounts += ', ' + pendingCount + ' pending ' + plural('spec', pendingCount);
85 | }
86 |
87 | print(specCounts);
88 | } else {
89 | print('No specs found');
90 | }
91 |
92 | printNewline();
93 | var seconds = timer.elapsed() / 1000;
94 | print('Finished in ' + seconds + ' ' + plural('second', seconds));
95 | printNewline();
96 |
97 | for(i = 0; i < failedSuites.length; i++) {
98 | suiteFailureDetails(failedSuites[i]);
99 | }
100 |
101 | onComplete(failureCount === 0);
102 | };
103 |
104 | this.specDone = function(result) {
105 | specCount++;
106 |
107 | if (result.status == 'pending') {
108 | pendingCount++;
109 | print(colored('yellow', '*'));
110 | return;
111 | }
112 |
113 | if (result.status == 'passed') {
114 | print(colored('green', '.'));
115 | return;
116 | }
117 |
118 | if (result.status == 'failed') {
119 | failureCount++;
120 | failedSpecs.push(result);
121 | print(colored('red', 'F'));
122 | }
123 | };
124 |
125 | this.suiteDone = function(result) {
126 | if (result.failedExpectations && result.failedExpectations.length > 0) {
127 | failureCount++;
128 | failedSuites.push(result);
129 | }
130 | };
131 |
132 | return this;
133 |
134 | function printNewline() {
135 | print('\n');
136 | }
137 |
138 | function colored(color, str) {
139 | return showColors ? (ansi[color] + str + ansi.none) : str;
140 | }
141 |
142 | function plural(str, count) {
143 | return count == 1 ? str : str + 's';
144 | }
145 |
146 | function repeat(thing, times) {
147 | var arr = [];
148 | for (var i = 0; i < times; i++) {
149 | arr.push(thing);
150 | }
151 | return arr;
152 | }
153 |
154 | function indent(str, spaces) {
155 | var lines = (str || '').split('\n');
156 | var newArr = [];
157 | for (var i = 0; i < lines.length; i++) {
158 | newArr.push(repeat(' ', spaces).join('') + lines[i]);
159 | }
160 | return newArr.join('\n');
161 | }
162 |
163 | function specFailureDetails(result) {
164 | printNewline();
165 | print(result.fullName);
166 |
167 | for (var i = 0; i < result.failedExpectations.length; i++) {
168 | var failedExpectation = result.failedExpectations[i];
169 | printNewline();
170 | print(indent(failedExpectation.message, 2));
171 | print(indent(failedExpectation.stack, 2));
172 | }
173 |
174 | printNewline();
175 | }
176 |
177 | function suiteFailureDetails(result) {
178 | for (var i = 0; i < result.failedExpectations.length; i++) {
179 | printNewline();
180 | print(colored('red', 'An error was thrown in an afterAll'));
181 | printNewline();
182 | print(colored('red', 'AfterAll ' + result.failedExpectations[i].message));
183 |
184 | }
185 | printNewline();
186 | }
187 | }
188 |
189 | return ConsoleReporter;
190 | };
191 |
--------------------------------------------------------------------------------
/test/workercodeSpec.js:
--------------------------------------------------------------------------------
1 | describe("Threadify worker", function () {
2 |
3 | it("can returns a value", function (done) {
4 | var callbacks = {
5 | done: function () {},
6 | failed: function () {},
7 | terminated: function () {
8 | expect(callbacks.done).toHaveBeenCalledWith("ok");
9 | expect(callbacks.failed).not.toHaveBeenCalled();
10 | expect(callbacks.terminated).toHaveBeenCalled();
11 | done();
12 | }
13 | };
14 |
15 | spyOn(callbacks, "done");
16 | spyOn(callbacks, "failed");
17 | spyOn(callbacks, "terminated").and.callThrough();
18 |
19 | var fn = threadify(function (a) {
20 | return a;
21 | });
22 |
23 | var job = fn("ok");
24 |
25 | job.done = callbacks.done;
26 | job.failed = callbacks.failed;
27 | job.terminated = callbacks.terminated;
28 | });
29 |
30 | it("can returns multiple values", function (done) {
31 | var callbacks = {
32 | done: function () {},
33 | failed: function () {},
34 | terminated: function () {
35 | expect(callbacks.done).toHaveBeenCalledWith("foo", "bar");
36 | expect(callbacks.failed).not.toHaveBeenCalled();
37 | expect(callbacks.terminated).toHaveBeenCalled();
38 | done();
39 | }
40 | };
41 |
42 | spyOn(callbacks, "done");
43 | spyOn(callbacks, "failed");
44 | spyOn(callbacks, "terminated").and.callThrough();
45 |
46 | var fn = threadify(function (a, b) {
47 | this.return(a, b);
48 | });
49 |
50 | var job = fn("foo", "bar");
51 |
52 | job.done = callbacks.done;
53 | job.failed = callbacks.failed;
54 | job.terminated = callbacks.terminated;
55 | });
56 |
57 | it("can returns a value (async)", function (done) {
58 | var callbacks = {
59 | done: function () {},
60 | failed: function () {},
61 | terminated: function () {
62 | expect(callbacks.done).toHaveBeenCalledWith("foo", "bar");
63 | expect(callbacks.failed).not.toHaveBeenCalled();
64 | expect(callbacks.terminated).toHaveBeenCalled();
65 | done();
66 | }
67 | };
68 |
69 | spyOn(callbacks, "done");
70 | spyOn(callbacks, "failed");
71 | spyOn(callbacks, "terminated").and.callThrough();
72 |
73 | var fn = threadify(function (a, b) {
74 | var thread = this;
75 | setTimeout(function () {
76 | thread.return(a, b);
77 | }, 100);
78 | });
79 |
80 | var job = fn("foo", "bar");
81 |
82 | job.done = callbacks.done;
83 | job.failed = callbacks.failed;
84 | job.terminated = callbacks.terminated;
85 | });
86 |
87 | it("is not terminated if it does not return nothing", function (done) {
88 | var callbacks = {
89 | done: function () {},
90 | failed: function () {},
91 | terminated: function () {}
92 | };
93 |
94 | spyOn(callbacks, "done");
95 | spyOn(callbacks, "failed");
96 | spyOn(callbacks, "terminated").and.callThrough();
97 |
98 | var fn = threadify(function () {});
99 |
100 | var job = fn();
101 |
102 | job.done = callbacks.done;
103 | job.failed = callbacks.failed;
104 | job.terminated = callbacks.terminated;
105 |
106 | setTimeout(function () {
107 | expect(callbacks.done).not.toHaveBeenCalled();
108 | expect(callbacks.failed).not.toHaveBeenCalled();
109 | expect(callbacks.terminated).not.toHaveBeenCalled();
110 | job.terminate();
111 | done();
112 | }, 100);
113 | });
114 |
115 | it("can terminates itself", function (done) {
116 | var callbacks = {
117 | done: function () {},
118 | failed: function () {},
119 | terminated: function () {
120 | expect(callbacks.done).not.toHaveBeenCalled();
121 | expect(callbacks.failed).not.toHaveBeenCalled();
122 | expect(callbacks.terminated).toHaveBeenCalled();
123 | done();
124 | }
125 | };
126 |
127 | spyOn(callbacks, "done");
128 | spyOn(callbacks, "failed");
129 | spyOn(callbacks, "terminated").and.callThrough();
130 |
131 | var fn = threadify(function () {
132 | this.terminate();
133 | });
134 |
135 | var job = fn();
136 |
137 | job.done = callbacks.done;
138 | job.failed = callbacks.failed;
139 | job.terminated = callbacks.terminated;
140 | });
141 |
142 | it("can reports non fatal error without terminating", function (done) {
143 | var callbacks = {
144 | done: function () {},
145 | failed: function () {},
146 | terminated: function () {}
147 | };
148 |
149 | spyOn(callbacks, "done");
150 | spyOn(callbacks, "failed");
151 | spyOn(callbacks, "terminated").and.callThrough();
152 |
153 | var fn = threadify(function () {
154 | this.error("test-error");
155 | });
156 |
157 | var job = fn();
158 |
159 | job.done = callbacks.done;
160 | job.failed = callbacks.failed;
161 | job.terminated = callbacks.terminated;
162 |
163 | setTimeout(function () {
164 | expect(callbacks.done).not.toHaveBeenCalled();
165 | expect(callbacks.failed).toHaveBeenCalledWith("test-error");
166 | expect(callbacks.terminated).not.toHaveBeenCalled();
167 | job.terminate();
168 | done();
169 | }, 100);
170 | });
171 |
172 | it("can reports errors thrown in the threadified function (and terminating itself)", function (done) {
173 | var callbacks = {
174 | done: function () {},
175 | failed: function () {},
176 | terminated: function () {
177 | expect(callbacks.done).not.toHaveBeenCalled();
178 | expect(callbacks.failed).toHaveBeenCalledWith("test-error");
179 | expect(callbacks.terminated).toHaveBeenCalled();
180 | done();
181 | }
182 | };
183 |
184 | spyOn(callbacks, "done");
185 | spyOn(callbacks, "failed");
186 | spyOn(callbacks, "terminated").and.callThrough();
187 |
188 | var fn = threadify(function () {
189 | throw "test-error";
190 | });
191 |
192 | var job = fn();
193 |
194 | job.done = callbacks.done;
195 | job.failed = callbacks.failed;
196 | job.terminated = callbacks.terminated;
197 | });
198 |
199 | });
200 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Threadify
2 |
3 | [  ](https://travis-ci.org/flozz/threadify)
4 | [  ](https://www.npmjs.com/package/threadify)
5 | [  ](https://github.com/flozz/threadify/blob/master/LICENSE)
6 |
7 | Threadify is a browser-side Javascript library that allows you to run any **Javascript function** into a **web worker**.
8 |
9 | Example of a threadified function:
10 |
11 | ```javascript
12 | var myFunction = threadify(function (param1, param2) {
13 | // This will be executed inside a web worker
14 | console.log("Hello from the worker");
15 | return param1 + param2;
16 | });
17 |
18 | var job = myFunction("foo", "bar");
19 |
20 | job.done = function (result) {
21 | console.log(result);
22 | };
23 | ```
24 |
25 |
26 | ## Getting Started
27 |
28 | ### Getting Threadify
29 |
30 | #### Standalone Version
31 |
32 | First download the [Threadify zip][dl-zip] or clone the repository.
33 |
34 | Then include the `threadify.js` or `threadify.min.js` (from the `dist` folder) in you HTML page:
35 |
36 | ```html
37 |
38 | ```
39 |
40 |
41 | #### NPM and Browserify
42 |
43 | First install the `threadify` package:
44 |
45 | npm install --save threadify
46 |
47 | Then include it where you need it:
48 |
49 | ```javascript
50 | var threadify = require("threadify");
51 | ```
52 |
53 |
54 | ### Threadifying a Function
55 |
56 | To run a function in a web worker, you have to threadify it:
57 |
58 | ```javascript
59 | var myFunction = threadify(function (param1, param2) {
60 | // The code of this function will be executed inside a web worker
61 | return param1 + param2;
62 | });
63 | ```
64 |
65 | Then, to run your threadified function, you just have to call it as any other function:
66 |
67 | ```javascript
68 | var job = myFunction("param1", "param2");
69 | ```
70 |
71 | When you call your threadified function, it returns a `Job` object that allow you to control the worker and to retrieve values returned by the function.
72 |
73 | To get the result of your function, just define a `done` callback on the `Job` object:
74 |
75 | ```javascript
76 | // this function will be called once the function return something.
77 | job.done = function (result) {
78 | console.log(result);
79 | };
80 | ```
81 |
82 |
83 | ### Returning Values
84 |
85 | The simplest way to return a value from the threadified function is to use the `return` keyword as usual:
86 |
87 | ```javascript
88 | var myFunction = threadify(function (param1, param2) {
89 | return param1 + param2;
90 | });
91 | ```
92 |
93 | But if you have asynchrone code in your function (or if you want to return more than one value), you will not be able to use the `return` keyword. But you can use the `this.return()` method instead:
94 |
95 | ```javascript
96 | var myFunction = threadify(function (param1, param2) {
97 | this.return(param1, param2);
98 | });
99 | ```
100 |
101 | Be careful of the `this` context when you call `this.return()` from a callback. For instance, the following code **will not work** because of the wrong `this` context:
102 |
103 | ```javascript
104 | var myFunction = threadify(function (param1, param2) {
105 | setTimeout(function () {
106 | this.return(param1, param2);
107 | }, 1000);
108 | });
109 | ```
110 |
111 | To fix it, you can modify it like this:
112 |
113 | ```javascript
114 | var myFunction = threadify(function (param1, param2) {
115 | var thread = this;
116 | setTimeout(function () {
117 | thread.return(param1, param2);
118 | }, 1000);
119 | });
120 | ```
121 |
122 | The worker still alive until you return something or you terminate it explicitly.
123 |
124 | **NOTE:** if you just use the `return` keyword without any value, the worker will not be terminated.
125 |
126 |
127 | ## About Web Workers Limitations
128 |
129 | Web workers are executed in an other thread than your main script / page, this causes some limitations detailed bellow.
130 |
131 | ### Accessible Objects
132 |
133 | From the worker (the threadified function) you cannot access to all browser's objects you are used to. For instance, this is some objects that are not accessible:
134 |
135 | * the `window` object,
136 |
137 | * the `document` object ,
138 |
139 | * DOM / DOM elements (you cannot manipulate the HTML of your page),
140 |
141 | * other functions / classes / objects / variables of your main thread (you cannot access to anything outside your threadified function),
142 |
143 | * ...
144 |
145 | ### Arguements and Returned Values
146 |
147 | You cannot send / return any type of argument to / from a worker. Threadify allows you to send / return:
148 |
149 | * Simple types / values (copied):
150 | * `Number`
151 | * `String`
152 | * `Boolean`
153 | * `Array`¹
154 | * `Object`¹
155 | * `undefined`
156 | * `null`
157 | * `Infinity`
158 | * `NaN`
159 |
160 | * `Blob`, ~~`File`~~ (copied) *TODO: check if File works*
161 |
162 | * `Error` objects (copied)
163 |
164 | * `ArrayBuffer` (transfered)
165 |
166 | * Typed arrays (transfered):
167 | * `Int8Array`
168 | * `Uint8Array`
169 | * `Uint8ClampedArray`
170 | * `Int16Array`
171 | * `Uint16Array`
172 | * `Int32Array`
173 | * `Uint32Array`
174 | * `Float32Array`
175 | * `Float64Array`
176 |
177 | * `DataView` (transfered)
178 |
179 | * Canvas `ImageData` (transfered)
180 |
181 | Depending on their types, arguments can be copied or transfered to the worker. A transfered argument means that its access is transfered to the worker and that it will no more be accessible from the main thread.
182 |
183 | **NOTE¹:** Only if they contains allowed types (no class, no function,...)
184 |
185 |
186 | ## Job API
187 |
188 | You get the `Job` object each time you call a threadified function:
189 |
190 | ```javascript
191 | var myFunction = threadify(function (param1, param2) {
192 | // The code of this function will be executed inside a web worker
193 | return param1 + param2;
194 | });
195 |
196 | var job = myFunction("foo", "bar");
197 | ```
198 |
199 | ### Methods
200 |
201 | * `job.terminate()`: terminates immediately the worker without letting it an opportunity to finish its operations.
202 |
203 | ```javascript
204 | job.terminate();
205 | ```
206 |
207 | ### Callbacks
208 |
209 | * `job.done = function (result) {}`: called when the threadified function returns something. Please note that the worker is terminated right after it returns the value.
210 |
211 | * `job.failed = function (error) {}`: called when an error occurred in the threadified function. Please note that the worker is not always terminated when an error occurred.
212 |
213 | * `job.terminated = function () {}`: called when the worker is terminated.
214 |
215 |
216 | ## Thread API
217 |
218 | Inside the threadified function, you have access to a `Thread` object through the `this` context.
219 |
220 | ```javascript
221 | var myFunction = threadify(function (param1, param2) {
222 | var thread = this;
223 | });
224 | ```
225 |
226 | ### Methods
227 |
228 | * `thread.terminate()`: terminates immediately the worker.
229 |
230 | * `thread.error(error)`: Reports an error (this will calls the `job.failed` callback function). Please not that calling this method will not terminate the worker.
231 |
232 | * `thread.return(param [, param2 [, ...]])`: return one or more values (this will call the `job.done` callback function) and terminates the worker.
233 |
234 |
235 | ## Hacking
236 |
237 | Threadify is built using [Grunt][grunt] and [Browserify][browserify]. To start hacking Threadify, you will have to install few tools.
238 |
239 |
240 | ### Installing Dependencies
241 |
242 | To build Threadify, you will first have to install [Node.js][nodejs] (or [io.js][iojs]).
243 |
244 | **NOTE:** If you are on Ubuntu / Debian Linux you must install the `nodejs-legacy` package.
245 |
246 | Next, install globally the `grunt-cli` npm package:
247 |
248 | npm install -g grunt-cli
249 |
250 | Then install the required dependencies:
251 |
252 | npm install
253 |
254 |
255 | ### Building Threadify
256 |
257 | Once the build stuff and dependencies installed, you just have to run the `grunt` command to build Threadify:
258 |
259 | grunt
260 |
261 | All generated files are in the `dist` folder.
262 |
263 |
264 | ### Coding Style
265 |
266 | Threadify follows the [Yandex Javascript CodeStyle][codestyle-yandex] **EXCEPT** for the quote marks where **double quotes** (`"`) are used.
267 |
268 | You can automatically check that your code follows the conventions by using this command:
269 |
270 | grunt jscs
271 |
272 |
273 | ### Running Tests
274 |
275 | To run the tests, first check that the javascript is well formed and that it follows the coding style:
276 |
277 | grunt jshint jscs
278 |
279 | Then, open the following page in your web browser:
280 |
281 | * `test/SpecRunner.html`
282 |
283 |
284 | ## Changelog
285 |
286 | * **0.2.4:** Cleaner package (avoid including useless files)
287 | * **0.2.3:**
288 | * Fixes issue with minification (#6)
289 | * Updates dependencies
290 | * **0.2.2:** Avoid minification issue (#3)
291 | * **0.2.1:** Fix for old browsers that do not implement `ImageData`
292 | * **0.2.0:** Allow to tranfer / copy more types between the main thread and the worker
293 | * **0.1.0:** First release: basic functionalities required to build and run workers
294 |
295 |
296 |
297 | [dl-zip]: https://github.com/flozz/threadify/archive/master.zip
298 | [grunt]: http://gruntjs.com/
299 | [browserify]: http://browserify.org/
300 | [nodejs]: https://nodejs.org/
301 | [iojs]: https://iojs.org/
302 | [codestyle-yandex]: https://github.com/yandex/codestyle/blob/master/javascript.md
303 |
--------------------------------------------------------------------------------
/dist/threadify.js:
--------------------------------------------------------------------------------
1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.threadify = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i 0) {
207 | statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount);
208 | if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); }
209 | statusBarClassName += (failureCount > 0) ? 'failed' : 'passed';
210 | } else {
211 | statusBarClassName += 'skipped';
212 | statusBarMessage += 'No specs found';
213 | }
214 |
215 | alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage));
216 |
217 | for(i = 0; i < failedSuites.length; i++) {
218 | var failedSuite = failedSuites[i];
219 | for(var j = 0; j < failedSuite.failedExpectations.length; j++) {
220 | var errorBarMessage = 'AfterAll ' + failedSuite.failedExpectations[j].message;
221 | var errorBarClassName = 'bar errored';
222 | alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessage));
223 | }
224 | }
225 |
226 | var results = find('.results');
227 | results.appendChild(summary);
228 |
229 | summaryList(topResults, summary);
230 |
231 | function summaryList(resultsTree, domParent) {
232 | var specListNode;
233 | for (var i = 0; i < resultsTree.children.length; i++) {
234 | var resultNode = resultsTree.children[i];
235 | if (resultNode.type == 'suite') {
236 | var suiteListNode = createDom('ul', {className: 'suite', id: 'suite-' + resultNode.result.id},
237 | createDom('li', {className: 'suite-detail'},
238 | createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description)
239 | )
240 | );
241 |
242 | summaryList(resultNode, suiteListNode);
243 | domParent.appendChild(suiteListNode);
244 | }
245 | if (resultNode.type == 'spec') {
246 | if (domParent.getAttribute('class') != 'specs') {
247 | specListNode = createDom('ul', {className: 'specs'});
248 | domParent.appendChild(specListNode);
249 | }
250 | var specDescription = resultNode.result.description;
251 | if(noExpectations(resultNode.result)) {
252 | specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
253 | }
254 | if(resultNode.result.status === 'pending' && resultNode.result.pendingReason !== '') {
255 | specDescription = specDescription + ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason;
256 | }
257 | specListNode.appendChild(
258 | createDom('li', {
259 | className: resultNode.result.status,
260 | id: 'spec-' + resultNode.result.id
261 | },
262 | createDom('a', {href: specHref(resultNode.result)}, specDescription)
263 | )
264 | );
265 | }
266 | }
267 | }
268 |
269 | if (failures.length) {
270 | alert.appendChild(
271 | createDom('span', {className: 'menu bar spec-list'},
272 | createDom('span', {}, 'Spec List | '),
273 | createDom('a', {className: 'failures-menu', href: '#'}, 'Failures')));
274 | alert.appendChild(
275 | createDom('span', {className: 'menu bar failure-list'},
276 | createDom('a', {className: 'spec-list-menu', href: '#'}, 'Spec List'),
277 | createDom('span', {}, ' | Failures ')));
278 |
279 | find('.failures-menu').onclick = function() {
280 | setMenuModeTo('failure-list');
281 | };
282 | find('.spec-list-menu').onclick = function() {
283 | setMenuModeTo('spec-list');
284 | };
285 |
286 | setMenuModeTo('failure-list');
287 |
288 | var failureNode = find('.failures');
289 | for (var i = 0; i < failures.length; i++) {
290 | failureNode.appendChild(failures[i]);
291 | }
292 | }
293 | };
294 |
295 | return this;
296 |
297 | function find(selector) {
298 | return getContainer().querySelector('.jasmine_html-reporter ' + selector);
299 | }
300 |
301 | function clearPrior() {
302 | // return the reporter
303 | var oldReporter = find('');
304 |
305 | if(oldReporter) {
306 | getContainer().removeChild(oldReporter);
307 | }
308 | }
309 |
310 | function createDom(type, attrs, childrenVarArgs) {
311 | var el = createElement(type);
312 |
313 | for (var i = 2; i < arguments.length; i++) {
314 | var child = arguments[i];
315 |
316 | if (typeof child === 'string') {
317 | el.appendChild(createTextNode(child));
318 | } else {
319 | if (child) {
320 | el.appendChild(child);
321 | }
322 | }
323 | }
324 |
325 | for (var attr in attrs) {
326 | if (attr == 'className') {
327 | el[attr] = attrs[attr];
328 | } else {
329 | el.setAttribute(attr, attrs[attr]);
330 | }
331 | }
332 |
333 | return el;
334 | }
335 |
336 | function pluralize(singular, count) {
337 | var word = (count == 1 ? singular : singular + 's');
338 |
339 | return '' + count + ' ' + word;
340 | }
341 |
342 | function specHref(result) {
343 | return addToExistingQueryString('spec', result.fullName);
344 | }
345 |
346 | function defaultQueryString(key, value) {
347 | return '?' + key + '=' + value;
348 | }
349 |
350 | function setMenuModeTo(mode) {
351 | htmlReporterMain.setAttribute('class', 'jasmine_html-reporter ' + mode);
352 | }
353 |
354 | function noExpectations(result) {
355 | return (result.failedExpectations.length + result.passedExpectations.length) === 0 &&
356 | result.status === 'passed';
357 | }
358 | }
359 |
360 | return HtmlReporter;
361 | };
362 |
363 | jasmineRequire.HtmlSpecFilter = function() {
364 | function HtmlSpecFilter(options) {
365 | var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
366 | var filterPattern = new RegExp(filterString);
367 |
368 | this.matches = function(specName) {
369 | return filterPattern.test(specName);
370 | };
371 | }
372 |
373 | return HtmlSpecFilter;
374 | };
375 |
376 | jasmineRequire.ResultsNode = function() {
377 | function ResultsNode(result, type, parent) {
378 | this.result = result;
379 | this.type = type;
380 | this.parent = parent;
381 |
382 | this.children = [];
383 |
384 | this.addChild = function(result, type) {
385 | this.children.push(new ResultsNode(result, type, this));
386 | };
387 |
388 | this.last = function() {
389 | return this.children[this.children.length - 1];
390 | };
391 | }
392 |
393 | return ResultsNode;
394 | };
395 |
396 | jasmineRequire.QueryString = function() {
397 | function QueryString(options) {
398 |
399 | this.navigateWithNewParam = function(key, value) {
400 | options.getWindowLocation().search = this.fullStringWithNewParam(key, value);
401 | };
402 |
403 | this.fullStringWithNewParam = function(key, value) {
404 | var paramMap = queryStringToParamMap();
405 | paramMap[key] = value;
406 | return toQueryString(paramMap);
407 | };
408 |
409 | this.getParam = function(key) {
410 | return queryStringToParamMap()[key];
411 | };
412 |
413 | return this;
414 |
415 | function toQueryString(paramMap) {
416 | var qStrPairs = [];
417 | for (var prop in paramMap) {
418 | qStrPairs.push(encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop]));
419 | }
420 | return '?' + qStrPairs.join('&');
421 | }
422 |
423 | function queryStringToParamMap() {
424 | var paramStr = options.getWindowLocation().search.substring(1),
425 | params = [],
426 | paramMap = {};
427 |
428 | if (paramStr.length > 0) {
429 | params = paramStr.split('&');
430 | for (var i = 0; i < params.length; i++) {
431 | var p = params[i].split('=');
432 | var value = decodeURIComponent(p[1]);
433 | if (value === 'true' || value === 'false') {
434 | value = JSON.parse(value);
435 | }
436 | paramMap[decodeURIComponent(p[0])] = value;
437 | }
438 | }
439 |
440 | return paramMap;
441 | }
442 |
443 | }
444 |
445 | return QueryString;
446 | };
447 |
--------------------------------------------------------------------------------
/test/jasmine-2.3.4/jasmine.css:
--------------------------------------------------------------------------------
1 | body { overflow-y: scroll; }
2 |
3 | .jasmine_html-reporter { background-color: #eee; padding: 5px; margin: -8px; font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333; }
4 | .jasmine_html-reporter a { text-decoration: none; }
5 | .jasmine_html-reporter a:hover { text-decoration: underline; }
6 | .jasmine_html-reporter p, .jasmine_html-reporter h1, .jasmine_html-reporter h2, .jasmine_html-reporter h3, .jasmine_html-reporter h4, .jasmine_html-reporter h5, .jasmine_html-reporter h6 { margin: 0; line-height: 14px; }
7 | .jasmine_html-reporter .banner, .jasmine_html-reporter .symbol-summary, .jasmine_html-reporter .summary, .jasmine_html-reporter .result-message, .jasmine_html-reporter .spec .description, .jasmine_html-reporter .spec-detail .description, .jasmine_html-reporter .alert .bar, .jasmine_html-reporter .stack-trace { padding-left: 9px; padding-right: 9px; }
8 | .jasmine_html-reporter .banner { position: relative; }
9 | .jasmine_html-reporter .banner .title { background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAAAZCAMAAACGusnyAAACdlBMVEX/////AP+AgICqVaqAQICZM5mAVYCSSZKAQICOOY6ATYCLRouAQICJO4mSSYCIRIiPQICHPIeOR4CGQ4aMQICGPYaLRoCFQ4WKQICPPYWJRYCOQoSJQICNPoSIRICMQoSHQICHRICKQoOHQICKPoOJO4OJQYOMQICMQ4CIQYKLQICIPoKLQ4CKQICNPoKJQISMQ4KJQoSLQYKJQISLQ4KIQoSKQYKIQICIQISMQoSKQYKLQIOLQoOJQYGLQIOKQIOMQoGKQYOLQYGKQIOLQoGJQYOJQIOKQYGJQIOKQoGKQIGLQIKLQ4KKQoGLQYKJQIGKQYKJQIGKQIKJQoGKQYKLQIGKQYKLQIOJQoKKQoOJQYKKQIOJQoKKQoOKQIOLQoKKQYOLQYKJQIOKQoKKQYKKQoKJQYOKQYKLQIOKQoKLQYOKQYKLQIOJQoGKQYKJQYGJQoGKQYKLQoGLQYGKQoGJQYKKQYGJQIKKQoGJQYKLQIKKQYGLQYKKQYGKQYGKQYKJQYOKQoKJQYOKQYKLQYOLQYOKQYKLQYOKQoKKQYKKQYOKQYOJQYKKQYKLQYKKQIKKQoKKQYKKQYKKQoKJQIKKQYKLQYKKQYKKQIKKQYKKQYKKQYKKQIKKQYKJQYGLQYGKQYKKQYKKQYGKQIKKQYGKQYOJQoKKQYOLQYKKQYOKQoKKQYKKQoKKQYKKQYKJQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKJQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKmIDpEAAAA0XRSTlMAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAiIyQlJycoKissLS4wMTQ1Njc4OTo7PDw+P0BCQ0RISUpLTE1OUFNUVVdYWFlaW15fYGFiY2ZnaGlqa2xtb3BxcnN0dnh5ent8fX5/gIGChIWIioyNjo+QkZOUlZaYmZqbnJ2eoKGio6WmqKmsra6vsLGztre4ubq7vL2+wMHDxMjJysvNzs/Q0dLU1tfY2dvc3t/g4eLj5ebn6Onq6+zt7u/w8vP09fb3+Pn6+/z9/vkVQXAAAAMaSURBVHhe5dXxV1N1GMfxz2ABbDgIAm5VDJOyVDIJLUMaVpBWUZUaGbmqoGpZRSiGiRWp6KoZ5AB0ZY50RImZQIlahKkMYXv/R90dBvET/rJfOr3Ouc8v99zPec59zvf56j+vYKlViSf7250X4Mr3O29Tgq08BdGB4DhcekEJ5YkQKFsgWZdtj9JpV+I8xPjLFqkrsEIqO8PHSpis36jWazcqjEsfJjkvRssVU37SdIOu4XCf5vEJPsnwJpnRNU9JmxhMk8l1gehIrq7hTFjzOD+Vf88629qKMJVNltInFeRexRQyJlNeqd1iGDlSzrIUIyXbyFfm3RYprcQRe7lqtWyGYbfc6dT0R2vmdOOkX3u55C1rP37ftiH+tDby4r/RBT0w8TyEkr+epB9XgPDmSYYWbrhCuFYaIyw3fDQAXTnSkh+ANofiHmWf9l+FY1I90FdQTetstO00o23novzVsJ7uB3/C5TkbjRwZ5JerwV4iRWq9HFbFMaK/d0TYqayRiQPuIxxS3Bu8JWU90/60tKi7vkhaznez0a/TbVOKj5CaOZh6fWG6/Lyv9B/ZLR1gw/S/fpbeVD3MCW1li6SvWDOn65tr99/uvWtBS0XDm4s1t+sOHpG0kpBKx/l77wOSnxLpcx6TXmXLTPQOKYOf9Q1dfr8/SJ2mFdCvl1Yl93DiHUZvXeLJbGSzYu5gVJ2slbSakOR8dxCq5adQ2oFLqsE9Ex3L4qQO0eOPeU5x56bypXp4onSEb5OkICX6lDat55TeoztNKQcJaakrz9KCb95oD69IKq+yKW4XPjknaS52V0TZqE2cTtXjcHSCRmUO88e+85hj3EP74i9p8pylw7lxgMDyyl6OV7ZejnjNMfatu87LxRbH0IS35gt2a4ZjmGpVBdKK3Wr6INk8jWWSGqbA55CKgjBRC6E9w78ydTg3ABS3AFV1QN0Y4Aa2pgEjWnQURj9L0ayK6R2ysEqxHUKzYnLvvyU+i9KM2JHJzE4vyZOyDcOwOsySajeLPc8sNvPJkFlyJd20wpqAzZeAfZ3oWybxd+P/3j+SG3uSBdf2VQAAAABJRU5ErkJggg==') no-repeat; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICB3aWR0aD0iNjgxLjk2MjUyIgogICBoZWlnaHQ9IjE4Ny41IgogICBpZD0ic3ZnMiIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhOCI+PHJkZjpSREY+PGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PjxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz48L2NjOldvcms+PC9yZGY6UkRGPjwvbWV0YWRhdGE+PGRlZnMKICAgICBpZD0iZGVmczYiPjxjbGlwUGF0aAogICAgICAgaWQ9ImNsaXBQYXRoMTgiPjxwYXRoCiAgICAgICAgIGQ9Ik0gMCwxNTAwIDAsMCBsIDU0NTUuNzQsMCAwLDE1MDAgTCAwLDE1MDAgeiIKICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgaWQ9InBhdGgyMCIgLz48L2NsaXBQYXRoPjwvZGVmcz48ZwogICAgIHRyYW5zZm9ybT0ibWF0cml4KDEuMjUsMCwwLC0xLjI1LDAsMTg3LjUpIgogICAgIGlkPSJnMTAiPjxnCiAgICAgICB0cmFuc2Zvcm09InNjYWxlKDAuMSwwLjEpIgogICAgICAgaWQ9ImcxMiI+PGcKICAgICAgICAgaWQ9ImcxNCI+PGcKICAgICAgICAgICBjbGlwLXBhdGg9InVybCgjY2xpcFBhdGgxOCkiCiAgICAgICAgICAgaWQ9ImcxNiI+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTU0NCw1OTkuNDM0IGMgMC45MiwtNDAuMzUyIDI1LjY4LC04MS42MDIgNzEuNTMsLTgxLjYwMiAyNy41MSwwIDQ3LjY4LDEyLjgzMiA2MS40NCwzNS43NTQgMTIuODMsMjIuOTMgMTIuODMsNTYuODUyIDEyLjgzLDgyLjUyNyBsIDAsMzI5LjE4NCAtNzEuNTIsMCAwLDEwNC41NDMgMjY2LjgzLDAgMCwtMTA0LjU0MyAtNzAuNiwwIDAsLTM0NC43NyBjIDAsLTU4LjY5MSAtMy42OCwtMTA0LjUzMSAtNDQuOTMsLTE1Mi4yMTggLTM2LjY4LC00Mi4xOCAtOTYuMjgsLTY2LjAyIC0xNTMuMTQsLTY2LjAyIC0xMTcuMzcsMCAtMjA3LjI0LDc3Ljk0MSAtMjAyLjY0LDE5Ny4xNDUgbCAxMzAuMiwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMjIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDIzMDEuNCw2NjIuNjk1IGMgMCw4MC43MDMgLTY2Ljk0LDE0NS44MTMgLTE0Ny42MywxNDUuODEzIC04My40NCwwIC0xNDcuNjMsLTY4Ljc4MSAtMTQ3LjYzLC0xNTEuMzAxIDAsLTc5Ljc4NSA2Ni45NCwtMTQ1LjgwMSAxNDUuOCwtMTQ1LjgwMSA4NC4zNSwwIDE0OS40Niw2Ny44NTIgMTQ5LjQ2LDE1MS4yODkgeiBtIC0xLjgzLC0xODEuNTQ3IGMgLTM1Ljc3LC01NC4wOTcgLTkzLjUzLC03OC44NTkgLTE1Ny43MiwtNzguODU5IC0xNDAuMywwIC0yNTEuMjQsMTE2LjQ0OSAtMjUxLjI0LDI1NC45MTggMCwxNDIuMTI5IDExMy43LDI2MC40MSAyNTYuNzQsMjYwLjQxIDYzLjI3LDAgMTE4LjI5LC0yOS4zMzYgMTUyLjIyLC04Mi41MjMgbCAwLDY5LjY4NyAxNzUuMTQsMCAwLC0xMDQuNTI3IC02MS40NCwwIDAsLTI4MC41OTggNjEuNDQsMCAwLC0xMDQuNTI3IC0xNzUuMTQsMCAwLDY2LjAxOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAyNjIyLjMzLDU1Ny4yNTggYyAzLjY3LC00NC4wMTYgMzMuMDEsLTczLjM0OCA3OC44NiwtNzMuMzQ4IDMzLjkzLDAgNjYuOTMsMjMuODI0IDY2LjkzLDYwLjUwNCAwLDQ4LjYwNiAtNDUuODQsNTYuODU2IC04My40NCw2Ni45NDEgLTg1LjI4LDIyLjAwNCAtMTc4LjgxLDQ4LjYwNiAtMTc4LjgxLDE1NS44NzkgMCw5My41MzYgNzguODYsMTQ3LjYzMyAxNjUuOTgsMTQ3LjYzMyA0NCwwIDgzLjQzLC05LjE3NiAxMTAuOTQsLTQ0LjAwOCBsIDAsMzMuOTIyIDgyLjUzLDAgMCwtMTMyLjk2NSAtMTA4LjIxLDAgYyAtMS44MywzNC44NTYgLTI4LjQyLDU3Ljc3NCAtNjMuMjYsNTcuNzc0IC0zMC4yNiwwIC02Mi4zNSwtMTcuNDIyIC02Mi4zNSwtNTEuMzQ4IDAsLTQ1Ljg0NyA0NC45MywtNTUuOTMgODAuNjksLTY0LjE4IDg4LjAyLC0yMC4xNzUgMTgyLjQ3LC00Ny42OTUgMTgyLjQ3LC0xNTcuNzM0IDAsLTk5LjAyNyAtODMuNDQsLTE1NC4wMzkgLTE3NS4xMywtMTU0LjAzOSAtNDkuNTMsMCAtOTQuNDYsMTUuNTgyIC0xMjYuNTUsNTMuMTggbCAwLC00MC4zNCAtODUuMjcsMCAwLDE0Mi4xMjkgMTE0LjYyLDAiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGgyNiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMjk4OC4xOCw4MDAuMjU0IC02My4yNiwwIDAsMTA0LjUyNyAxNjUuMDUsMCAwLC03My4zNTUgYyAzMS4xOCw1MS4zNDcgNzguODYsODUuMjc3IDE0MS4yMSw4NS4yNzcgNjcuODUsMCAxMjQuNzEsLTQxLjI1OCAxNTIuMjEsLTEwMi42OTkgMjYuNiw2Mi4zNTEgOTIuNjIsMTAyLjY5OSAxNjAuNDcsMTAyLjY5OSA1My4xOSwwIDEwNS40NiwtMjIgMTQxLjIxLC02Mi4zNTEgMzguNTIsLTQ0LjkzOCAzOC41MiwtOTMuNTMyIDM4LjUyLC0xNDkuNDU3IGwgMCwtMTg1LjIzOSA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40MiwwIDAsMTA0LjUyNyA2My4yOCwwIDAsMTU3LjcxNSBjIDAsMzIuMTAyIDAsNjAuNTI3IC0xNC42Nyw4OC45NTcgLTE4LjM0LDI2LjU4MiAtNDguNjEsNDAuMzQ0IC03OS43Nyw0MC4zNDQgLTMwLjI2LDAgLTYzLjI4LC0xMi44NDQgLTgyLjUzLC0zNi42NzIgLTIyLjkzLC0yOS4zNTUgLTIyLjkzLC01Ni44NjMgLTIyLjkzLC05Mi42MjkgbCAwLC0xNTcuNzE1IDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM4LjQxLDAgMCwxMDQuNTI3IDYzLjI4LDAgMCwxNTAuMzgzIGMgMCwyOS4zNDggMCw2Ni4wMjMgLTE0LjY3LDkxLjY5OSAtMTUuNTksMjkuMzM2IC00Ny42OSw0NC45MzQgLTgwLjcsNDQuOTM0IC0zMS4xOCwwIC01Ny43NywtMTEuMDA4IC03Ny45NCwtMzUuNzc0IC0yNC43NywtMzAuMjUzIC0yNi42LC02Mi4zNDMgLTI2LjYsLTk5Ljk0MSBsIDAsLTE1MS4zMDEgNjMuMjcsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNiwwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAzOTk4LjY2LDk1MS41NDcgLTExMS44NywwIDAsMTE4LjI5MyAxMTEuODcsMCAwLC0xMTguMjkzIHogbSAwLC00MzEuODkxIDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM5LjMzLDAgMCwxMDQuNTI3IDY0LjE5LDAgMCwyODAuNTk4IC02My4yNywwIDAsMTA0LjUyNyAxNzUuMTQsMCAwLC0zODUuMTI1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzAiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDQxNTkuMTIsODAwLjI1NCAtNjMuMjcsMCAwLDEwNC41MjcgMTc1LjE0LDAgMCwtNjkuNjg3IGMgMjkuMzUsNTQuMTAxIDg0LjM2LDgwLjY5OSAxNDQuODcsODAuNjk5IDUzLjE5LDAgMTA1LjQ1LC0yMi4wMTYgMTQxLjIyLC02MC41MjcgNDAuMzQsLTQ0LjkzNCA0MS4yNiwtODguMDMyIDQxLjI2LC0xNDMuOTU3IGwgMCwtMTkxLjY1MyA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40LDAgMCwxMDQuNTI3IDYzLjI2LDAgMCwxNTguNjM3IGMgMCwzMC4yNjIgMCw2MS40MzQgLTE5LjI2LDg4LjAzNSAtMjAuMTcsMjYuNTgyIC01My4xOCwzOS40MTQgLTg2LjE5LDM5LjQxNCAtMzMuOTMsMCAtNjguNzcsLTEzLjc1IC04OC45NCwtNDEuMjUgLTIxLjA5LC0yNy41IC0yMS4wOSwtNjkuNjg3IC0yMS4wOSwtMTAyLjcwNyBsIDAsLTE0Mi4xMjkgNjMuMjYsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNywwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDMyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA1MDgyLjQ4LDcwMy45NjUgYyAtMTkuMjQsNzAuNjA1IC04MS42LDExNS41NDcgLTE1NC4wNCwxMTUuNTQ3IC02Ni4wNCwwIC0xMjkuMywtNTEuMzQ4IC0xNDMuMDUsLTExNS41NDcgbCAyOTcuMDksMCB6IG0gODUuMjcsLTE0NC44ODMgYyAtMzguNTEsLTkzLjUyMyAtMTI5LjI3LC0xNTYuNzkzIC0yMzEuMDUsLTE1Ni43OTMgLTE0My4wNywwIC0yNTcuNjgsMTExLjg3MSAtMjU3LjY4LDI1NS44MzYgMCwxNDQuODgzIDEwOS4xMiwyNjEuMzI4IDI1NC45MSwyNjEuMzI4IDY3Ljg3LDAgMTM1LjcyLC0zMC4yNTggMTgzLjM5LC03OC44NjMgNDguNjIsLTUxLjM0NCA2OC43OSwtMTEzLjY5NSA2OC43OSwtMTgzLjM4MyBsIC0zLjY3LC0zOS40MzQgLTM5Ni4xMywwIGMgMTQuNjcsLTY3Ljg2MyA3Ny4wMywtMTE3LjM2MyAxNDYuNzIsLTExNy4zNjMgNDguNTksMCA5MC43NiwxOC4zMjggMTE4LjI4LDU4LjY3MiBsIDExNi40NCwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDY5MC44OTUsODUwLjcwMyA5MC43NSwwIDIyLjU0MywzMS4wMzUgMCwyNDMuMTIyIC0xMzUuODI5LDAgMCwtMjQzLjE0MSAyMi41MzYsLTMxLjAxNiIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDM2IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA2MzIuMzk1LDc0Mi4yNTggMjguMDM5LDg2LjMwNCAtMjIuNTUxLDMxLjA0IC0yMzEuMjIzLDc1LjEyOCAtNDEuOTc2LC0xMjkuMTgzIDIzMS4yNTcsLTc1LjEzNyAzNi40NTQsMTEuODQ4IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzgiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDcxNy40NDksNjUzLjEwNSAtNzMuNDEsNTMuMzYgLTM2LjQ4OCwtMTEuODc1IC0xNDIuOTAzLC0xOTYuNjkyIDEwOS44ODMsLTc5LjgyOCAxNDIuOTE4LDE5Ni43MDMgMCwzOC4zMzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0MCIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gODI4LjUyLDcwNi40NjUgLTczLjQyNiwtNTMuMzQgMC4wMTEsLTM4LjM1OSBMIDg5OC4wMDQsNDE4LjA3IDEwMDcuOSw0OTcuODk4IDg2NC45NzMsNjk0LjYwOSA4MjguNTIsNzA2LjQ2NSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA4MTIuMDg2LDgyOC41ODYgMjguMDU1LC04Ni4zMiAzNi40ODQsLTExLjgzNiAyMzEuMjI1LDc1LjExNyAtNDEuOTcsMTI5LjE4MyAtMjMxLjIzOSwtNzUuMTQgLTIyLjU1NSwtMzEuMDA0IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNDQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDczNi4zMDEsMTMzNS44OCBjIC0zMjMuMDQ3LDAgLTU4NS44NzUsLTI2Mi43OCAtNTg1Ljg3NSwtNTg1Ljc4MiAwLC0zMjMuMTE4IDI2Mi44MjgsLTU4NS45NzcgNTg1Ljg3NSwtNTg1Ljk3NyAzMjMuMDE5LDAgNTg1LjgwOSwyNjIuODU5IDU4NS44MDksNTg1Ljk3NyAwLDMyMy4wMDIgLTI2Mi43OSw1ODUuNzgyIC01ODUuODA5LDU4NS43ODIgbCAwLDAgeiBtIDAsLTExOC42MSBjIDI1Ny45NzIsMCA0NjcuMTg5LC0yMDkuMTMgNDY3LjE4OSwtNDY3LjE3MiAwLC0yNTguMTI5IC0yMDkuMjE3LC00NjcuMzQ4IC00NjcuMTg5LC00NjcuMzQ4IC0yNTguMDc0LDAgLTQ2Ny4yNTQsMjA5LjIxOSAtNDY3LjI1NCw0NjcuMzQ4IDAsMjU4LjA0MiAyMDkuMTgsNDY3LjE3MiA0NjcuMjU0LDQ2Ny4xNzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0NiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTA5MS4xMyw2MTkuODgzIC0xNzUuNzcxLDU3LjEyMSAxMS42MjksMzUuODA4IDE3NS43NjIsLTU3LjEyMSAtMTEuNjIsLTM1LjgwOCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQ4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA4NjYuOTU3LDkwMi4wNzQgODM2LjUsOTI0LjE5OSA5NDUuMTIxLDEwNzMuNzMgOTc1LjU4NiwxMDUxLjYxIDg2Ni45NTcsOTAyLjA3NCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDUwIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA2MDcuNDY1LDkwMy40NDUgNDk4Ljg1NSwxMDUyLjk3IDUyOS4zMiwxMDc1LjEgNjM3LjkzLDkyNS41NjYgNjA3LjQ2NSw5MDMuNDQ1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDM4MC42ODgsNjIyLjEyOSAtMTEuNjI2LDM1LjgwMSAxNzUuNzU4LDU3LjA5IDExLjYyMSwtMzUuODAxIC0xNzUuNzUzLC01Ny4wOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDU0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA3MTYuMjg5LDM3Ni41OSAzNy42NDA2LDAgMCwxODQuODE2IC0zNy42NDA2LDAgMCwtMTg0LjgxNiB6IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTYiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjwvZz48L2c+PC9nPjwvZz48L3N2Zz4=') no-repeat, none; -moz-background-size: 100%; -o-background-size: 100%; -webkit-background-size: 100%; background-size: 100%; display: block; float: left; width: 90px; height: 25px; }
10 | .jasmine_html-reporter .banner .version { margin-left: 14px; position: relative; top: 6px; }
11 | .jasmine_html-reporter #jasmine_content { position: fixed; right: 100%; }
12 | .jasmine_html-reporter .version { color: #aaa; }
13 | .jasmine_html-reporter .banner { margin-top: 14px; }
14 | .jasmine_html-reporter .duration { color: #fff; float: right; line-height: 28px; padding-right: 9px; }
15 | .jasmine_html-reporter .symbol-summary { overflow: hidden; *zoom: 1; margin: 14px 0; }
16 | .jasmine_html-reporter .symbol-summary li { display: inline-block; height: 8px; width: 14px; font-size: 16px; }
17 | .jasmine_html-reporter .symbol-summary li.passed { font-size: 14px; }
18 | .jasmine_html-reporter .symbol-summary li.passed:before { color: #007069; content: "\02022"; }
19 | .jasmine_html-reporter .symbol-summary li.failed { line-height: 9px; }
20 | .jasmine_html-reporter .symbol-summary li.failed:before { color: #ca3a11; content: "\d7"; font-weight: bold; margin-left: -1px; }
21 | .jasmine_html-reporter .symbol-summary li.disabled { font-size: 14px; }
22 | .jasmine_html-reporter .symbol-summary li.disabled:before { color: #bababa; content: "\02022"; }
23 | .jasmine_html-reporter .symbol-summary li.pending { line-height: 17px; }
24 | .jasmine_html-reporter .symbol-summary li.pending:before { color: #ba9d37; content: "*"; }
25 | .jasmine_html-reporter .symbol-summary li.empty { font-size: 14px; }
26 | .jasmine_html-reporter .symbol-summary li.empty:before { color: #ba9d37; content: "\02022"; }
27 | .jasmine_html-reporter .run-options { float: right; margin-right: 5px; border: 1px solid #8a4182; color: #8a4182; position: relative; line-height: 20px; }
28 | .jasmine_html-reporter .run-options .trigger { cursor: pointer; padding: 8px 16px; }
29 | .jasmine_html-reporter .run-options .payload { position: absolute; display: none; right: -1px; border: 1px solid #8a4182; background-color: #eee; white-space: nowrap; padding: 4px 8px; }
30 | .jasmine_html-reporter .run-options .payload.open { display: block; }
31 | .jasmine_html-reporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
32 | .jasmine_html-reporter .bar.failed { background-color: #ca3a11; }
33 | .jasmine_html-reporter .bar.passed { background-color: #007069; }
34 | .jasmine_html-reporter .bar.skipped { background-color: #bababa; }
35 | .jasmine_html-reporter .bar.errored { background-color: #ca3a11; }
36 | .jasmine_html-reporter .bar.menu { background-color: #fff; color: #aaa; }
37 | .jasmine_html-reporter .bar.menu a { color: #333; }
38 | .jasmine_html-reporter .bar a { color: white; }
39 | .jasmine_html-reporter.spec-list .bar.menu.failure-list, .jasmine_html-reporter.spec-list .results .failures { display: none; }
40 | .jasmine_html-reporter.failure-list .bar.menu.spec-list, .jasmine_html-reporter.failure-list .summary { display: none; }
41 | .jasmine_html-reporter .results { margin-top: 14px; }
42 | .jasmine_html-reporter .summary { margin-top: 14px; }
43 | .jasmine_html-reporter .summary ul { list-style-type: none; margin-left: 14px; padding-top: 0; padding-left: 0; }
44 | .jasmine_html-reporter .summary ul.suite { margin-top: 7px; margin-bottom: 7px; }
45 | .jasmine_html-reporter .summary li.passed a { color: #007069; }
46 | .jasmine_html-reporter .summary li.failed a { color: #ca3a11; }
47 | .jasmine_html-reporter .summary li.empty a { color: #ba9d37; }
48 | .jasmine_html-reporter .summary li.pending a { color: #ba9d37; }
49 | .jasmine_html-reporter .summary li.disabled a { color: #bababa; }
50 | .jasmine_html-reporter .description + .suite { margin-top: 0; }
51 | .jasmine_html-reporter .suite { margin-top: 14px; }
52 | .jasmine_html-reporter .suite a { color: #333; }
53 | .jasmine_html-reporter .failures .spec-detail { margin-bottom: 28px; }
54 | .jasmine_html-reporter .failures .spec-detail .description { background-color: #ca3a11; }
55 | .jasmine_html-reporter .failures .spec-detail .description a { color: white; }
56 | .jasmine_html-reporter .result-message { padding-top: 14px; color: #333; white-space: pre; }
57 | .jasmine_html-reporter .result-message span.result { display: block; }
58 | .jasmine_html-reporter .stack-trace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666; border: 1px solid #ddd; background: white; white-space: pre; }
59 |
--------------------------------------------------------------------------------
/test/jasmine-2.3.4/jasmine.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2008-2015 Pivotal Labs
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining
5 | a copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | */
23 | var getJasmineRequireObj = (function (jasmineGlobal) {
24 | var jasmineRequire;
25 |
26 | if (typeof module !== 'undefined' && module.exports) {
27 | jasmineGlobal = global;
28 | jasmineRequire = exports;
29 | } else {
30 | if (typeof window !== 'undefined' && typeof window.toString === 'function' && window.toString() === '[object GjsGlobal]') {
31 | jasmineGlobal = window;
32 | }
33 | jasmineRequire = jasmineGlobal.jasmineRequire = jasmineGlobal.jasmineRequire || {};
34 | }
35 |
36 | function getJasmineRequire() {
37 | return jasmineRequire;
38 | }
39 |
40 | getJasmineRequire().core = function(jRequire) {
41 | var j$ = {};
42 |
43 | jRequire.base(j$, jasmineGlobal);
44 | j$.util = jRequire.util();
45 | j$.errors = jRequire.errors();
46 | j$.Any = jRequire.Any(j$);
47 | j$.Anything = jRequire.Anything(j$);
48 | j$.CallTracker = jRequire.CallTracker();
49 | j$.MockDate = jRequire.MockDate();
50 | j$.Clock = jRequire.Clock();
51 | j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler();
52 | j$.Env = jRequire.Env(j$);
53 | j$.ExceptionFormatter = jRequire.ExceptionFormatter();
54 | j$.Expectation = jRequire.Expectation();
55 | j$.buildExpectationResult = jRequire.buildExpectationResult();
56 | j$.JsApiReporter = jRequire.JsApiReporter();
57 | j$.matchersUtil = jRequire.matchersUtil(j$);
58 | j$.ObjectContaining = jRequire.ObjectContaining(j$);
59 | j$.ArrayContaining = jRequire.ArrayContaining(j$);
60 | j$.pp = jRequire.pp(j$);
61 | j$.QueueRunner = jRequire.QueueRunner(j$);
62 | j$.ReportDispatcher = jRequire.ReportDispatcher();
63 | j$.Spec = jRequire.Spec(j$);
64 | j$.SpyRegistry = jRequire.SpyRegistry(j$);
65 | j$.SpyStrategy = jRequire.SpyStrategy();
66 | j$.StringMatching = jRequire.StringMatching(j$);
67 | j$.Suite = jRequire.Suite(j$);
68 | j$.Timer = jRequire.Timer();
69 | j$.TreeProcessor = jRequire.TreeProcessor();
70 | j$.version = jRequire.version();
71 |
72 | j$.matchers = jRequire.requireMatchers(jRequire, j$);
73 |
74 | return j$;
75 | };
76 |
77 | return getJasmineRequire;
78 | })(this);
79 |
80 | getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
81 | var availableMatchers = [
82 | 'toBe',
83 | 'toBeCloseTo',
84 | 'toBeDefined',
85 | 'toBeFalsy',
86 | 'toBeGreaterThan',
87 | 'toBeLessThan',
88 | 'toBeNaN',
89 | 'toBeNull',
90 | 'toBeTruthy',
91 | 'toBeUndefined',
92 | 'toContain',
93 | 'toEqual',
94 | 'toHaveBeenCalled',
95 | 'toHaveBeenCalledWith',
96 | 'toMatch',
97 | 'toThrow',
98 | 'toThrowError'
99 | ],
100 | matchers = {};
101 |
102 | for (var i = 0; i < availableMatchers.length; i++) {
103 | var name = availableMatchers[i];
104 | matchers[name] = jRequire[name](j$);
105 | }
106 |
107 | return matchers;
108 | };
109 |
110 | getJasmineRequireObj().base = function(j$, jasmineGlobal) {
111 | j$.unimplementedMethod_ = function() {
112 | throw new Error('unimplemented method');
113 | };
114 |
115 | j$.MAX_PRETTY_PRINT_DEPTH = 40;
116 | j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100;
117 | j$.DEFAULT_TIMEOUT_INTERVAL = 5000;
118 |
119 | j$.getGlobal = function() {
120 | return jasmineGlobal;
121 | };
122 |
123 | j$.getEnv = function(options) {
124 | var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options);
125 | //jasmine. singletons in here (setTimeout blah blah).
126 | return env;
127 | };
128 |
129 | j$.isArray_ = function(value) {
130 | return j$.isA_('Array', value);
131 | };
132 |
133 | j$.isString_ = function(value) {
134 | return j$.isA_('String', value);
135 | };
136 |
137 | j$.isNumber_ = function(value) {
138 | return j$.isA_('Number', value);
139 | };
140 |
141 | j$.isA_ = function(typeName, value) {
142 | return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
143 | };
144 |
145 | j$.isDomNode = function(obj) {
146 | return obj.nodeType > 0;
147 | };
148 |
149 | j$.fnNameFor = function(func) {
150 | return func.name || func.toString().match(/^\s*function\s*(\w*)\s*\(/)[1];
151 | };
152 |
153 | j$.any = function(clazz) {
154 | return new j$.Any(clazz);
155 | };
156 |
157 | j$.anything = function() {
158 | return new j$.Anything();
159 | };
160 |
161 | j$.objectContaining = function(sample) {
162 | return new j$.ObjectContaining(sample);
163 | };
164 |
165 | j$.stringMatching = function(expected) {
166 | return new j$.StringMatching(expected);
167 | };
168 |
169 | j$.arrayContaining = function(sample) {
170 | return new j$.ArrayContaining(sample);
171 | };
172 |
173 | j$.createSpy = function(name, originalFn) {
174 |
175 | var spyStrategy = new j$.SpyStrategy({
176 | name: name,
177 | fn: originalFn,
178 | getSpy: function() { return spy; }
179 | }),
180 | callTracker = new j$.CallTracker(),
181 | spy = function() {
182 | var callData = {
183 | object: this,
184 | args: Array.prototype.slice.apply(arguments)
185 | };
186 |
187 | callTracker.track(callData);
188 | var returnValue = spyStrategy.exec.apply(this, arguments);
189 | callData.returnValue = returnValue;
190 |
191 | return returnValue;
192 | };
193 |
194 | for (var prop in originalFn) {
195 | if (prop === 'and' || prop === 'calls') {
196 | throw new Error('Jasmine spies would overwrite the \'and\' and \'calls\' properties on the object being spied upon');
197 | }
198 |
199 | spy[prop] = originalFn[prop];
200 | }
201 |
202 | spy.and = spyStrategy;
203 | spy.calls = callTracker;
204 |
205 | return spy;
206 | };
207 |
208 | j$.isSpy = function(putativeSpy) {
209 | if (!putativeSpy) {
210 | return false;
211 | }
212 | return putativeSpy.and instanceof j$.SpyStrategy &&
213 | putativeSpy.calls instanceof j$.CallTracker;
214 | };
215 |
216 | j$.createSpyObj = function(baseName, methodNames) {
217 | if (j$.isArray_(baseName) && j$.util.isUndefined(methodNames)) {
218 | methodNames = baseName;
219 | baseName = 'unknown';
220 | }
221 |
222 | if (!j$.isArray_(methodNames) || methodNames.length === 0) {
223 | throw 'createSpyObj requires a non-empty array of method names to create spies for';
224 | }
225 | var obj = {};
226 | for (var i = 0; i < methodNames.length; i++) {
227 | obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]);
228 | }
229 | return obj;
230 | };
231 | };
232 |
233 | getJasmineRequireObj().util = function() {
234 |
235 | var util = {};
236 |
237 | util.inherit = function(childClass, parentClass) {
238 | var Subclass = function() {
239 | };
240 | Subclass.prototype = parentClass.prototype;
241 | childClass.prototype = new Subclass();
242 | };
243 |
244 | util.htmlEscape = function(str) {
245 | if (!str) {
246 | return str;
247 | }
248 | return str.replace(/&/g, '&')
249 | .replace(//g, '>');
251 | };
252 |
253 | util.argsToArray = function(args) {
254 | var arrayOfArgs = [];
255 | for (var i = 0; i < args.length; i++) {
256 | arrayOfArgs.push(args[i]);
257 | }
258 | return arrayOfArgs;
259 | };
260 |
261 | util.isUndefined = function(obj) {
262 | return obj === void 0;
263 | };
264 |
265 | util.arrayContains = function(array, search) {
266 | var i = array.length;
267 | while (i--) {
268 | if (array[i] === search) {
269 | return true;
270 | }
271 | }
272 | return false;
273 | };
274 |
275 | util.clone = function(obj) {
276 | if (Object.prototype.toString.apply(obj) === '[object Array]') {
277 | return obj.slice();
278 | }
279 |
280 | var cloned = {};
281 | for (var prop in obj) {
282 | if (obj.hasOwnProperty(prop)) {
283 | cloned[prop] = obj[prop];
284 | }
285 | }
286 |
287 | return cloned;
288 | };
289 |
290 | return util;
291 | };
292 |
293 | getJasmineRequireObj().Spec = function(j$) {
294 | function Spec(attrs) {
295 | this.expectationFactory = attrs.expectationFactory;
296 | this.resultCallback = attrs.resultCallback || function() {};
297 | this.id = attrs.id;
298 | this.description = attrs.description || '';
299 | this.queueableFn = attrs.queueableFn;
300 | this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return {befores: [], afters: []}; };
301 | this.userContext = attrs.userContext || function() { return {}; };
302 | this.onStart = attrs.onStart || function() {};
303 | this.getSpecName = attrs.getSpecName || function() { return ''; };
304 | this.expectationResultFactory = attrs.expectationResultFactory || function() { };
305 | this.queueRunnerFactory = attrs.queueRunnerFactory || function() {};
306 | this.catchingExceptions = attrs.catchingExceptions || function() { return true; };
307 | this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
308 |
309 | if (!this.queueableFn.fn) {
310 | this.pend();
311 | }
312 |
313 | this.result = {
314 | id: this.id,
315 | description: this.description,
316 | fullName: this.getFullName(),
317 | failedExpectations: [],
318 | passedExpectations: [],
319 | pendingReason: ''
320 | };
321 | }
322 |
323 | Spec.prototype.addExpectationResult = function(passed, data, isError) {
324 | var expectationResult = this.expectationResultFactory(data);
325 | if (passed) {
326 | this.result.passedExpectations.push(expectationResult);
327 | } else {
328 | this.result.failedExpectations.push(expectationResult);
329 |
330 | if (this.throwOnExpectationFailure && !isError) {
331 | throw new j$.errors.ExpectationFailed();
332 | }
333 | }
334 | };
335 |
336 | Spec.prototype.expect = function(actual) {
337 | return this.expectationFactory(actual, this);
338 | };
339 |
340 | Spec.prototype.execute = function(onComplete, enabled) {
341 | var self = this;
342 |
343 | this.onStart(this);
344 |
345 | if (!this.isExecutable() || this.markedPending || enabled === false) {
346 | complete(enabled);
347 | return;
348 | }
349 |
350 | var fns = this.beforeAndAfterFns();
351 | var allFns = fns.befores.concat(this.queueableFn).concat(fns.afters);
352 |
353 | this.queueRunnerFactory({
354 | queueableFns: allFns,
355 | onException: function() { self.onException.apply(self, arguments); },
356 | onComplete: complete,
357 | userContext: this.userContext()
358 | });
359 |
360 | function complete(enabledAgain) {
361 | self.result.status = self.status(enabledAgain);
362 | self.resultCallback(self.result);
363 |
364 | if (onComplete) {
365 | onComplete();
366 | }
367 | }
368 | };
369 |
370 | Spec.prototype.onException = function onException(e) {
371 | if (Spec.isPendingSpecException(e)) {
372 | this.pend(extractCustomPendingMessage(e));
373 | return;
374 | }
375 |
376 | if (e instanceof j$.errors.ExpectationFailed) {
377 | return;
378 | }
379 |
380 | this.addExpectationResult(false, {
381 | matcherName: '',
382 | passed: false,
383 | expected: '',
384 | actual: '',
385 | error: e
386 | }, true);
387 | };
388 |
389 | Spec.prototype.disable = function() {
390 | this.disabled = true;
391 | };
392 |
393 | Spec.prototype.pend = function(message) {
394 | this.markedPending = true;
395 | if (message) {
396 | this.result.pendingReason = message;
397 | }
398 | };
399 |
400 | Spec.prototype.getResult = function() {
401 | this.result.status = this.status();
402 | return this.result;
403 | };
404 |
405 | Spec.prototype.status = function(enabled) {
406 | if (this.disabled || enabled === false) {
407 | return 'disabled';
408 | }
409 |
410 | if (this.markedPending) {
411 | return 'pending';
412 | }
413 |
414 | if (this.result.failedExpectations.length > 0) {
415 | return 'failed';
416 | } else {
417 | return 'passed';
418 | }
419 | };
420 |
421 | Spec.prototype.isExecutable = function() {
422 | return !this.disabled;
423 | };
424 |
425 | Spec.prototype.getFullName = function() {
426 | return this.getSpecName(this);
427 | };
428 |
429 | var extractCustomPendingMessage = function(e) {
430 | var fullMessage = e.toString(),
431 | boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage),
432 | boilerplateEnd = boilerplateStart + Spec.pendingSpecExceptionMessage.length;
433 |
434 | return fullMessage.substr(boilerplateEnd);
435 | };
436 |
437 | Spec.pendingSpecExceptionMessage = '=> marked Pending';
438 |
439 | Spec.isPendingSpecException = function(e) {
440 | return !!(e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1);
441 | };
442 |
443 | return Spec;
444 | };
445 |
446 | if (typeof window == void 0 && typeof exports == 'object') {
447 | exports.Spec = jasmineRequire.Spec;
448 | }
449 |
450 | getJasmineRequireObj().Env = function(j$) {
451 | function Env(options) {
452 | options = options || {};
453 |
454 | var self = this;
455 | var global = options.global || j$.getGlobal();
456 |
457 | var totalSpecsDefined = 0;
458 |
459 | var catchExceptions = true;
460 |
461 | var realSetTimeout = j$.getGlobal().setTimeout;
462 | var realClearTimeout = j$.getGlobal().clearTimeout;
463 | this.clock = new j$.Clock(global, function () { return new j$.DelayedFunctionScheduler(); }, new j$.MockDate(global));
464 |
465 | var runnableLookupTable = {};
466 | var runnableResources = {};
467 |
468 | var currentSpec = null;
469 | var currentlyExecutingSuites = [];
470 | var currentDeclarationSuite = null;
471 | var throwOnExpectationFailure = false;
472 |
473 | var currentSuite = function() {
474 | return currentlyExecutingSuites[currentlyExecutingSuites.length - 1];
475 | };
476 |
477 | var currentRunnable = function() {
478 | return currentSpec || currentSuite();
479 | };
480 |
481 | var reporter = new j$.ReportDispatcher([
482 | 'jasmineStarted',
483 | 'jasmineDone',
484 | 'suiteStarted',
485 | 'suiteDone',
486 | 'specStarted',
487 | 'specDone'
488 | ]);
489 |
490 | this.specFilter = function() {
491 | return true;
492 | };
493 |
494 | this.addCustomEqualityTester = function(tester) {
495 | if(!currentRunnable()) {
496 | throw new Error('Custom Equalities must be added in a before function or a spec');
497 | }
498 | runnableResources[currentRunnable().id].customEqualityTesters.push(tester);
499 | };
500 |
501 | this.addMatchers = function(matchersToAdd) {
502 | if(!currentRunnable()) {
503 | throw new Error('Matchers must be added in a before function or a spec');
504 | }
505 | var customMatchers = runnableResources[currentRunnable().id].customMatchers;
506 | for (var matcherName in matchersToAdd) {
507 | customMatchers[matcherName] = matchersToAdd[matcherName];
508 | }
509 | };
510 |
511 | j$.Expectation.addCoreMatchers(j$.matchers);
512 |
513 | var nextSpecId = 0;
514 | var getNextSpecId = function() {
515 | return 'spec' + nextSpecId++;
516 | };
517 |
518 | var nextSuiteId = 0;
519 | var getNextSuiteId = function() {
520 | return 'suite' + nextSuiteId++;
521 | };
522 |
523 | var expectationFactory = function(actual, spec) {
524 | return j$.Expectation.Factory({
525 | util: j$.matchersUtil,
526 | customEqualityTesters: runnableResources[spec.id].customEqualityTesters,
527 | customMatchers: runnableResources[spec.id].customMatchers,
528 | actual: actual,
529 | addExpectationResult: addExpectationResult
530 | });
531 |
532 | function addExpectationResult(passed, result) {
533 | return spec.addExpectationResult(passed, result);
534 | }
535 | };
536 |
537 | var defaultResourcesForRunnable = function(id, parentRunnableId) {
538 | var resources = {spies: [], customEqualityTesters: [], customMatchers: {}};
539 |
540 | if(runnableResources[parentRunnableId]){
541 | resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters);
542 | resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers);
543 | }
544 |
545 | runnableResources[id] = resources;
546 | };
547 |
548 | var clearResourcesForRunnable = function(id) {
549 | spyRegistry.clearSpies();
550 | delete runnableResources[id];
551 | };
552 |
553 | var beforeAndAfterFns = function(suite) {
554 | return function() {
555 | var befores = [],
556 | afters = [];
557 |
558 | while(suite) {
559 | befores = befores.concat(suite.beforeFns);
560 | afters = afters.concat(suite.afterFns);
561 |
562 | suite = suite.parentSuite;
563 | }
564 |
565 | return {
566 | befores: befores.reverse(),
567 | afters: afters
568 | };
569 | };
570 | };
571 |
572 | var getSpecName = function(spec, suite) {
573 | return suite.getFullName() + ' ' + spec.description;
574 | };
575 |
576 | // TODO: we may just be able to pass in the fn instead of wrapping here
577 | var buildExpectationResult = j$.buildExpectationResult,
578 | exceptionFormatter = new j$.ExceptionFormatter(),
579 | expectationResultFactory = function(attrs) {
580 | attrs.messageFormatter = exceptionFormatter.message;
581 | attrs.stackFormatter = exceptionFormatter.stack;
582 |
583 | return buildExpectationResult(attrs);
584 | };
585 |
586 | // TODO: fix this naming, and here's where the value comes in
587 | this.catchExceptions = function(value) {
588 | catchExceptions = !!value;
589 | return catchExceptions;
590 | };
591 |
592 | this.catchingExceptions = function() {
593 | return catchExceptions;
594 | };
595 |
596 | var maximumSpecCallbackDepth = 20;
597 | var currentSpecCallbackDepth = 0;
598 |
599 | function clearStack(fn) {
600 | currentSpecCallbackDepth++;
601 | if (currentSpecCallbackDepth >= maximumSpecCallbackDepth) {
602 | currentSpecCallbackDepth = 0;
603 | realSetTimeout(fn, 0);
604 | } else {
605 | fn();
606 | }
607 | }
608 |
609 | var catchException = function(e) {
610 | return j$.Spec.isPendingSpecException(e) || catchExceptions;
611 | };
612 |
613 | this.throwOnExpectationFailure = function(value) {
614 | throwOnExpectationFailure = !!value;
615 | };
616 |
617 | this.throwingExpectationFailures = function() {
618 | return throwOnExpectationFailure;
619 | };
620 |
621 | var queueRunnerFactory = function(options) {
622 | options.catchException = catchException;
623 | options.clearStack = options.clearStack || clearStack;
624 | options.timeout = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout};
625 | options.fail = self.fail;
626 |
627 | new j$.QueueRunner(options).execute();
628 | };
629 |
630 | var topSuite = new j$.Suite({
631 | env: this,
632 | id: getNextSuiteId(),
633 | description: 'Jasmine__TopLevel__Suite',
634 | queueRunner: queueRunnerFactory
635 | });
636 | runnableLookupTable[topSuite.id] = topSuite;
637 | defaultResourcesForRunnable(topSuite.id);
638 | currentDeclarationSuite = topSuite;
639 |
640 | this.topSuite = function() {
641 | return topSuite;
642 | };
643 |
644 | this.execute = function(runnablesToRun) {
645 | if(!runnablesToRun) {
646 | if (focusedRunnables.length) {
647 | runnablesToRun = focusedRunnables;
648 | } else {
649 | runnablesToRun = [topSuite.id];
650 | }
651 | }
652 | var processor = new j$.TreeProcessor({
653 | tree: topSuite,
654 | runnableIds: runnablesToRun,
655 | queueRunnerFactory: queueRunnerFactory,
656 | nodeStart: function(suite) {
657 | currentlyExecutingSuites.push(suite);
658 | defaultResourcesForRunnable(suite.id, suite.parentSuite.id);
659 | reporter.suiteStarted(suite.result);
660 | },
661 | nodeComplete: function(suite, result) {
662 | if (!suite.disabled) {
663 | clearResourcesForRunnable(suite.id);
664 | }
665 | currentlyExecutingSuites.pop();
666 | reporter.suiteDone(result);
667 | }
668 | });
669 |
670 | if(!processor.processTree().valid) {
671 | throw new Error('Invalid order: would cause a beforeAll or afterAll to be run multiple times');
672 | }
673 |
674 | reporter.jasmineStarted({
675 | totalSpecsDefined: totalSpecsDefined
676 | });
677 |
678 | processor.execute(reporter.jasmineDone);
679 | };
680 |
681 | this.addReporter = function(reporterToAdd) {
682 | reporter.addReporter(reporterToAdd);
683 | };
684 |
685 | var spyRegistry = new j$.SpyRegistry({currentSpies: function() {
686 | if(!currentRunnable()) {
687 | throw new Error('Spies must be created in a before function or a spec');
688 | }
689 | return runnableResources[currentRunnable().id].spies;
690 | }});
691 |
692 | this.spyOn = function() {
693 | return spyRegistry.spyOn.apply(spyRegistry, arguments);
694 | };
695 |
696 | var suiteFactory = function(description) {
697 | var suite = new j$.Suite({
698 | env: self,
699 | id: getNextSuiteId(),
700 | description: description,
701 | parentSuite: currentDeclarationSuite,
702 | expectationFactory: expectationFactory,
703 | expectationResultFactory: expectationResultFactory,
704 | throwOnExpectationFailure: throwOnExpectationFailure
705 | });
706 |
707 | runnableLookupTable[suite.id] = suite;
708 | return suite;
709 | };
710 |
711 | this.describe = function(description, specDefinitions) {
712 | var suite = suiteFactory(description);
713 | addSpecsToSuite(suite, specDefinitions);
714 | return suite;
715 | };
716 |
717 | this.xdescribe = function(description, specDefinitions) {
718 | var suite = this.describe(description, specDefinitions);
719 | suite.disable();
720 | return suite;
721 | };
722 |
723 | var focusedRunnables = [];
724 |
725 | this.fdescribe = function(description, specDefinitions) {
726 | var suite = suiteFactory(description);
727 | suite.isFocused = true;
728 |
729 | focusedRunnables.push(suite.id);
730 | unfocusAncestor();
731 | addSpecsToSuite(suite, specDefinitions);
732 |
733 | return suite;
734 | };
735 |
736 | function addSpecsToSuite(suite, specDefinitions) {
737 | var parentSuite = currentDeclarationSuite;
738 | parentSuite.addChild(suite);
739 | currentDeclarationSuite = suite;
740 |
741 | var declarationError = null;
742 | try {
743 | specDefinitions.call(suite);
744 | } catch (e) {
745 | declarationError = e;
746 | }
747 |
748 | if (declarationError) {
749 | self.it('encountered a declaration exception', function() {
750 | throw declarationError;
751 | });
752 | }
753 |
754 | currentDeclarationSuite = parentSuite;
755 | }
756 |
757 | function findFocusedAncestor(suite) {
758 | while (suite) {
759 | if (suite.isFocused) {
760 | return suite.id;
761 | }
762 | suite = suite.parentSuite;
763 | }
764 |
765 | return null;
766 | }
767 |
768 | function unfocusAncestor() {
769 | var focusedAncestor = findFocusedAncestor(currentDeclarationSuite);
770 | if (focusedAncestor) {
771 | for (var i = 0; i < focusedRunnables.length; i++) {
772 | if (focusedRunnables[i] === focusedAncestor) {
773 | focusedRunnables.splice(i, 1);
774 | break;
775 | }
776 | }
777 | }
778 | }
779 |
780 | var specFactory = function(description, fn, suite, timeout) {
781 | totalSpecsDefined++;
782 | var spec = new j$.Spec({
783 | id: getNextSpecId(),
784 | beforeAndAfterFns: beforeAndAfterFns(suite),
785 | expectationFactory: expectationFactory,
786 | resultCallback: specResultCallback,
787 | getSpecName: function(spec) {
788 | return getSpecName(spec, suite);
789 | },
790 | onStart: specStarted,
791 | description: description,
792 | expectationResultFactory: expectationResultFactory,
793 | queueRunnerFactory: queueRunnerFactory,
794 | userContext: function() { return suite.clonedSharedUserContext(); },
795 | queueableFn: {
796 | fn: fn,
797 | timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
798 | },
799 | throwOnExpectationFailure: throwOnExpectationFailure
800 | });
801 |
802 | runnableLookupTable[spec.id] = spec;
803 |
804 | if (!self.specFilter(spec)) {
805 | spec.disable();
806 | }
807 |
808 | return spec;
809 |
810 | function specResultCallback(result) {
811 | clearResourcesForRunnable(spec.id);
812 | currentSpec = null;
813 | reporter.specDone(result);
814 | }
815 |
816 | function specStarted(spec) {
817 | currentSpec = spec;
818 | defaultResourcesForRunnable(spec.id, suite.id);
819 | reporter.specStarted(spec.result);
820 | }
821 | };
822 |
823 | this.it = function(description, fn, timeout) {
824 | var spec = specFactory(description, fn, currentDeclarationSuite, timeout);
825 | currentDeclarationSuite.addChild(spec);
826 | return spec;
827 | };
828 |
829 | this.xit = function() {
830 | var spec = this.it.apply(this, arguments);
831 | spec.pend();
832 | return spec;
833 | };
834 |
835 | this.fit = function(){
836 | var spec = this.it.apply(this, arguments);
837 |
838 | focusedRunnables.push(spec.id);
839 | unfocusAncestor();
840 | return spec;
841 | };
842 |
843 | this.expect = function(actual) {
844 | if (!currentRunnable()) {
845 | throw new Error('\'expect\' was used when there was no current spec, this could be because an asynchronous test timed out');
846 | }
847 |
848 | return currentRunnable().expect(actual);
849 | };
850 |
851 | this.beforeEach = function(beforeEachFunction, timeout) {
852 | currentDeclarationSuite.beforeEach({
853 | fn: beforeEachFunction,
854 | timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
855 | });
856 | };
857 |
858 | this.beforeAll = function(beforeAllFunction, timeout) {
859 | currentDeclarationSuite.beforeAll({
860 | fn: beforeAllFunction,
861 | timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
862 | });
863 | };
864 |
865 | this.afterEach = function(afterEachFunction, timeout) {
866 | currentDeclarationSuite.afterEach({
867 | fn: afterEachFunction,
868 | timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
869 | });
870 | };
871 |
872 | this.afterAll = function(afterAllFunction, timeout) {
873 | currentDeclarationSuite.afterAll({
874 | fn: afterAllFunction,
875 | timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
876 | });
877 | };
878 |
879 | this.pending = function(message) {
880 | var fullMessage = j$.Spec.pendingSpecExceptionMessage;
881 | if(message) {
882 | fullMessage += message;
883 | }
884 | throw fullMessage;
885 | };
886 |
887 | this.fail = function(error) {
888 | var message = 'Failed';
889 | if (error) {
890 | message += ': ';
891 | message += error.message || error;
892 | }
893 |
894 | currentRunnable().addExpectationResult(false, {
895 | matcherName: '',
896 | passed: false,
897 | expected: '',
898 | actual: '',
899 | message: message,
900 | error: error && error.message ? error : null
901 | });
902 | };
903 | }
904 |
905 | return Env;
906 | };
907 |
908 | getJasmineRequireObj().JsApiReporter = function() {
909 |
910 | var noopTimer = {
911 | start: function(){},
912 | elapsed: function(){ return 0; }
913 | };
914 |
915 | function JsApiReporter(options) {
916 | var timer = options.timer || noopTimer,
917 | status = 'loaded';
918 |
919 | this.started = false;
920 | this.finished = false;
921 |
922 | this.jasmineStarted = function() {
923 | this.started = true;
924 | status = 'started';
925 | timer.start();
926 | };
927 |
928 | var executionTime;
929 |
930 | this.jasmineDone = function() {
931 | this.finished = true;
932 | executionTime = timer.elapsed();
933 | status = 'done';
934 | };
935 |
936 | this.status = function() {
937 | return status;
938 | };
939 |
940 | var suites = [],
941 | suites_hash = {};
942 |
943 | this.suiteStarted = function(result) {
944 | suites_hash[result.id] = result;
945 | };
946 |
947 | this.suiteDone = function(result) {
948 | storeSuite(result);
949 | };
950 |
951 | this.suiteResults = function(index, length) {
952 | return suites.slice(index, index + length);
953 | };
954 |
955 | function storeSuite(result) {
956 | suites.push(result);
957 | suites_hash[result.id] = result;
958 | }
959 |
960 | this.suites = function() {
961 | return suites_hash;
962 | };
963 |
964 | var specs = [];
965 |
966 | this.specDone = function(result) {
967 | specs.push(result);
968 | };
969 |
970 | this.specResults = function(index, length) {
971 | return specs.slice(index, index + length);
972 | };
973 |
974 | this.specs = function() {
975 | return specs;
976 | };
977 |
978 | this.executionTime = function() {
979 | return executionTime;
980 | };
981 |
982 | }
983 |
984 | return JsApiReporter;
985 | };
986 |
987 | getJasmineRequireObj().CallTracker = function() {
988 |
989 | function CallTracker() {
990 | var calls = [];
991 |
992 | this.track = function(context) {
993 | calls.push(context);
994 | };
995 |
996 | this.any = function() {
997 | return !!calls.length;
998 | };
999 |
1000 | this.count = function() {
1001 | return calls.length;
1002 | };
1003 |
1004 | this.argsFor = function(index) {
1005 | var call = calls[index];
1006 | return call ? call.args : [];
1007 | };
1008 |
1009 | this.all = function() {
1010 | return calls;
1011 | };
1012 |
1013 | this.allArgs = function() {
1014 | var callArgs = [];
1015 | for(var i = 0; i < calls.length; i++){
1016 | callArgs.push(calls[i].args);
1017 | }
1018 |
1019 | return callArgs;
1020 | };
1021 |
1022 | this.first = function() {
1023 | return calls[0];
1024 | };
1025 |
1026 | this.mostRecent = function() {
1027 | return calls[calls.length - 1];
1028 | };
1029 |
1030 | this.reset = function() {
1031 | calls = [];
1032 | };
1033 | }
1034 |
1035 | return CallTracker;
1036 | };
1037 |
1038 | getJasmineRequireObj().Clock = function() {
1039 | function Clock(global, delayedFunctionSchedulerFactory, mockDate) {
1040 | var self = this,
1041 | realTimingFunctions = {
1042 | setTimeout: global.setTimeout,
1043 | clearTimeout: global.clearTimeout,
1044 | setInterval: global.setInterval,
1045 | clearInterval: global.clearInterval
1046 | },
1047 | fakeTimingFunctions = {
1048 | setTimeout: setTimeout,
1049 | clearTimeout: clearTimeout,
1050 | setInterval: setInterval,
1051 | clearInterval: clearInterval
1052 | },
1053 | installed = false,
1054 | delayedFunctionScheduler,
1055 | timer;
1056 |
1057 |
1058 | self.install = function() {
1059 | if(!originalTimingFunctionsIntact()) {
1060 | throw new Error('Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?');
1061 | }
1062 | replace(global, fakeTimingFunctions);
1063 | timer = fakeTimingFunctions;
1064 | delayedFunctionScheduler = delayedFunctionSchedulerFactory();
1065 | installed = true;
1066 |
1067 | return self;
1068 | };
1069 |
1070 | self.uninstall = function() {
1071 | delayedFunctionScheduler = null;
1072 | mockDate.uninstall();
1073 | replace(global, realTimingFunctions);
1074 |
1075 | timer = realTimingFunctions;
1076 | installed = false;
1077 | };
1078 |
1079 | self.withMock = function(closure) {
1080 | this.install();
1081 | try {
1082 | closure();
1083 | } finally {
1084 | this.uninstall();
1085 | }
1086 | };
1087 |
1088 | self.mockDate = function(initialDate) {
1089 | mockDate.install(initialDate);
1090 | };
1091 |
1092 | self.setTimeout = function(fn, delay, params) {
1093 | if (legacyIE()) {
1094 | if (arguments.length > 2) {
1095 | throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill');
1096 | }
1097 | return timer.setTimeout(fn, delay);
1098 | }
1099 | return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]);
1100 | };
1101 |
1102 | self.setInterval = function(fn, delay, params) {
1103 | if (legacyIE()) {
1104 | if (arguments.length > 2) {
1105 | throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill');
1106 | }
1107 | return timer.setInterval(fn, delay);
1108 | }
1109 | return Function.prototype.apply.apply(timer.setInterval, [global, arguments]);
1110 | };
1111 |
1112 | self.clearTimeout = function(id) {
1113 | return Function.prototype.call.apply(timer.clearTimeout, [global, id]);
1114 | };
1115 |
1116 | self.clearInterval = function(id) {
1117 | return Function.prototype.call.apply(timer.clearInterval, [global, id]);
1118 | };
1119 |
1120 | self.tick = function(millis) {
1121 | if (installed) {
1122 | mockDate.tick(millis);
1123 | delayedFunctionScheduler.tick(millis);
1124 | } else {
1125 | throw new Error('Mock clock is not installed, use jasmine.clock().install()');
1126 | }
1127 | };
1128 |
1129 | return self;
1130 |
1131 | function originalTimingFunctionsIntact() {
1132 | return global.setTimeout === realTimingFunctions.setTimeout &&
1133 | global.clearTimeout === realTimingFunctions.clearTimeout &&
1134 | global.setInterval === realTimingFunctions.setInterval &&
1135 | global.clearInterval === realTimingFunctions.clearInterval;
1136 | }
1137 |
1138 | function legacyIE() {
1139 | //if these methods are polyfilled, apply will be present
1140 | return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply;
1141 | }
1142 |
1143 | function replace(dest, source) {
1144 | for (var prop in source) {
1145 | dest[prop] = source[prop];
1146 | }
1147 | }
1148 |
1149 | function setTimeout(fn, delay) {
1150 | return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2));
1151 | }
1152 |
1153 | function clearTimeout(id) {
1154 | return delayedFunctionScheduler.removeFunctionWithId(id);
1155 | }
1156 |
1157 | function setInterval(fn, interval) {
1158 | return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true);
1159 | }
1160 |
1161 | function clearInterval(id) {
1162 | return delayedFunctionScheduler.removeFunctionWithId(id);
1163 | }
1164 |
1165 | function argSlice(argsObj, n) {
1166 | return Array.prototype.slice.call(argsObj, n);
1167 | }
1168 | }
1169 |
1170 | return Clock;
1171 | };
1172 |
1173 | getJasmineRequireObj().DelayedFunctionScheduler = function() {
1174 | function DelayedFunctionScheduler() {
1175 | var self = this;
1176 | var scheduledLookup = [];
1177 | var scheduledFunctions = {};
1178 | var currentTime = 0;
1179 | var delayedFnCount = 0;
1180 |
1181 | self.tick = function(millis) {
1182 | millis = millis || 0;
1183 | var endTime = currentTime + millis;
1184 |
1185 | runScheduledFunctions(endTime);
1186 | currentTime = endTime;
1187 | };
1188 |
1189 | self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) {
1190 | var f;
1191 | if (typeof(funcToCall) === 'string') {
1192 | /* jshint evil: true */
1193 | f = function() { return eval(funcToCall); };
1194 | /* jshint evil: false */
1195 | } else {
1196 | f = funcToCall;
1197 | }
1198 |
1199 | millis = millis || 0;
1200 | timeoutKey = timeoutKey || ++delayedFnCount;
1201 | runAtMillis = runAtMillis || (currentTime + millis);
1202 |
1203 | var funcToSchedule = {
1204 | runAtMillis: runAtMillis,
1205 | funcToCall: f,
1206 | recurring: recurring,
1207 | params: params,
1208 | timeoutKey: timeoutKey,
1209 | millis: millis
1210 | };
1211 |
1212 | if (runAtMillis in scheduledFunctions) {
1213 | scheduledFunctions[runAtMillis].push(funcToSchedule);
1214 | } else {
1215 | scheduledFunctions[runAtMillis] = [funcToSchedule];
1216 | scheduledLookup.push(runAtMillis);
1217 | scheduledLookup.sort(function (a, b) {
1218 | return a - b;
1219 | });
1220 | }
1221 |
1222 | return timeoutKey;
1223 | };
1224 |
1225 | self.removeFunctionWithId = function(timeoutKey) {
1226 | for (var runAtMillis in scheduledFunctions) {
1227 | var funcs = scheduledFunctions[runAtMillis];
1228 | var i = indexOfFirstToPass(funcs, function (func) {
1229 | return func.timeoutKey === timeoutKey;
1230 | });
1231 |
1232 | if (i > -1) {
1233 | if (funcs.length === 1) {
1234 | delete scheduledFunctions[runAtMillis];
1235 | deleteFromLookup(runAtMillis);
1236 | } else {
1237 | funcs.splice(i, 1);
1238 | }
1239 |
1240 | // intervals get rescheduled when executed, so there's never more
1241 | // than a single scheduled function with a given timeoutKey
1242 | break;
1243 | }
1244 | }
1245 | };
1246 |
1247 | return self;
1248 |
1249 | function indexOfFirstToPass(array, testFn) {
1250 | var index = -1;
1251 |
1252 | for (var i = 0; i < array.length; ++i) {
1253 | if (testFn(array[i])) {
1254 | index = i;
1255 | break;
1256 | }
1257 | }
1258 |
1259 | return index;
1260 | }
1261 |
1262 | function deleteFromLookup(key) {
1263 | var value = Number(key);
1264 | var i = indexOfFirstToPass(scheduledLookup, function (millis) {
1265 | return millis === value;
1266 | });
1267 |
1268 | if (i > -1) {
1269 | scheduledLookup.splice(i, 1);
1270 | }
1271 | }
1272 |
1273 | function reschedule(scheduledFn) {
1274 | self.scheduleFunction(scheduledFn.funcToCall,
1275 | scheduledFn.millis,
1276 | scheduledFn.params,
1277 | true,
1278 | scheduledFn.timeoutKey,
1279 | scheduledFn.runAtMillis + scheduledFn.millis);
1280 | }
1281 |
1282 | function forEachFunction(funcsToRun, callback) {
1283 | for (var i = 0; i < funcsToRun.length; ++i) {
1284 | callback(funcsToRun[i]);
1285 | }
1286 | }
1287 |
1288 | function runScheduledFunctions(endTime) {
1289 | if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) {
1290 | return;
1291 | }
1292 |
1293 | do {
1294 | currentTime = scheduledLookup.shift();
1295 |
1296 | var funcsToRun = scheduledFunctions[currentTime];
1297 | delete scheduledFunctions[currentTime];
1298 |
1299 | forEachFunction(funcsToRun, function(funcToRun) {
1300 | if (funcToRun.recurring) {
1301 | reschedule(funcToRun);
1302 | }
1303 | });
1304 |
1305 | forEachFunction(funcsToRun, function(funcToRun) {
1306 | funcToRun.funcToCall.apply(null, funcToRun.params || []);
1307 | });
1308 | } while (scheduledLookup.length > 0 &&
1309 | // checking first if we're out of time prevents setTimeout(0)
1310 | // scheduled in a funcToRun from forcing an extra iteration
1311 | currentTime !== endTime &&
1312 | scheduledLookup[0] <= endTime);
1313 | }
1314 | }
1315 |
1316 | return DelayedFunctionScheduler;
1317 | };
1318 |
1319 | getJasmineRequireObj().ExceptionFormatter = function() {
1320 | function ExceptionFormatter() {
1321 | this.message = function(error) {
1322 | var message = '';
1323 |
1324 | if (error.name && error.message) {
1325 | message += error.name + ': ' + error.message;
1326 | } else {
1327 | message += error.toString() + ' thrown';
1328 | }
1329 |
1330 | if (error.fileName || error.sourceURL) {
1331 | message += ' in ' + (error.fileName || error.sourceURL);
1332 | }
1333 |
1334 | if (error.line || error.lineNumber) {
1335 | message += ' (line ' + (error.line || error.lineNumber) + ')';
1336 | }
1337 |
1338 | return message;
1339 | };
1340 |
1341 | this.stack = function(error) {
1342 | return error ? error.stack : null;
1343 | };
1344 | }
1345 |
1346 | return ExceptionFormatter;
1347 | };
1348 |
1349 | getJasmineRequireObj().Expectation = function() {
1350 |
1351 | function Expectation(options) {
1352 | this.util = options.util || { buildFailureMessage: function() {} };
1353 | this.customEqualityTesters = options.customEqualityTesters || [];
1354 | this.actual = options.actual;
1355 | this.addExpectationResult = options.addExpectationResult || function(){};
1356 | this.isNot = options.isNot;
1357 |
1358 | var customMatchers = options.customMatchers || {};
1359 | for (var matcherName in customMatchers) {
1360 | this[matcherName] = Expectation.prototype.wrapCompare(matcherName, customMatchers[matcherName]);
1361 | }
1362 | }
1363 |
1364 | Expectation.prototype.wrapCompare = function(name, matcherFactory) {
1365 | return function() {
1366 | var args = Array.prototype.slice.call(arguments, 0),
1367 | expected = args.slice(0),
1368 | message = '';
1369 |
1370 | args.unshift(this.actual);
1371 |
1372 | var matcher = matcherFactory(this.util, this.customEqualityTesters),
1373 | matcherCompare = matcher.compare;
1374 |
1375 | function defaultNegativeCompare() {
1376 | var result = matcher.compare.apply(null, args);
1377 | result.pass = !result.pass;
1378 | return result;
1379 | }
1380 |
1381 | if (this.isNot) {
1382 | matcherCompare = matcher.negativeCompare || defaultNegativeCompare;
1383 | }
1384 |
1385 | var result = matcherCompare.apply(null, args);
1386 |
1387 | if (!result.pass) {
1388 | if (!result.message) {
1389 | args.unshift(this.isNot);
1390 | args.unshift(name);
1391 | message = this.util.buildFailureMessage.apply(null, args);
1392 | } else {
1393 | if (Object.prototype.toString.apply(result.message) === '[object Function]') {
1394 | message = result.message();
1395 | } else {
1396 | message = result.message;
1397 | }
1398 | }
1399 | }
1400 |
1401 | if (expected.length == 1) {
1402 | expected = expected[0];
1403 | }
1404 |
1405 | // TODO: how many of these params are needed?
1406 | this.addExpectationResult(
1407 | result.pass,
1408 | {
1409 | matcherName: name,
1410 | passed: result.pass,
1411 | message: message,
1412 | actual: this.actual,
1413 | expected: expected // TODO: this may need to be arrayified/sliced
1414 | }
1415 | );
1416 | };
1417 | };
1418 |
1419 | Expectation.addCoreMatchers = function(matchers) {
1420 | var prototype = Expectation.prototype;
1421 | for (var matcherName in matchers) {
1422 | var matcher = matchers[matcherName];
1423 | prototype[matcherName] = prototype.wrapCompare(matcherName, matcher);
1424 | }
1425 | };
1426 |
1427 | Expectation.Factory = function(options) {
1428 | options = options || {};
1429 |
1430 | var expect = new Expectation(options);
1431 |
1432 | // TODO: this would be nice as its own Object - NegativeExpectation
1433 | // TODO: copy instead of mutate options
1434 | options.isNot = true;
1435 | expect.not = new Expectation(options);
1436 |
1437 | return expect;
1438 | };
1439 |
1440 | return Expectation;
1441 | };
1442 |
1443 | //TODO: expectation result may make more sense as a presentation of an expectation.
1444 | getJasmineRequireObj().buildExpectationResult = function() {
1445 | function buildExpectationResult(options) {
1446 | var messageFormatter = options.messageFormatter || function() {},
1447 | stackFormatter = options.stackFormatter || function() {};
1448 |
1449 | var result = {
1450 | matcherName: options.matcherName,
1451 | message: message(),
1452 | stack: stack(),
1453 | passed: options.passed
1454 | };
1455 |
1456 | if(!result.passed) {
1457 | result.expected = options.expected;
1458 | result.actual = options.actual;
1459 | }
1460 |
1461 | return result;
1462 |
1463 | function message() {
1464 | if (options.passed) {
1465 | return 'Passed.';
1466 | } else if (options.message) {
1467 | return options.message;
1468 | } else if (options.error) {
1469 | return messageFormatter(options.error);
1470 | }
1471 | return '';
1472 | }
1473 |
1474 | function stack() {
1475 | if (options.passed) {
1476 | return '';
1477 | }
1478 |
1479 | var error = options.error;
1480 | if (!error) {
1481 | try {
1482 | throw new Error(message());
1483 | } catch (e) {
1484 | error = e;
1485 | }
1486 | }
1487 | return stackFormatter(error);
1488 | }
1489 | }
1490 |
1491 | return buildExpectationResult;
1492 | };
1493 |
1494 | getJasmineRequireObj().MockDate = function() {
1495 | function MockDate(global) {
1496 | var self = this;
1497 | var currentTime = 0;
1498 |
1499 | if (!global || !global.Date) {
1500 | self.install = function() {};
1501 | self.tick = function() {};
1502 | self.uninstall = function() {};
1503 | return self;
1504 | }
1505 |
1506 | var GlobalDate = global.Date;
1507 |
1508 | self.install = function(mockDate) {
1509 | if (mockDate instanceof GlobalDate) {
1510 | currentTime = mockDate.getTime();
1511 | } else {
1512 | currentTime = new GlobalDate().getTime();
1513 | }
1514 |
1515 | global.Date = FakeDate;
1516 | };
1517 |
1518 | self.tick = function(millis) {
1519 | millis = millis || 0;
1520 | currentTime = currentTime + millis;
1521 | };
1522 |
1523 | self.uninstall = function() {
1524 | currentTime = 0;
1525 | global.Date = GlobalDate;
1526 | };
1527 |
1528 | createDateProperties();
1529 |
1530 | return self;
1531 |
1532 | function FakeDate() {
1533 | switch(arguments.length) {
1534 | case 0:
1535 | return new GlobalDate(currentTime);
1536 | case 1:
1537 | return new GlobalDate(arguments[0]);
1538 | case 2:
1539 | return new GlobalDate(arguments[0], arguments[1]);
1540 | case 3:
1541 | return new GlobalDate(arguments[0], arguments[1], arguments[2]);
1542 | case 4:
1543 | return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3]);
1544 | case 5:
1545 | return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
1546 | arguments[4]);
1547 | case 6:
1548 | return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
1549 | arguments[4], arguments[5]);
1550 | default:
1551 | return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
1552 | arguments[4], arguments[5], arguments[6]);
1553 | }
1554 | }
1555 |
1556 | function createDateProperties() {
1557 | FakeDate.prototype = GlobalDate.prototype;
1558 |
1559 | FakeDate.now = function() {
1560 | if (GlobalDate.now) {
1561 | return currentTime;
1562 | } else {
1563 | throw new Error('Browser does not support Date.now()');
1564 | }
1565 | };
1566 |
1567 | FakeDate.toSource = GlobalDate.toSource;
1568 | FakeDate.toString = GlobalDate.toString;
1569 | FakeDate.parse = GlobalDate.parse;
1570 | FakeDate.UTC = GlobalDate.UTC;
1571 | }
1572 | }
1573 |
1574 | return MockDate;
1575 | };
1576 |
1577 | getJasmineRequireObj().pp = function(j$) {
1578 |
1579 | function PrettyPrinter() {
1580 | this.ppNestLevel_ = 0;
1581 | this.seen = [];
1582 | }
1583 |
1584 | PrettyPrinter.prototype.format = function(value) {
1585 | this.ppNestLevel_++;
1586 | try {
1587 | if (j$.util.isUndefined(value)) {
1588 | this.emitScalar('undefined');
1589 | } else if (value === null) {
1590 | this.emitScalar('null');
1591 | } else if (value === 0 && 1/value === -Infinity) {
1592 | this.emitScalar('-0');
1593 | } else if (value === j$.getGlobal()) {
1594 | this.emitScalar('');
1595 | } else if (value.jasmineToString) {
1596 | this.emitScalar(value.jasmineToString());
1597 | } else if (typeof value === 'string') {
1598 | this.emitString(value);
1599 | } else if (j$.isSpy(value)) {
1600 | this.emitScalar('spy on ' + value.and.identity());
1601 | } else if (value instanceof RegExp) {
1602 | this.emitScalar(value.toString());
1603 | } else if (typeof value === 'function') {
1604 | this.emitScalar('Function');
1605 | } else if (typeof value.nodeType === 'number') {
1606 | this.emitScalar('HTMLNode');
1607 | } else if (value instanceof Date) {
1608 | this.emitScalar('Date(' + value + ')');
1609 | } else if (j$.util.arrayContains(this.seen, value)) {
1610 | this.emitScalar('');
1611 | } else if (j$.isArray_(value) || j$.isA_('Object', value)) {
1612 | this.seen.push(value);
1613 | if (j$.isArray_(value)) {
1614 | this.emitArray(value);
1615 | } else {
1616 | this.emitObject(value);
1617 | }
1618 | this.seen.pop();
1619 | } else {
1620 | this.emitScalar(value.toString());
1621 | }
1622 | } finally {
1623 | this.ppNestLevel_--;
1624 | }
1625 | };
1626 |
1627 | PrettyPrinter.prototype.iterateObject = function(obj, fn) {
1628 | for (var property in obj) {
1629 | if (!Object.prototype.hasOwnProperty.call(obj, property)) { continue; }
1630 | fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) &&
1631 | obj.__lookupGetter__(property) !== null) : false);
1632 | }
1633 | };
1634 |
1635 | PrettyPrinter.prototype.emitArray = j$.unimplementedMethod_;
1636 | PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_;
1637 | PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_;
1638 | PrettyPrinter.prototype.emitString = j$.unimplementedMethod_;
1639 |
1640 | function StringPrettyPrinter() {
1641 | PrettyPrinter.call(this);
1642 |
1643 | this.string = '';
1644 | }
1645 |
1646 | j$.util.inherit(StringPrettyPrinter, PrettyPrinter);
1647 |
1648 | StringPrettyPrinter.prototype.emitScalar = function(value) {
1649 | this.append(value);
1650 | };
1651 |
1652 | StringPrettyPrinter.prototype.emitString = function(value) {
1653 | this.append('\'' + value + '\'');
1654 | };
1655 |
1656 | StringPrettyPrinter.prototype.emitArray = function(array) {
1657 | if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) {
1658 | this.append('Array');
1659 | return;
1660 | }
1661 | var length = Math.min(array.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH);
1662 | this.append('[ ');
1663 | for (var i = 0; i < length; i++) {
1664 | if (i > 0) {
1665 | this.append(', ');
1666 | }
1667 | this.format(array[i]);
1668 | }
1669 | if(array.length > length){
1670 | this.append(', ...');
1671 | }
1672 |
1673 | var self = this;
1674 | var first = array.length === 0;
1675 | this.iterateObject(array, function(property, isGetter) {
1676 | if (property.match(/^\d+$/)) {
1677 | return;
1678 | }
1679 |
1680 | if (first) {
1681 | first = false;
1682 | } else {
1683 | self.append(', ');
1684 | }
1685 |
1686 | self.formatProperty(array, property, isGetter);
1687 | });
1688 |
1689 | this.append(' ]');
1690 | };
1691 |
1692 | StringPrettyPrinter.prototype.emitObject = function(obj) {
1693 | var constructorName = obj.constructor ? j$.fnNameFor(obj.constructor) : 'null';
1694 | this.append(constructorName);
1695 |
1696 | if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) {
1697 | return;
1698 | }
1699 |
1700 | var self = this;
1701 | this.append('({ ');
1702 | var first = true;
1703 |
1704 | this.iterateObject(obj, function(property, isGetter) {
1705 | if (first) {
1706 | first = false;
1707 | } else {
1708 | self.append(', ');
1709 | }
1710 |
1711 | self.formatProperty(obj, property, isGetter);
1712 | });
1713 |
1714 | this.append(' })');
1715 | };
1716 |
1717 | StringPrettyPrinter.prototype.formatProperty = function(obj, property, isGetter) {
1718 | this.append(property);
1719 | this.append(': ');
1720 | if (isGetter) {
1721 | this.append('');
1722 | } else {
1723 | this.format(obj[property]);
1724 | }
1725 | };
1726 |
1727 | StringPrettyPrinter.prototype.append = function(value) {
1728 | this.string += value;
1729 | };
1730 |
1731 | return function(value) {
1732 | var stringPrettyPrinter = new StringPrettyPrinter();
1733 | stringPrettyPrinter.format(value);
1734 | return stringPrettyPrinter.string;
1735 | };
1736 | };
1737 |
1738 | getJasmineRequireObj().QueueRunner = function(j$) {
1739 |
1740 | function once(fn) {
1741 | var called = false;
1742 | return function() {
1743 | if (!called) {
1744 | called = true;
1745 | fn();
1746 | }
1747 | };
1748 | }
1749 |
1750 | function QueueRunner(attrs) {
1751 | this.queueableFns = attrs.queueableFns || [];
1752 | this.onComplete = attrs.onComplete || function() {};
1753 | this.clearStack = attrs.clearStack || function(fn) {fn();};
1754 | this.onException = attrs.onException || function() {};
1755 | this.catchException = attrs.catchException || function() { return true; };
1756 | this.userContext = attrs.userContext || {};
1757 | this.timeout = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout};
1758 | this.fail = attrs.fail || function() {};
1759 | }
1760 |
1761 | QueueRunner.prototype.execute = function() {
1762 | this.run(this.queueableFns, 0);
1763 | };
1764 |
1765 | QueueRunner.prototype.run = function(queueableFns, recursiveIndex) {
1766 | var length = queueableFns.length,
1767 | self = this,
1768 | iterativeIndex;
1769 |
1770 |
1771 | for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) {
1772 | var queueableFn = queueableFns[iterativeIndex];
1773 | if (queueableFn.fn.length > 0) {
1774 | attemptAsync(queueableFn);
1775 | return;
1776 | } else {
1777 | attemptSync(queueableFn);
1778 | }
1779 | }
1780 |
1781 | var runnerDone = iterativeIndex >= length;
1782 |
1783 | if (runnerDone) {
1784 | this.clearStack(this.onComplete);
1785 | }
1786 |
1787 | function attemptSync(queueableFn) {
1788 | try {
1789 | queueableFn.fn.call(self.userContext);
1790 | } catch (e) {
1791 | handleException(e, queueableFn);
1792 | }
1793 | }
1794 |
1795 | function attemptAsync(queueableFn) {
1796 | var clearTimeout = function () {
1797 | Function.prototype.apply.apply(self.timeout.clearTimeout, [j$.getGlobal(), [timeoutId]]);
1798 | },
1799 | next = once(function () {
1800 | clearTimeout(timeoutId);
1801 | self.run(queueableFns, iterativeIndex + 1);
1802 | }),
1803 | timeoutId;
1804 |
1805 | next.fail = function() {
1806 | self.fail.apply(null, arguments);
1807 | next();
1808 | };
1809 |
1810 | if (queueableFn.timeout) {
1811 | timeoutId = Function.prototype.apply.apply(self.timeout.setTimeout, [j$.getGlobal(), [function() {
1812 | var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.');
1813 | onException(error, queueableFn);
1814 | next();
1815 | }, queueableFn.timeout()]]);
1816 | }
1817 |
1818 | try {
1819 | queueableFn.fn.call(self.userContext, next);
1820 | } catch (e) {
1821 | handleException(e, queueableFn);
1822 | next();
1823 | }
1824 | }
1825 |
1826 | function onException(e, queueableFn) {
1827 | self.onException(e);
1828 | }
1829 |
1830 | function handleException(e, queueableFn) {
1831 | onException(e, queueableFn);
1832 | if (!self.catchException(e)) {
1833 | //TODO: set a var when we catch an exception and
1834 | //use a finally block to close the loop in a nice way..
1835 | throw e;
1836 | }
1837 | }
1838 | };
1839 |
1840 | return QueueRunner;
1841 | };
1842 |
1843 | getJasmineRequireObj().ReportDispatcher = function() {
1844 | function ReportDispatcher(methods) {
1845 |
1846 | var dispatchedMethods = methods || [];
1847 |
1848 | for (var i = 0; i < dispatchedMethods.length; i++) {
1849 | var method = dispatchedMethods[i];
1850 | this[method] = (function(m) {
1851 | return function() {
1852 | dispatch(m, arguments);
1853 | };
1854 | }(method));
1855 | }
1856 |
1857 | var reporters = [];
1858 |
1859 | this.addReporter = function(reporter) {
1860 | reporters.push(reporter);
1861 | };
1862 |
1863 | return this;
1864 |
1865 | function dispatch(method, args) {
1866 | for (var i = 0; i < reporters.length; i++) {
1867 | var reporter = reporters[i];
1868 | if (reporter[method]) {
1869 | reporter[method].apply(reporter, args);
1870 | }
1871 | }
1872 | }
1873 | }
1874 |
1875 | return ReportDispatcher;
1876 | };
1877 |
1878 |
1879 | getJasmineRequireObj().SpyRegistry = function(j$) {
1880 |
1881 | function SpyRegistry(options) {
1882 | options = options || {};
1883 | var currentSpies = options.currentSpies || function() { return []; };
1884 |
1885 | this.spyOn = function(obj, methodName) {
1886 | if (j$.util.isUndefined(obj)) {
1887 | throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()');
1888 | }
1889 |
1890 | if (j$.util.isUndefined(methodName)) {
1891 | throw new Error('No method name supplied');
1892 | }
1893 |
1894 | if (j$.util.isUndefined(obj[methodName])) {
1895 | throw new Error(methodName + '() method does not exist');
1896 | }
1897 |
1898 | if (obj[methodName] && j$.isSpy(obj[methodName])) {
1899 | //TODO?: should this return the current spy? Downside: may cause user confusion about spy state
1900 | throw new Error(methodName + ' has already been spied upon');
1901 | }
1902 |
1903 | var spy = j$.createSpy(methodName, obj[methodName]);
1904 |
1905 | currentSpies().push({
1906 | spy: spy,
1907 | baseObj: obj,
1908 | methodName: methodName,
1909 | originalValue: obj[methodName]
1910 | });
1911 |
1912 | obj[methodName] = spy;
1913 |
1914 | return spy;
1915 | };
1916 |
1917 | this.clearSpies = function() {
1918 | var spies = currentSpies();
1919 | for (var i = 0; i < spies.length; i++) {
1920 | var spyEntry = spies[i];
1921 | spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue;
1922 | }
1923 | };
1924 | }
1925 |
1926 | return SpyRegistry;
1927 | };
1928 |
1929 | getJasmineRequireObj().SpyStrategy = function() {
1930 |
1931 | function SpyStrategy(options) {
1932 | options = options || {};
1933 |
1934 | var identity = options.name || 'unknown',
1935 | originalFn = options.fn || function() {},
1936 | getSpy = options.getSpy || function() {},
1937 | plan = function() {};
1938 |
1939 | this.identity = function() {
1940 | return identity;
1941 | };
1942 |
1943 | this.exec = function() {
1944 | return plan.apply(this, arguments);
1945 | };
1946 |
1947 | this.callThrough = function() {
1948 | plan = originalFn;
1949 | return getSpy();
1950 | };
1951 |
1952 | this.returnValue = function(value) {
1953 | plan = function() {
1954 | return value;
1955 | };
1956 | return getSpy();
1957 | };
1958 |
1959 | this.returnValues = function() {
1960 | var values = Array.prototype.slice.call(arguments);
1961 | plan = function () {
1962 | return values.shift();
1963 | };
1964 | return getSpy();
1965 | };
1966 |
1967 | this.throwError = function(something) {
1968 | var error = (something instanceof Error) ? something : new Error(something);
1969 | plan = function() {
1970 | throw error;
1971 | };
1972 | return getSpy();
1973 | };
1974 |
1975 | this.callFake = function(fn) {
1976 | plan = fn;
1977 | return getSpy();
1978 | };
1979 |
1980 | this.stub = function(fn) {
1981 | plan = function() {};
1982 | return getSpy();
1983 | };
1984 | }
1985 |
1986 | return SpyStrategy;
1987 | };
1988 |
1989 | getJasmineRequireObj().Suite = function(j$) {
1990 | function Suite(attrs) {
1991 | this.env = attrs.env;
1992 | this.id = attrs.id;
1993 | this.parentSuite = attrs.parentSuite;
1994 | this.description = attrs.description;
1995 | this.expectationFactory = attrs.expectationFactory;
1996 | this.expectationResultFactory = attrs.expectationResultFactory;
1997 | this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
1998 |
1999 | this.beforeFns = [];
2000 | this.afterFns = [];
2001 | this.beforeAllFns = [];
2002 | this.afterAllFns = [];
2003 | this.disabled = false;
2004 |
2005 | this.children = [];
2006 |
2007 | this.result = {
2008 | id: this.id,
2009 | description: this.description,
2010 | fullName: this.getFullName(),
2011 | failedExpectations: []
2012 | };
2013 | }
2014 |
2015 | Suite.prototype.expect = function(actual) {
2016 | return this.expectationFactory(actual, this);
2017 | };
2018 |
2019 | Suite.prototype.getFullName = function() {
2020 | var fullName = this.description;
2021 | for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
2022 | if (parentSuite.parentSuite) {
2023 | fullName = parentSuite.description + ' ' + fullName;
2024 | }
2025 | }
2026 | return fullName;
2027 | };
2028 |
2029 | Suite.prototype.disable = function() {
2030 | this.disabled = true;
2031 | };
2032 |
2033 | Suite.prototype.beforeEach = function(fn) {
2034 | this.beforeFns.unshift(fn);
2035 | };
2036 |
2037 | Suite.prototype.beforeAll = function(fn) {
2038 | this.beforeAllFns.push(fn);
2039 | };
2040 |
2041 | Suite.prototype.afterEach = function(fn) {
2042 | this.afterFns.unshift(fn);
2043 | };
2044 |
2045 | Suite.prototype.afterAll = function(fn) {
2046 | this.afterAllFns.push(fn);
2047 | };
2048 |
2049 | Suite.prototype.addChild = function(child) {
2050 | this.children.push(child);
2051 | };
2052 |
2053 | Suite.prototype.status = function() {
2054 | if (this.disabled) {
2055 | return 'disabled';
2056 | }
2057 |
2058 | if (this.result.failedExpectations.length > 0) {
2059 | return 'failed';
2060 | } else {
2061 | return 'finished';
2062 | }
2063 | };
2064 |
2065 | Suite.prototype.isExecutable = function() {
2066 | return !this.disabled;
2067 | };
2068 |
2069 | Suite.prototype.canBeReentered = function() {
2070 | return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0;
2071 | };
2072 |
2073 | Suite.prototype.getResult = function() {
2074 | this.result.status = this.status();
2075 | return this.result;
2076 | };
2077 |
2078 | Suite.prototype.sharedUserContext = function() {
2079 | if (!this.sharedContext) {
2080 | this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {};
2081 | }
2082 |
2083 | return this.sharedContext;
2084 | };
2085 |
2086 | Suite.prototype.clonedSharedUserContext = function() {
2087 | return clone(this.sharedUserContext());
2088 | };
2089 |
2090 | Suite.prototype.onException = function() {
2091 | if (arguments[0] instanceof j$.errors.ExpectationFailed) {
2092 | return;
2093 | }
2094 |
2095 | if(isAfterAll(this.children)) {
2096 | var data = {
2097 | matcherName: '',
2098 | passed: false,
2099 | expected: '',
2100 | actual: '',
2101 | error: arguments[0]
2102 | };
2103 | this.result.failedExpectations.push(this.expectationResultFactory(data));
2104 | } else {
2105 | for (var i = 0; i < this.children.length; i++) {
2106 | var child = this.children[i];
2107 | child.onException.apply(child, arguments);
2108 | }
2109 | }
2110 | };
2111 |
2112 | Suite.prototype.addExpectationResult = function () {
2113 | if(isAfterAll(this.children) && isFailure(arguments)){
2114 | var data = arguments[1];
2115 | this.result.failedExpectations.push(this.expectationResultFactory(data));
2116 | if(this.throwOnExpectationFailure) {
2117 | throw new j$.errors.ExpectationFailed();
2118 | }
2119 | } else {
2120 | for (var i = 0; i < this.children.length; i++) {
2121 | var child = this.children[i];
2122 | try {
2123 | child.addExpectationResult.apply(child, arguments);
2124 | } catch(e) {
2125 | // keep going
2126 | }
2127 | }
2128 | }
2129 | };
2130 |
2131 | function isAfterAll(children) {
2132 | return children && children[0].result.status;
2133 | }
2134 |
2135 | function isFailure(args) {
2136 | return !args[0];
2137 | }
2138 |
2139 | function clone(obj) {
2140 | var clonedObj = {};
2141 | for (var prop in obj) {
2142 | if (obj.hasOwnProperty(prop)) {
2143 | clonedObj[prop] = obj[prop];
2144 | }
2145 | }
2146 |
2147 | return clonedObj;
2148 | }
2149 |
2150 | return Suite;
2151 | };
2152 |
2153 | if (typeof window == void 0 && typeof exports == 'object') {
2154 | exports.Suite = jasmineRequire.Suite;
2155 | }
2156 |
2157 | getJasmineRequireObj().Timer = function() {
2158 | var defaultNow = (function(Date) {
2159 | return function() { return new Date().getTime(); };
2160 | })(Date);
2161 |
2162 | function Timer(options) {
2163 | options = options || {};
2164 |
2165 | var now = options.now || defaultNow,
2166 | startTime;
2167 |
2168 | this.start = function() {
2169 | startTime = now();
2170 | };
2171 |
2172 | this.elapsed = function() {
2173 | return now() - startTime;
2174 | };
2175 | }
2176 |
2177 | return Timer;
2178 | };
2179 |
2180 | getJasmineRequireObj().TreeProcessor = function() {
2181 | function TreeProcessor(attrs) {
2182 | var tree = attrs.tree,
2183 | runnableIds = attrs.runnableIds,
2184 | queueRunnerFactory = attrs.queueRunnerFactory,
2185 | nodeStart = attrs.nodeStart || function() {},
2186 | nodeComplete = attrs.nodeComplete || function() {},
2187 | stats = { valid: true },
2188 | processed = false,
2189 | defaultMin = Infinity,
2190 | defaultMax = 1 - Infinity;
2191 |
2192 | this.processTree = function() {
2193 | processNode(tree, false);
2194 | processed = true;
2195 | return stats;
2196 | };
2197 |
2198 | this.execute = function(done) {
2199 | if (!processed) {
2200 | this.processTree();
2201 | }
2202 |
2203 | if (!stats.valid) {
2204 | throw 'invalid order';
2205 | }
2206 |
2207 | var childFns = wrapChildren(tree, 0);
2208 |
2209 | queueRunnerFactory({
2210 | queueableFns: childFns,
2211 | userContext: tree.sharedUserContext(),
2212 | onException: function() {
2213 | tree.onException.apply(tree, arguments);
2214 | },
2215 | onComplete: done
2216 | });
2217 | };
2218 |
2219 | function runnableIndex(id) {
2220 | for (var i = 0; i < runnableIds.length; i++) {
2221 | if (runnableIds[i] === id) {
2222 | return i;
2223 | }
2224 | }
2225 | }
2226 |
2227 | function processNode(node, parentEnabled) {
2228 | var executableIndex = runnableIndex(node.id);
2229 |
2230 | if (executableIndex !== undefined) {
2231 | parentEnabled = true;
2232 | }
2233 |
2234 | parentEnabled = parentEnabled && node.isExecutable();
2235 |
2236 | if (!node.children) {
2237 | stats[node.id] = {
2238 | executable: parentEnabled && node.isExecutable(),
2239 | segments: [{
2240 | index: 0,
2241 | owner: node,
2242 | nodes: [node],
2243 | min: startingMin(executableIndex),
2244 | max: startingMax(executableIndex)
2245 | }]
2246 | };
2247 | } else {
2248 | var hasExecutableChild = false;
2249 |
2250 | for (var i = 0; i < node.children.length; i++) {
2251 | var child = node.children[i];
2252 |
2253 | processNode(child, parentEnabled);
2254 |
2255 | if (!stats.valid) {
2256 | return;
2257 | }
2258 |
2259 | var childStats = stats[child.id];
2260 |
2261 | hasExecutableChild = hasExecutableChild || childStats.executable;
2262 | }
2263 |
2264 | stats[node.id] = {
2265 | executable: hasExecutableChild
2266 | };
2267 |
2268 | segmentChildren(node, stats[node.id], executableIndex);
2269 |
2270 | if (!node.canBeReentered() && stats[node.id].segments.length > 1) {
2271 | stats = { valid: false };
2272 | }
2273 | }
2274 | }
2275 |
2276 | function startingMin(executableIndex) {
2277 | return executableIndex === undefined ? defaultMin : executableIndex;
2278 | }
2279 |
2280 | function startingMax(executableIndex) {
2281 | return executableIndex === undefined ? defaultMax : executableIndex;
2282 | }
2283 |
2284 | function segmentChildren(node, nodeStats, executableIndex) {
2285 | var currentSegment = { index: 0, owner: node, nodes: [], min: startingMin(executableIndex), max: startingMax(executableIndex) },
2286 | result = [currentSegment],
2287 | lastMax = defaultMax,
2288 | orderedChildSegments = orderChildSegments(node.children);
2289 |
2290 | function isSegmentBoundary(minIndex) {
2291 | return lastMax !== defaultMax && minIndex !== defaultMin && lastMax < minIndex - 1;
2292 | }
2293 |
2294 | for (var i = 0; i < orderedChildSegments.length; i++) {
2295 | var childSegment = orderedChildSegments[i],
2296 | maxIndex = childSegment.max,
2297 | minIndex = childSegment.min;
2298 |
2299 | if (isSegmentBoundary(minIndex)) {
2300 | currentSegment = {index: result.length, owner: node, nodes: [], min: defaultMin, max: defaultMax};
2301 | result.push(currentSegment);
2302 | }
2303 |
2304 | currentSegment.nodes.push(childSegment);
2305 | currentSegment.min = Math.min(currentSegment.min, minIndex);
2306 | currentSegment.max = Math.max(currentSegment.max, maxIndex);
2307 | lastMax = maxIndex;
2308 | }
2309 |
2310 | nodeStats.segments = result;
2311 | }
2312 |
2313 | function orderChildSegments(children) {
2314 | var specifiedOrder = [],
2315 | unspecifiedOrder = [];
2316 |
2317 | for (var i = 0; i < children.length; i++) {
2318 | var child = children[i],
2319 | segments = stats[child.id].segments;
2320 |
2321 | for (var j = 0; j < segments.length; j++) {
2322 | var seg = segments[j];
2323 |
2324 | if (seg.min === defaultMin) {
2325 | unspecifiedOrder.push(seg);
2326 | } else {
2327 | specifiedOrder.push(seg);
2328 | }
2329 | }
2330 | }
2331 |
2332 | specifiedOrder.sort(function(a, b) {
2333 | return a.min - b.min;
2334 | });
2335 |
2336 | return specifiedOrder.concat(unspecifiedOrder);
2337 | }
2338 |
2339 | function executeNode(node, segmentNumber) {
2340 | if (node.children) {
2341 | return {
2342 | fn: function(done) {
2343 | nodeStart(node);
2344 |
2345 | queueRunnerFactory({
2346 | onComplete: function() {
2347 | nodeComplete(node, node.getResult());
2348 | done();
2349 | },
2350 | queueableFns: wrapChildren(node, segmentNumber),
2351 | userContext: node.sharedUserContext(),
2352 | onException: function() {
2353 | node.onException.apply(node, arguments);
2354 | }
2355 | });
2356 | }
2357 | };
2358 | } else {
2359 | return {
2360 | fn: function(done) { node.execute(done, stats[node.id].executable); }
2361 | };
2362 | }
2363 | }
2364 |
2365 | function wrapChildren(node, segmentNumber) {
2366 | var result = [],
2367 | segmentChildren = stats[node.id].segments[segmentNumber].nodes;
2368 |
2369 | for (var i = 0; i < segmentChildren.length; i++) {
2370 | result.push(executeNode(segmentChildren[i].owner, segmentChildren[i].index));
2371 | }
2372 |
2373 | if (!stats[node.id].executable) {
2374 | return result;
2375 | }
2376 |
2377 | return node.beforeAllFns.concat(result).concat(node.afterAllFns);
2378 | }
2379 | }
2380 |
2381 | return TreeProcessor;
2382 | };
2383 |
2384 | getJasmineRequireObj().Any = function(j$) {
2385 |
2386 | function Any(expectedObject) {
2387 | this.expectedObject = expectedObject;
2388 | }
2389 |
2390 | Any.prototype.asymmetricMatch = function(other) {
2391 | if (this.expectedObject == String) {
2392 | return typeof other == 'string' || other instanceof String;
2393 | }
2394 |
2395 | if (this.expectedObject == Number) {
2396 | return typeof other == 'number' || other instanceof Number;
2397 | }
2398 |
2399 | if (this.expectedObject == Function) {
2400 | return typeof other == 'function' || other instanceof Function;
2401 | }
2402 |
2403 | if (this.expectedObject == Object) {
2404 | return typeof other == 'object';
2405 | }
2406 |
2407 | if (this.expectedObject == Boolean) {
2408 | return typeof other == 'boolean';
2409 | }
2410 |
2411 | return other instanceof this.expectedObject;
2412 | };
2413 |
2414 | Any.prototype.jasmineToString = function() {
2415 | return '';
2416 | };
2417 |
2418 | return Any;
2419 | };
2420 |
2421 | getJasmineRequireObj().Anything = function(j$) {
2422 |
2423 | function Anything() {}
2424 |
2425 | Anything.prototype.asymmetricMatch = function(other) {
2426 | return !j$.util.isUndefined(other) && other !== null;
2427 | };
2428 |
2429 | Anything.prototype.jasmineToString = function() {
2430 | return '';
2431 | };
2432 |
2433 | return Anything;
2434 | };
2435 |
2436 | getJasmineRequireObj().ArrayContaining = function(j$) {
2437 | function ArrayContaining(sample) {
2438 | this.sample = sample;
2439 | }
2440 |
2441 | ArrayContaining.prototype.asymmetricMatch = function(other) {
2442 | var className = Object.prototype.toString.call(this.sample);
2443 | if (className !== '[object Array]') { throw new Error('You must provide an array to arrayContaining, not \'' + this.sample + '\'.'); }
2444 |
2445 | for (var i = 0; i < this.sample.length; i++) {
2446 | var item = this.sample[i];
2447 | if (!j$.matchersUtil.contains(other, item)) {
2448 | return false;
2449 | }
2450 | }
2451 |
2452 | return true;
2453 | };
2454 |
2455 | ArrayContaining.prototype.jasmineToString = function () {
2456 | return '';
2457 | };
2458 |
2459 | return ArrayContaining;
2460 | };
2461 |
2462 | getJasmineRequireObj().ObjectContaining = function(j$) {
2463 |
2464 | function ObjectContaining(sample) {
2465 | this.sample = sample;
2466 | }
2467 |
2468 | function getPrototype(obj) {
2469 | if (Object.getPrototypeOf) {
2470 | return Object.getPrototypeOf(obj);
2471 | }
2472 |
2473 | if (obj.constructor.prototype == obj) {
2474 | return null;
2475 | }
2476 |
2477 | return obj.constructor.prototype;
2478 | }
2479 |
2480 | function hasProperty(obj, property) {
2481 | if (!obj) {
2482 | return false;
2483 | }
2484 |
2485 | if (Object.prototype.hasOwnProperty.call(obj, property)) {
2486 | return true;
2487 | }
2488 |
2489 | return hasProperty(getPrototype(obj), property);
2490 | }
2491 |
2492 | ObjectContaining.prototype.asymmetricMatch = function(other) {
2493 | if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); }
2494 |
2495 | for (var property in this.sample) {
2496 | if (!hasProperty(other, property) ||
2497 | !j$.matchersUtil.equals(this.sample[property], other[property])) {
2498 | return false;
2499 | }
2500 | }
2501 |
2502 | return true;
2503 | };
2504 |
2505 | ObjectContaining.prototype.jasmineToString = function() {
2506 | return '';
2507 | };
2508 |
2509 | return ObjectContaining;
2510 | };
2511 |
2512 | getJasmineRequireObj().StringMatching = function(j$) {
2513 |
2514 | function StringMatching(expected) {
2515 | if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) {
2516 | throw new Error('Expected is not a String or a RegExp');
2517 | }
2518 |
2519 | this.regexp = new RegExp(expected);
2520 | }
2521 |
2522 | StringMatching.prototype.asymmetricMatch = function(other) {
2523 | return this.regexp.test(other);
2524 | };
2525 |
2526 | StringMatching.prototype.jasmineToString = function() {
2527 | return '';
2528 | };
2529 |
2530 | return StringMatching;
2531 | };
2532 |
2533 | getJasmineRequireObj().errors = function() {
2534 | function ExpectationFailed() {}
2535 |
2536 | ExpectationFailed.prototype = new Error();
2537 | ExpectationFailed.prototype.constructor = ExpectationFailed;
2538 |
2539 | return {
2540 | ExpectationFailed: ExpectationFailed
2541 | };
2542 | };
2543 | getJasmineRequireObj().matchersUtil = function(j$) {
2544 | // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter?
2545 |
2546 | return {
2547 | equals: function(a, b, customTesters) {
2548 | customTesters = customTesters || [];
2549 |
2550 | return eq(a, b, [], [], customTesters);
2551 | },
2552 |
2553 | contains: function(haystack, needle, customTesters) {
2554 | customTesters = customTesters || [];
2555 |
2556 | if ((Object.prototype.toString.apply(haystack) === '[object Array]') ||
2557 | (!!haystack && !haystack.indexOf))
2558 | {
2559 | for (var i = 0; i < haystack.length; i++) {
2560 | if (eq(haystack[i], needle, [], [], customTesters)) {
2561 | return true;
2562 | }
2563 | }
2564 | return false;
2565 | }
2566 |
2567 | return !!haystack && haystack.indexOf(needle) >= 0;
2568 | },
2569 |
2570 | buildFailureMessage: function() {
2571 | var args = Array.prototype.slice.call(arguments, 0),
2572 | matcherName = args[0],
2573 | isNot = args[1],
2574 | actual = args[2],
2575 | expected = args.slice(3),
2576 | englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
2577 |
2578 | var message = 'Expected ' +
2579 | j$.pp(actual) +
2580 | (isNot ? ' not ' : ' ') +
2581 | englishyPredicate;
2582 |
2583 | if (expected.length > 0) {
2584 | for (var i = 0; i < expected.length; i++) {
2585 | if (i > 0) {
2586 | message += ',';
2587 | }
2588 | message += ' ' + j$.pp(expected[i]);
2589 | }
2590 | }
2591 |
2592 | return message + '.';
2593 | }
2594 | };
2595 |
2596 | function isAsymmetric(obj) {
2597 | return obj && j$.isA_('Function', obj.asymmetricMatch);
2598 | }
2599 |
2600 | function asymmetricMatch(a, b) {
2601 | var asymmetricA = isAsymmetric(a),
2602 | asymmetricB = isAsymmetric(b);
2603 |
2604 | if (asymmetricA && asymmetricB) {
2605 | return undefined;
2606 | }
2607 |
2608 | if (asymmetricA) {
2609 | return a.asymmetricMatch(b);
2610 | }
2611 |
2612 | if (asymmetricB) {
2613 | return b.asymmetricMatch(a);
2614 | }
2615 | }
2616 |
2617 | // Equality function lovingly adapted from isEqual in
2618 | // [Underscore](http://underscorejs.org)
2619 | function eq(a, b, aStack, bStack, customTesters) {
2620 | var result = true;
2621 |
2622 | var asymmetricResult = asymmetricMatch(a, b);
2623 | if (!j$.util.isUndefined(asymmetricResult)) {
2624 | return asymmetricResult;
2625 | }
2626 |
2627 | for (var i = 0; i < customTesters.length; i++) {
2628 | var customTesterResult = customTesters[i](a, b);
2629 | if (!j$.util.isUndefined(customTesterResult)) {
2630 | return customTesterResult;
2631 | }
2632 | }
2633 |
2634 | if (a instanceof Error && b instanceof Error) {
2635 | return a.message == b.message;
2636 | }
2637 |
2638 | // Identical objects are equal. `0 === -0`, but they aren't identical.
2639 | // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
2640 | if (a === b) { return a !== 0 || 1 / a == 1 / b; }
2641 | // A strict comparison is necessary because `null == undefined`.
2642 | if (a === null || b === null) { return a === b; }
2643 | var className = Object.prototype.toString.call(a);
2644 | if (className != Object.prototype.toString.call(b)) { return false; }
2645 | switch (className) {
2646 | // Strings, numbers, dates, and booleans are compared by value.
2647 | case '[object String]':
2648 | // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
2649 | // equivalent to `new String("5")`.
2650 | return a == String(b);
2651 | case '[object Number]':
2652 | // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
2653 | // other numeric values.
2654 | return a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b);
2655 | case '[object Date]':
2656 | case '[object Boolean]':
2657 | // Coerce dates and booleans to numeric primitive values. Dates are compared by their
2658 | // millisecond representations. Note that invalid dates with millisecond representations
2659 | // of `NaN` are not equivalent.
2660 | return +a == +b;
2661 | // RegExps are compared by their source patterns and flags.
2662 | case '[object RegExp]':
2663 | return a.source == b.source &&
2664 | a.global == b.global &&
2665 | a.multiline == b.multiline &&
2666 | a.ignoreCase == b.ignoreCase;
2667 | }
2668 | if (typeof a != 'object' || typeof b != 'object') { return false; }
2669 |
2670 | var aIsDomNode = j$.isDomNode(a);
2671 | var bIsDomNode = j$.isDomNode(b);
2672 | if (aIsDomNode && bIsDomNode) {
2673 | // At first try to use DOM3 method isEqualNode
2674 | if (a.isEqualNode) {
2675 | return a.isEqualNode(b);
2676 | }
2677 | // IE8 doesn't support isEqualNode, try to use outerHTML && innerText
2678 | var aIsElement = a instanceof Element;
2679 | var bIsElement = b instanceof Element;
2680 | if (aIsElement && bIsElement) {
2681 | return a.outerHTML == b.outerHTML;
2682 | }
2683 | if (aIsElement || bIsElement) {
2684 | return false;
2685 | }
2686 | return a.innerText == b.innerText && a.textContent == b.textContent;
2687 | }
2688 | if (aIsDomNode || bIsDomNode) {
2689 | return false;
2690 | }
2691 |
2692 | // Assume equality for cyclic structures. The algorithm for detecting cyclic
2693 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
2694 | var length = aStack.length;
2695 | while (length--) {
2696 | // Linear search. Performance is inversely proportional to the number of
2697 | // unique nested structures.
2698 | if (aStack[length] == a) { return bStack[length] == b; }
2699 | }
2700 | // Add the first object to the stack of traversed objects.
2701 | aStack.push(a);
2702 | bStack.push(b);
2703 | var size = 0;
2704 | // Recursively compare objects and arrays.
2705 | // Compare array lengths to determine if a deep comparison is necessary.
2706 | if (className == '[object Array]' && a.length !== b.length) {
2707 | result = false;
2708 | }
2709 |
2710 | if (result) {
2711 | // Objects with different constructors are not equivalent, but `Object`s
2712 | // or `Array`s from different frames are.
2713 | if (className !== '[object Array]') {
2714 | var aCtor = a.constructor, bCtor = b.constructor;
2715 | if (aCtor !== bCtor && !(isFunction(aCtor) && aCtor instanceof aCtor &&
2716 | isFunction(bCtor) && bCtor instanceof bCtor)) {
2717 | return false;
2718 | }
2719 | }
2720 | // Deep compare objects.
2721 | for (var key in a) {
2722 | if (has(a, key)) {
2723 | // Count the expected number of properties.
2724 | size++;
2725 | // Deep compare each member.
2726 | if (!(result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters))) { break; }
2727 | }
2728 | }
2729 | // Ensure that both objects contain the same number of properties.
2730 | if (result) {
2731 | for (key in b) {
2732 | if (has(b, key) && !(size--)) { break; }
2733 | }
2734 | result = !size;
2735 | }
2736 | }
2737 | // Remove the first object from the stack of traversed objects.
2738 | aStack.pop();
2739 | bStack.pop();
2740 |
2741 | return result;
2742 |
2743 | function has(obj, key) {
2744 | return Object.prototype.hasOwnProperty.call(obj, key);
2745 | }
2746 |
2747 | function isFunction(obj) {
2748 | return typeof obj === 'function';
2749 | }
2750 | }
2751 | };
2752 |
2753 | getJasmineRequireObj().toBe = function() {
2754 | function toBe() {
2755 | return {
2756 | compare: function(actual, expected) {
2757 | return {
2758 | pass: actual === expected
2759 | };
2760 | }
2761 | };
2762 | }
2763 |
2764 | return toBe;
2765 | };
2766 |
2767 | getJasmineRequireObj().toBeCloseTo = function() {
2768 |
2769 | function toBeCloseTo() {
2770 | return {
2771 | compare: function(actual, expected, precision) {
2772 | if (precision !== 0) {
2773 | precision = precision || 2;
2774 | }
2775 |
2776 | return {
2777 | pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2)
2778 | };
2779 | }
2780 | };
2781 | }
2782 |
2783 | return toBeCloseTo;
2784 | };
2785 |
2786 | getJasmineRequireObj().toBeDefined = function() {
2787 | function toBeDefined() {
2788 | return {
2789 | compare: function(actual) {
2790 | return {
2791 | pass: (void 0 !== actual)
2792 | };
2793 | }
2794 | };
2795 | }
2796 |
2797 | return toBeDefined;
2798 | };
2799 |
2800 | getJasmineRequireObj().toBeFalsy = function() {
2801 | function toBeFalsy() {
2802 | return {
2803 | compare: function(actual) {
2804 | return {
2805 | pass: !!!actual
2806 | };
2807 | }
2808 | };
2809 | }
2810 |
2811 | return toBeFalsy;
2812 | };
2813 |
2814 | getJasmineRequireObj().toBeGreaterThan = function() {
2815 |
2816 | function toBeGreaterThan() {
2817 | return {
2818 | compare: function(actual, expected) {
2819 | return {
2820 | pass: actual > expected
2821 | };
2822 | }
2823 | };
2824 | }
2825 |
2826 | return toBeGreaterThan;
2827 | };
2828 |
2829 |
2830 | getJasmineRequireObj().toBeLessThan = function() {
2831 | function toBeLessThan() {
2832 | return {
2833 |
2834 | compare: function(actual, expected) {
2835 | return {
2836 | pass: actual < expected
2837 | };
2838 | }
2839 | };
2840 | }
2841 |
2842 | return toBeLessThan;
2843 | };
2844 | getJasmineRequireObj().toBeNaN = function(j$) {
2845 |
2846 | function toBeNaN() {
2847 | return {
2848 | compare: function(actual) {
2849 | var result = {
2850 | pass: (actual !== actual)
2851 | };
2852 |
2853 | if (result.pass) {
2854 | result.message = 'Expected actual not to be NaN.';
2855 | } else {
2856 | result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; };
2857 | }
2858 |
2859 | return result;
2860 | }
2861 | };
2862 | }
2863 |
2864 | return toBeNaN;
2865 | };
2866 |
2867 | getJasmineRequireObj().toBeNull = function() {
2868 |
2869 | function toBeNull() {
2870 | return {
2871 | compare: function(actual) {
2872 | return {
2873 | pass: actual === null
2874 | };
2875 | }
2876 | };
2877 | }
2878 |
2879 | return toBeNull;
2880 | };
2881 |
2882 | getJasmineRequireObj().toBeTruthy = function() {
2883 |
2884 | function toBeTruthy() {
2885 | return {
2886 | compare: function(actual) {
2887 | return {
2888 | pass: !!actual
2889 | };
2890 | }
2891 | };
2892 | }
2893 |
2894 | return toBeTruthy;
2895 | };
2896 |
2897 | getJasmineRequireObj().toBeUndefined = function() {
2898 |
2899 | function toBeUndefined() {
2900 | return {
2901 | compare: function(actual) {
2902 | return {
2903 | pass: void 0 === actual
2904 | };
2905 | }
2906 | };
2907 | }
2908 |
2909 | return toBeUndefined;
2910 | };
2911 |
2912 | getJasmineRequireObj().toContain = function() {
2913 | function toContain(util, customEqualityTesters) {
2914 | customEqualityTesters = customEqualityTesters || [];
2915 |
2916 | return {
2917 | compare: function(actual, expected) {
2918 |
2919 | return {
2920 | pass: util.contains(actual, expected, customEqualityTesters)
2921 | };
2922 | }
2923 | };
2924 | }
2925 |
2926 | return toContain;
2927 | };
2928 |
2929 | getJasmineRequireObj().toEqual = function() {
2930 |
2931 | function toEqual(util, customEqualityTesters) {
2932 | customEqualityTesters = customEqualityTesters || [];
2933 |
2934 | return {
2935 | compare: function(actual, expected) {
2936 | var result = {
2937 | pass: false
2938 | };
2939 |
2940 | result.pass = util.equals(actual, expected, customEqualityTesters);
2941 |
2942 | return result;
2943 | }
2944 | };
2945 | }
2946 |
2947 | return toEqual;
2948 | };
2949 |
2950 | getJasmineRequireObj().toHaveBeenCalled = function(j$) {
2951 |
2952 | function toHaveBeenCalled() {
2953 | return {
2954 | compare: function(actual) {
2955 | var result = {};
2956 |
2957 | if (!j$.isSpy(actual)) {
2958 | throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.');
2959 | }
2960 |
2961 | if (arguments.length > 1) {
2962 | throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
2963 | }
2964 |
2965 | result.pass = actual.calls.any();
2966 |
2967 | result.message = result.pass ?
2968 | 'Expected spy ' + actual.and.identity() + ' not to have been called.' :
2969 | 'Expected spy ' + actual.and.identity() + ' to have been called.';
2970 |
2971 | return result;
2972 | }
2973 | };
2974 | }
2975 |
2976 | return toHaveBeenCalled;
2977 | };
2978 |
2979 | getJasmineRequireObj().toHaveBeenCalledWith = function(j$) {
2980 |
2981 | function toHaveBeenCalledWith(util, customEqualityTesters) {
2982 | return {
2983 | compare: function() {
2984 | var args = Array.prototype.slice.call(arguments, 0),
2985 | actual = args[0],
2986 | expectedArgs = args.slice(1),
2987 | result = { pass: false };
2988 |
2989 | if (!j$.isSpy(actual)) {
2990 | throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.');
2991 | }
2992 |
2993 | if (!actual.calls.any()) {
2994 | result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; };
2995 | return result;
2996 | }
2997 |
2998 | if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) {
2999 | result.pass = true;
3000 | result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; };
3001 | } else {
3002 | result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; };
3003 | }
3004 |
3005 | return result;
3006 | }
3007 | };
3008 | }
3009 |
3010 | return toHaveBeenCalledWith;
3011 | };
3012 |
3013 | getJasmineRequireObj().toMatch = function(j$) {
3014 |
3015 | function toMatch() {
3016 | return {
3017 | compare: function(actual, expected) {
3018 | if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) {
3019 | throw new Error('Expected is not a String or a RegExp');
3020 | }
3021 |
3022 | var regexp = new RegExp(expected);
3023 |
3024 | return {
3025 | pass: regexp.test(actual)
3026 | };
3027 | }
3028 | };
3029 | }
3030 |
3031 | return toMatch;
3032 | };
3033 |
3034 | getJasmineRequireObj().toThrow = function(j$) {
3035 |
3036 | function toThrow(util) {
3037 | return {
3038 | compare: function(actual, expected) {
3039 | var result = { pass: false },
3040 | threw = false,
3041 | thrown;
3042 |
3043 | if (typeof actual != 'function') {
3044 | throw new Error('Actual is not a Function');
3045 | }
3046 |
3047 | try {
3048 | actual();
3049 | } catch (e) {
3050 | threw = true;
3051 | thrown = e;
3052 | }
3053 |
3054 | if (!threw) {
3055 | result.message = 'Expected function to throw an exception.';
3056 | return result;
3057 | }
3058 |
3059 | if (arguments.length == 1) {
3060 | result.pass = true;
3061 | result.message = function() { return 'Expected function not to throw, but it threw ' + j$.pp(thrown) + '.'; };
3062 |
3063 | return result;
3064 | }
3065 |
3066 | if (util.equals(thrown, expected)) {
3067 | result.pass = true;
3068 | result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; };
3069 | } else {
3070 | result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it threw ' + j$.pp(thrown) + '.'; };
3071 | }
3072 |
3073 | return result;
3074 | }
3075 | };
3076 | }
3077 |
3078 | return toThrow;
3079 | };
3080 |
3081 | getJasmineRequireObj().toThrowError = function(j$) {
3082 | function toThrowError (util) {
3083 | return {
3084 | compare: function(actual) {
3085 | var threw = false,
3086 | pass = {pass: true},
3087 | fail = {pass: false},
3088 | thrown;
3089 |
3090 | if (typeof actual != 'function') {
3091 | throw new Error('Actual is not a Function');
3092 | }
3093 |
3094 | var errorMatcher = getMatcher.apply(null, arguments);
3095 |
3096 | try {
3097 | actual();
3098 | } catch (e) {
3099 | threw = true;
3100 | thrown = e;
3101 | }
3102 |
3103 | if (!threw) {
3104 | fail.message = 'Expected function to throw an Error.';
3105 | return fail;
3106 | }
3107 |
3108 | if (!(thrown instanceof Error)) {
3109 | fail.message = function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; };
3110 | return fail;
3111 | }
3112 |
3113 | if (errorMatcher.hasNoSpecifics()) {
3114 | pass.message = 'Expected function not to throw an Error, but it threw ' + j$.fnNameFor(thrown) + '.';
3115 | return pass;
3116 | }
3117 |
3118 | if (errorMatcher.matches(thrown)) {
3119 | pass.message = function() {
3120 | return 'Expected function not to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + '.';
3121 | };
3122 | return pass;
3123 | } else {
3124 | fail.message = function() {
3125 | return 'Expected function to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() +
3126 | ', but it threw ' + errorMatcher.thrownDescription(thrown) + '.';
3127 | };
3128 | return fail;
3129 | }
3130 | }
3131 | };
3132 |
3133 | function getMatcher() {
3134 | var expected = null,
3135 | errorType = null;
3136 |
3137 | if (arguments.length == 2) {
3138 | expected = arguments[1];
3139 | if (isAnErrorType(expected)) {
3140 | errorType = expected;
3141 | expected = null;
3142 | }
3143 | } else if (arguments.length > 2) {
3144 | errorType = arguments[1];
3145 | expected = arguments[2];
3146 | if (!isAnErrorType(errorType)) {
3147 | throw new Error('Expected error type is not an Error.');
3148 | }
3149 | }
3150 |
3151 | if (expected && !isStringOrRegExp(expected)) {
3152 | if (errorType) {
3153 | throw new Error('Expected error message is not a string or RegExp.');
3154 | } else {
3155 | throw new Error('Expected is not an Error, string, or RegExp.');
3156 | }
3157 | }
3158 |
3159 | function messageMatch(message) {
3160 | if (typeof expected == 'string') {
3161 | return expected == message;
3162 | } else {
3163 | return expected.test(message);
3164 | }
3165 | }
3166 |
3167 | return {
3168 | errorTypeDescription: errorType ? j$.fnNameFor(errorType) : 'an exception',
3169 | thrownDescription: function(thrown) {
3170 | var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception',
3171 | thrownMessage = '';
3172 |
3173 | if (expected) {
3174 | thrownMessage = ' with message ' + j$.pp(thrown.message);
3175 | }
3176 |
3177 | return thrownName + thrownMessage;
3178 | },
3179 | messageDescription: function() {
3180 | if (expected === null) {
3181 | return '';
3182 | } else if (expected instanceof RegExp) {
3183 | return ' with a message matching ' + j$.pp(expected);
3184 | } else {
3185 | return ' with message ' + j$.pp(expected);
3186 | }
3187 | },
3188 | hasNoSpecifics: function() {
3189 | return expected === null && errorType === null;
3190 | },
3191 | matches: function(error) {
3192 | return (errorType === null || error instanceof errorType) &&
3193 | (expected === null || messageMatch(error.message));
3194 | }
3195 | };
3196 | }
3197 |
3198 | function isStringOrRegExp(potential) {
3199 | return potential instanceof RegExp || (typeof potential == 'string');
3200 | }
3201 |
3202 | function isAnErrorType(type) {
3203 | if (typeof type !== 'function') {
3204 | return false;
3205 | }
3206 |
3207 | var Surrogate = function() {};
3208 | Surrogate.prototype = type.prototype;
3209 | return (new Surrogate()) instanceof Error;
3210 | }
3211 | }
3212 |
3213 | return toThrowError;
3214 | };
3215 |
3216 | getJasmineRequireObj().interface = function(jasmine, env) {
3217 | var jasmineInterface = {
3218 | describe: function(description, specDefinitions) {
3219 | return env.describe(description, specDefinitions);
3220 | },
3221 |
3222 | xdescribe: function(description, specDefinitions) {
3223 | return env.xdescribe(description, specDefinitions);
3224 | },
3225 |
3226 | fdescribe: function(description, specDefinitions) {
3227 | return env.fdescribe(description, specDefinitions);
3228 | },
3229 |
3230 | it: function() {
3231 | return env.it.apply(env, arguments);
3232 | },
3233 |
3234 | xit: function() {
3235 | return env.xit.apply(env, arguments);
3236 | },
3237 |
3238 | fit: function() {
3239 | return env.fit.apply(env, arguments);
3240 | },
3241 |
3242 | beforeEach: function() {
3243 | return env.beforeEach.apply(env, arguments);
3244 | },
3245 |
3246 | afterEach: function() {
3247 | return env.afterEach.apply(env, arguments);
3248 | },
3249 |
3250 | beforeAll: function() {
3251 | return env.beforeAll.apply(env, arguments);
3252 | },
3253 |
3254 | afterAll: function() {
3255 | return env.afterAll.apply(env, arguments);
3256 | },
3257 |
3258 | expect: function(actual) {
3259 | return env.expect(actual);
3260 | },
3261 |
3262 | pending: function() {
3263 | return env.pending.apply(env, arguments);
3264 | },
3265 |
3266 | fail: function() {
3267 | return env.fail.apply(env, arguments);
3268 | },
3269 |
3270 | spyOn: function(obj, methodName) {
3271 | return env.spyOn(obj, methodName);
3272 | },
3273 |
3274 | jsApiReporter: new jasmine.JsApiReporter({
3275 | timer: new jasmine.Timer()
3276 | }),
3277 |
3278 | jasmine: jasmine
3279 | };
3280 |
3281 | jasmine.addCustomEqualityTester = function(tester) {
3282 | env.addCustomEqualityTester(tester);
3283 | };
3284 |
3285 | jasmine.addMatchers = function(matchers) {
3286 | return env.addMatchers(matchers);
3287 | };
3288 |
3289 | jasmine.clock = function() {
3290 | return env.clock;
3291 | };
3292 |
3293 | return jasmineInterface;
3294 | };
3295 |
3296 | getJasmineRequireObj().version = function() {
3297 | return '2.3.4';
3298 | };
3299 |
--------------------------------------------------------------------------------