TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import { describe, expect, it } from 'vitest';2import type { NormalizedRecord } from '@rareindex/shared';3import { ConnectorMetaSchema } from '@rareindex/connectors';4import { listFixtures, loadFixture, runFixtureSuite } from '@rareindex/connectors/testing';5import metaJson from './meta.json' with { type: 'json' };6import createConnector, { parseAuctionSnapshot, parsePastSales, parseSalePage } from './index.js';78const meta = ConnectorMetaSchema.parse(metaJson);9const connector = createConnector(meta);10const attrsOf = (r: NormalizedRecord) => {11 if (!('attributes' in r)) throw new Error(`record kind ${r.kind} has no attributes`);12 return r.attributes;13};1415const INDEX = `<h2>juillet 2026</h2><div class="card auction-card grid gap-y-4 md:grid-cols-12" wire:key="auction-1531"> <div class="auction-card-container self-end md:!col-span-4"> <a href="/fr/auctions/design-online-t2442" class="w-full h-full"> <img src="x" class="h-full w-full object-cover"> </a> </div> <div class="flex-col justify-between md:flex md:col-span-8"> <div> <div class="mb-1 text-sm uppercase !text-green "> Online Only </div> <h3 class="max-w-3xl"> <a href="/fr/auctions/design-online-t2442">Design Online</a> </h3></div></div></div>16<div class="card auction-card grid" wire:key="auction-1465"><a href="/fr/auctions/bijoux-horlogerie_20260218"></a><h3 class="max-w-3xl"> <a href="/fr/auctions/bijoux-horlogerie_20260218">Bijoux & Horlogerie</a> </h3></div>`;1718const SALE = `<h1 class="-mb-2 text-4xl">Bijoux & Horlogerie</h1><div wire:snapshot="{"data":{"page":[{"title":"Bijoux & Horlogerie","start_date":"2026-02-13 18:00","end_date":"2026-02-23 18:00","place":"Online","number":2381,"auction_status":"finished"}]}}"></div>19<div>Résultat (Frais de vente inclus)</div>20<div id="product-577430" class="card product-card swiper-slide !h-auto" wire:key="product-577430"> <div class="relative"> <a href="/fr/products/cartier-paris-1-2381-fr" class="absolute"></a> <div wire:snapshot="{"data":{"isFavorited":false}}"></div> <div class="product-card-container"> <img src="https://www.piasa.fr/image/resized/abc/320/480" class="block w-full h-auto"> </div> <div class=" -mt-2.5 mr-3 text-3xl font-medium text-gray-400 md:text-2xl lg:font-normal" > 01 </div> <h3 class=" text-sm md:text-base !normal-case" > CARTIER, Paris </h3> <div class=" text-sm md:text-base mt-2 normal-case"> Années 1900-10 </div> <div class=" -mb-1 text-xm uppercase text-gray-300">Estimation</div> <div class=" mt-1 uppercase ">6 000 / 8 000 €</div> <div class=" -mb-1 text-xm uppercase text-gray-300">Résultat</div> <div class=" mt-1 uppercase ">23 878 €</div> </div></div>21<div id="product-577431" class="card product-card swiper-slide !h-auto"> <a href="/fr/products/freres-rochat-suisse-vers-1810-2-2381-fr"></a> <div class=" -mt-2.5 mr-3 text-3xl font-medium text-gray-400" > 02 </div> <h3 class=" text-sm"> FRÈRES ROCHAT, Suisse </h3> <div class=" text-sm mt-2 normal-case"> Vers 1810 </div> <div class=" -mb-1 text-xm uppercase text-gray-300">Estimation</div> <div class=" mt-1 uppercase ">30 000 / 40 000 €</div> </div>`;2223describe('piasa', () => {24 runFixtureSuite(connector, it, expect);2526 it('fixtures: EUR premium-inclusive results', async () => {27 let sales = 0;28 for (const name of listFixtures('piasa')) {29 for (const r of await connector.normalize(loadFixture('piasa', name).raw)) {30 if (!('attributes' in r)) continue;31 expect(r.attributes.identifiers.piasa_lot).toMatch(/^[a-z0-9_-]+\/\S+$/);32 expect(r.sourceUrl).toMatch(/^https:\/\/www\.piasa\.fr\/fr\/products\//);33 if (r.kind === 'sale') {34 sales++;35 expect(r.currency).toBe('EUR');36 expect(r.buyerPremiumIncluded).toBe(true);37 expect(r.auctionHouse).toBe('PIASA');38 }39 }40 }41 expect(sales).toBeGreaterThan(5);42 });4344 it('parses the past-sales index, the Livewire snapshot and the inlined product cards', async () => {45 const sales = parsePastSales(INDEX);46 expect(sales.map((s) => s.id)).toEqual(['design-online-t2442', 'bijoux-horlogerie_20260218']);47 expect(sales[0]).toMatchObject({ title: 'Design Online', url: 'https://www.piasa.fr/fr/auctions/design-online-t2442', location: 'Online' });48 expect(parseAuctionSnapshot(SALE)).toMatchObject({ startDate: '2026-02-13 18:00', endDate: '2026-02-23 18:00', place: 'Online', number: '2381', status: 'finished' });49 const p = parseSalePage(SALE, sales[1]!)!;50 expect(p.hasMore).toBe(false);51 expect(p.sale).toMatchObject({ date: '2026-02-23T00:00:00.000Z', location: 'Online' });52 expect(p.lots).toHaveLength(2);53 expect(p.lots[0]).toMatchObject({ lotNo: '01', title: 'CARTIER, Paris', subtitle: 'Années 1900-10', price: 23878, currency: 'EUR', premiumIncluded: true, estimateLow: 6000, estimateHigh: 8000, sold: true, url: 'https://www.piasa.fr/fr/products/cartier-paris-1-2381-fr', image: 'https://www.piasa.fr/image/resized/abc/320/480' });54 expect(p.lots[1]).toMatchObject({ lotNo: '02', price: null, sold: false, estimateLow: 30000, estimateHigh: 40000 });55 const out = await connector.normalize({ url: sales[1]!.url, kind: 'sale', engine: 'api', fetchedAt: new Date(), payload: { kind: 'sale_results', url: sales[1]!.url, sale: { ...sales[1]!, ...p.sale, extra: { ...sales[1]!.extra, ...p.sale!.extra } }, page: 1, totalLots: 2, lots: p.lots } });56 expect(out.map((r) => r.kind)).toEqual(['sale', 'auction_lot']);57 if (out[0]!.kind === 'sale') expect(out[0]!.saleDate.toISOString()).toBe('2026-02-23T00:00:00.000Z');58 expect(['jewelry', 'other_watches']).toContain(attrsOf(out[0]!).categorySlug);59 expect(parseSalePage('<html>x</html>', sales[1]!)).toBeNull();60 });61});62