class_name TestImplantNavStack extends GdUnitTestSuite ## Unit tests for ImplantNavStack (#844, D-191, PR #131 item 5). ## Tests push/pop/replace/reset, signal emission, current state, and re-entrancy guard. ## All tests are synchronous — ImplantNavStack mutations are synchronous by design. # Untyped — class_name ImplantNavStack not yet registered at test-suite parse time. var _nav = null var _last_signal_id: String = "" var _signal_count: int = 0 func before_test() -> void: var NavStack := load("res://ui/implant/implant_nav_stack.gd") _nav = NavStack.new() add_child(_nav) _last_signal_id = "" _signal_count = 0 _nav.screen_changed.connect(_on_screen_changed) func after_test() -> void: _nav.queue_free() _nav = null func _on_screen_changed(id: String) -> void: _last_signal_id = id _signal_count += 1 # ============================================================================= # push / current / current_payload # ============================================================================= func test_push_sets_current() -> void: _nav.push("alpha") assert_that(_nav.current()).is_equal("alpha") func test_push_stacks() -> void: _nav.push("alpha") _nav.push("beta") assert_that(_nav.current()).is_equal("beta") func test_push_emits_screen_changed() -> void: _nav.push("alpha") assert_that(_last_signal_id).is_equal("alpha") assert_that(_signal_count).is_equal(1) func test_push_with_payload_accessible_via_current_payload() -> void: _nav.push("alpha", {"key": "val"}) assert_that(_nav.current_payload().get("key", "")).is_equal("val") func test_push_empty_payload_returns_empty_dict() -> void: _nav.push("alpha") assert_that(_nav.current_payload().is_empty()).is_true() func test_push_payload_does_not_bleed_to_next_push() -> void: _nav.push("alpha", {"key": "val"}) _nav.push("beta") assert_that(_nav.current_payload().is_empty()).is_true() # ============================================================================= # pop # ============================================================================= func test_pop_removes_top() -> void: _nav.push("alpha") _nav.push("beta") _nav.pop() assert_that(_nav.current()).is_equal("alpha") func test_pop_emits_screen_changed_to_previous() -> void: _nav.push("alpha") _nav.push("beta") _signal_count = 0 _nav.pop() assert_that(_last_signal_id).is_equal("alpha") assert_that(_signal_count).is_equal(1) func test_pop_to_empty_with_default_pushes_default() -> void: # Stack-floor behaviour: pop() never leaves the stack empty when a default # screen is set. Internally calls push(_default_screen_id). _nav.set_default("home") _nav.push("alpha") _nav.pop() assert_that(_nav.current()).is_equal("home") func test_pop_to_empty_without_default_empties_stack() -> void: _nav.push("alpha") _nav.pop() assert_that(_nav.is_empty()).is_true() func test_pop_on_empty_stack_does_not_crash() -> void: # Empty pop emits a warning and emits no signal. _nav.pop() assert_that(_signal_count).is_equal(0) assert_that(_nav.is_empty()).is_true() # ============================================================================= # replace # ============================================================================= func test_replace_swaps_top() -> void: _nav.push("alpha") _nav.replace("beta") assert_that(_nav.current()).is_equal("beta") func test_replace_does_not_grow_stack() -> void: _nav.push("alpha") _nav.replace("beta") # Stack has only "beta" — popping should empty it (no "alpha" below). _nav.pop() assert_that(_nav.is_empty()).is_true() func test_replace_emits_screen_changed() -> void: _nav.push("alpha") _signal_count = 0 _nav.replace("beta") assert_that(_last_signal_id).is_equal("beta") assert_that(_signal_count).is_equal(1) func test_replace_on_empty_stack_acts_as_push() -> void: _nav.replace("alpha") assert_that(_nav.current()).is_equal("alpha") func test_replace_updates_payload() -> void: _nav.push("alpha", {"step": 1}) _nav.replace("beta", {"step": 2}) assert_that(_nav.current_payload().get("step", 0)).is_equal(2) # ============================================================================= # reset_to_default # ============================================================================= func test_reset_to_default_clears_stack_and_pushes_default() -> void: _nav.set_default("home") _nav.push("alpha") _nav.push("beta") _nav.reset_to_default() assert_that(_nav.current()).is_equal("home") func test_reset_to_default_without_default_empties_stack() -> void: _nav.push("alpha") _nav.push("beta") _nav.reset_to_default() assert_that(_nav.is_empty()).is_true() # ============================================================================= # push_default # ============================================================================= func test_push_default_pushes_the_default_id() -> void: _nav.set_default("home") _nav.push_default() assert_that(_nav.current()).is_equal("home") func test_push_default_is_noop_if_no_default() -> void: _nav.push_default() assert_that(_nav.is_empty()).is_true() assert_that(_signal_count).is_equal(0) # ============================================================================= # is_empty # ============================================================================= func test_is_empty_true_at_start() -> void: assert_that(_nav.is_empty()).is_true() func test_is_empty_false_after_push() -> void: _nav.push("alpha") assert_that(_nav.is_empty()).is_false() func test_is_empty_true_after_pop_to_bottom() -> void: _nav.push("alpha") _nav.pop() assert_that(_nav.is_empty()).is_true() # ============================================================================= # Re-entrancy guard # ============================================================================= func test_nested_push_from_signal_handler_is_blocked() -> void: # Pushing from within a screen_changed handler must be rejected and must not # corrupt the stack. var nested_count := 0 _nav.screen_changed.connect( func(_id: String) -> void: nested_count += 1 if nested_count == 1: _nav.push("nested") # must be blocked by _mutating guard ) _nav.push("alpha") # "nested" must NOT have been pushed — stack top is "alpha". assert_that(_nav.current()).is_equal("alpha") func test_pop_internal_push_of_default_is_not_blocked() -> void: # pop() internally calls push(default) when the stack empties. This internal # call must NOT be blocked by the re-entrancy guard. _nav.set_default("home") _nav.push("alpha") _nav.pop() # pop alpha → empty → internal push("home") assert_that(_nav.current()).is_equal("home") func test_replace_from_signal_handler_is_blocked() -> void: var nested_count := 0 _nav.screen_changed.connect( func(_id: String) -> void: nested_count += 1 if nested_count == 1: _nav.replace("nested") # must be blocked ) _nav.push("alpha") assert_that(_nav.current()).is_equal("alpha")