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, { parseCataloguePage, parseNzDate, parsePreviousAuctions } 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 = `<div class="card auction-card mb-3"> <div class="card-body"> <span class="badge bg-secondary">Timed Auction</span> <h2 class="card-title">Vintage Toy Auction</h2> <h3 class="card-subtitle text-muted mb-2">Thursday, 2 - Wednesday, 15 October 2025</h3> <a class="btn btn-dark" href="/1858">Auction Details</a> </div></div>16<div class="card auction-card mb-3"><div class="card-body"><h2 class="card-title">Sterling Silver & Accoutrements</h2><h3 class="card-subtitle text-muted mb-2">Wednesday, 21 June 2023</h3><a class="btn btn-dark" href="/1745">Auction Details</a></div></div>`;1718const CAT = `<h1 class="page-header">Sterling Silver & Accoutrements <small class="text-muted">Wednesday, 21 June 2023 / 10:00 am start</small></h1><button type="button" class="btn dropdown-toggle"> Page 1 of 6 </button>19<div class="lot-grid row"> <div class="col mb-4"> <div id="0001" class="card h-100" data-ln="0001"> <a href="/1745/catalogue/0001"> <img class="card-img-top" src="/img/loading600.gif" data-src="https://d1v8q4.tandemauctions.com/1745/0001/sq/875763a3.webp" alt="Geo II S/S Sugar Caster"> </a> <div class="card-body"> <p class="card-subtitle mb-2 text-muted"> <a href="/1745/catalogue/0001"> 0001 </a> </p> <p class="card-text"> Geo II S/S Sugar Caster<br /> <small class="estimate">Estimate: $200 - $350</small> </p> </div> <div class="card-footer realised"> Realised: $140 <small>+ premium</small> </div> </div> </div>20<div class="col mb-4"> <div id="0002" class="card h-100" data-ln="0002"> <a href="/1745/catalogue/0002"><img data-src="https://d1v8q4.tandemauctions.com/1745/0002/sq/x.webp" alt="Rolex Oyster Perpetual wristwatch"></a> <div class="card-body"> <p class="card-text"> Rolex Oyster Perpetual wristwatch<br /> <small class="estimate">Estimate: $3,000 - $5,000</small> </p> </div> </div> </div></div>`;2122describe('dunbar-sloane', () => {23 runFixtureSuite(connector, it, expect);2425 it('fixtures: NZD hammer ("plus premium") results', async () => {26 let sales = 0;27 for (const name of listFixtures('dunbar-sloane')) {28 for (const r of await connector.normalize(loadFixture('dunbar-sloane', name).raw)) {29 if (!('attributes' in r)) continue;30 expect(r.attributes.identifiers.dunbar_sloane_lot).toMatch(/^\d+\/\S+$/);31 expect(r.sourceUrl).toMatch(/^https:\/\/auctions\.dunbarsloane\.co\.nz\/\d+\/catalogue\//);32 if (r.kind === 'sale') {33 sales++;34 expect(r.currency).toBe('NZD');35 expect(r.buyerPremiumIncluded).toBe(false);36 expect(r.auctionHouse).toBe('Dunbar Sloane');37 }38 }39 }40 expect(sales).toBeGreaterThan(10);41 });4243 it('parses NZ dates, the previous-auctions list and a catalogue page', async () => {44 expect(parseNzDate('Thursday, 2 - Wednesday, 15 October 2025')).toBe('2025-10-02T00:00:00.000Z');45 expect(parseNzDate('Wednesday, 21 June 2023 / 10:00 am start')).toBe('2023-06-21T00:00:00.000Z');46 expect(parseNzDate('Friday, 17 - Tuesday, 28 October 2025')).toBe('2025-10-17T00:00:00.000Z');47 const sales = parsePreviousAuctions(INDEX);48 expect(sales.map((s) => s.id)).toEqual(['1858', '1745']);49 expect(sales[0]).toMatchObject({ title: 'Vintage Toy Auction', url: 'https://auctions.dunbarsloane.co.nz/1858/catalogue', date: '2025-10-02T00:00:00.000Z' });50 const p = parseCataloguePage(CAT, sales[1]!, 1)!;51 expect(p.hasMore).toBe(true);52 expect(parseCataloguePage(CAT, sales[1]!, 6)!.hasMore).toBe(false);53 expect(p.sale).toMatchObject({ title: 'Sterling Silver & Accoutrements', date: '2023-06-21T00:00:00.000Z' });54 expect(p.lots[0]).toMatchObject({ lotNo: '0001', title: 'Geo II S/S Sugar Caster', price: 140, currency: 'NZD', premiumIncluded: false, estimateLow: 200, estimateHigh: 350, sold: true, url: 'https://auctions.dunbarsloane.co.nz/1745/catalogue/0001', image: 'https://d1v8q4.tandemauctions.com/1745/0001/sq/875763a3.webp' });55 expect(p.lots[1]).toMatchObject({ lotNo: '0002', price: null, sold: false, estimateLow: 3000, estimateHigh: 5000 });56 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 }, page: 1, totalLots: null, lots: p.lots } });57 expect(out.map((r) => r.kind)).toEqual(['sale', 'auction_lot']);58 expect(attrsOf(out[0]!).categorySlug).toBe('silver');59 expect(attrsOf(out[1]!).categorySlug).toBe('rolex');60 expect(connector.salePageUrl(sales[1]!, 2)).toBe('https://auctions.dunbarsloane.co.nz/1745/catalogue?page=2');61 expect(parseCataloguePage('<html>x</html>', sales[1]!, 1)).toBeNull();62 });63});64