You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by st...@apache.org on 2017/02/04 01:04:50 UTC

[03/12] cordova-lib git commit: CB-11960: Refactored add/remove/restore code and added tests.

CB-11960: Refactored add/remove/restore code and added tests.


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

Branch: refs/heads/master
Commit: b247e12df2458a75893f605b49544ce032dee54b
Parents: 4cd1be8
Author: Steve Gill <st...@gmail.com>
Authored: Mon Nov 7 16:45:00 2016 -0800
Committer: Audrey So <au...@apache.org>
Committed: Thu Jan 26 17:36:51 2017 -0800

----------------------------------------------------------------------
 .../spec-cordova/pkgJson-restore.spec.js        | 590 +++++++++++++------
 cordova-lib/spec-cordova/pkgJson.spec.js        |  77 ++-
 cordova-lib/src/cordova/restore-util.js         | 350 +++++------
 3 files changed, 575 insertions(+), 442 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/b247e12d/cordova-lib/spec-cordova/pkgJson-restore.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/pkgJson-restore.spec.js b/cordova-lib/spec-cordova/pkgJson-restore.spec.js
index 61a8945..a0646d6 100644
--- a/cordova-lib/spec-cordova/pkgJson-restore.spec.js
+++ b/cordova-lib/spec-cordova/pkgJson-restore.spec.js
@@ -18,30 +18,17 @@
 */
 var helpers = require('./helpers'),
     path = require('path'),
-    fs = require('fs'),
     shell = require('shelljs'),
     events = require('cordova-common').events,
-    cordova = require('../src/cordova/cordova'),
-    rewire = require('rewire'),
-    prepare = require('../src/cordova/prepare'),
-    platforms = require('../src/platforms/platforms'),
-    cordova_util = require('util'),
     ConfigParser = require('cordova-common').ConfigParser,
-    platform = rewire('../src/cordova/platform.js');
+    cordova = require('../src/cordova/cordova');
 
-var projectRoot = 'C:\\Projects\\cordova-projects\\move-tracker';
-var pluginsDir = path.join(__dirname, 'fixtures', 'plugins');
-
-/** Testing will check if "cordova prepare" is restoring platforms as expected.
-*   Uses different basePkgJson files depending on testing expecations of what should
-*   initially be in pkg.json and/or config.xml.
+/** Testing will check if "cordova prepare" is restoring platforms and plugins as expected.
+*   Uses different basePkgJson files depending on testing expecations of what (platforms/plugins/variables)
+*   should initially be in pkg.json and/or config.xml.
 */
 
-var req = function(someModule) {
-    delete require.cache[require.resolve(someModule)];
-    return require(someModule);
-}
-
+// Use basePkgJson
 describe('platform end-to-end with --save', function () {
     var tmpDir = helpers.tmpDir('platform_test_pkgjson');
     var project = path.join(tmpDir, 'project');
@@ -58,7 +45,6 @@ describe('platform end-to-end with --save', function () {
     });
 
     afterEach(function() {
-        var cwd = process.cwd();
         delete require.cache[require.resolve(path.join(process.cwd(),'package.json'))];
         process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on Windows.
         shell.rm('-rf', tmpDir);
@@ -81,7 +67,7 @@ describe('platform end-to-end with --save', function () {
         });
     }
     /** Test#001 will add a platform to package.json with the 'save' flag.
-    *   It will remove it from pkg.json without the save flag.
+    *   It will remove it from platforms.json without the save flag.
     *   After running cordova prepare, that platform should be restored in the
     *   installed platform list in platforms.json.
     */
@@ -89,7 +75,7 @@ describe('platform end-to-end with --save', function () {
         var cwd = process.cwd();
         var pkgJsonPath = path.join(cwd,'package.json');
         delete require.cache[require.resolve(pkgJsonPath)];
-        var pkgJson = require(pkgJsonPath);
+        var pkgJson;
         var platformsFolderPath = path.join(cwd,'platforms/platforms.json');
         var platformsJson;
 
@@ -97,6 +83,8 @@ describe('platform end-to-end with --save', function () {
             // Add the testing platform with --save.
             return cordova.raw.platform('add', [helpers.testPlatform], {'save':true});
         }).then(function() {
+            delete require.cache[require.resolve(pkgJsonPath)];
+            pkgJson = require(pkgJsonPath);
             // Require platformsFolderPath
             delete require.cache[require.resolve(platformsFolderPath)];
             platformsJson = require(platformsFolderPath);
@@ -147,8 +135,7 @@ describe('platform end-to-end with --save', function () {
         var platformsFolderPath = path.join(cwd,'platforms/platforms.json');
         var platformsJson;
         var secondPlatformAdded = 'ios';
-        
-
+    
         emptyPlatformList().then(function() {
             // Add the testing platform with --save.
             return cordova.raw.platform('add', [helpers.testPlatform], {'save':true});
@@ -161,8 +148,7 @@ describe('platform end-to-end with --save', function () {
             pkgJson = require(pkgJsonPath);
             delete require.cache[require.resolve(platformsFolderPath)];
             platformsJson = require(platformsFolderPath);
-            
-            // Check the platform add of both platforms was successful.
+            // Check the platform add of both platforms (to pkg.Json) was successful.
             expect(pkgJson.cordova.platforms).toBeDefined();
             expect(pkgJson.cordova.platforms.indexOf(helpers.testPlatform)).toEqual(0);
             expect(pkgJson.cordova.platforms.indexOf(secondPlatformAdded)).toEqual(1);
@@ -183,10 +169,10 @@ describe('platform end-to-end with --save', function () {
             platformsJson = require(platformsFolderPath);
             delete require.cache[require.resolve(pkgJsonPath)];
             pkgJson = require(pkgJsonPath);
-            // Check that the platform removed without --save is still in platforms key.
+            // Check that ONLY the platform removed without --save is still in (pkg.json) platforms key.
             expect(pkgJson.cordova.platforms.indexOf(secondPlatformAdded)).toEqual(0);
             expect(pkgJson.cordova.platforms.indexOf(helpers.testPlatform)).toEqual(-1);
-            // Check that the helpers.testPlatform (removed with --save) was removed from the platforms.json
+            // Check that both platforms were removed from the platforms.json
             expect(platformsJson[helpers.testPlatform]).toBeUndefined();
             expect(platformsJson[secondPlatformAdded]).toBeUndefined();
         }).then(function() {
@@ -231,7 +217,7 @@ describe('platform end-to-end with --save', function () {
             pkgJson = require(pkgJsonPath);
             delete require.cache[require.resolve(platformsFolderPath)];
             platformsJson = require(platformsFolderPath);
-            // Check the platform add of both platforms was successful in package.json.
+            // Check the platform add of only helpers.testPlatform was successful in package.json.
             expect(pkgJson.cordova.platforms).toBeDefined();
             expect(pkgJson.cordova.platforms.indexOf(helpers.testPlatform)).toBeGreaterThan(-1);
             expect(pkgJson.cordova.platforms.indexOf(secondPlatformAdded)).toEqual(-1);
@@ -263,7 +249,7 @@ describe('platform end-to-end with --save', function () {
             // Delete any previous caches of platformsJson
             delete require.cache[require.resolve(platformsFolderPath)];
             platformsJson = require(platformsFolderPath);
-            // Expect "helpers.testPlatform" to be in the installed platforms list
+            // Expect "helpers.testPlatform" to be in the installed platforms list.
             expect(platformsJson[helpers.testPlatform]).toBeDefined();
             // Expect that 'ios' will not be in platforms.json and has not been restored.
             expect(platformsJson[secondPlatformAdded]).toBeUndefined();
@@ -272,7 +258,6 @@ describe('platform end-to-end with --save', function () {
         }).fin(done);
     // Cordova prepare needs extra wait time to complete.
     },30000);
-
 });
 
 // Use basePkgJson6 because pkg.json and config.xml contain only android
@@ -293,7 +278,6 @@ describe('files should not be modified if their platforms are identical', functi
     });
 
     afterEach(function() {
-        var cwd = process.cwd();
         delete require.cache[require.resolve(path.join(process.cwd(),'package.json'))];
         process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on Windows.
         shell.rm('-rf', tmpDir);
@@ -307,15 +291,6 @@ describe('files should not be modified if their platforms are identical', functi
             expect(installed[1].indexOf(helpers.testPlatform)).toBe(-1);
         });
     }
-
-    function fullPlatformList() {
-        return cordova.raw.platform('list').then(function() {
-            var installed = results.match(/Installed platforms:\n  (.*)/);
-            expect(installed).toBeDefined();
-            expect(installed[1].indexOf(helpers.testPlatform)).toBeGreaterThan(-1);
-        });
-    }
-    
     /** Test#004 will check the platform list in package.json and config.xml. 
     *   When both files contain the same platforms and cordova prepare is run, 
     *   neither file is modified.
@@ -327,7 +302,6 @@ describe('files should not be modified if their platforms are identical', functi
         var engines = cfg1.getEngines();
         var pkgJsonPath = path.join(cwd,'package.json');
         var pkgJson;
-        var platformsFolderPath;
         var engNames = engines.map(function(elem) {
             return elem.name;
         });
@@ -375,7 +349,6 @@ describe('update pkg.json to include platforms in config.xml', function () {
     });
 
     afterEach(function() {
-        var cwd = process.cwd();
         delete require.cache[require.resolve(path.join(process.cwd(),'package.json'))];
         process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on Windows.
         shell.rm('-rf', tmpDir);
@@ -389,14 +362,6 @@ describe('update pkg.json to include platforms in config.xml', function () {
             expect(installed[1].indexOf(helpers.testPlatform)).toBe(-1);
         });
     }
-
-    function fullPlatformList() {
-        return cordova.raw.platform('list').then(function() {
-            var installed = results.match(/Installed platforms:\n  (.*)/);
-            expect(installed).toBeDefined();
-            expect(installed[1].indexOf(helpers.testPlatform)).toBeGreaterThan(-1);
-        });
-    }
     /** Test#005 will check the platform list in package.json and config.xml. 
     *   When config.xml has 'android and ios' and pkg.json only contains 'android', run cordova
     *   and pkg.json is updated to include 'ios'.
@@ -409,15 +374,14 @@ describe('update pkg.json to include platforms in config.xml', function () {
         var pkgJsonPath = path.join(cwd,'package.json');
         delete require.cache[require.resolve(pkgJsonPath)];
         var pkgJson = require(pkgJsonPath);
-        var platformsFolderPath;
         var engNames = engines.map(function(elem) {
             return elem.name;
         });
         var configEngArray = engNames.slice();
        
         // Config.xml contains(android & ios) and pkg.json contains android (basePkgJson5)
-        expect(configEngArray.indexOf('ios')).toBeGreaterThan(-1);
         expect(configEngArray.indexOf('android')).toBeGreaterThan(-1);
+        expect(configEngArray.indexOf('ios')).toBeGreaterThan(-1);
         // pkg.json should not contain 'ios' platform before cordova prepare
         expect(pkgJson.cordova.platforms.indexOf('ios')).toEqual(-1);
         expect(pkgJson.cordova.platforms.indexOf('android')).toBeGreaterThan(-1);
@@ -431,8 +395,11 @@ describe('update pkg.json to include platforms in config.xml', function () {
             // Expect 'android' to still be there in pkg.json
             expect(pkgJson.cordova.platforms.indexOf('android')).toBeGreaterThan(-1);
             // Expect both pkg.json and config.xml to each have both platforms in their arrays
-            expect(configEngArray.length === 1);
-            expect(pkgJson.cordova.platforms.length === 1);
+            expect(configEngArray.length === 2);
+            expect(pkgJson.cordova.platforms.length === 2);
+            // No changes to config.xml
+            expect(configEngArray.indexOf('android')).toBeGreaterThan(-1);
+            expect(configEngArray.indexOf('ios')).toBeGreaterThan(-1);
         }).fail(function(err) {
             expect(err).toBeUndefined();
         }).fin(done);
@@ -457,7 +424,6 @@ describe('update empty package.json to match config.xml', function () {
     });
 
     afterEach(function() {
-        var cwd = process.cwd();
         delete require.cache[require.resolve(path.join(process.cwd(),'package.json'))];
         process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on Windows.
         shell.rm('-rf', tmpDir);
@@ -471,15 +437,6 @@ describe('update empty package.json to match config.xml', function () {
             expect(installed[1].indexOf(helpers.testPlatform)).toBe(-1);
         });
     }
-
-    function fullPlatformList() {
-        return cordova.raw.platform('list').then(function() {
-            var installed = results.match(/Installed platforms:\n  (.*)/);
-            expect(installed).toBeDefined();
-            expect(installed[1].indexOf(helpers.testPlatform)).toBeGreaterThan(-1);
-        });
-    }
-
     /** Test#006 will check if pkg.json has a cordova key and platforms installed already.
      *   If it does not and config.xml has a platform(s) installed already, run cordova prepare
      *   and it will add a cordova key and the platform(s) from config.xml to package.json.
@@ -492,7 +449,6 @@ describe('update empty package.json to match config.xml', function () {
         var cfg1 = new ConfigParser(configXmlPath);
         var engines = cfg1.getEngines();
         var pkgJson = require(pkgJsonPath);
-        var platformsFolderPath;
         var engNames = engines.map(function(elem) {
             return elem.name;
         });
@@ -521,8 +477,8 @@ describe('update empty package.json to match config.xml', function () {
             // Expect cordova key and 'android' platform to be added to pkg.json
             expect(pkgJson.cordova.platforms.indexOf('android')).toBeGreaterThan(-1);
             // Expect both pkg.json and config.xml to each have (only) android in their arrays
-            expect(configEngArray.length === 0);
-            expect(pkgJson.cordova.platforms.length === 0);
+            expect(configEngArray.length === 1);
+            expect(pkgJson.cordova.platforms.length === 1);
         }).fail(function(err) {
             expect(err).toBeUndefined();
         }).fin(done);
@@ -547,7 +503,6 @@ describe('update config.xml to include platforms in pkg.json', function () {
     });
 
     afterEach(function() {
-        var cwd = process.cwd();
         delete require.cache[require.resolve(path.join(process.cwd(),'package.json'))];
         process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on Windows.
         shell.rm('-rf', tmpDir);
@@ -561,14 +516,6 @@ describe('update config.xml to include platforms in pkg.json', function () {
             expect(installed[1].indexOf(helpers.testPlatform)).toBe(-1);
         });
     }
-
-    function fullPlatformList() {
-        return cordova.raw.platform('list').then(function() {
-            var installed = results.match(/Installed platforms:\n  (.*)/);
-            expect(installed).toBeDefined();
-            expect(installed[1].indexOf(helpers.testPlatform)).toBeGreaterThan(-1);
-        });
-    }
     /** Test#007 will check the platform list in package.json and config.xml. 
     *   When packge.json has 'android and ios' and config.xml only contains 'android', run cordova
     *   and config.xml is updated to include 'ios'.
@@ -581,7 +528,6 @@ describe('update config.xml to include platforms in pkg.json', function () {
         var pkgJsonPath = path.join(cwd,'package.json');
         delete require.cache[require.resolve(pkgJsonPath)];
         var pkgJson = require(pkgJsonPath);
-        var platformsFolderPath;
         var engNames = engines.map(function(elem) {
             return elem.name;
         });
@@ -596,6 +542,8 @@ describe('update config.xml to include platforms in pkg.json', function () {
             // Run cordova prepare
             return cordova.raw.prepare();
         }).then(function() {
+            delete require.cache[require.resolve(pkgJsonPath)];
+            pkgJson = require(pkgJsonPath);
             var cfg2 = new ConfigParser(configXmlPath);
             engines = cfg2.getEngines();
             engNames = engines.map(function(elem) {
@@ -606,6 +554,8 @@ describe('update config.xml to include platforms in pkg.json', function () {
             expect(configEngArray.indexOf('ios')).toBeGreaterThan(-1);
             // Expect 'android' to still be in config.xml
             expect(configEngArray.indexOf('android')).toBeGreaterThan(-1);
+            // Expect config.xml array to have 2 elements (platforms);
+            expect(configEngArray.length === 2);
         }).fail(function(err) {
             expect(err).toBeUndefined();
         }).fin(done);
@@ -613,10 +563,11 @@ describe('update config.xml to include platforms in pkg.json', function () {
     },30000);
 });
 
-// PLUGINS START HERE
+// Plugin testing begins here.
 
-// Use basePkgJson2 as pkg.json contains 2 plugins and config.xml contains 1 plugin
-describe('update config.xml to include plugins found in pkg.json', function () {
+// Use basePkgJson8 as pkg.json contains 1 plugin and 1 variable and config contains 1 plugin 1 var
+// Same variable, different values... pkg.json should win
+describe('update config.xml to use the variable found in pkg.json', function () {
     var tmpDir = helpers.tmpDir('platform_test_pkgjson');
     var project = path.join(tmpDir, 'project');
     var results;
@@ -625,14 +576,13 @@ describe('update config.xml to include plugins found in pkg.json', function () {
         shell.rm('-rf', tmpDir);
         // Copy then move because we need to copy everything, but that means it will copy the whole directory.
         // Using /* doesn't work because of hidden files.
-        shell.cp('-R', path.join(__dirname, 'fixtures', 'basePkgJson2'), tmpDir);
-        shell.mv(path.join(tmpDir, 'basePkgJson2'), project);
+        shell.cp('-R', path.join(__dirname, 'fixtures', 'basePkgJson8'), tmpDir);
+        shell.mv(path.join(tmpDir, 'basePkgJson8'), project);
         process.chdir(project);
         events.on('results', function(res) { results = res; });
     });
 
     afterEach(function() {
-        var cwd = process.cwd();
         delete require.cache[require.resolve(path.join(process.cwd(),'package.json'))];
         process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on Windows.
         shell.rm('-rf', tmpDir);
@@ -646,42 +596,112 @@ describe('update config.xml to include plugins found in pkg.json', function () {
             expect(installed[1].indexOf(helpers.testPlatform)).toBe(-1);
         });
     }
+    /** Test#011 will check the plugin/variable list in package.json and config.xml. 
+    *   When pkg.json and config.xml have the same variables, but different values,
+    *   pkg.json should win and that value will be used and replaces config's value.
+    */
+    it('Test#011 : if pkg.Json has 1 plugin and 1 variable, update config.xml to include these variables', function(done) {
+        var cwd = process.cwd();
+        var configXmlPath = path.join(cwd, 'config.xml');
+        var pkgJsonPath = path.join(cwd,'package.json');
+        delete require.cache[require.resolve(pkgJsonPath)];
+        var cfg1 = new ConfigParser(configXmlPath);
+        var configPlugins = cfg1.getPluginIdList();
+        var configPlugin = cfg1.getPlugin(configPlugins);
+        var configPluginVariables = configPlugin.variables;
+        var pkgJson = require(pkgJsonPath);
 
-    function fullPlatformList() {
+        // Expect that pkg.json exists with 1 plugin, 1 variable, and a different value (json)
+        expect(pkgJson.cordova.plugins).toBeDefined();
+        expect(Object.keys(pkgJson.cordova.plugins).length === 1);
+        expect(pkgJson.cordova.plugins['cordova-plugin-camera']).toBeDefined();
+        expect(pkgJson.cordova.plugins['cordova-plugin-camera']).toEqual({ variable_1: 'json' });
+        // Expect that config.xml exists with 1 plugin, 1 variable, but a different value (config)
+        expect(configPlugin.name).toEqual('cordova-plugin-camera');
+        expect(configPluginVariables).toEqual({ variable_1: 'config' });
+        expect(Object.keys(configPlugin).length === 1);
+
+        emptyPlatformList().then(function() {
+            // Run cordova prepare
+            return cordova.raw.prepare();
+        }).then(function() {
+            // // Delete any previous caches of require(package.json)
+            delete require.cache[require.resolve(pkgJsonPath)];
+            pkgJson = require(pkgJsonPath);
+            var cfg2 = new ConfigParser(configXmlPath);
+            configPlugins = cfg2.getPluginIdList();
+            configPlugin = cfg2.getPlugin(configPlugins);
+            configPluginVariables = configPlugin.variables;
+            // Expect that pkg.json exists with 1 plugin, 1 variable, and the pkg.json value
+            expect(pkgJson.cordova.plugins['cordova-plugin-camera']).toBeDefined();
+            expect(Object.keys(pkgJson.cordova.plugins).length === 1);
+            expect(pkgJson.cordova.plugins['cordova-plugin-camera']).toEqual({ variable_1: 'json' });
+            // Expect that config.xml exists with 1 plugin, 1 variable and pkg.json's value
+            expect(configPlugin.name).toEqual('cordova-plugin-camera');
+            expect(configPluginVariables).toEqual({ variable_1: 'json' });
+            expect(Object.keys(configPlugin).length === 1);
+        }).fail(function(err) {
+            expect(err).toBeUndefined();
+        }).fin(done);
+    // Cordova prepare needs extra wait time to complete.
+    },30000);
+});
+
+// Use basePkgJson9 as config contains 1 plugin and 1 variable and pkg.json contains 1 plugin 0 var
+describe('update pkg.json to include plugin and variable found in config.xml', function () {
+    var tmpDir = helpers.tmpDir('platform_test_pkgjson');
+    var project = path.join(tmpDir, 'project');
+    var results;
+
+    beforeEach(function() {
+        shell.rm('-rf', tmpDir);
+        // Copy then move because we need to copy everything, but that means it will copy the whole directory.
+        // Using /* doesn't work because of hidden files.
+        shell.cp('-R', path.join(__dirname, 'fixtures', 'basePkgJson9'), tmpDir);
+        shell.mv(path.join(tmpDir, 'basePkgJson9'), project);
+        process.chdir(project);
+        events.on('results', function(res) { results = res; });
+    });
+
+    afterEach(function() {
+        delete require.cache[require.resolve(path.join(process.cwd(),'package.json'))];
+        process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on Windows.
+        shell.rm('-rf', tmpDir);
+    });
+
+    // Factoring out some repeated checks.
+    function emptyPlatformList() {
         return cordova.raw.platform('list').then(function() {
             var installed = results.match(/Installed platforms:\n  (.*)/);
             expect(installed).toBeDefined();
-            expect(installed[1].indexOf(helpers.testPlatform)).toBeGreaterThan(-1);
+            expect(installed[1].indexOf(helpers.testPlatform)).toBe(-1);
         });
     }
-
-    /** Test#008 will check the platform list in package.json and config.xml. 
-    *   When packge.json has 'splashscreen and camera' and config.xml only contains 'splashscreen', run cordova prepare
-    *   and config.xml is updated to include 'camera'.
+    /** Test#012 will check the plugin/variable list in package.json and config.xml. 
+    *   When config.xml has a 'camera plugin and 1 variable' and pkg.json has 1 plugins/0 variables,
+    *   cordova prepare runs and will update pkg.json to match config.xml's plugins/variables.
     */
-    it('Test#008 :  if pkgJson has 2 plugins and config.xml has only one of those plugins, update config to include both', function(done) {
+    it('Test#012 : if pkg.Json has 1 plugin and 2 variables, update config.xml to include these plugins/variables', function(done) {
         var cwd = process.cwd();
         var configXmlPath = path.join(cwd, 'config.xml');
         var pkgJsonPath = path.join(cwd,'package.json');
         delete require.cache[require.resolve(pkgJsonPath)];
         var cfg1 = new ConfigParser(configXmlPath);
-        var plugins = cfg1.getPluginIdList();
+        var configPlugins = cfg1.getPluginIdList();
+        var configPlugin = cfg1.getPlugin(configPlugins);
+        var configPluginVariables = configPlugin.variables;
         var pkgJson = require(pkgJsonPath);
-        var pluginNames = plugins.map(function(elem) {
-            return elem;
-        });
-        var configPlugArray = pluginNames.slice();
 
-        // Expect that pkg.json exists with 2 plugins
+        // Expect that pkg.json exists with 1 plugin
         expect(pkgJson.cordova.plugins).toBeDefined();
-        expect(pkgJson.cordova.plugins.length === 1);
-        expect(pkgJson.cordova.plugins.indexOf('cordova-plugin-camera')).toEqual(0);
-        expect(pkgJson.cordova.plugins.indexOf('cordova-plugin-splashscreen')).toEqual(1);
-        // Expect that config.xml exists with 1 plugin
-        expect(configPlugArray).toBeDefined();
-        expect(configPlugArray.length === 0);
-        expect(configPlugArray.indexOf('cordova-plugin-splashscreen')).toEqual(-1);
-        expect(configPlugArray.indexOf('cordova-plugin-camera')).toEqual(0);
+        expect(Object.keys(pkgJson.cordova.plugins).length === 1);
+        expect(pkgJson.cordova.plugins['cordova-plugin-camera']).toBeDefined();
+        expect(pkgJson.cordova.plugins).toEqual({ 'cordova-plugin-camera': {} });
+
+        // Expect that config.xml exists with 1 plugin and 1 variable
+        expect(configPlugin.name).toEqual('cordova-plugin-camera');
+        expect(configPluginVariables).toEqual({ variable_1: 'value_1' });
+        expect(Object.keys(configPlugin).length === 1);
 
         emptyPlatformList().then(function() {
             // Run cordova prepare
@@ -691,23 +711,17 @@ describe('update config.xml to include plugins found in pkg.json', function () {
             delete require.cache[require.resolve(pkgJsonPath)];
             pkgJson = require(pkgJsonPath);
             var cfg2 = new ConfigParser(configXmlPath);
-            plugins = cfg2.getPluginIdList();
-            pluginNames = plugins.map(function(elem) {
-                return elem;
-            });
-            configPlugArray = pluginNames.slice();
-            // Expect that pkg.json exists with 2 plugins
-            expect(pkgJson.cordova.plugins).toBeDefined();
-            expect(pkgJson.cordova.plugins.length === 1);
-            // Expect that config.xml exists with 2 plugins
-            expect(configPlugArray).toBeDefined();
-            expect(configPlugArray.length === 1);
-            // Expect config to contain camera and splashscreen
-            expect(configPlugArray.indexOf('cordova-plugin-camera')).toEqual(0);
-            expect(configPlugArray.indexOf('cordova-plugin-splashscreen')).toEqual(1);
-            // Expect pkg.json to contain camera and splashscreen
-            expect(pkgJson.cordova.plugins.indexOf('cordova-plugin-camera')).toEqual(0);
-            expect(pkgJson.cordova.plugins.indexOf('cordova-plugin-splashscreen')).toEqual(1);
+            configPlugins = cfg2.getPluginIdList();
+            configPlugin = cfg2.getPlugin(configPlugins);
+            configPluginVariables = configPlugin.variables;
+            // Expect that pkg.json exists with 1 plugin, 1 variable, and 1 value
+            expect(pkgJson.cordova.plugins['cordova-plugin-camera']).toBeDefined();
+            expect(Object.keys(pkgJson.cordova.plugins).length === 1);
+            expect(pkgJson.cordova.plugins).toEqual({ 'cordova-plugin-camera': { variable_1: 'value_1' } });
+            // Expect that config.xml exists with 1 plugin, 1 variable and 1 value
+            expect(configPlugin.name).toEqual('cordova-plugin-camera');
+            expect(configPluginVariables).toEqual({ variable_1: 'value_1' });
+            expect(Object.keys(configPlugin).length === 1);
         }).fail(function(err) {
             expect(err).toBeUndefined();
         }).fin(done);
@@ -715,9 +729,10 @@ describe('update config.xml to include plugins found in pkg.json', function () {
     },30000);
 });
 
-// Use basePkgJson7 as config.xml contains 2 plugins and pkg.json contains 1 plugin
-describe('update pkg.json to include plugins found in config.xml', function () {
-    var tmpDir = helpers.tmpDir('platform_test_pkgjson');
+// Use basePkgJson10 as pkg.json contains (camera plugin: var 1/var 2, splashscreen plugin) 
+// and config contains (camera plugin: var 3, value 1, device plugin)
+describe('update pkg.json AND config.xml to include all plugins and merge variables', function () {
+    var tmpDir = helpers.tmpDir('plugin_test_pkgjson');
     var project = path.join(tmpDir, 'project');
     var results;
 
@@ -725,14 +740,13 @@ describe('update pkg.json to include plugins found in config.xml', function () {
         shell.rm('-rf', tmpDir);
         // Copy then move because we need to copy everything, but that means it will copy the whole directory.
         // Using /* doesn't work because of hidden files.
-        shell.cp('-R', path.join(__dirname, 'fixtures', 'basePkgJson7'), tmpDir);
-        shell.mv(path.join(tmpDir, 'basePkgJson7'), project);
+        shell.cp('-R', path.join(__dirname, 'fixtures', 'basePkgJson10'), tmpDir);
+        shell.mv(path.join(tmpDir, 'basePkgJson10'), project);
         process.chdir(project);
         events.on('results', function(res) { results = res; });
     });
 
     afterEach(function() {
-        var cwd = process.cwd();
         delete require.cache[require.resolve(path.join(process.cwd(),'package.json'))];
         process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on Windows.
         shell.rm('-rf', tmpDir);
@@ -746,43 +760,168 @@ describe('update pkg.json to include plugins found in config.xml', function () {
             expect(installed[1].indexOf(helpers.testPlatform)).toBe(-1);
         });
     }
+    /** Test#013 will check the plugin/variable list in package.json and config.xml. 
+    *   For plugins that are the same, it will merge their variables together for the final list.
+    *   Plugins that are unique to that file, will be copied over to the file that is missing it.
+    *   Config.xml and pkg.json will have identical plugins and variables after cordova prepare.
+    */
+    it('Test#013 : update pkg.json AND config.xml to include all plugins and merge unique variables', function(done) {
+        var cwd = process.cwd();
+        var configXmlPath = path.join(cwd, 'config.xml');
+        var pkgJsonPath = path.join(cwd,'package.json');
+        delete require.cache[require.resolve(pkgJsonPath)];
+        var cfg1 = new ConfigParser(configXmlPath);
+        var configPlugins = cfg1.getPluginIdList();
+        var pkgJson = require(pkgJsonPath);
+        var configPlugin;
+        var configPluginVariables;
 
-    function fullPlatformList() {
+        // Config.xml has 2 plugins and does not have device yet
+        expect(Object.keys(configPlugins).length === 2);
+        expect(configPlugins.indexOf('cordova-plugin-device')).toEqual(-1);
+        expect(configPlugins.indexOf('cordova-plugin-camera')).toEqual(0);
+        expect(configPlugins.indexOf('cordova-plugin-splashscreen')).toEqual(1);
+        // Config.xml camera plugin has var_3,value_3 and splashscreen has 0 variables
+        for (var i = 0; i < configPlugins.length; i++) {
+            configPlugin = cfg1.getPlugin(configPlugins[i]);
+            configPluginVariables = configPlugin.variables;
+            if(configPlugin.name === 'cordova-plugin-camera') {
+                expect(configPluginVariables).toEqual({ variable_3: 'value_3'});
+            }
+            if(configPlugin.name === 'cordova-plugin-splashscreen') {
+                expect(configPluginVariables).toEqual({});
+            }
+        }
+        // Expect that pkg.json exists with 3 plugins (camera, device, and splashscreen)
+        expect(pkgJson.cordova.plugins).toBeDefined();
+        expect(Object.keys(pkgJson.cordova.plugins).length === 3);
+        expect(pkgJson.cordova.plugins['cordova-plugin-camera']).toBeDefined();
+        expect(pkgJson.cordova.plugins['cordova-plugin-splashscreen']).toBeDefined();
+        expect(pkgJson.cordova.plugins['cordova-plugin-device']).toBeDefined();
+        // Splashscreen has no variables and camera has var 1 and var 2 and device has var1, value1
+        expect(pkgJson.cordova.plugins['cordova-plugin-splashscreen']).toEqual({});
+        expect(pkgJson.cordova.plugins['cordova-plugin-camera']).toEqual({ variable_1: ' ', variable_2: ' ' });
+        expect(pkgJson.cordova.plugins['cordova-plugin-device']).toEqual({ variable_1: 'value_1' });
+
+        emptyPlatformList().then(function() {
+            // Run cordova prepare
+            return cordova.raw.prepare();
+         }).then(function() {
+            // Delete any previous caches of require(package.json)
+            delete require.cache[require.resolve(pkgJsonPath)];
+            pkgJson = require(pkgJsonPath);
+            var cfg2 = new ConfigParser(configXmlPath);
+            configPlugins = cfg2.getPluginIdList();
+
+            // Check to make sure that variables were added as expected
+            for (var i = 0; i < configPlugins.length; i++) {
+                configPlugin = cfg2.getPlugin(configPlugins[i]);
+                configPluginVariables = configPlugin.variables;
+                // Config.xml camera variables have been merged, no duplicates
+                if(configPlugin.name === 'cordova-plugin-camera') {
+                    expect(configPluginVariables).toEqual({ variable_1: ' ', 
+                    variable_2: ' ', variable_3: 'value_3' });
+                }
+                // Expect that device has var1, val1 and splashscreen has 0 var
+                if(configPlugin.name === 'cordova-plugin-device') {
+                    expect(configPluginVariables).toEqual({ variable_1: 'value_1'});
+                }
+                if(configPlugin.name === 'cordova-plugin-splashscreen') {
+                    expect(configPluginVariables).toEqual({});
+                }
+            }
+            // Expect pkg.json to have the variables from config.xml
+            expect(Object.keys(pkgJson.cordova.plugins).length === 3);
+            expect(pkgJson.cordova.plugins['cordova-plugin-camera']).toBeDefined();
+            expect(pkgJson.cordova.plugins['cordova-plugin-splashscreen']).toBeDefined();
+            expect(pkgJson.cordova.plugins['cordova-plugin-device']).toBeDefined();
+            expect(pkgJson.cordova.plugins['cordova-plugin-camera']).toEqual({ variable_1: ' ', variable_2: ' ', variable_3: 'value_3' });
+            // Expect config.xml to have the plugins from pkg.json
+            expect(Object.keys(configPlugins).length === 3);
+            expect(configPlugins.indexOf('cordova-plugin-camera')).toEqual(0);
+            expect(configPlugins.indexOf('cordova-plugin-device')).toEqual(1);
+            expect(configPlugins.indexOf('cordova-plugin-splashscreen')).toEqual(2);
+        }).fail(function(err) {
+            expect(err).toBeUndefined();
+        }).fin(done);
+    // Cordova prepare needs extra wait time to complete.
+    },30000);
+});
+
+// Use basePkgJson11 as pkg.json contains(splashscreen plugin, camera plugin: var1, value1, var2, value2) and
+// config.xml contains (device plugin, camera plugin: var1, value 1, var2, value 2)
+describe('update pkg.json AND config.xml to include all plugins/merge variables and check for duplicates', function () {
+    var tmpDir = helpers.tmpDir('platform_test_pkgjson');
+    var project = path.join(tmpDir, 'project');
+    var results;
+
+    beforeEach(function() {
+        shell.rm('-rf', tmpDir);
+        // Copy then move because we need to copy everything, but that means it will copy the whole directory.
+        // Using /* doesn't work because of hidden files.
+        shell.cp('-R', path.join(__dirname, 'fixtures', 'basePkgJson11'), tmpDir);
+        shell.mv(path.join(tmpDir, 'basePkgJson11'), project);
+        process.chdir(project);
+        events.on('results', function(res) { results = res; });
+    });
+
+    afterEach(function() {
+        delete require.cache[require.resolve(path.join(process.cwd(),'package.json'))];
+        process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on Windows.
+        shell.rm('-rf', tmpDir);
+    });
+
+    // Factoring out some repeated checks.
+    function emptyPlatformList() {
         return cordova.raw.platform('list').then(function() {
             var installed = results.match(/Installed platforms:\n  (.*)/);
             expect(installed).toBeDefined();
-            expect(installed[1].indexOf(helpers.testPlatform)).toBeGreaterThan(-1);
+            expect(installed[1].indexOf(helpers.testPlatform)).toBe(-1);
         });
     }
-
-    /** Test#009 will check the platform list in package.json and config.xml. 
-    *   When config.xml has 'splashscreen and camera' and pkg.json only contains 'splashscreen', run cordova prepare
-    *   and pkg.json is updated to include 'camera'. After both files are identical, run cordova prepare
-    *   to double check that neither file is modified once they are the same.
+    /** Test#014 will check the plugin/variable list in package.json and config.xml. 
+    *   If either file is missing a plugin, it will be added with the correct variables.
+    *   If there is a matching plugin name, the variables will be merged and then added
+    *   to config and pkg.json. 
     */
-    it('Test#009 :  if config.xml has 2 plugins and pkg.json has only one of those plugins, update pkg.json to include both', function(done) {
+    it('Test#014 : update pkg.json AND config.xml to include all plugins and merge variables (no dupes)', function(done) {
         var cwd = process.cwd();
         var configXmlPath = path.join(cwd, 'config.xml');
         var pkgJsonPath = path.join(cwd,'package.json');
         delete require.cache[require.resolve(pkgJsonPath)];
         var cfg1 = new ConfigParser(configXmlPath);
-        var plugins = cfg1.getPluginIdList();
+        var configPlugins = cfg1.getPluginIdList();
         var pkgJson = require(pkgJsonPath);
-        var pluginNames = plugins.map(function(elem) {
-            return elem;
-        });
-        var configPlugArray = pluginNames.slice();
+        var configPlugin;
+        var configPluginVariables;
 
-        // Expect that pkg.json exists with 1 plugin
+        // Config.xml initially has the camera and device plugin and NO splashscreen
+        expect(Object.keys(configPlugins).length === 2);
+        expect(configPlugins.indexOf('cordova-plugin-splashscreen')).toEqual(-1);
+        expect(configPlugins.indexOf('cordova-plugin-camera')).toEqual(0);
+        expect(configPlugins.indexOf('cordova-plugin-device')).toEqual(1);
+        // Config.xml camera initially has var1,val1 and var2, val2 and device has no variables
+        for (var i = 0; i < configPlugins.length; i++) {
+            configPlugin = cfg1.getPlugin(configPlugins[i]);
+            configPluginVariables = configPlugin.variables;
+            if(configPlugin.name === 'cordova-plugin-camera') {
+                expect(configPluginVariables).toEqual({ variable_1: 'value_1', variable_2: 'value_2' });
+            }
+            if(configPlugin.name === 'cordova-plugin-device') {
+                expect(configPluginVariables).toEqual({});
+            }
+        }
+        // Expect that pkg.json exists with 2 plugins
         expect(pkgJson.cordova.plugins).toBeDefined();
-        expect(pkgJson.cordova.plugins.length === 0);
-        expect(pkgJson.cordova.plugins.indexOf('cordova-plugin-camera')).toEqual(-1);
-        expect(pkgJson.cordova.plugins.indexOf('cordova-plugin-splashscreen')).toEqual(0);
-        // Expect that config.xml exists with 2 plugins
-        expect(configPlugArray).toBeDefined();
-        expect(configPlugArray.length === 1);
-        expect(configPlugArray.indexOf('cordova-plugin-camera')).toEqual(0);
-        expect(configPlugArray.indexOf('cordova-plugin-splashscreen')).toEqual(1);
+        expect(Object.keys(pkgJson.cordova.plugins).length === 2);
+        expect(pkgJson.cordova.plugins['cordova-plugin-camera']).toBeDefined();
+        expect(pkgJson.cordova.plugins['cordova-plugin-splashscreen']).toBeDefined();
+        // Pkg.json does not have device yet
+        expect(pkgJson.cordova.plugins['cordova-plugin-device']).toBeUndefined();
+        // Pkg.json camera plugin has var1, value 1 and var3, value 3
+        expect(pkgJson.cordova.plugins['cordova-plugin-camera']).toEqual({ variable_1: 'value_1', variable_3: 'value_3' });
+        // Pkg.json splashscreen has no variables
+        expect(pkgJson.cordova.plugins['cordova-plugin-splashscreen']).toEqual({});
 
         emptyPlatformList().then(function() {
             // Run cordova prepare
@@ -792,49 +931,128 @@ describe('update pkg.json to include plugins found in config.xml', function () {
             delete require.cache[require.resolve(pkgJsonPath)];
             pkgJson = require(pkgJsonPath);
             var cfg2 = new ConfigParser(configXmlPath);
-            plugins = cfg2.getPluginIdList();
-            pluginNames = plugins.map(function(elem) {
-                return elem;
-            });
-            configPlugArray = pluginNames.slice();
-            // Expect that pkg.json exists with 2 plugins
-            expect(pkgJson.cordova.plugins).toBeDefined();
-            expect(pkgJson.cordova.plugins.length === 1);
-            // Expect that config.xml exists with 2 plugins
-            expect(configPlugArray).toBeDefined();
-            expect(configPlugArray.length === 1);
-            // Expect config to contain camera and splashscreen
-            expect(configPlugArray.indexOf('cordova-plugin-camera')).toEqual(0);
-            expect(configPlugArray.indexOf('cordova-plugin-splashscreen')).toEqual(1);
-            // Expect pkg.json to contain camera and splashscreen
-            expect(pkgJson.cordova.plugins.indexOf('cordova-plugin-splashscreen')).toEqual(0);
-            expect(pkgJson.cordova.plugins.indexOf('cordova-plugin-camera')).toEqual(1);
-        }).then(function() {
-            // Now that files are the same, run cordova prepare again to check that neither 
-            // file gets modified again since both are identical now
+            configPlugins = cfg2.getPluginIdList();
+
+            // Check to make sure that variables were added as expected
+            for (var i = 0; i < configPlugins.length; i++) {
+                configPlugin = cfg2.getPlugin(configPlugins[i]);
+                configPluginVariables = configPlugin.variables;
+                // Config.xml camera variables have been merged, no duplicates
+                if(configPlugin.name === 'cordova-plugin-camera') {
+                    expect(configPluginVariables).toEqual({ variable_1: 'value_1',
+                    variable_3: 'value_3', variable_2: 'value_2' });
+                }
+                // Expect that splashscreen and device have 0 variables
+                if(configPlugin.name === 'cordova-plugin-device') {
+                    expect(configPluginVariables).toEqual({});
+                }
+                if(configPlugin.name === 'cordova-plugin-splashscreen') {
+                    expect(configPluginVariables).toEqual({});
+                }
+            }
+            // Config.xml now has the camera, splashscreen, and device plugin
+            expect(Object.keys(configPlugins).length === 3);
+            expect(configPlugins.indexOf('cordova-plugin-camera')).toEqual(0);
+            expect(configPlugins.indexOf('cordova-plugin-splashscreen')).toEqual(1);
+            expect(configPlugins.indexOf('cordova-plugin-device')).toEqual(2);
+            // Pkg.json has all 3 plugins
+            expect(Object.keys(pkgJson.cordova.plugins).length === 3);
+            expect(pkgJson.cordova.plugins['cordova-plugin-camera']).toBeDefined();
+            expect(pkgJson.cordova.plugins['cordova-plugin-splashscreen']).toBeDefined();
+            expect(pkgJson.cordova.plugins['cordova-plugin-device']).toBeDefined();
+            // Expect that splashscreen and device have 0 variables
+            expect(pkgJson.cordova.plugins['cordova-plugin-splashscreen']).toEqual({});
+            expect(pkgJson.cordova.plugins['cordova-plugin-device']).toEqual({});
+            // Expect that the variables from config have been merged with the variables 
+            // from pkg.json to the camera plugin
+            expect(pkgJson.cordova.plugins['cordova-plugin-camera']).toEqual({ variable_1: 'value_1',
+            variable_3: 'value_3', variable_2: 'value_2' });
+        }).fail(function(err) {
+            expect(err).toBeUndefined();
+        }).fin(done);
+    // Cordova prepare needs extra wait time to complete.
+    },30000);
+});
+
+// Use basePkgJson12 as config.xml has 0 plugins and pkg.json has 1
+describe('update config.xml to include the plugin that is in pkg.json', function () {
+    var tmpDir = helpers.tmpDir('platform_test_pkgjson');
+    var project = path.join(tmpDir, 'project');
+    var results;
+
+    beforeEach(function() {
+        shell.rm('-rf', tmpDir);
+        // Copy then move because we need to copy everything, but that means it will copy the whole directory.
+        // Using /* doesn't work because of hidden files.
+        shell.cp('-R', path.join(__dirname, 'fixtures', 'basePkgJson12'), tmpDir);
+        shell.mv(path.join(tmpDir, 'basePkgJson12'), project);
+        process.chdir(project);
+        events.on('results', function(res) { results = res; });
+    });
+
+    afterEach(function() {
+        delete require.cache[require.resolve(path.join(process.cwd(),'package.json'))];
+        process.chdir(path.join(__dirname, '..'));  // Needed to rm the dir on Windows.
+        shell.rm('-rf', tmpDir);
+    });
+
+    // Factoring out some repeated checks.
+    function emptyPlatformList() {
+        return cordova.raw.platform('list').then(function() {
+            var installed = results.match(/Installed platforms:\n  (.*)/);
+            expect(installed).toBeDefined();
+            expect(installed[1].indexOf(helpers.testPlatform)).toBe(-1);
+        });
+    }
+    /** Test#015 will check the plugin/variable list in package.json and config.xml. 
+    *   When config has 0 plugins, it will get updated with the plugins from
+    *   pkg.json.
+    */
+    it('Test#015 : update config.xml to include all plugins/variables from pkg.json', function(done) {
+        var cwd = process.cwd();
+        var configXmlPath = path.join(cwd, 'config.xml');
+        var pkgJsonPath = path.join(cwd,'package.json');
+        delete require.cache[require.resolve(pkgJsonPath)];
+        var cfg1 = new ConfigParser(configXmlPath);
+        var configPlugins = cfg1.getPluginIdList();
+        var pkgJson = require(pkgJsonPath);
+        var configPlugin;
+        var configPluginVariables;
+        // Config.xml is initially empty and has no plugins
+        expect(Object.keys(configPlugins).length === 0);
+        // Expect that pkg.json exists with 1 plugin
+        expect(pkgJson.cordova.plugins).toBeDefined();
+        expect(Object.keys(pkgJson.cordova.plugins).length === 1);
+        expect(pkgJson.cordova.plugins['cordova-plugin-camera']).toBeDefined();
+        // Pkg.json camera plugin has var1, value 1
+        expect(pkgJson.cordova.plugins['cordova-plugin-camera']).toEqual({ variable_1: 'value_1' });
+
+        emptyPlatformList().then(function() {
+            // Run cordova prepare
             return cordova.raw.prepare();
         }).then(function() {
             // Delete any previous caches of require(package.json)
             delete require.cache[require.resolve(pkgJsonPath)];
             pkgJson = require(pkgJsonPath);
             var cfg2 = new ConfigParser(configXmlPath);
-            plugins = cfg2.getPluginIdList();
-            pluginNames = plugins.map(function(elem) {
-                return elem;
-            });
-            configPlugArray = pluginNames.slice();
-            // Expect that pkg.json exists with 2 plugins
-            expect(pkgJson.cordova.plugins).toBeDefined();
-            expect(pkgJson.cordova.plugins.length === 1);
-            // Expect that config.xml exists with 2 plugins
-            expect(configPlugArray).toBeDefined();
-            expect(configPlugArray.length === 1);
-            // Expect config to contain camera and splashscreen
-            expect(configPlugArray.indexOf('cordova-plugin-camera')).toEqual(0);
-            expect(configPlugArray.indexOf('cordova-plugin-splashscreen')).toEqual(1);
-            // Expect pkg.json to contain camera and splashscreen
-            expect(pkgJson.cordova.plugins.indexOf('cordova-plugin-splashscreen')).toEqual(0);
-            expect(pkgJson.cordova.plugins.indexOf('cordova-plugin-camera')).toEqual(1);
+            configPlugins = cfg2.getPluginIdList();
+
+            // Check to make sure that the variables were added as expected
+            for (var i = 0; i < configPlugins.length; i++) {
+                configPlugin = cfg2.getPlugin(configPlugins[i]);
+                configPluginVariables = configPlugin.variables;
+                // Config.xml camera variables have been added
+                if(configPlugin.name === 'cordova-plugin-camera') {
+                    expect(configPluginVariables).toEqual({ variable_1: 'value_1' });
+                }
+            }
+            // Config.xml now has the camera plugin
+            expect(Object.keys(configPlugins).length === 1);
+            expect(configPlugins.indexOf('cordova-plugin-camera')).toEqual(0);
+            // No changes to pkg.json
+            expect(Object.keys(pkgJson.cordova.plugins).length === 1);
+            expect(pkgJson.cordova.plugins['cordova-plugin-camera']).toBeDefined();
+            expect(pkgJson.cordova.plugins['cordova-plugin-camera']).toEqual({ variable_1: 'value_1' });
         }).fail(function(err) {
             expect(err).toBeUndefined();
         }).fin(done);

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/b247e12d/cordova-lib/spec-cordova/pkgJson.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/pkgJson.spec.js b/cordova-lib/spec-cordova/pkgJson.spec.js
index 4347a0e..45eb8ee 100644
--- a/cordova-lib/spec-cordova/pkgJson.spec.js
+++ b/cordova-lib/spec-cordova/pkgJson.spec.js
@@ -18,47 +18,10 @@
 */
 var helpers = require('./helpers'),
     path = require('path'),
-    fs = require('fs'),
     shell = require('shelljs'),
-    superspawn = require('cordova-common').superspawn,
-    Q = require('q'),
     events = require('cordova-common').events,
-    cordova = require('../src/cordova/cordova'),
-    rewire = require('rewire'),
-    prepare = require('../src/cordova/prepare'),
-    platforms = require('../src/platforms/platforms'),
-    platform = rewire('../src/cordova/platform.js');
-
-var projectRoot = 'C:\\Projects\\cordova-projects\\move-tracker';
-var pluginsDir = path.join(__dirname, 'fixtures', 'plugins');
-
-function addPlugin(target, id, options) {
-    // Checks that there are no plugins yet.
-    return cordova.raw.plugin('list').then(function() {
-        expect(results).toMatch(/No plugins added/gi);
-    }).then(function() {
-        // Adds a fake plugin from fixtures.
-        return cordova.raw.plugin('add', target, options);
-    }).then(function() {
-        expect(path.join(project, 'plugins', id, 'plugin.xml')).toExist();
-    }).then(function() {
-        return cordova.raw.plugin('ls');
-    }).then(function() {
-        expect(results).toContain(id);
-    });
-}
-// Runs: remove, list.
-function removePlugin(id, options) {
-    return cordova.raw.plugin('rm', id)
-    .then(function() {
-        // The whole dir should be gone.
-        expect(path.join(project, 'plugins', id)).not.toExist();
-    }).then(function() {
-        return cordova.raw.plugin('ls');
-    }).then(function() {
-        expect(results).toMatch(/No plugins added/gi);
-    });
-}
+    cordova = require('../src/cordova/cordova');
+
 // This group of tests checks if plugins are added and removed as expected from package.json.
 describe('plugin end-to-end', function() {
     var pluginId = 'cordova-plugin-device';
@@ -199,6 +162,40 @@ describe('plugin end-to-end', function() {
             expect(err).toBeUndefined();
         }).fin(done);
     });
+    it('Test#005 : should successfully add and remove multiple plugins with save & fetch', function(done) {
+        var pkgJsonPath = path.join(process.cwd(),'package.json');
+        var pkgJson;
+    
+        expect(pkgJsonPath).toExist();
+
+        // Add the plugin with --save
+        return cordova.raw.plugin('add', [pluginId,'cordova-plugin-device-motion'], {'save':true, 'fetch':true})
+        .then(function() {
+            // Check that the plugin add was successful.
+            delete require.cache[require.resolve(pkgJsonPath)];
+            pkgJson = require(pkgJsonPath);
+            expect(pkgJson).not.toBeUndefined();
+            expect(pkgJson.cordova.plugins).not.toBeUndefined();
+            expect(pkgJson.cordova.plugins[pluginId]).toBeDefined();
+            expect(pkgJson.cordova.plugins['cordova-plugin-device-motion']).toBeDefined();
+            expect(pkgJson.dependencies[pluginId]).toBeDefined();
+            expect(pkgJson.dependencies['cordova-plugin-device-motion']).toBeDefined();
+        }).then(function() {
+            // And now remove it with --save.
+            return cordova.raw.plugin('rm', [pluginId,'cordova-plugin-device-motion'], {'save':true, 'fetch':true})
+        }).then(function() {
+            // Delete any previous caches of require(package.json)
+            delete require.cache[require.resolve(pkgJsonPath)];
+            pkgJson = require(pkgJsonPath);
+            // Checking that the plugin removed is in not in the platforms
+            expect(pkgJson.cordova.plugins[pluginId]).toBeUndefined();
+            expect(pkgJson.cordova.plugins['cordova-plugin-device-motion']).toBeUndefined();
+            expect(pkgJson.dependencies[pluginId]).toBeUndefined();
+            expect(pkgJson.dependencies['cordova-plugin-device-motion']).toBeUndefined();
+        }).fail(function(err) {
+            expect(err).toBeUndefined();
+        }).fin(done);
+    });
 });
 
 // This group of tests checks if platforms are added and removed as expected from package.json.
@@ -313,7 +310,7 @@ describe('platform end-to-end with --save', function () {
             delete require.cache[require.resolve(pkgJsonPath)];
             pkgJson = require(pkgJsonPath);
             // Platform list should be empty and helpers.testPlatform should NOT have been added.
-            expect(pkgJson.cordova).toBeUndefined();
+            expect(pkgJson.cordova.platforms.length).toEqual(0);
         }).then(fullPlatformList)
         .fail(function(err) {
             expect(err).toBeUndefined();

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/b247e12d/cordova-lib/src/cordova/restore-util.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/restore-util.js b/cordova-lib/src/cordova/restore-util.js
index 692c374..bfb3830 100644
--- a/cordova-lib/src/cordova/restore-util.js
+++ b/cordova-lib/src/cordova/restore-util.js
@@ -30,7 +30,6 @@ var cordova_util = require('./util'),
 exports.installPluginsFromConfigXML = installPluginsFromConfigXML;
 exports.installPlatformsFromConfigXML = installPlatformsFromConfigXML;
 
-
 function installPlatformsFromConfigXML(platforms, opts) {
     events.emit('verbose', 'Checking config.xml and package.json for saved platforms that haven\'t been added to the project');
 
@@ -40,169 +39,104 @@ function installPlatformsFromConfigXML(platforms, opts) {
     var engines = cfg.getEngines();
     var pkgJsonPath = path.join(projectHome,'package.json');
     var pkgJson;
-    var targetsPkgJson;
-    var comboArray; 
+    var pkgJsonPlatforms;
+    var comboArray = []; 
     var targets;
-    var targetsConfig;
-    var engines;
+    var configPlatforms = [];
     var installAllPlatforms;
     var platformPath;
     var platformAlreadyAdded;
     var t;
-    var ConfigAndPkgJsonArray = [];
     var modifiedPkgJson = false;
     var modifiedConfigXML = false;
-    var modifiedPkgJsonPlugin = false;
-    var modifiedConfigXMLPlugin = false;
-    var targetPlatformsArray = [];
+    var configObject;
 
     if(fs.existsSync(pkgJsonPath)) {
         pkgJson = require(pkgJsonPath);
     }
     if(pkgJson !== undefined && pkgJson.cordova !== undefined && pkgJson.cordova.platforms !== undefined) {
-        targetsPkgJson = pkgJson.cordova.platforms;
+        pkgJsonPlatforms = pkgJson.cordova.platforms;
     } 
+    
     if(cfg !== undefined) {
-        targetsConfig = [];
-        engines = cfg.getEngines(projectHome);
-        installAllPlatforms = !platforms || platforms.length === 0;
-
-        targets = engines.map(function(engine) {
-            platformPath = path.join(projectHome, 'platforms', engine.name);
-            platformAlreadyAdded = fs.existsSync(platformPath);
 
-            // If no platforms are specified we add all.
-            if ((installAllPlatforms || platforms.indexOf(engine.name) > -1) && !platformAlreadyAdded) {
-                t = engine.name;
-                if (engine.spec) {
-                    //t += '@' + engine.spec;
-                    targetsConfig.push(t);
-                }
-                return t;
-            }
-        });   
-        if (targetsPkgJson !== undefined) {
+        if (pkgJsonPlatforms !== undefined) {
             // Combining arrays and checking duplicates
-            comboArray = targetsPkgJson.slice();
-        } else {
-            comboArray = [];
+            comboArray = pkgJsonPlatforms.slice();
         }
-        targetsConfig.forEach(function(item) {
+
+        engines = cfg.getEngines(projectHome)
+        configPlatforms = engines.map(function(Engine) {
+            configPlatName = Engine.name;
+            return configPlatName;
+        });
+        
+        configPlatforms.forEach(function(item) {
             if(comboArray.indexOf(item) < 0 ) {
                 comboArray.push(item);
             }
         });
-        // If config.xml & pkgJson exist and the cordova key is undefined, create a cordova key.
-        if (cfg !== undefined && pkgJson !== undefined && pkgJson.cordova === undefined) {
-            pkgJson.cordova = {};
-        }
-        // If there is no platforms array, create an empty one.
-        if (pkgJson.cordova.platforms === undefined) {
-            pkgJson.cordova.platforms = [];
-            // Get a list of platforms names from config.xml.
-            targetPlatformsArray = engines.map(function(mEngine) {
-                t = mEngine.name;
-                return t;
-            }); 
-
-        if (!targets || !targets.length) {
-           return Q('No platforms found in config.xml that haven\'t been added to the project');
-        }
-
-            var uniq = targetPlatformsArray.reduce(function(a,b){
-                if (a.indexOf(b) < 0 ) a.push(b);
+        //comboArray should have all platforms from config.xml & package.json
+        //remove dupes in comboArray & sort
+        var uniq = comboArray.reduce(function(a,b) {
+            if (a.indexOf(b) < 0 ) a.push(b);
                 return a;
-            },[]);
-            targetPlatformsArray = uniq;
+        },[]);
+        comboArray = uniq;
+        comboArray = comboArray.sort();
 
-            // Add the platforms from config.xml to package.json
-            pkgJson.cordova.platforms = targetPlatformsArray;
-            modifiedPkgJson = true;
-            //fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 4), 'utf8');
+        // No platforms to restore from either config.xml or package.json
+        if (comboArray.length <= 0) {
+           return Q('No platforms found in config.xml or package.json. Nothing to restore');
         }
-        // If config.xml and pkg.json exist and both already contain platforms, run cordova prepare
-        // so that both files are identical
-        if(cfg !== undefined && targetsConfig !== undefined && pkgJson.cordova.platforms !== undefined) {
-            
-            targetsConfig = engines.map(function(mEngine) {
-                t = mEngine.name;
-                //targetsConfig.push(t);
-                return t;
-            });
-
-            var uniq = targetsConfig.reduce(function(a,b){
-                if (a.indexOf(b) < 0 ) a.push(b);
-                return a;
-            },[]);
-            targetsConfig = uniq;
-        }
-        if (pkgJson.cordova.platforms !== undefined) {
-            // Create a new array based on cordova.platforms
-            pkgJson.cordova.platforms.forEach(function(item) {
-                if (ConfigAndPkgJsonArray.indexOf(item) < 0 ) 
-                    ConfigAndPkgJsonArray.push(item);
-            });
-        }
-        targetsConfig.forEach(function(item) {
-            if(ConfigAndPkgJsonArray.indexOf(item) < 0 ) {
-                ConfigAndPkgJsonArray.push(item);
-            }
-        });
 
-        //sort the arrays alphabetically 
-        ConfigAndPkgJsonArray = ConfigAndPkgJsonArray.sort();
-        pkgJson.cordova.platforms = pkgJson.cordova.platforms.sort();
+        //if no package.json, don't bother
+        if (pkgJson !== undefined) { 
 
-        // Get list of engine names and create a new array of engines from config.xml.
-        var engNames = engines.map(function(elem) {
-            return elem.name;
-        });
-        var configEngArray = engNames.sort().slice();
+            // If config.xml & pkgJson exist and the cordova key is undefined, create a cordova key.
+            if (pkgJson.cordova === undefined) {
+                pkgJson.cordova = {};
+            }
+            // If there is no platforms array, create an empty one.
+            if (pkgJson.cordova.platforms === undefined) {
+                pkgJson.cordova.platforms = [];
+            }
+            // If comboArray has the same platforms as pkg.json, no modification to pkg.json.
+            if (comboArray.toString() === pkgJson.cordova.platforms.sort().toString()) {
+                events.emit('verbose', 'Config.xml and package.json platforms are the same. No pkg.json modification.');
+            } else {
+                // modify pkg.json to include the elements
+                // from the comboArray array so that the arrays are identical
+                events.emit('verbose', 'Config.xml and package.json platforms are different. Updating package.json with most current list of platforms.');
+                modifiedPkgJson = true;
+            }
 
-        // If ConfigAndPkgJson array has the same platforms as pkg.json, no modification to pkg.json.
-        if(ConfigAndPkgJsonArray.toString() === pkgJson.cordova.platforms.toString()) {
-            events.emit('warn', 'Config.xml and package.json platforms are the same. No pkg.json modification.');
-        }
-        // If ConfigAndPkgJson array has the same platforms as config.xml, no modification to config.xml.
-        if(ConfigAndPkgJsonArray.toString() === configEngArray.toString()) {
-            events.emit('warn', 'Package.json and config.xml are the same. No config.xml modification.');
-        }
-        // If ConfigAndPkgJson array do not have the same platforms, modify pkg.json to include the elements
-        // from the ConfigAndPkgJson array so that the arrays are identical
-        if(ConfigAndPkgJsonArray.toString() !== pkgJson.cordova.platforms.toString()) {
-            events.emit('warn', 'Config.xml and package.json platforms are not the same. Updating package.json with most current list of platforms.');
-            ConfigAndPkgJsonArray.forEach(function(item) {
-                if(pkgJson.cordova.platforms.indexOf(item) < 0 ) {
-                    pkgJson.cordova.platforms.push(item);
-                    modifiedPkgJson = true;
-                }
-            });
-        }
-        // If ConfigAndPkgJson array do not have the same platforms, modify config.xml to include the elements
-        // from the ConfigAndPkgJson array so that the arrays are identical
-        if(ConfigAndPkgJsonArray.toString() !== configEngArray.toString()) {
-            events.emit('warn', 'Package.json and config.xml platforms are not the same. Updating config.xml with most current list of platforms.');
-            ConfigAndPkgJsonArray.forEach(function(item) {
-                if(configEngArray.indexOf(item) < 0 ) {
-                    configEngArray.push(item);
-                    modifiedConfigXML = true;
-                    cfg.addEngine(item);
-                }
-            });
+            // If comboArray has the same platforms as config.xml, no modification to config.xml.
+            if(comboArray.length === configPlatforms.length && comboArray.toString() === configPlatforms.sort().toString()) {
+                events.emit('verbose', 'Package.json and config.xml are the same. No config.xml modification.');
+            } else {
+                events.emit('verbose', 'Package.json and config.xml platforms are different. Updating config.xml with most current list of platforms.');
+                comboArray.forEach(function(item) {
+                    if(configPlatforms.indexOf(item) < 0 ) {
+                        cfg.addEngine(item);
+                        modifiedConfigXML = true;
+                    }
+                });
+            }
         }
         // Write and update pkg.json if it has been modified.
         if (modifiedPkgJson === true) {
-            //sort then write
-            pkgJson.cordova.platforms = pkgJson.cordova.platforms.sort();
+            pkgJson.cordova.platforms = comboArray;
+            //pkgJson.cordova.dependencies[configPlatName] =configPlatSpec;
             fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 4), 'utf8');
         }
         if (modifiedConfigXML === true) {
+            configPlatforms = comboArray;
             cfg.write();
         }
         if (!comboArray || !comboArray.length) {
             return Q('No platforms found in config.xml and/or package.json that haven\'t been added to the project');
         }
-
     }
     // Run `platform add` for all the platforms separately
     // so that failure on one does not affect the other.
@@ -231,113 +165,97 @@ function installPluginsFromConfigXML(args) {
     var plugins_dir = path.join(projectRoot, 'plugins');
     var pkgJsonPath = path.join(projectRoot,'package.json');
     var pkgJson;
-    var pkgJsonArray = [];
-    var comboPluginArray;
-
-    // Get all configured plugins
-    var pluginIdConfig = cfg.getPluginIdList();
-    if (0 === pluginIdConfig.length) {
-        return Q('No plugins found in config.xml that haven\'t been added to the project');
-    }
-    // Check if path exists and require pkgJsonPath
+    var modifiedPkgJson = false;
+    var modifiedConfigXML = false;
+    //TODO: rename to comboObject?
+    var mergedPluginDataObj;
+    var comboPluginIdArray;
+    var configPlugin;
+    var configPluginVariables;
+    var pkgJsonPluginVariables;
+
+    //Check if path exists and require pkgJsonPath
     if(fs.existsSync(pkgJsonPath)) {
         pkgJson = require(pkgJsonPath);
     }
-    if (pkgJson.cordova === undefined) {
-        pkgJson.cordova = {};
-    }
-    // (In pkg.json), if there is a platforms array and not plugins array, create a new pluginIdConfig array 
-    // and add all plugins from config.xml to the new plugins array.
-    if (cfg !== undefined && pkgJson !== undefined && pkgJson.cordova.platforms !== undefined && 
-    pkgJson.cordova.plugins === undefined) {
-        pkgJson.cordova.plugins = {};
-        pluginIdConfig.forEach(function(foo) {
-            pkgJson.cordova.plugins[foo] = {}
-        });
-        fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 4), 'utf8');
-    }
-    // (In pkg.json), if there is no platforms array and no plugins array, create a new plugins array
-    // and add all plugins from config.xml to the new plugins array.
-    if (cfg !== undefined && pkgJson !== undefined && pkgJson.cordova.platforms === undefined && 
-    pkgJson.cordova.plugins === undefined) {
-        pkgJson.cordova.platforms = [];
-        pkgJson.cordova.plugins = {};
-        pluginIdConfig.forEach(function(foo) {
-            pkgJson.cordova.plugins[foo] = {}
-        });
-        fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 4), 'utf8');
-    }
 
-    pkgJsonArray = Object.keys(pkgJson.cordova.plugins);
+    if(pkgJson !== undefined && pkgJson.cordova !== undefined && pkgJson.cordova.plugins !== undefined) {
+        comboPluginIdArray = Object.keys(pkgJson.cordova.plugins);
+        // Create a merged plugin data array (mergedPluginDataObj)
+        // and add all of the package.json plugins to mergedPluginDataObj
+        mergedPluginDataObj = pkgJson.cordova.plugins;
+    } else {
+        mergedPluginDataObj = {};
+        comboPluginIdArray = [];
+    }
 
-    var pluginNames = pluginIdConfig.map(function(elem) {
-        return elem.name;
-    });
+    // Get all config.xml plugin ids
+    var pluginIdConfig = cfg.getPluginIdList();
+    if(pluginIdConfig === undefined) {
+        pluginIdConfig = [];
+    }
 
-    // Create a merged plugin data array (mergedPluginDataObj)
-    // and add all of the package.json plugins to mergedPluginDataObj
-    var mergedPluginDataObj = pkgJson.cordova.plugins;
-    var mergedPluginDataArray = pkgJsonArray;
+    if(pkgJson !== undefined) {
+        if (pkgJson.cordova === undefined) {
+            pkgJson.cordova = {};
+        }
+        if (pkgJson.cordova.plugins === undefined) {
+            pkgJson.cordova.plugins = {};
+        }
 
-    // Check to see which plugins are initially the same in pkg.json and config.xml
-    // Merge identical plugins and their variables together first
-    for (var i = 0; i < mergedPluginDataArray.length; i++) {
-        if(pluginIdConfig.includes(mergedPluginDataArray[i])) {
-            var configPlugin = cfg.getPlugin(mergedPluginDataArray[i]);
-            var configPluginVariables = configPlugin.variables;
-            var pkgJsonPluginVariables = mergedPluginDataObj[mergedPluginDataArray[i]];
-            for(var key in configPluginVariables) {
-                // Handle conflicts, package.json wins
-                if(pkgJsonPluginVariables[key] === undefined) {
-                    pkgJsonPluginVariables[key] = configPluginVariables[key];
-                    mergedPluginDataObj[mergedPluginDataArray[i]][key] = configPluginVariables[key];
+        // Check to see which plugins are initially the same in pkg.json and config.xml
+        // add missing plugin variables in package.json from config.xml
+        comboPluginIdArray.forEach(function(item) {
+            if(pluginIdConfig.includes(item)) {
+                configPlugin = cfg.getPlugin(item);
+                configPluginVariables = configPlugin.variables;
+                pkgJsonPluginVariables = mergedPluginDataObj[item];
+                for(var key in configPluginVariables) {
+                    // Handle conflicts, package.json wins
+                    // only add the variable to package.json if it doesn't already exist
+                    if(pkgJsonPluginVariables[key] === undefined) {
+                        pkgJsonPluginVariables[key] = configPluginVariables[key];
+                        mergedPluginDataObj[item][key] = configPluginVariables[key];
+                        modifiedPkgJson = true;
+                    }
                 }
             }
+        })
+
+        // Check to see if pkg.json plugin(id) and config plugin(id) match
+        if(comboPluginIdArray.sort().toString() !== pluginIdConfig.sort().toString()) {
+            // If there is a config plugin that does NOT already exist in
+            // mergedPluginDataArray, add it and its variables
+            pluginIdConfig.forEach(function(item) {
+                if(comboPluginIdArray.indexOf(item) < 0) {
+                    comboPluginIdArray.push(item);
+                    var configXMLPlugin = cfg.getPlugin(item);
+                    mergedPluginDataObj[item] = configXMLPlugin.variables;
+                    modifiedPkgJson = true;
+                }
+            });
         }
-    }
-    // Check to see if pkg.json plugin(id) and config plugin(id) match
-    if(mergedPluginDataArray.sort().toString() !== pluginIdConfig.sort().toString()) {
-        // If there is a config plugin that does NOT already exist in
-        // mergedPluginDataArray, add it and its variables
-        pluginIdConfig.forEach(function(item) {
-            if(mergedPluginDataArray.indexOf(item) < 0) {
-                mergedPluginDataArray.push(item);
-                var configXMLPlugin = cfg.getPlugin(item);
-                mergedPluginDataObj[item] = configXMLPlugin.variables;
-            }
-        });
-    }
-    // Write to pkg.Json
-    // TODO: toString comparision to see if they are the same and then write
-    pkgJson.cordova.plugins = mergedPluginDataObj;
-    fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 4), 'utf8');
 
+        if (modifiedPkgJson === true) {
+            pkgJson.cordova.plugins = mergedPluginDataObj;
+            fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 4), 'utf8');
+        }
+    }
+    
     // Write config.xml
-    mergedPluginDataArray.forEach(function(plugID) {
+    comboPluginIdArray.forEach(function(plugID) {
+        pluginIdConfig.push(plugID);
         cfg.removePlugin(plugID);
+        //TODO: spec needs to be added to addPlugin
         cfg.addPlugin({name:plugID}, mergedPluginDataObj[plugID]); 
+        modifiedConfigXML = true;
     });
-    cfg.write();
 
-    // If config.xml and pkg.json exist and both already contain plugins, run cordova prepare
-    // to check if these plugins are identical.
-    if(cfg !== undefined && pkgJson.cordova.plugins !== undefined) {
-        if(pkgJsonArray.toString() === pluginIdConfig.toString() && pkgJsonArray.length === pluginIdConfig.length) {
-            events.emit('warn', 'Config.xml and pkgJson plugins are the same. No pkg.json or config.xml modification.');
-        }
-
-        if(pkgJsonArray.toString() !== pluginIdConfig.toString() || pkgJsonArray.length !== pluginIdConfig.length) {
-           events.emit('warn', 'Config.xml and pkgJson plugins are different.');
-            // Combining arrays and checking duplicates
-
-            comboPluginArray = pkgJsonArray.slice();
-            pluginIdConfig.forEach(function(item) {
-                if(comboPluginArray.indexOf(item) < 0) {
-                    comboPluginArray.push(item);
-                }
-            });
-        }
+    if (modifiedConfigXML === true) {
+        cfg.write();    
     }
+    
+
     // Intermediate variable to store current installing plugin name
     // to be able to create informative warning on plugin failure
     var pluginName;


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