Files
Battle-for-Tismo/tests/modals_test.js
T
2026-09-17 22:01:30 +02:00

381 lines
16 KiB
JavaScript
Raw 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 { TrainModal, BuildingsModal, NationModal, ConfirmModal, NewsModal } from "../client/js/modals.js";
import { BUILDINGS, GOVERNMENTS, TECHNOLOGIES, PROTO_UNITS } from "../shared/data.js";
export class TrainModalTest extends TestCase {
async test_lists_trainable_units_and_requests_one() {
const env = await setupDom();
try {
const modal = new TrainModal();
let requested = null;
modal.onTrain = (cityId, protoIndex) => {
requested = [cityId, protoIndex];
};
modal.show({ id: 5, name: "Paris" }, 1.0e9, [{ ...PROTO_UNITS[0], serverIndex: 0 }]);
this.assertEqual(env.$("#train-title").text(), "Paris — Train");
this.assertSize(env.$("#train-list button"), 1);
this.assertTrue(env.$("#train-list button").text().includes("Modern infantry"));
env.$("#train-list button").click();
this.assertEqual(requested, [5, 0]);
modal.close();
this.assertTrue(env.$("#modal-train").hasClass("hidden"));
} finally {
teardownDom(env);
}
}
async test_training_progress_states() {
const env = await setupDom();
try {
const modal = new TrainModal();
modal.show({ id: 1, name: "Lyon" }, 1.0e9, [{ ...PROTO_UNITS[0], serverIndex: 0 }]);
modal.beginTraining("Modern infantry", 10, 2);
this.assertFalse(env.$("#train-status").hasClass("hidden"));
this.assertFalse(env.$("#train-list button").prop("disabled"), "more units can still be queued");
modal.setTrainingProgress("Modern infantry", 4, 10);
this.assertTrue(env.$("#train-label").text().includes("6 h left"));
modal.endTraining();
this.assertEqual(env.$("#train-label").text(), "Training complete");
this.assertFalse(env.$("#train-list button").prop("disabled"));
} finally {
teardownDom(env);
}
}
async test_queue_merges_only_contiguous_identical_units() {
const env = await setupDom();
try {
const modal = new TrainModal();
const cancelled = [];
modal.onCancel = (cityId, index) => cancelled.push([cityId, index]);
const unit = (i) => ({ ...PROTO_UNITS[i], serverIndex: i });
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 },
];
modal.show({ id: 7, name: "Lyon" }, 1.0e9, [unit(0), unit(2)], queue);
const rows = env.$("#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, [[7, 1]], "cancelling a run removes its last unit");
} finally {
teardownDom(env);
}
}
}
export class BuildingsModalTest extends TestCase {
async test_prices_rows_and_requests_build_and_demolish() {
const env = await setupDom();
try {
const modal = new BuildingsModal();
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" }, 1.0e9, 1.0e15, BUILDINGS, {});
this.assertEqual(env.$("#buildings-title").text(), "Lyon — Buildings");
this.assertSize(env.$("#buildings-list .row-card"), BUILDINGS.length);
const first = env.$("#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.refresh(1.0e9, 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-buildings").hasClass("hidden"));
} finally {
teardownDom(env);
}
}
async test_unaffordable_upgrade_is_disabled() {
const env = await setupDom();
try {
const modal = new BuildingsModal();
modal.show({ id: 1, name: "X" }, 1.0e9, 0, BUILDINGS, {});
const upgrade = env.$("#buildings-list .row-card").first().find(".actions button").first();
this.assertTrue(upgrade.prop("disabled"));
} 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.onTechnology = (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("Research"));
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_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 },
{ label: "Republic", kind: "government", effect: 0.1, amount: 90 },
],
},
buildingUpkeep: { total: 400, sources: [{ label: "Bank", kind: "building", 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"));
const rows = env.$("#budget-list .row-card");
this.assertSize(rows, 4);
this.assertTrue(rows.eq(0).find(".value").text().includes("+€1 035/h"));
this.assertTrue(rows.eq(0).find(".budget-detail").text().includes("Tax rate 0.10% of GDP"));
this.assertTrue(rows.eq(0).find(".budget-detail").text().includes("Bank (building) +5.0%"));
this.assertTrue(rows.eq(0).find(".budget-detail").text().includes("Republic (government) +10.0%"));
this.assertTrue(rows.eq(1).find(".value").text().includes("−€400/h"));
this.assertTrue(rows.eq(1).find(".budget-detail").text().includes("Bank — €400/h"));
this.assertTrue(rows.eq(2).find(".value").text().includes("−€150/h"));
this.assertTrue(rows.eq(2).find(".budget-detail").text().includes("Modern infantry ×3"));
this.assertTrue(rows.eq(3).find(".value").text().includes("+€485/h"));
this.assertTrue(rows.eq(3).find(".value").hasClass("positive"));
this.assertTrue(env.$("#budget-sub").text().includes("Treasury"));
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: [] },
unitUpkeep: { total: 150, sources: [] },
net: -450,
});
this.assertTrue(rows.eq(3).find(".value").text().includes("−€450/h"));
this.assertTrue(rows.eq(3).find(".value").hasClass("negative"));
this.assertTrue(rows.eq(0).find(".budget-detail").text().includes("No bonuses"));
} 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);
}
}
}