You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by mw...@apache.org on 2013/05/15 22:35:42 UTC

[03/37] git commit: Reorganize specs into cordova-cli/ and platform-script/

Reorganize specs into cordova-cli/ and platform-script/


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

Branch: refs/heads/master
Commit: b50b52841fe5016888a298ac0a48d9f5d7fbf410
Parents: 2293706
Author: Benn Mapes <be...@gmail.com>
Authored: Tue May 14 15:18:41 2013 -0700
Committer: Michael Brooks <mi...@michaelbrooks.ca>
Committed: Wed May 15 10:20:34 2013 -0700

----------------------------------------------------------------------
 package.json                                       |    2 +-
 spec/compile.spec.js                               |  179 --------
 spec/config_parser.spec.js                         |  167 -------
 spec/cordova-cli/compile.spec.js                   |  179 ++++++++
 spec/cordova-cli/config_parser.spec.js             |  167 +++++++
 spec/cordova-cli/create.spec.js                    |   68 +++
 spec/cordova-cli/emulate.spec.js                   |  191 ++++++++
 spec/cordova-cli/helper.js                         |   20 +
 spec/cordova-cli/hooker.spec.js                    |  137 ++++++
 spec/cordova-cli/platform.spec.js                  |  339 +++++++++++++++
 spec/cordova-cli/plugin.spec.js                    |  164 +++++++
 spec/cordova-cli/plugin_parser.spec.js             |   42 ++
 spec/cordova-cli/prepare.spec.js                   |  133 ++++++
 spec/cordova-cli/serve.spec.js                     |  132 ++++++
 spec/create.spec.js                                |   68 ---
 spec/emulate.spec.js                               |  191 --------
 spec/helper.js                                     |   20 -
 spec/hooker.spec.js                                |  137 ------
 spec/metadata/android_parser.spec.js               |  220 ----------
 spec/metadata/blackberry_parser.spec.js            |  249 -----------
 spec/metadata/ios_parser.spec.js                   |  218 ---------
 .../platform-script/android/android_parser.spec.js |  220 ++++++++++
 .../blackberry/blackberry_parser.spec.js           |  249 +++++++++++
 spec/platform-script/ios/ios_parser.spec.js        |  218 +++++++++
 spec/platform.spec.js                              |  339 ---------------
 spec/plugin.spec.js                                |  164 -------
 spec/plugin_parser.spec.js                         |   42 --
 spec/prepare.spec.js                               |  133 ------
 spec/serve.spec.js                                 |  132 ------
 29 files changed, 2260 insertions(+), 2260 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b50b5284/package.json
----------------------------------------------------------------------
diff --git a/package.json b/package.json
index 3a41515..96e53bd 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,7 @@
     "cordova": "./bin/cordova"
   },
   "scripts": {
-    "test": "./node_modules/jasmine-node/bin/jasmine-node --color spec",
+    "test": "./node_modules/jasmine-node/bin/jasmine-node --color spec/cordova-cli",
     "install": "node bootstrap.js"
   },
   "repository": {

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b50b5284/spec/compile.spec.js
----------------------------------------------------------------------
diff --git a/spec/compile.spec.js b/spec/compile.spec.js
deleted file mode 100644
index f13792a..0000000
--- a/spec/compile.spec.js
+++ /dev/null
@@ -1,179 +0,0 @@
-/**
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-*/
-var cordova = require('../cordova'),
-    et = require('elementtree'),
-    shell = require('shelljs'),
-    path = require('path'),
-    fs = require('fs'),
-    config_parser = require('../src/config_parser'),
-    android_parser = require('../src/metadata/android_parser'),
-    ios_parser = require('../src/metadata/ios_parser'),
-    blackberry_parser = require('../src/metadata/blackberry_parser'),
-    hooker = require('../src/hooker'),
-    fixtures = path.join(__dirname, 'fixtures'),
-    hooks = path.join(fixtures, 'hooks'),
-    tempDir = path.join(__dirname, '..', 'temp'),
-    cordova_project = path.join(fixtures, 'projects', 'cordova');
-
-var cwd = process.cwd();
-describe('compile command', function() {
-    beforeEach(function() {
-        shell.rm('-rf', tempDir);
-        shell.mkdir('-p', tempDir);
-    });
-
-    it('should not run inside a Cordova-based project with no added platforms', function() {
-        this.after(function() {
-            process.chdir(cwd);
-        });
-
-        cordova.create(tempDir);
-        process.chdir(tempDir);
-        expect(function() {
-            cordova.compile();
-        }).toThrow();
-    });
-    
-    it('should run inside a Cordova-based project with at least one added platform', function() {
-        // move platform project fixtures over to fake cordova into thinking platforms were added
-        // TODO: possibly add this to helper?
-        shell.mv('-f', path.join(cordova_project, 'platforms', 'blackberry'), path.join(tempDir));
-        shell.mv('-f', path.join(cordova_project, 'platforms', 'ios'), path.join(tempDir));
-        this.after(function() {
-            process.chdir(cwd);
-            shell.mv('-f', path.join(tempDir, 'blackberry'), path.join(cordova_project, 'platforms', 'blackberry'));
-            shell.mv('-f', path.join(tempDir, 'ios'), path.join(cordova_project, 'platforms', 'ios'));
-        });
-
-        process.chdir(cordova_project);
-
-        var sh_spy = spyOn(shell, 'exec');
-
-        expect(function() {
-            cordova.compile();
-            expect(sh_spy).toHaveBeenCalled();
-        }).not.toThrow();
-    });
-    it('should not run outside of a Cordova-based project', function() {
-        this.after(function() {
-            process.chdir(cwd);
-        });
-
-        shell.mkdir('-p', tempDir);
-        process.chdir(tempDir);
-
-        expect(function() {
-            cordova.compile();
-        }).toThrow();
-    });
-
-    describe('hooks', function() {
-        var s;
-        beforeEach(function() {
-            s = spyOn(hooker.prototype, 'fire').andReturn(true);
-        });
-
-        describe('when platforms are added', function() {
-            beforeEach(function() {
-                shell.mv('-f', path.join(cordova_project, 'platforms', 'blackberry'), path.join(tempDir));
-                shell.mv('-f', path.join(cordova_project, 'platforms', 'ios'), path.join(tempDir));
-                process.chdir(cordova_project);
-            });
-            afterEach(function() {
-                shell.mv('-f', path.join(tempDir, 'blackberry'), path.join(cordova_project, 'platforms', 'blackberry'));
-                shell.mv('-f', path.join(tempDir, 'ios'), path.join(cordova_project, 'platforms', 'ios'));
-                process.chdir(cwd);
-            });
-
-            it('should fire before hooks through the hooker module', function() {
-                spyOn(shell, 'exec');
-                cordova.compile();
-                expect(s).toHaveBeenCalledWith('before_compile');
-            });
-            it('should fire after hooks through the hooker module', function() {
-                var sh_spy = spyOn(shell, 'exec');
-                cordova.compile();
-                sh_spy.mostRecentCall.args[2](0); // shell cb
-                expect(s).toHaveBeenCalledWith('after_compile');
-            });
-        });
-
-        describe('with no platforms added', function() {
-            beforeEach(function() {
-                cordova.create(tempDir);
-                process.chdir(tempDir);
-            });
-            afterEach(function() {
-                process.chdir(cwd);
-            });
-            it('should not fire the hooker', function() {
-                expect(function() {
-                    cordova.compile();
-                }).toThrow();
-                expect(s).not.toHaveBeenCalledWith('before_compile');
-                expect(s).not.toHaveBeenCalledWith('after_compile');
-            });
-        });
-    });
-    describe('per platform', function() {
-        beforeEach(function() {
-            process.chdir(cordova_project);
-        });
-
-        afterEach(function() {
-            process.chdir(cwd);
-        });
-       
-        describe('Android', function() {
-            it('should shell out to build command on Android', function() {
-                var s = spyOn(require('shelljs'), 'exec').andReturn({code:0});
-                cordova.compile('android');
-                expect(s.mostRecentCall.args[0].match(/\/cordova\/build/)).not.toBeNull();
-            });
-        });
-        describe('iOS', function() {
-            it('should shell out to build command on iOS', function() {
-                var s = spyOn(require('shelljs'), 'exec');
-                cordova.compile('ios');
-                expect(s).toHaveBeenCalled();
-                expect(s.mostRecentCall.args[0].match(/\/cordova\/build/)).not.toBeNull();
-            });
-        });
-        describe('BlackBerry', function() {
-            it('should shell out to ant command on blackberry', function() {
-                var s = spyOn(shell, 'exec');
-                cordova.compile('blackberry');
-                expect(s).toHaveBeenCalled();
-                expect(s.mostRecentCall.args[0]).toMatch(/ant -f .*build\.xml" qnx load-device/);
-            });
-        });
-        it('should not treat a .gitignore file as a platform', function() {
-            var gitignore = path.join(cordova_project, 'platforms', '.gitignore');
-            fs.writeFileSync(gitignore, 'somethinghere', 'utf-8');
-            this.after(function() {
-                shell.rm('-f', gitignore);
-            });
-            var s = spyOn(shell, 'exec');
-            cordova.compile();
-            expect(s.calls[0].args[0]).not.toMatch(/\.gitignore/);
-            expect(s.calls[1].args[0]).not.toMatch(/\.gitignore/);
-            expect(s.calls[1].args[0]).not.toMatch(/\.gitignore/);
-        });
-    });
-});

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b50b5284/spec/config_parser.spec.js
----------------------------------------------------------------------
diff --git a/spec/config_parser.spec.js b/spec/config_parser.spec.js
deleted file mode 100644
index 99bc717..0000000
--- a/spec/config_parser.spec.js
+++ /dev/null
@@ -1,167 +0,0 @@
-
-/**
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-*/
-var cordova = require('../cordova'),
-    path = require('path'),
-    fs = require('fs'),
-    shell = require('shelljs'),
-    config_parser = require('../src/config_parser'),
-    tempDir = path.join(__dirname, '..', 'temp'),
-    et = require('elementtree'),
-    xml = path.join(tempDir, 'www', 'config.xml');
-
-
-describe('config.xml parser', function () {
-    beforeEach(function() {
-        shell.rm('-rf', tempDir);
-        cordova.create(tempDir);
-    });
-
-    it('should create an instance based on an xml file', function() {
-        var cfg;
-        expect(function () {
-            cfg = new config_parser(xml);
-        }).not.toThrow();
-        expect(cfg).toBeDefined();
-        expect(cfg.doc).toBeDefined();
-    });
-
-    describe('package name / id', function() {
-        var cfg;
-
-        beforeEach(function() {
-            cfg = new config_parser(xml);
-        });
-
-        it('should get the (default) packagename', function() {
-            expect(cfg.packageName()).toEqual('io.cordova.hellocordova');
-        });
-        it('should allow setting the packagename', function() {
-            cfg.packageName('this.is.bat.country');
-            expect(cfg.packageName()).toEqual('this.is.bat.country');
-        });
-        it('should write to disk after setting the packagename', function() {
-            cfg.packageName('this.is.bat.country');
-            expect(fs.readFileSync(xml, 'utf-8')).toMatch(/id="this\.is\.bat\.country"/);
-        });
-    });
-
-    describe('app name', function() {
-        var cfg;
-
-        beforeEach(function() {
-            cfg = new config_parser(xml);
-        });
-
-        it('should get the (default) app name', function() {
-            expect(cfg.name()).toEqual('HelloCordova');
-        });
-        it('should allow setting the app name', function() {
-            cfg.name('this.is.bat.country');
-            expect(cfg.name()).toEqual('this.is.bat.country');
-        });
-        it('should write to disk after setting the name', function() {
-            cfg.name('one toke over the line');
-            expect(fs.readFileSync(xml, 'utf-8')).toMatch(/<name>one toke over the line<\/name>/);
-        });
-    });
-
-    describe('access elements (whitelist)', function() {
-        var cfg;
-
-        beforeEach(function() {
-            cfg = new config_parser(xml);
-        });
-
-        describe('getter', function() {
-            it('should get the (default) access element', function() {
-                expect(cfg.access.get()[0]).toEqual('*');
-            });
-            it('should return an array of all access origin uris via access()', function() {
-                expect(cfg.access.get() instanceof Array).toBe(true);
-            });
-        });
-        describe('setters', function() {
-            it('should allow removing a uri from the access list', function() {
-                cfg.access.remove('*');
-                expect(cfg.access.get().length).toEqual(0);
-            });
-            it('should write to disk after removing a uri', function() {
-                cfg.access.remove('*');
-                expect(fs.readFileSync(xml, 'utf-8')).not.toMatch(/<access.*\/>/);
-            });
-            it('should allow adding a new uri to the access list', function() {
-                cfg.access.add('http://canucks.com');
-                expect(cfg.access.get().length).toEqual(2);
-                expect(cfg.access.get().indexOf('http://canucks.com') > -1).toBe(true);
-            });
-            it('should write to disk after adding a uri', function() {
-                cfg.access.add('http://cordova.io');
-                expect(fs.readFileSync(xml, 'utf-8')).toMatch(/<access origin="http:\/\/cordova\.io/);
-            });
-            it('should allow removing all access elements when no parameter is specified', function() {
-                cfg.access.add('http://cordova.io');
-                cfg.access.remove();
-
-                expect(fs.readFileSync(xml, 'utf-8')).not.toMatch(/<access.*\/>/);
-            });
-        });
-    });
-
-    describe('preference elements', function() {
-        var cfg;
-
-        beforeEach(function() {
-            cfg = new config_parser(xml);
-        });
-
-        describe('getter', function() {
-            it('should get all preference elements', function() {
-                expect(cfg.preference.get()[0].name).toEqual('phonegap-version');
-                expect(cfg.preference.get()[0].value).toEqual('1.9.0');
-            });
-            it('should return an array of all preference name/value pairs', function() {
-                expect(cfg.preference.get() instanceof Array).toBe(true);
-            });
-        });
-        describe('setters', function() {
-            it('should allow removing a preference by name', function() {
-                cfg.preference.remove('phonegap-version');
-                expect(cfg.preference.get().length).toEqual(3);
-            });
-            it('should write to disk after removing a preference', function() {
-                cfg.preference.remove('phonegap-version');
-                expect(fs.readFileSync(xml, 'utf-8')).not.toMatch(/<preference\sname="phonegap-version"/);
-            });
-            it('should allow adding a new preference', function() {
-                cfg.preference.add({name:'UIWebViewBounce',value:'false'});
-                expect(cfg.preference.get().length).toEqual(5);
-                expect(cfg.preference.get()[4].value).toEqual('false');
-            });
-            it('should write to disk after adding a preference', function() {
-                cfg.preference.add({name:'UIWebViewBounce',value:'false'});
-                expect(fs.readFileSync(xml, 'utf-8')).toMatch(/<preference name="UIWebViewBounce" value="false"/);
-            });
-            it('should allow removing all preference elements when no parameter is specified', function() {
-                cfg.preference.remove();
-                expect(fs.readFileSync(xml, 'utf-8')).not.toMatch(/<preference.*\/>/);
-            });
-        });
-    });
-});

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b50b5284/spec/cordova-cli/compile.spec.js
----------------------------------------------------------------------
diff --git a/spec/cordova-cli/compile.spec.js b/spec/cordova-cli/compile.spec.js
new file mode 100644
index 0000000..f13792a
--- /dev/null
+++ b/spec/cordova-cli/compile.spec.js
@@ -0,0 +1,179 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+var cordova = require('../cordova'),
+    et = require('elementtree'),
+    shell = require('shelljs'),
+    path = require('path'),
+    fs = require('fs'),
+    config_parser = require('../src/config_parser'),
+    android_parser = require('../src/metadata/android_parser'),
+    ios_parser = require('../src/metadata/ios_parser'),
+    blackberry_parser = require('../src/metadata/blackberry_parser'),
+    hooker = require('../src/hooker'),
+    fixtures = path.join(__dirname, 'fixtures'),
+    hooks = path.join(fixtures, 'hooks'),
+    tempDir = path.join(__dirname, '..', 'temp'),
+    cordova_project = path.join(fixtures, 'projects', 'cordova');
+
+var cwd = process.cwd();
+describe('compile command', function() {
+    beforeEach(function() {
+        shell.rm('-rf', tempDir);
+        shell.mkdir('-p', tempDir);
+    });
+
+    it('should not run inside a Cordova-based project with no added platforms', function() {
+        this.after(function() {
+            process.chdir(cwd);
+        });
+
+        cordova.create(tempDir);
+        process.chdir(tempDir);
+        expect(function() {
+            cordova.compile();
+        }).toThrow();
+    });
+    
+    it('should run inside a Cordova-based project with at least one added platform', function() {
+        // move platform project fixtures over to fake cordova into thinking platforms were added
+        // TODO: possibly add this to helper?
+        shell.mv('-f', path.join(cordova_project, 'platforms', 'blackberry'), path.join(tempDir));
+        shell.mv('-f', path.join(cordova_project, 'platforms', 'ios'), path.join(tempDir));
+        this.after(function() {
+            process.chdir(cwd);
+            shell.mv('-f', path.join(tempDir, 'blackberry'), path.join(cordova_project, 'platforms', 'blackberry'));
+            shell.mv('-f', path.join(tempDir, 'ios'), path.join(cordova_project, 'platforms', 'ios'));
+        });
+
+        process.chdir(cordova_project);
+
+        var sh_spy = spyOn(shell, 'exec');
+
+        expect(function() {
+            cordova.compile();
+            expect(sh_spy).toHaveBeenCalled();
+        }).not.toThrow();
+    });
+    it('should not run outside of a Cordova-based project', function() {
+        this.after(function() {
+            process.chdir(cwd);
+        });
+
+        shell.mkdir('-p', tempDir);
+        process.chdir(tempDir);
+
+        expect(function() {
+            cordova.compile();
+        }).toThrow();
+    });
+
+    describe('hooks', function() {
+        var s;
+        beforeEach(function() {
+            s = spyOn(hooker.prototype, 'fire').andReturn(true);
+        });
+
+        describe('when platforms are added', function() {
+            beforeEach(function() {
+                shell.mv('-f', path.join(cordova_project, 'platforms', 'blackberry'), path.join(tempDir));
+                shell.mv('-f', path.join(cordova_project, 'platforms', 'ios'), path.join(tempDir));
+                process.chdir(cordova_project);
+            });
+            afterEach(function() {
+                shell.mv('-f', path.join(tempDir, 'blackberry'), path.join(cordova_project, 'platforms', 'blackberry'));
+                shell.mv('-f', path.join(tempDir, 'ios'), path.join(cordova_project, 'platforms', 'ios'));
+                process.chdir(cwd);
+            });
+
+            it('should fire before hooks through the hooker module', function() {
+                spyOn(shell, 'exec');
+                cordova.compile();
+                expect(s).toHaveBeenCalledWith('before_compile');
+            });
+            it('should fire after hooks through the hooker module', function() {
+                var sh_spy = spyOn(shell, 'exec');
+                cordova.compile();
+                sh_spy.mostRecentCall.args[2](0); // shell cb
+                expect(s).toHaveBeenCalledWith('after_compile');
+            });
+        });
+
+        describe('with no platforms added', function() {
+            beforeEach(function() {
+                cordova.create(tempDir);
+                process.chdir(tempDir);
+            });
+            afterEach(function() {
+                process.chdir(cwd);
+            });
+            it('should not fire the hooker', function() {
+                expect(function() {
+                    cordova.compile();
+                }).toThrow();
+                expect(s).not.toHaveBeenCalledWith('before_compile');
+                expect(s).not.toHaveBeenCalledWith('after_compile');
+            });
+        });
+    });
+    describe('per platform', function() {
+        beforeEach(function() {
+            process.chdir(cordova_project);
+        });
+
+        afterEach(function() {
+            process.chdir(cwd);
+        });
+       
+        describe('Android', function() {
+            it('should shell out to build command on Android', function() {
+                var s = spyOn(require('shelljs'), 'exec').andReturn({code:0});
+                cordova.compile('android');
+                expect(s.mostRecentCall.args[0].match(/\/cordova\/build/)).not.toBeNull();
+            });
+        });
+        describe('iOS', function() {
+            it('should shell out to build command on iOS', function() {
+                var s = spyOn(require('shelljs'), 'exec');
+                cordova.compile('ios');
+                expect(s).toHaveBeenCalled();
+                expect(s.mostRecentCall.args[0].match(/\/cordova\/build/)).not.toBeNull();
+            });
+        });
+        describe('BlackBerry', function() {
+            it('should shell out to ant command on blackberry', function() {
+                var s = spyOn(shell, 'exec');
+                cordova.compile('blackberry');
+                expect(s).toHaveBeenCalled();
+                expect(s.mostRecentCall.args[0]).toMatch(/ant -f .*build\.xml" qnx load-device/);
+            });
+        });
+        it('should not treat a .gitignore file as a platform', function() {
+            var gitignore = path.join(cordova_project, 'platforms', '.gitignore');
+            fs.writeFileSync(gitignore, 'somethinghere', 'utf-8');
+            this.after(function() {
+                shell.rm('-f', gitignore);
+            });
+            var s = spyOn(shell, 'exec');
+            cordova.compile();
+            expect(s.calls[0].args[0]).not.toMatch(/\.gitignore/);
+            expect(s.calls[1].args[0]).not.toMatch(/\.gitignore/);
+            expect(s.calls[1].args[0]).not.toMatch(/\.gitignore/);
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b50b5284/spec/cordova-cli/config_parser.spec.js
----------------------------------------------------------------------
diff --git a/spec/cordova-cli/config_parser.spec.js b/spec/cordova-cli/config_parser.spec.js
new file mode 100644
index 0000000..99bc717
--- /dev/null
+++ b/spec/cordova-cli/config_parser.spec.js
@@ -0,0 +1,167 @@
+
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+var cordova = require('../cordova'),
+    path = require('path'),
+    fs = require('fs'),
+    shell = require('shelljs'),
+    config_parser = require('../src/config_parser'),
+    tempDir = path.join(__dirname, '..', 'temp'),
+    et = require('elementtree'),
+    xml = path.join(tempDir, 'www', 'config.xml');
+
+
+describe('config.xml parser', function () {
+    beforeEach(function() {
+        shell.rm('-rf', tempDir);
+        cordova.create(tempDir);
+    });
+
+    it('should create an instance based on an xml file', function() {
+        var cfg;
+        expect(function () {
+            cfg = new config_parser(xml);
+        }).not.toThrow();
+        expect(cfg).toBeDefined();
+        expect(cfg.doc).toBeDefined();
+    });
+
+    describe('package name / id', function() {
+        var cfg;
+
+        beforeEach(function() {
+            cfg = new config_parser(xml);
+        });
+
+        it('should get the (default) packagename', function() {
+            expect(cfg.packageName()).toEqual('io.cordova.hellocordova');
+        });
+        it('should allow setting the packagename', function() {
+            cfg.packageName('this.is.bat.country');
+            expect(cfg.packageName()).toEqual('this.is.bat.country');
+        });
+        it('should write to disk after setting the packagename', function() {
+            cfg.packageName('this.is.bat.country');
+            expect(fs.readFileSync(xml, 'utf-8')).toMatch(/id="this\.is\.bat\.country"/);
+        });
+    });
+
+    describe('app name', function() {
+        var cfg;
+
+        beforeEach(function() {
+            cfg = new config_parser(xml);
+        });
+
+        it('should get the (default) app name', function() {
+            expect(cfg.name()).toEqual('HelloCordova');
+        });
+        it('should allow setting the app name', function() {
+            cfg.name('this.is.bat.country');
+            expect(cfg.name()).toEqual('this.is.bat.country');
+        });
+        it('should write to disk after setting the name', function() {
+            cfg.name('one toke over the line');
+            expect(fs.readFileSync(xml, 'utf-8')).toMatch(/<name>one toke over the line<\/name>/);
+        });
+    });
+
+    describe('access elements (whitelist)', function() {
+        var cfg;
+
+        beforeEach(function() {
+            cfg = new config_parser(xml);
+        });
+
+        describe('getter', function() {
+            it('should get the (default) access element', function() {
+                expect(cfg.access.get()[0]).toEqual('*');
+            });
+            it('should return an array of all access origin uris via access()', function() {
+                expect(cfg.access.get() instanceof Array).toBe(true);
+            });
+        });
+        describe('setters', function() {
+            it('should allow removing a uri from the access list', function() {
+                cfg.access.remove('*');
+                expect(cfg.access.get().length).toEqual(0);
+            });
+            it('should write to disk after removing a uri', function() {
+                cfg.access.remove('*');
+                expect(fs.readFileSync(xml, 'utf-8')).not.toMatch(/<access.*\/>/);
+            });
+            it('should allow adding a new uri to the access list', function() {
+                cfg.access.add('http://canucks.com');
+                expect(cfg.access.get().length).toEqual(2);
+                expect(cfg.access.get().indexOf('http://canucks.com') > -1).toBe(true);
+            });
+            it('should write to disk after adding a uri', function() {
+                cfg.access.add('http://cordova.io');
+                expect(fs.readFileSync(xml, 'utf-8')).toMatch(/<access origin="http:\/\/cordova\.io/);
+            });
+            it('should allow removing all access elements when no parameter is specified', function() {
+                cfg.access.add('http://cordova.io');
+                cfg.access.remove();
+
+                expect(fs.readFileSync(xml, 'utf-8')).not.toMatch(/<access.*\/>/);
+            });
+        });
+    });
+
+    describe('preference elements', function() {
+        var cfg;
+
+        beforeEach(function() {
+            cfg = new config_parser(xml);
+        });
+
+        describe('getter', function() {
+            it('should get all preference elements', function() {
+                expect(cfg.preference.get()[0].name).toEqual('phonegap-version');
+                expect(cfg.preference.get()[0].value).toEqual('1.9.0');
+            });
+            it('should return an array of all preference name/value pairs', function() {
+                expect(cfg.preference.get() instanceof Array).toBe(true);
+            });
+        });
+        describe('setters', function() {
+            it('should allow removing a preference by name', function() {
+                cfg.preference.remove('phonegap-version');
+                expect(cfg.preference.get().length).toEqual(3);
+            });
+            it('should write to disk after removing a preference', function() {
+                cfg.preference.remove('phonegap-version');
+                expect(fs.readFileSync(xml, 'utf-8')).not.toMatch(/<preference\sname="phonegap-version"/);
+            });
+            it('should allow adding a new preference', function() {
+                cfg.preference.add({name:'UIWebViewBounce',value:'false'});
+                expect(cfg.preference.get().length).toEqual(5);
+                expect(cfg.preference.get()[4].value).toEqual('false');
+            });
+            it('should write to disk after adding a preference', function() {
+                cfg.preference.add({name:'UIWebViewBounce',value:'false'});
+                expect(fs.readFileSync(xml, 'utf-8')).toMatch(/<preference name="UIWebViewBounce" value="false"/);
+            });
+            it('should allow removing all preference elements when no parameter is specified', function() {
+                cfg.preference.remove();
+                expect(fs.readFileSync(xml, 'utf-8')).not.toMatch(/<preference.*\/>/);
+            });
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b50b5284/spec/cordova-cli/create.spec.js
----------------------------------------------------------------------
diff --git a/spec/cordova-cli/create.spec.js b/spec/cordova-cli/create.spec.js
new file mode 100644
index 0000000..fcb3e76
--- /dev/null
+++ b/spec/cordova-cli/create.spec.js
@@ -0,0 +1,68 @@
+var cordova = require('../cordova'),
+    path    = require('path'),
+    shell   = require('shelljs'),
+    fs      = require('fs'),
+    tempDir = path.join(__dirname, '..', 'temp');
+
+describe('create command', function () {
+    beforeEach(function() {
+        shell.rm('-rf', tempDir);
+    });
+
+    it('should print out help txt if no directory is provided', function() {
+        expect(cordova.create()).toMatch(/synopsis/i);
+    });
+    it('should create a cordova project in the specified directory if parameter is provided', function() {
+        cordova.create(tempDir);
+        var dotc = path.join(tempDir, '.cordova', 'config.json');
+        expect(fs.lstatSync(dotc).isFile()).toBe(true);
+        expect(JSON.parse(fs.readFileSync(dotc, 'utf8')).name).toBe("HelloCordova");
+        var hooks = path.join(tempDir, '.cordova', 'hooks');
+        expect(fs.existsSync(hooks)).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'before_platform_add'))).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'before_prepare'))).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'before_compile'))).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'after_platform_add'))).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'before_platform_rm'))).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'after_platform_rm'))).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'before_platform_ls'))).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'after_platform_ls'))).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'before_plugin_add'))).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'after_plugin_add'))).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'before_plugin_rm'))).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'after_plugin_rm'))).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'before_plugin_ls'))).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'after_plugin_ls'))).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'after_prepare'))).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'after_compile'))).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'before_build'))).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'after_build'))).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'before_emulate'))).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'after_emulate'))).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'before_docs'))).toBe(true);
+        expect(fs.existsSync(path.join(hooks, 'after_docs'))).toBe(true);
+    });
+    it('should throw if the directory is already a cordova project', function() {
+        shell.mkdir('-p', path.join(tempDir, '.cordova'));
+        
+        expect(function() {
+            cordova.create(tempDir);
+        }).toThrow();
+    });
+    it('should create a cordova project in the specified dir with specified name if provided', function() {
+        cordova.create(tempDir, "balls");
+
+        expect(fs.lstatSync(path.join(tempDir, '.cordova', 'config.json')).isFile()).toBe(true);
+
+        expect(fs.readFileSync(path.join(tempDir, 'www', 'config.xml')).toString('utf8')).toMatch(/<name>balls<\/name>/);
+    });
+    it('should create a cordova project in the specified dir with specified name and id if provided', function() {
+        cordova.create(tempDir, "birdy.nam.nam", "numnum");
+
+        expect(fs.lstatSync(path.join(tempDir, '.cordova', 'config.json')).isFile()).toBe(true);
+
+        var config = fs.readFileSync(path.join(tempDir, 'www', 'config.xml')).toString('utf8');
+        expect(config).toMatch(/<name>numnum<\/name>/);
+        expect(config).toMatch(/id="birdy\.nam\.nam"/);
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b50b5284/spec/cordova-cli/emulate.spec.js
----------------------------------------------------------------------
diff --git a/spec/cordova-cli/emulate.spec.js b/spec/cordova-cli/emulate.spec.js
new file mode 100644
index 0000000..3a4d1a4
--- /dev/null
+++ b/spec/cordova-cli/emulate.spec.js
@@ -0,0 +1,191 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+var cordova = require('../cordova'),
+    et = require('elementtree'),
+    shell = require('shelljs'),
+    path = require('path'),
+    fs = require('fs'),
+    config_parser = require('../src/config_parser'),
+    android_parser = require('../src/metadata/android_parser'),
+    ios_parser = require('../src/metadata/ios_parser'),
+    blackberry_parser = require('../src/metadata/blackberry_parser'),
+    hooker = require('../src/hooker'),
+    fixtures = path.join(__dirname, 'fixtures'),
+    hooks = path.join(fixtures, 'hooks'),
+    tempDir = path.join(__dirname, '..', 'temp'),
+    cordova_project = path.join(fixtures, 'projects', 'cordova');
+
+var cwd = process.cwd();
+
+describe('emulate command', function() {
+    beforeEach(function() {
+        shell.rm('-rf', tempDir);
+        shell.mkdir('-p', tempDir);
+    });
+
+    it('should not run inside a Cordova-based project with no added platforms', function() {
+        this.after(function() {
+            process.chdir(cwd);
+        });
+
+        cordova.create(tempDir);
+        process.chdir(tempDir);
+        expect(function() {
+            cordova.emulate();
+        }).toThrow();
+    });
+    
+    it('should run inside a Cordova-based project with at least one added platform', function() {
+        shell.mv('-f', path.join(cordova_project, 'platforms', 'blackberry'), path.join(tempDir));
+        shell.mv('-f', path.join(cordova_project, 'platforms', 'ios'), path.join(tempDir));
+        this.after(function() {
+            process.chdir(cwd);
+            shell.mv('-f', path.join(tempDir, 'blackberry'), path.join(cordova_project, 'platforms', 'blackberry'));
+            shell.mv('-f', path.join(tempDir, 'ios'), path.join(cordova_project, 'platforms', 'ios'));
+        });
+
+        process.chdir(cordova_project);
+
+        var s = spyOn(shell, 'exec');
+        var a_spy = spyOn(android_parser.prototype, 'update_project');
+        expect(function() {
+            cordova.emulate();
+            a_spy.mostRecentCall.args[1](); // fake out android parser
+            expect(s).toHaveBeenCalled();
+        }).not.toThrow();
+    });
+    it('should not run outside of a Cordova-based project', function() {
+        this.after(function() {
+            process.chdir(cwd);
+        });
+
+        shell.mkdir('-p', tempDir);
+        process.chdir(tempDir);
+
+        expect(function() {
+            cordova.emulate();
+        }).toThrow();
+    });
+    describe('per platform', function() {
+        beforeEach(function() {
+            process.chdir(cordova_project);
+        });
+
+        afterEach(function() {
+            process.chdir(cwd);
+        });
+       
+        describe('Android', function() {
+            var s;
+            beforeEach(function() {
+                s = spyOn(require('shelljs'), 'exec');
+            });
+            it('should shell out to run command on Android', function() {
+                cordova.emulate('android');
+                expect(s.mostRecentCall.args[0].match(/\/cordova\/run/)).not.toBeNull();
+            });
+            it('should call android_parser\'s update_project', function() {
+                var spy = spyOn(android_parser.prototype, 'update_project');
+                cordova.emulate('android');
+                expect(spy).toHaveBeenCalled();
+            });
+        });
+        describe('iOS', function() {
+            it('should shell out to emulate command on iOS', function() {
+                var s = spyOn(require('shelljs'), 'exec');
+                var proj_spy = spyOn(ios_parser.prototype, 'update_project');
+                cordova.emulate('ios');
+                proj_spy.mostRecentCall.args[1]();
+                expect(s).toHaveBeenCalled();
+                expect(s.mostRecentCall.args[0].match(/\/cordova\/emulate/)).not.toBeNull();
+            });
+            it('should call ios_parser\'s update_project', function() {
+                var s = spyOn(ios_parser.prototype, 'update_project');
+                cordova.emulate('ios');
+                expect(s).toHaveBeenCalled();
+            });
+        });
+        describe('BlackBerry', function() {
+            it('should shell out to ant command on blackberry', function() {
+                var proj_spy = spyOn(blackberry_parser.prototype, 'update_project');
+                var s = spyOn(require('shelljs'), 'exec');
+                cordova.emulate('blackberry');
+                proj_spy.mostRecentCall.args[1](); // update_project fake
+                expect(s).toHaveBeenCalled();
+                expect(s.mostRecentCall.args[0]).toMatch(/ant -f .*build\.xml" qnx load-simulator/);
+            });
+            it('should call blackberry_parser\'s update_project', function() {
+                var s = spyOn(blackberry_parser.prototype, 'update_project');
+                cordova.emulate('blackberry');
+                expect(s).toHaveBeenCalled();
+            });
+        });
+    });
+
+    describe('hooks', function() {
+        var s, sh, ap;
+        beforeEach(function() {
+            s = spyOn(hooker.prototype, 'fire').andReturn(true);
+        });
+
+        describe('when platforms are added', function() {
+            beforeEach(function() {
+                shell.mv('-f', path.join(cordova_project, 'platforms', 'blackberry'), path.join(tempDir));
+                shell.mv('-f', path.join(cordova_project, 'platforms', 'ios'), path.join(tempDir));
+                sh = spyOn(shell, 'exec');
+                ap = spyOn(android_parser.prototype, 'update_project');
+                process.chdir(cordova_project);
+            });
+            afterEach(function() {
+                shell.mv('-f', path.join(tempDir, 'blackberry'), path.join(cordova_project, 'platforms', 'blackberry'));
+                shell.mv('-f', path.join(tempDir, 'ios'), path.join(cordova_project, 'platforms', 'ios'));
+                process.chdir(cwd);
+            });
+
+            it('should fire before hooks through the hooker module', function() {
+                cordova.emulate();
+                expect(s).toHaveBeenCalledWith('before_emulate');
+            });
+            it('should fire after hooks through the hooker module', function() {
+                cordova.emulate();
+                ap.mostRecentCall.args[1](); // fake parser call
+                sh.mostRecentCall.args[2](0); //fake shell call
+                expect(s).toHaveBeenCalledWith('after_emulate');
+            });
+        });
+
+        describe('with no platforms added', function() {
+            beforeEach(function() {
+                cordova.create(tempDir);
+                process.chdir(tempDir);
+            });
+            afterEach(function() {
+                process.chdir(cwd);
+            });
+            it('should not fire the hooker', function() {
+                spyOn(shell, 'exec');
+                expect(function() {
+                    cordova.emulate();
+                }).toThrow();
+                expect(s).not.toHaveBeenCalledWith('before_emulate');
+                expect(s).not.toHaveBeenCalledWith('after_emulate');
+            });
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b50b5284/spec/cordova-cli/helper.js
----------------------------------------------------------------------
diff --git a/spec/cordova-cli/helper.js b/spec/cordova-cli/helper.js
new file mode 100644
index 0000000..2c4f331
--- /dev/null
+++ b/spec/cordova-cli/helper.js
@@ -0,0 +1,20 @@
+
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b50b5284/spec/cordova-cli/hooker.spec.js
----------------------------------------------------------------------
diff --git a/spec/cordova-cli/hooker.spec.js b/spec/cordova-cli/hooker.spec.js
new file mode 100644
index 0000000..4a0ca7d
--- /dev/null
+++ b/spec/cordova-cli/hooker.spec.js
@@ -0,0 +1,137 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+var hooker = require('../src/hooker'),
+    shell  = require('shelljs'),
+    path   = require('path'),
+    fs     = require('fs'),
+    tempDir= path.join(__dirname, '..', 'temp'),
+    hooks  = path.join(__dirname, 'fixtures', 'hooks'),
+    cordova= require('../cordova');
+
+var cwd = process.cwd();
+
+describe('hooker', function() {
+    it('should throw if provided directory is not a cordova project', function() {
+        shell.rm('-rf', tempDir);
+        shell.mkdir('-p', tempDir); 
+        this.after(function() {
+            shell.rm('-rf', tempDir);
+        });
+
+        expect(function() {
+            var h = new hooker(tempDir);
+        }).toThrow();
+    });
+    it('should not throw if provided directory is a cordova project', function() {
+        cordova.create(tempDir);
+        this.after(function() {
+            shell.rm('-rf', tempDir);
+        });
+
+        expect(function() {
+            var h = new hooker(tempDir);
+        }).not.toThrow();
+    });
+
+    describe('fire method', function() {
+        var h;
+
+        beforeEach(function() {
+            cordova.create(tempDir);
+            h = new hooker(tempDir);
+        });
+        afterEach(function() {
+            shell.rm('-rf', tempDir);
+        });
+
+        describe('failure', function() {
+            it('should not throw if the hook is unrecognized', function() {
+                expect(function() {
+                    h.fire('CLEAN YOUR SHORTS GODDAMNIT LIKE A BIG BOY!');
+                }).not.toThrow();
+            });
+            it('should throw if any script exits with non-zero code', function() {
+                var script = path.join(tempDir, '.cordova', 'hooks', 'before_build', 'fail.sh');
+                shell.cp(path.join(hooks, 'fail', 'fail.sh'), script);
+                fs.chmodSync(script, '754');
+                expect(function() {
+                    h.fire('before_build');
+                }).toThrow();
+            });
+        });
+
+        describe('success', function() {
+            it('should execute all scripts in order and return true', function() {
+                var hook = path.join(tempDir, '.cordova', 'hooks', 'before_build');
+                shell.cp(path.join(hooks, 'test', '*'), path.join(hook, '.'));
+                fs.readdirSync(hook).forEach(function(script) {
+                    fs.chmodSync(path.join(hook, script), '754');
+                });
+                var returnValue;
+                var s = spyOn(shell, 'exec').andReturn({code:0});
+                expect(function() {
+                    returnValue = h.fire('before_build');
+                }).not.toThrow();
+                expect(returnValue).toBe(true);
+                expect(s.calls[0].args[0]).toMatch(/0.sh/);
+                expect(s.calls[1].args[0]).toMatch(/1.sh/);
+            });
+            it('should pass the project root folder as parameter into the project-level hooks', function() {
+                var hook = path.join(tempDir, '.cordova', 'hooks', 'before_build');
+                shell.cp(path.join(hooks, 'test', '0.sh'), path.join(hook, '.'));
+                fs.readdirSync(hook).forEach(function(script) {
+                    fs.chmodSync(path.join(hook, script), '754');
+                });
+                var returnValue;
+                var s = spyOn(shell, 'exec').andReturn({code:0});
+                expect(function() {
+                    returnValue = h.fire('before_build');
+                }).not.toThrow();
+                expect(returnValue).toBe(true);
+                var paramRegex = new RegExp('0.sh "'+tempDir+'"$');
+                expect(s.calls[0].args[0]).toMatch(paramRegex);
+            });
+            describe('module-level hooks', function() {
+                var handler = jasmine.createSpy();
+                var test_event = 'before_build';
+                afterEach(function() {
+                    cordova.off(test_event, handler);
+                    handler.reset();
+                });
+
+                it('should fire handlers using cordova.on', function() {
+                    cordova.on(test_event, handler);
+                    h.fire(test_event);
+                    expect(handler).toHaveBeenCalled();
+                });
+                it('should pass the project root folder as parameter into the module-level handlers', function() {
+                    cordova.on(test_event, handler);
+                    h.fire('before_build');
+                    expect(handler).toHaveBeenCalledWith(tempDir);
+                });
+                it('should be able to stop listening to events using cordova.off', function() {
+                    cordova.on(test_event, handler);
+                    cordova.off(test_event, handler);
+                    h.fire('before_build');
+                    expect(handler).not.toHaveBeenCalled();
+                });
+            });
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b50b5284/spec/cordova-cli/platform.spec.js
----------------------------------------------------------------------
diff --git a/spec/cordova-cli/platform.spec.js b/spec/cordova-cli/platform.spec.js
new file mode 100644
index 0000000..7138c7e
--- /dev/null
+++ b/spec/cordova-cli/platform.spec.js
@@ -0,0 +1,339 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+var cordova = require('../cordova'),
+    path = require('path'),
+    shell = require('shelljs'),
+    request = require('request'),
+    fs = require('fs'),
+    et = require('elementtree'),
+    config_parser = require('../src/config_parser'),
+    helper = require('./helper'),
+    util = require('../src/util'),
+    hooker = require('../src/hooker'),
+    platforms = require('../platforms'),
+    tempDir = path.join(__dirname, '..', 'temp');
+    android_parser = require('../src/metadata/android_parser'),
+    ios_parser = require('../src/metadata/ios_parser'),
+    cordova_project = path.join(__dirname, 'fixtures', 'projects', 'cordova'),
+    blackberry_parser = require('../src/metadata/blackberry_parser');
+
+var cwd = process.cwd();
+
+describe('platform command', function() {
+    beforeEach(function() {
+        // Make a temp directory
+        shell.rm('-rf', tempDir);
+        shell.mkdir('-p', tempDir);
+    });
+    it('should run inside a Cordova-based project', function() {
+        this.after(function() {
+            process.chdir(cwd);
+        });
+
+        cordova.create(tempDir);
+
+        process.chdir(tempDir);
+
+        expect(function() {
+            cordova.platform();
+        }).not.toThrow();
+    });
+    it('should not run outside of a Cordova-based project', function() {
+        this.after(function() {
+            process.chdir(cwd);
+        });
+
+        process.chdir(tempDir);
+
+        expect(function() {
+            cordova.platform();
+        }).toThrow();
+    });
+
+    describe('`ls`', function() { 
+        beforeEach(function() {
+            process.chdir(cordova_project);
+        });
+
+        afterEach(function() {
+            process.chdir(cwd);
+        });
+
+        it('should list out no platforms for a fresh project', function() {
+            shell.mv('-f', path.join(cordova_project, 'platforms', '*'), tempDir);
+            this.after(function() {
+                shell.mv('-f', path.join(tempDir, '*'), path.join(cordova_project, 'platforms'));
+            });
+            expect(cordova.platform('list').length).toEqual(0);
+        });
+
+        it('should list out added platforms in a project', function() {
+            expect(cordova.platform('list').length).toEqual(3);
+        });
+    });
+
+    describe('`add`', function() {
+        beforeEach(function() {
+            cordova.create(tempDir);
+            process.chdir(tempDir);
+        });
+
+        afterEach(function() {
+            process.chdir(cwd);
+        });
+
+        describe('android', function() {
+            var sh, cr;
+            var fake_reqs_check = function() {
+                cr.mostRecentCall.args[0](false);
+            };
+            var fake_create = function(a_path) {
+                shell.mkdir('-p', a_path);
+                fs.writeFileSync(path.join(a_path, 'AndroidManifest.xml'), 'hi', 'utf-8');
+                sh.mostRecentCall.args[2](0, '');
+            };
+            beforeEach(function() {
+                sh = spyOn(shell, 'exec');
+                cr = spyOn(android_parser, 'check_requirements');
+            });
+
+            it('should shell out to android ./bin/create', function() {
+                cordova.platform('add', 'android');
+                fake_reqs_check();
+                var shell_cmd = sh.mostRecentCall.args[0];
+                expect(shell_cmd).toMatch(/android\/bin\/create/);
+            });
+            it('should call android_parser\'s update_project', function() {
+                var s = spyOn(android_parser.prototype, 'update_project');
+                cordova.platform('add', 'android');
+                fake_reqs_check();
+                fake_create(path.join(tempDir, 'platforms', 'android'));
+                expect(s).toHaveBeenCalled();
+            });
+        });
+        describe('ios', function() {
+            var sh, cr;
+            var fake_reqs_check = function() {
+                cr.mostRecentCall.args[0](false);
+            };
+            var fake_create = function(a_path) {
+                shell.mkdir('-p', a_path);
+                fs.writeFileSync(path.join(a_path, 'poo.xcodeproj'), 'hi', 'utf-8');
+                shell.mkdir('-p', path.join(a_path, 'poo'));
+                shell.cp(path.join(cordova_project, 'www', 'config.xml'), path.join(a_path, 'poo', 'config.xml'));
+                sh.mostRecentCall.args[2](0, '');
+            };
+            beforeEach(function() {
+                sh = spyOn(shell, 'exec');
+                cr = spyOn(ios_parser, 'check_requirements');
+            });
+            it('should shell out to ios ./bin/create', function() {
+                cordova.platform('add', 'ios');
+                fake_reqs_check();
+                var shell_cmd = sh.mostRecentCall.args[0];
+                expect(shell_cmd).toMatch(/ios\/bin\/create/);
+            });
+            it('should call ios_parser\'s update_project', function() {
+                var s = spyOn(ios_parser.prototype, 'update_project');
+                cordova.platform('add', 'ios');
+                fake_reqs_check();
+                fake_create(path.join(tempDir, 'platforms', 'ios'));
+                expect(s).toHaveBeenCalled();
+            });
+        });
+        describe('blackberry', function() {
+            var sh, cr;
+            var fake_reqs_check = function() {
+                cr.mostRecentCall.args[0](false);
+            };
+            var fake_create = function(a_path) {
+                shell.mkdir('-p', path.join(a_path, 'www'));
+                fs.writeFileSync(path.join(a_path, 'project.properties'), 'hi', 'utf-8');
+                fs.writeFileSync(path.join(a_path, 'build.xml'), 'hi', 'utf-8');
+                shell.cp(path.join(cordova_project, 'www', 'config.xml'), path.join(a_path, 'www', 'config.xml'));
+                sh.mostRecentCall.args[2](0, '');
+            };
+            beforeEach(function() {
+                sh = spyOn(shell, 'exec');
+                cr = spyOn(blackberry_parser, 'check_requirements');
+            });
+            it('should shell out to blackberry bin/create', function() {
+                cordova.platform('add', 'blackberry');
+                fake_reqs_check();
+                var shell_cmd = sh.mostRecentCall.args[0];
+                expect(shell_cmd).toMatch(/blackberry\/bin\/create/);
+            });
+            it('should call blackberry_parser\'s update_project', function() {
+                var s = spyOn(blackberry_parser.prototype, 'update_project');
+                cordova.platform('add', 'blackberry');
+                fake_reqs_check();
+                fake_create(path.join(tempDir, 'platforms', 'blackberry'));
+                expect(s).toHaveBeenCalled();
+            });
+        });
+        it('should handle multiple platforms', function() {
+            var arc = spyOn(android_parser, 'check_requirements');
+            var irc = spyOn(ios_parser, 'check_requirements');
+            var sh = spyOn(shell, 'exec');
+            cordova.platform('add', ['android', 'ios']);
+            arc.mostRecentCall.args[0](false);
+            irc.mostRecentCall.args[0](false);
+            expect(sh.argsForCall[0][0]).toMatch(/android\/bin\/create/);
+            expect(sh.argsForCall[1][0]).toMatch(/ios\/bin\/create/);
+        });
+    });
+
+    describe('`remove`',function() { 
+        beforeEach(function() {
+            process.chdir(cordova_project);
+            shell.cp('-rf', path.join(cordova_project, 'platforms' ,'*'), tempDir);
+        });
+
+        afterEach(function() {
+            process.chdir(cwd);
+            shell.cp('-rf', path.join(tempDir, '*'), path.join(cordova_project, 'platforms')); 
+        });
+
+        it('should remove a supported and added platform', function() {
+            cordova.platform('remove', 'android');
+            expect(cordova.platform('ls').length).toEqual(2);
+        });
+        it('should be able to remove multiple platforms', function() {
+            cordova.platform('remove', ['android','ios']);
+            expect(cordova.platform('ls').length).toEqual(1);
+        });
+    });
+
+    describe('hooks', function() {
+        var s;
+        beforeEach(function() {
+            cordova.create(tempDir);
+            process.chdir(tempDir);
+            s = spyOn(hooker.prototype, 'fire').andReturn(true);
+        });
+        afterEach(function() {
+            process.chdir(cwd);
+            shell.rm('-rf', tempDir);
+        });
+
+        describe('list (ls) hooks', function() {
+            it('should fire before hooks through the hooker module', function() {
+                cordova.platform();
+                expect(s).toHaveBeenCalledWith('before_platform_ls');
+            });
+            it('should fire after hooks through the hooker module', function() {
+                cordova.platform();
+                expect(s).toHaveBeenCalledWith('after_platform_ls');
+            });
+        });
+        describe('remove (rm) hooks', function() {
+            it('should fire before hooks through the hooker module', function() {
+                cordova.platform('rm', 'android');
+                expect(s).toHaveBeenCalledWith('before_platform_rm');
+            });
+            it('should fire after hooks through the hooker module', function() {
+                cordova.platform('rm', 'android');
+                expect(s).toHaveBeenCalledWith('after_platform_rm');
+            });
+        });
+        describe('add hooks', function() {
+            var sh, cr;
+            var fake_reqs_check = function() {
+                cr.mostRecentCall.args[0](false);
+            };
+            var fake_create = function(a_path) {
+                shell.mkdir('-p', a_path);
+                fs.writeFileSync(path.join(a_path, 'AndroidManifest.xml'), 'hi', 'utf-8');
+                sh.mostRecentCall.args[2](0, '');
+            };
+            beforeEach(function() {
+                sh = spyOn(shell, 'exec');
+                cr = spyOn(android_parser, 'check_requirements');
+            });
+            it('should fire before and after hooks through the hooker module', function() {
+                var ap = spyOn(android_parser.prototype, 'update_project');
+                cordova.platform('add', 'android');
+                fake_reqs_check();
+                fake_create(path.join(tempDir, 'platforms', 'android'));
+                ap.mostRecentCall.args[1](); // fake out update_project
+                expect(s).toHaveBeenCalledWith('before_platform_add');
+                expect(s).toHaveBeenCalledWith('after_platform_add');
+            });
+        });
+    });
+});
+
+describe('platform.supports(name, callback)', function() {
+    var androidParser = require('../src/metadata/android_parser');
+
+    beforeEach(function() {
+        spyOn(androidParser, 'check_requirements');
+    });
+
+    it('should require a platform name', function() {
+        expect(function() {
+            cordova.platform.supports(undefined, function(e){});
+        }).toThrow();
+    });
+
+    it('should require a callback function', function() {
+        expect(function() {
+            cordova.platform.supports('android', undefined);
+        }).toThrow();
+    });
+
+    describe('when platform is unknown', function() {
+        it('should trigger callback with false', function(done) {
+            cordova.platform.supports('windows-3.1', function(e) {
+                expect(e).toEqual(jasmine.any(Error));
+                done();
+            });
+        });
+    });
+
+    describe('when platform is supported', function() {
+        beforeEach(function() {
+            androidParser.check_requirements.andCallFake(function(callback) {
+                callback(null);
+            });
+        });
+
+        it('should trigger callback without error', function(done) {
+            cordova.platform.supports('android', function(e) {
+                expect(e).toBeNull();
+                done();
+            });
+        });
+    });
+
+    describe('when platform is unsupported', function() {
+        beforeEach(function() {
+            androidParser.check_requirements.andCallFake(function(callback) {
+                callback(new Error('could not find the android sdk'));
+            });
+        });
+
+        it('should trigger callback with error', function(done) {
+            cordova.platform.supports('android', function(e) {
+                expect(e).toEqual(jasmine.any(Error));
+                done();
+            });
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b50b5284/spec/cordova-cli/plugin.spec.js
----------------------------------------------------------------------
diff --git a/spec/cordova-cli/plugin.spec.js b/spec/cordova-cli/plugin.spec.js
new file mode 100644
index 0000000..3d370e7
--- /dev/null
+++ b/spec/cordova-cli/plugin.spec.js
@@ -0,0 +1,164 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+var cordova = require('../cordova'),
+    path = require('path'),
+    shell = require('shelljs'),
+    fs = require('fs'),
+    hooker = require('../src/hooker'),
+    tempDir = path.join(__dirname, '..', 'temp'),
+    fixturesDir = path.join(__dirname, 'fixtures'),
+    testPlugin = path.join(fixturesDir, 'plugins', 'test'),
+    cordova_project = path.join(fixturesDir, 'projects', 'cordova'),
+    androidPlugin = path.join(fixturesDir, 'plugins', 'android');
+
+var cwd = process.cwd();
+
+describe('plugin command', function() {
+    beforeEach(function() {
+        // Make a temp directory
+        shell.rm('-rf', tempDir);
+        shell.mkdir('-p', tempDir);
+    });
+
+    it('should run inside a Cordova-based project', function() {
+        this.after(function() {
+            process.chdir(cwd);
+        });
+
+        cordova.create(tempDir);
+
+        process.chdir(tempDir);
+
+        expect(function() {
+            cordova.plugin();
+        }).not.toThrow();
+    });
+    it('should not run outside of a Cordova-based project', function() {
+        this.after(function() {
+            process.chdir(cwd);
+        });
+
+        process.chdir(tempDir);
+
+        expect(function() {
+            cordova.plugin();
+        }).toThrow();
+    });
+
+    describe('edge cases', function() {
+       beforeEach(function() {
+           cordova.create(tempDir);
+           process.chdir(tempDir);
+       });
+
+       afterEach(function() {
+           process.chdir(cwd);
+       });
+
+       it('should not fail when the plugins directory is missing', function() {
+           fs.rmdirSync('plugins');
+
+           expect(function() {
+               cordova.plugin();
+           }).not.toThrow();
+       });
+
+       it('should ignore files, like .gitignore, in the plugins directory', function() {
+           var someFile = path.join(tempDir, 'plugins', '.gitignore');
+           fs.writeFileSync(someFile, 'not a plugin');
+
+           expect(cordova.plugin('list')).toEqual('No plugins added. Use `cordova plugin add <plugin>`.');
+       });
+    });
+
+    describe('`ls`', function() {
+        beforeEach(function() {
+            cordova.create(tempDir);
+        });
+
+        afterEach(function() {
+            process.chdir(cwd);
+        });
+
+        it('should list out no plugins for a fresh project', function() {
+            process.chdir(tempDir);
+
+            expect(cordova.plugin('list')).toEqual('No plugins added. Use `cordova plugin add <plugin>`.');
+        });
+    });
+
+    describe('`add`', function() {
+        beforeEach(function() {
+            cordova.create(tempDir);
+            process.chdir(tempDir);
+        });
+
+        afterEach(function() {
+            process.chdir(cwd);
+        });
+        describe('failure', function() {
+            it('should throw if your app has no platforms added', function() {
+                expect(function() {
+                    cordova.plugin('add', testPlugin);
+                }).toThrow('You need at least one platform added to your app. Use `cordova platform add <platform>`.');
+            });
+            it('should throw if plugin does not support any app platforms', function() {
+                process.chdir(cordova_project);
+                shell.mv('-f', path.join(cordova_project, 'platforms', 'android'), tempDir);
+                shell.mv('-f', path.join(cordova_project, 'platforms', 'blackberry'), tempDir);
+                this.after(function() {
+                    process.chdir(cwd);
+                    shell.mv('-f', path.join(tempDir, 'android'), path.join(cordova_project, 'platforms'));
+                    shell.mv('-f', path.join(tempDir, 'blackberry'), path.join(cordova_project, 'platforms'));
+                });
+                expect(function() {
+                    cordova.plugin('add', androidPlugin);
+                }).toThrow('Plugin "android" does not support any of your application\'s platforms. Plugin platforms: android; your application\'s platforms: ios');
+            });
+            it('should throw if plugin is already added to project', function() {
+                process.chdir(cordova_project);
+                var cb = jasmine.createSpy();
+                this.after(function() {
+                    process.chdir(cordova_project);
+                    cordova.plugin('rm', "test");
+                    process.chdir(cwd);
+                });
+                runs(function() {
+                    cordova.plugin('add', testPlugin, cb);
+                });
+                waitsFor(function() { return cb.wasCalled; }, 'frst add plugin');
+                runs(function(){
+                    expect(function() {
+                        cordova.plugin('add', testPlugin);
+                    }).toThrow('Plugin "test" already added to project.');
+                });
+            });
+            it('should throw if plugin does not have a plugin.xml', function() {
+                process.chdir(cordova_project);
+                this.after(function() {
+                    process.chdir(cwd);
+                });
+                expect(function() {
+                    cordova.plugin('add', fixturesDir);
+                }).toThrow('Plugin "fixtures" does not have a plugin.xml in the root. Plugin must support the Cordova Plugin Specification: https://github.com/alunny/cordova-plugin-spec');
+            });
+        });
+    });
+});
+

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b50b5284/spec/cordova-cli/plugin_parser.spec.js
----------------------------------------------------------------------
diff --git a/spec/cordova-cli/plugin_parser.spec.js b/spec/cordova-cli/plugin_parser.spec.js
new file mode 100644
index 0000000..4391003
--- /dev/null
+++ b/spec/cordova-cli/plugin_parser.spec.js
@@ -0,0 +1,42 @@
+
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+var cordova = require('../cordova'),
+    path = require('path'),
+    fs = require('fs'),
+    plugin_parser = require('../src/plugin_parser'),
+    et = require('elementtree'),
+    xml = path.join(__dirname, '..', 'fixtures', 'plugins', 'test', 'plugin.xml');
+
+describe('plugin.xml parser', function () {
+    it('should read a proper plugin.xml file', function() {
+        var cfg;
+        expect(function () {
+            cfg = new plugin_parser(xml);
+        }).not.toThrow();
+        expect(cfg).toBeDefined();
+        expect(cfg.doc).toBeDefined();
+    });
+    it('should be able to figure out which platforms the plugin supports', function() {
+        var cfg = new plugin_parser(xml);
+        expect(cfg.platforms.length).toBe(1);
+        expect(cfg.platforms.indexOf('ios') > -1).toBe(true);
+    });
+});
+

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b50b5284/spec/cordova-cli/prepare.spec.js
----------------------------------------------------------------------
diff --git a/spec/cordova-cli/prepare.spec.js b/spec/cordova-cli/prepare.spec.js
new file mode 100644
index 0000000..f232b3e
--- /dev/null
+++ b/spec/cordova-cli/prepare.spec.js
@@ -0,0 +1,133 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+var cordova = require('../cordova'),
+    et = require('elementtree'),
+    shell = require('shelljs'),
+    path = require('path'),
+    fs = require('fs'),
+    config_parser = require('../src/config_parser'),
+    android_parser = require('../src/metadata/android_parser'),
+    ios_parser = require('../src/metadata/ios_parser'),
+    blackberry_parser = require('../src/metadata/blackberry_parser'),
+    hooker = require('../src/hooker'),
+    fixtures = path.join(__dirname, 'fixtures'),
+    hooks = path.join(fixtures, 'hooks'),
+    tempDir = path.join(__dirname, '..', 'temp'),
+    cordova_project = path.join(fixtures, 'projects', 'cordova');
+
+var cwd = process.cwd();
+
+describe('prepare command', function() {
+    beforeEach(function() {
+        shell.rm('-rf', tempDir);
+        shell.mkdir('-p', tempDir);
+    });
+
+    it('should not run inside a Cordova-based project with no added platforms', function() {
+        this.after(function() {
+            process.chdir(cwd);
+        });
+
+        cordova.create(tempDir);
+        process.chdir(tempDir);
+        expect(function() {
+            cordova.prepare();
+        }).toThrow();
+    });
+    
+    it('should run inside a Cordova-based project with at least one added platform', function() {
+        // move platform project fixtures over to fake cordova into thinking platforms were added
+        // TODO: possibly add this to helper?
+        shell.mv('-f', path.join(cordova_project, 'platforms', 'blackberry'), path.join(tempDir));
+        this.after(function() {
+            process.chdir(cwd);
+            shell.mv('-f', path.join(tempDir, 'blackberry'), path.join(cordova_project, 'platforms', 'blackberry'));
+        });
+
+        process.chdir(cordova_project);
+
+        var a_parser_spy = spyOn(android_parser.prototype, 'update_project');
+        var i_parser_spy = spyOn(ios_parser.prototype, 'update_project');
+        expect(function() {
+            cordova.prepare();
+            expect(a_parser_spy).toHaveBeenCalled();
+            expect(i_parser_spy).toHaveBeenCalled();
+        }).not.toThrow();
+    });
+    it('should not run outside of a Cordova-based project', function() {
+        this.after(function() {
+            process.chdir(cwd);
+        });
+
+        shell.mkdir('-p', tempDir);
+        process.chdir(tempDir);
+
+        expect(function() {
+            cordova.prepare();
+        }).toThrow();
+    });
+
+    describe('hooks', function() {
+        var s;
+        beforeEach(function() {
+            s = spyOn(hooker.prototype, 'fire').andReturn(true);
+        });
+
+        describe('when platforms are added', function() {
+            beforeEach(function() {
+                shell.mv('-f', path.join(cordova_project, 'platforms', 'blackberry'), path.join(tempDir));
+                shell.mv('-f', path.join(cordova_project, 'platforms', 'ios'), path.join(tempDir));
+                process.chdir(cordova_project);
+            });
+            afterEach(function() {
+                shell.mv('-f', path.join(tempDir, 'blackberry'), path.join(cordova_project, 'platforms', 'blackberry'));
+                shell.mv('-f', path.join(tempDir, 'ios'), path.join(cordova_project, 'platforms', 'ios'));
+                process.chdir(cwd);
+            });
+
+            it('should fire before hooks through the hooker module', function() {
+                cordova.prepare();
+                expect(s).toHaveBeenCalledWith('before_prepare');
+            });
+            it('should fire after hooks through the hooker module', function() {
+                var parser_spy = spyOn(android_parser.prototype, 'update_project');
+                cordova.prepare();
+                parser_spy.mostRecentCall.args[1](); // parser cb
+                expect(s).toHaveBeenCalledWith('after_prepare');
+            });
+        });
+
+        describe('with no platforms added', function() {
+            beforeEach(function() {
+                cordova.create(tempDir);
+                process.chdir(tempDir);
+            });
+            afterEach(function() {
+                process.chdir(cwd);
+            });
+            it('should not fire the hooker', function() {
+                expect(function() {
+                    cordova.prepare();
+                }).toThrow();
+                expect(s).not.toHaveBeenCalledWith('before_prepare');
+                expect(s).not.toHaveBeenCalledWith('after_prepare');
+            });
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/b50b5284/spec/cordova-cli/serve.spec.js
----------------------------------------------------------------------
diff --git a/spec/cordova-cli/serve.spec.js b/spec/cordova-cli/serve.spec.js
new file mode 100644
index 0000000..8d00cab
--- /dev/null
+++ b/spec/cordova-cli/serve.spec.js
@@ -0,0 +1,132 @@
+
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+var cordova = require('../cordova'),
+    path = require('path'),
+    shell = require('shelljs'),
+    request = require('request'),
+    fs = require('fs'),
+    util = require('../src/util'),
+    hooker = require('../src/hooker'),
+    tempDir = path.join(__dirname, '..', 'temp'),
+    http = require('http'),
+    android_parser = require('../src/metadata/android_parser'),
+    ios_parser = require('../src/metadata/ios_parser'),
+    blackberry_parser = require('../src/metadata/blackberry_parser');
+
+var cwd = process.cwd();
+
+xdescribe('serve command', function() {
+    beforeEach(function() {
+        // Make a temp directory
+        shell.rm('-rf', tempDir);
+        shell.mkdir('-p', tempDir);
+    });
+    it('should not run outside of a Cordova-based project', function() {
+        this.after(function() {
+            process.chdir(cwd);
+        });
+
+        process.chdir(tempDir);
+
+        expect(function() {
+            cordova.serve('android');
+        }).toThrow();
+    });
+
+
+    describe('`serve`', function() {
+        var payloads = {
+            android: 'This is the Android test file.',
+            ios: 'This is the iOS test file.'
+        };
+
+        beforeEach(function() {
+            cordova.create(tempDir);
+            process.chdir(tempDir);
+            cordova.platform('add', 'android');
+            cordova.platform('add', 'ios');
+
+            // Write testing HTML files into the directory.
+            fs.writeFileSync(path.join(tempDir, 'platforms', 'android', 'assets', 'www', 'test.html'), payloads.android);
+            fs.writeFileSync(path.join(tempDir, 'platforms', 'ios', 'www', 'test.html'), payloads.ios);
+        });
+
+        afterEach(function() {
+            process.chdir(cwd);
+        });
+
+        function test_serve(platform, path, expectedContents, port) {
+            return function() {
+                var ret;
+                runs(function() {
+                    ret = port ? cordova.serve(platform, port) : cordova.serve(platform);
+                });
+
+                waitsFor(function() {
+                    return ret.server;
+                }, 'the server should start', 1000);
+
+                var done, errorCB;
+                runs(function() {
+                    expect(ret.server).toBeDefined();
+                    errorCB = jasmine.createSpy();
+                    http.get({
+                        host: 'localhost',
+                        port: port || 8000,
+                        path: path
+                    }).on('response', function(res) {
+                        var response = '';
+                        res.on('data', function(data) {
+                            response += data;
+                        });
+                        res.on('end', function() {
+                            expect(res.statusCode).toEqual(200);
+                            expect(response).toEqual(expectedContents);
+                            done = true;
+                        });
+                    }).on('error', errorCB);
+                });
+
+                waitsFor(function() {
+                    return done;
+                }, 'the HTTP request should complete', 1000);
+
+                runs(function() {
+                    expect(done).toBeTruthy();
+                    expect(errorCB).not.toHaveBeenCalled();
+
+                    ret.server.close();
+                });
+            };
+        };
+
+        it('should serve from top-level www if the file exists there', function() {
+            var payload = 'This is test file.';
+            fs.writeFileSync(path.join(tempDir, 'www', 'test.html'), payload);
+            test_serve('android', '/test.html', payload)();
+        });
+
+        it('should fall back to assets/www on Android', test_serve('android', '/test.html', payloads.android));
+        it('should fall back to www on iOS', test_serve('ios', '/test.html', payloads.ios));
+
+        it('should honour a custom port setting', test_serve('android', '/test.html', payloads.android, 9001));
+    });
+});
+