Skip to content

Commit 46ccce3

Browse files
committed
test(focus): cover the owning interactor binders, lower the find ratchet
Review follow-ups on #1925. P1: focus-runtime.test.ts bound a fake focusPoint, so deleting the interactor call inside bindLocalFocusInteractor left focus a successful no-op with every test green. Adds packages/contracts/src/focus-runtime.test.ts, which executes both binders and asserts resolver context, positional (x, y) forwarding, the structured missing-provider failure, and that an already-cancelled request never resolves an interactor at all. Two planted mutants confirm it bites: removing `await interactor.focus(input.point.x, input.point.y)` and transposing its two arguments each fail exactly the two forwarding tests, while the daemon-level focus and find suites stay green — which is the gap the reviewer named. Coverage: find.test.ts shrank to 1204 lines when its focus assertion moved off the dispatch mock; the ratchet pin follows it down.
1 parent 1aecdbd commit 46ccce3

2 files changed

Lines changed: 90 additions & 1 deletion

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { expect, test, vi } from 'vitest';
2+
import {
3+
bindLocalFocusInteractor,
4+
bindProviderFocusInteractor,
5+
focusRuntimeOperationFacts,
6+
} from './focus-runtime.ts';
7+
import type { Interactor } from './interactor-types.ts';
8+
9+
const device = {
10+
platform: 'android',
11+
id: 'emulator-5554',
12+
name: 'Pixel',
13+
kind: 'emulator',
14+
booted: true,
15+
} as const;
16+
17+
test('builds the exact focus operation fact catalog', () => {
18+
const focus = { available: true } as const;
19+
expect(focusRuntimeOperationFacts({ focus })).toEqual({ focusPoint: focus });
20+
});
21+
22+
test('a local binding drives the interactor at the resolved point', async () => {
23+
const focus = vi.fn(async () => undefined);
24+
const resolveInteractor = vi.fn(async () => ({ focus }) as unknown as Interactor);
25+
const signal = new AbortController().signal;
26+
27+
const operations = bindLocalFocusInteractor({ device, signal, resolveInteractor });
28+
await operations.focusPoint({
29+
point: { x: 176, y: 132 },
30+
options: { appBundleId: 'com.example.app' },
31+
execution: { logPath: '/tmp/daemon.log', requestId: 'focus-1' },
32+
});
33+
34+
expect(resolveInteractor).toHaveBeenCalledWith(device, {
35+
logPath: '/tmp/daemon.log',
36+
requestId: 'focus-1',
37+
appBundleId: 'com.example.app',
38+
signal,
39+
});
40+
// Positional (x, y), not an object: the `Interactor` seam takes two numbers, and swapping them
41+
// is the one transposition a point-shaped assertion would not catch.
42+
expect(focus).toHaveBeenCalledWith(176, 132);
43+
});
44+
45+
test('a provider binding drives its own resolved interactor at the point', async () => {
46+
const focus = vi.fn(async () => undefined);
47+
const resolveInteractor = vi.fn(() => ({ focus }) as unknown as Interactor);
48+
const signal = new AbortController().signal;
49+
50+
const operations = bindProviderFocusInteractor({ device, signal, resolveInteractor });
51+
await operations.focusPoint({ point: { x: 12, y: 34 }, execution: { requestId: 'focus-2' } });
52+
53+
expect(resolveInteractor).toHaveBeenCalledWith({
54+
requestId: 'focus-2',
55+
appBundleId: undefined,
56+
signal,
57+
});
58+
expect(focus).toHaveBeenCalledWith(12, 34);
59+
});
60+
61+
test('a provider binding fails closed when its exact owner exposes no interactor', async () => {
62+
const operations = bindProviderFocusInteractor({
63+
device,
64+
signal: new AbortController().signal,
65+
resolveInteractor: () => undefined,
66+
});
67+
68+
await expect(operations.focusPoint({ point: { x: 1, y: 2 } })).rejects.toMatchObject({
69+
code: 'UNSUPPORTED_OPERATION',
70+
details: { reason: 'provider-runtime-interactor-missing', deviceId: device.id },
71+
});
72+
});
73+
74+
test('an already-cancelled request never resolves an interactor', async () => {
75+
const controller = new AbortController();
76+
controller.abort();
77+
const focus = vi.fn(async () => undefined);
78+
const resolveInteractor = vi.fn(async () => ({ focus }) as unknown as Interactor);
79+
80+
const operations = bindLocalFocusInteractor({
81+
device,
82+
signal: controller.signal,
83+
resolveInteractor,
84+
});
85+
86+
await expect(operations.focusPoint({ point: { x: 1, y: 2 } })).rejects.toThrow();
87+
expect(resolveInteractor).not.toHaveBeenCalled();
88+
expect(focus).not.toHaveBeenCalled();
89+
});

‎src/__tests__/test-file-size-ratchet.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ const PINNED_TEST_FILE_LINES: Readonly<Record<string, number>> = Object.freeze({
4848
'src/platforms/apple/core/__tests__/runner-command-retry.test.ts': 1327,
4949
'src/__tests__/cli-client-commands.test.ts': 1317,
5050
'src/__tests__/cli-config.test.ts': 1282,
51-
'src/daemon/handlers/__tests__/find.test.ts': 1207,
51+
'src/daemon/handlers/__tests__/find.test.ts': 1204,
5252
'src/platforms/apple/core/__tests__/perf.test.ts': 1222,
5353
'src/mcp/__tests__/command-tools.test.ts': 1218,
5454
'src/daemon/handlers/__tests__/session-replay-divergence.test.ts': 1215,

0 commit comments

Comments
 (0)