diff --git a/src/org/labkey/test/WebDriverWrapper.java b/src/org/labkey/test/WebDriverWrapper.java index b742bde15c..4ea14429f2 100644 --- a/src/org/labkey/test/WebDriverWrapper.java +++ b/src/org/labkey/test/WebDriverWrapper.java @@ -3086,6 +3086,35 @@ public void dragAndDrop(WebElement fromEl, int xOffset, int yOffset) builder.clickAndHold(fromEl).moveByOffset(xOffset + 1, yOffset + 1).release().build().perform(); } + /** + * Reorder a drag-and-drop list with the keyboard instead of the mouse: focus the drag handle, Space to lift, + * one Arrow per step, Space to drop. Required for lists built on '@hello-pangea/dnd' (auth configurations, + * domain designer, field selection). + * + * @param dragHandle Element carrying the library's drag handle props; must be focusable. + * @param steps Positions to move; negative moves up, positive moves down. + */ + public void keyboardDragAndDrop(WebElement dragHandle, int steps) + { + dragHandle.sendKeys(Keys.SPACE); + + Actions drag = new Actions(getDriver()).pause(Duration.ofMillis(400)); + Keys arrow = steps < 0 ? Keys.ARROW_UP : Keys.ARROW_DOWN; + for (int i = 0; i < Math.abs(steps); i++) + drag.sendKeys(arrow).pause(Duration.ofMillis(300)); + drag.sendKeys(Keys.SPACE).perform(); + } + + /** + * @see #keyboardDragAndDrop(WebElement, int) + * @param dragHandle Drag handle of the element to move. + * @param target Drag handle of a sibling element to move it to. + */ + public void keyboardDragAndDrop(WebElement dragHandle, WebElement target) + { + keyboardDragAndDrop(dragHandle, getElementIndex(target) - getElementIndex(dragHandle)); + } + // This is useful when making a draggin selection in a plot, and there may be many elements ontop of the one you want. public void dragAndDrop(int xOffset, int yOffset) { diff --git a/src/org/labkey/test/components/ui/grids/FieldSelectionDialog.java b/src/org/labkey/test/components/ui/grids/FieldSelectionDialog.java index 73b5c143e6..a7b4cf5eca 100644 --- a/src/org/labkey/test/components/ui/grids/FieldSelectionDialog.java +++ b/src/org/labkey/test/components/ui/grids/FieldSelectionDialog.java @@ -522,49 +522,48 @@ private List getSelectedListItems(String fieldLabel) */ public FieldSelectionDialog repositionField(FieldKey fieldToMove, FieldKey targetField, boolean beforeTarget) { - WebElement elementToMove = elementCache().findSelectedField(fieldToMove); - WebElement elementTarget = elementCache().findSelectedField(targetField); + List order = selectedFieldKeys(); + int from = indexOfFieldKey(order, fieldToMove); + int target = indexOfFieldKey(order, targetField); + int to = beforeTarget ? (from < target ? target - 1 : target) : (from < target ? target : target + 1); - int yBefore = elementToMove.getRect().getY(); + getWrapper().keyboardDragAndDrop(elementCache().findDragHandle(fieldToMove), to - from); - int offset; + WebDriverWrapper.waitFor(() -> { + List now = selectedFieldKeys(); + return indexOfFieldKey(now, fieldToMove) - indexOfFieldKey(now, targetField) == (beforeTarget ? -1 : 1); + }, "Field '" + fieldToMove + "' was not repositioned as expected", 5_000); - if(beforeTarget) - { - if(elementTarget.getRect().getY() < elementToMove.getRect().getY()) - { - // If the target is above the field being moved. - offset = -1 * elementTarget.getSize().getHeight(); - } - else - { - // If the target is below the field being moved. - offset = -1 * elementTarget.getSize().getHeight() / 2; - } - } - else - { - offset = elementTarget.getSize().getHeight() / 2 + 10; - } - - WebElement dragHandle = Locator.tagWithAttribute("div", "role", "button").findWhenNeeded(elementToMove); - getWrapper().mouseOver(dragHandle); - new Actions(getDriver()) - .clickAndHold(dragHandle) - .moveToElement(elementTarget) - .moveByOffset(2, offset) - .release() - .perform(); - - // Maybe I don't need to wait? - WebDriverWrapper.sleep(1_000); - - int yAfter = elementToMove.getRect().getY(); + return this; + } - WebDriverWrapper.waitFor(()-> yAfter != yBefore, "I don't think I repositioned the field in the list.", - 1_000); + /** + * Get the 'data-fieldkey' values of the selected fields, in display order. + * + * @return The encoded field keys. + */ + private List selectedFieldKeys() + { + return elementCache().getListItemElements(elementCache().selectedFieldsPanel).stream() + .map(el -> el.getDomAttribute("data-fieldkey")) + .collect(Collectors.toList()); + } - return this; + /** + * Get the index of a field in the given list, matched case-insensitively (as findFieldRow locates rows). + * + * @param fieldKeys Encoded field keys, in display order. + * @param fieldKey Field to locate. + * @return The index, or -1 if not present. + */ + private static int indexOfFieldKey(List fieldKeys, FieldKey fieldKey) + { + for (int i = 0; i < fieldKeys.size(); i++) + { + if (fieldKey.toString().equalsIgnoreCase(fieldKeys.get(i))) + return i; + } + return -1; } /** @@ -662,6 +661,11 @@ protected WebElement findSelectedField(FieldKey fieldKey) return findFieldRow(fieldKey, selectedFieldsPanel); } + protected WebElement findDragHandle(FieldKey fieldKey) + { + return Locator.tagWithAttribute("div", "role", "button").findElement(findSelectedField(fieldKey)); + } + protected WebElement findAvailableField(FieldKey fieldKey) { return findFieldRow(fieldKey, availableFieldsPanel); diff --git a/src/org/labkey/test/pages/core/login/LoginConfigRow.java b/src/org/labkey/test/pages/core/login/LoginConfigRow.java index 2f840381e8..42059113e0 100644 --- a/src/org/labkey/test/pages/core/login/LoginConfigRow.java +++ b/src/org/labkey/test/pages/core/login/LoginConfigRow.java @@ -72,6 +72,11 @@ public boolean canEdit() return elementCache().editButtonLoc.existsIn(this); } + public WebElement getDragHandle() + { + return elementCache().dragHandle; + } + @Override public WebElement getComponentElement() { @@ -93,6 +98,9 @@ protected ElementCache newElementCache() protected class ElementCache extends Component.ElementCache { + final WebElement dragHandle = Locator.xpath("ancestor::div") + .withAttribute("data-rfd-drag-handle-draggable-id").findWhenNeeded(this); + final WebElement baseFieldsElement = Locator.tagWithClass("div", "domain-row-base-fields").findWhenNeeded(this); final WebElement description = Locator.tagWithClass("div", "description").findWhenNeeded(baseFieldsElement); final WebElement details = Locator.tagWithClass("div", "details").findWhenNeeded(baseFieldsElement);