Every road, railway and tile work now rises as an economic construction site instead of a queue entry. A site gathers its materials over days from reachable regions, refusing any supplier quoting above the price it was budgeted at (a 1% tolerance); a day that buys nothing stalls the site, raising a red mark and letting the nation raise the budget to current prices or cancel it. The upfront budget covers the build cost plus the market value of its steel and high-tech, energy stays a grid draw, and any money left over stays with the finished work as a maintenance reserve. That reserve now pays the work's own daily steel and high-tech wear before the treasury is billed, so a well funded work spares the state until it runs dry; the spent money is paid into the nation's regions. Building upgrades run through the same gather-build-maintain machinery, sharing the site's kind flag and target level. The legacy tile-work queue and the market building are gone. Bundles the in-progress chart sizing for the central bank panel.
1204 lines
49 KiB
JavaScript
1204 lines
49 KiB
JavaScript
import { TestCase } from "./framework/test_case.js";
|
||
import { setupDom, teardownDom } from "./framework/dom.js";
|
||
import { CityModal, NationModal, ConfirmModal, NewsModal } from "../client/js/modals.js";
|
||
import { buildingEffectText, describeNews } from "../client/js/modals/format.js";
|
||
import { BUILDINGS, GOVERNMENTS, TECHNOLOGIES, PROTO_UNITS, technologyCost } from "../shared/data.js";
|
||
import { groupDigits } from "../shared/text_format.js";
|
||
import { setCurrency, setCurrencyBook } from "../client/js/currency.js";
|
||
|
||
export class CityModalTest extends TestCase {
|
||
test_utility_buildings_describe_their_mechanic() {
|
||
const barracks = BUILDINGS.find((b) => b.id === "barracks");
|
||
this.assertTrue(
|
||
buildingEffectText(barracks, 2).includes("3 parallel training lines"),
|
||
"the barracks explains its parallel lines"
|
||
);
|
||
const shelters = BUILDINGS.find((b) => b.id === "shelters");
|
||
this.assertTrue(buildingEffectText(shelters, 2).includes("20%"), "the shelters show their protection");
|
||
const airport = BUILDINGS.find((b) => b.id === "airport");
|
||
this.assertTrue(buildingEffectText(airport, 1).includes("production"), "stats keep their effect text");
|
||
}
|
||
showTrain(modal, queue = []) {
|
||
modal.show(
|
||
{ id: 1, name: "Lyon" },
|
||
{
|
||
budget: 1.0e9,
|
||
gdp: 1.0e9,
|
||
trainable: [{ ...PROTO_UNITS[0], serverIndex: 0 }, { ...PROTO_UNITS[2], serverIndex: 2 }],
|
||
buildings: BUILDINGS,
|
||
levels: {},
|
||
queue,
|
||
}
|
||
);
|
||
}
|
||
|
||
async test_train_tab_lists_trainable_units_and_requests_one() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new CityModal();
|
||
let requested = null;
|
||
modal.onTrain = (cityId, protoIndex) => {
|
||
requested = [cityId, protoIndex];
|
||
};
|
||
this.showTrain(modal);
|
||
this.assertEqual(env.$("#city-title").text(), "Lyon — City");
|
||
this.assertSize(env.$("#city-train-list button"), 2, "infantry and artillery can be trained");
|
||
this.assertTrue(env.$("#city-train-list button").first().text().includes("Modern infantry"));
|
||
env.$("#city-train-list button").first().click();
|
||
this.assertEqual(requested, [1, 0]);
|
||
modal.close();
|
||
this.assertTrue(env.$("#modal-city").hasClass("hidden"));
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_training_progress_states() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new CityModal();
|
||
this.showTrain(modal);
|
||
modal.beginTraining("Modern infantry", 10, 2);
|
||
this.assertFalse(env.$("#city-train-status").hasClass("hidden"));
|
||
modal.setTrainingProgress("Modern infantry", 4, 10);
|
||
this.assertTrue(env.$("#city-train-label").text().includes("6 h left"));
|
||
modal.setTrainingProgress("Market", 1, 5, "Building");
|
||
this.assertTrue(env.$("#city-train-label").text().includes("Building Market"));
|
||
modal.endTraining();
|
||
this.assertEqual(env.$("#city-train-label").text(), "Training complete");
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_queue_labels_building_entries_alongside_units() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new CityModal();
|
||
const queue = [
|
||
{ kind: "building", protoIndex: 0, elapsedHours: 0, totalHours: 5 },
|
||
{ protoIndex: 0, elapsedHours: 0, totalHours: 10 },
|
||
];
|
||
this.showTrain(modal, queue);
|
||
const rows = env.$("#city-train-queue .queue-row");
|
||
this.assertSize(rows, 2, "a building and a unit never merge");
|
||
this.assertTrue(
|
||
rows.eq(0).text().includes(BUILDINGS[0].name),
|
||
"the building entry is labelled with its name"
|
||
);
|
||
this.assertTrue(rows.eq(1).text().includes("Modern infantry"), "the unit keeps its name");
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_queue_merges_only_contiguous_identical_units() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new CityModal();
|
||
const cancelled = [];
|
||
modal.onCancel = (cityId, index) => cancelled.push([cityId, index]);
|
||
const queue = [
|
||
{ protoIndex: 0, elapsedHours: 0, totalHours: 10 },
|
||
{ protoIndex: 0, elapsedHours: 0, totalHours: 10 },
|
||
{ protoIndex: 2, elapsedHours: 0, totalHours: 30 },
|
||
{ protoIndex: 0, elapsedHours: 0, totalHours: 10 },
|
||
];
|
||
this.showTrain(modal, queue);
|
||
const rows = env.$("#city-train-queue .queue-row");
|
||
this.assertSize(rows, 3, "only adjacent runs share a row");
|
||
this.assertTrue(rows.eq(0).text().includes("Modern infantry ×2"), "the run is labelled with its size");
|
||
this.assertTrue(rows.eq(0).text().includes("Next in"), "a run shows when its first unit lands");
|
||
this.assertTrue(rows.eq(0).text().includes("all ready in"), "and when the run completes");
|
||
this.assertTrue(rows.eq(1).text().includes("Artillery"), "a different type starts a row");
|
||
this.assertTrue(rows.eq(2).text().includes("Modern infantry"), "a later run of the same type is separate");
|
||
this.assertFalse(rows.eq(2).text().includes("×"), "the lone trailing unit is not a run");
|
||
rows.eq(0).find("button").click();
|
||
this.assertEqual(cancelled, [[1, 1]], "cancelling a run removes its last unit");
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_buildings_tab_prices_rows_and_requests_build_and_demolish() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new CityModal();
|
||
let built = null;
|
||
let demolished = null;
|
||
modal.onBuild = (cityId, index) => {
|
||
built = [cityId, index];
|
||
};
|
||
modal.onDemolish = (cityId, index) => {
|
||
demolished = [cityId, index];
|
||
};
|
||
modal.show({ id: 3, name: "Lyon" }, { budget: 1.0e15, buildings: BUILDINGS, levels: {} });
|
||
this.assertSize(env.$("#city-buildings-list .row-card"), BUILDINGS.length);
|
||
const first = env.$("#city-buildings-list .row-card").first();
|
||
this.assertTrue(first.find(".name").text().includes("Level 0"));
|
||
this.assertFalse(first.find(".actions button").first().prop("disabled"));
|
||
first.find(".actions button").first().click();
|
||
this.assertEqual(built, [3, 0]);
|
||
|
||
modal.refreshBuildings(1.0e15, { 0: 1 });
|
||
this.assertTrue(first.find(".name").text().includes("Level 1"));
|
||
first.find(".actions button").eq(1).click();
|
||
this.assertEqual(demolished, [3, 0]);
|
||
modal.close();
|
||
this.assertTrue(env.$("#modal-city").hasClass("hidden"));
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_queued_levels_price_the_next_upgrade_higher() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new CityModal();
|
||
modal.show({ id: 1, name: "X" }, { budget: 1.0e15, buildings: BUILDINGS, levels: {}, gdp: 1.0e9 });
|
||
const first = env.$("#city-buildings-list .row-card").first();
|
||
const plain = first.find(".cost").text();
|
||
modal.refreshBuildings(1.0e15, {}, 1.0e9, { 0: 1 });
|
||
const queued = first.find(".cost").text();
|
||
this.assertTrue(first.find(".name").text().includes("queued"), "the pending level is shown");
|
||
this.assertNotEqual(queued, plain, "the next order is priced off the queued level");
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_upgrade_stays_available_on_an_empty_treasury() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new CityModal();
|
||
modal.show({ id: 1, name: "X" }, { budget: 0, buildings: BUILDINGS, levels: {} });
|
||
const upgrade = env.$("#city-buildings-list .row-card").first().find(".actions button").first();
|
||
// The government can always pay, running an overdraft, so cost never
|
||
// disables the button; only an unmet requirement does.
|
||
this.assertFalse(upgrade.prop("disabled"));
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_budget_tab_renders_region_income_and_upkeep() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new CityModal();
|
||
const breakdown = {
|
||
income: {
|
||
rate: 0.001,
|
||
base: 900,
|
||
modifier: 0.15,
|
||
total: 1035,
|
||
sources: [
|
||
{ label: "Bank", kind: "building", effect: 0.05, amount: 45, cityId: 1, cityName: "Paris" },
|
||
],
|
||
},
|
||
buildingUpkeep: {
|
||
total: 400,
|
||
sources: [{ label: "Bank", kind: "building", amount: 400 }],
|
||
groups: [
|
||
{
|
||
index: 1,
|
||
label: "Bank",
|
||
amount: 400,
|
||
cities: [{ cityId: 1, cityName: "Paris", level: 2, amount: 400 }],
|
||
},
|
||
],
|
||
},
|
||
unitUpkeep: { total: 0, sources: [], groups: [] },
|
||
net: 635,
|
||
};
|
||
modal.show({ id: 1, name: "Paris" }, { budget: 1.0e15, buildings: BUILDINGS, levels: {} });
|
||
modal.renderBudget(breakdown);
|
||
this.assertTrue(env.$("#city-budget-sub").text().includes("+¤635/h"), "the region net is shown");
|
||
const text = env.$("#city-budget-list").text();
|
||
this.assertTrue(text.includes("+¤1 035/h"), "region tax income is green");
|
||
this.assertTrue(text.includes("Bank in Paris (building) +5.0%"), "a building bonus is listed");
|
||
this.assertTrue(text.includes("−¤400/h"), "the building upkeep is listed");
|
||
this.assertTrue(text.includes("TOTAL"), "a total row is present");
|
||
this.assertTrue(text.includes("+¤635/h"), "the total carries the region net");
|
||
this.assertFalse(text.includes("No units"), "unit upkeep is national, not regional");
|
||
|
||
env.$("#city-budget-list tr.budget-group .budget-name").click();
|
||
this.assertTrue(
|
||
env.$("#city-budget-list").text().includes("Bank in Paris — level 2"),
|
||
"expanding a building type shows its level"
|
||
);
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_the_people_tab_warns_of_famine() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new CityModal();
|
||
modal.show({ id: 1, name: "Paris" }, {
|
||
budget: 1.0e15,
|
||
buildings: BUILDINGS,
|
||
levels: {},
|
||
ethnicMakeup: [],
|
||
famine: true,
|
||
});
|
||
this.assertTrue(
|
||
env.$("#city-people-list").text().includes("Famine"),
|
||
"the famine warning is shown"
|
||
);
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_tabs_switch_the_visible_page() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new CityModal();
|
||
this.showTrain(modal);
|
||
this.assertTrue(env.$("#city-tab-train").hasClass("active"));
|
||
env.$("#modal-city .tab[data-tab=buildings]").click();
|
||
this.assertTrue(env.$("#city-tab-buildings").hasClass("active"));
|
||
this.assertFalse(env.$("#city-tab-train").hasClass("active"));
|
||
env.$("#modal-city .tab[data-tab=budget]").click();
|
||
this.assertTrue(env.$("#city-tab-budget").hasClass("active"));
|
||
env.$("#modal-city .tab[data-tab=train]").click();
|
||
this.assertTrue(env.$("#city-tab-train").hasClass("active"));
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_economy_tab_shows_the_regions_cash_basket() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new CityModal();
|
||
modal.show(
|
||
{ id: 1, name: "Lyon" },
|
||
{
|
||
budget: 1.0e9,
|
||
gdp: 1.0e9,
|
||
cash: { FRF: 4000, GBP: 250 },
|
||
cashValue: 4250,
|
||
debt: 250,
|
||
currencies: [
|
||
{ code: "FRF", symbol: "₣", name: "French Franc" },
|
||
{ code: "GBP", symbol: "£", name: "British Pound" },
|
||
],
|
||
}
|
||
);
|
||
env.$("#modal-city .tab[data-tab=economy]").click();
|
||
this.assertTrue(env.$("#city-tab-economy").hasClass("active"));
|
||
const text = env.$("#city-economy-list").text();
|
||
this.assertTrue(text.includes("French Franc"), "the national currency is listed");
|
||
this.assertTrue(text.includes("British Pound"), "a foreign holding is listed");
|
||
this.assertTrue(env.$("#city-economy-sub").text().includes("4 250"), "the total value shows");
|
||
this.assertTrue(env.$("#city-economy-sub").text().includes("Debt"), "the central-bank debt shows");
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_resources_tab_shows_reserves_value_and_consumption() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new CityModal();
|
||
modal.show(
|
||
{ id: 1, name: "Lyon" },
|
||
{
|
||
budget: 1.0e9,
|
||
gdp: 1.0e9,
|
||
resources: {
|
||
stock: { food: 1000, steel: 2000, luxury: 0, hightech: 50 },
|
||
consumption: { food: 100, steel: 200, luxury: 10, hightech: 2, energy: 2500 },
|
||
value: 123456,
|
||
},
|
||
}
|
||
);
|
||
env.$("#modal-city .tab[data-tab=resources]").click();
|
||
this.assertTrue(env.$("#city-tab-resources").hasClass("active"));
|
||
this.assertTrue(
|
||
env.$("#city-resources-sub").text().includes("123 456"),
|
||
"the reserve value shows"
|
||
);
|
||
const text = env.$("#city-resources-list").text();
|
||
this.assertTrue(text.includes("Food"), "food is listed");
|
||
this.assertTrue(text.includes("Steel"), "steel is listed");
|
||
this.assertTrue(text.includes("Energy"), "energy is listed");
|
||
this.assertTrue(text.includes("2.5 MWh"), "the daily power draw is shown");
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_people_tab_reports_resource_shortages() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new CityModal();
|
||
modal.show(
|
||
{ id: 1, name: "Lyon" },
|
||
{
|
||
budget: 1.0e9,
|
||
gdp: 1.0e9,
|
||
ethnicMakeup: [],
|
||
approval: 0.5,
|
||
shortage: { steel: { fraction: 0.4, amount: 1200 } },
|
||
}
|
||
);
|
||
env.$("#modal-city .tab[data-tab=people]").click();
|
||
this.assertTrue(env.$("#city-tab-people").hasClass("active"));
|
||
const text = env.$("#city-people-list").text();
|
||
this.assertTrue(text.includes("Resource shortages"), "the shortage section shows");
|
||
this.assertTrue(text.includes("Steel"), "the short resource is named");
|
||
this.assertTrue(text.includes("1200"), "the amount it is short by is shown");
|
||
this.assertTrue(text.includes("40%"), "the share of need is shown");
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
}
|
||
|
||
export class NationModalTest extends TestCase {
|
||
async test_government_and_technology_tabs() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new NationModal();
|
||
let government = null;
|
||
let technology = null;
|
||
modal.onGovernment = (index) => {
|
||
government = index;
|
||
};
|
||
modal.onFocusTechnology = (index) => {
|
||
technology = index;
|
||
};
|
||
modal.show(GOVERNMENTS, 0, TECHNOLOGIES, new Set(), 1.0e9, 1.0e15, 1.0e6);
|
||
this.assertSize(env.$("#government-list .row-card"), GOVERNMENTS.length);
|
||
this.assertSize(env.$("#technology-list .row-card"), TECHNOLOGIES.length);
|
||
|
||
const governmentRows = env.$("#government-list .row-card");
|
||
this.assertTrue(governmentRows.eq(0).find(".actions button").prop("disabled"), "current government is locked");
|
||
governmentRows.eq(1).find(".actions button").click();
|
||
this.assertEqual(government, 1);
|
||
|
||
// A prerequisite-gated technology starts locked.
|
||
const method = TECHNOLOGIES.findIndex((t) => t.id === "scientific_method");
|
||
const automation = TECHNOLOGIES.findIndex((t) => t.id === "industrial_automation");
|
||
const techRows = env.$("#technology-list .row-card");
|
||
this.assertEqual(techRows.eq(automation).find(".actions button").text(), "Locked");
|
||
|
||
// Researching its prerequisite unlocks it.
|
||
modal.refresh(0, new Set([method]), 1.0e9, 1.0e15, 1.0e6);
|
||
this.assertTrue(techRows.eq(method).find(".actions button").text().startsWith("Researched"));
|
||
this.assertTrue(techRows.eq(automation).find(".actions button").text().startsWith("Focus"));
|
||
techRows.eq(automation).find(".actions button").click();
|
||
this.assertEqual(technology, automation);
|
||
|
||
env.$("#modal-nation .tab[data-tab=technology]").click();
|
||
this.assertTrue(env.$("#tab-technology").hasClass("active"));
|
||
env.$("#modal-nation .tab[data-tab=government]").click();
|
||
this.assertTrue(env.$("#tab-government").hasClass("active"));
|
||
modal.close();
|
||
this.assertTrue(env.$("#modal-nation").hasClass("hidden"));
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_technology_theme_tabs_filter_the_list() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new NationModal();
|
||
modal.show(GOVERNMENTS, 0, TECHNOLOGIES, new Set(), 1.0e9, 1.0e15, 1.0e6);
|
||
const $themes = env.$("#technology-themes .subtab");
|
||
this.assertGreater($themes.length, 1, "the technologies are split into themes");
|
||
const method = TECHNOLOGIES.findIndex((t) => t.id === "scientific_method");
|
||
const automation = TECHNOLOGIES.findIndex((t) => t.id === "industrial_automation");
|
||
const rows = env.$("#technology-list .row-card");
|
||
this.assertNotEqual(rows.eq(method).css("display"), "none", "science is the opening theme");
|
||
this.assertEqual(rows.eq(automation).css("display"), "none", "other themes start hidden");
|
||
$themes.filter((_index, element) => env.$(element).text() === "Economy").trigger("click");
|
||
this.assertEqual(rows.eq(method).css("display"), "none", "science is hidden");
|
||
this.assertNotEqual(rows.eq(automation).css("display"), "none", "the economy theme shows");
|
||
modal.close();
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_a_repeatable_technology_shows_its_level() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new NationModal();
|
||
const method = TECHNOLOGIES.findIndex((t) => t.id === "scientific_method");
|
||
modal.show(GOVERNMENTS, 0, TECHNOLOGIES, new Set([method]), 1.0e9, 1.0e15, 1.0e6, {}, null, [], null, {
|
||
focus: -1,
|
||
progress: 0,
|
||
repeatCounts: { infantry_weapons: 3 },
|
||
});
|
||
const index = TECHNOLOGIES.findIndex((t) => t.id === "infantry_weapons");
|
||
const name = env.$("#technology-list .row-card").eq(index).find(".name").text();
|
||
this.assertTrue(name.includes("Level 3"), `the level is shown: ${name}`);
|
||
const text = env.$("#technology-list .row-card").eq(index).find(".cost").text();
|
||
this.assertTrue(text.startsWith("Cost"), "the next level is priced");
|
||
modal.close();
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_resources_tab_lists_stores_needs_and_the_market() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new NationModal();
|
||
const summary = {
|
||
stock: { steel: 1000, food: 2_000_000, luxury: 5000, hightech: 12 },
|
||
need: { steel: 10, food: 20, luxury: 30, hightech: 0 },
|
||
production: { steel: 5, food: 0, luxury: 0, hightech: 1 },
|
||
consumed: { steel: 15, food: 25, luxury: 35, hightech: 2, energy: 1.1e9 },
|
||
energy: { need: 1.0e9, supply: 2.0e9 },
|
||
trade: { imports: { steel: 40 }, exports: { steel: 7 } },
|
||
prices: {},
|
||
};
|
||
const market = [
|
||
{ id: "energy", price: 0.003, supply: 1.0e9, demand: 2.0e9 },
|
||
{ id: "steel", price: 600, supply: 100, demand: 200 },
|
||
{ id: "food", price: 300, supply: 50, demand: 60 },
|
||
{ id: "luxury", price: 60, supply: 0, demand: 10 },
|
||
{ id: "hightech", price: 800, supply: 0, demand: 0 },
|
||
];
|
||
modal.show(
|
||
GOVERNMENTS, 0, TECHNOLOGIES, new Set(), 1.0e9, 1.0e15, 5,
|
||
{}, null, [], null, null, { summary, market }
|
||
);
|
||
env.$("#modal-nation .tab[data-tab=resources]").click();
|
||
this.assertTrue(env.$("#tab-resources").hasClass("active"), "the tab opens");
|
||
const rows = env.$("#resources-list .resources-table tbody tr");
|
||
this.assertSize(rows, 5, "one row for every resource");
|
||
const energy = rows.eq(0);
|
||
this.assertTrue(energy.find(".name").text().includes("Energy"), "energy leads the list");
|
||
this.assertEqual(energy.find("td").eq(1).text(), "—", "energy has no store");
|
||
const steel = rows.eq(1);
|
||
this.assertTrue(steel.find(".name").text().includes("Steel"));
|
||
this.assertTrue(steel.find("td").eq(2).text().includes("15"), "consumption is shown");
|
||
this.assertTrue(steel.find("td").eq(4).text().includes("7"), "exports are shown");
|
||
this.assertTrue(steel.find("td").eq(5).text().includes("40"), "imports are shown");
|
||
this.assertTrue(steel.find("td").eq(6).text().includes("600"), "the steel price is shown");
|
||
this.assertTrue(steel.find("td").eq(7).text().length > 0, "world supply is shown");
|
||
modal.refresh(
|
||
0, new Set(), 1.0e9, 1.0e15, 5, {}, null, null, null, null,
|
||
{ summary, market }
|
||
);
|
||
this.assertSize(env.$("#resources-list .resources-table tbody tr"), 5, "refreshing keeps the rows");
|
||
modal.close();
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_resources_tab_ranks_major_exporters_and_importers() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new NationModal();
|
||
const summary = {
|
||
stock: { steel: 0, food: 0, luxury: 0, hightech: 0 },
|
||
need: { steel: 0, food: 0, luxury: 0, hightech: 0 },
|
||
production: { steel: 0, food: 0, luxury: 0, hightech: 0 },
|
||
consumed: { steel: 0, food: 0, luxury: 0, hightech: 0, energy: 0 },
|
||
energy: { need: 0, supply: 0 },
|
||
trade: { imports: {}, exports: {} },
|
||
prices: {},
|
||
};
|
||
const trade = [
|
||
{ civ: 0, imports: { steel: 10 }, exports: { steel: 100 } },
|
||
{ civ: 1, imports: { steel: 50 }, exports: { steel: 5 } },
|
||
];
|
||
const civs = [{ name: "France" }, { name: "Britain" }];
|
||
modal.show(
|
||
GOVERNMENTS, 0, TECHNOLOGIES, new Set(), 1.0e9, 1.0e15, 5,
|
||
{}, null, [], null, null, { summary, market: [], trade, civs }
|
||
);
|
||
env.$("#modal-nation .tab[data-tab=resources]").click();
|
||
env.$("#resources-subtabs .subtab[data-view=leaders]").click();
|
||
const rows = env.$("#resource-trade-list .resources-table tbody tr");
|
||
this.assertSize(rows, 5, "one row for every resource");
|
||
const steel = rows.eq(1);
|
||
this.assertTrue(steel.find("td").eq(1).text().includes("France"), "the major exporter leads");
|
||
this.assertTrue(steel.find("td").eq(3).text().includes("Britain"), "the major importer leads");
|
||
modal.close();
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_a_focused_technology_shows_a_progress_bar() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new NationModal();
|
||
const method = TECHNOLOGIES.findIndex((t) => t.id === "scientific_method");
|
||
modal.show(GOVERNMENTS, 0, TECHNOLOGIES, new Set(), 1.0e9, 1.0e15, 5, {}, null, [], null, {
|
||
focus: method,
|
||
progress: 10,
|
||
repeatCounts: {},
|
||
});
|
||
const card = env.$("#technology-list .row-card").eq(method);
|
||
this.assertTrue(card.find(".tech-progress-track").length > 0, "the focus has a bar");
|
||
this.assertEqual(
|
||
card.find(".tech-progress-text").text(),
|
||
`10 / ${groupDigits(technologyCost(TECHNOLOGIES[method]))}`,
|
||
"X / Y beside it"
|
||
);
|
||
this.assertEqual(card.find(".actions button").text(), "Focused", "no cost on the button");
|
||
modal.close();
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_military_technology_effects_are_readable_text() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new NationModal();
|
||
modal.show(GOVERNMENTS, 0, TECHNOLOGIES, new Set(), 1.0e9, 1.0e15, 5);
|
||
const infantry = TECHNOLOGIES.findIndex((t) => t.id === "infantry_weapons");
|
||
this.assertEqual(
|
||
env.$("#technology-list .row-card").eq(infantry).find(".effects").text(),
|
||
"+1.0% infantry attack"
|
||
);
|
||
modal.close();
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_reopening_rebuilds_rows_instead_of_stacking_them() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new NationModal();
|
||
let technology = null;
|
||
modal.onFocusTechnology = (index) => {
|
||
technology = index;
|
||
};
|
||
modal.show(GOVERNMENTS, 0, TECHNOLOGIES, new Set(), 1.0e9, 1.0e15, 0);
|
||
modal.close();
|
||
// Reopen with enough research to afford the first technology.
|
||
modal.show(GOVERNMENTS, 0, TECHNOLOGIES, new Set(), 1.0e9, 1.0e15, 1.0e6);
|
||
this.assertSize(env.$("#government-list .row-card"), GOVERNMENTS.length, "no stacked governments");
|
||
this.assertSize(env.$("#technology-list .row-card"), TECHNOLOGIES.length, "no stacked technologies");
|
||
const button = env.$("#technology-list .row-card").first().find(".actions button");
|
||
this.assertFalse(button.prop("disabled"), "the rebuilt button reflects the new budget");
|
||
button.click();
|
||
this.assertEqual(technology, 0);
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_budget_tab_shows_monthly_income_and_expenses() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new NationModal();
|
||
const month = (overrides) => {
|
||
const income = overrides.income || 0;
|
||
return {
|
||
monthIndex: 24000,
|
||
isCurrent: true,
|
||
label: "January 2000",
|
||
income,
|
||
taxes: { sales: income, export: 0, import: 0 },
|
||
sales: 0,
|
||
totalCash: 0,
|
||
totalEconomic: 0,
|
||
net: 0,
|
||
expenses: [],
|
||
resourceTrade: [],
|
||
...overrides,
|
||
};
|
||
};
|
||
const finances = {
|
||
months: [
|
||
month({
|
||
monthIndex: 23999,
|
||
isCurrent: false,
|
||
label: "December 1999",
|
||
income: 500,
|
||
totalCash: 400,
|
||
totalEconomic: 1200,
|
||
net: 100,
|
||
expenses: [["buildings", 400, 1200]],
|
||
}),
|
||
month({
|
||
income: 1035,
|
||
sales: 60,
|
||
totalCash: 550,
|
||
totalEconomic: 900,
|
||
net: 545,
|
||
expenses: [
|
||
["buildings", 400, 700, [
|
||
["steel", 250, [["Barracks", 250]]],
|
||
["hightech", 150, [["Laboratory", 150]]],
|
||
]],
|
||
["units", 150, 200, []],
|
||
],
|
||
resourceTrade: [["steel", 220, 0], ["food", 0, 90]],
|
||
}),
|
||
],
|
||
cities: [],
|
||
};
|
||
modal.show(GOVERNMENTS, 0, TECHNOLOGIES, new Set(), 1.0e9, 1.0e15, 1.0e6, finances);
|
||
env.$("#modal-nation .tab[data-tab=budget]").click();
|
||
this.assertTrue(env.$("#tab-budget").hasClass("active"));
|
||
this.assertSize(env.$("#budget-list table.budget-table"), 1);
|
||
|
||
const text = env.$("#budget-list").text();
|
||
this.assertTrue(text.includes("National sales tax"), "the month's taxes are shown");
|
||
this.assertTrue(text.includes("+¤1 035"), "the tax take is shown");
|
||
this.assertTrue(text.includes("Resource sales"), "resource sales are shown");
|
||
this.assertTrue(text.includes("Building upkeep"), "a building category is listed");
|
||
this.assertTrue(text.includes("−¤700"), "the rightmost column is the market value");
|
||
this.assertTrue(text.includes("cash −¤400"), "the cash the treasury paid is noted");
|
||
this.assertTrue(text.includes("Unit upkeep"), "a unit category is listed");
|
||
this.assertTrue(text.includes("Bought Steel"), "a bought resource is listed");
|
||
this.assertTrue(text.includes("Sold Food"), "a sold resource is listed");
|
||
this.assertTrue(text.includes("TOTAL"), "a total row is present");
|
||
this.assertTrue(text.includes("+¤545"), "the total carries the month net");
|
||
this.assertTrue(env.$("#budget-sub").text().includes("Treasury"));
|
||
this.assertTrue(env.$("#budget-sub").text().includes("January 2000"), "the month is named");
|
||
|
||
// Expanding an upkeep row names the resources it bought and the buildings
|
||
// that consumed them.
|
||
env.$("#budget-list tr .budget-name.clickable").first().click();
|
||
const expanded = env.$("#budget-list").text();
|
||
this.assertTrue(expanded.includes("Steel"), "the expanded row names steel");
|
||
this.assertTrue(expanded.includes("−¤250"), "and what it paid for it");
|
||
this.assertTrue(expanded.includes("Barracks"), "and the building that used it");
|
||
|
||
// The pager steps back to the previous month's digest.
|
||
env.$("#budget-months .budget-month-nav").first().click();
|
||
this.assertTrue(
|
||
env.$("#budget-sub").text().includes("December 1999"),
|
||
"the pager names the older month"
|
||
);
|
||
|
||
modal.refresh(0, new Set(), 1.0e9, 1.0e15, 1.0e6, {
|
||
months: [month({
|
||
income: 100,
|
||
totalCash: 400,
|
||
totalEconomic: 400,
|
||
net: -300,
|
||
expenses: [["buildings", 400, 400]],
|
||
})],
|
||
cities: [],
|
||
});
|
||
const refreshed = env.$("#budget-list").text();
|
||
this.assertTrue(refreshed.includes("−¤300"), "the total updates");
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_cities_tab_lists_regions_with_their_tax() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new NationModal();
|
||
const finances = {
|
||
income: { rate: 0.001, base: 0, modifier: 0, total: 0, sources: [], regions: [] },
|
||
buildingUpkeep: { total: 0, sources: [], groups: [] },
|
||
unitUpkeep: { total: 0, sources: [], groups: [] },
|
||
net: 0,
|
||
cities: [
|
||
{ id: 1, civ: 0, name: "Paris", isCapital: true, population: 1.0e6, gdp: 1.0e10, income: 300 },
|
||
{ id: 2, civ: 0, name: "Lille", isCapital: false, population: 5.0e5, gdp: 4.0e9, income: 120 },
|
||
{ id: 3, civ: 1, name: "London", isCapital: true, population: 9.0e5, gdp: 8.0e9, income: 240 },
|
||
],
|
||
};
|
||
modal.show(GOVERNMENTS, 0, TECHNOLOGIES, new Set(), 1.0e9, 1.0e15, 1.0e6, finances, {
|
||
civs: [{ name: "France" }, { name: "Britain" }],
|
||
localCiv: 0,
|
||
relations: [],
|
||
stats: [],
|
||
});
|
||
env.$("#modal-nation .tab[data-tab=cities]").click();
|
||
this.assertTrue(env.$("#tab-cities").hasClass("active"));
|
||
const text = env.$("#cities-list").text();
|
||
this.assertTrue(text.includes("Paris"), "own regions are listed");
|
||
this.assertTrue(text.includes("Lille"), "every own region is listed");
|
||
this.assertFalse(text.includes("London"), "foreign regions are hidden");
|
||
this.assertTrue(text.includes("TOTAL"), "the region totals are summed");
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_diplomacy_tab_lists_nations_and_declares_war() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new NationModal();
|
||
let declared = null;
|
||
modal.onDeclareWar = (index) => {
|
||
declared = index;
|
||
};
|
||
const civs = [{ name: "France" }, { name: "Britain" }, { name: "Poland" }];
|
||
const relations = [[0, 1, "peace"], [0, 2, "war"], [1, 2, "peace"]];
|
||
const stats = [
|
||
{ population: 2.0e6, gdp: 5.0e9, gdpPerCapita: 2500 },
|
||
{ population: 1.5e6, gdp: 3.0e9, gdpPerCapita: 2000 },
|
||
{ population: 1.0e6, gdp: 1.5e9, gdpPerCapita: 1500 },
|
||
];
|
||
modal.show(
|
||
GOVERNMENTS, 0, TECHNOLOGIES, new Set(), 1.0e9, 1.0e15, 1.0e6, {},
|
||
{ civs, localCiv: 0, relations, stats }
|
||
);
|
||
env.$("#modal-nation .tab[data-tab=diplomacy]").click();
|
||
this.assertTrue(env.$("#tab-diplomacy").hasClass("active"));
|
||
|
||
const rows = env.$("#diplomacy-list tbody tr");
|
||
this.assertSize(rows, 3, "every nation, including ours, is listed");
|
||
this.assertTrue(rows.eq(0).find(".name").text() === "Britain", "alphabetical by default");
|
||
this.assertTrue(rows.eq(0).find(".status").text().includes("At peace"));
|
||
const numbers = rows.eq(0).find("td.numeric");
|
||
this.assertTrue(numbers.eq(0).text().includes("¤3B"), "Britain's GDP shows");
|
||
this.assertTrue(numbers.eq(1).text().includes("¤2K"), "Britain's GDP/capita shows");
|
||
this.assertTrue(numbers.eq(2).text().includes("1.5M"), "Britain's population shows");
|
||
|
||
const own = rows.eq(1);
|
||
this.assertTrue(own.find(".name").text() === "France", "our own nation is listed");
|
||
this.assertTrue(own.hasClass("local-nation"), "our row is highlighted");
|
||
this.assertTrue(own.find(".status").text().includes("Your nation"));
|
||
this.assertSize(own.find("button"), 0, "we cannot declare war on ourselves");
|
||
|
||
this.assertTrue(rows.eq(2).find(".name").text() === "Poland");
|
||
this.assertTrue(rows.eq(2).find(".status").text().includes("At war"));
|
||
this.assertTrue(rows.eq(2).find("button").prop("disabled"), "already at war");
|
||
this.assertTrue(env.$("#diplomacy-sub").text().includes("at war with 1"));
|
||
|
||
rows.eq(0).find("button").click();
|
||
this.assertEqual(declared, 1);
|
||
modal.close();
|
||
this.assertTrue(env.$("#modal-nation").hasClass("hidden"));
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_diplomacy_shows_a_foreign_figure_in_its_own_currency_with_ours_beside_it() {
|
||
const env = await setupDom();
|
||
try {
|
||
setCurrency({ code: "EUR", symbol: "€", name: "Euro" });
|
||
setCurrencyBook(
|
||
[
|
||
{ civ: 0, symbol: "€", name: "Euro", value: 1 },
|
||
{ civ: 1, symbol: "₣", name: "Franc", value: 1 },
|
||
],
|
||
[
|
||
{ civ: 0, symbol: "€", rate: 1 },
|
||
{ civ: 1, symbol: "₣", rate: 2 },
|
||
],
|
||
0
|
||
);
|
||
const modal = new NationModal();
|
||
const civs = [{ name: "France" }, { name: "Britain" }];
|
||
const stats = [
|
||
{ population: 1.0e6, gdp: 1.0e9, gdpPerCapita: 1000 },
|
||
{ population: 1.5e6, gdp: 3.0e9, gdpPerCapita: 2000 },
|
||
];
|
||
modal.show(GOVERNMENTS, 0, TECHNOLOGIES, new Set(), 1.0e9, 1.0e15, 1.0e6, {}, {
|
||
civs,
|
||
localCiv: 0,
|
||
relations: [],
|
||
stats,
|
||
});
|
||
env.$("#modal-nation .tab[data-tab=diplomacy]").click();
|
||
|
||
const britain = env.$("#diplomacy-list tbody tr").eq(0);
|
||
const gdp = britain.find("td.numeric").eq(0);
|
||
this.assertTrue(gdp.text().includes("₣6B"), "the foreign GDP reads in its own currency");
|
||
this.assertTrue(
|
||
gdp.find(".conversion").text().includes("€3B"),
|
||
"our equivalent sits beside it in parentheses"
|
||
);
|
||
|
||
const france = env.$("#diplomacy-list tbody tr").eq(1);
|
||
const own = france.find("td.numeric").eq(0);
|
||
this.assertTrue(own.text().includes("€1B"), "our own GDP stays in our money");
|
||
this.assertSize(own.find(".conversion"), 0, "our own figure needs no conversion");
|
||
} finally {
|
||
setCurrencyBook([], [], -1);
|
||
setCurrency({ symbol: "¤" });
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_diplomacy_table_sorts_by_every_column() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new NationModal();
|
||
const civs = [{ name: "France" }, { name: "Britain" }, { name: "Poland" }];
|
||
const stats = [
|
||
{ population: 1.0e6, gdp: 1.0e9, gdpPerCapita: 1000 },
|
||
{ population: 3.0e6, gdp: 2.0e9, gdpPerCapita: 2000 },
|
||
{ population: 2.0e6, gdp: 3.0e9, gdpPerCapita: 3000 },
|
||
];
|
||
modal.show(
|
||
GOVERNMENTS, 0, TECHNOLOGIES, new Set(), 1.0e9, 1.0e15, 1.0e6, {},
|
||
{ civs, localCiv: 0, relations: [], stats }
|
||
);
|
||
const names = () => env.$("#diplomacy-list tbody tr .name")
|
||
.map((_, el) => env.$(el).text()).get();
|
||
this.assertEqual(names(), ["Britain", "France", "Poland"], "alphabetical by default");
|
||
this.assertTrue(env.$("#diplomacy-list th[data-sort=name]").hasClass("sorted-asc"));
|
||
|
||
// GDP ascending first, descending on the second click.
|
||
env.$("#diplomacy-list th[data-sort=gdp]").click();
|
||
this.assertEqual(names(), ["France", "Britain", "Poland"]);
|
||
this.assertTrue(env.$("#diplomacy-list th[data-sort=gdp]").hasClass("sorted-asc"));
|
||
env.$("#diplomacy-list th[data-sort=gdp]").click();
|
||
this.assertEqual(names(), ["Poland", "Britain", "France"]);
|
||
this.assertTrue(env.$("#diplomacy-list th[data-sort=gdp]").hasClass("sorted-desc"));
|
||
|
||
// A different column starts ascending.
|
||
env.$("#diplomacy-list th[data-sort=population]").click();
|
||
this.assertEqual(names(), ["France", "Poland", "Britain"]);
|
||
this.assertTrue(env.$("#diplomacy-list th[data-sort=population]").hasClass("sorted-asc"));
|
||
|
||
// Back to alphabetical.
|
||
env.$("#diplomacy-list th[data-sort=name]").click();
|
||
this.assertEqual(names(), ["Britain", "France", "Poland"]);
|
||
modal.close();
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_economy_tab_shows_private_cash_central_bank_and_rates() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new NationModal();
|
||
const money = {
|
||
viewerCiv: 0,
|
||
currency: { code: "FRF", symbol: "₣", name: "French Franc" },
|
||
currencies: [
|
||
{ civ: 0, code: "FRF", symbol: "₣", name: "French Franc", value: 1 },
|
||
{ civ: 1, code: "GBP", symbol: "£", name: "British Pound", value: 1 },
|
||
],
|
||
exchangeRates: [
|
||
{ civ: 0, code: "FRF", symbol: "₣", name: "French Franc", rate: 1 },
|
||
{ civ: 1, code: "GBP", symbol: "£", name: "British Pound", rate: 0.85 },
|
||
],
|
||
centralBank: {
|
||
rate: 0.02,
|
||
currency: { code: "FRF", symbol: "₣", name: "French Franc" },
|
||
reserves: [{ code: "GBP", symbol: "£", name: "British Pound", amount: 1234 }],
|
||
loans: 987654,
|
||
},
|
||
privateCash: 5000,
|
||
// The trade-tax rates, the day's take and the commodity inflation.
|
||
taxes: {
|
||
sales: { rate: 0.2, resources: { steel: 0.1 } },
|
||
export: { rate: 0.05, resources: {} },
|
||
import: { rate: 0.05, resources: {} },
|
||
},
|
||
takeTax: { sales: 1200, export: 300, import: 100 },
|
||
commodityInflation: 0.0123,
|
||
cities: [{ id: 1, civ: 0, name: "Paris", cashValue: 5000 }],
|
||
currencyHoldings: {
|
||
code: "FRF",
|
||
symbol: "₣",
|
||
name: "French Franc",
|
||
value: 1,
|
||
treasury: 1000,
|
||
treasuryDebt: 0,
|
||
own: [{ cityId: 1, name: "Paris", civ: 0, civName: "France", amount: 5000 }],
|
||
ownTotal: 5000,
|
||
foreign: [{ cityId: 2, name: "London", civ: 1, civName: "Britain", amount: 700 }],
|
||
foreignTotal: 700,
|
||
banks: [{ civ: 1, name: "Britain", amount: 1234 }],
|
||
bankTotal: 1234,
|
||
total: 7934,
|
||
},
|
||
};
|
||
const resourcesInfo = {
|
||
summary: {
|
||
stock: {},
|
||
need: {},
|
||
production: {},
|
||
consumed: { steel: 1200, food: 10, luxury: 0, hightech: 0, energy: 0 },
|
||
external: {
|
||
buy: { steel: { qty: 300, value: 180_000 } },
|
||
sell: { food: { qty: 40, value: 12_000 } },
|
||
},
|
||
prices: {},
|
||
},
|
||
market: [],
|
||
};
|
||
modal.show(
|
||
GOVERNMENTS, 0, TECHNOLOGIES, new Set(), 1.0e9, 1.0e15, 1.0e6,
|
||
{ months: [], cities: [] }, null, [], null, null, resourcesInfo, money
|
||
);
|
||
env.$("#modal-nation .tab[data-tab=economy]").click();
|
||
this.assertTrue(env.$("#tab-economy").hasClass("active"));
|
||
const summary = env.$("#economy-summary").text();
|
||
this.assertTrue(summary.includes("Paris"), "the region is listed");
|
||
this.assertTrue(summary.includes("5 000"), "its private cash is shown");
|
||
|
||
env.$("#economy-subtabs .subtab[data-view=taxes]").click();
|
||
const taxesText = env.$("#economy-taxes").text();
|
||
this.assertTrue(taxesText.includes("Sales tax"), "the sales tax is listed");
|
||
this.assertTrue(taxesText.includes("Export tariff"), "the export tariff is listed");
|
||
this.assertTrue(taxesText.includes("Import tariff"), "the import tariff is listed");
|
||
this.assertEqual(
|
||
env.$("#economy-taxes .tax-input[data-kind=sales]").first().val(), "20.00",
|
||
"the default sales rate is shown"
|
||
);
|
||
this.assertEqual(
|
||
env.$("#economy-taxes .tax-input[data-kind=sales][data-resource='steel']").val(), "10.00",
|
||
"the per-resource override is shown"
|
||
);
|
||
this.assertTrue(
|
||
env.$("#economy-sub").text().includes("1 600"), "the day's total take is shown"
|
||
);
|
||
|
||
env.$("#economy-subtabs .subtab[data-view=centralbank]").click();
|
||
const bank = env.$("#economy-central-bank").text();
|
||
this.assertTrue(bank.includes("British Pound"), "the foreign reserve is listed");
|
||
this.assertTrue(bank.includes("987 654"), "the outstanding loans are listed");
|
||
this.assertTrue(bank.includes("Commodity inflation"), "the inflation indicator is shown");
|
||
this.assertTrue(env.$("#cb-rate").length === 1, "the rate input is present");
|
||
|
||
env.$("#economy-subtabs .subtab[data-view=rates]").click();
|
||
this.assertTrue(env.$("#economy-rates").text().includes("British Pound"), "the rate table shows");
|
||
this.assertTrue(env.$("#economy-rates").text().includes("0.8500"), "the rate is shown");
|
||
|
||
env.$("#economy-subtabs .subtab[data-view=consumption]").click();
|
||
const consumption = env.$("#economy-consumption").text();
|
||
this.assertTrue(consumption.includes("Steel"), "the consumed resource is listed");
|
||
this.assertTrue(consumption.includes("1200"), "its daily consumption is shown");
|
||
this.assertTrue(consumption.includes("180 000"), "what was spent abroad is shown");
|
||
this.assertTrue(consumption.includes("40"), "what was sold abroad is shown");
|
||
|
||
env.$("#economy-subtabs .subtab[data-view=currency]").click();
|
||
const currency = env.$("#economy-currency").text();
|
||
this.assertTrue(
|
||
env.$("#economy-sub").text().includes("French Franc"),
|
||
"the currency is named"
|
||
);
|
||
this.assertTrue(currency.includes("Paris"), "a home region holding it is listed");
|
||
this.assertTrue(currency.includes("London"), "a foreign region holding it is listed");
|
||
this.assertTrue(currency.includes("Central bank of Britain"), "a foreign bank is listed");
|
||
this.assertTrue(currency.includes("7 934"), "the total in circulation is shown");
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_central_bank_charts_commodity_prices() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new NationModal();
|
||
const money = {
|
||
viewerCiv: 0,
|
||
currency: { code: "FRF", symbol: "₣", name: "French Franc" },
|
||
currencies: [{ civ: 0, code: "FRF", symbol: "₣", name: "French Franc", value: 1 }],
|
||
exchangeRates: [{ civ: 0, code: "FRF", symbol: "₣", name: "French Franc", rate: 1 }],
|
||
centralBank: {
|
||
rate: 0.02,
|
||
currency: { code: "FRF", symbol: "₣", name: "French Franc" },
|
||
reserves: [],
|
||
loans: 0,
|
||
},
|
||
taxes: {
|
||
sales: { rate: 0.2, resources: {} },
|
||
export: { rate: 0.05, resources: {} },
|
||
import: { rate: 0.05, resources: {} },
|
||
},
|
||
takeTax: { sales: 0, export: 0, import: 0 },
|
||
commodityInflation: 0,
|
||
currencyHoldings: null,
|
||
privateCash: 0,
|
||
cities: [],
|
||
resourcePriceHistory: [
|
||
["energy", [100, 110, 120]],
|
||
["food", [100, 95, 105]],
|
||
],
|
||
resourceInflation: [
|
||
{ id: "energy", index: 120, monthly: 0.09, yearly: 0.2 },
|
||
{ id: "food", index: 105, monthly: 0.105, yearly: 0.05 },
|
||
],
|
||
};
|
||
const resourcesInfo = {
|
||
summary: {
|
||
stock: {}, need: {}, production: {}, prices: {},
|
||
consumed: { steel: 0, food: 0, luxury: 0, hightech: 0, energy: 0 },
|
||
external: { buy: {}, sell: {} },
|
||
},
|
||
market: [],
|
||
trade: [],
|
||
};
|
||
modal.show(
|
||
GOVERNMENTS, 0, TECHNOLOGIES, new Set(), 1.0e9, 1.0e15, 1.0e6,
|
||
{ months: [], cities: [] }, null, [], null, null, resourcesInfo, money
|
||
);
|
||
env.$("#modal-nation .tab[data-tab=economy]").click();
|
||
env.$("#economy-subtabs .subtab[data-view=centralbank]").click();
|
||
this.assertSize(env.$("#economy-central-bank .line-chart"), 1, "the index chart is drawn");
|
||
this.assertSize(
|
||
env.$("#economy-central-bank .line-chart-line"), 2, "one line per commodity"
|
||
);
|
||
const table = env.$("#economy-central-bank .inflation-table").text();
|
||
this.assertTrue(table.includes("Energy"), "energy is tabled");
|
||
this.assertTrue(table.includes("Food"), "food is tabled");
|
||
this.assertTrue(table.includes("+9.00%"), "monthly inflation is shown");
|
||
this.assertTrue(table.includes("+20.00%"), "yearly inflation is shown");
|
||
this.assertTrue(
|
||
env.$("#economy-sub").text().includes("Commodity inflation"), "the headline is shown"
|
||
);
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
}
|
||
|
||
export class ResourceTrendTest extends TestCase {
|
||
async test_resources_tab_marks_day_on_day_changes() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new NationModal();
|
||
const info = (price, totalHours) => ({
|
||
totalHours,
|
||
summary: {
|
||
stock: { food: 100, steel: 100, luxury: 100, hightech: 0 },
|
||
consumed: { food: 10, steel: 10, luxury: 10, hightech: 0, energy: 100 },
|
||
production: { food: 10, steel: 10, luxury: 10, hightech: 0 },
|
||
energy: { need: 100, supply: 100 },
|
||
trade: { imports: {}, exports: {} },
|
||
},
|
||
market: [{ id: "food", price, prevPrice: price, supply: 1000, demand: 1000 }],
|
||
trade: [],
|
||
civs: [{ name: "France" }],
|
||
});
|
||
modal._renderResources(info(100, 0));
|
||
this.assertEqual(
|
||
env.$("#resources-list .stat-arrow").length, 0,
|
||
"the first day has no baseline to compare against"
|
||
);
|
||
modal._renderResources(info(120, 24));
|
||
this.assertTrue(
|
||
env.$("#resources-list .stat-arrow").length > 0, "the second day marks the changes"
|
||
);
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
}
|
||
|
||
export class ConfirmModalTest extends TestCase {
|
||
async test_confirm_runs_the_callback_and_cancel_does_not() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new ConfirmModal();
|
||
let confirmed = 0;
|
||
modal.show("Declare war?", "Marching in will declare war.", () => { confirmed += 1; });
|
||
this.assertTrue(modal.isShowing());
|
||
this.assertEqual(env.$("#confirm-title").text(), "Declare war?");
|
||
this.assertTrue(env.$("#confirm-message").text().includes("declare war"));
|
||
|
||
env.$("#confirm-cancel").click();
|
||
this.assertFalse(modal.isShowing());
|
||
this.assertEqual(confirmed, 0, "cancelling runs nothing");
|
||
|
||
modal.show("Again", "Sure?", () => { confirmed += 1; });
|
||
env.$("#confirm-ok").click();
|
||
this.assertEqual(confirmed, 1, "confirming runs the callback once");
|
||
this.assertFalse(modal.isShowing());
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
async test_close_button_cancels() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new ConfirmModal();
|
||
let confirmed = 0;
|
||
modal.show("T", "M", () => { confirmed += 1; });
|
||
env.$("#modal-confirm .modal-close").click();
|
||
this.assertEqual(confirmed, 0);
|
||
this.assertFalse(modal.isShowing());
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
}
|
||
|
||
export class NewsModalTest extends TestCase {
|
||
async test_lists_events_newest_first_with_dates() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new NewsModal();
|
||
const civs = [{ name: "France", demonym: "French" }, { name: "Britain", demonym: "British" }];
|
||
const news = [
|
||
{ id: 1, type: "war", actor: 0, target: 1, date: "1 January 2000, 00:00" },
|
||
{ id: 2, type: "capture", city: "Lyon", from: 1, to: 0, date: "2 January 2000, 05:00" },
|
||
];
|
||
modal.show(news, civs);
|
||
const items = env.$("#news-list .news-item");
|
||
this.assertSize(items, 2);
|
||
this.assertTrue(
|
||
items.eq(0).find(".news-text").text().includes("French troops have captured Lyon from the British")
|
||
);
|
||
this.assertTrue(items.eq(0).find(".news-date").text().includes("05:00"));
|
||
this.assertTrue(
|
||
items.eq(1).find(".news-text").text().includes("French leaders have declared war on Britain")
|
||
);
|
||
|
||
modal.refresh([], civs);
|
||
this.assertEqual(env.$("#news-list .news-item").length, 0);
|
||
this.assertTrue(env.$("#news-list").text().includes("No news"));
|
||
modal.close();
|
||
this.assertTrue(env.$("#modal-news").hasClass("hidden"));
|
||
} finally {
|
||
teardownDom(env);
|
||
}
|
||
}
|
||
|
||
test_news_names_the_actors_doing_the_action() {
|
||
const civs = [
|
||
{ name: "France", demonym: "French" },
|
||
{ name: "Vietnam", demonym: "Vietnamese" },
|
||
{ name: "Aotearoa", demonym: "New Zealander", adjective: "New Zealand" },
|
||
];
|
||
this.assertEqual(
|
||
describeNews({ type: "capture", city: "Saigon", from: 1, to: 0 }, civs),
|
||
"French troops have captured Saigon from the Vietnamese"
|
||
);
|
||
this.assertEqual(
|
||
describeNews({ type: "technology", civ: 0, name: "Radio" }, civs),
|
||
"French scientists have discovered Radio"
|
||
);
|
||
this.assertEqual(
|
||
describeNews({ type: "technology", civ: 0, name: "Railways", repeatable: true }, civs),
|
||
"French scientists have improved Railways"
|
||
);
|
||
this.assertEqual(
|
||
describeNews({ type: "war", actor: 0, target: 1 }, civs),
|
||
"French leaders have declared war on Vietnam"
|
||
);
|
||
this.assertEqual(
|
||
describeNews({ type: "policy", civ: 0, policy: "some_policy", ethnicity: 1 }, civs),
|
||
"French lawmakers have enacted some_policy for the Vietnamese"
|
||
);
|
||
this.assertEqual(
|
||
describeNews({ type: "join", player: "Alice", civ: 1 }, civs),
|
||
"Alice took control of Vietnam"
|
||
);
|
||
// Person-noun demonyms carry an explicit attributive form.
|
||
this.assertEqual(
|
||
describeNews({ type: "technology", civ: 2, name: "Radio" }, civs),
|
||
"New Zealand scientists have discovered Radio"
|
||
);
|
||
this.assertEqual(
|
||
describeNews({ type: "famine", civ: 1, region: "Hanoi" }, civs),
|
||
"the Vietnamese are starving in Hanoi: a famine has begun"
|
||
);
|
||
}
|
||
}
|