From b301f173ec106708c769619567cf479bb7321826 Mon Sep 17 00:00:00 2001 From: Stefan Penner Date: Tue, 26 Aug 2014 22:05:57 -0400 Subject: [PATCH] sync with RSVP. - performance - reduced file-size - bugfixes - new build system [fixes #38, #34, #33, #28, #26, #18, #14, #9, #5] --- .gitignore | 1 + .jshintrc | 2 + .npmignore | 1 - .release.json | 17 + .travis.yml | 6 +- Brocfile.js | 74 + CHANGELOG.md | 44 +- Gruntfile.js | 53 - README.md | 12 +- bin/publish_to_s3.js | 28 + bower.json | 29 + config/s3ProjectConfig.js | 26 + config/versionTemplate.txt | 7 + lib/calculateVersion.js | 36 + lib/es6-promise.umd.js | 16 + lib/es6-promise/-internal.js | 247 ++ lib/{promise => es6-promise}/asap.js | 60 +- lib/es6-promise/enumerator.js | 125 + lib/{promise => es6-promise}/polyfill.js | 8 +- lib/es6-promise/promise.js | 415 ++ lib/es6-promise/promise/all.js | 52 + lib/es6-promise/promise/race.js | 105 + lib/{ => es6-promise}/promise/reject.js | 31 +- lib/es6-promise/promise/resolve.js | 49 + lib/es6-promise/utils.js | 39 + lib/main.js | 3 - lib/promise/all.js | 91 - lib/promise/config.js | 13 - lib/promise/promise.js | 207 - lib/promise/race.js | 88 - lib/promise/resolve.js | 14 - lib/promise/utils.js | 18 - package.json | 61 +- server/.jshintrc | 3 + server/index.js | 6 + tasks/browser.js | 12 - tasks/build_tests.js | 31 - tasks/options/browser.js | 10 - tasks/options/browserify.js | 8 - tasks/options/buildTests.js | 11 - tasks/options/clean.js | 3 - tasks/options/concat.js | 27 - tasks/options/connect.js | 23 - tasks/options/jshint.js | 8 - tasks/options/mochaTest.js | 13 - tasks/options/mocha_phantomjs.js | 7 - tasks/options/s3.js | 19 - tasks/options/transpile.js | 54 - tasks/options/uglify.js | 18 - tasks/options/watch.js | 6 - tasks/options/yuidoc.js | 12 - test/index.html | 21 +- test/main.js | 19 + test/test-adapter.js | 56 +- test/tests/extension-test.js | 1006 +++++ test/tests/worker.js | 16 + test/vendor/assert.js | 370 -- test/vendor/mocha.css | 227 - test/vendor/mocha.js | 4911 ---------------------- testem.json | 11 + 60 files changed, 2464 insertions(+), 6422 deletions(-) create mode 100644 .release.json create mode 100644 Brocfile.js delete mode 100644 Gruntfile.js create mode 100755 bin/publish_to_s3.js create mode 100644 bower.json create mode 100644 config/s3ProjectConfig.js create mode 100644 config/versionTemplate.txt create mode 100644 lib/calculateVersion.js create mode 100644 lib/es6-promise.umd.js create mode 100644 lib/es6-promise/-internal.js rename lib/{promise => es6-promise}/asap.js (57%) create mode 100644 lib/es6-promise/enumerator.js rename lib/{promise => es6-promise}/polyfill.js (86%) create mode 100644 lib/es6-promise/promise.js create mode 100644 lib/es6-promise/promise/all.js create mode 100644 lib/es6-promise/promise/race.js rename lib/{ => es6-promise}/promise/reject.js (58%) create mode 100644 lib/es6-promise/promise/resolve.js create mode 100644 lib/es6-promise/utils.js delete mode 100644 lib/main.js delete mode 100644 lib/promise/all.js delete mode 100644 lib/promise/config.js delete mode 100644 lib/promise/promise.js delete mode 100644 lib/promise/race.js delete mode 100644 lib/promise/resolve.js delete mode 100644 lib/promise/utils.js create mode 100644 server/.jshintrc create mode 100644 server/index.js delete mode 100644 tasks/browser.js delete mode 100644 tasks/build_tests.js delete mode 100644 tasks/options/browser.js delete mode 100644 tasks/options/browserify.js delete mode 100644 tasks/options/buildTests.js delete mode 100644 tasks/options/clean.js delete mode 100644 tasks/options/concat.js delete mode 100644 tasks/options/connect.js delete mode 100644 tasks/options/jshint.js delete mode 100644 tasks/options/mochaTest.js delete mode 100644 tasks/options/mocha_phantomjs.js delete mode 100644 tasks/options/s3.js delete mode 100644 tasks/options/transpile.js delete mode 100644 tasks/options/uglify.js delete mode 100644 tasks/options/watch.js delete mode 100644 tasks/options/yuidoc.js create mode 100644 test/main.js create mode 100644 test/tests/extension-test.js create mode 100644 test/tests/worker.js delete mode 100644 test/vendor/assert.js delete mode 100644 test/vendor/mocha.css delete mode 100644 test/vendor/mocha.js create mode 100644 testem.json diff --git a/.gitignore b/.gitignore index 689fb94..82bac23 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /main.js /tmp /dist +/docs diff --git a/.jshintrc b/.jshintrc index 70ee19c..be502a3 100644 --- a/.jshintrc +++ b/.jshintrc @@ -16,6 +16,8 @@ "expect" ], + "esnext": true, + "proto": true, "node" : true, "browser" : true, diff --git a/.npmignore b/.npmignore index b65d931..7a75811 100644 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,5 @@ /node_modules/ /tmp -/lib /tasks /test /vendor diff --git a/.release.json b/.release.json new file mode 100644 index 0000000..dee8cbc --- /dev/null +++ b/.release.json @@ -0,0 +1,17 @@ +{ + "non-interactive": true, + "dry-run": false, + "verbose": false, + "force": false, + "pkgFiles": ["package.json", "bower.json"], + "increment": "patch", + "commitMessage": "Release %s", + "tagName": "%s", + "tagAnnotation": "Release %s", + "buildCommand": "npm run-script build-all", + "distRepo": "git@github.com:components/rsvp.js.git", + "distStageDir": "tmp/stage", + "distBase": "dist", + "distFiles": ["**/*", "../package.json", "../bower.json"], + "publish": false +} diff --git a/.travis.yml b/.travis.yml index b416a3b..9e9d1e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,8 @@ language: node_js node_js: - '0.10' -before_install: -- npm install -g grunt-cli -after_success: grunt build s3:dev +after_success: +- "./bin/publish_to_s3.js" env: global: - secure: |- @@ -15,3 +14,4 @@ env: mrNGoG5AycW+d5jlghF9PJHQW1ETT9jWI7bGe9QX1QRpoSLkGAvxavrujmhu UNRP/pe5AJBh3zDED38nD/ZGtrcSYQ5CC6QbFLx5mLtqn7mNTo1m3C5lr+55 Sb6mnyLLz2eoPBUMcXwY1i9Qisy40uhfV9VuD5B/gvVud2+oJWI= + - EMBER_ENV=production diff --git a/Brocfile.js b/Brocfile.js new file mode 100644 index 0000000..cd5218c --- /dev/null +++ b/Brocfile.js @@ -0,0 +1,74 @@ +/* jshint node:true, undef:true, unused:true */ +var AMDFormatter = require('es6-module-transpiler-amd-formatter'); +var closureCompiler = require('broccoli-closure-compiler'); +var compileModules = require('broccoli-compile-modules'); +var mergeTrees = require('broccoli-merge-trees'); +var moveFile = require('broccoli-file-mover'); +var es3Recast = require('broccoli-es3-safe-recast'); +var concat = require('broccoli-concat'); +var replace = require('broccoli-string-replace'); +var calculateVersion = require('./lib/calculateVersion'); +var path = require('path'); +var trees = []; +var env = process.env.EMBER_ENV || 'development'; + +var bundle = compileModules('lib', { + inputFiles: ['es6-promise.umd.js'], + output: '/es6-promise.js', + formatter: 'bundle', +}); + +trees.push(bundle); +trees.push(compileModules('lib', { + inputFiles: ['**/*.js'], + output: '/amd/', + formatter: new AMDFormatter() +})); + +if (env === 'production') { + trees.push(closureCompiler(moveFile(bundle, { + srcFile: 'es6-promise.js', + destFile: 'es6-promise.min.js' + }), { + compilation_level: 'ADVANCED_OPTIMIZATIONS', + })); +} + +var distTree = mergeTrees(trees.concat('config')); +var distTrees = []; + +distTrees.push(concat(distTree, { + inputFiles: [ + 'versionTemplate.txt', + 'es6-promise.js' + ], + outputFile: '/es6-promise.js' +})); + +if (env === 'production') { + distTrees.push(concat(distTree, { + inputFiles: [ + 'versionTemplate.txt', + 'es6-promise.min.js' + ], + outputFile: '/es6-promise.min.js' + })); +} + +if (env !== 'development') { + distTrees = distTrees.map(es3Recast); +} + +distTree = mergeTrees(distTrees); +var distTree = replace(distTree, { + files: [ + 'es6-promise.js', + 'es6-promise.min.js' + ], + pattern: { + match: /VERSION_PLACEHOLDER_STRING/g, + replacement: calculateVersion() + } +}); + +module.exports = distTree; diff --git a/CHANGELOG.md b/CHANGELOG.md index 363cc7b..e06b496 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,43 +1,9 @@ -# master +# Master -# 2.0.4 - -* Fix npm package - -# 2.0.3 - -* Fix useSetTimeout bug - -# 2.0.2 - -* Adding RSVP#rethrow -* add pre-built AMD link to README -* adding promise#fail +# 2.0.0 -# 2.0.1 -* misc IE fixes, including IE array detection -* upload passing builds to s3 -* async: use three args for addEventListener -* satisfy both 1.0 and 1.1 specs -* Run reduce tests only in node -* RSVP.resolve now simply uses the internal resolution procedure -* prevent multiple promise resolutions -* simplify thenable handling -* pre-allocate the deferred's shape -* Moved from Rake-based builds to Grunt -* Fix Promise subclassing bug -* Add RSVP.configure('onerror') -* Throw exception when RSVP.all is called without an array -* refactor RSVP.all to just use a promise directly -* Make `RSVP.denodeify` pass along `thisArg` -* add RSVP.reject -* Reject promise if resolver function throws an exception -* add travis build-status -* correctly test and fix self fulfillment -* remove promise coercion. -* Fix infinite recursion with deep self fulfilling promises -* doc fixes +* re-sync with RSVP. Many large performance improvements and bugfixes. -# 2.0.0 +# 1.0.0 -* No changelog beyond this point. Here be dragons. +* first subset of RSVP diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 81e3ec2..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,53 +0,0 @@ -module.exports = function(grunt) { - require('load-grunt-tasks')(grunt); - var config = require('load-grunt-config')(grunt, { - configPath: 'tasks/options', - init: false - }); - - grunt.loadTasks('tasks'); - - this.registerTask('default', ['build']); - -// Run client-side tests on the command line. - this.registerTask('test', 'Runs tests through the command line using PhantomJS', [ - 'build', 'tests', 'connect' - ]); - - // Run a server. This is ideal for running the QUnit tests in the browser. - this.registerTask('server', ['build', 'tests', 'connect', 'watch:server']); - - - // Build test files - this.registerTask('tests', 'Builds the test package', ['concat:deps', 'browserify:tests', - 'transpile:testsAmd', 'transpile:testsCommonjs', 'buildTests:dist']); - - // Build a new version of the library - this.registerTask('build', 'Builds a distributable version of <%= cfg.name %>', - ['clean', 'transpile:amd', 'transpile:commonjs', 'concat:amd', - 'concat:browser', 'browser:dist', 'jshint', 'uglify:browser']); - - // Custom phantomjs test task - this.registerTask('test:phantom', "Runs tests through the command line using PhantomJS", [ - 'build', 'tests', 'mocha_phantomjs']); - - // Custom Node test task - this.registerTask('test:node', ['build', 'tests', 'mochaTest']); - - this.registerTask('test', ['build', 'tests', 'mocha_phantomjs', 'mochaTest']); - - // Custom YUIDoc task - this.registerTask('docs', ['yuidoc']); - - config.env = process.env; - config.pkg = grunt.file.readJSON('package.json'); - - // Load custom tasks from NPM - grunt.loadNpmTasks('grunt-browserify'); - grunt.loadNpmTasks('grunt-mocha-phantomjs'); - grunt.loadNpmTasks('grunt-mocha-test'); - grunt.loadNpmTasks('grunt-contrib-yuidoc'); - - // Merge config into emberConfig, overwriting existing settings - grunt.initConfig(config); -}; diff --git a/README.md b/README.md index ee6f48d..62b05d7 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,9 @@ To use: var Promise = require('es6-promise').Promise; ``` -## Usage in IE<10 +## Usage in IE<9 -`catch` is a reserved word in IE<10, meaning `promise.catch(func)` throws a syntax error. To work around this, use a string to access the property: +`catch` is a reserved word in IE<9, meaning `promise.catch(func)` throws a syntax error. To work around this, use a string to access the property: ```js promise['catch'](function(err) { @@ -43,10 +43,4 @@ promise.then(undefined, function(err) { ## Building & Testing -This package uses the [grunt-microlib](https://github.com/thomasboyt/grunt-microlib) package for building. - -Custom tasks: - -* `grunt test` - Run Mocha tests through Node and PhantomJS. -* `grunt test:phantom` - Run Mocha tests through PhantomJS (browser build). -* `grunt test:node` - Run Mocha tests through Node (CommonJS build). +* `npm run build-all && npm test` - Run Mocha tests through Node and PhantomJS. diff --git a/bin/publish_to_s3.js b/bin/publish_to_s3.js new file mode 100755 index 0000000..7daf49a --- /dev/null +++ b/bin/publish_to_s3.js @@ -0,0 +1,28 @@ +#!/usr/bin/env node + +// To invoke this from the commandline you need the following to env vars to exist: +// +// S3_BUCKET_NAME +// TRAVIS_BRANCH +// TRAVIS_TAG +// TRAVIS_COMMIT +// S3_SECRET_ACCESS_KEY +// S3_ACCESS_KEY_ID +// +// Once you have those you execute with the following: +// +// ```sh +// ./bin/publish_to_s3.js +// ``` +var S3Publisher = require('ember-publisher'); +var configPath = require('path').join(__dirname, '../config/s3ProjectConfig.js'); +publisher = new S3Publisher({ projectConfigPath: configPath }); + +// Always use wildcard section of project config. +// This is useful when the including library does not +// require channels (like in ember.js / ember-data). +publisher.currentBranch = function() { + return (process.env.TRAVIS_BRANCH === 'master') ? 'wildcard' : 'no-op'; +}; +publisher.publish(); + diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..9217b0b --- /dev/null +++ b/bower.json @@ -0,0 +1,29 @@ +{ + "name": "es6-promise", + "namespace": "Promise", + "version": "2.0.0", + "description": "A polyfill for ES6-style Promises, tracking rsvp", + "authors": [ + "Stefan Penner " + ], + "main": "dist/es6-promise.js", + "keywords": [ + "promise" + ], + "repository": { + "type": "git", + "url": "git://github.com/jakearchibald/ES6-Promises.git" + }, + "bugs": { + "url": "https://github.com/jakearchibald/ES6-Promises/issues" + } + "license": "MIT", + "ignore": [ + "node_modules", + "bower_components", + "test", + "tests", + "vendor", + "tasks" + ] +} diff --git a/config/s3ProjectConfig.js b/config/s3ProjectConfig.js new file mode 100644 index 0000000..5f3349a --- /dev/null +++ b/config/s3ProjectConfig.js @@ -0,0 +1,26 @@ +/* + * Using wildcard because es6-promise does not currently have a + * channel system in place. + */ +module.exports = function(revision,tag,date){ + return { + 'es6-promise.js': + { contentType: 'text/javascript', + destinations: { + wildcard: [ + 'es6-promise-latest.js', + 'es6-promise-' + revision + '.js' + ] + } + }, + 'es6-promise.min.js': + { contentType: 'text/javascript', + destinations: { + wildcard: [ + 'es6-promise-latest.min.js', + 'es6-promise-' + revision + '.min.js' + ] + } + } + } +} diff --git a/config/versionTemplate.txt b/config/versionTemplate.txt new file mode 100644 index 0000000..999a5fc --- /dev/null +++ b/config/versionTemplate.txt @@ -0,0 +1,7 @@ +/*! + * @overview es6-promise - a tiny implementation of Promises/A+. + * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) + * @license Licensed under MIT license + * See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE + * @version VERSION_PLACEHOLDER_STRING + */ diff --git a/lib/calculateVersion.js b/lib/calculateVersion.js new file mode 100644 index 0000000..018e364 --- /dev/null +++ b/lib/calculateVersion.js @@ -0,0 +1,36 @@ +'use strict'; + +var fs = require('fs'); +var path = require('path'); + +module.exports = function () { + var packageVersion = require('../package.json').version; + var output = [packageVersion]; + var gitPath = path.join(__dirname,'..','.git'); + var headFilePath = path.join(gitPath, 'HEAD'); + + if (packageVersion.indexOf('+') > -1) { + try { + if (fs.existsSync(headFilePath)) { + var headFile = fs.readFileSync(headFilePath, {encoding: 'utf8'}); + var branchName = headFile.split('/').slice(-1)[0].trim(); + var refPath = headFile.split(' ')[1]; + var branchSHA; + + if (refPath) { + var branchPath = path.join(gitPath, refPath.trim()); + branchSHA = fs.readFileSync(branchPath); + } else { + branchSHA = branchName; + } + + output.push(branchSHA.slice(0,10)); + } + } catch (err) { + console.error(err.stack); + } + return output.join('.'); + } else { + return packageVersion; + } +}; diff --git a/lib/es6-promise.umd.js b/lib/es6-promise.umd.js new file mode 100644 index 0000000..78c590d --- /dev/null +++ b/lib/es6-promise.umd.js @@ -0,0 +1,16 @@ +import Promise from './es6-promise/promise'; +import polyfill from './es6-promise/polyfill'; + +var ES6Promise = { + Promise: Promise, + polyfill: polyfill +}; + +/* global define:true module:true window: true */ +if (typeof define === 'function' && define.amd) { + define(function() { return ES6Promise; }); +} else if (typeof module !== 'undefined' && module.exports) { + module.exports = ES6Promise; +} else if (typeof this !== 'undefined') { + this['ES6Promise'] = ES6Promise; +} diff --git a/lib/es6-promise/-internal.js b/lib/es6-promise/-internal.js new file mode 100644 index 0000000..14699be --- /dev/null +++ b/lib/es6-promise/-internal.js @@ -0,0 +1,247 @@ +import { + objectOrFunction, + isFunction +} from './utils'; + +import asap from './asap'; + +function noop() {} + +var PENDING = void 0; +var FULFILLED = 1; +var REJECTED = 2; + +var GET_THEN_ERROR = new ErrorObject(); + +function getThen(promise) { + try { + return promise.then; + } catch(error) { + GET_THEN_ERROR.error = error; + return GET_THEN_ERROR; + } +} + +function tryThen(then, value, fulfillmentHandler, rejectionHandler) { + try { + then.call(value, fulfillmentHandler, rejectionHandler); + } catch(e) { + return e; + } +} + +function handleForeignThenable(promise, thenable, then) { + asap(function(promise) { + var sealed = false; + var error = tryThen(then, thenable, function(value) { + if (sealed) { return; } + sealed = true; + if (thenable !== value) { + resolve(promise, value); + } else { + fulfill(promise, value); + } + }, function(reason) { + if (sealed) { return; } + sealed = true; + + reject(promise, reason); + }, 'Settle: ' + (promise._label || ' unknown promise')); + + if (!sealed && error) { + sealed = true; + reject(promise, error); + } + }, promise); +} + +function handleOwnThenable(promise, thenable) { + if (thenable._state === FULFILLED) { + fulfill(promise, thenable._result); + } else if (promise._state === REJECTED) { + reject(promise, thenable._result); + } else { + subscribe(thenable, undefined, function(value) { + if (thenable !== value) { + resolve(promise, value); + } else { + fulfill(promise, value); + } + }, function(reason) { + reject(promise, reason); + }); + } +} + +function handleMaybeThenable(promise, maybeThenable) { + if (maybeThenable.constructor === promise.constructor) { + handleOwnThenable(promise, maybeThenable); + } else { + var then = getThen(maybeThenable); + + if (then === GET_THEN_ERROR) { + reject(promise, GET_THEN_ERROR.error); + } else if (then === undefined) { + fulfill(promise, maybeThenable); + } else if (isFunction(then)) { + handleForeignThenable(promise, maybeThenable, then); + } else { + fulfill(promise, maybeThenable); + } + } +} + +function resolve(promise, value) { + if (promise === value) { + fulfill(promise, value); + } else if (objectOrFunction(value)) { + handleMaybeThenable(promise, value); + } else { + fulfill(promise, value); + } +} + +function publishRejection(promise) { + if (promise._onerror) { + promise._onerror(promise._result); + } + + publish(promise); +} + +function fulfill(promise, value) { + if (promise._state !== PENDING) { return; } + + promise._result = value; + promise._state = FULFILLED; + + if (promise._subscribers.length === 0) { + } else { + asap(publish, promise); + } +} + +function reject(promise, reason) { + if (promise._state !== PENDING) { return; } + promise._state = REJECTED; + promise._result = reason; + + asap(publishRejection, promise); +} + +function subscribe(parent, child, onFulfillment, onRejection) { + var subscribers = parent._subscribers; + var length = subscribers.length; + + parent._onerror = null; + + subscribers[length] = child; + subscribers[length + FULFILLED] = onFulfillment; + subscribers[length + REJECTED] = onRejection; + + if (length === 0 && parent._state) { + asap(publish, parent); + } +} + +function publish(promise) { + var subscribers = promise._subscribers; + var settled = promise._state; + + if (subscribers.length === 0) { return; } + + var child, callback, detail = promise._result; + + for (var i = 0; i < subscribers.length; i += 3) { + child = subscribers[i]; + callback = subscribers[i + settled]; + + if (child) { + invokeCallback(settled, child, callback, detail); + } else { + callback(detail); + } + } + + promise._subscribers.length = 0; +} + +function ErrorObject() { + this.error = null; +} + +var TRY_CATCH_ERROR = new ErrorObject(); + +function tryCatch(callback, detail) { + try { + return callback(detail); + } catch(e) { + TRY_CATCH_ERROR.error = e; + return TRY_CATCH_ERROR; + } +} + +function invokeCallback(settled, promise, callback, detail) { + var hasCallback = isFunction(callback), + value, error, succeeded, failed; + + if (hasCallback) { + value = tryCatch(callback, detail); + + if (value === TRY_CATCH_ERROR) { + failed = true; + error = value.error; + value = null; + } else { + succeeded = true; + } + + if (promise === value) { + reject(promise, new TypeError('A promises callback cannot return that same promise.')); + return; + } + + } else { + value = detail; + succeeded = true; + } + + if (promise._state !== PENDING) { + // noop + } else if (hasCallback && succeeded) { + resolve(promise, value); + } else if (failed) { + reject(promise, error); + } else if (settled === FULFILLED) { + fulfill(promise, value); + } else if (settled === REJECTED) { + reject(promise, value); + } +} + +function initializePromise(promise, resolver) { + try { + resolver(function resolvePromise(value){ + resolve(promise, value); + }, function rejectPromise(reason) { + reject(promise, reason); + }); + } catch(e) { + reject(promise, e); + } +} + +export { + noop, + resolve, + reject, + fulfill, + subscribe, + publish, + publishRejection, + initializePromise, + invokeCallback, + FULFILLED, + REJECTED, + PENDING +}; diff --git a/lib/promise/asap.js b/lib/es6-promise/asap.js similarity index 57% rename from lib/promise/asap.js rename to lib/es6-promise/asap.js index efdecce..4872589 100644 --- a/lib/promise/asap.js +++ b/lib/es6-promise/asap.js @@ -1,6 +1,24 @@ +var len = 0; + +export default function asap(callback, arg) { + queue[len] = callback; + queue[len + 1] = arg; + len += 2; + if (len === 2) { + // If len is 1, that means that we need to schedule an async flush. + // If additional callbacks are queued before the queue is flushed, they + // will be processed by this flush that we are scheduling. + scheduleFlush(); + } +} + var browserGlobal = (typeof window !== 'undefined') ? window : {}; var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver; -var local = (typeof global !== 'undefined') ? global : (this === undefined? window:this); + +// test for web worker but not in IE10 +var isWorker = typeof Uint8ClampedArray !== 'undefined' && + typeof importScripts !== 'undefined' && + typeof MessageChannel !== 'undefined'; // node function useNextTick() { @@ -20,20 +38,34 @@ function useMutationObserver() { }; } +// web worker +function useMessageChannel() { + var channel = new MessageChannel(); + channel.port1.onmessage = flush; + return function () { + channel.port2.postMessage(0); + }; +} + function useSetTimeout() { return function() { - local.setTimeout(flush, 1); + setTimeout(flush, 1); }; } -var queue = []; +var queue = new Array(1000); function flush() { - for (var i = 0; i < queue.length; i++) { - var tuple = queue[i]; - var callback = tuple[0], arg = tuple[1]; + for (var i = 0; i < len; i+=2) { + var callback = queue[i]; + var arg = queue[i+1]; + callback(arg); + + queue[i] = undefined; + queue[i+1] = undefined; } - queue = []; + + len = 0; } var scheduleFlush; @@ -43,18 +75,8 @@ if (typeof process !== 'undefined' && {}.toString.call(process) === '[object pro scheduleFlush = useNextTick(); } else if (BrowserMutationObserver) { scheduleFlush = useMutationObserver(); +} else if (isWorker) { + scheduleFlush = useMessageChannel(); } else { scheduleFlush = useSetTimeout(); } - -function asap(callback, arg) { - var length = queue.push([callback, arg]); - if (length === 1) { - // If length is 1, that means that we need to schedule an async flush. - // If additional callbacks are queued before the queue is flushed, they - // will be processed by this flush that we are scheduling. - scheduleFlush(); - } -} - -export { asap }; diff --git a/lib/es6-promise/enumerator.js b/lib/es6-promise/enumerator.js new file mode 100644 index 0000000..7d3f803 --- /dev/null +++ b/lib/es6-promise/enumerator.js @@ -0,0 +1,125 @@ +import { + isArray, + isMaybeThenable +} from './utils'; + +import { + noop, + reject, + fulfill, + subscribe, + FULFILLED, + REJECTED, + PENDING +} from './-internal'; + +export function makeSettledResult(state, position, value) { + if (state === FULFILLED) { + return { + state: 'fulfilled', + value: value + }; + } else { + return { + state: 'rejected', + reason: value + }; + } +} + +function Enumerator(Constructor, input, abortOnReject, label) { + this._instanceConstructor = Constructor; + this.promise = new Constructor(noop, label); + this._abortOnReject = abortOnReject; + + if (this._validateInput(input)) { + this._input = input; + this.length = input.length; + this._remaining = input.length; + + this._init(); + + if (this.length === 0) { + fulfill(this.promise, this._result); + } else { + this.length = this.length || 0; + this._enumerate(); + if (this._remaining === 0) { + fulfill(this.promise, this._result); + } + } + } else { + reject(this.promise, this._validationError()); + } +} + +Enumerator.prototype._validateInput = function(input) { + return isArray(input); +}; + +Enumerator.prototype._validationError = function() { + return new Error('Array Methods must be provided an Array'); +}; + +Enumerator.prototype._init = function() { + this._result = new Array(this.length); +}; + +export default Enumerator; + +Enumerator.prototype._enumerate = function() { + var length = this.length; + var promise = this.promise; + var input = this._input; + + for (var i = 0; promise._state === PENDING && i < length; i++) { + this._eachEntry(input[i], i); + } +}; + +Enumerator.prototype._eachEntry = function(entry, i) { + var c = this._instanceConstructor; + if (isMaybeThenable(entry)) { + if (entry.constructor === c && entry._state !== PENDING) { + entry._onerror = null; + this._settledAt(entry._state, i, entry._result); + } else { + this._willSettleAt(c.resolve(entry), i); + } + } else { + this._remaining--; + this._result[i] = this._makeResult(FULFILLED, i, entry); + } +}; + +Enumerator.prototype._settledAt = function(state, i, value) { + var promise = this.promise; + + if (promise._state === PENDING) { + this._remaining--; + + if (this._abortOnReject && state === REJECTED) { + reject(promise, value); + } else { + this._result[i] = this._makeResult(state, i, value); + } + } + + if (this._remaining === 0) { + fulfill(promise, this._result); + } +}; + +Enumerator.prototype._makeResult = function(state, i, value) { + return value; +}; + +Enumerator.prototype._willSettleAt = function(promise, i) { + var enumerator = this; + + subscribe(promise, undefined, function(value) { + enumerator._settledAt(FULFILLED, i, value); + }, function(reason) { + enumerator._settledAt(REJECTED, i, reason); + }); +}; diff --git a/lib/promise/polyfill.js b/lib/es6-promise/polyfill.js similarity index 86% rename from lib/promise/polyfill.js rename to lib/es6-promise/polyfill.js index 7bc7146..e5b0165 100644 --- a/lib/promise/polyfill.js +++ b/lib/es6-promise/polyfill.js @@ -1,8 +1,8 @@ /*global self*/ -import { Promise as RSVPPromise } from "./promise"; +import { default as RSVPPromise } from "./promise"; import { isFunction } from "./utils"; -function polyfill() { +export default function polyfill() { var local; if (typeof global !== 'undefined') { @@ -13,7 +13,7 @@ function polyfill() { local = self; } - var es6PromiseSupport = + var es6PromiseSupport = "Promise" in local && // Some of these methods are missing from // Firefox/Chrome experimental implementations @@ -33,5 +33,3 @@ function polyfill() { local.Promise = RSVPPromise; } } - -export { polyfill }; diff --git a/lib/es6-promise/promise.js b/lib/es6-promise/promise.js new file mode 100644 index 0000000..131fc01 --- /dev/null +++ b/lib/es6-promise/promise.js @@ -0,0 +1,415 @@ +import { + isFunction, + now +} from './utils'; + +import { + noop, + subscribe, + initializePromise, + invokeCallback, + FULFILLED, + REJECTED +} from './-internal'; + +import asap from './asap'; + +import all from './promise/all'; +import race from './promise/race'; +import Resolve from './promise/resolve'; +import Reject from './promise/reject'; + +var counter = 0; + +function needsResolver() { + throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); +} + +function needsNew() { + throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function."); +} + +export default Promise; +/** + Promise objects represent the eventual result of an asynchronous operation. The + primary way of interacting with a promise is through its `then` method, which + registers callbacks to receive either a promise’s eventual value or the reason + why the promise cannot be fulfilled. + + Terminology + ----------- + + - `promise` is an object or function with a `then` method whose behavior conforms to this specification. + - `thenable` is an object or function that defines a `then` method. + - `value` is any legal JavaScript value (including undefined, a thenable, or a promise). + - `exception` is a value that is thrown using the throw statement. + - `reason` is a value that indicates why a promise was rejected. + - `settled` the final resting state of a promise, fulfilled or rejected. + + A promise can be in one of three states: pending, fulfilled, or rejected. + + Promises that are fulfilled have a fulfillment value and are in the fulfilled + state. Promises that are rejected have a rejection reason and are in the + rejected state. A fulfillment value is never a thenable. + + Promises can also be said to *resolve* a value. If this value is also a + promise, then the original promise's settled state will match the value's + settled state. So a promise that *resolves* a promise that rejects will + itself reject, and a promise that *resolves* a promise that fulfills will + itself fulfill. + + + Basic Usage: + ------------ + + ```js + var promise = new Promise(function(resolve, reject) { + // on success + resolve(value); + + // on failure + reject(reason); + }); + + promise.then(function(value) { + // on fulfillment + }, function(reason) { + // on rejection + }); + ``` + + Advanced Usage: + --------------- + + Promises shine when abstracting away asynchronous interactions such as + `XMLHttpRequest`s. + + ```js + function getJSON(url) { + return new Promise(function(resolve, reject){ + var xhr = new XMLHttpRequest(); + + xhr.open('GET', url); + xhr.onreadystatechange = handler; + xhr.responseType = 'json'; + xhr.setRequestHeader('Accept', 'application/json'); + xhr.send(); + + function handler() { + if (this.readyState === this.DONE) { + if (this.status === 200) { + resolve(this.response); + } else { + reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']')); + } + } + }; + }); + } + + getJSON('/posts.json').then(function(json) { + // on fulfillment + }, function(reason) { + // on rejection + }); + ``` + + Unlike callbacks, promises are great composable primitives. + + ```js + Promise.all([ + getJSON('/posts'), + getJSON('/comments') + ]).then(function(values){ + values[0] // => postsJSON + values[1] // => commentsJSON + + return values; + }); + ``` + + @class Promise + @param {function} resolver + @param {String} label optional string for labeling the promise. + Useful for tooling. + @constructor +*/ +function Promise(resolver, label) { + this._id = counter++; + this._label = label; + this._state = undefined; + this._result = undefined; + this._subscribers = []; + + if (noop !== resolver) { + if (!isFunction(resolver)) { + needsResolver(); + } + + if (!(this instanceof Promise)) { + needsNew(); + } + + initializePromise(this, resolver); + } +} + +Promise.all = all; +Promise.race = race; +Promise.resolve = Resolve; +Promise.reject = Reject; + +Promise.prototype = { + constructor: Promise, + +/** + The primary way of interacting with a promise is through its `then` method, + which registers callbacks to receive either a promise's eventual value or the + reason why the promise cannot be fulfilled. + + ```js + findUser().then(function(user){ + // user is available + }, function(reason){ + // user is unavailable, and you are given the reason why + }); + ``` + + Chaining + -------- + + The return value of `then` is itself a promise. This second, 'downstream' + promise is resolved with the return value of the first promise's fulfillment + or rejection handler, or rejected if the handler throws an exception. + + ```js + findUser().then(function (user) { + return user.name; + }, function (reason) { + return 'default name'; + }).then(function (userName) { + // If `findUser` fulfilled, `userName` will be the user's name, otherwise it + // will be `'default name'` + }); + + findUser().then(function (user) { + throw new Error('Found user, but still unhappy'); + }, function (reason) { + throw new Error('`findUser` rejected and we're unhappy'); + }).then(function (value) { + // never reached + }, function (reason) { + // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'. + // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'. + }); + ``` + If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream. + + ```js + findUser().then(function (user) { + throw new PedagogicalException('Upstream error'); + }).then(function (value) { + // never reached + }).then(function (value) { + // never reached + }, function (reason) { + // The `PedgagocialException` is propagated all the way down to here + }); + ``` + + Assimilation + ------------ + + Sometimes the value you want to propagate to a downstream promise can only be + retrieved asynchronously. This can be achieved by returning a promise in the + fulfillment or rejection handler. The downstream promise will then be pending + until the returned promise is settled. This is called *assimilation*. + + ```js + findUser().then(function (user) { + return findCommentsByAuthor(user); + }).then(function (comments) { + // The user's comments are now available + }); + ``` + + If the assimliated promise rejects, then the downstream promise will also reject. + + ```js + findUser().then(function (user) { + return findCommentsByAuthor(user); + }).then(function (comments) { + // If `findCommentsByAuthor` fulfills, we'll have the value here + }, function (reason) { + // If `findCommentsByAuthor` rejects, we'll have the reason here + }); + ``` + + Simple Example + -------------- + + Synchronous Example + + ```javascript + var result; + + try { + result = findResult(); + // success + } catch(reason) { + // failure + } + ``` + + Errback Example + + ```js + findResult(function(result, err){ + if (err) { + // failure + } else { + // success + } + }); + ``` + + Promise Example; + + ```javascript + findResult().then(function(result){ + // success + }, function(reason){ + // failure + }); + ``` + + Advanced Example + -------------- + + Synchronous Example + + ```javascript + var author, books; + + try { + author = findAuthor(); + books = findBooksByAuthor(author); + // success + } catch(reason) { + // failure + } + ``` + + Errback Example + + ```js + + function foundBooks(books) { + + } + + function failure(reason) { + + } + + findAuthor(function(author, err){ + if (err) { + failure(err); + // failure + } else { + try { + findBoooksByAuthor(author, function(books, err) { + if (err) { + failure(err); + } else { + try { + foundBooks(books); + } catch(reason) { + failure(reason); + } + } + }); + } catch(error) { + failure(err); + } + // success + } + }); + ``` + + Promise Example; + + ```javascript + findAuthor(). + then(findBooksByAuthor). + then(function(books){ + // found books + }).catch(function(reason){ + // something went wrong + }); + ``` + + @method then + @param {Function} onFulfilled + @param {Function} onRejected + @param {String} label optional string for labeling the promise. + Useful for tooling. + @return {Promise} +*/ + then: function(onFulfillment, onRejection, label) { + var parent = this; + var state = parent._state; + + if (state === FULFILLED && !onFulfillment || state === REJECTED && !onRejection) { + return this; + } + + parent._onerror = null; + + var child = new this.constructor(noop, label); + var result = parent._result; + + if (state) { + var callback = arguments[state - 1]; + asap(function(){ + invokeCallback(state, child, callback, result); + }); + } else { + subscribe(parent, child, onFulfillment, onRejection); + } + + return child; + }, + +/** + `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same + as the catch block of a try/catch statement. + + ```js + function findAuthor(){ + throw new Error('couldn't find that author'); + } + + // synchronous + try { + findAuthor(); + } catch(reason) { + // something went wrong + } + + // async with promises + findAuthor().catch(function(reason){ + // something went wrong + }); + ``` + + @method catch + @param {Function} onRejection + @param {String} label optional string for labeling the promise. + Useful for tooling. + @return {Promise} +*/ + 'catch': function(onRejection, label) { + return this.then(null, onRejection, label); + } +}; diff --git a/lib/es6-promise/promise/all.js b/lib/es6-promise/promise/all.js new file mode 100644 index 0000000..8db7e71 --- /dev/null +++ b/lib/es6-promise/promise/all.js @@ -0,0 +1,52 @@ +import Enumerator from '../enumerator'; + +/** + `Promise.all` accepts an array of promises, and returns a new promise which + is fulfilled with an array of fulfillment values for the passed promises, or + rejected with the reason of the first passed promise to be rejected. It casts all + elements of the passed iterable to promises as it runs this algorithm. + + Example: + + ```javascript + var promise1 = resolve(1); + var promise2 = resolve(2); + var promise3 = resolve(3); + var promises = [ promise1, promise2, promise3 ]; + + Promise.all(promises).then(function(array){ + // The array here would be [ 1, 2, 3 ]; + }); + ``` + + If any of the `promises` given to `all` are rejected, the first promise + that is rejected will be given as an argument to the returned promises's + rejection handler. For example: + + Example: + + ```javascript + var promise1 = resolve(1); + var promise2 = reject(new Error("2")); + var promise3 = reject(new Error("3")); + var promises = [ promise1, promise2, promise3 ]; + + Promise.all(promises).then(function(array){ + // Code here never runs because there are rejected promises! + }, function(error) { + // error.message === "2" + }); + ``` + + @method all + @static + @param {Array} entries array of promises + @param {String} label optional string for labeling the promise. + Useful for tooling. + @return {Promise} promise that is fulfilled when all `promises` have been + fulfilled, or rejected if any of them become rejected. + @static +*/ +export default function all(entries, label) { + return new Enumerator(this, entries, true /* abort on reject */, label).promise; +} diff --git a/lib/es6-promise/promise/race.js b/lib/es6-promise/promise/race.js new file mode 100644 index 0000000..7daa28a --- /dev/null +++ b/lib/es6-promise/promise/race.js @@ -0,0 +1,105 @@ +import { + isArray +} from "../utils"; + +import { + noop, + resolve, + reject, + subscribe, + PENDING +} from '../-internal'; + +/** + `Promise.race` returns a new promise which is settled in the same way as the + first passed promise to settle. + + Example: + + ```javascript + var promise1 = new Promise(function(resolve, reject){ + setTimeout(function(){ + resolve('promise 1'); + }, 200); + }); + + var promise2 = new Promise(function(resolve, reject){ + setTimeout(function(){ + resolve('promise 2'); + }, 100); + }); + + Promise.race([promise1, promise2]).then(function(result){ + // result === 'promise 2' because it was resolved before promise1 + // was resolved. + }); + ``` + + `Promise.race` is deterministic in that only the state of the first + settled promise matters. For example, even if other promises given to the + `promises` array argument are resolved, but the first settled promise has + become rejected before the other promises became fulfilled, the returned + promise will become rejected: + + ```javascript + var promise1 = new Promise(function(resolve, reject){ + setTimeout(function(){ + resolve('promise 1'); + }, 200); + }); + + var promise2 = new Promise(function(resolve, reject){ + setTimeout(function(){ + reject(new Error('promise 2')); + }, 100); + }); + + Promise.race([promise1, promise2]).then(function(result){ + // Code here never runs + }, function(reason){ + // reason.message === 'promise 2' because promise 2 became rejected before + // promise 1 became fulfilled + }); + ``` + + An example real-world use case is implementing timeouts: + + ```javascript + Promise.race([ajax('foo.json'), timeout(5000)]) + ``` + + @method race + @static + @param {Array} promises array of promises to observe + @param {String} label optional string for describing the promise returned. + Useful for tooling. + @return {Promise} a promise which settles in the same way as the first passed + promise to settle. +*/ +export default function race(entries, label) { + /*jshint validthis:true */ + var Constructor = this; + + var promise = new Constructor(noop, label); + + if (!isArray(entries)) { + reject(promise, new TypeError('You must pass an array to race.')); + return promise; + } + + var length = entries.length; + + function onFulfillment(value) { + resolve(promise, value); + } + + function onRejection(reason) { + reject(promise, reason); + } + + for (var i = 0; promise._state === PENDING && i < length; i++) { + subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection); + } + + return promise; +} diff --git a/lib/promise/reject.js b/lib/es6-promise/promise/reject.js similarity index 58% rename from lib/promise/reject.js rename to lib/es6-promise/promise/reject.js index 28ceb7d..518eff7 100644 --- a/lib/promise/reject.js +++ b/lib/es6-promise/promise/reject.js @@ -1,9 +1,14 @@ +import { + noop, + reject as _reject +} from '../-internal'; + /** - `RSVP.reject` returns a promise that will become rejected with the passed - `reason`. `RSVP.reject` is essentially shorthand for the following: + `Promise.reject` returns a promise rejected with the passed `reason`. + It is shorthand for the following: ```javascript - var promise = new RSVP.Promise(function(resolve, reject){ + var promise = new Promise(function(resolve, reject){ reject(new Error('WHOOPS')); }); @@ -17,7 +22,7 @@ Instead of writing the above, your code now simply becomes the following: ```javascript - var promise = RSVP.reject(new Error('WHOOPS')); + var promise = Promise.reject(new Error('WHOOPS')); promise.then(function(value){ // Code here doesn't run because the promise is rejected! @@ -27,20 +32,16 @@ ``` @method reject - @for RSVP + @static @param {Any} reason value that the returned promise will be rejected with. @param {String} label optional string for identifying the returned promise. Useful for tooling. - @return {Promise} a promise that will become rejected with the given - `reason`. + @return {Promise} a promise rejected with the given `reason`. */ -function reject(reason) { +export default function reject(reason, label) { /*jshint validthis:true */ - var Promise = this; - - return new Promise(function (resolve, reject) { - reject(reason); - }); + var Constructor = this; + var promise = new Constructor(noop, label); + _reject(promise, reason); + return promise; } - -export { reject }; diff --git a/lib/es6-promise/promise/resolve.js b/lib/es6-promise/promise/resolve.js new file mode 100644 index 0000000..1b3c533 --- /dev/null +++ b/lib/es6-promise/promise/resolve.js @@ -0,0 +1,49 @@ +import { + noop, + resolve as _resolve +} from '../-internal'; + +/** + `Promise.resolve` returns a promise that will become resolved with the + passed `value`. It is shorthand for the following: + + ```javascript + var promise = new Promise(function(resolve, reject){ + resolve(1); + }); + + promise.then(function(value){ + // value === 1 + }); + ``` + + Instead of writing the above, your code now simply becomes the following: + + ```javascript + var promise = Promise.resolve(1); + + promise.then(function(value){ + // value === 1 + }); + ``` + + @method resolve + @static + @param {Any} value value that the returned promise will be resolved with + @param {String} label optional string for identifying the returned promise. + Useful for tooling. + @return {Promise} a promise that will become fulfilled with the given + `value` +*/ +export default function resolve(object, label) { + /*jshint validthis:true */ + var Constructor = this; + + if (object && typeof object === 'object' && object.constructor === Constructor) { + return object; + } + + var promise = new Constructor(noop, label); + _resolve(promise, object); + return promise; +} diff --git a/lib/es6-promise/utils.js b/lib/es6-promise/utils.js new file mode 100644 index 0000000..6b49bbf --- /dev/null +++ b/lib/es6-promise/utils.js @@ -0,0 +1,39 @@ +export function objectOrFunction(x) { + return typeof x === 'function' || (typeof x === 'object' && x !== null); +} + +export function isFunction(x) { + return typeof x === 'function'; +} + +export function isMaybeThenable(x) { + return typeof x === 'object' && x !== null; +} + +var _isArray; +if (!Array.isArray) { + _isArray = function (x) { + return Object.prototype.toString.call(x) === '[object Array]'; + }; +} else { + _isArray = Array.isArray; +} + +export var isArray = _isArray; + +// Date.now is not available in browsers < IE9 +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility +export var now = Date.now || function() { return new Date().getTime(); }; + +function F() { } + +export var o_create = (Object.create || function (o) { + if (arguments.length > 1) { + throw new Error('Second argument not supported'); + } + if (typeof o !== 'object') { + throw new TypeError('Argument must be an object'); + } + F.prototype = o; + return new F(); +}); diff --git a/lib/main.js b/lib/main.js deleted file mode 100644 index 949a92c..0000000 --- a/lib/main.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Promise } from "./promise/promise"; -import { polyfill } from "./promise/polyfill"; -export { Promise, polyfill }; \ No newline at end of file diff --git a/lib/promise/all.js b/lib/promise/all.js deleted file mode 100644 index e0cb251..0000000 --- a/lib/promise/all.js +++ /dev/null @@ -1,91 +0,0 @@ -/* global toString */ - -import { isArray, isFunction } from "./utils"; - -/** - Returns a promise that is fulfilled when all the given promises have been - fulfilled, or rejected if any of them become rejected. The return promise - is fulfilled with an array that gives all the values in the order they were - passed in the `promises` array argument. - - Example: - - ```javascript - var promise1 = RSVP.resolve(1); - var promise2 = RSVP.resolve(2); - var promise3 = RSVP.resolve(3); - var promises = [ promise1, promise2, promise3 ]; - - RSVP.all(promises).then(function(array){ - // The array here would be [ 1, 2, 3 ]; - }); - ``` - - If any of the `promises` given to `RSVP.all` are rejected, the first promise - that is rejected will be given as an argument to the returned promises's - rejection handler. For example: - - Example: - - ```javascript - var promise1 = RSVP.resolve(1); - var promise2 = RSVP.reject(new Error("2")); - var promise3 = RSVP.reject(new Error("3")); - var promises = [ promise1, promise2, promise3 ]; - - RSVP.all(promises).then(function(array){ - // Code here never runs because there are rejected promises! - }, function(error) { - // error.message === "2" - }); - ``` - - @method all - @for RSVP - @param {Array} promises - @param {String} label - @return {Promise} promise that is fulfilled when all `promises` have been - fulfilled, or rejected if any of them become rejected. -*/ -function all(promises) { - /*jshint validthis:true */ - var Promise = this; - - if (!isArray(promises)) { - throw new TypeError('You must pass an array to all.'); - } - - return new Promise(function(resolve, reject) { - var results = [], remaining = promises.length, - promise; - - if (remaining === 0) { - resolve([]); - } - - function resolver(index) { - return function(value) { - resolveAll(index, value); - }; - } - - function resolveAll(index, value) { - results[index] = value; - if (--remaining === 0) { - resolve(results); - } - } - - for (var i = 0; i < promises.length; i++) { - promise = promises[i]; - - if (promise && isFunction(promise.then)) { - promise.then(resolver(i), reject); - } else { - resolveAll(i, promise); - } - } - }); -} - -export { all }; diff --git a/lib/promise/config.js b/lib/promise/config.js deleted file mode 100644 index 50508d1..0000000 --- a/lib/promise/config.js +++ /dev/null @@ -1,13 +0,0 @@ -var config = { - instrument: false -}; - -function configure(name, value) { - if (arguments.length === 2) { - config[name] = value; - } else { - return config[name]; - } -} - -export { config, configure }; diff --git a/lib/promise/promise.js b/lib/promise/promise.js deleted file mode 100644 index 1b544d9..0000000 --- a/lib/promise/promise.js +++ /dev/null @@ -1,207 +0,0 @@ -import { config, configure } from "./config"; -import { objectOrFunction, isFunction, now } from './utils'; -import { all } from "./all"; -import { race } from "./race"; -import { resolve as staticResolve } from "./resolve"; -import { reject as staticReject } from "./reject"; -import { asap } from "./asap"; - -var counter = 0; - -config.async = asap; // default async is asap; - -function Promise(resolver) { - if (!isFunction(resolver)) { - throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); - } - - if (!(this instanceof Promise)) { - throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function."); - } - - this._subscribers = []; - - invokeResolver(resolver, this); -} - -function invokeResolver(resolver, promise) { - function resolvePromise(value) { - resolve(promise, value); - } - - function rejectPromise(reason) { - reject(promise, reason); - } - - try { - resolver(resolvePromise, rejectPromise); - } catch(e) { - rejectPromise(e); - } -} - -function invokeCallback(settled, promise, callback, detail) { - var hasCallback = isFunction(callback), - value, error, succeeded, failed; - - if (hasCallback) { - try { - value = callback(detail); - succeeded = true; - } catch(e) { - failed = true; - error = e; - } - } else { - value = detail; - succeeded = true; - } - - if (handleThenable(promise, value)) { - return; - } else if (hasCallback && succeeded) { - resolve(promise, value); - } else if (failed) { - reject(promise, error); - } else if (settled === FULFILLED) { - resolve(promise, value); - } else if (settled === REJECTED) { - reject(promise, value); - } -} - -var PENDING = void 0; -var SEALED = 0; -var FULFILLED = 1; -var REJECTED = 2; - -function subscribe(parent, child, onFulfillment, onRejection) { - var subscribers = parent._subscribers; - var length = subscribers.length; - - subscribers[length] = child; - subscribers[length + FULFILLED] = onFulfillment; - subscribers[length + REJECTED] = onRejection; -} - -function publish(promise, settled) { - var child, callback, subscribers = promise._subscribers, detail = promise._detail; - - for (var i = 0; i < subscribers.length; i += 3) { - child = subscribers[i]; - callback = subscribers[i + settled]; - - invokeCallback(settled, child, callback, detail); - } - - promise._subscribers = null; -} - -Promise.prototype = { - constructor: Promise, - - _state: undefined, - _detail: undefined, - _subscribers: undefined, - - then: function(onFulfillment, onRejection) { - var promise = this; - - var thenPromise = new this.constructor(function() {}); - - if (this._state) { - var callbacks = arguments; - config.async(function invokePromiseCallback() { - invokeCallback(promise._state, thenPromise, callbacks[promise._state - 1], promise._detail); - }); - } else { - subscribe(this, thenPromise, onFulfillment, onRejection); - } - - return thenPromise; - }, - - 'catch': function(onRejection) { - return this.then(null, onRejection); - } -}; - -Promise.all = all; -Promise.race = race; -Promise.resolve = staticResolve; -Promise.reject = staticReject; - -function handleThenable(promise, value) { - var then = null, - resolved; - - try { - if (promise === value) { - throw new TypeError("A promises callback cannot return that same promise."); - } - - if (objectOrFunction(value)) { - then = value.then; - - if (isFunction(then)) { - then.call(value, function(val) { - if (resolved) { return true; } - resolved = true; - - if (value !== val) { - resolve(promise, val); - } else { - fulfill(promise, val); - } - }, function(val) { - if (resolved) { return true; } - resolved = true; - - reject(promise, val); - }); - - return true; - } - } - } catch (error) { - if (resolved) { return true; } - reject(promise, error); - return true; - } - - return false; -} - -function resolve(promise, value) { - if (promise === value) { - fulfill(promise, value); - } else if (!handleThenable(promise, value)) { - fulfill(promise, value); - } -} - -function fulfill(promise, value) { - if (promise._state !== PENDING) { return; } - promise._state = SEALED; - promise._detail = value; - - config.async(publishFulfillment, promise); -} - -function reject(promise, reason) { - if (promise._state !== PENDING) { return; } - promise._state = SEALED; - promise._detail = reason; - - config.async(publishRejection, promise); -} - -function publishFulfillment(promise) { - publish(promise, promise._state = FULFILLED); -} - -function publishRejection(promise) { - publish(promise, promise._state = REJECTED); -} - -export { Promise }; diff --git a/lib/promise/race.js b/lib/promise/race.js deleted file mode 100644 index 6aff3d6..0000000 --- a/lib/promise/race.js +++ /dev/null @@ -1,88 +0,0 @@ -/* global toString */ -import { isArray } from "./utils"; - -/** - `RSVP.race` allows you to watch a series of promises and act as soon as the - first promise given to the `promises` argument fulfills or rejects. - - Example: - - ```javascript - var promise1 = new RSVP.Promise(function(resolve, reject){ - setTimeout(function(){ - resolve("promise 1"); - }, 200); - }); - - var promise2 = new RSVP.Promise(function(resolve, reject){ - setTimeout(function(){ - resolve("promise 2"); - }, 100); - }); - - RSVP.race([promise1, promise2]).then(function(result){ - // result === "promise 2" because it was resolved before promise1 - // was resolved. - }); - ``` - - `RSVP.race` is deterministic in that only the state of the first completed - promise matters. For example, even if other promises given to the `promises` - array argument are resolved, but the first completed promise has become - rejected before the other promises became fulfilled, the returned promise - will become rejected: - - ```javascript - var promise1 = new RSVP.Promise(function(resolve, reject){ - setTimeout(function(){ - resolve("promise 1"); - }, 200); - }); - - var promise2 = new RSVP.Promise(function(resolve, reject){ - setTimeout(function(){ - reject(new Error("promise 2")); - }, 100); - }); - - RSVP.race([promise1, promise2]).then(function(result){ - // Code here never runs because there are rejected promises! - }, function(reason){ - // reason.message === "promise2" because promise 2 became rejected before - // promise 1 became fulfilled - }); - ``` - - @method race - @for RSVP - @param {Array} promises array of promises to observe - @param {String} label optional string for describing the promise returned. - Useful for tooling. - @return {Promise} a promise that becomes fulfilled with the value the first - completed promises is resolved with if the first completed promise was - fulfilled, or rejected with the reason that the first completed promise - was rejected with. -*/ -function race(promises) { - /*jshint validthis:true */ - var Promise = this; - - if (!isArray(promises)) { - throw new TypeError('You must pass an array to race.'); - } - return new Promise(function(resolve, reject) { - var results = [], promise; - - for (var i = 0; i < promises.length; i++) { - promise = promises[i]; - - if (promise && typeof promise.then === 'function') { - promise.then(resolve, reject); - } else { - resolve(promise); - } - } - }); -} - -export { race }; diff --git a/lib/promise/resolve.js b/lib/promise/resolve.js deleted file mode 100644 index a82923d..0000000 --- a/lib/promise/resolve.js +++ /dev/null @@ -1,14 +0,0 @@ -function resolve(value) { - /*jshint validthis:true */ - if (value && typeof value === 'object' && value.constructor === this) { - return value; - } - - var Promise = this; - - return new Promise(function(resolve) { - resolve(value); - }); -} - -export { resolve }; diff --git a/lib/promise/utils.js b/lib/promise/utils.js deleted file mode 100644 index 13e8152..0000000 --- a/lib/promise/utils.js +++ /dev/null @@ -1,18 +0,0 @@ -function objectOrFunction(x) { - return isFunction(x) || (typeof x === "object" && x !== null); -} - -function isFunction(x) { - return typeof x === "function"; -} - -function isArray(x) { - return Object.prototype.toString.call(x) === "[object Array]"; -} - -// Date.now is not available in browsers < IE9 -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility -var now = Date.now || function() { return new Date().getTime(); }; - - -export { objectOrFunction, isFunction, isArray, now }; diff --git a/package.json b/package.json index aaf605e..56f6e83 100644 --- a/package.json +++ b/package.json @@ -1,39 +1,45 @@ { "name": "es6-promise", - "namespace": "Promise", - "version": "1.0.0", - "description": "A polyfill for ES6-style Promises, tracking rsvp", - "main": "dist/commonjs/main.js", + "namespace": "es6-promise", + "version": "2.0.0", + "description": "A lightweight library that provides tools for organizing asynchronous code", + "main": "dist/es6-promise.js", "directories": { "lib": "lib" }, "devDependencies": { + "bower": "^1.3.9", "brfs": "0.0.8", - "grunt": "~0.4.2", - "grunt-browserify": "~1.2.11", - "grunt-cli": "~0.1.11", - "grunt-contrib-clean": "~0.5.0", - "grunt-contrib-concat": "~0.3.0", - "grunt-contrib-connect": "~0.5.0", - "grunt-contrib-jshint": "~0.7.0", - "grunt-contrib-uglify": "~0.2.4", - "grunt-contrib-watch": "~0.5.3", - "grunt-es6-module-transpiler": "~0.6.0", - "grunt-mocha-phantomjs": "~0.3.1", - "grunt-mocha-test": "~0.5.0", - "grunt-s3": "~0.2.0-alpha.2", - "jshint": "~0.9", - "load-grunt-config": "~0.5.0", - "load-grunt-tasks": "~0.2.0", - "mocha-phantomjs": "~3.1.6", + "broccoli-closure-compiler": "^0.2.0", + "broccoli-compile-modules": "eventualbuddha/broccoli-compile-modules", + "broccoli-concat": "0.0.7", + "broccoli-es3-safe-recast": "0.0.8", + "broccoli-file-mover": "^0.4.0", + "broccoli-jshint": "^0.5.1", + "broccoli-merge-trees": "^0.1.4", + "broccoli-static-compiler": "^0.1.4", + "broccoli-string-replace": "0.0.1", + "browserify": "^4.2.0", + "ember-cli": "0.0.40", + "ember-publisher": "0.0.7", + "es6-module-transpiler-amd-formatter": "0.0.1", + "express": "^4.5.0", + "jshint": "~0.9.1", + "mkdirp": "^0.5.0", + "mocha": "^1.20.1", "promises-aplus-tests": "git://github.com/stefanpenner/promises-tests.git", - "connect-redirection": "0.0.1", - "grunt-contrib-yuidoc": "~0.5.0" + "release-it": "0.0.10", + "testem": "^0.6.17", + "json3": "^3.3.2" }, "scripts": { - "test": "grunt test", + "test": "testem ci -R dot", + "test-server": "testem", "lint": "jshint lib", - "prepublish": "grunt build" + "prepublish": "ember build --environment production", + "aplus": "browserify test/main.js", + "build-all": "ember build --environment production && browserify ./test/main.js -o tmp/test-bundle.js", + "dry-run-release": "ember build --environment production && release-it --dry-run --non-interactive" }, "repository": { "type": "git", @@ -44,9 +50,8 @@ }, "keywords": [ "promises", - "futures", - "events" + "futures" ], - "author": "Tilde, Inc. (Conversion to ES6 API by Jake Archibald)", + "author": "Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)", "license": "MIT" } diff --git a/server/.jshintrc b/server/.jshintrc new file mode 100644 index 0000000..c1f2978 --- /dev/null +++ b/server/.jshintrc @@ -0,0 +1,3 @@ +{ + "node": true +} diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..8a54d36 --- /dev/null +++ b/server/index.js @@ -0,0 +1,6 @@ +module.exports = function(app) { + app.use(require('express').static(__dirname + '/../')); + app.get('/', function(req, res) { + res.redirect('/test/'); + }) +}; diff --git a/tasks/browser.js b/tasks/browser.js deleted file mode 100644 index 9a921e2..0000000 --- a/tasks/browser.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = function(grunt) { - grunt.registerMultiTask('browser', 'Export the object in <%= pkg.name %> to the window', function() { - this.files.forEach(function(f) { - var output = ['(function() {']; - output.push.apply(output, f.src.map(grunt.file.read)); - output.push("requireModule('promise/polyfill').polyfill();"); - output.push('}());'); - - grunt.file.write(f.dest, grunt.template.process(output.join('\n'))); - }); - }); -}; diff --git a/tasks/build_tests.js b/tasks/build_tests.js deleted file mode 100644 index 397511b..0000000 --- a/tasks/build_tests.js +++ /dev/null @@ -1,31 +0,0 @@ -function nameFor(path) { - var result, match; - if (match = path.match(/^(?:lib|test|test\/tests)\/(.*?)(?:\.js)?$/)) { - result = match[1]; - } else { - result = path; - } - - return path; -} - -module.exports = function(grunt) { - grunt.registerMultiTask('buildTests', 'Execute the tests', function() { - var testFiles = grunt.file.expand('test/tests/**/*_test.js'); - - this.files.forEach(function(f) { - var output = ["(function(globals) {"]; - - output.push.apply(output, f.src.map(grunt.file.read)); - - testFiles.forEach(function(file) { - var moduleName = nameFor(file); - output.push('requireModule("' + nameFor(file) + '");'); - }); - - output.push('})(window);'); - - grunt.file.write(f.dest, output.join('\n')); - }); - }); -}; diff --git a/tasks/options/browser.js b/tasks/options/browser.js deleted file mode 100644 index 8970e32..0000000 --- a/tasks/options/browser.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - dist: { - src: 'tmp/promise.browser1.js', - dest: 'dist/promise-<%= pkg.version %>.js' - }, - distNoVersion: { - src: 'tmp/promise.browser1.js', - dest: 'dist/promise.js' - } -}; diff --git a/tasks/options/browserify.js b/tasks/options/browserify.js deleted file mode 100644 index 77394b3..0000000 --- a/tasks/options/browserify.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - tests: { - src: ['test/test-adapter.js', - 'node_modules/promises-aplus-tests/node_modules/sinon/lib/{sinon.js,sinon/*.js}', - 'node_modules/promises-aplus-tests/lib/tests/**/*.js'], - dest: 'tmp/tests-bundle.js' - } -}; diff --git a/tasks/options/buildTests.js b/tasks/options/buildTests.js deleted file mode 100644 index 5f530a2..0000000 --- a/tasks/options/buildTests.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - dist: { - src: [ - 'node_modules/grunt-microlib/assets/loader.js', - 'tmp/tests.amd.js', - 'tmp/<%= pkg.name %>/**/*.amd.js', - 'tmp/<%= pkg.name %>.amd.js' - ], - dest: 'tmp/tests.js' - } -}; diff --git a/tasks/options/clean.js b/tasks/options/clean.js deleted file mode 100644 index aae809d..0000000 --- a/tasks/options/clean.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - build: ['tmp', 'dist'] -}; diff --git a/tasks/options/concat.js b/tasks/options/concat.js deleted file mode 100644 index 987cbbf..0000000 --- a/tasks/options/concat.js +++ /dev/null @@ -1,27 +0,0 @@ -module.exports = { - amd: { - src: ['tmp/promise/**/*.amd.js', 'tmp/promise.amd.js'], - dest: 'dist/promise-<%= pkg.version %>.amd.js', - options: { - banner: '/**\n' + - ' @class RSVP\n' + - ' @module RSVP\n' + - ' */\n' - } - }, - - amdNoVersion: { - src: ['tmp/promise/**/*.amd.js', 'tmp/promise.amd.js'], - dest: 'dist/promise.amd.js' - }, - - deps: { - src: ['vendor/deps/*.js'], - dest: 'tmp/deps.amd.js' - }, - - browser: { - src: ['vendor/loader.js', 'tmp/promise/**/*.amd.js', 'tmp/promise.amd.js'], - dest: 'tmp/promise.browser1.js' - } -}; diff --git a/tasks/options/connect.js b/tasks/options/connect.js deleted file mode 100644 index 571f0a6..0000000 --- a/tasks/options/connect.js +++ /dev/null @@ -1,23 +0,0 @@ -module.exports = { - server: {}, - - options: { - hostname: '0.0.0.0', - port: (process.env.PORT || 8000), - base: '.', - middleware: function(connect, options) { - return [ - require('connect-redirection')(), - function(req, res, next) { - if (req.url === '/') { - res.redirect('/test'); - } else { - next(); - } - }, - connect.static(options.base) - ]; - } - - } -}; diff --git a/tasks/options/jshint.js b/tasks/options/jshint.js deleted file mode 100644 index 4b513bd..0000000 --- a/tasks/options/jshint.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - options: { - 'jshintrc': '.jshintrc' - }, - output: { - src: ['dist/promise-<%= pkg.version %>.js'] - } -}; diff --git a/tasks/options/mochaTest.js b/tasks/options/mochaTest.js deleted file mode 100644 index 6ee1482..0000000 --- a/tasks/options/mochaTest.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = { - test: { - src: [ - 'test/vendor/assert.js', - 'test/test-adapter.js', - 'node_modules/promises-aplus-tests/lib/tests/**/*.js', - 'tmp/tests.cjs.js' - ], - options: { - reporter: 'spec' - } - } -}; diff --git a/tasks/options/mocha_phantomjs.js b/tasks/options/mocha_phantomjs.js deleted file mode 100644 index 7406eed..0000000 --- a/tasks/options/mocha_phantomjs.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - phantom: { - options: { - urls: ['test/index.html'] - } - } -}; diff --git a/tasks/options/s3.js b/tasks/options/s3.js deleted file mode 100644 index cd8a097..0000000 --- a/tasks/options/s3.js +++ /dev/null @@ -1,19 +0,0 @@ -// the base for dist files -var baseDistFile = 'dist/<%= pkg.name %>-<%= pkg.version %>.'; -var builds = ['amd.', '' /* normal rsvp.js */ ]; -var s3Uploads = []; -builds.forEach(function(build){ - var srcFile = baseDistFile + build + 'js'; - s3Uploads.push({ src: srcFile, dest: 'rsvp-<%= env.TRAVIS_COMMIT %>.' + build + 'js' }); - s3Uploads.push({ src: srcFile, dest: 'rsvp-latest.' + build + 'js' }); -}); - -module.exports = { - options: { - bucket: 'rsvpjs-builds', - access: 'public-read' - }, - dev: { - upload: s3Uploads - } -}; diff --git a/tasks/options/transpile.js b/tasks/options/transpile.js deleted file mode 100644 index f126881..0000000 --- a/tasks/options/transpile.js +++ /dev/null @@ -1,54 +0,0 @@ -function nameFor(path) { - var result, match; - if (match = path.match(/^(?:lib|test|test\/tests)\/(.*?)(?:\.js)?$/)) { - result = match[1]; - } else { - result = path; - } - - return path; -} - -module.exports = { - amd: { - moduleName: nameFor, - type: 'amd', - files: [{ - expand: true, - cwd: 'lib/', - src: ['**/*.js'], - dest: 'tmp/', - ext: '.amd.js' - }] - }, - - commonjs: { - moduleName: nameFor, - type: 'cjs', - files: [{ - expand: true, - cwd: 'lib/', - src: ['promise/*.js'], - dest: 'dist/commonjs/', - ext: '.js' - }, - { - src: ['lib/*.js'], - dest: 'dist/commonjs/main.js' - }] - }, - - testsAmd: { - moduleName: nameFor, - type: 'amd', - src: ['test/test_helpers.js', 'test/tests.js', 'test/tests/**/*_test.js'], - dest: 'tmp/tests.amd.js' - }, - - testsCommonjs: { - moduleName: nameFor, - type: 'cjs', - src: ['test/test_helpers.js', 'test/tests.js', 'test/tests/**/*_test.js'], - dest: 'tmp/tests.cjs.js' - } -}; diff --git a/tasks/options/uglify.js b/tasks/options/uglify.js deleted file mode 100644 index 9347f19..0000000 --- a/tasks/options/uglify.js +++ /dev/null @@ -1,18 +0,0 @@ -module.exports = { - browser: { - options: { - mangle: true - }, - files: { - 'dist/promise-<%= pkg.version %>.min.js': ['dist/promise-<%= pkg.version %>.js'], - } - }, - browserNoVersion: { - options: { - mangle: true - }, - files: { - 'dist/promise.min.js': ['dist/promise.js'], - } - } -}; diff --git a/tasks/options/watch.js b/tasks/options/watch.js deleted file mode 100644 index 1f71369..0000000 --- a/tasks/options/watch.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - server: { - files: ['lib/**', 'vendor/*', 'test/**/*'], - tasks: ['build', 'tests'] - }, -}; diff --git a/tasks/options/yuidoc.js b/tasks/options/yuidoc.js deleted file mode 100644 index a3617ba..0000000 --- a/tasks/options/yuidoc.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = { - compile: { - name: '<%= pkg.name %>', - description: '<%= pkg.description %>', - version: '<%= pkg.version %>', - url: '<%= pkg.homepage %>', - options: { - paths: 'lib', - outdir: 'docs' - } - } -}; diff --git a/test/index.html b/test/index.html index 8572763..6d1f3c1 100644 --- a/test/index.html +++ b/test/index.html @@ -2,19 +2,26 @@ rsvp.js Tests - +
- + + - - - - + + + + diff --git a/test/main.js b/test/main.js new file mode 100644 index 0000000..427803f --- /dev/null +++ b/test/main.js @@ -0,0 +1,19 @@ +require('./test-adapter.js'); +require('./tests/extension-test.js'); +require('../node_modules/promises-aplus-tests/lib/tests/2.1.2.js'); +require('../node_modules/promises-aplus-tests/lib/tests/2.1.3.js'); +require('../node_modules/promises-aplus-tests/lib/tests/2.2.1.js'); +require('../node_modules/promises-aplus-tests/lib/tests/2.2.2.js'); +require('../node_modules/promises-aplus-tests/lib/tests/2.2.3.js'); +require('../node_modules/promises-aplus-tests/lib/tests/2.2.4.js'); +require('../node_modules/promises-aplus-tests/lib/tests/2.2.5.js'); +require('../node_modules/promises-aplus-tests/lib/tests/2.2.6.js'); +require('../node_modules/promises-aplus-tests/lib/tests/2.2.7.js'); +require('../node_modules/promises-aplus-tests/lib/tests/2.3.1.js'); +require('../node_modules/promises-aplus-tests/lib/tests/2.3.2.js'); +require('../node_modules/promises-aplus-tests/lib/tests/2.3.3.js'); +require('../node_modules/promises-aplus-tests/lib/tests/2.3.4.js'); +require('../node_modules/promises-aplus-tests/lib/tests/helpers/helpers.js'); +require('../node_modules/promises-aplus-tests/lib/tests/helpers/reasons.js'); +require('../node_modules/promises-aplus-tests/lib/tests/helpers/testThreeCases.js'); +require('../node_modules/promises-aplus-tests/lib/tests/helpers/thenables.js'); diff --git a/test/test-adapter.js b/test/test-adapter.js index 0107029..6d4b6df 100644 --- a/test/test-adapter.js +++ b/test/test-adapter.js @@ -1,48 +1,26 @@ -/*global RSVP*/ +var assert = require('assert'); -var resolve, reject; +var Promise = require('../dist/es6-promise').Promise; -function bind(func, thisVal) { - if (func.bind) { - return func.bind(thisVal); - } - return function() { - return func.apply(thisVal, arguments); - }; -} +function defer() { + var deferred = {}; -if (typeof Promise !== 'undefined') { - // Test the browser build - resolve = bind(Promise.resolve, Promise); - reject = bind(Promise.reject, Promise); -} else { - // Test the Node build - Promise = require('../dist/commonjs/main').Promise; - assert = require('./vendor/assert'); - resolve = bind(Promise.resolve, Promise); - reject = bind(Promise.reject, Promise); -} + deferred.promise = new Promise(function(resolve, reject) { + deferred.resolve = resolve; + deferred.reject = reject; + }); -if (typeof window === 'undefined' && typeof global !== 'undefined') { - window = global; + return deferred; } +var resolve = Promise.resolve; +var reject = Promise.reject; + +var g = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : this; -module.exports = global.adapter = { +module.exports = g.adapter = { resolved: resolve, rejected: reject, - deferred: function defer() { - var deferred = { - // pre-allocate shape - resolve: undefined, - reject: undefined, - promise: undefined - }; - - deferred.promise = new Promise(function(resolve, reject) { - deferred.resolve = resolve; - deferred.reject = reject; - }); - - return deferred; - } + deferred: defer, + Promise: Promise }; + diff --git a/test/tests/extension-test.js b/test/tests/extension-test.js new file mode 100644 index 0000000..063b7be --- /dev/null +++ b/test/tests/extension-test.js @@ -0,0 +1,1006 @@ +/*global describe, specify, it, assert */ + +if (typeof Object.getPrototypeOf !== "function") { + Object.getPrototypeOf = "".__proto__ === String.prototype + ? function (object) { + return object.__proto__; + } + : function (object) { + // May break if the constructor has been tampered with + return object.constructor.prototype; + }; +} + +function keysOf(object) { + var results = []; + + for (var key in object) { + if (object.hasOwnProperty(key)) { + results.push(key); + } + } + + return results; +} + +var g = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : this; +var Promise = g.adapter.Promise; +var assert = require('assert'); + +var o_create = Object.create || function(o, props) { + function F() {} + F.prototype = o; + + if (typeof(props) === "object") { + for (var prop in props) { + if (props.hasOwnProperty((prop))) { + F[prop] = props[prop]; + } + } + } + return new F(); +}; + +function objectEquals(obj1, obj2) { + for (var i in obj1) { + if (obj1.hasOwnProperty(i)) { + if (!obj2.hasOwnProperty(i)) return false; + if (obj1[i] != obj2[i]) return false; + } + } + for (var i in obj2) { + if (obj2.hasOwnProperty(i)) { + if (!obj1.hasOwnProperty(i)) return false; + if (obj1[i] != obj2[i]) return false; + } + } + return true; +} + +describe("extensions", function() { + describe("Promise constructor", function() { + it('should exist and have length 2', function() { + assert(Promise); + assert.equal(Promise.length, 2); + }); + + it('should fulfill if `resolve` is called with a value', function(done) { + var promise = new Promise(function(resolve) { resolve('value'); }); + + promise.then(function(value) { + assert.equal(value, 'value'); + done(); + }); + }); + + it('should reject if `reject` is called with a reason', function(done) { + var promise = new Promise(function(resolve, reject) { reject('reason'); }); + + promise.then(function() { + assert(false); + done(); + }, function(reason) { + assert.equal(reason, 'reason'); + done(); + }); + }); + + it('should be a constructor', function() { + var promise = new Promise(function() {}); + + assert.equal(Object.getPrototypeOf(promise), Promise.prototype, '[[Prototype]] equals Promise.prototype'); + assert.equal(promise.constructor, Promise, 'constructor property of instances is set correctly'); + assert.equal(Promise.prototype.constructor, Promise, 'constructor property of prototype is set correctly'); + }); + + it('should NOT work without `new`', function() { + assert.throws(function(){ + Promise(function(resolve) { resolve('value'); }); + }, TypeError) + }); + + it('should throw a `TypeError` if not given a function', function() { + assert.throws(function () { + new Promise(); + }, TypeError); + + assert.throws(function () { + new Promise({}); + }, TypeError); + + assert.throws(function () { + new Promise('boo!'); + }, TypeError); + }); + + it('should reject on resolver exception', function(done) { + new Promise(function() { + throw 'error'; + }).then(null, function(e) { + assert.equal(e, 'error'); + done(); + }); + }); + + it('should not resolve multiple times', function(done) { + var resolver, rejector, fulfilled = 0, rejected = 0; + var thenable = { + then: function(resolve, reject) { + resolver = resolve; + rejector = reject; + } + }; + + var promise = new Promise(function(resolve) { + resolve(1); + }); + + promise.then(function(value){ + return thenable; + }).then(function(value){ + fulfilled++; + }, function(reason) { + rejected++; + }); + + setTimeout(function() { + resolver(1); + resolver(1); + rejector(1); + rejector(1); + + setTimeout(function() { + assert.equal(fulfilled, 1); + assert.equal(rejected, 0); + done(); + }, 20); + }, 20); + + }); + + describe('assimilation', function() { + it('should assimilate if `resolve` is called with a fulfilled promise', function(done) { + var originalPromise = new Promise(function(resolve) { resolve('original value'); }); + var promise = new Promise(function(resolve) { resolve(originalPromise); }); + + promise.then(function(value) { + assert.equal(value, 'original value'); + done(); + }); + }); + + it('should assimilate if `resolve` is called with a rejected promise', function(done) { + var originalPromise = new Promise(function(resolve, reject) { reject('original reason'); }); + var promise = new Promise(function(resolve) { resolve(originalPromise); }); + + promise.then(function() { + assert(false); + done(); + }, function(reason) { + assert.equal(reason, 'original reason'); + done(); + }); + }); + + it('should assimilate if `resolve` is called with a fulfilled thenable', function(done) { + var originalThenable = { + then: function (onFulfilled) { + setTimeout(function() { onFulfilled('original value'); }, 0); + } + }; + var promise = new Promise(function(resolve) { resolve(originalThenable); }); + + promise.then(function(value) { + assert.equal(value, 'original value'); + done(); + }); + }); + + it('should assimilate if `resolve` is called with a rejected thenable', function(done) { + var originalThenable = { + then: function (onFulfilled, onRejected) { + setTimeout(function() { onRejected('original reason'); }, 0); + } + }; + var promise = new Promise(function(resolve) { resolve(originalThenable); }); + + promise.then(function() { + assert(false); + done(); + }, function(reason) { + assert.equal(reason, 'original reason'); + done(); + }); + }); + + + it('should assimilate two levels deep, for fulfillment of self fulfilling promises', function(done) { + var originalPromise, promise; + originalPromise = new Promise(function(resolve) { + setTimeout(function() { + resolve(originalPromise); + }, 0) + }); + + promise = new Promise(function(resolve) { + setTimeout(function() { + resolve(originalPromise); + }, 0); + }); + + promise.then(function(value) { + assert.equal(value, originalPromise); + done(); + }); + }); + + it('should assimilate two levels deep, for fulfillment', function(done) { + var originalPromise = new Promise(function(resolve) { resolve('original value'); }); + var nextPromise = new Promise(function(resolve) { resolve(originalPromise); }); + var promise = new Promise(function(resolve) { resolve(nextPromise); }); + + promise.then(function(value) { + assert.equal(value, 'original value'); + done(); + }); + }); + + it('should assimilate two levels deep, for rejection', function(done) { + var originalPromise = new Promise(function(resolve, reject) { reject('original reason'); }); + var nextPromise = new Promise(function(resolve) { resolve(originalPromise); }); + var promise = new Promise(function(resolve) { resolve(nextPromise); }); + + promise.then(function() { + assert(false); + done(); + }, function(reason) { + assert.equal(reason, 'original reason'); + done(); + }); + }); + + it('should assimilate three levels deep, mixing thenables and promises (fulfilled case)', function(done) { + var originalPromise = new Promise(function(resolve) { resolve('original value'); }); + var intermediateThenable = { + then: function (onFulfilled) { + setTimeout(function() { onFulfilled(originalPromise); }, 0); + } + }; + var promise = new Promise(function(resolve) { resolve(intermediateThenable); }); + + promise.then(function(value) { + assert.equal(value, 'original value'); + done(); + }); + }); + + it('should assimilate three levels deep, mixing thenables and promises (rejected case)', function(done) { + var originalPromise = new Promise(function(resolve, reject) { reject('original reason'); }); + var intermediateThenable = { + then: function (onFulfilled) { + setTimeout(function() { onFulfilled(originalPromise); }, 0); + } + }; + var promise = new Promise(function(resolve) { resolve(intermediateThenable); }); + + promise.then(function() { + assert(false); + done(); + }, function(reason) { + assert.equal(reason, 'original reason'); + done(); + }); + }); + }); + }); + + describe("Promise.all", function() { + testAll(function(){ + return Promise.all.apply(Promise, arguments); + }); + }); + + function testAll(all) { + it('should exist', function() { + assert(all); + }); + + it('throws when not passed an array', function(done) { + var nothing = assertRejection(all()); + var string = assertRejection(all('')); + var object = assertRejection(all({})); + + Promise.all([ + nothing, + string, + object + ]).then(function(){ done(); }); + }); + + specify('fulfilled only after all of the other promises are fulfilled', function(done) { + var firstResolved, secondResolved, firstResolver, secondResolver; + + var first = new Promise(function(resolve) { + firstResolver = resolve; + }); + first.then(function() { + firstResolved = true; + }); + + var second = new Promise(function(resolve) { + secondResolver = resolve; + }); + second.then(function() { + secondResolved = true; + }); + + setTimeout(function() { + firstResolver(true); + }, 0); + + setTimeout(function() { + secondResolver(true); + }, 0); + + all([first, second]).then(function() { + assert(firstResolved); + assert(secondResolved); + done(); + }); + }); + + specify('rejected as soon as a promise is rejected', function(done) { + var firstResolver, secondResolver; + + var first = new Promise(function(resolve, reject) { + firstResolver = { resolve: resolve, reject: reject }; + }); + + var second = new Promise(function(resolve, reject) { + secondResolver = { resolve: resolve, reject: reject }; + }); + + setTimeout(function() { + firstResolver.reject({}); + }, 0); + + var firstWasRejected, secondCompleted; + + first['catch'](function(){ + firstWasRejected = true; + }); + + second.then(function(){ + secondCompleted = true; + }, function() { + secondCompleted = true; + }); + + all([first, second]).then(function() { + assert(false); + }, function() { + assert(firstWasRejected); + assert(!secondCompleted); + done(); + }); + }); + + specify('passes the resolved values of each promise to the callback in the correct order', function(done) { + var firstResolver, secondResolver, thirdResolver; + + var first = new Promise(function(resolve, reject) { + firstResolver = { resolve: resolve, reject: reject }; + }); + + var second = new Promise(function(resolve, reject) { + secondResolver = { resolve: resolve, reject: reject }; + }); + + var third = new Promise(function(resolve, reject) { + thirdResolver = { resolve: resolve, reject: reject }; + }); + + thirdResolver.resolve(3); + firstResolver.resolve(1); + secondResolver.resolve(2); + + all([first, second, third]).then(function(results) { + assert(results.length === 3); + assert(results[0] === 1); + assert(results[1] === 2); + assert(results[2] === 3); + done(); + }); + }); + + specify('resolves an empty array passed to all()', function(done) { + all([]).then(function(results) { + assert(results.length === 0); + done(); + }); + }); + + specify('works with null', function(done) { + all([null]).then(function(results) { + assert.equal(results[0], null); + done(); + }); + }); + + specify('works with a mix of promises and thenables and non-promises', function(done) { + var promise = new Promise(function(resolve) { resolve(1); }); + var syncThenable = { then: function (onFulfilled) { onFulfilled(2); } }; + var asyncThenable = { then: function (onFulfilled) { setTimeout(function() { onFulfilled(3); }, 0); } }; + var nonPromise = 4; + + all([promise, syncThenable, asyncThenable, nonPromise]).then(function(results) { + assert(objectEquals(results, [1, 2, 3, 4])); + done(); + })['catch'](done); + }); + } + + describe("reject", function(){ + specify("it should exist", function(){ + assert(Promise.reject); + }); + + describe('it rejects', function(){ + var reason = 'the reason', + promise = Promise.reject(reason); + + promise.then(function(){ + assert(false, 'should not fulfill'); + }, function(actualReason){ + assert.equal(reason, actualReason); + }); + }); + }); + + function assertRejection(promise) { + return promise.then(function(){ + assert(false, 'expected rejection, but got fulfillment'); + }, function(reason){ + assert(reason instanceof Error); + }); + } + + function testRace(race) { + it("should exist", function() { + assert(race); + }); + + it("throws when not passed an array", function(done) { + var nothing = assertRejection(race()); + var string = assertRejection(race('')); + var object = assertRejection(race({})); + + Promise.all([ + nothing, + string, + object + ]).then(function(){ done(); }); + }); + + specify('fulfilled after one of the other promises are fulfilled', function(done) { + var firstResolved, secondResolved, firstResolver, secondResolver; + + var first = new Promise(function(resolve) { + firstResolver = resolve; + }); + first.then(function() { + firstResolved = true; + }); + + var second = new Promise(function(resolve) { + secondResolver = resolve; + }); + second.then(function() { + secondResolved = true; + }); + + setTimeout(function() { + firstResolver(true); + }, 100); + + setTimeout(function() { + secondResolver(true); + }, 0); + + race([first, second]).then(function() { + assert(secondResolved); + assert.equal(firstResolved, undefined); + done(); + }); + }); + + specify('the race begins on nextTurn and prioritized by array entry', function(done) { + var firstResolver, secondResolver, nonPromise = 5; + + var first = new Promise(function(resolve, reject) { + resolve(true); + }); + + var second = new Promise(function(resolve, reject) { + resolve(false); + }); + + race([first, second, nonPromise]).then(function(value) { + assert.equal(value, true); + done(); + }); + }); + + specify('rejected as soon as a promise is rejected', function(done) { + var firstResolver, secondResolver; + + var first = new Promise(function(resolve, reject) { + firstResolver = { resolve: resolve, reject: reject }; + }); + + var second = new Promise(function(resolve, reject) { + secondResolver = { resolve: resolve, reject: reject }; + }); + + setTimeout(function() { + firstResolver.reject({}); + }, 0); + + var firstWasRejected, secondCompleted; + + first['catch'](function(){ + firstWasRejected = true; + }); + + second.then(function(){ + secondCompleted = true; + }, function() { + secondCompleted = true; + }); + + race([first, second]).then(function() { + assert(false); + }, function() { + assert(firstWasRejected); + assert(!secondCompleted); + done(); + }); + }); + + specify('resolves an empty array to forever pending Promise', function(done) { + var foreverPendingPromise = race([]), + wasSettled = false; + + foreverPendingPromise.then(function() { + wasSettled = true; + }, function() { + wasSettled = true; + }); + + setTimeout(function() { + assert(!wasSettled); + done(); + }, 100); + }); + + specify('works with a mix of promises and thenables', function(done) { + var promise = new Promise(function(resolve) { setTimeout(function() { resolve(1); }, 10); }), + syncThenable = { then: function (onFulfilled) { onFulfilled(2); } }; + + race([promise, syncThenable]).then(function(result) { + assert(result, 2); + done(); + }); + }); + + specify('works with a mix of thenables and non-promises', function (done) { + var asyncThenable = { then: function (onFulfilled) { setTimeout(function() { onFulfilled(3); }, 0); } }, + nonPromise = 4; + race([asyncThenable, nonPromise]).then(function(result) { + assert(result, 4); + done(); + }); + }); + } + + describe("race", function() { + testRace(race); + }); + + describe("Promise.race", function() { + testRace(function(){ + return Promise.race.apply(Promise, arguments); + }); + }); + + describe("resolve", function(){ + specify("it should exist", function(){ + assert(Promise.resolve); + }); + + describe("1. If x is a promise, adopt its state ", function(){ + specify("1.1 If x is pending, promise must remain pending until x is fulfilled or rejected.", function(done){ + var expectedValue, resolver, thenable, wrapped; + + expectedValue = 'the value'; + thenable = { + then: function(resolve, reject){ + resolver = resolve; + } + }; + + wrapped = resolve(thenable); + + wrapped.then(function(value){ + assert(value === expectedValue); + done(); + }); + + setTimeout(function(){ + resolver(expectedValue); + }, 10); + }); + + specify("1.2 If/when x is fulfilled, fulfill promise with the same value.", function(done){ + var expectedValue, thenable, wrapped; + + expectedValue = 'the value'; + thenable = { + then: function(resolve, reject){ + resolve(expectedValue); + } + }; + + wrapped = resolve(thenable); + + wrapped.then(function(value){ + assert(value === expectedValue); + done(); + }) + }); + + specify("1.3 If/when x is rejected, reject promise with the same reason.", function(done){ + var expectedError, thenable, wrapped; + + expectedError = new Error(); + thenable = { + then: function(resolve, reject){ + reject(expectedError); + } + }; + + wrapped = resolve(thenable); + + wrapped.then(null, function(error){ + assert(error === expectedError); + done(); + }); + }); + }); + + describe("2. Otherwise, if x is an object or function,", function(){ + specify("2.1 Let then x.then", function(done){ + var accessCount, resolver, wrapped, thenable; + + accessCount = 0; + thenable = { }; + + // we likely don't need to test this, if the browser doesn't support it + if (typeof Object.defineProperty !== "function") { done(); return; } + + Object.defineProperty(thenable, 'then', { + get: function(){ + accessCount++; + + if (accessCount > 1) { + throw new Error(); + } + + return function(){ }; + } + }); + + assert(accessCount === 0); + + wrapped = resolve(thenable); + + assert(accessCount === 1); + + done(); + }); + + specify("2.2 If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.", function(done){ + var wrapped, thenable, expectedError; + + expectedError = new Error(); + thenable = { }; + + // we likely don't need to test this, if the browser doesn't support it + if (typeof Object.defineProperty !== "function") { done(); return; } + + Object.defineProperty(thenable, 'then', { + get: function(){ + throw expectedError; + } + }); + + wrapped = resolve(thenable); + + wrapped.then(null, function(error){ + assert(error === expectedError, 'incorrect exception was thrown'); + done(); + }); + }); + + describe('2.3. If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise, where', function(){ + specify('2.3.1 If/when resolvePromise is called with a value y, run Resolve(promise, y)', function(done){ + var expectedSuccess, resolver, rejector, thenable, wrapped, calledThis; + + thenable = { + then: function(resolve, reject){ + calledThis = this; + resolver = resolve; + rejector = reject; + } + }; + + expectedSuccess = 'success'; + wrapped = resolve(thenable); + + wrapped.then(function(success){ + assert(calledThis === thenable, 'this must be the thenable'); + assert(success === expectedSuccess, 'rejected promise with x'); + done(); + }); + + setTimeout(function() { + resolver(expectedSuccess); + }, 20); + }); + + specify('2.3.2 If/when rejectPromise is called with a reason r, reject promise with r.', function(done){ + var expectedError, resolver, rejector, thenable, wrapped, calledThis; + + thenable = { + then: function(resolve, reject){ + calledThis = this; + resolver = resolve; + rejector = reject; + } + }; + + expectedError = new Error(); + + wrapped = resolve(thenable); + + wrapped.then(null, function(error){ + assert(error === expectedError, 'rejected promise with x'); + done(); + }); + + setTimeout(function() { + rejector(expectedError); + }, 20); + }); + + specify("2.3.3 If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored", function(done){ + var expectedError, expectedSuccess, resolver, rejector, thenable, wrapped, calledThis, + calledRejected, calledResolved; + + calledRejected = 0; + calledResolved = 0; + + thenable = { + then: function(resolve, reject){ + calledThis = this; + resolver = resolve; + rejector = reject; + } + }; + + expectedError = new Error(); + + wrapped = resolve(thenable); + + wrapped.then(function(){ + calledResolved++; + }, function(error){ + calledRejected++; + assert(calledResolved === 0, 'never resolved'); + assert(calledRejected === 1, 'rejected only once'); + assert(error === expectedError, 'rejected promise with x'); + }); + + setTimeout(function() { + rejector(expectedError); + rejector(expectedError); + + rejector('foo'); + + resolver('bar'); + resolver('baz'); + }, 20); + + setTimeout(function(){ + assert(calledRejected === 1, 'only rejected once'); + assert(calledResolved === 0, 'never resolved'); + done(); + }, 50); + }); + + describe("2.3.4 If calling then throws an exception e", function(){ + specify("2.3.4.1 If resolvePromise or rejectPromise have been called, ignore it.", function(done){ + var expectedSuccess, resolver, rejector, thenable, wrapped, calledThis, + calledRejected, calledResolved; + + expectedSuccess = 'success'; + + thenable = { + then: function(resolve, reject){ + resolve(expectedSuccess); + throw expectedError; + } + }; + + wrapped = resolve(thenable); + + wrapped.then(function(success){ + assert(success === expectedSuccess, 'resolved not errored'); + done(); + }); + }); + + specify("2.3.4.2 Otherwise, reject promise with e as the reason.", function(done) { + var expectedError, resolver, rejector, thenable, wrapped, calledThis, callCount; + + expectedError = new Error(); + callCount = 0; + + thenable = { then: function() { throw expectedError; } }; + + wrapped = resolve(thenable); + + wrapped.then(null, function(error){ + callCount++; + assert(expectedError === error, 'expected the correct error to be rejected'); + done(); + }); + + assert(callCount === 0, 'expected async, was sync'); + }); + }); + }); + + specify("2.4 If then is not a function, fulfill promise with x", function(done){ + var expectedError, resolver, rejector, thenable, wrapped, calledThis, callCount; + + thenable = { then: 3 }; + callCount = 0; + wrapped = resolve(thenable); + + wrapped.then(function(success){ + callCount++; + assert(thenable === success, 'fulfilled promise with x'); + done(); + }); + + assert(callCount === 0, 'expected async, was sync'); + }); + }); + + describe("3. If x is not an object or function, ", function(){ + specify("fulfill promise with x.", function(done){ + var thenable, callCount, wrapped; + + thenable = null; + callCount = 0; + wrapped = resolve(thenable); + + wrapped.then(function(success){ + callCount++; + assert(success === thenable, 'fulfilled promise with x'); + done(); + }, function(a){ + assert(false, 'should not also reject'); + }); + + assert(callCount === 0, 'expected async, was sync'); + }); + }); + }); + + if (typeof Worker !== 'undefined') { + describe('web worker', function () { + it('should work', function (done) { + var worker = new Worker('tests/worker.js'); + worker.addEventListener('error', function(reason) { + done(new Error("Test failed:" + reason)); + }); + worker.addEventListener('message', function (e) { + worker.terminate(); + assert.equal(e.data, 'pong'); + done(); + }); + worker.postMessage('ping'); + }); + }); + } +}); + +// thanks to @wizardwerdna for the test case -> https://github.com/tildeio/rsvp.js/issues/66 +// Only run these tests in node (phantomjs cannot handle them) +if (typeof module !== 'undefined' && module.exports) { + + describe("using reduce to sum integers using promises", function(){ + var resolve = resolve; + + it("should build the promise pipeline without error", function(){ + var array, iters, pZero, i; + + array = []; + iters = 1000; + + for (i=1; i<=iters; i++) { + array.push(i); + } + + pZero = resolve(0); + + array.reduce(function(promise, nextVal) { + return promise.then(function(currentVal) { + return resolve(currentVal + nextVal); + }); + }, pZero); + }); + + it("should get correct answer without blowing the nextTick stack", function(done){ + var pZero, array, iters, result, i; + + pZero = resolve(0); + + array = []; + iters = 1000; + + for (i=1; i<=iters; i++) { + array.push(i); + } + + result = array.reduce(function(promise, nextVal) { + return promise.then(function(currentVal) { + return resolve(currentVal + nextVal); + }); + }, pZero); + + result.then(function(value){ + assert.equal(value, (iters*(iters+1)/2)); + done(); + }); + }); + }); +} + +// Kudos to @Octane at https://github.com/getify/native-promise-only/issues/5 for this, and @getify for pinging me. +describe("Thenables should not be able to run code during assimilation", function () { + specify("resolving to a thenable", function () { + var thenCalled = false; + var thenable = { + then: function () { + thenCalled = true; + } + }; + + Promise.resolve(thenable); + assert.strictEqual(thenCalled, false); + }); + + specify("resolving to an evil promise", function () { + var thenCalled = false; + var evilPromise = Promise.resolve(); + evilPromise.then = function () { + thenCalled = true; + }; + + Promise.resolve(evilPromise); + assert.strictEqual(thenCalled, false); + }); +}); diff --git a/test/tests/worker.js b/test/tests/worker.js new file mode 100644 index 0000000..9826c6b --- /dev/null +++ b/test/tests/worker.js @@ -0,0 +1,16 @@ +importScripts('../../dist/rsvp.js'); +new RSVP.Promise(function(resolve, reject) { + self.onmessage = function (e) { + if (e.data === 'ping') { + resolve('pong'); + } else { + reject(new Error('wrong message')); + } + }; +}).then(function (result) { + self.postMessage(result); +}, function (err){ + setTimeout(function () { + throw err; + }); +}); diff --git a/test/vendor/assert.js b/test/vendor/assert.js deleted file mode 100644 index ced0425..0000000 --- a/test/vendor/assert.js +++ /dev/null @@ -1,370 +0,0 @@ -// http://wiki.commonjs.org/wiki/Unit_Testing/1.0 -// -// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8! -// -// Copyright (c) 2011 Jxck -// -// Originally from node.js (http://nodejs.org) -// Copyright Joyent, Inc. -// -// 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 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. - -(function(global) { - -// Object.create compatible in IE -var create = Object.create || function(p) { - if (!p) throw Error('no type'); - function f() {}; - f.prototype = p; - return new f(); -}; - -// UTILITY -var util = { - inherits: function(ctor, superCtor) { - ctor.super_ = superCtor; - ctor.prototype = create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: false, - writable: true, - configurable: true - } - }); - } -}; - -var pSlice = Array.prototype.slice; - -// 1. The assert module provides functions that throw -// AssertionError's when particular conditions are not met. The -// assert module must conform to the following interface. - -var assert = ok; - -global['assert'] = assert; - -if (typeof module === 'object' && typeof module.exports === 'object') { - module.exports = assert; -}; - -// 2. The AssertionError is defined in assert. -// new assert.AssertionError({ message: message, -// actual: actual, -// expected: expected }) - -assert.AssertionError = function AssertionError(options) { - this.name = 'AssertionError'; - this.message = options.message; - this.actual = options.actual; - this.expected = options.expected; - this.operator = options.operator; - var stackStartFunction = options.stackStartFunction || fail; - - if (Error.captureStackTrace) { - Error.captureStackTrace(this, stackStartFunction); - } -}; -util.inherits(assert.AssertionError, Error); - -function replacer(key, value) { - if (value === undefined) { - return '' + value; - } - if (typeof value === 'number' && (isNaN(value) || !isFinite(value))) { - return value.toString(); - } - if (typeof value === 'function' || value instanceof RegExp) { - return value.toString(); - } - return value; -} - -function truncate(s, n) { - if (typeof s == 'string') { - return s.length < n ? s : s.slice(0, n); - } else { - return s; - } -} - -assert.AssertionError.prototype.toString = function() { - if (this.message) { - return [this.name + ':', this.message].join(' '); - } else { - return [ - this.name + ':', - truncate(JSON.stringify(this.actual, replacer), 128), - this.operator, - truncate(JSON.stringify(this.expected, replacer), 128) - ].join(' '); - } -}; - -// assert.AssertionError instanceof Error - -assert.AssertionError.__proto__ = Error.prototype; - -// At present only the three keys mentioned above are used and -// understood by the spec. Implementations or sub modules can pass -// other keys to the AssertionError's constructor - they will be -// ignored. - -// 3. All of the following functions must throw an AssertionError -// when a corresponding condition is not met, with a message that -// may be undefined if not provided. All assertion methods provide -// both the actual and expected values to the assertion error for -// display purposes. - -function fail(actual, expected, message, operator, stackStartFunction) { - throw new assert.AssertionError({ - message: message, - actual: actual, - expected: expected, - operator: operator, - stackStartFunction: stackStartFunction - }); -} - -// EXTENSION! allows for well behaved errors defined elsewhere. -assert.fail = fail; - -// 4. Pure assertion tests whether a value is truthy, as determined -// by !!guard. -// assert.ok(guard, message_opt); -// This statement is equivalent to assert.equal(true, !!guard, -// message_opt);. To test strictly for the value true, use -// assert.strictEqual(true, guard, message_opt);. - -function ok(value, message) { - if (!!!value) fail(value, true, message, '==', assert.ok); -} -assert.ok = ok; - -// 5. The equality assertion tests shallow, coercive equality with -// ==. -// assert.equal(actual, expected, message_opt); - -assert.equal = function equal(actual, expected, message) { - if (actual != expected) fail(actual, expected, message, '==', assert.equal); -}; - -// 6. The non-equality assertion tests for whether two objects are not equal -// with != assert.notEqual(actual, expected, message_opt); - -assert.notEqual = function notEqual(actual, expected, message) { - if (actual == expected) { - fail(actual, expected, message, '!=', assert.notEqual); - } -}; - -// 7. The equivalence assertion tests a deep equality relation. -// assert.deepEqual(actual, expected, message_opt); - -assert.deepEqual = function deepEqual(actual, expected, message) { - if (!_deepEqual(actual, expected)) { - fail(actual, expected, message, 'deepEqual', assert.deepEqual); - } -}; - -function _deepEqual(actual, expected) { - // 7.1. All identical values are equivalent, as determined by ===. - if (actual === expected) { - return true; - -// } else if (Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { -// if (actual.length != expected.length) return false; -// -// for (var i = 0; i < actual.length; i++) { -// if (actual[i] !== expected[i]) return false; -// } -// -// return true; -// - // 7.2. If the expected value is a Date object, the actual value is - // equivalent if it is also a Date object that refers to the same time. - } else if (actual instanceof Date && expected instanceof Date) { - return actual.getTime() === expected.getTime(); - - // 7.3 If the expected value is a RegExp object, the actual value is - // equivalent if it is also a RegExp object with the same source and - // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`). - } else if (actual instanceof RegExp && expected instanceof RegExp) { - return actual.source === expected.source && - actual.global === expected.global && - actual.multiline === expected.multiline && - actual.lastIndex === expected.lastIndex && - actual.ignoreCase === expected.ignoreCase; - - // 7.4. Other pairs that do not both pass typeof value == 'object', - // equivalence is determined by ==. - } else if (typeof actual != 'object' && typeof expected != 'object') { - return actual == expected; - - // 7.5 For all other Object pairs, including Array objects, equivalence is - // determined by having the same number of owned properties (as verified - // with Object.prototype.hasOwnProperty.call), the same set of keys - // (although not necessarily the same order), equivalent values for every - // corresponding key, and an identical 'prototype' property. Note: this - // accounts for both named and indexed properties on Arrays. - } else { - return objEquiv(actual, expected); - } -} - -function isUndefinedOrNull(value) { - return value === null || value === undefined; -} - -function isArguments(object) { - return Object.prototype.toString.call(object) == '[object Arguments]'; -} - -function objEquiv(a, b) { - if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) - return false; - // an identical 'prototype' property. - if (a.prototype !== b.prototype) return false; - //~~~I've managed to break Object.keys through screwy arguments passing. - // Converting to array solves the problem. - if (isArguments(a)) { - if (!isArguments(b)) { - return false; - } - a = pSlice.call(a); - b = pSlice.call(b); - return _deepEqual(a, b); - } - try { - var ka = Object.keys(a), - kb = Object.keys(b), - key, i; - } catch (e) {//happens when one is a string literal and the other isn't - return false; - } - // having the same number of owned properties (keys incorporates - // hasOwnProperty) - if (ka.length != kb.length) - return false; - //the same set of keys (although not necessarily the same order), - ka.sort(); - kb.sort(); - //~~~cheap key test - for (i = ka.length - 1; i >= 0; i--) { - if (ka[i] != kb[i]) - return false; - } - //equivalent values for every corresponding key, and - //~~~possibly expensive deep test - for (i = ka.length - 1; i >= 0; i--) { - key = ka[i]; - if (!_deepEqual(a[key], b[key])) return false; - } - return true; -} - -// 8. The non-equivalence assertion tests for any deep inequality. -// assert.notDeepEqual(actual, expected, message_opt); - -assert.notDeepEqual = function notDeepEqual(actual, expected, message) { - if (_deepEqual(actual, expected)) { - fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual); - } -}; - -// 9. The strict equality assertion tests strict equality, as determined by ===. -// assert.strictEqual(actual, expected, message_opt); - -assert.strictEqual = function strictEqual(actual, expected, message) { - if (actual !== expected) { - fail(actual, expected, message, '===', assert.strictEqual); - } -}; - -// 10. The strict non-equality assertion tests for strict inequality, as -// determined by !==. assert.notStrictEqual(actual, expected, message_opt); - -assert.notStrictEqual = function notStrictEqual(actual, expected, message) { - if (actual === expected) { - fail(actual, expected, message, '!==', assert.notStrictEqual); - } -}; - -function expectedException(actual, expected) { - if (!actual || !expected) { - return false; - } - - if (expected instanceof RegExp) { - return expected.test(actual); - } else if (actual instanceof expected) { - return true; - } else if (expected.call({}, actual) === true) { - return true; - } - - return false; -} - -function _throws(shouldThrow, block, expected, message) { - var actual; - - if (typeof expected === 'string') { - message = expected; - expected = null; - } - - try { - block(); - } catch (e) { - actual = e; - } - - message = (expected && expected.name ? ' (' + expected.name + ').' : '.') + - (message ? ' ' + message : '.'); - - if (shouldThrow && !actual) { - fail('Missing expected exception' + message); - } - - if (!shouldThrow && expectedException(actual, expected)) { - fail('Got unwanted exception' + message); - } - - if ((shouldThrow && actual && expected && - !expectedException(actual, expected)) || (!shouldThrow && actual)) { - throw actual; - } -} - -// 11. Expected to throw an error: -// assert.throws(block, Error_opt, message_opt); - -assert.throws = function(block, /*optional*/error, /*optional*/message) { - _throws.apply(this, [true].concat(pSlice.call(arguments))); -}; - -// EXTENSION! This is annoying to write outside this module. -assert.doesNotThrow = function(block, /*optional*/error, /*optional*/message) { - _throws.apply(this, [false].concat(pSlice.call(arguments))); -}; - -assert.ifError = function(err) { if (err) {throw err;}}; - -})(this); - diff --git a/test/vendor/mocha.css b/test/vendor/mocha.css deleted file mode 100644 index 724ac3c..0000000 --- a/test/vendor/mocha.css +++ /dev/null @@ -1,227 +0,0 @@ -@charset "UTF-8"; -body { - font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; - padding: 60px 50px; -} - -#mocha ul, #mocha li { - margin: 0; - padding: 0; -} - -#mocha ul { - list-style: none; -} - -#mocha h1, #mocha h2 { - margin: 0; -} - -#mocha h1 { - margin-top: 15px; - font-size: 1em; - font-weight: 200; -} - -#mocha h1 a { - text-decoration: none; - color: inherit; -} - -#mocha h1 a:hover { - text-decoration: underline; -} - -#mocha .suite .suite h1 { - margin-top: 0; - font-size: .8em; -} - -.hidden { - display: none; -} - -#mocha h2 { - font-size: 12px; - font-weight: normal; - cursor: pointer; -} - -#mocha .suite { - margin-left: 15px; -} - -#mocha .test { - margin-left: 15px; -} - -#mocha .test.pending:hover h2::after { - content: '(pending)'; - font-family: arial; -} - -#mocha .test.pass.medium .duration { - background: #C09853; -} - -#mocha .test.pass.slow .duration { - background: #B94A48; -} - -#mocha .test.pass::before { - content: '✓'; - font-size: 12px; - display: block; - float: left; - margin-right: 5px; - color: #00d6b2; -} - -#mocha .test.pass .duration { - font-size: 9px; - margin-left: 5px; - padding: 2px 5px; - color: white; - -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); - -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); - box-shadow: inset 0 1px 1px rgba(0,0,0,.2); - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - -ms-border-radius: 5px; - -o-border-radius: 5px; - border-radius: 5px; -} - -#mocha .test.pass.fast .duration { - display: none; -} - -#mocha .test.pending { - color: #0b97c4; -} - -#mocha .test.pending::before { - content: '◦'; - color: #0b97c4; -} - -#mocha .test.fail { - color: #c00; -} - -#mocha .test.fail pre { - color: black; -} - -#mocha .test.fail::before { - content: '✖'; - font-size: 12px; - display: block; - float: left; - margin-right: 5px; - color: #c00; -} - -#mocha .test pre.error { - color: #c00; - max-height: 300px; - overflow: auto; -} - -#mocha .test pre { - display: inline-block; - font: 12px/1.5 monaco, monospace; - margin: 5px; - padding: 15px; - border: 1px solid #eee; - border-bottom-color: #ddd; - -webkit-border-radius: 3px; - -webkit-box-shadow: 0 1px 3px #eee; - -moz-border-radius: 3px; - -moz-box-shadow: 0 1px 3px #eee; -} - -#mocha .test h2 { - position: relative; -} - -#mocha .test a.replay { - position: absolute; - top: 3px; - right: -20px; - text-decoration: none; - vertical-align: middle; - display: block; - width: 15px; - height: 15px; - line-height: 15px; - text-align: center; - background: #eee; - font-size: 15px; - -moz-border-radius: 15px; - border-radius: 15px; - -webkit-transition: opacity 200ms; - -moz-transition: opacity 200ms; - transition: opacity 200ms; - opacity: 0.2; - color: #888; -} - -#mocha .test:hover a.replay { - opacity: 1; -} - -#report.pass .test.fail { - display: none; -} - -#report.fail .test.pass { - display: none; -} - -#error { - color: #c00; - font-size: 1.5 em; - font-weight: 100; - letter-spacing: 1px; -} - -#stats { - position: fixed; - top: 15px; - right: 10px; - font-size: 12px; - margin: 0; - color: #888; -} - -#stats .progress { - float: right; - padding-top: 0; -} - -#stats em { - color: black; -} - -#stats a { - text-decoration: none; - color: inherit; -} - -#stats a:hover { - border-bottom: 1px solid #eee; -} - -#stats li { - display: inline-block; - margin: 0 5px; - list-style: none; - padding-top: 11px; -} - -code .comment { color: #ddd } -code .init { color: #2F6FAD } -code .string { color: #5890AD } -code .keyword { color: #8A6343 } -code .number { color: #2F6FAD } diff --git a/test/vendor/mocha.js b/test/vendor/mocha.js deleted file mode 100644 index 6e42dc2..0000000 --- a/test/vendor/mocha.js +++ /dev/null @@ -1,4911 +0,0 @@ -;(function(){ - - -// CommonJS require() - -function require(p){ - var path = require.resolve(p) - , mod = require.modules[path]; - if (!mod) throw new Error('failed to require "' + p + '"'); - if (!mod.exports) { - mod.exports = {}; - mod.call(mod.exports, mod, mod.exports, require.relative(path)); - } - return mod.exports; - } - -require.modules = {}; - -require.resolve = function (path){ - var orig = path - , reg = path + '.js' - , index = path + '/index.js'; - return require.modules[reg] && reg - || require.modules[index] && index - || orig; - }; - -require.register = function (path, fn){ - require.modules[path] = fn; - }; - -require.relative = function (parent) { - return function(p){ - if ('.' != p.charAt(0)) return require(p); - - var path = parent.split('/') - , segs = p.split('/'); - path.pop(); - - for (var i = 0; i < segs.length; i++) { - var seg = segs[i]; - if ('..' == seg) path.pop(); - else if ('.' != seg) path.push(seg); - } - - return require(path.join('/')); - }; - }; - - -require.register("browser/debug.js", function(module, exports, require){ - -module.exports = function(type){ - return function(){ - - } -}; -}); // module: browser/debug.js - -require.register("browser/diff.js", function(module, exports, require){ - -}); // module: browser/diff.js - -require.register("browser/events.js", function(module, exports, require){ - -/** - * Module exports. - */ - -exports.EventEmitter = EventEmitter; - -/** - * Check if `obj` is an array. - */ - -function isArray(obj) { - return '[object Array]' == {}.toString.call(obj); -} - -/** - * Event emitter constructor. - * - * @api public - */ - -function EventEmitter(){}; - -/** - * Adds a listener. - * - * @api public - */ - -EventEmitter.prototype.on = function (name, fn) { - if (!this.$events) { - this.$events = {}; - } - - if (!this.$events[name]) { - this.$events[name] = fn; - } else if (isArray(this.$events[name])) { - this.$events[name].push(fn); - } else { - this.$events[name] = [this.$events[name], fn]; - } - - return this; -}; - -EventEmitter.prototype.addListener = EventEmitter.prototype.on; - -/** - * Adds a volatile listener. - * - * @api public - */ - -EventEmitter.prototype.once = function (name, fn) { - var self = this; - - function on () { - self.removeListener(name, on); - fn.apply(this, arguments); - }; - - on.listener = fn; - this.on(name, on); - - return this; -}; - -/** - * Removes a listener. - * - * @api public - */ - -EventEmitter.prototype.removeListener = function (name, fn) { - if (this.$events && this.$events[name]) { - var list = this.$events[name]; - - if (isArray(list)) { - var pos = -1; - - for (var i = 0, l = list.length; i < l; i++) { - if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { - pos = i; - break; - } - } - - if (pos < 0) { - return this; - } - - list.splice(pos, 1); - - if (!list.length) { - delete this.$events[name]; - } - } else if (list === fn || (list.listener && list.listener === fn)) { - delete this.$events[name]; - } - } - - return this; -}; - -/** - * Removes all listeners for an event. - * - * @api public - */ - -EventEmitter.prototype.removeAllListeners = function (name) { - if (name === undefined) { - this.$events = {}; - return this; - } - - if (this.$events && this.$events[name]) { - this.$events[name] = null; - } - - return this; -}; - -/** - * Gets all listeners for a certain event. - * - * @api public - */ - -EventEmitter.prototype.listeners = function (name) { - if (!this.$events) { - this.$events = {}; - } - - if (!this.$events[name]) { - this.$events[name] = []; - } - - if (!isArray(this.$events[name])) { - this.$events[name] = [this.$events[name]]; - } - - return this.$events[name]; -}; - -/** - * Emits an event. - * - * @api public - */ - -EventEmitter.prototype.emit = function (name) { - if (!this.$events) { - return false; - } - - var handler = this.$events[name]; - - if (!handler) { - return false; - } - - var args = [].slice.call(arguments, 1); - - if ('function' == typeof handler) { - handler.apply(this, args); - } else if (isArray(handler)) { - var listeners = handler.slice(); - - for (var i = 0, l = listeners.length; i < l; i++) { - listeners[i].apply(this, args); - } - } else { - return false; - } - - return true; -}; -}); // module: browser/events.js - -require.register("browser/fs.js", function(module, exports, require){ - -}); // module: browser/fs.js - -require.register("browser/path.js", function(module, exports, require){ - -}); // module: browser/path.js - -require.register("browser/progress.js", function(module, exports, require){ - -/** - * Expose `Progress`. - */ - -module.exports = Progress; - -/** - * Initialize a new `Progress` indicator. - */ - -function Progress() { - this.percent = 0; - this.size(0); - this.fontSize(11); - this.font('helvetica, arial, sans-serif'); -} - -/** - * Set progress size to `n`. - * - * @param {Number} n - * @return {Progress} for chaining - * @api public - */ - -Progress.prototype.size = function(n){ - this._size = n; - return this; -}; - -/** - * Set text to `str`. - * - * @param {String} str - * @return {Progress} for chaining - * @api public - */ - -Progress.prototype.text = function(str){ - this._text = str; - return this; -}; - -/** - * Set font size to `n`. - * - * @param {Number} n - * @return {Progress} for chaining - * @api public - */ - -Progress.prototype.fontSize = function(n){ - this._fontSize = n; - return this; -}; - -/** - * Set font `family`. - * - * @param {String} family - * @return {Progress} for chaining - */ - -Progress.prototype.font = function(family){ - this._font = family; - return this; -}; - -/** - * Update percentage to `n`. - * - * @param {Number} n - * @return {Progress} for chaining - */ - -Progress.prototype.update = function(n){ - this.percent = n; - return this; -}; - -/** - * Draw on `ctx`. - * - * @param {CanvasRenderingContext2d} ctx - * @return {Progress} for chaining - */ - -Progress.prototype.draw = function(ctx){ - var percent = Math.min(this.percent, 100) - , size = this._size - , half = size / 2 - , x = half - , y = half - , rad = half - 1 - , fontSize = this._fontSize; - - ctx.font = fontSize + 'px ' + this._font; - - var angle = Math.PI * 2 * (percent / 100); - ctx.clearRect(0, 0, size, size); - - // outer circle - ctx.strokeStyle = '#9f9f9f'; - ctx.beginPath(); - ctx.arc(x, y, rad, 0, angle, false); - ctx.stroke(); - - // inner circle - ctx.strokeStyle = '#eee'; - ctx.beginPath(); - ctx.arc(x, y, rad - 1, 0, angle, true); - ctx.stroke(); - - // text - var text = this._text || (percent | 0) + '%' - , w = ctx.measureText(text).width; - - ctx.fillText( - text - , x - w / 2 + 1 - , y + fontSize / 2 - 1); - - return this; -}; - -}); // module: browser/progress.js - -require.register("browser/tty.js", function(module, exports, require){ - -exports.isatty = function(){ - return true; -}; - -exports.getWindowSize = function(){ - return [window.innerHeight, window.innerWidth]; -}; -}); // module: browser/tty.js - -require.register("context.js", function(module, exports, require){ - -/** - * Expose `Context`. - */ - -module.exports = Context; - -/** - * Initialize a new `Context`. - * - * @api private - */ - -function Context(){} - -/** - * Set or get the context `Runnable` to `runnable`. - * - * @param {Runnable} runnable - * @return {Context} - * @api private - */ - -Context.prototype.runnable = function(runnable){ - if (0 == arguments.length) return this._runnable; - this.test = this._runnable = runnable; - return this; -}; - -/** - * Set test timeout `ms`. - * - * @param {Number} ms - * @return {Context} self - * @api private - */ - -Context.prototype.timeout = function(ms){ - this.runnable().timeout(ms); - return this; -}; - -/** - * Set test slowness threshold `ms`. - * - * @param {Number} ms - * @return {Context} self - * @api private - */ - -Context.prototype.slow = function(ms){ - this.runnable().slow(ms); - return this; -}; - -/** - * Inspect the context void of `._runnable`. - * - * @return {String} - * @api private - */ - -Context.prototype.inspect = function(){ - return JSON.stringify(this, function(key, val){ - if ('_runnable' == key) return; - if ('test' == key) return; - return val; - }, 2); -}; - -}); // module: context.js - -require.register("hook.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Runnable = require('./runnable'); - -/** - * Expose `Hook`. - */ - -module.exports = Hook; - -/** - * Initialize a new `Hook` with the given `title` and callback `fn`. - * - * @param {String} title - * @param {Function} fn - * @api private - */ - -function Hook(title, fn) { - Runnable.call(this, title, fn); - this.type = 'hook'; -} - -/** - * Inherit from `Runnable.prototype`. - */ - -Hook.prototype = new Runnable; -Hook.prototype.constructor = Hook; - - -/** - * Get or set the test `err`. - * - * @param {Error} err - * @return {Error} - * @api public - */ - -Hook.prototype.error = function(err){ - if (0 == arguments.length) { - var err = this._error; - this._error = null; - return err; - } - - this._error = err; -}; - - -}); // module: hook.js - -require.register("interfaces/bdd.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Suite = require('../suite') - , Test = require('../test'); - -/** - * BDD-style interface: - * - * describe('Array', function(){ - * describe('#indexOf()', function(){ - * it('should return -1 when not present', function(){ - * - * }); - * - * it('should return the index when present', function(){ - * - * }); - * }); - * }); - * - */ - -module.exports = function(suite){ - var suites = [suite]; - - suite.on('pre-require', function(context, file, mocha){ - - /** - * Execute before running tests. - */ - - context.before = function(fn){ - suites[0].beforeAll(fn); - }; - - /** - * Execute after running tests. - */ - - context.after = function(fn){ - suites[0].afterAll(fn); - }; - - /** - * Execute before each test case. - */ - - context.beforeEach = function(fn){ - suites[0].beforeEach(fn); - }; - - /** - * Execute after each test case. - */ - - context.afterEach = function(fn){ - suites[0].afterEach(fn); - }; - - /** - * Describe a "suite" with the given `title` - * and callback `fn` containing nested suites - * and/or tests. - */ - - context.describe = context.context = function(title, fn){ - var suite = Suite.create(suites[0], title); - suites.unshift(suite); - fn.call(suite); - suites.shift(); - return suite; - }; - - /** - * Pending describe. - */ - - context.xdescribe = - context.xcontext = - context.describe.skip = function(title, fn){ - var suite = Suite.create(suites[0], title); - suite.pending = true; - suites.unshift(suite); - fn.call(suite); - suites.shift(); - }; - - /** - * Exclusive suite. - */ - - context.describe.only = function(title, fn){ - var suite = context.describe(title, fn); - mocha.grep(suite.fullTitle()); - }; - - /** - * Describe a specification or test-case - * with the given `title` and callback `fn` - * acting as a thunk. - */ - - context.it = context.specify = function(title, fn){ - var suite = suites[0]; - if (suite.pending) var fn = null; - var test = new Test(title, fn); - suite.addTest(test); - return test; - }; - - /** - * Exclusive test-case. - */ - - context.it.only = function(title, fn){ - var test = context.it(title, fn); - mocha.grep(test.fullTitle()); - }; - - /** - * Pending test case. - */ - - context.xit = - context.xspecify = - context.it.skip = function(title){ - context.it(title); - }; - }); -}; - -}); // module: interfaces/bdd.js - -require.register("interfaces/exports.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Suite = require('../suite') - , Test = require('../test'); - -/** - * TDD-style interface: - * - * exports.Array = { - * '#indexOf()': { - * 'should return -1 when the value is not present': function(){ - * - * }, - * - * 'should return the correct index when the value is present': function(){ - * - * } - * } - * }; - * - */ - -module.exports = function(suite){ - var suites = [suite]; - - suite.on('require', visit); - - function visit(obj) { - var suite; - for (var key in obj) { - if ('function' == typeof obj[key]) { - var fn = obj[key]; - switch (key) { - case 'before': - suites[0].beforeAll(fn); - break; - case 'after': - suites[0].afterAll(fn); - break; - case 'beforeEach': - suites[0].beforeEach(fn); - break; - case 'afterEach': - suites[0].afterEach(fn); - break; - default: - suites[0].addTest(new Test(key, fn)); - } - } else { - var suite = Suite.create(suites[0], key); - suites.unshift(suite); - visit(obj[key]); - suites.shift(); - } - } - } -}; -}); // module: interfaces/exports.js - -require.register("interfaces/index.js", function(module, exports, require){ - -exports.bdd = require('./bdd'); -exports.tdd = require('./tdd'); -exports.qunit = require('./qunit'); -exports.exports = require('./exports'); - -}); // module: interfaces/index.js - -require.register("interfaces/qunit.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Suite = require('../suite') - , Test = require('../test'); - -/** - * QUnit-style interface: - * - * suite('Array'); - * - * test('#length', function(){ - * var arr = [1,2,3]; - * ok(arr.length == 3); - * }); - * - * test('#indexOf()', function(){ - * var arr = [1,2,3]; - * ok(arr.indexOf(1) == 0); - * ok(arr.indexOf(2) == 1); - * ok(arr.indexOf(3) == 2); - * }); - * - * suite('String'); - * - * test('#length', function(){ - * ok('foo'.length == 3); - * }); - * - */ - -module.exports = function(suite){ - var suites = [suite]; - - suite.on('pre-require', function(context){ - - /** - * Execute before running tests. - */ - - context.before = function(fn){ - suites[0].beforeAll(fn); - }; - - /** - * Execute after running tests. - */ - - context.after = function(fn){ - suites[0].afterAll(fn); - }; - - /** - * Execute before each test case. - */ - - context.beforeEach = function(fn){ - suites[0].beforeEach(fn); - }; - - /** - * Execute after each test case. - */ - - context.afterEach = function(fn){ - suites[0].afterEach(fn); - }; - - /** - * Describe a "suite" with the given `title`. - */ - - context.suite = function(title){ - if (suites.length > 1) suites.shift(); - var suite = Suite.create(suites[0], title); - suites.unshift(suite); - }; - - /** - * Describe a specification or test-case - * with the given `title` and callback `fn` - * acting as a thunk. - */ - - context.test = function(title, fn){ - suites[0].addTest(new Test(title, fn)); - }; - }); -}; - -}); // module: interfaces/qunit.js - -require.register("interfaces/tdd.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Suite = require('../suite') - , Test = require('../test'); - -/** - * TDD-style interface: - * - * suite('Array', function(){ - * suite('#indexOf()', function(){ - * suiteSetup(function(){ - * - * }); - * - * test('should return -1 when not present', function(){ - * - * }); - * - * test('should return the index when present', function(){ - * - * }); - * - * suiteTeardown(function(){ - * - * }); - * }); - * }); - * - */ - -module.exports = function(suite){ - var suites = [suite]; - - suite.on('pre-require', function(context, file, mocha){ - - /** - * Execute before each test case. - */ - - context.setup = function(fn){ - suites[0].beforeEach(fn); - }; - - /** - * Execute after each test case. - */ - - context.teardown = function(fn){ - suites[0].afterEach(fn); - }; - - /** - * Execute before the suite. - */ - - context.suiteSetup = function(fn){ - suites[0].beforeAll(fn); - }; - - /** - * Execute after the suite. - */ - - context.suiteTeardown = function(fn){ - suites[0].afterAll(fn); - }; - - /** - * Describe a "suite" with the given `title` - * and callback `fn` containing nested suites - * and/or tests. - */ - - context.suite = function(title, fn){ - var suite = Suite.create(suites[0], title); - suites.unshift(suite); - fn.call(suite); - suites.shift(); - return suite; - }; - - /** - * Exclusive test-case. - */ - - context.suite.only = function(title, fn){ - var suite = context.suite(title, fn); - mocha.grep(suite.fullTitle()); - }; - - /** - * Describe a specification or test-case - * with the given `title` and callback `fn` - * acting as a thunk. - */ - - context.test = function(title, fn){ - var test = new Test(title, fn); - suites[0].addTest(test); - return test; - }; - - /** - * Exclusive test-case. - */ - - context.test.only = function(title, fn){ - var test = context.test(title, fn); - mocha.grep(test.fullTitle()); - }; - }); -}; - -}); // module: interfaces/tdd.js - -require.register("mocha.js", function(module, exports, require){ -/*! - * mocha - * Copyright(c) 2011 TJ Holowaychuk - * MIT Licensed - */ - -/** - * Module dependencies. - */ - -var path = require('browser/path') - , utils = require('./utils'); - -/** - * Expose `Mocha`. - */ - -exports = module.exports = Mocha; - -/** - * Expose internals. - */ - -exports.utils = utils; -exports.interfaces = require('./interfaces'); -exports.reporters = require('./reporters'); -exports.Runnable = require('./runnable'); -exports.Context = require('./context'); -exports.Runner = require('./runner'); -exports.Suite = require('./suite'); -exports.Hook = require('./hook'); -exports.Test = require('./test'); - -/** - * Return image `name` path. - * - * @param {String} name - * @return {String} - * @api private - */ - -function image(name) { - return __dirname + '/../images/' + name + '.png'; -} - -/** - * Setup mocha with `options`. - * - * Options: - * - * - `ui` name "bdd", "tdd", "exports" etc - * - `reporter` reporter instance, defaults to `mocha.reporters.Dot` - * - `globals` array of accepted globals - * - `timeout` timeout in milliseconds - * - `slow` milliseconds to wait before considering a test slow - * - `ignoreLeaks` ignore global leaks - * - `grep` string or regexp to filter tests with - * - * @param {Object} options - * @api public - */ - -function Mocha(options) { - options = options || {}; - this.files = []; - this.options = options; - this.grep(options.grep); - this.suite = new exports.Suite('', new exports.Context); - this.ui(options.ui); - this.reporter(options.reporter); - if (options.timeout) this.timeout(options.timeout); - if (options.slow) this.slow(options.slow); -} - -/** - * Add test `file`. - * - * @param {String} file - * @api public - */ - -Mocha.prototype.addFile = function(file){ - this.files.push(file); - return this; -}; - -/** - * Set reporter to `reporter`, defaults to "dot". - * - * @param {String|Function} reporter name of a reporter or a reporter constructor - * @api public - */ - -Mocha.prototype.reporter = function(reporter){ - if ('function' == typeof reporter) { - this._reporter = reporter; - } else { - reporter = reporter || 'dot'; - try { - this._reporter = require('./reporters/' + reporter); - } catch (err) { - this._reporter = require(reporter); - } - if (!this._reporter) throw new Error('invalid reporter "' + reporter + '"'); - } - return this; -}; - -/** - * Set test UI `name`, defaults to "bdd". - * - * @param {String} bdd - * @api public - */ - -Mocha.prototype.ui = function(name){ - name = name || 'bdd'; - this._ui = exports.interfaces[name]; - if (!this._ui) throw new Error('invalid interface "' + name + '"'); - this._ui = this._ui(this.suite); - return this; -}; - -/** - * Load registered files. - * - * @api private - */ - -Mocha.prototype.loadFiles = function(fn){ - var self = this; - var suite = this.suite; - var pending = this.files.length; - this.files.forEach(function(file){ - file = path.resolve(file); - suite.emit('pre-require', global, file, self); - suite.emit('require', require(file), file, self); - suite.emit('post-require', global, file, self); - --pending || (fn && fn()); - }); -}; - -/** - * Enable growl support. - * - * @api private - */ - -Mocha.prototype._growl = function(runner, reporter) { - var notify = require('growl'); - - runner.on('end', function(){ - var stats = reporter.stats; - if (stats.failures) { - var msg = stats.failures + ' of ' + runner.total + ' tests failed'; - notify(msg, { name: 'mocha', title: 'Failed', image: image('error') }); - } else { - notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', { - name: 'mocha' - , title: 'Passed' - , image: image('ok') - }); - } - }); -}; - -/** - * Add regexp to grep, if `re` is a string it is escaped. - * - * @param {RegExp|String} re - * @return {Mocha} - * @api public - */ - -Mocha.prototype.grep = function(re){ - this.options.grep = 'string' == typeof re - ? new RegExp(utils.escapeRegexp(re)) - : re; - return this; -}; - -/** - * Invert `.grep()` matches. - * - * @return {Mocha} - * @api public - */ - -Mocha.prototype.invert = function(){ - this.options.invert = true; - return this; -}; - -/** - * Ignore global leaks. - * - * @return {Mocha} - * @api public - */ - -Mocha.prototype.ignoreLeaks = function(){ - this.options.ignoreLeaks = true; - return this; -}; - -/** - * Enable global leak checking. - * - * @return {Mocha} - * @api public - */ - -Mocha.prototype.checkLeaks = function(){ - this.options.ignoreLeaks = false; - return this; -}; - -/** - * Enable growl support. - * - * @return {Mocha} - * @api public - */ - -Mocha.prototype.growl = function(){ - this.options.growl = true; - return this; -}; - -/** - * Ignore `globals` array or string. - * - * @param {Array|String} globals - * @return {Mocha} - * @api public - */ - -Mocha.prototype.globals = function(globals){ - this.options.globals = (this.options.globals || []).concat(globals); - return this; -}; - -/** - * Set the timeout in milliseconds. - * - * @param {Number} timeout - * @return {Mocha} - * @api public - */ - -Mocha.prototype.timeout = function(timeout){ - this.suite.timeout(timeout); - return this; -}; - -/** - * Set slowness threshold in milliseconds. - * - * @param {Number} slow - * @return {Mocha} - * @api public - */ - -Mocha.prototype.slow = function(slow){ - this.suite.slow(slow); - return this; -}; - -/** - * Run tests and invoke `fn()` when complete. - * - * @param {Function} fn - * @return {Runner} - * @api public - */ - -Mocha.prototype.run = function(fn){ - if (this.files.length) this.loadFiles(); - var suite = this.suite; - var options = this.options; - var runner = new exports.Runner(suite); - var reporter = new this._reporter(runner); - runner.ignoreLeaks = options.ignoreLeaks; - if (options.grep) runner.grep(options.grep, options.invert); - if (options.globals) runner.globals(options.globals); - if (options.growl) this._growl(runner, reporter); - return runner.run(fn); -}; - -}); // module: mocha.js - -require.register("ms.js", function(module, exports, require){ - -/** - * Helpers. - */ - -var s = 1000; -var m = s * 60; -var h = m * 60; -var d = h * 24; - -/** - * Parse or format the given `val`. - * - * @param {String|Number} val - * @return {String|Number} - * @api public - */ - -module.exports = function(val){ - if ('string' == typeof val) return parse(val); - return format(val); -} - -/** - * Parse the given `str` and return milliseconds. - * - * @param {String} str - * @return {Number} - * @api private - */ - -function parse(str) { - var m = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str); - if (!m) return; - var n = parseFloat(m[1]); - var type = (m[2] || 'ms').toLowerCase(); - switch (type) { - case 'years': - case 'year': - case 'y': - return n * 31557600000; - case 'days': - case 'day': - case 'd': - return n * 86400000; - case 'hours': - case 'hour': - case 'h': - return n * 3600000; - case 'minutes': - case 'minute': - case 'm': - return n * 60000; - case 'seconds': - case 'second': - case 's': - return n * 1000; - case 'ms': - return n; - } -} - -/** - * Format the given `ms`. - * - * @param {Number} ms - * @return {String} - * @api public - */ - -function format(ms) { - if (ms == d) return (ms / d) + ' day'; - if (ms > d) return (ms / d) + ' days'; - if (ms == h) return (ms / h) + ' hour'; - if (ms > h) return (ms / h) + ' hours'; - if (ms == m) return (ms / m) + ' minute'; - if (ms > m) return (ms / m) + ' minutes'; - if (ms == s) return (ms / s) + ' second'; - if (ms > s) return (ms / s) + ' seconds'; - return ms + ' ms'; -} -}); // module: ms.js - -require.register("reporters/base.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var tty = require('browser/tty') - , diff = require('browser/diff') - , ms = require('../ms'); - -/** - * Save timer references to avoid Sinon interfering (see GH-237). - */ - -var Date = global.Date - , setTimeout = global.setTimeout - , setInterval = global.setInterval - , clearTimeout = global.clearTimeout - , clearInterval = global.clearInterval; - -/** - * Check if both stdio streams are associated with a tty. - */ - -var isatty = tty.isatty(1) && tty.isatty(2); - -/** - * Expose `Base`. - */ - -exports = module.exports = Base; - -/** - * Enable coloring by default. - */ - -exports.useColors = isatty; - -/** - * Default color map. - */ - -exports.colors = { - 'pass': 90 - , 'fail': 31 - , 'bright pass': 92 - , 'bright fail': 91 - , 'bright yellow': 93 - , 'pending': 36 - , 'suite': 0 - , 'error title': 0 - , 'error message': 31 - , 'error stack': 90 - , 'checkmark': 32 - , 'fast': 90 - , 'medium': 33 - , 'slow': 31 - , 'green': 32 - , 'light': 90 - , 'diff gutter': 90 - , 'diff added': 42 - , 'diff removed': 41 -}; - -/** - * Color `str` with the given `type`, - * allowing colors to be disabled, - * as well as user-defined color - * schemes. - * - * @param {String} type - * @param {String} str - * @return {String} - * @api private - */ - -var color = exports.color = function(type, str) { - if (!exports.useColors) return str; - return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'; -}; - -/** - * Expose term window size, with some - * defaults for when stderr is not a tty. - */ - -exports.window = { - width: isatty - ? process.stdout.getWindowSize - ? process.stdout.getWindowSize(1)[0] - : tty.getWindowSize()[1] - : 75 -}; - -/** - * Expose some basic cursor interactions - * that are common among reporters. - */ - -exports.cursor = { - hide: function(){ - process.stdout.write('\u001b[?25l'); - }, - - show: function(){ - process.stdout.write('\u001b[?25h'); - }, - - deleteLine: function(){ - process.stdout.write('\u001b[2K'); - }, - - beginningOfLine: function(){ - process.stdout.write('\u001b[0G'); - }, - - CR: function(){ - exports.cursor.deleteLine(); - exports.cursor.beginningOfLine(); - } -}; - -/** - * Outut the given `failures` as a list. - * - * @param {Array} failures - * @api public - */ - -exports.list = function(failures){ - console.error(); - failures.forEach(function(test, i){ - // format - var fmt = color('error title', ' %s) %s:\n') - + color('error message', ' %s') - + color('error stack', '\n%s\n'); - - // msg - var err = test.err - , message = err.message || '' - , stack = err.stack || message - , index = stack.indexOf(message) + message.length - , msg = stack.slice(0, index) - , actual = err.actual - , expected = err.expected - , escape = true; - - // explicitly show diff - if (err.showDiff) { - escape = false; - err.actual = actual = JSON.stringify(actual, null, 2); - err.expected = expected = JSON.stringify(expected, null, 2); - } - - // actual / expected diff - if ('string' == typeof actual && 'string' == typeof expected) { - var len = Math.max(actual.length, expected.length); - - if (len < 20) msg = errorDiff(err, 'Chars', escape); - else msg = errorDiff(err, 'Words', escape); - - // linenos - var lines = msg.split('\n'); - if (lines.length > 4) { - var width = String(lines.length).length; - msg = lines.map(function(str, i){ - return pad(++i, width) + ' |' + ' ' + str; - }).join('\n'); - } - - // legend - msg = '\n' - + color('diff removed', 'actual') - + ' ' - + color('diff added', 'expected') - + '\n\n' - + msg - + '\n'; - - // indent - msg = msg.replace(/^/gm, ' '); - - fmt = color('error title', ' %s) %s:\n%s') - + color('error stack', '\n%s\n'); - } - - // indent stack trace without msg - stack = stack.slice(index ? index + 1 : index) - .replace(/^/gm, ' '); - - console.error(fmt, (i + 1), test.fullTitle(), msg, stack); - }); -}; - -/** - * Initialize a new `Base` reporter. - * - * All other reporters generally - * inherit from this reporter, providing - * stats such as test duration, number - * of tests passed / failed etc. - * - * @param {Runner} runner - * @api public - */ - -function Base(runner) { - var self = this - , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 } - , failures = this.failures = []; - - if (!runner) return; - this.runner = runner; - - runner.stats = stats; - - runner.on('start', function(){ - stats.start = new Date; - }); - - runner.on('suite', function(suite){ - stats.suites = stats.suites || 0; - suite.root || stats.suites++; - }); - - runner.on('test end', function(test){ - stats.tests = stats.tests || 0; - stats.tests++; - }); - - runner.on('pass', function(test){ - stats.passes = stats.passes || 0; - - var medium = test.slow() / 2; - test.speed = test.duration > test.slow() - ? 'slow' - : test.duration > medium - ? 'medium' - : 'fast'; - - stats.passes++; - }); - - runner.on('fail', function(test, err){ - stats.failures = stats.failures || 0; - stats.failures++; - test.err = err; - failures.push(test); - }); - - runner.on('end', function(){ - stats.end = new Date; - stats.duration = new Date - stats.start; - }); - - runner.on('pending', function(){ - stats.pending++; - }); -} - -/** - * Output common epilogue used by many of - * the bundled reporters. - * - * @api public - */ - -Base.prototype.epilogue = function(){ - var stats = this.stats - , fmt - , tests; - - console.log(); - - function pluralize(n) { - return 1 == n ? 'test' : 'tests'; - } - - // failure - if (stats.failures) { - fmt = color('bright fail', ' ✖') - + color('fail', ' %d of %d %s failed') - + color('light', ':') - - console.error(fmt, - stats.failures, - this.runner.total, - pluralize(this.runner.total)); - - Base.list(this.failures); - console.error(); - return; - } - - // pass - fmt = color('bright pass', ' ✔') - + color('green', ' %d %s complete') - + color('light', ' (%s)'); - - console.log(fmt, - stats.tests || 0, - pluralize(stats.tests), - ms(stats.duration)); - - // pending - if (stats.pending) { - fmt = color('pending', ' •') - + color('pending', ' %d %s pending'); - - console.log(fmt, stats.pending, pluralize(stats.pending)); - } - - console.log(); -}; - -/** - * Pad the given `str` to `len`. - * - * @param {String} str - * @param {String} len - * @return {String} - * @api private - */ - -function pad(str, len) { - str = String(str); - return Array(len - str.length + 1).join(' ') + str; -} - -/** - * Return a character diff for `err`. - * - * @param {Error} err - * @return {String} - * @api private - */ - -function errorDiff(err, type, escape) { - return diff['diff' + type](err.actual, err.expected).map(function(str){ - if (escape) { - str.value = str.value - .replace(/\t/g, '') - .replace(/\r/g, '') - .replace(/\n/g, '\n'); - } - if (str.added) return colorLines('diff added', str.value); - if (str.removed) return colorLines('diff removed', str.value); - return str.value; - }).join(''); -} - -/** - * Color lines for `str`, using the color `name`. - * - * @param {String} name - * @param {String} str - * @return {String} - * @api private - */ - -function colorLines(name, str) { - return str.split('\n').map(function(str){ - return color(name, str); - }).join('\n'); -} - -}); // module: reporters/base.js - -require.register("reporters/doc.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , utils = require('../utils'); - -/** - * Expose `Doc`. - */ - -exports = module.exports = Doc; - -/** - * Initialize a new `Doc` reporter. - * - * @param {Runner} runner - * @api public - */ - -function Doc(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , total = runner.total - , indents = 2; - - function indent() { - return Array(indents).join(' '); - } - - runner.on('suite', function(suite){ - if (suite.root) return; - ++indents; - console.log('%s
', indent()); - ++indents; - console.log('%s

%s

', indent(), utils.escape(suite.title)); - console.log('%s
', indent()); - }); - - runner.on('suite end', function(suite){ - if (suite.root) return; - console.log('%s
', indent()); - --indents; - console.log('%s
', indent()); - --indents; - }); - - runner.on('pass', function(test){ - console.log('%s
%s
', indent(), utils.escape(test.title)); - var code = utils.escape(utils.clean(test.fn.toString())); - console.log('%s
%s
', indent(), code); - }); -} - -}); // module: reporters/doc.js - -require.register("reporters/dot.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , color = Base.color; - -/** - * Expose `Dot`. - */ - -exports = module.exports = Dot; - -/** - * Initialize a new `Dot` matrix test reporter. - * - * @param {Runner} runner - * @api public - */ - -function Dot(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , width = Base.window.width * .75 | 0 - , c = '․' - , n = 0; - - runner.on('start', function(){ - process.stdout.write('\n '); - }); - - runner.on('pending', function(test){ - process.stdout.write(color('pending', c)); - }); - - runner.on('pass', function(test){ - if (++n % width == 0) process.stdout.write('\n '); - if ('slow' == test.speed) { - process.stdout.write(color('bright yellow', c)); - } else { - process.stdout.write(color(test.speed, c)); - } - }); - - runner.on('fail', function(test, err){ - if (++n % width == 0) process.stdout.write('\n '); - process.stdout.write(color('fail', c)); - }); - - runner.on('end', function(){ - console.log(); - self.epilogue(); - }); -} - -/** - * Inherit from `Base.prototype`. - */ - -Dot.prototype = new Base; -Dot.prototype.constructor = Dot; - -}); // module: reporters/dot.js - -require.register("reporters/html-cov.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var JSONCov = require('./json-cov') - , fs = require('browser/fs'); - -/** - * Expose `HTMLCov`. - */ - -exports = module.exports = HTMLCov; - -/** - * Initialize a new `JsCoverage` reporter. - * - * @param {Runner} runner - * @api public - */ - -function HTMLCov(runner) { - var jade = require('jade') - , file = __dirname + '/templates/coverage.jade' - , str = fs.readFileSync(file, 'utf8') - , fn = jade.compile(str, { filename: file }) - , self = this; - - JSONCov.call(this, runner, false); - - runner.on('end', function(){ - process.stdout.write(fn({ - cov: self.cov - , coverageClass: coverageClass - })); - }); -} - -/** - * Return coverage class for `n`. - * - * @return {String} - * @api private - */ - -function coverageClass(n) { - if (n >= 75) return 'high'; - if (n >= 50) return 'medium'; - if (n >= 25) return 'low'; - return 'terrible'; -} -}); // module: reporters/html-cov.js - -require.register("reporters/html.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , utils = require('../utils') - , Progress = require('../browser/progress') - , escape = utils.escape; - -/** - * Save timer references to avoid Sinon interfering (see GH-237). - */ - -var Date = global.Date - , setTimeout = global.setTimeout - , setInterval = global.setInterval - , clearTimeout = global.clearTimeout - , clearInterval = global.clearInterval; - -/** - * Expose `Doc`. - */ - -exports = module.exports = HTML; - -/** - * Stats template. - */ - -var statsTemplate = ''; - -/** - * Initialize a new `Doc` reporter. - * - * @param {Runner} runner - * @api public - */ - -function HTML(runner, root) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , total = runner.total - , stat = fragment(statsTemplate) - , items = stat.getElementsByTagName('li') - , passes = items[1].getElementsByTagName('em')[0] - , passesLink = items[1].getElementsByTagName('a')[0] - , failures = items[2].getElementsByTagName('em')[0] - , failuresLink = items[2].getElementsByTagName('a')[0] - , duration = items[3].getElementsByTagName('em')[0] - , canvas = stat.getElementsByTagName('canvas')[0] - , report = fragment('
    ') - , stack = [report] - , progress - , ctx - - root = root || document.getElementById('mocha'); - - if (canvas.getContext) { - var ratio = window.devicePixelRatio || 1; - canvas.style.width = canvas.width; - canvas.style.height = canvas.height; - canvas.width *= ratio; - canvas.height *= ratio; - ctx = canvas.getContext('2d'); - ctx.scale(ratio, ratio); - progress = new Progress; - } - - if (!root) return error('#mocha div missing, add it to your document'); - - // pass toggle - on(passesLink, 'click', function(){ - unhide(); - var name = /pass/.test(report.className) ? '' : ' pass'; - report.className = report.className.replace(/fail|pass/g, '') + name; - if (report.className.trim()) hideSuitesWithout('test pass'); - }); - - // failure toggle - on(failuresLink, 'click', function(){ - unhide(); - var name = /fail/.test(report.className) ? '' : ' fail'; - report.className = report.className.replace(/fail|pass/g, '') + name; - if (report.className.trim()) hideSuitesWithout('test fail'); - }); - - root.appendChild(stat); - root.appendChild(report); - - if (progress) progress.size(40); - - runner.on('suite', function(suite){ - if (suite.root) return; - - // suite - var url = '?grep=' + encodeURIComponent(suite.fullTitle()); - var el = fragment('
  • %s

  • ', url, escape(suite.title)); - - // container - stack[0].appendChild(el); - stack.unshift(document.createElement('ul')); - el.appendChild(stack[0]); - }); - - runner.on('suite end', function(suite){ - if (suite.root) return; - stack.shift(); - }); - - runner.on('fail', function(test, err){ - if ('hook' == test.type || err.uncaught) runner.emit('test end', test); - }); - - runner.on('test end', function(test){ - window.scrollTo(0, document.body.scrollHeight); - - // TODO: add to stats - var percent = stats.tests / total * 100 | 0; - if (progress) progress.update(percent).draw(ctx); - - // update stats - var ms = new Date - stats.start; - text(passes, stats.passes); - text(failures, stats.failures); - text(duration, (ms / 1000).toFixed(2)); - - // test - if ('passed' == test.state) { - var el = fragment('
  • %e%ems

  • ', test.speed, test.title, test.duration, test.fullTitle()); - } else if (test.pending) { - var el = fragment('
  • %e

  • ', test.title); - } else { - var el = fragment('
  • %e

  • ', test.title, test.fullTitle()); - var str = test.err.stack || test.err.toString(); - - // FF / Opera do not add the message - if (!~str.indexOf(test.err.message)) { - str = test.err.message + '\n' + str; - } - - // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we - // check for the result of the stringifying. - if ('[object Error]' == str) str = test.err.message; - - // Safari doesn't give you a stack. Let's at least provide a source line. - if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) { - str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")"; - } - - el.appendChild(fragment('
    %e
    ', str)); - } - - // toggle code - // TODO: defer - if (!test.pending) { - var h2 = el.getElementsByTagName('h2')[0]; - - on(h2, 'click', function(){ - pre.style.display = 'none' == pre.style.display - ? 'inline-block' - : 'none'; - }); - - var pre = fragment('
    %e
    ', utils.clean(test.fn.toString())); - el.appendChild(pre); - pre.style.display = 'none'; - } - - stack[0].appendChild(el); - }); -} - -/** - * Display error `msg`. - */ - -function error(msg) { - document.body.appendChild(fragment('
    %s
    ', msg)); -} - -/** - * Return a DOM fragment from `html`. - */ - -function fragment(html) { - var args = arguments - , div = document.createElement('div') - , i = 1; - - div.innerHTML = html.replace(/%([se])/g, function(_, type){ - switch (type) { - case 's': return String(args[i++]); - case 'e': return escape(args[i++]); - } - }); - - return div.firstChild; -} - -/** - * Check for suites that do not have elements - * with `classname`, and hide them. - */ - -function hideSuitesWithout(classname) { - var suites = document.getElementsByClassName('suite'); - for (var i = 0; i < suites.length; i++) { - var els = suites[i].getElementsByClassName(classname); - if (0 == els.length) suites[i].className += ' hidden'; - } -} - -/** - * Unhide .hidden suites. - */ - -function unhide() { - var els = document.getElementsByClassName('suite hidden'); - for (var i = 0; i < els.length; ++i) { - els[i].className = els[i].className.replace('suite hidden', 'suite'); - } -} - -/** - * Set `el` text to `str`. - */ - -function text(el, str) { - if (el.textContent) { - el.textContent = str; - } else { - el.innerText = str; - } -} - -/** - * Listen on `event` with callback `fn`. - */ - -function on(el, event, fn) { - if (el.addEventListener) { - el.addEventListener(event, fn, false); - } else { - el.attachEvent('on' + event, fn); - } -} - -}); // module: reporters/html.js - -require.register("reporters/index.js", function(module, exports, require){ - -exports.Base = require('./base'); -exports.Dot = require('./dot'); -exports.Doc = require('./doc'); -exports.TAP = require('./tap'); -exports.JSON = require('./json'); -exports.HTML = require('./html'); -exports.List = require('./list'); -exports.Min = require('./min'); -exports.Spec = require('./spec'); -exports.Nyan = require('./nyan'); -exports.XUnit = require('./xunit'); -exports.Markdown = require('./markdown'); -exports.Progress = require('./progress'); -exports.Landing = require('./landing'); -exports.JSONCov = require('./json-cov'); -exports.HTMLCov = require('./html-cov'); -exports.JSONStream = require('./json-stream'); -exports.Teamcity = require('./teamcity'); - -}); // module: reporters/index.js - -require.register("reporters/json-cov.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base'); - -/** - * Expose `JSONCov`. - */ - -exports = module.exports = JSONCov; - -/** - * Initialize a new `JsCoverage` reporter. - * - * @param {Runner} runner - * @param {Boolean} output - * @api public - */ - -function JSONCov(runner, output) { - var self = this - , output = 1 == arguments.length ? true : output; - - Base.call(this, runner); - - var tests = [] - , failures = [] - , passes = []; - - runner.on('test end', function(test){ - tests.push(test); - }); - - runner.on('pass', function(test){ - passes.push(test); - }); - - runner.on('fail', function(test){ - failures.push(test); - }); - - runner.on('end', function(){ - var cov = global._$jscoverage || {}; - var result = self.cov = map(cov); - result.stats = self.stats; - result.tests = tests.map(clean); - result.failures = failures.map(clean); - result.passes = passes.map(clean); - if (!output) return; - process.stdout.write(JSON.stringify(result, null, 2 )); - }); -} - -/** - * Map jscoverage data to a JSON structure - * suitable for reporting. - * - * @param {Object} cov - * @return {Object} - * @api private - */ - -function map(cov) { - var ret = { - instrumentation: 'node-jscoverage' - , sloc: 0 - , hits: 0 - , misses: 0 - , coverage: 0 - , files: [] - }; - - for (var filename in cov) { - var data = coverage(filename, cov[filename]); - ret.files.push(data); - ret.hits += data.hits; - ret.misses += data.misses; - ret.sloc += data.sloc; - } - - if (ret.sloc > 0) { - ret.coverage = (ret.hits / ret.sloc) * 100; - } - - return ret; -}; - -/** - * Map jscoverage data for a single source file - * to a JSON structure suitable for reporting. - * - * @param {String} filename name of the source file - * @param {Object} data jscoverage coverage data - * @return {Object} - * @api private - */ - -function coverage(filename, data) { - var ret = { - filename: filename, - coverage: 0, - hits: 0, - misses: 0, - sloc: 0, - source: {} - }; - - data.source.forEach(function(line, num){ - num++; - - if (data[num] === 0) { - ret.misses++; - ret.sloc++; - } else if (data[num] !== undefined) { - ret.hits++; - ret.sloc++; - } - - ret.source[num] = { - source: line - , coverage: data[num] === undefined - ? '' - : data[num] - }; - }); - - ret.coverage = ret.hits / ret.sloc * 100; - - return ret; -} - -/** - * Return a plain-object representation of `test` - * free of cyclic properties etc. - * - * @param {Object} test - * @return {Object} - * @api private - */ - -function clean(test) { - return { - title: test.title - , fullTitle: test.fullTitle() - , duration: test.duration - } -} - -}); // module: reporters/json-cov.js - -require.register("reporters/json-stream.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , color = Base.color; - -/** - * Expose `List`. - */ - -exports = module.exports = List; - -/** - * Initialize a new `List` test reporter. - * - * @param {Runner} runner - * @api public - */ - -function List(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , total = runner.total; - - runner.on('start', function(){ - console.log(JSON.stringify(['start', { total: total }])); - }); - - runner.on('pass', function(test){ - console.log(JSON.stringify(['pass', clean(test)])); - }); - - runner.on('fail', function(test, err){ - console.log(JSON.stringify(['fail', clean(test)])); - }); - - runner.on('end', function(){ - process.stdout.write(JSON.stringify(['end', self.stats])); - }); -} - -/** - * Return a plain-object representation of `test` - * free of cyclic properties etc. - * - * @param {Object} test - * @return {Object} - * @api private - */ - -function clean(test) { - return { - title: test.title - , fullTitle: test.fullTitle() - , duration: test.duration - } -} -}); // module: reporters/json-stream.js - -require.register("reporters/json.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; - -/** - * Expose `JSON`. - */ - -exports = module.exports = JSONReporter; - -/** - * Initialize a new `JSON` reporter. - * - * @param {Runner} runner - * @api public - */ - -function JSONReporter(runner) { - var self = this; - Base.call(this, runner); - - var tests = [] - , failures = [] - , passes = []; - - runner.on('test end', function(test){ - tests.push(test); - }); - - runner.on('pass', function(test){ - passes.push(test); - }); - - runner.on('fail', function(test){ - failures.push(test); - }); - - runner.on('end', function(){ - var obj = { - stats: self.stats - , tests: tests.map(clean) - , failures: failures.map(clean) - , passes: passes.map(clean) - }; - - process.stdout.write(JSON.stringify(obj, null, 2)); - }); -} - -/** - * Return a plain-object representation of `test` - * free of cyclic properties etc. - * - * @param {Object} test - * @return {Object} - * @api private - */ - -function clean(test) { - return { - title: test.title - , fullTitle: test.fullTitle() - , duration: test.duration - } -} -}); // module: reporters/json.js - -require.register("reporters/landing.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; - -/** - * Expose `Landing`. - */ - -exports = module.exports = Landing; - -/** - * Airplane color. - */ - -Base.colors.plane = 0; - -/** - * Airplane crash color. - */ - -Base.colors['plane crash'] = 31; - -/** - * Runway color. - */ - -Base.colors.runway = 90; - -/** - * Initialize a new `Landing` reporter. - * - * @param {Runner} runner - * @api public - */ - -function Landing(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , width = Base.window.width * .75 | 0 - , total = runner.total - , stream = process.stdout - , plane = color('plane', '✈') - , crashed = -1 - , n = 0; - - function runway() { - var buf = Array(width).join('-'); - return ' ' + color('runway', buf); - } - - runner.on('start', function(){ - stream.write('\n '); - cursor.hide(); - }); - - runner.on('test end', function(test){ - // check if the plane crashed - var col = -1 == crashed - ? width * ++n / total | 0 - : crashed; - - // show the crash - if ('failed' == test.state) { - plane = color('plane crash', '✈'); - crashed = col; - } - - // render landing strip - stream.write('\u001b[4F\n\n'); - stream.write(runway()); - stream.write('\n '); - stream.write(color('runway', Array(col).join('⋅'))); - stream.write(plane) - stream.write(color('runway', Array(width - col).join('⋅') + '\n')); - stream.write(runway()); - stream.write('\u001b[0m'); - }); - - runner.on('end', function(){ - cursor.show(); - console.log(); - self.epilogue(); - }); -} - -/** - * Inherit from `Base.prototype`. - */ - -Landing.prototype = new Base; -Landing.prototype.constructor = Landing; - -}); // module: reporters/landing.js - -require.register("reporters/list.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; - -/** - * Expose `List`. - */ - -exports = module.exports = List; - -/** - * Initialize a new `List` test reporter. - * - * @param {Runner} runner - * @api public - */ - -function List(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , n = 0; - - runner.on('start', function(){ - console.log(); - }); - - runner.on('test', function(test){ - process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); - }); - - runner.on('pending', function(test){ - var fmt = color('checkmark', ' -') - + color('pending', ' %s'); - console.log(fmt, test.fullTitle()); - }); - - runner.on('pass', function(test){ - var fmt = color('checkmark', ' ✓') - + color('pass', ' %s: ') - + color(test.speed, '%dms'); - cursor.CR(); - console.log(fmt, test.fullTitle(), test.duration); - }); - - runner.on('fail', function(test, err){ - cursor.CR(); - console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); - }); - - runner.on('end', self.epilogue.bind(self)); -} - -/** - * Inherit from `Base.prototype`. - */ - -List.prototype = new Base; -List.prototype.constructor = List; - - -}); // module: reporters/list.js - -require.register("reporters/markdown.js", function(module, exports, require){ -/** - * Module dependencies. - */ - -var Base = require('./base') - , utils = require('../utils'); - -/** - * Expose `Markdown`. - */ - -exports = module.exports = Markdown; - -/** - * Initialize a new `Markdown` reporter. - * - * @param {Runner} runner - * @api public - */ - -function Markdown(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , total = runner.total - , level = 0 - , buf = ''; - - function title(str) { - return Array(level).join('#') + ' ' + str; - } - - function indent() { - return Array(level).join(' '); - } - - function mapTOC(suite, obj) { - var ret = obj; - obj = obj[suite.title] = obj[suite.title] || { suite: suite }; - suite.suites.forEach(function(suite){ - mapTOC(suite, obj); - }); - return ret; - } - - function stringifyTOC(obj, level) { - ++level; - var buf = ''; - var link; - for (var key in obj) { - if ('suite' == key) continue; - if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; - if (key) buf += Array(level).join(' ') + link; - buf += stringifyTOC(obj[key], level); - } - --level; - return buf; - } - - function generateTOC(suite) { - var obj = mapTOC(suite, {}); - return stringifyTOC(obj, 0); - } - - generateTOC(runner.suite); - - runner.on('suite', function(suite){ - ++level; - var slug = utils.slug(suite.fullTitle()); - buf += '' + '\n'; - buf += title(suite.title) + '\n'; - }); - - runner.on('suite end', function(suite){ - --level; - }); - - runner.on('pass', function(test){ - var code = utils.clean(test.fn.toString()); - buf += test.title + '.\n'; - buf += '\n```js\n'; - buf += code + '\n'; - buf += '```\n\n'; - }); - - runner.on('end', function(){ - process.stdout.write('# TOC\n'); - process.stdout.write(generateTOC(runner.suite)); - process.stdout.write(buf); - }); -} -}); // module: reporters/markdown.js - -require.register("reporters/min.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base'); - -/** - * Expose `Min`. - */ - -exports = module.exports = Min; - -/** - * Initialize a new `Min` minimal test reporter (best used with --watch). - * - * @param {Runner} runner - * @api public - */ - -function Min(runner) { - Base.call(this, runner); - - runner.on('start', function(){ - // clear screen - process.stdout.write('\u001b[2J'); - // set cursor position - process.stdout.write('\u001b[1;3H'); - }); - - runner.on('end', this.epilogue.bind(this)); -} - -/** - * Inherit from `Base.prototype`. - */ - -Min.prototype = new Base; -Min.prototype.constructor = Min; - -}); // module: reporters/min.js - -require.register("reporters/nyan.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , color = Base.color; - -/** - * Expose `Dot`. - */ - -exports = module.exports = NyanCat; - -/** - * Initialize a new `Dot` matrix test reporter. - * - * @param {Runner} runner - * @api public - */ - -function NyanCat(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , width = Base.window.width * .75 | 0 - , rainbowColors = this.rainbowColors = self.generateColors() - , colorIndex = this.colorIndex = 0 - , numerOfLines = this.numberOfLines = 4 - , trajectories = this.trajectories = [[], [], [], []] - , nyanCatWidth = this.nyanCatWidth = 11 - , trajectoryWidthMax = this.trajectoryWidthMax = (width - nyanCatWidth) - , scoreboardWidth = this.scoreboardWidth = 5 - , tick = this.tick = 0 - , n = 0; - - runner.on('start', function(){ - Base.cursor.hide(); - self.draw('start'); - }); - - runner.on('pending', function(test){ - self.draw('pending'); - }); - - runner.on('pass', function(test){ - self.draw('pass'); - }); - - runner.on('fail', function(test, err){ - self.draw('fail'); - }); - - runner.on('end', function(){ - Base.cursor.show(); - for (var i = 0; i < self.numberOfLines; i++) write('\n'); - self.epilogue(); - }); -} - -/** - * Draw the nyan cat with runner `status`. - * - * @param {String} status - * @api private - */ - -NyanCat.prototype.draw = function(status){ - this.appendRainbow(); - this.drawScoreboard(); - this.drawRainbow(); - this.drawNyanCat(status); - this.tick = !this.tick; -}; - -/** - * Draw the "scoreboard" showing the number - * of passes, failures and pending tests. - * - * @api private - */ - -NyanCat.prototype.drawScoreboard = function(){ - var stats = this.stats; - var colors = Base.colors; - - function draw(color, n) { - write(' '); - write('\u001b[' + color + 'm' + n + '\u001b[0m'); - write('\n'); - } - - draw(colors.green, stats.passes); - draw(colors.fail, stats.failures); - draw(colors.pending, stats.pending); - write('\n'); - - this.cursorUp(this.numberOfLines); -}; - -/** - * Append the rainbow. - * - * @api private - */ - -NyanCat.prototype.appendRainbow = function(){ - var segment = this.tick ? '_' : '-'; - var rainbowified = this.rainbowify(segment); - - for (var index = 0; index < this.numberOfLines; index++) { - var trajectory = this.trajectories[index]; - if (trajectory.length >= this.trajectoryWidthMax) trajectory.shift(); - trajectory.push(rainbowified); - } -}; - -/** - * Draw the rainbow. - * - * @api private - */ - -NyanCat.prototype.drawRainbow = function(){ - var self = this; - - this.trajectories.forEach(function(line, index) { - write('\u001b[' + self.scoreboardWidth + 'C'); - write(line.join('')); - write('\n'); - }); - - this.cursorUp(this.numberOfLines); -}; - -/** - * Draw the nyan cat with `status`. - * - * @param {String} status - * @api private - */ - -NyanCat.prototype.drawNyanCat = function(status) { - var self = this; - var startWidth = this.scoreboardWidth + this.trajectories[0].length; - - [0, 1, 2, 3].forEach(function(index) { - write('\u001b[' + startWidth + 'C'); - - switch (index) { - case 0: - write('_,------,'); - write('\n'); - break; - case 1: - var padding = self.tick ? ' ' : ' '; - write('_|' + padding + '/\\_/\\ '); - write('\n'); - break; - case 2: - var padding = self.tick ? '_' : '__'; - var tail = self.tick ? '~' : '^'; - var face; - switch (status) { - case 'pass': - face = '( ^ .^)'; - break; - case 'fail': - face = '( o .o)'; - break; - default: - face = '( - .-)'; - } - write(tail + '|' + padding + face + ' '); - write('\n'); - break; - case 3: - var padding = self.tick ? ' ' : ' '; - write(padding + '"" "" '); - write('\n'); - break; - } - }); - - this.cursorUp(this.numberOfLines); -}; - -/** - * Move cursor up `n`. - * - * @param {Number} n - * @api private - */ - -NyanCat.prototype.cursorUp = function(n) { - write('\u001b[' + n + 'A'); -}; - -/** - * Move cursor down `n`. - * - * @param {Number} n - * @api private - */ - -NyanCat.prototype.cursorDown = function(n) { - write('\u001b[' + n + 'B'); -}; - -/** - * Generate rainbow colors. - * - * @return {Array} - * @api private - */ - -NyanCat.prototype.generateColors = function(){ - var colors = []; - - for (var i = 0; i < (6 * 7); i++) { - var pi3 = Math.floor(Math.PI / 3); - var n = (i * (1.0 / 6)); - var r = Math.floor(3 * Math.sin(n) + 3); - var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3); - var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3); - colors.push(36 * r + 6 * g + b + 16); - } - - return colors; -}; - -/** - * Apply rainbow to the given `str`. - * - * @param {String} str - * @return {String} - * @api private - */ - -NyanCat.prototype.rainbowify = function(str){ - var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length]; - this.colorIndex += 1; - return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m'; -}; - -/** - * Stdout helper. - */ - -function write(string) { - process.stdout.write(string); -} - -/** - * Inherit from `Base.prototype`. - */ - -NyanCat.prototype = new Base; -NyanCat.prototype.constructor = NyanCat; - - -}); // module: reporters/nyan.js - -require.register("reporters/progress.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; - -/** - * Expose `Progress`. - */ - -exports = module.exports = Progress; - -/** - * General progress bar color. - */ - -Base.colors.progress = 90; - -/** - * Initialize a new `Progress` bar test reporter. - * - * @param {Runner} runner - * @param {Object} options - * @api public - */ - -function Progress(runner, options) { - Base.call(this, runner); - - var self = this - , options = options || {} - , stats = this.stats - , width = Base.window.width * .50 | 0 - , total = runner.total - , complete = 0 - , max = Math.max; - - // default chars - options.open = options.open || '['; - options.complete = options.complete || '▬'; - options.incomplete = options.incomplete || '⋅'; - options.close = options.close || ']'; - options.verbose = false; - - // tests started - runner.on('start', function(){ - console.log(); - cursor.hide(); - }); - - // tests complete - runner.on('test end', function(){ - complete++; - var incomplete = total - complete - , percent = complete / total - , n = width * percent | 0 - , i = width - n; - - cursor.CR(); - process.stdout.write('\u001b[J'); - process.stdout.write(color('progress', ' ' + options.open)); - process.stdout.write(Array(n).join(options.complete)); - process.stdout.write(Array(i).join(options.incomplete)); - process.stdout.write(color('progress', options.close)); - if (options.verbose) { - process.stdout.write(color('progress', ' ' + complete + ' of ' + total)); - } - }); - - // tests are complete, output some stats - // and the failures if any - runner.on('end', function(){ - cursor.show(); - console.log(); - self.epilogue(); - }); -} - -/** - * Inherit from `Base.prototype`. - */ - -Progress.prototype = new Base; -Progress.prototype.constructor = Progress; - - -}); // module: reporters/progress.js - -require.register("reporters/spec.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; - -/** - * Expose `Spec`. - */ - -exports = module.exports = Spec; - -/** - * Initialize a new `Spec` test reporter. - * - * @param {Runner} runner - * @api public - */ - -function Spec(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , indents = 0 - , n = 0; - - function indent() { - return Array(indents).join(' ') - } - - runner.on('start', function(){ - console.log(); - }); - - runner.on('suite', function(suite){ - ++indents; - console.log(color('suite', '%s%s'), indent(), suite.title); - }); - - runner.on('suite end', function(suite){ - --indents; - if (1 == indents) console.log(); - }); - - runner.on('test', function(test){ - process.stdout.write(indent() + color('pass', ' ◦ ' + test.title + ': ')); - }); - - runner.on('pending', function(test){ - var fmt = indent() + color('pending', ' - %s'); - console.log(fmt, test.title); - }); - - runner.on('pass', function(test){ - if ('fast' == test.speed) { - var fmt = indent() - + color('checkmark', ' ✓') - + color('pass', ' %s '); - cursor.CR(); - console.log(fmt, test.title); - } else { - var fmt = indent() - + color('checkmark', ' ✓') - + color('pass', ' %s ') - + color(test.speed, '(%dms)'); - cursor.CR(); - console.log(fmt, test.title, test.duration); - } - }); - - runner.on('fail', function(test, err){ - cursor.CR(); - console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); - }); - - runner.on('end', self.epilogue.bind(self)); -} - -/** - * Inherit from `Base.prototype`. - */ - -Spec.prototype = new Base; -Spec.prototype.constructor = Spec; - - -}); // module: reporters/spec.js - -require.register("reporters/tap.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; - -/** - * Expose `TAP`. - */ - -exports = module.exports = TAP; - -/** - * Initialize a new `TAP` reporter. - * - * @param {Runner} runner - * @api public - */ - -function TAP(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , n = 1; - - runner.on('start', function(){ - var total = runner.grepTotal(runner.suite); - console.log('%d..%d', 1, total); - }); - - runner.on('test end', function(){ - ++n; - }); - - runner.on('pending', function(test){ - console.log('ok %d %s # SKIP -', n, title(test)); - }); - - runner.on('pass', function(test){ - console.log('ok %d %s', n, title(test)); - }); - - runner.on('fail', function(test, err){ - console.log('not ok %d %s', n, title(test)); - console.log(err.stack.replace(/^/gm, ' ')); - }); -} - -/** - * Return a TAP-safe title of `test` - * - * @param {Object} test - * @return {String} - * @api private - */ - -function title(test) { - return test.fullTitle().replace(/#/g, ''); -} - -}); // module: reporters/tap.js - -require.register("reporters/teamcity.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base'); - -/** - * Expose `Teamcity`. - */ - -exports = module.exports = Teamcity; - -/** - * Initialize a new `Teamcity` reporter. - * - * @param {Runner} runner - * @api public - */ - -function Teamcity(runner) { - Base.call(this, runner); - var stats = this.stats; - - runner.on('start', function() { - console.log("##teamcity[testSuiteStarted name='mocha.suite']"); - }); - - runner.on('test', function(test) { - console.log("##teamcity[testStarted name='" + escape(test.fullTitle()) + "']"); - }); - - runner.on('fail', function(test, err) { - console.log("##teamcity[testFailed name='" + escape(test.fullTitle()) + "' message='" + escape(err.message) + "']"); - }); - - runner.on('pending', function(test) { - console.log("##teamcity[testIgnored name='" + escape(test.fullTitle()) + "' message='pending']"); - }); - - runner.on('test end', function(test) { - console.log("##teamcity[testFinished name='" + escape(test.fullTitle()) + "' duration='" + test.duration + "']"); - }); - - runner.on('end', function() { - console.log("##teamcity[testSuiteFinished name='mocha.suite' duration='" + stats.duration + "']"); - }); -} - -/** - * Escape the given `str`. - */ - -function escape(str) { - return str - .replace(/\|/g, "||") - .replace(/\n/g, "|n") - .replace(/\r/g, "|r") - .replace(/\[/g, "|[") - .replace(/\]/g, "|]") - .replace(/\u0085/g, "|x") - .replace(/\u2028/g, "|l") - .replace(/\u2029/g, "|p") - .replace(/'/g, "|'"); -} - -}); // module: reporters/teamcity.js - -require.register("reporters/xunit.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , utils = require('../utils') - , escape = utils.escape; - -/** - * Save timer references to avoid Sinon interfering (see GH-237). - */ - -var Date = global.Date - , setTimeout = global.setTimeout - , setInterval = global.setInterval - , clearTimeout = global.clearTimeout - , clearInterval = global.clearInterval; - -/** - * Expose `XUnit`. - */ - -exports = module.exports = XUnit; - -/** - * Initialize a new `XUnit` reporter. - * - * @param {Runner} runner - * @api public - */ - -function XUnit(runner) { - Base.call(this, runner); - var stats = this.stats - , tests = [] - , self = this; - - runner.on('pass', function(test){ - tests.push(test); - }); - - runner.on('fail', function(test){ - tests.push(test); - }); - - runner.on('end', function(){ - console.log(tag('testsuite', { - name: 'Mocha Tests' - , tests: stats.tests - , failures: stats.failures - , errors: stats.failures - , skip: stats.tests - stats.failures - stats.passes - , timestamp: (new Date).toUTCString() - , time: stats.duration / 1000 - }, false)); - - tests.forEach(test); - console.log(''); - }); -} - -/** - * Inherit from `Base.prototype`. - */ - -XUnit.prototype = new Base; -XUnit.prototype.constructor = XUnit; - - -/** - * Output tag for the given `test.` - */ - -function test(test) { - var attrs = { - classname: test.parent.fullTitle() - , name: test.title - , time: test.duration / 1000 - }; - - if ('failed' == test.state) { - var err = test.err; - attrs.message = escape(err.message); - console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack)))); - } else if (test.pending) { - console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); - } else { - console.log(tag('testcase', attrs, true) ); - } -} - -/** - * HTML tag helper. - */ - -function tag(name, attrs, close, content) { - var end = close ? '/>' : '>' - , pairs = [] - , tag; - - for (var key in attrs) { - pairs.push(key + '="' + escape(attrs[key]) + '"'); - } - - tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; - if (content) tag += content + ''; -} - -}); // module: reporters/xunit.js - -require.register("runnable.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var EventEmitter = require('browser/events').EventEmitter - , debug = require('browser/debug')('mocha:runnable') - , milliseconds = require('./ms'); - -/** - * Save timer references to avoid Sinon interfering (see GH-237). - */ - -var Date = global.Date - , setTimeout = global.setTimeout - , setInterval = global.setInterval - , clearTimeout = global.clearTimeout - , clearInterval = global.clearInterval; - -/** - * Expose `Runnable`. - */ - -module.exports = Runnable; - -/** - * Initialize a new `Runnable` with the given `title` and callback `fn`. - * - * @param {String} title - * @param {Function} fn - * @api private - */ - -function Runnable(title, fn) { - this.title = title; - this.fn = fn; - this.async = fn && fn.length; - this.sync = ! this.async; - this._timeout = 2000; - this._slow = 75; - this.timedOut = false; -} - -/** - * Inherit from `EventEmitter.prototype`. - */ - -Runnable.prototype = new EventEmitter; -Runnable.prototype.constructor = Runnable; - - -/** - * Set & get timeout `ms`. - * - * @param {Number|String} ms - * @return {Runnable|Number} ms or self - * @api private - */ - -Runnable.prototype.timeout = function(ms){ - if (0 == arguments.length) return this._timeout; - if ('string' == typeof ms) ms = milliseconds(ms); - debug('timeout %d', ms); - this._timeout = ms; - if (this.timer) this.resetTimeout(); - return this; -}; - -/** - * Set & get slow `ms`. - * - * @param {Number|String} ms - * @return {Runnable|Number} ms or self - * @api private - */ - -Runnable.prototype.slow = function(ms){ - if (0 === arguments.length) return this._slow; - if ('string' == typeof ms) ms = milliseconds(ms); - debug('timeout %d', ms); - this._slow = ms; - return this; -}; - -/** - * Return the full title generated by recursively - * concatenating the parent's full title. - * - * @return {String} - * @api public - */ - -Runnable.prototype.fullTitle = function(){ - return this.parent.fullTitle() + ' ' + this.title; -}; - -/** - * Clear the timeout. - * - * @api private - */ - -Runnable.prototype.clearTimeout = function(){ - clearTimeout(this.timer); -}; - -/** - * Inspect the runnable void of private properties. - * - * @return {String} - * @api private - */ - -Runnable.prototype.inspect = function(){ - return JSON.stringify(this, function(key, val){ - if ('_' == key[0]) return; - if ('parent' == key) return '#'; - if ('ctx' == key) return '#'; - return val; - }, 2); -}; - -/** - * Reset the timeout. - * - * @api private - */ - -Runnable.prototype.resetTimeout = function(){ - var self = this - , ms = this.timeout(); - - this.clearTimeout(); - if (ms) { - this.timer = setTimeout(function(){ - self.callback(new Error('timeout of ' + ms + 'ms exceeded')); - self.timedOut = true; - }, ms); - } -}; - -/** - * Run the test and invoke `fn(err)`. - * - * @param {Function} fn - * @api private - */ - -Runnable.prototype.run = function(fn){ - var self = this - , ms = this.timeout() - , start = new Date - , ctx = this.ctx - , finished - , emitted; - - if (ctx) ctx.runnable(this); - - // timeout - if (this.async) { - if (ms) { - this.timer = setTimeout(function(){ - done(new Error('timeout of ' + ms + 'ms exceeded')); - self.timedOut = true; - }, ms); - } - } - - // called multiple times - function multiple(err) { - if (emitted) return; - emitted = true; - self.emit('error', err || new Error('done() called multiple times')); - } - - // finished - function done(err) { - if (self.timedOut) return; - if (finished) return multiple(err); - self.clearTimeout(); - self.duration = new Date - start; - finished = true; - fn(err); - } - - // for .resetTimeout() - this.callback = done; - - // async - if (this.async) { - try { - this.fn.call(ctx, function(err){ - if (err instanceof Error) return done(err); - if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); - done(); - }); - } catch (err) { - done(err); - } - return; - } - - // sync - try { - if (!this.pending) this.fn.call(ctx); - this.duration = new Date - start; - fn(); - } catch (err) { - fn(err); - } -}; - -}); // module: runnable.js - -require.register("runner.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var EventEmitter = require('browser/events').EventEmitter - , debug = require('browser/debug')('mocha:runner') - , Test = require('./test') - , utils = require('./utils') - , filter = utils.filter - , keys = utils.keys - , noop = function(){}; - -/** - * Expose `Runner`. - */ - -module.exports = Runner; - -/** - * Initialize a `Runner` for the given `suite`. - * - * Events: - * - * - `start` execution started - * - `end` execution complete - * - `suite` (suite) test suite execution started - * - `suite end` (suite) all tests (and sub-suites) have finished - * - `test` (test) test execution started - * - `test end` (test) test completed - * - `hook` (hook) hook execution started - * - `hook end` (hook) hook complete - * - `pass` (test) test passed - * - `fail` (test, err) test failed - * - * @api public - */ - -function Runner(suite) { - var self = this; - this._globals = []; - this.suite = suite; - this.total = suite.total(); - this.failures = 0; - this.on('test end', function(test){ self.checkGlobals(test); }); - this.on('hook end', function(hook){ self.checkGlobals(hook); }); - this.grep(/.*/); - this.globals(utils.keys(global).concat(['errno'])); -} - -/** - * Inherit from `EventEmitter.prototype`. - */ - -Runner.prototype = new EventEmitter; -Runner.prototype.constructor = Runner; - - -/** - * Run tests with full titles matching `re`. Updates runner.total - * with number of tests matched. - * - * @param {RegExp} re - * @param {Boolean} invert - * @return {Runner} for chaining - * @api public - */ - -Runner.prototype.grep = function(re, invert){ - debug('grep %s', re); - this._grep = re; - this._invert = invert; - this.total = this.grepTotal(this.suite); - return this; -}; - -/** - * Returns the number of tests matching the grep search for the - * given suite. - * - * @param {Suite} suite - * @return {Number} - * @api public - */ - -Runner.prototype.grepTotal = function(suite) { - var self = this; - var total = 0; - - suite.eachTest(function(test){ - var match = self._grep.test(test.fullTitle()); - if (self._invert) match = !match; - if (match) total++; - }); - - return total; -}; - -/** - * Allow the given `arr` of globals. - * - * @param {Array} arr - * @return {Runner} for chaining - * @api public - */ - -Runner.prototype.globals = function(arr){ - if (0 == arguments.length) return this._globals; - debug('globals %j', arr); - utils.forEach(arr, function(arr){ - this._globals.push(arr); - }, this); - return this; -}; - -/** - * Check for global variable leaks. - * - * @api private - */ - -Runner.prototype.checkGlobals = function(test){ - if (this.ignoreLeaks) return; - var ok = this._globals; - var globals = keys(global); - var isNode = process.kill; - var leaks; - - // check length - 2 ('errno' and 'location' globals) - if (isNode && 1 == ok.length - globals.length) return - else if (2 == ok.length - globals.length) return; - - leaks = filterLeaks(ok, globals); - this._globals = this._globals.concat(leaks); - - if (leaks.length > 1) { - this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); - } else if (leaks.length) { - this.fail(test, new Error('global leak detected: ' + leaks[0])); - } -}; - -/** - * Fail the given `test`. - * - * @param {Test} test - * @param {Error} err - * @api private - */ - -Runner.prototype.fail = function(test, err){ - ++this.failures; - test.state = 'failed'; - if ('string' == typeof err) { - err = new Error('the string "' + err + '" was thrown, throw an Error :)'); - } - this.emit('fail', test, err); -}; - -/** - * Fail the given `hook` with `err`. - * - * Hook failures (currently) hard-end due - * to that fact that a failing hook will - * surely cause subsequent tests to fail, - * causing jumbled reporting. - * - * @param {Hook} hook - * @param {Error} err - * @api private - */ - -Runner.prototype.failHook = function(hook, err){ - this.fail(hook, err); - this.emit('end'); -}; - -/** - * Run hook `name` callbacks and then invoke `fn()`. - * - * @param {String} name - * @param {Function} function - * @api private - */ - -Runner.prototype.hook = function(name, fn){ - var suite = this.suite - , hooks = suite['_' + name] - , self = this - , timer; - - function next(i) { - var hook = hooks[i]; - if (!hook) return fn(); - self.currentRunnable = hook; - - self.emit('hook', hook); - - hook.on('error', function(err){ - self.failHook(hook, err); - }); - - hook.run(function(err){ - hook.removeAllListeners('error'); - var testError = hook.error(); - if (testError) self.fail(self.test, testError); - if (err) return self.failHook(hook, err); - self.emit('hook end', hook); - next(++i); - }); - } - - process.nextTick(function(){ - next(0); - }); -}; - -/** - * Run hook `name` for the given array of `suites` - * in order, and callback `fn(err)`. - * - * @param {String} name - * @param {Array} suites - * @param {Function} fn - * @api private - */ - -Runner.prototype.hooks = function(name, suites, fn){ - var self = this - , orig = this.suite; - - function next(suite) { - self.suite = suite; - - if (!suite) { - self.suite = orig; - return fn(); - } - - self.hook(name, function(err){ - if (err) { - self.suite = orig; - return fn(err); - } - - next(suites.pop()); - }); - } - - next(suites.pop()); -}; - -/** - * Run hooks from the top level down. - * - * @param {String} name - * @param {Function} fn - * @api private - */ - -Runner.prototype.hookUp = function(name, fn){ - var suites = [this.suite].concat(this.parents()).reverse(); - this.hooks(name, suites, fn); -}; - -/** - * Run hooks from the bottom up. - * - * @param {String} name - * @param {Function} fn - * @api private - */ - -Runner.prototype.hookDown = function(name, fn){ - var suites = [this.suite].concat(this.parents()); - this.hooks(name, suites, fn); -}; - -/** - * Return an array of parent Suites from - * closest to furthest. - * - * @return {Array} - * @api private - */ - -Runner.prototype.parents = function(){ - var suite = this.suite - , suites = []; - while (suite = suite.parent) suites.push(suite); - return suites; -}; - -/** - * Run the current test and callback `fn(err)`. - * - * @param {Function} fn - * @api private - */ - -Runner.prototype.runTest = function(fn){ - var test = this.test - , self = this; - - try { - test.on('error', function(err){ - self.fail(test, err); - }); - test.run(fn); - } catch (err) { - fn(err); - } -}; - -/** - * Run tests in the given `suite` and invoke - * the callback `fn()` when complete. - * - * @param {Suite} suite - * @param {Function} fn - * @api private - */ - -Runner.prototype.runTests = function(suite, fn){ - var self = this - , tests = suite.tests.slice() - , test; - - function next(err) { - // if we bail after first err - if (self.failures && suite._bail) return fn(); - - // next test - test = tests.shift(); - - // all done - if (!test) return fn(); - - // grep - var match = self._grep.test(test.fullTitle()); - if (self._invert) match = !match; - if (!match) return next(); - - // pending - if (test.pending) { - self.emit('pending', test); - self.emit('test end', test); - return next(); - } - - // execute test and hook(s) - self.emit('test', self.test = test); - self.hookDown('beforeEach', function(){ - self.currentRunnable = self.test; - self.runTest(function(err){ - test = self.test; - - if (err) { - self.fail(test, err); - self.emit('test end', test); - return self.hookUp('afterEach', next); - } - - test.state = 'passed'; - self.emit('pass', test); - self.emit('test end', test); - self.hookUp('afterEach', next); - }); - }); - } - - this.next = next; - next(); -}; - -/** - * Run the given `suite` and invoke the - * callback `fn()` when complete. - * - * @param {Suite} suite - * @param {Function} fn - * @api private - */ - -Runner.prototype.runSuite = function(suite, fn){ - var total = this.grepTotal(suite) - , self = this - , i = 0; - - debug('run suite %s', suite.fullTitle()); - - if (!total) return fn(); - - this.emit('suite', this.suite = suite); - - function next() { - var curr = suite.suites[i++]; - if (!curr) return done(); - self.runSuite(curr, next); - } - - function done() { - self.suite = suite; - self.hook('afterAll', function(){ - self.emit('suite end', suite); - fn(); - }); - } - - this.hook('beforeAll', function(){ - self.runTests(suite, next); - }); -}; - -/** - * Handle uncaught exceptions. - * - * @param {Error} err - * @api private - */ - -Runner.prototype.uncaught = function(err){ - debug('uncaught exception %s', err.message); - var runnable = this.currentRunnable; - if (!runnable || 'failed' == runnable.state) return; - runnable.clearTimeout(); - err.uncaught = true; - this.fail(runnable, err); - - // recover from test - if ('test' == runnable.type) { - this.emit('test end', runnable); - this.hookUp('afterEach', this.next); - return; - } - - // bail on hooks - this.emit('end'); -}; - -/** - * Run the root suite and invoke `fn(failures)` - * on completion. - * - * @param {Function} fn - * @return {Runner} for chaining - * @api public - */ - -Runner.prototype.run = function(fn){ - var self = this - , fn = fn || function(){}; - - debug('start'); - - // uncaught callback - function uncaught(err) { - self.uncaught(err); - } - - // callback - this.on('end', function(){ - debug('end'); - process.removeListener('uncaughtException', uncaught); - fn(self.failures); - }); - - // run suites - this.emit('start'); - this.runSuite(this.suite, function(){ - debug('finished running'); - self.emit('end'); - }); - - // uncaught exception - process.on('uncaughtException', uncaught); - - return this; -}; - -/** - * Filter leaks with the given globals flagged as `ok`. - * - * @param {Array} ok - * @param {Array} globals - * @return {Array} - * @api private - */ - -function filterLeaks(ok, globals) { - return filter(globals, function(key){ - var matched = filter(ok, function(ok){ - if (~ok.indexOf('*')) return 0 == key.indexOf(ok.split('*')[0]); - return key == ok; - }); - return matched.length == 0 && (!global.navigator || 'onerror' !== key); - }); -} - -}); // module: runner.js - -require.register("suite.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var EventEmitter = require('browser/events').EventEmitter - , debug = require('browser/debug')('mocha:suite') - , milliseconds = require('./ms') - , utils = require('./utils') - , Hook = require('./hook'); - -/** - * Expose `Suite`. - */ - -exports = module.exports = Suite; - -/** - * Create a new `Suite` with the given `title` - * and parent `Suite`. When a suite with the - * same title is already present, that suite - * is returned to provide nicer reporter - * and more flexible meta-testing. - * - * @param {Suite} parent - * @param {String} title - * @return {Suite} - * @api public - */ - -exports.create = function(parent, title){ - var suite = new Suite(title, parent.ctx); - suite.parent = parent; - if (parent.pending) suite.pending = true; - title = suite.fullTitle(); - parent.addSuite(suite); - return suite; -}; - -/** - * Initialize a new `Suite` with the given - * `title` and `ctx`. - * - * @param {String} title - * @param {Context} ctx - * @api private - */ - -function Suite(title, ctx) { - this.title = title; - this.ctx = ctx; - this.suites = []; - this.tests = []; - this.pending = false; - this._beforeEach = []; - this._beforeAll = []; - this._afterEach = []; - this._afterAll = []; - this.root = !title; - this._timeout = 2000; - this._slow = 75; - this._bail = false; -} - -/** - * Inherit from `EventEmitter.prototype`. - */ - -Suite.prototype = new EventEmitter; -Suite.prototype.constructor = Suite; - - -/** - * Return a clone of this `Suite`. - * - * @return {Suite} - * @api private - */ - -Suite.prototype.clone = function(){ - var suite = new Suite(this.title); - debug('clone'); - suite.ctx = this.ctx; - suite.timeout(this.timeout()); - suite.slow(this.slow()); - suite.bail(this.bail()); - return suite; -}; - -/** - * Set timeout `ms` or short-hand such as "2s". - * - * @param {Number|String} ms - * @return {Suite|Number} for chaining - * @api private - */ - -Suite.prototype.timeout = function(ms){ - if (0 == arguments.length) return this._timeout; - if ('string' == typeof ms) ms = milliseconds(ms); - debug('timeout %d', ms); - this._timeout = parseInt(ms, 10); - return this; -}; - -/** - * Set slow `ms` or short-hand such as "2s". - * - * @param {Number|String} ms - * @return {Suite|Number} for chaining - * @api private - */ - -Suite.prototype.slow = function(ms){ - if (0 === arguments.length) return this._slow; - if ('string' == typeof ms) ms = milliseconds(ms); - debug('slow %d', ms); - this._slow = ms; - return this; -}; - -/** - * Sets whether to bail after first error. - * - * @parma {Boolean} bail - * @return {Suite|Number} for chaining - * @api private - */ - -Suite.prototype.bail = function(bail){ - if (0 == arguments.length) return this._bail; - debug('bail %s', bail); - this._bail = bail; - return this; -}; - -/** - * Run `fn(test[, done])` before running tests. - * - * @param {Function} fn - * @return {Suite} for chaining - * @api private - */ - -Suite.prototype.beforeAll = function(fn){ - if (this.pending) return this; - var hook = new Hook('"before all" hook', fn); - hook.parent = this; - hook.timeout(this.timeout()); - hook.slow(this.slow()); - hook.ctx = this.ctx; - this._beforeAll.push(hook); - this.emit('beforeAll', hook); - return this; -}; - -/** - * Run `fn(test[, done])` after running tests. - * - * @param {Function} fn - * @return {Suite} for chaining - * @api private - */ - -Suite.prototype.afterAll = function(fn){ - if (this.pending) return this; - var hook = new Hook('"after all" hook', fn); - hook.parent = this; - hook.timeout(this.timeout()); - hook.slow(this.slow()); - hook.ctx = this.ctx; - this._afterAll.push(hook); - this.emit('afterAll', hook); - return this; -}; - -/** - * Run `fn(test[, done])` before each test case. - * - * @param {Function} fn - * @return {Suite} for chaining - * @api private - */ - -Suite.prototype.beforeEach = function(fn){ - if (this.pending) return this; - var hook = new Hook('"before each" hook', fn); - hook.parent = this; - hook.timeout(this.timeout()); - hook.slow(this.slow()); - hook.ctx = this.ctx; - this._beforeEach.push(hook); - this.emit('beforeEach', hook); - return this; -}; - -/** - * Run `fn(test[, done])` after each test case. - * - * @param {Function} fn - * @return {Suite} for chaining - * @api private - */ - -Suite.prototype.afterEach = function(fn){ - if (this.pending) return this; - var hook = new Hook('"after each" hook', fn); - hook.parent = this; - hook.timeout(this.timeout()); - hook.slow(this.slow()); - hook.ctx = this.ctx; - this._afterEach.push(hook); - this.emit('afterEach', hook); - return this; -}; - -/** - * Add a test `suite`. - * - * @param {Suite} suite - * @return {Suite} for chaining - * @api private - */ - -Suite.prototype.addSuite = function(suite){ - suite.parent = this; - suite.timeout(this.timeout()); - suite.slow(this.slow()); - suite.bail(this.bail()); - this.suites.push(suite); - this.emit('suite', suite); - return this; -}; - -/** - * Add a `test` to this suite. - * - * @param {Test} test - * @return {Suite} for chaining - * @api private - */ - -Suite.prototype.addTest = function(test){ - test.parent = this; - test.timeout(this.timeout()); - test.slow(this.slow()); - test.ctx = this.ctx; - this.tests.push(test); - this.emit('test', test); - return this; -}; - -/** - * Return the full title generated by recursively - * concatenating the parent's full title. - * - * @return {String} - * @api public - */ - -Suite.prototype.fullTitle = function(){ - if (this.parent) { - var full = this.parent.fullTitle(); - if (full) return full + ' ' + this.title; - } - return this.title; -}; - -/** - * Return the total number of tests. - * - * @return {Number} - * @api public - */ - -Suite.prototype.total = function(){ - return utils.reduce(this.suites, function(sum, suite){ - return sum + suite.total(); - }, 0) + this.tests.length; -}; - -/** - * Iterates through each suite recursively to find - * all tests. Applies a function in the format - * `fn(test)`. - * - * @param {Function} fn - * @return {Suite} - * @api private - */ - -Suite.prototype.eachTest = function(fn){ - utils.forEach(this.tests, fn); - utils.forEach(this.suites, function(suite){ - suite.eachTest(fn); - }); - return this; -}; - -}); // module: suite.js - -require.register("test.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Runnable = require('./runnable'); - -/** - * Expose `Test`. - */ - -module.exports = Test; - -/** - * Initialize a new `Test` with the given `title` and callback `fn`. - * - * @param {String} title - * @param {Function} fn - * @api private - */ - -function Test(title, fn) { - Runnable.call(this, title, fn); - this.pending = !fn; - this.type = 'test'; -} - -/** - * Inherit from `Runnable.prototype`. - */ - -Test.prototype = new Runnable; -Test.prototype.constructor = Test; - - -}); // module: test.js - -require.register("utils.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var fs = require('browser/fs') - , path = require('browser/path') - , join = path.join - , debug = require('browser/debug')('mocha:watch'); - -/** - * Ignored directories. - */ - -var ignore = ['node_modules', '.git']; - -/** - * Escape special characters in the given string of html. - * - * @param {String} html - * @return {String} - * @api private - */ - -exports.escape = function(html){ - return String(html) - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(//g, '>'); -}; - -/** - * Array#forEach (<=IE8) - * - * @param {Array} array - * @param {Function} fn - * @param {Object} scope - * @api private - */ - -exports.forEach = function(arr, fn, scope){ - for (var i = 0, l = arr.length; i < l; i++) - fn.call(scope, arr[i], i); -}; - -/** - * Array#indexOf (<=IE8) - * - * @parma {Array} arr - * @param {Object} obj to find index of - * @param {Number} start - * @api private - */ - -exports.indexOf = function(arr, obj, start){ - for (var i = start || 0, l = arr.length; i < l; i++) { - if (arr[i] === obj) - return i; - } - return -1; -}; - -/** - * Array#reduce (<=IE8) - * - * @param {Array} array - * @param {Function} fn - * @param {Object} initial value - * @api private - */ - -exports.reduce = function(arr, fn, val){ - var rval = val; - - for (var i = 0, l = arr.length; i < l; i++) { - rval = fn(rval, arr[i], i, arr); - } - - return rval; -}; - -/** - * Array#filter (<=IE8) - * - * @param {Array} array - * @param {Function} fn - * @api private - */ - -exports.filter = function(arr, fn){ - var ret = []; - - for (var i = 0, l = arr.length; i < l; i++) { - var val = arr[i]; - if (fn(val, i, arr)) ret.push(val); - } - - return ret; -}; - -/** - * Object.keys (<=IE8) - * - * @param {Object} obj - * @return {Array} keys - * @api private - */ - -exports.keys = Object.keys || function(obj) { - var keys = [] - , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 - - for (var key in obj) { - if (has.call(obj, key)) { - keys.push(key); - } - } - - return keys; -}; - -/** - * Watch the given `files` for changes - * and invoke `fn(file)` on modification. - * - * @param {Array} files - * @param {Function} fn - * @api private - */ - -exports.watch = function(files, fn){ - var options = { interval: 100 }; - files.forEach(function(file){ - debug('file %s', file); - fs.watchFile(file, options, function(curr, prev){ - if (prev.mtime < curr.mtime) fn(file); - }); - }); -}; - -/** - * Ignored files. - */ - -function ignored(path){ - return !~ignore.indexOf(path); -} - -/** - * Lookup files in the given `dir`. - * - * @return {Array} - * @api private - */ - -exports.files = function(dir, ret){ - ret = ret || []; - - fs.readdirSync(dir) - .filter(ignored) - .forEach(function(path){ - path = join(dir, path); - if (fs.statSync(path).isDirectory()) { - exports.files(path, ret); - } else if (path.match(/\.(js|coffee)$/)) { - ret.push(path); - } - }); - - return ret; -}; - -/** - * Compute a slug from the given `str`. - * - * @param {String} str - * @return {String} - * @api private - */ - -exports.slug = function(str){ - return str - .toLowerCase() - .replace(/ +/g, '-') - .replace(/[^-\w]/g, ''); -}; - -/** - * Strip the function definition from `str`, - * and re-indent for pre whitespace. - */ - -exports.clean = function(str) { - str = str - .replace(/^function *\(.*\) *{/, '') - .replace(/\s+\}$/, ''); - - var spaces = str.match(/^\n?( *)/)[1].length - , re = new RegExp('^ {' + spaces + '}', 'gm'); - - str = str.replace(re, ''); - - return exports.trim(str); -}; - -/** - * Escape regular expression characters in `str`. - * - * @param {String} str - * @return {String} - * @api private - */ - -exports.escapeRegexp = function(str){ - return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); -}; - -/** - * Trim the given `str`. - * - * @param {String} str - * @return {String} - * @api private - */ - -exports.trim = function(str){ - return str.replace(/^\s+|\s+$/g, ''); -}; - -/** - * Parse the given `qs`. - * - * @param {String} qs - * @return {Object} - * @api private - */ - -exports.parseQuery = function(qs){ - return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair){ - var i = pair.indexOf('=') - , key = pair.slice(0, i) - , val = pair.slice(++i); - - obj[key] = decodeURIComponent(val); - return obj; - }, {}); -}; - -/** - * Highlight the given string of `js`. - * - * @param {String} js - * @return {String} - * @api private - */ - -function highlight(js) { - return js - .replace(//g, '>') - .replace(/\/\/(.*)/gm, '//$1') - .replace(/('.*?')/gm, '$1') - .replace(/(\d+\.\d+)/gm, '$1') - .replace(/(\d+)/gm, '$1') - .replace(/\bnew *(\w+)/gm, 'new $1') - .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '$1') -} - -/** - * Highlight the contents of tag `name`. - * - * @param {String} name - * @api private - */ - -exports.highlightTags = function(name) { - var code = document.getElementsByTagName(name); - for (var i = 0, len = code.length; i < len; ++i) { - code[i].innerHTML = highlight(code[i].innerHTML); - } -}; - -}); // module: utils.js -/** - * Node shims. - * - * These are meant only to allow - * mocha.js to run untouched, not - * to allow running node code in - * the browser. - */ - -process = {}; -process.exit = function(status){}; -process.stdout = {}; -global = window; - -/** - * next tick implementation. - */ - -process.nextTick = (function(){ - // postMessage behaves badly on IE8 - if (window.ActiveXObject || !window.postMessage) { - return function(fn){ fn() }; - } - - // based on setZeroTimeout by David Baron - // - http://dbaron.org/log/20100309-faster-timeouts - var timeouts = [] - , name = 'mocha-zero-timeout' - - window.addEventListener('message', function(e){ - if (e.source == window && e.data == name) { - if (e.stopPropagation) e.stopPropagation(); - if (timeouts.length) timeouts.shift()(); - } - }, true); - - return function(fn){ - timeouts.push(fn); - window.postMessage(name, '*'); - } -})(); - -/** - * Remove uncaughtException listener. - */ - -process.removeListener = function(e){ - if ('uncaughtException' == e) { - window.onerror = null; - } -}; - -/** - * Implements uncaughtException listener. - */ - -process.on = function(e, fn){ - if ('uncaughtException' == e) { - window.onerror = fn; - } -}; - -// boot -;(function(){ - - /** - * Expose mocha. - */ - - var Mocha = window.Mocha = require('mocha'), - mocha = window.mocha = new Mocha({ reporter: 'html' }); - - /** - * Override ui to ensure that the ui functions are initialized. - * Normally this would happen in Mocha.prototype.loadFiles. - */ - - mocha.ui = function(ui){ - Mocha.prototype.ui.call(this, ui); - this.suite.emit('pre-require', window, null, this); - return this; - }; - - /** - * Setup mocha with the given setting options. - */ - - mocha.setup = function(opts){ - if ('string' == typeof opts) opts = { ui: opts }; - for (var opt in opts) this[opt](opts[opt]); - return this; - }; - - /** - * Run mocha, returning the Runner. - */ - - mocha.run = function(fn){ - var options = mocha.options; - mocha.globals('location'); - - var query = Mocha.utils.parseQuery(window.location.search || ''); - if (query.grep) mocha.grep(query.grep); - - return Mocha.prototype.run.call(mocha, function(){ - Mocha.utils.highlightTags('code'); - if (fn) fn(); - }); - }; -})(); -})(); diff --git a/testem.json b/testem.json new file mode 100644 index 0000000..7494912 --- /dev/null +++ b/testem.json @@ -0,0 +1,11 @@ +{ + "test_page": "test/index.html", + "parallel": 5, + "before_tests": "npm run build-all", + "launchers": { + "Mocha": { + "command": "./node_modules/.bin/mocha test/main.js" + } + }, + "launch_in_ci": ["PhantomJS", "Mocha"] +}