@@ -1668,4 +1668,158 @@ public void Layout_Snake_PrismTheme_ProducesVisibleNonWhiteFillColors()
16681668 Assert . False ( ColorUtils . IsPaletteMonochrome ( segmentColors , Theme . Prism . BackgroundColor ) ,
16691669 "Segment colors must not be monochrome relative to the Prism background." ) ;
16701670 }
1671+
1672+ // ── TabList ───────────────────────────────────────────────────────────────
1673+
1674+ private static Diagram CreateTabListDiagram ( string layout , int categoryCount = 3 , int itemsPerCategory = 2 )
1675+ {
1676+ var diagram = new Diagram { DiagramType = "tablist" , SourceSyntax = "conceptual" } ;
1677+
1678+ for ( int c = 0 ; c < categoryCount ; c ++ )
1679+ {
1680+ var titleNode = new Node ( $ "tab_{ c } ", $ "Category { c + 1 } ") ;
1681+ titleNode . Metadata [ "tablist:kind" ] = "title" ;
1682+ titleNode . Metadata [ "tablist:categoryIndex" ] = c ;
1683+ titleNode . Metadata [ "tablist:layout" ] = layout ;
1684+ diagram . AddNode ( titleNode ) ;
1685+
1686+ for ( int i = 0 ; i < itemsPerCategory ; i ++ )
1687+ {
1688+ var itemNode = new Node ( $ "tab_{ c } _item_{ i } ", $ "Item { c + 1 } .{ i + 1 } ") ;
1689+ itemNode . Metadata [ "tablist:kind" ] = "item" ;
1690+ itemNode . Metadata [ "tablist:categoryIndex" ] = c ;
1691+ itemNode . Metadata [ "tablist:itemIndex" ] = i ;
1692+ itemNode . Metadata [ "tablist:layout" ] = layout ;
1693+ diagram . AddNode ( itemNode ) ;
1694+ }
1695+ }
1696+
1697+ return diagram ;
1698+ }
1699+
1700+ [ Theory ]
1701+ [ InlineData ( "cards" ) ]
1702+ [ InlineData ( "stacked" ) ]
1703+ [ InlineData ( "flat" ) ]
1704+ public void Layout_TabList_AllVariants_TitleNodesGetPositiveSize ( string layout )
1705+ {
1706+ var diagram = CreateTabListDiagram ( layout ) ;
1707+
1708+ _engine . Layout ( diagram , _theme ) ;
1709+
1710+ var titleNodes = diagram . Nodes . Values
1711+ . Where ( n => n . Metadata . TryGetValue ( "tablist:kind" , out var k ) && "title" . Equals ( k as string , StringComparison . Ordinal ) )
1712+ . ToList ( ) ;
1713+
1714+ Assert . True ( titleNodes . Count > 0 ) ;
1715+ Assert . All ( titleNodes , n =>
1716+ {
1717+ Assert . True ( n . Width > 0 , $ "Title node { n . Id } Width should be > 0") ;
1718+ Assert . True ( n . Height > 0 , $ "Title node { n . Id } Height should be > 0") ;
1719+ } ) ;
1720+ }
1721+
1722+ [ Theory ]
1723+ [ InlineData ( "cards" ) ]
1724+ [ InlineData ( "stacked" ) ]
1725+ [ InlineData ( "flat" ) ]
1726+ public void Layout_TabList_PrismTheme_TitleNodesHaveChromaticFills ( string layout )
1727+ {
1728+ // Prism has an all-white NodePalette. The layout must fall back to a
1729+ // chromatic palette so accent fills, tab fills, and bar fills are visible.
1730+ var diagram = CreateTabListDiagram ( layout ) ;
1731+
1732+ _engine . Layout ( diagram , Theme . Prism ) ;
1733+
1734+ var titleNodes = diagram . Nodes . Values
1735+ . Where ( n => n . Metadata . TryGetValue ( "tablist:kind" , out var k ) && "title" . Equals ( k as string , StringComparison . Ordinal ) )
1736+ . ToList ( ) ;
1737+
1738+ Assert . True ( titleNodes . Count > 0 ) ;
1739+
1740+ // Each title node's fill must differ from the white Prism background.
1741+ Assert . All ( titleNodes , n =>
1742+ {
1743+ Assert . NotNull ( n . FillColor ) ;
1744+ Assert . NotEqual ( Theme . Prism . BackgroundColor , n . FillColor , StringComparer . OrdinalIgnoreCase ) ;
1745+ } ) ;
1746+
1747+ // The set of fill colors across title nodes must not be monochrome.
1748+ var fills = titleNodes . Select ( n => n . FillColor ! ) . ToList ( ) ;
1749+ Assert . False ( ColorUtils . IsPaletteMonochrome ( fills , Theme . Prism . BackgroundColor ) ,
1750+ $ "TabList '{ layout } ' fill colors must not be monochrome with Prism theme.") ;
1751+ }
1752+
1753+ [ Theory ]
1754+ [ InlineData ( "cards" ) ]
1755+ [ InlineData ( "stacked" ) ]
1756+ [ InlineData ( "flat" ) ]
1757+ public void Layout_TabList_PrismTheme_AccentAndContentColorsAreChromaticInMetadata ( string layout )
1758+ {
1759+ // The layout stores accent/content colors in node metadata; these must also
1760+ // be chromatic so the rendered output is visible against Prism's white background.
1761+ var diagram = CreateTabListDiagram ( layout ) ;
1762+
1763+ _engine . Layout ( diagram , Theme . Prism ) ;
1764+
1765+ var titleNodes = diagram . Nodes . Values
1766+ . Where ( n => n . Metadata . TryGetValue ( "tablist:kind" , out var k ) && "title" . Equals ( k as string , StringComparison . Ordinal ) )
1767+ . ToList ( ) ;
1768+
1769+ Assert . True ( titleNodes . Count > 0 ) ;
1770+
1771+ foreach ( var n in titleNodes )
1772+ {
1773+ // contentFill is required for cards and stacked; optional (not expected) for flat.
1774+ if ( layout is "cards" or "stacked" )
1775+ {
1776+ Assert . True ( n . Metadata . TryGetValue ( "tablist:contentFill" , out var requiredCf ) && requiredCf is string ,
1777+ $ "tablist:contentFill must be set for '{ layout } ' layout.") ;
1778+ var requiredContentFill = n . Metadata [ "tablist:contentFill" ] as string ;
1779+ Assert . NotEqual ( Theme . Prism . BackgroundColor , requiredContentFill , StringComparer . OrdinalIgnoreCase ) ;
1780+ }
1781+ else
1782+ {
1783+ if ( n . Metadata . TryGetValue ( "tablist:contentFill" , out var cf ) && cf is string contentFill )
1784+ Assert . NotEqual ( Theme . Prism . BackgroundColor , contentFill , StringComparer . OrdinalIgnoreCase ) ;
1785+ }
1786+
1787+ // accentColor is required for flat layout; optional for others.
1788+ if ( string . Equals ( layout , "flat" , StringComparison . OrdinalIgnoreCase ) )
1789+ {
1790+ Assert . True ( n . Metadata . TryGetValue ( "tablist:accentColor" , out var requiredAc ) && requiredAc is string ,
1791+ "tablist:accentColor must be set for 'flat' layout." ) ;
1792+ var requiredAccentColor = n . Metadata [ "tablist:accentColor" ] as string ;
1793+ Assert . NotEqual ( Theme . Prism . BackgroundColor , requiredAccentColor , StringComparer . OrdinalIgnoreCase ) ;
1794+ }
1795+ else if ( n . Metadata . TryGetValue ( "tablist:accentColor" , out var ac ) && ac is string accentColor )
1796+ {
1797+ Assert . NotEqual ( Theme . Prism . BackgroundColor , accentColor , StringComparer . OrdinalIgnoreCase ) ;
1798+ }
1799+ }
1800+ }
1801+
1802+ [ Theory ]
1803+ [ InlineData ( "cards" ) ]
1804+ [ InlineData ( "stacked" ) ]
1805+ [ InlineData ( "flat" ) ]
1806+ public void Layout_TabList_DefaultTheme_IsUnaffected ( string layout )
1807+ {
1808+ // Non-Prism themes with chromatic palettes must continue to work as before.
1809+ var diagram = CreateTabListDiagram ( layout ) ;
1810+
1811+ _engine . Layout ( diagram , Theme . Default ) ;
1812+
1813+ var titleNodes = diagram . Nodes . Values
1814+ . Where ( n => n . Metadata . TryGetValue ( "tablist:kind" , out var k ) && "title" . Equals ( k as string , StringComparison . Ordinal ) )
1815+ . ToList ( ) ;
1816+
1817+ Assert . True ( titleNodes . Count > 0 ) ;
1818+ Assert . All ( titleNodes , n =>
1819+ {
1820+ Assert . NotNull ( n . FillColor ) ;
1821+ Assert . True ( n . Width > 0 ) ;
1822+ Assert . True ( n . Height > 0 ) ;
1823+ } ) ;
1824+ }
16711825}
0 commit comments