Files
Battle-for-Tismo/tests/simple_ui_test.js
T
adrien d5305da449 Gave the world a simple economy: one pool per nation and a global market
The simple economy is now the default and the old simulation is frozen as the hard model, selectable per game. A nation keeps one resource pool available in every region, fed by public production works rather than private per-region stores, and construction and training begin at once, paced by the region's spare power instead of gathering materials over days.

Money is euro only: a per-inhabitant daily tax funds a treasury spent in a global market, where buy and sell lots trade against the world stock and move its price. The hard economy keeps its per-nation currencies, central banks and exchange rates.

Simple games skip the price-settling warmup and economic migration; the snapshot carries the economy model, the market and the pool figures the client draws. Added the matching test suites and the design briefs under docs/simple_economy.
2026-09-25 14:58:49 +02:00

199 lines
7.2 KiB
JavaScript

import { TestCase } from "./framework/test_case.js";
import { setupDom, teardownDom } from "./framework/dom.js";
import { CityModal, NationModal } from "../client/js/modals.js";
import { GOVERNMENTS, TECHNOLOGIES } from "../shared/data.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");
}
}