diff --git a/PublicAPI.md b/PublicAPI.md index 6104b7a7c8..dce94a45d4 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -623,6 +623,29 @@ tns.liveSyncService.stopLiveSync(projectDir, deviceIdentifiers) }); ``` +### getLiveSyncDeviceDescriptors +Gives information for currently running LiveSync operation and parameters used to start it on each device. + +* Definition +```TypeScript +/** + * Returns the device information for current LiveSync operation of specified project. + * In case LiveSync has been started on many devices, but stopped for some of them at a later point, + * calling the method after that will return information only for devices for which LiveSync operation is in progress. + * @param {string} projectDir The path to project for which the LiveSync operation is executed + * @returns {ILiveSyncDeviceInfo[]} Array of elements describing parameters used to start LiveSync on each device. +*/ +getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[]; +``` + +* Usage +```JavaScript +const projectDir = "myProjectDir"; +const deviceIdentifiers = [ "4df18f307d8a8f1b", "12318af23ebc0e25" ]; +const currentlyRunningDescriptors = tns.liveSyncService.getLiveSyncDeviceDescriptors(projectDir); +console.log(`LiveSync for ${projectDir} is currently running on the following devices: ${currentlyRunningDescriptors.map(descriptor => descriptor.identifier)}`); +``` + ### Events `liveSyncService` raises several events in order to provide information for current state of the operation. * liveSyncStarted - raised whenever CLI starts a LiveSync operation for specific device. When `liveSync` method is called, the initial LiveSync operation will emit `liveSyncStarted` for each specified device. After that the event will be emitted only in case when liveSync method is called again with different device instances. The event is raised with the following data: diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index ec89823dbe..4216a9bbeb 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -185,6 +185,15 @@ interface ILiveSyncService { * @returns {Promise} */ stopLiveSync(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise; + + /** + * Returns the device information for current LiveSync operation of specified project. + * In case LiveSync has been started on many devices, but stopped for some of them at a later point, + * calling the method after that will return information only for devices for which LiveSync operation is in progress. + * @param {string} projectDir The path to project for which the LiveSync operation is executed + * @returns {ILiveSyncDeviceInfo[]} Array of elements describing parameters used to start LiveSync on each device. + */ + getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[]; } /** diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 69f112b8af..b8b0829979 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -17,7 +17,7 @@ const LiveSyncEvents = { export class LiveSyncService extends EventEmitter implements ILiveSyncService { // key is projectDir - private liveSyncProcessesInfo: IDictionary = {}; + protected liveSyncProcessesInfo: IDictionary = {}; constructor(protected $platformService: IPlatformService, private $projectDataService: IProjectDataService, @@ -48,12 +48,10 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { // so we cannot await it as this will cause infinite loop. const shouldAwaitPendingOperation = !stopOptions || stopOptions.shouldAwaitAllActions; - let removedDeviceIdentifiers: string[] = deviceIdentifiers || []; + const deviceIdentifiersToRemove = deviceIdentifiers || _.map(liveSyncProcessInfo.deviceDescriptors, d => d.identifier); - _.each(deviceIdentifiers, deviceId => { - removedDeviceIdentifiers = _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => descriptor.identifier === deviceId) - .map(deviceDescriptor => deviceDescriptor.identifier); - }); + const removedDeviceIdentifiers = _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => _.includes(deviceIdentifiersToRemove, descriptor.identifier)) + .map(descriptor => descriptor.identifier); // In case deviceIdentifiers are not passed, we should stop the whole LiveSync. if (!deviceIdentifiers || !deviceIdentifiers.length || !liveSyncProcessInfo.deviceDescriptors || !liveSyncProcessInfo.deviceDescriptors.length) { @@ -72,7 +70,6 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { await liveSyncProcessInfo.actionsChain; } - removedDeviceIdentifiers = _.map(liveSyncProcessInfo.deviceDescriptors, d => d.identifier); liveSyncProcessInfo.deviceDescriptors = []; // Kill typescript watcher @@ -93,6 +90,12 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { } } + public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { + const liveSyncProcessesInfo = this.liveSyncProcessesInfo[projectDir] || {}; + const currentDescriptors = liveSyncProcessesInfo.deviceDescriptors; + return currentDescriptors || []; + } + protected async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo): Promise { const platformLiveSyncService = this.getLiveSyncService(liveSyncResultInfo.deviceAppData.platform); try { @@ -127,7 +130,8 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const isAlreadyLiveSyncing = this.liveSyncProcessesInfo[projectData.projectDir] && !this.liveSyncProcessesInfo[projectData.projectDir].isStopped; // Prevent cases where liveSync is called consecutive times with the same device, for example [ A, B, C ] and then [ A, B, D ] - we want to execute initialSync only for D. - const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors; + const currentlyRunningDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectData.projectDir); + const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, currentlyRunningDeviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors; this.setLiveSyncProcessInfo(liveSyncData.projectDir, deviceDescriptors); await this.initialSync(projectData, deviceDescriptorsForInitialSync, liveSyncData); @@ -146,7 +150,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { this.liveSyncProcessesInfo[projectDir].currentSyncAction = this.liveSyncProcessesInfo[projectDir].actionsChain; this.liveSyncProcessesInfo[projectDir].isStopped = false; - const currentDeviceDescriptors = this.liveSyncProcessesInfo[projectDir].deviceDescriptors || []; + const currentDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectDir); this.liveSyncProcessesInfo[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), deviceDescriptorPrimaryKey); } diff --git a/test/services/livesync-service.ts b/test/services/livesync-service.ts new file mode 100644 index 0000000000..75d2073bec --- /dev/null +++ b/test/services/livesync-service.ts @@ -0,0 +1,147 @@ +import { Yok } from "../../lib/common/yok"; +import { assert } from "chai"; +import { LiveSyncService } from "../../lib/services/livesync/livesync-service"; +import { LoggerStub } from "../stubs"; + +const createTestInjector = (): IInjector => { + const testInjector = new Yok(); + + testInjector.register("platformService", {}); + testInjector.register("projectDataService", { + getProjectData: (projectDir: string): IProjectData => ({}) + }); + + testInjector.register("devicesService", {}); + testInjector.register("mobileHelper", {}); + testInjector.register("devicePlatformsConstants", {}); + testInjector.register("nodeModulesDependenciesBuilder", {}); + testInjector.register("logger", LoggerStub); + testInjector.register("processService", {}); + testInjector.register("hooksService", { + executeAfterHooks: (commandName: string, hookArguments?: IDictionary): Promise => Promise.resolve() + }); + + testInjector.register("pluginsService", {}); + testInjector.register("injector", testInjector); + + return testInjector; +}; + +class LiveSyncServiceInheritor extends LiveSyncService { + constructor($platformService: IPlatformService, + $projectDataService: IProjectDataService, + $devicesService: Mobile.IDevicesService, + $mobileHelper: Mobile.IMobileHelper, + $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, + $logger: ILogger, + $processService: IProcessService, + $hooksService: IHooksService, + $pluginsService: IPluginsService, + $injector: IInjector) { + + super( + $platformService, + $projectDataService, + $devicesService, + $mobileHelper, + $devicePlatformsConstants, + $nodeModulesDependenciesBuilder, + $logger, + $processService, + $hooksService, + $pluginsService, + $injector + ); + } + + public liveSyncProcessesInfo: IDictionary = {}; +} + +interface IStopLiveSyncTestCase { + name: string; + currentDeviceIdentifiers: string[]; + expectedDeviceIdentifiers: string[]; + deviceIdentifiersToBeStopped?: string[]; +} + +describe("liveSyncService", () => { + describe("stopLiveSync", () => { + const getLiveSyncProcessInfo = (): ILiveSyncProcessInfo => ({ + actionsChain: Promise.resolve(), + currentSyncAction: Promise.resolve(), + isStopped: false, + timer: setTimeout(() => undefined, 1000), + watcherInfo: { + watcher: { + close: (): any => undefined + }, + pattern: "pattern" + }, + deviceDescriptors: [] + }); + + const getDeviceDescriptor = (identifier: string): ILiveSyncDeviceInfo => ({ + identifier, + outputPath: "", + skipNativePrepare: false, + platformSpecificOptions: null, + buildAction: () => Promise.resolve("") + }); + + const testCases: IStopLiveSyncTestCase[] = [ + { + name: "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers", + currentDeviceIdentifiers: ["device1", "device2", "device3"], + expectedDeviceIdentifiers: ["device1", "device2", "device3"] + }, + { + name: "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers (when a single device is attached)", + currentDeviceIdentifiers: ["device1"], + expectedDeviceIdentifiers: ["device1"] + }, + { + name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them (when a single device is attached)", + currentDeviceIdentifiers: ["device1"], + expectedDeviceIdentifiers: ["device1"], + deviceIdentifiersToBeStopped: ["device1"] + }, + { + name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them", + currentDeviceIdentifiers: ["device1", "device2", "device3"], + expectedDeviceIdentifiers: ["device1", "device3"], + deviceIdentifiersToBeStopped: ["device1", "device3"] + }, + { + name: "does not raise liveSyncStopped event for device, which is not currently being liveSynced", + currentDeviceIdentifiers: ["device1", "device2", "device3"], + expectedDeviceIdentifiers: ["device1"], + deviceIdentifiersToBeStopped: ["device1", "device4"] + } + ]; + + for (const testCase of testCases) { + it(testCase.name, async () => { + const testInjector = createTestInjector(); + const liveSyncService = testInjector.resolve(LiveSyncServiceInheritor); + const projectDir = "projectDir"; + const emittedDeviceIdentifiersForLiveSyncStoppedEvent: string[] = []; + liveSyncService.on("liveSyncStopped", (data: { projectDir: string, deviceIdentifier: string }) => { + assert.equal(data.projectDir, projectDir); + emittedDeviceIdentifiersForLiveSyncStoppedEvent.push(data.deviceIdentifier); + }); + + // Setup liveSyncProcessesInfo for current test + liveSyncService.liveSyncProcessesInfo[projectDir] = getLiveSyncProcessInfo(); + const deviceDescriptors = testCase.currentDeviceIdentifiers.map(d => getDeviceDescriptor(d)); + liveSyncService.liveSyncProcessesInfo[projectDir].deviceDescriptors.push(...deviceDescriptors); + + await liveSyncService.stopLiveSync(projectDir, testCase.deviceIdentifiersToBeStopped); + + assert.deepEqual(emittedDeviceIdentifiersForLiveSyncStoppedEvent, testCase.expectedDeviceIdentifiers); + }); + } + + }); + +}); diff --git a/test/stubs.ts b/test/stubs.ts index 1a4902a444..cfb77841db 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -500,6 +500,10 @@ export class LiveSyncServiceStub implements ILiveSyncService { public async stopLiveSync(projectDir: string): Promise { return; } + + public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { + return []; + } } export class AndroidToolsInfoStub implements IAndroidToolsInfo {