basalt-win32 0.0.0-rc5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/cli/runWindows.js +35 -0
  3. package/cli/shared.js +75 -0
  4. package/native/CMakeLists.txt +398 -0
  5. package/native/tests/test_win32_accessibility.cpp +271 -0
  6. package/native/tests/test_win32_clipboard.cpp +121 -0
  7. package/native/tests/test_win32_focus.cpp +195 -0
  8. package/native/tests/test_win32_hittest.cpp +410 -0
  9. package/native/tests/test_win32_image.cpp +239 -0
  10. package/native/tests/test_win32_imageloader.cpp +208 -0
  11. package/native/tests/test_win32_input.cpp +361 -0
  12. package/native/tests/test_win32_key_events.cpp +84 -0
  13. package/native/tests/test_win32_mounting.cpp +410 -0
  14. package/native/tests/test_win32_paint.cpp +505 -0
  15. package/native/tests/test_win32_scrollview.cpp +385 -0
  16. package/native/tests/test_win32_text.cpp +258 -0
  17. package/native/tests/test_win32_textinput.cpp +609 -0
  18. package/native/tests/test_win32_titlebar.cpp +111 -0
  19. package/native/tests/test_win32_titlebar_actions.cpp +62 -0
  20. package/native/tests/test_win32_view.cpp +222 -0
  21. package/native/win32/ColorSchemeWin32.cpp +56 -0
  22. package/native/win32/ComponentRegistryWin32.cpp +124 -0
  23. package/native/win32/DirectWriteLayout.cpp +116 -0
  24. package/native/win32/DirectWriteLayout.h +36 -0
  25. package/native/win32/DirectWriteLayoutManager.cpp +124 -0
  26. package/native/win32/FontRegistryDirectWrite.cpp +117 -0
  27. package/native/win32/PlatformServicesWin32.cpp +643 -0
  28. package/native/win32/RnWin32Accessible.cpp +258 -0
  29. package/native/win32/RnWin32Accessible.h +91 -0
  30. package/native/win32/RnWin32Image.cpp +265 -0
  31. package/native/win32/RnWin32Image.h +93 -0
  32. package/native/win32/RnWin32TextLayout.cpp +310 -0
  33. package/native/win32/RnWin32TextLayout.h +160 -0
  34. package/native/win32/RnWin32View.cpp +1281 -0
  35. package/native/win32/RnWin32View.h +506 -0
  36. package/native/win32/Win32AnimationChoreographer.cpp +76 -0
  37. package/native/win32/Win32AnimationChoreographer.h +61 -0
  38. package/native/win32/Win32Clip.h +173 -0
  39. package/native/win32/Win32DropTarget.cpp +567 -0
  40. package/native/win32/Win32DropTarget.h +42 -0
  41. package/native/win32/Win32Focus.cpp +262 -0
  42. package/native/win32/Win32Focus.h +109 -0
  43. package/native/win32/Win32ImageLoader.cpp +167 -0
  44. package/native/win32/Win32ImageLoader.h +104 -0
  45. package/native/win32/Win32KeyEvents.cpp +126 -0
  46. package/native/win32/Win32KeyEvents.h +20 -0
  47. package/native/win32/Win32MenuBar.cpp +352 -0
  48. package/native/win32/Win32MenuBar.h +16 -0
  49. package/native/win32/Win32MountingManager.cpp +727 -0
  50. package/native/win32/Win32MountingManager.h +203 -0
  51. package/native/win32/Win32Packaging.cpp +161 -0
  52. package/native/win32/Win32Packaging.h +56 -0
  53. package/native/win32/Win32RunLoopObserver.cpp +48 -0
  54. package/native/win32/Win32RunLoopObserver.h +42 -0
  55. package/native/win32/Win32ScrollView.cpp +515 -0
  56. package/native/win32/Win32ScrollView.h +208 -0
  57. package/native/win32/Win32SkiaContext.cpp +221 -0
  58. package/native/win32/Win32SkiaContext.h +53 -0
  59. package/native/win32/Win32SkiaModule.cpp +80 -0
  60. package/native/win32/Win32SkiaModule.h +50 -0
  61. package/native/win32/Win32SkiaPeer.cpp +358 -0
  62. package/native/win32/Win32SkiaPeer.h +66 -0
  63. package/native/win32/Win32SkiaUnicode.cpp +42 -0
  64. package/native/win32/Win32Snapshot.cpp +201 -0
  65. package/native/win32/Win32Snapshot.h +66 -0
  66. package/native/win32/Win32Strings.h +54 -0
  67. package/native/win32/Win32TextInput.cpp +811 -0
  68. package/native/win32/Win32TextInput.h +232 -0
  69. package/native/win32/Win32TitleBar.cpp +562 -0
  70. package/native/win32/Win32TitleBar.h +146 -0
  71. package/native/win32/Win32TitleBarLayout.cpp +46 -0
  72. package/native/win32/Win32TitleBarLayout.h +58 -0
  73. package/native/win32/Win32TouchDispatcher.cpp +443 -0
  74. package/native/win32/Win32TouchDispatcher.h +153 -0
  75. package/native/win32/Win32UiThread.h +27 -0
  76. package/native/win32/Win32WindowControl.cpp +329 -0
  77. package/native/win32/Win32WindowModule.cpp +209 -0
  78. package/native/win32/Win32WindowModule.h +72 -0
  79. package/native/win32/demo_layout_win32.cpp +232 -0
  80. package/native/win32/main_win32.cpp +2481 -0
  81. package/native/win32/mount_harness_win32.cpp +316 -0
  82. package/package.json +26 -0
  83. package/react-native.config.js +22 -0
@@ -0,0 +1,410 @@
1
+ // Tests for Win32MountingManager: does a stream of ShadowViewMutations produce
2
+ // the view tree it describes?
3
+ //
4
+ // Deliberately the same nine questions `tests/test_mounting.cpp` asks of GTK
5
+ // and `tests/test_appkit_mounting.mm` asks of AppKit, in the same order, with
6
+ // the same tags and frames. All three managers share their walk, so most of
7
+ // this is asking whether the sharing actually holds -- and the day it stops,
8
+ // these fail here rather than in an app.
9
+ //
10
+ // One is new and is about this platform specifically:
11
+ // `win32_delete_frees_the_view` exists because Windows is the first of the
12
+ // three where the registry's ownership is written out rather than delegated to
13
+ // a refcount or to ARC.
14
+
15
+ #include "TestHarness.h"
16
+ #include "TreeDump.h"
17
+
18
+ #include "Win32MountingManager.h"
19
+
20
+ #include <react/renderer/components/view/ViewProps.h>
21
+ #include <react/renderer/graphics/Color.h>
22
+
23
+ #include <cstdint>
24
+ #include <sstream>
25
+ #include <vector>
26
+
27
+ using basalt::Win32MountingManager;
28
+ using basalt::win32::RnWin32View;
29
+ using facebook::react::ColorComponents;
30
+ using facebook::react::LayoutMetrics;
31
+ using facebook::react::MountingTransaction;
32
+ using facebook::react::ShadowView;
33
+ using facebook::react::ShadowViewMutation;
34
+ using facebook::react::ShadowViewMutationList;
35
+ using facebook::react::SurfaceId;
36
+ using facebook::react::Tag;
37
+ using facebook::react::TransactionTelemetry;
38
+ using facebook::react::ViewProps;
39
+
40
+ namespace {
41
+
42
+ constexpr SurfaceId kSurfaceId = 1;
43
+
44
+ ShadowView makeView(Tag tag,
45
+ float x,
46
+ float y,
47
+ float width,
48
+ float height,
49
+ float red = 0.0F,
50
+ float green = 0.0F,
51
+ float blue = 0.0F) {
52
+ auto props = std::make_shared<ViewProps>();
53
+ props->backgroundColor = facebook::react::colorFromComponents(
54
+ ColorComponents{.red = red, .green = green, .blue = blue, .alpha = 1.0F});
55
+
56
+ LayoutMetrics metrics;
57
+ metrics.frame = {.origin = {.x = x, .y = y}, .size = {.width = width, .height = height}};
58
+
59
+ ShadowView view;
60
+ view.componentName = "View";
61
+ view.surfaceId = kSurfaceId;
62
+ view.tag = tag;
63
+ view.props = props;
64
+ view.layoutMetrics = metrics;
65
+ return view;
66
+ }
67
+
68
+ // Applies mutations synchronously. executeMount would post to the UI thread,
69
+ // which a test has no message loop to pump -- though with no host installed
70
+ // postToUiThread runs inline anyway, so this is about being explicit rather
71
+ // than about avoiding a hang.
72
+ void apply(Win32MountingManager &manager, ShadowViewMutationList &&mutations) {
73
+ manager.applyTransaction(
74
+ kSurfaceId, MountingTransaction(kSurfaceId, 1, std::move(mutations), TransactionTelemetry{}));
75
+ }
76
+
77
+ size_t childCount(const RnWin32View *view) {
78
+ return view->children().size();
79
+ }
80
+
81
+ // The tag of the nth child, or -1.
82
+ int32_t childTagAt(const RnWin32View *view, size_t index) {
83
+ if (index >= view->children().size()) {
84
+ return -1;
85
+ }
86
+ return view->children()[index]->tag();
87
+ }
88
+
89
+ } // namespace
90
+
91
+ TEST(win32_create_and_insert_builds_the_tree) {
92
+ Win32MountingManager manager;
93
+ RnWin32View *root = manager.createSurfaceRoot(kSurfaceId);
94
+
95
+ ShadowViewMutationList mutations;
96
+ mutations.push_back(ShadowViewMutation::CreateMutation(makeView(10, 0, 0, 100, 50)));
97
+ mutations.push_back(ShadowViewMutation::CreateMutation(makeView(11, 0, 60, 100, 50)));
98
+ mutations.push_back(
99
+ ShadowViewMutation::InsertMutation(kSurfaceId, makeView(10, 0, 0, 100, 50), 0));
100
+ mutations.push_back(
101
+ ShadowViewMutation::InsertMutation(kSurfaceId, makeView(11, 0, 60, 100, 50), 1));
102
+ apply(manager, std::move(mutations));
103
+
104
+ EXPECT_EQ(childCount(root), size_t{2});
105
+ EXPECT_EQ(childTagAt(root, 0), 10);
106
+ EXPECT_EQ(childTagAt(root, 1), 11);
107
+
108
+ manager.destroySurfaceRoot(kSurfaceId);
109
+ }
110
+
111
+ TEST(win32_insert_honours_the_index) {
112
+ Win32MountingManager manager;
113
+ RnWin32View *root = manager.createSurfaceRoot(kSurfaceId);
114
+
115
+ ShadowViewMutationList first;
116
+ first.push_back(ShadowViewMutation::CreateMutation(makeView(20, 0, 0, 10, 10)));
117
+ first.push_back(ShadowViewMutation::CreateMutation(makeView(21, 0, 0, 10, 10)));
118
+ first.push_back(ShadowViewMutation::InsertMutation(kSurfaceId, makeView(20, 0, 0, 10, 10), 0));
119
+ first.push_back(ShadowViewMutation::InsertMutation(kSurfaceId, makeView(21, 0, 0, 10, 10), 1));
120
+ apply(manager, std::move(first));
121
+
122
+ // An Insert's index counts positions in the parent's *final* child list, so
123
+ // this lands between the two. An append-only implementation passes every
124
+ // other test in this file.
125
+ ShadowViewMutationList second;
126
+ second.push_back(ShadowViewMutation::CreateMutation(makeView(22, 0, 0, 10, 10)));
127
+ second.push_back(ShadowViewMutation::InsertMutation(kSurfaceId, makeView(22, 0, 0, 10, 10), 1));
128
+ apply(manager, std::move(second));
129
+
130
+ EXPECT_EQ(childCount(root), size_t{3});
131
+ EXPECT_EQ(childTagAt(root, 0), 20);
132
+ EXPECT_EQ(childTagAt(root, 1), 22);
133
+ EXPECT_EQ(childTagAt(root, 2), 21);
134
+
135
+ manager.destroySurfaceRoot(kSurfaceId);
136
+ }
137
+
138
+ TEST(win32_remove_detaches_but_does_not_destroy) {
139
+ Win32MountingManager manager;
140
+ RnWin32View *root = manager.createSurfaceRoot(kSurfaceId);
141
+
142
+ ShadowViewMutationList mutations;
143
+ mutations.push_back(ShadowViewMutation::CreateMutation(makeView(30, 0, 0, 10, 10)));
144
+ mutations.push_back(ShadowViewMutation::InsertMutation(kSurfaceId, makeView(30, 0, 0, 10, 10), 0));
145
+ apply(manager, std::move(mutations));
146
+
147
+ ShadowViewMutationList removal;
148
+ removal.push_back(ShadowViewMutation::RemoveMutation(kSurfaceId, makeView(30, 0, 0, 10, 10), 0));
149
+ apply(manager, std::move(removal));
150
+
151
+ // A reparent is a Remove and an Insert with no Delete between them, so the
152
+ // view has to survive the gap -- and the registry is the only thing holding
153
+ // it. Asking the registry is the assertion: if Remove had destroyed it, this
154
+ // would be null or a dangling pointer.
155
+ EXPECT_EQ(childCount(root), size_t{0});
156
+ EXPECT(manager.viewForTag(30) != nullptr);
157
+
158
+ manager.destroySurfaceRoot(kSurfaceId);
159
+ }
160
+
161
+ TEST(win32_update_changes_frame_without_reparenting) {
162
+ Win32MountingManager manager;
163
+ RnWin32View *root = manager.createSurfaceRoot(kSurfaceId);
164
+
165
+ ShadowViewMutationList mutations;
166
+ mutations.push_back(ShadowViewMutation::CreateMutation(makeView(40, 0, 0, 100, 50)));
167
+ mutations.push_back(
168
+ ShadowViewMutation::InsertMutation(kSurfaceId, makeView(40, 0, 0, 100, 50), 0));
169
+ apply(manager, std::move(mutations));
170
+
171
+ ShadowViewMutationList update;
172
+ update.push_back(ShadowViewMutation::UpdateMutation(
173
+ makeView(40, 0, 0, 100, 50), makeView(40, 5, 6, 70, 30), kSurfaceId));
174
+ apply(manager, std::move(update));
175
+
176
+ RnWin32View *view = manager.viewForTag(40);
177
+ EXPECT(view != nullptr);
178
+ EXPECT_EQ(childCount(root), size_t{1});
179
+ EXPECT_NEAR(view->frame().x, 5.0, 0.001);
180
+ EXPECT_NEAR(view->frame().y, 6.0, 0.001);
181
+ EXPECT_NEAR(view->frame().width, 70.0, 0.001);
182
+ EXPECT_NEAR(view->frame().height, 30.0, 0.001);
183
+
184
+ manager.destroySurfaceRoot(kSurfaceId);
185
+ }
186
+
187
+ TEST(win32_delete_removes_the_view_from_the_registry) {
188
+ Win32MountingManager manager;
189
+ manager.createSurfaceRoot(kSurfaceId);
190
+
191
+ ShadowViewMutationList mutations;
192
+ mutations.push_back(ShadowViewMutation::CreateMutation(makeView(50, 0, 0, 10, 10)));
193
+ mutations.push_back(ShadowViewMutation::InsertMutation(kSurfaceId, makeView(50, 0, 0, 10, 10), 0));
194
+ apply(manager, std::move(mutations));
195
+ EXPECT(manager.viewForTag(50) != nullptr);
196
+
197
+ // Fabric guarantees the Remove before the Delete, and this is that order.
198
+ ShadowViewMutationList teardown;
199
+ teardown.push_back(ShadowViewMutation::RemoveMutation(kSurfaceId, makeView(50, 0, 0, 10, 10), 0));
200
+ teardown.push_back(ShadowViewMutation::DeleteMutation(makeView(50, 0, 0, 10, 10)));
201
+ apply(manager, std::move(teardown));
202
+
203
+ EXPECT(manager.viewForTag(50) == nullptr);
204
+
205
+ manager.destroySurfaceRoot(kSurfaceId);
206
+ }
207
+
208
+ TEST(win32_nested_children_are_parented_to_their_own_parent) {
209
+ Win32MountingManager manager;
210
+ RnWin32View *root = manager.createSurfaceRoot(kSurfaceId);
211
+
212
+ ShadowViewMutationList mutations;
213
+ mutations.push_back(ShadowViewMutation::CreateMutation(makeView(60, 0, 0, 100, 100)));
214
+ mutations.push_back(ShadowViewMutation::CreateMutation(makeView(61, 10, 10, 20, 20)));
215
+ mutations.push_back(
216
+ ShadowViewMutation::InsertMutation(kSurfaceId, makeView(60, 0, 0, 100, 100), 0));
217
+ // Parented to 60, not to the surface root.
218
+ mutations.push_back(ShadowViewMutation::InsertMutation(60, makeView(61, 10, 10, 20, 20), 0));
219
+ apply(manager, std::move(mutations));
220
+
221
+ EXPECT_EQ(childCount(root), size_t{1});
222
+ RnWin32View *middle = manager.viewForTag(60);
223
+ EXPECT(middle != nullptr);
224
+ EXPECT_EQ(childCount(middle), size_t{1});
225
+ EXPECT_EQ(childTagAt(middle, 0), 61);
226
+
227
+ manager.destroySurfaceRoot(kSurfaceId);
228
+ }
229
+
230
+ TEST(win32_surface_root_is_reused_not_recreated) {
231
+ Win32MountingManager manager;
232
+ RnWin32View *first = manager.createSurfaceRoot(kSurfaceId);
233
+ RnWin32View *second = manager.createSurfaceRoot(kSurfaceId);
234
+
235
+ // In Fabric a SurfaceId *is* the root node's tag, and Fabric emits no Create
236
+ // for it -- the root shadow node is the base of every diff, so it must
237
+ // already exist when the first transaction arrives. Asking twice must not
238
+ // make two.
239
+ EXPECT(first == second);
240
+
241
+ manager.destroySurfaceRoot(kSurfaceId);
242
+ }
243
+
244
+ TEST(win32_a_mutation_for_an_unknown_tag_is_survivable) {
245
+ Win32MountingManager manager;
246
+ RnWin32View *root = manager.createSurfaceRoot(kSurfaceId);
247
+
248
+ // What a bug upstream, or a transaction racing a surface teardown, looks like
249
+ // from down here. It must not be fatal.
250
+ ShadowViewMutationList mutations;
251
+ mutations.push_back(
252
+ ShadowViewMutation::InsertMutation(kSurfaceId, makeView(999, 0, 0, 10, 10), 0));
253
+ mutations.push_back(ShadowViewMutation::RemoveMutation(kSurfaceId, makeView(998, 0, 0, 10, 10), 0));
254
+ mutations.push_back(ShadowViewMutation::DeleteMutation(makeView(997, 0, 0, 10, 10)));
255
+ apply(manager, std::move(mutations));
256
+
257
+ EXPECT_EQ(childCount(root), size_t{0});
258
+
259
+ manager.destroySurfaceRoot(kSurfaceId);
260
+ }
261
+
262
+ TEST(win32_props_reach_the_view_through_a_mutation) {
263
+ Win32MountingManager manager;
264
+ manager.createSurfaceRoot(kSurfaceId);
265
+
266
+ ShadowViewMutationList mutations;
267
+ mutations.push_back(
268
+ ShadowViewMutation::CreateMutation(makeView(70, 0, 0, 64, 32, 1.0F, 0.0F, 0.0F)));
269
+ mutations.push_back(ShadowViewMutation::InsertMutation(
270
+ kSurfaceId, makeView(70, 0, 0, 64, 32, 1.0F, 0.0F, 0.0F), 0));
271
+ apply(manager, std::move(mutations));
272
+
273
+ // The whole trip: a ViewProps built here, through the walk, into the view's
274
+ // own state and out again as the dump the three hosts compare.
275
+ RnWin32View *view = manager.viewForTag(70);
276
+ EXPECT(view != nullptr);
277
+ EXPECT(view->hasBackgroundColor());
278
+ EXPECT_EQ(view->describeTree(), std::string("view tag=70 frame=(0,0 64x32) bg=#ff0000ff\n"));
279
+
280
+ manager.destroySurfaceRoot(kSurfaceId);
281
+ }
282
+
283
+ TEST(win32_has_component_matches_the_registered_descriptors) {
284
+ Win32MountingManager manager;
285
+
286
+ // Two statements of one fact, and when they disagree the registry wins:
287
+ // Fabric builds shadow nodes nothing can mount and the app renders blank
288
+ // rectangles rather than reporting anything. See ComponentRegistryWin32.cpp.
289
+ EXPECT(manager.hasComponent("View"));
290
+ EXPECT(manager.hasComponent("RootView"));
291
+ EXPECT(manager.hasComponent("Paragraph"));
292
+ EXPECT(manager.hasComponent("Image"));
293
+ EXPECT(manager.hasComponent("ScrollView"));
294
+ EXPECT(manager.hasComponent("TextInput"));
295
+ EXPECT(manager.hasComponent("ActivityIndicatorView"));
296
+ EXPECT(manager.hasComponent("Switch"));
297
+ EXPECT(manager.hasComponent("ModalHostView"));
298
+ EXPECT(manager.hasComponent("PullToRefreshView"));
299
+ // What React Native substitutes for a component nobody registered. Mounting
300
+ // it is what turns "nothing happens" into a view the tree dump can show.
301
+ EXPECT(manager.hasComponent("UnimplementedNativeView"));
302
+
303
+ // Claiming a component this host cannot mount is worse than admitting the
304
+ // gap. `ActivityIndicator` is here rather than above on purpose: the
305
+ // component React Native actually mounts is `ActivityIndicatorView`, and
306
+ // answering to the name a reader expects would be answering to a name
307
+ // nothing sends.
308
+ EXPECT(!manager.hasComponent("Slider"));
309
+ EXPECT(!manager.hasComponent("ActivityIndicator"));
310
+ EXPECT(!manager.hasComponent("SomethingNobodyHasHeardOf"));
311
+ }
312
+
313
+ // --- transformOrigin --------------------------------------------------------
314
+ //
315
+ // `resolveTransform` folds the origin into the matrix, and all three hosts
316
+ // call it -- so this has been "passed through but never exercised" since it
317
+ // was written. What makes it checkable without a display is that the anchor
318
+ // is a claim about a point: scaling about the top-left must leave the
319
+ // top-left corner exactly where it was.
320
+ //
321
+ // The hosts anchor a transform at the view's centre, which is what an
322
+ // untouched `transformOrigin` means, so the matrix is in centre-relative
323
+ // coordinates and the top-left corner is at (-w/2, -h/2).
324
+ //
325
+ // The same two tests are in the other two suites, with the same tags, for the
326
+ // same reason the ones above are.
327
+
328
+ namespace {
329
+
330
+ ShadowView makeTransformed(Tag tag,
331
+ float width,
332
+ float height,
333
+ facebook::react::Transform transform,
334
+ facebook::react::TransformOrigin origin = {}) {
335
+ // Fresh props rather than a copy of makeView's: BaseViewProps has a deleted
336
+ // copy constructor at React Native 0.86, which is what a current Expo app
337
+ // installs. Copying compiled against main and broke the version an app
338
+ // actually has -- found by building a real create-expo-app, not here.
339
+ ShadowView view = makeView(tag, 0.0F, 0.0F, width, height);
340
+ auto props = std::make_shared<ViewProps>();
341
+ props->transform = transform;
342
+ props->transformOrigin = origin;
343
+ view.props = props;
344
+ return view;
345
+ }
346
+
347
+ } // namespace
348
+
349
+ TEST(transform_origin_anchors_the_corner_it_names) {
350
+ Win32MountingManager manager;
351
+ RnWin32View *root = manager.createSurfaceRoot(kSurfaceId);
352
+
353
+ using facebook::react::Transform;
354
+ using facebook::react::TransformOrigin;
355
+ using facebook::react::UnitType;
356
+ using facebook::react::ValueUnit;
357
+
358
+ // Doubled about the top-left of a 100x50 view.
359
+ TransformOrigin topLeft;
360
+ topLeft.xy = {ValueUnit(0.0F, UnitType::Point), ValueUnit(0.0F, UnitType::Point)};
361
+
362
+ ShadowViewMutationList mutations;
363
+ mutations.push_back(ShadowViewMutation::CreateMutation(
364
+ makeTransformed(30, 100.0F, 50.0F, Transform::Scale(2.0F, 2.0F, 1.0F), topLeft)));
365
+ mutations.push_back(ShadowViewMutation::InsertMutation(
366
+ kSurfaceId,
367
+ makeTransformed(30, 100.0F, 50.0F, Transform::Scale(2.0F, 2.0F, 1.0F), topLeft),
368
+ 0));
369
+ apply(manager, std::move(mutations));
370
+
371
+ const auto matrix = basalt::testing::transformIn(root->describeTree(), 30);
372
+ EXPECT_EQ(matrix[0], 2.0);
373
+ EXPECT_EQ(matrix[3], 2.0);
374
+
375
+ // The corner the origin names, in the centre-relative coordinates the
376
+ // matrix is written in, has to come back to itself: (-50,-25) scaled by two
377
+ // is (-100,-50), so the translation has to put 50 and 25 back.
378
+ const double cornerX = matrix[0] * -50.0 + matrix[2] * -25.0 + matrix[4];
379
+ const double cornerY = matrix[1] * -50.0 + matrix[3] * -25.0 + matrix[5];
380
+ EXPECT_EQ(cornerX, -50.0);
381
+ EXPECT_EQ(cornerY, -25.0);
382
+
383
+ manager.destroySurfaceRoot(kSurfaceId);
384
+ }
385
+
386
+ // And with no origin set, the centre is the anchor -- which is the default
387
+ // every host relies on, and the reason an unset origin must not be folded in
388
+ // as (0,0).
389
+ TEST(no_transform_origin_anchors_the_centre) {
390
+ Win32MountingManager manager;
391
+ RnWin32View *root = manager.createSurfaceRoot(kSurfaceId);
392
+
393
+ using facebook::react::Transform;
394
+
395
+ ShadowViewMutationList mutations;
396
+ mutations.push_back(ShadowViewMutation::CreateMutation(
397
+ makeTransformed(31, 100.0F, 50.0F, Transform::Scale(2.0F, 2.0F, 1.0F))));
398
+ mutations.push_back(ShadowViewMutation::InsertMutation(
399
+ kSurfaceId, makeTransformed(31, 100.0F, 50.0F, Transform::Scale(2.0F, 2.0F, 1.0F)), 0));
400
+ apply(manager, std::move(mutations));
401
+
402
+ const auto matrix = basalt::testing::transformIn(root->describeTree(), 31);
403
+ EXPECT_EQ(matrix[0], 2.0);
404
+ EXPECT_EQ(matrix[3], 2.0);
405
+ // No translation at all: the centre is already the anchor.
406
+ EXPECT_EQ(matrix[4], 0.0);
407
+ EXPECT_EQ(matrix[5], 0.0);
408
+
409
+ manager.destroySurfaceRoot(kSurfaceId);
410
+ }