132 lines
4.5 KiB
TypeScript
132 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,
|
|
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:<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");
|
|
});
|
|
});
|