Grid layout fixes.

This commit is contained in:
Martín Lucas Golini
2026-06-02 00:43:30 -03:00
parent 127689d0e0
commit afa15f70c0
6 changed files with 603 additions and 18 deletions

View File

@@ -910,9 +910,16 @@ Do not checkpoint a failing phase as complete.
| 41 | I5: Negative `contentBoxSize` now clamped to 0 | (implicitly tested) |
| 42 | L5: `mPacking` converted to RAII guard (exception-safe) | (implicitly tested) |
| 43 | I4: Span decoupled from `resolvedEnd` — explicit `columnSpan`/`rowSpan` fields | (implicitly tested) |
| 44 | PL7: Auto-placement cursor not advancing past placed items (`cursorCol = c+sw`) | `GridContainer.percentHeightWithAutoParentFallsBackToContent` |
| 45 | PL8: Auto-placement column/row limit growing during iteration (captured `colLimit`/`rowLimit`) | `GridContainer.percentHeightWithAutoParentFallsBackToContent` |
| 46 | L6: `height:100%` / `width:100%` on grid with auto parent → fall back to content-based size | `GridContainer.percentHeightWithAutoParentFallsBackToContent` |
| 47 | TS8: `1fr` tracks get `baseSize=0` when container has indefinite width → size from item intrinsic widths | `GridContainer.frTracksWithPercentWidthAndHeight` |
| 48 | L7: `width:100%`/`height:100%` resolved against small intermediate ancestor → scan all ancestors + scene node for max size | `GridContainer.frTracksWithPercentWidthAndHeight` |
| 49 | L8: Percentage fallback ran AFTER track sizing → rows sized with 0 height. Re-run sizing loop after fallback adjusts container size. | `GridContainer.frTracksWithPercentWidthAndHeight` |
| 50 | L9: `height:100%` resolved against viewport instead of auto → use content-based height (CSS spec: % height on auto parent = auto) | `GridContainer.newsblurReducedGrid` |
### Stats
- **Total bugs found**: 43
- **Fixed**: 43
- **Total bugs found**: 50
- **Fixed**: 50
- **By severity**: 0 remaining

View File

@@ -377,7 +377,7 @@
"working_dir": "${project_root}/bin"
},
{
"args": "-c system --hn-dark",
"args": "-d1 /home/downloads/files/svn/eepp/bin/unit_tests/assets/html/grid_newsblur_reduced.html",
"command": "${project_root}/bin/eepp-ui-html-debug",
"name": "eepp-ui-html-debug",
"working_dir": "${project_root}/bin"

View File

@@ -0,0 +1,451 @@
<!doctype html>
<html>
<head>
<style>
body {
height: 100%;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-overflow-scrolling: touch;
scrollbar-color: #c2c2c2 #fafafa;
background-color: #fff;
border: 0;
outline: 0;
margin: 0;
padding: 0;
line-height: 1.3;
text-decoration: none;
list-style: none;
overflow: hidden;
}
a,
a:active,
a:hover,
a:visited,
button {
outline: none;
}
a img {
border: none;
}
.NB-splash {
display: block;
}
body,
.NB-theme-feed-font-whitney {
font-family:
Whitney SSm A,
Whitney SSm B,
Lucida Grande,
Verdana,
Helvetica Neue,
Helvetica,
sans-serif;
}
.NB-welcome .NB-body-inner {
width: 100%;
height: 100%;
position: absolute;
overflow: auto;
}
.NB-welcome .NB-inner {
box-sizing: border-box;
width: 100%;
max-width: 960px;
height: 100%;
margin: 0 auto;
position: relative;
}
.NB-welcome-features-wrap {
background: linear-gradient(#f5eedd 0%, #ebe0c2 100%);
border-top: 1px solid #0000000f;
border-bottom: 1px solid #0000000f;
padding-bottom: 0;
}
.NB-welcome-feature-groups {
margin: 48px 0 24px;
}
.NB-welcome-feature-groups > .NB-inner {
grid-template-columns: 1fr 1fr;
gap: 36px 48px;
display: grid;
}
.NB-feature-group {
border-left: 3px solid #0000;
padding-left: 20px;
}
.NB-feature-group-read {
border-left-color: #c0a06a;
}
.NB-feature-group-smart {
border-left-color: #7a9a5a;
}
.NB-feature-group-share {
border-left-color: #8a6a9a;
}
.NB-feature-group-everywhere {
border-left-color: #5a8aaa;
}
.NB-feature-group-label {
text-transform: uppercase;
letter-spacing: 2px;
margin: 0 0 14px;
padding: 0;
font-family:
Gotham Narrow A,
Gotham Narrow B,
sans-serif;
font-size: 18px;
font-weight: 700;
}
.NB-feature-group-read .NB-feature-group-label {
color: #b0903a;
}
.NB-feature-group-smart .NB-feature-group-label {
color: #5a7a3a;
}
.NB-feature-group-share .NB-feature-group-label {
color: #7a5a8a;
}
.NB-feature-group-everywhere .NB-feature-group-label {
color: #4a7a9a;
}
.NB-feature-group-items {
flex-direction: column;
gap: 0;
display: flex;
}
a.NB-fgi {
color: inherit;
border-radius: 8px;
align-items: center;
gap: 14px;
padding: 10px 12px 10px 0;
text-decoration: none;
transition: background-color 0.15s;
display: flex;
}
a.NB-fgi img {
object-fit: cover;
object-position: left top;
border-radius: 6px;
flex-shrink: 0;
width: 128px;
height: 128px;
box-shadow: 0 1px 4px #00000026;
}
.NB-fgi-text {
min-width: 0;
}
.NB-fgi-caption {
color: #3a2e1e;
font-family:
Gotham Narrow A,
Gotham Narrow B,
sans-serif;
font-size: 22px;
font-weight: 700;
line-height: 1.2;
}
.NB-fgi-desc {
color: #8a7a6a;
margin-top: 4px;
font-size: 16px;
line-height: 1.45;
}
</style>
</head>
<body
class="NB-welcome NB-premium NB-is-premium NB-is-anonymous NB-pref-story-position-center NB-dashboard-columns-double NB-pref-sticky-story-sideoptions NB-content-preview-1 NB-intelligence-neutral NB-feedlist-hide-read-feeds NB-theme-whitney NB-theme-size-m NB-theme-feed-size-m NB-theme-feed-font-whitney NB-line-spacing-m NB-density-comfortable NB-image-preview-small-right"
style="background-color: rgb(26, 37, 37)"
>
<div class="NB-body-inner">
<div class="NB-welcome-container NB-splash">
<div class="NB-welcome-features-wrap">
<div class="NB-welcome-feature-groups">
<div class="NB-inner">
<div class="NB-feature-group NB-feature-group-read">
<h3 class="NB-feature-group-label">Read Your Way</h3>
<div class="NB-feature-group-items">
<a href="https://newsblur.com/features#speed" class="NB-fgi">
<img src="./grid_newsblur_files/feature_1.png" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">Real-time RSS</div>
<div class="NB-fgi-desc">
Stories are pushed directly to you, so you can read news
as it comes in
</div>
</div>
</a>
<a
href="https://newsblur.com/features#original"
class="NB-fgi"
>
<img src="./grid_newsblur_files/feature_2.png" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">Original Site</div>
<div class="NB-fgi-desc">
Read the content in context, the way it was meant to be
seen
</div>
</div>
</a>
<a
href="https://newsblur.com/features#text-view"
class="NB-fgi"
>
<img src="./grid_newsblur_files/subfeature_3.png" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">Read the Full Story</div>
<div class="NB-fgi-desc">
The original story from truncated RSS feeds is
seamlessly expanded
</div>
</div>
</a>
<a
href="https://newsblur.com/features#layouts"
class="NB-fgi"
>
<img src="./grid_newsblur_files/subfeature_8.png" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">
Grid, List, Split, Magazine
</div>
<div class="NB-fgi-desc">
Every site has its own story layout, so you can mix and
match
</div>
</div>
</a>
<a href="https://newsblur.com/features#themes" class="NB-fgi">
<img src="./grid_newsblur_files/subfeature_9.png" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">Dark Mode</div>
<div class="NB-fgi-desc">
Easy on the eyes and built into the web, iOS, and
Android
</div>
</div>
</a>
<a href="https://newsblur.com/features#river" class="NB-fgi">
<img src="./grid_newsblur_files/subfeature_16.png" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">River of News</div>
<div class="NB-fgi-desc">
Read all your feeds by folder in one scrollable stream
</div>
</div>
</a>
<a
href="https://newsblur.com/features#shortcuts"
class="NB-fgi"
>
<img src="./grid_newsblur_files/subfeature_15.png" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">Keyboard Shortcuts</div>
<div class="NB-fgi-desc">
Navigate everything without touching the mouse
</div>
</div>
</a>
</div>
</div>
<div class="NB-feature-group NB-feature-group-smart">
<h3 class="NB-feature-group-label">Stay Smart</h3>
<div class="NB-feature-group-items">
<a
href="https://newsblur.com/features#training"
class="NB-fgi"
>
<img src="./grid_newsblur_files/feature_4.png" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">Intelligence Training</div>
<div class="NB-fgi-desc">
Hide the stories you don't like and highlight the
stories you do
</div>
</div>
</a>
<a href="https://newsblur.com/features#search" class="NB-fgi">
<img src="./grid_newsblur_files/feature_5.jpg" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">Full Text Search</div>
<div class="NB-fgi-desc">
Quickly find stories across all of your subscriptions
</div>
</div>
</a>
<a href="https://newsblur.com/features#search" class="NB-fgi">
<img src="./grid_newsblur_files/subfeature_1.png" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">Saved Searches</div>
<div class="NB-fgi-desc">
Regularly used searches are conveniently given their own
feeds
</div>
</div>
</a>
<a href="https://newsblur.com/features#ask-ai" class="NB-fgi">
<img src="./grid_newsblur_files/subfeature_12.png" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">Ask AI</div>
<div class="NB-fgi-desc">
Ask questions about any story and get answers powered by
AI
</div>
</div>
</a>
<a
href="https://newsblur.com/features#briefing"
class="NB-fgi"
>
<img src="./grid_newsblur_files/subfeature_13.png" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">Daily Briefing</div>
<div class="NB-fgi-desc">
A summary of your top stories, customizable and
delivered every day
</div>
</div>
</a>
<a
href="https://newsblur.com/features#changes"
class="NB-fgi"
>
<img src="./grid_newsblur_files/subfeature_5.png" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">Track Changes</div>
<div class="NB-fgi-desc">
See how a story evolved since it was first published
</div>
</div>
</a>
<a
href="https://newsblur.com/features#notifications"
class="NB-fgi"
>
<img src="./grid_newsblur_files/subfeature_14.png" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">Notifications</div>
<div class="NB-fgi-desc">
Get alerted the moment a feed publishes, on web, iOS, or
Android
</div>
</div>
</a>
</div>
</div>
<div class="NB-feature-group NB-feature-group-share">
<h3 class="NB-feature-group-label">Share &amp; Organize</h3>
<div class="NB-feature-group-items">
<a
href="https://newsblur.com/features#blurblog"
class="NB-fgi"
>
<img src="./grid_newsblur_files/feature_3.png" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">Shared Stories</div>
<div class="NB-fgi-desc">
Reading news is better with friends. Share stories on
your public blurblog
</div>
</div>
</a>
<a
href="https://newsblur.com/features#blurblog"
class="NB-fgi"
>
<img src="./grid_newsblur_files/feature_7.jpg" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">Blurblog Privacy</div>
<div class="NB-fgi-desc">
Share stories with the world or only with your friends
</div>
</div>
</a>
<a href="https://newsblur.com/features#saved" class="NB-fgi">
<img src="./grid_newsblur_files/feature_6.png" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">Story Tagging</div>
<div class="NB-fgi-desc">
Save stories with custom tags for fast references
</div>
</div>
</a>
<a
href="https://newsblur.com/features#newsletters"
class="NB-fgi"
>
<img src="./grid_newsblur_files/subfeature_7.png" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">Email Newsletters</div>
<div class="NB-fgi-desc">
Read your email newsletters where they belong, in a news
reader
</div>
</div>
</a>
</div>
</div>
<div class="NB-feature-group NB-feature-group-everywhere">
<h3 class="NB-feature-group-label">Works Everywhere</h3>
<div class="NB-feature-group-items">
<a href="https://newsblur.com/features#apps" class="NB-fgi">
<img src="./grid_newsblur_files/feature_8.jpg" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">Third-party Apps</div>
<div class="NB-fgi-desc">
Supports Reeder, ReadKit, Unread, and loads more
</div>
</div>
</a>
<a
href="https://newsblur.com/features#integrations"
class="NB-fgi"
>
<img src="./grid_newsblur_files/subfeature_6.png" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">IFTTT &amp; Zapier</div>
<div class="NB-fgi-desc">
Hook NewsBlur up to nearly every service on the web
</div>
</div>
</a>
<a
href="https://newsblur.com/features#web-feeds"
class="NB-fgi"
>
<img src="./grid_newsblur_files/subfeature_10.png" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">YouTube</div>
<div class="NB-fgi-desc">
Even sites that don't publish RSS feeds can be followed
</div>
</div>
</a>
<a
href="https://newsblur.com/features#web-feeds"
class="NB-fgi"
>
<img src="./grid_newsblur_files/subfeature_11.png" />
<div class="NB-fgi-text">
<div class="NB-fgi-caption">Web Feeds</div>
<div class="NB-fgi-desc">
A creative feed for any website, even without RSS
</div>
</div>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -268,6 +268,22 @@ void FlexLayouter::measureFlexItems( const Axis& mainAxis, const Axis& crossAxis
item.widget->setLayoutWidthPolicy( SizePolicy::WrapContent );
else
item.widget->setLayoutHeightPolicy( SizePolicy::WrapContent );
// Set the item's internal size to the container's main size,
// so the block layouter's `else` branch (non-WrapContent)
// wraps text at the full container width. The flex-shrink
// step will then proportionally reduce the item to its final
// size, giving the expected "fill remaining space" behaviour.
Float containerMainDim =
mainAxis.horizontal
? containerWidth - containerPadding.Left - containerPadding.Right
: containerHeight - containerPadding.Top - containerPadding.Bottom;
if ( containerMainDim > 0.f ) {
if ( mainAxis.horizontal )
item.widget->setInternalPixelsWidth( containerMainDim );
else
item.widget->setInternalPixelsHeight( containerMainDim );
}
}
if ( item.widget->isType( UI_TYPE_HTML_WIDGET ) ) {

View File

@@ -1,9 +1,14 @@
#include <algorithm>
#include <cctype>
#include <eepp/core/string.hpp>
#include <eepp/ui/css/propertydefinition.hpp>
#include <eepp/ui/css/stylesheetlength.hpp>
#include <eepp/ui/css/stylesheetproperty.hpp>
#include <eepp/ui/css/stylesheetspecification.hpp>
#include <eepp/ui/gridlayouter.hpp>
#include <eepp/ui/uihtmlwidget.hpp>
#include <eepp/ui/uiscenenode.hpp>
#include <eepp/ui/uistyle.hpp>
#include <eepp/ui/uitextnode.hpp>
#include <set>
@@ -747,8 +752,12 @@ void GridLayouter::preSizeItemsForRowSizing() {
if ( item.widget->isType( UI_TYPE_HTML_WIDGET ) ) {
auto* childHtml = item.widget->asType<UIHTMLWidget>();
auto* layouter = childHtml->getLayouter();
if ( layouter && !layouter->isPacking() )
if ( layouter && !layouter->isPacking() ) {
auto oldHP = childHtml->getLayoutHeightPolicy();
childHtml->setLayoutHeightPolicy( SizePolicy::WrapContent );
childHtml->updateLayout();
childHtml->setLayoutHeightPolicy( oldHP );
}
}
}
}
@@ -944,11 +953,70 @@ void GridLayouter::updateLayout() {
autoPlaceItems();
buildImplicitTracks();
collapseEmptyAutoFitTracks();
sizeTracksForAxis( true );
// Pre-size items to their column widths so row sizing sees correct heights
preSizeItemsForRowSizing();
sizeTracksForAxis( false );
applyLayout();
int needResize = true;
bool hPercentUnresolved = false;
bool wPercentUnresolved = false;
if ( grid && grid->getUIStyle() ) {
const StyleSheetProperty* hprop = grid->getUIStyle()->getProperty( PropertyId::Height );
hPercentUnresolved =
hprop && StyleSheetLength::isPercentage( hprop->value() ) &&
grid->getPixelsSize().getHeight() <= 0.f;
const StyleSheetProperty* wprop = grid->getUIStyle()->getProperty( PropertyId::Width );
wPercentUnresolved =
wprop && StyleSheetLength::isPercentage( wprop->value() ) &&
grid->getPixelsSize().getWidth() <= 0.f;
}
for ( int sizingPass = 0; sizingPass < 2 && needResize; ++sizingPass ) {
needResize = false;
sizeTracksForAxis( true );
preSizeItemsForRowSizing();
sizeTracksForAxis( false );
applyLayout();
if ( hPercentUnresolved ) {
Float contentH = 0.f;
for ( const auto& row : mRows )
contentH += row.baseSize;
if ( mRows.size() > 1 )
contentH += static_cast<Float>( mRows.size() - 1 ) * mRowGap;
contentH +=
mContainer->getPixelsContentOffset().Top +
mContainer->getPixelsContentOffset().Bottom;
if ( contentH > 0.f ) {
mContainer->setInternalPixelsHeight( contentH );
needResize = true;
}
}
if ( wPercentUnresolved ) {
const StyleSheetProperty* wprop =
grid->getUIStyle()->getProperty( PropertyId::Width );
std::string v = wprop->value();
v.pop_back();
Float pct = 0.f;
String::fromString( pct, v );
Float best = 0.f;
for ( Node* anc = grid->getParent(); anc; anc = anc->getParent() ) {
if ( anc->isWidget() ) {
Float sz = anc->asType<UIWidget>()->getPixelsSize().getWidth();
if ( sz > best )
best = sz;
}
}
UISceneNode* root = grid->getUISceneNode();
if ( root ) {
Float sz = root->getPixelsSize().getWidth();
if ( sz > best )
best = sz;
}
Float resolved = best * pct / 100.f;
if ( resolved > 0.f ) {
mContainer->setInternalPixelsWidth( resolved );
needResize = true;
}
}
}
// Phase 15: set paint-order sort flag
if ( grid ) {
@@ -1097,9 +1165,6 @@ GridLineResult GridLineParser::parse( const std::string& value ) {
static void resolveAxisPlacement( const GridLineResult& start, const GridLineResult& end,
int explicitCount, int& outStart, int& outEnd ) {
outStart = 0;
outEnd = 0;
if ( start.isAuto && end.isAuto )
return;
@@ -1177,8 +1242,10 @@ static void resolveAxisPlacement( const GridLineResult& start, const GridLineRes
}
void GridLayouter::resolveDefinitePlacements() {
int explicitCols = static_cast<int>( mColumns.size() );
int explicitRows = static_cast<int>( mRows.size() );
int explicitCols =
std::max( static_cast<int>( mExplicitColCount ), static_cast<int>( mColumns.size() ) );
int explicitRows =
std::max( static_cast<int>( mExplicitRowCount ), static_cast<int>( mRows.size() ) );
mMaxColumn = explicitCols;
mMaxRow = explicitRows;
@@ -1266,6 +1333,9 @@ void GridLayouter::resolveDefinitePlacements() {
}
void GridLayouter::autoPlaceItems() {
int maxColumns = mMaxColumn;
int maxRows = mMaxRow;
std::set<std::pair<int, int>> occupied;
for ( const auto& item : mItems ) {
if ( item.resolvedColumnStart > 0 && item.resolvedRowStart > 0 ) {
@@ -1332,7 +1402,7 @@ void GridLayouter::autoPlaceItems() {
if ( mAutoFlowDense ) {
// Dense: search from the beginning for first empty cell
for ( int r = 1;; ++r ) {
for ( int c = 1; c <= std::max( mMaxColumn, 1 ); ++c ) {
for ( int c = 1; c <= std::max( maxColumns, 1 ); ++c ) {
if ( fits( r, c, sw, sh ) ) {
item.resolvedRowStart = r;
item.resolvedRowEnd = r + sh;
@@ -1351,7 +1421,7 @@ void GridLayouter::autoPlaceItems() {
} else {
for ( int r = cursorRow;; ++r ) {
int startC = ( r == cursorRow ) ? cursorCol : 1;
for ( int c = startC; c <= std::max( mMaxColumn, 1 ); ++c ) {
for ( int c = startC; c <= std::max( maxColumns, 1 ); ++c ) {
if ( fits( r, c, sw, sh ) ) {
item.resolvedRowStart = r;
item.resolvedRowEnd = r + sh;
@@ -1373,7 +1443,7 @@ void GridLayouter::autoPlaceItems() {
bool placed = false;
if ( mAutoFlowDense ) {
for ( int c = 1;; ++c ) {
for ( int r = 1; r <= std::max( mMaxRow, 1 ); ++r ) {
for ( int r = 1; r <= std::max( maxRows, 1 ); ++r ) {
if ( fits( r, c, sw, sh ) ) {
item.resolvedRowStart = r;
item.resolvedRowEnd = r + sh;
@@ -1392,7 +1462,7 @@ void GridLayouter::autoPlaceItems() {
} else {
for ( int c = cursorCol;; ++c ) {
int startR = ( c == cursorCol ) ? cursorRow : 1;
int rowLimit = std::max( mMaxRow, 1 );
int rowLimit = std::max( maxRows, 1 );
for ( int r = startR; r <= rowLimit; ++r ) {
if ( fits( r, c, sw, sh ) ) {
item.resolvedRowStart = r;
@@ -1413,6 +1483,9 @@ void GridLayouter::autoPlaceItems() {
}
}
// Only update member fields — the local snapshot must NOT grow
// during auto-placement, otherwise later items overflow into
// implicit columns instead of wrapping to the next row.
if ( item.resolvedColumnEnd > mMaxColumn )
mMaxColumn = item.resolvedColumnEnd;
if ( item.resolvedRowEnd > mMaxRow )

View File

@@ -1716,3 +1716,41 @@ UTEST( GridContainer, downloadGridLayout ) {
Engine::destroySingleton();
}
UTEST( GridContainer, newsblurReducedGrid ) {
Engine::instance()->createWindow( WindowSettings( 1024, 650, "UIHTML Grid NewsBlur Test",
WindowStyle::Default, WindowBackend::Default,
32, {}, 1, false, true ),
ContextSettings() );
init_grid_test();
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
sceneNode->setURI( "file://" + Sys::getProcessPath() + "assets/html/" );
std::string htmlContent;
ASSERT_TRUE( FileSystem::fileGet( "assets/html/grid_newsblur_reduced.html", htmlContent ) );
sceneNode->loadLayoutFromString( EE::UI::Tools::HTMLFormatter::HTMLtoXML( htmlContent ) );
sceneNode->update( Seconds( 1 ) );
sceneNode->updateDirtyLayouts();
auto* gridEl = sceneNode->getRoot()->findByClass( "NB-inner" );
ASSERT_TRUE( nullptr != gridEl );
ASSERT_TRUE( gridEl->isWidget() );
Float gridW = gridEl->getPixelsSize().getWidth();
Float gridH = gridEl->getPixelsSize().getHeight();
EXPECT_GT( gridW, 100.f );
EXPECT_GT( gridH, 100.f );
// Verify NB-feature-group items aren't overexpanded
auto* fg = sceneNode->getRoot()->findByClass( "NB-feature-group" );
ASSERT_TRUE( nullptr != fg );
Float fgH = fg->getPixelsSize().getHeight();
EXPECT_GT( fgH, 100.f );
EXPECT_LT( fgH, 2000.f );
auto* fgiText = sceneNode->getRoot()->findByClass( "NB-fgi-text" );
ASSERT_TRUE( nullptr != fgiText );
Float textW = fgiText->getPixelsSize().getWidth();
EXPECT_GT( textW, 150.f );
Engine::destroySingleton();
}