From 4d22191a2dfa71c0509b956fef727412c99c3722 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Wed, 27 May 2026 21:02:08 +0800 Subject: [PATCH] Fix city selector fallback coverage --- .../dashboard/ScanTerminalDashboard.tsx | 9 ++- .../__tests__/cityFallbackRows.test.ts | 67 ++++++++++++++++++- .../scan-terminal/city-fallback-rows.ts | 22 ++++++ 3 files changed, 95 insertions(+), 3 deletions(-) diff --git a/frontend/components/dashboard/ScanTerminalDashboard.tsx b/frontend/components/dashboard/ScanTerminalDashboard.tsx index a21a5c6d..dcd04a36 100644 --- a/frontend/components/dashboard/ScanTerminalDashboard.tsx +++ b/frontend/components/dashboard/ScanTerminalDashboard.tsx @@ -50,7 +50,10 @@ import { KoyfinRowsTable } from "@/components/dashboard/scan-terminal/KoyfinRows import { rowName, pct, money, temp, edgeClass } from "@/components/dashboard/scan-terminal/utils"; import { CitySelectorDropdown } from "@/components/dashboard/scan-terminal/CitySelectorDropdown"; import { GridLayoutSelector } from "@/components/dashboard/scan-terminal/GridLayoutSelector"; -import { cityListItemsToScanRows } from "@/components/dashboard/scan-terminal/city-fallback-rows"; +import { + cityListItemsToScanRows, + mergeScanRowsWithCityFallbackRows, +} from "@/components/dashboard/scan-terminal/city-fallback-rows"; function createEmptyAccess(loading = true): ProAccessState { return { @@ -1107,7 +1110,9 @@ function ScanTerminalScreen() { const rows = useMemo( () => { const scanRows = terminalData?.rows || []; - return sortRowsByUserTime(scanRows.length ? scanRows : cityFallbackRows); + return sortRowsByUserTime( + mergeScanRowsWithCityFallbackRows(scanRows, cityFallbackRows), + ); }, [cityFallbackRows, terminalData?.rows], ); diff --git a/frontend/components/dashboard/scan-terminal/__tests__/cityFallbackRows.test.ts b/frontend/components/dashboard/scan-terminal/__tests__/cityFallbackRows.test.ts index 43aad277..a35f8172 100644 --- a/frontend/components/dashboard/scan-terminal/__tests__/cityFallbackRows.test.ts +++ b/frontend/components/dashboard/scan-terminal/__tests__/cityFallbackRows.test.ts @@ -1,4 +1,7 @@ -import { cityListItemsToScanRows } from "@/components/dashboard/scan-terminal/city-fallback-rows"; +import { + cityListItemsToScanRows, + mergeScanRowsWithCityFallbackRows, +} from "@/components/dashboard/scan-terminal/city-fallback-rows"; import type { CityListItem } from "@/lib/dashboard-types"; function assert(condition: unknown, message: string) { @@ -49,6 +52,68 @@ export function runTests() { assert(rows[0].temp_symbol === "°C", "celsius cities should use °C"); assert(rows[1].trading_region === "north_america", "known cities should keep their configured product region"); assert(rows[1].temp_symbol === "°F", "fahrenheit cities should use °F"); + + const scanRows = [ + { + id: "scan:paris", + city: "paris", + city_display_name: "Paris", + current_temp: 31, + deb_prediction: 30.5, + temp_symbol: "°C", + }, + ] as any[]; + const fallbackRows = cityListItemsToScanRows([ + { + airport: "Paris Le Bourget", + display_name: "Paris", + icao: "LFPB", + lat: 48.97, + lon: 2.44, + name: "paris", + risk_level: "medium", + temp_unit: "celsius", + utc_offset_seconds: 7200, + }, + { + airport: "Munich Airport", + display_name: "Munich", + icao: "EDDM", + lat: 48.35, + lon: 11.78, + name: "munich", + risk_level: "medium", + temp_unit: "celsius", + utc_offset_seconds: 7200, + }, + { + airport: "Cape Town International", + display_name: "Cape Town", + icao: "FACT", + lat: -33.97, + lon: 18.6, + name: "cape town", + risk_level: "medium", + temp_unit: "celsius", + utc_offset_seconds: 7200, + }, + ]); + const merged = mergeScanRowsWithCityFallbackRows(scanRows, fallbackRows); + const mergedCities = merged.map((row) => row.city); + + assert(merged.length === 3, "city selector data should include scan rows plus missing registry cities"); + assert(mergedCities.includes("paris"), "merged rows should keep scan city"); + assert(mergedCities.includes("munich"), "merged rows should include missing registry city"); + assert(mergedCities.includes("cape town"), "merged rows should include another missing registry city"); + assert( + merged.filter((row) => row.city === "paris").length === 1, + "merged rows should not duplicate cities already present in scan rows", + ); + assert( + merged.find((row) => row.city === "paris")?.current_temp === 31, + "scan rows should win over fallback rows for cities already present", + ); + assert( dashboardSource.includes("cityListItemsToScanRows") && dashboardSource.includes("/api/cities") && diff --git a/frontend/components/dashboard/scan-terminal/city-fallback-rows.ts b/frontend/components/dashboard/scan-terminal/city-fallback-rows.ts index 474f78c1..ee7bab95 100644 --- a/frontend/components/dashboard/scan-terminal/city-fallback-rows.ts +++ b/frontend/components/dashboard/scan-terminal/city-fallback-rows.ts @@ -61,3 +61,25 @@ export function cityListItemsToScanRows(cities: CityListItem[]): ScanOpportunity }; }); } + +export function mergeScanRowsWithCityFallbackRows( + scanRows: ScanOpportunityRow[], + fallbackRows: ScanOpportunityRow[], +): ScanOpportunityRow[] { + const merged = [...(scanRows || [])]; + const seen = new Set(); + + for (const row of merged) { + const key = normalizeCityKey(row.city || row.city_display_name || row.display_name || ""); + if (key) seen.add(key); + } + + for (const row of fallbackRows || []) { + const key = normalizeCityKey(row.city || row.city_display_name || row.display_name || ""); + if (!key || seen.has(key)) continue; + seen.add(key); + merged.push(row); + } + + return merged; +}