From 8d4c9fec324ea648b8c81e5e05a9e6b1fc9e3bb2 Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Tue, 13 May 2025 16:12:06 -0700 Subject: [PATCH 01/26] Coverage for Issue 52142 --- .../test/tests/query/QueryLookupTest.java | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 src/org/labkey/test/tests/query/QueryLookupTest.java diff --git a/src/org/labkey/test/tests/query/QueryLookupTest.java b/src/org/labkey/test/tests/query/QueryLookupTest.java new file mode 100644 index 0000000000..a079703d01 --- /dev/null +++ b/src/org/labkey/test/tests/query/QueryLookupTest.java @@ -0,0 +1,88 @@ +package org.labkey.test.tests.query; + +import org.junit.BeforeClass; +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.labkey.api.query.QueryHelper; +import org.labkey.test.BaseWebDriverTest; +import org.labkey.test.params.FieldDefinition; +import org.labkey.test.params.list.IntListDefinition; +import org.labkey.test.util.TestDataGenerator; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; + +@Category({}) +public class QueryLookupTest extends BaseWebDriverTest +{ + private static final String PROJECT_NAME = "QueryLookupTest" + TRICKY_CHARACTERS_FOR_PROJECT_NAMES; + private static final String USER = "template_user@querylookuptest.test"; + private static final String LIST_NAME = TestDataGenerator.randomFieldName("list", "<>[]{};,`\"~!@#$%^*=|?\\"); + private static final FieldDefinition.ColumnType LIST_KEY_TYPE = FieldDefinition.ColumnType.Integer; + private static final String LIST_KEY_NAME = TestDataGenerator.randomFieldName("listkey"); + private static final FieldDefinition NAME_COLUMN = + new FieldDefinition("Name", FieldDefinition.ColumnType.String); + private static final FieldDefinition TSHIRT_COLUMN = + new FieldDefinition("TShirt", FieldDefinition.ColumnType.String); + + @Override + protected void doCleanup(boolean afterTest) + { + _containerHelper.deleteProject(getProjectName(), afterTest); + _userHelper.deleteUsers(afterTest, USER); + } + + @BeforeClass + public static void setupProject() throws Exception + { + QueryLookupTest init = getCurrentTest(); + + init.doSetup(); + } + + private void doSetup() throws Exception + { + _containerHelper.createProject(PROJECT_NAME, null); + _userHelper.createUser(USER); + + // create a list + var dgen = new IntListDefinition(LIST_NAME) + .setFields(List.of( + new FieldDefinition(LIST_KEY_NAME, LIST_KEY_TYPE), + NAME_COLUMN, + TSHIRT_COLUMN)) + .create(createDefaultConnection(), PROJECT_NAME); + dgen.withGeneratedRows(10) + .insertRows(); + + // create a query on the list + goToSchemaBrowser(); + //var queryPage = navigateToQuery("lists", LIST_NAME); + var queryPage = createNewQuery("lists", LIST_NAME); + + log("foo"); + } + + + // Issue 49511 Setting lookup to custom query shows "Error: Lookup target table does not exist." + @Test + public void testSomething() + { + assertTrue("Failing stub test", false); + } + + @Override + protected String getProjectName() + { + return PROJECT_NAME; + } + + @Override + public List getAssociatedModules() + { + return Arrays.asList(); + } +} From 9a85233c9f0073113c8966e059d3e7a3a90bb4f5 Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Wed, 14 May 2025 16:14:42 -0700 Subject: [PATCH 02/26] coverage for Issue 49511 --- .../test/tests/query/QueryLookupTest.java | 106 +++++++++++++----- 1 file changed, 81 insertions(+), 25 deletions(-) diff --git a/src/org/labkey/test/tests/query/QueryLookupTest.java b/src/org/labkey/test/tests/query/QueryLookupTest.java index a079703d01..132e74a1ac 100644 --- a/src/org/labkey/test/tests/query/QueryLookupTest.java +++ b/src/org/labkey/test/tests/query/QueryLookupTest.java @@ -1,28 +1,30 @@ package org.labkey.test.tests.query; +import org.assertj.core.api.Assertions; import org.junit.BeforeClass; -import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; -import org.labkey.api.query.QueryHelper; import org.labkey.test.BaseWebDriverTest; +import org.labkey.test.Locator; +import org.labkey.test.categories.Daily; +import org.labkey.test.pages.list.EditListDefinitionPage; import org.labkey.test.params.FieldDefinition; import org.labkey.test.params.list.IntListDefinition; -import org.labkey.test.util.TestDataGenerator; + +import org.labkey.test.params.list.VarListDefinition; +import org.labkey.test.util.DataRegionTable; +import org.labkey.test.util.EscapeUtil; import java.util.Arrays; import java.util.List; -import static org.junit.Assert.*; -@Category({}) +@Category({Daily.class}) public class QueryLookupTest extends BaseWebDriverTest { private static final String PROJECT_NAME = "QueryLookupTest" + TRICKY_CHARACTERS_FOR_PROJECT_NAMES; - private static final String USER = "template_user@querylookuptest.test"; - private static final String LIST_NAME = TestDataGenerator.randomFieldName("list", "<>[]{};,`\"~!@#$%^*=|?\\"); - private static final FieldDefinition.ColumnType LIST_KEY_TYPE = FieldDefinition.ColumnType.Integer; - private static final String LIST_KEY_NAME = TestDataGenerator.randomFieldName("listkey"); + private static final String LIST_NAME = "l&ist q"; + private static final FieldDefinition NAME_COLUMN = new FieldDefinition("Name", FieldDefinition.ColumnType.String); private static final FieldDefinition TSHIRT_COLUMN = @@ -32,7 +34,6 @@ public class QueryLookupTest extends BaseWebDriverTest protected void doCleanup(boolean afterTest) { _containerHelper.deleteProject(getProjectName(), afterTest); - _userHelper.deleteUsers(afterTest, USER); } @BeforeClass @@ -46,32 +47,87 @@ public static void setupProject() throws Exception private void doSetup() throws Exception { _containerHelper.createProject(PROJECT_NAME, null); - _userHelper.createUser(USER); // create a list - var dgen = new IntListDefinition(LIST_NAME) - .setFields(List.of( - new FieldDefinition(LIST_KEY_NAME, LIST_KEY_TYPE), - NAME_COLUMN, - TSHIRT_COLUMN)) + var dgen = new VarListDefinition(LIST_NAME) + .setFields(List.of(NAME_COLUMN, TSHIRT_COLUMN)) .create(createDefaultConnection(), PROJECT_NAME); dgen.withGeneratedRows(10) .insertRows(); + } + + // Issue 49511 Setting lookup to custom query shows "Error: Lookup target table does not exist." + @Test + public void testLookupToQueryColumn() throws Exception + { + var insertedRows = executeSelectRowCommand("lists", LIST_NAME).getRows(); + var itemNames = insertedRows.stream().map(a-> a.get("name").toString()).toList(); + String secondList = "secondList"; + // create a query from LIST_NAME list, with a key defined in the query xml + String queryName = "query from list"; + String querySql = """ + SELECT [list_name].Name, + [list_name].TShirt + FROM [list_name] + """.replace("[list_name]", EscapeUtil.getSqlQuotedValue(LIST_NAME)); + String queryXml = """ + + + + + true + + +
+
+ """.replace("[query_name]", EscapeUtil.getMarkupEscapedValue(queryName)) + .replace("[list_key]", EscapeUtil.getMarkupEscapedValue(NAME_COLUMN.getName())); // create a query on the list goToSchemaBrowser(); - //var queryPage = navigateToQuery("lists", LIST_NAME); - var queryPage = createNewQuery("lists", LIST_NAME); + createQuery(getProjectName(), queryName, "lists", querySql, queryXml, false); - log("foo"); - } + // now create another list, with a lookup to the custom query + new IntListDefinition(secondList, "Key") + .setFields(List.of(NAME_COLUMN, + new FieldDefinition("lookup", new FieldDefinition.StringLookup(getProjectName(), "lists", queryName)))) + .create(createDefaultConnection(), PROJECT_NAME); + // insert data into the list + goToManageLists(); + waitAndClickAndWait(Locator.linkWithText(secondList)); + var importPage = DataRegionTable.DataRegion(getDriver()).withName("query").waitFor() + .clickImportBulkData(); + StringBuilder builder = new StringBuilder("Name\tlookup\n"); + int textIndex = 0; + for (String name : itemNames) + { + builder.append(String.format("text-%d\t%s\n", textIndex, name)); + textIndex++; + } + importPage.setText(builder.toString()); + importPage.submit(); + + // verify the query-list-items resolve here + var secondListDataRegion = DataRegionTable.DataRegion(getDriver()).withName("query").waitFor(); + var resolvedLookupsInSecondList = secondListDataRegion.getColumnDataAsText("lookup"); + checker().withScreenshot("query lookup values not resolved") + .wrapAssertion(()-> Assertions.assertThat(resolvedLookupsInSecondList) + .as("the expected sample items were not resolved via query lookup") + .containsAll(itemNames)); + + // navigate to the secondlist edit page and verify the details on the lookup field row are correct + secondListDataRegion.clickHeaderButtonAndWait("Design"); + var listEditPage = new EditListDefinitionPage(getDriver()); + var lookupFieldRow = listEditPage.getFieldsPanel().getField("Lookup"); + checker().withScreenshot("unexpected lookup details") + .wrapAssertion(()-> Assertions.assertThat(lookupFieldRow.detailsMessage()) + .as("query lookup table not resolved") + .isEqualTo(String.format("/%s > lists > %s", PROJECT_NAME, queryName))); + // click the link in the details message, expect to navigate to a view of the query + clickAndWait(Locator.linkWithText(queryName).findElement(lookupFieldRow)); + assertTextPresent("lists Schema", queryName, PROJECT_NAME); - // Issue 49511 Setting lookup to custom query shows "Error: Lookup target table does not exist." - @Test - public void testSomething() - { - assertTrue("Failing stub test", false); } @Override From 8a3fd544aace758b8aae2bc2ea1ef3df496f36bd Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Wed, 14 May 2025 16:26:39 -0700 Subject: [PATCH 03/26] properly enquote tsv --- src/org/labkey/test/tests/query/QueryLookupTest.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/org/labkey/test/tests/query/QueryLookupTest.java b/src/org/labkey/test/tests/query/QueryLookupTest.java index 132e74a1ac..b5b9c54df8 100644 --- a/src/org/labkey/test/tests/query/QueryLookupTest.java +++ b/src/org/labkey/test/tests/query/QueryLookupTest.java @@ -14,9 +14,12 @@ import org.labkey.test.params.list.VarListDefinition; import org.labkey.test.util.DataRegionTable; import org.labkey.test.util.EscapeUtil; +import org.labkey.test.util.TestDataUtils; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; @Category({Daily.class}) @@ -98,14 +101,15 @@ public void testLookupToQueryColumn() throws Exception waitAndClickAndWait(Locator.linkWithText(secondList)); var importPage = DataRegionTable.DataRegion(getDriver()).withName("query").waitFor() .clickImportBulkData(); - StringBuilder builder = new StringBuilder("Name\tlookup\n"); + List> importData = new ArrayList<>(); int textIndex = 0; - for (String name : itemNames) + for (String lookupValue : itemNames) { - builder.append(String.format("text-%d\t%s\n", textIndex, name)); + String nameVal = String.format("text-%d", textIndex); + importData.add(Map.of("Name", nameVal, "lookup", lookupValue)); textIndex++; } - importPage.setText(builder.toString()); + importPage.setText(TestDataUtils.tsvStringFromRowMaps(importData, List.of("Name", "lookup"), true)); importPage.submit(); // verify the query-list-items resolve here From dbc978d719b72e7e3ba660c6651e053ed2be0cbd Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Thu, 15 May 2025 13:16:46 -0700 Subject: [PATCH 04/26] coverage for Issue 52171 --- src/org/labkey/test/tests/MenuBarTest.java | 61 +++++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/src/org/labkey/test/tests/MenuBarTest.java b/src/org/labkey/test/tests/MenuBarTest.java index 6c178e57c5..0ed5a9df14 100644 --- a/src/org/labkey/test/tests/MenuBarTest.java +++ b/src/org/labkey/test/tests/MenuBarTest.java @@ -15,6 +15,8 @@ */ package org.labkey.test.tests; +import org.assertj.core.api.Assertions; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; import org.labkey.test.BaseWebDriverTest; @@ -44,6 +46,19 @@ public class MenuBarTest extends BaseWebDriverTest private static final String DEM_STUDY_FOLDER = "DemStudyFolder"; private static final String STUDY_FOLDER = "StudyFolder"; + @BeforeClass + public static void setupProject() throws Exception + { + MenuBarTest initTest = getCurrentTest(); + initTest.doSetup(); + } + + protected void doSetup() + { + log("Open new project"); + _containerHelper.createProject(PROJECT_NAME, "Collaboration"); + } + @Override public List getAssociatedModules() { @@ -67,8 +82,7 @@ public void testSteps() PortalHelper portalHelper = new PortalHelper(this); WikiHelper wikiHelper = new WikiHelper(this); - log("Open new project"); - _containerHelper.createProject(PROJECT_NAME, "Collaboration"); + goToProjectHome(); goToProjectSettings(); clickAndWait(Locator.linkWithText("Menu Bar")); @@ -211,6 +225,49 @@ public void testSteps() openMenu("Folders"); } + // Issue 52171: All menus that link to absolute URLs are broken + @Test + public void testDocumentationMenuLinks() + { + goToProjectHome(); + clickUserMenuItem(false,"LabKey Documentation"); // expect documentation to pop in another window + switchToWindow(1); + checker().withScreenshot("unexpected_destination") + .wrapAssertion(()-> Assertions.assertThat(getURL().toString()) + .as("expect navigation to labkey.org /documentation") + .contains("labkey.org/Documentation")); + switchToMainWindow(); + closeExtraWindows(); + + goToProjectHome(); + clickUserMenuItem(true,"Support"); + checker().withScreenshot("unexpected_destination") + .wrapAssertion(()-> Assertions.assertThat(getURL().toString()) + .as("expect navigation to localhost/home/support") + .contains("localhost", "/home/support")); + + goToProjectHome(); + clickAdminMenuItem("Developer Links", "JavaScript API Reference"); + checker().withScreenshot("unexpected_destination") + .wrapAssertion(()-> Assertions.assertThat(getURL().toString()) + .as("expect navigation to labkey.org /download/clientapi_docs") + .endsWith("labkey.org/download/clientapi_docs/javascript-api/")); + + goToProjectHome(); + clickAdminMenuItem("Developer Links", "SQL Reference"); + checker().withScreenshot("unexpected_destination") + .wrapAssertion(()-> Assertions.assertThat(getURL().toString()) + .as("expect navigation to labkey.org /documentation") + .contains("labkey.org/Documentation")); + + goToProjectHome(); + clickAdminMenuItem("Developer Links", "XML Schema Reference"); + checker().withScreenshot("unexpected_destination") + .wrapAssertion(()-> Assertions.assertThat(getURL().toString()) + .as("expect navigation to labkey.org schema docs") + .endsWith("labkey.org/download/schema-docs/xml-schemas/")); + } + protected WebElement openMenu(String menuText) { WebElement menu = menuBarItem(menuText).findElement(getDriver()); From 445de90e2dd05e9f0a8d7708bf6b7074edde738b Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Mon, 19 May 2025 13:27:10 -0700 Subject: [PATCH 05/26] coverage for Issue 51843 --- .../labkey/test/tests/AdminConsoleTest.java | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/org/labkey/test/tests/AdminConsoleTest.java b/src/org/labkey/test/tests/AdminConsoleTest.java index c885b39ee5..e72e5d8818 100644 --- a/src/org/labkey/test/tests/AdminConsoleTest.java +++ b/src/org/labkey/test/tests/AdminConsoleTest.java @@ -20,6 +20,7 @@ import org.labkey.remoteapi.CommandException; import org.labkey.remoteapi.Connection; import org.labkey.remoteapi.SimpleGetCommand; +import org.labkey.remoteapi.SimplePostCommand; import org.labkey.test.BaseWebDriverTest; import org.labkey.test.Locator; import org.labkey.test.WebTestHelper; @@ -33,11 +34,13 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; @Category({Daily.class}) @BaseWebDriverTest.ClassTimeout(minutes = 3) @@ -151,6 +154,73 @@ public void testRibbonBar() assertElementNotPresent(ribbonLink); } + // Issue 51843 allow site banner configuration via api + @Test + public void testSiteBannerAPIConfiguration() throws Exception + { + goToAdminConsole(); + + String bannerMessage = "test banner message" + TRICKY_CHARACTERS; + Locator bannerLoc = Locator.tagWithClass("div", "lk-dismissable-warn").containing(bannerMessage); + + //As site admin + // set the message and show it + var showBannerCmd = new SimplePostCommand("admin", "setRibbonMessage.api"); + showBannerCmd.setParameters(Map.of("message", bannerMessage, + "show", true)); + showBannerCmd.execute(createDefaultConnection(), "/"); + refresh(); + // verify it is shown + checker().withScreenshot("banner not shown or not as expected") + .verifyTrue("expect banner to be shown", bannerLoc.isDisplayed(getDriver())); + + // hide the banner + var hideBannerCmd = new SimplePostCommand("admin", "setRibbonMessage.api"); + hideBannerCmd.setParameters(Map.of("show", false)); + hideBannerCmd.execute(createDefaultConnection(), "/"); + refresh(); + // verify it is hidden + checker().withScreenshot("banner is shown when not expected") + .verifyFalse("expect banner not to be shown", bannerLoc.isDisplayed(getDriver())); + + // restore it with the previous banner value + var restoreBannerCmd = new SimplePostCommand("admin", "setRibbonMessage.api"); + restoreBannerCmd.setParameters(Map.of("show", true)); + restoreBannerCmd.execute(createDefaultConnection(), "/"); + refresh(); + // verify it is restored with the previous value + checker().withScreenshot("banner not shown or not as expected") + .verifyTrue("expect banner to be shown", bannerLoc.isDisplayed(getDriver())); + + // as app admin + impersonateRole("Application Admin"); + var reHideBannerCmd = new SimplePostCommand("admin", "setRibbonMessage.api"); + reHideBannerCmd.setParameters(Map.of("show", false)); + try + { + reHideBannerCmd.execute(createDefaultConnection(), "/"); + fail("expect exception trying to call this API as app admin"); + } + catch (CommandException e) + { + // success, caller with app admin failed to hit this api + } + refresh(); + // verify it remains shown + checker().withScreenshot("banner is hidden when not expected") + .verifyTrue("expect banner to be shown", bannerLoc.isDisplayed(getDriver())); + + stopImpersonating(); + + // clean up after ourselves as site admin + var clearAndHideBannerCmd = new SimplePostCommand("admin", "setRibbonMessage.api"); + clearAndHideBannerCmd.setParameters(Map.of("show", false, "message", "")); + clearAndHideBannerCmd.execute(createDefaultConnection(), "/"); + refresh(); + checker().withScreenshot("banner is shown when not expected") + .verifyFalse("expect banner not to be shown", bannerLoc.isDisplayed(getDriver())); + } + @Test public void testAppAdminRole() { From d2f55d4993bcb006d33018089ada4ebda1468f7a Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Tue, 27 May 2025 14:29:41 -0700 Subject: [PATCH 06/26] coverage for Issue 52390 --- src/org/labkey/test/tests/SampleTypeTest.java | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/org/labkey/test/tests/SampleTypeTest.java b/src/org/labkey/test/tests/SampleTypeTest.java index c0dd03695a..169c76fb56 100644 --- a/src/org/labkey/test/tests/SampleTypeTest.java +++ b/src/org/labkey/test/tests/SampleTypeTest.java @@ -42,6 +42,7 @@ import org.labkey.test.components.html.OptionSelect; import org.labkey.test.pages.ImportDataPage; import org.labkey.test.pages.ReactAssayDesignerPage; +import org.labkey.test.pages.core.admin.BaseSettingsPage; import org.labkey.test.pages.experiment.CreateSampleTypePage; import org.labkey.test.pages.experiment.UpdateSampleTypePage; import org.labkey.test.params.FieldDefinition; @@ -137,6 +138,75 @@ protected void doCleanup(boolean afterTest) _userHelper.deleteUsers(false, USER_FOR_FILTERTEST.getEmail()); } + // Issue 52390: milliseconds are truncated from time fields on update or reshow + @Test + public void testDateAndTimeValueUpdates() throws Exception + { + var projectSettingsPage = goToProjectSettings(); + projectSettingsPage.setDefaultDateDisplayInherited(false); + projectSettingsPage.setDefaultDateDisplay(BaseSettingsPage.DATE_FORMAT.MMMM_dd_yyyy); + projectSettingsPage.setDefaultDateTimeDisplayInherited(false); + projectSettingsPage.setDefaultDateTimeDisplay(BaseSettingsPage.DATE_FORMAT.dd_MMM_yyyy, + BaseSettingsPage.TIME_FORMAT.HH_mm_ss_SSS); + projectSettingsPage.setDefaultTimeDisplayInherited(false); + projectSettingsPage.setDefaultTimeDisplay(BaseSettingsPage.TIME_FORMAT.HH_mm_ss_SSS); + projectSettingsPage.save(); + + final String sampleTypeName = "dateTimeEditSamples"; + final FieldDefinition txtField = new FieldDefinition( + TestDataGenerator.randomFieldName("text"), ColumnType.String).setRequired(true); + final FieldDefinition dateField = new FieldDefinition( + TestDataGenerator.randomFieldName("date", ":"), ColumnType.Date); + final FieldDefinition timeField = new FieldDefinition( + TestDataGenerator.randomFieldName("time", ":"), ColumnType.Time); + final FieldDefinition dateTimeField = new FieldDefinition( + TestDataGenerator.randomFieldName("dateTime", ":"), ColumnType.DateAndTime); + final List fields = List.of(txtField, dateField, timeField, dateTimeField); + + SampleTypeDefinition sampleTypeDefinition = new SampleTypeDefinition(sampleTypeName).setFields(fields); + sampleTypeDefinition.create(createDefaultConnection(), getProjectName()); + + goToProjectHome(); + waitAndClickAndWait(Locator.linkWithText(sampleTypeName)); + var dataRegion = DataRegionTable.DataRegion(getDriver()).withName("Material").waitFor(); + var updatePage = dataRegion.clickInsertNewRow(); + String date = "January 01 2025"; + String time = "23:56:54:123"; + String dateTime = "06-May-1986 23:58:34.123"; + updatePage.setField("Name", "sample01"); + updatePage.setField(dateField.getName(), date); + updatePage.setField(timeField.getName(), time); + updatePage.setField(dateTimeField.getName(), dateTime); + updatePage.submitExpectingError(); + + checker().wrapAssertion(()-> Assertions.assertThat(updatePage.getTextInputValue(dateField.getName())) + .isEqualTo("2025-01-01")); // expect reformat of date + checker().wrapAssertion(()-> Assertions.assertThat(updatePage.getTextInputValue(timeField.getName())) + .as("expect time to retain milliseconds") + .isEqualTo(time)); + checker().wrapAssertion(()-> Assertions.assertThat(updatePage.getTextInputValue(dateTimeField.getName())) + .as("expect dateTime to post back as entered") + .isEqualTo(dateTime)); + checker().screenShotIfNewError("unexpected data update"); + + // fill in the required text field and submit + updatePage.setField(txtField.getName(), "sample01"); + updatePage.submit(); + + var afterSubmit = DataRegionTable.DataRegion(getDriver()).withName("Material").waitFor(); + var rowData = afterSubmit.getRowDataAsText(0); + checker().withScreenshot("unexpected data persisted") + .wrapAssertion(()-> Assertions.assertThat(rowData) + .as("Issue 52390 expect date, time, dateTime to be shown as entered") + .contains(date, time, dateTime)); + + var cleanupDateFormatsPage = goToProjectSettings(); + cleanupDateFormatsPage.setDefaultDateDisplayInherited(true); + cleanupDateFormatsPage.setDefaultDateTimeDisplayInherited(true); + cleanupDateFormatsPage.setDefaultTimeDisplayInherited(true); + cleanupDateFormatsPage.save(); + } + @Test public void testCreateSampleTypeNoExpression() { From bba2a970fac888afc4cae2744e0f82f7a6a5de5b Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Tue, 27 May 2025 20:09:02 -0700 Subject: [PATCH 07/26] coverage for Issue 50774 --- .../assay/AssayTransformImportUpdateTest.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/org/labkey/test/tests/assay/AssayTransformImportUpdateTest.java b/src/org/labkey/test/tests/assay/AssayTransformImportUpdateTest.java index 0d99290cb2..42fd5ab277 100644 --- a/src/org/labkey/test/tests/assay/AssayTransformImportUpdateTest.java +++ b/src/org/labkey/test/tests/assay/AssayTransformImportUpdateTest.java @@ -13,6 +13,7 @@ import org.labkey.test.pages.admin.UsageStatisticsPage; import org.labkey.test.pages.assay.AssayImportPage; import org.labkey.test.pages.assay.AssayRunsPage; +import org.labkey.test.pages.pipeline.PipelineStatusDetailsPage; import org.labkey.test.params.FieldDefinition; import org.labkey.test.params.assay.GeneralAssayDesign; import org.labkey.test.util.RReportHelper; @@ -219,6 +220,74 @@ public void testEnableTransformForUpdate() throws Exception assaysWithTransformScripts > 0); } + // Issue 50774 + @Test + public void testCancelAsyncAssayTransformJob() throws Exception + { + String transformCancelFile = "importCancelTransform.R"; + String importCancelTransformAssay = "importCancelTransformAssay"; + String transformContent = """ + library(Rlabkey); + + run.props = labkey.transform.readRunPropertiesFile("${runInfo}"); + + run.data.file = labkey.transform.getRunPropertyValue(run.props, "runDataFile"); + run.output.file = run.props$val3[run.props$name == "runDataFile"]; + error.file = labkey.transform.getRunPropertyValue(run.props, "errorsFile"); + + # sleep a bit before writing the table, give the test time to cancel the job before it is complete + labkey.setDebugMode(TRUE); + print("before"); + Sys.sleep(4); + print("after"); + labkey.setDebugMode(FALSE); + + if (file.exists(run.data.file)) { + run.data = read.delim(run.data.file, header=TRUE, sep="\\t", check.names = FALSE); + run.data$M2 = 111; + run.data$TransformType = "${transformOperation} testing"; + write.table(run.data, file=run.output.file, sep="\\t", na="", row.names=FALSE, quote=FALSE); + } + + """; + File transformFile = TestFileUtils.writeTempFile(transformCancelFile, transformContent); + var protocolResponse = new GeneralAssayDesign(importCancelTransformAssay) + .setDataFields(List.of(new FieldDefinition("M2", FieldDefinition.ColumnType.Decimal), + new FieldDefinition("TransformType", FieldDefinition.ColumnType.String), + new FieldDefinition("Comment", FieldDefinition.ColumnType.String)), true) + .createAssay(getProjectName(), createDefaultConnection()); + goToProjectHome(); + + var assayDesignerPage = ReactAssayDesignerPage.beginAt(this, getProjectName(), protocolResponse.getProtocolId(), + "general", getURL().toString()); + assayDesignerPage.addTransformScript(transformFile, true); + assayDesignerPage.setBackgroundImport(true); + assayDesignerPage.clickSave(); + + StringBuilder importDataBuilder = new StringBuilder("VisitID\tParticipantID\tComment\n"); + for (int i=1; i<=10; i++) + importDataBuilder.append(String.format("%d\t%d\tComment-%d\n", i, i, i)); + + clickAndWait(Locator.linkWithText(importCancelTransformAssay)); + new AssayRunsPage(getDriver()).getTable().clickHeaderButton("Import Data"); + clickButton("Next"); + var importPage = new AssayImportPage(getDriver()); + importPage.setNamedInputText("name", "cancelTransformTestImport"); + importPage.setNamedTextAreaValue("TextAreaDataCollector.textArea", importDataBuilder.toString()); + importPage.clickSaveAndFinish(); + + waitAndClickAndWait(Locator.linkWithText("Assay upload RUNNING")); + PipelineStatusDetailsPage pipelineStatusDetailsPage = new PipelineStatusDetailsPage(getDriver()); + pipelineStatusDetailsPage.clickCancel(); + + pipelineStatusDetailsPage.showLogDetails(); + pipelineStatusDetailsPage.assertLogTextContains("INFO : Attempting to cancel as requested", + "INFO : Interrupting job by sending interrupt request.", + "ERROR: The following error was generated by the assay upload", + "INFO : Failed to complete task 'org.labkey.api.assay.pipeline.AssayUploadPipelineTask'"); + resetErrors(); + } + @Override protected String getProjectName() { From eac0a0c0c5aef44c85999745dbadfdd4f9be20d6 Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Tue, 27 May 2025 20:09:55 -0700 Subject: [PATCH 08/26] make pipeline root matching case-insensitive --- src/org/labkey/test/BaseWebDriverTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/test/BaseWebDriverTest.java b/src/org/labkey/test/BaseWebDriverTest.java index cc9345dbd9..ba979e2649 100644 --- a/src/org/labkey/test/BaseWebDriverTest.java +++ b/src/org/labkey/test/BaseWebDriverTest.java @@ -1905,7 +1905,7 @@ public void setPipelineRoot(String rootPath, boolean inherit) { _setPipelineRoot(rootPath, inherit); - waitForElement(Locators.labkeyMessage.withText("The pipeline root was set to '" + Paths.get(rootPath).normalize() + "'")); + waitForElement(Locators.labkeyMessage.withTextMatching("The pipeline root was set to '" + Paths.get(rootPath).normalize() + "'")); getArtifactCollector().addArtifactLocation(new File(rootPath)); From c86e1b15cdc74c51f5703a574a005ff6532a5023 Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Wed, 28 May 2025 09:13:07 -0700 Subject: [PATCH 09/26] Coverage for Issue 52661 --- .../components/ui/grids/ResponsiveGrid.java | 25 +++++++++++ .../ui/search/FilterExpressionPanel.java | 2 +- .../tests/component/GridPanelViewTest.java | 45 +++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java b/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java index 70d7aefab9..ccccb75243 100644 --- a/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java +++ b/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java @@ -12,6 +12,7 @@ import org.labkey.test.components.Component; import org.labkey.test.components.UpdatingComponent; import org.labkey.test.components.WebDriverComponent; +import org.labkey.test.components.html.Input; import org.labkey.test.components.html.RadioButton; import org.labkey.test.components.react.ReactCheckBox; import org.labkey.test.components.ui.search.FilterExpressionPanel; @@ -193,6 +194,30 @@ public T filterBooleanColumn(String fieldLabel, boolean value) return _this; } + /** + * + * @param index + * @param fieldLabel + * @param operator + * @param dateString1 + * @param dateString2 + * @return + */ + public T filterDateColumn(int index, String fieldLabel, Filter.Operator operator, String dateString1, String dateString2) + { + T _this = getThis(); + doAndWaitForUpdate(() -> { + clickColumnMenuItem(fieldLabel, "Filter...", false); + GridFilterModal filterModal = new GridFilterModal(getDriver(), this); + filterModal.selectExpressionTab().setFilterType(index, operator); + Input.Input(Locator.input("field-value-date-0"), getDriver()).waitFor().set(dateString1); + if (dateString2 != null) + Input.Input(Locator.input("field-value-date-1"), getDriver()).waitFor().set(dateString2); + filterModal.confirm(); + }); + return _this; + } + public String filterColumnExpectingError(String fieldLabel, Filter.Operator operator, Object value) { GridFilterModal filterModal = initFilterColumn(fieldLabel, operator, value); diff --git a/src/org/labkey/test/components/ui/search/FilterExpressionPanel.java b/src/org/labkey/test/components/ui/search/FilterExpressionPanel.java index b669ba328e..2943c00c83 100644 --- a/src/org/labkey/test/components/ui/search/FilterExpressionPanel.java +++ b/src/org/labkey/test/components/ui/search/FilterExpressionPanel.java @@ -117,7 +117,7 @@ else if (value2 != null) } } - private void setFilterType(int index, Object op) + public void setFilterType(int index, Object op) { if (op instanceof Operator operator) { diff --git a/src/org/labkey/test/tests/component/GridPanelViewTest.java b/src/org/labkey/test/tests/component/GridPanelViewTest.java index a3992c8ca3..1c64c4a740 100644 --- a/src/org/labkey/test/tests/component/GridPanelViewTest.java +++ b/src/org/labkey/test/tests/component/GridPanelViewTest.java @@ -1,5 +1,7 @@ package org.labkey.test.tests.component; +import org.aspectj.org.eclipse.jdt.core.dom.InfixExpression; +import org.assertj.core.api.Assertions; import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -33,6 +35,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -1260,6 +1263,48 @@ public void testRemoveAllFields() } + @Test + public void testWarningOnInvalidDateFilter() + { + String viewName = "broken view"; + goToProjectHome(); + + resetDefaultView(VIEW_DIALOG_ST, DEFAULT_COLUMNS); + + QueryGrid grid = beginAtQueryGrid(VIEW_DIALOG_ST); + + log("Save a view, call it 'broken view'"); + grid.saveView() + .setViewName(viewName) + .saveView(); + + log("add created column so we can filter on it"); + grid.customizeView() + .selectAvailableField("Created") + .clickUpdateGrid(); + + log("filter on a valid date"); + grid.filterDateColumn(0, "Created", Filter.Operator.GT, "May 27, 2024", null); + var filterStatusValueTexts = grid.getFilterStatusValuesText(); + checker().withScreenshot("Filter_Texts_Error") + .wrapAssertion(()-> Assertions.assertThat(filterStatusValueTexts) + .hasSize(1) + .containsExactly("Created > 2024-05-27")); + grid.clearFilters(); + + log("try to filter on an invalid date"); + grid.filterDateColumn(0, "Created", Filter.Operator.GT, "05/37/2024", null); + // don't expect the parser to get the invalid date right; current behavior won't do that + + log("ensure the view can be edited after"); + grid.filterColumn("Created", Filter.Operator.LTE, new Date()); // filter on right now + + grid.clickUndoButton(); // undo will clear the view from its edited state, make it deletable + grid.manageViews() // ensure the view can be deleted now + .deleteViewAndConfirm(viewName + " (shared)") + .dismiss("Done"); + } + /** * Helper to validate the 'Views' menu. * From 28efd4bcf5bd869f47f0c972a757ff1aa5936319 Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Wed, 28 May 2025 11:05:54 -0700 Subject: [PATCH 10/26] add comments --- src/org/labkey/test/tests/component/GridPanelViewTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/org/labkey/test/tests/component/GridPanelViewTest.java b/src/org/labkey/test/tests/component/GridPanelViewTest.java index 1c64c4a740..be5b46491f 100644 --- a/src/org/labkey/test/tests/component/GridPanelViewTest.java +++ b/src/org/labkey/test/tests/component/GridPanelViewTest.java @@ -1263,6 +1263,7 @@ public void testRemoveAllFields() } + // Issue 52661 Saved grid view with an invalid date filter can't be edited or deleted @Test public void testWarningOnInvalidDateFilter() { From a28df84107991d9a551475bf9107182c5f20cca4 Mon Sep 17 00:00:00 2001 From: Chris Joosse Date: Wed, 28 May 2025 14:35:08 -0700 Subject: [PATCH 11/26] Update src/org/labkey/test/tests/MenuBarTest.java Co-authored-by: Trey Chadick --- src/org/labkey/test/tests/MenuBarTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/org/labkey/test/tests/MenuBarTest.java b/src/org/labkey/test/tests/MenuBarTest.java index 0ed5a9df14..43b7403a4f 100644 --- a/src/org/labkey/test/tests/MenuBarTest.java +++ b/src/org/labkey/test/tests/MenuBarTest.java @@ -242,9 +242,9 @@ public void testDocumentationMenuLinks() goToProjectHome(); clickUserMenuItem(true,"Support"); checker().withScreenshot("unexpected_destination") - .wrapAssertion(()-> Assertions.assertThat(getURL().toString()) - .as("expect navigation to localhost/home/support") - .contains("localhost", "/home/support")); + .verifyEquals("Support menu destination", + WebTestHelper.buildUrl("project", "home/support", "begin"), + getDriver().getCurrentUrl()); goToProjectHome(); clickAdminMenuItem("Developer Links", "JavaScript API Reference"); From 0710109535e2833a46b9cd73d20aef76aa777fe6 Mon Sep 17 00:00:00 2001 From: Chris Joosse Date: Wed, 28 May 2025 14:37:27 -0700 Subject: [PATCH 12/26] Update src/org/labkey/test/tests/component/GridPanelViewTest.java Co-authored-by: Trey Chadick --- src/org/labkey/test/tests/component/GridPanelViewTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/org/labkey/test/tests/component/GridPanelViewTest.java b/src/org/labkey/test/tests/component/GridPanelViewTest.java index be5b46491f..15e2d61052 100644 --- a/src/org/labkey/test/tests/component/GridPanelViewTest.java +++ b/src/org/labkey/test/tests/component/GridPanelViewTest.java @@ -1,6 +1,5 @@ package org.labkey.test.tests.component; -import org.aspectj.org.eclipse.jdt.core.dom.InfixExpression; import org.assertj.core.api.Assertions; import org.junit.BeforeClass; import org.junit.Test; From 0222bc8d24a294462847a7e7a3203c601658163e Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Wed, 28 May 2025 18:06:13 -0700 Subject: [PATCH 13/26] longer sleep so we know the cancel gets there before the transform is complete --- .../labkey/test/tests/assay/AssayTransformImportUpdateTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/test/tests/assay/AssayTransformImportUpdateTest.java b/src/org/labkey/test/tests/assay/AssayTransformImportUpdateTest.java index 42fd5ab277..91272d4508 100644 --- a/src/org/labkey/test/tests/assay/AssayTransformImportUpdateTest.java +++ b/src/org/labkey/test/tests/assay/AssayTransformImportUpdateTest.java @@ -238,7 +238,7 @@ public void testCancelAsyncAssayTransformJob() throws Exception # sleep a bit before writing the table, give the test time to cancel the job before it is complete labkey.setDebugMode(TRUE); print("before"); - Sys.sleep(4); + Sys.sleep(30); print("after"); labkey.setDebugMode(FALSE); From 909c44f561b63b60e3fc12fb1f19406e25efe124 Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Wed, 28 May 2025 18:08:25 -0700 Subject: [PATCH 14/26] feedback --- src/org/labkey/test/tests/component/GridPanelViewTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/org/labkey/test/tests/component/GridPanelViewTest.java b/src/org/labkey/test/tests/component/GridPanelViewTest.java index 96fa066e69..c050ce2b04 100644 --- a/src/org/labkey/test/tests/component/GridPanelViewTest.java +++ b/src/org/labkey/test/tests/component/GridPanelViewTest.java @@ -1286,11 +1286,12 @@ public void testWarningOnInvalidDateFilter() log("filter on a valid date"); grid.filterDateColumn(0, "Created", Filter.Operator.GT, "May 27, 2024", null); - var filterStatusValueTexts = grid.getFilterStatusValuesText(); + var filterStatuses = grid.getFilterStatusValues(); checker().withScreenshot("Filter_Texts_Error") - .wrapAssertion(()-> Assertions.assertThat(filterStatusValueTexts) + .wrapAssertion(()-> Assertions.assertThat(filterStatuses) .hasSize(1) - .containsExactly("Created > 2024-05-27")); + .filteredOn(a-> a.getText().equals("Created > 2024-05-27")) + .isNotEmpty()); grid.clearFilters(); log("try to filter on an invalid date"); From bfe1a4bf6619d66a2797099190131ea8664cfb47 Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Wed, 28 May 2025 18:09:14 -0700 Subject: [PATCH 15/26] update import --- src/org/labkey/test/tests/query/QueryLookupTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/test/tests/query/QueryLookupTest.java b/src/org/labkey/test/tests/query/QueryLookupTest.java index b5b9c54df8..808958b5be 100644 --- a/src/org/labkey/test/tests/query/QueryLookupTest.java +++ b/src/org/labkey/test/tests/query/QueryLookupTest.java @@ -14,7 +14,7 @@ import org.labkey.test.params.list.VarListDefinition; import org.labkey.test.util.DataRegionTable; import org.labkey.test.util.EscapeUtil; -import org.labkey.test.util.TestDataUtils; +import org.labkey.test.util.data.TestDataUtils; import java.util.ArrayList; import java.util.Arrays; From 7394ef7a3f80a259cd5b952fe7eaae0894cd01e6 Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Wed, 28 May 2025 18:16:03 -0700 Subject: [PATCH 16/26] cr/feedback --- src/org/labkey/test/tests/AdminConsoleTest.java | 2 +- src/org/labkey/test/tests/MenuBarTest.java | 3 ++- src/org/labkey/test/tests/SampleTypeTest.java | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/org/labkey/test/tests/AdminConsoleTest.java b/src/org/labkey/test/tests/AdminConsoleTest.java index e72e5d8818..5b7e94a0df 100644 --- a/src/org/labkey/test/tests/AdminConsoleTest.java +++ b/src/org/labkey/test/tests/AdminConsoleTest.java @@ -160,7 +160,7 @@ public void testSiteBannerAPIConfiguration() throws Exception { goToAdminConsole(); - String bannerMessage = "test banner message" + TRICKY_CHARACTERS; + String bannerMessage = "test banner message" + TRICKY_CHARACTERS + INJECT_CHARS_1; Locator bannerLoc = Locator.tagWithClass("div", "lk-dismissable-warn").containing(bannerMessage); //As site admin diff --git a/src/org/labkey/test/tests/MenuBarTest.java b/src/org/labkey/test/tests/MenuBarTest.java index 43b7403a4f..d5ed412701 100644 --- a/src/org/labkey/test/tests/MenuBarTest.java +++ b/src/org/labkey/test/tests/MenuBarTest.java @@ -23,6 +23,7 @@ import org.labkey.test.Locator; import org.labkey.test.TestFileUtils; import org.labkey.test.TestTimeoutException; +import org.labkey.test.WebTestHelper; import org.labkey.test.categories.Daily; import org.labkey.test.util.PortalHelper; import org.labkey.test.util.UIContainerHelper; @@ -243,7 +244,7 @@ public void testDocumentationMenuLinks() clickUserMenuItem(true,"Support"); checker().withScreenshot("unexpected_destination") .verifyEquals("Support menu destination", - WebTestHelper.buildUrl("project", "home/support", "begin"), + WebTestHelper.buildURL("project", "home/support", "begin"), getDriver().getCurrentUrl()); goToProjectHome(); diff --git a/src/org/labkey/test/tests/SampleTypeTest.java b/src/org/labkey/test/tests/SampleTypeTest.java index 169c76fb56..f96827d043 100644 --- a/src/org/labkey/test/tests/SampleTypeTest.java +++ b/src/org/labkey/test/tests/SampleTypeTest.java @@ -171,7 +171,7 @@ public void testDateAndTimeValueUpdates() throws Exception var dataRegion = DataRegionTable.DataRegion(getDriver()).withName("Material").waitFor(); var updatePage = dataRegion.clickInsertNewRow(); String date = "January 01 2025"; - String time = "23:56:54:123"; + String time = "23:56:54.123"; String dateTime = "06-May-1986 23:58:34.123"; updatePage.setField("Name", "sample01"); updatePage.setField(dateField.getName(), date); @@ -186,7 +186,7 @@ public void testDateAndTimeValueUpdates() throws Exception .isEqualTo(time)); checker().wrapAssertion(()-> Assertions.assertThat(updatePage.getTextInputValue(dateTimeField.getName())) .as("expect dateTime to post back as entered") - .isEqualTo(dateTime)); + .isEqualTo("1986-05-06 23:58:34.123")); checker().screenShotIfNewError("unexpected data update"); // fill in the required text field and submit From 5743df75cd57fd9f407754369883552d0c2b15c9 Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Thu, 29 May 2025 11:00:04 -0700 Subject: [PATCH 17/26] Use DateString class to smuggle arbitrary date strings into date column filters --- .../components/ui/grids/ResponsiveGrid.java | 24 ---------------- .../ui/search/FilterExpressionPanel.java | 28 +++++++++++++++++-- .../tests/component/GridPanelViewTest.java | 4 +-- 3 files changed, 27 insertions(+), 29 deletions(-) diff --git a/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java b/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java index 430ee78683..e6c53de372 100644 --- a/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java +++ b/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java @@ -217,30 +217,6 @@ public T filterBooleanColumn(CharSequence columnIdentifier, boolean value) return _this; } - /** - * - * @param index - * @param fieldLabel - * @param operator - * @param dateString1 - * @param dateString2 - * @return - */ - public T filterDateColumn(int index, String fieldLabel, Filter.Operator operator, String dateString1, String dateString2) - { - T _this = getThis(); - doAndWaitForUpdate(() -> { - clickColumnMenuItem(fieldLabel, "Filter...", false); - GridFilterModal filterModal = new GridFilterModal(getDriver(), this); - filterModal.selectExpressionTab().setFilterType(index, operator); - Input.Input(Locator.input("field-value-date-0"), getDriver()).waitFor().set(dateString1); - if (dateString2 != null) - Input.Input(Locator.input("field-value-date-1"), getDriver()).waitFor().set(dateString2); - filterModal.confirm(); - }); - return _this; - } - /** * @param columnIdentifier fieldKey, name, or label of column */ diff --git a/src/org/labkey/test/components/ui/search/FilterExpressionPanel.java b/src/org/labkey/test/components/ui/search/FilterExpressionPanel.java index 2943c00c83..70c38e7f7b 100644 --- a/src/org/labkey/test/components/ui/search/FilterExpressionPanel.java +++ b/src/org/labkey/test/components/ui/search/FilterExpressionPanel.java @@ -92,11 +92,19 @@ public void setFilter(int index, Object operator, Object value1, Object value2) } else if (value1 instanceof Date dateVal1) { - final String dateStr1 = DATE_FORMAT.format(dateVal1); + String dateStr1; + if (value1 instanceof DateString) + dateStr1 = ((DateString)value1).getDateStr(); // use the test-supplied date string + else + dateStr1 = DATE_FORMAT.format(dateVal1); // format the supplied date value to string elementCache().dateValues.get(index).set(dateStr1); if (value2 instanceof Date dateVal2) { - final String dateStr2 = DATE_FORMAT.format(dateVal2); + String dateStr2; + if (value2 instanceof DateString) + dateStr2 = ((DateString)value2).getDateStr(); + else + dateStr2 = DATE_FORMAT.format(dateVal2); elementCache().dateValuesSecond.get(index).set(dateStr2); } else if (value2 != null) @@ -117,7 +125,7 @@ else if (value2 != null) } } - public void setFilterType(int index, Object op) + private void setFilterType(int index, Object op) { if (op instanceof Operator operator) { @@ -176,6 +184,20 @@ protected class ElementCache extends Component.ElementCache RadioButton.RadioButton(Locator.radioButtonByNameAndValue("field-value-bool-1", "false")).refindWhenNeeded(this)); } + public static class DateString extends Date + { + private final String _dateStr; + public DateString(String dateStr) + { + _dateStr = dateStr; + } + + public String getDateStr() + { + return _dateStr; + } + } + public static class FilterExpressionPanelFinder extends WebDriverComponentFinder { private final Locator.XPathLocator _baseLocator = Locator.byClass("filter-expression__input-wrapper").parent(); diff --git a/src/org/labkey/test/tests/component/GridPanelViewTest.java b/src/org/labkey/test/tests/component/GridPanelViewTest.java index c050ce2b04..c5c02994f2 100644 --- a/src/org/labkey/test/tests/component/GridPanelViewTest.java +++ b/src/org/labkey/test/tests/component/GridPanelViewTest.java @@ -1285,7 +1285,7 @@ public void testWarningOnInvalidDateFilter() .clickUpdateGrid(); log("filter on a valid date"); - grid.filterDateColumn(0, "Created", Filter.Operator.GT, "May 27, 2024", null); + grid.filterColumn("Created", Filter.Operator.GT, new FilterExpressionPanel.DateString("May 27, 2024")); var filterStatuses = grid.getFilterStatusValues(); checker().withScreenshot("Filter_Texts_Error") .wrapAssertion(()-> Assertions.assertThat(filterStatuses) @@ -1295,7 +1295,7 @@ public void testWarningOnInvalidDateFilter() grid.clearFilters(); log("try to filter on an invalid date"); - grid.filterDateColumn(0, "Created", Filter.Operator.GT, "05/37/2024", null); + grid.filterColumn("Created", Filter.Operator.GT, new FilterExpressionPanel.DateString("05/37/2024")); // don't expect the parser to get the invalid date right; current behavior won't do that log("ensure the view can be edited after"); From bf576f6cd6a1aaa68e8227069a62baae42c2b0e3 Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Thu, 29 May 2025 16:00:27 -0700 Subject: [PATCH 18/26] coverage for Issue 52729 --- src/org/labkey/test/tests/wiki/WikiTest.java | 66 +++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/src/org/labkey/test/tests/wiki/WikiTest.java b/src/org/labkey/test/tests/wiki/WikiTest.java index 7dc01635fd..b095259c5e 100644 --- a/src/org/labkey/test/tests/wiki/WikiTest.java +++ b/src/org/labkey/test/tests/wiki/WikiTest.java @@ -28,6 +28,7 @@ import org.labkey.test.Locator; import org.labkey.test.categories.Daily; import org.labkey.test.categories.Wiki; +import org.labkey.test.components.WebPartPanel; import org.labkey.test.pages.admin.ExternalSourcesPage; import org.labkey.test.pages.admin.ExternalSourcesPage.Directive; import org.labkey.test.pages.search.SearchResultsPage; @@ -38,6 +39,7 @@ import org.labkey.test.util.search.SearchAdminAPIHelper; import java.io.File; +import java.time.Duration; import java.util.Arrays; import java.util.List; @@ -46,6 +48,8 @@ public class WikiTest extends BaseWebDriverTest { private static final String PROJECT_NAME = TRICKY_CHARACTERS_FOR_PROJECT_NAMES + "WikiVerifyProject"; + private static final String SUBFOLDER_NAME = TRICKY_CHARACTERS_FOR_PROJECT_NAMES + "WikiVerifySubfolder"; + private static final String SUBFOLDER_PATH = String.format("%s/%s", PROJECT_NAME, SUBFOLDER_NAME); private static final String WIKI_PAGE_ALTTITLE = "PageBBB has HTML"; private static final String WIKI_PAGE_WEBPART_ID = "qwp999"; private static final String WIKI_PAGE_TITLE = "_Test Wiki " + BaseWebDriverTest.INJECT_CHARS_1; @@ -68,6 +72,8 @@ private void doSetup() { _containerHelper.createProject(PROJECT_NAME, null); _containerHelper.enableModules(Arrays.asList("Wiki")); + _containerHelper.createSubfolder(PROJECT_NAME, SUBFOLDER_NAME); + _containerHelper.enableModules(Arrays.asList("Wiki")); SearchAdminAPIHelper.pauseCrawler(getDriver()); @@ -344,7 +350,65 @@ public void testUpdateWikiWithHostileNameAndTitle() throws Exception .as("expect title field to be the source of the error") .isEqualTo("title")); } -} + } + + // Issue 52729 Wiki webpart doesn't resolve after wiki rename with alias + @Test + public void testRenameWebPartWiki() throws Exception + { + var newLine = '\u0081'; + var stringTerminator = '\u009c'; + String wikiContent = "

This is my content " + stringTerminator + TRICKY_CHARACTERS + newLine + "

"; + String wikiName = "webPartWiki"; + String wikiTitle = "wikiRenameWebPart"; + var cn = createDefaultConnection(); + + // first, create a straightforward wiki + var createCmd = new SimplePostCommand("wiki", "saveWiki"); + JSONObject createJson = new JSONObject(); + createJson.put("name", wikiName); + createJson.put("title", wikiTitle); + createJson.put("rendererType", "HTML"); + createJson.put("body", "

content for wiki webpart rename

"); + createJson.put("pageVersionId", -1); + createCmd.setJsonObject(createJson); + createCmd.execute(cn, SUBFOLDER_PATH); + SearchAdminAPIHelper.waitForIndexer(); + + // give the folder a wikiWebPart + goToProjectFolder(PROJECT_NAME, SUBFOLDER_NAME); + var wikiHelper = new WikiHelper(this); + new PortalHelper(this).addWebPart("Wiki"); + Locator wikiWebPartLoc = Locator.tagWithClass("div", "panel-portal") + .withDescendant(Locator.tagWithAttribute("h3", "title", wikiTitle)) + .descendant(Locator.tagWithText("p", "content for wiki webpart rename")); + + // configure the webPart to use the wiki created above + waitAndClickAndWait(Locator.linkWithText("Choose an existing page to display")); + var selectedPageOption = getSelectedOptionText(Locator.name("name")); + checker().withScreenshot("unexpected_selected_page") + .wrapAssertion(()-> Assertions.assertThat(selectedPageOption) + .as("expect our wiki to be selected") + .startsWith(wikiName)); + waitAndClickAndWait(Locator.id("btnSubmit")); + + // verify the webpart's content is our expected content + checker().withScreenshot("unexpected_wiki_content") + .awaiting(Duration.ofSeconds(1), ()-> Assertions.assertThat(wikiWebPartLoc.existsIn(getDriver())) + .as("expect our wiki content to be present") + .isTrue()); + + // Now edit the wiki, give it a new name, with an alias + var wikiConfigPage = wikiHelper.manageWikiConfiguration(); + wikiConfigPage.rename("webPartNewWikiName", true) + .save(); + + // verify the expected content is still present + checker().withScreenshot("unexpected_wiki_content_after_rename") + .awaiting(Duration.ofSeconds(1), ()-> Assertions.assertThat(wikiWebPartLoc.existsIn(getDriver())) + .as("expect our wiki content to be present") + .isTrue()); + } protected void verifyWikiPagePresent() { From 9132cdaa69df7c5cbe4989bfb73823b855df3919 Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Thu, 29 May 2025 17:12:25 -0700 Subject: [PATCH 19/26] fix banner test --- .../labkey/test/tests/AdminConsoleTest.java | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/org/labkey/test/tests/AdminConsoleTest.java b/src/org/labkey/test/tests/AdminConsoleTest.java index 5b7e94a0df..382c619c9b 100644 --- a/src/org/labkey/test/tests/AdminConsoleTest.java +++ b/src/org/labkey/test/tests/AdminConsoleTest.java @@ -23,6 +23,7 @@ import org.labkey.remoteapi.SimplePostCommand; import org.labkey.test.BaseWebDriverTest; import org.labkey.test.Locator; +import org.labkey.test.WebDriverWrapper; import org.labkey.test.WebTestHelper; import org.labkey.test.categories.Daily; import org.labkey.test.pages.core.admin.CustomizeSitePage; @@ -161,7 +162,8 @@ public void testSiteBannerAPIConfiguration() throws Exception goToAdminConsole(); String bannerMessage = "test banner message" + TRICKY_CHARACTERS + INJECT_CHARS_1; - Locator bannerLoc = Locator.tagWithClass("div", "lk-dismissable-warn").containing(bannerMessage); + Locator bannerLoc = Locator.tagWithClass("div", "lk-dismissable-warn") + .containing("test banner message" + TRICKY_CHARACTERS); //As site admin // set the message and show it @@ -171,17 +173,20 @@ public void testSiteBannerAPIConfiguration() throws Exception showBannerCmd.execute(createDefaultConnection(), "/"); refresh(); // verify it is shown - checker().withScreenshot("banner not shown or not as expected") - .verifyTrue("expect banner to be shown", bannerLoc.isDisplayed(getDriver())); - - // hide the banner - var hideBannerCmd = new SimplePostCommand("admin", "setRibbonMessage.api"); - hideBannerCmd.setParameters(Map.of("show", false)); - hideBannerCmd.execute(createDefaultConnection(), "/"); - refresh(); - // verify it is hidden - checker().withScreenshot("banner is shown when not expected") - .verifyFalse("expect banner not to be shown", bannerLoc.isDisplayed(getDriver())); + WebDriverWrapper.waitFor(()-> bannerLoc.isDisplayed(getDriver()), 1000); + if (checker().withScreenshot("banner not shown or not as expected") + .verifyTrue("expect banner to be shown", bannerLoc.isDisplayed(getDriver()))) + { + // hide the banner + var hideBannerCmd = new SimplePostCommand("admin", "setRibbonMessage.api"); + hideBannerCmd.setParameters(Map.of("show", false)); + hideBannerCmd.execute(createDefaultConnection(), "/"); + refresh(); + WebDriverWrapper.waitFor(()-> !bannerLoc.isDisplayed(getDriver()), 1000); + // verify it is hidden + checker().withScreenshot("banner is shown when not expected") + .verifyFalse("expect banner not to be shown", bannerLoc.isDisplayed(getDriver())); + } // restore it with the previous banner value var restoreBannerCmd = new SimplePostCommand("admin", "setRibbonMessage.api"); From 6a799575cebbc270df0792dc7e136ba9751ac1e0 Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Thu, 29 May 2025 17:13:30 -0700 Subject: [PATCH 20/26] use FieldInfo for constants --- .../labkey/test/tests/query/QueryLookupTest.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/org/labkey/test/tests/query/QueryLookupTest.java b/src/org/labkey/test/tests/query/QueryLookupTest.java index 808958b5be..980acf70c8 100644 --- a/src/org/labkey/test/tests/query/QueryLookupTest.java +++ b/src/org/labkey/test/tests/query/QueryLookupTest.java @@ -9,6 +9,7 @@ import org.labkey.test.categories.Daily; import org.labkey.test.pages.list.EditListDefinitionPage; import org.labkey.test.params.FieldDefinition; +import org.labkey.test.params.FieldInfo; import org.labkey.test.params.list.IntListDefinition; import org.labkey.test.params.list.VarListDefinition; @@ -28,10 +29,10 @@ public class QueryLookupTest extends BaseWebDriverTest private static final String PROJECT_NAME = "QueryLookupTest" + TRICKY_CHARACTERS_FOR_PROJECT_NAMES; private static final String LIST_NAME = "l&ist q"; - private static final FieldDefinition NAME_COLUMN = - new FieldDefinition("Name", FieldDefinition.ColumnType.String); - private static final FieldDefinition TSHIRT_COLUMN = - new FieldDefinition("TShirt", FieldDefinition.ColumnType.String); + private static final FieldInfo NAME_COLUMN = + new FieldInfo("Name", FieldDefinition.ColumnType.String); + private static final FieldInfo TSHIRT_COLUMN = + new FieldInfo("TShirt", FieldDefinition.ColumnType.String); @Override protected void doCleanup(boolean afterTest) @@ -53,7 +54,7 @@ private void doSetup() throws Exception // create a list var dgen = new VarListDefinition(LIST_NAME) - .setFields(List.of(NAME_COLUMN, TSHIRT_COLUMN)) + .setFields(List.of(NAME_COLUMN.getFieldDefinition(), TSHIRT_COLUMN.getFieldDefinition())) .create(createDefaultConnection(), PROJECT_NAME); dgen.withGeneratedRows(10) .insertRows(); @@ -92,7 +93,7 @@ public void testLookupToQueryColumn() throws Exception // now create another list, with a lookup to the custom query new IntListDefinition(secondList, "Key") - .setFields(List.of(NAME_COLUMN, + .setFields(List.of(NAME_COLUMN.getFieldDefinition(), new FieldDefinition("lookup", new FieldDefinition.StringLookup(getProjectName(), "lists", queryName)))) .create(createDefaultConnection(), PROJECT_NAME); From 9de90b031aaee3e34396f99d75ad17b94e8b1b60 Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Thu, 29 May 2025 17:14:13 -0700 Subject: [PATCH 21/26] use new syntax to avoid casting instanceof --- .../test/components/ui/search/FilterExpressionPanel.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/org/labkey/test/components/ui/search/FilterExpressionPanel.java b/src/org/labkey/test/components/ui/search/FilterExpressionPanel.java index 70c38e7f7b..1ab0794b14 100644 --- a/src/org/labkey/test/components/ui/search/FilterExpressionPanel.java +++ b/src/org/labkey/test/components/ui/search/FilterExpressionPanel.java @@ -93,16 +93,16 @@ public void setFilter(int index, Object operator, Object value1, Object value2) else if (value1 instanceof Date dateVal1) { String dateStr1; - if (value1 instanceof DateString) - dateStr1 = ((DateString)value1).getDateStr(); // use the test-supplied date string + if (value1 instanceof DateString ds1) + dateStr1 = ds1.getDateStr(); // use the test-supplied date string else dateStr1 = DATE_FORMAT.format(dateVal1); // format the supplied date value to string elementCache().dateValues.get(index).set(dateStr1); if (value2 instanceof Date dateVal2) { String dateStr2; - if (value2 instanceof DateString) - dateStr2 = ((DateString)value2).getDateStr(); + if (value2 instanceof DateString ds2) + dateStr2 = ds2.getDateStr(); else dateStr2 = DATE_FORMAT.format(dateVal2); elementCache().dateValuesSecond.get(index).set(dateStr2); From 1c0d23852a953002e2394d6211272feaf98a258d Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Thu, 29 May 2025 17:14:38 -0700 Subject: [PATCH 22/26] clear up unused imports --- src/org/labkey/test/components/ui/grids/ResponsiveGrid.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java b/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java index e6c53de372..2fd6fba318 100644 --- a/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java +++ b/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java @@ -12,7 +12,6 @@ import org.labkey.test.components.Component; import org.labkey.test.components.UpdatingComponent; import org.labkey.test.components.WebDriverComponent; -import org.labkey.test.components.html.Input; import org.labkey.test.components.html.RadioButton; import org.labkey.test.components.react.ReactCheckBox; import org.labkey.test.components.ui.grids.FieldReferenceManager.FieldReference; From 7b9d0a997238d4e42eb9d8dd2e28ce2c1080f8c6 Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Thu, 29 May 2025 20:51:38 -0700 Subject: [PATCH 23/26] save the view before attempting to edit --- .../test/tests/component/GridPanelViewTest.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/org/labkey/test/tests/component/GridPanelViewTest.java b/src/org/labkey/test/tests/component/GridPanelViewTest.java index c5c02994f2..150ed9d224 100644 --- a/src/org/labkey/test/tests/component/GridPanelViewTest.java +++ b/src/org/labkey/test/tests/component/GridPanelViewTest.java @@ -29,6 +29,7 @@ import org.labkey.test.util.SampleTypeHelper; import org.labkey.test.util.TestDataGenerator; import org.labkey.test.util.exp.SampleTypeAPIHelper; +import org.openqa.selenium.TimeoutException; import org.openqa.selenium.WebDriverException; import java.io.IOException; @@ -40,6 +41,8 @@ import java.util.List; import java.util.Map; +import static org.junit.Assert.fail; + @Category({Daily.class}) public class GridPanelViewTest extends GridPanelBaseTest { @@ -1292,18 +1295,23 @@ public void testWarningOnInvalidDateFilter() .hasSize(1) .filteredOn(a-> a.getText().equals("Created > 2024-05-27")) .isNotEmpty()); + grid.saveView().saveView(); grid.clearFilters(); log("try to filter on an invalid date"); grid.filterColumn("Created", Filter.Operator.GT, new FilterExpressionPanel.DateString("05/37/2024")); // don't expect the parser to get the invalid date right; current behavior won't do that + // also add a bogus filter on int column, verify it refuses to do it + var err = grid.filterColumnExpectingError("Int", Filter.Operator.GT, "XYZ"); + checker().verifyEquals("expect error when trying to configure invalid filter", + "Missing filter values for: Int.", err); + grid.saveView().saveView(); log("ensure the view can be edited after"); grid.filterColumn("Created", Filter.Operator.LTE, new Date()); // filter on right now - grid.clickUndoButton(); // undo will clear the view from its edited state, make it deletable grid.manageViews() // ensure the view can be deleted now - .deleteViewAndConfirm(viewName + " (shared)") + .deleteViewAndConfirm(viewName) .dismiss("Done"); } From 0f659ae5579c1b85e534026c1fc1fe64e91b0497 Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Fri, 30 May 2025 15:19:54 -0700 Subject: [PATCH 24/26] revert attempt to make pipelineroot matching case insensitive --- src/org/labkey/test/BaseWebDriverTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/test/BaseWebDriverTest.java b/src/org/labkey/test/BaseWebDriverTest.java index cca393013e..7c8fb20608 100644 --- a/src/org/labkey/test/BaseWebDriverTest.java +++ b/src/org/labkey/test/BaseWebDriverTest.java @@ -1904,7 +1904,7 @@ public void setPipelineRoot(String rootPath, boolean inherit) { _setPipelineRoot(rootPath, inherit); - waitForElement(Locators.labkeyMessage.withTextMatching("The pipeline root was set to '" + Paths.get(rootPath).normalize() + "'")); + waitForElement(Locators.labkeyMessage.withText("The pipeline root was set to '" + Paths.get(rootPath).normalize() + "'")); getArtifactCollector().addArtifactLocation(new File(rootPath)); From a86dbe9a6d54cfd8cf293fb11a2fc1beb3754764 Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Mon, 2 Jun 2025 12:09:22 -0700 Subject: [PATCH 25/26] try to avoid csp violations in banner --- src/org/labkey/test/tests/AdminConsoleTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/test/tests/AdminConsoleTest.java b/src/org/labkey/test/tests/AdminConsoleTest.java index 382c619c9b..756af7106d 100644 --- a/src/org/labkey/test/tests/AdminConsoleTest.java +++ b/src/org/labkey/test/tests/AdminConsoleTest.java @@ -161,7 +161,7 @@ public void testSiteBannerAPIConfiguration() throws Exception { goToAdminConsole(); - String bannerMessage = "test banner message" + TRICKY_CHARACTERS + INJECT_CHARS_1; + String bannerMessage = "test banner message" + TRICKY_CHARACTERS; Locator bannerLoc = Locator.tagWithClass("div", "lk-dismissable-warn") .containing("test banner message" + TRICKY_CHARACTERS); From ea17ebc3adba74b6a1a3be4c3847cfb4833f6cc9 Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Mon, 2 Jun 2025 15:07:20 -0700 Subject: [PATCH 26/26] fix test so it deletes broken view --- src/org/labkey/test/tests/component/GridPanelViewTest.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/org/labkey/test/tests/component/GridPanelViewTest.java b/src/org/labkey/test/tests/component/GridPanelViewTest.java index 150ed9d224..bb466c9bec 100644 --- a/src/org/labkey/test/tests/component/GridPanelViewTest.java +++ b/src/org/labkey/test/tests/component/GridPanelViewTest.java @@ -1280,6 +1280,7 @@ public void testWarningOnInvalidDateFilter() log("Save a view, call it 'broken view'"); grid.saveView() .setViewName(viewName) + .setMakeShared(false) .saveView(); log("add created column so we can filter on it"); @@ -1296,7 +1297,6 @@ public void testWarningOnInvalidDateFilter() .filteredOn(a-> a.getText().equals("Created > 2024-05-27")) .isNotEmpty()); grid.saveView().saveView(); - grid.clearFilters(); log("try to filter on an invalid date"); grid.filterColumn("Created", Filter.Operator.GT, new FilterExpressionPanel.DateString("05/37/2024")); @@ -1309,9 +1309,12 @@ public void testWarningOnInvalidDateFilter() log("ensure the view can be edited after"); grid.filterColumn("Created", Filter.Operator.LTE, new Date()); // filter on right now + grid.saveView().saveView(); grid.manageViews() // ensure the view can be deleted now - .deleteViewAndConfirm(viewName) + .revertDefaultView() + .deleteView(viewName) + .confirmDelete() .dismiss("Done"); }