From 22d20f67625e260f5f015ee691dcef476c58b8bd Mon Sep 17 00:00:00 2001 From: Anna Koskinen Date: Wed, 20 Dec 2017 16:13:36 +0200 Subject: [PATCH] Ensure Upload is properly reset after an upload is interrupted (#9635) (#10457) * Ensure Upload is properly reset after an upload is interrupted (#9635) --- .../java/com/vaadin/client/ui/VUpload.java | 1 + .../components/upload/InterruptUpload.java | 226 ++++++++++++++++++ .../upload/InterruptUploadTest.java | 113 +++++++++ 3 files changed, 340 insertions(+) create mode 100644 uitest/src/main/java/com/vaadin/tests/components/upload/InterruptUpload.java create mode 100644 uitest/src/test/java/com/vaadin/tests/components/upload/InterruptUploadTest.java diff --git a/client/src/main/java/com/vaadin/client/ui/VUpload.java b/client/src/main/java/com/vaadin/client/ui/VUpload.java index 0eb2322af2..ad3eca7e4f 100644 --- a/client/src/main/java/com/vaadin/client/ui/VUpload.java +++ b/client/src/main/java/com/vaadin/client/ui/VUpload.java @@ -209,6 +209,7 @@ public class VUpload extends SimplePanel { * file. A new target frame is created later." */ cleanTargetFrame(); + rebuildPanel(); submitted = false; } } diff --git a/uitest/src/main/java/com/vaadin/tests/components/upload/InterruptUpload.java b/uitest/src/main/java/com/vaadin/tests/components/upload/InterruptUpload.java new file mode 100644 index 0000000000..277d484109 --- /dev/null +++ b/uitest/src/main/java/com/vaadin/tests/components/upload/InterruptUpload.java @@ -0,0 +1,226 @@ +package com.vaadin.tests.components.upload; + +import java.io.OutputStream; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.FormLayout; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.ProgressBar; +import com.vaadin.ui.UI; +import com.vaadin.ui.Upload; +import com.vaadin.ui.Upload.FailedEvent; +import com.vaadin.ui.Upload.FinishedEvent; +import com.vaadin.ui.Upload.FinishedListener; +import com.vaadin.ui.Upload.Receiver; +import com.vaadin.ui.Upload.StartedEvent; +import com.vaadin.ui.Upload.StartedListener; +import com.vaadin.ui.Upload.SucceededEvent; +import com.vaadin.ui.Window; + +public class InterruptUpload extends AbstractTestUI { + + private Upload sample; + private UploadInfoWindow uploadInfoWindow; + + @Override + protected void setup(VaadinRequest request) { + LineBreakCounter lineBreakCounter = new LineBreakCounter(); + lineBreakCounter.setSlow(true); + + sample = new Upload(null, lineBreakCounter); + sample.setImmediate(true); + sample.setButtonCaption("Upload File"); + + uploadInfoWindow = new UploadInfoWindow(sample, lineBreakCounter); + + sample.addStartedListener(new StartedListener() { + @Override + public void uploadStarted(StartedEvent event) { + if (uploadInfoWindow.getParent() == null) { + UI.getCurrent().addWindow(uploadInfoWindow); + } + uploadInfoWindow.setClosable(false); + + } + }); + sample.addFinishedListener(new FinishedListener() { + @Override + public void uploadFinished(FinishedEvent event) { + uploadInfoWindow.setClosable(true); + } + }); + + addComponent(sample); + } + + private static class UploadInfoWindow extends Window + implements Upload.StartedListener, Upload.ProgressListener, + Upload.FailedListener, Upload.SucceededListener, + Upload.FinishedListener { + private final Label state = new Label(); + private final Label result = new Label(); + private final Label fileName = new Label(); + private final Label textualProgress = new Label(); + + private final ProgressBar progressBar = new ProgressBar(); + private final Button cancelButton; + private final LineBreakCounter counter; + + private UploadInfoWindow(final Upload upload, + final LineBreakCounter lineBreakCounter) { + super("Status"); + counter = lineBreakCounter; + + addStyleName("upload-info"); + + setResizable(false); + setDraggable(false); + + final FormLayout uploadInfoLayout = new FormLayout(); + setContent(uploadInfoLayout); + uploadInfoLayout.setMargin(true); + + final HorizontalLayout stateLayout = new HorizontalLayout(); + stateLayout.setSpacing(true); + stateLayout.addComponent(state); + + cancelButton = new Button("Cancel"); + cancelButton.addClickListener(new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + upload.interruptUpload(); + } + }); + cancelButton.setVisible(false); + cancelButton.setStyleName("small"); + stateLayout.addComponent(cancelButton); + + stateLayout.setCaption("Current state"); + state.setValue("Idle"); + uploadInfoLayout.addComponent(stateLayout); + + fileName.setCaption("File name"); + uploadInfoLayout.addComponent(fileName); + + result.setCaption("Line breaks counted"); + uploadInfoLayout.addComponent(result); + + progressBar.setCaption("Progress"); + progressBar.setVisible(false); + uploadInfoLayout.addComponent(progressBar); + + textualProgress.setVisible(false); + uploadInfoLayout.addComponent(textualProgress); + + upload.addStartedListener(this); + upload.addProgressListener(this); + upload.addFailedListener(this); + upload.addSucceededListener(this); + upload.addFinishedListener(this); + + } + + @Override + public void uploadFinished(final FinishedEvent event) { + state.setValue("Idle"); + progressBar.setVisible(false); + textualProgress.setVisible(false); + cancelButton.setVisible(false); + UI.getCurrent().setPollInterval(-1); + } + + @Override + public void uploadStarted(final StartedEvent event) { + // this method gets called immediately after upload is started + progressBar.setValue(0f); + progressBar.setVisible(true); + UI.getCurrent().setPollInterval(500); + textualProgress.setVisible(true); + // updates to client + state.setValue("Uploading"); + fileName.setValue(event.getFilename()); + + cancelButton.setVisible(true); + } + + @Override + public void updateProgress(final long readBytes, + final long contentLength) { + // this method gets called several times during the update + progressBar.setValue(readBytes / (float) contentLength); + textualProgress.setValue( + "Processed " + readBytes + " bytes of " + contentLength); + result.setValue(counter.getLineBreakCount() + " (counting...)"); + } + + @Override + public void uploadSucceeded(final SucceededEvent event) { + result.setValue(counter.getLineBreakCount() + " (total)"); + } + + @Override + public void uploadFailed(final FailedEvent event) { + result.setValue( + counter.getLineBreakCount() + " (counting interrupted at " + + Math.round(100 * progressBar.getValue()) + "%)"); + } + } + + private static class LineBreakCounter implements Receiver { + private int counter; + private int total; + private boolean sleep; + + /** + * return an OutputStream that simply counts lineends + */ + @Override + public OutputStream receiveUpload(final String filename, + final String MIMEType) { + counter = 0; + total = 0; + return new OutputStream() { + private static final int searchedByte = '\n'; + + @Override + public void write(final int b) { + total++; + if (b == searchedByte) { + counter++; + } + if (sleep && total % 1000 == 0) { + try { + Thread.sleep(100); + } catch (final InterruptedException e) { + e.printStackTrace(); + } + } + } + }; + } + + private int getLineBreakCount() { + return counter; + } + + private void setSlow(boolean value) { + sleep = value; + } + } + + @Override + protected Integer getTicketNumber() { + return 9635; + } + + @Override + public String getDescription() { + return "Interrupting an upload shouldn't prevent uploading that same file immediately afterwards."; + } + +} diff --git a/uitest/src/test/java/com/vaadin/tests/components/upload/InterruptUploadTest.java b/uitest/src/test/java/com/vaadin/tests/components/upload/InterruptUploadTest.java new file mode 100644 index 0000000000..ee8e0146a7 --- /dev/null +++ b/uitest/src/test/java/com/vaadin/tests/components/upload/InterruptUploadTest.java @@ -0,0 +1,113 @@ +package com.vaadin.tests.components.upload; + +import static org.junit.Assert.assertTrue; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.List; + +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.internal.WrapsElement; +import org.openqa.selenium.remote.DesiredCapabilities; +import org.openqa.selenium.remote.LocalFileDetector; +import org.openqa.selenium.remote.RemoteWebElement; + +import com.vaadin.testbench.elements.ButtonElement; +import com.vaadin.testbench.elements.LabelElement; +import com.vaadin.testbench.elements.WindowElement; +import com.vaadin.tests.tb3.MultiBrowserTest; +import com.vaadin.tests.util.LoremIpsum; + +public class InterruptUploadTest extends MultiBrowserTest { + + @Override + public List getBrowsersToTest() { + // PhantomJS fails to upload files for unknown reasons + return getBrowsersExcludingPhantomJS(); + } + + @Test + public void testInterruptUpload() throws Exception { + openTestURL(); + + File tempFile = createTempFile(); + fillPathToUploadInput(tempFile.getPath()); + + waitForElementPresent(By.className("v-window")); + + $(ButtonElement.class).caption("Cancel").first().click(); + + String expected = " (counting interrupted at "; + String actual = $(LabelElement.class).caption("Line breaks counted") + .first().getText(); + assertTrue("Line break count note does not match expected (was: " + + actual + ")", actual.contains(expected)); + + $(WindowElement.class).first() + .findElement(By.className("v-window-closebox")).click(); + waitForElementNotPresent(By.className("v-window")); + + tempFile = createTempFile(); + fillPathToUploadInput(tempFile.getPath()); + + waitForElementPresent(By.className("v-window")); + $(ButtonElement.class).caption("Cancel").first().click(); + } + + /** + * @return The generated temp file handle + * @throws IOException + */ + private File createTempFile() throws IOException { + File tempFile = File.createTempFile("TestFileUpload", ".txt"); + BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile)); + writer.write(getTempFileContents()); + writer.close(); + tempFile.deleteOnExit(); + return tempFile; + } + + private String getTempFileContents() { + StringBuilder sb = new StringBuilder("This is a big test file!"); + for (int i = 0; i < 70; ++i) { + sb.append("\n"); + sb.append(LoremIpsum.get()); + } + return sb.toString(); + } + + private void fillPathToUploadInput(String tempFileName) throws Exception { + // create a valid path in upload input element. Instead of selecting a + // file by some file browsing dialog, we use the local path directly. + WebElement input = getInput(); + setLocalFileDetector(input); + input.sendKeys(tempFileName); + } + + private WebElement getInput() { + return getDriver().findElement(By.className("gwt-FileUpload")); + } + + private void setLocalFileDetector(WebElement element) throws Exception { + if (getRunLocallyBrowser() != null) { + return; + } + + if (element instanceof WrapsElement) { + element = ((WrapsElement) element).getWrappedElement(); + } + if (element instanceof RemoteWebElement) { + ((RemoteWebElement) element) + .setFileDetector(new LocalFileDetector()); + } else { + throw new IllegalArgumentException( + "Expected argument of type RemoteWebElement, received " + + element.getClass().getName()); + } + } + +} -- 2.39.5