Skip to content

Commit fbad686

Browse files
committed
feat(limrun): long press on iOS direct sessions via HID touch primitives
The Limrun iOS interactor refused longpress as unsupported although the SDK exposes touchDown, wait, and touchUp for exactly this composition. Hold one touch as a single performActions batch so the duration runs on the device, default to the 800 ms the Android and Linux interactors use, and admit the longPress fact on the iOS leg.
1 parent 9173d3c commit fbad686

5 files changed

Lines changed: 80 additions & 3 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
- Added (limrun): `longpress` on Limrun iOS direct sessions. The interactor refused it as
6+
unsupported although the SDK exposes the HID primitives; it now holds one touch as a
7+
`performActions` batch of `touchDown`, `wait`, `touchUp`, defaulting to the 800 ms the Android
8+
and Linux interactors use when no duration is given.
59
- Added (ios): `type` and `fill` work in the Apple Pay sheet on iOS Simulator instead of failing
610
with `TEXT_INPUT_NOT_FOCUSED`. `com.apple.PassbookUIService` is served in place like the web
711
sign-in host (#2438).

‎packages/provider-limrun/src/interaction-operations.test.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
limrunSettingsOperationFacts,
99
limrunAppSwitcherOperationFacts,
1010
limrunClipboardOperationFacts,
11+
limrunInteractionOperationFacts,
1112
limrunNavigationOperationFacts,
1213
} from './interaction-operations.ts';
1314

@@ -212,3 +213,13 @@ test('binding omits every operation an unavailable fact refused', () => {
212213
expect(operations.home).toBeUndefined();
213214
expect(operations.tvRemote).toBeUndefined();
214215
});
216+
217+
test('long press is admitted on both direct-session legs and closed with a dead session', () => {
218+
expect(limrunInteractionOperationFacts(iosDevice).longPressPoint).toEqual({ available: true });
219+
expect(limrunInteractionOperationFacts(androidMobileDevice).longPressPoint).toEqual({
220+
available: true,
221+
});
222+
expect(limrunInteractionOperationFacts(iosDevice, liveSessionUnavailable).longPressPoint).toEqual(
223+
liveSessionUnavailable,
224+
);
225+
});

‎packages/provider-limrun/src/interaction-operations.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export function limrunInteractionOperationFacts(
164164
...touchRuntimeOperationFacts({
165165
unsupported: unsupportedTouch,
166166
tap: cell,
167-
longPress: liveSessionUnavailable ?? (isIosFamily(device) ? unsupportedTouch : cell),
167+
longPress: cell,
168168
// A dead session refuses hover for its own reason; the family builder adds the redirection
169169
// to longpress either way.
170170
hover: liveSessionUnavailable ?? unsupportedTouch,
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { expect, test, vi } from 'vitest';
2+
import { createLimrunIosInteractor, type LimrunIosSession } from './ios.ts';
3+
4+
function sessionWithClient() {
5+
const client = {
6+
tap: vi.fn(async () => {}),
7+
performActions: vi.fn(async () => ({ results: [] })),
8+
};
9+
const session = {
10+
platform: 'ios',
11+
instanceId: 'limrun-long-press-instance',
12+
client,
13+
} as unknown as LimrunIosSession;
14+
return { interactor: createLimrunIosInteractor(session), client };
15+
}
16+
17+
test('long press holds one touch on the device for the requested duration', async () => {
18+
const { interactor, client } = sessionWithClient();
19+
20+
await interactor.longPress(120, 340, 1200);
21+
22+
expect(client.performActions).toHaveBeenCalledTimes(1);
23+
expect(client.performActions).toHaveBeenCalledWith([
24+
{ type: 'touchDown', x: 120, y: 340 },
25+
{ type: 'wait', durationMs: 1200 },
26+
{ type: 'touchUp', x: 120, y: 340 },
27+
]);
28+
expect(client.tap).not.toHaveBeenCalled();
29+
});
30+
31+
test('an omitted duration holds for the 800 ms the other interactors default to', async () => {
32+
const { interactor, client } = sessionWithClient();
33+
34+
await interactor.longPress(40, 60);
35+
36+
expect(client.performActions).toHaveBeenCalledWith([
37+
{ type: 'touchDown', x: 40, y: 60 },
38+
{ type: 'wait', durationMs: 800 },
39+
{ type: 'touchUp', x: 40, y: 60 },
40+
]);
41+
});
42+
43+
test('a batch the device rejects surfaces as the long press failure', async () => {
44+
const { interactor, client } = sessionWithClient();
45+
client.performActions.mockRejectedValueOnce(new Error('touchDown: no foreground application'));
46+
47+
await expect(interactor.longPress(120, 340, 500)).rejects.toThrow(
48+
'touchDown: no foreground application',
49+
);
50+
});

‎packages/provider-limrun/src/ios.ts‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,17 @@ class LimrunIosInteractor implements Interactor {
217217
await this.tap(x, y);
218218
}
219219

220-
async longPress(): Promise<never> {
221-
throw unsupported('longpress', 'Limrun iOS direct sessions do not expose long press yet.');
220+
/** One held touch as HID primitives; the hold runs on the device, not across the wire. */
221+
async longPress(
222+
x: number,
223+
y: number,
224+
durationMs = DEFAULT_LONG_PRESS_DURATION_MS,
225+
): Promise<void> {
226+
await this.session.client.performActions([
227+
{ type: 'touchDown', x, y },
228+
{ type: 'wait', durationMs },
229+
{ type: 'touchUp', x, y },
230+
]);
222231
}
223232

224233
async focus(x: number, y: number): Promise<void> {
@@ -401,6 +410,9 @@ function inferAppNameFromPath(appPath: string): string | undefined {
401410

402411
const IOS_APP_INVENTORY_RETRY_DELAYS_MS = [0, 250] as const;
403412

413+
/** Hold applied when the caller names no duration; matches the Android and Linux interactors. */
414+
const DEFAULT_LONG_PRESS_DURATION_MS = 800;
415+
404416
function resolveInstalledIosAppId(params: {
405417
resultBundleId?: string;
406418
requestedBundleId?: string;

0 commit comments

Comments
 (0)