TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import { describe, expect, it } from 'vitest';2import { readFileSync } from 'node:fs';3import path from 'node:path';4import meta from './meta.json' with { type: 'json' };5import { localMeta } from '../_lib/local-meta.js';6import { fixtureDir, listFixtures, loadFixture, runFixtureSuite } from '@rareindex/connectors/testing';7import createConnector, { parseCardPage, parseSetIndex, parseSetList, parseShortDate, tcgplayerIdFromUrl } from './index.js';8import { usdEur } from '../_g1-cards-eu-jp-lib/index.js';910const connector = createConnector(localMeta(meta));11const snap = (name: string) => readFileSync(path.join(fixtureDir('limitless-tcg'), name), 'utf8');1213describe('limitless-tcg', () => {14 runFixtureSuite(connector, it, expect);1516 it('parses Limitless dates, prices and TCGplayer ids', () => {17 expect(parseShortDate('26 Sep 25')).toBe('2025-09-26');18 expect(parseShortDate('17 Jul 26')).toBe('2026-07-17');19 expect(parseShortDate('n/a')).toBeNull();20 expect(usdEur('$0.25')).toEqual({ amount: 0.25, currency: 'USD' });21 expect(usdEur('1,585.73€')).toEqual({ amount: 1585.73, currency: 'EUR' });22 expect(usdEur('')).toBeNull();23 expect(tcgplayerIdFromUrl('https://partner.tcgplayer.com/LIMITLESS?u=https%3A%2F%2Fwww.tcgplayer.com%2Fproduct%2F654340%2Fpokemon-me01')).toBe('654340');24 });2526 it('parses the saved set index, set list and card page HTML', () => {27 const idx = parseSetIndex(snap('sets-index.html'), 'en');28 expect(idx.length).toBeGreaterThan(50);29 expect(idx[0]).toMatchObject({ lang: 'en' });30 expect(idx.every((s) => /^[A-Z0-9-]+$/i.test(s.code))).toBe(true);31 expect(idx.some((s) => s.releaseDate && /^\d{4}-\d{2}-\d{2}$/.test(s.releaseDate))).toBe(true);3233 const list = parseSetList(snap('set-list.html'));34 expect(list.setName).toBeTruthy();35 expect(list.cards.length).toBeGreaterThan(100);36 const first = list.cards[0]!;37 expect(first.number).toBe('1');38 expect(first.url).toMatch(/^https:\/\/limitlesstcg\.com\/cards\//);39 expect(first.image).toMatch(/\.png$/);40 expect(first.usd?.currency).toBe('USD');41 expect(first.eur?.currency).toBe('EUR');42 expect(first.usd?.tcgplayerId).toMatch(/^\d+$/);4344 const cp = parseCardPage(snap('card-page.html'));45 expect(cp.cardId).toBeGreaterThan(0);46 expect(cp.name).toBeTruthy();47 expect(cp.number).toBeTruthy();48 });4950 it('emits catalog items with relayed USD/EUR observations for English sets and none for Japanese sets', async () => {51 const en = loadFixture('limitless-tcg', 'en-set-list');52 const out = await connector.normalize(en.raw);53 const obs = out.filter((r) => r.kind === 'price_observation');54 expect(obs.length).toBeGreaterThan(0);55 for (const o of obs) {56 if (o.kind !== 'price_observation') continue;57 expect(['USD', 'EUR']).toContain(o.currency);58 expect(['tcgplayer', 'cardmarket']).toContain((o.attributes.metadata as { provider: string }).provider);59 expect(o.confidence).toBeLessThanOrEqual(0.6);60 }61 const jp = loadFixture('limitless-tcg', 'jp-set-list-no-prices');62 const jout = await connector.normalize(jp.raw);63 expect(jout.every((r) => r.kind === 'catalog_item')).toBe(true);64 for (const r of jout) if (r.kind === 'catalog_item') expect(r.attributes.language).toBe('Japanese');65 });6667 it('turns the price-history XHR into dated observations (cents → units, one per day per provider)', async () => {68 const names = listFixtures('limitless-tcg');69 const fx = loadFixture('limitless-tcg', names.find((n) => n === 'card-price-history')!);70 const out = await connector.normalize(fx.raw);71 expect(out.length).toBeGreaterThan(0);72 const dates = new Set<string>();73 for (const o of out) {74 if (o.kind !== 'price_observation') continue;75 expect(o.observationDate.getUTCHours()).toBe(0);76 expect(o.observationDate.getTime()).toBeLessThan(fx.raw.fetchedAt.getTime());77 expect(o.price).toBeLessThan(10_000);78 const k = `${(o.attributes.metadata as { provider: string }).provider}|${o.observationDate.toISOString()}`;79 expect(dates.has(k)).toBe(false);80 dates.add(k);81 }82 });83});84