From e4f1c05f0f35658bd4769e0a7093636cf644daa4 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Thu, 12 Jun 2025 17:41:26 -0700 Subject: [PATCH 1/7] Modernize EditableGridTest --- .../components/ui/grids/EditableGrid.java | 15 + .../tests/component/EditableGridTest.java | 390 ++++++++---------- 2 files changed, 187 insertions(+), 218 deletions(-) diff --git a/src/org/labkey/test/components/ui/grids/EditableGrid.java b/src/org/labkey/test/components/ui/grids/EditableGrid.java index 0a0dbeef31..efbfd1df22 100644 --- a/src/org/labkey/test/components/ui/grids/EditableGrid.java +++ b/src/org/labkey/test/components/ui/grids/EditableGrid.java @@ -258,6 +258,21 @@ public List> getGridDataByName(CharSequence... columnIdentif return getGridData(FieldReference::getName, columnIdentifiers); } + /** + * @param columnIdentifiers fieldKeys, names, or labels of columns + * @return grid data for the specified columns, ordered as the provided columnIdentifiers + */ + public List> getGridData(CharSequence... columnIdentifiers) + { + List> rowMaps = getGridData(FieldReference::getDomIndex, columnIdentifiers); + List> gridData = new ArrayList<>(); + for (Map gridMap : rowMaps) + { + gridData.add(new ArrayList<>(gridMap.values())); // row maps remember insertion order + } + return gridData; + } + private List> getGridData(Function keyGenerator, CharSequence... columnIdentifiers) { List> gridData = new ArrayList<>(); diff --git a/src/org/labkey/test/tests/component/EditableGridTest.java b/src/org/labkey/test/tests/component/EditableGridTest.java index c2d4804ead..757e6b1ec5 100644 --- a/src/org/labkey/test/tests/component/EditableGridTest.java +++ b/src/org/labkey/test/tests/component/EditableGridTest.java @@ -15,6 +15,9 @@ import org.labkey.test.components.ui.grids.EditableGrid; import org.labkey.test.pages.test.CoreComponentsTestPage; import org.labkey.test.params.FieldDefinition; +import org.labkey.test.params.FieldDefinition.ColumnType; +import org.labkey.test.params.FieldDefinition.IntLookup; +import org.labkey.test.params.FieldInfo; import org.labkey.test.params.experiment.SampleTypeDefinition; import org.labkey.test.params.list.IntListDefinition; import org.labkey.test.params.list.ListDefinition; @@ -39,97 +42,66 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; +import static org.labkey.test.util.TestDataGenerator.randomDomainName; +import static org.labkey.test.util.TestDataGenerator.randomFieldName; @Category({Daily.class}) public class EditableGridTest extends BaseWebDriverTest { - private static final String EXTRAPOLATING_SAMPLE_TYPE = "ExtrapolatingSampleType"; - private static final String ASC_STRING = "Ascending String"; - private static final String DESC_STRING = "Descending String"; - private static final String ASC_INT = "Ascending Int"; - private static final String DESC_INT = "Descending Int"; - private static final String ASC_DATE = "Ascending Date"; - private static final String DESC_DATE = "Descending Date"; - - private static final String FILLING_SAMPLE_TYPE = "FillingSampleType"; - private static final String FILL_STRING = "Filling String"; - private static final String FILL_MULTI_LINE = "Filling Multi Line"; - private static final String FILL_INT = "Filling Int"; - private static final String FILL_DATE = "Filling Date"; - - private static final String PASTING_SAMPLE_TYPE = "PastingSampleType"; - private static final String PASTE_1 = "Paste Column 1"; - private static final String PASTE_2 = "Paste Column 2"; - private static final String PASTE_3 = "Paste Column 3"; - private static final String PASTE_4 = "Paste Column 4"; - private static final String PASTE_5 = "Paste Column 5"; - private static final String PASTE_ML = "Paste Multi Line"; + private static final String EXTRAPOLATING_SAMPLE_TYPE = randomDomainName("ExtrapolatingSampleType"); + private static final String ASC_STRING = randomFieldName("Ascending String"); + private static final String DESC_STRING = randomFieldName("Descending String"); + private static final String ASC_INT = randomFieldName("Ascending Int"); + private static final String DESC_INT = randomFieldName("Descending Int"); + private static final String ASC_DATE = randomFieldName("Ascending Date"); + private static final String DESC_DATE = randomFieldName("Descending Date"); + + private static final String FILLING_SAMPLE_TYPE = randomDomainName("FillingSampleType"); + private static final String FILL_STRING = randomFieldName("Filling String"); + private static final String FILL_MULTI_LINE = randomFieldName("Filling Multi Line"); + private static final String FILL_INT = randomFieldName("Filling Int"); + private static final String FILL_DATE = randomFieldName("Filling Date"); + + private static final String PASTING_SAMPLE_TYPE = randomDomainName("PastingSampleType"); + private static final String PASTE_1 = randomFieldName("Paste Column 1"); + private static final String PASTE_2 = randomFieldName("Paste Column 2"); + private static final String PASTE_3 = randomFieldName("Paste Column 3"); + private static final String PASTE_4 = randomFieldName("Paste Column 4"); + private static final String PASTE_5 = randomFieldName("Paste Column 5"); + private static final String PASTE_ML = randomFieldName("Paste Multi Line"); private static final List TEXT_CHOICES = Arrays.asList("red", "Orange", "YELLOW"); - private static final String LOOKUP_LIST = "Fruits"; + private static final String LOOKUP_LIST = randomDomainName("Fruits"); private static final List LOOKUP_CHOICES = Arrays.asList("apple", "Orange", "kiwi"); - private static final String ALL_TYPE_SAMPLE_TYPE = "AllFieldsSampleType"; - private static final String STR_FIELD_NAME = "Str Col"; - private static final String REQ_STR_FIELD_NAME = "Str Col Req"; - private static final String INT_FIELD_NAME = "Int Col"; - private static final String REQ_INT_FIELD_NAME = "Int Col Req"; - private static final String DATE_FIELD_NAME = "Date Col"; - private static final String REQ_DATETIME_FIELD_NAME = "Datetime Col Req"; - private static final String TIME_FIELD_NAME = "Time Col"; - private static final String REQ_TIME_FIELD_NAME = "Time Col Req"; - private static final String FLOAT_FIELD_NAME = "Float Col"; - private static final String BOOL_FIELD_NAME = "Bool Col"; - private static final String TEXTCHOICE_FIELD_NAME = "Textchoice Col"; - private static final String REQ_TEXTCHOICE_FIELD_NAME = "Textchoice Col Req"; - private static final String LOOKUP_FIELD_NAME = "Lookup Col"; - private static final String REQ_LOOKUP_FIELD_NAME = "Lookup Col Req"; - private static final FieldDefinition STR_FIELD; - private static final FieldDefinition REQ_STR_FIELD; - private static final FieldDefinition INT_FIELD; - private static final FieldDefinition REQ_INT_FIELD; - private static final FieldDefinition DATE_FIELD; - private static final FieldDefinition REQ_DATETTIME_FIELD; - private static final FieldDefinition TIME_FIELD; - private static final FieldDefinition REQ_TIME_FIELD; - private static final FieldDefinition BOOLEAN_FIELD; - private static final FieldDefinition FLOAT_FIELD; - private static final FieldDefinition TEXTCHOICE_FIELD; - private static final FieldDefinition REQ_TEXTCHOICE_FIELD; - private static final FieldDefinition LOOKUP_FIELD; - private static final FieldDefinition REQ_LOOKUP_FIELD; - - final List ALL_FIELD_NAMES = Arrays.asList(STR_FIELD_NAME, REQ_STR_FIELD_NAME, INT_FIELD_NAME, REQ_INT_FIELD_NAME, - DATE_FIELD_NAME, REQ_DATETIME_FIELD_NAME, TIME_FIELD_NAME, REQ_TIME_FIELD_NAME, - BOOL_FIELD_NAME, FLOAT_FIELD_NAME, TEXTCHOICE_FIELD_NAME, REQ_TEXTCHOICE_FIELD_NAME, LOOKUP_FIELD_NAME, REQ_LOOKUP_FIELD_NAME); - - static - { - STR_FIELD = new FieldDefinition(STR_FIELD_NAME, FieldDefinition.ColumnType.String); - STR_FIELD.setScale(10); - REQ_STR_FIELD = new FieldDefinition(REQ_STR_FIELD_NAME, FieldDefinition.ColumnType.String); - REQ_STR_FIELD.setScale(10); - REQ_STR_FIELD.setRequired(true); - INT_FIELD = new FieldDefinition(INT_FIELD_NAME, FieldDefinition.ColumnType.Integer); - REQ_INT_FIELD = new FieldDefinition(REQ_INT_FIELD_NAME, FieldDefinition.ColumnType.Integer); - REQ_INT_FIELD.setRequired(true); - DATE_FIELD = new FieldDefinition(DATE_FIELD_NAME, FieldDefinition.ColumnType.Date); - REQ_DATETTIME_FIELD = new FieldDefinition(REQ_DATETIME_FIELD_NAME, FieldDefinition.ColumnType.DateAndTime); - REQ_DATETTIME_FIELD.setRequired(true); - TIME_FIELD = new FieldDefinition(TIME_FIELD_NAME, FieldDefinition.ColumnType.Time); - REQ_TIME_FIELD = new FieldDefinition(REQ_TIME_FIELD_NAME, FieldDefinition.ColumnType.Time); - REQ_TIME_FIELD.setRequired(true); - FLOAT_FIELD = new FieldDefinition(FLOAT_FIELD_NAME, FieldDefinition.ColumnType.Decimal); - BOOLEAN_FIELD = new FieldDefinition(BOOL_FIELD_NAME, FieldDefinition.ColumnType.Boolean); - TEXTCHOICE_FIELD = new FieldDefinition(TEXTCHOICE_FIELD_NAME, FieldDefinition.ColumnType.TextChoice); - TEXTCHOICE_FIELD.setTextChoiceValues(TEXT_CHOICES); - REQ_TEXTCHOICE_FIELD = new FieldDefinition(REQ_TEXTCHOICE_FIELD_NAME, FieldDefinition.ColumnType.TextChoice); - REQ_TEXTCHOICE_FIELD.setTextChoiceValues(TEXT_CHOICES); - REQ_TEXTCHOICE_FIELD.setRequired(true); - LOOKUP_FIELD = new FieldDefinition(LOOKUP_FIELD_NAME, new FieldDefinition.IntLookup(null, "lists", LOOKUP_LIST)); - REQ_LOOKUP_FIELD = new FieldDefinition(REQ_LOOKUP_FIELD_NAME, new FieldDefinition.IntLookup(null, "lists", LOOKUP_LIST)); - REQ_LOOKUP_FIELD.setRequired(true); - } + private static final String ALL_TYPE_SAMPLE_TYPE = randomDomainName("AllFieldsSampleType"); + + private static final FieldInfo STR_FIELD = new FieldInfo(randomFieldName("strCol")) + .customizeFieldDefinition(fd -> fd.setScale(10)); + private static final FieldInfo REQ_STR_FIELD = new FieldInfo(randomFieldName("strColReq")) + .customizeFieldDefinition(fd -> fd.setScale(10).setRequired(true)); + private static final FieldInfo INT_FIELD = new FieldInfo(randomFieldName("intCol"), ColumnType.Integer); + private static final FieldInfo REQ_INT_FIELD = new FieldInfo(randomFieldName("intColReq"), ColumnType.Integer) + .customizeFieldDefinition(fd -> fd.setRequired(true)); + private static final FieldInfo DATE_FIELD = new FieldInfo(randomFieldName("dateCol"), ColumnType.Date); + private static final FieldInfo REQ_DATETIME_FIELD = new FieldInfo(randomFieldName("datetimeColReq"), ColumnType.DateAndTime) + .customizeFieldDefinition(fd -> fd.setRequired(true)); + private static final FieldInfo TIME_FIELD = new FieldInfo(randomFieldName("timeCol"), ColumnType.Time); + private static final FieldInfo REQ_TIME_FIELD = new FieldInfo(randomFieldName("timeColReq"), ColumnType.Time) + .customizeFieldDefinition(fd -> fd.setRequired(true)); + private static final FieldInfo BOOL_FIELD = new FieldInfo(randomFieldName("boolCol"), ColumnType.Boolean); + private static final FieldInfo FLOAT_FIELD = new FieldInfo(randomFieldName("floatCol"), ColumnType.Decimal); + private static final FieldInfo TEXTCHOICE_FIELD = new FieldInfo(randomFieldName("textchoiceCol"), ColumnType.TextChoice) + .customizeFieldDefinition(fd -> fd.setTextChoiceValues(TEXT_CHOICES)); + private static final FieldInfo REQ_TEXTCHOICE_FIELD = new FieldInfo(randomFieldName("textchoiceColReq"), ColumnType.TextChoice) + .customizeFieldDefinition(fd -> fd.setRequired(true).setTextChoiceValues(TEXT_CHOICES)); + private static final FieldInfo LOOKUP_FIELD = new FieldInfo(randomFieldName("lookupCol"), new IntLookup(null, "lists", LOOKUP_LIST)); + private static final FieldInfo REQ_LOOKUP_FIELD = new FieldInfo(randomFieldName("lookupColReq"), new IntLookup(null, "lists", LOOKUP_LIST)) + .customizeFieldDefinition(fd -> fd.setRequired(true)); + + final List ALL_FIELDS = Arrays.asList(STR_FIELD, REQ_STR_FIELD, INT_FIELD, REQ_INT_FIELD, + DATE_FIELD, REQ_DATETIME_FIELD, TIME_FIELD, REQ_TIME_FIELD, + BOOL_FIELD, FLOAT_FIELD, TEXTCHOICE_FIELD, REQ_TEXTCHOICE_FIELD, LOOKUP_FIELD, REQ_LOOKUP_FIELD); @BeforeClass public static void setupProject() throws Exception @@ -140,7 +112,7 @@ public static void setupProject() throws Exception private void createLookupList(Connection connection) throws IOException, CommandException { ListDefinition listDef = new IntListDefinition(LOOKUP_LIST, "Key"); - listDef.addField(new FieldDefinition("Name", FieldDefinition.ColumnType.String)); + listDef.addField(new FieldDefinition("Name", ColumnType.String)); listDef.create(connection, getProjectName()); final List> rows = new ArrayList<>(); for (String value : LOOKUP_CHOICES) @@ -159,41 +131,38 @@ private void doSetup() throws Exception new SampleTypeDefinition(EXTRAPOLATING_SAMPLE_TYPE) .setFields( List.of( - new FieldDefinition(ASC_STRING, FieldDefinition.ColumnType.String), - new FieldDefinition(DESC_STRING, FieldDefinition.ColumnType.String), - new FieldDefinition(ASC_INT, FieldDefinition.ColumnType.Integer), - new FieldDefinition(DESC_INT, FieldDefinition.ColumnType.Integer), - new FieldDefinition(ASC_DATE, FieldDefinition.ColumnType.DateAndTime), - new FieldDefinition(DESC_DATE, FieldDefinition.ColumnType.DateAndTime) + new FieldDefinition(ASC_STRING, ColumnType.String), + new FieldDefinition(DESC_STRING, ColumnType.String), + new FieldDefinition(ASC_INT, ColumnType.Integer), + new FieldDefinition(DESC_INT, ColumnType.Integer), + new FieldDefinition(ASC_DATE, ColumnType.DateAndTime), + new FieldDefinition(DESC_DATE, ColumnType.DateAndTime) )) .create(connection, getProjectName()); new SampleTypeDefinition(FILLING_SAMPLE_TYPE) .setFields( List.of( - new FieldDefinition(FILL_STRING, FieldDefinition.ColumnType.String), - new FieldDefinition(FILL_MULTI_LINE, FieldDefinition.ColumnType.MultiLine), - new FieldDefinition(FILL_INT, FieldDefinition.ColumnType.Integer), - new FieldDefinition(FILL_DATE, FieldDefinition.ColumnType.DateAndTime) + new FieldDefinition(FILL_STRING, ColumnType.String), + new FieldDefinition(FILL_MULTI_LINE, ColumnType.MultiLine), + new FieldDefinition(FILL_INT, ColumnType.Integer), + new FieldDefinition(FILL_DATE, ColumnType.DateAndTime) )) .create(connection, getProjectName()); new SampleTypeDefinition(PASTING_SAMPLE_TYPE) .setFields( List.of( - new FieldDefinition(PASTE_1, FieldDefinition.ColumnType.String), - new FieldDefinition(PASTE_2, FieldDefinition.ColumnType.String), - new FieldDefinition(PASTE_3, FieldDefinition.ColumnType.String), - new FieldDefinition(PASTE_4, FieldDefinition.ColumnType.String), - new FieldDefinition(PASTE_5, FieldDefinition.ColumnType.String), - new FieldDefinition(PASTE_ML, FieldDefinition.ColumnType.MultiLine) + new FieldDefinition(PASTE_1, ColumnType.String), + new FieldDefinition(PASTE_2, ColumnType.String), + new FieldDefinition(PASTE_3, ColumnType.String), + new FieldDefinition(PASTE_4, ColumnType.String), + new FieldDefinition(PASTE_5, ColumnType.String), + new FieldDefinition(PASTE_ML, ColumnType.MultiLine) )) .create(connection, getProjectName()); new SampleTypeDefinition(ALL_TYPE_SAMPLE_TYPE) .setFields( - List.of( - STR_FIELD, REQ_STR_FIELD, INT_FIELD, REQ_INT_FIELD, DATE_FIELD, REQ_DATETTIME_FIELD, TIME_FIELD, REQ_TIME_FIELD, - BOOLEAN_FIELD, FLOAT_FIELD, TEXTCHOICE_FIELD, REQ_TEXTCHOICE_FIELD, LOOKUP_FIELD, REQ_LOOKUP_FIELD - )) + ALL_FIELDS.stream().map(FieldInfo::getFieldDefinition).toList()) .create(connection, getProjectName()); } @@ -210,7 +179,7 @@ public void testTooWideErrorCase() "Unable to paste. Cannot paste columns beyond the columns found in the grid.", testGrid.getCellPopoverText(0, "Description")); assertThat("Expect failed paste to leave data unchanged", - testGrid.getColumnDataByLabel("Name"), everyItem(is(""))); + testGrid.getColumnData("Name"), everyItem(is(""))); } @Test @@ -227,8 +196,8 @@ public void testCanAddRowsWithTallShape() assertEquals("Initial editable grid row count", 0, testGrid.getRowCount()); testGrid.addRows(1); testGrid.pasteFromCell(0, DESC_STRING, tallShape); - List pastedColData = testGrid.getColumnDataByLabel(DESC_STRING); - List unpastedColData = testGrid.getColumnDataByLabel(ASC_STRING); + List pastedColData = testGrid.getColumnData(DESC_STRING); + List unpastedColData = testGrid.getColumnData(ASC_STRING); assertEquals("Didn't get correct values", List.of("42", "41", "40", "39", "38"), pastedColData); assertThat("expect other column to remain empty", @@ -254,16 +223,16 @@ public void testDragFillExtrapolatingIntegers() List expectedDecreasing = List.of("4", "2", "0", "-2", "-4", ""); assertEquals("Drag-fill should have extrapolated " + ASC_STRING, expectedIncreasing, - testGrid.getColumnDataByLabel(ASC_STRING)); + testGrid.getColumnData(ASC_STRING)); assertEquals("Drag-fill should have extrapolated " + DESC_STRING, expectedDecreasing, - testGrid.getColumnDataByLabel(DESC_STRING)); + testGrid.getColumnData(DESC_STRING)); assertEquals("Drag-fill should have extrapolated " + ASC_INT, expectedIncreasing, - testGrid.getColumnDataByLabel(ASC_INT)); + testGrid.getColumnData(ASC_INT)); assertEquals("Drag-fill should have extrapolated " + DESC_INT, expectedDecreasing, - testGrid.getColumnDataByLabel(DESC_INT)); + testGrid.getColumnData(DESC_INT)); } @Test @@ -283,10 +252,10 @@ public void testDragFillExtrapolatingIntegersWithPrefix() List expectedDecreasing = List.of("ABC-4", "ABC-2", "ABC-0", "ABC--2", "ABC--4", ""); assertEquals("Drag-fill should have extrapolated " + ASC_STRING, expectedIncreasing, - testGrid.getColumnDataByLabel(ASC_STRING)); + testGrid.getColumnData(ASC_STRING)); assertEquals("Drag-fill should have extrapolated " + DESC_STRING, expectedDecreasing, - testGrid.getColumnDataByLabel(DESC_STRING)); + testGrid.getColumnData(DESC_STRING)); } @Test @@ -328,19 +297,19 @@ public void testDragFillSingleRow() checker().verifyEquals("Drag-fill should have filled " + FILL_STRING, List.of(stringValue, stringValue, stringValue, ""), - testGrid.getColumnDataByLabel(FILL_STRING)); + testGrid.getColumnData(FILL_STRING)); checker().verifyEquals("Drag-fill should have filled " + FILL_MULTI_LINE, List.of(multiLineValue, multiLineValue, multiLineValue, ""), - testGrid.getColumnDataByLabel(FILL_MULTI_LINE)); + testGrid.getColumnData(FILL_MULTI_LINE)); checker().verifyEquals("Drag-fill should have filled " + FILL_INT, List.of(intValue, intValue, intValue, ""), - testGrid.getColumnDataByLabel(FILL_INT)); + testGrid.getColumnData(FILL_INT)); checker().verifyEquals("Drag-fill should have filled " + FILL_DATE, List.of(EditableGrid.DATE_FORMAT.format(now), EditableGrid.DATE_FORMAT.format(now.plusDays(1)), EditableGrid.DATE_FORMAT.format(now.plusDays(2)), ""), - testGrid.getColumnDataByLabel(FILL_DATE)); + testGrid.getColumnData(FILL_DATE)); // Check that pasting increased the size of all the rows. var totalHeightAfter = new Object(){int size = 0; }; @@ -377,10 +346,10 @@ public void testDragFillMultipleRows() checker().verifyEquals("Drag-fill should have filled " + FILL_STRING, List.of("QWE", "ASD", "ZXC", "QWE", "ASD", "ZXC", ""), - testGrid.getColumnDataByLabel(FILL_STRING)); + testGrid.getColumnData(FILL_STRING)); checker().verifyEquals("Drag-fill should have filled " + FILL_MULTI_LINE, List.of(mlRow1, "", mlRow2, mlRow1, "", mlRow2, ""), - testGrid.getColumnDataByLabel(FILL_MULTI_LINE)); + testGrid.getColumnData(FILL_MULTI_LINE)); checker().verifyEquals("Drag-fill should have filled " + FILL_DATE, List.of(EditableGrid.DATE_FORMAT.format(now), EditableGrid.DATE_FORMAT.format(now.plusDays(3)), @@ -389,7 +358,7 @@ public void testDragFillMultipleRows() EditableGrid.DATE_FORMAT.format(now.plusDays(3)), EditableGrid.DATE_FORMAT.format(now.plusDays(1)), ""), - testGrid.getColumnDataByLabel(FILL_DATE)); + testGrid.getColumnData(FILL_DATE)); } @Test @@ -647,7 +616,7 @@ public void testPasteIntoMultiLine() waitFor(()->expectedValues.size() == editableGrid.getRowCount(), 1_000)); checker().verifyEquals(String.format("Values in column '%s' not as expected.", PASTE_ML), - expectedValues, editableGrid.getColumnDataByLabel(PASTE_ML)); + expectedValues, editableGrid.getColumnData(PASTE_ML)); checker().screenShotIfNewError("Paste_Into_Multiple_Cells_Error"); @@ -1294,8 +1263,7 @@ private void checkSelectedStyle(EditableGrid editableGrid, private String getActualPaste(EditableGrid testGrid) { - List> gridData = testGrid.getGridDataByLabel(PASTE_1, PASTE_2, PASTE_3, PASTE_4, PASTE_5); - List> rows = gridData.stream().map(r -> List.of(r.get(PASTE_1), r.get(PASTE_2), r.get(PASTE_3), r.get(PASTE_4), r.get(PASTE_5))).toList(); + List> rows = testGrid.getGridData(PASTE_1, PASTE_2, PASTE_3, PASTE_4, PASTE_5); return rowsToString(rows); } @@ -1373,49 +1341,49 @@ public void testInputCellValidation() checker().verifyEquals("Cell error should be absent when a row is added on page load", 0, testGrid.getCellErrors().size()); log("Input empty string for required field should trigger cell warning."); - testGrid.setCellValue(1, REQ_STR_FIELD_NAME + " *", " "); - checker().verifyEquals("Cell warning status not as expected at row " + 1 + " for col " + REQ_STR_FIELD_NAME, true, testGrid.hasCellError(1, REQ_STR_FIELD_NAME + " *")); - checker().verifyEquals("Cell warning msg not as expected at row " + 1 + " for col " + REQ_STR_FIELD_NAME, REQ_STR_FIELD_NAME + " is required.", testGrid.getCellPopoverText(1, REQ_STR_FIELD_NAME + " *")); + testGrid.setCellValue(1, REQ_STR_FIELD.getFieldKey(), " "); + checker().verifyEquals("Cell warning status not as expected at row " + 1 + " for col " + REQ_STR_FIELD.getLabel(), true, testGrid.hasCellError(1, REQ_STR_FIELD.getFieldKey())); + checker().verifyEquals("Cell warning msg not as expected at row " + 1 + " for col " + REQ_STR_FIELD.getLabel(), REQ_STR_FIELD.getLabel() + " is required.", testGrid.getCellPopoverText(1, REQ_STR_FIELD.getFieldKey())); mouseOver(testGrid.getCell(0, "Row")); // dismiss warning popup - testGrid.setCellValue(1, REQ_INT_FIELD_NAME + " *", " "); - checker().verifyEquals("Cell warning status not as expected at row " + 1 + " for col " + REQ_INT_FIELD_NAME, true, testGrid.hasCellError(1, REQ_INT_FIELD_NAME + " *")); - checker().verifyEquals("Cell warning msg not as expected at row " + 1 + " for col " + REQ_INT_FIELD_NAME, REQ_INT_FIELD_NAME + " is required.", testGrid.getCellPopoverText(1, REQ_INT_FIELD_NAME + " *")); + testGrid.setCellValue(1, REQ_INT_FIELD.getFieldKey(), " "); + checker().verifyEquals("Cell warning status not as expected at row " + 1 + " for col " + REQ_INT_FIELD.getLabel(), true, testGrid.hasCellError(1, REQ_INT_FIELD.getFieldKey())); + checker().verifyEquals("Cell warning msg not as expected at row " + 1 + " for col " + REQ_INT_FIELD.getLabel(), REQ_INT_FIELD.getLabel() + " is required.", testGrid.getCellPopoverText(1, REQ_INT_FIELD.getFieldKey())); log("Correct values should remove cell warning, keep entering wrong values should update warning"); mouseOver(testGrid.getCell(0, "Row")); // dismiss warning popup - testGrid.setCellValue(0, STR_FIELD_NAME, ""); + testGrid.setCellValue(0, STR_FIELD.getFieldKey(), ""); mouseOver(testGrid.getCell(0, "Row")); - testGrid.setCellValue(1, STR_FIELD_NAME, "This value is too long"); + testGrid.setCellValue(1, STR_FIELD.getFieldKey(), "This value is too long"); mouseOver(testGrid.getCell(0, "Row")); - testGrid.setCellValue(0, REQ_STR_FIELD_NAME + " *", "good"); + testGrid.setCellValue(0, REQ_STR_FIELD.getFieldKey(), "good"); mouseOver(testGrid.getCell(0, "Row")); - testGrid.setCellValue(1, REQ_STR_FIELD_NAME + " *", "This value is too long"); - checker().verifyEquals("Cell warning status not as expected at row " + 0 + " for col " + STR_FIELD_NAME, false, testGrid.hasCellError(0, STR_FIELD_NAME)); - checker().verifyEquals("Cell warning msg not as expected at row " + 1 + " for col " + STR_FIELD_NAME, "22/10 characters", testGrid.getCellPopoverText(1, STR_FIELD_NAME)); - checker().verifyEquals("Cell warning status not as expected at row " + 0 + " for col " + REQ_STR_FIELD_NAME, false, testGrid.hasCellError(0, REQ_STR_FIELD_NAME + " *")); - checker().verifyEquals("Cell warning msg not as expected at row " + 1 + " for col " + REQ_STR_FIELD_NAME, "22/10 characters", testGrid.getCellPopoverText(1, REQ_STR_FIELD_NAME + " *")); + testGrid.setCellValue(1, REQ_STR_FIELD.getFieldKey(), "This value is too long"); + checker().verifyEquals("Cell warning status not as expected at row " + 0 + " for col " + STR_FIELD.getLabel(), false, testGrid.hasCellError(0, STR_FIELD.getFieldKey())); + checker().verifyEquals("Cell warning msg not as expected at row " + 1 + " for col " + STR_FIELD.getLabel(), "22/10 characters", testGrid.getCellPopoverText(1, STR_FIELD.getFieldKey())); + checker().verifyEquals("Cell warning status not as expected at row " + 0 + " for col " + REQ_STR_FIELD.getLabel(), false, testGrid.hasCellError(0, REQ_STR_FIELD.getFieldKey())); + checker().verifyEquals("Cell warning msg not as expected at row " + 1 + " for col " + REQ_STR_FIELD.getLabel(), "22/10 characters", testGrid.getCellPopoverText(1, REQ_STR_FIELD.getFieldKey())); log("Input invalid data type value should trigger cell warnings."); mouseOver(testGrid.getCell(0, "Row")); // dismiss warning popup - testGrid.setCellValue(0, INT_FIELD_NAME, "1.23"); + testGrid.setCellValue(0, INT_FIELD.getFieldKey(), "1.23"); mouseOver(testGrid.getCell(0, "Row")); // dismiss warning popup - testGrid.setCellValue(0, FLOAT_FIELD_NAME, "not float"); + testGrid.setCellValue(0, FLOAT_FIELD.getFieldKey(), "not float"); mouseOver(testGrid.getCell(0, "Row")); // dismiss warning popup - testGrid.setCellValue(0, BOOL_FIELD_NAME, "not bool"); - checker().verifyEquals("Cell warning msg not as expected at row " + 0 + " for col " + INT_FIELD_NAME, "Invalid integer", testGrid.getCellPopoverText(0, INT_FIELD_NAME)); - checker().verifyEquals("Cell warning msg not as expected at row " + 0 + " for col " + FLOAT_FIELD_NAME, "Invalid decimal", testGrid.getCellPopoverText(0, FLOAT_FIELD_NAME)); - checker().verifyEquals("Cell warning msg not as expected at row " + 0 + " for col " + BOOL_FIELD_NAME, "Invalid boolean", testGrid.getCellPopoverText(0, BOOL_FIELD_NAME)); + testGrid.setCellValue(0, BOOL_FIELD.getFieldKey(), "not bool"); + checker().verifyEquals("Cell warning msg not as expected at row " + 0 + " for col " + INT_FIELD.getLabel(), "Invalid integer", testGrid.getCellPopoverText(0, INT_FIELD.getFieldKey())); + checker().verifyEquals("Cell warning msg not as expected at row " + 0 + " for col " + FLOAT_FIELD.getLabel(), "Invalid decimal", testGrid.getCellPopoverText(0, FLOAT_FIELD.getFieldKey())); + checker().verifyEquals("Cell warning msg not as expected at row " + 0 + " for col " + BOOL_FIELD.getLabel(), "Invalid boolean", testGrid.getCellPopoverText(0, BOOL_FIELD.getFieldKey())); log("Correct values should remove data type warning."); mouseOver(testGrid.getCell(0, "Row")); // dismiss warning popup - testGrid.setCellValue(0, INT_FIELD_NAME, "123"); - checker().verifyFalse("Cell warning should disappear after correcting value", testGrid.hasCellError(0, INT_FIELD_NAME)); + testGrid.setCellValue(0, INT_FIELD.getFieldKey(), "123"); + checker().verifyFalse("Cell warning should disappear after correcting value", testGrid.hasCellError(0, INT_FIELD.getFieldKey())); log("Required value warning should be absent before the cell is acted on"); - checker().verifyFalse("Required value warning should not be present on page init", testGrid.hasCellError(0, REQ_STR_FIELD_NAME + " *")); + checker().verifyFalse("Required value warning should not be present on page init", testGrid.hasCellError(0, REQ_STR_FIELD.getFieldKey())); mouseOver(testGrid.getCell(0, "Row")); // dismiss warning popup - testGrid.clearCellValue(1, REQ_STR_FIELD_NAME + " *"); - checker().verifyTrue("Required value warning should show up after removing a value from cell", testGrid.hasCellError(1, REQ_STR_FIELD_NAME + " *")); + testGrid.clearCellValue(1, REQ_STR_FIELD.getFieldKey()); + checker().verifyTrue("Required value warning should show up after removing a value from cell", testGrid.hasCellError(1, REQ_STR_FIELD.getFieldKey())); } @Test @@ -1432,12 +1400,12 @@ public void testPasteCellValidation() testGrid.addRows(3); log("Pasting invalid values"); - testGrid.selectCell(0, STR_FIELD_NAME); + testGrid.selectCell(0, STR_FIELD.getFieldKey()); actionPaste(null, rowsToString(clipRows)); List> expectedCellWarnings = List.of( Arrays.asList(null, null, null, null, null, null, null, null, null, null, null, null, null, null), - Arrays.asList(null, REQ_STR_FIELD_NAME + " is required.", null, REQ_INT_FIELD_NAME + " is required.", null, REQ_DATETIME_FIELD_NAME + " is required.", null, REQ_TIME_FIELD_NAME + " is required.", null, null, null, REQ_TEXTCHOICE_FIELD_NAME + " is required.", null, REQ_LOOKUP_FIELD_NAME + " is required."), + Arrays.asList(null, REQ_STR_FIELD.getLabel() + " is required.", null, REQ_INT_FIELD.getLabel() + " is required.", null, REQ_DATETIME_FIELD.getLabel() + " is required.", null, REQ_TIME_FIELD.getLabel() + " is required.", null, null, null, REQ_TEXTCHOICE_FIELD.getLabel() + " is required.", null, REQ_LOOKUP_FIELD.getLabel() + " is required."), Arrays.asList("22/10 characters", "22/10 characters", "Invalid integer", "Invalid integer", "Invalid date, use format yyyy-MM-dd", "Invalid date time, use format yyyy-MM-dd HH:mm", "Invalid time", "Invalid time", "Invalid boolean", "Invalid decimal", "'wrong text choice' is not a valid choice", "'bad choice' is not a valid choice", "Could not find \"bad lookup\"", "Could not find \"bad lookup\"") ); @@ -1455,77 +1423,65 @@ public void testPasteCellValidation() "Bad value popup did not go away.", 500); log("Correct missing required fields should remove corresponding cell warnings"); - testGrid.setCellValue(1, REQ_STR_FIELD_NAME + " *", " "); - checker().verifyTrue("Cell warning should be present after setting another invalid value", testGrid.hasCellError(1, REQ_STR_FIELD_NAME + " *")); - mouseOver(testGrid.getCell(0, STR_FIELD_NAME)); // dismiss warning popup - testGrid.setCellValue(1, REQ_INT_FIELD_NAME + " *", "2"); - mouseOver(testGrid.getCell(0, STR_FIELD_NAME)); // dismiss warning popup - testGrid.setCellValue(1, REQ_TEXTCHOICE_FIELD_NAME + " *", List.of("Orange")); - mouseOver(testGrid.getCell(0, STR_FIELD_NAME)); // dismiss warning popup - testGrid.setCellValue(1, REQ_LOOKUP_FIELD_NAME + " *", List.of("Orange")); - mouseOver(testGrid.getCell(0, STR_FIELD_NAME)); // dismiss warning popup - testGrid.setCellValue(1, REQ_STR_FIELD_NAME + " *", "not empty"); - mouseOver(testGrid.getCell(0, STR_FIELD_NAME)); // dismiss warning popup - testGrid.setCellValue(1, REQ_DATETIME_FIELD_NAME + " *", LocalDateTime.of(2024, 7, 7, 10, 30)); - mouseOver(testGrid.getCell(0, STR_FIELD_NAME)); // dismiss warning popup - testGrid.setCellValue(1, REQ_TIME_FIELD_NAME + " *", LocalTime.of(2, 30)); - - for (int col = 0; col < ALL_FIELD_NAMES.size(); col++) + testGrid.setCellValue(1, REQ_STR_FIELD.getFieldKey(), " "); + checker().verifyTrue("Cell warning should be present after setting another invalid value", testGrid.hasCellError(1, REQ_STR_FIELD.getFieldKey())); + mouseOver(testGrid.getCell(0, STR_FIELD.getFieldKey())); // dismiss warning popup + testGrid.setCellValue(1, REQ_INT_FIELD.getFieldKey(), "2"); + mouseOver(testGrid.getCell(0, STR_FIELD.getFieldKey())); // dismiss warning popup + testGrid.setCellValue(1, REQ_TEXTCHOICE_FIELD.getFieldKey(), List.of("Orange")); + mouseOver(testGrid.getCell(0, STR_FIELD.getFieldKey())); // dismiss warning popup + testGrid.setCellValue(1, REQ_LOOKUP_FIELD.getFieldKey(), List.of("Orange")); + mouseOver(testGrid.getCell(0, STR_FIELD.getFieldKey())); // dismiss warning popup + testGrid.setCellValue(1, REQ_STR_FIELD.getFieldKey(), "not empty"); + mouseOver(testGrid.getCell(0, STR_FIELD.getFieldKey())); // dismiss warning popup + testGrid.setCellValue(1, REQ_DATETIME_FIELD.getFieldKey(), LocalDateTime.of(2024, 7, 7, 10, 30)); + mouseOver(testGrid.getCell(0, STR_FIELD.getFieldKey())); // dismiss warning popup + testGrid.setCellValue(1, REQ_TIME_FIELD.getFieldKey(), LocalTime.of(2, 30)); + + for (FieldInfo field : ALL_FIELDS) { - String colName = ALL_FIELD_NAMES.get(col); - if (colName.endsWith(" Req")) - colName += " *"; - - checker().verifyFalse("Cell warning be absent after required values are provided: " + colName, testGrid.hasCellError(1, colName)); + checker().verifyFalse("Cell warning be absent after required values are provided: " + field.getLabel(), testGrid.hasCellError(1, field.getFieldKey())); } log("Enter another bad value should retain cell warning"); - testGrid.setCellValue(2, INT_FIELD_NAME, "bad"); - checker().verifyTrue("Cell warning should be present after setting another invalid value", testGrid.hasCellError(2, INT_FIELD_NAME)); + testGrid.setCellValue(2, INT_FIELD.getFieldKey(), "bad"); + checker().verifyTrue("Cell warning should be present after setting another invalid value", testGrid.hasCellError(2, INT_FIELD.getFieldKey())); checker().screenShotIfNewError("after required value correction error"); log("Correct bad data type values should remove paste data warnings"); - testGrid.setCellValue(2, STR_FIELD_NAME, "good"); - testGrid.setCellValue(2, REQ_STR_FIELD_NAME + " *", "good"); - testGrid.setCellValue(2, INT_FIELD_NAME, "1"); - testGrid.setCellValue(2, REQ_INT_FIELD_NAME + " *", "134"); - testGrid.setCellValue(2, BOOL_FIELD_NAME, "on"); - testGrid.setCellValue(2, FLOAT_FIELD_NAME, "1.23"); - testGrid.setCellValue(2, TEXTCHOICE_FIELD_NAME, List.of("red")); - testGrid.setCellValue(2, REQ_TEXTCHOICE_FIELD_NAME + " *", List.of("red")); - testGrid.setCellValue(2, LOOKUP_FIELD_NAME, List.of("kiwi")); - testGrid.setCellValue(2, REQ_LOOKUP_FIELD_NAME + " *", List.of("kiwi")); - testGrid.setCellValue(2, DATE_FIELD_NAME, LocalDate.of(2024, 7, 7)); - testGrid.setCellValue(2, TIME_FIELD_NAME, LocalTime.of(2, 30)); - testGrid.setCellValue(2, REQ_DATETIME_FIELD_NAME + " *", LocalDateTime.of(2024, 7, 7, 10, 30)); - testGrid.setCellValue(2, REQ_TIME_FIELD_NAME + " *", LocalTime.of(2, 30)); - - for (int col = 0; col < ALL_FIELD_NAMES.size(); col++) + testGrid.setCellValue(2, STR_FIELD.getFieldKey(), "good"); + testGrid.setCellValue(2, REQ_STR_FIELD.getFieldKey(), "good"); + testGrid.setCellValue(2, INT_FIELD.getFieldKey(), "1"); + testGrid.setCellValue(2, REQ_INT_FIELD.getFieldKey(), "134"); + testGrid.setCellValue(2, BOOL_FIELD.getFieldKey(), "on"); + testGrid.setCellValue(2, FLOAT_FIELD.getFieldKey(), "1.23"); + testGrid.setCellValue(2, TEXTCHOICE_FIELD.getFieldKey(), List.of("red")); + testGrid.setCellValue(2, REQ_TEXTCHOICE_FIELD.getFieldKey(), List.of("red")); + testGrid.setCellValue(2, LOOKUP_FIELD.getFieldKey(), List.of("kiwi")); + testGrid.setCellValue(2, REQ_LOOKUP_FIELD.getFieldKey(), List.of("kiwi")); + testGrid.setCellValue(2, DATE_FIELD.getFieldKey(), LocalDate.of(2024, 7, 7)); + testGrid.setCellValue(2, TIME_FIELD.getFieldKey(), LocalTime.of(2, 30)); + testGrid.setCellValue(2, REQ_DATETIME_FIELD.getFieldKey(), LocalDateTime.of(2024, 7, 7, 10, 30)); + testGrid.setCellValue(2, REQ_TIME_FIELD.getFieldKey(), LocalTime.of(2, 30)); + + for (FieldInfo field : ALL_FIELDS) { - String colName = ALL_FIELD_NAMES.get(col); - if (colName.endsWith(" Req")) - colName += " *"; - - checker().verifyFalse("Cell warning should be absent after correct values are provided: " + colName, testGrid.hasCellError(2, colName)); + checker().verifyFalse("Cell warning should be absent after correct values are provided: " + field.getLabel(), testGrid.hasCellError(2, field.getFieldKey())); } checker().screenShotIfNewError("after data correction error"); log("Issue 46767: start date before 1000-01-01"); - for (int col = 0; col < ALL_FIELD_NAMES.size(); col++) + for (FieldInfo field : ALL_FIELDS) { - String colName = ALL_FIELD_NAMES.get(col); - if (colName.endsWith(" Req")) - colName += " *"; - // start date before year 1000 shouldn't trigger warning - checker().verifyFalse("Cell warning should not be present for: " + colName, testGrid.hasCellError(0, colName)); + checker().verifyFalse("Cell warning should not be present for: " + field.getLabel(), testGrid.hasCellError(0, field.getFieldKey())); } log("Verify UI is interactable with values before 1000-01-01"); - testGrid.setCellValue(3, DATE_FIELD_NAME, LocalDate.of(2024, 7, 7)); - testGrid.setCellValue(3, TIME_FIELD_NAME, LocalTime.of(2, 30)); - testGrid.setCellValue(3, REQ_DATETIME_FIELD_NAME + " *", LocalDate.of(2024, 7, 7)); - testGrid.setCellValue(3, REQ_TIME_FIELD_NAME + " *", LocalTime.of(2, 30)); + testGrid.setCellValue(3, DATE_FIELD.getFieldKey(), LocalDate.of(2024, 7, 7)); + testGrid.setCellValue(3, TIME_FIELD.getFieldKey(), LocalTime.of(2, 30)); + testGrid.setCellValue(3, REQ_DATETIME_FIELD.getFieldKey(), LocalDate.of(2024, 7, 7)); + testGrid.setCellValue(3, REQ_TIME_FIELD.getFieldKey(), LocalTime.of(2, 30)); checker().screenShotIfNewError("Issue 46767"); @@ -1535,16 +1491,14 @@ public void testPasteCellValidation() private void verifyCellWarning(EditableGrid testGrid, List expectedWarnings, int rowId) { - for (int col = 0; col < ALL_FIELD_NAMES.size(); col++) + for (int col = 0; col < ALL_FIELDS.size(); col++) { String expectedWarning = expectedWarnings.get(col); - String colName = ALL_FIELD_NAMES.get(col); - if (colName.endsWith(" Req")) - colName += " *"; + FieldInfo field = ALL_FIELDS.get(col); - checker().verifyEquals("Cell warning status not as expected at row " + rowId + " for col " + colName, !StringUtils.isEmpty(expectedWarning), testGrid.hasCellError(rowId, colName)); + checker().verifyEquals("Cell warning status not as expected at row " + rowId + " for col " + field.getLabel(), !StringUtils.isEmpty(expectedWarning), testGrid.hasCellError(rowId, field.getFieldKey())); if (!StringUtils.isEmpty(expectedWarning)) - checker().verifyEquals("Cell warning msg not as expected at row " + rowId + " for col " + colName, expectedWarning, testGrid.getCellPopoverText(rowId, colName)); + checker().verifyEquals("Cell warning msg not as expected at row " + rowId + " for col " + field.getLabel(), expectedWarning, testGrid.getCellPopoverText(rowId, field.getFieldKey())); } } @@ -1559,20 +1513,20 @@ public void testFillCellValidation() testGrid.addRows(3); log("Start with pasting invalid values, so we can fill down invalid values for dropdowns and data/time inputs"); - testGrid.selectCell(0, STR_FIELD_NAME); + testGrid.selectCell(0, STR_FIELD.getFieldKey()); actionPaste(null, rowsToString(clipRows)); - // Scroll one column to the right into view, this will help ensure the REQ_LOOKUP_FIELD_NAME is within the viewport. - var index = testGrid.getColumnLabels().indexOf(REQ_LOOKUP_FIELD_NAME + " *") + 1; + // Scroll one column to the right into view, this will help ensure the REQ_LOOKUP_FIELD is within the viewport. + var index = testGrid.getColumnLabels().indexOf(REQ_LOOKUP_FIELD.getLabel() + " *") + 1; scrollIntoView(testGrid.getCell(0, testGrid.getColumnLabels().get(index))); - WebElement fillFrom = testGrid.getCell(0, REQ_LOOKUP_FIELD_NAME + " *"); - WebElement fillTo = testGrid.getCell(2, REQ_LOOKUP_FIELD_NAME + " *"); + WebElement fillFrom = testGrid.getCell(0, REQ_LOOKUP_FIELD.getFieldKey()); + WebElement fillTo = testGrid.getCell(2, REQ_LOOKUP_FIELD.getFieldKey()); testGrid.dragFill(fillFrom, fillTo); - List expectedWarnings = Arrays.asList("22/10 characters", REQ_STR_FIELD_NAME + " is required.", "Invalid integer", "Invalid integer", - "Invalid date, use format yyyy-MM-dd", REQ_DATETIME_FIELD_NAME + " is required.", "Invalid time", REQ_TIME_FIELD_NAME + " is required.", - "Invalid boolean", "Invalid decimal", "'wrong text choice' is not a valid choice", "'bad choice' is not a valid choice", "Could not find \"bad lookup\"", REQ_LOOKUP_FIELD_NAME + " is required."); + List expectedWarnings = Arrays.asList("22/10 characters", REQ_STR_FIELD.getLabel() + " is required.", "Invalid integer", "Invalid integer", + "Invalid date, use format yyyy-MM-dd", REQ_DATETIME_FIELD.getLabel() + " is required.", "Invalid time", REQ_TIME_FIELD.getLabel() + " is required.", + "Invalid boolean", "Invalid decimal", "'wrong text choice' is not a valid choice", "'bad choice' is not a valid choice", "Could not find \"bad lookup\"", REQ_LOOKUP_FIELD.getLabel() + " is required."); log("Verify filled down cells have warnings"); for (int i = 0; i < 3; i++) From e3faea4368772fd950bf31b035c57e5c0682e2ed Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Mon, 16 Jun 2025 17:17:55 -0700 Subject: [PATCH 2/7] FieldInfo and FieldKey updates and more random field names --- src/org/labkey/test/BaseWebDriverTest.java | 5 +- .../ui/grids/FieldReferenceManager.java | 5 +- src/org/labkey/test/params/FieldInfo.java | 94 ++++++++++-- src/org/labkey/test/params/FieldKey.java | 29 ++-- src/org/labkey/test/params/WrapsFieldKey.java | 6 + .../tests/component/EditableGridTest.java | 136 +++++++++--------- .../labkey/test/util/TestDataGenerator.java | 14 +- .../labkey/test/util/data/TestDataUtils.java | 21 ++- 8 files changed, 203 insertions(+), 107 deletions(-) create mode 100644 src/org/labkey/test/params/WrapsFieldKey.java diff --git a/src/org/labkey/test/BaseWebDriverTest.java b/src/org/labkey/test/BaseWebDriverTest.java index 88fac31b26..63d7272e2a 100644 --- a/src/org/labkey/test/BaseWebDriverTest.java +++ b/src/org/labkey/test/BaseWebDriverTest.java @@ -40,7 +40,6 @@ import org.junit.runners.model.MultipleFailureException; import org.junit.runners.model.Statement; import org.junit.runners.model.TestTimedOutException; -import org.labkey.api.query.FieldKey; import org.labkey.junit.rules.TestWatcher; import org.labkey.remoteapi.CommandException; import org.labkey.remoteapi.CommandResponse; @@ -65,6 +64,7 @@ import org.labkey.test.pages.query.NewQueryPage; import org.labkey.test.pages.query.SourceQueryPage; import org.labkey.test.pages.search.SearchResultsPage; +import org.labkey.test.params.FieldKey; import org.labkey.test.teamcity.TeamCityUtils; import org.labkey.test.util.APIAssayHelper; import org.labkey.test.util.APIContainerHelper; @@ -215,8 +215,7 @@ public abstract class BaseWebDriverTest extends LabKeySiteWrapper implements Cle public static final double DELTA = 10E-10; - public static final String[] ILLEGAL_QUERY_KEY_CHARACTERS = FieldKey.ILLEGAL; - public static final String ALL_ILLEGAL_QUERY_KEY_CHARACTERS = StringUtils.join(ILLEGAL_QUERY_KEY_CHARACTERS, ""); + public static final String ALL_ILLEGAL_QUERY_KEY_CHARACTERS = StringUtils.join(FieldKey.getIllegalChars(), ""); // See TSVWriter.shouldQuote. Generally we are not able to use the tab and new line characters when creating field names in the UI, but including here for completeness public static final String[] TRICKY_IMPORT_FIELD_CHARACTERS = {"\\", "\"", "\\t", ",", "\\n", "\\r"}; diff --git a/src/org/labkey/test/components/ui/grids/FieldReferenceManager.java b/src/org/labkey/test/components/ui/grids/FieldReferenceManager.java index b8845cf93b..2f5558fb11 100644 --- a/src/org/labkey/test/components/ui/grids/FieldReferenceManager.java +++ b/src/org/labkey/test/components/ui/grids/FieldReferenceManager.java @@ -3,6 +3,7 @@ import org.apache.commons.lang3.mutable.Mutable; import org.apache.commons.lang3.mutable.MutableObject; import org.labkey.test.params.FieldKey; +import org.labkey.test.params.WrapsFieldKey; import org.labkey.test.util.selenium.WebElementUtils; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebElement; @@ -43,9 +44,9 @@ public final FieldReference findFieldReference(CharSequence fieldIdentifier) { List> options; - if (fieldIdentifier instanceof FieldKey fk) + if (fieldIdentifier instanceof WrapsFieldKey fk) { - options = List.of(() -> findColumnHeaderByFieldKey(fk)); // We know it is a FieldKey + options = List.of(() -> findColumnHeaderByFieldKey(fk.getFieldKey())); // We know it is a FieldKey } else { diff --git a/src/org/labkey/test/params/FieldInfo.java b/src/org/labkey/test/params/FieldInfo.java index ebd28a0019..7bbd51dc02 100644 --- a/src/org/labkey/test/params/FieldInfo.java +++ b/src/org/labkey/test/params/FieldInfo.java @@ -1,28 +1,34 @@ package org.labkey.test.params; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.labkey.test.params.FieldDefinition.ColumnType; +import org.labkey.test.util.TestDataGenerator; + import java.util.Objects; import java.util.function.Consumer; /** * Immutable alternative to 'FieldDefinition' * Use this for shared global field information + * Implements CharSequence to be compatible with grid components */ -public class FieldInfo +public class FieldInfo implements CharSequence, WrapsFieldKey { private final FieldKey _fieldKey; private final String _label; - private final FieldDefinition.ColumnType _columnType; + private final ColumnType _columnType; private final Consumer _fieldDefinitionMutator; - private FieldInfo(FieldKey fieldKey, String label, FieldDefinition.ColumnType columnType, Consumer fieldDefinitionMutator) + private FieldInfo(FieldKey fieldKey, String label, ColumnType columnType, Consumer fieldDefinitionMutator) { _fieldKey = fieldKey; _label = label; - _columnType = Objects.requireNonNullElse(columnType, FieldDefinition.ColumnType.String); + _columnType = Objects.requireNonNullElse(columnType, ColumnType.String); _fieldDefinitionMutator = fieldDefinitionMutator; } - public FieldInfo(String name, String label, FieldDefinition.ColumnType columnType) + public FieldInfo(String name, String label, ColumnType columnType) { this(FieldKey.fromParts(name.trim()), label, columnType, null); } @@ -32,7 +38,7 @@ public FieldInfo(String name, String label) this(name, label, null); } - public FieldInfo(String name, FieldDefinition.ColumnType columnType) + public FieldInfo(String name, ColumnType columnType) { this(name, null, columnType); } @@ -42,61 +48,105 @@ public FieldInfo(String name) this(name, null, null); } + /** + * Creates a FieldInfo with a semi-random name + */ + public static FieldInfo random(String namePart, ColumnType columnType) + { + return new FieldInfo(TestDataGenerator.randomFieldName(namePart), columnType); + } + + /** + * Creates a String field with a semi-random name + */ + public static FieldInfo random(String namePart) + { + return random(namePart, null); + } + + /** + * + * @param fieldDefinitionMutator will be invoked by {@link #getFieldDefinition()} + * @return a new FieldInfo with the provided mutator. Any existing mutator will be replaced. + */ + @Contract(pure = true) public FieldInfo customizeFieldDefinition(Consumer fieldDefinitionMutator) { + FieldDefinition verifier = new FieldDefinition("temp", _columnType); + fieldDefinitionMutator.accept(verifier); + if (verifier.getLabel() != null) + { + throw new IllegalArgumentException("FieldDefinition customizer should not modify field label"); + } return new FieldInfo(_fieldKey, _label, _columnType, fieldDefinitionMutator); } + @Contract(pure = true) protected String getRawLabel() { return _label; } + @Contract(pure = true) public String getLabel() { return Objects.requireNonNullElseGet(getRawLabel(), () -> FieldDefinition.labelFromName(_fieldKey.getName())); } + @Override + @Contract(pure = true) public FieldKey getFieldKey() { return _fieldKey; } + @Contract(pure = true) public String getName() { return _fieldKey.getName(); } + @Contract(pure = true) public FieldKey child(String name) { return _fieldKey.child(name); } + /** + * @return A FieldDefinition to be used for domain creation + */ + @Contract(pure = true) public FieldDefinition getFieldDefinition() { return getFieldDefinition(_columnType); } + /** + * Shared lookup definitions might want to customize the target table. + * @param lookupContainerPath the containerPath to use for the lookup + * @return A FieldDefinition to be used for domain creation + */ + @Contract(pure = true) public FieldDefinition getFieldDefinition(String lookupContainerPath) { if (!_columnType.isLookup()) { - throw new IllegalArgumentException("Unable to set lookup container for %s column: %s".formatted(_columnType.getLabel(), getName())); + throw new IllegalArgumentException("Unable to set lookup container for %s column: %s".formatted(_columnType.getLabel(), _fieldKey.getName())); } else { String schema = _columnType.getLookupInfo().getSchema(); String table = _columnType.getLookupInfo().getTable(); - FieldDefinition.ColumnType columnType = _columnType.getRangeURI().equals(FieldDefinition.ColumnType.Integer.getRangeURI()) + ColumnType columnType = _columnType.getRangeURI().equals(ColumnType.Integer.getRangeURI()) ? new FieldDefinition.IntLookup(lookupContainerPath, schema, table) : new FieldDefinition.StringLookup(lookupContainerPath, schema, table); return getFieldDefinition(columnType); } } - private FieldDefinition getFieldDefinition(FieldDefinition.ColumnType columnType) + private FieldDefinition getFieldDefinition(ColumnType columnType) { - FieldDefinition fieldDefinition = new FieldDefinition(getName(), columnType); + FieldDefinition fieldDefinition = new FieldDefinition(_fieldKey.getName(), columnType); if (getRawLabel() != null) { fieldDefinition.setLabel(getRawLabel()); @@ -107,4 +157,28 @@ private FieldDefinition getFieldDefinition(FieldDefinition.ColumnType columnType } return fieldDefinition; } + + @Override + public int length() + { + return _fieldKey.length(); + } + + @Override + public char charAt(int index) + { + return _fieldKey.charAt(index); + } + + @Override + public @NotNull CharSequence subSequence(int start, int end) + { + return _fieldKey.subSequence(start, end); + } + + @Override + public @NotNull String toString() + { + return _fieldKey.toString(); + } } diff --git a/src/org/labkey/test/params/FieldKey.java b/src/org/labkey/test/params/FieldKey.java index 6070cbde5f..a238acbd6d 100644 --- a/src/org/labkey/test/params/FieldKey.java +++ b/src/org/labkey/test/params/FieldKey.java @@ -10,8 +10,11 @@ import java.util.Iterator; import java.util.List; -public class FieldKey implements CharSequence +public final class FieldKey implements CharSequence, WrapsFieldKey { + private static final String[] ILLEGAL = {"$", "/", "&", "}", "~", ",", "."}; + private static final String[] REPLACEMENT = {"$D", "$S", "$A", "$B", "$T", "$C", "$P"}; + public static final FieldKey EMPTY = new FieldKey(""); // Useful as a sort of FieldKey builder starting point public static final FieldKey SOURCES_FK = new FieldKey("DataInputs"); public static final FieldKey PARENTS_FK = new FieldKey("MaterialInputs"); @@ -36,6 +39,11 @@ private FieldKey(FieldKey parent, String child) _fieldKey = parent + SEPARATOR + encodePart(child); } + public static List getIllegalChars() + { + return List.of(ILLEGAL); + } + public static FieldKey fromParts(List parts) { FieldKey fieldKey = EMPTY; @@ -62,9 +70,9 @@ public static FieldKey fromParts(String... parts) */ public static @Nullable FieldKey fromFieldKey(CharSequence fieldKey) { - if (fieldKey instanceof FieldKey fk) + if (fieldKey instanceof WrapsFieldKey fk) { - return fk; + return fk.getFieldKey(); } else { @@ -86,15 +94,12 @@ public static FieldKey fromParts(String... parts) */ public static FieldKey fromName(CharSequence nameOrFieldKey) { - if (nameOrFieldKey instanceof FieldKey fk) - return fk; + if (nameOrFieldKey instanceof WrapsFieldKey fk) + return fk.getFieldKey(); else return fromParts(nameOrFieldKey.toString()); } - private static final String[] ILLEGAL = {"$", "/", "&", "}", "~", ",", "."}; - private static final String[] REPLACEMENT = {"$D", "$S", "$A", "$B", "$T", "$C", "$P"}; - public static String encodePart(String str) { return StringUtils.replaceEach(str, ILLEGAL, REPLACEMENT); @@ -148,6 +153,12 @@ public String[] getNameArray() return Arrays.stream(_fieldKey.split(SEPARATOR)).map(FieldKey::decodePart).toArray(String[]::new); } + @Override + public FieldKey getFieldKey() + { + return this; + } + @Override public @NotNull String toString() { @@ -173,7 +184,7 @@ public char charAt(int index) } @Override - public final boolean equals(Object o) + public boolean equals(Object o) { if (!(o instanceof FieldKey fieldKey)) return false; diff --git a/src/org/labkey/test/params/WrapsFieldKey.java b/src/org/labkey/test/params/WrapsFieldKey.java new file mode 100644 index 0000000000..2199ec42f9 --- /dev/null +++ b/src/org/labkey/test/params/WrapsFieldKey.java @@ -0,0 +1,6 @@ +package org.labkey.test.params; + +public interface WrapsFieldKey +{ + FieldKey getFieldKey(); +} diff --git a/src/org/labkey/test/tests/component/EditableGridTest.java b/src/org/labkey/test/tests/component/EditableGridTest.java index 757e6b1ec5..3fd32ace1c 100644 --- a/src/org/labkey/test/tests/component/EditableGridTest.java +++ b/src/org/labkey/test/tests/component/EditableGridTest.java @@ -1341,49 +1341,49 @@ public void testInputCellValidation() checker().verifyEquals("Cell error should be absent when a row is added on page load", 0, testGrid.getCellErrors().size()); log("Input empty string for required field should trigger cell warning."); - testGrid.setCellValue(1, REQ_STR_FIELD.getFieldKey(), " "); - checker().verifyEquals("Cell warning status not as expected at row " + 1 + " for col " + REQ_STR_FIELD.getLabel(), true, testGrid.hasCellError(1, REQ_STR_FIELD.getFieldKey())); - checker().verifyEquals("Cell warning msg not as expected at row " + 1 + " for col " + REQ_STR_FIELD.getLabel(), REQ_STR_FIELD.getLabel() + " is required.", testGrid.getCellPopoverText(1, REQ_STR_FIELD.getFieldKey())); + testGrid.setCellValue(1, REQ_STR_FIELD, " "); + checker().verifyEquals("Cell warning status not as expected at row " + 1 + " for col " + REQ_STR_FIELD.getLabel(), true, testGrid.hasCellError(1, REQ_STR_FIELD)); + checker().verifyEquals("Cell warning msg not as expected at row " + 1 + " for col " + REQ_STR_FIELD.getLabel(), REQ_STR_FIELD.getLabel() + " is required.", testGrid.getCellPopoverText(1, REQ_STR_FIELD)); mouseOver(testGrid.getCell(0, "Row")); // dismiss warning popup - testGrid.setCellValue(1, REQ_INT_FIELD.getFieldKey(), " "); - checker().verifyEquals("Cell warning status not as expected at row " + 1 + " for col " + REQ_INT_FIELD.getLabel(), true, testGrid.hasCellError(1, REQ_INT_FIELD.getFieldKey())); - checker().verifyEquals("Cell warning msg not as expected at row " + 1 + " for col " + REQ_INT_FIELD.getLabel(), REQ_INT_FIELD.getLabel() + " is required.", testGrid.getCellPopoverText(1, REQ_INT_FIELD.getFieldKey())); + testGrid.setCellValue(1, REQ_INT_FIELD, " "); + checker().verifyEquals("Cell warning status not as expected at row " + 1 + " for col " + REQ_INT_FIELD.getLabel(), true, testGrid.hasCellError(1, REQ_INT_FIELD)); + checker().verifyEquals("Cell warning msg not as expected at row " + 1 + " for col " + REQ_INT_FIELD.getLabel(), REQ_INT_FIELD.getLabel() + " is required.", testGrid.getCellPopoverText(1, REQ_INT_FIELD)); log("Correct values should remove cell warning, keep entering wrong values should update warning"); mouseOver(testGrid.getCell(0, "Row")); // dismiss warning popup - testGrid.setCellValue(0, STR_FIELD.getFieldKey(), ""); + testGrid.setCellValue(0, STR_FIELD, ""); mouseOver(testGrid.getCell(0, "Row")); - testGrid.setCellValue(1, STR_FIELD.getFieldKey(), "This value is too long"); + testGrid.setCellValue(1, STR_FIELD, "This value is too long"); mouseOver(testGrid.getCell(0, "Row")); - testGrid.setCellValue(0, REQ_STR_FIELD.getFieldKey(), "good"); + testGrid.setCellValue(0, REQ_STR_FIELD, "good"); mouseOver(testGrid.getCell(0, "Row")); - testGrid.setCellValue(1, REQ_STR_FIELD.getFieldKey(), "This value is too long"); - checker().verifyEquals("Cell warning status not as expected at row " + 0 + " for col " + STR_FIELD.getLabel(), false, testGrid.hasCellError(0, STR_FIELD.getFieldKey())); - checker().verifyEquals("Cell warning msg not as expected at row " + 1 + " for col " + STR_FIELD.getLabel(), "22/10 characters", testGrid.getCellPopoverText(1, STR_FIELD.getFieldKey())); - checker().verifyEquals("Cell warning status not as expected at row " + 0 + " for col " + REQ_STR_FIELD.getLabel(), false, testGrid.hasCellError(0, REQ_STR_FIELD.getFieldKey())); - checker().verifyEquals("Cell warning msg not as expected at row " + 1 + " for col " + REQ_STR_FIELD.getLabel(), "22/10 characters", testGrid.getCellPopoverText(1, REQ_STR_FIELD.getFieldKey())); + testGrid.setCellValue(1, REQ_STR_FIELD, "This value is too long"); + checker().verifyEquals("Cell warning status not as expected at row " + 0 + " for col " + STR_FIELD.getLabel(), false, testGrid.hasCellError(0, STR_FIELD)); + checker().verifyEquals("Cell warning msg not as expected at row " + 1 + " for col " + STR_FIELD.getLabel(), "22/10 characters", testGrid.getCellPopoverText(1, STR_FIELD)); + checker().verifyEquals("Cell warning status not as expected at row " + 0 + " for col " + REQ_STR_FIELD.getLabel(), false, testGrid.hasCellError(0, REQ_STR_FIELD)); + checker().verifyEquals("Cell warning msg not as expected at row " + 1 + " for col " + REQ_STR_FIELD.getLabel(), "22/10 characters", testGrid.getCellPopoverText(1, REQ_STR_FIELD)); log("Input invalid data type value should trigger cell warnings."); mouseOver(testGrid.getCell(0, "Row")); // dismiss warning popup - testGrid.setCellValue(0, INT_FIELD.getFieldKey(), "1.23"); + testGrid.setCellValue(0, INT_FIELD, "1.23"); mouseOver(testGrid.getCell(0, "Row")); // dismiss warning popup - testGrid.setCellValue(0, FLOAT_FIELD.getFieldKey(), "not float"); + testGrid.setCellValue(0, FLOAT_FIELD, "not float"); mouseOver(testGrid.getCell(0, "Row")); // dismiss warning popup - testGrid.setCellValue(0, BOOL_FIELD.getFieldKey(), "not bool"); - checker().verifyEquals("Cell warning msg not as expected at row " + 0 + " for col " + INT_FIELD.getLabel(), "Invalid integer", testGrid.getCellPopoverText(0, INT_FIELD.getFieldKey())); - checker().verifyEquals("Cell warning msg not as expected at row " + 0 + " for col " + FLOAT_FIELD.getLabel(), "Invalid decimal", testGrid.getCellPopoverText(0, FLOAT_FIELD.getFieldKey())); - checker().verifyEquals("Cell warning msg not as expected at row " + 0 + " for col " + BOOL_FIELD.getLabel(), "Invalid boolean", testGrid.getCellPopoverText(0, BOOL_FIELD.getFieldKey())); + testGrid.setCellValue(0, BOOL_FIELD, "not bool"); + checker().verifyEquals("Cell warning msg not as expected at row " + 0 + " for col " + INT_FIELD.getLabel(), "Invalid integer", testGrid.getCellPopoverText(0, INT_FIELD)); + checker().verifyEquals("Cell warning msg not as expected at row " + 0 + " for col " + FLOAT_FIELD.getLabel(), "Invalid decimal", testGrid.getCellPopoverText(0, FLOAT_FIELD)); + checker().verifyEquals("Cell warning msg not as expected at row " + 0 + " for col " + BOOL_FIELD.getLabel(), "Invalid boolean", testGrid.getCellPopoverText(0, BOOL_FIELD)); log("Correct values should remove data type warning."); mouseOver(testGrid.getCell(0, "Row")); // dismiss warning popup - testGrid.setCellValue(0, INT_FIELD.getFieldKey(), "123"); - checker().verifyFalse("Cell warning should disappear after correcting value", testGrid.hasCellError(0, INT_FIELD.getFieldKey())); + testGrid.setCellValue(0, INT_FIELD, "123"); + checker().verifyFalse("Cell warning should disappear after correcting value", testGrid.hasCellError(0, INT_FIELD)); log("Required value warning should be absent before the cell is acted on"); - checker().verifyFalse("Required value warning should not be present on page init", testGrid.hasCellError(0, REQ_STR_FIELD.getFieldKey())); + checker().verifyFalse("Required value warning should not be present on page init", testGrid.hasCellError(0, REQ_STR_FIELD)); mouseOver(testGrid.getCell(0, "Row")); // dismiss warning popup - testGrid.clearCellValue(1, REQ_STR_FIELD.getFieldKey()); - checker().verifyTrue("Required value warning should show up after removing a value from cell", testGrid.hasCellError(1, REQ_STR_FIELD.getFieldKey())); + testGrid.clearCellValue(1, REQ_STR_FIELD); + checker().verifyTrue("Required value warning should show up after removing a value from cell", testGrid.hasCellError(1, REQ_STR_FIELD)); } @Test @@ -1400,7 +1400,7 @@ public void testPasteCellValidation() testGrid.addRows(3); log("Pasting invalid values"); - testGrid.selectCell(0, STR_FIELD.getFieldKey()); + testGrid.selectCell(0, STR_FIELD); actionPaste(null, rowsToString(clipRows)); List> expectedCellWarnings = List.of( @@ -1423,50 +1423,50 @@ public void testPasteCellValidation() "Bad value popup did not go away.", 500); log("Correct missing required fields should remove corresponding cell warnings"); - testGrid.setCellValue(1, REQ_STR_FIELD.getFieldKey(), " "); - checker().verifyTrue("Cell warning should be present after setting another invalid value", testGrid.hasCellError(1, REQ_STR_FIELD.getFieldKey())); - mouseOver(testGrid.getCell(0, STR_FIELD.getFieldKey())); // dismiss warning popup - testGrid.setCellValue(1, REQ_INT_FIELD.getFieldKey(), "2"); - mouseOver(testGrid.getCell(0, STR_FIELD.getFieldKey())); // dismiss warning popup - testGrid.setCellValue(1, REQ_TEXTCHOICE_FIELD.getFieldKey(), List.of("Orange")); - mouseOver(testGrid.getCell(0, STR_FIELD.getFieldKey())); // dismiss warning popup - testGrid.setCellValue(1, REQ_LOOKUP_FIELD.getFieldKey(), List.of("Orange")); - mouseOver(testGrid.getCell(0, STR_FIELD.getFieldKey())); // dismiss warning popup - testGrid.setCellValue(1, REQ_STR_FIELD.getFieldKey(), "not empty"); - mouseOver(testGrid.getCell(0, STR_FIELD.getFieldKey())); // dismiss warning popup - testGrid.setCellValue(1, REQ_DATETIME_FIELD.getFieldKey(), LocalDateTime.of(2024, 7, 7, 10, 30)); - mouseOver(testGrid.getCell(0, STR_FIELD.getFieldKey())); // dismiss warning popup - testGrid.setCellValue(1, REQ_TIME_FIELD.getFieldKey(), LocalTime.of(2, 30)); + testGrid.setCellValue(1, REQ_STR_FIELD, " "); + checker().verifyTrue("Cell warning should be present after setting another invalid value", testGrid.hasCellError(1, REQ_STR_FIELD)); + mouseOver(testGrid.getCell(0, STR_FIELD)); // dismiss warning popup + testGrid.setCellValue(1, REQ_INT_FIELD, "2"); + mouseOver(testGrid.getCell(0, STR_FIELD)); // dismiss warning popup + testGrid.setCellValue(1, REQ_TEXTCHOICE_FIELD, List.of("Orange")); + mouseOver(testGrid.getCell(0, STR_FIELD)); // dismiss warning popup + testGrid.setCellValue(1, REQ_LOOKUP_FIELD, List.of("Orange")); + mouseOver(testGrid.getCell(0, STR_FIELD)); // dismiss warning popup + testGrid.setCellValue(1, REQ_STR_FIELD, "not empty"); + mouseOver(testGrid.getCell(0, STR_FIELD)); // dismiss warning popup + testGrid.setCellValue(1, REQ_DATETIME_FIELD, LocalDateTime.of(2024, 7, 7, 10, 30)); + mouseOver(testGrid.getCell(0, STR_FIELD)); // dismiss warning popup + testGrid.setCellValue(1, REQ_TIME_FIELD, LocalTime.of(2, 30)); for (FieldInfo field : ALL_FIELDS) { - checker().verifyFalse("Cell warning be absent after required values are provided: " + field.getLabel(), testGrid.hasCellError(1, field.getFieldKey())); + checker().verifyFalse("Cell warning be absent after required values are provided: " + field.getLabel(), testGrid.hasCellError(1, field)); } log("Enter another bad value should retain cell warning"); - testGrid.setCellValue(2, INT_FIELD.getFieldKey(), "bad"); - checker().verifyTrue("Cell warning should be present after setting another invalid value", testGrid.hasCellError(2, INT_FIELD.getFieldKey())); + testGrid.setCellValue(2, INT_FIELD, "bad"); + checker().verifyTrue("Cell warning should be present after setting another invalid value", testGrid.hasCellError(2, INT_FIELD)); checker().screenShotIfNewError("after required value correction error"); log("Correct bad data type values should remove paste data warnings"); - testGrid.setCellValue(2, STR_FIELD.getFieldKey(), "good"); - testGrid.setCellValue(2, REQ_STR_FIELD.getFieldKey(), "good"); - testGrid.setCellValue(2, INT_FIELD.getFieldKey(), "1"); - testGrid.setCellValue(2, REQ_INT_FIELD.getFieldKey(), "134"); - testGrid.setCellValue(2, BOOL_FIELD.getFieldKey(), "on"); - testGrid.setCellValue(2, FLOAT_FIELD.getFieldKey(), "1.23"); - testGrid.setCellValue(2, TEXTCHOICE_FIELD.getFieldKey(), List.of("red")); - testGrid.setCellValue(2, REQ_TEXTCHOICE_FIELD.getFieldKey(), List.of("red")); - testGrid.setCellValue(2, LOOKUP_FIELD.getFieldKey(), List.of("kiwi")); - testGrid.setCellValue(2, REQ_LOOKUP_FIELD.getFieldKey(), List.of("kiwi")); - testGrid.setCellValue(2, DATE_FIELD.getFieldKey(), LocalDate.of(2024, 7, 7)); - testGrid.setCellValue(2, TIME_FIELD.getFieldKey(), LocalTime.of(2, 30)); - testGrid.setCellValue(2, REQ_DATETIME_FIELD.getFieldKey(), LocalDateTime.of(2024, 7, 7, 10, 30)); - testGrid.setCellValue(2, REQ_TIME_FIELD.getFieldKey(), LocalTime.of(2, 30)); + testGrid.setCellValue(2, STR_FIELD, "good"); + testGrid.setCellValue(2, REQ_STR_FIELD, "good"); + testGrid.setCellValue(2, INT_FIELD, "1"); + testGrid.setCellValue(2, REQ_INT_FIELD, "134"); + testGrid.setCellValue(2, BOOL_FIELD, "on"); + testGrid.setCellValue(2, FLOAT_FIELD, "1.23"); + testGrid.setCellValue(2, TEXTCHOICE_FIELD, List.of("red")); + testGrid.setCellValue(2, REQ_TEXTCHOICE_FIELD, List.of("red")); + testGrid.setCellValue(2, LOOKUP_FIELD, List.of("kiwi")); + testGrid.setCellValue(2, REQ_LOOKUP_FIELD, List.of("kiwi")); + testGrid.setCellValue(2, DATE_FIELD, LocalDate.of(2024, 7, 7)); + testGrid.setCellValue(2, TIME_FIELD, LocalTime.of(2, 30)); + testGrid.setCellValue(2, REQ_DATETIME_FIELD, LocalDateTime.of(2024, 7, 7, 10, 30)); + testGrid.setCellValue(2, REQ_TIME_FIELD, LocalTime.of(2, 30)); for (FieldInfo field : ALL_FIELDS) { - checker().verifyFalse("Cell warning should be absent after correct values are provided: " + field.getLabel(), testGrid.hasCellError(2, field.getFieldKey())); + checker().verifyFalse("Cell warning should be absent after correct values are provided: " + field.getLabel(), testGrid.hasCellError(2, field)); } checker().screenShotIfNewError("after data correction error"); @@ -1474,14 +1474,14 @@ public void testPasteCellValidation() for (FieldInfo field : ALL_FIELDS) { // start date before year 1000 shouldn't trigger warning - checker().verifyFalse("Cell warning should not be present for: " + field.getLabel(), testGrid.hasCellError(0, field.getFieldKey())); + checker().verifyFalse("Cell warning should not be present for: " + field.getLabel(), testGrid.hasCellError(0, field)); } log("Verify UI is interactable with values before 1000-01-01"); - testGrid.setCellValue(3, DATE_FIELD.getFieldKey(), LocalDate.of(2024, 7, 7)); - testGrid.setCellValue(3, TIME_FIELD.getFieldKey(), LocalTime.of(2, 30)); - testGrid.setCellValue(3, REQ_DATETIME_FIELD.getFieldKey(), LocalDate.of(2024, 7, 7)); - testGrid.setCellValue(3, REQ_TIME_FIELD.getFieldKey(), LocalTime.of(2, 30)); + testGrid.setCellValue(3, DATE_FIELD, LocalDate.of(2024, 7, 7)); + testGrid.setCellValue(3, TIME_FIELD, LocalTime.of(2, 30)); + testGrid.setCellValue(3, REQ_DATETIME_FIELD, LocalDate.of(2024, 7, 7)); + testGrid.setCellValue(3, REQ_TIME_FIELD, LocalTime.of(2, 30)); checker().screenShotIfNewError("Issue 46767"); @@ -1496,9 +1496,9 @@ private void verifyCellWarning(EditableGrid testGrid, List expectedWarni String expectedWarning = expectedWarnings.get(col); FieldInfo field = ALL_FIELDS.get(col); - checker().verifyEquals("Cell warning status not as expected at row " + rowId + " for col " + field.getLabel(), !StringUtils.isEmpty(expectedWarning), testGrid.hasCellError(rowId, field.getFieldKey())); + checker().verifyEquals("Cell warning status not as expected at row " + rowId + " for col " + field.getLabel(), !StringUtils.isEmpty(expectedWarning), testGrid.hasCellError(rowId, field)); if (!StringUtils.isEmpty(expectedWarning)) - checker().verifyEquals("Cell warning msg not as expected at row " + rowId + " for col " + field.getLabel(), expectedWarning, testGrid.getCellPopoverText(rowId, field.getFieldKey())); + checker().verifyEquals("Cell warning msg not as expected at row " + rowId + " for col " + field.getLabel(), expectedWarning, testGrid.getCellPopoverText(rowId, field)); } } @@ -1513,15 +1513,15 @@ public void testFillCellValidation() testGrid.addRows(3); log("Start with pasting invalid values, so we can fill down invalid values for dropdowns and data/time inputs"); - testGrid.selectCell(0, STR_FIELD.getFieldKey()); + testGrid.selectCell(0, STR_FIELD); actionPaste(null, rowsToString(clipRows)); // Scroll one column to the right into view, this will help ensure the REQ_LOOKUP_FIELD is within the viewport. var index = testGrid.getColumnLabels().indexOf(REQ_LOOKUP_FIELD.getLabel() + " *") + 1; scrollIntoView(testGrid.getCell(0, testGrid.getColumnLabels().get(index))); - WebElement fillFrom = testGrid.getCell(0, REQ_LOOKUP_FIELD.getFieldKey()); - WebElement fillTo = testGrid.getCell(2, REQ_LOOKUP_FIELD.getFieldKey()); + WebElement fillFrom = testGrid.getCell(0, REQ_LOOKUP_FIELD); + WebElement fillTo = testGrid.getCell(2, REQ_LOOKUP_FIELD); testGrid.dragFill(fillFrom, fillTo); List expectedWarnings = Arrays.asList("22/10 characters", REQ_STR_FIELD.getLabel() + " is required.", "Invalid integer", "Invalid integer", diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index ccc16975c5..68315ca9bf 100644 --- a/src/org/labkey/test/util/TestDataGenerator.java +++ b/src/org/labkey/test/util/TestDataGenerator.java @@ -697,19 +697,7 @@ public File writeData(String fileName, Iterator> rowIterator) { try { - String fileExtension = fileName.toLowerCase().substring(fileName.lastIndexOf('.') + 1); - switch (fileExtension) - { - case "xlsx": - case "xls": - return TestDataUtils.writeRowsToExcel(fileName, rowIterator); - case "csv": - return TestDataUtils.writeRowsToFile(fileName, rowIterator, CSVFormat.DEFAULT); - case "tsv": - return TestDataUtils.writeRowsToFile(fileName, rowIterator, CSVFormat.TDF); - default: - throw new IllegalArgumentException("Unsupported file extension: " + fileExtension); - } + return TestDataUtils.writeRowsToFile(fileName, rowIterator); } catch (IOException e) { diff --git a/src/org/labkey/test/util/data/TestDataUtils.java b/src/org/labkey/test/util/data/TestDataUtils.java index 30739cca0d..49be055156 100644 --- a/src/org/labkey/test/util/data/TestDataUtils.java +++ b/src/org/labkey/test/util/data/TestDataUtils.java @@ -308,6 +308,23 @@ public static List> replaceMapKeys(List> return updatedRows; } + public static File writeRowsToFile(String fileName, List> rows) throws IOException + { + return writeRowsToFile(fileName, rows.iterator()); + } + + public static File writeRowsToFile(String fileName, Iterator> rowIterator) throws IOException + { + String fileExtension = fileName.toLowerCase().substring(fileName.lastIndexOf('.') + 1); + return switch (fileExtension) + { + case "xlsx", "xls" -> TestDataUtils.writeRowsToExcel(fileName, rowIterator); + case "csv" -> TestDataUtils.writeRowsToFile(fileName, rowIterator, CSVFormat.DEFAULT); + case "tsv" -> TestDataUtils.writeRowsToFile(fileName, rowIterator, CSVFormat.TDF); + default -> throw new IllegalArgumentException("Unsupported file extension: " + fileExtension); + }; + } + public static File writeRowsToTsv(String fileName, List> rows) throws IOException { return writeRowsToFile(fileName, rows, CSVFormat.TDF); @@ -491,8 +508,8 @@ protected boolean shouldQuote(String value) } } - public static final String[] DECODED = {"\\", "$", "/", "&", "}", "~", ",", "."}; - public static final String[] ENCODED = {"\\\\", "\\$", "\\/", "\\&", "\\}", "\\~", "\\,", "\\."}; + private static final String[] DECODED = {"\\", "$", "/", "&", "}", "~", ",", "."}; + private static final String[] ENCODED = {"\\\\", "\\$", "\\/", "\\&", "\\}", "\\~", "\\,", "\\."}; public static String getEscapedNameExpression(String encoded) { return StringUtils.replaceEach(encoded, DECODED, ENCODED); From ee0a4c48e84fcf84b633b1447e55be64499e718c Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Wed, 25 Jun 2025 09:11:16 -0700 Subject: [PATCH 3/7] Check for blank fieldkey parts --- src/org/labkey/test/params/FieldKey.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/org/labkey/test/params/FieldKey.java b/src/org/labkey/test/params/FieldKey.java index a238acbd6d..56de9f3b4c 100644 --- a/src/org/labkey/test/params/FieldKey.java +++ b/src/org/labkey/test/params/FieldKey.java @@ -50,8 +50,6 @@ public static FieldKey fromParts(List parts) for (String part : parts) { - if (StringUtils.isBlank(part)) - throw new IllegalArgumentException("FieldKey contains a blank part: " + parts); fieldKey = fieldKey.child(part); } @@ -115,15 +113,18 @@ public FieldKey getParent() return _parent; } - public FieldKey child(String name) + public FieldKey child(String part) { + if (StringUtils.isBlank(part)) + throw new IllegalArgumentException("FieldKey can't have blank part(s): " + this); + if (StringUtils.isBlank(getName())) { - return new FieldKey(name); + return new FieldKey(part); } else { - return new FieldKey(this, name); + return new FieldKey(this, part); } } From 36310b1d50ed28581e47e57e764517343e9ea49f Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Wed, 25 Jun 2025 11:42:27 -0700 Subject: [PATCH 4/7] Improve FieldInfo and use random names for nameexpressiontest --- src/org/labkey/test/params/FieldInfo.java | 10 ++ .../tests/SampleTypeNameExpressionTest.java | 101 +++++++++++------- .../test/tests/list/ListLookupTest.java | 3 +- src/org/labkey/test/util/EscapeUtil.java | 10 +- .../labkey/test/util/TestDataGenerator.java | 2 +- .../labkey/test/util/data/TestDataUtils.java | 13 +-- 6 files changed, 87 insertions(+), 52 deletions(-) diff --git a/src/org/labkey/test/params/FieldInfo.java b/src/org/labkey/test/params/FieldInfo.java index 7bbd51dc02..7e5ce8b71f 100644 --- a/src/org/labkey/test/params/FieldInfo.java +++ b/src/org/labkey/test/params/FieldInfo.java @@ -3,6 +3,7 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.labkey.test.params.FieldDefinition.ColumnType; +import org.labkey.test.util.EscapeUtil; import org.labkey.test.util.TestDataGenerator; import java.util.Objects; @@ -106,6 +107,15 @@ public String getName() return _fieldKey.getName(); } + /** + * Get name escaped for use in sample or source name expressions + */ + @Contract(pure = true) + public String getExpName() + { + return EscapeUtil.escapeForNameExpression(getName()); + } + @Contract(pure = true) public FieldKey child(String name) { diff --git a/src/org/labkey/test/tests/SampleTypeNameExpressionTest.java b/src/org/labkey/test/tests/SampleTypeNameExpressionTest.java index bc83b90ada..672b3088b6 100644 --- a/src/org/labkey/test/tests/SampleTypeNameExpressionTest.java +++ b/src/org/labkey/test/tests/SampleTypeNameExpressionTest.java @@ -32,6 +32,8 @@ import org.labkey.test.pages.experiment.CreateSampleTypePage; import org.labkey.test.pages.experiment.UpdateSampleTypePage; import org.labkey.test.params.FieldDefinition; +import org.labkey.test.params.FieldDefinition.ColumnType; +import org.labkey.test.params.FieldInfo; import org.labkey.test.params.experiment.SampleTypeDefinition; import org.labkey.test.util.AuditLogHelper; import org.labkey.test.util.DataRegionTable; @@ -40,6 +42,7 @@ import org.labkey.test.util.SampleTypeHelper; import org.labkey.test.util.TestDataGenerator; import org.labkey.test.util.TextUtils; +import org.labkey.test.util.data.TestDataUtils; import org.labkey.test.util.exp.SampleTypeAPIHelper; import org.openqa.selenium.TimeoutException; import org.openqa.selenium.WebElement; @@ -61,7 +64,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.labkey.test.params.FieldDefinition.DOMAIN_TRICKY_CHARACTERS; -import static org.labkey.test.util.data.TestDataUtils.getEscapedNameExpression; +import static org.labkey.test.util.EscapeUtil.escapeForNameExpression; @Category({Daily.class}) @BaseWebDriverTest.ClassTimeout(minutes = 5) @@ -71,7 +74,16 @@ public class SampleTypeNameExpressionTest extends BaseWebDriverTest private static final String DEFAULT_SAMPLE_PARENT_VALUE = "SS" + TestDataGenerator.randomString(3).replaceAll("[_)]", "."); // '_' is used as delimiter to get batchRandomId and ) is used to close the defaultValue() private static final String PARENT_SAMPLE_TYPE = "PS" + DOMAIN_TRICKY_CHARACTERS; - private static final String PARENT_SAMPLE_TYPE_INPUT = "PS" + getEscapedNameExpression(DOMAIN_TRICKY_CHARACTERS); + private static final String PARENT_SAMPLE_TYPE_INPUT = escapeForNameExpression(PARENT_SAMPLE_TYPE); + + private static final FieldInfo COL_STR = FieldInfo.random("{Str", ColumnType.String); + private static final FieldInfo COL_INT = FieldInfo.random("}Int", ColumnType.Integer); + private static final FieldInfo COL_DATE = FieldInfo.random("$Date", ColumnType.DateAndTime); + + // No random names for deriving sample type + // Issue 53306: LabKey Server: Derive samples form does not distinguish between fields that differ by 'special characters' + private static final FieldInfo COL_DSTR = new FieldInfo("DStr", ColumnType.String); + private static final FieldInfo COL_DINT = new FieldInfo("DInt", ColumnType.Integer); private static final String PARENT_SAMPLE_01 = "parent01"; private static final String PARENT_SAMPLE_02 = "parent02"; @@ -108,10 +120,10 @@ public static void setupProject() throws IOException, CommandException private void addDataRow(TestDataGenerator dataGenerator, String name, int intVal) { Map sampleData = Map.of( - "name", name, - "Int", intVal, - "Str", "Parent Sample " + ((char) (intVal + 95)), - "Date", intVal + "/14/2020"); + "name", name, + COL_INT.getName(), intVal, + COL_STR.getName(), "Parent Sample " + ((char) (intVal + 95)), + COL_DATE.getName(), intVal + "/14/2020"); dataGenerator.addCustomRow(sampleData); } @@ -126,9 +138,9 @@ private void doSetup() throws IOException, CommandException SampleTypeDefinition definition = new SampleTypeDefinition(PARENT_SAMPLE_TYPE); definition = definition.setFields(List.of( - new FieldDefinition("Str", FieldDefinition.ColumnType.String), - new FieldDefinition("Int", FieldDefinition.ColumnType.Integer), - new FieldDefinition("Date", FieldDefinition.ColumnType.DateAndTime))); + COL_STR.getFieldDefinition(), + COL_INT.getFieldDefinition(), + COL_DATE.getFieldDefinition())); TestDataGenerator dataGenerator = SampleTypeAPIHelper.createEmptySampleType(getCurrentContainerPath(), definition); @@ -159,20 +171,23 @@ public void preTest() @Test public void testSimpleNameExpression() { - String nameExpression = "${A}-${B}.${genId}.${batchRandomId}.${container}.${randomId}"; - String data = """ - A\tB\tC - a\tb\tc - a\tb\tc - a\tb\tc - """; + FieldInfo colA = FieldInfo.random("A"); + FieldInfo colB = FieldInfo.random("B"); + FieldInfo colC = FieldInfo.random("C"); + String nameExpression = "${%s}-${%s}.${genId}.${batchRandomId}.${container}.${randomId}" + .formatted(escapeForNameExpression(colA.getName()), escapeForNameExpression(colB.getName())); + String data = TestDataUtils.stringFromRows(List.of( + List.of(colA.getName(), colB.getName(), colC.getName()), + List.of("a", "b", "c"), + List.of("a", "b", "c"), + List.of("a", "b", "c"))); SampleTypeHelper sampleHelper = new SampleTypeHelper(this); sampleHelper.createSampleType(new SampleTypeDefinition("SimpleNameExprTest") .setNameExpression(nameExpression) - .setFields(List.of(new FieldDefinition("A", FieldDefinition.ColumnType.String), - new FieldDefinition("B", FieldDefinition.ColumnType.String), - new FieldDefinition("C", FieldDefinition.ColumnType.String))), + .setFields(List.of(colA.getFieldDefinition(), + colB.getFieldDefinition(), + colC.getFieldDefinition())), data ); @@ -230,10 +245,10 @@ public void testDeriveFromCommentLikeParents() expectedNames.add("[" + PARENT_SAMPLE_01 + ", " + PARENT_SAMPLE_02 + ", " + PARENT_SAMPLE_03 + "]-child"); assertEquals("Sample names are not as expected", expectedNames, names); - log("Verify import tsv should successfully create derivatives from parent starting with #, as long as this is not the 1st field in the row"); - data = "Description\tMaterialInputs/" + EscapeUtil.fieldKeyEncodePart(PARENT_SAMPLE_TYPE) + "\n"; // fully encoded - data += "Parent with leading # should work\t" + PARENT_SAMPLE_03 + "\n"; - data += "Parents with leading # should work\t" + PARENT_SAMPLE_03 + "," + PARENT_SAMPLE_02 + "\n"; + log("Verify import tsv should successfully create derivatives from parent starting with #, as long as values are quoted"); + data = "MaterialInputs/" + EscapeUtil.fieldKeyEncodePart(PARENT_SAMPLE_TYPE) + "\n"; // fully encoded + data += "\"" + PARENT_SAMPLE_03 + "\"\n"; + data += "\"" + PARENT_SAMPLE_03 + "," + PARENT_SAMPLE_02 + "\"\n"; sampleHelper.bulkImport(data); @@ -476,7 +491,7 @@ public void testLookupNameExpression() throws Exception // now create a sampleType with a Color column that looks up to Colors var sampleTypeDef = new SampleTypeDefinition(nameExpSamples) .setFields(List.of(new FieldDefinition("ColorLookup", colorsLookup), - new FieldDefinition("Noun", FieldDefinition.ColumnType.String))) + new FieldDefinition("Noun", ColumnType.String))) .setNameExpression("TEST-${ColorLookup/ColorCode}"); // hopefully this will resolve the 'ColorCode' column from the list SampleTypeAPIHelper.createEmptySampleType(getProjectName(), sampleTypeDef); @@ -542,7 +557,7 @@ private void verifyNames(String sampleTypeName, String header, String nameExpres .setNameExpression(nameExpression); if (currentTypeAlias != null) definition = definition.setParentAliases(Map.of(currentTypeAlias, "(Current Sample Type)")); - definition = definition.setFields(List.of(new FieldDefinition("FieldB", FieldDefinition.ColumnType.String))); + definition = definition.setFields(List.of(new FieldDefinition("FieldB", ColumnType.String))); sampleHelper.createSampleType(definition, data); assertTextPresent(nameExpression); @@ -587,7 +602,7 @@ public void testDeriveSampleFromSampleDetailsPage() throws Exception SampleTypeHelper sampleHelper = new SampleTypeHelper(this); final String sampleType = "DerivedUI_SampleType"; - final String nameExpression = String.format("DUI_${genId}_${materialInputs/%s/Str}", PARENT_SAMPLE_TYPE_INPUT); + final String nameExpression = String.format("DUI_${genId}_${materialInputs/%s/%s}", PARENT_SAMPLE_TYPE_INPUT, COL_STR.getExpName()); // TODO: When Issue 44760 this test can be updated to use a parent alias in the name expression. @@ -600,8 +615,8 @@ public void testDeriveSampleFromSampleDetailsPage() throws Exception createPage.setNameExpression(nameExpression); createPage.addFields(Arrays.asList( - new FieldDefinition("Int", FieldDefinition.ColumnType.Integer), - new FieldDefinition("Str", FieldDefinition.ColumnType.String))); + COL_DINT.getFieldDefinition(), + COL_DSTR.getFieldDefinition())); createPage.clickSave(); @@ -614,7 +629,7 @@ public void testDeriveSampleFromSampleDetailsPage() throws Exception checker().verifyTrue(String.format("Doesn't look like there is a link to the parent sample '%s'.", PARENT_SAMPLE_01), isElementPresent(Locator.linkWithText(PARENT_SAMPLE_01))); - final String ancestorNameExpression = String.format("GrandChild_${MaterialInputs/%s/..[MaterialInputs/%s]/Str}_${genId}", sampleType, PARENT_SAMPLE_TYPE_INPUT); + final String ancestorNameExpression = String.format("GrandChild_${MaterialInputs/%s/..[MaterialInputs/%s]/%s}_${genId}", sampleType, PARENT_SAMPLE_TYPE_INPUT, COL_STR.getExpName()); log("Change the sample type name expression to support grandparent property lookup: " + ancestorNameExpression); goToProjectHome(); SampleTypeHelper sampleTypeHelper = new SampleTypeHelper(this); @@ -634,8 +649,9 @@ public void testDeriveSampleFromSampleDetailsPage() throws Exception String flagStringBulkImport = "bulk imported grand child."; log("Derive a sample using bulk import but give it no name. The name expression should be used to name the derived sample."); - String importData = "MaterialInputs/DerivedUI_SampleType\tStr\n" + - derivedSampleName + "\t" + flagStringBulkImport + "\n"; + String importData = TestDataUtils.stringFromRows(List.of( + List.of("MaterialInputs/%s".formatted(sampleType), COL_DSTR.getName()), + List.of(derivedSampleName, flagStringBulkImport))); sampleHelper.goToSampleType(sampleType); sampleHelper.getSamplesDataRegionTable() .clickImportBulkData() @@ -645,7 +661,7 @@ public void testDeriveSampleFromSampleDetailsPage() throws Exception waitForElement(Locator.tagWithText("td", flagStringBulkImport)); DataRegionTable table = sampleHelper.getSamplesDataRegionTable(); - int newSampleRowInd = table.getRowIndex("Str", flagStringBulkImport); + int newSampleRowInd = table.getRowIndex(COL_DSTR.getName(), flagStringBulkImport); String grandImportChildSampleName = table.getDataAsText(newSampleRowInd, "Name"); click(Locator.tagWithText("td", grandImportChildSampleName)); waitForElement(Locator.tagWithText("td", flagStringBulkImport)); @@ -673,8 +689,8 @@ private String deriveSample(String parentSampleName, String parentSampleType, St selectOptionByText(Locator.name("targetSampleTypeId"), String.format("%s in /%s", targetSampleType, getProjectName())); clickButton("Next"); - setFormElement(Locator.name("outputSample1_Int"), intVal); - setFormElement(Locator.name("outputSample1_Str"), strVal); + setFormElement(Locator.name("outputSample1_%s".formatted(COL_DINT.getName())), intVal); + setFormElement(Locator.name("outputSample1_%s".formatted(COL_DSTR.getName())), strVal); clickButton("Submit"); waitForElement(Locator.tagWithText("td", strVal)); @@ -755,7 +771,7 @@ public void testNameExpressionPreview() throws IOException, CommandException createPage.addParentAlias(parentAlias, String.format("Sample Type: %1$s (%2$s)", PARENT_SAMPLE_TYPE, PROJECT_NAME)); log("Use a name expression using a field from the named parent, with parent type not encoded."); - String nameExpressionBad = String.format("SNP_${genId}_${%1$s/Int}_${materialInputs/%2$s/Str}", parentAlias, PARENT_SAMPLE_TYPE); + String nameExpressionBad = String.format("SNP_${genId}_${%s/%s}_${materialInputs/%s/%s}", parentAlias, COL_INT.getExpName(), PARENT_SAMPLE_TYPE, COL_STR.getExpName()); createPage.setNameExpression(nameExpressionBad); actualMsg = createPage.getNameExpressionPreview(); checker().withScreenshot("Parent_Fields_Preview_Error") @@ -763,10 +779,19 @@ public void testNameExpressionPreview() throws IOException, CommandException // Make the tooltip go away. mouseOver(createPage.getComponentElement()); + log("Use a name expression using a field from the named parent, with parent type not encoded."); + nameExpressionBad = String.format("SNP_${genId}_${%s/$s}_${materialInputs/%s/%s}", parentAlias, COL_INT.getExpName(), PARENT_SAMPLE_TYPE, COL_STR.getName()); + createPage.setNameExpression(nameExpressionBad); + actualMsg = createPage.getNameExpressionPreview(); + checker().withScreenshot("Parent_Fields_Preview_Error") + .verifyTrue("Tool-tip message does not contain expected example.", actualMsg.contains("Unable to generate example name from the current pattern. Check for syntax errors.")); + // Make the tooltip go away. + mouseOver(createPage.getComponentElement()); + log("Use a name expression using a field from the named parent, with parent type encoded correctly."); - String nameExpression = String.format("SNP_${genId}_${%1$s/Int}_${materialInputs/%2$s/Str}", parentAlias, PARENT_SAMPLE_TYPE_INPUT); + String nameExpression = String.format("SNP_${genId}_${%s/%s}_${materialInputs/%s/%s}", parentAlias, COL_INT.getExpName(), PARENT_SAMPLE_TYPE_INPUT, COL_STR.getExpName()); createPage.setNameExpression(nameExpression); - expectedMsg = generateExpectedToolTip("SNP_1001_3_parentStrValue"); + expectedMsg = generateExpectedToolTip("SNP_1001_3_parent%sValue".formatted(COL_STR.getName())); actualMsg = createPage.getNameExpressionPreview(); log("Verify that the preview shows the fields as expected."); checker().withScreenshot("Parent_Fields_Preview_Error") @@ -777,7 +802,7 @@ public void testNameExpressionPreview() throws IOException, CommandException log("Use a name expression with a formatted date."); - nameExpression = String.format("SNP_${genId}_${%s/Date:date('yyyy-MM-dd')}", parentAlias); + nameExpression = String.format("SNP_${genId}_${%s/%s:date('yyyy-MM-dd')}", parentAlias, COL_DATE.getExpName()); createPage.setNameExpression(nameExpression); diff --git a/src/org/labkey/test/tests/list/ListLookupTest.java b/src/org/labkey/test/tests/list/ListLookupTest.java index 150c9fc6d1..056a1a0115 100644 --- a/src/org/labkey/test/tests/list/ListLookupTest.java +++ b/src/org/labkey/test/tests/list/ListLookupTest.java @@ -1,6 +1,5 @@ package org.labkey.test.tests.list; -import org.apache.commons.csv.CSVFormat; import org.jetbrains.annotations.Nullable; import org.junit.BeforeClass; import org.junit.Test; @@ -286,7 +285,7 @@ private void validateListValues(List> expectedValue) private String tsvFromColumn(List column) { List> rows = column.stream().map(Collections::singletonList).toList(); - return TestDataUtils.stringFromRows(rows, CSVFormat.TDF); + return TestDataUtils.stringFromRows(rows); } @Override diff --git a/src/org/labkey/test/util/EscapeUtil.java b/src/org/labkey/test/util/EscapeUtil.java index 652fef926f..8e7241219e 100644 --- a/src/org/labkey/test/util/EscapeUtil.java +++ b/src/org/labkey/test/util/EscapeUtil.java @@ -23,11 +23,9 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.regex.Pattern; import java.util.stream.Collectors; -/** - * UNDONE: Refactor useful methods from PageFlowUtil into util.jar that can be used by the test harness then delete this class. - */ public class EscapeUtil { static public String jsString(String s) @@ -146,4 +144,10 @@ public static String getMarkupEscapedValue(String value) { return StringEscapeUtils.escapeXml11(value); } + + private static final Pattern nameExpressionNeedsEscaping = Pattern.compile("([\\\\$/&}~,.])"); + public static String escapeForNameExpression(String name) + { + return nameExpressionNeedsEscaping.matcher(name).replaceAll("\\\\$1"); + } } diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index 68315ca9bf..40e05536f1 100644 --- a/src/org/labkey/test/util/TestDataGenerator.java +++ b/src/org/labkey/test/util/TestDataGenerator.java @@ -74,7 +74,7 @@ public class TestDataGenerator private static final char WIDE_PLACEHOLDER = '\u03A0'; // 'Π' - Wide character can't be picked from the string with 'charAt' private static final String NON_LATIN_STRING = "\u0438\uC548\u306F"; // "и안は" // chose a Character random from this String - public static final String CHARSET_STRING = "ABCDEFG01234abcdefvxyz~!@#$%^&*()-+=_{}[]|:;\"',.<>" + NON_LATIN_STRING + WIDE_PLACEHOLDER; + public static final String CHARSET_STRING = "ABCDEFG01234abcdefvxyz~!@#$%^&*()-+=_{}[]|:;\"\\',.<>" + NON_LATIN_STRING + WIDE_PLACEHOLDER; public static final String ALPHANUMERIC_STRING = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvxyz"; public static final String DOMAIN_SPECIAL_STRING = "+- _.:&()/"; public static final String ILLEGAL_DOMAIN_NAME_CHARSET = "<>[]{};,`\"~!@#$%^*=|?\\"; diff --git a/src/org/labkey/test/util/data/TestDataUtils.java b/src/org/labkey/test/util/data/TestDataUtils.java index 49be055156..00e13e869d 100644 --- a/src/org/labkey/test/util/data/TestDataUtils.java +++ b/src/org/labkey/test/util/data/TestDataUtils.java @@ -443,6 +443,11 @@ public static String stringFromRows(List> rows, CSVFormat format) return stringWriter.toString(); } + public static String stringFromRows(List> rows) + { + return stringFromRows(rows, CSVFormat.TDF); + } + /** * Used to quote values to be written to a TSV file * @see org.labkey.api.data.TSVWriter @@ -507,12 +512,4 @@ protected boolean shouldQuote(String value) return StringUtils.containsAny(value, _escapedChars); } } - - private static final String[] DECODED = {"\\", "$", "/", "&", "}", "~", ",", "."}; - private static final String[] ENCODED = {"\\\\", "\\$", "\\/", "\\&", "\\}", "\\~", "\\,", "\\."}; - public static String getEscapedNameExpression(String encoded) - { - return StringUtils.replaceEach(encoded, DECODED, ENCODED); - } - } From 52ccae889f1822fe53e7f781f05eac1ae2500289 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Wed, 25 Jun 2025 13:44:49 -0700 Subject: [PATCH 5/7] Update EditableGridTest with FieldInfo.random --- .../components/ui/grids/EditableGrid.java | 2 +- .../tests/component/EditableGridTest.java | 100 +++++++++--------- 2 files changed, 50 insertions(+), 52 deletions(-) diff --git a/src/org/labkey/test/components/ui/grids/EditableGrid.java b/src/org/labkey/test/components/ui/grids/EditableGrid.java index efbfd1df22..f0ff60f2a2 100644 --- a/src/org/labkey/test/components/ui/grids/EditableGrid.java +++ b/src/org/labkey/test/components/ui/grids/EditableGrid.java @@ -127,7 +127,7 @@ public List getColumnLabels() return elementCache().getColumnLabels(); } - protected Integer getColumnIndex(CharSequence columnIdentifier) + public Integer getColumnIndex(CharSequence columnIdentifier) { return elementCache().getColumnIndex(columnIdentifier); } diff --git a/src/org/labkey/test/tests/component/EditableGridTest.java b/src/org/labkey/test/tests/component/EditableGridTest.java index 3fd32ace1c..92a7cc60fb 100644 --- a/src/org/labkey/test/tests/component/EditableGridTest.java +++ b/src/org/labkey/test/tests/component/EditableGridTest.java @@ -43,32 +43,31 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.labkey.test.util.TestDataGenerator.randomDomainName; -import static org.labkey.test.util.TestDataGenerator.randomFieldName; @Category({Daily.class}) public class EditableGridTest extends BaseWebDriverTest { private static final String EXTRAPOLATING_SAMPLE_TYPE = randomDomainName("ExtrapolatingSampleType"); - private static final String ASC_STRING = randomFieldName("Ascending String"); - private static final String DESC_STRING = randomFieldName("Descending String"); - private static final String ASC_INT = randomFieldName("Ascending Int"); - private static final String DESC_INT = randomFieldName("Descending Int"); - private static final String ASC_DATE = randomFieldName("Ascending Date"); - private static final String DESC_DATE = randomFieldName("Descending Date"); + private static final FieldInfo ASC_STRING = FieldInfo.random("Ascending String"); + private static final FieldInfo DESC_STRING = FieldInfo.random("Descending String"); + private static final FieldInfo ASC_INT = FieldInfo.random("Ascending Int", ColumnType.Integer); + private static final FieldInfo DESC_INT = FieldInfo.random("Descending Int", ColumnType.Integer); + private static final FieldInfo ASC_DATE = FieldInfo.random("Ascending Date", ColumnType.DateAndTime); + private static final FieldInfo DESC_DATE = FieldInfo.random("Descending Date", ColumnType.DateAndTime); private static final String FILLING_SAMPLE_TYPE = randomDomainName("FillingSampleType"); - private static final String FILL_STRING = randomFieldName("Filling String"); - private static final String FILL_MULTI_LINE = randomFieldName("Filling Multi Line"); - private static final String FILL_INT = randomFieldName("Filling Int"); - private static final String FILL_DATE = randomFieldName("Filling Date"); + private static final FieldInfo FILL_STRING = FieldInfo.random("Filling String"); + private static final FieldInfo FILL_MULTI_LINE = FieldInfo.random("Filling Multi Line", ColumnType.MultiLine); + private static final FieldInfo FILL_INT = FieldInfo.random("Filling Int", ColumnType.Integer); + private static final FieldInfo FILL_DATE = FieldInfo.random("Filling Date", ColumnType.DateAndTime); private static final String PASTING_SAMPLE_TYPE = randomDomainName("PastingSampleType"); - private static final String PASTE_1 = randomFieldName("Paste Column 1"); - private static final String PASTE_2 = randomFieldName("Paste Column 2"); - private static final String PASTE_3 = randomFieldName("Paste Column 3"); - private static final String PASTE_4 = randomFieldName("Paste Column 4"); - private static final String PASTE_5 = randomFieldName("Paste Column 5"); - private static final String PASTE_ML = randomFieldName("Paste Multi Line"); + private static final FieldInfo PASTE_1 = FieldInfo.random("Paste Column 1"); + private static final FieldInfo PASTE_2 = FieldInfo.random("Paste Column 2"); + private static final FieldInfo PASTE_3 = FieldInfo.random("Paste Column 3"); + private static final FieldInfo PASTE_4 = FieldInfo.random("Paste Column 4"); + private static final FieldInfo PASTE_5 = FieldInfo.random("Paste Column 5"); + private static final FieldInfo PASTE_ML = FieldInfo.random("Paste Multi Line", ColumnType.MultiLine); private static final List TEXT_CHOICES = Arrays.asList("red", "Orange", "YELLOW"); private static final String LOOKUP_LIST = randomDomainName("Fruits"); @@ -76,27 +75,27 @@ public class EditableGridTest extends BaseWebDriverTest private static final String ALL_TYPE_SAMPLE_TYPE = randomDomainName("AllFieldsSampleType"); - private static final FieldInfo STR_FIELD = new FieldInfo(randomFieldName("strCol")) + private static final FieldInfo STR_FIELD = FieldInfo.random("strCol") .customizeFieldDefinition(fd -> fd.setScale(10)); - private static final FieldInfo REQ_STR_FIELD = new FieldInfo(randomFieldName("strColReq")) + private static final FieldInfo REQ_STR_FIELD = FieldInfo.random("strColReq") .customizeFieldDefinition(fd -> fd.setScale(10).setRequired(true)); - private static final FieldInfo INT_FIELD = new FieldInfo(randomFieldName("intCol"), ColumnType.Integer); - private static final FieldInfo REQ_INT_FIELD = new FieldInfo(randomFieldName("intColReq"), ColumnType.Integer) + private static final FieldInfo INT_FIELD = FieldInfo.random("intCol", ColumnType.Integer); + private static final FieldInfo REQ_INT_FIELD = FieldInfo.random("intColReq", ColumnType.Integer) .customizeFieldDefinition(fd -> fd.setRequired(true)); - private static final FieldInfo DATE_FIELD = new FieldInfo(randomFieldName("dateCol"), ColumnType.Date); - private static final FieldInfo REQ_DATETIME_FIELD = new FieldInfo(randomFieldName("datetimeColReq"), ColumnType.DateAndTime) + private static final FieldInfo DATE_FIELD = FieldInfo.random("dateCol", ColumnType.Date); + private static final FieldInfo REQ_DATETIME_FIELD = FieldInfo.random("datetimeColReq", ColumnType.DateAndTime) .customizeFieldDefinition(fd -> fd.setRequired(true)); - private static final FieldInfo TIME_FIELD = new FieldInfo(randomFieldName("timeCol"), ColumnType.Time); - private static final FieldInfo REQ_TIME_FIELD = new FieldInfo(randomFieldName("timeColReq"), ColumnType.Time) + private static final FieldInfo TIME_FIELD = FieldInfo.random("timeCol", ColumnType.Time); + private static final FieldInfo REQ_TIME_FIELD = FieldInfo.random("timeColReq", ColumnType.Time) .customizeFieldDefinition(fd -> fd.setRequired(true)); - private static final FieldInfo BOOL_FIELD = new FieldInfo(randomFieldName("boolCol"), ColumnType.Boolean); - private static final FieldInfo FLOAT_FIELD = new FieldInfo(randomFieldName("floatCol"), ColumnType.Decimal); - private static final FieldInfo TEXTCHOICE_FIELD = new FieldInfo(randomFieldName("textchoiceCol"), ColumnType.TextChoice) + private static final FieldInfo BOOL_FIELD = FieldInfo.random("boolCol", ColumnType.Boolean); + private static final FieldInfo FLOAT_FIELD = FieldInfo.random("floatCol", ColumnType.Decimal); + private static final FieldInfo TEXTCHOICE_FIELD = FieldInfo.random("textchoiceCol", ColumnType.TextChoice) .customizeFieldDefinition(fd -> fd.setTextChoiceValues(TEXT_CHOICES)); - private static final FieldInfo REQ_TEXTCHOICE_FIELD = new FieldInfo(randomFieldName("textchoiceColReq"), ColumnType.TextChoice) + private static final FieldInfo REQ_TEXTCHOICE_FIELD = FieldInfo.random("textchoiceColReq", ColumnType.TextChoice) .customizeFieldDefinition(fd -> fd.setRequired(true).setTextChoiceValues(TEXT_CHOICES)); - private static final FieldInfo LOOKUP_FIELD = new FieldInfo(randomFieldName("lookupCol"), new IntLookup(null, "lists", LOOKUP_LIST)); - private static final FieldInfo REQ_LOOKUP_FIELD = new FieldInfo(randomFieldName("lookupColReq"), new IntLookup(null, "lists", LOOKUP_LIST)) + private static final FieldInfo LOOKUP_FIELD = FieldInfo.random("lookupCol", new IntLookup(null, "lists", LOOKUP_LIST)); + private static final FieldInfo REQ_LOOKUP_FIELD = FieldInfo.random("lookupColReq", new IntLookup(null, "lists", LOOKUP_LIST)) .customizeFieldDefinition(fd -> fd.setRequired(true)); final List ALL_FIELDS = Arrays.asList(STR_FIELD, REQ_STR_FIELD, INT_FIELD, REQ_INT_FIELD, @@ -131,32 +130,32 @@ private void doSetup() throws Exception new SampleTypeDefinition(EXTRAPOLATING_SAMPLE_TYPE) .setFields( List.of( - new FieldDefinition(ASC_STRING, ColumnType.String), - new FieldDefinition(DESC_STRING, ColumnType.String), - new FieldDefinition(ASC_INT, ColumnType.Integer), - new FieldDefinition(DESC_INT, ColumnType.Integer), - new FieldDefinition(ASC_DATE, ColumnType.DateAndTime), - new FieldDefinition(DESC_DATE, ColumnType.DateAndTime) + ASC_STRING.getFieldDefinition(), + DESC_STRING.getFieldDefinition(), + ASC_INT.getFieldDefinition(), + DESC_INT.getFieldDefinition(), + ASC_DATE.getFieldDefinition(), + DESC_DATE.getFieldDefinition() )) .create(connection, getProjectName()); new SampleTypeDefinition(FILLING_SAMPLE_TYPE) .setFields( List.of( - new FieldDefinition(FILL_STRING, ColumnType.String), - new FieldDefinition(FILL_MULTI_LINE, ColumnType.MultiLine), - new FieldDefinition(FILL_INT, ColumnType.Integer), - new FieldDefinition(FILL_DATE, ColumnType.DateAndTime) + FILL_STRING.getFieldDefinition(), + FILL_MULTI_LINE.getFieldDefinition(), + FILL_INT.getFieldDefinition(), + FILL_DATE.getFieldDefinition() )) .create(connection, getProjectName()); new SampleTypeDefinition(PASTING_SAMPLE_TYPE) .setFields( List.of( - new FieldDefinition(PASTE_1, ColumnType.String), - new FieldDefinition(PASTE_2, ColumnType.String), - new FieldDefinition(PASTE_3, ColumnType.String), - new FieldDefinition(PASTE_4, ColumnType.String), - new FieldDefinition(PASTE_5, ColumnType.String), - new FieldDefinition(PASTE_ML, ColumnType.MultiLine) + PASTE_1.getFieldDefinition(), + PASTE_2.getFieldDefinition(), + PASTE_3.getFieldDefinition(), + PASTE_4.getFieldDefinition(), + PASTE_5.getFieldDefinition(), + PASTE_ML.getFieldDefinition() )) .create(connection, getProjectName()); @@ -999,8 +998,7 @@ public void testShiftArrowSelectHorizontal() .verifyEquals("There should be no grid cells already selected. Fatal error.", 0, editableGrid.getSelectedCells().size()); - List columns = editableGrid.getColumnLabels(); - int startColumn = columns.indexOf(PASTE_1); + int startColumn = editableGrid.getColumnIndex(PASTE_1); int gridRow = 4; WebElement startCell = editableGrid.getCell(gridRow, PASTE_1); @@ -1318,10 +1316,10 @@ private static String rowsToString(List> rows) .collect(Collectors.joining("\n")); } - private static List setCellValues(EditableGrid testGrid, String ascString, Object... values) + private static List setCellValues(EditableGrid testGrid, CharSequence columnIdentifier, Object... values) { List cells = new ArrayList<>(); - List.of(values).forEach(value -> cells.add(testGrid.setCellValue(cells.size(), ascString, value))); + List.of(values).forEach(value -> cells.add(testGrid.setCellValue(cells.size(), columnIdentifier, value))); return cells; } From c635b3cdc854555021cdd4fbbd3b801bde7680d6 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Wed, 25 Jun 2025 14:00:51 -0700 Subject: [PATCH 6/7] Fix test build --- src/org/labkey/test/BaseWebDriverTest.java | 2 ++ src/org/labkey/test/util/data/TestDataUtils.java | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/org/labkey/test/BaseWebDriverTest.java b/src/org/labkey/test/BaseWebDriverTest.java index c1b4f71ea4..61a5167cb3 100644 --- a/src/org/labkey/test/BaseWebDriverTest.java +++ b/src/org/labkey/test/BaseWebDriverTest.java @@ -215,6 +215,8 @@ public abstract class BaseWebDriverTest extends LabKeySiteWrapper implements Cle public static final double DELTA = 10E-10; + @Deprecated // Going away soon + public static final String[] ILLEGAL_QUERY_KEY_CHARACTERS = FieldKey.getIllegalChars().toArray(new String[0]); public static final String ALL_ILLEGAL_QUERY_KEY_CHARACTERS = StringUtils.join(FieldKey.getIllegalChars(), ""); // See TSVWriter.shouldQuote. Generally we are not able to use the tab and new line characters when creating field names in the UI, but including here for completeness public static final String[] TRICKY_IMPORT_FIELD_CHARACTERS = {"\\", "\"", "\\t", ",", "\\n", "\\r"}; diff --git a/src/org/labkey/test/util/data/TestDataUtils.java b/src/org/labkey/test/util/data/TestDataUtils.java index 00e13e869d..099465ee0f 100644 --- a/src/org/labkey/test/util/data/TestDataUtils.java +++ b/src/org/labkey/test/util/data/TestDataUtils.java @@ -14,6 +14,7 @@ import org.labkey.serverapi.reader.TabLoader; import org.labkey.test.TestFileUtils; import org.labkey.test.params.FieldDefinition; +import org.labkey.test.util.EscapeUtil; import org.labkey.test.util.TestDataGenerator; import org.labkey.test.util.TestLogger; @@ -512,4 +513,10 @@ protected boolean shouldQuote(String value) return StringUtils.containsAny(value, _escapedChars); } } + + @Deprecated // Going away soon + public static String getEscapedNameExpression(String name) + { + return EscapeUtil.escapeForNameExpression(name); + } } From 239dbbc25c14be41d886dbfcabed840eadf7aa04 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Thu, 26 Jun 2025 13:55:51 -0700 Subject: [PATCH 7/7] Reintroduce previous test coverage --- .../tests/SampleTypeNameExpressionTest.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/org/labkey/test/tests/SampleTypeNameExpressionTest.java b/src/org/labkey/test/tests/SampleTypeNameExpressionTest.java index 672b3088b6..827209e16e 100644 --- a/src/org/labkey/test/tests/SampleTypeNameExpressionTest.java +++ b/src/org/labkey/test/tests/SampleTypeNameExpressionTest.java @@ -245,10 +245,9 @@ public void testDeriveFromCommentLikeParents() expectedNames.add("[" + PARENT_SAMPLE_01 + ", " + PARENT_SAMPLE_02 + ", " + PARENT_SAMPLE_03 + "]-child"); assertEquals("Sample names are not as expected", expectedNames, names); - log("Verify import tsv should successfully create derivatives from parent starting with #, as long as values are quoted"); + log("Verify import tsv should successfully create derivatives from line starting with #, as long as values are quoted"); data = "MaterialInputs/" + EscapeUtil.fieldKeyEncodePart(PARENT_SAMPLE_TYPE) + "\n"; // fully encoded data += "\"" + PARENT_SAMPLE_03 + "\"\n"; - data += "\"" + PARENT_SAMPLE_03 + "," + PARENT_SAMPLE_02 + "\"\n"; sampleHelper.bulkImport(data); @@ -258,9 +257,25 @@ public void testDeriveFromCommentLikeParents() log("generated sample names:"); names.forEach(this::log); - assertEquals(4, names.size()); + assertEquals(3, names.size()); expectedNames.add(PARENT_SAMPLE_03 + "-child"); + assertEquals("Sample names are not as expected", expectedNames, names); + + log("Verify import tsv should successfully create derivatives from parent starting with #, as long as this is not the 1st field in the row"); + data = "Description\tMaterialInputs/" + EscapeUtil.fieldKeyEncodePart(PARENT_SAMPLE_TYPE) + "\n"; // fully encoded + data += "Parents with leading # should work\t" + PARENT_SAMPLE_03 + "," + PARENT_SAMPLE_02 + "\n"; + + sampleHelper.bulkImport(data); + + names = materialTable.getColumnDataAsText("Name"); + Collections.reverse(names); + + log("generated sample names:"); + names.forEach(this::log); + + assertEquals(4, names.size()); + expectedNames.add("[" + PARENT_SAMPLE_03 + ", " + PARENT_SAMPLE_02 + "]-child"); assertEquals("Sample names are not as expected", expectedNames, names);