feat: implement Telegram push utility and add new dashboard components for scan terminal and paywall management
This commit is contained in:
@@ -1,19 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
getMonitorRefreshRequest,
|
||||
MONITOR_CITY_DETAIL_DEPTH,
|
||||
} from "@/components/dashboard/monitoring/monitor-refresh-policy";
|
||||
|
||||
export function runTests() {
|
||||
const initial = getMonitorRefreshRequest("initial");
|
||||
assert.equal(
|
||||
initial.force,
|
||||
true,
|
||||
"monitor initial load must force refresh instead of showing 30-minute session cache",
|
||||
);
|
||||
assert.equal(initial.depth, MONITOR_CITY_DETAIL_DEPTH);
|
||||
|
||||
const interval = getMonitorRefreshRequest("interval");
|
||||
assert.equal(interval.force, true);
|
||||
assert.equal(interval.depth, MONITOR_CITY_DETAIL_DEPTH);
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { resolveMonitorTemperature } from "@/components/dashboard/monitoring/monitor-temperature";
|
||||
import type { CityDetail } from "@/lib/dashboard-types";
|
||||
|
||||
function detail(extra: Partial<CityDetail>): CityDetail {
|
||||
return {
|
||||
current: { temp: null },
|
||||
display_name: "Busan",
|
||||
lat: 0,
|
||||
local_date: "2026-05-14",
|
||||
local_time: "15:00",
|
||||
lon: 0,
|
||||
name: "busan",
|
||||
risk: { level: "low" },
|
||||
temp_symbol: "°C",
|
||||
...extra,
|
||||
} as CityDetail;
|
||||
}
|
||||
|
||||
export function runTests() {
|
||||
const busan = detail({
|
||||
airport_current: {
|
||||
obs_time: "15:00",
|
||||
source_label: "METAR",
|
||||
temp: 26,
|
||||
},
|
||||
amos: {
|
||||
runway_obs: {
|
||||
runway_pairs: [["18L", "36R"]],
|
||||
temperatures: [[25.2, 18.1]],
|
||||
},
|
||||
source: "amos",
|
||||
temp: 26,
|
||||
temp_c: 26,
|
||||
temp_source: "metar",
|
||||
},
|
||||
});
|
||||
const busanTemp = resolveMonitorTemperature(busan);
|
||||
assert.equal(busanTemp.value, 25.2);
|
||||
assert.equal(busanTemp.source, "amos_runway");
|
||||
|
||||
const seoul = detail({
|
||||
airport_current: { obs_time: "15:00", temp: 27 },
|
||||
amos: {
|
||||
runway_obs: {
|
||||
runway_pairs: [["15L", "33R"], ["15R", "33L"]],
|
||||
temperatures: [[25.2, 19], [24.8, 18.8]],
|
||||
},
|
||||
source: "amos",
|
||||
temp_source: "metar",
|
||||
},
|
||||
});
|
||||
const seoulTemp = resolveMonitorTemperature(seoul);
|
||||
assert.equal(seoulTemp.value, 25);
|
||||
assert.equal(seoulTemp.source, "amos_runway_median");
|
||||
|
||||
const beijing = detail({
|
||||
display_name: "Beijing",
|
||||
name: "beijing",
|
||||
amos: {
|
||||
runway_obs: {
|
||||
runway_pairs: [["18R", "36L"], ["18L", "36R"]],
|
||||
temperatures: [[20.8, null], [21.0, null]],
|
||||
},
|
||||
source: "amsc_awos",
|
||||
temp_c: 21,
|
||||
temp_source: "runway_max",
|
||||
},
|
||||
});
|
||||
const beijingTemp = resolveMonitorTemperature(beijing);
|
||||
assert.equal(beijingTemp.value, 21);
|
||||
assert.equal(beijingTemp.source, "amsc_awos_runway_max");
|
||||
|
||||
const metarOnly = detail({
|
||||
airport_current: { obs_time: "15:00", temp: 30 },
|
||||
current: { temp: 29 } as CityDetail["current"],
|
||||
});
|
||||
assert.equal(resolveMonitorTemperature(metarOnly).value, 30);
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
function assert(condition: unknown, message: string) {
|
||||
if (!condition) throw new Error(message);
|
||||
}
|
||||
|
||||
export function runTests() {
|
||||
const projectRoot = process.cwd();
|
||||
const shellPartsPath = path.join(
|
||||
projectRoot,
|
||||
"components",
|
||||
"dashboard",
|
||||
"scan-terminal",
|
||||
"ScanTerminalShellParts.tsx",
|
||||
);
|
||||
const dashboardPath = path.join(
|
||||
projectRoot,
|
||||
"components",
|
||||
"dashboard",
|
||||
"ScanTerminalDashboard.tsx",
|
||||
);
|
||||
const runwayPanelPath = path.join(
|
||||
projectRoot,
|
||||
"components",
|
||||
"dashboard",
|
||||
"scan-terminal",
|
||||
"RunwayObservationsPanel.tsx",
|
||||
);
|
||||
const monitorPanelPath = path.join(
|
||||
projectRoot,
|
||||
"components",
|
||||
"dashboard",
|
||||
"monitoring",
|
||||
"MonitorPanel.tsx",
|
||||
);
|
||||
|
||||
const shellPartsSource = fs.readFileSync(shellPartsPath, "utf8");
|
||||
const dashboardSource = fs.readFileSync(dashboardPath, "utf8");
|
||||
|
||||
assert(
|
||||
!shellPartsSource.includes('"monitor"') && !shellPartsSource.includes('"runway"'),
|
||||
"scan terminal content views must not include market monitor or runway tabs",
|
||||
);
|
||||
assert(
|
||||
!dashboardSource.includes('setActiveView("monitor")') &&
|
||||
!dashboardSource.includes('setActiveView("runway")') &&
|
||||
!dashboardSource.includes("市场监控") &&
|
||||
!dashboardSource.includes("跑道观测"),
|
||||
"dashboard must not expose market monitor or runway observation tabs",
|
||||
);
|
||||
assert(!fs.existsSync(runwayPanelPath), "dedicated runway observation panel must be removed");
|
||||
assert(!fs.existsSync(monitorPanelPath), "dedicated market monitor panel must be removed");
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
function assert(condition: unknown, message: string) {
|
||||
if (!condition) throw new Error(message);
|
||||
}
|
||||
|
||||
export function runTests() {
|
||||
const projectRoot = process.cwd();
|
||||
const shellPartsPath = path.join(
|
||||
projectRoot,
|
||||
"components",
|
||||
"dashboard",
|
||||
"scan-terminal",
|
||||
"ScanTerminalShellParts.tsx",
|
||||
);
|
||||
const dashboardPath = path.join(
|
||||
projectRoot,
|
||||
"components",
|
||||
"dashboard",
|
||||
"ScanTerminalDashboard.tsx",
|
||||
);
|
||||
const panelPath = path.join(
|
||||
projectRoot,
|
||||
"components",
|
||||
"dashboard",
|
||||
"scan-terminal",
|
||||
"RunwayObservationsPanel.tsx",
|
||||
);
|
||||
const monitorPath = path.join(
|
||||
projectRoot,
|
||||
"components",
|
||||
"dashboard",
|
||||
"monitoring",
|
||||
"MonitorPanel.tsx",
|
||||
);
|
||||
|
||||
const shellPartsSource = fs.readFileSync(shellPartsPath, "utf8");
|
||||
const dashboardSource = fs.readFileSync(dashboardPath, "utf8");
|
||||
assert(
|
||||
shellPartsSource.includes('"runway"'),
|
||||
"scan terminal content view must include a runway tab state",
|
||||
);
|
||||
assert(
|
||||
dashboardSource.includes("跑道观测") && dashboardSource.includes('setActiveView("runway")'),
|
||||
"dashboard tabs must expose 跑道观测 next to 市场监控",
|
||||
);
|
||||
assert(
|
||||
fs.existsSync(panelPath),
|
||||
"RunwayObservationsPanel.tsx must render the dedicated runway tab body",
|
||||
);
|
||||
const panelSource = fs.readFileSync(panelPath, "utf8");
|
||||
assert(
|
||||
panelSource.includes("AMSC AWOS") &&
|
||||
panelSource.includes("TDZ") &&
|
||||
panelSource.includes("MID") &&
|
||||
panelSource.includes("END"),
|
||||
"runway tab must identify AMSC AWOS and show TDZ/MID/END point temperatures",
|
||||
);
|
||||
assert(
|
||||
panelSource.includes('key: "qingdao"') &&
|
||||
panelSource.includes("青岛") &&
|
||||
panelSource.includes("ZSQD"),
|
||||
"runway tab must include Qingdao / ZSQD AMSC AWOS runway observations",
|
||||
);
|
||||
assert(
|
||||
panelSource.includes("RKSI") &&
|
||||
panelSource.includes("RKPK") &&
|
||||
panelSource.includes("runway_pairs") &&
|
||||
panelSource.includes("runway_temps"),
|
||||
"runway tab must also own Seoul/Busan AMOS runway-pair observations",
|
||||
);
|
||||
const monitorSource = fs.readFileSync(monitorPath, "utf8");
|
||||
assert(
|
||||
monitorSource.includes("ignoreRunway: KOREA_RUNWAY_MONITOR_KEYS.has(key)") &&
|
||||
monitorSource.includes("showRunwayRows = !KOREA_RUNWAY_MONITOR_KEYS.has(key)"),
|
||||
"market monitor must not render Seoul/Busan runway data after it moves to the runway tab",
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user