You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by br...@apache.org on 2013/09/23 20:00:13 UTC

[4/4] git commit: Refactor to use Q.js promises in place of callbacks everywhere.

Refactor to use Q.js promises in place of callbacks everywhere.

This significantly cleans up many parts of the code, and generally
shortens it by 25%, by dropping redundant error checks.

NOTE: This DOES NOT change the "public" API of CLI, in the sense
that cordova.foo still takes a callback. Under the hood, it wraps a call
to cordova.raw.foo. If you downstream CLI and require modules like
platform.js directly, (a) stop, and (b) you'll have to port to promises.

I created the cordova-3.1.x branch for all bugfixes for the 3.1.0
release, since this code should probably bake for a while before being
released to npm.

Mostly fixed tests.

Fixed all tests (expect the broken BB10 ones)

Cleaning up debugging output

First pass over async refactoring.

build spec passing

CLI and Compile specs passing.

Create tests repaired.

Fixed tests for emulate, hooker, and lazy_load.

Fixed platform tests.

Fixed plugin tests.

Prepare tests fixed.

Run tests passing.

Fixing some of the parser tests.

Platform parser tests all fixed.

And the final burst of tests fixed.

Fix a few error messages and other problems.

Refactor to preserve top-level semantics.

Add tests for the cordova.foo -> cordova.raw.foo wrappers


Project: http://git-wip-us.apache.org/repos/asf/cordova-cli/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-cli/commit/017b7d9b
Tree: http://git-wip-us.apache.org/repos/asf/cordova-cli/tree/017b7d9b
Diff: http://git-wip-us.apache.org/repos/asf/cordova-cli/diff/017b7d9b

Branch: refs/heads/master
Commit: 017b7d9b6bfa4510f4ada608da3aefe597d710cb
Parents: f8494a2
Author: Braden Shepherdson <br...@gmail.com>
Authored: Fri Sep 13 09:16:00 2013 -0400
Committer: Braden Shepherdson <br...@gmail.com>
Committed: Mon Sep 23 13:59:17 2013 -0400

----------------------------------------------------------------------
 cordova.js                              |  21 +-
 package.json                            |   1 +
 platforms.js                            |   2 +-
 spec/build.spec.js                      |  78 ++++---
 spec/cli.spec.js                        |  19 +-
 spec/compile.spec.js                    |  62 ++---
 spec/create.spec.js                     |  33 ++-
 spec/emulate.spec.js                    |  69 +++---
 spec/hooker.spec.js                     | 130 +++++------
 spec/lazy_load.spec.js                  | 155 ++++++++-----
 spec/metadata/android_parser.spec.js    |  50 ++--
 spec/metadata/blackberry_parser.spec.js |  80 ++++---
 spec/metadata/ios_parser.spec.js        | 111 +++++----
 spec/metadata/wp7_parser.spec.js        |  64 +++---
 spec/metadata/wp8_parser.spec.js        |  64 +++---
 spec/platform.spec.js                   | 275 ++++++++++++----------
 spec/plugin.spec.js                     | 240 +++++++++++--------
 spec/prepare.spec.js                    | 159 +++++++------
 spec/run.spec.js                        |  88 ++++---
 spec/serve.spec.js                      |   6 +-
 spec/wrappers.spec.js                   |  39 ++++
 src/build.js                            |  52 ++---
 src/cli.js                              |   8 +-
 src/compile.js                          |  66 ++----
 src/config.js                           |   4 +-
 src/create.js                           |  40 ++--
 src/emulate.js                          |  77 ++-----
 src/help.js                             |  26 +--
 src/hooker.js                           |  75 +++---
 src/lazy_load.js                        |  55 ++---
 src/metadata/android_parser.js          |  35 +--
 src/metadata/blackberry10_parser.js     |  25 +-
 src/metadata/firefoxos_parser.js        |  62 ++---
 src/metadata/ios_parser.js              |  56 +++--
 src/metadata/wp7_parser.js              |  29 ++-
 src/metadata/wp8_parser.js              |  31 ++-
 src/platform.js                         | 332 +++++++++++----------------
 src/plugin.js                           | 260 ++++++++-------------
 src/prepare.js                          |  85 +++----
 src/run.js                              |  74 ++----
 src/util.js                             |  34 ++-
 src/xml-helpers.js                      |   8 +-
 42 files changed, 1586 insertions(+), 1594 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/cordova.js
----------------------------------------------------------------------
diff --git a/cordova.js b/cordova.js
index 277c12a..bca8981 100644
--- a/cordova.js
+++ b/cordova.js
@@ -38,21 +38,22 @@ exports = module.exports = {
     },
     emit:      emit,
     trigger:   emit,
+    raw: {}
 };
 
-addModuleProperty(module, 'prepare', './src/prepare');
-addModuleProperty(module, 'build', './src/build');
+addModuleProperty(module, 'prepare', './src/prepare', true);
+addModuleProperty(module, 'build', './src/build', true);
 addModuleProperty(module, 'help', './src/help');
 addModuleProperty(module, 'config', './src/config');
-addModuleProperty(module, 'create', './src/create');
+addModuleProperty(module, 'create', './src/create', true);
 addModuleProperty(module, 'ripple', './src/ripple');
-addModuleProperty(module, 'emulate', './src/emulate');
-addModuleProperty(module, 'plugin', './src/plugin');
-addModuleProperty(module, 'plugins', './src/plugin');
+addModuleProperty(module, 'emulate', './src/emulate', true);
+addModuleProperty(module, 'plugin', './src/plugin', true);
+addModuleProperty(module, 'plugins', './src/plugin', true);
 addModuleProperty(module, 'serve', './src/serve');
-addModuleProperty(module, 'platform', './src/platform');
-addModuleProperty(module, 'platforms', './src/platform');
-addModuleProperty(module, 'compile', './src/compile');
-addModuleProperty(module, 'run', './src/run');
+addModuleProperty(module, 'platform', './src/platform', true);
+addModuleProperty(module, 'platforms', './src/platform', true);
+addModuleProperty(module, 'compile', './src/compile', true);
+addModuleProperty(module, 'run', './src/run', true);
 
 

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/package.json
----------------------------------------------------------------------
diff --git a/package.json b/package.json
index 1c8ab4f..9f8ff2d 100644
--- a/package.json
+++ b/package.json
@@ -45,6 +45,7 @@
     "tar": "0.1.x",
     "open": "0.0.3",
     "npm": "1.3.x",
+    "q": "~0.9",
     "optimist": "0.6.0",
     "mime": "~1.2.11"
   },

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/platforms.js
----------------------------------------------------------------------
diff --git a/platforms.js b/platforms.js
index d34c112..82ba8c8 100644
--- a/platforms.js
+++ b/platforms.js
@@ -58,6 +58,6 @@ var addModuleProperty = require('./src/util').addModuleProperty;
 Object.keys(module.exports).forEach(function(key) {
     var obj = module.exports[key];
     if (obj.parser) {
-        addModuleProperty(module, 'parser', obj.parser, obj);
+        addModuleProperty(module, 'parser', obj.parser, false, obj);
     }
 });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/build.spec.js
----------------------------------------------------------------------
diff --git a/spec/build.spec.js b/spec/build.spec.js
index 801fc1b..a3159d4 100644
--- a/spec/build.spec.js
+++ b/spec/build.spec.js
@@ -22,6 +22,7 @@ var cordova = require('../cordova'),
     path = require('path'),
     fs = require('fs'),
     hooker = require('../src/hooker'),
+    Q = require('q'),
     util = require('../src/util');
 
 var supported_platforms = Object.keys(platforms).filter(function(p) { return p != 'www'; });
@@ -30,46 +31,59 @@ describe('build command', function() {
     var is_cordova, list_platforms, fire;
     var project_dir = '/some/path';
     var prepare_spy, compile_spy;
+    var result;
+
+    function buildPromise(f) {
+        f.then(function() { result = true; }, function(err) { result = err; });
+    }
+
+    function wrapper(f, post) {
+        runs(function() {
+            buildPromise(f);
+        });
+        waitsFor(function() { return result; }, 'promise never resolved', 500);
+        runs(post);
+    }
+
     beforeEach(function() {
         is_cordova = spyOn(util, 'isCordova').andReturn(project_dir);
         list_platforms = spyOn(util, 'listPlatforms').andReturn(supported_platforms);
-        fire = spyOn(hooker.prototype, 'fire').andCallFake(function(e, opts, cb) {
-            cb(false);
-        });
-        prepare_spy = spyOn(cordova, 'prepare').andCallFake(function(platforms, cb) {
-            cb();
-        });
-        compile_spy = spyOn(cordova, 'compile').andCallFake(function(platforms, cb) {
-            cb();
-        });
+        fire = spyOn(hooker.prototype, 'fire').andReturn(Q());
+        prepare_spy = spyOn(cordova.raw, 'prepare').andReturn(Q());
+        compile_spy = spyOn(cordova.raw, 'compile').andReturn(Q());
     });
-    describe('failure', function() {
+    describe('failure', function(done) {
         it('should not run inside a Cordova-based project with no added platforms by calling util.listPlatforms', function() {
             list_platforms.andReturn([]);
-            expect(function() {
-                cordova.build();
-            }).toThrow('No platforms added to this project. Please use `cordova platform add <platform>`.');
+            runs(function() {
+                buildPromise(cordova.raw.build());
+            });
+            waitsFor(function() { return result; }, 'promise never resolved', 500);
+            runs(function() {
+                expect(result).toEqual(new Error('No platforms added to this project. Please use `cordova platform add <platform>`.'));
+            });
         });
         it('should not run outside of a Cordova-based project', function() {
             is_cordova.andReturn(false);
-            expect(function() {
-                cordova.build();
-            }).toThrow('Current working directory is not a Cordova-based project.');
+            wrapper(cordova.raw.build(), function() {
+                expect(result).toEqual(new Error('Current working directory is not a Cordova-based project.'));
+            });
         });
     });
 
     describe('success', function() {
         it('should run inside a Cordova-based project with at least one added platform and call both prepare and compile', function(done) {
-            cordova.build(['android','ios'], function(err) {
-                expect(prepare_spy).toHaveBeenCalledWith({verbose: false, platforms: ['android', 'ios'], options: []}, jasmine.any(Function));
-                expect(compile_spy).toHaveBeenCalledWith({verbose: false, platforms: ['android', 'ios'], options: []}, jasmine.any(Function));
+            debugger;
+            cordova.raw.build(['android','ios']).then(function() {
+                expect(prepare_spy).toHaveBeenCalledWith({verbose: false, platforms: ['android', 'ios'], options: []});
+                expect(compile_spy).toHaveBeenCalledWith({verbose: false, platforms: ['android', 'ios'], options: []});
                 done();
             });
         });
         it('should pass down options', function(done) {
-            cordova.build({platforms: ['android'], options: ['--release']}, function(err) {
-                expect(prepare_spy).toHaveBeenCalledWith({platforms: ['android'], options: ["--release"]}, jasmine.any(Function));
-                expect(compile_spy).toHaveBeenCalledWith({platforms: ['android'], options: ["--release"]}, jasmine.any(Function));
+            cordova.raw.build({platforms: ['android'], options: ['--release']}).then(function() {
+                expect(prepare_spy).toHaveBeenCalledWith({platforms: ['android'], options: ["--release"]});
+                expect(compile_spy).toHaveBeenCalledWith({platforms: ['android'], options: ["--release"]});
                 done();
             });
         });
@@ -77,13 +91,15 @@ describe('build command', function() {
 
     describe('hooks', function() {
         describe('when platforms are added', function() {
-            it('should fire before hooks through the hooker module', function() {
-                cordova.build(['android', 'ios']);
-                expect(fire).toHaveBeenCalledWith('before_build', {verbose: false, platforms:['android', 'ios'], options: []}, jasmine.any(Function));
+            it('should fire before hooks through the hooker module', function(done) {
+                cordova.raw.build(['android', 'ios']).then(function() {
+                    expect(fire).toHaveBeenCalledWith('before_build', {verbose: false, platforms:['android', 'ios'], options: []});
+                    done();
+                });
             });
             it('should fire after hooks through the hooker module', function(done) {
-                cordova.build('android', function() {
-                     expect(fire).toHaveBeenCalledWith('after_build', {verbose: false, platforms:['android'], options: []}, jasmine.any(Function));
+                cordova.raw.build('android').then(function() {
+                     expect(fire).toHaveBeenCalledWith('after_build', {verbose: false, platforms:['android'], options: []});
                      done();
                 });
             });
@@ -92,10 +108,10 @@ describe('build command', function() {
         describe('with no platforms added', function() {
             it('should not fire the hooker', function() {
                 list_platforms.andReturn([]);
-                expect(function() {
-                    cordova.build();
-                }).toThrow();
-                expect(fire).not.toHaveBeenCalled();
+                wrapper(cordova.raw.build(), function() {
+                    expect(result).toEqual(new Error('No platforms added to this project. Please use `cordova platform add <platform>`.'));
+                    expect(fire).not.toHaveBeenCalled();
+                });
             });
         });
     });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/cli.spec.js
----------------------------------------------------------------------
diff --git a/spec/cli.spec.js b/spec/cli.spec.js
index 33c40cd..999ce62 100644
--- a/spec/cli.spec.js
+++ b/spec/cli.spec.js
@@ -16,7 +16,8 @@
     specific language governing permissions and limitations
     under the License.
 */
-var CLI = require("../src/cli");
+var CLI = require("../src/cli"),
+    Q = require('q'),
     cordova = require("../cordova");
 
 describe("cordova cli", function () {
@@ -47,7 +48,7 @@ describe("cordova cli", function () {
 
     describe("project commands other than plugin and platform", function () {
         beforeEach(function () {
-            spyOn(cordova, "build");
+            spyOn(cordova.raw, "build").andReturn(Q());
         });
 
         afterEach(function () {
@@ -56,33 +57,33 @@ describe("cordova cli", function () {
 
         it("will call command with all arguments passed through", function () {
             new CLI(["node", "cordova", "build", "blackberry10", "-k", "abcd1234"]);
-            expect(cordova.build).toHaveBeenCalledWith({verbose: false, platforms: ["blackberry10"], options: ["-k", "abcd1234"]});
+            expect(cordova.raw.build).toHaveBeenCalledWith({verbose: false, platforms: ["blackberry10"], options: ["-k", "abcd1234"]});
         });
 
         it("will consume the first instance of -d", function () {
             new CLI(["node", "cordova", "-d", "build", "blackberry10", "-k", "abcd1234", "-d"]);
-            expect(cordova.build).toHaveBeenCalledWith({verbose: true, platforms: ["blackberry10"], options: ["-k", "abcd1234", "-d"]});
+            expect(cordova.raw.build).toHaveBeenCalledWith({verbose: true, platforms: ["blackberry10"], options: ["-k", "abcd1234", "-d"]});
         });
 
         it("will consume the first instance of --verbose", function () {
             new CLI(["node", "cordova", "--verbose", "build", "blackberry10", "-k", "abcd1234", "--verbose"]);
-            expect(cordova.build).toHaveBeenCalledWith({verbose: true, platforms: ["blackberry10"], options: ["-k", "abcd1234", "--verbose"]});
+            expect(cordova.raw.build).toHaveBeenCalledWith({verbose: true, platforms: ["blackberry10"], options: ["-k", "abcd1234", "--verbose"]});
         });
 
         it("will consume the first instance of either --verbose of -d", function () {
             new CLI(["node", "cordova", "--verbose", "build", "blackberry10", "-k", "abcd1234", "-d"]);
-            expect(cordova.build).toHaveBeenCalledWith({verbose: true, platforms: ["blackberry10"], options: ["-k", "abcd1234", "-d"]});
+            expect(cordova.raw.build).toHaveBeenCalledWith({verbose: true, platforms: ["blackberry10"], options: ["-k", "abcd1234", "-d"]});
         });
 
         it("will consume the first instance of either --verbose of -d", function () {
             new CLI(["node", "cordova", "-d", "build", "blackberry10", "-k", "abcd1234", "--verbose"]);
-            expect(cordova.build).toHaveBeenCalledWith({verbose: true, platforms: ["blackberry10"], options: ["-k", "abcd1234", "--verbose"]});
+            expect(cordova.raw.build).toHaveBeenCalledWith({verbose: true, platforms: ["blackberry10"], options: ["-k", "abcd1234", "--verbose"]});
         });
     });
 
     describe("plugin", function () {
         beforeEach(function () {
-            spyOn(cordova, "plugin");
+            spyOn(cordova.raw, "plugin").andReturn(Q());
         });
 
         afterEach(function () {
@@ -91,7 +92,7 @@ describe("cordova cli", function () {
 
         it("will call command with all arguments passed through", function () {
             new CLI(["node", "cordova", "plugin", "add", "facebook", "--variable", "FOO=foo"]);
-            expect(cordova.plugin).toHaveBeenCalledWith("add", ["facebook", "--variable", "FOO=foo"]);
+            expect(cordova.raw.plugin).toHaveBeenCalledWith("add", ["facebook", "--variable", "FOO=foo"]);
         });
     });
 });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/compile.spec.js
----------------------------------------------------------------------
diff --git a/spec/compile.spec.js b/spec/compile.spec.js
index 975fcdc..3b0fe0d 100644
--- a/spec/compile.spec.js
+++ b/spec/compile.spec.js
@@ -18,53 +18,61 @@
 */
 var cordova = require('../cordova'),
     platforms = require('../platforms'),
-    shell = require('shelljs'),
+    child_process = require('child_process'),
     path = require('path'),
     fs = require('fs'),
     hooker = require('../src/hooker'),
+    Q = require('q'),
     util = require('../src/util');
 
 var supported_platforms = Object.keys(platforms).filter(function(p) { return p != 'www'; });
 
 describe('compile command', function() {
-    var is_cordova, list_platforms, fire, exec;
+    var is_cordova, list_platforms, fire, exec, result;
     var project_dir = '/some/path';
+
+    function wrapper(f, post) {
+        runs(function() {
+            f.then(function() { result = true; }, function(err) { result = err; });
+        });
+        waitsFor(function() { return result; }, 'promise never resolved', 500);
+        runs(post);
+    }
     beforeEach(function() {
         is_cordova = spyOn(util, 'isCordova').andReturn(project_dir);
         list_platforms = spyOn(util, 'listPlatforms').andReturn(supported_platforms);
-        fire = spyOn(hooker.prototype, 'fire').andCallFake(function(e, opts, cb) {
-            cb(false);
-        });
-        exec = spyOn(shell, 'exec').andCallFake(function(cmd, opts, cb) {
-            cb(0, '');
+        fire = spyOn(hooker.prototype, 'fire').andReturn(Q());
+        exec = spyOn(child_process, 'exec').andCallFake(function(cmd, opts, cb) {
+            if (!cb) cb = opts;
+            cb(null, '', '');
         });
     });
     describe('failure', function() {
         it('should not run inside a Cordova-based project with no added platforms by calling util.listPlatforms', function() {
             list_platforms.andReturn([]);
-            expect(function() {
-                cordova.compile();
-            }).toThrow('No platforms added to this project. Please use `cordova platform add <platform>`.');
+            wrapper(cordova.raw.compile(), function() {
+                expect(result).toEqual(new Error('No platforms added to this project. Please use `cordova platform add <platform>`.'));
+            });
         });
         it('should not run outside of a Cordova-based project', function() {
             is_cordova.andReturn(false);
-            expect(function() {
-                cordova.compile();
-            }).toThrow('Current working directory is not a Cordova-based project.');
+            wrapper(cordova.raw.compile(), function() {
+                expect(result).toEqual(new Error('Current working directory is not a Cordova-based project.'));
+            });
         });
     });
 
     describe('success', function() {
         it('should run inside a Cordova-based project with at least one added platform and shell out to build', function(done) {
-            cordova.compile(['android','ios'], function(err) {
-                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'android', 'cordova', 'build') + '"', jasmine.any(Object), jasmine.any(Function));
+            cordova.raw.compile(['android','ios']).then(function() {
+                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'android', 'cordova', 'build') + '"', jasmine.any(Function));
                 done();
             });
         });
         it('should pass down optional parameters', function (done) {
-            cordova.compile({platforms:["blackberry10"], options:["--release"]}, function (err) {
+            cordova.raw.compile({platforms:["blackberry10"], options:["--release"]}).then(function () {
                 var buildCommand = path.join(project_dir, 'platforms', 'blackberry10', 'cordova', 'build');
-                expect(exec).toHaveBeenCalledWith('"' + buildCommand + '" --release', jasmine.any(Object), jasmine.any(Function));
+                expect(exec).toHaveBeenCalledWith('"' + buildCommand + '" --release', jasmine.any(Function));
                 done();
             });
         });
@@ -72,13 +80,15 @@ describe('compile command', function() {
 
     describe('hooks', function() {
         describe('when platforms are added', function() {
-            it('should fire before hooks through the hooker module', function() {
-                cordova.compile(['android', 'ios']);
-                expect(fire).toHaveBeenCalledWith('before_compile', {verbose: false, platforms:['android', 'ios'], options: []}, jasmine.any(Function));
+            it('should fire before hooks through the hooker module', function(done) {
+                cordova.raw.compile(['android', 'ios']).then(function() {;
+                    expect(fire).toHaveBeenCalledWith('before_compile', {verbose: false, platforms:['android', 'ios'], options: []});
+                    done();
+                });
             });
             it('should fire after hooks through the hooker module', function(done) {
-                cordova.compile('android', function() {
-                     expect(fire).toHaveBeenCalledWith('after_compile', {verbose: false, platforms:['android'], options: []}, jasmine.any(Function));
+                cordova.raw.compile('android').then(function() {
+                     expect(fire).toHaveBeenCalledWith('after_compile', {verbose: false, platforms:['android'], options: []});
                      done();
                 });
             });
@@ -87,10 +97,10 @@ describe('compile command', function() {
         describe('with no platforms added', function() {
             it('should not fire the hooker', function() {
                 list_platforms.andReturn([]);
-                expect(function() {
-                    cordova.compile();
-                }).toThrow();
-                expect(fire).not.toHaveBeenCalled();
+                wrapper(cordova.raw.compile(), function() {
+                    expect(result).toEqual(new Error('No platforms added to this project. Please use `cordova platform add <platform>`.'));
+                    expect(fire).not.toHaveBeenCalled();
+                });
             });
         });
     });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/create.spec.js
----------------------------------------------------------------------
diff --git a/spec/create.spec.js b/spec/create.spec.js
index b56d624..e46569d 100644
--- a/spec/create.spec.js
+++ b/spec/create.spec.js
@@ -23,6 +23,7 @@ var cordova = require('../cordova'),
     util    = require('../src/util'),
     config    = require('../src/config'),
     lazy_load = require('../src/lazy_load'),
+    Q = require('q'),
     tempDir = path.join(__dirname, '..', 'temp');
 
 describe('create command', function () {
@@ -34,12 +35,8 @@ describe('create command', function () {
         config_spy = spyOn(cordova, 'config');
         config_read = spyOn(config, 'read').andReturn({});
         exists = spyOn(fs, 'existsSync').andReturn(false);
-        load_cordova = spyOn(lazy_load, 'cordova').andCallFake(function(platform, cb) {
-            cb();
-        });
-        load_custom = spyOn(lazy_load, 'custom').andCallFake(function(url, id, platform, version, cb) {
-            cb();
-        });
+        load_cordova = spyOn(lazy_load, 'cordova').andReturn(Q());
+        load_custom = spyOn(lazy_load, 'custom').andReturn(Q());
         package = jasmine.createSpy('config.packageName');
         name = jasmine.createSpy('config.name');
         parser = spyOn(util, 'config_parser').andReturn({
@@ -57,13 +54,13 @@ describe('create command', function () {
                 expect(h).toMatch(/synopsis/gi);
                 done();
             });
-            cordova.create();
+            cordova.raw.create();
         });
     });
 
     describe('success', function() {
         it('should create a default project if only directory is specified', function(done) {
-            cordova.create(tempDir, function() {
+            cordova.raw.create(tempDir).then(function() {
                 expect(mkdir).toHaveBeenCalledWith('-p', path.join(tempDir, '.cordova'));
                 expect(package).toHaveBeenCalledWith('io.cordova.hellocordova');
                 expect(name).toHaveBeenCalledWith('HelloCordova');
@@ -71,7 +68,7 @@ describe('create command', function () {
             });
         });
         it('should create a default project if only directory and id is specified', function(done) {
-            cordova.create(tempDir, 'ca.filmaj.canucks', function() {
+            cordova.raw.create(tempDir, 'ca.filmaj.canucks').then(function() {
                 expect(mkdir).toHaveBeenCalledWith('-p', path.join(tempDir, '.cordova'));
                 expect(package).toHaveBeenCalledWith('ca.filmaj.canucks');
                 expect(name).toHaveBeenCalledWith('HelloCordova');
@@ -79,7 +76,7 @@ describe('create command', function () {
             });
         });
         it('should create a project in specified directory with specified name and id', function(done) {
-            cordova.create(tempDir, 'ca.filmaj.canucks', 'IHateTheBruins', function() {
+            cordova.raw.create(tempDir, 'ca.filmaj.canucks', 'IHateTheBruins').then(function() {
                 expect(mkdir).toHaveBeenCalledWith('-p', path.join(tempDir, '.cordova'));
                 expect(package).toHaveBeenCalledWith('ca.filmaj.canucks');
                 expect(name).toHaveBeenCalledWith('IHateTheBruins');
@@ -87,7 +84,7 @@ describe('create command', function () {
             });
         });
         it('should create top-level directory structure appropriate for a cordova-cli project', function(done) {
-            cordova.create(tempDir, function() {
+            cordova.raw.create(tempDir).then(function() {
                 expect(mkdir).toHaveBeenCalledWith('-p', path.join(tempDir, 'platforms'));
                 expect(mkdir).toHaveBeenCalledWith('-p', path.join(tempDir, 'merges'));
                 expect(mkdir).toHaveBeenCalledWith('-p', path.join(tempDir, 'plugins'));
@@ -97,7 +94,7 @@ describe('create command', function () {
         });
         it('should create appropriate directories for hooks', function(done) {
             var hooks_dir = path.join(tempDir, '.cordova', 'hooks');
-            cordova.create(tempDir, function() {
+            cordova.raw.create(tempDir).then(function() {
                 expect(mkdir).toHaveBeenCalledWith('-p', hooks_dir);
                 expect(mkdir).toHaveBeenCalledWith( (path.join(hooks_dir, 'after_build')));
                 expect(mkdir).toHaveBeenCalledWith( (path.join(hooks_dir, 'after_compile')));
@@ -127,8 +124,8 @@ describe('create command', function () {
             });
         });
         it('should by default use cordova-app-hello-world as www assets', function(done) {
-            cordova.create(tempDir, function() {
-                expect(load_cordova).toHaveBeenCalledWith('www', jasmine.any(Function));
+            cordova.raw.create(tempDir).then(function() {
+                expect(load_cordova).toHaveBeenCalledWith('www');
                 done();
             });
         });
@@ -143,13 +140,13 @@ describe('create command', function () {
                 }
             };
             config_read.andReturn(fake_config);
-            cordova.create(tempDir, function() {
-                expect(load_custom).toHaveBeenCalledWith(fake_config.lib.www.uri, fake_config.lib.www.id, 'www', fake_config.lib.www.version, jasmine.any(Function));
+            cordova.raw.create(tempDir).then(function() {
+                expect(load_custom).toHaveBeenCalledWith(fake_config.lib.www.uri, fake_config.lib.www.id, 'www', fake_config.lib.www.version);
                 done();
             });
         });
         it('should add a missing www/config.xml', function(done) {
-            cordova.create(tempDir, function() {
+            cordova.raw.create(tempDir).then(function() {
                 expect(shell.cp).toHaveBeenCalledWith(
                     path.resolve(__dirname, '..', 'templates', 'config.xml'),
                     jasmine.any(String)
@@ -162,7 +159,7 @@ describe('create command', function () {
                 if (p.indexOf('config.xml') > -1) return true;
                 return false;
             });
-            cordova.create(tempDir, function() {
+            cordova.raw.create(tempDir).then(function() {
                 expect(shell.cp).not.toHaveBeenCalledWith(
                     path.resolve(__dirname, '..', 'templates', 'config.xml'),
                     jasmine.any(String)

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/emulate.spec.js
----------------------------------------------------------------------
diff --git a/spec/emulate.spec.js b/spec/emulate.spec.js
index 807553c..6dd05ca 100644
--- a/spec/emulate.spec.js
+++ b/spec/emulate.spec.js
@@ -18,57 +18,66 @@
 */
 var cordova = require('../cordova'),
     platforms = require('../platforms'),
-    shell = require('shelljs'),
+    child_process = require('child_process'),
     path = require('path'),
     fs = require('fs'),
     hooker = require('../src/hooker'),
+    Q = require('q'),
     util = require('../src/util');
 
 var supported_platforms = Object.keys(platforms).filter(function(p) { return p != 'www'; });
 
 describe('emulate command', function() {
-    var is_cordova, list_platforms, fire, exec;
+    var is_cordova, list_platforms, fire, exec, result;
     var project_dir = '/some/path';
     var prepare_spy;
+
+    function wrapper(f, post) {
+        runs(function() {
+            f.then(function() { result = true; }, function(err) { result = err; });
+        });
+        waitsFor(function() { return result; }, 'promise never resolved', 500);
+        runs(post);
+    }
+
     beforeEach(function() {
         is_cordova = spyOn(util, 'isCordova').andReturn(project_dir);
         list_platforms = spyOn(util, 'listPlatforms').andReturn(supported_platforms);
-        fire = spyOn(hooker.prototype, 'fire').andCallFake(function(e, opts, cb) {
-            cb(false);
-        });
-        prepare_spy = spyOn(cordova, 'prepare').andCallFake(function(platforms, cb) {
-            cb();
+        fire = spyOn(hooker.prototype, 'fire').andReturn(Q());
+        prepare_spy = spyOn(cordova.raw, 'prepare').andReturn(Q());
+        exec = spyOn(child_process, 'exec').andCallFake(function(cmd, opts, cb) {
+            if (!cb) cb = opts;
+            cb(null, '', '');
         });
-        exec = spyOn(shell, 'exec').andCallFake(function(cmd, opts, cb) { cb(0, ''); });
     });
     describe('failure', function() {
         it('should not run inside a Cordova-based project with no added platforms by calling util.listPlatforms', function() {
             list_platforms.andReturn([]);
-            expect(function() {
-                cordova.emulate();
-            }).toThrow('No platforms added to this project. Please use `cordova platform add <platform>`.');
+            wrapper(cordova.raw.emulate(), function() {
+                expect(result).toEqual(new Error('No platforms added to this project. Please use `cordova platform add <platform>`.'));
+            });
         });
         it('should not run outside of a Cordova-based project', function() {
             is_cordova.andReturn(false);
-            expect(function() {
-                cordova.emulate();
-            }).toThrow('Current working directory is not a Cordova-based project.');
+            wrapper(cordova.raw.emulate(), function() {
+                expect(result).toEqual(new Error('Current working directory is not a Cordova-based project.'));
+            });
         });
     });
 
     describe('success', function() {
         it('should run inside a Cordova-based project with at least one added platform and call prepare and shell out to the emulate script', function(done) {
-            cordova.emulate(['android','ios'], function(err) {
-                expect(prepare_spy).toHaveBeenCalledWith(['android', 'ios'], jasmine.any(Function));
-                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'android', 'cordova', 'run') + '" --emulator', jasmine.any(Object), jasmine.any(Function));
-                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'ios', 'cordova', 'run') + '" --emulator', jasmine.any(Object), jasmine.any(Function));
+            cordova.raw.emulate(['android','ios']).then(function(err) {
+                expect(prepare_spy).toHaveBeenCalledWith(['android', 'ios']);
+                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'android', 'cordova', 'run') + '" --emulator', jasmine.any(Function));
+                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'ios', 'cordova', 'run') + '" --emulator', jasmine.any(Function));
                 done();
             });
         });
         it('should pass down options', function(done) {
-            cordova.emulate({platforms: ['ios'], options:["--optionTastic"]}, function(err) {
-                expect(prepare_spy).toHaveBeenCalledWith(['ios'], jasmine.any(Function));
-                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'ios', 'cordova', 'run') + '" --optionTastic', jasmine.any(Object), jasmine.any(Function));
+            cordova.raw.emulate({platforms: ['ios'], options:["--optionTastic"]}).then(function(err) {
+                expect(prepare_spy).toHaveBeenCalledWith(['ios']);
+                expect(exec).toHaveBeenCalledWith('"' + path.join(project_dir, 'platforms', 'ios', 'cordova', 'run') + '" --optionTastic', jasmine.any(Function));
                 done();
             });
         });
@@ -76,13 +85,15 @@ describe('emulate command', function() {
 
     describe('hooks', function() {
         describe('when platforms are added', function() {
-            it('should fire before hooks through the hooker module', function() {
-                cordova.emulate(['android', 'ios']);
-                expect(fire).toHaveBeenCalledWith('before_emulate', {verbose: false, platforms:['android', 'ios'], options: []}, jasmine.any(Function));
+            it('should fire before hooks through the hooker module', function(done) {
+                cordova.raw.emulate(['android', 'ios']).then(function() {
+                    expect(fire).toHaveBeenCalledWith('before_emulate', {verbose: false, platforms:['android', 'ios'], options: []});
+                    done();
+                });
             });
             it('should fire after hooks through the hooker module', function(done) {
-                cordova.emulate('android', function() {
-                     expect(fire).toHaveBeenCalledWith('after_emulate', {verbose: false, platforms:['android'], options: []}, jasmine.any(Function));
+                cordova.raw.emulate('android').then(function() {
+                     expect(fire).toHaveBeenCalledWith('after_emulate', {verbose: false, platforms:['android'], options: []});
                      done();
                 });
             });
@@ -91,9 +102,9 @@ describe('emulate command', function() {
         describe('with no platforms added', function() {
             it('should not fire the hooker', function() {
                 list_platforms.andReturn([]);
-                expect(function() {
-                    cordova.emulate();
-                }).toThrow();
+                wrapper(cordova.raw.emulate(), function() {
+                    expect(result).toEqual(new Error('No platforms added to this project. Please use `cordova platform add <platform>`.'));
+                });
                 expect(fire).not.toHaveBeenCalled();
             });
         });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/hooker.spec.js
----------------------------------------------------------------------
diff --git a/spec/hooker.spec.js b/spec/hooker.spec.js
index 4711b17..c303e25 100644
--- a/spec/hooker.spec.js
+++ b/spec/hooker.spec.js
@@ -19,9 +19,11 @@
 var hooker = require('../src/hooker'),
     util   = require('../src/util'),
     shell  = require('shelljs'),
+    child_process = require('child_process'),
     path   = require('path'),
     fs     = require('fs'),
     os     = require('os'),
+    Q      = require('q'),
     tempDir= path.join(__dirname, '..', 'temp'),
     hooks  = path.join(__dirname, 'fixtures', 'hooks'),
     cordova= require('../cordova');
@@ -48,28 +50,26 @@ describe('hooker', function() {
     describe('global (static) fire method', function() {
         it('should execute listeners serially', function(done) {
             var timeout = 20;
-            var test_event = 'poop';
+            var test_event = 'foo';
             var h1_fired = false;
-            var h1 = function(root, cb) {
+            var h1 = function() {
                 h1_fired = true;
-                setTimeout(cb, timeout);
+                expect(h2_fired).toBe(false);
+                return Q();
             };
             var h2_fired = false;
             var h2 = function() {
                 h2_fired = true;
+                expect(h1_fired).toBe(true);
+                return Q();
             };
-            runs(function() {
-                cordova.on(test_event, h1);
-                cordova.on(test_event, h2);
-                hooker.fire(test_event, function(err) {
-                    done();
-                });
+
+            cordova.on(test_event, h1);
+            cordova.on(test_event, h2);
+            hooker.fire(test_event).then(function() {
                 expect(h1_fired).toBe(true);
-                expect(h2_fired).toBe(false);
-            });
-            waits(timeout);
-            runs(function() {
                 expect(h2_fired).toBe(true);
+                done();
             });
         });
     });
@@ -81,10 +81,14 @@ describe('hooker', function() {
         });
 
         describe('failure', function() {
-            it('should not error if the hook is unrecognized', function(done) {
-                h.fire('CLEAN YOUR SHORTS GODDAMNIT LIKE A BIG BOY!', function(err){
-                    expect(err).not.toBeDefined();
-                    done();
+            it('should not error if the hook is unrecognized', function() {
+                var p;
+                runs(function() {
+                    p = h.fire('CLEAN YOUR SHORTS GODDAMNIT LIKE A BIG BOY!');
+                });
+                waitsFor(function() { return !p.isPending() }, 'promise not resolved', 500);
+                runs(function() {
+                    expect(p.isFulfilled()).toBe(true);
                 });
             });
             it('should error if any script exits with non-zero code', function(done) {
@@ -99,10 +103,11 @@ describe('hooker', function() {
                     shell.cp(path.join(hooks, 'fail', 'fail.sh'), script);
                 }
                 fs.chmodSync(script, '754');
-                h.fire('before_build', function(err){
+                h.fire('before_build').then(function() {
+                    expect('the call').toBe('a failure');
+                }, function(err) {
                     expect(err).toBeDefined();
-                    done();
-                });
+                }).fin(done);
             });
         });
 
@@ -122,16 +127,16 @@ describe('hooker', function() {
                     fs.readdirSync(hook).forEach(function(script) {
                         fs.chmodSync(path.join(hook, script), '754');
                     });
-                    s = spyOn(shell, 'exec').andCallFake(function(cmd, opts, cb) {
-                        cb(0, '');
+                    s = spyOn(child_process, 'exec').andCallFake(function(cmd, opts, cb) {
+                        if (!cb) cb = opts;
+                        cb(0, '', '');
                     });
                 });
                 afterEach(function() {
                     shell.rm('-rf', tempDir);
                 });
                 it('should execute all scripts in order and fire callback', function(done) {
-                    h.fire('before_build', function(err) {
-                        expect(err).not.toBeDefined();
+                    h.fire('before_build').then(function() {
                         if (platform.match(/(win32|win64)/)) {
                             expect(s.calls[0].args[0]).toMatch(/0.bat/);
                             expect(s.calls[1].args[0]).toMatch(/1.bat/);
@@ -139,12 +144,12 @@ describe('hooker', function() {
                             expect(s.calls[0].args[0]).toMatch(/0.sh/);
                             expect(s.calls[1].args[0]).toMatch(/1.sh/);
                         }
-                        done();
-                    });
+                    }, function(err) {
+                        expect(err).not.toBeDefined();
+                    }).fin(done);
                 });
                 it('should pass the project root folder as parameter into the project-level hooks', function(done) {
-                    h.fire('before_build', function(err) {
-                        expect(err).not.toBeDefined();
+                    h.fire('before_build').then(function() {
                         var param_str;
                         if (platform.match(/(win32|win64)/)) {
                             param_str = '0.bat "'+tempDir+'"';
@@ -152,20 +157,21 @@ describe('hooker', function() {
                             param_str = '0.sh "'+tempDir+'"'; 
                         }
                         expect(s.calls[0].args[0].indexOf(param_str)).not.toEqual(-1);
-                        done();
-                    });
+                    }, function(err) {
+                        expect(err).toBeUndefined();
+                    }).fin(done);
                 });
                 it('should skip any files starting with a dot on the scripts', function(done) {
                     shell.cp(path.join(hooks, 'test', '0.bat'), path.join(hook, '.swp.file'));
-                    h.fire('before_build', function(err) {
-                        expect(err).not.toBeDefined();
+                    h.fire('before_build').then(function() {
                         expect(s).not.toHaveBeenCalledWith(path.join(tempDir, '.cordova', 'hooks', 'before_build', '.swp.file') + ' "' + tempDir + '"', jasmine.any(Object), jasmine.any(Function));
-                        done();
-                    });
+                    }, function(err) {
+                        expect(err).not.toBeDefined();
+                    }).fin(done);
                 });
             });
             describe('module-level hooks', function() {
-                var handler = jasmine.createSpy();
+                var handler = jasmine.createSpy().andReturn(Q());
                 var test_event = 'before_build';
                 afterEach(function() {
                     cordova.removeAllListeners(test_event);
@@ -174,51 +180,49 @@ describe('hooker', function() {
 
                 it('should fire handlers using cordova.on', function(done) {
                     cordova.on(test_event, handler);
-                    h.fire(test_event, function(err) {
+                    h.fire(test_event).then(function() {
                         expect(handler).toHaveBeenCalled();
+                    }, function(err) {
                         expect(err).not.toBeDefined();
-                        done();
-                    });
+                    }).fin(done);
                 });
                 it('should pass the project root folder as parameter into the module-level handlers', function(done) {
                     cordova.on(test_event, handler);
-                    h.fire(test_event, function(err) {
+                    h.fire(test_event).then(function() {
                         expect(handler).toHaveBeenCalledWith({root:tempDir});
+                    }, function(err) {
                         expect(err).not.toBeDefined();
-                        done();
-                    });
+                    }).fin(done);
                 });
                 it('should be able to stop listening to events using cordova.off', function(done) {
                     cordova.on(test_event, handler);
                     cordova.off(test_event, handler);
-                    h.fire(test_event, function(err) {
+                    h.fire(test_event).then(function() {
                         expect(handler).not.toHaveBeenCalled();
-                        done();
-                    });
+                    }, function(err) {
+                        expect(err).toBeUndefined();
+                    }).fin(done);
                 });
                 it('should allow for hook to opt into asynchronous execution and block further hooks from firing using the done callback', function(done) {
                     var h1_fired = false;
-                    var timeout = 19;
-                    var h1 = function(root, cb) {
+                    var h1 = function() {
                         h1_fired = true;
-                        setTimeout(cb, timeout);
+                        expect(h2_fired).toBe(false);
+                        return Q();
                     };
                     var h2_fired = false;
                     var h2 = function() {
                         h2_fired = true;
+                        expect(h1_fired).toBe(true);
+                        return Q();
                     };
-                    runs(function() {
-                        cordova.on(test_event, h1);
-                        cordova.on(test_event, h2);
-                        h.fire(test_event, function(err) {
-                            done();
-                        });
+
+                    cordova.on(test_event, h1);
+                    cordova.on(test_event, h2);
+                    h.fire(test_event).then(function() {
                         expect(h1_fired).toBe(true);
-                        expect(h2_fired).toBe(false);
-                    });
-                    waits(timeout);
-                    runs(function() {
                         expect(h2_fired).toBe(true);
+                        done();
                     });
                 });
                 it('should pass data object that fire calls into async handlers', function(done) {
@@ -226,15 +230,13 @@ describe('hooker', function() {
                         "hi":"ho",
                         "offtowork":"wego"
                     };
-                    var async = function(opts, cb) {
+                    var async = function(opts) {
                         data.root = tempDir;
                         expect(opts).toEqual(data);
-                        cb();
+                        return Q();
                     };
                     cordova.on(test_event, async);
-                    h.fire(test_event, data, function() {
-                        done();
-                    });
+                    h.fire(test_event, data).then(done);
                 });
                 it('should pass data object that fire calls into sync handlers', function(done) {
                     var data = {
@@ -246,9 +248,7 @@ describe('hooker', function() {
                         expect(opts).toEqual(data);
                     };
                     cordova.on(test_event, async);
-                    h.fire(test_event, data, function() {
-                        done();
-                    });
+                    h.fire(test_event, data).then(done);
                 });
             });
         });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/lazy_load.spec.js
----------------------------------------------------------------------
diff --git a/spec/lazy_load.spec.js b/spec/lazy_load.spec.js
index f909400..f77ab9d 100644
--- a/spec/lazy_load.spec.js
+++ b/spec/lazy_load.spec.js
@@ -25,6 +25,7 @@ var lazy_load = require('../src/lazy_load'),
     hooker = require('../src/hooker'),
     request = require('request'),
     fs = require('fs'),
+    Q = require('q'),
     platforms = require('../platforms');
 
 describe('lazy_load module', function() {
@@ -35,16 +36,20 @@ describe('lazy_load module', function() {
     describe('cordova method (loads stock cordova libs)', function() {
         var custom;
         beforeEach(function() {
-            custom = spyOn(lazy_load, 'custom');
+            custom = spyOn(lazy_load, 'custom').andReturn(Q());
         });
-        it('should throw if platform is not a stock cordova platform', function() {
-            expect(function() {
-                lazy_load.cordova('atari');
-            }).toThrow('Cordova library "atari" not recognized.');
+        it('should throw if platform is not a stock cordova platform', function(done) {
+            lazy_load.cordova('atari').then(function() {
+                expect('this call').toEqual('to fail');
+            }, function(err) {
+                expect(err).toEqual(new Error('Cordova library "atari" not recognized.'));
+            }).fin(done);
         });
-        it('should invoke lazy_load.custom with appropriate url, platform, and version as specified in platforms manifest', function() {
-            lazy_load.cordova('android');
-            expect(custom).toHaveBeenCalledWith(platforms.android.url + ';a=snapshot;h=' + platforms.android.version + ';sf=tgz', 'cordova', 'android', platforms.android.version, jasmine.any(Function));
+        it('should invoke lazy_load.custom with appropriate url, platform, and version as specified in platforms manifest', function(done) {
+            lazy_load.cordova('android').then(function() {
+                expect(custom).toHaveBeenCalledWith(platforms.android.url + ';a=snapshot;h=' + platforms.android.version + ';sf=tgz', 'cordova', 'android', platforms.android.version);
+                done();
+            });
         });
     });
 
@@ -53,82 +58,108 @@ describe('lazy_load module', function() {
         beforeEach(function() {
             mkdir = spyOn(shell, 'mkdir');
             rm = spyOn(shell, 'rm');
+            mv = spyOn(shell, 'mv');
             sym = spyOn(fs, 'symlinkSync');
             exists = spyOn(fs, 'existsSync').andReturn(false);
-            fire = spyOn(hooker, 'fire').andCallFake(function(evt, data, cb) {
-                cb();
-            });
+            readdir = spyOn(fs, 'readdirSync').andReturn(['somefile.txt']);
+            fire = spyOn(hooker, 'fire').andReturn(Q());
         });
 
         it('should callback with no errors and not fire event hooks if library already exists', function(done) {
             exists.andReturn(true);
-            lazy_load.custom('some url', 'some id', 'platform X', 'three point five', function(err) {
-                expect(err).not.toBeDefined();
+            lazy_load.custom('some url', 'some id', 'platform X', 'three point five').then(function() {
                 expect(fire).not.toHaveBeenCalled()
-                done();
-            });
+            }, function(err) {
+                expect(err).not.toBeDefined();
+            }).fin(done);
         });
-        it('should fire a before_library_download event before it starts downloading a library', function() {
-            lazy_load.custom('some url', 'some id', 'platform X', 'three point five');
-            expect(fire).toHaveBeenCalledWith('before_library_download', {platform:'platform X', url:'some url', id:'some id', version:'three point five'}, jasmine.any(Function));
+        it('should fire a before_library_download event before it starts downloading a library', function(done) {
+            lazy_load.custom('some url', 'some id', 'platform X', 'three point five').then(function() {
+                expect(fire).toHaveBeenCalledWith('before_library_download', {platform:'platform X', url:'some url', id:'some id', version:'three point five'});
+            }, function(err) {
+                expect(err).not.toBeDefined();
+            }).fin(done);
         });
 
         describe('remote URLs for libraries', function() {
             var req,
                 load_spy,
-                p1 = jasmine.createSpy().andReturn({
-                    on:function() {
-                        return {
-                            on:function(){}
-                        }
-                    }
-                });
-            var p2 = jasmine.createSpy().andReturn({pipe:p1});
+                events = {},
+                fakeRequest = {
+                    on: jasmine.createSpy().andCallFake(function(event, cb) {
+                        events[event] = cb;
+                        return fakeRequest;
+                    }),
+                    pipe: jasmine.createSpy().andCallFake(function() { return fakeRequest; })
+                };
             beforeEach(function() {
-                req = spyOn(request, 'get').andReturn({
-                    pipe:p2
+                events = {};
+                fakeRequest.on.reset();
+                fakeRequest.pipe.reset();
+                req = spyOn(request, 'get').andCallFake(function() {
+                    // Fire the 'end' event shortly.
+                    setTimeout(function() {
+                        events['end']();
+                    }, 10);
+                    return fakeRequest;
                 });
                 load_spy = spyOn(npm, 'load').andCallFake(function(cb) { cb(); });
                 npm.config.get = function() { return null; };
             });
 
-            it('should call request with appopriate url params', function() {
+            it('should call request with appopriate url params', function(done) {
                 var url = 'https://github.com/apache/someplugin';
-                lazy_load.custom(url, 'random', 'android', '1.0');
-                expect(req).toHaveBeenCalledWith({
-                    uri:url
-                }, jasmine.any(Function));
+                lazy_load.custom(url, 'random', 'android', '1.0').then(function() {
+                    expect(req).toHaveBeenCalledWith({
+                        uri:url
+                    }, jasmine.any(Function));
+                }, function(err) {
+                    console.log(err);
+                    expect(err).not.toBeDefined();
+                }).fin(done);
             });
-            it('should take into account https-proxy npm configuration var if exists for https:// calls', function() {
+            it('should take into account https-proxy npm configuration var if exists for https:// calls', function(done) {
                 var proxy = 'https://somelocalproxy.com';
                 npm.config.get = function() { return proxy; };
                 var url = 'https://github.com/apache/someplugin';
-                lazy_load.custom(url, 'random', 'android', '1.0');
-                expect(req).toHaveBeenCalledWith({
-                    uri:url,
-                    proxy:proxy
-                }, jasmine.any(Function));
+                lazy_load.custom(url, 'random', 'android', '1.0').then(function() {
+                    expect(req).toHaveBeenCalledWith({
+                        uri:url,
+                        proxy:proxy
+                    }, jasmine.any(Function));
+                }, function(err) {
+                    expect(err).not.toBeDefined();
+                }).fin(done);
             });
-            it('should take into account proxy npm config var if exists for http:// calls', function() {
+            it('should take into account proxy npm config var if exists for http:// calls', function(done) {
                 var proxy = 'http://somelocalproxy.com';
                 npm.config.get = function() { return proxy; };
                 var url = 'http://github.com/apache/someplugin';
-                lazy_load.custom(url, 'random', 'android', '1.0');
-                expect(req).toHaveBeenCalledWith({
-                    uri:url,
-                    proxy:proxy
-                }, jasmine.any(Function));
+                lazy_load.custom(url, 'random', 'android', '1.0').then(function() {
+                    expect(req).toHaveBeenCalledWith({
+                        uri:url,
+                        proxy:proxy
+                    }, jasmine.any(Function));
+                }, function(err) {
+                    expect(err).not.toBeDefined();
+                }).fin(done);
             });
         });
 
         describe('local paths for libraries', function() {
-            it('should symlink to local path', function() {
-                lazy_load.custom('/some/random/lib', 'id', 'X', 'three point five')
-                expect(sym).toHaveBeenCalledWith('/some/random/lib', path.join(util.libDirectory, 'X', 'id', 'three point five'), 'dir');
+            it('should symlink to local path', function(done) {
+                lazy_load.custom('/some/random/lib', 'id', 'X', 'three point five').then(function() {
+                    expect(sym).toHaveBeenCalledWith('/some/random/lib', path.join(util.libDirectory, 'X', 'id', 'three point five'), 'dir');
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
-            it('should fire after hook once done', function() {
-                lazy_load.custom('/some/random/lib', 'id', 'X', 'three point five')
-                expect(fire).toHaveBeenCalledWith('after_library_download', {platform:'X',url:'/some/random/lib',id:'id',version:'three point five',path:path.join(util.libDirectory, 'X', 'id', 'three point five'), symlink:true}, jasmine.any(Function));
+            it('should fire after hook once done', function(done) {
+                lazy_load.custom('/some/random/lib', 'id', 'X', 'three point five').then(function() {
+                    expect(fire).toHaveBeenCalledWith('after_library_download', {platform:'X',url:'/some/random/lib',id:'id',version:'three point five',path:path.join(util.libDirectory, 'X', 'id', 'three point five'), symlink:true});
+                }, function(err) {
+                    expect(err).toBeUndefined();
+                }).fin(done);
             });
         });
     });
@@ -136,10 +167,10 @@ describe('lazy_load module', function() {
     describe('based_on_config method', function() {
         var cordova, custom;
         beforeEach(function() {
-            cordova = spyOn(lazy_load, 'cordova');
-            custom = spyOn(lazy_load, 'custom');
+            cordova = spyOn(lazy_load, 'cordova').andReturn(Q());
+            custom = spyOn(lazy_load, 'custom').andReturn(Q());
         });
-        it('should invoke custom if a custom lib is specified', function() {
+        it('should invoke custom if a custom lib is specified', function(done) {
             var read = spyOn(config, 'read').andReturn({
                 lib:{
                     maybe:{
@@ -151,12 +182,18 @@ describe('lazy_load module', function() {
             });
             var p = '/some/random/custom/path';
             custom_path.andReturn(p);
-            lazy_load.based_on_config('yup', 'maybe');
-            expect(custom).toHaveBeenCalledWith('you or eye?', 'eye dee', 'maybe', 'four point twenty', undefined);
+            lazy_load.based_on_config('yup', 'maybe').then(function() {
+                expect(custom).toHaveBeenCalledWith('you or eye?', 'eye dee', 'maybe', 'four point twenty');
+            }, function(err) {
+                expect(err).toBeUndefined();
+            }).fin(done);
         });
-        it('should invoke cordova if no custom lib is specified', function() {
-            lazy_load.based_on_config('yup', 'ios');
-            expect(cordova).toHaveBeenCalledWith('ios', undefined);
+        it('should invoke cordova if no custom lib is specified', function(done) {
+            lazy_load.based_on_config('yup', 'ios').then(function() {
+                expect(cordova).toHaveBeenCalledWith('ios');
+            }, function(err) {
+                expect(err).toBeUndefined();
+            }).fin(done);
         });
     });
 });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/metadata/android_parser.spec.js
----------------------------------------------------------------------
diff --git a/spec/metadata/android_parser.spec.js b/spec/metadata/android_parser.spec.js
index 228a11b..71479ae 100644
--- a/spec/metadata/android_parser.spec.js
+++ b/spec/metadata/android_parser.spec.js
@@ -22,6 +22,8 @@ var platforms = require('../../platforms'),
     shell = require('shelljs'),
     fs = require('fs'),
     ET = require('elementtree'),
+    Q = require('q'),
+    child_process = require('child_process'),
     config = require('../../src/config'),
     config_parser = require('../../src/config_parser'),
     cordova = require('../../cordova');
@@ -31,12 +33,25 @@ describe('android project parser', function() {
     var exists, exec, custom;
     beforeEach(function() {
         exists = spyOn(fs, 'existsSync').andReturn(true);
-        exec = spyOn(shell, 'exec').andCallFake(function(cmd, opts, cb) {
-            cb(0, 'android-17');
+        exec = spyOn(child_process, 'exec').andCallFake(function(cmd, opts, cb) {
+            if (!cb) cb = opts;
+            cb(null, 'android-17', '');
         });
         custom = spyOn(config, 'has_custom_path').andReturn(false);
     });
 
+    function wrapper(p, done, post) {
+        p.then(post, function(err) {
+            expect(err).toBeUndefined();
+        }).fin(done);
+    }
+
+    function errorWrapper(p, done, post) {
+        p.then(function() {
+            expect('this call').toBe('fail');
+        }, post).fin(done);
+    }
+
     describe('constructions', function() {
         it('should throw if provided directory does not contain an AndroidManifest.xml', function() {
             exists.andReturn(false);
@@ -58,45 +73,39 @@ describe('android project parser', function() {
     describe('check_requirements', function() {
         it('should fire a callback if there is an error during shelling out', function(done) {
             exec.andCallFake(function(cmd, opts, cb) {
-                cb(50, 'there was an errorz!');
+                if (!cb) cb = opts;
+                cb(50, 'there was an errorz!', '');
             });
-            platforms.android.parser.check_requirements(proj, function(err) {
+            errorWrapper(platforms.android.parser.check_requirements(proj), done, function(err) {
                 expect(err).toContain('there was an errorz!');
-                done();
             });
         });
         it('should fire a callback if `android list target` does not return anything containing "android-17"', function(done) {
             exec.andCallFake(function(cmd, opts, cb) {
-                cb(0, 'android-15');
+                if (!cb) cb = opts;
+                cb(0, 'android-15', '');
             });
-            platforms.android.parser.check_requirements(proj, function(err) {
-                expect(err).toEqual('Please install Android target 17 (the Android 4.2 SDK). Make sure you have the latest Android tools installed as well. Run `android` from your command-line to install/update any missing SDKs or tools.');
-                done();
+            errorWrapper(platforms.android.parser.check_requirements(proj), done, function(err) {
+                expect(err).toEqual(new Error('Please install Android target 17 (the Android 4.2 SDK). Make sure you have the latest Android tools installed as well. Run `android` from your command-line to install/update any missing SDKs or tools.'));
             });
         });
         it('should check that `android` is on the path by calling `android list target`', function(done) {
-            platforms.android.parser.check_requirements(proj, function(err) {
-                expect(err).toEqual(false);
-                expect(exec).toHaveBeenCalledWith('android list target', jasmine.any(Object), jasmine.any(Function));
-                done();
+            wrapper(platforms.android.parser.check_requirements(proj), done, function() {
+                expect(exec).toHaveBeenCalledWith('android list target', jasmine.any(Function));
             });
         });
         it('should check that we can update an android project by calling `android update project` on stock android path', function(done) {
-            platforms.android.parser.check_requirements(proj, function(err) {
-                expect(err).toEqual(false);
+            wrapper(platforms.android.parser.check_requirements(proj), done, function() {
                 expect(exec.mostRecentCall.args[0]).toMatch(/^android update project -p .* -t android-17$/gi);
                 expect(exec.mostRecentCall.args[0]).toContain(util.libDirectory);
-                done();
             });
         });
         it('should check that we can update an android project by calling `android update project` on a custom path if it is so defined', function(done) {
             var custom_path = path.join('some', 'custom', 'path', 'to', 'android', 'lib');
             custom.andReturn(custom_path);
-            platforms.android.parser.check_requirements(proj, function(err) {
-                expect(err).toEqual(false);
+            wrapper(platforms.android.parser.check_requirements(proj), done, function() {
                 expect(exec.mostRecentCall.args[0]).toMatch(/^android update project -p .* -t android-17$/gi);
                 expect(exec.mostRecentCall.args[0]).toContain(custom_path);
-                done();
             });
         });
     });
@@ -277,9 +286,8 @@ describe('android project parser', function() {
             it('should throw if update_from_config throws', function(done) {
                 var err = new Error('uh oh!');
                 config.andCallFake(function() { throw err; });
-                p.update_project({}, function(err) {
+                errorWrapper(p.update_project({}), done, function(err) {
                     expect(err).toEqual(err);
-                    done();
                 });
             });
             it('should call update_www', function() {

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/metadata/blackberry_parser.spec.js
----------------------------------------------------------------------
diff --git a/spec/metadata/blackberry_parser.spec.js b/spec/metadata/blackberry_parser.spec.js
index 354109e..97d8c7d 100644
--- a/spec/metadata/blackberry_parser.spec.js
+++ b/spec/metadata/blackberry_parser.spec.js
@@ -23,6 +23,8 @@ var platforms = require('../../platforms'),
     shell = require('shelljs'),
     fs = require('fs'),
     ET = require('elementtree'),
+    Q = require('q'),
+    child_process = require('child_process'),
     config = require('../../src/config'),
     prompt = require('prompt'),
     config_parser = require('../../src/config_parser'),
@@ -35,11 +37,23 @@ describe('blackberry10 project parser', function() {
         exists = spyOn(fs, 'existsSync').andReturn(true);
         custom = spyOn(config, 'has_custom_path').andReturn(false);
         config_p = spyOn(util, 'config_parser');
-        sh = spyOn(shell, 'exec').andCallFake(function(cmd, opts, cb) {
-            cb(0, '');
+        sh = spyOn(child_process, 'exec').andCallFake(function(cmd, opts, cb) {
+            (cb || opts)(0, '', '');
         });
     });
 
+    function wrapper(p, done, post) {
+        p.then(post, function(err) {
+            expect(err).toBeUndefined();
+        }).fin(done);
+    }
+
+    function errorWrapper(p, done, post) {
+        p.then(function() {
+            expect('this call').toBe('fail');
+        }, post).fin(done);
+    }
+
     describe('constructions', function() {
         it('should throw an exception with a path that is not a native blackberry project', function() {
             exists.andReturn(false);
@@ -59,17 +73,15 @@ describe('blackberry10 project parser', function() {
     describe('check_requirements', function() {
         it('should fire a callback if the blackberry-deploy shell-out fails', function(done) {
             sh.andCallFake(function(cmd, opts, cb) {
-                cb(1, 'no bb-deploy dewd!');
+                (cb || opts)(1, 'no bb-deploy dewd!');
             });
-            platforms.blackberry10.parser.check_requirements(proj, function(err) {
+            errorWrapper(platforms.blackberry10.parser.check_requirements(proj), done, function(err) {
                 expect(err).toContain('no bb-deploy dewd');
-                done();
             });
         });
         it('should fire a callback with no error if shell out is successful', function(done) {
-            platforms.blackberry10.parser.check_requirements(proj, function(err) {
-                expect(err).toEqual(false);
-                done();
+            wrapper(platforms.blackberry10.parser.check_requirements(proj), done, function() {
+                expect(1).toBe(1);
             });
         });
     });
@@ -97,6 +109,8 @@ describe('blackberry10 project parser', function() {
                 xml_access_add = jasmine.createSpy('xml access add');
                 xml_update = jasmine.createSpy('xml update');
                 xml_append = jasmine.createSpy('xml append');
+                xml_preference_remove = jasmine.createSpy('xml preference rm');
+                xml_preference_add = jasmine.createSpy('xml preference add');
                 p.xml.name = xml_name;
                 p.xml.packageName = xml_pkg;
                 p.xml.version = xml_version;
@@ -109,6 +123,10 @@ describe('blackberry10 project parser', function() {
                 p.xml.doc = {
                     getroot:function() { return { append:xml_append}; }
                 };
+                p.xml.preference = {
+                    add: xml_preference_add,
+                    remove: xml_preference_remove
+                };
                 find_obj = {
                     text:'hi'
                 };
@@ -131,7 +149,9 @@ describe('blackberry10 project parser', function() {
                 cfg.content = function() { return 'index.html'; };
                 cfg.packageName = function() { return 'testpkg'; };
                 cfg.version = function() { return 'one point oh'; };
+                cfg.access = {};
                 cfg.access.getAttributes = function() { return []; };
+                cfg.access.remove = function() { };
                 cfg.preference.get = function() { return []; };
             });
 
@@ -178,6 +198,10 @@ describe('blackberry10 project parser', function() {
             });
         });
         describe('update_www method', function() {
+            beforeEach(function() {
+                p.xml.update = jasmine.createSpy('xml update');
+            });
+
             it('should rm project-level www and cp in platform agnostic www', function() {
                 p.update_www();
                 expect(rm).toHaveBeenCalled();
@@ -226,33 +250,37 @@ describe('blackberry10 project parser', function() {
                 svn = spyOn(util, 'deleteSvnFolders');
                 parse = spyOn(JSON, 'parse').andReturn({blackberry:{qnx:{}}});
             });
-            it('should call update_from_config', function() {
-                p.update_project();
-                expect(config).toHaveBeenCalled();
+            it('should call update_from_config', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(config).toHaveBeenCalled();
+                });
             });
             it('should throw if update_from_config throws', function(done) {
                 var err = new Error('uh oh!');
                 config.andCallFake(function() { throw err; });
-                p.update_project({}, function(err) {
-                    expect(err).toEqual(err);
-                    done();
+                errorWrapper(p.update_project({}), done, function(e) {
+                    expect(e).toEqual(err);
                 });
             });
-            it('should call update_www', function() {
-                p.update_project();
-                expect(www).toHaveBeenCalled();
+            it('should call update_www', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(www).toHaveBeenCalled();
+                });
             });
-            it('should call update_overrides', function() {
-                p.update_project();
-                expect(overrides).toHaveBeenCalled();
+            it('should call update_overrides', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(overrides).toHaveBeenCalled();
+                });
             });
-            it('should call update_staging', function() {
-                p.update_project();
-                expect(staging).toHaveBeenCalled();
+            it('should call update_staging', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(staging).toHaveBeenCalled();
+                });
             });
-            it('should call deleteSvnFolders', function() {
-                p.update_project();
-                expect(svn).toHaveBeenCalled();
+            it('should call deleteSvnFolders', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(svn).toHaveBeenCalled();
+                });
             });
         });
     });

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/017b7d9b/spec/metadata/ios_parser.spec.js
----------------------------------------------------------------------
diff --git a/spec/metadata/ios_parser.spec.js b/spec/metadata/ios_parser.spec.js
index 509cf26..f3e57be 100644
--- a/spec/metadata/ios_parser.spec.js
+++ b/spec/metadata/ios_parser.spec.js
@@ -24,6 +24,8 @@ var platforms = require('../../platforms'),
     xcode = require('xcode'),
     ET = require('elementtree'),
     fs = require('fs'),
+    Q = require('q'),
+    child_process = require('child_process'),
     config = require('../../src/config'),
     config_parser = require('../../src/config_parser'),
     cordova = require('../../cordova');
@@ -32,13 +34,27 @@ describe('ios project parser', function () {
     var proj = path.join('some', 'path');
     var exec, custom, readdir, cfg_parser;
     beforeEach(function() {
-        exec = spyOn(shell, 'exec').andCallFake(function(cmd, opts, cb) {
-            cb(0, '');
+        exec = spyOn(child_process, 'exec').andCallFake(function(cmd, opts, cb) {
+            if (!cb) cb = opts;
+            cb(null, '', '');
         });
         custom = spyOn(config, 'has_custom_path').andReturn(false);
         readdir = spyOn(fs, 'readdirSync').andReturn(['test.xcodeproj']);
         cfg_parser = spyOn(util, 'config_parser');
     });
+
+    function wrapper(p, done, post) {
+        p.then(post, function(err) {
+            expect(err).toBeUndefined();
+        }).fin(done);
+    }
+
+    function errorWrapper(p, done, post) {
+        p.then(function() {
+            expect('this call').toBe('fail');
+        }, post).fin(done);
+    }
+
     describe('constructions', function() {
         it('should throw if provided directory does not contain an xcodeproj file', function() {
             readdir.andReturn(['noxcodehere']);
@@ -58,29 +74,29 @@ describe('ios project parser', function () {
     describe('check_requirements', function() {
         it('should fire a callback if there is an error during shelling out', function(done) {
             exec.andCallFake(function(cmd, opts, cb) {
-                cb(50, 'there was an errorz!');
+                if (!cb) cb = opts;
+                cb(50, 'there was an errorz!', '');
             });
-            platforms.ios.parser.check_requirements(proj, function(err) {
+            errorWrapper(platforms.ios.parser.check_requirements(proj), done, function(err) {
                 expect(err).toContain('there was an errorz!');
-                done();
             });
         });
         it('should fire a callback if the xcodebuild version is less than 4.5.x', function(done) {
             exec.andCallFake(function(cmd, opts, cb) {
-                cb(0, 'version 4.4.9');
+                if (!cb) cb = opts;
+                cb(0, 'version 4.4.9', '');
             });
-            platforms.ios.parser.check_requirements(proj, function(err) {
-                expect(err).toEqual('Xcode version installed is too old. Minimum: >=4.5.x, yours: 4.4.9');
-                done();
+            errorWrapper(platforms.ios.parser.check_requirements(proj), done, function(err) {
+                expect(err).toEqual(new Error('Xcode version installed is too old. Minimum: >=4.5.x, yours: 4.4.9'));
             });
         });
         it('should not return an error if the xcodebuild version 2 digits and not proper semver (eg: 5.0), but still satisfies the MIN_XCODE_VERSION', function(done) {
             exec.andCallFake(function(cmd, opts, cb) {
-                cb(0, 'version 5.0');
+                if (!cb) cb = opts;
+                cb(0, 'version 5.0', '');
             });
-            platforms.ios.parser.check_requirements(proj, function(err) {
-                expect(err).toBe(false);
-                done();
+            wrapper(platforms.ios.parser.check_requirements(proj), done, function() {
+                expect(1).toBe(1);
             });
         });
     });
@@ -162,64 +178,56 @@ describe('ios project parser', function () {
             it('should update the app name in pbxproj by calling xcode.updateProductName, and move the ios native files to match the new name', function(done) {
                 var test_path = path.join(proj, 'platforms', 'ios', 'test');
                 var testname_path = path.join(proj, 'platforms', 'ios', 'testname');
-                p.update_from_config(cfg, function() {
+                wrapper(p.update_from_config(cfg), done, function() {
                     expect(update_name).toHaveBeenCalledWith('testname');
                     expect(mv).toHaveBeenCalledWith(path.join(test_path, 'test-Info.plist'), path.join(test_path, 'testname-Info.plist'));
                     expect(mv).toHaveBeenCalledWith(path.join(test_path, 'test-Prefix.pch'), path.join(test_path, 'testname-Prefix.pch'));
                     expect(mv).toHaveBeenCalledWith(test_path + '.xcodeproj', testname_path + '.xcodeproj');
                     expect(mv).toHaveBeenCalledWith(test_path, testname_path);
-                    done();
                 });
             });
             it('should write out the app id to info plist as CFBundleIdentifier', function(done) {
-                p.update_from_config(cfg, function() {
+                wrapper(p.update_from_config(cfg), done, function() {
                     expect(plist_build.mostRecentCall.args[0].CFBundleIdentifier).toEqual('testpkg');
-                    done();
                 });
             });
             it('should write out the app version to info plist as CFBundleVersion', function(done) {
-                p.update_from_config(cfg, function() {
+                wrapper(p.update_from_config(cfg), done, function() {
                     expect(plist_build.mostRecentCall.args[0].CFBundleVersion).toEqual('one point oh');
-                    done();
                 });
             });
             it('should wipe out the ios whitelist every time', function(done) {
-                p.update_from_config(cfg, function() {
+                wrapper(p.update_from_config(cfg), done, function() {
                     expect(cfg_access_rm).toHaveBeenCalled();
-                    done();
                 });
             });
             it('should update the whitelist', function(done) {
                 cfg.access.get = function() { return ['one'] };
-                p.update_from_config(cfg, function() {
+                wrapper(p.update_from_config(cfg), done, function() {
                     expect(cfg_access_add).toHaveBeenCalledWith('one');
-                    done();
                 });
             });
             it('should update preferences', function(done) {
                 var sample_pref = {name:'pref',value:'yes'};
                 cfg.preference.get = function() { return [sample_pref] };
-                p.update_from_config(cfg, function() {
+                wrapper(p.update_from_config(cfg), done, function() {
                     expect(cfg_pref_add).toHaveBeenCalledWith(sample_pref);
-                    done();
                 });
             });
             it('should update the content tag / start page', function(done) {
-                p.update_from_config(cfg, function() {
+                wrapper(p.update_from_config(cfg), done, function() {
                     expect(cfg_content).toHaveBeenCalledWith('index.html');
-                    done();
                 });
             });
             it('should wipe out the ios preferences every time', function(done) {
-                p.update_from_config(cfg, function() {
+                wrapper(p.update_from_config(cfg), done, function() {
                     expect(cfg_pref_rm).toHaveBeenCalled();
-                    done();
                 });
             });
             it('should write out default preferences every time', function(done) {
                 var sample_pref = {name:'preftwo',value:'false'};
                 cfg.preference.get = function() { return [sample_pref] };
-                p.update_from_config(cfg, function() {
+                wrapper(p.update_from_config(cfg), done, function() {
                     expect(cfg_pref_add).toHaveBeenCalledWith({name:"KeyboardDisplayRequiresUserAction",value:"true"});
                     expect(cfg_pref_add).toHaveBeenCalledWith({name:"SuppressesIncrementalRendering",value:"false"});
                     expect(cfg_pref_add).toHaveBeenCalledWith({name:"UIWebViewBounce",value:"true"});
@@ -232,7 +240,6 @@ describe('ios project parser', function () {
                     expect(cfg_pref_add).toHaveBeenCalledWith({name:"AllowInlineMediaPlayback",value:"false"});
                     expect(cfg_pref_add).toHaveBeenCalledWith({name:"OpenAllWhitelistURLsInWebView",value:"false"});
                     expect(cfg_pref_add).toHaveBeenCalledWith({name:"BackupWebStorage",value:"cloud"});
-                    done();
                 });
             });
         });
@@ -301,41 +308,43 @@ describe('ios project parser', function () {
         describe('update_project method', function() {
             var config, www, overrides, staging, svn;
             beforeEach(function() {
-                config = spyOn(p, 'update_from_config').andCallFake(function(cfg, cb) { cb() });
+                config = spyOn(p, 'update_from_config').andReturn(Q());
                 www = spyOn(p, 'update_www');
                 overrides = spyOn(p, 'update_overrides');
                 staging = spyOn(p, 'update_staging');
                 svn = spyOn(util, 'deleteSvnFolders');
             });
-            it('should call update_from_config', function() {
-                p.update_project();
-                expect(config).toHaveBeenCalled();
+            it('should call update_from_config', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(config).toHaveBeenCalled();
+                });
             });
             it('should throw if update_from_config errors', function(done) {
-                var err = new Error('uh oh!');
-                config.andCallFake(function(cfg, cb) { cb(err); });
-                p.update_project({}, function(err) {
-                    expect(err).toEqual(err);
-                    done();
+                var e = new Error('uh oh!');
+                config.andReturn(Q.reject(e));
+                errorWrapper(p.update_project({}), done, function(err) {
+                    expect(err).toEqual(e);
                 });
             });
             it('should call update_www', function(done) {
-                p.update_project({}, function() {
+                wrapper(p.update_project({}), done, function() {
                     expect(www).toHaveBeenCalled();
-                    done();
                 });
             });
-            it('should call update_overrides', function() {
-                p.update_project();
-                expect(overrides).toHaveBeenCalled();
+            it('should call update_overrides', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(overrides).toHaveBeenCalled();
+                });
             });
-            it('should call update_staging', function() {
-                p.update_project();
-                expect(staging).toHaveBeenCalled();
+            it('should call update_staging', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(staging).toHaveBeenCalled();
+                });
             });
-            it('should call deleteSvnFolders', function() {
-                p.update_project();
-                expect(svn).toHaveBeenCalled();
+            it('should call deleteSvnFolders', function(done) {
+                wrapper(p.update_project(), done, function() {
+                    expect(svn).toHaveBeenCalled();
+                });
             });
         });
     });