Skip to content

Commit 92201b4

Browse files
authored
Add extensive unit tests for chart components and Objects helper (#3986)
1 parent 0c60277 commit 92201b4

File tree

6 files changed

+636
-0
lines changed

6 files changed

+636
-0
lines changed
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
package com.codename1.charts.views;
2+
3+
import com.codename1.charts.compat.Canvas;
4+
import com.codename1.charts.compat.Paint;
5+
import com.codename1.charts.models.XYMultipleSeriesDataset;
6+
import com.codename1.charts.models.XYSeries;
7+
import com.codename1.charts.renderers.XYMultipleSeriesRenderer;
8+
import com.codename1.charts.renderers.XYSeriesRenderer;
9+
import com.codename1.charts.util.ColorUtil;
10+
import com.codename1.impl.CodenameOneImplementation;
11+
import com.codename1.ui.Display;
12+
13+
import org.junit.jupiter.api.BeforeEach;
14+
import org.junit.jupiter.api.BeforeAll;
15+
import org.junit.jupiter.api.Test;
16+
import org.mockito.Answers;
17+
import org.mockito.Mockito;
18+
19+
import java.lang.reflect.Field;
20+
import java.util.ArrayList;
21+
import java.util.Arrays;
22+
import java.util.List;
23+
24+
import static org.mockito.ArgumentMatchers.anyInt;
25+
26+
import static org.junit.jupiter.api.Assertions.assertEquals;
27+
import static org.junit.jupiter.api.Assertions.assertNotNull;
28+
import static org.junit.jupiter.api.Assertions.assertTrue;
29+
30+
public class BarChartTest {
31+
private static boolean displayMocked;
32+
33+
@BeforeAll
34+
public static void initDisplayMock() throws Exception {
35+
if (displayMocked) {
36+
return;
37+
}
38+
CodenameOneImplementation mockImpl = Mockito.mock(CodenameOneImplementation.class, Answers.RETURNS_DEFAULTS);
39+
Mockito.when(mockImpl.createFont(anyInt(), anyInt(), anyInt())).thenReturn(new Object());
40+
41+
Field implField = Display.class.getDeclaredField("impl");
42+
implField.setAccessible(true);
43+
implField.set(null, mockImpl);
44+
displayMocked = true;
45+
}
46+
47+
private XYMultipleSeriesDataset dataset;
48+
private XYMultipleSeriesRenderer renderer;
49+
50+
@BeforeEach
51+
public void setup() {
52+
dataset = new XYMultipleSeriesDataset();
53+
XYSeries series1 = new XYSeries("s1");
54+
series1.add(1, 10);
55+
XYSeries series2 = new XYSeries("s2");
56+
series2.add(1, 20);
57+
dataset.addSeries(series1);
58+
dataset.addSeries(series2);
59+
60+
renderer = new XYMultipleSeriesRenderer();
61+
XYSeriesRenderer r1 = new XYSeriesRenderer();
62+
r1.setColor(ColorUtil.BLUE);
63+
XYSeriesRenderer r2 = new XYSeriesRenderer();
64+
r2.setColor(ColorUtil.GREEN);
65+
renderer.addSeriesRenderer(r1);
66+
renderer.addSeriesRenderer(r2);
67+
}
68+
69+
private ExposedBarChart createChart(BarChart.Type type) {
70+
return new ExposedBarChart(dataset, renderer, type);
71+
}
72+
73+
@Test
74+
public void testChartDefaults() {
75+
ExposedBarChart chart = createChart(BarChart.Type.DEFAULT);
76+
assertEquals("Bar", chart.getChartType());
77+
assertEquals(0.0, chart.getDefaultMinimum(), 1e-6);
78+
assertTrue(chart.isRenderNullValues());
79+
assertEquals(12, chart.getLegendShapeWidth(0));
80+
assertEquals(1f, chart.callGetCoeficient());
81+
}
82+
83+
@Test
84+
public void testGetHalfDiffXUsesRendererWidth() {
85+
renderer.setBarWidth(8f);
86+
ExposedBarChart chart = createChart(BarChart.Type.DEFAULT);
87+
List<Float> points = Arrays.asList(5f, 10f, 10f, 20f);
88+
float half = chart.callGetHalfDiffX(points, points.size(), dataset.getSeriesCount());
89+
assertEquals(4f, half, 1e-6f);
90+
}
91+
92+
@Test
93+
public void testGetHalfDiffXWithAutomaticWidth() {
94+
ExposedBarChart chart = createChart(BarChart.Type.DEFAULT);
95+
List<Float> points = Arrays.asList(10f, 10f, 30f, 20f, 50f, 30f);
96+
float half = chart.callGetHalfDiffX(points, points.size(), dataset.getSeriesCount());
97+
assertEquals(5f, half, 1e-6f);
98+
}
99+
100+
@Test
101+
public void testClickableAreasDefaultType() {
102+
ExposedBarChart chart = createChart(BarChart.Type.DEFAULT);
103+
List<Float> points = Arrays.asList(20f, 40f);
104+
List<Double> values = Arrays.asList(1d, 2d);
105+
ClickableArea[] areas = chart.callClickableAreas(points, values, 0f, 1, 0);
106+
assertEquals(1, areas.length);
107+
assertNotNull(areas[0]);
108+
double expectedLeft = 20 - dataset.getSeriesCount() * 5 + 1 * 10;
109+
assertEquals(expectedLeft, areas[0].getRect().getX(), 1e-6);
110+
assertEquals(10, areas[0].getRect().getWidth(), 1e-6);
111+
}
112+
113+
@Test
114+
public void testClickableAreasStackedType() {
115+
ExposedBarChart chart = createChart(BarChart.Type.STACKED);
116+
List<Float> points = Arrays.asList(20f, 40f);
117+
List<Double> values = Arrays.asList(1d, 2d);
118+
ClickableArea[] areas = chart.callClickableAreas(points, values, 10f, 0, 0);
119+
assertEquals(1, areas.length);
120+
assertNotNull(areas[0]);
121+
assertEquals(10, areas[0].getRect().getX(), 1e-6);
122+
assertEquals(20, areas[0].getRect().getWidth(), 1e-6);
123+
assertEquals(10f, areas[0].getRect().getY(), 1e-6);
124+
}
125+
126+
@Test
127+
public void testGradientPartialColorBlendsChannels() {
128+
ExposedBarChart chart = createChart(BarChart.Type.DEFAULT);
129+
int minColor = ColorUtil.argb(255, 0, 0, 255);
130+
int maxColor = ColorUtil.argb(255, 255, 0, 0);
131+
int mixed = chart.callGetGradientPartialColor(minColor, maxColor, 0.25f);
132+
assertEquals(191, ColorUtil.red(mixed));
133+
assertEquals(0, ColorUtil.green(mixed));
134+
assertEquals(64, ColorUtil.blue(mixed));
135+
}
136+
137+
@Test
138+
public void testDrawSeriesHeapedAdjustsPreviousValues() throws Exception {
139+
ExposedBarChart chart = createChart(BarChart.Type.HEAPED);
140+
Paint paint = new Paint();
141+
List<Float> firstSeriesPoints = new ArrayList<Float>(Arrays.asList(5f, 15f));
142+
chart.drawSeries(null, paint, firstSeriesPoints, (XYSeriesRenderer) renderer.getSeriesRendererAt(0), 0f, 0, 0);
143+
assertEquals(1, chart.recordedBars.size());
144+
float[] firstRect = chart.recordedBars.get(0);
145+
assertEquals(0f, firstRect[1], 1e-6f);
146+
assertEquals(15f, firstRect[3], 1e-6f);
147+
148+
chart.recordedBars.clear();
149+
chart.seedPreviousPoints(new ArrayList<Float>(firstSeriesPoints));
150+
List<Float> secondSeriesPoints = new ArrayList<Float>(Arrays.asList(5f, 10f));
151+
XYSeriesRenderer secondRenderer = (XYSeriesRenderer) renderer.getSeriesRendererAt(1);
152+
assertNotNull(secondRenderer);
153+
chart.drawSeries(null, paint, secondSeriesPoints, secondRenderer, 0f, 1, 0);
154+
assertEquals(1, chart.recordedBars.size());
155+
float[] secondRect = chart.recordedBars.get(0);
156+
assertEquals(15f, secondRect[1], 1e-6f);
157+
assertEquals(25f, secondRect[3], 1e-6f);
158+
assertEquals(25f, secondSeriesPoints.get(1), 1e-6f);
159+
}
160+
161+
private static class ExposedBarChart extends BarChart {
162+
final List<float[]> recordedBars = new ArrayList<float[]>();
163+
164+
ExposedBarChart(XYMultipleSeriesDataset dataset, XYMultipleSeriesRenderer renderer, Type type) {
165+
super(dataset, renderer, type);
166+
}
167+
168+
float callGetHalfDiffX(List<Float> points, int length, int seriesNr) {
169+
return super.getHalfDiffX(points, length, seriesNr);
170+
}
171+
172+
float callGetCoeficient() {
173+
return super.getCoeficient();
174+
}
175+
176+
ClickableArea[] callClickableAreas(List<Float> points, List<Double> values, float yAxisValue, int seriesIndex, int startIndex) {
177+
return super.clickableAreasForPoints(points, values, yAxisValue, seriesIndex, startIndex);
178+
}
179+
180+
int callGetGradientPartialColor(int minColor, int maxColor, float fraction) {
181+
return super.getGradientPartialColor(minColor, maxColor, fraction);
182+
}
183+
184+
@Override
185+
protected void drawBar(Canvas canvas, float xMin, float yMin, float xMax, float yMax, int scale, int seriesIndex, Paint paint) {
186+
float minX = Math.min(xMin, xMax);
187+
float maxX = Math.max(xMin, xMax);
188+
float minY = Math.min(yMin, yMax);
189+
float maxY = Math.max(yMin, yMax);
190+
recordedBars.add(new float[]{minX, minY, maxX, maxY});
191+
}
192+
193+
void seedPreviousPoints(List<Float> previousPoints) {
194+
try {
195+
Field field = BarChart.class.getDeclaredField("mPreviousSeriesPoints");
196+
field.setAccessible(true);
197+
field.set(this, previousPoints);
198+
} catch (ReflectiveOperationException e) {
199+
throw new IllegalStateException("Unable to seed previous series points", e);
200+
}
201+
}
202+
}
203+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.codename1.charts.views;
2+
3+
import com.codename1.charts.models.MultipleCategorySeries;
4+
import com.codename1.charts.renderers.DefaultRenderer;
5+
import com.codename1.charts.renderers.SimpleSeriesRenderer;
6+
import com.codename1.charts.util.ColorUtil;
7+
8+
import org.junit.jupiter.api.Test;
9+
10+
import static org.junit.jupiter.api.Assertions.assertEquals;
11+
12+
public class DoughnutChartTest {
13+
private DoughnutChart createChart() {
14+
MultipleCategorySeries dataset = new MultipleCategorySeries("donut");
15+
dataset.add("Cat1", new String[]{"A", "B"}, new double[]{1, 2});
16+
dataset.add("Cat2", new String[]{"C", "D"}, new double[]{3, 4});
17+
18+
DefaultRenderer renderer = new DefaultRenderer();
19+
SimpleSeriesRenderer r1 = new SimpleSeriesRenderer();
20+
r1.setColor(ColorUtil.BLUE);
21+
SimpleSeriesRenderer r2 = new SimpleSeriesRenderer();
22+
r2.setColor(ColorUtil.GREEN);
23+
renderer.addSeriesRenderer(r1);
24+
renderer.addSeriesRenderer(r2);
25+
renderer.setFitLegend(true);
26+
renderer.setChartTitle("Title");
27+
renderer.setLabelsTextSize(12f);
28+
renderer.setLegendTextSize(10f);
29+
renderer.setApplyBackgroundColor(true);
30+
renderer.setBackgroundColor(ColorUtil.WHITE);
31+
renderer.setScale(1f);
32+
return new DoughnutChart(dataset, renderer);
33+
}
34+
35+
@Test
36+
public void testLegendShapeWidthConstant() {
37+
DoughnutChart chart = createChart();
38+
assertEquals(10, chart.getLegendShapeWidth(0));
39+
}
40+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package com.codename1.charts.views;
2+
3+
import com.codename1.ui.geom.GeneralPath;
4+
import com.codename1.ui.geom.PathIterator;
5+
import com.codename1.ui.geom.Shape;
6+
7+
import org.junit.jupiter.api.Test;
8+
9+
import static org.junit.jupiter.api.Assertions.assertEquals;
10+
import static org.junit.jupiter.api.Assertions.assertFalse;
11+
import static org.junit.jupiter.api.Assertions.assertTrue;
12+
13+
public class PieSegmentTest {
14+
@Test
15+
public void testIsInSegmentWithinBounds() {
16+
PieSegment segment = new PieSegment(0, 10f, 45f, 90f);
17+
assertTrue(segment.isInSegment(60));
18+
assertFalse(segment.isInSegment(10));
19+
}
20+
21+
@Test
22+
public void testIsInSegmentHandlesWrapAround() {
23+
PieSegment segment = new PieSegment(1, 15f, 300f, 120f);
24+
assertTrue(segment.isInSegment(30));
25+
assertTrue(segment.isInSegment(350));
26+
assertFalse(segment.isInSegment(150));
27+
}
28+
29+
@Test
30+
public void testAccessorsExposeValues() {
31+
PieSegment segment = new PieSegment(2, 20f, 10f, 45f);
32+
assertEquals(10f, segment.getStartAngle(), 1e-6f);
33+
assertEquals(55f, segment.getEndAngle(), 1e-6f);
34+
assertEquals(2, segment.getDataIndex());
35+
assertEquals(20f, segment.getValue(), 1e-6f);
36+
}
37+
38+
@Test
39+
public void testToStringIncludesAllFields() {
40+
PieSegment segment = new PieSegment(3, 5f, 0f, 90f);
41+
String text = segment.toString();
42+
assertTrue(text.contains("mDataIndex=3"));
43+
assertTrue(text.contains("mValue=5.0"));
44+
assertTrue(text.contains("mStartAngle=0.0"));
45+
assertTrue(text.contains("mEndAngle=90.0"));
46+
}
47+
48+
@Test
49+
public void testGetShapeBuildsWedgePath() {
50+
PieSegment segment = new PieSegment(4, 12f, 0f, 90f);
51+
Shape shape = segment.getShape(50f, 60f, 10f);
52+
assertTrue(shape instanceof GeneralPath);
53+
GeneralPath path = (GeneralPath) shape;
54+
55+
float centerX = 50f;
56+
float centerY = 60f;
57+
float radius = 10f;
58+
float expectedStartX = centerX + radius;
59+
float expectedStartY = centerY;
60+
float expectedEndX = centerX;
61+
float expectedEndY = centerY + radius;
62+
63+
PathIterator iterator = path.getPathIterator();
64+
float[] coords = new float[6];
65+
66+
assertEquals(PathIterator.SEG_MOVETO, iterator.currentSegment(coords));
67+
boolean moveToAtCenter = Math.abs(coords[0] - centerX) < 1e-4f && Math.abs(coords[1] - centerY) < 1e-4f;
68+
boolean moveToAtStart = Math.abs(coords[0] - expectedStartX) < 1e-4f && Math.abs(coords[1] - expectedStartY) < 1e-4f;
69+
assertTrue(moveToAtCenter || moveToAtStart);
70+
71+
iterator.next();
72+
73+
boolean sawStartLine = moveToAtStart;
74+
boolean sawEndLine = false;
75+
boolean sawArcSegment = false;
76+
boolean sawClose = false;
77+
float[] arcEnd = new float[2];
78+
79+
while (!iterator.isDone()) {
80+
int type = iterator.currentSegment(coords);
81+
switch (type) {
82+
case PathIterator.SEG_LINETO:
83+
if (!sawStartLine && Math.abs(coords[0] - expectedStartX) < 1e-4f && Math.abs(coords[1] - expectedStartY) < 1e-4f) {
84+
sawStartLine = true;
85+
} else if (!sawEndLine && Math.abs(coords[0] - expectedEndX) < 1e-4f && Math.abs(coords[1] - expectedEndY) < 1e-4f) {
86+
sawEndLine = true;
87+
}
88+
break;
89+
case PathIterator.SEG_QUADTO:
90+
case PathIterator.SEG_CUBICTO:
91+
sawArcSegment = true;
92+
int endIndex = type == PathIterator.SEG_QUADTO ? 2 : 4;
93+
arcEnd[0] = coords[endIndex];
94+
arcEnd[1] = coords[endIndex + 1];
95+
break;
96+
case PathIterator.SEG_CLOSE:
97+
sawClose = true;
98+
break;
99+
default:
100+
break;
101+
}
102+
iterator.next();
103+
}
104+
105+
assertTrue(sawStartLine);
106+
assertTrue(sawArcSegment);
107+
assertEquals(expectedEndX, arcEnd[0], 1e-3f);
108+
assertEquals(expectedEndY, arcEnd[1], 1e-3f);
109+
assertTrue(sawEndLine);
110+
assertTrue(sawClose);
111+
112+
assertPathContainsPoint(path, centerX, centerY);
113+
assertPathContainsPoint(path, expectedStartX, expectedStartY);
114+
assertPathContainsPoint(path, expectedEndX, expectedEndY);
115+
}
116+
117+
private static void assertPathContainsPoint(GeneralPath path, float x, float y) {
118+
try {
119+
java.lang.reflect.Field pointSizeField = GeneralPath.class.getDeclaredField("pointSize");
120+
java.lang.reflect.Field pointsField = GeneralPath.class.getDeclaredField("points");
121+
pointSizeField.setAccessible(true);
122+
pointsField.setAccessible(true);
123+
int pointSize = pointSizeField.getInt(path);
124+
float[] points = (float[]) pointsField.get(path);
125+
for (int i = 0; i < pointSize; i += 2) {
126+
if (Math.abs(points[i] - x) < 1e-4f && Math.abs(points[i + 1] - y) < 1e-4f) {
127+
return;
128+
}
129+
}
130+
throw new AssertionError("Path did not contain point (" + x + ", " + y + ")");
131+
} catch (ReflectiveOperationException e) {
132+
throw new IllegalStateException("Unable to inspect path points", e);
133+
}
134+
}
135+
}

0 commit comments

Comments
 (0)