Compare commits

..

6 Commits

12 changed files with 361 additions and 41 deletions

View File

@ -2,7 +2,7 @@ version: 2.1
orbs:
node: circleci/node@5.1.0
browser-tools: circleci/browser-tools@1.4.3
browser-tools: circleci/browser-tools@1.4.5
executors:
java17:

14
pom.xml
View File

@ -161,6 +161,20 @@
<skipUpdateVersion>true</skipUpdateVersion>
</configuration>
</plugin>
<!-- Publish this project's test code as a jar -->
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@ -62,12 +62,10 @@ import {GoogleDriveFolderPickerWrapper} from "qqq/components/processes/GoogleDri
import ProcessSummaryResults from "qqq/components/processes/ProcessSummaryResults";
import ValidationReview from "qqq/components/processes/ValidationReview";
import BaseLayout from "qqq/layouts/BaseLayout";
import {TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT} from "qqq/pages/records/query/RecordQuery";
import Client from "qqq/utils/qqq/Client";
import TableUtils from "qqq/utils/qqq/TableUtils";
import ValueUtils from "qqq/utils/qqq/ValueUtils";
interface Props
{
process?: QProcessMetaData;
@ -90,7 +88,6 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
{
const processNameParam = useParams().processName;
const processName = process === null ? processNameParam : process.name;
const tableVariantLocalStorageKey = `${TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT}.${table.name}`;
///////////////////
// process state //
@ -371,15 +368,15 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
// but our first use case, they're the same, so... this needs fixed. //
// they also need to know the 'otherValues' in this process - e.g., for filtering //
////////////////////////////////////////////////////////////////////////////////////
if (formFields && processValues)
if(formFields && processValues)
{
Object.keys(formFields).forEach((key) =>
{
if (formFields[key].possibleValueProps)
if(formFields[key].possibleValueProps)
{
if (processValues[key])
if(processValues[key])
{
formFields[key].possibleValueProps.initialDisplayValue = processValues[key];
formFields[key].possibleValueProps.initialDisplayValue = processValues[key]
}
formFields[key].possibleValueProps.otherValues = formFields[key].possibleValueProps.otherValues ?? new Map<string, any>();
@ -388,7 +385,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
formFields[key].possibleValueProps.otherValues.set(otherKey, processValues[otherKey]);
});
}
});
})
}
return (
@ -744,7 +741,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
formValidations[fieldName] = validation;
};
if (tableMetaData)
if(tableMetaData)
{
console.log("Adding table name field... ?", tableMetaData.name);
addField("tableName", {type: "hidden", omitFromQDynamicForm: true}, tableMetaData.name, null);
@ -797,15 +794,15 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
////////////////////////////////////////////////////////////////////////////////////
Object.keys(dynamicFormFields).forEach((key: any) =>
{
if (dynamicFormFields[key].possibleValueProps)
if(dynamicFormFields[key].possibleValueProps)
{
dynamicFormFields[key].possibleValueProps.otherValues = dynamicFormFields[key].possibleValueProps.otherValues ?? new Map<string, any>();
Object.keys(initialValues).forEach((ivKey: any) =>
{
dynamicFormFields[key].possibleValueProps.otherValues.set(ivKey, initialValues[ivKey]);
});
})
}
});
})
////////////////////////////////////////////////////
// disable all fields if this is a bulk edit form //
@ -1105,12 +1102,6 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
}
}
if (localStorage.getItem(tableVariantLocalStorageKey))
{
let tableVariant = JSON.parse(localStorage.getItem(tableVariantLocalStorageKey));
queryStringPairsForInit.push(`tableVariant=${JSON.stringify(tableVariant)}`);
}
try
{
const qInstance = await Client.getInstance().loadMetaData();
@ -1156,9 +1147,9 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
}
}
if (tableMetaData)
if(tableMetaData)
{
queryStringPairsForInit.push(`tableName=${tableMetaData.name}`);
queryStringPairsForInit.push(`tableName=${tableMetaData.name}`)
}
try
@ -1196,12 +1187,6 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
formData.append(key, values[key]);
});
if (localStorage.getItem(tableVariantLocalStorageKey))
{
let tableVariant = JSON.parse(localStorage.getItem(tableVariantLocalStorageKey));
formData.append("tableVariant", JSON.stringify(tableVariant));
}
if (doesStepHaveComponent(activeStep, QComponentType.BULK_EDIT_FORM))
{
const bulkEditEnabledFields: string[] = [];

View File

@ -82,8 +82,7 @@ const ROWS_PER_PAGE_LOCAL_STORAGE_KEY_ROOT = "qqq.rowsPerPage";
const PINNED_COLUMNS_LOCAL_STORAGE_KEY_ROOT = "qqq.pinnedColumns";
const SEEN_JOIN_TABLES_LOCAL_STORAGE_KEY_ROOT = "qqq.seenJoinTables";
const DENSITY_LOCAL_STORAGE_KEY_ROOT = "qqq.density";
export const TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT = "qqq.tableVariant";
const TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT = "qqq.tableVariant";
interface Props
{

View File

@ -193,7 +193,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
{
document.removeEventListener("keydown", down)
}
}, [dotMenuOpen, keyboardHelpOpen, showEditChildForm, showAudit, metaData])
}, [dotMenuOpen, keyboardHelpOpen, showEditChildForm, showAudit, metaData, location])
const gotoCreate = () =>
{

View File

@ -18,7 +18,7 @@ import org.openqa.selenium.chrome.ChromeOptions;
*******************************************************************************/
public class QBaseSeleniumTest
{
private static ChromeOptions chromeOptions;
protected static ChromeOptions chromeOptions;
protected WebDriver driver;
protected QSeleniumJavalin qSeleniumJavalin;
@ -52,15 +52,29 @@ public class QBaseSeleniumTest
**
*******************************************************************************/
@BeforeEach
void beforeEach()
public void beforeEach()
{
driver = new ChromeDriver(chromeOptions);
driver.manage().window().setSize(new Dimension(1700, 1300));
qSeleniumLib = new QSeleniumLib(driver);
qSeleniumJavalin = new QSeleniumJavalin();
addJavalinRoutes(qSeleniumJavalin);
qSeleniumJavalin.start();
if(useInternalJavalin())
{
qSeleniumJavalin = new QSeleniumJavalin();
addJavalinRoutes(qSeleniumJavalin);
qSeleniumJavalin.start();
}
}
/*******************************************************************************
** control if the test needs to start its own javalin server, or if we're running
** in an environment where an external web server is being used.
*******************************************************************************/
protected boolean useInternalJavalin()
{
return (true);
}
@ -75,6 +89,8 @@ public class QBaseSeleniumTest
.withRouteToFile("/metaData/authentication", "metaData/authentication.json")
.withRouteToFile("/metaData/table/person", "metaData/table/person.json")
.withRouteToFile("/metaData/table/city", "metaData/table/person.json")
.withRouteToFile("/metaData/table/script", "metaData/table/script.json")
.withRouteToFile("/metaData/table/scriptRevision", "metaData/table/scriptRevision.json")
.withRouteToFile("/processes/querySavedFilter/init", "processes/querySavedFilter/init.json");
}

View File

@ -5,6 +5,7 @@ import java.io.File;
import java.time.Duration;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -96,6 +97,17 @@ public class QSeleniumLib
/*******************************************************************************
** Getter for BASE_URL
**
*******************************************************************************/
public String getBaseUrl()
{
return BASE_URL;
}
/*******************************************************************************
**
*******************************************************************************/
@ -265,6 +277,31 @@ public class QSeleniumLib
/*******************************************************************************
**
*******************************************************************************/
public void waitForNumberOfWindowsToBe(int number)
{
LOG.debug("Waiting for number of windows (tabs) to be [" + number + "]");
long start = System.currentTimeMillis();
do
{
if(driver.getWindowHandles().size() == number)
{
LOG.debug("Number of windows (tabs) is [" + number + "]");
return;
}
sleepABit();
}
while(start + (1000 * WAIT_SECONDS) > System.currentTimeMillis());
fail("Failed waiting for number of windows (tabs) to be [" + number + "] after [" + WAIT_SECONDS + "] seconds.");
}
/*******************************************************************************
**
*******************************************************************************/
@ -293,6 +330,53 @@ public class QSeleniumLib
/*******************************************************************************
**
*******************************************************************************/
public void switchToSecondaryTab()
{
String originalWindow = driver.getWindowHandle();
waitForNumberOfWindowsToBe(2);
Set<String> windowHandles = driver.getWindowHandles();
for(String windowHandle : windowHandles)
{
if(!windowHandle.equals(originalWindow))
{
driver.switchTo().window(windowHandle);
return;
}
}
fail("Failed to find a window handle not equal to the original window handle. Original=[" + originalWindow + "]. All=[" + windowHandles + "]");
}
/*******************************************************************************
**
*******************************************************************************/
public void closeSecondaryTab()
{
String originalWindow = driver.getWindowHandle();
driver.close();
Set<String> windowHandles = driver.getWindowHandles();
for(String windowHandle : windowHandles)
{
if(!windowHandle.equals(originalWindow))
{
driver.switchTo().window(windowHandle);
return;
}
}
fail("Failed to find a window handle not equal to the original window handle. Original=[" + originalWindow + "]. All=[" + windowHandles + "]");
}
@FunctionalInterface
public interface Code<T>
{

View File

@ -0,0 +1,63 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.materialdashboard.tests;
import com.kingsrook.qqq.materialdashboard.lib.QBaseSeleniumTest;
import com.kingsrook.qqq.materialdashboard.lib.javalin.QSeleniumJavalin;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
** Test for Associated Record Scripts functionality.
*******************************************************************************/
public class ClickLinkOnRecordThenEditShortcutTest extends QBaseSeleniumTest
{
/*******************************************************************************
**
*******************************************************************************/
@Override
protected void addJavalinRoutes(QSeleniumJavalin qSeleniumJavalin)
{
super.addJavalinRoutes(qSeleniumJavalin);
qSeleniumJavalin.withRouteToFile("/data/script/1", "data/script/1.json");
qSeleniumJavalin.withRouteToFile("/data/scriptRevision/100", "data/scriptRevision/100.json");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testClickLinkOnRecordThenEditShortcutTest()
{
qSeleniumLib.gotoAndWaitForBreadcrumbHeader("/developer/script/1", "Hello, Script");
qSeleniumLib.waitForSelectorContaining("A", "100").click();
qSeleniumLib.waitForSelectorContaining("BUTTON", "actions").sendKeys("e");
assertTrue(qSeleniumLib.driver.getCurrentUrl().endsWith("/scriptRevision/100/edit"));
}
}

View File

@ -64,8 +64,6 @@ public class ScriptTableTest extends QBaseSeleniumTest
qSeleniumLib.waitForSelectorContaining("DIV.ace_line", "var hello;");
qSeleniumLib.waitForSelectorContaining("DIV", "2nd commit");
qSeleniumLib.waitForSelectorContaining("DIV", "Initial checkin");
qSeleniumLib.waitForever();
}
}

View File

@ -1,14 +1,22 @@
{
"tableName": "scriptRevision",
"recordLabel": "Hello, Script Revision",
"values": {
"id": 100,
"id": "100",
"name": "Hello, Script Revision",
"sequenceNo": "22",
"commitMessage": "Initial checkin",
"author": "Jon Programmer",
"createDate": "2023-02-18T00:47:51Z",
"modifyDate": "2023-02-18T00:47:51Z"
},
"displayValues": {
"id": "1",
"name": "Hello, Script Revision",
"scriptId": "1",
"sequenceNo": "22",
"createDate": "2023-02-18T00:47:51Z",
"modifyDate": "2023-02-18T00:47:51Z"
},
"associatedRecords": {
"files": [
@ -25,4 +33,4 @@
}
]
}
}
}

View File

@ -131,7 +131,8 @@
"capabilities": [
"TABLE_COUNT",
"TABLE_GET",
"TABLE_QUERY"
"TABLE_QUERY",
"TABLE_UPDATE"
],
"readPermission": true,
"insertPermission": true,

View File

@ -0,0 +1,152 @@
{
"table": {
"name": "scriptRevision",
"label": "Script Revision",
"isHidden": false,
"primaryKeyField": "id",
"iconName": "history_edu",
"fields": {
"scriptId": {
"name": "scriptId",
"label": "Script",
"type": "INTEGER",
"isRequired": false,
"isEditable": true,
"isHeavy": false,
"possibleValueSourceName": "script",
"displayFormat": "%s",
"adornments": [
{
"type": "SIZE",
"values": {
"width": "large"
}
},
{
"type": "LINK",
"values": {
"toRecordFromTable": "script"
}
}
]
},
"apiName": {
"name": "apiName",
"label": "API Name",
"type": "STRING",
"isRequired": false,
"isEditable": true,
"isHeavy": false,
"possibleValueSourceName": "apiName",
"displayFormat": "%s"
},
"sequenceNo": {
"name": "sequenceNo",
"label": "Sequence No",
"type": "INTEGER",
"isRequired": false,
"isEditable": true,
"isHeavy": false,
"displayFormat": "%s"
},
"apiVersion": {
"name": "apiVersion",
"label": "API Version",
"type": "STRING",
"isRequired": false,
"isEditable": true,
"isHeavy": false,
"possibleValueSourceName": "apiVersion",
"displayFormat": "%s"
},
"commitMessage": {
"name": "commitMessage",
"label": "Commit Message",
"type": "STRING",
"isRequired": false,
"isEditable": true,
"isHeavy": false,
"displayFormat": "%s"
},
"modifyDate": {
"name": "modifyDate",
"label": "Modify Date",
"type": "DATE_TIME",
"isRequired": false,
"isEditable": false,
"isHeavy": false,
"displayFormat": "%s"
},
"author": {
"name": "author",
"label": "Author",
"type": "STRING",
"isRequired": false,
"isEditable": true,
"isHeavy": false,
"displayFormat": "%s"
},
"id": {
"name": "id",
"label": "Id",
"type": "INTEGER",
"isRequired": false,
"isEditable": false,
"isHeavy": false,
"displayFormat": "%s"
},
"createDate": {
"name": "createDate",
"label": "Create Date",
"type": "DATE_TIME",
"isRequired": false,
"isEditable": false,
"isHeavy": false,
"displayFormat": "%s"
}
},
"sections": [
{
"name": "identity",
"label": "Identity",
"tier": "T1",
"fieldNames": [
"id",
"scriptId",
"sequenceNo"
],
"icon": {
"name": "badge"
},
"isHidden": false
},
{
"name": "dates",
"label": "Dates",
"tier": "T3",
"fieldNames": [
"createDate",
"modifyDate"
],
"icon": {
"name": "calendar_month"
},
"isHidden": false
}
],
"exposedJoins": [],
"capabilities": [
"TABLE_COUNT",
"TABLE_GET",
"TABLE_QUERY",
"TABLE_INSERT",
"TABLE_UPDATE",
"QUERY_STATS"
],
"readPermission": true,
"insertPermission": true,
"editPermission": true,
"deletePermission": true,
"usesVariants": false
}
}