v1.6.0: leaderboard pane from the top-bar additional-panels button (ISS-115)
CI / build + unit + e2e (pull_request) Successful in 14m2s

This commit is contained in:
2026-09-26 19:38:25 +00:00
parent d87d848ca2
commit b04e504263
16 changed files with 188 additions and 4 deletions
+55 -2
View File
@@ -1,5 +1,12 @@
import { describe, expect, it } from 'vitest';
import { SSE_PACKAGE_STATUSES, sseFrame, type PackageState, type StateSnapshot } from './state';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import * as schema from '@/lib/db/schema';
import { getGameDb } from '@/lib/db/client';
import { PLANE_TYPES } from './catalog';
import { STARTING_CASH, STARTING_REPUTATION } from './seed';
import { getStateSnapshot, SSE_PACKAGE_STATUSES, sseFrame, type PackageState, type StateSnapshot } from './state';
// ISS-133 (v0.59.0) — the SSE projection keeps only the package statuses any component renders.
@@ -30,6 +37,7 @@ const snapshot = (packages: PackageState[]): StateSnapshot => ({
fuelPricePerL: 0.8,
sellFraction: 0.5,
planes: [],
players: [], // v1.6.0 (ISS-115) — leaderboard rows; empty in these sseFrame fixtures
packages,
});
@@ -60,3 +68,48 @@ describe('sseFrame', () => {
expect([...SSE_PACKAGE_STATUSES].sort()).toEqual(['expired', 'loaded', 'open']);
});
});
// v1.6.0 (ISS-115) — the snapshot's players array: one row per player with cash/reputation and
// how many planes they own. Runs against a throwaway seeded DB via the app singleton, exactly
// like the /api/state route tests (fresh module registry per file ⇒ fresh singleton).
describe('getStateSnapshot players', () => {
let dir: string;
beforeAll(async () => {
dir = await fs.mkdtemp(path.join(os.tmpdir(), 'flight-snapshot-players-'));
process.env.FLIGHT_DB_PATH = path.join(dir, 'players.db');
await getGameDb(); // seeds the local player + starter plane
});
afterAll(async () => {
delete process.env.FLIGHT_DB_PATH;
await fs.rm(dir, { recursive: true, force: true });
});
it('single-player regression guard — one row for the seeded local account', async () => {
const snap = await getStateSnapshot('local');
expect(snap.players).toEqual([
{ id: 'local', username: 'kamilkrzywda', cash: STARTING_CASH, reputation: STARTING_REPUTATION, planeCount: 1 },
]);
});
it('two players — cash/reputation/planeCount per owner (v1.6.0, ISS-115)', async () => {
const inst = await getGameDb();
// A second account with a distinct economy and two planes of its own — rows minted like /api/register does.
await inst.db.insert(schema.players).values({ id: 'b', provider: 'local', username: 'player-b', cash: 5_000, reputation: 3 });
const type = PLANE_TYPES.atr72;
for (const id of ['plane-b-1', 'plane-b-2']) {
await inst.db.insert(schema.planes).values({
id, ownerId: 'b', model: 'atr72', status: 'available',
fuel: type.maxFuel, maxFuel: type.maxFuel, locationIcao: 'EDDF', lat: 50.03, lon: 8.56,
});
}
const snap = await getStateSnapshot('b'); // any session sees the whole world's players
expect(snap.players).toHaveLength(2);
const local = snap.players.find((p) => p.id === 'local')!;
expect(local).toEqual({ id: 'local', username: 'kamilkrzywda', cash: STARTING_CASH, reputation: STARTING_REPUTATION, planeCount: 1 }); // starter still counted once…
const b = snap.players.find((p) => p.id === 'b')!;
expect(b).toEqual({ id: 'b', username: 'player-b', cash: 5_000, reputation: 3, planeCount: 2 }); // …and both of B's planes count for B.
});
});