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 { fileURLToPath } from 'node:url';5import { loadFixture, runFixtureSuite } from '@rareindex/connectors/testing';6import { localMeta } from '../_lib/local-meta.js';7import createConnector, { gradeToken, parseCategories, parseSeriesPage, parseYearMint, updatedDate } from './index.js';89const dir = path.dirname(fileURLToPath(import.meta.url));10const connector = createConnector(localMeta(JSON.parse(readFileSync(path.join(dir, 'meta.json'), 'utf8'))));1112const PAGE = `<h1 class="census-header-title">Morgan Dollars (1878-1921)</h1>13<table><tbody><tr class="header-row"><th>Year/Mint</th><th>Denom</th><th>Desig</th></tr>14<tr class="ms 1878"><td ngc-coin-view-menu="339018" parent-coin-id="17072" subcategory-id="49"><span><a href="/redirects/coin-explorer/17072/"><span class='standard'>1878 8TF</span><span class='merged'>1878 8TF $1 MS</span></a></span><span class="types price-guide-only"><span class="last-updated">Updated: 9/8/2026</span></span></td><td>$1</td><td>MS</td><td class="census-only">0</td></tr>15<tr class="pf 1878"><td parent-coin-id="17200" subcategory-id="49"><span><a><span class='standard'>1878 8TF</span><span class='merged'>1878 8TF $1 PF</span></a></span><span class="last-updated">Updated: 1/24/2025</span></td><td>$1</td><td>PF</td><td class="census-only">0</td></tr>16<tr class="ms 1893"><td parent-coin-id="17300" subcategory-id="49"><span><a><span class='standard'>1893-S</span><span class='merged'>1893-S $1 MS</span></a></span></td><td>$1</td><td>MS</td></tr>17</tbody></table>18<table><tbody>19<tr class="ms 1878"><td class="base grade-PrAg link" price-history-link coin-id="17072" grade="PrAg"><a>$59.00</a></td><td class="star grade-PrAg link"></td><td class="base grade-VF link" price-history-link coin-id="17072" grade="VF"><a>$100</a></td><td class="plus grade-XF link" price-history-link coin-id="17072" grade="XF+"><a>$135</a></td><td class="base grade-65 link" price-history-link coin-id="17072" grade="65"><a>$2,350</a></td><td class="plus grade-65 link" price-history-link coin-id="17072" grade="65+"><a>$3,000</a></td><td class="star grade-66 link" price-history-link coin-id="17072" grade="66★"><a>$9,000</a></td></tr>20<tr class="pf 1878"><td class="base grade-63 link" price-history-link coin-id="17200" grade="63"><a>$8,500</a></td></tr>21<tr class="ms 1893"><td class="base grade-65 link" price-history-link coin-id="17300" grade="65"><a>$750,000</a></td></tr>22</tbody></table>`;2324describe('ngc-priceguide', () => {25 runFixtureSuite(connector, it, expect);2627 it('parses categories JSON into subcategories', () => {28 const subs = parseCategories([{ CategoryID: 14, Name: 'Dollars', SeoName: 'dollars', Subcategories: [{ SubcategoryID: 49, Name: 'Morgan Dollars (1878-1921)', SubcategorySeoName: 'morgan-dollars-1878-1921', HasMS: true, HasPF: true }] }]);29 expect(subs).toEqual([{ id: '49', name: 'Morgan Dollars (1878-1921)', categorySeo: 'dollars', categoryName: 'Dollars' }]);30 expect(parseCategories({ nope: 1 })).toEqual([]);31 });3233 it('parses a series page: coin rows joined with price cells by coin id', () => {34 const p = parseSeriesPage(PAGE, 'https://www.ngccoin.com/price-guide/united-states/dollars/49/', 'dollars')!;35 expect(p.seriesName).toBe('Morgan Dollars (1878-1921)');36 expect(p.subcategoryId).toBe('49');37 expect(p.coins.length).toBe(3);38 expect(p.coins[0]).toMatchObject({ coinId: '17072', yearMint: '1878 8TF', description: '1878 8TF $1 MS', denomination: '$1', designation: 'MS', strike: 'ms', updatedText: '9/8/2026' });39 expect(p.coins[0]!.prices).toEqual({ PrAg: 59, VF: 100, 'XF+': 135, '65': 2350, '65+': 3000, '66★': 9000 });40 expect(p.coins[1]).toMatchObject({ strike: 'pf', designation: 'PF', prices: { '63': 8500 } });41 expect(p.coins[2]!.updatedText).toBeNull();42 });4344 it('helpers: dates, year/mint, grade tokens', () => {45 expect(updatedDate('9/8/2026')?.toISOString()).toBe('2026-09-08T00:00:00.000Z');46 expect(updatedDate(null)).toBeNull();47 expect(parseYearMint('1878 8TF')).toEqual({ year: 1878, mintMark: null, variety: '8TF' });48 expect(parseYearMint('1893-S')).toEqual({ year: 1893, mintMark: 'S', variety: null });49 expect(gradeToken('65', 'MS', 'ms')).toBe('MS65');50 expect(gradeToken('65+', 'MS', 'ms')).toBe('MS65+');51 expect(gradeToken('63', 'PF', 'pf')).toBe('PF63');52 expect(gradeToken('66★', 'MS', 'ms')).toBe('MS66★');53 expect(gradeToken('XF+', 'MS', 'ms')).toBe('XF+');54 expect(gradeToken('PrAg', 'MS', 'ms')).toBe('PrAg');55 expect(gradeToken('65', 'MSDPL', 'ms')).toBe('MS65');56 });5758 it('normalises catalog items + dated guide observations; rows without an Updated date are dropped', async () => {59 const p = parseSeriesPage(PAGE, 'https://www.ngccoin.com/price-guide/united-states/dollars/49/', 'dollars')!;60 const out = await connector.normalize({ url: p.url, externalId: 'x', kind: 'price_observation', engine: 'api', fetchedAt: new Date('2026-09-08T12:00:00Z'), payload: p });61 const items = out.filter((r) => r.kind === 'catalog_item');62 const obs = out.filter((r) => r.kind === 'price_observation');63 expect(items.length).toBe(2); // 1893-S has no Updated date → skipped64 expect(obs.length).toBe(7);65 const o = obs.find((r) => r.kind === 'price_observation' && r.grade.grade === 'PrAg')!;66 if (o.kind !== 'price_observation') throw new Error('obs');67 expect(o).toMatchObject({ priceKind: 'guide_value', price: 59, currency: 'USD', grade: { grader: 'ngc', grade: 'PrAg' } });68 expect(o.observationDate.toISOString()).toBe('2026-09-08T00:00:00.000Z');69 expect(o.attributes).toMatchObject({ categorySlug: 'coins', year: 1878, country: 'US', series: 'Morgan Dollars (1878-1921)', identifiers: { ngc_coin_id: '17072' }, variant: '8TF' });70 const ms65 = obs.find((r) => r.kind === 'price_observation' && r.grade.grade === 'MS65');71 expect(ms65 && ms65.kind === 'price_observation' ? ms65.price : null).toBe(2350);72 const pf = obs.find((r) => r.kind === 'price_observation' && r.grade.grade === 'PF63');73 expect(pf && pf.kind === 'price_observation' ? pf.observationDate.toISOString() : null).toBe('2025-01-24T00:00:00.000Z');74 });7576 it('fixtures: Morgan dollars and Walking Liberty halves', async () => {77 for (const name of ['series-morgan-dollars-49', 'series-walking-liberty-halves-42']) {78 const out = await connector.normalize(loadFixture('ngc-priceguide', name).raw);79 expect(out.some((r) => r.kind === 'price_observation' && r.grade.grader === 'ngc')).toBe(true);80 for (const r of out) if (r.kind === 'price_observation') expect(r.currency).toBe('USD');81 }82 });83});84