Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
333c490
initial commit for multichoice value
DariaBod Jan 23, 2026
fabe096
some changes
DariaBod Jan 26, 2026
349d08d
add tests for:
DariaBod Jan 28, 2026
0ab67be
Merge branch 'develop' into fb_mvtc_test
DariaBod Jan 28, 2026
fd9d700
new changes for test
DariaBod Jan 29, 2026
bf42d09
Merge branch 'develop' into fb_mvtc_test
DariaBod Jan 29, 2026
1745585
Merge branch 'develop' into fb_mvtc_test
DariaBod Jan 30, 2026
f349d82
add test testMultiChoiceUpdateFromFile(), testMultiChoiceEditInGridDr…
DariaBod Feb 3, 2026
92000dd
Merge branch 'develop' into fb_mvtc_test
DariaBod Feb 3, 2026
c3c8266
Merge branch 'develop' into fb_mvtc_test
DariaBod Feb 4, 2026
e5ec9db
-delete unused imports
DariaBod Feb 5, 2026
33683e0
-delete unexpected symbol
DariaBod Feb 5, 2026
9a5773a
-some code style fixes
DariaBod Feb 5, 2026
41e1c78
Apply suggestion from @labkey-tchad
DariaBod Feb 11, 2026
36a970a
Provide warnings for unknown fields for cross sample type import (#2862)
cnathe Feb 5, 2026
bac1f69
Dismiss popover for responsive grid (#2874)
DariaBod Feb 7, 2026
c44cbd6
Misc fixes for error message single quote change (#2876)
cnathe Feb 9, 2026
c60c514
Fix expected error messages in tests (#2875)
DariaBod Feb 9, 2026
fe04f1f
Fix build: remove import in ListDateAndTimeTest (#2879)
labkey-nicka Feb 9, 2026
0435138
Updating SampleFinder Test Component (#2877)
labkey-danield Feb 10, 2026
b4d52e0
fix comments about selectFilter javadoc and initFilterColumn method
DariaBod Feb 11, 2026
fd83f85
Merge branch 'develop' into fb_mvtc_test
labkey-danield Feb 11, 2026
6648cf1
Apply suggestions from code review
DariaBod Feb 13, 2026
d1751b3
Apply suggestions from code review
DariaBod Feb 13, 2026
187e9b4
Move getExpectedAuditDataChange method to AuditLogHelper. (#2880)
labkey-susanh Feb 10, 2026
7c72826
Make CSP error logging validation more robust (#2868)
labkey-jeckels Feb 4, 2026
e9aab1d
Merge branch 'refs/heads/develop' into fb_mvtc_test
DariaBod Feb 13, 2026
87c688b
fix all pr comments
DariaBod Feb 14, 2026
6cab358
fixed study and list tests
DariaBod Feb 16, 2026
987b284
Merge branch 'develop' into fb_mvtc_test
DariaBod Feb 16, 2026
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
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
11 changes: 11 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,17 @@ else if (validator instanceof FieldDefinition.TextChoiceValidator textChoiceVali
throw new IllegalArgumentException("TextChoice fields cannot have additional validators.");
}
fieldRow.setTextChoiceValues(textChoiceValidator.getValues());
fieldRow.setAllowMultipleSelections(false);
}
else if (validator instanceof FieldDefinition.MultiValueTextChoiceValidator multiValueTextChoiceValidator)
{
// MultiValueTextChoice is a field type; implemented using a special validator. TextChoice field cannot have other validators.
if (validators.size() > 1)
{
throw new IllegalArgumentException("TextChoice fields cannot have additional validators.");
}
fieldRow.setTextChoiceValues(multiValueTextChoiceValidator.getValues());
fieldRow.setAllowMultipleSelections(true);
Comment on lines +239 to +249
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MultiValueTextChoiceValidator is redundant. We can just check the column type to decide whether to allow multiple selections.

Suggested change
fieldRow.setAllowMultipleSelections(false);
}
else if (validator instanceof FieldDefinition.MultiValueTextChoiceValidator multiValueTextChoiceValidator)
{
// MultiValueTextChoice is a field type; implemented using a special validator. TextChoice field cannot have other validators.
if (validators.size() > 1)
{
throw new IllegalArgumentException("TextChoice fields cannot have additional validators.");
}
fieldRow.setTextChoiceValues(multiValueTextChoiceValidator.getValues());
fieldRow.setAllowMultipleSelections(true);
fieldRow.setAllowMultipleSelections(fieldDefinition.getType() == FieldDefinition.ColumnType.MultiValueTextChoice);
}

}
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();

Comment on lines +516 to +517
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this work if a test is trying to add to an existing selections?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect a method called setCellValue to overwrite any existing value.

lookupSelect.open();

for (String _value : values)
Expand Down
35 changes: 29 additions & 6 deletions src/org/labkey/test/components/ui/grids/ResponsiveGrid.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.labkey.test.components.react.ReactCheckBox;
import org.labkey.test.components.ui.grids.FieldReferenceManager.FieldReference;
import org.labkey.test.components.ui.search.FilterExpressionPanel;
import org.labkey.test.components.ui.search.FilterFacetedPanel;
import org.labkey.test.params.FieldKey;
import org.labkey.test.util.selenium.WebElementUtils;
import org.openqa.selenium.Keys;
Expand All @@ -40,6 +41,14 @@
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.remoteapi.query.Filter.Operator.IS_EMPTY;
import static org.labkey.remoteapi.query.Filter.Operator.IS_NOT_EMPTY;
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,18 +243,31 @@ 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);
Comment on lines 243 to +247
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IN isn't actually a list operator (this component just uses it as a signal to use the checkboxes). It shouldn't be grouped with them. This list should also be static and contain the empty operators.

Suggested change
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);
public static final List<Filter.Operator> ARRAY_OPERATORS = List.of(CONTAINS_ALL, CONTAINS_ANY, CONTAINS_EXACTLY, CONTAINS_NONE, DOES_NOT_CONTAIN_EXACTLY, IS_EMPTY, IS_NOT_EMPTY);
private GridFilterModal initFilterColumn(CharSequence columnIdentifier, Filter.Operator operator, Object value)
{

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));
FilterFacetedPanel filterPanel = filterModal.selectFacetTab();
filterPanel.selectValue(values.get(0));
filterPanel.checkValues(values.toArray(String[]::new));
if (filterPanel.isFiltersPresented())
{
filterPanel.selectFilter(operator);
}
}
else if (value == null)
{
filterModal.selectFacetTab().selectFilter(operator);
}
Comment on lines -241 to 266
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I have a better understanding of these new filter operators, I have a better idea for this method. We don't actually need to check whether the operator select is present; for the new operators, the select will always be present.
Something like this:

if (operator.equals(Filter.Operator.IN) && value instanceof List<?> || ARRAY_OPERATORS.contains(operator))
{
    FilterFacetedPanel filterPanel = filterModal.selectFacetTab();
    if (ARRAY_OPERATORS.contains(operator))
    {
        filterPanel.selectFilter(operator);
    }
    if (value != null)
    {
        List<String> values = (List<String>) value;
        filterPanel.selectValue(values.get(0));
        filterPanel.checkValues(values.toArray(String[]::new));
    }
}

else
{
filterModal.selectExpressionTab().setFilter(new FilterExpressionPanel.Expression(operator, value));
}
}
return filterModal;
}
Expand Down Expand Up @@ -385,15 +407,16 @@ public T selectRow(int index, boolean checked)

/**
* Finds the first row with the specified texts in the specified columns, and sets its checkbox
* @param partialMap key-column (fieldKey, name, or label), value-text in that column
* @param checked the desired checkbox state
*
* @param partialMap key-column (fieldKey, name, or label), value-text in that column
* @param checked the desired checkbox state
* @return this grid
*/
public T selectRow(Map<String, String> partialMap, boolean checked)
{
GridRow row = getRow(partialMap);
selectRowAndVerifyCheckedCounts(row, checked);
getWrapper().log("Row described by map ["+partialMap+"] selection state set to + ["+row.isSelected()+"]");
getWrapper().log("Row described by map [" + partialMap + "] selection state set to + [" + row.isSelected() + "]");

return getThis();
}
Expand Down
23 changes: 23 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,15 +6,19 @@
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.labkey.remoteapi.query.Filter;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;

import java.util.List;
import java.util.stream.Collectors;

import static org.labkey.test.WebDriverWrapper.waitFor;
import static org.labkey.test.components.html.Input.Input;
import static org.labkey.test.util.samplemanagement.SMTestUtils.isVisible;

public class FilterFacetedPanel extends WebDriverComponent<FilterFacetedPanel.ElementCache>
{
Expand Down Expand Up @@ -48,6 +52,23 @@ public void selectValue(String value)
elementCache().findCheckboxLabel(value).click();
}

/**
* Check that filter choosing option exists on the page.
*/
public boolean isFiltersPresented()
{
return waitFor(() -> isVisible(elementCache().filterTypeSelects), 1000);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't wait for things that might not be present, especially when the most common state is for it to not be present.

}

/**
* Select a filer by clicking its label. Right now this method relevant only for multi-value text choice.
* @param operator desired filter value
*/
public void selectFilter(Filter.Operator operator)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A slightly more descriptive method name.

Suggested change
public void selectFilter(Filter.Operator operator)
public void selectArrayFilterOperator(Filter.Operator operator)

{
elementCache().filterTypeSelects.select(operator.getDisplayValue());
}

/**
* Check single facet value by label to see if it is checked or not.
* @param value desired value
Expand Down Expand Up @@ -123,6 +144,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);
Comment on lines +147 to +148
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making name consistent with method rename suggestion.

Suggested change
protected final ReactSelect filterTypeSelects =
new ReactSelect.ReactSelectFinder(getDriver()).index(0).findWhenNeeded(this);
protected final ReactSelect arrayFilterOperatorSelect =
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
32 changes: 21 additions & 11 deletions src/org/labkey/test/pages/DatasetInsertPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

import java.util.List;
import java.util.Map;

import static org.labkey.test.util.EscapeUtil.FORM_FIELD_PREFIX;
Expand All @@ -45,26 +46,26 @@ protected void waitForReady()
waitForElement(Locator.tag("*").attributeStartsWith("name", FORM_FIELD_PREFIX));
}

public void insert(Map<String, String> values)
public void insert(Map<String, ?> values)
{
tryInsert(values);

assertElementNotPresent(Locators.labkeyError);
}

public void insert(Map<String, String> values, boolean b, String s)
public void insert(Map<String, ?> values, boolean b, String s)
{
tryInsert(values);

assertElementNotPresent(Locators.labkeyError);
}

public void insertExpectingError(Map<String, String> values)
public void insertExpectingError(Map<String, ?> values)
{
insertExpectingError(values, null);
}

public void insertExpectingError(Map<String, String> values, String errorMsg)
public void insertExpectingError(Map<String, ?> values, String errorMsg)
{
tryInsert(values);

Expand All @@ -78,20 +79,20 @@ public void insertExpectingError(Map<String, String> values, String errorMsg)
}
}

private void tryInsert(Map<String, String> values)
private void tryInsert(Map<String, ?> values)
{
for (Map.Entry<String, String> entry : values.entrySet())
for (Map.Entry<String, ?> entry : values.entrySet())
{
WebElement fieldInput = Locator.name(EscapeUtil.getFormFieldName(entry.getKey())).findElement(getDriver());
WebElement fieldInput = Locator.tag("*").attributeEndsWith("name", EscapeUtil.getFormFieldName(entry.getKey())).findElement(getDriver());
String type = fieldInput.getAttribute("type");
switch (type)
{
case "text":
case "file":
setFormElement(fieldInput, entry.getValue());
setFormElement(fieldInput, entry.getValue().toString());
break;
case "checkbox":
if (Boolean.valueOf(entry.getValue()))
if (Boolean.valueOf(entry.getValue().toString()))
checkCheckbox(fieldInput);
else
uncheckCheckbox(fieldInput);
Expand All @@ -101,10 +102,19 @@ private void tryInsert(Map<String, String> values)
switch (tag)
{
case "textarea":
setFormElementJS(fieldInput, entry.getValue());
setFormElementJS(fieldInput, entry.getValue().toString());
break;
case "select":
selectOptionByText(fieldInput, entry.getValue());
if (entry.getValue() instanceof List)
{
List<String> options = (List<String>) entry.getValue();
for (String option : options)
{
selectOptionByText(fieldInput, option);
}
break;
}
selectOptionByText(fieldInput, entry.getValue().toString());
break;
default:
throw new IllegalArgumentException("Update " + getClass().getSimpleName() + "#insert() to support field: " + entry.getKey() + ", tag = " + tag + ", type = " + type);
Expand Down
21 changes: 20 additions & 1 deletion src/org/labkey/test/pages/query/UpdateQueryRowPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@
import org.labkey.test.components.html.OptionSelect;
import org.labkey.test.pages.LabKeyPage;
import org.labkey.test.util.EscapeUtil;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class UpdateQueryRowPage extends LabKeyPage<UpdateQueryRowPage.ElementCache>
Expand Down Expand Up @@ -87,6 +89,10 @@ else if (value instanceof File f)
{
setField(entry.getKey(), f);
}
else if (value instanceof List l)
{
setField(entry.getKey(), l);
}
else
{
throw new IllegalArgumentException("Unsupported value type for '" + entry.getKey() + "': " + value.getClass().getName());
Expand All @@ -99,7 +105,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 All @@ -108,6 +114,19 @@ public UpdateQueryRowPage setField(String fieldName, String value)
return this;
}

public UpdateQueryRowPage setField(String fieldName, List<String> values)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can replace the existing setMultiValueField that was added last week. It only has one usage and doesn't actually support multiple values.

{
WebElement field = elementCache().findField(fieldName, true);
List<WebElement> options = field.findElements(By.tagName("option"));
//unselect all options that selected but shouldn't be selected
options.forEach(option ->
{
if (!values.contains(option.getText()) && option.getAttribute("selected") != null) option.click();
});
values.forEach(value -> selectOptionByText(field, value));
return this;
Comment on lines +119 to +127
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WebDriver's Select class handles multi-select.

Suggested change
WebElement field = elementCache().findField(fieldName, true);
List<WebElement> options = field.findElements(By.tagName("option"));
//unselect all options that selected but shouldn't be selected
options.forEach(option ->
{
if (!values.contains(option.getText()) && option.getAttribute("selected") != null) option.click();
});
values.forEach(value -> selectOptionByText(field, value));
return this;
WebElement field = new Select(elementCache().findField(fieldName, true));
field.deselectAll();
values.forEach(value -> field.selectByVisibleText(value));
return this;

}

public UpdateQueryRowPage setField(String fieldName, Boolean value)
{
new Checkbox(elementCache().findField(fieldName)).set(value);
Expand Down
Loading