import type { Dispatch, Ship } from "@deadhead/schema"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { loadArt, resetArtRegistration, type AssetLoader } from "../src/map/art/load"; import { artManifest } from "../src/map/art/manifest"; import { sceneVisuals } from "../src/map/art/visuals"; import { buildScene } from "../src/map/scene"; import { samplePlayerState, shipExtras } from "./render"; const NOW = Date.parse("2100-01-01T00:00:00Z"); const flight = (id: string, shipId: string, tutorial = false): Dispatch => ({ id, shipId, pilotId: "p", contract: null, originId: "earth", destinationId: "moon", tutorial, departureAt: NOW - 60_000, arrivalAt: NOW + 60_000, distanceMillionKm: 0.4, fuelCost: 0, fuelBurnedFu: 0, expectedPayout: 0, seed: 1, status: "in-flight", resolvedAt: null, }); const flying = (id: string, modelId: string, dispatchId: string): Ship => ({ id, modelId, name: id, status: "in-flight", locationId: null, dispatchId, pilotId: null, lastArrivalAt: 0, ...shipExtras(modelId), }); /** Ships of every model in flight, plus the tutorial loaner. */ const scene = () => buildScene( samplePlayerState({ ships: [ flying("s1", "courier", "d1"), flying("s2", "freighter", "d2"), flying("s3", "bulk-hauler", "d3"), ], activeDispatches: [ flight("d1", "s1"), flight("d2", "s2"), flight("d3", "s3"), flight("d4", "loaner", true), ], }), NOW, ); /** A fake PixiJS `Assets`: every alias loads as "tex:" unless it is in `failing`. */ function fakeLoader(failing: string[] = []) { const loader = { bundles: [] as Record[], addBundle: vi.fn((_name: string, assets: Record) => { loader.bundles.push(assets); }), load: vi.fn(async (alias: string) => { if (failing.includes(alias)) throw new Error("404"); return `tex:${alias}`; }), }; return loader satisfies AssetLoader; } describe("map art loading (CR-003)", () => { beforeEach(() => resetArtRegistration()); it("uses a texture for every location, the Sun, the background and every ship", async () => { const warn = vi.fn(); const textures = await loadArt(fakeLoader(), 1, warn); const visuals = sceneVisuals(scene(), textures); expect(warn).not.toHaveBeenCalled(); expect(visuals.background).toEqual({ kind: "sprite", texture: "tex:bg" }); expect(visuals.sun).toEqual({ kind: "sprite", texture: "tex:sun" }); for (const [id, visual] of visuals.locations) { expect(visual, id).toMatchObject({ kind: "sprite" }); } expect(Object.fromEntries(visuals.flights)).toEqual({ d1: { kind: "sprite", texture: "tex:ship-courier" }, d2: { kind: "sprite", texture: "tex:ship-freighter" }, d3: { kind: "sprite", texture: "tex:ship-bulk-hauler" }, d4: { kind: "sprite", texture: "tex:ship-company-courier" }, }); }); it("draws a placeholder for one failed asset, textures for the rest, and warns once", async () => { const warn = vi.fn(); const textures = await loadArt(fakeLoader(["body-mars"]), 1, warn); const visuals = sceneVisuals(scene(), textures); expect(visuals.locations.get("mars")).toEqual({ kind: "placeholder" }); for (const [id, visual] of visuals.locations) { if (id !== "mars") expect(visual, id).toMatchObject({ kind: "sprite" }); } expect(warn).toHaveBeenCalledTimes(1); expect(warn.mock.calls[0]![0]).toContain("body-mars"); }); it("draws Jupiter's badge as a placeholder when its art was not provided", async () => { const visuals = sceneVisuals(scene(), await loadArt(fakeLoader(), 1, vi.fn())); expect(artManifest.assets["badge-cloud-platform"]!.file).toBeNull(); expect(visuals.badges.get("jupiter-cloud-platform")).toEqual({ kind: "placeholder" }); }); it("requests the 2x variants at device pixel ratio 2, and 1x below it", async () => { const hiDpi = fakeLoader(); await loadArt(hiDpi, 2, vi.fn()); expect(hiDpi.addBundle).toHaveBeenCalledTimes(1); expect(hiDpi.bundles[0]!["body-earth"]).toBe("/assets/map-gen/body-earth@2x.png"); expect(hiDpi.bundles[0]!["ship-courier"]).toBe("/assets/map-gen/ship-courier@2x.png"); expect(hiDpi.bundles[0]!.bg).toBe("/assets/map-gen/bg.webp"); expect(hiDpi.load).toHaveBeenCalledWith("body-earth"); resetArtRegistration(); const normal = fakeLoader(); await loadArt(normal, 1.5, vi.fn()); expect(normal.bundles[0]!["body-earth"]).toBe("/assets/map-gen/body-earth@1x.png"); }); });