move blueprints out of the sidebar into Ctrl+C / Ctrl+V dialogs

This commit is contained in:
2026-08-06 19:03:59 +02:00
parent 2cbcf1554f
commit 18cfe238f6
22 changed files with 1021 additions and 400 deletions

View File

@@ -139,3 +139,100 @@ TEST_CASE("readBuildingConfig returns nullopt for an unknown id", "[copyconfig]"
Simulation sim(loadTestConfig(), 7);
CHECK_FALSE(readBuildingConfig(sim, kInvalidBuildingId).has_value());
}
// The derived facts a blueprint card shows (REQ-UI-BLUEPRINT-CARD). They live here
// rather than in the dialog so they can be tested: src/ui is off the test include path.
namespace
{
Blueprint makeBlueprint(const std::vector<BuildingType>& types)
{
Blueprint blueprint;
for (const BuildingType type : types)
{
BlueprintBuilding building;
building.type = type;
building.rotation = Rotation::East;
blueprint.buildings.push_back(building);
}
return blueprint;
}
} // namespace
TEST_CASE("computeBlueprintCost sums the constituent placement costs", "[blueprint]")
{
const GameConfig cfg = loadTestConfig();
// belt 2 + belt 2 + miner 15.
const Blueprint blueprint = makeBlueprint(
{BuildingType::Belt, BuildingType::Belt, BuildingType::Miner});
CHECK(computeBlueprintCost(blueprint, cfg.buildings) == 19);
}
TEST_CASE("computeBlueprintCost ignores types absent from buildings.toml", "[blueprint]")
{
const GameConfig cfg = loadTestConfig();
REQUIRE(cfg.buildings.findBuildingDef(BuildingType::Hq) == nullptr);
const Blueprint blueprint = makeBlueprint({BuildingType::Belt, BuildingType::Hq});
CHECK(computeBlueprintCost(blueprint, cfg.buildings) == 2);
}
TEST_CASE("computeBlueprintCost is zero for an empty blueprint", "[blueprint]")
{
const GameConfig cfg = loadTestConfig();
CHECK(computeBlueprintCost(Blueprint{}, cfg.buildings) == 0);
}
TEST_CASE("summarizeBlueprintContents orders by descending count", "[blueprint]")
{
const GameConfig cfg = loadTestConfig();
const Blueprint blueprint = makeBlueprint(
{BuildingType::Belt, BuildingType::Smelter, BuildingType::Miner,
BuildingType::Miner, BuildingType::Smelter, BuildingType::Miner});
const std::vector<BlueprintContentEntry> summary =
summarizeBlueprintContents(blueprint, cfg.buildings);
REQUIRE(summary.size() == 3);
CHECK(summary[0].buildingName == "Miner");
CHECK(summary[0].count == 3);
CHECK(summary[1].buildingName == "Smelter");
CHECK(summary[1].count == 2);
CHECK(summary[2].buildingName == "Belt");
CHECK(summary[2].count == 1);
}
TEST_CASE("summarizeBlueprintContents breaks ties in buildings.toml order", "[blueprint]")
{
const GameConfig cfg = loadTestConfig();
// Miner is declared after Belt in buildings.toml, which is the order the build
// button bar lays its buttons out, so Belt wins the tie despite the equal counts
// and despite sorting later by enum value.
const Blueprint blueprint = makeBlueprint(
{BuildingType::Miner, BuildingType::Belt, BuildingType::Miner, BuildingType::Belt});
const std::vector<BlueprintContentEntry> summary =
summarizeBlueprintContents(blueprint, cfg.buildings);
REQUIRE(summary.size() == 2);
CHECK(summary[0].buildingName == "Belt");
CHECK(summary[1].buildingName == "Miner");
}
TEST_CASE("summarizeBlueprintContents spells multi-word ids as display names", "[blueprint]")
{
const GameConfig cfg = loadTestConfig();
const Blueprint blueprint = makeBlueprint({BuildingType::TunnelEntry});
const std::vector<BlueprintContentEntry> summary =
summarizeBlueprintContents(blueprint, cfg.buildings);
REQUIRE(summary.size() == 1);
CHECK(summary[0].buildingName == "Tunnel Entry");
}
TEST_CASE("summarizeBlueprintContents is empty for an empty blueprint", "[blueprint]")
{
const GameConfig cfg = loadTestConfig();
CHECK(summarizeBlueprintContents(Blueprint{}, cfg.buildings).empty());
}