Files
Battle-for-Tismo/tests/simple_ui_test.js

319 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}
}