Skip to content

Commit 6dd57d9

Browse files
authored
feat(limrun): long press on iOS direct sessions via HID touch primitives (#2642)
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 6dd57d9

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)