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.
- package/LICENSE +21 -0
- package/dist/cli/basalt.d.ts +3 -0
- package/dist/cli/basalt.d.ts.map +1 -0
- package/dist/cli/basalt.js +47 -0
- package/dist/cli/basalt.js.map +1 -0
- package/dist/cli/bundle-cli.d.ts +12 -0
- package/dist/cli/bundle-cli.d.ts.map +1 -0
- package/dist/cli/bundle-cli.js +124 -0
- package/dist/cli/bundle-cli.js.map +1 -0
- package/dist/cli/bundleWithAssets.d.ts +79 -0
- package/dist/cli/bundleWithAssets.d.ts.map +1 -0
- package/dist/cli/bundleWithAssets.js +198 -0
- package/dist/cli/bundleWithAssets.js.map +1 -0
- package/dist/cli/desktop.d.ts +339 -0
- package/dist/cli/desktop.d.ts.map +1 -0
- package/dist/cli/desktop.js +1029 -0
- package/dist/cli/desktop.js.map +1 -0
- package/dist/cli/doctor.d.ts +31 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +283 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/init.d.ts +77 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +442 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/metro.d.ts +49 -0
- package/dist/cli/metro.d.ts.map +1 -0
- package/dist/cli/metro.js +148 -0
- package/dist/cli/metro.js.map +1 -0
- package/dist/cli/packageApp.d.ts +102 -0
- package/dist/cli/packageApp.d.ts.map +1 -0
- package/dist/cli/packageApp.js +319 -0
- package/dist/cli/packageApp.js.map +1 -0
- package/dist/cli/steps.d.ts +34 -0
- package/dist/cli/steps.d.ts.map +1 -0
- package/dist/cli/steps.js +53 -0
- package/dist/cli/steps.js.map +1 -0
- package/dist/metro-config.d.ts +168 -0
- package/dist/metro-config.d.ts.map +1 -0
- package/dist/metro-config.js +671 -0
- package/dist/metro-config.js.map +1 -0
- package/dist/react-native.config.d.ts +7 -0
- package/dist/react-native.config.d.ts.map +1 -0
- package/dist/react-native.config.js +80 -0
- package/dist/react-native.config.js.map +1 -0
- package/dist/src/DragSource.d.ts +52 -0
- package/dist/src/DragSource.d.ts.map +1 -0
- package/dist/src/DragSource.js +97 -0
- package/dist/src/DragSource.js.map +1 -0
- package/dist/src/DropTarget.d.ts +58 -0
- package/dist/src/DropTarget.d.ts.map +1 -0
- package/dist/src/DropTarget.js +131 -0
- package/dist/src/DropTarget.js.map +1 -0
- package/dist/src/KeyHandler.d.ts +57 -0
- package/dist/src/KeyHandler.d.ts.map +1 -0
- package/dist/src/KeyHandler.js +48 -0
- package/dist/src/KeyHandler.js.map +1 -0
- package/dist/src/Menu.d.ts +72 -0
- package/dist/src/Menu.d.ts.map +1 -0
- package/dist/src/Menu.js +179 -0
- package/dist/src/Menu.js.map +1 -0
- package/dist/src/TitleBar.d.ts +75 -0
- package/dist/src/TitleBar.d.ts.map +1 -0
- package/dist/src/TitleBar.js +236 -0
- package/dist/src/TitleBar.js.map +1 -0
- package/dist/src/Window.d.ts +87 -0
- package/dist/src/Window.d.ts.map +1 -0
- package/dist/src/Window.js +248 -0
- package/dist/src/Window.js.map +1 -0
- package/dist/src/closeRequest.d.ts +55 -0
- package/dist/src/closeRequest.d.ts.map +1 -0
- package/dist/src/closeRequest.js +134 -0
- package/dist/src/closeRequest.js.map +1 -0
- package/dist/src/index.d.ts +45 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +92 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/overrides/Alert.d.ts +69 -0
- package/dist/src/overrides/Alert.d.ts.map +1 -0
- package/dist/src/overrides/Alert.js +112 -0
- package/dist/src/overrides/Alert.js.map +1 -0
- package/dist/src/overrides/BaseViewConfig.d.ts +28 -0
- package/dist/src/overrides/BaseViewConfig.d.ts.map +1 -0
- package/dist/src/overrides/BaseViewConfig.js +72 -0
- package/dist/src/overrides/BaseViewConfig.js.map +1 -0
- package/dist/src/overrides/Platform.linux.d.ts +10 -0
- package/dist/src/overrides/Platform.linux.d.ts.map +1 -0
- package/dist/src/overrides/Platform.linux.js +15 -0
- package/dist/src/overrides/Platform.linux.js.map +1 -0
- package/dist/src/overrides/Platform.macos.d.ts +10 -0
- package/dist/src/overrides/Platform.macos.d.ts.map +1 -0
- package/dist/src/overrides/Platform.macos.js +15 -0
- package/dist/src/overrides/Platform.macos.js.map +1 -0
- package/dist/src/overrides/Platform.windows.d.ts +10 -0
- package/dist/src/overrides/Platform.windows.d.ts.map +1 -0
- package/dist/src/overrides/Platform.windows.js +15 -0
- package/dist/src/overrides/Platform.windows.js.map +1 -0
- package/dist/src/overrides/ReactDevToolsSettingsManager.d.ts +24 -0
- package/dist/src/overrides/ReactDevToolsSettingsManager.d.ts.map +1 -0
- package/dist/src/overrides/ReactDevToolsSettingsManager.js +30 -0
- package/dist/src/overrides/ReactDevToolsSettingsManager.js.map +1 -0
- package/dist/src/overrides/RefreshControl.d.ts +52 -0
- package/dist/src/overrides/RefreshControl.d.ts.map +1 -0
- package/dist/src/overrides/RefreshControl.js +92 -0
- package/dist/src/overrides/RefreshControl.js.map +1 -0
- package/dist/src/overrides/ScrollView.d.ts +39 -0
- package/dist/src/overrides/ScrollView.d.ts.map +1 -0
- package/dist/src/overrides/ScrollView.js +59 -0
- package/dist/src/overrides/ScrollView.js.map +1 -0
- package/dist/src/overrides/Share.d.ts +96 -0
- package/dist/src/overrides/Share.d.ts.map +1 -0
- package/dist/src/overrides/Share.js +97 -0
- package/dist/src/overrides/Share.js.map +1 -0
- package/dist/src/overrides/TextInput.d.ts +81 -0
- package/dist/src/overrides/TextInput.d.ts.map +1 -0
- package/dist/src/overrides/TextInput.js +213 -0
- package/dist/src/overrides/TextInput.js.map +1 -0
- package/dist/src/overrides/View.d.ts +36 -0
- package/dist/src/overrides/View.d.ts.map +1 -0
- package/dist/src/overrides/View.js +99 -0
- package/dist/src/overrides/View.js.map +1 -0
- package/dist/src/overrides/createPlatform.d.ts +32 -0
- package/dist/src/overrides/createPlatform.d.ts.map +1 -0
- package/dist/src/overrides/createPlatform.js +84 -0
- package/dist/src/overrides/createPlatform.js.map +1 -0
- package/dist/src/overrides/setUpDeveloperTools.d.ts +28 -0
- package/dist/src/overrides/setUpDeveloperTools.d.ts.map +1 -0
- package/dist/src/overrides/setUpDeveloperTools.js +84 -0
- package/dist/src/overrides/setUpDeveloperTools.js.map +1 -0
- package/dist/src/overrides/setUpXHR.d.ts +46 -0
- package/dist/src/overrides/setUpXHR.d.ts.map +1 -0
- package/dist/src/overrides/setUpXHR.js +236 -0
- package/dist/src/overrides/setUpXHR.js.map +1 -0
- package/dist/src/quitRequest.d.ts +55 -0
- package/dist/src/quitRequest.d.ts.map +1 -0
- package/dist/src/quitRequest.js +121 -0
- package/dist/src/quitRequest.js.map +1 -0
- package/dist/src/resourcePath.d.ts +18 -0
- package/dist/src/resourcePath.d.ts.map +1 -0
- package/dist/src/resourcePath.js +31 -0
- package/dist/src/resourcePath.js.map +1 -0
- package/dist/src/responseBody.d.ts +76 -0
- package/dist/src/responseBody.d.ts.map +1 -0
- package/dist/src/responseBody.js +94 -0
- package/dist/src/responseBody.js.map +1 -0
- package/dist/src/responseHeaders.d.ts +40 -0
- package/dist/src/responseHeaders.d.ts.map +1 -0
- package/dist/src/responseHeaders.js +75 -0
- package/dist/src/responseHeaders.js.map +1 -0
- package/dist/src/titleBarState.d.ts +43 -0
- package/dist/src/titleBarState.d.ts.map +1 -0
- package/dist/src/titleBarState.js +77 -0
- package/dist/src/titleBarState.js.map +1 -0
- package/dist/src/useContextMenu.d.ts +113 -0
- package/dist/src/useContextMenu.d.ts.map +1 -0
- package/dist/src/useContextMenu.js +109 -0
- package/dist/src/useContextMenu.js.map +1 -0
- package/dist/src/useDialog.d.ts +98 -0
- package/dist/src/useDialog.d.ts.map +1 -0
- package/dist/src/useDialog.js +112 -0
- package/dist/src/useDialog.js.map +1 -0
- package/dist/src/useDisplays.d.ts +70 -0
- package/dist/src/useDisplays.d.ts.map +1 -0
- package/dist/src/useDisplays.js +118 -0
- package/dist/src/useDisplays.js.map +1 -0
- package/dist/src/useHandledKeys.d.ts +38 -0
- package/dist/src/useHandledKeys.d.ts.map +1 -0
- package/dist/src/useHandledKeys.js +70 -0
- package/dist/src/useHandledKeys.js.map +1 -0
- package/dist/src/useWindow.d.ts +140 -0
- package/dist/src/useWindow.d.ts.map +1 -0
- package/dist/src/useWindow.js +233 -0
- package/dist/src/useWindow.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/native/CMakeLists.txt +355 -0
- package/native/bootstrap.sh +613 -0
- package/native/cmake/CompilerFlags.cmake +49 -0
- package/native/cmake/Expo.cmake +125 -0
- package/native/cmake/ReactNativeCore.cmake +612 -0
- package/native/cmake/Reanimated.cmake +108 -0
- package/native/cmake/Skia.cmake +379 -0
- package/native/cmake/ThirdParty.cmake +290 -0
- package/native/cmake/Worklets.cmake +67 -0
- package/native/cmake/codegen/CMakeLists.txt +32 -0
- package/native/core/AppIdentity.cpp +85 -0
- package/native/core/AppIdentity.h +59 -0
- package/native/core/AppPaths.cpp +96 -0
- package/native/core/AppPaths.h +53 -0
- package/native/core/AppearanceModule.cpp +60 -0
- package/native/core/AppearanceModule.h +40 -0
- package/native/core/Backface.h +59 -0
- package/native/core/BlobModule.cpp +283 -0
- package/native/core/BlobModule.h +76 -0
- package/native/core/BlobRegistry.cpp +75 -0
- package/native/core/BlobRegistry.h +44 -0
- package/native/core/ColorScheme.cpp +105 -0
- package/native/core/ColorScheme.h +59 -0
- package/native/core/ComponentRegistry.h +30 -0
- package/native/core/ControlMetrics.h +37 -0
- package/native/core/CoreModules.cpp +592 -0
- package/native/core/CoreModules.h +308 -0
- package/native/core/DebuggingOverlay.cpp +106 -0
- package/native/core/DebuggingOverlay.h +65 -0
- package/native/core/DesktopControls.cpp +232 -0
- package/native/core/DesktopControls.h +170 -0
- package/native/core/DevBundle.cpp +108 -0
- package/native/core/DevBundle.h +69 -0
- package/native/core/DevMenu.cpp +99 -0
- package/native/core/DevMenu.h +75 -0
- package/native/core/DialogModule.cpp +150 -0
- package/native/core/DialogModule.h +51 -0
- package/native/core/DragAndDrop.cpp +45 -0
- package/native/core/DragAndDrop.h +225 -0
- package/native/core/ExpoImageComponent.cpp +79 -0
- package/native/core/ExpoImageComponent.h +80 -0
- package/native/core/ExpoModules.cpp +415 -0
- package/native/core/ExpoModules.h +115 -0
- package/native/core/ExpoRuntime.cpp +366 -0
- package/native/core/ExpoRuntime.h +42 -0
- package/native/core/FocusRing.h +36 -0
- package/native/core/FontRegistry.h +51 -0
- package/native/core/GestureHandlerModule.cpp +136 -0
- package/native/core/GestureHandlerModule.h +71 -0
- package/native/core/Gestures.cpp +533 -0
- package/native/core/Gestures.h +145 -0
- package/native/core/HoverTracker.cpp +69 -0
- package/native/core/HoverTracker.h +109 -0
- package/native/core/HttpClient.cpp +282 -0
- package/native/core/ImageBytes.cpp +205 -0
- package/native/core/ImageBytes.h +29 -0
- package/native/core/ImageCache.cpp +51 -0
- package/native/core/ImageCache.h +89 -0
- package/native/core/JsRuntimeAccess.cpp +70 -0
- package/native/core/JsRuntimeAccess.h +60 -0
- package/native/core/JsiPromise.h +51 -0
- package/native/core/KeyEvents.cpp +116 -0
- package/native/core/KeyEvents.h +144 -0
- package/native/core/LogBoxSurface.cpp +46 -0
- package/native/core/LogBoxSurface.h +68 -0
- package/native/core/MenuModel.h +117 -0
- package/native/core/MenuModule.cpp +209 -0
- package/native/core/MenuModule.h +66 -0
- package/native/core/MountingWalk.h +524 -0
- package/native/core/PackageModules.cpp.in +25 -0
- package/native/core/PackageModules.h +50 -0
- package/native/core/PlatformConstantsModule.cpp +63 -0
- package/native/core/PlatformConstantsModule.h +64 -0
- package/native/core/PlatformServices.h +213 -0
- package/native/core/PointerButtons.h +63 -0
- package/native/core/PullToRefresh.cpp +57 -0
- package/native/core/PullToRefresh.h +73 -0
- package/native/core/ReanimatedCompat.h +47 -0
- package/native/core/ReanimatedModule.cpp +203 -0
- package/native/core/ReanimatedModule.h +55 -0
- package/native/core/ScrollAnimation.cpp +59 -0
- package/native/core/ScrollAnimation.h +70 -0
- package/native/core/ScrollBounds.cpp +26 -0
- package/native/core/ScrollBounds.h +68 -0
- package/native/core/ScrollIndicator.cpp +50 -0
- package/native/core/ScrollIndicator.h +69 -0
- package/native/core/ScrollMomentum.cpp +71 -0
- package/native/core/ScrollMomentum.h +73 -0
- package/native/core/ScrollSnap.cpp +123 -0
- package/native/core/ScrollSnap.h +80 -0
- package/native/core/ShareFallback.cpp +101 -0
- package/native/core/ShareFallback.h +45 -0
- package/native/core/SkiaPictureViewComponent.cpp +11 -0
- package/native/core/SkiaPictureViewComponent.h +47 -0
- package/native/core/SourceCodeModule.cpp +55 -0
- package/native/core/SourceCodeModule.h +53 -0
- package/native/core/StatusBarModule.cpp +23 -0
- package/native/core/StatusBarModule.h +42 -0
- package/native/core/TestDialog.cpp +148 -0
- package/native/core/TestDialog.h +96 -0
- package/native/core/TestKeys.cpp +78 -0
- package/native/core/TestKeys.h +50 -0
- package/native/core/TestQuitFile.cpp +33 -0
- package/native/core/TestQuitFile.h +60 -0
- package/native/core/TestSettle.h +192 -0
- package/native/core/TitleBarRegions.h +31 -0
- package/native/core/TurboModuleProxy.cpp +51 -0
- package/native/core/TurboModuleProxy.h +46 -0
- package/native/core/UIManagerAccess.cpp +43 -0
- package/native/core/UIManagerAccess.h +43 -0
- package/native/core/WindowBoundsCache.cpp +319 -0
- package/native/core/WindowControl.h +235 -0
- package/native/core/WindowHost.h +158 -0
- package/native/core/WindowMethods.cpp +167 -0
- package/native/core/WindowMethods.h +84 -0
- package/native/core/WindowsModule.cpp +448 -0
- package/native/core/WindowsModule.h +133 -0
- package/native/core/WorkletsModule.cpp +199 -0
- package/native/core/WorkletsModule.h +57 -0
- package/native/core/portability_probe.cpp +210 -0
- package/native/tests/EventRecorder.h +124 -0
- package/native/tests/TestHarness.cpp +109 -0
- package/native/tests/TestHarness.h +89 -0
- package/native/tests/TreeDump.h +49 -0
- package/native/tests/test_app_identity.cpp +114 -0
- package/native/tests/test_app_paths.cpp +48 -0
- package/native/tests/test_appearance.cpp +107 -0
- package/native/tests/test_backface.cpp +79 -0
- package/native/tests/test_blobs.cpp +157 -0
- package/native/tests/test_controls.cpp +185 -0
- package/native/tests/test_devbundle.cpp +103 -0
- package/native/tests/test_drag_and_drop.cpp +143 -0
- package/native/tests/test_gestures.cpp +331 -0
- package/native/tests/test_hover.cpp +92 -0
- package/native/tests/test_image_cache.cpp +109 -0
- package/native/tests/test_js_runtime_access.cpp +137 -0
- package/native/tests/test_key_events.cpp +235 -0
- package/native/tests/test_momentum.cpp +138 -0
- package/native/tests/test_quit_file.cpp +104 -0
- package/native/tests/test_scroll_animation.cpp +120 -0
- package/native/tests/test_scroll_bounds.cpp +66 -0
- package/native/tests/test_scroll_indicator.cpp +149 -0
- package/native/tests/test_scroll_snap.cpp +112 -0
- package/native/tests/test_settle.cpp +115 -0
- package/native/tests/test_share.cpp +75 -0
- package/native/tests/test_sourcecode.cpp +62 -0
- package/native/tests/test_window_close.cpp +119 -0
- package/native/tests/test_window_limits.cpp +123 -0
- package/package.json +43 -0
- package/react-native.config.js +86 -0
- package/supported-versions.json +66 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#include "TestDialog.h"
|
|
2
|
+
|
|
3
|
+
#include <cstdlib>
|
|
4
|
+
#include <string>
|
|
5
|
+
#include <utility>
|
|
6
|
+
#include <vector>
|
|
7
|
+
|
|
8
|
+
namespace basalt {
|
|
9
|
+
|
|
10
|
+
std::optional<int> scriptedDialogButton() {
|
|
11
|
+
static const std::optional<int> button = []() -> std::optional<int> {
|
|
12
|
+
const char *value = std::getenv("BASALT_TEST_DIALOG");
|
|
13
|
+
if (value == nullptr || *value == '\0') {
|
|
14
|
+
return std::nullopt;
|
|
15
|
+
}
|
|
16
|
+
// A button index, and `dismiss` for the way out -- which is the last
|
|
17
|
+
// button, the one React Native's alerts and this project's share picker
|
|
18
|
+
// both put the cancelling choice in.
|
|
19
|
+
if (std::string(value) == "dismiss") {
|
|
20
|
+
return -1;
|
|
21
|
+
}
|
|
22
|
+
char *end = nullptr;
|
|
23
|
+
const long parsed = std::strtol(value, &end, 10);
|
|
24
|
+
if (end == value) {
|
|
25
|
+
return std::nullopt;
|
|
26
|
+
}
|
|
27
|
+
return static_cast<int>(parsed);
|
|
28
|
+
}();
|
|
29
|
+
return button;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
void presentAlert(const AlertRequest &request, AlertCallback onButton) {
|
|
33
|
+
const std::optional<int> scripted = scriptedDialogButton();
|
|
34
|
+
if (!scripted.has_value()) {
|
|
35
|
+
showAlert(request, std::move(onButton));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// The last button for `dismiss`, and the named one otherwise, clamped: a
|
|
40
|
+
// script that names a button an app did not offer should answer the nearest
|
|
41
|
+
// real one rather than an index nothing will understand.
|
|
42
|
+
const int last = request.buttons.empty() ? 0 : static_cast<int>(request.buttons.size()) - 1;
|
|
43
|
+
int button = *scripted < 0 ? last : *scripted;
|
|
44
|
+
if (button > last) {
|
|
45
|
+
button = last;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Straight back, on this thread. The platform implementations marshal to the
|
|
49
|
+
// main thread because they have to touch a window; there is no window here,
|
|
50
|
+
// and an answer that arrives before the caller has returned is something
|
|
51
|
+
// every caller already handles -- `AsyncPromise` and the alert module's
|
|
52
|
+
// callback both settle whenever they are told to.
|
|
53
|
+
onButton(button, request.defaultText);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
std::optional<int> scriptedMenuChoice() {
|
|
57
|
+
static const std::optional<int> choice = []() -> std::optional<int> {
|
|
58
|
+
const char *value = std::getenv("BASALT_TEST_MENU");
|
|
59
|
+
if (value == nullptr || *value == '\0') {
|
|
60
|
+
return std::nullopt;
|
|
61
|
+
}
|
|
62
|
+
if (std::string(value) == "dismiss") {
|
|
63
|
+
return -1;
|
|
64
|
+
}
|
|
65
|
+
char *end = nullptr;
|
|
66
|
+
const long parsed = std::strtol(value, &end, 10);
|
|
67
|
+
if (end == value) {
|
|
68
|
+
return std::nullopt;
|
|
69
|
+
}
|
|
70
|
+
return static_cast<int>(parsed);
|
|
71
|
+
}();
|
|
72
|
+
return choice;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
void presentMenu(const MenuRequest &request, MenuCallback onChosen) {
|
|
76
|
+
const std::optional<int> scripted = scriptedMenuChoice();
|
|
77
|
+
if (!scripted.has_value()) {
|
|
78
|
+
showMenu(request, std::move(onChosen));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Out of range answers as a dismissal rather than being clamped, which is the
|
|
83
|
+
// opposite of what presentAlert does with a button. A menu's entries are not
|
|
84
|
+
// interchangeable -- picking the nearest one would run something the script
|
|
85
|
+
// did not ask for -- whereas an alert's last button is always the way out.
|
|
86
|
+
const int last = static_cast<int>(request.entries.size()) - 1;
|
|
87
|
+
const int index = (*scripted < 0 || *scripted > last) ? -1 : *scripted;
|
|
88
|
+
onChosen(index);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
std::optional<std::vector<std::string>> scriptedFileDialogPaths() {
|
|
92
|
+
static const std::optional<std::vector<std::string>> paths =
|
|
93
|
+
[]() -> std::optional<std::vector<std::string>> {
|
|
94
|
+
const char *value = std::getenv("BASALT_TEST_FILE_DIALOG");
|
|
95
|
+
if (value == nullptr || *value == '\0') {
|
|
96
|
+
return std::nullopt;
|
|
97
|
+
}
|
|
98
|
+
const std::string spec(value);
|
|
99
|
+
if (spec == "cancel") {
|
|
100
|
+
return std::vector<std::string>{};
|
|
101
|
+
}
|
|
102
|
+
// `;` on Windows, where a path starts "C:\\" and splitting on a colon
|
|
103
|
+
// would cut every path in two.
|
|
104
|
+
#ifdef _WIN32
|
|
105
|
+
const char separator = ';';
|
|
106
|
+
#else
|
|
107
|
+
const char separator = ':';
|
|
108
|
+
#endif
|
|
109
|
+
std::vector<std::string> parsed;
|
|
110
|
+
size_t start = 0;
|
|
111
|
+
while (start <= spec.size()) {
|
|
112
|
+
const size_t next = spec.find(separator, start);
|
|
113
|
+
const std::string part =
|
|
114
|
+
spec.substr(start, next == std::string::npos ? std::string::npos : next - start);
|
|
115
|
+
if (!part.empty()) {
|
|
116
|
+
parsed.push_back(part);
|
|
117
|
+
}
|
|
118
|
+
if (next == std::string::npos) {
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
start = next + 1;
|
|
122
|
+
}
|
|
123
|
+
return parsed;
|
|
124
|
+
}();
|
|
125
|
+
return paths;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
void presentFileDialog(const FileDialogRequest &request, FileDialogCallback onDone) {
|
|
129
|
+
const std::optional<std::vector<std::string>> scripted = scriptedFileDialogPaths();
|
|
130
|
+
if (!scripted.has_value()) {
|
|
131
|
+
showFileDialog(request, std::move(onDone));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// An empty list is `cancel`, which is the distinction the whole result shape
|
|
136
|
+
// exists to keep: "nothing was chosen" and "the person pressed Cancel" are
|
|
137
|
+
// not the same answer.
|
|
138
|
+
const bool canceled = scripted->empty();
|
|
139
|
+
std::vector<std::string> paths = *scripted;
|
|
140
|
+
// A save and a folder are one path however many the script named, which is
|
|
141
|
+
// what the platform dialogs enforce and what an app is entitled to assume.
|
|
142
|
+
if (!canceled && request.kind != FileDialogRequest::Kind::OpenFile && paths.size() > 1) {
|
|
143
|
+
paths.resize(1);
|
|
144
|
+
}
|
|
145
|
+
onDone(canceled, paths);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
} // namespace basalt
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Answering a modal dialog without showing one.
|
|
2
|
+
//
|
|
3
|
+
// The same escape hatch `BASALT_TEST_TAP` is, for the same reason and with the
|
|
4
|
+
// same limits. A modal dialog is the one piece of interface an automated run
|
|
5
|
+
// cannot get past: there is nobody to press a button, the window system will
|
|
6
|
+
// not synthesise a press into a sheet, and on macOS a sheet that is up stops
|
|
7
|
+
// `[NSApp terminate:]` outright -- so a run that shows one hangs until it is
|
|
8
|
+
// killed.
|
|
9
|
+
//
|
|
10
|
+
// ## Why not drive the real dialog
|
|
11
|
+
//
|
|
12
|
+
// Because that is where the flakiness lives, and the platforms say so
|
|
13
|
+
// themselves. Apple's own mechanism for this in UI tests,
|
|
14
|
+
// `addUIInterruptionMonitor`, is documented as not firing on recent iOS
|
|
15
|
+
// versions, and the workaround people reach for -- pressing the button through
|
|
16
|
+
// SpringBoard -- is widely reported as flaky; Apple's own advice for permission
|
|
17
|
+
// dialogs is to pre-grant them through launch arguments rather than to press
|
|
18
|
+
// anything. Detox, which is the closest thing React Native has to a house
|
|
19
|
+
// end-to-end runner, states the principle outright: gray box rather than black
|
|
20
|
+
// box, monitor the app from the inside, because that is what fights flakiness
|
|
21
|
+
// at the core.
|
|
22
|
+
//
|
|
23
|
+
// Driving it from outside is also not available here. AT-SPI on Linux, the
|
|
24
|
+
// Accessibility API on macOS and UI Automation on Windows can each press a
|
|
25
|
+
// button in a dialog, and each needs a permission or a session an automated run
|
|
26
|
+
// does not have -- which is the same wall `BASALT_TEST_TAP` exists because of.
|
|
27
|
+
//
|
|
28
|
+
// ## What this skips, exactly
|
|
29
|
+
//
|
|
30
|
+
// The dialog's presentation and nothing else. The request is built by the same
|
|
31
|
+
// module from the same JavaScript, the answer travels back through the same
|
|
32
|
+
// callback on the same thread, and every line above the platform runs. What
|
|
33
|
+
// goes untested is that the toolkit can put a dialog on screen and get a press
|
|
34
|
+
// out of it, which is the same thing every other instrument in this project
|
|
35
|
+
// leaves to a person.
|
|
36
|
+
|
|
37
|
+
#pragma once
|
|
38
|
+
|
|
39
|
+
#include "PlatformServices.h"
|
|
40
|
+
|
|
41
|
+
#include <vector>
|
|
42
|
+
|
|
43
|
+
#include <optional>
|
|
44
|
+
|
|
45
|
+
namespace basalt {
|
|
46
|
+
|
|
47
|
+
// The button index BASALT_TEST_DIALOG names, or nothing when it is unset.
|
|
48
|
+
//
|
|
49
|
+
// Read once and cached: an environment variable cannot change under a running
|
|
50
|
+
// process, and this is consulted on a path that runs whenever an app asks a
|
|
51
|
+
// question.
|
|
52
|
+
std::optional<int> scriptedDialogButton();
|
|
53
|
+
|
|
54
|
+
// `showAlert`, or the scripted answer when there is one.
|
|
55
|
+
//
|
|
56
|
+
// Everything that would put a modal dialog on screen goes through here rather
|
|
57
|
+
// than calling the platform directly, so the instrument covers the share
|
|
58
|
+
// picker's fallback as well as `Alert.alert` -- and covers them by *running*
|
|
59
|
+
// them: a scripted "Copy" still copies, because only the presentation is
|
|
60
|
+
// skipped.
|
|
61
|
+
//
|
|
62
|
+
// `dismiss` answers with the last button, which is where React Native's alerts
|
|
63
|
+
// and this project's share picker both put the cancelling choice.
|
|
64
|
+
void presentAlert(const AlertRequest &request, AlertCallback onButton);
|
|
65
|
+
|
|
66
|
+
// Which entry BASALT_TEST_MENU names, or nothing when it is unset. `dismiss`
|
|
67
|
+
// is -1, which is what closing a menu without choosing reports.
|
|
68
|
+
//
|
|
69
|
+
// A popup menu is the second thing an automated run cannot get past, and for a
|
|
70
|
+
// sharper reason than a dialog: on macOS `popUpMenuPositioningItem` runs the
|
|
71
|
+
// menu's own tracking loop on the main thread, so a menu nobody dismisses
|
|
72
|
+
// stops the process where it stands.
|
|
73
|
+
std::optional<int> scriptedMenuChoice();
|
|
74
|
+
|
|
75
|
+
// `showMenu`, or the scripted answer when there is one. Everything that would
|
|
76
|
+
// put a popup menu on screen goes through here rather than calling the platform
|
|
77
|
+
// directly, for the same reason `presentAlert` exists.
|
|
78
|
+
void presentMenu(const MenuRequest &request, MenuCallback onChosen);
|
|
79
|
+
|
|
80
|
+
// What BASALT_TEST_FILE_DIALOG names, or nothing when it is unset.
|
|
81
|
+
//
|
|
82
|
+
// `cancel` is the person pressing Cancel. Anything else is a list of paths
|
|
83
|
+
// separated by the platform's path separator -- `:` everywhere but Windows,
|
|
84
|
+
// where it is `;`, because a Windows path has a colon two characters in.
|
|
85
|
+
//
|
|
86
|
+
// A file dialog is the third thing an automated run cannot get past, and the
|
|
87
|
+
// one with the most to prove afterwards: what an app does with a path is the
|
|
88
|
+
// interesting part, and it cannot be reached at all without one.
|
|
89
|
+
std::optional<std::vector<std::string>> scriptedFileDialogPaths();
|
|
90
|
+
|
|
91
|
+
// `showFileDialog`, or the scripted answer when there is one. Everything that
|
|
92
|
+
// would put a file dialog on screen goes through here, for the same reason
|
|
93
|
+
// `presentAlert` exists.
|
|
94
|
+
void presentFileDialog(const FileDialogRequest &request, FileDialogCallback onDone);
|
|
95
|
+
|
|
96
|
+
} // namespace basalt
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#include "TestKeys.h"
|
|
2
|
+
|
|
3
|
+
#include <cstdlib>
|
|
4
|
+
#include <string>
|
|
5
|
+
#include <vector>
|
|
6
|
+
|
|
7
|
+
namespace basalt {
|
|
8
|
+
namespace {
|
|
9
|
+
|
|
10
|
+
std::vector<std::string> split(const std::string &text, char separator) {
|
|
11
|
+
std::vector<std::string> parts;
|
|
12
|
+
std::string current;
|
|
13
|
+
for (const char character : text) {
|
|
14
|
+
if (character == separator) {
|
|
15
|
+
parts.push_back(current);
|
|
16
|
+
current.clear();
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
current += character;
|
|
20
|
+
}
|
|
21
|
+
parts.push_back(current);
|
|
22
|
+
return parts;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
} // namespace
|
|
26
|
+
|
|
27
|
+
KeyCombination parseKeyPress(const std::string &spec) {
|
|
28
|
+
KeyCombination pressed;
|
|
29
|
+
if (spec.empty()) {
|
|
30
|
+
return pressed;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// The key first, then modifiers. Split on `+` from the left and take the first
|
|
34
|
+
// piece as the key, so a literal `+` as the key is `"+"` or `"++shift"` -- the
|
|
35
|
+
// first piece being empty is the one case where an empty piece means something.
|
|
36
|
+
const std::vector<std::string> pieces = split(spec, '+');
|
|
37
|
+
pressed.key = pieces.front();
|
|
38
|
+
for (size_t i = 1; i < pieces.size(); i++) {
|
|
39
|
+
const std::string &modifier = pieces[i];
|
|
40
|
+
if (modifier == "alt") {
|
|
41
|
+
pressed.modifiers.alt = true;
|
|
42
|
+
} else if (modifier == "ctrl") {
|
|
43
|
+
pressed.modifiers.ctrl = true;
|
|
44
|
+
} else if (modifier == "meta") {
|
|
45
|
+
pressed.modifiers.meta = true;
|
|
46
|
+
} else if (modifier == "shift") {
|
|
47
|
+
pressed.modifiers.shift = true;
|
|
48
|
+
} else if (modifier.empty() && pressed.key.empty()) {
|
|
49
|
+
// `"++shift"`: the key is a literal plus, and this empty piece is it.
|
|
50
|
+
pressed.key = "+";
|
|
51
|
+
}
|
|
52
|
+
// Anything else is ignored rather than refused. A typo in a modifier name
|
|
53
|
+
// gives a combination that matches nothing, which the scenario reports as a
|
|
54
|
+
// shortcut that did not fire -- and that is the honest symptom of a typo.
|
|
55
|
+
}
|
|
56
|
+
return pressed;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
std::vector<KeyCombination> scriptedKeyPresses() {
|
|
60
|
+
const char *value = std::getenv(kTestKeyVar);
|
|
61
|
+
if (value == nullptr || *value == '\0') {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
std::vector<KeyCombination> presses;
|
|
65
|
+
for (const std::string &spec : split(std::string(value), ';')) {
|
|
66
|
+
if (spec.empty()) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
KeyCombination pressed = parseKeyPress(spec);
|
|
70
|
+
if (pressed.key.empty()) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
presses.push_back(std::move(pressed));
|
|
74
|
+
}
|
|
75
|
+
return presses;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
} // namespace basalt
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// BASALT_TEST_KEY: press a key, without a keyboard.
|
|
2
|
+
//
|
|
3
|
+
// A real key press needs a window the display server considers focused, which an
|
|
4
|
+
// automated run does not reliably have on any of the three desktops -- the same
|
|
5
|
+
// reason `BASALT_TEST_TAP` exists and enters below the toolkit. So this enters
|
|
6
|
+
// after the platform mapping: it builds a `KeyCombination` directly and hands it
|
|
7
|
+
// to the host's own delivery, which is everything a real press does except the
|
|
8
|
+
// `NSEvent`, keyval or `WM_KEYDOWN` translation.
|
|
9
|
+
//
|
|
10
|
+
// **What it therefore does not test** is that translation, which is the half most
|
|
11
|
+
// likely to be wrong -- the three tables are each other's mirror and each one is
|
|
12
|
+
// unit-tested in its own host instead. Worth stating plainly because a scenario
|
|
13
|
+
// that passes here says the registry, the focus path and the round trip to
|
|
14
|
+
// JavaScript work, and says nothing about whether pressing the left arrow
|
|
15
|
+
// produces "ArrowLeft".
|
|
16
|
+
//
|
|
17
|
+
// ## The syntax
|
|
18
|
+
//
|
|
19
|
+
// BASALT_TEST_KEY="z+meta;ArrowLeft;m;A+shift"
|
|
20
|
+
//
|
|
21
|
+
// Presses separated by `;`, a key and its modifiers separated by `+`. The key is
|
|
22
|
+
// a W3C name and is taken literally, so `+` as a key is spelt by putting it
|
|
23
|
+
// first. Modifiers are `alt`, `ctrl`, `meta` and `shift`.
|
|
24
|
+
//
|
|
25
|
+
// Parsed here, once, because three hosts parsing it is three chances to disagree
|
|
26
|
+
// about what `z+meta` means -- and disagreeing quietly, since the wrong answer is
|
|
27
|
+
// a combination that matches nothing and a scenario that reports a shortcut as
|
|
28
|
+
// not firing.
|
|
29
|
+
|
|
30
|
+
#pragma once
|
|
31
|
+
|
|
32
|
+
#include "KeyEvents.h"
|
|
33
|
+
|
|
34
|
+
#include <string>
|
|
35
|
+
#include <vector>
|
|
36
|
+
|
|
37
|
+
namespace basalt {
|
|
38
|
+
|
|
39
|
+
inline constexpr const char *kTestKeyVar = "BASALT_TEST_KEY";
|
|
40
|
+
|
|
41
|
+
// The presses `BASALT_TEST_KEY` asks for, in order, or empty when it is unset.
|
|
42
|
+
// An entry with no key is skipped rather than refused: one malformed press should
|
|
43
|
+
// not lose the rest of a scripted sequence.
|
|
44
|
+
std::vector<KeyCombination> scriptedKeyPresses();
|
|
45
|
+
|
|
46
|
+
// The parser, exposed for its tests. `spec` is one press -- `"z+meta"` -- and the
|
|
47
|
+
// result has an empty key when it could not be read.
|
|
48
|
+
KeyCombination parseKeyPress(const std::string &spec);
|
|
49
|
+
|
|
50
|
+
} // namespace basalt
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#include "TestQuitFile.h"
|
|
2
|
+
|
|
3
|
+
#include <cstdlib>
|
|
4
|
+
#include <filesystem>
|
|
5
|
+
#include <optional>
|
|
6
|
+
#include <string>
|
|
7
|
+
|
|
8
|
+
namespace basalt {
|
|
9
|
+
|
|
10
|
+
std::optional<std::string> testQuitFilePath() {
|
|
11
|
+
static const std::optional<std::string> path = []() -> std::optional<std::string> {
|
|
12
|
+
const char *value = std::getenv(kTestQuitFileVar);
|
|
13
|
+
if (value == nullptr || *value == '\0') {
|
|
14
|
+
return std::nullopt;
|
|
15
|
+
}
|
|
16
|
+
return std::string(value);
|
|
17
|
+
}();
|
|
18
|
+
return path;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
bool testQuitFileAppeared() {
|
|
22
|
+
const std::optional<std::string> path = testQuitFilePath();
|
|
23
|
+
if (!path.has_value()) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
// The non-throwing overload. This runs on a timer on the UI thread, and a
|
|
27
|
+
// permission error or a vanished parent directory is a reason to keep waiting
|
|
28
|
+
// rather than to terminate the process from inside a timer callback.
|
|
29
|
+
std::error_code ignored;
|
|
30
|
+
return std::filesystem::exists(*path, ignored);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
} // namespace basalt
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Quitting, gracefully, at a moment the harness chooses.
|
|
2
|
+
//
|
|
3
|
+
// `BASALT_QUIT_AFTER_MS` is the other way out, and it is decided before the
|
|
4
|
+
// process starts. That is fine for a scenario whose length is known and wrong
|
|
5
|
+
// for one whose length is not: the Fast Refresh scenario waits for an edit to
|
|
6
|
+
// reach the window, which takes a second on a warm laptop and up to two and a
|
|
7
|
+
// half minutes on a cold CI machine, so no single budget is both quick and
|
|
8
|
+
// safe. It used a signal instead -- and signals are not portable. Windows has
|
|
9
|
+
// no SIGTERM, `TerminateProcess` cannot be handled, and every host writes its
|
|
10
|
+
// widget tree on the way out, so a killed host on Windows failed the assertion
|
|
11
|
+
// that reads that tree whether or not the thing under test had worked.
|
|
12
|
+
//
|
|
13
|
+
// So: `BASALT_TEST_QUIT_FILE=<path>`, and the host quits as soon as <path>
|
|
14
|
+
// exists, through exactly the shutdown `BASALT_QUIT_AFTER_MS` uses. The harness
|
|
15
|
+
// creates the file when it has what it came for. Nothing is read from the file
|
|
16
|
+
// and it may be empty; its existence is the whole message, which is why there
|
|
17
|
+
// is no race worth worrying about between writing it and seeing it.
|
|
18
|
+
//
|
|
19
|
+
// Not refusable, for the reason the timed quit is not: this is the harness
|
|
20
|
+
// ending the run rather than a person closing a window, and an app that
|
|
21
|
+
// intercepts its own close would otherwise refuse the harness and hang. The
|
|
22
|
+
// scenario for *that* feature is exactly such an app.
|
|
23
|
+
//
|
|
24
|
+
// Polled rather than watched. A file watcher is three different APIs across
|
|
25
|
+
// these hosts and each has its own failure mode on network and virtual
|
|
26
|
+
// filesystems; a quarter-second poll on a timer each host already runs costs
|
|
27
|
+
// nothing measurable and cannot fail to be delivered.
|
|
28
|
+
|
|
29
|
+
#pragma once
|
|
30
|
+
|
|
31
|
+
#include <optional>
|
|
32
|
+
#include <string>
|
|
33
|
+
|
|
34
|
+
namespace basalt {
|
|
35
|
+
|
|
36
|
+
// One spelling, in one place. Three hosts reading the variable by name is three
|
|
37
|
+
// chances to typo it, and a typo here does not fail: the host simply never
|
|
38
|
+
// quits, and the scenario reports a timeout somewhere unrelated.
|
|
39
|
+
inline constexpr const char *kTestQuitFileVar = "BASALT_TEST_QUIT_FILE";
|
|
40
|
+
|
|
41
|
+
// How often to look. Small enough that the wait is not noticeable next to the
|
|
42
|
+
// process teardown that follows it, large enough to be free.
|
|
43
|
+
inline constexpr unsigned kTestQuitFilePollMs = 250;
|
|
44
|
+
|
|
45
|
+
// The line all three hosts log when it fires, so that three transcripts read
|
|
46
|
+
// the same way. `BASALT_QUIT_AFTER_MS elapsed; quitting` had to be retrofitted
|
|
47
|
+
// onto Windows after a scenario asserted on a string only two hosts printed;
|
|
48
|
+
// this starts out shared instead.
|
|
49
|
+
inline constexpr const char *kTestQuitFileMessage = "BASALT_TEST_QUIT_FILE appeared; quitting";
|
|
50
|
+
|
|
51
|
+
// The path the instrument was pointed at, or nothing when it is off. Read once:
|
|
52
|
+
// the environment does not change under a running process, and each host calls
|
|
53
|
+
// this on a timer.
|
|
54
|
+
std::optional<std::string> testQuitFilePath();
|
|
55
|
+
|
|
56
|
+
// Whether that file exists now. False when the instrument is off, so a host can
|
|
57
|
+
// call it without checking first.
|
|
58
|
+
bool testQuitFileAppeared();
|
|
59
|
+
|
|
60
|
+
} // namespace basalt
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// Quitting when there is nothing left to wait for, rather than on a guess.
|
|
2
|
+
//
|
|
3
|
+
// `BASALT_QUIT_AFTER_MS` is a budget chosen before the process starts, and it
|
|
4
|
+
// has to be long enough for the slowest machine that will ever run it. So every
|
|
5
|
+
// scenario waits out the worst case on every machine: `Image.getSize` asks for
|
|
6
|
+
// three seconds and takes 3.7 of them, of which about 0.7 is real work.
|
|
7
|
+
//
|
|
8
|
+
// Two problems, and the smaller one is the time. The larger is that every such
|
|
9
|
+
// budget is a latent flake -- "should be long enough" is a guess that a loaded
|
|
10
|
+
// CI machine eventually falsifies, and when it does the scenario fails for a
|
|
11
|
+
// reason that has nothing to do with what it tests. One of those is already in
|
|
12
|
+
// docs/backlog/testing.md.
|
|
13
|
+
//
|
|
14
|
+
// `BASALT_QUIT_WHEN_SETTLED=<ms>` replaces the guess with a fact. The host quits
|
|
15
|
+
// <ms> after it has settled, where settled means:
|
|
16
|
+
//
|
|
17
|
+
// - the first mount has been applied -- React produced a tree and the host
|
|
18
|
+
// put it on screen. Recorded in MountingWalk::applyMutations, which is the
|
|
19
|
+
// one place all three hosts mount through, so this is one code site rather
|
|
20
|
+
// than three.
|
|
21
|
+
// - any scripted input has been delivered. Each host schedules its own and
|
|
22
|
+
// knows the total, and says so through noteScriptedInputEndsInMs.
|
|
23
|
+
//
|
|
24
|
+
// `BASALT_QUIT_AFTER_MS` stays as the backstop, so nothing gets *slower* and a
|
|
25
|
+
// host that never settles still ends. The two are complementary: one is the
|
|
26
|
+
// ceiling, this is the actual duration.
|
|
27
|
+
//
|
|
28
|
+
// The <ms> is a settle rather than a deadline, and it is small -- a few hundred
|
|
29
|
+
// milliseconds for React's next commit and the frame that draws it. It is still
|
|
30
|
+
// a guess, but it is a guess about one round trip instead of about a whole
|
|
31
|
+
// scenario, and quitting too early fails loudly: every scenario that opts in
|
|
32
|
+
// asserts on the dumped tree, so a tree that is not there yet is a failure and
|
|
33
|
+
// not a false pass. That direction is the whole reason this is safe to do.
|
|
34
|
+
|
|
35
|
+
#pragma once
|
|
36
|
+
|
|
37
|
+
#include <atomic>
|
|
38
|
+
#include <chrono>
|
|
39
|
+
#include <cstddef>
|
|
40
|
+
#include <cstdlib>
|
|
41
|
+
#include <optional>
|
|
42
|
+
#include <string>
|
|
43
|
+
|
|
44
|
+
namespace basalt {
|
|
45
|
+
|
|
46
|
+
inline constexpr const char *kQuitWhenSettledVar = "BASALT_QUIT_WHEN_SETTLED";
|
|
47
|
+
|
|
48
|
+
// One line, shared, for the reason core/TestQuitFile.h gives: three hosts
|
|
49
|
+
// spelling it themselves is three chances to differ, and a scenario that asserts
|
|
50
|
+
// on a string only two of them print fails on the third for no reason.
|
|
51
|
+
inline constexpr const char *kQuitWhenSettledMessage = "BASALT_QUIT_WHEN_SETTLED: settled; quitting";
|
|
52
|
+
|
|
53
|
+
inline long long testNowMs() {
|
|
54
|
+
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
55
|
+
std::chrono::steady_clock::now().time_since_epoch())
|
|
56
|
+
.count();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Zero means "not yet". A steady clock could in principle read zero at process
|
|
60
|
+
// start, which would make the first mount look like it had never happened, so
|
|
61
|
+
// the stored value is never allowed to be.
|
|
62
|
+
inline std::atomic<long long> &firstMountAtMsRef() {
|
|
63
|
+
static std::atomic<long long> value{0};
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// When the scripted input finishes, as an absolute time on the same clock. Zero
|
|
68
|
+
// when there is none, which is the common case.
|
|
69
|
+
inline std::atomic<long long> &scriptedInputEndsAtMsRef() {
|
|
70
|
+
static std::atomic<long long> value{0};
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Called from MountingWalk::applyMutations, on every transaction; only the first
|
|
75
|
+
// is kept. Cheap enough to sit on that path unconditionally -- one relaxed load
|
|
76
|
+
// and, once, one compare-exchange.
|
|
77
|
+
inline void noteMountApplied() {
|
|
78
|
+
if (firstMountAtMsRef().load(std::memory_order_relaxed) != 0) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
long long expected = 0;
|
|
82
|
+
const long long now = testNowMs();
|
|
83
|
+
firstMountAtMsRef().compare_exchange_strong(expected, now == 0 ? 1 : now);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Called by a host once, after it has scheduled whatever BASALT_TEST_TAP and its
|
|
87
|
+
// siblings asked for. `delayMs` is the total schedule, which is what each host
|
|
88
|
+
// already computes to lay the timers out.
|
|
89
|
+
inline void noteScriptedInputEndsInMs(long long delayMs) {
|
|
90
|
+
scriptedInputEndsAtMsRef().store(testNowMs() + (delayMs > 0 ? delayMs : 0));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Whether anything was asked to be delivered *into* the app. The hosts lay their
|
|
94
|
+
// scripted input out starting 1500ms in -- a tap needs a window to land on --
|
|
95
|
+
// and they report that total whether or not any of it was asked for, so without
|
|
96
|
+
// this a scenario that only mounts would wait out a schedule that is empty. It
|
|
97
|
+
// measured 2.96s against a 3s budget, which is to say no saving at all on
|
|
98
|
+
// exactly the scenarios this should help most.
|
|
99
|
+
//
|
|
100
|
+
// **A new instrument that a host puts on its scripted timer belongs in this
|
|
101
|
+
// list.** Left out, its scenario settles before the input lands -- which fails
|
|
102
|
+
// rather than passes, because everything that opts in asserts on the mounted
|
|
103
|
+
// tree, but it fails for a reason a long way from the change.
|
|
104
|
+
//
|
|
105
|
+
// Scheduled, not merely test-related. `BASALT_TEST_MENU` and
|
|
106
|
+
// `BASALT_TEST_DIALOG` are deliberately absent: they *answer* something when it
|
|
107
|
+
// appears, from core/TestDialog.cpp, and are never laid out on a host's timer --
|
|
108
|
+
// so they say nothing about when a schedule ends. Every scenario that uses them
|
|
109
|
+
// also drives the thing that shows the menu or the dialog, and that instrument
|
|
110
|
+
// is in this list. Including them was the first version, and the check below
|
|
111
|
+
// caught it: a list of "instruments to do with input" is not the same as a list
|
|
112
|
+
// of "instruments that delay the schedule", and only the second is what
|
|
113
|
+
// scriptedInputEndsAtMs needs.
|
|
114
|
+
//
|
|
115
|
+
// scripts/test_harness.py holds this against what the three host mains actually
|
|
116
|
+
// read, in both directions -- an instrument they schedule and this omits, and an
|
|
117
|
+
// entry here that no host knows about, which is what a rename leaves behind.
|
|
118
|
+
// native/tests/test_settle.cpp checks the contents.
|
|
119
|
+
inline const char *const *scriptedInputVars(std::size_t &count) {
|
|
120
|
+
static const char *const vars[] = {
|
|
121
|
+
"BASALT_TEST_TAP",
|
|
122
|
+
"BASALT_TEST_SECONDARY_TAP",
|
|
123
|
+
"BASALT_TEST_CLICK",
|
|
124
|
+
"BASALT_TEST_HOVER",
|
|
125
|
+
"BASALT_TEST_SCROLL",
|
|
126
|
+
"BASALT_TEST_FOCUS",
|
|
127
|
+
"BASALT_TEST_KEY",
|
|
128
|
+
"BASALT_TEST_TYPE",
|
|
129
|
+
"BASALT_TEST_DRAG",
|
|
130
|
+
"BASALT_TEST_DROP",
|
|
131
|
+
"BASALT_TEST_QUIT",
|
|
132
|
+
"BASALT_TEST_CLOSE_WINDOW",
|
|
133
|
+
};
|
|
134
|
+
count = sizeof(vars) / sizeof(vars[0]);
|
|
135
|
+
return vars;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
inline bool anyScriptedInputRequested() {
|
|
139
|
+
static const bool any = []() {
|
|
140
|
+
std::size_t count = 0;
|
|
141
|
+
const char *const *vars = scriptedInputVars(count);
|
|
142
|
+
for (std::size_t i = 0; i < count; i++) {
|
|
143
|
+
const char *value = std::getenv(vars[i]);
|
|
144
|
+
if (value != nullptr && *value != '\0') {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return false;
|
|
149
|
+
}();
|
|
150
|
+
return any;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
inline std::optional<unsigned> quitWhenSettledMs() {
|
|
154
|
+
static const std::optional<unsigned> ms = []() -> std::optional<unsigned> {
|
|
155
|
+
const char *value = std::getenv(kQuitWhenSettledVar);
|
|
156
|
+
if (value == nullptr || *value == '\0') {
|
|
157
|
+
return std::nullopt;
|
|
158
|
+
}
|
|
159
|
+
char *end = nullptr;
|
|
160
|
+
const unsigned long parsed = std::strtoul(value, &end, 10);
|
|
161
|
+
if (end == value) {
|
|
162
|
+
return std::nullopt;
|
|
163
|
+
}
|
|
164
|
+
return static_cast<unsigned>(parsed);
|
|
165
|
+
}();
|
|
166
|
+
return ms;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Whether everything worth waiting for has happened, and the settle has passed.
|
|
170
|
+
inline bool hasSettled() {
|
|
171
|
+
const std::optional<unsigned> settle = quitWhenSettledMs();
|
|
172
|
+
if (!settle.has_value()) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
const long long mounted = firstMountAtMsRef().load();
|
|
176
|
+
if (mounted == 0) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
// The later of the two, because a scenario that taps needs the tap delivered
|
|
180
|
+
// and a scenario that does not still needs its tree. Taking the maximum makes
|
|
181
|
+
// the two cases one expression rather than a branch that can be got wrong.
|
|
182
|
+
//
|
|
183
|
+
// Only when input was actually asked for. The hosts report their schedule
|
|
184
|
+
// total unconditionally, and that total is never zero -- it starts at 1500ms
|
|
185
|
+
// so a tap has a window to land on -- so an empty schedule would otherwise be
|
|
186
|
+
// waited out by every scenario that just mounts.
|
|
187
|
+
const long long scripted = anyScriptedInputRequested() ? scriptedInputEndsAtMsRef().load() : 0;
|
|
188
|
+
const long long ready = mounted > scripted ? mounted : scripted;
|
|
189
|
+
return testNowMs() - ready >= static_cast<long long>(*settle);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
} // namespace basalt
|