#include "catch.hpp" #include #include #include #include #include "Blueprint.h" #include "BlueprintSerializer.h" #include "BuildingType.h" #include "Rotation.h" namespace { Blueprint makeBlueprintWith(const std::vector& buildings, const QString& name = "Test") { Blueprint bp; bp.name = name; bp.buildings = buildings; return bp; } BlueprintBuilding makeBuilding(BuildingType type, Rotation rotation, QPoint offset, std::string recipeId = "") { BlueprintBuilding b; b.type = type; b.rotation = rotation; b.offset = offset; b.recipeId = std::move(recipeId); return b; } } // namespace // --------------------------------------------------------------------------- // Case 1 — single blueprint, one building: all fields survive round-trip // --------------------------------------------------------------------------- TEST_CASE("BlueprintSerializer: single blueprint round-trips all fields", "[serializer]") { const BlueprintBuilding building = makeBuilding(BuildingType::Miner, Rotation::East, QPoint(2, -1), "mine_iron_ore"); const Blueprint original = makeBlueprintWith({building}, "My Miner"); const std::string toml = BlueprintSerializer::serialize({original}); const std::vector loaded = BlueprintSerializer::deserialize(toml); REQUIRE(loaded.size() == 1); REQUIRE(loaded[0].name == "My Miner"); REQUIRE(loaded[0].buildings.size() == 1); const BlueprintBuilding& b = loaded[0].buildings[0]; REQUIRE(b.type == BuildingType::Miner); REQUIRE(b.rotation == Rotation::East); REQUIRE(b.offset == QPoint(2, -1)); REQUIRE(b.recipeId == "mine_iron_ore"); } // --------------------------------------------------------------------------- // Case 2 — multiple blueprints: count and order preserved // --------------------------------------------------------------------------- TEST_CASE("BlueprintSerializer: multiple blueprints preserve count and order", "[serializer]") { const Blueprint first = makeBlueprintWith( {makeBuilding(BuildingType::Belt, Rotation::North, QPoint(0, 0))}, "First"); const Blueprint second = makeBlueprintWith( {makeBuilding(BuildingType::Smelter, Rotation::South, QPoint(1, 0))}, "Second"); const Blueprint third = makeBlueprintWith( {makeBuilding(BuildingType::Assembler, Rotation::West, QPoint(0, 1))}, "Third"); const std::string toml = BlueprintSerializer::serialize({first, second, third}); const std::vector loaded = BlueprintSerializer::deserialize(toml); REQUIRE(loaded.size() == 3); REQUIRE(loaded[0].name == "First"); REQUIRE(loaded[1].name == "Second"); REQUIRE(loaded[2].name == "Third"); } // --------------------------------------------------------------------------- // Case 3 — all four Rotation values round-trip // --------------------------------------------------------------------------- TEST_CASE("BlueprintSerializer: all rotations round-trip", "[serializer]") { const std::vector rotations = { Rotation::North, Rotation::East, Rotation::South, Rotation::West }; for (const Rotation rot : rotations) { const Blueprint bp = makeBlueprintWith( {makeBuilding(BuildingType::Belt, rot, QPoint(0, 0))}); const std::string toml = BlueprintSerializer::serialize({bp}); const std::vector loaded = BlueprintSerializer::deserialize(toml); REQUIRE(loaded.size() == 1); REQUIRE(loaded[0].buildings.size() == 1); REQUIRE(loaded[0].buildings[0].rotation == rot); } } // --------------------------------------------------------------------------- // Case 5 — negative and zero offsets survive intact // --------------------------------------------------------------------------- TEST_CASE("BlueprintSerializer: negative and zero offsets round-trip", "[serializer]") { const Blueprint bp = makeBlueprintWith({ makeBuilding(BuildingType::Belt, Rotation::North, QPoint(0, 0)), makeBuilding(BuildingType::Belt, Rotation::North, QPoint(-3, -2)), makeBuilding(BuildingType::Belt, Rotation::North, QPoint(5, 4)), }); const std::string toml = BlueprintSerializer::serialize({bp}); const std::vector loaded = BlueprintSerializer::deserialize(toml); REQUIRE(loaded[0].buildings[0].offset == QPoint(0, 0)); REQUIRE(loaded[0].buildings[1].offset == QPoint(-3, -2)); REQUIRE(loaded[0].buildings[2].offset == QPoint(5, 4)); } // --------------------------------------------------------------------------- // Case 6 — empty and non-empty recipeId both round-trip correctly // --------------------------------------------------------------------------- TEST_CASE("BlueprintSerializer: empty and non-empty recipeId round-trip", "[serializer]") { const Blueprint bp = makeBlueprintWith({ makeBuilding(BuildingType::Miner, Rotation::North, QPoint(0, 0), "mine_iron_ore"), makeBuilding(BuildingType::Assembler, Rotation::North, QPoint(3, 0), ""), }); const std::string toml = BlueprintSerializer::serialize({bp}); const std::vector loaded = BlueprintSerializer::deserialize(toml); REQUIRE(loaded[0].buildings[0].recipeId == "mine_iron_ore"); REQUIRE(loaded[0].buildings[1].recipeId == ""); } // --------------------------------------------------------------------------- // Case 8 — empty blueprint list round-trips to empty vector // --------------------------------------------------------------------------- TEST_CASE("BlueprintSerializer: empty list round-trips to empty vector", "[serializer]") { const std::string toml = BlueprintSerializer::serialize({}); const std::vector loaded = BlueprintSerializer::deserialize(toml); REQUIRE(loaded.empty()); } // --------------------------------------------------------------------------- // Case 9 — malformed TOML throws std::runtime_error // --------------------------------------------------------------------------- TEST_CASE("BlueprintSerializer: malformed TOML throws", "[serializer]") { REQUIRE_THROWS_AS( BlueprintSerializer::deserialize("[[blueprint\nname = 42"), std::runtime_error); } // --------------------------------------------------------------------------- // Case 10 — unknown building type string throws std::runtime_error // --------------------------------------------------------------------------- TEST_CASE("BlueprintSerializer: unknown building type throws", "[serializer]") { const std::string badToml = "[[blueprint]]\n" "name = \"Broken\"\n" "buildings = [{type = \"unicorn\", rotation = \"north\"," " offset_x = 0, offset_y = 0, recipe_id = \"\"}]\n"; REQUIRE_THROWS_AS( BlueprintSerializer::deserialize(badToml), std::runtime_error); }