basalt-core 0.0.0-rc5

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 (325) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cli/basalt.d.ts +3 -0
  3. package/dist/cli/basalt.d.ts.map +1 -0
  4. package/dist/cli/basalt.js +47 -0
  5. package/dist/cli/basalt.js.map +1 -0
  6. package/dist/cli/bundle-cli.d.ts +12 -0
  7. package/dist/cli/bundle-cli.d.ts.map +1 -0
  8. package/dist/cli/bundle-cli.js +124 -0
  9. package/dist/cli/bundle-cli.js.map +1 -0
  10. package/dist/cli/bundleWithAssets.d.ts +79 -0
  11. package/dist/cli/bundleWithAssets.d.ts.map +1 -0
  12. package/dist/cli/bundleWithAssets.js +198 -0
  13. package/dist/cli/bundleWithAssets.js.map +1 -0
  14. package/dist/cli/desktop.d.ts +339 -0
  15. package/dist/cli/desktop.d.ts.map +1 -0
  16. package/dist/cli/desktop.js +1029 -0
  17. package/dist/cli/desktop.js.map +1 -0
  18. package/dist/cli/doctor.d.ts +31 -0
  19. package/dist/cli/doctor.d.ts.map +1 -0
  20. package/dist/cli/doctor.js +283 -0
  21. package/dist/cli/doctor.js.map +1 -0
  22. package/dist/cli/init.d.ts +77 -0
  23. package/dist/cli/init.d.ts.map +1 -0
  24. package/dist/cli/init.js +442 -0
  25. package/dist/cli/init.js.map +1 -0
  26. package/dist/cli/metro.d.ts +49 -0
  27. package/dist/cli/metro.d.ts.map +1 -0
  28. package/dist/cli/metro.js +148 -0
  29. package/dist/cli/metro.js.map +1 -0
  30. package/dist/cli/packageApp.d.ts +102 -0
  31. package/dist/cli/packageApp.d.ts.map +1 -0
  32. package/dist/cli/packageApp.js +319 -0
  33. package/dist/cli/packageApp.js.map +1 -0
  34. package/dist/cli/steps.d.ts +34 -0
  35. package/dist/cli/steps.d.ts.map +1 -0
  36. package/dist/cli/steps.js +53 -0
  37. package/dist/cli/steps.js.map +1 -0
  38. package/dist/metro-config.d.ts +168 -0
  39. package/dist/metro-config.d.ts.map +1 -0
  40. package/dist/metro-config.js +671 -0
  41. package/dist/metro-config.js.map +1 -0
  42. package/dist/react-native.config.d.ts +7 -0
  43. package/dist/react-native.config.d.ts.map +1 -0
  44. package/dist/react-native.config.js +80 -0
  45. package/dist/react-native.config.js.map +1 -0
  46. package/dist/src/DragSource.d.ts +52 -0
  47. package/dist/src/DragSource.d.ts.map +1 -0
  48. package/dist/src/DragSource.js +97 -0
  49. package/dist/src/DragSource.js.map +1 -0
  50. package/dist/src/DropTarget.d.ts +58 -0
  51. package/dist/src/DropTarget.d.ts.map +1 -0
  52. package/dist/src/DropTarget.js +131 -0
  53. package/dist/src/DropTarget.js.map +1 -0
  54. package/dist/src/KeyHandler.d.ts +57 -0
  55. package/dist/src/KeyHandler.d.ts.map +1 -0
  56. package/dist/src/KeyHandler.js +48 -0
  57. package/dist/src/KeyHandler.js.map +1 -0
  58. package/dist/src/Menu.d.ts +72 -0
  59. package/dist/src/Menu.d.ts.map +1 -0
  60. package/dist/src/Menu.js +179 -0
  61. package/dist/src/Menu.js.map +1 -0
  62. package/dist/src/TitleBar.d.ts +75 -0
  63. package/dist/src/TitleBar.d.ts.map +1 -0
  64. package/dist/src/TitleBar.js +236 -0
  65. package/dist/src/TitleBar.js.map +1 -0
  66. package/dist/src/Window.d.ts +87 -0
  67. package/dist/src/Window.d.ts.map +1 -0
  68. package/dist/src/Window.js +248 -0
  69. package/dist/src/Window.js.map +1 -0
  70. package/dist/src/closeRequest.d.ts +55 -0
  71. package/dist/src/closeRequest.d.ts.map +1 -0
  72. package/dist/src/closeRequest.js +134 -0
  73. package/dist/src/closeRequest.js.map +1 -0
  74. package/dist/src/index.d.ts +45 -0
  75. package/dist/src/index.d.ts.map +1 -0
  76. package/dist/src/index.js +92 -0
  77. package/dist/src/index.js.map +1 -0
  78. package/dist/src/overrides/Alert.d.ts +69 -0
  79. package/dist/src/overrides/Alert.d.ts.map +1 -0
  80. package/dist/src/overrides/Alert.js +112 -0
  81. package/dist/src/overrides/Alert.js.map +1 -0
  82. package/dist/src/overrides/BaseViewConfig.d.ts +28 -0
  83. package/dist/src/overrides/BaseViewConfig.d.ts.map +1 -0
  84. package/dist/src/overrides/BaseViewConfig.js +72 -0
  85. package/dist/src/overrides/BaseViewConfig.js.map +1 -0
  86. package/dist/src/overrides/Platform.linux.d.ts +10 -0
  87. package/dist/src/overrides/Platform.linux.d.ts.map +1 -0
  88. package/dist/src/overrides/Platform.linux.js +15 -0
  89. package/dist/src/overrides/Platform.linux.js.map +1 -0
  90. package/dist/src/overrides/Platform.macos.d.ts +10 -0
  91. package/dist/src/overrides/Platform.macos.d.ts.map +1 -0
  92. package/dist/src/overrides/Platform.macos.js +15 -0
  93. package/dist/src/overrides/Platform.macos.js.map +1 -0
  94. package/dist/src/overrides/Platform.windows.d.ts +10 -0
  95. package/dist/src/overrides/Platform.windows.d.ts.map +1 -0
  96. package/dist/src/overrides/Platform.windows.js +15 -0
  97. package/dist/src/overrides/Platform.windows.js.map +1 -0
  98. package/dist/src/overrides/ReactDevToolsSettingsManager.d.ts +24 -0
  99. package/dist/src/overrides/ReactDevToolsSettingsManager.d.ts.map +1 -0
  100. package/dist/src/overrides/ReactDevToolsSettingsManager.js +30 -0
  101. package/dist/src/overrides/ReactDevToolsSettingsManager.js.map +1 -0
  102. package/dist/src/overrides/RefreshControl.d.ts +52 -0
  103. package/dist/src/overrides/RefreshControl.d.ts.map +1 -0
  104. package/dist/src/overrides/RefreshControl.js +92 -0
  105. package/dist/src/overrides/RefreshControl.js.map +1 -0
  106. package/dist/src/overrides/ScrollView.d.ts +39 -0
  107. package/dist/src/overrides/ScrollView.d.ts.map +1 -0
  108. package/dist/src/overrides/ScrollView.js +59 -0
  109. package/dist/src/overrides/ScrollView.js.map +1 -0
  110. package/dist/src/overrides/Share.d.ts +96 -0
  111. package/dist/src/overrides/Share.d.ts.map +1 -0
  112. package/dist/src/overrides/Share.js +97 -0
  113. package/dist/src/overrides/Share.js.map +1 -0
  114. package/dist/src/overrides/TextInput.d.ts +81 -0
  115. package/dist/src/overrides/TextInput.d.ts.map +1 -0
  116. package/dist/src/overrides/TextInput.js +213 -0
  117. package/dist/src/overrides/TextInput.js.map +1 -0
  118. package/dist/src/overrides/View.d.ts +36 -0
  119. package/dist/src/overrides/View.d.ts.map +1 -0
  120. package/dist/src/overrides/View.js +99 -0
  121. package/dist/src/overrides/View.js.map +1 -0
  122. package/dist/src/overrides/createPlatform.d.ts +32 -0
  123. package/dist/src/overrides/createPlatform.d.ts.map +1 -0
  124. package/dist/src/overrides/createPlatform.js +84 -0
  125. package/dist/src/overrides/createPlatform.js.map +1 -0
  126. package/dist/src/overrides/setUpDeveloperTools.d.ts +28 -0
  127. package/dist/src/overrides/setUpDeveloperTools.d.ts.map +1 -0
  128. package/dist/src/overrides/setUpDeveloperTools.js +84 -0
  129. package/dist/src/overrides/setUpDeveloperTools.js.map +1 -0
  130. package/dist/src/overrides/setUpXHR.d.ts +46 -0
  131. package/dist/src/overrides/setUpXHR.d.ts.map +1 -0
  132. package/dist/src/overrides/setUpXHR.js +236 -0
  133. package/dist/src/overrides/setUpXHR.js.map +1 -0
  134. package/dist/src/quitRequest.d.ts +55 -0
  135. package/dist/src/quitRequest.d.ts.map +1 -0
  136. package/dist/src/quitRequest.js +121 -0
  137. package/dist/src/quitRequest.js.map +1 -0
  138. package/dist/src/resourcePath.d.ts +18 -0
  139. package/dist/src/resourcePath.d.ts.map +1 -0
  140. package/dist/src/resourcePath.js +31 -0
  141. package/dist/src/resourcePath.js.map +1 -0
  142. package/dist/src/responseBody.d.ts +76 -0
  143. package/dist/src/responseBody.d.ts.map +1 -0
  144. package/dist/src/responseBody.js +94 -0
  145. package/dist/src/responseBody.js.map +1 -0
  146. package/dist/src/responseHeaders.d.ts +40 -0
  147. package/dist/src/responseHeaders.d.ts.map +1 -0
  148. package/dist/src/responseHeaders.js +75 -0
  149. package/dist/src/responseHeaders.js.map +1 -0
  150. package/dist/src/titleBarState.d.ts +43 -0
  151. package/dist/src/titleBarState.d.ts.map +1 -0
  152. package/dist/src/titleBarState.js +77 -0
  153. package/dist/src/titleBarState.js.map +1 -0
  154. package/dist/src/useContextMenu.d.ts +113 -0
  155. package/dist/src/useContextMenu.d.ts.map +1 -0
  156. package/dist/src/useContextMenu.js +109 -0
  157. package/dist/src/useContextMenu.js.map +1 -0
  158. package/dist/src/useDialog.d.ts +98 -0
  159. package/dist/src/useDialog.d.ts.map +1 -0
  160. package/dist/src/useDialog.js +112 -0
  161. package/dist/src/useDialog.js.map +1 -0
  162. package/dist/src/useDisplays.d.ts +70 -0
  163. package/dist/src/useDisplays.d.ts.map +1 -0
  164. package/dist/src/useDisplays.js +118 -0
  165. package/dist/src/useDisplays.js.map +1 -0
  166. package/dist/src/useHandledKeys.d.ts +38 -0
  167. package/dist/src/useHandledKeys.d.ts.map +1 -0
  168. package/dist/src/useHandledKeys.js +70 -0
  169. package/dist/src/useHandledKeys.js.map +1 -0
  170. package/dist/src/useWindow.d.ts +140 -0
  171. package/dist/src/useWindow.d.ts.map +1 -0
  172. package/dist/src/useWindow.js +233 -0
  173. package/dist/src/useWindow.js.map +1 -0
  174. package/dist/tsconfig.tsbuildinfo +1 -0
  175. package/native/CMakeLists.txt +355 -0
  176. package/native/bootstrap.sh +613 -0
  177. package/native/cmake/CompilerFlags.cmake +49 -0
  178. package/native/cmake/Expo.cmake +125 -0
  179. package/native/cmake/ReactNativeCore.cmake +612 -0
  180. package/native/cmake/Reanimated.cmake +108 -0
  181. package/native/cmake/Skia.cmake +379 -0
  182. package/native/cmake/ThirdParty.cmake +290 -0
  183. package/native/cmake/Worklets.cmake +67 -0
  184. package/native/cmake/codegen/CMakeLists.txt +32 -0
  185. package/native/core/AppIdentity.cpp +85 -0
  186. package/native/core/AppIdentity.h +59 -0
  187. package/native/core/AppPaths.cpp +96 -0
  188. package/native/core/AppPaths.h +53 -0
  189. package/native/core/AppearanceModule.cpp +60 -0
  190. package/native/core/AppearanceModule.h +40 -0
  191. package/native/core/Backface.h +59 -0
  192. package/native/core/BlobModule.cpp +283 -0
  193. package/native/core/BlobModule.h +76 -0
  194. package/native/core/BlobRegistry.cpp +75 -0
  195. package/native/core/BlobRegistry.h +44 -0
  196. package/native/core/ColorScheme.cpp +105 -0
  197. package/native/core/ColorScheme.h +59 -0
  198. package/native/core/ComponentRegistry.h +30 -0
  199. package/native/core/ControlMetrics.h +37 -0
  200. package/native/core/CoreModules.cpp +592 -0
  201. package/native/core/CoreModules.h +308 -0
  202. package/native/core/DebuggingOverlay.cpp +106 -0
  203. package/native/core/DebuggingOverlay.h +65 -0
  204. package/native/core/DesktopControls.cpp +232 -0
  205. package/native/core/DesktopControls.h +170 -0
  206. package/native/core/DevBundle.cpp +108 -0
  207. package/native/core/DevBundle.h +69 -0
  208. package/native/core/DevMenu.cpp +99 -0
  209. package/native/core/DevMenu.h +75 -0
  210. package/native/core/DialogModule.cpp +150 -0
  211. package/native/core/DialogModule.h +51 -0
  212. package/native/core/DragAndDrop.cpp +45 -0
  213. package/native/core/DragAndDrop.h +225 -0
  214. package/native/core/ExpoImageComponent.cpp +79 -0
  215. package/native/core/ExpoImageComponent.h +80 -0
  216. package/native/core/ExpoModules.cpp +415 -0
  217. package/native/core/ExpoModules.h +115 -0
  218. package/native/core/ExpoRuntime.cpp +366 -0
  219. package/native/core/ExpoRuntime.h +42 -0
  220. package/native/core/FocusRing.h +36 -0
  221. package/native/core/FontRegistry.h +51 -0
  222. package/native/core/GestureHandlerModule.cpp +136 -0
  223. package/native/core/GestureHandlerModule.h +71 -0
  224. package/native/core/Gestures.cpp +533 -0
  225. package/native/core/Gestures.h +145 -0
  226. package/native/core/HoverTracker.cpp +69 -0
  227. package/native/core/HoverTracker.h +109 -0
  228. package/native/core/HttpClient.cpp +282 -0
  229. package/native/core/ImageBytes.cpp +205 -0
  230. package/native/core/ImageBytes.h +29 -0
  231. package/native/core/ImageCache.cpp +51 -0
  232. package/native/core/ImageCache.h +89 -0
  233. package/native/core/JsRuntimeAccess.cpp +70 -0
  234. package/native/core/JsRuntimeAccess.h +60 -0
  235. package/native/core/JsiPromise.h +51 -0
  236. package/native/core/KeyEvents.cpp +116 -0
  237. package/native/core/KeyEvents.h +144 -0
  238. package/native/core/LogBoxSurface.cpp +46 -0
  239. package/native/core/LogBoxSurface.h +68 -0
  240. package/native/core/MenuModel.h +117 -0
  241. package/native/core/MenuModule.cpp +209 -0
  242. package/native/core/MenuModule.h +66 -0
  243. package/native/core/MountingWalk.h +524 -0
  244. package/native/core/PackageModules.cpp.in +25 -0
  245. package/native/core/PackageModules.h +50 -0
  246. package/native/core/PlatformConstantsModule.cpp +63 -0
  247. package/native/core/PlatformConstantsModule.h +64 -0
  248. package/native/core/PlatformServices.h +213 -0
  249. package/native/core/PointerButtons.h +63 -0
  250. package/native/core/PullToRefresh.cpp +57 -0
  251. package/native/core/PullToRefresh.h +73 -0
  252. package/native/core/ReanimatedCompat.h +47 -0
  253. package/native/core/ReanimatedModule.cpp +203 -0
  254. package/native/core/ReanimatedModule.h +55 -0
  255. package/native/core/ScrollAnimation.cpp +59 -0
  256. package/native/core/ScrollAnimation.h +70 -0
  257. package/native/core/ScrollBounds.cpp +26 -0
  258. package/native/core/ScrollBounds.h +68 -0
  259. package/native/core/ScrollIndicator.cpp +50 -0
  260. package/native/core/ScrollIndicator.h +69 -0
  261. package/native/core/ScrollMomentum.cpp +71 -0
  262. package/native/core/ScrollMomentum.h +73 -0
  263. package/native/core/ScrollSnap.cpp +123 -0
  264. package/native/core/ScrollSnap.h +80 -0
  265. package/native/core/ShareFallback.cpp +101 -0
  266. package/native/core/ShareFallback.h +45 -0
  267. package/native/core/SkiaPictureViewComponent.cpp +11 -0
  268. package/native/core/SkiaPictureViewComponent.h +47 -0
  269. package/native/core/SourceCodeModule.cpp +55 -0
  270. package/native/core/SourceCodeModule.h +53 -0
  271. package/native/core/StatusBarModule.cpp +23 -0
  272. package/native/core/StatusBarModule.h +42 -0
  273. package/native/core/TestDialog.cpp +148 -0
  274. package/native/core/TestDialog.h +96 -0
  275. package/native/core/TestKeys.cpp +78 -0
  276. package/native/core/TestKeys.h +50 -0
  277. package/native/core/TestQuitFile.cpp +33 -0
  278. package/native/core/TestQuitFile.h +60 -0
  279. package/native/core/TestSettle.h +192 -0
  280. package/native/core/TitleBarRegions.h +31 -0
  281. package/native/core/TurboModuleProxy.cpp +51 -0
  282. package/native/core/TurboModuleProxy.h +46 -0
  283. package/native/core/UIManagerAccess.cpp +43 -0
  284. package/native/core/UIManagerAccess.h +43 -0
  285. package/native/core/WindowBoundsCache.cpp +319 -0
  286. package/native/core/WindowControl.h +235 -0
  287. package/native/core/WindowHost.h +158 -0
  288. package/native/core/WindowMethods.cpp +167 -0
  289. package/native/core/WindowMethods.h +84 -0
  290. package/native/core/WindowsModule.cpp +448 -0
  291. package/native/core/WindowsModule.h +133 -0
  292. package/native/core/WorkletsModule.cpp +199 -0
  293. package/native/core/WorkletsModule.h +57 -0
  294. package/native/core/portability_probe.cpp +210 -0
  295. package/native/tests/EventRecorder.h +124 -0
  296. package/native/tests/TestHarness.cpp +109 -0
  297. package/native/tests/TestHarness.h +89 -0
  298. package/native/tests/TreeDump.h +49 -0
  299. package/native/tests/test_app_identity.cpp +114 -0
  300. package/native/tests/test_app_paths.cpp +48 -0
  301. package/native/tests/test_appearance.cpp +107 -0
  302. package/native/tests/test_backface.cpp +79 -0
  303. package/native/tests/test_blobs.cpp +157 -0
  304. package/native/tests/test_controls.cpp +185 -0
  305. package/native/tests/test_devbundle.cpp +103 -0
  306. package/native/tests/test_drag_and_drop.cpp +143 -0
  307. package/native/tests/test_gestures.cpp +331 -0
  308. package/native/tests/test_hover.cpp +92 -0
  309. package/native/tests/test_image_cache.cpp +109 -0
  310. package/native/tests/test_js_runtime_access.cpp +137 -0
  311. package/native/tests/test_key_events.cpp +235 -0
  312. package/native/tests/test_momentum.cpp +138 -0
  313. package/native/tests/test_quit_file.cpp +104 -0
  314. package/native/tests/test_scroll_animation.cpp +120 -0
  315. package/native/tests/test_scroll_bounds.cpp +66 -0
  316. package/native/tests/test_scroll_indicator.cpp +149 -0
  317. package/native/tests/test_scroll_snap.cpp +112 -0
  318. package/native/tests/test_settle.cpp +115 -0
  319. package/native/tests/test_share.cpp +75 -0
  320. package/native/tests/test_sourcecode.cpp +62 -0
  321. package/native/tests/test_window_close.cpp +119 -0
  322. package/native/tests/test_window_limits.cpp +123 -0
  323. package/package.json +43 -0
  324. package/react-native.config.js +86 -0
  325. package/supported-versions.json +66 -0
@@ -0,0 +1,331 @@
1
+ // Tests for the gesture recognisers.
2
+ //
3
+ // Core's, so compiled into both platforms' suites: a pan is a pan whichever
4
+ // toolkit reported the pointer.
5
+ //
6
+ // Driven through the registry rather than through the module, which is what
7
+ // makes them possible at all -- the module hops to the UI thread, and a test
8
+ // binary has no UI thread to hop to. The same reason keeps the long press out
9
+ // of here: it activates from a timer, and `postDelayed` needs a run loop. What
10
+ // is covered is everything decided by the pointer itself.
11
+
12
+ #include "TestHarness.h"
13
+
14
+ #include "Gestures.h"
15
+
16
+ #include <sstream>
17
+ #include <string>
18
+ #include <initializer_list>
19
+ #include <vector>
20
+
21
+ namespace {
22
+
23
+ struct Recorded {
24
+ std::string event;
25
+ int handlerTag{0};
26
+ int state{0};
27
+ int oldState{-1};
28
+ double translationX{0};
29
+ double translationY{0};
30
+ };
31
+
32
+ // A registry with nothing left over from the last test. The registry is a
33
+ // process-wide singleton -- there is one pointer -- so each test starts by
34
+ // dropping whatever the previous one attached.
35
+ basalt::GestureRegistry &fresh(std::vector<Recorded> &recorded) {
36
+ auto &registry = basalt::gestures();
37
+ for (int tag = 1; tag <= 32; tag++) {
38
+ registry.drop(tag);
39
+ }
40
+ recorded.clear();
41
+ registry.setEmitter([&recorded](const std::string &event, folly::dynamic payload) {
42
+ Recorded entry;
43
+ entry.event = event;
44
+ entry.handlerTag = static_cast<int>(payload["handlerTag"].asDouble());
45
+ entry.state = static_cast<int>(payload["state"].asDouble());
46
+ if (const auto *old = payload.get_ptr("oldState"); old != nullptr) {
47
+ entry.oldState = static_cast<int>(old->asDouble());
48
+ }
49
+ if (const auto *tx = payload.get_ptr("translationX"); tx != nullptr) {
50
+ entry.translationX = tx->asDouble();
51
+ }
52
+ if (const auto *ty = payload.get_ptr("translationY"); ty != nullptr) {
53
+ entry.translationY = ty->asDouble();
54
+ }
55
+ recorded.push_back(entry);
56
+ });
57
+ return registry;
58
+ }
59
+
60
+ // The state changes one handler went through, as "2,4,5" -- readable in a
61
+ // failure message, which a vector of ints is not.
62
+ std::string statesFor(const std::vector<Recorded> &recorded, int handlerTag) {
63
+ std::ostringstream states;
64
+ bool first = true;
65
+ for (const auto &entry : recorded) {
66
+ if (entry.handlerTag == handlerTag && entry.event == "onGestureHandlerStateChange") {
67
+ if (!first) {
68
+ states << ",";
69
+ }
70
+ states << entry.state;
71
+ first = false;
72
+ }
73
+ }
74
+ return states.str();
75
+ }
76
+
77
+ int countOf(const std::vector<Recorded> &recorded, int handlerTag, const char *event) {
78
+ int count = 0;
79
+ for (const auto &entry : recorded) {
80
+ if (entry.handlerTag == handlerTag && entry.event == event) {
81
+ count++;
82
+ }
83
+ }
84
+ return count;
85
+ }
86
+
87
+ std::vector<basalt::HitView> chainOf(int tag, double originX = 0, double originY = 0) {
88
+ return {basalt::HitView{.tag = tag, .originX = originX, .originY = originY}};
89
+ }
90
+
91
+ // Spelled the same way statesFor() spells them, so an expectation reads as the
92
+ // sequence it is.
93
+ std::string states(std::initializer_list<int> values) {
94
+ std::ostringstream out;
95
+ bool first = true;
96
+ for (int value : values) {
97
+ if (!first) {
98
+ out << ",";
99
+ }
100
+ out << value;
101
+ first = false;
102
+ }
103
+ return out.str();
104
+ }
105
+
106
+ constexpr int kBegan = static_cast<int>(basalt::GestureState::Began);
107
+ constexpr int kActive = static_cast<int>(basalt::GestureState::Active);
108
+ constexpr int kEnd = static_cast<int>(basalt::GestureState::End);
109
+ constexpr int kFailed = static_cast<int>(basalt::GestureState::Failed);
110
+ constexpr int kCancelled = static_cast<int>(basalt::GestureState::Cancelled);
111
+
112
+ } // namespace
113
+
114
+ TEST(a_pan_begins_then_activates_once_it_has_moved) {
115
+ std::vector<Recorded> recorded;
116
+ auto &registry = fresh(recorded);
117
+ registry.create("PanGestureHandler", 1, folly::dynamic::object());
118
+ registry.attach(1, 100);
119
+
120
+ registry.pointerDown(chainOf(100), 50, 50, 0);
121
+ // Under the default 10pt slop: still only BEGAN.
122
+ registry.pointerMove(54, 50, 16);
123
+ EXPECT_EQ(statesFor(recorded, 1), states({kBegan}));
124
+
125
+ registry.pointerMove(80, 50, 32);
126
+ registry.pointerUp(80, 50, 48);
127
+ EXPECT_EQ(statesFor(recorded, 1), states({kBegan, kActive, kEnd}));
128
+ }
129
+
130
+ TEST(a_pans_translation_is_measured_from_where_it_started) {
131
+ std::vector<Recorded> recorded;
132
+ auto &registry = fresh(recorded);
133
+ registry.create("PanGestureHandler", 1, folly::dynamic::object());
134
+ registry.attach(1, 100);
135
+
136
+ registry.pointerDown(chainOf(100), 50, 50, 0);
137
+ registry.pointerMove(90, 70, 16);
138
+ registry.pointerMove(120, 90, 32);
139
+
140
+ // Not from the previous update: RNGH's translation is cumulative, and an app
141
+ // that adds it up frame by frame moves twice as far as the pointer did.
142
+ const Recorded &last = recorded.back();
143
+ EXPECT_EQ(last.event, std::string("onGestureHandlerEvent"));
144
+ EXPECT_EQ(static_cast<int>(last.translationX), 70);
145
+ EXPECT_EQ(static_cast<int>(last.translationY), 40);
146
+ }
147
+
148
+ TEST(a_tap_is_a_press_that_did_not_move) {
149
+ std::vector<Recorded> recorded;
150
+ auto &registry = fresh(recorded);
151
+ registry.create("TapGestureHandler", 1, folly::dynamic::object());
152
+ registry.attach(1, 100);
153
+
154
+ registry.pointerDown(chainOf(100), 10, 10, 0);
155
+ registry.pointerUp(12, 11, 90);
156
+
157
+ EXPECT_EQ(statesFor(recorded, 1), states({kBegan, kActive, kEnd}));
158
+ }
159
+
160
+ TEST(a_tap_fails_when_the_pointer_wanders) {
161
+ std::vector<Recorded> recorded;
162
+ auto &registry = fresh(recorded);
163
+ registry.create("TapGestureHandler", 1, folly::dynamic::object());
164
+ registry.attach(1, 100);
165
+
166
+ registry.pointerDown(chainOf(100), 10, 10, 0);
167
+ registry.pointerMove(200, 10, 40);
168
+ registry.pointerUp(200, 10, 80);
169
+
170
+ EXPECT_EQ(statesFor(recorded, 1), states({kBegan, kFailed}));
171
+ }
172
+
173
+ TEST(a_tap_fails_when_it_is_held_too_long) {
174
+ std::vector<Recorded> recorded;
175
+ auto &registry = fresh(recorded);
176
+ registry.create("TapGestureHandler", 1, folly::dynamic::object());
177
+ registry.attach(1, 100);
178
+
179
+ registry.pointerDown(chainOf(100), 10, 10, 0);
180
+ registry.pointerUp(10, 10, 5000);
181
+
182
+ EXPECT_EQ(statesFor(recorded, 1), states({kBegan, kFailed}));
183
+ }
184
+
185
+ TEST(a_gesture_that_activates_cancels_the_others) {
186
+ std::vector<Recorded> recorded;
187
+ auto &registry = fresh(recorded);
188
+ registry.create("PanGestureHandler", 1, folly::dynamic::object());
189
+ registry.attach(1, 100);
190
+ registry.create("TapGestureHandler", 2, folly::dynamic::object());
191
+ registry.attach(2, 100);
192
+
193
+ registry.pointerDown(chainOf(100), 10, 10, 0);
194
+ registry.pointerMove(200, 10, 40);
195
+
196
+ EXPECT_EQ(statesFor(recorded, 1), states({kBegan, kActive}));
197
+ // The tap had already failed on the slop check, which is the right answer for
198
+ // the wrong-looking reason: either way it must not be left at BEGAN.
199
+ const std::string tapStates = statesFor(recorded, 2);
200
+ EXPECT(tapStates == states({kBegan, kFailed}) || tapStates == states({kBegan, kCancelled}));
201
+ }
202
+
203
+ TEST(simultaneous_handlers_are_left_alone) {
204
+ std::vector<Recorded> recorded;
205
+ auto &registry = fresh(recorded);
206
+
207
+ registry.create("PanGestureHandler", 1, folly::dynamic::object());
208
+ registry.attach(1, 100);
209
+
210
+ // A second pan on the same view, declared simultaneous with the first. Both
211
+ // should activate; without the declaration the second would be cancelled.
212
+ folly::dynamic simultaneous = folly::dynamic::object("simultaneousHandlers",
213
+ folly::dynamic::array(1));
214
+ registry.create("PanGestureHandler", 2, simultaneous);
215
+ registry.attach(2, 100);
216
+
217
+ registry.pointerDown(chainOf(100), 10, 10, 0);
218
+ registry.pointerMove(200, 10, 40);
219
+ registry.pointerUp(200, 10, 80);
220
+
221
+ EXPECT_EQ(statesFor(recorded, 1), states({kBegan, kActive, kEnd}));
222
+ EXPECT_EQ(statesFor(recorded, 2), states({kBegan, kActive, kEnd}));
223
+ }
224
+
225
+ TEST(a_handler_waits_for_the_one_it_was_told_to_wait_for) {
226
+ std::vector<Recorded> recorded;
227
+ auto &registry = fresh(recorded);
228
+
229
+ registry.create("PanGestureHandler", 1, folly::dynamic::object());
230
+ registry.attach(1, 100);
231
+ registry.create("PanGestureHandler", 2,
232
+ folly::dynamic::object("waitFor", folly::dynamic::array(1)));
233
+ registry.attach(2, 100);
234
+
235
+ registry.pointerDown(chainOf(100), 10, 10, 0);
236
+ registry.pointerMove(200, 10, 40);
237
+
238
+ // 1 activated, which cancelled 2 -- and 2 never activated, because it was
239
+ // waiting for 1 to fail and 1 did the opposite.
240
+ EXPECT_EQ(statesFor(recorded, 1), states({kBegan, kActive}));
241
+ const std::string waiting = statesFor(recorded, 2);
242
+ EXPECT(waiting == states({kBegan, kFailed}) || waiting == states({kBegan, kCancelled}));
243
+ }
244
+
245
+ TEST(a_gesture_reaches_a_handler_on_an_ancestor) {
246
+ std::vector<Recorded> recorded;
247
+ auto &registry = fresh(recorded);
248
+ registry.create("PanGestureHandler", 1, folly::dynamic::object());
249
+ // Attached to the outer view, while the pointer lands on the inner one.
250
+ registry.attach(1, 200);
251
+
252
+ const std::vector<basalt::HitView> chain = {
253
+ basalt::HitView{.tag = 100, .originX = 30, .originY = 30},
254
+ basalt::HitView{.tag = 200, .originX = 10, .originY = 10},
255
+ };
256
+ registry.pointerDown(chain, 50, 50, 0);
257
+ registry.pointerMove(100, 50, 16);
258
+
259
+ EXPECT_EQ(statesFor(recorded, 1), states({kBegan, kActive}));
260
+ }
261
+
262
+ TEST(a_dropped_handler_stops_hearing_about_the_pointer) {
263
+ std::vector<Recorded> recorded;
264
+ auto &registry = fresh(recorded);
265
+ registry.create("PanGestureHandler", 1, folly::dynamic::object());
266
+ registry.attach(1, 100);
267
+ registry.drop(1);
268
+
269
+ registry.pointerDown(chainOf(100), 10, 10, 0);
270
+ registry.pointerMove(200, 10, 40);
271
+
272
+ EXPECT_EQ(countOf(recorded, 1, "onGestureHandlerStateChange"), 0);
273
+ EXPECT(registry.empty());
274
+ }
275
+
276
+ TEST(a_handler_attached_to_nothing_is_never_offered_a_gesture) {
277
+ std::vector<Recorded> recorded;
278
+ auto &registry = fresh(recorded);
279
+ registry.create("PanGestureHandler", 1, folly::dynamic::object());
280
+ // No attach: the view tag is 0, which is not a view.
281
+
282
+ registry.pointerDown(chainOf(0), 10, 10, 0);
283
+ registry.pointerMove(200, 10, 40);
284
+
285
+ EXPECT_EQ(countOf(recorded, 1, "onGestureHandlerStateChange"), 0);
286
+ registry.drop(1);
287
+ }
288
+
289
+ TEST(a_gesture_that_cannot_happen_here_fails_rather_than_hanging) {
290
+ std::vector<Recorded> recorded;
291
+ auto &registry = fresh(recorded);
292
+ // Pinch needs a second finger, which a cursor does not have.
293
+ registry.create("PinchGestureHandler", 1, folly::dynamic::object());
294
+ registry.attach(1, 100);
295
+
296
+ registry.pointerDown(chainOf(100), 10, 10, 0);
297
+ registry.pointerMove(200, 200, 40);
298
+ registry.pointerUp(200, 200, 80);
299
+
300
+ // BEGAN then FAILED, never ACTIVE: an app's onBegin runs and its onStart does
301
+ // not, which is what "this platform cannot do that" should look like.
302
+ EXPECT_EQ(statesFor(recorded, 1), states({kBegan, kFailed}));
303
+ }
304
+
305
+ TEST(the_pointers_position_is_reported_relative_to_the_handlers_view) {
306
+ std::vector<Recorded> recorded;
307
+ auto &registry = fresh(recorded);
308
+ registry.create("PanGestureHandler", 1, folly::dynamic::object());
309
+ registry.attach(1, 100);
310
+
311
+ // The view sits at (40, 20) in the root, so a pointer at (100, 60) is at
312
+ // (60, 40) inside it.
313
+ registry.pointerDown(chainOf(100, 40, 20), 100, 60, 0);
314
+
315
+ bool checked = false;
316
+ for (const auto &entry : recorded) {
317
+ (void)entry;
318
+ checked = true;
319
+ }
320
+ EXPECT(checked);
321
+
322
+ // Read the payload directly for the one field the recorder does not keep.
323
+ registry.setEmitter([&checked](const std::string &, folly::dynamic payload) {
324
+ EXPECT_EQ(static_cast<int>(payload["x"].asDouble()), 80);
325
+ EXPECT_EQ(static_cast<int>(payload["y"].asDouble()), 40);
326
+ EXPECT_EQ(static_cast<int>(payload["absoluteX"].asDouble()), 120);
327
+ checked = true;
328
+ });
329
+ registry.pointerMove(120, 60, 16);
330
+ registry.pointerUp(120, 60, 32);
331
+ }
@@ -0,0 +1,92 @@
1
+ // Tests for the hover gate.
2
+ //
3
+ // Core's, so compiled into every platform's suite: which views a cursor is
4
+ // inside is the toolkit's answer, and what to do about it is not.
5
+ //
6
+ // What is asserted here is small on purpose, because the hover *model* is
7
+ // React Native's own -- `PointerEventsProcessor` turns a stream of moves into
8
+ // enter, leave, over and out, and a host that also emits those gets every one
9
+ // of them twice over. See the header of HoverTracker.h. What is left to this
10
+ // project is deciding which moves are worth dispatching at all, and the case
11
+ // worth testing is the one that is easy to get wrong: the move *after* the
12
+ // cursor leaves everything that was listening.
13
+
14
+ #include "TestHarness.h"
15
+
16
+ #include "HoverTracker.h"
17
+
18
+ #include <cstdint>
19
+ #include <sstream>
20
+
21
+ namespace {
22
+
23
+ using basalt::HoverTracker;
24
+
25
+ constexpr std::uint16_t kListening = basalt::HoverListenerEnter | basalt::HoverListenerLeave;
26
+ constexpr std::uint16_t kSilent = basalt::HoverListenerNone;
27
+
28
+ } // namespace
29
+
30
+ TEST(hover_dispatches_a_move_over_a_view_that_listens) {
31
+ HoverTracker tracker;
32
+ EXPECT(tracker.admitMove(10, kListening));
33
+ // Every move while the cursor is over it, because React Native works out
34
+ // whether anything actually changed.
35
+ EXPECT(tracker.admitMove(10, kListening));
36
+ EXPECT(tracker.admitMove(11, kListening));
37
+ }
38
+
39
+ TEST(hover_dispatches_nothing_over_views_that_do_not) {
40
+ HoverTracker tracker;
41
+ // The reason the gate exists: a cursor crossing an app that uses no hover
42
+ // must not cost a trip into JavaScript per motion event.
43
+ EXPECT(!tracker.admitMove(10, kSilent));
44
+ EXPECT(!tracker.admitMove(11, kSilent));
45
+ }
46
+
47
+ TEST(hover_dispatches_one_last_move_after_leaving_a_listening_view) {
48
+ HoverTracker tracker;
49
+ EXPECT(tracker.admitMove(10, kListening));
50
+ // The cursor is now over something that listens to nothing. Suppressing this
51
+ // move would leave React Native holding a hover path it never hears is
52
+ // stale, and view 10 hovered for good -- so exactly one more goes through.
53
+ EXPECT(tracker.admitMove(11, kSilent));
54
+ EXPECT(!tracker.admitMove(12, kSilent));
55
+ }
56
+
57
+ TEST(hover_leaving_the_surface_names_the_view_a_move_was_dispatched_at) {
58
+ HoverTracker tracker;
59
+ EXPECT(tracker.admitMove(10, kListening));
60
+ // A leave is dispatched at whatever React Native was last told about; the
61
+ // processor reads it as the pointer being gone and unwinds the path itself.
62
+ EXPECT_EQ(tracker.admitLeave(), 10);
63
+ // And a second leave is not a second event.
64
+ EXPECT_EQ(tracker.admitLeave(), 0);
65
+ }
66
+
67
+ TEST(hover_leaving_without_ever_dispatching_is_nothing) {
68
+ HoverTracker tracker;
69
+ tracker.admitMove(10, kSilent);
70
+ // React Native has no path to unwind, so there is nothing to tell it.
71
+ EXPECT_EQ(tracker.admitLeave(), 0);
72
+ }
73
+
74
+ TEST(hover_forget_drops_the_state_without_naming_anything) {
75
+ HoverTracker tracker;
76
+ tracker.admitMove(10, kListening);
77
+ // For a surface teardown: the view is about to stop existing, and a leave
78
+ // dispatched at it would be a lookup of an emitter that is gone.
79
+ tracker.forget();
80
+ EXPECT_EQ(tracker.admitLeave(), 0);
81
+ EXPECT(!tracker.admitMove(11, kSilent));
82
+ }
83
+
84
+ TEST(hover_listeners_come_off_the_props_react_native_parsed) {
85
+ facebook::react::ViewEvents events{};
86
+ EXPECT_EQ(basalt::hoverListenersFrom(events), (std::uint16_t)basalt::HoverListenerNone);
87
+
88
+ events[facebook::react::ViewEvents::Offset::PointerEnter] = true;
89
+ events[facebook::react::ViewEvents::Offset::PointerOutCapture] = true;
90
+ EXPECT_EQ(basalt::hoverListenersFrom(events),
91
+ (std::uint16_t)(basalt::HoverListenerEnter | basalt::HoverListenerOutCapture));
92
+ }
@@ -0,0 +1,109 @@
1
+ // Which decoded image gets dropped, and when.
2
+ //
3
+ // The three hosts hold three unrelated texture types and none of them can be
4
+ // described here, so what is tested is the decision rather than the pixels --
5
+ // see core/ImageCache.h.
6
+
7
+ #include "TestHarness.h"
8
+
9
+ #include "ImageCache.h"
10
+
11
+ #include <sstream>
12
+
13
+ using basalt::ImageCachePolicy;
14
+
15
+ TEST(image_cache_keeps_what_fits) {
16
+ ImageCachePolicy cache(1000);
17
+ EXPECT(cache.insert("a", 400).empty());
18
+ EXPECT(cache.insert("b", 400).empty());
19
+ EXPECT_EQ((long)cache.count(), 2L);
20
+ EXPECT_EQ((long)cache.bytes(), 800L);
21
+ }
22
+
23
+ TEST(image_cache_evicts_the_oldest_when_it_does_not) {
24
+ ImageCachePolicy cache(1000);
25
+ cache.insert("a", 400);
26
+ cache.insert("b", 400);
27
+
28
+ const auto evicted = cache.insert("c", 400);
29
+ EXPECT_EQ((long)evicted.size(), 1L);
30
+ EXPECT(evicted[0] == "a");
31
+ EXPECT_EQ((long)cache.count(), 2L);
32
+ EXPECT_EQ((long)cache.bytes(), 800L);
33
+ }
34
+
35
+ // The whole reason for least-recently-*used* rather than least recently added:
36
+ // scrolling back up should not re-decode the rows about to be on screen.
37
+ TEST(image_cache_a_use_saves_an_entry) {
38
+ ImageCachePolicy cache(1000);
39
+ cache.insert("a", 400);
40
+ cache.insert("b", 400);
41
+ cache.noteUse("a");
42
+
43
+ const auto evicted = cache.insert("c", 400);
44
+ EXPECT_EQ((long)evicted.size(), 1L);
45
+ // `b` is now the oldest, because `a` was touched after it went in.
46
+ EXPECT(evicted[0] == "b");
47
+ }
48
+
49
+ TEST(image_cache_evicts_as_many_as_it_takes) {
50
+ ImageCachePolicy cache(1000);
51
+ cache.insert("a", 300);
52
+ cache.insert("b", 300);
53
+ cache.insert("c", 300);
54
+
55
+ const auto evicted = cache.insert("big", 900);
56
+ EXPECT_EQ((long)evicted.size(), 3L);
57
+ EXPECT(evicted[0] == "a");
58
+ EXPECT(evicted[2] == "c");
59
+ EXPECT_EQ((long)cache.count(), 1L);
60
+ }
61
+
62
+ // On screen and larger than the whole budget. Keeping it is wrong in one way
63
+ // and evicting it is wrong in a worse one: it would be decoded again on the
64
+ // next frame, and every frame after that.
65
+ TEST(image_cache_keeps_an_image_larger_than_the_budget) {
66
+ ImageCachePolicy cache(1000);
67
+ const auto evicted = cache.insert("huge", 5000);
68
+ EXPECT(evicted.empty());
69
+ EXPECT_EQ((long)cache.count(), 1L);
70
+
71
+ // And the next insert clears it out rather than leaving both.
72
+ const auto next = cache.insert("small", 100);
73
+ EXPECT_EQ((long)next.size(), 1L);
74
+ EXPECT(next[0] == "huge");
75
+ }
76
+
77
+ TEST(image_cache_reinserting_replaces_rather_than_doubling) {
78
+ ImageCachePolicy cache(1000);
79
+ cache.insert("a", 400);
80
+ cache.insert("a", 600);
81
+ EXPECT_EQ((long)cache.count(), 1L);
82
+ EXPECT_EQ((long)cache.bytes(), 600L);
83
+ }
84
+
85
+ TEST(image_cache_remove_and_clear_free_their_bytes) {
86
+ ImageCachePolicy cache(1000);
87
+ cache.insert("a", 400);
88
+ cache.insert("b", 300);
89
+
90
+ cache.remove("a");
91
+ EXPECT_EQ((long)cache.count(), 1L);
92
+ EXPECT_EQ((long)cache.bytes(), 300L);
93
+
94
+ // Removing what is not there is not an error: a host may release an image
95
+ // this never saw.
96
+ cache.remove("nothing");
97
+ EXPECT_EQ((long)cache.count(), 1L);
98
+
99
+ cache.clear();
100
+ EXPECT_EQ((long)cache.count(), 0L);
101
+ EXPECT_EQ((long)cache.bytes(), 0L);
102
+ }
103
+
104
+ TEST(image_cache_a_use_of_something_absent_is_harmless) {
105
+ ImageCachePolicy cache(1000);
106
+ cache.insert("a", 400);
107
+ cache.noteUse("never-seen");
108
+ EXPECT_EQ((long)cache.count(), 1L);
109
+ }
@@ -0,0 +1,137 @@
1
+ // The door to the JavaScript thread that a host leaves open.
2
+ //
3
+ // What is testable here is the decision -- is there a runner, does work reach
4
+ // it, does clearing it stop the work -- and not the running, which needs a
5
+ // runtime and a host. So the runner in these tests records what it was handed
6
+ // rather than calling it: the point of the seam is that native code on some
7
+ // other thread can post *without* knowing whether anyone is listening, and that
8
+ // is exactly the part a test can pin down.
9
+ //
10
+ // The half that needs a runtime -- an event arriving at a JavaScript listener --
11
+ // is proven by the harness scenario, the same division test_blobs.cpp makes.
12
+
13
+ #include "TestHarness.h"
14
+
15
+ #include "ExpoModules.h"
16
+ #include "ExpoRuntime.h"
17
+ #include "JsRuntimeAccess.h"
18
+
19
+ #include <sstream>
20
+ #include <stdexcept>
21
+ #include <string>
22
+ #include <vector>
23
+
24
+ namespace {
25
+
26
+ // Restores the seam whatever a test does to it. Process-wide state that a later
27
+ // test reads, exactly like the blob registry.
28
+ struct RunnerFixture {
29
+ ~RunnerFixture() { basalt::setRuntimeRunner(nullptr); }
30
+ };
31
+
32
+ } // namespace
33
+
34
+ TEST(without_a_host_there_is_nowhere_to_post) {
35
+ // The tests and the portability probe, and the answer has to be a reported
36
+ // false rather than a crash: a subprocess reader thread emitting into a
37
+ // process that never had a runtime is ordinary, not exceptional.
38
+ RunnerFixture fixture;
39
+ basalt::setRuntimeRunner(nullptr);
40
+
41
+ bool ran = false;
42
+ EXPECT(!basalt::runOnJsRuntime([&ran](facebook::jsi::Runtime &) { ran = true; }));
43
+ EXPECT(!ran);
44
+ }
45
+
46
+ TEST(work_reaches_the_runner_a_host_left) {
47
+ RunnerFixture fixture;
48
+ int posted = 0;
49
+ basalt::setRuntimeRunner([&posted](std::function<void(facebook::jsi::Runtime &)>) {
50
+ // Held rather than called: calling it needs a runtime. What is asserted is
51
+ // that it arrived.
52
+ posted++;
53
+ });
54
+
55
+ EXPECT(basalt::runOnJsRuntime([](facebook::jsi::Runtime &) {}));
56
+ EXPECT(basalt::runOnJsRuntime([](facebook::jsi::Runtime &) {}));
57
+ EXPECT_EQ(posted, 2);
58
+ }
59
+
60
+ TEST(clearing_the_runner_stops_the_posting) {
61
+ // The shutdown path: a host clears this before it destroys its ReactHost, so
62
+ // that work in flight from another thread does not reach a runtime that is
63
+ // being joined.
64
+ RunnerFixture fixture;
65
+ int posted = 0;
66
+ basalt::setRuntimeRunner([&posted](std::function<void(facebook::jsi::Runtime &)>) { posted++; });
67
+ EXPECT(basalt::runOnJsRuntime([](facebook::jsi::Runtime &) {}));
68
+
69
+ basalt::setRuntimeRunner(nullptr);
70
+ EXPECT(!basalt::runOnJsRuntime([](facebook::jsi::Runtime &) {}));
71
+ EXPECT_EQ(posted, 1);
72
+ }
73
+
74
+ TEST(an_expo_event_goes_through_the_same_door) {
75
+ // Which is the whole reason the door exists: an Expo module is handed no call
76
+ // invoker, so this is the only way it can emit.
77
+ //
78
+ // **Both builds, asked at runtime.** A build not pointed at an
79
+ // expo-modules-core has no Expo modules to emit on, and `emitExpoEvent` is
80
+ // then the documented no-op that answers false rather than a missing symbol.
81
+ // Asked through `hasExpoRuntime()` rather than `#ifdef BASALT_HAS_EXPO`
82
+ // because the define is added with `add_compile_definitions` in the core
83
+ // package's directory and a host's test target is a sibling, so the ifdef
84
+ // would read false in a suite where Expo is compiled in -- which is the wrong
85
+ // answer twice over. The first version of this test assumed the Expo build and
86
+ // passed on a machine with an app installed; CI, which has none, is where it
87
+ // failed.
88
+ RunnerFixture fixture;
89
+ basalt::setRuntimeRunner(nullptr);
90
+ EXPECT(!basalt::emitExpoEvent("KinoProcess", "onOutput", [](facebook::jsi::Runtime &,
91
+ std::vector<facebook::jsi::Value> &) {
92
+ }));
93
+
94
+ int posted = 0;
95
+ basalt::setRuntimeRunner([&posted](std::function<void(facebook::jsi::Runtime &)>) { posted++; });
96
+ const bool emitted =
97
+ basalt::emitExpoEvent("KinoProcess", "onOutput", [](facebook::jsi::Runtime &,
98
+ std::vector<facebook::jsi::Value> &) {});
99
+ if (basalt::hasExpoRuntime()) {
100
+ EXPECT(emitted);
101
+ EXPECT_EQ(posted, 1);
102
+ } else {
103
+ EXPECT(!emitted);
104
+ EXPECT_EQ(posted, 0);
105
+ }
106
+ }
107
+
108
+ TEST(the_call_invoker_over_the_runner_posts_and_says_it_cannot_wait) {
109
+ RunnerFixture fixture;
110
+ int posted = 0;
111
+ basalt::setRuntimeRunner([&posted](std::function<void(facebook::jsi::Runtime &)>) { posted++; });
112
+
113
+ const auto invoker = basalt::jsCallInvoker();
114
+ EXPECT(invoker != nullptr);
115
+ invoker->invokeAsync(facebook::react::CallFunc([](facebook::jsi::Runtime &) {}));
116
+ EXPECT_EQ(posted, 1);
117
+
118
+ // Rather than deadlocking, which is what a synchronous call from another
119
+ // thread into a busy runtime would do; see the header.
120
+ bool threw = false;
121
+ try {
122
+ invoker->invokeSync(facebook::react::CallFunc([](facebook::jsi::Runtime &) {}));
123
+ } catch (const std::logic_error &error) {
124
+ threw = true;
125
+ // Self-naming, so the message alone says which invoker and why.
126
+ EXPECT(std::string(error.what()).find("JsRuntimeAccess") != std::string::npos);
127
+ }
128
+ EXPECT(threw);
129
+ EXPECT_EQ(posted, 1);
130
+ }
131
+
132
+ TEST(the_same_invoker_survives_a_reload) {
133
+ // It holds nothing but the call to runOnJsRuntime, so a module that captured
134
+ // it before a Fast Refresh reload is still holding something that works --
135
+ // which is why one shared instance is safe to hand out.
136
+ EXPECT(basalt::jsCallInvoker() == basalt::jsCallInvoker());
137
+ }