diff --git a/maven/core-unittests/src/test/java/com/codename1/charts/ChartComponentTest.java b/maven/core-unittests/src/test/java/com/codename1/charts/ChartComponentTest.java index 84c2e952d3..e2ed42b575 100644 --- a/maven/core-unittests/src/test/java/com/codename1/charts/ChartComponentTest.java +++ b/maven/core-unittests/src/test/java/com/codename1/charts/ChartComponentTest.java @@ -58,19 +58,35 @@ void shapeConversionsTranslateBetweenSpaces() throws Exception { RecordingChart chart = new RecordingChart(); ChartComponent component = new PositionedChartComponent(chart, 10, 15); - Rectangle screenRect = new Rectangle(component.getAbsoluteX(), component.getAbsoluteY(), 40, 50); - Rectangle chartBounds = component.screenToChartShape(screenRect).getBounds(); - assertEquals(0, chartBounds.getX()); - assertEquals(0, chartBounds.getY()); - assertEquals(0, chartBounds.getSize().getWidth()); - assertEquals(0, chartBounds.getSize().getHeight()); - - Rectangle chartRect = new Rectangle(0, 0, 40, 50); - Rectangle screenBounds = component.chartToScreenShape(chartRect).getBounds(); - assertEquals(0, screenBounds.getX()); - assertEquals(0, screenBounds.getY()); - assertEquals(0, screenBounds.getSize().getWidth()); - assertEquals(0, screenBounds.getSize().getHeight()); + Rectangle screenRect = new Rectangle(component.getAbsoluteX() + 6, component.getAbsoluteY() + 4, 40, 50); + Shape chartShape = component.screenToChartShape(screenRect); + Rectangle chartBounds = chartShape.getBounds(); + assertEquals(screenRect.getX() - component.getAbsoluteX(), chartBounds.getX()); + assertEquals(screenRect.getY() - component.getAbsoluteY(), chartBounds.getY()); + assertEquals(screenRect.getSize().getWidth(), chartBounds.getWidth()); + assertEquals(screenRect.getSize().getHeight(), chartBounds.getHeight()); + + Shape roundTrippedScreenShape = component.chartToScreenShape(chartShape); + Rectangle roundTrippedScreenBounds = roundTrippedScreenShape.getBounds(); + assertEquals(screenRect.getX(), roundTrippedScreenBounds.getX()); + assertEquals(screenRect.getY(), roundTrippedScreenBounds.getY()); + assertEquals(screenRect.getSize().getWidth(), roundTrippedScreenBounds.getWidth()); + assertEquals(screenRect.getSize().getHeight(), roundTrippedScreenBounds.getHeight()); + + Rectangle chartRect = new Rectangle(3, 7, 40, 50); + Shape screenShape = component.chartToScreenShape(chartRect); + Rectangle screenBounds = screenShape.getBounds(); + assertEquals(chartRect.getX() + component.getAbsoluteX(), screenBounds.getX()); + assertEquals(chartRect.getY() + component.getAbsoluteY(), screenBounds.getY()); + assertEquals(chartRect.getSize().getWidth(), screenBounds.getWidth()); + assertEquals(chartRect.getSize().getHeight(), screenBounds.getHeight()); + + Shape roundTrippedChartShape = component.screenToChartShape(screenShape); + Rectangle roundTrippedChartBounds = roundTrippedChartShape.getBounds(); + assertEquals(chartRect.getX(), roundTrippedChartBounds.getX()); + assertEquals(chartRect.getY(), roundTrippedChartBounds.getY()); + assertEquals(chartRect.getSize().getWidth(), roundTrippedChartBounds.getWidth()); + assertEquals(chartRect.getSize().getHeight(), roundTrippedChartBounds.getHeight()); } @Test diff --git a/maven/core-unittests/src/test/java/com/codename1/test/UITestBase.java b/maven/core-unittests/src/test/java/com/codename1/test/UITestBase.java index adfa6f8998..9b36ac761a 100644 --- a/maven/core-unittests/src/test/java/com/codename1/test/UITestBase.java +++ b/maven/core-unittests/src/test/java/com/codename1/test/UITestBase.java @@ -8,12 +8,15 @@ import com.codename1.ui.plaf.UIManager; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.mockito.Mockito; import java.lang.reflect.Field; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; +import java.util.Hashtable; import java.util.List; +import java.util.Map; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -87,6 +90,7 @@ protected void setUpDisplay() throws Exception { @AfterEach protected void tearDownDisplay() throws Exception { + flushSerialCalls(); resetUIManager(); setDisplayField("codenameOneGraphics", null); setDisplayField("impl", null); @@ -94,6 +98,13 @@ protected void tearDownDisplay() throws Exception { setDisplayField("codenameOneRunning", false); setDisplayField("edt", null); Util.setImplementation(null); + if (implementation != null) { + Mockito.reset(implementation); + } + implementation = null; + pluginSupport = null; + codenameOneGraphics = null; + display = null; } private void setDisplayField(String fieldName, Object value) throws Exception { @@ -109,7 +120,58 @@ private void setDisplayField(String fieldName, Object value) throws Exception { private void resetUIManager() throws Exception { Field instanceField = UIManager.class.getDeclaredField("instance"); instanceField.setAccessible(true); + UIManager instance = (UIManager) instanceField.get(null); + if (instance != null) { + clearMap(instance, "styles"); + clearMap(instance, "selectedStyles"); + clearMap(instance, "themeConstants"); + clearMap(instance, "imageCache"); + clearMap(instance, "parseCache"); + + Field themePropsField = UIManager.class.getDeclaredField("themeProps"); + themePropsField.setAccessible(true); + Object themeProps = themePropsField.get(instance); + if (themeProps instanceof Map) { + ((Map) themeProps).clear(); + } + themePropsField.set(instance, null); + + Field resourceBundleField = UIManager.class.getDeclaredField("resourceBundle"); + resourceBundleField.setAccessible(true); + Object resourceBundle = resourceBundleField.get(instance); + if (resourceBundle instanceof Hashtable) { + ((Hashtable) resourceBundle).clear(); + } + resourceBundleField.set(instance, null); + + Field bundleField = UIManager.class.getDeclaredField("bundle"); + bundleField.setAccessible(true); + Object bundle = bundleField.get(instance); + if (bundle instanceof Map) { + ((Map) bundle).clear(); + } + bundleField.set(instance, null); + } instanceField.set(null, null); + + Field accessibleField = UIManager.class.getDeclaredField("accessible"); + accessibleField.setAccessible(true); + accessibleField.setBoolean(null, true); + + Field localeAccessibleField = UIManager.class.getDeclaredField("localeAccessible"); + localeAccessibleField.setAccessible(true); + localeAccessibleField.setBoolean(null, true); + } + + private void clearMap(UIManager manager, String fieldName) throws Exception { + Field field = UIManager.class.getDeclaredField(fieldName); + field.setAccessible(true); + Object value = field.get(manager); + if (value instanceof Map) { + ((Map) value).clear(); + } else if (value instanceof Hashtable) { + ((Hashtable) value).clear(); + } } private Graphics createGraphics() throws Exception { diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/GraphicsTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/GraphicsTest.java new file mode 100644 index 0000000000..352c56cf18 --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/ui/GraphicsTest.java @@ -0,0 +1,164 @@ +package com.codename1.ui; + +import com.codename1.test.UITestBase; +import com.codename1.ui.Paint; +import com.codename1.ui.Stroke; +import com.codename1.ui.geom.Rectangle; +import com.codename1.ui.geom.Shape; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.lang.reflect.Constructor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.same; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class GraphicsTest extends UITestBase { + + private Object nativeGraphics; + + @BeforeEach + @Override + protected void setUpDisplay() throws Exception { + super.setUpDisplay(); + nativeGraphics = new Object(); + } + + private Graphics newGraphics() throws Exception { + Constructor constructor = Graphics.class.getDeclaredConstructor(Object.class); + constructor.setAccessible(true); + return constructor.newInstance(nativeGraphics); + } + + @Test + void testTranslateDelegatesWhenSupported() throws Exception { + when(implementation.isTranslationSupported()).thenReturn(true); + when(implementation.getTranslateX(nativeGraphics)).thenReturn(3); + when(implementation.getTranslateY(nativeGraphics)).thenReturn(4); + Graphics graphics = newGraphics(); + graphics.translate(5, 6); + verify(implementation).translate(nativeGraphics, 5, 6); + assertEquals(3, graphics.getTranslateX()); + assertEquals(4, graphics.getTranslateY()); + } + + @Test + void testTranslateWithoutSupportAccumulates() throws Exception { + when(implementation.isTranslationSupported()).thenReturn(false); + Graphics graphics = newGraphics(); + graphics.translate(2, 3); + graphics.translate(1, -1); + verify(implementation, never()).translate(eq(nativeGraphics), anyInt(), anyInt()); + assertEquals(3, graphics.getTranslateX()); + assertEquals(2, graphics.getTranslateY()); + } + + @Test + void testSetColorAndSetAndGetColor() throws Exception { + Graphics graphics = newGraphics(); + graphics.setColor(0x123456); + verify(implementation).setColor(nativeGraphics, 0x123456); + int previous = graphics.setAndGetColor(0xABCDEF); + assertEquals(0x123456, previous); + verify(implementation).setColor(nativeGraphics, 0xABCDEF & 0xFFFFFF); + } + + @Test + void testClipRectUsesTranslationOffsets() throws Exception { + Graphics graphics = newGraphics(); + graphics.translate(5, 7); + graphics.clipRect(1, 2, 3, 4); + verify(implementation).clipRect(nativeGraphics, 6, 9, 3, 4); + } + + @Test + void testSetClipShapeAppliesTranslation() throws Exception { + when(implementation.isShapeSupported(nativeGraphics)).thenReturn(true); + when(implementation.isShapeClipSupported(nativeGraphics)).thenReturn(true); + Graphics graphics = newGraphics(); + graphics.translate(10, 5); + Rectangle shape = new Rectangle(0, 0, 10, 10); + graphics.setClip(shape); + ArgumentCaptor shapeCaptor = ArgumentCaptor.forClass(Shape.class); + verify(implementation).setClip(eq(nativeGraphics), shapeCaptor.capture()); + Rectangle bounds = shapeCaptor.getValue().getBounds(); + assertEquals(10, bounds.getX()); + assertEquals(5, bounds.getY()); + } + + @Test + void testDrawShapeTranslatesWhenNeeded() throws Exception { + when(implementation.isShapeSupported(nativeGraphics)).thenReturn(true); + Graphics graphics = newGraphics(); + graphics.translate(3, 4); + Rectangle shape = new Rectangle(0, 0, 5, 5); + Stroke stroke = new Stroke(1, Stroke.CAP_BUTT, Stroke.JOIN_MITER, 1f); + graphics.drawShape(shape, stroke); + ArgumentCaptor shapeCaptor = ArgumentCaptor.forClass(Shape.class); + verify(implementation).drawShape(eq(nativeGraphics), shapeCaptor.capture(), eq(stroke)); + Rectangle bounds = shapeCaptor.getValue().getBounds(); + assertEquals(3, bounds.getX()); + assertEquals(4, bounds.getY()); + } + + @Test + void testFillShapeWithPaintUsesCustomPaint() throws Exception { + when(implementation.isShapeSupported(nativeGraphics)).thenReturn(true); + when(implementation.isShapeClipSupported(nativeGraphics)).thenReturn(true); + when(implementation.getClipX(nativeGraphics)).thenReturn(0); + when(implementation.getClipY(nativeGraphics)).thenReturn(0); + when(implementation.getClipWidth(nativeGraphics)).thenReturn(100); + when(implementation.getClipHeight(nativeGraphics)).thenReturn(100); + Graphics graphics = newGraphics(); + Paint paint = mock(Paint.class); + graphics.setColor(paint); + Rectangle shape = new Rectangle(0, 0, 10, 10); + graphics.fillShape(shape); + verify(paint).paint(same(graphics), eq(0.0), eq(0.0), eq(10.0), eq(10.0)); + verify(implementation).setClip(eq(nativeGraphics), any(Shape.class)); + verify(implementation).clipRect(nativeGraphics, 0, 0, 100, 100); + verify(implementation).setClip(nativeGraphics, 0, 0, 100, 100); + } + + @Test + void testPushAndPopClipDelegates() throws Exception { + Graphics graphics = newGraphics(); + graphics.pushClip(); + graphics.popClip(); + verify(implementation).pushClip(nativeGraphics); + verify(implementation).popClip(nativeGraphics); + } + + @Test + void testDrawLineAppliesTranslation() throws Exception { + Graphics graphics = newGraphics(); + graphics.translate(2, 3); + graphics.drawLine(1, 1, 4, 4); + verify(implementation).drawLine(nativeGraphics, 3, 4, 6, 7); + } + + @Test + void testGetClipReflectsCurrentTranslation() throws Exception { + when(implementation.getClipX(nativeGraphics)).thenReturn(50); + when(implementation.getClipY(nativeGraphics)).thenReturn(60); + Graphics graphics = newGraphics(); + graphics.translate(5, 7); + assertEquals(45, graphics.getClipX()); + assertEquals(53, graphics.getClipY()); + } + + @Test + void testSetAlphaDelegatesToImplementation() throws Exception { + Graphics graphics = newGraphics(); + graphics.setAlpha(128); + verify(implementation).setAlpha(nativeGraphics, 128); + } +} diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/geom/GeneralPathTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/geom/GeneralPathTest.java new file mode 100644 index 0000000000..85e87e2824 --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/ui/geom/GeneralPathTest.java @@ -0,0 +1,210 @@ +package com.codename1.ui.geom; + +import com.codename1.ui.Transform; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class GeneralPathTest { + + @Test + void testBasicPathOperations() { + GeneralPath path = new GeneralPath(GeneralPath.WIND_NON_ZERO); + path.moveTo(0, 0); + path.lineTo(10, 0); + path.quadTo(20, 20, 30, 0); + path.curveTo(40, 10, 50, -10, 60, 0); + path.closePath(); + + assertEquals(5, path.getTypesSize(), "Unexpected number of path commands"); + assertEquals(14, path.getPointsSize(), "Unexpected number of stored coordinates"); + + byte[] types = new byte[path.getTypesSize()]; + path.getTypes(types); + assertArrayEquals(new byte[]{PathIterator.SEG_MOVETO, PathIterator.SEG_LINETO, PathIterator.SEG_QUADTO, + PathIterator.SEG_CUBICTO, PathIterator.SEG_CLOSE}, types, "Unexpected command sequence"); + + float[] points = new float[path.getPointsSize()]; + path.getPoints(points); + assertEquals(60f, points[path.getPointsSize() - 2], 1e-6f, "Last x coordinate should match the final curve endpoint"); + assertEquals(0f, points[path.getPointsSize() - 1], 1e-6f, "Last y coordinate should match the final curve endpoint"); + + float[] current = path.getCurrentPoint(); + assertArrayEquals(new float[]{0f, 0f}, current, 1e-6f, "Current point after closePath should be the start of the subpath"); + } + + @Test + void testBoundsAndReset() { + GeneralPath path = new GeneralPath(); + Rectangle rect = new Rectangle(10, 20, 30, 40); + path.setRect(rect, null); + + Rectangle bounds = path.getBounds(); + assertEquals(10, bounds.getX()); + assertEquals(20, bounds.getY()); + assertEquals(30, bounds.getWidth()); + assertEquals(40, bounds.getHeight()); + assertTrue(path.isRectangle(), "setRect should create a rectangular path"); + + path.reset(); + Rectangle empty = path.getBounds(); + assertEquals(0, empty.getWidth()); + assertEquals(0, empty.getHeight()); + assertEquals(0, path.getTypesSize()); + assertEquals(0, path.getPointsSize()); + } + + @Test + void testSetRectAndTransform() { + GeneralPath path = new GeneralPath(); + Rectangle rect = new Rectangle(10, 20, 30, 40); + path.setRect(rect, null); + + float[] rectPoints = new float[path.getPointsSize()]; + path.getPoints(rectPoints); + assertArrayEquals(new float[]{10, 20, 40, 20, 40, 60, 10, 60}, rectPoints, 1e-6f); + assertTrue(path.isRectangle()); + + GeneralPath copy = new GeneralPath(); + copy.setPath(path, null); + assertEquals(path.getTypesSize(), copy.getTypesSize()); + float[] copyPoints = new float[copy.getPointsSize()]; + copy.getPoints(copyPoints); + assertArrayEquals(rectPoints, copyPoints, 1e-6f); + } + + @Test + void testSetRectAppliesProvidedTransform() { + GeneralPath path = new GeneralPath(); + Rectangle rect = new Rectangle(0, 0, 10, 20); + Transform translation = Transform.makeTranslation(5, -5); + + path.setRect(rect, translation); + + float[] transformed = new float[path.getPointsSize()]; + path.getPoints(transformed); + + assertArrayEquals(new float[]{5f, -5f, 15f, -5f, 15f, 15f, 5f, 15f}, transformed, 1e-6f); + } + + @Test + void testAppendAndSetPath() { + GeneralPath base = new GeneralPath(); + base.moveTo(0, 0); + base.lineTo(5, 0); + + GeneralPath addition = new GeneralPath(); + addition.moveTo(5, 0); + addition.lineTo(5, 5); + addition.closePath(); + + base.append(addition, true); + assertEquals(4, base.getTypesSize(), "Append with connect should convert initial move into a line"); + + float[] currentPoint = base.getCurrentPoint(); + assertArrayEquals(new float[]{0f, 0f}, currentPoint, 1e-6f, "Closed path should report the start as current point"); + + GeneralPath copy = new GeneralPath(); + copy.setPath(base, null); + assertEquals(base.getTypesSize(), copy.getTypesSize()); + float[] basePoints = new float[base.getPointsSize()]; + base.getPoints(basePoints); + float[] copyPoints = new float[copy.getPointsSize()]; + copy.getPoints(copyPoints); + assertArrayEquals(basePoints, copyPoints, 1e-6f); + } + + @Test + void testIntersectionAndContains() { + GeneralPath path = new GeneralPath(); + path.setRect(new Rectangle(0, 0, 10, 10), null); + + assertTrue(path.contains(5, 5)); + assertFalse(path.contains(15, 5)); + + boolean intersected = path.intersect(new Rectangle(5, 5, 10, 10)); + assertTrue(intersected, "Intersecting rectangles should leave a non-empty path"); + Rectangle intersectionBounds = path.getBounds(); + assertEquals(5, intersectionBounds.getX()); + assertEquals(5, intersectionBounds.getY()); + assertEquals(5, intersectionBounds.getWidth()); + assertEquals(5, intersectionBounds.getHeight()); + + boolean cleared = path.intersect(new Rectangle(20, 20, 5, 5)); + assertFalse(cleared, "Non-overlapping intersection should return false"); + assertEquals(0, path.getTypesSize(), "Path should be cleared when intersection is empty"); + } + + @Test + void testConvexPolygonDetection() { + assertTrue(GeneralPath.isConvexPolygon(new float[]{0, 6, 6, 0}, new float[]{0, 0, 6, 6})); + assertFalse(GeneralPath.isConvexPolygon(new float[]{0, 4, 2, 4, 0}, new float[]{0, 0, 2, 4, 4})); + + assertTrue(GeneralPath.isConvexPolygon(new int[]{0, 6, 6, 0}, new int[]{0, 0, 6, 6})); + assertFalse(GeneralPath.isConvexPolygon(new int[]{0, 4, 2, 4, 0}, new int[]{0, 0, 2, 4, 4})); + } + + @Test + void testCreateFromPoolReuse() { + GeneralPath first = GeneralPath.createFromPool(); + first.moveTo(1, 1); + GeneralPath.recycle(first); + + GeneralPath second = GeneralPath.createFromPool(); + assertSame(first, second, "Returned path should be reused from the pool"); + assertEquals(0, second.getTypesSize(), "Reused path should be reset to empty state"); + GeneralPath.recycle(second); + } + + @Test + void testSetShapeCopiesNonRectangleShape() { + GeneralPath source = new GeneralPath(); + source.moveTo(0, 0); + source.curveTo(1, 2, 3, 4, 5, 6); + GeneralPath target = new GeneralPath(); + target.setShape(source, null); + + assertEquals(source.getTypesSize(), target.getTypesSize()); + float[] sourcePoints = new float[source.getPointsSize()]; + source.getPoints(sourcePoints); + float[] targetPoints = new float[target.getPointsSize()]; + target.getPoints(targetPoints); + assertArrayEquals(sourcePoints, targetPoints, 1e-6f); + } + + @Test + void testPathIteratorTraversesCommandsInInsertionOrder() { + GeneralPath path = new GeneralPath(); + path.moveTo(0, 0); + path.lineTo(10, 0); + path.lineTo(10, 10); + path.closePath(); + + PathIterator iterator = path.getPathIterator(); + float[] coords = new float[6]; + + assertFalse(iterator.isDone()); + assertEquals(PathIterator.SEG_MOVETO, iterator.currentSegment(coords)); + assertEquals(0f, coords[0], 1e-6f); + assertEquals(0f, coords[1], 1e-6f); + + iterator.next(); + assertFalse(iterator.isDone()); + assertEquals(PathIterator.SEG_LINETO, iterator.currentSegment(coords)); + assertEquals(10f, coords[0], 1e-6f); + assertEquals(0f, coords[1], 1e-6f); + + iterator.next(); + assertFalse(iterator.isDone()); + assertEquals(PathIterator.SEG_LINETO, iterator.currentSegment(coords)); + assertEquals(10f, coords[0], 1e-6f); + assertEquals(10f, coords[1], 1e-6f); + + iterator.next(); + assertFalse(iterator.isDone()); + assertEquals(PathIterator.SEG_CLOSE, iterator.currentSegment(coords)); + + iterator.next(); + assertTrue(iterator.isDone()); + } +} diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/geom/GeometryTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/geom/GeometryTest.java new file mode 100644 index 0000000000..663ccfe398 --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/ui/geom/GeometryTest.java @@ -0,0 +1,164 @@ +package com.codename1.ui.geom; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class GeometryTest { + + @Test + void testBezierCurveConstructionAndCopy() { + Geometry.BezierCurve curve = new Geometry.BezierCurve(0, 0, 1, 1, 2, 0); + assertEquals(2, curve.n()); + assertEquals(0d, curve.getStartPoint().getX(), 1e-6); + assertEquals(0d, curve.getStartPoint().getY(), 1e-6); + assertEquals(2d, curve.getEndPoint().getX(), 1e-6); + assertEquals(0d, curve.getEndPoint().getY(), 1e-6); + + Geometry.BezierCurve copy = new Geometry.BezierCurve(curve); + assertTrue(curve.equals(copy, 1e-9)); + } + + @Test + void testExtractBezierCurvesFromPath() { + GeneralPath path = new GeneralPath(); + path.moveTo(0, 0); + path.quadTo(1, 1, 2, 0); + path.curveTo(3, 1, 4, -1, 5, 0); + path.lineTo(6, 0); + + List curves = new ArrayList(); + Geometry.BezierCurve.extractBezierCurvesFromPath(path, curves); + assertEquals(2, curves.size(), "Should extract quadratic and cubic segments only"); + assertEquals(0d, curves.get(0).getStartPoint().getX(), 1e-6); + assertEquals(5d, curves.get(1).getEndPoint().getX(), 1e-6); + } + + @Test + void testPolynomialEvaluation() { + Geometry.BezierCurve curve = new Geometry.BezierCurve(0, 0, 1, 1, 2, 0); + assertEquals(0d, curve.x(0d), 1e-6); + assertEquals(2d, curve.x(1d), 1e-6); + assertEquals(1d, curve.x(0.5d), 1e-6); + + assertEquals(0d, curve.y(0d), 1e-6); + assertEquals(0d, curve.y(1d), 1e-6); + assertEquals(0.5d, curve.y(0.5d), 1e-6); + } + + @Test + void testDerivativeCoefficients() { + Geometry.BezierCurve cubic = new Geometry.BezierCurve(0, 0, 1, 3, 2, 3, 3, 0); + double[] dx = cubic.getDerivativeCoefficientsX(); + assertEquals(3d, dx[0], 1e-6); + assertEquals(0d, dx[1], 1e-6); + assertEquals(0d, dx[2], 1e-6); + + double[] dy = cubic.getDerivativeCoefficientsY(); + assertEquals(9d, dy[0], 1e-6); + assertEquals(-18d, dy[1], 1e-6); + assertEquals(0d, dy[2], 1e-6); + + Geometry.BezierCurve high = new Geometry.BezierCurve(0, 0, 1, 1, 2, 2, 3, 3, 4, 4); + assertThrows(IllegalArgumentException.class, high::getDerivativeCoefficientsX); + } + + @Test + void testReverseAndSegment() { + Geometry.BezierCurve curve = new Geometry.BezierCurve(0, 0, 1, 2, 2, 0); + Geometry.BezierCurve reversed = curve.reverse(); + assertEquals(curve.getStartPoint().getX(), reversed.getEndPoint().getX(), 1e-6); + assertEquals(curve.getStartPoint().getY(), reversed.getEndPoint().getY(), 1e-6); + assertEquals(curve.getEndPoint().getX(), reversed.getStartPoint().getX(), 1e-6); + assertEquals(curve.getEndPoint().getY(), reversed.getStartPoint().getY(), 1e-6); + + List segments = new ArrayList(); + curve.segment(0.5d, segments); + assertEquals(2, segments.size()); + Geometry.BezierCurve first = segments.get(0); + Geometry.BezierCurve second = segments.get(1); + assertEquals(first.getEndPoint().getX(), second.getStartPoint().getX(), 1e-6); + assertEquals(first.getEndPoint().getY(), second.getStartPoint().getY(), 1e-6); + } + + @Test + void testSegmentRectangleSplitsCurve() { + Geometry.BezierCurve curve = new Geometry.BezierCurve(0, 0, 2, 4, 4, 0); + Rectangle2D rect = new Rectangle2D(1d, 1d, 2d, 2d); + List pieces = new ArrayList(); + curve.segment(rect, pieces); + assertTrue(pieces.size() > 1, "Intersections with the rectangle should split the curve"); + Geometry.BezierCurve first = pieces.get(0); + Geometry.BezierCurve last = pieces.get(pieces.size() - 1); + assertEquals(curve.getStartPoint().getX(), first.getStartPoint().getX(), 1e-6); + assertEquals(curve.getEndPoint().getX(), last.getEndPoint().getX(), 1e-6); + } + + @Test + void testFindTValuesForYWithinRange() { + Geometry.BezierCurve curve = new Geometry.BezierCurve(0, 0, 2, 4, 4, 0); + double[] result = new double[3]; + int count = curve.findTValuesForY(2d, 1d, 3d, result); + assertEquals(1, count, "Expected a single tangential intersection at the chosen height"); + double t = result[0]; + assertTrue(t >= 0d && t <= 1d); + assertEquals(0.5d, t, 1e-6); + double x = curve.x(t); + assertTrue(x >= 1d - 1e-6 && x <= 3d + 1e-6); + } + + @Test + void testFindTValuesForXWithinRange() { + Geometry.BezierCurve curve = new Geometry.BezierCurve(0, 0, 2, 4, 4, 0); + double[] result = new double[3]; + int count = curve.findTValuesForX(2d, 0d, 4d, result); + assertEquals(1, count, "Expected a single intersection with the vertical line at x=2"); + assertEquals(0.5d, result[0], 1e-6); + assertEquals(2d, curve.y(result[0]), 1e-6); + + int filtered = curve.findTValuesForX(2d, 3d, 4d, result); + assertEquals(0, filtered, "Filtering outside of the y-range should produce no matches"); + } + + @Test + void testBoundingRectCoversCurveExtents() { + Geometry.BezierCurve curve = new Geometry.BezierCurve(0, 0, 1, 3, 2, -3, 3, 0); + Rectangle2D bounds = curve.getBoundingRect(); + assertTrue(bounds.getWidth() > 0); + assertTrue(bounds.getHeight() > 0); + assertTrue(bounds.getX() <= 0); + assertTrue(bounds.getX() + bounds.getWidth() >= 3); + } + + @Test + void testAddToPathHonorsJoinFlag() { + Geometry.BezierCurve quad = new Geometry.BezierCurve(0, 0, 1, 2, 2, 0); + GeneralPath fresh = new GeneralPath(); + quad.addToPath(fresh, false); + assertEquals(2, fresh.getTypesSize()); + assertEquals(6, fresh.getPointsSize()); + byte[] freshTypes = new byte[fresh.getTypesSize()]; + fresh.getTypes(freshTypes); + assertEquals(PathIterator.SEG_MOVETO, freshTypes[0]); + assertEquals(PathIterator.SEG_QUADTO, freshTypes[1]); + + GeneralPath seeded = new GeneralPath(); + seeded.moveTo(0, 0); + quad.addToPath(seeded, true); + assertEquals(2, seeded.getTypesSize()); + assertEquals(6, seeded.getPointsSize()); + byte[] seededTypes = new byte[seeded.getTypesSize()]; + seeded.getTypes(seededTypes); + assertEquals(PathIterator.SEG_MOVETO, seededTypes[0]); + assertEquals(PathIterator.SEG_QUADTO, seededTypes[1]); + float[] coords = new float[seeded.getPointsSize()]; + seeded.getPoints(coords); + assertEquals(0f, coords[0], 1e-6f); + assertEquals(0f, coords[1], 1e-6f); + assertEquals(2f, coords[4], 1e-6f); + assertEquals(0f, coords[5], 1e-6f); + } +} diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/geom/RectangleTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/geom/RectangleTest.java new file mode 100644 index 0000000000..3c9beb0385 --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/ui/geom/RectangleTest.java @@ -0,0 +1,95 @@ +package com.codename1.ui.geom; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class RectangleTest { + + @Test + void testStaticContains() { + assertTrue(Rectangle.contains(0, 0, 10, 10, 2, 2, 3, 3)); + assertFalse(Rectangle.contains(0, 0, 5, 5, 4, 4, 3, 3)); + } + + @Test + void testStaticIntersection() { + Rectangle dest = new Rectangle(); + Rectangle.intersection(0, 0, 10, 10, 5, 5, 10, 10, dest); + assertEquals(5, dest.getX()); + assertEquals(5, dest.getY()); + assertEquals(5, dest.getWidth()); + assertEquals(5, dest.getHeight()); + } + + @Test + void testPoolReuse() { + Rectangle first = Rectangle.createFromPool(1, 2, 3, 4); + Rectangle.recycle(first); + Rectangle second = Rectangle.createFromPool(5, 6, 7, 8); + assertSame(first, second, "Recycled rectangle should be reused from the pool"); + assertEquals(5, second.getX()); + assertEquals(6, second.getY()); + assertEquals(7, second.getWidth()); + assertEquals(8, second.getHeight()); + Rectangle.recycle(second); + } + + @Test + void testInstanceContainsAndIntersects() { + Rectangle rect = new Rectangle(0, 0, 10, 10); + assertTrue(rect.contains(2, 2)); + assertTrue(rect.contains(0, 0, 5, 5)); + assertFalse(rect.contains(9, 9, 5, 5)); + assertTrue(rect.intersects(8, 8, 5, 5)); + assertFalse(rect.intersects(20, 20, 2, 2)); + } + + @Test + void testIntersectionResults() { + Rectangle base = new Rectangle(0, 0, 10, 10); + Rectangle overlap = base.intersection(5, 5, 10, 10); + assertEquals(new Rectangle(5, 5, 5, 5), overlap); + + Rectangle disjoint = base.intersection(20, 20, 5, 5); + assertTrue(disjoint.getWidth() <= 0); + assertTrue(disjoint.getHeight() <= 0); + } + + @Test + void testPathIteratorProducesRectangle() { + Rectangle rect = new Rectangle(1, 2, 3, 4); + PathIterator iterator = rect.getPathIterator(); + int[] expectedTypes = new int[]{PathIterator.SEG_MOVETO, PathIterator.SEG_LINETO, PathIterator.SEG_LINETO, + PathIterator.SEG_LINETO, PathIterator.SEG_CLOSE}; + float[][] expectedPoints = new float[][]{ + {1f, 2f}, + {4f, 2f}, + {4f, 6f}, + {1f, 6f} + }; + float[] coords = new float[6]; + int index = 0; + while (!iterator.isDone()) { + int type = iterator.currentSegment(coords); + assertEquals(expectedTypes[index], type); + if (type != PathIterator.SEG_CLOSE) { + assertEquals(expectedPoints[index][0], coords[0], 1e-6f); + assertEquals(expectedPoints[index][1], coords[1], 1e-6f); + } + iterator.next(); + index++; + } + assertEquals(expectedTypes.length, index); + } + + @Test + void testEqualsAndHashCode() { + Rectangle first = new Rectangle(0, 0, 5, 5); + Rectangle second = new Rectangle(first); + assertEquals(first, second); + assertEquals(first.hashCode(), second.hashCode()); + second.setX(1); + assertNotEquals(first, second); + } +}