deadhead-space-freight-tycoon/apps/web/test/map-art-loading.test.ts

131 lines
4.5 KiB
TypeScript

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,
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:<alias>" unless it is in `failing`. */
function fakeLoader(failing: string[] = []) {
const loader = {
bundles: [] as Record<string, string>[],
addBundle: vi.fn((_name: string, assets: Record<string, string>) => {
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<string>;
}
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");
});
});