From 7e31a1c4966f7c57678da8c019f54575eff7f9eb Mon Sep 17 00:00:00 2001 From: Paola De Bartolo Date: Tue, 27 Oct 2026 11:32:19 -0300 Subject: [PATCH] fix!: reset cropped image when the image source changes BREAKING CHANGE: ImageCrop(String) no longer reports the image source as the cropped image; getCroppedImageDataUri() returns null until the image is cropped. addCroppedImageListener was removed, and the CroppedImageEvent constructor takes an additional image revision parameter. Close #38 Close #41 --- README.md | 11 ++- .../addons/imagecrop/CroppedImageEvent.java | 13 +++- .../vaadin/addons/imagecrop/ImageCrop.java | 65 +++++++++-------- .../META-INF/frontend/src/image-crop.tsx | 25 ++++++- .../addons/imagecrop/BasicImageCropDemo.java | 13 +--- .../addons/imagecrop/CroppedImageResult.java | 51 +++++++++++++ .../DownloadHandlerImageCropDemo.java | 16 +---- .../imagecrop/OutputFormatImageCropDemo.java | 18 ++--- .../addons/imagecrop/UploadImageCropDemo.java | 5 ++ .../addons/imagecrop/test/ImageCropTest.java | 71 ++++++++++++++++--- 10 files changed, 211 insertions(+), 77 deletions(-) create mode 100644 src/test/java/com/flowingcode/vaadin/addons/imagecrop/CroppedImageResult.java diff --git a/README.md b/README.md index f937b9d..590b770 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,16 @@ add(imageCrop); * Get cropped image ```java -Image croppedImage = new Image(imageCrop.getCroppedImageDataUri(), "cropped image") +String dataUri = imageCrop.getCroppedImageDataUri(); // null until the image is cropped +if (dataUri != null) { + add(new Image(dataUri, "cropped image")); +} +``` + +The cropped image is `null` until the image is cropped, either by the user or by an initial crop. To get the full image without user interaction, set an initial crop that covers it: + +```java +imageCrop.setCrop(new Crop("%", 0, 0, 100, 100)); ``` ## Special configuration when using Spring diff --git a/src/main/java/com/flowingcode/vaadin/addons/imagecrop/CroppedImageEvent.java b/src/main/java/com/flowingcode/vaadin/addons/imagecrop/CroppedImageEvent.java index 6f49b54..2f66276 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/imagecrop/CroppedImageEvent.java +++ b/src/main/java/com/flowingcode/vaadin/addons/imagecrop/CroppedImageEvent.java @@ -26,12 +26,15 @@ /** * Represents an event triggered when an image is cropped and encoded. + * This event is for internal use only. */ @DomEvent("cropped-image") public class CroppedImageEvent extends ComponentEvent { private String croppedImageDataUri; + private int imageRevision; + /** * Constructs a new CroppedImageEvent. * @@ -39,11 +42,19 @@ public class CroppedImageEvent extends ComponentEvent { * @param fromClient true if the event originated from the client-side, * false otherwise * @param croppedImageDataUri the data URL of the cropped image + * @param imageRevision the revision of the image that was cropped */ public CroppedImageEvent(ImageCrop source, boolean fromClient, - @EventData("event.detail.croppedImageDataUri") String croppedImageDataUri) { + @EventData("event.detail.croppedImageDataUri") String croppedImageDataUri, + @EventData("event.detail.imageRevision") int imageRevision) { super(source, fromClient); this.croppedImageDataUri = croppedImageDataUri; + this.imageRevision = imageRevision; + } + + /** Returns the revision of the image that was cropped. */ + int getImageRevision() { + return this.imageRevision; } /** diff --git a/src/main/java/com/flowingcode/vaadin/addons/imagecrop/ImageCrop.java b/src/main/java/com/flowingcode/vaadin/addons/imagecrop/ImageCrop.java index 3181f1e..1c24d7d 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/imagecrop/ImageCrop.java +++ b/src/main/java/com/flowingcode/vaadin/addons/imagecrop/ImageCrop.java @@ -25,14 +25,12 @@ import java.util.Objects; import com.vaadin.flow.component.AttachEvent; -import com.vaadin.flow.component.ComponentEventListener; import com.vaadin.flow.component.Tag; import com.vaadin.flow.component.dependency.CssImport; import com.vaadin.flow.component.dependency.JsModule; import com.vaadin.flow.component.dependency.NpmPackage; import com.vaadin.flow.component.react.ReactAdapterComponent; import com.vaadin.flow.server.streams.DownloadHandler; -import com.vaadin.flow.shared.Registration; /** * Component for cropping images based on @@ -53,8 +51,16 @@ public class ImageCrop extends ReactAdapterComponent { private static final String IMG_SRC_RESOURCE_ATTRIBUTE = "img-src"; + private static final String IMG_SRC_STATE = "imgSrc"; + private String croppedImageDataUri; + // incremented each time the image changes, so that a crop can be matched to its image + private int imageRevision; + + // revision of the image that croppedImageDataUri was cropped from + private int croppedImageRevision; + /** * Constructs an ImageCrop component with the given image URL. * @@ -62,8 +68,7 @@ public class ImageCrop extends ReactAdapterComponent { */ public ImageCrop(String src) { this.setImageSrc(src); - this.addCroppedImageListener(this::updateCroppedImage); - this.croppedImageDataUri = src; + addListener(CroppedImageEvent.class, this::updateCroppedImage); } /** @@ -76,20 +81,7 @@ public ImageCrop(String src) { */ public ImageCrop(DownloadHandler downloadHandler) { this.setImageSrc(downloadHandler); - this.addCroppedImageListener(this::updateCroppedImage); - } - - /** - * Adds a listener for the {@link CroppedImageEvent} fired when the - * cropped image is updated. - * - * @param listener the listener to be added - * @return a registration for the listener, which can be used to remove the - * listener - */ - protected Registration addCroppedImageListener( - ComponentEventListener listener) { - return this.addListener(CroppedImageEvent.class, listener); + addListener(CroppedImageEvent.class, this::updateCroppedImage); } /** @@ -99,15 +91,23 @@ protected Registration addCroppedImageListener( */ private void updateCroppedImage(CroppedImageEvent event) { this.croppedImageDataUri = event.getCroppedImageDataUri(); + this.croppedImageRevision = event.getImageRevision(); + } + + /** Marks the image as changed, so that crops of the previous image are discarded. */ + private void nextImageRevision() { + imageRevision++; + setState("imgRev", imageRevision); } @Override protected void onAttach(AttachEvent attachEvent) { super.onAttach(attachEvent); // the resource URL is regenerated on attach (e.g. with a new UI id after a - // refresh with @PreserveOnRefresh), so the image source must follow it + // refresh with @PreserveOnRefresh), so the image source must follow it; the + // image itself is the same, so its revision is kept if (getElement().hasAttribute(IMG_SRC_RESOURCE_ATTRIBUTE)) { - setState("imgSrc", getElement().getAttribute(IMG_SRC_RESOURCE_ATTRIBUTE)); + setState(IMG_SRC_STATE, getElement().getAttribute(IMG_SRC_RESOURCE_ATTRIBUTE)); } } @@ -117,8 +117,12 @@ protected void onAttach(AttachEvent attachEvent) { * @param imageSrc the image source */ public void setImageSrc(String imageSrc) { + // the browser only reloads (and re-crops) the image when its source changes + if (!Objects.equals(imageSrc, getImageSrc())) { + nextImageRevision(); + } getElement().removeAttribute(IMG_SRC_RESOURCE_ATTRIBUTE); - setState("imgSrc", imageSrc); + setState(IMG_SRC_STATE, imageSrc); } /** @@ -148,9 +152,10 @@ public void setImageSrc(String imageSrc) { */ public void setImageSrc(DownloadHandler downloadHandler) { Objects.requireNonNull(downloadHandler, "downloadHandler must not be null"); + // each handler is served under a new URL + nextImageRevision(); getElement().setAttribute(IMG_SRC_RESOURCE_ATTRIBUTE, downloadHandler); - setState("imgSrc", getElement().getAttribute(IMG_SRC_RESOURCE_ATTRIBUTE)); - croppedImageDataUri = null; + setState(IMG_SRC_STATE, getElement().getAttribute(IMG_SRC_RESOURCE_ATTRIBUTE)); } /** @@ -165,7 +170,7 @@ public void setImageSrc(DownloadHandler downloadHandler) { * @return the image source */ public String getImageSrc() { - return getState("imgSrc", String.class); + return getState(IMG_SRC_STATE, String.class); } /** @@ -488,13 +493,16 @@ public double getOutputQuality() { * Returns the cropped image data URI. * *

- * When the component is created with a {@link DownloadHandler}, this is {@code null} - * until the image is cropped for the first time. + * Only a crop of the current {@linkplain #getImageSrc() image source} is returned, so + * this is {@code null} until the current image is cropped for the first time. After + * the image source changes, it is set again when the user crops the new image, or as + * soon as the new image loads if a crop is already defined (either with + * {@link #setCrop(Crop)} or the selection kept from the previous image). * * @return the cropped image data URI, or {@code null} if not available yet */ public String getCroppedImageDataUri() { - return this.croppedImageDataUri; + return croppedImageRevision == imageRevision ? this.croppedImageDataUri : null; } /** @@ -522,7 +530,8 @@ public void setImageFullHeight(Boolean fullHeight) { * https://github.com/F0rce/cropper *

* - * @return byte[] the decoded byte array of the cropped image + * @return the decoded bytes of the cropped image, or {@code null} if the image has not + * been cropped yet */ public byte[] getCroppedImageBase64() { String croppedDataUri = this.getCroppedImageDataUri(); diff --git a/src/main/resources/META-INF/frontend/src/image-crop.tsx b/src/main/resources/META-INF/frontend/src/image-crop.tsx index cb484a2..560b99a 100644 --- a/src/main/resources/META-INF/frontend/src/image-crop.tsx +++ b/src/main/resources/META-INF/frontend/src/image-crop.tsx @@ -90,6 +90,8 @@ class ImageCropElement extends ReactAdapterElement { const [crop, setCrop] = hooks.useState("crop"); const [imgSrc] = hooks.useState("imgSrc"); + // incremented by the server each time the image changes + const [imgRev] = hooks.useState("imgRev", 0); const imgRef = useRef(null); const [imgAlt] = hooks.useState("imgAlt"); const [aspect] = hooks.useState("aspect"); @@ -156,6 +158,10 @@ class ImageCropElement extends ReactAdapterElement { * below). setCrop's Javadoc documents the difference. */ const onImageLoad = () => { + // The image whose pixels are now drawn; until the next load, every + // cropped image comes from it, even if imgSrc has already changed. + this.loadedImgSrc = imgSrc; + this.loadedImgRev = imgRev; const img = imgRef.current; if (!img || !crop) { return; @@ -245,11 +251,23 @@ class ImageCropElement extends ReactAdapterElement { ); } + /** + * Source of the image currently drawn, as set by the server (imgSrc). Recorded + * on load rather than read from image.src, which already points to the new + * source while the previous image is still displayed (see issue #41). + */ + private loadedImgSrc?: string; + + /** Revision (imgRev) of the image currently drawn, recorded on load like loadedImgSrc. */ + private loadedImgRev?: number; + private fireCroppedImageEvent(croppedImageDataUri: string) { this.dispatchEvent( new CustomEvent("cropped-image", { detail: { - croppedImageDataUri: croppedImageDataUri + croppedImageDataUri: croppedImageDataUri, + // lets the server discard a crop of an image that is no longer the source + imageRevision: this.loadedImgRev }, }) ); @@ -281,7 +299,7 @@ class ImageCropElement extends ReactAdapterElement { // convertToPixelCrop returns a "px" crop unchanged: it would keep the // configured size, pass the check below and draw a not-yet-loaded image, // firing a fully transparent image through "cropped-image". - if (!image.naturalWidth || !image.naturalHeight) { + if (!image.naturalWidth || !image.naturalHeight || this.loadedImgRev === undefined) { return; } @@ -336,7 +354,8 @@ class ImageCropElement extends ReactAdapterElement { ctx.restore(); // encode the cropped image using the resolved output format - const outputType = resolveOutputType(this.outputMimeType, image.src, this.circularCrop); + // detected from the image actually drawn, not from image.src (see loadedImgSrc) + const outputType = resolveOutputType(this.outputMimeType, this.loadedImgSrc, this.circularCrop); let croppedImageDataUri = canvas.toDataURL(outputType, this.outputQuality ?? 1.0); // dispatch the event containing cropped image diff --git a/src/test/java/com/flowingcode/vaadin/addons/imagecrop/BasicImageCropDemo.java b/src/test/java/com/flowingcode/vaadin/addons/imagecrop/BasicImageCropDemo.java index d689c95..79c26da 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/imagecrop/BasicImageCropDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/imagecrop/BasicImageCropDemo.java @@ -22,8 +22,6 @@ import com.flowingcode.vaadin.addons.demo.DemoSource; import com.vaadin.flow.component.button.Button; -import com.vaadin.flow.component.html.Div; -import com.vaadin.flow.component.html.Image; import com.vaadin.flow.component.html.Span; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.router.PageTitle; @@ -35,7 +33,7 @@ @Route(value = "image-crop/basic", layout = ImageCropDemoView.class) public class BasicImageCropDemo extends VerticalLayout { - private Div croppedResultDiv = new Div(); + private CroppedImageResult croppedResult = new CroppedImageResult(); public BasicImageCropDemo() { add(new Span("Select a portion of the picture to crop: ")); @@ -46,14 +44,9 @@ public BasicImageCropDemo() { Button getCropButton = new Button("Get Cropped Image"); - croppedResultDiv.setId("result-cropped-image-div"); + getCropButton.addClickListener(e -> croppedResult.show(imageCrop.getCroppedImageDataUri())); - getCropButton.addClickListener(e -> { - croppedResultDiv.removeAll(); - croppedResultDiv.add(new Image(imageCrop.getCroppedImageDataUri(), "cropped image")); - }); - - add(getCropButton, new Span("Crop Result:"), croppedResultDiv); + add(getCropButton, new Span("Crop Result:"), croppedResult); } } diff --git a/src/test/java/com/flowingcode/vaadin/addons/imagecrop/CroppedImageResult.java b/src/test/java/com/flowingcode/vaadin/addons/imagecrop/CroppedImageResult.java new file mode 100644 index 0000000..40c3ec2 --- /dev/null +++ b/src/test/java/com/flowingcode/vaadin/addons/imagecrop/CroppedImageResult.java @@ -0,0 +1,51 @@ +/*- + * #%L + * Image Crop Add-on + * %% + * Copyright (C) 2024-2026 Flowing Code + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +package com.flowingcode.vaadin.addons.imagecrop; + +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.html.Image; +import com.vaadin.flow.component.html.Span; + +/** Shows the cropped image of a demo, or asks the user to select an area first. */ +@SuppressWarnings("serial") +class CroppedImageResult extends Div { + + CroppedImageResult() { + setId("result-cropped-image-div"); + } + + /** + * Replaces the content with the given cropped image. + * + * @param croppedImageDataUri the cropped image, or {@code null} if there is none yet + * @return whether there was a cropped image to show + */ + boolean show(String croppedImageDataUri) { + removeAll(); + // there is no cropped image until the user selects an area + if (croppedImageDataUri == null) { + add(new Span("Select an area first")); + return false; + } + add(new Image(croppedImageDataUri, "cropped image")); + return true; + } +} diff --git a/src/test/java/com/flowingcode/vaadin/addons/imagecrop/DownloadHandlerImageCropDemo.java b/src/test/java/com/flowingcode/vaadin/addons/imagecrop/DownloadHandlerImageCropDemo.java index cbb38b6..ca722af 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/imagecrop/DownloadHandlerImageCropDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/imagecrop/DownloadHandlerImageCropDemo.java @@ -22,8 +22,6 @@ import com.flowingcode.vaadin.addons.demo.DemoSource; import com.vaadin.flow.component.button.Button; -import com.vaadin.flow.component.html.Div; -import com.vaadin.flow.component.html.Image; import com.vaadin.flow.component.html.Span; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.router.PageTitle; @@ -36,7 +34,7 @@ @Route(value = "image-crop/download-handler", layout = ImageCropDemoView.class) public class DownloadHandlerImageCropDemo extends VerticalLayout { - private Div croppedResultDiv = new Div(); + private CroppedImageResult croppedResult = new CroppedImageResult(); public DownloadHandlerImageCropDemo() { add(new Span("Select a portion of the picture to crop: ")); @@ -50,17 +48,9 @@ public DownloadHandlerImageCropDemo() { Button getCropButton = new Button("Get Cropped Image"); - croppedResultDiv.setId("result-cropped-image-div"); + getCropButton.addClickListener(e -> croppedResult.show(imageCrop.getCroppedImageDataUri())); - getCropButton.addClickListener(e -> { - croppedResultDiv.removeAll(); - // there is no cropped image until the user selects an area - if (imageCrop.getCroppedImageDataUri() != null) { - croppedResultDiv.add(new Image(imageCrop.getCroppedImageDataUri(), "cropped image")); - } - }); - - add(getCropButton, new Span("Crop Result:"), croppedResultDiv); + add(getCropButton, new Span("Crop Result:"), croppedResult); } } diff --git a/src/test/java/com/flowingcode/vaadin/addons/imagecrop/OutputFormatImageCropDemo.java b/src/test/java/com/flowingcode/vaadin/addons/imagecrop/OutputFormatImageCropDemo.java index 3bdc6ae..2dd9cc4 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/imagecrop/OutputFormatImageCropDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/imagecrop/OutputFormatImageCropDemo.java @@ -22,8 +22,6 @@ import com.flowingcode.vaadin.addons.demo.DemoSource; import com.vaadin.flow.component.button.Button; -import com.vaadin.flow.component.html.Div; -import com.vaadin.flow.component.html.Image; import com.vaadin.flow.component.html.Span; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.select.Select; @@ -38,7 +36,7 @@ public class OutputFormatImageCropDemo extends VerticalLayout { private static final String AUTO_DETECT = "Auto-detect"; - private Div croppedResultDiv = new Div(); + private CroppedImageResult croppedResult = new CroppedImageResult(); public OutputFormatImageCropDemo() { add(new Span("Choose an output format and crop the image. " @@ -61,18 +59,16 @@ public OutputFormatImageCropDemo() { add(formatSelect, imageCrop); Button getCropButton = new Button("Get Cropped Image"); - croppedResultDiv.setId("result-cropped-image-div"); - getCropButton.addClickListener(e -> { - croppedResultDiv.removeAll(); String dataUri = imageCrop.getCroppedImageDataUri(); - Span mimeType = new Span("Result MIME type: " + extractMimeType(dataUri)); - mimeType.getStyle().set("display", "block"); - croppedResultDiv.add(mimeType); - croppedResultDiv.add(new Image(dataUri, "cropped image")); + if (croppedResult.show(dataUri)) { + Span mimeType = new Span("Result MIME type: " + extractMimeType(dataUri)); + mimeType.getStyle().set("display", "block"); + croppedResult.addComponentAsFirst(mimeType); + } }); - add(getCropButton, new Span("Crop Result:"), croppedResultDiv); + add(getCropButton, new Span("Crop Result:"), croppedResult); } /** Reads the MIME type from the prefix of a {@code data:} URI, for display purposes. */ diff --git a/src/test/java/com/flowingcode/vaadin/addons/imagecrop/UploadImageCropDemo.java b/src/test/java/com/flowingcode/vaadin/addons/imagecrop/UploadImageCropDemo.java index cb91d8e..ae9bbb3 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/imagecrop/UploadImageCropDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/imagecrop/UploadImageCropDemo.java @@ -108,6 +108,11 @@ private void openCropDialog(ByteArrayOutputStream outputStream, String mimeType) imageCrop.setKeepSelection(true); cropButton.addClickListener(event -> { + // there is no cropped image until the image is loaded and an area is selected + if (imageCrop.getCroppedImageDataUri() == null) { + Notification.show("Select an area first", 3000, Notification.Position.MIDDLE); + return; + } newCroppedPicture = imageCrop.getCroppedImageBase64(); avatar.setImage(imageCrop.getCroppedImageDataUri()); dialog.close(); diff --git a/src/test/java/com/flowingcode/vaadin/addons/imagecrop/test/ImageCropTest.java b/src/test/java/com/flowingcode/vaadin/addons/imagecrop/test/ImageCropTest.java index 4baec3c..34985a3 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/imagecrop/test/ImageCropTest.java +++ b/src/test/java/com/flowingcode/vaadin/addons/imagecrop/test/ImageCropTest.java @@ -18,11 +18,14 @@ import com.flowingcode.vaadin.addons.imagecrop.Crop; import com.flowingcode.vaadin.addons.imagecrop.CroppedImageEvent; import com.flowingcode.vaadin.addons.imagecrop.ImageCrop; +import com.vaadin.flow.component.ComponentUtil; import com.vaadin.flow.component.UI; import com.vaadin.flow.server.streams.DownloadHandler; import com.vaadin.flow.server.streams.DownloadResponse; public class ImageCropTest { + private static final String CROPPED_IMAGE = "data:image/png;base64,SGVsbG8gV29ybGQ="; + private ImageCrop imageCrop; // keeps the current UI strongly reachable during the test @@ -57,12 +60,70 @@ public void testSetImageSrcWithStringReleasesDownloadHandler() { assertFalse(imageCrop.getElement().hasAttribute("img-src")); } + @Test + public void testCroppedImageOfCurrentSource() { + cropImage(imageRevision()); + assertEquals(CROPPED_IMAGE, imageCrop.getCroppedImageDataUri()); + assertNotNull(imageCrop.getCroppedImageBase64()); + } + @Test public void testSetImageSrcWithDownloadHandlerResetsCroppedImage() { + cropImage(imageRevision()); imageCrop.setImageSrc(imageHandler()); assertNull(imageCrop.getCroppedImageDataUri()); } + @Test + public void testSetImageSrcWithStringResetsCroppedImage() { + cropImage(imageRevision()); + imageCrop.setImageSrc("newImageSrc"); + assertNull(imageCrop.getCroppedImageDataUri()); + assertNull(imageCrop.getCroppedImageBase64()); + } + + @Test + public void testSetSameImageSrcKeepsCroppedImage() { + cropImage(imageRevision()); + imageCrop.setImageSrc("dummyImageSrc"); + assertEquals(CROPPED_IMAGE, imageCrop.getCroppedImageDataUri()); + } + + @Test + public void testLateCroppedImageOfPreviousSourceIsIgnored() { + int previousRevision = imageRevision(); + imageCrop.setImageSrc("newImageSrc"); + cropImage(previousRevision); + assertNull(imageCrop.getCroppedImageDataUri()); + } + + @Test + public void testSetPreviousImageSrcBackCountsAsNewImage() { + cropImage(imageRevision()); + imageCrop.setImageSrc("newImageSrc"); + imageCrop.setImageSrc("dummyImageSrc"); + // the client reloads the image and re-crops it if a crop is defined + assertNull(imageCrop.getCroppedImageDataUri()); + } + + /** The revision of the current image, as the client reads it from the component state. */ + private int imageRevision() { + return imageCrop.getElement().getProperty("imgRev", -1); + } + + /** Simulates a cropped-image event sent by the client for the given image revision. */ + private void cropImage(int imageRevision) { + ComponentUtil.fireEvent(imageCrop, + new CroppedImageEvent(imageCrop, true, CROPPED_IMAGE, imageRevision)); + } + + @Test + public void testStringConstructorHasNoCroppedImage() { + imageCrop = new ImageCrop("images/empty-plant.png"); + assertNull(imageCrop.getCroppedImageDataUri()); + assertNull(imageCrop.getCroppedImageBase64()); + } + @Test public void testDownloadHandlerConstructorHasNoCroppedImage() { imageCrop = new ImageCrop(imageHandler()); @@ -133,16 +194,6 @@ public void testGetOutputQualityDefaultsToOneWhenUnset() { assertEquals(1.0, imageCrop.getOutputQuality(), 0.0); } - @Test - public void testEncodedCroppedImageEvent() { - String expectedCroppedImageUri = "croppedImageUri"; - CroppedImageEvent event = mock(CroppedImageEvent.class); - when(event.getCroppedImageDataUri()).thenReturn(expectedCroppedImageUri); - imageCrop = mock(ImageCrop.class); - when(imageCrop.getCroppedImageDataUri()).thenReturn(expectedCroppedImageUri); - assertEquals(expectedCroppedImageUri, imageCrop.getCroppedImageDataUri()); - } - @Test public void testGetCroppedImageBase64() { byte[] expectedCroppedImageBytes = Base64.getDecoder().decode("SGVsbG8gV29ybGQ=");