import { TestCase } from "./framework/test_case.js"; import { setupDom, teardownDom } from "./framework/dom.js"; import { CityModal, NationModal } from "../client/js/modals.js"; import { panelMethods } from "../client/js/game_screen/panels.js"; import { GOVERNMENTS, TECHNOLOGIES } from "../shared/data.js"; import { groupDigits } from "../shared/text_format.js"; import { formatResourceAmount } from "../shared/resources.js"; import { simpleState, smallState, cityOf } from "./framework/helpers.js"; import { setCurrency, setCurrencyBook, currencyFor, rateToNational, } from "../client/js/currency.js"; // The market figures a simple game's snapshot carries: the viewer's pool in the // summary's stock, and the world stock and price per storable good. const SIMPLE_MARKET = [ { id: "energy", price: 0.003, stock: null }, { id: "steel", price: 600, stock: 900_000 }, { id: "food", price: 300, stock: 1_000_000 }, { id: "luxury", price: 60, stock: 1_000_000 }, { id: "hightech", price: 800, stock: 1_000_000 }, ]; const SIMPLE_SUMMARY = { stock: { steel: 1_000_000, food: 500_000, luxury: 500_000, hightech: 20_000 }, consumption: { steel: 10, food: 20, luxury: 30, hightech: 1, energy: 50 }, demand: {}, prices: {}, }; export class SimpleUiTest extends TestCase { async test_the_simple_resources_tab_is_a_market() { const env = await setupDom(); try { const modal = new NationModal(); const trades = []; modal.onMarketTrade = (resource, side, lot) => trades.push([resource, side, lot]); modal.show( GOVERNMENTS, 0, TECHNOLOGIES, new Set(), 1.0e9, 1.0e9, 0, {}, null, [], null, null, { summary: SIMPLE_SUMMARY, market: SIMPLE_MARKET, economyModel: "simple", budget: 1.0e9 } ); env.$("#modal-nation .tab[data-tab=resources]").click(); // One row per storable good; energy is a flow and is not marketable. const rows = env.$("#resources-list .market-table tbody tr"); this.assertSize(rows, 4, "the four storable goods are listed"); this.assertTrue( env.$("#resources-sub").text().includes("Treasury"), "the treasury is shown with the market" ); // Steel leads the storable list; its first buy lot is the 100 button. rows.eq(0).find(".market-lot").eq(0).click(); this.assertSize(trades, 1, "a buy order is sent"); this.assertEqual(trades[0][0], "steel"); this.assertEqual(trades[0][1], "buy"); this.assertEqual(trades[0][2], 100); this.assertTrue( env.$("#resources-subtabs").hasClass("hidden"), "the hard sub-tabs are hidden in simple mode" ); modal.close(); } finally { teardownDom(env); } } async test_an_unaffordable_buy_and_an_uncovered_sell_are_disabled() { const env = await setupDom(); try { const modal = new NationModal(); const summary = { ...SIMPLE_SUMMARY, // Nothing in the pool to sell, and a treasury too small for any lot. stock: { steel: 0, food: 0, luxury: 0, hightech: 0 }, }; modal.show( GOVERNMENTS, 0, TECHNOLOGIES, new Set(), 1.0e9, 1.0e9, 0, {}, null, [], null, null, { summary, market: SIMPLE_MARKET, economyModel: "simple", budget: 0 } ); env.$("#modal-nation .tab[data-tab=resources]").click(); const $lot = env.$("#resources-list .market-lot"); this.assertTrue($lot.eq(0).prop("disabled"), "a buy the treasury cannot afford is disabled"); this.assertTrue($lot.eq(3).prop("disabled"), "a sell the pool cannot cover is disabled"); modal.close(); } finally { teardownDom(env); } } async test_the_simple_economy_tab_keeps_only_the_treasury_summary() { const env = await setupDom(); try { const modal = new NationModal(); modal.show( GOVERNMENTS, 0, TECHNOLOGIES, new Set(), 1.0e9, 1.0e9, 0, {}, null, [], null, null, null, { economyModel: "simple", budget: 1.0e9, takeTax: { income: 5000 } } ); env.$("#modal-nation .tab[data-tab=economy]").click(); this.assertTrue( env.$("#economy-subtabs").hasClass("hidden"), "no Taxes, central-bank or FX sub-tabs remain" ); const text = env.$("#economy-summary").text(); this.assertTrue(text.includes("Tax income"), "the single tax income line is shown"); this.assertTrue(text.includes("Treasury"), "the treasury is shown"); modal.close(); } finally { teardownDom(env); } } async test_the_simple_city_resources_show_the_pool_and_region_power() { const env = await setupDom(); try { const modal = new CityModal(); modal.show( { id: 1, name: "Lyon" }, { economyModel: "simple", resources: { stock: { steel: 123456, food: 500_000, luxury: 500_000, hightech: 20_000 }, consumption: { steel: 10, food: 20, luxury: 30, hightech: 1, energy: 50 }, demand: {}, energy: 7_000_000, }, } ); env.$("#modal-city .tab[data-tab=resources]").click(); const text = env.$("#city-resources-list").text(); this.assertTrue(text.includes("National pool"), "the pool is named"); this.assertTrue(text.includes("spare power"), "the region's spare power is named"); this.assertTrue( !env.$("#city-resources-list").text().includes("private sector holds"), "no per-region store table is drawn" ); modal.close(); } finally { teardownDom(env); } } async test_the_simple_build_line_names_the_spare_power() { const env = await setupDom(); try { const modal = new CityModal(); modal.economyModel = "simple"; modal.energyAvailable = 7_000_000; modal.beginTraining("Riflemen", 10, 0, "Training"); this.assertTrue( env.$("#city-train-label").text().includes("spare power"), "the progress line explains the build speed" ); modal.close(); } finally { teardownDom(env); } } test_a_single_currency_book_needs_no_conversion() { setCurrency({ code: "EUR", symbol: "€", name: "Euro" }); setCurrencyBook( [ { civ: 0, code: "EUR", symbol: "€", name: "Euro", value: 1 }, { civ: 1, code: "EUR", symbol: "€", name: "Euro", value: 1 }, ], [], 0 ); this.assertEqual(currencyFor(1), null, "one shared currency has no foreign money"); this.assertEqual(rateToNational(1), 1, "and no rate to apply"); } test_a_simple_city_ships_its_region_spare_power() { const state = simpleState(); state.applyStartingScenario(); const shared = state.serializeShared(); const city = cityOf(state, 0); const entry = shared.cityStats.find((stats) => stats.id === city.id); this.assertNotNull(entry, "the city has stats"); this.assertEqual( entry.resources.energy, state.regionAvailableEnergy(city), "the snapshot ships the region's spare power" ); this.assertGreater(entry.resources.energy, 0, "a simple region opens with spare power"); } test_a_hard_city_ships_no_region_energy() { const state = smallState(); state.applyStartingScenario(); const shared = state.serializeShared(); const city = cityOf(state, 0); const entry = shared.cityStats.find((stats) => stats.id === city.id); this.assertNotNull(entry, "the city has stats"); this.assertEqual(entry.resources.energy, undefined, "the hard city has no power field"); } // The top panel gains one chip per storable good in simple mode, each an icon // and the full grouped pool. An arrow reads production against consumption, // and the chip's tooltip spells both out. The hard economy keeps its // per-region stores and shows nothing here. async test_the_top_panel_shows_the_pool_in_simple_mode() { const env = await setupDom(); try { const screen = { economyModel: "simple", _updateTopResources: panelMethods._updateTopResources }; screen._updateTopResources({ resources: { stock: { steel: 1_000_000, food: 500_000, luxury: 500_000, hightech: 20_000 }, production: { steel: 1_500, food: 500, luxury: 0, hightech: 0 }, consumed: { steel: 1_000, food: 600, luxury: 10, hightech: 5 }, }, }); const chips = env.$("#top-resources .top-resource"); this.assertSize(chips, 4, "one chip per storable good"); this.assertEqual( chips.eq(0).find(".top-resource-value").text(), groupDigits(1_000_000), "the steel pool is grouped in full, not abbreviated" ); this.assertTrue( chips.eq(0).find("img").attr("src").includes("icon_steel"), "the chip leads with the resource's icon" ); // Steel makes 1 500 and uses 1 000, so the pool is filling; food makes // less than it eats, so it is draining. this.assertTrue(chips.eq(0).find(".stat-arrow").hasClass("up"), "a growing pool arrow up"); this.assertTrue(chips.eq(1).find(".stat-arrow").hasClass("down"), "a draining pool arrow down"); const tooltip = chips.eq(0).attr("data-tooltip"); this.assertTrue( tooltip.includes(`Produced ${formatResourceAmount("steel", 1_500)}/day`), "the tooltip names production" ); this.assertTrue( tooltip.includes(`Consumed ${formatResourceAmount("steel", 1_000)}/day`), "the tooltip names consumption" ); } finally { teardownDom(env); } } async test_the_top_panel_hides_the_pool_in_hard_mode() { const env = await setupDom(); try { const screen = { economyModel: "hard", _updateTopResources: panelMethods._updateTopResources }; screen._updateTopResources({ resources: { stock: { steel: 1 } } }); this.assertTrue(env.$("#top-resources").hasClass("hidden"), "hard mode shows no pool"); this.assertSize(env.$("#top-resources .top-resource"), 0, "no chips are left behind"); } finally { teardownDom(env); } } }