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, { parseListPage } from './index.js';8import { jpCondition, parseJpCardTitle } from '../_g1-cards-eu-jp-lib/index.js';910const connector = createConnector(localMeta(meta));1112describe('cardrush', () => {13 runFixtureSuite(connector, it, expect);1415 it('parses MTG and Pokémon store titles', () => {16 const m = parseJpCardTitle('[PLD](黒枠)ラノワールのエルフ/Llanowar Elves《日本語》【4ED】', { bracket: 'set' });17 expect(m).toMatchObject({ name: 'Llanowar Elves', conditionRaw: 'PLD', setCode: '4ED', language: 'Japanese', notes: ['黒枠'], rarity: null });18 expect(jpCondition('PLD')).toBeNull();19 const e = parseJpCardTitle('[EX]暗黒の儀式/Dark Ritual《英語》【4ED】', { bracket: 'set' });20 expect(e).toMatchObject({ name: 'Dark Ritual', language: 'English', setCode: '4ED' });21 expect(jpCondition(e.conditionRaw)).toBe('excellent');22 const p = parseJpCardTitle('〔PSA10鑑定済〕ミロカロスδ-デルタ種【★】{013/068}');23 expect(p).toMatchObject({ name: 'ミロカロスδ-デルタ種', grader: 'psa', grade: '10', rarity: '★', number: '013', total: '068' });24 const box = parseJpCardTitle('ブースターパック 受け継がれる意志【未開封BOX】{-}');25 expect(box.sealed).toBe(true);26 expect(box.number).toBeNull();27 const mixed = parseJpCardTitle('〔※状態難/PSA8鑑定済〕リザードン LV.76(かえん/マークあり)【★】{旧裏}');28 expect(mixed.grade).toBe('8');29 expect(mixed.number).toBe('旧裏');30 });3132 it('parses the saved list page: items, yen prices, stock, sold-out flag, pager', () => {33 const doc = readFileSync(path.join(fixtureDir('cardrush'), 'list-page.html'), 'utf8');34 const { title, items, hasNext } = parseListPage(doc, 'www.cardrush-mtg.jp');35 expect(title).toBeTruthy();36 expect(items.length).toBe(100);37 expect(hasNext).toBe(true);38 const first = items[0]!;39 expect(first.url).toMatch(/^https:\/\/www\.cardrush-mtg\.jp\/product\/\d+$/);40 expect(first.priceJpy).toBeGreaterThan(0);41 expect(first.image).toMatch(/^https:\/\//);42 expect(items.some((i) => i.soldOut && i.stock === 0)).toBe(true);43 expect(items.some((i) => !i.soldOut && (i.stock ?? 0) > 0)).toBe(true);44 });4546 it('emits JPY listings per store with the right category, language and availability', async () => {47 for (const name of listFixtures('cardrush')) {48 const fx = loadFixture('cardrush', name);49 const out = await connector.normalize(fx.raw);50 expect(out.length).toBeGreaterThan(0);51 const payload = fx.raw.payload as { store: { categorySlug: string }; items: Array<{ soldOut: boolean; title: string }> };52 for (const r of out) {53 if (r.kind !== 'listing') continue;54 expect(r.currency).toBe('JPY');55 expect(r.price).toBeGreaterThan(0);56 expect(r.seller).toBe('Cardrush');57 expect(r.attributes.categorySlug).toBe(payload.store.categorySlug);58 expect(['Japanese', 'English', 'Korean', 'Chinese']).toContain(r.attributes.language);59 expect(r.attributes.identifiers.cardrush_product_id).toMatch(/^www\.cardrush[a-z-]*\.jp\/\d+$/);60 }61 const ended = out.filter((r) => r.kind === 'listing' && r.availability === 'ended').length;62 const soldOutIn = payload.items.filter((i) => i.soldOut && !/オリパ|サプライ/.test(i.title)).length;63 expect(ended).toBe(soldOutIn);64 if (name.includes('sealed')) for (const r of out) if (r.kind === 'listing') expect(r.condition.completeness).toBe('sealed');65 if (name.includes('pokemon')) expect(out.some((r) => r.kind === 'listing' && r.grade.grader === 'psa' && r.grade.grade)).toBe(true);66 }67 });68});69