remove jasmine, added chai+mocha with promise support

This commit is contained in:
Chris Karlof 2013-02-09 20:32:16 -08:00
Родитель 9dc1a87892
Коммит a52765498a
19 изменённых файлов: 13138 добавлений и 54 удалений

Просмотреть файл

@ -6,10 +6,12 @@
*
*/
// Gombot is optional
// Gombot and importedModules are optional. (However, importedModules need to be
// provided in Firefox and defined in some other way for other platforms.)
var _Gombot = function(importedModules, Gombot) {
Gombot = Gombot || {};
importedModules = importedModules || {};
function getModule(name) {
if (typeof window !== "undefined" && typeof window[name] !== "undefined") {
@ -54,7 +56,6 @@ var _Gombot = function(importedModules, Gombot) {
Gombot.CapturedCredentialStorage = getModule("CapturedCredentialStorage")(Gombot, getModule("Uri"));
Gombot.Linker = getModule("Linker")(Gombot);
Gombot.AccountManager = getModule("AccountManager")(Gombot, _);
Gombot.CommandHandler = getModule("CommandHandler")(Gombot, Gombot.Messaging, _);
Gombot.Pages = getModule("Pages")(Gombot);
Gombot.Crypto = getModule("GombotCrypto");
Gombot.User = getModule("User")(Backbone, _, Gombot);
@ -82,6 +83,12 @@ var _Gombot = function(importedModules, Gombot) {
options = options || {};
options.storeName = options.storeName || "users";
options.callback = options.callback || checkFirstRun;
if (!options.testing) {
Gombot.CommandHandler = getModule("CommandHandler")(Gombot, Gombot.Messaging, _);
}
// TODO: refactor this code (maybe using promises?) so the SyncAdapter
// and UserCollection don't need to be created inside this init function.
// Also maybe move the storage creation out of here
new Gombot.Storage(options.storeName, function(store) {
Gombot.SyncAdapter = getModule("SyncAdapter")(Gombot, Gombot.Crypto, store, _);
Gombot.UserCollection = getModule("UserCollection")(Backbone, _, Gombot, store);
@ -113,6 +120,6 @@ var _Gombot = function(importedModules, Gombot) {
if (typeof module !== "undefined" && module.exports) {
module.exports = _Gombot; // export namespace constructor, for Firefox
} else { // otherwise, just create the global Gombot namespace and init
var Gombot = _Gombot({});
Gombot.init();
var gGombot = _Gombot({});
gGombot.init();
}

Просмотреть файл

@ -2,6 +2,7 @@ var Pages = function(Gombot) {
const PAGES_PATH="pages/first_run/";
// Firefox specific parts
if (typeof chrome === "undefined") {
// define a pageMod for resource urls
var pageMod = require("page-mod");

Просмотреть файл

@ -1,7 +1,7 @@
<!doctype html>
<html>
<head>
<link href="../../lib/bootstrap.css" rel="stylesheet">
<link href="../../../lib/bootstrap.css" rel="stylesheet">
<script src="../../lib/jquery.js"></script>
<script src="debug_settings.js"></script>
<style>
@ -16,6 +16,7 @@
</head>
<body>
<a href="../../../spec/runner.html">Tests</a>
<div class="container">
<div class="row">
<div class="span8">

1569
lib/q-0.8.12.js Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

@ -1 +1 @@
Subproject commit 081416b488a5e9f07893644c9b033d5b5a105d45
Subproject commit 4fe6d2ae6e8c30237498e067f6ac91faaee65d2f

67
spec/helpers.js Normal file
Просмотреть файл

@ -0,0 +1,67 @@
var SpecHelpers = function(Gombot) {
var LocalStorage = Gombot.LocalStorage;
var User = Gombot.User;
var self = {
STORE_NAME: "testUsers",
TEST_PASSWORD: "pässwörd",
TEST_PIN: "1234",
generateTestEmail: function() {
return "test+"+Math.floor((1+Math.random())*1000000)+"@test.com";
},
getLocalStorageKeyForUser: function(model) {
return this.STORE_NAME+"-"+model.id;
},
getLocalStorageItem: function(name) {
var dfd = Q.defer();
LocalStorage.getItem(name, function(value) {
dfd.resolve(value);
});
return dfd.promise;
},
validateUserAgainstLocalStorage: function(user) {
describe("User model matches data in LocalStorage", function() {
it("should have created a record for the User", function() {
var indexOfUserRecord = -1;
runs(function() {
LocalStorage.getItem(self.STORE_NAME, function(store) {
var records = (store && store.split(",")) || [];
indexOfUserRecord = records.indexOf(user.id) >= 0
});
});
waitsFor(function() { return indexOfUserRecord >=0; },
"Could not find User record",
100);
});
});
// LocalStorage.getItem(getStorageKeyForUser(user), function(json) {
// var attrs = JSON.parse(json);
// passed = passed && (user.get("email").length > 0) && (user.get("email") === attrs.email);
// passed = passed && (user.get("version").length > 0) && (user.get("version") === attrs.version)
// passed = passed && (user.id.length > 0) && (user.id === attrs.id);
// passed = passed && (attrs.ciphertext.length > 0);
// passed = passed && (typeof attrs.pin === "undefined"); // pin should be missing
// cb(test, passed);
// });
},
createUser: function(options) {
var dfd = Q.defer();
options = options || {};
var email = options.email || self.generateTestEmail();
var user = new User({ email: email, pin: self.TEST_PIN });
// TODO: throw in one login entry here
var o = _.clone(options);
user.save(null, _.extend(o, { success: function() {
dfd.resolve(user);
}, error: function(err) {
dfd.reject(err);
}}));
return dfd.promise;
}
};
return self;
};

Просмотреть файл

@ -0,0 +1,405 @@
(function (chaiAsPromised) {
"use strict";
// Module systems magic dance.
if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
// NodeJS
module.exports = chaiAsPromised;
} else if (typeof define === "function" && define.amd) {
// AMD
define(function () {
return chaiAsPromised;
});
} else {
// Other environment (usually <script> tag): plug in to global chai instance directly.
chai.use(chaiAsPromised);
}
}(function chaiAsPromised(chai, utils) {
"use strict";
var Assertion = chai.Assertion;
var assert = chai.assert;
function assertIsAboutPromise(assertion) {
if (typeof assertion._obj.then !== "function") {
throw new TypeError(utils.inspect(assertion._obj) + " is not a promise!");
}
if (typeof assertion._obj.pipe === "function") {
throw new TypeError("Chai as Promised is incompatible with jQuery's so-called “promises.” Sorry!");
}
}
function property(name, asserter) {
utils.addProperty(Assertion.prototype, name, function () {
assertIsAboutPromise(this);
return asserter.apply(this, arguments);
});
}
function method(name, asserter) {
utils.addMethod(Assertion.prototype, name, function () {
assertIsAboutPromise(this);
return asserter.apply(this, arguments);
});
}
function notify(promise, callback) {
return promise.then(function () { callback(); }, callback);
}
function addNotifyMethod(extensiblePromise) {
extensiblePromise.notify = function (callback) {
return notify(extensiblePromise, callback);
};
}
function fulfilledAsserter() {
/*jshint validthis:true */
var assertion = this;
var transformedPromise = assertion._obj.then(
function (value) {
if (utils.flag(assertion, "negate")) {
// If we're negated, `this.assert`'s behavior is actually flipped, so `this.assert(true, ...)` will
// throw an error, as desired.
assertion.assert(true, null, "expected promise to be rejected but it was fulfilled with " +
utils.inspect(value));
}
return value;
},
function (reason) {
// If we're in a negated state (i.e. `.not.fulfilled`) then this assertion will get flipped and thus
// pass, as desired.
assertion.assert(false, "expected promise to be fulfilled but it was rejected with " +
utils.inspect(reason));
}
);
return makeAssertionPromise(transformedPromise, assertion);
}
function rejectedAsserter() {
// THIS SHIT IS COMPLICATED. Best illustrated by exhaustive example.
////////////////////////////////////////////////////////////////////
// `fulfilledPromise.should.be.rejected`:
// `onOriginalFulfilled` → `this.assert(false, …)` throws → rejects
// `fulfilledPromise.should.not.be.rejected`:
// `onOriginalFulfilled` → `this.assert(false, …)` does nothing → fulfills
// `rejectedPromise.should.be.rejected`:
// `onOriginalRejected` does nothing relevant → fulfills
// `rejectedPromise.should.not.be.rejected`:
// `onOriginalRejected` → `this.assert(true, …)` throws → rejects
// `rejectedPromise.should.be.rejected.with(xxx)`:
// `onOriginalRejected` saves `rejectionReason` → fulfills →
// `with(xxx)` called → `onTransformedFulfilled` → assert about xxx → fulfills/rejects based on asserts
// `rejectedPromise.should.not.be.rejected.with(xxx)`:
// `onOriginalRejected` saves `rejectionReason`, `this.assert(true, …)` throws → rejects →
// `with(xxx)` called → `onTransformedRejected` → assert about xxx → fulfills/rejects based on asserts
// `fulfilledPromise.should.be.rejected.with(xxx)`:
// `onOriginalFulfilled` → `this.assert(false, …)` throws → rejects →
// `with(xxx)` called → `onTransformedRejected` → `this.assert(false, …)` throws → rejected
// `fulfilledPromise.should.not.be.rejected.with(xxx)`:
// `onOriginalFulfilled` → `this.assert(false, …)` does nothing → fulfills →
// `with(xxx)` called → `onTransformedFulfilled` → fulfills
/*jshint validthis:true */
var assertion = this;
var rejectionReason = null;
function onOriginalFulfilled(value) {
assertion.assert(false, "expected promise to be rejected but it was fulfilled with " + utils.inspect(value));
}
function onOriginalRejected(reason) {
// Store the reason so that `with` can look at it later. Be sure to do this before asserting, since
// throwing an error from the assert would cancel the process.
rejectionReason = reason;
if (utils.flag(assertion, "negate")) {
assertion.assert(true, null, "expected promise to be fulfilled but it was rejected with " +
utils.inspect(reason));
}
// If we didn't throw from the assert, transform rejections into fulfillments, by not re-throwing the
// reason.
}
function withMethod(Constructor, message) {
var desiredReason = null;
if (Constructor instanceof RegExp || typeof Constructor === "string") {
message = Constructor;
Constructor = null;
} else if (Constructor && Constructor instanceof Error) {
desiredReason = Constructor;
Constructor = null;
message = null;
}
var messageVerb = null;
var messageIsGood = null;
if (message instanceof RegExp) {
messageVerb = "matching";
messageIsGood = function () {
return message.test(rejectionReason.message);
};
} else {
messageVerb = "including";
messageIsGood = function () {
return rejectionReason.message.indexOf(message) !== -1;
};
}
function constructorIsGood() {
return rejectionReason instanceof Constructor;
}
function matchesDesiredReason() {
return rejectionReason === desiredReason;
}
function onTransformedFulfilled() {
if (!utils.flag(assertion, "negate")) {
if (desiredReason) {
assertion.assert(matchesDesiredReason(),
null,
"expected promise to be rejected with " + utils.inspect(desiredReason) + " but " +
"it was rejected with " + utils.inspect(rejectionReason));
}
if (Constructor) {
assertion.assert(constructorIsGood(),
"expected promise to be rejected with " + Constructor.prototype.name + " but it " +
"was rejected with " + utils.inspect(rejectionReason));
}
if (message) {
assertion.assert(messageIsGood(),
"expected promise to be rejected with an error " + messageVerb + " " + message +
" but got " + utils.inspect(rejectionReason.message));
}
}
}
function onTransformedRejected() {
if (utils.flag(assertion, "negate")) {
if (desiredReason) {
assertion.assert(matchesDesiredReason(),
null,
"expected promise to not be rejected with " + utils.inspect(desiredReason));
}
if (Constructor) {
assertion.assert(constructorIsGood(),
null,
"expected promise to not be rejected with " + Constructor.prototype.name);
}
if (message) {
assertion.assert(messageIsGood(),
null,
"expected promise to be not be rejected with an error " + messageVerb + " " +
message);
}
} else {
if (desiredReason) {
assertion.assert(false,
"expected promise to be rejected with " + utils.inspect(desiredReason) +
" but it was fulfilled");
}
if (Constructor) {
assertion.assert(false, "expected promise to be rejected with " + Constructor.prototype.name +
" but it was fulfilled");
}
if (message) {
assertion.assert(false, "expected promise to be rejected with an error " + messageVerb + " " +
message + " but it was fulfilled");
}
}
}
return makeAssertionPromise(
transformedPromise.then(onTransformedFulfilled, onTransformedRejected),
assertion
);
}
var derivedPromise = assertion._obj.then(onOriginalFulfilled, onOriginalRejected);
var transformedPromise = makeAssertionPromise(derivedPromise, assertion);
Object.defineProperty(transformedPromise, "with", { enumerable: true, configurable: true, value: withMethod });
return transformedPromise;
}
function isChaiAsPromisedAsserter(asserterName) {
return ["fulfilled", "rejected", "broken", "eventually", "become"].indexOf(asserterName) !== -1;
}
function makeAssertionPromiseToDoAsserter(currentAssertionPromise, previousAssertionPromise, doAsserter) {
var promiseToDoAsserter = currentAssertionPromise.then(function (fulfillmentValue) {
// The previous assertion promise might have picked up some flags while waiting for fulfillment.
utils.transferFlags(previousAssertionPromise, currentAssertionPromise);
// Replace the object flag with the fulfillment value, so that doAsserter can operate properly.
utils.flag(currentAssertionPromise, "object", fulfillmentValue);
// Perform the actual asserter action and return the result of it.
return doAsserter();
});
return makeAssertionPromise(promiseToDoAsserter, currentAssertionPromise);
}
function makeAssertionPromise(promise, baseAssertion) {
// An assertion-promise is an (extensible!) promise with the following additions:
var assertionPromise = Object.create(promise);
// 1. A `notify` method.
addNotifyMethod(assertionPromise);
// 2. An `assert` method that acts exactly as it would on an assertion. This is called by promisified
// asserters after the promise fulfills.
assertionPromise.assert = function () {
return Assertion.prototype.assert.apply(assertionPromise, arguments);
};
// 3. Chai asserters, which act upon the promise's fulfillment value.
var asserterNames = Object.getOwnPropertyNames(Assertion.prototype);
asserterNames.forEach(function (asserterName) {
// We already added `notify` and `assert`; don't mess with those.
if (asserterName === "notify" || asserterName === "assert") {
return;
}
// Only add asserters for other libraries; poison-pill Chai as Promised ones.
if (isChaiAsPromisedAsserter(asserterName)) {
utils.addProperty(assertionPromise, asserterName, function () {
throw new Error("Cannot use Chai as Promised asserters more than once in an assertion.");
});
return;
}
// The asserter will need to be added differently depending on its type. In all cases we use
// `makeAssertionPromiseToDoAsserter`, which, given this current `assertionPromise` we are going to
// return, plus the `baseAssertion` we are basing it off of, will return a new assertion-promise that
// builds off of `assertionPromise` and `baseAssertion` to perform the actual asserter action upon
// fulfillment.
var propertyDescriptor = Object.getOwnPropertyDescriptor(Assertion.prototype, asserterName);
if (typeof propertyDescriptor.value === "function") {
// Case 1: simple method asserters
utils.addMethod(assertionPromise, asserterName, function () {
var args = arguments;
return makeAssertionPromiseToDoAsserter(assertionPromise, baseAssertion, function () {
return propertyDescriptor.value.apply(assertionPromise, args);
});
});
} else if (typeof propertyDescriptor.get === "function") {
// Case 2: property asserters. These break down into two subcases: chainable methods, and pure
// properties. An example of the former is `a`/`an`: `.should.be.an.instanceOf` vs.
// `should.be.an("object")`.
var isChainableMethod = false;
try {
isChainableMethod = typeof propertyDescriptor.get.call({}) === "function";
} catch (e) { }
if (isChainableMethod) {
// Case 2A: chainable methods. Recreate the chainable method, but operating on the augmented
// promise. We need to copy both the assertion behavior and the chaining behavior, since the
// chaining behavior might for example set flags on the object.
utils.addChainableMethod(
assertionPromise,
asserterName,
function () {
var args = arguments;
return makeAssertionPromiseToDoAsserter(assertionPromise, baseAssertion, function () {
// Due to https://github.com/chaijs/chai/commit/514dd6ce466d7b4110b38345e4073d586c017f3f
// we can't use `propertyDescriptor.get().apply`.
return Function.prototype.apply.call(propertyDescriptor.get(), assertionPromise, args);
});
},
function () {
// As above.
return Function.prototype.call.call(propertyDescriptor.get, assertionPromise);
}
);
} else {
// Case 2B: pure property case
utils.addProperty(assertionPromise, asserterName, function () {
return makeAssertionPromiseToDoAsserter(assertionPromise, baseAssertion, function () {
return propertyDescriptor.get.call(assertionPromise);
});
});
}
}
});
return assertionPromise;
}
property("fulfilled", fulfilledAsserter);
property("rejected", rejectedAsserter);
property("broken", rejectedAsserter);
property("eventually", function () {
return makeAssertionPromise(this._obj, this);
});
method("become", function (value) {
return this.eventually.eql(value);
});
method("notify", function (callback) {
return notify(this._obj, callback);
});
// Now use the Assertion framework to build an `assert` interface.
var originalAssertMethods = Object.getOwnPropertyNames(assert).filter(function (propName) {
return typeof assert[propName] === "function";
});
assert.isFulfilled = function (promise, message) {
return (new Assertion(promise, message)).to.be.fulfilled;
};
assert.isRejected = assert.isBroken = function (promise, toTestAgainst, message) {
if (typeof toTestAgainst === "string") {
message = toTestAgainst;
toTestAgainst = null;
}
var shouldBeRejectedPromise = (new Assertion(promise, message)).to.be.rejected;
// Use `['with']` to handle crappy non-ES5 environments like PhantomJS.
return toTestAgainst ? shouldBeRejectedPromise['with'](toTestAgainst) : shouldBeRejectedPromise;
};
assert.eventually = {};
originalAssertMethods.forEach(function (assertMethodName) {
assert.eventually[assertMethodName] = function (promise) {
var otherArgs = Array.prototype.slice.call(arguments, 1);
var promiseToAssert = promise.then(function (fulfillmentValue) {
return assert[assertMethodName].apply(assert, [fulfillmentValue].concat(otherArgs));
});
var augmentedPromiseToAssert = Object.create(promiseToAssert);
addNotifyMethod(augmentedPromiseToAssert);
return augmentedPromiseToAssert;
};
});
assert.becomes = function (promise, value) {
return assert.eventually.deepEqual(promise, value);
};
assert.doesNotBecome = function (promise, value) {
return assert.eventually.notDeepEqual(promise, value);
};
}));

3996
spec/lib/chai.js Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

1253
spec/lib/expect.js Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

231
spec/lib/mocha-1.8.1.css Normal file
Просмотреть файл

@ -0,0 +1,231 @@
@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;
overflow: hidden;
}
#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: block;
float: left;
clear: left;
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: 0;
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.3;
color: #888;
}
#mocha .test:hover a.replay {
opacity: 1;
}
#mocha-report.pass .test.fail {
display: none;
}
#mocha-report.fail .test.pass {
display: none;
}
#mocha-error {
color: #c00;
font-size: 1.5 em;
font-weight: 100;
letter-spacing: 1px;
}
#mocha-stats {
position: fixed;
top: 15px;
right: 10px;
font-size: 12px;
margin: 0;
color: #888;
}
#mocha-stats .progress {
float: right;
padding-top: 0;
}
#mocha-stats em {
color: black;
}
#mocha-stats a {
text-decoration: none;
color: inherit;
}
#mocha-stats a:hover {
border-bottom: 1px solid #eee;
}
#mocha-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 }

5340
spec/lib/mocha-1.8.1.js Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,141 @@
(function (mochaAsPromised) {
"use strict";
// Module systems magic dance.
if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
// Node.js: plug in automatically, if no argument is provided. This is a good idea since one can run Mocha tests
// using the Mocha test runner from either a locally-installed package, or from a globally-installed one.
// In the latter case, naively plugging in `require("mocha")` would end up duck-punching the wrong instance,
// so we provide this shortcut to auto-detect which Mocha package needs to be duck-punched.
module.exports = function (mocha) {
if (!mocha) {
if (typeof process === "object" && Object.prototype.toString.call(process) === "[object process]") {
// We're in *real* Node.js, not in a browserify-like environment. Do automatic detection logic.
var path = require("path");
// `process.argv[1]` is either something like `"/host/package/node_modules/mocha/bin/_mocha`", or
// `"/path/to/global/node_modules/mocha/bin/_mocha"`. Verify that, though:
var lastThreeSegments = process.argv[1].split(path.sep).slice(-3);
if (lastThreeSegments[0] !== "mocha" || lastThreeSegments[1] !== "bin") {
throw new Error("Attempted to automatically plug in to Mocha, but was not running through " +
"the Mocha test runner. Either run using the Mocha command-line test runner, " +
"or plug in manually by passing the running Mocha module.");
}
var mochaPath = path.resolve(process.argv[1], "../..");
mocha = (require)(mochaPath); // Trick browserify into not complaining.
} else if (typeof Mocha !== "undefined") {
// We're in a browserify-like emulation environment. Try the `Mocha` global.
mocha = Mocha;
} else {
throw new Error("Attempted to automatically plug in to Mocha, but could not detect the " +
"environment. Plug in manually by passing the running Mocha module.");
}
}
mochaAsPromised(mocha);
};
} else if (typeof define === "function" && define.amd) {
// AMD
define(function () {
return mochaAsPromised;
});
} else {
// Other environment (usually <script> tag): plug in global `Mocha` directly and automatically.
mochaAsPromised(Mocha);
}
}((function () {
"use strict";
function isPromise(x) {
return typeof x === "object" && x !== null && typeof x.then === "function";
}
var duckPunchedAlready = false;
return function mochaAsPromised(mocha) {
if (duckPunchedAlready) {
return;
}
duckPunchedAlready = true;
// Soooo this is an awesome hack.
// Here's the idea: Mocha `Runnable` instances have a `fn` property, representing the test to run. Async tests
// in Mocha are done with `fn`s that take a `done` callback, and call it with either nothing (success) or an
// error (failure). We want to add another paradigm for async tests: `fn`s that take no arguments, but return a
// promise. Promise fulfillment corresponds to success, and rejection to failure.
// To do this, we translate promise-returning `fn`s into callback-calling ones. So Mocha never sees the promisey
// functions, but instead sees a wrapper around them that we provide. The only trick is, how and when to insert
// this wrapper into a Mocha `Runnable`?
// We accomplish this by intercepting all [[Put]]s to the `fn` property of *any* `Runnable`. That is, we define
// a setter for `fn` on `Runnable.prototype` (!). So when Mocha sets the `fn` property of a runnable inside the
// `Runnable` constructor (i.e. `this.fn = fn`), it is immediately translated into a wrapped version, which is
// then stored as a `_wrappedFn` instance property. Finally we define a getter for `fn` on `Runnable.prototype`
// as well, so that any retrievals of the `fn` property (e.g. in `Runnable.prototype.run`) return the wrapped
// version we've stored.
// We also need to override the `async` property, since the Mocha constructor sets it by looking at the `fn`
// passed in, and not at the `this.fn` property we have control over. We just give it a getter that performs the
// same logic as the Mocha constructor, and a no-op setter (so that it silently ignores Mocha's attempts to set
// it).
// ISN'T THIS COOL!?
Object.defineProperties(mocha.Runnable.prototype, {
fn: {
configurable: true,
enumerable: true,
get: function () {
return this._wrappedFn;
},
set: function (fn) {
this._wrappedFn = function (done) {
// Run the original `fn`, passing along `done` for the case in which it's callback-asynchronous.
// Make sure to forward the `this` context, since you can set variables and stuff on it to share
// within a suite.
var retVal = fn.call(this, done);
if (isPromise(retVal)) {
// If we get a promise back...
retVal.then(
function () {
// On fulfillment, ignore the fulfillment value and call `done()` with no arguments.
done();
},
function (reason) {
// On rejection, make sure there's a rejection reason, then call `done` with it.
if (reason === null || reason === undefined) {
reason = new Error("Promise rejected with no rejection reason.");
}
done(reason);
}
);
} else if (fn.length === 0) {
// If `fn` is synchronous (i.e. didn't have a `done` parameter and didn't return a promise),
// call `done` now. (If it's callback-asynchronous, `fn` will call `done` eventually since
// we passed it in above.)
done();
}
};
this._wrappedFn.toString = function () {
return fn.toString();
};
}
},
async: {
configurable: true,
enumerable: true,
get: function () {
return typeof this._wrappedFn === "function";
},
set: function () {
// Ignore Mocha trying to set this; it doesn't know the whole picture.
}
}
});
};
}())));

1
spec/lib/q-0.8.12.js Symbolic link
Просмотреть файл

@ -0,0 +1 @@
../../lib/q-0.8.12.js

1
spec/lib/underscore.js Symbolic link
Просмотреть файл

@ -0,0 +1 @@
../../lib/underscore.js

41
spec/local-sync-spec.js Normal file
Просмотреть файл

@ -0,0 +1,41 @@
describe("Local sync", function() {
this.timeout(20000);
runUserSpec();
});
// exports.testCreateThenFetch = function(test) {
// init(function() {
// createUser({
// callback: function(user) {
// var u = new Gombot.User({ id: user.id, email: user.get("email") });
// u.fetch({
// password: TEST_PASSWORD,
// success: function() {
// test.pass();
// test.done();
// },
// error: function(err) {
// console.log("error:", err);
// test.fail();
// test.done();
// }
// });
// },
// password: TEST_PASSWORD
// });
// });
// test.waitUntilDone();
// };
// exports.testUpdateThenFetch = function(test) {
// test.pass();
// test.done();
// };
// exports.testCreateThenDestroy = function(test) {
// test.pass();
// test.done();
// };

22
spec/runner.html Normal file
Просмотреть файл

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Mocha Tests</title>
<link rel="stylesheet" href="lib/mocha-1.8.1.css" />
</head>
<body>
<div id="mocha"></div>
<script src="lib/underscore.js"></script>
<script src="lib/q-0.8.12.js"></script>
<script src="lib/chai.js"></script>
<script src="lib/chai-as-promised.js"></script>
<script src="lib/mocha-1.8.1.js"></script>
<script src="lib/mocha-as-promised.js"></script>
<script src="helpers.js"></script>
<script src="runner.js"></script>
<script src="user-spec.js"></script>
<script src="local-sync-spec.js"></script>
</body>
</html>

19
spec/runner.js Normal file
Просмотреть файл

@ -0,0 +1,19 @@
var GombotTest;
var SH;
var should = chai.should();
var assert = chai.assert;
mocha.setup('bdd');
// TODO: put some teardown code here to remove usersTest store, etc
GombotTest = chrome.extension.getBackgroundPage()._Gombot();
GombotTest.init({ storeName: "usersTest", callback: go, testing: true });
SH = SpecHelpers(GombotTest);
function go() {
mocha.run();
}

Просмотреть файл

@ -1,48 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Jasmine Spec Runner</title>
<link rel="shortcut icon" type="image/png" href="lib/jasmine-1.3.1/jasmine_favicon.png">
<link rel="stylesheet" type="text/css" href="lib/jasmine-1.3.1/jasmine.css">
<script type="text/javascript" src="lib/jasmine-1.3.1/jasmine.js"></script>
<script type="text/javascript" src="lib/jasmine-1.3.1/jasmine-html.js"></script>
<!-- include source files here... -->
<!-- include spec files here... -->
<script type="text/javascript">
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
})();
</script>
</head>
<body>
</body>
</html>

37
spec/user-spec.js Normal file
Просмотреть файл

@ -0,0 +1,37 @@
function runUserSpec() {
describe("User", function() {
describe("#create", function() {
var userPromise = null,
testEmail = SH.generateTestEmail();
before(function() {
userPromise = SH.createUser({ password: SH.TEST_PASSWORD, email: testEmail });
});
it("should create a model with the attributes used to create it", function() {
return Q.all([
userPromise.should.eventually.be.an("object"),
userPromise.should.eventually.have.deep.property("attributes.email", testEmail),
userPromise.should.eventually.have.deep.property("attributes.pin", SH.TEST_PIN)
]);
});
it("should create a record for the model in local storage", function() {
var user;
var lsAttrs;
var lsPromise = userPromise.then(function(u) { user = u; return SH.getLocalStorageItem(SH.STORE_NAME); });
lsPromise.then(function(attrs) { lsAttrs = attrs; });
lsPromise.should.eventually.satisfy(function (lsAttrs) { console.log(user, lsAttrs); return true; } );
return lsPromise;
// userPromise.should.eventually.satisfy(function(user) {
// var indexOfUserRecord = -1;
// runs(function() {
// LocalStorage.getItem(self.STORE_NAME, function(store) {
// var records = (store && store.split(",")) || [];
// indexOfUserRecord = records.indexOf(user.id) >= 0
// });
// });
// });
});
});
});
}