The dashboard risk is now mostly contradictory state combinations rather than styling. This extracts calendar action grouping into a pure utility and adds a lightweight TypeScript business-state runner so key decision states can be asserted without adding a test dependency. Constraint: No new dependencies; use the existing TypeScript package for a local runner. Rejected: Only rely on Next build/typecheck | it cannot catch product-language regressions such as fallback AI being labeled complete. Confidence: high Scope-risk: moderate Reversibility: clean Tested: npm run test:business Tested: npm run build Not-tested: Browser-rendered mobile fold interaction snapshots.
82 lines
2.3 KiB
TypeScript
82 lines
2.3 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import {
|
|
buildCalendarCoreReason,
|
|
buildCalendarMeta,
|
|
getCalendarActionGroup,
|
|
} from "@/components/dashboard/scan-terminal/calendar-action-utils";
|
|
import type { ScanOpportunityRow } from "@/lib/dashboard-types";
|
|
|
|
function row(overrides: Partial<ScanOpportunityRow>): ScanOpportunityRow {
|
|
return {
|
|
city: "London",
|
|
city_display_name: "London",
|
|
current_temp: 21,
|
|
deb_prediction: 24,
|
|
id: "london-2026-04-27",
|
|
local_date: "2026-04-27",
|
|
local_time: "4/27·00:01",
|
|
selected_date: "2026-04-27",
|
|
temp_symbol: "°C",
|
|
...overrides,
|
|
} as ScanOpportunityRow;
|
|
}
|
|
|
|
export function runTests() {
|
|
const snapshotMs = Date.UTC(2026, 3, 27, 16, 1);
|
|
const sameNow = snapshotMs;
|
|
const localTimeCase = row({
|
|
minutes_until_peak_end: 119,
|
|
minutes_until_peak_start: 0,
|
|
window_phase: "active_peak",
|
|
});
|
|
const meta = buildCalendarMeta(localTimeCase, "zh-CN", snapshotMs, sameNow);
|
|
|
|
assert.equal(meta.startAtMs, snapshotMs);
|
|
assert.ok(meta.localWindowLabel, "should expose the user's local peak window");
|
|
assert.notEqual(meta.localWindowLabel, localTimeCase.local_time);
|
|
assert.doesNotMatch(meta.localWindowLabel || "", /4\/27·00:01/);
|
|
|
|
const activeGroup = getCalendarActionGroup(localTimeCase, meta, sameNow, "zh-CN");
|
|
assert.equal(activeGroup.key, "now");
|
|
|
|
const breakoutReason = buildCalendarCoreReason(
|
|
row({
|
|
cluster_core_high: 24.7,
|
|
cluster_core_low: 23.1,
|
|
cluster_model_count: 6,
|
|
current_temp: 25.2,
|
|
model_cluster_sources: {
|
|
ecmwf: 24.4,
|
|
gfs: 24.7,
|
|
},
|
|
}),
|
|
activeGroup,
|
|
"zh-CN",
|
|
);
|
|
assert.match(breakoutReason, /实测已高于模型上沿/);
|
|
assert.doesNotMatch(breakoutReason, /等待下一报文确认方向/);
|
|
|
|
const pastMeta = buildCalendarMeta(
|
|
row({
|
|
minutes_until_peak_end: -20,
|
|
minutes_until_peak_start: -120,
|
|
window_phase: "post_peak",
|
|
}),
|
|
"zh-CN",
|
|
snapshotMs,
|
|
sameNow,
|
|
);
|
|
const pastGroup = getCalendarActionGroup(
|
|
row({ minutes_until_peak_end: -20, minutes_until_peak_start: -120, window_phase: "post_peak" }),
|
|
pastMeta,
|
|
sameNow,
|
|
"zh-CN",
|
|
);
|
|
|
|
assert.equal(pastGroup.key, "past");
|
|
assert.match(
|
|
buildCalendarCoreReason(row({}), pastGroup, "zh-CN"),
|
|
/峰值窗口已过,若无新高应避免追高/,
|
|
);
|
|
}
|