diff --git a/src/lib/stock-api.ts b/src/lib/stock-api.ts index 930d064..0d3825a 100755 --- a/src/lib/stock-api.ts +++ b/src/lib/stock-api.ts @@ -97,7 +97,9 @@ export interface BoardInfo { * - 8/4/920开头:北交所(BJB) * - 其他:主板 */ -export function getStockBoard(code: string): BoardInfo { +export function getStockBoard(code: string | null | undefined): BoardInfo { + // 题材列表等场景下 securityCode 可能为 null(无领涨股的题材),兜底为主板 + if (!code) return { board: "main", label: "", className: "" }; if (code.startsWith("688")) { return { board: "kcb", label: "科", className: "bg-red-500/10 text-red-500 border-red-500/30" }; } diff --git a/src/lib/theme-api.ts b/src/lib/theme-api.ts index ac44ae6..88d9b6b 100644 --- a/src/lib/theme-api.ts +++ b/src/lib/theme-api.ts @@ -6,8 +6,8 @@ import { getApiBaseUrl } from "@/lib/api-client"; export interface ThemeItem { themeCode: string; themeName: string; - securityName: string; // 领涨股名称 - securityCode: string; // 领涨股代码 + securityName: string | null; // 领涨股名称(无领涨股的题材为 null) + securityCode: string | null; // 领涨股代码(无领涨股的题材为 null) codeWithSuffix: string; hotRank: number; // 热度排名 f3: number | null; // 领涨股涨幅 diff --git a/tests/_stub-api-client.cjs b/tests/_stub-api-client.cjs new file mode 100644 index 0000000..2dc2bf2 --- /dev/null +++ b/tests/_stub-api-client.cjs @@ -0,0 +1,5 @@ +// stub:getStockBoard 测试用,仅用于解析 @/lib/api-client 别名(CommonJS) +function getApiBaseUrl() { + return "http://localhost:8000"; +} +module.exports = { getApiBaseUrl }; diff --git a/tests/get-stock-board-repro.mjs b/tests/get-stock-board-repro.mjs new file mode 100644 index 0000000..8892ff0 --- /dev/null +++ b/tests/get-stock-board-repro.mjs @@ -0,0 +1,63 @@ +// 最小复现/验证脚本:直接编译 src/lib/stock-api.ts 真实源码, +// 调用 getStockBoard(null),复现「Cannot read properties of null (reading 'startsWith')」。 +// 无测试框架,node 直接运行:node tests/get-stock-board-repro.mjs +import fs from "node:fs"; +import path from "node:path"; +import Module from "node:module"; +import ts from "typescript"; + +const srcPath = path.resolve("src/lib/stock-api.ts"); +const source = fs.readFileSync(srcPath, "utf8"); + +const js = ts.transpileModule(source, { + compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020, esModuleInterop: true }, +}).outputText; + +const mod = new Module(srcPath); +mod.filename = srcPath; +mod.paths = Module._nodeModulePaths(path.dirname(srcPath)); + +// 拦截 @/lib/api-client 别名导入(getStockBoard 本身不依赖它) +const origResolve = Module._resolveFilename; +Module._resolveFilename = function (request, ...args) { + if (request === "@/lib/api-client") return path.resolve("tests/_stub-api-client.cjs"); + return origResolve.call(this, request, ...args); +}; + +try { + mod._compile(js, srcPath); +} finally { + Module._resolveFilename = origResolve; +} + +const { getStockBoard } = mod.exports; + +// ---- 断言 ---- +function assertThrows(fn, label) { + try { + fn(); + console.log(`✗ ${label}: 未抛错`); + process.exitCode = 1; + } catch (e) { + console.log(`✓ ${label}: 抛错 -> ${e.message}`); + } +} + +function assertNoThrow(fn, label) { + try { + const r = fn(); + console.log(`✓ ${label}: 未抛错 -> ${JSON.stringify(r)}`); + return r; + } catch (e) { + console.log(`✗ ${label}: 抛错 -> ${e.message}`); + process.exitCode = 1; + } +} + +// 正常代码 +assertNoThrow(() => getStockBoard("600000"), "正常代码 getStockBoard('600000')"); +// 崩盘场景:securityCode 为 null(East Money 题材列表实测存在),修复后应兜底返回主板 +assertNoThrow(() => getStockBoard(null), "null securityCode"); +assertNoThrow(() => getStockBoard(undefined), "undefined securityCode"); +// 空串不崩 +assertNoThrow(() => getStockBoard(""), "空串 getStockBoard('')");