import { describe, expect, it } from 'vitest'; import { loadEligiblePlanes, slotsUsedFor } from './RightPane'; import type { PackageState, PlaneState, StateSnapshot } from '@/lib/game/state'; // v0.12.0 — the package-model picker rules: a plane can LOAD a package only while // it is GROUNDED at the package's origin with a free slot (capacity from the // snapshot catalog; expired packages still occupy theirs). The Load button acts // directly on the single eligible plane, or shows a picker when several qualify. const CATALOG = [ { model: 'atr72', label: 'ATR 72-600', price: 200_000, maxFuel: 6300, burnPerKm: 1.75, speedKmh: 510, capacity: 6 }, ]; function plane(over: Partial & Pick): PlaneState { return { model: 'atr72', status: 'available', fuel: 100, maxFuel: 100, locationIcao: 'EDDF', locationName: 'Frankfurt Main', destIcao: null, destName: null, departureSimTime: null, lat: 50, lon: 8.5, ...over, }; } function pkg(over: Partial & Pick): PackageState { return { originIcao: 'EDDF', originName: 'Frankfurt Main', destIcao: 'LEMD', destName: 'Madrid Barajas', reward: 1000, cargoType: 'mail', cargoCount: 4, deadlineSimTime: 1_000_000, status: 'open', planeId: null, createdSimTime: 0, ...over, }; } function snap(planes: PlaneState[], packages: PackageState[] = []): StateSnapshot { return { account: { cash: 0, reputation: 0 }, simState: { clockMs: 0, timeScale: 1, paused: false }, planes, packages, catalog: CATALOG, fuelPricePerL: 0.8, sellFraction: 0.5, }; } describe('slotsUsedFor', () => { it('counts loaded AND expired packages bound to the plane (both occupy a slot)', () => { const s = snap( [plane({ id: 'p1' })], [ pkg({ id: 'a', status: 'loaded', planeId: 'p1' }), pkg({ id: 'b', status: 'expired', planeId: 'p1' }), pkg({ id: 'c', status: 'open', planeId: null }), // not on any plane pkg({ id: 'd', status: 'delivered', planeId: 'p1' }), // terminal — slot long freed ], ); expect(slotsUsedFor(s, 'p1')).toBe(2); }); it('is 0 for a plane with no bound cargo', () => { expect(slotsUsedFor(snap([plane({ id: 'p1' })]), 'p1')).toBe(0); }); }); describe('loadEligiblePlanes (Load button eligibility)', () => { it('includes only grounded planes at the package origin with a free slot', () => { const s = snap([ plane({ id: 'at-origin' }), // eligible plane({ id: 'elsewhere', locationIcao: 'LEMD' }), // wrong airport plane({ id: 'in-flight', status: 'flying', destIcao: 'LEMD', destName: 'Madrid Barajas' }), // flying ]); expect(loadEligiblePlanes(s, pkg({ id: 'x' })).map((p) => p.id)).toEqual(['at-origin']); }); it('excludes a plane whose slots are full (capacity from the catalog)', () => { const packages = Array.from({ length: 6 }, (_, i) => pkg({ id: `fill-${i}`, status: 'loaded', planeId: 'full' }), ); const s = snap([plane({ id: 'full' })], packages); expect(loadEligiblePlanes(s, pkg({ id: 'x' }))).toEqual([]); }); it('a full plane with one slot freed by an expired package still counts as full', () => { const packages = [ ...Array.from({ length: 5 }, (_, i) => pkg({ id: `fill-${i}`, status: 'loaded', planeId: 'full' })), pkg({ id: 'exp', status: 'expired', planeId: 'full' }), // occupies its slot ]; const s = snap([plane({ id: 'full' })], packages); expect(loadEligiblePlanes(s, pkg({ id: 'x' }))).toEqual([]); }); it('returns several planes when several qualify (picker case)', () => { const s = snap([plane({ id: 'a' }), plane({ id: 'b' })]); expect(loadEligiblePlanes(s, pkg({ id: 'x' })).map((p) => p.id).sort()).toEqual(['a', 'b']); }); it('returns nothing when no plane is at the origin (Load stays disabled)', () => { const s = snap([plane({ id: 'far', locationIcao: 'LEMD' })]); expect(loadEligiblePlanes(s, pkg({ id: 'x' }))).toEqual([]); }); it('a plane with an unknown model has no capacity → never eligible', () => { const s = snap([plane({ id: 'mystery', model: 'mystery' })]); expect(loadEligiblePlanes(s, pkg({ id: 'x' }))).toEqual([]); }); });