stock-portfolio/__tests__/tencent-quote.test.ts
kennethcheng 0051f92b2b v1.0.5 (2026-04-13)
- 📛 证券名称增强:修复 INTC 等证券名称显示为空的问题 (BUG-101)
- 🔒 SELL超量校验:修复卖出数量超过持仓导致持仓变负的Bug (BUG-002)
- 🔄 撤销BUY成本还原:修复撤销买入时平均成本计算公式错误 (BUG-003)
- 💹 Decimal精度计算:持仓分析改用Prisma.Decimal进行金融计算,防止浮点精度丢失 (BUG-004)
- 📛 证券名称显示:在持仓分析卡片、资产分布、盈亏排行等位置同时显示股票代码和名称
- 📋 证券数据库扩展:新增 Intel Corp. (INTC) 证券记录
- 🔍 回退逻辑增强:确保证券名称为空时显示代码而非空白
- 📈 腾讯行情解析升级:精准解析股票名称(索引1),支持港/A/美股及 ETF 名称自动获取
- 🔀 多市场涨跌解析修复:重构腾讯行情多市场适配层,美股(索引4/5)与港A股(索引31/32)使用差异化索引解析涨跌数据
- 🇨🇳 GBK中文解码修复:改用 `arrayBuffer() + TextDecoder('gbk')` 替代 `text()`,彻底解决A股/港股中文股票名称乱码问题
- 💱 JisuAPI实时汇率:接入 JisuAPI 获取实时汇率,缓存1小时,支持 CNY/HKD/USD 转换
- 📊 资产配置动态图:环形图改由后端实时聚合持仓数据驱动,支持 Tooltip 和百分比显示
- 🎨 资产配置图表优化:精美毛玻璃 Tooltip、颜色图标、去除生硬描边、useMemo 性能优化
- 💱 全局货币联动:资产配置图表数值随 CNY/USD/HKD 切换实时转换
- 📝 交易流水增强:新增证券名称列,显示"名称+代码"双行格式
- 💹 全局汇率展示:在导航栏实时显示 USD/CNY/HKD 汇率信息
- 🔧 BUG-202 修复:修正 `convertCurrency` 汇率换算逻辑(原逻辑除法/乘法颠倒,导致 USD→CNY 换算失效)
- 🔧 BUG-201 修复:腾讯行情 API 获取失败时,`priceAvailable` 标记配合前端显示 "N/A" 替代虚假 0%
- 🔧 BUG-203 增强:持仓分析 `name` 字段确保回退到 `pos.symbol`,名称永不空
- 💹 Decimal 精度保障:所有盈亏/汇率计算均使用 Prisma.Decimal,防止浮点精度丢失
2026-04-13 18:41:37 +08:00

88 lines
2.7 KiB
TypeScript

import { toTencentSymbol, parseTencentQuote, fetchPricesFromTencent } from '../src/lib/tencent-quote'
describe('腾讯行情接口符号转换', () => {
describe('toTencentSymbol', () => {
it('CN A股 - 上海以6开头', () => {
expect(toTencentSymbol('600000', 'CN')).toBe('sh600000')
})
it('CN A股 - 深圳以0/3开头', () => {
expect(toTencentSymbol('000001', 'CN')).toBe('sz000001')
})
it('CN A股 - 已带前缀', () => {
expect(toTencentSymbol('sh600000', 'CN')).toBe('sh600000')
})
it('HK 港股 - 纯数字', () => {
expect(toTencentSymbol('09868', 'HK')).toBe('r_hk09868')
})
it('HK 港股 - 已带hk前缀', () => {
expect(toTencentSymbol('hk09868', 'HK')).toBe('r_hk09868')
})
it('US 美股', () => {
expect(toTencentSymbol('AAPL', 'US')).toBe('s_usAAPL')
})
it('US 美股 - 已带前缀', () => {
expect(toTencentSymbol('s_usAAPL', 'US')).toBe('s_usaapl')
})
it('CRYPTO 加密货币', () => {
expect(toTencentSymbol('btc', 'CRYPTO')).toBe('usdtbtc')
})
})
describe('parseTencentQuote', () => {
it('正常解析腾讯行情数据', () => {
const data = 'v_xxx="100~Apple Inc~AAPL~185.50~183.20~1000000~..."'
const result = parseTencentQuote(data)
expect(result?.price).toBe(185.5)
expect(result?.change).toBeCloseTo(2.3, 10)
expect(result?.changePercent).toBeCloseTo(1.2554, 3)
})
it('格式错误返回null', () => {
expect(parseTencentQuote('invalid')).toBeNull()
})
it('字段不足返回null', () => {
expect(parseTencentQuote('v_xxx="100~Name~"')).toBeNull()
})
it('除以零防护 - 昨收价为0', () => {
const data = 'v_xxx="100~Name~AAPL~185.50~0~..."'
expect(parseTencentQuote(data)).toBeNull()
})
it('价格非数字返回null', () => {
const data = 'v_xxx="100~Name~AAPL~invalid~183.20~..."'
expect(parseTencentQuote(data)).toBeNull()
})
})
})
describe('fetchPricesFromTencent 批量获取', () => {
beforeEach(() => {
jest.clearAllMocks()
})
it('空数组返回空对象', async () => {
const result = await fetchPricesFromTencent([])
expect(result).toEqual({})
})
it('网络错误返回空对象', async () => {
global.fetch = jest.fn().mockRejectedValue(new Error('Network error'))
const result = await fetchPricesFromTencent([{ symbol: 'AAPL', marketType: 'US' }])
expect(result).toEqual({})
})
it('HTTP错误返回空对象', async () => {
global.fetch = jest.fn().mockResolvedValue({ ok: false, status: 500 })
const result = await fetchPricesFromTencent([{ symbol: 'AAPL', marketType: 'US' }])
expect(result).toEqual({})
})
})