Skip to content

Commit 3ec3933

Browse files
authored
Merge branch 'master' into add-security-dependency-overrides-to-readme
2 parents a098f42 + d2fb038 commit 3ec3933

6 files changed

Lines changed: 245 additions & 8 deletions

File tree

src/extension/android/androidPlatform.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,40 @@ export class AndroidPlatform extends GeneralMobilePlatform {
208208
targetId = await this.getTargetIdForRunApp(onlineTargetsIds);
209209
this.packageName = await this.getPackageName();
210210
devicesIdsForLaunch = [targetId];
211+
212+
// Save target info for status indicator
213+
if (targetId) {
214+
const onlineTargets = await this.adbHelper.getOnlineTargets();
215+
const targetInfo = onlineTargets.find(t => t.id === targetId);
216+
if (targetInfo) {
217+
let deviceName = targetId;
218+
try {
219+
// For emulators, try to get AVD name first
220+
if (targetInfo.isVirtualTarget) {
221+
const avdName = await this.adbHelper.getAvdNameById(targetId);
222+
if (avdName) {
223+
deviceName = `${avdName} (${targetId})`;
224+
}
225+
} else {
226+
// For physical devices, get model name
227+
const modelResult = await this.adbHelper.executeQuery(
228+
targetId,
229+
"shell getprop ro.product.model",
230+
);
231+
const model = modelResult.trim();
232+
if (model) {
233+
deviceName = `${model} (${targetId})`;
234+
}
235+
}
236+
} catch (error) {}
237+
this.target = new AndroidTarget(
238+
targetInfo.isOnline,
239+
targetInfo.isVirtualTarget,
240+
targetInfo.id,
241+
deviceName,
242+
);
243+
}
244+
}
211245
}
212246
} catch (error) {
213247
if (!targetId) {

src/extension/appLauncher.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,9 @@ export class AppLauncher {
304304
try {
305305
if (this.mobilePlatform instanceof GeneralMobilePlatform) {
306306
generator.step("resolveMobileTarget");
307-
await this.resolveAndSaveMobileTarget(launchArgs, this.mobilePlatform);
307+
if (launchArgs.target) {
308+
await this.resolveAndSaveMobileTarget(launchArgs, this.mobilePlatform);
309+
}
308310
}
309311

310312
await this.mobilePlatform.beforeStartPackager();
@@ -339,6 +341,14 @@ export class AppLauncher {
339341
await this.mobilePlatform.runApp();
340342
}
341343

344+
// Show device name in status bar after app is launched
345+
if (this.mobilePlatform instanceof GeneralMobilePlatform) {
346+
const target = this.mobilePlatform.getResolvedTarget();
347+
if (target?.name) {
348+
DeviceStatusIndicator.show(target.name);
349+
}
350+
}
351+
342352
if (mobilePlatformOptions.isDirect) {
343353
if (launchArgs.useHermesEngine) {
344354
generator.step("mobilePlatform.enableHermesDebuggingMode");
@@ -598,11 +608,6 @@ export class AppLauncher {
598608
});
599609
}
600610
}
601-
602-
const target = mobilePlatform.getResolvedTarget();
603-
if (target?.name) {
604-
DeviceStatusIndicator.show(target.name);
605-
}
606611
}
607612
}
608613

src/extension/commands/launchAndroidEmulator.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import { AndroidTargetManager } from "../android/androidTargetManager";
99
import { TargetType } from "../generalPlatform";
1010
import { Command } from "./util/command";
1111

12-
// #todo> codeName differs from Class Name
13-
export class LaunchAndroidEmulator extends Command {
12+
export class LaunchAndroidSimulator extends Command {
1413
codeName = "launchAndroidSimulator";
1514
label = "Launch Android Emulator";
1615
error = ErrorHelper.getInternalError(InternalErrorCode.FailedToStartAndroidEmulator);

src/extension/ios/iOSPlatform.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,20 @@ export class IOSPlatform extends GeneralMobilePlatform {
224224
() => Promise.resolve(IOSPlatform.RUN_IOS_FAILURE_PATTERNS),
225225
PlatformType.iOS,
226226
).process(runIosSpawn);
227+
228+
// Save target info for status indicator
229+
if (!this.target) {
230+
const target = await this.getTarget();
231+
if (target && target.name) {
232+
this.target = new IOSTarget(
233+
target.isOnline,
234+
target.isVirtualTarget,
235+
target.id,
236+
target.name,
237+
target.system,
238+
);
239+
}
240+
}
227241
});
228242
}
229243

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for details.
3+
4+
import assert = require("assert");
5+
import * as vscode from "vscode";
6+
import * as sinon from "sinon";
7+
import { DeviceStatusIndicator } from "../../src/extension/deviceStatusIndicator";
8+
9+
function makeFakeStatusBarItem(): vscode.StatusBarItem {
10+
return {
11+
id: "",
12+
name: "",
13+
text: "",
14+
tooltip: undefined,
15+
color: undefined,
16+
backgroundColor: undefined,
17+
command: undefined,
18+
accessibilityInformation: undefined,
19+
alignment: vscode.StatusBarAlignment.Left,
20+
priority: undefined,
21+
show: sinon.stub(),
22+
hide: sinon.stub(),
23+
dispose: sinon.stub(),
24+
} as unknown as vscode.StatusBarItem;
25+
}
26+
27+
suite("DeviceStatusIndicator", function () {
28+
suite("extensionContext", function () {
29+
let createStatusBarItemStub: Sinon.SinonStub;
30+
let fakeItem: vscode.StatusBarItem;
31+
setup(() => {
32+
fakeItem = makeFakeStatusBarItem();
33+
createStatusBarItemStub = sinon.stub(vscode.window, "createStatusBarItem");
34+
createStatusBarItemStub.returns(fakeItem);
35+
});
36+
teardown(() => {
37+
const instance = (DeviceStatusIndicator as any).instance;
38+
if (instance) {
39+
instance.dispose();
40+
}
41+
createStatusBarItemStub.restore();
42+
});
43+
test("show should set correct icon and device name in text", function () {
44+
DeviceStatusIndicator.show("Test Device");
45+
assert.strictEqual(fakeItem.text, "$(device-mobile) Test Device");
46+
});
47+
test("show should set correct tooltip with device name", function () {
48+
DeviceStatusIndicator.show("Test Device");
49+
assert.strictEqual(fakeItem.tooltip, "Active debug target: Test Device");
50+
});
51+
test("show should call show on the status bar item", function () {
52+
DeviceStatusIndicator.show("Test Device");
53+
assert.ok((fakeItem.show as Sinon.SinonStub).calledOnce);
54+
});
55+
test("hide should call hide on the status bar item after show", function () {
56+
DeviceStatusIndicator.show("Test Device");
57+
DeviceStatusIndicator.hide();
58+
assert.ok((fakeItem.hide as Sinon.SinonStub).calledOnce);
59+
});
60+
test("hide before show should not throw", function () {
61+
assert.doesNotThrow(() => DeviceStatusIndicator.hide());
62+
});
63+
test("show should reuse singleton instance across multiple calls", function () {
64+
DeviceStatusIndicator.show("Device A");
65+
DeviceStatusIndicator.show("Device B");
66+
assert.strictEqual(createStatusBarItemStub.callCount, 1);
67+
assert.strictEqual(fakeItem.text, "$(device-mobile) Device B");
68+
});
69+
test("dispose should clear the singleton instance", function () {
70+
DeviceStatusIndicator.show("Test Device");
71+
const instance = (DeviceStatusIndicator as any).instance as DeviceStatusIndicator;
72+
instance.dispose();
73+
assert.strictEqual((DeviceStatusIndicator as any).instance, undefined);
74+
});
75+
});
76+
});
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for details.
3+
4+
import assert = require("assert");
5+
import * as vscode from "vscode";
6+
import * as sinon from "sinon";
7+
import {
8+
PackagerStatus,
9+
PackagerStatusIndicator,
10+
} from "../../src/extension/packagerStatusIndicator";
11+
import { SettingsHelper } from "../../src/extension/settingsHelper";
12+
13+
function makeFakeStatusBarItem(): vscode.StatusBarItem {
14+
return {
15+
id: "",
16+
name: "",
17+
text: "",
18+
tooltip: undefined,
19+
color: undefined,
20+
backgroundColor: undefined,
21+
command: undefined,
22+
accessibilityInformation: undefined,
23+
alignment: vscode.StatusBarAlignment.Left,
24+
priority: undefined,
25+
show: sinon.stub(),
26+
hide: sinon.stub(),
27+
dispose: sinon.stub(),
28+
} as unknown as vscode.StatusBarItem;
29+
}
30+
31+
suite("PackagerStatusIndicator", function () {
32+
suite("extensionContext", function () {
33+
let createStatusBarItemStub: Sinon.SinonStub;
34+
let getShowIndicatorStub: Sinon.SinonStub;
35+
let getPatternStub: Sinon.SinonStub;
36+
let fakeToggleItem: vscode.StatusBarItem;
37+
let fakeRestartItem: vscode.StatusBarItem;
38+
let indicator: PackagerStatusIndicator;
39+
const PROJECT_ROOT = "/workspace";
40+
setup(() => {
41+
fakeRestartItem = makeFakeStatusBarItem();
42+
fakeToggleItem = makeFakeStatusBarItem();
43+
createStatusBarItemStub = sinon.stub(vscode.window, "createStatusBarItem");
44+
createStatusBarItemStub.onFirstCall().returns(fakeRestartItem);
45+
createStatusBarItemStub.onSecondCall().returns(fakeToggleItem);
46+
getShowIndicatorStub = sinon.stub(SettingsHelper, "getShowIndicator").returns(true);
47+
getPatternStub = sinon
48+
.stub(SettingsHelper, "getPackagerStatusIndicatorPattern")
49+
.returns(PackagerStatusIndicator.FULL_VERSION);
50+
indicator = new PackagerStatusIndicator(PROJECT_ROOT);
51+
});
52+
teardown(() => {
53+
indicator.dispose();
54+
createStatusBarItemStub.restore();
55+
getShowIndicatorStub.restore();
56+
getPatternStub.restore();
57+
});
58+
test("should display port in full version when packager starts with port", function () {
59+
indicator.updatePackagerStatus(PackagerStatus.PACKAGER_STARTED, 8081);
60+
assert.ok(
61+
(fakeToggleItem.text as string).includes(":8081"),
62+
`Expected text to contain ':8081', got: ${fakeToggleItem.text}`,
63+
);
64+
});
65+
test("should not display port in full version when packager starts without port", function () {
66+
indicator.updatePackagerStatus(PackagerStatus.PACKAGER_STARTED);
67+
assert.ok(
68+
!(fakeToggleItem.text as string).includes(":"),
69+
`Expected text to not contain port, got: ${fakeToggleItem.text}`,
70+
);
71+
});
72+
test("should clear port from status bar text when packager stops", function () {
73+
indicator.updatePackagerStatus(PackagerStatus.PACKAGER_STARTED, 8081);
74+
indicator.updatePackagerStatus(PackagerStatus.PACKAGER_STOPPED);
75+
assert.ok(
76+
!(fakeToggleItem.text as string).includes(":8081"),
77+
`Expected text to not contain ':8081' after stop, got: ${fakeToggleItem.text}`,
78+
);
79+
});
80+
test("should display port in short version when packager starts with port", function () {
81+
getPatternStub.returns(PackagerStatusIndicator.SHORT_VERSION);
82+
indicator.updatePackagerStatus(PackagerStatus.PACKAGER_STARTED, 9090);
83+
assert.ok(
84+
(fakeToggleItem.text as string).includes(":9090"),
85+
`Expected short version text to contain ':9090', got: ${fakeToggleItem.text}`,
86+
);
87+
});
88+
test("should retain port across status transitions from starting to started", function () {
89+
indicator.updatePackagerStatus(PackagerStatus.PACKAGER_STARTING, 8081);
90+
indicator.updatePackagerStatus(PackagerStatus.PACKAGER_STARTED);
91+
assert.ok(
92+
(fakeToggleItem.text as string).includes(":8081"),
93+
`Expected port to be retained after STARTED, got: ${fakeToggleItem.text}`,
94+
);
95+
});
96+
test("should update port when called with a new port value", function () {
97+
indicator.updatePackagerStatus(PackagerStatus.PACKAGER_STARTED, 8081);
98+
indicator.updatePackagerStatus(PackagerStatus.PACKAGER_STARTED, 9090);
99+
assert.ok(
100+
(fakeToggleItem.text as string).includes(":9090"),
101+
`Expected text to contain updated port ':9090', got: ${fakeToggleItem.text}`,
102+
);
103+
assert.ok(
104+
!(fakeToggleItem.text as string).includes(":8081"),
105+
`Expected old port ':8081' to be gone, got: ${fakeToggleItem.text}`,
106+
);
107+
});
108+
});
109+
});

0 commit comments

Comments
 (0)