Skip to content

Commit fcbd1f5

Browse files
authored
Improve device handling with fallback support and comprehensive tests (#209)
* Improve device handling with fallback support and add comprehensive tests This PR enhances device handling in SweetPad to better support both modern (iOS 17+) and legacy devices: - Enhanced device type definitions with safe defaults for missing fields - Added fallback lookups using xcdevice for devices with incomplete devicectl data - Added deviceUtils module for OS version comparison utilities - Created xcdevice module to query device info from Xcode's xcdevice tool - Created ios-deploy module for deploying to iOS < 17 devices - Added comprehensive test coverage for device management - Added test fixtures with real-world devicectl output samples - Gracefully handle missing device name, OS version, and UDID fields This improves reliability when working with older devices that may not return complete information through devicectl. * Update tests * Improve device handling by ensuring UDID fallback to "unknown" for iOS, watchOS, tvOS, and visionOS devices --------- Co-authored-by: quang.tranminh <quang.tranminh>
1 parent ab5a11b commit fcbd1f5

26 files changed

+3503
-127
lines changed

jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ module.exports = {
44
testEnvironment: "node",
55
setupFiles: ["<rootDir>/tests/setup.js"],
66
moduleNameMapper: {
7-
"^vscode$": "<rootDir>/tests/__mocks__/vscode",
7+
"^vscode$": "<rootDir>/src/__mocks__/vscode",
88
},
99
};

package-lock.json

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/__mocks__/devices.ts

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/**
2+
* Mock utilities for device-related tests
3+
* Provides helper functions to generate mock device objects and test contexts
4+
*/
5+
6+
import type { ExtensionContext } from "../common/commands";
7+
import type { TaskTerminal } from "../common/tasks";
8+
import type { DeviceCtlDevice } from "../common/xcode/devicectl";
9+
import type { XcdeviceDevice } from "../common/xcode/xcdevice";
10+
11+
/**
12+
* Create a mock DeviceCtlDevice object with optional overrides
13+
*/
14+
export function createMockDevice(overrides: Partial<DeviceCtlDevice> = {}): DeviceCtlDevice {
15+
return {
16+
capabilities: [],
17+
connectionProperties: {
18+
tunnelState: "connected",
19+
pairingState: "paired",
20+
},
21+
deviceProperties: {
22+
name: "iPhone 14 Pro",
23+
osVersionNumber: "17.0",
24+
},
25+
hardwareProperties: {
26+
deviceType: "iPhone",
27+
marketingName: "iPhone 14 Pro",
28+
productType: "iPhone15,2",
29+
udid: "00008110-001234567890001E",
30+
platform: "iOS",
31+
},
32+
identifier: "urn:x-ios-devicectl:device-CS4567890-1234567890123456",
33+
visibilityClass: "default",
34+
...overrides,
35+
};
36+
}
37+
38+
/**
39+
* Create a mock XcdeviceDevice object with optional overrides
40+
*/
41+
export function createMockXcdeviceDevice(overrides: Partial<XcdeviceDevice> = {}): XcdeviceDevice {
42+
return {
43+
identifier: "00008110-001234567890001E",
44+
modelCode: "iPhone15,2",
45+
name: "iPhone 14 Pro",
46+
operatingSystemVersion: "16.7.12",
47+
platform: "com.apple.platform.iphoneos",
48+
...overrides,
49+
};
50+
}
51+
52+
/**
53+
* Create a mock ExtensionContext for testing
54+
*/
55+
export function createMockContext(overrides: Partial<ExtensionContext> = {}): ExtensionContext {
56+
return {
57+
startExecutionScope: jest.fn().mockImplementation(async (_scope, callback) => {
58+
return await callback();
59+
}),
60+
updateProgressStatus: jest.fn(),
61+
getWorkspaceState: jest.fn().mockReturnValue(undefined),
62+
updateWorkspaceState: jest.fn(),
63+
buildManager: {} as any,
64+
destinationsManager: {} as any,
65+
...overrides,
66+
} as unknown as ExtensionContext;
67+
}
68+
69+
/**
70+
* Create a mock TaskTerminal for testing
71+
*/
72+
export function createMockTerminal(): TaskTerminal {
73+
return {
74+
execute: jest.fn().mockResolvedValue(undefined),
75+
write: jest.fn(),
76+
} as unknown as TaskTerminal;
77+
}
78+
79+
/**
80+
* Helper to create a mock device with specific OS version
81+
*/
82+
export function createMockDeviceWithOS(osVersion: string): DeviceCtlDevice {
83+
return createMockDevice({
84+
deviceProperties: {
85+
name: "iPhone Test Device",
86+
osVersionNumber: osVersion,
87+
},
88+
});
89+
}
90+
91+
/**
92+
* Helper to create a mock device of a specific type
93+
*/
94+
export function createMockDeviceOfType(
95+
deviceType: "iPhone" | "iPad" | "appleWatch" | "appleTV" | "appleVision",
96+
): DeviceCtlDevice {
97+
const hardwareProps: Record<string, any> = {
98+
iPhone: {
99+
deviceType: "iPhone",
100+
marketingName: "iPhone 14 Pro",
101+
productType: "iPhone15,2",
102+
},
103+
iPad: {
104+
deviceType: "iPad",
105+
marketingName: 'iPad Pro 12.9"',
106+
productType: "iPad14,5",
107+
},
108+
appleWatch: {
109+
deviceType: "appleWatch",
110+
marketingName: "Apple Watch Series 9",
111+
productType: "Watch10,1",
112+
},
113+
appleTV: {
114+
deviceType: "appleTV",
115+
marketingName: "Apple TV 4K",
116+
productType: "AppleTV14,1",
117+
},
118+
appleVision: {
119+
deviceType: "appleVision",
120+
marketingName: "Apple Vision Pro",
121+
productType: "VisionPro,1",
122+
},
123+
};
124+
125+
return createMockDevice({
126+
hardwareProperties: {
127+
...createMockDevice().hardwareProperties,
128+
...hardwareProps[deviceType],
129+
},
130+
});
131+
}
132+
133+
/**
134+
* Create a mock device without OS version (for testing fallback behavior)
135+
*/
136+
export function createMockDeviceWithoutOS(): DeviceCtlDevice {
137+
return createMockDevice({
138+
deviceProperties: {
139+
name: "iPhone Unknown",
140+
},
141+
hardwareProperties: {
142+
...createMockDevice().hardwareProperties,
143+
udid: undefined,
144+
},
145+
});
146+
}
147+
148+
/**
149+
* Create a mock device without UDID (for testing fallback behavior)
150+
*/
151+
export function createMockDeviceWithoutUDID(): DeviceCtlDevice {
152+
return createMockDevice({
153+
hardwareProperties: {
154+
...createMockDevice().hardwareProperties,
155+
udid: undefined,
156+
},
157+
});
158+
}
159+
160+
/**
161+
* Create a mock device with missing name (for testing fallback behavior)
162+
*/
163+
export function createMockDeviceWithoutName(): DeviceCtlDevice {
164+
return createMockDevice({
165+
deviceProperties: {
166+
osVersionNumber: "17.0",
167+
},
168+
hardwareProperties: {
169+
...createMockDevice().hardwareProperties,
170+
marketingName: undefined,
171+
},
172+
});
173+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export const window = {
33
createOutputChannel: jest.fn(() => ({
44
appendLine: jest.fn(),
55
show: jest.fn(),
6+
clear: jest.fn(),
67
})),
78
};
89

0 commit comments

Comments
 (0)