Skip to content
5 changes: 5 additions & 0 deletions src/org/labkey/test/Locator.java
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,11 @@ public static XPathLocator name(String name)
return tag("*").withAttribute("name", name);
}

public static XPathLocator nameContaining(String name)
{
return tag("*").withAttributeContaining("name", name);
}

public static CssLocator css(String selector)
{
return new CssLocator(selector);
Expand Down
7 changes: 7 additions & 0 deletions src/org/labkey/test/WebDriverWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -4059,6 +4059,13 @@ public void selectOptionByText(Locator locator, String text)

public void selectOptionByText(WebElement selectElement, String value)
{
if(Boolean.parseBoolean(selectElement.getAttribute("multiple"))) {
List<WebElement> elems = selectElement.findElements(Locator.tag("option"));
elems.forEach(element->{
if(value.contains(element.getAttribute("value")) ^ element.isSelected()) element.click();
});
return;
}
Select select = new Select(selectElement);
select.selectByVisibleText(value);
}
Expand Down
9 changes: 9 additions & 0 deletions src/org/labkey/test/components/domain/DomainFieldRow.java
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,11 @@ public DomainFieldRow clickRemoveOntologyConcept()
// behind the scenes. Because of that the validator aspect of the TextChoice field is hidden from the user (just like
// it is in the product).

public void setAllowMultipleSelections(Boolean allowMultipleSelections)
{
elementCache().allowMultipleSelectionsCheckbox.set(allowMultipleSelections);
}

/**
* Set the list of allowed values for a TextChoice field.
*
Expand Down Expand Up @@ -1702,6 +1707,10 @@ protected class ElementCache extends WebDriverComponent.ElementCache
public final WebElement domainWarningIcon = Locator.tagWithClass("span", "domain-warning-icon")
.findWhenNeeded(this);

// text choice field option
public final Checkbox allowMultipleSelectionsCheckbox = new Checkbox(Locator.tagWithClass("input", "domain-text-choice-multi")
.refindWhenNeeded(this).withTimeout(WAIT_FOR_JAVASCRIPT));

// lookup field options
public final Select lookupContainerSelect = SelectWrapper.Select(Locator.name("domainpropertiesrow-lookupContainer"))
.findWhenNeeded(this);
Expand Down
4 changes: 4 additions & 0 deletions src/org/labkey/test/components/domain/DomainFormPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,10 @@ else if (validator instanceof FieldDefinition.TextChoiceValidator textChoiceVali
throw new IllegalArgumentException("TextChoice fields cannot have additional validators.");
}
fieldRow.setTextChoiceValues(textChoiceValidator.getValues());
if(textChoiceValidator.getMultipleSelections())
{
fieldRow.setAllowMultipleSelections(textChoiceValidator.getMultipleSelections());
}
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,19 @@ public EntityBulkUpdateDialog setSelectionField(CharSequence fieldIdentifier, Li
return this;
}

/**
* Clear the field (fieldIdentifier).
*
* @param fieldIdentifier Identifier for the field; name ({@link String}) or fieldKey ({@link FieldKey})
* @return this component
*/
public EntityBulkUpdateDialog clearSelection(CharSequence fieldIdentifier)
{
FilteringReactSelect reactSelect = enableSelectionField(fieldIdentifier);
reactSelect.clearSelection();
return this;
}

/**
* @param fieldIdentifier Identifier for the field; name ({@link String}) or fieldKey ({@link FieldKey})
* @param selectValue value to select
Expand Down
3 changes: 3 additions & 0 deletions src/org/labkey/test/components/ui/grids/EditableGrid.java
Original file line number Diff line number Diff line change
Expand Up @@ -507,11 +507,14 @@ public WebElement setCellValue(int row, CharSequence columnIdentifier, Object va

if (value instanceof List)
{

// If this is a list assume that it will need a lookup.
List<String> values = (List) value;

ReactSelect lookupSelect = elementCache().lookupSelect(gridCell);

lookupSelect.clearSelection();

lookupSelect.open();

for (String _value : values)
Expand Down
11 changes: 10 additions & 1 deletion src/org/labkey/test/components/ui/grids/ResponsiveGrid.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.labkey.remoteapi.query.Filter.Operator.CONTAINS_ALL;
import static org.labkey.remoteapi.query.Filter.Operator.CONTAINS_ANY;
import static org.labkey.remoteapi.query.Filter.Operator.CONTAINS_EXACTLY;
import static org.labkey.remoteapi.query.Filter.Operator.CONTAINS_NONE;
import static org.labkey.remoteapi.query.Filter.Operator.DOES_NOT_CONTAIN_EXACTLY;
import static org.labkey.remoteapi.query.Filter.Operator.IN;
import static org.labkey.test.WebDriverWrapper.waitFor;

public class ResponsiveGrid<T extends ResponsiveGrid<?>> extends WebDriverComponent<ResponsiveGrid<T>.ElementCache> implements UpdatingComponent
Expand Down Expand Up @@ -234,15 +240,18 @@ public String filterColumnExpectingError(CharSequence columnIdentifier, Filter.O

private GridFilterModal initFilterColumn(CharSequence columnIdentifier, Filter.Operator operator, Object value)
{
List<Filter.Operator> listOperators = List.of(IN, CONTAINS_ALL, CONTAINS_ANY, CONTAINS_EXACTLY, CONTAINS_NONE,
DOES_NOT_CONTAIN_EXACTLY);
clickColumnMenuItem(columnIdentifier, "Filter...", false);
GridFilterModal filterModal = new GridFilterModal(getDriver(), this);
if (operator != null)
{
if (operator.equals(Filter.Operator.IN) && value instanceof List<?>)
if (listOperators.contains(operator) && value instanceof List<?>)
{
List<String> values = (List<String>) value;
filterModal.selectFacetTab().selectValue(values.get(0));
filterModal.selectFacetTab().checkValues(values.toArray(String[]::new));
filterModal.selectFacetTab().selectFilter(operator.getDisplayValue());
}
else
filterModal.selectExpressionTab().setFilter(new FilterExpressionPanel.Expression(operator, value));
Expand Down
12 changes: 12 additions & 0 deletions src/org/labkey/test/components/ui/search/FilterFacetedPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.labkey.test.components.WebDriverComponent;
import org.labkey.test.components.html.Checkbox;
import org.labkey.test.components.html.Input;
import org.labkey.test.components.react.ReactSelect;
import org.labkey.test.components.ui.FilterStatusValue;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
Expand Down Expand Up @@ -48,6 +49,15 @@ public void selectValue(String value)
elementCache().findCheckboxLabel(value).click();
}

/**
* Select a single facet value by clicking its label. Should replace all existing selections.
* @param value desired value
*/
public void selectFilter(String value)
{
elementCache().filterTypeSelects.select(value);
}

/**
* Check single facet value by label to see if it is checked or not.
* @param value desired value
Expand Down Expand Up @@ -123,6 +133,8 @@ protected class ElementCache extends Component<?>.ElementCache
{
protected final Input filterInput =
Input(Locator.id("filter-faceted__typeahead-input"), getDriver()).findWhenNeeded(this);
protected final ReactSelect filterTypeSelects =
new ReactSelect.ReactSelectFinder(getDriver()).index(0).findWhenNeeded(this);
protected final WebElement checkboxSection =
Locator.byClass("labkey-wizard-pills").index(0).refindWhenNeeded(this);
protected final Locator.XPathLocator checkboxLabelLoc
Expand Down
2 changes: 1 addition & 1 deletion src/org/labkey/test/pages/DatasetInsertPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ private void tryInsert(Map<String, String> values)
{
for (Map.Entry<String, String> entry : values.entrySet())
{
WebElement fieldInput = Locator.name(EscapeUtil.getFormFieldName(entry.getKey())).findElement(getDriver());
WebElement fieldInput = Locator.nameContaining(EscapeUtil.getFormFieldName(entry.getKey())).findElement(getDriver());
String type = fieldInput.getAttribute("type");
switch (type)
{
Expand Down
5 changes: 2 additions & 3 deletions src/org/labkey/test/pages/query/UpdateQueryRowPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import org.labkey.test.util.EscapeUtil;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -99,7 +98,7 @@ public UpdateQueryRowPage setField(String fieldName, String value)
WebElement field = elementCache().findField(fieldName);
if (field.getTagName().equals("select"))
{
setField(fieldName, OptionSelect.SelectOption.textOption(value));
selectOptionByText(field, value);
}
else
{
Expand Down Expand Up @@ -186,7 +185,7 @@ WebElement findField(String name)
{
if (!fieldMap.containsKey(name))
{
fieldMap.put(name, Locator.name(EscapeUtil.getFormFieldName(name)).findElement(this));
fieldMap.put(name, Locator.nameContaining(EscapeUtil.getFormFieldName(name)).findElement(this));
}
return fieldMap.get(name);
}
Expand Down
20 changes: 20 additions & 0 deletions src/org/labkey/test/params/FieldDefinition.java
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,13 @@ public FieldDefinition setTextChoiceValues(List<String> values)
return this;
}

public FieldDefinition setMultiChoiceValues(List<String> values)
{
Assert.assertEquals("Invalid field type for text choice values.", ColumnType.TextChoice, getType());
setValidators(List.of(new FieldDefinition.TextChoiceValidator(values).setMultipleSelections()));
return this;
}

public ExpSchema.DerivationDataScopeType getAliquotOption()
{
return _aliquotOption;
Expand Down Expand Up @@ -1112,6 +1119,8 @@ public static class TextChoiceValidator extends FieldValidator<TextChoiceValidat
{
private final List<String> _values;

private Boolean multipleSelections = false;

public TextChoiceValidator(List<String> values)
{
// The TextChoice validator only has a name and no description or message.
Expand Down Expand Up @@ -1143,6 +1152,17 @@ public List<String> getValues()
return _values;
}

public TextChoiceValidator setMultipleSelections()
{
this.multipleSelections = true;
return this;
}

public Boolean getMultipleSelections()
{
return this.multipleSelections;
}

}

}
Expand Down
37 changes: 37 additions & 0 deletions src/org/labkey/test/tests/list/ListTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
Expand Down Expand Up @@ -1688,6 +1689,42 @@ public void testAutoIncrementKeyEncoded()
_listHelper.deleteList();
}

@Test
public void testMultiChoiceValues()
{
// setup a list with an auto-increment key and multi text choice field
String encodedListName = "multiChoiceList";
String keyName = "'><script>alert(\":(\")</script>'";
String columnName = "MultiChoiceField";
List<String> tcValues = List.of("~`!@#$%^&*()_+=[]{}\\|';:\"<>?,./", "1", "2");
_listHelper.createList(PROJECT_VERIFY, encodedListName, keyName, col(columnName, ColumnType.TextChoice)
.setMultiChoiceValues(tcValues));
_listHelper.goToList(encodedListName);

DataRegionTable table = new DataRegionTable("query", getDriver());
table.clickInsertNewRow();
String valuesToChoose = tcValues.subList(1, 3).stream()
.sorted()
.collect(Collectors.joining(" "));
Locator loc = Locator.nameContaining(EscapeUtil.getFormFieldName(columnName));
selectOptionByText(loc, valuesToChoose);

clickButton("Submit");
checker().verifyEquals("Multi choice value not as expected", valuesToChoose, table.getDataAsText(0, columnName));

table.clickEditRow(0);
valuesToChoose = tcValues.subList(1, 3).stream()
.sorted()
.collect(Collectors.joining(" "));
selectOptionByText(loc, valuesToChoose);
clickButton("Submit");

// verify the multi choice value is persisted
checker().verifyEquals("Multi choice value not as expected", valuesToChoose, table.getDataAsText(0, columnName));

_listHelper.deleteList();
}

private List<String> getQueryFormFieldNames()
{
return Locator.tag("input").attributeStartsWith("name", "quf_")
Expand Down
7 changes: 7 additions & 0 deletions src/org/labkey/test/util/TestDataGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Function;
Expand Down Expand Up @@ -956,6 +957,12 @@ public ImportDataResponse importRows(Connection cn, List<Map<String, Object>> ro
return getQueryHelper(cn).importData(TestDataUtils.stringFromRows(TestDataUtils.rowListsFromMaps(rows)), lookupByAlternateKey);
}

public static <T> List<T> shuffleSelect(List<T> allFields)
{
int randomSize = new Random().nextInt(allFields.size()) + 1;
return shuffleSelect(allFields, randomSize);
}

public static <T> List<T> shuffleSelect(List<T> allFields, int selectCount)
{
List<T> shuffled = new ArrayList<>(allFields);
Expand Down