import { describe, it, expect } from 'vitest'; import { formatSymbolForTradingView, formatMarketCapValue, formatChangePercent, getChangeColorClass, calculateNewsDistribution, } from '@/lib/utils'; describe('formatSymbolForTradingView', () => { it('returns empty string for empty input', () => { expect(formatSymbolForTradingView('')).toBe(''); }); it('returns uppercase US symbol as-is', () => { expect(formatSymbolForTradingView('AAPL')).toBe('AAPL'); expect(formatSymbolForTradingView('aapl')).toBe('AAPL'); }); // Asia-Pacific exchanges it('maps Taiwan (.TW) to TWSE prefix', () => { expect(formatSymbolForTradingView('2330.TW')).toBe('TWSE:2330'); }); it('maps Taiwan OTC (.TWO) to TPEX prefix', () => { expect(formatSymbolForTradingView('6488.TWO')).toBe('TPEX:6488'); }); it('maps Tokyo (.T) to TSE prefix', () => { expect(formatSymbolForTradingView('7203.T')).toBe('TSE:7203'); }); it('maps Hong Kong (.HK) to HKEX prefix', () => { expect(formatSymbolForTradingView('0700.HK')).toBe('HKEX:0700'); }); it('maps Shanghai (.SS) to SSE prefix', () => { expect(formatSymbolForTradingView('600519.SS')).toBe('SSE:600519'); }); it('maps Shenzhen (.SZ) to SZSE prefix', () => { expect(formatSymbolForTradingView('000858.SZ')).toBe('SZSE:000858'); }); it('maps Korea (.KS) to KRX prefix', () => { expect(formatSymbolForTradingView('005930.KS')).toBe('KRX:005930'); }); it('maps India NSE (.NS) to NSE prefix', () => { expect(formatSymbolForTradingView('RELIANCE.NS')).toBe('NSE:RELIANCE'); }); it('maps India BSE (.BO) to BSE prefix', () => { expect(formatSymbolForTradingView('RELIANCE.BO')).toBe('BSE:RELIANCE'); }); it('maps Australia (.AX) to ASX prefix', () => { expect(formatSymbolForTradingView('BHP.AX')).toBe('ASX:BHP'); }); it('maps Singapore (.SI) to SGX prefix', () => { expect(formatSymbolForTradingView('D05.SI')).toBe('SGX:D05'); }); // European exchanges it('maps London (.L) to LSE prefix', () => { expect(formatSymbolForTradingView('SHEL.L')).toBe('LSE:SHEL'); }); it('maps Germany/Xetra (.DE) to XETR prefix', () => { expect(formatSymbolForTradingView('SAP.DE')).toBe('XETR:SAP'); }); it('maps Paris (.PA) to EURONEXT prefix', () => { expect(formatSymbolForTradingView('MC.PA')).toBe('EURONEXT:MC'); }); it('maps Amsterdam (.AS) to EURONEXT prefix', () => { expect(formatSymbolForTradingView('ASML.AS')).toBe('EURONEXT:ASML'); }); it('maps Milan (.MI) to MIL prefix', () => { expect(formatSymbolForTradingView('ENI.MI')).toBe('MIL:ENI'); }); it('maps Madrid (.MC) to BME prefix', () => { expect(formatSymbolForTradingView('SAN.MC')).toBe('BME:SAN'); }); it('maps Swiss (.SW) to SIX prefix', () => { expect(formatSymbolForTradingView('NESN.SW')).toBe('SIX:NESN'); }); it('maps Stockholm (.ST) to OMXSTO prefix', () => { expect(formatSymbolForTradingView('ERIC-B.ST')).toBe('OMXSTO:ERIC-B'); }); // Americas it('maps Toronto (.TO) to TSX prefix', () => { expect(formatSymbolForTradingView('RY.TO')).toBe('TSX:RY'); }); it('maps Brazil (.SA) to BMFBOVESPA prefix', () => { expect(formatSymbolForTradingView('VALE3.SA')).toBe('BMFBOVESPA:VALE3'); }); // Middle East & Africa it('maps Tel Aviv (.TA) to TASE prefix', () => { expect(formatSymbolForTradingView('TEVA.TA')).toBe('TASE:TEVA'); }); it('maps Johannesburg (.JO) to JSE prefix', () => { expect(formatSymbolForTradingView('NPN.JO')).toBe('JSE:NPN'); }); // Case insensitivity it('handles lowercase input', () => { expect(formatSymbolForTradingView('2330.tw')).toBe('TWSE:2330'); expect(formatSymbolForTradingView('shel.l')).toBe('LSE:SHEL'); }); // Longer suffix matched before shorter (.TWO before .TW) it('prioritizes longer suffixes over shorter ones', () => { // .TWO should match before .TW expect(formatSymbolForTradingView('6488.TWO')).toBe('TPEX:6488'); // But .TW still works for regular TW symbols expect(formatSymbolForTradingView('2330.TW')).toBe('TWSE:2330'); }); }); describe('formatMarketCapValue', () => { it('returns N/A for invalid inputs', () => { expect(formatMarketCapValue(0)).toBe('N/A'); expect(formatMarketCapValue(-100)).toBe('N/A'); expect(formatMarketCapValue(NaN)).toBe('N/A'); expect(formatMarketCapValue(Infinity)).toBe('N/A'); }); it('formats trillions', () => { expect(formatMarketCapValue(3.1e12)).toBe('$3.10T'); }); it('formats billions', () => { expect(formatMarketCapValue(900e9)).toBe('$900.00B'); }); it('formats millions', () => { expect(formatMarketCapValue(25e6)).toBe('$25.00M'); }); it('formats sub-million values', () => { expect(formatMarketCapValue(999999.99)).toBe('$999999.99'); }); }); describe('formatChangePercent', () => { it('returns empty string for undefined/null', () => { expect(formatChangePercent(undefined)).toBe(''); expect(formatChangePercent(null as any)).toBe(''); }); it('formats positive change with + sign', () => { expect(formatChangePercent(2.5)).toBe('+2.50%'); }); it('formats negative change', () => { expect(formatChangePercent(-1.23)).toBe('-1.23%'); }); it('formats zero change', () => { expect(formatChangePercent(0)).toBe('0.00%'); }); }); describe('getChangeColorClass', () => { it('returns gray for zero or undefined', () => { expect(getChangeColorClass(0)).toBe('text-gray-400'); expect(getChangeColorClass(undefined)).toBe('text-gray-400'); }); it('returns green for positive', () => { expect(getChangeColorClass(1.5)).toBe('text-green-500'); }); it('returns red for negative', () => { expect(getChangeColorClass(-0.5)).toBe('text-red-500'); }); }); describe('calculateNewsDistribution', () => { it('returns 3 items per symbol for 1-2 symbols', () => { expect(calculateNewsDistribution(1).itemsPerSymbol).toBe(3); expect(calculateNewsDistribution(2).itemsPerSymbol).toBe(3); }); it('returns 2 items per symbol for exactly 3', () => { expect(calculateNewsDistribution(3).itemsPerSymbol).toBe(2); }); it('returns 1 item per symbol for 4+', () => { expect(calculateNewsDistribution(5).itemsPerSymbol).toBe(1); expect(calculateNewsDistribution(10).itemsPerSymbol).toBe(1); }); });