basalt-core 0.0.0-rc5 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/dist/cli/basalt.d.ts.map +1 -1
  2. package/dist/cli/basalt.js +18 -1
  3. package/dist/cli/basalt.js.map +1 -1
  4. package/dist/cli/bundle-cli.js +0 -0
  5. package/dist/cli/desktop.d.ts.map +1 -1
  6. package/dist/cli/desktop.js +53 -4
  7. package/dist/cli/desktop.js.map +1 -1
  8. package/dist/cli/packageApp.d.ts.map +1 -1
  9. package/dist/cli/packageApp.js +7 -1
  10. package/dist/cli/packageApp.js.map +1 -1
  11. package/dist/metro-config.d.ts.map +1 -1
  12. package/dist/metro-config.js +53 -5
  13. package/dist/metro-config.js.map +1 -1
  14. package/dist/src/KeyHandler.d.ts +1 -1
  15. package/dist/src/KeyHandler.d.ts.map +1 -1
  16. package/dist/src/KeyHandler.js.map +1 -1
  17. package/dist/src/index.d.ts +5 -0
  18. package/dist/src/index.d.ts.map +1 -1
  19. package/dist/src/overrides/Alert.d.ts +1 -1
  20. package/dist/src/overrides/Alert.js +1 -1
  21. package/dist/src/overrides/BaseViewConfig.d.ts +10 -3
  22. package/dist/src/overrides/BaseViewConfig.d.ts.map +1 -1
  23. package/dist/src/overrides/BaseViewConfig.js +21 -3
  24. package/dist/src/overrides/BaseViewConfig.js.map +1 -1
  25. package/dist/src/overrides/Button.d.ts +81 -0
  26. package/dist/src/overrides/Button.d.ts.map +1 -0
  27. package/dist/src/overrides/Button.js +109 -0
  28. package/dist/src/overrides/Button.js.map +1 -0
  29. package/dist/src/overrides/ScrollView.d.ts +25 -2
  30. package/dist/src/overrides/ScrollView.d.ts.map +1 -1
  31. package/dist/src/overrides/ScrollView.js +64 -7
  32. package/dist/src/overrides/ScrollView.js.map +1 -1
  33. package/dist/src/overrides/Settings.d.ts +81 -0
  34. package/dist/src/overrides/Settings.d.ts.map +1 -0
  35. package/dist/src/overrides/Settings.js +120 -0
  36. package/dist/src/overrides/Settings.js.map +1 -0
  37. package/dist/src/overrides/processDecelerationRate.d.ts +20 -0
  38. package/dist/src/overrides/processDecelerationRate.d.ts.map +1 -0
  39. package/dist/src/overrides/processDecelerationRate.js +31 -0
  40. package/dist/src/overrides/processDecelerationRate.js.map +1 -0
  41. package/dist/src/useContextMenu.d.ts +57 -6
  42. package/dist/src/useContextMenu.d.ts.map +1 -1
  43. package/dist/src/useContextMenu.js +34 -1
  44. package/dist/src/useContextMenu.js.map +1 -1
  45. package/dist/tsconfig.tsbuildinfo +1 -1
  46. package/native/CMakeLists.txt +85 -1
  47. package/native/cmake/Skia.cmake +29 -6
  48. package/native/core/ActionSheet.cpp +75 -0
  49. package/native/core/ActionSheet.h +90 -0
  50. package/native/core/AppStateHost.cpp +76 -0
  51. package/native/core/AppStateHost.h +68 -0
  52. package/native/core/BackgroundLayers.h +282 -0
  53. package/native/core/BlendModes.h +67 -0
  54. package/native/core/CoreModules.cpp +380 -11
  55. package/native/core/CoreModules.h +140 -4
  56. package/native/core/CrashHandler.cpp +246 -0
  57. package/native/core/CrashHandler.h +65 -0
  58. package/native/core/CursorNames.h +110 -0
  59. package/native/core/DevMenu.cpp +6 -5
  60. package/native/core/DeviceMetrics.cpp +87 -0
  61. package/native/core/DeviceMetrics.h +80 -0
  62. package/native/core/Filters.h +336 -0
  63. package/native/core/FontFitting.h +183 -0
  64. package/native/core/FontScaling.h +153 -0
  65. package/native/core/FontVariants.h +148 -0
  66. package/native/core/Gradients.h +523 -0
  67. package/native/core/ImageAnimation.h +109 -0
  68. package/native/core/ImageBytes.cpp +28 -1
  69. package/native/core/ImageBytes.h +17 -1
  70. package/native/core/ImageProgress.h +85 -0
  71. package/native/core/LabelRegistry.h +189 -0
  72. package/native/core/LegacyShadow.h +90 -0
  73. package/native/core/LiveRegions.h +114 -0
  74. package/native/core/MenuModule.cpp +35 -4
  75. package/native/core/MountingWalk.h +125 -2
  76. package/native/core/PlatformServices.h +187 -3
  77. package/native/core/SafeAreaComponent.cpp +115 -0
  78. package/native/core/SafeAreaComponent.h +105 -0
  79. package/native/core/ScreensComponent.cpp +201 -0
  80. package/native/core/ScreensComponent.h +216 -0
  81. package/native/core/ScrollBounds.cpp +8 -0
  82. package/native/core/ScrollBounds.h +14 -0
  83. package/native/core/ScrollIndicator.h +67 -0
  84. package/native/core/ScrollMomentum.cpp +24 -0
  85. package/native/core/ScrollMomentum.h +14 -0
  86. package/native/core/ScrollSnap.cpp +121 -12
  87. package/native/core/ScrollSnap.h +45 -0
  88. package/native/core/ScrollVisiblePosition.cpp +68 -0
  89. package/native/core/ScrollVisiblePosition.h +101 -0
  90. package/native/core/SettingsStore.cpp +199 -0
  91. package/native/core/SettingsStore.h +127 -0
  92. package/native/core/ShadowShape.h +38 -0
  93. package/native/core/SkiaViewComponent.cpp +11 -0
  94. package/native/core/SkiaViewComponent.h +50 -0
  95. package/native/core/SystemLocale.cpp +103 -0
  96. package/native/core/SystemLocale.h +86 -0
  97. package/native/core/TestDialog.cpp +45 -6
  98. package/native/core/TestDialog.h +15 -0
  99. package/native/core/TextAlignments.h +240 -0
  100. package/native/core/TextChecking.h +133 -0
  101. package/native/core/TextColors.h +83 -0
  102. package/native/core/TextDecorations.h +101 -0
  103. package/native/core/TextDirection.cpp +242 -0
  104. package/native/core/TextDirection.h +88 -0
  105. package/native/core/TextHighlight.h +30 -0
  106. package/native/core/TextSelection.cpp +97 -0
  107. package/native/core/TextSelection.h +161 -0
  108. package/native/core/TextShadows.h +96 -0
  109. package/native/core/TextVerticalAlign.h +33 -0
  110. package/native/core/UIManagerAccess.cpp +36 -0
  111. package/native/core/UIManagerAccess.h +28 -0
  112. package/native/core/WindowBoundsCache.cpp +58 -1
  113. package/native/core/WindowControl.h +24 -0
  114. package/native/core/WritingDirections.h +52 -0
  115. package/native/core/portability_probe.cpp +7 -0
  116. package/native/tests/RenamedFontFile.h +161 -0
  117. package/native/tests/TestHarness.cpp +18 -0
  118. package/native/tests/test_action_sheet.cpp +157 -0
  119. package/native/tests/test_app_state.cpp +60 -0
  120. package/native/tests/test_background_layers.cpp +281 -0
  121. package/native/tests/test_blend_modes.cpp +79 -0
  122. package/native/tests/test_blobs.cpp +1 -1
  123. package/native/tests/test_device_metrics.cpp +126 -0
  124. package/native/tests/test_filters.cpp +271 -0
  125. package/native/tests/test_font_fitting.cpp +172 -0
  126. package/native/tests/test_font_scaling.cpp +127 -0
  127. package/native/tests/test_font_variants.cpp +84 -0
  128. package/native/tests/test_gradients.cpp +390 -0
  129. package/native/tests/test_image_animation.cpp +105 -0
  130. package/native/tests/test_image_progress.cpp +94 -0
  131. package/native/tests/test_label_registry.cpp +262 -0
  132. package/native/tests/test_legacy_shadow.cpp +165 -0
  133. package/native/tests/test_live_regions.cpp +108 -0
  134. package/native/tests/test_menu_entries.cpp +192 -0
  135. package/native/tests/test_momentum.cpp +45 -0
  136. package/native/tests/test_scroll_bounds.cpp +42 -0
  137. package/native/tests/test_scroll_indicator.cpp +61 -0
  138. package/native/tests/test_scroll_snap.cpp +129 -0
  139. package/native/tests/test_scroll_visible_position.cpp +121 -0
  140. package/native/tests/test_settings_store.cpp +257 -0
  141. package/native/tests/test_shadow_shape.cpp +47 -0
  142. package/native/tests/test_system_locale.cpp +36 -0
  143. package/native/tests/test_text_alignment.cpp +299 -0
  144. package/native/tests/test_text_checking.cpp +31 -0
  145. package/native/tests/test_text_colors.cpp +92 -0
  146. package/native/tests/test_text_decorations.cpp +100 -0
  147. package/native/tests/test_text_selection.cpp +168 -0
  148. package/native/tests/test_text_shadows.cpp +132 -0
  149. package/package.json +4 -2
@@ -0,0 +1,299 @@
1
+ // Which edge a paragraph's text sits against, and which way it runs.
2
+ //
3
+ // The two halves of one question that three hosts were each answering locally
4
+ // and differently; `core/TextAlignments.h` holds the measurements that prompted
5
+ // this. What is asserted here is the table itself, plus Unicode's rule P2 over
6
+ // UTF-8 -- the direction a paragraph runs when the app said `natural` or said
7
+ // nothing, which is the common case and the one every relative alignment needs.
8
+ //
9
+ // The GTK suite holds `textStartsRightToLeft` against `pango_find_base_dir`
10
+ // over the same scripts, so the rule is checked against a real implementation
11
+ // of it rather than only against this file's expectations.
12
+
13
+ #include "TestHarness.h"
14
+
15
+ #include "TextAlignments.h"
16
+ #include "TextDirection.h"
17
+
18
+ #include <sstream>
19
+ #include <string>
20
+
21
+ using basalt::PhysicalTextAlignment;
22
+ using basalt::physicalTextAlignment;
23
+ using basalt::textStartsRightToLeft;
24
+ using facebook::react::TextAlignment;
25
+
26
+ namespace {
27
+
28
+ // UTF-8 as a source file can carry it without depending on the compiler's idea
29
+ // of an execution charset.
30
+ const std::string kHebrew = "\xd7\xa9\xd7\x9c\xd7\x95\xd7\x9d"; // Hebrew
31
+ const std::string kArabic = "\xd8\xa7\xd9\x84\xd8\xb3\xd9\x84\xd8\xa7\xd9\x85"; // Arabic
32
+ const std::string kThaana = "\xde\x8b\xde\xa8"; // Thaana
33
+ const std::string kNko = "\xdf\x92\xdf\x8a"; // NKo
34
+
35
+ const char *name(PhysicalTextAlignment alignment) {
36
+ switch (alignment) {
37
+ case PhysicalTextAlignment::Left:
38
+ return "left";
39
+ case PhysicalTextAlignment::Center:
40
+ return "center";
41
+ case PhysicalTextAlignment::Right:
42
+ return "right";
43
+ case PhysicalTextAlignment::Justified:
44
+ return "justified";
45
+ }
46
+ return "?";
47
+ }
48
+
49
+ // The common case: a paragraph whose text and whose app run the same way.
50
+ std::string edge(std::optional<TextAlignment> alignment, bool rightToLeft) {
51
+ return name(physicalTextAlignment(
52
+ alignment, basalt::TextDirections{.paragraph = rightToLeft, .relative = rightToLeft}));
53
+ }
54
+
55
+ #if BASALT_RN_MINOR >= 87
56
+ // The two apart: `text` is which way this paragraph's own text runs, `layout`
57
+ // is which way the app is laid out. They differ whenever an app shows Latin
58
+ // text on a mirrored desktop, or Hebrew in a left-to-right one.
59
+ //
60
+ // Behind the same guard as its callers: `start` and `end` arrived in 0.87, so
61
+ // on 0.86 every test that uses this is compiled out and an unused function is
62
+ // an error here.
63
+ std::string edgeIn(std::optional<TextAlignment> alignment, bool text, bool layout) {
64
+ return name(physicalTextAlignment(
65
+ alignment, basalt::TextDirections{.paragraph = text, .relative = layout}));
66
+ }
67
+ #endif
68
+
69
+ } // namespace
70
+
71
+ TEST(alignment_the_physical_two_are_the_same_in_both_directions) {
72
+ // `left` means the left of the box, in any paragraph. This is the row AppKit
73
+ // had wrong: it folded `left` into `natural`, so a right-to-left paragraph
74
+ // with `textAlign: 'left'` drew flush right.
75
+ EXPECT_EQ(edge(TextAlignment::Left, false), std::string("left"));
76
+ EXPECT_EQ(edge(TextAlignment::Left, true), std::string("left"));
77
+ EXPECT_EQ(edge(TextAlignment::Right, false), std::string("right"));
78
+ EXPECT_EQ(edge(TextAlignment::Right, true), std::string("right"));
79
+ EXPECT_EQ(edge(TextAlignment::Center, false), std::string("center"));
80
+ EXPECT_EQ(edge(TextAlignment::Center, true), std::string("center"));
81
+ }
82
+
83
+ TEST(alignment_natural_follows_the_direction) {
84
+ // React Native's default, and the answer for a paragraph that said nothing.
85
+ EXPECT_EQ(edge(TextAlignment::Natural, false), std::string("left"));
86
+ EXPECT_EQ(edge(TextAlignment::Natural, true), std::string("right"));
87
+ EXPECT_EQ(edge(std::nullopt, false), std::string("left"));
88
+ EXPECT_EQ(edge(std::nullopt, true), std::string("right"));
89
+ }
90
+
91
+ #if BASALT_RN_MINOR >= 87
92
+ TEST(alignment_start_and_end_are_the_edges_the_line_runs_between) {
93
+ // The entry this file closes: `end` is where a line finishes, which in a
94
+ // right-to-left paragraph is the left edge. All three hosts drew it flush
95
+ // right, two of them by folding it into `right`.
96
+ EXPECT_EQ(edge(TextAlignment::Start, false), std::string("left"));
97
+ EXPECT_EQ(edge(TextAlignment::Start, true), std::string("right"));
98
+ EXPECT_EQ(edge(TextAlignment::End, false), std::string("right"));
99
+ EXPECT_EQ(edge(TextAlignment::End, true), std::string("left"));
100
+ }
101
+ #endif
102
+
103
+ #if BASALT_RN_MINOR >= 87
104
+ TEST(alignment_start_and_end_follow_the_layout_rather_than_the_text) {
105
+ // The distinction this pair exists for, and backlog/text.md entry 14.
106
+ //
107
+ // `start` is the word every other relative thing in React Native uses --
108
+ // `paddingStart`, `marginEnd`, `borderStartColor` -- and Yoga resolves all
109
+ // of those against the layout direction. A paragraph that resolved it
110
+ // against its own text would face the other way from its own padding on a
111
+ // mirrored desktop showing Latin text, which is the case here.
112
+ //
113
+ // Upstream resolves it the same way: `RCTResolveTextAlignment` in
114
+ // `RCTAttributedTextUtils.mm` takes the layout direction and answers `Start`
115
+ // from it. The field it reads is `TextAttributes::layoutDirection`, filled
116
+ // by `ParagraphShadowNode::getContent` from Yoga.
117
+ EXPECT_EQ(edgeIn(TextAlignment::Start, /*text=*/false, /*layout=*/true), std::string("right"));
118
+ EXPECT_EQ(edgeIn(TextAlignment::End, /*text=*/false, /*layout=*/true), std::string("left"));
119
+ // And the other way round: Hebrew in an app that is not mirrored.
120
+ EXPECT_EQ(edgeIn(TextAlignment::Start, /*text=*/true, /*layout=*/false), std::string("left"));
121
+ EXPECT_EQ(edgeIn(TextAlignment::End, /*text=*/true, /*layout=*/false), std::string("right"));
122
+ }
123
+
124
+ TEST(alignment_natural_follows_the_text_rather_than_the_layout) {
125
+ // The other half of the same decision, and deliberately not symmetrical:
126
+ // `natural` is React Native's default and means "the edge this text starts
127
+ // from", so Hebrew in a left-to-right app is still flush right. Upstream's
128
+ // iOS half agrees -- `Natural` is the one value `RCTResolveTextAlignment`
129
+ // passes through untouched, for `NSParagraphStyle` to resolve from the
130
+ // paragraph's own writing direction.
131
+ EXPECT_EQ(edgeIn(TextAlignment::Natural, /*text=*/true, /*layout=*/false), std::string("right"));
132
+ EXPECT_EQ(edgeIn(std::nullopt, /*text=*/true, /*layout=*/false), std::string("right"));
133
+ EXPECT_EQ(edgeIn(TextAlignment::Natural, /*text=*/false, /*layout=*/true), std::string("left"));
134
+ EXPECT_EQ(edgeIn(std::nullopt, /*text=*/false, /*layout=*/true), std::string("left"));
135
+ }
136
+
137
+ TEST(alignment_a_paragraph_that_says_which_way_it_runs_decides_its_own_edges) {
138
+ // `textDirections` rather than the table: the question is which direction
139
+ // reaches `relative`, and the answer is the prop when there is one.
140
+ using facebook::react::WritingDirection;
141
+ const std::string latin = "hello";
142
+ // Said nothing: the layout decides, which is upstream's rule.
143
+ EXPECT(basalt::textDirections(std::nullopt, latin, true).relative);
144
+ EXPECT(!basalt::textDirections(std::nullopt, latin, false).relative);
145
+ EXPECT(basalt::textDirections(WritingDirection::Natural, latin, true).relative);
146
+ // Said so: the prop decides, in an app mirrored either way. `end` is the
147
+ // edge this paragraph's lines finish at, which is what it just declared.
148
+ EXPECT(basalt::textDirections(WritingDirection::RightToLeft, latin, false).relative);
149
+ EXPECT(!basalt::textDirections(WritingDirection::LeftToRight, latin, true).relative);
150
+ // And the text alone never decides it, which is the change: Hebrew in an app
151
+ // that is not mirrored has `start` on the left.
152
+ EXPECT(!basalt::textDirections(std::nullopt, kHebrew, false).relative);
153
+ EXPECT(basalt::textDirections(std::nullopt, kHebrew, false).paragraph);
154
+ }
155
+
156
+ TEST(alignment_left_and_right_stay_physical_in_a_mirrored_app) {
157
+ // Upstream's iOS half mirrors these two with the layout, so `textAlign:
158
+ // 'left'` draws flush right in a right-to-left app. This platform does not,
159
+ // and backlog/text.md records the divergence with the call that would make
160
+ // it upstream's. `left` is the left of the box on all three hosts, which is
161
+ // what the rest of this file is written to.
162
+ EXPECT_EQ(edgeIn(TextAlignment::Left, /*text=*/false, /*layout=*/true), std::string("left"));
163
+ EXPECT_EQ(edgeIn(TextAlignment::Right, /*text=*/false, /*layout=*/true), std::string("right"));
164
+ }
165
+ #endif
166
+
167
+ TEST(alignment_justified_is_not_an_edge_and_stays_itself) {
168
+ // It reaches the engine, which stretches the lines; the flush factor below is
169
+ // for the last line, which goes against the start edge.
170
+ EXPECT_EQ(edge(TextAlignment::Justified, false), std::string("justified"));
171
+ EXPECT_EQ(edge(TextAlignment::Justified, true), std::string("justified"));
172
+ EXPECT_NEAR(basalt::textFlushFactor(PhysicalTextAlignment::Justified, false), 0.0, 0.001);
173
+ EXPECT_NEAR(basalt::textFlushFactor(PhysicalTextAlignment::Justified, true), 1.0, 0.001);
174
+ }
175
+
176
+ TEST(alignment_flush_factors_are_the_three_core_text_takes) {
177
+ EXPECT_NEAR(basalt::textFlushFactor(PhysicalTextAlignment::Left, false), 0.0, 0.001);
178
+ EXPECT_NEAR(basalt::textFlushFactor(PhysicalTextAlignment::Center, false), 0.5, 0.001);
179
+ EXPECT_NEAR(basalt::textFlushFactor(PhysicalTextAlignment::Right, false), 1.0, 0.001);
180
+ // The direction does not enter into it: the edge has already been resolved.
181
+ EXPECT_NEAR(basalt::textFlushFactor(PhysicalTextAlignment::Left, true), 0.0, 0.001);
182
+ EXPECT_NEAR(basalt::textFlushFactor(PhysicalTextAlignment::Right, true), 1.0, 0.001);
183
+ }
184
+
185
+ TEST(direction_a_right_to_left_script_makes_a_right_to_left_paragraph) {
186
+ EXPECT(textStartsRightToLeft(kHebrew));
187
+ EXPECT(textStartsRightToLeft(kArabic));
188
+ EXPECT(textStartsRightToLeft(kThaana));
189
+ EXPECT(textStartsRightToLeft(kNko));
190
+ }
191
+
192
+ TEST(direction_latin_and_every_other_left_to_right_script_does_not) {
193
+ EXPECT(!textStartsRightToLeft("hello"));
194
+ // Latin with diacritics, Japanese, Greek and Cyrillic, as UTF-8 bytes: this
195
+ // file is compiled by MSVC too, which reads a source as the machine's code
196
+ // page unless it is told otherwise, and would re-encode a literal written in
197
+ // the script itself. The escapes say exactly what reaches the function.
198
+ // Split before the `e`: a hex escape in C++ is greedy, so "\x9fe" is one
199
+ // escape and out of range rather than an eszett followed by a letter.
200
+ EXPECT(!textStartsRightToLeft("Gr\xc3\xbc\xc3\x9f" "e")); // Gruesse
201
+ EXPECT(!textStartsRightToLeft("\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e")); // Japanese
202
+ EXPECT(!textStartsRightToLeft("\xce\x95\xce\xbb\xce\xbb\xce\xb7")); // Greek
203
+ EXPECT(!textStartsRightToLeft("\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2")); // Cyrillic
204
+ }
205
+
206
+ TEST(direction_the_first_strong_character_decides_and_the_rest_do_not) {
207
+ // Rule P2: the *first* strong character, not the majority and not the last.
208
+ EXPECT(!textStartsRightToLeft("hello " + kHebrew));
209
+ EXPECT(textStartsRightToLeft(kHebrew + " hello"));
210
+ }
211
+
212
+ TEST(direction_punctuation_digits_and_spaces_are_skipped) {
213
+ // What sits in front of a word in real text. A quotation mark deciding the
214
+ // direction of the sentence after it is the bug this skipping prevents.
215
+ EXPECT(textStartsRightToLeft(" \"" + kHebrew + "\""));
216
+ EXPECT(textStartsRightToLeft("(1) " + kHebrew));
217
+ EXPECT(!textStartsRightToLeft("(1) hello"));
218
+ EXPECT(!textStartsRightToLeft("123"));
219
+ EXPECT(!textStartsRightToLeft(""));
220
+ EXPECT(!textStartsRightToLeft(" "));
221
+ // An emoji is not a letter either.
222
+ EXPECT(textStartsRightToLeft("\xf0\x9f\x98\x80 " + kHebrew));
223
+ }
224
+
225
+ TEST(direction_arabic_indic_digits_are_not_strong) {
226
+ // U+0660..0669 are class AN, inside the Arabic block and not strong, so a
227
+ // paragraph that starts with them is not right-to-left by P2. Reading the
228
+ // block as a whole is the obvious mistake here.
229
+ const std::string digits = "\xd9\xa1\xd9\xa2\xd9\xa3"; // Arabic-Indic 123
230
+ EXPECT(!textStartsRightToLeft(digits));
231
+ EXPECT(!textStartsRightToLeft(digits + " hello"));
232
+ EXPECT(textStartsRightToLeft(digits + " " + kArabic));
233
+ // The extended Arabic-Indic digits, U+06F0..06F9, which are class EN.
234
+ const std::string extended = "\xdb\xb1\xdb\xb2"; // extended Arabic-Indic 12
235
+ EXPECT(!textStartsRightToLeft(extended + " hello"));
236
+ EXPECT(textStartsRightToLeft(extended + " " + kArabic));
237
+ }
238
+
239
+ TEST(direction_a_right_to_left_mark_is_strong_on_its_own) {
240
+ // U+200F is class R and is how a string with no letters at all can still ask
241
+ // for a right-to-left paragraph.
242
+ EXPECT(textStartsRightToLeft("\xe2\x80\x8f 123"));
243
+ }
244
+
245
+ TEST(direction_broken_utf8_is_skipped_rather_than_refused) {
246
+ // This decides an alignment. A string with a bad byte in it still has to lay
247
+ // out, and refusing would mean a paragraph that does not draw.
248
+ EXPECT(!textStartsRightToLeft("\xff\xfe"));
249
+ EXPECT(textStartsRightToLeft("\xff" + kHebrew));
250
+ // A truncated sequence at the end, which is what a cut-off string looks like.
251
+ EXPECT(!textStartsRightToLeft("\xd7"));
252
+ }
253
+
254
+ TEST(direction_the_prop_wins_when_the_app_set_one) {
255
+ using facebook::react::WritingDirection;
256
+ EXPECT(!basalt::paragraphIsRightToLeft(WritingDirection::LeftToRight, kHebrew));
257
+ EXPECT(basalt::paragraphIsRightToLeft(WritingDirection::RightToLeft, "hello"));
258
+ // `natural` and nothing are the same question, and both ask the text.
259
+ EXPECT(basalt::paragraphIsRightToLeft(WritingDirection::Natural, kHebrew));
260
+ EXPECT(basalt::paragraphIsRightToLeft(std::nullopt, kHebrew));
261
+ EXPECT(!basalt::paragraphIsRightToLeft(std::nullopt, "hello"));
262
+ }
263
+
264
+ // --- The other axis -----------------------------------------------------------
265
+ //
266
+ // `textAlignVertical`, which puts the paragraph somewhere in its own box, and
267
+ // which `verticalAlign` becomes in React Native's own JavaScript before any of
268
+ // this sees it. The arithmetic is three lines and one of them is the clamp,
269
+ // which is the line that matters: a paragraph taller than its box must start at
270
+ // the top rather than above it.
271
+
272
+ TEST(vertical_alignment_flush_factors_are_the_three_positions) {
273
+ using V = facebook::react::TextAlignmentVertical;
274
+ EXPECT_NEAR(basalt::textVerticalFlushFactor(V::Top), 0.0, 0.001);
275
+ EXPECT_NEAR(basalt::textVerticalFlushFactor(V::Center), 0.5, 0.001);
276
+ EXPECT_NEAR(basalt::textVerticalFlushFactor(V::Bottom), 1.0, 0.001);
277
+ // `auto`, and saying nothing, are both the top: that is what every engine
278
+ // does with no instruction.
279
+ EXPECT_NEAR(basalt::textVerticalFlushFactor(V::Auto), 0.0, 0.001);
280
+ EXPECT_NEAR(basalt::textVerticalFlushFactor(std::nullopt), 0.0, 0.001);
281
+ }
282
+
283
+ TEST(vertical_alignment_offsets_the_slack_in_the_box) {
284
+ // A 40pt paragraph in a 100pt box: 60 points of slack.
285
+ EXPECT_NEAR(basalt::textVerticalOffset(100.0, 40.0, 0.0), 0.0, 0.001);
286
+ EXPECT_NEAR(basalt::textVerticalOffset(100.0, 40.0, 0.5), 30.0, 0.001);
287
+ EXPECT_NEAR(basalt::textVerticalOffset(100.0, 40.0, 1.0), 60.0, 0.001);
288
+ }
289
+
290
+ TEST(vertical_alignment_a_paragraph_taller_than_its_box_starts_at_the_top) {
291
+ // The `numberOfLines` case: the box is the lines that fit and the text is all
292
+ // of them. A negative offset would push the first line out through the top
293
+ // edge, where nothing can scroll it back.
294
+ EXPECT_NEAR(basalt::textVerticalOffset(40.0, 100.0, 1.0), 0.0, 0.001);
295
+ EXPECT_NEAR(basalt::textVerticalOffset(40.0, 100.0, 0.5), 0.0, 0.001);
296
+ // And exactly as tall is no slack at all, which is every paragraph Yoga sized
297
+ // to its own content.
298
+ EXPECT_NEAR(basalt::textVerticalOffset(40.0, 40.0, 1.0), 0.0, 0.001);
299
+ }
@@ -0,0 +1,31 @@
1
+ // `spellCheck` and `autoCorrect` as three states. See core/TextChecking.h.
2
+ //
3
+ // One rule, and it is the whole reason this is shared: unset is not false. A
4
+ // field that says nothing wants the platform's default, and resolving that to
5
+ // `false` would opt every ordinary field out of spell checking without the app
6
+ // ever asking.
7
+
8
+ #include "TestHarness.h"
9
+
10
+ #include "TextChecking.h"
11
+
12
+ #include <sstream>
13
+ #include <string>
14
+
15
+ using basalt::TextCheckingFlag;
16
+ using basalt::textCheckingFlag;
17
+ using basalt::textCheckingName;
18
+
19
+ TEST(text_checking_unset_is_its_own_answer) {
20
+ EXPECT(textCheckingFlag(std::nullopt) == TextCheckingFlag::Unset);
21
+ EXPECT(textCheckingFlag(true) == TextCheckingFlag::On);
22
+ EXPECT(textCheckingFlag(false) == TextCheckingFlag::Off);
23
+ }
24
+
25
+ // The dump says nothing for a field that asked for nothing, rather than
26
+ // printing the platform's default as though the app had chosen it.
27
+ TEST(text_checking_an_unset_flag_has_no_name) {
28
+ EXPECT(textCheckingName(TextCheckingFlag::Unset) == nullptr);
29
+ EXPECT_EQ(std::string(textCheckingName(TextCheckingFlag::On)), std::string("on"));
30
+ EXPECT_EQ(std::string(textCheckingName(TextCheckingFlag::Off)), std::string("off"));
31
+ }
@@ -0,0 +1,92 @@
1
+ // A fragment's colours, and the `opacity` that multiplies them. See
2
+ // core/TextColors.h.
3
+ //
4
+ // Shared because the three rules are React Native's rather than a toolkit's:
5
+ // the default is opaque black and not the platform's label colour, opacity
6
+ // multiplies an alpha rather than replacing it, and a background nobody set is
7
+ // nothing rather than transparent black.
8
+
9
+ #include "TestHarness.h"
10
+
11
+ #include "TextColors.h"
12
+
13
+ #include <cmath>
14
+
15
+ using basalt::textBackgroundColor;
16
+ using basalt::textForegroundColor;
17
+ using basalt::textOpacity;
18
+ using facebook::react::ColorComponents;
19
+ using facebook::react::TextAttributes;
20
+
21
+ namespace {
22
+
23
+ // Not `near`: see test_font_scaling.cpp for the Windows macro that takes.
24
+ bool closeTo(float a, float b) { return std::fabs(a - b) < 0.01F; }
25
+
26
+ TextAttributes coloured(float red, float alpha) {
27
+ TextAttributes attributes;
28
+ attributes.foregroundColor =
29
+ facebook::react::colorFromComponents(ColorComponents{red, 0.0F, 0.0F, alpha});
30
+ return attributes;
31
+ }
32
+
33
+ } // namespace
34
+
35
+ // React Native's default, which is not the platform's.
36
+ TEST(text_colors_the_default_foreground_is_opaque_black) {
37
+ TextAttributes attributes;
38
+ const auto color = textForegroundColor(attributes);
39
+ EXPECT(closeTo(color.red, 0.0F));
40
+ EXPECT(closeTo(color.green, 0.0F));
41
+ EXPECT(closeTo(color.blue, 0.0F));
42
+ EXPECT(closeTo(color.alpha, 1.0F));
43
+ }
44
+
45
+ TEST(text_colors_opacity_multiplies_the_alpha_rather_than_replacing_it) {
46
+ TextAttributes attributes = coloured(1.0F, 0.5F);
47
+ attributes.opacity = 0.5;
48
+ // Half of a half, within a byte: a colour is packed to eight bits a channel
49
+ // on the way in.
50
+ EXPECT(closeTo(textForegroundColor(attributes).alpha, 0.25F));
51
+
52
+ // And it applies to the default black too, which an app setting opacity and
53
+ // no colour depends on.
54
+ TextAttributes bare;
55
+ bare.opacity = 0.25;
56
+ EXPECT(closeTo(textForegroundColor(bare).alpha, 0.25F));
57
+ }
58
+
59
+ TEST(text_colors_an_unset_opacity_changes_nothing) {
60
+ TextAttributes attributes = coloured(1.0F, 0.75F);
61
+ EXPECT(std::isnan(attributes.opacity));
62
+ EXPECT(closeTo(textForegroundColor(attributes).alpha, 0.75F));
63
+ EXPECT(closeTo(textOpacity(attributes), 1.0F));
64
+ }
65
+
66
+ // A stylesheet can ask for either, and neither is a thing an alpha can be.
67
+ TEST(text_colors_an_opacity_outside_zero_to_one_is_clamped) {
68
+ TextAttributes high = coloured(1.0F, 1.0F);
69
+ high.opacity = 4.0;
70
+ EXPECT(closeTo(textForegroundColor(high).alpha, 1.0F));
71
+
72
+ TextAttributes low = coloured(1.0F, 1.0F);
73
+ low.opacity = -2.0;
74
+ EXPECT(closeTo(textForegroundColor(low).alpha, 0.0F));
75
+ }
76
+
77
+ // Nothing, rather than transparent black: a host that drew it would paint a box
78
+ // behind text that asked for none.
79
+ TEST(text_colors_a_background_nobody_set_is_nothing) {
80
+ TextAttributes attributes;
81
+ EXPECT(!textBackgroundColor(attributes).has_value());
82
+
83
+ attributes.opacity = 0.5;
84
+ EXPECT(!textBackgroundColor(attributes).has_value());
85
+
86
+ attributes.backgroundColor =
87
+ facebook::react::colorFromComponents(ColorComponents{0.0F, 0.0F, 1.0F, 1.0F});
88
+ const auto background = textBackgroundColor(attributes);
89
+ EXPECT(background.has_value());
90
+ EXPECT(background.has_value() && closeTo(background->blue, 1.0F));
91
+ EXPECT(background.has_value() && closeTo(background->alpha, 0.5F));
92
+ }
@@ -0,0 +1,100 @@
1
+ // Which lines a fragment decorates with, and in what colour. See
2
+ // core/TextDecorations.h.
3
+ //
4
+ // Shared because the rules are decisions rather than drawing: a colour with no
5
+ // line draws nothing, an unset colour means the text's own, and the style is
6
+ // passed through for each toolkit to map to what it has.
7
+
8
+ #include "TestHarness.h"
9
+
10
+ #include "TextDecorations.h"
11
+
12
+ #include <cmath>
13
+
14
+ using basalt::textDecoration;
15
+ using facebook::react::ColorComponents;
16
+ using facebook::react::TextAttributes;
17
+ using facebook::react::TextDecorationLineType;
18
+ using facebook::react::TextDecorationStyle;
19
+
20
+ namespace {
21
+
22
+ // Not `near`: see test_font_scaling.cpp for the Windows macro that takes.
23
+ bool closeTo(float a, float b) { return std::fabs(a - b) < 0.01F; }
24
+
25
+ } // namespace
26
+
27
+ TEST(text_decoration_underline_strikethrough_and_both) {
28
+ TextAttributes attributes;
29
+ attributes.textDecorationLineType = TextDecorationLineType::Underline;
30
+ auto decoration = textDecoration(attributes);
31
+ EXPECT(decoration.has_value());
32
+ EXPECT(decoration.has_value() && decoration->underline);
33
+ EXPECT(decoration.has_value() && !decoration->strikethrough);
34
+
35
+ attributes.textDecorationLineType = TextDecorationLineType::Strikethrough;
36
+ decoration = textDecoration(attributes);
37
+ EXPECT(decoration.has_value() && !decoration->underline);
38
+ EXPECT(decoration.has_value() && decoration->strikethrough);
39
+
40
+ attributes.textDecorationLineType = TextDecorationLineType::UnderlineStrikethrough;
41
+ decoration = textDecoration(attributes);
42
+ EXPECT(decoration.has_value() && decoration->underline);
43
+ EXPECT(decoration.has_value() && decoration->strikethrough);
44
+ }
45
+
46
+ // The prop that decides whether there is a decoration at all.
47
+ TEST(text_decoration_needs_a_line_to_decorate) {
48
+ TextAttributes none;
49
+ EXPECT(!textDecoration(none).has_value());
50
+
51
+ none.textDecorationLineType = TextDecorationLineType::None;
52
+ EXPECT(!textDecoration(none).has_value());
53
+
54
+ // A colour and a style with no line is a stylesheet styling something it did
55
+ // not ask for, and drawing a line there would be inventing one.
56
+ TextAttributes styledOnly;
57
+ styledOnly.textDecorationStyle = TextDecorationStyle::Dotted;
58
+ styledOnly.textDecorationColor =
59
+ facebook::react::colorFromComponents(ColorComponents{1.0F, 0.0F, 0.0F, 1.0F});
60
+ EXPECT(!textDecoration(styledOnly).has_value());
61
+ }
62
+
63
+ TEST(text_decoration_takes_the_colour_it_was_given) {
64
+ TextAttributes attributes;
65
+ attributes.textDecorationLineType = TextDecorationLineType::Underline;
66
+ attributes.textDecorationColor =
67
+ facebook::react::colorFromComponents(ColorComponents{0.0F, 0.5F, 1.0F, 1.0F});
68
+ const auto decoration = textDecoration(attributes);
69
+ EXPECT(decoration.has_value() && decoration->hasColor);
70
+ EXPECT(decoration.has_value() && closeTo(decoration->blue, 1.0F));
71
+ EXPECT(decoration.has_value() && closeTo(decoration->red, 0.0F));
72
+ }
73
+
74
+ // Unset is not black: it is the text's own colour, which is what both toolkits
75
+ // draw when nothing names one.
76
+ TEST(text_decoration_an_unset_colour_is_the_texts_own) {
77
+ TextAttributes attributes;
78
+ attributes.textDecorationLineType = TextDecorationLineType::Underline;
79
+ const auto decoration = textDecoration(attributes);
80
+ EXPECT(decoration.has_value() && !decoration->hasColor);
81
+ }
82
+
83
+ TEST(text_decoration_carries_the_style_for_each_host_to_map) {
84
+ TextAttributes attributes;
85
+ attributes.textDecorationLineType = TextDecorationLineType::Underline;
86
+ // Unset is solid, which is CSS's default and React Native's.
87
+ EXPECT(textDecoration(attributes)->style == TextDecorationStyle::Solid);
88
+
89
+ // `wavy` is 0.87's; 0.86's enum stops at Dashed, and naming the member
90
+ // there does not compile. The same guard the hosts carry.
91
+ for (const auto style : {TextDecorationStyle::Solid, TextDecorationStyle::Double,
92
+ TextDecorationStyle::Dotted, TextDecorationStyle::Dashed,
93
+ #if BASALT_RN_MINOR >= 87
94
+ TextDecorationStyle::Wavy,
95
+ #endif
96
+ TextDecorationStyle::Solid}) {
97
+ attributes.textDecorationStyle = style;
98
+ EXPECT(textDecoration(attributes)->style == style);
99
+ }
100
+ }