@reactiive/ennio 0.0.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.
Files changed (57) hide show
  1. package/EnnioCore.podspec +61 -0
  2. package/LICENSE +21 -0
  3. package/README.md +50 -0
  4. package/android/CMakeLists.txt +40 -0
  5. package/android/build.gradle +64 -0
  6. package/cpp/ElementMatcher.cpp +661 -0
  7. package/cpp/ElementMatcher.hpp +244 -0
  8. package/cpp/EnnioLog.hpp +182 -0
  9. package/cpp/HybridEnnio.cpp +1161 -0
  10. package/cpp/HybridEnnio.hpp +174 -0
  11. package/cpp/IdleMonitor.hpp +277 -0
  12. package/cpp/Protocol.cpp +135 -0
  13. package/cpp/Protocol.hpp +47 -0
  14. package/cpp/SelectorCriteria.hpp +281 -0
  15. package/cpp/SelectorParser.cpp +649 -0
  16. package/cpp/SelectorParser.hpp +94 -0
  17. package/cpp/ShadowTreeTraverser.cpp +305 -0
  18. package/cpp/ShadowTreeTraverser.hpp +142 -0
  19. package/cpp/TestIDRegistry.cpp +109 -0
  20. package/cpp/TestIDRegistry.hpp +84 -0
  21. package/dist/cli.js +16221 -0
  22. package/ios/EnnioAutoInit.mm +338 -0
  23. package/ios/EnnioDebugBanner.h +19 -0
  24. package/ios/EnnioDebugBanner.mm +178 -0
  25. package/ios/EnnioRuntimeHelper.h +264 -0
  26. package/ios/EnnioRuntimeHelper.mm +2443 -0
  27. package/lib/Ennio.nitro.d.ts +263 -0
  28. package/lib/Ennio.nitro.d.ts.map +1 -0
  29. package/lib/Ennio.nitro.js +2 -0
  30. package/lib/Ennio.nitro.js.map +1 -0
  31. package/lib/index.d.ts +16 -0
  32. package/lib/index.d.ts.map +1 -0
  33. package/lib/index.js +45 -0
  34. package/lib/index.js.map +1 -0
  35. package/nitro.json +24 -0
  36. package/nitrogen/generated/.gitattributes +1 -0
  37. package/nitrogen/generated/android/EnnioCore+autolinking.cmake +81 -0
  38. package/nitrogen/generated/android/EnnioCore+autolinking.gradle +27 -0
  39. package/nitrogen/generated/android/EnnioCoreOnLoad.cpp +49 -0
  40. package/nitrogen/generated/android/EnnioCoreOnLoad.hpp +34 -0
  41. package/nitrogen/generated/android/kotlin/com/margelo/nitro/ennio/EnnioCoreOnLoad.kt +35 -0
  42. package/nitrogen/generated/ios/EnnioCore+autolinking.rb +62 -0
  43. package/nitrogen/generated/ios/EnnioCore-Swift-Cxx-Bridge.cpp +17 -0
  44. package/nitrogen/generated/ios/EnnioCore-Swift-Cxx-Bridge.hpp +27 -0
  45. package/nitrogen/generated/ios/EnnioCore-Swift-Cxx-Umbrella.hpp +38 -0
  46. package/nitrogen/generated/ios/EnnioCoreAutolinking.mm +35 -0
  47. package/nitrogen/generated/ios/EnnioCoreAutolinking.swift +16 -0
  48. package/nitrogen/generated/shared/c++/ExtendedElementInfo.hpp +118 -0
  49. package/nitrogen/generated/shared/c++/HybridEnnioSpec.cpp +44 -0
  50. package/nitrogen/generated/shared/c++/HybridEnnioSpec.hpp +93 -0
  51. package/nitrogen/generated/shared/c++/LayoutMetrics.hpp +103 -0
  52. package/nitrogen/generated/shared/c++/ScrollDirection.hpp +84 -0
  53. package/package.json +78 -0
  54. package/react-native.config.js +14 -0
  55. package/src/Ennio.nitro.ts +363 -0
  56. package/src/cli/hid-daemon.py +129 -0
  57. package/src/index.ts +72 -0
@@ -0,0 +1,264 @@
1
+ //
2
+ // EnnioRuntimeHelper.h
3
+ // Provides access to React Native runtime and UIManager for Ennio
4
+ //
5
+
6
+ #pragma once
7
+
8
+ #ifdef __OBJC__
9
+ @class RCTSurfacePresenter;
10
+ #endif
11
+
12
+ #ifdef __cplusplus
13
+
14
+ #include <memory>
15
+ #include <string>
16
+ #include <vector>
17
+
18
+ namespace facebook {
19
+ namespace react {
20
+ class UIManager;
21
+ class ShadowNode;
22
+ } // namespace react
23
+ } // namespace facebook
24
+
25
+ namespace ennio {
26
+
27
+ /**
28
+ * EnnioRuntimeHelper - Singleton for accessing React Native's UIManager
29
+ *
30
+ * This class bridges iOS's RCTSurfacePresenter to C++ code that needs
31
+ * access to the shadow tree.
32
+ */
33
+ class EnnioRuntimeHelper {
34
+ public:
35
+ static EnnioRuntimeHelper& getInstance();
36
+
37
+ /**
38
+ * Set the surface presenter (called from Objective-C during app startup)
39
+ */
40
+ void setSurfacePresenter(void* surfacePresenter);
41
+
42
+ /**
43
+ * Get the UIManager for shadow tree access
44
+ * Returns nullptr if not yet initialized
45
+ */
46
+ std::shared_ptr<facebook::react::UIManager> getUIManager();
47
+
48
+ /**
49
+ * Get the current shadow tree root for the default surface
50
+ */
51
+ std::shared_ptr<const facebook::react::ShadowNode> getShadowTreeRoot();
52
+
53
+ /**
54
+ * Check if the helper is properly initialized
55
+ */
56
+ bool isInitialized() const;
57
+
58
+ // ============================================
59
+ // Alert/Modal Handling
60
+ // ============================================
61
+
62
+ /**
63
+ * Check if an alert is currently presented
64
+ */
65
+ bool isAlertPresent();
66
+
67
+ /**
68
+ * Get the text content of the current alert (title + message)
69
+ */
70
+ std::string getAlertText();
71
+
72
+ /**
73
+ * Get the list of button titles in the current alert
74
+ */
75
+ std::vector<std::string> getAlertButtons();
76
+
77
+ // ============================================
78
+ // Fast-mode Writes
79
+ //
80
+ // Each method finds a UIView via accessibilityIdentifier (RN testID)
81
+ // and drives the action directly through UIKit / iOS accessibility
82
+ // APIs. No XCUI helper, no synthetic touches. Per-call latency is
83
+ // dominated by the view-tree DFS (~5ms on the example app); no
84
+ // cross-process IPC, no animation race.
85
+ // ============================================
86
+
87
+ bool tap(const std::string& testID);
88
+ bool tapByLabel(const std::string& text);
89
+ /**
90
+ * Tap at an absolute window-coordinate point (logical pt, not pixels).
91
+ * Synthesises a UITouch sequence so UIKit's hit-test routes the
92
+ * gesture through the responder chain. Used by tapBySelector when a
93
+ * shadow-tree match resolves to a layout instead of a UIView with a
94
+ * testID.
95
+ */
96
+ bool tapAtScreenPoint(double x, double y);
97
+
98
+ /**
99
+ * Prepare a tap by testID: stable-coord poll + auto-scroll
100
+ * fallback + UIMenu detection, all in a single JSI call.
101
+ * Replaces the CLI-side `layoutCenter` polling loop — saves
102
+ * ~5-10 CDP round trips per tap. Returns JSON object
103
+ * `{"x":..,"y":..,"isMenu":..}` on success, empty string on
104
+ * failure. CLI runs the actual tap via idb HID so RNGH-wrapped
105
+ * components (pressto's PressableScale, RNBetterTapGestureRecognizer)
106
+ * see real CoreSimulator touch events.
107
+ */
108
+ std::string prepareTap(const std::string& testID, double screenW, double screenH);
109
+
110
+ /**
111
+ * Synthesise a pan gesture from (x1,y1) to (x2,y2) over `durationMs`.
112
+ * Fast path when the start point hits a UIScrollView ancestor:
113
+ * setContentOffset with the delta, no UITouch tax. Otherwise drives
114
+ * a UITouchPhaseMoved loop along the line. Window-coords (logical pt).
115
+ * Sim-only.
116
+ */
117
+ bool swipeAtPoints(double x1, double y1, double x2, double y2, double durationMs);
118
+
119
+ /**
120
+ * Synthesise a hardware key press by HID keycode against the current
121
+ * first responder when it conforms to UIKeyInput. Mapped: 42=backspace,
122
+ * 40=return, 44=space. Returns false when no input field is focused.
123
+ */
124
+ bool pressHardwareKey(double keyCode);
125
+ /**
126
+ * Origin of the React surface inside the user app's window, in
127
+ * logical points. Adds to Fabric's surface-relative screenX/screenY
128
+ * to get window-relative coords idb can tap on.
129
+ */
130
+ std::pair<double, double> getSurfaceOffset();
131
+ /**
132
+ * Logical-point bounds of the key UIWindow. (width, height) — origin
133
+ * is always (0,0). Used by tap-time visibility gates: a view whose
134
+ * window-frame falls fully outside this rect is unreachable by a
135
+ * real finger and should fail the tap.
136
+ */
137
+ std::pair<double, double> getKeyWindowSize();
138
+ /**
139
+ * Returns true iff the testID-bearing view exists, has non-zero size,
140
+ * and overlaps the key window's visible bounds. Mirrors what a real
141
+ * finger could reach: an offscreen / not-yet-laid-out element returns
142
+ * false. Skips the gesture / responder tree — purely a layout check.
143
+ */
144
+ bool isViewOnscreen(const std::string& testID);
145
+ /**
146
+ * True iff the testID's UIView is in the iOS accessibility tree.
147
+ * The predicate: view is attached to a window AND no ancestor has
148
+ * accessibilityElementsHidden=YES. This is what XCUI uses to decide
149
+ * whether an element is enumerable. Used to filter shadow-tree
150
+ * selector matches that resolved to a stale UIView under an inactive
151
+ * tab / pushed stack frame / occluded modal host.
152
+ */
153
+ bool isInA11yTree(const std::string& testID);
154
+ /**
155
+ * Window-relative frame of the UIView with the given testID. Returns
156
+ * (x, y, w, h) all in logical points. Width/height = 0 when not
157
+ * found. This bypasses Fabric's surface-relative layout entirely:
158
+ * the value already accounts for ScrollView contentInsetAdjustment,
159
+ * safe-area padding, and any other UIKit-runtime offsets.
160
+ */
161
+ std::tuple<double, double, double, double> getViewWindowFrame(const std::string& testID);
162
+ /**
163
+ * Window-relative frame for the first UIView whose accessibilityLabel
164
+ * matches `text` (substring). Used for native widgets that don't show
165
+ * up in the Fabric tree — UITabBar items, alert buttons, system
166
+ * sheets, RNScreens stack headers.
167
+ */
168
+ std::tuple<double, double, double, double> getViewWindowFrameByLabel(const std::string& text);
169
+ /**
170
+ * True if the testID's UIView (or any ancestor) is a UIButton with
171
+ * `menu` set + `showsMenuAsPrimaryAction` (zeego DropdownMenu,
172
+ * react-native-ios-context-menu). Such triggers cannot be opened by
173
+ * UIControl.sendActions or programmatic _presentMenuAtLocation: in
174
+ * the host app — only by real HID input. Caller routes through idb.
175
+ */
176
+ bool isMenuTriggerAncestor(const std::string& testID);
177
+ /**
178
+ * Wipe the app's sandbox: Library/, Documents/, tmp/. Runs in-process
179
+ * via NSFileManager so it works identically on Simulator and on a
180
+ * real device — no host filesystem access required, no shell-out.
181
+ * Caller should restart the app afterwards to drop in-memory state
182
+ * (Zustand stores, RN HermesRuntime caches, etc.).
183
+ * Keychain is a separate iOS subsystem — see clearKeychain().
184
+ */
185
+ bool clearAppDataDirectories();
186
+ bool doubleTap(const std::string& testID);
187
+ bool longPress(const std::string& testID, int durationMs);
188
+ bool typeText(const std::string& testID, const std::string& text);
189
+ bool clearText(const std::string& testID);
190
+ bool eraseText(const std::string& testID, int count);
191
+ bool pressKey(const std::string& testID, const std::string& keyName);
192
+ bool scroll(const std::string& testID, const std::string& direction, double distance);
193
+ bool swipe(const std::string& testID, const std::string& direction, double distance);
194
+ bool scrollTo(const std::string& scrollViewTestID, const std::string& elementTestID);
195
+ bool tapTab(int index);
196
+ /**
197
+ * Find a UITabBarController in any window scene and select the tab
198
+ * whose viewController's title (or tabBarItem.title) matches `name`
199
+ * case-insensitively. Used when NativeTabs renders bar items via
200
+ * SwiftUI / UIKit hosts that don't surface their UIView subtree to
201
+ * accessibility-label walks. Returns false if no matching tab.
202
+ */
203
+ bool tapTabByName(const std::string& name);
204
+ /**
205
+ * One-shot tap-readiness query. Returns the window-coord frame for
206
+ * `testID` only after iOS is in a state where the next touch will
207
+ * actually deliver: no UIPresentationController / UINavigation
208
+ * transition is in flight, no alert-level UIWindow is sitting on
209
+ * top, the target's userInteractionEnabled chain is clean. Polls
210
+ * in-process every ~20 ms up to `maxWaitMs`; the JS / CLI side pays
211
+ * a single WS round-trip regardless of wait duration. Returns
212
+ * (0,0,0,0) on timeout. Library-agnostic — works for Pressable,
213
+ * TouchableOpacity, RNGH BaseButton, pressto, anything that hangs
214
+ * its handler off iOS's responder + recognizer pipeline.
215
+ */
216
+ std::tuple<double, double, double, double> getReadyCoord(const std::string& testID, int maxWaitMs);
217
+ /**
218
+ * Library-agnostic tap that bypasses iOS HID + gesture coordinator.
219
+ * Cascades through three direct-fire paths against the testID's
220
+ * UIView:
221
+ * 1. nearest enabled UIControl ancestor → sendActions touchUpInside
222
+ * (UIButton, UISwitch, RNGestureHandlerButton)
223
+ * 2. gestureRecognizers on view + ancestors driven by direct
224
+ * touchesBegan: / touchesEnded: calls (RCTSurfaceTouchHandler →
225
+ * RN responder release → Pressable/Touchable onPress;
226
+ * RNNativeViewGestureRecognizer → RNGH onActivated → user onPress)
227
+ * 3. accessibilityActivate (a11y-tagged buttons)
228
+ * Returns true if any path fired. Caller falls back to idb HID on
229
+ * false (covers pure-native UIAlertController buttons et al).
230
+ */
231
+ bool fireTapByTestID(const std::string& testID);
232
+ bool backGesture();
233
+ bool hideKeyboard();
234
+ bool tapAlertButton(const std::string& buttonText);
235
+ bool dismissAlert();
236
+ bool copyToClipboard(const std::string& text);
237
+ bool pasteFromClipboard(const std::string& testID);
238
+ std::string getClipboardText();
239
+
240
+ private:
241
+ EnnioRuntimeHelper() = default;
242
+ ~EnnioRuntimeHelper() = default;
243
+ EnnioRuntimeHelper(const EnnioRuntimeHelper&) = delete;
244
+ EnnioRuntimeHelper& operator=(const EnnioRuntimeHelper&) = delete;
245
+
246
+ void* surfacePresenter_ = nullptr;
247
+ };
248
+
249
+ } // namespace ennio
250
+
251
+ #endif // __cplusplus
252
+
253
+ // C function for Objective-C code to call
254
+ #ifdef __cplusplus
255
+ extern "C" {
256
+ #endif
257
+
258
+ #ifdef __OBJC__
259
+ void EnnioSetSurfacePresenter(RCTSurfacePresenter* _Nonnull presenter);
260
+ #endif
261
+
262
+ #ifdef __cplusplus
263
+ }
264
+ #endif