[Pkg-javascript-commits] [node-q] 01/20: Imported Upstream version 1.0.0

Sebastiaan Couwenberg sebastic at moszumanska.debian.org
Sun Mar 1 12:36:26 UTC 2015


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch master
in repository node-q.

commit 06a79decf4dfea79d500209c3c769cc6631f0846
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Sat Feb 28 22:33:02 2015 +0100

    Imported Upstream version 1.0.0
---
 .coverignore                                     |    1 +
 .gitignore                                       |   10 +
 .jshintrc                                        |   26 +
 .mailmap                                         |    2 +
 .npmignore                                       |   23 +
 .travis.yml                                      |    7 +
 CHANGES.md                                       |  722 ++++++
 CONTRIBUTING.md                                  |   40 +
 Gruntfile.js                                     |   16 +
 LICENSE                                          |   19 +
 README.md                                        |  813 +++++++
 VERSIONS.md                                      |   18 +
 benchmark/compare-with-callbacks.js              |   71 +
 benchmark/scenarios.js                           |   36 +
 design/README.js                                 | 1026 +++++++++
 design/q0.js                                     |   22 +
 design/q1.js                                     |   26 +
 design/q2.js                                     |   33 +
 design/q3.js                                     |   30 +
 design/q4.js                                     |   48 +
 design/q5.js                                     |   56 +
 design/q6.js                                     |   64 +
 design/q7.js                                     |  126 ++
 examples/all.js                                  |   21 +
 examples/async-generators/0.html                 |   36 +
 examples/async-generators/1-return.js            |   19 +
 examples/async-generators/2-error-propagation.js |   21 +
 examples/async-generators/3-spawn.js             |   21 +
 examples/async-generators/4-flow-control.js      |   42 +
 examples/async-generators/README.md              |   66 +
 package.json                                     |   74 +
 q.js                                             | 1935 +++++++++++++++++
 queue.js                                         |   35 +
 ref_send.md                                      |   26 +
 spec/aplus-adapter.js                            |   15 +
 spec/lib/jasmine-1.2.0/MIT.LICENSE               |   20 +
 spec/lib/jasmine-1.2.0/jasmine-html.js           |  616 ++++++
 spec/lib/jasmine-1.2.0/jasmine.css               |   81 +
 spec/lib/jasmine-1.2.0/jasmine.js                | 2529 ++++++++++++++++++++++
 spec/lib/jasmine-promise.js                      |   91 +
 spec/q-spec.html                                 |   52 +
 spec/q-spec.js                                   | 2490 +++++++++++++++++++++
 spec/queue-spec.js                               |  180 ++
 43 files changed, 11605 insertions(+)

diff --git a/.coverignore b/.coverignore
new file mode 100644
index 0000000..1564ca0
--- /dev/null
+++ b/.coverignore
@@ -0,0 +1 @@
+spec/
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..db6189f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+node_modules
+npm-debug.log
+CHANGES.html
+README.html
+.tmp
+q.min.js
+
+.coverage_data/
+.coverage_debug/
+cover_html/
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..5382c9b
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,26 @@
+{
+    "browser": true,
+    "node": true,
+
+    "curly": true,
+    "eqeqeq": true,
+    "es3": true,
+    "newcap": false,
+    "noarg": true,
+    "nonew": true,
+    "quotmark": "double",
+    "strict": true,
+    "trailing": true,
+    "undef": true,
+    "unused": true,
+
+    "globals": {
+        "bootstrap": false,
+        "cajaVM": false,
+        "define": false,
+        "ReturnValue": false,
+        "ses": false,
+        "setImmediate": false,
+        "Q": true
+    }
+}
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 0000000..281850a
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,2 @@
+Domenic Denicola <domenic at domenicdenicola.com>
+Kris Kowal <kris.kowal at cixar.com>
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..b89b4e7
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,23 @@
+node_modules
+npm-debug.log
+CHANGES.html
+README.html
+
+.coverage_data/
+.coverage_debug/
+.tmp
+cover_html/
+
+design
+examples
+spec
+.coverignore
+.jshintrc
+.mailmap
+.npmignore
+.travis.yml
+CHANGES.md
+VERSIONS.md
+q.min.js
+ref_send.md
+Gruntfile.js
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..0eeb53f
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,7 @@
+language: node_js
+node_js:
+  - "0.6"
+  - "0.8"
+  - "0.10"
+script:
+  npm run lint && npm test
diff --git a/CHANGES.md b/CHANGES.md
new file mode 100644
index 0000000..f10fcc8
--- /dev/null
+++ b/CHANGES.md
@@ -0,0 +1,722 @@
+<!-- vim:ts=4:sts=4:sw=4:et:tw=70 -->
+
+## 1.0.0
+
+:cake: This is all but a re-release of version 0.9, which has settled
+into a gentle maintenance mode and rightly deserves an official 1.0.
+An ambitious 2.0 release is already around the corner, but 0.9/1.0
+have been distributed far and wide and demand long term support.
+
+ - Q will now attempt to post a debug message in browsers regardless
+   of whether window.Touch is defined. Chrome at least now has this
+   property regardless of whether touch is supported by the underlying
+   hardware.
+ - Remove deprecation warning from `promise.valueOf`. The function is
+   called by the browser in various ways so there is no way to
+   distinguish usage that should be migrated from usage that cannot be
+   altered.
+
+## 0.9.7
+
+ - :warning: `q.min.js` is no longer checked-in.  It is however still
+   created by Grunt and NPM.
+ - Fixes a bug that inhibited `Q.async` with implementations of the new
+   ES6 generators.
+ - Fixes a bug with `nextTick` affecting Safari 6.0.5 the first time a
+   page loads when an `iframe` is involved.
+ - Introduces `passByCopy`, `join`, and `race`.
+ - Shows stack traces or error messages on the console, instead of
+   `Error` objects.
+ - Elimintates wrapper methods for improved performance.
+ - `Q.all` now propagates progress notifications of the form you might
+   expect of ES6 iterations, `{value, index}` where the `value` is the
+   progress notification from the promise at `index`.
+
+## 0.9.6
+
+ - Fixes a bug in recognizing the difference between compatible Q
+   promises, and Q promises from before the implementation of "inspect".
+   The latter are now coerced.
+ - Fixes an infinite asynchronous coercion cycle introduced by former
+   solution, in two independently sufficient ways.  1.) All promises
+   returned by makePromise now implement "inspect", albeit a default
+   that reports that the promise has an "unknown" state.  2.) The
+   implementation of "then/when" is now in "then" instead of "when", so
+   that the responsibility to "coerce" the given promise rests solely in
+   the "when" method and the "then" method may assume that "this" is a
+   promise of the right type.
+ - Refactors `nextTick` to use an unrolled microtask within Q regardless
+   of how new ticks a requested. #316 @rkatic
+
+## 0.9.5
+
+ - Introduces `inspect` for getting the state of a promise as
+   `{state: "fulfilled" | "rejected" | "pending", value | reason}`.
+ - Introduces `allSettled` which produces an array of promises states
+   for the input promises once they have all "settled".  This is in
+   accordance with a discussion on Promises/A+ that "settled" refers to
+   a promise that is "fulfilled" or "rejected".  "resolved" refers to a
+   deferred promise that has been "resolved" to another promise,
+   "sealing its fate" to the fate of the successor promise.
+ - Long stack traces are now off by default.  Set `Q.longStackSupport`
+   to true to enable long stack traces.
+ - Long stack traces can now follow the entire asynchronous history of a
+   promise, not just a single jump.
+ - Introduces `spawn` for an immediately invoked asychronous generator.
+   @jlongster
+ - Support for *experimental* synonyms `mapply`, `mcall`, `nmapply`,
+   `nmcall` for method invocation.
+
+## 0.9.4
+
+ - `isPromise` and `isPromiseAlike` now always returns a boolean 
+   (even for falsy values). #284 @lfac-pt
+ - Support for ES6 Generators in `async` #288 @andywingo
+ - Clear duplicate promise rejections from dispatch methods #238 @SLaks
+ - Unhandled rejection API #296 @domenic
+   `stopUnhandledRejectionTracking`, `getUnhandledReasons`,
+   `resetUnhandledRejections`.
+
+## 0.9.3
+
+ - Add the ability to give `Q.timeout`'s errors a custom error message. #270
+   @jgrenon
+ - Fix Q's call-stack busting behavior in Node.js 0.10, by switching from
+   `process.nextTick` to `setImmediate`. #254 #259
+ - Fix Q's behavior when used with the Mocha test runner in the browser, since
+   Mocha introduces a fake `process` global without a `nextTick` property. #267
+ - Fix some, but not all, cases wherein Q would give false positives in its
+   unhandled rejection detection (#252). A fix for other cases (#238) is
+   hopefully coming soon.
+ - Made `Q.promise` throw early if given a non-function.
+
+## 0.9.2
+
+ - Pass through progress notifications when using `timeout`. #229 @omares
+ - Pass through progress notifications when using `delay`.
+ - Fix `nbind` to actually bind the `thisArg`. #232 @davidpadbury
+
+## 0.9.1
+
+ - Made the AMD detection compatible with the RequireJS optimizer's `namespace`
+   option. #225 @terinjokes
+ - Fix side effects from `valueOf`, and thus from `isFulfilled`, `isRejected`,
+   and `isPending`. #226 @benjamn
+
+## 0.9.0
+
+This release removes many layers of deprecated methods and brings Q closer to
+alignment with Mark Miller’s TC39 [strawman][] for concurrency. At the same
+time, it fixes many bugs and adds a few features around error handling. Finally,
+it comes with an updated and comprehensive [API Reference][].
+
+[strawman]: http://wiki.ecmascript.org/doku.php?id=strawman:concurrency
+[API Reference]: https://github.com/kriskowal/q/wiki/API-Reference
+
+### API Cleanup
+
+The following deprecated or undocumented methods have been removed.
+Their replacements are listed here:
+
+<table>
+   <thead>
+      <tr>
+         <th>0.8.x method</th>
+         <th>0.9 replacement</th>
+      </tr>
+   </thead>
+   <tbody>
+      <tr>
+         <td><code>Q.ref</code></td>
+         <td><code>Q</code></td>
+      </tr>
+      <tr>
+         <td><code>call</code>, <code>apply</code>, <code>bind</code> (*)</td>
+         <td><code>fcall</code>/<code>invoke</code>, <code>fapply</code>/<code>post</code>, <code>fbind</code></td>
+      </tr>
+      <tr>
+         <td><code>ncall</code>, <code>napply</code> (*)</td>
+         <td><code>nfcall</code>/<code>ninvoke</code>, <code>nfapply</code>/<code>npost</code></td>
+      </tr>
+      <tr>
+         <td><code>end</code></td>
+         <td><code>done</code></td>
+      </tr>
+      <tr>
+         <td><code>put</code></td>
+         <td><code>set</code></td>
+      </tr>
+      <tr>
+         <td><code>node</code></td>
+         <td><code>nbind</code></td>
+      </tr>
+      <tr>
+         <td><code>nend</code></td>
+         <td><code>nodeify</code></td>
+      </tr>
+      <tr>
+         <td><code>isResolved</code></td>
+         <td><code>isPending</code></td>
+      </tr>
+      <tr>
+         <td><code>deferred.node</code></td>
+         <td><code>deferred.makeNodeResolver</code></td>
+      </tr>
+      <tr>
+         <td><code>Method</code>, <code>sender</code></td>
+         <td><code>dispatcher</code></td>
+      </tr>
+      <tr>
+         <td><code>send</code></td>
+         <td><code>dispatch</code></td>
+      </tr>
+      <tr>
+         <td><code>view</code>, <code>viewInfo</code></td>
+         <td>(none)</td>
+      </tr>
+   </tbody>
+</table>
+
+
+(*) Use of ``thisp`` is discouraged. For calling methods, use ``post`` or
+``invoke``.
+
+### Alignment with the Concurrency Strawman
+
+-   Q now exports a `Q(value)` function, an alias for `resolve`.
+    `Q.call`, `Q.apply`, and `Q.bind` were removed to make room for the
+    same methods on the function prototype.
+-   `invoke` has been aliased to `send` in all its forms.
+-   `post` with no method name acts like `fapply`.
+
+### Error Handling
+
+-   Long stack traces can be turned off by setting `Q.stackJumpLimit` to zero.
+    In the future, this property will be used to fine tune how many stack jumps
+    are retained in long stack traces; for now, anything nonzero is treated as
+    one (since Q only tracks one stack jump at the moment, see #144). #168
+-   In Node.js, if there are unhandled rejections when the process exits, they
+    are output to the console. #115
+
+### Other
+
+-   `delete` and `set` (née `put`) no longer have a fulfillment value.
+-   Q promises are no longer frozen, which
+    [helps with performance](http://code.google.com/p/v8/issues/detail?id=1858).
+-   `thenReject` is now included, as a counterpart to `thenResolve`.
+-   The included browser `nextTick` shim is now faster. #195 @rkatic.
+
+### Bug Fixes
+
+-   Q now works in Internet Explorer 10. #186 @ForbesLindesay
+-   `fbind` no longer hard-binds the returned function's `this` to `undefined`.
+    #202
+-   `Q.reject` no longer leaks memory. #148
+-   `npost` with no arguments now works. #207
+-   `allResolved` now works with non-Q promises ("thenables"). #179
+-   `keys` behavior is now correct even in browsers without native
+    `Object.keys`. #192 @rkatic
+-   `isRejected` and the `exception` property now work correctly if the
+    rejection reason is falsy. #198
+
+### Internals and Advanced
+
+-   The internal interface for a promise now uses
+    `dispatchPromise(resolve, op, operands)` instead of `sendPromise(op,
+    resolve, ...operands)`, which reduces the cases where Q needs to do
+    argument slicing.
+-   The internal protocol uses different operands. "put" is now "set".
+    "del" is now "delete". "view" and "viewInfo" have been removed.
+-   `Q.fulfill` has been added. It is distinct from `Q.resolve` in that
+    it does not pass promises through, nor coerces promises from other
+    systems. The promise becomes the fulfillment value. This is only
+    recommended for use when trying to fulfill a promise with an object that has
+    a `then` function that is at the same time not a promise.
+
+## 0.8.12
+- Treat foreign promises as unresolved in `Q.isFulfilled`; this lets `Q.all`
+  work on arrays containing foreign promises. #154
+- Fix minor incompliances with the [Promises/A+ spec][] and [test suite][]. #157
+  #158
+
+[Promises/A+ spec]: http://promises-aplus.github.com/promises-spec/
+[test suite]: https://github.com/promises-aplus/promises-tests
+
+## 0.8.11
+
+ - Added ``nfcall``, ``nfapply``, and ``nfbind`` as ``thisp``-less versions of
+   ``ncall``, ``napply``, and ``nbind``. The latter are now deprecated. #142
+ - Long stack traces no longer cause linearly-growing memory usage when chaining
+   promises together. #111
+ - Inspecting ``error.stack`` in a rejection handler will now give a long stack
+   trace. #103
+ - Fixed ``Q.timeout`` to clear its timeout handle when the promise is rejected;
+   previously, it kept the event loop alive until the timeout period expired.
+   #145 @dfilatov
+ - Added `q/queue` module, which exports an infinite promise queue
+   constructor.
+
+## 0.8.10
+
+ - Added ``done`` as a replacement for ``end``, taking the usual fulfillment,
+   rejection, and progress handlers. It's essentially equivalent to
+   ``then(f, r, p).end()``.
+ - Added ``Q.onerror``, a settable error trap that you can use to get full stack
+   traces for uncaught errors. #94
+ - Added ``thenResolve`` as a shortcut for returning a constant value once a
+   promise is fulfilled. #108 @ForbesLindesay
+ - Various tweaks to progress notification, including propagation and
+   transformation of progress values and only forwarding a single progress
+   object.
+ - Renamed ``nend`` to ``nodeify``. It no longer returns an always-fulfilled
+   promise when a Node callback is passed.
+ - ``deferred.resolve`` and ``deferred.reject`` no longer (sometimes) return
+   ``deferred.promise``.
+ - Fixed stack traces getting mangled if they hit ``end`` twice. #116 #121 @ef4
+ - Fixed ``ninvoke`` and ``npost`` to work on promises for objects with Node
+   methods. #134
+ - Fixed accidental coercion of objects with nontrivial ``valueOf`` methods,
+   like ``Date``s, by the promise's ``valueOf`` method. #135
+ - Fixed ``spread`` not calling the passed rejection handler if given a rejected
+   promise.
+
+## 0.8.9
+
+ - Added ``nend``
+ - Added preliminary progress notification support, via
+   ``promise.then(onFulfilled, onRejected, onProgress)``,
+   ``promise.progress(onProgress)``, and ``deferred.notify(...progressData)``.
+ - Made ``put`` and ``del`` return the object acted upon for easier chaining.
+   #84
+ - Fixed coercion cycles with cooperating promises. #106
+
+## 0.8.7
+
+ - Support [Montage Require](http://github.com/kriskowal/mr)
+
+## 0.8.6
+
+ - Fixed ``npost`` and ``ninvoke`` to pass the correct ``thisp``. #74
+ - Fixed various cases involving unorthodox rejection reasons. #73 #90
+   @ef4
+ - Fixed double-resolving of misbehaved custom promises. #75
+ - Sped up ``Q.all`` for arrays contain already-resolved promises or scalar
+   values. @ForbesLindesay
+ - Made stack trace filtering work when concatenating assets. #93 @ef4
+ - Added warnings for deprecated methods. @ForbesLindesay
+ - Added ``.npmignore`` file so that dependent packages get a slimmer
+   ``node_modules`` directory.
+
+## 0.8.5
+
+ - Added preliminary support for long traces (@domenic)
+ - Added ``fapply``, ``fcall``, ``fbind`` for non-thisp
+   promised function calls.
+ - Added ``return`` for async generators, where generators
+   are implemented.
+ - Rejected promises now have an "exception" property.  If an object
+   isRejected(object), then object.valueOf().exception will
+   be the wrapped error.
+ - Added Jasmine specifications
+ - Support Internet Explorers 7–9 (with multiple bug fixes @domenic)
+ - Support Firefox 12
+ - Support Safari 5.1.5
+ - Support Chrome 18
+
+## 0.8.4
+
+ - WARNING: ``promise.timeout`` is now rejected with an ``Error`` object
+   and the message now includes the duration of the timeout in
+   miliseconds.  This doesn't constitute (in my opinion) a
+   backward-incompatibility since it is a change of an undocumented and
+   unspecified public behavior, but if you happened to depend on the
+   exception being a string, you will need to revise your code.
+ - Added ``deferred.makeNodeResolver()`` to replace the more cryptic
+   ``deferred.node()`` method.
+ - Added experimental ``Q.promise(maker(resolve, reject))`` to make a
+   promise inside a callback, such that thrown exceptions in the
+   callback are converted and the resolver and rejecter are arguments.
+   This is a shorthand for making a deferred directly and inspired by
+   @gozala’s stream constructor pattern and the Microsoft Windows Metro
+   Promise constructor interface.
+ - Added experimental ``Q.begin()`` that is intended to kick off chains
+   of ``.then`` so that each of these can be reordered without having to
+   edit the new and former first step.
+
+## 0.8.3
+
+ - Added ``isFulfilled``, ``isRejected``, and ``isResolved``
+   to the promise prototype.
+ - Added ``allResolved`` for waiting for every promise to either be
+   fulfilled or rejected, without propagating an error. @utvara #53
+ - Added ``Q.bind`` as a method to transform functions that
+   return and throw into promise-returning functions. See
+   [an example](https://gist.github.com/1782808). @domenic
+ - Renamed ``node`` export to ``nbind``, and added ``napply`` to
+   complete the set. ``node`` remains as deprecated. @domenic #58
+ - Renamed ``Method`` export to ``sender``.  ``Method``
+   remains as deprecated and will be removed in the next
+   major version since I expect it has very little usage.
+ - Added browser console message indicating a live list of
+   unhandled errors.
+ - Added support for ``msSetImmediate`` (IE10) or ``setImmediate``
+   (available via [polyfill](https://github.com/NobleJS/setImmediate))
+   as a browser-side ``nextTick`` implementation. #44 #50 #59
+ - Stopped using the event-queue dependency, which was in place for
+   Narwhal support: now directly using ``process.nextTick``.
+ - WARNING: EXPERIMENTAL: added ``finally`` alias for ``fin``, ``catch``
+   alias for ``fail``, ``try`` alias for ``call``, and ``delete`` alias
+   for ``del``.  These properties are enquoted in the library for
+   cross-browser compatibility, but may be used as property names in
+   modern engines.
+
+## 0.8.2
+
+ - Deprecated ``ref`` in favor of ``resolve`` as recommended by
+   @domenic.
+ - Update event-queue dependency.
+
+## 0.8.1
+
+ - Fixed Opera bug. #35 @cadorn
+ - Fixed ``Q.all([])`` #32 @domenic
+
+## 0.8.0
+
+ - WARNING: ``enqueue`` removed.  Use ``nextTick`` instead.
+   This is more consistent with NodeJS and (subjectively)
+   more explicit and intuitive.
+ - WARNING: ``def`` removed.  Use ``master`` instead.  The
+   term ``def`` was too confusing to new users.
+ - WARNING: ``spy`` removed in favor of ``fin``.
+ - WARNING: ``wait`` removed. Do ``all(args).get(0)`` instead.
+ - WARNING: ``join`` removed. Do ``all(args).spread(callback)`` instead.
+ - WARNING: Removed the ``Q`` function module.exports alias
+   for ``Q.ref``. It conflicts with ``Q.apply`` in weird
+   ways, making it uncallable.
+ - Revised ``delay`` so that it accepts both ``(value,
+   timeout)`` and ``(timeout)`` variations based on
+   arguments length.
+ - Added ``ref().spread(cb(...args))``, a variant of
+   ``then`` that spreads an array across multiple arguments.
+   Useful with ``all()``.
+ - Added ``defer().node()`` Node callback generator.  The
+   callback accepts ``(error, value)`` or ``(error,
+   ...values)``.  For multiple value arguments, the
+   fulfillment value is an array, useful in conjunction with
+   ``spread``.
+ - Added ``node`` and ``ncall``, both with the signature
+   ``(fun, thisp_opt, ...args)``.  The former is a decorator
+   and the latter calls immediately.  ``node`` optional
+   binds and partially applies.  ``ncall`` can bind and pass
+   arguments.
+
+## 0.7.2
+
+ - Fixed thenable promise assimilation.
+
+## 0.7.1
+
+ - Stopped shimming ``Array.prototype.reduce``. The
+   enumerable property has bad side-effects.  Libraries that
+   depend on this (for example, QQ) will need to be revised.
+
+## 0.7.0 - BACKWARD INCOMPATIBILITY
+
+ - WARNING: Removed ``report`` and ``asap``
+ - WARNING: The ``callback`` argument of the ``fin``
+   function no longer receives any arguments. Thus, it can
+   be used to call functions that should not receive
+   arguments on resolution.  Use ``when``, ``then``, or
+   ``fail`` if you need a value.
+ - IMPORTANT: Fixed a bug in the use of ``MessageChannel``
+   for ``nextTick``.
+ - Renamed ``enqueue`` to ``nextTick``.
+ - Added experimental ``view`` and ``viewInfo`` for creating
+   views of promises either when or before they're
+   fulfilled.
+ - Shims are now externally applied so subsequent scripts or
+   dependees can use them.
+ - Improved minification results.
+ - Improved readability.
+
+## 0.6.0 - BACKWARD INCOMPATIBILITY
+
+ - WARNING: In practice, the implementation of ``spy`` and
+   the name ``fin`` were useful.  I've removed the old
+   ``fin`` implementation and renamed/aliased ``spy``.
+ - The "q" module now exports its ``ref`` function as a "Q"
+   constructor, with module systems that support exports
+   assignment including NodeJS, RequireJS, and when used as
+   a ``<script>`` tag. Notably, strictly compliant CommonJS
+   does not support this, but UncommonJS does.
+ - Added ``async`` decorator for generators that use yield
+   to "trampoline" promises. In engines that support
+   generators (SpiderMonkey), this will greatly reduce the
+   need for nested callbacks.
+ - Made ``when`` chainable.
+ - Made ``all`` chainable.
+
+## 0.5.3
+
+ - Added ``all`` and refactored ``join`` and ``wait`` to use
+   it.  All of these will now reject at the earliest
+   rejection.
+
+## 0.5.2
+
+ - Minor improvement to ``spy``; now waits for resolution of
+   callback promise.
+
+## 0.5.1
+
+ - Made most Q API methods chainable on promise objects, and
+   turned the previous promise-methods of ``join``,
+   ``wait``, and ``report`` into Q API methods.
+ - Added ``apply`` and ``call`` to the Q API, and ``apply``
+   as a promise handler.
+ - Added ``fail``, ``fin``, and ``spy`` to Q and the promise
+   prototype for convenience when observing rejection,
+   fulfillment and rejection, or just observing without
+   affecting the resolution.
+ - Renamed ``def`` (although ``def`` remains shimmed until
+   the next major release) to ``master``.
+ - Switched to using ``MessageChannel`` for next tick task
+   enqueue in browsers that support it.
+
+## 0.5.0 - MINOR BACKWARD INCOMPATIBILITY
+
+ - Exceptions are no longer reported when consumed.
+ - Removed ``error`` from the API.  Since exceptions are
+   getting consumed, throwing them in an errback causes the
+   exception to silently disappear.  Use ``end``.
+ - Added ``end`` as both an API method and a promise-chain
+   ending method.  It causes propagated rejections to be
+   thrown, which allows Node to write stack traces and
+   emit ``uncaughtException`` events, and browsers to
+   likewise emit ``onerror`` and log to the console.
+ - Added ``join`` and ``wait`` as promise chain functions,
+   so you can wait for variadic promises, returning your own
+   promise back, or join variadic promises, resolving with a
+   callback that receives variadic fulfillment values.
+
+## 0.4.4
+
+ - ``end`` no longer returns a promise. It is the end of the
+   promise chain.
+ - Stopped reporting thrown exceptions in ``when`` callbacks
+   and errbacks.  These must be explicitly reported through
+   ``.end()``, ``.then(null, Q.error)``, or some other
+   mechanism.
+ - Added ``report`` as an API method, which can be used as
+   an errback to report and propagate an error.
+ - Added ``report`` as a promise-chain method, so an error
+   can be reported if it passes such a gate.
+
+## 0.4.3
+
+ - Fixed ``<script>`` support that regressed with 0.4.2
+   because of "use strict" in the module system
+   multi-plexer.
+
+## 0.4.2
+
+ - Added support for RequireJS (jburke)
+
+## 0.4.1
+
+ - Added an "end" method to the promise prototype,
+   as a shorthand for waiting for the promise to
+   be resolved gracefully, and failing to do so,
+   to dump an error message.
+
+## 0.4.0 - BACKWARD INCOMPATIBLE*
+
+ - *Removed the utility modules. NPM and Node no longer
+   expose any module except the main module.  These have
+   been moved and merged into the "qq" package.
+ - *In a non-CommonJS browser, q.js can be used as a script.
+   It now creates a Q global variable.
+ - Fixed thenable assimilation.
+ - Fixed some issues with asap, when it resolves to
+   undefined, or throws an exception.
+
+## 0.3.0 - BACKWARD-INCOMPATIBLE
+
+ - The `post` method has been reverted to its original
+   signature, as provided in Tyler Close's `ref_send` API.
+   That is, `post` accepts two arguments, the second of
+   which is an arbitrary object, but usually invocation
+   arguments as an `Array`.  To provide variadic arguments
+   to `post`, there is a new `invoke` function that posts
+   the variadic arguments to the value given in the first
+   argument.
+ - The `defined` method has been moved from `q` to `q/util`
+   since it gets no use in practice but is still
+   theoretically useful.
+ - The `Promise` constructor has been renamed to
+   `makePromise` to be consistent with the convention that
+   functions that do not require the `new` keyword to be
+   used as constructors have camelCase names.
+ - The `isResolved` function has been renamed to
+   `isFulfilled`.  There is a new `isResolved` function that
+   indicates whether a value is not a promise or, if it is a
+   promise, whether it has been either fulfilled or
+   rejected.  The code has been revised to reflect this
+   nuance in terminology.
+
+## 0.2.10
+
+ - Added `join` to `"q/util"` for variadically joining
+   multiple promises.
+
+## 0.2.9
+
+ - The future-compatible `invoke` method has been added,
+   to replace `post`, since `post` will become backward-
+   incompatible in the next major release.
+ - Exceptions thrown in the callbacks of a `when` call are
+   now emitted to Node's `"uncaughtException"` `process`
+   event in addition to being returned as a rejection reason.
+
+## 0.2.8
+
+ - Exceptions thrown in the callbacks of a `when` call
+   are now consumed, warned, and transformed into
+   rejections of the promise returned by `when`.
+
+## 0.2.7
+
+ - Fixed a minor bug in thenable assimilation, regressed
+   because of the change in the forwarding protocol.
+ - Fixed behavior of "q/util" `deep` method on dates and
+   other primitives. Github issue #11.
+
+## 0.2.6
+
+ - Thenables (objects with a "then" method) are accepted
+   and provided, bringing this implementation of Q
+   into conformance with Promises/A, B, and D.
+ - Added `makePromise`, to replace the `Promise` function
+   eventually.
+ - Rejections are now also duck-typed. A rejection is a
+   promise with a valueOf method that returns a rejection
+   descriptor. A rejection descriptor has a
+   "promiseRejected" property equal to "true" and a
+   "reason" property corresponding to the rejection reason.
+ - Altered the `makePromise` API such that the `fallback`
+   method no longer receives a superfluous `resolved` method
+   after the `operator`.  The fallback method is responsible
+   only for returning a resolution.  This breaks an
+   undocumented API, so third-party API's depending on the
+   previous undocumented behavior may break.
+
+## 0.2.5
+
+ - Changed promises into a duck-type such that multiple
+   instances of the Q module can exchange promise objects.
+   A promise is now defined as "an object that implements the
+   `promiseSend(op, resolved, ...)` method and `valueOf`".
+ - Exceptions in promises are now captured and returned
+   as rejections.
+
+## 0.2.4
+
+ - Fixed bug in `ref` that prevented `del` messages from
+   being received (gozala)
+ - Fixed a conflict with FireFox 4; constructor property
+   is now read-only.
+
+## 0.2.3
+
+ - Added `keys` message to promises and to the promise API.
+
+## 0.2.2
+
+ - Added boilerplate to `q/queue` and `q/util`.
+ - Fixed missing dependency to `q/queue`.
+
+## 0.2.1
+
+ - The `resolve` and `reject` methods of `defer` objects now
+   return the resolution promise for convenience.
+ - Added `q/util`, which provides `step`, `delay`, `shallow`,
+   `deep`, and three reduction orders.
+ - Added `q/queue` module for a promise `Queue`.
+ - Added `q-comm` to the list of compatible libraries.
+ - Deprecated `defined` from `q`, with intent to move it to
+   `q/util`.
+
+## 0.2.0 - BACKWARD INCOMPATIBLE
+
+ - Changed post(ref, name, args) to variadic
+   post(ref, name, ...args). BACKWARD INCOMPATIBLE
+ - Added a def(value) method to annotate an object as being
+   necessarily a local value that cannot be serialized, such
+   that inter-process/worker/vat promise communication
+   libraries will send messages to it, but never send it
+   back.
+ - Added a send(value, op, ...args) method to the public API, for
+   forwarding messages to a value or promise in a future turn.
+
+## 0.1.9
+
+ - Added isRejected() for testing whether a value is a rejected
+   promise.  isResolved() retains the behavior of stating
+   that rejected promises are not resolved.
+
+## 0.1.8
+
+ - Fixed isResolved(null) and isResolved(undefined) [issue #9]
+ - Fixed a problem with the Object.create shim
+
+## 0.1.7
+
+ - shimmed ES5 Object.create in addition to Object.freeze
+   for compatibility on non-ES5 engines (gozala)
+
+## 0.1.6
+
+ - Q.isResolved added
+ - promise.valueOf() now returns the value of resolved
+   and near values
+ - asap retried
+ - promises are frozen when possible
+
+## 0.1.5
+
+ - fixed dependency list for Teleport (gozala)
+ - all unit tests now pass (gozala)
+
+## 0.1.4
+
+ - added support for Teleport as an engine (gozala)
+ - simplified and updated methods for getting internal
+   print and enqueue functions universally (gozala)
+
+## 0.1.3
+
+ - fixed erroneous link to the q module in package.json
+
+## 0.1.2
+
+ - restructured for overlay style package compatibility
+
+## 0.1.0
+
+ - removed asap because it was broken, probably down to the
+   philosophy.
+
+## 0.0.3
+
+ - removed q-util
+ - fixed asap so it returns a value if completed
+
+## 0.0.2
+
+ - added q-util
+
+## 0.0.1
+
+ - initial version
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..500ab17
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,40 @@
+
+For pull requests:
+
+-   Be consistent with prevalent style and design decisions.
+-   Add a Jasmine spec to `specs/q-spec.js`.
+-   Use `npm test` to avoid regressions.
+-   Run tests in `q-spec/run.html` in as many supported browsers as you
+    can find the will to deal with.
+-   Do not build minified versions; we do this each release.
+-   If you would be so kind, add a note to `CHANGES.md` in an
+    appropriate section:
+
+    -   `Next Major Version` if it introduces backward incompatibilities
+        to code in the wild using documented features.
+    -   `Next Minor Version` if it adds a new feature.
+    -   `Next Patch Version` if it fixes a bug.
+
+For releases:
+
+-   Run `npm test`.
+-   Run tests in `q-spec/run.html` in a representative sample of every
+    browser under the sun.
+-   Run `npm run cover` and make sure you're happy with the results.
+-   Run `npm run minify` and be sure to commit the resulting `q.min.js`.
+-   Note the Gzipped size output by the previous command, and update
+    `README.md` if it has changed to 1 significant digit.
+-   Stash any local changes.
+-   Update `CHANGES.md` to reflect all changes in the differences
+    between `HEAD` and the previous tagged version.  Give credit where
+    credit is due.
+-   Update `README.md` to address all new, non-experimental features.
+-   Update the API reference on the Wiki to reflect all non-experimental
+    features.
+-   Use `npm version major|minor|patch` to update `package.json`,
+    commit, and tag the new version.
+-   Use `npm publish` to send up a new release.
+-   Send an email to the q-continuum mailing list announcing the new
+    release and the notes from the change log.  This helps folks
+    maintaining other package ecosystems.
+
diff --git a/Gruntfile.js b/Gruntfile.js
new file mode 100644
index 0000000..d764dd3
--- /dev/null
+++ b/Gruntfile.js
@@ -0,0 +1,16 @@
+"use strict";
+
+module.exports = function (grunt) {
+    grunt.loadNpmTasks("grunt-contrib-uglify");
+
+    grunt.initConfig({
+        uglify: {
+            "q.min.js": ["q.js"],
+            options: {
+                report: "gzip"
+            }
+        }
+    });
+
+    grunt.registerTask("default", ["uglify"]);
+};
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..76c5fe4
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+
+Copyright 2009–2012 Kristopher Michael Kowal. All rights reserved.
+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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c0f513c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,813 @@
+[![Build Status](https://secure.travis-ci.org/kriskowal/q.png?branch=master)](http://travis-ci.org/kriskowal/q)
+
+<a href="http://promises-aplus.github.com/promises-spec">
+    <img src="http://promises-aplus.github.com/promises-spec/assets/logo-small.png"
+         align="right" alt="Promises/A+ logo" />
+</a>
+
+If a function cannot return a value or throw an exception without
+blocking, it can return a promise instead.  A promise is an object
+that represents the return value or the thrown exception that the
+function may eventually provide.  A promise can also be used as a
+proxy for a [remote object][Q-Connection] to overcome latency.
+
+[Q-Connection]: https://github.com/kriskowal/q-connection
+
+On the first pass, promises can mitigate the “[Pyramid of
+Doom][POD]”: the situation where code marches to the right faster
+than it marches forward.
+
+[POD]: http://calculist.org/blog/2011/12/14/why-coroutines-wont-work-on-the-web/
+
+```javascript
+step1(function (value1) {
+    step2(value1, function(value2) {
+        step3(value2, function(value3) {
+            step4(value3, function(value4) {
+                // Do something with value4
+            });
+        });
+    });
+});
+```
+
+With a promise library, you can flatten the pyramid.
+
+```javascript
+Q.fcall(promisedStep1)
+.then(promisedStep2)
+.then(promisedStep3)
+.then(promisedStep4)
+.then(function (value4) {
+    // Do something with value4
+})
+.catch(function (error) {
+    // Handle any error from all above steps
+})
+.done();
+```
+
+With this approach, you also get implicit error propagation, just like `try`,
+`catch`, and `finally`.  An error in `promisedStep1` will flow all the way to
+the `catch` function, where it’s caught and handled.  (Here `promisedStepN` is
+a version of `stepN` that returns a promise.)
+
+The callback approach is called an “inversion of control”.
+A function that accepts a callback instead of a return value
+is saying, “Don’t call me, I’ll call you.”.  Promises
+[un-invert][IOC] the inversion, cleanly separating the input
+arguments from control flow arguments.  This simplifies the
+use and creation of API’s, particularly variadic,
+rest and spread arguments.
+
+[IOC]: http://www.slideshare.net/domenicdenicola/callbacks-promises-and-coroutines-oh-my-the-evolution-of-asynchronicity-in-javascript
+
+
+## Getting Started
+
+The Q module can be loaded as:
+
+-   A ``<script>`` tag (creating a ``Q`` global variable): ~2.5 KB minified and
+    gzipped.
+-   A Node.js and CommonJS module, available in [npm](https://npmjs.org/) as
+    the [q](https://npmjs.org/package/q) package
+-   An AMD module
+-   A [component](https://github.com/component/component) as ``microjs/q``
+-   Using [bower](http://bower.io/) as ``q``
+-   Using [NuGet](http://nuget.org/) as [Q](https://nuget.org/packages/q)
+
+Q can exchange promises with jQuery, Dojo, When.js, WinJS, and more.
+
+## Resources
+
+Our [wiki][] contains a number of useful resources, including:
+
+- A method-by-method [Q API reference][reference].
+- A growing [examples gallery][examples], showing how Q can be used to make
+  everything better. From XHR to database access to accessing the Flickr API,
+  Q is there for you.
+- There are many libraries that produce and consume Q promises for everything
+  from file system/database access or RPC to templating. For a list of some of
+  the more popular ones, see [Libraries][].
+- If you want materials that introduce the promise concept generally, and the
+  below tutorial isn't doing it for you, check out our collection of
+  [presentations, blog posts, and podcasts][resources].
+- A guide for those [coming from jQuery's `$.Deferred`][jquery].
+
+We'd also love to have you join the Q-Continuum [mailing list][].
+
+[wiki]: https://github.com/kriskowal/q/wiki
+[reference]: https://github.com/kriskowal/q/wiki/API-Reference
+[examples]: https://github.com/kriskowal/q/wiki/Examples-Gallery
+[Libraries]: https://github.com/kriskowal/q/wiki/Libraries
+[resources]: https://github.com/kriskowal/q/wiki/General-Promise-Resources
+[jquery]: https://github.com/kriskowal/q/wiki/Coming-from-jQuery
+[mailing list]: https://groups.google.com/forum/#!forum/q-continuum
+
+
+## Tutorial
+
+Promises have a ``then`` method, which you can use to get the eventual
+return value (fulfillment) or thrown exception (rejection).
+
+```javascript
+promiseMeSomething()
+.then(function (value) {
+}, function (reason) {
+});
+```
+
+If ``promiseMeSomething`` returns a promise that gets fulfilled later
+with a return value, the first function (the fulfillment handler) will be
+called with the value.  However, if the ``promiseMeSomething`` function
+gets rejected later by a thrown exception, the second function (the
+rejection handler) will be called with the exception.
+
+Note that resolution of a promise is always asynchronous: that is, the
+fulfillment or rejection handler will always be called in the next turn of the
+event loop (i.e. `process.nextTick` in Node). This gives you a nice
+guarantee when mentally tracing the flow of your code, namely that
+``then`` will always return before either handler is executed.
+
+In this tutorial, we begin with how to consume and work with promises. We'll
+talk about how to create them, and thus create functions like
+`promiseMeSomething` that return promises, [below](#the-beginning).
+
+
+### Propagation
+
+The ``then`` method returns a promise, which in this example, I’m
+assigning to ``outputPromise``.
+
+```javascript
+var outputPromise = getInputPromise()
+.then(function (input) {
+}, function (reason) {
+});
+```
+
+The ``outputPromise`` variable becomes a new promise for the return
+value of either handler.  Since a function can only either return a
+value or throw an exception, only one handler will ever be called and it
+will be responsible for resolving ``outputPromise``.
+
+-   If you return a value in a handler, ``outputPromise`` will get
+    fulfilled.
+
+-   If you throw an exception in a handler, ``outputPromise`` will get
+    rejected.
+
+-   If you return a **promise** in a handler, ``outputPromise`` will
+    “become” that promise.  Being able to become a new promise is useful
+    for managing delays, combining results, or recovering from errors.
+
+If the ``getInputPromise()`` promise gets rejected and you omit the
+rejection handler, the **error** will go to ``outputPromise``:
+
+```javascript
+var outputPromise = getInputPromise()
+.then(function (value) {
+});
+```
+
+If the input promise gets fulfilled and you omit the fulfillment handler, the
+**value** will go to ``outputPromise``:
+
+```javascript
+var outputPromise = getInputPromise()
+.then(null, function (error) {
+});
+```
+
+Q promises provide a ``fail`` shorthand for ``then`` when you are only
+interested in handling the error:
+
+```javascript
+var outputPromise = getInputPromise()
+.fail(function (error) {
+});
+```
+
+If you are writing JavaScript for modern engines only or using
+CoffeeScript, you may use `catch` instead of `fail`.
+
+Promises also have a ``fin`` function that is like a ``finally`` clause.
+The final handler gets called, with no arguments, when the promise
+returned by ``getInputPromise()`` either returns a value or throws an
+error.  The value returned or error thrown by ``getInputPromise()``
+passes directly to ``outputPromise`` unless the final handler fails, and
+may be delayed if the final handler returns a promise.
+
+```javascript
+var outputPromise = getInputPromise()
+.fin(function () {
+    // close files, database connections, stop servers, conclude tests
+});
+```
+
+-   If the handler returns a value, the value is ignored
+-   If the handler throws an error, the error passes to ``outputPromise``
+-   If the handler returns a promise, ``outputPromise`` gets postponed.  The
+    eventual value or error has the same effect as an immediate return
+    value or thrown error: a value would be ignored, an error would be
+    forwarded.
+
+If you are writing JavaScript for modern engines only or using
+CoffeeScript, you may use `finally` instead of `fin`.
+
+### Chaining
+
+There are two ways to chain promises.  You can chain promises either
+inside or outside handlers.  The next two examples are equivalent.
+
+```javascript
+return getUsername()
+.then(function (username) {
+    return getUser(username)
+    .then(function (user) {
+        // if we get here without an error,
+        // the value returned here
+        // or the exception thrown here
+        // resolves the promise returned
+        // by the first line
+    })
+});
+```
+
+```javascript
+return getUsername()
+.then(function (username) {
+    return getUser(username);
+})
+.then(function (user) {
+    // if we get here without an error,
+    // the value returned here
+    // or the exception thrown here
+    // resolves the promise returned
+    // by the first line
+});
+```
+
+The only difference is nesting.  It’s useful to nest handlers if you
+need to capture multiple input values in your closure.
+
+```javascript
+function authenticate() {
+    return getUsername()
+    .then(function (username) {
+        return getUser(username);
+    })
+    // chained because we will not need the user name in the next event
+    .then(function (user) {
+        return getPassword()
+        // nested because we need both user and password next
+        .then(function (password) {
+            if (user.passwordHash !== hash(password)) {
+                throw new Error("Can't authenticate");
+            }
+        });
+    });
+}
+```
+
+
+### Combination
+
+You can turn an array of promises into a promise for the whole,
+fulfilled array using ``all``.
+
+```javascript
+return Q.all([
+    eventualAdd(2, 2),
+    eventualAdd(10, 20)
+]);
+```
+
+If you have a promise for an array, you can use ``spread`` as a
+replacement for ``then``.  The ``spread`` function “spreads” the
+values over the arguments of the fulfillment handler.  The rejection handler
+will get called at the first sign of failure.  That is, whichever of
+the recived promises fails first gets handled by the rejection handler.
+
+```javascript
+function eventualAdd(a, b) {
+    return Q.spread([a, b], function (a, b) {
+        return a + b;
+    })
+}
+```
+
+But ``spread`` calls ``all`` initially, so you can skip it in chains.
+
+```javascript
+return getUsername()
+.then(function (username) {
+    return [username, getUser(username)];
+})
+.spread(function (username, user) {
+});
+```
+
+The ``all`` function returns a promise for an array of values.  When this
+promise is fulfilled, the array contains the fulfillment values of the original
+promises, in the same order as those promises.  If one of the given promises
+is rejected, the returned promise is immediately rejected, not waiting for the
+rest of the batch.  If you want to wait for all of the promises to either be
+fulfilled or rejected, you can use ``allSettled``.
+
+```javascript
+Q.allSettled(promises)
+.then(function (results) {
+    results.forEach(function (result) {
+        if (result.state === "fulfilled") {
+            var value = result.value;
+        } else {
+            var reason = result.reason;
+        }
+    });
+});
+```
+
+
+### Sequences
+
+If you have a number of promise-producing functions that need
+to be run sequentially, you can of course do so manually:
+
+```javascript
+return foo(initialVal).then(bar).then(baz).then(qux);
+```
+
+However, if you want to run a dynamically constructed sequence of
+functions, you'll want something like this:
+
+```javascript
+var funcs = [foo, bar, baz, qux];
+
+var result = Q(initialVal);
+funcs.forEach(function (f) {
+    result = result.then(f);
+});
+return result;
+```
+
+You can make this slightly more compact using `reduce`:
+
+```javascript
+return funcs.reduce(function (soFar, f) {
+    return soFar.then(f);
+}, Q(initialVal));
+```
+
+Or, you could use th ultra-compact version:
+
+```javascript
+return funcs.reduce(Q.when, Q());
+```
+
+### Handling Errors
+
+One sometimes-unintuive aspect of promises is that if you throw an
+exception in the fulfillment handler, it will not be be caught by the error
+handler.
+
+```javascript
+return foo()
+.then(function (value) {
+    throw new Error("Can't bar.");
+}, function (error) {
+    // We only get here if "foo" fails
+});
+```
+
+To see why this is, consider the parallel between promises and
+``try``/``catch``. We are ``try``-ing to execute ``foo()``: the error
+handler represents a ``catch`` for ``foo()``, while the fulfillment handler
+represents code that happens *after* the ``try``/``catch`` block.
+That code then needs its own ``try``/``catch`` block.
+
+In terms of promises, this means chaining your rejection handler:
+
+```javascript
+return foo()
+.then(function (value) {
+    throw new Error("Can't bar.");
+})
+.fail(function (error) {
+    // We get here with either foo's error or bar's error
+});
+```
+
+### Progress Notification
+
+It's possible for promises to report their progress, e.g. for tasks that take a
+long time like a file upload. Not all promises will implement progress
+notifications, but for those that do, you can consume the progress values using
+a third parameter to ``then``:
+
+```javascript
+return uploadFile()
+.then(function () {
+    // Success uploading the file
+}, function (err) {
+    // There was an error, and we get the reason for error
+}, function (progress) {
+    // We get notified of the upload's progress as it is executed
+});
+```
+
+Like `fail`, Q also provides a shorthand for progress callbacks
+called `progress`:
+
+```javascript
+return uploadFile().progress(function (progress) {
+    // We get notified of the upload's progress
+});
+```
+
+### The End
+
+When you get to the end of a chain of promises, you should either
+return the last promise or end the chain.  Since handlers catch
+errors, it’s an unfortunate pattern that the exceptions can go
+unobserved.
+
+So, either return it,
+
+```javascript
+return foo()
+.then(function () {
+    return "bar";
+});
+```
+
+Or, end it.
+
+```javascript
+foo()
+.then(function () {
+    return "bar";
+})
+.done();
+```
+
+Ending a promise chain makes sure that, if an error doesn’t get
+handled before the end, it will get rethrown and reported.
+
+This is a stopgap. We are exploring ways to make unhandled errors
+visible without any explicit handling.
+
+
+### The Beginning
+
+Everything above assumes you get a promise from somewhere else.  This
+is the common case.  Every once in a while, you will need to create a
+promise from scratch.
+
+#### Using ``Q.fcall``
+
+You can create a promise from a value using ``Q.fcall``.  This returns a
+promise for 10.
+
+```javascript
+return Q.fcall(function () {
+    return 10;
+});
+```
+
+You can also use ``fcall`` to get a promise for an exception.
+
+```javascript
+return Q.fcall(function () {
+    throw new Error("Can't do it");
+});
+```
+
+As the name implies, ``fcall`` can call functions, or even promised
+functions.  This uses the ``eventualAdd`` function above to add two
+numbers.
+
+```javascript
+return Q.fcall(eventualAdd, 2, 2);
+```
+
+
+#### Using Deferreds
+
+If you have to interface with asynchronous functions that are callback-based
+instead of promise-based, Q provides a few shortcuts (like ``Q.nfcall`` and
+friends). But much of the time, the solution will be to use *deferreds*.
+
+```javascript
+var deferred = Q.defer();
+FS.readFile("foo.txt", "utf-8", function (error, text) {
+    if (error) {
+        deferred.reject(new Error(error));
+    } else {
+        deferred.resolve(text);
+    }
+});
+return deferred.promise;
+```
+
+Note that a deferred can be resolved with a value or a promise.  The
+``reject`` function is a shorthand for resolving with a rejected
+promise.
+
+```javascript
+// this:
+deferred.reject(new Error("Can't do it"));
+
+// is shorthand for:
+var rejection = Q.fcall(function () {
+    throw new Error("Can't do it");
+});
+deferred.resolve(rejection);
+```
+
+This is a simplified implementation of ``Q.delay``.
+
+```javascript
+function delay(ms) {
+    var deferred = Q.defer();
+    setTimeout(deferred.resolve, ms);
+    return deferred.promise;
+}
+```
+
+This is a simplified implementation of ``Q.timeout``
+
+```javascript
+function timeout(promise, ms) {
+    var deferred = Q.defer();
+    Q.when(promise, deferred.resolve);
+    delay(ms).then(function () {
+        deferred.reject(new Error("Timed out"));
+    });
+    return deferred.promise;
+}
+```
+
+Finally, you can send a progress notification to the promise with
+``deferred.notify``.
+
+For illustration, this is a wrapper for XML HTTP requests in the browser. Note
+that a more [thorough][XHR] implementation would be in order in practice.
+
+[XHR]: https://github.com/montagejs/mr/blob/71e8df99bb4f0584985accd6f2801ef3015b9763/browser.js#L29-L73
+
+```javascript
+function requestOkText(url) {
+    var request = new XMLHttpRequest();
+    var deferred = Q.defer();
+
+    request.open("GET", url, true);
+    request.onload = onload;
+    request.onerror = onerror;
+    request.onprogress = onprogress;
+    request.send();
+
+    function onload() {
+        if (request.status === 200) {
+            deferred.resolve(request.responseText);
+        } else {
+            deferred.reject(new Error("Status code was " + request.status));
+        }
+    }
+
+    function onerror() {
+        deferred.reject(new Error("Can't XHR " + JSON.stringify(url)));
+    }
+
+    function onprogress(event) {
+        deferred.notify(event.loaded / event.total);
+    }
+
+    return deferred.promise;
+}
+```
+
+Below is an example of how to use this ``requestOkText`` function:
+
+```javascript
+requestOkText("http://localhost:3000")
+.then(function (responseText) {
+    // If the HTTP response returns 200 OK, log the response text.
+    console.log(responseText);
+}, function (error) {
+    // If there's an error or a non-200 status code, log the error.
+    console.error(error);
+}, function (progress) {
+    // Log the progress as it comes in.
+    console.log("Request progress: " + Math.round(progress * 100) + "%");
+});
+```
+
+### The Middle
+
+If you are using a function that may return a promise, but just might
+return a value if it doesn’t need to defer, you can use the “static”
+methods of the Q library.
+
+The ``when`` function is the static equivalent for ``then``.
+
+```javascript
+return Q.when(valueOrPromise, function (value) {
+}, function (error) {
+});
+```
+
+All of the other methods on a promise have static analogs with the
+same name.
+
+The following are equivalent:
+
+```javascript
+return Q.all([a, b]);
+```
+
+```javascript
+return Q.fcall(function () {
+    return [a, b];
+})
+.all();
+```
+
+When working with promises provided by other libraries, you should
+convert it to a Q promise.  Not all promise libraries make the same
+guarantees as Q and certainly don’t provide all of the same methods.
+Most libraries only provide a partially functional ``then`` method.
+This thankfully is all we need to turn them into vibrant Q promises.
+
+```javascript
+return Q($.ajax(...))
+.then(function () {
+});
+```
+
+If there is any chance that the promise you receive is not a Q promise
+as provided by your library, you should wrap it using a Q function.
+You can even use ``Q.invoke`` as a shorthand.
+
+```javascript
+return Q.invoke($, 'ajax', ...)
+.then(function () {
+});
+```
+
+
+### Over the Wire
+
+A promise can serve as a proxy for another object, even a remote
+object.  There are methods that allow you to optimistically manipulate
+properties or call functions.  All of these interactions return
+promises, so they can be chained.
+
+```
+direct manipulation         using a promise as a proxy
+--------------------------  -------------------------------
+value.foo                   promise.get("foo")
+value.foo = value           promise.put("foo", value)
+delete value.foo            promise.del("foo")
+value.foo(...args)          promise.post("foo", [args])
+value.foo(...args)          promise.invoke("foo", ...args)
+value(...args)              promise.fapply([args])
+value(...args)              promise.fcall(...args)
+```
+
+If the promise is a proxy for a remote object, you can shave
+round-trips by using these functions instead of ``then``.  To take
+advantage of promises for remote objects, check out [Q-Connection][].
+
+[Q-Connection]: https://github.com/kriskowal/q-connection
+
+Even in the case of non-remote objects, these methods can be used as
+shorthand for particularly-simple fulfillment handlers. For example, you
+can replace
+
+```javascript
+return Q.fcall(function () {
+    return [{ foo: "bar" }, { foo: "baz" }];
+})
+.then(function (value) {
+    return value[0].foo;
+});
+```
+
+with
+
+```javascript
+return Q.fcall(function () {
+    return [{ foo: "bar" }, { foo: "baz" }];
+})
+.get(0)
+.get("foo");
+```
+
+
+### Adapting Node
+
+If you're working with functions that make use of the Node.js callback pattern,
+where callbacks are in the form of `function(err, result)`, Q provides a few
+useful utility functions for converting between them. The most straightforward
+are probably `Q.nfcall` and `Q.nfapply` ("Node function call/apply") for calling
+Node.js-style functions and getting back a promise:
+
+```javascript
+return Q.nfcall(FS.readFile, "foo.txt", "utf-8");
+return Q.nfapply(FS.readFile, ["foo.txt", "utf-8"]);
+```
+
+If you are working with methods, instead of simple functions, you can easily
+run in to the usual problems where passing a method to another function—like
+`Q.nfcall`—"un-binds" the method from its owner. To avoid this, you can either
+use `Function.prototype.bind` or some nice shortcut methods we provide:
+
+```javascript
+return Q.ninvoke(redisClient, "get", "user:1:id");
+return Q.npost(redisClient, "get", ["user:1:id"]);
+```
+
+You can also create reusable wrappers with `Q.denodeify` or `Q.nbind`:
+
+```javascript
+var readFile = Q.denodeify(FS.readFile);
+return readFile("foo.txt", "utf-8");
+
+var redisClientGet = Q.nbind(redisClient.get, redisClient);
+return redisClientGet("user:1:id");
+```
+
+Finally, if you're working with raw deferred objects, there is a
+`makeNodeResolver` method on deferreds that can be handy:
+
+```javascript
+var deferred = Q.defer();
+FS.readFile("foo.txt", "utf-8", deferred.makeNodeResolver());
+return deferred.promise;
+```
+
+### Long Stack Traces
+
+Q comes with optional support for “long stack traces,” wherein the `stack`
+property of `Error` rejection reasons is rewritten to be traced along
+asynchronous jumps instead of stopping at the most recent one. As an example:
+
+```js
+function theDepthsOfMyProgram() {
+  Q.delay(100).done(function explode() {
+    throw new Error("boo!");
+  });
+}
+
+theDepthsOfMyProgram();
+```
+
+usually would give a rather unhelpful stack trace looking something like
+
+```
+Error: boo!
+    at explode (/path/to/test.js:3:11)
+    at _fulfilled (/path/to/test.js:q:54)
+    at resolvedValue.promiseDispatch.done (/path/to/q.js:823:30)
+    at makePromise.promise.promiseDispatch (/path/to/q.js:496:13)
+    at pending (/path/to/q.js:397:39)
+    at process.startup.processNextTick.process._tickCallback (node.js:244:9)
+```
+
+But, if you turn this feature on by setting
+
+```js
+Q.longStackSupport = true;
+```
+
+then the above code gives a nice stack trace to the tune of
+
+```
+Error: boo!
+    at explode (/path/to/test.js:3:11)
+From previous event:
+    at theDepthsOfMyProgram (/path/to/test.js:2:16)
+    at Object.<anonymous> (/path/to/test.js:7:1)
+```
+
+Note how you can see the the function that triggered the async operation in the
+stack trace! This is very helpful for debugging, as otherwise you end up getting
+only the first line, plus a bunch of Q internals, with no sign of where the
+operation started.
+
+This feature does come with somewhat-serious performance and memory overhead,
+however. If you're working with lots of promises, or trying to scale a server
+to many users, you should probably keep it off. But in development, go for it!
+
+## Tests
+
+You can view the results of the Q test suite [in your browser][tests]!
+
+[tests]: https://rawgithub.com/kriskowal/q/master/spec/q-spec.html
+
+## License
+
+Copyright 2009–2013 Kristopher Michael Kowal
+MIT License (enclosed)
+
diff --git a/VERSIONS.md b/VERSIONS.md
new file mode 100644
index 0000000..8f0e11d
--- /dev/null
+++ b/VERSIONS.md
@@ -0,0 +1,18 @@
+<!-- vim:ts=4:sts=4:sw=4:et:tw=60 -->
+
+This library has the following policy about versions.
+
+-   Presently, all planned versions have a major version number of 0.
+-   The minor version number increases for every backward-incompatible
+    change to a documented behavior.
+-   The patch version number increases for every added feature,
+    backward-incompatible changes to undocumented features, and
+    bug-fixes.
+
+Upon the release of a version 1.0.0, the strategy will be revised.
+
+-   The major version will increase for any backward-incompatible
+    changes.
+-   The minor version will increase for added features.
+-   The patch version will increase for bug-fixes.
+
diff --git a/benchmark/compare-with-callbacks.js b/benchmark/compare-with-callbacks.js
new file mode 100644
index 0000000..97f1298
--- /dev/null
+++ b/benchmark/compare-with-callbacks.js
@@ -0,0 +1,71 @@
+"use strict";
+
+var Q = require("../q");
+var fs = require("fs");
+
+suite("A single simple async operation", function () {
+    bench("with an immediately-fulfilled promise", function (done) {
+        Q().then(done);
+    });
+
+    bench("with direct setImmediate usage", function (done) {
+        setImmediate(done);
+    });
+
+    bench("with direct setTimeout(…, 0)", function (done) {
+        setTimeout(done, 0);
+    });
+});
+
+suite("A fs.readFile", function () {
+    var denodeified = Q.denodeify(fs.readFile);
+
+    set("iterations", 1000);
+    set("delay", 1000);
+
+    bench("directly, with callbacks", function (done) {
+        fs.readFile(__filename, done);
+    });
+
+    bench("with Q.nfcall", function (done) {
+        Q.nfcall(fs.readFile, __filename).then(done);
+    });
+
+    bench("with a Q.denodeify'ed version", function (done) {
+        denodeified(__filename).then(done);
+    });
+
+    bench("with manual usage of deferred.makeNodeResolver", function (done) {
+        var deferred = Q.defer();
+        fs.readFile(__filename, deferred.makeNodeResolver());
+        deferred.promise.then(done);
+    });
+});
+
+suite("1000 operations in parallel", function () {
+    function makeCounter(desiredCount, ultimateCallback) {
+        var soFar = 0;
+        return function () {
+            if (++soFar === desiredCount) {
+                ultimateCallback();
+            }
+        };
+    }
+    var numberOfOps = 1000;
+
+    bench("with immediately-fulfilled promises", function (done) {
+        var counter = makeCounter(numberOfOps, done);
+
+        for (var i = 0; i < numberOfOps; ++i) {
+            Q().then(counter);
+        }
+    });
+
+    bench("with direct setImmediate usage", function (done) {
+        var counter = makeCounter(numberOfOps, done);
+
+        for (var i = 0; i < numberOfOps; ++i) {
+            setImmediate(counter);
+        }
+    });
+});
diff --git a/benchmark/scenarios.js b/benchmark/scenarios.js
new file mode 100644
index 0000000..7c18564
--- /dev/null
+++ b/benchmark/scenarios.js
@@ -0,0 +1,36 @@
+"use strict";
+
+var Q = require("../q");
+
+suite("Chaining", function () {
+    var numberToChain = 1000;
+
+    bench("Chaining many already-fulfilled promises together", function (done) {
+        var currentPromise = Q();
+        for (var i = 0; i < numberToChain; ++i) {
+            currentPromise = currentPromise.then(function () {
+                return Q();
+            });
+        }
+
+        currentPromise.then(done);
+    });
+
+    bench("Chaining and then fulfilling the end of the chain", function (done) {
+        var deferred = Q.defer();
+
+        var currentPromise = deferred.promise;
+        for (var i = 0; i < numberToChain; ++i) {
+            (function () {
+                var promiseToReturn = currentPromise;
+                currentPromise = Q().then(function () {
+                    return promiseToReturn;
+                });
+            }());
+        }
+
+        currentPromise.then(done);
+
+        deferred.resolve();
+    });
+});
diff --git a/design/README.js b/design/README.js
new file mode 100644
index 0000000..db4aa33
--- /dev/null
+++ b/design/README.js
@@ -0,0 +1,1026 @@
+/*
+This document is intended to explain how promises work and why this
+implementation works its particular way by building a promise library
+incrementally and reviewing all of its major design decisions.  This is
+intended to leave the reader at liberty to experiment with variations
+of this implementation that suit their own requirements, without missing
+any important details.
+
+-
+
+Suppose that you're writing a function that can't return a value immediately.
+The most obvious API is to forward the eventual value to a callback as an
+argument instead of returning the value.
+*/
+
+var oneOneSecondLater = function (callback) {
+    setTimeout(function () {
+        callback(1);
+    }, 1000);
+};
+
+/*
+This is a very simple solution to a trival problem, but there is a lot of room
+for improvement.
+
+A more general solution would provide analogous tools for both return values
+and thrown exceptions.  There are several obvious ways to extend the callback
+pattern to handle exceptions.  One is to provide both a callback and an
+errback.
+*/
+
+var maybeOneOneSecondLater = function (callback, errback) {
+    setTimeout(function () {
+        if (Math.random() < .5) {
+            callback(1);
+        } else {
+            errback(new Error("Can't provide one."));
+        }
+    }, 1000);
+};
+
+/*
+There are other approaches, variations on providing the error as an argument
+to the callback, either by position or a distinguished sentinel value.
+However, none of these approaches actually model thrown exceptions.  The
+purpose of exceptions and try/catch blocks is to postpone the explicit
+handling of exceptions until the program has returned to a point where it
+makes sense to attempt to recover.  There needs to be some mechanism for
+implicitly propagating exceptions if they are not handled.
+
+
+Promises
+========
+
+Consider a more general approach, where instead of returning values or
+throwing exceptions, functions return an object that represents the eventual
+result of the function, either sucessful or failed.  This object is a promise,
+both figuratively and by name, to eventually resolve.  We can call a function
+on the promise to observe either its fulfillment or rejection.  If the promise
+is rejected and the rejection is not explicitly observed, any derrived
+promises will be implicitly rejected for the same reason.
+
+In this particular iteration of the design, we'll model a promise as an object
+with a "then" function that registers the callback.
+*/
+
+var maybeOneOneSecondLater = function () {
+    var callback;
+    setTimeout(function () {
+        callback(1);
+    }, 1000);
+    return {
+        then: function (_callback) {
+            callback = _callback;
+        }
+    };
+};
+
+maybeOneOneSecondLater().then(callback);
+
+/*
+This design has two weaknesses: 
+
+- The first caller of the then method determines the callback that is used.
+  It would be more useful if every registered callback were notified of
+  the resolution.
+- If the callback is registered more than a second after the promise was
+  constructed, it won't be called.
+
+A more general solution would accept any number of callbacks and permit them
+to be registered either before or after the timeout, or generally, the
+resolution event.  We accomplish this by making the promise a two-state object.
+
+A promise is initially unresolved and all callbacks are added to an array of
+pending observers.  When the promise is resolved, all of the observers are
+notified.  After the promise has been resolved, new callbacks are called
+immediately.  We distinguish the state change by whether the array of pending
+callbacks still exists, and we throw them away after resolution.
+*/
+
+var maybeOneOneSecondLater = function () {
+    var pending = [], value;
+    setTimeout(function () {
+        value = 1;
+        for (var i = 0, ii = pending.length; i < ii; i++) {
+            var callback = pending[i];
+            callback(value);
+        }
+        pending = undefined;
+    }, 1000);
+    return {
+        then: function (callback) {
+            if (pending) {
+                pending.push(callback);
+            } else {
+                callback(value);
+            }
+        }
+    };
+};
+
+/*
+This is already doing enough that it would be useful to break it into a
+utility function.  A deferred is an object with two parts: one for registering
+observers and another for notifying observers of resolution.
+(see design/q0.js)
+*/
+
+var defer = function () {
+    var pending = [], value;
+    return {
+        resolve: function (_value) {
+            value = _value;
+            for (var i = 0, ii = pending.length; i < ii; i++) {
+                var callback = pending[i];
+                callback(value);
+            }
+            pending = undefined;
+        },
+        then: function (callback) {
+            if (pending) {
+                pending.push(callback);
+            } else {
+                callback(value);
+            }
+        }
+    }
+};
+
+var oneOneSecondLater = function () {
+    var result = defer();
+    setTimeout(function () {
+        result.resolve(1);
+    }, 1000);
+    return result;
+};
+
+oneOneSecondLater().then(callback);
+
+/*
+The resolve function now has a flaw: it can be called multiple times, changing
+the value of the promised result.  This fails to model the fact that a
+function only either returns one value or throws one error.  We can protect
+against accidental or malicious resets by only allowing only the first call to
+resolve to set the resolution.
+*/
+
+var defer = function () {
+    var pending = [], value;
+    return {
+        resolve: function (_value) {
+            if (pending) {
+                value = _value;
+                for (var i = 0, ii = pending.length; i < ii; i++) {
+                    var callback = pending[i];
+                    callback(value);
+                }
+                pending = undefined;
+            } else {
+                throw new Error("A promise can only be resolved once.");
+            }
+        },
+        then: function (callback) {
+            if (pending) {
+                pending.push(callback);
+            } else {
+                callback(value);
+            }
+        }
+    }
+};
+
+/*
+You can make an argument either for throwing an error or for ignoring all
+subsequent resolutions.  One use-case is to give the resolver to a bunch of
+workers and have a race to resolve the promise, where subsequent resolutions
+would be ignored.  It's also possible that you do not want the workers to know
+which won.  Hereafter, all examples will ignore rather than fault on multiple
+resolution.
+
+At this point, defer can handle both multiple resolution and multiple
+observation. (see design/q1.js)
+
+--------------------------------
+
+There are a few variations on this design which arise from two separate
+tensions.  The first tension is that it is both useful to separate or combine
+the promise and resolver parts of the deferred.  It is also useful to have
+some way of distinguishing promises from other values.
+
+-
+
+Separating the promise portion from the resolver allows us to code within the
+principle of least authority.  Giving someone a promise should give only the
+authority to observe the resolution and giving someone a resolver should only
+give the authority to determine the resolution.  One should not implicitly
+give the other.  The test of time shows that any excess authority will
+inevitably be abused and will be very difficult to redact.
+
+The disadvantage of separation, however, is the additional burden on the
+garbage collector to quickly dispose of used promise objects.
+
+-
+
+Also, there are a variety of ways to distinguish a promise from other values.
+The most obvious and strongest distinction is to use prototypical inheritance.
+(design/q2.js)
+*/
+
+var Promise = function () {
+};
+
+var isPromise = function (value) {
+    return value instanceof Promise;
+};
+
+var defer = function () {
+    var pending = [], value;
+    var promise = new Promise();
+    promise.then = function (callback) {
+        if (pending) {
+            pending.push(callback);
+        } else {
+            callback(value);
+        }
+    };
+    return {
+        resolve: function (_value) {
+            if (pending) {
+                value = _value;
+                for (var i = 0, ii = pending.length; i < ii; i++) {
+                    var callback = pending[i];
+                    callback(value);
+                }
+                pending = undefined;
+            }
+        },
+        promise: promise
+    };
+};
+
+
+/*
+Using prototypical inheritance has the disadvantage that only one instance of
+a promise library can be used in a single program.  This can be difficult to
+enforce, leading to dependency enforcement woes.
+
+Another approach is to use duck-typing, distinguishing promises from other
+values by the existence of a conventionally named method.  In our case,
+CommonJS/Promises/A establishes the use of "then" to distinguish its brand of
+promises from other values.  This has the disadvantage of failing to
+distinguish other objects that just happen to have a "then" method.  In
+practice, this is not a problem, and the minor variations in "thenable"
+implementations in the wild are manageable.
+*/
+
+var isPromise = function (value) {
+    return value && typeof value.then === "function";
+};
+
+var defer = function () {
+    var pending = [], value;
+    return {
+        resolve: function (_value) {
+            if (pending) {
+                value = _value;
+                for (var i = 0, ii = pending.length; i < ii; i++) {
+                    var callback = pending[i];
+                    callback(value);
+                }
+                pending = undefined;
+            }
+        },
+        promise: {
+            then: function (callback) {
+                if (pending) {
+                    pending.push(callback);
+                } else {
+                    callback(value);
+                }
+            }
+        }
+    };
+};
+
+/*
+The next big step is making it easy to compose promises, to make new promises
+using values obtained from old promises.  Supposing that you have received
+promises for two numbers from a couple function calls, we would like to be
+able to create a promise for their sum.  Consider how this is achieved with
+callbacks.
+*/
+
+var twoOneSecondLater = function (callback) {
+    var a, b;
+    var consider = function () {
+        if (a === undefined || b === undefined)
+            return;
+        callback(a + b);
+    };
+    oneOneSecondLater(function (_a) {
+        a = _a;
+        consider();
+    });
+    oneOneSecondLater(function (_b) {
+        b = _b;
+        consider();
+    });
+};
+
+twoOneSecondLater(function (c) {
+    // c === 2
+});
+
+/*
+This approach is fragile for a number of reasons, particularly that there
+needs to be code to explicitly notice, in this case by a sentinel value,
+whether a callback has been called.  One must also take care to account for cases
+where callbacks are issued before the end of the event loop turn: the `consider`
+function must appear before it is used.
+
+In a few more steps, we will be able to accomplish this using promises in less
+code and handling error propagation implicitly.
+*/
+
+var a = oneOneSecondLater();
+var b = oneOneSecondLater();
+var c = a.then(function (a) {
+    return b.then(function (b) {
+        return a + b;
+    });
+});
+
+/*
+For this to work, several things have to fall into place:
+
+ - The "then" method must return a promise.
+ - The returned promise must be eventually resolved with the
+   return value of the callback.
+ - The return value of the callback must be either a fulfilled
+   value or a promise.
+
+Converting values into promises that have already been fulfilled
+is straightforward.  This is a promise that immediately informs
+any observers that the value has already been fulfilled.
+*/
+
+var ref = function (value) {
+    return {
+        then: function (callback) {
+            callback(value);
+        }
+    };
+};
+
+/*
+This method can be altered to coerce the argument into a promise
+regardless of whether it is a value or a promise already.
+*/
+
+var ref = function (value) {
+    if (value && typeof value.then === "function")
+        return value;
+    return {
+        then: function (callback) {
+            callback(value);
+        }
+    };
+};
+
+/*
+Now, we need to start altering our "then" methods so that they
+return promises for the return value of their given callback.
+The "ref" case is simple.  We'll coerce the return value of the
+callback to a promise and return that immediately.
+*/
+
+var ref = function (value) {
+    if (value && typeof value.then === "function")
+        return value;
+    return {
+        then: function (callback) {
+            return ref(callback(value));
+        }
+    };
+};
+
+/*
+This is more complicated for the deferred since the callback
+will be called in a future turn.  In this case, we recur on "defer"
+and wrap the callback.  The value returned by the callback will
+resolve the promise returned by "then".
+
+Furthermore, the "resolve" method needs to handle the case where the
+resolution is itself a promise to resolve later.  This is accomplished by
+changing the resolution value to a promise.  That is, it implements a "then"
+method, and can either be a promise returned by "defer" or a promise returned
+by "ref".  If it's a "ref" promise, the behavior is identical to before: the
+callback is called immediately by "then(callback)".  If it's a "defer"
+promise, the callback is passed forward to the next promise by calling
+"then(callback)".  Thus, your callback is now observing a new promise for a
+more fully resolved value.  Callbacks can be forwarded many times, making
+"progress" toward an eventual resolution with each forwarding.
+*/
+
+var defer = function () {
+    var pending = [], value;
+    return {
+        resolve: function (_value) {
+            if (pending) {
+                value = ref(_value); // values wrapped in a promise
+                for (var i = 0, ii = pending.length; i < ii; i++) {
+                    var callback = pending[i];
+                    value.then(callback); // then called instead
+                }
+                pending = undefined;
+            }
+        },
+        promise: {
+            then: function (_callback) {
+                var result = defer();
+                // callback is wrapped so that its return
+                // value is captured and used to resolve the promise
+                // that "then" returns
+                var callback = function (value) {
+                    result.resolve(_callback(value));
+                };
+                if (pending) {
+                    pending.push(callback);
+                } else {
+                    value.then(callback);
+                }
+                return result.promise;
+            }
+        }
+    };
+};
+
+/*
+The implementation at this point uses "thenable" promises and separates the
+"promise" and "resolve" components of a "deferred".
+(see design/q4.js)
+
+
+Error Propagation
+=================
+
+To achieve error propagation, we need to reintroduce errbacks.  We use a new
+type of promise, analogous to a "ref" promise, that instead of informing a
+callback of the promise's fulfillment, it will inform the errback of its
+rejection and the reason why.
+*/
+
+var reject = function (reason) {
+    return {
+        then: function (callback, errback) {
+            return ref(errback(reason));
+        }
+    };
+};
+
+/*
+The simplest way to see this in action is to observe the resolution of
+an immediate rejection.
+*/
+
+reject("Meh.").then(function (value) {
+    // we never get here
+}, function (reason) {
+    // reason === "Meh."
+});
+
+/*
+We can now revise our original errback use-case to use the promise
+API.
+*/
+
+var maybeOneOneSecondLater = function (callback, errback) {
+    var result = defer();
+    setTimeout(function () {
+        if (Math.random() < .5) {
+            result.resolve(1);
+        } else {
+            result.resolve(reject("Can't provide one."));
+        }
+    }, 1000);
+    return result.promise;
+};
+
+/*
+To make this example work, the defer system needs new plumbing so that it can
+forward both the callback and errback components.  So, the array of pending
+callbacks will be replaced with an array of arguments for "then" calls.
+*/
+
+var defer = function () {
+    var pending = [], value;
+    return {
+        resolve: function (_value) {
+            if (pending) {
+                value = ref(_value);
+                for (var i = 0, ii = pending.length; i < ii; i++) {
+                    // apply the pending arguments to "then"
+                    value.then.apply(value, pending[i]);
+                }
+                pending = undefined;
+            }
+        },
+        promise: {
+            then: function (_callback, _errback) {
+                var result = defer();
+                var callback = function (value) {
+                    result.resolve(_callback(value));
+                };
+                var errback = function (reason) {
+                    result.resolve(_errback(reason));
+                };
+                if (pending) {
+                    pending.push([callback, errback]);
+                } else {
+                    value.then(callback, errback);
+                }
+                return result.promise;
+            }
+        }
+    };
+};
+
+/*
+There is, however, a subtle problem with this version of "defer".  It mandates
+that an errback must be provided on all "then" calls, or an exception will be
+thrown when trying to call a non-existant function.  The simplest solution to
+this problem is to provide a default errback that forwards the rejection.  It
+is also reasonable for the callback to be omitted if you're only interested in
+observing rejections, so we provide a default callback that forwards the
+fulfilled value.
+*/
+
+var defer = function () {
+    var pending = [], value;
+    return {
+        resolve: function (_value) {
+            if (pending) {
+                value = ref(_value);
+                for (var i = 0, ii = pending.length; i < ii; i++) {
+                    value.then.apply(value, pending[i]);
+                }
+                pending = undefined;
+            }
+        },
+        promise: {
+            then: function (_callback, _errback) {
+                var result = defer();
+                // provide default callbacks and errbacks
+                _callback = _callback || function (value) {
+                    // by default, forward fulfillment
+                    return value;
+                };
+                _errback = _errback || function (reason) {
+                    // by default, forward rejection
+                    return reject(reason);
+                };
+                var callback = function (value) {
+                    result.resolve(_callback(value));
+                };
+                var errback = function (reason) {
+                    result.resolve(_errback(reason));
+                };
+                if (pending) {
+                    pending.push([callback, errback]);
+                } else {
+                    value.then(callback, errback);
+                }
+                return result.promise;
+            }
+        }
+    };
+};
+
+/*
+At this point, we've achieved composition and implicit error propagation.  We
+can now very easily create promises from other promises either in serial or in
+parallel (see design/q6.js).  This example creates a promise for the eventual
+sum of promised values.
+*/
+
+promises.reduce(function (accumulating, promise) {
+    return accumulating.then(function (accumulated) {
+        return promise.then(function (value) {
+            return accumulated + value;
+        });
+    });
+}, ref(0)) // start with a promise for zero, so we can call then on it
+           // just like any of the combined promises
+.then(function (sum) {
+    // the sum is here
+});
+
+/*
+
+
+Safety and Invariants
+=====================
+
+Another incremental improvement is to make sure that callbacks and errbacks
+are called in future turns of the event loop, in the same order that they
+were registered.  This greatly reduces the number of control-flow hazards
+inherent to asynchronous programming.  Consider a brief and contrived example:
+*/
+
+var blah = function () {
+    var result = foob().then(function () {
+        return barf();
+    });
+    var barf = function () {
+        return 10;
+    };
+    return result;
+};
+
+/*
+This function will either throw an exception or return a promise that will
+quickly be fulfilled with the value of 10.  It depends on whether foob()
+resolves in the same turn of the event loop (issuing its callback on the same
+stack immediately) or in a future turn.  If the callback is delayed to a
+future turn, it will allways succeed.
+(see design/q7.js)
+*/
+
+var enqueue = function (callback) {
+    //process.nextTick(callback); // NodeJS
+    setTimeout(callback, 1); // Naïve browser solution
+};
+
+var defer = function () {
+    var pending = [], value;
+    return {
+        resolve: function (_value) {
+            if (pending) {
+                value = ref(_value);
+                for (var i = 0, ii = pending.length; i < ii; i++) {
+                    // XXX
+                    enqueue(function () {
+                        value.then.apply(value, pending[i]);
+                    });
+                }
+                pending = undefined;
+            }
+        },
+        promise: {
+            then: function (_callback, _errback) {
+                var result = defer();
+                _callback = _callback || function (value) {
+                    return value;
+                };
+                _errback = _errback || function (reason) {
+                    return reject(reason);
+                };
+                var callback = function (value) {
+                    result.resolve(_callback(value));
+                };
+                var errback = function (reason) {
+                    result.resolve(_errback(reason));
+                };
+                if (pending) {
+                    pending.push([callback, errback]);
+                } else {
+                    // XXX
+                    enqueue(function () {
+                        value.then(callback, errback);
+                    });
+                }
+                return result.promise;
+            }
+        }
+    };
+};
+
+var ref = function (value) {
+    if (value && value.then)
+        return value;
+    return {
+        then: function (callback) {
+            var result = defer();
+            // XXX
+            enqueue(function () {
+                result.resolve(callback(value));
+            });
+            return result.promise;
+        }
+    };
+};
+
+var reject = function (reason) {
+    return {
+        then: function (callback, errback) {
+            var result = defer();
+            // XXX
+            enqueue(function () {
+                result.resolve(errback(reason));
+            });
+            return result.promise;
+        }
+    };
+};
+
+/*
+There remains one safty issue, though.  Given that any object that implements
+"then" is treated as a promise, anyone who calls "then" directly is at risk
+of surprise.
+
+ - The callback or errback might get called in the same turn
+ - The callback and errback might both be called
+ - The callback or errback might be called more than once
+
+A "when" method wraps a promise and prevents these surprises.
+
+We can also take the opportunity to wrap the callback and errback
+so that any exceptions thrown get transformed into rejections.
+*/
+
+var when = function (value, _callback, _errback) {
+    var result = defer();
+    var done;
+
+    _callback = _callback || function (value) {
+        return value;
+    };
+    _errback = _errback || function (reason) {
+        return reject(reason);
+    };
+
+    var callback = function (value) {
+        try {
+            return _callback(value);
+        } catch (reason) {
+            return reject(reason);
+        }
+    };
+    var errback = function (reason) {
+        try {
+            return _errback(reason);
+        } catch (reason) {
+            return reject(reason);
+        }
+    };
+
+    enqueue(function () {
+        ref(value).then(function (value) {
+            if (done)
+                return;
+            done = true;
+            result.resolve(ref(value).then(callback, errback));
+        }, function (reason) {
+            if (done)
+                return;
+            done = true;
+            result.resolve(errback(reason));
+        });
+    });
+
+    return result.promise;
+};
+
+/*
+At this point, we have the means to protect ourselves against several
+surprises including unnecessary non-deterministic control-flow in the course
+of an event and broken callback and errback control-flow invariants.
+(see design/q7.js)
+
+
+Message Passing
+===============
+
+If we take a step back, promises have become objects that receive "then"
+messages.  Deferred promises forward those messages to their resolution
+promise.  Fulfilled promises respond to then messages by calling the callback
+with the fulfilled value.  Rejected promises respond to then messages by
+calling the errback with the rejection reason.
+
+We can generalize promises to be objects that receive arbitrary messages,
+including "then/when" messages.  This is useful if there is a lot of latency
+preventing the immediate observation of a promise's resolution, as in a
+promise that is in another process or worker or another computer on a network.
+
+If we have to wait for a message to make a full round-trip across a network to
+get a value, the round-trips can add up a lot and much time will be wasted.
+This ammounts to "chatty" network protocol problems, which are the downfall
+of SOAP and RPC in general.
+
+However, if we can send a message to a distant promise before it resolves, the
+remote promise can send responses in rapid succession.  Consider the case
+where an object is housed on a remote server and cannot itself be sent across
+the network; it has some internal state and capabilities that cannot be
+serialized, like access to a database.  Suppose we obtain a promise for
+this object and can now send messages.  These messages would likely mostly
+comprise method calls like "query", which would in turn send promises back.
+
+---
+
+We must found a new family of promises based on a new method that sends
+arbitrary messages to a promise.  "promiseSend" is defined by
+CommonJS/Promises/D.  Sending a "when" message is equivalent to calling the
+"then" method.
+
+*/
+
+promise.then(callback, errback);
+promise.promiseSend("when", callback, errback);
+
+/*
+We must revisit all of our methods, building them on "promiseSend" instead of
+"then".  However, we do not abandon "then" entirely; we still produce and
+consume "thenable" promises, routing their message through "promiseSend"
+internally.
+*/
+
+function Promise() {}
+Promise.prototype.then = function (callback, errback) {
+    return when(this, callback, errback);
+};
+
+/*
+If a promise does not recognize a message type (an "operator" like "when"),
+it must return a promise that will be eventually rejected.
+
+Being able to receive arbitrary messages means that we can also implement new
+types of promise that serves as a proxy for a remote promise, simply
+forwarding all messages to the remote promise and forwarding all of its
+responses back to promises in the local worker.
+
+Between the use-case for proxies and rejecting unrecognized messages, it
+is useful to create a promise abstraction that routes recognized messages to
+a handler object, and unrecognized messages to a fallback method.
+
+*/
+
+var makePromise = function (handler, fallback) {
+    var promise = new Promise();
+    handler = handler || {};
+    fallback = fallback || function (op) {
+        return reject("Can't " + op);
+    };
+    promise.promiseSend = function (op, callback) {
+        var args = Array.prototype.slice.call(arguments, 2);
+        var result;
+        callback = callback || function (value) {return value};
+        if (handler[op]) {
+            result = handler[op].apply(handler, args);
+        } else {
+            result = fallback.apply(handler, [op].concat(args));
+        }
+        return callback(result);
+    };
+    return promise;
+};
+
+/*
+Each of the handler methods and the fallback method are all expected to return
+a value which will be forwarded to the callback.  The handlers do not receive
+their own name, but the fallback does receive the operator name so it can
+route it.  Otherwise, arguments are passed through.
+*/
+
+/*
+For the "ref" method, we still only coerce values that are not already
+promises.  We also coerce "thenables" into "promiseSend" promises.
+We provide methods for basic interaction with a fulfilled value, including
+property manipulation and method calls.
+*/
+
+var ref = function (object) {
+    if (object && typeof object.promiseSend !== "undefined") {
+        return object;
+    }
+    if (object && typeof object.then !== "undefined") {
+        return makePromise({
+            when: function () {
+                var result = defer();
+                object.then(result.resolve, result.reject);
+                return result;
+            }
+        }, function fallback(op) {
+            return Q.when(object, function (object) {
+                return Q.ref(object).promiseSend.apply(object, arguments);
+            });
+        });
+    }
+    return makePromise({
+        when: function () {
+            return object;
+        },
+        get: function (name) {
+            return object[name];
+        },
+        put: function (name, value) {
+            object[name] = value;
+        },
+        del: function (name) {
+            delete object[name];
+        }
+    }); 
+};
+
+/*
+Rejected promises simply forward their rejection to any message.
+*/
+
+var reject = function (reason) {
+    var forward = function (reason) {
+        return reject(reason);
+    };
+    return makePromise({
+        when: function (errback) {
+            errback = errback || forward;
+            return errback(reason);
+        }
+    }, forward);
+};
+
+/*
+Defer sustains very little damage.  Instead of having an array of arguments to
+forward to "then", we have an array of arguments to forward to "promiseSend".
+"makePromise" and "when" absorb the responsibility for handling the callback
+and errback argument defaults and wrappers.
+*/
+
+var defer = function () {
+    var pending = [], value;
+    return {
+        resolve: function (_value) {
+            if (pending) {
+                value = ref(_value);
+                for (var i = 0, ii = pending.length; i < ii; i++) {
+                    enqueue(function () {
+                        value.promiseSend.apply(value, pending[i]);
+                    });
+                }
+                pending = undefined;
+            }
+        },
+        promise: {
+            promiseSend: function () {
+                var args = Array.prototype.slice.call(arguments);
+                var result = defer();
+                if (pending) {
+                    pending.push(args);
+                } else {
+                    enqueue(function () {
+                        value.promiseSend.apply(value, args);
+                    });
+                }
+            }
+        }
+    };
+};
+
+/*
+The last step is to make it syntactically convenient to send messages to
+promises.  We create "get", "put", "post" and "del" functions that send
+the corresponding messages and return promises for the results.  They
+all look very similar.
+*/
+
+var get = function (object, name) {
+    var result = defer();
+    ref(object).promiseSend("get", result.resolve, name);
+    return result.promise;
+};
+
+get({"a": 10}, "a").then(function (ten) {
+    // ten === ten
+});
+
+/*
+
+The last improvment to get promises up to the state-of-the-art is to rename
+all of the callbacks to "win" and all of the errbacks to "fail".  I've left
+this as an exercise.
+
+
+Future
+======
+
+
+Andrew Sutherland did a great exercise in creating a variation of the Q
+library that supported annotations so that waterfalls of promise creation,
+resolution, and dependencies could be graphically depicited.  Optional
+annotations and a debug variation of the Q library would be a logical
+next-step.
+
+There remains some question about how to ideally cancel a promise.  At the
+moment, a secondary channel would have to be used to send the abort message.
+This requires further research.
+
+CommonJS/Promises/A also supports progress notification callbacks.  A
+variation of this library that supports implicit composition and propagation
+of progress information would be very awesome.
+
+It is a common pattern that remote objects have a fixed set of methods, all
+of which return promises.  For those cases, it is a common pattern to create
+a local object that proxies for the remote object by forwarding all of its
+method calls to the remote object using "post".  The construction of such
+proxies could be automated.  Lazy-Arrays are certainly one use-case.
+
+*/
diff --git a/design/q0.js b/design/q0.js
new file mode 100644
index 0000000..c8a0596
--- /dev/null
+++ b/design/q0.js
@@ -0,0 +1,22 @@
+
+var defer = function () {
+    var pending = [], value;
+    return {
+        resolve: function (_value) {
+            value = _value;
+            for (var i = 0, ii = pending.length; i < ii; i++) {
+                var callback = pending[i];
+                callback(value);
+            }
+            pending = undefined;
+        },
+        then: function (callback) {
+            if (pending) {
+                pending.push(callback);
+            } else {
+                callback(value);
+            }
+        }
+    }
+};
+
diff --git a/design/q1.js b/design/q1.js
new file mode 100644
index 0000000..6539204
--- /dev/null
+++ b/design/q1.js
@@ -0,0 +1,26 @@
+
+var defer = function () {
+    var pending = [], value;
+    return {
+        resolve: function (_value) {
+            if (pending) {
+                value = _value;
+                for (var i = 0, ii = pending.length; i < ii; i++) {
+                    var callback = pending[i];
+                    callback(value);
+                }
+                pending = undefined;
+            } else {
+                throw new Error("A promise can only be resolved once.");
+            }
+        },
+        then: function (callback) {
+            if (pending) {
+                pending.push(callback);
+            } else {
+                callback(value);
+            }
+        }
+    }
+};
+
diff --git a/design/q2.js b/design/q2.js
new file mode 100644
index 0000000..0690a47
--- /dev/null
+++ b/design/q2.js
@@ -0,0 +1,33 @@
+
+var Promise = function () {
+};
+
+var isPromise = function (value) {
+    return value instanceof Promise;
+};
+
+var defer = function () {
+    var pending = [], value;
+    var promise = new Promise();
+    promise.then = function (callback) {
+        if (pending) {
+            pending.push(callback);
+        } else {
+            callback(value);
+        }
+    };
+    return {
+        resolve: function (_value) {
+            if (pending) {
+                value = _value;
+                for (var i = 0, ii = pending.length; i < ii; i++) {
+                    var callback = pending[i];
+                    callback(value);
+                }
+                pending = undefined;
+            }
+        },
+        promise: promise
+    };
+};
+
diff --git a/design/q3.js b/design/q3.js
new file mode 100644
index 0000000..90a1270
--- /dev/null
+++ b/design/q3.js
@@ -0,0 +1,30 @@
+
+var isPromise = function (value) {
+    return value && typeof value.then === "function";
+};
+
+var defer = function () {
+    var pending = [], value;
+    return {
+        resolve: function (_value) {
+            if (pending) {
+                value = _value;
+                for (var i = 0, ii = pending.length; i < ii; i++) {
+                    var callback = pending[i];
+                    callback(value);
+                }
+                pending = undefined;
+            }
+        },
+        promise: {
+            then: function (callback) {
+                if (pending) {
+                    pending.push(callback);
+                } else {
+                    callback(value);
+                }
+            }
+        }
+    };
+};
+
diff --git a/design/q4.js b/design/q4.js
new file mode 100644
index 0000000..5ef6049
--- /dev/null
+++ b/design/q4.js
@@ -0,0 +1,48 @@
+
+var isPromise = function (value) {
+    return value && typeof value.then === "function";
+};
+
+var defer = function () {
+    var pending = [], value;
+    return {
+        resolve: function (_value) {
+            if (pending) {
+                value = ref(_value); // values wrapped in a promise
+                for (var i = 0, ii = pending.length; i < ii; i++) {
+                    var callback = pending[i];
+                    value.then(callback); // then called instead
+                }
+                pending = undefined;
+            }
+        },
+        promise: {
+            then: function (_callback) {
+                var result = defer();
+                // callback is wrapped so that its return
+                // value is captured and used to resolve the promise
+                // that "then" returns
+                var callback = function (value) {
+                    result.resolve(_callback(value));
+                };
+                if (pending) {
+                    pending.push(callback);
+                } else {
+                    value.then(callback);
+                }
+                return result.promise;
+            }
+        }
+    };
+};
+
+var ref = function (value) {
+    if (value && typeof value.then === "function")
+        return value;
+    return {
+        then: function (callback) {
+            return ref(callback(value));
+        }
+    };
+};
+
diff --git a/design/q5.js b/design/q5.js
new file mode 100644
index 0000000..0e3bbde
--- /dev/null
+++ b/design/q5.js
@@ -0,0 +1,56 @@
+
+var isPromise = function (value) {
+    return value && typeof value.then === "function";
+};
+
+var defer = function () {
+    var pending = [], value;
+    return {
+        resolve: function (_value) {
+            if (pending) {
+                value = ref(_value);
+                for (var i = 0, ii = pending.length; i < ii; i++) {
+                    // apply the pending arguments to "then"
+                    value.then.apply(value, pending[i]);
+                }
+                pending = undefined;
+            }
+        },
+        promise: {
+            then: function (_callback, _errback) {
+                var result = defer();
+                var callback = function (value) {
+                    result.resolve(_callback(value));
+                };
+                var errback = function (reason) {
+                    result.resolve(_errback(reason));
+                };
+                if (pending) {
+                    pending.push([callback, errback]);
+                } else {
+                    value.then(callback, errback);
+                }
+                return result.promise;
+            }
+        }
+    };
+};
+
+var ref = function (value) {
+    if (value && typeof value.then === "function")
+        return value;
+    return {
+        then: function (callback) {
+            return ref(callback(value));
+        }
+    };
+};
+
+var reject = function (reason) {
+    return {
+        then: function (callback, errback) {
+            return ref(errback(reason));
+        }
+    };
+};
+
diff --git a/design/q6.js b/design/q6.js
new file mode 100644
index 0000000..be61aeb
--- /dev/null
+++ b/design/q6.js
@@ -0,0 +1,64 @@
+
+var isPromise = function (value) {
+    return value && typeof value.then === "function";
+};
+
+var defer = function () {
+    var pending = [], value;
+    return {
+        resolve: function (_value) {
+            if (pending) {
+                value = ref(_value);
+                for (var i = 0, ii = pending.length; i < ii; i++) {
+                    value.then.apply(value, pending[i]);
+                }
+                pending = undefined;
+            }
+        },
+        promise: {
+            then: function (_callback, _errback) {
+                var result = defer();
+                // provide default callbacks and errbacks
+                _callback = _callback || function (value) {
+                    // by default, forward fulfillment
+                    return value;
+                };
+                _errback = _errback || function (reason) {
+                    // by default, forward rejection
+                    return reject(reason);
+                };
+                var callback = function (value) {
+                    result.resolve(_callback(value));
+                };
+                var errback = function (reason) {
+                    result.resolve(_errback(reason));
+                };
+                if (pending) {
+                    pending.push([callback, errback]);
+                } else {
+                    value.then(callback, errback);
+                }
+                return result.promise;
+            }
+        }
+    };
+};
+
+var ref = function (value) {
+    if (value && typeof value.then === "function")
+        return value;
+    return {
+        then: function (callback) {
+            return ref(callback(value));
+        }
+    };
+};
+
+var reject = function (reason) {
+    return {
+        then: function (callback, errback) {
+            return ref(errback(reason));
+        }
+    };
+};
+
diff --git a/design/q7.js b/design/q7.js
new file mode 100644
index 0000000..b0ffe4a
--- /dev/null
+++ b/design/q7.js
@@ -0,0 +1,126 @@
+
+var enqueue = function (callback) {
+    //process.nextTick(callback); // NodeJS
+    setTimeout(callback, 1); // Naïve browser solution
+};
+
+var isPromise = function (value) {
+    return value && typeof value.then === "function";
+};
+
+var defer = function () {
+    var pending = [], value;
+    return {
+        resolve: function (_value) {
+            if (pending) {
+                value = ref(_value);
+                for (var i = 0, ii = pending.length; i < ii; i++) {
+                    // XXX
+                    enqueue(function () {
+                        value.then.apply(value, pending[i]);
+                    });
+                }
+                pending = undefined;
+            }
+        },
+        promise: {
+            then: function (_callback, _errback) {
+                var result = defer();
+                _callback = _callback || function (value) {
+                    return value;
+                };
+                _errback = _errback || function (reason) {
+                    return reject(reason);
+                };
+                var callback = function (value) {
+                    result.resolve(_callback(value));
+                };
+                var errback = function (reason) {
+                    result.resolve(_errback(reason));
+                };
+                if (pending) {
+                    pending.push([callback, errback]);
+                } else {
+                    // XXX
+                    enqueue(function () {
+                        value.then(callback, errback);
+                    });
+                }
+                return result.promise;
+            }
+        }
+    };
+};
+
+var ref = function (value) {
+    if (value && value.then)
+        return value;
+    return {
+        then: function (callback) {
+            var result = defer();
+            // XXX
+            enqueue(function () {
+                result.resolve(callback(value));
+            });
+            return result.promise;
+        }
+    };
+};
+
+var reject = function (reason) {
+    return {
+        then: function (callback, errback) {
+            var result = defer();
+            // XXX
+            enqueue(function () {
+                result.resolve(errback(reason));
+            });
+            return result.promise;
+        }
+    };
+};
+
+var when = function (value, _callback, _errback) {
+    var result = defer();
+    var done;
+
+    _callback = _callback || function (value) {
+        return value;
+    };
+    _errback = _errback || function (reason) {
+        return reject(reason);
+    };
+
+    // XXX
+    var callback = function (value) {
+        try {
+            return _callback(value);
+        } catch (reason) {
+            return reject(reason);
+        }
+    };
+    var errback = function (reason) {
+        try {
+            return _errback(reason);
+        } catch (reason) {
+            return reject(reason);
+        }
+    };
+
+    enqueue(function () {
+        ref(value).then(function (value) {
+            if (done)
+                return;
+            done = true;
+            result.resolve(ref(value).then(callback, errback));
+        }, function (reason) {
+            if (done)
+                return;
+            done = true;
+            result.resolve(errback(reason));
+        });
+    });
+
+    return result.promise;
+};
+
diff --git a/examples/all.js b/examples/all.js
new file mode 100644
index 0000000..4794c5a
--- /dev/null
+++ b/examples/all.js
@@ -0,0 +1,21 @@
+"use strict";
+
+var Q = require("../q");
+
+function eventually(value) {
+    return Q.delay(value, 1000);
+}
+
+Q.all([1, 2, 3].map(eventually))
+.done(function (result) {
+    console.log(x);
+});
+
+Q.all([
+    eventually(10),
+    eventually(20)
+])
+.spread(function (x, y) {
+    console.log(x, y);
+})
+.done();
diff --git a/examples/async-generators/0.html b/examples/async-generators/0.html
new file mode 100644
index 0000000..a4c6552
--- /dev/null
+++ b/examples/async-generators/0.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Q.async animation example</title>
+    <!--
+        Works in browsers that support ES6 geneartors, like Chromium 29 with
+        the --harmony flag.
+
+        Peter Hallam, Tom van Cutsem, Mark S. Miller, Dave Herman, Andy Wingo.
+        The animation example was taken from
+        <http://wiki.ecmascript.org/doku.php?id=strawman:deferred_functions>
+    -->
+</head>
+<body>
+    <div id="box" style="width: 20px; height: 20px; background-color: red;"></div>
+
+    <script src="../../q.js"></script>
+    <script>
+    (function () {
+        "use strict";
+
+        var deferredAnimate = Q.async(function* (element) {
+            for (var i = 0; i < 100; ++i) {
+                element.style.marginLeft = i + "px";
+                yield Q.delay(20);
+            }
+        });
+
+        Q.spawn(function* () {
+            yield deferredAnimate(document.getElementById("box"));
+            alert("Done!");
+        });
+    }());
+    </script>
+</body>
+</html>
diff --git a/examples/async-generators/1-return.js b/examples/async-generators/1-return.js
new file mode 100644
index 0000000..7aab6db
--- /dev/null
+++ b/examples/async-generators/1-return.js
@@ -0,0 +1,19 @@
+"use strict";
+
+var Q = require("../../q");
+
+var generator = Q.async(function* () {
+    var ten = yield 10;
+    console.log(ten, 10);
+    var twenty = yield ten + 10;
+    console.log(twenty, 20);
+    var thirty = yield twenty + 10;
+    console.log(thirty, 30);
+    return thirty + 10;
+});
+
+generator().then(function (forty) {
+    console.log(forty, 40);
+}, function (reason) {
+    console.log("reason", reason);
+});
diff --git a/examples/async-generators/2-error-propagation.js b/examples/async-generators/2-error-propagation.js
new file mode 100644
index 0000000..ccd788b
--- /dev/null
+++ b/examples/async-generators/2-error-propagation.js
@@ -0,0 +1,21 @@
+"use strict";
+
+var Q = require("../../q");
+
+var generator = Q.async(function* () {
+    try {
+        var ten = yield Q.reject(new Error("Rejected!"));
+        console.log("Should not get here 1");
+    } catch (exception) {
+        console.log("Should get here 1");
+        console.log(exception.message, "should be", "Rejected!");
+        throw new Error("Threw!");
+    }
+});
+
+generator().then(function () {
+    console.log("Should not get here 2");
+}, function (reason) {
+    console.log("Should get here 2");
+    console.log(reason.message, "should be", "Threw!");
+});
diff --git a/examples/async-generators/3-spawn.js b/examples/async-generators/3-spawn.js
new file mode 100644
index 0000000..85fe067
--- /dev/null
+++ b/examples/async-generators/3-spawn.js
@@ -0,0 +1,21 @@
+"use strict";
+
+var Q = require("../../q");
+
+function foo() {
+    return Q.delay(5, 1000);
+}
+
+function bar() {
+    return Q.delay(10, 1000);
+}
+
+Q.spawn(function* () {
+    var x = yield foo();
+    console.log(x);
+
+    var y = yield bar();
+    console.log(y);
+
+    console.log("result", x + y);
+});
diff --git a/examples/async-generators/4-flow-control.js b/examples/async-generators/4-flow-control.js
new file mode 100644
index 0000000..017bf8a
--- /dev/null
+++ b/examples/async-generators/4-flow-control.js
@@ -0,0 +1,42 @@
+"use strict";
+
+var Q = require("../../q");
+
+// We get back blocking semantics: can use promises with `if`, `while`, `for`,
+// etc.
+var filter = Q.async(function* (promises, test) {
+    var results = [];
+    for (var i = 0; i < promises.length; i++) {
+        var val = yield promises[i];
+        if (test(val)) {
+            results.push(val);
+        }
+    }
+    return results;
+});
+
+var promises = [
+    Q.delay("a", 500),
+    Q.delay("d", 1000),
+    Q("l")
+];
+
+filter(promises, function (letter) {
+    return "f" > letter;
+}).done(function (all) {
+    console.log(all); // [ "a", "d" ]
+});
+
+
+// we can use try and catch to handle rejected promises
+var logRejections = Q.async(function* (work) {
+    try {
+        yield work;
+        console.log("Never end up here");
+    } catch (e) {
+        console.log("Caught:", e.message);
+    }
+});
+
+var rejection = Q.reject(new Error("Oh dear"));
+logRejections(rejection); // Caught: Oh dear
diff --git a/examples/async-generators/README.md b/examples/async-generators/README.md
new file mode 100644
index 0000000..46dc310
--- /dev/null
+++ b/examples/async-generators/README.md
@@ -0,0 +1,66 @@
+:warning: Warning: The behavior described here is likely to be quickly
+obseleted by developments in standardization and implementation.  Tread with
+care.
+
+Q has an `async` function.  This can be used to decorate a generator function
+such that `yield` is effectively equivalent to `await` or `defer` syntax as
+supported by languages like Go and C# 5.
+
+Generator functions are presently on the standards track for ES6.  As of July
+2013, they are only fully supported by bleeding edge V8, which hasn't made it
+out to a released Chromium yet but will probably be in Chromium 29. Even then,
+they must be enabled from [chrome://flags](chrome://flags) as "Experimental
+JavaScript features." SpiderMonkey (used in Firefox) includes an older style of
+generators, but these are not supported by Q.
+
+Here's an example of using generators by themselves, without any Q features:
+
+```js
+function* count() {
+    var i = 0;
+    while (true) {
+        yield i++;
+    }
+}
+
+var counter = count();
+count.next().value === 0;
+count.next().value === 1;
+count.next().value === 2;
+```
+
+`yield` can also return a value, if the `next` method of the generator is
+called with a parameter:
+
+```js
+var buffer = (function* () {
+    var x;
+    while (true) {
+        x = yield x;
+    }
+}());
+
+buffer.next(1).value === undefined;
+buffer.next("a").value === 1;
+buffer.value(2).value === "a";
+buffer.next().value === 2;
+buffer.next().value === undefined;
+buffer.next().value === undefined;
+```
+
+Inside functions wrapped with `Q.async`, we can use `yield` to wait for a
+promise to settle:
+
+```js
+var eventualAdd = Q.async(function* (oneP, twoP) {
+    var one = yield oneP;
+    var two = yield twoP;
+    return one + two;
+});
+
+eventualAdd(eventualOne, eventualTwo).then(function (three) {
+    three === 3;
+});
+```
+You can see more examples of how this works, as well as the `Q.spawn` function,
+in the other files in this folder.
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..ed22194
--- /dev/null
+++ b/package.json
@@ -0,0 +1,74 @@
+{
+  "name": "q",
+  "version": "1.0.0",
+  "description": "A library for promises (CommonJS/Promises/A,B,D)",
+  "homepage": "https://github.com/kriskowal/q",
+  "author": "Kris Kowal <kris at cixar.com> (https://github.com/kriskowal)",
+  "keywords": [
+    "q",
+    "promise",
+    "promises",
+    "promises-a",
+    "promises-aplus",
+    "deferred",
+    "future",
+    "async",
+    "flow control",
+    "fluent",
+    "browser",
+    "node"
+  ],
+  "contributors": [
+    "Kris Kowal <kris at cixar.com> (https://github.com/kriskowal)",
+    "Irakli Gozalishvili <rfobic at gmail.com> (http://jeditoolkit.com)",
+    "Domenic Denicola <domenic at domenicdenicola.com> (http://domenicdenicola.com)"
+  ],
+  "bugs": {
+    "mail": "kris at cixar.com",
+    "url": "http://github.com/kriskowal/q/issues"
+  },
+  "license": {
+    "type": "MIT",
+    "url": "http://github.com/kriskowal/q/raw/master/LICENSE"
+  },
+  "main": "q.js",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/kriskowal/q.git"
+  },
+  "engines": {
+    "node": ">=0.6.0",
+    "teleport": ">=0.2.0"
+  },
+  "dependencies": {},
+  "devDependencies": {
+    "jshint": "~2.1.9",
+    "cover": "*",
+    "jasmine-node": "1.11.0",
+    "opener": "*",
+    "promises-aplus-tests": "1.x",
+    "grunt": "~0.4.1",
+    "grunt-cli": "~0.1.9",
+    "grunt-contrib-uglify": "~0.2.2",
+    "matcha": "~0.2.0"
+  },
+  "scripts": {
+    "test": "jasmine-node spec && promises-aplus-tests spec/aplus-adapter",
+    "test-browser": "opener spec/q-spec.html",
+    "benchmark": "matcha",
+    "lint": "jshint q.js",
+    "cover": "cover run node_modules/jasmine-node/bin/jasmine-node spec && cover report html && opener cover_html/index.html",
+    "minify": "grunt",
+    "prepublish": "grunt"
+  },
+  "overlay": {
+    "teleport": {
+      "dependencies": {
+        "system": ">=0.0.4"
+      }
+    }
+  },
+  "directories": {
+    "test": "./spec"
+  }
+}
diff --git a/q.js b/q.js
new file mode 100644
index 0000000..5ed1dba
--- /dev/null
+++ b/q.js
@@ -0,0 +1,1935 @@
+// vim:ts=4:sts=4:sw=4:
+/*!
+ *
+ * Copyright 2009-2012 Kris Kowal under the terms of the MIT
+ * license found at http://github.com/kriskowal/q/raw/master/LICENSE
+ *
+ * With parts by Tyler Close
+ * Copyright 2007-2009 Tyler Close under the terms of the MIT X license found
+ * at http://www.opensource.org/licenses/mit-license.html
+ * Forked at ref_send.js version: 2009-05-11
+ *
+ * With parts by Mark Miller
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+(function (definition) {
+    // Turn off strict mode for this function so we can assign to global.Q
+    /* jshint strict: false */
+
+    // This file will function properly as a <script> tag, or a module
+    // using CommonJS and NodeJS or RequireJS module formats.  In
+    // Common/Node/RequireJS, the module exports the Q API and when
+    // executed as a simple <script>, it creates a Q global instead.
+
+    // Montage Require
+    if (typeof bootstrap === "function") {
+        bootstrap("promise", definition);
+
+    // CommonJS
+    } else if (typeof exports === "object") {
+        module.exports = definition();
+
+    // RequireJS
+    } else if (typeof define === "function" && define.amd) {
+        define(definition);
+
+    // SES (Secure EcmaScript)
+    } else if (typeof ses !== "undefined") {
+        if (!ses.ok()) {
+            return;
+        } else {
+            ses.makeQ = definition;
+        }
+
+    // <script>
+    } else {
+        Q = definition();
+    }
+
+})(function () {
+"use strict";
+
+var hasStacks = false;
+try {
+    throw new Error();
+} catch (e) {
+    hasStacks = !!e.stack;
+}
+
+// All code after this point will be filtered from stack traces reported
+// by Q.
+var qStartingLine = captureLine();
+var qFileName;
+
+// shims
+
+// used for fallback in "allResolved"
+var noop = function () {};
+
+// Use the fastest possible means to execute a task in a future turn
+// of the event loop.
+var nextTick =(function () {
+    // linked list of tasks (single, with head node)
+    var head = {task: void 0, next: null};
+    var tail = head;
+    var flushing = false;
+    var requestTick = void 0;
+    var isNodeJS = false;
+
+    function flush() {
+        /* jshint loopfunc: true */
+
+        while (head.next) {
+            head = head.next;
+            var task = head.task;
+            head.task = void 0;
+            var domain = head.domain;
+
+            if (domain) {
+                head.domain = void 0;
+                domain.enter();
+            }
+
+            try {
+                task();
+
+            } catch (e) {
+                if (isNodeJS) {
+                    // In node, uncaught exceptions are considered fatal errors.
+                    // Re-throw them synchronously to interrupt flushing!
+
+                    // Ensure continuation if the uncaught exception is suppressed
+                    // listening "uncaughtException" events (as domains does).
+                    // Continue in next event to avoid tick recursion.
+                    if (domain) {
+                        domain.exit();
+                    }
+                    setTimeout(flush, 0);
+                    if (domain) {
+                        domain.enter();
+                    }
+
+                    throw e;
+
+                } else {
+                    // In browsers, uncaught exceptions are not fatal.
+                    // Re-throw them asynchronously to avoid slow-downs.
+                    setTimeout(function() {
+                       throw e;
+                    }, 0);
+                }
+            }
+
+            if (domain) {
+                domain.exit();
+            }
+        }
+
+        flushing = false;
+    }
+
+    nextTick = function (task) {
+        tail = tail.next = {
+            task: task,
+            domain: isNodeJS && process.domain,
+            next: null
+        };
+
+        if (!flushing) {
+            flushing = true;
+            requestTick();
+        }
+    };
+
+    if (typeof process !== "undefined" && process.nextTick) {
+        // Node.js before 0.9. Note that some fake-Node environments, like the
+        // Mocha test runner, introduce a `process` global without a `nextTick`.
+        isNodeJS = true;
+
+        requestTick = function () {
+            process.nextTick(flush);
+        };
+
+    } else if (typeof setImmediate === "function") {
+        // In IE10, Node.js 0.9+, or https://github.com/NobleJS/setImmediate
+        if (typeof window !== "undefined") {
+            requestTick = setImmediate.bind(window, flush);
+        } else {
+            requestTick = function () {
+                setImmediate(flush);
+            };
+        }
+
+    } else if (typeof MessageChannel !== "undefined") {
+        // modern browsers
+        // http://www.nonblocking.io/2011/06/windownexttick.html
+        var channel = new MessageChannel();
+        // At least Safari Version 6.0.5 (8536.30.1) intermittently cannot create
+        // working message ports the first time a page loads.
+        channel.port1.onmessage = function () {
+            requestTick = requestPortTick;
+            channel.port1.onmessage = flush;
+            flush();
+        };
+        var requestPortTick = function () {
+            // Opera requires us to provide a message payload, regardless of
+            // whether we use it.
+            channel.port2.postMessage(0);
+        };
+        requestTick = function () {
+            setTimeout(flush, 0);
+            requestPortTick();
+        };
+
+    } else {
+        // old browsers
+        requestTick = function () {
+            setTimeout(flush, 0);
+        };
+    }
+
+    return nextTick;
+})();
+
+// Attempt to make generics safe in the face of downstream
+// modifications.
+// There is no situation where this is necessary.
+// If you need a security guarantee, these primordials need to be
+// deeply frozen anyway, and if you don’t need a security guarantee,
+// this is just plain paranoid.
+// However, this **might** have the nice side-effect of reducing the size of
+// the minified code by reducing x.call() to merely x()
+// See Mark Miller’s explanation of what this does.
+// http://wiki.ecmascript.org/doku.php?id=conventions:safe_meta_programming
+var call = Function.call;
+function uncurryThis(f) {
+    return function () {
+        return call.apply(f, arguments);
+    };
+}
+// This is equivalent, but slower:
+// uncurryThis = Function_bind.bind(Function_bind.call);
+// http://jsperf.com/uncurrythis
+
+var array_slice = uncurryThis(Array.prototype.slice);
+
+var array_reduce = uncurryThis(
+    Array.prototype.reduce || function (callback, basis) {
+        var index = 0,
+            length = this.length;
+        // concerning the initial value, if one is not provided
+        if (arguments.length === 1) {
+            // seek to the first value in the array, accounting
+            // for the possibility that is is a sparse array
+            do {
+                if (index in this) {
+                    basis = this[index++];
+                    break;
+                }
+                if (++index >= length) {
+                    throw new TypeError();
+                }
+            } while (1);
+        }
+        // reduce
+        for (; index < length; index++) {
+            // account for the possibility that the array is sparse
+            if (index in this) {
+                basis = callback(basis, this[index], index);
+            }
+        }
+        return basis;
+    }
+);
+
+var array_indexOf = uncurryThis(
+    Array.prototype.indexOf || function (value) {
+        // not a very good shim, but good enough for our one use of it
+        for (var i = 0; i < this.length; i++) {
+            if (this[i] === value) {
+                return i;
+            }
+        }
+        return -1;
+    }
+);
+
+var array_map = uncurryThis(
+    Array.prototype.map || function (callback, thisp) {
+        var self = this;
+        var collect = [];
+        array_reduce(self, function (undefined, value, index) {
+            collect.push(callback.call(thisp, value, index, self));
+        }, void 0);
+        return collect;
+    }
+);
+
+var object_create = Object.create || function (prototype) {
+    function Type() { }
+    Type.prototype = prototype;
+    return new Type();
+};
+
+var object_hasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty);
+
+var object_keys = Object.keys || function (object) {
+    var keys = [];
+    for (var key in object) {
+        if (object_hasOwnProperty(object, key)) {
+            keys.push(key);
+        }
+    }
+    return keys;
+};
+
+var object_toString = uncurryThis(Object.prototype.toString);
+
+function isObject(value) {
+    return value === Object(value);
+}
+
+// generator related shims
+
+// FIXME: Remove this function once ES6 generators are in SpiderMonkey.
+function isStopIteration(exception) {
+    return (
+        object_toString(exception) === "[object StopIteration]" ||
+        exception instanceof QReturnValue
+    );
+}
+
+// FIXME: Remove this helper and Q.return once ES6 generators are in
+// SpiderMonkey.
+var QReturnValue;
+if (typeof ReturnValue !== "undefined") {
+    QReturnValue = ReturnValue;
+} else {
+    QReturnValue = function (value) {
+        this.value = value;
+    };
+}
+
+// Until V8 3.19 / Chromium 29 is released, SpiderMonkey is the only
+// engine that has a deployed base of browsers that support generators.
+// However, SM's generators use the Python-inspired semantics of
+// outdated ES6 drafts.  We would like to support ES6, but we'd also
+// like to make it possible to use generators in deployed browsers, so
+// we also support Python-style generators.  At some point we can remove
+// this block.
+var hasES6Generators;
+try {
+    /* jshint evil: true, nonew: false */
+    new Function("(function* (){ yield 1; })");
+    hasES6Generators = true;
+} catch (e) {
+    hasES6Generators = false;
+}
+
+// long stack traces
+
+var STACK_JUMP_SEPARATOR = "From previous event:";
+
+function makeStackTraceLong(error, promise) {
+    // If possible, transform the error stack trace by removing Node and Q
+    // cruft, then concatenating with the stack trace of `promise`. See #57.
+    if (hasStacks &&
+        promise.stack &&
+        typeof error === "object" &&
+        error !== null &&
+        error.stack &&
+        error.stack.indexOf(STACK_JUMP_SEPARATOR) === -1
+    ) {
+        var stacks = [];
+        for (var p = promise; !!p; p = p.source) {
+            if (p.stack) {
+                stacks.unshift(p.stack);
+            }
+        }
+        stacks.unshift(error.stack);
+
+        var concatedStacks = stacks.join("\n" + STACK_JUMP_SEPARATOR + "\n");
+        error.stack = filterStackString(concatedStacks);
+    }
+}
+
+function filterStackString(stackString) {
+    var lines = stackString.split("\n");
+    var desiredLines = [];
+    for (var i = 0; i < lines.length; ++i) {
+        var line = lines[i];
+
+        if (!isInternalFrame(line) && !isNodeFrame(line) && line) {
+            desiredLines.push(line);
+        }
+    }
+    return desiredLines.join("\n");
+}
+
+function isNodeFrame(stackLine) {
+    return stackLine.indexOf("(module.js:") !== -1 ||
+           stackLine.indexOf("(node.js:") !== -1;
+}
+
+function getFileNameAndLineNumber(stackLine) {
+    // Named functions: "at functionName (filename:lineNumber:columnNumber)"
+    // In IE10 function name can have spaces ("Anonymous function") O_o
+    var attempt1 = /at .+ \((.+):(\d+):(?:\d+)\)$/.exec(stackLine);
+    if (attempt1) {
+        return [attempt1[1], Number(attempt1[2])];
+    }
+
+    // Anonymous functions: "at filename:lineNumber:columnNumber"
+    var attempt2 = /at ([^ ]+):(\d+):(?:\d+)$/.exec(stackLine);
+    if (attempt2) {
+        return [attempt2[1], Number(attempt2[2])];
+    }
+
+    // Firefox style: "function at filename:lineNumber or @filename:lineNumber"
+    var attempt3 = /.*@(.+):(\d+)$/.exec(stackLine);
+    if (attempt3) {
+        return [attempt3[1], Number(attempt3[2])];
+    }
+}
+
+function isInternalFrame(stackLine) {
+    var fileNameAndLineNumber = getFileNameAndLineNumber(stackLine);
+
+    if (!fileNameAndLineNumber) {
+        return false;
+    }
+
+    var fileName = fileNameAndLineNumber[0];
+    var lineNumber = fileNameAndLineNumber[1];
+
+    return fileName === qFileName &&
+        lineNumber >= qStartingLine &&
+        lineNumber <= qEndingLine;
+}
+
+// discover own file name and line number range for filtering stack
+// traces
+function captureLine() {
+    if (!hasStacks) {
+        return;
+    }
+
+    try {
+        throw new Error();
+    } catch (e) {
+        var lines = e.stack.split("\n");
+        var firstLine = lines[0].indexOf("@") > 0 ? lines[1] : lines[2];
+        var fileNameAndLineNumber = getFileNameAndLineNumber(firstLine);
+        if (!fileNameAndLineNumber) {
+            return;
+        }
+
+        qFileName = fileNameAndLineNumber[0];
+        return fileNameAndLineNumber[1];
+    }
+}
+
+function deprecate(callback, name, alternative) {
+    return function () {
+        if (typeof console !== "undefined" &&
+            typeof console.warn === "function") {
+            console.warn(name + " is deprecated, use " + alternative +
+                         " instead.", new Error("").stack);
+        }
+        return callback.apply(callback, arguments);
+    };
+}
+
+// end of shims
+// beginning of real work
+
+/**
+ * Constructs a promise for an immediate reference, passes promises through, or
+ * coerces promises from different systems.
+ * @param value immediate reference or promise
+ */
+function Q(value) {
+    // If the object is already a Promise, return it directly.  This enables
+    // the resolve function to both be used to created references from objects,
+    // but to tolerably coerce non-promises to promises.
+    if (isPromise(value)) {
+        return value;
+    }
+
+    // assimilate thenables
+    if (isPromiseAlike(value)) {
+        return coerce(value);
+    } else {
+        return fulfill(value);
+    }
+}
+Q.resolve = Q;
+
+/**
+ * Performs a task in a future turn of the event loop.
+ * @param {Function} task
+ */
+Q.nextTick = nextTick;
+
+/**
+ * Controls whether or not long stack traces will be on
+ */
+Q.longStackSupport = false;
+
+/**
+ * Constructs a {promise, resolve, reject} object.
+ *
+ * `resolve` is a callback to invoke with a more resolved value for the
+ * promise. To fulfill the promise, invoke `resolve` with any value that is
+ * not a thenable. To reject the promise, invoke `resolve` with a rejected
+ * thenable, or invoke `reject` with the reason directly. To resolve the
+ * promise to another thenable, thus putting it in the same state, invoke
+ * `resolve` with that other thenable.
+ */
+Q.defer = defer;
+function defer() {
+    // if "messages" is an "Array", that indicates that the promise has not yet
+    // been resolved.  If it is "undefined", it has been resolved.  Each
+    // element of the messages array is itself an array of complete arguments to
+    // forward to the resolved promise.  We coerce the resolution value to a
+    // promise using the `resolve` function because it handles both fully
+    // non-thenable values and other thenables gracefully.
+    var messages = [], progressListeners = [], resolvedPromise;
+
+    var deferred = object_create(defer.prototype);
+    var promise = object_create(Promise.prototype);
+
+    promise.promiseDispatch = function (resolve, op, operands) {
+        var args = array_slice(arguments);
+        if (messages) {
+            messages.push(args);
+            if (op === "when" && operands[1]) { // progress operand
+                progressListeners.push(operands[1]);
+            }
+        } else {
+            nextTick(function () {
+                resolvedPromise.promiseDispatch.apply(resolvedPromise, args);
+            });
+        }
+    };
+
+    // XXX deprecated
+    promise.valueOf = function () {
+        if (messages) {
+            return promise;
+        }
+        var nearerValue = nearer(resolvedPromise);
+        if (isPromise(nearerValue)) {
+            resolvedPromise = nearerValue; // shorten chain
+        }
+        return nearerValue;
+    };
+
+    promise.inspect = function () {
+        if (!resolvedPromise) {
+            return { state: "pending" };
+        }
+        return resolvedPromise.inspect();
+    };
+
+    if (Q.longStackSupport && hasStacks) {
+        try {
+            throw new Error();
+        } catch (e) {
+            // NOTE: don't try to use `Error.captureStackTrace` or transfer the
+            // accessor around; that causes memory leaks as per GH-111. Just
+            // reify the stack trace as a string ASAP.
+            //
+            // At the same time, cut off the first line; it's always just
+            // "[object Promise]\n", as per the `toString`.
+            promise.stack = e.stack.substring(e.stack.indexOf("\n") + 1);
+        }
+    }
+
+    // NOTE: we do the checks for `resolvedPromise` in each method, instead of
+    // consolidating them into `become`, since otherwise we'd create new
+    // promises with the lines `become(whatever(value))`. See e.g. GH-252.
+
+    function become(newPromise) {
+        resolvedPromise = newPromise;
+        promise.source = newPromise;
+
+        array_reduce(messages, function (undefined, message) {
+            nextTick(function () {
+                newPromise.promiseDispatch.apply(newPromise, message);
+            });
+        }, void 0);
+
+        messages = void 0;
+        progressListeners = void 0;
+    }
+
+    deferred.promise = promise;
+    deferred.resolve = function (value) {
+        if (resolvedPromise) {
+            return;
+        }
+
+        become(Q(value));
+    };
+
+    deferred.fulfill = function (value) {
+        if (resolvedPromise) {
+            return;
+        }
+
+        become(fulfill(value));
+    };
+    deferred.reject = function (reason) {
+        if (resolvedPromise) {
+            return;
+        }
+
+        become(reject(reason));
+    };
+    deferred.notify = function (progress) {
+        if (resolvedPromise) {
+            return;
+        }
+
+        array_reduce(progressListeners, function (undefined, progressListener) {
+            nextTick(function () {
+                progressListener(progress);
+            });
+        }, void 0);
+    };
+
+    return deferred;
+}
+
+/**
+ * Creates a Node-style callback that will resolve or reject the deferred
+ * promise.
+ * @returns a nodeback
+ */
+defer.prototype.makeNodeResolver = function () {
+    var self = this;
+    return function (error, value) {
+        if (error) {
+            self.reject(error);
+        } else if (arguments.length > 2) {
+            self.resolve(array_slice(arguments, 1));
+        } else {
+            self.resolve(value);
+        }
+    };
+};
+
+/**
+ * @param resolver {Function} a function that returns nothing and accepts
+ * the resolve, reject, and notify functions for a deferred.
+ * @returns a promise that may be resolved with the given resolve and reject
+ * functions, or rejected by a thrown exception in resolver
+ */
+Q.promise = promise;
+function promise(resolver) {
+    if (typeof resolver !== "function") {
+        throw new TypeError("resolver must be a function.");
+    }
+    var deferred = defer();
+    try {
+        resolver(deferred.resolve, deferred.reject, deferred.notify);
+    } catch (reason) {
+        deferred.reject(reason);
+    }
+    return deferred.promise;
+}
+
+// XXX experimental.  This method is a way to denote that a local value is
+// serializable and should be immediately dispatched to a remote upon request,
+// instead of passing a reference.
+Q.passByCopy = function (object) {
+    //freeze(object);
+    //passByCopies.set(object, true);
+    return object;
+};
+
+Promise.prototype.passByCopy = function () {
+    //freeze(object);
+    //passByCopies.set(object, true);
+    return this;
+};
+
+/**
+ * If two promises eventually fulfill to the same value, promises that value,
+ * but otherwise rejects.
+ * @param x {Any*}
+ * @param y {Any*}
+ * @returns {Any*} a promise for x and y if they are the same, but a rejection
+ * otherwise.
+ *
+ */
+Q.join = function (x, y) {
+    return Q(x).join(y);
+};
+
+Promise.prototype.join = function (that) {
+    return Q([this, that]).spread(function (x, y) {
+        if (x === y) {
+            // TODO: "===" should be Object.is or equiv
+            return x;
+        } else {
+            throw new Error("Can't join: not the same: " + x + " " + y);
+        }
+    });
+};
+
+/**
+ * Returns a promise for the first of an array of promises to become fulfilled.
+ * @param answers {Array[Any*]} promises to race
+ * @returns {Any*} the first promise to be fulfilled
+ */
+Q.race = race;
+function race(answerPs) {
+    return promise(function(resolve, reject) {
+        // Switch to this once we can assume at least ES5
+        // answerPs.forEach(function(answerP) {
+        //     Q(answerP).then(resolve, reject);
+        // });
+        // Use this in the meantime
+        for (var i = 0, len = answerPs.length; i < len; i++) {
+            Q(answerPs[i]).then(resolve, reject);
+        }
+    });
+}
+
+Promise.prototype.race = function () {
+    return this.then(Q.race);
+};
+
+/**
+ * Constructs a Promise with a promise descriptor object and optional fallback
+ * function.  The descriptor contains methods like when(rejected), get(name),
+ * set(name, value), post(name, args), and delete(name), which all
+ * return either a value, a promise for a value, or a rejection.  The fallback
+ * accepts the operation name, a resolver, and any further arguments that would
+ * have been forwarded to the appropriate method above had a method been
+ * provided with the proper name.  The API makes no guarantees about the nature
+ * of the returned object, apart from that it is usable whereever promises are
+ * bought and sold.
+ */
+Q.makePromise = Promise;
+function Promise(descriptor, fallback, inspect) {
+    if (fallback === void 0) {
+        fallback = function (op) {
+            return reject(new Error(
+                "Promise does not support operation: " + op
+            ));
+        };
+    }
+    if (inspect === void 0) {
+        inspect = function () {
+            return {state: "unknown"};
+        };
+    }
+
+    var promise = object_create(Promise.prototype);
+
+    promise.promiseDispatch = function (resolve, op, args) {
+        var result;
+        try {
+            if (descriptor[op]) {
+                result = descriptor[op].apply(promise, args);
+            } else {
+                result = fallback.call(promise, op, args);
+            }
+        } catch (exception) {
+            result = reject(exception);
+        }
+        if (resolve) {
+            resolve(result);
+        }
+    };
+
+    promise.inspect = inspect;
+
+    // XXX deprecated `valueOf` and `exception` support
+    if (inspect) {
+        var inspected = inspect();
+        if (inspected.state === "rejected") {
+            promise.exception = inspected.reason;
+        }
+
+        promise.valueOf = function () {
+            var inspected = inspect();
+            if (inspected.state === "pending" ||
+                inspected.state === "rejected") {
+                return promise;
+            }
+            return inspected.value;
+        };
+    }
+
+    return promise;
+}
+
+Promise.prototype.toString = function () {
+    return "[object Promise]";
+};
+
+Promise.prototype.then = function (fulfilled, rejected, progressed) {
+    var self = this;
+    var deferred = defer();
+    var done = false;   // ensure the untrusted promise makes at most a
+                        // single call to one of the callbacks
+
+    function _fulfilled(value) {
+        try {
+            return typeof fulfilled === "function" ? fulfilled(value) : value;
+        } catch (exception) {
+            return reject(exception);
+        }
+    }
+
+    function _rejected(exception) {
+        if (typeof rejected === "function") {
+            makeStackTraceLong(exception, self);
+            try {
+                return rejected(exception);
+            } catch (newException) {
+                return reject(newException);
+            }
+        }
+        return reject(exception);
+    }
+
+    function _progressed(value) {
+        return typeof progressed === "function" ? progressed(value) : value;
+    }
+
+    nextTick(function () {
+        self.promiseDispatch(function (value) {
+            if (done) {
+                return;
+            }
+            done = true;
+
+            deferred.resolve(_fulfilled(value));
+        }, "when", [function (exception) {
+            if (done) {
+                return;
+            }
+            done = true;
+
+            deferred.resolve(_rejected(exception));
+        }]);
+    });
+
+    // Progress propagator need to be attached in the current tick.
+    self.promiseDispatch(void 0, "when", [void 0, function (value) {
+        var newValue;
+        var threw = false;
+        try {
+            newValue = _progressed(value);
+        } catch (e) {
+            threw = true;
+            if (Q.onerror) {
+                Q.onerror(e);
+            } else {
+                throw e;
+            }
+        }
+
+        if (!threw) {
+            deferred.notify(newValue);
+        }
+    }]);
+
+    return deferred.promise;
+};
+
+/**
+ * Registers an observer on a promise.
+ *
+ * Guarantees:
+ *
+ * 1. that fulfilled and rejected will be called only once.
+ * 2. that either the fulfilled callback or the rejected callback will be
+ *    called, but not both.
+ * 3. that fulfilled and rejected will not be called in this turn.
+ *
+ * @param value      promise or immediate reference to observe
+ * @param fulfilled  function to be called with the fulfilled value
+ * @param rejected   function to be called with the rejection exception
+ * @param progressed function to be called on any progress notifications
+ * @return promise for the return value from the invoked callback
+ */
+Q.when = when;
+function when(value, fulfilled, rejected, progressed) {
+    return Q(value).then(fulfilled, rejected, progressed);
+}
+
+Promise.prototype.thenResolve = function (value) {
+    return this.then(function () { return value; });
+};
+
+Q.thenResolve = function (promise, value) {
+    return Q(promise).thenResolve(value);
+};
+
+Promise.prototype.thenReject = function (reason) {
+    return this.then(function () { throw reason; });
+};
+
+Q.thenReject = function (promise, reason) {
+    return Q(promise).thenReject(reason);
+};
+
+/**
+ * If an object is not a promise, it is as "near" as possible.
+ * If a promise is rejected, it is as "near" as possible too.
+ * If it’s a fulfilled promise, the fulfillment value is nearer.
+ * If it’s a deferred promise and the deferred has been resolved, the
+ * resolution is "nearer".
+ * @param object
+ * @returns most resolved (nearest) form of the object
+ */
+
+// XXX should we re-do this?
+Q.nearer = nearer;
+function nearer(value) {
+    if (isPromise(value)) {
+        var inspected = value.inspect();
+        if (inspected.state === "fulfilled") {
+            return inspected.value;
+        }
+    }
+    return value;
+}
+
+/**
+ * @returns whether the given object is a promise.
+ * Otherwise it is a fulfilled value.
+ */
+Q.isPromise = isPromise;
+function isPromise(object) {
+    return isObject(object) &&
+        typeof object.promiseDispatch === "function" &&
+        typeof object.inspect === "function";
+}
+
+Q.isPromiseAlike = isPromiseAlike;
+function isPromiseAlike(object) {
+    return isObject(object) && typeof object.then === "function";
+}
+
+/**
+ * @returns whether the given object is a pending promise, meaning not
+ * fulfilled or rejected.
+ */
+Q.isPending = isPending;
+function isPending(object) {
+    return isPromise(object) && object.inspect().state === "pending";
+}
+
+Promise.prototype.isPending = function () {
+    return this.inspect().state === "pending";
+};
+
+/**
+ * @returns whether the given object is a value or fulfilled
+ * promise.
+ */
+Q.isFulfilled = isFulfilled;
+function isFulfilled(object) {
+    return !isPromise(object) || object.inspect().state === "fulfilled";
+}
+
+Promise.prototype.isFulfilled = function () {
+    return this.inspect().state === "fulfilled";
+};
+
+/**
+ * @returns whether the given object is a rejected promise.
+ */
+Q.isRejected = isRejected;
+function isRejected(object) {
+    return isPromise(object) && object.inspect().state === "rejected";
+}
+
+Promise.prototype.isRejected = function () {
+    return this.inspect().state === "rejected";
+};
+
+//// BEGIN UNHANDLED REJECTION TRACKING
+
+// This promise library consumes exceptions thrown in handlers so they can be
+// handled by a subsequent promise.  The exceptions get added to this array when
+// they are created, and removed when they are handled.  Note that in ES6 or
+// shimmed environments, this would naturally be a `Set`.
+var unhandledReasons = [];
+var unhandledRejections = [];
+var unhandledReasonsDisplayed = false;
+var trackUnhandledRejections = true;
+function displayUnhandledReasons() {
+    if (
+        !unhandledReasonsDisplayed &&
+        typeof window !== "undefined" &&
+        window.console
+    ) {
+        console.warn("[Q] Unhandled rejection reasons (should be empty):",
+                     unhandledReasons);
+    }
+
+    unhandledReasonsDisplayed = true;
+}
+
+function logUnhandledReasons() {
+    for (var i = 0; i < unhandledReasons.length; i++) {
+        var reason = unhandledReasons[i];
+        console.warn("Unhandled rejection reason:", reason);
+    }
+}
+
+function resetUnhandledRejections() {
+    unhandledReasons.length = 0;
+    unhandledRejections.length = 0;
+    unhandledReasonsDisplayed = false;
+
+    if (!trackUnhandledRejections) {
+        trackUnhandledRejections = true;
+
+        // Show unhandled rejection reasons if Node exits without handling an
+        // outstanding rejection.  (Note that Browserify presently produces a
+        // `process` global without the `EventEmitter` `on` method.)
+        if (typeof process !== "undefined" && process.on) {
+            process.on("exit", logUnhandledReasons);
+        }
+    }
+}
+
+function trackRejection(promise, reason) {
+    if (!trackUnhandledRejections) {
+        return;
+    }
+
+    unhandledRejections.push(promise);
+    if (reason && typeof reason.stack !== "undefined") {
+        unhandledReasons.push(reason.stack);
+    } else {
+        unhandledReasons.push("(no stack) " + reason);
+    }
+    displayUnhandledReasons();
+}
+
+function untrackRejection(promise) {
+    if (!trackUnhandledRejections) {
+        return;
+    }
+
+    var at = array_indexOf(unhandledRejections, promise);
+    if (at !== -1) {
+        unhandledRejections.splice(at, 1);
+        unhandledReasons.splice(at, 1);
+    }
+}
+
+Q.resetUnhandledRejections = resetUnhandledRejections;
+
+Q.getUnhandledReasons = function () {
+    // Make a copy so that consumers can't interfere with our internal state.
+    return unhandledReasons.slice();
+};
+
+Q.stopUnhandledRejectionTracking = function () {
+    resetUnhandledRejections();
+    if (typeof process !== "undefined" && process.on) {
+        process.removeListener("exit", logUnhandledReasons);
+    }
+    trackUnhandledRejections = false;
+};
+
+resetUnhandledRejections();
+
+//// END UNHANDLED REJECTION TRACKING
+
+/**
+ * Constructs a rejected promise.
+ * @param reason value describing the failure
+ */
+Q.reject = reject;
+function reject(reason) {
+    var rejection = Promise({
+        "when": function (rejected) {
+            // note that the error has been handled
+            if (rejected) {
+                untrackRejection(this);
+            }
+            return rejected ? rejected(reason) : this;
+        }
+    }, function fallback() {
+        return this;
+    }, function inspect() {
+        return { state: "rejected", reason: reason };
+    });
+
+    // Note that the reason has not been handled.
+    trackRejection(rejection, reason);
+
+    return rejection;
+}
+
+/**
+ * Constructs a fulfilled promise for an immediate reference.
+ * @param value immediate reference
+ */
+Q.fulfill = fulfill;
+function fulfill(value) {
+    return Promise({
+        "when": function () {
+            return value;
+        },
+        "get": function (name) {
+            return value[name];
+        },
+        "set": function (name, rhs) {
+            value[name] = rhs;
+        },
+        "delete": function (name) {
+            delete value[name];
+        },
+        "post": function (name, args) {
+            // Mark Miller proposes that post with no name should apply a
+            // promised function.
+            if (name === null || name === void 0) {
+                return value.apply(void 0, args);
+            } else {
+                return value[name].apply(value, args);
+            }
+        },
+        "apply": function (thisp, args) {
+            return value.apply(thisp, args);
+        },
+        "keys": function () {
+            return object_keys(value);
+        }
+    }, void 0, function inspect() {
+        return { state: "fulfilled", value: value };
+    });
+}
+
+/**
+ * Converts thenables to Q promises.
+ * @param promise thenable promise
+ * @returns a Q promise
+ */
+function coerce(promise) {
+    var deferred = defer();
+    nextTick(function () {
+        try {
+            promise.then(deferred.resolve, deferred.reject, deferred.notify);
+        } catch (exception) {
+            deferred.reject(exception);
+        }
+    });
+    return deferred.promise;
+}
+
+/**
+ * Annotates an object such that it will never be
+ * transferred away from this process over any promise
+ * communication channel.
+ * @param object
+ * @returns promise a wrapping of that object that
+ * additionally responds to the "isDef" message
+ * without a rejection.
+ */
+Q.master = master;
+function master(object) {
+    return Promise({
+        "isDef": function () {}
+    }, function fallback(op, args) {
+        return dispatch(object, op, args);
+    }, function () {
+        return Q(object).inspect();
+    });
+}
+
+/**
+ * Spreads the values of a promised array of arguments into the
+ * fulfillment callback.
+ * @param fulfilled callback that receives variadic arguments from the
+ * promised array
+ * @param rejected callback that receives the exception if the promise
+ * is rejected.
+ * @returns a promise for the return value or thrown exception of
+ * either callback.
+ */
+Q.spread = spread;
+function spread(value, fulfilled, rejected) {
+    return Q(value).spread(fulfilled, rejected);
+}
+
+Promise.prototype.spread = function (fulfilled, rejected) {
+    return this.all().then(function (array) {
+        return fulfilled.apply(void 0, array);
+    }, rejected);
+};
+
+/**
+ * The async function is a decorator for generator functions, turning
+ * them into asynchronous generators.  Although generators are only part
+ * of the newest ECMAScript 6 drafts, this code does not cause syntax
+ * errors in older engines.  This code should continue to work and will
+ * in fact improve over time as the language improves.
+ *
+ * ES6 generators are currently part of V8 version 3.19 with the
+ * --harmony-generators runtime flag enabled.  SpiderMonkey has had them
+ * for longer, but under an older Python-inspired form.  This function
+ * works on both kinds of generators.
+ *
+ * Decorates a generator function such that:
+ *  - it may yield promises
+ *  - execution will continue when that promise is fulfilled
+ *  - the value of the yield expression will be the fulfilled value
+ *  - it returns a promise for the return value (when the generator
+ *    stops iterating)
+ *  - the decorated function returns a promise for the return value
+ *    of the generator or the first rejected promise among those
+ *    yielded.
+ *  - if an error is thrown in the generator, it propagates through
+ *    every following yield until it is caught, or until it escapes
+ *    the generator function altogether, and is translated into a
+ *    rejection for the promise returned by the decorated generator.
+ */
+Q.async = async;
+function async(makeGenerator) {
+    return function () {
+        // when verb is "send", arg is a value
+        // when verb is "throw", arg is an exception
+        function continuer(verb, arg) {
+            var result;
+            if (hasES6Generators) {
+                try {
+                    result = generator[verb](arg);
+                } catch (exception) {
+                    return reject(exception);
+                }
+                if (result.done) {
+                    return result.value;
+                } else {
+                    return when(result.value, callback, errback);
+                }
+            } else {
+                // FIXME: Remove this case when SM does ES6 generators.
+                try {
+                    result = generator[verb](arg);
+                } catch (exception) {
+                    if (isStopIteration(exception)) {
+                        return exception.value;
+                    } else {
+                        return reject(exception);
+                    }
+                }
+                return when(result, callback, errback);
+            }
+        }
+        var generator = makeGenerator.apply(this, arguments);
+        var callback = continuer.bind(continuer, "next");
+        var errback = continuer.bind(continuer, "throw");
+        return callback();
+    };
+}
+
+/**
+ * The spawn function is a small wrapper around async that immediately
+ * calls the generator and also ends the promise chain, so that any
+ * unhandled errors are thrown instead of forwarded to the error
+ * handler. This is useful because it's extremely common to run
+ * generators at the top-level to work with libraries.
+ */
+Q.spawn = spawn;
+function spawn(makeGenerator) {
+    Q.done(Q.async(makeGenerator)());
+}
+
+// FIXME: Remove this interface once ES6 generators are in SpiderMonkey.
+/**
+ * Throws a ReturnValue exception to stop an asynchronous generator.
+ *
+ * This interface is a stop-gap measure to support generator return
+ * values in older Firefox/SpiderMonkey.  In browsers that support ES6
+ * generators like Chromium 29, just use "return" in your generator
+ * functions.
+ *
+ * @param value the return value for the surrounding generator
+ * @throws ReturnValue exception with the value.
+ * @example
+ * // ES6 style
+ * Q.async(function* () {
+ *      var foo = yield getFooPromise();
+ *      var bar = yield getBarPromise();
+ *      return foo + bar;
+ * })
+ * // Older SpiderMonkey style
+ * Q.async(function () {
+ *      var foo = yield getFooPromise();
+ *      var bar = yield getBarPromise();
+ *      Q.return(foo + bar);
+ * })
+ */
+Q["return"] = _return;
+function _return(value) {
+    throw new QReturnValue(value);
+}
+
+/**
+ * The promised function decorator ensures that any promise arguments
+ * are settled and passed as values (`this` is also settled and passed
+ * as a value).  It will also ensure that the result of a function is
+ * always a promise.
+ *
+ * @example
+ * var add = Q.promised(function (a, b) {
+ *     return a + b;
+ * });
+ * add(Q(a), Q(B));
+ *
+ * @param {function} callback The function to decorate
+ * @returns {function} a function that has been decorated.
+ */
+Q.promised = promised;
+function promised(callback) {
+    return function () {
+        return spread([this, all(arguments)], function (self, args) {
+            return callback.apply(self, args);
+        });
+    };
+}
+
+/**
+ * sends a message to a value in a future turn
+ * @param object* the recipient
+ * @param op the name of the message operation, e.g., "when",
+ * @param args further arguments to be forwarded to the operation
+ * @returns result {Promise} a promise for the result of the operation
+ */
+Q.dispatch = dispatch;
+function dispatch(object, op, args) {
+    return Q(object).dispatch(op, args);
+}
+
+Promise.prototype.dispatch = function (op, args) {
+    var self = this;
+    var deferred = defer();
+    nextTick(function () {
+        self.promiseDispatch(deferred.resolve, op, args);
+    });
+    return deferred.promise;
+};
+
+/**
+ * Gets the value of a property in a future turn.
+ * @param object    promise or immediate reference for target object
+ * @param name      name of property to get
+ * @return promise for the property value
+ */
+Q.get = function (object, key) {
+    return Q(object).dispatch("get", [key]);
+};
+
+Promise.prototype.get = function (key) {
+    return this.dispatch("get", [key]);
+};
+
+/**
+ * Sets the value of a property in a future turn.
+ * @param object    promise or immediate reference for object object
+ * @param name      name of property to set
+ * @param value     new value of property
+ * @return promise for the return value
+ */
+Q.set = function (object, key, value) {
+    return Q(object).dispatch("set", [key, value]);
+};
+
+Promise.prototype.set = function (key, value) {
+    return this.dispatch("set", [key, value]);
+};
+
+/**
+ * Deletes a property in a future turn.
+ * @param object    promise or immediate reference for target object
+ * @param name      name of property to delete
+ * @return promise for the return value
+ */
+Q.del = // XXX legacy
+Q["delete"] = function (object, key) {
+    return Q(object).dispatch("delete", [key]);
+};
+
+Promise.prototype.del = // XXX legacy
+Promise.prototype["delete"] = function (key) {
+    return this.dispatch("delete", [key]);
+};
+
+/**
+ * Invokes a method in a future turn.
+ * @param object    promise or immediate reference for target object
+ * @param name      name of method to invoke
+ * @param value     a value to post, typically an array of
+ *                  invocation arguments for promises that
+ *                  are ultimately backed with `resolve` values,
+ *                  as opposed to those backed with URLs
+ *                  wherein the posted value can be any
+ *                  JSON serializable object.
+ * @return promise for the return value
+ */
+// bound locally because it is used by other methods
+Q.mapply = // XXX As proposed by "Redsandro"
+Q.post = function (object, name, args) {
+    return Q(object).dispatch("post", [name, args]);
+};
+
+Promise.prototype.mapply = // XXX As proposed by "Redsandro"
+Promise.prototype.post = function (name, args) {
+    return this.dispatch("post", [name, args]);
+};
+
+/**
+ * Invokes a method in a future turn.
+ * @param object    promise or immediate reference for target object
+ * @param name      name of method to invoke
+ * @param ...args   array of invocation arguments
+ * @return promise for the return value
+ */
+Q.send = // XXX Mark Miller's proposed parlance
+Q.mcall = // XXX As proposed by "Redsandro"
+Q.invoke = function (object, name /*...args*/) {
+    return Q(object).dispatch("post", [name, array_slice(arguments, 2)]);
+};
+
+Promise.prototype.send = // XXX Mark Miller's proposed parlance
+Promise.prototype.mcall = // XXX As proposed by "Redsandro"
+Promise.prototype.invoke = function (name /*...args*/) {
+    return this.dispatch("post", [name, array_slice(arguments, 1)]);
+};
+
+/**
+ * Applies the promised function in a future turn.
+ * @param object    promise or immediate reference for target function
+ * @param args      array of application arguments
+ */
+Q.fapply = function (object, args) {
+    return Q(object).dispatch("apply", [void 0, args]);
+};
+
+Promise.prototype.fapply = function (args) {
+    return this.dispatch("apply", [void 0, args]);
+};
+
+/**
+ * Calls the promised function in a future turn.
+ * @param object    promise or immediate reference for target function
+ * @param ...args   array of application arguments
+ */
+Q["try"] =
+Q.fcall = function (object /* ...args*/) {
+    return Q(object).dispatch("apply", [void 0, array_slice(arguments, 1)]);
+};
+
+Promise.prototype.fcall = function (/*...args*/) {
+    return this.dispatch("apply", [void 0, array_slice(arguments)]);
+};
+
+/**
+ * Binds the promised function, transforming return values into a fulfilled
+ * promise and thrown errors into a rejected one.
+ * @param object    promise or immediate reference for target function
+ * @param ...args   array of application arguments
+ */
+Q.fbind = function (object /*...args*/) {
+    var promise = Q(object);
+    var args = array_slice(arguments, 1);
+    return function fbound() {
+        return promise.dispatch("apply", [
+            this,
+            args.concat(array_slice(arguments))
+        ]);
+    };
+};
+Promise.prototype.fbind = function (/*...args*/) {
+    var promise = this;
+    var args = array_slice(arguments);
+    return function fbound() {
+        return promise.dispatch("apply", [
+            this,
+            args.concat(array_slice(arguments))
+        ]);
+    };
+};
+
+/**
+ * Requests the names of the owned properties of a promised
+ * object in a future turn.
+ * @param object    promise or immediate reference for target object
+ * @return promise for the keys of the eventually settled object
+ */
+Q.keys = function (object) {
+    return Q(object).dispatch("keys", []);
+};
+
+Promise.prototype.keys = function () {
+    return this.dispatch("keys", []);
+};
+
+/**
+ * Turns an array of promises into a promise for an array.  If any of
+ * the promises gets rejected, the whole array is rejected immediately.
+ * @param {Array*} an array (or promise for an array) of values (or
+ * promises for values)
+ * @returns a promise for an array of the corresponding values
+ */
+// By Mark Miller
+// http://wiki.ecmascript.org/doku.php?id=strawman:concurrency&rev=1308776521#allfulfilled
+Q.all = all;
+function all(promises) {
+    return when(promises, function (promises) {
+        var countDown = 0;
+        var deferred = defer();
+        array_reduce(promises, function (undefined, promise, index) {
+            var snapshot;
+            if (
+                isPromise(promise) &&
+                (snapshot = promise.inspect()).state === "fulfilled"
+            ) {
+                promises[index] = snapshot.value;
+            } else {
+                ++countDown;
+                when(
+                    promise,
+                    function (value) {
+                        promises[index] = value;
+                        if (--countDown === 0) {
+                            deferred.resolve(promises);
+                        }
+                    },
+                    deferred.reject,
+                    function (progress) {
+                        deferred.notify({ index: index, value: progress });
+                    }
+                );
+            }
+        }, void 0);
+        if (countDown === 0) {
+            deferred.resolve(promises);
+        }
+        return deferred.promise;
+    });
+}
+
+Promise.prototype.all = function () {
+    return all(this);
+};
+
+/**
+ * Waits for all promises to be settled, either fulfilled or
+ * rejected.  This is distinct from `all` since that would stop
+ * waiting at the first rejection.  The promise returned by
+ * `allResolved` will never be rejected.
+ * @param promises a promise for an array (or an array) of promises
+ * (or values)
+ * @return a promise for an array of promises
+ */
+Q.allResolved = deprecate(allResolved, "allResolved", "allSettled");
+function allResolved(promises) {
+    return when(promises, function (promises) {
+        promises = array_map(promises, Q);
+        return when(all(array_map(promises, function (promise) {
+            return when(promise, noop, noop);
+        })), function () {
+            return promises;
+        });
+    });
+}
+
+Promise.prototype.allResolved = function () {
+    return allResolved(this);
+};
+
+/**
+ * @see Promise#allSettled
+ */
+Q.allSettled = allSettled;
+function allSettled(promises) {
+    return Q(promises).allSettled();
+}
+
+/**
+ * Turns an array of promises into a promise for an array of their states (as
+ * returned by `inspect`) when they have all settled.
+ * @param {Array[Any*]} values an array (or promise for an array) of values (or
+ * promises for values)
+ * @returns {Array[State]} an array of states for the respective values.
+ */
+Promise.prototype.allSettled = function () {
+    return this.then(function (promises) {
+        return all(array_map(promises, function (promise) {
+            promise = Q(promise);
+            function regardless() {
+                return promise.inspect();
+            }
+            return promise.then(regardless, regardless);
+        }));
+    });
+};
+
+/**
+ * Captures the failure of a promise, giving an oportunity to recover
+ * with a callback.  If the given promise is fulfilled, the returned
+ * promise is fulfilled.
+ * @param {Any*} promise for something
+ * @param {Function} callback to fulfill the returned promise if the
+ * given promise is rejected
+ * @returns a promise for the return value of the callback
+ */
+Q.fail = // XXX legacy
+Q["catch"] = function (object, rejected) {
+    return Q(object).then(void 0, rejected);
+};
+
+Promise.prototype.fail = // XXX legacy
+Promise.prototype["catch"] = function (rejected) {
+    return this.then(void 0, rejected);
+};
+
+/**
+ * Attaches a listener that can respond to progress notifications from a
+ * promise's originating deferred. This listener receives the exact arguments
+ * passed to ``deferred.notify``.
+ * @param {Any*} promise for something
+ * @param {Function} callback to receive any progress notifications
+ * @returns the given promise, unchanged
+ */
+Q.progress = progress;
+function progress(object, progressed) {
+    return Q(object).then(void 0, void 0, progressed);
+}
+
+Promise.prototype.progress = function (progressed) {
+    return this.then(void 0, void 0, progressed);
+};
+
+/**
+ * Provides an opportunity to observe the settling of a promise,
+ * regardless of whether the promise is fulfilled or rejected.  Forwards
+ * the resolution to the returned promise when the callback is done.
+ * The callback can return a promise to defer completion.
+ * @param {Any*} promise
+ * @param {Function} callback to observe the resolution of the given
+ * promise, takes no arguments.
+ * @returns a promise for the resolution of the given promise when
+ * ``fin`` is done.
+ */
+Q.fin = // XXX legacy
+Q["finally"] = function (object, callback) {
+    return Q(object)["finally"](callback);
+};
+
+Promise.prototype.fin = // XXX legacy
+Promise.prototype["finally"] = function (callback) {
+    callback = Q(callback);
+    return this.then(function (value) {
+        return callback.fcall().then(function () {
+            return value;
+        });
+    }, function (reason) {
+        // TODO attempt to recycle the rejection with "this".
+        return callback.fcall().then(function () {
+            throw reason;
+        });
+    });
+};
+
+/**
+ * Terminates a chain of promises, forcing rejections to be
+ * thrown as exceptions.
+ * @param {Any*} promise at the end of a chain of promises
+ * @returns nothing
+ */
+Q.done = function (object, fulfilled, rejected, progress) {
+    return Q(object).done(fulfilled, rejected, progress);
+};
+
+Promise.prototype.done = function (fulfilled, rejected, progress) {
+    var onUnhandledError = function (error) {
+        // forward to a future turn so that ``when``
+        // does not catch it and turn it into a rejection.
+        nextTick(function () {
+            makeStackTraceLong(error, promise);
+            if (Q.onerror) {
+                Q.onerror(error);
+            } else {
+                throw error;
+            }
+        });
+    };
+
+    // Avoid unnecessary `nextTick`ing via an unnecessary `when`.
+    var promise = fulfilled || rejected || progress ?
+        this.then(fulfilled, rejected, progress) :
+        this;
+
+    if (typeof process === "object" && process && process.domain) {
+        onUnhandledError = process.domain.bind(onUnhandledError);
+    }
+
+    promise.then(void 0, onUnhandledError);
+};
+
+/**
+ * Causes a promise to be rejected if it does not get fulfilled before
+ * some milliseconds time out.
+ * @param {Any*} promise
+ * @param {Number} milliseconds timeout
+ * @param {String} custom error message (optional)
+ * @returns a promise for the resolution of the given promise if it is
+ * fulfilled before the timeout, otherwise rejected.
+ */
+Q.timeout = function (object, ms, message) {
+    return Q(object).timeout(ms, message);
+};
+
+Promise.prototype.timeout = function (ms, message) {
+    var deferred = defer();
+    var timeoutId = setTimeout(function () {
+        deferred.reject(new Error(message || "Timed out after " + ms + " ms"));
+    }, ms);
+
+    this.then(function (value) {
+        clearTimeout(timeoutId);
+        deferred.resolve(value);
+    }, function (exception) {
+        clearTimeout(timeoutId);
+        deferred.reject(exception);
+    }, deferred.notify);
+
+    return deferred.promise;
+};
+
+/**
+ * Returns a promise for the given value (or promised value), some
+ * milliseconds after it resolved. Passes rejections immediately.
+ * @param {Any*} promise
+ * @param {Number} milliseconds
+ * @returns a promise for the resolution of the given promise after milliseconds
+ * time has elapsed since the resolution of the given promise.
+ * If the given promise rejects, that is passed immediately.
+ */
+Q.delay = function (object, timeout) {
+    if (timeout === void 0) {
+        timeout = object;
+        object = void 0;
+    }
+    return Q(object).delay(timeout);
+};
+
+Promise.prototype.delay = function (timeout) {
+    return this.then(function (value) {
+        var deferred = defer();
+        setTimeout(function () {
+            deferred.resolve(value);
+        }, timeout);
+        return deferred.promise;
+    });
+};
+
+/**
+ * Passes a continuation to a Node function, which is called with the given
+ * arguments provided as an array, and returns a promise.
+ *
+ *      Q.nfapply(FS.readFile, [__filename])
+ *      .then(function (content) {
+ *      })
+ *
+ */
+Q.nfapply = function (callback, args) {
+    return Q(callback).nfapply(args);
+};
+
+Promise.prototype.nfapply = function (args) {
+    var deferred = defer();
+    var nodeArgs = array_slice(args);
+    nodeArgs.push(deferred.makeNodeResolver());
+    this.fapply(nodeArgs).fail(deferred.reject);
+    return deferred.promise;
+};
+
+/**
+ * Passes a continuation to a Node function, which is called with the given
+ * arguments provided individually, and returns a promise.
+ * @example
+ * Q.nfcall(FS.readFile, __filename)
+ * .then(function (content) {
+ * })
+ *
+ */
+Q.nfcall = function (callback /*...args*/) {
+    var args = array_slice(arguments, 1);
+    return Q(callback).nfapply(args);
+};
+
+Promise.prototype.nfcall = function (/*...args*/) {
+    var nodeArgs = array_slice(arguments);
+    var deferred = defer();
+    nodeArgs.push(deferred.makeNodeResolver());
+    this.fapply(nodeArgs).fail(deferred.reject);
+    return deferred.promise;
+};
+
+/**
+ * Wraps a NodeJS continuation passing function and returns an equivalent
+ * version that returns a promise.
+ * @example
+ * Q.nfbind(FS.readFile, __filename)("utf-8")
+ * .then(console.log)
+ * .done()
+ */
+Q.nfbind =
+Q.denodeify = function (callback /*...args*/) {
+    var baseArgs = array_slice(arguments, 1);
+    return function () {
+        var nodeArgs = baseArgs.concat(array_slice(arguments));
+        var deferred = defer();
+        nodeArgs.push(deferred.makeNodeResolver());
+        Q(callback).fapply(nodeArgs).fail(deferred.reject);
+        return deferred.promise;
+    };
+};
+
+Promise.prototype.nfbind =
+Promise.prototype.denodeify = function (/*...args*/) {
+    var args = array_slice(arguments);
+    args.unshift(this);
+    return Q.denodeify.apply(void 0, args);
+};
+
+Q.nbind = function (callback, thisp /*...args*/) {
+    var baseArgs = array_slice(arguments, 2);
+    return function () {
+        var nodeArgs = baseArgs.concat(array_slice(arguments));
+        var deferred = defer();
+        nodeArgs.push(deferred.makeNodeResolver());
+        function bound() {
+            return callback.apply(thisp, arguments);
+        }
+        Q(bound).fapply(nodeArgs).fail(deferred.reject);
+        return deferred.promise;
+    };
+};
+
+Promise.prototype.nbind = function (/*thisp, ...args*/) {
+    var args = array_slice(arguments, 0);
+    args.unshift(this);
+    return Q.nbind.apply(void 0, args);
+};
+
+/**
+ * Calls a method of a Node-style object that accepts a Node-style
+ * callback with a given array of arguments, plus a provided callback.
+ * @param object an object that has the named method
+ * @param {String} name name of the method of object
+ * @param {Array} args arguments to pass to the method; the callback
+ * will be provided by Q and appended to these arguments.
+ * @returns a promise for the value or error
+ */
+Q.nmapply = // XXX As proposed by "Redsandro"
+Q.npost = function (object, name, args) {
+    return Q(object).npost(name, args);
+};
+
+Promise.prototype.nmapply = // XXX As proposed by "Redsandro"
+Promise.prototype.npost = function (name, args) {
+    var nodeArgs = array_slice(args || []);
+    var deferred = defer();
+    nodeArgs.push(deferred.makeNodeResolver());
+    this.dispatch("post", [name, nodeArgs]).fail(deferred.reject);
+    return deferred.promise;
+};
+
+/**
+ * Calls a method of a Node-style object that accepts a Node-style
+ * callback, forwarding the given variadic arguments, plus a provided
+ * callback argument.
+ * @param object an object that has the named method
+ * @param {String} name name of the method of object
+ * @param ...args arguments to pass to the method; the callback will
+ * be provided by Q and appended to these arguments.
+ * @returns a promise for the value or error
+ */
+Q.nsend = // XXX Based on Mark Miller's proposed "send"
+Q.nmcall = // XXX Based on "Redsandro's" proposal
+Q.ninvoke = function (object, name /*...args*/) {
+    var nodeArgs = array_slice(arguments, 2);
+    var deferred = defer();
+    nodeArgs.push(deferred.makeNodeResolver());
+    Q(object).dispatch("post", [name, nodeArgs]).fail(deferred.reject);
+    return deferred.promise;
+};
+
+Promise.prototype.nsend = // XXX Based on Mark Miller's proposed "send"
+Promise.prototype.nmcall = // XXX Based on "Redsandro's" proposal
+Promise.prototype.ninvoke = function (name /*...args*/) {
+    var nodeArgs = array_slice(arguments, 1);
+    var deferred = defer();
+    nodeArgs.push(deferred.makeNodeResolver());
+    this.dispatch("post", [name, nodeArgs]).fail(deferred.reject);
+    return deferred.promise;
+};
+
+/**
+ * If a function would like to support both Node continuation-passing-style and
+ * promise-returning-style, it can end its internal promise chain with
+ * `nodeify(nodeback)`, forwarding the optional nodeback argument.  If the user
+ * elects to use a nodeback, the result will be sent there.  If they do not
+ * pass a nodeback, they will receive the result promise.
+ * @param object a result (or a promise for a result)
+ * @param {Function} nodeback a Node.js-style callback
+ * @returns either the promise or nothing
+ */
+Q.nodeify = nodeify;
+function nodeify(object, nodeback) {
+    return Q(object).nodeify(nodeback);
+}
+
+Promise.prototype.nodeify = function (nodeback) {
+    if (nodeback) {
+        this.then(function (value) {
+            nextTick(function () {
+                nodeback(null, value);
+            });
+        }, function (error) {
+            nextTick(function () {
+                nodeback(error);
+            });
+        });
+    } else {
+        return this;
+    }
+};
+
+// All code before this point will be filtered from stack traces.
+var qEndingLine = captureLine();
+
+return Q;
+
+});
diff --git a/queue.js b/queue.js
new file mode 100644
index 0000000..1505fd0
--- /dev/null
+++ b/queue.js
@@ -0,0 +1,35 @@
+
+var Q = require("./q");
+
+module.exports = Queue;
+function Queue() {
+    var ends = Q.defer();
+    var closed = Q.defer();
+    return {
+        put: function (value) {
+            var next = Q.defer();
+            ends.resolve({
+                head: value,
+                tail: next.promise
+            });
+            ends.resolve = next.resolve;
+        },
+        get: function () {
+            var result = ends.promise.get("head");
+            ends.promise = ends.promise.get("tail");
+            return result.fail(function (error) {
+                closed.resolve(error);
+                throw error;
+            });
+        },
+        closed: closed.promise,
+        close: function (error) {
+            error = error || new Error("Can't get value from closed queue");
+            var end = {head: Q.reject(error)};
+            end.tail = end;
+            ends.resolve(end);
+            return closed.promise;
+        }
+    };
+}
+
diff --git a/ref_send.md b/ref_send.md
new file mode 100644
index 0000000..c096bb2
--- /dev/null
+++ b/ref_send.md
@@ -0,0 +1,26 @@
+
+This API varies from Tyler Closes ref_send in the
+following ways:
+
+*   Promises can be resolved to function values.
+*   Promises can be resolved to null or undefined.
+*   Promises are distinguishable from arbitrary functions.
+*   The promise API is abstracted with a Promise constructor
+    that accepts a descriptor that receives all of the
+    messages forwarded to that promise and handles the
+    common patterns for message receivers.  The promise
+    constructor also takes optional fallback and valueOf
+    methods which handle the cases for missing handlers on
+    the descriptor (rejection by default) and the valueOf
+    call (which returns the promise itself by default)
+*   near(ref) has been changed to Promise.valueOf() in
+    keeping with JavaScript's existing Object.valueOf().
+*   post(promise, name, args) has been altered to a variadic
+    post(promise, name ...args)
+*   variadic arguments are used internally where
+    applicable. However, I have not altered the Q.post()
+    API to expand variadic arguments since Tyler Close
+    informed the CommonJS list that it would restrict
+    usage patterns for web_send, posting arbitrary JSON
+    objects as the "arguments" over HTTP.
+
diff --git a/spec/aplus-adapter.js b/spec/aplus-adapter.js
new file mode 100644
index 0000000..f2c659d
--- /dev/null
+++ b/spec/aplus-adapter.js
@@ -0,0 +1,15 @@
+"use strict";
+
+var Q = require("../q");
+
+exports.fulfilled = Q.resolve;
+exports.rejected = Q.reject;
+exports.pending = function () {
+    var deferred = Q.defer();
+
+    return {
+        promise: deferred.promise,
+        fulfill: deferred.resolve,
+        reject: deferred.reject
+    };
+};
diff --git a/spec/lib/jasmine-1.2.0/MIT.LICENSE b/spec/lib/jasmine-1.2.0/MIT.LICENSE
new file mode 100644
index 0000000..7c435ba
--- /dev/null
+++ b/spec/lib/jasmine-1.2.0/MIT.LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008-2011 Pivotal Labs
+
+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.
diff --git a/spec/lib/jasmine-1.2.0/jasmine-html.js b/spec/lib/jasmine-1.2.0/jasmine-html.js
new file mode 100644
index 0000000..a0b0639
--- /dev/null
+++ b/spec/lib/jasmine-1.2.0/jasmine-html.js
@@ -0,0 +1,616 @@
+jasmine.HtmlReporterHelpers = {};
+
+jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
+  var el = document.createElement(type);
+
+  for (var i = 2; i < arguments.length; i++) {
+    var child = arguments[i];
+
+    if (typeof child === 'string') {
+      el.appendChild(document.createTextNode(child));
+    } else {
+      if (child) {
+        el.appendChild(child);
+      }
+    }
+  }
+
+  for (var attr in attrs) {
+    if (attr == "className") {
+      el[attr] = attrs[attr];
+    } else {
+      el.setAttribute(attr, attrs[attr]);
+    }
+  }
+
+  return el;
+};
+
+jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
+  var results = child.results();
+  var status = results.passed() ? 'passed' : 'failed';
+  if (results.skipped) {
+    status = 'skipped';
+  }
+
+  return status;
+};
+
+jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
+  var parentDiv = this.dom.summary;
+  var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
+  var parent = child[parentSuite];
+
+  if (parent) {
+    if (typeof this.views.suites[parent.id] == 'undefined') {
+      this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
+    }
+    parentDiv = this.views.suites[parent.id].element;
+  }
+
+  parentDiv.appendChild(childElement);
+};
+
+
+jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
+  for(var fn in jasmine.HtmlReporterHelpers) {
+    ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
+  }
+};
+
+jasmine.HtmlReporter = function(_doc) {
+  var self = this;
+  var doc = _doc || window.document;
+
+  var reporterView;
+
+  var dom = {};
+
+  // Jasmine Reporter Public Interface
+  self.logRunningSpecs = false;
+
+  self.reportRunnerStarting = function(runner) {
+    var specs = runner.specs() || [];
+
+    if (specs.length == 0) {
+      return;
+    }
+
+    createReporterDom(runner.env.versionString());
+    doc.body.appendChild(dom.reporter);
+
+    reporterView = new jasmine.HtmlReporter.ReporterView(dom);
+    reporterView.addSpecs(specs, self.specFilter);
+  };
+
+  self.reportRunnerResults = function(runner) {
+    reporterView && reporterView.complete();
+  };
+
+  self.reportSuiteResults = function(suite) {
+    reporterView.suiteComplete(suite);
+  };
+
+  self.reportSpecStarting = function(spec) {
+    if (self.logRunningSpecs) {
+      self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
+    }
+  };
+
+  self.reportSpecResults = function(spec) {
+    reporterView.specComplete(spec);
+  };
+
+  self.log = function() {
+    var console = jasmine.getGlobal().console;
+    if (console && console.log) {
+      if (console.log.apply) {
+        console.log.apply(console, arguments);
+      } else {
+        console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
+      }
+    }
+  };
+
+  self.specFilter = function(spec) {
+    if (!focusedSpecName()) {
+      return true;
+    }
+
+    return spec.getFullName().indexOf(focusedSpecName()) === 0;
+  };
+
+  return self;
+
+  function focusedSpecName() {
+    var specName;
+
+    (function memoizeFocusedSpec() {
+      if (specName) {
+        return;
+      }
+
+      var paramMap = [];
+      var params = doc.location.search.substring(1).split('&');
+
+      for (var i = 0; i < params.length; i++) {
+        var p = params[i].split('=');
+        paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
+      }
+
+      specName = paramMap.spec;
+    })();
+
+    return specName;
+  }
+
+  function createReporterDom(version) {
+    dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
+      dom.banner = self.createDom('div', { className: 'banner' },
+        self.createDom('span', { className: 'title' }, "Jasmine "),
+        self.createDom('span', { className: 'version' }, version)),
+
+      dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
+      dom.alert = self.createDom('div', {className: 'alert'}),
+      dom.results = self.createDom('div', {className: 'results'},
+        dom.summary = self.createDom('div', { className: 'summary' }),
+        dom.details = self.createDom('div', { id: 'details' }))
+    );
+  }
+};
+jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);jasmine.HtmlReporter.ReporterView = function(dom) {
+  this.startedAt = new Date();
+  this.runningSpecCount = 0;
+  this.completeSpecCount = 0;
+  this.passedCount = 0;
+  this.failedCount = 0;
+  this.skippedCount = 0;
+
+  this.createResultsMenu = function() {
+    this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
+      this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
+      ' | ',
+      this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
+
+    this.summaryMenuItem.onclick = function() {
+      dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
+    };
+
+    this.detailsMenuItem.onclick = function() {
+      showDetails();
+    };
+  };
+
+  this.addSpecs = function(specs, specFilter) {
+    this.totalSpecCount = specs.length;
+
+    this.views = {
+      specs: {},
+      suites: {}
+    };
+
+    for (var i = 0; i < specs.length; i++) {
+      var spec = specs[i];
+      this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
+      if (specFilter(spec)) {
+        this.runningSpecCount++;
+      }
+    }
+  };
+
+  this.specComplete = function(spec) {
+    this.completeSpecCount++;
+
+    if (isUndefined(this.views.specs[spec.id])) {
+      this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
+    }
+
+    var specView = this.views.specs[spec.id];
+
+    switch (specView.status()) {
+      case 'passed':
+        this.passedCount++;
+        break;
+
+      case 'failed':
+        this.failedCount++;
+        break;
+
+      case 'skipped':
+        this.skippedCount++;
+        break;
+    }
+
+    specView.refresh();
+    this.refresh();
+  };
+
+  this.suiteComplete = function(suite) {
+    var suiteView = this.views.suites[suite.id];
+    if (isUndefined(suiteView)) {
+      return;
+    }
+    suiteView.refresh();
+  };
+
+  this.refresh = function() {
+
+    if (isUndefined(this.resultsMenu)) {
+      this.createResultsMenu();
+    }
+
+    // currently running UI
+    if (isUndefined(this.runningAlert)) {
+      this.runningAlert = this.createDom('a', {href: "?", className: "runningAlert bar"});
+      dom.alert.appendChild(this.runningAlert);
+    }
+    this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
+
+    // skipped specs UI
+    if (isUndefined(this.skippedAlert)) {
+      this.skippedAlert = this.createDom('a', {href: "?", className: "skippedAlert bar"});
+    }
+
+    this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
+
+    if (this.skippedCount === 1 && isDefined(dom.alert)) {
+      dom.alert.appendChild(this.skippedAlert);
+    }
+
+    // passing specs UI
+    if (isUndefined(this.passedAlert)) {
+      this.passedAlert = this.createDom('span', {href: "?", className: "passingAlert bar"});
+    }
+    this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
+
+    // failing specs UI
+    if (isUndefined(this.failedAlert)) {
+      this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
+    }
+    this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
+
+    if (this.failedCount === 1 && isDefined(dom.alert)) {
+      dom.alert.appendChild(this.failedAlert);
+      dom.alert.appendChild(this.resultsMenu);
+    }
+
+    // summary info
+    this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
+    this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
+  };
+
+  this.complete = function() {
+    dom.alert.removeChild(this.runningAlert);
+
+    this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
+
+    if (this.failedCount === 0) {
+      dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
+    } else {
+      showDetails();
+    }
+
+    dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
+  };
+
+  return this;
+
+  function showDetails() {
+    if (dom.reporter.className.search(/showDetails/) === -1) {
+      dom.reporter.className += " showDetails";
+    }
+  }
+
+  function isUndefined(obj) {
+    return typeof obj === 'undefined';
+  }
+
+  function isDefined(obj) {
+    return !isUndefined(obj);
+  }
+
+  function specPluralizedFor(count) {
+    var str = count + " spec";
+    if (count > 1) {
+      str += "s"
+    }
+    return str;
+  }
+
+};
+
+jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
+
+
+jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
+  this.spec = spec;
+  this.dom = dom;
+  this.views = views;
+
+  this.symbol = this.createDom('li', { className: 'pending' });
+  this.dom.symbolSummary.appendChild(this.symbol);
+
+  this.summary = this.createDom('div', { className: 'specSummary' },
+      this.createDom('a', {
+        className: 'description',
+        href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
+        title: this.spec.getFullName()
+      }, this.spec.description)
+  );
+
+  this.detail = this.createDom('div', { className: 'specDetail' },
+      this.createDom('a', {
+        className: 'description',
+        href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
+        title: this.spec.getFullName()
+      }, this.spec.getFullName())
+  );
+};
+
+jasmine.HtmlReporter.SpecView.prototype.status = function() {
+  return this.getSpecStatus(this.spec);
+};
+
+jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
+  this.symbol.className = this.status();
+
+  switch (this.status()) {
+    case 'skipped':
+      break;
+
+    case 'passed':
+      this.appendSummaryToSuiteDiv();
+      break;
+
+    case 'failed':
+      this.appendSummaryToSuiteDiv();
+      this.appendFailureDetail();
+      break;
+  }
+};
+
+jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
+  this.summary.className += ' ' + this.status();
+  this.appendToSummary(this.spec, this.summary);
+};
+
+jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
+  this.detail.className += ' ' + this.status();
+
+  var resultItems = this.spec.results().getItems();
+  var messagesDiv = this.createDom('div', { className: 'messages' });
+
+  for (var i = 0; i < resultItems.length; i++) {
+    var result = resultItems[i];
+
+    if (result.type == 'log') {
+      messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
+    } else if (result.type == 'expect' && result.passed && !result.passed()) {
+      messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
+
+      if (result.trace.stack) {
+        messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
+      }
+    }
+  }
+
+  if (messagesDiv.childNodes.length > 0) {
+    this.detail.appendChild(messagesDiv);
+    this.dom.details.appendChild(this.detail);
+  }
+};
+
+jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
+  this.suite = suite;
+  this.dom = dom;
+  this.views = views;
+
+  this.element = this.createDom('div', { className: 'suite' },
+      this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.suite.getFullName()) }, this.suite.description)
+  );
+
+  this.appendToSummary(this.suite, this.element);
+};
+
+jasmine.HtmlReporter.SuiteView.prototype.status = function() {
+  return this.getSpecStatus(this.suite);
+};
+
+jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
+  this.element.className += " " + this.status();
+};
+
+jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
+
+/* @deprecated Use jasmine.HtmlReporter instead
+ */
+jasmine.TrivialReporter = function(doc) {
+  this.document = doc || document;
+  this.suiteDivs = {};
+  this.logRunningSpecs = false;
+};
+
+jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
+  var el = document.createElement(type);
+
+  for (var i = 2; i < arguments.length; i++) {
+    var child = arguments[i];
+
+    if (typeof child === 'string') {
+      el.appendChild(document.createTextNode(child));
+    } else {
+      if (child) { el.appendChild(child); }
+    }
+  }
+
+  for (var attr in attrs) {
+    if (attr == "className") {
+      el[attr] = attrs[attr];
+    } else {
+      el.setAttribute(attr, attrs[attr]);
+    }
+  }
+
+  return el;
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
+  var showPassed, showSkipped;
+
+  this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
+      this.createDom('div', { className: 'banner' },
+        this.createDom('div', { className: 'logo' },
+            this.createDom('span', { className: 'title' }, "Jasmine"),
+            this.createDom('span', { className: 'version' }, runner.env.versionString())),
+        this.createDom('div', { className: 'options' },
+            "Show ",
+            showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
+            this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
+            showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
+            this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
+            )
+          ),
+
+      this.runnerDiv = this.createDom('div', { className: 'runner running' },
+          this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
+          this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
+          this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
+      );
+
+  this.document.body.appendChild(this.outerDiv);
+
+  var suites = runner.suites();
+  for (var i = 0; i < suites.length; i++) {
+    var suite = suites[i];
+    var suiteDiv = this.createDom('div', { className: 'suite' },
+        this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
+        this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
+    this.suiteDivs[suite.id] = suiteDiv;
+    var parentDiv = this.outerDiv;
+    if (suite.parentSuite) {
+      parentDiv = this.suiteDivs[suite.parentSuite.id];
+    }
+    parentDiv.appendChild(suiteDiv);
+  }
+
+  this.startedAt = new Date();
+
+  var self = this;
+  showPassed.onclick = function(evt) {
+    if (showPassed.checked) {
+      self.outerDiv.className += ' show-passed';
+    } else {
+      self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
+    }
+  };
+
+  showSkipped.onclick = function(evt) {
+    if (showSkipped.checked) {
+      self.outerDiv.className += ' show-skipped';
+    } else {
+      self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
+    }
+  };
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
+  var results = runner.results();
+  var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
+  this.runnerDiv.setAttribute("class", className);
+  //do it twice for IE
+  this.runnerDiv.setAttribute("className", className);
+  var specs = runner.specs();
+  var specCount = 0;
+  for (var i = 0; i < specs.length; i++) {
+    if (this.specFilter(specs[i])) {
+      specCount++;
+    }
+  }
+  var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
+  message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
+  this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
+
+  this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
+};
+
+jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
+  var results = suite.results();
+  var status = results.passed() ? 'passed' : 'failed';
+  if (results.totalCount === 0) { // todo: change this to check results.skipped
+    status = 'skipped';
+  }
+  this.suiteDivs[suite.id].className += " " + status;
+};
+
+jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
+  if (this.logRunningSpecs) {
+    this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
+  }
+};
+
+jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
+  var results = spec.results();
+  var status = results.passed() ? 'passed' : 'failed';
+  if (results.skipped) {
+    status = 'skipped';
+  }
+  var specDiv = this.createDom('div', { className: 'spec '  + status },
+      this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
+      this.createDom('a', {
+        className: 'description',
+        href: '?spec=' + encodeURIComponent(spec.getFullName()),
+        title: spec.getFullName()
+      }, spec.description));
+
+
+  var resultItems = results.getItems();
+  var messagesDiv = this.createDom('div', { className: 'messages' });
+  for (var i = 0; i < resultItems.length; i++) {
+    var result = resultItems[i];
+
+    if (result.type == 'log') {
+      messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
+    } else if (result.type == 'expect' && result.passed && !result.passed()) {
+      messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
+
+      if (result.trace.stack) {
+        messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
+      }
+    }
+  }
+
+  if (messagesDiv.childNodes.length > 0) {
+    specDiv.appendChild(messagesDiv);
+  }
+
+  this.suiteDivs[spec.suite.id].appendChild(specDiv);
+};
+
+jasmine.TrivialReporter.prototype.log = function() {
+  var console = jasmine.getGlobal().console;
+  if (console && console.log) {
+    if (console.log.apply) {
+      console.log.apply(console, arguments);
+    } else {
+      console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
+    }
+  }
+};
+
+jasmine.TrivialReporter.prototype.getLocation = function() {
+  return this.document.location;
+};
+
+jasmine.TrivialReporter.prototype.specFilter = function(spec) {
+  var paramMap = {};
+  var params = this.getLocation().search.substring(1).split('&');
+  for (var i = 0; i < params.length; i++) {
+    var p = params[i].split('=');
+    paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
+  }
+
+  if (!paramMap.spec) {
+    return true;
+  }
+  return spec.getFullName().indexOf(paramMap.spec) === 0;
+};
diff --git a/spec/lib/jasmine-1.2.0/jasmine.css b/spec/lib/jasmine-1.2.0/jasmine.css
new file mode 100644
index 0000000..826e575
--- /dev/null
+++ b/spec/lib/jasmine-1.2.0/jasmine.css
@@ -0,0 +1,81 @@
+body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
+
+#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
+#HTMLReporter a { text-decoration: none; }
+#HTMLReporter a:hover { text-decoration: underline; }
+#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
+#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
+#HTMLReporter #jasmine_content { position: fixed; right: 100%; }
+#HTMLReporter .version { color: #aaaaaa; }
+#HTMLReporter .banner { margin-top: 14px; }
+#HTMLReporter .duration { color: #aaaaaa; float: right; }
+#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
+#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
+#HTMLReporter .symbolSummary li.passed { font-size: 14px; }
+#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
+#HTMLReporter .symbolSummary li.failed { line-height: 9px; }
+#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
+#HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
+#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
+#HTMLReporter .symbolSummary li.pending { line-height: 11px; }
+#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
+#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
+#HTMLReporter .runningAlert { background-color: #666666; }
+#HTMLReporter .skippedAlert { background-color: #aaaaaa; }
+#HTMLReporter .skippedAlert:first-child { background-color: #333333; }
+#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
+#HTMLReporter .passingAlert { background-color: #a6b779; }
+#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
+#HTMLReporter .failingAlert { background-color: #cf867e; }
+#HTMLReporter .failingAlert:first-child { background-color: #b03911; }
+#HTMLReporter .results { margin-top: 14px; }
+#HTMLReporter #details { display: none; }
+#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
+#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
+#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
+#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
+#HTMLReporter.showDetails .summary { display: none; }
+#HTMLReporter.showDetails #details { display: block; }
+#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
+#HTMLReporter .summary { margin-top: 14px; }
+#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
+#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
+#HTMLReporter .summary .specSummary.failed a { color: #b03911; }
+#HTMLReporter .description + .suite { margin-top: 0; }
+#HTMLReporter .suite { margin-top: 14px; }
+#HTMLReporter .suite a { color: #333333; }
+#HTMLReporter #details .specDetail { margin-bottom: 28px; }
+#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
+#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
+#HTMLReporter .resultMessage span.result { display: block; }
+#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
+
+#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
+#TrivialReporter a:visited, #TrivialReporter a { color: #303; }
+#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
+#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
+#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
+#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
+#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
+#TrivialReporter .runner.running { background-color: yellow; }
+#TrivialReporter .options { text-align: right; font-size: .8em; }
+#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
+#TrivialReporter .suite .suite { margin: 5px; }
+#TrivialReporter .suite.passed { background-color: #dfd; }
+#TrivialReporter .suite.failed { background-color: #fdd; }
+#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
+#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
+#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
+#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
+#TrivialReporter .spec.skipped { background-color: #bbb; }
+#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
+#TrivialReporter .passed { background-color: #cfc; display: none; }
+#TrivialReporter .failed { background-color: #fbb; }
+#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
+#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
+#TrivialReporter .resultMessage .mismatch { color: black; }
+#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
+#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
+#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
+#TrivialReporter #jasmine_content { position: fixed; right: 100%; }
+#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
diff --git a/spec/lib/jasmine-1.2.0/jasmine.js b/spec/lib/jasmine-1.2.0/jasmine.js
new file mode 100644
index 0000000..03bf89a
--- /dev/null
+++ b/spec/lib/jasmine-1.2.0/jasmine.js
@@ -0,0 +1,2529 @@
+var isCommonJS = typeof window == "undefined";
+
+/**
+ * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
+ *
+ * @namespace
+ */
+var jasmine = {};
+if (isCommonJS) exports.jasmine = jasmine;
+/**
+ * @private
+ */
+jasmine.unimplementedMethod_ = function() {
+  throw new Error("unimplemented method");
+};
+
+/**
+ * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> 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);
+};
+
+/**
+ * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the
+ * attributes on the object.
+ *
+ * @example
+ * // don't care about any other attributes than foo.
+ * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"});
+ *
+ * @param sample {Object} sample
+ * @returns matchable object for the sample
+ */
+jasmine.objectContaining = function (sample) {
+    return new jasmine.Matchers.ObjectContaining(sample);
+};
+
+/**
+ * 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 <b>not</b> 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 <code>jasmine.log</code> 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 <em>disabled</em> 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, '<')
+    .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.jasmineMatches) {
+    return a.jasmineMatches(b);
+  }
+
+  if (b.jasmineMatches) {
+    return b.jasmineMatches(a);
+  }
+
+  if (a instanceof jasmine.Matchers.ObjectContaining) {
+    return a.matches(b);
+  }
+
+  if (b instanceof jasmine.Matchers.ObjectContaining) {
+    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.toEqual() 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.toContain() 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.jasmineMatches = 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.jasmineToString = function() {
+  return '<jasmine.any(' + this.expectedClass + ')>';
+};
+
+jasmine.Matchers.ObjectContaining = function (sample) {
+  this.sample = sample;
+};
+
+jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) {
+  mismatchKeys = mismatchKeys || [];
+  mismatchValues = mismatchValues || [];
+
+  var env = jasmine.getEnv();
+
+  var hasKey = function(obj, keyName) {
+    return obj != null && obj[keyName] !== jasmine.undefined;
+  };
+
+  for (var property in this.sample) {
+    if (!hasKey(other, property) && hasKey(this.sample, property)) {
+      mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
+    }
+    else if (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) {
+      mismatchValues.push("'" + property + "' was '" + (other[property] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual.");
+    }
+  }
+
+  return (mismatchKeys.length === 0 && mismatchValues.length === 0);
+};
+
+jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () {
+  return "<jasmine.objectContaining(" + jasmine.pp(this.sample) + ")>";
+};
+// 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);
+  }
+};
+
+/**
+ * @constructor
+ */
+jasmine.MultiReporter = function() {
+  this.subReporters_ = [];
+};
+jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
+
+jasmine.MultiReporter.prototype.addReporter = function(reporter) {
+  this.subReporters_.push(reporter);
+};
+
+(function() {
+  var functionNames = [
+    "reportRunnerStarting",
+    "reportRunnerResults",
+    "reportSuiteResults",
+    "reportSpecStarting",
+    "reportSpecResults",
+    "log"
+  ];
+  for (var i = 0; i < functionNames.length; i++) {
+    var functionName = functionNames[i];
+    jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
+      return function() {
+        for (var j = 0; j < this.subReporters_.length; j++) {
+          var subReporter = this.subReporters_[j];
+          if (subReporter[functionName]) {
+            subReporter[functionName].apply(subReporter, arguments);
+          }
+        }
+      };
+    })(functionName);
+  }
+})();
+/**
+ * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
+ *
+ * @constructor
+ */
+jasmine.NestedResults = function() {
+  /**
+   * The total count of results
+   */
+  this.totalCount = 0;
+  /**
+   * Number of passed results
+   */
+  this.passedCount = 0;
+  /**
+   * Number of failed results
+   */
+  this.failedCount = 0;
+  /**
+   * Was this suite/spec skipped?
+   */
+  this.skipped = false;
+  /**
+   * @ignore
+   */
+  this.items_ = [];
+};
+
+/**
+ * Roll up the result counts.
+ *
+ * @param result
+ */
+jasmine.NestedResults.prototype.rollupCounts = function(result) {
+  this.totalCount += result.totalCount;
+  this.passedCount += result.passedCount;
+  this.failedCount += result.failedCount;
+};
+
+/**
+ * Adds a log message.
+ * @param values Array of message parts which will be concatenated later.
+ */
+jasmine.NestedResults.prototype.log = function(values) {
+  this.items_.push(new jasmine.MessageResult(values));
+};
+
+/**
+ * Getter for the results: message & results.
+ */
+jasmine.NestedResults.prototype.getItems = function() {
+  return this.items_;
+};
+
+/**
+ * Adds a result, tracking counts (total, passed, & failed)
+ * @param {jasmine.ExpectationResult|jasmine.NestedResults} result
+ */
+jasmine.NestedResults.prototype.addResult = function(result) {
+  if (result.type != 'log') {
+    if (result.items_) {
+      this.rollupCounts(result);
+    } else {
+      this.totalCount++;
+      if (result.passed()) {
+        this.passedCount++;
+      } else {
+        this.failedCount++;
+      }
+    }
+  }
+  this.items_.push(result);
+};
+
+/**
+ * @returns {Boolean} True if <b>everything</b> below passed
+ */
+jasmine.NestedResults.prototype.passed = function() {
+  return this.passedCount === this.totalCount;
+};
+/**
+ * Base class for pretty printing for expectation results.
+ */
+jasmine.PrettyPrinter = function() {
+  this.ppNestLevel_ = 0;
+};
+
+/**
+ * Formats a value in a nice, human-readable string.
+ *
+ * @param value
+ */
+jasmine.PrettyPrinter.prototype.format = function(value) {
+  if (this.ppNestLevel_ > 40) {
+    throw new Error('jasmine.PrettyPrinter: format() nested too deeply!');
+  }
+
+  this.ppNestLevel_++;
+  try {
+    if (value === jasmine.undefined) {
+      this.emitScalar('undefined');
+    } else if (value === null) {
+      this.emitScalar('null');
+    } else if (value === jasmine.getGlobal()) {
+      this.emitScalar('<global>');
+    } else if (value.jasmineToString) {
+      this.emitScalar(value.jasmineToString());
+    } else if (typeof value === 'string') {
+      this.emitString(value);
+    } else if (jasmine.isSpy(value)) {
+      this.emitScalar("spy on " + value.identity);
+    } else if (value instanceof RegExp) {
+      this.emitScalar(value.toString());
+    } else if (typeof value === 'function') {
+      this.emitScalar('Function');
+    } else if (typeof value.nodeType === 'number') {
+      this.emitScalar('HTMLNode');
+    } else if (value instanceof Date) {
+      this.emitScalar('Date(' + value + ')');
+    } else if (value.__Jasmine_been_here_before__) {
+      this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
+    } else if (jasmine.isArray_(value) || typeof value == 'object') {
+      value.__Jasmine_been_here_before__ = true;
+      if (jasmine.isArray_(value)) {
+        this.emitArray(value);
+      } else {
+        this.emitObject(value);
+      }
+      delete value.__Jasmine_been_here_before__;
+    } else {
+      this.emitScalar(value.toString());
+    }
+  } finally {
+    this.ppNestLevel_--;
+  }
+};
+
+jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
+  for (var property in obj) {
+    if (property == '__Jasmine_been_here_before__') continue;
+    fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && 
+                                         obj.__lookupGetter__(property) !== null) : false);
+  }
+};
+
+jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
+
+jasmine.StringPrettyPrinter = function() {
+  jasmine.PrettyPrinter.call(this);
+
+  this.string = '';
+};
+jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
+
+jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
+  this.append(value);
+};
+
+jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
+  this.append("'" + value + "'");
+};
+
+jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
+  this.append('[ ');
+  for (var i = 0; i < array.length; i++) {
+    if (i > 0) {
+      this.append(', ');
+    }
+    this.format(array[i]);
+  }
+  this.append(' ]');
+};
+
+jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
+  var self = this;
+  this.append('{ ');
+  var first = true;
+
+  this.iterateObject(obj, function(property, isGetter) {
+    if (first) {
+      first = false;
+    } else {
+      self.append(', ');
+    }
+
+    self.append(property);
+    self.append(' : ');
+    if (isGetter) {
+      self.append('<getter>');
+    } else {
+      self.format(obj[property]);
+    }
+  });
+
+  this.append(' }');
+};
+
+jasmine.StringPrettyPrinter.prototype.append = function(value) {
+  this.string += value;
+};
+jasmine.Queue = function(env) {
+  this.env = env;
+  this.blocks = [];
+  this.running = false;
+  this.index = 0;
+  this.offset = 0;
+  this.abort = false;
+};
+
+jasmine.Queue.prototype.addBefore = function(block) {
+  this.blocks.unshift(block);
+};
+
+jasmine.Queue.prototype.add = function(block) {
+  this.blocks.push(block);
+};
+
+jasmine.Queue.prototype.insertNext = function(block) {
+  this.blocks.splice((this.index + this.offset + 1), 0, block);
+  this.offset++;
+};
+
+jasmine.Queue.prototype.start = function(onComplete) {
+  this.running = true;
+  this.onComplete = onComplete;
+  this.next_();
+};
+
+jasmine.Queue.prototype.isRunning = function() {
+  return this.running;
+};
+
+jasmine.Queue.LOOP_DONT_RECURSE = true;
+
+jasmine.Queue.prototype.next_ = function() {
+  var self = this;
+  var goAgain = true;
+
+  while (goAgain) {
+    goAgain = false;
+    
+    if (self.index < self.blocks.length && !this.abort) {
+      var calledSynchronously = true;
+      var completedSynchronously = false;
+
+      var onComplete = function () {
+        if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
+          completedSynchronously = true;
+          return;
+        }
+
+        if (self.blocks[self.index].abort) {
+          self.abort = true;
+        }
+
+        self.offset = 0;
+        self.index++;
+
+        var now = new Date().getTime();
+        if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
+          self.env.lastUpdate = now;
+          self.env.setTimeout(function() {
+            self.next_();
+          }, 0);
+        } else {
+          if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
+            goAgain = true;
+          } else {
+            self.next_();
+          }
+        }
+      };
+      self.blocks[self.index].execute(onComplete);
+
+      calledSynchronously = false;
+      if (completedSynchronously) {
+        onComplete();
+      }
+      
+    } else {
+      self.running = false;
+      if (self.onComplete) {
+        self.onComplete();
+      }
+    }
+  }
+};
+
+jasmine.Queue.prototype.results = function() {
+  var results = new jasmine.NestedResults();
+  for (var i = 0; i < this.blocks.length; i++) {
+    if (this.blocks[i].results) {
+      results.addResult(this.blocks[i].results());
+    }
+  }
+  return results;
+};
+
+
+/**
+ * Runner
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ */
+jasmine.Runner = function(env) {
+  var self = this;
+  self.env = env;
+  self.queue = new jasmine.Queue(env);
+  self.before_ = [];
+  self.after_ = [];
+  self.suites_ = [];
+};
+
+jasmine.Runner.prototype.execute = function() {
+  var self = this;
+  if (self.env.reporter.reportRunnerStarting) {
+    self.env.reporter.reportRunnerStarting(this);
+  }
+  self.queue.start(function () {
+    self.finishCallback();
+  });
+};
+
+jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
+  beforeEachFunction.typeName = 'beforeEach';
+  this.before_.splice(0,0,beforeEachFunction);
+};
+
+jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
+  afterEachFunction.typeName = 'afterEach';
+  this.after_.splice(0,0,afterEachFunction);
+};
+
+
+jasmine.Runner.prototype.finishCallback = function() {
+  this.env.reporter.reportRunnerResults(this);
+};
+
+jasmine.Runner.prototype.addSuite = function(suite) {
+  this.suites_.push(suite);
+};
+
+jasmine.Runner.prototype.add = function(block) {
+  if (block instanceof jasmine.Suite) {
+    this.addSuite(block);
+  }
+  this.queue.add(block);
+};
+
+jasmine.Runner.prototype.specs = function () {
+  var suites = this.suites();
+  var specs = [];
+  for (var i = 0; i < suites.length; i++) {
+    specs = specs.concat(suites[i].specs());
+  }
+  return specs;
+};
+
+jasmine.Runner.prototype.suites = function() {
+  return this.suites_;
+};
+
+jasmine.Runner.prototype.topLevelSuites = function() {
+  var topLevelSuites = [];
+  for (var i = 0; i < this.suites_.length; i++) {
+    if (!this.suites_[i].parentSuite) {
+      topLevelSuites.push(this.suites_[i]);
+    }
+  }
+  return topLevelSuites;
+};
+
+jasmine.Runner.prototype.results = function() {
+  return this.queue.results();
+};
+/**
+ * Internal representation of a Jasmine specification, or test.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {jasmine.Suite} suite
+ * @param {String} description
+ */
+jasmine.Spec = function(env, suite, description) {
+  if (!env) {
+    throw new Error('jasmine.Env() required');
+  }
+  if (!suite) {
+    throw new Error('jasmine.Suite() required');
+  }
+  var spec = this;
+  spec.id = env.nextSpecId ? env.nextSpecId() : null;
+  spec.env = env;
+  spec.suite = suite;
+  spec.description = description;
+  spec.queue = new jasmine.Queue(env);
+
+  spec.afterCallbacks = [];
+  spec.spies_ = [];
+
+  spec.results_ = new jasmine.NestedResults();
+  spec.results_.description = description;
+  spec.matchersClass = null;
+};
+
+jasmine.Spec.prototype.getFullName = function() {
+  return this.suite.getFullName() + ' ' + this.description + '.';
+};
+
+
+jasmine.Spec.prototype.results = function() {
+  return this.results_;
+};
+
+/**
+ * All parameters are pretty-printed and concatenated together, then written to the spec's output.
+ *
+ * Be careful not to leave calls to <code>jasmine.log</code> 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);
+  }
+};
+
+jasmine.version_= {
+  "major": 1,
+  "minor": 2,
+  "build": 0,
+  "revision": 1337005947
+};
diff --git a/spec/lib/jasmine-promise.js b/spec/lib/jasmine-promise.js
new file mode 100644
index 0000000..fbdfbd0
--- /dev/null
+++ b/spec/lib/jasmine-promise.js
@@ -0,0 +1,91 @@
+"use strict";
+
+/**
+ * Modifies the way that individual specs are run to easily test async
+ * code with promises.
+ *
+ * A spec may return a promise. If it does, then the spec passes if and
+ * only if that promise is fulfilled within a very short period of time.
+ * If it is rejected, or if it isn't fulfilled quickly, the spec fails.
+ *
+ * In this way, we can use promise chaining to structure our asynchronous
+ * tests. Expectations all down the chain of promises are all checked and
+ * guaranteed to be run and resolved or the test fails.
+ *
+ * This is a big win over the runs() and watches() code that jasmine
+ * supports out of the box.
+ */
+jasmine.Block.prototype.execute = function (onComplete) {
+    var spec = this.spec;
+    try {
+        var result = this.func.call(spec, onComplete);
+
+        // It seems Jasmine likes to return the suite if you pass it anything.
+        // So make sure it's a promise first.
+        if (result && typeof result.then === "function") {
+            Q.timeout(result, 500).then(function () {
+                onComplete();
+            }, function (error) {
+                spec.fail(error);
+                onComplete();
+            });
+        } else if (this.func.length === 0) {
+            onComplete();
+        }
+    } catch (error) {
+        spec.fail(error);
+        onComplete();
+    }
+};
+
+/**
+ * Tests and documents the behavior of the above extension to jasmine.
+ */
+describe('jasmine-promise', function() {
+  it('passes if the deferred resolves immediately', function() {
+    var deferred = Q.defer();
+    deferred.resolve();
+    return deferred.promise;
+  });
+  it('passes if the deferred resolves after a short delay', function() {
+    var deferred = Q.defer();
+    setTimeout(function() {deferred.resolve();}, 100);
+    return deferred.promise;
+  });
+  it('lets specs that return nothing pass', function() {
+
+  });
+  it('lets specs that return non-promises pass', function() {
+    return {'some object': 'with values'};
+  });
+  it('works ok with specs that return crappy non-Q promises', function() {
+    return {
+      'then': function(callback) {
+        callback();
+      }
+    }
+  });
+  // These are expected to fail. Remove the x from xdescribe to test that.
+  xdescribe('failure cases (expected to fail)', function() {
+    it('fails if the deferred is rejected', function() {
+      var deferred = Q.defer();
+      deferred.reject();
+      return deferred.promise;
+    });
+    it('fails if the deferred takes too long to resolve', function() {
+      var deferred = Q.defer();
+      setTimeout(function() {deferred.resolve()}, 5 * 1000);
+      return deferred.promise;
+    });
+    it('fails if a returned crappy non-Q promise is rejected', function() {
+      return {
+        'then': function(_, callback) {callback()}
+      }
+    });
+    it('fails if a returned crappy promise is never resolved', function() {
+      return {
+        'then': function() {}
+      }
+    });
+  })
+});
diff --git a/spec/q-spec.html b/spec/q-spec.html
new file mode 100644
index 0000000..74ce471
--- /dev/null
+++ b/spec/q-spec.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Jasmine Spec Runner</title>
+    <meta charset="utf-8" />
+
+    <link rel="shortcut icon" type="image/png" href="lib/jasmine-1.2.0/jasmine_favicon.png" />
+    <link rel="stylesheet" href="lib/jasmine-1.2.0/jasmine.css" />
+    <script src="lib/jasmine-1.2.0/jasmine.js"></script>
+    <script src="lib/jasmine-1.2.0/jasmine-html.js"></script>
+    <script src="lib/jasmine-promise.js"></script>
+
+    <!-- include source files here... -->
+    <script src="../q.js"></script>
+
+    <!-- include spec files here... -->
+    <script src="q-spec.js"></script>
+
+    <script>
+        (function() {
+            var jasmineEnv = jasmine.getEnv();
+            jasmineEnv.updateInterval = 1000;
+
+            var htmlReporter = new jasmine.HtmlReporter();
+
+            jasmineEnv.addReporter(htmlReporter);
+
+            jasmineEnv.specFilter = function(spec) {
+                return htmlReporter.specFilter(spec);
+            };
+
+            var currentWindowOnload = window.onload;
+
+            window.onload = function() {
+                if (currentWindowOnload) {
+                    currentWindowOnload();
+                }
+                execJasmine();
+            };
+
+            function execJasmine() {
+                jasmineEnv.execute();
+            }
+
+        })();
+    </script>
+
+</head>
+
+<body>
+</body>
+</html>
diff --git a/spec/q-spec.js b/spec/q-spec.js
new file mode 100644
index 0000000..450685c
--- /dev/null
+++ b/spec/q-spec.js
@@ -0,0 +1,2490 @@
+"use strict";
+/*jshint newcap: false*/
+/*global Q: true, describe: false, it: false, expect: false, beforeEach: false,
+         afterEach: false, require: false, jasmine: false, waitsFor: false,
+         runs: false */
+
+if (typeof Q === "undefined" && typeof require !== "undefined") {
+    // For Node compatibility.
+    global.Q = require("../q");
+    require("./lib/jasmine-promise");
+}
+
+var REASON = "this is not an error, but it might show up in the console";
+
+// In browsers that support strict mode, it'll be `undefined`; otherwise, the global.
+var calledAsFunctionThis = (function () { return this; }());
+
+afterEach(function () {
+    Q.onerror = null;
+});
+
+describe("computing sum of integers using promises", function() {
+    it("should compute correct result without blowing stack", function () {
+        var array = [];
+        var iters = 1000;
+        for (var i = 1; i <= iters; i++) {
+            array.push(i);
+        }
+
+        var pZero = Q.fulfill(0);
+        var result = array.reduce(function (promise, nextVal) {
+            return promise.then(function (currentVal) {
+                return Q.fulfill(currentVal + nextVal);
+            });
+        }, pZero);
+
+        return result.then(function (value) {
+            expect(value).toEqual(iters * (iters + 1) / 2);
+        });
+    });
+});
+
+describe("Q function", function () {
+    it("should result in a fulfilled promise when given a value", function () {
+        expect(Q(5).isFulfilled()).toBe(true);
+    });
+
+    it("should be the identity when given promise", function () {
+        var f = Q.fulfill(5);
+        var r = Q.reject(new Error("aaargh"));
+        var p = Q.promise(function () { });
+
+        expect(Q(f)).toBe(f);
+        expect(Q(r)).toBe(r);
+        expect(Q(p)).toBe(p);
+    });
+});
+
+describe("defer and when", function () {
+
+    it("resolve before when", function () {
+        var turn = 0;
+        var deferred = Q.defer();
+        deferred.resolve(10);
+        var promise = Q.when(deferred.promise, function (value) {
+            expect(turn).toEqual(1);
+            expect(value).toEqual(10);
+        });
+        turn++;
+        return promise;
+    });
+
+    it("reject before when", function () {
+        var turn = 0;
+        var deferred = Q.defer();
+        deferred.reject(-1);
+        var promise = Q.when(deferred.promise, function () {
+            expect(true).toBe(false);
+        }, function (value) {
+            expect(turn).toEqual(1);
+            expect(value).toEqual(-1);
+        });
+        turn++;
+        return promise;
+    });
+
+    it("when before resolve", function () {
+        var turn = 0;
+        var deferred = Q.defer();
+        var promise = deferred.promise.then(function (value) {
+            expect(turn).toEqual(2);
+            expect(value).toEqual(10);
+            turn++;
+        });
+        Q.nextTick(function () {
+            expect(turn).toEqual(1);
+            deferred.resolve(10);
+            turn++;
+        });
+        turn++;
+        return promise;
+    });
+
+    it("when before reject", function () {
+        var turn = 0;
+        var deferred = Q.defer();
+        var promise = deferred.promise.then(function () {
+            expect(true).toBe(false);
+        }, function (value) {
+            expect(turn).toEqual(2);
+            expect(value).toEqual(-1);
+            turn++;
+        });
+        Q.nextTick(function () {
+            expect(turn).toEqual(1);
+            deferred.reject(-1);
+            turn++;
+        });
+        turn++;
+        return promise;
+    });
+
+    it("resolves multiple observers", function (done) {
+        var nextTurn = false;
+
+        var resolution = "Taram pam param!";
+        var deferred = Q.defer();
+        var count = 10;
+        var i = 0;
+
+        function resolve(value) {
+            i++;
+            expect(value).toBe(resolution);
+            expect(nextTurn).toBe(true);
+            if (i === count) {
+                done();
+            }
+        }
+
+        while (++i <= count) {
+            Q.when(deferred.promise, resolve);
+        }
+
+        deferred.resolve(resolution);
+        i = 0;
+        nextTurn = true;
+    });
+
+    it("observers called even after throw", function () {
+        var threw = false;
+        var deferred = Q.defer();
+        Q.when(deferred.promise, function () {
+            threw = true;
+            throw new Error(REASON);
+        });
+        var promise = Q.when(deferred.promise, function (value) {
+            expect(value).toEqual(10);
+        }, function () {
+            expect("not").toEqual("here");
+        });
+        deferred.resolve(10);
+        return promise;
+    });
+
+    it("returns `undefined` from the deferred's methods", function () {
+        expect(Q.defer().resolve()).toBe(undefined);
+        expect(Q.defer().reject()).toBe(undefined);
+    });
+
+});
+
+describe("always next tick", function () {
+
+    it("generated by `resolve`", function () {
+        var turn = 0;
+        var promise = Q.when(Q(), function () {
+            expect(turn).toEqual(1);
+        });
+        turn++;
+        return promise;
+    });
+
+    it("generated by `reject`", function () {
+        var turn = 0;
+        var promise = Q.when(Q.reject(), function () {
+            expect(true).toBe(false);
+        }, function () {
+            expect(turn).toEqual(1);
+        });
+        turn++;
+        return promise;
+    });
+
+});
+
+describe("progress", function () {
+
+    it("calls a single progress listener", function () {
+        var progressed = false;
+        var deferred = Q.defer();
+
+        var promise = Q.when(
+            deferred.promise,
+            function () {
+                expect(progressed).toBe(true);
+            },
+            function () {
+                expect(true).toBe(false);
+            },
+            function () {
+                progressed = true;
+            }
+        );
+
+        deferred.notify();
+        deferred.resolve();
+
+        return promise;
+    });
+
+    it("calls multiple progress listeners", function () {
+        var progressed1 = false;
+        var progressed2 = false;
+        var deferred = Q.defer();
+        var promise = Q.when(
+            deferred.promise,
+            function () {
+                expect(progressed1).toBe(true);
+                expect(progressed2).toBe(true);
+            },
+            function () {
+                expect(true).toBe(false);
+            },
+            function () {
+                progressed1 = true;
+            }
+        );
+        Q.when(deferred.promise, null, null, function () {
+            progressed2 = true;
+        });
+
+        deferred.notify();
+        deferred.resolve();
+
+        return promise;
+    });
+
+    it("calls all progress listeners even if one throws", function () {
+        var progressed1 = false;
+        var progressed2 = false;
+        var progressed3 = false;
+        var deferred = Q.defer();
+        var promise = Q.when(
+            deferred.promise,
+            function () {
+                expect(progressed1).toBe(true);
+                expect(progressed2).toBe(true);
+                expect(progressed3).toBe(true);
+            },
+            function () {
+                expect(true).toBe(false);
+            },
+            function () {
+                progressed1 = true;
+            }
+        );
+
+        Q.onerror = function () { };
+
+        Q.when(deferred.promise, null, null, function () {
+            progressed2 = true;
+            throw new Error("just a test, ok if it shows up in the console");
+        });
+        Q.when(deferred.promise, null, null, function () {
+            progressed3 = true;
+        });
+
+        deferred.notify();
+        deferred.resolve();
+
+        return promise;
+    });
+
+    it("calls the progress listener even if later rejected", function () {
+        var progressed = false;
+        var deferred = Q.defer();
+        var promise = Q.when(
+            deferred.promise,
+            function () {
+                expect(true).toBe(false);
+            },
+            function () {
+                expect(progressed).toEqual(true);
+            },
+            function () {
+                progressed = true;
+            }
+        );
+
+        deferred.notify();
+        deferred.reject();
+
+        return promise;
+    });
+
+    it("calls the progress listener with the notify values", function () {
+        var progressValues = [];
+        var desiredProgressValues = [{}, {}, "foo", 5];
+        var deferred = Q.defer();
+        var promise = Q.when(
+            deferred.promise,
+            function () {
+                for (var i = 0; i < desiredProgressValues.length; ++i) {
+                    var desired = desiredProgressValues[i];
+                    var actual = progressValues[i];
+                    expect(actual).toBe(desired);
+                }
+            },
+            function () {
+                expect(true).toBe(false);
+            },
+            function (value) {
+                progressValues.push(value);
+            }
+        );
+
+        for (var i = 0; i < desiredProgressValues.length; ++i) {
+            deferred.notify(desiredProgressValues[i]);
+        }
+        deferred.resolve();
+
+        return promise;
+    });
+
+    it("does not call the progress listener if notify is called after fulfillment", function () {
+        var deferred = Q.defer();
+        var called = false;
+
+        Q.when(deferred.promise, null, null, function () {
+            called = true;
+        });
+
+        deferred.resolve();
+        deferred.notify();
+
+        return Q.delay(10).then(function () {
+            expect(called).toBe(false);
+        });
+    });
+
+    it("does not call the progress listener if notify is called after rejection", function () {
+        var deferred = Q.defer();
+        var called = false;
+
+        Q.when(deferred.promise, null, null, function () {
+            called = true;
+        });
+
+        deferred.reject();
+        deferred.notify();
+
+        return Q.delay(10).then(function () {
+            expect(called).toBe(false);
+        });
+    });
+
+    it("should not save and re-emit progress notifications", function () {
+        var deferred = Q.defer();
+        var progressValues = [];
+
+        deferred.notify(1);
+
+        var promise = Q.when(
+            deferred.promise,
+            function () {
+                expect(progressValues).toEqual([2]);
+            },
+            function () {
+                expect(true).toBe(false);
+            },
+            function (progressValue) {
+                progressValues.push(progressValue);
+            }
+        );
+
+        deferred.notify(2);
+        deferred.resolve();
+
+        return promise;
+    });
+
+    it("should allow attaching progress listeners w/ .progress", function () {
+        var progressed = false;
+        var deferred = Q.defer();
+
+        deferred.promise.progress(function () {
+            progressed = true;
+        });
+
+        deferred.notify();
+        deferred.resolve();
+
+        return deferred.promise;
+    });
+
+    it("should allow attaching progress listeners w/ Q.progress", function () {
+        var progressed = false;
+        var deferred = Q.defer();
+
+        Q.progress(deferred.promise, function () {
+            progressed = true;
+        });
+
+        deferred.notify();
+        deferred.resolve();
+
+        return deferred.promise;
+    });
+
+    it("should call the progress listener with undefined context", function () {
+        var progressed = false;
+        var progressContext = {};
+        var deferred = Q.defer();
+        var promise = Q.when(
+            deferred.promise,
+            function () {
+                expect(progressed).toBe(true);
+                expect(progressContext).toBe(calledAsFunctionThis);
+            },
+            function () {
+                expect(true).toBe(false);
+            },
+            function () {
+                progressed = true;
+                progressContext = this;
+            }
+        );
+
+        deferred.notify();
+        deferred.resolve();
+
+        return promise;
+    });
+
+    it("should forward only the first notify argument to listeners", function () {
+        var progressValueArrays = [];
+        var deferred = Q.defer();
+
+        var promise = Q.when(
+            deferred.promise,
+            function () {
+                expect(progressValueArrays).toEqual([[1], [2], [4]]);
+            },
+            function () {
+                expect(true).toBe(false);
+            },
+            function () {
+                var args = Array.prototype.slice.call(arguments);
+                progressValueArrays.push(args);
+            }
+        );
+
+        deferred.notify(1);
+        deferred.notify(2, 3);
+        deferred.notify(4, 5, 6);
+        deferred.resolve();
+
+        return promise;
+    });
+
+    it("should work with .then as well", function () {
+        var progressed = false;
+        var deferred = Q.defer();
+
+        var promise = deferred.promise.then(
+            function () {
+                expect(progressed).toBe(true);
+            },
+            function () {
+                expect(true).toBe(false);
+            },
+            function () {
+                progressed = true;
+            }
+        );
+
+        deferred.notify();
+        deferred.resolve();
+
+        return promise;
+    });
+
+    it("should re-throw all errors thrown by listeners to Q.onerror", function () {
+        var theError = new Error("boo!");
+
+        var def = Q.defer();
+        def.promise.progress(function () {
+            throw theError;
+        });
+
+        var deferred = Q.defer();
+        Q.onerror = function (error) {
+            expect(error).toBe(theError);
+            deferred.resolve();
+        };
+        Q.delay(100).then(deferred.reject);
+
+        def.notify();
+
+        return deferred.promise;
+    });
+});
+
+describe("promises for objects", function () {
+
+    describe("get", function () {
+
+        it("fulfills a promise", function () {
+            var deferred = Q.defer();
+            deferred.resolve({a: 1});
+            return deferred.promise.get("a")
+            .then(function (a) {
+                expect(a).toBe(1);
+            });
+        });
+
+        it("propagates a rejection", function () {
+            var exception = new Error("boo!");
+            return Q.fcall(function () {
+                throw exception;
+            })
+            .get("a")
+            .then(function () {
+                expect("be").toBe("not to be");
+            }, function (_exception) {
+                expect(_exception).toBe(exception);
+            });
+        });
+
+    });
+
+    describe("set", function () {
+
+        it("fulfills a promise", function () {
+            var object = {};
+            return Q(object)
+            .set("a", 1)
+            .then(function (result) {
+                expect(result).toBe(undefined);
+                expect(object.a).toBe(1);
+            });
+        });
+
+        it("propagates a rejection", function () {
+            var exception = new Error("Gah!");
+            return Q.reject(exception)
+            .set("a", 1)
+            .then(function () {
+                expect("frozen over").toBe("quite warm");
+            }, function (_exception) {
+                expect(_exception).toBe(exception);
+            });
+        });
+
+    });
+
+    describe("del", function () {
+
+        it("fulfills a promise", function () {
+            var object = {a: 10};
+            return Q.fcall(function () {
+                return object;
+            })
+            .del("a")
+            .then(function (result) {
+                expect("a" in object).toBe(false);
+                expect(result).toBe(void 0);
+            }, function () {
+                expect("up").toBe("down");
+            });
+        });
+
+        it("propagates a rejection", function () {
+            var exception = new Error("hah-hah");
+            return Q.fcall(function () {
+                throw exception;
+            })
+            .del("a")
+            .then(function () {
+                expect(true).toBe(false);
+            }, function (_exception) {
+                expect(_exception).toBe(exception);
+            });
+        });
+
+    });
+
+    describe("post", function () {
+
+        it("fulfills a promise", function () {
+            var subject = {
+                a: function a(value) {
+                    this._a = value;
+                    return 1 + value;
+                }
+            };
+            return Q.when(Q.post(subject, "a", [1]), function (two) {
+                expect(subject._a).toBe(1);
+                expect(two).toBe(2);
+            });
+        });
+
+        it("works as apply when given no name", function () {
+            return Q(function (a, b, c) {
+                return a + b + c;
+            })
+            .post(undefined, [1, 2, 3])
+            .then(function (sum) {
+                expect(sum).toEqual(6);
+            });
+        });
+
+    });
+
+    describe("send", function () {
+
+        it("fulfills a promise", function () {
+            var foo;
+            var subject = {
+                foo: function (_bar) {
+                    return _bar;
+                },
+                bar: function (_foo, _bar) {
+                    foo = _foo;
+                    return this.foo(_bar);
+                }
+            };
+            return Q.send(subject, "bar", 1, 2)
+            .then(function (two) {
+                expect(foo).toEqual(1);
+                expect(two).toEqual(2);
+            });
+        });
+
+        it("is rejected for undefined method", function () {
+            var subject = {};
+            return Q(subject)
+            .send("foo")
+            .then(function () {
+                expect("here").toEqual("not here");
+            }, function () {
+            });
+        });
+
+        it("is rejected for undefined object", function () {
+            return Q()
+            .send("foo")
+            .then(function () {
+                expect("here").toEqual("not here");
+            }, function () {
+            });
+        });
+
+    });
+
+    describe("keys", function () {
+
+        function Klass (a, b) {
+            this.a = a;
+            this.b = b;
+        }
+        Klass.prototype.notOwn = 1;
+
+        it("fulfills a promise", function () {
+            return Q.keys(new Klass(10, 20))
+            .then(function (keys) {
+                expect(keys.sort()).toEqual(["a", "b"]);
+            });
+        });
+
+    });
+
+});
+
+describe("promises for functions", function () {
+
+    describe("fapply", function () {
+        it("fulfills a promise using arguments", function () {
+            return Q(function (a, b, c) {
+                return a + b + c;
+            })
+            .fapply([1, 2, 3])
+            .then(function (sum) {
+                expect(sum).toEqual(6);
+            });
+        });
+    });
+
+    describe("fcall", function () {
+        it("fulfills a promise using arguments", function () {
+            return Q(function (a, b, c) {
+                return a + b + c;
+            })
+            .fcall(1, 2, 3)
+            .then(function (sum) {
+                expect(sum).toEqual(6);
+            });
+        });
+    });
+
+    describe("fbind", function () {
+
+        it("accepts a promise for a function", function () {
+            return Q.fbind(Q(function (high, low) {
+                return high - low;
+            }))
+            (2, 1)
+            .then(function (difference) {
+                expect(difference).toEqual(1);
+            });
+        });
+
+        it("chains partial application on a promise for a function", function () {
+            return Q(function (a, b) {
+                return a * b;
+            })
+            .fbind(2)(3)
+            .then(function (product) {
+                expect(product).toEqual(6);
+            });
+        });
+
+        it("returns a fulfilled promise", function () {
+            var result = {};
+            var bound = Q.fbind(function () {
+                return result;
+            });
+            return bound()
+            .then(function (_result) {
+                expect(_result).toBe(result);
+            });
+        });
+
+        it("returns a rejected promise from a thrown error", function () {
+            var exception = new Error("Boo!");
+            var bound = Q.fbind(function () {
+                throw exception;
+            });
+            return bound()
+            .then(function () {
+                expect("flying pigs").toBe("swillin' pigs");
+            }, function (_exception) {
+                expect(_exception).toBe(exception);
+            });
+        });
+
+        it("passes arguments through", function () {
+            var x = {}, y = {};
+            var bound = Q.fbind(function (a, b) {
+                expect(a).toBe(x);
+                expect(b).toBe(y);
+            });
+            return bound(x, y);
+        });
+
+        it("passes and also partially applies arguments", function () {
+            var x = {}, y = {};
+            var bound = Q.fbind(function (a, b) {
+                expect(a).toBe(x);
+                expect(b).toBe(y);
+            }, x);
+            return bound(y);
+        });
+
+        it("doesn't bind `this`", function () {
+            var theThis = { me: "this" };
+            var bound = Q.fbind(function () {
+                expect(this).toBe(theThis);
+            });
+
+            return bound.call(theThis);
+        });
+
+    });
+
+});
+
+describe("inspect", function () {
+
+    it("for a fulfilled promise", function () {
+        expect(Q(10).inspect()).toEqual({
+            state: "fulfilled",
+            value: 10
+        });
+    });
+
+    it("for a rejected promise", function () {
+        var error = new Error("In your face.");
+        var rejected = Q.reject(error);
+        expect(rejected.inspect()).toEqual({
+            state: "rejected",
+            reason: error
+        });
+    });
+
+    it("for a pending, unresolved promise", function () {
+        var pending = Q.defer().promise;
+        expect(pending.inspect()).toEqual({ state: "pending" });
+    });
+
+    it("for a promise resolved to a rejected promise", function () {
+        var deferred = Q.defer();
+        var error = new Error("Rejected!");
+        var rejected = Q.reject(error);
+        deferred.resolve(rejected);
+
+        expect(deferred.promise.inspect()).toEqual({
+            state: "rejected",
+            reason: error
+        });
+    });
+
+    it("for a promise resolved to a fulfilled promise", function () {
+        var deferred = Q.defer();
+        var fulfilled = Q(10);
+        deferred.resolve(fulfilled);
+
+        expect(deferred.promise.inspect()).toEqual({
+            state: "fulfilled",
+            value: 10
+        });
+    });
+
+    it("for a promise resolved to a pending promise", function () {
+        var a = Q.defer();
+        var b = Q.defer();
+        a.resolve(b.promise);
+
+        expect(a.promise.inspect()).toEqual({ state: "pending" });
+    });
+
+});
+
+describe("promise states", function () {
+
+    it("of fulfilled value", function () {
+        expect(Q.isFulfilled(void 0)).toBe(true);
+        expect(Q.isRejected(false)).toBe(false);
+        expect(Q.isPending(true)).toBe(false);
+    });
+
+    it("of fulfillment", function () {
+        var promise = Q(10);
+        expect(Q.isFulfilled(promise)).toBe(true);
+        expect(promise.isFulfilled()).toBe(true);
+        expect(Q.isRejected(promise)).toBe(false);
+        expect(promise.isRejected()).toBe(false);
+        expect(Q.isPending(promise)).toBe(false);
+        expect(promise.isPending()).toBe(false);
+    });
+
+    it("of rejection", function () {
+        var error = new Error("Oh, snap.");
+        var promise = Q.reject(error);
+        expect(promise.isFulfilled()).toBe(false);
+        expect(promise.isRejected()).toBe(true);
+        expect(promise.isPending()).toBe(false);
+    });
+
+    it("of rejection with a falsy value", function () {
+        var promise = Q.reject(undefined);
+        expect(promise.isFulfilled()).toBe(false);
+        expect(promise.isRejected()).toBe(true);
+        expect(promise.isPending()).toBe(false);
+    });
+
+    it("of deferred", function () {
+        var deferred = Q.defer();
+        var promise = deferred.promise;
+        expect(promise.isFulfilled()).toBe(false);
+        expect(promise.isRejected()).toBe(false);
+        expect(promise.isPending()).toBe(true);
+    });
+
+    it("of deferred rejection", function () {
+        var deferred = Q.defer();
+        var rejection = Q.reject(new Error("Rejected!"));
+        deferred.resolve(rejection);
+        var promise = deferred.promise;
+        expect(promise.isFulfilled()).toBe(false);
+        expect(promise.isRejected()).toBe(true);
+        expect(promise.isPending()).toBe(false);
+    });
+
+    it("of deferred fulfillment", function () {
+        var deferred = Q.defer();
+        deferred.resolve(10);
+        var promise = deferred.promise;
+        expect(promise.isFulfilled()).toBe(true);
+        expect(promise.isRejected()).toBe(false);
+        expect(promise.isPending()).toBe(false);
+    });
+
+    it("of deferred deferred", function () {
+        var a = Q.defer();
+        var b = Q.defer();
+        a.resolve(b.promise);
+        var promise = a.promise;
+        expect(promise.isFulfilled()).toBe(false);
+        expect(promise.isRejected()).toBe(false);
+        expect(promise.isPending()).toBe(true);
+    });
+
+    it("of isFulfilled side effects", function () {
+        var deferred = Q.defer();
+        var finished = false;
+
+        waitsFor(function () {
+            return finished;
+        });
+
+        var parentPromise = deferred.promise;
+
+        var childPromise = parentPromise.then(function () {
+            expect(parentPromise.isFulfilled()).toBe(true);
+            expect(childPromise.isFulfilled()).toBe(false);
+
+            return parentPromise.then(function (value) {
+                finished = true;
+                return value + 1;
+            });
+        });
+
+        deferred.resolve(1);
+
+        runs(function () {
+            expect(childPromise.isPending()).toBe(false);
+            expect(childPromise.isRejected()).toBe(false);
+            expect(childPromise.isFulfilled()).toBe(true);
+            expect(childPromise.inspect().value).toBe(2);
+        });
+    });
+
+});
+
+describe("propagation", function () {
+
+    it("propagate through then with no callback", function () {
+        return Q(10)
+        .then()
+        .then(function (ten) {
+            expect(ten).toBe(10);
+        });
+    });
+
+    it("propagate through then with modifying callback", function () {
+        return Q(10)
+        .then(function (ten) {
+            return ten + 10;
+        })
+        .then(function (twen) {
+            expect(twen).toBe(20);
+        });
+    });
+
+    it("errback recovers from exception", function () {
+        var error = new Error("Bah!");
+        return Q.reject(error)
+        .then(null, function (_error) {
+            expect(_error).toBe(error);
+            return 10;
+        })
+        .then(function (value) {
+            expect(value).toBe(10);
+        });
+    });
+
+    it("rejection propagates through then with no errback", function () {
+        var error = new Error("Foolish mortals!");
+        return Q.reject(error)
+        .then()
+        .then(null, function (_error) {
+            expect(_error).toBe(error);
+        });
+    });
+
+    it("rejection intercepted and rethrown", function () {
+        var error = new Error("Foolish mortals!");
+        var nextError = new Error("Silly humans!");
+        return Q.reject(error)
+        .fail(function () {
+            throw nextError;
+        })
+        .then(null, function (_error) {
+            expect(_error).toBe(nextError);
+        });
+    });
+
+    it("resolution is forwarded through deferred promise", function () {
+        var a = Q.defer();
+        var b = Q.defer();
+        a.resolve(b.promise);
+        b.resolve(10);
+        return a.promise.then(function (eh) {
+            expect(eh).toEqual(10);
+        });
+    });
+
+    it("should propagate progress by default", function () {
+        var d = Q.defer();
+
+        var progressValues = [];
+        var promise = d.promise
+        .then()
+        .then(
+            function () {
+                expect(progressValues).toEqual([1]);
+            },
+            function () {
+                expect(true).toBe(false);
+            },
+            function (progressValue) {
+                progressValues.push(progressValue);
+            }
+        );
+
+        d.notify(1);
+        d.resolve();
+
+        return promise;
+    });
+
+    it("should allow translation of progress in the progressback", function () {
+        var d = Q.defer();
+
+        var progressValues = [];
+        var promise = d.promise
+        .progress(function (p) {
+            return p + 5;
+        })
+        .then(
+            function () {
+                expect(progressValues).toEqual([10]);
+            },
+            function () {
+                expect(true).toBe(false);
+            },
+            function (progressValue) {
+                progressValues.push(progressValue);
+            }
+        );
+
+        d.notify(5);
+        d.resolve();
+
+        return promise;
+    });
+
+
+    it("should stop progress propagation if an error is thrown", function () {
+        var def = Q.defer();
+        var p2 = def.promise.progress(function () {
+            throw new Error("boo!");
+        });
+
+        Q.onerror = function () { /* just swallow it for this test */ };
+
+        var progressValues = [];
+        var result = p2.then(
+            function () {
+                expect(progressValues).toEqual([]);
+            },
+            function () {
+                expect(true).toBe(false);
+            },
+            function (progressValue) {
+                progressValues.push(progressValue);
+            }
+        );
+
+        def.notify();
+        def.resolve();
+        return result;
+    });
+});
+
+describe("all", function () {
+    it("fulfills when passed an empty array", function () {
+        return Q.all([]);
+    });
+
+    it("rejects after any constituent promise is rejected", function () {
+        var toResolve = Q.defer(); // never resolve
+        var toReject = Q.defer();
+        var promises = [toResolve.promise, toReject.promise];
+        var promise = Q.all(promises);
+
+        toReject.reject(new Error("Rejected"));
+
+        return Q.delay(250)
+        .then(function () {
+            expect(promise.isRejected()).toBe(true);
+        })
+        .timeout(1000);
+    });
+
+    it("resolves foreign thenables", function () {
+        var normal = Q(1);
+        var foreign = { then: function (f) { f(2); } };
+
+        return Q.all([normal, foreign])
+        .then(function (result) {
+            expect(result).toEqual([1, 2]);
+        });
+    });
+
+    it("fulfills when passed an sparse array", function () {
+        var toResolve = Q.defer();
+        var promises = [];
+        promises[0] = Q(0);
+        promises[2] = toResolve.promise;
+        var promise = Q.all(promises);
+
+        toResolve.resolve(2);
+
+        return promise.then(function (result) {
+            expect(result).toEqual([0, void 0, 2]);
+        });
+    });
+
+    it("modifies the input array", function () {
+        var input = [Q(0), Q(1)];
+
+        return Q.all(input).then(function (result) {
+            expect(result).toBe(input);
+            expect(input).toEqual([0, 1]);
+        });
+    });
+
+    it("sends { index, value } progress updates", function () {
+        var deferred1 = Q.defer();
+        var deferred2 = Q.defer();
+
+        var progressValues = [];
+
+        Q.delay(50).then(function () {
+            deferred1.notify("a");
+        });
+        Q.delay(100).then(function () {
+            deferred2.notify("b");
+            deferred2.resolve();
+        });
+        Q.delay(150).then(function () {
+            deferred1.notify("c");
+            deferred1.resolve();
+        });
+
+        return Q.all([deferred1.promise, deferred2.promise]).then(
+            function () {
+                expect(progressValues).toEqual([
+                    { index: 0, value: "a" },
+                    { index: 1, value: "b" },
+                    { index: 0, value: "c" }
+                ]);
+            },
+            undefined,
+            function (progressValue) {
+                progressValues.push(progressValue);
+            }
+        )
+    });
+
+});
+
+describe("allSettled", function () {
+    it("works on an empty array", function () {
+        return Q.allSettled([])
+        .then(function (snapshots) {
+            expect(snapshots).toEqual([]);
+        });
+    });
+
+    it("deals with a mix of non-promises and promises", function () {
+        return Q.allSettled([1, Q(2), Q.reject(3)])
+        .then(function (snapshots) {
+            expect(snapshots).toEqual([
+                { state: "fulfilled", value: 1 },
+                { state: "fulfilled", value: 2 },
+                { state: "rejected", reason: 3 }
+            ]);
+        });
+    });
+
+    it("is settled after every constituent promise is settled", function () {
+        var toFulfill = Q.defer();
+        var toReject = Q.defer();
+        var promises = [toFulfill.promise, toReject.promise];
+        var fulfilled;
+        var rejected;
+
+        Q.fcall(function () {
+            toReject.reject();
+            rejected = true;
+        })
+        .delay(15)
+        .then(function () {
+            toFulfill.resolve();
+            fulfilled = true;
+        });
+
+        return Q.allSettled(promises)
+        .then(function () {
+            expect(fulfilled).toBe(true);
+            expect(rejected).toBe(true);
+        });
+    });
+
+    it("does not modify the input array", function () {
+        var input = [1, Q(2), Q.reject(3)];
+
+        return Q.allSettled(input)
+        .then(function (snapshots) {
+            expect(snapshots).not.toBe(input);
+            expect(snapshots).toEqual([
+                { state: "fulfilled", value: 1 },
+                { state: "fulfilled", value: 2 },
+                { state: "rejected", reason: 3 }
+            ]);
+        });
+    });
+
+});
+
+describe("spread", function () {
+
+    it("spreads values across arguments", function () {
+        return Q.spread([1, 2, 3], function (a, b) {
+            expect(b).toBe(2);
+        });
+    });
+
+    it("spreads promises for arrays across arguments", function () {
+        return Q([Q(10)])
+        .spread(function (value) {
+            expect(value).toEqual(10);
+        });
+    });
+
+    it("spreads arrays of promises across arguments", function () {
+        var deferredA = Q.defer();
+        var deferredB = Q.defer();
+
+        var promise = Q.spread([deferredA.promise, deferredB.promise],
+                               function (a, b) {
+            expect(a).toEqual(10);
+            expect(b).toEqual(20);
+        });
+
+        Q.delay(5).then(function () {
+            deferredA.resolve(10);
+        });
+        Q.delay(10).then(function () {
+            deferredB.resolve(20);
+        });
+
+        return promise;
+    });
+
+    it("calls the errback when given a rejected promise", function () {
+        var err = new Error();
+        return Q.spread([Q(10), Q.reject(err)],
+            function () {
+                expect(true).toBe(false);
+            },
+            function (actual) {
+                expect(actual).toBe(err);
+            }
+        );
+    });
+
+});
+
+describe("fin", function () {
+
+    var exception1 = new Error("boo!");
+    var exception2 = new TypeError("evil!");
+
+    describe("when the promise is fulfilled", function () {
+
+        it("should call the callback", function () {
+            var called = false;
+
+            return Q("foo")
+            .fin(function () {
+                called = true;
+            })
+            .then(function () {
+                expect(called).toBe(true);
+            });
+        });
+
+        it("should fulfill with the original value", function () {
+            return Q("foo")
+            .fin(function () {
+                return "bar";
+            })
+            .then(function (result) {
+                expect(result).toBe("foo");
+            });
+        });
+
+        describe("when the callback returns a promise", function () {
+
+            describe("that is fulfilled", function () {
+                it("should fulfill with the original reason after that promise resolves", function () {
+                    var promise = Q.delay(250);
+
+                    return Q("foo")
+                    .fin(function () {
+                        return promise;
+                    })
+                    .then(function (result) {
+                        expect(Q.isPending(promise)).toBe(false);
+                        expect(result).toBe("foo");
+                    });
+                });
+            });
+
+            describe("that is rejected", function () {
+                it("should reject with this new rejection reason", function () {
+                    return Q("foo")
+                    .fin(function () {
+                        return Q.reject(exception1);
+                    })
+                    .then(function () {
+                        expect(false).toBe(true);
+                    },
+                    function (exception) {
+                        expect(exception).toBe(exception1);
+                    });
+                });
+            });
+
+        });
+
+        describe("when the callback throws an exception", function () {
+            it("should reject with this new exception", function () {
+                return Q("foo")
+                .fin(function () {
+                    throw exception1;
+                })
+                .then(function () {
+                    expect(false).toBe(true);
+                },
+                function (exception) {
+                    expect(exception).toBe(exception1);
+                });
+            });
+        });
+
+    });
+
+    describe("when the promise is rejected", function () {
+
+        it("should call the callback", function () {
+            var called = false;
+
+            return Q.reject(exception1)
+            .fin(function () {
+                called = true;
+            })
+            .then(function () {
+                expect(called).toBe(true);
+            }, function () {
+                expect(called).toBe(true);
+            });
+        });
+
+        it("should reject with the original reason", function () {
+            return Q.reject(exception1)
+            .fin(function () {
+                return "bar";
+            })
+            .then(function () {
+                expect(false).toBe(true);
+            },
+            function (exception) {
+                expect(exception).toBe(exception1);
+            });
+        });
+
+        describe("when the callback returns a promise", function () {
+
+            describe("that is fulfilled", function () {
+                it("should reject with the original reason after that promise resolves", function () {
+                    var promise = Q.delay(250);
+
+                    return Q.reject(exception1)
+                    .fin(function () {
+                        return promise;
+                    })
+                    .then(function () {
+                        expect(false).toBe(true);
+                    },
+                    function (exception) {
+                        expect(exception).toBe(exception1);
+                        expect(Q.isPending(promise)).toBe(false);
+                    });
+                });
+            });
+
+            describe("that is rejected", function () {
+                it("should reject with the new reason", function () {
+                    return Q.reject(exception1)
+                    .fin(function () {
+                        return Q.reject(exception2);
+                    })
+                    .then(function () {
+                        expect(false).toBe(true);
+                    },
+                    function (exception) {
+                        expect(exception).toBe(exception2);
+                    });
+                });
+            });
+
+        });
+
+        describe("when the callback throws an exception", function () {
+            it("should reject with this new exception", function () {
+                return Q.reject(exception1)
+                .fin(function () {
+                    throw exception2;
+                })
+                .then(function () {
+                    expect(false).toBe(true);
+                },
+                function (exception) {
+                    expect(exception).toBe(exception2);
+                });
+            });
+        });
+
+    });
+
+});
+
+describe("done", function () {
+    describe("when the promise is fulfilled", function () {
+        describe("and the callback does not throw", function () {
+            it("should call the callback and return nothing", function () {
+                var called = false;
+
+                var promise = Q();
+
+                var returnValue = promise.done(function () {
+                    called = true;
+                });
+
+                return promise.fail(function () { }).fin(function () {
+                    expect(called).toBe(true);
+                    expect(returnValue).toBe(undefined);
+                });
+            });
+        });
+
+        describe("and the callback throws", function () {
+            it("should rethrow that error in the next turn and return nothing", function () {
+                var turn = 0;
+                Q.nextTick(function () {
+                    ++turn;
+                });
+
+                var returnValue = Q().done(
+                    function () {
+                        throw "foo";
+                    }
+                );
+
+                var deferred = Q.defer();
+                Q.onerror = function (error) {
+                    expect(turn).toBe(1);
+                    expect(error).toBe("foo");
+                    expect(returnValue).toBe(undefined);
+                    deferred.resolve();
+                };
+                Q.delay(100).then(deferred.reject);
+
+                return deferred.promise;
+            });
+        });
+    });
+
+    describe("when the promise is rejected", function () {
+        describe("and the errback handles it", function () {
+            it("should call the errback and return nothing", function () {
+                var called = false;
+
+                var promise = Q.reject(new Error());
+
+                var returnValue = promise.done(
+                    function () { },
+                    function () {
+                        called = true;
+                    }
+                );
+
+                return promise.fail(function () { }).fin(function () {
+                    expect(called).toBe(true);
+                    expect(returnValue).toBe(undefined);
+                });
+            });
+        });
+
+        describe("and the errback throws", function () {
+            it("should rethrow that error in the next turn and return nothing", function () {
+                var turn = 0;
+                Q.nextTick(function () {
+                    ++turn;
+                });
+
+                var returnValue = Q.reject("bar").done(
+                    null,
+                    function () {
+                        throw "foo";
+                    }
+                );
+
+                var deferred = Q.defer();
+                Q.onerror = function (error) {
+                    expect(turn).toBe(1);
+                    expect(error).toBe("foo");
+                    expect(returnValue).toBe(undefined);
+                    deferred.resolve();
+                };
+                Q.delay(100).then(deferred.reject);
+
+                return deferred.promise;
+            });
+        });
+
+        describe("and there is no errback", function () {
+            it("should throw the original error in the next turn", function () {
+                var turn = 0;
+                Q.nextTick(function () {
+                    ++turn;
+                });
+
+                var returnValue = Q.reject("bar").done();
+
+                var deferred = Q.defer();
+                Q.onerror = function (error) {
+                    expect(turn).toBe(1);
+                    expect(error).toBe("bar");
+                    expect(returnValue).toBe(undefined);
+                    deferred.resolve();
+                };
+                Q.delay(10).then(deferred.reject);
+
+                return deferred.promise;
+            });
+        });
+    });
+
+    it("should attach a progress listener", function () {
+        var deferred = Q.defer();
+
+        var spy = jasmine.createSpy();
+        deferred.promise.done(null, null, spy);
+
+        deferred.notify(10);
+        deferred.resolve();
+
+        return deferred.promise.then(function () {
+            expect(spy).toHaveBeenCalledWith(10);
+        });
+    });
+});
+
+describe("timeout", function () {
+    it("should do nothing if the promise fulfills quickly", function () {
+        return Q.delay(10).timeout(200);
+    });
+
+    it("should do nothing if the promise rejects quickly", function () {
+        var goodError = new Error("haha!");
+        return Q.delay(10)
+        .then(function () {
+            throw goodError;
+        })
+        .timeout(200)
+        .then(undefined, function (error) {
+            expect(error).toBe(goodError);
+        });
+    });
+
+    it("should reject with a timeout error if the promise is too slow", function () {
+        return Q.delay(100)
+        .timeout(10)
+        .then(
+            function () {
+                expect(true).toBe(false);
+            },
+            function (error) {
+                expect(/time/i.test(error.message)).toBe(true);
+            }
+        );
+    });
+
+    it("should pass through progress notifications", function () {
+        var deferred = Q.defer();
+
+        var progressValsSeen = [];
+        var promise = Q.timeout(deferred.promise, 300).then(function () {
+            expect(progressValsSeen).toEqual([1, 2, 3]);
+        }, undefined, function (progressVal) {
+            progressValsSeen.push(progressVal);
+        });
+
+        Q.delay(5).then(function () { deferred.notify(1); });
+        Q.delay(15).then(function () { deferred.notify(2); });
+        Q.delay(25).then(function () { deferred.notify(3); });
+        Q.delay(35).then(function () { deferred.resolve(); });
+
+        return promise;
+    });
+
+    it("should reject with a custom timeout error if the promise is too slow and msg was provided", function () {
+        return Q.delay(100)
+        .timeout(10, "custom")
+        .then(
+            function () {
+                expect(true).toBe(false);
+            },
+            function (error) {
+                expect(/custom/i.test(error.message)).toBe(true);
+            }
+        );
+    });
+
+
+});
+
+describe("delay", function () {
+    it("should delay fulfillment", function () {
+        var promise = Q(5).delay(50);
+
+        setTimeout(function () {
+            expect(promise.isPending()).toBe(true);
+        }, 40);
+
+        return promise;
+    });
+
+    it("should not delay rejection", function () {
+        var promise = Q.reject(5).delay(50);
+
+        return Q.delay(20).then(function () {
+            expect(promise.isPending()).toBe(false);
+        });
+    });
+
+    it("should treat a single argument as a time", function () {
+        var promise = Q.delay(50);
+
+        setTimeout(function () {
+            expect(promise.isPending()).toBe(true);
+        }, 40);
+
+        return promise;
+    });
+
+    it("should treat two arguments as a value + a time", function () {
+        var promise = Q.delay("what", 50);
+
+        setTimeout(function () {
+            expect(promise.isPending()).toBe(true);
+        }, 40);
+
+        return promise.then(function (value) {
+            expect(value).toBe("what");
+        });
+    });
+
+    it("should delay after resolution", function () {
+        var promise1 = Q.delay("what", 30);
+        var promise2 = promise1.delay(30);
+
+        setTimeout(function () {
+            expect(promise1.isPending()).toBe(false);
+            expect(promise2.isPending()).toBe(true);
+        }, 40);
+
+        return promise2.then(function (value) {
+            expect(value).toBe("what");
+        });
+    });
+
+
+    it("should pass through progress notifications from passed promises", function () {
+        var deferred = Q.defer();
+
+        var progressValsSeen = [];
+        var promise = Q.delay(deferred.promise, 100).then(function () {
+            expect(progressValsSeen).toEqual([1, 2, 3]);
+        }, undefined, function (progressVal) {
+            progressValsSeen.push(progressVal);
+        });
+
+        Q.delay(5).then(function () { deferred.notify(1); });
+        Q.delay(15).then(function () { deferred.notify(2); });
+        Q.delay(25).then(function () { deferred.notify(3); });
+        Q.delay(35).then(function () { deferred.resolve(); });
+
+        return promise;
+    });
+});
+
+describe("thenResolve", function () {
+    describe("Resolving with a non-thenable value", function () {
+        it("returns a promise for that object once the promise is resolved", function () {
+            var waited = false;
+            return Q.delay(20)
+                .then(function () {
+                    waited = true;
+                })
+                .thenResolve("foo")
+                .then(function (val) {
+                    expect(waited).toBe(true);
+                    expect(val).toBe("foo");
+                });
+        });
+
+        describe("based off a rejected promise", function () {
+            it("does nothing, letting the rejection flow through", function () {
+                return Q.reject("boo")
+                    .thenResolve("foo")
+                    .then(
+                        function () {
+                            expect(true).toBe(false);
+                        },
+                        function (reason) {
+                            expect(reason).toBe("boo");
+                        }
+                    );
+            });
+        });
+    });
+
+    describe("Resolving with an promise", function () {
+        it("returns a promise for the result of that promise once the promise is resolved", function () {
+            var waited = false;
+            return Q.delay(20)
+                .then(function () {
+                    waited = true;
+                })
+                .thenResolve(Q("foo"))
+                .then(function (val) {
+                    expect(waited).toBe(true);
+                    expect(val).toBe("foo");
+                });
+        });
+    });
+});
+
+describe("thenReject", function () {
+    describe("Rejecting with a reason", function () {
+        it("returns a promise rejected with that object once the original promise is resolved", function () {
+            var waited = false;
+            return Q.delay(20)
+                .then(function () {
+                    waited = true;
+                })
+                .thenReject("foo")
+                .then(
+                    function () {
+                        expect(true).toBe(false);
+                    },
+                    function (reason) {
+                        expect(waited).toBe(true);
+                        expect(reason).toBe("foo");
+                    }
+                );
+        });
+
+        describe("based off a rejected promise", function () {
+            it("does nothing, letting the rejection flow through", function () {
+                return Q.reject("boo")
+                    .thenResolve("foo")
+                    .then(
+                        function () {
+                            expect(true).toBe(false);
+                        },
+                        function (reason) {
+                            expect(reason).toBe("boo");
+                        }
+                    );
+            });
+        });
+    });
+});
+
+
+describe("thenables", function () {
+
+    it("assimilates a thenable with fulfillment with resolve", function () {
+        return Q({
+            then: function (resolved) {
+                resolved(10);
+            }
+        })
+        .then(function (ten) {
+            expect(ten).toEqual(10);
+        })
+        .then(function (undefined) {
+            expect(undefined).toEqual(void 0);
+        });
+    });
+
+    it("assimilates a thenable with progress and fulfillment (using resolve)", function () {
+        var progressValueArrays = [];
+        return Q({
+            then: function (fulfilled, rejected, progressed) {
+                Q.nextTick(function () {
+                    progressed(1, 2);
+                    progressed(3, 4, 5);
+                    fulfilled();
+                });
+            }
+        })
+        .progress(function () {
+            progressValueArrays.push(Array.prototype.slice.call(arguments));
+        })
+        .then(function () {
+            expect(progressValueArrays).toEqual([[1], [3]]);
+        });
+    });
+
+    it("assimilates a thenable with progress and fulfillment (using when)", function () {
+        var progressValueArrays = [];
+        return Q.when({
+            then: function (fulfilled, rejected, progressed) {
+                Q.nextTick(function () {
+                    progressed(1, 2);
+                    progressed(3, 4, 5);
+                    fulfilled();
+                });
+            }
+        })
+        .progress(function () {
+            progressValueArrays.push(Array.prototype.slice.call(arguments));
+        })
+        .then(function () {
+            expect(progressValueArrays).toEqual([[1], [3]]);
+        });
+    });
+
+    it("flows fulfillment into a promise pipeline", function () {
+        return Q({
+            then: function (resolved) {
+                resolved([10]);
+            }
+        })
+        .get(0)
+        .then(function (ten) {
+            expect(ten).toEqual(10);
+        });
+    });
+
+    it("assimilates an immediately-fulfilled thenable in allSettled", function () {
+        return Q.allSettled([
+            {then: function (win) {
+                win(10);
+            }}
+        ])
+        .then(function (snapshots) {
+            expect(snapshots).toEqual([{ state: "fulfilled", value: 10 }]);
+        });
+    });
+
+    it("assimilates an eventually-fulfilled thenable in allSettled", function () {
+        return Q.allSettled([
+            {then: function (win) {
+                setTimeout(function () {
+                    win(10);
+                }, 100);
+            }}
+        ])
+        .then(function (snapshots) {
+            expect(snapshots).toEqual([{ state: "fulfilled", value: 10 }]);
+        });
+    });
+
+});
+
+describe("node support", function () {
+
+    var exception = new Error("That is not your favorite color.");
+
+    var obj = {
+        method: function (a, b, c, callback) {
+            callback(null, a + b + c);
+        },
+        thispChecker: function (callback) {
+            callback(null, this === obj);
+        },
+        errorCallbacker: function (a, b, c, callback) {
+            callback(exception);
+        },
+        errorThrower: function () {
+            throw exception;
+        }
+    };
+
+    describe("nfapply", function () {
+
+        it("fulfills with callback result", function () {
+            return Q.nfapply(function (a, b, c, callback) {
+                callback(null, a + b + c);
+            }, [1, 2, 3])
+            .then(function (sum) {
+                expect(sum).toEqual(6);
+            });
+        });
+
+        it("rejects with callback error", function () {
+            var exception = new Error("That is not your favorite color.");
+            return Q.nfapply(function (a, b, c, callback) {
+                callback(exception);
+            }, [1, 2, 3])
+            .then(function () {
+                expect(true).toBe(false);
+            }, function (_exception) {
+                expect(_exception).toBe(exception);
+            });
+        });
+
+    });
+
+    describe("nfcall", function () {
+        it("fulfills with callback result", function () {
+            return Q.nfcall(function (a, b, c, callback) {
+                callback(null, a + b + c);
+            }, 1, 2, 3)
+            .then(function (sum) {
+                expect(sum).toEqual(6);
+            });
+        });
+
+        it("rejects with callback error", function () {
+            var exception = new Error("That is not your favorite color.");
+            return Q.nfcall(function (a, b, c, callback) {
+                callback(exception);
+            }, 1, 2, 3)
+            .then(function () {
+                expect(true).toBe(false);
+            }, function (_exception) {
+                expect(_exception).toBe(exception);
+            });
+        });
+
+    });
+
+    describe("nfbind", function () {
+
+        it("mixes partial application with complete application", function () {
+            return Q.nfbind(function (a, b, c, d, callback) {
+                callback(null, a + b + c + d);
+            }, 1, 2).call({}, 3, 4)
+            .then(function (ten) {
+                expect(ten).toBe(10);
+            });
+        });
+
+    });
+
+    describe("nbind", function () {
+
+        it("binds this, and mixes partial application with complete application", function () {
+            return Q.nbind(function (a, b, c, callback) {
+                callback(null, this + a + b + c);
+            }, 1, 2).call(3 /* effectively ignored as fn bound to 1 */, 4, 5)
+            .then(function (twelve) {
+                expect(twelve).toBe(12);
+            });
+        });
+
+        it("second arg binds this", function() {
+            var expectedThis = { test: null };
+
+            return Q.nbind(function(callback) {
+                callback(null, this);
+            }, expectedThis).call()
+            .then(function(actualThis) {
+                expect(actualThis).toEqual(expectedThis);
+            });
+        });
+
+    });
+    describe("npost", function () {
+
+        it("fulfills with callback result", function () {
+            return Q.npost(obj, "method", [1, 2, 3])
+            .then(function (sum) {
+                expect(sum).toEqual(6);
+            });
+        });
+
+        it("gets the correct thisp", function () {
+            return Q.npost(obj, "thispChecker", [])
+            .then(function (result) {
+                expect(result).toBe(true);
+            });
+        });
+
+        it("rejects with callback error", function () {
+            return Q.npost(obj, "errorCallbacker", [1, 2, 3])
+            .then(function () {
+                expect("blue").toBe("no, yellow!");
+            }, function (_exception) {
+                expect(_exception).toBe(exception);
+            });
+        });
+
+        it("rejects with thrown error", function () {
+            return Q.npost(obj, "errorThrower", [1, 2, 3])
+            .then(function () {
+                expect(true).toBe(false);
+            }, function (_exception) {
+                expect(_exception).toBe(exception);
+            });
+        });
+
+        it("works on promises for objects with Node methods", function () {
+            return Q(obj)
+            .npost("method", [1, 2, 3])
+            .then(function (sum) {
+                expect(sum).toEqual(6);
+            });
+        });
+
+    });
+
+    describe("nsend", function () {
+
+        it("fulfills with callback result", function () {
+            return Q.nsend(obj, "method", 1, 2, 3)
+            .then(function (sum) {
+                expect(sum).toEqual(6);
+            });
+        });
+
+        it("gets the correct thisp", function () {
+            return Q.nsend(obj, "thispChecker")
+            .then(function (result) {
+                expect(result).toBe(true);
+            });
+        });
+
+        it("rejects with callback error", function () {
+            return Q.nsend(obj, "errorCallbacker", 1, 2, 3)
+            .then(function () {
+                expect("blue").toBe("no, yellow!");
+            }, function (_exception) {
+                expect(_exception).toBe(exception);
+            });
+        });
+
+        it("rejects with thrown error", function () {
+            return Q.nsend(obj, "errorThrower", 1, 2, 3)
+            .then(function () {
+                expect(true).toBe(false);
+            }, function (_exception) {
+                expect(_exception).toBe(exception);
+            });
+        });
+
+        it("works on promises for objects with Node methods", function () {
+            return Q(obj)
+            .nsend("method", 1, 2, 3)
+            .then(function (sum) {
+                expect(sum).toEqual(6);
+            });
+        });
+
+    });
+
+    describe("deferred.makeNodeResolver", function () {
+
+        it("fulfills a promise with a single callback argument", function () {
+            var deferred = Q.defer();
+            var callback = deferred.makeNodeResolver();
+            callback(null, 10);
+            return deferred.promise.then(function (value) {
+                expect(value).toBe(10);
+            });
+        });
+
+        it("fulfills a promise with multiple callback arguments", function () {
+            var deferred = Q.defer();
+            var callback = deferred.makeNodeResolver();
+            callback(null, 10, 20);
+            return deferred.promise.then(function (value) {
+                expect(value).toEqual([10, 20]);
+            });
+        });
+
+        it("rejects a promise", function () {
+            var deferred = Q.defer();
+            var callback = deferred.makeNodeResolver();
+            var exception = new Error("Holy Exception of Anitoch");
+            callback(exception);
+            return deferred.promise.then(function () {
+                expect(5).toBe(3);
+            }, function (_exception) {
+                expect(_exception).toBe(exception);
+            });
+        });
+
+    });
+
+    describe("nodeify", function () {
+
+        it("calls back with a resolution", function () {
+            var spy = jasmine.createSpy();
+            Q(10).nodeify(spy);
+            waitsFor(function () {
+                return spy.argsForCall.length;
+            });
+            runs(function () {
+                expect(spy.argsForCall).toEqual([[null, 10]]);
+            });
+        });
+
+        it("calls back with an error", function () {
+            var spy = jasmine.createSpy();
+            Q.reject(10).nodeify(spy);
+            waitsFor(function () {
+                return spy.argsForCall.length;
+            });
+            runs(function () {
+                expect(spy.argsForCall).toEqual([[10]]);
+            });
+        });
+
+        it("forwards a promise", function () {
+            return Q(10).nodeify().then(function (ten) {
+                expect(ten).toBe(10);
+            });
+        });
+
+    });
+
+});
+
+describe("isPromise", function () {
+    it("returns true if passed a promise", function () {
+        expect(Q.isPromise(Q(10))).toBe(true);
+    });
+
+    it("returns false if not passed a promise", function () {
+        expect(Q.isPromise(undefined)).toBe(false);
+        expect(Q.isPromise(null)).toBe(false);
+        expect(Q.isPromise(10)).toBe(false);
+        expect(Q.isPromise("str")).toBe(false);
+        expect(Q.isPromise("")).toBe(false);
+        expect(Q.isPromise(true)).toBe(false);
+        expect(Q.isPromise(false)).toBe(false);
+        expect(Q.isPromise({})).toBe(false);
+        expect(Q.isPromise({
+            then: function () {}
+        })).toBe(false);
+        expect(Q.isPromise(function () {})).toBe(false);
+    });
+});
+
+describe("isPromiseAlike", function () {
+    it("returns true if passed a promise like object", function () {
+        expect(Q.isPromiseAlike(Q(10))).toBe(true);
+        expect(Q.isPromiseAlike({
+            then: function () {}
+        })).toBe(true);
+    });
+
+    it("returns false if not passed a promise like object", function () {
+        expect(Q.isPromiseAlike(undefined)).toBe(false);
+        expect(Q.isPromiseAlike(null)).toBe(false);
+        expect(Q.isPromiseAlike(10)).toBe(false);
+        expect(Q.isPromiseAlike("str")).toBe(false);
+        expect(Q.isPromiseAlike("")).toBe(false);
+        expect(Q.isPromiseAlike(true)).toBe(false);
+        expect(Q.isPromiseAlike(false)).toBe(false);
+        expect(Q.isPromiseAlike({})).toBe(false);
+        expect(Q.isPromiseAlike(function () {})).toBe(false);
+    });
+});
+
+if (typeof require === "function") {
+    var domain;
+    try {
+        domain = require("domain");
+    } catch (e) { }
+
+    if (domain) {
+        var EventEmitter = require("events").EventEmitter;
+
+        describe("node domain support", function () {
+            var d;
+
+            beforeEach(function () {
+                d = domain.create();
+            });
+            afterEach(function() {
+                d.dispose();
+            });
+
+            it("should work for non-promise async inside a promise handler",
+               function (done) {
+                var error = new Error("should be caught by the domain");
+
+                d.run(function () {
+                    Q().then(function () {
+                        setTimeout(function () {
+                            throw error;
+                        }, 10);
+                    });
+                });
+
+                var errorTimeout = setTimeout(function () {
+                    done(new Error("Wasn't caught"));
+                }, 100);
+
+                d.on("error", function (theError) {
+                    expect(theError).toBe(error);
+                    clearTimeout(errorTimeout);
+                    done();
+                });
+            });
+
+            it("should transfer errors from `done` into the domain",
+               function (done) {
+                var error = new Error("should be caught by the domain");
+
+                d.run(function () {
+                    Q.reject(error).done();
+                });
+
+                var errorTimeout = setTimeout(function () {
+                    done(new Error("Wasn't caught"));
+                }, 100);
+
+                d.on("error", function (theError) {
+                    expect(theError).toBe(error);
+                    clearTimeout(errorTimeout);
+                    done();
+                });
+            });
+
+            it("should take care of re-used event emitters", function (done) {
+                // See discussion in https://github.com/kriskowal/q/issues/120
+                var error = new Error("should be caught by the domain");
+
+                var e = new EventEmitter();
+
+                d.run(function () {
+                    callAsync().done();
+                });
+                setTimeout(function () {
+                    e.emit("beep");
+                }, 100);
+
+                var errorTimeout = setTimeout(function () {
+                    done(new Error("Wasn't caught"));
+                }, 500);
+
+                d.on("error", function (theError) {
+                    expect(theError).toBe(error);
+                    clearTimeout(errorTimeout);
+                    done();
+                });
+
+                function callAsync() {
+                    var def = Q.defer();
+                    e.once("beep", function () {
+                        def.reject(error);
+                    });
+                    return def.promise;
+                }
+            });
+        });
+    }
+}
+
+describe("decorator functions", function () {
+    describe("promised", function () {
+        var exception = new Error("That is not the meaning of life.");
+        it("resolves promised arguments", function () {
+            var sum = Q.promised(function add(a, b) {
+                return a + b;
+            });
+            return sum(Q(4), Q(5)).then(function (sum) {
+                expect(sum).toEqual(9);
+            });
+        });
+        it("resolves promised `this`", function () {
+            var inc = Q.promised(function inc(a) {
+                return this + a;
+            });
+            return inc.call(Q(4), Q(5)).then(function (sum) {
+                expect(sum).toEqual(9);
+            });
+        });
+        it("is rejected if an argument is rejected", function () {
+            var sum = Q.promised(function add(a, b) {
+                return a + b;
+            });
+            return sum(Q.reject(exception), Q(4)).then(function () {
+                expect(4).toEqual(42);
+            }, function (_exception) {
+                expect(_exception).toBe(exception);
+            });
+        });
+        it("is rejected if `this` is rejected", function () {
+            var inc = Q.promised(function inc(a) {
+                return this + a;
+            });
+            return inc.call(Q.reject(exception), Q(4)).then(function () {
+                expect(4).toEqual(42);
+            }, function (_exception) {
+                expect(_exception).toBe(exception);
+            });
+        });
+    });
+});
+
+describe("stack trace formatting", function () {
+    it("doesn't mangle a stack trace that gets handled twice", function () {
+        var d1 = Q.defer();
+        var d2 = Q.defer();
+        var captured = [];
+        d1.promise.done();
+        d2.promise.done();
+
+        Q.onerror = function (err) {
+            captured.push(err.stack);
+        };
+
+        var error = new Error("boom!");
+        d1.reject(error);
+        d2.reject(error);
+
+        return Q.all([d1.promise.fail(function () {}), d2.promise.fail(function () { })])
+        .then(function () {
+            expect(captured[0]).toEqual(captured[1]);
+        });
+    });
+});
+
+describe("possible regressions", function () {
+
+    describe("gh-9", function () {
+        it("treats falsy values as resolved values without error", function () {
+            expect(Q.isPending(null)).toEqual(false);
+            expect(Q.isPending(void 0)).toEqual(false);
+            expect(Q.isPending(false)).toEqual(false);
+            expect(Q.isPending()).toEqual(false);
+        });
+    });
+
+    describe("gh-22", function () {
+        it("ensures that the array prototype is intact", function () {
+            var keys = [];
+            for (var key in []) {
+                keys.push(key);
+            }
+            expect(keys.length).toBe(0);
+        });
+    });
+
+    describe("gh-73", function () {
+        it("does not choke on non-error rejection reasons", function () {
+            Q.reject(REASON).done();
+
+            var deferred = Q.defer();
+
+            Q.onerror = function (error) {
+                expect(error).toBe(REASON);
+                deferred.resolve();
+            };
+            Q.delay(10).then(deferred.reject);
+
+            return deferred.promise;
+        });
+    });
+
+    describe("gh-90", function () {
+        it("does not choke on rejection reasons with an undefined `stack`", function () {
+            var error = new RangeError(REASON);
+            error.stack = undefined;
+            Q.reject(error).done();
+
+            var deferred = Q.defer();
+
+            Q.onerror = function (theError) {
+                expect(theError).toBe(error);
+                deferred.resolve();
+            };
+            Q.delay(10).then(deferred.reject);
+
+            return deferred.promise;
+        });
+    });
+
+    describe("gh-75", function () {
+        it("does not double-resolve misbehaved promises", function () {
+            var badPromise = Q.makePromise({
+                post: function () { return "hello"; }
+            });
+
+            var resolutions = 0;
+            function onResolution() {
+                ++resolutions;
+            }
+
+            return Q.when(badPromise, onResolution, onResolution).then(function () {
+                expect(resolutions).toBe(1);
+            });
+        });
+    });
+
+});
+
+describe("unhandled rejection reporting", function () {
+    beforeEach(function () {
+        Q.resetUnhandledRejections();
+    });
+
+    it("doesn't report a resolve, then reject (gh-252)", function () {
+        var deferred = Q.defer();
+        deferred.resolve();
+        deferred.reject();
+
+        expect(Q.getUnhandledReasons().length).toEqual(0);
+    });
+
+    it("doesn't report when you chain off a rejection", function () {
+        return Q.reject("this will be handled").get("property").fail(function () {
+            // now it should be handled.
+        }).fin(function() {
+            expect(Q.getUnhandledReasons().length).toEqual(0);
+        });
+    });
+
+    it("reports the most basic case", function () {
+        Q.reject("a reason");
+
+        expect(Q.getUnhandledReasons()).toEqual(["(no stack) a reason"]);
+    });
+
+    it("reports a stack trace", function () {
+        var error = new Error("a reason");
+        Q.reject(error);
+
+        expect(Q.getUnhandledReasons()).toEqual([error.stack]);
+    });
+
+    it("doesn't let you mutate the internal array", function () {
+        Q.reject("a reason");
+
+        Q.getUnhandledReasons().length = 0;
+        expect(Q.getUnhandledReasons()).toEqual(["(no stack) a reason"]);
+    });
+
+    it("resets after calling `Q.resetUnhandledRejections`", function () {
+        Q.reject("a reason");
+
+        Q.resetUnhandledRejections();
+        expect(Q.getUnhandledReasons()).toEqual([]);
+    });
+
+    it("stops tracking after calling `Q.stopUnhandledRejectionTracking`", function () {
+        Q.reject("a reason");
+
+        Q.stopUnhandledRejectionTracking();
+
+        Q.reject("another reason");
+
+        expect(Q.getUnhandledReasons()).toEqual([]);
+    });
+});
diff --git a/spec/queue-spec.js b/spec/queue-spec.js
new file mode 100644
index 0000000..4cec14e
--- /dev/null
+++ b/spec/queue-spec.js
@@ -0,0 +1,180 @@
+
+var Q = require("../q");
+var Queue = require("../queue");
+
+global.Q = Q;
+require("./lib/jasmine-promise");
+
+describe("queue", function () {
+
+    it("should enqueue then dequeue", function () {
+        var queue = Queue();
+        queue.put(1);
+        return queue.get().then(function (value) {
+            expect(value).toBe(1);
+        });
+    });
+
+    it("should dequeue then enqueue", function () {
+        var queue = Queue();
+        var promise = queue.get().then(function (value) {
+            expect(value).toBe(1);
+        });
+        queue.put(1);
+        return promise;
+    });
+
+    it("should stream", function () {
+        var queue = Queue();
+
+        Q.try(function () {
+            return Q.delay(20).then(function () {
+                queue.put(1);
+            })
+        })
+        .then(function () {
+            return Q.delay(20).then(function () {
+                queue.put(2);
+            })
+        })
+        .then(function () {
+            return Q.delay(20).then(function () {
+                queue.put(3);
+            })
+        })
+        .done();
+
+        return Q.try(function () {
+            return queue.get()
+            .then(function (value) {
+                expect(value).toBe(1);
+            });
+        })
+        .then(function () {
+            return queue.get()
+            .then(function (value) {
+                expect(value).toBe(2);
+            });
+        })
+        .then(function () {
+            return queue.get()
+            .then(function (value) {
+                expect(value).toBe(3);
+            });
+        })
+
+    });
+
+    it("should be order agnostic", function () {
+        var queue = Queue();
+
+        Q.try(function () {
+            return Q.delay(20).then(function () {
+                queue.put(1);
+            })
+        })
+        .then(function () {
+            return Q.delay(20).then(function () {
+                queue.put(2);
+            })
+        })
+        .then(function () {
+            return Q.delay(20).then(function () {
+                queue.put(3);
+            })
+        })
+        .done();
+
+        return Q.all([
+            queue.get()
+            .then(function (value) {
+                expect(value).toBe(1);
+            }),
+            queue.get()
+            .then(function (value) {
+                expect(value).toBe(2);
+            }),
+            queue.get()
+            .then(function (value) {
+                expect(value).toBe(3);
+            })
+        ]);
+    });
+
+    it("should close", function () {
+
+        var queue = Queue();
+
+        Q.try(function () {
+            return Q.delay(20).then(function () {
+                queue.put(1);
+            })
+        })
+        .then(function () {
+            return Q.delay(20).then(function () {
+                queue.put(2);
+            })
+        })
+        .then(function () {
+            queue.close();
+        })
+        .done();
+
+        return Q.try(function () {
+            return queue.get()
+            .then(function (value) {
+                expect(value).toBe(1);
+            });
+        })
+        .then(function () {
+            return queue.get()
+            .then(function (value) {
+                expect(value).toBe(2);
+            });
+        })
+        .then(function () {
+            return queue.get()
+            .then(function (value) {
+                expect(false).toBe(true); // should not get here
+            });
+        })
+        .catch(function (error) {
+            expect(error.message).toBe("Can't get value from closed queue");
+            return queue.get();
+        })
+        .catch(function (error) {
+            expect(error.message).toBe("Can't get value from closed queue");
+        })
+        .then(function () {
+            return queue.closed;
+        })
+        .then(function (error) {
+            expect(error.message).toBe("Can't get value from closed queue");
+        })
+    });
+
+    it("should close with alternate error", function () {
+
+        var queue = Queue();
+        queue.close(new Error("Alternate reason"));
+
+        return Q.try(function () {
+            return queue.get();
+        })
+        .catch(function (error) {
+            expect(error.message).toBe("Alternate reason");
+            return queue.get();
+        })
+        .catch(function (error) {
+            expect(error.message).toBe("Alternate reason");
+        })
+        .then(function () {
+            return queue.closed;
+        })
+        .then(function (error) {
+            expect(error.message).toBe("Alternate reason");
+        })
+    });
+
+});
+

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-q.git



More information about the Pkg-javascript-commits mailing list