/* * Copyright (c) Facebook, Inc. and its 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 #include #include #include #include namespace facebook { namespace yoga { namespace test { template struct TypedEventTestData {}; template <> struct TypedEventTestData { void* layoutContext; YGMarkerLayoutData layoutData; }; struct EventArgs { const YGNode* node; Event::Type type; std::unique_ptr> dataPtr; std::unique_ptr> eventTestDataPtr; template const Event::TypedData& data() { return *static_cast*>(dataPtr.get()); } template const TypedEventTestData& eventTestData() { return *static_cast*>(eventTestDataPtr.get()); } }; class EventTest : public ::testing::Test { ScopedEventSubscription subscription = {&EventTest::listen}; static void listen(const YGNode&, Event::Type, Event::Data); public: static std::vector events; static EventArgs& lastEvent() { return events.back(); } void TearDown() override; }; TEST_F(EventTest, new_node_has_event) { auto c = YGConfigGetDefault(); auto n = YGNodeNew(); ASSERT_EQ(lastEvent().node, n); ASSERT_EQ(lastEvent().type, Event::NodeAllocation); ASSERT_EQ(lastEvent().data().config, c); YGNodeFree(n); } TEST_F(EventTest, new_node_with_config_event) { auto c = YGConfigNew(); auto n = YGNodeNewWithConfig(c); ASSERT_EQ(lastEvent().node, n); ASSERT_EQ(lastEvent().type, Event::NodeAllocation); ASSERT_EQ(lastEvent().data().config, c); YGNodeFree(n); YGConfigFree(c); } TEST_F(EventTest, clone_node_event) { auto c = YGConfigNew(); auto n = YGNodeNewWithConfig(c); auto clone = YGNodeClone(n); ASSERT_EQ(lastEvent().node, clone); ASSERT_EQ(lastEvent().type, Event::NodeAllocation); ASSERT_EQ(lastEvent().data().config, c); YGNodeFree(n); YGNodeFree(clone); YGConfigFree(c); } TEST_F(EventTest, free_node_event) { auto c = YGConfigNew(); auto n = YGNodeNewWithConfig(c); YGNodeFree(n); ASSERT_EQ(lastEvent().node, n); ASSERT_EQ(lastEvent().type, Event::NodeDeallocation); ASSERT_EQ(lastEvent().data().config, c); YGConfigFree(c); } TEST_F(EventTest, layout_events) { auto root = YGNodeNew(); auto child = YGNodeNew(); YGNodeInsertChild(root, child, 0); YGNodeCalculateLayout(root, 123, 456, YGDirectionLTR); ASSERT_EQ(events[2].node, root); ASSERT_EQ(events[2].type, Event::LayoutPassStart); ASSERT_EQ(events[3].node, child); ASSERT_EQ(events[3].type, Event::NodeLayout); ASSERT_EQ(events[4].node, child); ASSERT_EQ(events[4].type, Event::NodeLayout); ASSERT_EQ(events[5].node, child); ASSERT_EQ(events[5].type, Event::NodeLayout); ASSERT_EQ(events[6].node, root); ASSERT_EQ(events[6].type, Event::NodeLayout); ASSERT_EQ(events[7].node, root); ASSERT_EQ(events[7].type, Event::LayoutPassEnd); YGNodeFreeRecursive(root); } TEST_F(EventTest, layout_events_single_node) { auto root = YGNodeNew(); YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); ASSERT_EQ(events[1].node, root); ASSERT_EQ(events[1].type, Event::LayoutPassStart); ASSERT_EQ(events[2].node, root); ASSERT_EQ(events[2].type, Event::NodeLayout); ASSERT_EQ(events[3].node, root); ASSERT_EQ(events[3].type, Event::LayoutPassEnd); YGMarkerLayoutData layoutData = events[3].eventTestData().layoutData; ASSERT_EQ(layoutData.layouts, 1); ASSERT_EQ(layoutData.measures, 0); ASSERT_EQ(layoutData.maxMeasureCache, 1); } TEST_F(EventTest, layout_events_counts_multi_node_layout) { auto root = YGNodeNew(); auto childA = YGNodeNew(); YGNodeInsertChild(root, childA, 0); auto childB = YGNodeNew(); YGNodeInsertChild(root, childB, 1); YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); ASSERT_EQ(events[3].node, root); ASSERT_EQ(events[3].type, Event::LayoutPassStart); ASSERT_EQ(events[11].node, root); ASSERT_EQ(events[11].type, Event::LayoutPassEnd); YGMarkerLayoutData layoutData = events[11].eventTestData().layoutData; ASSERT_EQ(layoutData.layouts, 3); ASSERT_EQ(layoutData.measures, 4); ASSERT_EQ(layoutData.maxMeasureCache, 3); } TEST_F(EventTest, layout_events_counts_cache_hits_single_node_layout) { auto root = YGNodeNew(); YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); ASSERT_EQ(events[4].node, root); ASSERT_EQ(events[4].type, Event::LayoutPassStart); ASSERT_EQ(events[6].node, root); ASSERT_EQ(events[6].type, Event::LayoutPassEnd); YGMarkerLayoutData layoutData = events[6].eventTestData().layoutData; ASSERT_EQ(layoutData.layouts, 0); ASSERT_EQ(layoutData.measures, 0); ASSERT_EQ(layoutData.cachedLayouts, 1); ASSERT_EQ(layoutData.cachedMeasures, 0); } TEST_F(EventTest, layout_events_counts_cache_hits_multi_node_layout) { auto root = YGNodeNew(); auto childA = YGNodeNew(); YGNodeInsertChild(root, childA, 0); auto childB = YGNodeNew(); YGNodeInsertChild(root, childB, 1); YGNodeCalculateLayout(root, 987, 654, YGDirectionLTR); YGNodeCalculateLayout(root, 123, 456, YGDirectionLTR); YGNodeCalculateLayout(root, 987, 654, YGDirectionLTR); ASSERT_EQ(lastEvent().node, root); ASSERT_EQ(lastEvent().type, Event::LayoutPassEnd); YGMarkerLayoutData layoutData = lastEvent().eventTestData().layoutData; ASSERT_EQ(layoutData.layouts, 3); ASSERT_EQ(layoutData.measures, 0); ASSERT_EQ(layoutData.maxMeasureCache, 5); ASSERT_EQ(layoutData.cachedLayouts, 0); ASSERT_EQ(layoutData.cachedMeasures, 4); } TEST_F(EventTest, layout_events_has_max_measure_cache) { auto root = YGNodeNew(); auto a = YGNodeNew(); YGNodeInsertChild(root, a, 0); auto b = YGNodeNew(); YGNodeInsertChild(root, b, 1); YGNodeStyleSetFlexBasis(a, 10.0f); for (auto s : {20, 30, 40}) { YGNodeCalculateLayout(root, s, s, YGDirectionLTR); } ASSERT_EQ(lastEvent().node, root); ASSERT_EQ(lastEvent().type, Event::LayoutPassEnd); YGMarkerLayoutData layoutData = lastEvent().eventTestData().layoutData; ASSERT_EQ(layoutData.layouts, 3); ASSERT_EQ(layoutData.measures, 3); ASSERT_EQ(layoutData.maxMeasureCache, 7); } namespace { template EventArgs createArgs(const YGNode& node, const Event::Data data) { using Data = Event::TypedData; auto deleteData = [](void* x) { delete static_cast(x); }; return {&node, E, {new Data{(data.get())}, deleteData}}; } template EventArgs createArgs( const YGNode& node, const Event::Data data, TypedEventTestData eventTestData) { using EventTestData = TypedEventTestData; auto deleteEventTestData = [](void* x) { delete static_cast(x); }; EventArgs args = createArgs(node, data); args.eventTestDataPtr = {new EventTestData{eventTestData}, deleteEventTestData}; return args; } } // namespace void EventTest::listen(const YGNode& node, Event::Type type, Event::Data data) { switch (type) { case Event::NodeAllocation: events.push_back(createArgs(node, data)); break; case Event::NodeDeallocation: events.push_back(createArgs(node, data)); break; case Event::NodeLayout: events.push_back(createArgs(node, data)); break; case Event::LayoutPassStart: events.push_back(createArgs(node, data)); break; case Event::LayoutPassEnd: { auto& eventData = data.get(); events.push_back(createArgs( node, data, {eventData.layoutContext, *eventData.layoutData})); break; } case Event::MeasureCallbackStart: case Event::MeasureCallbackEnd: break; } } void EventTest::TearDown() { events.clear(); } std::vector EventTest::events{}; } // namespace test } // namespace yoga } // namespace facebook