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>
This commit is contained in:
parent
e1d45e1734
commit
2c49a1e1b1
3 changed files with 61 additions and 4 deletions
|
|
@ -28,11 +28,11 @@
|
|||
<td>@(c.Plan ?? "–")</td>
|
||||
<td class="nowrap @(c.LicenseExpires < Queries.Now.AddDays(30) ? "bad" : "")">@(c.LicenseExpires?.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture) ?? "–")</td>
|
||||
<td class="num">@c.Instances</td>
|
||||
<td class="counts">
|
||||
<td><div class="counts">
|
||||
@if (c.Critical > 0) { <Sev Severity="4" Count="c.Critical" /> }
|
||||
@if (c.High > 0) { <Sev Severity="3" Count="c.High" /> }
|
||||
@if (c.Critical == 0 && c.High == 0) { <span class="muted">ingen kritiske/høje</span> }
|
||||
</td>
|
||||
</div></td>
|
||||
<td class="nowrap @(c.LastSubmission < Queries.Now.AddHours(-1) ? "bad" : "")">@(c.LastSubmission?.ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture) ?? "aldrig") UTC</td>
|
||||
</tr>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,12 +45,12 @@ else
|
|||
</td>
|
||||
<td><a href="/instanser/@Uri.EscapeDataString(i.Name)"><strong>@i.Name</strong></a><div class="muted">@i.ServerName</div></td>
|
||||
<td>@(i.ProductVersion is null ? "–" : Display.Release(i.ProductVersion, i.EngineEdition))<div class="muted">@i.ProductVersion @i.ProductLevel @i.ProductUpdateLevel</div></td>
|
||||
<td class="counts">
|
||||
<td><div class="counts">
|
||||
@if (i.Critical > 0) { <SeverityBadge Severity="Severity.Critical" Count="i.Critical" /> }
|
||||
@if (i.High > 0) { <SeverityBadge Severity="Severity.High" Count="i.High" /> }
|
||||
@if (i.Medium > 0) { <SeverityBadge Severity="Severity.Medium" Count="i.Medium" /> }
|
||||
@if (i.Low > 0) { <SeverityBadge Severity="Severity.Low" Count="i.Low" /> }
|
||||
</td>
|
||||
</div></td>
|
||||
<td>@i.NotMeasurable</td>
|
||||
<td title="@Display.Time(i.LastSeenAt)">@Display.Ago(i.LastSeenAt)</td>
|
||||
</tr>
|
||||
|
|
|
|||
57
tests/SQLInsight.Community.Tests/TableMarkupTests.cs
Normal file
57
tests/SQLInsight.Community.Tests/TableMarkupTests.cs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
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();
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue