From 2c49a1e1b1d723cfe619ca0130bbbc3480872603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Eggert=20Lundsteen=20Olsen?= Date: Thu, 1 Oct 2026 15:39:36 +0200 Subject: [PATCH] Keep severity badge cells as table cells on both overviews .counts is display: flex. On the 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
inside the cell; the CSS rule is unchanged. TableMarkupTests checks Web, Hub and Community: no / may carry a class whose rule sets display flex or grid. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../Portal/Pages/Overview.razor | 4 +- .../Components/Pages/Home.razor | 4 +- .../TableMarkupTests.cs | 57 +++++++++++++++++++ 3 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 tests/SQLInsight.Community.Tests/TableMarkupTests.cs diff --git a/src/SQLInsight.Hub/Portal/Pages/Overview.razor b/src/SQLInsight.Hub/Portal/Pages/Overview.razor index 7dd6c13..2e9b1be 100644 --- a/src/SQLInsight.Hub/Portal/Pages/Overview.razor +++ b/src/SQLInsight.Hub/Portal/Pages/Overview.razor @@ -28,11 +28,11 @@ @(c.Plan ?? "–") @(c.LicenseExpires?.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture) ?? "–") @c.Instances - +
@if (c.Critical > 0) { } @if (c.High > 0) { } @if (c.Critical == 0 && c.High == 0) { ingen kritiske/høje } - +
@(c.LastSubmission?.ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture) ?? "aldrig") UTC } diff --git a/src/SQLInsight.Web/Components/Pages/Home.razor b/src/SQLInsight.Web/Components/Pages/Home.razor index 3a1d18a..5839f4a 100644 --- a/src/SQLInsight.Web/Components/Pages/Home.razor +++ b/src/SQLInsight.Web/Components/Pages/Home.razor @@ -45,12 +45,12 @@ else @i.Name
@i.ServerName
@(i.ProductVersion is null ? "–" : Display.Release(i.ProductVersion, i.EngineEdition))
@i.ProductVersion @i.ProductLevel @i.ProductUpdateLevel
- +
@if (i.Critical > 0) { } @if (i.High > 0) { } @if (i.Medium > 0) { } @if (i.Low > 0) { } - +
@i.NotMeasurable @Display.Ago(i.LastSeenAt) diff --git a/tests/SQLInsight.Community.Tests/TableMarkupTests.cs b/tests/SQLInsight.Community.Tests/TableMarkupTests.cs new file mode 100644 index 0000000..e9713b5 --- /dev/null +++ b/tests/SQLInsight.Community.Tests/TableMarkupTests.cs @@ -0,0 +1,57 @@ +using System.Text.RegularExpressions; + +namespace SQLInsight.Community.Tests; + +/// +/// 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. +/// +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)}: ")) + .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(@"(?[^{}]+)\{[^{}]*\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(@"]*\bclass=""(?[^""]*)""")] + private static partial Regex CellClass(); +}