Skip to content
markpaper

src/assets/names.test.ts

v0.3.0 · 3.3 KB

Download file
import { describe, expect, it } from 'vitest';
import {
  dexOfCoin,
  isSpotCoin,
  isValidDexName,
  MAX_COIN_NAME_LENGTH,
  parseCoin,
  qualifyCoin,
  stripDexPrefix,
} from './names.js';

describe('parseCoin', () => {
  it('parses main perps', () => {
    expect(parseCoin('BTC')).toEqual({ market: 'perp', coin: 'BTC', dex: '', base: 'BTC' });
    expect(parseCoin('kPEPE')).toMatchObject({ market: 'perp', dex: '', base: 'kPEPE' });
  });

  it('parses HIP-3 perps with the dex prefix', () => {
    expect(parseCoin('xyz:TSLA')).toEqual({ market: 'perp', coin: 'xyz:TSLA', dex: 'xyz', base: 'TSLA' });
  });

  it('parses spot pair and index forms', () => {
    expect(parseCoin('PURR/USDC')).toEqual({ market: 'spot', coin: 'PURR/USDC', form: 'pair', base: 'PURR', quote: 'USDC' });
    expect(parseCoin('@107')).toEqual({ market: 'spot', coin: '@107', form: 'index', index: 107 });
    expect(parseCoin('@0')).toMatchObject({ form: 'index', index: 0 });
  });

  it('rejects a doubled dex prefix (normalization bug upstream)', () => {
    expect(parseCoin('xyz:xyz:TSLA')).toBeNull();
  });

  it('rejects malformed names', () => {
    for (const bad of ['', ':TSLA', 'xyz:', '@', '@01', '@1a', '@-1', 'A/B/C', '/USDC', 'PURR/', 'xyz:A/B']) {
      expect(parseCoin(bad), bad).toBeNull();
    }
    expect(parseCoin(`BTC${String.fromCharCode(1)}`)).toBeNull();
    expect(parseCoin('A'.repeat(MAX_COIN_NAME_LENGTH + 1))).toBeNull();
    expect(parseCoin(undefined as unknown as string)).toBeNull();
  });
});

describe('isSpotCoin', () => {
  it('detects spot by "/" or "@"', () => {
    expect(isSpotCoin('PURR/USDC')).toBe(true);
    expect(isSpotCoin('@85')).toBe(true);
    expect(isSpotCoin('BTC')).toBe(false);
    expect(isSpotCoin('xyz:TSLA')).toBe(false);
  });
});

describe('qualifyCoin', () => {
  it('adds the prefix to bare HIP-3 names (old meta format, position.coin quirk)', () => {
    expect(qualifyCoin('xyz', 'TSLA')).toBe('xyz:TSLA');
  });

  it('is idempotent for already prefixed names (no xyz:xyz:)', () => {
    expect(qualifyCoin('xyz', 'xyz:TSLA')).toBe('xyz:TSLA');
    expect(qualifyCoin('xyz', qualifyCoin('xyz', 'SNDK'))).toBe('xyz:SNDK');
    expect(qualifyCoin('xyz', 'xyz:xyz:SNDK')).toBe('xyz:SNDK');
  });

  it('leaves main-dex names unchanged', () => {
    expect(qualifyCoin('', 'BTC')).toBe('BTC');
  });

  it('does not strip a different dex prefix', () => {
    expect(qualifyCoin('xyz', 'flx:ABC')).toBe('xyz:flx:ABC');
    expect(parseCoin(qualifyCoin('xyz', 'flx:ABC'))).toBeNull();
  });
});

describe('dexOfCoin / stripDexPrefix', () => {
  it('extracts the dex', () => {
    expect(dexOfCoin('xyz:TSLA')).toBe('xyz');
    expect(dexOfCoin('BTC')).toBe('');
    expect(dexOfCoin('@85')).toBe('');
    expect(dexOfCoin('xyz:xyz:A')).toBeNull();
  });

  it('strips the dex prefix of perps only', () => {
    expect(stripDexPrefix('xyz:INTC')).toBe('INTC');
    expect(stripDexPrefix('BTC')).toBe('BTC');
    expect(stripDexPrefix('PURR/USDC')).toBe('PURR/USDC');
  });
});

describe('isValidDexName', () => {
  it('accepts plain dex names and rejects separators', () => {
    expect(isValidDexName('xyz')).toBe(true);
    expect(isValidDexName('mkts')).toBe(true);
    for (const bad of ['', 'a:b', 'a/b', '@a', 'a b']) expect(isValidDexName(bad), bad).toBe(false);
  });
});
All files