feature updates

This commit is contained in:
2026-02-02 16:28:53 -05:00
parent bf9aa3fa23
commit 44c06530ac
83 changed files with 282641 additions and 11251 deletions

View 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")

View 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)")

View 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

View 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

View 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)

View 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

View 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

View 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

View 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")

View 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")

View 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()