diff --git a/flow-tests/test-root-context/src/main/java/com/vaadin/flow/uitest/ui/ModalDialogView.java b/flow-tests/test-root-context/src/main/java/com/vaadin/flow/uitest/ui/ModalDialogView.java new file mode 100644 index 00000000000..aee3025d915 --- /dev/null +++ b/flow-tests/test-root-context/src/main/java/com/vaadin/flow/uitest/ui/ModalDialogView.java @@ -0,0 +1,140 @@ +/* + * Copyright 2000-2020 Vaadin Ltd. + * + * 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. + */ + +package com.vaadin.flow.uitest.ui; + +import com.vaadin.flow.component.ClickEvent; +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Key; +import com.vaadin.flow.component.Text; +import com.vaadin.flow.component.UI; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.html.Input; +import com.vaadin.flow.component.html.NativeButton; +import com.vaadin.flow.router.Route; + +@Route(value = "com.vaadin.flow.uitest.ui.ModalDialogView") +public class ModalDialogView extends Div { + + public static final String EVENT_LOG = "event-log"; + public static final String UI_BUTTON = "ui-button"; + public static final String OPEN_MODELESS_BUTTON = "modeless-dialog-button"; + public static final String OPEN_MODAL_BUTTON = "modal-dialog-button"; + public static final String DIALOG_BUTTON = "dialog-button"; + public static final String DIALOG_CLOSE_BUTTON = "dialog-close-button"; + public static final String DIALOG = "DIALOG"; + public static final String LISTEN_ON_UI_BUTTON = "listen-on-ui-button"; + public static final String LISTEN_ON_DIALOG_BUTTON = "listen-on-dialog-button"; + public static final Key SHORTCUT_KEY = Key.KEY_X; + private int eventCounter; + private final Div eventLog; + + public ModalDialogView() { + eventLog = new Div(new Text("Click events and their sources:")); + eventLog.setId(EVENT_LOG); + + final NativeButton testButton = createButton("Key-X shortcut", + this::logClickEvent); + testButton.setId(UI_BUTTON); + testButton.addClickShortcut(SHORTCUT_KEY); + + add(createOpenDialogButton(true, OPEN_MODAL_BUTTON), + createOpenDialogButton(false, OPEN_MODELESS_BUTTON), testButton, + eventLog); + setId("main-div"); + } + + private void logClickEvent(ClickEvent event) { + eventLog.addComponentAsFirst(new Div(new Text((eventCounter++) + "-" + + event.getSource().getId().orElse("NO-SOURCE-ID")))); + } + + private Component createOpenDialogButton(boolean modal, String id) { + final NativeButton button = createButton( + "Open " + (modal ? "modal" : "non-modal") + " dialog", + event -> { + final Dialog dialog = new Dialog(modal); + dialog.open(); + }); + button.setId(id); + return button; + } + + private NativeButton createButton(String caption, + ComponentEventListener> listener) { + final NativeButton button = new NativeButton(); + button.setText(caption); + button.addClickListener(listener); + button.getStyle().set("border", "1px solid black"); + button.setWidth("100px"); + return button; + } + + public class Dialog extends Div { + private boolean modal; + + public Dialog(boolean modal) { + this.modal = modal; + + final NativeButton testButton = createButton( + "Test button with enter shortcut", + ModalDialogView.this::logClickEvent); + testButton.setId(DIALOG_BUTTON); + final NativeButton uiScopeShortcutButton = new NativeButton( + "Add shortcut with listenOn(UI)", event -> { + testButton.addClickShortcut(SHORTCUT_KEY); + event.getSource().setEnabled(false); + }); + uiScopeShortcutButton.setId(LISTEN_ON_UI_BUTTON); + final NativeButton dialogScopeShortcutButton = new NativeButton( + "Add shortcut with listenOn(Dialog)", event -> { + testButton.addClickShortcut(SHORTCUT_KEY) + .listenOn(Dialog.this); + event.getSource().setEnabled(false); + }); + dialogScopeShortcutButton.setId(LISTEN_ON_DIALOG_BUTTON); + + final Component closeButton = createButton("Close", + event -> close()); + closeButton.setId(DIALOG_CLOSE_BUTTON); + add(new Text("A " + (modal ? "modal" : "modeless") + " dialog"), + new Input(), new Div(), closeButton, uiScopeShortcutButton, + dialogScopeShortcutButton, new Div(), testButton); + + getUI().ifPresent(ui -> { + ui.setChildComponentModal(this, modal); + }); + getStyle().set("position", "fixed").set("inset", "50% 50%") + .set("border", "1px solid black"); + setId(DIALOG); + } + + public void open() { + final UI ui = ModalDialogView.this.getUI().get(); + if (modal) { + ui.addModal(this); + } else { + ui.add(this); + } + } + + public void close() { + final UI ui = ModalDialogView.this.getUI().get(); + ui.remove(this); + } + } +} diff --git a/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/ModalDialogIT.java b/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/ModalDialogIT.java new file mode 100644 index 00000000000..a186aa61370 --- /dev/null +++ b/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/ModalDialogIT.java @@ -0,0 +1,152 @@ +package com.vaadin.flow.uitest.ui; + +import com.vaadin.flow.component.html.testbench.DivElement; +import com.vaadin.flow.component.html.testbench.NativeButtonElement; +import com.vaadin.flow.testutil.ChromeBrowserTest; +import com.vaadin.testbench.TestBenchElement; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +public class ModalDialogIT extends ChromeBrowserTest { + + private TestBenchElement eventLog; + private TestBenchElement modalDialogButton; + private TestBenchElement modelessDialogButton; + + @Before + public void init() { + open(); + eventLog = $(DivElement.class).id(ModalDialogView.EVENT_LOG); + modalDialogButton = $(NativeButtonElement.class) + .id(ModalDialogView.OPEN_MODAL_BUTTON); + modelessDialogButton = $(NativeButtonElement.class) + .id(ModalDialogView.OPEN_MODELESS_BUTTON); + } + + // #7799 + @Test + public void modalDialogOpened_sameShortcutsListeningOnUi_noShortcutTriggered() { + pressShortcutKey( + $(NativeButtonElement.class).id(ModalDialogView.UI_BUTTON)); + validateLatestShortcutEvent(0, ModalDialogView.UI_BUTTON); + + openDialog(modalDialogButton); + pressShortcutKey(getDialogInput()); + // no event occurred + validateLatestShortcutEvent(0, ModalDialogView.UI_BUTTON); + listenToButtonShortcutOnUI(); + pressShortcutKey(getDialogInput()); + // no event occurred since shortcut is listened on ui which is inept + validateLatestShortcutEvent(0, ModalDialogView.UI_BUTTON); + + closeDialog(); + pressShortcutKey( + $(NativeButtonElement.class).id(ModalDialogView.UI_BUTTON)); + validateLatestShortcutEvent(1, ModalDialogView.UI_BUTTON); + } + + @Test + public void modalDialogOpened_sameShortcutListeningOnUiAndDialog_onlyDialogShortcutExecuted() { + pressShortcutKey( + $(NativeButtonElement.class).id(ModalDialogView.UI_BUTTON)); + validateLatestShortcutEvent(0, ModalDialogView.UI_BUTTON); + + openDialog(modalDialogButton); + pressShortcutKey(getDialogInput()); + // no event occurred + validateLatestShortcutEvent(0, ModalDialogView.UI_BUTTON); + + listenToButtonShortcutOnDialog(); + pressShortcutKey(getDialogInput()); + validateLatestShortcutEvent(1, ModalDialogView.DIALOG_BUTTON); + + closeDialog(); + pressShortcutKey( + $(NativeButtonElement.class).id(ModalDialogView.UI_BUTTON)); + validateLatestShortcutEvent(2, ModalDialogView.UI_BUTTON); + } + + @Test + public void modelessDialogOpened_sharesShortcutWithUI_bothExecuted() { + pressShortcutKey( + $(NativeButtonElement.class).id(ModalDialogView.UI_BUTTON)); + validateLatestShortcutEvent(0, ModalDialogView.UI_BUTTON); + + openDialog(modelessDialogButton); + listenToButtonShortcutOnUI(); + pressShortcutKey(getDialogInput()); + + validateShortcutEvent(1, 1, ModalDialogView.UI_BUTTON); + validateLatestShortcutEvent(2, ModalDialogView.DIALOG_BUTTON); + + closeDialog(); + pressShortcutKey( + $(NativeButtonElement.class).id(ModalDialogView.UI_BUTTON)); + validateLatestShortcutEvent(3, ModalDialogView.UI_BUTTON); + } + + @Test + public void modelessDialogOpened_sameShortcutListeningOnUiAndDialog_bothExecuted() { + pressShortcutKey( + $(NativeButtonElement.class).id(ModalDialogView.UI_BUTTON)); + validateLatestShortcutEvent(0, ModalDialogView.UI_BUTTON); + + openDialog(modelessDialogButton); + listenToButtonShortcutOnDialog(); + pressShortcutKey(getDialogInput()); + + validateLatestShortcutEvent(1, ModalDialogView.DIALOG_BUTTON); + pressShortcutKey(getDialogInput()); + validateLatestShortcutEvent(2, ModalDialogView.DIALOG_BUTTON); + + closeDialog(); + pressShortcutKey( + $(NativeButtonElement.class).id(ModalDialogView.UI_BUTTON)); + validateLatestShortcutEvent(3, ModalDialogView.UI_BUTTON); + } + + private void openDialog(WebElement modalDialogButton) { + modalDialogButton.click(); + } + + private void closeDialog() { + $(NativeButtonElement.class).id(ModalDialogView.DIALOG_CLOSE_BUTTON) + .click(); + } + + private void validateLatestShortcutEvent(int eventCounter, + String eventSourceId) { + validateShortcutEvent(0, eventCounter, eventSourceId); + } + + private void validateShortcutEvent(int indexFromTop, int eventCounter, + String eventSourceId) { + final WebElement latestEvent = eventLog.findElements(By.tagName("div")) + .get(indexFromTop); + Assert.assertEquals("Invalid latest event", + eventCounter + "-" + eventSourceId, latestEvent.getText()); + } + + private void pressShortcutKey(TestBenchElement elementToFocus) { + elementToFocus.focus(); + elementToFocus.sendKeys("x"); + } + + private TestBenchElement getDialogInput() { + return $(DivElement.class).id(ModalDialogView.DIALOG).$("input") + .first(); + } + + private void listenToButtonShortcutOnUI() { + $(NativeButtonElement.class).id(ModalDialogView.LISTEN_ON_UI_BUTTON) + .click(); + } + + private void listenToButtonShortcutOnDialog() { + $(NativeButtonElement.class).id(ModalDialogView.LISTEN_ON_DIALOG_BUTTON) + .click(); + } +}