diff --git a/messages/prompts.md b/messages/prompts.md index b184f3a1..0759094a 100644 --- a/messages/prompts.md +++ b/messages/prompts.md @@ -21,3 +21,11 @@ Mobile - Android # device-type.choice.ios Mobile - iOS + +# device-id.title + +Which device do you want to use for the preview? + +# error.device.enumeration + +Unable to enumerate a list of available devices. diff --git a/src/shared/previewUtils.ts b/src/shared/previewUtils.ts index c94f4954..5e8023ea 100644 --- a/src/shared/previewUtils.ts +++ b/src/shared/previewUtils.ts @@ -29,6 +29,7 @@ import { Progress, Spinner } from '@salesforce/sf-plugins-core'; import fetch from 'node-fetch'; import { ConfigUtils, LOCAL_DEV_SERVER_DEFAULT_HTTP_PORT, LocalWebServerIdentityData } from './configUtils.js'; import { OrgUtils } from './orgUtils.js'; +import { PromptUtils } from './promptUtils.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-lightning-dev', 'lightning.dev.app'); @@ -95,14 +96,9 @@ export class PreviewUtils { ? await new AppleDeviceManager(logger).getDevice(deviceId) : await new AndroidDeviceManager(logger).getDevice(deviceId); } else { - logger?.debug('No particular device was targeted by the user... fetching the first available device.'); - const devices = - platform === Platform.ios - ? await new AppleDeviceManager(logger).enumerateDevices() - : await new AndroidDeviceManager(logger).enumerateDevices(); - if (devices && devices.length > 0) { - device = devices[0]; - } + logger?.debug('Prompting the user to select a device.'); + + device = await PromptUtils.promptUserToSelectMobileDevice(platform, logger); } return Promise.resolve(device); diff --git a/src/shared/promptUtils.ts b/src/shared/promptUtils.ts index 648f8acc..4a89b221 100644 --- a/src/shared/promptUtils.ts +++ b/src/shared/promptUtils.ts @@ -6,8 +6,14 @@ */ import select from '@inquirer/select'; import { confirm } from '@inquirer/prompts'; -import { Messages } from '@salesforce/core'; -import { Platform } from '@salesforce/lwc-dev-mobile-core'; +import { Logger, Messages } from '@salesforce/core'; +import { + AndroidDeviceManager, + AppleDeviceManager, + BaseDevice, + Platform, + Version, +} from '@salesforce/lwc-dev-mobile-core'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-lightning-dev', 'prompts'); @@ -44,4 +50,46 @@ export class PromptUtils { return response; } + + public static async promptUserToSelectMobileDevice( + platform: Platform.ios | Platform.android, + logger?: Logger + ): Promise { + const availableDevices = + platform === Platform.ios + ? await new AppleDeviceManager(logger).enumerateDevices() + : await new AndroidDeviceManager(logger).enumerateDevices(); + + if (!availableDevices || availableDevices.length === 0) { + throw new Error(messages.getMessage('error.device.enumeration')); + } + + const choices = availableDevices.map((device) => ({ + name: `${device.name}, ${device.osType} ${this.getShortVersion(device.osVersion)}`, + value: device, + })); + + const response = await select({ + message: messages.getMessage('device-id.title'), + choices, + }); + + return response; + } + + // returns the shorthand version of a Version object (eg. 17.0.0 => 17, 17.4.0 => 17.4, 17.4.1 => 17.4.1) + private static getShortVersion(version: Version | string): string { + // TODO: consider making this function part of the Version class in @lwc-dev-mobile-core + if (typeof version === 'string') { + return version; // codenamed versions will be returned as is + } + + if (version.patch > 0) { + return `${version.major}.${version.minor}.${version.patch}`; + } else if (version.minor > 0) { + return `${version.major}.${version.minor}`; + } else { + return `${version.major}`; + } + } } diff --git a/test/commands/lightning/dev/app.test.ts b/test/commands/lightning/dev/app.test.ts index 6be4f791..c6935ee2 100644 --- a/test/commands/lightning/dev/app.test.ts +++ b/test/commands/lightning/dev/app.test.ts @@ -144,15 +144,13 @@ describe('lightning dev app', () => { } }); - describe('interactive', () => { + describe('desktop dev', () => { it('prompts user to select platform when not provided', async () => { const promptStub = $$.SANDBOX.stub(PromptUtils, 'promptUserToSelectPlatform').resolves(Platform.desktop); await verifyOrgOpen('lightning'); expect(promptStub.calledOnce); }); - }); - describe('desktop dev', () => { it('runs org:open with proper flags when app name provided', async () => { await verifyOrgOpen(`lightning/app/${testAppId}`, Platform.desktop, 'Sales'); }); @@ -160,6 +158,36 @@ describe('lightning dev app', () => { it('runs org:open with proper flags when no app name provided', async () => { await verifyOrgOpen('lightning', Platform.desktop); }); + + async function verifyOrgOpen(expectedAppPath: string, deviceType?: Platform, appName?: string): Promise { + $$.SANDBOX.stub(OrgUtils, 'getAppId').resolves(testAppId); + $$.SANDBOX.stub(PreviewUtils, 'generateWebSocketUrlForLocalDevServer').returns(testServerUrl); + $$.SANDBOX.stub(ConfigUtils, 'getIdentityData').resolves(testIdentityData); + + const runCmdStub = $$.SANDBOX.stub(OclifConfig.prototype, 'runCommand').resolves(); + const flags = ['--target-org', testOrgData.username]; + + if (deviceType) { + flags.push('--device-type', deviceType); + } + + if (appName) { + flags.push('--name', appName); + } + + await MockedLightningPreviewApp.run(flags); + + expect(runCmdStub.calledOnce); + expect(runCmdStub.getCall(0).args).to.deep.equal([ + 'org:open', + [ + '--path', + `${expectedAppPath}?0.aura.ldpServerUrl=${testServerUrl}&0.aura.ldpServerId=${testLdpServerId}&0.aura.mode=DEVPREVIEW`, + '--target-org', + testOrgData.username, + ], + ]); + } }); describe('mobile dev', () => { @@ -240,6 +268,22 @@ describe('lightning dev app', () => { await verifyMobileThrowsWhenUserDeclinesToInstallApp(Platform.android); }); + it('prompts user to select mobile device when not provided', async () => { + $$.SANDBOX.stub(OrgUtils, 'getAppId').resolves(testAppId); + $$.SANDBOX.stub(PreviewUtils, 'generateWebSocketUrlForLocalDevServer').returns(testServerUrl); + $$.SANDBOX.stub(ConfigUtils, 'getIdentityData').resolves(testIdentityData); + + $$.SANDBOX.stub(LwcDevMobileCoreSetup.prototype, 'init').resolves(); + $$.SANDBOX.stub(LwcDevMobileCoreSetup.prototype, 'run').resolves(); + + $$.SANDBOX.stub(PreviewUtils, 'generateSelfSignedCert').resolves(certData); + $$.SANDBOX.stub(MockedLightningPreviewApp.prototype, 'confirm').resolves(true); + + const promptStub = $$.SANDBOX.stub(PromptUtils, 'promptUserToSelectMobileDevice').resolves(testIOSDevice); + await verifyAppInstallAndLaunch(Platform.ios); + expect(promptStub.calledOnce); + }); + it('installs and launches app on mobile device', async () => { $$.SANDBOX.stub(OrgUtils, 'getAppId').resolves(testAppId); $$.SANDBOX.stub(PreviewUtils, 'generateWebSocketUrlForLocalDevServer').returns(testServerUrl); @@ -396,34 +440,4 @@ describe('lightning dev app', () => { launchStub.restore(); } }); - - async function verifyOrgOpen(expectedAppPath: string, deviceType?: Platform, appName?: string): Promise { - $$.SANDBOX.stub(OrgUtils, 'getAppId').resolves(testAppId); - $$.SANDBOX.stub(PreviewUtils, 'generateWebSocketUrlForLocalDevServer').returns(testServerUrl); - $$.SANDBOX.stub(ConfigUtils, 'getIdentityData').resolves(testIdentityData); - - const runCmdStub = $$.SANDBOX.stub(OclifConfig.prototype, 'runCommand').resolves(); - const flags = ['--target-org', testOrgData.username]; - - if (deviceType) { - flags.push('--device-type', deviceType); - } - - if (appName) { - flags.push('--name', appName); - } - - await MockedLightningPreviewApp.run(flags); - - expect(runCmdStub.calledOnce); - expect(runCmdStub.getCall(0).args).to.deep.equal([ - 'org:open', - [ - '--path', - `${expectedAppPath}?0.aura.ldpServerUrl=${testServerUrl}&0.aura.ldpServerId=${testLdpServerId}&0.aura.mode=DEVPREVIEW`, - '--target-org', - testOrgData.username, - ], - ]); - } }); diff --git a/test/shared/previewUtils.test.ts b/test/shared/previewUtils.test.ts index f1f53c93..39818ff3 100644 --- a/test/shared/previewUtils.test.ts +++ b/test/shared/previewUtils.test.ts @@ -103,16 +103,6 @@ describe('previewUtils', () => { expect(androidDevice).to.deep.equal(testAndroidDevice); }); - it('getMobileDevice returns first available device', async () => { - $$.SANDBOX.stub(AppleDeviceManager.prototype, 'enumerateDevices').resolves([testIOSDevice]); - const iosDevice = await PreviewUtils.getMobileDevice(Platform.ios); - expect(iosDevice).to.deep.equal(testIOSDevice); - - $$.SANDBOX.stub(AndroidDeviceManager.prototype, 'enumerateDevices').resolves([testAndroidDevice]); - const androidDevice = await PreviewUtils.getMobileDevice(Platform.android); - expect(androidDevice).to.deep.equal(testAndroidDevice); - }); - it('generateDesktopPreviewLaunchArguments', async () => { expect( PreviewUtils.generateDesktopPreviewLaunchArguments(