Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
f7de985
Time range model: presets, calendar periods, typed-range parser and s…
erikdarlingdata Oct 8, 2026
3a8eb28
Shared WPF time range picker control, STA tests, UTC offsets allowed …
erikdarlingdata Oct 8, 2026
6ca61ec
Web and MCP: a per-read window ceiling, and the reach table the web p…
erikdarlingdata Oct 8, 2026
a4daccb
A query trend past seven days is served from the hourly rollup, pinne…
erikdarlingdata Oct 8, 2026
633d2b0
The Darling Viewer's time range controls use the shared picker (#5562)
erikdarlingdata Oct 8, 2026
bc2ca9f
Web time range picker: grammar module, picker control and the server …
erikdarlingdata Oct 8, 2026
53152d5
The web module reads a read's collector interval from the catalog (#5…
erikdarlingdata Oct 8, 2026
edc2a56
Lite: every time range control uses the shared picker (#5562)
erikdarlingdata Oct 8, 2026
abccc7a
Lite: HoursBackOf spelled without an empty-brace pattern, cref follow…
erikdarlingdata Oct 8, 2026
cf5a64a
Web pages move onto the shared time range picker; the server page not…
erikdarlingdata Oct 8, 2026
eaa6e84
Page tests and harnesses drive the shared picker; new WebPageRangeTes…
erikdarlingdata Oct 8, 2026
814a50f
Web module load harness treats finops/window.js as a helper, not a ta…
erikdarlingdata Oct 8, 2026
164f7a7
The Viewer's FinOps pickers are rolling-only, Job History bounds a fi…
erikdarlingdata Oct 8, 2026
cddfeaf
Lite: FinOps pickers rolling only, Job History and Alert History end …
erikdarlingdata Oct 8, 2026
c2ceca2
Lite: the sample note names the main collector of the sub-tab on scre…
erikdarlingdata Oct 8, 2026
17f08de
Lite: every banner site feeds the picker's data start (#5562)
erikdarlingdata Oct 8, 2026
9860b2b
Lite: tests for the FinOps, Job History, Alert History, sample note a…
erikdarlingdata Oct 8, 2026
83b11ba
Lite: FinOps and Alert History use the shared control's RollingUnit a…
erikdarlingdata Oct 8, 2026
ec600b5
Web and MCP: raw-table trends reach 30 days, Alert History 90 days, S…
erikdarlingdata Oct 8, 2026
f190aaa
Desktop round-1 fixes for the shared time range picker (#5562)
erikdarlingdata Oct 8, 2026
254a8b5
CI darling filter reaches Lite.Tests/LiteTimeRangeGapTests.cs, which …
erikdarlingdata Oct 8, 2026
25fa2ae
Web picker review round 1: Alert History trims to the range start, St…
erikdarlingdata Oct 8, 2026
9a127c2
Web picker review round 1: perfmon and heatmap keep the 168 hour over…
erikdarlingdata Oct 8, 2026
51240a2
A calendar period is exempt from the 5 minute floor in the web module…
erikdarlingdata Oct 8, 2026
650b0be
The server catalog cache expires on lookup, not on a 5 minute timer t…
erikdarlingdata Oct 8, 2026
c0c2191
Job History page pin follows the page's readParams(w) call (#5562)
erikdarlingdata Oct 8, 2026
3b2d246
Server page: each tab reads its reach from the catalog, and a range l…
erikdarlingdata Oct 8, 2026
d8b0c4e
A calendar period at exactly its start says it has only just started,…
erikdarlingdata Oct 8, 2026
0be679e
Server page title pin follows the reach slot in the page's mount (#5562)
erikdarlingdata Oct 8, 2026
2eeafa5
Merge origin/dev into fix/5562-time-range-picker
erikdarlingdata Oct 8, 2026
b09bb0d
Time range picker review round 2: alert times parsed as UTC, future-s…
erikdarlingdata Oct 8, 2026
a02545a
STA test helpers take the WPF STA gate (#5559 census) (#5562)
erikdarlingdata Oct 8, 2026
a9d60b0
Merge remote-tracking branch 'origin/dev' into fix/5562-time-range-pi…
erikdarlingdata Oct 8, 2026
501bbed
Server page reach lists move to their own module and MCP reach text m…
erikdarlingdata Oct 8, 2026
3f313e4
Merge remote-tracking branch 'origin/dev' into fix/5562-time-range-pi…
erikdarlingdata Oct 8, 2026
b92e3ed
The seven guided tools keep their own routing detail first in the gui…
erikdarlingdata Oct 8, 2026
6cb5ad0
Merge origin/dev into fix/5562-time-range-picker (keep both Lite.Test…
erikdarlingdata Oct 8, 2026
331de71
Merge branch 'dev' into fix/5562-time-range-picker
erikdarlingdata Oct 8, 2026
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
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,8 @@ jobs:
- 'Lite/**/*.csproj'
# #4966: Darling.Tests' CommentFilterAdoptionTests reads this Lite.Tests file, so an edit to it has to reach the Darling suite.
- 'Lite.Tests/DataStartBannerQueriesTabTests.cs'
# #5562: same, for the Lite.Tests file CommentFilterAdoptionTests lists as a stated bound.
- 'Lite.Tests/LiteTimeRangeGapTests.cs'
# #5596: Darling.Tests' WpfExitSwitchPinTests reads this project file for the WPF exit switch, so an edit to it has to reach the Darling suite.
- 'Lite.Tests/Lite.Tests.csproj'
# #5459 cut 3a: the part of `darling` that is NOT a Lite file. A diff that reaches `darling` through
Expand Down
66 changes: 56 additions & 10 deletions Darling/Darling.Tests/AlertHistoryRangePageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,13 @@ public void ThePageReadsGetAlertHistoryWithTheFourChoicesAndNeverAFixedWindow()
var page = Page();
Assert.DoesNotContain("hours_back: 24", page);
Assert.DoesNotContain("limit: 200 }", page);
Assert.Contains("readTool(\"get_alert_history\", readParams())", page);
Assert.Contains("hours_back: choices.hours", page);
Assert.Contains("readTool(\"get_alert_history\", req.params)", page);
// #5562: the range is the shared picker's spec, read as of each read; a finished range also sends its end.
Assert.Contains("windowResultOfSpec(choices.spec);", page);
Assert.Contains("hours_back: w.hours", page);
Assert.Contains("as_of: w.asOf", page);
// Review r1 H2: the reply is trimmed to the range start, since the read takes whole hours.
Assert.Contains("trimAlertsToStart(res.data.alerts || [], req.startMs)", page);
Assert.Contains("limit: choices.limit", page);
Assert.Contains("server_name: choices.server || null", page);
Assert.Contains("include_dismissed: choices.dismissed ? \"true\" : null", page);
Expand All @@ -45,9 +50,14 @@ public void ThePageReadsGetAlertHistoryWithTheFourChoicesAndNeverAFixedWindow()
public void NoWindowOrLimitIsOfferedThatTheToolOrTheDispatchLayerRefuses()
{
var page = Page();
var hours = System.Text.RegularExpressions.Regex.Matches(page, "\\{ hours: (\\d+), label").Select(m => int.Parse(m.Groups[1].Value)).ToList();
Assert.Equal(new[] { 1, 4, 24, 168 }, hours);
Assert.True(hours.Max() <= PerformanceMonitor.Common.McpHelpers.MaxHoursBack);
// #5562 R8: no hand-listed windows and no "All". The picker takes its reach from the catalog's max_hours for the read, which is
// the 2160 hours (90 days) the read's validator accepts, so a longer range is greyed out with the reason and never clamped. That
// is the alert table's retention (AlertHistoryRetentionDays, DarlingRetention.cs), the longest choice the Viewer offers (R8, L4b).
Assert.DoesNotContain("WINDOW_CHOICES", page);
Assert.Contains("read: \"get_alert_history\"", page);
Assert.Contains("pageRangePicker({", page);
Assert.Equal(24 * PerformanceMonitor.Darling.Storage.DarlingRetentionHorizons.AlertHistoryRetentionDays, PerformanceMonitor.Darling.Service.WebReadReach.All["get_alert_history"].MaxHours);
Assert.Equal(90, PerformanceMonitor.Darling.Storage.DarlingRetentionHorizons.AlertHistoryRetentionDays);

var limits = System.Text.RegularExpressions.Regex.Match(page, "const LIMIT_CHOICES = \\[([0-9, ]+)\\]").Groups[1].Value
.Split(',').Select(s => int.Parse(s.Trim())).ToList();
Expand All @@ -63,12 +73,17 @@ public void TheChoicesLiveAtModuleScopeAndTheDismissRouteIsNotTheDesktopOne()
Assert.DoesNotContain("/api/alerts/dismiss", page);
}

private static JsonElement Run(string scenario)
private static JsonElement Run(string scenario, string? timeZone = null)
{
var psi = new ProcessStartInfo("node") { RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false };
psi.ArgumentList.Add(PathTo("Darling", "Darling.Tests", "alert-history-range-harness.mjs"));
psi.ArgumentList.Add(PathTo("Darling", "PerformanceMonitor.Darling.Service", "wwwroot", "js"));
psi.ArgumentList.Add(scenario);
if (timeZone is not null)
{
psi.ArgumentList.Add(timeZone);
}

Process proc;
try
{
Expand Down Expand Up @@ -118,7 +133,15 @@ public void EachChoiceReachesTheRead()
Assert.Equal("1000", Get(dismissed, "limit"));
Assert.Equal("srv-b", Get(dismissed, "server_name"));

Assert.Equal("1,4,24,168", string.Join(",", r.GetProperty("windowOptions").EnumerateArray().Select(e => e.GetString())));
// The popup offers every preset and calendar period within the catalog's 90 days; the longer ones are greyed out, never clamped.
Assert.Equal("Past 5 minutes,Past 15 minutes,Past 30 minutes,Past hour,Past 4 hours,Past day,Past 2 days,Past week,Past 30 days,Past 90 days,Today,Yesterday,Week to Date,Previous Week,Month to Date,Previous Month",
string.Join(",", r.GetProperty("windowOptions").EnumerateArray().Select(e => e.GetString())));
Assert.Equal("Year to Date,Previous Year",
string.Join(",", r.GetProperty("windowGreyed").EnumerateArray().Select(e => e.GetString())));
Assert.Equal("This page's reads reach at most 90 days back.", r.GetProperty("refused").GetString());
// A finished range sends its end as as_of; a shorter-than-an-hour range reads the whole hour.
Assert.EndsWith("Z", Get(r.GetProperty("finished"), "as_of"));
Assert.Equal("1", Get(r.GetProperty("thirtyMinutes"), "hours_back"));
Assert.Equal("200,500,1000", string.Join(",", r.GetProperty("limitOptions").EnumerateArray().Select(e => e.GetString())));
Assert.Equal(",srv-a,srv-b", string.Join(",", r.GetProperty("serverOptions").EnumerateArray().Select(e => e.GetString())));
}
Expand All @@ -138,7 +161,7 @@ public void TheChoicesSurviveThePollAndAVisitElsewhere()
}

var shown = r.GetProperty("shown");
Assert.Equal("4", shown.GetProperty("window").GetString());
Assert.Equal("Past 4 hours", shown.GetProperty("window").GetString());
Assert.Equal("500", shown.GetProperty("limit").GetString());
Assert.Equal("srv-a", shown.GetProperty("server").GetString());
Assert.True(shown.GetProperty("dismissed").GetBoolean());
Expand Down Expand Up @@ -182,9 +205,13 @@ public void TwoServersThatShareADisplayNameKeepBothRows()
}

[Fact]
public void TheRangePickerSaysTheWebReadsAtMostSevenDays()
public void TheRangePickerTakesItsReachFromTheCatalog_NinetyDays()
{
Assert.Contains("The web reads at most 7 days", Page());
// #5562: the picker greys out what the read cannot reach and says why (the catalog's max_hours), so the page needs no hand-written
// tooltip; the reach is the read's own 2160 hours, the alert table's retention (L4b).
Assert.DoesNotContain("The web reads at most 7 days of alert history, so there is no All choice.", Page());
Assert.DoesNotContain("the web read stops at the common 7 day reach", Page());
Assert.Contains("get_alert_history reaches 2160 hours, 90 days", Page());
}

[Fact]
Expand All @@ -197,6 +224,25 @@ public void ANewWindowReconcilesInPlace_RowsLeaveAndNewOnesLandInOrder()
Assert.Equal("High CPU 3", string.Join(",", r.GetProperty("shrunk").EnumerateArray().Select(e => e.GetString())));
}

// Review r2 H1: alert_time is naive UTC with no zone suffix, which the browser reads as LOCAL time unless the page parses it as UTC. The
// harness rows carry the server's real shape, and the same scenario runs in zones that are not UTC (and in UTC) because a UTC-only run
// passes the old Date.parse code. The offset check proves Node honoured the zone, so the test cannot pass by running in UTC every time.
[Theory]
[InlineData("UTC", 0)]
[InlineData("Europe/Berlin", -1)]
[InlineData("America/New_York", 1)]
[InlineData("Asia/Tokyo", -1)]
public void ARangeShorterThanTheWholeHoursItFetches_ListsOnlyTheAlertsInsideIt(string zone, int offsetSign)
{
var r = Run("trim", zone);
Assert.Equal(offsetSign, Math.Sign(r.GetProperty("tzOffsetMinutes").GetInt32()));
string[] Names(string key) => r.GetProperty(key).EnumerateArray().Select(e => e.GetString()!).ToArray();
Assert.Equal(new[] { "High CPU 0", "High CPU 19", "High CPU 89" }, Names("day"));
Assert.Equal(new[] { "High CPU 0" }, Names("fiveMinutes"));
Assert.Equal(new[] { "High CPU 0", "High CPU 19" }, Names("thirtyMinutes"));
Assert.Equal(new[] { "High CPU 0", "High CPU 19", "High CPU 89" }, Names("fourHours"));
}

[Fact]
public void TheMuteLinksStillRenderOnEveryRow()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ MouseDoubleClick=>QueryStoreRegressionsGrid_MouseDoubleClick
SelectionChanged=>AutoRefreshInterval_Changed
SelectionChanged=>BlockingSubTabs_SelectionChanged
SelectionChanged=>CompareToCombo_SelectionChanged
SelectionChanged=>CustomTimeCombo_Changed
SelectionChanged=>FileIoSubTabs_SelectionChanged
SelectionChanged=>HeatmapMetric_SelectionChanged
SelectionChanged=>InnerTabs_SelectionChanged
Expand All @@ -19,5 +18,4 @@ SelectionChanged=>QueryStatsGrid_SelectionChanged
SelectionChanged=>QueryStoreGrid_SelectionChanged
SelectionChanged=>SystemEventsSubTabs_SelectionChanged
SelectionChanged=>TimeDisplayMode_SelectionChanged
SelectionChanged=>TimeRangeCombo_SelectionChanged
SelectionChanged=>WaitStatsMetric_SelectionChanged
11 changes: 10 additions & 1 deletion Darling/Darling.Tests/CommentFilterAdoptionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public sealed class CommentFilterAdoptionTests
/// name a new entry.
///
/// <para>Four kinds live here and they are not the same kind. Eight <b>collect</b> a doc-comment run,
/// where the prefix is what defines the run. Two read a <b>stated, measured</b> bound off a named file each.
/// where the prefix is what defines the run. Three read a <b>stated, measured</b> bound off a named file each.
/// One filters <b>SQL</b>, which the C# walk cannot help with. One <b>demonstrates</b> the shape on an
/// arranged fixture, which is this file.</para>
/// </summary>
Expand Down Expand Up @@ -168,6 +168,15 @@ public sealed class CommentFilterAdoptionTests
+ "which is a pass for the wrong reason. Measurement-dependent, not permanent: a second block "
+ "comment in that file makes the bound wrong.",

["Lite.Tests/LiteTimeRangeGapTests.cs"] =
"STATED BOUND, measured. It COUNTS call sites of ApplyWindowFloorToBanner( across Lite's tab sources (#5562 R7: every banner site feeds the "
+ "picker's data start). IsDocOrDefinition drops a match whose line, trimmed, starts with //, ///, * or /*, so a "
+ "doc or comment mention is not a site, and drops the method's own definition line. Stated bound: it reads one "
+ "line, so a call written inside a block comment on a line that does not begin with * or /* is miscounted as a "
+ "site (a spurious extra site, the loud direction: the per-file count then disagrees with the sites pinned). "
+ "The walker would drop the call as comment text correctly, but the same test also needs the definition line "
+ "kept apart, which the line check does in one step; the sources it reads carry no such block comment today.",

["Darling.Tests/Pg18IoBytesTests.cs"] =
"NOT C#. The filter runs over DarlingPgIoReader.PgIoSql - SQL text already extracted from a raw "
+ "string literal - and drops SQL block-comment lines from the outer SELECT list before counting "
Expand Down
2 changes: 2 additions & 0 deletions Darling/Darling.Tests/Darling.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@
<ItemGroup>
<!-- system_health event samples for SystemHealthParser (one per feeder event; shapes per sp_HealthParser xpath). -->
<None Include="Fixtures\FinOpsIndexAnalysis\golden.json" CopyToOutputDirectory="PreserveNewest" />
<!-- The shared time range grammar cases (#5562); the web harness reads the same file. -->
<None Include="Fixtures\time-range-cases.json" CopyToOutputDirectory="PreserveNewest" />
<None Include="Fixtures\SystemHealth\*.xml" CopyToOutputDirectory="PreserveNewest" />
<!-- real, scrubbed system_health event captures for SystemHealthParserRealEventTests -->
<None Include="Fixtures\SystemHealth\real\*.xml" CopyToOutputDirectory="PreserveNewest" />
Expand Down
2 changes: 1 addition & 1 deletion Darling/Darling.Tests/DarlingMcpJobHistoryToolTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ public async Task ViewerRows_EqualTheStorageReaderRows_MappedThroughFrom()
var since = AsOf.AddHours(-24);

await using var viewer = new ViewerDataService(Cs!);
var viewerRows = await viewer.GetJobHistoryAsync(since, null, 2000, ct);
var viewerRows = await viewer.GetJobHistoryAsync(since, null, 2000, cancellationToken: ct);
var storage = await DarlingJobHistoryReader.GetAsync(postgres, since, null, 2000, 60, cancellationToken: ct);

/* D5: the viewer stamps each row with its own server's clock, which the storage reader's row does not carry, and
Expand Down
1 change: 1 addition & 0 deletions Darling/Darling.Tests/DarlingWebAssetsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public void Wwwroot_IndexHtml_IsCopiedToTheBuildOutput()
[InlineData("css/theme.css")]
[InlineData("css/app.css")]
[InlineData("css/editor.css")]
[InlineData("css/time-range.css")]
[InlineData("js/app.js")]
[InlineData("js/util.js")]
[InlineData("js/panels.js")]
Expand Down
3 changes: 2 additions & 1 deletion Darling/Darling.Tests/FinOpsPageShellTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ public void EveryTabFile_ExportsTabWithIdLabelBuild_AndTheIdIsTheFileName()
.Select(Path.GetFileNameWithoutExtension)
// database-box.js is the shared Database box the Index Analysis and Locking tabs import (#5231), not a tab.
// gate.js is the helper the tabs share for their empty and not-collected states on a PostgreSQL target, also not a tab.
.Where(name => name != "database-box" && name != "gate")
// window.js is the rolling-only time range picker the windowed tabs share (#5562), also not a tab.
.Where(name => name != "database-box" && name != "gate" && name != "window")
.OrderBy(x => x, StringComparer.Ordinal).ToArray();
Assert.Equal(TabIds.OrderBy(x => x, StringComparer.Ordinal).ToArray(), onDisk);

Expand Down
20 changes: 14 additions & 6 deletions Darling/Darling.Tests/FinOpsStorageGrowthWindowBehaviourTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,21 @@ public void TheObjectsLevelDefaultsToThirtyDays_AndEachPickSendsDaysTimes24()
var run = Run();
var objects = run.GetProperty("objects");
Assert.Equal(new[] { "720" }, Hours(objects));
Assert.Equal("7,30,90", string.Join(",", objects.GetProperty("options").EnumerateArray().Select(e => e.GetString())));
Assert.Equal("30", objects.GetProperty("value").GetString());
// #5562 R5: the Compact rolling-only picker, whole days, reach 2160 hours for this view (not the catalog entry's 168).
Assert.Equal("Past week,Past 30 days,Past 90 days", string.Join(",", objects.GetProperty("options").EnumerateArray().Select(e => e.GetString())));
var refused = objects.GetProperty("refused");
Assert.Equal("This page reads whole days back from now, such as 7d or 30d.", refused.GetProperty("hours").GetString());
Assert.Equal("This page reads a length back from now, so pick a rolling length such as 24h or 7d.", refused.GetProperty("calendar").GetString());
Assert.Equal("This page's reads reach at most 90 days back.", refused.GetProperty("tooLong").GetString());
/* Review r1 H1: 1 day would read the server's 30-day default and 2 to 6 days are refused by the read, so neither is offered. */
Assert.Equal("This page reads at least 7d back from now.", refused.GetProperty("tooShort").GetString());
Assert.Equal("This page reads at least 7d back from now.", refused.GetProperty("oneDay").GetString());
Assert.Equal("Past 30 days", objects.GetProperty("value").GetString());

Assert.Equal(new[] { "2160" }, Hours(run.GetProperty("picked90")));
Assert.Equal("90", run.GetProperty("picked90").GetProperty("value").GetString());
Assert.Equal("Past 90 days", run.GetProperty("picked90").GetProperty("value").GetString());
Assert.Equal(new[] { "168" }, Hours(run.GetProperty("picked7")));
Assert.Equal("7", run.GetProperty("picked7").GetProperty("value").GetString());
Assert.Equal("Past week", run.GetProperty("picked7").GetProperty("value").GetString());
}

[Fact]
Expand All @@ -99,11 +107,11 @@ public void ThePickSurvivesARebuildForTheSameServer_AndAnotherServerStartsAtThir
var run = Run();
var rebuilt = run.GetProperty("rebuilt");
Assert.Equal(new[] { "168" }, Hours(rebuilt));
Assert.Equal("7", rebuilt.GetProperty("value").GetString());
Assert.Equal("Past week", rebuilt.GetProperty("value").GetString());

Assert.Equal(new[] { "24" }, Hours(run.GetProperty("other")));
var otherObjects = run.GetProperty("otherObjects");
Assert.Equal(new[] { "720" }, Hours(otherObjects));
Assert.Equal("30", otherObjects.GetProperty("value").GetString());
Assert.Equal("Past 30 days", otherObjects.GetProperty("value").GetString());
}
}
10 changes: 4 additions & 6 deletions Darling/Darling.Tests/FinOpsTabDatabaseResourcesPageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,16 @@ public void TheTabReadsGetFinOpsWithTheDatabaseResourcesViewWindow()
[Fact]
public void ThePickerOffersTheDesktopWindowsInOrder()
{
// Source: FinOpsTab.xaml ~:497-499, FinOpsResourceUsageTimeRangeCombo.
// #5562 R5: the shared picker in its Compact, rolling-only form (finops/window.js), not a hand-listed select.
var tab = Tab();
var pairs = Regex.Matches(tab, "\\{ value: (\\d+), label: \"([^\"]+)\" \\}").Select(m => m.Groups[1].Value + "=" + m.Groups[2].Value);
Assert.Equal("1=Last 1 hour,4=Last 4 hours,12=Last 12 hours,24=Last 24 hours,168=Last 7 days", string.Join(",", pairs));
Assert.Contains("pickerControl(\"Window\", WINDOWS, HOURS,", tab);
Assert.DoesNotContain("WINDOWS", tab);
Assert.Contains("finopsWindowControl({ hours: HOURS, onChange: (hours) => load(hours) })", tab);
}

[Fact]
public void TheChangeHandlerRereadsWithTheChosenHoursAndTheFirstReadUsesTheDefault()
{
var tab = Tab();
Assert.Contains("sel.addEventListener(\"change\", () => onPick(Number(sel.value)));", tab);
Assert.Contains("(hours) => load(hours)", tab);
Assert.Contains("load(HOURS);", tab);
}
Expand Down Expand Up @@ -127,7 +125,7 @@ public void TheTabImportsOnlyTheSharedHelpers()
{
var imports = Regex.Matches(Tab(), "from \"([^\"]+)\";").Select(m => m.Groups[1].Value).ToList();
Assert.NotEmpty(imports);
Assert.All(imports, i => Assert.Contains(i, new[] { "../../panels.js", "../../charts.js", "../../util.js", "./gate.js" }));
Assert.All(imports, i => Assert.Contains(i, new[] { "../../panels.js", "../../charts.js", "../../util.js", "./gate.js", "./window.js" }));
Assert.Contains("./gate.js", imports);
}

Expand Down
Loading
Loading