diff --git a/src/game/engine/tests/unit/test_culture_web.gd b/src/game/engine/tests/unit/test_culture_web.gd new file mode 100644 index 00000000..f3d0716a --- /dev/null +++ b/src/game/engine/tests/unit/test_culture_web.gd @@ -0,0 +1,112 @@ +extends GutTest +## Verifies the `CultureWeb` GDScript wrapper drives `GdCultureWeb` correctly: +## load → graph queries → per-player gating → research-completion signal. + +const CultureWebScript: GDScript = preload( + "res://engine/src/modules/empire/culture_web.gd" +) +const PlayerScript: GDScript = preload("res://engine/src/entities/player.gd") + +const SAMPLE_TRADITIONS: Array = [ + { + "id": "ancestor_song", + "name": "Ancestor Song", + "pillar": "ancestor_worship", + "era": 1, "tier": 1, "cost": 10, + "requires": [], + "unlocks": {"buildings": ["shrine"], "units": [], "improvements": []}, + "flavor": "We sing them so they remember us.", + }, + { + "id": "rite_of_remembering", + "name": "Rite of Remembering", + "pillar": "ancestor_worship", + "era": 2, "tier": 2, "cost": 25, + "requires": ["ancestor_song"], + "unlocks": { + "buildings": [], "units": [], "improvements": [], + "mechanics": [ + {"key": "happiness_per_shrine", "label": "Happiness Per Shrine +1", "value": 1} + ] + }, + "flavor": "The dead are louder when we listen.", + }, +] + + +func before_each() -> void: + # Each test rebuilds CultureWeb from the in-test fixture; bypass DataLoader + # by stubbing the bridge directly through the wrapper's lazy build path. + pass + + +func _make_web() -> CultureWebScript: + var web: CultureWebScript = CultureWebScript.new() + # The wrapper normally calls DataLoader.get_all_culture(); for the test we + # instantiate the bridge ourselves and load the fixture JSON. + if not ClassDB.class_exists("GdCultureWeb"): + fail_test("GdCultureWeb GDExtension class not registered") + return null + var bridge: RefCounted = ClassDB.instantiate("GdCultureWeb") as RefCounted + var ok: bool = bool(bridge.call("load_from_json", JSON.stringify(SAMPLE_TRADITIONS))) + assert_true(ok, "GdCultureWeb.load_from_json should succeed") + # Inject the bridge into the wrapper's KnowledgeWeb for direct testing. + # The wrapper exposes `_web` which is a KnowledgeWeb; we set its `_bridge` + # field via duck-typed `set` so the rest of the API works as in production. + var inner: RefCounted = web._web as RefCounted + inner.set("_bridge", bridge) + inner.set("_node_count", SAMPLE_TRADITIONS.size()) + return web + + +func test_tradition_count_and_pillars() -> void: + var web: CultureWebScript = _make_web() + assert_eq(web.get_tradition_count(), 2) + var pillars: Array[String] = web.get_pillars() + assert_eq(pillars.size(), 1) + assert_eq(pillars[0], "ancestor_worship") + + +func test_prerequisite_gating() -> void: + var web: CultureWebScript = _make_web() + # Set up a player with no culture researched yet. + GameState.players = [PlayerScript.new(0, "Tester", "dwarf")] + GameState.current_player_index = 0 + + assert_true(web.can_research("ancestor_song", 0)) + assert_false(web.can_research("rite_of_remembering", 0), + "rite_of_remembering needs ancestor_song first") + + # Grant the prereq and re-check. + (GameState.players[0] as PlayerScript).add_tradition("ancestor_song") + assert_false(web.can_research("ancestor_song", 0), + "already-researched tradition should not be researchable again") + assert_true(web.can_research("rite_of_remembering", 0)) + + +func test_start_research_emits_signal() -> void: + var web: CultureWebScript = _make_web() + GameState.players = [PlayerScript.new(0, "Tester", "dwarf")] + GameState.current_player_index = 0 + + watch_signals(EventBus) + var ok: bool = web.start_research("ancestor_song", 0) + assert_true(ok) + assert_signal_emitted_with_parameters( + EventBus, "culture_research_started", ["ancestor_song", 0] + ) + var p: PlayerScript = GameState.players[0] as PlayerScript + assert_eq(p.researching_tradition, "ancestor_song") + assert_eq(p.culture_research_progress, 0) + + +func test_unlocks_typed_buckets_round_trip() -> void: + var web: CultureWebScript = _make_web() + var data: Dictionary = web.get_tradition_data("rite_of_remembering") + assert_false(data.is_empty(), "tradition data should round-trip") + var unlocks: Dictionary = data.get("unlocks", {}) + var mechanics: Array = unlocks.get("mechanics", []) as Array + assert_eq(mechanics.size(), 1) + var m: Dictionary = mechanics[0] as Dictionary + assert_eq(String(m.get("key", "")), "happiness_per_shrine") + assert_eq(int(m.get("value", 0)), 1)