import { TestCase } from "./framework/test_case.js"; import { setupDom, teardownDom } from "./framework/dom.js"; import { LoadingLog } from "../client/js/loading.js"; import { GameScreen } from "../client/js/game_screen.js"; import { CIVILISATIONS } from "../shared/data.js"; import { smallState } from "./framework/helpers.js"; export class LoadingTest extends TestCase { async test_steps_are_logged_with_their_timings() { const env = await setupDom(); try { const log = new LoadingLog().bind(env.$("#loading-overlay")); log.begin(); this.assertFalse(env.$("#loading-overlay").hasClass("hidden"), "the overlay is shown"); this.assertSize(env.$("#loading-overlay .loading-line"), 1, "the boot line is first"); log.step("did a thing"); log.measure("did a bigger thing", () => 42); log.finish(); const lines = env.$("#loading-overlay .loading-line"); this.assertGreaterOrEqual(lines.length, 4, "every step is a line"); const text = lines.map((_index, element) => env.$(element).text()).get().join("\n"); this.assertTrue(text.includes("did a thing"), "the step label is shown"); this.assertTrue(text.includes("did a bigger thing"), "the measured label is shown"); this.assertTrue(text.includes("Total map setup"), "the total is shown"); this.assertTrue(/ms|s/.test(text), "each line carries a duration"); } finally { teardownDom(env); } } async test_measure_returns_the_wrapped_value_and_logs_nothing_when_idle() { const idle = new LoadingLog(); this.assertEqual(idle.measure("noop", () => 7), 7, "the value is returned when idle"); idle.step("ignored"); this.assertNull(idle.$root, "an unbound log stays inert"); const env = await setupDom(); try { const log = new LoadingLog().bind(env.$("#loading-overlay")); log.begin(); this.assertEqual(log.measure("value", () => 7), 7, "the value is returned while active"); this.assertGreater(env.$("#loading-overlay .loading-line").length, 1, "the measure logs a line"); } finally { teardownDom(env); } } // The GPU renderer uploads its textures asynchronously, so the overlay must // stay up until the map is actually ready instead of lifting on a blank map. async test_loading_waits_for_the_map_before_finishing() { const env = await setupDom(); try { const screen = new GameScreen(stubNetwork(), stubConfig()); screen.enter(); screen.beginLoading(); this.assertTrue(screen.map.isReady(), "the DOM map is ready at once"); this.assertNull(screen.map.loadingProgress(), "the DOM map reports no texture progress"); // Pretend the GPU renderer still has textures to upload. screen.map.isReady = () => false; screen.onState(smallState().snapshot(0)); this.assertTrue(screen._loading, "the screen stays in the loading state"); this.assertFalse( env.$("#loading-overlay").hasClass("done"), "the overlay has not started hiding" ); // Once the map is ready, the next frame finishes the log. screen.map.isReady = () => true; screen._pollLoading(); this.assertFalse(screen._loading, "the screen leaves the loading state"); this.assertTrue(env.$("#loading-overlay").hasClass("done"), "the overlay starts hiding"); } finally { teardownDom(env); } } async test_loading_finishes_immediately_when_the_map_is_ready() { const env = await setupDom(); try { const screen = new GameScreen(stubNetwork(), stubConfig()); screen.enter(); screen.beginLoading(); screen.onState(smallState().snapshot(0)); this.assertFalse(screen._loading, "the ready map finishes loading in one step"); this.assertTrue(env.$("#loading-overlay").hasClass("done"), "the overlay starts hiding"); } finally { teardownDom(env); } } async test_loading_never_finishes_before_the_world_arrives() { const env = await setupDom(); try { const screen = new GameScreen(stubNetwork(), stubConfig()); screen.enter(); screen.beginLoading(); // The map is ready, but the snapshot has not been mirrored yet. screen._pollLoading(); this.assertTrue(screen._loading, "the frame loop keeps waiting for the world"); this.assertFalse( env.$("#loading-overlay").hasClass("done"), "the overlay stays up until the world arrives" ); screen.onState(smallState().snapshot(0)); this.assertFalse(screen._loading, "the world arrives and the map is revealed"); } finally { teardownDom(env); } } } function stubNetwork() { return { sendOrder() {}, sendGameSetup() {} }; } function stubConfig() { return { playerName: "Carol", playerCivId: "france", civilisations: [CIVILISATIONS[0], CIVILISATIONS[1]], }; }