"""Tests for core engine mechanics: containment, flags, clock, articles.""" from h2g2.engine.game_object import GameObject, Room, Flag from h2g2.engine.clock import Clock class TestObjectContainment: def test_move_to_sets_parent(self): room = Room("R1", desc="room") obj = GameObject("O1", desc="thing") obj.move_to(room) assert obj.parent is room assert obj in room.children def test_move_to_removes_from_old_parent(self): room1 = Room("R1", desc="room1") room2 = Room("R2", desc="room2") obj = GameObject("O1", desc="thing") obj.move_to(room1) obj.move_to(room2) assert obj not in room1.children assert obj in room2.children assert obj.parent is room2 def test_contains(self): room = Room("R1", desc="room") obj = GameObject("O1", desc="thing") obj.move_to(room) assert room.contains(obj) def test_is_held_by_direct(self): holder = GameObject("H", desc="holder") obj = GameObject("O", desc="thing") obj.move_to(holder) assert obj.is_held_by(holder) def test_is_held_by_nested(self): holder = GameObject("H", desc="holder") container = GameObject("C", desc="container") obj = GameObject("O", desc="thing") container.move_to(holder) obj.move_to(container) assert obj.is_held_by(holder) def test_is_held_by_false(self): holder = GameObject("H", desc="holder") obj = GameObject("O", desc="thing") assert not obj.is_held_by(holder) def test_contents_string_excludes_invisible(self): room = Room("R1", desc="room") visible = GameObject("V", desc="visible") invisible = GameObject("I", desc="invisible", flags={Flag.INVISIBLE}) visible.move_to(room) invisible.move_to(room) result = room.contents_string() assert visible in result assert invisible not in result def test_contents_string_excludes_ndescbit(self): room = Room("R1", desc="room") visible = GameObject("V", desc="visible") hidden = GameObject("H", desc="hidden", flags={Flag.NDESCBIT}) visible.move_to(room) hidden.move_to(room) result = room.contents_string() assert visible in result assert hidden not in result class TestFlagOperations: def test_fset_and_fset_q(self): obj = GameObject("O", desc="thing") assert not obj.fset_q(Flag.TAKEBIT) obj.fset(Flag.TAKEBIT) assert obj.fset_q(Flag.TAKEBIT) def test_fclear(self): obj = GameObject("O", desc="thing", flags={Flag.TAKEBIT}) assert obj.fset_q(Flag.TAKEBIT) obj.fclear(Flag.TAKEBIT) assert not obj.fset_q(Flag.TAKEBIT) def test_fclear_nonexistent_flag_no_error(self): obj = GameObject("O", desc="thing") obj.fclear(Flag.TAKEBIT) # should not raise def test_multiple_flags(self): obj = GameObject("O", desc="thing", flags={Flag.TAKEBIT, Flag.OPENBIT}) assert obj.fset_q(Flag.TAKEBIT) assert obj.fset_q(Flag.OPENBIT) assert not obj.fset_q(Flag.WEARBIT) class TestObjectArticle: def test_default_article(self): obj = GameObject("O", desc="ball") assert obj.article() == "a " assert obj.a_desc() == "a ball" assert obj.the_desc() == "the ball" def test_vowel_article(self): obj = GameObject("O", desc="egg", flags={Flag.VOWELBIT}) assert obj.article() == "an " assert obj.a_desc() == "an egg" def test_no_article(self): obj = GameObject("O", desc="Ford Prefect", flags={Flag.NARTICLEBIT}) assert obj.article() == "" assert obj.a_desc() == "Ford Prefect" assert obj.the_desc() == "Ford Prefect" class TestClock: def test_tick_and_fire(self): clock = Clock() fired = [] def handler(state): fired.append(True) return True clock.queue(handler, 3, name="test") clock.tick_all(None) # tick 1: countdown 3->2 assert len(fired) == 0 clock.tick_all(None) # tick 2: countdown 2->1 assert len(fired) == 0 clock.tick_all(None) # tick 3: countdown 1->0, fires assert len(fired) == 1 def test_one_shot_does_not_refire(self): clock = Clock() fired = [] def handler(state): fired.append(True) return True clock.queue(handler, 1, name="test") clock.tick_all(None) # fires clock.tick_all(None) # should not fire again assert len(fired) == 1 def test_disable_prevents_fire(self): clock = Clock() fired = [] def handler(state): fired.append(True) return True entry = clock.queue(handler, 2, name="test") clock.tick_all(None) # tick 1 clock.disable(entry) clock.tick_all(None) # tick 2 — disabled, should not fire clock.tick_all(None) # tick 3 assert len(fired) == 0 def test_negative_tick_fires_every_turn(self): clock = Clock() fired = [] def handler(state): fired.append(True) return True clock.queue(handler, -1, name="repeating") clock.tick_all(None) clock.tick_all(None) clock.tick_all(None) assert len(fired) == 3 def test_queue_updates_existing_entry(self): clock = Clock() fired = [] def handler(state): fired.append(True) return True clock.queue(handler, 5, name="test") clock.queue(handler, 1, name="test") # reset to 1 clock.tick_all(None) # should fire now (1->0) assert len(fired) == 1 def test_remove_entry(self): clock = Clock() fired = [] def handler(state): fired.append(True) return True entry = clock.queue(handler, 1, name="test") clock.remove(entry) clock.tick_all(None) assert len(fired) == 0