Mi2dRPGamEng/tests/test_menu_draw.py

356 lines
13 KiB
Python

from collections import defaultdict
from pathlib import Path
import pytest
from engine.character import Character
from engine.character_creator import CharacterCreator
from engine.controls_menu import ControlsMenu
from engine.defs import DefRegistry
from engine.game_modes import CharacterCreationFlow, MainMenu, PauseMenu
from engine.inventory import Inventory
from engine.item import ItemInstance
from engine.render import menu_draw
from engine.ui import AbilityBar, AbilityBarSlot, CharacterMenu
from engine.world_creation import WorldCreationMenu
DEFS_DIR = Path(__file__).resolve().parent.parent / "resources" / "defs"
@pytest.fixture()
def registry():
return DefRegistry.load(DEFS_DIR)
def new_buckets():
return defaultdict(list)
def test_draw_main_menu_emits_a_background_panel_and_option_text():
buckets = new_buckets()
menu_draw.draw_main_menu(buckets, MainMenu(has_existing_save=True), 800, 600)
assert buckets["ui/panel/background.png"]
assert any(k.startswith("ui/font/") for k in buckets)
def test_draw_pause_menu_emits_content():
buckets = new_buckets()
menu_draw.draw_pause_menu(buckets, PauseMenu(), 800, 600)
assert buckets["ui/panel/background.png"]
def test_draw_character_creation_species_step(registry):
buckets = new_buckets()
flow = CharacterCreationFlow(CharacterCreator(registry))
menu_draw.draw_character_creation(buckets, flow, 800, 600)
assert buckets["ui/panel/background.png"]
def test_draw_character_creation_name_step(registry):
buckets = new_buckets()
flow = CharacterCreationFlow(CharacterCreator(registry))
flow.advance()
flow.type_char("V")
menu_draw.draw_character_creation(buckets, flow, 800, 600)
assert buckets["ui/panel/background.png"]
def test_draw_character_creation_password_step_masks_the_typed_password(registry):
from engine.render.ui_draw import glyph_texture_path
buckets = new_buckets()
flow = CharacterCreationFlow(CharacterCreator(registry))
flow.advance() # species -> name
flow.type_char("V")
flow.advance() # name -> password
flow.type_char("q") # "q" appears in none of this screen's own labels/footer text
menu_draw.draw_character_creation(buckets, flow, 800, 600)
assert buckets["ui/panel/background.png"]
assert glyph_texture_path("*") in buckets # the typed password renders masked
assert glyph_texture_path("q") not in buckets # the raw character never appears
def test_draw_character_creation_defaults_to_new_character_title(registry):
from engine.render.ui_draw import glyph_texture_path
buckets = new_buckets()
flow = CharacterCreationFlow(CharacterCreator(registry))
menu_draw.draw_character_creation(buckets, flow, 800, 600)
assert glyph_texture_path("P") not in buckets # "P" appears in no default-screen text
def test_draw_character_creation_accepts_a_custom_title(registry):
from engine.render.ui_draw import glyph_texture_path
buckets = new_buckets()
flow = CharacterCreationFlow(CharacterCreator(registry))
menu_draw.draw_character_creation(buckets, flow, 800, 600, title="Spawn NPC")
assert buckets[glyph_texture_path("P")] # from "NPC" - only present with the custom title
def test_draw_world_creation_lists_templates_and_highlights_selection():
buckets = new_buckets()
menu = WorldCreationMenu()
menu.select(0)
menu_draw.draw_world_creation(buckets, menu, 800, 600)
assert buckets["ui/panel/background.png"]
assert buckets["ui/panel/highlight.png"]
assert any(k.startswith("ui/font/") for k in buckets)
def test_draw_world_creation_with_nothing_selected_defaults_to_first_row():
buckets = new_buckets()
menu_draw.draw_world_creation(buckets, WorldCreationMenu(), 800, 600)
assert buckets["ui/panel/highlight.png"]
def test_draw_controls_menu_highlights_selected_row():
buckets = new_buckets()
from engine.config import KeyBindings
menu = ControlsMenu(KeyBindings.load(Path("/nonexistent")))
menu_draw.draw_controls_menu(buckets, menu, 800, 600)
assert buckets["ui/panel/highlight.png"]
def test_draw_ability_bar_highlights_the_selected_slot():
buckets = new_buckets()
bar = AbilityBar()
bar.assign(2, AbilityBarSlot(kind="hand", source_id="right_hand", ability_id="time_warp_sphere"))
bar.select(2)
menu_draw.draw_ability_bar(buckets, bar, 600)
assert buckets["ui/panel/highlight.png"]
assert buckets["ui/panel/border.png"]
assert any(k.startswith("ui/font/") for k in buckets)
def test_draw_ability_bar_with_nothing_selected_has_no_highlight():
buckets = new_buckets()
menu_draw.draw_ability_bar(buckets, AbilityBar(), 600)
assert "ui/panel/highlight.png" not in buckets
assert buckets["ui/panel/border.png"]
def test_draw_character_card_bio_tab(registry):
buckets = new_buckets()
character = Character(
name="Vex", species_id="human", default_body_parts=registry.new_default_body_parts("human")
)
menu_draw.draw_character_card(buckets, character, CharacterMenu(), registry, 600)
assert buckets["ui/panel/background.png"]
assert any(k.startswith("ui/font/") for k in buckets)
def test_draw_character_card_equipment_tab(registry):
buckets = new_buckets()
character = Character(
name="Vex", species_id="human", default_body_parts=registry.new_default_body_parts("human")
)
card_menu = CharacterMenu()
card_menu.select_tab("equipment")
menu_draw.draw_character_card(buckets, character, card_menu, registry, 600)
assert buckets["ui/panel/background.png"]
def test_draw_character_card_medical_tab_lists_body_parts_and_organs(registry):
buckets = new_buckets()
character = Character(
name="Vex", species_id="human", default_body_parts=registry.new_default_body_parts("human")
)
card_menu = CharacterMenu()
card_menu.select_tab("medical")
menu_draw.draw_character_card(buckets, character, card_menu, registry, 600)
assert buckets["ui/panel/background.png"]
assert any(k.startswith("ui/font/") for k in buckets)
def test_medical_lines_omits_log_section_when_empty(registry):
character = Character(species_id="human", default_body_parts=registry.new_default_body_parts("human"))
lines = menu_draw._medical_lines(character, registry)
assert "Log:" not in lines
def test_medical_lines_includes_recent_log_entries_newest_first(registry):
character = Character(species_id="human", default_body_parts=registry.new_default_body_parts("human"))
character.log_medical_event("first event")
character.log_medical_event("second event")
lines = menu_draw._medical_lines(character, registry)
assert "Log:" in lines
log_index = lines.index("Log:")
assert lines[log_index + 1] == " second event"
assert lines[log_index + 2] == " first event"
def test_medical_lines_caps_log_display_count(registry):
character = Character(species_id="human", default_body_parts=registry.new_default_body_parts("human"))
for i in range(10):
character.log_medical_event(f"event {i}")
lines = menu_draw._medical_lines(character, registry)
log_index = lines.index("Log:")
log_lines = lines[log_index + 1 :]
assert len(log_lines) == menu_draw.MEDICAL_LOG_DISPLAY_COUNT
assert log_lines[0] == " event 9"
def test_draw_character_card_medical_tab_shows_removed_parts_and_ailments(registry):
from engine.character import Ailment
buckets = new_buckets()
character = Character(
name="Vex", species_id="human", default_body_parts=registry.new_default_body_parts("human")
)
torso = character.get_or_create_body_part_override("torso")
torso.ailments.append(Ailment(id="infection", name="Infection", severity=0.5))
leg = character.get_or_create_body_part_override("left_leg")
leg.removed = True
card_menu = CharacterMenu()
card_menu.select_tab("medical")
menu_draw.draw_character_card(buckets, character, card_menu, registry, 600)
assert buckets["ui/panel/background.png"]
# --- character card hit-testing + inventory grid ------------------------------------------------
def test_character_card_rect_matches_ability_bar_offset():
x, y, w, h = menu_draw.character_card_rect(600)
assert x == menu_draw.PADDING + menu_draw.ABILITY_BAR_W + menu_draw.PADDING
assert y == 600 - menu_draw.CHARACTER_CARD_H - menu_draw.PADDING
assert (w, h) == (menu_draw.CHARACTER_CARD_W, menu_draw.CHARACTER_CARD_H)
def test_point_in_rect_true_inside_false_outside():
rect = (10.0, 20.0, 100.0, 50.0)
assert menu_draw.point_in_rect(50, 40, rect) is True
assert menu_draw.point_in_rect(5, 40, rect) is False
assert menu_draw.point_in_rect(50, 100, rect) is False
def test_inventory_cell_at_finds_the_top_left_cell():
inventory = Inventory(6, 5)
origin = (100.0, 200.0)
cell = menu_draw.inventory_cell_at(inventory, origin, origin[0] + 1, origin[1] + 1)
assert cell == (0, 0)
def test_inventory_cell_at_none_outside_the_grid():
inventory = Inventory(6, 5)
assert menu_draw.inventory_cell_at(inventory, (100.0, 200.0), 0, 0) is None
def test_draw_inventory_grid_highlights_occupied_cells(registry):
buckets = new_buckets()
inventory = Inventory(2, 1)
inventory.place(ItemInstance("item-1", "bandage"), registry.get_item_def("bandage"), 0, 0)
menu_draw.draw_inventory_grid(buckets, inventory, (10.0, 10.0))
assert buckets["ui/panel/highlight.png"]
assert buckets["ui/panel/border.png"]
assert any(k.startswith("ui/font/") for k in buckets)
def test_draw_inventory_grid_blanks_the_dragged_cell(registry):
buckets = new_buckets()
inventory = Inventory(1, 1)
inventory.place(ItemInstance("item-1", "bandage"), registry.get_item_def("bandage"), 0, 0)
menu_draw.draw_inventory_grid(buckets, inventory, (10.0, 10.0), dragging_item_id="item-1")
assert "ui/panel/highlight.png" not in buckets # dragged cell drawn as blank "border" instead
assert buckets["ui/panel/border.png"]
def test_draw_drag_ghost_emits_a_panel_and_label():
buckets = new_buckets()
menu_draw.draw_drag_ghost(buckets, "bandage", 100.0, 100.0)
assert buckets["ui/panel/highlight.png"]
assert any(k.startswith("ui/font/") for k in buckets)
# --- equipment tab rows + backpack popup card -------------------------------------------------
def test_equipment_row_at_finds_a_worn_slot_row(registry):
from engine.character import ClothingPiece
character = Character(
species_id="human",
default_body_parts=registry.new_default_body_parts("human"),
clothing=[ClothingPiece("back", "backpack_basic")],
)
x, y, _w, _h = menu_draw.character_card_rect(600)
body_y = y + 10 + menu_draw.ROW_H + menu_draw.ROW_H * 1.5
assert menu_draw.equipment_row_at(character, 600, x + 20, body_y + 1) == "back"
def test_equipment_row_at_finds_the_pockets_row_after_worn_items(registry):
from engine.character import ClothingPiece
character = Character(
species_id="human",
default_body_parts=registry.new_default_body_parts("human"),
clothing=[ClothingPiece("back", "backpack_basic")],
)
character.inventory = Inventory(4, 4)
x, y, _w, _h = menu_draw.character_card_rect(600)
body_y = y + 10 + menu_draw.ROW_H + menu_draw.ROW_H * 1.5
pockets_row_y = body_y + menu_draw.ROW_H # second row, after "back"
from engine.ui import CharacterMenu
assert menu_draw.equipment_row_at(character, 600, x + 20, pockets_row_y + 1) == CharacterMenu.POCKETS_SLOT
def test_equipment_row_at_none_below_the_last_row(registry):
from engine.character import ClothingPiece
character = Character(
species_id="human",
default_body_parts=registry.new_default_body_parts("human"),
clothing=[ClothingPiece("back", "backpack_basic")],
)
x, y, _w, _h = menu_draw.character_card_rect(600)
body_y = y + 10 + menu_draw.ROW_H + menu_draw.ROW_H * 1.5
below_the_single_row = body_y + menu_draw.ROW_H * 5
assert menu_draw.equipment_row_at(character, 600, x + 20, below_the_single_row) is None
def test_backpack_popup_rect_sized_to_the_inventory_and_anchored_above_the_card():
inventory = Inventory(3, 2)
card_x, card_y, _w, _h = menu_draw.character_card_rect(600)
rect = menu_draw.backpack_popup_rect(inventory, 600)
x, y, w, h = rect
assert x == card_x
assert y < card_y # sits above the character card, not overlapping it
cell = menu_draw.INVENTORY_CELL_SIZE + menu_draw.INVENTORY_CELL_GAP
assert w == inventory.width * cell + 20.0
assert h == inventory.height * cell + 20.0 + menu_draw.ROW_H
def test_draw_backpack_popup_does_nothing_when_closed(registry):
from engine.ui import CharacterMenu
buckets = new_buckets()
character = Character(species_id="human", default_body_parts=registry.new_default_body_parts("human"))
menu_draw.draw_backpack_popup(buckets, character, CharacterMenu(), registry, 600)
assert buckets == {}
def test_draw_backpack_popup_renders_the_open_pockets_inventory(registry):
from engine.ui import CharacterMenu
buckets = new_buckets()
character = Character(species_id="human", default_body_parts=registry.new_default_body_parts("human"))
character.inventory = Inventory(2, 1)
menu = CharacterMenu()
menu.toggle_backpack(CharacterMenu.POCKETS_SLOT, character, registry)
menu_draw.draw_backpack_popup(buckets, character, menu, registry, 600)
assert buckets["ui/panel/background.png"]
assert buckets["ui/panel/border.png"]
assert any(k.startswith("ui/font/") for k in buckets)