169 lines
6.3 KiB
TypeScript
169 lines
6.3 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
||
import type { PackageState, PlaneState, StateSnapshot } from './game/state';
|
||
import { diffSnapshots } from './deriveEvents';
|
||
|
||
// ISS-008 (v0.17.0): snapshot-diffing event derivation — pure core of the toast/notification
|
||
// feature, unit-tested in node like advanceSimClock/fetchState.
|
||
|
||
const CATALOG = [
|
||
{ model: 'atr72', label: 'ATR 72-600', price: 200_000, maxFuel: 6300, burnPerKm: 1.75, emptyWeightKg: 9300, speedKmh: 510, maxPayloadKg: 4800 },
|
||
];
|
||
|
||
const basePlane = (id: string): PlaneState => ({
|
||
id,
|
||
model: 'atr72',
|
||
status: 'available',
|
||
fuel: 6300,
|
||
maxFuel: 6300,
|
||
locationIcao: 'EDDF',
|
||
locationName: 'Frankfurt',
|
||
destIcao: null,
|
||
destName: null,
|
||
departureSimTime: null,
|
||
lat: 50.03,
|
||
lon: 8.56,
|
||
health: 100, // v0.39.0 (ISS-069) — full-health fixture default
|
||
repairUntilSimTime: null,
|
||
queue: [],
|
||
});
|
||
|
||
const basePackage = (id: string): PackageState => ({
|
||
id,
|
||
originIcao: 'EDDF',
|
||
originName: 'Frankfurt',
|
||
destIcao: 'LEMD',
|
||
destName: 'Madrid–Barajas',
|
||
reward: 1234.5,
|
||
cargoType: 'mail',
|
||
cargoCount: 2,
|
||
weightKg: 600,
|
||
special: null, // v0.37.0 (ISS-073) — no special status by default in fixtures
|
||
distanceKm: 1839, // v0.48.0 (ISS-095) — EDDF→LEMD great-circle distance
|
||
deadlineSimTime: 0,
|
||
status: 'open',
|
||
planeId: null,
|
||
createdSimTime: 0,
|
||
});
|
||
|
||
const snapshot = (over: Partial<StateSnapshot> = {}): StateSnapshot => ({
|
||
account: { cash: 20_000, reputation: 10 },
|
||
simState: { clockMs: 0, timeScale: 1, paused: false },
|
||
planes: [basePlane('plane-starter')],
|
||
packages: [],
|
||
catalog: CATALOG,
|
||
fuelPricePerL: 0.8,
|
||
sellFraction: 0.5,
|
||
...over,
|
||
});
|
||
|
||
describe('diffSnapshots', () => {
|
||
it('returns [] for identical snapshots (only clock/cash drift)', () => {
|
||
const prev = snapshot();
|
||
const next = snapshot({ account: { cash: 21_000, reputation: 11 }, simState: { clockMs: 60_000, timeScale: 1, paused: false } });
|
||
expect(diffSnapshots(prev, next)).toEqual([]);
|
||
});
|
||
|
||
it('returns [] for the first snapshot (no baseline — no "new package" spam on page load)', () => {
|
||
const next = snapshot({ packages: [basePackage('pkg-1'), basePackage('pkg-2')] });
|
||
expect(diffSnapshots(null, next)).toEqual([]);
|
||
});
|
||
|
||
it('emits a landing event when a plane goes flying → available (new locationName is the arrival)', () => {
|
||
const prev = snapshot({
|
||
planes: [
|
||
{
|
||
...basePlane('plane-starter'),
|
||
status: 'flying',
|
||
destIcao: 'LEMD',
|
||
destName: 'Madrid–Barajas',
|
||
departureSimTime: 0,
|
||
},
|
||
],
|
||
});
|
||
const next = snapshot({
|
||
planes: [
|
||
{ ...basePlane('plane-starter'), locationIcao: 'LEMD', locationName: 'Madrid–Barajas' },
|
||
],
|
||
});
|
||
expect(diffSnapshots(prev, next)).toEqual([
|
||
{ kind: 'info', message: 'ATR 72-600 landed at Madrid–Barajas' },
|
||
]);
|
||
});
|
||
|
||
it('emits a delivery event with the reward when a loaded package is delivered on arrival', () => {
|
||
const prev = snapshot({ packages: [{ ...basePackage('pkg-1'), status: 'loaded', planeId: 'plane-starter' }] });
|
||
const next = snapshot({ packages: [{ ...basePackage('pkg-1'), status: 'delivered', planeId: 'plane-starter' }] });
|
||
expect(diffSnapshots(prev, next)).toEqual([
|
||
{ kind: 'info', message: 'Package delivered: +€1,234.50' },
|
||
]);
|
||
});
|
||
|
||
it('emits an expiry event for open → expired and loaded → expired', () => {
|
||
const fromOpen = diffSnapshots(
|
||
snapshot({ packages: [basePackage('pkg-1')] }),
|
||
snapshot({ packages: [{ ...basePackage('pkg-1'), status: 'expired' }] }),
|
||
);
|
||
expect(fromOpen).toEqual([{ kind: 'info', message: 'Package expired: Frankfurt → Madrid–Barajas' }]);
|
||
|
||
const fromLoaded = diffSnapshots(
|
||
snapshot({ packages: [{ ...basePackage('pkg-2'), status: 'loaded', planeId: 'plane-starter' }] }),
|
||
snapshot({ packages: [{ ...basePackage('pkg-2'), status: 'expired', planeId: 'plane-starter' }] }),
|
||
);
|
||
expect(fromLoaded).toEqual([{ kind: 'info', message: 'Package expired: Frankfurt → Madrid–Barajas' }]);
|
||
});
|
||
|
||
it('emits a new-package event for ids only in the next snapshot', () => {
|
||
const prev = snapshot({ packages: [basePackage('pkg-1')] });
|
||
const next = snapshot({ packages: [basePackage('pkg-1'), basePackage('pkg-2')] });
|
||
expect(diffSnapshots(prev, next)).toEqual([
|
||
{ kind: 'info', message: 'New package available: Frankfurt → Madrid–Barajas' },
|
||
]);
|
||
});
|
||
|
||
it('emits bought/sold events when the plane id set changes (with the model name)', () => {
|
||
const bought = diffSnapshots(
|
||
snapshot(),
|
||
snapshot({ planes: [basePlane('plane-starter'), basePlane('plane-2')] }),
|
||
);
|
||
expect(bought).toEqual([{ kind: 'info', message: 'Bought ATR 72-600' }]);
|
||
|
||
const sold = diffSnapshots(
|
||
snapshot({ planes: [basePlane('plane-starter'), basePlane('plane-2')] }),
|
||
snapshot(),
|
||
);
|
||
expect(sold).toEqual([{ kind: 'info', message: 'Sold ATR 72-600' }]);
|
||
});
|
||
|
||
it('reports every transition in one diff (planes first, then packages)', () => {
|
||
const prev = snapshot({
|
||
planes: [
|
||
{ ...basePlane('plane-starter'), status: 'flying', destIcao: 'LEMD', destName: 'Madrid–Barajas' },
|
||
basePlane('plane-2'),
|
||
],
|
||
packages: [{ ...basePackage('pkg-1'), status: 'loaded', planeId: 'plane-starter' }],
|
||
});
|
||
const next = snapshot({
|
||
planes: [
|
||
{ ...basePlane('plane-starter'), locationIcao: 'LEMD', locationName: 'Madrid–Barajas' },
|
||
basePlane('plane-3'),
|
||
],
|
||
packages: [{ ...basePackage('pkg-1'), status: 'delivered', planeId: 'plane-starter' }],
|
||
});
|
||
expect(diffSnapshots(prev, next)).toEqual([
|
||
{ kind: 'info', message: 'ATR 72-600 landed at Madrid–Barajas' },
|
||
{ kind: 'info', message: 'Bought ATR 72-600' },
|
||
{ kind: 'info', message: 'Sold ATR 72-600' },
|
||
{ kind: 'info', message: 'Package delivered: +€1,234.50' },
|
||
]);
|
||
});
|
||
|
||
it('ignores transitions that are not events (open → loaded, available → flying)', () => {
|
||
const prev = snapshot({ packages: [basePackage('pkg-1')] });
|
||
const next = snapshot({
|
||
planes: [{ ...basePlane('plane-starter'), status: 'flying', destIcao: 'LEMD', destName: 'Madrid–Barajas' }],
|
||
packages: [{ ...basePackage('pkg-1'), status: 'loaded', planeId: 'plane-starter' }],
|
||
});
|
||
expect(diffSnapshots(prev, next)).toEqual([]);
|
||
});
|
||
});
|