Files
Battle-for-Tismo/tests/modals_test.js
T

567 lines
23 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 { CityModal, NationModal, ConfirmModal, NewsModal } from "../client/js/modals.js";
import { BUILDINGS, GOVERNMENTS, TECHNOLOGIES, PROTO_UNITS } from "../shared/data.js";
export class CityModalTest extends TestCase {
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.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_reopening_rebuilds_rows_instead_of_stacking_them() {
const env = await setupDom();
try {
const modal = new NationModal();
let technology = null;
modal.onTechnology = (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);
}
}
}