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-android.git


The following commit(s) were added to refs/heads/master by this push:
     new c04ea9b  refactor: unify target resolution for devices & emulators 
(#1101)
c04ea9b is described below

commit c04ea9b1c0bfaf79409de73adcfdaffc5997b8de
Author: Raphael von der GrĂ¼n <[email protected]>
AuthorDate: Fri Apr 9 08:37:56 2021 +0200

    refactor: unify target resolution for devices & emulators (#1101)
    
    * refactor: unify target resolution for devices & emulators
    * fix: use unified target methods in platform-centric bins
---
 bin/templates/cordova/lib/device.js        |  48 -------
 bin/templates/cordova/lib/emulator.js      |  19 ---
 bin/templates/cordova/lib/install-device   |  10 +-
 bin/templates/cordova/lib/install-emulator |  10 +-
 bin/templates/cordova/lib/list-devices     |  12 +-
 bin/templates/cordova/lib/run.js           |  78 ++++--------
 bin/templates/cordova/lib/target.js        |  86 ++++++++++++-
 spec/unit/device.spec.js                   | 106 ----------------
 spec/unit/emulator.spec.js                 |  47 -------
 spec/unit/run.spec.js                      | 192 ++++------------------------
 spec/unit/target.spec.js                   | 195 ++++++++++++++++++++++++++++-
 11 files changed, 338 insertions(+), 465 deletions(-)

diff --git a/bin/templates/cordova/lib/device.js 
b/bin/templates/cordova/lib/device.js
deleted file mode 100644
index 1ea4cdc..0000000
--- a/bin/templates/cordova/lib/device.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
-       Licensed to the Apache Software Foundation (ASF) under one
-       or more contributor license agreements.  See the NOTICE file
-       distributed with this work for additional information
-       regarding copyright ownership.  The ASF licenses this file
-       to you under the Apache License, Version 2.0 (the
-       "License"); you may not use this file except in compliance
-       with the License.  You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-       Unless required by applicable law or agreed to in writing,
-       software distributed under the License is distributed on an
-       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-       KIND, either express or implied.  See the License for the
-       specific language governing permissions and limitations
-       under the License.
-*/
-
-var build = require('./build');
-var Adb = require('./Adb');
-var CordovaError = require('cordova-common').CordovaError;
-
-/**
- * Returns a promise for the list of the device ID's found
- */
-module.exports.list = async () => {
-    return (await Adb.devices())
-        .filter(id => !id.startsWith('emulator-'));
-};
-
-module.exports.resolveTarget = function (target) {
-    return this.list().then(function (device_list) {
-        if (!device_list || !device_list.length) {
-            return Promise.reject(new CordovaError('Failed to deploy to 
device, no devices found.'));
-        }
-        // default device
-        target = target || device_list[0];
-
-        if (device_list.indexOf(target) < 0) {
-            return Promise.reject(new CordovaError('ERROR: Unable to find 
target \'' + target + '\'.'));
-        }
-
-        return build.detectArchitecture(target).then(function (arch) {
-            return { target: target, arch: arch, isEmulator: false };
-        });
-    });
-};
diff --git a/bin/templates/cordova/lib/emulator.js 
b/bin/templates/cordova/lib/emulator.js
index 7c9ee41..7a5fbd1 100644
--- a/bin/templates/cordova/lib/emulator.js
+++ b/bin/templates/cordova/lib/emulator.js
@@ -20,7 +20,6 @@
 const execa = require('execa');
 const fs = require('fs-extra');
 var android_versions = require('android-versions');
-var build = require('./build');
 var path = require('path');
 var Adb = require('./Adb');
 var events = require('cordova-common').events;
@@ -349,21 +348,3 @@ module.exports.wait_for_boot = function (emulator_id, 
time_remaining) {
         }
     });
 };
-
-module.exports.resolveTarget = function (target) {
-    return this.list_started().then(function (emulator_list) {
-        if (emulator_list.length < 1) {
-            return Promise.reject(new CordovaError('No running Android 
emulators found, please start an emulator before deploying your project.'));
-        }
-
-        // default emulator
-        target = target || emulator_list[0];
-        if (emulator_list.indexOf(target) < 0) {
-            return Promise.reject(new CordovaError('Unable to find target \'' 
+ target + '\'. Failed to deploy to emulator.'));
-        }
-
-        return build.detectArchitecture(target).then(function (arch) {
-            return { target: target, arch: arch, isEmulator: true };
-        });
-    });
-};
diff --git a/bin/templates/cordova/lib/install-device 
b/bin/templates/cordova/lib/install-device
index 2d5a8b1..1340a4d 100755
--- a/bin/templates/cordova/lib/install-device
+++ b/bin/templates/cordova/lib/install-device
@@ -19,21 +19,21 @@
        under the License.
 */
 
-const { install } = require('./target');
-var device = require('./device');
+const { resolve, install } = require('./target');
+
 var args = process.argv;
+const targetSpec = { type: 'device' };
 
 if (args.length > 2) {
-    var install_target;
     if (args[2].substring(0, 9) === '--target=') {
-        install_target = args[2].substring(9, args[2].length);
+        targetSpec.id = args[2].substring(9, args[2].length);
     } else {
         console.error('ERROR : argument \'' + args[2] + '\' not recognized.');
         process.exit(2);
     }
 }
 
-device.resolveTarget(install_target).then(install).catch(err => {
+resolve(targetSpec).then(install).catch(err => {
     console.error('ERROR: ' + err);
     process.exit(2);
 });
diff --git a/bin/templates/cordova/lib/install-emulator 
b/bin/templates/cordova/lib/install-emulator
index d67997e..f73cd05 100755
--- a/bin/templates/cordova/lib/install-emulator
+++ b/bin/templates/cordova/lib/install-emulator
@@ -19,21 +19,21 @@
        under the License.
 */
 
-const { install } = require('./target');
-var emulator = require('./emulator');
+const { resolve, install } = require('./target');
+
 var args = process.argv;
+const targetSpec = { type: 'emulator' };
 
-var install_target;
 if (args.length > 2) {
     if (args[2].substring(0, 9) === '--target=') {
-        install_target = args[2].substring(9, args[2].length);
+        targetSpec.id = args[2].substring(9, args[2].length);
     } else {
         console.error('ERROR : argument \'' + args[2] + '\' not recognized.');
         process.exit(2);
     }
 }
 
-emulator.resolveTarget(install_target).then(install).catch(err => {
+resolve(targetSpec).then(install).catch(err => {
     console.error('ERROR: ' + err);
     process.exit(2);
 });
diff --git a/bin/templates/cordova/lib/list-devices 
b/bin/templates/cordova/lib/list-devices
index 339c665..ed8e111 100755
--- a/bin/templates/cordova/lib/list-devices
+++ b/bin/templates/cordova/lib/list-devices
@@ -19,14 +19,16 @@
        under the License.
 */
 
-var devices = require('./device');
+const { list } = require('./target');
 
 // Usage support for when args are given
 require('./check_reqs').check_android().then(function () {
-    devices.list().then(function (device_list) {
-        device_list && device_list.forEach(function (dev) {
-            console.log(dev);
-        });
+    list().then(targets => {
+        const deviceIds = targets
+            .filter(({ type }) => type === 'device')
+            .map(({ id }) => id);
+
+        console.log(deviceIds.join('\n'));
     }, function (err) {
         console.error('ERROR: ' + err);
         process.exit(2);
diff --git a/bin/templates/cordova/lib/run.js b/bin/templates/cordova/lib/run.js
index 05ff775..1741659 100644
--- a/bin/templates/cordova/lib/run.js
+++ b/bin/templates/cordova/lib/run.js
@@ -19,22 +19,30 @@
 
 var path = require('path');
 var emulator = require('./emulator');
-var device = require('./device');
 const target = require('./target');
 var PackageType = require('./PackageType');
-const { CordovaError, events } = require('cordova-common');
+const { events } = require('cordova-common');
 
-function getInstallTarget (runOptions) {
-    var install_target;
+/**
+ * Builds a target spec from a runOptions object
+ *
+ * @param {{target?: string, device?: boolean, emulator?: boolean}} runOptions
+ * @return {target.TargetSpec}
+ */
+function buildTargetSpec (runOptions) {
+    const spec = {};
     if (runOptions.target) {
-        install_target = runOptions.target;
+        spec.id = runOptions.target;
     } else if (runOptions.device) {
-        install_target = '--device';
+        spec.type = 'device';
     } else if (runOptions.emulator) {
-        install_target = '--emulator';
+        spec.type = 'emulator';
     }
+    return spec;
+}
 
-    return install_target;
+function formatResolvedTarget ({ id, type }) {
+    return `${type} ${id}`;
 }
 
 /**
@@ -51,55 +59,11 @@ module.exports.run = function (runOptions) {
     runOptions = runOptions || {};
 
     var self = this;
-    var install_target = getInstallTarget(runOptions);
+    const spec = buildTargetSpec(runOptions);
+
+    return target.resolve(spec).then(function (resolvedTarget) {
+        events.emit('log', `Deploying to 
${formatResolvedTarget(resolvedTarget)}`);
 
-    return Promise.resolve().then(function () {
-        if (!install_target) {
-            // no target given, deploy to device if available, otherwise use 
the emulator.
-            return device.list().then(function (device_list) {
-                if (device_list.length > 0) {
-                    events.emit('warn', 'No target specified, deploying to 
device \'' + device_list[0] + '\'.');
-                    install_target = device_list[0];
-                } else {
-                    events.emit('warn', 'No target specified and no devices 
found, deploying to emulator');
-                    install_target = '--emulator';
-                }
-            });
-        }
-    }).then(function () {
-        if (install_target === '--device') {
-            return device.resolveTarget(null);
-        } else if (install_target === '--emulator') {
-            // Give preference to any already started emulators. Else, start 
one.
-            return emulator.list_started().then(function (started) {
-                return started && started.length > 0 ? started[0] : 
emulator.start();
-            }).then(function (emulatorId) {
-                return emulator.resolveTarget(emulatorId);
-            });
-        }
-        // They specified a specific device/emulator ID.
-        return device.list().then(function (devices) {
-            if (devices.indexOf(install_target) > -1) {
-                return device.resolveTarget(install_target);
-            }
-            return emulator.list_started().then(function (started_emulators) {
-                if (started_emulators.indexOf(install_target) > -1) {
-                    return emulator.resolveTarget(install_target);
-                }
-                return emulator.list_images().then(function (avds) {
-                    // if target emulator isn't started, then start it.
-                    for (var avd in avds) {
-                        if (avds[avd].name === install_target) {
-                            return 
emulator.start(install_target).then(function (emulatorId) {
-                                return emulator.resolveTarget(emulatorId);
-                            });
-                        }
-                    }
-                    return Promise.reject(new CordovaError(`Target 
'${install_target}' not found, unable to run project`));
-                });
-            });
-        });
-    }).then(function (resolvedTarget) {
         return new Promise((resolve) => {
             const buildOptions = 
require('./build').parseBuildOptions(runOptions, null, self.root);
 
@@ -112,7 +76,7 @@ module.exports.run = function (runOptions) {
 
             resolve(self._builder.fetchBuildResults(buildOptions.buildType, 
buildOptions.arch));
         }).then(async function (buildResults) {
-            if (resolvedTarget && resolvedTarget.isEmulator) {
+            if (resolvedTarget.type === 'emulator') {
                 await emulator.wait_for_boot(resolvedTarget.id);
             }
 
diff --git a/bin/templates/cordova/lib/target.js 
b/bin/templates/cordova/lib/target.js
index 1494144..ea03640 100644
--- a/bin/templates/cordova/lib/target.js
+++ b/bin/templates/cordova/lib/target.js
@@ -18,17 +18,97 @@
 */
 
 const path = require('path');
+const { inspect } = require('util');
 const Adb = require('./Adb');
 const build = require('./build');
+const emulator = require('./emulator');
 const AndroidManifest = require('./AndroidManifest');
+const { compareBy } = require('./utils');
 const { retryPromise } = require('./retry');
-const { events } = require('cordova-common');
+const { events, CordovaError } = require('cordova-common');
 
 const INSTALL_COMMAND_TIMEOUT = 5 * 60 * 1000;
 const NUM_INSTALL_RETRIES = 3;
 const EXEC_KILL_SIGNAL = 'SIGKILL';
 
-exports.install = async function ({ target, arch, isEmulator }, buildResults) {
+/**
+ * @typedef { 'device' | 'emulator' } TargetType
+ * @typedef { { id: string, type: TargetType } } Target
+ * @typedef { { id?: string, type?: TargetType } } TargetSpec
+ */
+
+/**
+ * Returns a list of available targets (connected devices & started emulators)
+ *
+ * @return {Promise<Target[]>}
+ */
+exports.list = async () => {
+    return (await Adb.devices())
+        .map(id => ({
+            id,
+            type: id.startsWith('emulator-') ? 'emulator' : 'device'
+        }));
+};
+
+/**
+ * @param {TargetSpec?} spec
+ * @return {Promise<Target>}
+ */
+async function resolveToOnlineTarget (spec = {}) {
+    const targetList = await exports.list();
+    if (targetList.length === 0) return null;
+
+    // Sort by type: devices first, then emulators.
+    targetList.sort(compareBy(t => t.type));
+
+    // Find first matching target for spec. {} matches any target.
+    return targetList.find(target =>
+        Object.keys(spec).every(k => spec[k] === target[k])
+    ) || null;
+}
+
+async function isEmulatorName (name) {
+    const emus = await emulator.list_images();
+    return emus.some(avd => avd.name === name);
+}
+
+/**
+ * @param {TargetSpec?} spec
+ * @return {Promise<Target>}
+ */
+async function resolveToOfflineEmulator (spec = {}) {
+    if (spec.type === 'device') return null;
+    if (spec.id && !(await isEmulatorName(spec.id))) return null;
+
+    // try to start an emulator with name spec.id
+    // if spec.id is undefined, picks best match regarding target API
+    const emulatorId = await emulator.start(spec.id);
+
+    return { id: emulatorId, type: 'emulator' };
+}
+
+/**
+ * @param {TargetSpec?} spec
+ * @return {Promise<Target & {arch: string}>}
+ */
+exports.resolve = async (spec = {}) => {
+    events.emit('verbose', `Trying to find target matching ${inspect(spec)}`);
+
+    const resolvedTarget =
+        (await resolveToOnlineTarget(spec)) ||
+        (await resolveToOfflineEmulator(spec));
+
+    if (!resolvedTarget) {
+        throw new CordovaError(`Could not find target matching 
${inspect(spec)}`);
+    }
+
+    return {
+        ...resolvedTarget,
+        arch: await build.detectArchitecture(resolvedTarget.id)
+    };
+};
+
+exports.install = async function ({ id: target, arch, type }, buildResults) {
     const apk_path = build.findBestApkForArchitecture(buildResults, arch);
     const manifest = new AndroidManifest(path.join(__dirname, 
'../../app/src/main/AndroidManifest.xml'));
     const pkgName = manifest.getPackageId();
@@ -56,7 +136,7 @@ exports.install = async function ({ target, arch, isEmulator 
}, buildResults) {
         }
     }
 
-    if (isEmulator) {
+    if (type === 'emulator') {
         // Work around sporadic emulator hangs: 
http://issues.apache.org/jira/browse/CB-9119
         await retryPromise(NUM_INSTALL_RETRIES, () => doInstall({
             timeout: INSTALL_COMMAND_TIMEOUT,
diff --git a/spec/unit/device.spec.js b/spec/unit/device.spec.js
deleted file mode 100644
index 499bb63..0000000
--- a/spec/unit/device.spec.js
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-        http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-*/
-
-const rewire = require('rewire');
-
-const CordovaError = require('cordova-common').CordovaError;
-
-describe('device', () => {
-    const DEVICE_LIST = ['device1', 'device2', 'device3'];
-    let AdbSpy;
-    let device;
-
-    beforeEach(() => {
-        device = rewire('../../bin/templates/cordova/lib/device');
-        AdbSpy = jasmine.createSpyObj('Adb', ['devices', 'install', 'shell', 
'start', 'uninstall']);
-        device.__set__('Adb', AdbSpy);
-    });
-
-    describe('list', () => {
-        it('should return a list of all connected devices', () => {
-            AdbSpy.devices.and.resolveTo(['emulator-5556', 
'123a76565509e124']);
-
-            return device.list().then(list => {
-                expect(list).toEqual(['123a76565509e124']);
-            });
-        });
-    });
-
-    describe('resolveTarget', () => {
-        let buildSpy;
-
-        beforeEach(() => {
-            buildSpy = jasmine.createSpyObj('build', ['detectArchitecture']);
-            buildSpy.detectArchitecture.and.returnValue(Promise.resolve());
-            device.__set__('build', buildSpy);
-
-            spyOn(device, 
'list').and.returnValue(Promise.resolve(DEVICE_LIST));
-        });
-
-        it('should select the first device to be the target if none is 
specified', () => {
-            return device.resolveTarget().then(deviceInfo => {
-                expect(deviceInfo.target).toBe(DEVICE_LIST[0]);
-            });
-        });
-
-        it('should use the given target instead of the default', () => {
-            return device.resolveTarget(DEVICE_LIST[2]).then(deviceInfo => {
-                expect(deviceInfo.target).toBe(DEVICE_LIST[2]);
-            });
-        });
-
-        it('should set emulator to false', () => {
-            return device.resolveTarget().then(deviceInfo => {
-                expect(deviceInfo.isEmulator).toBe(false);
-            });
-        });
-
-        it('should throw an error if there are no devices', () => {
-            device.list.and.returnValue(Promise.resolve([]));
-
-            return device.resolveTarget().then(
-                () => fail('Unexpectedly resolved'),
-                err => {
-                    expect(err).toEqual(jasmine.any(CordovaError));
-                }
-            );
-        });
-
-        it('should throw an error if the specified target does not exist', () 
=> {
-            return device.resolveTarget('nonexistent-target').then(
-                () => fail('Unexpectedly resolved'),
-                err => {
-                    expect(err).toEqual(jasmine.any(CordovaError));
-                }
-            );
-        });
-
-        it('should detect the architecture and return it with the device 
info', () => {
-            const target = DEVICE_LIST[1];
-            const arch = 'unittestarch';
-
-            buildSpy.detectArchitecture.and.returnValue(Promise.resolve(arch));
-
-            return device.resolveTarget(target).then(deviceInfo => {
-                
expect(buildSpy.detectArchitecture).toHaveBeenCalledWith(target);
-                expect(deviceInfo.arch).toBe(arch);
-            });
-        });
-    });
-});
diff --git a/spec/unit/emulator.spec.js b/spec/unit/emulator.spec.js
index c15eaee..3507926 100644
--- a/spec/unit/emulator.spec.js
+++ b/spec/unit/emulator.spec.js
@@ -26,7 +26,6 @@ const CordovaError = require('cordova-common').CordovaError;
 const check_reqs = require('../../bin/templates/cordova/lib/check_reqs');
 
 describe('emulator', () => {
-    const EMULATOR_LIST = ['emulator-5555', 'emulator-5556', 'emulator-5557'];
     let emu;
 
     beforeEach(() => {
@@ -529,50 +528,4 @@ describe('emulator', () => {
             });
         });
     });
-
-    describe('resolveTarget', () => {
-        const arch = 'arm7-test';
-
-        beforeEach(() => {
-            const buildSpy = jasmine.createSpyObj('build', 
['detectArchitecture']);
-            buildSpy.detectArchitecture.and.returnValue(Promise.resolve(arch));
-            emu.__set__('build', buildSpy);
-
-            spyOn(emu, 
'list_started').and.returnValue(Promise.resolve(EMULATOR_LIST));
-        });
-
-        it('should throw an error if there are no running emulators', () => {
-            emu.list_started.and.returnValue(Promise.resolve([]));
-
-            return emu.resolveTarget().then(
-                () => fail('Unexpectedly resolved'),
-                err => {
-                    expect(err).toEqual(jasmine.any(CordovaError));
-                }
-            );
-        });
-
-        it('should throw an error if the requested emulator is not running', 
() => {
-            const targetEmulator = 'unstarted-emu';
-
-            return emu.resolveTarget(targetEmulator).then(
-                () => fail('Unexpectedly resolved'),
-                err => {
-                    expect(err.message).toContain(targetEmulator);
-                }
-            );
-        });
-
-        it('should return info on the first running emulator if none is 
specified', () => {
-            return emu.resolveTarget().then(emulatorInfo => {
-                expect(emulatorInfo.target).toBe(EMULATOR_LIST[0]);
-            });
-        });
-
-        it('should return the emulator info', () => {
-            return emu.resolveTarget(EMULATOR_LIST[1]).then(emulatorInfo => {
-                expect(emulatorInfo).toEqual({ target: EMULATOR_LIST[1], arch, 
isEmulator: true });
-            });
-        });
-    });
 });
diff --git a/spec/unit/run.spec.js b/spec/unit/run.spec.js
index 287905a..e780e68 100644
--- a/spec/unit/run.spec.js
+++ b/spec/unit/run.spec.js
@@ -25,45 +25,38 @@ describe('run', () => {
 
     beforeEach(() => {
         run = rewire('../../bin/templates/cordova/lib/run');
+        run.__set__({
+            events: jasmine.createSpyObj('eventsSpy', ['emit'])
+        });
     });
 
-    describe('getInstallTarget', () => {
-        const targetOpts = { target: 'emu' };
-        const deviceOpts = { device: true };
-        const emulatorOpts = { emulator: true };
-        const emptyOpts = {};
-
+    describe('buildTargetSpec', () => {
         it('Test#001 : should select correct target based on the run opts', () 
=> {
-            const getInstallTarget = run.__get__('getInstallTarget');
-            expect(getInstallTarget(targetOpts)).toBe('emu');
-            expect(getInstallTarget(deviceOpts)).toBe('--device');
-            expect(getInstallTarget(emulatorOpts)).toBe('--emulator');
-            expect(getInstallTarget(emptyOpts)).toBeUndefined();
+            const buildTargetSpec = run.__get__('buildTargetSpec');
+
+            expect(buildTargetSpec({ target: 'emu' })).toEqual({ id: 'emu' });
+            expect(buildTargetSpec({ device: true })).toEqual({ type: 'device' 
});
+            expect(buildTargetSpec({ emulator: true })).toEqual({ type: 
'emulator' });
+            expect(buildTargetSpec({})).toEqual({});
         });
     });
 
     describe('run method', () => {
-        let deviceSpyObj;
-        let emulatorSpyObj;
-        let targetSpyObj;
-        let eventsSpyObj;
-        let getInstallTargetSpy;
+        let targetSpyObj, emulatorSpyObj, resolvedTarget;
 
         beforeEach(() => {
-            deviceSpyObj = jasmine.createSpyObj('deviceSpy', ['list', 
'resolveTarget']);
-            emulatorSpyObj = jasmine.createSpyObj('emulatorSpy', 
['list_images', 'list_started', 'resolveTarget', 'start', 'wait_for_boot']);
-            eventsSpyObj = jasmine.createSpyObj('eventsSpy', ['emit']);
-            getInstallTargetSpy = jasmine.createSpy('getInstallTargetSpy');
+            resolvedTarget = { id: 'dev1', type: 'device', arch: 'atari' };
 
-            targetSpyObj = jasmine.createSpyObj('target', ['install']);
+            targetSpyObj = jasmine.createSpyObj('deviceSpy', ['resolve', 
'install']);
+            targetSpyObj.resolve.and.resolveTo(resolvedTarget);
             targetSpyObj.install.and.resolveTo();
 
+            emulatorSpyObj = jasmine.createSpyObj('emulatorSpy', 
['wait_for_boot']);
+            emulatorSpyObj.wait_for_boot.and.resolveTo();
+
             run.__set__({
-                device: deviceSpyObj,
-                emulator: emulatorSpyObj,
                 target: targetSpyObj,
-                events: eventsSpyObj,
-                getInstallTarget: getInstallTargetSpy
+                emulator: emulatorSpyObj
             });
 
             // run needs `this` to behave like an Api instance
@@ -72,152 +65,19 @@ describe('run', () => {
             });
         });
 
-        it('should run on default device when no target arguments are 
specified', () => {
-            const deviceList = ['testDevice1', 'testDevice2'];
-
-            getInstallTargetSpy.and.returnValue(null);
-            deviceSpyObj.list.and.returnValue(Promise.resolve(deviceList));
-
-            return run.run().then(() => {
-                
expect(deviceSpyObj.resolveTarget).toHaveBeenCalledWith(deviceList[0]);
-            });
-        });
-
-        it('should run on emulator when no target arguments are specified, and 
no devices are found', () => {
-            const deviceList = [];
-
-            getInstallTargetSpy.and.returnValue(null);
-            deviceSpyObj.list.and.returnValue(Promise.resolve(deviceList));
-            emulatorSpyObj.list_started.and.returnValue(Promise.resolve([]));
-
-            return run.run().then(() => {
-                expect(emulatorSpyObj.list_started).toHaveBeenCalled();
-            });
-        });
-
-        it('should run on default device when device is requested, but none 
specified', () => {
-            getInstallTargetSpy.and.returnValue('--device');
-
-            return run.run().then(() => {
-                // Default device is selected by calling 
device.resolveTarget(null)
-                expect(deviceSpyObj.resolveTarget).toHaveBeenCalledWith(null);
-            });
-        });
-
-        it('should run on a running emulator if one exists', () => {
-            const emulatorList = ['emulator1', 'emulator2'];
-
-            getInstallTargetSpy.and.returnValue('--emulator');
-            
emulatorSpyObj.list_started.and.returnValue(Promise.resolve(emulatorList));
-
-            return run.run().then(() => {
-                
expect(emulatorSpyObj.resolveTarget).toHaveBeenCalledWith(emulatorList[0]);
-            });
-        });
-
-        it('should start an emulator and run on that if none is running', () 
=> {
-            const emulatorList = [];
-            const defaultEmulator = 'default-emu';
-
-            getInstallTargetSpy.and.returnValue('--emulator');
-            
emulatorSpyObj.list_started.and.returnValue(Promise.resolve(emulatorList));
-            
emulatorSpyObj.start.and.returnValue(Promise.resolve(defaultEmulator));
-
+        it('should install on target after build', () => {
             return run.run().then(() => {
-                
expect(emulatorSpyObj.resolveTarget).toHaveBeenCalledWith(defaultEmulator);
-            });
-        });
-
-        it('should run on a named device if it is specified', () => {
-            const deviceList = ['device1', 'device2', 'device3'];
-            getInstallTargetSpy.and.returnValue(deviceList[1]);
-
-            deviceSpyObj.list.and.returnValue(Promise.resolve(deviceList));
-
-            return run.run().then(() => {
-                
expect(deviceSpyObj.resolveTarget).toHaveBeenCalledWith(deviceList[1]);
-            });
-        });
-
-        it('should run on a named emulator if it is specified', () => {
-            const startedEmulatorList = ['emu1', 'emu2', 'emu3'];
-            getInstallTargetSpy.and.returnValue(startedEmulatorList[2]);
-
-            deviceSpyObj.list.and.returnValue(Promise.resolve([]));
-            
emulatorSpyObj.list_started.and.returnValue(Promise.resolve(startedEmulatorList));
-
-            return run.run().then(() => {
-                
expect(emulatorSpyObj.resolveTarget).toHaveBeenCalledWith(startedEmulatorList[2]);
-            });
-        });
-
-        it('should start named emulator and then run on it if it is 
specified', () => {
-            const emulatorList = [
-                { name: 'emu1', id: 1 },
-                { name: 'emu2', id: 2 },
-                { name: 'emu3', id: 3 }
-            ];
-            getInstallTargetSpy.and.returnValue(emulatorList[2].name);
-
-            deviceSpyObj.list.and.returnValue(Promise.resolve([]));
-            emulatorSpyObj.list_started.and.returnValue(Promise.resolve([]));
-            
emulatorSpyObj.list_images.and.returnValue(Promise.resolve(emulatorList));
-            
emulatorSpyObj.start.and.returnValue(Promise.resolve(emulatorList[2].id));
-
-            return run.run().then(() => {
-                
expect(emulatorSpyObj.start).toHaveBeenCalledWith(emulatorList[2].name);
-                
expect(emulatorSpyObj.resolveTarget).toHaveBeenCalledWith(emulatorList[2].id);
-            });
-        });
-
-        it('should throw an error if target is specified but does not exist', 
() => {
-            const emulatorList = [{ name: 'emu1', id: 1 }];
-            const deviceList = ['device1'];
-            const target = 'nonexistentdevice';
-            getInstallTargetSpy.and.returnValue(target);
-
-            deviceSpyObj.list.and.returnValue(Promise.resolve(deviceList));
-            emulatorSpyObj.list_started.and.returnValue(Promise.resolve([]));
-            
emulatorSpyObj.list_images.and.returnValue(Promise.resolve(emulatorList));
-
-            return run.run().then(
-                () => fail('Expected error to be thrown'),
-                err => expect(err.message).toContain(target)
-            );
-        });
-
-        it('should install on device after build', () => {
-            const deviceTarget = { target: 'device1', isEmulator: false };
-            getInstallTargetSpy.and.returnValue('--device');
-            deviceSpyObj.resolveTarget.and.returnValue(deviceTarget);
-
-            return run.run().then(() => {
-                
expect(targetSpyObj.install).toHaveBeenCalledWith(deviceTarget, { apkPaths: [], 
buildType: 'debug' });
-            });
-        });
-
-        it('should install on emulator after build', () => {
-            const emulatorTarget = { target: 'emu1', isEmulator: true };
-            getInstallTargetSpy.and.returnValue('--emulator');
-            
emulatorSpyObj.list_started.and.returnValue(Promise.resolve([emulatorTarget.target]));
-            emulatorSpyObj.resolveTarget.and.returnValue(emulatorTarget);
-            emulatorSpyObj.wait_for_boot.and.returnValue(Promise.resolve());
-
-            return run.run().then(() => {
-                
expect(targetSpyObj.install).toHaveBeenCalledWith(emulatorTarget, { apkPaths: 
[], buildType: 'debug' });
+                expect(targetSpyObj.install).toHaveBeenCalledWith(
+                    resolvedTarget,
+                    { apkPaths: [], buildType: 'debug' }
+                );
             });
         });
 
         it('should fail with the error message if --packageType=bundle setting 
is used', () => {
-            const deviceList = ['testDevice1', 'testDevice2'];
-            getInstallTargetSpy.and.returnValue(null);
-
-            deviceSpyObj.list.and.returnValue(Promise.resolve(deviceList));
-
-            return run.run({ argv: ['--packageType=bundle'] }).then(
-                () => fail('Expected error to be thrown'),
-                err => expect(err).toContain('Package type "bundle" is not 
supported during cordova run.')
-            );
+            targetSpyObj.resolve.and.resolveTo(resolvedTarget);
+            return expectAsync(run.run({ argv: ['--packageType=bundle'] }))
+                .toBeRejectedWith(jasmine.stringMatching(/Package type 
"bundle" is not supported/));
         });
     });
 
diff --git a/spec/unit/target.spec.js b/spec/unit/target.spec.js
index 345cda1..0e0fcc6 100644
--- a/spec/unit/target.spec.js
+++ b/spec/unit/target.spec.js
@@ -27,6 +27,193 @@ describe('target', () => {
         target = rewire('../../bin/templates/cordova/lib/target');
     });
 
+    describe('list', () => {
+        it('should return available targets from Adb.devices', () => {
+            const AdbSpy = jasmine.createSpyObj('Adb', ['devices']);
+            AdbSpy.devices.and.resolveTo(['emulator-5556', 
'123a76565509e124']);
+            target.__set__('Adb', AdbSpy);
+
+            return target.list().then(emus => {
+                expect(emus).toEqual([
+                    { id: 'emulator-5556', type: 'emulator' },
+                    { id: '123a76565509e124', type: 'device' }
+                ]);
+            });
+        });
+    });
+
+    describe('resolveToOnlineTarget', () => {
+        let resolveToOnlineTarget, emus, devs;
+
+        beforeEach(() => {
+            resolveToOnlineTarget = target.__get__('resolveToOnlineTarget');
+
+            emus = [
+                { id: 'emu1', type: 'emulator' },
+                { id: 'emu2', type: 'emulator' }
+            ];
+            devs = [
+                { id: 'dev1', type: 'device' },
+                { id: 'dev2', type: 'device' }
+            ];
+
+            spyOn(target, 'list').and.returnValue([...emus, ...devs]);
+        });
+
+        it('should return first device when no target arguments are 
specified', async () => {
+            return resolveToOnlineTarget().then(result => {
+                expect(result.id).toBe('dev1');
+            });
+        });
+
+        it('should return first emulator when no target arguments are 
specified and no devices are found', async () => {
+            target.list.and.resolveTo(emus);
+            return resolveToOnlineTarget().then(result => {
+                expect(result.id).toBe('emu1');
+            });
+        });
+
+        it('should return first device when type device is specified', async 
() => {
+            return resolveToOnlineTarget({ type: 'device' }).then(result => {
+                expect(result.id).toBe('dev1');
+            });
+        });
+
+        it('should return first running emulator when type emulator is 
specified', async () => {
+            return resolveToOnlineTarget({ type: 'emulator' }).then(result => {
+                expect(result.id).toBe('emu1');
+            });
+        });
+
+        it('should return a device that matches given ID', async () => {
+            return resolveToOnlineTarget({ id: 'dev2' }).then(result => {
+                expect(result.id).toBe('dev2');
+            });
+        });
+
+        it('should return a running emulator that matches given ID', async () 
=> {
+            return resolveToOnlineTarget({ id: 'emu2' }).then(result => {
+                expect(result.id).toBe('emu2');
+            });
+        });
+
+        it('should return null if there are no online targets', async () => {
+            target.list.and.resolveTo([]);
+            return expectAsync(resolveToOnlineTarget())
+                .toBeResolvedTo(null);
+        });
+
+        it('should return null if no target matches given ID', async () => {
+            return expectAsync(resolveToOnlineTarget({ id: 'foo' }))
+                .toBeResolvedTo(null);
+        });
+
+        it('should return null if no target matches given type', async () => {
+            target.list.and.resolveTo(devs);
+            return expectAsync(resolveToOnlineTarget({ type: 'emulator' }))
+                .toBeResolvedTo(null);
+        });
+    });
+
+    describe('resolveToOfflineEmulator', () => {
+        const emuId = 'emulator-5554';
+        let resolveToOfflineEmulator, emulatorSpyObj;
+
+        beforeEach(() => {
+            resolveToOfflineEmulator = 
target.__get__('resolveToOfflineEmulator');
+
+            emulatorSpyObj = jasmine.createSpyObj('emulatorSpy', ['start']);
+            emulatorSpyObj.start.and.resolveTo(emuId);
+
+            target.__set__({
+                emulator: emulatorSpyObj,
+                isEmulatorName: name => name.startsWith('emu')
+            });
+        });
+
+        it('should start an emulator and run on that if none is running', () 
=> {
+            return resolveToOfflineEmulator().then(result => {
+                expect(result).toEqual({ id: emuId, type: 'emulator' });
+                expect(emulatorSpyObj.start).toHaveBeenCalled();
+            });
+        });
+
+        it('should start named emulator and then run on it if it is 
specified', () => {
+            return resolveToOfflineEmulator({ id: 'emu3' }).then(result => {
+                expect(result).toEqual({ id: emuId, type: 'emulator' });
+                expect(emulatorSpyObj.start).toHaveBeenCalledWith('emu3');
+            });
+        });
+
+        it('should return null if given ID is not an avd name', () => {
+            return resolveToOfflineEmulator({ id: 'dev1' }).then(result => {
+                expect(result).toBe(null);
+                expect(emulatorSpyObj.start).not.toHaveBeenCalled();
+            });
+        });
+
+        it('should return null if given type is not emulator', () => {
+            return resolveToOfflineEmulator({ type: 'device' }).then(result => 
{
+                expect(result).toBe(null);
+                expect(emulatorSpyObj.start).not.toHaveBeenCalled();
+            });
+        });
+    });
+
+    describe('resolve', () => {
+        let resolveToOnlineTarget, resolveToOfflineEmulator;
+
+        beforeEach(() => {
+            resolveToOnlineTarget = jasmine.createSpy('resolveToOnlineTarget')
+                .and.resolveTo(null);
+
+            resolveToOfflineEmulator = 
jasmine.createSpy('resolveToOfflineEmulator')
+                .and.resolveTo(null);
+
+            target.__set__({
+                resolveToOnlineTarget,
+                resolveToOfflineEmulator,
+                build: { detectArchitecture: id => id + '-arch' }
+            });
+        });
+
+        it('should delegate to resolveToOnlineTarget', () => {
+            const spec = { type: 'device' };
+            resolveToOnlineTarget.and.resolveTo({ id: 'dev1', type: 'device' 
});
+
+            return target.resolve(spec).then(result => {
+                expect(result.id).toBe('dev1');
+                expect(resolveToOnlineTarget).toHaveBeenCalledWith(spec);
+                expect(resolveToOfflineEmulator).not.toHaveBeenCalled();
+            });
+        });
+
+        it('should delegate to resolveToOfflineEmulator if 
resolveToOnlineTarget fails', () => {
+            const spec = { type: 'emulator' };
+            resolveToOfflineEmulator.and.resolveTo({ id: 'emu1', type: 
'emulator' });
+
+            return target.resolve(spec).then(result => {
+                expect(result.id).toBe('emu1');
+                expect(resolveToOnlineTarget).toHaveBeenCalledWith(spec);
+                expect(resolveToOfflineEmulator).toHaveBeenCalledWith(spec);
+            });
+        });
+
+        it('should add the target arch', () => {
+            const spec = { type: 'device' };
+            resolveToOnlineTarget.and.resolveTo({ id: 'dev1', type: 'device' 
});
+
+            return target.resolve(spec).then(result => {
+                expect(result.arch).toBe('dev1-arch');
+            });
+        });
+
+        it('should throw an error if target cannot be resolved', () => {
+            return expectAsync(target.resolve())
+                .toBeRejectedWithError(/Could not find target matching/);
+        });
+    });
+
     describe('install', () => {
         let AndroidManifestSpy;
         let AndroidManifestFns;
@@ -36,7 +223,7 @@ describe('target', () => {
         let installTarget;
 
         beforeEach(() => {
-            installTarget = { target: 'emulator-5556', isEmulator: true, arch: 
'atari' };
+            installTarget = { id: 'emulator-5556', type: 'emulator', arch: 
'atari' };
 
             buildSpy = jasmine.createSpyObj('build', 
['findBestApkForArchitecture']);
             target.__set__('build', buildSpy);
@@ -60,7 +247,7 @@ describe('target', () => {
 
         it('should install to the passed target', () => {
             return target.install(installTarget, {}).then(() => {
-                
expect(AdbSpy.install.calls.argsFor(0)[0]).toBe(installTarget.target);
+                
expect(AdbSpy.install.calls.argsFor(0)[0]).toBe(installTarget.id);
             });
         });
 
@@ -108,7 +295,7 @@ describe('target', () => {
 
         it('should unlock the screen on device', () => {
             return target.install(installTarget, {}).then(() => {
-                
expect(AdbSpy.shell).toHaveBeenCalledWith(installTarget.target, 'input keyevent 
82');
+                expect(AdbSpy.shell).toHaveBeenCalledWith(installTarget.id, 
'input keyevent 82');
             });
         });
 
@@ -119,7 +306,7 @@ describe('target', () => {
             
AndroidManifestGetActivitySpy.getName.and.returnValue(activityName);
 
             return target.install(installTarget, {}).then(() => {
-                
expect(AdbSpy.start).toHaveBeenCalledWith(installTarget.target, 
`${packageId}/.${activityName}`);
+                expect(AdbSpy.start).toHaveBeenCalledWith(installTarget.id, 
`${packageId}/.${activityName}`);
             });
         });
     });

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to