import * as stubs from "./stubs";
import * as yok from "../lib/common/yok";
import { DebugAndroidCommand, DebugPlatformCommand } from "../lib/commands/debug";
import { assert } from "chai";
import { Configuration, StaticConfig } from "../lib/config";
import { Options } from "../lib/options";
import { DevicePlatformsConstants } from "../lib/common/mobile/device-platforms-constants";
import { FileSystem } from "../lib/common/file-system";
import { AndroidProjectService } from "../lib/services/android-project-service";
import { AndroidDebugBridge } from "../lib/common/mobile/android/android-debug-bridge";
import { AndroidDebugBridgeResultHandler } from "../lib/common/mobile/android/android-debug-bridge-result-handler";
import { DebugCommandErrors } from "../lib/constants";
import { CONNECTED_STATUS, UNREACHABLE_STATUS } from "../lib/common/constants";
import { SettingsService } from "../lib/common/test/unit-tests/stubs";
const helpers = require("../lib/common/helpers");
const originalIsInteracive = helpers.isInteractive;
function createTestInjector(): IInjector {
const testInjector: IInjector = new yok.Yok();
testInjector.register("debug|android", DebugAndroidCommand);
testInjector.register("config", Configuration);
testInjector.register("staticConfig", StaticConfig);
testInjector.register("logger", stubs.LoggerStub);
testInjector.register("options", Options);
testInjector.register("devicePlatformsConstants", DevicePlatformsConstants);
testInjector.register('childProcess', stubs.ChildProcessStub);
testInjector.register('fs', FileSystem);
testInjector.register('errors', stubs.ErrorsStub);
testInjector.register('hostInfo', {});
testInjector.register("analyticsService", {
trackException: async (): Promise => undefined,
checkConsent: async (): Promise => undefined,
trackFeature: async (): Promise => undefined
});
testInjector.register('devicesService', {
initialize: async () => { /* Intentionally left blank */ },
getDeviceInstances: (): any[] => { return []; },
execute: async (): Promise => ({})
});
testInjector.register("liveSyncService", stubs.LiveSyncServiceStub);
testInjector.register("androidProjectService", AndroidProjectService);
testInjector.register("androidToolsInfo", stubs.AndroidToolsInfoStub);
testInjector.register("hostInfo", {});
testInjector.register("projectData", { platformsDir: "test", initializeProjectData: () => { /* empty */ } });
testInjector.register("projectDataService", {});
testInjector.register("sysInfo", {});
testInjector.register("mobileHelper", {});
testInjector.register("pluginVariablesService", {});
testInjector.register("deviceAppDataFactory", {});
testInjector.register("projectTemplatesService", {});
testInjector.register("debugService", {});
testInjector.register("xmlValidator", {});
testInjector.register("npm", {});
testInjector.register("debugDataService", {
createDebugData: () => ({})
});
testInjector.register("androidEmulatorServices", {});
testInjector.register("adb", AndroidDebugBridge);
testInjector.register("androidDebugBridgeResultHandler", AndroidDebugBridgeResultHandler);
testInjector.register("platformService", stubs.PlatformServiceStub);
testInjector.register("platformsData", {
availablePlatforms: {
Android: "Android",
iOS: "iOS"
}
});
testInjector.register("prompter", {});
testInjector.registerCommand("debug|android", DebugAndroidCommand);
testInjector.register("liveSyncCommandHelper", {
executeLiveSyncOperation: async (): Promise => {
return null;
}
});
testInjector.register("settingsService", SettingsService);
testInjector.register("androidPluginBuildService", stubs.AndroidPluginBuildServiceStub);
testInjector.register("platformEnvironmentRequirements", {});
testInjector.register("androidResourcesMigrationService", stubs.AndroidResourcesMigrationServiceStub);
return testInjector;
}
describe("debug command tests", () => {
describe("getDeviceForDebug", () => {
it("throws error when both --for-device and --emulator are passed", async () => {
const testInjector = createTestInjector();
const options = testInjector.resolve("options");
options.forDevice = options.emulator = true;
const debugCommand = testInjector.resolve(DebugPlatformCommand, { debugService: {}, platform: "android" });
await assert.isRejected(debugCommand.getDeviceForDebug(), DebugCommandErrors.UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR);
});
it("returns selected device, when --device is passed", async () => {
const testInjector = createTestInjector();
const devicesService = testInjector.resolve("devicesService");
const deviceInstance = {};
const specifiedDeviceOption = "device1";
devicesService.getDevice = async (deviceOption: string): Promise => {
if (deviceOption === specifiedDeviceOption) {
return deviceInstance;
}
};
const options = testInjector.resolve("options");
options.device = specifiedDeviceOption;
const debugCommand = testInjector.resolve(DebugPlatformCommand, { debugService: {}, platform: "android" });
const selectedDeviceInstance = await debugCommand.getDeviceForDebug();
assert.deepEqual(selectedDeviceInstance, deviceInstance);
});
const assertErrorIsThrown = async (getDeviceInstancesResult: Mobile.IDevice[], passedOptions?: { forDevice: boolean, emulator: boolean }) => {
const testInjector = createTestInjector();
if (passedOptions) {
const options = testInjector.resolve("options");
options.forDevice = passedOptions.forDevice;
options.emulator = passedOptions.emulator;
}
const devicesService = testInjector.resolve("devicesService");
devicesService.getDeviceInstances = (): Mobile.IDevice[] => getDeviceInstancesResult;
const debugCommand = testInjector.resolve(DebugPlatformCommand, { debugService: {}, platform: "android" });
await assert.isRejected(debugCommand.getDeviceForDebug(), DebugCommandErrors.NO_DEVICES_EMULATORS_FOUND_FOR_OPTIONS);
};
it("throws error when there are no devices/emulators available", () => {
return assertErrorIsThrown([]);
});
it("throws error when there are no devices/emulators available for selected platform", () => {
return assertErrorIsThrown([
{
deviceInfo: {
platform: "ios",
status: CONNECTED_STATUS
}
}
]);
});
it("throws error when there are only not-trusted devices/emulators available for selected platform", () => {
return assertErrorIsThrown([
{
deviceInfo: {
platform: "android",
status: UNREACHABLE_STATUS
}
}
]);
});
it("throws error when there are only devices and --emulator is passed", () => {
return assertErrorIsThrown([
{
deviceInfo: {
platform: "android",
status: CONNECTED_STATUS
},
isEmulator: false
}
], {
forDevice: false,
emulator: true
});
});
it("throws error when there are only emulators and --forDevice is passed", () => {
return assertErrorIsThrown([
{
deviceInfo: {
platform: "android",
status: CONNECTED_STATUS
},
isEmulator: true
}
], {
forDevice: true,
emulator: false
});
});
it("returns the only available device/emulator when it matches passed -- options", async () => {
const testInjector = createTestInjector();
const deviceInstance = {
deviceInfo: {
platform: "android",
status: CONNECTED_STATUS
},
isEmulator: true
};
const devicesService = testInjector.resolve("devicesService");
devicesService.getDeviceInstances = (): Mobile.IDevice[] => [deviceInstance];
const debugCommand = testInjector.resolve(DebugPlatformCommand, { debugService: {}, platform: "android" });
const actualDeviceInstance = await debugCommand.getDeviceForDebug();
assert.deepEqual(actualDeviceInstance, deviceInstance);
});
describe("when multiple devices are detected", () => {
beforeEach(() => {
helpers.isInteractive = originalIsInteracive;
});
after(() => {
helpers.isInteractive = originalIsInteracive;
});
describe("when terminal is interactive", () => {
it("prompts the user with information about available devices for specified platform only and returns the selected device instance", async () => {
helpers.isInteractive = () => true;
const testInjector = createTestInjector();
const deviceInstance1 = {
deviceInfo: {
platform: "android",
status: CONNECTED_STATUS,
identifier: "deviceInstance1",
displayName: "displayName1"
},
isEmulator: true
};
const deviceInstance2 = {
deviceInfo: {
platform: "android",
status: CONNECTED_STATUS,
identifier: "deviceInstance2",
displayName: "displayName2"
},
isEmulator: true
};
const iOSDeviceInstance = {
deviceInfo: {
platform: "ios",
status: CONNECTED_STATUS,
identifier: "iosDevice",
displayName: "iPhone"
},
isEmulator: true
};
const devicesService = testInjector.resolve("devicesService");
devicesService.getDeviceInstances = (): Mobile.IDevice[] => [deviceInstance1, deviceInstance2, iOSDeviceInstance];
let choicesPassedToPrompter: string[];
const prompter = testInjector.resolve("prompter");
prompter.promptForChoice = async (promptMessage: string, choices: any[]): Promise => {
choicesPassedToPrompter = choices;
return choices[1];
};
const debugCommand = testInjector.resolve(DebugPlatformCommand, { debugService: {}, platform: "android" });
const actualDeviceInstance = await debugCommand.getDeviceForDebug();
const expectedChoicesPassedToPrompter = [deviceInstance1, deviceInstance2].map(d => `${d.deviceInfo.identifier} - ${d.deviceInfo.displayName}`);
assert.deepEqual(choicesPassedToPrompter, expectedChoicesPassedToPrompter);
assert.deepEqual(actualDeviceInstance, deviceInstance2);
});
});
describe("when terminal is not interactive", () => {
beforeEach(() => {
helpers.isInteractive = () => false;
});
const assertCorrectInstanceIsUsed = async (opts: { forDevice: boolean, emulator: boolean, isEmulatorTest: boolean, excludeLastDevice?: boolean }) => {
const testInjector = createTestInjector();
const deviceInstance1 = {
deviceInfo: {
platform: "android",
status: CONNECTED_STATUS,
identifier: "deviceInstance1",
displayName: "displayName1",
version: "5.1"
},
isEmulator: opts.isEmulatorTest
};
const deviceInstance2 = {
deviceInfo: {
platform: "android",
status: CONNECTED_STATUS,
identifier: "deviceInstance2",
displayName: "displayName2",
version: "6.0"
},
isEmulator: opts.isEmulatorTest
};
const deviceInstance3 = {
deviceInfo: {
platform: "android",
status: CONNECTED_STATUS,
identifier: "deviceInstance3",
displayName: "displayName3",
version: "7.1"
},
isEmulator: !opts.isEmulatorTest
};
const options = testInjector.resolve("options");
options.forDevice = opts.forDevice;
options.emulator = opts.emulator;
const devicesService = testInjector.resolve("devicesService");
const deviceInstances = [deviceInstance1, deviceInstance2];
if (!opts.excludeLastDevice) {
deviceInstances.push(deviceInstance3);
}
devicesService.getDeviceInstances = (): Mobile.IDevice[] => deviceInstances;
const debugCommand = testInjector.resolve(DebugPlatformCommand, { debugService: {}, platform: "android" });
const actualDeviceInstance = await debugCommand.getDeviceForDebug();
assert.deepEqual(actualDeviceInstance, deviceInstance2);
};
it("returns the emulator with highest API level when --emulator is passed", () => {
return assertCorrectInstanceIsUsed({ forDevice: false, emulator: true, isEmulatorTest: true });
});
it("returns the device with highest API level when --forDevice is passed", () => {
return assertCorrectInstanceIsUsed({ forDevice: true, emulator: false, isEmulatorTest: false });
});
it("returns the emulator with highest API level when neither --emulator and --forDevice are passed", () => {
return assertCorrectInstanceIsUsed({ forDevice: false, emulator: false, isEmulatorTest: true });
});
it("returns the device with highest API level when neither --emulator and --forDevice are passed and emulators are not available", async () => {
return assertCorrectInstanceIsUsed({ forDevice: false, emulator: false, isEmulatorTest: false, excludeLastDevice: true });
});
});
});
});
describe("Debugger tests", () => {
let testInjector: IInjector;
beforeEach(() => {
testInjector = createTestInjector();
});
it("Ensures that beforePrepareAllPlugins will call gradle with clean option when *NOT* livesyncing", async () => {
const platformData = testInjector.resolve("platformsData");
platformData.frameworkPackageName = "tns-android";
// only test that 'clean' is performed on android {
return { version: "3.2.0" };
};
const childProcess: stubs.ChildProcessStub = testInjector.resolve("childProcess");
const androidProjectService: IPlatformProjectService = testInjector.resolve("androidProjectService");
androidProjectService.getPlatformData = (projectData: IProjectData): IPlatformData => {
return platformData;
};
const projectData: IProjectData = testInjector.resolve("projectData");
const spawnFromEventCount = childProcess.spawnFromEventCount;
await androidProjectService.beforePrepareAllPlugins(projectData);
assert.isTrue(childProcess.lastCommand.indexOf("gradle") !== -1);
assert.isTrue(childProcess.lastCommandArgs[0] === "clean");
assert.isTrue(spawnFromEventCount === 0);
assert.isTrue(spawnFromEventCount + 1 === childProcess.spawnFromEventCount);
});
});
});