Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions src/ExCSS.Tests/GridLineGrammarTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System.Collections.Generic;
using Xunit;

namespace ExCSS.Tests
{
/// <summary>Tests for the shared <see cref="GridLineGrammar"/> — the grid <c>&lt;grid-line&gt;</c> value
/// (<c>auto | &lt;integer&gt; | span &lt;integer&gt;</c>).</summary>
public class GridLineGrammarTests : CssConstructionFunctions
{
private static IReadOnlyList<Token> Tokens(string value)
{
var lexer = new Lexer(new TextSource(value));
var tokens = new List<Token>();
var token = lexer.Get();
while (token.Type != TokenType.EndOfFile)
{
tokens.Add(token);
token = lexer.Get();
}
return tokens;
}

private static GridLine Parse(string value) =>
GridLineGrammar.TryParse(Tokens(value));

[Fact]
public void Auto_Parses()
{
var line = Parse("auto");
Assert.NotNull(line);
Assert.True(line.IsAuto);
}

[Theory]
[InlineData("1", 1)]
[InlineData("3", 3)]
[InlineData("-1", -1)]
public void IntegerLine_Parses(string value, int expected)
{
var line = Parse(value);
Assert.NotNull(line);
Assert.False(line.IsAuto);
Assert.False(line.IsSpan);
Assert.Equal(expected, line.Value);
}

[Theory]
[InlineData("span 1", 1)]
[InlineData("span 3", 3)]
public void Span_Parses(string value, int expected)
{
var line = Parse(value);
Assert.NotNull(line);
Assert.True(line.IsSpan);
Assert.Equal(expected, line.Value);
}

[Theory]
[InlineData("sidebar")]
[InlineData("main-start")]
public void NamedLine_Parses(string value)
{
var line = Parse(value);
Assert.NotNull(line);
Assert.False(line.IsAuto);
Assert.False(line.IsSpan);
Assert.Equal(value, line.Name);
Assert.Equal(1, line.Value);
}

[Theory]
[InlineData("col 2", "col", 2)]
[InlineData("2 col", "col", 2)]
[InlineData("col -1", "col", -1)]
public void NamedNthLine_Parses(string value, string name, int nth)
{
var line = Parse(value);
Assert.NotNull(line);
Assert.Equal(name, line.Name);
Assert.Equal(nth, line.Value);
}

[Theory]
[InlineData("")]
[InlineData("0")] // line 0 is invalid
[InlineData("span 0")] // span must be >= 1
[InlineData("span")] // span needs a count
[InlineData("span auto")]
[InlineData("1.5")] // not an integer
[InlineData("[name]")] // bracketed line names belong in a track list, not a <grid-line>
[InlineData("none")] // a CSS-wide/reserved keyword is not a custom-ident line name
[InlineData("initial")]
[InlineData("span foo")] // span <custom-ident> is out of v1 scope
public void Invalid_ReturnsNull(string value)
{
Assert.Null(Parse(value));
}
}
}
74 changes: 74 additions & 0 deletions src/ExCSS.Tests/GridTemplateAreasGrammarTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.Collections.Generic;
using Xunit;

namespace ExCSS.Tests
{
/// <summary>Tests for the shared <see cref="GridTemplateAreasGrammar"/> — the
/// <c>grid-template-areas</c> value (a rectangular grid of named cells).</summary>
public class GridTemplateAreasGrammarTests : CssConstructionFunctions
{
private static IReadOnlyList<Token> Tokens(string value)
{
var lexer = new Lexer(new TextSource(value));
var tokens = new List<Token>();
var token = lexer.Get();
while (token.Type != TokenType.EndOfFile)
{
tokens.Add(token);
token = lexer.Get();
}
return tokens;
}

private static GridAreas Parse(string value) =>
GridTemplateAreasGrammar.TryParse(Tokens(value));

[Fact]
public void RectangularGrid_ParsesWithAreaBounds()
{
var areas = Parse("\"header header header\" \"nav main main\" \"footer footer footer\"");
Assert.NotNull(areas);
Assert.Equal(3, areas.RowCount);
Assert.Equal(3, areas.ColCount);
Assert.Equal((0, 0, 0, 2), areas.Areas["header"]); // whole top row
Assert.Equal((1, 0, 1, 0), areas.Areas["nav"]); // row 1, col 0
Assert.Equal((1, 1, 1, 2), areas.Areas["main"]); // row 1, cols 1-2
Assert.Equal((2, 0, 2, 2), areas.Areas["footer"]);
}

[Fact]
public void DotCells_AreEmpty_AndNotAreas()
{
var areas = Parse("\"a . b\" \". . .\"");
Assert.NotNull(areas);
Assert.Equal(2, areas.RowCount);
Assert.Equal(3, areas.ColCount);
Assert.True(areas.Areas.ContainsKey("a"));
Assert.True(areas.Areas.ContainsKey("b"));
Assert.Null(areas.Cells[0, 1]);
Assert.Null(areas.Cells[1, 0]);
}

[Fact]
public void TripleDot_IsAlsoAnEmptyCell()
{
var areas = Parse("\"a ... b\"");
Assert.NotNull(areas);
Assert.Null(areas.Cells[0, 1]);
}

[Theory]
[InlineData("\"a a\" \"a a a\"")] // ragged rows (2 vs 3 columns)
[InlineData("\"a b\" \"b a\"")] // 'a' is not a rectangle (diagonal)
[InlineData("\"a a\" \"a .\"")] // 'a' bounding box includes an empty cell → not filled
[InlineData("\"a b a\"")] // 'a' occupies cols 0 and 2 with b between → not rectangular
[InlineData("100px")] // not a string list
[InlineData("\"\"")] // an empty string row
[InlineData("\"a.b c\"")] // a cell mixing a name and a dot is not a valid cell token
[InlineData("")]
public void Invalid_ReturnsNull(string value)
{
Assert.Null(Parse(value));
}
}
}
153 changes: 153 additions & 0 deletions src/ExCSS.Tests/GridTemplateShorthandTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
using System.Linq;
using Xunit;

namespace ExCSS.Tests
{
/// <summary>
/// The <c>grid</c> / <c>grid-template</c> mega-shorthands (CSS Grid §7.4 / §7.8) parse and expand to the
/// grid longhands at parse time, and — like the other reconstruction-excluded shorthands — are never
/// re-collapsed when a declaration block is serialized.
/// </summary>
public class GridTemplateShorthandTests : CssConstructionFunctions
{
private static StyleDeclaration Style(string declaration) =>
(StyleDeclaration)ParseStyleSheet($"div {{ {declaration} }}").Rules.OfType<StyleRule>().Single().Style;

// grid-template

[Fact]
public void GridTemplate_None_ResetsAllThree()
{
var style = Style("grid-template: none;");
Assert.Equal("none", style.GetPropertyValue("grid-template-rows"));
Assert.Equal("none", style.GetPropertyValue("grid-template-columns"));
Assert.Equal("none", style.GetPropertyValue("grid-template-areas"));
}

[Fact]
public void GridTemplate_RowsSlashColumns_SetsBothAxesAreasNone()
{
var style = Style("grid-template: 1fr 2fr / 100px 200px;");
Assert.Equal("1fr 2fr", style.GetPropertyValue("grid-template-rows"));
Assert.Equal("100px 200px", style.GetPropertyValue("grid-template-columns"));
// An omitted axis is reset to its initial value (via the CSS-wide `initial` keyword).
Assert.Equal("initial", style.GetPropertyValue("grid-template-areas"));
}

[Fact]
public void GridTemplate_AreasForm_SynthesizesRowsAndColumns()
{
var style = Style("grid-template: \"a a\" 40px \"b b\" / 1fr 1fr;");
Assert.Equal("\"a a\" \"b b\"", style.GetPropertyValue("grid-template-areas"));
Assert.Equal("40px auto", style.GetPropertyValue("grid-template-rows"));
Assert.Equal("1fr 1fr", style.GetPropertyValue("grid-template-columns"));
}

[Fact]
public void GridTemplate_AreasForm_WithLineNames_PreservesThem()
{
var style = Style("grid-template: [r1] \"a\" 10px [r2] \"b\" [r3];");
Assert.Equal("\"a\" \"b\"", style.GetPropertyValue("grid-template-areas"));
Assert.Equal("[r1] 10px [r2] auto [r3]", style.GetPropertyValue("grid-template-rows"));
// No explicit column track list → columns reset to its initial value.
Assert.Equal("initial", style.GetPropertyValue("grid-template-columns"));
}

[Theory]
// `none` is a valid <grid-template-rows>/<grid-template-columns> value on either side of the slash.
[InlineData("grid-template: none / 1fr 1fr;", "none", "1fr 1fr")]
[InlineData("grid-template: 1fr 2fr / none;", "1fr 2fr", "none")]
public void GridTemplate_NoneOnOneAxis_IsAccepted(string declaration, string expectedRows, string expectedColumns)
{
var style = Style(declaration);
Assert.Equal(expectedRows, style.GetPropertyValue("grid-template-rows"));
Assert.Equal(expectedColumns, style.GetPropertyValue("grid-template-columns"));
}

// grid

[Fact]
public void Grid_TemplateForm_ResetsAutoProperties()
{
var style = Style("grid: \"a\" / 1fr;");
Assert.Equal("\"a\"", style.GetPropertyValue("grid-template-areas"));
Assert.Equal("auto", style.GetPropertyValue("grid-template-rows"));
Assert.Equal("1fr", style.GetPropertyValue("grid-template-columns"));
// The grid-auto-* longhands are reset to their initial values (via the CSS-wide `initial`).
Assert.Equal("initial", style.GetPropertyValue("grid-auto-flow"));
Assert.Equal("initial", style.GetPropertyValue("grid-auto-rows"));
Assert.Equal("initial", style.GetPropertyValue("grid-auto-columns"));
}

[Fact]
public void Grid_ColumnAutoFlowForm_SetsRowsFlowAndAutoColumns()
{
var style = Style("grid: 1fr / auto-flow 2fr;");
Assert.Equal("1fr", style.GetPropertyValue("grid-template-rows"));
Assert.Equal("column", style.GetPropertyValue("grid-auto-flow"));
Assert.Equal("2fr", style.GetPropertyValue("grid-auto-columns"));
Assert.Equal("initial", style.GetPropertyValue("grid-template-columns"));
}

[Fact]
public void Grid_RowAutoFlowForm_WithDense_SetsColumnsFlowAndAutoRows()
{
var style = Style("grid: auto-flow dense 10px / 1fr;");
Assert.Equal("1fr", style.GetPropertyValue("grid-template-columns"));
Assert.Equal("row dense", style.GetPropertyValue("grid-auto-flow"));
Assert.Equal("10px", style.GetPropertyValue("grid-auto-rows"));
}

[Fact]
public void Grid_NoneRows_WithColumnAutoFlow_IsAccepted()
{
// The <grid-template-rows> side of the auto-flow form also accepts `none`.
var style = Style("grid: none / auto-flow 1fr;");
Assert.Equal("none", style.GetPropertyValue("grid-template-rows"));
Assert.Equal("column", style.GetPropertyValue("grid-auto-flow"));
Assert.Equal("1fr", style.GetPropertyValue("grid-auto-columns"));
}

// rejection (whole declaration dropped)

[Theory]
[InlineData("grid-template: 10px \"a\";")] // track-size before the first string
[InlineData("grid-template: \"a\" repeat(2, 1fr);")] // repeat() is not a valid row <track-size>
[InlineData("grid-template: \"a\" \"b\" / repeat(2, 1fr);")] // trailing columns are an <explicit-track-list> (no repeat())
[InlineData("grid-template: \"a a\" \"b\";")] // ragged area rows
[InlineData("grid-template: 1fr / 2fr / 3fr;")] // two top-level slashes
[InlineData("grid-template: 1fr 2fr;")] // a bare track list is not a valid grid-template
[InlineData("grid: auto-flow / auto-flow;")] // auto-flow on both sides
[InlineData("grid: dense 10px / 1fr;")] // dense without auto-flow
public void InvalidValue_IsDropped(string declaration)
{
var style = Style(declaration);
Assert.Equal(string.Empty, style.GetPropertyValue("grid-template-rows"));
Assert.Equal(string.Empty, style.GetPropertyValue("grid-template-columns"));
}

// serialization: never reconstructed

[Fact]
public void Expanded_DoesNotReconstructMegaShorthand()
{
var sheet = ParseStyleSheet("div { grid: auto-flow dense 10px / 1fr; }");
var css = sheet.ToCss();

Assert.DoesNotContain("grid:", css);
Assert.DoesNotContain("grid-template:", css);
Assert.Contains("grid-auto-flow", css);
Assert.Contains("grid-template-columns", css);
}

// var() is kept whole and deferred to cascade time

[Fact]
public void GridTemplate_WithVar_IsNotExpandedAtParseTime()
{
var style = Style("grid-template: var(--t);");
// The shorthand stays whole (var() can't be sliced at parse time), so the longhands are not set.
Assert.Equal(string.Empty, style.GetPropertyValue("grid-template-rows"));
}
}
}
Loading