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,149 @@
|
|
|
1
|
+
// Where a scrollbar's thumb goes. Arithmetic, so it can be asserted without a
|
|
2
|
+
// display -- which matters more here than usual, because a scrollbar is pure
|
|
3
|
+
// paint and paint is what these hosts cannot otherwise check.
|
|
4
|
+
|
|
5
|
+
#include "TestHarness.h"
|
|
6
|
+
|
|
7
|
+
#include "ScrollIndicator.h"
|
|
8
|
+
|
|
9
|
+
#include <sstream>
|
|
10
|
+
|
|
11
|
+
using basalt::kScrollIndicatorInset;
|
|
12
|
+
using basalt::kScrollIndicatorMinimumLength;
|
|
13
|
+
using basalt::ScrollIndicator;
|
|
14
|
+
using basalt::scrollIndicatorFor;
|
|
15
|
+
|
|
16
|
+
namespace {
|
|
17
|
+
|
|
18
|
+
TEST(indicator_content_that_fits_has_none) {
|
|
19
|
+
// A scrollbar for content that cannot scroll is noise, and every list shorter
|
|
20
|
+
// than its container would otherwise grow one.
|
|
21
|
+
EXPECT(!scrollIndicatorFor(400, 400, 0).visible);
|
|
22
|
+
EXPECT(!scrollIndicatorFor(400, 120, 0).visible);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
TEST(indicator_a_long_list_gets_a_short_thumb) {
|
|
26
|
+
// The thumb is the visible fraction: a quarter on screen, a quarter of the
|
|
27
|
+
// track.
|
|
28
|
+
const ScrollIndicator indicator = scrollIndicatorFor(400, 1600, 0);
|
|
29
|
+
EXPECT(indicator.visible);
|
|
30
|
+
const double track = 400 - 2 * kScrollIndicatorInset;
|
|
31
|
+
EXPECT(indicator.length > track * 0.24);
|
|
32
|
+
EXPECT(indicator.length < track * 0.26);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
TEST(indicator_a_very_long_list_still_has_a_grabbable_thumb) {
|
|
36
|
+
// Without a floor this is under a pixel, which stops reading as a position.
|
|
37
|
+
const ScrollIndicator indicator = scrollIndicatorFor(400, 400000, 0);
|
|
38
|
+
EXPECT_EQ(indicator.length, kScrollIndicatorMinimumLength);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
TEST(indicator_at_the_top_it_sits_at_the_top) {
|
|
42
|
+
const ScrollIndicator indicator = scrollIndicatorFor(400, 1600, 0);
|
|
43
|
+
EXPECT_EQ(indicator.offset, kScrollIndicatorInset);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
TEST(indicator_at_the_bottom_it_sits_at_the_bottom) {
|
|
47
|
+
// The end of the track, not past it: the thumb's far edge lands on the inset.
|
|
48
|
+
const ScrollIndicator indicator = scrollIndicatorFor(400, 1600, 1200);
|
|
49
|
+
const double track = 400 - 2 * kScrollIndicatorInset;
|
|
50
|
+
EXPECT_EQ(indicator.offset + indicator.length, kScrollIndicatorInset + track);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
TEST(indicator_halfway_is_halfway) {
|
|
54
|
+
const ScrollIndicator indicator = scrollIndicatorFor(400, 1600, 600);
|
|
55
|
+
const double track = 400 - 2 * kScrollIndicatorInset;
|
|
56
|
+
const double expected = kScrollIndicatorInset + 0.5 * (track - indicator.length);
|
|
57
|
+
EXPECT_EQ(indicator.offset, expected);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
TEST(indicator_an_overscroll_does_not_move_it_past_the_ends) {
|
|
61
|
+
// An elastic overscroll goes negative at the top and past the end at the
|
|
62
|
+
// bottom. A thumb that vanished there would flicker at exactly the moment
|
|
63
|
+
// somebody is looking at it.
|
|
64
|
+
const ScrollIndicator above = scrollIndicatorFor(400, 1600, -80);
|
|
65
|
+
EXPECT(above.visible);
|
|
66
|
+
EXPECT_EQ(above.offset, kScrollIndicatorInset);
|
|
67
|
+
|
|
68
|
+
const ScrollIndicator below = scrollIndicatorFor(400, 1600, 1600);
|
|
69
|
+
const double track = 400 - 2 * kScrollIndicatorInset;
|
|
70
|
+
EXPECT_EQ(below.offset + below.length, kScrollIndicatorInset + track);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
TEST(indicator_a_container_of_nothing_has_none) {
|
|
74
|
+
// A view that has not been laid out yet, which happens on the first mount.
|
|
75
|
+
EXPECT(!scrollIndicatorFor(0, 1600, 0).visible);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
} // namespace
|
|
79
|
+
|
|
80
|
+
// --------------------------------------------------------------------------
|
|
81
|
+
// Insets
|
|
82
|
+
// --------------------------------------------------------------------------
|
|
83
|
+
//
|
|
84
|
+
// The two kinds do different jobs, and the tests are separate for that reason:
|
|
85
|
+
// `contentInset` changes how far there is to go, and `scrollIndicatorInsets`
|
|
86
|
+
// changes only where the bar is drawn.
|
|
87
|
+
|
|
88
|
+
TEST(indicator_a_content_inset_leaves_room_at_the_top) {
|
|
89
|
+
// 100 of container, 400 of content, and 50 more to pull into above it. At the
|
|
90
|
+
// very top the offset is -50, and the thumb belongs at the start of the track.
|
|
91
|
+
const auto top =
|
|
92
|
+
basalt::scrollIndicatorFor(100.0, 400.0, -50.0, basalt::ScrollAxisInsets{50.0, 0.0});
|
|
93
|
+
EXPECT(top.visible);
|
|
94
|
+
EXPECT_NEAR(top.offset, basalt::kScrollIndicatorInset, 0.001);
|
|
95
|
+
|
|
96
|
+
// And at the bottom it still ends at the end.
|
|
97
|
+
const auto bottom =
|
|
98
|
+
basalt::scrollIndicatorFor(100.0, 400.0, 300.0, basalt::ScrollAxisInsets{50.0, 0.0});
|
|
99
|
+
EXPECT_NEAR(bottom.offset + bottom.length, 100.0 - basalt::kScrollIndicatorInset, 0.001);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Offset zero is the top of the *content*, which with a leading inset is no
|
|
103
|
+
// longer the top of the range -- so the thumb is a little way down. This is the
|
|
104
|
+
// case that was wrong before insets were read at all.
|
|
105
|
+
TEST(indicator_a_content_inset_moves_where_zero_sits) {
|
|
106
|
+
const auto without = basalt::scrollIndicatorFor(100.0, 400.0, 0.0);
|
|
107
|
+
const auto with =
|
|
108
|
+
basalt::scrollIndicatorFor(100.0, 400.0, 0.0, basalt::ScrollAxisInsets{50.0, 0.0});
|
|
109
|
+
EXPECT(with.offset > without.offset);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
TEST(indicator_an_indicator_inset_shortens_the_track) {
|
|
113
|
+
// A 400 view over 800 of content, so the thumb is about half the track and
|
|
114
|
+
// well clear of the minimum length -- which at a 100 view would have been
|
|
115
|
+
// the answer either way, and would have made this pass without proving
|
|
116
|
+
// anything.
|
|
117
|
+
const auto plain = basalt::scrollIndicatorFor(400.0, 800.0, 0.0);
|
|
118
|
+
const auto inset = basalt::scrollIndicatorFor(
|
|
119
|
+
400.0, 800.0, 0.0, basalt::ScrollAxisInsets{}, basalt::ScrollAxisInsets{20.0, 10.0});
|
|
120
|
+
|
|
121
|
+
// It starts below the inset...
|
|
122
|
+
EXPECT_NEAR(inset.offset, basalt::kScrollIndicatorInset + 20.0, 0.001);
|
|
123
|
+
// ...and a shorter track means a shorter thumb.
|
|
124
|
+
EXPECT(inset.length < plain.length);
|
|
125
|
+
|
|
126
|
+
// At the far end it stops short of the trailing inset.
|
|
127
|
+
const auto end = basalt::scrollIndicatorFor(
|
|
128
|
+
400.0, 800.0, 400.0, basalt::ScrollAxisInsets{}, basalt::ScrollAxisInsets{20.0, 10.0});
|
|
129
|
+
EXPECT_NEAR(end.offset + end.length, 400.0 - basalt::kScrollIndicatorInset - 10.0, 0.001);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// An indicator inset does not change what there is to scroll through, so it
|
|
133
|
+
// must not change the *fraction* the thumb represents beyond the track being
|
|
134
|
+
// shorter -- and it must not make the bar disappear.
|
|
135
|
+
TEST(indicator_an_indicator_inset_alone_does_not_hide_the_bar) {
|
|
136
|
+
const auto indicator = basalt::scrollIndicatorFor(
|
|
137
|
+
100.0, 400.0, 0.0, basalt::ScrollAxisInsets{}, basalt::ScrollAxisInsets{40.0, 40.0});
|
|
138
|
+
EXPECT(indicator.visible);
|
|
139
|
+
EXPECT(indicator.length > 0.0);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Content that fits exactly, with room to pull above it: there *is* somewhere
|
|
143
|
+
// to go, so there is something to indicate. Asking the content rather than the
|
|
144
|
+
// range would have called this unscrollable.
|
|
145
|
+
TEST(indicator_content_that_fits_but_can_be_pulled_still_has_a_bar) {
|
|
146
|
+
const auto indicator =
|
|
147
|
+
basalt::scrollIndicatorFor(100.0, 100.0, 0.0, basalt::ScrollAxisInsets{50.0, 0.0});
|
|
148
|
+
EXPECT(indicator.visible);
|
|
149
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Where a scroll comes to rest: paging, intervals and explicit offsets.
|
|
2
|
+
//
|
|
3
|
+
// Arithmetic, so it can be asserted without a display -- which is most of the
|
|
4
|
+
// reason it lives in core rather than in three hosts.
|
|
5
|
+
|
|
6
|
+
#include "TestHarness.h"
|
|
7
|
+
|
|
8
|
+
#include "ScrollSnap.h"
|
|
9
|
+
|
|
10
|
+
#include <sstream>
|
|
11
|
+
|
|
12
|
+
using basalt::ScrollSnapAlignment;
|
|
13
|
+
using basalt::ScrollSnapConfig;
|
|
14
|
+
using basalt::scrollSnapTarget;
|
|
15
|
+
|
|
16
|
+
namespace {
|
|
17
|
+
|
|
18
|
+
// A page-sized container over five pages of content.
|
|
19
|
+
constexpr double kContainer = 400.0;
|
|
20
|
+
constexpr double kContent = 2000.0;
|
|
21
|
+
|
|
22
|
+
double snapped(const ScrollSnapConfig &config, double offset, double velocity) {
|
|
23
|
+
const auto target = scrollSnapTarget(config, offset, velocity, kContainer, kContent);
|
|
24
|
+
return target ? *target : -1.0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
TEST(snap_nothing_configured_snaps_nothing) {
|
|
28
|
+
// A plain ScrollView must be unaffected: this runs at the end of every drag.
|
|
29
|
+
const ScrollSnapConfig none;
|
|
30
|
+
EXPECT(!scrollSnapTarget(none, 137, 0, kContainer, kContent).has_value());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
TEST(snap_paging_settles_on_the_nearest_page) {
|
|
34
|
+
ScrollSnapConfig config;
|
|
35
|
+
config.paging = true;
|
|
36
|
+
// Just past a page boundary, released: back to the page behind.
|
|
37
|
+
EXPECT_EQ(snapped(config, 420, 0), 400.0);
|
|
38
|
+
// Most of the way to the next: forward to it.
|
|
39
|
+
EXPECT_EQ(snapped(config, 790, 0), 800.0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
TEST(snap_a_flick_moves_a_whole_page) {
|
|
43
|
+
// The rule that makes a carousel feel like one: a flick carries even when the
|
|
44
|
+
// finger left nearer the page it started on.
|
|
45
|
+
ScrollSnapConfig config;
|
|
46
|
+
config.paging = true;
|
|
47
|
+
EXPECT_EQ(snapped(config, 410, 900), 800.0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
TEST(snap_a_flick_backwards_moves_back) {
|
|
51
|
+
ScrollSnapConfig config;
|
|
52
|
+
config.paging = true;
|
|
53
|
+
EXPECT_EQ(snapped(config, 790, -900), 400.0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
TEST(snap_an_interval_is_not_the_container) {
|
|
57
|
+
ScrollSnapConfig config;
|
|
58
|
+
config.interval = 250;
|
|
59
|
+
EXPECT_EQ(snapped(config, 260, 0), 250.0);
|
|
60
|
+
EXPECT_EQ(snapped(config, 380, 0), 500.0);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
TEST(snap_explicit_offsets_win_over_an_interval) {
|
|
64
|
+
// A list of points is a more specific statement than a spacing.
|
|
65
|
+
ScrollSnapConfig config;
|
|
66
|
+
config.interval = 250;
|
|
67
|
+
config.offsets = {0, 90, 610};
|
|
68
|
+
EXPECT_EQ(snapped(config, 100, 0), 90.0);
|
|
69
|
+
EXPECT_EQ(snapped(config, 500, 0), 610.0);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
TEST(snap_a_flick_across_explicit_offsets_moves_one_entry) {
|
|
73
|
+
ScrollSnapConfig config;
|
|
74
|
+
config.offsets = {0, 90, 610};
|
|
75
|
+
EXPECT_EQ(snapped(config, 95, 900), 610.0);
|
|
76
|
+
EXPECT_EQ(snapped(config, 600, -900), 90.0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
TEST(snap_never_past_the_end_of_the_content) {
|
|
80
|
+
// The furthest a scroll view can be is content minus container. A flick at
|
|
81
|
+
// the last page must not propose an offset beyond it, which would be a view
|
|
82
|
+
// showing nothing.
|
|
83
|
+
ScrollSnapConfig config;
|
|
84
|
+
config.paging = true;
|
|
85
|
+
EXPECT_EQ(snapped(config, 1600, 2000), kContent - kContainer);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
TEST(snap_never_before_the_start) {
|
|
89
|
+
ScrollSnapConfig config;
|
|
90
|
+
config.paging = true;
|
|
91
|
+
EXPECT_EQ(snapped(config, 10, -2000), 0.0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
TEST(snap_centre_alignment_lines_up_the_middle) {
|
|
95
|
+
// With a 400 container and a 400 interval, centre alignment puts a boundary
|
|
96
|
+
// at the middle of the view -- so the resting offsets sit half a container
|
|
97
|
+
// back from the start-aligned ones.
|
|
98
|
+
ScrollSnapConfig config;
|
|
99
|
+
config.interval = 400;
|
|
100
|
+
config.alignment = ScrollSnapAlignment::Center;
|
|
101
|
+
EXPECT_EQ(snapped(config, 210, 0), 200.0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
TEST(snap_content_shorter_than_the_container_stays_at_zero) {
|
|
105
|
+
ScrollSnapConfig config;
|
|
106
|
+
config.paging = true;
|
|
107
|
+
const auto target = scrollSnapTarget(config, 0, 900, 400, 200);
|
|
108
|
+
EXPECT(target.has_value());
|
|
109
|
+
EXPECT_EQ(*target, 0.0);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
} // namespace
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// When a host has nothing left to wait for.
|
|
2
|
+
//
|
|
3
|
+
// The decision, not the quitting: three hosts shut down three ways and each owns
|
|
4
|
+
// that. What is shared -- and so what can be got wrong once and be wrong
|
|
5
|
+
// everywhere -- is when they are *allowed* to.
|
|
6
|
+
//
|
|
7
|
+
// The interesting case is the one that was wrong first. The hosts report their
|
|
8
|
+
// scripted-input schedule whether or not any was asked for, and that total is
|
|
9
|
+
// never zero because a tap needs a window to land on, so a scenario that only
|
|
10
|
+
// mounts was waiting out an empty schedule: 2.96 seconds against a 3 second
|
|
11
|
+
// budget, which is no saving at all on exactly the scenarios this exists for.
|
|
12
|
+
|
|
13
|
+
#include "TestHarness.h"
|
|
14
|
+
|
|
15
|
+
#include "TestSettle.h"
|
|
16
|
+
|
|
17
|
+
#include <cstdlib>
|
|
18
|
+
#include <sstream>
|
|
19
|
+
#include <string>
|
|
20
|
+
|
|
21
|
+
namespace {
|
|
22
|
+
|
|
23
|
+
void setVariable(const char *name, const char *value) {
|
|
24
|
+
#ifdef _WIN32
|
|
25
|
+
_putenv_s(name, value);
|
|
26
|
+
#else
|
|
27
|
+
setenv(name, value, 1);
|
|
28
|
+
#endif
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
} // namespace
|
|
32
|
+
|
|
33
|
+
// First, because both `quitWhenSettledMs` and `anyScriptedInputRequested` cache
|
|
34
|
+
// on first read -- the environment does not change under a running process, and
|
|
35
|
+
// hosts ask on a timer. The ordering below is deliberate, not incidental.
|
|
36
|
+
//
|
|
37
|
+
// Zero settle so nothing here has to sleep: the question is whether the
|
|
38
|
+
// conditions are met, and the delay after them is arithmetic.
|
|
39
|
+
TEST(settle_reads_the_variable) {
|
|
40
|
+
setVariable(basalt::kQuitWhenSettledVar, "0");
|
|
41
|
+
EXPECT(basalt::quitWhenSettledMs().has_value());
|
|
42
|
+
EXPECT_EQ((int)*basalt::quitWhenSettledMs(), 0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
TEST(settle_waits_for_the_first_mount) {
|
|
46
|
+
// Nothing has mounted yet in this process -- no host here -- so however long
|
|
47
|
+
// has passed, there is nothing to have settled.
|
|
48
|
+
EXPECT(!basalt::hasSettled());
|
|
49
|
+
|
|
50
|
+
basalt::noteMountApplied();
|
|
51
|
+
EXPECT(basalt::hasSettled());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
TEST(settle_keeps_the_first_mount_not_the_last) {
|
|
55
|
+
const long long first = basalt::firstMountAtMsRef().load();
|
|
56
|
+
EXPECT(first != 0);
|
|
57
|
+
|
|
58
|
+
// A reload mounts again. Taking the later one would push the settle out on
|
|
59
|
+
// every commit, so an app that re-renders steadily would never settle at all.
|
|
60
|
+
basalt::noteMountApplied();
|
|
61
|
+
EXPECT_EQ((long long)basalt::firstMountAtMsRef().load(), first);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
TEST(settle_ignores_a_schedule_nobody_asked_for) {
|
|
65
|
+
// No input instrument is set in this process, so the schedule the hosts report
|
|
66
|
+
// is the bare 1500ms base and means nothing. Reporting an hour must not push
|
|
67
|
+
// the settle out by an hour.
|
|
68
|
+
basalt::noteScriptedInputEndsInMs(3600000);
|
|
69
|
+
EXPECT(!basalt::anyScriptedInputRequested());
|
|
70
|
+
EXPECT(basalt::hasSettled());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
TEST(settle_lists_the_instruments_that_delay_it) {
|
|
74
|
+
// The list is what decides whether a reported schedule is real. An input
|
|
75
|
+
// instrument missing from it settles its scenario before the input lands --
|
|
76
|
+
// which fails rather than passes, because everything that opts in asserts on
|
|
77
|
+
// the mounted tree, but it fails a long way from the change that caused it.
|
|
78
|
+
std::size_t count = 0;
|
|
79
|
+
const char *const *vars = basalt::scriptedInputVars(count);
|
|
80
|
+
EXPECT(count >= 11);
|
|
81
|
+
|
|
82
|
+
// Every instrument a host lays out on its scripted timer. Not
|
|
83
|
+
// BASALT_TEST_MENU or BASALT_TEST_DIALOG: those answer something when it
|
|
84
|
+
// appears rather than being scheduled, so they do not move the end of the
|
|
85
|
+
// schedule. scripts/test_harness.py holds the list against the hosts.
|
|
86
|
+
for (const char *needed : {"BASALT_TEST_TAP",
|
|
87
|
+
"BASALT_TEST_SECONDARY_TAP",
|
|
88
|
+
"BASALT_TEST_CLICK",
|
|
89
|
+
"BASALT_TEST_HOVER",
|
|
90
|
+
"BASALT_TEST_SCROLL",
|
|
91
|
+
"BASALT_TEST_FOCUS",
|
|
92
|
+
"BASALT_TEST_TYPE",
|
|
93
|
+
"BASALT_TEST_DRAG",
|
|
94
|
+
"BASALT_TEST_DROP",
|
|
95
|
+
"BASALT_TEST_QUIT",
|
|
96
|
+
"BASALT_TEST_CLOSE_WINDOW"}) {
|
|
97
|
+
bool found = false;
|
|
98
|
+
for (std::size_t i = 0; i < count; i++) {
|
|
99
|
+
if (std::string(vars[i]) == needed) {
|
|
100
|
+
found = true;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
EXPECT_EQ(std::string(found ? needed : "missing from scriptedInputVars"), std::string(needed));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
TEST(settle_message_is_shared) {
|
|
109
|
+
// For the reason core/TestQuitFile.h gives: BASALT_QUIT_AFTER_MS's line had to
|
|
110
|
+
// be retrofitted onto Windows after a scenario asserted on a string only two
|
|
111
|
+
// hosts printed.
|
|
112
|
+
EXPECT_EQ(std::string(basalt::kQuitWhenSettledVar), std::string("BASALT_QUIT_WHEN_SETTLED"));
|
|
113
|
+
EXPECT_EQ(std::string(basalt::kQuitWhenSettledMessage),
|
|
114
|
+
std::string("BASALT_QUIT_WHEN_SETTLED: settled; quitting"));
|
|
115
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// The share picker's two answers, which are the only part of sharing with a
|
|
2
|
+
// result that can be wrong.
|
|
3
|
+
//
|
|
4
|
+
// Core's, so compiled into every platform's suite. What is *not* here is
|
|
5
|
+
// `presentAlert`: it reads BASALT_TEST_DIALOG once into a static, because an
|
|
6
|
+
// environment variable cannot change under a running process, and a test that
|
|
7
|
+
// wanted two different values could not have them. That path is covered by the
|
|
8
|
+
// end-to-end suite, which can run the host twice.
|
|
9
|
+
|
|
10
|
+
#include "TestHarness.h"
|
|
11
|
+
|
|
12
|
+
#include "ShareFallback.h"
|
|
13
|
+
|
|
14
|
+
#include <sstream>
|
|
15
|
+
#include <string>
|
|
16
|
+
|
|
17
|
+
namespace {
|
|
18
|
+
|
|
19
|
+
basalt::ShareRequest requestOf(const char *message, const char *url, const char *title = "") {
|
|
20
|
+
basalt::ShareRequest request;
|
|
21
|
+
request.message = message;
|
|
22
|
+
request.url = url;
|
|
23
|
+
request.title = title;
|
|
24
|
+
return request;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
} // namespace
|
|
28
|
+
|
|
29
|
+
TEST(share_the_clipboard_gets_both_the_message_and_the_link) {
|
|
30
|
+
// A link with a sentence about it is what most calls to Share are, and
|
|
31
|
+
// dropping either half would be dropping half of what the app asked for.
|
|
32
|
+
EXPECT_EQ(basalt::shareClipboardText(requestOf("Look at this", "https://example.com")),
|
|
33
|
+
std::string("Look at this\nhttps://example.com"));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
TEST(share_the_clipboard_gets_whichever_half_was_given) {
|
|
37
|
+
EXPECT_EQ(basalt::shareClipboardText(requestOf("Look at this", "")), std::string("Look at this"));
|
|
38
|
+
EXPECT_EQ(basalt::shareClipboardText(requestOf("", "https://example.com")),
|
|
39
|
+
std::string("https://example.com"));
|
|
40
|
+
// React Native's own invariant refuses this before it reaches a platform, so
|
|
41
|
+
// it is only here to say that nothing here falls over if it ever does.
|
|
42
|
+
EXPECT_EQ(basalt::shareClipboardText(requestOf("", "")), std::string(""));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
TEST(share_a_mailto_carries_the_body_and_the_subject) {
|
|
46
|
+
const std::string url =
|
|
47
|
+
basalt::shareMailtoUrl(requestOf("Look at this", "https://example.com", "A link"));
|
|
48
|
+
EXPECT(url.rfind("mailto:?body=", 0) == 0);
|
|
49
|
+
EXPECT(url.find("subject=A%20link") != std::string::npos);
|
|
50
|
+
// The newline between the two halves, encoded rather than literal: a raw one
|
|
51
|
+
// ends the URL as far as most mail clients are concerned.
|
|
52
|
+
EXPECT(url.find("%0A") != std::string::npos);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
TEST(share_a_mailto_encodes_what_would_otherwise_change_its_meaning) {
|
|
56
|
+
const std::string url = basalt::shareMailtoUrl(requestOf("tea & biscuits?", ""));
|
|
57
|
+
// The one that actually bites: an unencoded `&` ends the body and starts a
|
|
58
|
+
// header, so half the message silently disappears from the draft.
|
|
59
|
+
EXPECT(url.find("%26") != std::string::npos);
|
|
60
|
+
EXPECT(url.find("&biscuits") == std::string::npos);
|
|
61
|
+
// And `?`, which would otherwise start the query a second time.
|
|
62
|
+
EXPECT(url.find("%3F") != std::string::npos);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
TEST(share_a_mailto_leaves_the_unreserved_characters_alone) {
|
|
66
|
+
// Encoding everything would be correct and unreadable. RFC 3986's unreserved
|
|
67
|
+
// set is what a URL may carry as-is.
|
|
68
|
+
const std::string url = basalt::shareMailtoUrl(requestOf("abcXYZ019-_.~", ""));
|
|
69
|
+
EXPECT(url.find("abcXYZ019-_.~") != std::string::npos);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
TEST(share_a_mailto_without_a_title_has_no_subject) {
|
|
73
|
+
const std::string url = basalt::shareMailtoUrl(requestOf("no title here", ""));
|
|
74
|
+
EXPECT(url.find("subject=") == std::string::npos);
|
|
75
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// The bundle's own URL, which is also where every asset is looked for.
|
|
2
|
+
//
|
|
3
|
+
// Core's, so compiled into every platform's suite -- and the one thing here
|
|
4
|
+
// worth testing hard is the shape of the string rather than its contents,
|
|
5
|
+
// because React Native takes it apart with string operations that are silent
|
|
6
|
+
// when they fail.
|
|
7
|
+
//
|
|
8
|
+
// `resolveAssetSource` finds the directory beside the bundle with
|
|
9
|
+
// `scriptURL.lastIndexOf('/')`. A Windows path has no forward slash in it, so
|
|
10
|
+
// that is -1, the directory comes out empty, and every `require()`d image
|
|
11
|
+
// resolves to `file://assets/...` -- which opens nothing and says nothing. That
|
|
12
|
+
// is what these assertions are for.
|
|
13
|
+
|
|
14
|
+
#include "TestHarness.h"
|
|
15
|
+
|
|
16
|
+
#include "SourceCodeModule.h"
|
|
17
|
+
|
|
18
|
+
#include <sstream>
|
|
19
|
+
#include <string>
|
|
20
|
+
|
|
21
|
+
namespace {
|
|
22
|
+
|
|
23
|
+
// Where the directory ends, which is the only thing React Native asks of this
|
|
24
|
+
// string other than its `file://` prefix.
|
|
25
|
+
std::string directoryOf(const std::string &url) {
|
|
26
|
+
const std::size_t slash = url.rfind('/');
|
|
27
|
+
return slash == std::string::npos ? std::string() : url.substr(0, slash + 1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
} // namespace
|
|
31
|
+
|
|
32
|
+
TEST(sourcecode_a_bundle_url_is_a_file_url_with_a_directory_in_it) {
|
|
33
|
+
const std::string url = basalt::scriptURLFor("build/main.jsbundle.js", false, "", 0, "", "linux");
|
|
34
|
+
|
|
35
|
+
EXPECT(url.rfind("file:///", 0) == 0);
|
|
36
|
+
// Three slashes and then something: `file:///` alone would mean the
|
|
37
|
+
// directory is the filesystem root, which is what an empty path looks like.
|
|
38
|
+
EXPECT(url.size() > std::string("file:///").size());
|
|
39
|
+
// A directory that is more than the scheme. This is the assertion that fails
|
|
40
|
+
// on a path with no forward slashes in it.
|
|
41
|
+
EXPECT(directoryOf(url).size() > std::string("file:///").size());
|
|
42
|
+
EXPECT(url.find("main.jsbundle.js") != std::string::npos);
|
|
43
|
+
// No backslashes, on any platform: a Windows path reaches JavaScript as a URL
|
|
44
|
+
// and is taken apart as one.
|
|
45
|
+
EXPECT(url.find('\\') == std::string::npos);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
TEST(sourcecode_a_relative_bundle_path_is_made_absolute) {
|
|
49
|
+
const std::string url = basalt::scriptURLFor("main.jsbundle.js", false, "", 0, "", "macos");
|
|
50
|
+
// `file://main.jsbundle.js` would be a URI with an authority and no path, and
|
|
51
|
+
// the directory beside it would be nothing at all.
|
|
52
|
+
EXPECT(url.rfind("file:///", 0) == 0);
|
|
53
|
+
EXPECT(directoryOf(url).size() > std::string("file:///").size());
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
TEST(sourcecode_dev_mode_reports_the_server_rather_than_the_file) {
|
|
57
|
+
const std::string url = basalt::scriptURLFor("build/main.jsbundle.js", true, "localhost", 8081, "index", "windows");
|
|
58
|
+
// Shaped like the URL the dev server served, because React Native recovers
|
|
59
|
+
// the server's address by stripping the path off this -- and assets then
|
|
60
|
+
// resolve to that server, with nothing copied anywhere.
|
|
61
|
+
EXPECT_EQ(url, std::string("http://localhost:8081/index.bundle?platform=windows&dev=true"));
|
|
62
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// Being asked before a window closes: the bookkeeping under it.
|
|
2
|
+
//
|
|
3
|
+
// The interesting parts of this feature are in three hosts and can only be
|
|
4
|
+
// answered end to end, but what decides whether a close is refused is a set of
|
|
5
|
+
// surface ids and a listener, and those are portable and cheap to be sure of.
|
|
6
|
+
//
|
|
7
|
+
// Worth asserting rather than assuming, because both ways it can be wrong are
|
|
8
|
+
// silent. A window that stays in the set after it closes marks whichever window
|
|
9
|
+
// is given that id next -- a window nobody asked to guard that refuses to
|
|
10
|
+
// close, which looks like a stuck window manager. And a listener left holding a
|
|
11
|
+
// module's emitter after the module has gone is a use-after-free reached from a
|
|
12
|
+
// close button.
|
|
13
|
+
|
|
14
|
+
#include "TestHarness.h"
|
|
15
|
+
|
|
16
|
+
#include "WindowHost.h"
|
|
17
|
+
|
|
18
|
+
#include <sstream>
|
|
19
|
+
#include <vector>
|
|
20
|
+
|
|
21
|
+
using facebook::react::SurfaceId;
|
|
22
|
+
|
|
23
|
+
namespace {
|
|
24
|
+
|
|
25
|
+
TEST(close_a_window_is_not_intercepted_by_default) {
|
|
26
|
+
// The default matters more than it looks: every window that never asks for
|
|
27
|
+
// this has to close exactly as it did before, and a close path that waits on
|
|
28
|
+
// JavaScript to say "go ahead" would be a window that does not close while
|
|
29
|
+
// the runtime is busy.
|
|
30
|
+
EXPECT(!basalt::hostWindowCloseIntercepted(4242));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
TEST(close_interception_is_per_window) {
|
|
34
|
+
basalt::setHostWindowCloseIntercepted(11, true);
|
|
35
|
+
EXPECT(basalt::hostWindowCloseIntercepted(11));
|
|
36
|
+
EXPECT(!basalt::hostWindowCloseIntercepted(12));
|
|
37
|
+
basalt::setHostWindowCloseIntercepted(11, false);
|
|
38
|
+
EXPECT(!basalt::hostWindowCloseIntercepted(11));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
TEST(close_a_window_that_closed_stops_being_intercepted) {
|
|
42
|
+
// Surface ids are not reused today. Relying on that would be relying on it:
|
|
43
|
+
// the cost of forgetting is nothing and the cost of remembering is a window
|
|
44
|
+
// that refuses to close for a reason nobody can find.
|
|
45
|
+
basalt::setHostWindowCloseIntercepted(21, true);
|
|
46
|
+
basalt::setHostWindowClosedListener(nullptr);
|
|
47
|
+
basalt::hostWindowClosed(21);
|
|
48
|
+
EXPECT(!basalt::hostWindowCloseIntercepted(21));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
TEST(close_a_request_reaches_the_listener_with_the_window) {
|
|
52
|
+
std::vector<SurfaceId> asked;
|
|
53
|
+
basalt::setHostWindowCloseRequestListener([&asked](SurfaceId id) { asked.push_back(id); });
|
|
54
|
+
|
|
55
|
+
basalt::hostWindowCloseRequested(31);
|
|
56
|
+
basalt::hostWindowCloseRequested(32);
|
|
57
|
+
|
|
58
|
+
EXPECT_EQ((long)asked.size(), 2L);
|
|
59
|
+
EXPECT_EQ((long)asked[0], 31L);
|
|
60
|
+
EXPECT_EQ((long)asked[1], 32L);
|
|
61
|
+
|
|
62
|
+
// Cleared, the way the module clears it on the way out. A listener that
|
|
63
|
+
// outlives the module holding its emitter is a use-after-free with a close
|
|
64
|
+
// button in front of it.
|
|
65
|
+
basalt::setHostWindowCloseRequestListener(nullptr);
|
|
66
|
+
basalt::hostWindowCloseRequested(33);
|
|
67
|
+
EXPECT_EQ((long)asked.size(), 2L);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
TEST(close_a_request_with_nobody_listening_is_not_a_crash) {
|
|
71
|
+
// The order the host and the module start in is not something a close button
|
|
72
|
+
// waits for.
|
|
73
|
+
basalt::setHostWindowCloseRequestListener(nullptr);
|
|
74
|
+
basalt::hostWindowCloseRequested(41);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
} // namespace
|
|
78
|
+
|
|
79
|
+
// --- Refusing to quit -------------------------------------------------------
|
|
80
|
+
//
|
|
81
|
+
// The same seam one level up: one application rather than one window. See
|
|
82
|
+
// core/WindowHost.h for why both are a flag the host reads synchronously
|
|
83
|
+
// rather than a callback it waits on.
|
|
84
|
+
|
|
85
|
+
TEST(quit_is_not_intercepted_until_somebody_asks) {
|
|
86
|
+
// The default matters more here than in the window case: every application
|
|
87
|
+
// that never registers a handler must quit exactly as it did before, and
|
|
88
|
+
// there is only one flag to get wrong.
|
|
89
|
+
basalt::setHostQuitIntercepted(false);
|
|
90
|
+
EXPECT(!basalt::hostQuitIntercepted());
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
TEST(quit_interception_is_one_flag_for_the_application) {
|
|
94
|
+
basalt::setHostQuitIntercepted(true);
|
|
95
|
+
EXPECT(basalt::hostQuitIntercepted());
|
|
96
|
+
basalt::setHostQuitIntercepted(false);
|
|
97
|
+
EXPECT(!basalt::hostQuitIntercepted());
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
TEST(quit_a_request_reaches_the_listener) {
|
|
101
|
+
int asked = 0;
|
|
102
|
+
basalt::setHostQuitRequestListener([&asked]() { asked++; });
|
|
103
|
+
|
|
104
|
+
basalt::hostQuitRequested();
|
|
105
|
+
basalt::hostQuitRequested();
|
|
106
|
+
EXPECT_EQ((long)asked, 2L);
|
|
107
|
+
|
|
108
|
+
// Cleared the way the module clears it on the way out, for the same reason
|
|
109
|
+
// the close listener is: one that outlives the module holding its emitter
|
|
110
|
+
// is a use-after-free with a menu item in front of it.
|
|
111
|
+
basalt::setHostQuitRequestListener(nullptr);
|
|
112
|
+
basalt::hostQuitRequested();
|
|
113
|
+
EXPECT_EQ((long)asked, 2L);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
TEST(quit_a_request_with_nobody_listening_is_not_a_crash) {
|
|
117
|
+
basalt::setHostQuitRequestListener(nullptr);
|
|
118
|
+
basalt::hostQuitRequested();
|
|
119
|
+
}
|