You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by fi...@apache.org on 2017/06/22 17:34:32 UTC

[01/14] cordova-lib git commit: the existing plugin specs are all e2e tests... moving them to relevant directory. plugin e2e test changes to account for dir move

Repository: cordova-lib
Updated Branches:
  refs/heads/master a7f55ecc9 -> 1a2892956


the existing plugin specs are all e2e tests... moving them to relevant directory. plugin e2e test changes to account for dir move


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

Branch: refs/heads/master
Commit: 080653f992e82e0d2defdf4c4900431c13931a80
Parents: a7f55ec
Author: filmaj <ma...@gmail.com>
Authored: Mon Jun 19 17:18:17 2017 -0500
Committer: filmaj <ma...@gmail.com>
Committed: Wed Jun 21 14:57:33 2017 -0500

----------------------------------------------------------------------
 integration-tests/plugin.spec.js | 297 ++++++++++++++++++++++++++++++++++
 spec-cordova/plugin.spec.js      | 296 ---------------------------------
 2 files changed, 297 insertions(+), 296 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/080653f9/integration-tests/plugin.spec.js
----------------------------------------------------------------------
diff --git a/integration-tests/plugin.spec.js b/integration-tests/plugin.spec.js
new file mode 100644
index 0000000..8572362
--- /dev/null
+++ b/integration-tests/plugin.spec.js
@@ -0,0 +1,297 @@
+/**
+    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 helpers = require('./helpers'),
+    path = require('path'),
+    Q = require('q'),
+    shell = require('shelljs'),
+    events = require('cordova-common').events,
+    cordova = require('../src/cordova/cordova'),
+    platforms = require('../src/platforms/platforms'),
+    plugman = require('../src/plugman/plugman'),
+    install = require('../src/plugman/install'),
+    registry = require('../src/plugman/registry/registry');
+
+var util = require('../src/cordova/util');
+
+var tmpDir = helpers.tmpDir('plugin_test');
+var project = path.join(tmpDir, 'project');
+var fixturesDir = path.join(__dirname, '..', 'spec-cordova', 'fixtures');
+var pluginsDir = path.join(fixturesDir, 'plugins');
+
+var pluginId = 'org.apache.cordova.fakeplugin1';
+var org_test_defaultvariables = 'org.test.defaultvariables';
+
+// This plugin is published to npm and defines cordovaDependencies
+// in its package.json. Based on the dependencies and the version of
+// cordova-android installed in our test project, the CLI should
+// select version 1.1.2 of the plugin. We don't actually fetch from
+// npm, but we do check the npm info.
+var npmInfoTestPlugin = 'cordova-lib-test-plugin';
+var npmInfoTestPluginVersion = '1.1.2';
+
+var testGitPluginRepository = 'https://github.com/apache/cordova-plugin-device.git';
+var testGitPluginId = 'cordova-plugin-device';
+
+var results;
+
+// Runs: list, add, list
+function addPlugin(target, id, options) {
+    // Check there are no plugins yet.
+    return cordova.plugin('list').then(function() {
+        expect(results).toMatch(/No plugins added/gi);
+    }).then(function() {
+        // Add a fake plugin from fixtures.
+        return cordova.plugin('add', target, options);
+    }).then(function() {
+        expect(path.join(project, 'plugins', id, 'plugin.xml')).toExist();
+    }).then(function() {
+        return cordova.plugin('ls');
+    }).then(function() {
+        expect(results).toContain(id);
+    });
+}
+
+// Runs: remove, list
+function removePlugin(id) {
+    return cordova.plugin('rm', id)
+    .then(function() {
+        // The whole dir should be gone.
+        expect(path.join(project, 'plugins', id)).not.toExist();
+    }).then(function() {
+        return cordova.plugin('ls');
+    }).then(function() {
+        expect(results).toMatch(/No plugins added/gi);
+    });
+}
+
+var errorHandler = {
+    errorCallback: function(error) {
+        // We want the error to be printed by jasmine
+        expect(error).toBeUndefined();
+    }
+};
+
+// We can't call add with a searchpath or else we will conflict with other tests
+// that use a searchpath. See loadLocalPlugins() in plugman/fetch.js for details.
+// The searchpath behavior gets tested in the plugman spec
+function mockPluginFetch(id, dir) {
+    spyOn(plugman, 'fetch').and.callFake(function(target, pluginPath, fetchOptions) {
+        var dest = path.join(project, 'plugins', id);
+        var src = path.join(dir, 'plugin.xml');
+
+        shell.mkdir(dest);
+        shell.cp(src, dest);
+        return Q(dest);
+    });
+}
+
+describe('plugin end-to-end', function() {
+    events.on('results', function(res) { results = res; });
+
+    beforeEach(function() {
+        shell.rm('-rf', project);
+
+        // cp then mv because we need to copy everything, but that means it'll copy the whole directory.
+        // Using /* doesn't work because of hidden files.
+        shell.cp('-R', path.join(fixturesDir, 'base'), tmpDir);
+        shell.mv(path.join(tmpDir, 'base'), project);
+        // Copy some platform to avoid working on a project with no platforms.
+        shell.cp('-R', path.join(__dirname, '..', 'spec-plugman', 'projects', helpers.testPlatform), path.join(project, 'platforms'));
+        process.chdir(project);
+
+        // Reset origCwd before each spec to respect chdirs
+        util._resetOrigCwd();
+        delete process.env.PWD;
+
+        spyOn(platforms, 'getPlatformApi').and.callThrough();
+        spyOn(install, 'runInstall').and.callThrough();
+        spyOn(errorHandler, 'errorCallback').and.callThrough();
+    });
+
+    afterEach(function() {
+        process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on Windows.
+        shell.rm('-rf', tmpDir);
+        expect(errorHandler.errorCallback).not.toHaveBeenCalled();
+    });
+
+    it('Test 001 : should successfully add and remove a plugin with no options', function(done) {
+        addPlugin(path.join(pluginsDir, 'fake1'), pluginId, {}, done)
+        .then(function() {
+            expect(install.runInstall).toHaveBeenCalled();
+            expect(platforms.getPlatformApi.calls.count()).toEqual(1);
+            return removePlugin(pluginId);
+        }).then(function() {
+            expect(platforms.getPlatformApi.calls.count()).toEqual(2);
+        }).fail(function(err) {
+            expect(err).toBeUndefined();
+        })
+        .fin(done);
+    }, 30000);
+
+    it('Test 004 : should successfully add a plugin using relative path when running from subdir inside of project', function(done) {
+        // Copy plugin to subdir inside of the project. This is required since path.relative
+        // returns an absolute path when source and dest are on different drives
+        var plugindir = path.join(project, 'custom-plugins/some-plugin-inside-subfolder');
+        shell.mkdir('-p', plugindir);
+        shell.cp('-r', path.join(pluginsDir, 'fake1/*'), plugindir);
+
+        // Create a subdir, where we're going to run cordova from
+        var subdir = path.join(project, 'bin');
+        shell.mkdir('-p', subdir);
+        shell.cd(subdir);
+
+        // Add plugin using relative path
+        addPlugin(path.relative(subdir, plugindir), pluginId, {}, done)
+        .then(function() {
+            return removePlugin(pluginId);
+        })
+        .fail(function(err) {
+            expect(err).toBeUndefined();
+        })
+        .fin(done);
+    }, 30000);
+
+    it('Test 005 : should respect preference default values', function (done) {    
+       addPlugin(path.join(pluginsDir, org_test_defaultvariables), org_test_defaultvariables, {cli_variables: { REQUIRED:'NO', REQUIRED_ANDROID:'NO'}}, done)
+       .then(function() {
+            var platformJsonPath = path.join(project, 'plugins', helpers.testPlatform + '.json');
+            var installed_plugins = require(platformJsonPath).installed_plugins;
+            var defaultPluginPreferences = installed_plugins[org_test_defaultvariables];
+            expect(defaultPluginPreferences).toBeDefined();
+            expect(defaultPluginPreferences.DEFAULT).toBe('yes');
+            expect(defaultPluginPreferences.DEFAULT_ANDROID).toBe('yes');
+            expect(defaultPluginPreferences.REQUIRED_ANDROID).toBe('NO');
+            expect(defaultPluginPreferences.REQUIRED).toBe('NO');
+            return removePlugin(org_test_defaultvariables);
+       })
+        .fail(function(err) {
+            expect(err).toBeUndefined();
+        })
+        .fin(done);
+    }, 30000);
+
+    it('Test 006 : should successfully add a plugin when specifying CLI variables', function(done) {
+        addPlugin(path.join(pluginsDir, org_test_defaultvariables), org_test_defaultvariables, {cli_variables: { REQUIRED:'yes', REQUIRED_ANDROID:'yes'}}, done)
+        .fail(function(err) {
+            expect(err).toBeUndefined();
+        })
+        .fin(done);
+    }, 30000);
+
+    it('Test 007 : should not check npm info when using the searchpath flag', function(done) {
+        mockPluginFetch(npmInfoTestPlugin, path.join(pluginsDir, npmInfoTestPlugin));
+        spyOn(registry, 'info');
+        return addPlugin(npmInfoTestPlugin, npmInfoTestPlugin, {searchpath: pluginsDir}, done)
+        .then(function() {
+            expect(registry.info).not.toHaveBeenCalled();
+            var fetchOptions = plugman.fetch.calls.mostRecent().args[2];
+            expect(fetchOptions.searchpath[0]).toExist();
+        })
+        .fail(function(err) {
+            expect(err).toBeUndefined();
+        })
+        .fin(done);
+    }, 30000);
+
+    it('Test 008 : should not check npm info when using the noregistry flag', function(done) {
+        mockPluginFetch(npmInfoTestPlugin, path.join(pluginsDir, npmInfoTestPlugin));
+
+        spyOn(registry, 'info');
+        addPlugin(npmInfoTestPlugin, npmInfoTestPlugin, {noregistry:true}, done)
+        .then(function() {
+            expect(registry.info).not.toHaveBeenCalled();
+
+            var fetchOptions = plugman.fetch.calls.mostRecent().args[2];
+            expect(fetchOptions.noregistry).toBeTruthy();
+        })
+        .fail(function(err) {
+            expect(err).toBeUndefined();
+        })
+        .fin(done);
+    }, 30000);
+
+    it('Test 009 : should not check npm info when fetching from a Git repository', function(done) {
+        spyOn(registry, 'info');
+        addPlugin(testGitPluginRepository, testGitPluginId, {}, done)
+        .then(function() {
+            expect(registry.info).not.toHaveBeenCalled();
+        })
+        .fail(function(err) {
+            expect(err).toBeUndefined();
+        })
+        .fin(done);
+    }, 30000);
+
+    it('Test 010 : should select the plugin version based on npm info when fetching from npm', function(done) {
+        mockPluginFetch(npmInfoTestPlugin, path.join(pluginsDir, npmInfoTestPlugin));
+
+        spyOn(registry, 'info').and.callThrough();
+        addPlugin(npmInfoTestPlugin, npmInfoTestPlugin, {'fetch':true}, done)
+        .then(function() {
+            expect(registry.info).toHaveBeenCalled();
+
+            var fetchTarget = plugman.fetch.calls.mostRecent().args[0];
+            expect(fetchTarget).toEqual(npmInfoTestPlugin + '@' + npmInfoTestPluginVersion);
+        })
+        .fail(function(err) {
+            expect(err).toBeUndefined();
+        })
+        .fin(done);
+    }, 30000);
+
+    it('Test 011 : should handle scoped npm packages', function(done) {
+        var scopedPackage = '@testscope/' + npmInfoTestPlugin;
+        mockPluginFetch(npmInfoTestPlugin, path.join(pluginsDir, npmInfoTestPlugin));
+
+        spyOn(registry, 'info').and.returnValue(Q({}));
+        addPlugin(scopedPackage, npmInfoTestPlugin, {}, done)
+        .then(function() {
+            // Check to make sure that we are at least trying to get the correct package.
+            // This package is not published to npm, so we can't truly do end-to-end tests
+
+            expect(registry.info).toHaveBeenCalledWith([scopedPackage]);
+
+            var fetchTarget = plugman.fetch.calls.mostRecent().args[0];
+            expect(fetchTarget).toEqual(scopedPackage);
+        })
+        .fail(function(err) {
+            expect(err).toBeUndefined();
+        })
+        .fin(done);
+    }, 30000);
+
+    it('Test 012 : should handle scoped npm packages with given version tags', function(done) {
+        var scopedPackage = '@testscope/' + npmInfoTestPlugin + '@latest';
+        mockPluginFetch(npmInfoTestPlugin, path.join(pluginsDir, npmInfoTestPlugin));
+
+        spyOn(registry, 'info');
+        addPlugin(scopedPackage, npmInfoTestPlugin, {}, done)
+        .then(function() {
+            expect(registry.info).not.toHaveBeenCalled();
+
+            var fetchTarget = plugman.fetch.calls.mostRecent().args[0];
+            expect(fetchTarget).toEqual(scopedPackage);
+        })
+        .fail(function(err) {
+            expect(err).toBeUndefined();
+        })
+        .fin(done);
+    }, 30000);
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/080653f9/spec-cordova/plugin.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/plugin.spec.js b/spec-cordova/plugin.spec.js
deleted file mode 100644
index 35fef78..0000000
--- a/spec-cordova/plugin.spec.js
+++ /dev/null
@@ -1,296 +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 helpers = require('./helpers'),
-    path = require('path'),
-    Q = require('q'),
-    shell = require('shelljs'),
-    events = require('cordova-common').events,
-    cordova = require('../src/cordova/cordova'),
-    platforms = require('../src/platforms/platforms'),
-    plugman = require('../src/plugman/plugman'),
-    install = require('../src/plugman/install'),
-    registry = require('../src/plugman/registry/registry');
-
-var util = require('../src/cordova/util');
-
-var tmpDir = helpers.tmpDir('plugin_test');
-var project = path.join(tmpDir, 'project');
-var pluginsDir = path.join(__dirname, 'fixtures', 'plugins');
-
-var pluginId = 'org.apache.cordova.fakeplugin1';
-var org_test_defaultvariables = 'org.test.defaultvariables';
-
-// This plugin is published to npm and defines cordovaDependencies
-// in its package.json. Based on the dependencies and the version of
-// cordova-android installed in our test project, the CLI should
-// select version 1.1.2 of the plugin. We don't actually fetch from
-// npm, but we do check the npm info.
-var npmInfoTestPlugin = 'cordova-lib-test-plugin';
-var npmInfoTestPluginVersion = '1.1.2';
-
-var testGitPluginRepository = 'https://github.com/apache/cordova-plugin-device.git';
-var testGitPluginId = 'cordova-plugin-device';
-
-var results;
-
-// Runs: list, add, list
-function addPlugin(target, id, options) {
-    // Check there are no plugins yet.
-    return cordova.plugin('list').then(function() {
-        expect(results).toMatch(/No plugins added/gi);
-    }).then(function() {
-        // Add a fake plugin from fixtures.
-        return cordova.plugin('add', target, options);
-    }).then(function() {
-        expect(path.join(project, 'plugins', id, 'plugin.xml')).toExist();
-    }).then(function() {
-        return cordova.plugin('ls');
-    }).then(function() {
-        expect(results).toContain(id);
-    });
-}
-
-// Runs: remove, list
-function removePlugin(id) {
-    return cordova.plugin('rm', id)
-    .then(function() {
-        // The whole dir should be gone.
-        expect(path.join(project, 'plugins', id)).not.toExist();
-    }).then(function() {
-        return cordova.plugin('ls');
-    }).then(function() {
-        expect(results).toMatch(/No plugins added/gi);
-    });
-}
-
-var errorHandler = {
-    errorCallback: function(error) {
-        // We want the error to be printed by jasmine
-        expect(error).toBeUndefined();
-    }
-};
-
-// We can't call add with a searchpath or else we will conflict with other tests
-// that use a searchpath. See loadLocalPlugins() in plugman/fetch.js for details.
-// The searchpath behavior gets tested in the plugman spec
-function mockPluginFetch(id, dir) {
-    spyOn(plugman, 'fetch').and.callFake(function(target, pluginPath, fetchOptions) {
-        var dest = path.join(project, 'plugins', id);
-        var src = path.join(dir, 'plugin.xml');
-
-        shell.mkdir(dest);
-        shell.cp(src, dest);
-        return Q(dest);
-    });
-}
-
-describe('plugin end-to-end', function() {
-    events.on('results', function(res) { results = res; });
-
-    beforeEach(function() {
-        shell.rm('-rf', project);
-
-        // cp then mv because we need to copy everything, but that means it'll copy the whole directory.
-        // Using /* doesn't work because of hidden files.
-        shell.cp('-R', path.join(__dirname, 'fixtures', 'base'), tmpDir);
-        shell.mv(path.join(tmpDir, 'base'), project);
-        // Copy some platform to avoid working on a project with no platforms.
-        shell.cp('-R', path.join(__dirname, '..', 'spec-plugman', 'projects', helpers.testPlatform), path.join(project, 'platforms'));
-        process.chdir(project);
-
-        // Reset origCwd before each spec to respect chdirs
-        util._resetOrigCwd();
-        delete process.env.PWD;
-
-        spyOn(platforms, 'getPlatformApi').and.callThrough();
-        spyOn(install, 'runInstall').and.callThrough();
-        spyOn(errorHandler, 'errorCallback').and.callThrough();
-    });
-
-    afterEach(function() {
-        process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on Windows.
-        shell.rm('-rf', tmpDir);
-        expect(errorHandler.errorCallback).not.toHaveBeenCalled();
-    });
-
-    it('Test 001 : should successfully add and remove a plugin with no options', function(done) {
-        addPlugin(path.join(pluginsDir, 'fake1'), pluginId, {}, done)
-        .then(function() {
-            expect(install.runInstall).toHaveBeenCalled();
-            expect(platforms.getPlatformApi.calls.count()).toEqual(1);
-            return removePlugin(pluginId);
-        }).then(function() {
-            expect(platforms.getPlatformApi.calls.count()).toEqual(2);
-        }).fail(function(err) {
-            expect(err).toBeUndefined();
-        })
-        .fin(done);
-    }, 30000);
-
-    it('Test 004 : should successfully add a plugin using relative path when running from subdir inside of project', function(done) {
-        // Copy plugin to subdir inside of the project. This is required since path.relative
-        // returns an absolute path when source and dest are on different drives
-        var plugindir = path.join(project, 'custom-plugins/some-plugin-inside-subfolder');
-        shell.mkdir('-p', plugindir);
-        shell.cp('-r', path.join(pluginsDir, 'fake1/*'), plugindir);
-
-        // Create a subdir, where we're going to run cordova from
-        var subdir = path.join(project, 'bin');
-        shell.mkdir('-p', subdir);
-        shell.cd(subdir);
-
-        // Add plugin using relative path
-        addPlugin(path.relative(subdir, plugindir), pluginId, {}, done)
-        .then(function() {
-            return removePlugin(pluginId);
-        })
-        .fail(function(err) {
-            expect(err).toBeUndefined();
-        })
-        .fin(done);
-    }, 30000);
-
-    it('Test 005 : should respect preference default values', function (done) {    
-       addPlugin(path.join(pluginsDir, org_test_defaultvariables), org_test_defaultvariables, {cli_variables: { REQUIRED:'NO', REQUIRED_ANDROID:'NO'}}, done)
-       .then(function() {
-            var platformJsonPath = path.join(project, 'plugins', helpers.testPlatform + '.json');
-            var installed_plugins = require(platformJsonPath).installed_plugins;
-            var defaultPluginPreferences = installed_plugins[org_test_defaultvariables];
-            expect(defaultPluginPreferences).toBeDefined();
-            expect(defaultPluginPreferences.DEFAULT).toBe('yes');
-            expect(defaultPluginPreferences.DEFAULT_ANDROID).toBe('yes');
-            expect(defaultPluginPreferences.REQUIRED_ANDROID).toBe('NO');
-            expect(defaultPluginPreferences.REQUIRED).toBe('NO');
-            return removePlugin(org_test_defaultvariables);
-       })
-        .fail(function(err) {
-            expect(err).toBeUndefined();
-        })
-        .fin(done);
-    }, 30000);
-
-    it('Test 006 : should successfully add a plugin when specifying CLI variables', function(done) {
-        addPlugin(path.join(pluginsDir, org_test_defaultvariables), org_test_defaultvariables, {cli_variables: { REQUIRED:'yes', REQUIRED_ANDROID:'yes'}}, done)
-        .fail(function(err) {
-            expect(err).toBeUndefined();
-        })
-        .fin(done);
-    }, 30000);
-
-    it('Test 007 : should not check npm info when using the searchpath flag', function(done) {
-        mockPluginFetch(npmInfoTestPlugin, path.join(pluginsDir, npmInfoTestPlugin));
-        spyOn(registry, 'info');
-        return addPlugin(npmInfoTestPlugin, npmInfoTestPlugin, {searchpath: pluginsDir}, done)
-        .then(function() {
-            expect(registry.info).not.toHaveBeenCalled();
-            var fetchOptions = plugman.fetch.calls.mostRecent().args[2];
-            expect(fetchOptions.searchpath[0]).toExist();
-        })
-        .fail(function(err) {
-            expect(err).toBeUndefined();
-        })
-        .fin(done);
-    }, 30000);
-
-    it('Test 008 : should not check npm info when using the noregistry flag', function(done) {
-        mockPluginFetch(npmInfoTestPlugin, path.join(pluginsDir, npmInfoTestPlugin));
-
-        spyOn(registry, 'info');
-        addPlugin(npmInfoTestPlugin, npmInfoTestPlugin, {noregistry:true}, done)
-        .then(function() {
-            expect(registry.info).not.toHaveBeenCalled();
-
-            var fetchOptions = plugman.fetch.calls.mostRecent().args[2];
-            expect(fetchOptions.noregistry).toBeTruthy();
-        })
-        .fail(function(err) {
-            expect(err).toBeUndefined();
-        })
-        .fin(done);
-    }, 30000);
-
-    it('Test 009 : should not check npm info when fetching from a Git repository', function(done) {
-        spyOn(registry, 'info');
-        addPlugin(testGitPluginRepository, testGitPluginId, {}, done)
-        .then(function() {
-            expect(registry.info).not.toHaveBeenCalled();
-        })
-        .fail(function(err) {
-            expect(err).toBeUndefined();
-        })
-        .fin(done);
-    }, 30000);
-
-    it('Test 010 : should select the plugin version based on npm info when fetching from npm', function(done) {
-        mockPluginFetch(npmInfoTestPlugin, path.join(pluginsDir, npmInfoTestPlugin));
-
-        spyOn(registry, 'info').and.callThrough();
-        addPlugin(npmInfoTestPlugin, npmInfoTestPlugin, {'fetch':true}, done)
-        .then(function() {
-            expect(registry.info).toHaveBeenCalled();
-
-            var fetchTarget = plugman.fetch.calls.mostRecent().args[0];
-            expect(fetchTarget).toEqual(npmInfoTestPlugin + '@' + npmInfoTestPluginVersion);
-        })
-        .fail(function(err) {
-            expect(err).toBeUndefined();
-        })
-        .fin(done);
-    }, 30000);
-
-    it('Test 011 : should handle scoped npm packages', function(done) {
-        var scopedPackage = '@testscope/' + npmInfoTestPlugin;
-        mockPluginFetch(npmInfoTestPlugin, path.join(pluginsDir, npmInfoTestPlugin));
-
-        spyOn(registry, 'info').and.returnValue(Q({}));
-        addPlugin(scopedPackage, npmInfoTestPlugin, {}, done)
-        .then(function() {
-            // Check to make sure that we are at least trying to get the correct package.
-            // This package is not published to npm, so we can't truly do end-to-end tests
-
-            expect(registry.info).toHaveBeenCalledWith([scopedPackage]);
-
-            var fetchTarget = plugman.fetch.calls.mostRecent().args[0];
-            expect(fetchTarget).toEqual(scopedPackage);
-        })
-        .fail(function(err) {
-            expect(err).toBeUndefined();
-        })
-        .fin(done);
-    }, 30000);
-
-    it('Test 012 : should handle scoped npm packages with given version tags', function(done) {
-        var scopedPackage = '@testscope/' + npmInfoTestPlugin + '@latest';
-        mockPluginFetch(npmInfoTestPlugin, path.join(pluginsDir, npmInfoTestPlugin));
-
-        spyOn(registry, 'info');
-        addPlugin(scopedPackage, npmInfoTestPlugin, {}, done)
-        .then(function() {
-            expect(registry.info).not.toHaveBeenCalled();
-
-            var fetchTarget = plugman.fetch.calls.mostRecent().args[0];
-            expect(fetchTarget).toEqual(scopedPackage);
-        })
-        .fail(function(err) {
-            expect(err).toBeUndefined();
-        })
-        .fin(done);
-    }, 30000);
-});


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[07/14] cordova-lib git commit: plugin_fetch.spec.js, hooks runner and plugman fetch specs are all integration tests - moving to integration-tests dir

Posted by fi...@apache.org.
plugin_fetch.spec.js, hooks runner and plugman fetch specs are all integration tests - moving to integration-tests dir


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

Branch: refs/heads/master
Commit: 04c12e2bba820b9bf16ee77d4e60c209ab51ed77
Parents: da205ae
Author: filmaj <ma...@gmail.com>
Authored: Mon Jun 19 23:23:18 2017 -0500
Committer: filmaj <ma...@gmail.com>
Committed: Wed Jun 21 14:57:49 2017 -0500

----------------------------------------------------------------------
 integration-tests/HooksRunner.spec.js       | 683 +++++++++++++++++++++++
 integration-tests/plugin_fetch.spec.js      | 537 ++++++++++++++++++
 integration-tests/plugman_fetch.spec.js     | 340 +++++++++++
 integration-tests/plugman_uninstall.spec.js | 333 +++++++++++
 spec-cordova/HooksRunner.spec.js            | 683 -----------------------
 spec-cordova/plugin_fetch.spec.js           | 536 ------------------
 spec-plugman/fetch.spec.js                  | 339 -----------
 spec-plugman/uninstall.spec.js              | 333 -----------
 8 files changed, 1893 insertions(+), 1891 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/04c12e2b/integration-tests/HooksRunner.spec.js
----------------------------------------------------------------------
diff --git a/integration-tests/HooksRunner.spec.js b/integration-tests/HooksRunner.spec.js
new file mode 100644
index 0000000..61962ac
--- /dev/null
+++ b/integration-tests/HooksRunner.spec.js
@@ -0,0 +1,683 @@
+/**
+ 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.
+ **/
+
+/* jshint boss:true */
+
+var cordova = require('../src/cordova/cordova'),
+    HooksRunner = require('../src/hooks/HooksRunner'),
+    shell  = require('shelljs'),
+    path   = require('path'),
+    fs     = require('fs'),
+    os     = require('os'),
+    Q      = require('q'),
+    child_process = require('child_process'),
+    helpers = require('../spec-cordova/helpers'),
+    PluginInfo = require('cordova-common').PluginInfo,
+    superspawn = require('cordova-common').superspawn,
+    config = require('../src/cordova/config');
+
+var platform = os.platform();
+var tmpDir = helpers.tmpDir('hooks_test');
+var project = path.join(tmpDir, 'project');
+var dotCordova = path.join(project, '.cordova');
+var hooksDir = path.join(project, 'hooks');
+var hooksDirDot = path.join(project, '.cordova', 'hooks');
+var scriptsDir = path.join(project, 'scripts');
+var ext = platform.match(/(win32|win64)/)?'bat':'sh';
+var fixtures = path.join(__dirname, '..', 'spec-cordova', 'fixtures');
+var testPluginFixturePath = path.join(fixtures, 'plugins', 'com.plugin.withhooks');
+
+var cordovaUtil = require('../src/cordova/util');
+
+// copy fixture
+shell.rm('-rf', project);
+shell.mkdir('-p', project);
+shell.cp('-R', path.join(fixtures, 'projWithHooks', '*'), project);
+
+shell.mkdir('-p', dotCordova);
+shell.cp('-R', path.join(fixtures, 'projWithHooks', '.cordova'), project);
+
+// copy sh/bat scripts
+if(ext === 'bat') {
+    shell.cp('-R', path.join(fixtures, 'projWithHooks', '_bat', '*'), project);
+    shell.cp('-R', path.join(fixtures, 'projWithHooks', '_bat', '.cordova'), project);
+} else {
+    shell.cp('-R', path.join(fixtures, 'projWithHooks', '_sh', '*'), project);
+    shell.cp('-R', path.join(fixtures, 'projWithHooks', '_sh', '.cordova'), project);
+}
+
+shell.chmod('-R', 'ug+x', hooksDir);
+shell.chmod('-R', 'ug+x', hooksDirDot);
+shell.chmod('-R', 'ug+x', scriptsDir);
+
+// To get more verbose output to help trace program flow, uncomment the lines below
+// TODO: think about how to factor this out so that it can be invoked from the command line
+//var events = require('cordova-common').events;
+//events.on('log', function(msg) { console.log(msg); });
+//events.on('verbose', function(msg) { console.log(msg); });
+//events.on('warn', function(msg) { console.log(msg); });
+
+describe('HooksRunner', function() {
+    var hooksRunner;
+    var hookOptions;
+    var testPluginInstalledPath;
+    var projectRoot;
+    var fire;
+
+    beforeEach(function() {
+        process.chdir(project);
+    });
+
+    afterEach(function() {
+        process.chdir(path.join(__dirname, '..'));  // Non e2e tests assume CWD is repo root.
+    });
+
+    it('Test 001 : should throw if provided directory is not a cordova project', function() {
+        expect(function() {
+            new HooksRunner(tmpDir);
+        }).toThrow();
+    });
+
+    it('Test 002 : should not throw if provided directory is a cordova project', function() {
+        expect(function () {
+            new HooksRunner(project);
+        }).not.toThrow();
+    });
+
+    it('Test 003 : should init test fixtures', function(done) {
+        hooksRunner = new HooksRunner(project);
+
+        // Now we load the config.json in the newly created project and edit the target platform's lib entry
+        // to point at the fixture version. This is necessary so that cordova.prepare can find cordova.js there.
+        var c = config.read(project);
+        c.lib[helpers.testPlatform].url = path.join(fixtures, 'platforms', helpers.testPlatform + '-lib');
+        config.write(project, c);
+
+        // The config.json in the fixture project points at fake "local" paths.
+        // Since it's not a URL, the lazy-loader will just return the junk path.
+        spyOn(superspawn, 'spawn').and.callFake(function(cmd, args) {
+            if (cmd.match(/create\b/)) {
+                // This is a call to the bin/create script, so do the copy ourselves.
+                shell.cp('-R', path.join(fixtures, 'platforms', 'android'), path.join(project, 'platforms'));
+            } else if(cmd.match(/update\b/)) {
+                fs.writeFileSync(path.join(project, 'platforms', helpers.testPlatform, 'updated'), 'I was updated!', 'utf-8');
+            } else if (cmd.match(/version/)) {
+                return '3.6.0';
+            }
+            return Q();
+        });
+
+
+        // Add the testing platform.
+        cordova.platform('add', [helpers.testPlatform]).fail(function (err) {
+            expect(err).toBeUndefined();
+            console.error(err);
+            done();
+        }).then(function () {
+            // Add the testing plugin
+            projectRoot = cordovaUtil.isCordova();
+
+            var options = {
+                verbose: false,
+                platforms: [],
+                options: []
+            };
+
+            options = cordovaUtil.preProcessOptions(options);
+
+            hookOptions = { projectRoot: project, cordova: options };
+
+            cordova.plugin('add', testPluginFixturePath).fail(function (err) {
+                expect(err && err.stack).toBeUndefined();
+                done();
+            }).then(function () {
+                testPluginInstalledPath = path.join(projectRoot, 'plugins', 'com.plugin.withhooks');
+                shell.chmod('-R', 'ug+x', path.join(testPluginInstalledPath, 'scripts'));
+                done();
+            });
+        });
+    }, 100000);
+
+    describe('fire method', function() {
+        beforeEach(function() {
+            projectRoot = cordovaUtil.isCordova();
+
+            var options = {
+                verbose: false,
+                platforms: [],
+                options: []
+            };
+
+            options = cordovaUtil.preProcessOptions(options);
+            hookOptions = { projectRoot: project, cordova: options };
+
+            var hooksOrderFile = path.join(projectRoot, 'hooks_order.txt');
+            removeFileIfExists(hooksOrderFile);
+
+            fire = spyOn(HooksRunner.prototype, 'fire').and.callThrough();
+        });
+
+        // helper methods
+        function hooksOrderFileContents(hooksOrderFile) {
+            var order = fs.readFileSync(hooksOrderFile, 'ascii').replace(/\W/gm, ' ');
+
+            var orderArrOriginal = order.split(' ').slice(0);
+
+            function splitNumbers(mixedString) {
+                var re = /\w+/g, match, params = [];
+
+                while (match = re.exec(mixedString)) {
+                    params.push(match[0]);
+                }
+                return params;
+            }
+
+            return splitNumbers(orderArrOriginal).map(function(str) { return parseInt(str, 10); });
+        }
+
+        function hooksOrderFileIsOrdered(hooksOrderFile) {
+            var splitArrOriginal = hooksOrderFileContents(hooksOrderFile);
+            var splitArrSorted = splitArrOriginal.slice(0).sort(function(a, b) { return a - b; });
+
+            return JSON.stringify(splitArrOriginal) === JSON.stringify(splitArrSorted);
+        }
+
+        function backupAppConfig(projectRoot) {
+            shell.cp('-f', path.join(projectRoot, 'config.xml'), path.join(projectRoot, 'configOrig.xml'));
+        }
+
+        function restoreAppConfig(projectRoot) {
+            shell.cp('-f', path.join(projectRoot, 'configOrig.xml'), path.join(projectRoot, 'config.xml'));
+            shell.rm('-rf', path.join(projectRoot, 'configOrig.xml'));
+        }
+
+        function switchToOnlyNonPlatformScriptsAppConfig(projectRoot) {
+            backupAppConfig(projectRoot);
+            shell.cp('-f', path.join(projectRoot, 'configOnlyNonPlatformScripts_' + ext + '.xml'), path.join(projectRoot, 'config.xml'));
+        }
+
+        function switchToOnePlatformScriptsAppConfig(projectRoot) {
+            backupAppConfig(projectRoot);
+            shell.cp('-f', path.join(projectRoot, 'configOnePlatform_' + ext + '.xml'), path.join(projectRoot, 'config.xml'));
+        }
+
+        function switchToTwoPlatformsScriptsAppConfig(projectRoot) {
+            backupAppConfig(projectRoot);
+            shell.cp('-f', path.join(projectRoot, 'configTwoPlatforms_' + ext + '.xml'), path.join(projectRoot, 'config.xml'));
+        }
+
+        function backupPluginConfig() {
+            shell.cp('-f', path.join(testPluginInstalledPath, 'plugin.xml'), path.join(testPluginInstalledPath, 'pluginOrig.xml'));
+        }
+
+        function restorePluginConfig() {
+            shell.cp('-f', path.join(testPluginInstalledPath, 'pluginOrig.xml'), path.join(testPluginInstalledPath, 'plugin.xml'));
+            shell.rm('-rf', path.join(testPluginInstalledPath, 'pluginOrig.xml'));
+        }
+
+        function switchToOnlyNonPlatformScriptsPluginConfig(projectRoot) {
+            backupPluginConfig();
+            shell.cp('-f', path.join(testPluginInstalledPath, 'pluginOnlyNonPlatformScripts_' + ext + '.xml'), path.join(testPluginInstalledPath, 'plugin.xml'));
+        }
+
+        function switchToOnePlatformScriptsPluginConfig(projectRoot) {
+            backupPluginConfig();
+            shell.cp('-f', path.join(testPluginInstalledPath, 'pluginOnePlatform_' + ext + '.xml'), path.join(testPluginInstalledPath, 'plugin.xml'));
+        }
+
+        function switchToTwoPlatformsScriptsPluginConfig(projectRoot) {
+            backupPluginConfig();
+            shell.cp('-f', path.join(testPluginInstalledPath, 'pluginTwoPlatforms_' + ext + '.xml'), path.join(testPluginInstalledPath, 'plugin.xml'));
+        }
+
+        function removeFileIfExists(file) {
+            if (fs.existsSync(file)) {
+                fs.unlinkSync(file);
+            }
+        }
+
+        describe('application hooks', function() {
+            it('Test 004 : should execute hook scripts serially', function (done) {
+                var test_event = 'before_build';
+                var projectRoot = cordovaUtil.isCordova();
+                var hooksOrderFile = path.join(projectRoot, 'hooks_order.txt');
+
+                return hooksRunner.fire(test_event, hookOptions).then(function() {
+                    expect(hooksOrderFile).toExist();
+
+                    expect(hooksOrderFileIsOrdered(hooksOrderFile)).toBe(true);
+                }).fail(function (err) {
+                    expect(err).toBeUndefined();
+                }).then(function () {
+                    done();
+                });
+            });
+
+            it('Test 005 : should execute hook scripts serially from .cordova/hooks/hook_type and hooks/hook_type directories', function (done) {
+                var test_event = 'before_build';
+                var projectRoot = cordovaUtil.isCordova();
+                var hooksOrderFile = path.join(projectRoot, 'hooks_order.txt');
+
+                // using empty platforms list to test only hooks/ directories
+                hookOptions.cordova.platforms = [];
+
+                return hooksRunner.fire(test_event, hookOptions).then(function () {
+                    expect(hooksOrderFile).toExist();
+
+                    expect(hooksOrderFileIsOrdered(hooksOrderFile)).toBe(true);
+                }).fail(function (err) {
+                    expect(err).toBeUndefined();
+                }).then(function () {
+                    done();
+                });
+            });
+
+            it('Test 006 : should execute hook scripts serially from config.xml', function (done) {
+                var test_event = 'before_build';
+                var projectRoot = cordovaUtil.isCordova();
+                var hooksOrderFile = path.join(projectRoot, 'hooks_order.txt');
+
+                switchToOnlyNonPlatformScriptsAppConfig(projectRoot);
+
+                return hooksRunner.fire(test_event, hookOptions).then(function () {
+                    expect(hooksOrderFile).toExist();
+
+                    expect(hooksOrderFileIsOrdered(hooksOrderFile)).toBe(true);
+                }).fail(function (err) {
+                    expect(err).toBeUndefined();
+                }).then(function () {
+                    restoreAppConfig(projectRoot);
+                    done();
+                });
+            });
+
+            it('Test 007 : should execute hook scripts serially from config.xml including platform scripts', function (done) {
+                var test_event = 'before_build';
+                var projectRoot = cordovaUtil.isCordova();
+                var hooksOrderFile = path.join(projectRoot, 'hooks_order.txt');
+
+                switchToOnePlatformScriptsAppConfig(projectRoot);
+
+                return hooksRunner.fire(test_event, hookOptions).then(function () {
+                    expect(hooksOrderFile).toExist();
+
+                    expect(hooksOrderFileIsOrdered(hooksOrderFile)).toBe(true);
+                }).fail(function (err) {
+                    expect(err).toBeUndefined();
+                }).then(function () {
+                    restoreAppConfig(projectRoot);
+                    done();
+                });
+            });
+
+            it('Test 008 : should filter hook scripts from config.xml by platform', function (done) {
+                var test_event = 'before_build';
+                var projectRoot = cordovaUtil.isCordova();
+                var hooksOrderFile = path.join(projectRoot, 'hooks_order.txt');
+
+                switchToTwoPlatformsScriptsAppConfig(projectRoot);
+
+                hookOptions.cordova.platforms = ['android'];
+
+                return hooksRunner.fire(test_event, hookOptions).then(function () {
+                    expect(hooksOrderFile).toExist();
+
+                    expect(hooksOrderFileIsOrdered(hooksOrderFile)).toBe(true);
+
+                    var baseScriptResults = [1, 2, 3, 4, 5, 6, 7, 8, 9];
+                    var androidPlatformScriptsResults = [14, 15];
+
+                    expect(JSON.stringify(hooksOrderFileContents(hooksOrderFile)) ===
+                        JSON.stringify(baseScriptResults.slice(0).concat(androidPlatformScriptsResults))).toBe(true);
+                }).fail(function (err) {
+                    expect(err).toBeUndefined();
+                }).then(function () {
+                    restoreAppConfig(projectRoot);
+                    done();
+                });
+            });
+        });
+
+        describe('plugin hooks', function() {
+            it('Test 009 : should execute hook scripts serially from plugin.xml', function (done) {
+                var test_event = 'before_build';
+                var projectRoot = cordovaUtil.isCordova();
+                var hooksOrderFile = path.join(projectRoot, 'hooks_order.txt');
+
+                switchToOnlyNonPlatformScriptsPluginConfig();
+
+                return hooksRunner.fire(test_event, hookOptions).then(function () {
+                    expect(hooksOrderFile).toExist();
+
+                    expect(hooksOrderFileIsOrdered(hooksOrderFile)).toBe(true);
+                }).fail(function (err) {
+                    expect(err).toBeUndefined();
+                }).then(function () {
+                    restorePluginConfig(projectRoot);
+                    done();
+                });
+            });
+
+            it('Test 010 : should execute hook scripts serially from plugin.xml including platform scripts', function (done) {
+                var test_event = 'before_build';
+                var projectRoot = cordovaUtil.isCordova();
+                var hooksOrderFile = path.join(projectRoot, 'hooks_order.txt');
+
+                switchToOnePlatformScriptsPluginConfig();
+
+                return hooksRunner.fire(test_event, hookOptions).then(function () {
+                    expect(hooksOrderFile).toExist();
+
+                    expect(hooksOrderFileIsOrdered(hooksOrderFile)).toBe(true);
+                }).fail(function (err) {
+                    expect(err).toBeUndefined();
+                }).then(function () {
+                    restorePluginConfig(projectRoot);
+                    done();
+                });
+            });
+
+            it('Test 011 : should filter hook scripts from plugin.xml by platform', function (done) {
+                var test_event = 'before_build';
+                var projectRoot = cordovaUtil.isCordova();
+                var hooksOrderFile = path.join(projectRoot, 'hooks_order.txt');
+
+                switchToTwoPlatformsScriptsPluginConfig(projectRoot);
+
+                hookOptions.cordova.platforms = ['android'];
+
+                return hooksRunner.fire(test_event, hookOptions).then(function () {
+                    expect(hooksOrderFile).toExist();
+
+                    expect(hooksOrderFileIsOrdered(hooksOrderFile)).toBe(true);
+
+                    var baseScriptResults = [1, 2, 3, 4, 5, 6, 7, 21, 22];
+                    var androidPlatformScriptsResults = [26];
+
+                    expect(JSON.stringify(hooksOrderFileContents(hooksOrderFile)) ===
+                        JSON.stringify(baseScriptResults.slice(0).concat(androidPlatformScriptsResults))).toBe(true);
+                }).fail(function (err) {
+                    expect(err).toBeUndefined();
+                }).then(function () {
+                    restorePluginConfig(projectRoot);
+                    done();
+                });
+            });
+
+            it('Test 012 : should run before_plugin_uninstall, before_plugin_install, after_plugin_install hooks for a plugin being installed with correct opts.plugin context', function (done) {
+                var projectRoot = cordovaUtil.isCordova();
+
+                // remove plugin
+                cordova.plugin('rm', 'com.plugin.withhooks').fail(function (err) {
+                    expect(err.stack).toBeUndefined();
+                }).then(function () {
+                    cordova.plugin('add', testPluginFixturePath).fail(function (err) {
+                        expect(err).toBeUndefined();
+                    }).then(function () {
+                        testPluginInstalledPath = path.join(projectRoot, 'plugins', 'com.plugin.withhooks');
+                        shell.chmod('-R', 'ug+x', path.join(testPluginInstalledPath, 'scripts'));
+
+                        var pluginInfo = new PluginInfo(testPluginInstalledPath);
+
+                        var cordovaVersion = require('../package').version;
+
+                        var androidPluginOpts = {
+                            cordova: {
+                                platforms: [ 'android' ],
+                                plugins: ['com.plugin.withhooks'],
+                                version: cordovaVersion
+                            },
+                            plugin: {
+                                id: 'com.plugin.withhooks',
+                                pluginInfo: pluginInfo,
+                                platform: 'android',
+                                dir: testPluginInstalledPath
+                            },
+                            projectRoot: projectRoot
+                        };
+                        // Delete unique ids to allow comparing PluginInfo
+                        delete androidPluginOpts.plugin.pluginInfo._et;
+
+                        fire.calls.all().forEach(function(call) {
+                            if(call.args[1] && call.args[1].plugin) {
+                                // Delete unique ids to allow comparing PluginInfo
+                                delete call.args[1].plugin.pluginInfo._et;
+                            }
+
+                            if(call.args[0] == 'before_plugin_uninstall' ||
+                                call.args[0] == 'before_plugin_install' ||
+                                call.args[0] == 'after_plugin_install') {
+                                if(call.args[1]) {
+                                    expect(call.args[1].plugin).toBeDefined();
+                                    if(call.args[1].plugin.platform == 'android') {
+                                        expect(JSON.stringify(androidPluginOpts) ===
+                                            JSON.stringify(call.args[1])).toBe(true);
+                                    }
+                                }
+                            }
+                        });
+                    }).fail(function (err) {
+                        expect(err).toBeUndefined();
+                    }).fin(done);
+                });
+            });
+
+            it('Test 013 : should not execute the designated hook when --nohooks option specifies the exact hook name', function (done) {
+                var test_event = 'before_build';
+                hookOptions.nohooks = ['before_build'];
+
+                return hooksRunner.fire(test_event, hookOptions).then(function (msg) {
+                    expect(msg).toBeDefined();
+                    expect(msg).toBe('hook before_build is disabled.');
+                }).fail(function (err) {
+                    expect(err).toBeUndefined();
+                }).then(function () {
+                    done();
+                });
+            });
+
+            it('Test 014 : should not execute a set of matched hooks when --nohooks option specifies the hook pattern.', function (done) {
+                var test_events = ['before_build', 'after_plugin_add', 'before_platform_rm', 'before_prepare'];
+                hookOptions.nohooks = ['before*'];
+
+                return test_events.reduce(function(soFar, test_event) {
+                    return soFar.then(function() {
+                        return hooksRunner.fire(test_event, hookOptions).then(function (msg) {
+                            if (msg) {
+                                expect(msg).toBe('hook ' + test_event + ' is disabled.');
+                            } else {
+                                expect(test_event).toBe('after_plugin_add');
+                            }
+                        });
+                    });
+                }, Q()).fail(function (err) {
+                    expect(err).toBeUndefined();
+                }).then(function () {
+                    done();
+                });
+            });
+
+            it('Test 015 : should not execute all hooks when --nohooks option specifies .', function (done) {
+                var test_events = ['before_build', 'after_plugin_add', 'before_platform_rm', 'before_prepare'];
+                hookOptions.nohooks = ['.'];
+
+                return test_events.reduce(function(soFar, test_event) {
+                    return soFar.then(function() {
+                        return hooksRunner.fire(test_event, hookOptions).then(function (msg) {
+                            expect(msg).toBeDefined();
+                            expect(msg).toBe('hook ' + test_event + ' is disabled.');
+                        });
+                    });
+                }, Q()).fail(function (err) {
+                    expect(err).toBeUndefined();
+                }).then(function () {
+                    done();
+                });
+            });
+        });
+
+        describe('module-level hooks (event handlers)', function() {
+            var handler = jasmine.createSpy().and.returnValue(Q());
+            var test_event = 'before_build';
+
+            afterEach(function () {
+                cordova.removeAllListeners(test_event);
+                handler.calls.reset();
+            });
+
+            it('Test 016 : should fire handlers using cordova.on', function(done) {
+                cordova.on(test_event, handler);
+                hooksRunner.fire(test_event, hookOptions).then(function () {
+                    expect(handler).toHaveBeenCalled();
+                }).fail(function (err) {
+                    expect(err).not.toBeDefined();
+                }).fin(done);
+            });
+
+            it('Test 017 : should pass the project root folder as parameter into the module-level handlers', function (done) {
+                cordova.on(test_event, handler);
+                hooksRunner.fire(test_event, hookOptions).then(function () {
+                    expect(handler).toHaveBeenCalledWith(hookOptions);
+                }).fail(function (err) {
+                    console.log(err);
+                    expect(err).not.toBeDefined();
+                }).fin(done);
+            });
+
+            it('Test 018 : should be able to stop listening to events using cordova.off', function(done) {
+                cordova.on(test_event, handler);
+                cordova.off(test_event, handler);
+                hooksRunner.fire(test_event, hookOptions).then(function () {
+                    expect(handler).not.toHaveBeenCalled();
+                }).fail(function (err) {
+                    console.log(err);
+                    expect(err).toBeUndefined();
+                }).fin(done);
+            });
+
+            it('Test 019 : should execute event listeners serially', function(done) {
+                var h1_fired = false;
+                var h2_fired;
+                var h1 = function() {
+                    expect(h2_fired).toBe(false);
+                    // Delay 100 ms here to check that h2 is not executed until after
+                    // the promise returned by h1 is resolved.
+                    var q = Q.delay(100).then(function() {
+                        h1_fired = true;
+                    });
+                    return q;
+                };
+                h2_fired = false;
+                var h2 = function() {
+                    h2_fired = true;
+                    expect(h1_fired).toBe(true);
+                    return Q();
+                };
+
+                cordova.on(test_event, h1);
+                cordova.on(test_event, h2);
+
+                return hooksRunner.fire(test_event, hookOptions).then(function() {
+                    expect(h1_fired).toBe(true);
+                    expect(h2_fired).toBe(true);
+                }).fail(function(err) {
+                    expect(err).toBeUndefined();
+                }).then(function() {
+                    cordova.removeAllListeners(test_event);
+                    done();
+                });
+            });
+
+            it('Test 020 : 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 h2_fired;
+                var h1 = function () {
+                    h1_fired = true;
+                    expect(h2_fired).toBe(false);
+                    return Q();
+                };
+                h2_fired = false;
+                var h2 = function () {
+                    h2_fired = true;
+                    expect(h1_fired).toBe(true);
+                    return Q();
+                };
+
+                cordova.on(test_event, h1);
+                cordova.on(test_event, h2);
+                hooksRunner.fire(test_event, hookOptions).then(function() {
+                    expect(h1_fired).toBe(true);
+                    expect(h2_fired).toBe(true);
+                    done();
+                });
+            });
+
+            it('Test 021 : should pass data object that fire calls into async handlers', function(done) {
+                var async = function (opts) {
+                    expect(opts).toEqual(hookOptions);
+                    return Q();
+                };
+                cordova.on(test_event, async);
+                hooksRunner.fire(test_event, hookOptions).then(function () {
+                    done();
+                });
+            });
+
+            it('Test 022 : should pass data object that fire calls into sync handlers', function(done) {
+                var async = function (opts) {
+                    expect(opts).toEqual(hookOptions);
+                };
+                cordova.on(test_event, async);
+                hooksRunner.fire(test_event, hookOptions).fin(done);
+            });
+
+            it('Test 023 : should error if any script exits with non-zero code', function(done) {
+                hooksRunner.fire('fail', hookOptions).then(function () {
+                    expect('the call').toBe('a failure');
+                }, function (err) {
+                    expect(err).toBeDefined();
+                }).fin(done);
+            });
+        });
+
+        it('Test 024 :should not error if the hook is unrecognized', function(done) {
+            hooksRunner.fire('CLEAN YOUR SHORTS GODDAMNIT LIKE A BIG BOY!', hookOptions).fail(function (err) {
+                expect('Call with unrecognized hook ').toBe('successful.\n' + err);
+            }).fin(done);
+        });
+    });
+
+    // Cleanup. Must be the last spec. Is there a better place for final cleanup in Jasmine?
+    it('Test 025 : should not fail during cleanup', function () {
+        process.chdir(path.join(__dirname, '..'));  // Non e2e tests assume CWD is repo root.
+        if (ext == 'sh') {
+            shell.rm('-rf', tmpDir);
+        } else { // Windows:
+            // For some mysterious reason, both shell.rm and RMDIR /S /Q won't
+            // delete the dir on Windows, but they do remove the files leaving
+            // only folders. But the dir is removed just fine by
+            // shell.rm('-rf', tmpDir) at the top of this file with the next
+            // invocation of this test. The benefit of RMDIR /S /Q is that it
+            // doesn't print warnings like shell.rmdir() that look like this:
+            // rm: could not remove directory (code ENOTEMPTY): C:\Users\...
+            var cmd = 'RMDIR /S /Q ' + tmpDir;
+            child_process.exec(cmd);
+        }
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/04c12e2b/integration-tests/plugin_fetch.spec.js
----------------------------------------------------------------------
diff --git a/integration-tests/plugin_fetch.spec.js b/integration-tests/plugin_fetch.spec.js
new file mode 100644
index 0000000..d6791c2
--- /dev/null
+++ b/integration-tests/plugin_fetch.spec.js
@@ -0,0 +1,537 @@
+/**
+    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 plugin  = require('../src/cordova/plugin'),
+    helpers = require('./helpers'),
+    path    = require('path'),
+    events = require('cordova-common').events,
+    shell   = require('shelljs');
+
+var testPluginVersions = [
+    '0.0.2',
+    '0.7.0',
+    '1.0.0',
+    '1.1.0',
+    '1.1.3',
+    '1.3.0',
+    '1.7.0',
+    '1.7.1',
+    '2.0.0-rc.1',
+    '2.0.0-rc.2',
+    '2.0.0',
+    '2.3.0'
+];
+
+var cordovaVersion = '3.4.2';
+
+var tempDir = helpers.tmpDir('plugin_fetch_spec');
+var project = path.join(tempDir, 'project');
+
+var getVersionErrorCallback;
+var warnings = [];
+
+// Used to extract the constraint, the installed version, and the required
+// semver range from a warning message
+var UNMET_REQ_REGEX = /\s+([^\s]+)[^\d]+(\d+\.\d+\.\d+) in project, (.+) required\)/;
+
+// We generate warnings when we don't fetch latest. Collect them to make sure we
+// are making the correct warnings
+events.on('warn', function(warning) {
+    warnings.push(warning);
+});
+
+// Tests a sample engine against the installed platforms/plugins in our test
+// project
+function testEngineWithProject(done, testEngine, testResult) {
+    plugin.getFetchVersion(project,
+        {
+            'version': '2.3.0',
+            'name': 'test-plugin',
+            'engines': { 'cordovaDependencies': testEngine },
+            'versions': testPluginVersions
+        }, cordovaVersion)
+    .then(function(toFetch) {
+        expect(toFetch).toBe(testResult);
+    })
+    .fail(getVersionErrorCallback)
+    .fin(done);
+}
+
+// Checks the warnings that were printed by the CLI to ensure that the code is
+// listing the correct reasons for failure. Checks against the global warnings
+// object which is reset before each test
+function checkUnmetRequirements(requirements) {
+    var reqWarnings = [];
+
+    warnings.forEach(function(warning) {
+        var extracted = UNMET_REQ_REGEX.exec(warning);
+        if (extracted) {
+            reqWarnings.push({
+                dependency: extracted[1],
+                installed: extracted[2],
+                required: extracted[3]
+            });
+        }
+    });
+    expect(reqWarnings.length).toEqual(requirements.length);
+
+
+    requirements.forEach(function(requirement) {
+        expect(reqWarnings).toContainArray(function(extractedWarning) {
+            return  extractedWarning.dependency === requirement.dependency.trim() &&
+                    extractedWarning.installed  === requirement.installed.trim() &&
+                    extractedWarning.required   === requirement.required.trim();
+        }, requirement);
+    });
+}
+
+// Helper functions for creating the requirements objects taken by
+// checkUnmetRequirements()
+function getPlatformRequirement(requirement) {
+    return {
+        dependency: 'cordova-android',
+        installed: '3.1.0',
+        required: requirement
+    };
+}
+
+function getCordovaRequirement(requirement) {
+    return {
+        dependency: 'cordova',
+        installed: cordovaVersion,
+        required: requirement
+    };
+}
+
+function getPluginRequirement(requirement) {
+    return {
+        dependency: 'ca.filmaj.AndroidPlugin',
+        installed: '4.2.0',
+        required: requirement
+    };
+}
+
+// Generates a callback that checks warning messages after the test is complete
+function getWarningCheckCallback(done, requirements) {
+    return function() {
+        checkUnmetRequirements(requirements);
+        expect(getVersionErrorCallback).not.toHaveBeenCalled();
+        done();
+    };
+}
+var fixtures = path.join(__dirname, '..', 'spec-cordova', 'fixtures');
+
+function createTestProject() {
+    // Get the base project
+    shell.cp('-R', path.join(fixtures, 'base'), tempDir);
+    shell.mv(path.join(tempDir, 'base'), project);
+
+    // Copy a platform and a plugin to our sample project
+    shell.cp('-R',
+        path.join(fixtures, 'platforms', helpers.testPlatform),
+        path.join(project, 'platforms'));
+    shell.cp('-R',
+        path.join(fixtures, 'plugins', 'android'),
+        path.join(project, 'plugins'));
+}
+
+function removeTestProject() {
+    shell.rm('-rf', tempDir);
+}
+
+describe('plugin fetching version selection', function() {
+    createTestProject();
+    beforeEach(function() {
+        jasmine.addMatchers({
+            'toContainArray': function() {
+                return {
+                    compare: function(actual, expected) {
+                        var result = {};
+                        result.pass = false;
+                        for(var i = 0; i < actual.length; i++) {
+                            if (expected(actual[i])) {
+                                result.pass = true;
+                                break;
+                            }
+                        }
+                        return result;
+                    }
+                };
+            }
+        });
+
+        warnings = [];
+        getVersionErrorCallback = jasmine.createSpy('unexpectedPluginFetchErrorCallback');
+    });
+
+    it('Test 001 : should handle a mix of upper bounds and single versions', function(done) {
+        var testEngine = {
+            '0.0.0' : { 'cordova-android': '1.0.0' },
+            '0.0.2' : { 'cordova-android': '>1.0.0' },
+            '<1.0.0': { 'cordova-android': '<2.0.0' },
+            '1.0.0' : { 'cordova-android': '>2.0.0' },
+            '1.7.0' : { 'cordova-android': '>4.0.0' },
+            '<2.3.0': { 'cordova-android': '<6.1.1' },
+            '2.3.0' : { 'cordova-android': '6.1.1' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('6.1.1')
+        ]);
+        testEngineWithProject(after, testEngine, '1.3.0');
+    }, 6000);
+
+    it('Test 002 : should apply upper bound engine constraints when there are no unspecified constraints above the upper bound', function(done) {
+        var testEngine = {
+            '1.0.0' : { 'cordova-android': '>2.0.0' },
+            '1.7.0' : { 'cordova-android': '>4.0.0' },
+            '<2.3.0': {
+                'cordova-android': '<6.1.1',
+                'ca.filmaj.AndroidPlugin': '<1.0.0'
+            },
+            '2.3.0' : { 'cordova-android': '6.1.1' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('6.1.1')
+        ]);
+        testEngineWithProject(after, testEngine, null);
+    });
+
+    it('Test 003 : should apply upper bound engine constraints when there are unspecified constraints above the upper bound', function(done) {
+        var testEngine = {
+            '0.0.0' : {},
+            '2.0.0' : { 'cordova-android': '~5.0.0' },
+            '<1.0.0': { 'cordova-android': '>5.0.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('~5.0.0')
+        ]);
+        testEngineWithProject(after, testEngine, '1.7.1');
+
+    });
+
+    it('Test 004 : should handle the case where there are no constraints for earliest releases', function(done) {
+        var testEngine = {
+            '1.0.0' : { 'cordova-android': '~5.0.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('~5.0.0')
+        ]);
+        testEngineWithProject(after, testEngine, '0.7.0');
+
+    });
+
+    it('Test 005 : should handle the case where the lowest version is unsatisfied', function(done) {
+        var testEngine = {
+            '0.0.2' : { 'cordova-android': '~5.0.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('~5.0.0')
+        ]);
+        testEngineWithProject(after, testEngine, null);
+
+    });
+
+    it('Test 006 : should handle upperbounds if no single version constraints are given', function(done) {
+        var testEngine = {
+            '<1.0.0': { 'cordova-android': '<2.0.0' }
+        };
+
+        var after = getWarningCheckCallback(done, []);
+
+        testEngineWithProject(after, testEngine, '2.3.0');
+
+    });
+
+    it('Test 007 : should apply upper bounds greater than highest version', function(done) {
+        var testEngine = {
+            '0.0.0' : {},
+            '<5.0.0': { 'cordova-android': '<2.0.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('<2.0.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, null);
+
+    });
+
+    it('Test 008 : should treat empty constraints as satisfied', function(done) {
+        var testEngine = {
+            '1.0.0' : {},
+            '1.1.0' : { 'cordova-android': '>5.0.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('>5.0.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, '1.0.0');
+
+    });
+
+    it('Test 009 : should ignore an empty cordovaDependencies entry', function(done) {
+        var testEngine = {};
+
+        var after = getWarningCheckCallback(done, []);
+
+        testEngineWithProject(after, testEngine, null);
+
+    });
+
+    it('Test 010 : should ignore a badly formatted semver range', function(done) {
+        var testEngine = {
+            '1.1.3' : { 'cordova-android': 'badSemverRange' }
+        };
+
+        var after = getWarningCheckCallback(done, []);
+
+        testEngineWithProject(after, testEngine, '2.3.0');
+
+    });
+
+    it('Test 011 : should respect unreleased versions in constraints', function(done) {
+        var testEngine = {
+            '1.0.0' : { 'cordova-android': '3.1.0' },
+            '1.1.2' : { 'cordova-android': '6.1.1' },
+            '1.3.0' : { 'cordova-android': '6.1.1' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('6.1.1')
+        ]);
+
+        testEngineWithProject(after, testEngine, '1.1.0');
+
+    });
+
+    it('Test 012 : should respect plugin constraints', function(done) {
+        var testEngine = {
+            '0.0.0' : { 'ca.filmaj.AndroidPlugin': '1.2.0' },
+            '1.1.3' : { 'ca.filmaj.AndroidPlugin': '<5.0.0 || >2.3.0' },
+            '2.3.0' : { 'ca.filmaj.AndroidPlugin': '6.1.1' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPluginRequirement('6.1.1')
+        ]);
+
+        testEngineWithProject(after, testEngine, '2.0.0');
+
+    });
+
+    it('Test 013 : should respect cordova constraints', function(done) {
+        var testEngine = {
+            '0.0.0' : { 'cordova': '>1.0.0' },
+            '1.1.3' : { 'cordova': '<3.0.0 || >4.0.0' },
+            '2.3.0' : { 'cordova': '6.1.1' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getCordovaRequirement('6.1.1')
+        ]);
+
+        testEngineWithProject(after, testEngine, '1.1.0');
+
+    });
+
+    it('Test 014 : should not include pre-release versions', function(done) {
+        var testEngine = {
+            '0.0.0' : {},
+            '2.0.0' : { 'cordova-android': '>5.0.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('>5.0.0')
+        ]);
+
+        // Should not return 2.0.0-rc.2
+        testEngineWithProject(after, testEngine, '1.7.1');
+
+    });
+
+    it('Test 015 : should not fail if there is no engine in the npm info', function(done) {
+        plugin.getFetchVersion(project, {
+                version: '2.3.0',
+                name: 'test-plugin',
+                versions: testPluginVersions
+            }, cordovaVersion)
+        .then(function(toFetch) {
+            expect(toFetch).toBe(null);
+        })
+        .fail(getVersionErrorCallback).fin(done);
+    });
+
+    it('Test 016 : should not fail if there is no cordovaDependencies in the engines', function(done) {
+        var after = getWarningCheckCallback(done, []);
+
+        plugin.getFetchVersion(project, {
+                version: '2.3.0',
+                name: 'test-plugin',
+                versions: testPluginVersions,
+                engines: {
+                    'node': '>7.0.0',
+                    'npm': '~2.0.0'
+                }
+            }, cordovaVersion)
+        .then(function(toFetch) {
+            expect(toFetch).toBe(null);
+        })
+        .fail(getVersionErrorCallback).fin(after);
+
+    });
+
+    it('Test 017 : should handle extra whitespace', function(done) {
+        var testEngine = {
+            '  1.0.0    '   : {},
+            '2.0.0   '      : { ' cordova-android': '~5.0.0   ' },
+            ' <  1.0.0\t'   : { ' cordova-android  ': ' > 5.0.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('~5.0.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, '1.7.1');
+
+    });
+
+    it('Test 018 : should ignore badly typed version requirement entries', function(done) {
+        var testEngine = {
+            '1.1.0' : ['cordova', '5.0.0'],
+            '1.3.0' : undefined,
+            '1.7.0' : null
+        };
+
+        var after = getWarningCheckCallback(done, []);
+
+        testEngineWithProject(after, testEngine, '2.3.0');
+
+    });
+
+    it('Test 019 : should ignore badly typed constraint entries', function(done) {
+        var testEngine = {
+            '0.0.2' : { 'cordova': 1 },
+            '0.7.0' : { 'cordova': {}},
+            '1.0.0' : { 'cordova': undefined},
+            '1.1.3' : { 8        : '5.0.0'},
+            '1.3.0' : { 'cordova': [] },
+            '1.7.1' : { 'cordova': null }
+        };
+
+        var after = getWarningCheckCallback(done, []);
+
+        testEngineWithProject(after, testEngine, '2.3.0');
+
+    });
+
+    it('Test 020 : should ignore bad semver versions', function(done) {
+        var testEngine = {
+            '0.0.0'         : { 'cordova-android': '5.0.0' },
+            'notAVersion'   : { 'cordova-android': '3.1.0' },
+            '^1.1.2'        : { 'cordova-android': '3.1.0' },
+            '<=1.3.0'       : { 'cordova-android': '3.1.0' },
+            '1.0'           : { 'cordova-android': '3.1.0' },
+            '2'               : { 'cordova-android': '3.1.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('5.0.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, null);
+    });
+
+    it('Test 021 : should not fail if there are bad semver versions', function(done) {
+        var testEngine = {
+            'notAVersion'   : { 'cordova-android': '3.1.0' },
+            '^1.1.2'        : { 'cordova-android': '3.1.0' },
+            '<=1.3.0'       : { 'cordova-android': '3.1.0' },
+            '1.0.0'         : { 'cordova-android': '~3'    },   // Good semver
+            '2.0.0'         : { 'cordova-android': '5.1.0' },   // Good semver
+            '1.0'           : { 'cordova-android': '3.1.0' },
+            '2'               : { 'cordova-android': '3.1.0' }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('5.1.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, '1.7.1');
+    });
+
+    it('Test 022 : should properly warn about multiple unmet requirements', function(done) {
+        var testEngine = {
+            '1.7.0' : {
+                'cordova-android'           : '>5.1.0',
+                'ca.filmaj.AndroidPlugin'   : '3.1.0',
+                'cordova'                   : '3.4.2'
+            }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('>5.1.0'),
+            getPluginRequirement('3.1.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, '1.3.0');
+    });
+
+    it('Test 023 : should properly warn about both unmet latest and upper bound requirements', function(done) {
+        var testEngine = {
+            '1.7.0' : { 'cordova-android': '>5.1.0' },
+            '<5.0.0': {
+                'cordova-android'           : '>7.1.0',
+                'ca.filmaj.AndroidPlugin'   : '3.1.0'
+            }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('>5.1.0 AND >7.1.0'),
+            getPluginRequirement('3.1.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, null);
+    });
+
+    it('Test 024 : should not warn about versions past latest', function(done) {
+        var testEngine = {
+            '1.7.0' : { 'cordova-android': '>5.1.0' },
+            '7.0.0': {
+                'cordova-android'           : '>7.1.0',
+                'ca.filmaj.AndroidPlugin'   : '3.1.0'
+            }
+        };
+
+        var after = getWarningCheckCallback(done, [
+            getPlatformRequirement('>5.1.0')
+        ]);
+
+        testEngineWithProject(after, testEngine, '1.3.0');
+    });
+
+    it('Test 025 : clean up after plugin fetch spec', function() {
+        removeTestProject();
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/04c12e2b/integration-tests/plugman_fetch.spec.js
----------------------------------------------------------------------
diff --git a/integration-tests/plugman_fetch.spec.js b/integration-tests/plugman_fetch.spec.js
new file mode 100644
index 0000000..5e02b39
--- /dev/null
+++ b/integration-tests/plugman_fetch.spec.js
@@ -0,0 +1,340 @@
+/**
+    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 rewire  = require('rewire'),
+    fetch   = rewire('../src/plugman/fetch'),
+    fs      = require('fs'),
+    os      = require('os'),
+    path    = require('path'),
+    shell   = require('shelljs'),
+    realrm  = shell.rm,
+    TIMEOUT = 60 * 1000,
+    //xml_helpers = require('../src/util/xml-helpers'),
+    metadata = require('../src/plugman/util/metadata'),
+    temp    = path.join(os.tmpdir(), 'plugman', 'fetch');
+var plugins_dir = path.join(__dirname, '..', 'spec-plugman', 'plugins'),
+    test_plugin = path.join(plugins_dir, 'org.test.plugins.childbrowser'),
+    test_pkgjson_plugin = path.join(plugins_dir, 'pkgjson-test-plugin'),
+    test_plugin_searchpath = path.join(test_plugin, '..'),
+    //test_plugin_with_space = path.join(__dirname, 'folder with space', 'plugins', 'org.test.plugins.childbrowser'),
+    //test_plugin_xml = xml_helpers.parseElementtreeSync(path.join(test_plugin, 'plugin.xml')),
+    test_plugin_id = 'org.test.plugins.childbrowser',
+    test_plugin_version ='0.6.0',
+    plugins = require('../src/plugman/util/plugins'),
+    Q = require('q'),
+    registry = require('../src/plugman/registry/registry');
+
+describe('fetch', function() {
+
+    function wrapper(p, done, post) {
+        p.then(post, function(err) {
+            expect(err).toBeUndefined('Unexpected exception' + err.stack);
+        }).fin(done);
+    }
+    /*
+     * Taking out the following test. Fetch has a copyPlugin method that uses existsSync to see if a plugin already exists in the plugins folder. If the plugin exists in the plugins directory for the cordova project, it won't be copied over. This test fails now due it always returning true for existsSync.
+    describe('plugin in a dir with spaces', function() {
+        it('should copy locally-available plugin to plugins directory when spaces in path', function(done) {
+            // XXX: added this because plugman tries to fetch from registry when plugin folder does not exist
+            spyOn(fs,'existsSync').andReturn(true);
+            spyOn(xml_helpers, 'parseElementtreeSync').andReturn(test_plugin_xml);
+            spyOn(shell, 'rm');
+            spyOn(metadata, 'save_fetch_metadata');
+            var cp = spyOn(shell, 'cp');
+            wrapper(fetch(test_plugin_with_space, temp), done, function() {
+                expect(cp).toHaveBeenCalledWith('-R', path.join(test_plugin_with_space, '*'), path.join(temp, test_plugin_id));
+            });
+        });
+    });
+*/
+    describe('local plugins', function() {
+        var rm, sym, cp, save_metadata, revertLocal, revertFetch, fetchCalls = 0;
+        beforeEach(function() {
+            rm = spyOn(shell, 'rm');
+            sym = spyOn(fs, 'symlinkSync');
+            cp = spyOn(shell, 'cp').and.callThrough();
+            save_metadata = spyOn(metadata, 'save_fetch_metadata');
+            realrm('-rf', temp);
+            revertLocal = fetch.__set__('localPlugins', null);
+            revertFetch = fetch.__set__('fetch', function(pluginDir) {
+                fetchCalls ++;
+                return Q(pluginDir);
+            });
+        });
+
+        afterEach(function(){
+            revertLocal();
+            revertFetch();
+            fetchCalls = 0;
+        });
+
+        it('Test 001 : should copy locally-available plugin to plugins directory', function(done) {
+            wrapper(fetch(test_plugin, temp), done, function() {
+                expect(cp).toHaveBeenCalledWith('-R', path.join(test_plugin, '*'), path.join(temp, test_plugin_id));
+            });
+        });
+        it('Test 002 : should copy locally-available plugin to plugins directory when adding a plugin with searchpath argument', function(done) {
+            wrapper(fetch(test_plugin_id, temp, { searchpath: test_plugin_searchpath }), done, function() {
+                expect(cp).toHaveBeenCalledWith('-R', path.join(test_plugin, '*'), path.join(temp, test_plugin_id));
+            });
+        });
+        it('Test 003 : should create a symlink if used with `link` param', function(done) {
+            wrapper(fetch(test_plugin, temp, { link: true }), done, function() {
+                expect(sym).toHaveBeenCalledWith(test_plugin, path.join(temp, test_plugin_id), 'dir');
+            });
+        });
+
+        it('Test 004 : should fail when the expected ID doesn\'t match', function(done) {
+            fetch(test_plugin, temp, { expected_id: 'wrongID' })
+            .then(function() {
+                expect('this call').toBe('fail');
+            }, function(err) {
+                expect(''+err).toContain('Expected plugin to have ID "wrongID" but got');
+            }).fin(done);
+        });
+
+        it('Test 005 : should succeed when the expected ID is correct', function(done) {
+            wrapper(fetch(test_plugin, temp, { expected_id: test_plugin_id }), done, function() {
+                expect(1).toBe(1);
+            });
+        });
+        it('Test 006 : should fail when the expected ID with version specified doesn\'t match', function(done) {
+            fetch(test_plugin, temp, { expected_id: test_plugin_id + '@wrongVersion' })
+            .then(function() {
+                expect('this call').toBe('fail');
+            }, function(err) {
+                expect(''+err).toContain('to satisfy version "wrongVersion" but got');
+            }).fin(done);
+        });
+        it('Test 007 : should succeed when the plugin version specified is correct', function(done) {
+            var exp_id = test_plugin_id + '@' + test_plugin_version;
+            wrapper(fetch(test_plugin, temp, { expected_id: exp_id}), done, function() {
+                expect(1).toBe(1);
+            });
+        });
+        it('Test 027 : should copy locally-available plugin to plugins directory', function(done) {
+            wrapper(fetch(test_pkgjson_plugin, temp, {fetch:true}), done, function() {
+                expect(cp).toHaveBeenCalledWith('-R', path.join(test_pkgjson_plugin, '*'), path.join(temp, 'pkgjson-test-plugin'));
+                expect(fetchCalls).toBe(1);
+            });
+        });
+        it('Test 028 : should fail when locally-available plugin is missing pacakge.json', function(done) {
+            fetch(test_plugin, temp, {fetch:true})
+            .then(function() {
+                expect(false).toBe(true);
+            }).fail(function(err) { 
+                expect(err).toBeDefined();
+                expect(err.message).toContain('needs a valid package.json');
+                done();
+            });
+        });
+    });
+    describe('git plugins', function() {
+        var clone, save_metadata, done;
+
+        beforeEach(function() {
+            clone = spyOn(plugins, 'clonePluginGitRepo').and.returnValue(Q(test_plugin));
+            save_metadata = spyOn(metadata, 'save_fetch_metadata');
+            done = false;
+        });
+        it('Test 008 : should call clonePluginGitRepo for https:// and git:// based urls', function(done) {
+            var url = 'https://github.com/bobeast/GAPlugin.git';
+                fetch(url, temp).then(function(){
+                    expect(save_metadata).toHaveBeenCalled();
+                    expect(clone).toHaveBeenCalledWith(url, temp, '.', undefined, undefined);
+                    done();
+            });
+        }, 6000);
+        
+        it('Test 009 : should call clonePluginGitRepo with subdir if applicable', function(done) {
+            var url = 'https://github.com/bobeast/GAPlugin.git';
+            var dir = 'fakeSubDir';
+            fetch(url, temp, { subdir: dir }).then(function(){
+                expect(clone).toHaveBeenCalledWith(url, temp, dir, undefined, undefined);
+                expect(save_metadata).toHaveBeenCalled();
+                done();
+            });
+        }, 6000);
+
+        it('Test 010 : should call clonePluginGitRepo with subdir and git ref if applicable', function(done) {
+            var url = 'https://github.com/bobeast/GAPlugin.git';
+            var dir = 'fakeSubDir';
+            var ref = 'fakeGitRef';
+            fetch(url, temp, { subdir: dir, git_ref: ref }).then(function(){
+                expect(clone).toHaveBeenCalledWith(url, temp, dir, ref, undefined);
+                expect(save_metadata).toHaveBeenCalled();
+                done();
+            });
+        }, 6000);
+
+        it('Test 011 : should extract the git ref from the URL hash, if provided', function(done) {
+            var url = 'https://github.com/bobeast/GAPlugin.git#fakeGitRef';
+            var baseURL = 'https://github.com/bobeast/GAPlugin.git';
+            fetch(url, temp, {}).then(function(){
+                expect(clone).toHaveBeenCalledWith(baseURL, temp, '.', 'fakeGitRef', undefined);
+                expect(save_metadata).toHaveBeenCalled();
+                done();
+            });
+        }, 6000);
+
+        it('Test 012 : should extract the subdir from the URL hash, if provided', function(done) {
+            var url = 'https://github.com/bobeast/GAPlugin.git#:fakeSubDir';
+            var baseURL = 'https://github.com/bobeast/GAPlugin.git';
+            fetch(url, temp, {}).then(function(result){
+                expect(clone).toHaveBeenCalledWith(baseURL, temp, 'fakeSubDir', undefined, undefined);
+                expect(save_metadata).toHaveBeenCalled();
+                done();
+            });
+        }, 6000);
+
+        it('Test 013 : should extract the git ref and subdir from the URL hash, if provided', function(done) {
+            var url = 'https://github.com/bobeast/GAPlugin.git#fakeGitRef:/fake/Sub/Dir/';
+            var baseURL = 'https://github.com/bobeast/GAPlugin.git';
+            fetch(url, temp, {}).then(function(result){
+                expect(clone).toHaveBeenCalledWith(baseURL, temp, 'fake/Sub/Dir', 'fakeGitRef', undefined);
+                expect(save_metadata).toHaveBeenCalled();
+                done();
+            });
+        }, 6000);
+
+        it('Test 014 : should fail when the expected ID doesn\'t match', function(done) {
+            fetch('https://github.com/bobeast/GAPlugin.git', temp, { expected_id: 'wrongID' })
+            .then(function() {
+                expect('this call').toBe('fail');
+            }, function(err) {
+                expect(''+err).toContain('Expected plugin to have ID "wrongID" but got');
+            }).fin(done);
+        });
+
+        it('Test 015 : should fail when the expected ID with version specified doesn\'t match', function(done) {
+            fetch('https://github.com/bobeast/GAPlugin.git', temp, { expected_id: 'id@wrongVersion' })
+            .then(function() {
+                expect('this call').toBe('fail');
+            }, function(err) {
+                expect(''+err).toContain('Expected plugin to have ID "id" but got');
+            }).fin(done);
+        });
+
+        it('Test 016 : should succeed when the expected ID is correct', function(done) {
+            wrapper(fetch('https://github.com/bobeast/GAPlugin.git', temp, { expected_id: test_plugin_id }), done, function() {
+                expect(1).toBe(1);
+            });
+        });
+    });
+
+    describe('github plugins', function() {
+        // these tests actually pull a plugin from github
+        beforeEach(function(){
+            realrm('-rf',temp);
+        });
+
+        // this commit uses the new id
+        it('Test 017 : should fetch from a commit-sha', function(done) {
+            wrapper(fetch('http://github.com/apache/cordova-plugin-device.git#ad5f1e7bfd05ef98c01df549a0fa98036a5625db', temp, { expected_id: 'cordova-plugin-device' }), done, function() {
+                expect(1).toBe(1);
+                done();
+            });
+        }, TIMEOUT);
+        // this branch uses the old id
+        it('Test 018 : should fetch from a branch', function(done) {
+            wrapper(fetch('http://github.com/apache/cordova-plugin-device.git#cdvtest', temp, { expected_id: 'org.apache.cordova.device' }), done, function() {
+                expect(1).toBe(1);
+                done();
+            });
+        }, TIMEOUT);
+        // this tag uses the new id
+        it('Test 019 : should fetch from a tag', function(done) {
+            wrapper(fetch('http://github.com/apache/cordova-plugin-device.git#r1.0.0', temp, { expected_id: 'cordova-plugin-device' }), done, function() {
+                expect(1).toBe(1);
+                done();
+            });
+        }, TIMEOUT);
+    });
+
+    describe('fetch recursive error CB-8809', function(){
+
+        var srcDir = path.join(plugins_dir, 'recursivePlug');
+        var appDir = path.join(plugins_dir, 'recursivePlug', 'demo');
+
+        if(/^win/.test(process.platform)) {
+            it('Test 020 : should copy all but the /demo/ folder',function(done) {
+                var cp = spyOn(shell, 'cp');
+                wrapper(fetch(srcDir, appDir),done, function() {
+                    expect(cp).toHaveBeenCalledWith('-R',path.join(srcDir,'asset.txt'),path.join(appDir,'test-recursive'));
+                    expect(cp).not.toHaveBeenCalledWith('-R',srcDir,path.join(appDir,'test-recursive'));
+                });
+            });
+        }
+        else {
+            it('Test 021 : should skip copy to avoid recursive error', function(done) {
+
+                var cp = spyOn(shell, 'cp').and.callFake(function(){});
+
+                wrapper(fetch(srcDir, appDir),done, function() {
+                    expect(cp).not.toHaveBeenCalled();
+                });
+            });
+        }
+
+    });
+
+    describe('registry plugins', function() {
+        var pluginId = 'dummyplugin', sFetch;
+        var rm, sym, save_metadata;
+        beforeEach(function() {
+            rm = spyOn(shell, 'rm');
+            sym = spyOn(fs, 'symlinkSync');
+            save_metadata = spyOn(metadata, 'save_fetch_metadata');
+            sFetch = spyOn(registry, 'fetch').and.returnValue(Q(test_plugin));
+            realrm('-rf', temp);
+        });
+
+        it('Test 022 : should fail when the expected ID with version specified doesn\'t match', function(done) {
+            //fetch(pluginId, temp, { expected_id: test_plugin_id + '@wrongVersion' })
+            fetch(pluginId, temp, { expected_id: 'wrongID' })
+            .then(function() {
+                expect('this call').toBe('fail');
+            }, function(err) {
+                expect(''+err).toContain('Expected plugin to have ID "wrongID" but got');
+            }).fin(done);
+        });
+        
+        it('Test 023 : should succeed when the expected ID is correct', function(done) {
+            wrapper(fetch(pluginId, temp, { expected_id: test_plugin_id }), done, function() {
+                expect(1).toBe(1);
+            });
+        });
+        it('Test 024 : should succeed when the plugin version specified is correct', function(done) {
+            wrapper(fetch(pluginId, temp, { expected_id: test_plugin_id + '@' + test_plugin_version }), done, function() {
+                expect(1).toBe(1);
+            });
+        });
+        it('Test 025 : should fetch plugins that are scoped packages', function(done) {
+            var scopedPackage = '@testcope/dummy-plugin';
+            wrapper(fetch(scopedPackage, temp, { expected_id: test_plugin_id }), done, function() {
+                expect(sFetch).toHaveBeenCalledWith([scopedPackage]);
+            });
+        });
+        it('Test 026 : should fetch plugins that are scoped packages and have versions specified', function(done) {
+            var scopedPackage = '@testcope/dummy-plugin@latest';
+            wrapper(fetch(scopedPackage, temp, { expected_id: test_plugin_id }), done, function() {
+                expect(sFetch).toHaveBeenCalledWith([scopedPackage]);
+            });
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/04c12e2b/integration-tests/plugman_uninstall.spec.js
----------------------------------------------------------------------
diff --git a/integration-tests/plugman_uninstall.spec.js b/integration-tests/plugman_uninstall.spec.js
new file mode 100644
index 0000000..2b81705
--- /dev/null
+++ b/integration-tests/plugman_uninstall.spec.js
@@ -0,0 +1,333 @@
+/**
+    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.
+*/
+
+/* jshint sub:true */
+
+var uninstall = require('../src/plugman/uninstall'),
+    install = require('../src/plugman/install'),
+    actions = require('cordova-common').ActionStack,
+    PluginInfo = require('cordova-common').PluginInfo,
+    events = require('cordova-common').events,
+    plugman = require('../src/plugman/plugman'),
+    common  = require('./common'),
+    platforms = require('../src/platforms/platforms'),
+    xmlHelpers = require('cordova-common').xmlHelpers,
+    et      = require('elementtree'),
+    fs      = require('fs'),
+    path    = require('path'),
+    shell   = require('shelljs'),
+    Q       = require('q'),
+    spec    = path.join(__dirname, '..', 'spec-plugman'),
+    done    = false,
+    srcProject = path.join(spec, 'projects', 'android'),
+    project = path.join(spec, 'projects', 'android_uninstall.test'),
+    project2 = path.join(spec, 'projects', 'android_uninstall.test2'),
+    project3 = path.join(spec, 'projects', 'android_uninstall.test3'),
+
+    plugins_dir = path.join(spec, 'plugins'),
+    plugins_install_dir = path.join(project, 'cordova', 'plugins'),
+    plugins_install_dir2 = path.join(project2, 'cordova', 'plugins'),
+    plugins_install_dir3 = path.join(project3, 'cordova', 'plugins'),
+
+    plugins = {
+        'org.test.plugins.dummyplugin' : path.join(plugins_dir, 'org.test.plugins.dummyplugin'),
+        'A' : path.join(plugins_dir, 'dependencies', 'A'),
+        'C' : path.join(plugins_dir, 'dependencies', 'C')
+    },
+    dummy_id = 'org.test.plugins.dummyplugin';
+
+var dummyPluginInfo = new PluginInfo(plugins['org.test.plugins.dummyplugin']);
+
+var TEST_XML = '<?xml version="1.0" encoding="UTF-8"?>\n' +
+    '<widget xmlns     = "http://www.w3.org/ns/widgets"\n' +
+    '        xmlns:cdv = "http://cordova.apache.org/ns/1.0"\n' +
+    '        id        = "io.cordova.hellocordova"\n' +
+    '        version   = "0.0.1">\n' +
+    '    <name>Hello Cordova</name>\n' +
+    '    <description>\n' +
+    '        A sample Apache Cordova application that responds to the deviceready event.\n' +
+    '    </description>\n' +
+    '    <author href="http://cordova.io" email="dev@cordova.apache.org">\n' +
+    '        Apache Cordova Team\n' +
+    '    </author>\n' +
+    '    <content src="index.html" />\n' +
+    '    <access origin="*" />\n' +
+    '</widget>\n';
+
+describe('plugman uninstall start', function() {
+    beforeEach(function () {
+        var origParseElementtreeSync = xmlHelpers.parseElementtreeSync.bind(xmlHelpers);
+        spyOn(xmlHelpers, 'parseElementtreeSync').and.callFake(function(path) {
+            if (/config.xml$/.test(path)) return new et.ElementTree(et.XML(TEST_XML));
+            return origParseElementtreeSync(path);
+        });
+    });
+
+    it('Test 001 : plugman uninstall start', function(done) {
+        shell.rm('-rf', project, project2, project3);
+        shell.cp('-R', path.join(srcProject, '*'), project);
+        shell.cp('-R', path.join(srcProject, '*'), project2);
+        shell.cp('-R', path.join(srcProject, '*'), project3);
+
+        return install('android', project, plugins['org.test.plugins.dummyplugin'])
+        .then(function(result){
+            return install('android', project, plugins['A']);
+        }).then( function(){
+            return install('android', project2, plugins['C']);
+        }).then(function(){
+            return install('android', project2, plugins['A']);
+        }).then(function(){
+            return install('android', project3, plugins['A']);
+        }).then(function(){
+            return install('android', project3, plugins['C']);
+        }).then(function(result){
+            expect(result).toEqual(true);
+            done();
+        }).fail(function(err){
+            expect(err).toBeUndefined();
+        });
+    }, 60000);
+});
+
+describe('uninstallPlatform', function() {
+    var proc, rm;
+    var fsWrite;
+
+    beforeEach(function() {
+        proc = spyOn(actions.prototype, 'process').and.returnValue(Q());
+        fsWrite = spyOn(fs, 'writeFileSync').and.returnValue(true);
+        rm = spyOn(shell, 'rm').and.returnValue(true);
+        spyOn(shell, 'cp').and.returnValue(true);
+        done = false;
+    });
+    describe('success', function() {
+
+        it('Test 002 : should get PlatformApi instance for platform and invoke its\' removePlugin method', function(done) {
+            var platformApi = { removePlugin: jasmine.createSpy('removePlugin').and.returnValue(Q()) };
+            var getPlatformApi = spyOn(platforms, 'getPlatformApi').and.returnValue(platformApi);
+
+            uninstall.uninstallPlatform('android', project, dummy_id)
+            .then(function() {
+                expect(getPlatformApi).toHaveBeenCalledWith('android', project);
+                expect(platformApi.removePlugin).toHaveBeenCalled();
+                done();
+            }).fail(function (err) {
+                expect(err).toBeUndefined();
+                done();
+            });
+        }, 6000);
+
+        it('Test 003 : should return propagate value returned by PlatformApi removePlugin method', function(done) {
+            var platformApi = { removePlugin: jasmine.createSpy('removePlugin') };
+            spyOn(platforms, 'getPlatformApi').and.returnValue(platformApi);
+
+            var existsSyncOrig = fs.existsSync;
+            spyOn(fs, 'existsSync').and.callFake(function (file) {
+                if (file.indexOf(dummy_id) >= 0) return true;
+                return existsSyncOrig.call(fs, file);
+            });
+
+            var fakeProvider = jasmine.createSpyObj('fakeProvider', ['get']);
+            fakeProvider.get.and.returnValue(dummyPluginInfo);
+
+            function validateReturnedResultFor(values, expectedResult) {
+                return values.reduce(function (promise, value) {
+                    return promise
+                    .then(function () {
+                        platformApi.removePlugin.and.returnValue(Q(value));
+                        return uninstall.uninstallPlatform('android', project, dummy_id, null,
+                            { pluginInfoProvider: fakeProvider, platformVersion: '9.9.9' });
+                    })
+                    .then(function(result) {
+                        expect(!!result).toEqual(expectedResult);
+                    }, function(err) {
+                        expect(err).toBeUndefined();
+                    });
+                }, Q());
+            }
+
+            validateReturnedResultFor([ true, {}, [], 'foo', function(){} ], true)
+            .then(function () {
+                return validateReturnedResultFor([ false, null, undefined, '' ], false);
+            })
+            .fin(done);
+        });
+
+        describe('with dependencies', function() {
+            var emit;
+            beforeEach(function() {
+                emit = spyOn(events, 'emit');
+            });
+            uninstall.uninstallPlatform('android', project, 'A')
+            .then(function(result) {
+                expect(emit).toHaveBeenCalledWith('log', 'Uninstalling 2 dependent plugins.');
+                done();
+            });
+        });
+    });
+
+    describe('failure ', function() {
+        it('Test 004 : should throw if platform is unrecognized', function(done) {
+            uninstall.uninstallPlatform('atari', project, 'SomePlugin')
+            .then(function(result){
+                expect(false).toBe(true);
+                done();
+            }).fail(function err (errMsg) {
+                expect(errMsg.toString()).toContain('atari not supported.');
+                done();
+            });
+        }, 6000);
+
+        it('Test 005 : should throw if plugin is missing', function(done) {
+            uninstall.uninstallPlatform('android', project, 'SomePluginThatDoesntExist')
+            .then(function(result){
+                expect(false).toBe(true);
+                done();
+            }).fail(function err (errMsg) {
+                expect(errMsg.toString()).toContain('Plugin "SomePluginThatDoesntExist" not found. Already uninstalled?');
+                done();
+            });
+        }, 6000);
+    });
+});
+
+describe('uninstallPlugin', function() {
+    var rm, fsWrite, rmstack = [], emit;
+
+    beforeEach(function() {
+        fsWrite = spyOn(fs, 'writeFileSync').and.returnValue(true);
+        rm = spyOn(shell, 'rm').and.callFake(function(f,p) { rmstack.push(p); return true; });
+        rmstack = [];
+        emit = spyOn(events, 'emit');
+        done = false;
+    });
+    describe('with dependencies', function() {
+
+        it('Test 006 : should delete all dependent plugins', function(done) {
+            uninstall.uninstallPlugin('A', plugins_install_dir)
+            .then(function(result) {
+                var del = common.spy.getDeleted(emit);
+                expect(del).toEqual([
+                    'Deleted "C"',
+                    'Deleted "D"',
+                    'Deleted "A"'
+                ]);
+                done();
+            });
+        });
+
+        it('Test 007 : should fail if plugin is a required dependency', function(done) {
+            uninstall.uninstallPlugin('C', plugins_install_dir)
+            .then(function(result){
+                expect(false).toBe(true);
+                done();
+            }).fail(function err (errMsg) {
+                expect(errMsg.toString()).toEqual('"C" is required by (A) and cannot be removed (hint: use -f or --force)');
+                done();
+            });
+        }, 6000);
+
+        it('Test 008 : allow forcefully removing a plugin', function(done) {
+            uninstall.uninstallPlugin('C', plugins_install_dir, {force: true}) 
+            .then(function() {
+                var del = common.spy.getDeleted(emit);
+                expect(del).toEqual(['Deleted "C"']);
+                done();
+            });
+        });
+
+        it('Test 009 : never remove top level plugins if they are a dependency', function(done) {
+            uninstall.uninstallPlugin('A', plugins_install_dir2)
+            .then(function() {
+                var del = common.spy.getDeleted(emit);
+                expect(del).toEqual([
+                    'Deleted "D"',
+                    'Deleted "A"'
+                ]);
+                done();
+            });
+        });
+
+        it('Test 010 : should not remove dependent plugin if it was installed after as top-level', function(done) {
+            uninstall.uninstallPlugin('A', plugins_install_dir3)
+            .then(function() {
+                var del = common.spy.getDeleted(emit);
+                expect(del).toEqual([
+                    'Deleted "D"',
+                    'Deleted "A"'
+                ]);
+                done();
+            });
+        });
+    });
+});
+
+describe('uninstall', function() {
+    var fsWrite, rm;
+
+    beforeEach(function() {
+        fsWrite = spyOn(fs, 'writeFileSync').and.returnValue(true);
+        rm = spyOn(shell, 'rm').and.returnValue(true);
+        done = false;
+    });
+    
+    describe('failure', function() {
+        it('Test 011 : should throw if platform is unrecognized', function(done) {
+            return uninstall('atari', project, 'SomePlugin')
+            .then(function(result){
+                expect(false).toBe(true);
+                done();
+            }).fail(function err (errMsg) {
+                expect(errMsg.toString()).toContain('atari not supported.');
+                done();
+            });
+        }, 6000);
+
+        it('Test 012 : should throw if plugin is missing', function(done) {
+            uninstall('android', project, 'SomePluginThatDoesntExist')
+            .then(function(result){
+                expect(false).toBe(true);
+                done();
+            }).fail(function err (errMsg) {
+                expect(errMsg.toString()).toContain('Plugin "SomePluginThatDoesntExist" not found. Already uninstalled?');
+                done();
+            });
+        }, 6000);
+    });
+});
+
+describe('end', function() {
+    it('Test 013 : end', function(done) {
+        return uninstall('android', project, plugins['org.test.plugins.dummyplugin'])
+        .then(function(){
+            // Fails... A depends on
+            return uninstall('android', project, plugins['C']);
+        }).fail(function(err) {
+            expect(err.stack).toMatch(/The plugin 'C' is required by \(A\), skipping uninstallation./);
+        }).then(function(){
+            // dependencies on C,D ... should this only work with --recursive? prompt user..?
+            return uninstall('android', project, plugins['A']);
+        }).fin(function(err){
+            if(err)
+                plugman.emit('error', err);
+            shell.rm('-rf', project, project2, project3);
+            done();
+        });
+    });
+});


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[03/14] cordova-lib git commit: deprecate anything related to plugin_parser class.

Posted by fi...@apache.org.
deprecate anything related to plugin_parser class.


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

Branch: refs/heads/master
Commit: cc90653d00374713484aea97663283b0d0b9be88
Parents: ce93923
Author: filmaj <ma...@gmail.com>
Authored: Mon Jun 19 23:02:29 2017 -0500
Committer: filmaj <ma...@gmail.com>
Committed: Wed Jun 21 14:57:48 2017 -0500

----------------------------------------------------------------------
 src/cordova/plugin_parser.js | 6 ++++--
 src/cordova/util.js          | 1 +
 2 files changed, 5 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/cc90653d/src/cordova/plugin_parser.js
----------------------------------------------------------------------
diff --git a/src/cordova/plugin_parser.js b/src/cordova/plugin_parser.js
index 4147942..d251557 100644
--- a/src/cordova/plugin_parser.js
+++ b/src/cordova/plugin_parser.js
@@ -18,12 +18,14 @@
 */
 
 var xml = require('cordova-common').xmlHelpers;
+var events = require('cordova-common').events;
 
 /** Deprecated. Use PluginInfo instead. */
-function plugin_parser(xmlPath) {
+function plugin_parser (xmlPath) {
+    events.emit('warn', 'WARNING: Cordova\'s plugin_parser class is now deprecated and will be removed in a future version. Please transition to using the PluginInfo class instead.');
     this.path = xmlPath;
     this.doc = xml.parseElementtreeSync(xmlPath);
-    this.platforms = this.doc.findall('platform').map(function(p) {
+    this.platforms = this.doc.findall('platform').map(function (p) {
         return p.attrib.name;
     });
 }

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/cc90653d/src/cordova/util.js
----------------------------------------------------------------------
diff --git a/src/cordova/util.js b/src/cordova/util.js
index 294dfa5..c9407dc 100644
--- a/src/cordova/util.js
+++ b/src/cordova/util.js
@@ -56,6 +56,7 @@ Object.defineProperty(exports,'libDirectory', {
     }
 });
 
+// TODO: this is no longer used. we should deprecate and remove.
 exports.plugin_parser = require('./plugin_parser');
 exports.raw = {};
 // Alias the plugin_parser method to the raw:{} object above.


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[08/14] cordova-lib git commit: removing unnecessary else from plugin remove. moved plugin-package-parse to plugin subdir and renamed to plugin_spec_parser (to reflect name of module under test).

Posted by fi...@apache.org.
removing unnecessary else from plugin remove. moved plugin-package-parse to plugin subdir and renamed to plugin_spec_parser (to reflect name of module under test).


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

Branch: refs/heads/master
Commit: ba6b57df83d0ea7a170bcd5c595d4556868068ab
Parents: 04c12e2
Author: filmaj <ma...@gmail.com>
Authored: Mon Jun 19 23:56:02 2017 -0500
Committer: filmaj <ma...@gmail.com>
Committed: Wed Jun 21 14:58:14 2017 -0500

----------------------------------------------------------------------
 spec-cordova/plugin/plugin_spec_parser.spec.js | 59 +++++++++++++++++++++
 spec-cordova/plugin_package_parse.spec.js      | 59 ---------------------
 src/cordova/plugin/remove.js                   |  3 --
 3 files changed, 59 insertions(+), 62 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/ba6b57df/spec-cordova/plugin/plugin_spec_parser.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/plugin/plugin_spec_parser.spec.js b/spec-cordova/plugin/plugin_spec_parser.spec.js
new file mode 100644
index 0000000..e5224e4
--- /dev/null
+++ b/spec-cordova/plugin/plugin_spec_parser.spec.js
@@ -0,0 +1,59 @@
+/**
+    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.
+*/
+/* eslint-env jasmine */
+
+var pluginSpec = require('../../src/cordova/plugin/plugin_spec_parser');
+
+describe('methods for parsing npm plugin packages', function () {
+    function checkPluginSpecParsing (testString, scope, id, version) {
+        var parsedSpec = pluginSpec.parse(testString);
+        expect(parsedSpec.scope).toEqual(scope);
+        expect(parsedSpec.id).toEqual(id || testString);
+        expect(parsedSpec.version).toEqual(version);
+        expect(parsedSpec.package).toEqual(scope ? scope + id : id);
+    }
+
+    it('Test 001 : should handle package names with no scope or version', function () {
+        checkPluginSpecParsing('test-plugin', null, 'test-plugin', null);
+    });
+    it('Test 002 : should handle package names with a version', function () {
+        checkPluginSpecParsing('test-plugin@1.0.0', null, 'test-plugin', '1.0.0');
+        checkPluginSpecParsing('test-plugin@latest', null, 'test-plugin', 'latest');
+    });
+    it('Test 003 : should handle package names with a scope', function () {
+        checkPluginSpecParsing('@test/test-plugin', '@test/', 'test-plugin', null);
+    });
+    it('Test 004 : should handle package names with a scope and a version', function () {
+        checkPluginSpecParsing('@test/test-plugin@1.0.0', '@test/', 'test-plugin', '1.0.0');
+        checkPluginSpecParsing('@test/test-plugin@latest', '@test/', 'test-plugin', 'latest');
+    });
+    it('Test 005 : should handle invalid package specs', function () {
+        checkPluginSpecParsing('@nonsense', null, null, null);
+        checkPluginSpecParsing('@/nonsense', null, null, null);
+        checkPluginSpecParsing('@', null, null, null);
+        checkPluginSpecParsing('@nonsense@latest', null, null, null);
+        checkPluginSpecParsing('@/@', null, null, null);
+        checkPluginSpecParsing('/', null, null, null);
+        checkPluginSpecParsing('../../@directory', null, null, null);
+        checkPluginSpecParsing('@directory/../@directory', null, null, null);
+        checkPluginSpecParsing('./directory', null, null, null);
+        checkPluginSpecParsing('directory/directory', null, null, null);
+        checkPluginSpecParsing('http://cordova.apache.org', null, null, null);
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/ba6b57df/spec-cordova/plugin_package_parse.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/plugin_package_parse.spec.js b/spec-cordova/plugin_package_parse.spec.js
deleted file mode 100644
index 25a75d4..0000000
--- a/spec-cordova/plugin_package_parse.spec.js
+++ /dev/null
@@ -1,59 +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.
-*/
-/* eslint-env jasmine */
-
-var pluginSpec = require('../src/cordova/plugin/plugin_spec_parser');
-
-describe('methods for parsing npm plugin packages', function () {
-    function checkPluginSpecParsing (testString, scope, id, version) {
-        var parsedSpec = pluginSpec.parse(testString);
-        expect(parsedSpec.scope).toEqual(scope);
-        expect(parsedSpec.id).toEqual(id || testString);
-        expect(parsedSpec.version).toEqual(version);
-        expect(parsedSpec.package).toEqual(scope ? scope + id : id);
-    }
-
-    it('Test 001 : should handle package names with no scope or version', function () {
-        checkPluginSpecParsing('test-plugin', null, 'test-plugin', null);
-    });
-    it('Test 002 : should handle package names with a version', function () {
-        checkPluginSpecParsing('test-plugin@1.0.0', null, 'test-plugin', '1.0.0');
-        checkPluginSpecParsing('test-plugin@latest', null, 'test-plugin', 'latest');
-    });
-    it('Test 003 : should handle package names with a scope', function () {
-        checkPluginSpecParsing('@test/test-plugin', '@test/', 'test-plugin', null);
-    });
-    it('Test 004 : should handle package names with a scope and a version', function () {
-        checkPluginSpecParsing('@test/test-plugin@1.0.0', '@test/', 'test-plugin', '1.0.0');
-        checkPluginSpecParsing('@test/test-plugin@latest', '@test/', 'test-plugin', 'latest');
-    });
-    it('Test 005 : should handle invalid package specs', function () {
-        checkPluginSpecParsing('@nonsense', null, null, null);
-        checkPluginSpecParsing('@/nonsense', null, null, null);
-        checkPluginSpecParsing('@', null, null, null);
-        checkPluginSpecParsing('@nonsense@latest', null, null, null);
-        checkPluginSpecParsing('@/@', null, null, null);
-        checkPluginSpecParsing('/', null, null, null);
-        checkPluginSpecParsing('../../@directory', null, null, null);
-        checkPluginSpecParsing('@directory/../@directory', null, null, null);
-        checkPluginSpecParsing('./directory', null, null, null);
-        checkPluginSpecParsing('directory/directory', null, null, null);
-        checkPluginSpecParsing('http://cordova.apache.org', null, null, null);
-    });
-});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/ba6b57df/src/cordova/plugin/remove.js
----------------------------------------------------------------------
diff --git a/src/cordova/plugin/remove.js b/src/cordova/plugin/remove.js
index 9579fee..ca27601 100644
--- a/src/cordova/plugin/remove.js
+++ b/src/cordova/plugin/remove.js
@@ -90,9 +90,6 @@ function remove (projectRoot, targets, hooksRunner, opts) {
                     if (fs.existsSync(pkgJsonPath)) {
                         // delete any previous caches of require(package.json)
                         pkgJson = cordova_util.requireNoCache(pkgJsonPath);
-                    } else {
-                        // TODO: well this comment below seems kinda importnat
-                        // Create package.json in cordova@7
                     }
                     // If package.json exists and contains a specified plugin in cordova['plugins'], it will be removed
                     if (pkgJson !== undefined && pkgJson.cordova !== undefined && pkgJson.cordova.plugins !== undefined) {


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[12/14] cordova-lib git commit: pending tests for plugin specs. small refactor tweaks, mostly TODOs being dropped. remove old platform-add tests. fixes for jshint.

Posted by fi...@apache.org.
pending tests for plugin specs. small refactor tweaks, mostly TODOs
being dropped. remove old platform-add tests. fixes for jshint.


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

Branch: refs/heads/master
Commit: 2daef359c7a6e7f644d30ec688d7259f4371f731
Parents: e314d07
Author: filmaj <ma...@gmail.com>
Authored: Tue Jun 20 18:36:47 2017 -0500
Committer: filmaj <ma...@gmail.com>
Committed: Wed Jun 21 14:58:29 2017 -0500

----------------------------------------------------------------------
 spec-cordova/platform/addHelper.spec.js | 466 ---------------------------
 spec-cordova/platform/index.spec.js     |   2 +
 spec-cordova/plugin/add.spec.js         |  78 +++++
 spec-cordova/plugin/search.spec.js      |  28 ++
 spec-cordova/plugin/util.spec.js        |  31 ++
 src/cordova/platform/addHelper.js       |   3 +-
 src/cordova/plugin/add.js               |  15 +-
 src/cordova/plugin/index.js             |   2 +-
 8 files changed, 151 insertions(+), 474 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/2daef359/spec-cordova/platform/addHelper.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/platform/addHelper.spec.js b/spec-cordova/platform/addHelper.spec.js
index 0617fb0..8218d91 100644
--- a/spec-cordova/platform/addHelper.spec.js
+++ b/spec-cordova/platform/addHelper.spec.js
@@ -16,15 +16,6 @@
 */
 /* eslint-env jasmine */
 
-var path = require('path');
-var fs = require('fs');
-var shell = require('shelljs');
-var rewire = require('rewire');
-var platform_addHelper = rewire('../../src/cordova/platform/addHelper');
-var cordova_util = require('../../src/cordova/util');
-
-var config_xml_path = 'spec-cordova/fixtures/config.xml';
-
 describe('cordova/platform/addHelper', function () {
     describe('error/warning conditions', function () {
         it('should require specifying at least one platform');
@@ -55,461 +46,4 @@ describe('cordova/platform/addHelper', function () {
          * - fire after_platform_add/update hook
          */
     });
-    /*
-    var projectRoot = path.join('some', 'path'),
-        windowsPath = path.join(projectRoot,'cordova-windows'),
-        configParserRevert,
-        fetchRevert,
-        downloadPlatformRevert,
-        pkgJson = {},
-        configEngines = [],
-        fetchArgs = [];
-
-    // Mock HooksRunner
-    var hooksRunnerMock = {
-        fire: function () {
-            return Q();
-        }
-    };
-
-    // Mock Platform Api
-    function PlatformApiMock() {}
-    PlatformApiMock.createPlatform = function() {
-        return Q();
-    };
-    PlatformApiMock.updatePlatform = function() {
-        return Q();
-    };
-
-    // Mock cordova-fetch
-    var fetchMock = function(target) {
-        fetchArgs.push(target);
-        //return the basename of either the target, url or local path 
-        return Q(path.basename(target));
-    };
-
-    // Mock ConfigParser
-    function ConfigParserMock() {}
-    ConfigParserMock.prototype = {
-        write: function() {
-            //do nothing
-        },
-        addEngine: function(plat, spec) {
-            //add engine to configEngines
-            configEngines.push({'name': plat, 'spec': spec});
-        },
-        removeEngine: function(plat) {
-            //delete engine from configEngines
-            configEngines.forEach(function(item, index) {
-                if(item.name === plat){
-                    delete configEngines[index]; 
-                }
-            });
-        },
-        getEngines: function() {
-            return configEngines;
-        }
-    };
-
-    function getPlatformDetailsFromDirMock(dir, platform) {
-        var ver;
-        var parts = dir.split('@');
-        //attempt to derive version from dir/target
-        //eg dir = android@~6.1.1 || atari@1.0.0
-        if(parts.length > 1) {
-            ver = parts[1] || parts[0];
-            //remove ~ or ^ since the real function version wouldn't have that
-            if(ver[0] === '~' || ver[0] === '^') {
-                ver = ver.slice(1);
-            }
-        }
-        // not a perfect representation of the real function, but good for testing
-		return Q({
-            'libDir':'Api.js',
-            'platform':platform || path.basename(dir),
-            'version':ver || 'n/a'
-        });
-    }
-
-    beforeEach(function () {
-        spyOn(cordova_util, 'projectConfig').and.returnValue(config_xml_path);
-        spyOn(shell, 'mkdir').and.returnValue(true);
-
-        configParserRevert = platform_addHelper.__set__('ConfigParser', ConfigParserMock);
-        fetchRevert = platform_addHelper.__set__('fetch', fetchMock);
-        spyOn(platform, 'getPlatformDetailsFromDir').and.callFake(getPlatformDetailsFromDirMock);
-        spyOn(prepare, 'preparePlatforms').and.returnValue(Q());
-        spyOn(cordova, 'prepare').and.returnValue(Q());
-        spyOn(platformMetadata, 'save').and.returnValue(true);
-        spyOn(cordova_util, 'getPlatformApiFunction').and.returnValue(PlatformApiMock);
-        // writes to package.json
-        spyOn(fs, 'writeFileSync').and.callFake(function (dest, pkgJ) {
-            pkgJson = JSON.parse(pkgJ);
-            return true;
-        });
-
-        // return true for windows local path target
-        spyOn(cordova_util, 'isDirectory').and.callFake(function (filePath) {
-            if (filePath.indexOf(windowsPath) !== -1) {
-                return true;
-            } else {
-                return false;
-            }
-        });
-
-        spyOn(lazy_load, 'git_clone').and.callFake(function (git_url, branch) {
-            return Q(path.basename(git_url));
-        });
-        spyOn(lazy_load, 'based_on_config').and.callFake(function (projRoot, target) {
-            return Q(target);
-        });
-    });
-
-    afterEach(function () {
-        configParserRevert();
-        fetchRevert();
-        if (downloadPlatformRevert) {
-            downloadPlatformRevert();
-            downloadPlatformRevert = undefined;
-        }
-        pkgJson = {};
-        configEngines = [];
-        fetchArgs = [];
-    });
-    */
-
-    xdescribe('old add tests', function () {
-        it('should succeed with fetch, save and package.json. Tests npm package, gitURL, local path and non core platforms with spec as targets', function(done) {
-            //spy for package.json to exist
-            spyOn(fs,'existsSync').and.callFake(function(filePath) {
-                if(path.basename(filePath) === 'package.json') {
-                    return true;
-                } else {
-                    return false;
-                }
-            });
-            
-            //require packge.json object
-            spyOn(cordova_util, 'requireNoCache').and.returnValue(pkgJson);
-            
-            platform.add(hooksRunnerMock, projectRoot, ['android', 'https://github.com/apache/cordova-ios', windowsPath, 'atari@1.0.0'], {'fetch': true, 'save': true})
-            .then(function() {
-                expect(cordova_util.projectConfig.calls.count()).toEqual(1);
-                expect(shell.mkdir.calls.count()).toEqual(1);
-                expect(platform.getPlatformDetailsFromDir.calls.count()).toEqual(4);
-                expect(cordova_util.getPlatformApiFunction.calls.count()).toEqual(4);
-                expect(cordova.prepare.calls.count()).toEqual(4);
-                expect(prepare.preparePlatforms.calls.count()).toEqual(4);
-                expect(platformMetadata.save.calls.count()).toEqual(4);
-                expect(lazy_load.git_clone.calls.count()).toEqual(0);
-                expect(lazy_load.based_on_config.calls.count()).toEqual(0);
-                //expect correct arugments to be passed to cordova-fetch
-                expect(fetchArgs[0]).toEqual('cordova-android@'+pinnedAndroidVer);
-                expect(fetchArgs[1]).toEqual('https://github.com/apache/cordova-ios');
-                expect(fetchArgs[2]).toContain('cordova-windows');
-                expect(fetchArgs[3]).toEqual('atari@1.0.0'); 
-                //test pkgJson is being built correctly
-                expect(fs.writeFileSync.calls.count()).toEqual(1);
-                expect(pkgJson.cordova).toBeDefined();
-                expect(pkgJson.cordova.platforms).toEqual([ 'android', 'atari', 'cordova-ios', 'cordova-windows' ]);
-                expect(cordova_util.requireNoCache.calls.count()).toEqual(5);
-                //test cfg.engines code is being run with correct arugments
-                expect(configEngines.length).toEqual(4);
-                expect(configEngines).toEqual(
-                    [ { name: 'android', spec: pinnedAndroidVer },
-                    { name: 'cordova-ios',
-                    spec: 'https://github.com/apache/cordova-ios' },
-                    { name: 'cordova-windows',
-                    spec: windowsPath },
-                    { name: 'atari', spec: '~1.0.0' } ]);
-            }).fail(function(e) {
-                expect(e).toBeUndefined();
-            })
-            .fin(done);
-        });
-
-        it('should succeed with fetch, save and no package.json. Tests npm package, gitURL, local path and non core platforms with spec as targets', function(done) {
-            //spy for package.json to not exist
-            spyOn(fs,'existsSync').and.returnValue(false);
-            
-            //require packge.json object
-            spyOn(cordova_util, 'requireNoCache');
-     
-            platform.add(hooksRunnerMock, projectRoot, ['android', 'https://github.com/apache/cordova-ios', windowsPath, 'atari@1.0.0'], {'fetch': true, 'save': true})
-            .then(function() {
-                expect(cordova_util.projectConfig.calls.count()).toEqual(1);
-                expect(shell.mkdir.calls.count()).toEqual(1);
-                expect(platform.getPlatformDetailsFromDir.calls.count()).toEqual(4);
-                expect(cordova_util.getPlatformApiFunction.calls.count()).toEqual(4);
-                expect(cordova.prepare.calls.count()).toEqual(4);
-                expect(prepare.preparePlatforms.calls.count()).toEqual(4);
-                expect(platformMetadata.save.calls.count()).toEqual(4);
-                expect(lazy_load.git_clone.calls.count()).toEqual(0);
-                expect(lazy_load.based_on_config.calls.count()).toEqual(0);
-                //expect correct arugments to be passed to cordova-fetch
-                expect(fetchArgs[0]).toEqual('cordova-android@'+pinnedAndroidVer);
-                expect(fetchArgs[1]).toEqual('https://github.com/apache/cordova-ios');
-                expect(fetchArgs[2]).toContain('cordova-windows');
-                expect(fetchArgs[3]).toEqual('atari@1.0.0');
-                //test pkgJson releated commands aren't being called
-                expect(fs.writeFileSync.calls.count()).toEqual(0);
-                expect(pkgJson.cordova).toBeUndefined();
-                expect(cordova_util.requireNoCache.calls.count()).toEqual(0);
-                //test cfg.engines code is being run with correct arugments
-                expect(configEngines.length).toEqual(4);
-                expect(configEngines).toEqual(
-                    [ { name: 'android', spec: pinnedAndroidVer },
-                    { name: 'cordova-ios',
-                    spec: 'https://github.com/apache/cordova-ios' },
-                    { name: 'cordova-windows',
-                    spec: windowsPath },
-                    { name: 'atari', spec: '~1.0.0' } ]);
-            }).fail(function(e) {
-                expect(e).toBeUndefined();
-            })
-            .fin(done);
-        });
-
-        //no need to worry about packagae.json in this case
-        it('should succeed with fetch, no save. Tests npm package, gitURL, local path and non core platforms with spec as targets', function(done) {
-            //spy for package.json to not exist
-            spyOn(fs,'existsSync').and.returnValue(false);
-     
-            platform.add(hooksRunnerMock, projectRoot, ['android', 'https://github.com/apache/cordova-ios', windowsPath, 'atari@1.0.0'], {'fetch': true, 'save': false})
-            .then(function() {
-                expect(cordova_util.projectConfig.calls.count()).toEqual(1);
-                expect(shell.mkdir.calls.count()).toEqual(1);
-                expect(platform.getPlatformDetailsFromDir.calls.count()).toEqual(4);
-                expect(cordova_util.getPlatformApiFunction.calls.count()).toEqual(4);
-                expect(cordova.prepare.calls.count()).toEqual(4);
-                expect(prepare.preparePlatforms.calls.count()).toEqual(4);
-                expect(platformMetadata.save.calls.count()).toEqual(4);
-                expect(lazy_load.git_clone.calls.count()).toEqual(0);
-                expect(lazy_load.based_on_config.calls.count()).toEqual(0);
-                //expect correct arugments to be passed to cordova-fetch
-                expect(fetchArgs[0]).toEqual('cordova-android@'+pinnedAndroidVer);
-                expect(fetchArgs[1]).toEqual('https://github.com/apache/cordova-ios');
-                expect(fetchArgs[2]).toContain('cordova-windows');
-                expect(fetchArgs[3]).toEqual('atari@1.0.0'); 
-                //test pkgJson releated commands aren't being called
-                expect(fs.writeFileSync.calls.count()).toEqual(0);
-                expect(pkgJson.cordova).toBeUndefined();
-                //test cfg.engines code is being run with correct arugments
-                expect(configEngines.length).toEqual(0);
-            }).fail(function(e) {
-                expect(e).toBeUndefined();
-            })
-            .fin(done);
-        });
-
-        it('should succeed with save, package.json and no fetch. Tests npm package, gitURL, local path and non core platforms with spec as targets', function(done) {
-            //spy for package.json to exist
-            spyOn(fs,'existsSync').and.callFake(function(filePath) {
-                if(path.basename(filePath) === 'package.json') {
-                    return true;
-                } else {
-                    return false;
-                }
-            });
-            
-            //require packge.json object
-            spyOn(cordova_util, 'requireNoCache').and.returnValue(pkgJson);
-            
-            platform.add(hooksRunnerMock, projectRoot, ['android', 'https://github.com/apache/cordova-ios', windowsPath, 'atari@1.0.0'], {'fetch': false, 'save': true})
-            .then(function() {
-                expect(cordova_util.projectConfig.calls.count()).toEqual(1);
-                expect(shell.mkdir.calls.count()).toEqual(1);
-                expect(platform.getPlatformDetailsFromDir.calls.count()).toEqual(4);
-                expect(cordova_util.getPlatformApiFunction.calls.count()).toEqual(4);
-                expect(cordova.prepare.calls.count()).toEqual(4);
-                expect(prepare.preparePlatforms.calls.count()).toEqual(4);
-                expect(platformMetadata.save.calls.count()).toEqual(4);
-                expect(lazy_load.git_clone.calls.count()).toEqual(1);
-                expect(lazy_load.based_on_config.calls.count()).toEqual(2);
-                //expect correct arguments to be passed to cordova-fetch
-                expect(fetchArgs.length).toEqual(0);
-                //test pkgJson is being built correctly
-                expect(fs.writeFileSync.calls.count()).toEqual(1);
-                expect(pkgJson.cordova).toBeDefined();
-                expect(pkgJson.cordova.platforms).toEqual([ 'android', 'atari', 'cordova-ios', 'cordova-windows' ]);
-                expect(cordova_util.requireNoCache.calls.count()).toEqual(5);
-                //test cfg.engines code is being run with correct arugments
-                expect(configEngines.length).toEqual(4);
-                expect(configEngines).toEqual(
-                    [ { name: 'android', spec: pinnedAndroidVer },
-                    { name: 'cordova-ios',
-                    spec: 'https://github.com/apache/cordova-ios' },
-                    { name: 'cordova-windows',
-                    spec: windowsPath },
-                    { name: 'atari', spec: '~1.0.0' } ]);
-            }).fail(function(e) {
-                expect(e).toBeUndefined();
-            })
-            .fin(done);
-        });
-
-        it('should succeed with save, no package.json and no fetch. Tests npm package, gitURL, local path and non core platforms with spec as targets', function(done) {
-            //spy for package.json to not exist
-            spyOn(fs,'existsSync').and.returnValue(false);
-                    
-            //require packge.json object
-            spyOn(cordova_util, 'requireNoCache').and.returnValue(pkgJson);
-            
-            platform.add(hooksRunnerMock, projectRoot, ['android', 'https://github.com/apache/cordova-ios', windowsPath, 'atari@1.0.0'], {'fetch': false, 'save': true})
-            .then(function() {
-                expect(cordova_util.projectConfig.calls.count()).toEqual(1);
-                expect(shell.mkdir.calls.count()).toEqual(1);
-                expect(platform.getPlatformDetailsFromDir.calls.count()).toEqual(4);
-                expect(cordova_util.getPlatformApiFunction.calls.count()).toEqual(4);
-                expect(cordova.prepare.calls.count()).toEqual(4);
-                expect(prepare.preparePlatforms.calls.count()).toEqual(4);
-                expect(platformMetadata.save.calls.count()).toEqual(4);
-                expect(lazy_load.git_clone.calls.count()).toEqual(1);
-                expect(lazy_load.based_on_config.calls.count()).toEqual(2);
-                //expect correct arguments to be passed to cordova-fetch
-                expect(fetchArgs.length).toEqual(0);
-                //test pkgJson releated commands aren't being called
-                expect(fs.writeFileSync.calls.count()).toEqual(0);
-                expect(pkgJson.cordova).toBeUndefined();
-                expect(cordova_util.requireNoCache.calls.count()).toEqual(0);
-                //test cfg.engines code is being run with correct arugments
-                expect(configEngines.length).toEqual(4);
-                expect(configEngines).toEqual(
-                    [ { name: 'android', spec: pinnedAndroidVer },
-                    { name: 'cordova-ios',
-                    spec: 'https://github.com/apache/cordova-ios' },
-                    { name: 'cordova-windows',
-                    spec: windowsPath },
-                    { name: 'atari', spec: '~1.0.0' } ]);
-            }).fail(function(e) {
-                expect(e).toBeUndefined();
-            })
-            .fin(done);
-        });
-
-        //no need to worry about packagae.json in this case
-        it('should succeed with no fetch, no save. Tests npm package, gitURL, local path and non core platforms with spec as targets', function(done) {
-            //spy for package.json to not exist
-            spyOn(fs,'existsSync').and.returnValue(false);
-     
-            platform.add(hooksRunnerMock, projectRoot, ['android', 'https://github.com/apache/cordova-ios', windowsPath, 'atari@1.0.0'], {'fetch': false, 'save': false})
-            .then(function() {
-                expect(cordova_util.projectConfig.calls.count()).toEqual(1);
-                expect(shell.mkdir.calls.count()).toEqual(1);
-                expect(platform.getPlatformDetailsFromDir.calls.count()).toEqual(4);
-                expect(cordova_util.getPlatformApiFunction.calls.count()).toEqual(4);
-                expect(cordova.prepare.calls.count()).toEqual(4);
-                expect(prepare.preparePlatforms.calls.count()).toEqual(4);
-                expect(platformMetadata.save.calls.count()).toEqual(4);
-                expect(lazy_load.git_clone.calls.count()).toEqual(1);
-                expect(lazy_load.based_on_config.calls.count()).toEqual(2);
-                //test pkgJson releated commands aren't being called
-                expect(fs.writeFileSync.calls.count()).toEqual(0);
-                expect(pkgJson.cordova).toBeUndefined();
-                //test cfg.engines code is being run with correct arugments
-                expect(configEngines.length).toEqual(0);
-            }).fail(function(e) {
-                expect(e).toBeUndefined();
-            })
-            .fin(done);
-        });
-
-        it('should succeed with fetch, save and package.json. Gets android spec from package.json', function(done) {
-            //spy for package.json to exist
-            spyOn(fs,'existsSync').and.callFake(function(filePath) {
-                if(path.basename(filePath) === 'package.json') {
-                    return true;
-                } else {
-                    return false;
-                }
-            });
-            
-            pkgJson = {
-                'dependencies': {
-                    'cordova-android': '^6.2.1'
-                }
-            };
-            //require packge.json object
-            spyOn(cordova_util, 'requireNoCache').and.returnValue(pkgJson);
-            
-            platform.add(hooksRunnerMock, projectRoot, ['android'], {'fetch': true, 'save': true})
-            .then(function() {
-                expect(cordova_util.projectConfig.calls.count()).toEqual(1);
-                expect(shell.mkdir.calls.count()).toEqual(1);
-                expect(platform.getPlatformDetailsFromDir.calls.count()).toEqual(1);
-                expect(cordova_util.getPlatformApiFunction.calls.count()).toEqual(1);
-                expect(cordova.prepare.calls.count()).toEqual(1);
-                expect(prepare.preparePlatforms.calls.count()).toEqual(1);
-                expect(platformMetadata.save.calls.count()).toEqual(1);
-                expect(lazy_load.git_clone.calls.count()).toEqual(0);
-                expect(lazy_load.based_on_config.calls.count()).toEqual(0);
-                //expect correct arugments to be passed to cordova-fetch
-                expect(fetchArgs).toEqual([ 'cordova-android@^6.2.1']);
-                //test pkgJson is being built correctly
-                expect(fs.writeFileSync.calls.count()).toEqual(1);
-                expect(pkgJson.cordova).toBeDefined();
-                expect(pkgJson.cordova.platforms).toEqual([ 'android']);
-                expect(cordova_util.requireNoCache.calls.count()).toEqual(2);
-                //test cfg.engines code is being run with correct arugments
-                expect(configEngines.length).toEqual(1);
-                expect(configEngines).toEqual(
-                    [ { name: 'android', spec: '~6.2.1'}]);
-            }).fail(function(e) {
-                expect(e).toBeUndefined();
-            })
-            .fin(done);
-        });
-
-        it('throws if platform already added', function (done) {
-            // spy for android to exist
-            spyOn(fs, 'existsSync').and.callFake(function (filePath) {
-                var name = path.basename(filePath);
-                if (['android'].indexOf(name) > -1) {
-                    return true;
-                } else {
-                    return false;
-                }
-            });
-            downloadPlatformRevert = platform_addHelper.__set__('module.exports.downloadPlatform', function(proj, plat, spec, opts) {
-                console.log('calling fake');
-                return Q({
-                    platform: 'android'
-                });
-            });
-
-            platform_addHelper(hooksRunnerMock, projectRoot, ['android'], {'fetch': true, 'save': true})
-            .then(false)
-            .fail(function(e) {
-                expect(e.message).toBe('Platform android already added.');
-            })
-            .fin(done);
-        });
-
-        it('throws if the target list is undefined', function (done) {
-            var targets; // = undefined;
-            platform.add(hooksRunnerMock, projectRoot, targets, {})
-            .then(false)
-            .fail(function (error) {
-                expect(error.message).toBe('No platform specified. Please specify a platform to add. See `cordova platform list`.');
-            }).fin(done);
-        });
-
-        it('throws if the target list is null', function (done) {
-            var targets = null; // = undefined;
-            platform.add(hooksRunnerMock, projectRoot, targets, {})
-            .then(false)
-            .fail(function (error) {
-                expect(error.message).toBe('No platform specified. Please specify a platform to add. See `cordova platform list`.');
-            }).fin(done);
-        });
-
-        it('throws if the target list is empty', function (done) {
-            var targets = []; // = undefined;
-            platform.add(hooksRunnerMock, projectRoot, targets, {})
-            .then(false)
-            .fail(function (error) {
-                expect(error.message).toBe('No platform specified. Please specify a platform to add. See `cordova platform list`.');
-            }).fin(done);
-        });
-    });
 });

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/2daef359/spec-cordova/platform/index.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/platform/index.spec.js b/spec-cordova/platform/index.spec.js
index f05ca85..a39d43e 100644
--- a/spec-cordova/platform/index.spec.js
+++ b/spec-cordova/platform/index.spec.js
@@ -14,7 +14,9 @@
     specific language governing permissions and limitations
     under the License.
 */
+// TODO: remove these once eslint lands
 /* eslint-env jasmine */
+/* globals fail */
 
 var rewire = require('rewire');
 var platform = rewire('../../src/cordova/platform');

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/2daef359/spec-cordova/plugin/add.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/plugin/add.spec.js b/spec-cordova/plugin/add.spec.js
new file mode 100644
index 0000000..4860578
--- /dev/null
+++ b/spec-cordova/plugin/add.spec.js
@@ -0,0 +1,78 @@
+/**
+    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.
+*/
+/* eslint-env jasmine */
+
+var add = require('../../src/cordova/plugin/add');
+
+describe('cordova/plugin/add', function () {
+    describe('main method', function () {
+        describe('error/warning conditions', function () {
+            it('should error out if at least one plugin is not specified');
+            it('should error out if any mandatory plugin variables are not provided');
+        });
+        describe('happy path', function () {
+            it('should fire the before_plugin_add hook');
+            it('should determine where to fetch a plugin from using determinePluginTarget and invoke plugman.fetch with the resolved target');
+            it('should retrieve any variables for the plugin from config.xml and provide them as cli variables only when the cli variables are not already provided via options');
+            it('should invoke plugman.install for each platform added to the project');
+            it('should save plugin variable information to package.json file (if exists)');
+            it('should overwrite plugin information in config.xml after a successful installation');
+            it('should invoke preparePlatforms if plugman.install returned a truthy value');
+            it('should fire after_plugin_add hook');
+        });
+    });
+    describe('determinePluginTarget helper method', function () {
+        it('should return the target directly if the target is pluginSpec-parseable');
+        it('should return the target directly if the target is a URL');
+        it('should return the target directly if the target is a directory');
+        it('should retrieve plugin version from package.json (if exists)');
+        it('should retrieve plugin version from config.xml as a last resort');
+        describe('with fetchOptions.save=true, writing out plugin versions retrieved from config files (either config.xml or package.json)', function () {
+            it('should write out URL-as-plugin-spec to package.json');
+            it('should write out directory-as-plugin-spec to package.json');
+        });
+        it('should return plugin version retrieved from package.json or config.xml if it is a URL');
+        it('should return plugin version retrieved from package.json or config.xml if it is a directory');
+        it('should return plugin version retrieved from package.json or config.xml if it has a scope');
+        it('should return plugin-id@plugin-version if retrieved from package.json or config.xml ');
+        describe('with no version inferred from config files or provided plugin target', function () {
+            describe('when searchpath or noregistry flag is provided', function () {
+                it('should end up just returning the target passed in');
+            });
+            describe('when registry/npm is to be used (neither searchpath nor noregistry flag is provided)', function () {
+                it('should retrieve plugin info via registry.info');
+                it('should feed registry.info plugin information into getFetchVersion');
+                it('should return the target as plugin-id@fetched-version');
+            });
+        });
+    });
+    // TODO: reorganize these tests once the logic here is understood! -filmaj
+    // TODO: rewrite the tests from integration-tests/plugin_fetch.spec.js to here.
+    describe('TODO! unit tests to replace integration-tests/plugin_fetch.spec.js', function () {
+        describe('getFetchVersion helper method', function () {
+            it('should resolve with null if plugin info does not contain engines and engines.cordovaDependencies properties');
+            it('should retrieve platform version info via getInstalledPlatformsWithVersions and feed that information into determinePluginVersionToFetch');
+        });
+        describe('determinePluginVersionToFetch helper method', function () {
+            it('should return null if no valid semver versions exist and no upperbound constraints were placed');
+        });
+        describe('getFailedRequirements helper method', function () {
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/2daef359/spec-cordova/plugin/search.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/plugin/search.spec.js b/spec-cordova/plugin/search.spec.js
new file mode 100644
index 0000000..b940eeb
--- /dev/null
+++ b/spec-cordova/plugin/search.spec.js
@@ -0,0 +1,28 @@
+/**
+    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.
+*/
+/* eslint-env jasmine */
+
+var search = require('../../src/cordova/plugin/search');
+
+describe('cordova/plugin/search', function () {
+    it('should fire the before_plugin_search hook');
+    it('should open a link to cordova.apache.org/plugins if no plugins are provided as parameter');
+    it('should open a link to cordova.apache.org/plugins, providing the plugins passed in as a query-string parameter');
+    it('should fire the after_plugin_search hook');
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/2daef359/spec-cordova/plugin/util.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/plugin/util.spec.js b/spec-cordova/plugin/util.spec.js
new file mode 100644
index 0000000..f967706
--- /dev/null
+++ b/spec-cordova/plugin/util.spec.js
@@ -0,0 +1,31 @@
+/**
+    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.
+*/
+/* eslint-env jasmine */
+
+var plugin_util = require('../../src/cordova/plugin/util');
+
+describe('cordova/plugin/util', function () {
+    describe('getInstalledPlugins helper method', function () {
+        it('should return result of PluginInfoProvider\'s getAllWithinSearchPath method');
+    });
+    describe('saveToConfigXmlOn helper method', function () {
+        it('should return true if config.json\'s autosave option is truthy');
+        it('should return true if options passed in have a truthy save property');
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/2daef359/src/cordova/platform/addHelper.js
----------------------------------------------------------------------
diff --git a/src/cordova/platform/addHelper.js b/src/cordova/platform/addHelper.js
index 4d999c1..4161900 100644
--- a/src/cordova/platform/addHelper.js
+++ b/src/cordova/platform/addHelper.js
@@ -185,8 +185,7 @@ function addHelper (cmd, hooksRunner, projectRoot, targets, opts) {
                 events.emit('log', (cmd === 'add' ? 'Adding ' : 'Updating ') + platform + ' project...');
                 var PlatformApi = cordova_util.getPlatformApiFunction(platDetails.libDir, platform);
                 var destination = path.resolve(projectRoot, 'platforms', platform);
-                var promise = cmd === 'add'
-                    ? PlatformApi.createPlatform.bind(null, destination, cfg, options, events)
+                var promise = cmd === 'add' ? PlatformApi.createPlatform.bind(null, destination, cfg, options, events)
                     : PlatformApi.updatePlatform.bind(null, destination, options, events);
                 // TODO: if we return the promise immediately, can we not unindent the promise .then()s by one indent?
                 return promise()

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/2daef359/src/cordova/plugin/add.js
----------------------------------------------------------------------
diff --git a/src/cordova/plugin/add.js b/src/cordova/plugin/add.js
index ca12612..3b801d0 100644
--- a/src/cordova/plugin/add.js
+++ b/src/cordova/plugin/add.js
@@ -34,6 +34,7 @@ var Q = require('Q');
 var path = require('path');
 var fs = require('fs');
 var semver = require('semver');
+var url = require('url');
 
 module.exports = add;
 module.exports.determinePluginTarget = determinePluginTarget;
@@ -45,8 +46,8 @@ module.exports.getFailedRequirements = getFailedRequirements;
 module.exports.findVersion = findVersion;
 module.exports.listUnmetRequirements = listUnmetRequirements;
 
-function add (projectRoot, targets, hooksRunner, opts) {
-    if (!targets || !targets.length) {
+function add (projectRoot, hooksRunner, opts) {
+    if (!opts.plugins || !opts.plugins.length) {
         return Q.reject(new CordovaError('No plugin specified. Please specify a plugin to add. See `' + cordova_util.binname + ' plugin search`.'));
     }
 
@@ -289,7 +290,7 @@ function determinePluginTarget (projectRoot, cfg, target, fetchOptions) {
     // their package.json
     var shouldUseNpmInfo = !fetchOptions.searchpath && !fetchOptions.noregistry;
 
-    events.emit('verbose', 'No version for ' + parsedSpec.package + ' saved in config.xml');
+    events.emit('verbose', 'No version for ' + parsedSpec.package + ' saved in config.xml or package.json');
     if (shouldUseNpmInfo) {
         events.emit('verbose', 'Attempting to use npm info for ' + parsedSpec.package + ' to choose a compatible release');
     } else {
@@ -307,7 +308,6 @@ function determinePluginTarget (projectRoot, cfg, target, fetchOptions) {
 }
 
 function parseSource (target, opts) {
-    var url = require('url');
     var uri = url.parse(target);
     if (uri.protocol && uri.protocol !== 'file:' && uri.protocol[1] !== ':' && !target.match(/^\w+:\\/)) {
         return target;
@@ -387,6 +387,8 @@ var UPPER_BOUND_REGEX = /^<\d+\.\d+\.\d+$/;
  *      '3.0.0' : { 'cordova-ios': '>5.0.0' }
  *  }
  *
+ * TODO: provide a better function description once logic is groked
+ * TODO: update comment below once tests are rewritten/moved around.
  * See cordova-spec/plugin_fetch.spec.js for test cases and examples
  */
 function determinePluginVersionToFetch (pluginInfo, pluginMap, platformMap, cordovaVersion) {
@@ -402,8 +404,11 @@ function determinePluginVersionToFetch (pluginInfo, pluginMap, platformMap, cord
     var upperBoundRange = null;
     var upperBoundExists = false;
 
+    // TODO: lots of 'versions' being thrown around in this function: cordova version,
+    // platform version, plugin version. The below for loop: what version is it
+    // iterating over? plugin version? please clarify the variable name.
     for (var version in engine) {
-        if (semver.valid(semver.clean(version)) && !semver.gt(version, latest)) {
+        if (semver.valid(semver.clean(version)) && semver.lte(version, latest)) {
             versions.push(version);
         } else {
             // Check if this is an upperbound; validRange() handles whitespace

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/2daef359/src/cordova/plugin/index.js
----------------------------------------------------------------------
diff --git a/src/cordova/plugin/index.js b/src/cordova/plugin/index.js
index 7b34780..2ceae70 100644
--- a/src/cordova/plugin/index.js
+++ b/src/cordova/plugin/index.js
@@ -80,7 +80,7 @@ function plugin (command, targets, opts) {
 
         switch (command) {
         case 'add':
-            return module.exports.add(projectRoot, targets, hooksRunner, opts);
+            return module.exports.add(projectRoot, hooksRunner, opts);
         case 'rm':
         case 'remove':
             return module.exports.remove(projectRoot, targets, hooksRunner, opts);


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[10/14] cordova-lib git commit: fixes to integration test refrences so they run, bye bye save integration specs.

Posted by fi...@apache.org.
fixes to integration test refrences so they run, bye bye save integration specs.


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

Branch: refs/heads/master
Commit: 4c8563c6234c6b854b3fdbb4db5d05bb80a785df
Parents: 96facdc
Author: filmaj <ma...@gmail.com>
Authored: Tue Jun 20 14:20:54 2017 -0500
Committer: filmaj <ma...@gmail.com>
Committed: Wed Jun 21 14:58:15 2017 -0500

----------------------------------------------------------------------
 integration-tests/plugin.spec.js            |   2 +-
 integration-tests/plugin_fetch.spec.js      |   2 +-
 integration-tests/plugman_uninstall.spec.js |   2 +-
 integration-tests/save.spec.js              | 683 -----------------------
 src/cordova/plugin/index.js                 |   2 +-
 src/cordova/plugin/remove.js                |   1 +
 6 files changed, 5 insertions(+), 687 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/4c8563c6/integration-tests/plugin.spec.js
----------------------------------------------------------------------
diff --git a/integration-tests/plugin.spec.js b/integration-tests/plugin.spec.js
index 8572362..15243ca 100644
--- a/integration-tests/plugin.spec.js
+++ b/integration-tests/plugin.spec.js
@@ -17,7 +17,7 @@
     under the License.
 */
 
-var helpers = require('./helpers'),
+var helpers = require('../spec-cordova/helpers'),
     path = require('path'),
     Q = require('q'),
     shell = require('shelljs'),

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/4c8563c6/integration-tests/plugin_fetch.spec.js
----------------------------------------------------------------------
diff --git a/integration-tests/plugin_fetch.spec.js b/integration-tests/plugin_fetch.spec.js
index d6791c2..6bf5501 100644
--- a/integration-tests/plugin_fetch.spec.js
+++ b/integration-tests/plugin_fetch.spec.js
@@ -18,7 +18,7 @@
 */
 
 var plugin  = require('../src/cordova/plugin'),
-    helpers = require('./helpers'),
+    helpers = require('../spec-cordova/helpers'),
     path    = require('path'),
     events = require('cordova-common').events,
     shell   = require('shelljs');

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/4c8563c6/integration-tests/plugman_uninstall.spec.js
----------------------------------------------------------------------
diff --git a/integration-tests/plugman_uninstall.spec.js b/integration-tests/plugman_uninstall.spec.js
index 2b81705..af6ed1c 100644
--- a/integration-tests/plugman_uninstall.spec.js
+++ b/integration-tests/plugman_uninstall.spec.js
@@ -25,7 +25,7 @@ var uninstall = require('../src/plugman/uninstall'),
     PluginInfo = require('cordova-common').PluginInfo,
     events = require('cordova-common').events,
     plugman = require('../src/plugman/plugman'),
-    common  = require('./common'),
+    common  = require('../spec-plugman/common'),
     platforms = require('../src/platforms/platforms'),
     xmlHelpers = require('cordova-common').xmlHelpers,
     et      = require('elementtree'),

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/4c8563c6/integration-tests/save.spec.js
----------------------------------------------------------------------
diff --git a/integration-tests/save.spec.js b/integration-tests/save.spec.js
deleted file mode 100644
index e83b096..0000000
--- a/integration-tests/save.spec.js
+++ /dev/null
@@ -1,683 +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.
-*/
-
-describe('(save flag)', function () {
-    var rewire      = require('rewire'),
-        cordova     = require('../src/cordova/cordova'),
-        helpers     = require('../spec-cordova/helpers'),
-        path        = require('path'),
-        Q           = require('q'),
-        fs          = require('fs'),
-        shell       = require('shelljs'),
-        util        = require('../src/cordova/util'),
-        node_util   = require('util'),
-        prepare     = require('../src/cordova/prepare'),
-        registry    = require('../src/plugman/registry/registry'),
-        PlatformApi = require('../src/platforms/PlatformApiPoly'),
-        events      = require('cordova-common').events,
-        addHelper   = rewire('../src/cordova/platform/addHelper'),
-        platform    = require('../src/cordova/platform');
-
-    var appName                = 'testApp',
-        tempPath               = path.join(__dirname, 'temp'),
-        appPath                = path.join(tempPath, appName),
-        //srcProject             = path.join(__dirname, '..', 'spec-plugman','projects', 'android'),
-        platformName           = helpers.testPlatform,
-        platformVersionOld     = '5.0.0',
-        platformVersionNew     = '6.0.0',
-        platformVersionNewer   = '6.2.1',
-        platformLocalPathOld   = path.join(__dirname, 'cordova-' + platformName + '-old'),
-        platformLocalPathNew   = path.join(__dirname, 'cordova-' + platformName + '-new'),
-        platformLocalPathNewer = path.join(__dirname, 'cordova-' + platformName + '-newer'),
-        platformGitUrl         = 'https://github.com/apache/cordova-' + platformName,
-        platformGitRef         = '4.0.x',
-        platformTgzUrl         = 'https://git-wip-us.apache.org/repos/asf?p=cordova-' + platformName + '.git;a=snapshot;h=' + platformVersionNew + ';sf=tgz',
-        otherPlatformName      = 'windows',
-        otherPlatformSpec      = '4.0.0',
-        pluginName             = 'cordova-plugin-console',
-        pluginVersion          = '1.0.0',
-        pluginName2            = 'cordova-plugin-globalization',
-        pluginVersion2         = '1.0.2',
-        pluginGitUrl           = 'https://github.com/apache/cordova-plugin-console.git',
-        pluginOldName          = 'org.apache.cordova.console',
-        pluginOldVersion       = '0.2.11',
-        gitPluginName          = 'cordova-plugin-device',
-        gitPluginUrl           = 'https://github.com/apache/cordova-plugin-device.git',
-        variablePluginName     = 'cordova-plugin-facebook4',
-        variablePluginUrl      = 'https://github.com/jeduan/cordova-plugin-facebook4',
-        localPluginName        = 'org.apache.cordova.fakeplugin1',
-        localPluginPath        = path.join(__dirname, 'fixtures', 'plugins', 'fake1'),
-        TIMEOUT                = 60 * 1000,
-        BIG_TIMEOUT            = 2 * 60 * 1000;
-
-    //mock variables
-    var revertInstallPluginsForNewPlatform,
-        revertDownloadPlatform,
-        createPlatformOrig = PlatformApi.createPlatform;
-
-    function mockDownloadPlatform(libDir, version) {
-        console.log('in mockDownloadPlatform');
-        console.log(addHelper);
-        revertDownloadPlatform = addHelper.__set__('downloadPlatform', function () {
-            console.log('fake');
-            return Q({
-                libDir: libDir,
-                platform: platformName,
-                version: version
-            });
-        });
-    }
-
-    /**
-     * For testing scoped packages. We don't have those packages published, so just
-     * redirect the registry calls to their un-scoped counterparts
-     */
-    function redirectRegistryCalls(id) {
-        var originalFetch = registry.fetch;
-        spyOn(registry, 'fetch').and.callFake(function(package) {
-            return originalFetch([id]);
-        });
-
-        var originalInfo = registry.info;
-        spyOn(registry, 'info').and.callFake(function(package) {
-            return originalInfo([id]);
-        });
-    }
-
-    beforeEach(function (done) {
-        // initial cleanup
-        shell.rm('-rf', tempPath);
-        shell.mkdir(tempPath);
-
-        //jasmine mocks
-        spyOn(util, 'isCordova').and.returnValue(appPath);
-        spyOn(util, 'cdProjectRoot').and.returnValue(appPath);
-        spyOn(cordova, 'prepare').and.returnValue(Q());
-        spyOn(prepare, 'preparePlatforms').and.returnValue(Q());
-
-        spyOn(PlatformApi, 'createPlatform').and.returnValue(Q());
-        spyOn(PlatformApi, 'updatePlatform').and.returnValue(Q());
-
-        //rewire mocks
-        revertInstallPluginsForNewPlatform = addHelper.__set__('installPluginsForNewPlatform', function () { return Q(); });
-
-       //creating test app
-        cordova.create(appPath, undefined, undefined, {}, events).then(function () {
-            //removing unnecessary whitelist plugin from config
-            helpers.removePlugin(appPath, 'cordova-plugin-whitelist');
-            done();
-        }, function (err) {
-            expect(true).toBe(false);
-            console.log(err);
-            done();
-        });
-    }, TIMEOUT);
-
-    afterEach(function () {
-        revertInstallPluginsForNewPlatform();
-    });
-
-    function gitClone(repo, dir, ref, cb) {
-        var gitTemplate = 'git clone --branch %s --depth=1 %s %s';
-        var gitCommand = node_util.format(gitTemplate, ref, repo, dir);
-        shell.exec(gitCommand, { silent: true }, cb);
-    }
-
-    describe('preparing fixtures', function () {
-        it('Test 001 : cloning "old" platform', function (done) {
-            shell.rm('-rf', platformLocalPathOld);
-            gitClone(platformGitUrl, platformLocalPathOld, platformVersionOld, function (err) {
-                expect(err).toBe(0);
-                done();
-            });
-        }, BIG_TIMEOUT);
-
-        it('Test 002 : cloning "new" platform', function (done) {
-            shell.rm('-rf', platformLocalPathNew);
-            gitClone(platformGitUrl, platformLocalPathNew, platformVersionNew, function (err) {
-                expect(err).toBe(0);
-                done();
-            });
-        }, BIG_TIMEOUT);
-
-        it('Test 003 : cloning "newer" platform', function (done) {
-            shell.rm('-rf', platformLocalPathNewer);
-            gitClone(platformGitUrl, platformLocalPathNewer, platformVersionNewer, function (err) {
-                expect(err).toBe(0);
-                done();
-            });
-        }, BIG_TIMEOUT);
-    });
-
-    describe('platform add --save', function () {
-        it('Test 004 : spec.1 should support custom tgz files', function (done) {
-            helpers.removeEngine(appPath, platformName);
-            platform('add', platformName + '@' + platformTgzUrl, { 'save': true })
-            .then(function () {
-                expect(helpers.getEngineSpec(appPath, platformName)).toBe(platformTgzUrl);
-                done();
-            }).catch(function (err) {
-                console.log(err);
-                expect(false).toBe(true);
-                done();
-            });
-        }, BIG_TIMEOUT);
-
-       it('Test 005 : spec.2 should save platform to config', function (done) {
-            helpers.removeEngine(appPath, platformName);
-            mockDownloadPlatform(platformLocalPathNew, platformVersionNew);
-            platform('add', platformName, { 'save': true })
-            .then(function () {
-                expect(helpers.getEngineSpec(appPath, platformName)).toBe('~' + platformVersionNew);
-                done();
-            }).catch(function (err) {
-                console.log(err);
-                expect(false).toBe(true);
-                done();
-            }).finally(function () {
-                revertDownloadPlatform();
-            });
-        }, TIMEOUT);
-
-        it('Test 006 : spec.3 should overwrite platform in config, spec = version', function (done) {
-            helpers.setEngineSpec(appPath, platformName, platformVersionOld);
-            mockDownloadPlatform(platformLocalPathOld, platformVersionOld);
-
-            platform('add', platformName, { 'save': true })
-            .then(function () {
-                expect(helpers.getEngineSpec(appPath, platformName)).toBe('~' + platformVersionOld);
-                done();
-            }).catch(function (err) {
-                console.log(err);
-                expect(false).toBe(true);
-                done();
-            }).finally(function () {
-                revertDownloadPlatform();
-            });
-        }, TIMEOUT);
-
-        it('Test 007 : spec.4 should overwrite platform in config, spec = path', function (done) {
-            helpers.setEngineSpec(appPath, platformName, platformLocalPathNewer);
-            mockDownloadPlatform(platformLocalPathNewer, platformVersionNewer);
-
-            platform('add', platformName, { 'save': true })
-            .then(function () {
-                expect(helpers.getEngineSpec(appPath, platformName)).toBe(platformLocalPathNewer);
-                done();
-            }).catch(function (err) {
-                console.log(err);
-                expect(false).toBe(true);
-                done();
-            }).finally(function () {
-                revertDownloadPlatform();
-            });
-        }, TIMEOUT);
-
-        it('Test 008 : spec.5 should fail and should not update config if invalid version is specified', function (done) {
-            helpers.removeEngine(appPath, platformName);
-
-            platform('add', platformName + '@3.969.696', { 'save': true })
-            .then(function () {
-                expect(false).toBe(true);
-                done();
-            }).catch(function (err) {
-                expect(err.message.indexOf('version not found') >= 0).toBe(true);
-                expect(helpers.getEngineSpec(appPath, platformName)).toBe(null);
-                done();
-            });
-        }, TIMEOUT);
-
-        it('Test 009 : spec.6 should save local path as spec if added using only local path', function (done) {
-            helpers.removeEngine(appPath, platformName);
-
-            platform('add', platformLocalPathNewer, { 'save': true })
-            .then(function () {
-                expect(helpers.getEngineSpec(appPath, platformName)).toBe(platformLocalPathNewer);
-                done();
-            }).catch(function (err) {
-                console.log(err);
-                expect(false).toBe(true);
-                done();
-            });
-        }, TIMEOUT);
-
-        it('Test 010 : spec.7 should save git url with git ref properly', function (done) {
-            var platformUrl = platformGitUrl + '#' + platformGitRef;
-            helpers.removeEngine(appPath, platformName);
-            mockDownloadPlatform(platformLocalPathNew, platformVersionNew);
-
-            platform('add', platformUrl, { 'save': true })
-            .then(function () {
-                expect(helpers.getEngineSpec(appPath, platformName)).toBe(platformUrl);
-                done();
-            }).catch(function (err) {
-                console.log(err);
-                expect(false).toBe(true);
-                done();
-            }).finally(function () {
-                revertDownloadPlatform();
-            });
-        }, TIMEOUT);
-    });
-
-    describe('platform remove --save', function () {
-        it('Test 011 : spec.8 should not update config if there is no engine in it', function (done) {
-            helpers.removeEngine(appPath, platformName);
-
-            platform('add', platformLocalPathNewer)
-            .then(function () {
-                return cordova.platform('rm', platformName, { 'save': true });
-            }).then(function () {
-                expect(helpers.getEngineSpec(appPath, platformName)).toBe(null);
-                done();
-            }).catch(function (err) {
-                console.log(err);
-                expect(false).toBe(true);
-                done();
-            });
-        }, TIMEOUT);
-
-        it('Test 012 : spec.9 should remove engine from config', function (done) {
-            helpers.setEngineSpec(appPath, platformName, platformLocalPathNewer);
-
-            platform('add', platformLocalPathNewer)
-            .then(function () {
-                return cordova.platform('rm', platformName, { 'save': true });
-            }).then(function () {
-                expect(helpers.getEngineSpec(appPath, platformName)).toBe(null);
-                done();
-            }).catch(function (err) {
-                console.log(err);
-                expect(false).toBe(true);
-                done();
-            });
-        }, TIMEOUT);
-    });
-
-    describe('platform update --save', function () {
-        it('Test 013 : spec.10 should update config with new spec', function (done) {
-            helpers.setEngineSpec(appPath, platformName, platformVersionNew);
-            mockDownloadPlatform(platformLocalPathNew, platformVersionNew);
-
-            platform('add', platformName + '@' + platformVersionNew)
-            .then(function () {
-                var fsExistsSync = fs.existsSync.bind(fs);
-                spyOn(fs, 'existsSync').and.callFake(function (somePath) {
-                    return (somePath === path.join(appPath, 'platforms', platformName)) || fsExistsSync(somePath);
-                });
-
-                revertDownloadPlatform();
-                mockDownloadPlatform(platformLocalPathOld, platformVersionOld);
-
-                return platform('update', platformName + '@' + platformVersionOld, { 'save': true });
-            }).then(function () {
-                expect(helpers.getEngineSpec(appPath, platformName)).toBe('~' + platformVersionOld);
-                done();
-            }).catch(function (err) {
-                console.log(err);
-                expect(false).toBe(true);
-                done();
-            }).finally(function () {
-                revertDownloadPlatform();
-            });
-        }, TIMEOUT);
-
-        it('Test 014 : spec.11 should update spec with git url when updating using git url', function (done) {
-            helpers.setEngineSpec(appPath, platformName, platformVersionNew);
-            mockDownloadPlatform(platformLocalPathOld, platformVersionOld);
-
-            platform('add', platformName + '@' + platformVersionOld)
-            .then(function () {
-                revertDownloadPlatform();
-                var fsExistsSync = fs.existsSync.bind(fs);
-                spyOn(fs, 'existsSync').and.callFake(function (somePath) {
-                    return (somePath === path.join(appPath, 'platforms', platformName)) || fsExistsSync(somePath);
-                });
-                mockDownloadPlatform(platformLocalPathNew, platformVersionNew);
-                return platform('update', platformGitUrl, { 'save': true });
-            }).then(function () {
-                revertDownloadPlatform();
-                var spec = helpers.getEngineSpec(appPath, platformName);
-                expect(spec).not.toBe(null);
-                expect(spec).not.toBe(platformVersionNew);
-                done();
-            }).catch(function (err) {
-                console.log(err);
-                expect(false).toBe(true);
-                done();
-            }).finally(function (err) {
-
-            });
-        }, TIMEOUT);
-    });
-
-    describe('plugin add --save', function () {
-
-        it('Test 015 : spec.12 should save plugin to config', function (done) {
-            cordova.plugin('add', pluginName, { 'save': true })
-            .then(function () {
-                expect(helpers.getPluginSpec(appPath, pluginName)).not.toBe(null);
-                done();
-            }).catch(function (err) {
-                console.log(err.message);
-                expect(err).toBeUndefined();
-                done();
-            });
-        }, TIMEOUT);
-
-        it('Test 016 : spec.13 should create new plugin tag in config with old plugin id when downgrading from plugin with new id', function (done) {
-            helpers.setPluginSpec(appPath, pluginName, pluginOldVersion);
-            cordova.plugin('add', pluginName, { 'save': true })
-            .then(function () {
-                expect(helpers.getPluginSpec(appPath, pluginOldName)).toBe('~' + pluginOldVersion);
-                done();
-            }).catch(function (err) {
-                expect(true).toBe(false);
-                console.log(err.message);
-                done();
-            });
-        }, TIMEOUT);
-
-        it('Test 017 : spec.14 should save variables', function (done) {
-            cordova.plugin('add', variablePluginUrl, {
-                'save': true,
-                'cli_variables': {
-                    'APP_ID':'123456789',
-                    'APP_NAME':'myApplication'
-                }
-            }).then(function () {
-                expect(helpers.getPluginVariable(appPath, variablePluginName, 'APP_ID')).toBe('123456789');
-                expect(helpers.getPluginVariable(appPath, variablePluginName, 'APP_NAME')).toBe('myApplication');
-                done();
-            }).catch(function (err) {
-                expect(true).toBe(false);
-                console.log(err.message);
-                done();
-            });
-        }, BIG_TIMEOUT);
-
-        it('Test 018 : spec.14.1 should restore plugin with variables', function (done) {
-            cordova.plugin('add', variablePluginUrl, {
-                'save': true,
-                'cli_variables': {
-                    'APP_ID':'123456789',
-                    'APP_NAME':'myApplication'
-                }
-            }).then(function () {
-                expect(helpers.getPluginVariable(appPath, variablePluginName, 'APP_ID')).toBe('123456789');
-                expect(helpers.getPluginVariable(appPath, variablePluginName, 'APP_NAME')).toBe('myApplication');
-                return cordova.plugin('rm', variablePluginName);
-            }).then(function() {
-                expect(path.join(appPath, 'plugins', variablePluginName)).not.toExist();
-                return cordova.plugin('add', variablePluginName);
-            }).then(function() {
-                expect(path.join(appPath, 'plugins', variablePluginName)).toExist();
-                done();
-            }).catch(function (err) {
-                console.log(err.message);
-                expect(true).toBe(false);
-                done();
-            });
-        }, BIG_TIMEOUT);
-
-        it('Test 019 : spec.15 save git url as spec', function (done) {
-            cordova.plugin('add', pluginGitUrl, { 'save': true })
-            .then(function () {
-                expect(helpers.getPluginSpec(appPath, pluginName)).toBe(pluginGitUrl);
-                done();
-            }).catch(function (err) {
-                expect(true).toBe(false);
-                console.log(err.message);
-                done();
-            });
-        }, TIMEOUT);
-
-        it('Test 020 : spec.16 should save local directory as spec', function (done) {
-            cordova.plugin('add', localPluginPath, { 'save': true })
-            .then(function () {
-                expect(helpers.getPluginSpec(appPath, localPluginName)).toBe(localPluginPath);
-                done();
-            }).catch(function (err) {
-                expect(true).toBe(false);
-                console.log(err.message);
-                done();
-            });
-        }, TIMEOUT);
-
-        it('Test 021 : spec.16.1 save scoped registry packages as spec', function (done) {
-            redirectRegistryCalls(pluginName + '@' + pluginVersion);
-            var scopedPackage = '@test-scope/' + pluginName;
-
-            cordova.plugin('add', scopedPackage + '@' + pluginVersion, { 'save': true })
-            .then(function () {
-                expect(registry.fetch).toHaveBeenCalledWith([scopedPackage + '@' + pluginVersion]);
-                expect(helpers.getPluginSpec(appPath, pluginName)).toBe(scopedPackage + '@~' + pluginVersion);
-                done();
-            }).catch(function (err) {
-                expect(true).toBe(false);
-                console.log(err.message);
-                done();
-            });
-        }, TIMEOUT);
-    });
-
-    describe('plugin remove --save', function () {
-        it('Test 022 : spec.17 should not add plugin to config', function (done) {
-            cordova.plugin('add', pluginName)
-            .then(function () {
-                return cordova.plugin('rm', pluginName, { 'save': true });
-            }).then(function () {
-                expect(helpers.getPluginSpec(appPath, pluginName)).toBe(null);
-                done();
-            }).catch(function (err) {
-                expect(true).toBe(false);
-                console.log(err.message);
-                done();
-            });
-        }, TIMEOUT);
-
-        it('Test 023 : spec.18 should remove plugin from config', function (done) {
-            cordova.plugin('add', pluginName)
-            .then(function () {
-                helpers.setPluginSpec(appPath, pluginName, pluginGitUrl);
-                return cordova.plugin('rm', pluginName, { 'save': true });
-            }).then(function () {
-                expect(helpers.getPluginSpec(appPath, pluginName)).toBe(null);
-                done();
-            }).catch(function (err) {
-                expect(true).toBe(false);
-                console.log(err.message);
-                done();
-            });
-        }, TIMEOUT);
-    });
-
-    describe('platform save', function () {
-        it('Test 024 : spec.19 should not update config when there are no platforms installed', function (done) {
-            var configContent = helpers.getConfigContent(appPath);
-            platform('save')
-            .then(function () {
-                expect(helpers.getConfigContent(appPath)).toBe(configContent);
-                done();
-            }).catch(function (err) {
-                expect(true).toBe(false);
-                console.log(err.message);
-                done();
-            });
-        }, TIMEOUT);
-
-        it('Test 025 : spec.20 should add platform to config', function (done) {
-            mockDownloadPlatform(platformLocalPathNew, platformVersionNew);
-
-            platform('add', platformName + '@' + platformVersionNew)
-            .then(function () {
-                return platform('save');
-            }).then(function () {
-                expect(helpers.getEngineSpec(appPath, platformName)).toBe('~' + platformVersionNew);
-                done();
-            }).catch(function (err) {
-                expect(true).toBe(false);
-                console.log(err.message);
-                done();
-            }).finally(function () {
-                revertDownloadPlatform();
-            });
-        }, TIMEOUT);
-    });
-
-    describe('plugin save', function () {
-        it('Test 026: spec.21 should not update config when there are no plugins installed', function (done) {
-            var configContent = helpers.getConfigContent(appPath);
-            cordova.plugin('save')
-            .finally(function () {
-                expect(helpers.getConfigContent(appPath)).toBe(configContent);
-                done();
-            });
-        }, TIMEOUT);
-
-        it('Test 028 : spec.22.1 should update config with a spec that includes the scope for scoped plugins', function (done) {
-            // Fetching globalization rather than console to avoid conflicts with earlier tests
-            redirectRegistryCalls(pluginName2 + '@' + pluginVersion2);
-            var scopedPackage = '@test-scope/' + pluginName2;
-            cordova.plugin('add', scopedPackage + '@' + pluginVersion2)
-            .then(function () {
-                return cordova.plugin('save');
-            }).then(function () {
-                expect(registry.fetch).toHaveBeenCalledWith([scopedPackage + '@' + pluginVersion2]);
-                expect(helpers.getPluginSpec(appPath, pluginName2)).toBe(scopedPackage + '@~' + pluginVersion2);
-                done();
-            }).catch(function (err) {
-                expect(true).toBe(false);
-                console.log(err.message);
-                done();
-            });
-        }, TIMEOUT);
-    });
-
-    describe('prepare', function () {
-        beforeEach(function () {
-            // Restore back mocked createPlatform functionality
-            PlatformApi.createPlatform = createPlatformOrig;
-        });
-
-        it('Test 029 : spec.23 should restore all platforms and plugins', function (done) {
-            helpers.setEngineSpec(appPath, platformName, platformLocalPathNewer);
-            helpers.setPluginSpec(appPath, localPluginName, localPluginPath);
-            prepare()
-            .then(function () {
-                expect(path.join(appPath, 'platforms', platformName)).toExist();
-                expect(path.join(appPath, 'plugins', localPluginName)).toExist();
-                done();
-            }).catch(function (err) {
-                expect(true).toBe(false);
-                console.log(err.message);
-                done();
-            });
-        }, TIMEOUT);
-
-        it('Test 030 : spec.23.2 should restore plugins without spec attribute', function (done) {
-            redirectRegistryCalls(pluginName2);
-            helpers.setEngineSpec(appPath, platformName, platformLocalPathNewer);
-            helpers.setPluginSpec(appPath, pluginName2/**, do not specify spec here */);
-            prepare()
-            .then(function () {
-                expect(registry.fetch).toHaveBeenCalledWith([pluginName2]);
-                expect(path.join(appPath, 'plugins', pluginName2)).toExist();
-            }).catch(function (err) {
-                expect(true).toBe(false);
-                console.log(err.message);
-            })
-            .fin(done);
-        }, TIMEOUT);
-
-        it('Test 031 : spec.23.1 should restore scoped plugins', function (done) {
-            redirectRegistryCalls(pluginName2 + '@~' + pluginVersion2);
-            var scopedPackage = '@test-scope/' + pluginName2;
-            helpers.setEngineSpec(appPath, platformName, platformLocalPathNewer);
-            helpers.setPluginSpec(appPath, pluginName2, scopedPackage + '@~' + pluginVersion2);
-            prepare()
-            .then(function () {
-                expect(registry.fetch).toHaveBeenCalledWith([scopedPackage + '@~' + pluginVersion2]);
-                expect(path.join(appPath, 'plugins', pluginName2)).toExist();
-                done();
-            }).catch(function (err) {
-                expect(true).toBe(false);
-                console.log(err.message);
-                done();
-            });
-        }, TIMEOUT);
-
-        it('Test 032 : spec.24 should restore only specified platform', function (done) {
-            
-            helpers.setEngineSpec(appPath, platformName, platformLocalPathNewer);
-            helpers.setEngineSpec(appPath, otherPlatformName, otherPlatformSpec);
-            var options = {
-                verbose: false,
-                platforms: [ platformName ],
-                options: []
-            };
-            prepare(options)
-            .then(function () {
-                expect(path.join(appPath, 'platforms', platformName)).toExist();
-                expect(path.join(appPath, 'platforms', otherPlatformName)).not.toExist();
-                done();
-            }).catch(function (err) {
-                expect(true).toBe(false);
-                console.log(err.message);
-                done();
-            });
-        }, BIG_TIMEOUT);
-
-        xit('Test 033 : spec.25 should install plugins already added to the project into platform when restoring it', function (done) {
-            var fail = jasmine.createSpy('fail').and.callFake(function (err) {
-                console.log(err.message);
-            });
-
-            cordova.plugin('add', localPluginPath)
-            .then(function() {
-                helpers.setEngineSpec(appPath, platformName, platformLocalPathNewer);
-                return prepare({ platforms: [ platformName ] });
-            })
-            .then(function() {
-                console.log('prepare ran');
-                expect(path.join(appPath, 'platforms', platformName)).toExist();
-                // Validate that plugin has been installed to platform by checking
-                console.log(path.join(appPath, 'platforms', platformName, platformName + '.json'));
-                // platformJson file for plugin entry added
-                var platformJson = require(path.join(appPath, 'platforms', platformName, platformName + '.json'));
-                console.log(platformJson);
-                //console.log(fs.readFileSync(path.join(appPath, 'config.xml'), 'utf8'))
-                expect(platformJson.installed_plugins[localPluginName]).toBeDefined();
-            })
-            .finally(function () {
-                expect(fail).not.toHaveBeenCalled();
-                done();
-            });
-        }, BIG_TIMEOUT);
-    });
-
-    describe('(cleanup)', function () {
-        it('Test 034 : removing temp dir', function () {
-            shell.rm('-rf', tempPath);
-            shell.rm('-rf', platformLocalPathNewer);
-            shell.rm('-rf', platformLocalPathNew);
-            shell.rm('-rf', platformLocalPathOld);
-        });
-    });
-});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/4c8563c6/src/cordova/plugin/index.js
----------------------------------------------------------------------
diff --git a/src/cordova/plugin/index.js b/src/cordova/plugin/index.js
index 1099de1..7b34780 100644
--- a/src/cordova/plugin/index.js
+++ b/src/cordova/plugin/index.js
@@ -59,7 +59,7 @@ function plugin (command, targets, opts) {
 
         // Massage plugin name(s) / path(s)
         if (!targets || !targets.length) {
-            // TODO: what if command provided is 'remove' ?
+            // TODO: what if command provided is 'remove' ? shouldnt search need a target too?
             if (command === 'add' || command === 'rm') {
                 return Q.reject(new CordovaError('You need to qualify `' + cordova_util.binname + ' plugin add` or `' + cordova_util.binname + ' plugin remove` with one or more plugins!'));
             } else {

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/4c8563c6/src/cordova/plugin/remove.js
----------------------------------------------------------------------
diff --git a/src/cordova/plugin/remove.js b/src/cordova/plugin/remove.js
index ca27601..78a355e 100644
--- a/src/cordova/plugin/remove.js
+++ b/src/cordova/plugin/remove.js
@@ -67,6 +67,7 @@ function remove (projectRoot, targets, hooksRunner, opts) {
                     .then(function (didPrepare) {
                         // If platform does not returned anything we'll need
                         // to trigger a prepare after all plugins installed
+                        // TODO: if didPrepare is falsy, what does that imply? WHY are we doing this?
                         if (!didPrepare) shouldRunPrepare = true;
                     });
                 });


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[09/14] cordova-lib git commit: start of unit tests for cordova.plugin commands

Posted by fi...@apache.org.
start of unit tests for cordova.plugin commands


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

Branch: refs/heads/master
Commit: 96facdc17134fb729de4b50ba105acff64cfef38
Parents: ba6b57d
Author: filmaj <ma...@gmail.com>
Authored: Tue Jun 20 14:20:20 2017 -0500
Committer: filmaj <ma...@gmail.com>
Committed: Wed Jun 21 14:58:14 2017 -0500

----------------------------------------------------------------------
 spec-cordova/plugin/index.spec.js  | 41 +++++++++++++++++++++++++++++++++
 spec-cordova/plugin/list.spec.js   | 31 +++++++++++++++++++++++++
 spec-cordova/plugin/remove.spec.js | 38 ++++++++++++++++++++++++++++++
 spec-cordova/plugin/save.spec.js   | 39 +++++++++++++++++++++++++++++++
 4 files changed, 149 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/96facdc1/spec-cordova/plugin/index.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/plugin/index.spec.js b/spec-cordova/plugin/index.spec.js
new file mode 100644
index 0000000..479de82
--- /dev/null
+++ b/spec-cordova/plugin/index.spec.js
@@ -0,0 +1,41 @@
+/**
+    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.
+*/
+/* eslint-env jasmine */
+
+var plugin = require('../../src/cordova/plugin');
+
+describe('cordova/plugin', function () {
+    describe('error conditions', function () {
+        // TODO: what about search cmd?
+        it('should require at least one target for add and rm commands');
+    });
+    describe('handling/massaging of parameters', function () {
+        it('should be able to handle an array of targets');
+        it('should be able to handle a single string as a target');
+        it('should transform targets that start with a dash into options');
+        it('should also include targets into a plugins property on options');
+    });
+    describe('happy path', function () {
+        it('should direct "add" command to the "add" submodule');
+        it('should direct "rm" and "remove" commands to the "remove" submodule');
+        it('should direct "search" command to the "search" submodule');
+        it('should direct "save" command to the "save" submodule');
+        it('should direct "list", all other commands and no command at all to the "list" submodule');
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/96facdc1/spec-cordova/plugin/list.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/plugin/list.spec.js b/spec-cordova/plugin/list.spec.js
new file mode 100644
index 0000000..0bf27b4
--- /dev/null
+++ b/spec-cordova/plugin/list.spec.js
@@ -0,0 +1,31 @@
+/**
+    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.
+*/
+/* eslint-env jasmine */
+
+var list = require('../../src/cordova/plugin/list');
+
+describe('cordova/plugin/list', function () {
+    it('should fire the before_plugin_ls hook');
+    it('should emit a "no plugins added" result if there are no installed plugins');
+    it('should warn if plugin list contains dependencies that are missing');
+    it('should warn if plugin list contains a plugin dependency that does not have a version satisfied');
+    it('should emit a result containing a description of plugins installed');
+    it('should fire the after_plugin_ls hook');
+    it('should resolve the promise by returning an array of plugin ids installed');
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/96facdc1/spec-cordova/plugin/remove.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/plugin/remove.spec.js b/spec-cordova/plugin/remove.spec.js
new file mode 100644
index 0000000..5101fb9
--- /dev/null
+++ b/spec-cordova/plugin/remove.spec.js
@@ -0,0 +1,38 @@
+/**
+    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.
+*/
+/* eslint-env jasmine */
+
+var remove = require('../../src/cordova/plugin/remove');
+
+describe('cordova/plugin/remove', function () {
+    describe('error/warning conditions', function () {
+        it('should require that a plugin be provided');
+        it('should require that a provided plugin be installed in the current project');
+    });
+    describe('happy path', function () {
+        it('should fire the before_plugin_rm hook');
+        it('should call plugman.uninstall.uninstallPlatform for each platform installed in the project and for each provided plugin');
+        it('should trigger a prepare if plugman.uninstall.uninstallPlatform returned something falsy');
+        it('should call plugman.uninstall.uninstallPlugin once plugin has been uninstalled for each platform');
+        it('should remove provided plugins from config.xml');
+        it('should remove provided plugins from package.json (if exists)');
+        it('should remove fetch metadata from fetch.json');
+        it('should fire the after_plugin_rm hook');
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/96facdc1/spec-cordova/plugin/save.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/plugin/save.spec.js b/spec-cordova/plugin/save.spec.js
new file mode 100644
index 0000000..c2b5f68
--- /dev/null
+++ b/spec-cordova/plugin/save.spec.js
@@ -0,0 +1,39 @@
+/**
+    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.
+*/
+/* eslint-env jasmine */
+
+var save = require('../../src/cordova/plugin/save');
+
+describe('cordova/plugin/save', function () {
+    describe('error conditions', function () {
+        it('should explode if there was an issue parsing or reading from fetch.json file');
+    });
+    describe('happy path', function () {
+        it('should remove all plugins from config.xml and re-add new ones based on those retrieved from fetch.json');
+        it('should only add top-level plugins to config.xml');
+        it('should write individual plugin specs to config.xml');
+        it('should write individual plugin variables to config.xml');
+    });
+    describe('getSpec helper method', function () {
+        it('should return a plugin source\'s url or path property immediately');
+        it('should return a version if a version was provided to plugin id');
+        it('should return a version that includes scope if scope was part of plugin id');
+        it('should fall back to using PluginInfoProvider to retrieve a version as last resort');
+    });
+});


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[02/14] cordova-lib git commit: eslint fixes for plugin specs

Posted by fi...@apache.org.
eslint fixes for plugin specs


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

Branch: refs/heads/master
Commit: da205ae8da307c6a90dae4d4aa7d6dd5c1a83349
Parents: cc90653
Author: filmaj <ma...@gmail.com>
Authored: Mon Jun 19 23:10:13 2017 -0500
Committer: filmaj <ma...@gmail.com>
Committed: Wed Jun 21 14:57:48 2017 -0500

----------------------------------------------------------------------
 spec-cordova/plugin_package_parse.spec.js | 18 +++++++++---------
 spec-cordova/plugin_parser.spec.js        | 22 +++++++++++-----------
 src/plugman/fetch.js                      |  2 +-
 src/plugman/install.js                    |  2 +-
 src/plugman/registry/registry.js          |  2 +-
 5 files changed, 23 insertions(+), 23 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/da205ae8/spec-cordova/plugin_package_parse.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/plugin_package_parse.spec.js b/spec-cordova/plugin_package_parse.spec.js
index 3ae1ca5..25a75d4 100644
--- a/spec-cordova/plugin_package_parse.spec.js
+++ b/spec-cordova/plugin_package_parse.spec.js
@@ -16,12 +16,12 @@
     specific language governing permissions and limitations
     under the License.
 */
+/* eslint-env jasmine */
 
-var pluginSpec = require('../src/cordova/plugin_spec_parser');
+var pluginSpec = require('../src/cordova/plugin/plugin_spec_parser');
 
-describe('methods for parsing npm plugin packages', function() {
-
-    function checkPluginSpecParsing(testString, scope, id, version) {
+describe('methods for parsing npm plugin packages', function () {
+    function checkPluginSpecParsing (testString, scope, id, version) {
         var parsedSpec = pluginSpec.parse(testString);
         expect(parsedSpec.scope).toEqual(scope);
         expect(parsedSpec.id).toEqual(id || testString);
@@ -29,21 +29,21 @@ describe('methods for parsing npm plugin packages', function() {
         expect(parsedSpec.package).toEqual(scope ? scope + id : id);
     }
 
-    it('Test 001 : should handle package names with no scope or version', function() {
+    it('Test 001 : should handle package names with no scope or version', function () {
         checkPluginSpecParsing('test-plugin', null, 'test-plugin', null);
     });
-    it('Test 002 : should handle package names with a version', function() {
+    it('Test 002 : should handle package names with a version', function () {
         checkPluginSpecParsing('test-plugin@1.0.0', null, 'test-plugin', '1.0.0');
         checkPluginSpecParsing('test-plugin@latest', null, 'test-plugin', 'latest');
     });
-    it('Test 003 : should handle package names with a scope', function() {
+    it('Test 003 : should handle package names with a scope', function () {
         checkPluginSpecParsing('@test/test-plugin', '@test/', 'test-plugin', null);
     });
-    it('Test 004 : should handle package names with a scope and a version', function() {
+    it('Test 004 : should handle package names with a scope and a version', function () {
         checkPluginSpecParsing('@test/test-plugin@1.0.0', '@test/', 'test-plugin', '1.0.0');
         checkPluginSpecParsing('@test/test-plugin@latest', '@test/', 'test-plugin', 'latest');
     });
-    it('Test 005 : should handle invalid package specs', function() {
+    it('Test 005 : should handle invalid package specs', function () {
         checkPluginSpecParsing('@nonsense', null, null, null);
         checkPluginSpecParsing('@/nonsense', null, null, null);
         checkPluginSpecParsing('@', null, null, null);

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/da205ae8/spec-cordova/plugin_parser.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/plugin_parser.spec.js b/spec-cordova/plugin_parser.spec.js
index d5c9007..ad79f08 100644
--- a/spec-cordova/plugin_parser.spec.js
+++ b/spec-cordova/plugin_parser.spec.js
@@ -16,29 +16,29 @@
     specific language governing permissions and limitations
     under the License.
 */
-var path = require('path'),
-    fs = require('fs'),
-    plugin_parser = require('../src/cordova/plugin_parser'),
-    xml = path.join(__dirname, 'fixtures', 'plugins', 'test', 'plugin.xml');
+/* eslint-env jasmine */
+var path = require('path');
+var fs = require('fs');
+var Plugin_parser = require('../src/cordova/plugin_parser');
+var xml = path.join(__dirname, 'fixtures', 'plugins', 'test', 'plugin.xml');
 
 var xml_contents = fs.readFileSync(xml, 'utf-8');
 
 describe('plugin.xml parser', function () {
-    var readfile;
-    beforeEach(function() {
-        readfile = spyOn(fs, 'readFileSync').and.returnValue(xml_contents);
+    beforeEach(function () {
+        spyOn(fs, 'readFileSync').and.returnValue(xml_contents);
     });
 
-    it('Test 001 : should read a proper plugin.xml file', function() {
+    it('Test 001 : should read a proper plugin.xml file', function () {
         var cfg;
         expect(function () {
-            cfg = new plugin_parser(xml);
+            cfg = new Plugin_parser(xml);
         }).not.toThrow();
         expect(cfg).toBeDefined();
         expect(cfg.doc).toBeDefined();
     });
-    it('Test 002 : should be able to figure out which platforms the plugin supports', function() {
-        var cfg = new plugin_parser(xml);
+    it('Test 002 : 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-lib/blob/da205ae8/src/plugman/fetch.js
----------------------------------------------------------------------
diff --git a/src/plugman/fetch.js b/src/plugman/fetch.js
index 75024fd..48f7596 100644
--- a/src/plugman/fetch.js
+++ b/src/plugman/fetch.js
@@ -30,7 +30,7 @@ var shell   = require('shelljs'),
     path    = require('path'),
     Q       = require('q'),
     registry = require('./registry/registry'),
-    pluginSpec      = require('../cordova/plugin_spec_parser'),
+    pluginSpec      = require('../cordova/plugin/plugin_spec_parser'),
     fetch = require('cordova-fetch'),
     cordovaUtil = require('../cordova/util');
 

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/da205ae8/src/plugman/install.js
----------------------------------------------------------------------
diff --git a/src/plugman/install.js b/src/plugman/install.js
index d13d8fe..b00bc09 100644
--- a/src/plugman/install.js
+++ b/src/plugman/install.js
@@ -35,7 +35,7 @@ var path = require('path'),
     events = require('cordova-common').events,
     HooksRunner = require('../hooks/HooksRunner'),
     isWindows = (os.platform().substr(0,3) === 'win'),
-    pluginSpec = require('../cordova/plugin_spec_parser'),
+    pluginSpec = require('../cordova/plugin/plugin_spec_parser'),
     cordovaUtil = require('../cordova/util');
 
 var superspawn = require('cordova-common').superspawn;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/da205ae8/src/plugman/registry/registry.js
----------------------------------------------------------------------
diff --git a/src/plugman/registry/registry.js b/src/plugman/registry/registry.js
index c11eba5..5123e73 100644
--- a/src/plugman/registry/registry.js
+++ b/src/plugman/registry/registry.js
@@ -23,7 +23,7 @@ var npm = require('npm'),
     Q = require('q'),
     npmhelper = require('../../util/npm-helper'),
     events = require('cordova-common').events,
-    pluginSpec = require('../../cordova/plugin_spec_parser');
+    pluginSpec = require('../../cordova/plugin/plugin_spec_parser');
 
 module.exports = {
     settings: null,


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[06/14] cordova-lib git commit: plugin_fetch.spec.js, hooks runner and plugman fetch specs are all integration tests - moving to integration-tests dir

Posted by fi...@apache.org.
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/04c12e2b/spec-cordova/HooksRunner.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/HooksRunner.spec.js b/spec-cordova/HooksRunner.spec.js
deleted file mode 100644
index d8cc62a..0000000
--- a/spec-cordova/HooksRunner.spec.js
+++ /dev/null
@@ -1,683 +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.
- **/
-
-/* jshint boss:true */
-
-var cordova = require('../src/cordova/cordova'),
-    HooksRunner = require('../src/hooks/HooksRunner'),
-    shell  = require('shelljs'),
-    path   = require('path'),
-    fs     = require('fs'),
-    os     = require('os'),
-    Q      = require('q'),
-    child_process = require('child_process'),
-    helpers = require('./helpers'),
-    PluginInfo = require('cordova-common').PluginInfo,
-    superspawn = require('cordova-common').superspawn,
-    config = require('../src/cordova/config');
-
-var platform = os.platform();
-var tmpDir = helpers.tmpDir('hooks_test');
-var project = path.join(tmpDir, 'project');
-var dotCordova = path.join(project, '.cordova');
-var hooksDir = path.join(project, 'hooks');
-var hooksDirDot = path.join(project, '.cordova', 'hooks');
-var scriptsDir = path.join(project, 'scripts');
-var ext = platform.match(/(win32|win64)/)?'bat':'sh';
-var testPluginFixturePath = path.join(__dirname, 'fixtures', 'plugins', 'com.plugin.withhooks');
-
-var cordovaUtil = require('../src/cordova/util');
-
-
-// copy fixture
-shell.rm('-rf', project);
-shell.mkdir('-p', project);
-shell.cp('-R', path.join(__dirname, 'fixtures', 'projWithHooks', '*'), project);
-
-shell.mkdir('-p', dotCordova);
-shell.cp('-R', path.join(__dirname, 'fixtures', 'projWithHooks', '.cordova'), project);
-
-// copy sh/bat scripts
-if(ext === 'bat') {
-    shell.cp('-R', path.join(__dirname, 'fixtures', 'projWithHooks', '_bat', '*'), project);
-    shell.cp('-R', path.join(__dirname, 'fixtures', 'projWithHooks', '_bat', '.cordova'), project);
-} else {
-    shell.cp('-R', path.join(__dirname, 'fixtures', 'projWithHooks', '_sh', '*'), project);
-    shell.cp('-R', path.join(__dirname, 'fixtures', 'projWithHooks', '_sh', '.cordova'), project);
-}
-
-shell.chmod('-R', 'ug+x', hooksDir);
-shell.chmod('-R', 'ug+x', hooksDirDot);
-shell.chmod('-R', 'ug+x', scriptsDir);
-
-// To get more verbose output to help trace program flow, uncomment the lines below
-// TODO: think about how to factor this out so that it can be invoked from the command line
-//var events = require('cordova-common').events;
-//events.on('log', function(msg) { console.log(msg); });
-//events.on('verbose', function(msg) { console.log(msg); });
-//events.on('warn', function(msg) { console.log(msg); });
-
-describe('HooksRunner', function() {
-    var hooksRunner;
-    var hookOptions;
-    var testPluginInstalledPath;
-    var projectRoot;
-    var fire;
-
-    beforeEach(function() {
-        process.chdir(project);
-    });
-
-    afterEach(function() {
-        process.chdir(path.join(__dirname, '..'));  // Non e2e tests assume CWD is repo root.
-    });
-
-    it('Test 001 : should throw if provided directory is not a cordova project', function() {
-        expect(function() {
-            new HooksRunner(tmpDir);
-        }).toThrow();
-    });
-
-    it('Test 002 : should not throw if provided directory is a cordova project', function() {
-        expect(function () {
-            new HooksRunner(project);
-        }).not.toThrow();
-    });
-
-    it('Test 003 : should init test fixtures', function(done) {
-        hooksRunner = new HooksRunner(project);
-
-        // Now we load the config.json in the newly created project and edit the target platform's lib entry
-        // to point at the fixture version. This is necessary so that cordova.prepare can find cordova.js there.
-        var c = config.read(project);
-        c.lib[helpers.testPlatform].url = path.join(__dirname, 'fixtures', 'platforms', helpers.testPlatform + '-lib');
-        config.write(project, c);
-
-        // The config.json in the fixture project points at fake "local" paths.
-        // Since it's not a URL, the lazy-loader will just return the junk path.
-        spyOn(superspawn, 'spawn').and.callFake(function(cmd, args) {
-            if (cmd.match(/create\b/)) {
-                // This is a call to the bin/create script, so do the copy ourselves.
-                shell.cp('-R', path.join(__dirname, 'fixtures', 'platforms', 'android'), path.join(project, 'platforms'));
-            } else if(cmd.match(/update\b/)) {
-                fs.writeFileSync(path.join(project, 'platforms', helpers.testPlatform, 'updated'), 'I was updated!', 'utf-8');
-            } else if (cmd.match(/version/)) {
-                return '3.6.0';
-            }
-            return Q();
-        });
-
-
-        // Add the testing platform.
-        cordova.platform('add', [helpers.testPlatform]).fail(function (err) {
-            expect(err).toBeUndefined();
-            console.error(err);
-            done();
-        }).then(function () {
-            // Add the testing plugin
-            projectRoot = cordovaUtil.isCordova();
-
-            var options = {
-                verbose: false,
-                platforms: [],
-                options: []
-            };
-
-            options = cordovaUtil.preProcessOptions(options);
-
-            hookOptions = { projectRoot: project, cordova: options };
-
-            cordova.plugin('add', testPluginFixturePath).fail(function (err) {
-                expect(err && err.stack).toBeUndefined();
-                done();
-            }).then(function () {
-                testPluginInstalledPath = path.join(projectRoot, 'plugins', 'com.plugin.withhooks');
-                shell.chmod('-R', 'ug+x', path.join(testPluginInstalledPath, 'scripts'));
-                done();
-            });
-        });
-    }, 100000);
-
-    describe('fire method', function() {
-        beforeEach(function() {
-            projectRoot = cordovaUtil.isCordova();
-
-            var options = {
-                verbose: false,
-                platforms: [],
-                options: []
-            };
-
-            options = cordovaUtil.preProcessOptions(options);
-            hookOptions = { projectRoot: project, cordova: options };
-
-            var hooksOrderFile = path.join(projectRoot, 'hooks_order.txt');
-            removeFileIfExists(hooksOrderFile);
-
-            fire = spyOn(HooksRunner.prototype, 'fire').and.callThrough();
-        });
-
-        // helper methods
-        function hooksOrderFileContents(hooksOrderFile) {
-            var order = fs.readFileSync(hooksOrderFile, 'ascii').replace(/\W/gm, ' ');
-
-            var orderArrOriginal = order.split(' ').slice(0);
-
-            function splitNumbers(mixedString) {
-                var re = /\w+/g, match, params = [];
-
-                while (match = re.exec(mixedString)) {
-                    params.push(match[0]);
-                }
-                return params;
-            }
-
-            return splitNumbers(orderArrOriginal).map(function(str) { return parseInt(str, 10); });
-        }
-
-        function hooksOrderFileIsOrdered(hooksOrderFile) {
-            var splitArrOriginal = hooksOrderFileContents(hooksOrderFile);
-            var splitArrSorted = splitArrOriginal.slice(0).sort(function(a, b) { return a - b; });
-
-            return JSON.stringify(splitArrOriginal) === JSON.stringify(splitArrSorted);
-        }
-
-        function backupAppConfig(projectRoot) {
-            shell.cp('-f', path.join(projectRoot, 'config.xml'), path.join(projectRoot, 'configOrig.xml'));
-        }
-
-        function restoreAppConfig(projectRoot) {
-            shell.cp('-f', path.join(projectRoot, 'configOrig.xml'), path.join(projectRoot, 'config.xml'));
-            shell.rm('-rf', path.join(projectRoot, 'configOrig.xml'));
-        }
-
-        function switchToOnlyNonPlatformScriptsAppConfig(projectRoot) {
-            backupAppConfig(projectRoot);
-            shell.cp('-f', path.join(projectRoot, 'configOnlyNonPlatformScripts_' + ext + '.xml'), path.join(projectRoot, 'config.xml'));
-        }
-
-        function switchToOnePlatformScriptsAppConfig(projectRoot) {
-            backupAppConfig(projectRoot);
-            shell.cp('-f', path.join(projectRoot, 'configOnePlatform_' + ext + '.xml'), path.join(projectRoot, 'config.xml'));
-        }
-
-        function switchToTwoPlatformsScriptsAppConfig(projectRoot) {
-            backupAppConfig(projectRoot);
-            shell.cp('-f', path.join(projectRoot, 'configTwoPlatforms_' + ext + '.xml'), path.join(projectRoot, 'config.xml'));
-        }
-
-        function backupPluginConfig() {
-            shell.cp('-f', path.join(testPluginInstalledPath, 'plugin.xml'), path.join(testPluginInstalledPath, 'pluginOrig.xml'));
-        }
-
-        function restorePluginConfig() {
-            shell.cp('-f', path.join(testPluginInstalledPath, 'pluginOrig.xml'), path.join(testPluginInstalledPath, 'plugin.xml'));
-            shell.rm('-rf', path.join(testPluginInstalledPath, 'pluginOrig.xml'));
-        }
-
-        function switchToOnlyNonPlatformScriptsPluginConfig(projectRoot) {
-            backupPluginConfig();
-            shell.cp('-f', path.join(testPluginInstalledPath, 'pluginOnlyNonPlatformScripts_' + ext + '.xml'), path.join(testPluginInstalledPath, 'plugin.xml'));
-        }
-
-        function switchToOnePlatformScriptsPluginConfig(projectRoot) {
-            backupPluginConfig();
-            shell.cp('-f', path.join(testPluginInstalledPath, 'pluginOnePlatform_' + ext + '.xml'), path.join(testPluginInstalledPath, 'plugin.xml'));
-        }
-
-        function switchToTwoPlatformsScriptsPluginConfig(projectRoot) {
-            backupPluginConfig();
-            shell.cp('-f', path.join(testPluginInstalledPath, 'pluginTwoPlatforms_' + ext + '.xml'), path.join(testPluginInstalledPath, 'plugin.xml'));
-        }
-
-        function removeFileIfExists(file) {
-            if (fs.existsSync(file)) {
-                fs.unlinkSync(file);
-            }
-        }
-
-        describe('application hooks', function() {
-            it('Test 004 : should execute hook scripts serially', function (done) {
-                var test_event = 'before_build';
-                var projectRoot = cordovaUtil.isCordova();
-                var hooksOrderFile = path.join(projectRoot, 'hooks_order.txt');
-
-                return hooksRunner.fire(test_event, hookOptions).then(function() {
-                    expect(hooksOrderFile).toExist();
-
-                    expect(hooksOrderFileIsOrdered(hooksOrderFile)).toBe(true);
-                }).fail(function (err) {
-                    expect(err).toBeUndefined();
-                }).then(function () {
-                    done();
-                });
-            });
-
-            it('Test 005 : should execute hook scripts serially from .cordova/hooks/hook_type and hooks/hook_type directories', function (done) {
-                var test_event = 'before_build';
-                var projectRoot = cordovaUtil.isCordova();
-                var hooksOrderFile = path.join(projectRoot, 'hooks_order.txt');
-
-                // using empty platforms list to test only hooks/ directories
-                hookOptions.cordova.platforms = [];
-
-                return hooksRunner.fire(test_event, hookOptions).then(function () {
-                    expect(hooksOrderFile).toExist();
-
-                    expect(hooksOrderFileIsOrdered(hooksOrderFile)).toBe(true);
-                }).fail(function (err) {
-                    expect(err).toBeUndefined();
-                }).then(function () {
-                    done();
-                });
-            });
-
-            it('Test 006 : should execute hook scripts serially from config.xml', function (done) {
-                var test_event = 'before_build';
-                var projectRoot = cordovaUtil.isCordova();
-                var hooksOrderFile = path.join(projectRoot, 'hooks_order.txt');
-
-                switchToOnlyNonPlatformScriptsAppConfig(projectRoot);
-
-                return hooksRunner.fire(test_event, hookOptions).then(function () {
-                    expect(hooksOrderFile).toExist();
-
-                    expect(hooksOrderFileIsOrdered(hooksOrderFile)).toBe(true);
-                }).fail(function (err) {
-                    expect(err).toBeUndefined();
-                }).then(function () {
-                    restoreAppConfig(projectRoot);
-                    done();
-                });
-            });
-
-            it('Test 007 : should execute hook scripts serially from config.xml including platform scripts', function (done) {
-                var test_event = 'before_build';
-                var projectRoot = cordovaUtil.isCordova();
-                var hooksOrderFile = path.join(projectRoot, 'hooks_order.txt');
-
-                switchToOnePlatformScriptsAppConfig(projectRoot);
-
-                return hooksRunner.fire(test_event, hookOptions).then(function () {
-                    expect(hooksOrderFile).toExist();
-
-                    expect(hooksOrderFileIsOrdered(hooksOrderFile)).toBe(true);
-                }).fail(function (err) {
-                    expect(err).toBeUndefined();
-                }).then(function () {
-                    restoreAppConfig(projectRoot);
-                    done();
-                });
-            });
-
-            it('Test 008 : should filter hook scripts from config.xml by platform', function (done) {
-                var test_event = 'before_build';
-                var projectRoot = cordovaUtil.isCordova();
-                var hooksOrderFile = path.join(projectRoot, 'hooks_order.txt');
-
-                switchToTwoPlatformsScriptsAppConfig(projectRoot);
-
-                hookOptions.cordova.platforms = ['android'];
-
-                return hooksRunner.fire(test_event, hookOptions).then(function () {
-                    expect(hooksOrderFile).toExist();
-
-                    expect(hooksOrderFileIsOrdered(hooksOrderFile)).toBe(true);
-
-                    var baseScriptResults = [1, 2, 3, 4, 5, 6, 7, 8, 9];
-                    var androidPlatformScriptsResults = [14, 15];
-
-                    expect(JSON.stringify(hooksOrderFileContents(hooksOrderFile)) ===
-                        JSON.stringify(baseScriptResults.slice(0).concat(androidPlatformScriptsResults))).toBe(true);
-                }).fail(function (err) {
-                    expect(err).toBeUndefined();
-                }).then(function () {
-                    restoreAppConfig(projectRoot);
-                    done();
-                });
-            });
-        });
-
-        describe('plugin hooks', function() {
-            it('Test 009 : should execute hook scripts serially from plugin.xml', function (done) {
-                var test_event = 'before_build';
-                var projectRoot = cordovaUtil.isCordova();
-                var hooksOrderFile = path.join(projectRoot, 'hooks_order.txt');
-
-                switchToOnlyNonPlatformScriptsPluginConfig();
-
-                return hooksRunner.fire(test_event, hookOptions).then(function () {
-                    expect(hooksOrderFile).toExist();
-
-                    expect(hooksOrderFileIsOrdered(hooksOrderFile)).toBe(true);
-                }).fail(function (err) {
-                    expect(err).toBeUndefined();
-                }).then(function () {
-                    restorePluginConfig(projectRoot);
-                    done();
-                });
-            });
-
-            it('Test 010 : should execute hook scripts serially from plugin.xml including platform scripts', function (done) {
-                var test_event = 'before_build';
-                var projectRoot = cordovaUtil.isCordova();
-                var hooksOrderFile = path.join(projectRoot, 'hooks_order.txt');
-
-                switchToOnePlatformScriptsPluginConfig();
-
-                return hooksRunner.fire(test_event, hookOptions).then(function () {
-                    expect(hooksOrderFile).toExist();
-
-                    expect(hooksOrderFileIsOrdered(hooksOrderFile)).toBe(true);
-                }).fail(function (err) {
-                    expect(err).toBeUndefined();
-                }).then(function () {
-                    restorePluginConfig(projectRoot);
-                    done();
-                });
-            });
-
-            it('Test 011 : should filter hook scripts from plugin.xml by platform', function (done) {
-                var test_event = 'before_build';
-                var projectRoot = cordovaUtil.isCordova();
-                var hooksOrderFile = path.join(projectRoot, 'hooks_order.txt');
-
-                switchToTwoPlatformsScriptsPluginConfig(projectRoot);
-
-                hookOptions.cordova.platforms = ['android'];
-
-                return hooksRunner.fire(test_event, hookOptions).then(function () {
-                    expect(hooksOrderFile).toExist();
-
-                    expect(hooksOrderFileIsOrdered(hooksOrderFile)).toBe(true);
-
-                    var baseScriptResults = [1, 2, 3, 4, 5, 6, 7, 21, 22];
-                    var androidPlatformScriptsResults = [26];
-
-                    expect(JSON.stringify(hooksOrderFileContents(hooksOrderFile)) ===
-                        JSON.stringify(baseScriptResults.slice(0).concat(androidPlatformScriptsResults))).toBe(true);
-                }).fail(function (err) {
-                    expect(err).toBeUndefined();
-                }).then(function () {
-                    restorePluginConfig(projectRoot);
-                    done();
-                });
-            });
-
-            it('Test 012 : should run before_plugin_uninstall, before_plugin_install, after_plugin_install hooks for a plugin being installed with correct opts.plugin context', function (done) {
-                var projectRoot = cordovaUtil.isCordova();
-
-                // remove plugin
-                cordova.plugin('rm', 'com.plugin.withhooks').fail(function (err) {
-                    expect(err.stack).toBeUndefined();
-                }).then(function () {
-                    cordova.plugin('add', testPluginFixturePath).fail(function (err) {
-                        expect(err).toBeUndefined();
-                    }).then(function () {
-                        testPluginInstalledPath = path.join(projectRoot, 'plugins', 'com.plugin.withhooks');
-                        shell.chmod('-R', 'ug+x', path.join(testPluginInstalledPath, 'scripts'));
-
-                        var pluginInfo = new PluginInfo(testPluginInstalledPath);
-
-                        var cordovaVersion = require('../package').version;
-
-                        var androidPluginOpts = {
-                            cordova: {
-                                platforms: [ 'android' ],
-                                plugins: ['com.plugin.withhooks'],
-                                version: cordovaVersion
-                            },
-                            plugin: {
-                                id: 'com.plugin.withhooks',
-                                pluginInfo: pluginInfo,
-                                platform: 'android',
-                                dir: testPluginInstalledPath
-                            },
-                            projectRoot: projectRoot
-                        };
-                        // Delete unique ids to allow comparing PluginInfo
-                        delete androidPluginOpts.plugin.pluginInfo._et;
-
-                        fire.calls.all().forEach(function(call) {
-                            if(call.args[1] && call.args[1].plugin) {
-                                // Delete unique ids to allow comparing PluginInfo
-                                delete call.args[1].plugin.pluginInfo._et;
-                            }
-
-                            if(call.args[0] == 'before_plugin_uninstall' ||
-                                call.args[0] == 'before_plugin_install' ||
-                                call.args[0] == 'after_plugin_install') {
-                                if(call.args[1]) {
-                                    expect(call.args[1].plugin).toBeDefined();
-                                    if(call.args[1].plugin.platform == 'android') {
-                                        expect(JSON.stringify(androidPluginOpts) ===
-                                            JSON.stringify(call.args[1])).toBe(true);
-                                    }
-                                }
-                            }
-                        });
-                    }).fail(function (err) {
-                        expect(err).toBeUndefined();
-                    }).fin(done);
-                });
-            });
-
-            it('Test 013 : should not execute the designated hook when --nohooks option specifies the exact hook name', function (done) {
-                var test_event = 'before_build';
-                hookOptions.nohooks = ['before_build'];
-
-                return hooksRunner.fire(test_event, hookOptions).then(function (msg) {
-                    expect(msg).toBeDefined();
-                    expect(msg).toBe('hook before_build is disabled.');
-                }).fail(function (err) {
-                    expect(err).toBeUndefined();
-                }).then(function () {
-                    done();
-                });
-            });
-
-            it('Test 014 : should not execute a set of matched hooks when --nohooks option specifies the hook pattern.', function (done) {
-                var test_events = ['before_build', 'after_plugin_add', 'before_platform_rm', 'before_prepare'];
-                hookOptions.nohooks = ['before*'];
-
-                return test_events.reduce(function(soFar, test_event) {
-                    return soFar.then(function() {
-                        return hooksRunner.fire(test_event, hookOptions).then(function (msg) {
-                            if (msg) {
-                                expect(msg).toBe('hook ' + test_event + ' is disabled.');
-                            } else {
-                                expect(test_event).toBe('after_plugin_add');
-                            }
-                        });
-                    });
-                }, Q()).fail(function (err) {
-                    expect(err).toBeUndefined();
-                }).then(function () {
-                    done();
-                });
-            });
-
-            it('Test 015 : should not execute all hooks when --nohooks option specifies .', function (done) {
-                var test_events = ['before_build', 'after_plugin_add', 'before_platform_rm', 'before_prepare'];
-                hookOptions.nohooks = ['.'];
-
-                return test_events.reduce(function(soFar, test_event) {
-                    return soFar.then(function() {
-                        return hooksRunner.fire(test_event, hookOptions).then(function (msg) {
-                            expect(msg).toBeDefined();
-                            expect(msg).toBe('hook ' + test_event + ' is disabled.');
-                        });
-                    });
-                }, Q()).fail(function (err) {
-                    expect(err).toBeUndefined();
-                }).then(function () {
-                    done();
-                });
-            });
-        });
-
-        describe('module-level hooks (event handlers)', function() {
-            var handler = jasmine.createSpy().and.returnValue(Q());
-            var test_event = 'before_build';
-
-            afterEach(function () {
-                cordova.removeAllListeners(test_event);
-                handler.calls.reset();
-            });
-
-            it('Test 016 : should fire handlers using cordova.on', function(done) {
-                cordova.on(test_event, handler);
-                hooksRunner.fire(test_event, hookOptions).then(function () {
-                    expect(handler).toHaveBeenCalled();
-                }).fail(function (err) {
-                    expect(err).not.toBeDefined();
-                }).fin(done);
-            });
-
-            it('Test 017 : should pass the project root folder as parameter into the module-level handlers', function (done) {
-                cordova.on(test_event, handler);
-                hooksRunner.fire(test_event, hookOptions).then(function () {
-                    expect(handler).toHaveBeenCalledWith(hookOptions);
-                }).fail(function (err) {
-                    console.log(err);
-                    expect(err).not.toBeDefined();
-                }).fin(done);
-            });
-
-            it('Test 018 : should be able to stop listening to events using cordova.off', function(done) {
-                cordova.on(test_event, handler);
-                cordova.off(test_event, handler);
-                hooksRunner.fire(test_event, hookOptions).then(function () {
-                    expect(handler).not.toHaveBeenCalled();
-                }).fail(function (err) {
-                    console.log(err);
-                    expect(err).toBeUndefined();
-                }).fin(done);
-            });
-
-            it('Test 019 : should execute event listeners serially', function(done) {
-                var h1_fired = false;
-                var h2_fired;
-                var h1 = function() {
-                    expect(h2_fired).toBe(false);
-                    // Delay 100 ms here to check that h2 is not executed until after
-                    // the promise returned by h1 is resolved.
-                    var q = Q.delay(100).then(function() {
-                        h1_fired = true;
-                    });
-                    return q;
-                };
-                h2_fired = false;
-                var h2 = function() {
-                    h2_fired = true;
-                    expect(h1_fired).toBe(true);
-                    return Q();
-                };
-
-                cordova.on(test_event, h1);
-                cordova.on(test_event, h2);
-
-                return hooksRunner.fire(test_event, hookOptions).then(function() {
-                    expect(h1_fired).toBe(true);
-                    expect(h2_fired).toBe(true);
-                }).fail(function(err) {
-                    expect(err).toBeUndefined();
-                }).then(function() {
-                    cordova.removeAllListeners(test_event);
-                    done();
-                });
-            });
-
-            it('Test 020 : 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 h2_fired;
-                var h1 = function () {
-                    h1_fired = true;
-                    expect(h2_fired).toBe(false);
-                    return Q();
-                };
-                h2_fired = false;
-                var h2 = function () {
-                    h2_fired = true;
-                    expect(h1_fired).toBe(true);
-                    return Q();
-                };
-
-                cordova.on(test_event, h1);
-                cordova.on(test_event, h2);
-                hooksRunner.fire(test_event, hookOptions).then(function() {
-                    expect(h1_fired).toBe(true);
-                    expect(h2_fired).toBe(true);
-                    done();
-                });
-            });
-
-            it('Test 021 : should pass data object that fire calls into async handlers', function(done) {
-                var async = function (opts) {
-                    expect(opts).toEqual(hookOptions);
-                    return Q();
-                };
-                cordova.on(test_event, async);
-                hooksRunner.fire(test_event, hookOptions).then(function () {
-                    done();
-                });
-            });
-
-            it('Test 022 : should pass data object that fire calls into sync handlers', function(done) {
-                var async = function (opts) {
-                    expect(opts).toEqual(hookOptions);
-                };
-                cordova.on(test_event, async);
-                hooksRunner.fire(test_event, hookOptions).fin(done);
-            });
-
-            it('Test 023 : should error if any script exits with non-zero code', function(done) {
-                hooksRunner.fire('fail', hookOptions).then(function () {
-                    expect('the call').toBe('a failure');
-                }, function (err) {
-                    expect(err).toBeDefined();
-                }).fin(done);
-            });
-        });
-
-        it('Test 024 :should not error if the hook is unrecognized', function(done) {
-            hooksRunner.fire('CLEAN YOUR SHORTS GODDAMNIT LIKE A BIG BOY!', hookOptions).fail(function (err) {
-                expect('Call with unrecognized hook ').toBe('successful.\n' + err);
-            }).fin(done);
-        });
-    });
-
-    // Cleanup. Must be the last spec. Is there a better place for final cleanup in Jasmine?
-    it('Test 025 : should not fail during cleanup', function () {
-        process.chdir(path.join(__dirname, '..'));  // Non e2e tests assume CWD is repo root.
-        if (ext == 'sh') {
-            shell.rm('-rf', tmpDir);
-        } else { // Windows:
-            // For some mysterious reason, both shell.rm and RMDIR /S /Q won't
-            // delete the dir on Windows, but they do remove the files leaving
-            // only folders. But the dir is removed just fine by
-            // shell.rm('-rf', tmpDir) at the top of this file with the next
-            // invocation of this test. The benefit of RMDIR /S /Q is that it
-            // doesn't print warnings like shell.rmdir() that look like this:
-            // rm: could not remove directory (code ENOTEMPTY): C:\Users\...
-            var cmd = 'RMDIR /S /Q ' + tmpDir;
-            child_process.exec(cmd);
-        }
-    });
-});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/04c12e2b/spec-cordova/plugin_fetch.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/plugin_fetch.spec.js b/spec-cordova/plugin_fetch.spec.js
deleted file mode 100644
index eab2768..0000000
--- a/spec-cordova/plugin_fetch.spec.js
+++ /dev/null
@@ -1,536 +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 plugin  = require('../src/cordova/plugin'),
-    helpers = require('./helpers'),
-    path    = require('path'),
-    events = require('cordova-common').events,
-    shell   = require('shelljs');
-
-var testPluginVersions = [
-    '0.0.2',
-    '0.7.0',
-    '1.0.0',
-    '1.1.0',
-    '1.1.3',
-    '1.3.0',
-    '1.7.0',
-    '1.7.1',
-    '2.0.0-rc.1',
-    '2.0.0-rc.2',
-    '2.0.0',
-    '2.3.0'
-];
-
-var cordovaVersion = '3.4.2';
-
-var tempDir = helpers.tmpDir('plugin_fetch_spec');
-var project = path.join(tempDir, 'project');
-
-var getVersionErrorCallback;
-var warnings = [];
-
-// Used to extract the constraint, the installed version, and the required
-// semver range from a warning message
-var UNMET_REQ_REGEX = /\s+([^\s]+)[^\d]+(\d+\.\d+\.\d+) in project, (.+) required\)/;
-
-// We generate warnings when we don't fetch latest. Collect them to make sure we
-// are making the correct warnings
-events.on('warn', function(warning) {
-    warnings.push(warning);
-});
-
-// Tests a sample engine against the installed platforms/plugins in our test
-// project
-function testEngineWithProject(done, testEngine, testResult) {
-    plugin.getFetchVersion(project,
-        {
-            'version': '2.3.0',
-            'name': 'test-plugin',
-            'engines': { 'cordovaDependencies': testEngine },
-            'versions': testPluginVersions
-        }, cordovaVersion)
-    .then(function(toFetch) {
-        expect(toFetch).toBe(testResult);
-    })
-    .fail(getVersionErrorCallback)
-    .fin(done);
-}
-
-// Checks the warnings that were printed by the CLI to ensure that the code is
-// listing the correct reasons for failure. Checks against the global warnings
-// object which is reset before each test
-function checkUnmetRequirements(requirements) {
-    var reqWarnings = [];
-
-    warnings.forEach(function(warning) {
-        var extracted = UNMET_REQ_REGEX.exec(warning);
-        if (extracted) {
-            reqWarnings.push({
-                dependency: extracted[1],
-                installed: extracted[2],
-                required: extracted[3]
-            });
-        }
-    });
-    expect(reqWarnings.length).toEqual(requirements.length);
-
-
-    requirements.forEach(function(requirement) {
-        expect(reqWarnings).toContainArray(function(extractedWarning) {
-            return  extractedWarning.dependency === requirement.dependency.trim() &&
-                    extractedWarning.installed  === requirement.installed.trim() &&
-                    extractedWarning.required   === requirement.required.trim();
-        }, requirement);
-    });
-}
-
-// Helper functions for creating the requirements objects taken by
-// checkUnmetRequirements()
-function getPlatformRequirement(requirement) {
-    return {
-        dependency: 'cordova-android',
-        installed: '3.1.0',
-        required: requirement
-    };
-}
-
-function getCordovaRequirement(requirement) {
-    return {
-        dependency: 'cordova',
-        installed: cordovaVersion,
-        required: requirement
-    };
-}
-
-function getPluginRequirement(requirement) {
-    return {
-        dependency: 'ca.filmaj.AndroidPlugin',
-        installed: '4.2.0',
-        required: requirement
-    };
-}
-
-// Generates a callback that checks warning messages after the test is complete
-function getWarningCheckCallback(done, requirements) {
-    return function() {
-        checkUnmetRequirements(requirements);
-        expect(getVersionErrorCallback).not.toHaveBeenCalled();
-        done();
-    };
-}
-
-function createTestProject() {
-    // Get the base project
-    shell.cp('-R', path.join(__dirname, 'fixtures', 'base'), tempDir);
-    shell.mv(path.join(tempDir, 'base'), project);
-
-    // Copy a platform and a plugin to our sample project
-    shell.cp('-R',
-        path.join(__dirname, 'fixtures', 'platforms', helpers.testPlatform),
-        path.join(project, 'platforms'));
-    shell.cp('-R',
-        path.join(__dirname, 'fixtures', 'plugins', 'android'),
-        path.join(project, 'plugins'));
-}
-
-function removeTestProject() {
-    shell.rm('-rf', tempDir);
-}
-
-describe('plugin fetching version selection', function() {
-    createTestProject();
-    beforeEach(function() {
-        jasmine.addMatchers({
-            'toContainArray': function() {
-                return {
-                    compare: function(actual, expected) {
-                        var result = {};
-                        result.pass = false;
-                        for(var i = 0; i < actual.length; i++) {
-                            if (expected(actual[i])) {
-                                result.pass = true;
-                                break;
-                            }
-                        }
-                        return result;
-                    }
-                };
-            }
-        });
-
-        warnings = [];
-        getVersionErrorCallback = jasmine.createSpy('unexpectedPluginFetchErrorCallback');
-    });
-
-    it('Test 001 : should handle a mix of upper bounds and single versions', function(done) {
-        var testEngine = {
-            '0.0.0' : { 'cordova-android': '1.0.0' },
-            '0.0.2' : { 'cordova-android': '>1.0.0' },
-            '<1.0.0': { 'cordova-android': '<2.0.0' },
-            '1.0.0' : { 'cordova-android': '>2.0.0' },
-            '1.7.0' : { 'cordova-android': '>4.0.0' },
-            '<2.3.0': { 'cordova-android': '<6.1.1' },
-            '2.3.0' : { 'cordova-android': '6.1.1' }
-        };
-
-        var after = getWarningCheckCallback(done, [
-            getPlatformRequirement('6.1.1')
-        ]);
-        testEngineWithProject(after, testEngine, '1.3.0');
-    }, 6000);
-
-    it('Test 002 : should apply upper bound engine constraints when there are no unspecified constraints above the upper bound', function(done) {
-        var testEngine = {
-            '1.0.0' : { 'cordova-android': '>2.0.0' },
-            '1.7.0' : { 'cordova-android': '>4.0.0' },
-            '<2.3.0': {
-                'cordova-android': '<6.1.1',
-                'ca.filmaj.AndroidPlugin': '<1.0.0'
-            },
-            '2.3.0' : { 'cordova-android': '6.1.1' }
-        };
-
-        var after = getWarningCheckCallback(done, [
-            getPlatformRequirement('6.1.1')
-        ]);
-        testEngineWithProject(after, testEngine, null);
-    });
-
-    it('Test 003 : should apply upper bound engine constraints when there are unspecified constraints above the upper bound', function(done) {
-        var testEngine = {
-            '0.0.0' : {},
-            '2.0.0' : { 'cordova-android': '~5.0.0' },
-            '<1.0.0': { 'cordova-android': '>5.0.0' }
-        };
-
-        var after = getWarningCheckCallback(done, [
-            getPlatformRequirement('~5.0.0')
-        ]);
-        testEngineWithProject(after, testEngine, '1.7.1');
-
-    });
-
-    it('Test 004 : should handle the case where there are no constraints for earliest releases', function(done) {
-        var testEngine = {
-            '1.0.0' : { 'cordova-android': '~5.0.0' }
-        };
-
-        var after = getWarningCheckCallback(done, [
-            getPlatformRequirement('~5.0.0')
-        ]);
-        testEngineWithProject(after, testEngine, '0.7.0');
-
-    });
-
-    it('Test 005 : should handle the case where the lowest version is unsatisfied', function(done) {
-        var testEngine = {
-            '0.0.2' : { 'cordova-android': '~5.0.0' }
-        };
-
-        var after = getWarningCheckCallback(done, [
-            getPlatformRequirement('~5.0.0')
-        ]);
-        testEngineWithProject(after, testEngine, null);
-
-    });
-
-    it('Test 006 : should handle upperbounds if no single version constraints are given', function(done) {
-        var testEngine = {
-            '<1.0.0': { 'cordova-android': '<2.0.0' }
-        };
-
-        var after = getWarningCheckCallback(done, []);
-
-        testEngineWithProject(after, testEngine, '2.3.0');
-
-    });
-
-    it('Test 007 : should apply upper bounds greater than highest version', function(done) {
-        var testEngine = {
-            '0.0.0' : {},
-            '<5.0.0': { 'cordova-android': '<2.0.0' }
-        };
-
-        var after = getWarningCheckCallback(done, [
-            getPlatformRequirement('<2.0.0')
-        ]);
-
-        testEngineWithProject(after, testEngine, null);
-
-    });
-
-    it('Test 008 : should treat empty constraints as satisfied', function(done) {
-        var testEngine = {
-            '1.0.0' : {},
-            '1.1.0' : { 'cordova-android': '>5.0.0' }
-        };
-
-        var after = getWarningCheckCallback(done, [
-            getPlatformRequirement('>5.0.0')
-        ]);
-
-        testEngineWithProject(after, testEngine, '1.0.0');
-
-    });
-
-    it('Test 009 : should ignore an empty cordovaDependencies entry', function(done) {
-        var testEngine = {};
-
-        var after = getWarningCheckCallback(done, []);
-
-        testEngineWithProject(after, testEngine, null);
-
-    });
-
-    it('Test 010 : should ignore a badly formatted semver range', function(done) {
-        var testEngine = {
-            '1.1.3' : { 'cordova-android': 'badSemverRange' }
-        };
-
-        var after = getWarningCheckCallback(done, []);
-
-        testEngineWithProject(after, testEngine, '2.3.0');
-
-    });
-
-    it('Test 011 : should respect unreleased versions in constraints', function(done) {
-        var testEngine = {
-            '1.0.0' : { 'cordova-android': '3.1.0' },
-            '1.1.2' : { 'cordova-android': '6.1.1' },
-            '1.3.0' : { 'cordova-android': '6.1.1' }
-        };
-
-        var after = getWarningCheckCallback(done, [
-            getPlatformRequirement('6.1.1')
-        ]);
-
-        testEngineWithProject(after, testEngine, '1.1.0');
-
-    });
-
-    it('Test 012 : should respect plugin constraints', function(done) {
-        var testEngine = {
-            '0.0.0' : { 'ca.filmaj.AndroidPlugin': '1.2.0' },
-            '1.1.3' : { 'ca.filmaj.AndroidPlugin': '<5.0.0 || >2.3.0' },
-            '2.3.0' : { 'ca.filmaj.AndroidPlugin': '6.1.1' }
-        };
-
-        var after = getWarningCheckCallback(done, [
-            getPluginRequirement('6.1.1')
-        ]);
-
-        testEngineWithProject(after, testEngine, '2.0.0');
-
-    });
-
-    it('Test 013 : should respect cordova constraints', function(done) {
-        var testEngine = {
-            '0.0.0' : { 'cordova': '>1.0.0' },
-            '1.1.3' : { 'cordova': '<3.0.0 || >4.0.0' },
-            '2.3.0' : { 'cordova': '6.1.1' }
-        };
-
-        var after = getWarningCheckCallback(done, [
-            getCordovaRequirement('6.1.1')
-        ]);
-
-        testEngineWithProject(after, testEngine, '1.1.0');
-
-    });
-
-    it('Test 014 : should not include pre-release versions', function(done) {
-        var testEngine = {
-            '0.0.0' : {},
-            '2.0.0' : { 'cordova-android': '>5.0.0' }
-        };
-
-        var after = getWarningCheckCallback(done, [
-            getPlatformRequirement('>5.0.0')
-        ]);
-
-        // Should not return 2.0.0-rc.2
-        testEngineWithProject(after, testEngine, '1.7.1');
-
-    });
-
-    it('Test 015 : should not fail if there is no engine in the npm info', function(done) {
-        plugin.getFetchVersion(project, {
-                version: '2.3.0',
-                name: 'test-plugin',
-                versions: testPluginVersions
-            }, cordovaVersion)
-        .then(function(toFetch) {
-            expect(toFetch).toBe(null);
-        })
-        .fail(getVersionErrorCallback).fin(done);
-    });
-
-    it('Test 016 : should not fail if there is no cordovaDependencies in the engines', function(done) {
-        var after = getWarningCheckCallback(done, []);
-
-        plugin.getFetchVersion(project, {
-                version: '2.3.0',
-                name: 'test-plugin',
-                versions: testPluginVersions,
-                engines: {
-                    'node': '>7.0.0',
-                    'npm': '~2.0.0'
-                }
-            }, cordovaVersion)
-        .then(function(toFetch) {
-            expect(toFetch).toBe(null);
-        })
-        .fail(getVersionErrorCallback).fin(after);
-
-    });
-
-    it('Test 017 : should handle extra whitespace', function(done) {
-        var testEngine = {
-            '  1.0.0    '   : {},
-            '2.0.0   '      : { ' cordova-android': '~5.0.0   ' },
-            ' <  1.0.0\t'   : { ' cordova-android  ': ' > 5.0.0' }
-        };
-
-        var after = getWarningCheckCallback(done, [
-            getPlatformRequirement('~5.0.0')
-        ]);
-
-        testEngineWithProject(after, testEngine, '1.7.1');
-
-    });
-
-    it('Test 018 : should ignore badly typed version requirement entries', function(done) {
-        var testEngine = {
-            '1.1.0' : ['cordova', '5.0.0'],
-            '1.3.0' : undefined,
-            '1.7.0' : null
-        };
-
-        var after = getWarningCheckCallback(done, []);
-
-        testEngineWithProject(after, testEngine, '2.3.0');
-
-    });
-
-    it('Test 019 : should ignore badly typed constraint entries', function(done) {
-        var testEngine = {
-            '0.0.2' : { 'cordova': 1 },
-            '0.7.0' : { 'cordova': {}},
-            '1.0.0' : { 'cordova': undefined},
-            '1.1.3' : { 8        : '5.0.0'},
-            '1.3.0' : { 'cordova': [] },
-            '1.7.1' : { 'cordova': null }
-        };
-
-        var after = getWarningCheckCallback(done, []);
-
-        testEngineWithProject(after, testEngine, '2.3.0');
-
-    });
-
-    it('Test 020 : should ignore bad semver versions', function(done) {
-        var testEngine = {
-            '0.0.0'         : { 'cordova-android': '5.0.0' },
-            'notAVersion'   : { 'cordova-android': '3.1.0' },
-            '^1.1.2'        : { 'cordova-android': '3.1.0' },
-            '<=1.3.0'       : { 'cordova-android': '3.1.0' },
-            '1.0'           : { 'cordova-android': '3.1.0' },
-            '2'               : { 'cordova-android': '3.1.0' }
-        };
-
-        var after = getWarningCheckCallback(done, [
-            getPlatformRequirement('5.0.0')
-        ]);
-
-        testEngineWithProject(after, testEngine, null);
-    });
-
-    it('Test 021 : should not fail if there are bad semver versions', function(done) {
-        var testEngine = {
-            'notAVersion'   : { 'cordova-android': '3.1.0' },
-            '^1.1.2'        : { 'cordova-android': '3.1.0' },
-            '<=1.3.0'       : { 'cordova-android': '3.1.0' },
-            '1.0.0'         : { 'cordova-android': '~3'    },   // Good semver
-            '2.0.0'         : { 'cordova-android': '5.1.0' },   // Good semver
-            '1.0'           : { 'cordova-android': '3.1.0' },
-            '2'               : { 'cordova-android': '3.1.0' }
-        };
-
-        var after = getWarningCheckCallback(done, [
-            getPlatformRequirement('5.1.0')
-        ]);
-
-        testEngineWithProject(after, testEngine, '1.7.1');
-    });
-
-    it('Test 022 : should properly warn about multiple unmet requirements', function(done) {
-        var testEngine = {
-            '1.7.0' : {
-                'cordova-android'           : '>5.1.0',
-                'ca.filmaj.AndroidPlugin'   : '3.1.0',
-                'cordova'                   : '3.4.2'
-            }
-        };
-
-        var after = getWarningCheckCallback(done, [
-            getPlatformRequirement('>5.1.0'),
-            getPluginRequirement('3.1.0')
-        ]);
-
-        testEngineWithProject(after, testEngine, '1.3.0');
-    });
-
-    it('Test 023 : should properly warn about both unmet latest and upper bound requirements', function(done) {
-        var testEngine = {
-            '1.7.0' : { 'cordova-android': '>5.1.0' },
-            '<5.0.0': {
-                'cordova-android'           : '>7.1.0',
-                'ca.filmaj.AndroidPlugin'   : '3.1.0'
-            }
-        };
-
-        var after = getWarningCheckCallback(done, [
-            getPlatformRequirement('>5.1.0 AND >7.1.0'),
-            getPluginRequirement('3.1.0')
-        ]);
-
-        testEngineWithProject(after, testEngine, null);
-    });
-
-    it('Test 024 : should not warn about versions past latest', function(done) {
-        var testEngine = {
-            '1.7.0' : { 'cordova-android': '>5.1.0' },
-            '7.0.0': {
-                'cordova-android'           : '>7.1.0',
-                'ca.filmaj.AndroidPlugin'   : '3.1.0'
-            }
-        };
-
-        var after = getWarningCheckCallback(done, [
-            getPlatformRequirement('>5.1.0')
-        ]);
-
-        testEngineWithProject(after, testEngine, '1.3.0');
-    });
-
-    it('Test 025 : clean up after plugin fetch spec', function() {
-        removeTestProject();
-    });
-});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/04c12e2b/spec-plugman/fetch.spec.js
----------------------------------------------------------------------
diff --git a/spec-plugman/fetch.spec.js b/spec-plugman/fetch.spec.js
deleted file mode 100644
index f772822..0000000
--- a/spec-plugman/fetch.spec.js
+++ /dev/null
@@ -1,339 +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 rewire  = require('rewire'),
-    fetch   = rewire('../src/plugman/fetch'),
-    fs      = require('fs'),
-    os      = require('os'),
-    path    = require('path'),
-    shell   = require('shelljs'),
-    realrm  = shell.rm,
-    TIMEOUT = 60 * 1000,
-    //xml_helpers = require('../src/util/xml-helpers'),
-    metadata = require('../src/plugman/util/metadata'),
-    temp    = path.join(os.tmpdir(), 'plugman', 'fetch'),
-    test_plugin = path.join(__dirname, 'plugins', 'org.test.plugins.childbrowser'),
-    test_pkgjson_plugin = path.join(__dirname, 'plugins', 'pkgjson-test-plugin'),
-    test_plugin_searchpath = path.join(test_plugin, '..'),
-    //test_plugin_with_space = path.join(__dirname, 'folder with space', 'plugins', 'org.test.plugins.childbrowser'),
-    //test_plugin_xml = xml_helpers.parseElementtreeSync(path.join(test_plugin, 'plugin.xml')),
-    test_plugin_id = 'org.test.plugins.childbrowser',
-    test_plugin_version ='0.6.0',
-    plugins = require('../src/plugman/util/plugins'),
-    Q = require('q'),
-    registry = require('../src/plugman/registry/registry');
-
-describe('fetch', function() {
-
-    function wrapper(p, done, post) {
-        p.then(post, function(err) {
-            expect(err).toBeUndefined('Unexpected exception' + err.stack);
-        }).fin(done);
-    }
-    /*
-     * Taking out the following test. Fetch has a copyPlugin method that uses existsSync to see if a plugin already exists in the plugins folder. If the plugin exists in the plugins directory for the cordova project, it won't be copied over. This test fails now due it always returning true for existsSync.
-    describe('plugin in a dir with spaces', function() {
-        it('should copy locally-available plugin to plugins directory when spaces in path', function(done) {
-            // XXX: added this because plugman tries to fetch from registry when plugin folder does not exist
-            spyOn(fs,'existsSync').andReturn(true);
-            spyOn(xml_helpers, 'parseElementtreeSync').andReturn(test_plugin_xml);
-            spyOn(shell, 'rm');
-            spyOn(metadata, 'save_fetch_metadata');
-            var cp = spyOn(shell, 'cp');
-            wrapper(fetch(test_plugin_with_space, temp), done, function() {
-                expect(cp).toHaveBeenCalledWith('-R', path.join(test_plugin_with_space, '*'), path.join(temp, test_plugin_id));
-            });
-        });
-    });
-*/
-    describe('local plugins', function() {
-        var rm, sym, cp, save_metadata, revertLocal, revertFetch, fetchCalls = 0;
-        beforeEach(function() {
-            rm = spyOn(shell, 'rm');
-            sym = spyOn(fs, 'symlinkSync');
-            cp = spyOn(shell, 'cp').and.callThrough();
-            save_metadata = spyOn(metadata, 'save_fetch_metadata');
-            realrm('-rf', temp);
-            revertLocal = fetch.__set__('localPlugins', null);
-            revertFetch = fetch.__set__('fetch', function(pluginDir) {
-                fetchCalls ++;
-                return Q(pluginDir);
-            });
-        });
-
-        afterEach(function(){
-            revertLocal();
-            revertFetch();
-            fetchCalls = 0;
-        });
-
-        it('Test 001 : should copy locally-available plugin to plugins directory', function(done) {
-            wrapper(fetch(test_plugin, temp), done, function() {
-                expect(cp).toHaveBeenCalledWith('-R', path.join(test_plugin, '*'), path.join(temp, test_plugin_id));
-            });
-        });
-        it('Test 002 : should copy locally-available plugin to plugins directory when adding a plugin with searchpath argument', function(done) {
-            wrapper(fetch(test_plugin_id, temp, { searchpath: test_plugin_searchpath }), done, function() {
-                expect(cp).toHaveBeenCalledWith('-R', path.join(test_plugin, '*'), path.join(temp, test_plugin_id));
-            });
-        });
-        it('Test 003 : should create a symlink if used with `link` param', function(done) {
-            wrapper(fetch(test_plugin, temp, { link: true }), done, function() {
-                expect(sym).toHaveBeenCalledWith(test_plugin, path.join(temp, test_plugin_id), 'dir');
-            });
-        });
-
-        it('Test 004 : should fail when the expected ID doesn\'t match', function(done) {
-            fetch(test_plugin, temp, { expected_id: 'wrongID' })
-            .then(function() {
-                expect('this call').toBe('fail');
-            }, function(err) {
-                expect(''+err).toContain('Expected plugin to have ID "wrongID" but got');
-            }).fin(done);
-        });
-
-        it('Test 005 : should succeed when the expected ID is correct', function(done) {
-            wrapper(fetch(test_plugin, temp, { expected_id: test_plugin_id }), done, function() {
-                expect(1).toBe(1);
-            });
-        });
-        it('Test 006 : should fail when the expected ID with version specified doesn\'t match', function(done) {
-            fetch(test_plugin, temp, { expected_id: test_plugin_id + '@wrongVersion' })
-            .then(function() {
-                expect('this call').toBe('fail');
-            }, function(err) {
-                expect(''+err).toContain('to satisfy version "wrongVersion" but got');
-            }).fin(done);
-        });
-        it('Test 007 : should succeed when the plugin version specified is correct', function(done) {
-            var exp_id = test_plugin_id + '@' + test_plugin_version;
-            wrapper(fetch(test_plugin, temp, { expected_id: exp_id}), done, function() {
-                expect(1).toBe(1);
-            });
-        });
-        it('Test 027 : should copy locally-available plugin to plugins directory', function(done) {
-            wrapper(fetch(test_pkgjson_plugin, temp, {fetch:true}), done, function() {
-                expect(cp).toHaveBeenCalledWith('-R', path.join(test_pkgjson_plugin, '*'), path.join(temp, 'pkgjson-test-plugin'));
-                expect(fetchCalls).toBe(1);
-            });
-        });
-        it('Test 028 : should fail when locally-available plugin is missing pacakge.json', function(done) {
-            fetch(test_plugin, temp, {fetch:true})
-            .then(function() {
-                expect(false).toBe(true);
-            }).fail(function(err) { 
-                expect(err).toBeDefined();
-                expect(err.message).toContain('needs a valid package.json');
-                done();
-            });
-        });
-    });
-    describe('git plugins', function() {
-        var clone, save_metadata, done;
-
-        beforeEach(function() {
-            clone = spyOn(plugins, 'clonePluginGitRepo').and.returnValue(Q(test_plugin));
-            save_metadata = spyOn(metadata, 'save_fetch_metadata');
-            done = false;
-        });
-        it('Test 008 : should call clonePluginGitRepo for https:// and git:// based urls', function(done) {
-            var url = 'https://github.com/bobeast/GAPlugin.git';
-                fetch(url, temp).then(function(){
-                    expect(save_metadata).toHaveBeenCalled();
-                    expect(clone).toHaveBeenCalledWith(url, temp, '.', undefined, undefined);
-                    done();
-            });
-        }, 6000);
-        
-        it('Test 009 : should call clonePluginGitRepo with subdir if applicable', function(done) {
-            var url = 'https://github.com/bobeast/GAPlugin.git';
-            var dir = 'fakeSubDir';
-            fetch(url, temp, { subdir: dir }).then(function(){
-                expect(clone).toHaveBeenCalledWith(url, temp, dir, undefined, undefined);
-                expect(save_metadata).toHaveBeenCalled();
-                done();
-            });
-        }, 6000);
-
-        it('Test 010 : should call clonePluginGitRepo with subdir and git ref if applicable', function(done) {
-            var url = 'https://github.com/bobeast/GAPlugin.git';
-            var dir = 'fakeSubDir';
-            var ref = 'fakeGitRef';
-            fetch(url, temp, { subdir: dir, git_ref: ref }).then(function(){
-                expect(clone).toHaveBeenCalledWith(url, temp, dir, ref, undefined);
-                expect(save_metadata).toHaveBeenCalled();
-                done();
-            });
-        }, 6000);
-
-        it('Test 011 : should extract the git ref from the URL hash, if provided', function(done) {
-            var url = 'https://github.com/bobeast/GAPlugin.git#fakeGitRef';
-            var baseURL = 'https://github.com/bobeast/GAPlugin.git';
-            fetch(url, temp, {}).then(function(){
-                expect(clone).toHaveBeenCalledWith(baseURL, temp, '.', 'fakeGitRef', undefined);
-                expect(save_metadata).toHaveBeenCalled();
-                done();
-            });
-        }, 6000);
-
-        it('Test 012 : should extract the subdir from the URL hash, if provided', function(done) {
-            var url = 'https://github.com/bobeast/GAPlugin.git#:fakeSubDir';
-            var baseURL = 'https://github.com/bobeast/GAPlugin.git';
-            fetch(url, temp, {}).then(function(result){
-                expect(clone).toHaveBeenCalledWith(baseURL, temp, 'fakeSubDir', undefined, undefined);
-                expect(save_metadata).toHaveBeenCalled();
-                done();
-            });
-        }, 6000);
-
-        it('Test 013 : should extract the git ref and subdir from the URL hash, if provided', function(done) {
-            var url = 'https://github.com/bobeast/GAPlugin.git#fakeGitRef:/fake/Sub/Dir/';
-            var baseURL = 'https://github.com/bobeast/GAPlugin.git';
-            fetch(url, temp, {}).then(function(result){
-                expect(clone).toHaveBeenCalledWith(baseURL, temp, 'fake/Sub/Dir', 'fakeGitRef', undefined);
-                expect(save_metadata).toHaveBeenCalled();
-                done();
-            });
-        }, 6000);
-
-        it('Test 014 : should fail when the expected ID doesn\'t match', function(done) {
-            fetch('https://github.com/bobeast/GAPlugin.git', temp, { expected_id: 'wrongID' })
-            .then(function() {
-                expect('this call').toBe('fail');
-            }, function(err) {
-                expect(''+err).toContain('Expected plugin to have ID "wrongID" but got');
-            }).fin(done);
-        });
-
-        it('Test 015 : should fail when the expected ID with version specified doesn\'t match', function(done) {
-            fetch('https://github.com/bobeast/GAPlugin.git', temp, { expected_id: 'id@wrongVersion' })
-            .then(function() {
-                expect('this call').toBe('fail');
-            }, function(err) {
-                expect(''+err).toContain('Expected plugin to have ID "id" but got');
-            }).fin(done);
-        });
-
-        it('Test 016 : should succeed when the expected ID is correct', function(done) {
-            wrapper(fetch('https://github.com/bobeast/GAPlugin.git', temp, { expected_id: test_plugin_id }), done, function() {
-                expect(1).toBe(1);
-            });
-        });
-    });
-
-    describe('github plugins', function() {
-        // these tests actually pull a plugin from github
-        beforeEach(function(){
-            realrm('-rf',temp);
-        });
-
-        // this commit uses the new id
-        it('Test 017 : should fetch from a commit-sha', function(done) {
-            wrapper(fetch('http://github.com/apache/cordova-plugin-device.git#ad5f1e7bfd05ef98c01df549a0fa98036a5625db', temp, { expected_id: 'cordova-plugin-device' }), done, function() {
-                expect(1).toBe(1);
-                done();
-            });
-        }, TIMEOUT);
-        // this branch uses the old id
-        it('Test 018 : should fetch from a branch', function(done) {
-            wrapper(fetch('http://github.com/apache/cordova-plugin-device.git#cdvtest', temp, { expected_id: 'org.apache.cordova.device' }), done, function() {
-                expect(1).toBe(1);
-                done();
-            });
-        }, TIMEOUT);
-        // this tag uses the new id
-        it('Test 019 : should fetch from a tag', function(done) {
-            wrapper(fetch('http://github.com/apache/cordova-plugin-device.git#r1.0.0', temp, { expected_id: 'cordova-plugin-device' }), done, function() {
-                expect(1).toBe(1);
-                done();
-            });
-        }, TIMEOUT);
-    });
-
-    describe('fetch recursive error CB-8809', function(){
-
-        var srcDir = path.join(__dirname, 'plugins/recursivePlug');
-        var appDir = path.join(__dirname, 'plugins/recursivePlug/demo');
-
-        if(/^win/.test(process.platform)) {
-            it('Test 020 : should copy all but the /demo/ folder',function(done) {
-                var cp = spyOn(shell, 'cp');
-                wrapper(fetch(srcDir, appDir),done, function() {
-                    expect(cp).toHaveBeenCalledWith('-R',path.join(srcDir,'asset.txt'),path.join(appDir,'test-recursive'));
-                    expect(cp).not.toHaveBeenCalledWith('-R',srcDir,path.join(appDir,'test-recursive'));
-                });
-            });
-        }
-        else {
-            it('Test 021 : should skip copy to avoid recursive error', function(done) {
-
-                var cp = spyOn(shell, 'cp').and.callFake(function(){});
-
-                wrapper(fetch(srcDir, appDir),done, function() {
-                    expect(cp).not.toHaveBeenCalled();
-                });
-            });
-        }
-
-    });
-
-    describe('registry plugins', function() {
-        var pluginId = 'dummyplugin', sFetch;
-        var rm, sym, save_metadata;
-        beforeEach(function() {
-            rm = spyOn(shell, 'rm');
-            sym = spyOn(fs, 'symlinkSync');
-            save_metadata = spyOn(metadata, 'save_fetch_metadata');
-            sFetch = spyOn(registry, 'fetch').and.returnValue(Q(test_plugin));
-            realrm('-rf', temp);
-        });
-
-        it('Test 022 : should fail when the expected ID with version specified doesn\'t match', function(done) {
-            //fetch(pluginId, temp, { expected_id: test_plugin_id + '@wrongVersion' })
-            fetch(pluginId, temp, { expected_id: 'wrongID' })
-            .then(function() {
-                expect('this call').toBe('fail');
-            }, function(err) {
-                expect(''+err).toContain('Expected plugin to have ID "wrongID" but got');
-            }).fin(done);
-        });
-        
-        it('Test 023 : should succeed when the expected ID is correct', function(done) {
-            wrapper(fetch(pluginId, temp, { expected_id: test_plugin_id }), done, function() {
-                expect(1).toBe(1);
-            });
-        });
-        it('Test 024 : should succeed when the plugin version specified is correct', function(done) {
-            wrapper(fetch(pluginId, temp, { expected_id: test_plugin_id + '@' + test_plugin_version }), done, function() {
-                expect(1).toBe(1);
-            });
-        });
-        it('Test 025 : should fetch plugins that are scoped packages', function(done) {
-            var scopedPackage = '@testcope/dummy-plugin';
-            wrapper(fetch(scopedPackage, temp, { expected_id: test_plugin_id }), done, function() {
-                expect(sFetch).toHaveBeenCalledWith([scopedPackage]);
-            });
-        });
-        it('Test 026 : should fetch plugins that are scoped packages and have versions specified', function(done) {
-            var scopedPackage = '@testcope/dummy-plugin@latest';
-            wrapper(fetch(scopedPackage, temp, { expected_id: test_plugin_id }), done, function() {
-                expect(sFetch).toHaveBeenCalledWith([scopedPackage]);
-            });
-        });
-    });
-});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/04c12e2b/spec-plugman/uninstall.spec.js
----------------------------------------------------------------------
diff --git a/spec-plugman/uninstall.spec.js b/spec-plugman/uninstall.spec.js
deleted file mode 100644
index d1b2cbb..0000000
--- a/spec-plugman/uninstall.spec.js
+++ /dev/null
@@ -1,333 +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.
-*/
-
-/* jshint sub:true */
-
-var uninstall = require('../src/plugman/uninstall'),
-    install = require('../src/plugman/install'),
-    actions = require('cordova-common').ActionStack,
-    PluginInfo = require('cordova-common').PluginInfo,
-    events = require('cordova-common').events,
-    plugman = require('../src/plugman/plugman'),
-    common  = require('./common'),
-    platforms = require('../src/platforms/platforms'),
-    xmlHelpers = require('cordova-common').xmlHelpers,
-    et      = require('elementtree'),
-    fs      = require('fs'),
-    path    = require('path'),
-    shell   = require('shelljs'),
-    Q       = require('q'),
-    spec    = __dirname,
-    done    = false,
-    srcProject = path.join(spec, 'projects', 'android'),
-    project = path.join(spec, 'projects', 'android_uninstall.test'),
-    project2 = path.join(spec, 'projects', 'android_uninstall.test2'),
-    project3 = path.join(spec, 'projects', 'android_uninstall.test3'),
-
-    plugins_dir = path.join(spec, 'plugins'),
-    plugins_install_dir = path.join(project, 'cordova', 'plugins'),
-    plugins_install_dir2 = path.join(project2, 'cordova', 'plugins'),
-    plugins_install_dir3 = path.join(project3, 'cordova', 'plugins'),
-
-    plugins = {
-        'org.test.plugins.dummyplugin' : path.join(plugins_dir, 'org.test.plugins.dummyplugin'),
-        'A' : path.join(plugins_dir, 'dependencies', 'A'),
-        'C' : path.join(plugins_dir, 'dependencies', 'C')
-    },
-    dummy_id = 'org.test.plugins.dummyplugin';
-
-var dummyPluginInfo = new PluginInfo(plugins['org.test.plugins.dummyplugin']);
-
-var TEST_XML = '<?xml version="1.0" encoding="UTF-8"?>\n' +
-    '<widget xmlns     = "http://www.w3.org/ns/widgets"\n' +
-    '        xmlns:cdv = "http://cordova.apache.org/ns/1.0"\n' +
-    '        id        = "io.cordova.hellocordova"\n' +
-    '        version   = "0.0.1">\n' +
-    '    <name>Hello Cordova</name>\n' +
-    '    <description>\n' +
-    '        A sample Apache Cordova application that responds to the deviceready event.\n' +
-    '    </description>\n' +
-    '    <author href="http://cordova.io" email="dev@cordova.apache.org">\n' +
-    '        Apache Cordova Team\n' +
-    '    </author>\n' +
-    '    <content src="index.html" />\n' +
-    '    <access origin="*" />\n' +
-    '</widget>\n';
-
-describe('plugman uninstall start', function() {
-    beforeEach(function () {
-        var origParseElementtreeSync = xmlHelpers.parseElementtreeSync.bind(xmlHelpers);
-        spyOn(xmlHelpers, 'parseElementtreeSync').and.callFake(function(path) {
-            if (/config.xml$/.test(path)) return new et.ElementTree(et.XML(TEST_XML));
-            return origParseElementtreeSync(path);
-        });
-    });
-
-    it('Test 001 : plugman uninstall start', function(done) {
-        shell.rm('-rf', project, project2, project3);
-        shell.cp('-R', path.join(srcProject, '*'), project);
-        shell.cp('-R', path.join(srcProject, '*'), project2);
-        shell.cp('-R', path.join(srcProject, '*'), project3);
-
-        return install('android', project, plugins['org.test.plugins.dummyplugin'])
-        .then(function(result){
-            return install('android', project, plugins['A']);
-        }).then( function(){
-            return install('android', project2, plugins['C']);
-        }).then(function(){
-            return install('android', project2, plugins['A']);
-        }).then(function(){
-            return install('android', project3, plugins['A']);
-        }).then(function(){
-            return install('android', project3, plugins['C']);
-        }).then(function(result){
-            expect(result).toEqual(true);
-            done();
-        }).fail(function(err){
-            expect(err).toBeUndefined();
-        });
-    }, 60000);
-});
-
-describe('uninstallPlatform', function() {
-    var proc, rm;
-    var fsWrite;
-
-    beforeEach(function() {
-        proc = spyOn(actions.prototype, 'process').and.returnValue(Q());
-        fsWrite = spyOn(fs, 'writeFileSync').and.returnValue(true);
-        rm = spyOn(shell, 'rm').and.returnValue(true);
-        spyOn(shell, 'cp').and.returnValue(true);
-        done = false;
-    });
-    describe('success', function() {
-
-        it('Test 002 : should get PlatformApi instance for platform and invoke its\' removePlugin method', function(done) {
-            var platformApi = { removePlugin: jasmine.createSpy('removePlugin').and.returnValue(Q()) };
-            var getPlatformApi = spyOn(platforms, 'getPlatformApi').and.returnValue(platformApi);
-
-            uninstall.uninstallPlatform('android', project, dummy_id)
-            .then(function() {
-                expect(getPlatformApi).toHaveBeenCalledWith('android', project);
-                expect(platformApi.removePlugin).toHaveBeenCalled();
-                done();
-            }).fail(function (err) {
-                expect(err).toBeUndefined();
-                done();
-            });
-        }, 6000);
-
-        it('Test 003 : should return propagate value returned by PlatformApi removePlugin method', function(done) {
-            var platformApi = { removePlugin: jasmine.createSpy('removePlugin') };
-            spyOn(platforms, 'getPlatformApi').and.returnValue(platformApi);
-
-            var existsSyncOrig = fs.existsSync;
-            spyOn(fs, 'existsSync').and.callFake(function (file) {
-                if (file.indexOf(dummy_id) >= 0) return true;
-                return existsSyncOrig.call(fs, file);
-            });
-
-            var fakeProvider = jasmine.createSpyObj('fakeProvider', ['get']);
-            fakeProvider.get.and.returnValue(dummyPluginInfo);
-
-            function validateReturnedResultFor(values, expectedResult) {
-                return values.reduce(function (promise, value) {
-                    return promise
-                    .then(function () {
-                        platformApi.removePlugin.and.returnValue(Q(value));
-                        return uninstall.uninstallPlatform('android', project, dummy_id, null,
-                            { pluginInfoProvider: fakeProvider, platformVersion: '9.9.9' });
-                    })
-                    .then(function(result) {
-                        expect(!!result).toEqual(expectedResult);
-                    }, function(err) {
-                        expect(err).toBeUndefined();
-                    });
-                }, Q());
-            }
-
-            validateReturnedResultFor([ true, {}, [], 'foo', function(){} ], true)
-            .then(function () {
-                return validateReturnedResultFor([ false, null, undefined, '' ], false);
-            })
-            .fin(done);
-        });
-
-        describe('with dependencies', function() {
-            var emit;
-            beforeEach(function() {
-                emit = spyOn(events, 'emit');
-            });
-            uninstall.uninstallPlatform('android', project, 'A')
-            .then(function(result) {
-                expect(emit).toHaveBeenCalledWith('log', 'Uninstalling 2 dependent plugins.');
-                done();
-            });
-        });
-    });
-
-    describe('failure ', function() {
-        it('Test 004 : should throw if platform is unrecognized', function(done) {
-            uninstall.uninstallPlatform('atari', project, 'SomePlugin')
-            .then(function(result){
-                expect(false).toBe(true);
-                done();
-            }).fail(function err (errMsg) {
-                expect(errMsg.toString()).toContain('atari not supported.');
-                done();
-            });
-        }, 6000);
-
-        it('Test 005 : should throw if plugin is missing', function(done) {
-            uninstall.uninstallPlatform('android', project, 'SomePluginThatDoesntExist')
-            .then(function(result){
-                expect(false).toBe(true);
-                done();
-            }).fail(function err (errMsg) {
-                expect(errMsg.toString()).toContain('Plugin "SomePluginThatDoesntExist" not found. Already uninstalled?');
-                done();
-            });
-        }, 6000);
-    });
-});
-
-describe('uninstallPlugin', function() {
-    var rm, fsWrite, rmstack = [], emit;
-
-    beforeEach(function() {
-        fsWrite = spyOn(fs, 'writeFileSync').and.returnValue(true);
-        rm = spyOn(shell, 'rm').and.callFake(function(f,p) { rmstack.push(p); return true; });
-        rmstack = [];
-        emit = spyOn(events, 'emit');
-        done = false;
-    });
-    describe('with dependencies', function() {
-
-        it('Test 006 : should delete all dependent plugins', function(done) {
-            uninstall.uninstallPlugin('A', plugins_install_dir)
-            .then(function(result) {
-                var del = common.spy.getDeleted(emit);
-                expect(del).toEqual([
-                    'Deleted "C"',
-                    'Deleted "D"',
-                    'Deleted "A"'
-                ]);
-                done();
-            });
-        });
-
-        it('Test 007 : should fail if plugin is a required dependency', function(done) {
-            uninstall.uninstallPlugin('C', plugins_install_dir)
-            .then(function(result){
-                expect(false).toBe(true);
-                done();
-            }).fail(function err (errMsg) {
-                expect(errMsg.toString()).toEqual('"C" is required by (A) and cannot be removed (hint: use -f or --force)');
-                done();
-            });
-        }, 6000);
-
-        it('Test 008 : allow forcefully removing a plugin', function(done) {
-            uninstall.uninstallPlugin('C', plugins_install_dir, {force: true}) 
-            .then(function() {
-                var del = common.spy.getDeleted(emit);
-                expect(del).toEqual(['Deleted "C"']);
-                done();
-            });
-        });
-
-        it('Test 009 : never remove top level plugins if they are a dependency', function(done) {
-            uninstall.uninstallPlugin('A', plugins_install_dir2)
-            .then(function() {
-                var del = common.spy.getDeleted(emit);
-                expect(del).toEqual([
-                    'Deleted "D"',
-                    'Deleted "A"'
-                ]);
-                done();
-            });
-        });
-
-        it('Test 010 : should not remove dependent plugin if it was installed after as top-level', function(done) {
-            uninstall.uninstallPlugin('A', plugins_install_dir3)
-            .then(function() {
-                var del = common.spy.getDeleted(emit);
-                expect(del).toEqual([
-                    'Deleted "D"',
-                    'Deleted "A"'
-                ]);
-                done();
-            });
-        });
-    });
-});
-
-describe('uninstall', function() {
-    var fsWrite, rm;
-
-    beforeEach(function() {
-        fsWrite = spyOn(fs, 'writeFileSync').and.returnValue(true);
-        rm = spyOn(shell, 'rm').and.returnValue(true);
-        done = false;
-    });
-    
-    describe('failure', function() {
-        it('Test 011 : should throw if platform is unrecognized', function(done) {
-            return uninstall('atari', project, 'SomePlugin')
-            .then(function(result){
-                expect(false).toBe(true);
-                done();
-            }).fail(function err (errMsg) {
-                expect(errMsg.toString()).toContain('atari not supported.');
-                done();
-            });
-        }, 6000);
-
-        it('Test 012 : should throw if plugin is missing', function(done) {
-            uninstall('android', project, 'SomePluginThatDoesntExist')
-            .then(function(result){
-                expect(false).toBe(true);
-                done();
-            }).fail(function err (errMsg) {
-                expect(errMsg.toString()).toContain('Plugin "SomePluginThatDoesntExist" not found. Already uninstalled?');
-                done();
-            });
-        }, 6000);
-    });
-});
-
-describe('end', function() {
-    it('Test 013 : end', function(done) {
-        return uninstall('android', project, plugins['org.test.plugins.dummyplugin'])
-        .then(function(){
-            // Fails... A depends on
-            return uninstall('android', project, plugins['C']);
-        }).fail(function(err) {
-            expect(err.stack).toMatch(/The plugin 'C' is required by \(A\), skipping uninstallation./);
-        }).then(function(){
-            // dependencies on C,D ... should this only work with --recursive? prompt user..?
-            return uninstall('android', project, plugins['A']);
-        }).fin(function(err){
-            if(err)
-                plugman.emit('error', err);
-            shell.rm('-rf', project, project2, project3);
-            done();
-        });
-    });
-});


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[14/14] cordova-lib git commit: first unit test in each plugin submodule, jshint fixes

Posted by fi...@apache.org.
first unit test in each plugin submodule, jshint fixes


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

Branch: refs/heads/master
Commit: 1a28929564d32d8a85dca039ee8f34656c594dfd
Parents: ea8af45
Author: filmaj <ma...@gmail.com>
Authored: Wed Jun 21 10:17:18 2017 -0500
Committer: filmaj <ma...@gmail.com>
Committed: Wed Jun 21 14:58:42 2017 -0500

----------------------------------------------------------------------
 spec-cordova/plugin/add.spec.js    | 17 ++++++++++++++++-
 spec-cordova/plugin/index.spec.js  | 24 ++++++++++++++++++++++--
 spec-cordova/plugin/list.spec.js   | 22 +++++++++++++++++++++-
 spec-cordova/plugin/remove.spec.js | 11 ++++++++++-
 spec-cordova/plugin/save.spec.js   | 33 +++++++++++++++++++++++++++++++--
 spec-cordova/plugin/search.spec.js | 28 ++++++++++++++++++++++++++--
 src/cordova/plugin/add.js          |  2 +-
 src/cordova/plugin/search.js       |  2 +-
 8 files changed, 128 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/1a289295/spec-cordova/plugin/add.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/plugin/add.spec.js b/spec-cordova/plugin/add.spec.js
index 4860578..2788d13 100644
--- a/spec-cordova/plugin/add.spec.js
+++ b/spec-cordova/plugin/add.spec.js
@@ -16,14 +16,29 @@
     specific language governing permissions and limitations
     under the License.
 */
+// TODO: remove once eslint lands
 /* eslint-env jasmine */
+/* globals fail */
 
+var Q = require('q');
 var add = require('../../src/cordova/plugin/add');
 
 describe('cordova/plugin/add', function () {
+    var projectRoot = '/some/path';
+    var hook_mock;
+    beforeEach(function () {
+        hook_mock = jasmine.createSpyObj('hooks runner mock', ['fire']);
+        hook_mock.fire.and.returnValue(Q());
+    });
     describe('main method', function () {
         describe('error/warning conditions', function () {
-            it('should error out if at least one plugin is not specified');
+            it('should error out if at least one plugin is not specified', function (done) {
+                add(projectRoot, hook_mock, {plugins: []}).then(function () {
+                    fail('success handler unexpectedly invoked');
+                }).fail(function (e) {
+                    expect(e.message).toContain('No plugin specified');
+                }).done(done);
+            });
             it('should error out if any mandatory plugin variables are not provided');
         });
         describe('happy path', function () {

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/1a289295/spec-cordova/plugin/index.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/plugin/index.spec.js b/spec-cordova/plugin/index.spec.js
index 479de82..565d6f9 100644
--- a/spec-cordova/plugin/index.spec.js
+++ b/spec-cordova/plugin/index.spec.js
@@ -16,14 +16,34 @@
     specific language governing permissions and limitations
     under the License.
 */
+// TODO: remove once eslint lands
 /* eslint-env jasmine */
+/* globals fail */
 
-var plugin = require('../../src/cordova/plugin');
+var rewire = require('rewire');
+var plugin = rewire('../../src/cordova/plugin');
+var cordova_util = require('../../src/cordova/util');
 
 describe('cordova/plugin', function () {
+    var projectRoot = '/some/path';
+    var hook_mock = function () {};
+    var hook_revert_mock;
+    beforeEach(function () {
+        hook_revert_mock = plugin.__set__('HooksRunner', hook_mock);
+        spyOn(cordova_util, 'cdProjectRoot').and.returnValue(projectRoot);
+    });
+    afterEach(function () {
+        hook_revert_mock();
+    });
     describe('error conditions', function () {
         // TODO: what about search cmd?
-        it('should require at least one target for add and rm commands');
+        it('should require at least one target for add and rm commands', function (done) {
+            plugin('add', null).then(function () {
+                fail('success handler unexpectedly invoked');
+            }).fail(function (e) {
+                expect(e.message).toContain('one or more plugins');
+            }).done(done);
+        });
     });
     describe('handling/massaging of parameters', function () {
         it('should be able to handle an array of targets');

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/1a289295/spec-cordova/plugin/list.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/plugin/list.spec.js b/spec-cordova/plugin/list.spec.js
index 0bf27b4..d836418 100644
--- a/spec-cordova/plugin/list.spec.js
+++ b/spec-cordova/plugin/list.spec.js
@@ -16,12 +16,32 @@
     specific language governing permissions and limitations
     under the License.
 */
+// TODO: remove once eslint lands
 /* eslint-env jasmine */
+/* globals fail */
 
+var Q = require('q');
 var list = require('../../src/cordova/plugin/list');
+var plugin_util = require('../../src/cordova/plugin/util');
 
 describe('cordova/plugin/list', function () {
-    it('should fire the before_plugin_ls hook');
+    var projectRoot = '/some/path';
+    var hook_mock;
+    var fake_plugins_list = [{id: 'VRPlugin'}, {id: 'MastodonSocialPlugin'}];
+    beforeEach(function () {
+        hook_mock = jasmine.createSpyObj('hooks runner mock', ['fire']);
+        hook_mock.fire.and.returnValue(Q());
+        spyOn(plugin_util, 'getInstalledPlugins').and.returnValue(Q.resolve(fake_plugins_list));
+    });
+    it('should fire the before_plugin_ls hook', function (done) {
+        var opts = {important: 'options'};
+        list(projectRoot, hook_mock, opts).then(function () {
+            expect(hook_mock.fire).toHaveBeenCalledWith('before_plugin_ls', opts);
+        }).fail(function (e) {
+            fail('fail handler unexpectedly invoked');
+            console.error(e);
+        }).done(done);
+    });
     it('should emit a "no plugins added" result if there are no installed plugins');
     it('should warn if plugin list contains dependencies that are missing');
     it('should warn if plugin list contains a plugin dependency that does not have a version satisfied');

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/1a289295/spec-cordova/plugin/remove.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/plugin/remove.spec.js b/spec-cordova/plugin/remove.spec.js
index cbe1747..ab7b572 100644
--- a/spec-cordova/plugin/remove.spec.js
+++ b/spec-cordova/plugin/remove.spec.js
@@ -16,13 +16,22 @@
     specific language governing permissions and limitations
     under the License.
 */
+// TODO: remove this once eslint lands
 /* eslint-env jasmine */
+/* globals fail */
 
 var remove = require('../../src/cordova/plugin/remove');
 
 describe('cordova/plugin/remove', function () {
+    var projectRoot = '/some/path';
     describe('error/warning conditions', function () {
-        it('should require that a plugin be provided');
+        it('should require that a plugin be provided', function (done) {
+            remove(projectRoot, null).then(function () {
+                fail('success handler unexpectedly invoked');
+            }).fail(function (e) {
+                expect(e.message).toContain('No plugin specified');
+            }).done(done);
+        });
         it('should require that a provided plugin be installed in the current project');
     });
     describe('happy path', function () {

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/1a289295/spec-cordova/plugin/save.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/plugin/save.spec.js b/spec-cordova/plugin/save.spec.js
index c2b5f68..1fb8b46 100644
--- a/spec-cordova/plugin/save.spec.js
+++ b/spec-cordova/plugin/save.spec.js
@@ -16,13 +16,42 @@
     specific language governing permissions and limitations
     under the License.
 */
+// TODO: remove this once eslint lands
 /* eslint-env jasmine */
+/* globals fail */
 
-var save = require('../../src/cordova/plugin/save');
+var rewire = require('rewire');
+var fs = require('fs');
+var save = rewire('../../src/cordova/plugin/save');
+var cordova_util = require('../../src/cordova/util');
 
 describe('cordova/plugin/save', function () {
+    var projectRoot = '/some/path';
+    var cfg_parser_mock = function () {};
+    var cfg_parser_revert_mock;
+    var fake_plugin_list = ['VRPlugin', 'MastodonSocialPlugin'];
+    var fake_fetch_json = {'VRPlugin': {}, 'MastodonSocialPlugin': {}};
+    beforeEach(function () {
+        cfg_parser_mock.prototype = jasmine.createSpyObj('config parser protytpe mock', ['getPluginIdList', 'removePlugin']);
+        cfg_parser_mock.prototype.getPluginIdList.and.returnValue(fake_plugin_list);
+        cfg_parser_revert_mock = save.__set__('ConfigParser', cfg_parser_mock);
+        spyOn(cordova_util, 'projectConfig').and.returnValue(projectRoot + '/config.xml');
+        spyOn(fs, 'readFileSync').and.returnValue(JSON.stringify(fake_fetch_json));
+    });
+    afterEach(function () {
+        cfg_parser_revert_mock();
+    });
     describe('error conditions', function () {
-        it('should explode if there was an issue parsing or reading from fetch.json file');
+        it('should explode if there was an issue parsing or reading from fetch.json file', function (done) {
+            fs.readFileSync.and.callFake(function () {
+                throw new Error('massive explosions during file reading!');
+            });
+            save(projectRoot).then(function () {
+                fail('unexpected success handler invoked');
+            }).fail(function (e) {
+                expect(e).toContain('massive explosions');
+            }).done(done);
+        });
     });
     describe('happy path', function () {
         it('should remove all plugins from config.xml and re-add new ones based on those retrieved from fetch.json');

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/1a289295/spec-cordova/plugin/search.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/plugin/search.spec.js b/spec-cordova/plugin/search.spec.js
index b940eeb..aa3ba54 100644
--- a/spec-cordova/plugin/search.spec.js
+++ b/spec-cordova/plugin/search.spec.js
@@ -16,12 +16,36 @@
     specific language governing permissions and limitations
     under the License.
 */
+// TODO: remove once eslint lands
 /* eslint-env jasmine */
+/* globals fail */
 
-var search = require('../../src/cordova/plugin/search');
+var Q = require('q');
+var rewire = require('rewire');
+var search = rewire('../../src/cordova/plugin/search');
 
 describe('cordova/plugin/search', function () {
-    it('should fire the before_plugin_search hook');
+    var hook_mock;
+    var opener_mock;
+    var opener_revert_mock;
+    beforeEach(function () {
+        hook_mock = jasmine.createSpyObj('hooks runner mock', ['fire']);
+        hook_mock.fire.and.returnValue(Q());
+        opener_mock = jasmine.createSpy('opener mock');
+        opener_revert_mock = search.__set__('opener', opener_mock);
+    });
+    afterEach(function () {
+        opener_revert_mock();
+    });
+    it('should fire the before_plugin_search hook', function (done) {
+        var opts = {important: 'options', plugins: []};
+        search(hook_mock, opts).then(function () {
+            expect(hook_mock.fire).toHaveBeenCalledWith('before_plugin_search', opts);
+        }).fail(function (e) {
+            fail('fail handler unexpectedly invoked');
+            console.error(e);
+        }).done(done);
+    });
     it('should open a link to cordova.apache.org/plugins if no plugins are provided as parameter');
     it('should open a link to cordova.apache.org/plugins, providing the plugins passed in as a query-string parameter');
     it('should fire the after_plugin_search hook');

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/1a289295/src/cordova/plugin/add.js
----------------------------------------------------------------------
diff --git a/src/cordova/plugin/add.js b/src/cordova/plugin/add.js
index 3b801d0..4e975d3 100644
--- a/src/cordova/plugin/add.js
+++ b/src/cordova/plugin/add.js
@@ -30,7 +30,7 @@ var CordovaError = require('cordova-common').CordovaError;
 var PluginInfoProvider = require('cordova-common').PluginInfoProvider;
 var events = require('cordova-common').events;
 var shell = require('shelljs');
-var Q = require('Q');
+var Q = require('q');
 var path = require('path');
 var fs = require('fs');
 var semver = require('semver');

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/1a289295/src/cordova/plugin/search.js
----------------------------------------------------------------------
diff --git a/src/cordova/plugin/search.js b/src/cordova/plugin/search.js
index e439c3b..0108e4e 100644
--- a/src/cordova/plugin/search.js
+++ b/src/cordova/plugin/search.js
@@ -18,7 +18,7 @@
 */
 
 var opener = require('opener');
-var Q = require('Q');
+var Q = require('q');
 
 module.exports = search;
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[11/14] cordova-lib git commit: nest certain plugin-remove-save tests into a describe to better reflect structure

Posted by fi...@apache.org.
nest certain plugin-remove-save tests into a describe to better reflect structure


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

Branch: refs/heads/master
Commit: e314d0736885fe96384f3804ecc2b579999637d3
Parents: 4c8563c
Author: filmaj <ma...@gmail.com>
Authored: Tue Jun 20 14:43:22 2017 -0500
Committer: filmaj <ma...@gmail.com>
Committed: Wed Jun 21 14:58:28 2017 -0500

----------------------------------------------------------------------
 integration-tests/plugin_fetch.spec.js | 4 +++-
 spec-cordova/plugin/remove.spec.js     | 6 ++++--
 src/cordova/plugin/add.js              | 4 ++--
 src/cordova/plugin/remove.js           | 2 +-
 4 files changed, 10 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/e314d073/integration-tests/plugin_fetch.spec.js
----------------------------------------------------------------------
diff --git a/integration-tests/plugin_fetch.spec.js b/integration-tests/plugin_fetch.spec.js
index 6bf5501..1e171c5 100644
--- a/integration-tests/plugin_fetch.spec.js
+++ b/integration-tests/plugin_fetch.spec.js
@@ -17,7 +17,9 @@
     under the License.
 */
 
-var plugin  = require('../src/cordova/plugin'),
+// TODO: all of these tests should go as unit tests to src/cordova/plugin/add
+
+var plugin  = require('../src/cordova/plugin/add'),
     helpers = require('../spec-cordova/helpers'),
     path    = require('path'),
     events = require('cordova-common').events,

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/e314d073/spec-cordova/plugin/remove.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/plugin/remove.spec.js b/spec-cordova/plugin/remove.spec.js
index 5101fb9..cbe1747 100644
--- a/spec-cordova/plugin/remove.spec.js
+++ b/spec-cordova/plugin/remove.spec.js
@@ -30,8 +30,10 @@ describe('cordova/plugin/remove', function () {
         it('should call plugman.uninstall.uninstallPlatform for each platform installed in the project and for each provided plugin');
         it('should trigger a prepare if plugman.uninstall.uninstallPlatform returned something falsy');
         it('should call plugman.uninstall.uninstallPlugin once plugin has been uninstalled for each platform');
-        it('should remove provided plugins from config.xml');
-        it('should remove provided plugins from package.json (if exists)');
+        describe('when save option is provided or autosave config is on', function () {
+            it('should remove provided plugins from config.xml');
+            it('should remove provided plugins from package.json (if exists)');
+        });
         it('should remove fetch metadata from fetch.json');
         it('should fire the after_plugin_rm hook');
     });

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/e314d073/src/cordova/plugin/add.js
----------------------------------------------------------------------
diff --git a/src/cordova/plugin/add.js b/src/cordova/plugin/add.js
index 346e688..ca12612 100644
--- a/src/cordova/plugin/add.js
+++ b/src/cordova/plugin/add.js
@@ -215,7 +215,7 @@ function add (projectRoot, targets, hooksRunner, opts) {
         // Need to require right here instead of doing this at the beginning of file
         // otherwise tests are failing without any real reason.
         // TODO: possible circular dependency?
-        return require('./prepare').preparePlatforms(platformList, projectRoot, opts);
+        return require('../prepare').preparePlatforms(platformList, projectRoot, opts);
     }).then(function () {
         opts.cordova = { plugins: cordova_util.findPlugins(pluginPath) };
         return hooksRunner.fire('after_plugin_add', opts);
@@ -299,7 +299,7 @@ function determinePluginTarget (projectRoot, cfg, target, fetchOptions) {
     // TODO: whoa wat
     return (shouldUseNpmInfo ? registry.info([id])
     .then(function (pluginInfo) {
-        return getFetchVersion(projectRoot, pluginInfo, cordovaVersion);
+        return module.exports.getFetchVersion(projectRoot, pluginInfo, cordovaVersion);
     }) : Q(null))
     .then(function (fetchVersion) {
         return fetchVersion ? (id + '@' + fetchVersion) : target;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/e314d073/src/cordova/plugin/remove.js
----------------------------------------------------------------------
diff --git a/src/cordova/plugin/remove.js b/src/cordova/plugin/remove.js
index 78a355e..01de6fb 100644
--- a/src/cordova/plugin/remove.js
+++ b/src/cordova/plugin/remove.js
@@ -112,7 +112,7 @@ function remove (projectRoot, targets, hooksRunner, opts) {
         if (!shouldRunPrepare) {
             return Q();
         }
-        return require('./prepare').preparePlatforms(platformList, projectRoot, opts);
+        return require('../prepare').preparePlatforms(platformList, projectRoot, opts);
     }).then(function () {
         opts.cordova = { plugins: cordova_util.findPlugins(pluginPath) };
         return hooksRunner.fire('after_plugin_rm', opts);


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[05/14] cordova-lib git commit: first pass at plugin command refactor

Posted by fi...@apache.org.
first pass at plugin command refactor


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

Branch: refs/heads/master
Commit: ce93923f1f74f0531e126eb49c62edb3a28c25e0
Parents: 080653f
Author: filmaj <ma...@gmail.com>
Authored: Mon Jun 19 18:45:31 2017 -0500
Committer: filmaj <ma...@gmail.com>
Committed: Wed Jun 21 14:57:48 2017 -0500

----------------------------------------------------------------------
 integration-tests/save.spec.js           |  20 -
 src/cordova/plugin.js                    | 914 --------------------------
 src/cordova/plugin/add.js                | 564 ++++++++++++++++
 src/cordova/plugin/index.js              |  96 +++
 src/cordova/plugin/list.js               |  75 +++
 src/cordova/plugin/plugin_spec_parser.js |  61 ++
 src/cordova/plugin/remove.js             | 132 ++++
 src/cordova/plugin/save.js               | 146 ++++
 src/cordova/plugin/search.js             |  41 ++
 src/cordova/plugin/util.js               |  37 ++
 src/cordova/plugin_spec_parser.js        |  61 --
 11 files changed, 1152 insertions(+), 995 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/ce93923f/integration-tests/save.spec.js
----------------------------------------------------------------------
diff --git a/integration-tests/save.spec.js b/integration-tests/save.spec.js
index 616d3db..e83b096 100644
--- a/integration-tests/save.spec.js
+++ b/integration-tests/save.spec.js
@@ -551,26 +551,6 @@ describe('(save flag)', function () {
             });
         }, TIMEOUT);
 
-        it('Test 027 : spec.22 should update config with plugins: one with version, one with local folder and another one vith git url', function (done) {
-            cordova.plugin('add', pluginName + '@' + pluginVersion)
-            .then(function () {
-                return cordova.plugin('add', gitPluginUrl);
-            }).then(function () {
-                return cordova.plugin('add', localPluginPath);
-            }).then(function () {
-                return cordova.plugin('save');
-            }).then(function () {
-                expect(helpers.getPluginSpec(appPath, pluginName)).toBe('~' + pluginVersion);
-                expect(helpers.getPluginSpec(appPath, gitPluginName)).toBe(gitPluginUrl);
-                expect(helpers.getPluginSpec(appPath, localPluginName)).toBe(localPluginPath);
-                done();
-            }).catch(function (err) {
-                expect(true).toBe(false);
-                console.log(err.message);
-                done();
-            });
-        }, TIMEOUT);
-
         it('Test 028 : spec.22.1 should update config with a spec that includes the scope for scoped plugins', function (done) {
             // Fetching globalization rather than console to avoid conflicts with earlier tests
             redirectRegistryCalls(pluginName2 + '@' + pluginVersion2);

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/ce93923f/src/cordova/plugin.js
----------------------------------------------------------------------
diff --git a/src/cordova/plugin.js b/src/cordova/plugin.js
deleted file mode 100644
index d631abc..0000000
--- a/src/cordova/plugin.js
+++ /dev/null
@@ -1,914 +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_util  = require('./util'),
-    path          = require('path'),
-    config        = require('./config'),
-    Q             = require('q'),
-    CordovaError  = require('cordova-common').CordovaError,
-    ConfigParser  = require('cordova-common').ConfigParser,
-    fs            = require('fs'),
-    shell         = require('shelljs'),
-    PluginInfoProvider = require('cordova-common').PluginInfoProvider,
-    plugman       = require('../plugman/plugman'),
-    pluginSpec    = require('./plugin_spec_parser'),
-    events        = require('cordova-common').events,
-    metadata      = require('../plugman/util/metadata'),
-    registry      = require('../plugman/registry/registry'),
-    chainMap      = require('../util/promise-util').Q_chainmap,
-    pkgJson       = require('../../package.json'),
-    semver        = require('semver'),
-    opener        = require('opener');
-
-// For upper bounds in cordovaDependencies
-var UPPER_BOUND_REGEX = /^<\d+\.\d+\.\d+$/;
-// Returns a promise.
-module.exports = function plugin(command, targets, opts) {
-    // CB-10519 wrap function code into promise so throwing error
-    // would result in promise rejection instead of uncaught exception
-    return Q().then(function () {
-        var projectRoot = cordova_util.cdProjectRoot();
-
-        // Dance with all the possible call signatures we've come up over the time. They can be:
-        // 1. plugin() -> list the plugins
-        // 2. plugin(command, Array of targets, maybe opts object)
-        // 3. plugin(command, target1, target2, target3 ... )
-        // The targets are not really targets, they can be a mixture of plugins and options to be passed to plugman.
-
-        command = command || 'ls';
-        targets = targets || [];
-        opts = opts || {};
-        if ( opts.length ) {
-            // This is the case with multiple targets as separate arguments and opts is not opts but another target.
-            targets = Array.prototype.slice.call(arguments, 1);
-            opts = {};
-        }
-        if ( !Array.isArray(targets) ) {
-            // This means we had a single target given as string.
-            targets = [targets];
-        }
-        opts.options = opts.options || [];
-        opts.plugins = [];
-
-        // TODO: Otherwise HooksRunner will be Object instead of function when run from tests - investigate why
-        var HooksRunner = require('../hooks/HooksRunner');
-        var hooksRunner = new HooksRunner(projectRoot);
-        var config_json = config.read(projectRoot);
-        var platformList = cordova_util.listPlatforms(projectRoot);
-
-        // Massage plugin name(s) / path(s)
-        var pluginPath = path.join(projectRoot, 'plugins');
-        var plugins = cordova_util.findPlugins(pluginPath);
-        if (!targets || !targets.length) {
-            if (command == 'add' || command == 'rm') {
-                return Q.reject(new CordovaError('You need to qualify `'+cordova_util.binname+' plugin add` or `'+cordova_util.binname+' plugin remove` with one or more plugins!'));
-            } else {
-                targets = [];
-            }
-        }
-
-        //Split targets between plugins and options
-        //Assume everything after a token with a '-' is an option
-        var i;
-        for (i = 0; i < targets.length; i++) {
-            if (targets[i].match(/^-/)) {
-                opts.options = targets.slice(i);
-                break;
-            } else {
-                opts.plugins.push(targets[i]);
-            }
-        }
-        // Assume we don't need to run prepare by default
-        var shouldRunPrepare = false;
-
-        switch(command) {
-            case 'add':
-                if (!targets || !targets.length) {
-                    return Q.reject(new CordovaError('No plugin specified. Please specify a plugin to add. See `'+cordova_util.binname+' plugin search`.'));
-                }
-
-                var xml = cordova_util.projectConfig(projectRoot);
-                var cfg = new ConfigParser(xml);
-                var searchPath = config_json.plugin_search_path || [];
-                if (typeof opts.searchpath == 'string') {
-                    searchPath = opts.searchpath.split(path.delimiter).concat(searchPath);
-                } else if (opts.searchpath) {
-                    searchPath = opts.searchpath.concat(searchPath);
-                }
-                // Blank it out to appease unit tests.
-                if (searchPath.length === 0) {
-                    searchPath = undefined;
-                }
-
-                opts.cordova = { plugins: cordova_util.findPlugins(pluginPath) };
-                return hooksRunner.fire('before_plugin_add', opts)
-                .then(function() {
-                    var pluginInfoProvider = new PluginInfoProvider();
-                    return opts.plugins.reduce(function(soFar, target) {
-                        return soFar.then(function() {
-                            if (target[target.length - 1] == path.sep) {
-                                target = target.substring(0, target.length - 1);
-                            }
-
-                            // Fetch the plugin first.
-                            var fetchOptions = {
-                                searchpath: searchPath,
-                                noregistry: opts.noregistry,
-                                fetch: opts.fetch || false,
-                                save: opts.save,
-                                nohooks: opts.nohooks,
-                                link: opts.link,
-                                pluginInfoProvider: pluginInfoProvider,
-                                variables: opts.cli_variables,
-                                is_top_level: true
-                            };
-
-                            return determinePluginTarget(projectRoot, cfg, target, fetchOptions)
-                            .then(function(resolvedTarget) {
-                                target = resolvedTarget;
-                                events.emit('verbose', 'Calling plugman.fetch on plugin "' + target + '"');
-                                return plugman.fetch(target, pluginPath, fetchOptions);
-                            })
-                            .then(function (directory) {
-                                return pluginInfoProvider.get(directory);
-                            });
-                        })
-                        .then(function(pluginInfo) {
-                            // Validate top-level required variables
-                            var pluginVariables = pluginInfo.getPreferences();
-                            opts.cli_variables = opts.cli_variables || {};
-                            var pluginEntry = cfg.getPlugin(pluginInfo.id);
-                            // Get variables from config.xml
-                            var configVariables = pluginEntry ? pluginEntry.variables : {};
-                            // Add config variable if it's missing in cli_variables
-                            Object.keys(configVariables).forEach(function(variable) {
-                                opts.cli_variables[variable] = opts.cli_variables[variable] || configVariables[variable];
-                            });
-                            var missingVariables = Object.keys(pluginVariables)
-                            .filter(function (variableName) {
-                                // discard variables with default value
-                                return !(pluginVariables[variableName] || opts.cli_variables[variableName]);
-                            });
-
-                            if (missingVariables.length) {
-                                events.emit('verbose', 'Removing ' + pluginInfo.dir + ' because mandatory plugin variables were missing.');
-                                shell.rm('-rf', pluginInfo.dir);
-                                var msg = 'Variable(s) missing (use: --variable ' + missingVariables.join('=value --variable ') + '=value).';
-                                return Q.reject(new CordovaError(msg));
-                            }
-
-                            // Iterate (in serial!) over all platforms in the project and install the plugin.
-                            return chainMap(platformList, function (platform) {
-                                var platformRoot = path.join(projectRoot, 'platforms', platform),
-                                options = {
-                                    cli_variables: opts.cli_variables || {},
-                                    browserify: opts.browserify || false,
-                                    fetch: opts.fetch || false,
-                                    save: opts.save,
-                                    searchpath: searchPath,
-                                    noregistry: opts.noregistry,
-                                    link: opts.link,
-                                    pluginInfoProvider: pluginInfoProvider,
-                                    // Set up platform to install asset files/js modules to <platform>/platform_www dir
-                                    // instead of <platform>/www. This is required since on each prepare platform's www dir is changed
-                                    // and files from 'platform_www' merged into 'www'. Thus we need to persist these
-                                    // files platform_www directory, so they'll be applied to www on each prepare.
-                                    usePlatformWww: true,
-                                    nohooks: opts.nohooks,
-                                    force: opts.force
-                                };
-
-                                events.emit('verbose', 'Calling plugman.install on plugin "' + pluginInfo.dir + '" for platform "' + platform);
-                                return plugman.install(platform, platformRoot, path.basename(pluginInfo.dir), pluginPath, options)
-                                .then(function (didPrepare) {
-                                    // If platform does not returned anything we'll need
-                                    // to trigger a prepare after all plugins installed
-                                    if (!didPrepare) shouldRunPrepare = true;
-                                });
-                            })
-                            .thenResolve(pluginInfo);
-                        })
-                        .then(function(pluginInfo){
-                            var pkgJson;
-                            var pkgJsonPath = path.join(projectRoot,'package.json');
-
-                            // save to config.xml
-                            if(saveToConfigXmlOn(config_json, opts)){
-                                // If statement to see if pkgJsonPath exists in the filesystem
-                                if(fs.existsSync(pkgJsonPath)) {
-                                    // Delete any previous caches of require(package.json)
-                                    pkgJson = cordova_util.requireNoCache(pkgJsonPath);
-                                }
-                                // If package.json exists, the plugin object and plugin name 
-                                // will be added to package.json if not already there.
-                                if(pkgJson) {
-                                    pkgJson.cordova = pkgJson.cordova || {};
-                                    pkgJson.cordova.plugins = pkgJson.cordova.plugins || {};
-                                    // Plugin and variables are added.
-                                    pkgJson.cordova.plugins[pluginInfo.id] = opts.cli_variables;
-                                    events.emit('log','Adding '+pluginInfo.id+ ' to package.json');
-
-                                    // Write to package.json
-                                    fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2), 'utf8');
-                                }
-
-                                var src = parseSource(target, opts);
-                                var attributes = {
-                                    name: pluginInfo.id
-                                };
-
-                                if (src) {
-                                    attributes.spec = src;
-                                } else {
-                                    var ver = '~' + pluginInfo.version;
-                                    // Scoped packages need to have the package-spec along with the version
-                                    var parsedSpec = pluginSpec.parse(target);
-                                    if(pkgJson && pkgJson.dependencies && pkgJson.dependencies[pluginInfo.id]){
-                                        attributes.spec = pkgJson.dependencies[pluginInfo.id];
-                                    } else {
-                                        if (parsedSpec.scope) {
-                                            attributes.spec = parsedSpec.package + '@' + ver;
-                                        } else {
-                                            attributes.spec = ver;
-                                        }
-                                    }
-                                }
-                                xml = cordova_util.projectConfig(projectRoot);
-                                cfg = new ConfigParser(xml);
-                                cfg.removePlugin(pluginInfo.id);
-                                cfg.addPlugin(attributes, opts.cli_variables);
-                                cfg.write();
-
-                                events.emit('results', 'Saved plugin info for "' + pluginInfo.id + '" to config.xml');
-                            }
-                        });
-                    }, Q());
-                }).then(function() {
-                    // CB-11022 We do not need to run prepare after plugin install until shouldRunPrepare flag is set to true
-                    if (!shouldRunPrepare) {
-                        return Q();
-                    }
-                    // Need to require right here instead of doing this at the beginning of file
-                    // otherwise tests are failing without any real reason.
-                    return require('./prepare').preparePlatforms(platformList, projectRoot, opts);
-                }).then(function() {
-                    opts.cordova = { plugins: cordova_util.findPlugins(pluginPath) };
-                    return hooksRunner.fire('after_plugin_add', opts);
-                });
-            case 'rm':
-            case 'remove':
-                if (!targets || !targets.length) {
-                    return Q.reject(new CordovaError('No plugin specified. Please specify a plugin to remove. See `'+cordova_util.binname+' plugin list`.'));
-                }
-
-                opts.cordova = { plugins: cordova_util.findPlugins(pluginPath) };
-                return hooksRunner.fire('before_plugin_rm', opts)
-                .then(function() {
-                    return opts.plugins.reduce(function(soFar, target) {
-                        var validatedPluginId = validatePluginId(target, plugins);
-                        if (!validatedPluginId) {
-                            return Q.reject(new CordovaError('Plugin "' + target + '" is not present in the project. See `' + cordova_util.binname + ' plugin list`.'));
-                        }
-                        target = validatedPluginId;
-
-                        // Iterate over all installed platforms and uninstall.
-                        // If this is a web-only or dependency-only plugin, then
-                        // there may be nothing to do here except remove the
-                        // reference from the platform's plugin config JSON.
-                        return platformList.reduce(function(soFar, platform) {
-                            return soFar.then(function() {
-                                var platformRoot = path.join(projectRoot, 'platforms', platform);
-                                events.emit('verbose', 'Calling plugman.uninstall on plugin "' + target + '" for platform "' + platform + '"');
-                                var options = {
-                                    force: opts.force || false
-                                };
-                                return plugman.uninstall.uninstallPlatform(platform, platformRoot, target, pluginPath, options)
-                                .then(function (didPrepare) {
-                                    // If platform does not returned anything we'll need
-                                    // to trigger a prepare after all plugins installed
-                                    if (!didPrepare) shouldRunPrepare = true;
-                                });
-                            });
-                        }, Q())
-                        .then(function() {
-                            // TODO: Should only uninstallPlugin when no platforms have it.
-                            return plugman.uninstall.uninstallPlugin(target, pluginPath, opts);
-                        }).then(function(){
-                            //remove plugin from config.xml
-                            if(saveToConfigXmlOn(config_json, opts)){
-                                events.emit('log', 'Removing plugin ' + target + ' from config.xml file...');
-                                var configPath = cordova_util.projectConfig(projectRoot);
-                                if(fs.existsSync(configPath)){//should not happen with real life but needed for tests
-                                    var configXml = new ConfigParser(configPath);
-                                    configXml.removePlugin(target);
-                                    configXml.write();
-                                }
-                                var pkgJson;
-                                var pkgJsonPath = path.join(projectRoot,'package.json');
-                                // If statement to see if pkgJsonPath exists in the filesystem
-                                if(fs.existsSync(pkgJsonPath)) {
-                                    //delete any previous caches of require(package.json)
-                                    pkgJson = cordova_util.requireNoCache(pkgJsonPath);
-                                } else {
-                                    // Create package.json in cordova@7
-                                }
-                                // If package.json exists and contains a specified plugin in cordova['plugins'], it will be removed    
-                                if(pkgJson !== undefined && pkgJson.cordova !== undefined && pkgJson.cordova.plugins !== undefined) {
-                                    events.emit('log', 'Removing '  + target +  ' from package.json');
-                                    // Remove plugin from package.json
-                                    delete pkgJson.cordova.plugins[target];
-                                    //Write out new package.json with plugin removed correctly.
-                                    fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2), 'utf8');
-                                }
-                            }
-                            
-                        }).then(function(){
-                            // Remove plugin from fetch.json
-                            events.emit('verbose', 'Removing plugin ' + target + ' from fetch.json');
-                            metadata.remove_fetch_metadata(pluginPath, target);
-                        });
-                    }, Q());
-                }).then(function () {
-                    // CB-11022 We do not need to run prepare after plugin install until shouldRunPrepare flag is set to true
-                    if (!shouldRunPrepare) {
-                        return Q();
-                    }   
-
-                    return require('./prepare').preparePlatforms(platformList, projectRoot, opts);
-                }).then(function() {
-                    opts.cordova = { plugins: cordova_util.findPlugins(pluginPath) };
-                    return hooksRunner.fire('after_plugin_rm', opts);
-                });
-            case 'search':
-                return hooksRunner.fire('before_plugin_search', opts)
-                .then(function() {
-                    var link = 'http://cordova.apache.org/plugins/';
-                    if (opts.plugins.length > 0) {
-                        var keywords = (opts.plugins).join(' ');
-                        var query = link + '?q=' + encodeURI(keywords);
-                        opener(query);
-                    }
-                    else {
-                        opener(link);
-                    }
-
-                    return Q.resolve();
-                }).then(function() {
-                    return hooksRunner.fire('after_plugin_search', opts);
-                });
-            case 'save':
-                // save the versions/folders/git-urls of currently installed plugins into config.xml
-                return save(projectRoot, opts);
-            default:
-                return list(projectRoot, hooksRunner);
-        }
-    });
-};
-
-function determinePluginTarget(projectRoot, cfg, target, fetchOptions) {
-    var parsedSpec = pluginSpec.parse(target);
-    var id = parsedSpec.package || target;
-    // CB-10975 We need to resolve relative path to plugin dir from app's root before checking whether if it exists
-    var maybeDir = cordova_util.fixRelativePath(id);
-    if (parsedSpec.version || cordova_util.isUrl(id) || cordova_util.isDirectory(maybeDir)) {
-        return Q(target);
-    }
-    // Require project pkgJson.
-    var pkgJsonPath = path.join(projectRoot, 'package.json');
-    var cordovaVersion = pkgJson.version;
-    if(fs.existsSync(pkgJsonPath)) {
-        pkgJson = cordova_util.requireNoCache(pkgJsonPath); 
-    }
-
-    // If no parsedSpec.version, use the one from pkg.json or config.xml.
-    if (!parsedSpec.version) {
-        // Retrieve from pkg.json.
-        if(pkgJson && pkgJson.dependencies && pkgJson.dependencies[id]) {
-            events.emit('verbose', 'No version specified for ' + id + ', retrieving version from package.json');
-            parsedSpec.version = pkgJson.dependencies[id];
-        } else {
-            // If no version is specified, retrieve the version (or source) from config.xml.
-            events.emit('verbose', 'No version specified for ' + id + ', retrieving version from config.xml');
-            parsedSpec.version = getVersionFromConfigFile(id, cfg);
-        }
-    }
-
-    // If parsedSpec.version satisfies pkgJson version, no writing to pkg.json. Only write when
-    // it does not satisfy.
-    
-    if(parsedSpec.version) {
-        if(pkgJson && pkgJson.dependencies && pkgJson.dependencies[parsedSpec.package]) {
-            var noSymbolVersion = parsedSpec.version;
-            if (parsedSpec.version.charAt(0) === '^' || parsedSpec.version.charAt(0) === '~') {
-                noSymbolVersion = parsedSpec.version.slice(1);
-            }
-
-            if(cordova_util.isUrl(parsedSpec.version) || cordova_util.isDirectory(parsedSpec.version)) {
-                if (pkgJson.dependencies[parsedSpec.package] !== parsedSpec.version) {
-                    pkgJson.dependencies[parsedSpec.package] = parsedSpec.version;
-                }
-                if(fetchOptions.save === true) {
-                    fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2), 'utf8');
-                }
-            } else if (!semver.satisfies(noSymbolVersion, pkgJson.dependencies[parsedSpec.package])) {
-                pkgJson.dependencies[parsedSpec.package] = parsedSpec.version;
-                if (fetchOptions.save === true) {
-                    fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2), 'utf8');
-                }
-            }
-        }
-    }
-
-    if (cordova_util.isUrl(parsedSpec.version) || cordova_util.isDirectory(parsedSpec.version) || pluginSpec.parse(parsedSpec.version).scope) {
-        return Q(parsedSpec.version);
-    }
-
-    // If version exists in pkg.json or config.xml, use that.
-    if (parsedSpec.version) {
-        return Q(id + '@' + parsedSpec.version);
-    }
-
-    // If no version is given at all and we are fetching from npm, we
-    // can attempt to use the Cordova dependencies the plugin lists in
-    // their package.json
-    var shouldUseNpmInfo = !fetchOptions.searchpath && !fetchOptions.noregistry;
-
-    events.emit('verbose', 'No version for ' + parsedSpec.package + ' saved in config.xml');
-    if(shouldUseNpmInfo) {
-        events.emit('verbose', 'Attempting to use npm info for ' + parsedSpec.package + ' to choose a compatible release');
-    } else {
-        events.emit('verbose', 'Not checking npm info for ' + parsedSpec.package + ' because searchpath or noregistry flag was given');
-    }
-
-    return (shouldUseNpmInfo ? registry.info([id])
-    .then(function(pluginInfo) {
-        return getFetchVersion(projectRoot, pluginInfo, cordovaVersion);
-    }) : Q(null))
-    .then(function(fetchVersion) {
-        return fetchVersion ? (id + '@' + fetchVersion) : target;
-    });
-}
-
-// Exporting for testing purposes
-module.exports.getFetchVersion = getFetchVersion;
-
-function validatePluginId(pluginId, installedPlugins) {
-    if (installedPlugins.indexOf(pluginId) >= 0) {
-        return pluginId;
-    }
-
-    if (pluginId.indexOf('cordova-plugin-') < 0) {
-        return validatePluginId('cordova-plugin-' + pluginId, installedPlugins);
-    }
-}
-
-function save(projectRoot, opts){
-    var xml = cordova_util.projectConfig(projectRoot);
-    var cfg = new ConfigParser(xml);
-
-    // First, remove all pre-existing plugins from config.xml
-    cfg.getPluginIdList().forEach(function(plugin){
-        cfg.removePlugin(plugin);
-    });
-
-    // Then, save top-level plugins and their sources
-    var jsonFile = path.join(projectRoot, 'plugins', 'fetch.json');
-    var plugins;
-    try {
-        // It might be the case that fetch.json file is not yet existent.
-        // for example: when we have never ran the command 'cordova plugin add foo' on the project
-        // in that case, there's nothing to do except bubble up the error
-        plugins = JSON.parse(fs.readFileSync(jsonFile, 'utf-8'));
-    } catch (err) {
-        return Q.reject(err.message);
-    }
-
-    Object.keys(plugins).forEach(function(pluginName){
-        var plugin = plugins[pluginName];
-        var pluginSource = plugin.source;
-
-        // If not a top-level plugin, skip it, don't save it to config.xml
-        if(!plugin.is_top_level){
-            return;
-        }
-
-        var attribs = {name: pluginName};
-        var spec = getSpec(pluginSource, projectRoot, pluginName);
-        if (spec) {
-            attribs.spec = spec;
-        }
-
-        var variables = getPluginVariables(plugin.variables);
-        cfg.addPlugin(attribs, variables);
-    });
-    cfg.write();
-
-    return Q.resolve();
-}
-
-function getPluginVariables(variables){
-    var result = [];
-    if(!variables){
-        return result;
-    }
-
-    Object.keys(variables).forEach(function(pluginVar){
-        result.push({name: pluginVar, value: variables[pluginVar]});
-    });
-
-    return result;
-}
-
-function getVersionFromConfigFile(plugin, cfg){
-    var parsedSpec = pluginSpec.parse(plugin);
-    var pluginEntry = cfg.getPlugin(parsedSpec.id);
-
-    return pluginEntry && pluginEntry.spec;
-}
-
-function list(projectRoot, hooksRunner, opts) {
-    var pluginsList = [];
-    return hooksRunner.fire('before_plugin_ls', opts)
-    .then(function() {
-        return getInstalledPlugins(projectRoot);
-    })
-    .then(function(plugins) {
-        if (plugins.length === 0) {
-            events.emit('results', 'No plugins added. Use `'+cordova_util.binname+' plugin add <plugin>`.');
-            return;
-        }
-        var pluginsDict = {};
-        var lines = [];
-        var txt, p;
-        for (var i=0; i<plugins.length; i++) {
-            p = plugins[i];
-            pluginsDict[p.id] = p;
-            pluginsList.push(p.id);
-            txt = p.id + ' ' + p.version + ' "' + (p.name || p.description) + '"';
-            lines.push(txt);
-        }
-        // Add warnings for deps with wrong versions.
-        for (var id in pluginsDict) {
-            p = pluginsDict[id];
-            for (var depId in p.deps) {
-                var dep = pluginsDict[depId];
-                //events.emit('results', p.deps[depId].version);
-                //events.emit('results', dep != null);
-                if (!dep) {
-                    txt = 'WARNING, missing dependency: plugin ' + id +
-                          ' depends on ' + depId +
-                          ' but it is not installed';
-                    lines.push(txt);
-                } else if (!semver.satisfies(dep.version, p.deps[depId].version)) {
-                    txt = 'WARNING, broken dependency: plugin ' + id +
-                          ' depends on ' + depId + ' ' + p.deps[depId].version +
-                          ' but installed version is ' + dep.version;
-                    lines.push(txt);
-                }
-            }
-        }
-        events.emit('results', lines.join('\n'));
-    })
-    .then(function() {
-        return hooksRunner.fire('after_plugin_ls', opts);
-    })
-    .then(function() {
-        return pluginsList;
-    });
-}
-
-function getInstalledPlugins(projectRoot) {
-    var pluginsDir = path.join(projectRoot, 'plugins');
-    // TODO: This should list based off of platform.json, not directories within plugins/
-    var pluginInfoProvider = new PluginInfoProvider();
-    return pluginInfoProvider.getAllWithinSearchPath(pluginsDir);
-}
-
-function saveToConfigXmlOn(config_json, options){
-    options = options || {};
-    var autosave =  config_json.auto_save_plugins || false;
-    return autosave || options.save;
-}
-
-function parseSource(target, opts) {
-    var url = require('url');
-    var uri = url.parse(target);
-    if (uri.protocol && uri.protocol != 'file:' && uri.protocol[1] != ':' && !target.match(/^\w+:\\/)) {
-        return target;
-    } else {
-        var plugin_dir = cordova_util.fixRelativePath(path.join(target, (opts.subdir || '.')));
-        if (fs.existsSync(plugin_dir)) {
-            return target;
-        }
-    }
-    return null;
-}
-
-function getSpec(pluginSource, projectRoot, pluginName) {
-    if (pluginSource.hasOwnProperty('url') || pluginSource.hasOwnProperty('path')) {
-        return pluginSource.url || pluginSource.path;
-    }
-
-    var version = null;
-    var scopedPackage = null;
-    if (pluginSource.hasOwnProperty('id')) {
-        // Note that currently version is only saved here if it was explicitly specified when the plugin was added.
-        var parsedSpec = pluginSpec.parse(pluginSource.id);
-        version = parsedSpec.version;
-        if (version) {
-            version = versionString(version);
-        }
-
-        if (parsedSpec.scope) {
-            scopedPackage = parsedSpec.package;
-        }
-    }
-
-    if (!version) {
-        // Fallback on getting version from the plugin folder, if it's there
-        var pluginInfoProvider = new PluginInfoProvider();
-        var dir = path.join(projectRoot, 'plugins', pluginName);
-
-        try {
-            // pluginInfoProvider.get() will throw if directory does not exist.
-            var pluginInfo = pluginInfoProvider.get(dir);
-            if (pluginInfo) {
-                version = versionString(pluginInfo.version);
-            }
-        } catch (err) {
-        }
-    }
-
-    if (scopedPackage) {
-        version = scopedPackage + '@' + version;
-    }
-
-    return version;
-}
-
-function versionString(version) {
-    var validVersion = semver.valid(version, true);
-    if (validVersion) {
-        return '~' + validVersion;
-    }
-
-    if (semver.validRange(version, true)) {
-        // Return what we were passed rather than the result of the validRange() call, as that call makes modifications
-        // we don't want, like converting '^1.2.3' to '>=1.2.3-0 <2.0.0-0'
-        return version;
-    }
-
-    return null;
-}
-
-/**
- * Gets the version of a plugin that should be fetched for a given project based
- * on the plugin's engine information from NPM and the platforms/plugins installed
- * in the project. The cordovaDependencies object in the package.json's engines
- * entry takes the form of an object that maps plugin versions to a series of
- * constraints and semver ranges. For example:
- *
- *     { plugin-version: { constraint: semver-range, ...}, ...}
- *
- * Constraint can be a plugin, platform, or cordova version. Plugin-version
- * can be either a single version (e.g. 3.0.0) or an upper bound (e.g. <3.0.0)
- *
- * @param {string}  projectRoot     The path to the root directory of the project
- * @param {object}  pluginInfo      The NPM info of the plugin to be fetched (e.g. the
- *                                  result of calling `registry.info()`)
- * @param {string}  cordovaVersion  The semver version of cordova-lib
- *
- * @return {Promise}                A promise that will resolve to either a string
- *                                  if there is a version of the plugin that this
- *                                  project satisfies or null if there is not
- */
-function getFetchVersion(projectRoot, pluginInfo, cordovaVersion) {
-    // Figure out the project requirements
-    if (pluginInfo.engines && pluginInfo.engines.cordovaDependencies) {
-        var pluginList = getInstalledPlugins(projectRoot);
-        var pluginMap = {};
-
-        pluginList.forEach(function(plugin) {
-            pluginMap[plugin.id] = plugin.version;
-        });
-
-        return cordova_util.getInstalledPlatformsWithVersions(projectRoot)
-        .then(function(platformVersions) {
-            return determinePluginVersionToFetch(
-                pluginInfo,
-                pluginMap,
-                platformVersions,
-                cordovaVersion);
-        });
-    } else {
-        // If we have no engine, we want to fall back to the default behavior
-        events.emit('verbose', 'npm info for ' + pluginInfo.name + ' did not contain any engine info. Fetching latest release');
-        return Q(null);
-    }
-}
-
-function findVersion(versions, version) {
-    var cleanedVersion = semver.clean(version);
-    for(var i = 0; i < versions.length; i++) {
-        if(semver.clean(versions[i]) === cleanedVersion) {
-            return versions[i];
-        }
-    }
-    return null;
-}
-
-/*
- * The engine entry maps plugin versions to constraints like so:
- *  {
- *      '1.0.0' : { 'cordova': '<5.0.0' },
- *      '<2.0.0': {
- *          'cordova': '>=5.0.0',
- *          'cordova-ios': '~5.0.0',
- *          'cordova-plugin-camera': '~5.0.0'
- *      },
- *      '3.0.0' : { 'cordova-ios': '>5.0.0' }
- *  }
- *
- * See cordova-spec/plugin_fetch.spec.js for test cases and examples
- */
-function determinePluginVersionToFetch(pluginInfo, pluginMap, platformMap, cordovaVersion) {
-    var allVersions = pluginInfo.versions;
-    var engine = pluginInfo.engines.cordovaDependencies;
-    var name = pluginInfo.name;
-
-    // Filters out pre-release versions
-    var latest = semver.maxSatisfying(allVersions, '>=0.0.0');
-
-    var versions = [];
-    var upperBound = null;
-    var upperBoundRange = null;
-    var upperBoundExists = false;
-
-    for(var version in engine) {
-        if(semver.valid(semver.clean(version)) && !semver.gt(version, latest)) {
-            versions.push(version);
-        } else {
-            // Check if this is an upperbound; validRange() handles whitespace
-            var cleanedRange = semver.validRange(version);
-            if(cleanedRange && UPPER_BOUND_REGEX.exec(cleanedRange)) {
-                upperBoundExists = true;
-                // We only care about the highest upper bound that our project does not support
-                if(getFailedRequirements(engine[version], pluginMap, platformMap, cordovaVersion).length !== 0) {
-                    var maxMatchingUpperBound = cleanedRange.substring(1);
-                    if (maxMatchingUpperBound && (!upperBound || semver.gt(maxMatchingUpperBound, upperBound))) {
-                        upperBound = maxMatchingUpperBound;
-                        upperBoundRange = version;
-                    }
-                }
-            } else {
-                events.emit('verbose', 'Ignoring invalid version in ' + name + ' cordovaDependencies: ' + version + ' (must be a single version <= latest or an upper bound)');
-            }
-        }
-    }
-
-    // If there were no valid requirements, we fall back to old behavior
-    if(!upperBoundExists && versions.length === 0) {
-        events.emit('verbose', 'Ignoring ' + name + ' cordovaDependencies entry because it did not contain any valid plugin version entries');
-        return null;
-    }
-
-    // Handle the lower end of versions by giving them a satisfied engine
-    if(!findVersion(versions, '0.0.0')) {
-        versions.push('0.0.0');
-        engine['0.0.0'] = {};
-    }
-
-    // Add an entry after the upper bound to handle the versions above the
-    // upper bound but below the next entry. For example: 0.0.0, <1.0.0, 2.0.0
-    // needs a 1.0.0 entry that has the same engine as 0.0.0
-    if(upperBound && !findVersion(versions, upperBound) && !semver.gt(upperBound, latest)) {
-        versions.push(upperBound);
-        var below = semver.maxSatisfying(versions, upperBoundRange);
-
-        // Get the original entry without trimmed whitespace
-        below = below ? findVersion(versions, below) : null;
-        engine[upperBound] = below ? engine[below] : {};
-    }
-
-    // Sort in descending order; we want to start at latest and work back
-    versions.sort(semver.rcompare);
-
-    for(var i = 0; i < versions.length; i++) {
-        if(upperBound && semver.lt(versions[i], upperBound)) {
-            // Because we sorted in desc. order, if the upper bound we found
-            // applies to this version (and thus the ones below) we can just
-            // quit
-            break;
-        }
-
-        var range = i? ('>=' + versions[i] + ' <' + versions[i-1]) : ('>=' + versions[i]);
-        var maxMatchingVersion = semver.maxSatisfying(allVersions, range);
-
-        if (maxMatchingVersion && getFailedRequirements(engine[versions[i]], pluginMap, platformMap, cordovaVersion).length === 0) {
-
-            // Because we sorted in descending order, we can stop searching once
-            // we hit a satisfied constraint
-            if (maxMatchingVersion !== latest) {
-                var failedReqs = getFailedRequirements(engine[versions[0]], pluginMap, platformMap, cordovaVersion);
-
-                // Warn the user that we are not fetching latest
-                listUnmetRequirements(name, failedReqs);
-                events.emit('warn', 'Fetching highest version of ' + name + ' that this project supports: ' + maxMatchingVersion + ' (latest is ' + latest + ')');
-            }
-            return maxMatchingVersion;
-        }
-    }
-
-    // No version of the plugin is satisfied. In this case, we fall back to
-    // fetching the latest version, but also output a warning
-    var latestFailedReqs = versions.length > 0 ? getFailedRequirements(engine[versions[0]], pluginMap, platformMap, cordovaVersion) : [];
-
-    // If the upper bound is greater than latest, we need to combine its engine
-    // requirements with latest to print out in the warning
-    if(upperBound && semver.satisfies(latest, upperBoundRange)) {
-        var upperFailedReqs = getFailedRequirements(engine[upperBoundRange], pluginMap, platformMap, cordovaVersion);
-        upperFailedReqs.forEach(function(failedReq) {
-            for(var i = 0; i < latestFailedReqs.length; i++) {
-                if(latestFailedReqs[i].dependency === failedReq.dependency) {
-                    // Not going to overcomplicate things and actually merge the ranges
-                    latestFailedReqs[i].required += ' AND ' + failedReq.required;
-                    return;
-                }
-            }
-
-            // There is no req to merge it with
-            latestFailedReqs.push(failedReq);
-        });
-    }
-
-    listUnmetRequirements(name, latestFailedReqs);
-    events.emit('warn', 'Current project does not satisfy the engine requirements specified by any version of ' + name + '. Fetching latest version of plugin anyway (may be incompatible)');
-
-    // No constraints were satisfied
-    return null;
-}
-
-
-function getFailedRequirements(reqs, pluginMap, platformMap, cordovaVersion) {
-    var failed = [];
-    var version = cordovaVersion;
-    if (semver.prerelease(version)) {
-        //  semver.inc with 'patch' type removes prereleased tag from version
-        version = semver.inc(version, 'patch');
-    }
-
-    for (var req in reqs) {
-        if(reqs.hasOwnProperty(req) && typeof req === 'string' && semver.validRange(reqs[req])) {
-            var badInstalledVersion = null;
-            var trimmedReq = req.trim();
-
-            if(pluginMap[trimmedReq] && !semver.satisfies(pluginMap[trimmedReq], reqs[req])) {
-                badInstalledVersion = pluginMap[req];
-            } else if(trimmedReq === 'cordova' && !semver.satisfies(version, reqs[req])) {
-                badInstalledVersion = cordovaVersion;
-            } else if(trimmedReq.indexOf('cordova-') === 0) {
-                // Might be a platform constraint
-                var platform = trimmedReq.substring(8);
-                if(platformMap[platform] && !semver.satisfies(platformMap[platform], reqs[req])) {
-                    badInstalledVersion = platformMap[platform];
-                }
-            }
-
-            if(badInstalledVersion) {
-                failed.push({
-                    dependency: trimmedReq,
-                    installed: badInstalledVersion.trim(),
-                    required: reqs[req].trim()
-                });
-            }
-        } else {
-            events.emit('verbose', 'Ignoring invalid plugin dependency constraint ' + req + ':' + reqs[req]);
-        }
-    }
-
-    return failed;
-}
-
-function listUnmetRequirements(name, failedRequirements) {
-    events.emit('warn', 'Unmet project requirements for latest version of ' + name + ':');
-
-    failedRequirements.forEach(function(req) {
-        events.emit('warn', '    ' + req.dependency + ' (' + req.installed + ' in project, ' + req.required + ' required)');
-    });
-}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/ce93923f/src/cordova/plugin/add.js
----------------------------------------------------------------------
diff --git a/src/cordova/plugin/add.js b/src/cordova/plugin/add.js
new file mode 100644
index 0000000..346e688
--- /dev/null
+++ b/src/cordova/plugin/add.js
@@ -0,0 +1,564 @@
+/**
+    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_util = require('../util');
+var plugin_util = require('./util');
+var config = require('../config');
+var pkgJson = require('../../../package.json');
+var pluginSpec = require('./plugin_spec_parser');
+var plugman = require('../../plugman/plugman');
+var registry = require('../../plugman/registry/registry');
+var chainMap = require('../../util/promise-util').Q_chainmap;
+var ConfigParser = require('cordova-common').ConfigParser;
+var CordovaError = require('cordova-common').CordovaError;
+var PluginInfoProvider = require('cordova-common').PluginInfoProvider;
+var events = require('cordova-common').events;
+var shell = require('shelljs');
+var Q = require('Q');
+var path = require('path');
+var fs = require('fs');
+var semver = require('semver');
+
+module.exports = add;
+module.exports.determinePluginTarget = determinePluginTarget;
+module.exports.parseSource = parseSource;
+module.exports.getVersionFromConfigFile = getVersionFromConfigFile;
+module.exports.getFetchVersion = getFetchVersion;
+module.exports.determinePluginVersionToFetch = determinePluginVersionToFetch;
+module.exports.getFailedRequirements = getFailedRequirements;
+module.exports.findVersion = findVersion;
+module.exports.listUnmetRequirements = listUnmetRequirements;
+
+function add (projectRoot, targets, hooksRunner, opts) {
+    if (!targets || !targets.length) {
+        return Q.reject(new CordovaError('No plugin specified. Please specify a plugin to add. See `' + cordova_util.binname + ' plugin search`.'));
+    }
+
+    var shouldRunPrepare = false;
+    var pluginPath = path.join(projectRoot, 'plugins');
+    var platformList = cordova_util.listPlatforms(projectRoot);
+    var config_json = config.read(projectRoot);
+    var xml = cordova_util.projectConfig(projectRoot);
+    var cfg = new ConfigParser(xml);
+    var searchPath = config_json.plugin_search_path || [];
+    if (typeof opts.searchpath === 'string') {
+        searchPath = opts.searchpath.split(path.delimiter).concat(searchPath);
+    } else if (opts.searchpath) {
+        searchPath = opts.searchpath.concat(searchPath);
+    }
+    // Blank it out to appease unit tests.
+    if (searchPath.length === 0) {
+        searchPath = undefined;
+    }
+
+    opts.cordova = { plugins: cordova_util.findPlugins(pluginPath) };
+    return hooksRunner.fire('before_plugin_add', opts)
+    .then(function () {
+        var pluginInfoProvider = new PluginInfoProvider();
+        return opts.plugins.reduce(function (soFar, target) {
+            return soFar.then(function () {
+                if (target[target.length - 1] === path.sep) {
+                    target = target.substring(0, target.length - 1);
+                }
+
+                // Fetch the plugin first.
+                var fetchOptions = {
+                    searchpath: searchPath,
+                    noregistry: opts.noregistry,
+                    fetch: opts.fetch || false,
+                    save: opts.save,
+                    nohooks: opts.nohooks,
+                    link: opts.link,
+                    pluginInfoProvider: pluginInfoProvider,
+                    variables: opts.cli_variables,
+                    is_top_level: true
+                };
+
+                return module.exports.determinePluginTarget(projectRoot, cfg, target, fetchOptions).then(function (resolvedTarget) {
+                    target = resolvedTarget;
+                    events.emit('verbose', 'Calling plugman.fetch on plugin "' + target + '"');
+                    return plugman.fetch(target, pluginPath, fetchOptions);
+                });
+            }).then(function (directory) {
+                return pluginInfoProvider.get(directory);
+            }).then(function (pluginInfo) {
+                // Validate top-level required variables
+                var pluginVariables = pluginInfo.getPreferences();
+                opts.cli_variables = opts.cli_variables || {};
+                var pluginEntry = cfg.getPlugin(pluginInfo.id);
+                // Get variables from config.xml
+                var configVariables = pluginEntry ? pluginEntry.variables : {};
+                // Add config variable if it's missing in cli_variables
+                Object.keys(configVariables).forEach(function (variable) {
+                    opts.cli_variables[variable] = opts.cli_variables[variable] || configVariables[variable];
+                });
+                var missingVariables = Object.keys(pluginVariables)
+                .filter(function (variableName) {
+                    // discard variables with default value
+                    return !(pluginVariables[variableName] || opts.cli_variables[variableName]);
+                });
+
+                if (missingVariables.length) {
+                    events.emit('verbose', 'Removing ' + pluginInfo.dir + ' because mandatory plugin variables were missing.');
+                    shell.rm('-rf', pluginInfo.dir);
+                    var msg = 'Variable(s) missing (use: --variable ' + missingVariables.join('=value --variable ') + '=value).';
+                    return Q.reject(new CordovaError(msg));
+                }
+
+                // Iterate (in serial!) over all platforms in the project and install the plugin.
+                return chainMap(platformList, function (platform) {
+                    var platformRoot = path.join(projectRoot, 'platforms', platform);
+                    var options = {
+                        cli_variables: opts.cli_variables || {},
+                        browserify: opts.browserify || false,
+                        fetch: opts.fetch || false,
+                        save: opts.save,
+                        searchpath: searchPath,
+                        noregistry: opts.noregistry,
+                        link: opts.link,
+                        pluginInfoProvider: pluginInfoProvider,
+                        // Set up platform to install asset files/js modules to <platform>/platform_www dir
+                        // instead of <platform>/www. This is required since on each prepare platform's www dir is changed
+                        // and files from 'platform_www' merged into 'www'. Thus we need to persist these
+                        // files platform_www directory, so they'll be applied to www on each prepare.
+                        usePlatformWww: true,
+                        nohooks: opts.nohooks,
+                        force: opts.force
+                    };
+
+                    events.emit('verbose', 'Calling plugman.install on plugin "' + pluginInfo.dir + '" for platform "' + platform);
+                    return plugman.install(platform, platformRoot, path.basename(pluginInfo.dir), pluginPath, options)
+                    .then(function (didPrepare) {
+                        // If platform does not returned anything we'll need
+                        // to trigger a prepare after all plugins installed
+                        if (!didPrepare) shouldRunPrepare = true;
+                    });
+                })
+                .thenResolve(pluginInfo);
+            })
+            .then(function (pluginInfo) {
+                var pkgJson;
+                var pkgJsonPath = path.join(projectRoot, 'package.json');
+
+                // save to config.xml
+                if (plugin_util.saveToConfigXmlOn(config_json, opts)) {
+                    // If statement to see if pkgJsonPath exists in the filesystem
+                    if (fs.existsSync(pkgJsonPath)) {
+                        // Delete any previous caches of require(package.json)
+                        pkgJson = cordova_util.requireNoCache(pkgJsonPath);
+                    }
+                    // If package.json exists, the plugin object and plugin name
+                    // will be added to package.json if not already there.
+                    if (pkgJson) {
+                        pkgJson.cordova = pkgJson.cordova || {};
+                        pkgJson.cordova.plugins = pkgJson.cordova.plugins || {};
+                        // Plugin and variables are added.
+                        pkgJson.cordova.plugins[pluginInfo.id] = opts.cli_variables;
+                        events.emit('log', 'Adding ' + pluginInfo.id + ' to package.json');
+
+                        // Write to package.json
+                        fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2), 'utf8');
+                    }
+
+                    var src = module.exports.parseSource(target, opts);
+                    var attributes = {
+                        name: pluginInfo.id
+                    };
+
+                    if (src) {
+                        attributes.spec = src;
+                    } else {
+                        var ver = '~' + pluginInfo.version;
+                        // Scoped packages need to have the package-spec along with the version
+                        var parsedSpec = pluginSpec.parse(target);
+                        if (pkgJson && pkgJson.dependencies && pkgJson.dependencies[pluginInfo.id]) {
+                            attributes.spec = pkgJson.dependencies[pluginInfo.id];
+                        } else {
+                            if (parsedSpec.scope) {
+                                attributes.spec = parsedSpec.package + '@' + ver;
+                            } else {
+                                attributes.spec = ver;
+                            }
+                        }
+                    }
+                    xml = cordova_util.projectConfig(projectRoot);
+                    cfg = new ConfigParser(xml);
+                    cfg.removePlugin(pluginInfo.id);
+                    cfg.addPlugin(attributes, opts.cli_variables);
+                    cfg.write();
+
+                    events.emit('results', 'Saved plugin info for "' + pluginInfo.id + '" to config.xml');
+                }
+            });
+        }, Q());
+    }).then(function () {
+        // CB-11022 We do not need to run prepare after plugin install until shouldRunPrepare flag is set to true
+        if (!shouldRunPrepare) {
+            return Q();
+        }
+        // Need to require right here instead of doing this at the beginning of file
+        // otherwise tests are failing without any real reason.
+        // TODO: possible circular dependency?
+        return require('./prepare').preparePlatforms(platformList, projectRoot, opts);
+    }).then(function () {
+        opts.cordova = { plugins: cordova_util.findPlugins(pluginPath) };
+        return hooksRunner.fire('after_plugin_add', opts);
+    });
+}
+
+function determinePluginTarget (projectRoot, cfg, target, fetchOptions) {
+    var parsedSpec = pluginSpec.parse(target);
+    var id = parsedSpec.package || target;
+    // CB-10975 We need to resolve relative path to plugin dir from app's root before checking whether if it exists
+    var maybeDir = cordova_util.fixRelativePath(id);
+    if (parsedSpec.version || cordova_util.isUrl(id) || cordova_util.isDirectory(maybeDir)) {
+        return Q(target);
+    }
+    // Require project pkgJson.
+    var pkgJsonPath = path.join(projectRoot, 'package.json');
+    var cordovaVersion = pkgJson.version;
+    if (fs.existsSync(pkgJsonPath)) {
+        pkgJson = cordova_util.requireNoCache(pkgJsonPath);
+    }
+
+    // If no parsedSpec.version, use the one from pkg.json or config.xml.
+    if (!parsedSpec.version) {
+        // Retrieve from pkg.json.
+        if (pkgJson && pkgJson.dependencies && pkgJson.dependencies[id]) {
+            events.emit('verbose', 'No version specified for ' + id + ', retrieving version from package.json');
+            parsedSpec.version = pkgJson.dependencies[id];
+        } else {
+            // If no version is specified, retrieve the version (or source) from config.xml.
+            events.emit('verbose', 'No version specified for ' + id + ', retrieving version from config.xml');
+            parsedSpec.version = module.exports.getVersionFromConfigFile(id, cfg);
+        }
+    }
+
+    // If parsedSpec.version satisfies pkgJson version, no writing to pkg.json. Only write when
+    // it does not satisfy.
+    if (parsedSpec.version) {
+        if (pkgJson && pkgJson.dependencies && pkgJson.dependencies[parsedSpec.package]) {
+            var noSymbolVersion = parsedSpec.version;
+            if (parsedSpec.version.charAt(0) === '^' || parsedSpec.version.charAt(0) === '~') {
+                noSymbolVersion = parsedSpec.version.slice(1);
+            }
+
+            if (cordova_util.isUrl(parsedSpec.version) || cordova_util.isDirectory(parsedSpec.version)) {
+                if (pkgJson.dependencies[parsedSpec.package] !== parsedSpec.version) {
+                    pkgJson.dependencies[parsedSpec.package] = parsedSpec.version;
+                }
+                if (fetchOptions.save === true) {
+                    fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2), 'utf8');
+                }
+            } else if (!semver.satisfies(noSymbolVersion, pkgJson.dependencies[parsedSpec.package])) {
+                pkgJson.dependencies[parsedSpec.package] = parsedSpec.version;
+                if (fetchOptions.save === true) {
+                    fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2), 'utf8');
+                }
+            }
+        }
+    }
+
+    if (cordova_util.isUrl(parsedSpec.version) || cordova_util.isDirectory(parsedSpec.version) || pluginSpec.parse(parsedSpec.version).scope) {
+        return Q(parsedSpec.version);
+    }
+
+    // If version exists in pkg.json or config.xml, use that.
+    if (parsedSpec.version) {
+        return Q(id + '@' + parsedSpec.version);
+    }
+
+    // If no version is given at all and we are fetching from npm, we
+    // can attempt to use the Cordova dependencies the plugin lists in
+    // their package.json
+    var shouldUseNpmInfo = !fetchOptions.searchpath && !fetchOptions.noregistry;
+
+    events.emit('verbose', 'No version for ' + parsedSpec.package + ' saved in config.xml');
+    if (shouldUseNpmInfo) {
+        events.emit('verbose', 'Attempting to use npm info for ' + parsedSpec.package + ' to choose a compatible release');
+    } else {
+        events.emit('verbose', 'Not checking npm info for ' + parsedSpec.package + ' because searchpath or noregistry flag was given');
+    }
+
+    // TODO: whoa wat
+    return (shouldUseNpmInfo ? registry.info([id])
+    .then(function (pluginInfo) {
+        return getFetchVersion(projectRoot, pluginInfo, cordovaVersion);
+    }) : Q(null))
+    .then(function (fetchVersion) {
+        return fetchVersion ? (id + '@' + fetchVersion) : target;
+    });
+}
+
+function parseSource (target, opts) {
+    var url = require('url');
+    var uri = url.parse(target);
+    if (uri.protocol && uri.protocol !== 'file:' && uri.protocol[1] !== ':' && !target.match(/^\w+:\\/)) {
+        return target;
+    } else {
+        var plugin_dir = cordova_util.fixRelativePath(path.join(target, (opts.subdir || '.')));
+        if (fs.existsSync(plugin_dir)) {
+            return target;
+        }
+    }
+    return null;
+}
+
+function getVersionFromConfigFile (plugin, cfg) {
+    var parsedSpec = pluginSpec.parse(plugin);
+    var pluginEntry = cfg.getPlugin(parsedSpec.id);
+
+    return pluginEntry && pluginEntry.spec;
+}
+
+/**
+ * Gets the version of a plugin that should be fetched for a given project based
+ * on the plugin's engine information from NPM and the platforms/plugins installed
+ * in the project. The cordovaDependencies object in the package.json's engines
+ * entry takes the form of an object that maps plugin versions to a series of
+ * constraints and semver ranges. For example:
+ *
+ *     { plugin-version: { constraint: semver-range, ...}, ...}
+ *
+ * Constraint can be a plugin, platform, or cordova version. Plugin-version
+ * can be either a single version (e.g. 3.0.0) or an upper bound (e.g. <3.0.0)
+ *
+ * @param {string}  projectRoot     The path to the root directory of the project
+ * @param {object}  pluginInfo      The NPM info of the plugin to be fetched (e.g. the
+ *                                  result of calling `registry.info()`)
+ * @param {string}  cordovaVersion  The semver version of cordova-lib
+ *
+ * @return {Promise}                A promise that will resolve to either a string
+ *                                  if there is a version of the plugin that this
+ *                                  project satisfies or null if there is not
+ */
+function getFetchVersion (projectRoot, pluginInfo, cordovaVersion) {
+    // Figure out the project requirements
+    if (pluginInfo.engines && pluginInfo.engines.cordovaDependencies) {
+        var pluginList = plugin_util.getInstalledPlugins(projectRoot);
+        var pluginMap = {};
+
+        pluginList.forEach(function (plugin) {
+            pluginMap[plugin.id] = plugin.version;
+        });
+
+        return cordova_util.getInstalledPlatformsWithVersions(projectRoot)
+        .then(function (platformVersions) {
+            return module.exports.determinePluginVersionToFetch(
+                pluginInfo,
+                pluginMap,
+                platformVersions,
+                cordovaVersion);
+        });
+    } else {
+        // If we have no engine, we want to fall back to the default behavior
+        events.emit('verbose', 'npm info for ' + pluginInfo.name + ' did not contain any engine info. Fetching latest release');
+        return Q(null);
+    }
+}
+
+// For upper bounds in cordovaDependencies
+var UPPER_BOUND_REGEX = /^<\d+\.\d+\.\d+$/;
+/*
+ * The engine entry maps plugin versions to constraints like so:
+ *  {
+ *      '1.0.0' : { 'cordova': '<5.0.0' },
+ *      '<2.0.0': {
+ *          'cordova': '>=5.0.0',
+ *          'cordova-ios': '~5.0.0',
+ *          'cordova-plugin-camera': '~5.0.0'
+ *      },
+ *      '3.0.0' : { 'cordova-ios': '>5.0.0' }
+ *  }
+ *
+ * See cordova-spec/plugin_fetch.spec.js for test cases and examples
+ */
+function determinePluginVersionToFetch (pluginInfo, pluginMap, platformMap, cordovaVersion) {
+    var allVersions = pluginInfo.versions;
+    var engine = pluginInfo.engines.cordovaDependencies;
+    var name = pluginInfo.name;
+
+    // Filters out pre-release versions
+    var latest = semver.maxSatisfying(allVersions, '>=0.0.0');
+
+    var versions = [];
+    var upperBound = null;
+    var upperBoundRange = null;
+    var upperBoundExists = false;
+
+    for (var version in engine) {
+        if (semver.valid(semver.clean(version)) && !semver.gt(version, latest)) {
+            versions.push(version);
+        } else {
+            // Check if this is an upperbound; validRange() handles whitespace
+            var cleanedRange = semver.validRange(version);
+            if (cleanedRange && UPPER_BOUND_REGEX.exec(cleanedRange)) {
+                upperBoundExists = true;
+                // We only care about the highest upper bound that our project does not support
+                if (module.exports.getFailedRequirements(engine[version], pluginMap, platformMap, cordovaVersion).length !== 0) {
+                    var maxMatchingUpperBound = cleanedRange.substring(1);
+                    if (maxMatchingUpperBound && (!upperBound || semver.gt(maxMatchingUpperBound, upperBound))) {
+                        upperBound = maxMatchingUpperBound;
+                        upperBoundRange = version;
+                    }
+                }
+            } else {
+                events.emit('verbose', 'Ignoring invalid version in ' + name + ' cordovaDependencies: ' + version + ' (must be a single version <= latest or an upper bound)');
+            }
+        }
+    }
+
+    // If there were no valid requirements, we fall back to old behavior
+    if (!upperBoundExists && versions.length === 0) {
+        events.emit('verbose', 'Ignoring ' + name + ' cordovaDependencies entry because it did not contain any valid plugin version entries');
+        return null;
+    }
+
+    // Handle the lower end of versions by giving them a satisfied engine
+    if (!module.exports.findVersion(versions, '0.0.0')) {
+        versions.push('0.0.0');
+        engine['0.0.0'] = {};
+    }
+
+    // Add an entry after the upper bound to handle the versions above the
+    // upper bound but below the next entry. For example: 0.0.0, <1.0.0, 2.0.0
+    // needs a 1.0.0 entry that has the same engine as 0.0.0
+    if (upperBound && !module.exports.findVersion(versions, upperBound) && !semver.gt(upperBound, latest)) {
+        versions.push(upperBound);
+        var below = semver.maxSatisfying(versions, upperBoundRange);
+
+        // Get the original entry without trimmed whitespace
+        below = below ? module.exports.findVersion(versions, below) : null;
+        engine[upperBound] = below ? engine[below] : {};
+    }
+
+    // Sort in descending order; we want to start at latest and work back
+    versions.sort(semver.rcompare);
+
+    for (var i = 0; i < versions.length; i++) {
+        if (upperBound && semver.lt(versions[i], upperBound)) {
+            // Because we sorted in desc. order, if the upper bound we found
+            // applies to this version (and thus the ones below) we can just
+            // quit
+            break;
+        }
+
+        var range = i ? ('>=' + versions[i] + ' <' + versions[i - 1]) : ('>=' + versions[i]);
+        var maxMatchingVersion = semver.maxSatisfying(allVersions, range);
+
+        if (maxMatchingVersion && module.exports.getFailedRequirements(engine[versions[i]], pluginMap, platformMap, cordovaVersion).length === 0) {
+            // Because we sorted in descending order, we can stop searching once
+            // we hit a satisfied constraint
+            if (maxMatchingVersion !== latest) {
+                var failedReqs = module.exports.getFailedRequirements(engine[versions[0]], pluginMap, platformMap, cordovaVersion);
+
+                // Warn the user that we are not fetching latest
+                module.exports.listUnmetRequirements(name, failedReqs);
+                events.emit('warn', 'Fetching highest version of ' + name + ' that this project supports: ' + maxMatchingVersion + ' (latest is ' + latest + ')');
+            }
+            return maxMatchingVersion;
+        }
+    }
+
+    // No version of the plugin is satisfied. In this case, we fall back to
+    // fetching the latest version, but also output a warning
+    var latestFailedReqs = versions.length > 0 ? module.exports.getFailedRequirements(engine[versions[0]], pluginMap, platformMap, cordovaVersion) : [];
+
+    // If the upper bound is greater than latest, we need to combine its engine
+    // requirements with latest to print out in the warning
+    if (upperBound && semver.satisfies(latest, upperBoundRange)) {
+        var upperFailedReqs = module.exports.getFailedRequirements(engine[upperBoundRange], pluginMap, platformMap, cordovaVersion);
+        upperFailedReqs.forEach(function (failedReq) {
+            for (var i = 0; i < latestFailedReqs.length; i++) {
+                if (latestFailedReqs[i].dependency === failedReq.dependency) {
+                    // Not going to overcomplicate things and actually merge the ranges
+                    latestFailedReqs[i].required += ' AND ' + failedReq.required;
+                    return;
+                }
+            }
+
+            // There is no req to merge it with
+            latestFailedReqs.push(failedReq);
+        });
+    }
+
+    module.exports.listUnmetRequirements(name, latestFailedReqs);
+    events.emit('warn', 'Current project does not satisfy the engine requirements specified by any version of ' + name + '. Fetching latest version of plugin anyway (may be incompatible)');
+
+    // No constraints were satisfied
+    return null;
+}
+
+function getFailedRequirements (reqs, pluginMap, platformMap, cordovaVersion) {
+    var failed = [];
+    var version = cordovaVersion;
+    if (semver.prerelease(version)) {
+        //  semver.inc with 'patch' type removes prereleased tag from version
+        version = semver.inc(version, 'patch');
+    }
+
+    for (var req in reqs) {
+        if (reqs.hasOwnProperty(req) && typeof req === 'string' && semver.validRange(reqs[req])) {
+            var badInstalledVersion = null;
+            var trimmedReq = req.trim();
+
+            if (pluginMap[trimmedReq] && !semver.satisfies(pluginMap[trimmedReq], reqs[req])) {
+                badInstalledVersion = pluginMap[req];
+            } else if (trimmedReq === 'cordova' && !semver.satisfies(version, reqs[req])) {
+                badInstalledVersion = cordovaVersion;
+            } else if (trimmedReq.indexOf('cordova-') === 0) {
+                // Might be a platform constraint
+                var platform = trimmedReq.substring(8);
+                if (platformMap[platform] && !semver.satisfies(platformMap[platform], reqs[req])) {
+                    badInstalledVersion = platformMap[platform];
+                }
+            }
+
+            if (badInstalledVersion) {
+                failed.push({
+                    dependency: trimmedReq,
+                    installed: badInstalledVersion.trim(),
+                    required: reqs[req].trim()
+                });
+            }
+        } else {
+            events.emit('verbose', 'Ignoring invalid plugin dependency constraint ' + req + ':' + reqs[req]);
+        }
+    }
+
+    return failed;
+}
+
+function findVersion (versions, version) {
+    var cleanedVersion = semver.clean(version);
+    for (var i = 0; i < versions.length; i++) {
+        if (semver.clean(versions[i]) === cleanedVersion) {
+            return versions[i];
+        }
+    }
+    return null;
+}
+
+function listUnmetRequirements (name, failedRequirements) {
+    events.emit('warn', 'Unmet project requirements for latest version of ' + name + ':');
+
+    failedRequirements.forEach(function (req) {
+        events.emit('warn', '    ' + req.dependency + ' (' + req.installed + ' in project, ' + req.required + ' required)');
+    });
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/ce93923f/src/cordova/plugin/index.js
----------------------------------------------------------------------
diff --git a/src/cordova/plugin/index.js b/src/cordova/plugin/index.js
new file mode 100644
index 0000000..1099de1
--- /dev/null
+++ b/src/cordova/plugin/index.js
@@ -0,0 +1,96 @@
+/**
+    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_util = require('../util');
+var Q = require('q');
+var CordovaError = require('cordova-common').CordovaError;
+var HooksRunner = require('../../hooks/HooksRunner');
+
+module.exports = plugin;
+module.exports.add = require('./add');
+module.exports.remove = require('./remove');
+module.exports.list = require('./list');
+module.exports.save = require('./save');
+
+function plugin (command, targets, opts) {
+    // CB-10519 wrap function code into promise so throwing error
+    // would result in promise rejection instead of uncaught exception
+    return Q().then(function () {
+        var projectRoot = cordova_util.cdProjectRoot();
+
+        // Dance with all the possible call signatures we've come up over the time. They can be:
+        // 1. plugin() -> list the plugins
+        // 2. plugin(command, Array of targets, maybe opts object)
+        // 3. plugin(command, target1, target2, target3 ... )
+        // The targets are not really targets, they can be a mixture of plugins and options to be passed to plugman.
+
+        command = command || 'ls';
+        targets = targets || [];
+        opts = opts || {};
+        if (opts.length) {
+            // This is the case with multiple targets as separate arguments and opts is not opts but another target.
+            targets = Array.prototype.slice.call(arguments, 1);
+            opts = {};
+        }
+        if (!Array.isArray(targets)) {
+            // This means we had a single target given as string.
+            targets = [targets];
+        }
+        opts.options = opts.options || [];
+        opts.plugins = [];
+
+        var hooksRunner = new HooksRunner(projectRoot);
+
+        // Massage plugin name(s) / path(s)
+        if (!targets || !targets.length) {
+            // TODO: what if command provided is 'remove' ?
+            if (command === 'add' || command === 'rm') {
+                return Q.reject(new CordovaError('You need to qualify `' + cordova_util.binname + ' plugin add` or `' + cordova_util.binname + ' plugin remove` with one or more plugins!'));
+            } else {
+                targets = [];
+            }
+        }
+
+        // Split targets between plugins and options
+        // Assume everything after a token with a '-' is an option
+        for (var i = 0; i < targets.length; i++) {
+            if (targets[i].match(/^-/)) {
+                opts.options = targets.slice(i);
+                break;
+            } else {
+                opts.plugins.push(targets[i]);
+            }
+        }
+
+        switch (command) {
+        case 'add':
+            return module.exports.add(projectRoot, targets, hooksRunner, opts);
+        case 'rm':
+        case 'remove':
+            return module.exports.remove(projectRoot, targets, hooksRunner, opts);
+        case 'search':
+            return module.exports.search(hooksRunner, opts);
+        case 'save':
+            // save the versions/folders/git-urls of currently installed plugins into config.xml
+            return module.exports.save(projectRoot, opts);
+        default:
+            return module.exports.list(projectRoot, hooksRunner);
+        }
+    });
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/ce93923f/src/cordova/plugin/list.js
----------------------------------------------------------------------
diff --git a/src/cordova/plugin/list.js b/src/cordova/plugin/list.js
new file mode 100644
index 0000000..78d9521
--- /dev/null
+++ b/src/cordova/plugin/list.js
@@ -0,0 +1,75 @@
+/**
+    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 semver = require('semver');
+var events = require('cordova-common').events;
+var plugin_util = require('./util');
+var cordova_util = require('../util');
+
+module.exports = list;
+
+function list (projectRoot, hooksRunner, opts) {
+    var pluginsList = [];
+    return hooksRunner.fire('before_plugin_ls', opts)
+    .then(function () {
+        return plugin_util.getInstalledPlugins(projectRoot);
+    }).then(function (plugins) {
+        if (plugins.length === 0) {
+            events.emit('results', 'No plugins added. Use `' + cordova_util.binname + ' plugin add <plugin>`.');
+            return;
+        }
+        var pluginsDict = {};
+        var lines = [];
+        var txt, p;
+        for (var i = 0; i < plugins.length; i++) {
+            p = plugins[i];
+            pluginsDict[p.id] = p;
+            pluginsList.push(p.id);
+            txt = p.id + ' ' + p.version + ' "' + (p.name || p.description) + '"';
+            lines.push(txt);
+        }
+        // Add warnings for deps with wrong versions.
+        for (var id in pluginsDict) {
+            p = pluginsDict[id];
+            for (var depId in p.deps) {
+                var dep = pluginsDict[depId];
+                // events.emit('results', p.deps[depId].version);
+                // events.emit('results', dep != null);
+                if (!dep) {
+                    txt = 'WARNING, missing dependency: plugin ' + id +
+                          ' depends on ' + depId +
+                          ' but it is not installed';
+                    lines.push(txt);
+                } else if (!semver.satisfies(dep.version, p.deps[depId].version)) {
+                    txt = 'WARNING, broken dependency: plugin ' + id +
+                          ' depends on ' + depId + ' ' + p.deps[depId].version +
+                          ' but installed version is ' + dep.version;
+                    lines.push(txt);
+                }
+            }
+        }
+        events.emit('results', lines.join('\n'));
+    })
+    .then(function () {
+        return hooksRunner.fire('after_plugin_ls', opts);
+    })
+    .then(function () {
+        return pluginsList;
+    });
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/ce93923f/src/cordova/plugin/plugin_spec_parser.js
----------------------------------------------------------------------
diff --git a/src/cordova/plugin/plugin_spec_parser.js b/src/cordova/plugin/plugin_spec_parser.js
new file mode 100644
index 0000000..7ad1bae
--- /dev/null
+++ b/src/cordova/plugin/plugin_spec_parser.js
@@ -0,0 +1,61 @@
+/**
+    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.
+*/
+
+// npm packages follow the pattern of (@scope/)?package(@spec)? where scope and tag are optional
+var NPM_SPEC_REGEX = /^(@[^\/]+\/)?([^@\/]+)(?:@(.+))?$/;
+
+module.exports.parse = parse;
+
+/**
+ * Represents a parsed specification for a plugin
+ * @class
+ * @param {String} raw      The raw specification (i.e. provided by the user)
+ * @param {String} scope    The scope of the package if this is an npm package
+ * @param {String} id       The id of the package if this is an npm package
+ * @param {String} version  The version specified for the package if this is an npm package
+ */
+function PluginSpec (raw, scope, id, version) {
+    /** @member {String|null} The npm scope of the plugin spec or null if it does not have one */
+    this.scope = scope || null;
+
+    /** @member {String|null} The id of the plugin or the raw plugin spec if it is not an npm package */
+    this.id = id || raw;
+
+    /** @member {String|null} The specified version of the plugin or null if no version was specified */
+    this.version = version || null;
+
+    /** @member {String|null} The npm package of the plugin (with scope) or null if this is not a spec for an npm package */
+    this.package = (scope ? scope + id : id) || null;
+}
+
+/**
+ * Tries to parse the given string as an npm-style package specification of
+ * the form (@scope/)?package(@version)? and return the various parts.
+ *
+ * @param {String} raw  The string to be parsed
+ * @return {PluginSpec}  The parsed plugin spec
+ */
+function parse (raw) {
+    var split = NPM_SPEC_REGEX.exec(raw);
+    if (split) {
+        return new PluginSpec(raw, split[1], split[2], split[3]);
+    }
+
+    return new PluginSpec(raw);
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[04/14] cordova-lib git commit: first pass at plugin command refactor

Posted by fi...@apache.org.
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/ce93923f/src/cordova/plugin/remove.js
----------------------------------------------------------------------
diff --git a/src/cordova/plugin/remove.js b/src/cordova/plugin/remove.js
new file mode 100644
index 0000000..9579fee
--- /dev/null
+++ b/src/cordova/plugin/remove.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 ConfigParser = require('cordova-common').ConfigParser;
+var CordovaError = require('cordova-common').CordovaError;
+var events = require('cordova-common').events;
+var cordova_util = require('../util');
+var config = require('../config');
+var plugin_util = require('./util');
+var plugman = require('../../plugman/plugman');
+var metadata = require('../../plugman/util/metadata');
+var Q = require('q');
+var path = require('path');
+var fs = require('fs');
+
+module.exports = remove;
+module.exports.validatePluginId = validatePluginId;
+
+function remove (projectRoot, targets, hooksRunner, opts) {
+    if (!targets || !targets.length) {
+        return Q.reject(new CordovaError('No plugin specified. Please specify a plugin to remove. See: ' + cordova_util.binname + ' plugin list.'));
+    }
+    var config_json = config.read(projectRoot);
+    var pluginPath = path.join(projectRoot, 'plugins');
+    var plugins = cordova_util.findPlugins(pluginPath);
+    var platformList = cordova_util.listPlatforms(projectRoot);
+    var shouldRunPrepare = false;
+
+    opts.cordova = { plugins: cordova_util.findPlugins(pluginPath) };
+    return hooksRunner.fire('before_plugin_rm', opts)
+    .then(function () {
+        return opts.plugins.reduce(function (soFar, target) {
+            var validatedPluginId = module.exports.validatePluginId(target, plugins);
+            if (!validatedPluginId) {
+                return Q.reject(new CordovaError('Plugin "' + target + '" is not present in the project. See `' + cordova_util.binname + ' plugin list`.'));
+            }
+            target = validatedPluginId;
+
+            // Iterate over all installed platforms and uninstall.
+            // If this is a web-only or dependency-only plugin, then
+            // there may be nothing to do here except remove the
+            // reference from the platform's plugin config JSON.
+            return platformList.reduce(function (soFar, platform) {
+                return soFar.then(function () {
+                    var platformRoot = path.join(projectRoot, 'platforms', platform);
+                    events.emit('verbose', 'Calling plugman.uninstall on plugin "' + target + '" for platform "' + platform + '"');
+                    var options = {
+                        force: opts.force || false
+                    };
+                    return plugman.uninstall.uninstallPlatform(platform, platformRoot, target, pluginPath, options)
+                    .then(function (didPrepare) {
+                        // If platform does not returned anything we'll need
+                        // to trigger a prepare after all plugins installed
+                        if (!didPrepare) shouldRunPrepare = true;
+                    });
+                });
+            }, Q())
+            .then(function () {
+                // TODO: Should only uninstallPlugin when no platforms have it.
+                return plugman.uninstall.uninstallPlugin(target, pluginPath, opts);
+            }).then(function () {
+                // remove plugin from config.xml
+                if (plugin_util.saveToConfigXmlOn(config_json, opts)) {
+                    events.emit('log', 'Removing plugin ' + target + ' from config.xml file...');
+                    var configPath = cordova_util.projectConfig(projectRoot);
+                    if (fs.existsSync(configPath)) { // should not happen with real life but needed for tests
+                        var configXml = new ConfigParser(configPath);
+                        configXml.removePlugin(target);
+                        configXml.write();
+                    }
+                    var pkgJson;
+                    var pkgJsonPath = path.join(projectRoot, 'package.json');
+                    // If statement to see if pkgJsonPath exists in the filesystem
+                    if (fs.existsSync(pkgJsonPath)) {
+                        // delete any previous caches of require(package.json)
+                        pkgJson = cordova_util.requireNoCache(pkgJsonPath);
+                    } else {
+                        // TODO: well this comment below seems kinda importnat
+                        // Create package.json in cordova@7
+                    }
+                    // If package.json exists and contains a specified plugin in cordova['plugins'], it will be removed
+                    if (pkgJson !== undefined && pkgJson.cordova !== undefined && pkgJson.cordova.plugins !== undefined) {
+                        events.emit('log', 'Removing ' + target + ' from package.json');
+                        // Remove plugin from package.json
+                        delete pkgJson.cordova.plugins[target];
+                        // Write out new package.json with plugin removed correctly.
+                        fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2), 'utf8');
+                    }
+                }
+            }).then(function () {
+                // Remove plugin from fetch.json
+                events.emit('verbose', 'Removing plugin ' + target + ' from fetch.json');
+                metadata.remove_fetch_metadata(pluginPath, target);
+            });
+        }, Q());
+    }).then(function () {
+        // CB-11022 We do not need to run prepare after plugin install until shouldRunPrepare flag is set to true
+        if (!shouldRunPrepare) {
+            return Q();
+        }
+        return require('./prepare').preparePlatforms(platformList, projectRoot, opts);
+    }).then(function () {
+        opts.cordova = { plugins: cordova_util.findPlugins(pluginPath) };
+        return hooksRunner.fire('after_plugin_rm', opts);
+    });
+}
+
+function validatePluginId (pluginId, installedPlugins) {
+    if (installedPlugins.indexOf(pluginId) >= 0) {
+        return pluginId;
+    }
+
+    if (pluginId.indexOf('cordova-plugin-') < 0) {
+        return validatePluginId('cordova-plugin-' + pluginId, installedPlugins);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/ce93923f/src/cordova/plugin/save.js
----------------------------------------------------------------------
diff --git a/src/cordova/plugin/save.js b/src/cordova/plugin/save.js
new file mode 100644
index 0000000..d24ffb5
--- /dev/null
+++ b/src/cordova/plugin/save.js
@@ -0,0 +1,146 @@
+/**
+    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_util = require('../util');
+var pluginSpec = require('./plugin_spec_parser');
+var ConfigParser = require('cordova-common').ConfigParser;
+var PluginInfoProvider = require('cordova-common').PluginInfoProvider;
+var path = require('path');
+var fs = require('fs');
+var Q = require('q');
+var semver = require('semver');
+
+module.exports = save;
+module.exports.getSpec = getSpec;
+module.exports.getPluginVariables = getPluginVariables;
+module.exports.versionString = versionString;
+
+function save (projectRoot, opts) {
+    var xml = cordova_util.projectConfig(projectRoot);
+    var cfg = new ConfigParser(xml);
+
+    // First, remove all pre-existing plugins from config.xml
+    cfg.getPluginIdList().forEach(function (plugin) {
+        cfg.removePlugin(plugin);
+    });
+
+    // Then, save top-level plugins and their sources
+    var jsonFile = path.join(projectRoot, 'plugins', 'fetch.json');
+    var plugins;
+    try {
+        // It might be the case that fetch.json file is not yet existent.
+        // for example: when we have never ran the command 'cordova plugin add foo' on the project
+        // in that case, there's nothing to do except bubble up the error
+        plugins = JSON.parse(fs.readFileSync(jsonFile, 'utf-8'));
+    } catch (err) {
+        return Q.reject(err.message);
+    }
+
+    Object.keys(plugins).forEach(function (pluginName) {
+        var plugin = plugins[pluginName];
+        var pluginSource = plugin.source;
+
+        // If not a top-level plugin, skip it, don't save it to config.xml
+        if (!plugin.is_top_level) {
+            return;
+        }
+
+        var attribs = {name: pluginName};
+        var spec = module.exports.getSpec(pluginSource, projectRoot, pluginName);
+        if (spec) {
+            attribs.spec = spec;
+        }
+
+        var variables = module.exports.getPluginVariables(plugin.variables);
+        cfg.addPlugin(attribs, variables);
+    });
+    cfg.write();
+
+    return Q.resolve();
+}
+
+function getSpec (pluginSource, projectRoot, pluginName) {
+    if (pluginSource.hasOwnProperty('url') || pluginSource.hasOwnProperty('path')) {
+        return pluginSource.url || pluginSource.path;
+    }
+
+    var version = null;
+    var scopedPackage = null;
+    if (pluginSource.hasOwnProperty('id')) {
+        // Note that currently version is only saved here if it was explicitly specified when the plugin was added.
+        var parsedSpec = pluginSpec.parse(pluginSource.id);
+        version = parsedSpec.version;
+        if (version) {
+            version = module.exports.versionString(version);
+        }
+
+        if (parsedSpec.scope) {
+            scopedPackage = parsedSpec.package;
+        }
+    }
+
+    if (!version) {
+        // Fallback on getting version from the plugin folder, if it's there
+        var pluginInfoProvider = new PluginInfoProvider();
+        var dir = path.join(projectRoot, 'plugins', pluginName);
+
+        try {
+            // pluginInfoProvider.get() will throw if directory does not exist.
+            var pluginInfo = pluginInfoProvider.get(dir);
+            if (pluginInfo) {
+                version = module.exports.versionString(pluginInfo.version);
+            }
+        } catch (err) {
+        }
+    }
+
+    if (scopedPackage) {
+        version = scopedPackage + '@' + version;
+    }
+
+    return version;
+}
+
+function getPluginVariables (variables) {
+    var result = [];
+    if (!variables) {
+        return result;
+    }
+
+    Object.keys(variables).forEach(function (pluginVar) {
+        result.push({name: pluginVar, value: variables[pluginVar]});
+    });
+
+    return result;
+}
+
+function versionString (version) {
+    var validVersion = semver.valid(version, true);
+    if (validVersion) {
+        return '~' + validVersion;
+    }
+
+    if (semver.validRange(version, true)) {
+        // Return what we were passed rather than the result of the validRange() call, as that call makes modifications
+        // we don't want, like converting '^1.2.3' to '>=1.2.3-0 <2.0.0-0'
+        return version;
+    }
+
+    return null;
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/ce93923f/src/cordova/plugin/search.js
----------------------------------------------------------------------
diff --git a/src/cordova/plugin/search.js b/src/cordova/plugin/search.js
new file mode 100644
index 0000000..e439c3b
--- /dev/null
+++ b/src/cordova/plugin/search.js
@@ -0,0 +1,41 @@
+/**
+    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 opener = require('opener');
+var Q = require('Q');
+
+module.exports = search;
+
+function search (hooksRunner, opts) {
+    return hooksRunner.fire('before_plugin_search', opts)
+    .then(function () {
+        var link = 'http://cordova.apache.org/plugins/';
+        if (opts.plugins.length > 0) {
+            var keywords = (opts.plugins).join(' ');
+            var query = link + '?q=' + encodeURI(keywords);
+            opener(query);
+        } else {
+            opener(link);
+        }
+
+        return Q.resolve();
+    }).then(function () {
+        return hooksRunner.fire('after_plugin_search', opts);
+    });
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/ce93923f/src/cordova/plugin/util.js
----------------------------------------------------------------------
diff --git a/src/cordova/plugin/util.js b/src/cordova/plugin/util.js
new file mode 100644
index 0000000..0da0fb7
--- /dev/null
+++ b/src/cordova/plugin/util.js
@@ -0,0 +1,37 @@
+/**
+    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 path = require('path');
+var PluginInfoProvider = require('cordova-common').PluginInfoProvider;
+
+module.exports.saveToConfigXmlOn = saveToConfigXmlOn;
+module.exports.getInstalledPlugins = getInstalledPlugins;
+
+function getInstalledPlugins (projectRoot) {
+    var pluginsDir = path.join(projectRoot, 'plugins');
+    // TODO: This should list based off of platform.json, not directories within plugins/
+    var pluginInfoProvider = new PluginInfoProvider();
+    return pluginInfoProvider.getAllWithinSearchPath(pluginsDir);
+}
+
+function saveToConfigXmlOn (config_json, options) {
+    options = options || {};
+    var autosave = config_json.auto_save_plugins || false;
+    return autosave || options.save;
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/ce93923f/src/cordova/plugin_spec_parser.js
----------------------------------------------------------------------
diff --git a/src/cordova/plugin_spec_parser.js b/src/cordova/plugin_spec_parser.js
deleted file mode 100644
index a7b38ed..0000000
--- a/src/cordova/plugin_spec_parser.js
+++ /dev/null
@@ -1,61 +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.
-*/
-
-// npm packages follow the pattern of (@scope/)?package(@spec)? where scope and tag are optional
-var NPM_SPEC_REGEX = /^(@[^\/]+\/)?([^@\/]+)(?:@(.+))?$/;
-
-module.exports.parse = parse;
-
-/**
- * Represents a parsed specification for a plugin
- * @class
- * @param {String} raw      The raw specification (i.e. provided by the user)
- * @param {String} scope    The scope of the package if this is an npm package
- * @param {String} id       The id of the package if this is an npm package
- * @param {String} version  The version specified for the package if this is an npm package
- */
-function PluginSpec(raw, scope, id, version) {
-    /** @member {String|null} The npm scope of the plugin spec or null if it does not have one */
-    this.scope = scope || null;
-
-    /** @member {String|null} The id of the plugin or the raw plugin spec if it is not an npm package */
-    this.id = id || raw;
-
-    /** @member {String|null} The specified version of the plugin or null if no version was specified */
-    this.version = version || null;
-
-    /** @member {String|null} The npm package of the plugin (with scope) or null if this is not a spec for an npm package */
-    this.package = (scope ? scope + id : id) || null;
-}
-
-/**
- * Tries to parse the given string as an npm-style package specification of
- * the form (@scope/)?package(@version)? and return the various parts.
- *
- * @param {String} raw  The string to be parsed
- * @return {PluginSpec}  The parsed plugin spec
- */
-function parse(raw) {
-    var split = NPM_SPEC_REGEX.exec(raw);
-    if (split) {
-        return new PluginSpec(raw, split[1], split[2], split[3]);
-    }
-
-    return new PluginSpec(raw);
-}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[13/14] cordova-lib git commit: plugin util specs implemented

Posted by fi...@apache.org.
plugin util specs implemented


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

Branch: refs/heads/master
Commit: ea8af4553d203a2a07731837a887f638a04f4f7d
Parents: 2daef35
Author: filmaj <ma...@gmail.com>
Authored: Wed Jun 21 09:39:51 2017 -0500
Committer: filmaj <ma...@gmail.com>
Committed: Wed Jun 21 14:58:42 2017 -0500

----------------------------------------------------------------------
 spec-cordova/plugin/util.spec.js | 30 ++++++++++++++++++++++++++----
 1 file changed, 26 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/ea8af455/spec-cordova/plugin/util.spec.js
----------------------------------------------------------------------
diff --git a/spec-cordova/plugin/util.spec.js b/spec-cordova/plugin/util.spec.js
index f967706..b94412c 100644
--- a/spec-cordova/plugin/util.spec.js
+++ b/spec-cordova/plugin/util.spec.js
@@ -18,14 +18,36 @@
 */
 /* eslint-env jasmine */
 
-var plugin_util = require('../../src/cordova/plugin/util');
+var rewire = require('rewire');
+var plugin_util = rewire('../../src/cordova/plugin/util');
 
 describe('cordova/plugin/util', function () {
+    var plugin_info_mock = function () {};
+    var plugin_info_revert_mock;
+    beforeEach(function () {
+        plugin_info_mock.prototype = jasmine.createSpyObj('plugin info provider prototype mock', ['getAllWithinSearchPath']);
+        plugin_info_revert_mock = plugin_util.__set__('PluginInfoProvider', plugin_info_mock);
+    });
+    afterEach(function () {
+        plugin_info_revert_mock();
+    });
     describe('getInstalledPlugins helper method', function () {
-        it('should return result of PluginInfoProvider\'s getAllWithinSearchPath method');
+        it('should return result of PluginInfoProvider\'s getAllWithinSearchPath method', function () {
+            var plugins_list = ['VRPlugin', 'MastodonSocialPlugin'];
+            plugin_info_mock.prototype.getAllWithinSearchPath.and.returnValue(plugins_list);
+            expect(plugin_util.getInstalledPlugins('/some/path/to/a/project')).toEqual(plugins_list);
+        });
     });
     describe('saveToConfigXmlOn helper method', function () {
-        it('should return true if config.json\'s autosave option is truthy');
-        it('should return true if options passed in have a truthy save property');
+        it('should return true if config.json\'s autosave option is truthy', function () {
+            expect(plugin_util.saveToConfigXmlOn({
+                auto_save_plugins: true
+            })).toBe(true);
+        });
+        it('should return true if options passed in have a truthy save property', function () {
+            expect(plugin_util.saveToConfigXmlOn({}, {
+                save: true
+            })).toBe(true);
+        });
     });
 });


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org