From 85b706869ecc4045eb36e233436c997e8aab7642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Mon, 25 May 2026 02:42:55 -0300 Subject: [PATCH] Float and text wrap fixes. --- .../plans/reddit_old_thread_rendering_plan.md | 8 ++- src/eepp/graphics/richtext.cpp | 4 +- src/eepp/ui/uirichtext.cpp | 11 +++- src/tests/unit_tests/uihtml_float_tests.cpp | 37 +++++++++++ src/tests/unit_tests/uihtml_tests.cpp | 64 +++++++++++++++++++ 5 files changed, 117 insertions(+), 7 deletions(-) diff --git a/.agent/plans/reddit_old_thread_rendering_plan.md b/.agent/plans/reddit_old_thread_rendering_plan.md index a2a5c78be..2bcd562be 100644 --- a/.agent/plans/reddit_old_thread_rendering_plan.md +++ b/.agent/plans/reddit_old_thread_rendering_plan.md @@ -42,11 +42,13 @@ Current progress: - Legacy `` is registered as an HTML phrasing element and receives default `line-through` styling, removing a missing-element warning from the fixture. - Horizontal `auto` margins are recomputed at RichText/block layout read points, and the old Reddit vote arrow is centered inside `.midcol` (`arrow.x=17`, `midcol.x=15`, `arrow.width=15`, `midcol.width=19` in the current smoke run). - Vote arrow sprite CSS now resolves relative to the stylesheet file, preserves negative `background-position`, and is asserted in both a reduced sprite test and the old Reddit smoke test. -- CSS `white-space` now maps browser values such as `normal`, `nowrap`, `pre`, `pre-wrap`, `pre-line`, and `break-spaces` into RichText whitespace-collapse and soft-wrap behavior. `nowrap` suppresses soft wrapping for text and atomic inline boxes, including old Reddit-style inline flat lists. +- CSS `white-space` now maps browser values such as `normal`, `nowrap`, `pre`, `pre-wrap`, `pre-line`, and `break-spaces` into RichText whitespace-collapse and soft-wrap behavior. `nowrap` suppresses soft wrapping for text and atomic inline boxes, including old Reddit-style inline flat lists, and overflowing text runs no longer force following inline content onto a new line. - Collapsed whitespace-only text nodes between non-inline boxes no longer create a spurious line box in RichText layout. - HTML ` +
menu
+
+
    +
  • home
  • +
  • - popular
  • +
  • - all
  • +
+
+ + + + )html" ) ); + sceneNode->updateDirtyLayouts(); + + auto* drop = sceneNode->find( "drop" ); + auto* bfc = sceneNode->find( "bfc" ); + ASSERT_TRUE( drop != nullptr ); + ASSERT_TRUE( bfc != nullptr ); + + Vector2f dropPos = drop->convertToWorldSpace( { 0, 0 } ); + Vector2f bfcPos = bfc->convertToWorldSpace( { 0, 0 } ); + + EXPECT_NEAR( dropPos.y, bfcPos.y, 1.f ); + EXPECT_GE( bfcPos.x, dropPos.x + drop->getPixelsSize().getWidth() - 1.f ); + + Engine::destroySingleton(); +} + UTEST( UIHTMLFloat, autoHorizontalMarginsCenterBlockInsideFloat ) { init_float_test(); UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode(); diff --git a/src/tests/unit_tests/uihtml_tests.cpp b/src/tests/unit_tests/uihtml_tests.cpp index eb836ad7e..d2c5998ec 100644 --- a/src/tests/unit_tests/uihtml_tests.cpp +++ b/src/tests/unit_tests/uihtml_tests.cpp @@ -216,6 +216,7 @@ UTEST( UIHTML, redditOldThreadWebViewSmoke ) { auto redesignButton = sceneNode->getRoot()->find( "redesign-beta-optin-btn" ); auto srDrop = sceneNode->getRoot()->querySelector( "#sr-header-area .dropdown.srdrop" ); auto srList = sceneNode->getRoot()->querySelector( "#sr-header-area .sr-list" ); + auto srFlatList = sceneNode->getRoot()->querySelector( "#sr-header-area .sr-list .flat-list" ); auto headerBottomLeft = sceneNode->getRoot()->find( "header-bottom-left" ); auto dropChoices = sceneNode->getRoot()->querySelector( ".drop-choices.srdrop" ); @@ -228,6 +229,7 @@ UTEST( UIHTML, redditOldThreadWebViewSmoke ) { ASSERT_TRUE( redesignButton != nullptr ); ASSERT_TRUE( srDrop != nullptr ); ASSERT_TRUE( srList != nullptr ); + ASSERT_TRUE( srFlatList != nullptr ); ASSERT_TRUE( headerBottomLeft != nullptr ); ASSERT_TRUE( dropChoices != nullptr ); @@ -245,6 +247,7 @@ UTEST( UIHTML, redditOldThreadWebViewSmoke ) { redesignButton->asType()->convertToWorldSpace( { 0, 0 } ); Vector2f srDropPos = srDrop->asType()->convertToWorldSpace( { 0, 0 } ); Vector2f srListPos = srList->asType()->convertToWorldSpace( { 0, 0 } ); + Vector2f srFlatListPos = srFlatList->asType()->convertToWorldSpace( { 0, 0 } ); Vector2f headerBottomLeftPos = headerBottomLeft->asType()->convertToWorldSpace( { 0, 0 } ); Vector2f dropChoicesPos = dropChoices->asType()->convertToWorldSpace( { 0, 0 } ); @@ -295,6 +298,20 @@ UTEST( UIHTML, redditOldThreadWebViewSmoke ) { const Float arrowCenter = arrowPos.x + arrow->asType()->getPixelsSize().getWidth() / 2.f; EXPECT_NEAR( midcolCenter, arrowCenter, 1.f ); + EXPECT_NEAR( srListPos.y, srHeaderPos.y, 1.f ); + EXPECT_GE( srListPos.x, + srDropPos.x + srDrop->asType()->getPixelsSize().getWidth() - 1.f ); + EXPECT_LE( srList->asType()->getPixelsSize().getWidth(), + win->getWidth() - srListPos.x + 1.f ); + EXPECT_LE( srList->asType()->getPixelsSize().getHeight(), + srHeader->asType()->getPixelsSize().getHeight() + 1.f ); + EXPECT_EQ( srFlatList->asType()->getRichTextPtr()->getLines().size(), + (size_t)1 ); + EXPECT_NEAR( srFlatListPos.y, srHeaderPos.y, 1.f ); + EXPECT_GE( headerBottomLeftPos.y, + srHeaderPos.y + srHeader->asType()->getPixelsSize().getHeight() - 1.f ); + EXPECT_FALSE( dropChoices->asType()->isVisible() ); + EXPECT_EQ( dropChoices->asType()->getDisplay(), CSSDisplay::None ); auto* arrowBackground = arrow->asType()->getBackground(); ASSERT_TRUE( arrowBackground != nullptr ); @@ -390,11 +407,58 @@ UTEST( UIHTML, WhiteSpaceNowrapKeepsInlineListOnOneLine ) { EXPECT_EQ( bar->getRichTextPtr()->getLines().size(), (size_t)1 ); EXPECT_FALSE( bar->getLineWrap() ); + EXPECT_EQ( list->getRichTextPtr()->getLines().size(), (size_t)1 ); EXPECT_FALSE( list->getLineWrap() ); Engine::destroySingleton(); } +UTEST( UIHTML, WhiteSpaceNowrapContinuesInlineContentAfterOverflow ) { + init_ui_test(); + UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode(); + + sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html( + +
+
    +
  • home
  • +
  • - popular
  • +
  • - all
  • +
  • - friends
  • +
+ | +
    +
  • movies
  • +
  • - videos
  • +
  • - pcgaming
  • +
  • - gamedev
  • +
  • - science
  • +
  • - space
  • +
+
+ + )html" ) ); + SceneManager::instance()->update(); + + auto* bar = sceneNode->getRoot()->find( "bar" )->asType(); + auto* first = sceneNode->getRoot()->find( "first" )->asType(); + auto* second = sceneNode->getRoot()->find( "second" )->asType(); + ASSERT_TRUE( bar != nullptr ); + ASSERT_TRUE( first != nullptr ); + ASSERT_TRUE( second != nullptr ); + + EXPECT_FALSE( bar->getLineWrap() ); + EXPECT_FALSE( first->getLineWrap() ); + EXPECT_FALSE( second->getLineWrap() ); + EXPECT_EQ( bar->getRichTextPtr()->getLines().size(), (size_t)1 ); + EXPECT_EQ( first->getRichTextPtr()->getLines().size(), (size_t)1 ); + EXPECT_EQ( second->getRichTextPtr()->getLines().size(), (size_t)1 ); + EXPECT_NEAR( first->convertToWorldSpace( { 0, 0 } ).y, + second->convertToWorldSpace( { 0, 0 } ).y, 1.f ); + + Engine::destroySingleton(); +} + UTEST( UIRichText, anchorMargins ) { auto win = Engine::instance()->createWindow( WindowSettings( 800, 600, "Anchor Margins Test", WindowStyle::Default,