import { describe, expect, it } from 'vitest'; import { getConnectorMeta } from '@rareindex/connectors'; import { loadFixture, runFixtureSuite } from '@rareindex/connectors/testing'; import createConnector, { parseProductPage } from './index.js'; const connector = createConnector(getConnectorMeta('pricecharting')); describe('pricecharting', () => { runFixtureSuite(connector, it, expect); it('maps a video game page to catalog + guide values + eBay sales', async () => { const fx = loadFixture('pricecharting', 'nintendo-64__zelda-ocarina-of-time'); const out = await connector.normalize(fx.raw); const catalog = out.find((r) => r.kind === 'catalog_item'); expect(catalog).toBeTruthy(); if (catalog?.kind !== 'catalog_item') throw new Error(); expect(catalog.attributes.categorySlug).toBe('nintendo_games'); expect(catalog.attributes.set).toBe('Nintendo 64'); expect(catalog.attributes.name).toBe('Zelda Ocarina of Time'); expect(catalog.attributes.identifiers.pricecharting_id).toBe('3977'); expect(catalog.attributes.identifiers.upc).toMatch(/^\d{12}$/); const guides = out.filter((r) => r.kind === 'price_observation'); expect(guides.map((g) => g.kind === 'price_observation' && g.condition.completeness)).toEqual(expect.arrayContaining(['loose', 'cib', 'sealed'])); const sales = out.filter((r) => r.kind === 'sale'); expect(sales.length).toBeGreaterThan(50); const s = sales[0]; if (s?.kind !== 'sale') throw new Error(); expect(s.currency).toBe('USD'); expect(s.externalId).toMatch(/^ebay:\d+$/); expect(s.saleDate.getUTCFullYear()).toBeGreaterThanOrEqual(2024); expect(['loose', 'cib', 'sealed', 'box_only', 'manual_only', null]).toContain(s.condition.completeness); }); it('maps LEGO, Funko and comics pages to the right categories', async () => { const lego = await connector.normalize(loadFixture('pricecharting', 'lego-star-wars__cloud-city-10123').raw); const legoCat = lego.find((r) => r.kind === 'catalog_item'); if (legoCat?.kind !== 'catalog_item') throw new Error(); expect(legoCat.attributes.categorySlug).toBe('lego_sets'); expect(legoCat.attributes.number).toBe('10123'); expect(legoCat.attributes.identifiers.lego_set_number).toBe('10123'); expect(legoCat.attributes.set).toBe('Star Wars'); const funko = await connector.normalize(loadFixture('pricecharting', 'funko-pop-marvel__spider-man-metallic-3').raw); const funkoCat = funko.find((r) => r.kind === 'catalog_item'); if (funkoCat?.kind !== 'catalog_item') throw new Error(); expect(funkoCat.attributes.categorySlug).toBe('funko'); expect(funkoCat.attributes.name).toBe('Spider-Man'); expect(funkoCat.attributes.variant).toBe('Metallic'); expect(funkoCat.attributes.number).toBe('3'); const comic = await connector.normalize(loadFixture('pricecharting', 'comic-books-amazing-spider-man__amazing-spider-man-300-1988').raw); const comicCat = comic.find((r) => r.kind === 'catalog_item'); if (comicCat?.kind !== 'catalog_item') throw new Error(); expect(comicCat.attributes.categorySlug).toBe('marvel_comics'); expect(comicCat.attributes.number).toBe('300'); expect(comicCat.attributes.year).toBe(1988); const graded98 = comic.find((r) => r.kind === 'price_observation' && r.grade.grade === '9.8'); expect(graded98).toBeTruthy(); }); it('parses the product page HTML deterministically', () => { const html = `

Super Mario 64 [Player's Choice] Nintendo 64

$39.99$134.48$1,628.37
2026-09-05Mario 64 cart$37.95
UPC:045496870010
`; const p = parseProductPage(html, 'https://www.pricecharting.com/game/nintendo-64/super-mario-64'); expect(p.title).toBe("Super Mario 64 [Player's Choice]"); expect(p.consoleUri).toBe('nintendo-64'); expect(p.productId).toBe('3924'); expect(p.prices.map((x) => x.value)).toEqual([39.99, 134.48, 1628.37]); expect(p.sales).toEqual([{ tab: 'used', date: '2026-09-05', title: 'Mario 64 cart', price: 37.95, ebayId: '1', listedPrice: null }]); expect(p.details['UPC']).toBe('045496870010'); }); }); describe('pricecharting cards', () => { it('maps a Pokémon Base Set card to the pokemontcg set code and per-grade sales', async () => { const out = await connector.normalize(loadFixture('pricecharting', 'pokemon-base-set__charizard-4').raw); const cat = out.find((r) => r.kind === 'catalog_item'); if (cat?.kind !== 'catalog_item') throw new Error('no catalog item'); expect(cat.attributes.categorySlug).toBe('pokemon'); expect(cat.attributes.setCode).toBe('BS'); expect(cat.attributes.number).toBe('4'); expect(cat.attributes.name).toBe('Charizard'); expect(cat.attributes.identifiers.pokemontcg_id).toBe('base1-4'); expect(cat.attributes.identifiers.tcgplayer_id).toBe('42382'); const guides = out.filter((r) => r.kind === 'price_observation'); expect(guides.some((g) => g.kind === 'price_observation' && g.grade.grader === 'psa' && g.grade.grade === '10')).toBe(true); expect(guides.some((g) => g.kind === 'price_observation' && g.grade.grader === 'bgs' && g.grade.qualifier === 'Black Label')).toBe(true); const sales = out.filter((r) => r.kind === 'sale'); expect(sales.length).toBeGreaterThan(10); expect(sales.some((s) => s.kind === 'sale' && s.grade.grader === 'psa' && s.grade.grade === '10')).toBe(true); expect(sales.some((s) => s.kind === 'sale' && s.grade.grader === null && s.grade.grade === null)).toBe(true); }); it('keeps 1st Edition printings separate from the unlimited pokemontcg id', async () => { const out = await connector.normalize(loadFixture('pricecharting', 'pokemon-base-set__charizard-1st-edition-4').raw); const cat = out.find((r) => r.kind === 'catalog_item'); if (cat?.kind !== 'catalog_item') throw new Error('no catalog item'); expect(cat.attributes.variant).toBe('1st Edition'); expect(cat.attributes.identifiers.pokemontcg_id).toBeUndefined(); expect(cat.attributes.setCode).toBe('BS'); }); it('resolves a Magic card to its Scryfall id and collector number', async () => { const out = await connector.normalize(loadFixture('pricecharting', 'magic-alpha__black-lotus').raw); const cat = out.find((r) => r.kind === 'catalog_item'); if (cat?.kind !== 'catalog_item') throw new Error('no catalog item'); expect(cat.attributes.categorySlug).toBe('magic_the_gathering'); expect(cat.attributes.setCode).toBe('LEA'); expect(cat.attributes.set).toBe('Limited Edition Alpha'); expect(cat.attributes.number).toBe('232'); expect(cat.attributes.identifiers.scryfall_id).toMatch(/^[0-9a-f-]{36}$/); }); it('parses Yu-Gi-Oh! printed codes into set code + number', async () => { const out = await connector.normalize(loadFixture('pricecharting', 'yugioh-lob__blue-eyes-1st-edition').raw); const cat = out.find((r) => r.kind === 'catalog_item'); if (cat?.kind !== 'catalog_item') throw new Error('no catalog item'); expect(cat.attributes.categorySlug).toBe('yugioh'); expect(cat.attributes.setCode).toBe('LOB'); expect(cat.attributes.number).toBe('LOB-001'); expect(cat.attributes.name).toBe('Blue-Eyes White Dragon'); expect(cat.attributes.variant).toBe('1st Edition'); }); });