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, loadFixture, runFixtureSuite } from '@rareindex/connectors/testing';7import createConnector, { parseSellPage, parseVersions } from './index.js';8import { yen } from '../_g1-cards-eu-jp-lib/index.js';910const connector = createConnector(localMeta(meta));1112describe('yuyu-tei', () => {13 runFixtureSuite(connector, it, expect);1415 it('parses yen prices in the shop format', () => {16 expect(yen('118,000 円')).toBe(118000);17 expect(yen('2,980 円')).toBe(2980);18 expect(yen('¥ 187,614')).toBe(187614);19 expect(yen('売り切れ')).toBeNull();20 });2122 it('parses the saved sell page: rarity sections, numbers, prices, stock, kizu flag', () => {23 const doc = readFileSync(path.join(fixtureDir('yuyu-tei'), 'sell-page.html'), 'utf8');24 const { setName, items } = parseSellPage(doc);25 expect(setName).toMatch(/^\[/);26 expect(items.length).toBeGreaterThan(50);27 const first = items[0]!;28 expect(first.url).toMatch(/^https:\/\/yuyu-tei\.jp\/sell\/poc\/card\//);29 expect(first.number).toMatch(/^\d+\/\d+$/);30 expect(first.rarity).toBeTruthy();31 expect(first.priceJpy).toBeGreaterThan(0);32 expect(first.stock).not.toBeNull();33 expect(first.image).toMatch(/^https:\/\/card\.yuyu-tei\.jp\//);34 expect(items.every((i) => i.kizu === false)).toBe(true);35 // the expansion filter (vers[] checkboxes) is rendered on sell pages too: code + "[M6] name" label36 const versions = parseVersions(doc);37 expect(versions.length).toBeGreaterThan(20);38 expect(versions[0]).toMatchObject({ code: expect.stringMatching(/^[a-z0-9-]+$/), name: expect.stringMatching(/^\[/) });39 });4041 it('emits JPY listings (never sales) plus catalog items with Japanese language and set code', async () => {42 const fx = loadFixture('yuyu-tei', 'pokemon-set');43 const out = await connector.normalize(fx.raw);44 const listings = out.filter((r) => r.kind === 'listing');45 const cats = out.filter((r) => r.kind === 'catalog_item');46 expect(listings.length).toBeGreaterThan(0);47 expect(cats.length).toBe(listings.length);48 for (const l of listings) {49 if (l.kind !== 'listing') continue;50 expect(l.currency).toBe('JPY');51 expect(l.price).toBeGreaterThan(0);52 expect(l.seller).toBe('Yuyu-tei');53 expect(l.attributes.language).toBe('Japanese');54 expect(l.attributes.setCode).toMatch(/^[A-Z0-9-]+$/i);55 expect(l.attributes.categorySlug).toBe('pokemon');56 expect(['available', 'ended']).toContain(l.availability);57 }58 const op = loadFixture('yuyu-tei', 'one-piece-set');59 const oout = await connector.normalize(op.raw);60 for (const r of oout) if ('attributes' in r) expect(r.attributes.categorySlug).toBe('one_piece_card_game');61 const numbered = oout.find((r) => r.kind === 'catalog_item' && r.attributes.number);62 expect(numbered && numbered.kind === 'catalog_item' ? numbered.attributes.number : null).toMatch(/^[A-Z]+\d+-\d+$/);63 });64});65