Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions messages/prompts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
12 changes: 4 additions & 8 deletions src/shared/previewUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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);
Expand Down
52 changes: 50 additions & 2 deletions src/shared/promptUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -44,4 +50,46 @@ export class PromptUtils {

return response;
}

public static async promptUserToSelectMobileDevice(
platform: Platform.ios | Platform.android,
logger?: Logger
): Promise<BaseDevice> {
const availableDevices =
platform === Platform.ios
? await new AppleDeviceManager(logger).enumerateDevices()
: await new AndroidDeviceManager(logger).enumerateDevices();

if (!availableDevices || availableDevices.length === 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Could this line be shortened to availableDevices?.length === 0?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not. Coalesce operator returning null can by true to === 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}`;
}
}
}
80 changes: 47 additions & 33 deletions test/commands/lightning/dev/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,22 +144,50 @@ 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');
});

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<void> {
$$.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', () => {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -396,34 +440,4 @@ describe('lightning dev app', () => {
launchStub.restore();
}
});

async function verifyOrgOpen(expectedAppPath: string, deviceType?: Platform, appName?: string): Promise<void> {
$$.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,
],
]);
}
});
10 changes: 0 additions & 10 deletions test/shared/previewUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading