160 lines
7.0 KiB
JavaScript
160 lines
7.0 KiB
JavaScript
import { TestCase } from "./framework/test_case.js";
|
|
import { smallState } from "./framework/helpers.js";
|
|
import { HOURS_PER_DAY } from "../shared/game_state/constants.js";
|
|
import { BUDGET_HISTORY_MONTHS } from "../shared/game_state/budget.js";
|
|
|
|
export class BudgetTest extends TestCase {
|
|
test_the_opening_month_is_the_only_report() {
|
|
const state = smallState();
|
|
const months = state.budgetMonthReports(0);
|
|
this.assertSize(months, 1, "only the live month exists at the start");
|
|
this.assertTrue(months[0].isCurrent, "the live month is flagged current");
|
|
this.assertEqual(months[0].label, "January 2000");
|
|
this.assertEqual(months[0].expenses.length, 0, "nothing has been spent yet");
|
|
this.assertEqual(months[0].net, 0, "the month opens balanced");
|
|
}
|
|
|
|
test_spending_is_filed_under_its_category() {
|
|
const state = smallState();
|
|
state._spendBudget(0, 1_000, "training");
|
|
state._spendBudget(0, 4_000, "construction");
|
|
state._recordTax(0, "sales", "steel", 9_000);
|
|
const report = state.budgetMonthReports(0).at(-1);
|
|
const byId = new Map(report.expenses.map((entry) => [entry.id, entry]));
|
|
this.assertApprox(byId.get("training").cash, 1_000);
|
|
this.assertApprox(byId.get("construction").cash, 4_000);
|
|
this.assertApprox(report.income, 9_000);
|
|
this.assertApprox(report.totalCash, 5_000);
|
|
this.assertApprox(report.net, 4_000);
|
|
}
|
|
|
|
test_a_spend_on_an_empty_treasury_becomes_debt_and_is_recorded() {
|
|
const state = smallState();
|
|
state.budgets.set(0, 0);
|
|
// The government always pays; the overdraft is the central bank's loan.
|
|
this.assertTrue(state._spendBudget(0, 500, "training"));
|
|
this.assertLess(state.getBudget(0), 0, "the treasury went into the red");
|
|
const report = state.budgetMonthReports(0).at(-1);
|
|
const training = report.expenses.find((entry) => entry.id === "training");
|
|
this.assertApprox(training.cash, 500);
|
|
}
|
|
|
|
test_a_refund_cancels_the_category() {
|
|
const state = smallState();
|
|
state._spendBudget(0, 1_000, "construction");
|
|
state._refundBudget(0, 1_000, "construction");
|
|
const report = state.budgetMonthReports(0).at(-1);
|
|
this.assertEqual(report.expenses.length, 0, "a refunded order does not linger");
|
|
this.assertEqual(report.net, 0);
|
|
}
|
|
|
|
test_daily_upkeep_records_cash_and_market_value() {
|
|
const state = smallState();
|
|
state.advanceHour();
|
|
for (let i = 0; i < 23; i++) state.advanceHour();
|
|
const report = state.budgetMonthReports(0).at(-1);
|
|
const byId = new Map(report.expenses.map((entry) => [entry.id, entry]));
|
|
for (const id of ["buildings", "units", "works", "transport"]) {
|
|
const entry = byId.get(id);
|
|
this.assertNotNull(entry, `${id} upkeep is recorded`);
|
|
this.assertTrue(entry.economic >= entry.cash - 1e-6, `${id} market value covers its cash`);
|
|
}
|
|
}
|
|
|
|
test_rolling_a_month_freezes_a_digest_and_resets() {
|
|
const state = smallState();
|
|
state._spendBudget(0, 2_000, "training");
|
|
// January 2000 has 31 days.
|
|
for (let i = 0; i < 31 * HOURS_PER_DAY; i++) state.tickHour();
|
|
const months = state.budgetMonthReports(0);
|
|
this.assertSize(months, 2, "January is frozen behind the live February");
|
|
this.assertEqual(months[0].label, "January 2000");
|
|
this.assertTrue(!months[0].isCurrent, "the frozen month is not current");
|
|
this.assertEqual(months[1].label, "February 2000");
|
|
this.assertTrue(months[1].isCurrent, "the live month is February");
|
|
const training = months[0].expenses.find((entry) => entry.id === "training");
|
|
this.assertApprox(training.cash, 2_000, 1e-6);
|
|
this.assertEqual(
|
|
months[1].expenses.find((entry) => entry.id === "training"),
|
|
undefined,
|
|
"the new month starts empty"
|
|
);
|
|
}
|
|
|
|
test_the_history_is_capped() {
|
|
const state = smallState();
|
|
for (let i = 0; i < BUDGET_HISTORY_MONTHS + 5; i++) state._rolloverBudget(24000 + i);
|
|
this.assertSize(state.budgetHistory, BUDGET_HISTORY_MONTHS, "only two years are kept");
|
|
}
|
|
|
|
test_resource_trade_is_mirrored_into_the_month() {
|
|
const state = smallState();
|
|
state._recordResourceSpend(0, "goods", 5_000, "steel");
|
|
state._recordResourceEarn(0, "steel", 2_000);
|
|
const report = state.budgetMonthReports(0).at(-1);
|
|
const trade = report.resourceTrade.find((entry) => entry.id === "steel");
|
|
this.assertNotNull(trade);
|
|
this.assertApprox(trade.bought, 5_000);
|
|
this.assertApprox(trade.sold, 2_000);
|
|
}
|
|
|
|
test_expense_rows_carry_their_resource_detail() {
|
|
const state = smallState();
|
|
state._recordBudgetCash(0, "buildings", -500);
|
|
state._recordBudgetCategoryResource(0, "buildings", "steel", 300);
|
|
state._recordBudgetCategoryResource(0, "buildings", "hightech", 200);
|
|
const report = state.budgetMonthReports(0).at(-1);
|
|
const buildings = report.expenses.find((entry) => entry.id === "buildings");
|
|
this.assertApprox(buildings.cash, 500);
|
|
this.assertEqual(buildings.buys.length, 2, "both resources are listed");
|
|
const steel = buildings.buys.find((entry) => entry[0] === "steel");
|
|
this.assertApprox(steel[1], 300);
|
|
}
|
|
|
|
test_expense_rows_name_the_buildings_that_consumed_a_resource() {
|
|
const state = smallState();
|
|
state._recordBudgetCash(0, "buildings", -500);
|
|
state._recordBudgetCategoryResource(0, "buildings", "steel", 300, "Barracks");
|
|
state._recordBudgetCategoryResource(0, "buildings", "steel", 100, "Factory");
|
|
const report = state.budgetMonthReports(0).at(-1);
|
|
const buildings = report.expenses.find((entry) => entry.id === "buildings");
|
|
const steel = buildings.buys.find((entry) => entry[0] === "steel");
|
|
this.assertApprox(steel[1], 400);
|
|
const sources = new Map(steel[2]);
|
|
this.assertApprox(sources.get("Barracks"), 300);
|
|
this.assertApprox(sources.get("Factory"), 100);
|
|
}
|
|
|
|
test_upkeep_records_the_resources_it_bought() {
|
|
const state = smallState();
|
|
// Empty the viewer's stores and stock a foreign region, so the day's upkeep
|
|
// has to be bought across the border.
|
|
for (const city of state.cities) {
|
|
const store = state.getCityResourceStock(city);
|
|
if (city.civ === 0) {
|
|
store.steel = 0;
|
|
store.hightech = 0;
|
|
} else {
|
|
store.steel = 1.0e9;
|
|
store.hightech = 1.0e9;
|
|
}
|
|
}
|
|
state.budgets.set(0, 1.0e15);
|
|
for (let i = 0; i < HOURS_PER_DAY; i++) state.tickHour();
|
|
const report = state.budgetMonthReports(0).at(-1);
|
|
const buildings = report.expenses.find((entry) => entry.id === "buildings");
|
|
this.assertNotNull(buildings, "building upkeep is recorded");
|
|
this.assertGreater(buildings.buys.length, 0, "the row names the resource it bought");
|
|
}
|
|
|
|
test_the_snapshot_ships_the_month_reports() {
|
|
const state = smallState();
|
|
const snapshot = state.snapshot(0);
|
|
const months = snapshot.viewerStats.budgetMonths;
|
|
this.assertGreaterOrEqual(months.length, 1);
|
|
const current = months[months.length - 1];
|
|
this.assertTrue(current.isCurrent, "the last shipped report is the live month");
|
|
this.assertTrue(Array.isArray(current.expenses), "the report carries its expenses");
|
|
}
|
|
}
|