Skip to content

Commit a25fa75

Browse files
Fix stopLiveSync method (#3077)
* Fix stopLiveSync method Currently stopLiveSync method does not emit correct events in some cases. For example, when there's only one attached device and its identifier is passed to `stopLiveSync` method, we do not emit `liveSyncStopped` event. Fix the code and add unit tests to assure `liveSyncStopped` event is raised correctly. * Expose getLiveSyncDeviceDescriptors method Add new public method `getLiveSyncDeviceDescriptors` to `liveSyncService` - its purpose is to get information for current LiveSync operation.
1 parent ff5d1e5 commit a25fa75

File tree

5 files changed

+196
-9
lines changed

5 files changed

+196
-9
lines changed

PublicAPI.md

+23
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,29 @@ tns.liveSyncService.stopLiveSync(projectDir, deviceIdentifiers)
623623
});
624624
```
625625
626+
### getLiveSyncDeviceDescriptors
627+
Gives information for currently running LiveSync operation and parameters used to start it on each device.
628+
629+
* Definition
630+
```TypeScript
631+
/**
632+
* Returns the device information for current LiveSync operation of specified project.
633+
* In case LiveSync has been started on many devices, but stopped for some of them at a later point,
634+
* calling the method after that will return information only for devices for which LiveSync operation is in progress.
635+
* @param {string} projectDir The path to project for which the LiveSync operation is executed
636+
* @returns {ILiveSyncDeviceInfo[]} Array of elements describing parameters used to start LiveSync on each device.
637+
*/
638+
getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[];
639+
```
640+
641+
* Usage
642+
```JavaScript
643+
const projectDir = "myProjectDir";
644+
const deviceIdentifiers = [ "4df18f307d8a8f1b", "12318af23ebc0e25" ];
645+
const currentlyRunningDescriptors = tns.liveSyncService.getLiveSyncDeviceDescriptors(projectDir);
646+
console.log(`LiveSync for ${projectDir} is currently running on the following devices: ${currentlyRunningDescriptors.map(descriptor => descriptor.identifier)}`);
647+
```
648+
626649
### Events
627650
`liveSyncService` raises several events in order to provide information for current state of the operation.
628651
* 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:

lib/definitions/livesync.d.ts

+9
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,15 @@ interface ILiveSyncService {
185185
* @returns {Promise<void>}
186186
*/
187187
stopLiveSync(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise<void>;
188+
189+
/**
190+
* Returns the device information for current LiveSync operation of specified project.
191+
* In case LiveSync has been started on many devices, but stopped for some of them at a later point,
192+
* calling the method after that will return information only for devices for which LiveSync operation is in progress.
193+
* @param {string} projectDir The path to project for which the LiveSync operation is executed
194+
* @returns {ILiveSyncDeviceInfo[]} Array of elements describing parameters used to start LiveSync on each device.
195+
*/
196+
getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[];
188197
}
189198

190199
/**

lib/services/livesync/livesync-service.ts

+13-9
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const LiveSyncEvents = {
1717

1818
export class LiveSyncService extends EventEmitter implements ILiveSyncService {
1919
// key is projectDir
20-
private liveSyncProcessesInfo: IDictionary<ILiveSyncProcessInfo> = {};
20+
protected liveSyncProcessesInfo: IDictionary<ILiveSyncProcessInfo> = {};
2121

2222
constructor(protected $platformService: IPlatformService,
2323
private $projectDataService: IProjectDataService,
@@ -48,12 +48,10 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService {
4848
// so we cannot await it as this will cause infinite loop.
4949
const shouldAwaitPendingOperation = !stopOptions || stopOptions.shouldAwaitAllActions;
5050

51-
let removedDeviceIdentifiers: string[] = deviceIdentifiers || [];
51+
const deviceIdentifiersToRemove = deviceIdentifiers || _.map(liveSyncProcessInfo.deviceDescriptors, d => d.identifier);
5252

53-
_.each(deviceIdentifiers, deviceId => {
54-
removedDeviceIdentifiers = _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => descriptor.identifier === deviceId)
55-
.map(deviceDescriptor => deviceDescriptor.identifier);
56-
});
53+
const removedDeviceIdentifiers = _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => _.includes(deviceIdentifiersToRemove, descriptor.identifier))
54+
.map(descriptor => descriptor.identifier);
5755

5856
// In case deviceIdentifiers are not passed, we should stop the whole LiveSync.
5957
if (!deviceIdentifiers || !deviceIdentifiers.length || !liveSyncProcessInfo.deviceDescriptors || !liveSyncProcessInfo.deviceDescriptors.length) {
@@ -72,7 +70,6 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService {
7270
await liveSyncProcessInfo.actionsChain;
7371
}
7472

75-
removedDeviceIdentifiers = _.map(liveSyncProcessInfo.deviceDescriptors, d => d.identifier);
7673
liveSyncProcessInfo.deviceDescriptors = [];
7774

7875
// Kill typescript watcher
@@ -93,6 +90,12 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService {
9390
}
9491
}
9592

93+
public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] {
94+
const liveSyncProcessesInfo = this.liveSyncProcessesInfo[projectDir] || <ILiveSyncProcessInfo>{};
95+
const currentDescriptors = liveSyncProcessesInfo.deviceDescriptors;
96+
return currentDescriptors || [];
97+
}
98+
9699
protected async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo): Promise<void> {
97100
const platformLiveSyncService = this.getLiveSyncService(liveSyncResultInfo.deviceAppData.platform);
98101
try {
@@ -127,7 +130,8 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService {
127130
const isAlreadyLiveSyncing = this.liveSyncProcessesInfo[projectData.projectDir] && !this.liveSyncProcessesInfo[projectData.projectDir].isStopped;
128131

129132
// 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.
130-
const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors;
133+
const currentlyRunningDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectData.projectDir);
134+
const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, currentlyRunningDeviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors;
131135
this.setLiveSyncProcessInfo(liveSyncData.projectDir, deviceDescriptors);
132136

133137
await this.initialSync(projectData, deviceDescriptorsForInitialSync, liveSyncData);
@@ -146,7 +150,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService {
146150
this.liveSyncProcessesInfo[projectDir].currentSyncAction = this.liveSyncProcessesInfo[projectDir].actionsChain;
147151
this.liveSyncProcessesInfo[projectDir].isStopped = false;
148152

149-
const currentDeviceDescriptors = this.liveSyncProcessesInfo[projectDir].deviceDescriptors || [];
153+
const currentDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectDir);
150154
this.liveSyncProcessesInfo[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), deviceDescriptorPrimaryKey);
151155
}
152156

test/services/livesync-service.ts

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { Yok } from "../../lib/common/yok";
2+
import { assert } from "chai";
3+
import { LiveSyncService } from "../../lib/services/livesync/livesync-service";
4+
import { LoggerStub } from "../stubs";
5+
6+
const createTestInjector = (): IInjector => {
7+
const testInjector = new Yok();
8+
9+
testInjector.register("platformService", {});
10+
testInjector.register("projectDataService", {
11+
getProjectData: (projectDir: string): IProjectData => (<any>{})
12+
});
13+
14+
testInjector.register("devicesService", {});
15+
testInjector.register("mobileHelper", {});
16+
testInjector.register("devicePlatformsConstants", {});
17+
testInjector.register("nodeModulesDependenciesBuilder", {});
18+
testInjector.register("logger", LoggerStub);
19+
testInjector.register("processService", {});
20+
testInjector.register("hooksService", {
21+
executeAfterHooks: (commandName: string, hookArguments?: IDictionary<any>): Promise<void> => Promise.resolve()
22+
});
23+
24+
testInjector.register("pluginsService", {});
25+
testInjector.register("injector", testInjector);
26+
27+
return testInjector;
28+
};
29+
30+
class LiveSyncServiceInheritor extends LiveSyncService {
31+
constructor($platformService: IPlatformService,
32+
$projectDataService: IProjectDataService,
33+
$devicesService: Mobile.IDevicesService,
34+
$mobileHelper: Mobile.IMobileHelper,
35+
$devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
36+
$nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder,
37+
$logger: ILogger,
38+
$processService: IProcessService,
39+
$hooksService: IHooksService,
40+
$pluginsService: IPluginsService,
41+
$injector: IInjector) {
42+
43+
super(
44+
$platformService,
45+
$projectDataService,
46+
$devicesService,
47+
$mobileHelper,
48+
$devicePlatformsConstants,
49+
$nodeModulesDependenciesBuilder,
50+
$logger,
51+
$processService,
52+
$hooksService,
53+
$pluginsService,
54+
$injector
55+
);
56+
}
57+
58+
public liveSyncProcessesInfo: IDictionary<ILiveSyncProcessInfo> = {};
59+
}
60+
61+
interface IStopLiveSyncTestCase {
62+
name: string;
63+
currentDeviceIdentifiers: string[];
64+
expectedDeviceIdentifiers: string[];
65+
deviceIdentifiersToBeStopped?: string[];
66+
}
67+
68+
describe("liveSyncService", () => {
69+
describe("stopLiveSync", () => {
70+
const getLiveSyncProcessInfo = (): ILiveSyncProcessInfo => ({
71+
actionsChain: Promise.resolve(),
72+
currentSyncAction: Promise.resolve(),
73+
isStopped: false,
74+
timer: setTimeout(() => undefined, 1000),
75+
watcherInfo: {
76+
watcher: <any>{
77+
close: (): any => undefined
78+
},
79+
pattern: "pattern"
80+
},
81+
deviceDescriptors: []
82+
});
83+
84+
const getDeviceDescriptor = (identifier: string): ILiveSyncDeviceInfo => ({
85+
identifier,
86+
outputPath: "",
87+
skipNativePrepare: false,
88+
platformSpecificOptions: null,
89+
buildAction: () => Promise.resolve("")
90+
});
91+
92+
const testCases: IStopLiveSyncTestCase[] = [
93+
{
94+
name: "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers",
95+
currentDeviceIdentifiers: ["device1", "device2", "device3"],
96+
expectedDeviceIdentifiers: ["device1", "device2", "device3"]
97+
},
98+
{
99+
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)",
100+
currentDeviceIdentifiers: ["device1"],
101+
expectedDeviceIdentifiers: ["device1"]
102+
},
103+
{
104+
name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them (when a single device is attached)",
105+
currentDeviceIdentifiers: ["device1"],
106+
expectedDeviceIdentifiers: ["device1"],
107+
deviceIdentifiersToBeStopped: ["device1"]
108+
},
109+
{
110+
name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them",
111+
currentDeviceIdentifiers: ["device1", "device2", "device3"],
112+
expectedDeviceIdentifiers: ["device1", "device3"],
113+
deviceIdentifiersToBeStopped: ["device1", "device3"]
114+
},
115+
{
116+
name: "does not raise liveSyncStopped event for device, which is not currently being liveSynced",
117+
currentDeviceIdentifiers: ["device1", "device2", "device3"],
118+
expectedDeviceIdentifiers: ["device1"],
119+
deviceIdentifiersToBeStopped: ["device1", "device4"]
120+
}
121+
];
122+
123+
for (const testCase of testCases) {
124+
it(testCase.name, async () => {
125+
const testInjector = createTestInjector();
126+
const liveSyncService = testInjector.resolve<LiveSyncServiceInheritor>(LiveSyncServiceInheritor);
127+
const projectDir = "projectDir";
128+
const emittedDeviceIdentifiersForLiveSyncStoppedEvent: string[] = [];
129+
liveSyncService.on("liveSyncStopped", (data: { projectDir: string, deviceIdentifier: string }) => {
130+
assert.equal(data.projectDir, projectDir);
131+
emittedDeviceIdentifiersForLiveSyncStoppedEvent.push(data.deviceIdentifier);
132+
});
133+
134+
// Setup liveSyncProcessesInfo for current test
135+
liveSyncService.liveSyncProcessesInfo[projectDir] = getLiveSyncProcessInfo();
136+
const deviceDescriptors = testCase.currentDeviceIdentifiers.map(d => getDeviceDescriptor(d));
137+
liveSyncService.liveSyncProcessesInfo[projectDir].deviceDescriptors.push(...deviceDescriptors);
138+
139+
await liveSyncService.stopLiveSync(projectDir, testCase.deviceIdentifiersToBeStopped);
140+
141+
assert.deepEqual(emittedDeviceIdentifiersForLiveSyncStoppedEvent, testCase.expectedDeviceIdentifiers);
142+
});
143+
}
144+
145+
});
146+
147+
});

test/stubs.ts

+4
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,10 @@ export class LiveSyncServiceStub implements ILiveSyncService {
500500
public async stopLiveSync(projectDir: string): Promise<void> {
501501
return;
502502
}
503+
504+
public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] {
505+
return [];
506+
}
503507
}
504508

505509
export class AndroidToolsInfoStub implements IAndroidToolsInfo {

0 commit comments

Comments
 (0)