react-native-tapline 1.4.0 → 1.5.0
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/dist/actions.d.ts +54 -1
- package/dist/actions.js +156 -18
- package/dist/actions.js.map +1 -1
- package/dist/checks.d.ts +43 -1
- package/dist/checks.js +17 -5
- package/dist/checks.js.map +1 -1
- package/dist/client.js +4 -1
- package/dist/client.js.map +1 -1
- package/dist/glass.d.ts +3 -0
- package/dist/glass.js +81 -0
- package/dist/glass.js.map +1 -0
- package/dist/glassclip.d.ts +18 -0
- package/dist/glassclip.js +69 -0
- package/dist/glassclip.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +7 -1
- package/dist/inspect.js +12 -1
- package/dist/inspect.js.map +1 -1
- package/dist/media.d.ts +71 -0
- package/dist/media.js +516 -0
- package/dist/media.js.map +1 -0
- package/dist/methods.js +6 -1
- package/dist/methods.js.map +1 -1
- package/dist/network.js +20 -1
- package/dist/network.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.js.map +1 -1
- package/dist/uxcheck.d.ts +57 -0
- package/dist/uxcheck.js +222 -0
- package/dist/uxcheck.js.map +1 -0
- package/dist/wait.js +30 -3
- package/dist/wait.js.map +1 -1
- package/package.json +1 -1
package/dist/actions.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Entry, type Snapshot } from './inspect';
|
|
2
2
|
import { type ElementSource } from './source';
|
|
3
3
|
import { type AuditType } from './checks';
|
|
4
|
+
import { type UxAction, type UxCheckName, type UxKind } from './uxcheck';
|
|
4
5
|
import { type Fiber } from './fiber';
|
|
5
6
|
import { type Frame, type LogEntry, type TaplineElement } from './types';
|
|
6
7
|
export { tree } from './inspect';
|
|
@@ -60,7 +61,7 @@ export declare function labelOf(info: {
|
|
|
60
61
|
/** What the caller means to do; a point target then picks only an element that does it. */
|
|
61
62
|
type ActionHint = 'press' | 'longPress' | 'toggle' | 'scroll' | 'type';
|
|
62
63
|
/** Resolve a protocol target to exactly one element, or throw not_found / ambiguous. */
|
|
63
|
-
export declare function resolveTarget(raw: unknown, hint?: ActionHint): Promise<Resolved>;
|
|
64
|
+
export declare function resolveTarget(raw: unknown, hint?: ActionHint, glassOnly?: boolean): Promise<Resolved>;
|
|
64
65
|
/** The nearest listed element at or above a host fiber, for naming what covers a target. */
|
|
65
66
|
export declare function describeHit(snapshot: Snapshot, hit: Fiber, actor?: Entry): string;
|
|
66
67
|
/** `back`: what the back button or gesture does, on the focused screen; with a sheet open, closes the sheet. */
|
|
@@ -69,6 +70,15 @@ export declare function back(rawParams: unknown): Promise<{
|
|
|
69
70
|
closed?: 'sheet';
|
|
70
71
|
dryRun?: true;
|
|
71
72
|
} & SettleOutcome>;
|
|
73
|
+
/** What happened to a double tap's second tap, and how long after the first it came. */
|
|
74
|
+
export type PressSecond = {
|
|
75
|
+
done: true;
|
|
76
|
+
afterMs: number;
|
|
77
|
+
} | {
|
|
78
|
+
done: false;
|
|
79
|
+
reason: string;
|
|
80
|
+
afterMs: number;
|
|
81
|
+
};
|
|
72
82
|
/**
|
|
73
83
|
* What `press` and `longPress` answer: the element, `closedKeyboard: true` when the keyboard was
|
|
74
84
|
* closed first, and `forced` (the reason word) when `force` let through what the checks refuse.
|
|
@@ -86,6 +96,8 @@ export type PressResult = {
|
|
|
86
96
|
x: number;
|
|
87
97
|
y: number;
|
|
88
98
|
};
|
|
99
|
+
/** With `again`: the second tap of a double tap. */
|
|
100
|
+
second?: PressSecond;
|
|
89
101
|
} & SettleOutcome;
|
|
90
102
|
export declare function press(params: unknown): Promise<PressResult>;
|
|
91
103
|
export declare function longPress(params: unknown): Promise<PressResult>;
|
|
@@ -142,6 +154,28 @@ export type ScrollResult = {
|
|
|
142
154
|
dryRun?: true;
|
|
143
155
|
} & SettleOutcome;
|
|
144
156
|
export declare function scroll(rawParams: unknown): Promise<ScrollResult>;
|
|
157
|
+
interface ScrollState {
|
|
158
|
+
/** The real content offset; negative at rest when the system adds an inset. */
|
|
159
|
+
offset: {
|
|
160
|
+
x: number;
|
|
161
|
+
y: number;
|
|
162
|
+
};
|
|
163
|
+
/** The furthest offset a scrollTo can reach. */
|
|
164
|
+
max: {
|
|
165
|
+
x: number;
|
|
166
|
+
y: number;
|
|
167
|
+
};
|
|
168
|
+
visible: {
|
|
169
|
+
x: number;
|
|
170
|
+
y: number;
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* The scroll view's real offset, from where its content container sits inside it. On the new
|
|
175
|
+
* architecture `measureInWindow` includes the content offset, so scroll frame minus content frame
|
|
176
|
+
* is the offset. Null when either can't be measured (old architecture, nothing laid out).
|
|
177
|
+
*/
|
|
178
|
+
export declare function measureScrollState(entry: Entry): Promise<ScrollState | null>;
|
|
145
179
|
export declare function toggle(rawParams: unknown): Promise<{
|
|
146
180
|
element: TaplineElement;
|
|
147
181
|
value: boolean;
|
|
@@ -190,6 +224,25 @@ export declare function audit(rawParams: unknown): Promise<{
|
|
|
190
224
|
issues: AuditIssue[];
|
|
191
225
|
checked: AuditType[];
|
|
192
226
|
}>;
|
|
227
|
+
export interface UxIssue {
|
|
228
|
+
kind: UxKind;
|
|
229
|
+
element: TaplineElement | null;
|
|
230
|
+
where?: AuditWhere;
|
|
231
|
+
problem: string;
|
|
232
|
+
basis: string;
|
|
233
|
+
at?: {
|
|
234
|
+
file: string;
|
|
235
|
+
line: number;
|
|
236
|
+
direct: boolean;
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/** `ux_check {wayBack?, earlier?}` (AGENT-TOOLS 1.12 §17.3): the screen against the usability basics. */
|
|
240
|
+
export declare function uxCheck(rawParams: unknown): Promise<{
|
|
241
|
+
issues: UxIssue[];
|
|
242
|
+
checked: UxCheckName[];
|
|
243
|
+
choices: number;
|
|
244
|
+
actions: UxAction[];
|
|
245
|
+
}>;
|
|
193
246
|
export declare function getLogs(rawParams: unknown): Promise<{
|
|
194
247
|
entries: LogEntry[];
|
|
195
248
|
}>;
|
package/dist/actions.js
CHANGED
|
@@ -15,10 +15,12 @@ exports.longPress = longPress;
|
|
|
15
15
|
exports.setKeyboardControl = setKeyboardControl;
|
|
16
16
|
exports.typeText = typeText;
|
|
17
17
|
exports.scroll = scroll;
|
|
18
|
+
exports.measureScrollState = measureScrollState;
|
|
18
19
|
exports.toggle = toggle;
|
|
19
20
|
exports.openURL = openURL;
|
|
20
21
|
exports.inspect = inspect;
|
|
21
22
|
exports.audit = audit;
|
|
23
|
+
exports.uxCheck = uxCheck;
|
|
22
24
|
exports.getLogs = getLogs;
|
|
23
25
|
exports.reload = reload;
|
|
24
26
|
exports.screenshotAvailable = screenshotAvailable;
|
|
@@ -30,7 +32,9 @@ const native_1 = require("./native");
|
|
|
30
32
|
const inspect_1 = require("./inspect");
|
|
31
33
|
const source_1 = require("./source");
|
|
32
34
|
const closest_1 = require("./closest");
|
|
35
|
+
const glassclip_1 = require("./glassclip");
|
|
33
36
|
const checks_1 = require("./checks");
|
|
37
|
+
const uxcheck_1 = require("./uxcheck");
|
|
34
38
|
const alerts_1 = require("./alerts");
|
|
35
39
|
const fiber_1 = require("./fiber");
|
|
36
40
|
const logs_1 = require("./logs");
|
|
@@ -487,7 +491,7 @@ function inFrontLayer(matches) {
|
|
|
487
491
|
return inAlert.length > 0 ? inAlert : matches;
|
|
488
492
|
}
|
|
489
493
|
/** Resolve a protocol target to exactly one element, or throw not_found / ambiguous. */
|
|
490
|
-
async function resolveTarget(raw, hint) {
|
|
494
|
+
async function resolveTarget(raw, hint, glassOnly = false) {
|
|
491
495
|
const target = parseTarget(raw);
|
|
492
496
|
const snapshot = (0, inspect_1.takeSnapshot)();
|
|
493
497
|
if (target.kind === 'ref') {
|
|
@@ -531,7 +535,14 @@ async function resolveTarget(raw, hint) {
|
|
|
531
535
|
}
|
|
532
536
|
return { snapshot, entry: hit };
|
|
533
537
|
}
|
|
534
|
-
|
|
538
|
+
let matches = inFrontLayer(matchEntries(snapshot, target));
|
|
539
|
+
// A journey in a user's eyes (§17.1): visible text matches only what shows on the glass, not what a scroll view
|
|
540
|
+
// clips or the keyboard covers (1.5 review, finding 11).
|
|
541
|
+
if (glassOnly && target.kind === 'text' && matches.length > 0) {
|
|
542
|
+
const scrollers = await (0, glassclip_1.measureScrollers)(snapshot);
|
|
543
|
+
const frames = await Promise.all(matches.map((entry) => (0, inspect_1.measureFiber)(entry.node.fiber)));
|
|
544
|
+
matches = matches.filter((entry, i) => (0, glassclip_1.visiblePartOnGlass)(snapshot, entry, frames[i] ?? null, scrollers) !== null);
|
|
545
|
+
}
|
|
535
546
|
if (matches.length === 0)
|
|
536
547
|
await refuseBehindSheet(snapshot, target);
|
|
537
548
|
if (target.index !== undefined) {
|
|
@@ -935,7 +946,8 @@ function focusInput(entry, frame) {
|
|
|
935
946
|
}
|
|
936
947
|
async function pressLike(kind, rawParams) {
|
|
937
948
|
const params = paramsOf(rawParams);
|
|
938
|
-
const
|
|
949
|
+
const afterMs = kind === 'press' ? againAfter(params.again) : undefined;
|
|
950
|
+
const { snapshot, entry } = await resolveTarget(params.target, kind, params.noScroll === true);
|
|
939
951
|
if (entry.tab) {
|
|
940
952
|
if (kind === 'longPress')
|
|
941
953
|
throw notActionable('no_action', `${describe(entry)} has no long-press action`);
|
|
@@ -977,7 +989,7 @@ async function pressLike(kind, rawParams) {
|
|
|
977
989
|
if (params.force !== true) {
|
|
978
990
|
// What doesn't depend on where the element is comes first: an open alert is answered before anything scrolls.
|
|
979
991
|
assertNoBlockingAlert(planned.actor);
|
|
980
|
-
reach = await scrollIntoReach(snapshot, planned.actor, params.dryRun === true);
|
|
992
|
+
reach = await scrollIntoReach(snapshot, planned.actor, params.dryRun === true, params.noScroll === true);
|
|
981
993
|
if (reach.scrolled) {
|
|
982
994
|
const again = await reresolve(planned.actor, kind, params.target);
|
|
983
995
|
current = again.snapshot;
|
|
@@ -1004,7 +1016,61 @@ async function pressLike(kind, rawParams) {
|
|
|
1004
1016
|
if (own)
|
|
1005
1017
|
callHandler('onPress', own, [pressEvent(actor.node.fiber, frame)]);
|
|
1006
1018
|
}
|
|
1007
|
-
|
|
1019
|
+
const second = afterMs === undefined ? undefined : await pressAgain(actor, params, afterMs);
|
|
1020
|
+
return {
|
|
1021
|
+
element,
|
|
1022
|
+
...(reach.scrolled ? { scrolled: true } : {}),
|
|
1023
|
+
...keyboardNote(closedKeyboard),
|
|
1024
|
+
...forced,
|
|
1025
|
+
...(second !== undefined ? { second } : {}),
|
|
1026
|
+
...(await settleOutcome()),
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
/** `again`: a second tap this many milliseconds after the first (a stress double tap, AGENT-TOOLS 1.12 §17.4). */
|
|
1030
|
+
function againAfter(raw) {
|
|
1031
|
+
if (raw === undefined)
|
|
1032
|
+
return undefined;
|
|
1033
|
+
if (typeof raw !== 'number' || !Number.isFinite(raw) || raw < 20 || raw > 1000) {
|
|
1034
|
+
throw badParams('again must be milliseconds from 20 to 1000');
|
|
1035
|
+
}
|
|
1036
|
+
return Math.round(raw);
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* The second tap of a double tap, made `afterMs` after the first on whatever is there by then, as a finger would
|
|
1040
|
+
* land: the app may have disabled or removed the control (that is the guard against a double submit working).
|
|
1041
|
+
* It goes through the same checks as a tap; a refusal is the answer's `second`, never an error.
|
|
1042
|
+
*/
|
|
1043
|
+
async function pressAgain(first, params, afterMs) {
|
|
1044
|
+
const started = Date.now();
|
|
1045
|
+
await new Promise((resolve) => setTimeout(resolve, afterMs));
|
|
1046
|
+
try {
|
|
1047
|
+
const again = await reresolve(first, 'press', params.target, false);
|
|
1048
|
+
const found = findActionable(again.snapshot, again.entry, 'press', false);
|
|
1049
|
+
if (!found || found.info.disabled || again.entry.info.disabled) {
|
|
1050
|
+
return { done: false, reason: found ? 'disabled' : 'no_action', afterMs: Date.now() - started };
|
|
1051
|
+
}
|
|
1052
|
+
// The second tap is refused as `changed` when the id, label or text is no longer what the first tap found: a
|
|
1053
|
+
// control that renames itself ("Added 1") and a list row that was recycled are both another element now.
|
|
1054
|
+
if (!sameTarget(first, again.entry))
|
|
1055
|
+
return { done: false, reason: 'changed', afterMs: Date.now() - started };
|
|
1056
|
+
const handler = (0, inspect_1.findHandler)(found.node, 'onPress');
|
|
1057
|
+
if (!handler)
|
|
1058
|
+
return { done: false, reason: 'no_action', afterMs: Date.now() - started };
|
|
1059
|
+
const { frame } = await elementFor(found);
|
|
1060
|
+
if (params.force !== true)
|
|
1061
|
+
await assertReachable(again.snapshot, found, frame);
|
|
1062
|
+
const at = Date.now() - started;
|
|
1063
|
+
callHandler('onPress', handler, [pressEvent(found.node.fiber, frame)]);
|
|
1064
|
+
return { done: true, afterMs: at };
|
|
1065
|
+
}
|
|
1066
|
+
catch (error) {
|
|
1067
|
+
const reason = error instanceof types_1.TaplineError
|
|
1068
|
+
? error.code === 'not_actionable'
|
|
1069
|
+
? (error.message.split(':')[0] ?? error.code)
|
|
1070
|
+
: error.code
|
|
1071
|
+
: 'internal';
|
|
1072
|
+
return { done: false, reason, afterMs: Date.now() - started };
|
|
1073
|
+
}
|
|
1008
1074
|
}
|
|
1009
1075
|
/** Where a finger would land: the middle of the element (points, rounded). */
|
|
1010
1076
|
function pointOf(frame) {
|
|
@@ -1129,9 +1195,9 @@ async function moveFocusAboveKeyboard(entry) {
|
|
|
1129
1195
|
function keyboardNote(closed) {
|
|
1130
1196
|
return closed ? { closedKeyboard: true } : {};
|
|
1131
1197
|
}
|
|
1132
|
-
async function resolveInput(snapshot, rawTarget) {
|
|
1198
|
+
async function resolveInput(snapshot, rawTarget, glassOnly = false) {
|
|
1133
1199
|
if (rawTarget !== undefined && rawTarget !== null) {
|
|
1134
|
-
const { snapshot: snap, entry } = await resolveTarget(rawTarget, 'type');
|
|
1200
|
+
const { snapshot: snap, entry } = await resolveTarget(rawTarget, 'type', glassOnly);
|
|
1135
1201
|
const input = findActionable(snap, entry, 'type', true);
|
|
1136
1202
|
if (!input)
|
|
1137
1203
|
throw notActionable('no_action', `${describe(entry)} is not an input`);
|
|
@@ -1178,7 +1244,7 @@ async function typeText(rawParams) {
|
|
|
1178
1244
|
throw badParams('text is required (or pass submit: true)');
|
|
1179
1245
|
// Deleting from the end edits what is there, so it never replaces the whole text.
|
|
1180
1246
|
const replace = params.replace !== false && !deleting;
|
|
1181
|
-
let { snapshot, entry } = await resolveInput(null, params.target);
|
|
1247
|
+
let { snapshot, entry } = await resolveInput(null, params.target, params.noScroll === true);
|
|
1182
1248
|
if (entry.info.disabled)
|
|
1183
1249
|
throw notActionable('disabled', `${describe(entry)} is not editable; ${ENABLE_FIRST}.`);
|
|
1184
1250
|
// Off screen, in the tree: scroll it into view first (AGENT-TOOLS 1.12 §16.2; the policy ran before this
|
|
@@ -1187,7 +1253,7 @@ async function typeText(rawParams) {
|
|
|
1187
1253
|
if (params.force !== true)
|
|
1188
1254
|
assertNoBlockingAlert(entry);
|
|
1189
1255
|
if (params.force !== true && !ownsInstance(entry, keyboard.focusedInput())) {
|
|
1190
|
-
reach = await scrollIntoReach(snapshot, entry, params.dryRun === true);
|
|
1256
|
+
reach = await scrollIntoReach(snapshot, entry, params.dryRun === true, params.noScroll === true);
|
|
1191
1257
|
if (reach.scrolled) {
|
|
1192
1258
|
let again;
|
|
1193
1259
|
try {
|
|
@@ -1528,10 +1594,17 @@ function visiblePart(frame, screen) {
|
|
|
1528
1594
|
* alert, under a bar) run afterwards, as for any element. Refused as `off_screen`, with the reason, when
|
|
1529
1595
|
* no scroll can bring it on screen. `dry` predicts the point after the scroll and scrolls nothing.
|
|
1530
1596
|
*/
|
|
1531
|
-
async function scrollIntoReach(snapshot, entry, dry) {
|
|
1597
|
+
async function scrollIntoReach(snapshot, entry, dry, noScroll = false) {
|
|
1532
1598
|
const frame = await (0, inspect_1.measureFiber)(entry.node.fiber);
|
|
1533
1599
|
if (frame === null)
|
|
1534
1600
|
return {};
|
|
1601
|
+
// A journey in a user's eyes (`noScroll`, §17.1): nothing scrolls by itself. An element wholly off screen is refused.
|
|
1602
|
+
if (noScroll) {
|
|
1603
|
+
// On the glass, not just on screen: a scroll view clips it, the keyboard covers it (1.5 review, finding 11).
|
|
1604
|
+
if ((0, glassclip_1.visiblePartOnGlass)(snapshot, entry, frame, await (0, glassclip_1.measureScrollers)(snapshot)) !== null)
|
|
1605
|
+
return {};
|
|
1606
|
+
throw notActionable('off_screen', `${describe(entry)} is off screen; in a journey nothing scrolls by itself: scroll the way a person would, then try again.`);
|
|
1607
|
+
}
|
|
1535
1608
|
// Partly on screen (over the edge, or under a bar): scrolled clear too, with one attempt; if that
|
|
1536
1609
|
// can't be done the action goes on as before and the touch checks decide. Never refused for this.
|
|
1537
1610
|
const partial = visiblePart(frame, snapshot.screen) !== null;
|
|
@@ -1627,7 +1700,7 @@ function refindTarget(entry, original) {
|
|
|
1627
1700
|
* The same element after a scroll, from a fresh snapshot (the list may have laid out other rows on the
|
|
1628
1701
|
* way). When a list unmounted the row, the ref is gone: it is found again by the original target.
|
|
1629
1702
|
*/
|
|
1630
|
-
async function reresolve(entry, hint, original) {
|
|
1703
|
+
async function reresolve(entry, hint, original, checkSame = true) {
|
|
1631
1704
|
let found;
|
|
1632
1705
|
try {
|
|
1633
1706
|
found = await resolveTarget({ ref: (0, inspect_1.refFor)(entry.node.fiber) }, hint);
|
|
@@ -1640,21 +1713,24 @@ async function reresolve(entry, hint, original) {
|
|
|
1640
1713
|
throw error;
|
|
1641
1714
|
found = await resolveTarget(again, hint);
|
|
1642
1715
|
}
|
|
1643
|
-
|
|
1716
|
+
if (checkSame)
|
|
1717
|
+
assertSameTarget(entry, found.entry);
|
|
1644
1718
|
return found;
|
|
1645
1719
|
}
|
|
1720
|
+
/** The same element by id, label and text, each on its own (a row can keep its id and label while its text changes). */
|
|
1721
|
+
function sameTarget(before, after) {
|
|
1722
|
+
return before.info.testID === after.info.testID && before.info.label === after.info.label && before.info.text === after.info.text;
|
|
1723
|
+
}
|
|
1646
1724
|
/**
|
|
1647
1725
|
* After a scroll the element found again must still be the one the safety check saw: a list that recycles its rows
|
|
1648
1726
|
* can hand the same component another row ("Continue" becomes "Delete account"). A changed id, label or text is
|
|
1649
1727
|
* refused before anything is touched (Codex review of 1.4.0).
|
|
1650
1728
|
*/
|
|
1651
1729
|
function assertSameTarget(before, after) {
|
|
1652
|
-
|
|
1730
|
+
if (sameTarget(before, after))
|
|
1731
|
+
return;
|
|
1653
1732
|
const was = { id: before.info.testID, name: labelOf(before.info) };
|
|
1654
1733
|
const now = { id: after.info.testID, name: labelOf(after.info) };
|
|
1655
|
-
const same = was.id === now.id && before.info.label === after.info.label && before.info.text === after.info.text;
|
|
1656
|
-
if (same)
|
|
1657
|
-
return;
|
|
1658
1734
|
if (was.name === now.name) {
|
|
1659
1735
|
was.name = before.info.text;
|
|
1660
1736
|
now.name = after.info.text;
|
|
@@ -1887,7 +1963,7 @@ async function toggle(rawParams) {
|
|
|
1887
1963
|
const params = paramsOf(rawParams);
|
|
1888
1964
|
if (params.value !== undefined && typeof params.value !== 'boolean')
|
|
1889
1965
|
throw badParams('value must be a boolean');
|
|
1890
|
-
const first = await resolveTarget(params.target, 'toggle');
|
|
1966
|
+
const first = await resolveTarget(params.target, 'toggle', params.noScroll === true);
|
|
1891
1967
|
const plan = (snap, picked) => {
|
|
1892
1968
|
const found = findActionable(snap, picked, 'toggle', true);
|
|
1893
1969
|
if (!found)
|
|
@@ -1905,7 +1981,7 @@ async function toggle(rawParams) {
|
|
|
1905
1981
|
let reach = {};
|
|
1906
1982
|
if (params.force !== true) {
|
|
1907
1983
|
assertNoBlockingAlert(planned.control);
|
|
1908
|
-
reach = await scrollIntoReach(snapshot, planned.control, params.dryRun === true);
|
|
1984
|
+
reach = await scrollIntoReach(snapshot, planned.control, params.dryRun === true, params.noScroll === true);
|
|
1909
1985
|
if (reach.scrolled) {
|
|
1910
1986
|
const again = await reresolve(planned.control, 'toggle', params.target);
|
|
1911
1987
|
snapshot = again.snapshot;
|
|
@@ -2036,6 +2112,68 @@ async function audit(rawParams) {
|
|
|
2036
2112
|
}
|
|
2037
2113
|
return { issues, checked: [...types] };
|
|
2038
2114
|
}
|
|
2115
|
+
/** `ux_check {wayBack?, earlier?}` (AGENT-TOOLS 1.12 §17.3): the screen against the usability basics. */
|
|
2116
|
+
async function uxCheck(rawParams) {
|
|
2117
|
+
const params = paramsOf(rawParams);
|
|
2118
|
+
if (params.wayBack !== undefined && typeof params.wayBack !== 'boolean')
|
|
2119
|
+
throw badParams('wayBack must be true or false');
|
|
2120
|
+
const earlierRaw = params.earlier;
|
|
2121
|
+
let earlier;
|
|
2122
|
+
if (earlierRaw !== undefined && earlierRaw !== null) {
|
|
2123
|
+
if (!Array.isArray(earlierRaw))
|
|
2124
|
+
throw badParams('earlier must be a list of {label, id?}');
|
|
2125
|
+
earlier = [];
|
|
2126
|
+
for (const item of earlierRaw) {
|
|
2127
|
+
if (!(0, types_1.isRecord)(item) || typeof item.label !== 'string')
|
|
2128
|
+
throw badParams('earlier must be a list of {label, id?}');
|
|
2129
|
+
earlier.push({ label: item.label, ...(typeof item.id === 'string' && item.id !== '' ? { id: item.id } : {}) });
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
if (params.glass !== undefined && typeof params.glass !== 'boolean')
|
|
2133
|
+
throw badParams('glass must be true or false');
|
|
2134
|
+
const result = await (0, uxcheck_1.uxScreen)({
|
|
2135
|
+
...(params.glass === true ? { glass: true } : {}),
|
|
2136
|
+
...(params.wayBack === true ? { wayBack: true } : {}),
|
|
2137
|
+
...(earlier !== undefined ? { earlier } : {}),
|
|
2138
|
+
});
|
|
2139
|
+
let sources = [];
|
|
2140
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
2141
|
+
const fibers = result.findings.flatMap((f) => (f.entry !== null ? [f.entry.node.fiber] : []));
|
|
2142
|
+
try {
|
|
2143
|
+
sources = fibers.length > 0 ? await (0, source_1.sourcesOf)(fibers) : [];
|
|
2144
|
+
}
|
|
2145
|
+
catch {
|
|
2146
|
+
sources = []; // Metro unreachable: the report still stands, without lines
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
let next = 0;
|
|
2150
|
+
const issues = [];
|
|
2151
|
+
for (const finding of result.findings) {
|
|
2152
|
+
const source = finding.entry !== null ? sources[next++] : undefined;
|
|
2153
|
+
let element = null;
|
|
2154
|
+
if (finding.entry !== null) {
|
|
2155
|
+
try {
|
|
2156
|
+
element = (await elementFor(finding.entry, finding.frame)).element;
|
|
2157
|
+
}
|
|
2158
|
+
catch {
|
|
2159
|
+
element = null;
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
const where = element === null && finding.entry !== null
|
|
2163
|
+
? whereOf({ type: 'description', entry: finding.entry, frame: finding.frame, problem: '', fix: '' })
|
|
2164
|
+
: null;
|
|
2165
|
+
const location = source?.location ?? null;
|
|
2166
|
+
issues.push({
|
|
2167
|
+
kind: finding.kind,
|
|
2168
|
+
element,
|
|
2169
|
+
...(where !== null ? { where } : {}),
|
|
2170
|
+
problem: finding.problem,
|
|
2171
|
+
basis: finding.basis,
|
|
2172
|
+
...(location !== null ? { at: { file: location.file, line: location.line, direct: location.direct } } : {}),
|
|
2173
|
+
});
|
|
2174
|
+
}
|
|
2175
|
+
return { issues, checked: result.checked, choices: result.choices, actions: result.actions };
|
|
2176
|
+
}
|
|
2039
2177
|
async function getLogs(rawParams) {
|
|
2040
2178
|
const params = paramsOf(rawParams);
|
|
2041
2179
|
if (params.since !== undefined && typeof params.since !== 'number')
|