383 lines
14 KiB
Python
383 lines
14 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.dev_tools import DevFactionMenu
|
|
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_faction_editor_lists_existing_tags_with_the_selected_one_highlighted(registry):
|
|
buckets = new_buckets()
|
|
character = Character(
|
|
name="Vex", species_id="human", default_body_parts=registry.new_default_body_parts("human"),
|
|
factions=["player", "crew"],
|
|
)
|
|
menu = DevFactionMenu()
|
|
|
|
menu_draw.draw_faction_editor(buckets, menu, character, 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_faction_editor_handles_a_character_with_no_tags_yet(registry):
|
|
buckets = new_buckets()
|
|
character = Character(species_id="human", default_body_parts=registry.new_default_body_parts("human"))
|
|
menu = DevFactionMenu()
|
|
|
|
menu_draw.draw_faction_editor(buckets, menu, character, 800, 600)
|
|
|
|
assert buckets["ui/panel/background.png"]
|
|
assert "ui/panel/highlight.png" not in buckets
|
|
|
|
|
|
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)
|