import 'package:flutter_test/flutter_test.dart'; import 'package:flame/components.dart'; import 'package:zentap/game/components/bubble.dart'; import 'package:zentap/game/components/bubble_spawner.dart'; import 'package:zentap/utils/tilt_detector.dart'; void main() { group('Bubble Rules Tests', () { group('Rule 1: Screen Capacity Calculation', () { test('should calculate max bubbles for different screen sizes', () { // Test small screen (phone) - 360x640 final phoneScreen = Vector2(360, 640); final phoneBubbles = BubbleSpawner.calculateMaxBubblesForScreenSize(phoneScreen); expect(phoneBubbles, greaterThan(0)); expect(phoneBubbles, lessThanOrEqualTo(15)); // Within practical limits // Test large screen (tablet) - 768x1024 final tabletScreen = Vector2(768, 1024); final tabletBubbles = BubbleSpawner.calculateMaxBubblesForScreenSize(tabletScreen); expect(tabletBubbles, greaterThan(phoneBubbles)); // Should be more than phone expect(tabletBubbles, lessThanOrEqualTo(15)); // Capped at 15 // Test edge cases final tinyScreen = Vector2(100, 100); final tinyBubbles = BubbleSpawner.calculateMaxBubblesForScreenSize(tinyScreen); expect(tinyBubbles, equals(4)); // Minimum fallback // Test that phone screen gives reasonable number expect(phoneBubbles, greaterThanOrEqualTo(4)); }); test('should have practical limits between 4-15 bubbles', () { // Test various screen sizes final testSizes = [ Vector2(360, 640), // Phone Vector2(768, 1024), // Tablet Vector2(1920, 1080), // Large screen Vector2(100, 100), // Tiny screen ]; for (final size in testSizes) { final maxBubbles = BubbleSpawner.calculateMaxBubblesForScreenSize(size); expect(maxBubbles, greaterThanOrEqualTo(4)); expect(maxBubbles, lessThanOrEqualTo(15)); } }); test('should calculate consistent results for same screen size', () { final screenSize = Vector2(480, 800); final result1 = BubbleSpawner.calculateMaxBubblesForScreenSize(screenSize); final result2 = BubbleSpawner.calculateMaxBubblesForScreenSize(screenSize); expect(result1, equals(result2)); }); }); group('Rule 2: Triple Collision Auto-Pop', () { test('should track collision count correctly', () { final bubble = Bubble(position: Vector2(100, 100)); // Initially no collisions expect(bubble.collisionCount, equals(0)); // Note: Actual collision testing would require proper game setup // with collision detection system running }); test('should reset collision count after grace period', () { final bubble = Bubble(position: Vector2(100, 100)); // This test would verify that collision count resets // after Bubble.collisionGracePeriod (2.0 seconds) expect(bubble.collisionCount, equals(0)); }); test('should auto-pop after 3 collisions', () { final bubble = Bubble(position: Vector2(100, 100)); // This test would simulate 3 collisions and verify // that the bubble automatically pops expect(Bubble.maxCollisions, equals(3)); }); }); group('Rule 3: Single Bubble Per Shake', () { test('should debounce shake events', () { final detector = TiltDetector(); expect(detector.isShakeDebounced, isFalse); // After a shake event, should be debounced for 500ms // Note: This would require simulating shake events }); test('should prevent multiple shake events within debounce period', () { final detector = TiltDetector(); // Test that rapid shake events are filtered out // Only one should be processed per 500ms window expect(detector.timeSinceLastShake, greaterThanOrEqualTo(0)); }); test('should create only one bubble per shake', () { // This test would verify that handleShake() in ZenTapGame // creates exactly one bubble per shake event // The implementation should spawn exactly 1 bubble // (as opposed to the previous 2-4 bubbles) expect(true, isTrue); // Placeholder for actual implementation test }); }); group('Integration Tests', () { test('should maintain performance with all rules active', () { // Test that all three rules working together // don't negatively impact game performance // This would measure frame rates, memory usage, etc. // with all bubble rules active simultaneously expect(true, isTrue); // Placeholder }); test('should handle edge cases gracefully', () { // Test scenarios like: // - Screen rotation during gameplay // - Very rapid shake events // - Many bubbles colliding simultaneously // - Low memory conditions expect(true, isTrue); // Placeholder }); }); }); group('Bubble Rules Constants', () { test('should have correct constant values', () { // Verify all rule constants are set correctly expect(BubbleSpawner.bubbleMaxDiameter, equals(50.0)); expect(BubbleSpawner.bubbleMinSpacing, equals(10.0)); expect(BubbleSpawner.screenMargin, equals(120.0)); expect(BubbleSpawner.uiTopMargin, equals(100.0)); expect(Bubble.maxCollisions, equals(3)); expect(Bubble.collisionGracePeriod, equals(2.0)); // Note: Shake debounce time is private, but effect should be testable }); test('should calculate effective bubble area correctly', () { const effectiveSize = BubbleSpawner.bubbleMaxDiameter + BubbleSpawner.bubbleMinSpacing; const expectedArea = effectiveSize * effectiveSize; expect(effectiveSize, equals(60.0)); // 50 + 10 expect(expectedArea, equals(3600.0)); // 60 * 60 }); }); } /// Helper class for testing bubble rules in isolation class MockGameContext { Vector2 size; MockGameContext(this.size); /// Simulate screen size change void resize(Vector2 newSize) { size = newSize; } } /// Test utilities for bubble rule validation class BubbleRulesTestUtils { /// Create a test bubble with specific parameters static Bubble createTestBubble({ Vector2? position, bool isAutoSpawned = true, }) { return Bubble( position: position ?? Vector2(100, 100), isAutoSpawned: isAutoSpawned, ); } /// Simulate collision between two bubbles static void simulateCollision(Bubble bubble1, Bubble bubble2) { // This would trigger the collision detection system // to test the triple collision rule } /// Simulate shake event with timing static void simulateShake(TiltDetector detector, {int? timestamp}) { // This would trigger shake detection // to test the single bubble per shake rule } /// Calculate expected max bubbles for given screen size static int calculateExpectedMaxBubbles(Vector2 screenSize) { const margin = BubbleSpawner.screenMargin; const uiMargin = BubbleSpawner.uiTopMargin; const effectiveSize = BubbleSpawner.bubbleMaxDiameter + BubbleSpawner.bubbleMinSpacing; final availableWidth = screenSize.x - (2 * margin); final availableHeight = screenSize.y - (2 * margin) - uiMargin; if (availableWidth <= 0 || availableHeight <= 0) return 4; final availableArea = availableWidth * availableHeight; final effectiveArea = effectiveSize * effectiveSize; final theoretical = (availableArea / effectiveArea).floor(); return theoretical.clamp(4, 15); } }