sqlinsight-pro/tests/SQLInsight.Community.Tests/TableMarkupTests.cs
Søren Eggert Lundsteen Olsen 2c49a1e1b1 Keep severity badge cells as table cells on both overviews
.counts is display: flex. On the <td> itself it made the cell a flex box
with its own height, so the row divider jumped in the findings column of
SQLInsight Web's overview and the hub portal's customer overview. The
class now sits on a <div> inside the cell; the CSS rule is unchanged.

TableMarkupTests checks Web, Hub and Community: no <td>/<th> may carry a
class whose rule sets display flex or grid.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 15:39:36 +02:00

57 lines
2.8 KiB
C#

using System.Text.RegularExpressions;
namespace SQLInsight.Community.Tests;
/// <summary>
/// A table cell must stay a table cell. A class that sets display: flex or grid turns a &lt;td&gt;/&lt;th&gt; into a flex box,
/// so it gets its own height and border and the row's divider jumps (the severity badges on the overviews did that).
/// Such classes belong on an element inside the cell. Checked for every site's stylesheet against its own pages.
/// </summary>
public sealed partial class TableMarkupTests
{
[Theory]
[InlineData("SQLInsight.Web")]
[InlineData("SQLInsight.Hub")]
[InlineData("SQLInsight.Community")]
public void No_table_cell_has_a_flex_or_grid_class(string project)
{
var folder = Path.Combine(RepositoryRoot(), "src", project);
var layoutClasses = Directory.EnumerateFiles(Path.Combine(folder, "wwwroot"), "*.css", SearchOption.AllDirectories)
.SelectMany(css => FlexOrGridRule().Matches(File.ReadAllText(css)))
.SelectMany(rule => ClassName().Matches(rule.Groups["selectors"].Value).Select(c => c.Groups[1].Value))
.ToHashSet(StringComparer.Ordinal);
var offenders = Directory.EnumerateFiles(folder, "*.razor", SearchOption.AllDirectories)
.Where(file => !file.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}", StringComparison.Ordinal)
&& !file.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}", StringComparison.Ordinal))
.SelectMany(file => CellClass().Matches(File.ReadAllText(file))
.SelectMany(cell => cell.Groups["classes"].Value.Split(' ', StringSplitOptions.RemoveEmptyEntries))
.Where(layoutClasses.Contains)
.Select(c => $"{Path.GetFileName(file)}: <td/th class=\"{c}\">"))
.ToList();
Assert.True(offenders.Count == 0, "Flyt klassen ind på et element i cellen: " + string.Join(", ", offenders));
}
private static string RepositoryRoot()
{
var root = AppContext.BaseDirectory;
while (!File.Exists(Path.Combine(root, "SQLInsight.slnx")))
{
root = Path.GetDirectoryName(root) ?? throw new InvalidOperationException("SQLInsight.slnx ikke fundet.");
}
return root;
}
// A rule whose declarations set display to (inline-)flex or (inline-)grid; media queries are fine, their inner rules match.
[GeneratedRegex(@"(?<selectors>[^{}]+)\{[^{}]*\bdisplay\s*:\s*(inline-)?(flex|grid)\b")]
private static partial Regex FlexOrGridRule();
[GeneratedRegex(@"\.([A-Za-z][\w-]*)")]
private static partial Regex ClassName();
// Literal class names only; Razor expressions (@…) in the attribute are skipped by splitting on spaces.
[GeneratedRegex(@"<t[dh]\b[^>]*\bclass=""(?<classes>[^""]*)""")]
private static partial Regex CellClass();
}