From 006e049149dca977da7bae3c71a4e2ab21ad8dbd Mon Sep 17 00:00:00 2001 From: DevmateUtgenCppGeneralFBOrg Bot Date: Tue, 22 Sep 2026 02:11:33 -0700 Subject: [PATCH] xplat/js/react-native-github/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphEventEmitter.cpp Reviewed By: javache Differential Revision: D121155236 --- .../text/tests/ParagraphEventEmitterTest.cpp | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 packages/react-native/ReactCommon/react/renderer/components/text/tests/ParagraphEventEmitterTest.cpp diff --git a/packages/react-native/ReactCommon/react/renderer/components/text/tests/ParagraphEventEmitterTest.cpp b/packages/react-native/ReactCommon/react/renderer/components/text/tests/ParagraphEventEmitterTest.cpp new file mode 100644 index 000000000000..6845e1096ae4 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/text/tests/ParagraphEventEmitterTest.cpp @@ -0,0 +1,180 @@ +/* + * 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 +#include +#include + +namespace facebook::react { + +namespace { + +struct CapturedEvent { + std::string type; + SharedEventPayload payload; +}; + +class ParagraphEventEmitterTest : public ::testing::Test { + protected: + void SetUp() override { + runtime_ = facebook::hermes::makeHermesRuntime( + ::hermes::vm::RuntimeConfig::Builder().build()); + + RuntimeExecutor runtimeExecutor = + [this](std::function&& callback) { + callback(*runtime_); + }; + runtimeScheduler_ = std::make_unique(runtimeExecutor); + + auto eventBeat = std::make_unique( + std::make_shared(), *runtimeScheduler_); + auto eventQueueProcessor = EventQueueProcessor( + [](jsi::Runtime&, + EventTarget*, + const std::string&, + ReactEventPriority, + const EventPayload&, + HighResTimeStamp) {}, + [](jsi::Runtime&) {}, + [](const StateUpdate&) {}, + {}); + eventDispatcher_ = std::make_shared( + eventQueueProcessor, + std::move(eventBeat), + [](const StateUpdate&) {}, + std::weak_ptr{}); + + listener_ = std::make_shared( + [this](const RawEvent& event) { + events_.push_back({event.type, event.eventPayload}); + return true; + }); + eventDispatcher_->addListener(listener_); + } + + void expectLine( + const jsi::Array& lines, + size_t index, + const LineMeasurement& expected) { + auto line = lines.getValueAtIndex(*runtime_, index).asObject(*runtime_); + EXPECT_EQ( + line.getProperty(*runtime_, "text") + .asString(*runtime_) + .utf8(*runtime_), + expected.text); + EXPECT_EQ( + line.getProperty(*runtime_, "x").asNumber(), expected.frame.origin.x); + EXPECT_EQ( + line.getProperty(*runtime_, "y").asNumber(), expected.frame.origin.y); + EXPECT_EQ( + line.getProperty(*runtime_, "width").asNumber(), + expected.frame.size.width); + EXPECT_EQ( + line.getProperty(*runtime_, "height").asNumber(), + expected.frame.size.height); + EXPECT_EQ( + line.getProperty(*runtime_, "descender").asNumber(), + expected.descender); + EXPECT_EQ( + line.getProperty(*runtime_, "capHeight").asNumber(), + expected.capHeight); + EXPECT_EQ( + line.getProperty(*runtime_, "ascender").asNumber(), + expected.ascender); + EXPECT_EQ( + line.getProperty(*runtime_, "xHeight").asNumber(), expected.xHeight); + } + + std::unique_ptr runtime_; + std::unique_ptr runtimeScheduler_; + std::shared_ptr eventDispatcher_; + std::shared_ptr listener_; + std::vector events_; +}; + +TEST_F( + ParagraphEventEmitterTest, + testOnTextLayoutSerializesEveryLineMeasurement) { + const LinesMeasurements measurements{ + {"First line", {{1.25, 2.5}, {30.75, 10.5}}, -2.25, 7.5, 8.25, 4.5}, + {"Second line", {{3.5, 13.75}, {42.25, 11.5}}, -1.5, 8.0, 9.75, 5.25}, + }; + ParagraphEventEmitter eventEmitter(nullptr, eventDispatcher_); + + eventEmitter.onTextLayout(measurements); + + ASSERT_EQ(events_.size(), 1); + EXPECT_EQ(events_[0].type, "topTextLayout"); + auto payload = events_[0].payload->asJSIValue(*runtime_).asObject(*runtime_); + auto lines = payload.getProperty(*runtime_, "lines") + .asObject(*runtime_) + .asArray(*runtime_); + ASSERT_EQ(lines.size(*runtime_), measurements.size()); + expectLine(lines, 0, measurements[0]); + expectLine(lines, 1, measurements[1]); +} + +TEST_F( + ParagraphEventEmitterTest, + testOnTextLayoutSuppressesIdenticalMeasurementsButDispatchesChanges) { + const LinesMeasurements measurements{ + {"Line", {{1, 2}, {30, 10}}, -2, 7, 8, 4}, + }; + auto changedMeasurements = measurements; + changedMeasurements[0].xHeight = 5; + ParagraphEventEmitter eventEmitter(nullptr, eventDispatcher_); + + eventEmitter.onTextLayout(measurements); + ASSERT_EQ(events_.size(), 1); + + eventEmitter.onTextLayout(measurements); + ASSERT_EQ(events_.size(), 1); + + eventEmitter.onTextLayout(changedMeasurements); + ASSERT_EQ(events_.size(), 2); + auto payload = events_[1].payload->asJSIValue(*runtime_).asObject(*runtime_); + auto lines = payload.getProperty(*runtime_, "lines") + .asObject(*runtime_) + .asArray(*runtime_); + ASSERT_EQ(lines.size(*runtime_), changedMeasurements.size()); + auto line = lines.getValueAtIndex(*runtime_, 0).asObject(*runtime_); + EXPECT_EQ( + line.getProperty(*runtime_, "xHeight").asNumber(), + changedMeasurements[0].xHeight); +} + +TEST_F( + ParagraphEventEmitterTest, + testOnTextLayoutDispatchesTransitionToEmptyMeasurements) { + const LinesMeasurements measurements{ + {"Removed line", {{1, 2}, {30, 10}}, -2, 7, 8, 4}, + }; + ParagraphEventEmitter eventEmitter(nullptr, eventDispatcher_); + + eventEmitter.onTextLayout(measurements); + ASSERT_EQ(events_.size(), 1); + + eventEmitter.onTextLayout({}); + ASSERT_EQ(events_.size(), 2); + auto payload = events_[1].payload->asJSIValue(*runtime_).asObject(*runtime_); + auto lines = payload.getProperty(*runtime_, "lines") + .asObject(*runtime_) + .asArray(*runtime_); + EXPECT_EQ(lines.size(*runtime_), 0); + + eventEmitter.onTextLayout({}); + EXPECT_EQ(events_.size(), 2); +} + +} // namespace + +} // namespace facebook::react