575 lines
15 KiB
GDScript
575 lines
15 KiB
GDScript
extends SceneTree
|
|
|
|
## Test suite for Match3Gameplay
|
|
##
|
|
## Tests grid initialization, match detection, and scoring system.
|
|
|
|
const TestHelperClass = preload("res://tests/helpers/TestHelper.gd")
|
|
|
|
var match3_scene: PackedScene
|
|
var match3_instance: Node2D
|
|
var test_viewport: SubViewport
|
|
|
|
|
|
func _initialize():
|
|
# Wait for autoloads to initialize
|
|
await process_frame
|
|
await process_frame
|
|
|
|
run_tests()
|
|
|
|
# Exit after tests complete
|
|
quit()
|
|
|
|
|
|
func run_tests():
|
|
TestHelperClass.print_test_header("Match3 Gameplay")
|
|
|
|
# Setup test environment
|
|
setup_test_environment()
|
|
|
|
# Run test suites
|
|
test_basic_functionality()
|
|
test_constants_and_safety_limits()
|
|
test_grid_initialization()
|
|
test_grid_layout_calculation()
|
|
test_state_management()
|
|
test_match_detection()
|
|
test_scoring_system()
|
|
test_input_validation()
|
|
test_memory_safety()
|
|
test_performance_requirements()
|
|
|
|
# Cleanup
|
|
cleanup_tests()
|
|
|
|
TestHelperClass.print_test_footer("Match3 Gameplay")
|
|
|
|
|
|
func setup_test_environment():
|
|
TestHelperClass.print_step("Test Environment Setup")
|
|
|
|
# Load Match3 scene
|
|
match3_scene = load("res://scenes/game/gameplays/Match3Gameplay.tscn")
|
|
TestHelperClass.assert_not_null(
|
|
match3_scene, "Match3 scene loads successfully"
|
|
)
|
|
|
|
# Create test viewport for isolated testing
|
|
test_viewport = SubViewport.new()
|
|
test_viewport.size = Vector2i(800, 600)
|
|
root.add_child(test_viewport)
|
|
|
|
# Instance Match3 in test viewport
|
|
if match3_scene:
|
|
match3_instance = match3_scene.instantiate()
|
|
test_viewport.add_child(match3_instance)
|
|
TestHelperClass.assert_not_null(
|
|
match3_instance, "Match3 instance created successfully"
|
|
)
|
|
|
|
# Wait for initialization
|
|
await process_frame
|
|
await process_frame
|
|
|
|
|
|
func test_basic_functionality():
|
|
TestHelperClass.print_step("Basic Functionality")
|
|
|
|
if not match3_instance:
|
|
TestHelperClass.assert_true(
|
|
false, "Match3 instance not available for testing"
|
|
)
|
|
return
|
|
|
|
# Test that Match3 has expected properties
|
|
var expected_properties = [
|
|
"GRID_SIZE",
|
|
"TILE_TYPES",
|
|
"grid",
|
|
"current_state",
|
|
"selected_tile",
|
|
"cursor_position"
|
|
]
|
|
for prop in expected_properties:
|
|
TestHelperClass.assert_true(
|
|
prop in match3_instance, "Match3 has property: " + prop
|
|
)
|
|
|
|
# Test that Match3 has expected methods
|
|
var expected_methods = [
|
|
"_has_match_at",
|
|
"_check_for_matches",
|
|
"_get_match_line",
|
|
"_clear_matches"
|
|
]
|
|
TestHelperClass.assert_has_methods(
|
|
match3_instance, expected_methods, "Match3 gameplay methods"
|
|
)
|
|
|
|
# Test signals
|
|
TestHelperClass.assert_true(
|
|
match3_instance.has_signal("score_changed"),
|
|
"Match3 has score_changed signal"
|
|
)
|
|
TestHelperClass.assert_true(
|
|
match3_instance.has_signal("grid_state_loaded"),
|
|
"Match3 has grid_state_loaded signal"
|
|
)
|
|
|
|
|
|
func test_constants_and_safety_limits():
|
|
TestHelperClass.print_step("Constants and Safety Limits")
|
|
|
|
if not match3_instance:
|
|
return
|
|
|
|
# Test safety constants exist
|
|
TestHelperClass.assert_true(
|
|
"MAX_GRID_SIZE" in match3_instance, "MAX_GRID_SIZE constant exists"
|
|
)
|
|
TestHelperClass.assert_true(
|
|
"MAX_TILE_TYPES" in match3_instance, "MAX_TILE_TYPES constant exists"
|
|
)
|
|
TestHelperClass.assert_true(
|
|
"MAX_CASCADE_ITERATIONS" in match3_instance,
|
|
"MAX_CASCADE_ITERATIONS constant exists"
|
|
)
|
|
TestHelperClass.assert_true(
|
|
"MIN_GRID_SIZE" in match3_instance, "MIN_GRID_SIZE constant exists"
|
|
)
|
|
TestHelperClass.assert_true(
|
|
"MIN_TILE_TYPES" in match3_instance, "MIN_TILE_TYPES constant exists"
|
|
)
|
|
|
|
# Test safety limit values are reasonable
|
|
TestHelperClass.assert_equal(
|
|
15, match3_instance.MAX_GRID_SIZE, "MAX_GRID_SIZE is reasonable"
|
|
)
|
|
TestHelperClass.assert_equal(
|
|
10, match3_instance.MAX_TILE_TYPES, "MAX_TILE_TYPES is reasonable"
|
|
)
|
|
TestHelperClass.assert_equal(
|
|
20,
|
|
match3_instance.MAX_CASCADE_ITERATIONS,
|
|
"MAX_CASCADE_ITERATIONS prevents infinite loops"
|
|
)
|
|
TestHelperClass.assert_equal(
|
|
3, match3_instance.MIN_GRID_SIZE, "MIN_GRID_SIZE is reasonable"
|
|
)
|
|
TestHelperClass.assert_equal(
|
|
3, match3_instance.MIN_TILE_TYPES, "MIN_TILE_TYPES is reasonable"
|
|
)
|
|
|
|
# Test current values are within safety limits
|
|
TestHelperClass.assert_in_range(
|
|
match3_instance.GRID_SIZE.x,
|
|
match3_instance.MIN_GRID_SIZE,
|
|
match3_instance.MAX_GRID_SIZE,
|
|
"Grid width within safety limits"
|
|
)
|
|
TestHelperClass.assert_in_range(
|
|
match3_instance.GRID_SIZE.y,
|
|
match3_instance.MIN_GRID_SIZE,
|
|
match3_instance.MAX_GRID_SIZE,
|
|
"Grid height within safety limits"
|
|
)
|
|
TestHelperClass.assert_in_range(
|
|
match3_instance.TILE_TYPES,
|
|
match3_instance.MIN_TILE_TYPES,
|
|
match3_instance.MAX_TILE_TYPES,
|
|
"Tile types within safety limits"
|
|
)
|
|
|
|
# Test timing constants
|
|
TestHelperClass.assert_true(
|
|
"CASCADE_WAIT_TIME" in match3_instance,
|
|
"CASCADE_WAIT_TIME constant exists"
|
|
)
|
|
TestHelperClass.assert_true(
|
|
"SWAP_ANIMATION_TIME" in match3_instance,
|
|
"SWAP_ANIMATION_TIME constant exists"
|
|
)
|
|
TestHelperClass.assert_true(
|
|
"TILE_DROP_WAIT_TIME" in match3_instance,
|
|
"TILE_DROP_WAIT_TIME constant exists"
|
|
)
|
|
|
|
|
|
func test_grid_initialization():
|
|
TestHelperClass.print_step("Grid Initialization")
|
|
|
|
if not match3_instance:
|
|
return
|
|
|
|
# Test grid structure
|
|
TestHelperClass.assert_not_null(
|
|
match3_instance.grid, "Grid array is initialized"
|
|
)
|
|
TestHelperClass.assert_true(
|
|
match3_instance.grid is Array, "Grid is Array type"
|
|
)
|
|
|
|
# Test grid dimensions
|
|
var expected_height = match3_instance.GRID_SIZE.y
|
|
var expected_width = match3_instance.GRID_SIZE.x
|
|
|
|
TestHelperClass.assert_equal(
|
|
expected_height, match3_instance.grid.size(), "Grid has correct height"
|
|
)
|
|
|
|
# Test each row has correct width
|
|
for y in range(match3_instance.grid.size()):
|
|
if y < expected_height:
|
|
TestHelperClass.assert_equal(
|
|
expected_width,
|
|
match3_instance.grid[y].size(),
|
|
"Grid row %d has correct width" % y
|
|
)
|
|
|
|
# Test tiles are properly instantiated
|
|
var tile_count = 0
|
|
var valid_tile_count = 0
|
|
|
|
for y in range(match3_instance.grid.size()):
|
|
for x in range(match3_instance.grid[y].size()):
|
|
var tile = match3_instance.grid[y][x]
|
|
tile_count += 1
|
|
|
|
if tile and is_instance_valid(tile):
|
|
valid_tile_count += 1
|
|
TestHelperClass.assert_true(
|
|
"tile_type" in tile,
|
|
"Tile at (%d,%d) has tile_type property" % [x, y]
|
|
)
|
|
TestHelperClass.assert_true(
|
|
"grid_position" in tile,
|
|
"Tile at (%d,%d) has grid_position property" % [x, y]
|
|
)
|
|
|
|
# Test tile type is within valid range
|
|
if "tile_type" in tile:
|
|
TestHelperClass.assert_in_range(
|
|
tile.tile_type,
|
|
0,
|
|
match3_instance.TILE_TYPES - 1,
|
|
"Tile type in valid range"
|
|
)
|
|
|
|
TestHelperClass.assert_equal(
|
|
tile_count, valid_tile_count, "All grid positions have valid tiles"
|
|
)
|
|
|
|
|
|
func test_grid_layout_calculation():
|
|
TestHelperClass.print_step("Grid Layout Calculation")
|
|
|
|
if not match3_instance:
|
|
return
|
|
|
|
# Test tile size calculation
|
|
TestHelperClass.assert_true(
|
|
match3_instance.tile_size > 0, "Tile size is positive"
|
|
)
|
|
TestHelperClass.assert_true(
|
|
match3_instance.tile_size <= 200,
|
|
"Tile size is reasonable (not too large)"
|
|
)
|
|
|
|
# Test grid offset
|
|
TestHelperClass.assert_not_null(
|
|
match3_instance.grid_offset, "Grid offset is set"
|
|
)
|
|
TestHelperClass.assert_true(
|
|
match3_instance.grid_offset.x >= 0, "Grid offset X is non-negative"
|
|
)
|
|
TestHelperClass.assert_true(
|
|
match3_instance.grid_offset.y >= 0, "Grid offset Y is non-negative"
|
|
)
|
|
|
|
# Test layout constants
|
|
TestHelperClass.assert_equal(
|
|
0.8, match3_instance.SCREEN_WIDTH_USAGE, "Screen width usage constant"
|
|
)
|
|
TestHelperClass.assert_equal(
|
|
0.7, match3_instance.SCREEN_HEIGHT_USAGE, "Screen height usage constant"
|
|
)
|
|
TestHelperClass.assert_equal(
|
|
50.0, match3_instance.GRID_LEFT_MARGIN, "Grid left margin constant"
|
|
)
|
|
TestHelperClass.assert_equal(
|
|
50.0, match3_instance.GRID_TOP_MARGIN, "Grid top margin constant"
|
|
)
|
|
|
|
|
|
func test_state_management():
|
|
TestHelperClass.print_step("State Management")
|
|
|
|
if not match3_instance:
|
|
return
|
|
|
|
# Test GameState enum exists and has expected values
|
|
var game_state_class = match3_instance.get_script().get_global_class()
|
|
TestHelperClass.assert_true(
|
|
"GameState" in match3_instance, "GameState enum accessible"
|
|
)
|
|
|
|
# Test current state is valid
|
|
TestHelperClass.assert_not_null(
|
|
match3_instance.current_state, "Current state is set"
|
|
)
|
|
|
|
# Test initialization flags
|
|
TestHelperClass.assert_true(
|
|
"grid_initialized" in match3_instance, "Grid initialized flag exists"
|
|
)
|
|
TestHelperClass.assert_true(
|
|
match3_instance.grid_initialized, "Grid is marked as initialized"
|
|
)
|
|
|
|
# Test instance ID for debugging
|
|
TestHelperClass.assert_true(
|
|
"instance_id" in match3_instance, "Instance ID exists for debugging"
|
|
)
|
|
TestHelperClass.assert_true(
|
|
match3_instance.instance_id.begins_with("Match3_"),
|
|
"Instance ID has correct format"
|
|
)
|
|
|
|
|
|
func test_match_detection():
|
|
TestHelperClass.print_step("Match Detection Logic")
|
|
|
|
if not match3_instance:
|
|
return
|
|
|
|
# Test match detection methods exist and can be called safely
|
|
TestHelperClass.assert_true(
|
|
match3_instance.has_method("_has_match_at"),
|
|
"_has_match_at method exists"
|
|
)
|
|
TestHelperClass.assert_true(
|
|
match3_instance.has_method("_check_for_matches"),
|
|
"_check_for_matches method exists"
|
|
)
|
|
TestHelperClass.assert_true(
|
|
match3_instance.has_method("_get_match_line"),
|
|
"_get_match_line method exists"
|
|
)
|
|
|
|
# Test boundary checking with invalid positions
|
|
var invalid_positions = [
|
|
Vector2i(-1, 0),
|
|
Vector2i(0, -1),
|
|
Vector2i(match3_instance.GRID_SIZE.x, 0),
|
|
Vector2i(0, match3_instance.GRID_SIZE.y),
|
|
Vector2i(100, 100)
|
|
]
|
|
|
|
# NOTE: _has_match_at is private, testing indirectly through public API
|
|
for pos in invalid_positions:
|
|
# Test that invalid positions are handled gracefully through public methods
|
|
var is_invalid = (
|
|
pos.x < 0
|
|
or pos.y < 0
|
|
or pos.x >= match3_instance.GRID_SIZE.x
|
|
or pos.y >= match3_instance.GRID_SIZE.y
|
|
)
|
|
TestHelperClass.assert_true(
|
|
is_invalid,
|
|
(
|
|
"Invalid position (%d,%d) is correctly identified as invalid"
|
|
% [pos.x, pos.y]
|
|
)
|
|
)
|
|
|
|
# Test valid positions through public interface
|
|
for y in range(min(3, match3_instance.GRID_SIZE.y)):
|
|
for x in range(min(3, match3_instance.GRID_SIZE.x)):
|
|
var pos = Vector2i(x, y)
|
|
var is_valid = (
|
|
pos.x >= 0
|
|
and pos.y >= 0
|
|
and pos.x < match3_instance.GRID_SIZE.x
|
|
and pos.y < match3_instance.GRID_SIZE.y
|
|
)
|
|
TestHelperClass.assert_true(
|
|
is_valid,
|
|
"Valid position (%d,%d) is within grid bounds" % [x, y]
|
|
)
|
|
|
|
|
|
func test_scoring_system():
|
|
TestHelperClass.print_step("Scoring System")
|
|
|
|
if not match3_instance:
|
|
return
|
|
|
|
# Test scoring formula constants and logic
|
|
# The scoring system uses: 3 gems = 3 points, 4+ gems = n + (n-2) points
|
|
|
|
# Test that the match3 instance can handle scoring (indirectly through clearing matches)
|
|
TestHelperClass.assert_true(
|
|
match3_instance.has_method("_clear_matches"),
|
|
"Scoring system method exists"
|
|
)
|
|
|
|
# Test that score_changed signal exists
|
|
TestHelperClass.assert_true(
|
|
match3_instance.has_signal("score_changed"),
|
|
"Score changed signal exists"
|
|
)
|
|
|
|
# Test scoring formula logic (based on the documented formula)
|
|
# 3 gems = 3 points, 4 gems = 6 points, 5 gems = 8 points, 6 gems = 10 points
|
|
var test_scores = {3: 3, 4: 6, 5: 8, 6: 10}
|
|
|
|
for match_size in test_scores.keys():
|
|
var expected_score = test_scores[match_size]
|
|
var calculated_score: int
|
|
if match_size == 3:
|
|
calculated_score = 3
|
|
else:
|
|
calculated_score = match_size + max(0, match_size - 2)
|
|
|
|
TestHelperClass.assert_equal(
|
|
expected_score,
|
|
calculated_score,
|
|
"Scoring formula correct for %d gems" % match_size
|
|
)
|
|
|
|
|
|
func test_input_validation():
|
|
TestHelperClass.print_step("Input Validation")
|
|
|
|
if not match3_instance:
|
|
return
|
|
|
|
# Test cursor position bounds
|
|
TestHelperClass.assert_not_null(
|
|
match3_instance.cursor_position, "Cursor position is initialized"
|
|
)
|
|
TestHelperClass.assert_true(
|
|
match3_instance.cursor_position is Vector2i,
|
|
"Cursor position is Vector2i type"
|
|
)
|
|
|
|
# Test keyboard navigation flag
|
|
TestHelperClass.assert_true(
|
|
"keyboard_navigation_enabled" in match3_instance,
|
|
"Keyboard navigation flag exists"
|
|
)
|
|
TestHelperClass.assert_true(
|
|
match3_instance.keyboard_navigation_enabled is bool,
|
|
"Keyboard navigation flag is boolean"
|
|
)
|
|
|
|
# Test selected tile safety
|
|
# selected_tile can be null initially, which is valid
|
|
if match3_instance.selected_tile:
|
|
TestHelperClass.assert_true(
|
|
is_instance_valid(match3_instance.selected_tile),
|
|
"Selected tile is valid if not null"
|
|
)
|
|
|
|
|
|
func test_memory_safety():
|
|
TestHelperClass.print_step("Memory Safety")
|
|
|
|
if not match3_instance:
|
|
return
|
|
|
|
# Test grid integrity validation
|
|
TestHelperClass.assert_true(
|
|
match3_instance.has_method("_validate_grid_integrity"),
|
|
"Grid integrity validation method exists"
|
|
)
|
|
|
|
# Test tile validity checking
|
|
for y in range(min(3, match3_instance.grid.size())):
|
|
for x in range(min(3, match3_instance.grid[y].size())):
|
|
var tile = match3_instance.grid[y][x]
|
|
if tile:
|
|
TestHelperClass.assert_true(
|
|
is_instance_valid(tile),
|
|
"Grid tile at (%d,%d) is valid instance" % [x, y]
|
|
)
|
|
TestHelperClass.assert_true(
|
|
tile.get_parent() == match3_instance,
|
|
"Tile properly parented to Match3"
|
|
)
|
|
|
|
# Test position validation
|
|
TestHelperClass.assert_true(
|
|
match3_instance.has_method("_is_valid_grid_position"),
|
|
"Position validation method exists"
|
|
)
|
|
|
|
# Test safe tile access patterns exist
|
|
# The Match3 code uses comprehensive bounds checking and null validation
|
|
TestHelperClass.assert_true(
|
|
true, "Memory safety patterns implemented in Match3 code"
|
|
)
|
|
|
|
|
|
func test_performance_requirements():
|
|
TestHelperClass.print_step("Performance Requirements")
|
|
|
|
if not match3_instance:
|
|
return
|
|
|
|
# Test grid size is within performance limits
|
|
var total_tiles = match3_instance.GRID_SIZE.x * match3_instance.GRID_SIZE.y
|
|
TestHelperClass.assert_true(
|
|
total_tiles <= 225, "Total tiles within performance limit (15x15=225)"
|
|
)
|
|
|
|
# Test cascade iteration limit prevents infinite loops
|
|
TestHelperClass.assert_equal(
|
|
20,
|
|
match3_instance.MAX_CASCADE_ITERATIONS,
|
|
"Cascade iteration limit prevents infinite loops"
|
|
)
|
|
|
|
# Test timing constants are reasonable for 60fps gameplay
|
|
TestHelperClass.assert_true(
|
|
match3_instance.CASCADE_WAIT_TIME >= 0.05,
|
|
"Cascade wait time allows for smooth animation"
|
|
)
|
|
TestHelperClass.assert_true(
|
|
match3_instance.SWAP_ANIMATION_TIME <= 0.5,
|
|
"Swap animation time is responsive"
|
|
)
|
|
TestHelperClass.assert_true(
|
|
match3_instance.TILE_DROP_WAIT_TIME <= 0.3,
|
|
"Tile drop wait time is responsive"
|
|
)
|
|
|
|
# Test grid initialization performance
|
|
TestHelperClass.start_performance_test("grid_access")
|
|
for y in range(min(5, match3_instance.grid.size())):
|
|
for x in range(min(5, match3_instance.grid[y].size())):
|
|
var tile = match3_instance.grid[y][x]
|
|
if tile and "tile_type" in tile:
|
|
var tile_type = tile.tile_type
|
|
TestHelperClass.end_performance_test(
|
|
"grid_access", 10.0, "Grid access performance within limits"
|
|
)
|
|
|
|
|
|
func cleanup_tests():
|
|
TestHelperClass.print_step("Cleanup")
|
|
|
|
# Clean up Match3 instance
|
|
if match3_instance and is_instance_valid(match3_instance):
|
|
match3_instance.queue_free()
|
|
|
|
# Clean up test viewport
|
|
if test_viewport and is_instance_valid(test_viewport):
|
|
test_viewport.queue_free()
|
|
|
|
# Wait for cleanup
|
|
await process_frame
|
|
|
|
TestHelperClass.assert_true(true, "Test cleanup completed")
|