react-native-tapline 1.5.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/TaplineNative.podspec +3 -1
- package/dist/actions.d.ts +22 -2
- package/dist/actions.js +186 -16
- package/dist/actions.js.map +1 -1
- package/dist/alerts.d.ts +2 -0
- package/dist/alerts.js +5 -0
- package/dist/alerts.js.map +1 -1
- package/dist/anim.d.ts +25 -0
- package/dist/anim.js +311 -0
- package/dist/anim.js.map +1 -0
- package/dist/appinfo.d.ts +12 -0
- package/dist/appinfo.js +78 -0
- package/dist/appinfo.js.map +1 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +25 -2
- package/dist/client.js.map +1 -1
- package/dist/commands.js +28 -0
- package/dist/commands.js.map +1 -1
- package/dist/guard.d.ts +17 -0
- package/dist/guard.js +44 -0
- package/dist/guard.js.map +1 -0
- package/dist/idle.d.ts +43 -0
- package/dist/idle.js +379 -0
- package/dist/idle.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +11 -0
- package/dist/inspect.js +36 -7
- package/dist/inspect.js.map +1 -1
- package/dist/loading.d.ts +17 -0
- package/dist/loading.js +96 -0
- package/dist/loading.js.map +1 -0
- package/dist/locale.js +1 -1
- package/dist/locale.js.map +1 -1
- package/dist/methods.js +8 -0
- package/dist/methods.js.map +1 -1
- package/dist/move.d.ts +40 -0
- package/dist/move.js +125 -0
- package/dist/move.js.map +1 -0
- package/dist/native.d.ts +5 -0
- package/dist/native.js.map +1 -1
- package/dist/network.d.ts +4 -0
- package/dist/network.js +29 -0
- package/dist/network.js.map +1 -1
- package/dist/place.d.ts +23 -0
- package/dist/place.js +66 -0
- package/dist/place.js.map +1 -0
- package/dist/placekey.d.ts +29 -0
- package/dist/placekey.js +134 -0
- package/dist/placekey.js.map +1 -0
- package/dist/routetree.d.ts +27 -0
- package/dist/routetree.js +216 -0
- package/dist/routetree.js.map +1 -0
- package/dist/types.d.ts +23 -2
- package/dist/types.js +5 -1
- package/dist/types.js.map +1 -1
- package/dist/wait.d.ts +5 -0
- package/dist/wait.js +4 -0
- package/dist/wait.js.map +1 -1
- package/ios/TaplineNativeModule.swift +61 -0
- package/package.json +1 -1
package/TaplineNative.podspec
CHANGED
|
@@ -5,7 +5,9 @@ Pod::Spec.new do |s|
|
|
|
5
5
|
s.license = { :type => 'MIT' }
|
|
6
6
|
s.author = 'Tapline'
|
|
7
7
|
s.homepage = 'https://github.com/tapline/tapline'
|
|
8
|
-
|
|
8
|
+
# 15.1 is the floor of Expo SDK 55 and React Native 0.83. CocoaPods skips a pod that needs a newer iOS than the
|
|
9
|
+
# app, silently, so a higher number here left SDK 55 apps without the module (found by the native CI job).
|
|
10
|
+
s.platforms = { :ios => '15.1' }
|
|
9
11
|
s.source = { :git => '' }
|
|
10
12
|
s.static_framework = true
|
|
11
13
|
s.dependency 'ExpoModulesCore'
|
package/dist/actions.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export interface SettleOutcome {
|
|
|
22
22
|
export declare function settle(): Promise<number>;
|
|
23
23
|
/** Like `settle`, and says when the wait ended at the cap: spread it into an action's result. */
|
|
24
24
|
export declare function settleOutcome(): Promise<SettleOutcome>;
|
|
25
|
+
/** An action is starting: only requests begun from now on count as its own. */
|
|
26
|
+
export declare function beginAction(): void;
|
|
25
27
|
type ParsedTarget = {
|
|
26
28
|
kind: 'ref';
|
|
27
29
|
ref: string;
|
|
@@ -60,8 +62,12 @@ export declare function labelOf(info: {
|
|
|
60
62
|
}): string;
|
|
61
63
|
/** What the caller means to do; a point target then picks only an element that does it. */
|
|
62
64
|
type ActionHint = 'press' | 'longPress' | 'toggle' | 'scroll' | 'type';
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Resolve a protocol target to exactly one element, or throw not_found / ambiguous. A target that isn't there while the screen
|
|
67
|
+
* is loading (a request in flight, a spinner) is waited for up to the settle cap before `not_found` is answered (AGENT-TOOLS
|
|
68
|
+
* 1.13 §18.7); the answer after the wait is made from the screen as it is then, so its closest matches are unchanged.
|
|
69
|
+
*/
|
|
70
|
+
export declare function resolveTarget(raw: unknown, hint?: ActionHint, glassOnly?: boolean, loadingWait?: boolean): Promise<Resolved>;
|
|
65
71
|
/** The nearest listed element at or above a host fiber, for naming what covers a target. */
|
|
66
72
|
export declare function describeHit(snapshot: Snapshot, hit: Fiber, actor?: Entry): string;
|
|
67
73
|
/** `back`: what the back button or gesture does, on the focused screen; with a sheet open, closes the sheet. */
|
|
@@ -74,6 +80,7 @@ export declare function back(rawParams: unknown): Promise<{
|
|
|
74
80
|
export type PressSecond = {
|
|
75
81
|
done: true;
|
|
76
82
|
afterMs: number;
|
|
83
|
+
threw?: string;
|
|
77
84
|
} | {
|
|
78
85
|
done: false;
|
|
79
86
|
reason: string;
|
|
@@ -98,7 +105,20 @@ export type PressResult = {
|
|
|
98
105
|
};
|
|
99
106
|
/** With `again`: the second tap of a double tap. */
|
|
100
107
|
second?: PressSecond;
|
|
108
|
+
/** Whose handler took the touch, and its message if it threw (AGENT-TOOLS 1.13 §3). Absent on a dry run. */
|
|
109
|
+
handler?: HandlerFacts;
|
|
110
|
+
/** Requests that began during the action. Absent on a dry run. */
|
|
111
|
+
startedRequests?: number;
|
|
101
112
|
} & SettleOutcome;
|
|
113
|
+
/** An own handler (`threw`: its message), or an ancestor's (`element`: that ancestor) that took the touch. */
|
|
114
|
+
export type HandlerFacts = {
|
|
115
|
+
kind: 'own';
|
|
116
|
+
threw?: string;
|
|
117
|
+
} | {
|
|
118
|
+
kind: 'parent';
|
|
119
|
+
element: TaplineElement;
|
|
120
|
+
threw?: string;
|
|
121
|
+
};
|
|
102
122
|
export declare function press(params: unknown): Promise<PressResult>;
|
|
103
123
|
export declare function longPress(params: unknown): Promise<PressResult>;
|
|
104
124
|
/** What the SDK asks of the keyboard and the focused input; replaceable so tests need no native side. */
|
package/dist/actions.js
CHANGED
|
@@ -5,6 +5,7 @@ exports.configureActions = configureActions;
|
|
|
5
5
|
exports.onActed = onActed;
|
|
6
6
|
exports.settle = settle;
|
|
7
7
|
exports.settleOutcome = settleOutcome;
|
|
8
|
+
exports.beginAction = beginAction;
|
|
8
9
|
exports.parseTarget = parseTarget;
|
|
9
10
|
exports.labelOf = labelOf;
|
|
10
11
|
exports.resolveTarget = resolveTarget;
|
|
@@ -28,6 +29,9 @@ exports.screenshot = screenshot;
|
|
|
28
29
|
exports.swipe = swipe;
|
|
29
30
|
const react_native_1 = require("react-native");
|
|
30
31
|
const keyboard_1 = require("./keyboard");
|
|
32
|
+
const guard_1 = require("./guard");
|
|
33
|
+
const network_1 = require("./network");
|
|
34
|
+
const loading_1 = require("./loading");
|
|
31
35
|
const native_1 = require("./native");
|
|
32
36
|
const inspect_1 = require("./inspect");
|
|
33
37
|
const source_1 = require("./source");
|
|
@@ -39,6 +43,7 @@ const alerts_1 = require("./alerts");
|
|
|
39
43
|
const fiber_1 = require("./fiber");
|
|
40
44
|
const logs_1 = require("./logs");
|
|
41
45
|
const register_1 = require("./register");
|
|
46
|
+
const idle_1 = require("./idle");
|
|
42
47
|
const settle_1 = require("./settle");
|
|
43
48
|
const types_1 = require("./types");
|
|
44
49
|
var inspect_2 = require("./inspect");
|
|
@@ -130,14 +135,90 @@ async function sheetProbe() {
|
|
|
130
135
|
}
|
|
131
136
|
return null;
|
|
132
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Where `startedRequests()` stood when the action began (or when the last settle ended): a request begun after
|
|
140
|
+
* it is one the action started (F1).
|
|
141
|
+
*/
|
|
142
|
+
let requestMark = 0;
|
|
143
|
+
/** An action is starting: only requests begun from now on count as its own. */
|
|
144
|
+
function beginAction() {
|
|
145
|
+
requestMark = (0, network_1.startedRequests)();
|
|
146
|
+
}
|
|
147
|
+
const FRAME_MS = 16;
|
|
148
|
+
/** Whether an open or closing sheet hides the app's motion from the SDK. */
|
|
149
|
+
function sheetPresent() {
|
|
150
|
+
return (0, inspect_1.openModalHosts)().length > 0 || (0, inspect_1.closingModalHosts)().length > 0;
|
|
151
|
+
}
|
|
152
|
+
/** The elements whose positions say the screen has stopped: the acting ones and the outermost. */
|
|
153
|
+
function watchedFibers() {
|
|
154
|
+
try {
|
|
155
|
+
const entries = (0, inspect_1.takeSnapshot)().entries;
|
|
156
|
+
const picked = new Map();
|
|
157
|
+
for (const entry of [...entries.filter((e) => e.info.actions.length > 0).slice(0, 6), ...entries.slice(0, 6)]) {
|
|
158
|
+
picked.set(entry.node.index, entry.node.fiber);
|
|
159
|
+
}
|
|
160
|
+
return [...picked.values()];
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function sameFrame(a, b) {
|
|
167
|
+
if (a === null || b === null)
|
|
168
|
+
return a === b;
|
|
169
|
+
return Math.abs(a.x - b.x) <= 1 && Math.abs(a.y - b.y) <= 1 && Math.abs(a.width - b.width) <= 1 && Math.abs(a.height - b.height) <= 1;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* A native push, a Lottie or a gesture-handler sheet moves without a commit or an Animated call, so after the
|
|
173
|
+
* idle hold the visible positions are read twice, a frame apart. Says which element moved, and whether React
|
|
174
|
+
* committed in between.
|
|
175
|
+
*/
|
|
176
|
+
async function positionsMoved() {
|
|
177
|
+
const before = (0, register_1.commitRevision)();
|
|
178
|
+
const fibers = watchedFibers();
|
|
179
|
+
const read = () => Promise.all(fibers.map((fiber) => (0, inspect_1.measureFiber)(fiber).catch(() => null)));
|
|
180
|
+
const first = await read();
|
|
181
|
+
await new Promise((resolve) => setTimeout(resolve, FRAME_MS));
|
|
182
|
+
const second = await read();
|
|
183
|
+
const at = first.findIndex((frame, i) => !sameFrame(frame, second[i] ?? null));
|
|
184
|
+
return { moving: at >= 0 ? (fibers[at] ?? null) : null, committed: (0, register_1.commitRevision)() !== before };
|
|
185
|
+
}
|
|
133
186
|
async function settleNow() {
|
|
134
187
|
const started = Date.now();
|
|
135
188
|
let capped = false;
|
|
136
189
|
const onCap = () => {
|
|
137
190
|
capped = true;
|
|
138
191
|
};
|
|
192
|
+
// Know-don't-watch (off unless `knowIdle`): answer when every signal the SDK can see is idle and still.
|
|
193
|
+
// An open sheet is out of its sight, so that keeps today's window and position poll.
|
|
194
|
+
let knowSpentMs = 0;
|
|
195
|
+
if ((0, idle_1.isKnowIdle)() && settings.capMs > 0 && !sheetPresent()) {
|
|
196
|
+
const known = await (0, idle_1.waitUntilIdle)({ frames: 2, capMs: settings.capMs, requestMark });
|
|
197
|
+
let moving = null;
|
|
198
|
+
let steady = !known.usedFallback && !known.capped;
|
|
199
|
+
if (steady && !sheetPresent()) {
|
|
200
|
+
const check = await positionsMoved();
|
|
201
|
+
moving = check.moving;
|
|
202
|
+
steady = moving === null && !check.committed;
|
|
203
|
+
}
|
|
204
|
+
if (!known.usedFallback && !sheetPresent() && (steady || known.capped)) {
|
|
205
|
+
requestMark = (0, network_1.startedRequests)();
|
|
206
|
+
const settledMs = Math.max(0, Math.min(settings.capMs, Date.now() - started));
|
|
207
|
+
return known.capped ? { settledMs, settleCapMs: settings.capMs } : { settledMs };
|
|
208
|
+
}
|
|
209
|
+
if (known.capped)
|
|
210
|
+
onCap();
|
|
211
|
+
knowSpentMs = Math.max(0, Date.now() - started);
|
|
212
|
+
// Something moved without a commit: today's poll waits for it to stop.
|
|
213
|
+
const left = settings.capMs - knowSpentMs;
|
|
214
|
+
if (moving && left > 0) {
|
|
215
|
+
await (0, settle_1.waitUntilSteady)(() => (0, inspect_1.measureFiber)(moving), { pollMs: SHEET_POLL_MS, capMs: left, now: Date.now, onCap });
|
|
216
|
+
knowSpentMs = Math.max(0, Date.now() - started);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
139
219
|
await (0, settle_1.waitUntilStill)({
|
|
140
220
|
...settings,
|
|
221
|
+
capMs: settings.capMs - knowSpentMs,
|
|
141
222
|
onCommit: (listener) => (0, register_1.onCommit)(() => listener()),
|
|
142
223
|
now: Date.now,
|
|
143
224
|
onCap,
|
|
@@ -172,6 +253,7 @@ async function settleNow() {
|
|
|
172
253
|
}
|
|
173
254
|
}
|
|
174
255
|
const settledMs = Math.max(0, Math.min(settings.capMs, Date.now() - started));
|
|
256
|
+
requestMark = (0, network_1.startedRequests)();
|
|
175
257
|
return capped && settings.capMs > 0 ? { settledMs, settleCapMs: settings.capMs } : { settledMs };
|
|
176
258
|
}
|
|
177
259
|
function badParams(message) {
|
|
@@ -207,6 +289,22 @@ function callHandler(name, handler, args) {
|
|
|
207
289
|
});
|
|
208
290
|
}
|
|
209
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* Calls a press handler as `callHandler` does, but a throw is not an error of the action: a person's tap
|
|
294
|
+
* on a button whose handler throws still happened. The message goes to the logs and to the answer's facts
|
|
295
|
+
* (AGENT-TOOLS 1.13 §3). Returns the message, or undefined when the handler didn't throw.
|
|
296
|
+
*/
|
|
297
|
+
function callHandlerCaught(name, handler, args) {
|
|
298
|
+
try {
|
|
299
|
+
callHandler(name, handler, args);
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
const message = error instanceof types_1.TaplineError ? error.message.replace(/^[^:]*threw: /, '') : String(error);
|
|
303
|
+
console.error(`[tapline] ${name} threw: ${message}`);
|
|
304
|
+
return message.slice(0, 500);
|
|
305
|
+
}
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
210
308
|
function parseTarget(raw) {
|
|
211
309
|
if (!(0, types_1.isRecord)(raw))
|
|
212
310
|
throw badParams('target must be an object such as {"testID":"login"}');
|
|
@@ -490,8 +588,38 @@ function inFrontLayer(matches) {
|
|
|
490
588
|
const inAlert = matches.filter((entry) => inStandInAlert(entry.node.fiber));
|
|
491
589
|
return inAlert.length > 0 ? inAlert : matches;
|
|
492
590
|
}
|
|
493
|
-
/**
|
|
494
|
-
|
|
591
|
+
/**
|
|
592
|
+
* Resolve a protocol target to exactly one element, or throw not_found / ambiguous. A target that isn't there while the screen
|
|
593
|
+
* is loading (a request in flight, a spinner) is waited for up to the settle cap before `not_found` is answered (AGENT-TOOLS
|
|
594
|
+
* 1.13 §18.7); the answer after the wait is made from the screen as it is then, so its closest matches are unchanged.
|
|
595
|
+
*/
|
|
596
|
+
async function resolveTarget(raw, hint, glassOnly = false, loadingWait = true) {
|
|
597
|
+
try {
|
|
598
|
+
return await resolveNow(raw, hint, glassOnly);
|
|
599
|
+
}
|
|
600
|
+
catch (error) {
|
|
601
|
+
if (!(error instanceof types_1.TaplineError) || error.code !== 'not_found' || !loadingWait || !isNamedTarget(raw) || settings.capMs <= 0)
|
|
602
|
+
throw error;
|
|
603
|
+
await (0, loading_1.waitWhileLoading)({
|
|
604
|
+
requestMark,
|
|
605
|
+
capMs: settings.capMs,
|
|
606
|
+
// Anything but `not_found` (ambiguous, covered) means the target has come.
|
|
607
|
+
isThere: () => resolveNow(raw, hint, glassOnly).then(() => true, (again) => !(again instanceof types_1.TaplineError && again.code === 'not_found')),
|
|
608
|
+
});
|
|
609
|
+
return resolveNow(raw, hint, glassOnly);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
/** A target by id, label or text: the kinds a screen can still be loading. A ref or a point is never waited for. */
|
|
613
|
+
function isNamedTarget(raw) {
|
|
614
|
+
try {
|
|
615
|
+
const kind = parseTarget(raw).kind;
|
|
616
|
+
return kind === 'testID' || kind === 'label' || kind === 'text';
|
|
617
|
+
}
|
|
618
|
+
catch {
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
async function resolveNow(raw, hint, glassOnly = false) {
|
|
495
623
|
const target = parseTarget(raw);
|
|
496
624
|
const snapshot = (0, inspect_1.takeSnapshot)();
|
|
497
625
|
if (target.kind === 'ref') {
|
|
@@ -735,7 +863,8 @@ async function assertReachable(snapshot, actor, frame) {
|
|
|
735
863
|
const name = describe(actor);
|
|
736
864
|
if (right <= left || bottom <= top) {
|
|
737
865
|
throw notActionable('off_screen', `${name} is off screen (at ${Math.round(frame.x)},${Math.round(frame.y)}; the screen is ${width}x${height}). ` +
|
|
738
|
-
|
|
866
|
+
'It lies in a part of the screen that nothing scrolls (behind a fixed bar, or outside every list); look at the screen, then target something else or report it. ' +
|
|
867
|
+
'Pass force: true to call its handler anyway.');
|
|
739
868
|
}
|
|
740
869
|
const bar = (0, inspect_1.nativeTabBar)(snapshot);
|
|
741
870
|
const navBar = (0, inspect_1.nativeNavBar)(snapshot);
|
|
@@ -944,21 +1073,28 @@ function focusInput(entry, frame) {
|
|
|
944
1073
|
callHandler(key, handler, [pressEvent(entry.node.fiber, frame)]);
|
|
945
1074
|
}
|
|
946
1075
|
}
|
|
1076
|
+
/** The guard a `move` hands to the touch it makes, never part of a plain call. */
|
|
1077
|
+
function recipientGuardOf(params) {
|
|
1078
|
+
return params.recipientGuard === undefined ? undefined : (0, guard_1.parseRecipientGuard)(params.recipientGuard);
|
|
1079
|
+
}
|
|
947
1080
|
async function pressLike(kind, rawParams) {
|
|
948
1081
|
const params = paramsOf(rawParams);
|
|
949
1082
|
const afterMs = kind === 'press' ? againAfter(params.again) : undefined;
|
|
950
|
-
const
|
|
1083
|
+
const guard = recipientGuardOf(params);
|
|
1084
|
+
const { snapshot, entry } = await resolveTarget(params.target, kind, params.noScroll === true, params.dryRun !== true);
|
|
951
1085
|
if (entry.tab) {
|
|
952
1086
|
if (kind === 'longPress')
|
|
953
1087
|
throw notActionable('no_action', `${describe(entry)} has no long-press action`);
|
|
954
1088
|
if (params.dryRun === true)
|
|
955
1089
|
return { element: (0, inspect_1.toElement)(entry, null), dryRun: true, settledMs: 0 };
|
|
1090
|
+
(0, guard_1.assertRecipient)(guard, entry);
|
|
956
1091
|
selectTab(snapshot, entry.tab);
|
|
957
1092
|
return { element: (0, inspect_1.toElement)(entry, null), ...keyboardNote(closeKeyboardFor(entry)), ...(await settleOutcome()) };
|
|
958
1093
|
}
|
|
959
1094
|
if (entry.navBack) {
|
|
960
1095
|
if (params.dryRun === true)
|
|
961
1096
|
return { element: (0, inspect_1.toElement)(entry, null), dryRun: true, settledMs: 0 };
|
|
1097
|
+
(0, guard_1.assertRecipient)(guard, entry);
|
|
962
1098
|
const { route: _route, ...outcome } = await back({});
|
|
963
1099
|
return { element: (0, inspect_1.toElement)(entry, null), ...outcome };
|
|
964
1100
|
}
|
|
@@ -983,6 +1119,8 @@ async function pressLike(kind, rawParams) {
|
|
|
983
1119
|
return { actor: found, handler: own, focusing: isInput };
|
|
984
1120
|
};
|
|
985
1121
|
let planned = plan(snapshot, entry);
|
|
1122
|
+
// The element the agent named; a scroll may find it again.
|
|
1123
|
+
let touched = entry;
|
|
986
1124
|
// Off screen, in the tree: scroll it into view first (AGENT-TOOLS 1.12 §16.2). The policy ran before this call.
|
|
987
1125
|
let reach = {};
|
|
988
1126
|
let current = snapshot;
|
|
@@ -993,11 +1131,12 @@ async function pressLike(kind, rawParams) {
|
|
|
993
1131
|
if (reach.scrolled) {
|
|
994
1132
|
const again = await reresolve(planned.actor, kind, params.target);
|
|
995
1133
|
current = again.snapshot;
|
|
1134
|
+
touched = again.entry;
|
|
996
1135
|
planned = plan(again.snapshot, again.entry);
|
|
997
1136
|
await assertRefoundOnScreen(current, planned.actor, reach);
|
|
998
1137
|
}
|
|
999
1138
|
}
|
|
1000
|
-
const { actor, handler
|
|
1139
|
+
const { actor, handler } = planned;
|
|
1001
1140
|
const { element, frame } = await elementFor(actor);
|
|
1002
1141
|
if (reach.scrolls)
|
|
1003
1142
|
return { element, dryRun: true, scrolls: true, ...(reach.point ? { point: reach.point } : {}), settledMs: 0 };
|
|
@@ -1005,25 +1144,40 @@ async function pressLike(kind, rawParams) {
|
|
|
1005
1144
|
// A dry run (AGENT-TOOLS 1.8 §11.4): every check ran; nothing is touched.
|
|
1006
1145
|
if (params.dryRun === true)
|
|
1007
1146
|
return { element, dryRun: true, ...pointOf(frame), ...forced, settledMs: 0 };
|
|
1147
|
+
// The policy checked this very element: refuse when the one that takes the touch is another now (§18.4).
|
|
1148
|
+
(0, guard_1.assertRecipient)(guard, actor);
|
|
1008
1149
|
const closedKeyboard = closeKeyboardFor(actor);
|
|
1150
|
+
const requestsBefore = (0, network_1.startedRequests)();
|
|
1151
|
+
let threw;
|
|
1152
|
+
// An input with no onPress of its own was only focused: no handler ran, so none is reported.
|
|
1153
|
+
let handled = handler !== null;
|
|
1009
1154
|
if (handler)
|
|
1010
|
-
|
|
1155
|
+
threw = callHandlerCaught(handlerName, handler, [pressEvent(actor.node.fiber, frame)]);
|
|
1011
1156
|
else {
|
|
1012
1157
|
// A finger focuses the input and also fires the input's own onPress (not an ancestor's: the
|
|
1013
1158
|
// input takes the touch).
|
|
1014
1159
|
focusInput(actor, frame);
|
|
1015
1160
|
const own = appHandler(actor, 'onPress');
|
|
1016
|
-
if (own)
|
|
1017
|
-
|
|
1161
|
+
if (own) {
|
|
1162
|
+
handled = true;
|
|
1163
|
+
threw = callHandlerCaught('onPress', own, [pressEvent(actor.node.fiber, frame)]);
|
|
1164
|
+
}
|
|
1018
1165
|
}
|
|
1166
|
+
// Whose handler it was: the touched element's own, or an ancestor's that took the touch (§3).
|
|
1167
|
+
const facts = !handled ? undefined : (0, fiber_1.sameFiber)(actor.node.fiber, touched.node.fiber)
|
|
1168
|
+
? { kind: 'own', ...(threw !== undefined ? { threw } : {}) }
|
|
1169
|
+
: { kind: 'parent', element, ...(threw !== undefined ? { threw } : {}) };
|
|
1019
1170
|
const second = afterMs === undefined ? undefined : await pressAgain(actor, params, afterMs);
|
|
1171
|
+
const settled = await settleOutcome();
|
|
1020
1172
|
return {
|
|
1173
|
+
...(facts !== undefined ? { handler: facts } : {}),
|
|
1174
|
+
startedRequests: Math.max(0, (0, network_1.startedRequests)() - requestsBefore),
|
|
1021
1175
|
element,
|
|
1022
1176
|
...(reach.scrolled ? { scrolled: true } : {}),
|
|
1023
1177
|
...keyboardNote(closedKeyboard),
|
|
1024
1178
|
...forced,
|
|
1025
1179
|
...(second !== undefined ? { second } : {}),
|
|
1026
|
-
...
|
|
1180
|
+
...settled,
|
|
1027
1181
|
};
|
|
1028
1182
|
}
|
|
1029
1183
|
/** `again`: a second tap this many milliseconds after the first (a stress double tap, AGENT-TOOLS 1.12 §17.4). */
|
|
@@ -1060,8 +1214,9 @@ async function pressAgain(first, params, afterMs) {
|
|
|
1060
1214
|
if (params.force !== true)
|
|
1061
1215
|
await assertReachable(again.snapshot, found, frame);
|
|
1062
1216
|
const at = Date.now() - started;
|
|
1063
|
-
|
|
1064
|
-
|
|
1217
|
+
// A handler that throws on the second tap was reached: that is the app's failure, not a tap that didn't land.
|
|
1218
|
+
const threw = callHandlerCaught('onPress', handler, [pressEvent(found.node.fiber, frame)]);
|
|
1219
|
+
return threw === undefined ? { done: true, afterMs: at } : { done: true, afterMs: at, threw };
|
|
1065
1220
|
}
|
|
1066
1221
|
catch (error) {
|
|
1067
1222
|
const reason = error instanceof types_1.TaplineError
|
|
@@ -1195,9 +1350,9 @@ async function moveFocusAboveKeyboard(entry) {
|
|
|
1195
1350
|
function keyboardNote(closed) {
|
|
1196
1351
|
return closed ? { closedKeyboard: true } : {};
|
|
1197
1352
|
}
|
|
1198
|
-
async function resolveInput(snapshot, rawTarget, glassOnly = false) {
|
|
1353
|
+
async function resolveInput(snapshot, rawTarget, glassOnly = false, loadingWait = true) {
|
|
1199
1354
|
if (rawTarget !== undefined && rawTarget !== null) {
|
|
1200
|
-
const { snapshot: snap, entry } = await resolveTarget(rawTarget, 'type', glassOnly);
|
|
1355
|
+
const { snapshot: snap, entry } = await resolveTarget(rawTarget, 'type', glassOnly, loadingWait);
|
|
1201
1356
|
const input = findActionable(snap, entry, 'type', true);
|
|
1202
1357
|
if (!input)
|
|
1203
1358
|
throw notActionable('no_action', `${describe(entry)} is not an input`);
|
|
@@ -1230,6 +1385,12 @@ function setNativeText(entry, text) {
|
|
|
1230
1385
|
}
|
|
1231
1386
|
}
|
|
1232
1387
|
}
|
|
1388
|
+
function assertFieldKind(entry, kind) {
|
|
1389
|
+
const isSecure = (0, inspect_1.isSecureInput)(entry.node);
|
|
1390
|
+
if (kind !== undefined && (kind === 'secure') !== isSecure) {
|
|
1391
|
+
throw new types_1.TaplineError('bad_params', `${describe(entry)} is ${isSecure ? 'a secure field' : 'not a secure field'}, not a ${kind} field`, undefined, undefined, { secure: isSecure });
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1233
1394
|
async function typeText(rawParams) {
|
|
1234
1395
|
const params = paramsOf(rawParams);
|
|
1235
1396
|
const submit = params.submit === true;
|
|
@@ -1244,7 +1405,13 @@ async function typeText(rawParams) {
|
|
|
1244
1405
|
throw badParams('text is required (or pass submit: true)');
|
|
1245
1406
|
// Deleting from the end edits what is there, so it never replaces the whole text.
|
|
1246
1407
|
const replace = params.replace !== false && !deleting;
|
|
1247
|
-
|
|
1408
|
+
const kind = params.kind;
|
|
1409
|
+
if (kind !== undefined && kind !== 'secure' && kind !== 'plain')
|
|
1410
|
+
throw badParams('kind is "secure" or "plain"');
|
|
1411
|
+
let { snapshot, entry } = await resolveInput(null, params.target, params.noScroll === true, params.dryRun !== true);
|
|
1412
|
+
// A test account's value goes only into the kind of field it is for (AGENT-TOOLS 1.13 §18.6). The refusal
|
|
1413
|
+
// comes before anything is typed or scrolled, and says only whether the field is secure, never its text.
|
|
1414
|
+
assertFieldKind(entry, kind);
|
|
1248
1415
|
if (entry.info.disabled)
|
|
1249
1416
|
throw notActionable('disabled', `${describe(entry)} is not editable; ${ENABLE_FIRST}.`);
|
|
1250
1417
|
// Off screen, in the tree: scroll it into view first (AGENT-TOOLS 1.12 §16.2; the policy ran before this
|
|
@@ -1267,6 +1434,8 @@ async function typeText(rawParams) {
|
|
|
1267
1434
|
again = await resolveInput(null, refind);
|
|
1268
1435
|
}
|
|
1269
1436
|
assertSameTarget(entry, again.entry);
|
|
1437
|
+
// A recycled row may hold another kind of field under the same names.
|
|
1438
|
+
assertFieldKind(again.entry, kind);
|
|
1270
1439
|
snapshot = again.snapshot;
|
|
1271
1440
|
entry = again.entry;
|
|
1272
1441
|
if (entry.info.disabled)
|
|
@@ -1785,7 +1954,7 @@ async function scroll(rawParams) {
|
|
|
1785
1954
|
let entry;
|
|
1786
1955
|
let frame = null;
|
|
1787
1956
|
if (params.target !== undefined && params.target !== null) {
|
|
1788
|
-
const resolved = await resolveTarget(params.target, 'scroll');
|
|
1957
|
+
const resolved = await resolveTarget(params.target, 'scroll', undefined, params.dryRun !== true);
|
|
1789
1958
|
const scroller = findActionable(resolved.snapshot, resolved.entry, 'scroll', true);
|
|
1790
1959
|
if (!scroller)
|
|
1791
1960
|
throw notActionable('no_action', `${describe(resolved.entry)} is not inside a scroll view`);
|
|
@@ -1963,7 +2132,7 @@ async function toggle(rawParams) {
|
|
|
1963
2132
|
const params = paramsOf(rawParams);
|
|
1964
2133
|
if (params.value !== undefined && typeof params.value !== 'boolean')
|
|
1965
2134
|
throw badParams('value must be a boolean');
|
|
1966
|
-
const first = await resolveTarget(params.target, 'toggle', params.noScroll === true);
|
|
2135
|
+
const first = await resolveTarget(params.target, 'toggle', params.noScroll === true, params.dryRun !== true);
|
|
1967
2136
|
const plan = (snap, picked) => {
|
|
1968
2137
|
const found = findActionable(snap, picked, 'toggle', true);
|
|
1969
2138
|
if (!found)
|
|
@@ -2006,6 +2175,7 @@ async function toggle(rawParams) {
|
|
|
2006
2175
|
const { element } = await elementFor(control);
|
|
2007
2176
|
return { element, value: params.value, unchanged: true, ...(reach.scrolled ? { scrolled: true } : {}), settledMs: 0 };
|
|
2008
2177
|
}
|
|
2178
|
+
(0, guard_1.assertRecipient)(recipientGuardOf(params), control);
|
|
2009
2179
|
const closedKeyboard = closeKeyboardFor(control);
|
|
2010
2180
|
callHandler('onValueChange', handler, [next]);
|
|
2011
2181
|
const outcome = await settleOutcome();
|