Repository navigation
Particle Effects Component Test Plan
This test plan outlines comprehensive testing strategies for the ParticleEffectsRenderingComponent class, covering functionality, integration, performance, and edge cases.
- Verify correct particle rendering for each effect type (fire, ice, poison)
- Ensure proper state management and component lifecycle
- Validate color gradients and visual interpolation
- Test integration with status components
- Verify lighting system integration
- Confirm resource cleanup and disposal
- Test edge cases and error handling
- Assess performance with multiple active effects
- Framework: JUnit 4 or JUnit 5
- Mocking Library: Mockito
- Game Framework: LibGDX Test Fixtures
- Build Tool: Gradle or Maven
Objective: Verify component retrieves all stat components during creation.
Setup:
- Create entity with
BurnStatsComponent,SlowStatsComponent,PoisonStatsComponent, andPointLightComponent - Create
ParticleEffectsRenderingComponent
Expected Result:
- All component references are non-null
- Point light component is successfully retrieved
Code Sketch:
@Test
public void testCreateWithAllComponents() {
Entity entity = new Entity();
entity.addComponent(new BurnStatsComponent());
entity.addComponent(new SlowStatsComponent());
entity.addComponent(new PoisonStatsComponent());
entity.addComponent(new PointLightComponent());
ParticleEffectsRenderingComponent component = new ParticleEffectsRenderingComponent();
entity.addComponent(component);
component.create();
assertNotNull(component.burnStats);
assertNotNull(component.slowStats);
assertNotNull(component.poisonStats);
assertNotNull(component.pointLightComponent);
}Objective: Verify component handles missing stat components gracefully.
Setup:
- Create entity with no status components
Expected Result:
- Component creation succeeds
- All stat component references are null
- No exception is thrown
Code Sketch:
@Test
public void testCreateWithMissingComponents() {
Entity entity = new Entity();
ParticleEffectsRenderingComponent component = new ParticleEffectsRenderingComponent();
entity.addComponent(component);
assertDoesNotThrow(() -> component.create());
assertNull(component.burnStats);
assertNull(component.slowStats);
assertNull(component.poisonStats);
}Objective: Verify component handles missing point light component.
Setup:
- Create entity with status components but no
PointLightComponent
Expected Result:
- Component creation succeeds
- Point light reference is null
- No exception during draw call
Code Sketch:
@Test
public void testCreateWithoutPointLight() {
Entity entity = new Entity();
entity.addComponent(new BurnStatsComponent());
ParticleEffectsRenderingComponent component = new ParticleEffectsRenderingComponent();
entity.addComponent(component);
assertDoesNotThrow(() -> component.create());
assertNull(component.pointLightComponent);
}Objective: Verify component skips rendering when no effects are active.
Setup:
- Mock
BurnStatsComponent,SlowStatsComponent,PoisonStatsComponentto return false for active flags - Mock
GameTimeandServiceLocator - Mock
SpriteBatch
Expected Result:
- No shapes are rendered
- Point light is cleared
- Early return occurs
Code Sketch:
@Test
public void testDrawWithNoActiveEffects() {
ParticleEffectsRenderingComponent component = new ParticleEffectsRenderingComponent();
component.burnStats = mock(BurnStatsComponent.class);
component.slowStats = mock(SlowStatsComponent.class);
component.poisonStats = mock(PoisonStatsComponent.class);
component.pointLightComponent = mock(PointLightComponent.class);
when(component.burnStats.isBurning()).thenReturn(false);
when(component.slowStats.isSlowed()).thenReturn(false);
when(component.poisonStats.isPoisoned()).thenReturn(false);
SpriteBatch batch = mock(SpriteBatch.class);
component.draw(batch);
verify(component.pointLightComponent).setColor(Color.CLEAR);
}Objective: Verify fire particles render when entity is burning.
Setup:
- Mock
BurnStatsComponentto return true forisBurning() - Mock
GameTimeto return valid time - Mock entity position and scale
Expected Result:
- Fire effect is drawn
- Correct fire colors are used
- Point light is set to fire base color
Code Sketch:
@Test
public void testDrawWithBurningEffect() {
ParticleEffectsRenderingComponent component = new ParticleEffectsRenderingComponent();
component.burnStats = mock(BurnStatsComponent.class);
component.slowStats = mock(SlowStatsComponent.class);
component.poisonStats = mock(PoisonStatsComponent.class);
component.pointLightComponent = mock(PointLightComponent.class);
when(component.burnStats.isBurning()).thenReturn(true);
when(component.slowStats.isSlowed()).thenReturn(false);
when(component.poisonStats.isPoisoned()).thenReturn(false);
// Mock entity and GameTime
Entity entity = mock(Entity.class);
when(entity.getCenterPosition()).thenReturn(new Vector2(10, 10));
when(entity.getScale()).thenReturn(new Vector2(1, 1));
// Setup component with entity
component.entity = entity;
SpriteBatch batch = mock(SpriteBatch.class);
component.draw(batch);
// Verify fire base color was set
verify(component.pointLightComponent).setColor(ParticleEffectsRenderingComponent.fireBaseColour);
}Objective: Verify ice particles render when entity is slowed.
Setup:
- Mock
SlowStatsComponentto return true forisSlowed()
Expected Result:
- Ice effect is drawn with correct colors
- Point light is set to ice base color
Objective: Verify poison particles render when entity is poisoned.
Setup:
- Mock
PoisonStatsComponentto return true forisPoisoned()
Expected Result:
- Poison effect is drawn with correct colors
- Point light is set to poison base color
Objective: Verify all three effects render simultaneously.
Setup:
- All status components return true
Expected Result:
- All three effects are drawn
- Point light is set to last effect's base color (poison)
- No exceptions occur
Code Sketch:
@Test
public void testDrawWithMultipleEffects() {
ParticleEffectsRenderingComponent component = new ParticleEffectsRenderingComponent();
component.burnStats = mock(BurnStatsComponent.class);
component.slowStats = mock(SlowStatsComponent.class);
component.poisonStats = mock(PoisonStatsComponent.class);
component.pointLightComponent = mock(PointLightComponent.class);
when(component.burnStats.isBurning()).thenReturn(true);
when(component.slowStats.isSlowed()).thenReturn(true);
when(component.poisonStats.isPoisoned()).thenReturn(true);
Entity entity = mock(Entity.class);
component.entity = entity;
when(entity.getCenterPosition()).thenReturn(new Vector2(10, 10));
when(entity.getScale()).thenReturn(new Vector2(1, 1));
SpriteBatch batch = mock(SpriteBatch.class);
assertDoesNotThrow(() -> component.draw(batch));
}Objective: Verify component handles missing GameTime gracefully.
Setup:
- Mock
ServiceLocator.getTimeSource()to return null
Expected Result:
- Early return without rendering
- No exception thrown
Code Sketch:
@Test
public void testDrawWithNullGameTime() {
ParticleEffectsRenderingComponent component = new ParticleEffectsRenderingComponent();
try (MockedStatic<ServiceLocator> mockedServiceLocator = mockStatic(ServiceLocator.class)) {
mockedServiceLocator.when(ServiceLocator::getTimeSource).thenReturn(null);
SpriteBatch batch = mock(SpriteBatch.class);
assertDoesNotThrow(() -> component.draw(batch));
}
}Objective: Verify color interpolation in lower half of gradient (base to mid).
Setup:
- Call
colourGradient()with t values between 0 and 0.5 - Use fire colors: base (red), mid (orange), tip (yellow)
Expected Result:
- For t=0: output color equals base color
- For t=0.25: output is between base and mid
- For t=0.5: output color equals mid color
Code Sketch:
@Test
public void testColourGradientLowerHalf() {
Color base = new Color(0.55f, 0.05f, 0.02f, 1f); // Fire base (red)
Color mid = new Color(0.95f, 0.35f, 0.05f, 1f); // Fire mid (orange)
Color tip = new Color(1f, 0.9f, 0.3f, 1f); // Fire tip (yellow)
Color out = new Color();
// Test t = 0
ParticleEffectsRenderingComponent.colourGradient(base, mid, tip, 0f, out);
assertEquals(base.r, out.r, 0.001f);
assertEquals(base.g, out.g, 0.001f);
assertEquals(base.b, out.b, 0.001f);
// Test t = 0.5
ParticleEffectsRenderingComponent.colourGradient(base, mid, tip, 0.5f, out);
assertEquals(mid.r, out.r, 0.001f);
assertEquals(mid.g, out.g, 0.001f);
assertEquals(mid.b, out.b, 0.001f);
}Objective: Verify color interpolation in upper half of gradient (mid to tip).
Setup:
- Call
colourGradient()with t values between 0.5 and 1.0
Expected Result:
- For t=0.5: output color equals mid color
- For t=0.75: output is between mid and tip
- For t=1.0: output color equals tip color
Code Sketch:
@Test
public void testColourGradientUpperHalf() {
Color base = new Color(0.55f, 0.05f, 0.02f, 1f);
Color mid = new Color(0.95f, 0.35f, 0.05f, 1f);
Color tip = new Color(1f, 0.9f, 0.3f, 1f);
Color out = new Color();
// Test t = 1.0
ParticleEffectsRenderingComponent.colourGradient(base, mid, tip, 1f, out);
assertEquals(tip.r, out.r, 0.001f);
assertEquals(tip.g, out.g, 0.001f);
assertEquals(tip.b, out.b, 0.001f);
}Objective: Verify color gradient works correctly for all effect types.
Setup:
- Test fire, ice, and poison color palettes
Expected Result:
- Each palette produces smooth gradients
- No out-of-range color values
Objective: Verify Z-index is offset above parent component.
Setup:
- Mock parent
RenderComponentto return Z-index of 5.0f
Expected Result:
- Component returns 5.01f (parent + 0.01f)
Code Sketch:
@Test
public void testGetZIndex() {
ParticleEffectsRenderingComponent component = new ParticleEffectsRenderingComponent();
// Mock parent getZIndex
float parentZIndex = 5.0f;
float expectedZIndex = parentZIndex + 0.01f;
// This would require refactoring to make testable, or using reflection
float actualZIndex = component.getZIndex();
assertEquals(expectedZIndex, actualZIndex, 0.001f);
}Objective: Verify resources are properly cleaned up.
Setup:
- Create component with all sub-components
Expected Result:
-
PointLightComponent.dispose()is called (if present) -
ShapeRenderer.dispose()is called - No exceptions occur
Code Sketch:
@Test
public void testDispose() {
ParticleEffectsRenderingComponent component = new ParticleEffectsRenderingComponent();
component.pointLightComponent = mock(PointLightComponent.class);
assertDoesNotThrow(() -> component.dispose());
verify(component.pointLightComponent).dispose();
}Objective: Verify disposal works when point light is null.
Setup:
- Component without point light
Expected Result:
- No exception thrown
- Disposal completes successfully
Objective: Test full lifecycle: create → draw → dispose.
Setup:
- Create entity with all components
- Initialize component
- Call draw multiple times
- Dispose
Expected Result:
- All operations complete without error
- Visual effect is consistent across draw calls
Objective: Test transitions between effect states.
Setup:
- Start with no effects
- Activate burning
- Add slowness (while burning)
- Remove burning
- Verify only slowness is active
Expected Result:
- Visual effects update correctly at each transition
- No flickering or artifacts
Objective: Verify lighting system integrates correctly.
Setup:
- Entity with
PointLightComponent - Activate various effects
Expected Result:
- Light color matches effect base color
- Light is cleared when effects are inactive
Objective: Verify component works alongside other render components.
Setup:
- Entity with multiple render components
- Include
ParticleEffectsRenderingComponent
Expected Result:
- All components render correctly
- Z-order is respected
- No rendering conflicts
Objective: Measure frame time with one active effect.
Setup:
- Single entity with one effect type
- Run for 60 frames
- Measure average frame time
Expected Result:
- Average frame time < 5ms
Code Sketch:
@Test
public void testRenderPerformanceWithSingleEntity() {
ParticleEffectsRenderingComponent component = new ParticleEffectsRenderingComponent();
// Setup component...
long startTime = System.nanoTime();
for (int i = 0; i < 60; i++) {
component.draw(mockBatch);
}
long endTime = System.nanoTime();
double avgFrameTimeMs = (endTime - startTime) / 1_000_000.0 / 60.0;
assertTrue("Average frame time should be < 5ms", avgFrameTimeMs < 5.0);
}Objective: Measure frame time with all three effects active.
Setup:
- Entity with all three effects
- Run for 60 frames
Expected Result:
- Average frame time < 7ms
Objective: Measure frame time with 10+ entities all with effects.
Setup:
- 10 entities, each with effects
- Run for 60 frames
Expected Result:
- Average frame time remains acceptable
- No memory leaks
Objective: Verify no memory leaks in repeated draw cycles.
Setup:
- Call draw 10,000 times
- Monitor memory usage
- Force garbage collection
Expected Result:
- Heap size remains stable
- No OutOfMemory errors
Objective: Verify component handles zero entity scale.
Setup:
- Entity with scale (0, 0)
Expected Result:
- No exceptions
- Minimal or no particles rendered
Objective: Verify component handles very large entity scale.
Setup:
- Entity with scale (1000, 1000)
Expected Result:
- Particles scale appropriately
- No performance degradation
- Particles remain visible
Objective: Verify component handles negative scale values.
Setup:
- Entity with negative scale (-1, -1)
Expected Result:
- Component handles gracefully
- No visual artifacts
Objective: Verify animations work correctly at high time values.
Setup:
- GameTime returns very large values (e.g., 1,000,000 seconds)
Expected Result:
- Animations continue to loop correctly
- No numerical precision issues
Objective: Verify component handles zero elapsed time.
Setup:
- GameTime returns 0
Expected Result:
- Initial particle state is correct
- No division by zero errors
Objective: Verify rendering at world origin.
Setup:
- Entity center at (0, 0)
Expected Result:
- Particles render correctly at origin
Objective: Verify rendering at negative coordinates.
Setup:
- Entity center at (-100, -100)
Expected Result:
- Particles render correctly at negative positions
Objective: Manual visual verification of fire effect.
Setup:
- Render fire effect to screen
Expected Result:
- Flames rise upward
- Colors transition from red → orange → yellow
- Effect sways gently
- Flickering is visible and organic
Objective: Manual visual verification of ice effect.
Expected Result:
- Particles rise upward
- Colors transition from blue → cyan → pale white
- Effect is distinct from fire
Objective: Manual visual verification of poison effect.
Expected Result:
- Particles rise upward
- Colors transition from purple → violet → greenish-yellow
- Effect is distinct from fire and ice
Objective: Verify animations are smooth across frames.
Setup:
- Render 300 frames (5 seconds at 60 FPS)
- Record frame-to-frame transitions
Expected Result:
- No frame skips or stutters
- Motion is smooth and continuous
- No popping or sudden changes
BurnStatsComponentSlowStatsComponentPoisonStatsComponentPointLightComponentEntityGameTimeSpriteBatchShapeRenderer
- Standard entity position: (0, 0)
- Standard entity scale: (1, 1)
- Test game time: 1000 ms (1 second)
- Multiple entity count: 10
| Component | Target Coverage |
|---|---|
create() |
100% |
draw() |
95% |
drawEffect() |
90% |
colourGradient() |
100% |
getZIndex() |
100% |
dispose() |
100% |
| Overall | 95% |
- Unit Tests: All unit tests must pass
- Integration Tests: Integration tests must pass
- Performance Tests: Frame time benchmarks within tolerance
- Code Coverage: Minimum 95% coverage
- Visual Regression: Compare screenshots (optional)
gradle test --tests ParticleEffectsRenderingComponentTest-
Deterministic Randomness: Tests using
MathUtils.random()may be non-deterministic. Consider seeding for reproducibility. -
LibGDX Dependencies: Tests may require
libgdx-backend-headlessor equivalent for CI environments. -
Visual Regression: Manual visual tests may require screenshot comparison tools or human review.
-
Performance Baselines: Performance test thresholds should be calibrated for target hardware.
-
Thread Safety: Not tested; component assumes single-threaded execution.
- Unit Tests: Run on every commit
- Integration Tests: Run on every commit
- Performance Tests: Run daily or before release
- Visual Tests: Run before release
- Full Suite: Run before version release
Entities and Components
Input Handling
Arrow Selection Wheel Arrow Selection Wheel — Sprint 3