.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>
57 lines
2.8 KiB
C#
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 <td>/<th> 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();
|
|
}
|