react-native-tapline 1.0.0 → 1.1.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 CHANGED
@@ -1,4 +1,6 @@
1
1
  import { type Entry, type Snapshot } from './inspect';
2
+ import { type ElementSource } from './source';
3
+ import { type AuditType } from './checks';
2
4
  import { type Fiber } from './fiber';
3
5
  import { type LogEntry, type TaplineElement } from './types';
4
6
  export { tree } from './inspect';
@@ -65,6 +67,7 @@ export declare function describeHit(snapshot: Snapshot, hit: Fiber, actor?: Entr
65
67
  export declare function back(rawParams: unknown): Promise<{
66
68
  route: string[];
67
69
  closed?: 'sheet';
70
+ dryRun?: true;
68
71
  } & SettleOutcome>;
69
72
  /**
70
73
  * What `press` and `longPress` answer: the element, `closedKeyboard: true` when the keyboard was
@@ -74,6 +77,11 @@ export type PressResult = {
74
77
  element: TaplineElement;
75
78
  closedKeyboard?: true;
76
79
  forced?: string;
80
+ dryRun?: true;
81
+ point?: {
82
+ x: number;
83
+ y: number;
84
+ };
77
85
  } & SettleOutcome;
78
86
  export declare function press(params: unknown): Promise<PressResult>;
79
87
  export declare function longPress(params: unknown): Promise<PressResult>;
@@ -100,8 +108,10 @@ export declare function setKeyboardControl(next: KeyboardControl | null): void;
100
108
  export declare function typeText(rawParams: unknown): Promise<{
101
109
  element: TaplineElement;
102
110
  value: string;
111
+ secure?: true;
103
112
  closedKeyboard?: true;
104
113
  forced?: string;
114
+ dryRun?: true;
105
115
  } & SettleOutcome>;
106
116
  /** What `scroll` answers; `target`, `reached` and `unchanged` only with `to: "target"`. */
107
117
  export type ScrollResult = {
@@ -118,6 +128,8 @@ export type ScrollResult = {
118
128
  reached?: boolean;
119
129
  /** The element was clear already: nothing was scrolled. */
120
130
  unchanged?: true;
131
+ /** A dry run (AGENT-TOOLS 1.8 §11.4): resolved and checked, nothing scrolled. */
132
+ dryRun?: true;
121
133
  } & SettleOutcome;
122
134
  export declare function scroll(rawParams: unknown): Promise<ScrollResult>;
123
135
  export declare function toggle(rawParams: unknown): Promise<{
@@ -126,8 +138,38 @@ export declare function toggle(rawParams: unknown): Promise<{
126
138
  closedKeyboard?: true;
127
139
  unchanged?: true;
128
140
  forced?: string;
141
+ dryRun?: true;
142
+ point?: {
143
+ x: number;
144
+ y: number;
145
+ };
129
146
  } & SettleOutcome>;
130
147
  export declare function openURL(rawParams: unknown): Promise<SettleOutcome>;
148
+ /**
149
+ * `inspect {target}` (Tapline 1.1, development builds): the element, the components that draw it, the line
150
+ * of the app's code that wrote it, and that component's props in short. From the screen to the code.
151
+ */
152
+ export declare function inspect(rawParams: unknown): Promise<{
153
+ element: TaplineElement;
154
+ source: ElementSource;
155
+ }>;
156
+ export interface AuditIssue {
157
+ type: AuditType;
158
+ element: TaplineElement | null;
159
+ problem: string;
160
+ fix: string;
161
+ /** Where the app's code wrote the element (development builds), as Metro names the file. */
162
+ at?: {
163
+ file: string;
164
+ line: number;
165
+ direct: boolean;
166
+ };
167
+ }
168
+ /** `audit {types?}` (AGENT-TOOLS 1.8 §10): the screen's accessibility problems, each with its source line in development. */
169
+ export declare function audit(rawParams: unknown): Promise<{
170
+ issues: AuditIssue[];
171
+ checked: AuditType[];
172
+ }>;
131
173
  export declare function getLogs(rawParams: unknown): Promise<{
132
174
  entries: LogEntry[];
133
175
  }>;
@@ -157,4 +199,5 @@ export declare function swipe(rawParams: unknown): Promise<{
157
199
  x: number;
158
200
  y: number;
159
201
  };
202
+ dryRun?: true;
160
203
  } & SettleOutcome>;
package/dist/actions.js CHANGED
@@ -17,6 +17,8 @@ exports.typeText = typeText;
17
17
  exports.scroll = scroll;
18
18
  exports.toggle = toggle;
19
19
  exports.openURL = openURL;
20
+ exports.inspect = inspect;
21
+ exports.audit = audit;
20
22
  exports.getLogs = getLogs;
21
23
  exports.reload = reload;
22
24
  exports.screenshotAvailable = screenshotAvailable;
@@ -26,6 +28,8 @@ const react_native_1 = require("react-native");
26
28
  const keyboard_1 = require("./keyboard");
27
29
  const native_1 = require("./native");
28
30
  const inspect_1 = require("./inspect");
31
+ const source_1 = require("./source");
32
+ const checks_1 = require("./checks");
29
33
  const alerts_1 = require("./alerts");
30
34
  const fiber_1 = require("./fiber");
31
35
  const logs_1 = require("./logs");
@@ -774,7 +778,7 @@ function requestCloseOf(host) {
774
778
  }
775
779
  /** `back`: what the back button or gesture does, on the focused screen; with a sheet open, closes the sheet. */
776
780
  async function back(rawParams) {
777
- paramsOf(rawParams);
781
+ const dry = paramsOf(rawParams).dryRun === true;
778
782
  const snapshot = (0, inspect_1.takeSnapshot)();
779
783
  const current = (0, inspect_1.currentRoute)(snapshot.routes);
780
784
  // An open sheet is what back closes first (AGENT-TOOLS v1.4): the top Modal's own onRequestClose,
@@ -786,6 +790,8 @@ async function back(rawParams) {
786
790
  // a person taps that, so back does.
787
791
  const backdrop = onRequestClose ? null : await backdropOf(snapshot);
788
792
  if (backdrop) {
793
+ if (dry)
794
+ return { route: current?.chain ?? [], closed: 'sheet', dryRun: true, settledMs: 0 };
789
795
  callHandler('onPress', backdrop.onPress, [pressEvent(backdrop.entry.node.fiber, backdrop.frame)]);
790
796
  return { route: current?.chain ?? [], closed: 'sheet', ...(await settleOutcome()) };
791
797
  }
@@ -794,6 +800,8 @@ async function back(rawParams) {
794
800
  const shows = await frontLine(snapshot);
795
801
  throw notActionable('no_action', `the open sheet${shows !== undefined ? ` (in front: ${shows})` : ''} has nothing for back to call (its Modal has no onRequestClose); tap its close control.`);
796
802
  }
803
+ if (dry)
804
+ return { route: current?.chain ?? [], closed: 'sheet', dryRun: true, settledMs: 0 };
797
805
  callHandler('onRequestClose', onRequestClose, []);
798
806
  return { route: current?.chain ?? [], closed: 'sheet', ...(await settleOutcome()) };
799
807
  }
@@ -812,6 +820,8 @@ async function back(rawParams) {
812
820
  if (!canGoBack || typeof nav.goBack !== 'function') {
813
821
  throw notActionable('no_action', `Nothing to go back to from ${current.chain.join(' › ')}.`);
814
822
  }
823
+ if (dry)
824
+ return { route: current.chain, dryRun: true, settledMs: 0 };
815
825
  callHandler('goBack', nav.goBack, []);
816
826
  return { route: current.chain, ...(await settleOutcome()) };
817
827
  }
@@ -890,10 +900,14 @@ async function pressLike(kind, rawParams) {
890
900
  if (entry.tab) {
891
901
  if (kind === 'longPress')
892
902
  throw notActionable('no_action', `${describe(entry)} has no long-press action`);
903
+ if (params.dryRun === true)
904
+ return { element: (0, inspect_1.toElement)(entry, null), dryRun: true, settledMs: 0 };
893
905
  selectTab(snapshot, entry.tab);
894
906
  return { element: (0, inspect_1.toElement)(entry, null), ...keyboardNote(closeKeyboardFor(entry)), ...(await settleOutcome()) };
895
907
  }
896
908
  if (entry.navBack) {
909
+ if (params.dryRun === true)
910
+ return { element: (0, inspect_1.toElement)(entry, null), dryRun: true, settledMs: 0 };
897
911
  const { route: _route, ...outcome } = await back({});
898
912
  return { element: (0, inspect_1.toElement)(entry, null), ...outcome };
899
913
  }
@@ -916,6 +930,9 @@ async function pressLike(kind, rawParams) {
916
930
  }
917
931
  const { element, frame } = await elementFor(actor);
918
932
  const forced = await checkedOrForced(params.force === true, () => assertReachable(snapshot, actor, frame));
933
+ // A dry run (AGENT-TOOLS 1.8 §11.4): every check ran; nothing is touched.
934
+ if (params.dryRun === true)
935
+ return { element, dryRun: true, ...pointOf(frame), ...forced, settledMs: 0 };
919
936
  const closedKeyboard = closeKeyboardFor(actor);
920
937
  if (handler)
921
938
  callHandler(handlerName, handler, [pressEvent(actor.node.fiber, frame)]);
@@ -929,6 +946,10 @@ async function pressLike(kind, rawParams) {
929
946
  }
930
947
  return { element, ...keyboardNote(closedKeyboard), ...forced, ...(await settleOutcome()) };
931
948
  }
949
+ /** Where a finger would land: the middle of the element (points, rounded). */
950
+ function pointOf(frame) {
951
+ return frame ? { point: { x: Math.round(frame.x + frame.width / 2), y: Math.round(frame.y + frame.height / 2) } } : {};
952
+ }
932
953
  function press(params) {
933
954
  return pressLike('press', params);
934
955
  }
@@ -1112,6 +1133,10 @@ async function typeText(rawParams) {
1112
1133
  await assertReachable(snapshot, entry, await (0, inspect_1.measureFiber)(entry.node.fiber));
1113
1134
  }
1114
1135
  });
1136
+ if (params.dryRun === true) {
1137
+ const { element } = await elementFor(entry);
1138
+ return { element, value: (0, inspect_1.displayText)((0, inspect_1.currentInputValue)(entry.node)), dryRun: true, ...forced, settledMs: 0 };
1139
+ }
1115
1140
  // Typing into the input that has focus leaves the keyboard alone. Another input above the keyboard
1116
1141
  // takes the focus with the keyboard up; one the keyboard covers closes it first.
1117
1142
  const closedKeyboard = !(await moveFocusAboveKeyboard(entry)) && closeKeyboardFor(entry);
@@ -1146,7 +1171,19 @@ async function typeText(rawParams) {
1146
1171
  }
1147
1172
  const outcome = await settleOutcome();
1148
1173
  const { element } = await elementFor(entry);
1149
- return { element: { ...element, value: (0, inspect_1.displayText)(next) }, value: (0, inspect_1.displayText)(next), ...keyboardNote(closedKeyboard), ...forced, ...outcome };
1174
+ // A password field's text never leaves the app: one bullet per character, as iOS draws it.
1175
+ const secure = (0, inspect_1.isSecureInput)(entry.node);
1176
+ const shown = secure ? (0, inspect_1.maskSecret)(next) : (0, inspect_1.displayText)(next);
1177
+ // An input that now holds text shows no placeholder (AGENT-TOOLS v1.7).
1178
+ const { placeholder, ...rest } = element;
1179
+ return {
1180
+ element: { ...rest, ...(next === '' && placeholder !== undefined ? { placeholder } : {}), value: shown },
1181
+ value: shown,
1182
+ ...(secure ? { secure: true } : {}),
1183
+ ...keyboardNote(closedKeyboard),
1184
+ ...forced,
1185
+ ...outcome,
1186
+ };
1150
1187
  }
1151
1188
  const rememberedOffsets = new WeakMap();
1152
1189
  /** Objects that may expose scroll methods: host public instances first, then list class instances. */
@@ -1241,6 +1278,13 @@ async function scrollToTarget(params) {
1241
1278
  break;
1242
1279
  p = node.parent;
1243
1280
  }
1281
+ if (params.dryRun === true) {
1282
+ const first = scrollers[0];
1283
+ const target = (await elementFor(wanted)).element;
1284
+ if (!first)
1285
+ return { element: target, target, offset: { x: 0, y: 0 }, moved: false, dryRun: true, settledMs: 0 };
1286
+ return { element: (await elementFor(first)).element, target, offset: { x: 0, y: 0 }, moved: false, dryRun: true, settledMs: 0 };
1287
+ }
1244
1288
  const { width, height } = snapshot.screen;
1245
1289
  const navBar = (0, inspect_1.nativeNavBar)(snapshot);
1246
1290
  const tabBar = (0, inspect_1.nativeTabBar)(snapshot);
@@ -1426,6 +1470,10 @@ async function scroll(rawParams) {
1426
1470
  entry = best;
1427
1471
  frame = bestFrame;
1428
1472
  }
1473
+ if (params.dryRun === true) {
1474
+ const { element } = await elementFor(entry, frame);
1475
+ return { element, offset: { x: 0, y: 0 }, moved: false, dryRun: true, settledMs: 0 };
1476
+ }
1429
1477
  const candidates = scrollCandidates(entry);
1430
1478
  const key = rememberKey(entry, candidates);
1431
1479
  const horizontal = (0, inspect_1.findProp)(entry.node, 'horizontal') === true;
@@ -1579,6 +1627,10 @@ async function toggle(rawParams) {
1579
1627
  const current = control.info.checked === true;
1580
1628
  const next = typeof params.value === 'boolean' ? params.value : !current;
1581
1629
  const forced = await checkedOrForced(params.force === true, async () => assertReachable(snapshot, control, await (0, inspect_1.measureFiber)(control.node.fiber)));
1630
+ if (params.dryRun === true) {
1631
+ const { element, frame } = await elementFor(control);
1632
+ return { element, value: next, dryRun: true, ...pointOf(frame), ...forced, settledMs: 0 };
1633
+ }
1582
1634
  if (typeof params.value === 'boolean' && control.info.checked === params.value) {
1583
1635
  // Already there: a person wouldn't touch it, so the handler isn't called and the keyboard stays.
1584
1636
  const { element } = await elementFor(control);
@@ -1617,6 +1669,56 @@ async function openURL(rawParams) {
1617
1669
  }
1618
1670
  return settleOutcome();
1619
1671
  }
1672
+ /**
1673
+ * `inspect {target}` (Tapline 1.1, development builds): the element, the components that draw it, the line
1674
+ * of the app's code that wrote it, and that component's props in short. From the screen to the code.
1675
+ */
1676
+ async function inspect(rawParams) {
1677
+ if (typeof __DEV__ === 'undefined' || !__DEV__) {
1678
+ throw new types_1.TaplineError('unsupported', 'inspect needs a development build: release builds keep no source locations');
1679
+ }
1680
+ const params = paramsOf(rawParams);
1681
+ const { entry } = await resolveTarget(params.target);
1682
+ const [source] = await (0, source_1.sourcesOf)([entry.node.fiber]);
1683
+ const { element } = await elementFor(entry);
1684
+ return { element, source: source ?? { components: [], location: null, props: {} } };
1685
+ }
1686
+ /** `audit {types?}` (AGENT-TOOLS 1.8 §10): the screen's accessibility problems, each with its source line in development. */
1687
+ async function audit(rawParams) {
1688
+ const params = paramsOf(rawParams);
1689
+ const asked = params.types;
1690
+ if (asked !== undefined && (!Array.isArray(asked) || asked.some((t) => typeof t !== 'string'))) {
1691
+ throw badParams('types must be a list of audit types');
1692
+ }
1693
+ const known = new Set(checks_1.AUDIT_TYPES);
1694
+ const types = new Set(asked === undefined ? checks_1.AUDIT_TYPES : checks_1.AUDIT_TYPES.filter((t) => asked.includes(t) && known.has(t)));
1695
+ const findings = await (0, checks_1.auditScreen)(types);
1696
+ let sources = [];
1697
+ if (typeof __DEV__ !== 'undefined' && __DEV__) {
1698
+ const fibers = findings.flatMap((f) => (f.entry !== null ? [f.entry.node.fiber] : []));
1699
+ try {
1700
+ sources = fibers.length > 0 ? await (0, source_1.sourcesOf)(fibers) : [];
1701
+ }
1702
+ catch {
1703
+ sources = []; // Metro unreachable: the report still stands, without lines
1704
+ }
1705
+ }
1706
+ let next = 0;
1707
+ const issues = [];
1708
+ for (const finding of findings) {
1709
+ const source = finding.entry !== null ? sources[next++] : undefined;
1710
+ const element = finding.entry !== null ? (await elementFor(finding.entry, finding.frame)).element : null;
1711
+ const location = source?.location ?? null;
1712
+ issues.push({
1713
+ type: finding.type,
1714
+ element,
1715
+ problem: finding.problem,
1716
+ fix: finding.fix,
1717
+ ...(location !== null ? { at: { file: location.file, line: location.line, direct: location.direct } } : {}),
1718
+ });
1719
+ }
1720
+ return { issues, checked: [...types] };
1721
+ }
1620
1722
  async function getLogs(rawParams) {
1621
1723
  const params = paramsOf(rawParams);
1622
1724
  if (params.since !== undefined && typeof params.since !== 'number')
@@ -1802,6 +1904,12 @@ async function swipe(rawParams) {
1802
1904
  throw notActionable('no_action', `Nothing at (${x0}, ${y0}) to swipe on.`);
1803
1905
  const origin = start;
1804
1906
  const chain = hostChain(origin);
1907
+ if (params.dryRun === true) {
1908
+ // Who would take the drag is only known by asking the views, which can act on it: a dry run names what lies under the start.
1909
+ const under = snapshot.entries.filter((entry) => chain.some((fiber) => (0, fiber_1.sameFiber)(fiber, entry.node.fiber))).pop();
1910
+ const element = under ? (await elementFor(under)).element : null;
1911
+ return { element, from: { x: x0, y: y0 }, to: { x: x1, y: y1 }, dryRun: true, settledMs: 0 };
1912
+ }
1805
1913
  const begin = Date.now();
1806
1914
  const record = {
1807
1915
  touchActive: true,