Lock decision edge cases with business state tests
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.
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
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"),
|
||||
/峰值窗口已过,若无新高应避免追高/,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user