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/match3_gameplay.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")