Skip to content

Commit 6fa575c

Browse files
authored
feat: prompt user to select mobile device (#196)
* feat: prompt user to select mobile device * chore: remove old unit test
1 parent 8133060 commit 6fa575c

File tree

5 files changed

+109
-53
lines changed

5 files changed

+109
-53
lines changed

messages/prompts.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,11 @@ Mobile - Android
2121
# device-type.choice.ios
2222

2323
Mobile - iOS
24+
25+
# device-id.title
26+
27+
Which device do you want to use for the preview?
28+
29+
# error.device.enumeration
30+
31+
Unable to enumerate a list of available devices.

src/shared/previewUtils.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { Progress, Spinner } from '@salesforce/sf-plugins-core';
2929
import fetch from 'node-fetch';
3030
import { ConfigUtils, LOCAL_DEV_SERVER_DEFAULT_HTTP_PORT, LocalWebServerIdentityData } from './configUtils.js';
3131
import { OrgUtils } from './orgUtils.js';
32+
import { PromptUtils } from './promptUtils.js';
3233

3334
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
3435
const messages = Messages.loadMessages('@salesforce/plugin-lightning-dev', 'lightning.dev.app');
@@ -95,14 +96,9 @@ export class PreviewUtils {
9596
? await new AppleDeviceManager(logger).getDevice(deviceId)
9697
: await new AndroidDeviceManager(logger).getDevice(deviceId);
9798
} else {
98-
logger?.debug('No particular device was targeted by the user... fetching the first available device.');
99-
const devices =
100-
platform === Platform.ios
101-
? await new AppleDeviceManager(logger).enumerateDevices()
102-
: await new AndroidDeviceManager(logger).enumerateDevices();
103-
if (devices && devices.length > 0) {
104-
device = devices[0];
105-
}
99+
logger?.debug('Prompting the user to select a device.');
100+
101+
device = await PromptUtils.promptUserToSelectMobileDevice(platform, logger);
106102
}
107103

108104
return Promise.resolve(device);

src/shared/promptUtils.ts

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@
66
*/
77
import select from '@inquirer/select';
88
import { confirm } from '@inquirer/prompts';
9-
import { Messages } from '@salesforce/core';
10-
import { Platform } from '@salesforce/lwc-dev-mobile-core';
9+
import { Logger, Messages } from '@salesforce/core';
10+
import {
11+
AndroidDeviceManager,
12+
AppleDeviceManager,
13+
BaseDevice,
14+
Platform,
15+
Version,
16+
} from '@salesforce/lwc-dev-mobile-core';
1117

1218
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
1319
const messages = Messages.loadMessages('@salesforce/plugin-lightning-dev', 'prompts');
@@ -44,4 +50,46 @@ export class PromptUtils {
4450

4551
return response;
4652
}
53+
54+
public static async promptUserToSelectMobileDevice(
55+
platform: Platform.ios | Platform.android,
56+
logger?: Logger
57+
): Promise<BaseDevice> {
58+
const availableDevices =
59+
platform === Platform.ios
60+
? await new AppleDeviceManager(logger).enumerateDevices()
61+
: await new AndroidDeviceManager(logger).enumerateDevices();
62+
63+
if (!availableDevices || availableDevices.length === 0) {
64+
throw new Error(messages.getMessage('error.device.enumeration'));
65+
}
66+
67+
const choices = availableDevices.map((device) => ({
68+
name: `${device.name}, ${device.osType} ${this.getShortVersion(device.osVersion)}`,
69+
value: device,
70+
}));
71+
72+
const response = await select({
73+
message: messages.getMessage('device-id.title'),
74+
choices,
75+
});
76+
77+
return response;
78+
}
79+
80+
// returns the shorthand version of a Version object (eg. 17.0.0 => 17, 17.4.0 => 17.4, 17.4.1 => 17.4.1)
81+
private static getShortVersion(version: Version | string): string {
82+
// TODO: consider making this function part of the Version class in @lwc-dev-mobile-core
83+
if (typeof version === 'string') {
84+
return version; // codenamed versions will be returned as is
85+
}
86+
87+
if (version.patch > 0) {
88+
return `${version.major}.${version.minor}.${version.patch}`;
89+
} else if (version.minor > 0) {
90+
return `${version.major}.${version.minor}`;
91+
} else {
92+
return `${version.major}`;
93+
}
94+
}
4795
}

test/commands/lightning/dev/app.test.ts

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -144,22 +144,50 @@ describe('lightning dev app', () => {
144144
}
145145
});
146146

147-
describe('interactive', () => {
147+
describe('desktop dev', () => {
148148
it('prompts user to select platform when not provided', async () => {
149149
const promptStub = $$.SANDBOX.stub(PromptUtils, 'promptUserToSelectPlatform').resolves(Platform.desktop);
150150
await verifyOrgOpen('lightning');
151151
expect(promptStub.calledOnce);
152152
});
153-
});
154153

155-
describe('desktop dev', () => {
156154
it('runs org:open with proper flags when app name provided', async () => {
157155
await verifyOrgOpen(`lightning/app/${testAppId}`, Platform.desktop, 'Sales');
158156
});
159157

160158
it('runs org:open with proper flags when no app name provided', async () => {
161159
await verifyOrgOpen('lightning', Platform.desktop);
162160
});
161+
162+
async function verifyOrgOpen(expectedAppPath: string, deviceType?: Platform, appName?: string): Promise<void> {
163+
$$.SANDBOX.stub(OrgUtils, 'getAppId').resolves(testAppId);
164+
$$.SANDBOX.stub(PreviewUtils, 'generateWebSocketUrlForLocalDevServer').returns(testServerUrl);
165+
$$.SANDBOX.stub(ConfigUtils, 'getIdentityData').resolves(testIdentityData);
166+
167+
const runCmdStub = $$.SANDBOX.stub(OclifConfig.prototype, 'runCommand').resolves();
168+
const flags = ['--target-org', testOrgData.username];
169+
170+
if (deviceType) {
171+
flags.push('--device-type', deviceType);
172+
}
173+
174+
if (appName) {
175+
flags.push('--name', appName);
176+
}
177+
178+
await MockedLightningPreviewApp.run(flags);
179+
180+
expect(runCmdStub.calledOnce);
181+
expect(runCmdStub.getCall(0).args).to.deep.equal([
182+
'org:open',
183+
[
184+
'--path',
185+
`${expectedAppPath}?0.aura.ldpServerUrl=${testServerUrl}&0.aura.ldpServerId=${testLdpServerId}&0.aura.mode=DEVPREVIEW`,
186+
'--target-org',
187+
testOrgData.username,
188+
],
189+
]);
190+
}
163191
});
164192

165193
describe('mobile dev', () => {
@@ -240,6 +268,22 @@ describe('lightning dev app', () => {
240268
await verifyMobileThrowsWhenUserDeclinesToInstallApp(Platform.android);
241269
});
242270

271+
it('prompts user to select mobile device when not provided', async () => {
272+
$$.SANDBOX.stub(OrgUtils, 'getAppId').resolves(testAppId);
273+
$$.SANDBOX.stub(PreviewUtils, 'generateWebSocketUrlForLocalDevServer').returns(testServerUrl);
274+
$$.SANDBOX.stub(ConfigUtils, 'getIdentityData').resolves(testIdentityData);
275+
276+
$$.SANDBOX.stub(LwcDevMobileCoreSetup.prototype, 'init').resolves();
277+
$$.SANDBOX.stub(LwcDevMobileCoreSetup.prototype, 'run').resolves();
278+
279+
$$.SANDBOX.stub(PreviewUtils, 'generateSelfSignedCert').resolves(certData);
280+
$$.SANDBOX.stub(MockedLightningPreviewApp.prototype, 'confirm').resolves(true);
281+
282+
const promptStub = $$.SANDBOX.stub(PromptUtils, 'promptUserToSelectMobileDevice').resolves(testIOSDevice);
283+
await verifyAppInstallAndLaunch(Platform.ios);
284+
expect(promptStub.calledOnce);
285+
});
286+
243287
it('installs and launches app on mobile device', async () => {
244288
$$.SANDBOX.stub(OrgUtils, 'getAppId').resolves(testAppId);
245289
$$.SANDBOX.stub(PreviewUtils, 'generateWebSocketUrlForLocalDevServer').returns(testServerUrl);
@@ -396,34 +440,4 @@ describe('lightning dev app', () => {
396440
launchStub.restore();
397441
}
398442
});
399-
400-
async function verifyOrgOpen(expectedAppPath: string, deviceType?: Platform, appName?: string): Promise<void> {
401-
$$.SANDBOX.stub(OrgUtils, 'getAppId').resolves(testAppId);
402-
$$.SANDBOX.stub(PreviewUtils, 'generateWebSocketUrlForLocalDevServer').returns(testServerUrl);
403-
$$.SANDBOX.stub(ConfigUtils, 'getIdentityData').resolves(testIdentityData);
404-
405-
const runCmdStub = $$.SANDBOX.stub(OclifConfig.prototype, 'runCommand').resolves();
406-
const flags = ['--target-org', testOrgData.username];
407-
408-
if (deviceType) {
409-
flags.push('--device-type', deviceType);
410-
}
411-
412-
if (appName) {
413-
flags.push('--name', appName);
414-
}
415-
416-
await MockedLightningPreviewApp.run(flags);
417-
418-
expect(runCmdStub.calledOnce);
419-
expect(runCmdStub.getCall(0).args).to.deep.equal([
420-
'org:open',
421-
[
422-
'--path',
423-
`${expectedAppPath}?0.aura.ldpServerUrl=${testServerUrl}&0.aura.ldpServerId=${testLdpServerId}&0.aura.mode=DEVPREVIEW`,
424-
'--target-org',
425-
testOrgData.username,
426-
],
427-
]);
428-
}
429443
});

test/shared/previewUtils.test.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,6 @@ describe('previewUtils', () => {
103103
expect(androidDevice).to.deep.equal(testAndroidDevice);
104104
});
105105

106-
it('getMobileDevice returns first available device', async () => {
107-
$$.SANDBOX.stub(AppleDeviceManager.prototype, 'enumerateDevices').resolves([testIOSDevice]);
108-
const iosDevice = await PreviewUtils.getMobileDevice(Platform.ios);
109-
expect(iosDevice).to.deep.equal(testIOSDevice);
110-
111-
$$.SANDBOX.stub(AndroidDeviceManager.prototype, 'enumerateDevices').resolves([testAndroidDevice]);
112-
const androidDevice = await PreviewUtils.getMobileDevice(Platform.android);
113-
expect(androidDevice).to.deep.equal(testAndroidDevice);
114-
});
115-
116106
it('generateDesktopPreviewLaunchArguments', async () => {
117107
expect(
118108
PreviewUtils.generateDesktopPreviewLaunchArguments(

0 commit comments

Comments
 (0)