feature updates
This commit is contained in:
56
tests/unit/test_ability_system.gd
Normal file
56
tests/unit/test_ability_system.gd
Normal file
@@ -0,0 +1,56 @@
|
||||
extends GutTest
|
||||
|
||||
## Tests for the AbilitySystem and related components
|
||||
|
||||
|
||||
func test_target_selector_instantiates() -> void:
|
||||
var selector = TargetSelector.new()
|
||||
assert_not_null(selector, "TargetSelector should instantiate")
|
||||
|
||||
|
||||
func test_trigger_matcher_instantiates() -> void:
|
||||
var matcher = TriggerMatcher.new()
|
||||
assert_not_null(matcher, "TriggerMatcher should instantiate")
|
||||
|
||||
|
||||
func test_effect_resolver_instantiates() -> void:
|
||||
var resolver = EffectResolver.new()
|
||||
assert_not_null(resolver, "EffectResolver should instantiate")
|
||||
|
||||
|
||||
func test_field_effect_manager_instantiates() -> void:
|
||||
var manager = FieldEffectManager.new()
|
||||
assert_not_null(manager, "FieldEffectManager should instantiate")
|
||||
|
||||
|
||||
func test_field_effect_manager_clear() -> void:
|
||||
var manager = FieldEffectManager.new()
|
||||
manager.clear_all()
|
||||
assert_eq(manager.get_active_ability_count(), 0, "Should have no active abilities after clear")
|
||||
|
||||
|
||||
func test_target_selector_empty_spec() -> void:
|
||||
var selector = TargetSelector.new()
|
||||
var result = selector.get_valid_targets({}, null, null)
|
||||
assert_eq(result.size(), 0, "Empty spec should return empty array")
|
||||
|
||||
|
||||
func test_trigger_matcher_event_matching() -> void:
|
||||
var matcher = TriggerMatcher.new()
|
||||
|
||||
# Test direct event match
|
||||
assert_true(matcher._event_matches("ATTACKS", "ATTACKS"), "Direct match should work")
|
||||
|
||||
# Test enters field variations
|
||||
assert_true(matcher._event_matches("ENTERS_FIELD", "CARD_PLAYED"), "ENTERS_FIELD should match CARD_PLAYED")
|
||||
|
||||
# Test leaves field variations
|
||||
assert_true(matcher._event_matches("LEAVES_FIELD", "FORWARD_BROKEN"), "LEAVES_FIELD should match FORWARD_BROKEN")
|
||||
|
||||
|
||||
func test_trigger_matcher_source_matching() -> void:
|
||||
var matcher = TriggerMatcher.new()
|
||||
|
||||
# ANY should always match
|
||||
var event_data = {"card": null}
|
||||
assert_true(matcher._source_matches("ANY", event_data, null, null), "ANY source should always match")
|
||||
86
tests/unit/test_attack_step_enum.gd
Normal file
86
tests/unit/test_attack_step_enum.gd
Normal file
@@ -0,0 +1,86 @@
|
||||
extends GutTest
|
||||
|
||||
## Unit tests to verify AttackStep enum alignment between client and server
|
||||
## Server uses: NONE=0, PREPARATION=1, DECLARATION=2, BLOCK_DECLARATION=3, DAMAGE_RESOLUTION=4
|
||||
|
||||
|
||||
## Test that AttackStep enum values match expected server values
|
||||
## Critical for network synchronization
|
||||
|
||||
func test_attack_step_none_equals_zero():
|
||||
assert_eq(Enums.AttackStep.NONE, 0, "AttackStep.NONE should be 0 to match server")
|
||||
|
||||
|
||||
func test_attack_step_preparation_equals_one():
|
||||
assert_eq(Enums.AttackStep.PREPARATION, 1, "AttackStep.PREPARATION should be 1 to match server")
|
||||
|
||||
|
||||
func test_attack_step_declaration_equals_two():
|
||||
assert_eq(Enums.AttackStep.DECLARATION, 2, "AttackStep.DECLARATION should be 2 to match server")
|
||||
|
||||
|
||||
func test_attack_step_block_declaration_equals_three():
|
||||
assert_eq(Enums.AttackStep.BLOCK_DECLARATION, 3, "AttackStep.BLOCK_DECLARATION should be 3 to match server")
|
||||
|
||||
|
||||
func test_attack_step_damage_resolution_equals_four():
|
||||
assert_eq(Enums.AttackStep.DAMAGE_RESOLUTION, 4, "AttackStep.DAMAGE_RESOLUTION should be 4 to match server")
|
||||
|
||||
|
||||
## Test enum ordering is correct
|
||||
func test_attack_step_enum_order():
|
||||
# Verify the enum values are in correct order
|
||||
assert_true(
|
||||
Enums.AttackStep.NONE < Enums.AttackStep.PREPARATION,
|
||||
"NONE should be less than PREPARATION"
|
||||
)
|
||||
assert_true(
|
||||
Enums.AttackStep.PREPARATION < Enums.AttackStep.DECLARATION,
|
||||
"PREPARATION should be less than DECLARATION"
|
||||
)
|
||||
assert_true(
|
||||
Enums.AttackStep.DECLARATION < Enums.AttackStep.BLOCK_DECLARATION,
|
||||
"DECLARATION should be less than BLOCK_DECLARATION"
|
||||
)
|
||||
assert_true(
|
||||
Enums.AttackStep.BLOCK_DECLARATION < Enums.AttackStep.DAMAGE_RESOLUTION,
|
||||
"BLOCK_DECLARATION should be less than DAMAGE_RESOLUTION"
|
||||
)
|
||||
|
||||
|
||||
## Test TurnPhase enum alignment with server as well
|
||||
## Server uses: ACTIVE=0, DRAW=1, MAIN_1=2, ATTACK=3, MAIN_2=4, END=5
|
||||
|
||||
func test_turn_phase_active_equals_zero():
|
||||
assert_eq(Enums.TurnPhase.ACTIVE, 0, "TurnPhase.ACTIVE should be 0 to match server")
|
||||
|
||||
|
||||
func test_turn_phase_draw_equals_one():
|
||||
assert_eq(Enums.TurnPhase.DRAW, 1, "TurnPhase.DRAW should be 1 to match server")
|
||||
|
||||
|
||||
func test_turn_phase_main_1_equals_two():
|
||||
assert_eq(Enums.TurnPhase.MAIN_1, 2, "TurnPhase.MAIN_1 should be 2 to match server")
|
||||
|
||||
|
||||
func test_turn_phase_attack_equals_three():
|
||||
assert_eq(Enums.TurnPhase.ATTACK, 3, "TurnPhase.ATTACK should be 3 to match server")
|
||||
|
||||
|
||||
func test_turn_phase_main_2_equals_four():
|
||||
assert_eq(Enums.TurnPhase.MAIN_2, 4, "TurnPhase.MAIN_2 should be 4 to match server")
|
||||
|
||||
|
||||
func test_turn_phase_end_equals_five():
|
||||
assert_eq(Enums.TurnPhase.END, 5, "TurnPhase.END should be 5 to match server")
|
||||
|
||||
|
||||
## Test that all enum values are present
|
||||
func test_attack_step_has_five_values():
|
||||
var values = Enums.AttackStep.values()
|
||||
assert_eq(values.size(), 5, "AttackStep should have 5 values (NONE, PREPARATION, DECLARATION, BLOCK_DECLARATION, DAMAGE_RESOLUTION)")
|
||||
|
||||
|
||||
func test_turn_phase_has_six_values():
|
||||
var values = Enums.TurnPhase.values()
|
||||
assert_eq(values.size(), 6, "TurnPhase should have 6 values (ACTIVE, DRAW, MAIN_1, ATTACK, MAIN_2, END)")
|
||||
491
tests/unit/test_card_filter.gd
Normal file
491
tests/unit/test_card_filter.gd
Normal file
@@ -0,0 +1,491 @@
|
||||
extends GutTest
|
||||
|
||||
## Tests for CardFilter utility class
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# FILTER MATCHING TESTS - ELEMENT
|
||||
# =============================================================================
|
||||
|
||||
func test_element_filter_matches_fire() -> void:
|
||||
var filter = {"element": "FIRE"}
|
||||
var card = _create_mock_forward_with_element(Enums.Element.FIRE)
|
||||
|
||||
assert_true(CardFilter.matches_filter(card, filter), "Fire card should match FIRE filter")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_element_filter_rejects_wrong_element() -> void:
|
||||
var filter = {"element": "FIRE"}
|
||||
var card = _create_mock_forward_with_element(Enums.Element.ICE)
|
||||
|
||||
assert_false(CardFilter.matches_filter(card, filter), "Ice card should not match FIRE filter")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_element_filter_case_insensitive() -> void:
|
||||
var filter = {"element": "fire"}
|
||||
var card = _create_mock_forward_with_element(Enums.Element.FIRE)
|
||||
|
||||
assert_true(CardFilter.matches_filter(card, filter), "Element filter should be case insensitive")
|
||||
card.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# FILTER MATCHING TESTS - JOB
|
||||
# =============================================================================
|
||||
|
||||
func test_job_filter_matches_samurai() -> void:
|
||||
var filter = {"job": "Samurai"}
|
||||
var card = _create_mock_forward_with_job("Samurai")
|
||||
|
||||
assert_true(CardFilter.matches_filter(card, filter), "Samurai should match Samurai filter")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_job_filter_rejects_wrong_job() -> void:
|
||||
var filter = {"job": "Samurai"}
|
||||
var card = _create_mock_forward_with_job("Warrior")
|
||||
|
||||
assert_false(CardFilter.matches_filter(card, filter), "Warrior should not match Samurai filter")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_job_filter_case_insensitive() -> void:
|
||||
var filter = {"job": "SAMURAI"}
|
||||
var card = _create_mock_forward_with_job("samurai")
|
||||
|
||||
assert_true(CardFilter.matches_filter(card, filter), "Job filter should be case insensitive")
|
||||
card.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# FILTER MATCHING TESTS - CATEGORY
|
||||
# =============================================================================
|
||||
|
||||
func test_category_filter_matches() -> void:
|
||||
var filter = {"category": "VII"}
|
||||
var card = _create_mock_forward_with_category("VII")
|
||||
|
||||
assert_true(CardFilter.matches_filter(card, filter), "VII card should match VII filter")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_category_filter_rejects_wrong_category() -> void:
|
||||
var filter = {"category": "VII"}
|
||||
var card = _create_mock_forward_with_category("X")
|
||||
|
||||
assert_false(CardFilter.matches_filter(card, filter), "Category X should not match VII filter")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_category_filter_matches_partial() -> void:
|
||||
var filter = {"category": "FF"}
|
||||
var card = _create_mock_forward_with_category("FFVII")
|
||||
|
||||
assert_true(CardFilter.matches_filter(card, filter), "FFVII should match FF filter (partial)")
|
||||
card.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# FILTER MATCHING TESTS - COST
|
||||
# =============================================================================
|
||||
|
||||
func test_cost_exact_filter_matches() -> void:
|
||||
var filter = {"cost": 3}
|
||||
var card = _create_mock_forward_with_cost(3)
|
||||
|
||||
assert_true(CardFilter.matches_filter(card, filter), "Cost 3 should match exact cost 3")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_cost_exact_filter_rejects_wrong_cost() -> void:
|
||||
var filter = {"cost": 3}
|
||||
var card = _create_mock_forward_with_cost(5)
|
||||
|
||||
assert_false(CardFilter.matches_filter(card, filter), "Cost 5 should not match exact cost 3")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_cost_comparison_lte() -> void:
|
||||
var filter = {"cost_comparison": "LTE", "cost_value": 4}
|
||||
var card3 = _create_mock_forward_with_cost(3)
|
||||
var card4 = _create_mock_forward_with_cost(4)
|
||||
var card5 = _create_mock_forward_with_cost(5)
|
||||
|
||||
assert_true(CardFilter.matches_filter(card3, filter), "Cost 3 should match LTE 4")
|
||||
assert_true(CardFilter.matches_filter(card4, filter), "Cost 4 should match LTE 4")
|
||||
assert_false(CardFilter.matches_filter(card5, filter), "Cost 5 should not match LTE 4")
|
||||
|
||||
card3.free()
|
||||
card4.free()
|
||||
card5.free()
|
||||
|
||||
|
||||
func test_cost_comparison_gte() -> void:
|
||||
var filter = {"cost_comparison": "GTE", "cost_value": 4}
|
||||
var card3 = _create_mock_forward_with_cost(3)
|
||||
var card4 = _create_mock_forward_with_cost(4)
|
||||
var card5 = _create_mock_forward_with_cost(5)
|
||||
|
||||
assert_false(CardFilter.matches_filter(card3, filter), "Cost 3 should not match GTE 4")
|
||||
assert_true(CardFilter.matches_filter(card4, filter), "Cost 4 should match GTE 4")
|
||||
assert_true(CardFilter.matches_filter(card5, filter), "Cost 5 should match GTE 4")
|
||||
|
||||
card3.free()
|
||||
card4.free()
|
||||
card5.free()
|
||||
|
||||
|
||||
func test_cost_min_max_filters() -> void:
|
||||
var filter = {"cost_min": 3, "cost_max": 5}
|
||||
var card2 = _create_mock_forward_with_cost(2)
|
||||
var card3 = _create_mock_forward_with_cost(3)
|
||||
var card4 = _create_mock_forward_with_cost(4)
|
||||
var card5 = _create_mock_forward_with_cost(5)
|
||||
var card6 = _create_mock_forward_with_cost(6)
|
||||
|
||||
assert_false(CardFilter.matches_filter(card2, filter), "Cost 2 should not be in range 3-5")
|
||||
assert_true(CardFilter.matches_filter(card3, filter), "Cost 3 should be in range 3-5")
|
||||
assert_true(CardFilter.matches_filter(card4, filter), "Cost 4 should be in range 3-5")
|
||||
assert_true(CardFilter.matches_filter(card5, filter), "Cost 5 should be in range 3-5")
|
||||
assert_false(CardFilter.matches_filter(card6, filter), "Cost 6 should not be in range 3-5")
|
||||
|
||||
card2.free()
|
||||
card3.free()
|
||||
card4.free()
|
||||
card5.free()
|
||||
card6.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# FILTER MATCHING TESTS - CARD TYPE
|
||||
# =============================================================================
|
||||
|
||||
func test_card_type_forward_matches() -> void:
|
||||
var filter = {"card_type": "FORWARD"}
|
||||
var forward = _create_mock_forward()
|
||||
var backup = _create_mock_backup()
|
||||
|
||||
assert_true(CardFilter.matches_filter(forward, filter), "Forward should match FORWARD filter")
|
||||
assert_false(CardFilter.matches_filter(backup, filter), "Backup should not match FORWARD filter")
|
||||
|
||||
forward.free()
|
||||
backup.free()
|
||||
|
||||
|
||||
func test_card_type_backup_matches() -> void:
|
||||
var filter = {"card_type": "BACKUP"}
|
||||
var forward = _create_mock_forward()
|
||||
var backup = _create_mock_backup()
|
||||
|
||||
assert_false(CardFilter.matches_filter(forward, filter), "Forward should not match BACKUP filter")
|
||||
assert_true(CardFilter.matches_filter(backup, filter), "Backup should match BACKUP filter")
|
||||
|
||||
forward.free()
|
||||
backup.free()
|
||||
|
||||
|
||||
func test_card_type_character_matches_both() -> void:
|
||||
var filter = {"card_type": "CHARACTER"}
|
||||
var forward = _create_mock_forward()
|
||||
var backup = _create_mock_backup()
|
||||
|
||||
assert_true(CardFilter.matches_filter(forward, filter), "Forward should match CHARACTER filter")
|
||||
assert_true(CardFilter.matches_filter(backup, filter), "Backup should match CHARACTER filter")
|
||||
|
||||
forward.free()
|
||||
backup.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# FILTER MATCHING TESTS - NAME
|
||||
# =============================================================================
|
||||
|
||||
func test_card_name_filter_matches() -> void:
|
||||
var filter = {"card_name": "Cloud"}
|
||||
var card = _create_mock_forward_with_name("Cloud")
|
||||
|
||||
assert_true(CardFilter.matches_filter(card, filter), "Cloud should match Cloud filter")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_card_name_filter_rejects_wrong_name() -> void:
|
||||
var filter = {"card_name": "Cloud"}
|
||||
var card = _create_mock_forward_with_name("Sephiroth")
|
||||
|
||||
assert_false(CardFilter.matches_filter(card, filter), "Sephiroth should not match Cloud filter")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_name_filter_alternative_key() -> void:
|
||||
var filter = {"name": "Cloud"}
|
||||
var card = _create_mock_forward_with_name("Cloud")
|
||||
|
||||
assert_true(CardFilter.matches_filter(card, filter), "Should support 'name' as well as 'card_name'")
|
||||
card.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# FILTER MATCHING TESTS - POWER
|
||||
# =============================================================================
|
||||
|
||||
func test_power_comparison_gte() -> void:
|
||||
var filter = {"power_comparison": "GTE", "power_value": 8000}
|
||||
var card7000 = _create_mock_forward_with_power(7000)
|
||||
var card8000 = _create_mock_forward_with_power(8000)
|
||||
var card9000 = _create_mock_forward_with_power(9000)
|
||||
|
||||
assert_false(CardFilter.matches_filter(card7000, filter), "Power 7000 should not match GTE 8000")
|
||||
assert_true(CardFilter.matches_filter(card8000, filter), "Power 8000 should match GTE 8000")
|
||||
assert_true(CardFilter.matches_filter(card9000, filter), "Power 9000 should match GTE 8000")
|
||||
|
||||
card7000.free()
|
||||
card8000.free()
|
||||
card9000.free()
|
||||
|
||||
|
||||
func test_power_min_max_filters() -> void:
|
||||
var filter = {"power_min": 5000, "power_max": 7000}
|
||||
var card4000 = _create_mock_forward_with_power(4000)
|
||||
var card6000 = _create_mock_forward_with_power(6000)
|
||||
var card8000 = _create_mock_forward_with_power(8000)
|
||||
|
||||
assert_false(CardFilter.matches_filter(card4000, filter), "Power 4000 should not be in range 5000-7000")
|
||||
assert_true(CardFilter.matches_filter(card6000, filter), "Power 6000 should be in range 5000-7000")
|
||||
assert_false(CardFilter.matches_filter(card8000, filter), "Power 8000 should not be in range 5000-7000")
|
||||
|
||||
card4000.free()
|
||||
card6000.free()
|
||||
card8000.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# EMPTY/NULL FILTER TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_empty_filter_matches_all() -> void:
|
||||
var filter = {}
|
||||
var card = _create_mock_forward()
|
||||
|
||||
assert_true(CardFilter.matches_filter(card, filter), "Empty filter should match any card")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_null_card_returns_false() -> void:
|
||||
var filter = {"element": "FIRE"}
|
||||
|
||||
assert_false(CardFilter.matches_filter(null, filter), "Null card should return false")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# COMBINED FILTER TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_combined_job_and_cost_filter() -> void:
|
||||
var filter = {"job": "Warrior", "cost_comparison": "LTE", "cost_value": 4}
|
||||
|
||||
var cheap_warrior = _create_mock_forward_with_job_and_cost("Warrior", 3)
|
||||
var expensive_warrior = _create_mock_forward_with_job_and_cost("Warrior", 5)
|
||||
var cheap_mage = _create_mock_forward_with_job_and_cost("Mage", 3)
|
||||
|
||||
assert_true(CardFilter.matches_filter(cheap_warrior, filter), "Cheap Warrior should match")
|
||||
assert_false(CardFilter.matches_filter(expensive_warrior, filter), "Expensive Warrior should not match")
|
||||
assert_false(CardFilter.matches_filter(cheap_mage, filter), "Cheap Mage should not match")
|
||||
|
||||
cheap_warrior.free()
|
||||
expensive_warrior.free()
|
||||
cheap_mage.free()
|
||||
|
||||
|
||||
func test_combined_element_and_type_filter() -> void:
|
||||
var filter = {"element": "FIRE", "card_type": "FORWARD"}
|
||||
|
||||
var fire_forward = _create_mock_forward_with_element(Enums.Element.FIRE)
|
||||
var ice_forward = _create_mock_forward_with_element(Enums.Element.ICE)
|
||||
var fire_backup = _create_mock_backup_with_element(Enums.Element.FIRE)
|
||||
|
||||
assert_true(CardFilter.matches_filter(fire_forward, filter), "Fire Forward should match")
|
||||
assert_false(CardFilter.matches_filter(ice_forward, filter), "Ice Forward should not match (wrong element)")
|
||||
assert_false(CardFilter.matches_filter(fire_backup, filter), "Fire Backup should not match (wrong type)")
|
||||
|
||||
fire_forward.free()
|
||||
ice_forward.free()
|
||||
fire_backup.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# EXCLUDE SELF TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_exclude_self_filter() -> void:
|
||||
var filter = {"card_type": "FORWARD", "exclude_self": true}
|
||||
var source = _create_mock_forward()
|
||||
var other = _create_mock_forward()
|
||||
|
||||
assert_false(CardFilter.matches_filter(source, filter, source), "Source should be excluded")
|
||||
assert_true(CardFilter.matches_filter(other, filter, source), "Other card should not be excluded")
|
||||
|
||||
source.free()
|
||||
other.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# COLLECTION METHODS TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_count_matching() -> void:
|
||||
var filter = {"element": "FIRE"}
|
||||
var cards = [
|
||||
_create_mock_forward_with_element(Enums.Element.FIRE),
|
||||
_create_mock_forward_with_element(Enums.Element.ICE),
|
||||
_create_mock_forward_with_element(Enums.Element.FIRE),
|
||||
_create_mock_forward_with_element(Enums.Element.WATER)
|
||||
]
|
||||
|
||||
assert_eq(CardFilter.count_matching(cards, filter), 2, "Should count 2 Fire cards")
|
||||
|
||||
for card in cards:
|
||||
card.free()
|
||||
|
||||
|
||||
func test_get_matching() -> void:
|
||||
var filter = {"element": "FIRE"}
|
||||
var cards = [
|
||||
_create_mock_forward_with_element(Enums.Element.FIRE),
|
||||
_create_mock_forward_with_element(Enums.Element.ICE),
|
||||
_create_mock_forward_with_element(Enums.Element.FIRE)
|
||||
]
|
||||
|
||||
var matching = CardFilter.get_matching(cards, filter)
|
||||
assert_eq(matching.size(), 2, "Should get 2 Fire cards")
|
||||
|
||||
for card in cards:
|
||||
card.free()
|
||||
|
||||
|
||||
func test_get_highest_power() -> void:
|
||||
var cards = [
|
||||
_create_mock_forward_with_power(5000),
|
||||
_create_mock_forward_with_power(9000),
|
||||
_create_mock_forward_with_power(7000)
|
||||
]
|
||||
|
||||
assert_eq(CardFilter.get_highest_power(cards), 9000, "Should return highest power 9000")
|
||||
|
||||
for card in cards:
|
||||
card.free()
|
||||
|
||||
|
||||
func test_get_highest_power_with_filter() -> void:
|
||||
var filter = {"element": "FIRE"}
|
||||
var fire1 = _create_mock_forward_with_element(Enums.Element.FIRE)
|
||||
fire1.card_data.power = 5000
|
||||
var fire2 = _create_mock_forward_with_element(Enums.Element.FIRE)
|
||||
fire2.card_data.power = 8000
|
||||
var ice = _create_mock_forward_with_element(Enums.Element.ICE)
|
||||
ice.card_data.power = 10000
|
||||
|
||||
var cards = [fire1, fire2, ice]
|
||||
|
||||
assert_eq(CardFilter.get_highest_power(cards, filter), 8000, "Should return highest Fire power 8000")
|
||||
|
||||
for card in cards:
|
||||
card.free()
|
||||
|
||||
|
||||
func test_get_lowest_power() -> void:
|
||||
var cards = [
|
||||
_create_mock_forward_with_power(5000),
|
||||
_create_mock_forward_with_power(3000),
|
||||
_create_mock_forward_with_power(7000)
|
||||
]
|
||||
|
||||
assert_eq(CardFilter.get_lowest_power(cards), 3000, "Should return lowest power 3000")
|
||||
|
||||
for card in cards:
|
||||
card.free()
|
||||
|
||||
|
||||
func test_get_lowest_power_empty_array() -> void:
|
||||
var cards: Array = []
|
||||
|
||||
assert_eq(CardFilter.get_lowest_power(cards), 0, "Should return 0 for empty array")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
func _create_mock_forward() -> CardInstance:
|
||||
var card = CardInstance.new()
|
||||
var data = CardDatabase.CardData.new()
|
||||
data.type = Enums.CardType.FORWARD
|
||||
data.cost = 3
|
||||
data.power = 7000
|
||||
data.name = "Test Forward"
|
||||
data.job = "Warrior"
|
||||
data.elements = [Enums.Element.FIRE]
|
||||
card.card_data = data
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_backup() -> CardInstance:
|
||||
var card = CardInstance.new()
|
||||
var data = CardDatabase.CardData.new()
|
||||
data.type = Enums.CardType.BACKUP
|
||||
data.cost = 2
|
||||
data.name = "Test Backup"
|
||||
data.job = "Scholar"
|
||||
data.elements = [Enums.Element.WATER]
|
||||
card.card_data = data
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_forward_with_element(element: Enums.Element) -> CardInstance:
|
||||
var card = _create_mock_forward()
|
||||
card.card_data.elements = [element]
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_backup_with_element(element: Enums.Element) -> CardInstance:
|
||||
var card = _create_mock_backup()
|
||||
card.card_data.elements = [element]
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_forward_with_job(job: String) -> CardInstance:
|
||||
var card = _create_mock_forward()
|
||||
card.card_data.job = job
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_forward_with_category(category: String) -> CardInstance:
|
||||
var card = _create_mock_forward()
|
||||
card.card_data.category = category
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_forward_with_cost(cost: int) -> CardInstance:
|
||||
var card = _create_mock_forward()
|
||||
card.card_data.cost = cost
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_forward_with_name(card_name: String) -> CardInstance:
|
||||
var card = _create_mock_forward()
|
||||
card.card_data.name = card_name
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_forward_with_power(power: int) -> CardInstance:
|
||||
var card = _create_mock_forward()
|
||||
card.card_data.power = power
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_forward_with_job_and_cost(job: String, cost: int) -> CardInstance:
|
||||
var card = _create_mock_forward()
|
||||
card.card_data.job = job
|
||||
card.card_data.cost = cost
|
||||
return card
|
||||
420
tests/unit/test_condition_checker.gd
Normal file
420
tests/unit/test_condition_checker.gd
Normal file
@@ -0,0 +1,420 @@
|
||||
extends GutTest
|
||||
|
||||
## Tests for the ConditionChecker class
|
||||
## Tests various condition types and logical operators
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# SETUP
|
||||
# =============================================================================
|
||||
|
||||
var checker: ConditionChecker
|
||||
|
||||
|
||||
func before_each() -> void:
|
||||
checker = ConditionChecker.new()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# BASIC TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_checker_instantiates() -> void:
|
||||
assert_not_null(checker, "ConditionChecker should instantiate")
|
||||
|
||||
|
||||
func test_empty_condition_returns_true() -> void:
|
||||
var result = checker.evaluate({}, {})
|
||||
assert_true(result, "Empty condition should return true (unconditional)")
|
||||
|
||||
|
||||
func test_unknown_condition_type_returns_false() -> void:
|
||||
var condition = {"type": "NONEXISTENT_TYPE"}
|
||||
var result = checker.evaluate(condition, {})
|
||||
assert_false(result, "Unknown condition type should return false")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# COMPARISON HELPER TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_compare_eq() -> void:
|
||||
assert_true(checker._compare(5, "EQ", 5), "5 == 5")
|
||||
assert_false(checker._compare(5, "EQ", 3), "5 != 3")
|
||||
|
||||
|
||||
func test_compare_neq() -> void:
|
||||
assert_true(checker._compare(5, "NEQ", 3), "5 != 3")
|
||||
assert_false(checker._compare(5, "NEQ", 5), "5 == 5")
|
||||
|
||||
|
||||
func test_compare_gt() -> void:
|
||||
assert_true(checker._compare(5, "GT", 3), "5 > 3")
|
||||
assert_false(checker._compare(5, "GT", 5), "5 not > 5")
|
||||
assert_false(checker._compare(3, "GT", 5), "3 not > 5")
|
||||
|
||||
|
||||
func test_compare_gte() -> void:
|
||||
assert_true(checker._compare(5, "GTE", 3), "5 >= 3")
|
||||
assert_true(checker._compare(5, "GTE", 5), "5 >= 5")
|
||||
assert_false(checker._compare(3, "GTE", 5), "3 not >= 5")
|
||||
|
||||
|
||||
func test_compare_lt() -> void:
|
||||
assert_true(checker._compare(3, "LT", 5), "3 < 5")
|
||||
assert_false(checker._compare(5, "LT", 5), "5 not < 5")
|
||||
assert_false(checker._compare(5, "LT", 3), "5 not < 3")
|
||||
|
||||
|
||||
func test_compare_lte() -> void:
|
||||
assert_true(checker._compare(3, "LTE", 5), "3 <= 5")
|
||||
assert_true(checker._compare(5, "LTE", 5), "5 <= 5")
|
||||
assert_false(checker._compare(5, "LTE", 3), "5 not <= 3")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# LOGICAL OPERATOR TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_and_with_all_true() -> void:
|
||||
var condition = {
|
||||
"type": "AND",
|
||||
"conditions": [
|
||||
{"type": "DAMAGE_RECEIVED", "comparison": "GTE", "value": 3},
|
||||
{"type": "DAMAGE_RECEIVED", "comparison": "LTE", "value": 5}
|
||||
]
|
||||
}
|
||||
|
||||
var context = _create_mock_context_with_damage(4)
|
||||
var result = checker.evaluate(condition, context)
|
||||
assert_true(result, "AND with all true conditions should return true")
|
||||
|
||||
|
||||
func test_and_with_one_false() -> void:
|
||||
var condition = {
|
||||
"type": "AND",
|
||||
"conditions": [
|
||||
{"type": "DAMAGE_RECEIVED", "comparison": "GTE", "value": 3},
|
||||
{"type": "DAMAGE_RECEIVED", "comparison": "LTE", "value": 2}
|
||||
]
|
||||
}
|
||||
|
||||
var context = _create_mock_context_with_damage(4)
|
||||
var result = checker.evaluate(condition, context)
|
||||
assert_false(result, "AND with one false condition should return false")
|
||||
|
||||
|
||||
func test_or_with_one_true() -> void:
|
||||
var condition = {
|
||||
"type": "OR",
|
||||
"conditions": [
|
||||
{"type": "DAMAGE_RECEIVED", "comparison": "GTE", "value": 10},
|
||||
{"type": "DAMAGE_RECEIVED", "comparison": "GTE", "value": 3}
|
||||
]
|
||||
}
|
||||
|
||||
var context = _create_mock_context_with_damage(5)
|
||||
var result = checker.evaluate(condition, context)
|
||||
assert_true(result, "OR with one true condition should return true")
|
||||
|
||||
|
||||
func test_or_with_all_false() -> void:
|
||||
var condition = {
|
||||
"type": "OR",
|
||||
"conditions": [
|
||||
{"type": "DAMAGE_RECEIVED", "comparison": "GTE", "value": 10},
|
||||
{"type": "DAMAGE_RECEIVED", "comparison": "EQ", "value": 0}
|
||||
]
|
||||
}
|
||||
|
||||
var context = _create_mock_context_with_damage(5)
|
||||
var result = checker.evaluate(condition, context)
|
||||
assert_false(result, "OR with all false conditions should return false")
|
||||
|
||||
|
||||
func test_not_inverts_true() -> void:
|
||||
var condition = {
|
||||
"type": "NOT",
|
||||
"condition": {"type": "DAMAGE_RECEIVED", "comparison": "GTE", "value": 10}
|
||||
}
|
||||
|
||||
var context = _create_mock_context_with_damage(5)
|
||||
var result = checker.evaluate(condition, context)
|
||||
assert_true(result, "NOT should invert false to true")
|
||||
|
||||
|
||||
func test_not_inverts_false() -> void:
|
||||
var condition = {
|
||||
"type": "NOT",
|
||||
"condition": {"type": "DAMAGE_RECEIVED", "comparison": "GTE", "value": 3}
|
||||
}
|
||||
|
||||
var context = _create_mock_context_with_damage(5)
|
||||
var result = checker.evaluate(condition, context)
|
||||
assert_false(result, "NOT should invert true to false")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# DAMAGE_RECEIVED CONDITION TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_damage_received_gte() -> void:
|
||||
var condition = {
|
||||
"type": "DAMAGE_RECEIVED",
|
||||
"comparison": "GTE",
|
||||
"value": 5
|
||||
}
|
||||
|
||||
# Not enough damage
|
||||
var context = _create_mock_context_with_damage(3)
|
||||
assert_false(checker.evaluate(condition, context), "3 damage not >= 5")
|
||||
|
||||
# Exactly enough
|
||||
context = _create_mock_context_with_damage(5)
|
||||
assert_true(checker.evaluate(condition, context), "5 damage >= 5")
|
||||
|
||||
# More than enough
|
||||
context = _create_mock_context_with_damage(7)
|
||||
assert_true(checker.evaluate(condition, context), "7 damage >= 5")
|
||||
|
||||
|
||||
func test_damage_received_no_game_state() -> void:
|
||||
var condition = {
|
||||
"type": "DAMAGE_RECEIVED",
|
||||
"comparison": "GTE",
|
||||
"value": 5
|
||||
}
|
||||
|
||||
var context = {"game_state": null, "player_id": 0}
|
||||
var result = checker.evaluate(condition, context)
|
||||
assert_false(result, "Should return false without game state")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# FORWARD STATE TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_forward_state_dull() -> void:
|
||||
var condition = {
|
||||
"type": "FORWARD_STATE",
|
||||
"state": "DULL",
|
||||
"check_self": false
|
||||
}
|
||||
|
||||
# Create mock card
|
||||
var card = _create_mock_forward()
|
||||
card.is_dull = true
|
||||
|
||||
var context = {"target_card": card}
|
||||
assert_true(checker.evaluate(condition, context), "Dull card should match DULL state")
|
||||
|
||||
card.is_dull = false
|
||||
assert_false(checker.evaluate(condition, context), "Active card should not match DULL state")
|
||||
|
||||
card.free()
|
||||
|
||||
|
||||
func test_forward_state_active() -> void:
|
||||
var condition = {
|
||||
"type": "FORWARD_STATE",
|
||||
"state": "ACTIVE",
|
||||
"check_self": false
|
||||
}
|
||||
|
||||
var card = _create_mock_forward()
|
||||
card.is_dull = false
|
||||
|
||||
var context = {"target_card": card}
|
||||
assert_true(checker.evaluate(condition, context), "Active card should match ACTIVE state")
|
||||
|
||||
card.is_dull = true
|
||||
assert_false(checker.evaluate(condition, context), "Dull card should not match ACTIVE state")
|
||||
|
||||
card.free()
|
||||
|
||||
|
||||
func test_forward_state_check_self() -> void:
|
||||
var condition = {
|
||||
"type": "FORWARD_STATE",
|
||||
"state": "DULL",
|
||||
"check_self": true
|
||||
}
|
||||
|
||||
var source_card = _create_mock_forward()
|
||||
source_card.is_dull = true
|
||||
|
||||
var context = {"source_card": source_card, "target_card": null}
|
||||
assert_true(checker.evaluate(condition, context), "check_self=true should use source_card")
|
||||
|
||||
source_card.free()
|
||||
|
||||
|
||||
func test_forward_state_no_card() -> void:
|
||||
var condition = {
|
||||
"type": "FORWARD_STATE",
|
||||
"state": "DULL",
|
||||
"check_self": false
|
||||
}
|
||||
|
||||
var context = {"target_card": null}
|
||||
assert_false(checker.evaluate(condition, context), "Should return false without target card")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# COST COMPARISON TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_cost_comparison_lte() -> void:
|
||||
var condition = {
|
||||
"type": "COST_COMPARISON",
|
||||
"comparison": "LTE",
|
||||
"value": 3
|
||||
}
|
||||
|
||||
var card = _create_mock_forward_with_cost(2)
|
||||
var context = {"target_card": card}
|
||||
assert_true(checker.evaluate(condition, context), "Cost 2 <= 3")
|
||||
|
||||
card.card_data.cost = 3
|
||||
assert_true(checker.evaluate(condition, context), "Cost 3 <= 3")
|
||||
|
||||
card.card_data.cost = 5
|
||||
assert_false(checker.evaluate(condition, context), "Cost 5 not <= 3")
|
||||
|
||||
card.free()
|
||||
|
||||
|
||||
func test_cost_comparison_gte() -> void:
|
||||
var condition = {
|
||||
"type": "COST_COMPARISON",
|
||||
"comparison": "GTE",
|
||||
"value": 4
|
||||
}
|
||||
|
||||
var card = _create_mock_forward_with_cost(5)
|
||||
var context = {"target_card": card}
|
||||
assert_true(checker.evaluate(condition, context), "Cost 5 >= 4")
|
||||
|
||||
card.card_data.cost = 4
|
||||
assert_true(checker.evaluate(condition, context), "Cost 4 >= 4")
|
||||
|
||||
card.card_data.cost = 3
|
||||
assert_false(checker.evaluate(condition, context), "Cost 3 not >= 4")
|
||||
|
||||
card.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# POWER COMPARISON TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_power_comparison_lt_value() -> void:
|
||||
var condition = {
|
||||
"type": "POWER_COMPARISON",
|
||||
"comparison": "LT",
|
||||
"value": 8000
|
||||
}
|
||||
|
||||
var card = _create_mock_forward_with_power(5000)
|
||||
var context = {"target_card": card}
|
||||
assert_true(checker.evaluate(condition, context), "Power 5000 < 8000")
|
||||
|
||||
card.current_power = 8000
|
||||
assert_false(checker.evaluate(condition, context), "Power 8000 not < 8000")
|
||||
|
||||
card.free()
|
||||
|
||||
|
||||
func test_power_comparison_self_power() -> void:
|
||||
var condition = {
|
||||
"type": "POWER_COMPARISON",
|
||||
"comparison": "LT",
|
||||
"compare_to": "SELF_POWER"
|
||||
}
|
||||
|
||||
var attacker = _create_mock_forward_with_power(8000)
|
||||
var blocker = _create_mock_forward_with_power(5000)
|
||||
|
||||
var context = {
|
||||
"source_card": attacker,
|
||||
"target_card": blocker
|
||||
}
|
||||
|
||||
assert_true(checker.evaluate(condition, context), "Blocker 5000 < Attacker 8000")
|
||||
|
||||
blocker.current_power = 9000
|
||||
assert_false(checker.evaluate(condition, context), "Blocker 9000 not < Attacker 8000")
|
||||
|
||||
attacker.free()
|
||||
blocker.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
func _create_mock_context_with_damage(damage: int) -> Dictionary:
|
||||
var mock_game_state = MockGameState.new(damage)
|
||||
return {
|
||||
"game_state": mock_game_state,
|
||||
"player_id": 0
|
||||
}
|
||||
|
||||
|
||||
func _create_mock_forward() -> CardInstance:
|
||||
var card = CardInstance.new()
|
||||
var data = CardDatabase.CardData.new()
|
||||
data.type = Enums.CardType.FORWARD
|
||||
data.cost = 3
|
||||
data.power = 7000
|
||||
card.card_data = data
|
||||
card.is_dull = false
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_forward_with_cost(cost: int) -> CardInstance:
|
||||
var card = _create_mock_forward()
|
||||
card.card_data.cost = cost
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_forward_with_power(power: int) -> CardInstance:
|
||||
var card = _create_mock_forward()
|
||||
card.card_data.power = power
|
||||
card.current_power = power
|
||||
return card
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# MOCK CLASSES
|
||||
# =============================================================================
|
||||
|
||||
class MockGameState:
|
||||
var players: Array
|
||||
|
||||
func _init(player_damage: int = 0):
|
||||
players = [MockPlayer.new(player_damage), MockPlayer.new(0)]
|
||||
|
||||
func get_player_damage(player_id: int) -> int:
|
||||
if player_id < players.size():
|
||||
return players[player_id].damage
|
||||
return 0
|
||||
|
||||
func get_field_cards(player_id: int) -> Array:
|
||||
if player_id < players.size():
|
||||
return players[player_id].field
|
||||
return []
|
||||
|
||||
func get_break_zone(player_id: int) -> Array:
|
||||
if player_id < players.size():
|
||||
return players[player_id].break_zone
|
||||
return []
|
||||
|
||||
|
||||
class MockPlayer:
|
||||
var damage: int = 0
|
||||
var field: Array = []
|
||||
var break_zone: Array = []
|
||||
|
||||
func _init(p_damage: int = 0):
|
||||
damage = p_damage
|
||||
348
tests/unit/test_cost_validation.gd
Normal file
348
tests/unit/test_cost_validation.gd
Normal file
@@ -0,0 +1,348 @@
|
||||
extends GutTest
|
||||
|
||||
## Tests for ability cost validation in AbilitySystem
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CP COST VALIDATION TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_validate_empty_cost_always_valid() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var cost = {}
|
||||
var player = MockPlayer.new()
|
||||
|
||||
var result = ability_system._validate_ability_cost(cost, null, player)
|
||||
|
||||
assert_true(result.valid, "Empty cost should always be valid")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_validate_cp_cost_with_sufficient_cp() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var cost = {"cp": 3}
|
||||
var player = MockPlayer.new()
|
||||
player.cp_pool.add_cp(Enums.Element.FIRE, 5)
|
||||
|
||||
var result = ability_system._validate_ability_cost(cost, null, player)
|
||||
|
||||
assert_true(result.valid, "Should be valid with sufficient CP")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_validate_cp_cost_with_insufficient_cp() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var cost = {"cp": 5}
|
||||
var player = MockPlayer.new()
|
||||
player.cp_pool.add_cp(Enums.Element.FIRE, 3)
|
||||
|
||||
var result = ability_system._validate_ability_cost(cost, null, player)
|
||||
|
||||
assert_false(result.valid, "Should be invalid with insufficient CP")
|
||||
assert_true("Not enough CP" in result.reason, "Reason should mention CP")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_validate_specific_element_cp_cost_valid() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var cost = {"cp": 2, "element": "FIRE"}
|
||||
var player = MockPlayer.new()
|
||||
player.cp_pool.add_cp(Enums.Element.FIRE, 3)
|
||||
|
||||
var result = ability_system._validate_ability_cost(cost, null, player)
|
||||
|
||||
assert_true(result.valid, "Should be valid with sufficient Fire CP")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_validate_specific_element_cp_cost_invalid() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var cost = {"cp": 2, "element": "FIRE"}
|
||||
var player = MockPlayer.new()
|
||||
player.cp_pool.add_cp(Enums.Element.ICE, 5) # Wrong element
|
||||
|
||||
var result = ability_system._validate_ability_cost(cost, null, player)
|
||||
|
||||
assert_false(result.valid, "Should be invalid with wrong element CP")
|
||||
assert_true("FIRE CP" in result.reason, "Reason should mention Fire")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_validate_any_element_cp_cost() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var cost = {"cp": 4, "element": "ANY"}
|
||||
var player = MockPlayer.new()
|
||||
player.cp_pool.add_cp(Enums.Element.FIRE, 2)
|
||||
player.cp_pool.add_cp(Enums.Element.ICE, 2)
|
||||
|
||||
var result = ability_system._validate_ability_cost(cost, null, player)
|
||||
|
||||
assert_true(result.valid, "Should be valid with total CP from multiple elements")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# DISCARD COST VALIDATION TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_validate_discard_cost_with_sufficient_cards() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var cost = {"discard": 2}
|
||||
var player = MockPlayer.new()
|
||||
# Add 3 cards to hand
|
||||
for i in range(3):
|
||||
var card = _create_mock_card()
|
||||
player.hand.add_card(card)
|
||||
|
||||
var result = ability_system._validate_ability_cost(cost, null, player)
|
||||
|
||||
assert_true(result.valid, "Should be valid with sufficient cards to discard")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_validate_discard_cost_with_insufficient_cards() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var cost = {"discard": 3}
|
||||
var player = MockPlayer.new()
|
||||
# Add only 1 card to hand
|
||||
player.hand.add_card(_create_mock_card())
|
||||
|
||||
var result = ability_system._validate_ability_cost(cost, null, player)
|
||||
|
||||
assert_false(result.valid, "Should be invalid with insufficient cards")
|
||||
assert_true("discard" in result.reason, "Reason should mention discard")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# DULL SELF COST VALIDATION TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_validate_dull_self_cost_when_active() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var cost = {"dull_self": true}
|
||||
var source = _create_mock_card()
|
||||
source.activate() # Ensure active
|
||||
var player = MockPlayer.new()
|
||||
|
||||
var result = ability_system._validate_ability_cost(cost, source, player)
|
||||
|
||||
assert_true(result.valid, "Should be valid when card is active")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_validate_dull_self_cost_when_already_dull() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var cost = {"dull_self": true}
|
||||
var source = _create_mock_card()
|
||||
source.dull() # Already dull
|
||||
var player = MockPlayer.new()
|
||||
|
||||
var result = ability_system._validate_ability_cost(cost, source, player)
|
||||
|
||||
assert_false(result.valid, "Should be invalid when card is already dull")
|
||||
assert_true("dulled" in result.reason, "Reason should mention dulled")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# SPECIFIC DISCARD COST VALIDATION TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_validate_specific_discard_cost_with_matching_card() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var cost = {"specific_discard": "Cloud"}
|
||||
var player = MockPlayer.new()
|
||||
var cloud = _create_mock_card_with_name("Cloud")
|
||||
player.hand.add_card(cloud)
|
||||
|
||||
var result = ability_system._validate_ability_cost(cost, null, player)
|
||||
|
||||
assert_true(result.valid, "Should be valid with matching card in hand")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_validate_specific_discard_cost_without_matching_card() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var cost = {"specific_discard": "Cloud"}
|
||||
var player = MockPlayer.new()
|
||||
var sephiroth = _create_mock_card_with_name("Sephiroth")
|
||||
player.hand.add_card(sephiroth)
|
||||
|
||||
var result = ability_system._validate_ability_cost(cost, null, player)
|
||||
|
||||
assert_false(result.valid, "Should be invalid without matching card")
|
||||
assert_true("Cloud" in result.reason, "Reason should mention required card")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# COMBINED COST VALIDATION TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_validate_combined_cp_and_dull_cost() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var cost = {"cp": 2, "dull_self": true}
|
||||
var source = _create_mock_card()
|
||||
source.activate()
|
||||
var player = MockPlayer.new()
|
||||
player.cp_pool.add_cp(Enums.Element.FIRE, 3)
|
||||
|
||||
var result = ability_system._validate_ability_cost(cost, source, player)
|
||||
|
||||
assert_true(result.valid, "Should be valid when all requirements met")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_validate_combined_cost_fails_on_cp() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var cost = {"cp": 5, "dull_self": true}
|
||||
var source = _create_mock_card()
|
||||
source.activate()
|
||||
var player = MockPlayer.new()
|
||||
player.cp_pool.add_cp(Enums.Element.FIRE, 2) # Insufficient
|
||||
|
||||
var result = ability_system._validate_ability_cost(cost, source, player)
|
||||
|
||||
assert_false(result.valid, "Should fail on CP requirement")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_validate_combined_cost_fails_on_dull() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var cost = {"cp": 2, "dull_self": true}
|
||||
var source = _create_mock_card()
|
||||
source.dull() # Already dull
|
||||
var player = MockPlayer.new()
|
||||
player.cp_pool.add_cp(Enums.Element.FIRE, 5)
|
||||
|
||||
var result = ability_system._validate_ability_cost(cost, source, player)
|
||||
|
||||
assert_false(result.valid, "Should fail on dull requirement")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# COST PAYMENT TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_pay_empty_cost() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var cost = {}
|
||||
var player = MockPlayer.new()
|
||||
|
||||
var success = ability_system._pay_ability_cost(cost, null, player)
|
||||
|
||||
assert_true(success, "Paying empty cost should succeed")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_pay_generic_cp_cost() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var cost = {"cp": 3}
|
||||
var player = MockPlayer.new()
|
||||
player.cp_pool.add_cp(Enums.Element.FIRE, 5)
|
||||
|
||||
var initial_cp = player.cp_pool.get_total_cp()
|
||||
var success = ability_system._pay_ability_cost(cost, null, player)
|
||||
|
||||
assert_true(success, "Should successfully pay cost")
|
||||
assert_eq(player.cp_pool.get_total_cp(), initial_cp - 3, "Should spend 3 CP")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_pay_specific_element_cp_cost() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var cost = {"cp": 2, "element": "FIRE"}
|
||||
var player = MockPlayer.new()
|
||||
player.cp_pool.add_cp(Enums.Element.FIRE, 4)
|
||||
player.cp_pool.add_cp(Enums.Element.ICE, 3)
|
||||
|
||||
var success = ability_system._pay_ability_cost(cost, null, player)
|
||||
|
||||
assert_true(success, "Should successfully pay cost")
|
||||
assert_eq(player.cp_pool.get_cp(Enums.Element.FIRE), 2, "Should spend 2 Fire CP")
|
||||
assert_eq(player.cp_pool.get_cp(Enums.Element.ICE), 3, "Ice CP should be unchanged")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_pay_dull_self_cost() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var cost = {"dull_self": true}
|
||||
var source = _create_mock_card()
|
||||
source.activate()
|
||||
var player = MockPlayer.new()
|
||||
|
||||
assert_true(source.is_active(), "Source should start active")
|
||||
|
||||
var success = ability_system._pay_ability_cost(cost, source, player)
|
||||
|
||||
assert_true(success, "Should successfully pay cost")
|
||||
assert_true(source.is_dull(), "Source should be dulled")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_pay_combined_cost() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var cost = {"cp": 2, "dull_self": true}
|
||||
var source = _create_mock_card()
|
||||
source.activate()
|
||||
var player = MockPlayer.new()
|
||||
player.cp_pool.add_cp(Enums.Element.FIRE, 5)
|
||||
|
||||
var success = ability_system._pay_ability_cost(cost, source, player)
|
||||
|
||||
assert_true(success, "Should successfully pay combined cost")
|
||||
assert_eq(player.cp_pool.get_total_cp(), 3, "Should spend 2 CP")
|
||||
assert_true(source.is_dull(), "Source should be dulled")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# SIGNAL TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_ability_cost_failed_signal_declared() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
|
||||
# Check signal exists
|
||||
var signal_exists = ability_system.has_signal("ability_cost_failed")
|
||||
assert_true(signal_exists, "AbilitySystem should have ability_cost_failed signal")
|
||||
|
||||
ability_system.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
func _create_mock_card() -> CardInstance:
|
||||
var card = CardInstance.new()
|
||||
var data = CardDatabase.CardData.new()
|
||||
data.type = Enums.CardType.FORWARD
|
||||
data.power = 5000
|
||||
data.cost = 3
|
||||
data.name = "Test Card"
|
||||
data.elements = [Enums.Element.FIRE]
|
||||
card.card_data = data
|
||||
card.current_power = 5000
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_card_with_name(card_name: String) -> CardInstance:
|
||||
var card = _create_mock_card()
|
||||
card.card_data.name = card_name
|
||||
return card
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# MOCK CLASSES
|
||||
# =============================================================================
|
||||
|
||||
class MockPlayer:
|
||||
var cp_pool: CPPool
|
||||
var hand: Zone
|
||||
|
||||
func _init():
|
||||
cp_pool = CPPool.new()
|
||||
hand = Zone.new(Enums.ZoneType.HAND, 0)
|
||||
751
tests/unit/test_effect_resolver.gd
Normal file
751
tests/unit/test_effect_resolver.gd
Normal file
@@ -0,0 +1,751 @@
|
||||
extends GutTest
|
||||
|
||||
## Tests for EffectResolver effect execution
|
||||
## Covers core effects: DAMAGE, POWER_MOD, DULL, ACTIVATE, DRAW, BREAK, RETURN
|
||||
|
||||
|
||||
var resolver: EffectResolver
|
||||
|
||||
|
||||
func before_each() -> void:
|
||||
resolver = EffectResolver.new()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# DAMAGE EFFECT TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_resolve_damage_basic() -> void:
|
||||
var card = _create_mock_forward(7000)
|
||||
var game_state = _create_mock_game_state_with_forward(card, 0)
|
||||
var effect = {"type": "DAMAGE", "amount": 3000}
|
||||
|
||||
resolver.resolve(effect, null, [card], game_state)
|
||||
|
||||
assert_eq(card.damage_received, 3000, "Card should have received 3000 damage")
|
||||
|
||||
|
||||
func test_resolve_damage_breaks_forward() -> void:
|
||||
var card = _create_mock_forward(5000)
|
||||
var game_state = _create_mock_game_state_with_forward(card, 0)
|
||||
var effect = {"type": "DAMAGE", "amount": 5000}
|
||||
|
||||
resolver.resolve(effect, null, [card], game_state)
|
||||
|
||||
assert_true(game_state.players[0].break_zone.has_card(card), "Forward should be in break zone")
|
||||
assert_false(game_state.players[0].field_forwards.has_card(card), "Forward should not be on field")
|
||||
|
||||
|
||||
func test_resolve_damage_exact_power_breaks() -> void:
|
||||
var card = _create_mock_forward(6000)
|
||||
var game_state = _create_mock_game_state_with_forward(card, 0)
|
||||
var effect = {"type": "DAMAGE", "amount": 6000}
|
||||
|
||||
resolver.resolve(effect, null, [card], game_state)
|
||||
|
||||
assert_true(game_state.players[0].break_zone.has_card(card), "Forward should break when damage equals power")
|
||||
|
||||
|
||||
func test_resolve_damage_multiple_targets() -> void:
|
||||
var card1 = _create_mock_forward(8000)
|
||||
var card2 = _create_mock_forward(5000)
|
||||
var game_state = _create_mock_game_state()
|
||||
game_state.players[0].field_forwards.add_card(card1)
|
||||
game_state.players[0].field_forwards.add_card(card2)
|
||||
card1.controller_index = 0
|
||||
card2.controller_index = 0
|
||||
|
||||
var effect = {"type": "DAMAGE", "amount": 3000}
|
||||
|
||||
resolver.resolve(effect, null, [card1, card2], game_state)
|
||||
|
||||
assert_eq(card1.damage_received, 3000, "First card should have 3000 damage")
|
||||
assert_eq(card2.damage_received, 3000, "Second card should have 3000 damage")
|
||||
|
||||
|
||||
func test_resolve_damage_ignores_non_forwards() -> void:
|
||||
var backup = _create_mock_backup()
|
||||
var game_state = _create_mock_game_state()
|
||||
var effect = {"type": "DAMAGE", "amount": 5000}
|
||||
|
||||
resolver.resolve(effect, null, [backup], game_state)
|
||||
|
||||
assert_eq(backup.damage_received, 0, "Backup should not receive damage")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# POWER_MOD EFFECT TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_resolve_power_mod_positive() -> void:
|
||||
var card = _create_mock_forward(5000)
|
||||
var game_state = _create_mock_game_state()
|
||||
var effect = {"type": "POWER_MOD", "amount": 2000}
|
||||
|
||||
resolver.resolve(effect, null, [card], game_state)
|
||||
|
||||
assert_eq(card.power_modifiers.size(), 1, "Should have one power modifier")
|
||||
assert_eq(card.power_modifiers[0], 2000, "Power modifier should be +2000")
|
||||
|
||||
|
||||
func test_resolve_power_mod_negative() -> void:
|
||||
var card = _create_mock_forward(7000)
|
||||
var game_state = _create_mock_game_state()
|
||||
var effect = {"type": "POWER_MOD", "amount": -3000}
|
||||
|
||||
resolver.resolve(effect, null, [card], game_state)
|
||||
|
||||
assert_eq(card.power_modifiers[0], -3000, "Power modifier should be -3000")
|
||||
|
||||
|
||||
func test_resolve_power_mod_multiple_targets() -> void:
|
||||
var card1 = _create_mock_forward(5000)
|
||||
var card2 = _create_mock_forward(6000)
|
||||
var game_state = _create_mock_game_state()
|
||||
var effect = {"type": "POWER_MOD", "amount": 1000}
|
||||
|
||||
resolver.resolve(effect, null, [card1, card2], game_state)
|
||||
|
||||
assert_eq(card1.power_modifiers[0], 1000, "First card should have +1000")
|
||||
assert_eq(card2.power_modifiers[0], 1000, "Second card should have +1000")
|
||||
|
||||
|
||||
func test_resolve_power_mod_applies_to_source_if_no_targets() -> void:
|
||||
var source = _create_mock_forward(5000)
|
||||
var game_state = _create_mock_game_state()
|
||||
var effect = {"type": "POWER_MOD", "amount": 3000}
|
||||
|
||||
resolver.resolve(effect, source, [], game_state)
|
||||
|
||||
assert_eq(source.power_modifiers[0], 3000, "Source should get modifier when no targets")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# DULL EFFECT TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_resolve_dull_single_target() -> void:
|
||||
var card = _create_mock_forward(5000)
|
||||
card.activate() # Ensure card starts active
|
||||
var game_state = _create_mock_game_state()
|
||||
var effect = {"type": "DULL"}
|
||||
|
||||
assert_true(card.is_active(), "Card should start active")
|
||||
|
||||
resolver.resolve(effect, null, [card], game_state)
|
||||
|
||||
assert_true(card.is_dull(), "Card should be dulled")
|
||||
|
||||
|
||||
func test_resolve_dull_multiple_targets() -> void:
|
||||
var card1 = _create_mock_forward(5000)
|
||||
var card2 = _create_mock_forward(6000)
|
||||
card1.activate()
|
||||
card2.activate()
|
||||
var game_state = _create_mock_game_state()
|
||||
var effect = {"type": "DULL"}
|
||||
|
||||
resolver.resolve(effect, null, [card1, card2], game_state)
|
||||
|
||||
assert_true(card1.is_dull(), "First card should be dulled")
|
||||
assert_true(card2.is_dull(), "Second card should be dulled")
|
||||
|
||||
|
||||
func test_resolve_dull_already_dull_card() -> void:
|
||||
var card = _create_mock_forward(5000)
|
||||
card.dull() # Already dull
|
||||
var game_state = _create_mock_game_state()
|
||||
var effect = {"type": "DULL"}
|
||||
|
||||
resolver.resolve(effect, null, [card], game_state)
|
||||
|
||||
assert_true(card.is_dull(), "Card should remain dulled")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# ACTIVATE EFFECT TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_resolve_activate_single_target() -> void:
|
||||
var card = _create_mock_forward(5000)
|
||||
card.dull() # Start dull
|
||||
var game_state = _create_mock_game_state()
|
||||
var effect = {"type": "ACTIVATE"}
|
||||
|
||||
assert_true(card.is_dull(), "Card should start dull")
|
||||
|
||||
resolver.resolve(effect, null, [card], game_state)
|
||||
|
||||
assert_true(card.is_active(), "Card should be activated")
|
||||
|
||||
|
||||
func test_resolve_activate_multiple_targets() -> void:
|
||||
var card1 = _create_mock_forward(5000)
|
||||
var card2 = _create_mock_forward(6000)
|
||||
card1.dull()
|
||||
card2.dull()
|
||||
var game_state = _create_mock_game_state()
|
||||
var effect = {"type": "ACTIVATE"}
|
||||
|
||||
resolver.resolve(effect, null, [card1, card2], game_state)
|
||||
|
||||
assert_true(card1.is_active(), "First card should be active")
|
||||
assert_true(card2.is_active(), "Second card should be active")
|
||||
|
||||
|
||||
func test_resolve_activate_already_active_card() -> void:
|
||||
var card = _create_mock_forward(5000)
|
||||
card.activate() # Already active
|
||||
var game_state = _create_mock_game_state()
|
||||
var effect = {"type": "ACTIVATE"}
|
||||
|
||||
resolver.resolve(effect, null, [card], game_state)
|
||||
|
||||
assert_true(card.is_active(), "Card should remain active")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# DRAW EFFECT TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_resolve_draw_single_card() -> void:
|
||||
var source = _create_mock_forward(5000)
|
||||
source.controller_index = 0
|
||||
var game_state = _create_mock_game_state_with_deck(5)
|
||||
var effect = {"type": "DRAW", "amount": 1}
|
||||
|
||||
var initial_deck_size = game_state.players[0].deck.get_count()
|
||||
var initial_hand_size = game_state.players[0].hand.get_count()
|
||||
|
||||
resolver.resolve(effect, source, [], game_state)
|
||||
|
||||
assert_eq(game_state.players[0].deck.get_count(), initial_deck_size - 1, "Deck should have 1 less card")
|
||||
assert_eq(game_state.players[0].hand.get_count(), initial_hand_size + 1, "Hand should have 1 more card")
|
||||
|
||||
|
||||
func test_resolve_draw_multiple_cards() -> void:
|
||||
var source = _create_mock_forward(5000)
|
||||
source.controller_index = 0
|
||||
var game_state = _create_mock_game_state_with_deck(10)
|
||||
var effect = {"type": "DRAW", "amount": 3}
|
||||
|
||||
var initial_deck_size = game_state.players[0].deck.get_count()
|
||||
|
||||
resolver.resolve(effect, source, [], game_state)
|
||||
|
||||
assert_eq(game_state.players[0].deck.get_count(), initial_deck_size - 3, "Deck should have 3 fewer cards")
|
||||
|
||||
|
||||
func test_resolve_draw_opponent() -> void:
|
||||
var source = _create_mock_forward(5000)
|
||||
source.controller_index = 0
|
||||
var game_state = _create_mock_game_state_with_deck(5)
|
||||
# Add cards to opponent's deck too
|
||||
for i in range(5):
|
||||
var deck_card = _create_mock_forward(1000)
|
||||
game_state.players[1].deck.add_card(deck_card)
|
||||
|
||||
var effect = {"type": "DRAW", "amount": 1, "target": {"type": "OPPONENT"}}
|
||||
|
||||
var initial_opp_deck = game_state.players[1].deck.get_count()
|
||||
var initial_opp_hand = game_state.players[1].hand.get_count()
|
||||
|
||||
resolver.resolve(effect, source, [], game_state)
|
||||
|
||||
assert_eq(game_state.players[1].deck.get_count(), initial_opp_deck - 1, "Opponent deck should have 1 less card")
|
||||
assert_eq(game_state.players[1].hand.get_count(), initial_opp_hand + 1, "Opponent hand should have 1 more card")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# BREAK EFFECT TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_resolve_break_single_target() -> void:
|
||||
var card = _create_mock_forward(7000)
|
||||
var game_state = _create_mock_game_state_with_forward(card, 0)
|
||||
var effect = {"type": "BREAK"}
|
||||
|
||||
assert_true(game_state.players[0].field_forwards.has_card(card), "Card should start on field")
|
||||
|
||||
resolver.resolve(effect, null, [card], game_state)
|
||||
|
||||
assert_false(game_state.players[0].field_forwards.has_card(card), "Card should not be on field")
|
||||
assert_true(game_state.players[0].break_zone.has_card(card), "Card should be in break zone")
|
||||
|
||||
|
||||
func test_resolve_break_multiple_targets() -> void:
|
||||
var card1 = _create_mock_forward(5000)
|
||||
var card2 = _create_mock_forward(6000)
|
||||
var game_state = _create_mock_game_state()
|
||||
game_state.players[0].field_forwards.add_card(card1)
|
||||
game_state.players[0].field_forwards.add_card(card2)
|
||||
card1.controller_index = 0
|
||||
card2.controller_index = 0
|
||||
|
||||
var effect = {"type": "BREAK"}
|
||||
|
||||
resolver.resolve(effect, null, [card1, card2], game_state)
|
||||
|
||||
assert_true(game_state.players[0].break_zone.has_card(card1), "First card should be in break zone")
|
||||
assert_true(game_state.players[0].break_zone.has_card(card2), "Second card should be in break zone")
|
||||
|
||||
|
||||
func test_resolve_break_backup() -> void:
|
||||
var backup = _create_mock_backup()
|
||||
var game_state = _create_mock_game_state()
|
||||
game_state.players[0].field_backups.add_card(backup)
|
||||
backup.controller_index = 0
|
||||
|
||||
var effect = {"type": "BREAK"}
|
||||
|
||||
resolver.resolve(effect, null, [backup], game_state)
|
||||
|
||||
assert_true(game_state.players[0].break_zone.has_card(backup), "Backup should be in break zone")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# RETURN EFFECT TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_resolve_return_forward_to_hand() -> void:
|
||||
var card = _create_mock_forward(5000)
|
||||
var game_state = _create_mock_game_state_with_forward(card, 0)
|
||||
var effect = {"type": "RETURN"}
|
||||
|
||||
resolver.resolve(effect, null, [card], game_state)
|
||||
|
||||
assert_false(game_state.players[0].field_forwards.has_card(card), "Card should not be on field")
|
||||
assert_true(game_state.players[0].hand.has_card(card), "Card should be in hand")
|
||||
|
||||
|
||||
func test_resolve_return_backup_to_hand() -> void:
|
||||
var backup = _create_mock_backup()
|
||||
var game_state = _create_mock_game_state()
|
||||
game_state.players[0].field_backups.add_card(backup)
|
||||
backup.controller_index = 0
|
||||
backup.owner_index = 0
|
||||
|
||||
var effect = {"type": "RETURN"}
|
||||
|
||||
resolver.resolve(effect, null, [backup], game_state)
|
||||
|
||||
assert_false(game_state.players[0].field_backups.has_card(backup), "Backup should not be on field")
|
||||
assert_true(game_state.players[0].hand.has_card(backup), "Backup should be in hand")
|
||||
|
||||
|
||||
func test_resolve_return_multiple_targets() -> void:
|
||||
var card1 = _create_mock_forward(5000)
|
||||
var card2 = _create_mock_forward(6000)
|
||||
var game_state = _create_mock_game_state()
|
||||
game_state.players[0].field_forwards.add_card(card1)
|
||||
game_state.players[0].field_forwards.add_card(card2)
|
||||
card1.controller_index = 0
|
||||
card1.owner_index = 0
|
||||
card2.controller_index = 0
|
||||
card2.owner_index = 0
|
||||
|
||||
var effect = {"type": "RETURN"}
|
||||
|
||||
resolver.resolve(effect, null, [card1, card2], game_state)
|
||||
|
||||
assert_true(game_state.players[0].hand.has_card(card1), "First card should be in hand")
|
||||
assert_true(game_state.players[0].hand.has_card(card2), "Second card should be in hand")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# SCALING_EFFECT TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_resolve_scaling_effect_by_forwards() -> void:
|
||||
var source = _create_mock_forward(5000)
|
||||
source.controller_index = 0
|
||||
var target = _create_mock_forward(10000)
|
||||
target.controller_index = 1
|
||||
|
||||
var game_state = _create_mock_game_state()
|
||||
# Add 3 forwards to controller's field
|
||||
for i in range(3):
|
||||
var forward = _create_mock_forward(4000)
|
||||
forward.controller_index = 0
|
||||
game_state.players[0].field_forwards.add_card(forward)
|
||||
# Add target to opponent's field
|
||||
game_state.players[1].field_forwards.add_card(target)
|
||||
|
||||
var effect = {
|
||||
"type": "SCALING_EFFECT",
|
||||
"base_effect": {"type": "DAMAGE"},
|
||||
"scale_by": "FORWARDS_CONTROLLED",
|
||||
"multiplier": 1000,
|
||||
"scale_filter": {}
|
||||
}
|
||||
|
||||
resolver.resolve(effect, source, [target], game_state)
|
||||
|
||||
assert_eq(target.damage_received, 3000, "Should deal 3000 damage (3 forwards * 1000)")
|
||||
|
||||
|
||||
func test_resolve_scaling_effect_by_backups() -> void:
|
||||
var source = _create_mock_forward(5000)
|
||||
source.controller_index = 0
|
||||
var target = _create_mock_forward(10000)
|
||||
target.controller_index = 1
|
||||
|
||||
var game_state = _create_mock_game_state()
|
||||
# Add 4 backups to controller's field
|
||||
for i in range(4):
|
||||
var backup = _create_mock_backup()
|
||||
backup.controller_index = 0
|
||||
game_state.players[0].field_backups.add_card(backup)
|
||||
game_state.players[1].field_forwards.add_card(target)
|
||||
|
||||
var effect = {
|
||||
"type": "SCALING_EFFECT",
|
||||
"base_effect": {"type": "DAMAGE"},
|
||||
"scale_by": "BACKUPS_CONTROLLED",
|
||||
"multiplier": 500,
|
||||
"scale_filter": {}
|
||||
}
|
||||
|
||||
resolver.resolve(effect, source, [target], game_state)
|
||||
|
||||
assert_eq(target.damage_received, 2000, "Should deal 2000 damage (4 backups * 500)")
|
||||
|
||||
|
||||
func test_resolve_scaling_effect_with_filter() -> void:
|
||||
var source = _create_mock_forward(5000)
|
||||
source.controller_index = 0
|
||||
var target = _create_mock_forward(10000)
|
||||
target.controller_index = 1
|
||||
|
||||
var game_state = _create_mock_game_state()
|
||||
# Add 2 Fire forwards and 2 Ice forwards
|
||||
for i in range(2):
|
||||
var fire_forward = _create_mock_forward_with_element(Enums.Element.FIRE)
|
||||
fire_forward.controller_index = 0
|
||||
game_state.players[0].field_forwards.add_card(fire_forward)
|
||||
for i in range(2):
|
||||
var ice_forward = _create_mock_forward_with_element(Enums.Element.ICE)
|
||||
ice_forward.controller_index = 0
|
||||
game_state.players[0].field_forwards.add_card(ice_forward)
|
||||
game_state.players[1].field_forwards.add_card(target)
|
||||
|
||||
var effect = {
|
||||
"type": "SCALING_EFFECT",
|
||||
"base_effect": {"type": "DAMAGE"},
|
||||
"scale_by": "FORWARDS_CONTROLLED",
|
||||
"multiplier": 2000,
|
||||
"scale_filter": {"element": "FIRE"} # Only count Fire forwards
|
||||
}
|
||||
|
||||
resolver.resolve(effect, source, [target], game_state)
|
||||
|
||||
assert_eq(target.damage_received, 4000, "Should deal 4000 damage (2 Fire forwards * 2000)")
|
||||
|
||||
|
||||
func test_resolve_scaling_effect_zero_count() -> void:
|
||||
var source = _create_mock_forward(5000)
|
||||
source.controller_index = 0
|
||||
var target = _create_mock_forward(10000)
|
||||
target.controller_index = 1
|
||||
|
||||
var game_state = _create_mock_game_state()
|
||||
# No forwards on controller's field
|
||||
game_state.players[1].field_forwards.add_card(target)
|
||||
|
||||
var effect = {
|
||||
"type": "SCALING_EFFECT",
|
||||
"base_effect": {"type": "DAMAGE"},
|
||||
"scale_by": "FORWARDS_CONTROLLED",
|
||||
"multiplier": 1000,
|
||||
"scale_filter": {}
|
||||
}
|
||||
|
||||
resolver.resolve(effect, source, [target], game_state)
|
||||
|
||||
assert_eq(target.damage_received, 0, "Should deal 0 damage (0 forwards * 1000)")
|
||||
|
||||
|
||||
func test_resolve_scaling_effect_power_mod() -> void:
|
||||
var source = _create_mock_forward(5000)
|
||||
source.controller_index = 0
|
||||
|
||||
var game_state = _create_mock_game_state()
|
||||
# Add 2 backups
|
||||
for i in range(2):
|
||||
var backup = _create_mock_backup()
|
||||
backup.controller_index = 0
|
||||
game_state.players[0].field_backups.add_card(backup)
|
||||
|
||||
var effect = {
|
||||
"type": "SCALING_EFFECT",
|
||||
"base_effect": {"type": "POWER_MOD"},
|
||||
"scale_by": "BACKUPS_CONTROLLED",
|
||||
"multiplier": 1000,
|
||||
"scale_filter": {}
|
||||
}
|
||||
|
||||
resolver.resolve(effect, source, [source], game_state)
|
||||
|
||||
assert_eq(source.power_modifiers[0], 2000, "Should get +2000 power (2 backups * 1000)")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CONDITIONAL EFFECT TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_resolve_conditional_then_branch() -> void:
|
||||
var source = _create_mock_forward(5000)
|
||||
source.controller_index = 0
|
||||
var target = _create_mock_forward(7000)
|
||||
|
||||
var game_state = _create_mock_game_state()
|
||||
game_state.players[0].damage = 5 # 5 damage points
|
||||
|
||||
# Setup ConditionChecker mock
|
||||
resolver.condition_checker = MockConditionChecker.new(true)
|
||||
|
||||
var effect = {
|
||||
"type": "CONDITIONAL",
|
||||
"condition": {"type": "DAMAGE_RECEIVED", "comparison": "GTE", "value": 3},
|
||||
"then_effects": [{"type": "DAMAGE", "amount": 5000}],
|
||||
"else_effects": []
|
||||
}
|
||||
|
||||
resolver.resolve(effect, source, [target], game_state)
|
||||
|
||||
assert_eq(target.damage_received, 5000, "Should execute then_effects branch")
|
||||
|
||||
|
||||
func test_resolve_conditional_else_branch() -> void:
|
||||
var source = _create_mock_forward(5000)
|
||||
source.controller_index = 0
|
||||
var target = _create_mock_forward(7000)
|
||||
|
||||
var game_state = _create_mock_game_state()
|
||||
game_state.players[0].damage = 1 # Only 1 damage
|
||||
|
||||
# Setup ConditionChecker mock that returns false
|
||||
resolver.condition_checker = MockConditionChecker.new(false)
|
||||
|
||||
var effect = {
|
||||
"type": "CONDITIONAL",
|
||||
"condition": {"type": "DAMAGE_RECEIVED", "comparison": "GTE", "value": 5},
|
||||
"then_effects": [{"type": "DAMAGE", "amount": 5000}],
|
||||
"else_effects": [{"type": "DAMAGE", "amount": 1000}]
|
||||
}
|
||||
|
||||
resolver.resolve(effect, source, [target], game_state)
|
||||
|
||||
assert_eq(target.damage_received, 1000, "Should execute else_effects branch")
|
||||
|
||||
|
||||
func test_resolve_conditional_no_else_branch() -> void:
|
||||
var source = _create_mock_forward(5000)
|
||||
source.controller_index = 0
|
||||
var target = _create_mock_forward(7000)
|
||||
|
||||
var game_state = _create_mock_game_state()
|
||||
|
||||
# Setup ConditionChecker mock that returns false
|
||||
resolver.condition_checker = MockConditionChecker.new(false)
|
||||
|
||||
var effect = {
|
||||
"type": "CONDITIONAL",
|
||||
"condition": {"type": "SOME_CONDITION"},
|
||||
"then_effects": [{"type": "DAMAGE", "amount": 5000}],
|
||||
"else_effects": [] # No else branch
|
||||
}
|
||||
|
||||
resolver.resolve(effect, source, [target], game_state)
|
||||
|
||||
assert_eq(target.damage_received, 0, "Should do nothing when condition false and no else")
|
||||
|
||||
|
||||
func test_resolve_conditional_multiple_then_effects() -> void:
|
||||
var source = _create_mock_forward(5000)
|
||||
source.controller_index = 0
|
||||
var target = _create_mock_forward(7000)
|
||||
|
||||
var game_state = _create_mock_game_state()
|
||||
|
||||
resolver.condition_checker = MockConditionChecker.new(true)
|
||||
|
||||
var effect = {
|
||||
"type": "CONDITIONAL",
|
||||
"condition": {"type": "ALWAYS_TRUE"},
|
||||
"then_effects": [
|
||||
{"type": "DAMAGE", "amount": 2000},
|
||||
{"type": "POWER_MOD", "amount": -1000}
|
||||
],
|
||||
"else_effects": []
|
||||
}
|
||||
|
||||
resolver.resolve(effect, source, [target], game_state)
|
||||
|
||||
assert_eq(target.damage_received, 2000, "Should apply damage")
|
||||
assert_eq(target.power_modifiers[0], -1000, "Should apply power mod")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# EFFECT_COMPLETED SIGNAL TEST
|
||||
# =============================================================================
|
||||
|
||||
func test_effect_completed_signal_emitted() -> void:
|
||||
var card = _create_mock_forward(5000)
|
||||
var game_state = _create_mock_game_state()
|
||||
var effect = {"type": "DULL"}
|
||||
var signal_received = false
|
||||
var received_effect = {}
|
||||
var received_targets = []
|
||||
|
||||
resolver.effect_completed.connect(func(e, t):
|
||||
signal_received = true
|
||||
received_effect = e
|
||||
received_targets = t
|
||||
)
|
||||
|
||||
resolver.resolve(effect, null, [card], game_state)
|
||||
|
||||
assert_true(signal_received, "effect_completed signal should be emitted")
|
||||
assert_eq(received_effect.type, "DULL", "Signal should contain effect")
|
||||
assert_eq(received_targets.size(), 1, "Signal should contain targets")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# COMBINED DAMAGE AND BREAK TEST
|
||||
# =============================================================================
|
||||
|
||||
func test_damage_accumulates_before_break() -> void:
|
||||
var card = _create_mock_forward(10000)
|
||||
var game_state = _create_mock_game_state_with_forward(card, 0)
|
||||
|
||||
# Apply 4000 damage (shouldn't break)
|
||||
resolver.resolve({"type": "DAMAGE", "amount": 4000}, null, [card], game_state)
|
||||
assert_eq(card.damage_received, 4000, "Should have 4000 damage")
|
||||
assert_true(game_state.players[0].field_forwards.has_card(card), "Should still be on field")
|
||||
|
||||
# Apply 6000 more (total 10000, should break)
|
||||
resolver.resolve({"type": "DAMAGE", "amount": 6000}, null, [card], game_state)
|
||||
assert_true(game_state.players[0].break_zone.has_card(card), "Should be broken with 10000 total damage")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
func _create_mock_forward(power: int) -> CardInstance:
|
||||
var card = CardInstance.new()
|
||||
var data = CardDatabase.CardData.new()
|
||||
data.type = Enums.CardType.FORWARD
|
||||
data.power = power
|
||||
data.cost = 3
|
||||
data.name = "Test Forward"
|
||||
data.elements = [Enums.Element.FIRE]
|
||||
card.card_data = data
|
||||
card.current_power = power
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_backup() -> CardInstance:
|
||||
var card = CardInstance.new()
|
||||
var data = CardDatabase.CardData.new()
|
||||
data.type = Enums.CardType.BACKUP
|
||||
data.cost = 2
|
||||
data.name = "Test Backup"
|
||||
data.elements = [Enums.Element.WATER]
|
||||
card.card_data = data
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_forward_with_element(element: Enums.Element) -> CardInstance:
|
||||
var card = _create_mock_forward(5000)
|
||||
card.card_data.elements = [element]
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_game_state() -> MockGameState:
|
||||
return MockGameState.new()
|
||||
|
||||
|
||||
func _create_mock_game_state_with_forward(card: CardInstance, player_index: int) -> MockGameState:
|
||||
var gs = MockGameState.new()
|
||||
gs.players[player_index].field_forwards.add_card(card)
|
||||
card.controller_index = player_index
|
||||
card.owner_index = player_index
|
||||
return gs
|
||||
|
||||
|
||||
func _create_mock_game_state_with_deck(card_count: int) -> MockGameState:
|
||||
var gs = MockGameState.new()
|
||||
for i in range(card_count):
|
||||
var deck_card = _create_mock_forward(1000 + i * 1000)
|
||||
gs.players[0].deck.add_card(deck_card)
|
||||
return gs
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# MOCK CLASSES
|
||||
# =============================================================================
|
||||
|
||||
class MockGameState:
|
||||
var players: Array
|
||||
|
||||
func _init():
|
||||
players = [MockPlayer.new(0), MockPlayer.new(1)]
|
||||
|
||||
func get_player(index: int):
|
||||
if index >= 0 and index < players.size():
|
||||
return players[index]
|
||||
return null
|
||||
|
||||
|
||||
class MockPlayer:
|
||||
var player_index: int
|
||||
var damage: int = 0
|
||||
var deck: Zone
|
||||
var hand: Zone
|
||||
var field_forwards: Zone
|
||||
var field_backups: Zone
|
||||
var break_zone: Zone
|
||||
|
||||
func _init(index: int = 0):
|
||||
player_index = index
|
||||
deck = Zone.new(Enums.ZoneType.DECK, index)
|
||||
hand = Zone.new(Enums.ZoneType.HAND, index)
|
||||
field_forwards = Zone.new(Enums.ZoneType.FIELD, index)
|
||||
field_backups = Zone.new(Enums.ZoneType.FIELD, index)
|
||||
break_zone = Zone.new(Enums.ZoneType.BREAK_ZONE, index)
|
||||
|
||||
func draw_cards(count: int) -> Array:
|
||||
var drawn: Array = []
|
||||
for i in range(count):
|
||||
var card = deck.pop_top_card()
|
||||
if card:
|
||||
hand.add_card(card)
|
||||
drawn.append(card)
|
||||
return drawn
|
||||
|
||||
func break_card(card: CardInstance) -> bool:
|
||||
var removed = false
|
||||
|
||||
if field_forwards.has_card(card):
|
||||
field_forwards.remove_card(card)
|
||||
removed = true
|
||||
elif field_backups.has_card(card):
|
||||
field_backups.remove_card(card)
|
||||
removed = true
|
||||
|
||||
if removed:
|
||||
break_zone.add_card(card)
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
class MockConditionChecker:
|
||||
var _return_value: bool
|
||||
|
||||
func _init(return_value: bool = true):
|
||||
_return_value = return_value
|
||||
|
||||
func evaluate(_condition: Dictionary, _context: Dictionary) -> bool:
|
||||
return _return_value
|
||||
306
tests/unit/test_filtered_scaling.gd
Normal file
306
tests/unit/test_filtered_scaling.gd
Normal file
@@ -0,0 +1,306 @@
|
||||
extends GutTest
|
||||
|
||||
## Tests for filtered scaling in EffectResolver
|
||||
## Tests element, job, category, cost, and card name filters
|
||||
|
||||
# =============================================================================
|
||||
# SETUP
|
||||
# =============================================================================
|
||||
|
||||
var resolver: EffectResolver
|
||||
|
||||
|
||||
func before_each() -> void:
|
||||
resolver = EffectResolver.new()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# FILTER MATCHING TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_element_filter_matches_fire() -> void:
|
||||
var filter = {"element": "FIRE"}
|
||||
var card = _create_mock_forward_with_element(Enums.Element.FIRE)
|
||||
|
||||
assert_true(resolver._card_matches_scale_filter(card, filter), "Fire card should match FIRE filter")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_element_filter_rejects_wrong_element() -> void:
|
||||
var filter = {"element": "FIRE"}
|
||||
var card = _create_mock_forward_with_element(Enums.Element.ICE)
|
||||
|
||||
assert_false(resolver._card_matches_scale_filter(card, filter), "Ice card should not match FIRE filter")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_job_filter_matches_samurai() -> void:
|
||||
var filter = {"job": "Samurai"}
|
||||
var card = _create_mock_forward_with_job("Samurai")
|
||||
|
||||
assert_true(resolver._card_matches_scale_filter(card, filter), "Samurai should match Samurai filter")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_job_filter_rejects_wrong_job() -> void:
|
||||
var filter = {"job": "Samurai"}
|
||||
var card = _create_mock_forward_with_job("Warrior")
|
||||
|
||||
assert_false(resolver._card_matches_scale_filter(card, filter), "Warrior should not match Samurai filter")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_job_filter_case_insensitive() -> void:
|
||||
var filter = {"job": "SAMURAI"}
|
||||
var card = _create_mock_forward_with_job("samurai")
|
||||
|
||||
assert_true(resolver._card_matches_scale_filter(card, filter), "Job filter should be case insensitive")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_category_filter_matches() -> void:
|
||||
var filter = {"category": "VII"}
|
||||
var card = _create_mock_forward_with_category("VII")
|
||||
|
||||
assert_true(resolver._card_matches_scale_filter(card, filter), "VII card should match VII filter")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_category_filter_rejects_wrong_category() -> void:
|
||||
var filter = {"category": "VII"}
|
||||
var card = _create_mock_forward_with_category("X")
|
||||
|
||||
assert_false(resolver._card_matches_scale_filter(card, filter), "Category X should not match VII filter")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_cost_exact_filter_matches() -> void:
|
||||
var filter = {"cost": 3}
|
||||
var card = _create_mock_forward_with_cost(3)
|
||||
|
||||
assert_true(resolver._card_matches_scale_filter(card, filter), "Cost 3 should match cost 3 filter")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_cost_exact_filter_rejects_wrong_cost() -> void:
|
||||
var filter = {"cost": 3}
|
||||
var card = _create_mock_forward_with_cost(5)
|
||||
|
||||
assert_false(resolver._card_matches_scale_filter(card, filter), "Cost 5 should not match cost 3 filter")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_cost_lte_filter_matches_lower() -> void:
|
||||
var filter = {"cost_comparison": "LTE", "cost_value": 3}
|
||||
var card = _create_mock_forward_with_cost(2)
|
||||
|
||||
assert_true(resolver._card_matches_scale_filter(card, filter), "Cost 2 should match cost <= 3")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_cost_lte_filter_matches_equal() -> void:
|
||||
var filter = {"cost_comparison": "LTE", "cost_value": 3}
|
||||
var card = _create_mock_forward_with_cost(3)
|
||||
|
||||
assert_true(resolver._card_matches_scale_filter(card, filter), "Cost 3 should match cost <= 3")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_cost_lte_filter_rejects_higher() -> void:
|
||||
var filter = {"cost_comparison": "LTE", "cost_value": 3}
|
||||
var card = _create_mock_forward_with_cost(5)
|
||||
|
||||
assert_false(resolver._card_matches_scale_filter(card, filter), "Cost 5 should not match cost <= 3")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_cost_gte_filter_matches_higher() -> void:
|
||||
var filter = {"cost_comparison": "GTE", "cost_value": 3}
|
||||
var card = _create_mock_forward_with_cost(5)
|
||||
|
||||
assert_true(resolver._card_matches_scale_filter(card, filter), "Cost 5 should match cost >= 3")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_cost_gte_filter_rejects_lower() -> void:
|
||||
var filter = {"cost_comparison": "GTE", "cost_value": 3}
|
||||
var card = _create_mock_forward_with_cost(2)
|
||||
|
||||
assert_false(resolver._card_matches_scale_filter(card, filter), "Cost 2 should not match cost >= 3")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_card_name_filter_matches() -> void:
|
||||
var filter = {"card_name": "Cloud"}
|
||||
var card = _create_mock_forward_with_name("Cloud")
|
||||
|
||||
assert_true(resolver._card_matches_scale_filter(card, filter), "Cloud should match Cloud filter")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_card_name_filter_rejects_wrong_name() -> void:
|
||||
var filter = {"card_name": "Cloud"}
|
||||
var card = _create_mock_forward_with_name("Tifa")
|
||||
|
||||
assert_false(resolver._card_matches_scale_filter(card, filter), "Tifa should not match Cloud filter")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_card_type_filter_forward() -> void:
|
||||
var filter = {"card_type": "FORWARD"}
|
||||
var card = _create_mock_forward()
|
||||
|
||||
assert_true(resolver._card_matches_scale_filter(card, filter), "Forward should match FORWARD filter")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_card_type_filter_backup() -> void:
|
||||
var filter = {"card_type": "BACKUP"}
|
||||
var card = _create_mock_backup()
|
||||
|
||||
assert_true(resolver._card_matches_scale_filter(card, filter), "Backup should match BACKUP filter")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_card_type_filter_rejects_wrong_type() -> void:
|
||||
var filter = {"card_type": "BACKUP"}
|
||||
var card = _create_mock_forward()
|
||||
|
||||
assert_false(resolver._card_matches_scale_filter(card, filter), "Forward should not match BACKUP filter")
|
||||
card.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# COMBINED FILTER TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_combined_element_and_type_filter() -> void:
|
||||
var filter = {"element": "FIRE", "card_type": "FORWARD"}
|
||||
|
||||
var fire_forward = _create_mock_forward_with_element(Enums.Element.FIRE)
|
||||
var fire_backup = _create_mock_backup_with_element(Enums.Element.FIRE)
|
||||
var ice_forward = _create_mock_forward_with_element(Enums.Element.ICE)
|
||||
|
||||
assert_true(resolver._card_matches_scale_filter(fire_forward, filter), "Fire Forward should match")
|
||||
assert_false(resolver._card_matches_scale_filter(fire_backup, filter), "Fire Backup should not match")
|
||||
assert_false(resolver._card_matches_scale_filter(ice_forward, filter), "Ice Forward should not match")
|
||||
|
||||
fire_forward.free()
|
||||
fire_backup.free()
|
||||
ice_forward.free()
|
||||
|
||||
|
||||
func test_combined_job_and_cost_filter() -> void:
|
||||
var filter = {"job": "Warrior", "cost_comparison": "LTE", "cost_value": 3}
|
||||
|
||||
var cheap_warrior = _create_mock_forward_with_job_and_cost("Warrior", 2)
|
||||
var expensive_warrior = _create_mock_forward_with_job_and_cost("Warrior", 5)
|
||||
var cheap_mage = _create_mock_forward_with_job_and_cost("Mage", 2)
|
||||
|
||||
assert_true(resolver._card_matches_scale_filter(cheap_warrior, filter), "Cheap Warrior should match")
|
||||
assert_false(resolver._card_matches_scale_filter(expensive_warrior, filter), "Expensive Warrior should not match")
|
||||
assert_false(resolver._card_matches_scale_filter(cheap_mage, filter), "Cheap Mage should not match")
|
||||
|
||||
cheap_warrior.free()
|
||||
expensive_warrior.free()
|
||||
cheap_mage.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# EMPTY/NULL FILTER TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_empty_filter_matches_all() -> void:
|
||||
var filter = {}
|
||||
var card = _create_mock_forward()
|
||||
|
||||
assert_true(resolver._card_matches_scale_filter(card, filter), "Empty filter should match any card")
|
||||
card.free()
|
||||
|
||||
|
||||
func test_null_card_returns_false() -> void:
|
||||
var filter = {"element": "FIRE"}
|
||||
|
||||
assert_false(resolver._card_matches_scale_filter(null, filter), "Null card should return false")
|
||||
|
||||
|
||||
func test_owner_only_filter_treated_as_empty() -> void:
|
||||
# Owner is used for collection selection, not card matching
|
||||
var filter = {"owner": "CONTROLLER"}
|
||||
var card = _create_mock_forward()
|
||||
|
||||
# The filter with only owner should effectively be empty for card matching
|
||||
assert_true(resolver._card_matches_scale_filter(card, filter), "Owner-only filter should match any card")
|
||||
card.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
func _create_mock_forward() -> CardInstance:
|
||||
var card = CardInstance.new()
|
||||
var data = CardDatabase.CardData.new()
|
||||
data.type = Enums.CardType.FORWARD
|
||||
data.cost = 3
|
||||
data.power = 7000
|
||||
data.name = "Test Forward"
|
||||
data.job = "Warrior"
|
||||
data.elements = [Enums.Element.FIRE]
|
||||
card.card_data = data
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_backup() -> CardInstance:
|
||||
var card = CardInstance.new()
|
||||
var data = CardDatabase.CardData.new()
|
||||
data.type = Enums.CardType.BACKUP
|
||||
data.cost = 2
|
||||
data.name = "Test Backup"
|
||||
data.job = "Scholar"
|
||||
data.elements = [Enums.Element.WATER]
|
||||
card.card_data = data
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_forward_with_element(element: Enums.Element) -> CardInstance:
|
||||
var card = _create_mock_forward()
|
||||
card.card_data.elements = [element]
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_backup_with_element(element: Enums.Element) -> CardInstance:
|
||||
var card = _create_mock_backup()
|
||||
card.card_data.elements = [element]
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_forward_with_job(job: String) -> CardInstance:
|
||||
var card = _create_mock_forward()
|
||||
card.card_data.job = job
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_forward_with_category(category: String) -> CardInstance:
|
||||
var card = _create_mock_forward()
|
||||
card.card_data.category = category
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_forward_with_cost(cost: int) -> CardInstance:
|
||||
var card = _create_mock_forward()
|
||||
card.card_data.cost = cost
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_forward_with_name(card_name: String) -> CardInstance:
|
||||
var card = _create_mock_forward()
|
||||
card.card_data.name = card_name
|
||||
return card
|
||||
|
||||
|
||||
func _create_mock_forward_with_job_and_cost(job: String, cost: int) -> CardInstance:
|
||||
var card = _create_mock_forward()
|
||||
card.card_data.job = job
|
||||
card.card_data.cost = cost
|
||||
return card
|
||||
363
tests/unit/test_modal_choice_system.gd
Normal file
363
tests/unit/test_modal_choice_system.gd
Normal file
@@ -0,0 +1,363 @@
|
||||
extends GutTest
|
||||
|
||||
## Tests for the Modal Choice System (CHOOSE_MODE effects)
|
||||
## Tests parser output, ChoiceModal UI, and AbilitySystem integration
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# PARSER OUTPUT TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_modal_effect_has_correct_structure() -> void:
|
||||
# Simulate a parsed CHOOSE_MODE effect
|
||||
var effect = {
|
||||
"type": "CHOOSE_MODE",
|
||||
"select_count": 1,
|
||||
"select_up_to": false,
|
||||
"mode_count": 3,
|
||||
"modes": [
|
||||
{
|
||||
"index": 0,
|
||||
"description": "Choose 1 Forward. Deal it 7000 damage.",
|
||||
"effects": [{"type": "DAMAGE", "amount": 7000}]
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"description": "Choose 1 Monster. Break it.",
|
||||
"effects": [{"type": "BREAK"}]
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"description": "Deal 3000 damage to all Forwards.",
|
||||
"effects": [{"type": "DAMAGE", "amount": 3000}]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
assert_eq(effect.type, "CHOOSE_MODE", "Effect type should be CHOOSE_MODE")
|
||||
assert_eq(effect.select_count, 1, "Should select 1 mode")
|
||||
assert_eq(effect.mode_count, 3, "Should have 3 modes")
|
||||
assert_eq(effect.modes.size(), 3, "Modes array should have 3 entries")
|
||||
assert_false(effect.select_up_to, "Should not be 'up to' selection")
|
||||
|
||||
|
||||
func test_modal_effect_with_enhanced_condition() -> void:
|
||||
var effect = {
|
||||
"type": "CHOOSE_MODE",
|
||||
"select_count": 1,
|
||||
"select_up_to": false,
|
||||
"mode_count": 3,
|
||||
"modes": [],
|
||||
"enhanced_condition": {
|
||||
"description": "if you have 5 or more ifrit in your break zone",
|
||||
"select_count": 3,
|
||||
"select_up_to": true
|
||||
}
|
||||
}
|
||||
|
||||
assert_true(effect.has("enhanced_condition"), "Should have enhanced condition")
|
||||
assert_eq(effect.enhanced_condition.select_count, 3, "Enhanced should allow 3 selections")
|
||||
assert_true(effect.enhanced_condition.select_up_to, "Enhanced should be 'up to'")
|
||||
|
||||
|
||||
func test_modal_mode_has_description_and_effects() -> void:
|
||||
var mode = {
|
||||
"index": 0,
|
||||
"description": "Draw 2 cards.",
|
||||
"effects": [{"type": "DRAW", "amount": 2}]
|
||||
}
|
||||
|
||||
assert_true(mode.has("description"), "Mode should have description")
|
||||
assert_true(mode.has("effects"), "Mode should have effects array")
|
||||
assert_eq(mode.effects.size(), 1, "Mode should have 1 effect")
|
||||
assert_eq(mode.effects[0].type, "DRAW", "Effect should be DRAW")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CHOICE MODAL TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_choice_modal_instantiates() -> void:
|
||||
var modal = ChoiceModal.new()
|
||||
assert_not_null(modal, "ChoiceModal should instantiate")
|
||||
modal.free()
|
||||
|
||||
|
||||
func test_choice_modal_starts_invisible() -> void:
|
||||
var modal = ChoiceModal.new()
|
||||
add_child_autofree(modal)
|
||||
|
||||
# Need to wait for _ready to complete
|
||||
await get_tree().process_frame
|
||||
|
||||
assert_false(modal.visible, "ChoiceModal should start invisible")
|
||||
|
||||
|
||||
func test_choice_modal_has_correct_layer() -> void:
|
||||
var modal = ChoiceModal.new()
|
||||
add_child_autofree(modal)
|
||||
|
||||
await get_tree().process_frame
|
||||
|
||||
assert_eq(modal.layer, 200, "ChoiceModal should be at layer 200")
|
||||
|
||||
|
||||
func test_choice_modal_signals_exist() -> void:
|
||||
var modal = ChoiceModal.new()
|
||||
|
||||
assert_true(modal.has_signal("choice_made"), "Should have choice_made signal")
|
||||
assert_true(modal.has_signal("choice_cancelled"), "Should have choice_cancelled signal")
|
||||
|
||||
modal.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# EFFECT RESOLVER TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_effect_resolver_has_choice_required_signal() -> void:
|
||||
var resolver = EffectResolver.new()
|
||||
|
||||
assert_true(resolver.has_signal("choice_required"), "Should have choice_required signal")
|
||||
|
||||
|
||||
func test_effect_resolver_emits_choice_required_for_choose_mode() -> void:
|
||||
var resolver = EffectResolver.new()
|
||||
var signal_received = false
|
||||
var received_effect = {}
|
||||
var received_modes = []
|
||||
|
||||
resolver.choice_required.connect(func(effect, modes):
|
||||
signal_received = true
|
||||
received_effect = effect
|
||||
received_modes = modes
|
||||
)
|
||||
|
||||
var effect = {
|
||||
"type": "CHOOSE_MODE",
|
||||
"modes": [
|
||||
{"index": 0, "description": "Option 1", "effects": []},
|
||||
{"index": 1, "description": "Option 2", "effects": []}
|
||||
]
|
||||
}
|
||||
|
||||
# Resolve the effect
|
||||
resolver.resolve(effect, null, [], null)
|
||||
|
||||
assert_true(signal_received, "Should emit choice_required signal")
|
||||
assert_eq(received_modes.size(), 2, "Should pass modes to signal")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# FIELD EFFECT MANAGER TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_field_effect_manager_block_immunity_empty() -> void:
|
||||
var manager = FieldEffectManager.new()
|
||||
|
||||
# With no abilities registered, should return false
|
||||
var result = manager.has_block_immunity(null, null, null)
|
||||
assert_false(result, "Should return false with no abilities")
|
||||
|
||||
|
||||
func test_field_effect_manager_attack_restriction_empty() -> void:
|
||||
var manager = FieldEffectManager.new()
|
||||
|
||||
var result = manager.has_attack_restriction(null, null)
|
||||
assert_false(result, "Should return false with no abilities")
|
||||
|
||||
|
||||
func test_field_effect_manager_block_restriction_empty() -> void:
|
||||
var manager = FieldEffectManager.new()
|
||||
|
||||
var result = manager.has_block_restriction(null, null)
|
||||
assert_false(result, "Should return false with no abilities")
|
||||
|
||||
|
||||
func test_field_effect_manager_taunt_targets_empty() -> void:
|
||||
var manager = FieldEffectManager.new()
|
||||
|
||||
var result = manager.get_taunt_targets(0, null)
|
||||
assert_eq(result.size(), 0, "Should return empty array with no abilities")
|
||||
|
||||
|
||||
func test_field_effect_manager_cost_modifier_empty() -> void:
|
||||
var manager = FieldEffectManager.new()
|
||||
|
||||
var result = manager.get_cost_modifier(null, 0, null)
|
||||
assert_eq(result, 0, "Should return 0 with no abilities")
|
||||
|
||||
|
||||
func test_field_effect_manager_max_attacks_default() -> void:
|
||||
var manager = FieldEffectManager.new()
|
||||
|
||||
var result = manager.get_max_attacks(null, null)
|
||||
assert_eq(result, 1, "Default max attacks should be 1")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# BLOCKER IMMUNITY CONDITION TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_blocker_immunity_condition_empty() -> void:
|
||||
var manager = FieldEffectManager.new()
|
||||
|
||||
# Empty condition means unconditional immunity
|
||||
var result = manager._blocker_matches_immunity_condition(null, {}, null)
|
||||
assert_true(result, "Empty condition should return true (unconditional)")
|
||||
|
||||
|
||||
func test_blocker_immunity_condition_cost_gte() -> void:
|
||||
var manager = FieldEffectManager.new()
|
||||
|
||||
# Create mock card data
|
||||
var blocker = _create_mock_card(3, 5000) # cost 3, power 5000
|
||||
|
||||
var condition = {
|
||||
"comparison": "GTE",
|
||||
"attribute": "cost",
|
||||
"value": 4
|
||||
}
|
||||
|
||||
var result = manager._blocker_matches_immunity_condition(blocker, condition, null)
|
||||
assert_false(result, "Cost 3 is not >= 4")
|
||||
|
||||
blocker.card_data.cost = 4
|
||||
result = manager._blocker_matches_immunity_condition(blocker, condition, null)
|
||||
assert_true(result, "Cost 4 is >= 4")
|
||||
|
||||
blocker.free()
|
||||
|
||||
|
||||
func test_blocker_immunity_condition_power_lt_self() -> void:
|
||||
var manager = FieldEffectManager.new()
|
||||
|
||||
var blocker = _create_mock_card(3, 5000)
|
||||
var attacker = _create_mock_card(4, 8000)
|
||||
|
||||
var condition = {
|
||||
"comparison": "LT",
|
||||
"attribute": "power",
|
||||
"compare_to": "SELF_POWER"
|
||||
}
|
||||
|
||||
# Blocker power 5000 < attacker power 8000
|
||||
var result = manager._blocker_matches_immunity_condition(blocker, condition, attacker)
|
||||
assert_true(result, "5000 < 8000 should be true")
|
||||
|
||||
# Change blocker power to be higher
|
||||
blocker.current_power = 9000
|
||||
result = manager._blocker_matches_immunity_condition(blocker, condition, attacker)
|
||||
assert_false(result, "9000 < 8000 should be false")
|
||||
|
||||
blocker.free()
|
||||
attacker.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# ABILITY SYSTEM INTEGRATION TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_ability_system_has_choice_modal_reference() -> void:
|
||||
# Check that AbilitySystem has the choice_modal variable
|
||||
var ability_system_script = load("res://scripts/game/abilities/AbilitySystem.gd")
|
||||
assert_not_null(ability_system_script, "AbilitySystem script should exist")
|
||||
|
||||
|
||||
func test_modes_selected_queues_effects() -> void:
|
||||
# This tests the logic of _on_modes_selected indirectly
|
||||
var effect = {
|
||||
"type": "CHOOSE_MODE",
|
||||
"modes": [
|
||||
{"index": 0, "description": "Deal damage", "effects": [{"type": "DAMAGE", "amount": 5000}]},
|
||||
{"index": 1, "description": "Draw cards", "effects": [{"type": "DRAW", "amount": 2}]}
|
||||
]
|
||||
}
|
||||
|
||||
var modes = effect.modes
|
||||
var selected_indices = [1] # Player selected option 2 (draw)
|
||||
|
||||
# Verify mode selection logic
|
||||
var queued_effects = []
|
||||
for index in selected_indices:
|
||||
if index >= 0 and index < modes.size():
|
||||
var mode = modes[index]
|
||||
for mode_effect in mode.get("effects", []):
|
||||
queued_effects.append(mode_effect)
|
||||
|
||||
assert_eq(queued_effects.size(), 1, "Should queue 1 effect")
|
||||
assert_eq(queued_effects[0].type, "DRAW", "Queued effect should be DRAW")
|
||||
assert_eq(queued_effects[0].amount, 2, "Draw amount should be 2")
|
||||
|
||||
|
||||
func test_multi_select_queues_multiple_effects() -> void:
|
||||
var effect = {
|
||||
"type": "CHOOSE_MODE",
|
||||
"select_count": 2,
|
||||
"modes": [
|
||||
{"index": 0, "effects": [{"type": "DAMAGE", "amount": 5000}]},
|
||||
{"index": 1, "effects": [{"type": "DRAW", "amount": 2}]},
|
||||
{"index": 2, "effects": [{"type": "BREAK"}]}
|
||||
]
|
||||
}
|
||||
|
||||
var modes = effect.modes
|
||||
var selected_indices = [0, 2] # Player selected damage and break
|
||||
|
||||
var queued_effects = []
|
||||
for index in selected_indices:
|
||||
if index >= 0 and index < modes.size():
|
||||
var mode = modes[index]
|
||||
for mode_effect in mode.get("effects", []):
|
||||
queued_effects.append(mode_effect)
|
||||
|
||||
assert_eq(queued_effects.size(), 2, "Should queue 2 effects")
|
||||
assert_eq(queued_effects[0].type, "DAMAGE", "First effect should be DAMAGE")
|
||||
assert_eq(queued_effects[1].type, "BREAK", "Second effect should be BREAK")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# ENHANCED CONDITION TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_enhanced_condition_parsing() -> void:
|
||||
var condition = {
|
||||
"description": "if you have a total of 5 or more card name ifrita and/or card name ifrit in your break zone",
|
||||
"select_count": 3,
|
||||
"select_up_to": true
|
||||
}
|
||||
|
||||
# Test that description contains expected keywords
|
||||
assert_true("break zone" in condition.description, "Should mention break zone")
|
||||
assert_true("5 or more" in condition.description, "Should mention count requirement")
|
||||
|
||||
|
||||
func test_regex_count_extraction() -> void:
|
||||
var description = "if you have 5 or more ifrit in your break zone"
|
||||
|
||||
var regex = RegEx.new()
|
||||
regex.compile(r"(\d+) or more")
|
||||
var match_result = regex.search(description)
|
||||
|
||||
assert_not_null(match_result, "Should find count pattern")
|
||||
assert_eq(match_result.get_string(1), "5", "Should extract '5'")
|
||||
assert_eq(int(match_result.get_string(1)), 5, "Should convert to int 5")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
func _create_mock_card(cost: int, power: int) -> CardInstance:
|
||||
var card = CardInstance.new()
|
||||
|
||||
# Create minimal card data
|
||||
var data = CardDatabase.CardData.new()
|
||||
data.cost = cost
|
||||
data.power = power
|
||||
data.type = Enums.CardType.FORWARD
|
||||
|
||||
card.card_data = data
|
||||
card.current_power = power
|
||||
|
||||
return card
|
||||
293
tests/unit/test_network_signals.gd
Normal file
293
tests/unit/test_network_signals.gd
Normal file
@@ -0,0 +1,293 @@
|
||||
extends GutTest
|
||||
|
||||
## Unit tests for NetworkManager signal emissions
|
||||
## Tests signal behavior without requiring actual network connections
|
||||
|
||||
|
||||
var network_manager: NetworkManager
|
||||
var signal_received: bool = false
|
||||
var last_signal_data: Variant = null
|
||||
|
||||
|
||||
func before_each():
|
||||
# Create a fresh NetworkManager instance for testing
|
||||
network_manager = NetworkManager.new()
|
||||
add_child_autoqfree(network_manager)
|
||||
signal_received = false
|
||||
last_signal_data = null
|
||||
|
||||
|
||||
func after_each():
|
||||
# Cleanup handled by autoqfree
|
||||
pass
|
||||
|
||||
|
||||
# ======= HELPER FUNCTIONS =======
|
||||
|
||||
func _connect_signal_and_track(signal_obj: Signal) -> void:
|
||||
signal_obj.connect(_on_signal_received)
|
||||
|
||||
|
||||
func _on_signal_received(data = null) -> void:
|
||||
signal_received = true
|
||||
last_signal_data = data
|
||||
|
||||
|
||||
# ======= CONNECTION STATE SIGNAL TESTS =======
|
||||
|
||||
func test_connection_state_changed_signal_exists():
|
||||
assert_has_signal(network_manager, "connection_state_changed")
|
||||
|
||||
|
||||
func test_authenticated_signal_exists():
|
||||
assert_has_signal(network_manager, "authenticated")
|
||||
|
||||
|
||||
func test_authentication_failed_signal_exists():
|
||||
assert_has_signal(network_manager, "authentication_failed")
|
||||
|
||||
|
||||
func test_logged_out_signal_exists():
|
||||
assert_has_signal(network_manager, "logged_out")
|
||||
|
||||
|
||||
func test_network_error_signal_exists():
|
||||
assert_has_signal(network_manager, "network_error")
|
||||
|
||||
|
||||
# ======= MATCHMAKING SIGNAL TESTS =======
|
||||
|
||||
func test_queue_joined_signal_exists():
|
||||
assert_has_signal(network_manager, "queue_joined")
|
||||
|
||||
|
||||
func test_queue_left_signal_exists():
|
||||
assert_has_signal(network_manager, "queue_left")
|
||||
|
||||
|
||||
func test_match_found_signal_exists():
|
||||
assert_has_signal(network_manager, "match_found")
|
||||
|
||||
|
||||
func test_room_created_signal_exists():
|
||||
assert_has_signal(network_manager, "room_created")
|
||||
|
||||
|
||||
func test_room_joined_signal_exists():
|
||||
assert_has_signal(network_manager, "room_joined")
|
||||
|
||||
|
||||
func test_room_updated_signal_exists():
|
||||
assert_has_signal(network_manager, "room_updated")
|
||||
|
||||
|
||||
func test_matchmaking_update_signal_exists():
|
||||
assert_has_signal(network_manager, "matchmaking_update")
|
||||
|
||||
|
||||
# ======= GAME MESSAGE SIGNAL TESTS =======
|
||||
|
||||
func test_opponent_action_received_signal_exists():
|
||||
assert_has_signal(network_manager, "opponent_action_received")
|
||||
|
||||
|
||||
func test_turn_timer_update_signal_exists():
|
||||
assert_has_signal(network_manager, "turn_timer_update")
|
||||
|
||||
|
||||
func test_game_started_signal_exists():
|
||||
assert_has_signal(network_manager, "game_started")
|
||||
|
||||
|
||||
func test_game_ended_signal_exists():
|
||||
assert_has_signal(network_manager, "game_ended")
|
||||
|
||||
|
||||
func test_phase_changed_signal_exists():
|
||||
assert_has_signal(network_manager, "phase_changed")
|
||||
|
||||
|
||||
func test_action_confirmed_signal_exists():
|
||||
assert_has_signal(network_manager, "action_confirmed")
|
||||
|
||||
|
||||
func test_action_failed_signal_exists():
|
||||
assert_has_signal(network_manager, "action_failed")
|
||||
|
||||
|
||||
func test_opponent_disconnected_signal_exists():
|
||||
assert_has_signal(network_manager, "opponent_disconnected")
|
||||
|
||||
|
||||
func test_opponent_reconnected_signal_exists():
|
||||
assert_has_signal(network_manager, "opponent_reconnected")
|
||||
|
||||
|
||||
func test_game_state_sync_signal_exists():
|
||||
assert_has_signal(network_manager, "game_state_sync")
|
||||
|
||||
|
||||
# ======= CONNECTION STATE ENUM TESTS =======
|
||||
|
||||
func test_connection_state_disconnected_value():
|
||||
assert_eq(NetworkManager.ConnectionState.DISCONNECTED, 0)
|
||||
|
||||
|
||||
func test_connection_state_connecting_value():
|
||||
assert_eq(NetworkManager.ConnectionState.CONNECTING, 1)
|
||||
|
||||
|
||||
func test_connection_state_connected_value():
|
||||
assert_eq(NetworkManager.ConnectionState.CONNECTED, 2)
|
||||
|
||||
|
||||
func test_connection_state_authenticating_value():
|
||||
assert_eq(NetworkManager.ConnectionState.AUTHENTICATING, 3)
|
||||
|
||||
|
||||
func test_connection_state_authenticated_value():
|
||||
assert_eq(NetworkManager.ConnectionState.AUTHENTICATED, 4)
|
||||
|
||||
|
||||
func test_connection_state_in_queue_value():
|
||||
assert_eq(NetworkManager.ConnectionState.IN_QUEUE, 5)
|
||||
|
||||
|
||||
func test_connection_state_in_room_value():
|
||||
assert_eq(NetworkManager.ConnectionState.IN_ROOM, 6)
|
||||
|
||||
|
||||
func test_connection_state_in_game_value():
|
||||
assert_eq(NetworkManager.ConnectionState.IN_GAME, 7)
|
||||
|
||||
|
||||
# ======= INITIAL STATE TESTS =======
|
||||
|
||||
func test_initial_connection_state_is_disconnected():
|
||||
assert_eq(network_manager.connection_state, NetworkManager.ConnectionState.DISCONNECTED)
|
||||
|
||||
|
||||
func test_initial_auth_token_is_empty():
|
||||
assert_eq(network_manager.auth_token, "")
|
||||
|
||||
|
||||
func test_initial_is_authenticated_is_false():
|
||||
assert_false(network_manager.is_authenticated)
|
||||
|
||||
|
||||
func test_initial_current_game_id_is_empty():
|
||||
assert_eq(network_manager.current_game_id, "")
|
||||
|
||||
|
||||
func test_initial_current_room_code_is_empty():
|
||||
assert_eq(network_manager.current_room_code, "")
|
||||
|
||||
|
||||
func test_initial_local_player_index_is_zero():
|
||||
assert_eq(network_manager.local_player_index, 0)
|
||||
|
||||
|
||||
# ======= LOGOUT TESTS =======
|
||||
|
||||
func test_logout_clears_auth_token():
|
||||
network_manager.auth_token = "test_token"
|
||||
network_manager.logout()
|
||||
assert_eq(network_manager.auth_token, "")
|
||||
|
||||
|
||||
func test_logout_clears_current_user():
|
||||
network_manager.current_user = { "id": "123", "username": "test" }
|
||||
network_manager.logout()
|
||||
assert_eq(network_manager.current_user, {})
|
||||
|
||||
|
||||
func test_logout_sets_is_authenticated_false():
|
||||
network_manager.is_authenticated = true
|
||||
network_manager.logout()
|
||||
assert_false(network_manager.is_authenticated)
|
||||
|
||||
|
||||
func test_logout_emits_logged_out_signal():
|
||||
watch_signals(network_manager)
|
||||
network_manager.logout()
|
||||
assert_signal_emitted(network_manager, "logged_out")
|
||||
|
||||
|
||||
# ======= CONNECTION STATE NAME TESTS =======
|
||||
|
||||
func test_get_connection_state_name_disconnected():
|
||||
network_manager.connection_state = NetworkManager.ConnectionState.DISCONNECTED
|
||||
assert_eq(network_manager.get_connection_state_name(), "Disconnected")
|
||||
|
||||
|
||||
func test_get_connection_state_name_connecting():
|
||||
network_manager.connection_state = NetworkManager.ConnectionState.CONNECTING
|
||||
assert_eq(network_manager.get_connection_state_name(), "Connecting")
|
||||
|
||||
|
||||
func test_get_connection_state_name_connected():
|
||||
network_manager.connection_state = NetworkManager.ConnectionState.CONNECTED
|
||||
assert_eq(network_manager.get_connection_state_name(), "Connected")
|
||||
|
||||
|
||||
func test_get_connection_state_name_authenticating():
|
||||
network_manager.connection_state = NetworkManager.ConnectionState.AUTHENTICATING
|
||||
assert_eq(network_manager.get_connection_state_name(), "Authenticating")
|
||||
|
||||
|
||||
func test_get_connection_state_name_authenticated():
|
||||
network_manager.connection_state = NetworkManager.ConnectionState.AUTHENTICATED
|
||||
assert_eq(network_manager.get_connection_state_name(), "Online")
|
||||
|
||||
|
||||
func test_get_connection_state_name_in_queue():
|
||||
network_manager.connection_state = NetworkManager.ConnectionState.IN_QUEUE
|
||||
assert_eq(network_manager.get_connection_state_name(), "In Queue")
|
||||
|
||||
|
||||
func test_get_connection_state_name_in_room():
|
||||
network_manager.connection_state = NetworkManager.ConnectionState.IN_ROOM
|
||||
assert_eq(network_manager.get_connection_state_name(), "In Room")
|
||||
|
||||
|
||||
func test_get_connection_state_name_in_game():
|
||||
network_manager.connection_state = NetworkManager.ConnectionState.IN_GAME
|
||||
assert_eq(network_manager.get_connection_state_name(), "In Game")
|
||||
|
||||
|
||||
# ======= CONFIGURATION TESTS =======
|
||||
|
||||
func test_configure_sets_http_url():
|
||||
network_manager.configure("http://test.com", "ws://test.com")
|
||||
assert_eq(network_manager.http_base_url, "http://test.com")
|
||||
|
||||
|
||||
func test_configure_sets_ws_url():
|
||||
network_manager.configure("http://test.com", "ws://test.com:3001")
|
||||
assert_eq(network_manager.ws_url, "ws://test.com:3001")
|
||||
|
||||
|
||||
func test_default_http_url():
|
||||
assert_eq(network_manager.http_base_url, "http://localhost:3000")
|
||||
|
||||
|
||||
func test_default_ws_url():
|
||||
assert_eq(network_manager.ws_url, "ws://localhost:3001")
|
||||
|
||||
|
||||
# ======= CONSTANTS TESTS =======
|
||||
|
||||
func test_heartbeat_interval_constant():
|
||||
assert_eq(NetworkManager.HEARTBEAT_INTERVAL, 10.0)
|
||||
|
||||
|
||||
func test_reconnect_delay_constant():
|
||||
assert_eq(NetworkManager.RECONNECT_DELAY, 5.0)
|
||||
|
||||
|
||||
func test_max_reconnect_attempts_constant():
|
||||
assert_eq(NetworkManager.MAX_RECONNECT_ATTEMPTS, 3)
|
||||
|
||||
|
||||
func test_token_file_constant():
|
||||
assert_eq(NetworkManager.TOKEN_FILE, "user://auth_token.dat")
|
||||
280
tests/unit/test_online_game_helpers.gd
Normal file
280
tests/unit/test_online_game_helpers.gd
Normal file
@@ -0,0 +1,280 @@
|
||||
extends GutTest
|
||||
|
||||
## Unit tests for online game helper functions in Main.gd
|
||||
## Tests the logic for determining if local player can act
|
||||
|
||||
|
||||
# Mock classes for testing
|
||||
class MockTurnManager:
|
||||
var current_player_index: int = 0
|
||||
var turn_number: int = 1
|
||||
var current_phase: int = 0
|
||||
|
||||
|
||||
class MockGameState:
|
||||
var turn_manager: MockTurnManager
|
||||
|
||||
func _init():
|
||||
turn_manager = MockTurnManager.new()
|
||||
|
||||
|
||||
# Simulated Main.gd helper functions for testing
|
||||
# These mirror the actual functions in Main.gd
|
||||
|
||||
var is_online_game: bool = false
|
||||
var local_player_index: int = 0
|
||||
var mock_game_state: MockGameState = null
|
||||
|
||||
|
||||
func before_each():
|
||||
is_online_game = false
|
||||
local_player_index = 0
|
||||
mock_game_state = MockGameState.new()
|
||||
|
||||
|
||||
# Helper function that mirrors Main.gd._is_local_player_turn()
|
||||
func _is_local_player_turn() -> bool:
|
||||
if not is_online_game:
|
||||
return true
|
||||
if not mock_game_state:
|
||||
return true
|
||||
return mock_game_state.turn_manager.current_player_index == local_player_index
|
||||
|
||||
|
||||
# Helper function that mirrors Main.gd._can_perform_local_action()
|
||||
func _can_perform_local_action() -> bool:
|
||||
if not is_online_game:
|
||||
return true
|
||||
return _is_local_player_turn()
|
||||
|
||||
|
||||
# ======= _is_local_player_turn() Tests =======
|
||||
|
||||
func test_is_local_player_turn_returns_true_when_not_online():
|
||||
is_online_game = false
|
||||
mock_game_state.turn_manager.current_player_index = 1
|
||||
local_player_index = 0
|
||||
|
||||
assert_true(_is_local_player_turn(), "Should return true when not in online game")
|
||||
|
||||
|
||||
func test_is_local_player_turn_returns_true_when_no_game_state():
|
||||
is_online_game = true
|
||||
mock_game_state = null
|
||||
|
||||
assert_true(_is_local_player_turn(), "Should return true when game_state is null")
|
||||
|
||||
|
||||
func test_is_local_player_turn_returns_true_when_local_index_matches():
|
||||
is_online_game = true
|
||||
local_player_index = 0
|
||||
mock_game_state.turn_manager.current_player_index = 0
|
||||
|
||||
assert_true(_is_local_player_turn(), "Should return true when it's local player's turn")
|
||||
|
||||
|
||||
func test_is_local_player_turn_returns_false_when_opponent_turn():
|
||||
is_online_game = true
|
||||
local_player_index = 0
|
||||
mock_game_state.turn_manager.current_player_index = 1
|
||||
|
||||
assert_false(_is_local_player_turn(), "Should return false when it's opponent's turn")
|
||||
|
||||
|
||||
func test_is_local_player_turn_player_1_perspective():
|
||||
is_online_game = true
|
||||
local_player_index = 1
|
||||
mock_game_state.turn_manager.current_player_index = 1
|
||||
|
||||
assert_true(_is_local_player_turn(), "Player 1 should be able to act on their turn")
|
||||
|
||||
|
||||
func test_is_local_player_turn_player_1_opponent_turn():
|
||||
is_online_game = true
|
||||
local_player_index = 1
|
||||
mock_game_state.turn_manager.current_player_index = 0
|
||||
|
||||
assert_false(_is_local_player_turn(), "Player 1 should not act on opponent's turn")
|
||||
|
||||
|
||||
# ======= _can_perform_local_action() Tests =======
|
||||
|
||||
func test_can_perform_local_action_delegates_to_is_local_player_turn_online():
|
||||
is_online_game = true
|
||||
local_player_index = 0
|
||||
mock_game_state.turn_manager.current_player_index = 0
|
||||
|
||||
assert_true(_can_perform_local_action())
|
||||
|
||||
mock_game_state.turn_manager.current_player_index = 1
|
||||
assert_false(_can_perform_local_action())
|
||||
|
||||
|
||||
func test_can_perform_local_action_always_true_offline():
|
||||
is_online_game = false
|
||||
local_player_index = 0
|
||||
mock_game_state.turn_manager.current_player_index = 1
|
||||
|
||||
assert_true(_can_perform_local_action(), "Should always be able to act in offline game")
|
||||
|
||||
|
||||
# ======= Player Index Validation Tests =======
|
||||
|
||||
func test_valid_player_index_0():
|
||||
var player_index = 0
|
||||
assert_true(player_index >= 0 and player_index <= 1, "Player index 0 should be valid")
|
||||
|
||||
|
||||
func test_valid_player_index_1():
|
||||
var player_index = 1
|
||||
assert_true(player_index >= 0 and player_index <= 1, "Player index 1 should be valid")
|
||||
|
||||
|
||||
func test_invalid_player_index_negative():
|
||||
var player_index = -1
|
||||
assert_false(player_index >= 0 and player_index <= 1, "Negative player index should be invalid")
|
||||
|
||||
|
||||
func test_invalid_player_index_too_high():
|
||||
var player_index = 2
|
||||
assert_false(player_index >= 0 and player_index <= 1, "Player index > 1 should be invalid")
|
||||
|
||||
|
||||
# ======= Turn Timer Format Tests =======
|
||||
|
||||
func test_timer_format_full_minutes():
|
||||
var seconds = 120
|
||||
var minutes = seconds / 60
|
||||
var secs = seconds % 60
|
||||
var formatted = "%d:%02d" % [minutes, secs]
|
||||
|
||||
assert_eq(formatted, "2:00")
|
||||
|
||||
|
||||
func test_timer_format_partial_minutes():
|
||||
var seconds = 90
|
||||
var minutes = seconds / 60
|
||||
var secs = seconds % 60
|
||||
var formatted = "%d:%02d" % [minutes, secs]
|
||||
|
||||
assert_eq(formatted, "1:30")
|
||||
|
||||
|
||||
func test_timer_format_under_minute():
|
||||
var seconds = 45
|
||||
var minutes = seconds / 60
|
||||
var secs = seconds % 60
|
||||
var formatted = "%d:%02d" % [minutes, secs]
|
||||
|
||||
assert_eq(formatted, "0:45")
|
||||
|
||||
|
||||
func test_timer_format_single_digit_seconds():
|
||||
var seconds = 65
|
||||
var minutes = seconds / 60
|
||||
var secs = seconds % 60
|
||||
var formatted = "%d:%02d" % [minutes, secs]
|
||||
|
||||
assert_eq(formatted, "1:05")
|
||||
|
||||
|
||||
func test_timer_format_zero():
|
||||
var seconds = 0
|
||||
var minutes = seconds / 60
|
||||
var secs = seconds % 60
|
||||
var formatted = "%d:%02d" % [minutes, secs]
|
||||
|
||||
assert_eq(formatted, "0:00")
|
||||
|
||||
|
||||
# ======= Timer Color Thresholds Tests =======
|
||||
|
||||
func test_timer_color_threshold_critical():
|
||||
var seconds = 10
|
||||
var is_critical = seconds <= 10
|
||||
|
||||
assert_true(is_critical, "10 seconds should be critical (red)")
|
||||
|
||||
|
||||
func test_timer_color_threshold_warning():
|
||||
var seconds = 30
|
||||
var is_warning = seconds > 10 and seconds <= 30
|
||||
|
||||
assert_true(is_warning, "30 seconds should be warning (yellow)")
|
||||
|
||||
|
||||
func test_timer_color_threshold_normal():
|
||||
var seconds = 60
|
||||
var is_normal = seconds > 30
|
||||
|
||||
assert_true(is_normal, "60 seconds should be normal (white)")
|
||||
|
||||
|
||||
func test_timer_color_threshold_boundary_10():
|
||||
var seconds = 10
|
||||
var is_critical = seconds <= 10
|
||||
|
||||
assert_true(is_critical, "Exactly 10 should be critical")
|
||||
|
||||
|
||||
func test_timer_color_threshold_boundary_11():
|
||||
var seconds = 11
|
||||
var is_warning = seconds > 10 and seconds <= 30
|
||||
|
||||
assert_true(is_warning, "11 should be warning, not critical")
|
||||
|
||||
|
||||
func test_timer_color_threshold_boundary_30():
|
||||
var seconds = 30
|
||||
var is_warning = seconds > 10 and seconds <= 30
|
||||
|
||||
assert_true(is_warning, "Exactly 30 should be warning")
|
||||
|
||||
|
||||
func test_timer_color_threshold_boundary_31():
|
||||
var seconds = 31
|
||||
var is_normal = seconds > 30
|
||||
|
||||
assert_true(is_normal, "31 should be normal, not warning")
|
||||
|
||||
|
||||
# ======= Online Game State Sync Tests =======
|
||||
|
||||
func test_game_state_sync_updates_current_player():
|
||||
var state = { "current_player_index": 1, "current_phase": 2, "turn_number": 3 }
|
||||
|
||||
mock_game_state.turn_manager.current_player_index = state.get("current_player_index", 0)
|
||||
|
||||
assert_eq(mock_game_state.turn_manager.current_player_index, 1)
|
||||
|
||||
|
||||
func test_game_state_sync_updates_phase():
|
||||
var state = { "current_player_index": 0, "current_phase": 3, "turn_number": 1 }
|
||||
|
||||
mock_game_state.turn_manager.current_phase = state.get("current_phase", 0)
|
||||
|
||||
assert_eq(mock_game_state.turn_manager.current_phase, 3)
|
||||
|
||||
|
||||
func test_game_state_sync_updates_turn_number():
|
||||
var state = { "current_player_index": 0, "current_phase": 0, "turn_number": 5 }
|
||||
|
||||
mock_game_state.turn_manager.turn_number = state.get("turn_number", 1)
|
||||
|
||||
assert_eq(mock_game_state.turn_manager.turn_number, 5)
|
||||
|
||||
|
||||
func test_game_state_sync_timer_extraction():
|
||||
var state = { "turn_timer_seconds": 90 }
|
||||
|
||||
var timer_seconds = state.get("turn_timer_seconds", 120)
|
||||
|
||||
assert_eq(timer_seconds, 90)
|
||||
|
||||
|
||||
func test_game_state_sync_timer_default():
|
||||
var state = {}
|
||||
|
||||
var timer_seconds = state.get("turn_timer_seconds", 120)
|
||||
|
||||
assert_eq(timer_seconds, 120, "Should default to 120 seconds")
|
||||
191
tests/unit/test_optional_effects.gd
Normal file
191
tests/unit/test_optional_effects.gd
Normal file
@@ -0,0 +1,191 @@
|
||||
extends GutTest
|
||||
|
||||
## Tests for optional effect prompting in AbilitySystem
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# BUILD EFFECT DESCRIPTION TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_build_description_draw_single() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var effect = {"type": "DRAW", "amount": 1}
|
||||
|
||||
var description = ability_system._build_effect_description(effect)
|
||||
|
||||
assert_eq(description, "Draw 1 card", "Should describe single card draw")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_build_description_draw_multiple() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var effect = {"type": "DRAW", "amount": 3}
|
||||
|
||||
var description = ability_system._build_effect_description(effect)
|
||||
|
||||
assert_eq(description, "Draw 3 cards", "Should describe multiple card draw with plural")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_build_description_damage() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var effect = {"type": "DAMAGE", "amount": 5000}
|
||||
|
||||
var description = ability_system._build_effect_description(effect)
|
||||
|
||||
assert_eq(description, "Deal 5000 damage", "Should describe damage amount")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_build_description_power_mod_positive() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var effect = {"type": "POWER_MOD", "amount": 2000}
|
||||
|
||||
var description = ability_system._build_effect_description(effect)
|
||||
|
||||
assert_eq(description, "Give +2000 power", "Should describe positive power mod with +")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_build_description_power_mod_negative() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var effect = {"type": "POWER_MOD", "amount": -3000}
|
||||
|
||||
var description = ability_system._build_effect_description(effect)
|
||||
|
||||
assert_eq(description, "Give -3000 power", "Should describe negative power mod")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_build_description_dull() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var effect = {"type": "DULL"}
|
||||
|
||||
var description = ability_system._build_effect_description(effect)
|
||||
|
||||
assert_eq(description, "Dull a Forward", "Should describe dull effect")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_build_description_activate() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var effect = {"type": "ACTIVATE"}
|
||||
|
||||
var description = ability_system._build_effect_description(effect)
|
||||
|
||||
assert_eq(description, "Activate a card", "Should describe activate effect")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_build_description_break() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var effect = {"type": "BREAK"}
|
||||
|
||||
var description = ability_system._build_effect_description(effect)
|
||||
|
||||
assert_eq(description, "Break a card", "Should describe break effect")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_build_description_return() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var effect = {"type": "RETURN"}
|
||||
|
||||
var description = ability_system._build_effect_description(effect)
|
||||
|
||||
assert_eq(description, "Return a card to hand", "Should describe return effect")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_build_description_search() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var effect = {"type": "SEARCH"}
|
||||
|
||||
var description = ability_system._build_effect_description(effect)
|
||||
|
||||
assert_eq(description, "Search your deck", "Should describe search effect")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_build_description_discard_single() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var effect = {"type": "DISCARD", "amount": 1}
|
||||
|
||||
var description = ability_system._build_effect_description(effect)
|
||||
|
||||
assert_eq(description, "Discard 1 card", "Should describe single discard")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_build_description_discard_multiple() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var effect = {"type": "DISCARD", "amount": 2}
|
||||
|
||||
var description = ability_system._build_effect_description(effect)
|
||||
|
||||
assert_eq(description, "Discard 2 cards", "Should describe multiple discard with plural")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_build_description_uses_original_text() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var effect = {"type": "UNKNOWN_EFFECT", "original_text": "Do something special"}
|
||||
|
||||
var description = ability_system._build_effect_description(effect)
|
||||
|
||||
assert_eq(description, "Do something special", "Should use original_text for unknown effects")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
func test_build_description_fallback() -> void:
|
||||
var ability_system = AbilitySystem.new()
|
||||
var effect = {"type": "UNKNOWN_EFFECT"}
|
||||
|
||||
var description = ability_system._build_effect_description(effect)
|
||||
|
||||
assert_eq(description, "Use this effect", "Should use fallback for unknown effects without original_text")
|
||||
ability_system.free()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# OPTIONAL EFFECT FLAG TESTS
|
||||
# =============================================================================
|
||||
|
||||
func test_optional_flag_is_detected() -> void:
|
||||
var effect_with_optional = {"type": "DRAW", "amount": 1, "optional": true}
|
||||
var effect_without_optional = {"type": "DRAW", "amount": 1}
|
||||
|
||||
assert_true(effect_with_optional.get("optional", false), "Should detect optional flag")
|
||||
assert_false(effect_without_optional.get("optional", false), "Should default to false")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# INTEGRATION TESTS (Signal Emission)
|
||||
# =============================================================================
|
||||
|
||||
func test_optional_effect_signal_contains_correct_data() -> void:
|
||||
# This test verifies the signal parameters are correct
|
||||
var ability_system = AbilitySystem.new()
|
||||
var received_player_index = -1
|
||||
var received_effect = {}
|
||||
var received_description = ""
|
||||
var received_callback = null
|
||||
|
||||
ability_system.optional_effect_prompt.connect(func(player_index, effect, description, callback):
|
||||
received_player_index = player_index
|
||||
received_effect = effect
|
||||
received_description = description
|
||||
received_callback = callback
|
||||
)
|
||||
|
||||
# Manually emit to test signal structure
|
||||
var test_effect = {"type": "DRAW", "amount": 2, "optional": true}
|
||||
var test_callback = func(_accepted): pass
|
||||
ability_system.optional_effect_prompt.emit(0, test_effect, "Draw 2 cards", test_callback)
|
||||
|
||||
assert_eq(received_player_index, 0, "Player index should be passed")
|
||||
assert_eq(received_effect.type, "DRAW", "Effect should be passed")
|
||||
assert_eq(received_description, "Draw 2 cards", "Description should be passed")
|
||||
assert_not_null(received_callback, "Callback should be passed")
|
||||
|
||||
ability_system.free()
|
||||
Reference in New Issue
Block a user