diff --git a/tests/lib/q-1.4.1/.coverignore b/tests/lib/q-1.4.1/.coverignore new file mode 100644 index 0000000000000000000000000000000000000000..1564ca04270703128dff497735b39e6e138e0186 --- /dev/null +++ b/tests/lib/q-1.4.1/.coverignore @@ -0,0 +1 @@ +spec/ diff --git a/tests/lib/q-1.4.1/.gitignore b/tests/lib/q-1.4.1/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..4aca249106a0c9a6586ba8290288134362f3a16d --- /dev/null +++ b/tests/lib/q-1.4.1/.gitignore @@ -0,0 +1,12 @@ +node_modules +npm-debug.log +CHANGES.html +README.html +.tmp +q.min.js + +coverage/ + +# IntelliJ IDEA project files +.idea +*.iml diff --git a/tests/lib/q-1.4.1/.jshintrc b/tests/lib/q-1.4.1/.jshintrc new file mode 100644 index 0000000000000000000000000000000000000000..387bb8d21d0f1f7c656be083e8dbcf0d9929836a --- /dev/null +++ b/tests/lib/q-1.4.1/.jshintrc @@ -0,0 +1,27 @@ +{ + "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": { + "self": false, + "bootstrap": false, + "cajaVM": false, + "define": false, + "ReturnValue": false, + "ses": false, + "setImmediate": false, + "Q": true + } +} diff --git a/tests/lib/q-1.4.1/.mailmap b/tests/lib/q-1.4.1/.mailmap new file mode 100644 index 0000000000000000000000000000000000000000..281850a3462e607c980c1c6ac9af6e86e9708630 --- /dev/null +++ b/tests/lib/q-1.4.1/.mailmap @@ -0,0 +1,2 @@ +Domenic Denicola <domenic@domenicdenicola.com> +Kris Kowal <kris.kowal@cixar.com> diff --git a/tests/lib/q-1.4.1/.travis.yml b/tests/lib/q-1.4.1/.travis.yml new file mode 100644 index 0000000000000000000000000000000000000000..d68110e6676aa39f322e55bd7384a8123894aa88 --- /dev/null +++ b/tests/lib/q-1.4.1/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "0.10" +script: + npm run lint && npm test diff --git a/tests/lib/q-1.4.1/CHANGES.md b/tests/lib/q-1.4.1/CHANGES.md new file mode 100644 index 0000000000000000000000000000000000000000..cd351fdb85c9d6a9711b47e2d94fceaaeafa4a3a --- /dev/null +++ b/tests/lib/q-1.4.1/CHANGES.md @@ -0,0 +1,786 @@ + +## 1.4.1 + + - Address an issue that prevented Q from being used as a `<script>` for + Firefox add-ons. Q can now be used in any environment that provides `window` + or `self` globals, favoring `window` since add-ons have an an immutable + `self` that is distinct from `window`. + +## 1.4.0 + + - Add `noConflict` support for use in `<script>` (@jahnjw). + +## 1.3.0 + + - Add tracking for unhandled and handled rejections in Node.js (@benjamingr). + +## 1.2.1 + + - Fix Node.js environment detection for modern Browserify (@kahnjw). + +## 1.2.0 + + - Added Q.any(promisesArray) method (@vergara). + Returns a promise fulfilled with the value of the first resolved promise in + promisesArray. If all promises in promisesArray are rejected, it returns + a rejected promise. + +## 1.1.2 + + - Removed extraneous files from the npm package by using the "files" + whitelist in package.json instead of the .npmignore blacklist. + (@anton-rudeshko) + +## 1.1.1 + + - Fix a pair of regressions in bootstrapping, one which precluded + WebWorker support, and another that precluded support in + ``<script>`` usage outright. #607 + +## 1.1.0 + + - Adds support for enabling long stack traces in node.js by setting + environment variable `Q_DEBUG=1`. + - Introduces the `tap` method to promises, which will see a value + pass through without alteration. + - Use instanceof to recognize own promise instances as opposed to + thenables. + - Construct timeout errors with `code === ETIMEDOUT` (Kornel LesiÅ„ski) + - More descriminant CommonJS module environment detection. + - Dropped continuous integration for Node.js 0.6 and 0.8 because of + changes to npm that preclude the use of new `^` version predicate + operator in any transitive dependency. + - Users can now override `Q.nextTick`. + +## 1.0.1 + + - Adds support for `Q.Promise`, which implements common usage of the + ES6 `Promise` constructor and its methods. `Promise` does not have + a valid promise constructor and a proper implementation awaits + version 2 of Q. + - Removes the console stopgap for a promise inspector. This no longer + works with any degree of reliability. + - Fixes support for content security policies that forbid eval. Now + using the `StopIteration` global to distinguish SpiderMonkey + generators from ES6 generators, assuming that they will never + coexist. + +## 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/tests/lib/q-1.4.1/CONTRIBUTING.md b/tests/lib/q-1.4.1/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..500ab17b1f51311407b7840b2b7680d0f5ffd2f3 --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/Gruntfile.js b/tests/lib/q-1.4.1/Gruntfile.js new file mode 100644 index 0000000000000000000000000000000000000000..d764dd30ffd9445f3df6a98af7699657faf6f678 --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/LICENSE b/tests/lib/q-1.4.1/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..8a706b59ca80035287689f53d0a2b198c4ac20ad --- /dev/null +++ b/tests/lib/q-1.4.1/LICENSE @@ -0,0 +1,18 @@ +Copyright 2009–2014 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/tests/lib/q-1.4.1/README.md b/tests/lib/q-1.4.1/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9065bfa7ffb2f9949fa5875f0a9f346b55b90423 --- /dev/null +++ b/tests/lib/q-1.4.1/README.md @@ -0,0 +1,881 @@ +[](http://travis-ci.org/kriskowal/q) + +<a href="http://promises-aplus.github.com/promises-spec"> + <img src="http://kriskowal.github.io/q/q.png" + align="right" alt="Q logo" /> +</a> + +*This is Q version 1, from the `v1` branch in Git. This documentation applies to +the latest of both the version 1 and version 0.9 release trains. These releases +are stable. There will be no further releases of 0.9 after 0.9.7 which is nearly +equivalent to version 1.0.0. All further releases of `q@~1.0` will be backward +compatible. The version 2 release train introduces significant and +backward-incompatible changes and is experimental at this time.* + +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#1.0.1` +- 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 received 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; + } + }); +}); +``` + +The ``any`` function accepts an array of promises and returns a promise that is +fulfilled by the first given promise to be fulfilled, or rejected if all of the +given promises are rejected. + +```javascript +Q.any(promises) +.then(function (first) { + // Any of the promises was fulfilled. +}, function (error) { + // All of the promises were rejected. +}); +``` + +### 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 the ultra-compact version: + +```javascript +return funcs.reduce(Q.when, Q(initialVal)); +``` + +### Handling Errors + +One sometimes-unintuive aspect of promises is that if you throw an +exception in the fulfillment handler, it will not 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) + "%"); +}); +``` + +#### Using `Q.Promise` + +This is an alternative promise-creation API that has the same power as +the deferred concept, but without introducing another conceptual entity. + +Rewriting the `requestOkText` example above using `Q.Promise`: + +```javascript +function requestOkText(url) { + return Q.Promise(function(resolve, reject, notify) { + var request = new XMLHttpRequest(); + + request.open("GET", url, true); + request.onload = onload; + request.onerror = onerror; + request.onprogress = onprogress; + request.send(); + + function onload() { + if (request.status === 200) { + resolve(request.responseText); + } else { + reject(new Error("Status code was " + request.status)); + } + } + + function onerror() { + reject(new Error("Can't XHR " + JSON.stringify(url))); + } + + function onprogress(event) { + notify(event.loaded / event.total); + } + }); +} +``` + +If `requestOkText` were to throw an exception, the returned promise would be +rejected with that thrown exception as the rejection reason. + +### 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 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. + +In node.js, this feature can also be enabled through the Q_DEBUG environment +variable: + +``` +Q_DEBUG=1 node server.js +``` + +This will enable long stack support in every instance of Q. + +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/v1/spec/q-spec.html + +## License + +Copyright 2009–2015 Kristopher Michael Kowal and contributors +MIT License (enclosed) + diff --git a/tests/lib/q-1.4.1/VERSIONS.md b/tests/lib/q-1.4.1/VERSIONS.md new file mode 100644 index 0000000000000000000000000000000000000000..8f0e11df95862ca904111c88db1b149216a15946 --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/benchmark/compare-with-callbacks.js b/tests/lib/q-1.4.1/benchmark/compare-with-callbacks.js new file mode 100644 index 0000000000000000000000000000000000000000..97f12989211d0c1485dacee76ec78f8ca74d04f4 --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/benchmark/scenarios.js b/tests/lib/q-1.4.1/benchmark/scenarios.js new file mode 100644 index 0000000000000000000000000000000000000000..7c18564c57719fc3d3458f04318d165fb0715781 --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/design/README.js b/tests/lib/q-1.4.1/design/README.js new file mode 100644 index 0000000000000000000000000000000000000000..db4aa3312c924d2550cd36c8af2d62502555a55d --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/design/q0.js b/tests/lib/q-1.4.1/design/q0.js new file mode 100644 index 0000000000000000000000000000000000000000..c8a0596ecaf6b69f05ebac18161ef542502ecc93 --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/design/q1.js b/tests/lib/q-1.4.1/design/q1.js new file mode 100644 index 0000000000000000000000000000000000000000..653920463c8f1a77eac390d9b04c3a7603ac014d --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/design/q2.js b/tests/lib/q-1.4.1/design/q2.js new file mode 100644 index 0000000000000000000000000000000000000000..0690a4708f26d4412bfab332e22875e6e92d17b5 --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/design/q3.js b/tests/lib/q-1.4.1/design/q3.js new file mode 100644 index 0000000000000000000000000000000000000000..90a127039abba390a05dadcc0a37656a2773817d --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/design/q4.js b/tests/lib/q-1.4.1/design/q4.js new file mode 100644 index 0000000000000000000000000000000000000000..5ef6049619f7e443d5dc89f4eecc9ac445722a76 --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/design/q5.js b/tests/lib/q-1.4.1/design/q5.js new file mode 100644 index 0000000000000000000000000000000000000000..0e3bbde7884aa664d11e70f22ff3d903d8a2ec70 --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/design/q6.js b/tests/lib/q-1.4.1/design/q6.js new file mode 100644 index 0000000000000000000000000000000000000000..be61aeb94dc12721426ae8af060fa89cbc3d5d79 --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/design/q7.js b/tests/lib/q-1.4.1/design/q7.js new file mode 100644 index 0000000000000000000000000000000000000000..b0ffe4a8583fccef1e273f3a5206a1d71e32d6ba --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/examples/all.js b/tests/lib/q-1.4.1/examples/all.js new file mode 100644 index 0000000000000000000000000000000000000000..8e01c67aef3b3ae0121054ec41cd20bffefac22d --- /dev/null +++ b/tests/lib/q-1.4.1/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(result); +}); + +Q.all([ + eventually(10), + eventually(20) +]) +.spread(function (x, y) { + console.log(x, y); +}) +.done(); diff --git a/tests/lib/q-1.4.1/examples/async-generators/0.html b/tests/lib/q-1.4.1/examples/async-generators/0.html new file mode 100644 index 0000000000000000000000000000000000000000..a4c6552b2a73b3b619797e78cd86e8a8a2659dbf --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/examples/async-generators/1-return.js b/tests/lib/q-1.4.1/examples/async-generators/1-return.js new file mode 100644 index 0000000000000000000000000000000000000000..7aab6db4489262474149d73290dd973bfb4527af --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/examples/async-generators/2-error-propagation.js b/tests/lib/q-1.4.1/examples/async-generators/2-error-propagation.js new file mode 100644 index 0000000000000000000000000000000000000000..ccd788bd59b26df2fb67f8da9711329455cd2abb --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/examples/async-generators/3-spawn.js b/tests/lib/q-1.4.1/examples/async-generators/3-spawn.js new file mode 100644 index 0000000000000000000000000000000000000000..85fe0672c83c42ae53210ff66aadca41193e7db9 --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/examples/async-generators/4-flow-control.js b/tests/lib/q-1.4.1/examples/async-generators/4-flow-control.js new file mode 100644 index 0000000000000000000000000000000000000000..017bf8a0d9f96d8aff2bbcf9f578ec560e960b42 --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/examples/async-generators/README.md b/tests/lib/q-1.4.1/examples/async-generators/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c409f0b0ee6610e176947b6f45acdc96e72046ba --- /dev/null +++ b/tests/lib/q-1.4.1/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(); +counter.next().value === 0; +counter.next().value === 1; +counter.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/tests/lib/q-1.4.1/package.json b/tests/lib/q-1.4.1/package.json new file mode 100644 index 0000000000000000000000000000000000000000..acfd18271b6636292fa20e1c5507f99bbde964cb --- /dev/null +++ b/tests/lib/q-1.4.1/package.json @@ -0,0 +1,79 @@ +{ + "name": "q", + "version": "1.4.1", + "description": "A library for promises (CommonJS/Promises/A,B,D)", + "homepage": "https://github.com/kriskowal/q", + "author": "Kris Kowal <kris@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@cixar.com> (https://github.com/kriskowal)", + "Irakli Gozalishvili <rfobic@gmail.com> (http://jeditoolkit.com)", + "Domenic Denicola <domenic@domenicdenicola.com> (http://domenicdenicola.com)" + ], + "bugs": { + "mail": "kris@cixar.com", + "url": "http://github.com/kriskowal/q/issues" + }, + "license": { + "type": "MIT", + "url": "http://github.com/kriskowal/q/raw/master/LICENSE" + }, + "main": "q.js", + "files": [ + "LICENSE", + "q.js", + "queue.js" + ], + "repository": { + "type": "git", + "url": "git://github.com/kriskowal/q.git" + }, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + }, + "dependencies": {}, + "devDependencies": { + "cover": "*", + "grunt": "~0.4.1", + "grunt-cli": "~0.1.9", + "grunt-contrib-uglify": "~0.9.1", + "jasmine-node": "1.11.0", + "jshint": "~2.1.9", + "matcha": "~0.2.0", + "opener": "*", + "promises-aplus-tests": "1.x" + }, + "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 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/tests/lib/q-1.4.1/q.js b/tests/lib/q-1.4.1/q.js new file mode 100644 index 0000000000000000000000000000000000000000..cf5339e33227aaf7a22c6f030f1675f1372376e9 --- /dev/null +++ b/tests/lib/q-1.4.1/q.js @@ -0,0 +1,2048 @@ +// 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) { + "use strict"; + + // 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" && typeof module === "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 if (typeof window !== "undefined" || typeof self !== "undefined") { + // Prefer window over self for add-on scripts. Use self for + // non-windowed contexts. + var global = typeof window !== "undefined" ? window : self; + + // Get the `window` object, save the previous Q global + // and initialize Q as a global. + var previousQ = global.Q; + global.Q = definition(); + + // Add a noConflict function so Q can be removed from the + // global namespace. + global.Q.noConflict = function () { + global.Q = previousQ; + return this; + }; + + } else { + throw new Error("This environment was not anticipated by Q. Please file a bug."); + } + +})(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; + // queue for late tasks, used by unhandled rejection tracking + var laterQueue = []; + + function flush() { + /* jshint loopfunc: true */ + var task, domain; + + while (head.next) { + head = head.next; + task = head.task; + head.task = void 0; + domain = head.domain; + + if (domain) { + head.domain = void 0; + domain.enter(); + } + runSingle(task, domain); + + } + while (laterQueue.length) { + task = laterQueue.pop(); + runSingle(task); + } + flushing = false; + } + // runs a single function in the async queue + function runSingle(task, domain) { + 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(); + } + } + + nextTick = function (task) { + tail = tail.next = { + task: task, + domain: isNodeJS && process.domain, + next: null + }; + + if (!flushing) { + flushing = true; + requestTick(); + } + }; + + if (typeof process === "object" && + process.toString() === "[object process]" && process.nextTick) { + // Ensure Q is in a real Node environment, with a `process.nextTick`. + // To see through fake Node environments: + // * Mocha test runner - exposes a `process` global without a `nextTick` + // * Browserify - exposes a `process.nexTick` function that uses + // `setTimeout`. In this case `setImmediate` is preferred because + // it is faster. Browserify's `process.toString()` yields + // "[object Object]", while in a real Node environment + // `process.nextTick()` yields "[object process]". + 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); + }; + } + // runs a task after all other tasks have been run + // this is useful for unhandled rejection tracking that needs to happen + // after all `then`d tasks have been run. + nextTick.runAfter = function (task) { + laterQueue.push(task); + if (!flushing) { + flushing = true; + requestTick(); + } + }; + 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; + }; +} + +// 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@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 (value instanceof Promise) { + 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; + +// enable long stacks if Q_DEBUG is set +if (typeof process === "object" && process && process.env && process.env.Q_DEBUG) { + Q.longStackSupport = true; +} + +/** + * 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 { + Q.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) { + Q.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) { + Q.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; // ES6 +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; +} + +promise.race = race; // ES6 +promise.all = all; // ES6 +promise.reject = reject; // ES6 +promise.resolve = Q; // ES6 + +// 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 settled. + * @param answers {Array[Any*]} promises to race + * @returns {Any*} the first promise to be settled + */ +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; + } + + Q.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; +}; + +Q.tap = function (promise, callback) { + return Q(promise).tap(callback); +}; + +/** + * Works almost like "finally", but not called for rejections. + * Original resolution value is passed through callback unaffected. + * Callback may return a promise that will be awaited for. + * @param {Function} callback + * @returns {Q.Promise} + * @example + * doSomething() + * .then(...) + * .tap(console.log) + * .then(...); + */ +Promise.prototype.tap = function (callback) { + callback = Q(callback); + + return this.then(function (value) { + return callback.fcall(value).thenResolve(value); + }); +}; + +/** + * 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 object instanceof Promise; +} + +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 reportedUnhandledRejections = []; +var trackUnhandledRejections = true; + +function resetUnhandledRejections() { + unhandledReasons.length = 0; + unhandledRejections.length = 0; + + if (!trackUnhandledRejections) { + trackUnhandledRejections = true; + } +} + +function trackRejection(promise, reason) { + if (!trackUnhandledRejections) { + return; + } + if (typeof process === "object" && typeof process.emit === "function") { + Q.nextTick.runAfter(function () { + if (array_indexOf(unhandledRejections, promise) !== -1) { + process.emit("unhandledRejection", reason, promise); + reportedUnhandledRejections.push(promise); + } + }); + } + + unhandledRejections.push(promise); + if (reason && typeof reason.stack !== "undefined") { + unhandledReasons.push(reason.stack); + } else { + unhandledReasons.push("(no stack) " + reason); + } +} + +function untrackRejection(promise) { + if (!trackUnhandledRejections) { + return; + } + + var at = array_indexOf(unhandledRejections, promise); + if (at !== -1) { + if (typeof process === "object" && typeof process.emit === "function") { + Q.nextTick.runAfter(function () { + var atReport = array_indexOf(reportedUnhandledRejections, promise); + if (atReport !== -1) { + process.emit("rejectionHandled", unhandledReasons[at], promise); + reportedUnhandledRejections.splice(atReport, 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(); + 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(); + Q.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; + + // 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. + + if (typeof StopIteration === "undefined") { + // ES6 Generators + try { + result = generator[verb](arg); + } catch (exception) { + return reject(exception); + } + if (result.done) { + return Q(result.value); + } else { + return when(result.value, callback, errback); + } + } else { + // SpiderMonkey Generators + // FIXME: Remove this case when SM does ES6 generators. + try { + result = generator[verb](arg); + } catch (exception) { + if (isStopIteration(exception)) { + return Q(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(); + Q.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 pendingCount = 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 { + ++pendingCount; + when( + promise, + function (value) { + promises[index] = value; + if (--pendingCount === 0) { + deferred.resolve(promises); + } + }, + deferred.reject, + function (progress) { + deferred.notify({ index: index, value: progress }); + } + ); + } + }, void 0); + if (pendingCount === 0) { + deferred.resolve(promises); + } + return deferred.promise; + }); +} + +Promise.prototype.all = function () { + return all(this); +}; + +/** + * Returns the first resolved promise of an array. Prior rejected promises are + * ignored. Rejects only if all promises are rejected. + * @param {Array*} an array containing values or promises for values + * @returns a promise fulfilled with the value of the first resolved promise, + * or a rejected promise if all promises are rejected. + */ +Q.any = any; + +function any(promises) { + if (promises.length === 0) { + return Q.resolve(); + } + + var deferred = Q.defer(); + var pendingCount = 0; + array_reduce(promises, function (prev, current, index) { + var promise = promises[index]; + + pendingCount++; + + when(promise, onFulfilled, onRejected, onProgress); + function onFulfilled(result) { + deferred.resolve(result); + } + function onRejected() { + pendingCount--; + if (pendingCount === 0) { + deferred.reject(new Error( + "Can't get fulfillment value from any promise, all " + + "promises were rejected." + )); + } + } + function onProgress(progress) { + deferred.notify({ + index: index, + value: progress + }); + } + }, undefined); + + return deferred.promise; +} + +Promise.prototype.any = function () { + return any(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. + Q.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 {Any*} custom error message or Error object (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, error) { + return Q(object).timeout(ms, error); +}; + +Promise.prototype.timeout = function (ms, error) { + var deferred = defer(); + var timeoutId = setTimeout(function () { + if (!error || "string" === typeof error) { + error = new Error(error || "Timed out after " + ms + " ms"); + error.code = "ETIMEDOUT"; + } + deferred.reject(error); + }, 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) { + Q.nextTick(function () { + nodeback(null, value); + }); + }, function (error) { + Q.nextTick(function () { + nodeback(error); + }); + }); + } else { + return this; + } +}; + +Q.noConflict = function() { + throw new Error("Q.noConflict only works when Q is used as a global"); +}; + +// All code before this point will be filtered from stack traces. +var qEndingLine = captureLine(); + +return Q; + +}); diff --git a/tests/lib/q-1.4.1/q.png b/tests/lib/q-1.4.1/q.png new file mode 100644 index 0000000000000000000000000000000000000000..fb1b33639a483676df0792a57f04320826443698 Binary files /dev/null and b/tests/lib/q-1.4.1/q.png differ diff --git a/tests/lib/q-1.4.1/q.svg b/tests/lib/q-1.4.1/q.svg new file mode 100644 index 0000000000000000000000000000000000000000..e8cab2e61c10c88c7f9fea48d166a245e535986b --- /dev/null +++ b/tests/lib/q-1.4.1/q.svg @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="157.5" + height="180" + id="svg2" + version="1.1" + inkscape:version="0.48.1 r9760" + sodipodi:docname="q.svg" + inkscape:export-filename="/home/kris/art/q.png" + inkscape:export-xdpi="51.200001" + inkscape:export-ydpi="51.200001"> + <defs + id="defs4" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="2" + inkscape:cx="62.864072" + inkscape:cy="167.31214" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false" + units="in" + inkscape:window-width="1680" + inkscape:window-height="973" + inkscape:window-x="0" + inkscape:window-y="24" + inkscape:window-maximized="1" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(0,-872.3622)"> + <path + sodipodi:type="star" + style="fill:#f9df34;fill-opacity:1;stroke:none;opacity:1" + id="path3833" + sodipodi:sides="6" + sodipodi:cx="1118" + sodipodi:cy="456.36218" + sodipodi:r1="277.59683" + sodipodi:r2="240.40591" + sodipodi:arg1="0.52359878" + sodipodi:arg2="1.0471976" + inkscape:flatsided="true" + inkscape:rounded="0" + inkscape:randomized="0" + d="M 1358.4059,595.1606 1118,733.95901 877.59409,595.1606 l 0,-277.59683 L 1118,178.76535 1358.4059,317.56377 z" + transform="matrix(0.28609737,0,0,0.28609737,-241.10686,831.79819)" /> + <path + inkscape:connector-curvature="0" + id="path3840" + d="m 23.150536,930.17141 c 0.02584,21.44054 0.05616,42.88112 0.0266,64.32167 18.544384,10.71192 37.074475,21.44862 55.599461,32.19412 18.524303,-10.7114 37.048573,-21.4227 55.572863,-32.13419 -0.0259,-21.44055 -0.0561,-42.88112 -0.0266,-64.32168 C 115.77802,919.52027 97.24878,908.78212 78.723431,898.0372 60.199152,908.74861 41.674869,919.46002 23.150565,930.17141 z m 55.792247,-8.92728 c 11.80936,6.85273 23.635797,13.68573 35.470907,20.49746 -0.004,13.75374 -0.009,27.50747 -0.0132,41.2612 -3.04307,1.75007 -6.08198,3.50751 -9.12033,5.26584 -6.092341,-10.55513 -12.176786,-21.11483 -18.260622,-31.67487 -5.796593,3.34635 -11.593186,6.69268 -17.389782,10.03903 6.083829,10.56004 12.168276,21.11974 18.260615,31.67487 -3.035798,1.77164 -6.093381,3.50564 -9.120329,5.29244 -11.883066,-6.88841 -23.778192,-13.75616 -35.676998,-20.61727 0.0022,-13.75374 0.0045,-27.50747 0.0066,-41.2612 11.884425,-6.85251 23.763957,-13.71376 35.630451,-20.59733 l 0.185407,0.10445 0.02732,0.0154 z" + style="fill:#b7a634;fill-opacity:1;stroke:#524839;stroke-width:5.37970828999999995;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;opacity:0.96" /> + <path + style="fill:#f9df34;fill-opacity:1;stroke:#524839;stroke-width:5.54999999999999982;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" + d="m 3.9712996,919.13021 c 0.034761,28.79449 0.07553,57.58902 0.035762,86.38349 24.9413394,14.3861 49.8634534,28.8054 74.7787004,43.2365 24.914328,-14.3853 49.828638,-28.7707 74.742938,-43.156 -0.0347,-28.79449 -0.0755,-57.58901 -0.0358,-86.38352 -24.942,-14.38487 -49.86297,-28.80613 -74.778703,-43.23647 -24.914303,14.38534 -49.828606,28.77067 -74.7429375,43.156 z M 79.00929,907.14095 c 15.883039,9.20316 31.78905,18.37981 47.70674,27.52793 -0.006,18.47116 -0.0123,36.94232 -0.0177,55.41348 -4.0928,2.35032 -8.17998,4.71056 -12.26643,7.07197 -8.19391,-14.17545 -16.377211,-28.35704 -24.559682,-42.5391 -7.796153,4.49411 -15.592307,8.98822 -23.38846,13.48234 8.182471,14.18206 16.365771,28.36365 24.559685,42.53913 -4.08301,2.3792 -8.195316,4.7081 -12.266424,7.1077 -15.982172,-9.2511 -31.980565,-18.47444 -47.983908,-27.68886 0.0029,-18.47116 0.006,-36.94232 0.0089,-55.41348 15.984003,-9.20286 31.961425,-18.41747 47.92131,-27.66204 l 0.249362,0.14026 0.03674,0.0207 z" + id="path2993" + inkscape:connector-curvature="0" /> + <g + id="g4862" + transform="matrix(0.11775097,-0.06798356,0.06798356,0.11775097,5.1642661,919.0661)"> + <path + style="fill:#524739" + inkscape:connector-curvature="0" + d="m 217.503,388.691 v 14.757 h -16.155 v 39.766 c 0,3.729 0.621,6.214 1.864,7.457 1.243,1.242 3.728,1.863 7.456,1.863 1.243,0 2.432,-0.051 3.573,-0.155 1.138,-0.102 2.226,-0.257 3.262,-0.466 V 469 c -1.864,0.311 -3.937,0.517 -6.213,0.621 -2.279,0.103 -4.505,0.155 -6.68,0.155 -3.417,0 -6.655,-0.232 -9.708,-0.698 -3.056,-0.467 -5.747,-1.372 -8.077,-2.719 -2.33,-1.345 -4.17,-3.262 -5.515,-5.747 -1.347,-2.485 -2.019,-5.748 -2.019,-9.786 v -47.378 h -13.359 v -14.757 h 13.359 v -24.077 h 22.058 v 24.077 h 16.154 z" + id="path4864" /> + + <path + style="fill:#524739" + inkscape:connector-curvature="0" + d="m 248.569,358.091 v 41.785 h 0.466 c 2.796,-4.66 6.369,-8.051 10.718,-10.175 4.349,-2.121 8.594,-3.185 12.737,-3.185 5.903,0 10.742,0.804 14.524,2.408 3.778,1.606 6.757,3.832 8.932,6.68 2.175,2.849 3.701,6.317 4.582,10.407 0.879,4.092 1.32,8.621 1.32,13.592 V 469 H 279.79 v -45.357 c 0,-6.627 -1.036,-11.573 -3.106,-14.835 -2.073,-3.262 -5.747,-4.894 -11.029,-4.894 -6.007,0 -10.356,1.787 -13.048,5.359 -2.694,3.573 -4.039,9.451 -4.039,17.631 V 469 H 226.51 V 358.091 h 22.059 z" + id="path4866" /> + + <path + style="fill:#524739" + inkscape:connector-curvature="0" + d="m 334.467,449.738 c 3.313,3.211 8.077,4.815 14.291,4.815 4.451,0 8.283,-1.111 11.495,-3.34 3.208,-2.226 5.177,-4.582 5.902,-7.067 h 19.417 c -3.106,9.631 -7.871,16.519 -14.291,20.659 -6.422,4.144 -14.188,6.214 -23.3,6.214 -6.318,0 -12.015,-1.01 -17.087,-3.029 -5.075,-2.02 -9.374,-4.893 -12.894,-8.621 -3.521,-3.728 -6.24,-8.18 -8.154,-13.358 -1.918,-5.178 -2.874,-10.874 -2.874,-17.087 0,-6.005 0.982,-11.597 2.951,-16.776 1.966,-5.177 4.762,-9.655 8.388,-13.437 3.624,-3.779 7.947,-6.757 12.971,-8.932 5.021,-2.175 10.587,-3.263 16.699,-3.263 6.834,0 12.788,1.32 17.863,3.962 5.072,2.641 9.242,6.188 12.504,10.64 3.263,4.454 5.617,9.529 7.068,15.224 1.449,5.696 1.966,11.649 1.553,17.863 H 329.03 c 0.311,7.146 2.121,12.325 5.437,15.533 z m 24.931,-42.251 c -2.641,-2.898 -6.655,-4.35 -12.039,-4.35 -3.521,0 -6.446,0.598 -8.776,1.786 -2.33,1.192 -4.193,2.668 -5.592,4.428 -1.398,1.762 -2.384,3.626 -2.951,5.592 -0.57,1.969 -0.908,3.728 -1.01,5.281 h 35.882 c -1.036,-5.591 -2.873,-9.836 -5.514,-12.737 z" + id="path4868" /> + + <path + style="fill:#524739" + inkscape:connector-curvature="0" + d="m 413.221,388.691 v 11.185 h 0.466 c 2.796,-4.66 6.42,-8.051 10.874,-10.175 4.451,-2.121 9.009,-3.185 13.669,-3.185 5.903,0 10.742,0.804 14.524,2.408 3.778,1.606 6.757,3.832 8.932,6.68 2.175,2.849 3.701,6.317 4.582,10.407 0.879,4.092 1.32,8.621 1.32,13.592 V 469 H 445.53 v -45.357 c 0,-6.627 -1.036,-11.573 -3.106,-14.835 -2.073,-3.262 -5.747,-4.894 -11.029,-4.894 -6.007,0 -10.356,1.787 -13.048,5.359 -2.694,3.573 -4.039,9.451 -4.039,17.631 V 469 H 392.25 v -80.309 h 20.971 z" + id="path4870" /> + +</g> + </g> +</svg> diff --git a/tests/lib/q-1.4.1/queue.js b/tests/lib/q-1.4.1/queue.js new file mode 100644 index 0000000000000000000000000000000000000000..1505fd0b369a44ee62081501b5a0454f11b9d918 --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/ref_send.md b/tests/lib/q-1.4.1/ref_send.md new file mode 100644 index 0000000000000000000000000000000000000000..c096bb27a662732f75a304627ca84e94cd2ad447 --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/spec/aplus-adapter.js b/tests/lib/q-1.4.1/spec/aplus-adapter.js new file mode 100644 index 0000000000000000000000000000000000000000..f2c659dc5f441672c39a3af75ca9312de433fa3c --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/spec/lib/jasmine-1.2.0/MIT.LICENSE b/tests/lib/q-1.4.1/spec/lib/jasmine-1.2.0/MIT.LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..7c435baaec86c0ebe2eb56b0550c11820c181b05 --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/spec/lib/jasmine-1.2.0/jasmine-html.js b/tests/lib/q-1.4.1/spec/lib/jasmine-1.2.0/jasmine-html.js new file mode 100644 index 0000000000000000000000000000000000000000..a0b06394ec4337b38d8e61d5dfda5819eb126400 --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/spec/lib/jasmine-1.2.0/jasmine.css b/tests/lib/q-1.4.1/spec/lib/jasmine-1.2.0/jasmine.css new file mode 100644 index 0000000000000000000000000000000000000000..826e575310cd8e5ebc747192f47ff2e88e14fb4d --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/spec/lib/jasmine-1.2.0/jasmine.js b/tests/lib/q-1.4.1/spec/lib/jasmine-1.2.0/jasmine.js new file mode 100644 index 0000000000000000000000000000000000000000..03bf89a0c4107414605c0aa24929ced18854bcf7 --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/spec/lib/jasmine-promise.js b/tests/lib/q-1.4.1/spec/lib/jasmine-promise.js new file mode 100644 index 0000000000000000000000000000000000000000..fbdfbd0a73633201885d6897c16f390afeb87ec6 --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/spec/q-spec.html b/tests/lib/q-1.4.1/spec/q-spec.html new file mode 100644 index 0000000000000000000000000000000000000000..74ce4718031c342d14bd26f1e679071563a51aed --- /dev/null +++ b/tests/lib/q-1.4.1/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/tests/lib/q-1.4.1/spec/q-spec.js b/tests/lib/q-1.4.1/spec/q-spec.js new file mode 100644 index 0000000000000000000000000000000000000000..ce47ef183454a65410fc4c908ee93a52806b2414 --- /dev/null +++ b/tests/lib/q-1.4.1/spec/q-spec.js @@ -0,0 +1,2858 @@ +"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; + }); + + it("allows overriding global nextTick", function () { + var spy = jasmine.createSpy(); + spyOn(Q, 'nextTick').andCallFake(function immediateTick(task){ + task(); + }); + + Q.when(Q(), spy); + + expect(spy).toHaveBeenCalled(); + expect(Q.nextTick).toHaveBeenCalled(); + }); +}); + +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("any", function() { + it("fulfills when passed an empty array", function() { + return Q.any([]); + }); + + it("rejects after all promises are rejected", function() { + var deferreds = [Q.defer(), Q.defer()]; + var promises = [deferreds[0].promise, deferreds[1].promise]; + + return testReject(promises, deferreds); + }); + + it("rejects after all promises in a sparse array are rejected", function() { + var deferreds = [Q.defer(), Q.defer()]; + var promises = []; + promises[0] = deferreds[0].promise; + promises[3] = deferreds[1].promise; + + return testReject(promises, deferreds); + }); + + function testReject(promises, deferreds) { + var promise = Q.any(promises); + + for (var index = 0; index < deferreds.length; index++) { + var deferred = deferreds[index]; + (function() { + deferred.reject(new Error('Rejected')); + })(); + } + + return Q.delay(250) + .then(function() { + expect(promise.isRejected()).toBe(true); + expect(promise.inspect().reason.message) + .toBe("Can't get fulfillment value from any promise, all promises were rejected."); + }) + .timeout(1000); + } + + it("fulfills with the first resolved promise", function() { + var deferreds = [Q.defer(), Q.defer()]; + var promises = [deferreds[0].promise, deferreds[1].promise]; + + testFulfill(promises, deferreds); + }); + + it("fulfills when passed a sparse array", function() { + var deferreds = [Q.defer(), Q.defer()]; + var promises = []; + promises[0] = deferreds[0].promise; + promises[2] = deferreds[1].promise; + + testFulfill(promises, deferreds); + }); + + function testFulfill(promises, deferreds) { + var promise = Q.any(promises); + + var j = 1; + for (var index = 0; index < deferreds.length; index++) { + var toResolve = deferreds[index]; + if (!toResolve || !Q.isPromiseAlike(toResolve.promise)) { + continue; + } + + (function(index, toResolve) { + var time = index * 50; + Q.delay(time).then(function() { + toResolve.resolve('Fulfilled' + index); + }); + })(j, toResolve); + + j++; + } + + return Q.delay(400) + .then(function() { + expect(promise.isFulfilled()).toBe(true); + expect(promise.inspect().value).toBe('Fulfilled1'); + }) + .timeout(1000); + } + + it("fulfills with the first value", function() { + var toResolve1 = Q.defer(); + var toResolve2 = Q.defer(); + var toResolve3 = Q.defer(); + var promises = [toResolve1.promise, toResolve2.promise, 4, 5, + toResolve3.promise + ]; + + var promise = Q.any(promises); + + Q.delay(150).then(function() { + toResolve1.resolve(1); + }); + Q.delay(50).then(function() { + toResolve2.resolve(2); + }); + Q.delay(100).then(function() { + toResolve3.resolve(3); + }); + + return Q.delay(250) + .then(function() { + expect(promise.isFulfilled()).toBe(true); + expect(promise.inspect().value).toBe(4); + }) + .timeout(1000); + }); + + it("fulfills after rejections", function() { + var toReject = [Q.defer(), Q.defer()]; + var toResolve = Q.defer(); + var promises = [toReject[0].promise, toReject[1].promise, + toResolve + .promise + ]; + + var promise = Q.any(promises); + + testFulfillAfterRejections(promises, toReject, toResolve); + }); + + it("fulfills after rejections in sparse array", function() { + var toReject = [Q.defer(), Q.defer()]; + var toResolve = Q.defer(); + var promises = []; + promises[2] = toReject[0].promise; + promises[5] = toReject[1].promise; + promises[9] = toResolve.promise; + + testFulfillAfterRejections(promises, toReject, toResolve); + }); + + function testFulfillAfterRejections(promises, rejectDeferreds, + fulfillDeferred) { + var promise = Q.any(promises); + + for (var index = 0; index < rejectDeferreds.length; index++) { + var toReject = rejectDeferreds[index]; + (function(index, toReject) { + var time = (index + 1) * 50; + Q.delay(time).then(function() { + toReject.reject(new Error('Rejected')); + }); + })(index, toReject); + + index++; + } + Q.delay(index * 50).then(function() { + fulfillDeferred.resolve('Fulfilled'); + }); + + return Q.delay(400) + .then(function() { + expect(promise.isFulfilled()).toBe(true); + expect(promise.inspect().value).toBe('Fulfilled'); + }) + .timeout(1000); + } + + it("resolves foreign thenables", function() { + var normal = Q.delay(150) + .then(function() {}) + .thenResolve(1); + var foreign = { + then: function(f) { + return f(2); + } + }; + + return Q.any([normal, foreign]) + .then(function(result) { + expect(result).toEqual(2); + }); + }); + + 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"); // Is lost, deferred2 already resolved. + deferred1.resolve(); + }); + + return Q.any([deferred1.promise, deferred2.promise]) + .delay(250) + .then(function() { + expect(progressValues).toEqual([{ + index: 0, + value: "a" + }, { + index: 1, + value: "b" + }]); + }, + 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); + }); + }); + }); + + }); + +}); + +// Almost like "fin" +describe("tap", function () { + var exception1 = new Error("boo!"); + + describe("when the promise is fulfilled", function () { + it("should call the callback", function () { + var called = false; + return Q("foo") + .tap(function () { + called = true; + }) + .then(function () { + expect(called).toBe(true); + }); + }); + + it("should fulfill with the original value", function () { + return Q("foo") + .tap(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") + .tap(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") + .tap(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") + .tap(function () { + throw exception1; + }) + .then(function () { + expect(false).toBe(true); + }, + function (exception) { + expect(exception).toBe(exception1); + }); + }); + }); + + }); + + describe("when the promise is rejected", function () { + it("should not call the callback", function () { + var called = false; + + return Q.reject(exception1) + .tap(function () { + called = true; + }) + .then(function () { + expect(called).toBe(false); + }, function () { + expect(called).toBe(false); + }); + }); + }); +}); + + +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); + expect(error.code).toBe("ETIMEDOUT"); + } + ); + }); + + it("should reject with a custom timeout error if the promise is too slow and Error object was provided", function () { + var customError = new Error("custom"); + customError.isCustom = true; + return Q.delay(100) + .timeout(10, customError) + .then( + function () { + expect(true).toBe(false); + }, + function (error) { + expect(/custom/i.test(error.message)).toBe(true); + expect(error.isCustom).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("browser support", function () { + var _Q; + + beforeEach(function() { + _Q = Q; + }); + + afterEach(function() { + Q = _Q; + }); + + it("sets the global Q object to its original value", function() { + if (typeof window !== 'undefined') { + // If window is not undefined, the tests are running in the browser + // assert that Q.noConflict returns window.Q to it's initial value + // In this context the original value of Q is undefined + Q.noConflict(); + expect(Q).toEqual(undefined); + } + }); + + it("throws an error if Q.noConflict is called in node", function () { + if (typeof window === 'undefined') { + // If window is undefined the tests are being run in node, and + // Q.noConflict should throw an error + expect(Q.noConflict).toThrow(); + } + }); +}); + +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/tests/lib/q-1.4.1/spec/queue-spec.js b/tests/lib/q-1.4.1/spec/queue-spec.js new file mode 100644 index 0000000000000000000000000000000000000000..4cec14e23bf6a15ee16f4a3289042da0a2c4dd64 --- /dev/null +++ b/tests/lib/q-1.4.1/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"); + }) + }); + +}); +