You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by er...@apache.org on 2019/02/15 02:39:55 UTC

[cordova-electron] branch master updated: Implement Electron Custom App Icons Functionality (#13)

This is an automated email from the ASF dual-hosted git repository.

erisu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cordova-electron.git


The following commit(s) were added to refs/heads/master by this push:
     new 31f6619  Implement Electron Custom App Icons Functionality (#13)
31f6619 is described below

commit 31f6619f8bdc060ece266a13336997a66f436372
Author: Gedas Gardauskas <ge...@gmail.com>
AuthorDate: Fri Feb 15 11:39:51 2019 +0900

    Implement Electron Custom App Icons Functionality (#13)
---
 DOCUMENTATION.md                                   |  45 +-
 bin/lib/create.js                                  |   3 +
 bin/templates/build-res/installer.png              | Bin 0 -> 39830 bytes
 bin/templates/cordova/Api.js                       |   1 +
 bin/templates/cordova/lib/build.js                 |   4 +-
 bin/templates/cordova/lib/build/base.json          |   4 +-
 bin/templates/cordova/lib/build/darwin.json        |   1 +
 bin/templates/cordova/lib/build/linux.json         |   1 +
 bin/templates/cordova/lib/build/win32.json         |   1 +
 bin/templates/cordova/lib/prepare.js               | 216 ++++-
 bin/templates/project/main.js                      |  23 +-
 bin/templates/project/www/icons/icon.png           | Bin 0 -> 39830 bytes
 package.json                                       |   1 +
 .../unit/templates/cordova/lib/prepare.spec.js     | 893 +++++++++++++++++++++
 14 files changed, 1174 insertions(+), 19 deletions(-)

diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md
index f6c41b2..28dafd6 100644
--- a/DOCUMENTATION.md
+++ b/DOCUMENTATION.md
@@ -30,6 +30,7 @@ Electron is a framework that uses web technologies (HTML, CSS, and JS) to build
     - [Create a Project](#create-a-project)
     - [Preview a Project](#preview-a-project)
     - [Build a Project](#build-a-project)
+  - [Customizing the Application's Icon](#customizing-the-applications-icon)
   - [Customizing the Application's Main Process](#customizing-the-applications-main-process)
     - [Window Appearance (BrowserWindow)](#window-appearance-browserwindow)
       - [How to set the window default size?](#how-to-set-the-window-default-size)
@@ -107,18 +108,48 @@ $ cordova build electron --debug
 $ cordova build electron --release
 ```
 
-<!-- @todo Update prepare.
-Customizing the Application's Icon
+## Customizing the Application's Icon
 
-In the `config.xml` file, there should be an Electron platform node with the icons defined. The example is seen as below:
+Customized icon(s) can be declared with the `<icon>` element(s) in the `config.xml` file. There are two types of icons that can be defined, the application icon and the package installer icon. These icons should be defined in the Electron's platform node `<platform name="electron">`.
+
+One icon can be used for the application and installer, but this icon should be at least **512x512** pixels to work across all operating systems.
+
+_Notice: If a customized icon is not provided, the Apache Cordova default icons are used._
+
+_Notice: macOS does not display custom icons when using `cordova run`. It defaults to the Electron's icon._
+
+```xml
+<platform name="electron">
+  <icon src="res/electron/icon.png" />
+</platform>
 ```
+
+You can supply unique icons for the application and installer by setting the `target` attribute. As mentioned above, the installer image should be **512x512** pixels to work across all platforms.
+
+```xml
 <platform name="electron">
-  <icon src="res/electron/icon.ico" />
-  <icon src="res/electron/icon.icns" />
-  <icon src="res/electron/32x32.png" />
+  <icon src="res/electron/app.png" target="app" />
+  <icon src="res/electron/installer.png" target="installer" />
+</platform>
+```
+
+For devices that support high-DPI resolutions, such as Apple's Retina display, you can create a set of images with the same base filename but suffix with its multiplier.
+
+For example, if the base image's filename `icon.png` and is the standard resolution, then `icon@2x.png` will be treated as a high-resolution image that with a DPI doubled from the base.
+
+* icon.png (256px x 256px)
+* icon@2x.png (512px x 512px)
+
+If you want to support displays with different DPI densities at the same time, you can put images with different sizes in the same folder and use the filename without DPI suffixes. For example:
+
+```xml
+<platform name="electron">
+  <icon src="res/electron/icon.png" />
+  <icon src="res/electron/icon@1.5x.png" />
+  <icon src="res/electron/icon@2x.png" />
+  <icon src="res/electron/icon@4x.png" target="installer" />
 </platform>
 ```
--->
 
 ## Customizing the Application's Main Process
 
diff --git a/bin/lib/create.js b/bin/lib/create.js
index 0122f6d..b028647 100644
--- a/bin/lib/create.js
+++ b/bin/lib/create.js
@@ -53,6 +53,9 @@ module.exports.createProject = (project_path, package_name, project_name, option
     // Make sure that the platform directory is created if missing.
     fs.ensureDirSync(project_path);
 
+    // copy templates/build-res directory ( recursive )
+    fs.copySync(path.join(ROOT, 'bin/templates/build-res'), path.join(project_path, 'build-res'), { overwrite: false });
+
     // copy templates/cordova directory ( recursive )
     fs.copySync(path.join(ROOT, 'bin/templates/cordova'), path.join(project_path, 'cordova'), { overwrite: false });
 
diff --git a/bin/templates/build-res/installer.png b/bin/templates/build-res/installer.png
new file mode 100644
index 0000000..c9465f3
Binary files /dev/null and b/bin/templates/build-res/installer.png differ
diff --git a/bin/templates/cordova/Api.js b/bin/templates/cordova/Api.js
index d16d9eb..3260259 100644
--- a/bin/templates/cordova/Api.js
+++ b/bin/templates/cordova/Api.js
@@ -68,6 +68,7 @@ class Api {
             configXml: path.join(this.root, 'config.xml'),
             defaultConfigXml: path.join(this.root, 'cordova/defaults.xml'),
             build: path.join(this.root, 'build'),
+            buildRes: path.join(this.root, 'build-res'),
             cache: path.join(this.root, 'cache'),
             // NOTE: Due to platformApi spec we need to return relative paths here
             cordovaJs: 'bin/templates/project/assets/www/cordova.js',
diff --git a/bin/templates/cordova/lib/build.js b/bin/templates/cordova/lib/build.js
index 190c3e0..f005859 100644
--- a/bin/templates/cordova/lib/build.js
+++ b/bin/templates/cordova/lib/build.js
@@ -249,8 +249,10 @@ class ElectronBuilder {
             APP_AUTHOR: packageJson.author,
             APP_ID: packageJson.name,
             APP_TITLE: packageJson.displayName,
-            APP_WWW_DIR: this.api.locations.www,
+            APP_INSTALLER_ICON: 'installer.png',
             APP_BUILD_DIR: this.api.locations.build,
+            APP_BUILD_RES_DIR: this.api.locations.buildRes,
+            APP_WWW_DIR: this.api.locations.www,
             BUILD_TYPE: this.isDevelopment ? 'development' : 'distribution'
         };
 
diff --git a/bin/templates/cordova/lib/build/base.json b/bin/templates/cordova/lib/build/base.json
index 27b671b..de00a29 100644
--- a/bin/templates/cordova/lib/build/base.json
+++ b/bin/templates/cordova/lib/build/base.json
@@ -5,13 +5,13 @@
 
     "directories": {
       "app": "${APP_WWW_DIR}",
+      "buildResources": "${APP_BUILD_RES_DIR}",
       "output": "${APP_BUILD_DIR}"
     },
-
     "electronVersion": "4.0.1",
 
     "electronDownload": {
       "version": "4.0.1"
     }
   }
-}
+}
\ No newline at end of file
diff --git a/bin/templates/cordova/lib/build/darwin.json b/bin/templates/cordova/lib/build/darwin.json
index bd608f8..31b85e5 100644
--- a/bin/templates/cordova/lib/build/darwin.json
+++ b/bin/templates/cordova/lib/build/darwin.json
@@ -3,6 +3,7 @@
   "config": {
     "mac": {
       "type": "${BUILD_TYPE}",
+      "icon": "${APP_INSTALLER_ICON}",
       "target": [
         {
           "target": "dmg",
diff --git a/bin/templates/cordova/lib/build/linux.json b/bin/templates/cordova/lib/build/linux.json
index 0bdeb9e..9154c99 100644
--- a/bin/templates/cordova/lib/build/linux.json
+++ b/bin/templates/cordova/lib/build/linux.json
@@ -3,6 +3,7 @@
   "config": {
     "linux": {
       "maintainer": "${APP_AUTHOR}",
+      "icon": "${APP_INSTALLER_ICON}",
       "target": [
         {
           "target": "tar.gz",
diff --git a/bin/templates/cordova/lib/build/win32.json b/bin/templates/cordova/lib/build/win32.json
index 05326f9..956c9a8 100644
--- a/bin/templates/cordova/lib/build/win32.json
+++ b/bin/templates/cordova/lib/build/win32.json
@@ -2,6 +2,7 @@
   "win": [],
   "config": {
     "win": {
+      "icon": "${APP_INSTALLER_ICON}",
       "target": [
         {
           "target": "nsis",
diff --git a/bin/templates/cordova/lib/prepare.js b/bin/templates/cordova/lib/prepare.js
index b6b78bd..f64bd64 100644
--- a/bin/templates/cordova/lib/prepare.js
+++ b/bin/templates/cordova/lib/prepare.js
@@ -17,9 +17,10 @@
     under the License.
 */
 
-const path = require('path');
 const fs = require('fs-extra');
-const { ConfigParser, xmlHelpers } = require('cordova-common');
+const path = require('path');
+const shell = require('shelljs');
+const { ConfigParser, xmlHelpers, events, CordovaError } = require('cordova-common');
 
 module.exports.prepare = function (cordovaProject, options) {
     // First cleanup current config and merge project's one into own
@@ -47,7 +48,9 @@ module.exports.prepare = function (cordovaProject, options) {
     this.config.write();
 
     // Update own www dir with project's www assets and plugins' assets and js-files
-    this.parser.update_www(cordovaProject, options);
+    this.parser.update_www(cordovaProject, this.locations);
+    // Update icons
+    updateIcons(cordovaProject, this.locations);
 
     // Copy or Create manifest.json
     const srcManifestPath = path.join(cordovaProject.locations.www, 'manifest.json');
@@ -243,3 +246,210 @@ class ManifestJson {
         fs.writeFileSync(this.path, JSON.stringify(this.manifest, null, 2), 'utf8');
     }
 }
+
+/**
+ * Update Electron App and Installer icons.
+ */
+function updateIcons (cordovaProject, locations) {
+    const icons = cordovaProject.projectConfig.getIcons('electron');
+
+    // Skip if there are no app defined icons in config.xml
+    if (icons.length === 0) {
+        events.emit('verbose', 'This app does not have icons defined');
+        return;
+    }
+
+    checkIconsAttributes(icons);
+
+    const choosenIcons = prepareIcons(icons);
+    const resourceMap = createResourceMap(cordovaProject, locations, choosenIcons);
+
+    events.emit('verbose', 'Updating icons');
+    copyIcons(cordovaProject.root, resourceMap);
+}
+
+/**
+ * Check if all required attributes are set.
+ */
+function checkIconsAttributes (icons) {
+    let errorMissingAttributes = [];
+    let errorWrongSize = [];
+    let errorMessage = [];
+    icons.forEach(icon => {
+        if (!icon.src) {
+            errorMissingAttributes.push(icon.target ? icon.target : 'size=' + (icon.height || icon.width));
+        }
+        if ((icon.height && icon.width) < 512 && (icon.height && icon.width) !== undefined) {
+            errorWrongSize.push(icon.target === 'installer' ? `for target: ${icon.target}` : `given: width=${icon.width} height=${icon.height}`);
+        }
+    });
+    if (errorMissingAttributes.length > 0) {
+        errorMessage.push('One of the following attributes are set but missing the other for the target: ' + errorMissingAttributes.join(', ') + '. Please ensure that all required attributes are defined.');
+    }
+    if (errorWrongSize.length > 0) {
+        errorMessage.push('Size of icon does not match required size ' + errorWrongSize.join(', ') + '. Please ensure that .png icon for is at least 512x512.');
+    }
+    if (errorMessage.length > 0) {
+        throw new CordovaError(errorMessage.join(' '));
+    }
+}
+
+/**
+ *  Find and select icons for the app and installer.
+ *  Also, set high resolution icons, if provided by a user.
+ */
+function prepareIcons (icons) {
+    let customIcon;
+    let appIcon;
+    let installerIcon;
+
+    // choose one icon for a target
+    const chooseOne = (defaultIcon, icon) => {
+        events.emit('verbose', `Found extra icon for target ${icon.target}: ${defaultIcon.src} and ignoring in favor of ${icon.src}.`);
+
+        defaultIcon = icon;
+
+        return defaultIcon;
+    };
+
+    // find if there is high resolution images that has DPI suffix
+    const highResAndRemainingIcons = findHighResIcons(icons);
+
+    const highResIcons = highResAndRemainingIcons.highResIcons;
+    const remainingIcons = highResAndRemainingIcons.remainingIcons;
+
+    // iterate over remaining icon elements to find the icons for the app and installer
+    for (let i = 0; i < remainingIcons.length; i++) {
+        // if (fs.existsSync(icons[i].src)) {
+        let icon = remainingIcons[i];
+        const size = icon.width || icon.height;
+        icon.extension = path.extname(icon.src);
+
+        switch (icon.target) {
+        case 'app':
+            appIcon = appIcon ? chooseOne(appIcon, icon) : icon;
+            break;
+        case 'installer':
+            installerIcon = installerIcon ? chooseOne(installerIcon, icon) : icon;
+            break;
+        case undefined:
+            if ((size <= 512 || size === undefined) && !Object.keys(highResIcons).length) {
+                customIcon = customIcon ? chooseOne(customIcon, icon) : icon;
+            }
+            break;
+        }
+    }
+
+    return { customIcon, appIcon, installerIcon, highResIcons };
+}
+
+/**
+ *  Find and high resolution icons and return remaining icons,
+ *  unless an icon has a specified target.
+ */
+function findHighResIcons (icons) {
+    // find icons that are not in the highResIcons, unless they have a target set
+    const findRemainingIcons = (icons, highResIcons) => icons.filter(
+        (icon) => (icon.target || (!icon.target && !highResIcons.includes(icon)))
+            ? Object.assign(icon)
+            : false
+    );
+
+    let highResIcons = icons.filter(icon => {
+        if (icon.src.includes('@')) {
+            const extension = path.extname(icon.src);
+            const suffix = icon.src.split('@').pop().slice(0, -extension.length);
+
+            return Object.assign(icon, { suffix, extension });
+        }
+    });
+
+    let remainingIcons = findRemainingIcons(icons, highResIcons);
+    // set normal image that has standard resolution
+    const has1x = highResIcons.find(obj => obj.suffix === '1x');
+    if (!has1x && Object.keys(highResIcons).length) {
+        const highResIcon = highResIcons[Object.keys(highResIcons)[0]];
+        let baseIcon = remainingIcons.find(obj => obj.src === highResIcon.src.split('@')[0] + highResIcon.extension);
+
+        if (!baseIcon) {
+            throw new CordovaError('Base icon for high resolution images was not found.');
+        }
+
+        const extension = path.extname(baseIcon.src);
+        const suffix = '1x';
+
+        baseIcon = Object.assign(baseIcon, { suffix, extension });
+
+        highResIcons.push(baseIcon);
+
+        remainingIcons = findRemainingIcons(icons, highResIcons);
+    }
+
+    return { highResIcons, remainingIcons };
+}
+
+/**
+ * Map selected icons to the approporiate target directory and name.
+ */
+function createResourceMap (cordovaProject, locations, icons) {
+    let resourceMap = [];
+
+    for (const key in icons) {
+        const icon = icons[key];
+
+        if (!icon) {
+            continue;
+        }
+
+        let targetPath;
+        switch (key) {
+        case 'customIcon':
+            // Copy icon for the App
+            targetPath = path.join(locations.www, 'img', `app${icon.extension}`);
+            resourceMap.push(mapIconResources(cordovaProject.root, icon.src, targetPath));
+            // Copy icon for the Installer
+            targetPath = path.join(locations.buildRes, `installer${icon.extension}`);
+            resourceMap.push(mapIconResources(cordovaProject.root, icon.src, targetPath));
+            break;
+        case 'appIcon':
+            targetPath = path.join(locations.www, 'img', `app${icon.extension}`);
+            resourceMap.push(mapIconResources(cordovaProject.root, icon.src, targetPath));
+            break;
+        case 'installerIcon':
+            targetPath = path.join(locations.buildRes, `installer${icon.extension}`);
+            resourceMap.push(mapIconResources(cordovaProject.root, icon.src, targetPath));
+            break;
+        case 'highResIcons':
+            for (const key in icon) {
+                const highResIcon = icon[key];
+                targetPath = path.join(locations.www, 'img', 'icon');
+                targetPath += highResIcon.suffix === '1x' ? highResIcon.extension : `@${highResIcon.suffix}${highResIcon.extension}`;
+                resourceMap.push(mapIconResources(cordovaProject.root, highResIcon.src, targetPath));
+            }
+            break;
+        }
+    }
+    return resourceMap;
+}
+
+/**
+ * Get a map containing resources of a specified name (or directory) to the target directory.
+ */
+function mapIconResources (rootDir, sourcePath, targetPath) {
+    let pathMap = {};
+    shell.ls(path.join(rootDir, sourcePath)).forEach(() => {
+        pathMap[sourcePath] = targetPath;
+    });
+    return pathMap;
+}
+
+/**
+ * Copy icons to the target destination according to the resource map.
+ */
+function copyIcons (rootDir, resourceMap) {
+    resourceMap.forEach(element => {
+        if (Object.keys(element).length) {
+            fs.copySync(path.join(rootDir, Object.keys(element)[0]), Object.values(element)[0]);
+        }
+    });
+}
diff --git a/bin/templates/project/main.js b/bin/templates/project/main.js
index d3f8b36..9312ece 100644
--- a/bin/templates/project/main.js
+++ b/bin/templates/project/main.js
@@ -17,11 +17,9 @@
     under the License.
 */
 
-const electron = require('electron');
-// Module to control application life.
-const app = electron.app;
-// Module to create native browser window.
-const BrowserWindow = electron.BrowserWindow;
+const fs = require('fs');
+// Module to control application life, browser window and tray.
+const { app, BrowserWindow } = require('electron');
 // Electron settings from .json file.
 const cdvElectronSettings = require('./cdv-electron-settings.json');
 
@@ -31,7 +29,20 @@ let mainWindow;
 
 function createWindow () {
     // Create the browser window.
-    mainWindow = new BrowserWindow({ width: 800, height: 600 });
+    let appIcon;
+    if (fs.existsSync(`${__dirname}/img/app.png`)) {
+        appIcon = `${__dirname}/img/app.png`;
+    } else if (fs.existsSync(`${__dirname}/img/icon.png`)) {
+        appIcon = `${__dirname}/img/icon.png`;
+    } else {
+        appIcon = `${__dirname}/img/logo.png`;
+    }
+
+    mainWindow = new BrowserWindow({
+        width: 800,
+        height: 600,
+        icon: appIcon
+    });
 
     // and load the index.html of the app.
     // TODO: possibly get this data from config.xml
diff --git a/bin/templates/project/www/icons/icon.png b/bin/templates/project/www/icons/icon.png
new file mode 100644
index 0000000..c9465f3
Binary files /dev/null and b/bin/templates/project/www/icons/icon.png differ
diff --git a/package.json b/package.json
index b194125..61c6e3d 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
     "eslint-plugin-standard": "^4.0.0",
     "jasmine": "^3.3.1",
     "nyc": "^13.1.0",
+    "rewire": "^4.0.1",
     "tmp": "^0.0.33"
   },
   "author": "Apache Software Foundation",
diff --git a/tests/spec/unit/templates/cordova/lib/prepare.spec.js b/tests/spec/unit/templates/cordova/lib/prepare.spec.js
new file mode 100644
index 0000000..0d8541e
--- /dev/null
+++ b/tests/spec/unit/templates/cordova/lib/prepare.spec.js
@@ -0,0 +1,893 @@
+/*
+    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.
+*/
+
+const rewire = require('rewire');
+const CordovaError = require('cordova-common').CordovaError;
+
+/**
+ * Create a mock item from the getIcon collection with the supplied updated data.
+ *
+ * @param {Object} data Changes to apply to the mock getIcon item
+ */
+function mockGetIconItem (data) {
+    return Object.assign({}, {
+        src: undefined,
+        target: undefined,
+        density: undefined,
+        platform: 'electron',
+        width: undefined,
+        height: undefined,
+        background: undefined,
+        foreground: undefined
+    }, data);
+}
+
+describe('Testing prepare.js:', () => {
+    let prepare;
+
+    // Spies
+    let emitSpy;
+    let updatePathsSpy;
+
+    let cordovaProject;
+    let locations;
+
+    beforeEach(() => {
+        prepare = rewire('../../../../../../bin/templates/cordova/lib/prepare');
+
+        cordovaProject = {
+            root: '/mock',
+            projectConfig: {
+                path: '/mock/config.xml',
+                cdvNamespacePrefix: 'cdv'
+            }
+        };
+
+        locations = {
+            buildRes: '/mock/build-res',
+            www: '/mock/www'
+        };
+
+        emitSpy = jasmine.createSpy('emit');
+        prepare.__set__('events', {
+            emit: emitSpy
+        });
+
+        updatePathsSpy = jasmine.createSpy('updatePaths');
+        prepare.__set__('FileUpdater', {
+            updatePaths: updatePathsSpy
+        });
+    });
+
+    describe('updateIcons method', () => {
+
+        it('should detect no defined icons.', () => {
+            const updateIcons = prepare.__get__('updateIcons');
+
+            cordovaProject.projectConfig.getIcons = () => {
+                return [];
+            };
+
+            updateIcons(cordovaProject, locations);
+
+            // The emit was called
+            expect(emitSpy).toHaveBeenCalled();
+
+            // The emit message was.
+            const actual = emitSpy.calls.argsFor(0)[1];
+            const expected = 'This app does not have icons defined';
+            expect(actual).toEqual(expected);
+        });
+
+        it('should update icons.', () => {
+            const updateIcons = prepare.__get__('updateIcons');
+
+            // create spies
+            const checkIconsAttributesSpy = jasmine.createSpy('checkIconsAttributes');
+            prepare.__set__('checkIconsAttributes', checkIconsAttributesSpy);
+            const prepareIconsSpy = jasmine.createSpy('prepareIcons');
+            prepare.__set__('prepareIcons', prepareIconsSpy);
+            const createResourceMapSpy = jasmine.createSpy('createResourceMap');
+            prepare.__set__('createResourceMap', createResourceMapSpy);
+            const copyIconsSpy = jasmine.createSpy('copyIcons');
+            prepare.__set__('copyIcons', copyIconsSpy);
+
+            cordovaProject.projectConfig.getIcons = () => {
+                const icon = mockGetIconItem({});
+                return [icon];
+            };
+
+            updateIcons(cordovaProject, locations);
+
+            // The emit was called
+            expect(emitSpy).toHaveBeenCalled();
+            expect(checkIconsAttributesSpy).toHaveBeenCalled();
+            expect(prepareIconsSpy).toHaveBeenCalled();
+            expect(createResourceMapSpy).toHaveBeenCalled();
+            expect(copyIconsSpy).toHaveBeenCalled();
+
+            // The emit message was.
+            const actual = emitSpy.calls.argsFor(0)[1];
+            const expected = 'Updating icons';
+            expect(actual).toEqual(expected);
+        });
+    });
+
+    describe('checkIconsAttributes method', () => {
+        let checkIconsAttributes;
+
+        beforeEach(() => {
+            checkIconsAttributes = prepare.__get__('checkIconsAttributes');
+        });
+
+        it('should detect icons with missing src and throw an error with size=undefined in message.', () => {
+            const icons = [
+                mockGetIconItem({
+                    src: ''
+                })
+            ];
+
+            expect(() => {
+                checkIconsAttributes(icons);
+            }).toThrow(
+                new CordovaError('One of the following attributes are set but missing the other for the target: size=undefined. Please ensure that all required attributes are defined.')
+            );
+        });
+
+        it('should detect icons with missing src, but defined size and throw an error with size in message.', () => {
+            const icons = [
+                mockGetIconItem({
+                    src: '',
+                    width: 512,
+                    height: 512
+                })
+            ];
+
+            expect(() => {
+                checkIconsAttributes(icons);
+            }).toThrow(
+                new CordovaError('One of the following attributes are set but missing the other for the target: size=512. Please ensure that all required attributes are defined.')
+            );
+        });
+
+        it('should detect icons with target set, but missing src and throw an error with target in message.', () => {
+            const icons = [
+                mockGetIconItem({
+                    src: '',
+                    target: 'installer'
+                })
+            ];
+
+            expect(() => {
+                checkIconsAttributes(icons);
+            }).toThrow(
+                new CordovaError('One of the following attributes are set but missing the other for the target: installer. Please ensure that all required attributes are defined.')
+            );
+        });
+
+        it('should detect icons with wrong size defined and throw an error with and sizes in message.', () => {
+            const icons = [
+                mockGetIconItem({
+                    src: 'res/electron/cordova_512.png',
+                    height: 512,
+                    width: 256
+                })
+            ];
+
+            expect(() => {
+                checkIconsAttributes(icons);
+            }).toThrow(
+                new CordovaError('Size of icon does not match required size given: width=256 height=512. Please ensure that .png icon for is at least 512x512.')
+            );
+        });
+
+        it('should detect icons with wrong size defined for the installer and throw an error with target and sizes in message.', () => {
+            const icons = [
+                mockGetIconItem({
+                    src: 'res/electron/cordova_512.png',
+                    target: 'installer',
+                    height: 256,
+                    width: 256
+                })
+            ];
+
+            expect(() => {
+                checkIconsAttributes(icons);
+            }).toThrow(
+                new CordovaError('Size of icon does not match required size for target: installer. Please ensure that .png icon for is at least 512x512.')
+            );
+        });
+    });
+
+    describe('prepareIcons method', () => {
+        let prepareIcons;
+
+        beforeEach(() => {
+            prepareIcons = prepare.__get__('prepareIcons');
+        });
+
+        it('should return array of objects with custom icon, when there is only one icon in res folder.', () => {
+            const icons = mockGetIconItem({
+                src: 'res/logo.png',
+                platform: undefined
+            });
+
+            const actual = prepareIcons([icons]);
+            const expected = {
+                customIcon: Object.assign(icons, { extension: '.png' }),
+                appIcon: undefined,
+                installerIcon: undefined,
+                highResIcons: []
+            };
+
+            expect(expected).toEqual(actual);
+        });
+
+        it('should return array of objects with custom icon, when there is only one icon in res/electron folder.', () => {
+            const icons = mockGetIconItem({
+                src: 'res/electron/logo.png'
+            });
+
+            const actual = prepareIcons([icons]);
+            const expected = {
+                customIcon: Object.assign(icons, { extension: '.png' }),
+                appIcon: undefined,
+                installerIcon: undefined,
+                highResIcons: []
+            };
+
+            expect(expected).toEqual(actual);
+        });
+
+        it('should return array of objects with custom icons, when there is only one icon with correct width and height set.', () => {
+            const icons = mockGetIconItem({
+                src: 'res/electron/cordova_512.png',
+                width: 512,
+                height: 512
+            });
+
+            const actual = prepareIcons([icons]);
+            const expected = {
+                customIcon: Object.assign(icons, { extension: '.png' }),
+                appIcon: undefined,
+                installerIcon: undefined,
+                highResIcons: []
+            };
+
+            expect(expected).toEqual(actual);
+        });
+
+        it('should return array of objects with installer icon, when icon is defined for target=installer', () => {
+            const icons = mockGetIconItem({
+                src: 'res/electron/cordova_512.png',
+                target: 'installer',
+                width: 512
+            });
+
+            const actual = prepareIcons([icons]);
+            const expected = {
+                customIcon: undefined,
+                appIcon: undefined,
+                installerIcon: Object.assign(icons, { extension: '.png' }),
+                highResIcons: []
+            };
+
+            expect(expected).toEqual(actual);
+        });
+
+        it('should return array of objects with app and installer icon, when there is one icon with target=app and one with target=installer', () => {
+            const app = mockGetIconItem({
+                src: 'res/electron/cordova.png',
+                target: 'app',
+                width: 512,
+                height: 512
+            });
+            const installer = mockGetIconItem({
+                src: 'res/electron/cordova_512.png',
+                target: 'installer',
+                width: 512,
+                height: 512
+            });
+            const icons = [ app, installer ];
+
+            const actual = prepareIcons(icons);
+            const expected = {
+                customIcon: undefined,
+                appIcon: Object.assign(app, { extension: '.png' }),
+                installerIcon: Object.assign(installer, { extension: '.png' }),
+                highResIcons: []
+            };
+
+            expect(expected).toEqual(actual);
+        });
+
+        it('should return array of objects with app and installer icon, when there more one icon with target=app and more than one with target=installer', () => {
+            const app = mockGetIconItem({
+                src: 'res/electron/cordova.png',
+                target: 'app',
+                width: 512,
+                height: 512
+            });
+            const installer = mockGetIconItem({
+                src: 'res/electron/cordova_512.png',
+                target: 'installer',
+                width: 512,
+                height: 512
+            });
+            const installer2 = mockGetIconItem({
+                src: 'res/electron/cordova_512_extra.png',
+                target: 'installer',
+                width: 512,
+                height: 512
+            });
+            const icons = [ app, installer, installer2 ];
+
+            let actual = prepareIcons(icons);
+            let expected = {
+                customIcon: undefined,
+                appIcon: Object.assign(app, { extension: '.png' }),
+                installerIcon: Object.assign(installer2, { extension: '.png' }),
+                highResIcons: []
+            };
+
+            expect(expected).toEqual(actual);
+
+            // The emit was called
+            expect(emitSpy).toHaveBeenCalled();
+
+            // The emit message was.
+            actual = emitSpy.calls.argsFor(0)[1];
+            expected = 'Found extra icon for target installer: res/electron/cordova_512.png and ignoring in favor of res/electron/cordova_512_extra.png.';
+            expect(actual).toEqual(expected);
+        });
+
+        it('should return array of objects with high resolution icons, if they are defined', () => {
+            const highRes10 = mockGetIconItem({ src: 'res/electron/cordova.png' });
+            const highRes15 = mockGetIconItem({ src: 'res/electron/cordova@1.5x.png' });
+            const highRes20 = mockGetIconItem({ src: 'res/electron/cordova@2x.png' });
+            const highRes40 = mockGetIconItem({ src: 'res/electron/cordova@4x.png' });
+            const highRes80 = mockGetIconItem({ src: 'res/electron/cordova@8x.png' });
+
+            const icons = [ highRes10, highRes15, highRes20, highRes40, highRes80 ];
+
+            const actual = prepareIcons(icons);
+            const expected = {
+                customIcon: undefined,
+                appIcon: undefined,
+                installerIcon: undefined,
+                highResIcons: [
+                    Object.assign(highRes15, { suffix: '1.5x', extension: '.png' }),
+                    Object.assign(highRes20, { suffix: '2x', extension: '.png' }),
+                    Object.assign(highRes40, { suffix: '4x', extension: '.png' }),
+                    Object.assign(highRes80, { suffix: '8x', extension: '.png' }),
+                    Object.assign(highRes10, { suffix: '1x', extension: '.png' })
+                ]
+            };
+
+            expect(expected).toEqual(actual);
+        });
+
+        it('should return array of objects with high resolution icons, if they are defined and an extra icon with target=installer', () => {
+            const installer = mockGetIconItem({
+                src: 'res/electron/cordova_512.png',
+                target: 'installer',
+                width: 512,
+                height: 512
+            });
+            const highRes10 = mockGetIconItem({ src: 'res/electron/cordova.png' });
+            const highRes15 = mockGetIconItem({ src: 'res/electron/cordova@1.5x.png' });
+            const highRes20 = mockGetIconItem({ src: 'res/electron/cordova@2x.png' });
+            const highRes40 = mockGetIconItem({ src: 'res/electron/cordova@4x.png' });
+            const highRes80 = mockGetIconItem({ src: 'res/electron/cordova@8x.png' });
+
+            const icons = [ installer, highRes10, highRes15, highRes20, highRes40, highRes80 ];
+
+            const actual = prepareIcons(icons);
+            const expected = {
+                customIcon: undefined,
+                appIcon: undefined,
+                installerIcon: Object.assign(installer, { extension: '.png' }),
+                highResIcons: [
+                    Object.assign(highRes15, { suffix: '1.5x', extension: '.png' }),
+                    Object.assign(highRes20, { suffix: '2x', extension: '.png' }),
+                    Object.assign(highRes40, { suffix: '4x', extension: '.png' }),
+                    Object.assign(highRes80, { suffix: '8x', extension: '.png' }),
+                    Object.assign(highRes10, { suffix: '1x', extension: '.png' })
+                ]
+            };
+
+            expect(expected).toEqual(actual);
+        });
+    });
+
+    describe('findHighResIcons', () => {
+        let findHighResIcons;
+
+        beforeEach(() => {
+            findHighResIcons = prepare.__get__('findHighResIcons');
+        });
+
+        it('should return array of objects with remaining icons, when there is only one icon in res folder.', () => {
+            const icons = mockGetIconItem({
+                src: 'res/logo.png',
+                platform: undefined
+            });
+
+            const actual = findHighResIcons([icons]);
+            const expected = {
+                highResIcons: [],
+                remainingIcons: [icons]
+            };
+
+            expect(expected).toEqual(actual);
+        });
+
+        it('should return array of objects with remaining icons, when there is only one icon in res/electron folder.', () => {
+            const icons = mockGetIconItem({
+                src: 'res/electron/logo.png'
+            });
+
+            const actual = findHighResIcons([icons]);
+            const expected = {
+                highResIcons: [],
+                remainingIcons: [icons]
+            };
+
+            expect(expected).toEqual(actual);
+        });
+
+        it('should return array of objects with remaining icon, when there is only one icon with correct width and height set.', () => {
+            const icons = mockGetIconItem({
+                src: 'res/electron/cordova_512.png',
+                width: 512,
+                height: 512
+            });
+
+            const actual = findHighResIcons([icons]);
+            const expected = {
+                highResIcons: [],
+                remainingIcons: [icons]
+            };
+
+            expect(expected).toEqual(actual);
+        });
+
+        it('should return array of objects with remaining icon, when icon is defined for target=installer', () => {
+            const icons = mockGetIconItem({
+                src: 'res/electron/cordova_512.png',
+                target: 'installer',
+                width: 512
+            });
+
+            const actual = findHighResIcons([icons]);
+            const expected = {
+                highResIcons: [],
+                remainingIcons: [icons]
+            };
+
+            expect(expected).toEqual(actual);
+        });
+
+        it('should return array of objects with app and installer icon, when there is one icon with target=app and one with target=installer', () => {
+            const app = mockGetIconItem({
+                src: 'res/electron/cordova.png',
+                target: 'app',
+                width: 512,
+                height: 512
+            });
+            const installer = mockGetIconItem({
+                src: 'res/electron/cordova_512.png',
+                target: 'installer',
+                width: 512,
+                height: 512
+            });
+            const icons = [ app, installer ];
+
+            const actual = findHighResIcons(icons);
+            const expected = {
+                highResIcons: [],
+                remainingIcons: icons
+            };
+
+            expect(expected).toEqual(actual);
+        });
+
+        it('should return remainingIcons array of objects with app and installer icon, when there more one icon with target=app and more than one with target=installer', () => {
+            const app = mockGetIconItem({
+                src: 'res/electron/cordova.png',
+                target: 'app',
+                width: 512,
+                height: 512
+            });
+            const installer = mockGetIconItem({
+                src: 'res/electron/cordova_512.png',
+                target: 'installer',
+                width: 512,
+                height: 512
+            });
+            const installer2 = mockGetIconItem({
+                src: 'res/electron/cordova_512_extra.png',
+                target: 'installer',
+                width: 512,
+                height: 512
+            });
+            const icons = [ app, installer, installer2 ];
+
+            const actual = findHighResIcons(icons);
+            const expected = {
+                highResIcons: [],
+                remainingIcons: icons
+            };
+
+            expect(expected).toEqual(actual);
+        });
+
+        it('should throw Cordova Error when there is no base icon', () => {
+            const highRes15 = mockGetIconItem({ src: 'res/electron/cordova@1.5x.png' });
+            const highRes20 = mockGetIconItem({ src: 'res/electron/cordova@2x.png' });
+            const highRes40 = mockGetIconItem({ src: 'res/electron/cordova@4x.png' });
+            const highRes80 = mockGetIconItem({ src: 'res/electron/cordova@8x.png' });
+
+            const icons = [ highRes15, highRes20, highRes40, highRes80 ];
+
+            expect(() => {
+                findHighResIcons(icons);
+            }).toThrow(
+                new CordovaError('Base icon for high resolution images was not found.')
+            );
+        });
+
+        it('should return array of objects with high resolution icons, if they are defined', () => {
+            const highRes10 = mockGetIconItem({ src: 'res/electron/cordova.png' });
+            const highRes15 = mockGetIconItem({ src: 'res/electron/cordova@1.5x.png' });
+            const highRes20 = mockGetIconItem({ src: 'res/electron/cordova@2x.png' });
+            const highRes40 = mockGetIconItem({ src: 'res/electron/cordova@4x.png' });
+            const highRes80 = mockGetIconItem({ src: 'res/electron/cordova@8x.png' });
+
+            const icons = [ highRes10, highRes15, highRes20, highRes40, highRes80 ];
+
+            const actual = findHighResIcons(icons);
+            const expected = {
+                highResIcons: [
+                    Object.assign(highRes15, { suffix: '1.5x', extension: '.png' }),
+                    Object.assign(highRes20, { suffix: '2x', extension: '.png' }),
+                    Object.assign(highRes40, { suffix: '4x', extension: '.png' }),
+                    Object.assign(highRes80, { suffix: '8x', extension: '.png' }),
+                    Object.assign(highRes10, { suffix: '1x', extension: '.png' })
+                ],
+                remainingIcons: []
+            };
+
+            expect(expected).toEqual(actual);
+        });
+
+        it('should return array of objects with high resolution icons, if they are defined and an extra icon with target=installer', () => {
+            const installer = mockGetIconItem({
+                src: 'res/electron/cordova_512.png',
+                target: 'installer',
+                width: 512,
+                height: 512
+            });
+            const highRes10 = mockGetIconItem({ src: 'res/electron/cordova.png' });
+            const highRes15 = mockGetIconItem({ src: 'res/electron/cordova@1.5x.png' });
+            const highRes20 = mockGetIconItem({ src: 'res/electron/cordova@2x.png' });
+            const highRes40 = mockGetIconItem({ src: 'res/electron/cordova@4x.png' });
+            const highRes80 = mockGetIconItem({ src: 'res/electron/cordova@8x.png' });
+
+            const icons = [ installer, highRes10, highRes15, highRes20, highRes40, highRes80 ];
+
+            const actual = findHighResIcons(icons);
+            const expected = {
+                highResIcons: [
+                    Object.assign(highRes15, { suffix: '1.5x', extension: '.png' }),
+                    Object.assign(highRes20, { suffix: '2x', extension: '.png' }),
+                    Object.assign(highRes40, { suffix: '4x', extension: '.png' }),
+                    Object.assign(highRes80, { suffix: '8x', extension: '.png' }),
+                    Object.assign(highRes10, { suffix: '1x', extension: '.png' })
+                ],
+                remainingIcons: [installer]
+            };
+
+            expect(expected).toEqual(actual);
+        });
+
+        it('should return array of objects with high resolution icons, if they are defined and remaining icon with target=installer', () => {
+            const highRes10 = mockGetIconItem({ src: 'res/electron/cordova.png' });
+            const highRes15 = mockGetIconItem({ src: 'res/electron/cordova@1.5x.png' });
+            const highRes20 = mockGetIconItem({ src: 'res/electron/cordova@2x.png' });
+            const highRes40 = mockGetIconItem({ src: 'res/electron/cordova@4x.png' });
+            const highRes80 = mockGetIconItem({ src: 'res/electron/cordova@8x.png', target: 'installer' });
+
+            const icons = [ highRes10, highRes15, highRes20, highRes40, highRes80 ];
+
+            const actual = findHighResIcons(icons);
+            const expected = {
+                highResIcons: [
+                    Object.assign(highRes15, { suffix: '1.5x', extension: '.png' }),
+                    Object.assign(highRes20, { suffix: '2x', extension: '.png' }),
+                    Object.assign(highRes40, { suffix: '4x', extension: '.png' }),
+                    Object.assign(highRes80, { suffix: '8x', extension: '.png' }),
+                    Object.assign(highRes10, { suffix: '1x', extension: '.png' })
+                ],
+                remainingIcons: [Object.assign(highRes80, { suffix: '8x', extension: '.png' })]
+            };
+
+            expect(expected).toEqual(actual);
+        });
+    });
+
+    describe('createResourceMap method', () => {
+        let createResourceMap;
+        let shellLsSpy;
+
+        beforeEach(() => {
+            createResourceMap = prepare.__get__('createResourceMap');
+            shellLsSpy = prepare.__get__('mapIconResources');
+
+            shellLsSpy = jasmine.createSpy('ls').and.returnValue([true]);
+            prepare.__set__('shell', { ls: shellLsSpy });
+        });
+
+        it('should map custom icon to installer and app icon locations', () => {
+
+            const icon = mockGetIconItem({
+                src: 'res/logo.png',
+                platform: undefined
+            });
+            let data = {
+                customIcon: Object.assign(icon, { extension: '.png' }),
+                appIcon: undefined,
+                installerIcon: undefined,
+                highResIcons: []
+            };
+
+            const actual = createResourceMap(cordovaProject, locations, data);
+
+            expect(shellLsSpy).toHaveBeenCalled();
+
+            const expected = [
+                { 'res/logo.png': '/mock/www/img/app.png' },
+                { 'res/logo.png': '/mock/build-res/installer.png' }
+            ];
+
+            expect(expected).toEqual(actual);
+        });
+
+        it('should map installer icon to appoporiate location', () => {
+            const icons = mockGetIconItem({
+                src: 'res/electron/cordova_512.png',
+                target: 'installer',
+                width: 512
+            });
+            const data = {
+                customIcon: undefined,
+                appIcon: undefined,
+                installerIcon: Object.assign(icons, { extension: '.png' }),
+                highResIcons: []
+            };
+
+            const actual = createResourceMap(cordovaProject, locations, data);
+
+            expect(shellLsSpy).toHaveBeenCalled();
+
+            const expected = [
+                { 'res/electron/cordova_512.png': '/mock/build-res/installer.png' }
+            ];
+
+            expect(expected).toEqual(actual);
+        });
+
+        it('should map installer and app icon to appoporiate location', () => {
+            const app = mockGetIconItem({
+                src: 'res/electron/cordova.png',
+                target: 'app',
+                width: 512,
+                height: 512
+            });
+            const installer = mockGetIconItem({
+                src: 'res/electron/cordova_512.png',
+                target: 'installer',
+                width: 512,
+                height: 512
+            });
+            const data = {
+                customIcon: undefined,
+                appIcon: Object.assign(app, { extension: '.png' }),
+                installerIcon: Object.assign(installer, { extension: '.png' }),
+                highResIcons: []
+            };
+
+            const actual = createResourceMap(cordovaProject, locations, data);
+
+            expect(shellLsSpy).toHaveBeenCalled();
+
+            const expected = [
+                { 'res/electron/cordova.png': '/mock/www/img/app.png' },
+                { 'res/electron/cordova_512.png': '/mock/build-res/installer.png' }
+            ];
+
+            expect(expected).toEqual(actual);
+        });
+
+        it('should map high resolution icons to appoporiate location', () => {
+            const highRes10 = mockGetIconItem({ src: 'res/electron/cordova.png' });
+            const highRes15 = mockGetIconItem({ src: 'res/electron/cordova@1.5x.png' });
+            const highRes20 = mockGetIconItem({ src: 'res/electron/cordova@2x.png' });
+            const highRes40 = mockGetIconItem({ src: 'res/electron/cordova@4x.png' });
+            const highRes80 = mockGetIconItem({ src: 'res/electron/cordova@8x.png' });
+
+            const data = {
+                customIcon: undefined,
+                appIcon: undefined,
+                installerIcon: undefined,
+                highResIcons: [
+                    Object.assign(highRes15, { suffix: '1.5x', extension: '.png' }),
+                    Object.assign(highRes20, { suffix: '2x', extension: '.png' }),
+                    Object.assign(highRes40, { suffix: '4x', extension: '.png' }),
+                    Object.assign(highRes80, { suffix: '8x', extension: '.png' }),
+                    Object.assign(highRes10, { suffix: '1x', extension: '.png' })
+                ]
+            };
+
+            const actual = createResourceMap(cordovaProject, locations, data);
+
+            expect(shellLsSpy).toHaveBeenCalled();
+
+            const expected = [
+                { 'res/electron/cordova@1.5x.png': '/mock/www/img/icon@1.5x.png' },
+                { 'res/electron/cordova@2x.png': '/mock/www/img/icon@2x.png' },
+                { 'res/electron/cordova@4x.png': '/mock/www/img/icon@4x.png' },
+                { 'res/electron/cordova@8x.png': '/mock/www/img/icon@8x.png' },
+                { 'res/electron/cordova.png': '/mock/www/img/icon.png' }
+            ];
+
+            expect(expected).toEqual(actual);
+        });
+
+        it('should map high resolution icons and installer icon to appoporiate location', () => {
+            const installer = mockGetIconItem({
+                src: 'res/electron/cordova_512.png',
+                target: 'installer',
+                width: 512,
+                height: 512
+            });
+            const highRes10 = mockGetIconItem({ src: 'res/electron/cordova.png' });
+            const highRes15 = mockGetIconItem({ src: 'res/electron/cordova@1.5x.png' });
+            const highRes20 = mockGetIconItem({ src: 'res/electron/cordova@2x.png' });
+            const highRes40 = mockGetIconItem({ src: 'res/electron/cordova@4x.png' });
+            const highRes80 = mockGetIconItem({ src: 'res/electron/cordova@8x.png' });
+
+            const data = {
+                customIcon: undefined,
+                appIcon: undefined,
+                installerIcon: Object.assign(installer, { extension: '.png' }),
+                highResIcons: [
+                    Object.assign(highRes15, { suffix: '1.5x', extension: '.png' }),
+                    Object.assign(highRes20, { suffix: '2x', extension: '.png' }),
+                    Object.assign(highRes40, { suffix: '4x', extension: '.png' }),
+                    Object.assign(highRes80, { suffix: '8x', extension: '.png' }),
+                    Object.assign(highRes10, { suffix: '1x', extension: '.png' })
+                ]
+            };
+
+            const actual = createResourceMap(cordovaProject, locations, data);
+
+            expect(shellLsSpy).toHaveBeenCalled();
+
+            const expected = [
+                { 'res/electron/cordova_512.png': '/mock/build-res/installer.png' },
+                { 'res/electron/cordova@1.5x.png': '/mock/www/img/icon@1.5x.png' },
+                { 'res/electron/cordova@2x.png': '/mock/www/img/icon@2x.png' },
+                { 'res/electron/cordova@4x.png': '/mock/www/img/icon@4x.png' },
+                { 'res/electron/cordova@8x.png': '/mock/www/img/icon@8x.png' },
+                { 'res/electron/cordova.png': '/mock/www/img/icon.png' }
+            ];
+
+            expect(expected).toEqual(actual);
+        });
+
+    });
+
+    describe('mapIconResources method', () => {
+        let mapIconResources;
+        let shellLsSpy;
+
+        beforeEach(() => {
+            mapIconResources = prepare.__get__('mapIconResources');
+            shellLsSpy = prepare.__get__('mapIconResources');
+
+            shellLsSpy = jasmine.createSpy('ls').and.returnValue([true]);
+            prepare.__set__('shell', { ls: shellLsSpy });
+        });
+
+        it('should not be called if resource does not exist.', () => {
+            mapIconResources(cordovaProject.root, '', '');
+
+            shellLsSpy = jasmine.createSpy('ls').and.returnValue([false]);
+            prepare.__set__('shell', { ls: shellLsSpy });
+
+            expect(shellLsSpy).not.toHaveBeenCalled();
+        });
+
+        it('should map to file to file', () => {
+            const sourcePath = `${cordovaProject.root}/res/electron/cordova_512.png`;
+            const targetPath = `${cordovaProject.root}/www/img/icon.png`;
+
+            const expected = {};
+            expected[sourcePath] = targetPath;
+
+            const actual = mapIconResources(cordovaProject.root, sourcePath, targetPath);
+
+            expect(shellLsSpy).toHaveBeenCalled();
+
+            expect(expected).toEqual(actual);
+        });
+
+        it('should map to folder to folder', () => {
+            const sourcePath = `${cordovaProject.root}/res/electron/`;
+            const targetPath = `${cordovaProject.root}/www/img/`;
+
+            const expected = {};
+            expected[sourcePath] = targetPath;
+
+            const actual = mapIconResources(cordovaProject.root, sourcePath, targetPath);
+
+            expect(shellLsSpy).toHaveBeenCalled();
+
+            expect(expected).toEqual(actual);
+        });
+
+    });
+
+    describe('copyIcons method', () => {
+        let copyIcons;
+        let fsCopySyncSpy;
+
+        beforeEach(() => {
+            copyIcons = prepare.__get__('copyIcons');
+
+            fsCopySyncSpy = jasmine.createSpy('copySync');
+            prepare.__set__('fs', { copySync: fsCopySyncSpy });
+        });
+
+        it('should not copy as no resources provided.', () => {
+            copyIcons(cordovaProject.root, []);
+            expect(fsCopySyncSpy).not.toHaveBeenCalled();
+        });
+
+        it('should copy provided resources.', () => {
+            copyIcons(cordovaProject.root, [
+                { 'res/electron/cordova_512.png': `${cordovaProject.root}/build-res/installer.png` },
+                { 'res/electron/cordova.png': `${cordovaProject.root}/www/img/icon.png` }
+            ]);
+
+            expect(fsCopySyncSpy).toHaveBeenCalled();
+
+            const installerIconSrcPathTest = fsCopySyncSpy.calls.argsFor(0)[0];
+            const installerIconDestPathTest = fsCopySyncSpy.calls.argsFor(0)[1];
+            expect(installerIconSrcPathTest).toBe(`${cordovaProject.root}/res/electron/cordova_512.png`);
+            expect(installerIconDestPathTest).toBe(`${cordovaProject.root}/build-res/installer.png`);
+
+            const appIconSrcPathTest = fsCopySyncSpy.calls.argsFor(1)[0];
+            const appIconDestPathTest = fsCopySyncSpy.calls.argsFor(1)[1];
+            expect(appIconSrcPathTest).toBe(`${cordovaProject.root}/res/electron/cordova.png`);
+            expect(appIconDestPathTest).toBe(`${cordovaProject.root}/www/img/icon.png`);
+        });
+    });
+});


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