You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ka...@apache.org on 2014/05/01 20:32:12 UTC

[27/53] [abbrv] Split out cordova-lib: move cordova-plugman files

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/spec-plugman/projects/windows8/www/css/index.css
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/windows8/www/css/index.css b/cordova-lib/spec-plugman/projects/windows8/www/css/index.css
new file mode 100644
index 0000000..51daa79
--- /dev/null
+++ b/cordova-lib/spec-plugman/projects/windows8/www/css/index.css
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+* {
+    -webkit-tap-highlight-color: rgba(0,0,0,0); /* make transparent link selection, adjust last value opacity 0 to 1.0 */
+}
+
+body {
+    -webkit-touch-callout: none;                /* prevent callout to copy image, etc when tap to hold */
+    -webkit-text-size-adjust: none;             /* prevent webkit from resizing text to fit */
+    -webkit-user-select: none;                  /* prevent copy paste, to allow, change 'none' to 'text' */
+    background-color:#E4E4E4;
+    background-image:linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
+    background-image:-webkit-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
+    background-image:-ms-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
+    background-image:-webkit-gradient(
+        linear,
+        left top,
+        left bottom,
+        color-stop(0, #A7A7A7),
+        color-stop(0.51, #E4E4E4)
+    );
+    background-attachment:fixed;
+    font-family:'HelveticaNeue-Light', 'HelveticaNeue', Helvetica, Arial, sans-serif;
+    font-size:12px;
+    height:100%;
+    margin:0px;
+    padding:0px;
+    text-transform:uppercase;
+    width:100%;
+}
+
+/* Portrait layout (default) */
+.app {
+    background:url(../img/logo.png) no-repeat center top; /* 170px x 200px */
+    position:absolute;             /* position in the center of the screen */
+    left:50%;
+    top:50%;
+    height:50px;                   /* text area height */
+    width:225px;                   /* text area width */
+    text-align:center;
+    padding:180px 0px 0px 0px;     /* image height is 200px (bottom 20px are overlapped with text) */
+    margin:-115px 0px 0px -112px;  /* offset vertical: half of image height and text area height */
+                                   /* offset horizontal: half of text area width */
+}
+
+/* Landscape layout (with min-width) */
+@media screen and (min-aspect-ratio: 1/1) and (min-width:400px) {
+    .app {
+        background-position:left center;
+        padding:75px 0px 75px 170px;  /* padding-top + padding-bottom + text area = image height */
+        margin:-90px 0px 0px -198px;  /* offset vertical: half of image height */
+                                      /* offset horizontal: half of image width and text area width */
+    }
+}
+
+h1 {
+    font-size:24px;
+    font-weight:normal;
+    margin:0px;
+    overflow:visible;
+    padding:0px;
+    text-align:center;
+}
+
+.event {
+    border-radius:4px;
+    -webkit-border-radius:4px;
+    color:#FFFFFF;
+    font-size:12px;
+    margin:0px 30px;
+    padding:2px 0px;
+}
+
+.event.listening {
+    background-color:#333333;
+    display:block;
+}
+
+.event.received {
+    background-color:#4B946A;
+    display:none;
+}
+
+@keyframes fade {
+    from { opacity: 1.0; }
+    50% { opacity: 0.4; }
+    to { opacity: 1.0; }
+}
+ 
+@-webkit-keyframes fade {
+    from { opacity: 1.0; }
+    50% { opacity: 0.4; }
+    to { opacity: 1.0; }
+}
+ 
+.blink {
+    animation:fade 3000ms infinite;
+    -webkit-animation:fade 3000ms infinite;
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/spec-plugman/projects/windows8/www/img/logo.png
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/windows8/www/img/logo.png b/cordova-lib/spec-plugman/projects/windows8/www/img/logo.png
new file mode 100644
index 0000000..86a48a8
Binary files /dev/null and b/cordova-lib/spec-plugman/projects/windows8/www/img/logo.png differ

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/spec-plugman/projects/windows8/www/img/smalllogo.png
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/windows8/www/img/smalllogo.png b/cordova-lib/spec-plugman/projects/windows8/www/img/smalllogo.png
new file mode 100644
index 0000000..0e648ef
Binary files /dev/null and b/cordova-lib/spec-plugman/projects/windows8/www/img/smalllogo.png differ

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/spec-plugman/projects/windows8/www/img/splashscreen.png
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/windows8/www/img/splashscreen.png b/cordova-lib/spec-plugman/projects/windows8/www/img/splashscreen.png
new file mode 100644
index 0000000..d1e6c98
Binary files /dev/null and b/cordova-lib/spec-plugman/projects/windows8/www/img/splashscreen.png differ

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/spec-plugman/projects/windows8/www/img/storelogo.png
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/windows8/www/img/storelogo.png b/cordova-lib/spec-plugman/projects/windows8/www/img/storelogo.png
new file mode 100644
index 0000000..dd00478
Binary files /dev/null and b/cordova-lib/spec-plugman/projects/windows8/www/img/storelogo.png differ

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/spec-plugman/projects/windows8/www/index.html
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/windows8/www/index.html b/cordova-lib/spec-plugman/projects/windows8/www/index.html
new file mode 100644
index 0000000..ca8ab84
--- /dev/null
+++ b/cordova-lib/spec-plugman/projects/windows8/www/index.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<!--
+    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.
+-->
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <meta name="format-detection" content="telephone=no" />
+        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
+        <link rel="stylesheet" type="text/css" href="css/index.css" />
+        <title>Hello World</title>
+    </head>
+    <body>
+        <div class="app">
+            <h1>Apache Cordova</h1>
+            <div id="deviceready" class="blink">
+                <p class="event listening">Connecting to Device</p>
+                <p class="event received">Device is Ready</p>
+            </div>
+        </div>
+        <script type="text/javascript" src="cordova-2.6.0.js"></script>
+        <script type="text/javascript" src="js/index.js"></script>
+        <script type="text/javascript">
+            app.initialize();
+        </script>
+    </body>
+</html>

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/spec-plugman/projects/windows8/www/js/index.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/windows8/www/js/index.js b/cordova-lib/spec-plugman/projects/windows8/www/js/index.js
new file mode 100644
index 0000000..87b5660
--- /dev/null
+++ b/cordova-lib/spec-plugman/projects/windows8/www/js/index.js
@@ -0,0 +1,49 @@
+/*
+ * 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 app = {
+    // Application Constructor
+    initialize: function() {
+        this.bindEvents();
+    },
+    // Bind Event Listeners
+    //
+    // Bind any events that are required on startup. Common events are:
+    // 'load', 'deviceready', 'offline', and 'online'.
+    bindEvents: function() {
+        document.addEventListener('deviceready', this.onDeviceReady, false);
+    },
+    // deviceready Event Handler
+    //
+    // The scope of 'this' is the event. In order to call the 'receivedEvent'
+    // function, we must explicitly call 'app.receivedEvent(...);'
+    onDeviceReady: function() {
+        app.receivedEvent('deviceready');
+    },
+    // Update DOM on a Received Event
+    receivedEvent: function(id) {
+        var parentElement = document.getElementById(id);
+        var listeningElement = parentElement.querySelector('.listening');
+        var receivedElement = parentElement.querySelector('.received');
+
+        listeningElement.setAttribute('style', 'display:none;');
+        receivedElement.setAttribute('style', 'display:block;');
+
+        console.log('Received Event: ' + id);
+    }
+};

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/spec-plugman/projects/wp7/CordovaAppProj.csproj
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/wp7/CordovaAppProj.csproj b/cordova-lib/spec-plugman/projects/wp7/CordovaAppProj.csproj
new file mode 100644
index 0000000..4b122a2
--- /dev/null
+++ b/cordova-lib/spec-plugman/projects/wp7/CordovaAppProj.csproj
@@ -0,0 +1,76 @@
+<?xml version='1.0' encoding='utf-8'?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+    <PropertyGroup>
+        <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+        <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+        <ProductVersion>10.0.20506</ProductVersion>
+        <SchemaVersion>2.0</SchemaVersion>
+        <ProjectGuid>{3677C1B7-D68B-4CF9-BF8A-E869D437A6DF}</ProjectGuid>
+        <ProjectTypeGuids>{C089C8C0-30E0-4E22-80C0-CE093F111A43};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
+        <OutputType>Library</OutputType>
+        <AppDesignerFolder>Properties</AppDesignerFolder>
+        <RootNamespace>$safeprojectname$</RootNamespace>
+        <AssemblyName>$safeprojectname$</AssemblyName>
+        <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+        <SilverlightVersion>$(TargetFrameworkVersion)</SilverlightVersion>
+        <TargetFrameworkProfile>WindowsPhone71</TargetFrameworkProfile>
+        <TargetFrameworkIdentifier>Silverlight</TargetFrameworkIdentifier>
+        <SilverlightApplication>true</SilverlightApplication>
+        <SupportedCultures>
+        </SupportedCultures>
+        <XapOutputs>true</XapOutputs>
+        <GenerateSilverlightManifest>true</GenerateSilverlightManifest>
+        <XapFilename>$safeprojectname$.xap</XapFilename>
+        <SilverlightManifestTemplate>Properties\AppManifest.xml</SilverlightManifestTemplate>
+        <SilverlightAppEntry>$safeprojectname$.App</SilverlightAppEntry>
+        <ValidateXaml>true</ValidateXaml>
+        <ThrowErrorsInValidation>true</ThrowErrorsInValidation>
+    </PropertyGroup>
+    <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+        <DebugSymbols>true</DebugSymbols>
+        <DebugType>full</DebugType>
+        <Optimize>false</Optimize>
+        <OutputPath>Bin\Debug</OutputPath>
+        <DefineConstants>DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants>
+        <NoStdLib>true</NoStdLib>
+        <NoConfig>true</NoConfig>
+        <ErrorReport>prompt</ErrorReport>
+        <WarningLevel>4</WarningLevel>
+    </PropertyGroup>
+    <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+        <DebugType>pdbonly</DebugType>
+        <Optimize>true</Optimize>
+        <OutputPath>Bin\Release</OutputPath>
+        <DefineConstants>TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants>
+        <NoStdLib>true</NoStdLib>
+        <NoConfig>true</NoConfig>
+        <ErrorReport>prompt</ErrorReport>
+        <WarningLevel>4</WarningLevel>
+    </PropertyGroup>
+    <ItemGroup>
+        <Reference Include="Microsoft.Devices.Sensors" />
+        <Reference Include="Microsoft.Phone" />
+        <Reference Include="Microsoft.Phone.Interop" />
+        <Reference Include="Microsoft.Xna.Framework" />
+        <Reference Include="System.Device" />
+        <Reference Include="System.Runtime.Serialization" />
+        <Reference Include="System.Servicemodel.Web" />
+        <Reference Include="System.Windows" />
+        <Reference Include="system" />
+        <Reference Include="System.Core" />
+        <Reference Include="System.Net" />
+        <Reference Include="System.Xml" />
+        <Reference Include="System.Xml.Linq" />
+    </ItemGroup>
+    <Import Project="$(MSBuildExtensionsPath)\Microsoft\Silverlight for Phone\$(TargetFrameworkVersion)\Microsoft.Silverlight.$(TargetFrameworkProfile).Overrides.targets" />
+    <Import Project="$(MSBuildExtensionsPath)\Microsoft\Silverlight for Phone\$(TargetFrameworkVersion)\Microsoft.Silverlight.CSharp.targets" />
+    <ProjectExtensions />
+    <PropertyGroup>
+        <PreBuildEvent>CScript "$(ProjectDir)/BuildManifestProcessor.js" "$(ProjectPath)"</PreBuildEvent>
+    </PropertyGroup>
+    <PropertyGroup>
+        <PostBuildEvent>
+        </PostBuildEvent>
+    </PropertyGroup>
+    <ItemGroup />
+</Project>

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/spec-plugman/projects/wp7/Properties/WMAppManifest.xml
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/wp7/Properties/WMAppManifest.xml b/cordova-lib/spec-plugman/projects/wp7/Properties/WMAppManifest.xml
new file mode 100644
index 0000000..b218fcb
--- /dev/null
+++ b/cordova-lib/spec-plugman/projects/wp7/Properties/WMAppManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Deployment xmlns="http://schemas.microsoft.com/windowsphone/2009/deployment" AppPlatformVersion="7.1">
+  <App xmlns="" ProductID="{5FC10D78-8779-4EDB-9B61-1D04F0A755D4}" Title="An App" 
+       RuntimeType="Silverlight" Version="1.0.0.0" Genre="apps.normal"  
+       Author="An App author" 
+       BitsPerPixel="32" 
+       Description="Apache Cordova for Windows Phone 7"
+       Publisher="An App">
+    
+    <IconPath IsRelative="true" IsResource="false">ApplicationIcon.png</IconPath>
+    <Capabilities>
+      <Capability Name="ID_CAP_WEBBROWSERCOMPONENT" />   
+    </Capabilities>
+    
+    <Tasks>
+      <DefaultTask Name="_default" NavigationPage="MainPage.xaml" />
+    </Tasks>
+    <Tokens>
+      <PrimaryToken TokenID="An AppToken" TaskName="_default">
+        <TemplateType5>
+          <BackgroundImageURI IsRelative="true" IsResource="false">Background.png</BackgroundImageURI>
+          <Count>0</Count>
+          <Title>An App</Title>
+        </TemplateType5>
+      </PrimaryToken>
+    </Tokens>
+  </App>
+</Deployment>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/spec-plugman/projects/wp8/CordovaAppProj.csproj
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/wp8/CordovaAppProj.csproj b/cordova-lib/spec-plugman/projects/wp8/CordovaAppProj.csproj
new file mode 100644
index 0000000..bc229e5
--- /dev/null
+++ b/cordova-lib/spec-plugman/projects/wp8/CordovaAppProj.csproj
@@ -0,0 +1,136 @@
+<?xml version='1.0' encoding='utf-8'?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+    <PropertyGroup>
+        <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+        <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+        <ProductVersion>10.0.20506</ProductVersion>
+        <SchemaVersion>2.0</SchemaVersion>
+        <ProjectGuid>{3677C1B7-D68B-4CF9-BF8A-E869D437A6DF}</ProjectGuid>
+        <ProjectTypeGuids>{C089C8C0-30E0-4E22-80C0-CE093F111A43};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
+        <OutputType>Library</OutputType>
+        <AppDesignerFolder>Properties</AppDesignerFolder>
+        <RootNamespace>my.test.project</RootNamespace>
+        <AssemblyName>my.test.project</AssemblyName>
+        <TargetFrameworkVersion>v8.0</TargetFrameworkVersion>
+        <SilverlightVersion>
+        </SilverlightVersion>
+        <TargetFrameworkProfile>
+        </TargetFrameworkProfile>
+        <TargetFrameworkIdentifier>WindowsPhone</TargetFrameworkIdentifier>
+        <SilverlightApplication>true</SilverlightApplication>
+        <SupportedCultures>en-US</SupportedCultures>
+        <XapOutputs>true</XapOutputs>
+        <GenerateSilverlightManifest>true</GenerateSilverlightManifest>
+        <XapFilename>CordovaAppProj_$(Configuration)_$(Platform).xap</XapFilename>
+        <SilverlightManifestTemplate>Properties\AppManifest.xml</SilverlightManifestTemplate>
+        <SilverlightAppEntry>my.test.project.App</SilverlightAppEntry>
+        <ValidateXaml>true</ValidateXaml>
+        <ThrowErrorsInValidation>true</ThrowErrorsInValidation>
+        <MinimumVisualStudioVersion>11.0</MinimumVisualStudioVersion>
+        <BackgroundAgentType />
+    </PropertyGroup>
+    <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+        <DebugSymbols>true</DebugSymbols>
+        <DebugType>full</DebugType>
+        <Optimize>false</Optimize>
+        <OutputPath>Bin\Debug</OutputPath>
+        <DefineConstants>TRACE;DEBUG;SILVERLIGHT;WINDOWS_PHONE;WP8</DefineConstants>
+        <NoStdLib>true</NoStdLib>
+        <NoConfig>true</NoConfig>
+        <ErrorReport>prompt</ErrorReport>
+        <WarningLevel>4</WarningLevel>
+        <Prefer32Bit>false</Prefer32Bit>
+    </PropertyGroup>
+    <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+        <DebugType>pdbonly</DebugType>
+        <Optimize>true</Optimize>
+        <OutputPath>Bin\Release</OutputPath>
+        <DefineConstants>TRACE;SILVERLIGHT;WINDOWS_PHONE;WP8</DefineConstants>
+        <NoStdLib>true</NoStdLib>
+        <NoConfig>true</NoConfig>
+        <ErrorReport>prompt</ErrorReport>
+        <WarningLevel>4</WarningLevel>
+        <Prefer32Bit>false</Prefer32Bit>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
+        <DebugSymbols>true</DebugSymbols>
+        <OutputPath>Bin\x86\Debug</OutputPath>
+        <DefineConstants>DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants>
+        <NoStdLib>true</NoStdLib>
+        <DebugType>full</DebugType>
+        <PlatformTarget>
+        </PlatformTarget>
+        <ErrorReport>prompt</ErrorReport>
+        <CodeAnalysisRuleSet>ManagedMinimumRules.ruleset</CodeAnalysisRuleSet>
+        <Optimize>false</Optimize>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
+        <OutputPath>Bin\x86\Release</OutputPath>
+        <DefineConstants>TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants>
+        <Optimize>true</Optimize>
+        <NoStdLib>true</NoStdLib>
+        <DebugType>pdbonly</DebugType>
+        <PlatformTarget>
+        </PlatformTarget>
+        <ErrorReport>prompt</ErrorReport>
+        <CodeAnalysisRuleSet>ManagedMinimumRules.ruleset</CodeAnalysisRuleSet>
+        <Prefer32Bit>false</Prefer32Bit>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM'">
+        <DebugSymbols>true</DebugSymbols>
+        <OutputPath>Bin\ARM\Debug</OutputPath>
+        <DefineConstants>DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants>
+        <NoStdLib>true</NoStdLib>
+        <DebugType>full</DebugType>
+        <PlatformTarget>
+        </PlatformTarget>
+        <ErrorReport>prompt</ErrorReport>
+        <CodeAnalysisRuleSet>ManagedMinimumRules.ruleset</CodeAnalysisRuleSet>
+        <Prefer32Bit>false</Prefer32Bit>
+        <Optimize>false</Optimize>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM'">
+        <OutputPath>Bin\ARM\Release</OutputPath>
+        <DefineConstants>TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants>
+        <Optimize>true</Optimize>
+        <NoStdLib>true</NoStdLib>
+        <DebugType>pdbonly</DebugType>
+        <PlatformTarget>
+        </PlatformTarget>
+        <ErrorReport>prompt</ErrorReport>
+        <CodeAnalysisRuleSet>ManagedMinimumRules.ruleset</CodeAnalysisRuleSet>
+        <Prefer32Bit>false</Prefer32Bit>
+    </PropertyGroup>
+    <ItemGroup>
+        <Compile Include="App.xaml.cs">
+            <DependentUpon>App.xaml</DependentUpon>
+        </Compile>
+        <Compile Include="MainPage.xaml.cs">
+            <DependentUpon>MainPage.xaml</DependentUpon>
+        </Compile>
+        <Compile Include="Properties\AssemblyInfo.cs" />
+        <Page Include="src\UI\PageTest.xaml">
+            <SubType>Designer</SubType>
+            <Generator>MSBuild:Compile</Generator>
+        </Page>
+    </ItemGroup>
+    <ItemGroup>
+        <Compile Include="Plugins\org.apache.cordova.core.InAppBrowser\InAppBrowser.cs" />
+    </ItemGroup>
+        <ItemGroup>
+        <Compile Include="src\UI\PageTest.xaml.cs">
+            <DependentUpon>PageTest.xaml</DependentUpon>
+        </Compile>
+    </ItemGroup>
+    <ItemGroup>
+        <Reference Include="LibraryTest">
+            <HintPath>lib\LibraryTest.dll</HintPath>
+        </Reference>
+    </ItemGroup>
+    <ItemGroup>
+        <Compile Include="src\FileTest.cs" />
+    </ItemGroup>
+    <ItemGroup>
+        <Content Include="src\Content.img" />
+    </ItemGroup>
+</Project>

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/spec-plugman/projects/wp8/Properties/WMAppManifest.xml
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/wp8/Properties/WMAppManifest.xml b/cordova-lib/spec-plugman/projects/wp8/Properties/WMAppManifest.xml
new file mode 100644
index 0000000..5b37a95
--- /dev/null
+++ b/cordova-lib/spec-plugman/projects/wp8/Properties/WMAppManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Deployment xmlns="http://schemas.microsoft.com/windowsphone/2012/deployment" AppPlatformVersion="8.0">
+  <DefaultLanguage xmlns="" code="en-US" />
+  <Languages xmlns="">
+    <Language code="en-US" />
+  </Languages>
+  <App xmlns="" ProductID="{F3A8197B-6B16-456D-B5F4-DD4F04AC0BEC}" Title="An App" RuntimeType="Silverlight" Version="1.0.0.0" Genre="apps.normal" Author="An App author" BitsPerPixel="32" Description="Apache Cordova for Windows Phone" Publisher="An App" PublisherID="{db093ed5-53b1-45f7-af72-751e8f36ab80}">
+    <IconPath IsRelative="true" IsResource="false">ApplicationIcon.png</IconPath>
+    <Capabilities>
+      <Capability Name="ID_CAP_WEBBROWSERCOMPONENT" />
+    </Capabilities>
+    <Tasks>
+      <DefaultTask Name="_default" NavigationPage="MainPage.xaml" />
+    </Tasks>
+    <Tokens>
+      <PrimaryToken TokenID="An AppToken" TaskName="_default">
+        <TemplateFlip>
+          <SmallImageURI IsResource="false" IsRelative="true">Background.png</SmallImageURI>
+          <Count>0</Count>
+          <BackgroundImageURI IsResource="false" IsRelative="true">Background.png</BackgroundImageURI>
+          <Title>An App</Title>
+          <BackContent></BackContent>
+          <BackBackgroundImageURI></BackBackgroundImageURI>
+          <BackTitle></BackTitle>
+          <LargeBackgroundImageURI></LargeBackgroundImageURI>
+          <LargeBackContent></LargeBackContent>
+          <LargeBackBackgroundImageURI></LargeBackBackgroundImageURI>
+          <DeviceLockImageURI></DeviceLockImageURI>
+          <HasLarge>false</HasLarge>
+        </TemplateFlip>
+      </PrimaryToken>
+    </Tokens>
+    <ScreenResolutions>
+      <ScreenResolution Name="ID_RESOLUTION_WVGA" />
+      <ScreenResolution Name="ID_RESOLUTION_WXGA" />
+      <ScreenResolution Name="ID_RESOLUTION_HD720P" />
+    </ScreenResolutions>
+  </App>
+</Deployment>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/spec-plugman/projects/www-only/.gitkeep
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/www-only/.gitkeep b/cordova-lib/spec-plugman/projects/www-only/.gitkeep
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/spec-plugman/publish.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/publish.spec.js b/cordova-lib/spec-plugman/publish.spec.js
new file mode 100644
index 0000000..3498bd6
--- /dev/null
+++ b/cordova-lib/spec-plugman/publish.spec.js
@@ -0,0 +1,11 @@
+var publish = require('../src/publish'),
+    Q = require('q'),
+    registry = require('../src/registry/registry');
+
+describe('publish', function() {
+    it('should publish a plugin', function() {
+        var sPublish = spyOn(registry, 'publish').andReturn(Q(['/path/to/my/plugin']));
+        publish(new Array('/path/to/myplugin'));
+        expect(sPublish).toHaveBeenCalledWith(['/path/to/myplugin']);
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/spec-plugman/registry/registry.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/registry/registry.spec.js b/cordova-lib/spec-plugman/registry/registry.spec.js
new file mode 100644
index 0000000..7615ee6
--- /dev/null
+++ b/cordova-lib/spec-plugman/registry/registry.spec.js
@@ -0,0 +1,134 @@
+var registry = require('../../src/registry/registry'),
+    manifest = require('../../src/registry/manifest'),
+    fs = require('fs'),
+    path = require('path'),
+    Q = require('q'),
+    shell   = require('shelljs'),
+    os = require('os'),
+    npm = require('npm');
+
+describe('registry', function() {
+    var done;
+    beforeEach(function() {
+        done = false;
+    });
+    function registryPromise(shouldSucceed, f) {
+        waitsFor(function() { return done; }, 'promise never resolved', 500);
+        return f.then(function() {
+          done = true;
+          expect(shouldSucceed).toBe(true);
+        }, function(err) {
+          done = err;
+          expect(shouldSucceed).toBe(false);
+        });
+    }
+
+    describe('manifest', function() {
+        var pluginDir, packageJson, tmp_plugin, tmp_plugin_xml, tmp_package_json;
+        beforeEach(function() {
+            pluginDir = __dirname + '/../plugins/EnginePlugin';
+            tmp_plugin = path.join(os.tmpdir(), 'plugin');
+            tmp_plugin_xml = path.join(tmp_plugin, 'plugin.xml');
+            tmp_package_json = path.join(tmp_plugin, 'package.json');
+            shell.cp('-R', pluginDir+"/*", tmp_plugin);
+        });
+        afterEach(function() {
+            shell.rm('-rf', tmp_plugin);
+        });
+        it('should generate a package.json from a plugin.xml', function() {
+            registryPromise(true, manifest.generatePackageJsonFromPluginXml(tmp_plugin).then(function() {
+                expect(fs.existsSync(tmp_package_json));
+                var packageJson = JSON.parse(fs.readFileSync(tmp_package_json));
+                expect(packageJson.name).toEqual('com.cordova.engine');
+                expect(packageJson.version).toEqual('1.0.0');
+                expect(packageJson.engines).toEqual(
+                    [ { name : 'cordova', version : '>=2.3.0' }, { name : 'cordova-plugman', version : '>=0.10.0' }, { name : 'mega-fun-plugin', version : '>=1.0.0' }, { name : 'mega-boring-plugin', version : '>=3.0.0' } ]);
+            }));
+        });
+        it('should raise an error if name does not follow com.domain.* format', function() {
+            var xmlData = fs.readFileSync(tmp_plugin_xml).toString().replace('id="com.cordova.engine"', 'id="engine"');
+            fs.writeFileSync(tmp_plugin_xml, xmlData);
+            registryPromise(false, manifest.generatePackageJsonFromPluginXml(tmp_plugin));
+        });
+        it('should generate a package.json if name uses org.apache.cordova.* for a whitelisted plugin', function() {
+            var xmlData = fs.readFileSync(tmp_plugin_xml).toString().replace('id="com.cordova.engine"', 'id="org.apache.cordova.camera"');
+            fs.writeFileSync(tmp_plugin_xml, xmlData);
+            registryPromise(true, manifest.generatePackageJsonFromPluginXml(tmp_plugin).then(function() {
+                expect(!fs.existsSync(tmp_package_json));
+            }));
+        });
+        it('should raise an error if name uses org.apache.cordova.* for a non-whitelisted plugin', function() {
+            var xmlData = fs.readFileSync(tmp_plugin_xml).toString().replace('id="com.cordova.engine"', 'id="org.apache.cordova.myinvalidplugin"');
+            fs.writeFileSync(tmp_plugin_xml, xmlData);
+            registryPromise(false, manifest.generatePackageJsonFromPluginXml(tmp_plugin));
+        });
+    });
+    describe('actions', function() {
+        var fakeLoad, fakeNPMCommands;
+
+        beforeEach(function() {
+            done = false;
+            var fakeSettings = {
+                cache: '/some/cache/dir',
+                logstream: 'somelogstream@2313213',
+                userconfig: '/some/config/dir'
+            };
+
+            var fakeNPM = function() {
+                if (arguments.length > 0) {
+                    var cb = arguments[arguments.length-1];
+                    if (cb && typeof cb === 'function') cb(null, true);
+                }
+            };
+
+            registry.settings = fakeSettings;
+            fakeLoad = spyOn(npm, 'load').andCallFake(function(settings, cb) { cb(null, true); });
+
+            fakeNPMCommands = {};
+            ['config', 'adduser', 'cache', 'publish', 'unpublish', 'search'].forEach(function(cmd) {
+                fakeNPMCommands[cmd] = jasmine.createSpy(cmd).andCallFake(fakeNPM);
+            });
+
+            npm.commands = fakeNPMCommands;
+        });
+        it('should run config', function() {
+            var params = ['set', 'registry', 'http://registry.cordova.io'];
+            registryPromise(true, registry.config(params).then(function() {
+                expect(fakeLoad).toHaveBeenCalledWith(registry.settings, jasmine.any(Function));
+                expect(fakeNPMCommands.config).toHaveBeenCalledWith(params, jasmine.any(Function));
+            }));
+        });
+        it('should run adduser', function() {
+            registryPromise(true, registry.adduser(null).then(function() {
+                expect(fakeLoad).toHaveBeenCalledWith(registry.settings, jasmine.any(Function));
+                expect(fakeNPMCommands.adduser).toHaveBeenCalledWith(null, jasmine.any(Function));
+            }));
+        });
+        it('should run publish', function() {
+            var params = [__dirname + '/../plugins/DummyPlugin'];
+            var spyGenerate = spyOn(manifest, 'generatePackageJsonFromPluginXml').andReturn(Q());
+            var spyUnlink = spyOn(fs, 'unlink');
+            registryPromise(true, registry.publish(params).then(function() {
+                expect(fakeLoad).toHaveBeenCalledWith(registry.settings, jasmine.any(Function));
+                expect(spyGenerate).toHaveBeenCalledWith(params[0]);
+                expect(fakeNPMCommands.publish).toHaveBeenCalledWith(params, jasmine.any(Function));
+                expect(spyUnlink).toHaveBeenCalledWith(path.resolve(params[0], 'package.json'));
+            }));
+        });
+        it('should run unpublish', function() {
+            var params = ['dummyplugin@0.6.0'];
+            registryPromise(true, registry.unpublish(params).then(function() {
+                expect(fakeLoad).toHaveBeenCalledWith(registry.settings, jasmine.any(Function));
+                expect(fakeNPMCommands.unpublish).toHaveBeenCalledWith(params, jasmine.any(Function));
+                expect(fakeNPMCommands.cache).toHaveBeenCalledWith(['clean'], jasmine.any(Function));
+            }));
+        });
+        it('should run search', function() {
+            var params = ['dummyplugin', 'plugin'];
+            registryPromise(true, registry.search(params).then(function() {
+                expect(fakeLoad).toHaveBeenCalledWith(registry.settings, jasmine.any(Function));
+                expect(fakeNPMCommands.search).toHaveBeenCalledWith(params, true, jasmine.any(Function));
+            }));
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/spec-plugman/search.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/search.spec.js b/cordova-lib/spec-plugman/search.spec.js
new file mode 100644
index 0000000..4955d2d
--- /dev/null
+++ b/cordova-lib/spec-plugman/search.spec.js
@@ -0,0 +1,11 @@
+var search = require('../src/search'),
+    Q = require('q'),
+    registry = require('../src/registry/registry');
+
+describe('search', function() {
+    it('should search a plugin', function() {
+        var sSearch = spyOn(registry, 'search').andReturn(Q());
+        search(new Array('myplugin', 'keyword'));
+        expect(sSearch).toHaveBeenCalledWith(['myplugin', 'keyword']);
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/spec-plugman/uninstall.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/uninstall.spec.js b/cordova-lib/spec-plugman/uninstall.spec.js
new file mode 100644
index 0000000..a8c2b97
--- /dev/null
+++ b/cordova-lib/spec-plugman/uninstall.spec.js
@@ -0,0 +1,289 @@
+var uninstall = require('../src/uninstall'),
+    install = require('../src/install'),
+    actions = require('../src/util/action-stack'),
+    config_changes = require('../src/util/config-changes'),
+    events  = require('../src/events'),
+    plugman = require('../plugman'),
+    common  = require('./common'),
+    fs      = require('fs'),
+    path    = require('path'),
+    shell   = require('shelljs'),
+    Q       = require('q'),
+    spec    = __dirname,
+    done    = false,
+    srcProject = path.join(spec, 'projects', 'android_uninstall'),
+    project = path.join(spec, 'projects', 'android_uninstall.test'),
+    project2 = path.join(spec, 'projects', 'android_uninstall.test2'),
+
+    plugins_dir = path.join(spec, 'plugins'),
+    plugins_install_dir = path.join(project, 'cordova', 'plugins'),
+    plugins_install_dir2 = path.join(project2, 'cordova', 'plugins'),
+
+    plugins = {
+        'DummyPlugin' : path.join(plugins_dir, 'DummyPlugin'),
+        'A' : path.join(plugins_dir, 'dependencies', 'A'),
+        'C' : path.join(plugins_dir, 'dependencies', 'C')
+    },
+    promise,
+    dummy_id = 'com.phonegap.plugins.dummyplugin';
+
+function uninstallPromise(f) {
+    return f.then(function() { done = true; }, function(err) { done = err; });
+}
+
+describe('start', function() {
+
+    it('start', function() {
+        shell.rm('-rf', project);
+        shell.rm('-rf', project2);
+        shell.cp('-R', path.join(srcProject, '*'), project);
+        shell.cp('-R', path.join(srcProject, '*'), project2);
+
+        done = false;
+        promise = Q()
+        .then(
+            function(){ return install('android', project, plugins['DummyPlugin']) }
+        ).then(
+            function(){ return install('android', project, plugins['A']) }
+        ).then(
+            function(){ return install('android', project2, plugins['C']) }
+        ).then(
+            function(){ return install('android', project2, plugins['A']) }
+        ).then(
+            function(){ done = true; }
+        );
+        waitsFor(function() { return done; }, 'promise never resolved', 500);
+    });
+});
+
+describe('uninstallPlatform', function() {
+    var proc, prepare, actions_push, add_to_queue, c_a, rm;
+    var fsWrite;
+
+    var plat_common = require('../src/platforms/common');
+
+    beforeEach(function() {
+        proc = spyOn(actions.prototype, 'process').andReturn(Q());
+        actions_push = spyOn(actions.prototype, 'push');
+        c_a = spyOn(actions.prototype, 'createAction');
+        prepare = spyOn(plugman, 'prepare');
+        fsWrite = spyOn(fs, 'writeFileSync').andReturn(true);
+        rm = spyOn(shell, 'rm').andReturn(true);
+        spyOn(shell, 'cp').andReturn(true);
+        add_to_queue = spyOn(config_changes, 'add_uninstalled_plugin_to_prepare_queue');
+        done = false;
+    });
+    describe('success', function() {
+        it('should call prepare after a successful uninstall', function() {
+            runs(function() {
+                uninstallPromise(uninstall.uninstallPlatform('android', project, dummy_id));
+            });
+            waitsFor(function() { return done; }, 'promise never resolved', 200);
+            runs(function() {
+                expect(prepare).toHaveBeenCalled();
+            });
+        });
+        it('should call the config-changes module\'s add_uninstalled_plugin_to_prepare_queue method after processing an install', function() {
+            runs(function() {
+                uninstallPromise(uninstall.uninstallPlatform('android', project, dummy_id));
+            });
+            waitsFor(function() { return done; }, 'promise never resolved', 200);
+            runs(function() {
+                expect(add_to_queue).toHaveBeenCalledWith(plugins_install_dir, dummy_id, 'android', true);
+            });
+        });
+        it('should queue up actions as appropriate for that plugin and call process on the action stack', function() {
+            runs(function() {
+                uninstallPromise(uninstall.uninstallPlatform('android', project, dummy_id));
+            });
+            waitsFor(function() { return done; }, 'promise never resolved', 200);
+            runs(function() {
+                expect(actions_push.calls.length).toEqual(5);
+                expect(proc).toHaveBeenCalled();
+            });
+        });
+
+        describe('with dependencies', function() {
+            var emit;
+            beforeEach(function() {
+                emit = spyOn(events, 'emit');
+            });
+            it('should uninstall "dangling" dependencies', function() {
+                runs(function() {
+                    uninstallPromise(uninstall.uninstallPlatform('android', project, 'A'));
+                });
+                waitsFor(function() { return done; }, 'promise never resolved', 200);
+                runs(function() {
+                    expect(emit).toHaveBeenCalledWith('log', 'Uninstalling 2 dependent plugins.');
+                });
+            });
+        });
+    });
+
+    describe('failure', function() {
+        it('should throw if platform is unrecognized', function() {
+            runs(function() {
+                uninstallPromise( uninstall.uninstallPlatform('atari', project, 'SomePlugin') );
+            });
+            waitsFor(function() { return done; }, 'promise never resolved', 200);
+            runs(function() {
+                expect(''+done).toContain('atari not supported.');
+            });
+        });
+        it('should throw if plugin is missing', function() {
+            runs(function() {
+                uninstallPromise( uninstall.uninstallPlatform('android', project, 'SomePluginThatDoesntExist') );
+            });
+            waitsFor(function() { return done; }, 'promise never resolved', 200);
+            runs(function() {
+                expect(''+done).toContain('Plugin "SomePluginThatDoesntExist" not found. Already uninstalled?');
+            });
+        });
+    });
+});
+
+describe('uninstallPlugin', function() {
+    var rm, fsWrite, rmstack = [], emit;
+
+    beforeEach(function() {
+        fsWrite = spyOn(fs, 'writeFileSync').andReturn(true);
+        rm = spyOn(shell, 'rm').andCallFake(function(f,p) { rmstack.push(p); return true});
+        rmstack = [];
+        emit = spyOn(events, 'emit');
+        done = false;
+    });
+    describe('with dependencies', function() {
+
+        it('should delete all dependent plugins', function() {
+            runs(function() {
+                uninstallPromise( uninstall.uninstallPlugin('A', plugins_install_dir) );
+            });
+            waitsFor(function() { return done; }, 'promise never resolved', 200);
+            runs(function() {
+                var del = common.spy.getDeleted(emit);
+
+                expect(del).toEqual([
+                    'Deleted "C"',
+                    'Deleted "D"',
+                    'Deleted "A"'
+                ]);
+            });
+        });
+
+        it("should fail if plugin is a required dependency", function() {
+            runs(function() {
+                uninstallPromise( uninstall.uninstallPlugin('C', plugins_install_dir) );
+            });
+            waitsFor(function() { return done; }, 'promise never resolved', 200);
+            runs(function() {
+                expect(done.message).toBe('"C" is required by (A) and cannot be removed (hint: use -f or --force)');
+            });
+        });
+
+        it("allow forcefully removing a plugin", function() {
+            runs(function() {
+                uninstallPromise( uninstall.uninstallPlugin('C', plugins_install_dir, {force: true}) );
+            });
+            waitsFor(function() { return done; }, 'promise never resolved', 200);
+            runs(function() {
+                expect(done).toBe(true);
+                var del = common.spy.getDeleted(emit);
+                expect(del).toEqual(['Deleted "C"']);
+            });
+        });
+
+        it("never remove top level plugins if they are a dependency", function() {
+            runs(function() {
+                uninstallPromise( uninstall.uninstallPlugin('A', plugins_install_dir2) );
+            });
+            waitsFor(function() { return done; }, 'promise never resolved', 200);
+            runs(function() {
+                var del = common.spy.getDeleted(emit);
+
+                expect(del).toEqual([
+                    'Deleted "D"',
+                    'Deleted "A"'
+                ]);
+            });
+        });
+    });
+});
+
+describe('uninstall', function() {
+    var fsWrite, rm, add_to_queue;
+
+    beforeEach(function() {
+        fsWrite = spyOn(fs, 'writeFileSync').andReturn(true);
+        rm = spyOn(shell, 'rm').andReturn(true);
+        add_to_queue = spyOn(config_changes, 'add_uninstalled_plugin_to_prepare_queue');
+        done = false;
+    });
+    describe('success', function() {
+        it('should call the config-changes module\'s add_uninstalled_plugin_to_prepare_queue method after processing an install', function() {
+            runs(function() {
+                uninstallPromise( uninstall('android', project, plugins['DummyPlugin']) );
+            });
+            waitsFor(function() { return done; }, 'promise never resolved', 200);
+            runs(function() {
+                expect(add_to_queue).toHaveBeenCalledWith(plugins_install_dir, dummy_id, 'android', true);
+            });
+        });
+    });
+
+    describe('failure', function() {
+        it('should throw if platform is unrecognized', function() {
+            runs(function() {
+                uninstallPromise(uninstall('atari', project, 'SomePlugin'));
+            });
+            waitsFor(function() { return done; }, 'promise never resolved', 200);
+            runs(function() {
+                expect(''+done).toContain('atari not supported.');
+            });
+        });
+        it('should throw if plugin is missing', function() {
+            runs(function() {
+                uninstallPromise(uninstall('android', project, 'SomePluginThatDoesntExist'));
+            });
+            waitsFor(function() { return done; }, 'promise never resolved', 200);
+            runs(function() {
+                expect(''+done).toContain('Plugin "SomePluginThatDoesntExist" not found. Already uninstalled?');
+            });
+        });
+    });
+});
+
+describe('end', function() {
+
+    it('end', function() {
+        done = false;
+
+        promise.then(
+            function(){
+                return uninstall('android', project, plugins['DummyPlugin'])
+            }
+        ).then(
+            function(){
+                // Fails... A depends on
+                return uninstall('android', project, plugins['C'])
+            }
+        ).fail(
+            function(err) {
+                expect(err.message).toBe("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);
+            shell.rm('-rf', project2);
+            done = true;
+        });
+
+        waitsFor(function() { return done; }, 'promise never resolved', 500);
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/spec-plugman/unpublish.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/unpublish.spec.js b/cordova-lib/spec-plugman/unpublish.spec.js
new file mode 100644
index 0000000..944f640
--- /dev/null
+++ b/cordova-lib/spec-plugman/unpublish.spec.js
@@ -0,0 +1,11 @@
+var unpublish = require('../src/unpublish'),
+    Q = require('q'),
+    registry = require('../src/registry/registry');
+
+describe('unpublish', function() {
+    it('should unpublish a plugin', function() {
+        var sUnpublish = spyOn(registry, 'unpublish').andReturn(Q());
+        unpublish(new Array('myplugin@0.0.1'));
+        expect(sUnpublish).toHaveBeenCalledWith(['myplugin@0.0.1']);
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/spec-plugman/util/action-stack.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/util/action-stack.spec.js b/cordova-lib/spec-plugman/util/action-stack.spec.js
new file mode 100644
index 0000000..3aa732f
--- /dev/null
+++ b/cordova-lib/spec-plugman/util/action-stack.spec.js
@@ -0,0 +1,58 @@
+var action_stack = require('../../src/util/action-stack'),
+    ios = require('../../src/platforms/ios');
+
+describe('action-stack', function() {
+    var stack;
+    beforeEach(function() {
+        stack = new action_stack();
+    });
+    describe('processing of actions', function() {
+        it('should process actions one at a time until all are done', function() {
+            var first_spy = jasmine.createSpy();
+            var first_args = [1];
+            var second_spy = jasmine.createSpy();
+            var second_args = [2];
+            var third_spy = jasmine.createSpy();
+            var third_args = [3];
+            stack.push(stack.createAction(first_spy, first_args, function(){}, []));
+            stack.push(stack.createAction(second_spy, second_args, function(){}, []));
+            stack.push(stack.createAction(third_spy, third_args, function(){}, []));
+            stack.process('android', 'blah');
+            expect(first_spy).toHaveBeenCalledWith(first_args[0]);
+            expect(second_spy).toHaveBeenCalledWith(second_args[0]);
+            expect(third_spy).toHaveBeenCalledWith(third_args[0]);
+        });
+        it('should revert processed actions if an exception occurs', function() {
+            spyOn(console, 'log');
+            var first_spy = jasmine.createSpy();
+            var first_args = [1];
+            var first_reverter = jasmine.createSpy();
+            var first_reverter_args = [true];
+            var process_err = new Error('process_err');
+            var second_spy = jasmine.createSpy().andCallFake(function() {
+                throw process_err;
+            });
+            var second_args = [2];
+            var third_spy = jasmine.createSpy();
+            var third_args = [3];
+            stack.push(stack.createAction(first_spy, first_args, first_reverter, first_reverter_args));
+            stack.push(stack.createAction(second_spy, second_args, function(){}, []));
+            stack.push(stack.createAction(third_spy, third_args, function(){}, []));
+            // process should throw
+            var error;
+            runs(function() {
+                stack.process('android', 'blah').fail(function(err) { error = err; });
+            });
+            waitsFor(function(){ return error; }, 'process promise never resolved', 500);
+            runs(function() {
+                expect(error).toEqual(process_err);
+                // first two actions should have been called, but not the third
+                expect(first_spy).toHaveBeenCalledWith(first_args[0]);
+                expect(second_spy).toHaveBeenCalledWith(second_args[0]);
+                expect(third_spy).not.toHaveBeenCalledWith(third_args[0]);
+                // first reverter should have been called after second action exploded
+                expect(first_reverter).toHaveBeenCalledWith(first_reverter_args[0]);
+            });
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/spec-plugman/util/config-changes.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/util/config-changes.spec.js b/cordova-lib/spec-plugman/util/config-changes.spec.js
new file mode 100644
index 0000000..9d93d72
--- /dev/null
+++ b/cordova-lib/spec-plugman/util/config-changes.spec.js
@@ -0,0 +1,449 @@
+/* jshint node:true, sub:true, indent:4  */
+/* global jasmine, describe, beforeEach, afterEach, it, spyOn, expect */
+
+var configChanges = require('../../src/util/config-changes'),
+    xml_helpers = require('../../src/util/xml-helpers'),
+    ios_parser = require('../../src/platforms/ios'),
+    fs      = require('fs'),
+    os      = require('osenv'),
+    plugman = require('../../plugman'),
+    events  = require('../../src/events'),
+    et      = require('elementtree'),
+    path    = require('path'),
+    plist = require('plist-with-patches'),
+    shell   = require('shelljs'),
+    xcode = require('xcode'),
+    temp    = path.join(os.tmpdir(), 'plugman'),
+    dummyplugin = path.join(__dirname, '..', 'plugins', 'DummyPlugin'),
+    cbplugin = path.join(__dirname, '..', 'plugins', 'ChildBrowser'),
+    childrenplugin = path.join(__dirname, '..', 'plugins', 'multiple-children'),
+    shareddepsplugin = path.join(__dirname, '..', 'plugins', 'shared-deps-multi-child'),
+    configplugin = path.join(__dirname, '..', 'plugins', 'ConfigTestPlugin'),
+    varplugin = path.join(__dirname, '..', 'plugins', 'VariablePlugin'),
+    android_two_project = path.join(__dirname, '..', 'projects', 'android_two', '*'),
+    android_two_no_perms_project = path.join(__dirname, '..', 'projects', 'android_two_no_perms', '*'),
+    ios_plist_project = path.join(__dirname, '..', 'projects', 'ios-plist', '*'),
+    ios_config_xml = path.join(__dirname, '..', 'projects', 'ios-config-xml', '*'),
+    plugins_dir = path.join(temp, 'cordova', 'plugins');
+
+// TODO: dont do fs so much
+
+var dummy_xml = new et.ElementTree(et.XML(fs.readFileSync(path.join(dummyplugin, 'plugin.xml'), 'utf-8')));
+
+function innerXML(xmltext) {
+    return xmltext.replace(/^<[\w\s\-=\/"\.]+>/, '').replace(/<\/[\w\s\-=\/"\.]+>$/,'');
+}
+
+describe('config-changes module', function() {
+    beforeEach(function() {
+        shell.mkdir('-p', temp);
+        shell.mkdir('-p', plugins_dir);
+    });
+    afterEach(function() {
+        shell.rm('-rf', temp);
+        ios_parser.purgeProjectFileCache(temp);
+    });
+
+    describe('queue methods', function() {
+        describe('add_installed_plugin_to_prepare_queue', function() {
+            it('should call get_platform_json method', function() {
+                var spy = spyOn(configChanges, 'get_platform_json').andReturn({
+                    prepare_queue:{
+                        installed:[],
+                        uninstalled:[]
+                    }
+                });
+                configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'PooPlugin', 'android', {});
+                expect(spy).toHaveBeenCalledWith(plugins_dir, 'android');
+            });
+            it('should append specified plugin to platform.json', function() {
+                configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'PooPlugin', 'android', {});
+                var json = configChanges.get_platform_json(plugins_dir, 'android');
+                expect(json.prepare_queue.installed[0].plugin).toEqual('PooPlugin');
+                expect(json.prepare_queue.installed[0].vars).toEqual({});
+            });
+            it('should append specified plugin with any variables to platform.json', function() {
+                configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'PooPlugin', 'android', {'dude':'man'});
+                var json = configChanges.get_platform_json(plugins_dir, 'android');
+                expect(json.prepare_queue.installed[0].plugin).toEqual('PooPlugin');
+                expect(json.prepare_queue.installed[0].vars).toEqual({'dude':'man'});
+            });
+            it('should call save_platform_json with updated config', function() {
+                var spy = spyOn(configChanges, 'save_platform_json');
+                configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'PooPlugin', 'android', {});
+                var config = spy.mostRecentCall.args[0];
+                expect(config.prepare_queue.installed[0].plugin).toEqual('PooPlugin');
+            });
+        });
+
+        describe('add_uninstalled_plugin_to_prepare_queue', function() {
+            beforeEach(function() {
+                shell.cp('-rf', dummyplugin, plugins_dir);
+            });
+
+            it('should call get_platform_json method', function() {
+                var spy = spyOn(configChanges, 'get_platform_json').andReturn({
+                    prepare_queue:{
+                        installed:[],
+                        uninstalled:[]
+                    }
+                });
+                configChanges.add_uninstalled_plugin_to_prepare_queue(plugins_dir, 'DummyPlugin', 'android');
+                expect(spy).toHaveBeenCalledWith(plugins_dir, 'android');
+            });
+            it('should append specified plugin to platform.json', function() {
+                configChanges.add_uninstalled_plugin_to_prepare_queue(plugins_dir, 'DummyPlugin', 'android');
+                var json = configChanges.get_platform_json(plugins_dir, 'android');
+                expect(json.prepare_queue.uninstalled[0].plugin).toEqual('DummyPlugin');
+                expect(json.prepare_queue.uninstalled[0].id).toEqual('com.phonegap.plugins.dummyplugin');
+            });
+            it('should call save_platform_json with updated config', function() {
+                var spy = spyOn(configChanges, 'save_platform_json');
+                configChanges.add_uninstalled_plugin_to_prepare_queue(plugins_dir, 'DummyPlugin', 'android', {});
+                var config = spy.mostRecentCall.args[0];
+                expect(config.prepare_queue.uninstalled[0].plugin).toEqual('DummyPlugin');
+                expect(config.prepare_queue.uninstalled[0].id).toEqual('com.phonegap.plugins.dummyplugin');
+            });
+        });
+    });
+
+    describe('get_platform_json method', function() {
+        it('should return an empty config json object if file doesn\'t exist', function() {
+            var filepath = path.join(plugins_dir, 'android.json');
+            var cfg = configChanges.get_platform_json(plugins_dir, 'android');
+            expect(cfg).toBeDefined();
+            expect(cfg.prepare_queue).toBeDefined();
+            expect(cfg.config_munge).toBeDefined();
+            expect(cfg.installed_plugins).toBeDefined();
+        });
+        it('should return the json file if it exists', function() {
+            var filepath = path.join(plugins_dir, 'android.json');
+            var json = {
+                prepare_queue: {installed: [], uninstalled: []},
+                config_munge: {files: {"some_file": {parents: {"some_parent": [{"xml": "some_change", "count": 1}]}}}},
+                installed_plugins: {}};
+            fs.writeFileSync(filepath, JSON.stringify(json), 'utf-8');
+            var cfg = configChanges.get_platform_json(plugins_dir, 'android');
+            expect(JSON.stringify(json)).toEqual(JSON.stringify(cfg));
+        });
+    });
+
+    describe('save_platform_json method', function() {
+        it('should write out specified json', function() {
+            var filepath = path.join(plugins_dir, 'android.json');
+            var cfg = {poop:true};
+            configChanges.save_platform_json(cfg, plugins_dir, 'android');
+            expect(fs.existsSync(filepath)).toBe(true);
+            expect(JSON.parse(fs.readFileSync(filepath, 'utf-8'))).toEqual(cfg);
+        });
+    });
+
+    describe('generate_plugin_config_munge method', function() {
+        describe('for android projects', function() {
+            beforeEach(function() {
+                shell.cp('-rf', android_two_project, temp);
+            });
+            it('should return a flat config hierarchy for simple, one-off config changes', function() {
+                var xml;
+                var munger = new configChanges.PlatformMunger('android', temp, 'unused');
+                var munge = munger.generate_plugin_config_munge(dummyplugin, {});
+                expect(munge.files['AndroidManifest.xml']).toBeDefined();
+                expect(munge.files['AndroidManifest.xml'].parents['/manifest/application']).toBeDefined();
+                xml = (new et.ElementTree(dummy_xml.find('./platform[@name="android"]/config-file[@target="AndroidManifest.xml"]'))).write({xml_declaration:false});
+                xml = innerXML(xml);
+                expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest/application', xml).count).toEqual(1);
+                expect(munge.files['res/xml/plugins.xml']).toBeDefined();
+                expect(munge.files['res/xml/plugins.xml'].parents['/plugins']).toBeDefined();
+                xml = (new et.ElementTree(dummy_xml.find('./platform[@name="android"]/config-file[@target="res/xml/plugins.xml"]'))).write({xml_declaration:false});
+                xml = innerXML(xml);
+                expect(configChanges.get_munge_change(munge, 'res/xml/plugins.xml', '/plugins', xml).count).toEqual(1);
+                expect(munge.files['res/xml/config.xml']).toBeDefined();
+                expect(munge.files['res/xml/config.xml'].parents['/cordova/plugins']).toBeDefined();
+                xml = (new et.ElementTree(dummy_xml.find('./platform[@name="android"]/config-file[@target="res/xml/config.xml"]'))).write({xml_declaration:false});
+                xml = innerXML(xml);
+                expect(configChanges.get_munge_change(munge, 'res/xml/config.xml', '/cordova/plugins', xml).count).toEqual(1);
+            });
+            it('should split out multiple children of config-file elements into individual leaves', function() {
+                var munger = new configChanges.PlatformMunger('android', temp, 'unused');
+                var munge = munger.generate_plugin_config_munge(childrenplugin, {});
+                expect(munge.files['AndroidManifest.xml']).toBeDefined();
+                expect(munge.files['AndroidManifest.xml'].parents['/manifest']).toBeDefined();
+                expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />')).toBeDefined();
+                expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />')).toBeDefined();
+                expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<uses-permission android:name="android.permission.READ_PHONE_STATE" />')).toBeDefined();
+                expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<uses-permission android:name="android.permission.INTERNET" />')).toBeDefined();
+                expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<uses-permission android:name="android.permission.GET_ACCOUNTS" />')).toBeDefined();
+                expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<uses-permission android:name="android.permission.WAKE_LOCK" />')).toBeDefined();
+                expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<permission android:name="com.alunny.childapp.permission.C2D_MESSAGE" android:protectionLevel="signature" />')).toBeDefined();
+                expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<uses-permission android:name="com.alunny.childapp.permission.C2D_MESSAGE" />')).toBeDefined();
+                expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />')).toBeDefined();
+            });
+            it('should not use xml comments as config munge leaves', function() {
+                var munger = new configChanges.PlatformMunger('android', temp, 'unused');
+                var munge = munger.generate_plugin_config_munge(childrenplugin, {});
+                expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<!--library-->')).not.toBeDefined();
+                expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<!-- GCM connects to Google Services. -->')).not.toBeDefined();
+            });
+            it('should increment config hierarchy leaves if different config-file elements target the same file + selector + xml', function() {
+                var munger = new configChanges.PlatformMunger('android', temp, 'unused');
+                var munge = munger.generate_plugin_config_munge(configplugin, {});
+                expect(configChanges.get_munge_change(munge, 'res/xml/config.xml', '/widget', '<poop />').count).toEqual(2);
+            });
+            it('should take into account interpolation variables', function() {
+                var munger = new configChanges.PlatformMunger('android', temp, 'unused');
+                var munge = munger.generate_plugin_config_munge(childrenplugin, {PACKAGE_NAME:'ca.filmaj.plugins'});
+                expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<uses-permission android:name="ca.filmaj.plugins.permission.C2D_MESSAGE" />')).toBeDefined();
+            });
+            it('should create munges for platform-agnostic config.xml changes', function() {
+                var munger = new configChanges.PlatformMunger('android', temp, 'unused');
+                var munge = munger.generate_plugin_config_munge(dummyplugin, {});
+                expect(configChanges.get_munge_change(munge, 'config.xml', '/*', '<access origin="build.phonegap.com" />')).toBeDefined();
+                expect(configChanges.get_munge_change(munge, 'config.xml', '/*', '<access origin="s3.amazonaws.com" />')).toBeDefined();
+            });
+            it('should automatically add on app java identifier as PACKAGE_NAME variable for android config munges', function() {
+                shell.cp('-rf', android_two_project, temp);
+                var munger = new configChanges.PlatformMunger('android', temp, 'unused');
+                var munge = munger.generate_plugin_config_munge(varplugin, {});
+                var expected_xml = '<package>com.alunny.childapp</package>';
+                expect(configChanges.get_munge_change(munge, 'AndroidManifest.xml', '/manifest', expected_xml)).toBeDefined();
+            });
+        });
+
+        describe('for ios projects', function() {
+            beforeEach(function() {
+                shell.cp('-rf', ios_config_xml, temp);
+            });
+            it('should automatically add on ios bundle identifier as PACKAGE_NAME variable for ios config munges', function() {
+                var munger = new configChanges.PlatformMunger('ios', temp, 'unused');
+                var munge = munger.generate_plugin_config_munge(varplugin, {});
+                var expected_xml = '<cfbundleid>com.example.friendstring</cfbundleid>';
+                expect(configChanges.get_munge_change(munge, 'config.xml', '/widget', expected_xml)).toBeDefined();
+            });
+            it('should special case framework elements for ios', function() {
+                var munger = new configChanges.PlatformMunger('ios', temp, 'unused');
+                var munge = munger.generate_plugin_config_munge(cbplugin, {});
+                expect(munge.files['framework']).toBeDefined();
+                expect(configChanges.get_munge_change(munge, 'framework', 'libsqlite3.dylib', 'false')).toBeDefined();
+                expect(configChanges.get_munge_change(munge, 'framework', 'social.framework', 'true')).toBeDefined();
+                expect(configChanges.get_munge_change(munge, 'framework', 'music.framework', 'false')).toBeDefined();
+                expect(munge.files['framework'].parents['Custom.framework']).not.toBeDefined();
+            });
+        });
+    });
+
+    describe('processing of plugins (via process method)', function() {
+        beforeEach(function() {
+            shell.cp('-rf', dummyplugin, plugins_dir);
+        });
+        it('should generate config munges for queued plugins', function() {
+            shell.cp('-rf', android_two_project, temp);
+            var cfg = configChanges.get_platform_json(plugins_dir, 'android');
+            cfg.prepare_queue.installed = [{'plugin':'DummyPlugin', 'vars':{}}];
+            configChanges.save_platform_json(cfg, plugins_dir, 'android');
+            var munger = new configChanges.PlatformMunger('android', temp, plugins_dir);
+            var spy = spyOn(munger, 'generate_plugin_config_munge').andReturn({});
+            munger.process();
+            expect(spy).toHaveBeenCalledWith(path.join(plugins_dir, 'DummyPlugin'), {});
+        });
+        it('should get a reference to existing config munge by calling get_platform_json', function() {
+            shell.cp('-rf', android_two_project, temp);
+            var spy = spyOn(configChanges, 'get_platform_json').andReturn({
+                prepare_queue:{
+                    installed:[],
+                    uninstalled:[]
+                },
+                config_munge:{}
+            });
+            configChanges.process(plugins_dir, temp, 'android');
+            expect(spy).toHaveBeenCalledWith(plugins_dir, 'android');
+        });
+        describe(': installation', function() {
+            describe('of xml config files', function() {
+                beforeEach(function() {
+                    shell.cp('-rf', android_two_project, temp);
+                });
+                it('should call graftXML for every new config munge it introduces (every leaf in config munge that does not exist)', function() {
+                    var cfg = configChanges.get_platform_json(plugins_dir, 'android');
+                    cfg.prepare_queue.installed = [{'plugin':'DummyPlugin', 'vars':{}}];
+                    configChanges.save_platform_json(cfg, plugins_dir, 'android');
+
+                    var spy = spyOn(xml_helpers, 'graftXML').andReturn(true);
+
+                    var manifest_doc = new et.ElementTree(et.XML(fs.readFileSync(path.join(temp, 'AndroidManifest.xml'), 'utf-8')));
+                    var munge = dummy_xml.find('./platform[@name="android"]/config-file[@target="AndroidManifest.xml"]');
+                    configChanges.process(plugins_dir, temp, 'android');
+                    expect(spy.calls.length).toEqual(4);
+                    expect(spy.argsForCall[0][2]).toEqual('/*');
+                    expect(spy.argsForCall[1][2]).toEqual('/*');
+                    expect(spy.argsForCall[2][2]).toEqual('/manifest/application');
+                    expect(spy.argsForCall[3][2]).toEqual('/cordova/plugins');
+                });
+                it('should not call graftXML for a config munge that already exists from another plugin', function() {
+                    shell.cp('-rf', configplugin, plugins_dir);
+                    configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'ConfigTestPlugin', 'android', {});
+
+                    var spy = spyOn(xml_helpers, 'graftXML').andReturn(true);
+                    configChanges.process(plugins_dir, temp, 'android');
+                    expect(spy.calls.length).toEqual(1);
+                });
+                it('should not call graftXML for a config munge targeting a config file that does not exist', function() {
+                    configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'DummyPlugin', 'android', {});
+
+                    var spy = spyOn(fs, 'readFileSync').andCallThrough();
+
+                    configChanges.process(plugins_dir, temp, 'android');
+                    expect(spy).not.toHaveBeenCalledWith(path.join(temp, 'res', 'xml', 'plugins.xml'), 'utf-8');
+                });
+            });
+            describe('of plist config files', function() {
+                var xcode_add, xcode_rm;
+                it('should write empty string nodes with no whitespace', function() {
+                    shell.cp('-rf', ios_config_xml, temp);
+                    shell.cp('-rf', varplugin, plugins_dir);
+                    configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'VariablePlugin', 'ios', {});
+                    configChanges.process(plugins_dir, temp, 'ios');
+                    expect(fs.readFileSync(path.join(temp, 'SampleApp', 'SampleApp-Info.plist'), 'utf-8')).toMatch(/<key>APluginNode<\/key>\n    <string><\/string>/m);
+                });
+            });
+            describe('of pbxproject framework files', function() {
+                var xcode_add, xcode_rm;
+                beforeEach(function() {
+                    shell.cp('-rf', ios_config_xml, temp);
+                    shell.cp('-rf', cbplugin, plugins_dir);
+                    xcode_add = spyOn(xcode.project.prototype, 'addFramework').andCallThrough();
+                });
+                it('should call into xcode.addFramework if plugin has <framework> file defined and is ios',function() {
+                    configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'ChildBrowser', 'ios', {});
+                    configChanges.process(plugins_dir, temp, 'ios');
+                    expect(xcode_add).toHaveBeenCalledWith('libsqlite3.dylib', {weak:false});
+                    expect(xcode_add).toHaveBeenCalledWith('social.framework', {weak:true});
+                    expect(xcode_add).toHaveBeenCalledWith('music.framework', {weak:false});
+                    expect(xcode_add).not.toHaveBeenCalledWith('Custom.framework');
+                });
+            });
+            it('should resolve wildcard config-file targets to the project, if applicable', function() {
+                shell.cp('-rf', ios_config_xml, temp);
+                shell.cp('-rf', cbplugin, plugins_dir);
+                configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'ChildBrowser', 'ios', {});
+                var spy = spyOn(fs, 'readFileSync').andCallThrough();
+
+                configChanges.process(plugins_dir, temp, 'ios');
+                expect(spy).toHaveBeenCalledWith(path.join(temp, 'SampleApp', 'SampleApp-Info.plist').replace(/\\/g, '/'), 'utf8');
+            });
+            it('should move successfully installed plugins from queue to installed plugins section, and include/retain vars if applicable', function() {
+                shell.cp('-rf', android_two_project, temp);
+                shell.cp('-rf', varplugin, plugins_dir);
+                configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'VariablePlugin', 'android', {"API_KEY":"hi"}, true);
+
+                configChanges.process(plugins_dir, temp, 'android');
+
+                var cfg = configChanges.get_platform_json(plugins_dir, 'android');
+                expect(cfg.prepare_queue.installed.length).toEqual(0);
+                expect(cfg.installed_plugins['com.adobe.vars']).toBeDefined();
+                expect(cfg.installed_plugins['com.adobe.vars']['API_KEY']).toEqual('hi');
+            });
+            it('should save changes to global config munge after completing an install', function() {
+                shell.cp('-rf', android_two_project, temp);
+                shell.cp('-rf', varplugin, plugins_dir);
+                configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'VariablePlugin', 'android', {"API_KEY":"hi"});
+
+                var spy = spyOn(configChanges, 'save_platform_json');
+                configChanges.process(plugins_dir, temp, 'android');
+                expect(spy).toHaveBeenCalled();
+            });
+        });
+
+        describe(': uninstallation', function() {
+            it('should call pruneXML for every config munge it completely removes from the app (every leaf that is decremented to 0)', function() {
+                shell.cp('-rf', android_two_project, temp);
+                // Run through an "install"
+                configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'DummyPlugin', 'android', {});
+                configChanges.process(plugins_dir, temp, 'android');
+
+                // Now set up an uninstall and make sure prunexml is called properly
+                configChanges.add_uninstalled_plugin_to_prepare_queue(plugins_dir, 'DummyPlugin', 'android');
+                var spy = spyOn(xml_helpers, 'pruneXML').andReturn(true);
+                configChanges.process(plugins_dir, temp, 'android');
+                expect(spy.calls.length).toEqual(4);
+                expect(spy.argsForCall[0][2]).toEqual('/*');
+                expect(spy.argsForCall[1][2]).toEqual('/*');
+                expect(spy.argsForCall[2][2]).toEqual('/manifest/application');
+                expect(spy.argsForCall[3][2]).toEqual('/cordova/plugins');
+            });
+            it('should generate a config munge that interpolates variables into config changes, if applicable', function() {
+                shell.cp('-rf', android_two_project, temp);
+                shell.cp('-rf', varplugin, plugins_dir);
+                // Run through an "install"
+                configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'VariablePlugin', 'android', {"API_KEY":"canucks"});
+                var munger = new configChanges.PlatformMunger('android', temp, plugins_dir);
+                munger.process();
+
+                // Now set up an uninstall and make sure prunexml is called properly
+                configChanges.add_uninstalled_plugin_to_prepare_queue(plugins_dir, 'VariablePlugin', 'android');
+                var spy = spyOn(munger, 'generate_plugin_config_munge').andReturn({});
+                munger.process();
+                var munge_params = spy.mostRecentCall.args;
+                expect(munge_params[0]).toEqual(path.join(plugins_dir, 'VariablePlugin'));
+                expect(munge_params[1]['API_KEY']).toEqual('canucks');
+            });
+            it('should not call pruneXML for a config munge that another plugin depends on', function() {
+                shell.cp('-rf', android_two_no_perms_project, temp);
+                shell.cp('-rf', childrenplugin, plugins_dir);
+                shell.cp('-rf', shareddepsplugin, plugins_dir);
+
+                // Run through and "install" two plugins (they share a permission for INTERNET)
+                configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'multiple-children', 'android', {});
+                configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'shared-deps-multi-child', 'android', {});
+                configChanges.process(plugins_dir, temp, 'android');
+
+                // Now set up an uninstall for multi-child plugin
+                configChanges.add_uninstalled_plugin_to_prepare_queue(plugins_dir, 'multiple-children', 'android');
+                configChanges.process(plugins_dir, temp, 'android');
+                var am_xml = new et.ElementTree(et.XML(fs.readFileSync(path.join(temp, 'AndroidManifest.xml'), 'utf-8')));
+                var permission = am_xml.find('./uses-permission');
+                expect(permission).toBeDefined();
+                expect(permission.attrib['android:name']).toEqual('android.permission.INTERNET');
+            });
+            it('should not call pruneXML for a config munge targeting a config file that does not exist', function() {
+                shell.cp('-rf', android_two_project, temp);
+                // install a plugin
+                configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'DummyPlugin', 'android', {});
+                configChanges.process(plugins_dir, temp, 'android');
+                // set up an uninstall for the same plugin
+                configChanges.add_uninstalled_plugin_to_prepare_queue(plugins_dir, 'DummyPlugin', 'android', {});
+
+                var spy = spyOn(fs, 'readFileSync').andCallThrough();
+                configChanges.process(plugins_dir, temp, 'android');
+
+                expect(spy).not.toHaveBeenCalledWith(path.join(temp, 'res', 'xml', 'plugins.xml'), 'utf-8');
+            });
+            it('should remove uninstalled plugins from installed plugins list', function() {
+                shell.cp('-rf', android_two_project, temp);
+                shell.cp('-rf', varplugin, plugins_dir);
+                // install the var plugin
+                configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'VariablePlugin', 'android', {"API_KEY":"eat my shorts"});
+                configChanges.process(plugins_dir, temp, 'android');
+                // queue up an uninstall for the same plugin
+                configChanges.add_uninstalled_plugin_to_prepare_queue(plugins_dir, 'VariablePlugin', 'android');
+                configChanges.process(plugins_dir, temp, 'android');
+
+                var cfg = configChanges.get_platform_json(plugins_dir, 'android');
+                expect(cfg.prepare_queue.uninstalled.length).toEqual(0);
+                expect(cfg.installed_plugins['com.adobe.vars']).not.toBeDefined();
+            });
+            it('should save changes to global config munge after completing an uninstall', function() {
+                shell.cp('-rf', android_two_project, temp);
+                shell.cp('-rf', varplugin, plugins_dir);
+                // install a plugin
+                configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'VariablePlugin', 'android', {"API_KEY":"eat my shorts"});
+                configChanges.process(plugins_dir, temp, 'android');
+                // set up an uninstall for the plugin
+                configChanges.add_uninstalled_plugin_to_prepare_queue(plugins_dir, 'VariablePlugin', 'android');
+
+                var spy = spyOn(configChanges, 'save_platform_json');
+                configChanges.process(plugins_dir, temp, 'android');
+                expect(spy).toHaveBeenCalled();
+            });
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/spec-plugman/util/csproj.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/util/csproj.spec.js b/cordova-lib/spec-plugman/util/csproj.spec.js
new file mode 100644
index 0000000..c506c38
--- /dev/null
+++ b/cordova-lib/spec-plugman/util/csproj.spec.js
@@ -0,0 +1,97 @@
+var csproj  = require('../../src/util/csproj'),
+    path    = require('path'),
+    os      = require('osenv'),
+    et      = require('elementtree'),
+    fs      = require('fs'),
+    xml_helpers = require('../../src/util/xml-helpers');
+
+var wp7_project     = path.join(__dirname, '..', 'projects', 'wp7'),
+    wp8_project     = path.join(__dirname, '..', 'projects', 'wp8'),
+    temp            = path.join(os.tmpdir(), 'plugman'),
+    example1_csproj  = path.join(wp7_project, 'CordovaAppProj.csproj'),
+    example2_csproj  = path.join(wp8_project, 'CordovaAppProj.csproj'),
+    wpcsproj        = path.join(__dirname, '..', 'plugins', 'WPcsproj');
+
+describe('csproj', function() {
+    it('should throw if passed in an invalid xml file path ref', function() {
+        expect(function() {
+            new csproj('blahblah');
+        }).toThrow();
+    });
+    it('should successfully parse a valid csproj file into an xml document', function() {
+        var doc;
+        expect(function() {
+            doc = new csproj(example1_csproj);
+        }).not.toThrow();
+        expect(doc.xml.getroot()).toBeDefined();
+    });
+
+    describe('write method', function() {
+
+    });
+
+    describe('source file', function() {
+
+        var test_csproj;
+        var page_test   = path.join('src', 'UI', 'PageTest.xaml');
+        var page_test_cs = path.join('src', 'UI', 'PageTest.xaml.cs');
+        var lib_test    = path.join('lib', 'LibraryTest.dll');
+        var file_test   = path.join('src', 'FileTest.cs');
+        var content_test   = path.join('src', 'Content.img');
+
+        describe('add method', function() {
+            var test_csproj = new csproj(example1_csproj);
+            it('should properly add .xaml files', function() {
+                test_csproj.addSourceFile(page_test);
+                expect(test_csproj.xml.getroot().find('.//Page[@Include="src\\UI\\PageTest.xaml"]')).toBeTruthy();
+                expect(test_csproj.xml.getroot().find('.//Page[@Include="src\\UI\\PageTest.xaml"]/Generator').text).toEqual('MSBuild:Compile');
+                expect(test_csproj.xml.getroot().find('.//Page[@Include="src\\UI\\PageTest.xaml"]/SubType').text).toEqual('Designer');
+            });
+            it('should properly add .xaml.cs files', function() {
+                test_csproj.addSourceFile(page_test_cs);
+                expect(test_csproj.xml.getroot().find('.//Compile[@Include="src\\UI\\PageTest.xaml.cs"]')).toBeTruthy();
+                expect(test_csproj.xml.getroot().find('.//Compile[@Include="src\\UI\\PageTest.xaml.cs"]/DependentUpon').text).toEqual('PageTest.xaml');
+            });
+            it('should properly add .cs files', function() {
+                test_csproj.addSourceFile(file_test);
+                expect(test_csproj.xml.getroot().find('.//Compile[@Include="src\\FileTest.cs"]')).toBeTruthy();
+            });
+            it('should properly add content files', function() {
+                test_csproj.addSourceFile(content_test);
+                expect(test_csproj.xml.getroot().find('.//Content[@Include="src\\Content.img"]')).toBeTruthy();
+            });
+        });
+
+        describe('remove method', function() {
+            var test_csproj = new csproj(example2_csproj);
+            it('should properly remove .xaml pages', function() {
+                test_csproj.removeSourceFile(page_test);
+                expect(test_csproj.xml.getroot().find('.//Page[@Include="src\\UI\\PageTest.xaml"]')).toBeFalsy();
+            });
+            it('should properly remove .xaml.cs files', function() {
+                test_csproj.removeSourceFile(page_test_cs);
+                expect(test_csproj.xml.getroot().find('.//Compile[@Include="src\\UI\\PageTest.xaml.cs"]')).toBeFalsy();
+            });
+            it('should properly remove .cs files', function() {
+                test_csproj.removeSourceFile(file_test);
+                expect(test_csproj.xml.getroot().find('.//Compile[@Include="src\\FileTest.cs"]')).toBeFalsy();
+            });
+            it('should properly remove content files', function() {
+                test_csproj.removeSourceFile(content_test);
+                expect(test_csproj.xml.getroot().find('.//Content[@Include="src\\Content.img"]')).toBeFalsy();
+            });
+            it('should remove all empty ItemGroup\'s', function() {
+                test_csproj.removeSourceFile(page_test);
+                test_csproj.removeSourceFile(page_test_cs);
+                test_csproj.removeSourceFile(lib_test);
+                test_csproj.removeSourceFile(file_test);
+                var item_groups = test_csproj.xml.findall('ItemGroup');
+                for (var i = 0, l = item_groups.length; i < l; i++) {
+                    var group = item_groups[i];
+                    expect(group._children.length).toBeGreaterThan(0);
+                }
+            })
+
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/0318d8cd/cordova-lib/spec-plugman/util/dependencies.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/util/dependencies.spec.js b/cordova-lib/spec-plugman/util/dependencies.spec.js
new file mode 100644
index 0000000..bbc7111
--- /dev/null
+++ b/cordova-lib/spec-plugman/util/dependencies.spec.js
@@ -0,0 +1,41 @@
+var dependencies = require('../../src/util/dependencies'),
+    xml_helpers = require('../../src/util/xml-helpers'),
+    path = require('path'),
+    config = require('../../src/util/config-changes');
+
+describe('dependency module', function() {
+    describe('generate_dependency_info method', function() {
+        it('should return a list of top-level plugins based on what is inside a platform.json file', function() {
+            var tlps = {
+                "hello":"",
+                "isitme":"",
+                "yourelookingfor":""
+            };
+            spyOn(xml_helpers, 'parseElementtreeSync').andReturn({findall:function(){}});
+            var spy = spyOn(config, 'get_platform_json').andReturn({
+                installed_plugins:tlps,
+                dependent_plugins:[]
+            });
+            var obj = dependencies.generate_dependency_info('some dir');
+            expect(obj.top_level_plugins).toEqual(Object.keys(tlps));
+        });
+        it('should return a dependency graph for the plugins', function() {
+            var tlps = {
+                "A":"",
+                "B":""
+            };
+            var deps = {
+                "C":"",
+                "D":"",
+                "E":""
+            };
+            var spy = spyOn(config, 'get_platform_json').andReturn({
+                installed_plugins:tlps,
+                dependent_plugins:[]
+            });
+            var obj = dependencies.generate_dependency_info(path.join(__dirname, '..', 'plugins', 'dependencies'), 'android');
+            expect(obj.graph.getChain('A')).toEqual(['C','D']);
+            expect(obj.graph.getChain('B')).toEqual(['D', 'E']);
+        });
+    });
+});