diff --git a/packages/react-native/ReactCommon/react/renderer/animations/tests/MutationComparatorTest.cpp b/packages/react-native/ReactCommon/react/renderer/animations/tests/MutationComparatorTest.cpp new file mode 100644 index 000000000000..f5ed376a8859 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/animations/tests/MutationComparatorTest.cpp @@ -0,0 +1,273 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include + +#include + +#include +#include + +namespace facebook::react { + +namespace { + +ShadowView makeShadowView(Tag tag) { + ShadowView sv{}; + sv.tag = tag; + return sv; +} + +} // namespace + +// Verify strict weak ordering: irreflexivity +// comp(a, a) must be false +TEST(MutationComparatorTest, Irreflexivity) { + auto sv1 = makeShadowView(1); + auto sv2 = makeShadowView(2); + + // Same-type mutations where the fallback path is exercised + auto update1 = ShadowViewMutation::UpdateMutation(sv1, sv2, /*parentTag=*/10); + EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(update1, update1)); + + auto create1 = ShadowViewMutation::CreateMutation(sv1); + EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(create1, create1)); + + auto insert1 = + ShadowViewMutation::InsertMutation(/*parentTag=*/10, sv1, /*index=*/0); + EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(insert1, insert1)); + + auto remove1 = + ShadowViewMutation::RemoveMutation(/*parentTag=*/10, sv1, /*index=*/0); + EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(remove1, remove1)); + + auto del1 = ShadowViewMutation::DeleteMutation(sv1); + EXPECT_FALSE(shouldFirstComeBeforeSecondMutation(del1, del1)); +} + +// Verify strict weak ordering: asymmetry +// If comp(a, b) then !comp(b, a) +TEST(MutationComparatorTest, Asymmetry) { + auto sv1 = makeShadowView(1); + auto sv2 = makeShadowView(2); + auto sv3 = makeShadowView(3); + + // Two updates with same type but different parentTags (fallback path) + auto update1 = ShadowViewMutation::UpdateMutation(sv1, sv2, /*parentTag=*/10); + auto update2 = ShadowViewMutation::UpdateMutation(sv1, sv3, /*parentTag=*/20); + + bool a_before_b = shouldFirstComeBeforeSecondMutation(update1, update2); + bool b_before_a = shouldFirstComeBeforeSecondMutation(update2, update1); + + // Exactly one must be true (asymmetry) + EXPECT_NE(a_before_b, b_before_a); +} + +// Verify strict weak ordering: transitivity +// If comp(a, b) and comp(b, c) then comp(a, c) +TEST(MutationComparatorTest, Transitivity) { + auto sv1 = makeShadowView(1); + auto sv2 = makeShadowView(2); + auto sv3 = makeShadowView(3); + + auto update1 = ShadowViewMutation::UpdateMutation(sv1, sv1, /*parentTag=*/10); + auto update2 = ShadowViewMutation::UpdateMutation(sv1, sv1, /*parentTag=*/20); + auto update3 = ShadowViewMutation::UpdateMutation(sv1, sv1, /*parentTag=*/30); + + bool a_b = shouldFirstComeBeforeSecondMutation(update1, update2); + bool b_c = shouldFirstComeBeforeSecondMutation(update2, update3); + bool a_c = shouldFirstComeBeforeSecondMutation(update1, update3); + + if (a_b && b_c) { + EXPECT_TRUE(a_c) << "Transitivity violated: a