feat: productize landing and chart education

This commit is contained in:
2569718930@qq.com
2026-06-06 19:17:24 +08:00
parent c2f9bc5994
commit 7a38e3588a
7 changed files with 722 additions and 45 deletions
@@ -0,0 +1,66 @@
import * as Chart from "@/components/dashboard/scan-terminal/LiveTemperatureThresholdChart";
function assert(condition: unknown, message: string): asserts condition {
if (!condition) throw new Error(message);
}
export function runTests() {
const buildItems = (Chart as any).__buildAdvancedWeatherVariableItemsForTest;
assert(typeof buildItems === "function", "advanced weather variable item builder should be exported for tests");
const buildCadence = (Chart as any).__buildSourceCadenceSummaryForTest;
assert(typeof buildCadence === "function", "source cadence summary builder should be exported for tests");
const items = buildItems(
{
metar_context: {
airport_wind_speed_kt: 7,
airport_wind_dir: 220,
airport_humidity: 68,
},
},
{
current: { dewpoint: 18.2 },
airportPrimary: {
wind_speed_kt: 9,
wind_dir: 240,
humidity: 64,
pressure_hpa: 1009.4,
source_label: "MADIS HFMETAR",
},
},
false,
);
assert(items.length === 5, `expected five advanced variable items, got ${items.length}`);
assert(items.some((item: any) => item.key === "wind_dir" && item.value === "240°"), "wind direction should prefer airport-primary detail data");
assert(items.some((item: any) => item.key === "wind_speed" && item.value === "9 kt"), "wind speed should include kt units");
assert(items.some((item: any) => item.key === "dewpoint" && item.value === "18.2°C"), "dew point should render from current conditions");
assert(items.some((item: any) => item.key === "humidity" && item.value === "64%"), "humidity should render as a percentage");
assert(items.some((item: any) => item.key === "pressure" && item.value === "1009.4 hPa"), "pressure should render as hPa");
const emptyItems = buildItems({}, {}, true);
assert(emptyItems.length === 0, "advanced variables should stay hidden when no source fields exist");
const backendCadence = buildCadence(
{},
{
airportPrimary: {
source_code: "custom_source",
source_label: "Custom Feed",
freshness: { native_update_interval_sec: 420, freshness_status: "fresh" },
},
},
true,
);
assert(backendCadence?.cadence === "420s", "source cadence should prefer backend native_update_interval_sec");
assert(backendCadence?.label.includes("Custom Feed"), "source cadence should include the source label");
const amscCadence = buildCadence({}, { airportPrimary: { source: "amsc_awos" } }, false);
assert(amscCadence?.cadence === "180s", "AMSC AWOS should fall back to 180s source cadence");
const amosCadence = buildCadence({}, { airportPrimary: { source_label: "AMOS runway" } }, false);
assert(amosCadence?.cadence === "60s", "AMOS should fall back to 60s source cadence");
const madisCadence = buildCadence({}, { airportPrimary: { source_code: "madis_hfmetar" } }, false);
assert(madisCadence?.cadence === "300s", "MADIS should fall back to 300s source cadence");
}
@@ -0,0 +1,57 @@
import { getDocsPage } from "@/content/docs/docs";
function assert(condition: unknown, message: string): asserts condition {
if (!condition) throw new Error(message);
}
function pageText(slug: string, locale: "zh-CN" | "en-US") {
const page = getDocsPage(slug);
assert(page, `${slug} docs page should exist`);
return [
page.content[locale].title,
page.content[locale].description,
...page.content[locale].sections.flatMap((section) => [
section.title,
...section.blocks.flatMap((block) => {
if (block.type === "paragraph" || block.type === "callout") return [block.text];
if (block.type === "bullets" || block.type === "steps") return block.items;
if (block.type === "link") return [block.label, block.caption || ""];
if (block.type === "image") return [block.alt, block.caption || ""];
return [];
}),
]),
].join("\n");
}
export function runTests() {
const introZh = pageText("intro", "zh-CN");
assert(
introZh.includes("结算源优先") && introZh.includes("实时实测终端"),
"intro should position PolyWeather as a settlement-source-first live terminal",
);
const chartGuideZh = pageText("chart-guide", "zh-CN");
assert(chartGuideZh.includes("如何读 PolyWeather 图表"), "chart guide title should be present");
assert(chartGuideZh.includes("高级气象变量") && chartGuideZh.includes("默认隐藏"), "chart guide should explain advanced variables as hidden-by-default context");
assert(chartGuideZh.includes("不要把概率温度带当成实测曲线"), "chart guide should warn against reading probability as observation");
const realtimeSourcesZh = pageText("realtime-sources", "zh-CN");
assert(realtimeSourcesZh.includes("AMSC 180s"), "realtime sources should document the AMSC 180s cadence");
assert(realtimeSourcesZh.includes("AMOS 60s"), "realtime sources should document the AMOS 60s cadence");
assert(realtimeSourcesZh.includes("SSE patch"), "realtime sources should document the SSE patch path");
const settlementSourcesZh = pageText("settlement-sources", "zh-CN");
assert(settlementSourcesZh.includes("结算站点"), "settlement source guide should exist");
assert(settlementSourcesZh.includes("机场 METAR") && settlementSourcesZh.includes("官方结算站点"), "settlement source guide should distinguish airport and official station settlement");
assert(
getDocsPage("chart-guide")?.group === "getting-started" &&
getDocsPage("realtime-sources")?.group === "settlement" &&
getDocsPage("settlement-sources")?.group === "settlement",
"docs navigation should expose chart, realtime source, and settlement source guides in the right groups",
);
const chartGuideEn = pageText("chart-guide", "en-US");
assert(chartGuideEn.includes("How To Read PolyWeather Charts"), "English chart guide should exist");
assert(chartGuideEn.includes("hidden by default"), "English chart guide should describe hidden-by-default advanced variables");
}