The catalogue had grown a barracks, wartime shelters, an air-defense battery, a warm-water port and a shipyard, but only as data: infantry still trained without a barracks, shelters did nothing, and an airport was just an airbase. Each building now names the rule it grants with a `mechanic` id, so the simulation and the city panel describe the same thing without magic strings. - Barracks and shipyards open one more production line each. A line sticks to one entry at full rate, so two units build at once rather than the head absorbing every line; the base behaviour (one queue, overflow carrying into the next entry) is unchanged. - Wartime shelters keep 10% of civilians per level safe from bombardment and are very hard to knock down, and ranged fire can now destroy a building level outright, with no refund. Each building rolls its own chance per point of damage, from a generator seeded off the world so a bombardment replays identically. - Air defense fires one volley at a hostile aircraft the moment it enters the city tile, scaled by its level; a plane shot down never drops its bombs. - A port adds 1000 GDP per capita per level, per adjacent sea tile, to every tile in its region. - An airport draws a daily trickle of people from every other airport city in the world, scaled by the destination's level. - Wired the new effects into the city panel and taught the repeatable Industrial Automation to satisfy other technologies' prerequisites. Added tests for each mechanic and for the parallel lines.
657 lines
27 KiB
JavaScript
657 lines
27 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 } from "../client/js/modals/format.js";
|
||
import { BUILDINGS, GOVERNMENTS, TECHNOLOGIES, PROTO_UNITS } from "../shared/data.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 market = BUILDINGS.find((b) => b.id === "market");
|
||
this.assertTrue(buildingEffectText(market, 1).includes("GDP/capita"), "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_unaffordable_upgrade_is_disabled() {
|
||
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();
|
||
this.assertTrue(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_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);
|
||
}
|
||
}
|
||
}
|
||
|
||
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_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 / 20", "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_income_and_upkeep_breakdown() {
|
||
const env = await setupDom();
|
||
try {
|
||
const modal = new NationModal();
|
||
const finances = {
|
||
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" },
|
||
{ label: "Republic", kind: "government", effect: 0.1, amount: 90 },
|
||
],
|
||
},
|
||
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: 150,
|
||
sources: [{ label: "Modern infantry", kind: "unit", amount: 150, count: 3 }],
|
||
},
|
||
net: 485,
|
||
};
|
||
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("+€1 035/h"), "tax income is green");
|
||
this.assertTrue(text.includes("Bank in Paris (building) +5.0%"), "a building bonus is listed");
|
||
this.assertTrue(text.includes("Republic (government) +10.0%"), "a government bonus is listed");
|
||
this.assertTrue(text.includes("−€400/h"), "the building group shows its total cost");
|
||
this.assertTrue(text.includes("Modern infantry ×3"), "unit upkeep is listed");
|
||
this.assertTrue(text.includes("TOTAL"), "a total row is present");
|
||
this.assertTrue(text.includes("+€485/h"), "the total carries the net balance");
|
||
this.assertTrue(env.$("#budget-sub").text().includes("Treasury"));
|
||
|
||
// Expanding the building type reveals the per-city cost.
|
||
env.$("#budget-list tr.budget-group .budget-name").click();
|
||
this.assertTrue(
|
||
env.$("#budget-list").text().includes("Bank in Paris — level 2"),
|
||
"expanding a building type shows its cities"
|
||
);
|
||
|
||
modal.refresh(0, new Set(), 1.0e9, 1.0e15, 1.0e6, {
|
||
income: { rate: 0.001, base: 100, modifier: 0, total: 100, sources: [] },
|
||
buildingUpkeep: { total: 400, sources: [], groups: [] },
|
||
unitUpkeep: { total: 150, sources: [], groups: [] },
|
||
net: -450,
|
||
});
|
||
const refreshed = env.$("#budget-list").text();
|
||
this.assertTrue(refreshed.includes("−€450/h"), "the total updates");
|
||
this.assertTrue(refreshed.includes("No bonuses"), "empty income sources are named");
|
||
} 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_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);
|
||
}
|
||
}
|
||
}
|
||
|
||
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("The French captured Lyon from Britain"));
|
||
this.assertTrue(items.eq(0).find(".news-date").text().includes("05:00"));
|
||
this.assertTrue(items.eq(1).find(".news-text").text().includes("The French 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);
|
||
}
|
||
}
|
||
}
|