Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
- TextArea
- TextField
- Time Picker
- Upload

## Components not implemented

Expand All @@ -39,7 +40,6 @@
- Message Input
- Message List
- Multi-Select Combobox
- Upload
- Virtual List

## Shared interfaces implemented
Expand All @@ -66,4 +66,4 @@

- HasSize

## Known issues
## Known issues
160 changes: 160 additions & 0 deletions src/main/java/org/vaadin/addons/dramafinder/element/UploadElement.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package org.vaadin.addons.dramafinder.element;

import java.nio.file.Path;

import com.microsoft.playwright.Locator;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.options.AriaRole;
import org.vaadin.addons.dramafinder.element.shared.FocusableElement;
import org.vaadin.addons.dramafinder.element.shared.HasEnabledElement;
import org.vaadin.addons.dramafinder.element.shared.HasThemeElement;
import org.vaadin.addons.dramafinder.element.shared.HasValidationPropertiesElement;

import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;

/**
* PlaywrightElement for {@code vaadin-upload}.
* <p>
* Provides helpers to feed files via the native file input, inspect the file
* list entries, and assert upload completion using the file row state.
* Factory lookup relies on the upload button {@link AriaRole#BUTTON button}
* accessible name.
*/
@PlaywrightElement(UploadElement.FIELD_TAG_NAME)
public class UploadElement extends VaadinElement
implements HasEnabledElement,
HasValidationPropertiesElement, HasThemeElement, FocusableElement {

public static final String FIELD_TAG_NAME = "vaadin-upload";
public static final String FILE_ITEM_TAG_NAME = "vaadin-upload-file";

/**
* Create a new {@code UploadElement}.
*
* @param locator the locator for the {@code vaadin-upload} element
*/
public UploadElement(Locator locator) {
super(locator);
}

/**
* {@inheritDoc}
*/
@Override
public Locator getFocusLocator() {
return getUploadButtonLocator();
}

/**
* {@inheritDoc}
*/
@Override
public Locator getEnabledLocator() {
return getUploadButtonLocator();
}

/**
* Locator for the native {@code input[type=file]} element.
*
* @return the file input locator
*/
public Locator getFileInputLocator() {
return getLocator().locator("input[type=\"file\"]").first();
}

/**
* Locator for the primary upload button.
*
* @return the upload button locator
*/
public Locator getUploadButtonLocator() {
return getLocator().locator("vaadin-button[slot=\"add-button\"]").first();
}

/**
* Locator for a specific file row.
*
* @param fileName the file name to search
* @return the matching file row locator
*/
public Locator getFileItemLocator(String fileName) {
return getLocator().locator(FILE_ITEM_TAG_NAME)
.filter(new Locator.FilterOptions().setHasText(fileName))
.first();
}

/**
* Locator for the status cell of a given file row.
*
* @param fileName the file name to search
* @return the matching status locator
*/
public Locator getFileStatusLocator(String fileName) {
return getFileItemLocator(fileName).locator("[part=\"status\"]").first();
}

/**
* Upload one or more files by feeding the hidden input.
*
* @param files file paths to upload
*/
public void uploadFiles(Path... files) {
getFileInputLocator().setInputFiles(files);
}

/**
* Remove a file from the list using the remove button.
*
* @param fileName the file name to remove
*/
public void removeFile(String fileName) {
getFileItemLocator(fileName).locator("[part=\"remove-button\"]").first().click();
}

/**
* Assert that a file is listed in the upload file list.
*
* @param fileName the expected file name
*/
public void assertHasFile(String fileName) {
assertThat(getFileItemLocator(fileName)).isVisible();
}

/**
* Assert that a file is not present in the upload file list.
*
* @param fileName the file name that should be absent
*/
public void assertNoFile(String fileName) {
assertThat(getFileItemLocator(fileName)).isHidden();
}

/**
* Assert that a file row is marked complete.
*
* @param fileName the file name to check
*/
public void assertFileComplete(String fileName) {
assertThat(getFileItemLocator(fileName)).hasAttribute("complete", "");
}

public void assertMaxFilesReached() {
assertThat(getLocator()).hasAttribute("max-files-reached", "");
}

/**
* Get the {@code UploadElement} by the accessible text of its upload button.
*
* @param page the Playwright page
* @param buttonText the accessible text of the upload button (ARIA role {@code button})
* @return the matching {@code UploadElement}
*/
public static UploadElement getByButtonText(Page page, String buttonText) {
return new UploadElement(
page.locator(FIELD_TAG_NAME)
.filter(new Locator.FilterOptions()
.setHas(page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName(buttonText))))
.first());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package org.vaadin.addons.dramafinder.tests.it;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.vaadin.addons.dramafinder.element.UploadElement;

import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class UploadViewIT extends SpringPlaywrightIT {

@TempDir
Path tempDir;

@Override
public String getView() {
return "upload";
}

@Test
public void testSingleFileUploadCompletes() throws IOException {
Path file = Files.writeString(tempDir.resolve("single.txt"), "single");

UploadElement upload = UploadElement.getByButtonText(page, "Select single file");
upload.uploadFiles(file);

upload.assertHasFile("single.txt");
upload.assertFileComplete("single.txt");
assertThat(page.locator("#single-upload-status")).hasText("single.txt");
}

@Test
public void testMultiFileUploadAndClear() throws IOException {
Path first = Files.writeString(tempDir.resolve("first.txt"), "first");
Path second = Files.writeString(tempDir.resolve("second.txt"), "second");
Path third = Files.writeString(tempDir.resolve("third.txt"), "third");

UploadElement upload = UploadElement.getByButtonText(page, "Select multiple files");
upload.uploadFiles(first, second);

upload.assertHasFile("first.txt");
upload.assertHasFile("second.txt");
upload.assertFileComplete("first.txt");
upload.assertFileComplete("second.txt");

upload.uploadFiles(third);
upload.assertHasFile("third.txt");
upload.assertFileComplete("third.txt");
upload.assertMaxFilesReached();

upload.removeFile("first.txt");
upload.assertNoFile("first.txt");

upload.removeFile("second.txt");
upload.assertNoFile("second.txt");

upload.removeFile("third.txt");
upload.assertNoFile("third.txt");

assertThat(page.locator("#multi-upload-status")).hasText("Uploaded third.txt");
}

@Test
public void textFileRejected() throws IOException {
Path first = Files.writeString(tempDir.resolve("first.forbidden"), "first");

UploadElement upload = UploadElement.getByButtonText(page, "Select multiple files");
upload.uploadFiles(first);

upload.assertNoFile("first.forbidden");

assertThat(page.locator("#multi-upload-status")).hasText("Rejected: Incorrect File Type.");
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.vaadin.addons.dramafinder.tests.testuis;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.upload.Upload;
import com.vaadin.flow.component.upload.UploadI18N;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.streams.UploadHandler;

@PageTitle("Upload Demo")
@Route(value = "upload", layout = MainLayout.class)
public class UploadView extends Main {

public UploadView() {
createSingleUpload();
createMultiUpload();
}

private void createSingleUpload() {
Span status = new Span("Waiting for file");
status.setId("single-upload-status");
Upload upload = new Upload(
UploadHandler.inMemory((metadata, data) -> {
// No-op handler, demo asserts component behavior only.
})
.whenComplete((result, error) -> status.setText(result.fileName())));
upload.setMaxFiles(1);
upload.setDropLabel(new Span("Drop a single file"));
upload.setUploadButton(new Button("Select single file"));
upload.addFileRejectedListener(event -> status.setText("Rejected: " + event.getErrorMessage()));

addExample("Single upload", new Div(upload, status));
}

private void createMultiUpload() {
Span status = new Span("No uploads yet");
status.setId("multi-upload-status");
Upload upload = new Upload(
UploadHandler.inMemory((metadata, data) -> {
// No-op handler, demo asserts component behavior only.
})
.whenComplete((result, error) -> status.setText("Uploaded " + result.fileName())));
upload.setMaxFiles(3);
upload.setMaxFileSize(1000000);
upload.setDropLabel(new Span("Drop multiple files"));
upload.setUploadButton(new Button("Select multiple files"));
upload.setAcceptedFileTypes("text/plain");

upload.setI18n(new UploadI18N().setError(new UploadI18N.Error().setFileIsTooBig("File is too big")));
upload.addFileRejectedListener(event -> status.setText("Rejected: " + event.getErrorMessage()));

Button clear = new Button("Clear uploads", event -> upload.clearFileList());

addExample("Multi upload", new Div(upload, clear, status));
}

private void addExample(String title, Component component) {
add(new H2(title), component);
}
}
Loading