from pathlib import Path import pytest from engine.defs import DefRegistry from engine.inventory import Inventory from engine.item import ItemInstance from engine.tile import Tile, TileLayerInstance from engine.ui import LayerPickerMenu from resources.logic.construction import ( construct_tile_layer, damage_tile_layer, deconstruct_tile_layer, destroy_tile_layer, present_layers, ) DEFS_DIR = Path(__file__).resolve().parent.parent / "resources" / "defs" @pytest.fixture() def registry(): return DefRegistry.load(DEFS_DIR) def make_walled_tile() -> Tile: return Tile( subfloor=TileLayerInstance("dirt"), flooring=TileLayerInstance("wood_deck"), room=TileLayerInstance("wood_wall"), ) def test_present_layers_lists_only_occupied_layers(): tile = make_walled_tile() assert present_layers(tile) == ["subfloor", "flooring", "room"] def test_destroy_clears_layer_with_no_item_yielded(): tile = make_walled_tile() assert destroy_tile_layer(tile, "room") is True assert tile.room is None assert present_layers(tile) == ["subfloor", "flooring"] def test_destroy_on_empty_layer_is_a_no_op(): tile = make_walled_tile() assert destroy_tile_layer(tile, "roof") is False def test_deconstruct_clears_layer_and_yields_its_item(registry): tile = make_walled_tile() item = deconstruct_tile_layer(tile, "room", registry) assert tile.room is None assert item is not None assert item.def_id == "wood_planks" def test_deconstruct_layer_without_yield_returns_none(registry): tile = make_walled_tile() item = deconstruct_tile_layer(tile, "flooring", registry) # wood_deck has no deconstruct_item_id assert tile.flooring is None assert item is None def test_damage_tile_layer_reduces_integrity_and_clears_at_zero(): tile = make_walled_tile() assert damage_tile_layer(tile, "room", 40.0) is False assert tile.room.integrity == pytest.approx(60.0) assert damage_tile_layer(tile, "room", 60.0) is True assert tile.room is None def test_construct_tile_layer_consumes_item_and_places_layer(registry): tile = Tile(subfloor=TileLayerInstance("dirt"), flooring=TileLayerInstance("wood_deck")) inventory = Inventory(4, 4) planks_def = registry.get_item_def("wood_planks") inventory.place(ItemInstance("planks-1", "wood_planks"), planks_def, 0, 0) assert construct_tile_layer(tile, "room", planks_def, inventory, "planks-1") is True assert tile.room.def_id == "wood_wall" assert inventory.items() == [] def test_construct_fails_if_layer_already_occupied(registry): tile = make_walled_tile() # room already has a wall inventory = Inventory(4, 4) planks_def = registry.get_item_def("wood_planks") inventory.place(ItemInstance("planks-1", "wood_planks"), planks_def, 0, 0) assert construct_tile_layer(tile, "room", planks_def, inventory, "planks-1") is False assert {p.item.item_id for p in inventory.items()} == {"planks-1"} # item not consumed def test_construct_fails_if_item_does_not_build_this_layer(registry): tile = Tile() inventory = Inventory(4, 4) sandwich_def = registry.get_item_def("sandwich") inventory.place(ItemInstance("sandwich-1", "sandwich"), sandwich_def, 0, 0) assert construct_tile_layer(tile, "room", sandwich_def, inventory, "sandwich-1") is False def test_layer_picker_menu_cycle_wraps_and_confirm_returns_selected(): menu = LayerPickerMenu(map_id="ship", x=1, y=1, z=0, options=["subfloor", "flooring", "room"]) assert menu.confirm() == "subfloor" menu.cycle(1) assert menu.confirm() == "flooring" menu.cycle(1) menu.cycle(1) assert menu.confirm() == "subfloor" # wrapped around menu.cycle(-1) assert menu.confirm() == "room" def test_layer_picker_menu_with_no_options_confirms_none(): menu = LayerPickerMenu(map_id="ship", x=1, y=1, z=0, options=[]) assert menu.confirm() is None menu.cycle(1) # no-op, doesn't crash