319 lines
12 KiB
JavaScript
319 lines
12 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 { panelMethods } from "../client/js/game_screen/panels.js";
|
||
import { GOVERNMENTS, TECHNOLOGIES, PROTO_UNITS, BUILDINGS } from "../shared/data.js";
|
||
import { groupDigits } from "../shared/text_format.js";
|
||
import { constructionResourceCost, 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);
|
||
}
|
||
}
|
||
|
||
// The unit and building cards show what a work spends in materials, not only
|
||
// its money quote: the steel (and high-tech) bill, drawn with the commodity
|
||
// icons on the action button's meta line.
|
||
async test_unit_and_building_cards_show_their_resource_cost() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new CityModal();
|
||
const unit = { ...PROTO_UNITS[0], serverIndex: 0 };
|
||
modal.show(
|
||
{ id: 1, name: "Lyon" },
|
||
{
|
||
economyModel: "simple",
|
||
resources: { energy: 3_500_000_000 },
|
||
gdp: 1.0e9,
|
||
trainable: [unit],
|
||
unitProtos: PROTO_UNITS,
|
||
buildings: BUILDINGS,
|
||
levels: {},
|
||
}
|
||
);
|
||
const unitBill = constructionResourceCost(PROTO_UNITS[0], 0, PROTO_UNITS[0].cost);
|
||
const $unitMeta = env.$("#city-train-list .bt-meta").first();
|
||
this.assertTrue(
|
||
$unitMeta.find("img[src$='icon_steel.svg']").length === 1,
|
||
"the unit's steel cost is on the card"
|
||
);
|
||
this.assertTrue(
|
||
$unitMeta.text().includes(formatResourceAmount("steel", unitBill.steel)),
|
||
`the unit's steel bill is quoted: ${$unitMeta.text()}`
|
||
);
|
||
this.assertTrue(
|
||
$unitMeta.text().includes("45"),
|
||
`the unit's simple build clock is quoted: ${$unitMeta.text()}`
|
||
);
|
||
|
||
const index = BUILDINGS.findIndex((b) => b.id === "barracks");
|
||
const $buildMeta = env.$("#city-buildings-list .building-tile").eq(index).find(".bt-meta");
|
||
this.assertTrue(
|
||
$buildMeta.find("img[src$='icon_steel.svg']").length === 1,
|
||
"the building's steel cost is on the card"
|
||
);
|
||
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,
|
||
_showStatTooltip: panelMethods._showStatTooltip,
|
||
_statTooltipContent: panelMethods._statTooltipContent,
|
||
_resourceTooltip: panelMethods._resourceTooltip,
|
||
$statTooltip: env.$("#stat-tooltip"),
|
||
};
|
||
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");
|
||
// The chip's tooltip is a two-line bill: production with a plus, then
|
||
// consumption with a minus, each with its figure and unit.
|
||
screen._showStatTooltip(chips.eq(0));
|
||
const tip = env.$("#stat-tooltip .resource-tip");
|
||
const signs = tip.find(".rt-sign");
|
||
const figures = tip.find(".rt-figure");
|
||
const tails = tip.find(".rt-tail");
|
||
this.assertSize(signs, 2, "production and consumption");
|
||
this.assertTrue(signs.eq(0).hasClass("produce"), "production leads");
|
||
this.assertEqual(signs.eq(0).text(), "+");
|
||
this.assertEqual(figures.eq(0).text(), groupDigits(1_500));
|
||
this.assertEqual(tails.eq(0).text(), "t/day");
|
||
this.assertTrue(signs.eq(1).hasClass("consume"), "consumption follows");
|
||
this.assertEqual(signs.eq(1).text(), "−");
|
||
this.assertEqual(figures.eq(1).text(), groupDigits(1_000));
|
||
this.assertEqual(tails.eq(1).text(), "t/day");
|
||
} 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);
|
||
}
|
||
}
|
||
}
|