using Sandbox.UI;
namespace UITests.PropertyCoverage;
///
/// Cross-cutting CSS *value syntax* coverage: color forms, length forms, CSS-wide
/// keywords (inherit/initial/unset) and !important. Exercised mostly through
/// 'background-color', 'color' (-> FontColor) and 'width'.
///
[TestClass]
public class ValueSyntaxTest
{
// ----------------------------------------------------------------------------
// COLOR - hex forms
// ----------------------------------------------------------------------------
[TestMethod]
public void Color_Hex_Rgb()
{
var s = new Styles();
bool ok = s.Set( "background-color", "#f00" );
Assert.IsTrue( ok );
Assert.IsTrue( s.BackgroundColor.HasValue );
Assert.AreEqual( new Color( 1, 0, 0, 1 ), s.BackgroundColor.Value );
}
[TestMethod]
public void Color_Hex_Rgba()
{
// #RGBA -> each nibble doubled. #0f08 -> r=00 g=ff b=00 a=88
var s = new Styles();
bool ok = s.Set( "background-color", "#0f08" );
Assert.IsTrue( ok );
Assert.IsTrue( s.BackgroundColor.HasValue );
Assert.AreEqual( 0.0f, s.BackgroundColor.Value.r );
Assert.AreEqual( 1.0f, s.BackgroundColor.Value.g );
Assert.AreEqual( 0.0f, s.BackgroundColor.Value.b );
Assert.AreEqual( 0x88 / 255.0f, s.BackgroundColor.Value.a );
}
[TestMethod]
public void Color_Hex_RrGgBb()
{
var s = new Styles();
bool ok = s.Set( "background-color", "#00ff00" );
Assert.IsTrue( ok );
Assert.AreEqual( new Color( 0, 1, 0, 1 ), s.BackgroundColor.Value );
}
[TestMethod]
public void Color_Hex_RrGgBbAa()
{
// #RRGGBBAA - alpha 0x80
var s = new Styles();
bool ok = s.Set( "background-color", "#ff000080" );
Assert.IsTrue( ok );
Assert.AreEqual( 1.0f, s.BackgroundColor.Value.r );
Assert.AreEqual( 0.0f, s.BackgroundColor.Value.g );
Assert.AreEqual( 0.0f, s.BackgroundColor.Value.b );
Assert.AreEqual( 0x80 / 255.0f, s.BackgroundColor.Value.a );
}
// ----------------------------------------------------------------------------
// COLOR - rgb()/rgba() forms
// ----------------------------------------------------------------------------
[TestMethod]
public void Color_Rgb_LegacyComma()
{
var s = new Styles();
bool ok = s.Set( "background-color", "rgb(255,0,0)" );
Assert.IsTrue( ok );
Assert.AreEqual( new Color( 1, 0, 0, 1 ), s.BackgroundColor.Value );
}
[TestMethod]
public void Color_Rgb_ModernSpace()
{
var s = new Styles();
bool ok = s.Set( "background-color", "rgb(255 0 0)" );
Assert.IsTrue( ok );
Assert.AreEqual( new Color( 1, 0, 0, 1 ), s.BackgroundColor.Value );
}
[TestMethod]
public void Color_Rgb_ModernSpaceAlpha()
{
// rgb( r g b / a )
var s = new Styles();
bool ok = s.Set( "background-color", "rgb(255 0 0 / 0.5)" );
Assert.IsTrue( ok );
Assert.AreEqual( 1.0f, s.BackgroundColor.Value.r );
Assert.AreEqual( 0.0f, s.BackgroundColor.Value.g );
Assert.AreEqual( 0.0f, s.BackgroundColor.Value.b );
Assert.AreEqual( 0.5f, s.BackgroundColor.Value.a );
}
[TestMethod]
public void Color_Rgba_LegacyComma()
{
var s = new Styles();
bool ok = s.Set( "background-color", "rgba(0,0,0,0.1)" );
Assert.IsTrue( ok );
Assert.AreEqual( new Color( 0, 0, 0, 0.1f ), s.BackgroundColor.Value );
}
[TestMethod]
public void Color_Rgb_Percentages()
{
// rgb(100% 0% 0%) -> pure red
var s = new Styles();
bool ok = s.Set( "background-color", "rgb(100% 0% 0%)" );
Assert.IsTrue( ok );
Assert.AreEqual( 1.0f, s.BackgroundColor.Value.r );
Assert.AreEqual( 0.0f, s.BackgroundColor.Value.g );
Assert.AreEqual( 0.0f, s.BackgroundColor.Value.b );
Assert.AreEqual( 1.0f, s.BackgroundColor.Value.a );
}
// ----------------------------------------------------------------------------
// COLOR - hsl()/hsla() forms
// ----------------------------------------------------------------------------
[TestMethod]
public void Color_Hsl_LegacyComma()
{
// hsl(0,100%,50%) -> red
var s = new Styles();
bool ok = s.Set( "background-color", "hsl(0,100%,50%)" );
Assert.IsTrue( ok );
Assert.AreEqual( 1.0f, s.BackgroundColor.Value.r, 0.001f );
Assert.AreEqual( 0.0f, s.BackgroundColor.Value.g, 0.001f );
Assert.AreEqual( 0.0f, s.BackgroundColor.Value.b, 0.001f );
Assert.AreEqual( 1.0f, s.BackgroundColor.Value.a, 0.001f );
}
[TestMethod]
public void Color_Hsl_ModernSpace()
{
// hsl(0 100% 50%) -> red
var s = new Styles();
bool ok = s.Set( "background-color", "hsl(0 100% 50%)" );
Assert.IsTrue( ok );
Assert.AreEqual( 1.0f, s.BackgroundColor.Value.r, 0.001f );
Assert.AreEqual( 0.0f, s.BackgroundColor.Value.g, 0.001f );
Assert.AreEqual( 0.0f, s.BackgroundColor.Value.b, 0.001f );
}
[TestMethod]
public void Color_Hsla_Alpha()
{
// hsla(0,100%,50%,0.5) -> red with half alpha
var s = new Styles();
bool ok = s.Set( "background-color", "hsla(0,100%,50%,0.5)" );
Assert.IsTrue( ok );
Assert.AreEqual( 1.0f, s.BackgroundColor.Value.r, 0.001f );
Assert.AreEqual( 0.5f, s.BackgroundColor.Value.a, 0.001f );
}
// ----------------------------------------------------------------------------
// COLOR - named colors
// ----------------------------------------------------------------------------
[TestMethod]
public void Color_Named_Red()
{
var s = new Styles();
bool ok = s.Set( "background-color", "red" );
Assert.IsTrue( ok );
Assert.AreEqual( new Color( 1, 0, 0, 1 ), s.BackgroundColor.Value );
}
[TestMethod]
public void Color_Named_White()
{
var s = new Styles();
bool ok = s.Set( "background-color", "white" );
Assert.IsTrue( ok );
Assert.AreEqual( new Color( 1, 1, 1, 1 ), s.BackgroundColor.Value );
}
[TestMethod]
public void Color_Named_Black()
{
var s = new Styles();
bool ok = s.Set( "background-color", "black" );
Assert.IsTrue( ok );
Assert.AreEqual( new Color( 0, 0, 0, 1 ), s.BackgroundColor.Value );
}
[TestMethod]
public void Color_Named_RebeccaPurple()
{
// rebeccapurple = #663399
var s = new Styles();
bool ok = s.Set( "background-color", "rebeccapurple" );
Assert.IsTrue( ok );
Assert.AreEqual( Color.FromBytes( 0x66, 0x33, 0x99 ), s.BackgroundColor.Value );
}
[TestMethod]
public void Color_Named_Transparent()
{
// s&box maps "transparent" to "#AAAAAA00" (grey rgb, zero alpha).
// The defining feature is alpha == 0.
var s = new Styles();
bool ok = s.Set( "background-color", "transparent" );
Assert.IsTrue( ok );
Assert.AreEqual( 0.0f, s.BackgroundColor.Value.a );
Assert.AreEqual( 0xAA / 255.0f, s.BackgroundColor.Value.r );
}
// ----------------------------------------------------------------------------
// COLOR - via 'color' shorthand (-> FontColor)
// ----------------------------------------------------------------------------
[TestMethod]
public void Color_ColorAliasesToFontColor()
{
var s = new Styles();
bool ok = s.Set( "color", "red" );
Assert.IsTrue( ok );
Assert.IsTrue( s.FontColor.HasValue );
Assert.AreEqual( new Color( 1, 0, 0, 1 ), s.FontColor.Value );
}
[TestMethod]
public void Color_FontColorHex()
{
var s = new Styles();
bool ok = s.Set( "color", "#00ff00ff" );
Assert.IsTrue( ok );
Assert.AreEqual( new Color( 0, 1, 0, 1 ), s.FontColor.Value );
}
// ----------------------------------------------------------------------------
// COLOR - invalid
// ----------------------------------------------------------------------------
[TestMethod]
public void Color_Invalid()
{
var s = new Styles();
bool ok = s.Set( "background-color", "notacolor" );
Assert.IsFalse( ok );
Assert.IsFalse( s.BackgroundColor.HasValue );
}
// ----------------------------------------------------------------------------
// LENGTH - units (via 'width')
// ----------------------------------------------------------------------------
[TestMethod]
public void Length_Pixels()
{
var s = new Styles();
bool ok = s.Set( "width", "140px" );
Assert.IsTrue( ok );
Assert.IsTrue( s.Width.HasValue );
Assert.AreEqual( 140, s.Width.Value.Value );
Assert.AreEqual( LengthUnit.Pixels, s.Width.Value.Unit );
}
[TestMethod]
public void Length_Percentage()
{
var s = new Styles();
bool ok = s.Set( "width", "50%" );
Assert.IsTrue( ok );
Assert.AreEqual( 50, s.Width.Value.Value );
Assert.AreEqual( LengthUnit.Percentage, s.Width.Value.Unit );
}
[TestMethod]
public void Length_Em()
{
var s = new Styles();
bool ok = s.Set( "width", "2em" );
Assert.IsTrue( ok );
Assert.AreEqual( 2, s.Width.Value.Value );
Assert.AreEqual( LengthUnit.Em, s.Width.Value.Unit );
}
[TestMethod]
public void Length_Rem()
{
var s = new Styles();
bool ok = s.Set( "width", "3rem" );
Assert.IsTrue( ok );
Assert.AreEqual( 3, s.Width.Value.Value );
Assert.AreEqual( LengthUnit.RootEm, s.Width.Value.Unit );
}
[TestMethod]
public void Length_ViewWidth()
{
var s = new Styles();
bool ok = s.Set( "width", "80vw" );
Assert.IsTrue( ok );
Assert.AreEqual( 80, s.Width.Value.Value );
Assert.AreEqual( LengthUnit.ViewWidth, s.Width.Value.Unit );
}
[TestMethod]
public void Length_ViewHeight()
{
var s = new Styles();
bool ok = s.Set( "width", "75vh" );
Assert.IsTrue( ok );
Assert.AreEqual( 75, s.Width.Value.Value );
Assert.AreEqual( LengthUnit.ViewHeight, s.Width.Value.Unit );
}
[TestMethod]
public void Length_ViewMin()
{
var s = new Styles();
bool ok = s.Set( "width", "10vmin" );
Assert.IsTrue( ok );
Assert.AreEqual( 10, s.Width.Value.Value );
Assert.AreEqual( LengthUnit.ViewMin, s.Width.Value.Unit );
}
[TestMethod]
public void Length_ViewMax()
{
var s = new Styles();
bool ok = s.Set( "width", "20vmax" );
Assert.IsTrue( ok );
Assert.AreEqual( 20, s.Width.Value.Value );
Assert.AreEqual( LengthUnit.ViewMax, s.Width.Value.Unit );
}
[TestMethod]
public void Length_UnitlessNumber()
{
// A bare number is treated as pixels.
var s = new Styles();
bool ok = s.Set( "width", "100" );
Assert.IsTrue( ok );
Assert.AreEqual( 100, s.Width.Value.Value );
Assert.AreEqual( LengthUnit.Pixels, s.Width.Value.Unit );
}
[TestMethod]
public void Length_Zero()
{
var s = new Styles();
bool ok = s.Set( "width", "0" );
Assert.IsTrue( ok );
Assert.AreEqual( 0, s.Width.Value.Value );
Assert.AreEqual( LengthUnit.Pixels, s.Width.Value.Unit );
}
[TestMethod]
public void Length_Auto()
{
var s = new Styles();
bool ok = s.Set( "width", "auto" );
Assert.IsTrue( ok );
Assert.IsTrue( s.Width.HasValue );
Assert.AreEqual( LengthUnit.Auto, s.Width.Value.Unit );
}
[TestMethod]
public void Length_Calc()
{
// calc() is stored as a deferred expression.
var s = new Styles();
bool ok = s.Set( "width", "calc(50% - 10px)" );
Assert.IsTrue( ok );
Assert.IsTrue( s.Width.HasValue );
Assert.AreEqual( LengthUnit.Expression, s.Width.Value.Unit );
}
[TestMethod]
public void Length_NegativePixels()
{
var s = new Styles();
bool ok = s.Set( "width", "-25px" );
Assert.IsTrue( ok );
Assert.AreEqual( -25, s.Width.Value.Value );
Assert.AreEqual( LengthUnit.Pixels, s.Width.Value.Unit );
}
[TestMethod]
public void Length_Invalid()
{
var s = new Styles();
bool ok = s.Set( "width", "blah" );
Assert.IsFalse( ok );
Assert.IsFalse( s.Width.HasValue );
}
// ----------------------------------------------------------------------------
// COLOR - currentColor and extended color spaces (oklch/hwb/lab)
// ----------------------------------------------------------------------------
///
/// 'currentColor' is valid CSS on any colour property (it resolves to the element's 'color'), so
/// Set should accept it.
///
[TestMethod]
public void Color_CurrentColor()
{
var s = new Styles();
Assert.IsTrue( s.Set( "color", "currentColor" ) );
Assert.IsTrue( s.Set( "background-color", "currentColor" ) );
Assert.IsTrue( s.Set( "border-color", "currentcolor" ) ); // case-insensitive
}
///
/// 'currentColor' resolves to the element's own computed font colour - here border-color picks up
/// the red set by 'color' on the same element.
///
[TestMethod]
[DoNotParallelize] // Modifies UI System Global
public void CurrentColor_ResolvesToFontColor()
{
var panel = new RootPanel();
var child = panel.Add.Panel( "child" );
panel.StyleSheet.Parse( ".child { color: red; border-color: currentColor; }" );
panel.Layout();
Assert.AreEqual( Color.Red, child.ComputedStyle.BorderLeftColor.Value );
Assert.AreEqual( Color.Red, child.ComputedStyle.BorderTopColor.Value );
Assert.AreEqual( Color.Red, child.ComputedStyle.BorderRightColor.Value );
Assert.AreEqual( Color.Red, child.ComputedStyle.BorderBottomColor.Value );
}
///
/// 'currentColor' nested inside the 'border' shorthand resolves too, since it's parsed through the
/// shared colour parser.
///
[TestMethod]
[DoNotParallelize] // Modifies UI System Global
public void CurrentColor_InBorderShorthand()
{
var panel = new RootPanel();
var child = panel.Add.Panel( "child" );
panel.StyleSheet.Parse( ".child { color: green; border: 2px solid currentColor; }" );
panel.Layout();
// The border colour should match the element's computed colour, whatever 'green' parses to.
Assert.AreEqual( child.ComputedStyle.FontColor.Value, child.ComputedStyle.BorderLeftColor.Value );
}
///
/// 'currentColor' uses the *computed* colour, so it picks up a colour inherited from an ancestor
/// when the element doesn't set its own.
///
[TestMethod]
[DoNotParallelize] // Modifies UI System Global
public void CurrentColor_UsesInheritedColor()
{
var panel = new RootPanel();
var child = panel.Add.Panel( "child" );
panel.StyleSheet.Parse( "RootPanel { color: blue; } .child { background-color: currentColor; }" );
panel.Layout();
Assert.AreEqual( Color.Blue, child.ComputedStyle.BackgroundColor.Value );
}
[TestMethod]
public void Color_Oklch()
{
// Anchors: black, white, and the OKLCH coordinates of pure sRGB red.
Assert.AreEqual( new Color( 0, 0, 0, 1 ), Color.Parse( "oklch(0 0 0)" ).Value );
Assert.AreEqual( new Color( 1, 1, 1, 1 ), Color.Parse( "oklch(1 0 0)" ).Value );
var red = Color.Parse( "oklch(0.627955 0.257683 29.2339)" ).Value;
Assert.AreEqual( 1.0f, red.r, 0.02f );
Assert.AreEqual( 0.0f, red.g, 0.02f );
Assert.AreEqual( 0.0f, red.b, 0.02f );
// The gap input: a teal (red is out of gamut -> clamps to 0).
var s = new Styles();
Assert.IsTrue( s.Set( "background-color", "oklch(0.7 0.15 200)" ) );
Assert.AreEqual( 0.0f, s.BackgroundColor.Value.r, 0.02f );
Assert.IsTrue( s.BackgroundColor.Value.b > 0.5f );
}
[TestMethod]
public void Color_Hwb()
{
// Pure-hue primaries, white/black, and a mid grey - all exact (hwb is in sRGB space directly).
Assert.AreEqual( new Color( 1, 0, 0, 1 ), Color.Parse( "hwb(0 0% 0%)" ).Value );
Assert.AreEqual( new Color( 0, 1, 0, 1 ), Color.Parse( "hwb(120 0% 0%)" ).Value );
Assert.AreEqual( new Color( 0, 0, 1, 1 ), Color.Parse( "hwb(240 0% 0%)" ).Value );
Assert.AreEqual( new Color( 1, 1, 1, 1 ), Color.Parse( "hwb(0 100% 0%)" ).Value );
Assert.AreEqual( new Color( 0, 0, 0, 1 ), Color.Parse( "hwb(0 0% 100%)" ).Value );
var grey = Color.Parse( "hwb(0 50% 50%)" ).Value;
Assert.AreEqual( 0.5f, grey.r, 0.01f );
Assert.AreEqual( 0.5f, grey.g, 0.01f );
Assert.AreEqual( 0.5f, grey.b, 0.01f );
// The gap input: cyan-ish (r=0, full blue).
var s = new Styles();
Assert.IsTrue( s.Set( "background-color", "hwb(194 0% 0%)" ) );
Assert.AreEqual( 0.0f, s.BackgroundColor.Value.r, 0.01f );
Assert.AreEqual( 1.0f, s.BackgroundColor.Value.b, 0.01f );
}
[TestMethod]
public void Color_Lab()
{
// Anchors: black, white, and the (D65) Lab coordinates of pure sRGB red.
Assert.AreEqual( new Color( 0, 0, 0, 1 ), Color.Parse( "lab(0 0 0)" ).Value );
Assert.AreEqual( new Color( 1, 1, 1, 1 ), Color.Parse( "lab(100 0 0)" ).Value );
var red = Color.Parse( "lab(53.2408 80.0925 67.2032)" ).Value;
Assert.AreEqual( 1.0f, red.r, 0.02f );
Assert.AreEqual( 0.0f, red.g, 0.02f );
Assert.AreEqual( 0.0f, red.b, 0.02f );
// The gap input: a reddish-brown.
var s = new Styles();
Assert.IsTrue( s.Set( "background-color", "lab(50% 40 59)" ) );
Assert.IsTrue( s.BackgroundColor.Value.r > 0.5f );
Assert.IsTrue( s.BackgroundColor.Value.b < 0.1f );
}
// ----------------------------------------------------------------------------
// LENGTH - math functions (min/max/clamp)
// ----------------------------------------------------------------------------
///
/// min() math function resolves to the smallest argument against the reference size.
///
[TestMethod]
public void Length_Min()
{
var s = new Styles();
Assert.IsTrue( s.Set( "width", "min(100%,600px)" ) );
Assert.AreEqual( 600, s.Width.Value.GetPixels( 1000 ) );
}
///
/// clamp(min, val, max) constrains val to the [min, max] range.
///
[TestMethod]
public void Length_Clamp()
{
var s = new Styles();
Assert.IsTrue( s.Set( "width", "clamp(10px,50%,300px)" ) );
Assert.AreEqual( 300, s.Width.Value.GetPixels( 1000 ) );
}
///
/// max() math function resolves to the largest argument.
///
[TestMethod]
public void Length_Max()
{
var s = new Styles();
Assert.IsTrue( s.Set( "width", "max(10px,5%)" ) );
Assert.AreEqual( 50, s.Width.Value.GetPixels( 1000 ) );
}
// ----------------------------------------------------------------------------
// LENGTH - dynamic/small/large viewport units (dvh/svh/lvh)
// ----------------------------------------------------------------------------
///
/// Dynamic/small/large viewport units parse (mapped to the static vh equivalent).
///
[TestMethod]
public void Length_Dvh()
{
var s = new Styles();
Assert.IsTrue( s.Set( "width", "100dvh" ) );
Assert.AreEqual( LengthUnit.ViewHeight, s.Width.Value.Unit );
}
[TestMethod]
public void Length_Svh()
{
var s = new Styles();
Assert.IsTrue( s.Set( "width", "100svh" ) );
Assert.AreEqual( LengthUnit.ViewHeight, s.Width.Value.Unit );
}
[TestMethod]
public void Length_Lvh()
{
var s = new Styles();
Assert.IsTrue( s.Set( "width", "100lvh" ) );
Assert.AreEqual( LengthUnit.ViewHeight, s.Width.Value.Unit );
}
// ----------------------------------------------------------------------------
// !important
// ----------------------------------------------------------------------------
[TestMethod]
public void Width_ImportantSuffixTolerated()
{
// Styles.Set strips a trailing !important for ALL properties before parsing the value
// (see Styles.Set.cs), so the declaration still applies. (It is NOT honoured as a
// cascade override - just tolerated.)
var s = new Styles();
Assert.IsTrue( s.Set( "width", "100px !important" ) );
Assert.AreEqual( 100, s.Width.Value.Value );
}
///
/// !important is stripped so the declaration applies (we don't implement cascade priority).
///
[TestMethod]
public void Important_KeywordPropertyApplied()
{
var s = new Styles();
Assert.IsTrue( s.Set( "display", "flex !important" ) );
Assert.AreEqual( DisplayMode.Flex, s.Display );
}
// ----------------------------------------------------------------------------
// CSS-wide keywords (inherit / initial / unset / revert)
// ----------------------------------------------------------------------------
///
/// The CSS-wide keywords are valid on any property that maps to a single field, so Set should
/// accept them rather than dropping the declaration. (Shorthands like 'flex' keep handling their
/// own keyword values - e.g. 'flex: initial' - so they aren't covered here.)
///
[TestMethod]
public void Global_KeywordsAccepted()
{
var s = new Styles();
Assert.IsTrue( s.Set( "display", "inherit" ) );
Assert.IsTrue( s.Set( "color", "initial" ) );
Assert.IsTrue( s.Set( "width", "unset" ) );
Assert.IsTrue( s.Set( "background-color", "revert" ) );
// case-insensitive
Assert.IsTrue( s.Set( "opacity", "INHERIT" ) );
}
///
/// 'initial' resets a property to its specification default, overriding inheritance - color is
/// normally inherited, so without 'initial' the child would be red.
///
[TestMethod]
[DoNotParallelize] // Modifies UI System Global
public void Global_InitialOverridesInheritance()
{
var panel = new RootPanel();
var child = panel.Add.Panel( "child" );
panel.StyleSheet.Parse( "RootPanel { color: red; } .child { color: initial; }" );
panel.Layout();
Assert.AreEqual( Color.Black, child.ComputedStyle.FontColor.Value );
}
///
/// 'inherit' pulls the parent's computed value even for a property that doesn't normally inherit -
/// background-color isn't inherited, so without 'inherit' the child would be transparent.
///
[TestMethod]
[DoNotParallelize] // Modifies UI System Global
public void Global_InheritFromParent()
{
var panel = new RootPanel();
var child = panel.Add.Panel( "child" );
panel.StyleSheet.Parse( "RootPanel { background-color: red; } .child { background-color: inherit; }" );
panel.Layout();
Assert.AreEqual( Color.Red, child.ComputedStyle.BackgroundColor.Value );
}
///
/// 'unset' acts like 'initial' on a non-inherited property, so it resets an earlier rule's value
/// back to the default (transparent) rather than keeping the red.
///
[TestMethod]
[DoNotParallelize] // Modifies UI System Global
public void Global_UnsetResetsToInitial()
{
var panel = new RootPanel();
var child = panel.Add.Panel( "child" );
panel.StyleSheet.Parse( ".child { background-color: red; } RootPanel .child { background-color: unset; }" );
panel.Layout();
Assert.AreEqual( Color.Transparent, child.ComputedStyle.BackgroundColor.Value );
}
///
/// 'unset' on an inherited property (color) acts like 'inherit', so it takes the parent's value
/// even though the child's own rule sets green.
///
[TestMethod]
[DoNotParallelize] // Modifies UI System Global
public void Global_UnsetInheritsForInheritedProperty()
{
var panel = new RootPanel();
var child = panel.Add.Panel( "child" );
panel.StyleSheet.Parse( "RootPanel { color: red; } .child { color: green; } RootPanel .child { color: unset; }" );
panel.Layout();
Assert.AreEqual( Color.Red, child.ComputedStyle.FontColor.Value );
}
// ----------------------------------------------------------------------------
// CSS-wide keywords on shorthands (expanded to their longhands)
// ----------------------------------------------------------------------------
///
/// A CSS-wide keyword on a shorthand applies to each longhand. 'overflow' isn't inherited, so
/// without 'inherit' the child would be Visible; here both axes take the parent's Hidden.
///
[TestMethod]
[DoNotParallelize] // Modifies UI System Global
public void Global_ShorthandOverflowInherit()
{
var panel = new RootPanel();
var child = panel.Add.Panel( "child" );
panel.StyleSheet.Parse( "RootPanel { overflow: hidden; } .child { overflow: inherit; }" );
panel.Layout();
Assert.AreEqual( OverflowMode.Hidden, child.ComputedStyle.OverflowX.Value );
Assert.AreEqual( OverflowMode.Hidden, child.ComputedStyle.OverflowY.Value );
}
///
/// 'overflow: initial' resets both axes to the default (Visible), overriding the earlier rule -
/// and crucially does so without the spurious 'Unhandled overflow property' warning the old
/// fall-through produced.
///
[TestMethod]
[DoNotParallelize] // Modifies UI System Global
public void Global_ShorthandOverflowInitial()
{
var panel = new RootPanel();
var child = panel.Add.Panel( "child" );
panel.StyleSheet.Parse( ".child { overflow: hidden; } RootPanel .child { overflow: initial; }" );
panel.Layout();
Assert.AreEqual( OverflowMode.Visible, child.ComputedStyle.OverflowX.Value );
Assert.AreEqual( OverflowMode.Visible, child.ComputedStyle.OverflowY.Value );
}
///
/// The box-model shorthand 'margin' expands to all four edges, none of which inherit by default,
/// so 'margin: inherit' makes every edge match the parent's computed margin.
///
[TestMethod]
[DoNotParallelize] // Modifies UI System Global
public void Global_ShorthandMarginInherit()
{
var panel = new RootPanel();
var child = panel.Add.Panel( "child" );
panel.StyleSheet.Parse( "RootPanel { margin: 7px; } .child { margin: inherit; }" );
panel.Layout();
var expected = panel.ComputedStyle.MarginLeft.Value.Value;
Assert.AreNotEqual( 0, expected ); // sanity: the parent's margin actually applied
Assert.AreEqual( expected, child.ComputedStyle.MarginLeft.Value.Value );
Assert.AreEqual( expected, child.ComputedStyle.MarginTop.Value.Value );
Assert.AreEqual( expected, child.ComputedStyle.MarginRight.Value.Value );
Assert.AreEqual( expected, child.ComputedStyle.MarginBottom.Value.Value );
}
///
/// Last-declaration-wins is preserved per-longhand after expansion: 'overflow: inherit' followed
/// by 'overflow-x: hidden' inherits y but overrides x.
///
[TestMethod]
[DoNotParallelize] // Modifies UI System Global
public void Global_ShorthandKeywordThenLonghandValue()
{
var panel = new RootPanel();
var child = panel.Add.Panel( "child" );
panel.StyleSheet.Parse( "RootPanel { overflow: scroll; } .child { overflow: inherit; overflow-x: hidden; }" );
panel.Layout();
Assert.AreEqual( OverflowMode.Hidden, child.ComputedStyle.OverflowX.Value );
Assert.AreEqual( OverflowMode.Scroll, child.ComputedStyle.OverflowY.Value );
}
///
/// 'initial'/'unset' on a non-inherited string property must resolve to the property's real
/// default ("none" for animation-name), never null.
///
[TestMethod]
[DoNotParallelize] // Modifies UI System Global
public void Global_StringPropertyInitialIsNotNull()
{
var panel = new RootPanel();
var child = panel.Add.Panel( "child" );
panel.StyleSheet.Parse( ".child { animation-name: initial; }" );
panel.Layout();
Assert.AreEqual( "none", child.ComputedStyle.AnimationName );
}
///
/// 'flex' keeps handling initial/none/auto natively (it has no single backing field), so it is
/// NOT intercepted as a CSS-wide keyword. 'flex: initial' must still expand to grow 0 / shrink 1.
///
[TestMethod]
public void Global_FlexInitialStaysNative()
{
var s = new Styles();
Assert.IsTrue( s.Set( "flex", "initial" ) );
Assert.AreEqual( 0f, s.FlexGrow );
Assert.AreEqual( 1f, s.FlexShrink );
}
///
/// Every longhand in the shorthand expansion table must resolve to a real backing field, or the
/// keyword would silently no-op for that longhand.
///
[TestMethod]
public void Global_ShorthandExpansionsResolveToFields()
{
foreach ( var shorthand in BaseStyles.ShorthandExpansions )
{
foreach ( var longhand in shorthand.Value )
Assert.IsNotNull( BaseStyles.GetStyleField( longhand ), $"{shorthand.Key} -> {longhand} has no backing field" );
}
}
///
/// Clone must deep-copy the recorded keywords - otherwise a clone would share the dictionary and
/// clearing the keyword on the original (here via 'color: red') would also wipe it from the clone.
///
[TestMethod]
public void Global_CloneDeepCopiesKeywords()
{
var s = new Styles();
s.Set( "color", "inherit" );
var clone = (Styles)s.Clone();
s.Set( "color", "red" ); // clears the keyword on the original only
Assert.IsNotNull( clone.CssWide );
Assert.IsTrue( clone.CssWide.ContainsKey( "font-color" ) );
}
}