import { test } from "node:test"; import assert from "node:assert/strict"; import * as THREE from "three"; import { loadPanoramaTextures } from "../lib/panorama-textures"; function texture() { const value = new THREE.Texture(); let disposals = 0; value.addEventListener("dispose", () => disposals++); return { value, disposals: () => disposals }; } test("panorama textures retain face order and are owned separately by each viewer", async () => { const loader = { loadAsync: async () => new THREE.Texture() }; const first = await loadPanoramaTextures(["px", "nx"], loader); const second = await loadPanoramaTextures(["px", "nx"], loader); assert.equal(first.length, 2); assert.equal(first[0].colorSpace, THREE.SRGBColorSpace); assert.notEqual(first[0], second[0]); [...first, ...second].forEach((value) => value.dispose()); }); test("a failed panorama frees both loaded faces and faces that complete later", async () => { const early = texture(), late = texture(); let finish!: (value: THREE.Texture) => void; const loader = { loadAsync: (url: string) => url === "early" ? Promise.resolve(early.value) : url === "late" ? new Promise((resolve) => { finish = resolve; }) : Promise.reject(new Error("missing face")), }; await assert.rejects( loadPanoramaTextures(["early", "late", "missing"], loader), /missing face/, ); assert.equal(early.disposals(), 1); finish(late.value); await new Promise((resolve) => setImmediate(resolve)); assert.equal(late.disposals(), 1); }); test("stalled panoramas time out and release textures that arrive afterward", async () => { const late = texture(); let finish!: (value: THREE.Texture) => void; const loader = { loadAsync: () => new Promise((resolve) => { finish = resolve; }), }; await assert.rejects(loadPanoramaTextures(["late"], loader, 5), /timed out/); finish(late.value); await new Promise((resolve) => setImmediate(resolve)); assert.equal(late.disposals(), 1); });