Speedometer is a browser benchmark that measures the responsiveness of Web applications. It uses demo web applications to simulate user actions such as adding to-do items.
@@ -35,10 +37,11 @@
+
+ Score
Score
+ Detailed Results
@@ -100,34 +106,68 @@Detailed Metrics
+ About Speedometer 2.1
+About Speedometer 3
Speedometer tests a browser's Web app responsiveness by timing simulated user interactions.
- -- This benchmark simulates user actions for adding, completing, and removing to-do items using multiple examples in TodoMVC. Each example in TodoMVC implements the same todo application using DOM APIs in different ways. Some - call DOM APIs directly from ECMAScript 5 (ES5), ECMASCript 2015 (ES6), ES6 transpiled to ES5, and Elm transpiled to ES5. Others use one of eleven popular JavaScript frameworks: React, React with Redux, Ember.js, - Backbone.js, AngularJS, (new) Angular, Vue.js, jQuery, Preact, Inferno, and Flight. Many of these frameworks are used on the most popular websites in the world, such as Facebook and Twitter. The performance of these types - of operations depends on the speed of the DOM APIs, the JavaScript engine, CSS style resolution, layout, and other technologies. -
- +Speedometer 3 is a benchmark for web browsers that measures Web application responsiveness by timing simulated user interactions on various workloads.
- Although user-driven actions like mouse movements and keyboard input cannot be accurately emulated in JavaScript, Speedometer does its best to faithfully replay a typical workload within the demo applications. To make the - run time long enough to measure with the limited precision, we synchronously execute a large number of the operations, such as adding one hundred to-do items. + The following high level user journeys are implemented in the current version. Each of these journeys has one or more workloads which test important aspects of it - for example commonly used patterns, frameworks, or + technologies.
+-
+
- Working with a todo list +
- Measures the time to add, complete, and remove 100 todo items in a basic list. +
- Each example implements the same todo application (TodoMVC) using different techniques and frameworks. +
- + Workloads: TodoMVC-JavaScript-ES5, + TodoMVC-WebComponents, + TodoMVC-React-Complex-DOM, + TodoMVC-Backbone, TodoMVC-Angular, + TodoMVC-Vue, TodoMVC-jQuery, + TodoMVC-Preact, TodoMVC-Svelte, + TodoMVC-Lit + +
- Editing rich text +
- Loading and styling text inside WYSIWYG and code editors. +
- Workloads: Editor-CodeMirror, Editor-TipTap +
- Rendering charts +
- Loading and interacting with SVG and canvas charts. +
- + Workloads: Charts-observable-plot, Charts-chartjs, + React-Stockcharts-SVG, Perf-Dashboard + +
- Reading a news site +
- Navigating across pages and interacting with a typical looking news site. +
- Workloads: NewsSite-Next, NewsSite-Nuxt +
-
+
-
+
-
+
-
+
- Modern browser engines execute some work asynchronously as an optimization strategy to reduce the run time of synchronous operations. While returning control back to JavaScript execution as soon as possible is worth - pursuing, the run time cost of such an asynchronous work should still be taken into a holistic measurement of web application performance. In addition, some modern JavaScript frameworks such as Vue.js and Preact call into - DOM APIs asynchronously as an optimization technique. Speedometer approximates the run time of this asynchronous work in the UI thread with a zero-second timer that is scheduled immediately after each execution of - synchronous operations. -
- -Speedometer does not attempt to measure concurrent asynchronous work that does not directly impact the UI thread, which tends not to affect app responsiveness.
- -Note: Speedometer should not be used as a way to compare the performance of different JavaScript frameworks as work load differs greatly in each framework.
+Notes about methodology
+-
+
- Although user-driven actions like mouse movements and keyboard input cannot be fully emulated in JavaScript, Speedometer does its best to faithfully replay a typical workload within the demo applications. +
- To make the run time long enough to measure with the limited precision, we synchronously execute a large number of the operations, such as adding one hundred to-do items. +
- + Modern browser engines execute some work asynchronously as an optimization strategy to reduce the run time of synchronous operations. While returning control back to JavaScript execution as soon as possible is worth + pursuing, the run time cost of such an asynchronous work should still be taken into a holistic measurement of web application performance. In addition, some JavaScript frameworks call into DOM APIs asynchronously as an + optimization technique. Speedometer approximates the run time of this asynchronous work in the UI thread with a zero-second timer that is scheduled immediately after each execution of synchronous operations. + +
- Speedometer does not attempt to measure concurrent asynchronous work (e.g. in Web Workers). +
- Speedometer should not be used as a way to compare the performance of different JavaScript frameworks. +
- + The goal of all workloads is to represent a scenario that could be found on the Web. Although all workloads strive to use patterns that are commonly used, some implementation details are Speedometer specific and should + not be used as a guideline on how to implement and deploy a standalone app. For example, due to constraints within the test harness, workloads must not depend on a server infrastructure to function properly and are + built as static files ahead of time. + +
- \
+ {{#moveToItems}}\
+
- {{.}} \ + {{/moveToItems}}\ +
jasmine.undefined instead of undefined, since undefined is just
+ * a plain old variable and may be redefined by somebody else.
+ *
+ * @private
+ */
+jasmine.undefined = jasmine.___undefined___;
+
+/**
+ * Show diagnostic messages in the console if set to true
+ *
+ */
+jasmine.VERBOSE = false;
+
+/**
+ * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed.
+ *
+ */
+jasmine.DEFAULT_UPDATE_INTERVAL = 250;
+
+/**
+ * Default timeout interval in milliseconds for waitsFor() blocks.
+ */
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
+
+jasmine.getGlobal = function() {
+ function getGlobal() {
+ return this;
+ }
+
+ return getGlobal();
+};
+
+/**
+ * Allows for bound functions to be compared. Internal use only.
+ *
+ * @ignore
+ * @private
+ * @param base {Object} bound 'this' for the function
+ * @param name {Function} function to find
+ */
+jasmine.bindOriginal_ = function(base, name) {
+ var original = base[name];
+ if (original.apply) {
+ return function() {
+ return original.apply(base, arguments);
+ };
+ } else {
+ // IE support
+ return jasmine.getGlobal()[name];
+ }
+};
+
+jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
+jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
+jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
+jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
+
+jasmine.MessageResult = function(values) {
+ this.type = 'log';
+ this.values = values;
+ this.trace = new Error(); // todo: test better
+};
+
+jasmine.MessageResult.prototype.toString = function() {
+ var text = "";
+ for (var i = 0; i < this.values.length; i++) {
+ if (i > 0) text += " ";
+ if (jasmine.isString_(this.values[i])) {
+ text += this.values[i];
+ } else {
+ text += jasmine.pp(this.values[i]);
+ }
+ }
+ return text;
+};
+
+jasmine.ExpectationResult = function(params) {
+ this.type = 'expect';
+ this.matcherName = params.matcherName;
+ this.passed_ = params.passed;
+ this.expected = params.expected;
+ this.actual = params.actual;
+ this.message = this.passed_ ? 'Passed.' : params.message;
+
+ var trace = (params.trace || new Error(this.message));
+ this.trace = this.passed_ ? '' : trace;
+};
+
+jasmine.ExpectationResult.prototype.toString = function () {
+ return this.message;
+};
+
+jasmine.ExpectationResult.prototype.passed = function () {
+ return this.passed_;
+};
+
+/**
+ * Getter for the Jasmine environment. Ensures one gets created
+ */
+jasmine.getEnv = function() {
+ var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
+ return env;
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isArray_ = function(value) {
+ return jasmine.isA_("Array", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isString_ = function(value) {
+ return jasmine.isA_("String", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isNumber_ = function(value) {
+ return jasmine.isA_("Number", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param {String} typeName
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isA_ = function(typeName, value) {
+ return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
+};
+
+/**
+ * Pretty printer for expecations. Takes any object and turns it into a human-readable string.
+ *
+ * @param value {Object} an object to be outputted
+ * @returns {String}
+ */
+jasmine.pp = function(value) {
+ var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
+ stringPrettyPrinter.format(value);
+ return stringPrettyPrinter.string;
+};
+
+/**
+ * Returns true if the object is a DOM Node.
+ *
+ * @param {Object} obj object to check
+ * @returns {Boolean}
+ */
+jasmine.isDomNode = function(obj) {
+ return obj.nodeType > 0;
+};
+
+/**
+ * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter.
+ *
+ * @example
+ * // don't care about which function is passed in, as long as it's a function
+ * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
+ *
+ * @param {Class} clazz
+ * @returns matchable object of the type clazz
+ */
+jasmine.any = function(clazz) {
+ return new jasmine.Matchers.Any(clazz);
+};
+
+/**
+ * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
+ *
+ * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine
+ * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
+ *
+ * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
+ *
+ * Spies are torn down at the end of every spec.
+ *
+ * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
+ *
+ * @example
+ * // a stub
+ * var myStub = jasmine.createSpy('myStub'); // can be used anywhere
+ *
+ * // spy example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ *
+ * // actual foo.not will not be called, execution stops
+ * spyOn(foo, 'not');
+
+ // foo.not spied upon, execution will continue to implementation
+ * spyOn(foo, 'not').andCallThrough();
+ *
+ * // fake example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ *
+ * // foo.not(val) will return val
+ * spyOn(foo, 'not').andCallFake(function(value) {return value;});
+ *
+ * // mock example
+ * foo.not(7 == 7);
+ * expect(foo.not).toHaveBeenCalled();
+ * expect(foo.not).toHaveBeenCalledWith(true);
+ *
+ * @constructor
+ * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
+ * @param {String} name
+ */
+jasmine.Spy = function(name) {
+ /**
+ * The name of the spy, if provided.
+ */
+ this.identity = name || 'unknown';
+ /**
+ * Is this Object a spy?
+ */
+ this.isSpy = true;
+ /**
+ * The actual function this spy stubs.
+ */
+ this.plan = function() {
+ };
+ /**
+ * Tracking of the most recent call to the spy.
+ * @example
+ * var mySpy = jasmine.createSpy('foo');
+ * mySpy(1, 2);
+ * mySpy.mostRecentCall.args = [1, 2];
+ */
+ this.mostRecentCall = {};
+
+ /**
+ * Holds arguments for each call to the spy, indexed by call count
+ * @example
+ * var mySpy = jasmine.createSpy('foo');
+ * mySpy(1, 2);
+ * mySpy(7, 8);
+ * mySpy.mostRecentCall.args = [7, 8];
+ * mySpy.argsForCall[0] = [1, 2];
+ * mySpy.argsForCall[1] = [7, 8];
+ */
+ this.argsForCall = [];
+ this.calls = [];
+};
+
+/**
+ * Tells a spy to call through to the actual implemenatation.
+ *
+ * @example
+ * var foo = {
+ * bar: function() { // do some stuff }
+ * }
+ *
+ * // defining a spy on an existing property: foo.bar
+ * spyOn(foo, 'bar').andCallThrough();
+ */
+jasmine.Spy.prototype.andCallThrough = function() {
+ this.plan = this.originalValue;
+ return this;
+};
+
+/**
+ * For setting the return value of a spy.
+ *
+ * @example
+ * // defining a spy from scratch: foo() returns 'baz'
+ * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() returns 'baz'
+ * spyOn(foo, 'bar').andReturn('baz');
+ *
+ * @param {Object} value
+ */
+jasmine.Spy.prototype.andReturn = function(value) {
+ this.plan = function() {
+ return value;
+ };
+ return this;
+};
+
+/**
+ * For throwing an exception when a spy is called.
+ *
+ * @example
+ * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
+ * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
+ * spyOn(foo, 'bar').andThrow('baz');
+ *
+ * @param {String} exceptionMsg
+ */
+jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
+ this.plan = function() {
+ throw exceptionMsg;
+ };
+ return this;
+};
+
+/**
+ * Calls an alternate implementation when a spy is called.
+ *
+ * @example
+ * var baz = function() {
+ * // do some stuff, return something
+ * }
+ * // defining a spy from scratch: foo() calls the function baz
+ * var foo = jasmine.createSpy('spy on foo').andCall(baz);
+ *
+ * // defining a spy on an existing property: foo.bar() calls an anonymnous function
+ * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
+ *
+ * @param {Function} fakeFunc
+ */
+jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
+ this.plan = fakeFunc;
+ return this;
+};
+
+/**
+ * Resets all of a spy's the tracking variables so that it can be used again.
+ *
+ * @example
+ * spyOn(foo, 'bar');
+ *
+ * foo.bar();
+ *
+ * expect(foo.bar.callCount).toEqual(1);
+ *
+ * foo.bar.reset();
+ *
+ * expect(foo.bar.callCount).toEqual(0);
+ */
+jasmine.Spy.prototype.reset = function() {
+ this.wasCalled = false;
+ this.callCount = 0;
+ this.argsForCall = [];
+ this.calls = [];
+ this.mostRecentCall = {};
+};
+
+jasmine.createSpy = function(name) {
+
+ var spyObj = function() {
+ spyObj.wasCalled = true;
+ spyObj.callCount++;
+ var args = jasmine.util.argsToArray(arguments);
+ spyObj.mostRecentCall.object = this;
+ spyObj.mostRecentCall.args = args;
+ spyObj.argsForCall.push(args);
+ spyObj.calls.push({object: this, args: args});
+ return spyObj.plan.apply(this, arguments);
+ };
+
+ var spy = new jasmine.Spy(name);
+
+ for (var prop in spy) {
+ spyObj[prop] = spy[prop];
+ }
+
+ spyObj.reset();
+
+ return spyObj;
+};
+
+/**
+ * Determines whether an object is a spy.
+ *
+ * @param {jasmine.Spy|Object} putativeSpy
+ * @returns {Boolean}
+ */
+jasmine.isSpy = function(putativeSpy) {
+ return putativeSpy && putativeSpy.isSpy;
+};
+
+/**
+ * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something
+ * large in one call.
+ *
+ * @param {String} baseName name of spy class
+ * @param {Array} methodNames array of names of methods to make spies
+ */
+jasmine.createSpyObj = function(baseName, methodNames) {
+ if (!jasmine.isArray_(methodNames) || methodNames.length === 0) {
+ throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
+ }
+ var obj = {};
+ for (var i = 0; i < methodNames.length; i++) {
+ obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
+ }
+ return obj;
+};
+
+/**
+ * All parameters are pretty-printed and concatenated together, then written to the current spec's output.
+ *
+ * Be careful not to leave calls to jasmine.log in production code.
+ */
+jasmine.log = function() {
+ var spec = jasmine.getEnv().currentSpec;
+ spec.log.apply(spec, arguments);
+};
+
+/**
+ * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy.
+ *
+ * @example
+ * // spy example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
+ *
+ * @see jasmine.createSpy
+ * @param obj
+ * @param methodName
+ * @returns a Jasmine spy that can be chained with all spy methods
+ */
+var spyOn = function(obj, methodName) {
+ return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
+};
+if (isCommonJS) exports.spyOn = spyOn;
+
+/**
+ * Creates a Jasmine spec that will be added to the current suite.
+ *
+ * // TODO: pending tests
+ *
+ * @example
+ * it('should be true', function() {
+ * expect(true).toEqual(true);
+ * });
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var it = function(desc, func) {
+ return jasmine.getEnv().it(desc, func);
+};
+if (isCommonJS) exports.it = it;
+
+/**
+ * Creates a disabled Jasmine spec.
+ *
+ * A convenience method that allows existing specs to be disabled temporarily during development.
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var xit = function(desc, func) {
+ return jasmine.getEnv().xit(desc, func);
+};
+if (isCommonJS) exports.xit = xit;
+
+/**
+ * Starts a chain for a Jasmine expectation.
+ *
+ * It is passed an Object that is the actual value and should chain to one of the many
+ * jasmine.Matchers functions.
+ *
+ * @param {Object} actual Actual value to test against and expected value
+ */
+var expect = function(actual) {
+ return jasmine.getEnv().currentSpec.expect(actual);
+};
+if (isCommonJS) exports.expect = expect;
+
+/**
+ * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs.
+ *
+ * @param {Function} func Function that defines part of a jasmine spec.
+ */
+var runs = function(func) {
+ jasmine.getEnv().currentSpec.runs(func);
+};
+if (isCommonJS) exports.runs = runs;
+
+/**
+ * Waits a fixed time period before moving to the next block.
+ *
+ * @deprecated Use waitsFor() instead
+ * @param {Number} timeout milliseconds to wait
+ */
+var waits = function(timeout) {
+ jasmine.getEnv().currentSpec.waits(timeout);
+};
+if (isCommonJS) exports.waits = waits;
+
+/**
+ * Waits for the latchFunction to return true before proceeding to the next block.
+ *
+ * @param {Function} latchFunction
+ * @param {String} optional_timeoutMessage
+ * @param {Number} optional_timeout
+ */
+var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
+ jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
+};
+if (isCommonJS) exports.waitsFor = waitsFor;
+
+/**
+ * A function that is called before each spec in a suite.
+ *
+ * Used for spec setup, including validating assumptions.
+ *
+ * @param {Function} beforeEachFunction
+ */
+var beforeEach = function(beforeEachFunction) {
+ jasmine.getEnv().beforeEach(beforeEachFunction);
+};
+if (isCommonJS) exports.beforeEach = beforeEach;
+
+/**
+ * A function that is called after each spec in a suite.
+ *
+ * Used for restoring any state that is hijacked during spec execution.
+ *
+ * @param {Function} afterEachFunction
+ */
+var afterEach = function(afterEachFunction) {
+ jasmine.getEnv().afterEach(afterEachFunction);
+};
+if (isCommonJS) exports.afterEach = afterEach;
+
+/**
+ * Defines a suite of specifications.
+ *
+ * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
+ * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
+ * of setup in some tests.
+ *
+ * @example
+ * // TODO: a simple suite
+ *
+ * // TODO: a simple suite with a nested describe block
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var describe = function(description, specDefinitions) {
+ return jasmine.getEnv().describe(description, specDefinitions);
+};
+if (isCommonJS) exports.describe = describe;
+
+/**
+ * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development.
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var xdescribe = function(description, specDefinitions) {
+ return jasmine.getEnv().xdescribe(description, specDefinitions);
+};
+if (isCommonJS) exports.xdescribe = xdescribe;
+
+
+// Provide the XMLHttpRequest class for IE 5.x-6.x:
+jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
+ function tryIt(f) {
+ try {
+ return f();
+ } catch(e) {
+ }
+ return null;
+ }
+
+ var xhr = tryIt(function() {
+ return new ActiveXObject("Msxml2.XMLHTTP.6.0");
+ }) ||
+ tryIt(function() {
+ return new ActiveXObject("Msxml2.XMLHTTP.3.0");
+ }) ||
+ tryIt(function() {
+ return new ActiveXObject("Msxml2.XMLHTTP");
+ }) ||
+ tryIt(function() {
+ return new ActiveXObject("Microsoft.XMLHTTP");
+ });
+
+ if (!xhr) throw new Error("This browser does not support XMLHttpRequest.");
+
+ return xhr;
+} : XMLHttpRequest;
+/**
+ * @namespace
+ */
+jasmine.util = {};
+
+/**
+ * Declare that a child class inherit it's prototype from the parent class.
+ *
+ * @private
+ * @param {Function} childClass
+ * @param {Function} parentClass
+ */
+jasmine.util.inherit = function(childClass, parentClass) {
+ /**
+ * @private
+ */
+ var subclass = function() {
+ };
+ subclass.prototype = parentClass.prototype;
+ childClass.prototype = new subclass();
+};
+
+jasmine.util.formatException = function(e) {
+ var lineNumber;
+ if (e.line) {
+ lineNumber = e.line;
+ }
+ else if (e.lineNumber) {
+ lineNumber = e.lineNumber;
+ }
+
+ var file;
+
+ if (e.sourceURL) {
+ file = e.sourceURL;
+ }
+ else if (e.fileName) {
+ file = e.fileName;
+ }
+
+ var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
+
+ if (file && lineNumber) {
+ message += ' in ' + file + ' (line ' + lineNumber + ')';
+ }
+
+ return message;
+};
+
+jasmine.util.htmlEscape = function(str) {
+ if (!str) return str;
+ return str.replace(/&/g, '&')
+ .replace(//g, '>');
+};
+
+jasmine.util.argsToArray = function(args) {
+ var arrayOfArgs = [];
+ for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
+ return arrayOfArgs;
+};
+
+jasmine.util.extend = function(destination, source) {
+ for (var property in source) destination[property] = source[property];
+ return destination;
+};
+
+/**
+ * Environment for Jasmine
+ *
+ * @constructor
+ */
+jasmine.Env = function() {
+ this.currentSpec = null;
+ this.currentSuite = null;
+ this.currentRunner_ = new jasmine.Runner(this);
+
+ this.reporter = new jasmine.MultiReporter();
+
+ this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
+ this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
+ this.lastUpdate = 0;
+ this.specFilter = function() {
+ return true;
+ };
+
+ this.nextSpecId_ = 0;
+ this.nextSuiteId_ = 0;
+ this.equalityTesters_ = [];
+
+ // wrap matchers
+ this.matchersClass = function() {
+ jasmine.Matchers.apply(this, arguments);
+ };
+ jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
+
+ jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
+};
+
+
+jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
+jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
+jasmine.Env.prototype.setInterval = jasmine.setInterval;
+jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
+
+/**
+ * @returns an object containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.version = function () {
+ if (jasmine.version_) {
+ return jasmine.version_;
+ } else {
+ throw new Error('Version not set');
+ }
+};
+
+/**
+ * @returns string containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.versionString = function() {
+ if (!jasmine.version_) {
+ return "version unknown";
+ }
+
+ var version = this.version();
+ var versionString = version.major + "." + version.minor + "." + version.build;
+ if (version.release_candidate) {
+ versionString += ".rc" + version.release_candidate
+ }
+ versionString += " revision " + version.revision;
+ return versionString;
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSpecId = function () {
+ return this.nextSpecId_++;
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSuiteId = function () {
+ return this.nextSuiteId_++;
+};
+
+/**
+ * Register a reporter to receive status updates from Jasmine.
+ * @param {jasmine.Reporter} reporter An object which will receive status updates.
+ */
+jasmine.Env.prototype.addReporter = function(reporter) {
+ this.reporter.addReporter(reporter);
+};
+
+jasmine.Env.prototype.execute = function() {
+ this.currentRunner_.execute();
+};
+
+jasmine.Env.prototype.describe = function(description, specDefinitions) {
+ var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
+
+ var parentSuite = this.currentSuite;
+ if (parentSuite) {
+ parentSuite.add(suite);
+ } else {
+ this.currentRunner_.add(suite);
+ }
+
+ this.currentSuite = suite;
+
+ var declarationError = null;
+ try {
+ specDefinitions.call(suite);
+ } catch(e) {
+ declarationError = e;
+ }
+
+ if (declarationError) {
+ this.it("encountered a declaration exception", function() {
+ throw declarationError;
+ });
+ }
+
+ this.currentSuite = parentSuite;
+
+ return suite;
+};
+
+jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
+ if (this.currentSuite) {
+ this.currentSuite.beforeEach(beforeEachFunction);
+ } else {
+ this.currentRunner_.beforeEach(beforeEachFunction);
+ }
+};
+
+jasmine.Env.prototype.currentRunner = function () {
+ return this.currentRunner_;
+};
+
+jasmine.Env.prototype.afterEach = function(afterEachFunction) {
+ if (this.currentSuite) {
+ this.currentSuite.afterEach(afterEachFunction);
+ } else {
+ this.currentRunner_.afterEach(afterEachFunction);
+ }
+
+};
+
+jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
+ return {
+ execute: function() {
+ }
+ };
+};
+
+jasmine.Env.prototype.it = function(description, func) {
+ var spec = new jasmine.Spec(this, this.currentSuite, description);
+ this.currentSuite.add(spec);
+ this.currentSpec = spec;
+
+ if (func) {
+ spec.runs(func);
+ }
+
+ return spec;
+};
+
+jasmine.Env.prototype.xit = function(desc, func) {
+ return {
+ id: this.nextSpecId(),
+ runs: function() {
+ }
+ };
+};
+
+jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
+ if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
+ return true;
+ }
+
+ a.__Jasmine_been_here_before__ = b;
+ b.__Jasmine_been_here_before__ = a;
+
+ var hasKey = function(obj, keyName) {
+ return obj !== null && obj[keyName] !== jasmine.undefined;
+ };
+
+ for (var property in b) {
+ if (!hasKey(a, property) && hasKey(b, property)) {
+ mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
+ }
+ }
+ for (property in a) {
+ if (!hasKey(b, property) && hasKey(a, property)) {
+ mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
+ }
+ }
+ for (property in b) {
+ if (property == '__Jasmine_been_here_before__') continue;
+ if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
+ mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
+ }
+ }
+
+ if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
+ mismatchValues.push("arrays were not the same length");
+ }
+
+ delete a.__Jasmine_been_here_before__;
+ delete b.__Jasmine_been_here_before__;
+ return (mismatchKeys.length === 0 && mismatchValues.length === 0);
+};
+
+jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
+ mismatchKeys = mismatchKeys || [];
+ mismatchValues = mismatchValues || [];
+
+ for (var i = 0; i < this.equalityTesters_.length; i++) {
+ var equalityTester = this.equalityTesters_[i];
+ var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
+ if (result !== jasmine.undefined) return result;
+ }
+
+ if (a === b) return true;
+
+ if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
+ return (a == jasmine.undefined && b == jasmine.undefined);
+ }
+
+ if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
+ return a === b;
+ }
+
+ if (a instanceof Date && b instanceof Date) {
+ return a.getTime() == b.getTime();
+ }
+
+ if (a instanceof jasmine.Matchers.Any) {
+ return a.matches(b);
+ }
+
+ if (b instanceof jasmine.Matchers.Any) {
+ return b.matches(a);
+ }
+
+ if (jasmine.isString_(a) && jasmine.isString_(b)) {
+ return (a == b);
+ }
+
+ if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
+ return (a == b);
+ }
+
+ if (typeof a === "object" && typeof b === "object") {
+ return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
+ }
+
+ //Straight check
+ return (a === b);
+};
+
+jasmine.Env.prototype.contains_ = function(haystack, needle) {
+ if (jasmine.isArray_(haystack)) {
+ for (var i = 0; i < haystack.length; i++) {
+ if (this.equals_(haystack[i], needle)) return true;
+ }
+ return false;
+ }
+ return haystack.indexOf(needle) >= 0;
+};
+
+jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
+ this.equalityTesters_.push(equalityTester);
+};
+/** No-op base class for Jasmine reporters.
+ *
+ * @constructor
+ */
+jasmine.Reporter = function() {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSpecStarting = function(spec) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSpecResults = function(spec) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.log = function(str) {
+};
+
+/**
+ * Blocks are functions with executable code that make up a spec.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {Function} func
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Block = function(env, func, spec) {
+ this.env = env;
+ this.func = func;
+ this.spec = spec;
+};
+
+jasmine.Block.prototype.execute = function(onComplete) {
+ try {
+ this.func.apply(this.spec);
+ } catch (e) {
+ this.spec.fail(e);
+ }
+ onComplete();
+};
+/** JavaScript API reporter.
+ *
+ * @constructor
+ */
+jasmine.JsApiReporter = function() {
+ this.started = false;
+ this.finished = false;
+ this.suites_ = [];
+ this.results_ = {};
+};
+
+jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
+ this.started = true;
+ var suites = runner.topLevelSuites();
+ for (var i = 0; i < suites.length; i++) {
+ var suite = suites[i];
+ this.suites_.push(this.summarize_(suite));
+ }
+};
+
+jasmine.JsApiReporter.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
+ var isSuite = suiteOrSpec instanceof jasmine.Suite;
+ var summary = {
+ id: suiteOrSpec.id,
+ name: suiteOrSpec.description,
+ type: isSuite ? 'suite' : 'spec',
+ children: []
+ };
+
+ if (isSuite) {
+ var children = suiteOrSpec.children();
+ for (var i = 0; i < children.length; i++) {
+ summary.children.push(this.summarize_(children[i]));
+ }
+ }
+ return summary;
+};
+
+jasmine.JsApiReporter.prototype.results = function() {
+ return this.results_;
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
+ return this.results_[specId];
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
+ this.finished = true;
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
+ this.results_[spec.id] = {
+ messages: spec.results().getItems(),
+ result: spec.results().failedCount > 0 ? "failed" : "passed"
+ };
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.log = function(str) {
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
+ var results = {};
+ for (var i = 0; i < specIds.length; i++) {
+ var specId = specIds[i];
+ results[specId] = this.summarizeResult_(this.results_[specId]);
+ }
+ return results;
+};
+
+jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
+ var summaryMessages = [];
+ var messagesLength = result.messages.length;
+ for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
+ var resultMessage = result.messages[messageIndex];
+ summaryMessages.push({
+ text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined,
+ passed: resultMessage.passed ? resultMessage.passed() : true,
+ type: resultMessage.type,
+ message: resultMessage.message,
+ trace: {
+ stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
+ }
+ });
+ }
+
+ return {
+ result : result.result,
+ messages : summaryMessages
+ };
+};
+
+/**
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param actual
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Matchers = function(env, actual, spec, opt_isNot) {
+ this.env = env;
+ this.actual = actual;
+ this.spec = spec;
+ this.isNot = opt_isNot || false;
+ this.reportWasCalled_ = false;
+};
+
+// todo: @deprecated as of Jasmine 0.11, remove soon [xw]
+jasmine.Matchers.pp = function(str) {
+ throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
+};
+
+// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw]
+jasmine.Matchers.prototype.report = function(result, failing_message, details) {
+ throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
+};
+
+jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
+ for (var methodName in prototype) {
+ if (methodName == 'report') continue;
+ var orig = prototype[methodName];
+ matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
+ }
+};
+
+jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
+ return function() {
+ var matcherArgs = jasmine.util.argsToArray(arguments);
+ var result = matcherFunction.apply(this, arguments);
+
+ if (this.isNot) {
+ result = !result;
+ }
+
+ if (this.reportWasCalled_) return result;
+
+ var message;
+ if (!result) {
+ if (this.message) {
+ message = this.message.apply(this, arguments);
+ if (jasmine.isArray_(message)) {
+ message = message[this.isNot ? 1 : 0];
+ }
+ } else {
+ var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
+ message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
+ if (matcherArgs.length > 0) {
+ for (var i = 0; i < matcherArgs.length; i++) {
+ if (i > 0) message += ",";
+ message += " " + jasmine.pp(matcherArgs[i]);
+ }
+ }
+ message += ".";
+ }
+ }
+ var expectationResult = new jasmine.ExpectationResult({
+ matcherName: matcherName,
+ passed: result,
+ expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
+ actual: this.actual,
+ message: message
+ });
+ this.spec.addMatcherResult(expectationResult);
+ return jasmine.undefined;
+ };
+};
+
+
+
+
+/**
+ * toBe: compares the actual to the expected using ===
+ * @param expected
+ */
+jasmine.Matchers.prototype.toBe = function(expected) {
+ return this.actual === expected;
+};
+
+/**
+ * toNotBe: compares the actual to the expected using !==
+ * @param expected
+ * @deprecated as of 1.0. Use not.toBe() instead.
+ */
+jasmine.Matchers.prototype.toNotBe = function(expected) {
+ return this.actual !== expected;
+};
+
+/**
+ * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toEqual = function(expected) {
+ return this.env.equals_(this.actual, expected);
+};
+
+/**
+ * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
+ * @param expected
+ * @deprecated as of 1.0. Use not.toNotEqual() instead.
+ */
+jasmine.Matchers.prototype.toNotEqual = function(expected) {
+ return !this.env.equals_(this.actual, expected);
+};
+
+/**
+ * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes
+ * a pattern or a String.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toMatch = function(expected) {
+ return new RegExp(expected).test(this.actual);
+};
+
+/**
+ * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
+ * @param expected
+ * @deprecated as of 1.0. Use not.toMatch() instead.
+ */
+jasmine.Matchers.prototype.toNotMatch = function(expected) {
+ return !(new RegExp(expected).test(this.actual));
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeDefined = function() {
+ return (this.actual !== jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeUndefined = function() {
+ return (this.actual === jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to null.
+ */
+jasmine.Matchers.prototype.toBeNull = function() {
+ return (this.actual === null);
+};
+
+/**
+ * Matcher that boolean not-nots the actual.
+ */
+jasmine.Matchers.prototype.toBeTruthy = function() {
+ return !!this.actual;
+};
+
+
+/**
+ * Matcher that boolean nots the actual.
+ */
+jasmine.Matchers.prototype.toBeFalsy = function() {
+ return !this.actual;
+};
+
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called.
+ */
+jasmine.Matchers.prototype.toHaveBeenCalled = function() {
+ if (arguments.length > 0) {
+ throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
+ }
+
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy " + this.actual.identity + " to have been called.",
+ "Expected spy " + this.actual.identity + " not to have been called."
+ ];
+ };
+
+ return this.actual.wasCalled;
+};
+
+/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */
+jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled;
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was not called.
+ *
+ * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead
+ */
+jasmine.Matchers.prototype.wasNotCalled = function() {
+ if (arguments.length > 0) {
+ throw new Error('wasNotCalled does not take arguments');
+ }
+
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy " + this.actual.identity + " to not have been called.",
+ "Expected spy " + this.actual.identity + " to have been called."
+ ];
+ };
+
+ return !this.actual.wasCalled;
+};
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
+ *
+ * @example
+ *
+ */
+jasmine.Matchers.prototype.toHaveBeenCalledWith = function() {
+ var expectedArgs = jasmine.util.argsToArray(arguments);
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+ this.message = function() {
+ if (this.actual.callCount === 0) {
+ // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw]
+ return [
+ "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.",
+ "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was."
+ ];
+ } else {
+ return [
+ "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall),
+ "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall)
+ ];
+ }
+ };
+
+ return this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */
+jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith;
+
+/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */
+jasmine.Matchers.prototype.wasNotCalledWith = function() {
+ var expectedArgs = jasmine.util.argsToArray(arguments);
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was",
+ "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was"
+ ];
+ };
+
+ return !this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+/**
+ * Matcher that checks that the expected item is an element in the actual Array.
+ *
+ * @param {Object} expected
+ */
+jasmine.Matchers.prototype.toContain = function(expected) {
+ return this.env.contains_(this.actual, expected);
+};
+
+/**
+ * Matcher that checks that the expected item is NOT an element in the actual Array.
+ *
+ * @param {Object} expected
+ * @deprecated as of 1.0. Use not.toNotContain() instead.
+ */
+jasmine.Matchers.prototype.toNotContain = function(expected) {
+ return !this.env.contains_(this.actual, expected);
+};
+
+jasmine.Matchers.prototype.toBeLessThan = function(expected) {
+ return this.actual < expected;
+};
+
+jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
+ return this.actual > expected;
+};
+
+/**
+ * Matcher that checks that the expected item is equal to the actual item
+ * up to a given level of decimal precision (default 2).
+ *
+ * @param {Number} expected
+ * @param {Number} precision
+ */
+jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) {
+ if (!(precision === 0)) {
+ precision = precision || 2;
+ }
+ var multiplier = Math.pow(10, precision);
+ var actual = Math.round(this.actual * multiplier);
+ expected = Math.round(expected * multiplier);
+ return expected == actual;
+};
+
+/**
+ * Matcher that checks that the expected exception was thrown by the actual.
+ *
+ * @param {String} expected
+ */
+jasmine.Matchers.prototype.toThrow = function(expected) {
+ var result = false;
+ var exception;
+ if (typeof this.actual != 'function') {
+ throw new Error('Actual is not a function');
+ }
+ try {
+ this.actual();
+ } catch (e) {
+ exception = e;
+ }
+ if (exception) {
+ result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
+ }
+
+ var not = this.isNot ? "not " : "";
+
+ this.message = function() {
+ if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
+ return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' ');
+ } else {
+ return "Expected function to throw an exception.";
+ }
+ };
+
+ return result;
+};
+
+jasmine.Matchers.Any = function(expectedClass) {
+ this.expectedClass = expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.matches = function(other) {
+ if (this.expectedClass == String) {
+ return typeof other == 'string' || other instanceof String;
+ }
+
+ if (this.expectedClass == Number) {
+ return typeof other == 'number' || other instanceof Number;
+ }
+
+ if (this.expectedClass == Function) {
+ return typeof other == 'function' || other instanceof Function;
+ }
+
+ if (this.expectedClass == Object) {
+ return typeof other == 'object';
+ }
+
+ return other instanceof this.expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.toString = function() {
+ return 'jasmine.log in production code.
+ */
+jasmine.Spec.prototype.log = function() {
+ return this.results_.log(arguments);
+};
+
+jasmine.Spec.prototype.runs = function (func) {
+ var block = new jasmine.Block(this.env, func, this);
+ this.addToQueue(block);
+ return this;
+};
+
+jasmine.Spec.prototype.addToQueue = function (block) {
+ if (this.queue.isRunning()) {
+ this.queue.insertNext(block);
+ } else {
+ this.queue.add(block);
+ }
+};
+
+/**
+ * @param {jasmine.ExpectationResult} result
+ */
+jasmine.Spec.prototype.addMatcherResult = function(result) {
+ this.results_.addResult(result);
+};
+
+jasmine.Spec.prototype.expect = function(actual) {
+ var positive = new (this.getMatchersClass_())(this.env, actual, this);
+ positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
+ return positive;
+};
+
+/**
+ * Waits a fixed time period before moving to the next block.
+ *
+ * @deprecated Use waitsFor() instead
+ * @param {Number} timeout milliseconds to wait
+ */
+jasmine.Spec.prototype.waits = function(timeout) {
+ var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
+ this.addToQueue(waitsFunc);
+ return this;
+};
+
+/**
+ * Waits for the latchFunction to return true before proceeding to the next block.
+ *
+ * @param {Function} latchFunction
+ * @param {String} optional_timeoutMessage
+ * @param {Number} optional_timeout
+ */
+jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
+ var latchFunction_ = null;
+ var optional_timeoutMessage_ = null;
+ var optional_timeout_ = null;
+
+ for (var i = 0; i < arguments.length; i++) {
+ var arg = arguments[i];
+ switch (typeof arg) {
+ case 'function':
+ latchFunction_ = arg;
+ break;
+ case 'string':
+ optional_timeoutMessage_ = arg;
+ break;
+ case 'number':
+ optional_timeout_ = arg;
+ break;
+ }
+ }
+
+ var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this);
+ this.addToQueue(waitsForFunc);
+ return this;
+};
+
+jasmine.Spec.prototype.fail = function (e) {
+ var expectationResult = new jasmine.ExpectationResult({
+ passed: false,
+ message: e ? jasmine.util.formatException(e) : 'Exception',
+ trace: { stack: e.stack }
+ });
+ this.results_.addResult(expectationResult);
+};
+
+jasmine.Spec.prototype.getMatchersClass_ = function() {
+ return this.matchersClass || this.env.matchersClass;
+};
+
+jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
+ var parent = this.getMatchersClass_();
+ var newMatchersClass = function() {
+ parent.apply(this, arguments);
+ };
+ jasmine.util.inherit(newMatchersClass, parent);
+ jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
+ this.matchersClass = newMatchersClass;
+};
+
+jasmine.Spec.prototype.finishCallback = function() {
+ this.env.reporter.reportSpecResults(this);
+};
+
+jasmine.Spec.prototype.finish = function(onComplete) {
+ this.removeAllSpies();
+ this.finishCallback();
+ if (onComplete) {
+ onComplete();
+ }
+};
+
+jasmine.Spec.prototype.after = function(doAfter) {
+ if (this.queue.isRunning()) {
+ this.queue.add(new jasmine.Block(this.env, doAfter, this));
+ } else {
+ this.afterCallbacks.unshift(doAfter);
+ }
+};
+
+jasmine.Spec.prototype.execute = function(onComplete) {
+ var spec = this;
+ if (!spec.env.specFilter(spec)) {
+ spec.results_.skipped = true;
+ spec.finish(onComplete);
+ return;
+ }
+
+ this.env.reporter.reportSpecStarting(this);
+
+ spec.env.currentSpec = spec;
+
+ spec.addBeforesAndAftersToQueue();
+
+ spec.queue.start(function () {
+ spec.finish(onComplete);
+ });
+};
+
+jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
+ var runner = this.env.currentRunner();
+ var i;
+
+ for (var suite = this.suite; suite; suite = suite.parentSuite) {
+ for (i = 0; i < suite.before_.length; i++) {
+ this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
+ }
+ }
+ for (i = 0; i < runner.before_.length; i++) {
+ this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
+ }
+ for (i = 0; i < this.afterCallbacks.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this));
+ }
+ for (suite = this.suite; suite; suite = suite.parentSuite) {
+ for (i = 0; i < suite.after_.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, suite.after_[i], this));
+ }
+ }
+ for (i = 0; i < runner.after_.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, runner.after_[i], this));
+ }
+};
+
+jasmine.Spec.prototype.explodes = function() {
+ throw 'explodes function should not have been called';
+};
+
+jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
+ if (obj == jasmine.undefined) {
+ throw "spyOn could not find an object to spy upon for " + methodName + "()";
+ }
+
+ if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) {
+ throw methodName + '() method does not exist';
+ }
+
+ if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
+ throw new Error(methodName + ' has already been spied upon');
+ }
+
+ var spyObj = jasmine.createSpy(methodName);
+
+ this.spies_.push(spyObj);
+ spyObj.baseObj = obj;
+ spyObj.methodName = methodName;
+ spyObj.originalValue = obj[methodName];
+
+ obj[methodName] = spyObj;
+
+ return spyObj;
+};
+
+jasmine.Spec.prototype.removeAllSpies = function() {
+ for (var i = 0; i < this.spies_.length; i++) {
+ var spy = this.spies_[i];
+ spy.baseObj[spy.methodName] = spy.originalValue;
+ }
+ this.spies_ = [];
+};
+
+/**
+ * Internal representation of a Jasmine suite.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {String} description
+ * @param {Function} specDefinitions
+ * @param {jasmine.Suite} parentSuite
+ */
+jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
+ var self = this;
+ self.id = env.nextSuiteId ? env.nextSuiteId() : null;
+ self.description = description;
+ self.queue = new jasmine.Queue(env);
+ self.parentSuite = parentSuite;
+ self.env = env;
+ self.before_ = [];
+ self.after_ = [];
+ self.children_ = [];
+ self.suites_ = [];
+ self.specs_ = [];
+};
+
+jasmine.Suite.prototype.getFullName = function() {
+ var fullName = this.description;
+ for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
+ fullName = parentSuite.description + ' ' + fullName;
+ }
+ return fullName;
+};
+
+jasmine.Suite.prototype.finish = function(onComplete) {
+ this.env.reporter.reportSuiteResults(this);
+ this.finished = true;
+ if (typeof(onComplete) == 'function') {
+ onComplete();
+ }
+};
+
+jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) {
+ beforeEachFunction.typeName = 'beforeEach';
+ this.before_.unshift(beforeEachFunction);
+};
+
+jasmine.Suite.prototype.afterEach = function(afterEachFunction) {
+ afterEachFunction.typeName = 'afterEach';
+ this.after_.unshift(afterEachFunction);
+};
+
+jasmine.Suite.prototype.results = function() {
+ return this.queue.results();
+};
+
+jasmine.Suite.prototype.add = function(suiteOrSpec) {
+ this.children_.push(suiteOrSpec);
+ if (suiteOrSpec instanceof jasmine.Suite) {
+ this.suites_.push(suiteOrSpec);
+ this.env.currentRunner().addSuite(suiteOrSpec);
+ } else {
+ this.specs_.push(suiteOrSpec);
+ }
+ this.queue.add(suiteOrSpec);
+};
+
+jasmine.Suite.prototype.specs = function() {
+ return this.specs_;
+};
+
+jasmine.Suite.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.Suite.prototype.children = function() {
+ return this.children_;
+};
+
+jasmine.Suite.prototype.execute = function(onComplete) {
+ var self = this;
+ this.queue.start(function () {
+ self.finish(onComplete);
+ });
+};
+jasmine.WaitsBlock = function(env, timeout, spec) {
+ this.timeout = timeout;
+ jasmine.Block.call(this, env, null, spec);
+};
+
+jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block);
+
+jasmine.WaitsBlock.prototype.execute = function (onComplete) {
+ if (jasmine.VERBOSE) {
+ this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...');
+ }
+ this.env.setTimeout(function () {
+ onComplete();
+ }, this.timeout);
+};
+/**
+ * A block which waits for some condition to become true, with timeout.
+ *
+ * @constructor
+ * @extends jasmine.Block
+ * @param {jasmine.Env} env The Jasmine environment.
+ * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true.
+ * @param {Function} latchFunction A function which returns true when the desired condition has been met.
+ * @param {String} message The message to display if the desired condition hasn't been met within the given time period.
+ * @param {jasmine.Spec} spec The Jasmine spec.
+ */
+jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) {
+ this.timeout = timeout || env.defaultTimeoutInterval;
+ this.latchFunction = latchFunction;
+ this.message = message;
+ this.totalTimeSpentWaitingForLatch = 0;
+ jasmine.Block.call(this, env, null, spec);
+};
+jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
+
+jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10;
+
+jasmine.WaitsForBlock.prototype.execute = function(onComplete) {
+ if (jasmine.VERBOSE) {
+ this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
+ }
+ var latchFunctionResult;
+ try {
+ latchFunctionResult = this.latchFunction.apply(this.spec);
+ } catch (e) {
+ this.spec.fail(e);
+ onComplete();
+ return;
+ }
+
+ if (latchFunctionResult) {
+ onComplete();
+ } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) {
+ var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen');
+ this.spec.fail({
+ name: 'timeout',
+ message: message
+ });
+
+ this.abort = true;
+ onComplete();
+ } else {
+ this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT;
+ var self = this;
+ this.env.setTimeout(function() {
+ self.execute(onComplete);
+ }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT);
+ }
+};
+// Mock setTimeout, clearTimeout
+// Contributed by Pivotal Computer Systems, www.pivotalsf.com
+
+jasmine.FakeTimer = function() {
+ this.reset();
+
+ var self = this;
+ self.setTimeout = function(funcToCall, millis) {
+ self.timeoutsMade++;
+ self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
+ return self.timeoutsMade;
+ };
+
+ self.setInterval = function(funcToCall, millis) {
+ self.timeoutsMade++;
+ self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
+ return self.timeoutsMade;
+ };
+
+ self.clearTimeout = function(timeoutKey) {
+ self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ };
+
+ self.clearInterval = function(timeoutKey) {
+ self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ };
+
+};
+
+jasmine.FakeTimer.prototype.reset = function() {
+ this.timeoutsMade = 0;
+ this.scheduledFunctions = {};
+ this.nowMillis = 0;
+};
+
+jasmine.FakeTimer.prototype.tick = function(millis) {
+ var oldMillis = this.nowMillis;
+ var newMillis = oldMillis + millis;
+ this.runFunctionsWithinRange(oldMillis, newMillis);
+ this.nowMillis = newMillis;
+};
+
+jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
+ var scheduledFunc;
+ var funcsToRun = [];
+ for (var timeoutKey in this.scheduledFunctions) {
+ scheduledFunc = this.scheduledFunctions[timeoutKey];
+ if (scheduledFunc != jasmine.undefined &&
+ scheduledFunc.runAtMillis >= oldMillis &&
+ scheduledFunc.runAtMillis <= nowMillis) {
+ funcsToRun.push(scheduledFunc);
+ this.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ }
+ }
+
+ if (funcsToRun.length > 0) {
+ funcsToRun.sort(function(a, b) {
+ return a.runAtMillis - b.runAtMillis;
+ });
+ for (var i = 0; i < funcsToRun.length; ++i) {
+ try {
+ var funcToRun = funcsToRun[i];
+ this.nowMillis = funcToRun.runAtMillis;
+ funcToRun.funcToCall();
+ if (funcToRun.recurring) {
+ this.scheduleFunction(funcToRun.timeoutKey,
+ funcToRun.funcToCall,
+ funcToRun.millis,
+ true);
+ }
+ } catch(e) {
+ }
+ }
+ this.runFunctionsWithinRange(oldMillis, nowMillis);
+ }
+};
+
+jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
+ this.scheduledFunctions[timeoutKey] = {
+ runAtMillis: this.nowMillis + millis,
+ funcToCall: funcToCall,
+ recurring: recurring,
+ timeoutKey: timeoutKey,
+ millis: millis
+ };
+};
+
+/**
+ * @namespace
+ */
+jasmine.Clock = {
+ defaultFakeTimer: new jasmine.FakeTimer(),
+
+ reset: function() {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.defaultFakeTimer.reset();
+ },
+
+ tick: function(millis) {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.defaultFakeTimer.tick(millis);
+ },
+
+ runFunctionsWithinRange: function(oldMillis, nowMillis) {
+ jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
+ },
+
+ scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
+ jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
+ },
+
+ useMock: function() {
+ if (!jasmine.Clock.isInstalled()) {
+ var spec = jasmine.getEnv().currentSpec;
+ spec.after(jasmine.Clock.uninstallMock);
+
+ jasmine.Clock.installMock();
+ }
+ },
+
+ installMock: function() {
+ jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
+ },
+
+ uninstallMock: function() {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.installed = jasmine.Clock.real;
+ },
+
+ real: {
+ setTimeout: jasmine.getGlobal().setTimeout,
+ clearTimeout: jasmine.getGlobal().clearTimeout,
+ setInterval: jasmine.getGlobal().setInterval,
+ clearInterval: jasmine.getGlobal().clearInterval
+ },
+
+ assertInstalled: function() {
+ if (!jasmine.Clock.isInstalled()) {
+ throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
+ }
+ },
+
+ isInstalled: function() {
+ return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer;
+ },
+
+ installed: null
+};
+jasmine.Clock.installed = jasmine.Clock.real;
+
+//else for IE support
+jasmine.getGlobal().setTimeout = function(funcToCall, millis) {
+ if (jasmine.Clock.installed.setTimeout.apply) {
+ return jasmine.Clock.installed.setTimeout.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.setTimeout(funcToCall, millis);
+ }
+};
+
+jasmine.getGlobal().setInterval = function(funcToCall, millis) {
+ if (jasmine.Clock.installed.setInterval.apply) {
+ return jasmine.Clock.installed.setInterval.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.setInterval(funcToCall, millis);
+ }
+};
+
+jasmine.getGlobal().clearTimeout = function(timeoutKey) {
+ if (jasmine.Clock.installed.clearTimeout.apply) {
+ return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.clearTimeout(timeoutKey);
+ }
+};
+
+jasmine.getGlobal().clearInterval = function(timeoutKey) {
+ if (jasmine.Clock.installed.clearTimeout.apply) {
+ return jasmine.Clock.installed.clearInterval.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.clearInterval(timeoutKey);
+ }
+};
+
+jasmine.version_= {
+ "major": 1,
+ "minor": 1,
+ "build": 0,
+ "revision": 1308618948,
+ "release_candidate": 1
+};
diff --git a/resources/flightjs-example-app/components/es5-shim/tests/lib/jasmine_favicon.png b/resources/flightjs-example-app/components/es5-shim/tests/lib/jasmine_favicon.png
new file mode 100644
index 000000000..218f3b437
Binary files /dev/null and b/resources/flightjs-example-app/components/es5-shim/tests/lib/jasmine_favicon.png differ
diff --git a/resources/flightjs-example-app/components/es5-shim/tests/lib/json2.js b/resources/flightjs-example-app/components/es5-shim/tests/lib/json2.js
new file mode 100644
index 000000000..ac5807921
--- /dev/null
+++ b/resources/flightjs-example-app/components/es5-shim/tests/lib/json2.js
@@ -0,0 +1,478 @@
+/*
+ http://www.JSON.org/json2.js
+ 2009-08-17
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ See http://www.JSON.org/js.html
+
+ This file creates a global JSON object containing two methods: stringify
+ and parse.
+
+ JSON.stringify(value, replacer, space)
+ value any JavaScript value, usually an object or array.
+
+ replacer an optional parameter that determines how object
+ values are stringified for objects. It can be a
+ function or an array of strings.
+
+ space an optional parameter that specifies the indentation
+ of nested structures. If it is omitted, the text will
+ be packed without extra whitespace. If it is a number,
+ it will specify the number of spaces to indent at each
+ level. If it is a string (such as '\t' or ' '),
+ it contains the characters used to indent at each level.
+
+ This method produces a JSON text from a JavaScript value.
+
+ When an object value is found, if the object contains a toJSON
+ method, its toJSON method will be called and the result will be
+ stringified. A toJSON method does not serialize: it returns the
+ value represented by the name/value pair that should be serialized,
+ or undefined if nothing should be serialized. The toJSON method
+ will be passed the key associated with the value, and this will be
+ bound to the value
+
+ For example, this would serialize Dates as ISO strings.
+
+ Date.prototype.toJSON = function (key) {
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ You can provide an optional replacer method. It will be passed the
+ key and value of each member, with this bound to the containing
+ object. The value that is returned from your method will be
+ serialized. If your method returns undefined, then the member will
+ be excluded from the serialization.
+
+ If the replacer parameter is an array of strings, then it will be
+ used to select the members to be serialized. It filters the results
+ such that only members with keys listed in the replacer array are
+ stringified.
+
+ Values that do not have JSON representations, such as undefined or
+ functions, will not be serialized. Such values in objects will be
+ dropped; in arrays they will be replaced with null. You can use
+ a replacer function to replace those with JSON values.
+ JSON.stringify(undefined) returns undefined.
+
+ The optional space parameter produces a stringification of the
+ value that is filled with line breaks and indentation to make it
+ easier to read.
+
+ If the space parameter is a non-empty string, then that string will
+ be used for indentation. If the space parameter is a number, then
+ the indentation will be that many spaces.
+
+ Example:
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
+ // text is '["e",{"pluribus":"unum"}]'
+
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+ text = JSON.stringify([new Date()], function (key, value) {
+ return this[key] instanceof Date ?
+ 'Date(' + this[key] + ')' : value;
+ });
+ // text is '["Date(---current time---)"]'
+
+
+ JSON.parse(text, reviver)
+ This method parses a JSON text to produce an object or array.
+ It can throw a SyntaxError exception.
+
+ The optional reviver parameter is a function that can filter and
+ transform the results. It receives each of the keys and values,
+ and its return value is used instead of the original value.
+ If it returns what it received, then the structure is not modified.
+ If it returns undefined then the member is deleted.
+
+ Example:
+
+ // Parse the text. Values that look like ISO date strings will
+ // be converted to Date objects.
+
+ myData = JSON.parse(text, function (key, value) {
+ var a;
+ if (typeof value === 'string') {
+ a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+ if (a) {
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+ +a[5], +a[6]));
+ }
+ }
+ return value;
+ });
+
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+ var d;
+ if (typeof value === 'string' &&
+ value.slice(0, 5) === 'Date(' &&
+ value.slice(-1) === ')') {
+ d = new Date(value.slice(5, -1));
+ if (d) {
+ return d;
+ }
+ }
+ return value;
+ });
+
+
+ This is a reference implementation. You are free to copy, modify, or
+ redistribute.
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+ NOT CONTROL.
+*/
+
+/*jslint evil: true */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
+ test, toJSON, toString, valueOf
+*/
+
+"use strict";
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (!this.JSON) {
+ this.JSON = {};
+}
+
+(function () {
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ if (typeof Date.prototype.toJSON !== 'function') {
+
+ Date.prototype.toJSON = function (key) {
+
+ return isFinite(this.valueOf()) ?
+ this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z' : null;
+ };
+
+ String.prototype.toJSON =
+ Number.prototype.toJSON =
+ Boolean.prototype.toJSON = function (key) {
+ return this.valueOf();
+ };
+ }
+
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ gap,
+ indent,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ },
+ rep;
+
+
+ function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+ escapable.lastIndex = 0;
+ return escapable.test(string) ?
+ '"' + string.replace(escapable, function (a) {
+ var c = meta[a];
+ return typeof c === 'string' ? c :
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ }) + '"' :
+ '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+// Produce a string from holder[key].
+
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+ if (value && typeof value === 'object' &&
+ typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+// What happens next depends on the value's type.
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+ return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+ case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+ if (!value) {
+ return 'null';
+ }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+ gap += indent;
+ partial = [];
+
+// Is the value an array?
+
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+ v = partial.length === 0 ? '[]' :
+ gap ? '[\n' + gap +
+ partial.join(',\n' + gap) + '\n' +
+ mind + ']' :
+ '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ k = rep[i];
+ if (typeof k === 'string') {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+ v = partial.length === 0 ? '{}' :
+ gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+ mind + '}' : '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+ if (typeof JSON.stringify !== 'function') {
+ JSON.stringify = function (value, replacer, space) {
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+ var i;
+ gap = '';
+ indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+ return str('', {'': value});
+ };
+ }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+ if (typeof JSON.parse !== 'function') {
+ JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+ var j;
+
+ function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+ var k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+ cx.lastIndex = 0;
+ if (cx.test(text)) {
+ text = text.replace(cx, function (a) {
+ return '\\u' +
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ if (/^[\],:{}\s]*$/.
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+ j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+ return typeof reviver === 'function' ?
+ walk({'': j}, '') : j;
+ }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+ throw new SyntaxError('JSON.parse');
+ };
+ }
+}());
diff --git a/resources/flightjs-example-app/components/es5-shim/tests/spec/s-array.js b/resources/flightjs-example-app/components/es5-shim/tests/spec/s-array.js
new file mode 100644
index 000000000..6c353d85b
--- /dev/null
+++ b/resources/flightjs-example-app/components/es5-shim/tests/spec/s-array.js
@@ -0,0 +1,1070 @@
+describe('Array', function() {
+ var testSubject;
+ beforeEach(function() {
+ testSubject = [2, 3, undefined, true, 'hej', null, false, 0];
+ delete testSubject[1];
+ });
+ function createArrayLikeFromArray(arr) {
+ var o = {};
+ Array.prototype.forEach.call(arr, function(e, i) {
+ o[i]=e;
+ });
+ o.length = arr.length;
+ return o;
+ };
+
+ describe('forEach', function() {
+ "use strict";
+ var expected, actual;
+
+ beforeEach(function() {
+ expected = {0:2, 2: undefined, 3:true, 4: 'hej', 5:null, 6:false, 7:0 };
+ actual = {};
+ });
+ it('should pass the right parameters', function() {
+ var callback = jasmine.createSpy('callback'),
+ array = ['1'];
+ array.forEach(callback);
+ expect(callback).toHaveBeenCalledWith('1', 0, array);
+ });
+ it('should not affect elements added to the array after it has begun', function() {
+ var arr = [1,2,3],
+ i = 0;
+ arr.forEach(function(a) {
+ i++;
+ arr.push(a+3);
+ });
+ expect(arr).toEqual([1,2,3,4,5,6]);
+ expect(i).toBe(3);
+ });
+
+ it('should set the right context when given none', function() {
+ var context;
+ [1].forEach(function() {context = this;});
+ expect(context).toBe(function() {return this}.call());
+ });
+ it('should iterate all', function() {
+ testSubject.forEach(function(obj, index) {
+ actual[index] = obj;
+ });
+ expect(actual).toExactlyMatch(expected);
+ });
+ it('should iterate all using a context', function() {
+ var o = { a: actual };
+
+ testSubject.forEach(function(obj, index) {
+ this.a[index] = obj;
+ }, o);
+ expect(actual).toExactlyMatch(expected);
+ });
+
+ it('should iterate all in an array-like object', function() {
+ var ts = createArrayLikeFromArray(testSubject);
+ Array.prototype.forEach.call(ts, function(obj, index) {
+ actual[index] = obj;
+ });
+ expect(actual).toExactlyMatch(expected);
+ });
+ it('should iterate all in an array-like object using a context', function() {
+ var ts = createArrayLikeFromArray(testSubject),
+ o = { a: actual };
+
+ Array.prototype.forEach.call(ts, function(obj, index) {
+ this.a[index] = obj;
+ }, o);
+ expect(actual).toExactlyMatch(expected);
+ });
+ });
+ describe('some', function() {
+ var actual, expected, numberOfRuns;
+
+ beforeEach(function() {
+ expected = {0:2, 2: undefined, 3:true };
+ actual = {};
+ numberOfRuns = 0;
+ });
+
+ it('should pass the correct values along to the callback', function() {
+ var callback = jasmine.createSpy('callback');
+ var array = ['1'];
+ array.some(callback);
+ expect(callback).toHaveBeenCalledWith('1', 0, array);
+ });
+ it('should not affect elements added to the array after it has begun', function() {
+ var arr = [1,2,3],
+ i = 0;
+ arr.some(function(a) {
+ i++;
+ arr.push(a+3);
+ return i > 3;
+ });
+ expect(arr).toEqual([1,2,3,4,5,6]);
+ expect(i).toBe(3);
+ });
+ it('should set the right context when given none', function() {
+ var context;
+ [1].some(function() {context = this;});
+ expect(context).toBe(function() {return this}.call());
+ });
+
+ it('should return false if it runs to the end', function() {
+ actual = testSubject.some(function() {});
+ expect(actual).toBeFalsy();
+ });
+ it('should return true if it is stopped somewhere', function() {
+ actual = testSubject.some(function() { return true; });
+ expect(actual).toBeTruthy();
+ });
+ it('should return false if there are no elements', function() {
+ actual = [].some(function() { return true; });
+ expect(actual).toBeFalsy();
+ });
+
+ it('should stop after 3 elements', function() {
+ testSubject.some(function(obj, index) {
+ actual[index] = obj;
+ numberOfRuns += 1;
+ if(numberOfRuns == 3) {
+ return true;
+ }
+ return false;
+ });
+ expect(actual).toExactlyMatch(expected);
+ });
+ it('should stop after 3 elements using a context', function() {
+ var o = { a: actual };
+ testSubject.some(function(obj, index) {
+ this.a[index] = obj;
+ numberOfRuns += 1;
+ if(numberOfRuns == 3) {
+ return true;
+ }
+ return false;
+ }, o);
+ expect(actual).toExactlyMatch(expected);
+ });
+
+ it('should stop after 3 elements in an array-like object', function() {
+ var ts = createArrayLikeFromArray(testSubject);
+ Array.prototype.some.call(ts, function(obj, index) {
+ actual[index] = obj;
+ numberOfRuns += 1;
+ if(numberOfRuns == 3) {
+ return true;
+ }
+ return false;
+ });
+ expect(actual).toExactlyMatch(expected);
+ });
+ it('should stop after 3 elements in an array-like object using a context', function() {
+ var ts = createArrayLikeFromArray(testSubject);
+ var o = { a: actual };
+ Array.prototype.some.call(ts, function(obj, index) {
+ this.a[index] = obj;
+ numberOfRuns += 1;
+ if(numberOfRuns == 3) {
+ return true;
+ }
+ return false;
+ }, o);
+ expect(actual).toExactlyMatch(expected);
+ });
+ });
+ describe('every', function() {
+ var actual, expected, numberOfRuns;
+
+ beforeEach(function() {
+ expected = {0:2, 2: undefined, 3:true };
+ actual = {};
+ numberOfRuns = 0;
+ });
+
+ it('should pass the correct values along to the callback', function() {
+ var callback = jasmine.createSpy('callback');
+ var array = ['1'];
+ array.every(callback);
+ expect(callback).toHaveBeenCalledWith('1', 0, array);
+ });
+ it('should not affect elements added to the array after it has begun', function() {
+ var arr = [1,2,3],
+ i = 0;
+ arr.every(function(a) {
+ i++;
+ arr.push(a+3);
+ return i <= 3;
+ });
+ expect(arr).toEqual([1,2,3,4,5,6]);
+ expect(i).toBe(3);
+ });
+ it('should set the right context when given none', function() {
+ var context;
+ [1].every(function() {context = this;});
+ expect(context).toBe(function() {return this}.call());
+ });
+
+ it('should return true if the array is empty', function() {
+ actual = [].every(function() { return true; });
+ expect(actual).toBeTruthy();
+
+ actual = [].every(function() { return false; });
+ expect(actual).toBeTruthy();
+ });
+ it('should return true if it runs to the end', function() {
+ actual = [1,2,3].every(function() { return true; });
+ expect(actual).toBeTruthy();
+ });
+ it('should return false if it is stopped before the end', function() {
+ actual = [1,2,3].every(function() { return false; });
+ expect(actual).toBeFalsy();
+ });
+
+ it('should return after 3 elements', function() {
+ testSubject.every(function(obj, index) {
+ actual[index] = obj;
+ numberOfRuns += 1;
+ if(numberOfRuns == 3) {
+ return false;
+ }
+ return true;
+ });
+ expect(actual).toExactlyMatch(expected);
+ });
+ it('should stop after 3 elements using a context', function() {
+ var o = { a: actual };
+ testSubject.every(function(obj, index) {
+ this.a[index] = obj;
+ numberOfRuns += 1;
+ if(numberOfRuns == 3) {
+ return false;
+ }
+ return true;
+ }, o);
+ expect(actual).toExactlyMatch(expected);
+ });
+
+ it('should stop after 3 elements in an array-like object', function() {
+ var ts = createArrayLikeFromArray(testSubject);
+ Array.prototype.every.call(ts, function(obj, index) {
+ actual[index] = obj;
+ numberOfRuns += 1;
+ if(numberOfRuns == 3) {
+ return false;
+ }
+ return true;
+ });
+ expect(actual).toExactlyMatch(expected);
+ });
+ it('should stop after 3 elements in an array-like object using a context', function() {
+ var ts = createArrayLikeFromArray(testSubject);
+ var o = { a: actual };
+ Array.prototype.every.call(ts, function(obj, index) {
+ this.a[index] = obj;
+ numberOfRuns += 1;
+ if(numberOfRuns == 3) {
+ return false;
+ }
+ return true;
+ }, o);
+ expect(actual).toExactlyMatch(expected);
+ });
+ });
+
+ describe('indexOf', function() {
+ "use strict";
+ var actual, expected, testSubject;
+
+ beforeEach(function() {
+ testSubject = [2, 3, undefined, true, 'hej', null, 2, false, 0];
+ delete testSubject[1];
+
+ });
+
+ it('should find the element', function() {
+ expected = 4;
+ actual = testSubject.indexOf('hej');
+ expect(actual).toEqual(expected);
+ });
+ it('should not find the element', function() {
+ expected = -1;
+ actual = testSubject.indexOf('mus');
+ expect(actual).toEqual(expected);
+ });
+ it('should find undefined as well', function() {
+ expected = -1;
+ actual = testSubject.indexOf(undefined);
+ expect(actual).not.toEqual(expected);
+ });
+ it('should skip unset indexes', function() {
+ expected = 2;
+ actual = testSubject.indexOf(undefined);
+ expect(actual).toEqual(expected);
+ });
+ it('should use a strict test', function() {
+ actual = testSubject.indexOf(null);
+ expect(actual).toEqual(5);
+
+ actual = testSubject.indexOf('2');
+ expect(actual).toEqual(-1);
+ });
+ it('should skip the first if fromIndex is set', function() {
+ expect(testSubject.indexOf(2, 2)).toEqual(6);
+ expect(testSubject.indexOf(2, 0)).toEqual(0);
+ expect(testSubject.indexOf(2, 6)).toEqual(6);
+ });
+ it('should work with negative fromIndex', function() {
+ expect(testSubject.indexOf(2, -3)).toEqual(6);
+ expect(testSubject.indexOf(2, -9)).toEqual(0);
+ });
+ it('should work with fromIndex being greater than the length', function() {
+ expect(testSubject.indexOf('hej', 20)).toEqual(-1);
+ });
+ it('should work with fromIndex being negative and greater than the length', function() {
+ expect(testSubject.indexOf('hej', -20)).toEqual(4);
+ });
+
+ describe('Array-like', function ArrayLike() {
+ var indexOf = Array.prototype.indexOf,
+ testAL;
+ beforeEach(function beforeEach() {
+ testAL = {};
+ testSubject = [2, 3, undefined, true, 'hej', null, 2, false, 0];
+ testSubject.forEach(function (o,i) {
+ testAL[i] = o;
+ });
+ testAL.length = testSubject.length;
+ });
+ it('should find the element (array-like)', function() {
+ expected = 4;
+ actual = indexOf.call(testAL, 'hej');
+ expect(actual).toEqual(expected);
+ });
+ it('should not find the element (array-like)', function() {
+ expected = -1;
+ actual = indexOf.call(testAL, 'mus');
+ expect(actual).toEqual(expected);
+ });
+ it('should find undefined as well (array-like)', function() {
+ expected = -1;
+ actual = indexOf.call(testAL, undefined);
+ expect(actual).not.toEqual(expected);
+ });
+ it('should skip unset indexes (array-like)', function() {
+ expected = 2;
+ actual = indexOf.call(testAL, undefined);
+ expect(actual).toEqual(expected);
+ });
+ it('should use a strict test (array-like)', function() {
+ actual = Array.prototype.indexOf.call(testAL, null);
+ expect(actual).toEqual(5);
+
+ actual = Array.prototype.indexOf.call(testAL, '2');
+ expect(actual).toEqual(-1);
+ });
+ it('should skip the first if fromIndex is set (array-like)', function() {
+ expect(indexOf.call(testAL, 2, 2)).toEqual(6);
+ expect(indexOf.call(testAL, 2, 0)).toEqual(0);
+ expect(indexOf.call(testAL, 2, 6)).toEqual(6);
+ });
+ it('should work with negative fromIndex (array-like)', function() {
+ expect(indexOf.call(testAL, 2, -3)).toEqual(6);
+ expect(indexOf.call(testAL, 2, -9)).toEqual(0);
+ });
+ it('should work with fromIndex being greater than the length (array-like)', function() {
+ expect(indexOf.call(testAL, 'hej', 20)).toEqual(-1);
+ });
+ it('should work with fromIndex being negative and greater than the length (array-like)', function() {
+ expect(indexOf.call(testAL, 'hej', -20)).toEqual(4);
+ });
+ });
+ });
+ describe('lastIndexOf', function() {
+ "use strict";
+ var actual, expected, testSubject, testAL;
+
+ beforeEach(function() {
+ testSubject = [2, 3, undefined, true, 'hej', null, 2, 3, false, 0];
+ delete testSubject[1];
+ delete testSubject[7];
+ });
+ describe('Array', function() {
+ it('should find the element', function() {
+ expected = 4;
+ actual = testSubject.lastIndexOf('hej');
+ expect(actual).toEqual(expected);
+ });
+ it('should not find the element', function() {
+ expected = -1;
+ actual = testSubject.lastIndexOf('mus');
+ expect(actual).toEqual(expected);
+ });
+ it('should find undefined as well', function() {
+ expected = -1;
+ actual = testSubject.lastIndexOf(undefined);
+ expect(actual).not.toEqual(expected);
+ });
+ it('should skip unset indexes', function() {
+ expected = 2;
+ actual = testSubject.lastIndexOf(undefined);
+ expect(actual).toEqual(expected);
+ });
+ it('should use a strict test', function() {
+ actual = testSubject.lastIndexOf(null);
+ expect(actual).toEqual(5);
+
+ actual = testSubject.lastIndexOf('2');
+ expect(actual).toEqual(-1);
+ });
+ it('should skip the first if fromIndex is set', function() {
+ expect(testSubject.lastIndexOf(2, 2)).toEqual(0);
+ expect(testSubject.lastIndexOf(2, 0)).toEqual(0);
+ expect(testSubject.lastIndexOf(2, 6)).toEqual(6);
+ });
+ it('should work with negative fromIndex', function() {
+ expect(testSubject.lastIndexOf(2, -3)).toEqual(6);
+ expect(testSubject.lastIndexOf(2, -9)).toEqual(0);
+ });
+ it('should work with fromIndex being greater than the length', function() {
+ expect(testSubject.lastIndexOf(2, 20)).toEqual(6);
+ });
+ it('should work with fromIndex being negative and greater than the length', function() {
+ expect(testSubject.lastIndexOf(2, -20)).toEqual(-1);
+ });
+ });
+
+ describe('Array like', function() {
+ var lastIndexOf = Array.prototype.lastIndexOf,
+ testAL;
+ beforeEach(function() {
+ testAL = {};
+ testSubject.forEach(function (o,i) {
+ testAL[i] = o;
+ });
+ testAL.length = testSubject.length;
+ });
+ it('should find the element (array-like)', function() {
+ expected = 4;
+ actual = lastIndexOf.call(testAL, 'hej');
+ expect(actual).toEqual(expected);
+ });
+ it('should not find the element (array-like)', function() {
+ expected = -1;
+ actual = lastIndexOf.call(testAL, 'mus');
+ expect(actual).toEqual(expected);
+ });
+ it('should find undefined as well (array-like)', function() {
+ expected = -1;
+ actual = lastIndexOf.call(testAL, undefined);
+ expect(actual).not.toEqual(expected);
+ });
+ it('should skip unset indexes (array-like)', function() {
+ expected = 2;
+ actual = lastIndexOf.call(testAL, undefined);
+ expect(actual).toEqual(expected);
+ });
+ it('should use a strict test (array-like)', function() {
+ actual = lastIndexOf.call(testAL, null);
+ expect(actual).toEqual(5);
+
+ actual = lastIndexOf.call(testAL, '2');
+ expect(actual).toEqual(-1);
+ });
+ it('should skip the first if fromIndex is set', function() {
+ expect(lastIndexOf.call(testAL, 2, 2)).toEqual(0);
+ expect(lastIndexOf.call(testAL, 2, 0)).toEqual(0);
+ expect(lastIndexOf.call(testAL, 2, 6)).toEqual(6);
+ });
+ it('should work with negative fromIndex', function() {
+ expect(lastIndexOf.call(testAL, 2, -3)).toEqual(6);
+ expect(lastIndexOf.call(testAL, 2, -9)).toEqual(0);
+ });
+ it('should work with fromIndex being greater than the length', function() {
+ expect(lastIndexOf.call(testAL, 2, 20)).toEqual(6);
+ });
+ it('should work with fromIndex being negative and greater than the length', function() {
+ expect(lastIndexOf.call(testAL, 2, -20)).toEqual(-1);
+ });
+ });
+ });
+
+ describe('filter', function() {
+ var filteredArray,
+ callback = function callback(o, i, arr) {
+ return (
+ i != 3 && i != 5
+ );
+ };
+
+ beforeEach(function() {
+ testSubject = [2, 3, undefined, true, 'hej', 3, null, false, 0];
+ delete testSubject[1];
+ filteredArray = [2, undefined, 'hej', null, false, 0];
+ });
+ describe('Array object', function() {
+
+ it('should call the callback with the proper arguments', function() {
+ var callback = jasmine.createSpy('callback'),
+ arr = ['1'];
+ arr.filter(callback);
+ expect(callback).toHaveBeenCalledWith('1', 0, arr);
+ });
+ it('should not affect elements added to the array after it has begun', function() {
+ var arr = [1,2,3],
+ i = 0;
+ arr.filter(function(a) {
+ i++;
+ if(i <= 4) {
+ arr.push(a+3);
+ }
+ return true;
+ });
+ expect(arr).toEqual([1,2,3,4,5,6]);
+ expect(i).toBe(3);
+ });
+ it('should skip non-set values', function() {
+ var passedValues = {};
+ testSubject = [1,2,3,4];
+ delete testSubject[1];
+ testSubject.filter(function(o, i) {
+ passedValues[i] = o;
+ return true;
+ });
+ expect(passedValues).toExactlyMatch(testSubject);
+ });
+ it('should pass the right context to the filter', function() {
+ var passedValues = {};
+ testSubject = [1,2,3,4];
+ delete testSubject[1];
+ testSubject.filter(function(o, i) {
+ this[i] = o;
+ return true;
+ }, passedValues);
+ expect(passedValues).toExactlyMatch(testSubject);
+ });
+ it('should set the right context when given none', function() {
+ var context;
+ [1].filter(function() {context = this;});
+ expect(context).toBe(function() {return this}.call());
+ });
+ it('should remove only the values for which the callback returns false', function() {
+ var result = testSubject.filter(callback);
+ expect(result).toExactlyMatch(filteredArray);
+ });
+ it('should leave the original array untouched', function() {
+ var copy = testSubject.slice();
+ testSubject.filter(callback);
+ expect(testSubject).toExactlyMatch(copy);
+ });
+ it('should not be affected by same-index mutation', function () {
+ var results = [1, 2, 3]
+ .filter(function (value, index, array) {
+ array[index] = 'a';
+ return true;
+ });
+ expect(results).toEqual([1, 2, 3]);
+ });
+ });
+ describe('Array like', function() {
+ beforeEach(function() {
+ testSubject = createArrayLikeFromArray(testSubject);
+ });
+ it('should call the callback with the proper arguments', function() {
+ var callback = jasmine.createSpy('callback'),
+ arr = createArrayLikeFromArray(['1']);
+ Array.prototype.filter.call(arr, callback);
+ expect(callback).toHaveBeenCalledWith('1', 0, arr);
+ });
+ it('should not affect elements added to the array after it has begun', function() {
+ var arr = createArrayLikeFromArray([1,2,3]),
+ i = 0;
+ Array.prototype.filter.call(arr, function(a) {
+ i++;
+ if(i <= 4) {
+ arr[i+2] = a+3;
+ }
+ return true;
+ });
+ delete arr.length;
+ expect(arr).toExactlyMatch([1,2,3,4,5,6]);
+ expect(i).toBe(3);
+ });
+ it('should skip non-set values', function() {
+ var passedValues = {};
+ testSubject = createArrayLikeFromArray([1,2,3,4]);
+ delete testSubject[1];
+ Array.prototype.filter.call(testSubject, function(o, i) {
+ passedValues[i] = o;
+ return true;
+ });
+ delete testSubject.length;
+ expect(passedValues).toExactlyMatch(testSubject);
+ });
+ it('should set the right context when given none', function() {
+ var context;
+ Array.prototype.filter.call(createArrayLikeFromArray([1]), function() {context = this;}, undefined);
+ expect(context).toBe(function() {return this}.call());
+ });
+ it('should pass the right context to the filter', function() {
+ var passedValues = {};
+ testSubject = createArrayLikeFromArray([1,2,3,4]);
+ delete testSubject[1];
+ Array.prototype.filter.call(testSubject, function(o, i) {
+ this[i] = o;
+ return true;
+ }, passedValues);
+ delete testSubject.length;
+ expect(passedValues).toExactlyMatch(testSubject);
+ });
+ it('should remove only the values for which the callback returns false', function() {
+ var result = Array.prototype.filter.call(testSubject, callback);
+ expect(result).toExactlyMatch(filteredArray);
+ });
+ it('should leave the original array untouched', function() {
+ var copy = createArrayLikeFromArray(testSubject);
+ Array.prototype.filter.call(testSubject, callback);
+ expect(testSubject).toExactlyMatch(copy);
+ });
+ });
+ });
+ describe('map', function() {
+ var callback;
+ beforeEach(function() {
+ var i = 0;
+ callback = function() {
+ return i++;
+ };
+ });
+ describe('Array object', function() {
+ it('should call callback with the right parameters', function() {
+ var callback = jasmine.createSpy('callback'),
+ array = [1];
+ array.map(callback);
+ expect(callback).toHaveBeenCalledWith(1, 0, array);
+ });
+ it('should set the context correctly', function() {
+ var context = {};
+ testSubject.map(function(o,i) {
+ this[i] = o;
+ }, context);
+ expect(context).toExactlyMatch(testSubject);
+ });
+ it('should set the right context when given none', function() {
+ var context;
+ [1].map(function() {context = this;});
+ expect(context).toBe(function() {return this}.call());
+ });
+ it('should not change the array it is called on', function() {
+ var copy = testSubject.slice();
+ testSubject.map(callback);
+ expect(testSubject).toExactlyMatch(copy);
+ });
+ it('should only run for the number of objects in the array when it started', function() {
+ var arr = [1,2,3],
+ i = 0;
+ arr.map(function(o) {
+ arr.push(o+3);
+ i++;
+ return o;
+ });
+ expect(arr).toExactlyMatch([1,2,3,4,5,6]);
+ expect(i).toBe(3);
+ });
+ it('should properly translate the values as according to the callback', function() {
+ var result = testSubject.map(callback),
+ expected = [0,0,1,2,3,4,5,6];
+ delete expected[1];
+ expect(result).toExactlyMatch(expected);
+ });
+ it('should skip non-existing values', function() {
+ var array = [1,2,3,4],
+ i = 0;
+ delete array[2];
+ array.map(function() {
+ i++;
+ });
+ expect(i).toBe(3);
+ });
+ });
+ describe('Array-like', function() {
+ beforeEach(function() {
+ testSubject = createArrayLikeFromArray(testSubject);
+ });
+ it('should call callback with the right parameters', function() {
+ var callback = jasmine.createSpy('callback'),
+ array = createArrayLikeFromArray([1]);
+ Array.prototype.map.call(array, callback);
+ expect(callback).toHaveBeenCalledWith(1, 0, array);
+ });
+ it('should set the context correctly', function() {
+ var context = {};
+ Array.prototype.map.call(testSubject, function(o,i) {
+ this[i] = o;
+ }, context);
+ delete testSubject.length;
+ expect(context).toExactlyMatch(testSubject);
+ });
+ it('should set the right context when given none', function() {
+ var context;
+ Array.prototype.map.call(createArrayLikeFromArray([1]), function() {context = this;});
+ expect(context).toBe(function() {return this}.call());
+ });
+ it('should not change the array it is called on', function() {
+ var copy = createArrayLikeFromArray(testSubject);
+ Array.prototype.map.call(testSubject, callback);
+ expect(testSubject).toExactlyMatch(copy);
+ });
+ it('should only run for the number of objects in the array when it started', function() {
+ var arr = createArrayLikeFromArray([1,2,3]),
+ i = 0;
+ Array.prototype.map.call(arr, function(o) {
+ Array.prototype.push.call(arr, o+3);
+ i++;
+ return o;
+ });
+ delete arr.length;
+ expect(arr).toExactlyMatch([1,2,3,4,5,6]);
+ expect(i).toBe(3);
+ });
+ it('should properly translate the values as according to the callback', function() {
+ var result = Array.prototype.map.call(testSubject, callback),
+ expected = [0,0,1,2,3,4,5,6];
+ delete expected[1];
+ expect(result).toExactlyMatch(expected);
+ });
+ it('should skip non-existing values', function() {
+ var array = createArrayLikeFromArray([1,2,3,4]),
+ i = 0;
+ delete array[2];
+ Array.prototype.map.call(array, function() {
+ i++;
+ });
+ expect(i).toBe(3);
+ });
+ });
+ });
+
+ describe('reduce', function() {
+ beforeEach(function() {
+ testSubject = [1,2,3];
+ });
+
+ describe('Array', function() {
+ it('should pass the correct arguments to the callback', function() {
+ var spy = jasmine.createSpy().andReturn(0);
+ testSubject.reduce(spy);
+ expect(spy.calls[0].args).toExactlyMatch([1, 2, 1, testSubject]);
+ });
+ it('should start with the right initialValue', function() {
+ var spy = jasmine.createSpy().andReturn(0);
+ testSubject.reduce(spy, 0);
+ expect(spy.calls[0].args).toExactlyMatch([0, 1, 0, testSubject]);
+ });
+ it('should not affect elements added to the array after it has begun', function() {
+ var arr = [1,2,3],
+ i = 0;
+ arr.reduce(function(a, b) {
+ i++;
+ if(i <= 4) {
+ arr.push(a+3);
+ };
+ return b;
+ });
+ expect(arr).toEqual([1,2,3,4,5]);
+ expect(i).toBe(2);
+ });
+ it('should work as expected for empty arrays', function() {
+ var spy = jasmine.createSpy();
+ expect(function() {
+ [].reduce(spy);
+ }).toThrow();
+ expect(spy).not.toHaveBeenCalled();
+ });
+ it('should throw correctly if no callback is given', function() {
+ expect(function() {
+ testSubject.reduce();
+ }).toThrow();
+ });
+ it('should return the expected result', function() {
+ expect(testSubject.reduce(function(a,b) {
+ return (a||'').toString()+(b||'').toString();
+ })).toEqual(testSubject.join(''));
+ });
+ it('should not directly affect the passed array', function() {
+ var copy = testSubject.slice();
+ testSubject.reduce(function(a,b) {
+ return a+b;
+ });
+ expect(testSubject).toEqual(copy);
+ });
+ it('should skip non-set values', function() {
+ delete testSubject[1];
+ var visited = {};
+ testSubject.reduce(function(a,b) {
+ if(a)
+ visited[a] = true;
+ if(b)
+ visited[b] = true;
+ return 0;
+ });
+
+ expect(visited).toEqual({ '1': true, '3': true });
+ });
+ it('should have the right length', function() {
+ expect(testSubject.reduce.length).toBe(1);
+ });
+ });
+ describe('Array-like objects', function() {
+ beforeEach(function() {
+ testSubject = createArrayLikeFromArray(testSubject);
+ testSubject.reduce = Array.prototype.reduce;
+ });
+ it('should pass the correct arguments to the callback', function() {
+ var spy = jasmine.createSpy().andReturn(0);
+ testSubject.reduce(spy);
+ expect(spy.calls[0].args).toExactlyMatch([1, 2, 1, testSubject]);
+ });
+ it('should start with the right initialValue', function() {
+ var spy = jasmine.createSpy().andReturn(0);
+ testSubject.reduce(spy, 0);
+ expect(spy.calls[0].args).toExactlyMatch([0, 1, 0, testSubject]);
+ });
+ it('should not affect elements added to the array after it has begun', function() {
+ var arr = createArrayLikeFromArray([1,2,3]),
+ i = 0;
+ Array.prototype.reduce.call(arr, function(a, b) {
+ i++;
+ if(i <= 4) {
+ arr[i+2] = a+3;
+ };
+ return b;
+ });
+ expect(arr).toEqual({
+ 0: 1,
+ 1: 2,
+ 2: 3,
+ 3: 4,
+ 4: 5,
+ length: 3
+ });
+ expect(i).toBe(2);
+ });
+ it('should work as expected for empty arrays', function() {
+ var spy = jasmine.createSpy();
+ expect(function() {
+ Array.prototype.reduce.call({length: 0}, spy);
+ }).toThrow();
+ expect(spy).not.toHaveBeenCalled();
+ });
+ it('should throw correctly if no callback is given', function() {
+ expect(function() {
+ testSubject.reduce();
+ }).toThrow();
+ });
+ it('should return the expected result', function() {
+ expect(testSubject.reduce(function(a,b) {
+ return (a||'').toString()+(b||'').toString();
+ })).toEqual('123');
+ });
+ it('should not directly affect the passed array', function() {
+ var copy = createArrayLikeFromArray(testSubject);
+ testSubject.reduce(function(a,b) {
+ return a+b;
+ });
+ delete(testSubject.reduce);
+ expect(testSubject).toEqual(copy);
+ });
+ it('should skip non-set values', function() {
+ delete testSubject[1];
+ var visited = {};
+ testSubject.reduce(function(a,b) {
+ if(a)
+ visited[a] = true;
+ if(b)
+ visited[b] = true;
+ return 0;
+ });
+
+ expect(visited).toEqual({ '1': true, '3': true });
+ });
+ it('should have the right length', function() {
+ expect(testSubject.reduce.length).toBe(1);
+ });
+ });
+ });
+ describe('reduceRight', function() {
+ beforeEach(function() {
+ testSubject = [1,2,3];
+ });
+
+ describe('Array', function() {
+ it('should pass the correct arguments to the callback', function() {
+ var spy = jasmine.createSpy().andReturn(0);
+ testSubject.reduceRight(spy);
+ expect(spy.calls[0].args).toExactlyMatch([3, 2, 1, testSubject]);
+ });
+ it('should start with the right initialValue', function() {
+ var spy = jasmine.createSpy().andReturn(0);
+ testSubject.reduceRight(spy, 0);
+ expect(spy.calls[0].args).toExactlyMatch([0, 3, 2, testSubject]);
+ });
+ it('should not affect elements added to the array after it has begun', function() {
+ var arr = [1,2,3],
+ i = 0;
+ arr.reduceRight(function(a, b) {
+ i++;
+ if(i <= 4) {
+ arr.push(a+3);
+ };
+ return b;
+ });
+ expect(arr).toEqual([1,2,3,6,5]);
+ expect(i).toBe(2);
+ });
+ it('should work as expected for empty arrays', function() {
+ var spy = jasmine.createSpy();
+ expect(function() {
+ [].reduceRight(spy);
+ }).toThrow();
+ expect(spy).not.toHaveBeenCalled();
+ });
+ it('should throw correctly if no callback is given', function() {
+ expect(function() {
+ testSubject.reduceRight();
+ }).toThrow();
+ });
+ it('should return the expected result', function() {
+ expect(testSubject.reduceRight(function(a,b) {
+ return (a||'').toString()+(b||'').toString();
+ })).toEqual('321');
+ });
+ it('should not directly affect the passed array', function() {
+ var copy = testSubject.slice();
+ testSubject.reduceRight(function(a,b) {
+ return a+b;
+ });
+ expect(testSubject).toEqual(copy);
+ });
+ it('should skip non-set values', function() {
+ delete testSubject[1];
+ var visited = {};
+ testSubject.reduceRight(function(a,b) {
+ if(a)
+ visited[a] = true;
+ if(b)
+ visited[b] = true;
+ return 0;
+ });
+
+ expect(visited).toEqual({ '1': true, '3': true });
+ });
+ it('should have the right length', function() {
+ expect(testSubject.reduceRight.length).toBe(1);
+ });
+ });
+ describe('Array-like objects', function() {
+ beforeEach(function() {
+ testSubject = createArrayLikeFromArray(testSubject);
+ testSubject.reduceRight = Array.prototype.reduceRight;
+ });
+ it('should pass the correct arguments to the callback', function() {
+ var spy = jasmine.createSpy().andReturn(0);
+ testSubject.reduceRight(spy);
+ expect(spy.calls[0].args).toExactlyMatch([3, 2, 1, testSubject]);
+ });
+ it('should start with the right initialValue', function() {
+ var spy = jasmine.createSpy().andReturn(0);
+ testSubject.reduceRight(spy, 0);
+ expect(spy.calls[0].args).toExactlyMatch([0, 3, 2, testSubject]);
+ });
+ it('should not affect elements added to the array after it has begun', function() {
+ var arr = createArrayLikeFromArray([1,2,3]),
+ i = 0;
+ Array.prototype.reduceRight.call(arr, function(a, b) {
+ i++;
+ if(i <= 4) {
+ arr[i+2] = a+3;
+ };
+ return b;
+ });
+ expect(arr).toEqual({
+ 0: 1,
+ 1: 2,
+ 2: 3,
+ 3: 6,
+ 4: 5,
+ length: 3 // does not get updated on property assignment
+ });
+ expect(i).toBe(2);
+ });
+ it('should work as expected for empty arrays', function() {
+ var spy = jasmine.createSpy();
+ expect(function() {
+ Array.prototype.reduceRight.call({length:0}, spy);
+ }).toThrow();
+ expect(spy).not.toHaveBeenCalled();
+ });
+ it('should throw correctly if no callback is given', function() {
+ expect(function() {
+ testSubject.reduceRight();
+ }).toThrow();
+ });
+ it('should return the expected result', function() {
+ expect(testSubject.reduceRight(function(a,b) {
+ return (a||'').toString()+(b||'').toString();
+ })).toEqual('321');
+ });
+ it('should not directly affect the passed array', function() {
+ var copy = createArrayLikeFromArray(testSubject);
+ testSubject.reduceRight(function(a,b) {
+ return a+b;
+ });
+ delete(testSubject.reduceRight);
+ expect(testSubject).toEqual(copy);
+ });
+ it('should skip non-set values', function() {
+ delete testSubject[1];
+ var visited = {};
+ testSubject.reduceRight(function(a,b) {
+ if(a)
+ visited[a] = true;
+ if(b)
+ visited[b] = true;
+ return 0;
+ });
+
+ expect(visited).toEqual({ '1': true, '3': true });
+ });
+ it('should have the right length', function() {
+ expect(testSubject.reduceRight.length).toBe(1);
+ });
+ });
+ });
+
+ describe('isArray', function () {
+ it('should work for Array', function () {
+ var ret = Array.isArray([]);
+
+ expect(ret).toBe(true);
+ });
+
+ it('should fail for other objects', function () {
+ var objects = [
+ "someString",
+ true,
+ false,
+ 42,
+ 0,
+ {},
+ Object.create(null),
+ /foo/,
+ arguments,
+ document.getElementsByTagName("div")
+ ];
+
+ objects.forEach(function (v) {
+ expect(Array.isArray(v)).toBe(false);
+ });
+ });
+ });
+
+
+});
diff --git a/resources/flightjs-example-app/components/es5-shim/tests/spec/s-date.js b/resources/flightjs-example-app/components/es5-shim/tests/spec/s-date.js
new file mode 100644
index 000000000..26ab28529
--- /dev/null
+++ b/resources/flightjs-example-app/components/es5-shim/tests/spec/s-date.js
@@ -0,0 +1,40 @@
+describe('Date', function () {
+
+ describe('now', function () {
+ it('should be the current time', function () {
+ expect(Date.now() === new Date().getTime()).toBe(true);
+ });
+ });
+
+ describe("parse", function () {
+ // TODO: Write the rest of the test.
+
+ it('should support extended years', function () {
+
+ expect(Date.parse('0001-01-01T00:00:00Z')).toBe(-62135596800000);
+ expect(Date.parse('+275760-09-13T00:00:00.000Z')).toBe(8.64e15);
+ expect(Date.parse('+033658-09-27T01:46:40.000Z')).toBe(1e15);
+ expect(Date.parse('-000001-01-01T00:00:00Z')).toBe(-62198755200000);
+ expect(Date.parse('+002009-12-15T00:00:00Z')).toBe(1260835200000);
+
+ });
+
+ });
+
+ describe("toISOString", function () {
+ // TODO: write the rest of the test.
+
+ it('should support extended years', function () {
+ expect(new Date(-62198755200000).toISOString().indexOf('-000001-01-01')).toBe(0);
+ expect(new Date(8.64e15).toISOString().indexOf('+275760-09-13')).toBe(0);
+ });
+ });
+
+ describe("toJSON", function () {
+ it('should return the isoString when stringified', function () {
+ var date = new Date();
+ expect(JSON.stringify(date.toISOString())).toBe(JSON.stringify(date));
+ })
+ });
+
+});
\ No newline at end of file
diff --git a/resources/flightjs-example-app/components/es5-shim/tests/spec/s-function.js b/resources/flightjs-example-app/components/es5-shim/tests/spec/s-function.js
new file mode 100644
index 000000000..adb6023fc
--- /dev/null
+++ b/resources/flightjs-example-app/components/es5-shim/tests/spec/s-function.js
@@ -0,0 +1,137 @@
+
+describe('Function', function() {
+ "use strict";
+ describe('bind', function() {
+ var actual, expected,
+ testSubject;
+
+ testSubject = {
+ push: function(o) {
+ this.a.push(o);
+ }
+ };
+
+ function func() {
+ Array.prototype.forEach.call(arguments, function(a) {
+ this.push(a);
+ }, this);
+ return this;
+ };
+
+ beforeEach(function() {
+ actual = [];
+ testSubject.a = [];
+ });
+
+ it('binds properly without a context', function() {
+ var context;
+ testSubject.func = function() {
+ context = this;
+ }.bind();
+ testSubject.func();
+ expect(context).toBe(function() {return this}.call());
+ });
+ it('binds properly without a context, and still supplies bound arguments', function() {
+ var a, context;
+ testSubject.func = function() {
+ a = Array.prototype.slice.call(arguments);
+ context = this;
+ }.bind(undefined, 1,2,3);
+ testSubject.func(1,2,3);
+ expect(a).toEqual([1,2,3,1,2,3]);
+ expect(context).toBe(function() {return this}.call());
+ });
+ it('binds a context properly', function() {
+ testSubject.func = func.bind(actual);
+ testSubject.func(1,2,3);
+ expect(actual).toEqual([1,2,3]);
+ expect(testSubject.a).toEqual([]);
+ });
+ it('binds a context and supplies bound arguments', function() {
+ testSubject.func = func.bind(actual, 1,2,3);
+ testSubject.func(4,5,6);
+ expect(actual).toEqual([1,2,3,4,5,6]);
+ expect(testSubject.a).toEqual([]);
+ });
+
+ it('returns properly without binding a context', function() {
+ testSubject.func = function() {
+ return this;
+ }.bind();
+ var context = testSubject.func();
+ expect(context).toBe(function() {return this}.call());
+ });
+ it('returns properly without binding a context, and still supplies bound arguments', function() {
+ var context;
+ testSubject.func = function() {
+ context = this;
+ return Array.prototype.slice.call(arguments);
+ }.bind(undefined, 1,2,3);
+ actual = testSubject.func(1,2,3);
+ expect(context).toBe(function() {return this}.call());
+ expect(actual).toEqual([1,2,3,1,2,3]);
+ });
+ it('returns properly while binding a context properly', function() {
+ var ret;
+ testSubject.func = func.bind(actual);
+ ret = testSubject.func(1,2,3);
+ expect(ret).toBe(actual);
+ expect(ret).not.toBe(testSubject);
+ });
+ it('returns properly while binding a context and supplies bound arguments', function() {
+ var ret;
+ testSubject.func = func.bind(actual, 1,2,3);
+ ret = testSubject.func(4,5,6);
+ expect(ret).toBe(actual);
+ expect(ret).not.toBe(testSubject);
+ });
+ it('passes the correct arguments as a constructor', function() {
+ var ret, expected = { name: "Correct" };
+ testSubject.func = function(arg) {
+ return arg;
+ }.bind({ name: "Incorrect" });
+ ret = new testSubject.func(expected);
+ expect(ret).toBe(expected);
+ });
+ it('returns the return value of the bound function when called as a constructor', function () {
+ var oracle = [1, 2, 3];
+ var subject = function () {
+ return oracle;
+ }.bind(null);
+ var result = new subject;
+ expect(result).toBe(oracle);
+ });
+ it('returns the correct value if constructor returns primitive', function() {
+ var oracle = [1, 2, 3];
+ var subject = function () {
+ return oracle;
+ }.bind(null);
+ var result = new subject;
+ expect(result).toBe(oracle);
+
+ oracle = {};
+ result = new subject;
+ expect(result).toBe(oracle);
+
+ oracle = function(){};
+ result = new subject;
+ expect(result).toBe(oracle);
+
+ oracle = "asdf";
+ result = new subject;
+ expect(result).not.toBe(oracle);
+
+ oracle = null;
+ result = new subject;
+ expect(result).not.toBe(oracle);
+
+ oracle = true;
+ result = new subject;
+ expect(result).not.toBe(oracle);
+
+ oracle = 1;
+ result = new subject;
+ expect(result).not.toBe(oracle);
+ });
+ });
+});
diff --git a/resources/flightjs-example-app/components/es5-shim/tests/spec/s-object.js b/resources/flightjs-example-app/components/es5-shim/tests/spec/s-object.js
new file mode 100644
index 000000000..8f2a87ad8
--- /dev/null
+++ b/resources/flightjs-example-app/components/es5-shim/tests/spec/s-object.js
@@ -0,0 +1,84 @@
+describe('Object', function () {
+ "use strict";
+
+ describe("Object.keys", function () {
+ var obj = {
+ "str": "boz",
+ "obj": { },
+ "arr": [],
+ "bool": true,
+ "num": 42,
+ "null": null,
+ "undefined": undefined
+ };
+
+ var loopedValues = [];
+ for (var k in obj) {
+ loopedValues.push(k);
+ }
+
+ var keys = Object.keys(obj);
+ it('should have correct length', function () {
+ expect(keys.length).toBe(7);
+ });
+
+ it('should return an Array', function () {
+ expect(Array.isArray(keys)).toBe(true);
+ });
+
+ it('should return names which are own properties', function () {
+ keys.forEach(function (name) {
+ expect(obj.hasOwnProperty(name)).toBe(true);
+ });
+ });
+
+ it('should return names which are enumerable', function () {
+ keys.forEach(function (name) {
+ expect(loopedValues.indexOf(name)).toNotBe(-1);
+ })
+ });
+
+ it('should throw error for non object', function () {
+ var e = {};
+ expect(function () {
+ try {
+ Object.keys(42)
+ } catch (err) {
+ throw e;
+ }
+ }).toThrow(e);
+ });
+ });
+
+ describe("Object.isExtensible", function () {
+ var obj = { };
+
+ it('should return true if object is extensible', function () {
+ expect(Object.isExtensible(obj)).toBe(true);
+ });
+
+ it('should return false if object is not extensible', function () {
+ expect(Object.isExtensible(Object.preventExtensions(obj))).toBe(false);
+ });
+
+ it('should return false if object is seal', function () {
+ expect(Object.isExtensible(Object.seal(obj))).toBe(false);
+ });
+
+ it('should return false if object is freeze', function () {
+ expect(Object.isExtensible(Object.freeze(obj))).toBe(false);
+ });
+
+ it('should throw error for non object', function () {
+ var e1 = {};
+ expect(function () {
+ try {
+ Object.isExtensible(42)
+ } catch (err) {
+ throw e1;
+ }
+ }).toThrow(e1);
+ });
+ });
+
+});
\ No newline at end of file
diff --git a/resources/flightjs-example-app/components/es5-shim/tests/spec/s-string.js b/resources/flightjs-example-app/components/es5-shim/tests/spec/s-string.js
new file mode 100644
index 000000000..c31498dd8
--- /dev/null
+++ b/resources/flightjs-example-app/components/es5-shim/tests/spec/s-string.js
@@ -0,0 +1,11 @@
+describe('String', function() {
+ "use strict";
+ describe("trim", function() {
+ var test = "\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFFHello, World!\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF";
+
+ it('trims all ES5 whitespace', function() {
+ expect(test.trim()).toEqual("Hello, World!");
+ expect(test.trim().length).toEqual(13);
+ });
+ });
+});
diff --git a/resources/flightjs-example-app/components/flight/.travis.yml b/resources/flightjs-example-app/components/flight/.travis.yml
new file mode 100644
index 000000000..03c668038
--- /dev/null
+++ b/resources/flightjs-example-app/components/flight/.travis.yml
@@ -0,0 +1,7 @@
+language: node_js
+node_js:
+ - 0.8
+before_script:
+ - "export DISPLAY=:99.0"
+ - "sh -e /etc/init.d/xvfb start"
+script: "npm install; make standalone; phantomjs test/phantom-jasmine/run_jasmine_test.coffee test/run/jasmine_test.html"
diff --git a/resources/flightjs-example-app/components/flight/lib/advice.js b/resources/flightjs-example-app/components/flight/lib/advice.js
new file mode 100644
index 000000000..0f4686929
--- /dev/null
+++ b/resources/flightjs-example-app/components/flight/lib/advice.js
@@ -0,0 +1,69 @@
+// ==========================================
+// Copyright 2013 Twitter, Inc
+// Licensed under The MIT License
+// http://opensource.org/licenses/MIT
+// ==========================================
+
+"use strict";
+
+define(
+
+ [
+ './utils',
+ './compose'
+ ],
+
+ function (util, compose) {
+
+ var advice = {
+
+ around: function(base, wrapped) {
+ return function composedAround() {
+ // unpacking arguments by hand benchmarked faster
+ var i = 0, l = arguments.length, args = new Array(l + 1);
+ args[0] = base.bind(this);
+ for (; i < l; i++) args[i + 1] = arguments[i];
+
+ return wrapped.apply(this, args);
+ }
+ },
+
+ before: function(base, before) {
+ var beforeFn = (typeof before == 'function') ? before : before.obj[before.fnName];
+ return function composedBefore() {
+ beforeFn.apply(this, arguments);
+ return base.apply(this, arguments);
+ }
+ },
+
+ after: function(base, after) {
+ var afterFn = (typeof after == 'function') ? after : after.obj[after.fnName];
+ return function composedAfter() {
+ var res = (base.unbound || base).apply(this, arguments);
+ afterFn.apply(this, arguments);
+ return res;
+ }
+ },
+
+ // a mixin that allows other mixins to augment existing functions by adding additional
+ // code before, after or around.
+ withAdvice: function() {
+ ['before', 'after', 'around'].forEach(function(m) {
+ this[m] = function(method, fn) {
+
+ compose.unlockProperty(this, method, function() {
+ if (typeof this[method] == 'function') {
+ return this[method] = advice[m](this[method], fn);
+ } else {
+ return this[method] = fn;
+ }
+ });
+
+ };
+ }, this);
+ }
+ };
+
+ return advice;
+ }
+);
diff --git a/resources/flightjs-example-app/components/flight/lib/component.js b/resources/flightjs-example-app/components/flight/lib/component.js
new file mode 100644
index 000000000..d9a5077ac
--- /dev/null
+++ b/resources/flightjs-example-app/components/flight/lib/component.js
@@ -0,0 +1,308 @@
+// ==========================================
+// Copyright 2013 Twitter, Inc
+// Licensed under The MIT License
+// http://opensource.org/licenses/MIT
+// ==========================================
+
+"use strict";
+
+define(
+
+ [
+ './advice',
+ './utils',
+ './compose',
+ './registry'
+ ],
+
+ function(advice, utils, compose, registry) {
+
+ var functionNameRegEx = /function (.*?)\s?\(/;
+ var componentId = 0;
+
+ function teardownInstance(instanceInfo){
+ instanceInfo.events.slice().forEach(function(event) {
+ var args = [event.type];
+
+ event.element && args.unshift(event.element);
+ (typeof event.callback == 'function') && args.push(event.callback);
+
+ this.off.apply(this, args);
+ }, instanceInfo.instance);
+ }
+
+
+ function teardown() {
+ teardownInstance(registry.findInstanceInfo(this));
+ }
+
+ //teardown for all instances of this constructor
+ function teardownAll() {
+ var componentInfo = registry.findComponentInfo(this);
+
+ componentInfo && Object.keys(componentInfo.instances).forEach(function(k) {
+ var info = componentInfo.instances[k];
+ info.instance.teardown();
+ });
+ }
+
+ function checkSerializable(type, data) {
+ try {
+ window.postMessage(data, '*');
+ } catch(e) {
+ console.log('unserializable data for event',type,':',data);
+ throw new Error(
+ ["The event", type, "on component", this.toString(), "was triggered with non-serializable data"].join(" ")
+ );
+ }
+ }
+
+ //common mixin allocates basic functionality - used by all component prototypes
+ //callback context is bound to component
+ function withBaseComponent() {
+
+ // delegate trigger, bind and unbind to an element
+ // if $element not supplied, use component's node
+ // other arguments are passed on
+ // event can be either a string specifying the type
+ // of the event, or a hash specifying both the type
+ // and a default function to be called.
+ this.trigger = function() {
+ var $element, type, data, event, defaultFn;
+ var lastIndex = arguments.length - 1, lastArg = arguments[lastIndex];
+
+ if (typeof lastArg != "string" && !(lastArg && lastArg.defaultBehavior)) {
+ lastIndex--;
+ data = lastArg;
+ }
+
+ if (lastIndex == 1) {
+ $element = $(arguments[0]);
+ event = arguments[1];
+ } else {
+ $element = this.$node;
+ event = arguments[0];
+ }
+
+ if (event.defaultBehavior) {
+ defaultFn = event.defaultBehavior;
+ event = $.Event(event.type);
+ }
+
+ type = event.type || event;
+
+ if (window.DEBUG && window.DEBUG.enabled && window.postMessage) {
+ checkSerializable.call(this, type, data);
+ }
+
+ if (typeof this.attr.eventData === 'object') {
+ data = $.extend(true, {}, this.attr.eventData, data);
+ }
+
+ $element.trigger((event || type), data);
+
+ if (defaultFn && !event.isDefaultPrevented()) {
+ (this[defaultFn] || defaultFn).call(this);
+ }
+
+ return $element;
+ };
+
+ this.on = function() {
+ var $element, type, callback, originalCb;
+ var lastIndex = arguments.length - 1, origin = arguments[lastIndex];
+
+ if (typeof origin == "object") {
+ //delegate callback
+ originalCb = utils.delegate(
+ this.resolveDelegateRules(origin)
+ );
+ } else {
+ originalCb = origin;
+ }
+
+ if (lastIndex == 2) {
+ $element = $(arguments[0]);
+ type = arguments[1];
+ } else {
+ $element = this.$node;
+ type = arguments[0];
+ }
+
+ if (typeof originalCb != 'function' && typeof originalCb != 'object') {
+ throw new Error("Unable to bind to '" + type + "' because the given callback is not a function or an object");
+ }
+
+ callback = originalCb.bind(this);
+ callback.target = originalCb;
+
+ // if the original callback is already branded by jQuery's guid, copy it to the context-bound version
+ if (originalCb.guid) {
+ callback.guid = originalCb.guid;
+ }
+
+ $element.on(type, callback);
+
+ // get jquery's guid from our bound fn, so unbinding will work
+ originalCb.guid = callback.guid;
+
+ return callback;
+ };
+
+ this.off = function() {
+ var $element, type, callback;
+ var lastIndex = arguments.length - 1;
+
+ if (typeof arguments[lastIndex] == "function") {
+ callback = arguments[lastIndex];
+ lastIndex -= 1;
+ }
+
+ if (lastIndex == 1) {
+ $element = $(arguments[0]);
+ type = arguments[1];
+ } else {
+ $element = this.$node;
+ type = arguments[0];
+ }
+
+ return $element.off(type, callback);
+ };
+
+ this.resolveDelegateRules = function(ruleInfo) {
+ var rules = {};
+
+ Object.keys(ruleInfo).forEach(function(r) {
+ if (!r in this.attr) {
+ throw new Error('Component "' + this.toString() + '" wants to listen on "' + r + '" but no such attribute was defined.');
+ }
+ rules[this.attr[r]] = ruleInfo[r];
+ }, this);
+
+ return rules;
+ };
+
+ this.defaultAttrs = function(defaults) {
+ utils.push(this.defaults, defaults, true) || (this.defaults = defaults);
+ };
+
+ this.select = function(attributeKey) {
+ return this.$node.find(this.attr[attributeKey]);
+ };
+
+ this.initialize = $.noop;
+ this.teardown = teardown;
+ }
+
+ function attachTo(selector/*, options args */) {
+ // unpacking arguments by hand benchmarked faster
+ var l = arguments.length;
+ var args = new Array(l - 1);
+ for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
+
+ if (!selector) {
+ throw new Error("Component needs to be attachTo'd a jQuery object, native node or selector string");
+ }
+
+ var options = utils.merge.apply(utils, args);
+
+ $(selector).each(function(i, node) {
+ var rawNode = node.jQuery ? node[0] : node;
+ var componentInfo = registry.findComponentInfo(this)
+ if (componentInfo && componentInfo.isAttachedTo(rawNode)) {
+ //already attached
+ return;
+ }
+
+ new this(node, options);
+ }.bind(this));
+ }
+
+ // define the constructor for a custom component type
+ // takes an unlimited number of mixin functions as arguments
+ // typical api call with 3 mixins: define(timeline, withTweetCapability, withScrollCapability);
+ function define(/*mixins*/) {
+ // unpacking arguments by hand benchmarked faster
+ var l = arguments.length;
+ var mixins = new Array(l);
+ for (var i = 0; i < l; i++) mixins[i] = arguments[i];
+
+ Component.toString = function() {
+ var prettyPrintMixins = mixins.map(function(mixin) {
+ if (mixin.name == null) {
+ //function name property not supported by this browser, use regex
+ var m = mixin.toString().match(functionNameRegEx);
+ return (m && m[1]) ? m[1] : "";
+ } else {
+ return (mixin.name != "withBaseComponent") ? mixin.name : "";
+ }
+ }).filter(Boolean).join(', ');
+ return prettyPrintMixins;
+ };
+
+ if (window.DEBUG && window.DEBUG.enabled) {
+ Component.describe = Component.toString();
+ }
+
+ //'options' is optional hash to be merged with 'defaults' in the component definition
+ function Component(node, options) {
+ options = options || {};
+ this.identity = componentId++;
+
+ if (!node) {
+ throw new Error("Component needs a node");
+ }
+
+ if (node.jquery) {
+ this.node = node[0];
+ this.$node = node;
+ } else {
+ this.node = node;
+ this.$node = $(node);
+ }
+
+ this.toString = Component.toString;
+ if (window.DEBUG && window.DEBUG.enabled) {
+ this.describe = this.toString();
+ }
+
+ //merge defaults with supplied options
+ //put options in attr.__proto__ to avoid merge overhead
+ var attr = Object.create(options);
+ for (var key in this.defaults) {
+ if (!options.hasOwnProperty(key)) {
+ attr[key] = this.defaults[key];
+ }
+ }
+ this.attr = attr;
+
+ Object.keys(this.defaults || {}).forEach(function(key) {
+ if (this.defaults[key] === null && this.attr[key] === null) {
+ throw new Error('Required attribute "' + key + '" not specified in attachTo for component "' + this.toString() + '".');
+ }
+ }, this);
+
+ this.initialize.call(this, options);
+ }
+
+ Component.attachTo = attachTo;
+ Component.teardownAll = teardownAll;
+
+ // prepend common mixins to supplied list, then mixin all flavors
+ mixins.unshift(withBaseComponent, advice.withAdvice, registry.withRegistration);
+
+ compose.mixin(Component.prototype, mixins);
+
+ return Component;
+ }
+
+ define.teardownAll = function() {
+ registry.components.slice().forEach(function(c) {
+ c.component.teardownAll();
+ });
+ registry.reset();
+ };
+
+ return define;
+ }
+);
diff --git a/resources/flightjs-example-app/components/flight/lib/compose.js b/resources/flightjs-example-app/components/flight/lib/compose.js
new file mode 100644
index 000000000..c1343e7fe
--- /dev/null
+++ b/resources/flightjs-example-app/components/flight/lib/compose.js
@@ -0,0 +1,86 @@
+// ==========================================
+// Copyright 2013 Twitter, Inc
+// Licensed under The MIT License
+// http://opensource.org/licenses/MIT
+// ==========================================
+
+"use strict";
+
+define(
+
+ [
+ './utils',
+ '../tools/debug/debug'
+ ],
+
+ function(util, debug) {
+
+ //enumerables are shims - getOwnPropertyDescriptor shim doesn't work
+ var canWriteProtect = debug.enabled && !util.isEnumerable(Object, 'getOwnPropertyDescriptor');
+ //whitelist of unlockable property names
+ var dontLock = ['mixedIn'];
+
+ if (canWriteProtect) {
+ //IE8 getOwnPropertyDescriptor is built-in but throws exeption on non DOM objects
+ try {
+ Object.getOwnPropertyDescriptor(Object, 'keys');
+ } catch(e) {
+ canWriteProtect = false;
+ }
+ }
+
+ function setPropertyWritability(obj, isWritable) {
+ if (!canWriteProtect) {
+ return;
+ }
+
+ var props = Object.create(null);
+
+ Object.keys(obj).forEach(
+ function (key) {
+ if (dontLock.indexOf(key) < 0) {
+ var desc = Object.getOwnPropertyDescriptor(obj, key);
+ desc.writable = isWritable;
+ props[key] = desc;
+ }
+ }
+ );
+
+ Object.defineProperties(obj, props);
+ }
+
+ function unlockProperty(obj, prop, op) {
+ var writable;
+
+ if (!canWriteProtect || !obj.hasOwnProperty(prop)) {
+ op.call(obj);
+ return;
+ }
+
+ writable = Object.getOwnPropertyDescriptor(obj, prop).writable;
+ Object.defineProperty(obj, prop, { writable: true });
+ op.call(obj);
+ Object.defineProperty(obj, prop, { writable: writable });
+ }
+
+ function mixin(base, mixins) {
+ base.mixedIn = base.hasOwnProperty('mixedIn') ? base.mixedIn : [];
+
+ mixins.forEach(function(mixin) {
+ if (base.mixedIn.indexOf(mixin) == -1) {
+ setPropertyWritability(base, false);
+ mixin.call(base);
+ base.mixedIn.push(mixin);
+ }
+ });
+
+ setPropertyWritability(base, true);
+ }
+
+ return {
+ mixin: mixin,
+ unlockProperty: unlockProperty
+ };
+
+ }
+);
diff --git a/resources/flightjs-example-app/components/flight/lib/index.js b/resources/flightjs-example-app/components/flight/lib/index.js
new file mode 100644
index 000000000..1604b253b
--- /dev/null
+++ b/resources/flightjs-example-app/components/flight/lib/index.js
@@ -0,0 +1,30 @@
+// ==========================================
+// Copyright 2013 Twitter, Inc
+// Licensed under The MIT License
+// http://opensource.org/licenses/MIT
+// ==========================================
+
+define(
+
+ [
+ './advice',
+ './component',
+ './compose',
+ './logger',
+ './registry',
+ './utils'
+ ],
+
+ function (advice, component, compose, logger, registry, utils) {
+
+ return {
+ advice: advice,
+ component: component,
+ compose: compose,
+ logger: logger,
+ registry: registry,
+ utils: utils
+ };
+
+ }
+);
diff --git a/resources/flightjs-example-app/components/flight/lib/logger.js b/resources/flightjs-example-app/components/flight/lib/logger.js
new file mode 100644
index 000000000..2c89b1bd2
--- /dev/null
+++ b/resources/flightjs-example-app/components/flight/lib/logger.js
@@ -0,0 +1,93 @@
+// ==========================================
+// Copyright 2013 Twitter, Inc
+// Licensed under The MIT License
+// http://opensource.org/licenses/MIT
+// ==========================================
+
+"use strict";
+
+define(
+
+ [
+ './compose',
+ './utils'
+ ],
+
+ function (compose, util) {
+
+ var actionSymbols = {
+ on:'<-',
+ trigger: '->',
+ off: 'x '
+ };
+
+ function elemToString(elem) {
+ var tagStr = elem.tagName ? elem.tagName.toLowerCase() : elem.toString();
+ var classStr = elem.className ? "." + (elem.className) : "";
+ var result = tagStr + classStr;
+ return elem.tagName ? ['\'', '\''].join(result) : result;
+ }
+
+ function log(action, component, eventArgs) {
+
+ var name, elem, fn, fnName, logFilter, toRegExp, actionLoggable, nameLoggable;
+
+ if (typeof eventArgs[eventArgs.length-1] == 'function') {
+ fn = eventArgs.pop();
+ fn = fn.unbound || fn; //use unbound version if any (better info)
+ }
+
+ if (typeof eventArgs[eventArgs.length - 1] == 'object') {
+ eventArgs.pop(); //trigger data arg - not logged right now
+ }
+
+ if (eventArgs.length == 2) {
+ elem = eventArgs[0];
+ name = eventArgs[1];
+ } else {
+ elem = component.$node[0];
+ name = eventArgs[0];
+ }
+
+ if (window.DEBUG && window.DEBUG.enabled) {
+ logFilter = DEBUG.events.logFilter;
+
+ // no regex for you, actions...
+ actionLoggable = logFilter.actions=="all" || (logFilter.actions.indexOf(action) > -1);
+ // event name filter allow wildcards or regex...
+ toRegExp = function(expr) {
+ return expr.test ? expr : new RegExp("^" + expr.replace(/\*/g, ".*") + "$");
+ };
+ nameLoggable =
+ logFilter.eventNames=="all" ||
+ logFilter.eventNames.some(function(e) {return toRegExp(e).test(name)});
+
+ if (actionLoggable && nameLoggable) {
+ console.info(
+ actionSymbols[action],
+ action,
+ '[' + name + ']',
+ elemToString(elem),
+ component.constructor.toString(),
+ fn && (fnName = fn.name || fn.displayName) && '-> ' + fnName
+ );
+ }
+ }
+ }
+
+
+ function withLogging() {
+ this.before('trigger', function() {
+ log('trigger', this, util.toArray(arguments));
+ });
+ this.before('on', function() {
+ log('on', this, util.toArray(arguments));
+ });
+ this.before('off', function(eventArgs) {
+ log('off', this, util.toArray(arguments));
+ });
+ }
+
+ return withLogging;
+ }
+);
diff --git a/resources/flightjs-example-app/components/flight/lib/registry.js b/resources/flightjs-example-app/components/flight/lib/registry.js
new file mode 100644
index 000000000..baaa3b10a
--- /dev/null
+++ b/resources/flightjs-example-app/components/flight/lib/registry.js
@@ -0,0 +1,220 @@
+// ==========================================
+// Copyright 2013 Twitter, Inc
+// Licensed under The MIT License
+// http://opensource.org/licenses/MIT
+// ==========================================
+
+"use strict";
+
+define(
+
+ [
+ './utils'
+ ],
+
+ function (util) {
+
+ function parseEventArgs(instance, args) {
+ var element, type, callback;
+ var end = args.length;
+
+ if (typeof args[end - 1] === 'function') {
+ end -= 1;
+ callback = args[end];
+ }
+
+ if (typeof args[end - 1] === 'object') {
+ end -= 1;
+ }
+
+ if (end == 2) {
+ element = args[0];
+ type = args[1];
+ } else {
+ element = instance.node;
+ type = args[0];
+ }
+
+ return {
+ element: element,
+ type: type,
+ callback: callback
+ };
+ }
+
+ function matchEvent(a, b) {
+ return (
+ (a.element == b.element) &&
+ (a.type == b.type) &&
+ (b.callback == null || (a.callback == b.callback))
+ );
+ }
+
+ function Registry() {
+
+ var registry = this;
+
+ (this.reset = function() {
+ this.components = [];
+ this.allInstances = {};
+ this.events = [];
+ }).call(this);
+
+ function ComponentInfo(component) {
+ this.component = component;
+ this.attachedTo = [];
+ this.instances = {};
+
+ this.addInstance = function(instance) {
+ var instanceInfo = new InstanceInfo(instance);
+ this.instances[instance.identity] = instanceInfo;
+ this.attachedTo.push(instance.node);
+
+ return instanceInfo;
+ }
+
+ this.removeInstance = function(instance) {
+ delete this.instances[instance.identity];
+ var indexOfNode = this.attachedTo.indexOf(instance.node);
+ (indexOfNode > -1) && this.attachedTo.splice(indexOfNode, 1);
+
+ if (!this.instances.length) {
+ //if I hold no more instances remove me from registry
+ registry.removeComponentInfo(this);
+ }
+ }
+
+ this.isAttachedTo = function(node) {
+ return this.attachedTo.indexOf(node) > -1;
+ }
+ }
+
+ function InstanceInfo(instance) {
+ this.instance = instance;
+ this.events = [];
+
+ this.addBind = function(event) {
+ this.events.push(event);
+ registry.events.push(event);
+ };
+
+ this.removeBind = function(event) {
+ for (var i = 0, e; e = this.events[i]; i++) {
+ if (matchEvent(e, event)) {
+ this.events.splice(i, 1);
+ }
+ }
+ }
+ }
+
+ this.addInstance = function(instance) {
+ var component = this.findComponentInfo(instance);
+
+ if (!component) {
+ component = new ComponentInfo(instance.constructor);
+ this.components.push(component);
+ }
+
+ var inst = component.addInstance(instance);
+
+ this.allInstances[instance.identity] = inst;
+
+ return component;
+ };
+
+ this.removeInstance = function(instance) {
+ var index, instInfo = this.findInstanceInfo(instance);
+
+ //remove from component info
+ var componentInfo = this.findComponentInfo(instance);
+ componentInfo && componentInfo.removeInstance(instance);
+
+ //remove from registry
+ delete this.allInstances[instance.identity];
+ };
+
+ this.removeComponentInfo = function(componentInfo) {
+ var index = this.components.indexOf(componentInfo);
+ (index > -1) && this.components.splice(index, 1);
+ };
+
+ this.findComponentInfo = function(which) {
+ var component = which.attachTo ? which : which.constructor;
+
+ for (var i = 0, c; c = this.components[i]; i++) {
+ if (c.component === component) {
+ return c;
+ }
+ }
+
+ return null;
+ };
+
+ this.findInstanceInfo = function(instance) {
+ return this.allInstances[instance.identity] || null;
+ };
+
+ this.findInstanceInfoByNode = function(node) {
+ var result = [];
+ Object.keys(this.allInstances).forEach(function(k) {
+ var thisInstanceInfo = this.allInstances[k];
+ if(thisInstanceInfo.instance.node === node) {
+ result.push(thisInstanceInfo);
+ }
+ }, this);
+ return result;
+ };
+
+ this.on = function(componentOn) {
+ var instance = registry.findInstanceInfo(this), boundCallback;
+
+ // unpacking arguments by hand benchmarked faster
+ var l = arguments.length, i = 1;
+ var otherArgs = new Array(l - 1);
+ for (; i < l; i++) otherArgs[i - 1] = arguments[i];
+
+ if (instance) {
+ boundCallback = componentOn.apply(null, otherArgs);
+ if (boundCallback) {
+ otherArgs[otherArgs.length-1] = boundCallback;
+ }
+ var event = parseEventArgs(this, otherArgs);
+ instance.addBind(event);
+ }
+ };
+
+ this.off = function(el, type, callback) {
+ var event = parseEventArgs(this, arguments),
+ instance = registry.findInstanceInfo(this);
+
+ if (instance) {
+ instance.removeBind(event);
+ }
+ };
+
+ //debug tools may want to add advice to trigger
+ if (window.DEBUG && DEBUG.enabled) {
+ registry.trigger = new Function;
+ }
+
+ this.teardown = function() {
+ registry.removeInstance(this);
+ };
+
+ this.withRegistration = function() {
+ this.before('initialize', function() {
+ registry.addInstance(this);
+ });
+
+ this.around('on', registry.on);
+ this.after('off', registry.off);
+ //debug tools may want to add advice to trigger
+ window.DEBUG && DEBUG.enabled && this.after('trigger', registry.trigger);
+ this.after('teardown', {obj:registry, fnName:'teardown'});
+ };
+
+ }
+
+ return new Registry;
+ }
+);
diff --git a/resources/flightjs-example-app/components/flight/lib/utils.js b/resources/flightjs-example-app/components/flight/lib/utils.js
new file mode 100644
index 000000000..31af77734
--- /dev/null
+++ b/resources/flightjs-example-app/components/flight/lib/utils.js
@@ -0,0 +1,236 @@
+// ==========================================
+// Copyright 2013 Twitter, Inc
+// Licensed under The MIT License
+// http://opensource.org/licenses/MIT
+// ==========================================
+
+"use strict";
+
+define(
+
+ [],
+
+ function () {
+
+ var arry = [];
+ var DEFAULT_INTERVAL = 100;
+
+ var utils = {
+
+ isDomObj: function(obj) {
+ return !!(obj.nodeType || (obj === window));
+ },
+
+ toArray: function(obj, from) {
+ return arry.slice.call(obj, from);
+ },
+
+ // returns new object representing multiple objects merged together
+ // optional final argument is boolean which specifies if merge is recursive
+ // original objects are unmodified
+ //
+ // usage:
+ // var base = {a:2, b:6};
+ // var extra = {b:3, c:4};
+ // merge(base, extra); //{a:2, b:3, c:4}
+ // base; //{a:2, b:6}
+ //
+ // var base = {a:2, b:6};
+ // var extra = {b:3, c:4};
+ // var extraExtra = {a:4, d:9};
+ // merge(base, extra, extraExtra); //{a:4, b:3, c:4. d: 9}
+ // base; //{a:2, b:6}
+ //
+ // var base = {a:2, b:{bb:4, cc:5}};
+ // var extra = {a:4, b:{cc:7, dd:1}};
+ // merge(base, extra, true); //{a:4, b:{bb:4, cc:7, dd:1}}
+ // base; //{a:2, b:6}
+
+ merge: function(/*obj1, obj2,....deepCopy*/) {
+ // unpacking arguments by hand benchmarked faster
+ var l = arguments.length,
+ i = 0,
+ args = new Array(l + 1);
+ for (; i < l; i++) args[i + 1] = arguments[i];
+
+ if (l === 0) {
+ return {};
+ }
+
+ //start with empty object so a copy is created
+ args[0] = {};
+
+ if (args[args.length - 1] === true) {
+ //jquery extend requires deep copy as first arg
+ args.pop();
+ args.unshift(true);
+ }
+
+ return $.extend.apply(undefined, args);
+ },
+
+ // updates base in place by copying properties of extra to it
+ // optionally clobber protected
+ // usage:
+ // var base = {a:2, b:6};
+ // var extra = {c:4};
+ // push(base, extra); //{a:2, b:6, c:4}
+ // base; //{a:2, b:6, c:4}
+ //
+ // var base = {a:2, b:6};
+ // var extra = {b: 4 c:4};
+ // push(base, extra, true); //Error ("utils.push attempted to overwrite 'b' while running in protected mode")
+ // base; //{a:2, b:6}
+ //
+ // objects with the same key will merge recursively when protect is false
+ // eg:
+ // var base = {a:16, b:{bb:4, cc:10}};
+ // var extra = {b:{cc:25, dd:19}, c:5};
+ // push(base, extra); //{a:16, {bb:4, cc:25, dd:19}, c:5}
+ //
+ push: function(base, extra, protect) {
+ if (base) {
+ Object.keys(extra || {}).forEach(function(key) {
+ if (base[key] && protect) {
+ throw Error("utils.push attempted to overwrite '" + key + "' while running in protected mode");
+ }
+
+ if (typeof base[key] == "object" && typeof extra[key] == "object") {
+ //recurse
+ this.push(base[key], extra[key]);
+ } else {
+ //no protect, so extra wins
+ base[key] = extra[key];
+ }
+ }, this);
+ }
+
+ return base;
+ },
+
+ isEnumerable: function(obj, property) {
+ return Object.keys(obj).indexOf(property) > -1;
+ },
+
+ //build a function from other function(s)
+ //util.compose(a,b,c) -> a(b(c()));
+ //implementation lifted from underscore.js (c) 2009-2012 Jeremy Ashkenas
+ compose: function() {
+ var funcs = arguments;
+
+ return function() {
+ var args = arguments;
+
+ for (var i = funcs.length-1; i >= 0; i--) {
+ args = [funcs[i].apply(this, args)];
+ }
+
+ return args[0];
+ };
+ },
+
+ // Can only unique arrays of homogeneous primitives, e.g. an array of only strings, an array of only booleans, or an array of only numerics
+ uniqueArray: function(array) {
+ var u = {}, a = [];
+
+ for (var i = 0, l = array.length; i < l; ++i) {
+ if (u.hasOwnProperty(array[i])) {
+ continue;
+ }
+
+ a.push(array[i]);
+ u[array[i]] = 1;
+ }
+
+ return a;
+ },
+
+ debounce: function(func, wait, immediate) {
+ if (typeof wait != 'number') {
+ wait = DEFAULT_INTERVAL;
+ }
+
+ var timeout, result;
+
+ return function() {
+ var context = this, args = arguments;
+ var later = function() {
+ timeout = null;
+ if (!immediate) {
+ result = func.apply(context, args);
+ }
+ };
+ var callNow = immediate && !timeout;
+
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+
+ if (callNow) {
+ result = func.apply(context, args);
+ }
+
+ return result;
+ };
+ },
+
+ throttle: function(func, wait) {
+ if (typeof wait != 'number') {
+ wait = DEFAULT_INTERVAL;
+ }
+
+ var context, args, timeout, throttling, more, result;
+ var whenDone = this.debounce(function(){
+ more = throttling = false;
+ }, wait);
+
+ return function() {
+ context = this; args = arguments;
+ var later = function() {
+ timeout = null;
+ if (more) {
+ result = func.apply(context, args);
+ }
+ whenDone();
+ };
+
+ if (!timeout) {
+ timeout = setTimeout(later, wait);
+ }
+
+ if (throttling) {
+ more = true;
+ } else {
+ throttling = true;
+ result = func.apply(context, args);
+ }
+
+ whenDone();
+ return result;
+ };
+ },
+
+ countThen: function(num, base) {
+ return function() {
+ if (!--num) { return base.apply(this, arguments); }
+ };
+ },
+
+ delegate: function(rules) {
+ return function(e, data) {
+ var target = $(e.target), parent;
+
+ Object.keys(rules).forEach(function(selector) {
+ if ((parent = target.closest(selector)).length) {
+ data = data || {};
+ data.el = parent[0];
+ return rules[selector].apply(this, [e, data]);
+ }
+ }, this);
+ };
+ }
+
+ };
+
+ return utils;
+ }
+);
diff --git a/resources/flightjs-example-app/components/flight/tools/debug/debug.js b/resources/flightjs-example-app/components/flight/tools/debug/debug.js
new file mode 100644
index 000000000..b2e4fdfef
--- /dev/null
+++ b/resources/flightjs-example-app/components/flight/tools/debug/debug.js
@@ -0,0 +1,157 @@
+"use strict";
+
+define(
+
+ [
+ '../../lib/registry',
+ '../../lib/utils'
+ ],
+
+ function(registry, utils) {
+
+ var logFilter;
+
+ //******************************************************************************************
+ // Search object model
+ //******************************************************************************************
+
+ function traverse(util, searchTerm, options) {
+ var options = options || {};
+ var obj = options.obj || window;
+ var path = options.path || ((obj==window) ? "window" : "");
+ var props = Object.keys(obj);
+ props.forEach(function(prop) {
+ if ((tests[util] || util)(searchTerm, obj, prop)){
+ console.log([path, ".", prop].join(""), "->",["(", typeof obj[prop], ")"].join(""), obj[prop]);
+ }
+ if(Object.prototype.toString.call(obj[prop])=="[object Object]" && (obj[prop] != obj) && path.split(".").indexOf(prop) == -1) {
+ traverse(util, searchTerm, {obj: obj[prop], path: [path,prop].join(".")});
+ }
+ });
+ }
+
+ function search(util, expected, searchTerm, options) {
+ if (!expected || typeof searchTerm == expected) {
+ traverse(util, searchTerm, options);
+ } else {
+ console.error([searchTerm, 'must be', expected].join(' '))
+ }
+ }
+
+ var tests = {
+ 'name': function(searchTerm, obj, prop) {return searchTerm == prop},
+ 'nameContains': function(searchTerm, obj, prop) {return prop.indexOf(searchTerm)>-1},
+ 'type': function(searchTerm, obj, prop) {return obj[prop] instanceof searchTerm},
+ 'value': function(searchTerm, obj, prop) {return obj[prop] === searchTerm},
+ 'valueCoerced': function(searchTerm, obj, prop) {return obj[prop] == searchTerm}
+ }
+
+ function byName(searchTerm, options) {search('name', 'string', searchTerm, options);};
+ function byNameContains(searchTerm, options) {search('nameContains', 'string', searchTerm, options);};
+ function byType(searchTerm, options) {search('type', 'function', searchTerm, options);};
+ function byValue(searchTerm, options) {search('value', null, searchTerm, options);};
+ function byValueCoerced(searchTerm, options) {search('valueCoerced', null, searchTerm, options);};
+ function custom(fn, options) {traverse(fn, null, options);};
+
+ //******************************************************************************************
+ // Event logging
+ //******************************************************************************************
+
+ var ALL = 'all'; //no filter
+
+ //no logging by default
+ var defaultEventNamesFilter = [];
+ var defaultActionsFilter = [];
+
+ var logFilter = retrieveLogFilter();
+
+ function filterEventLogsByAction(/*actions*/) {
+ var actions = [].slice.call(arguments);
+
+ logFilter.eventNames.length || (logFilter.eventNames = ALL);
+ logFilter.actions = actions.length ? actions : ALL;
+ saveLogFilter();
+ }
+
+ function filterEventLogsByName(/*eventNames*/) {
+ var eventNames = [].slice.call(arguments);
+
+ logFilter.actions.length || (logFilter.actions = ALL);
+ logFilter.eventNames = eventNames.length ? eventNames : ALL;
+ saveLogFilter();
+ }
+
+ function hideAllEventLogs() {
+ logFilter.actions = [];
+ logFilter.eventNames = [];
+ saveLogFilter();
+ }
+
+ function showAllEventLogs() {
+ logFilter.actions = ALL;
+ logFilter.eventNames = ALL;
+ saveLogFilter();
+ }
+
+ function saveLogFilter() {
+ if (window.localStorage) {
+ localStorage.setItem('logFilter_eventNames', logFilter.eventNames);
+ localStorage.setItem('logFilter_actions', logFilter.actions);
+ }
+ }
+
+ function retrieveLogFilter() {
+ var result = {
+ eventNames: (window.localStorage && localStorage.getItem('logFilter_eventNames')) || defaultEventNamesFilter,
+ actions: (window.localStorage && localStorage.getItem('logFilter_actions')) || defaultActionsFilter
+ };
+ //reconstitute arrays
+ Object.keys(result).forEach(function(k) {
+ var thisProp = result[k];
+ if (typeof thisProp == 'string' && thisProp !== ALL) {
+ result[k] = thisProp.split(',');
+ }
+ });
+ return result;
+ }
+
+ return {
+
+ enable: function(enable) {
+ this.enabled = !!enable;
+
+ if (enable && window.console) {
+ console.info('Booting in DEBUG mode');
+ console.info('You can configure event logging with DEBUG.events.logAll()/logNone()/logByName()/logByAction()');
+ }
+
+ window.DEBUG = this;
+ },
+
+ find: {
+ byName: byName,
+ byNameContains: byNameContains,
+ byType: byType,
+ byValue: byValue,
+ byValueCoerced: byValueCoerced,
+ custom: custom
+ },
+
+ events: {
+ logFilter: logFilter,
+
+ // Accepts any number of action args
+ // e.g. DEBUG.events.logByAction("on", "off")
+ logByAction: filterEventLogsByAction,
+
+ // Accepts any number of event name args (inc. regex or wildcards)
+ // e.g. DEBUG.events.logByName(/ui.*/, "*Thread*");
+ logByName: filterEventLogsByName,
+
+ logAll: showAllEventLogs,
+ logNone: hideAllEventLogs
+ }
+ };
+ }
+);
+
diff --git a/resources/flightjs-example-app/components/jasmine-flight/LICENSE.md b/resources/flightjs-example-app/components/jasmine-flight/LICENSE.md
new file mode 100644
index 000000000..57ccdff5f
--- /dev/null
+++ b/resources/flightjs-example-app/components/jasmine-flight/LICENSE.md
@@ -0,0 +1,19 @@
+Copyright (c) 2013 Twitter, Inc and others
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/resources/flightjs-example-app/components/jasmine-flight/README.md b/resources/flightjs-example-app/components/jasmine-flight/README.md
new file mode 100644
index 000000000..aa8b0f84c
--- /dev/null
+++ b/resources/flightjs-example-app/components/jasmine-flight/README.md
@@ -0,0 +1,145 @@
+# jasmine-flight [](http://travis-ci.org/flightjs/jasmine-flight)
+
+Extensions to the Jasmine test framework for use with [Flight](https://github.com/flightjs/flight)
+
+# Getting started
+
+Include [jasmine-flight.js](https://raw.github.com/flightjs/jasmine-flight/master/lib/jasmine-flight.js)
+in your app and load it in your test runner.
+
+Or install it with [Bower](http://bower.io/):
+
+```bash
+bower install --save-dev jasmine-flight
+```
+
+**N.B.** jasmine-flight depends on
+[jasmine](https://github.com/pivotal/jasmine) and
+[jasmine-jquery](https://github.com/velesin/jasmine-jquery)
+
+## Components
+
+```javascript
+describeComponent('path/to/component', function () {
+ beforeEach(function () {
+ setupComponent();
+ });
+
+ it('should do x', function () {
+ // a component instance is now accessible as this.component
+ // the component root node is attached to the DOM
+ // the component root node is also available as this.$node
+ });
+});
+```
+
+## Mixins
+
+```javascript
+describeMixin('path/to/mixin', function () {
+ // initialize the component and attach it to the DOM
+ beforeEach(function () {
+ setupComponent();
+ });
+
+ it('should do x', function () {
+ expect(this.component.doSomething()).toBe(expected);
+ });
+});
+```
+
+## Event spy
+
+```javascript
+describeComponent('data/twitter_profile', function () {
+ beforeEach(function () {
+ setupComponent();
+ });
+
+ describe('listens for uiNeedsTwitterUserId', function () {
+ // was the event triggered?
+ it('and triggers dataTwitterUserId', function () {
+ var eventSpy = spyOnEvent(document, 'dataTwitterProfile');
+ $(document).trigger('uiNeedsTwitterUserId', {
+ screen_name: 'tbrd'
+ });
+ expect(eventSpy).toHaveBeenTriggeredOn(document);
+ });
+
+ // is the user id correct?
+ it('and has correct id', function () {
+ var eventSpy = spyOnEvent(document, 'dataTwitterUserId');
+ $(document).trigger('uiNeedsTwitteruserId', {
+ screen_name: 'tbrd'
+ });
+ expect(eventSpy.mostRecentCall.data).toEqual({
+ screen_name: 'tbrd',
+ id: 4149861
+ });
+ });
+ });
+});
+```
+
+## setupComponent
+
+```javascript
+setupComponent(optionalFixture, optionalOptions);
+```
+
+Calling `setupComponent` twice will create an instance, tear it down and create a new one.
+
+### HTML Fixtures
+
+```javascript
+describeComponent('ui/twitter_profile', function () {
+ // is the component attached to the fixture?
+ it('this.component.$node has class "foo"', function () {
+ setupComponent('Test');
+ expect(this.component.$node).toHaveClass('foo');
+ });
+});
+```
+
+### Component Options
+
+```javascript
+describeComponent('data/twitter_profile', function () {
+ // is the option set correctly?
+ it('this.component.attr.baseUrl is set', function () {
+ setupComponent({
+ baseUrl: 'http://twitter.com/1.1/'
+ });
+ expect(this.component.attr.baseUrl).toBe('http://twitter.com/1.1/');
+ });
+});
+```
+
+# Teardown
+
+Components are automatically torn down after each test.
+
+## Contributing to this project
+
+Anyone and everyone is welcome to contribute. Please take a moment to
+review the [guidelines for contributing](CONTRIBUTING.md).
+
+* [Bug reports](CONTRIBUTING.md#bugs)
+* [Feature requests](CONTRIBUTING.md#features)
+* [Pull requests](CONTRIBUTING.md#pull-requests)
+
+## Authors
+
+* [@tbrd](http://github.com/tbrd)
+
+## Thanks
+
+* [@esbie](http://github.com/esbie) and
+ [@skilldrick](http://github.com/skilldrick) for creating the original
+ `describeComponent` & `describeMixin` methods.
+
+## License
+
+Copyright 2013 Twitter, Inc and other contributors.
+
+Licensed under the MIT License
diff --git a/resources/flightjs-example-app/components/jasmine-flight/bower.json b/resources/flightjs-example-app/components/jasmine-flight/bower.json
new file mode 100644
index 000000000..dd5f09f08
--- /dev/null
+++ b/resources/flightjs-example-app/components/jasmine-flight/bower.json
@@ -0,0 +1,30 @@
+{
+ "name": "jasmine-flight",
+ "version": "1.1.0",
+ "description": "Extensions to the Jasmine test framework for use with Flight",
+ "main": "lib/jasmine-flight.js",
+ "keywords": [
+ "flight",
+ "jasmine",
+ "test"
+ ],
+ "ignore": [
+ ".*",
+ "test",
+ "package.json",
+ "karma.conf.js",
+ "CONTRIBUTING.md",
+ "CHANGELOG.md"
+ ],
+ "devDependencies": {
+ "flight": "~1.0.9"
+ },
+ "gitHead": "5174302b9a87ebdda99de6a436ec2a64200fc5ff",
+ "readme": "# jasmine-flight [](http://travis-ci.org/flightjs/jasmine-flight)\n\nExtensions to the Jasmine test framework for use with [Flight](https://github.com/flightjs/flight)\n\n# Getting started\n\nInclude [jasmine-flight.js](https://raw.github.com/flightjs/jasmine-flight/master/lib/jasmine-flight.js)\nin your app and load it in your test runner.\n\nOr install it with [Bower](http://bower.io/):\n\n```bash\nbower install --save-dev jasmine-flight\n```\n\n**N.B.** jasmine-flight depends on\n[jasmine](https://github.com/pivotal/jasmine) and\n[jasmine-jquery](https://github.com/velesin/jasmine-jquery)\n\n## Components\n\n```javascript\ndescribeComponent('path/to/component', function () {\n beforeEach(function () {\n setupComponent();\n });\n\n it('should do x', function () {\n // a component instance is now accessible as this.component\n // the component root node is attached to the DOM\n // the component root node is also available as this.$node\n });\n});\n```\n\n## Mixins\n\n```javascript\ndescribeMixin('path/to/mixin', function () {\n // initialize the component and attach it to the DOM\n beforeEach(function () {\n setupComponent();\n });\n\n it('should do x', function () {\n expect(this.component.doSomething()).toBe(expected);\n });\n});\n```\n\n## Event spy\n\n```javascript\ndescribeComponent('data/twitter_profile', function () {\n beforeEach(function () {\n setupComponent();\n });\n\n describe('listens for uiNeedsTwitterUserId', function () {\n // was the event triggered?\n it('and triggers dataTwitterUserId', function () {\n var eventSpy = spyOnEvent(document, 'dataTwitterProfile');\n $(document).trigger('uiNeedsTwitterUserId', {\n screen_name: 'tbrd'\n });\n expect(eventSpy).toHaveBeenTriggeredOn(document);\n });\n\n // is the user id correct?\n it('and has correct id', function () {\n var eventSpy = spyOnEvent(document, 'dataTwitterUserId');\n $(document).trigger('uiNeedsTwitteruserId', {\n screen_name: 'tbrd'\n });\n expect(eventSpy.mostRecentCall.data).toEqual({\n screen_name: 'tbrd',\n id: 4149861\n });\n });\n });\n});\n```\n\n## setupComponent\n\n```javascript\nsetupComponent(optionalFixture, optionalOptions);\n```\n\nCalling `setupComponent` twice will create an instance, tear it down and create a new one.\n\n### HTML Fixtures\n\n```javascript\ndescribeComponent('ui/twitter_profile', function () {\n // is the component attached to the fixture?\n it('this.component.$node has class \"foo\"', function () {\n setupComponent('Test');\n expect(this.component.$node).toHaveClass('foo');\n });\n});\n```\n\n### Component Options\n\n```javascript\ndescribeComponent('data/twitter_profile', function () {\n // is the option set correctly?\n it('this.component.attr.baseUrl is set', function () {\n setupComponent({\n baseUrl: 'http://twitter.com/1.1/'\n });\n expect(this.component.attr.baseUrl).toBe('http://twitter.com/1.1/');\n });\n});\n```\n\n# Teardown\n\nComponents are automatically torn down after each test.\n\n## Contributing to this project\n\nAnyone and everyone is welcome to contribute. Please take a moment to\nreview the [guidelines for contributing](CONTRIBUTING.md).\n\n* [Bug reports](CONTRIBUTING.md#bugs)\n* [Feature requests](CONTRIBUTING.md#features)\n* [Pull requests](CONTRIBUTING.md#pull-requests)\n\n## Authors\n\n* [@tbrd](http://github.com/tbrd)\n\n## Thanks\n\n* [@esbie](http://github.com/esbie) and\n [@skilldrick](http://github.com/skilldrick) for creating the original\n `describeComponent` & `describeMixin` methods.\n\n## License\n\nCopyright 2013 Twitter, Inc and other contributors.\n\nLicensed under the MIT License\n",
+ "readmeFilename": "README.md",
+ "_id": "jasmine-flight@1.1.0",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/flightjs/jasmine-flight.git"
+ }
+}
\ No newline at end of file
diff --git a/resources/flightjs-example-app/components/jasmine-flight/lib/jasmine-flight.js b/resources/flightjs-example-app/components/jasmine-flight/lib/jasmine-flight.js
new file mode 100644
index 000000000..8ec76b4e1
--- /dev/null
+++ b/resources/flightjs-example-app/components/jasmine-flight/lib/jasmine-flight.js
@@ -0,0 +1,444 @@
+/**
+ * Copyright 2013, Twitter Inc. and other contributors
+ * Licensed under the MIT License
+ */
+
+(function (root) {
+ 'use strict';
+
+ jasmine.flight = {};
+
+ /**
+ * Wrapper for describe. Load component before each test.
+ *
+ * @param componentPath
+ * @param specDefinitions
+ */
+
+ root.describeComponent = function (componentPath, specDefinitions) {
+ jasmine.getEnv().describeComponent(componentPath, specDefinitions);
+ };
+
+ jasmine.Env.prototype.describeComponent = function (componentPath, specDefinitions) {
+ describe(componentPath, function () {
+ beforeEach(function () {
+ this.Component = this.component = this.$node = null;
+
+ var requireCallback = function (registry, Component) {
+ registry.reset();
+ this.Component = Component;
+ }.bind(this);
+
+ require(['flight/lib/registry', componentPath], requireCallback);
+
+ waitsFor(function () {
+ return this.Component !== null;
+ }.bind(this));
+ });
+
+ afterEach(function () {
+ if (this.$node) {
+ this.$node.remove();
+ this.$node = null;
+ }
+
+ var requireCallback = function (defineComponent) {
+ if (this.component) {
+ this.component = null;
+ }
+
+ this.Component = null;
+ defineComponent.teardownAll();
+ }.bind(this);
+
+ require(['flight/lib/component'], requireCallback);
+
+ waitsFor(function () {
+ return this.Component === null;
+ }.bind(this));
+ });
+
+ specDefinitions.apply(this);
+ });
+ };
+
+ /**
+ * Wrapper for describe. Load mixin before each test.
+ *
+ * @param mixinPath
+ * @param specDefinitions
+ */
+
+ root.describeMixin = function (mixinPath, specDefinitions) {
+ jasmine.getEnv().describeMixin(mixinPath, specDefinitions);
+ };
+
+ jasmine.Env.prototype.describeMixin = function (mixinPath, specDefinitions) {
+ describe(mixinPath, function () {
+ beforeEach(function () {
+ this.Component = this.component = this.$node = null;
+
+ var requireCallback = function (registry, defineComponent, Mixin) {
+ registry.reset();
+ this.Component = defineComponent(function () {}, Mixin);
+ }.bind(this);
+
+ require(['flight/lib/registry', 'flight/lib/component', mixinPath], requireCallback);
+
+ waitsFor(function () {
+ return this.Component !== null;
+ });
+ });
+
+ afterEach(function () {
+ if (this.$node) {
+ this.$node.remove();
+ this.$node = null;
+ }
+
+ var requireCallback = function (defineComponent) {
+ if (this.component) {
+ this.component = null;
+ }
+
+ this.Component = null;
+ defineComponent.teardownAll();
+ }.bind(this);
+
+ require(['flight/lib/component'], requireCallback);
+
+ waitsFor(function () {
+ return this.Component === null;
+ }.bind(this));
+ });
+
+ specDefinitions.apply(this);
+ });
+ };
+
+ /**
+ * Wrapper for describe. Load module before each test.
+ *
+ * @param modulePath
+ * @param specDefinitions
+ */
+
+ root.describeModule = function (modulePath, specDefinitions) {
+ return jasmine.getEnv().describeModule(modulePath, specDefinitions);
+ };
+
+ jasmine.Env.prototype.describeModule = function (modulePath, specDefinitions) {
+ describe(modulePath, function () {
+ beforeEach(function () {
+ this.module = null;
+
+ var requireCallback = function (module) {
+ this.module = module;
+ }.bind(this);
+
+ require([modulePath], requireCallback);
+
+ waitsFor(function () {
+ return this.module !== null;
+ });
+ });
+
+ specDefinitions.apply(this);
+ });
+ };
+
+ /**
+ * Create root node and initialize component. Fixture should be html string
+ * or jQuery object.
+ *
+ * @param fixture {String} (Optional)
+ * @param options {Options} (Optional)
+ */
+
+ root.setupComponent = function (fixture, options) {
+ jasmine.getEnv().currentSpec.setupComponent(fixture, options);
+ };
+
+ jasmine.Spec.prototype.setupComponent = function (fixture, options) {
+ if (this.component) {
+ this.component.teardown();
+ this.$node.remove();
+ }
+
+ this.$node = $('');
+ $('body').append(this.$node);
+
+ if (fixture instanceof jQuery || typeof fixture === 'string') {
+ this.$node.append(fixture);
+ } else {
+ options = fixture;
+ fixture = null;
+ }
+
+ options = options === undefined ? {} : options;
+
+ this.component = new this.Component(this.$node, options);
+ };
+
+
+ (function (namespace) {
+ var eventsData = {
+ spiedEvents: {},
+ handlers: []
+ };
+
+ namespace.formatElement = function ($element) {
+ var limit = 200;
+ var output = '';
+
+ if ($element instanceof jQuery) {
+ output = jasmine.JQuery.elementToString($element);
+ if (output.length > limit) {
+ output = output.slice(0, 200) + '...';
+ }
+ } else {
+ //$element should always be a jQuery object
+ output = 'element is not a jQuery object';
+ }
+
+ return output;
+ };
+
+ namespace.compareColors = function (color1, color2) {
+ if (color1.charAt(0) === color2.charAt(0)) {
+ return color1 === color2;
+ } else {
+ return namespace.hex2rgb(color1) === namespace.hex2rgb(color2);
+ }
+ };
+
+ namespace.hex2rgb = function (colorString) {
+ if (colorString.charAt(0) !== '#') return colorString;
+ // note: hexStr should be #rrggbb
+ var hex = parseInt(colorString.substring(1), 16);
+ var r = (hex & 0xff0000) >> 16;
+ var g = (hex & 0x00ff00) >> 8;
+ var b = hex & 0x0000ff;
+ return 'rgb(' + r + ', ' + g + ', ' + b + ')';
+ };
+
+ namespace.events = {
+ spyOn: function (selector, eventName) {
+ eventsData.spiedEvents[[selector, eventName]] = {
+ callCount: 0,
+ calls: [],
+ mostRecentCall: {},
+ name: eventName
+ };
+
+ var handler = function (e, data) {
+ var call = {
+ event: e,
+ args: jasmine.util.argsToArray(arguments),
+ data: data
+ };
+ eventsData.spiedEvents[[selector, eventName]].callCount++;
+ eventsData.spiedEvents[[selector, eventName]].calls.push(call);
+ eventsData.spiedEvents[[selector, eventName]].mostRecentCall = call;
+ };
+
+ jQuery(selector).on(eventName, handler);
+ eventsData.handlers.push(handler);
+ return eventsData.spiedEvents[[selector, eventName]];
+ },
+
+ eventArgs: function (selector, eventName, expectedArg) {
+ var actualArgs = eventsData.spiedEvents[[selector, eventName]].mostRecentCall.args;
+
+ if (!actualArgs) {
+ throw 'No event spy found on ' + eventName + '. Try adding a call to spyOnEvent or make sure that the selector the event is triggered on and the selector being spied on are correct.';
+ }
+
+ // remove extra event metadata if it is not tested for
+ if ((actualArgs.length === 2) && typeof actualArgs[1] === 'object' &&
+ expectedArg && !expectedArg.scribeContext && !expectedArg.sourceEventData && !expectedArg.scribeData) {
+ actualArgs[1] = $.extend({}, actualArgs[1]);
+ delete actualArgs[1].sourceEventData;
+ delete actualArgs[1].scribeContext;
+ delete actualArgs[1].scribeData;
+ }
+
+ return actualArgs;
+ },
+
+ wasTriggered: function (selector, event) {
+ var spiedEvent = eventsData.spiedEvents[[selector, event]];
+ return spiedEvent && spiedEvent.callCount > 0;
+ },
+
+ wasTriggeredWith: function (selector, eventName, expectedArg, env) {
+ var actualArgs = jasmine.flight.events.eventArgs(selector, eventName, expectedArg);
+ return actualArgs && env.contains_(actualArgs, expectedArg);
+ },
+
+ wasTriggeredWithData: function (selector, eventName, expectedArg, env) {
+ var actualArgs = jasmine.flight.events.eventArgs(selector, eventName, expectedArg);
+ var valid;
+
+ if (actualArgs) {
+ valid = false;
+ for (var i = 0; i < actualArgs.length; i++) {
+ if (jasmine.flight.validateHash(expectedArg, actualArgs[i])) {
+ return true;
+ }
+ }
+ return valid;
+ }
+
+ return false;
+ },
+
+ cleanUp: function () {
+ eventsData.spiedEvents = {};
+ eventsData.handlers = [];
+ }
+ };
+
+ namespace.validateHash = function (a, b, intersection) {
+ var validHash;
+ for (var field in a) {
+ if ((typeof a[field] === 'object') && (typeof b[field] === 'object')) {
+ validHash = jasmine.flight.validateHash(a[field], b[field]);
+ } else if (intersection && (typeof a[field] === 'undefined' || typeof b[field] === 'undefined')) {
+ validHash = true;
+ } else {
+ validHash = (a[field] === b[field]);
+ }
+ if (!validHash) {
+ break;
+ }
+ }
+ return validHash;
+ };
+ })(jasmine.flight);
+
+ beforeEach(function () {
+ this.addMatchers({
+ toHaveBeenTriggeredOn: function () {
+ var selector = arguments[0];
+ var eventName = typeof this.actual === 'string' ? this.actual : this.actual.name;
+ var wasTriggered = jasmine.flight.events.wasTriggered(selector, eventName);
+
+ this.message = function () {
+ var $pp = function (obj) {
+ var description;
+ var attr;
+
+ if (!(obj instanceof jQuery)) {
+ obj = $(obj);
+ }
+
+ description = [
+ obj.get(0).nodeName
+ ];
+
+ attr = obj.get(0).attributes || [];
+
+ for (var x = 0; x < attr.length; x++) {
+ description.push(attr[x].name + '="' + attr[x].value + '"');
+ }
+
+ return '<' + description.join(' ') + '>';
+ };
+
+ if (wasTriggered) {
+ return [
+ '| t |