-
Notifications
You must be signed in to change notification settings - Fork 116
Expand file tree
/
Copy pathdispatch-resolve.test.ts
More file actions
175 lines (144 loc) · 6.05 KB
/
dispatch-resolve.test.ts
File metadata and controls
175 lines (144 loc) · 6.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
import { beforeEach, test, vi } from 'vitest';
import assert from 'node:assert/strict';
const { mockFindBootableIosSimulator, mockListAppleDevices } = vi.hoisted(() => ({
mockFindBootableIosSimulator: vi.fn(),
mockListAppleDevices: vi.fn(),
}));
vi.mock('../../platforms/ios/devices.ts', async (importOriginal) => {
const actual = await importOriginal<typeof import('../../platforms/ios/devices.ts')>();
return {
...actual,
findBootableIosSimulator: mockFindBootableIosSimulator,
listAppleDevices: mockListAppleDevices,
};
});
import {
resolveIosDevice,
resolveTargetDevice,
withResolveTargetDeviceCacheScope,
} from '../dispatch-resolve.ts';
import type { DeviceInfo } from '../../utils/device.ts';
import { AppError } from '../../utils/errors.ts';
const physical: DeviceInfo = {
platform: 'ios',
id: 'phys-1',
name: 'My iPhone',
kind: 'device',
target: 'mobile',
booted: true,
};
const simulator: DeviceInfo = {
platform: 'ios',
id: 'sim-1',
name: 'iPhone 16',
kind: 'simulator',
target: 'mobile',
booted: false,
};
const bootedSimulator: DeviceInfo = {
platform: 'ios',
id: 'sim-2',
name: 'iPhone 15',
kind: 'simulator',
target: 'mobile',
booted: true,
};
beforeEach(() => {
mockFindBootableIosSimulator.mockReset();
mockFindBootableIosSimulator.mockResolvedValue(null);
mockListAppleDevices.mockReset();
});
// --- Physical device rejected in favour of simulator fallback ---
test('resolveIosDevice prefers fallback simulator over auto-selected physical device', async () => {
mockFindBootableIosSimulator.mockResolvedValue(simulator);
const result = await resolveIosDevice([physical], { platform: 'ios' }, {});
assert.equal(result.id, 'sim-1');
assert.equal(result.kind, 'simulator');
});
test('resolveIosDevice falls back to physical device when no simulator is found', async () => {
const result = await resolveIosDevice([physical], { platform: 'ios' }, {});
assert.equal(result.id, 'phys-1');
assert.equal(result.kind, 'device');
});
// --- Explicit selectors bypass the fallback ---
test('resolveIosDevice keeps physical device when udid is explicit', async () => {
mockFindBootableIosSimulator.mockResolvedValue(simulator);
const result = await resolveIosDevice([physical], { platform: 'ios', udid: 'phys-1' }, {});
assert.equal(result.id, 'phys-1');
assert.equal(mockFindBootableIosSimulator.mock.calls.length, 0);
});
test('resolveIosDevice keeps physical device when deviceName is explicit', async () => {
mockFindBootableIosSimulator.mockResolvedValue(simulator);
const result = await resolveIosDevice(
[physical],
{ platform: 'ios', deviceName: 'My iPhone' },
{},
);
assert.equal(result.id, 'phys-1');
assert.equal(mockFindBootableIosSimulator.mock.calls.length, 0);
});
// --- Empty device list triggers fallback (P1-A: DEVICE_NOT_FOUND recovery) ---
test('resolveIosDevice recovers from empty device list via simulator fallback', async () => {
mockFindBootableIosSimulator.mockResolvedValue(simulator);
const result = await resolveIosDevice([], { platform: 'ios' }, {});
assert.equal(result.id, 'sim-1');
assert.equal(result.kind, 'simulator');
});
test('resolveIosDevice throws DEVICE_NOT_FOUND when empty list and no fallback simulator', async () => {
const err = await resolveIosDevice([], { platform: 'ios' }, {}).catch((e) => e);
assert.ok(err instanceof AppError);
assert.equal(err.code, 'DEVICE_NOT_FOUND');
});
test('resolveIosDevice rethrows DEVICE_NOT_FOUND from resolveDevice when explicit selector used', async () => {
mockFindBootableIosSimulator.mockResolvedValue(simulator);
const err = await resolveIosDevice([], { platform: 'ios', udid: 'nonexistent' }, {}).catch(
(e) => e,
);
assert.ok(err instanceof AppError);
assert.equal(err.code, 'DEVICE_NOT_FOUND');
});
// --- Simulator already in the device list (normal path) ---
test('resolveIosDevice returns simulator directly when present in device list', async () => {
const result = await resolveIosDevice([physical, bootedSimulator], { platform: 'ios' }, {});
assert.equal(result.id, 'sim-2');
assert.equal(result.kind, 'simulator');
assert.equal(mockFindBootableIosSimulator.mock.calls.length, 0);
});
test('resolveTargetDevice reuses request-scoped device resolution cache for identical selectors', async () => {
mockListAppleDevices.mockResolvedValue([bootedSimulator]);
const [first, second] = await withResolveTargetDeviceCacheScope(async () => [
await resolveTargetDevice({ platform: 'ios', device: 'iPhone 15' }),
await resolveTargetDevice({ platform: 'ios', device: 'iPhone 15' }),
]);
assert.equal(first.id, 'sim-2');
assert.equal(second.id, 'sim-2');
assert.equal(mockListAppleDevices.mock.calls.length, 1);
});
test('resolveTargetDevice request cache key separates device selectors', async () => {
mockListAppleDevices.mockResolvedValue([simulator, bootedSimulator]);
await withResolveTargetDeviceCacheScope(async () => {
await resolveTargetDevice({ platform: 'ios', device: 'iPhone 16' });
await resolveTargetDevice({ platform: 'ios', device: 'iPhone 15' });
});
assert.equal(mockListAppleDevices.mock.calls.length, 2);
});
test('resolveTargetDevice does not reuse cache across request scopes', async () => {
mockListAppleDevices.mockResolvedValue([bootedSimulator]);
await withResolveTargetDeviceCacheScope(
async () => await resolveTargetDevice({ platform: 'ios', device: 'iPhone 15' }),
);
await withResolveTargetDeviceCacheScope(
async () => await resolveTargetDevice({ platform: 'ios', device: 'iPhone 15' }),
);
assert.equal(mockListAppleDevices.mock.calls.length, 2);
});
test('resolveTargetDevice reuses cache across nested request scopes', async () => {
mockListAppleDevices.mockResolvedValue([bootedSimulator]);
await withResolveTargetDeviceCacheScope(async () => {
await resolveTargetDevice({ platform: 'ios', device: 'iPhone 15' });
await withResolveTargetDeviceCacheScope(
async () => await resolveTargetDevice({ platform: 'ios', device: 'iPhone 15' }),
);
});
assert.equal(mockListAppleDevices.mock.calls.length, 1);
});