From 4b9f53992387a6122b9d4bf37cc27e6439bc7327 Mon Sep 17 00:00:00 2001 From: Daria Bodiakova <70635654+DariaBod@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:47:01 -0700 Subject: [PATCH 1/7] sample finder conversion tests --- src/org/labkey/test/components/domain/DomainFieldRow.java | 8 +++++++- .../test/components/ui/search/FilterExpressionPanel.java | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/org/labkey/test/components/domain/DomainFieldRow.java b/src/org/labkey/test/components/domain/DomainFieldRow.java index d78be327e5..a2df56264c 100644 --- a/src/org/labkey/test/components/domain/DomainFieldRow.java +++ b/src/org/labkey/test/components/domain/DomainFieldRow.java @@ -767,11 +767,17 @@ 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) + public DomainFieldRow setAllowMultipleSelections(Boolean allowMultipleSelections) { WebDriverWrapper.waitFor(() -> elementCache().allowMultipleSelectionsCheckbox.isDisplayed(), "Allow Multiple Selections checkbox did not become visible", 1000); elementCache().allowMultipleSelectionsCheckbox.set(allowMultipleSelections); + // A confirmation dialog may appear when re-enabling multiple selections; dismiss it if present + ModalDialog modal = new ModalDialog.ModalDialogFinder(getDriver()) + .withTitle("Confirm Data Type Change").findOrNull(getDriver()); + if (modal != null) + modal.dismiss("Yes, Change Data Type"); + return this; } /** diff --git a/src/org/labkey/test/components/ui/search/FilterExpressionPanel.java b/src/org/labkey/test/components/ui/search/FilterExpressionPanel.java index 6020bae857..b1cd326742 100644 --- a/src/org/labkey/test/components/ui/search/FilterExpressionPanel.java +++ b/src/org/labkey/test/components/ui/search/FilterExpressionPanel.java @@ -27,7 +27,8 @@ public class FilterExpressionPanel extends WebDriverComponent Date: Tue, 14 Apr 2026 13:53:19 -0700 Subject: [PATCH 2/7] test cross folder MVTC to TC conversion --- .../components/domain/DomainFieldRow.java | 2 + .../MultiValueTextChoiceSampleTypeTest.java | 172 ++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 src/org/labkey/test/tests/MultiValueTextChoiceSampleTypeTest.java diff --git a/src/org/labkey/test/components/domain/DomainFieldRow.java b/src/org/labkey/test/components/domain/DomainFieldRow.java index a2df56264c..bfc86b2cb0 100644 --- a/src/org/labkey/test/components/domain/DomainFieldRow.java +++ b/src/org/labkey/test/components/domain/DomainFieldRow.java @@ -771,6 +771,8 @@ public DomainFieldRow setAllowMultipleSelections(Boolean allowMultipleSelections { WebDriverWrapper.waitFor(() -> elementCache().allowMultipleSelectionsCheckbox.isDisplayed(), "Allow Multiple Selections checkbox did not become visible", 1000); + WebDriverWrapper.waitFor(() -> elementCache().allowMultipleSelectionsCheckbox.isEnabled(), + "Allow Multiple Selections checkbox isn't enabled", 1000); elementCache().allowMultipleSelectionsCheckbox.set(allowMultipleSelections); // A confirmation dialog may appear when re-enabling multiple selections; dismiss it if present ModalDialog modal = new ModalDialog.ModalDialogFinder(getDriver()) diff --git a/src/org/labkey/test/tests/MultiValueTextChoiceSampleTypeTest.java b/src/org/labkey/test/tests/MultiValueTextChoiceSampleTypeTest.java new file mode 100644 index 0000000000..c09e23f64d --- /dev/null +++ b/src/org/labkey/test/tests/MultiValueTextChoiceSampleTypeTest.java @@ -0,0 +1,172 @@ +package org.labkey.test.tests; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.labkey.remoteapi.CommandException; +import org.labkey.test.BaseWebDriverTest; +import org.labkey.test.WebTestHelper; +import org.labkey.test.categories.Daily; +import org.labkey.test.components.domain.DomainFieldRow; +import org.labkey.test.pages.experiment.UpdateSampleTypePage; +import org.labkey.test.pages.query.UpdateQueryRowPage; +import org.labkey.test.params.FieldDefinition; +import org.labkey.test.params.FieldDefinition.ColumnType; +import org.labkey.test.params.experiment.SampleTypeDefinition; +import org.labkey.test.util.DataRegionTable; +import org.labkey.test.util.DomainUtils; +import org.labkey.test.util.PortalHelper; +import org.labkey.test.util.PostgresOnlyTest; +import org.labkey.test.util.TestDataGenerator; +import org.labkey.test.util.exp.SampleTypeAPIHelper; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.labkey.test.util.SampleTypeHelper.beginAtSampleTypesList; +import static org.labkey.test.util.TestDataGenerator.randomDomainName; +import static org.labkey.test.util.TestDataGenerator.randomFieldName; +import static org.labkey.test.util.TestDataGenerator.randomTextChoice; +import static org.labkey.test.util.TestDataGenerator.shuffleSelect; + +@Category({Daily.class}) +public class MultiValueTextChoiceSampleTypeTest extends BaseWebDriverTest implements PostgresOnlyTest +{ + private static final String SUB_FOLDER = "ChildFolder_MultiValueTextChoice_SampleType_Test"; + private final String SUB_FOLDER_PATH = getProjectName() + "/" + SUB_FOLDER; + + @Override + public List getAssociatedModules() + { + return Arrays.asList("experiment"); + } + + @Override + protected String getProjectName() + { + return "MultiValueTextChoice_SampleType_Test"; + } + + @BeforeClass + public static void setupProject() + { + MultiValueTextChoiceSampleTypeTest init = getCurrentTest(); + init.doSetup(); + } + + private void doSetup() + { + PortalHelper portalHelper = new PortalHelper(this); + _containerHelper.createProject(getProjectName(), null); + portalHelper.enterAdminMode(); + portalHelper.addWebPart("Sample Types"); + _containerHelper.createSubfolder(getProjectName(), SUB_FOLDER); + portalHelper.addWebPart("Sample Types"); + portalHelper.exitAdminMode(); + } + + @Before + public void beforeTest() + { + goToProjectHome(); + } + + private TestDataGenerator createSampleType(String sampleTypeName, String sampleNamePrefix, String multiValueTextChoiceFieldName, List multiValueTextChoiceValues) + { + log(String.format("Create a new sample type named '%s'.", sampleTypeName)); + SampleTypeDefinition sampleTypeDefinition = new SampleTypeDefinition(sampleTypeName); + sampleTypeDefinition.setNameExpression(String.format("%s${genId}", sampleNamePrefix)); + + log(String.format("Add a MultiValueTextChoice field named '%s'.", multiValueTextChoiceFieldName)); + FieldDefinition textChoiceField = new FieldDefinition(multiValueTextChoiceFieldName, ColumnType.MultiValueTextChoice); + textChoiceField.setMultiChoiceValues(multiValueTextChoiceValues); + + sampleTypeDefinition.addField(textChoiceField); + + return SampleTypeAPIHelper.createEmptySampleType(getCurrentContainerPath(), sampleTypeDefinition); + } + + /** + * Validate cross folder MVTC to TC conversion. + */ + @Test + public void testCrossFolderMVTCtoTCConversion() throws IOException, CommandException + { + final String sampleTypeName = randomDomainName("MVTC_Sample_Edit", DomainUtils.DomainKind.SampleSet); + final String multiValueTextChoiceFieldName = randomFieldName("MultiValueTextChoice_Field"); + final String namePrefix = "MVTC_"; + int samplesCount = 3; + List mvtcValues = randomTextChoice(10); + + // Create Sample type in main folder. + TestDataGenerator dataGenerator = createSampleType(sampleTypeName, namePrefix, multiValueTextChoiceFieldName, mvtcValues); + + log("Create some samples in child folder. They have MultiValueTextChoice filed filled with random multiple values."); + + for (int i = 1; i <= samplesCount; i++) + { + Map sample = new HashMap<>(); + String sampleName = String.format("%s%d", namePrefix, i); + sample.put("Name", sampleName); + sample.put(multiValueTextChoiceFieldName, shuffleSelect(mvtcValues, 2)); + dataGenerator.addCustomRow(sample); + } + + dataGenerator.insertRows(WebTestHelper.getRemoteApiConnection(), SUB_FOLDER_PATH); + + // Check that impossible to convert MVTC to TC. + DomainFieldRow fieldRow = beginAtSampleTypesList(this, getProjectName()) + .goToEditSampleType(sampleTypeName) + .getFieldsPanel() + .getField(multiValueTextChoiceFieldName) + .expand(); + checker().wrapAssertion(() -> + assertThatThrownBy(() -> fieldRow.setAllowMultipleSelections(false)) + .as("'Allow Multiple Selections' checkbox should not be available") + .hasMessageContaining("Allow Multiple Selections checkbox isn't enabled") + ); + + // Edit all MVTC fields to have 1 chosen value. + DataRegionTable samplesTable = beginAtSampleTypesList(this, SUB_FOLDER_PATH) + .goToSampleType(sampleTypeName) + .getSamplesDataRegionTable(); + + for (int i = 0; i < samplesCount; i++) + { + UpdateQueryRowPage updateQueryRowPage = samplesTable.clickEditRow(i); + updateQueryRowPage.setField(multiValueTextChoiceFieldName, shuffleSelect(mvtcValues, 1)); + updateQueryRowPage.submit(); + } + + // Convert MVTC to TC. + UpdateSampleTypePage updateSampleTypePage = beginAtSampleTypesList(this, getProjectName()) + .goToEditSampleType(sampleTypeName); + updateSampleTypePage.getFieldsPanel() + .getField(multiValueTextChoiceFieldName) + .expand() + .setAllowMultipleSelections(false); + updateSampleTypePage.clickSave(); + + // Check that impossible to choose multiple values. + samplesTable = beginAtSampleTypesList(this, SUB_FOLDER_PATH) + .goToSampleType(sampleTypeName) + .getSamplesDataRegionTable(); + UpdateQueryRowPage updateQueryRowPage = samplesTable.clickEditRow(0); + checker().wrapAssertion(() -> + assertThatThrownBy(() -> updateQueryRowPage.setField(multiValueTextChoiceFieldName, shuffleSelect(mvtcValues, 2))) + .as("MVTC element isn't found on the page.") + .hasMessageContaining("Unable to find element") + ); + } + + @Override + protected void doCleanup(boolean afterTest) + { + _containerHelper.deleteProject(getProjectName(), false); + } +} From 55909cd6dad6b5ab3184268b1c38927c761a8058 Mon Sep 17 00:00:00 2001 From: Daria Bodiakova <70635654+DariaBod@users.noreply.github.com> Date: Wed, 15 Apr 2026 08:59:14 -0700 Subject: [PATCH 3/7] sample finder tests add mvtc field check after conversions --- .../test/util/data/TestArrayDataUtils.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/org/labkey/test/util/data/TestArrayDataUtils.java b/src/org/labkey/test/util/data/TestArrayDataUtils.java index deaf7fa81b..7db7e07e42 100644 --- a/src/org/labkey/test/util/data/TestArrayDataUtils.java +++ b/src/org/labkey/test/util/data/TestArrayDataUtils.java @@ -3,7 +3,10 @@ import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; +import org.junit.Assert; import org.labkey.remoteapi.query.Filter; +import org.labkey.test.components.ui.grids.QueryGrid; +import org.labkey.test.util.DeferredErrorCollector; import java.io.IOException; import java.io.StringReader; @@ -13,6 +16,8 @@ import java.util.Map; import java.util.stream.Collectors; +import static org.assertj.core.api.Assertions.assertThat; + public class TestArrayDataUtils { @@ -127,4 +132,25 @@ private static boolean isMatch(List actualValues, List searchVal default -> throw new IllegalArgumentException("Invalid filter type " + type); }; } + + /** + * Verifies that the grid contains exactly the expected sample IDs and that each sample's MVTC column + * value matches the expected value in {@code sampleMVTCMap}. + * Size mismatch is a hard failure; ID and per-row value checks are soft (collected via {@code checker}). + */ + public static void verifyMVTCResults(QueryGrid grid, Map sampleMVTCMap, + String colLabel, String stepMessage, DeferredErrorCollector checker) + { + List foundIds = grid.getColumnDataAsText("Sample ID"); + Assert.assertEquals(stepMessage + ": size mismatch", sampleMVTCMap.size(), foundIds.size()); + checker.wrapAssertion(() -> assertThat(foundIds) + .as(stepMessage + ": Sample IDs") + .containsExactlyInAnyOrderElementsOf(sampleMVTCMap.keySet())); + sampleMVTCMap.forEach((sampleId, expected) -> { + Map rowMap = grid.getRowMapByLabel("Sample ID", sampleId); + checker.wrapAssertion(() -> assertThat(rowMap.get(colLabel)) + .as("%s: %s for sample %s", stepMessage, colLabel, sampleId) + .isEqualTo(expected == null ? "" : expected)); + }); + } } \ No newline at end of file From f860fd44b4bc2b9a0bba83c7de3706bde718e1d7 Mon Sep 17 00:00:00 2001 From: Daria Bodiakova <70635654+DariaBod@users.noreply.github.com> Date: Thu, 16 Apr 2026 16:24:15 -0700 Subject: [PATCH 4/7] tests for saved grid type conversion --- .../tests/component/GridPanelViewTest.java | 642 +++++++++++++++++- .../test/util/data/TestArrayDataUtils.java | 18 +- 2 files changed, 628 insertions(+), 32 deletions(-) diff --git a/src/org/labkey/test/tests/component/GridPanelViewTest.java b/src/org/labkey/test/tests/component/GridPanelViewTest.java index bc778ae7b4..ce04ee9a7b 100644 --- a/src/org/labkey/test/tests/component/GridPanelViewTest.java +++ b/src/org/labkey/test/tests/component/GridPanelViewTest.java @@ -1,6 +1,7 @@ package org.labkey.test.tests.component; import org.assertj.core.api.Assertions; +import org.jetbrains.annotations.Nullable; import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -13,6 +14,7 @@ import org.labkey.remoteapi.query.Sort; import org.labkey.test.Locator; import org.labkey.test.SortDirection; +import org.labkey.test.WebTestHelper; import org.labkey.test.categories.Daily; import org.labkey.test.components.CustomizeView; import org.labkey.test.components.bootstrap.ModalDialog; @@ -24,17 +26,20 @@ import org.labkey.test.components.ui.grids.SaveViewDialog; import org.labkey.test.components.ui.search.FilterExpressionPanel; import org.labkey.test.components.ui.search.FilterFacetedPanel; +import org.labkey.test.pages.experiment.UpdateSampleTypePage; +import org.labkey.test.pages.test.CoreComponentsTestPage; import org.labkey.test.params.FieldDefinition; import org.labkey.test.params.FieldKey; import org.labkey.test.params.experiment.SampleTypeDefinition; -import org.labkey.test.WebTestHelper; import org.labkey.test.util.APIUserHelper; import org.labkey.test.util.ApiPermissionsHelper; import org.labkey.test.util.AuditLogHelper; import org.labkey.test.util.DataRegionTable; import org.labkey.test.util.PermissionsHelper; +import org.labkey.test.util.PortalHelper; import org.labkey.test.util.SampleTypeHelper; import org.labkey.test.util.TestDataGenerator; +import org.labkey.test.util.data.TestArrayDataUtils; import org.labkey.test.util.exp.SampleTypeAPIHelper; import org.openqa.selenium.WebDriverException; @@ -44,10 +49,15 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.function.Predicate; import static org.labkey.test.util.PermissionsHelper.FOLDER_ADMIN_ROLE; +import static org.labkey.test.util.SampleTypeHelper.beginAtSampleTypesList; +import static org.labkey.test.util.TestDataGenerator.randomTextChoice; @Category({Daily.class}) public class GridPanelViewTest extends GridPanelBaseTest @@ -63,14 +73,21 @@ public class GridPanelViewTest extends GridPanelBaseTest private static final int VIEW_DIALOG_ST_SIZE = 100; private static final String VIEW_DIALOG_ST_PREFIX = "DLG-"; + // Samples in the subfolder. + private static final String GRID_SUB_FOLDER = "Sub Folder For Grid Views"; + private static final int SUBFOLDER_VIEW_SAMPLE_TYPE_SIZE = 30; + private static final String SUBFOLDER_VIEW_SAMPLE_PREFIX = "SUBFOLDER_VIEW-"; + // Column names. private static final String COL_NAME = "Name"; private static final String COL_STRING1 = "Str1"; private static final String COL_STRING2 = "Str2"; private static final String COL_INT = "Int"; private static final String COL_BOOL = "Bool"; + public static final List TEXT_MULTI_CHOICE_LIST = randomTextChoice(10); + public static final String COL_MULTITEXTCHOICE = "Multi Choice"; - private static final List DEFAULT_COLUMNS = Arrays.asList(COL_NAME, COL_INT, COL_STRING1, COL_STRING2, COL_BOOL); + private static final List DEFAULT_COLUMNS = Arrays.asList(COL_NAME, COL_INT, COL_STRING1, COL_STRING2, COL_BOOL, COL_MULTITEXTCHOICE); // Will keep track of state of the columns, that is are they filtered, sorted, or have no modifiers. private static Map defaultColumnState = new HashMap<>(); @@ -96,6 +113,28 @@ public class GridPanelViewTest extends GridPanelBaseTest private final AuditLogHelper _auditLogHelper = new AuditLogHelper(this); + // --- MVTC conversion test support --- + + private record GridConversionResult(boolean isDropped, @Nullable String expectedFilterText) + { + static GridConversionResult kept(String filterText) { return new GridConversionResult(false, filterText); } + static GridConversionResult dropped() { return new GridConversionResult(true, null); } + } + + private record GridMVTCCase(String viewName, Filter.Operator op, String[] filterVals, + GridConversionResult conversionResult, + Map expectedSampleMap) {} + + private record GridTCCase(String viewName, Filter.Operator op, String[] filterVals, + String expectedAfterConversionText, + Map expectedSampleMap) {} + + /** Holds a single case for the subfolder round-trip test (MVTC → TC → MVTC → Text). */ + private record GridSubFolderCase(String viewName, Filter.Operator op, String[] filterVals, + GridConversionResult resultTC, + GridConversionResult resultMVTC, + GridConversionResult resultText) {} + // Tests that need to be written: // Validate "Save As..." from the grid save button. // Validate views that are locked, or in some other way, cannot be updates in the manage views dialog. @@ -122,6 +161,13 @@ public static void setupProject() throws IOException, CommandException init.doSetup(); } + + static void main() + { + List randomTextChoice = randomTextChoice(10); + System.out.println("Done"); + } + private void doSetup() throws IOException, CommandException { initProject(); @@ -136,7 +182,9 @@ private void doSetup() throws IOException, CommandException List fields = Arrays.asList(new FieldDefinition(COL_INT, FieldDefinition.ColumnType.Integer), new FieldDefinition(COL_STRING1, FieldDefinition.ColumnType.String), new FieldDefinition(COL_STRING2, FieldDefinition.ColumnType.String), - new FieldDefinition(COL_BOOL, FieldDefinition.ColumnType.Boolean)); + new FieldDefinition(COL_BOOL, FieldDefinition.ColumnType.Boolean), + new FieldDefinition(COL_MULTITEXTCHOICE, FieldDefinition.ColumnType.MultiValueTextChoice) + .setMultiChoiceValues(TEXT_MULTI_CHOICE_LIST)); createSampleType(VIEW_DIALOG_ST, VIEW_DIALOG_ST_PREFIX, VIEW_DIALOG_ST_SIZE, fields); @@ -145,16 +193,33 @@ private void doSetup() throws IOException, CommandException _userHelper.createUser(OTHER_USER, true,false); new ApiPermissionsHelper(this).addMemberToRole(OTHER_USER, FOLDER_ADMIN_ROLE, PermissionsHelper.MemberType.user, getProjectName()); + PortalHelper portalHelper = new PortalHelper(this); + _containerHelper.createSubfolder(getProjectName(), GRID_SUB_FOLDER); + portalHelper.addWebPart("Sample Types"); + portalHelper.exitAdminMode(); + + TestDataGenerator sampleSetDataGeneratorForSubfolder = new TestDataGenerator("samples", DEFAULT_VIEW_SAMPLE_TYPE, getProjectName() + "/" + GRID_SUB_FOLDER); + generateSamples(sampleSetDataGeneratorForSubfolder, SUBFOLDER_VIEW_SAMPLE_PREFIX, SUBFOLDER_VIEW_SAMPLE_TYPE_SIZE); + sampleSetDataGeneratorForSubfolder.insertRows(); + removeFlagColumnFromDefaultView(DEFAULT_VIEW_SAMPLE_TYPE); } private void createSampleType(String sampleTypeName, String samplePrefix, int numOfSamples, List fields) throws IOException, CommandException { - SampleTypeDefinition props = new SampleTypeDefinition(sampleTypeName) .setFields(fields); TestDataGenerator sampleSetDataGenerator = SampleTypeAPIHelper.createEmptySampleType(getProjectName(), props); + generateSamples(sampleSetDataGenerator, samplePrefix, numOfSamples); + + sampleSetDataGenerator.insertRows(); + + removeFlagColumnFromDefaultView(sampleTypeName); + } + + private void generateSamples(TestDataGenerator sampleSetDataGenerator, String samplePrefix, int numOfSamples) throws IOException, CommandException + { int sampleId = 1; int allPossibleIndex = 0; int memIndex = 0; @@ -167,21 +232,19 @@ private void createSampleType(String sampleTypeName, String samplePrefix, int nu if(memIndex == stringSetMembers.size()) memIndex = 0; + String name = String.format("%s%d", samplePrefix, sampleId); sampleSetDataGenerator.addCustomRow( - Map.of(COL_NAME, String.format("%s%d", samplePrefix, sampleId), + Map.of(COL_NAME, name, COL_INT, sampleId, COL_STRING1, stringSets.get(allPossibleIndex), COL_STRING2, stringSetMembers.get(memIndex), - COL_BOOL, sampleId % 2 == 0)); + COL_BOOL, sampleId % 2 == 0, + COL_MULTITEXTCHOICE, sampleId % 5 == 0 ? List.of() : List.of(TEXT_MULTI_CHOICE_LIST.get(Math.abs(name.hashCode()) % TEXT_MULTI_CHOICE_LIST.size())))); allPossibleIndex++; memIndex++; sampleId++; } - - sampleSetDataGenerator.insertRows(); - - removeFlagColumnFromDefaultView(sampleTypeName); } /** @@ -190,11 +253,11 @@ private void createSampleType(String sampleTypeName, String samplePrefix, int nu * @param sampleTypeName Name of the sample type with the default view to change. * @param columns The columns to show in the default view. Will be added in the order of the list. */ - private void resetDefaultView(String sampleTypeName, List columns) throws Exception + private void resetDefaultView(String projectPath, String sampleTypeName, List columns) throws Exception { log(String.format("Set the default view for '%s' to have these columns: '%s'", sampleTypeName, columns)); - goToProjectHome(); + goToProjectHome(projectPath); waitAndClickAndWait(Locator.linkWithText(sampleTypeName)); SampleTypeHelper sampleHelper = new SampleTypeHelper(this); DataRegionTable drtSamples = sampleHelper.getSamplesDataRegionTable(); @@ -290,7 +353,7 @@ public void testMyDefaultView() throws Exception { String screenShotID = "testMyDefaultView"; - resetDefaultView(DEFAULT_VIEW_SAMPLE_TYPE, DEFAULT_COLUMNS); + resetDefaultView(getProjectName(), DEFAULT_VIEW_SAMPLE_TYPE, DEFAULT_COLUMNS); goToProjectHome(); @@ -388,7 +451,7 @@ public void testRemoveColumnForView() throws Exception final String screenShotPrefix = "testRemoveColumnForView"; - resetDefaultView(DEFAULT_VIEW_SAMPLE_TYPE, DEFAULT_COLUMNS); + resetDefaultView(getProjectName(), DEFAULT_VIEW_SAMPLE_TYPE, DEFAULT_COLUMNS); String columnToRemove = COL_BOOL; log(String.format("For sample type '%s' remove the '%s' column using the column header menu.", DEFAULT_VIEW_SAMPLE_TYPE, columnToRemove)); @@ -502,7 +565,7 @@ public void testColumnHeaderAndFilterPillCustomView() throws Exception public void testColumnHeaderAndFilterPill(String testName, String viewName) throws Exception { - resetDefaultView(DEFAULT_VIEW_SAMPLE_TYPE, DEFAULT_COLUMNS); + resetDefaultView(getProjectName(), DEFAULT_VIEW_SAMPLE_TYPE, DEFAULT_COLUMNS); QueryGrid grid = beginAtQueryGrid(DEFAULT_VIEW_SAMPLE_TYPE); @@ -662,7 +725,7 @@ public void testEditDefaultView() throws Exception private void testEditView(String testName, String viewName) throws Exception { - resetDefaultView(DEFAULT_VIEW_SAMPLE_TYPE, DEFAULT_COLUMNS); + resetDefaultView(getProjectName(), DEFAULT_VIEW_SAMPLE_TYPE, DEFAULT_COLUMNS); String filterCol1 = COL_STRING1; String filterValue1 = stringSetMembers.get(2); @@ -886,7 +949,7 @@ private void testEditView(String testName, String viewName) throws Exception public void testSaveViewTrickyName() throws Exception { - resetDefaultView(DEFAULT_VIEW_SAMPLE_TYPE, DEFAULT_COLUMNS); + resetDefaultView(getProjectName(), DEFAULT_VIEW_SAMPLE_TYPE, DEFAULT_COLUMNS); String hideCol = COL_INT; @@ -941,7 +1004,7 @@ public void testFieldInsertionOrder() throws Exception { goToProjectHome(); - resetDefaultView(VIEW_DIALOG_ST, DEFAULT_COLUMNS); + resetDefaultView(getProjectName(), VIEW_DIALOG_ST, DEFAULT_COLUMNS); QueryGrid grid = beginAtQueryGrid(VIEW_DIALOG_ST); @@ -992,7 +1055,7 @@ public void testFieldInsertionOrder() throws Exception customizeModal.isAvailableFieldSelected(columnToAdd)); log("Validate that the order of the fields in the 'Shown in Grid' column are as expected."); - expectedFields = List.of(COL_NAME, COL_STRING1, COL_STRING2, COL_INT, COL_BOOL); + expectedFields = List.of(COL_NAME, COL_STRING1, COL_STRING2, COL_INT, COL_BOOL, COL_MULTITEXTCHOICE); checker().verifyEquals(String.format("After adding '%s' fields displayed in 'Show in Grid' panel not as expected.", columnToAdd), expectedFields, customizeModal.getSelectedFieldLabels()); @@ -1026,7 +1089,7 @@ public void testShowAllLabelEditAndUndo() throws Exception { goToProjectHome(); - resetDefaultView(VIEW_DIALOG_ST, DEFAULT_COLUMNS); + resetDefaultView(getProjectName(), VIEW_DIALOG_ST, DEFAULT_COLUMNS); QueryGrid grid = beginAtQueryGrid(VIEW_DIALOG_ST); @@ -1139,7 +1202,7 @@ public void testManageViews() throws Exception { goToProjectHome(); - resetDefaultView(VIEW_DIALOG_ST, DEFAULT_COLUMNS); + resetDefaultView(getProjectName(), VIEW_DIALOG_ST, DEFAULT_COLUMNS); QueryGrid grid = beginAtQueryGrid(VIEW_DIALOG_ST); @@ -1314,7 +1377,7 @@ public void testRemoveAllFields() throws Exception goToProjectHome(); - resetDefaultView(VIEW_DIALOG_ST, DEFAULT_COLUMNS); + resetDefaultView(getProjectName(), VIEW_DIALOG_ST, DEFAULT_COLUMNS); QueryGrid grid = beginAtQueryGrid(VIEW_DIALOG_ST); @@ -1348,7 +1411,7 @@ public void testWarningOnInvalidDateFilter() throws Exception String viewName = "broken view"; goToProjectHome(); - resetDefaultView(VIEW_DIALOG_ST, DEFAULT_COLUMNS); + resetDefaultView(getProjectName(), VIEW_DIALOG_ST, DEFAULT_COLUMNS); QueryGrid grid = beginAtQueryGrid(VIEW_DIALOG_ST); @@ -1393,6 +1456,537 @@ public void testWarningOnInvalidDateFilter() throws Exception .dismiss("Done"); } + @Test + public void testCustomGridViewsMVTCtoTC() throws Exception + { + goToProjectHome(); + resetDefaultView(getProjectName(), DEFAULT_VIEW_SAMPLE_TYPE, DEFAULT_COLUMNS); + + log("Compute expected sample data for MVTC conversion test."); + Map allSamples = computeSampleMVTCMap(); + String filterValue = TEXT_MULTI_CHOICE_LIST.get(0); + String filterValue2 = TEXT_MULTI_CHOICE_LIST.get(1); + + Map emptyMap = filterSampleMap(allSamples, v -> v == null); + Map nonemptyMap = filterSampleMap(allSamples, v -> v != null); + Map containsAnyMap = filterSampleMap(allSamples, v -> filterValue.equals(v) || filterValue2.equals(v)); + Map containsExactMap = filterSampleMap(allSamples, v -> filterValue.equals(v)); + Map containsNotEqMap = filterSampleMap(allSamples, v -> v == null || !filterValue.equals(v)); + Map containsNoneMap = filterSampleMap(allSamples, v -> v == null || (!filterValue.equals(v) && !filterValue2.equals(v))); + + List cases = List.of( + new GridMVTCCase("MVTC→TC Grid - Is Empty", Filter.Operator.ARRAY_ISEMPTY, new String[0], + GridConversionResult.kept(Filter.Operator.ISBLANK.getDisplayValue()), emptyMap), + new GridMVTCCase("MVTC→TC Grid - Contains Any", Filter.Operator.ARRAY_CONTAINS_ANY, new String[]{filterValue, filterValue2}, + GridConversionResult.kept(Filter.Operator.IN.getDisplayValue()), containsAnyMap), + new GridMVTCCase("MVTC→TC Grid - Contains All", Filter.Operator.ARRAY_CONTAINS_ALL, new String[]{filterValue, filterValue2}, + GridConversionResult.dropped(), containsAnyMap), + new GridMVTCCase("MVTC→TC Grid - Is Not Empty", Filter.Operator.ARRAY_ISNOTEMPTY, new String[0], + GridConversionResult.kept(Filter.Operator.NONBLANK.getDisplayValue()), nonemptyMap), + new GridMVTCCase("MVTC→TC Grid - Contains Not Exact", Filter.Operator.ARRAY_CONTAINS_NOT_EXACT, new String[]{filterValue}, + GridConversionResult.kept(filterValue), containsNotEqMap), + new GridMVTCCase("MVTC→TC Grid - Contains Exact", Filter.Operator.ARRAY_CONTAINS_EXACT, new String[]{filterValue}, + GridConversionResult.kept(filterValue), containsExactMap), + new GridMVTCCase("MVTC→TC Grid - Contains None", Filter.Operator.ARRAY_CONTAINS_NONE, new String[]{filterValue, filterValue2}, + GridConversionResult.kept(filterValue), containsNoneMap) + ); + + log("Creating saved grid views with all MVTC operators on column '" + COL_MULTITEXTCHOICE + "'."); + List viewNames = cases.stream().map(GridMVTCCase::viewName).toList(); + for (GridMVTCCase c : cases) + createMVTCGridView(c.viewName(), c.op(), c.filterVals()); + + log("Converting '" + COL_MULTITEXTCHOICE + "' field of " + DEFAULT_VIEW_SAMPLE_TYPE + " from MVTC to TextChoice."); + enableMVTCFieldMultiSelect(false); + + log("Verifying saved grid views after MVTC → TC conversion."); + verifyGridMVTCCases(cases); + checker().screenShotIfNewError("GridMVTCtoTC_Error"); + + log("Restoring field back to MultiValueTextChoice."); + enableMVTCFieldMultiSelect(true); + + log("Cleaning up MVTC→TC test views."); + cleanupGridViews(beginAtQueryGrid(DEFAULT_VIEW_SAMPLE_TYPE), viewNames); + } + + @Test + public void testCustomGridViewsTCtoMVTC() throws Exception + { + goToProjectHome(); + resetDefaultView(getProjectName(), DEFAULT_VIEW_SAMPLE_TYPE, DEFAULT_COLUMNS); + + log("Converting '" + COL_MULTITEXTCHOICE + "' field of " + DEFAULT_VIEW_SAMPLE_TYPE + " from MVTC to TextChoice for TC→MVTC test setup."); + enableMVTCFieldMultiSelect(false); + + log("Compute expected sample data for TC conversion test."); + Map allSamples = computeSampleMVTCMap(); + String filterValue = TEXT_MULTI_CHOICE_LIST.get(0); + String filterValue2 = TEXT_MULTI_CHOICE_LIST.get(1); + + Map emptyMap = filterSampleMap(allSamples, v -> v == null); + Map nonemptyMap = filterSampleMap(allSamples, v -> v != null); + Map equalsMap = filterSampleMap(allSamples, v -> filterValue.equals(v)); + Map equalsAnyMap = filterSampleMap(allSamples, v -> filterValue.equals(v) || filterValue2.equals(v)); + Map notEqualsMap = filterSampleMap(allSamples, v -> v == null || !filterValue.equals(v)); + + List cases = List.of( + new GridTCCase("TC→MVTC Grid - Is Blank", Filter.Operator.ISBLANK, new String[0], + Filter.Operator.ARRAY_ISEMPTY.getDisplayValue(), emptyMap), + new GridTCCase("TC→MVTC Grid - Is Not Blank", Filter.Operator.NONBLANK, new String[0], + Filter.Operator.ARRAY_ISNOTEMPTY.getDisplayValue(), nonemptyMap), + new GridTCCase("TC→MVTC Grid - Equals", Filter.Operator.EQUAL, new String[]{filterValue}, + Filter.Operator.ARRAY_CONTAINS_EXACT.getDisplayValue(), equalsMap), + new GridTCCase("TC→MVTC Grid - Equals One Of", Filter.Operator.IN, new String[]{filterValue, filterValue2}, + Filter.Operator.ARRAY_CONTAINS_ANY.getDisplayValue(), equalsAnyMap), + new GridTCCase("TC→MVTC Grid - Does Not Equal", Filter.Operator.NEQ, new String[]{filterValue}, + filterValue, notEqualsMap), + new GridTCCase("TC→MVTC Grid - Equals None Of", Filter.Operator.NOT_IN, new String[]{filterValue}, + filterValue, notEqualsMap) + ); + + log("Creating saved grid views with all TC operators on column '" + COL_MULTITEXTCHOICE + "'."); + List viewNames = cases.stream().map(GridTCCase::viewName).toList(); + for (GridTCCase c : cases) + createTCGridView(c.viewName(), c.op(), c.filterVals()); + + log("Converting '" + COL_MULTITEXTCHOICE + "' field of " + DEFAULT_VIEW_SAMPLE_TYPE + " back to MultiValueTextChoice."); + enableMVTCFieldMultiSelect(true); + + log("Verifying saved grid views after TC → MVTC conversion."); + verifyGridTCCases(cases); + checker().screenShotIfNewError("GridTCtoMVTC_Error"); + + log("Cleaning up TC→MVTC test views."); + cleanupGridViews(beginAtQueryGrid(DEFAULT_VIEW_SAMPLE_TYPE), viewNames); + } + + @Test + public void testCustomGridViewsMVTCtoText() throws Exception + { + goToProjectHome(); + resetDefaultView(getProjectName(), DEFAULT_VIEW_SAMPLE_TYPE, DEFAULT_COLUMNS); + + log("Compute expected sample data for MVTC→Text conversion test."); + Map allSamples = computeSampleMVTCMap(); + String filterValue = TEXT_MULTI_CHOICE_LIST.get(0); + String filterValue2 = TEXT_MULTI_CHOICE_LIST.get(1); + + Map emptyMap = filterSampleMap(allSamples, v -> v == null); + Map nonemptyMap = filterSampleMap(allSamples, v -> v != null); + Map containsAnyMap = filterSampleMap(allSamples, v -> filterValue.equals(v) || filterValue2.equals(v)); + Map containsExactMap = filterSampleMap(allSamples, v -> filterValue.equals(v)); + Map containsNotEqMap = filterSampleMap(allSamples, v -> v == null || !filterValue.equals(v)); + Map containsNoneMap = filterSampleMap(allSamples, v -> v == null || (!filterValue.equals(v) && !filterValue2.equals(v))); + + List cases = List.of( + new GridMVTCCase("MVTC→Str Grid - Is Empty", Filter.Operator.ARRAY_ISEMPTY, new String[0], + GridConversionResult.kept(Filter.Operator.ISBLANK.getDisplayValue()), emptyMap), + new GridMVTCCase("MVTC→Str Grid - Contains Any", Filter.Operator.ARRAY_CONTAINS_ANY, new String[]{filterValue, filterValue2}, + GridConversionResult.kept(Filter.Operator.IN.getDisplayValue()), containsAnyMap), + new GridMVTCCase("MVTC→Str Grid - Contains All", Filter.Operator.ARRAY_CONTAINS_ALL, new String[]{filterValue, filterValue2}, + GridConversionResult.dropped(), containsAnyMap), + new GridMVTCCase("MVTC→Str Grid - Is Not Empty", Filter.Operator.ARRAY_ISNOTEMPTY, new String[0], + GridConversionResult.kept(Filter.Operator.NONBLANK.getDisplayValue()), nonemptyMap), + new GridMVTCCase("MVTC→Str Grid - Contains Not Exact", Filter.Operator.ARRAY_CONTAINS_NOT_EXACT, new String[]{filterValue}, + GridConversionResult.dropped(), containsNotEqMap), + new GridMVTCCase("MVTC→Str Grid - Contains Exact", Filter.Operator.ARRAY_CONTAINS_EXACT, new String[]{filterValue}, + GridConversionResult.dropped(), containsExactMap), + new GridMVTCCase("MVTC→Str Grid - Contains None", Filter.Operator.ARRAY_CONTAINS_NONE, new String[]{filterValue, filterValue2}, + GridConversionResult.kept(filterValue), containsNoneMap) + ); + + log("Creating saved grid views with all MVTC operators on column '" + COL_MULTITEXTCHOICE + "'."); + List viewNames = cases.stream().map(GridMVTCCase::viewName).toList(); + for (GridMVTCCase c : cases) + createMVTCGridView(c.viewName(), c.op(), c.filterVals()); + + log("Converting '" + COL_MULTITEXTCHOICE + "' field of " + DEFAULT_VIEW_SAMPLE_TYPE + " from MVTC to plain String."); + changeMVTCFieldToText(); + + log("Verifying saved grid views after MVTC → String conversion."); + verifyGridMVTCCases(cases); + checker().screenShotIfNewError("GridMVTCtoStr_Error"); + + log("Restoring field back to MultiValueTextChoice."); + changeTextFieldToMVTC(); + + log("Cleaning up MVTC→String test views."); + cleanupGridViews(beginAtQueryGrid(DEFAULT_VIEW_SAMPLE_TYPE), viewNames); + } + + // @Test + public void testCustomGridViewsMVTCSubFolderConversion() throws Exception + { + String subFolderPath = getProjectName() + "/" + GRID_SUB_FOLDER; + log("Verify saved views created in sub folder survive MVTC→TC→MVTC→String conversions."); + goToProjectHome(getProjectName()+"/"+GRID_SUB_FOLDER); + resetDefaultView(subFolderPath, DEFAULT_VIEW_SAMPLE_TYPE, DEFAULT_COLUMNS); + + String filterValue = TEXT_MULTI_CHOICE_LIST.get(0); + String filterValue2 = TEXT_MULTI_CHOICE_LIST.get(1); + + List cases = List.of( + new GridSubFolderCase("SF Grid - Is Empty", Filter.Operator.ARRAY_ISEMPTY, new String[0], + GridConversionResult.kept(Filter.Operator.ISBLANK.getDisplayValue()), + GridConversionResult.kept(Filter.Operator.ARRAY_ISEMPTY.getDisplayValue()), + GridConversionResult.kept(Filter.Operator.ISBLANK.getDisplayValue())), + + new GridSubFolderCase("SF Grid - Contains Any", Filter.Operator.ARRAY_CONTAINS_ANY, new String[]{filterValue, filterValue2}, + GridConversionResult.kept(Filter.Operator.IN.getDisplayValue()), + GridConversionResult.kept(Filter.Operator.ARRAY_CONTAINS_ANY.getDisplayValue()), + GridConversionResult.dropped()), + + new GridSubFolderCase("SF Grid - Contains All", Filter.Operator.ARRAY_CONTAINS_ALL, new String[]{filterValue, filterValue2}, + GridConversionResult.dropped(), + GridConversionResult.dropped(), + GridConversionResult.dropped()), + + new GridSubFolderCase("SF Grid - Is Not Empty", Filter.Operator.ARRAY_ISNOTEMPTY, new String[0], + GridConversionResult.kept(Filter.Operator.NONBLANK.getDisplayValue()), + GridConversionResult.kept(Filter.Operator.ARRAY_ISNOTEMPTY.getDisplayValue()), + GridConversionResult.kept(Filter.Operator.NONBLANK.getDisplayValue())), + + new GridSubFolderCase("SF Grid - Contains Not Exact", Filter.Operator.ARRAY_CONTAINS_NOT_EXACT, new String[]{filterValue}, + GridConversionResult.kept(filterValue), + GridConversionResult.kept(filterValue), + GridConversionResult.dropped()), + + new GridSubFolderCase("SF Grid - Contains Exact", Filter.Operator.ARRAY_CONTAINS_EXACT, new String[]{filterValue}, + GridConversionResult.kept(filterValue), + GridConversionResult.kept(Filter.Operator.ARRAY_CONTAINS_EXACT.getDisplayValue()), + GridConversionResult.dropped()), + + new GridSubFolderCase("SF Grid - Contains None", Filter.Operator.ARRAY_CONTAINS_NONE, new String[]{filterValue, filterValue2}, + GridConversionResult.kept(filterValue), + GridConversionResult.kept(filterValue), + GridConversionResult.kept(filterValue)) + ); + + log("Creating saved grid views in sub folder with all MVTC operators on column '" + COL_MULTITEXTCHOICE + "'."); + List viewNames = cases.stream().map(GridSubFolderCase::viewName).toList(); + for (GridSubFolderCase c : cases) + createMVTCGridViewInFolder(subFolderPath, c.viewName(), c.op(), c.filterVals()); + + log("Converting '" + COL_MULTITEXTCHOICE + "' field from MVTC to TextChoice."); + enableMVTCFieldMultiSelect(false); + + log("Verifying sub folder views after MVTC → TC conversion."); + verifyGridSubFolderPhase(subFolderPath, cases, GridSubFolderCase::resultTC); + checker().screenShotIfNewError("SF_GridMVTCtoTC_Error"); + + log("Converting '" + COL_MULTITEXTCHOICE + "' field back to MultiValueTextChoice."); + enableMVTCFieldMultiSelect(true); + + log("Verifying sub folder views after TC → MVTC conversion."); + verifyGridSubFolderPhase(subFolderPath, cases, GridSubFolderCase::resultMVTC); + checker().screenShotIfNewError("SF_GridTCtoMVTC_Error"); + + log("Converting '" + COL_MULTITEXTCHOICE + "' field from MVTC to plain String."); + changeMVTCFieldToText(); + + log("Verifying sub folder views after MVTC → String conversion."); + verifyGridSubFolderPhase(subFolderPath, cases, GridSubFolderCase::resultText); + checker().screenShotIfNewError("SF_GridMVTCtoStr_Error"); + + log("Restoring field back to MultiValueTextChoice."); + changeTextFieldToMVTC(); + + log("Cleaning up sub folder views."); + cleanupGridViews(beginAtQueryGridInFolder(subFolderPath), viewNames); + } + + /** + * Builds a map of sample name → expected MVTC column value for DEFAULT_VIEW_SAMPLE_TYPE. + * Samples with sampleId % 5 == 0 have a {@code null} (empty) value; others get one + * value from {@link #TEXT_MULTI_CHOICE_LIST} determined by the hash of the sample name. + */ + private static Map computeSampleMVTCMap() + { + Map map = new LinkedHashMap<>(); + for (int i = 1; i <= DEFAULT_VIEW_SAMPLE_TYPE_SIZE; i++) + { + String name = DEFAULT_VIEW_SAMPLE_PREFIX + i; + String value = (i % 5 == 0) ? null + : TEXT_MULTI_CHOICE_LIST.get(Math.abs(name.hashCode()) % TEXT_MULTI_CHOICE_LIST.size()); + map.put(name, value); + } + return map; + } + + /** + * Returns a sub-map keeping only entries whose value satisfies {@code valuePredicate}. + */ + private static Map filterSampleMap(Map allSamples, + Predicate valuePredicate) + { + // Collectors.toMap rejects null values, so populate the map manually. + Map result = new LinkedHashMap<>(); + allSamples.entrySet().stream() + .filter(e -> valuePredicate.test(e.getValue())) + .forEach(e -> result.put(e.getKey(), e.getValue())); + return result; + } + + /** + * Navigates to DEFAULT_VIEW_SAMPLE_TYPE grid, applies an MVTC-type array filter via the + * grid filter dialog, and saves the result as a personal named view. + */ + private void createMVTCGridView(String viewName, Filter.Operator op, String... vals) + { + QueryGrid grid = beginAtQueryGrid(DEFAULT_VIEW_SAMPLE_TYPE); + GridFilterModal filterDialog = grid.getGridBar().openFilterDialog(); + filterDialog.selectField(COL_MULTITEXTCHOICE); + FilterFacetedPanel facetPanel = filterDialog.selectFacetTab(); + facetPanel.selectArrayFilterOperator(op); + if (vals.length > 0) + facetPanel.checkValues(vals); + filterDialog.confirm(); + + grid.saveView() + .setMakeCustom() + .setViewName(viewName) + .setMakeShared(false) + .saveView(); + } + + /** + * Navigates to DEFAULT_VIEW_SAMPLE_TYPE grid (with TextChoice field), applies a TC filter + * via the expression tab, and saves the result as a personal named view. + */ + /** + * Navigates to DEFAULT_VIEW_SAMPLE_TYPE grid (with TextChoice field), applies a TC filter, + * and saves the result as a personal named view. + * For {@link Filter.Operator#IN}, values are selected via the facet tab (supporting multiple + * values); all other operators use the expression tab. + */ + private void createTCGridView(String viewName, Filter.Operator op, String... vals) + { + QueryGrid grid = beginAtQueryGrid(DEFAULT_VIEW_SAMPLE_TYPE); + GridFilterModal filterDialog = grid.getGridBar().openFilterDialog(); + filterDialog.selectField(COL_MULTITEXTCHOICE); + if (op == Filter.Operator.IN) + { + FilterFacetedPanel filterFacetedPanel = filterDialog.selectFacetTab(); + filterFacetedPanel.uncheckValues("[All]"); + filterFacetedPanel.checkValues(vals); + } + else if (vals.length > 0) + { + filterDialog.selectExpressionTab().setFilter(new FilterExpressionPanel.Expression(op, vals[0])); + } + else + { + filterDialog.selectExpressionTab().setFilter(new FilterExpressionPanel.Expression(op)); + } + filterDialog.confirm(); + + grid.saveView() + .setMakeCustom() + .setViewName(viewName) + .setMakeShared(false) + .saveView(); + } + + /** + * Toggles 'Allow Multiple Selections' on the {@value #COL_MULTITEXTCHOICE} field of + * DEFAULT_VIEW_SAMPLE_TYPE. Pass {@code false} for MVTC→TC, {@code true} for TC→MVTC. + */ + private void enableMVTCFieldMultiSelect(boolean enable) + { + UpdateSampleTypePage updatePage = beginAtSampleTypesList(this, getProjectName()) + .goToEditSampleType(DEFAULT_VIEW_SAMPLE_TYPE); + updatePage.getFieldsPanel() + .getField(COL_MULTITEXTCHOICE) + .expand() + .setAllowMultipleSelections(enable); + updatePage.clickSave(); + } + + /** + * Changes the {@value #COL_MULTITEXTCHOICE} field of DEFAULT_VIEW_SAMPLE_TYPE from MVTC + * to plain String type (MVTC → Text conversion). + */ + private void changeMVTCFieldToText() + { + UpdateSampleTypePage updatePage = beginAtSampleTypesList(this, getProjectName()) + .goToEditSampleType(DEFAULT_VIEW_SAMPLE_TYPE); + updatePage.getFieldsPanel() + .getField(COL_MULTITEXTCHOICE) + .expand() + .setType(FieldDefinition.ColumnType.String, true); + updatePage.clickSave(); + } + + /** + * Restores the {@value #COL_MULTITEXTCHOICE} field of DEFAULT_VIEW_SAMPLE_TYPE from plain + * String back to MultiValueTextChoice (Text → MVTC restoration). + */ + private void changeTextFieldToMVTC() + { + UpdateSampleTypePage updatePage = beginAtSampleTypesList(this, getProjectName()) + .goToEditSampleType(DEFAULT_VIEW_SAMPLE_TYPE); + updatePage.getFieldsPanel() + .getField(COL_MULTITEXTCHOICE) + .expand() + .setType(FieldDefinition.ColumnType.MultiValueTextChoice, false) + .setTextChoiceValues(TEXT_MULTI_CHOICE_LIST) + .setAllowMultipleSelections(true); + updatePage.clickSave(); + } + + /** + * Verifies all MVTC cases after a type conversion. + * For kept filters: checks the filter pill text and (when row count fits on one page) data. + * For dropped filters: checks that no filter pills are shown. + */ + private void verifyGridMVTCCases(List cases) + { + for (GridMVTCCase c : cases) + { + if (c.conversionResult().isDropped()) + verifyDroppedGridView(c.viewName()); + else + { + QueryGrid grid = verifyGridViewFilter(c.viewName(), c.conversionResult().expectedFilterText()); + if (c.expectedSampleMap().size() <= DEFAULT_PAGE_SIZE) + TestArrayDataUtils.verifyMVTCResults(grid, c.expectedSampleMap(), COL_NAME, COL_MULTITEXTCHOICE, checker()); + } + } + } + + /** + * Verifies all TC→MVTC cases: checks filter pill text and (when small enough) data. + */ + private void verifyGridTCCases(List cases) + { + for (GridTCCase c : cases) + { + QueryGrid grid = verifyGridViewFilter(c.viewName(), c.expectedAfterConversionText()); + if (c.expectedSampleMap().size() <= DEFAULT_PAGE_SIZE) + TestArrayDataUtils.verifyMVTCResults(grid, c.expectedSampleMap(), COL_NAME, COL_MULTITEXTCHOICE, checker()); + } + } + + /** + * Navigates to the DEFAULT_VIEW_SAMPLE_TYPE grid, selects the named view, and verifies + * that at least one filter pill text contains {@code expectedFilterText}. + * + * @return the QueryGrid showing the named view, for further data assertions. + */ + private QueryGrid verifyGridViewFilter(String viewName, String expectedFilterText) + { + QueryGrid grid = beginAtQueryGrid(DEFAULT_VIEW_SAMPLE_TYPE); + grid.selectView(viewName); + List pillTexts = grid.getFilterStatusValuesText(); + checker().withScreenshot().verifyTrue( + "View '" + viewName + "': filter pill should contain '" + expectedFilterText + "'", + pillTexts.stream().anyMatch(pill -> pill.contains(expectedFilterText))); + return grid; + } + + /** + * Verifies that a saved view whose filter was dropped after type conversion shows no + * filter pills (i.e., the grid displays all samples without restriction). + */ + private void verifyDroppedGridView(String viewName) + { + QueryGrid grid = beginAtQueryGrid(DEFAULT_VIEW_SAMPLE_TYPE); + grid.selectView(viewName); + checker().withScreenshot().verifyTrue( + "View '" + viewName + "': dropped filter should not show any filter pills", + grid.getFilterStatusValues().isEmpty()); + } + + /** + * Deletes the named views from the DEFAULT_VIEW_SAMPLE_TYPE grid's Manage Views dialog. + * Views that are no longer present (e.g., already deleted) are silently skipped. + */ + private void cleanupGridViews(QueryGrid grid, List viewNames) + { + ManageViewsDialog dialog = grid.manageViews(); + List existingViews = dialog.getViewNames(); + for (String name : viewNames) + { + if (existingViews.stream().anyMatch(v -> v.equals(name))) + dialog.deleteView(name).confirmDelete(); + } + dialog.dismiss("Done"); + } + + /** + * Navigates to the DEFAULT_VIEW_SAMPLE_TYPE grid in the given container, clears any transient + * state (filters, search, selections), and returns the grid. + */ + private QueryGrid beginAtQueryGridInFolder(String containerPath) + { + QueryGrid grid = CoreComponentsTestPage.beginAt(this, containerPath) + .getGridPanel(TEST_SCHEMA, DEFAULT_VIEW_SAMPLE_TYPE); + grid.clearFilters(); + grid.clearSearch(); + grid.clearAllSelections(); + return grid; + } + + /** + * Navigates to the DEFAULT_VIEW_SAMPLE_TYPE grid in {@code containerPath}, applies an MVTC + * array filter on {@link #COL_MULTITEXTCHOICE}, and saves the result as a personal named view + * scoped to that container. + */ + private void createMVTCGridViewInFolder(String containerPath, String viewName, + Filter.Operator op, String... vals) + { + QueryGrid grid = beginAtQueryGridInFolder(containerPath); + GridFilterModal filterDialog = grid.getGridBar().openFilterDialog(); + filterDialog.selectField(COL_MULTITEXTCHOICE); + FilterFacetedPanel facetPanel = filterDialog.selectFacetTab(); + facetPanel.selectArrayFilterOperator(op); + if (vals.length > 0) + facetPanel.checkValues(vals); + filterDialog.confirm(); + + grid.saveView() + .setMakeCustom() + .setViewName(viewName) + .setMakeShared(false) + .saveView(); + } + + /** + * For each case in {@code cases}, navigates to the DEFAULT_VIEW_SAMPLE_TYPE grid in the + * subfolder, selects the saved view, and verifies that the filter state matches the + * {@link GridConversionResult} extracted by {@code resultExtractor}: + *
    + *
  • dropped result → no filter pills shown
  • + *
  • kept result → at least one filter pill contains the expected text
  • + *
+ */ + private void verifyGridSubFolderPhase(String containerPath, List cases, + Function resultExtractor) + { + for (GridSubFolderCase c : cases) + { + GridConversionResult result = resultExtractor.apply(c); + QueryGrid grid = beginAtQueryGridInFolder(containerPath); + grid.selectView(c.viewName()); + if (result.isDropped()) + { + checker().withScreenshot().verifyTrue( + "Sub-folder view '" + c.viewName() + "': dropped filter should show no filter pills", + grid.getFilterStatusValues().isEmpty()); + } + else + { + List pillTexts = grid.getFilterStatusValuesText(); + checker().withScreenshot().verifyTrue( + "Sub-folder view '" + c.viewName() + "': filter pill should contain '" + result.expectedFilterText() + "'", + pillTexts.stream().anyMatch(pill -> pill.contains(result.expectedFilterText()))); + } + } + } + + // ===== end MVTC conversion helpers ===== + /** * Helper to validate the 'Views' menu. * @@ -1578,7 +2172,7 @@ public void testGridViewAuditEvents() throws Exception { goToProjectHome(); - resetDefaultView(VIEW_DIALOG_ST, DEFAULT_COLUMNS); + resetDefaultView(getProjectName(), VIEW_DIALOG_ST, DEFAULT_COLUMNS); // Part A: Verify audit event on Create diff --git a/src/org/labkey/test/util/data/TestArrayDataUtils.java b/src/org/labkey/test/util/data/TestArrayDataUtils.java index 7db7e07e42..2bac3ff78a 100644 --- a/src/org/labkey/test/util/data/TestArrayDataUtils.java +++ b/src/org/labkey/test/util/data/TestArrayDataUtils.java @@ -134,22 +134,24 @@ private static boolean isMatch(List actualValues, List searchVal } /** - * Verifies that the grid contains exactly the expected sample IDs and that each sample's MVTC column + * Verifies that the grid contains exactly the expected row IDs and that each row's MVTC column * value matches the expected value in {@code sampleMVTCMap}. * Size mismatch is a hard failure; ID and per-row value checks are soft (collected via {@code checker}). + * + * @param idColumn the column label used to identify rows (e.g. "Sample ID" or "Name") */ public static void verifyMVTCResults(QueryGrid grid, Map sampleMVTCMap, - String colLabel, String stepMessage, DeferredErrorCollector checker) + String idColumn, String colLabel, DeferredErrorCollector checker) { - List foundIds = grid.getColumnDataAsText("Sample ID"); - Assert.assertEquals(stepMessage + ": size mismatch", sampleMVTCMap.size(), foundIds.size()); + List foundIds = grid.getColumnDataAsText(idColumn); + Assert.assertEquals("grid row count mismatch", sampleMVTCMap.size(), foundIds.size()); checker.wrapAssertion(() -> assertThat(foundIds) - .as(stepMessage + ": Sample IDs") + .as(idColumn + " values in grid") .containsExactlyInAnyOrderElementsOf(sampleMVTCMap.keySet())); - sampleMVTCMap.forEach((sampleId, expected) -> { - Map rowMap = grid.getRowMapByLabel("Sample ID", sampleId); + sampleMVTCMap.forEach((id, expected) -> { + Map rowMap = grid.getRowMapByLabel(idColumn, id); checker.wrapAssertion(() -> assertThat(rowMap.get(colLabel)) - .as("%s: %s for sample %s", stepMessage, colLabel, sampleId) + .as("'%s' value for %s '%s'", colLabel, idColumn, id) .isEqualTo(expected == null ? "" : expected)); }); } From a0c65e8747e1bebf9e47cd9173bc67059b9514b4 Mon Sep 17 00:00:00 2001 From: Daria Bodiakova <70635654+DariaBod@users.noreply.github.com> Date: Fri, 17 Apr 2026 12:48:37 -0700 Subject: [PATCH 5/7] small fixes --- .../test/tests/component/GridPanelViewTest.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/org/labkey/test/tests/component/GridPanelViewTest.java b/src/org/labkey/test/tests/component/GridPanelViewTest.java index ce04ee9a7b..d150abceac 100644 --- a/src/org/labkey/test/tests/component/GridPanelViewTest.java +++ b/src/org/labkey/test/tests/component/GridPanelViewTest.java @@ -1615,7 +1615,7 @@ public void testCustomGridViewsMVTCtoText() throws Exception cleanupGridViews(beginAtQueryGrid(DEFAULT_VIEW_SAMPLE_TYPE), viewNames); } - // @Test + @Test public void testCustomGridViewsMVTCSubFolderConversion() throws Exception { String subFolderPath = getProjectName() + "/" + GRID_SUB_FOLDER; @@ -1635,7 +1635,7 @@ public void testCustomGridViewsMVTCSubFolderConversion() throws Exception new GridSubFolderCase("SF Grid - Contains Any", Filter.Operator.ARRAY_CONTAINS_ANY, new String[]{filterValue, filterValue2}, GridConversionResult.kept(Filter.Operator.IN.getDisplayValue()), GridConversionResult.kept(Filter.Operator.ARRAY_CONTAINS_ANY.getDisplayValue()), - GridConversionResult.dropped()), + GridConversionResult.kept(Filter.Operator.IN.getDisplayValue())), new GridSubFolderCase("SF Grid - Contains All", Filter.Operator.ARRAY_CONTAINS_ALL, new String[]{filterValue, filterValue2}, GridConversionResult.dropped(), @@ -1670,6 +1670,7 @@ public void testCustomGridViewsMVTCSubFolderConversion() throws Exception log("Converting '" + COL_MULTITEXTCHOICE + "' field from MVTC to TextChoice."); enableMVTCFieldMultiSelect(false); + refresh(); log("Verifying sub folder views after MVTC → TC conversion."); verifyGridSubFolderPhase(subFolderPath, cases, GridSubFolderCase::resultTC); @@ -1677,6 +1678,7 @@ public void testCustomGridViewsMVTCSubFolderConversion() throws Exception log("Converting '" + COL_MULTITEXTCHOICE + "' field back to MultiValueTextChoice."); enableMVTCFieldMultiSelect(true); + refresh(); log("Verifying sub folder views after TC → MVTC conversion."); verifyGridSubFolderPhase(subFolderPath, cases, GridSubFolderCase::resultMVTC); @@ -1684,6 +1686,7 @@ public void testCustomGridViewsMVTCSubFolderConversion() throws Exception log("Converting '" + COL_MULTITEXTCHOICE + "' field from MVTC to plain String."); changeMVTCFieldToText(); + refresh(); log("Verifying sub folder views after MVTC → String conversion."); verifyGridSubFolderPhase(subFolderPath, cases, GridSubFolderCase::resultText); @@ -1972,21 +1975,19 @@ private void verifyGridSubFolderPhase(String containerPath, List pillTexts = grid.getFilterStatusValuesText(); + List filterTexts = grid.getFilterStatusValuesText(); checker().withScreenshot().verifyTrue( - "Sub-folder view '" + c.viewName() + "': filter pill should contain '" + result.expectedFilterText() + "'", - pillTexts.stream().anyMatch(pill -> pill.contains(result.expectedFilterText()))); + "Sub-folder view '" + c.viewName() + "': filter should contain '" + result.expectedFilterText() + "'", + filterTexts.stream().anyMatch(pill -> pill.contains(result.expectedFilterText()))); } } } - // ===== end MVTC conversion helpers ===== - /** * Helper to validate the 'Views' menu. * From c4b56082af635bf4b4ed7e1e2de887c33dbdfc89 Mon Sep 17 00:00:00 2001 From: Daria Bodiakova <70635654+DariaBod@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:49:05 -0700 Subject: [PATCH 6/7] subfolder test move to LKSM some fixes --- .../tests/component/GridPanelViewTest.java | 258 +++--------------- 1 file changed, 40 insertions(+), 218 deletions(-) diff --git a/src/org/labkey/test/tests/component/GridPanelViewTest.java b/src/org/labkey/test/tests/component/GridPanelViewTest.java index d150abceac..eb3466fe2d 100644 --- a/src/org/labkey/test/tests/component/GridPanelViewTest.java +++ b/src/org/labkey/test/tests/component/GridPanelViewTest.java @@ -2,6 +2,7 @@ import org.assertj.core.api.Assertions; import org.jetbrains.annotations.Nullable; +import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -15,6 +16,7 @@ import org.labkey.test.Locator; import org.labkey.test.SortDirection; import org.labkey.test.WebTestHelper; +import org.labkey.test.WebTestHelper.DatabaseType; import org.labkey.test.categories.Daily; import org.labkey.test.components.CustomizeView; import org.labkey.test.components.bootstrap.ModalDialog; @@ -27,7 +29,6 @@ import org.labkey.test.components.ui.search.FilterExpressionPanel; import org.labkey.test.components.ui.search.FilterFacetedPanel; import org.labkey.test.pages.experiment.UpdateSampleTypePage; -import org.labkey.test.pages.test.CoreComponentsTestPage; import org.labkey.test.params.FieldDefinition; import org.labkey.test.params.FieldKey; import org.labkey.test.params.experiment.SampleTypeDefinition; @@ -36,7 +37,6 @@ import org.labkey.test.util.AuditLogHelper; import org.labkey.test.util.DataRegionTable; import org.labkey.test.util.PermissionsHelper; -import org.labkey.test.util.PortalHelper; import org.labkey.test.util.SampleTypeHelper; import org.labkey.test.util.TestDataGenerator; import org.labkey.test.util.data.TestArrayDataUtils; @@ -52,11 +52,11 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.function.Function; import java.util.function.Predicate; import static org.labkey.test.util.PermissionsHelper.FOLDER_ADMIN_ROLE; import static org.labkey.test.util.SampleTypeHelper.beginAtSampleTypesList; +import static org.labkey.test.util.TestDataGenerator.randomFieldName; import static org.labkey.test.util.TestDataGenerator.randomTextChoice; @Category({Daily.class}) @@ -73,11 +73,6 @@ public class GridPanelViewTest extends GridPanelBaseTest private static final int VIEW_DIALOG_ST_SIZE = 100; private static final String VIEW_DIALOG_ST_PREFIX = "DLG-"; - // Samples in the subfolder. - private static final String GRID_SUB_FOLDER = "Sub Folder For Grid Views"; - private static final int SUBFOLDER_VIEW_SAMPLE_TYPE_SIZE = 30; - private static final String SUBFOLDER_VIEW_SAMPLE_PREFIX = "SUBFOLDER_VIEW-"; - // Column names. private static final String COL_NAME = "Name"; private static final String COL_STRING1 = "Str1"; @@ -85,9 +80,12 @@ public class GridPanelViewTest extends GridPanelBaseTest private static final String COL_INT = "Int"; private static final String COL_BOOL = "Bool"; public static final List TEXT_MULTI_CHOICE_LIST = randomTextChoice(10); - public static final String COL_MULTITEXTCHOICE = "Multi Choice"; + public static final String COL_MULTITEXTCHOICE = randomFieldName("Multi Choice", 20); - private static final List DEFAULT_COLUMNS = Arrays.asList(COL_NAME, COL_INT, COL_STRING1, COL_STRING2, COL_BOOL, COL_MULTITEXTCHOICE); + private static final boolean MULTI_CHOICE_ENABLED = WebTestHelper.getDatabaseType() == DatabaseType.PostgreSQL; + private static final List DEFAULT_COLUMNS = MULTI_CHOICE_ENABLED + ? Arrays.asList(COL_NAME, COL_INT, COL_STRING1, COL_STRING2, COL_BOOL, COL_MULTITEXTCHOICE) + : Arrays.asList(COL_NAME, COL_INT, COL_STRING1, COL_STRING2, COL_BOOL); // Will keep track of state of the columns, that is are they filtered, sorted, or have no modifiers. private static Map defaultColumnState = new HashMap<>(); @@ -113,8 +111,6 @@ public class GridPanelViewTest extends GridPanelBaseTest private final AuditLogHelper _auditLogHelper = new AuditLogHelper(this); - // --- MVTC conversion test support --- - private record GridConversionResult(boolean isDropped, @Nullable String expectedFilterText) { static GridConversionResult kept(String filterText) { return new GridConversionResult(false, filterText); } @@ -129,12 +125,6 @@ private record GridTCCase(String viewName, Filter.Operator op, String[] filterVa String expectedAfterConversionText, Map expectedSampleMap) {} - /** Holds a single case for the subfolder round-trip test (MVTC → TC → MVTC → Text). */ - private record GridSubFolderCase(String viewName, Filter.Operator op, String[] filterVals, - GridConversionResult resultTC, - GridConversionResult resultMVTC, - GridConversionResult resultText) {} - // Tests that need to be written: // Validate "Save As..." from the grid save button. // Validate views that are locked, or in some other way, cannot be updates in the manage views dialog. @@ -161,13 +151,6 @@ public static void setupProject() throws IOException, CommandException init.doSetup(); } - - static void main() - { - List randomTextChoice = randomTextChoice(10); - System.out.println("Done"); - } - private void doSetup() throws IOException, CommandException { initProject(); @@ -179,12 +162,14 @@ private void doSetup() throws IOException, CommandException // Create a sample type that will validate views can be saved and shared. Primarily interested in the views not // with complex filtering scenarios. - List fields = Arrays.asList(new FieldDefinition(COL_INT, FieldDefinition.ColumnType.Integer), + List fields = new ArrayList<>(Arrays.asList( + new FieldDefinition(COL_INT, FieldDefinition.ColumnType.Integer), new FieldDefinition(COL_STRING1, FieldDefinition.ColumnType.String), new FieldDefinition(COL_STRING2, FieldDefinition.ColumnType.String), - new FieldDefinition(COL_BOOL, FieldDefinition.ColumnType.Boolean), - new FieldDefinition(COL_MULTITEXTCHOICE, FieldDefinition.ColumnType.MultiValueTextChoice) - .setMultiChoiceValues(TEXT_MULTI_CHOICE_LIST)); + new FieldDefinition(COL_BOOL, FieldDefinition.ColumnType.Boolean))); + if (MULTI_CHOICE_ENABLED) + fields.add(new FieldDefinition(COL_MULTITEXTCHOICE, FieldDefinition.ColumnType.MultiValueTextChoice) + .setMultiChoiceValues(TEXT_MULTI_CHOICE_LIST)); createSampleType(VIEW_DIALOG_ST, VIEW_DIALOG_ST_PREFIX, VIEW_DIALOG_ST_SIZE, fields); @@ -193,14 +178,6 @@ private void doSetup() throws IOException, CommandException _userHelper.createUser(OTHER_USER, true,false); new ApiPermissionsHelper(this).addMemberToRole(OTHER_USER, FOLDER_ADMIN_ROLE, PermissionsHelper.MemberType.user, getProjectName()); - PortalHelper portalHelper = new PortalHelper(this); - _containerHelper.createSubfolder(getProjectName(), GRID_SUB_FOLDER); - portalHelper.addWebPart("Sample Types"); - portalHelper.exitAdminMode(); - - TestDataGenerator sampleSetDataGeneratorForSubfolder = new TestDataGenerator("samples", DEFAULT_VIEW_SAMPLE_TYPE, getProjectName() + "/" + GRID_SUB_FOLDER); - generateSamples(sampleSetDataGeneratorForSubfolder, SUBFOLDER_VIEW_SAMPLE_PREFIX, SUBFOLDER_VIEW_SAMPLE_TYPE_SIZE); - sampleSetDataGeneratorForSubfolder.insertRows(); removeFlagColumnFromDefaultView(DEFAULT_VIEW_SAMPLE_TYPE); } @@ -233,13 +210,19 @@ private void generateSamples(TestDataGenerator sampleSetDataGenerator, String sa if(memIndex == stringSetMembers.size()) memIndex = 0; String name = String.format("%s%d", samplePrefix, sampleId); - sampleSetDataGenerator.addCustomRow( - Map.of(COL_NAME, name, - COL_INT, sampleId, - COL_STRING1, stringSets.get(allPossibleIndex), - COL_STRING2, stringSetMembers.get(memIndex), - COL_BOOL, sampleId % 2 == 0, - COL_MULTITEXTCHOICE, sampleId % 5 == 0 ? List.of() : List.of(TEXT_MULTI_CHOICE_LIST.get(Math.abs(name.hashCode()) % TEXT_MULTI_CHOICE_LIST.size())))); + Map rowData = new HashMap<>(); + rowData.put(COL_NAME, name); + rowData.put(COL_INT, sampleId); + rowData.put(COL_STRING1, stringSets.get(allPossibleIndex)); + rowData.put(COL_STRING2, stringSetMembers.get(memIndex)); + rowData.put(COL_BOOL, sampleId % 2 == 0); + if (MULTI_CHOICE_ENABLED) + { + rowData.put(COL_MULTITEXTCHOICE, sampleId % 5 == 0 + ? List.of() + : List.of(TEXT_MULTI_CHOICE_LIST.get(Math.abs(name.hashCode()) % TEXT_MULTI_CHOICE_LIST.size()))); + } + sampleSetDataGenerator.addCustomRow(rowData); allPossibleIndex++; memIndex++; @@ -1459,11 +1442,12 @@ public void testWarningOnInvalidDateFilter() throws Exception @Test public void testCustomGridViewsMVTCtoTC() throws Exception { + Assume.assumeTrue("Multi-choice text fields are only supported on PostgreSQL", MULTI_CHOICE_ENABLED); goToProjectHome(); resetDefaultView(getProjectName(), DEFAULT_VIEW_SAMPLE_TYPE, DEFAULT_COLUMNS); log("Compute expected sample data for MVTC conversion test."); - Map allSamples = computeSampleMVTCMap(); + Map allSamples = createSampleMVTCMap(); String filterValue = TEXT_MULTI_CHOICE_LIST.get(0); String filterValue2 = TEXT_MULTI_CHOICE_LIST.get(1); @@ -1513,6 +1497,7 @@ public void testCustomGridViewsMVTCtoTC() throws Exception @Test public void testCustomGridViewsTCtoMVTC() throws Exception { + Assume.assumeTrue("Multi-choice text fields are only supported on PostgreSQL", MULTI_CHOICE_ENABLED); goToProjectHome(); resetDefaultView(getProjectName(), DEFAULT_VIEW_SAMPLE_TYPE, DEFAULT_COLUMNS); @@ -1520,7 +1505,7 @@ public void testCustomGridViewsTCtoMVTC() throws Exception enableMVTCFieldMultiSelect(false); log("Compute expected sample data for TC conversion test."); - Map allSamples = computeSampleMVTCMap(); + Map allSamples = createSampleMVTCMap(); String filterValue = TEXT_MULTI_CHOICE_LIST.get(0); String filterValue2 = TEXT_MULTI_CHOICE_LIST.get(1); @@ -1564,11 +1549,12 @@ public void testCustomGridViewsTCtoMVTC() throws Exception @Test public void testCustomGridViewsMVTCtoText() throws Exception { + Assume.assumeTrue("Multi-choice text fields are only supported on PostgreSQL", MULTI_CHOICE_ENABLED); goToProjectHome(); resetDefaultView(getProjectName(), DEFAULT_VIEW_SAMPLE_TYPE, DEFAULT_COLUMNS); - log("Compute expected sample data for MVTC→Text conversion test."); - Map allSamples = computeSampleMVTCMap(); + log("Create expected sample data for MVTC→Text conversion test."); + Map allSamples = createSampleMVTCMap(); String filterValue = TEXT_MULTI_CHOICE_LIST.get(0); String filterValue2 = TEXT_MULTI_CHOICE_LIST.get(1); @@ -1615,96 +1601,12 @@ public void testCustomGridViewsMVTCtoText() throws Exception cleanupGridViews(beginAtQueryGrid(DEFAULT_VIEW_SAMPLE_TYPE), viewNames); } - @Test - public void testCustomGridViewsMVTCSubFolderConversion() throws Exception - { - String subFolderPath = getProjectName() + "/" + GRID_SUB_FOLDER; - log("Verify saved views created in sub folder survive MVTC→TC→MVTC→String conversions."); - goToProjectHome(getProjectName()+"/"+GRID_SUB_FOLDER); - resetDefaultView(subFolderPath, DEFAULT_VIEW_SAMPLE_TYPE, DEFAULT_COLUMNS); - - String filterValue = TEXT_MULTI_CHOICE_LIST.get(0); - String filterValue2 = TEXT_MULTI_CHOICE_LIST.get(1); - - List cases = List.of( - new GridSubFolderCase("SF Grid - Is Empty", Filter.Operator.ARRAY_ISEMPTY, new String[0], - GridConversionResult.kept(Filter.Operator.ISBLANK.getDisplayValue()), - GridConversionResult.kept(Filter.Operator.ARRAY_ISEMPTY.getDisplayValue()), - GridConversionResult.kept(Filter.Operator.ISBLANK.getDisplayValue())), - - new GridSubFolderCase("SF Grid - Contains Any", Filter.Operator.ARRAY_CONTAINS_ANY, new String[]{filterValue, filterValue2}, - GridConversionResult.kept(Filter.Operator.IN.getDisplayValue()), - GridConversionResult.kept(Filter.Operator.ARRAY_CONTAINS_ANY.getDisplayValue()), - GridConversionResult.kept(Filter.Operator.IN.getDisplayValue())), - - new GridSubFolderCase("SF Grid - Contains All", Filter.Operator.ARRAY_CONTAINS_ALL, new String[]{filterValue, filterValue2}, - GridConversionResult.dropped(), - GridConversionResult.dropped(), - GridConversionResult.dropped()), - - new GridSubFolderCase("SF Grid - Is Not Empty", Filter.Operator.ARRAY_ISNOTEMPTY, new String[0], - GridConversionResult.kept(Filter.Operator.NONBLANK.getDisplayValue()), - GridConversionResult.kept(Filter.Operator.ARRAY_ISNOTEMPTY.getDisplayValue()), - GridConversionResult.kept(Filter.Operator.NONBLANK.getDisplayValue())), - - new GridSubFolderCase("SF Grid - Contains Not Exact", Filter.Operator.ARRAY_CONTAINS_NOT_EXACT, new String[]{filterValue}, - GridConversionResult.kept(filterValue), - GridConversionResult.kept(filterValue), - GridConversionResult.dropped()), - - new GridSubFolderCase("SF Grid - Contains Exact", Filter.Operator.ARRAY_CONTAINS_EXACT, new String[]{filterValue}, - GridConversionResult.kept(filterValue), - GridConversionResult.kept(Filter.Operator.ARRAY_CONTAINS_EXACT.getDisplayValue()), - GridConversionResult.dropped()), - - new GridSubFolderCase("SF Grid - Contains None", Filter.Operator.ARRAY_CONTAINS_NONE, new String[]{filterValue, filterValue2}, - GridConversionResult.kept(filterValue), - GridConversionResult.kept(filterValue), - GridConversionResult.kept(filterValue)) - ); - - log("Creating saved grid views in sub folder with all MVTC operators on column '" + COL_MULTITEXTCHOICE + "'."); - List viewNames = cases.stream().map(GridSubFolderCase::viewName).toList(); - for (GridSubFolderCase c : cases) - createMVTCGridViewInFolder(subFolderPath, c.viewName(), c.op(), c.filterVals()); - - log("Converting '" + COL_MULTITEXTCHOICE + "' field from MVTC to TextChoice."); - enableMVTCFieldMultiSelect(false); - refresh(); - - log("Verifying sub folder views after MVTC → TC conversion."); - verifyGridSubFolderPhase(subFolderPath, cases, GridSubFolderCase::resultTC); - checker().screenShotIfNewError("SF_GridMVTCtoTC_Error"); - - log("Converting '" + COL_MULTITEXTCHOICE + "' field back to MultiValueTextChoice."); - enableMVTCFieldMultiSelect(true); - refresh(); - - log("Verifying sub folder views after TC → MVTC conversion."); - verifyGridSubFolderPhase(subFolderPath, cases, GridSubFolderCase::resultMVTC); - checker().screenShotIfNewError("SF_GridTCtoMVTC_Error"); - - log("Converting '" + COL_MULTITEXTCHOICE + "' field from MVTC to plain String."); - changeMVTCFieldToText(); - refresh(); - - log("Verifying sub folder views after MVTC → String conversion."); - verifyGridSubFolderPhase(subFolderPath, cases, GridSubFolderCase::resultText); - checker().screenShotIfNewError("SF_GridMVTCtoStr_Error"); - - log("Restoring field back to MultiValueTextChoice."); - changeTextFieldToMVTC(); - - log("Cleaning up sub folder views."); - cleanupGridViews(beginAtQueryGridInFolder(subFolderPath), viewNames); - } - /** * Builds a map of sample name → expected MVTC column value for DEFAULT_VIEW_SAMPLE_TYPE. * Samples with sampleId % 5 == 0 have a {@code null} (empty) value; others get one * value from {@link #TEXT_MULTI_CHOICE_LIST} determined by the hash of the sample name. */ - private static Map computeSampleMVTCMap() + private static Map createSampleMVTCMap() { Map map = new LinkedHashMap<>(); for (int i = 1; i <= DEFAULT_VIEW_SAMPLE_TYPE_SIZE; i++) @@ -1745,12 +1647,7 @@ private void createMVTCGridView(String viewName, Filter.Operator op, String... v if (vals.length > 0) facetPanel.checkValues(vals); filterDialog.confirm(); - - grid.saveView() - .setMakeCustom() - .setViewName(viewName) - .setMakeShared(false) - .saveView(); + grid.saveView(viewName); } /** @@ -1784,15 +1681,11 @@ else if (vals.length > 0) } filterDialog.confirm(); - grid.saveView() - .setMakeCustom() - .setViewName(viewName) - .setMakeShared(false) - .saveView(); + grid.saveView(viewName); } /** - * Toggles 'Allow Multiple Selections' on the {@value #COL_MULTITEXTCHOICE} field of + * Toggles 'Allow Multiple Selections' on the COL_MULTITEXTCHOICE field of * DEFAULT_VIEW_SAMPLE_TYPE. Pass {@code false} for MVTC→TC, {@code true} for TC→MVTC. */ private void enableMVTCFieldMultiSelect(boolean enable) @@ -1807,7 +1700,7 @@ private void enableMVTCFieldMultiSelect(boolean enable) } /** - * Changes the {@value #COL_MULTITEXTCHOICE} field of DEFAULT_VIEW_SAMPLE_TYPE from MVTC + * Changes the COL_MULTITEXTCHOICE field of DEFAULT_VIEW_SAMPLE_TYPE from MVTC * to plain String type (MVTC → Text conversion). */ private void changeMVTCFieldToText() @@ -1822,7 +1715,7 @@ private void changeMVTCFieldToText() } /** - * Restores the {@value #COL_MULTITEXTCHOICE} field of DEFAULT_VIEW_SAMPLE_TYPE from plain + * Restores the COL_MULTITEXTCHOICE field of DEFAULT_VIEW_SAMPLE_TYPE from plain * String back to MultiValueTextChoice (Text → MVTC restoration). */ private void changeTextFieldToMVTC() @@ -1917,77 +1810,6 @@ private void cleanupGridViews(QueryGrid grid, List viewNames) dialog.dismiss("Done"); } - /** - * Navigates to the DEFAULT_VIEW_SAMPLE_TYPE grid in the given container, clears any transient - * state (filters, search, selections), and returns the grid. - */ - private QueryGrid beginAtQueryGridInFolder(String containerPath) - { - QueryGrid grid = CoreComponentsTestPage.beginAt(this, containerPath) - .getGridPanel(TEST_SCHEMA, DEFAULT_VIEW_SAMPLE_TYPE); - grid.clearFilters(); - grid.clearSearch(); - grid.clearAllSelections(); - return grid; - } - - /** - * Navigates to the DEFAULT_VIEW_SAMPLE_TYPE grid in {@code containerPath}, applies an MVTC - * array filter on {@link #COL_MULTITEXTCHOICE}, and saves the result as a personal named view - * scoped to that container. - */ - private void createMVTCGridViewInFolder(String containerPath, String viewName, - Filter.Operator op, String... vals) - { - QueryGrid grid = beginAtQueryGridInFolder(containerPath); - GridFilterModal filterDialog = grid.getGridBar().openFilterDialog(); - filterDialog.selectField(COL_MULTITEXTCHOICE); - FilterFacetedPanel facetPanel = filterDialog.selectFacetTab(); - facetPanel.selectArrayFilterOperator(op); - if (vals.length > 0) - facetPanel.checkValues(vals); - filterDialog.confirm(); - - grid.saveView() - .setMakeCustom() - .setViewName(viewName) - .setMakeShared(false) - .saveView(); - } - - /** - * For each case in {@code cases}, navigates to the DEFAULT_VIEW_SAMPLE_TYPE grid in the - * subfolder, selects the saved view, and verifies that the filter state matches the - * {@link GridConversionResult} extracted by {@code resultExtractor}: - *
    - *
  • dropped result → no filter pills shown
  • - *
  • kept result → at least one filter pill contains the expected text
  • - *
- */ - private void verifyGridSubFolderPhase(String containerPath, List cases, - Function resultExtractor) - { - for (GridSubFolderCase c : cases) - { - GridConversionResult result = resultExtractor.apply(c); - QueryGrid grid = beginAtQueryGridInFolder(containerPath); - grid.selectView(c.viewName()); - if (result.isDropped()) - { - checker().withScreenshot().verifyTrue( - "Sub-folder view '" + c.viewName() + "': dropped filter should show no filter", - grid.getFilterStatusValues().isEmpty()); - } - else - { - List filterTexts = grid.getFilterStatusValuesText(); - checker().withScreenshot().verifyTrue( - "Sub-folder view '" + c.viewName() + "': filter should contain '" + result.expectedFilterText() + "'", - filterTexts.stream().anyMatch(pill -> pill.contains(result.expectedFilterText()))); - } - } - } - /** * Helper to validate the 'Views' menu. * From 71960930340dbb0f0b55ace06056c3438584844b Mon Sep 17 00:00:00 2001 From: Daria Bodiakova <70635654+DariaBod@users.noreply.github.com> Date: Wed, 22 Apr 2026 09:28:26 -0700 Subject: [PATCH 7/7] fix code style delete github issue links --- src/org/labkey/test/tests/component/GridPanelViewTest.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/org/labkey/test/tests/component/GridPanelViewTest.java b/src/org/labkey/test/tests/component/GridPanelViewTest.java index eb3466fe2d..bf116ffaf5 100644 --- a/src/org/labkey/test/tests/component/GridPanelViewTest.java +++ b/src/org/labkey/test/tests/component/GridPanelViewTest.java @@ -1650,10 +1650,6 @@ private void createMVTCGridView(String viewName, Filter.Operator op, String... v grid.saveView(viewName); } - /** - * Navigates to DEFAULT_VIEW_SAMPLE_TYPE grid (with TextChoice field), applies a TC filter - * via the expression tab, and saves the result as a personal named view. - */ /** * Navigates to DEFAULT_VIEW_SAMPLE_TYPE grid (with TextChoice field), applies a TC filter, * and saves the result as a personal named view.