import { describe, expect, it } from 'vitest'; import { readFileSync } from 'node:fs'; import path from 'node:path'; import meta from './meta.json' with { type: 'json' }; import { localMeta } from '../_lib/local-meta.js'; import { fixtureDir, loadFixture, runFixtureSuite } from '@rareindex/connectors/testing'; import createConnector, { parseSellPage, parseVersions } from './index.js'; import { yen } from '../_g1-cards-eu-jp-lib/index.js'; const connector = createConnector(localMeta(meta)); describe('yuyu-tei', () => { runFixtureSuite(connector, it, expect); it('parses yen prices in the shop format', () => { expect(yen('118,000 円')).toBe(118000); expect(yen('2,980 円')).toBe(2980); expect(yen('¥ 187,614')).toBe(187614); expect(yen('売り切れ')).toBeNull(); }); it('parses the saved sell page: rarity sections, numbers, prices, stock, kizu flag', () => { const doc = readFileSync(path.join(fixtureDir('yuyu-tei'), 'sell-page.html'), 'utf8'); const { setName, items } = parseSellPage(doc); expect(setName).toMatch(/^\[/); expect(items.length).toBeGreaterThan(50); const first = items[0]!; expect(first.url).toMatch(/^https:\/\/yuyu-tei\.jp\/sell\/poc\/card\//); expect(first.number).toMatch(/^\d+\/\d+$/); expect(first.rarity).toBeTruthy(); expect(first.priceJpy).toBeGreaterThan(0); expect(first.stock).not.toBeNull(); expect(first.image).toMatch(/^https:\/\/card\.yuyu-tei\.jp\//); expect(items.every((i) => i.kizu === false)).toBe(true); // the expansion filter (vers[] checkboxes) is rendered on sell pages too: code + "[M6] name" label const versions = parseVersions(doc); expect(versions.length).toBeGreaterThan(20); expect(versions[0]).toMatchObject({ code: expect.stringMatching(/^[a-z0-9-]+$/), name: expect.stringMatching(/^\[/) }); }); it('emits JPY listings (never sales) plus catalog items with Japanese language and set code', async () => { const fx = loadFixture('yuyu-tei', 'pokemon-set'); const out = await connector.normalize(fx.raw); const listings = out.filter((r) => r.kind === 'listing'); const cats = out.filter((r) => r.kind === 'catalog_item'); expect(listings.length).toBeGreaterThan(0); expect(cats.length).toBe(listings.length); for (const l of listings) { if (l.kind !== 'listing') continue; expect(l.currency).toBe('JPY'); expect(l.price).toBeGreaterThan(0); expect(l.seller).toBe('Yuyu-tei'); expect(l.attributes.language).toBe('Japanese'); expect(l.attributes.setCode).toMatch(/^[A-Z0-9-]+$/i); expect(l.attributes.categorySlug).toBe('pokemon'); expect(['available', 'ended']).toContain(l.availability); } const op = loadFixture('yuyu-tei', 'one-piece-set'); const oout = await connector.normalize(op.raw); for (const r of oout) if ('attributes' in r) expect(r.attributes.categorySlug).toBe('one_piece_card_game'); const numbered = oout.find((r) => r.kind === 'catalog_item' && r.attributes.number); expect(numbered && numbered.kind === 'catalog_item' ? numbered.attributes.number : null).toMatch(/^[A-Z]+\d+-\d+$/); }); });