// Wave 2B (data train) — sportsbook wordmarks. Every book the odds feed emits // resolves to a real brand (name + non-gray color); the ~8 major books render a // bundled local wordmark SVG; the ledger row renders BookWordmark, never bare // lowercase `row.book`. The SVGs are self-authored styled text wordmarks (not // copied trademarked logos), swappable for official press-kit art later. const fs = require('fs'); const path = require('path'); const { bookInfo, bookSlug, hasBookSvg, BUNDLED_BOOK_SVGS } = require('../../web/src/lib/books'); // The exact keys oddsNormalizer.ALLOWED_BOOKS emits into the pipeline. const ALLOWED_BOOKS = ['draftkings', 'fanduel', 'betmgm', 'caesars', 'fanatics', 'bet365', 'hardrockbet', 'pointsbet', 'betrivers', 'pinnacle']; const DEFAULT_FG = '#B8BCC8'; const REPO = path.resolve(__dirname, '../..'); const read = (p) => fs.readFileSync(path.join(REPO, p), 'utf8'); describe('bookInfo — every ALLOWED_BOOK is a real brand (no neutral-gray fall)', () => { test.each(ALLOWED_BOOKS)('%s resolves to a real brand name + color', (key) => { const b = bookInfo(key); // name must be a real brand, not the raw uppercased key echoed back // (compare the ACTUAL name to the uppercased key — do NOT uppercase the // name, or a correct casing-only brand like "DraftKings" would trivially // equal "DRAFTKINGS" and false-fail; "bet365" ≠ "BET365" still passes). expect(b.name).toBeTruthy(); expect(b.name).not.toBe(key.toUpperCase()); // color must not be the neutral-gray default expect(b.fg).toBeTruthy(); expect(b.fg.toLowerCase()).not.toBe(DEFAULT_FG.toLowerCase()); // never a bare lowercase feed key echoed as the display name — EXCEPT // bet365, whose official brand wordmark genuinely is lowercase "bet365". if (key !== 'bet365') expect(b.name).not.toBe(key); }); test('the 6 previously-missing keys now resolve (were gray)', () => { expect(bookInfo('fanatics').name).toBe('Fanatics'); expect(bookInfo('bet365').name).toBe('bet365'); expect(bookInfo('hardrockbet').name).toBe('Hard Rock'); expect(bookInfo('betrivers').name).toBe('BetRivers'); expect(bookInfo('pointsbet').name).toBe('PointsBet'); expect(bookInfo('pinnacle').name).toBe('Pinnacle'); }); test('case-insensitive: DraftKings / DK / draftkings all resolve the same', () => { expect(bookInfo('DraftKings').name).toBe('DraftKings'); expect(bookInfo('DK').name).toBe('DraftKings'); expect(bookInfo('draftkings').name).toBe('DraftKings'); }); }); describe('bundled book wordmark SVGs', () => { const EXPECTED = ['draftkings', 'fanduel', 'betmgm', 'caesars', 'bet365', 'pinnacle', 'hardrockbet', 'betrivers']; test('BUNDLED_BOOK_SVGS is exactly the 8 major books', () => { expect([...BUNDLED_BOOK_SVGS].sort()).toEqual([...EXPECTED].sort()); }); test.each(EXPECTED)('web/public/books/%s.svg exists and is a real ', (slug) => { const svg = read(`web/public/books/${slug}.svg`); expect(svg).toMatch(//); }); test('bookSlug resolves feed keys + codes to the bundled slug', () => { expect(bookSlug('draftkings')).toBe('draftkings'); expect(bookSlug('DK')).toBe('draftkings'); expect(bookSlug('hardrockbet')).toBe('hardrockbet'); expect(hasBookSvg('betmgm')).toBe(true); expect(hasBookSvg('MGM')).toBe(true); }); test('a book with no bundled SVG (fanatics) still has NO svg but a real name', () => { expect(hasBookSvg('fanatics')).toBe(false); expect(bookInfo('fanatics').name).toBe('Fanatics'); }); test('unknown book → no svg, no crash', () => { expect(hasBookSvg('zzzbook')).toBe(false); expect(bookSlug('zzzbook')).toBeNull(); }); }); describe('BookWordmark source — SVG-first, styled-text fallback, no lowercase leak', () => { const src = read('web/src/components/vyndr/BookWordmark.tsx'); test('references the local /books/{slug}.svg path', () => { expect(src).toContain('/books/'); expect(src).toMatch(/\$\{slug\}\.svg/); }); test('resolves via the books registry (hasBookSvg + bookInfo + bookSlug)', () => { expect(src).toContain('hasBookSvg'); expect(src).toContain('bookInfo'); expect(src).toContain('bookSlug'); }); test('falls back to the brand NAME (never the raw lowercase key)', () => { // the fallback renders b.name (properly-cased brand), not the raw `book` prop expect(src).toContain('b.name'); expect(src).not.toMatch(/>\s*\{book\}\s* { test('ledger page imports + uses BookWordmark', () => { const src = read('web/src/app/ledger/page.tsx'); expect(src).toMatch(/import\s*\{[^}]*BookWordmark[^}]*\}\s*from\s*'@\/components\/vyndr'/); expect(src).toContain(' { const src = read('web/src/app/u/[handle]/PublicProfile.tsx'); expect(src).toContain('BookWordmark'); expect(src).toContain('