// MLB park factors (Session 15) — pin the membership list + assert // the expected magnitude of the headline parks. Park factors shift // year-to-year but the directional signal (Coors hot, Oracle cold) // is stable; the test catches obvious typos. const pf = require('../../src/data/parkFactors'); describe('parkFactors', () => { test('covers all 30 MLB teams', () => { const codes = Object.keys(pf.PARK_FACTORS); expect(codes.length).toBe(30); }); test('every entry has hr/h/r as finite numbers', () => { for (const [code, vals] of Object.entries(pf.PARK_FACTORS)) { expect(typeof code).toBe('string'); expect(code).toMatch(/^[A-Z]{2,3}$/); expect(Number.isFinite(vals.hr)).toBe(true); expect(Number.isFinite(vals.h)).toBe(true); expect(Number.isFinite(vals.r)).toBe(true); } }); test('Coors Field is the most extreme HR park', () => { const hrs = Object.values(pf.PARK_FACTORS).map((p) => p.hr); const maxHr = Math.max(...hrs); expect(pf.PARK_FACTORS.COL.hr).toBe(maxHr); expect(pf.PARK_FACTORS.COL.hr).toBeGreaterThan(120); }); test('Oracle Park (SF) suppresses HRs heavily', () => { expect(pf.PARK_FACTORS.SF.hr).toBeLessThan(95); }); test('Coors also boosts hits and runs', () => { expect(pf.PARK_FACTORS.COL.h).toBeGreaterThan(100); expect(pf.PARK_FACTORS.COL.r).toBeGreaterThan(100); }); test('most parks land within ±15% of neutral', () => { // Coors + SF are deliberate outliers; check the rest cluster. const codes = Object.keys(pf.PARK_FACTORS).filter((c) => c !== 'COL' && c !== 'SF'); for (const code of codes) { const { hr, h, r } = pf.PARK_FACTORS[code]; expect(hr).toBeGreaterThanOrEqual(85); expect(hr).toBeLessThanOrEqual(115); expect(h).toBeGreaterThanOrEqual(90); expect(h).toBeLessThanOrEqual(110); expect(r).toBeGreaterThanOrEqual(90); expect(r).toBeLessThanOrEqual(110); } }); }); describe('getParkFactor', () => { test('returns the row for known teams', () => { expect(pf.getParkFactor('NYY').hr).toBe(pf.PARK_FACTORS.NYY.hr); expect(pf.getParkFactor('COL').hr).toBeGreaterThan(120); }); test('case-insensitive', () => { expect(pf.getParkFactor('nyy').hr).toBeGreaterThan(0); expect(pf.getParkFactor('Col')).toBe(pf.PARK_FACTORS.COL); }); test('whitespace-tolerant', () => { expect(pf.getParkFactor(' COL ')).toBeTruthy(); }); test('returns null for unknown / empty', () => { expect(pf.getParkFactor('XYZ')).toBeNull(); expect(pf.getParkFactor('')).toBeNull(); expect(pf.getParkFactor(null)).toBeNull(); expect(pf.getParkFactor(undefined)).toBeNull(); }); }); describe('getParkFactorOrNeutral', () => { test('returns the row when known', () => { expect(pf.getParkFactorOrNeutral('NYY')).toBe(pf.PARK_FACTORS.NYY); }); test('falls back to neutral 100s when unknown', () => { expect(pf.getParkFactorOrNeutral('XYZ')).toEqual({ hr: 100, h: 100, r: 100 }); expect(pf.getParkFactorOrNeutral(null)).toEqual({ hr: 100, h: 100, r: 100 }); }); }); describe('immutability', () => { test('PARK_FACTORS is frozen at the top level', () => { expect(Object.isFrozen(pf.PARK_FACTORS)).toBe(true); }); test('NEUTRAL is frozen', () => { expect(Object.isFrozen(pf.NEUTRAL)).toBe(true); }); });