feat: implement LiveTemperatureThresholdChart and associated data processing logic with comprehensive unit tests

This commit is contained in:
2569718930@qq.com
2026-05-26 23:56:42 +08:00
parent b3496bed2d
commit 91ccb061a7
4 changed files with 164 additions and 27 deletions
@@ -104,6 +104,17 @@ export function runTests() {
assert(chart.includes("nearestSeriesValue"), "temperature chart tooltip must fall back to nearest non-null value for connected sparse lines");
assert(chart.includes("isHourlyLoading"), "temperature chart must keep a per-panel hourly loading state");
assert(chart.includes("加载图表") && chart.includes("absolute inset-2"), "temperature chart must render an in-chart loading overlay");
assert(chart.includes("hasLoadedHourlyDetailRef"), "temperature chart must distinguish first load from background refreshes");
const fallbackRefreshBlock = chart.match(/const refreshFullDetail = \(\) => \{[\s\S]*?\n \};/)?.[0] || "";
assert(
!fallbackRefreshBlock.includes("setIsHourlyLoading(true)"),
"no-patch fallback refresh should update the chart in the background without showing the loading overlay",
);
const resyncBlock = chart.match(/useEffect\(\(\) => \{\s*if \(!resyncVersion \|\| !city\) return;[\s\S]*?\}, \[resyncVersion, city, targetResolution\]\);/)?.[0] || "";
assert(
!resyncBlock.includes("setIsHourlyLoading(true)"),
"SSE replay resync should refresh full detail in the background without showing the loading overlay",
);
assert(chart.includes("viewMode"), "temperature chart must expose a view mode for DEB-peak auto view versus full-day view");
assert(chart.includes("getDebPeakWindowRange"), "temperature chart must derive its default view from the DEB peak window");
assert(chart.includes("nextTargetResolution"), "temperature chart must derive target resolution without setting state on every render");
@@ -331,6 +331,27 @@ export function runTests() {
"Istanbul/MGM high label should be weather station",
);
const panamaLabels = __getLiveObservationLabelsForTest(
{
city: "panama city",
airport: "MPMG",
metar_context: {
source: "metar",
station: "MPMG",
station_label: "MPMG METAR",
},
} as any,
null,
);
assert(
panamaLabels.runwayHeaderLabel === "机场气象站",
"Panama City/MPMG should not be labeled as runway observations when no runway sensor feed exists",
);
assert(
panamaLabels.runwayHighLabel === "机场气象站",
"Panama City high label should use airport weather station wording, not runway wording",
);
const newYorkWithMadis = __buildTemperatureChartDataForTest(
{
city: "new york",
@@ -505,6 +526,40 @@ export function runTests() {
"DEB curve should not be pulled into an impossible negative range by stale row deb_prediction=0",
);
const qingdaoFullDay = __buildTemperatureChartDataForTest(
{
city: "qingdao",
local_date: "2026-05-26",
local_time: "23:30",
tz_offset_seconds: 8 * 60 * 60,
deb_prediction: 22,
runway_plate_history: {
"16/34": [
{ time: "2026-05-25T23:30:00+08:00", temp: 23.8 },
{ time: "2026-05-26T00:05:00+08:00", temp: 23.5 },
{ time: "2026-05-26T12:00:00+08:00", temp: 21.6 },
],
},
} as any,
{
localTime: "23:30",
times: ["00:00", "06:00", "12:00", "18:00", "23:00"],
temps: [24, 19, 21.5, 21.5, 20],
debPrediction: 22,
} as any,
"1D",
);
const qingdaoDayStart = Date.UTC(2026, 4, 26, 0, 0, 0);
const qingdaoDayEnd = Date.UTC(2026, 4, 27, 0, 0, 0);
assert(
qingdaoFullDay.data.every((point) => point.ts >= qingdaoDayStart && point.ts < qingdaoDayEnd),
"Full-day chart should clamp observation history to the selected local_date so DEB does not appear broken after cross-day runway history",
);
assert(
qingdaoFullDay.data[0]?.ts === qingdaoDayStart,
"Full-day chart should start at local 00:00 when the DEB hourly path has a midnight point",
);
// ── Runway range band and runway_max test ──
const shanghaiWithBand = __buildTemperatureChartDataForTest(
{