diff --git a/maven/core-unittests/src/test/java/com/codename1/capture/CapturePackageTest.java b/maven/core-unittests/src/test/java/com/codename1/capture/CapturePackageTest.java new file mode 100644 index 0000000000..e039d82c11 --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/capture/CapturePackageTest.java @@ -0,0 +1,57 @@ +package com.codename1.capture; + +import com.codename1.junit.FormTest; +import com.codename1.junit.UITestBase; +import com.codename1.media.MediaRecorderBuilder; +import com.codename1.ui.events.ActionEvent; +import com.codename1.ui.events.ActionListener; +import com.codename1.capture.VideoCaptureConstraints; + +import static org.junit.jupiter.api.Assertions.*; + +class CapturePackageTest extends UITestBase { + + @FormTest + void captureSynchronousMethodsReturnConfiguredPaths() { + implementation.setNextCapturePhotoPath("file://photo.jpg"); + assertEquals("file://photo.jpg", Capture.capturePhoto()); + + implementation.setNextCaptureAudioPath("file://audio.wav"); + assertEquals("file://audio.wav", Capture.captureAudio()); + + implementation.setNextCaptureVideoPath("file://video.mp4"); + assertEquals("file://video.mp4", Capture.captureVideo()); + + VideoCaptureConstraints constraints = new VideoCaptureConstraints(); + Capture.captureVideo(constraints); + assertSame(constraints, implementation.getLastVideoConstraints()); + + MediaRecorderBuilder builder = new MediaRecorderBuilder(); + Capture.captureAudio(builder); + assertSame(builder, implementation.getLastMediaRecorderBuilder()); + } + + @FormTest + void asynchronousCaptureInvokesListenersImmediately() { + implementation.setNextCapturePhotoPath("file://async-photo.jpg"); + RecordingListener listener = new RecordingListener(); + Capture.capturePhoto(listener); + assertEquals("file://async-photo.jpg", listener.lastPath); + + implementation.setNextCaptureAudioPath("file://async-audio.wav"); + Capture.captureAudio(listener); + assertEquals("file://async-audio.wav", listener.lastPath); + + implementation.setNextCaptureVideoPath("file://async-video.mp4"); + Capture.captureVideo(listener); + assertEquals("file://async-video.mp4", listener.lastPath); + } + + private static class RecordingListener implements ActionListener { + private String lastPath; + + public void actionPerformed(ActionEvent evt) { + lastPath = (String) evt.getSource(); + } + } +} diff --git a/maven/core-unittests/src/test/java/com/codename1/charts/ChartComponentInteractionTest.java b/maven/core-unittests/src/test/java/com/codename1/charts/ChartComponentInteractionTest.java new file mode 100644 index 0000000000..a0b23aa9ce --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/charts/ChartComponentInteractionTest.java @@ -0,0 +1,127 @@ +package com.codename1.charts; + +import com.codename1.charts.compat.Canvas; +import com.codename1.charts.compat.Paint; +import com.codename1.charts.models.Point; +import com.codename1.charts.models.SeriesSelection; +import com.codename1.charts.models.XYMultipleSeriesDataset; +import com.codename1.charts.models.XYSeries; +import com.codename1.charts.renderers.SimpleSeriesRenderer; +import com.codename1.charts.renderers.XYMultipleSeriesRenderer; +import com.codename1.charts.renderers.XYSeriesRenderer; +import com.codename1.charts.views.AbstractChart; +import com.codename1.charts.views.LineChart; +import com.codename1.junit.FormTest; +import com.codename1.junit.UITestBase; +import com.codename1.ui.Form; +import com.codename1.ui.Graphics; +import com.codename1.ui.Image; +import com.codename1.ui.Transform; +import com.codename1.ui.geom.Rectangle; + +import static org.junit.jupiter.api.Assertions.*; + +class ChartComponentInteractionTest extends UITestBase { + + @FormTest + void panAndZoomFlagsPropagateToRenderer() { + XYMultipleSeriesDataset dataset = new XYMultipleSeriesDataset(); + XYSeries series = new XYSeries("Series"); + series.add(0, 1); + series.add(1, 2); + dataset.addSeries(series); + + XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer(); + renderer.addSeriesRenderer(new XYSeriesRenderer()); + ChartComponent component = new ChartComponent(new LineChart(dataset, renderer)); + + Form form = new Form(); + form.add(component); + form.show(); + + assertEquals(renderer.isZoomEnabled(), component.isZoomEnabled()); + assertEquals(renderer.isPanEnabled(), component.isPanEnabled()); + + component.setZoomEnabled(false); + assertFalse(component.isZoomEnabled()); + assertFalse(renderer.isZoomEnabled()); + + component.setZoomEnabled(true); + assertTrue(component.isZoomEnabled()); + assertTrue(renderer.isZoomEnabled()); + + component.setZoomEnabled(false, true); + assertTrue(component.isZoomEnabled()); + assertFalse(renderer.isZoomXEnabled()); + assertTrue(renderer.isZoomYEnabled()); + + component.setPanEnabled(false); + assertFalse(renderer.isPanEnabled()); + component.setPanEnabled(false, true); + assertTrue(renderer.isPanYEnabled()); + assertFalse(renderer.isPanXEnabled()); + } + + @FormTest + void chartUtilDelegatesToChartDraw() { + RecordingChart chart = new RecordingChart(); + ChartUtil util = new ChartUtil(); + Image buffer = Image.createImage(40, 30); + Graphics graphics = buffer.getGraphics(); + Rectangle bounds = new Rectangle(5, 6, 20, 10); + + util.paintChart(graphics, chart, bounds, 7, 9); + + assertSame(graphics, chart.lastGraphics); + assertEquals(bounds, chart.lastBounds); + assertEquals(7, chart.lastAbsX); + assertEquals(9, chart.lastAbsY); + } + + @FormTest + void transformSetterAndGetterPersistState() { + XYMultipleSeriesDataset dataset = new XYMultipleSeriesDataset(); + dataset.addSeries(new XYSeries("Series")); + XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer(); + renderer.addSeriesRenderer(new XYSeriesRenderer()); + ChartComponent component = new ChartComponent(new LineChart(dataset, renderer)); + Form form = new Form(); + form.add(component); + form.show(); + + Transform transform = Transform.makeIdentity(); + transform.translate(5, 7); + component.setTransform(transform); + assertSame(transform, component.getTransform()); + } + + private static class RecordingChart extends AbstractChart { + private Graphics lastGraphics; + private Rectangle lastBounds; + private int lastAbsX; + private int lastAbsY; + + @Override + public void draw(Canvas canvas, int x, int y, int width, int height, Paint paint) { + lastGraphics = canvas.g; + lastBounds = canvas.bounds; + lastAbsX = canvas.absoluteX; + lastAbsY = canvas.absoluteY; + } + + @Override + public void drawLegendShape(Canvas canvas, SimpleSeriesRenderer renderer, float x, float y, int seriesIndex, Paint paint) { + } + + @Override + public int getLegendShapeWidth(int seriesIndex) { + return 0; + } + + @Override + public SeriesSelection getSeriesAndPointForScreenCoordinate(Point point) { + return null; + } + + } +} diff --git a/maven/core-unittests/src/test/java/com/codename1/charts/compat/CompatCanvasTest.java b/maven/core-unittests/src/test/java/com/codename1/charts/compat/CompatCanvasTest.java new file mode 100644 index 0000000000..342b795610 --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/charts/compat/CompatCanvasTest.java @@ -0,0 +1,97 @@ +package com.codename1.charts.compat; + +import com.codename1.junit.FormTest; +import com.codename1.junit.UITestBase; +import com.codename1.testing.TestCodenameOneImplementation.FillOperation; +import com.codename1.testing.TestCodenameOneImplementation.GradientOperation; +import com.codename1.ui.Image; +import com.codename1.ui.geom.Rectangle; + +import static org.junit.jupiter.api.Assertions.*; + +class CompatCanvasTest extends UITestBase { + + @FormTest + void drawRectFillsOrStrokesBasedOnPaintStyle() { + Image image = Image.createImage(6, 6); + Canvas canvas = new Canvas(); + canvas.g = image.getGraphics(); + canvas.g.setAlpha(255); + canvas.bounds = new Rectangle(0, 0, 6, 6); + + Paint fillPaint = new Paint(); + fillPaint.setColor(0xFFFF0000); + fillPaint.setStyle(Paint.Style.FILL); + implementation.clearGraphicsOperations(); + canvas.drawRect(0, 0, 6, 6, fillPaint); + FillOperation fill = latestFill(); + assertNotNull(fill); + assertEquals(0, fill.getX()); + assertEquals(0, fill.getY()); + assertEquals(6, fill.getWidth()); + assertEquals(6, fill.getHeight()); + assertEquals(0xFFFF0000, fill.getColor()); + + Image strokeImage = Image.createImage(6, 6); + canvas.g = strokeImage.getGraphics(); + canvas.g.setAlpha(255); + Paint strokePaint = new Paint(); + strokePaint.setColor(0xFF00FF00); + strokePaint.setStyle(Paint.Style.STROKE); + implementation.clearGraphicsOperations(); + canvas.drawRect(0, 0, 6, 6, strokePaint); + boolean hasHorizontalEdge = false; + boolean hasVerticalEdge = false; + for (FillOperation op : implementation.getFillOperationsSnapshot()) { + if (op.getColor() == 0xFF00FF00 && op.getY() == 0 && op.getHeight() == 1 && op.getWidth() == 6) { + hasHorizontalEdge = true; + } + if (op.getColor() == 0xFF00FF00 && op.getX() == 0 && op.getWidth() == 1 && op.getHeight() >= 4) { + hasVerticalEdge = true; + } + } + assertTrue(hasHorizontalEdge); + assertTrue(hasVerticalEdge); + } + + @FormTest + void gradientDrawableDrawsUsingCanvasOrientation() { + Image image = Image.createImage(4, 4); + Canvas canvas = new Canvas(); + canvas.g = image.getGraphics(); + canvas.g.setAlpha(255); + canvas.bounds = new Rectangle(0, 0, 4, 4); + + GradientDrawable horizontal = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, + new int[]{0xFF0000FF, 0xFFFF0000}); + horizontal.setBounds(0, 0, 4, 4); + implementation.clearGraphicsOperations(); + horizontal.draw(canvas); + GradientOperation gradient = implementation.getLastGradientOperation(); + assertNotNull(gradient); + assertTrue(gradient.isHorizontal()); + assertEquals(0xFF0000FF, gradient.getStartColor()); + assertEquals(0xFFFF0000, gradient.getEndColor()); + + Image fallbackImage = Image.createImage(4, 4); + canvas.g = fallbackImage.getGraphics(); + canvas.g.setAlpha(255); + GradientDrawable fallback = new GradientDrawable(GradientDrawable.Orientation.TL_BR, + new int[]{0xFF00FF00, 0xFF000000}); + fallback.setBounds(0, 0, 4, 4); + implementation.clearGraphicsOperations(); + fallback.draw(canvas); + assertNull(implementation.getLastGradientOperation()); + FillOperation fallbackFill = latestFill(); + assertNotNull(fallbackFill); + assertEquals(0xFF00FF00, fallbackFill.getColor()); + } + + private FillOperation latestFill() { + FillOperation fill = null; + for (FillOperation op : implementation.getFillOperationsSnapshot()) { + fill = op; + } + return fill; + } +} diff --git a/maven/core-unittests/src/test/java/com/codename1/charts/transitions/XYTransitionsTest.java b/maven/core-unittests/src/test/java/com/codename1/charts/transitions/XYTransitionsTest.java new file mode 100644 index 0000000000..47d6539f40 --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/charts/transitions/XYTransitionsTest.java @@ -0,0 +1,100 @@ +package com.codename1.charts.transitions; + +import com.codename1.charts.ChartComponent; +import com.codename1.charts.models.XYMultipleSeriesDataset; +import com.codename1.charts.models.XYSeries; +import com.codename1.charts.models.XYValueSeries; +import com.codename1.charts.renderers.XYMultipleSeriesRenderer; +import com.codename1.charts.renderers.XYSeriesRenderer; +import com.codename1.charts.views.LineChart; +import com.codename1.junit.FormTest; +import com.codename1.junit.UITestBase; +import com.codename1.ui.Form; + +import static org.junit.jupiter.api.Assertions.*; + +class XYTransitionsTest extends UITestBase { + + @FormTest + void xySeriesTransitionAnimatesBufferValues() throws Exception { + XYMultipleSeriesDataset dataset = new XYMultipleSeriesDataset(); + XYSeries series = new XYSeries("Series"); + series.add(0, 1); + series.add(1, 2); + dataset.addSeries(series); + + ChartComponent chartComponent = createChartComponent(dataset); + Form form = new Form(); + form.add(chartComponent); + form.show(); + + XYSeriesTransition transition = new XYSeriesTransition(chartComponent, series); + XYSeries buffer = new XYSeries(series.getTitle(), series.getScaleNumber()); + buffer.add(0, 5); + buffer.add(1, 7); + transition.setBuffer(buffer); + + transition.setDuration(5); + transition.animateChart(); + while (transition.animate()) { + Thread.sleep(5); + } + + assertEquals(2, series.getItemCount()); + assertEquals(5.0, series.getY(0)); + assertEquals(7.0, series.getY(1)); + assertEquals(0, buffer.getItemCount()); + } + + @FormTest + void multiSeriesTransitionUpdatesAllSeries() throws Exception { + XYMultipleSeriesDataset dataset = new XYMultipleSeriesDataset(); + XYSeries first = new XYSeries("First"); + first.add(0, 1); + first.add(1, 2); + XYSeries second = new XYSeries("Second"); + second.add(0, 4); + second.add(1, 5); + dataset.addSeries(first); + dataset.addSeries(second); + + ChartComponent chartComponent = createChartComponent(dataset); + Form form = new Form(); + form.add(chartComponent); + form.show(); + + XYMultiSeriesTransition transition = new XYMultiSeriesTransition(chartComponent, dataset); + XYMultipleSeriesDataset buffer = transition.getBuffer(); + assertEquals(2, buffer.getSeriesCount()); + buffer.getSeriesAt(0).add(0, 10); + buffer.getSeriesAt(0).add(1, 12); + buffer.getSeriesAt(1).add(0, 8); + buffer.getSeriesAt(1).add(1, 9); + + transition.setDuration(5); + transition.animateChart(); + while (transition.animate()) { + Thread.sleep(5); + } + + assertEquals(10.0, first.getY(0)); + assertEquals(12.0, first.getY(1)); + assertEquals(8.0, second.getY(0)); + assertEquals(9.0, second.getY(1)); + assertEquals(0, buffer.getSeriesAt(0).getItemCount()); + assertEquals(0, buffer.getSeriesAt(1).getItemCount()); + } + + private ChartComponent createChartComponent(XYMultipleSeriesDataset dataset) { + XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer(); + for (int i = 0; i < dataset.getSeriesCount(); i++) { + renderer.addSeriesRenderer(new XYSeriesRenderer()); + } + LineChart chart = new LineChart(dataset, renderer); + ChartComponent component = new ChartComponent(chart); + component.setWidth(100); + component.setHeight(100); + return component; + } + +} diff --git a/maven/core-unittests/src/test/java/com/codename1/db/DatabaseTest.java b/maven/core-unittests/src/test/java/com/codename1/db/DatabaseTest.java index 52bcf46b02..4cbe0bcc2c 100644 --- a/maven/core-unittests/src/test/java/com/codename1/db/DatabaseTest.java +++ b/maven/core-unittests/src/test/java/com/codename1/db/DatabaseTest.java @@ -26,14 +26,20 @@ void testDatabaseOperationsUseImplementation() throws IOException { testDb.setQueryResult(new String[]{"id", "name"}, rows); Cursor cursor = db.executeQuery("select * from users"); + assertEquals(2, cursor.getColumnCount()); + assertEquals("id", cursor.getColumnName(0)); + assertEquals(0, cursor.getColumnIndex("id")); assertTrue(cursor.first()); Row row = cursor.getRow(); assertEquals(1, row.getInteger(0)); assertEquals("Alice", row.getString(1)); + assertEquals(1.0d, row.getDouble(0)); assertTrue(cursor.next()); row = cursor.getRow(); assertEquals(2, row.getInteger(0)); assertEquals("Bob", row.getString(1)); + assertEquals(2L, row.getLong(0)); + assertFalse(cursor.next()); cursor.close(); db.beginTransaction(); diff --git a/maven/core-unittests/src/test/java/com/codename1/io/services/ServicesPackageTest.java b/maven/core-unittests/src/test/java/com/codename1/io/services/ServicesPackageTest.java new file mode 100644 index 0000000000..dece9d11bd --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/io/services/ServicesPackageTest.java @@ -0,0 +1,80 @@ +package com.codename1.io.services; + +import com.codename1.junit.FormTest; +import com.codename1.junit.UITestBase; +import com.codename1.ui.events.ActionEvent; +import com.codename1.ui.events.ActionListener; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.lang.reflect.Field; + +import static org.junit.jupiter.api.Assertions.*; + +class ServicesPackageTest extends UITestBase { + + @FormTest + void cachedDataExternalizesAndRestoresState() throws Exception { + CachedData cached = new CachedData(); + cached.setUrl("https://example.com/data"); + cached.setData(new byte[]{1, 2, 3}); + cached.setEtag("etag-value"); + cached.setModified("Wed, 01 Jan 2020 00:00:00 GMT"); + cached.setFetching(true); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + cached.externalize(new DataOutputStream(out)); + + CachedData restored = new CachedData(); + restored.internalize(restored.getVersion(), new DataInputStream(new ByteArrayInputStream(out.toByteArray()))); + + assertArrayEquals(new byte[]{1, 2, 3}, restored.getData()); + assertEquals("https://example.com/data", restored.getUrl()); + assertEquals("etag-value", restored.getEtag()); + assertEquals("Wed, 01 Jan 2020 00:00:00 GMT", restored.getModified()); + assertFalse(restored.isFetching()); + + restored.setFetching(true); + assertTrue(restored.isFetching()); + } + + @FormTest + void imageDownloadServiceStaticConfigurationApplies() throws Exception { + boolean originalFastScale = ImageDownloadService.isFastScale(); + boolean originalAlwaysRevalidate = ImageDownloadService.isAlwaysRevalidate(); + boolean originalDefaultMaintain = ImageDownloadService.isDefaultMaintainAspectRatio(); + int originalTimeout = ImageDownloadService.getDefaultTimeout(); + try { + ImageDownloadService.setFastScale(false); + assertFalse(ImageDownloadService.isFastScale()); + ImageDownloadService.setAlwaysRevalidate(true); + assertTrue(ImageDownloadService.isAlwaysRevalidate()); + ImageDownloadService.setDefaultMaintainAspectRatio(true); + assertTrue(ImageDownloadService.isDefaultMaintainAspectRatio()); + ImageDownloadService.setDefaultTimeout(5000); + assertEquals(5000, ImageDownloadService.getDefaultTimeout()); + + ActionListener listener = new ActionListener() { + public void actionPerformed(ActionEvent evt) { + } + }; + ImageDownloadService.addErrorListener(listener); + assertNotNull(getErrorDispatcher()); + ImageDownloadService.removeErrorListener(listener); + assertNull(getErrorDispatcher()); + } finally { + ImageDownloadService.setFastScale(originalFastScale); + ImageDownloadService.setAlwaysRevalidate(originalAlwaysRevalidate); + ImageDownloadService.setDefaultMaintainAspectRatio(originalDefaultMaintain); + ImageDownloadService.setDefaultTimeout(originalTimeout); + } + } + + private Object getErrorDispatcher() throws Exception { + Field field = ImageDownloadService.class.getDeclaredField("onErrorListeners"); + field.setAccessible(true); + return field.get(null); + } +} diff --git a/maven/core-unittests/src/test/java/com/codename1/share/ShareServiceTest.java b/maven/core-unittests/src/test/java/com/codename1/share/ShareServiceTest.java new file mode 100644 index 0000000000..10230d7950 --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/share/ShareServiceTest.java @@ -0,0 +1,74 @@ +package com.codename1.share; + +import com.codename1.junit.FormTest; +import com.codename1.junit.UITestBase; +import com.codename1.ui.Form; +import com.codename1.ui.Image; +import com.codename1.ui.events.ActionEvent; + +import static org.junit.jupiter.api.Assertions.*; + +class ShareServiceTest extends UITestBase { + + @FormTest + void actionPerformedChoosesCorrectShareMethod() { + RecordingShare share = new RecordingShare(); + RecordingForm form = new RecordingForm(); + share.setOriginalForm(form); + share.setMessage("hello world"); + + share.actionPerformed(new ActionEvent(this)); + assertEquals("hello world", share.lastText); + assertNull(share.lastImagePath); + + share.setImage("file://image.png", "image/png"); + share.actionPerformed(new ActionEvent(this)); + assertEquals("file://image.png", share.lastImagePath); + assertEquals("image/png", share.lastMime); + } + + @FormTest + void finishShowsOriginalForm() { + RecordingShare share = new RecordingShare(); + RecordingForm form = new RecordingForm(); + share.setOriginalForm(form); + + share.finish(); + assertTrue(form.showBackInvoked); + } + + private static class RecordingShare extends ShareService { + private String lastText; + private String lastImagePath; + private String lastMime; + + RecordingShare() { + super("Test", (Image) null); + } + + public void share(String text) { + lastText = text; + lastImagePath = null; + lastMime = null; + } + + public void share(String text, String image, String mimeType) { + lastText = text; + lastImagePath = image; + lastMime = mimeType; + } + + public boolean canShareImage() { + return true; + } + } + + private static class RecordingForm extends Form { + private boolean showBackInvoked; + + public void showBack() { + showBackInvoked = true; + super.showBack(); + } + } +} diff --git a/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java b/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java index db7d777a26..4d70c391b7 100644 --- a/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java +++ b/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java @@ -1,5 +1,6 @@ package com.codename1.testing; +import com.codename1.capture.VideoCaptureConstraints; import com.codename1.contacts.Contact; import com.codename1.db.Cursor; import com.codename1.db.Database; @@ -38,12 +39,14 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Hashtable; import java.util.Enumeration; +import java.util.Deque; import java.util.List; import java.util.Map; import java.util.Queue; @@ -93,6 +96,8 @@ public class TestCodenameOneImplementation extends CodenameOneImplementation { private Shape lastDrawShape; private Shape lastFillShape; private Stroke lastDrawStroke; + private final Deque fillOperations = new ArrayDeque(); + private final Deque gradientOperations = new ArrayDeque(); private String[] accessPointIds = new String[0]; private final Map accessPointTypes = new HashMap<>(); private final Map accessPointNames = new HashMap<>(); @@ -103,7 +108,7 @@ public class TestCodenameOneImplementation extends CodenameOneImplementation { private MediaRecorderBuilderHandler mediaRecorderBuilderHandler; private MediaRecorderHandler mediaRecorderHandler; private boolean animation; - private String[] availableRecordingMimeTypes; + private String[] availableRecordingMimeTypes = new String[]{"audio/wav"}; private Media mediaRecorder; private boolean trueTypeSupported = true; private static TestCodenameOneImplementation instance; @@ -159,6 +164,11 @@ public class TestCodenameOneImplementation extends CodenameOneImplementation { private int galleryTypeSupportedCallCount; private int lastGalleryTypeQuery; private final Map galleryTypeSupport = new HashMap(); + private String nextCapturePhotoPath = "file://test-photo.jpg"; + private String nextCaptureVideoPath = "file://test-video.mp4"; + private String nextCaptureAudioPath = "file://test-audio.wav"; + private MediaRecorderBuilder lastMediaRecorderBuilder; + private VideoCaptureConstraints lastVideoConstraints; public TestCodenameOneImplementation() { @@ -825,6 +835,23 @@ public Stroke getLastDrawStroke() { return lastDrawStroke; } + public void clearGraphicsOperations() { + fillOperations.clear(); + gradientOperations.clear(); + } + + public List getFillOperationsSnapshot() { + return new ArrayList(fillOperations); + } + + public List getGradientOperationsSnapshot() { + return new ArrayList(gradientOperations); + } + + public GradientOperation getLastGradientOperation() { + return gradientOperations.isEmpty() ? null : gradientOperations.peekLast(); + } + public void resetShapeTracking() { drawShapeInvoked = false; fillShapeInvoked = false; @@ -1346,10 +1373,103 @@ public void drawLine(Object graphics, int x1, int y1, int x2, int y2) { @Override public void fillRect(Object graphics, int x, int y, int width, int height) { + if (!(graphics instanceof TestGraphics)) { + return; + } + TestGraphics g = (TestGraphics) graphics; + fillArea(g, x, y, width, height); } @Override public void drawRect(Object graphics, int x, int y, int width, int height) { + if (!(graphics instanceof TestGraphics)) { + return; + } + TestGraphics g = (TestGraphics) graphics; + if (width <= 0 || height <= 0) { + return; + } + int drawWidth = Math.max(1, width); + int drawHeight = Math.max(1, height); + fillArea(g, x, y, drawWidth, 1); + if (drawHeight > 1) { + fillArea(g, x, y + drawHeight - 1, drawWidth, 1); + } + if (drawHeight > 2) { + fillArea(g, x, y + 1, 1, drawHeight - 2); + if (drawWidth > 1) { + fillArea(g, x + drawWidth - 1, y + 1, 1, drawHeight - 2); + } + } + } + + private void fillArea(TestGraphics g, int x, int y, int width, int height) { + fillArea(g, x, y, width, height, currentColor(g)); + } + + private void fillArea(TestGraphics g, int x, int y, int width, int height, int argb) { + if (g.image == null || width <= 0 || height <= 0) { + return; + } + int translatedX = x + g.translateX; + int translatedY = y + g.translateY; + int clipLeft = g.clipX; + int clipTop = g.clipY; + int clipRight = clipLeft + Math.max(0, g.clipWidth); + int clipBottom = clipTop + Math.max(0, g.clipHeight); + + int startX = Math.max(translatedX, clipLeft); + int startY = Math.max(translatedY, clipTop); + int endX = Math.min(translatedX + width, clipRight); + int endY = Math.min(translatedY + height, clipBottom); + + if (startX >= endX || startY >= endY) { + return; + } + + int recordedWidth = endX - startX; + int recordedHeight = endY - startY; + recordFillOperation(startX, startY, recordedWidth, recordedHeight, argb); + + for (int row = startY; row < endY; row++) { + if (row < 0 || row >= g.image.height) { + continue; + } + int offset = row * g.image.width; + for (int col = startX; col < endX; col++) { + if (col < 0 || col >= g.image.width) { + continue; + } + g.image.argb[offset + col] = argb; + } + } + } + + private int currentColor(TestGraphics g) { + int alpha = g.alpha; + if (alpha < 0) { + alpha = 0; + } else if (alpha > 255) { + alpha = 255; + } + return (alpha << 24) | (g.color & 0x00ffffff); + } + + private void recordFillOperation(int x, int y, int width, int height, int color) { + if (width <= 0 || height <= 0) { + return; + } + if (fillOperations.size() >= 256) { + fillOperations.removeFirst(); + } + fillOperations.addLast(new FillOperation(x, y, width, height, color)); + } + + private void recordGradientOperation(GradientOperation operation) { + if (gradientOperations.size() >= 64) { + gradientOperations.removeFirst(); + } + gradientOperations.addLast(operation); } @Override @@ -1377,6 +1497,30 @@ public void drawArc(Object graphics, int x, int y, int width, int height, int st public void drawString(Object graphics, String str, int x, int y) { } + @Override + public void fillLinearGradient(Object graphics, int startColor, int endColor, int x, int y, int width, int height, boolean horizontal) { + if (graphics instanceof TestGraphics) { + TestGraphics g = (TestGraphics) graphics; + int translatedX = x + g.translateX; + int translatedY = y + g.translateY; + recordGradientOperation(new GradientOperation(translatedX, translatedY, Math.max(0, width), Math.max(0, height), startColor, endColor, horizontal)); + if (width <= 0 || height <= 0) { + return; + } + if (horizontal) { + int split = Math.max(1, width / 2); + fillArea(g, x, y, split, height, startColor); + fillArea(g, x + split, y, Math.max(0, width - split), height, endColor); + } else { + int split = Math.max(1, height / 2); + fillArea(g, x, y, width, split, startColor); + fillArea(g, x, y + split, width, Math.max(0, height - split), endColor); + } + return; + } + super.fillLinearGradient(graphics, startColor, endColor, x, y, width, height, horizontal); + } + @Override public void drawImage(Object graphics, Object img, int x, int y) { } @@ -1396,6 +1540,11 @@ public Object getNativeGraphics(Object image) { if (img.graphics == null) { img.graphics = new TestGraphics(img.width, img.height); } + img.graphics.image = img; + img.graphics.clipX = 0; + img.graphics.clipY = 0; + img.graphics.clipWidth = img.width; + img.graphics.clipHeight = img.height; return img.graphics; } @@ -2591,6 +2740,90 @@ public void close() throws IOException { } } + public static final class FillOperation { + private final int x; + private final int y; + private final int width; + private final int height; + private final int color; + + FillOperation(int x, int y, int width, int height, int color) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.color = color; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public int getColor() { + return color; + } + } + + public static final class GradientOperation { + private final int x; + private final int y; + private final int width; + private final int height; + private final int startColor; + private final int endColor; + private final boolean horizontal; + + GradientOperation(int x, int y, int width, int height, int startColor, int endColor, boolean horizontal) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.startColor = startColor; + this.endColor = endColor; + this.horizontal = horizontal; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public int getStartColor() { + return startColor; + } + + public int getEndColor() { + return endColor; + } + + public boolean isHorizontal() { + return horizontal; + } + } + public static final class TestGraphics { int color = 0x000000; int alpha = 0xff; @@ -2601,6 +2834,7 @@ public static final class TestGraphics { int translateX; int translateY; TestFont font; + TestImage image; TestGraphics(int width, int height) { this.clipWidth = width; @@ -3054,6 +3288,56 @@ public void resetGalleryTracking() { galleryTypeSupport.clear(); } + @Override + public void capturePhoto(ActionListener response) { + response.actionPerformed(new ActionEvent(nextCapturePhotoPath)); + } + + @Override + public void captureAudio(ActionListener response) { + captureAudio(new MediaRecorderBuilder(), response); + } + + @Override + public void captureAudio(MediaRecorderBuilder recordingOptions, ActionListener response) { + if (recordingOptions == null) { + recordingOptions = new MediaRecorderBuilder(); + } + lastMediaRecorderBuilder = recordingOptions; + response.actionPerformed(new ActionEvent(nextCaptureAudioPath)); + } + + @Override + public void captureVideo(ActionListener response) { + response.actionPerformed(new ActionEvent(nextCaptureVideoPath)); + } + + @Override + public void captureVideo(VideoCaptureConstraints constraints, ActionListener response) { + lastVideoConstraints = constraints; + captureVideo(response); + } + + public void setNextCapturePhotoPath(String path) { + nextCapturePhotoPath = path; + } + + public void setNextCaptureVideoPath(String path) { + nextCaptureVideoPath = path; + } + + public void setNextCaptureAudioPath(String path) { + nextCaptureAudioPath = path; + } + + public MediaRecorderBuilder getLastMediaRecorderBuilder() { + return lastMediaRecorderBuilder; + } + + public VideoCaptureConstraints getLastVideoConstraints() { + return lastVideoConstraints; + } + public static final class TestFile { final boolean directory; final byte[] content; diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/AnimationAwareForm.java b/maven/core-unittests/src/test/java/com/codename1/ui/AnimationAwareForm.java new file mode 100644 index 0000000000..d9835261a8 --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/ui/AnimationAwareForm.java @@ -0,0 +1,8 @@ +package com.codename1.ui; + +public class AnimationAwareForm extends Form { + public boolean hasAnimationsExposed() { + return hasAnimations(); + } +} + diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/spinner/SpinnerPackageTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/spinner/SpinnerPackageTest.java new file mode 100644 index 0000000000..5a669d6554 --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/ui/spinner/SpinnerPackageTest.java @@ -0,0 +1,63 @@ +package com.codename1.ui.spinner; + +import com.codename1.junit.FormTest; +import com.codename1.junit.UITestBase; +import com.codename1.ui.events.SelectionListener; + +import static org.junit.jupiter.api.Assertions.*; + +class SpinnerPackageTest extends UITestBase { + + @FormTest + void numericSpinnerReflectsPropertyChanges() { + NumericSpinner spinner = new NumericSpinner(); + spinner.setMin(5); + spinner.setMax(15); + spinner.setStep(2); + spinner.setValue(9); + spinner.initSpinner(); + + assertEquals(5.0, spinner.getMin()); + assertEquals(15.0, spinner.getMax()); + assertEquals(2.0, spinner.getStep()); + assertEquals(9.0, spinner.getValue()); + + assertEquals(new Double(5.0), spinner.getPropertyValue("min")); + assertEquals(new Double(15.0), spinner.getPropertyValue("max")); + assertEquals(new Double(9.0), spinner.getPropertyValue("value")); + assertEquals(new Double(2.0), spinner.getPropertyValue("step")); + + spinner.setPropertyValue("min", 7); + spinner.setPropertyValue("max", 12); + spinner.setPropertyValue("value", 10); + spinner.setPropertyValue("step", 1.5); + + assertEquals(7.0, spinner.getMin()); + assertEquals(12.0, spinner.getMax()); + assertEquals(10.0, spinner.getValue()); + assertEquals(1.5, spinner.getStep()); + } + + @FormTest + void spinnerNumberModelNotifiesSelectionListeners() { + SpinnerNumberModel model = new SpinnerNumberModel(0, 10, 0, 1); + RecordingSelection selection = new RecordingSelection(); + model.addSelectionListener(selection); + model.setSelectedIndex(3); + assertEquals(3, model.getSelectedIndex()); + assertTrue(selection.wasNotified); + assertEquals(3, model.getItemAt(model.getSelectedIndex())); + + selection.wasNotified = false; + model.setSelectedIndex(model.getSelectedIndex()); + assertFalse(selection.wasNotified); + } + + private static class RecordingSelection implements SelectionListener { + private boolean wasNotified; + + public void selectionChanged(int oldSelection, int newSelection) { + wasNotified = true; + } + } +} diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/tree/TreePackageTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/tree/TreePackageTest.java new file mode 100644 index 0000000000..d8987fb37b --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/ui/tree/TreePackageTest.java @@ -0,0 +1,137 @@ +package com.codename1.ui.tree; + +import com.codename1.components.SpanButton; +import com.codename1.junit.FormTest; +import com.codename1.junit.UITestBase; +import com.codename1.ui.Button; +import com.codename1.ui.Component; +import com.codename1.ui.Container; +import com.codename1.ui.Form; +import com.codename1.ui.DisplayTest; +import com.codename1.ui.events.ActionEvent; +import com.codename1.ui.events.ActionListener; + +import java.util.Vector; + +import static org.junit.jupiter.api.Assertions.*; + +class TreePackageTest extends UITestBase { + + @FormTest + void treeStateRestoresExpansionAndLeafEvents() { + SimpleModel model = new SimpleModel(); + Tree tree = new Tree(model); + Form form = new Form(); + form.add(tree); + form.show(); + + RecordingListener recordingListener = new RecordingListener(); + tree.addLeafListener(recordingListener); + + Component leafComponent = tree.findNodeComponent(SimpleModel.LEAF); + assertNotNull(leafComponent); + assertTrue(fireAction(leafComponent)); + flushSerialCalls(); + DisplayTest.flushEdt(); + flushSerialCalls(); + DisplayTest.flushEdt(); + assertEquals(SimpleModel.LEAF, recordingListener.lastSource); + + tree.expandPath(SimpleModel.PARENT); + flushSerialCalls(); + DisplayTest.flushEdt(); + Tree.TreeState state = tree.getTreeState(); + tree.collapsePath(SimpleModel.PARENT); + + Tree restored = new Tree(model); + + Form otherForm = new Form(); + otherForm.add(restored); + otherForm.show(); + + flushSerialCalls(); + DisplayTest.flushEdt(); + + restored.setTreeState(state); + } + + @FormTest + void multilineModeUsesSpanButtons() { + SimpleModel model = new SimpleModel(); + Tree tree = new Tree(model); + Form form = new Form(); + form.add(tree); + form.show(); + + tree.setMultilineMode(true); + tree.setModel(model); + + Component node = tree.findNodeComponent(SimpleModel.PARENT); + assertTrue(node instanceof SpanButton); + } + + private boolean fireAction(Component component) { + if (component instanceof Button) { + Button button = (Button) component; + Form form = button.getComponentForm(); + if (form == null) { + return false; + } + int targetX = button.getAbsoluteX() + Math.max(1, button.getWidth() / 2); + int targetY = button.getAbsoluteY() + Math.max(1, button.getHeight() / 2); + implementation.dispatchPointerPressAndRelease(targetX, targetY); + flushSerialCalls(); + DisplayTest.flushEdt(); + flushSerialCalls(); + return true; + } + if (component instanceof Container) { + Container container = (Container) component; + int count = container.getComponentCount(); + for (int i = 0; i < count; i++) { + if (fireAction(container.getComponentAt(i))) { + return true; + } + } + } + return false; + } + + private static class SimpleModel implements TreeModel { + static final String PARENT = "Parent"; + static final String LEAF = "Leaf"; + private final Vector roots; + private final Vector children; + + SimpleModel() { + roots = new Vector(); + roots.addElement(PARENT); + roots.addElement(LEAF); + children = new Vector(); + children.addElement("Child 1"); + children.addElement("Child 2"); + } + + public Vector getChildren(Object parent) { + if (parent == null) { + return roots; + } + if (PARENT.equals(parent)) { + return children; + } + return new Vector(); + } + + public boolean isLeaf(Object node) { + return !PARENT.equals(node); + } + } + + private static class RecordingListener implements ActionListener { + private Object lastSource; + + public void actionPerformed(ActionEvent evt) { + lastSource = evt.getSource(); + } + } +} diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/util/UIUtilPackageTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/util/UIUtilPackageTest.java new file mode 100644 index 0000000000..934ed66afd --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/ui/util/UIUtilPackageTest.java @@ -0,0 +1,113 @@ +package com.codename1.ui.util; + +import com.codename1.junit.FormTest; +import com.codename1.junit.UITestBase; +import com.codename1.ui.AnimationAwareForm; +import com.codename1.ui.DisplayTest; +import com.codename1.ui.events.ActionEvent; +import com.codename1.ui.events.ActionListener; + +import java.util.HashMap; +import java.util.Vector; + +import static org.junit.jupiter.api.Assertions.*; + +class UIUtilPackageTest extends UITestBase { + + @FormTest + void eventDispatcherDispatchesAndRemovesListeners() { + EventDispatcher dispatcher = new EventDispatcher(); + RecordingActionListener first = new RecordingActionListener(); + RecordingActionListener second = new RecordingActionListener(); + dispatcher.addListener(first); + dispatcher.addListener(second); + + dispatcher.fireActionEvent(new ActionEvent(this)); + flushSerialCalls(); + assertEquals(1, first.invocations); + assertEquals(1, second.invocations); + + dispatcher.removeListener(first); + dispatcher.fireActionEvent(new ActionEvent(this)); + flushSerialCalls(); + assertEquals(1, first.invocations); + assertEquals(2, second.invocations); + + Vector listenerVector = dispatcher.getListenerVector(); + listenerVector.clear(); + assertEquals(1, dispatcher.getListenerCollection().size()); + } + + @FormTest + void weakHashMapStoresValuesUsingDisplayReferences() { + WeakHashMap map = new WeakHashMap(); + assertTrue(map.isEmpty()); + map.put("key", "value"); + assertEquals(1, map.size()); + assertTrue(map.containsKey("key")); + assertEquals("value", map.get("key")); + assertEquals("value", map.remove("key")); + assertTrue(map.isEmpty()); + assertThrows(RuntimeException.class, map::values); + + HashMap other = new HashMap(); + other.put("a", "b"); + map.putAll(other); + assertEquals("b", map.get("a")); + map.clear(); + assertTrue(map.isEmpty()); + } + + @FormTest + void uiTimerSchedulesAndCancelsTasks() throws Exception { + AnimationAwareForm form = new AnimationAwareForm(); + form.show(); + + DisplayTest.flushEdt(); + flushSerialCalls(); + + CountingRunnable counting = new CountingRunnable(); + UITimer timer = new UITimer(counting); + timer.schedule(0, false, form); + assertTrue(form.hasAnimationsExposed()); + timer.testEllapse(); + assertEquals(1, counting.count); + flushSerialCalls(); + DisplayTest.flushEdt(); + assertFalse(form.hasAnimationsExposed()); + + counting.count = 0; + UITimer repeating = new UITimer(counting); + repeating.schedule(0, true, form); + assertTrue(form.hasAnimationsExposed()); + repeating.testEllapse(); + assertEquals(1, counting.count); + repeating.testEllapse(); + assertEquals(2, counting.count); + assertTrue(form.hasAnimationsExposed()); + + repeating.cancel(); + assertFalse(form.hasAnimationsExposed()); + int before = counting.count; + flushSerialCalls(); + DisplayTest.flushEdt(); + assertEquals(before, counting.count); + } + + private static class RecordingActionListener implements ActionListener { + int invocations; + + public void actionPerformed(ActionEvent evt) { + invocations++; + } + } + + private static class CountingRunnable implements Runnable { + int count; + + public void run() { + count++; + } + } + +} diff --git a/maven/core-unittests/src/test/java/com/codename1/util/AsyncResourceTest.java b/maven/core-unittests/src/test/java/com/codename1/util/AsyncResourceTest.java new file mode 100644 index 0000000000..c049ff8e0d --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/util/AsyncResourceTest.java @@ -0,0 +1,61 @@ +package com.codename1.util; + +import com.codename1.junit.FormTest; +import com.codename1.junit.UITestBase; +import com.codename1.util.SuccessCallback; + +import static org.junit.jupiter.api.Assertions.*; + +class AsyncResourceTest extends UITestBase { + + @FormTest + void allCompletesWhenAllResourcesFinish() throws Exception { + AsyncResource first = new AsyncResource(); + AsyncResource second = new AsyncResource(); + AsyncResource combined = AsyncResource.all(first, second); + RecordingCallback success = new RecordingCallback(); + combined.ready(success); + + first.complete("one"); + assertFalse(success.invoked); + second.complete("two"); + assertTrue(success.invoked); + + AsyncResource.await(first, second); + } + + @FormTest + void errorPropagatesThroughCombinedResources() { + AsyncResource first = new AsyncResource(); + AsyncResource second = new AsyncResource(); + AsyncResource combined = AsyncResource.all(first, second); + RecordingErrorCallback errors = new RecordingErrorCallback(); + combined.except(errors); + + RuntimeException failure = new RuntimeException("boom"); + second.error(failure); + assertSame(failure, errors.lastError); + + AsyncResource.AsyncExecutionException ex = assertThrows(AsyncResource.AsyncExecutionException.class, + () -> AsyncResource.await(first, second)); + assertSame(failure, ex.getCause()); + assertFalse(AsyncResource.isCancelled(ex)); + assertTrue(AsyncResource.isCancelled(new AsyncResource.AsyncExecutionException(new AsyncResource.CancellationException()))); + } + + private static class RecordingCallback implements SuccessCallback { + private boolean invoked; + + public void onSucess(Boolean value) { + invoked = true; + } + } + + private static class RecordingErrorCallback implements SuccessCallback { + private Throwable lastError; + + public void onSucess(Throwable value) { + lastError = value; + } + } +}