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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.vaadin.flow.component.contextmenu;

import java.util.ArrayList;
import java.util.List;

import org.junit.jupiter.api.Assertions;
Expand Down Expand Up @@ -51,6 +52,19 @@ void openCloseMenu_menuIsAttachedAndDetached() {
"context menu should be detached from the UI, but was not");
}

@Test
void openCloseMenu_openedChangeEventsComeFromClient() {
List<Boolean> fromClient = new ArrayList<>();
view.menu.addOpenedChangeListener(
event -> fromClient.add(event.isFromClient()));

test(view.menu).open();
test(view.menu).close();

Assertions.assertEquals(List.of(true, true), fromClient,
"opening and closing the menu should be reported as user actions");
}

@Test
void programmaticallyClose_menuIsDetached() {
test(view.menu).open();
Expand All @@ -70,6 +84,23 @@ void openMenu_alreadyOpen_throws() {
Assertions.assertTrue(exception.getMessage().contains("already open"));
}

@Test
void openMenu_notUsable_throws() {
view.menu.setVisible(false);

IllegalStateException exception = Assertions.assertThrows(
IllegalStateException.class, test(view.menu)::open);
Assertions.assertTrue(exception.getMessage().contains("is not usable"));

Assertions.assertFalse(view.menu.isOpened(),
"a refused open should leave the menu closed");
Assertions.assertFalse(view.menu.isAttached(),
"a refused open should leave the menu detached from the UI");
Assertions.assertEquals(0,
find(Div.class).withText("Component Item").all().size(),
"a refused open should not leave the menu content reachable through a top level find()");
}

@Test
void closeMenu_menuNotOpened_throws() {
Assertions.assertThrows(IllegalStateException.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,22 @@ void clickItem_disabledItem_throws() {
Assertions.assertTrue(exception.getMessage().contains("not usable"));
}

@Test
void openOnRow_menuNotUsable_throws() {
view.menu.setVisible(false);

IllegalStateException exception = Assertions.assertThrows(
IllegalStateException.class, () -> test(view.menu).open(0));
Assertions.assertTrue(exception.getMessage().contains("is not usable"));

Assertions.assertFalse(view.menu.isOpened(),
"a refused open should leave the menu closed");
Assertions.assertFalse(view.menu.isAttached(),
"a refused open should leave the menu detached from the UI");
Assertions.assertEquals(0, find(Checkbox.class).all().size(),
"a refused open should not leave the menu content reachable through a top level find()");
}

@Test
void clickItem_menuNotOpened_throws() {
Assertions.assertThrows(IllegalStateException.class,
Expand Down
23 changes: 23 additions & 0 deletions shared/src/main/java/com/vaadin/browserless/ComponentTester.java
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,29 @@ public final void ensureComponentIsUsable() {
this::notUsableReasons);
}

/**
* Checks that the wrapped component is usable and, if it is not, detaches
* it from the UI before rethrowing.
* <p>
* For an overlay the tester has to attach to the UI before it can tell
* whether it is usable, such as a context menu opened by a before-open
* event. Detaching it again keeps a refused interaction from leaving a
* closed overlay behind in the UI tree, where a top level {@code find(...)}
* would still reach its content.
*
* @throws IllegalStateException
* if the component is not usable, with details on its current
* state.
*/
protected void ensureComponentIsUsableOrDetach() {
try {
ensureComponentIsUsable();
} catch (RuntimeException e) {
getComponent().getElement().removeFromParent();
throw e;
}
}

/**
* Throws an {@link IllegalStateException} with details on the current state
* of the component if it is not usable according to the provided test.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,18 @@ public void open() {
}
attachMenuToUI();
roundTrip();
getComponent().getElement().setProperty("opened", true);
ensureComponentIsUsable();
ensureComponentIsUsableOrDetach();
// Simulate the overlay reporting itself as opened so that the
// resulting OpenedChangeEvent is seen as a user action.
setPropertyAsUser("opened", true);
}

/**
* Closes the context menu.
*/
public void close() {
ensureComponentIsUsable();
getComponent().getElement().setProperty("opened", false);
setPropertyAsUser("opened", false);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,11 @@ public void open(int row, String columnKey) {
throw new IllegalStateException(
"Context menu did not open. Its dynamic content handler returned false for the target row.");
}
ensureComponentIsUsableOrDetach();
// opened is a synchronized property, so pushing it through the
// client path makes the GridContextMenuOpenedEvent report
// isFromClient() as true, the way a real open does
setPropertyAsUser("opened", true);
ensureComponentIsUsable();
}

/**
Expand Down
Loading