TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import { describe, expect, it } from 'vitest';2import { parsePrice, formatMoney } from './money.js';3import { parseSourceDate } from './dates.js';4import { median, trimmedMean, ewMean, maxDrawdown } from './stats.js';5import { normalizeForMatch, jaccard, slugify } from './text.js';67describe('parsePrice', () => {8 it('parses US formats', () => {9 expect(parsePrice('$1,234.56')).toMatchObject({ amount: 1234.56, currency: 'USD' });10 expect(parsePrice('US $2,500.00')).toMatchObject({ amount: 2500, currency: 'USD' });11 expect(parsePrice('Sold for $420,000')).toMatchObject({ amount: 420000, currency: 'USD' });12 });13 it('parses EU formats', () => {14 expect(parsePrice('1.234,56 €')).toMatchObject({ amount: 1234.56, currency: 'EUR' });15 expect(parsePrice('EUR 12,50')).toMatchObject({ amount: 12.5, currency: 'EUR' });16 expect(parsePrice('2.500 €')).toMatchObject({ amount: 2500, currency: 'EUR' });17 });18 it('parses GBP/JPY/CAD', () => {19 expect(parsePrice('£12.99')).toMatchObject({ amount: 12.99, currency: 'GBP' });20 expect(parsePrice('¥15,000')).toMatchObject({ amount: 15000, currency: 'JPY' });21 expect(parsePrice('CA$1 200,00')).toMatchObject({ amount: 1200, currency: 'CAD' });22 expect(parsePrice('C$99')).toMatchObject({ amount: 99, currency: 'CAD' });23 });24 it('does not invent a currency', () => {25 expect(parsePrice('1500')).toMatchObject({ amount: 1500, currency: null });26 expect(parsePrice('1500', 'GBP')).toMatchObject({ amount: 1500, currency: 'GBP' });27 expect(parsePrice('no price')).toBeNull();28 });29 it('formats', () => {30 expect(formatMoney(8450)).toBe('$8,450');31 expect(formatMoney(12.5)).toBe('$12.50');32 expect(formatMoney(null)).toBe('—');33 });34});3536describe('parseSourceDate', () => {37 it('handles common formats', () => {38 expect(parseSourceDate('2024-03-05')?.toISOString()).toBe('2024-03-05T00:00:00.000Z');39 expect(parseSourceDate('Mar 5, 2024')?.toISOString()).toBe('2024-03-05T00:00:00.000Z');40 expect(parseSourceDate('5 March 2024')?.toISOString()).toBe('2024-03-05T00:00:00.000Z');41 expect(parseSourceDate('03/05/2024')?.toISOString()).toBe('2024-03-05T00:00:00.000Z');42 expect(parseSourceDate('05.03.2024', { dayFirst: true })?.toISOString()).toBe('2024-03-05T00:00:00.000Z');43 expect(parseSourceDate('garbage')).toBeNull();44 });45});4647describe('stats', () => {48 it('median/trimmed/ew/drawdown', () => {49 expect(median([3, 1, 2])).toBe(2);50 expect(median([4, 1, 2, 3])).toBe(2.5);51 expect(trimmedMean([1, 2, 3, 4, 100], 0.2)).toBe(3);52 expect(ewMean([{ value: 100, ageDays: 0 }, { value: 50, ageDays: 1000 }])).toBeGreaterThan(90);53 expect(maxDrawdown([100, 120, 60, 90])).toBeCloseTo(0.5);54 });55});5657describe('text', () => {58 it('normalises and compares', () => {59 const a = '1999 Pokemon Base Charizard 1st PSA 10';60 const b = 'Pokémon 1999 First Edition Charizard Holo PSA GEM MT 10';61 expect(normalizeForMatch(a)).toBe('1999 pokemon base charizard 1st psa 10');62 expect(jaccard(a, b)).toBeGreaterThan(0.3);63 expect(slugify('1999 Base Set Charizard #4/102 — 1st Edition')).toBe('1999-base-set-charizard-4-102-1st-edition');64 });65});66