From 284571803bd4a8a63feb4e529960480392735d1e Mon Sep 17 00:00:00 2001 From: Anand Srinivasan <66776129+eco-monk@users.noreply.github.com> Date: Mon, 12 Dec 2022 10:21:14 +0530 Subject: [PATCH] fix: update settings pane validations (#18739) * init fix * button style update * decode url in preview * bring back custom slug validation * custom slug validation on type * remove imports * handle special characters in url preview * change label * code clean up * remove pageId from action name selector * messages clean up * tests for validations * cypress cases update * Renamed helper methods * add index for test cases * AppSettings specs script ts updates * Added Bud id in title #18698 * GIt Import spec fix * GItImport spec fix * Git import unskip * Skipping Git import Co-authored-by: Aishwarya UR --- .../ActionExecution/ClearStore_spec.ts | 48 +++---- .../Git/GitImport/GitImport_spec.js | 9 +- ...Theming_spec.ts => GitWithTheming_spec.js} | 4 +- .../SettingsPane/GeneralSettings_spec.ts | 54 ++++--- .../SettingsPane/PageSettings_spec.ts | 132 ++++++++++++------ .../ThemingTests/Basic_spec.js | 8 +- ...{ThemeReset_spec.ts => ThemeReset_spec.js} | 2 +- ..._Default_spec.ts => Theme_Default_spec.js} | 8 +- ...idget_spec.ts => Theme_FormWidget_spec.js} | 6 +- .../Theme_MultiSelectWidget_spec.js | 4 +- ...=> FilePickerV2_Widget_Reskinning_spec.js} | 8 +- .../cypress/support/Objects/ObjectsCore.ts | 13 ++ .../cypress/support/Objects/Registry.ts | 43 ++++-- .../cypress/support/Pages/AggregateHelper.ts | 4 - .../support/Pages/AppSettings/AppSettings.ts | 83 +++++++++-- .../Pages/AppSettings/GeneralSettings.ts | 37 +++-- .../support/Pages/AppSettings/PageSettings.ts | 115 ++++++++++++--- app/client/src/ce/constants/messages.ts | 4 +- .../src/entities/URLRedirect/URLAssembly.ts | 4 +- .../AppSettings/GeneralSettings.tsx | 28 ++-- .../AppSettings/PageSettings.tsx | 73 ++++++---- .../src/pages/Editor/AppSettingsPane/Utils.ts | 16 ++- .../pages/Editor/CanvasPropertyPane/index.tsx | 2 +- .../EditorAppName/NavigationMenuData.ts | 2 +- .../Editor/Explorer/Pages/PageContextMenu.tsx | 10 +- app/client/src/utils/validation/CheckRegex.ts | 24 ---- 26 files changed, 495 insertions(+), 246 deletions(-) rename app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Git/GitWithTheming/{GitWithTheming_spec.ts => GitWithTheming_spec.js} (98%) rename app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/{ThemeReset_spec.ts => ThemeReset_spec.js} (97%) rename app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/{Theme_Default_spec.ts => Theme_Default_spec.js} (93%) rename app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/{Theme_FormWidget_spec.ts => Theme_FormWidget_spec.js} (99%) rename app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Filepicker/{FilePickerV2_Widget_Reskinning_spec.ts => FilePickerV2_Widget_Reskinning_spec.js} (96%) create mode 100644 app/client/cypress/support/Objects/ObjectsCore.ts delete mode 100644 app/client/src/utils/validation/CheckRegex.ts diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ActionExecution/ClearStore_spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ActionExecution/ClearStore_spec.ts index 0f2f4c6522..9f76e36b5e 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ActionExecution/ClearStore_spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ActionExecution/ClearStore_spec.ts @@ -1,17 +1,9 @@ -import { ObjectsRegistry } from "../../../../support/Objects/Registry"; - -const { - AggregateHelper: agHelper, - DeployMode: deployMode, - EntityExplorer: ee, - JSEditor: jsEditor, - PropertyPane: propPane, -} = ObjectsRegistry; +import * as _objects from "../../../../support/Objects/ObjectsCore" describe("clearStore Action test", () => { before(() => { - ee.DragDropWidgetNVerify("buttonwidget", 100, 100); - ee.NavigateToSwitcher("explorer"); + _objects.ee.DragDropWidgetNVerify("buttonwidget", 100, 100); + _objects.ee.NavigateToSwitcher("explorer"); }); it("1. Feature 11639 : Clear all store value", function() { @@ -24,7 +16,7 @@ describe("clearStore Action test", () => { storeValue('val3', 'value 3'), ]; await Promise.all(values); - await showAlert(JSON.stringify(appsmith.store)); + await showAlert(JSON.stringify(appsmith.store)); }, clearStore: async () => { await clearStore(); @@ -33,7 +25,7 @@ describe("clearStore Action test", () => { }`; // Create js object - jsEditor.CreateJSObject(JS_OBJECT_BODY, { + _objects.jsEditor.CreateJSObject(JS_OBJECT_BODY, { paste: true, completeReplace: true, toRun: false, @@ -41,40 +33,40 @@ describe("clearStore Action test", () => { shouldCreateNewJSObj: true, }); - ee.SelectEntityByName("Button1", "Widgets"); - propPane.UpdatePropertyFieldValue("Label", ""); - propPane.TypeTextIntoField("Label", "StoreValue"); + _objects.ee.SelectEntityByName("Button1", "Widgets"); + _objects.propPane.UpdatePropertyFieldValue("Label", ""); + _objects.propPane.TypeTextIntoField("Label", "StoreValue"); cy.get("@jsObjName").then((jsObj: any) => { - propPane.SelectJSFunctionToExecute( + _objects.propPane.SelectJSFunctionToExecute( "onClick", jsObj as string, "storeValue", ); }); - ee.DragDropWidgetNVerify("buttonwidget", 100, 200); - ee.SelectEntityByName("Button2", "Widgets"); - propPane.UpdatePropertyFieldValue("Label", ""); - propPane.TypeTextIntoField("Label", "ClearStore"); + _objects.ee.DragDropWidgetNVerify("buttonwidget", 100, 200); + _objects.ee.SelectEntityByName("Button2", "Widgets"); + _objects.propPane.UpdatePropertyFieldValue("Label", ""); + _objects.propPane.TypeTextIntoField("Label", "ClearStore"); cy.get("@jsObjName").then((jsObj: any) => { - propPane.SelectJSFunctionToExecute( + _objects.propPane.SelectJSFunctionToExecute( "onClick", jsObj as string, "clearStore", ); }); - deployMode.DeployApp(); - agHelper.ClickButton("StoreValue"); - agHelper.AssertContains( + _objects.deployMode.DeployApp(); + _objects.agHelper.ClickButton("StoreValue"); + _objects.agHelper.AssertContains( JSON.stringify({ val1: "value 1", val2: "value 2", val3: "value 3", }), ); - agHelper.ClickButton("ClearStore"); - agHelper.AssertContains(JSON.stringify({})); - deployMode.NavigateBacktoEditor(); + _objects.agHelper.ClickButton("ClearStore"); + _objects.agHelper.AssertContains(JSON.stringify({})); + _objects.deployMode.NavigateBacktoEditor(); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Git/GitImport/GitImport_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Git/GitImport/GitImport_spec.js index 7c466b5177..fa13652707 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Git/GitImport/GitImport_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Git/GitImport/GitImport_spec.js @@ -71,7 +71,7 @@ describe("Git import flow ", function() { cy.connectToGitRepo(repoName); }); }); - cy.wait(4000); // for git connection to settle! + cy.wait(5000); // for git connection to settle! }); it("2. Import the previous app connected to Git and reconnect Postgres, MySQL and Mongo db ", () => { @@ -90,7 +90,7 @@ describe("Git import flow ", function() { .next() .click(); cy.importAppFromGit(repoName); - cy.wait(100); + cy.wait(5000); cy.get(reconnectDatasourceModal.Modal).should("be.visible"); cy.ReconnectDatasource("TEDPostgres"); cy.wait(500); @@ -142,7 +142,8 @@ describe("Git import flow ", function() { // verify js object binded to input widget cy.xpath("//input[@value='Success']").should("be.visible"); }); - // skipping these tests until bug #18776 is fixed + + // skipping this due to open bug #18776 it.skip("4. Create a new branch, clone page and validate data on that branch in view and edit mode", () => { cy.createGitBranch(newBranch); cy.get(".tbody") @@ -213,6 +214,7 @@ describe("Git import flow ", function() { cy.wait(2000); }); + // skipping this due to open bug #18776 it.skip("5. Switch to master and verify data in edit and view mode", () => { cy.switchGitBranch("master"); cy.wait(2000); @@ -236,6 +238,7 @@ describe("Git import flow ", function() { cy.wait(2000); }); + // skipping this due to open bug #18776 it.skip("6. Add widget to master, merge then checkout to child branch and verify data", () => { cy.get(explorer.widgetSwitchId).click(); cy.wait(2000); // wait for transition diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Git/GitWithTheming/GitWithTheming_spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Git/GitWithTheming/GitWithTheming_spec.js similarity index 98% rename from app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Git/GitWithTheming/GitWithTheming_spec.ts rename to app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Git/GitWithTheming/GitWithTheming_spec.js index b59c84aa17..c5b03b0270 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Git/GitWithTheming/GitWithTheming_spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Git/GitWithTheming/GitWithTheming_spec.js @@ -32,7 +32,7 @@ describe("Git with Theming:", function() { }); }); it("Bug #13860 Theming is not getting applied on view mode when the app is connected to Git", function() { - appSettings.OpenPaneFromCta(); + appSettings.OpenAppSettings(); appSettings.GoToThemeSettings(); // apply theme on master branch and deploy cy.get(commonlocators.changeThemeBtn).click({ force: true }); @@ -65,7 +65,7 @@ describe("Git with Theming:", function() { cy.wait(1000); cy.get("body").click(300, 300); // change theme on tempBranch - appSettings.OpenPaneFromCta(); + appSettings.OpenAppSettings(); appSettings.GoToThemeSettings(); cy.get(commonlocators.changeThemeBtn).click({ force: true }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/SettingsPane/GeneralSettings_spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/SettingsPane/GeneralSettings_spec.ts index 065c875ddc..e5d6a7632f 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/SettingsPane/GeneralSettings_spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/SettingsPane/GeneralSettings_spec.ts @@ -1,25 +1,43 @@ -import { ObjectsRegistry } from "../../../../support/Objects/Registry"; -import { checkUrl } from "../../../../support/Pages/AppSettings/Utils"; - -const appSettings = ObjectsRegistry.AppSettings, - deployMode = ObjectsRegistry.DeployMode, - homePage = ObjectsRegistry.HomePage; +import * as _ from "../../../../support/Objects/ObjectsCore"; +let guid: string; describe("General Settings", () => { - it("App name change updates URL", () => { - appSettings.OpenPaneFromCta(); - appSettings.GoToGeneralSettings(); - appSettings.general.changeAppNameAndVerifyUrl(true, "myapp"); - homePage.GetAppName().then((appName) => { - deployMode.DeployApp(); - checkUrl(appName as string, "Page1", undefined, false); - deployMode.NavigateBacktoEditor(); + before(() => { + _.agHelper.GenerateUUID(); + cy.get("@guid").then((uid: any) => { + guid = uid; }); }); - it("Handles app icon change", () => { - appSettings.OpenPaneFromCta(); - appSettings.GoToGeneralSettings(); - appSettings.general.changeAppIcon(); + it("1. App name change updates URL", () => { + _.appSettings.OpenAppSettings(); + _.appSettings.GoToGeneralSettings(); + _.generalSettings.UpdateAppNameAndVerifyUrl(true, guid); + _.homePage.GetAppName().then((appName) => { + _.deployMode.DeployApp(); + _.appSettings.CheckUrl(appName as string, "Page1", undefined, false); + _.deployMode.NavigateBacktoEditor(); + }); + }); + + it("2. Handles app icon change", () => { + _.appSettings.OpenAppSettings(); + _.appSettings.GoToGeneralSettings(); + _.generalSettings.UpdateAppIcon(); + _.appSettings.ClosePane(); + }); + + it("3. App name allows special and accented character", () => { + _.appSettings.OpenAppSettings(); + _.appSettings.GoToGeneralSettings(); + _.generalSettings.UpdateAppNameAndVerifyUrl(true, guid + "!@#œ™¡", guid); + _.appSettings.ClosePane(); + }); + + it("4. Veirfy App name doesn't allow empty", () => { + _.appSettings.OpenAppSettings(); + _.appSettings.GoToGeneralSettings(); + _.generalSettings.AssertAppErrorMessage("", "App name cannot be empty"); + _.appSettings.ClosePane(); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/SettingsPane/PageSettings_spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/SettingsPane/PageSettings_spec.ts index eb6a09513f..2ca83710af 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/SettingsPane/PageSettings_spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/SettingsPane/PageSettings_spec.ts @@ -1,55 +1,101 @@ -import { ObjectsRegistry } from "../../../../support/Objects/Registry"; -import { checkUrl } from "../../../../support/Pages/AppSettings/Utils"; - -const appSettings = ObjectsRegistry.AppSettings, - ee = ObjectsRegistry.EntityExplorer, - agHelper = ObjectsRegistry.AggregateHelper, - commonLocators = ObjectsRegistry.CommonLocators, - deployMode = ObjectsRegistry.DeployMode, - homePage = ObjectsRegistry.HomePage; +import * as _ from "../../../../support/Objects/ObjectsCore"; describe("Page Settings", () => { - it("Page name change updates URL", () => { - appSettings.OpenPaneFromCta(); - appSettings.GoToPageSettings("Page1"); - appSettings.page.changePageNameAndVerifyUrl("Page2"); - homePage.GetAppName().then((appName) => { - deployMode.DeployApp(); - checkUrl(appName as string, "Page2", undefined, false); - deployMode.NavigateBacktoEditor(); + it("1. Page name change updates URL", () => { + _.appSettings.OpenAppSettings(); + _.appSettings.GoToPageSettings("Page1"); + _.pageSettings.UpdatePageNameAndVerifyUrl("Page2", undefined, false); + _.homePage.GetAppName().then((appName) => { + _.deployMode.DeployApp(); + _.appSettings.CheckUrl(appName as string, "Page2", undefined, false); + _.deployMode.NavigateBacktoEditor(); }); - cy.wait(2000); + _.agHelper.Sleep(); }); - it("Custom slug change updates URL", () => { - appSettings.OpenPaneFromCta(); - appSettings.GoToPageSettings("Page2"); - appSettings.page.changeCustomSlugAndVerifyUrl("custom"); - homePage.GetAppName().then((appName) => { - deployMode.DeployApp(); - checkUrl(appName as string, "Page2", "custom", false); - deployMode.NavigateBacktoEditor(); + it("2. Custom slug change updates URL", () => { + _.appSettings.OpenAppSettings(); + _.appSettings.GoToPageSettings("Page2"); + _.pageSettings.UpdateCustomSlugAndVerifyUrl("custom"); + _.homePage.GetAppName().then((appName) => { + _.deployMode.DeployApp(); + _.appSettings.CheckUrl(appName as string, "Page2", "custom", false); + _.deployMode.NavigateBacktoEditor(); }); - cy.wait(2000); + _.agHelper.Sleep(); }); - it("Check default page is updated", () => { - ee.AddNewPage(); - appSettings.OpenPaneFromCta(); - appSettings.GoToPageSettings("Page3"); - appSettings.page.setAsHomePage(); - appSettings.page.isHomePage("Page3"); + it("3. Check SetAsHome page setting", () => { + _.ee.AddNewPage(); + _.appSettings.OpenAppSettings(); + _.appSettings.GoToPageSettings("Page3"); + _.pageSettings.ToggleHomePage(); + _.pageSettings.AssertHomePage("Page3"); }); - it("Check page navigation is updated", () => { - agHelper.GetNClick(commonLocators._previewModeToggle); - agHelper.AssertElementExist(commonLocators._deployedPage); - agHelper.GetNClick(commonLocators._editModeToggle); - appSettings.OpenPaneFromCta(); - appSettings.GoToPageSettings("Page2"); - appSettings.page.changePageNavigationSetting(); - agHelper.GetNClick(commonLocators._previewModeToggle); - agHelper.AssertElementAbsence(commonLocators._deployedPage); - agHelper.GetNClick(commonLocators._editModeToggle); + it("4. Check SetPageNavigation settings", () => { + _.agHelper.GetNClick(_.locators._previewModeToggle); + _.agHelper.AssertElementExist(_.locators._deployedPage); + _.agHelper.GetNClick(_.locators._editModeToggle); + _.appSettings.OpenAppSettings(); + _.appSettings.GoToPageSettings("Page2"); + _.pageSettings.TogglePageNavigation(); + _.agHelper.GetNClick(_.locators._previewModeToggle); + _.agHelper.AssertElementAbsence(_.locators._deployedPage); + _.agHelper.GetNClick(_.locators._editModeToggle); + }); + + it("5. Page name allows accented character", () => { + _.appSettings.OpenAppSettings(); + _.appSettings.GoToPageSettings("Page3"); + _.pageSettings.UpdatePageNameAndVerifyUrl("Page3œßð", "Page3"); + _.appSettings.ClosePane(); + }); + + it("6. Page name doesn't allow special character", () => { + _.appSettings.OpenAppSettings(); + _.appSettings.GoToPageSettings("Page3"); + _.pageSettings.UpdatePageNameAndVerifyTextValue("Page3!@#", "Page3 "); + _.appSettings.ClosePane(); + }); + + it("7. Page name doesn't allow empty", () => { + _.appSettings.OpenAppSettings(); + _.appSettings.GoToPageSettings("Page3"); + _.pageSettings.AssertPageErrorMessage( + "", + "Page name cannot be empty", + ); + _.appSettings.ClosePane(); + }); + + it("8. Bug #18698 : Page name doesn't allow duplicate name", () => { + _.appSettings.OpenAppSettings(); + _.appSettings.GoToPageSettings("Page3"); + _.pageSettings.AssertPageErrorMessage( + "Page2", + "Page2 is already being used.", + ); + _.appSettings.ClosePane(); + }); + + it("9. Page name doesn't allow keywords", () => { + _.appSettings.OpenAppSettings(); + _.appSettings.GoToPageSettings("Page3"); + _.pageSettings.AssertPageErrorMessage( + "appsmith", + "appsmith is already being used.", + ); + _.appSettings.ClosePane(); + }); + + it("10. Custom slug doesn't allow special/accented characters", () => { + _.appSettings.OpenAppSettings(); + _.appSettings.GoToPageSettings("Page2"); + _.pageSettings.UpdateCustomSlugAndVerifyTextValue( + "custom-slug!@#œßð", + "custom-slug", + ); + _.appSettings.ClosePane(); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Basic_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Basic_spec.js index 3549574ce9..87b257a322 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Basic_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Basic_spec.js @@ -26,7 +26,7 @@ describe("App Theming funtionality", function() { themesSection(sectionName, themeName) + "/following-sibling::button"; it("1. Checks if theme can be changed to one of the existing themes", function() { - appSettings.OpenPaneFromCta(); + appSettings.OpenAppSettings(); appSettings.GoToThemeSettings(); cy.get(commonlocators.changeThemeBtn).click({ force: true }); @@ -70,7 +70,7 @@ describe("App Theming funtionality", function() { .first(0) .trigger("click", { force: true }); - appSettings.OpenPaneFromCta(); + appSettings.OpenAppSettings(); appSettings.GoToThemeSettings(); //Click the back button //Commenting below since expanded by default @@ -219,7 +219,7 @@ describe("App Theming funtionality", function() { .first(0) .trigger("click", { force: true }); - appSettings.OpenPaneFromCta(); + appSettings.OpenAppSettings(); appSettings.GoToThemeSettings(); //#region Change Font & verify widgets: // cy.contains("Font") @@ -1009,7 +1009,7 @@ describe("App Theming funtionality", function() { .first(0) .trigger("click", { force: true }); - appSettings.OpenPaneFromCta(); + appSettings.OpenAppSettings(); appSettings.GoToThemeSettings(); cy.get(commonlocators.changeThemeBtn).click({ force: true }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/ThemeReset_spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/ThemeReset_spec.js similarity index 97% rename from app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/ThemeReset_spec.ts rename to app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/ThemeReset_spec.js index d5f914202c..dc5a8f3eac 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/ThemeReset_spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/ThemeReset_spec.js @@ -24,7 +24,7 @@ describe("Theme validation usecases", function() { // click on canvas to see the theming pane cy.get("#canvas-selection-0").click({ force: true }); - appSettings.OpenPaneFromCta(); + appSettings.OpenAppSettings(); appSettings.GoToThemeSettings(); // reset theme cy.contains("Theme Properties") diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_Default_spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_Default_spec.js similarity index 93% rename from app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_Default_spec.ts rename to app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_Default_spec.js index b278a6b240..763b1c5ade 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_Default_spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_Default_spec.js @@ -1,18 +1,14 @@ import { ObjectsRegistry } from "../../../../support/Objects/Registry"; -const testdata = require("../../../../fixtures/testdata.json"); -const apiwidget = require("../../../../locators/apiWidgetslocator.json"); const widgetsPage = require("../../../../locators/Widgets.json"); const explorer = require("../../../../locators/explorerlocators.json"); const commonlocators = require("../../../../locators/commonlocators.json"); const formWidgetsPage = require("../../../../locators/FormWidgets.json"); -const publish = require("../../../../locators/publishWidgetspage.json"); const themelocator = require("../../../../locators/ThemeLocators.json"); const appSettings = ObjectsRegistry.AppSettings; let themeBackgroudColor; -let themeFont; describe("Theme validation for default data", function() { it("Drag and drop form widget and validate Default color/font/shadow/border and list of font validation", function() { @@ -33,7 +29,7 @@ describe("Theme validation for default data", function() { cy.get(themelocator.canvas).click({ force: true }); cy.wait(2000); - appSettings.OpenPaneFromCta(); + appSettings.OpenAppSettings(); appSettings.GoToThemeSettings(); //Border validation //cy.contains("Border").click({ force: true }); @@ -93,7 +89,7 @@ describe("Theme validation for default data", function() { .should("have.css", "background-color") .and("eq", "rgb(21, 128, 61)"); cy.get("#canvas-selection-0").click({ force: true }); - appSettings.OpenPaneFromCta(); + appSettings.OpenAppSettings(); appSettings.GoToThemeSettings(); //Change the Theme cy.get(commonlocators.changeThemeBtn).click({ force: true }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_FormWidget_spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_FormWidget_spec.js similarity index 99% rename from app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_FormWidget_spec.ts rename to app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_FormWidget_spec.js index af99c37a6d..e41b1f1772 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_FormWidget_spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_FormWidget_spec.js @@ -30,7 +30,7 @@ describe("Theme validation usecases", function() { cy.get(themelocator.canvas).click({ force: true }); cy.wait(2000); - appSettings.OpenPaneFromCta(); + appSettings.OpenAppSettings(); appSettings.GoToThemeSettings(); //Border validation //cy.contains("Border").click({ force: true }); @@ -185,7 +185,7 @@ describe("Theme validation usecases", function() { .and("eq", "rgb(21, 128, 61)"); cy.get("#canvas-selection-0").click({ force: true }); - appSettings.OpenPaneFromCta(); + appSettings.OpenAppSettings(); appSettings.GoToThemeSettings(); //Change the Theme cy.get(commonlocators.changeThemeBtn).click({ force: true }); @@ -247,7 +247,7 @@ describe("Theme validation usecases", function() { cy.get("#canvas-selection-0").click({ force: true }); - appSettings.OpenPaneFromCta(); + appSettings.OpenAppSettings(); appSettings.GoToThemeSettings(); //Change the Theme cy.get(commonlocators.changeThemeBtn).click({ force: true }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_MultiSelectWidget_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_MultiSelectWidget_spec.js index 39210927b4..6552a77c27 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_MultiSelectWidget_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_MultiSelectWidget_spec.js @@ -17,7 +17,7 @@ describe("Theme validation usecase for multi-select widget", function() { cy.get(themelocator.canvas).click({ force: true }); cy.wait(2000); - appSettings.OpenPaneFromCta(); + appSettings.OpenAppSettings(); appSettings.GoToThemeSettings(); //Border validation //cy.contains("Border").click({ force: true }); @@ -124,7 +124,7 @@ describe("Theme validation usecase for multi-select widget", function() { it("3. Validate current theme feature", function() { cy.get("#canvas-selection-0").click({ force: true }); - appSettings.OpenPaneFromCta(); + appSettings.OpenAppSettings(); appSettings.GoToThemeSettings(); //Change the Theme cy.get(commonlocators.changeThemeBtn).click({ force: true }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Filepicker/FilePickerV2_Widget_Reskinning_spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Filepicker/FilePickerV2_Widget_Reskinning_spec.js similarity index 96% rename from app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Filepicker/FilePickerV2_Widget_Reskinning_spec.ts rename to app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Filepicker/FilePickerV2_Widget_Reskinning_spec.js index 854037cee9..b4294bbd8f 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Filepicker/FilePickerV2_Widget_Reskinning_spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Filepicker/FilePickerV2_Widget_Reskinning_spec.js @@ -15,7 +15,7 @@ describe("Checkbox Widget Functionality", function() { // Click on canvas to get global theme settings cy.get(commonlocators.canvas).click({ force: true }); - appSettings.OpenPaneFromCta(); + appSettings.OpenAppSettings(); appSettings.GoToThemeSettings(); cy.get(commonlocators.themeAppBorderRadiusBtn) .last() @@ -61,7 +61,7 @@ describe("Checkbox Widget Functionality", function() { // Change the theme border radius to M and check if the remove file icon's border radius is 4px; cy.get(commonlocators.canvas).click({ force: true }); - appSettings.OpenPaneFromCta(); + appSettings.OpenAppSettings(); appSettings.GoToThemeSettings(); cy.get(commonlocators.themeAppBorderRadiusBtn) .eq(1) @@ -85,7 +85,7 @@ describe("Checkbox Widget Functionality", function() { // Change the global theme primary color cy.get(commonlocators.canvas).click({ force: true }); cy.wait(300); - appSettings.OpenPaneFromCta(); + appSettings.OpenAppSettings(); appSettings.GoToThemeSettings(); cy.get(themeLocator.inputColor).click({ force: true }); @@ -118,7 +118,7 @@ describe("Checkbox Widget Functionality", function() { cy.get(".uppy-Dashboard-close").click({ force: true }); cy.get(commonlocators.canvas).click({ force: true }); cy.wait(300); - appSettings.OpenPaneFromCta(); + appSettings.OpenAppSettings(); appSettings.GoToThemeSettings(); cy.get(themeLocator.fontsSelected).click({ force: true }); diff --git a/app/client/cypress/support/Objects/ObjectsCore.ts b/app/client/cypress/support/Objects/ObjectsCore.ts new file mode 100644 index 0000000000..d74b57dfce --- /dev/null +++ b/app/client/cypress/support/Objects/ObjectsCore.ts @@ -0,0 +1,13 @@ +import { ObjectsRegistry } from "../Objects/Registry"; + +export const agHelper = ObjectsRegistry.AggregateHelper; +export const locators = ObjectsRegistry.CommonLocators; +export const ee = ObjectsRegistry.EntityExplorer; +export const jsEditor = ObjectsRegistry.JSEditor; +export const propPane = ObjectsRegistry.PropertyPane; +export const deployMode = ObjectsRegistry.DeployMode; +export const appSettings = ObjectsRegistry.AppSettings; +export const generalSettings = ObjectsRegistry.GeneralSettings; +export const pageSettings = ObjectsRegistry.PageSettings; +export const homePage = ObjectsRegistry.HomePage; +export const theme = ObjectsRegistry.ThemeSettings; diff --git a/app/client/cypress/support/Objects/Registry.ts b/app/client/cypress/support/Objects/Registry.ts index 7965316a13..dffc1c7343 100644 --- a/app/client/cypress/support/Objects/Registry.ts +++ b/app/client/cypress/support/Objects/Registry.ts @@ -13,6 +13,9 @@ import { GitSync } from "../Pages/GitSync"; import { FakerHelper } from "../Pages/FakerHelper"; import { DebuggerHelper } from "../Pages/DebuggerHelper"; import { AppSettings } from "../Pages/AppSettings/AppSettings"; +import { GeneralSettings } from "../Pages/AppSettings/GeneralSettings"; +import { PageSettings } from "../Pages/AppSettings/PageSettings"; +import { ThemeSettings } from "../Pages/AppSettings/ThemeSettings"; export class ObjectsRegistry { private static aggregateHelper__: AggregateHelper; @@ -119,20 +122,44 @@ export class ObjectsRegistry { return ObjectsRegistry.fakerHelper__; } - private static DebuggerHelper__: DebuggerHelper; + private static debuggerHelper__: DebuggerHelper; static get DebuggerHelper(): DebuggerHelper { - if (ObjectsRegistry.DebuggerHelper__ === undefined) { - ObjectsRegistry.DebuggerHelper__ = new DebuggerHelper(); + if (ObjectsRegistry.debuggerHelper__ === undefined) { + ObjectsRegistry.debuggerHelper__ = new DebuggerHelper(); } - return ObjectsRegistry.DebuggerHelper__; + return ObjectsRegistry.debuggerHelper__; } - private static AppSettings__: AppSettings; + private static appSettings__: AppSettings; static get AppSettings(): AppSettings { - if (ObjectsRegistry.AppSettings__ === undefined) { - ObjectsRegistry.AppSettings__ = new AppSettings(); + if (ObjectsRegistry.appSettings__ === undefined) { + ObjectsRegistry.appSettings__ = new AppSettings(); } - return ObjectsRegistry.AppSettings__; + return ObjectsRegistry.appSettings__; + } + + private static generalSettings__: GeneralSettings; + static get GeneralSettings(): GeneralSettings { + if (ObjectsRegistry.generalSettings__ === undefined) { + ObjectsRegistry.generalSettings__ = new GeneralSettings(); + } + return ObjectsRegistry.generalSettings__; + } + + private static pageSettings__: PageSettings; + static get PageSettings(): PageSettings { + if (ObjectsRegistry.pageSettings__ === undefined) { + ObjectsRegistry.pageSettings__ = new PageSettings(); + } + return ObjectsRegistry.pageSettings__; + } + + private static themeSettings__: ThemeSettings; + static get ThemeSettings(): ThemeSettings { + if (ObjectsRegistry.themeSettings__ === undefined) { + ObjectsRegistry.themeSettings__ = new ThemeSettings(); + } + return ObjectsRegistry.themeSettings__; } } diff --git a/app/client/cypress/support/Pages/AggregateHelper.ts b/app/client/cypress/support/Pages/AggregateHelper.ts index 7b6c999a87..4c713b36a8 100644 --- a/app/client/cypress/support/Pages/AggregateHelper.ts +++ b/app/client/cypress/support/Pages/AggregateHelper.ts @@ -526,10 +526,6 @@ export class AggregateHelper { this.GetElement(selector).clear(); } - public InvokeVal(selector: string) { - return cy.get(selector).invoke("val"); - } - public TypeText( selector: string, value: string, diff --git a/app/client/cypress/support/Pages/AppSettings/AppSettings.ts b/app/client/cypress/support/Pages/AppSettings/AppSettings.ts index c523736cf6..699d75a2ce 100644 --- a/app/client/cypress/support/Pages/AppSettings/AppSettings.ts +++ b/app/client/cypress/support/Pages/AppSettings/AppSettings.ts @@ -1,12 +1,10 @@ import { ObjectsRegistry } from "../../Objects/Registry"; -import { ThemeSettings } from "./ThemeSettings"; -import { GeneralSettings } from "./GeneralSettings"; -import { PageSettings } from "./PageSettings"; - export class AppSettings { private agHelper = ObjectsRegistry.AggregateHelper; + private theme = ObjectsRegistry.ThemeSettings; + private locators = { - _appSettings_cta: "#t--app-settings-cta", + _appSettings: "#t--app-settings-cta", _closeSettings: "#t--close-app-settings-pane", _themeSettingsHeader: "#t--theme-settings-header", _generalSettingsHeader: "#t--general-settings-header", @@ -14,12 +12,13 @@ export class AppSettings { `#t--page-settings-${pageName}`, }; - public readonly theme = new ThemeSettings(); - public readonly general = new GeneralSettings(); - public readonly page = new PageSettings(); + public errorMessageSelector = (fieldId: string) => { + fieldId = fieldId[0] === "#" ? fieldId.slice(1, fieldId.length) : fieldId; + return `//input[@id='${fieldId}']/following-sibling::div/span`; + }; - public OpenPaneFromCta() { - this.agHelper.GetNClick(this.locators._appSettings_cta); + public OpenAppSettings() { + this.agHelper.GetNClick(this.locators._appSettings); } public ClosePane() { @@ -39,7 +38,7 @@ export class AppSettings { } public OpenPaneAndChangeTheme(themeName: string) { - this.OpenPaneFromCta(); + this.OpenAppSettings(); this.GoToThemeSettings(); this.theme.ChangeTheme(themeName); this.ClosePane(); @@ -49,10 +48,70 @@ export class AppSettings { primaryColorIndex: number, backgroundColorIndex: number, ) { - this.OpenPaneFromCta(); + this.OpenAppSettings(); this.GoToThemeSettings(); this.theme.ChangeThemeColor(primaryColorIndex, "Primary"); this.theme.ChangeThemeColor(backgroundColorIndex, "Background"); this.ClosePane(); } + + public CheckUrl( + appName: string, + pageName: string, + customSlug?: string, + editMode = true, + ) { + cy.location("pathname").then((pathname) => { + if (customSlug && customSlug.length > 0) { + const pageId = pathname + .split("/")[2] + ?.split("-") + .pop(); + expect(pathname).to.be.equal( + `/app/${customSlug}-${pageId}${editMode ? "/edit" : ""}`.toLowerCase(), + ); + } else { + const pageId = pathname + .split("/")[3] + ?.split("-") + .pop(); + expect(pathname).to.be.equal( + `/app/${appName}/${pageName}-${pageId}${ + editMode ? "/edit" : "" + }`.toLowerCase(), + ); + } + }); + }; + + public AssertErrorMessage( + fieldId: string, + newValue: string, + errorMessage: string, + resetValue = true, + ) { + this.agHelper.GetText(fieldId, "val").then((currentValue) => { + if (newValue.length === 0) this.agHelper.ClearTextField(fieldId); + else + this.agHelper.RemoveCharsNType( + fieldId, + (currentValue as string).length, + newValue, + ); + this.agHelper.AssertText( + this.errorMessageSelector(fieldId), + "text", + errorMessage, + ); + if (resetValue) { + this.agHelper.RemoveCharsNType( + fieldId, + newValue.length, + currentValue as string, + ); + } + }); + } + + } diff --git a/app/client/cypress/support/Pages/AppSettings/GeneralSettings.ts b/app/client/cypress/support/Pages/AppSettings/GeneralSettings.ts index 92653276cc..6c46b860cd 100644 --- a/app/client/cypress/support/Pages/AppSettings/GeneralSettings.ts +++ b/app/client/cypress/support/Pages/AppSettings/GeneralSettings.ts @@ -1,45 +1,58 @@ import { ObjectsRegistry } from "../../Objects/Registry"; -import { checkUrl } from "./Utils"; export class GeneralSettings { private agHelper = ObjectsRegistry.AggregateHelper; + private appSettings = ObjectsRegistry.AppSettings; + private locators = { - appNameField: "#t--general-settings-app-name", - appNonSelectedIcon: ".t--icon-not-selected", - appIconSelector: "#t--general-settings-app-icon", + _appNameField: "#t--general-settings-app-name", + _appNonSelectedIcon: ".t--icon-not-selected", + _appIconSelector: "#t--general-settings-app-icon", }; - changeAppNameAndVerifyUrl( + UpdateAppNameAndVerifyUrl( reset: boolean, newAppName: string, + verifyAppNameAs?: string, pageName = "page1", ) { + const appNameToBeVerified = verifyAppNameAs ?? newAppName; this.agHelper - .InvokeVal(this.locators.appNameField) + .GetText(this.locators._appNameField, "val") .then((currentAppName) => { this.agHelper.RemoveCharsNType( - this.locators.appNameField, + this.locators._appNameField, (currentAppName as string).length, newAppName, ); this.agHelper.PressEnter(); + this.agHelper.Sleep(); this.agHelper.ValidateNetworkStatus("@updateApplication", 200); - checkUrl(newAppName, pageName); + this.appSettings.CheckUrl(appNameToBeVerified, pageName); if (reset) { this.agHelper.RemoveCharsNType( - this.locators.appNameField, + this.locators._appNameField, newAppName.length, currentAppName as string, ); this.agHelper.PressEnter(); this.agHelper.ValidateNetworkStatus("@updateApplication", 200); - checkUrl(currentAppName as string, pageName); + this.appSettings.CheckUrl(currentAppName as string, pageName); } }); } - changeAppIcon() { - this.agHelper.GetNClick(this.locators.appNonSelectedIcon, 0); + AssertAppErrorMessage(newAppName: string, errorMessage: string) { + this.appSettings.AssertErrorMessage( + this.locators._appNameField, + newAppName, + errorMessage, + true, + ); + } + + UpdateAppIcon() { + this.agHelper.GetNClick(this.locators._appNonSelectedIcon, 0); this.agHelper.ValidateNetworkStatus("@updateApplication", 200); } } diff --git a/app/client/cypress/support/Pages/AppSettings/PageSettings.ts b/app/client/cypress/support/Pages/AppSettings/PageSettings.ts index e09038fd9a..debe15f7f2 100644 --- a/app/client/cypress/support/Pages/AppSettings/PageSettings.ts +++ b/app/client/cypress/support/Pages/AppSettings/PageSettings.ts @@ -1,76 +1,145 @@ import { ObjectsRegistry } from "../../Objects/Registry"; -import { checkUrl } from "./Utils"; export class PageSettings { private agHelper = ObjectsRegistry.AggregateHelper; private homePage = ObjectsRegistry.HomePage; + private appSettings = ObjectsRegistry.AppSettings; + private locators = { - pageNameField: "#t--page-settings-name", - customSlugField: "#t--page-settings-custom-slug", - showPageNavSwitch: "#t--page-settings-show-nav-control", - setAsHomePageSwitch: "#t--page-settings-home-page-control", - homePageHeader: "#t--page-settings-default-page", + _pageNameField: "#t--page-settings-name", + _customSlugField: "#t--page-settings-custom-slug", + _showPageNavSwitch: "#t--page-settings-show-nav-control", + _setAsHomePageSwitch: "#t--page-settings-home-page-control", + _setHomePageToggle : ".bp3-control-indicator", + _homePageHeader: "#t--page-settings-default-page", }; - changePageNameAndVerifyUrl(newPageName: string) { + UpdatePageNameAndVerifyTextValue(newPageName: string, verifyPageNameAs: string) { + this.AssertPageValue( + this.locators._pageNameField, + newPageName, + verifyPageNameAs, + ); + } + + UpdateCustomSlugAndVerifyTextValue( + newCustomSlug: string, + verifyCustomSlugAs: string, + ) { + this.AssertPageValue( + this.locators._customSlugField, + newCustomSlug, + verifyCustomSlugAs, + ); + } + + public AssertPageValue( + locator: string, + newValue: string, + verifyValueAs: string, + ) { + this.agHelper.GetText(locator, "val").then((currentValue) => { + const currentValueLength = (currentValue as string).length; + if (currentValueLength === 0) this.agHelper.TypeText(locator, newValue); + else + this.agHelper.RemoveCharsNType(locator, currentValueLength, newValue); + + this.agHelper.GetText(locator, "val").then((fieldValue) => { + expect(fieldValue).to.equal(verifyValueAs); + if (currentValueLength === 0) this.agHelper.ClearTextField(locator); + else + this.agHelper.RemoveCharsNType( + locator, + (fieldValue as string).length, + currentValue as string, + ); + }); + }); + } + + UpdatePageNameAndVerifyUrl( + newPageName: string, + verifyPageNameAs?: string, + reset = true, + ) { + const pageNameToBeVerified = verifyPageNameAs ?? newPageName; this.agHelper - .InvokeVal(this.locators.pageNameField) + .GetText(this.locators._pageNameField, "val") .then((currentPageName) => { const currentPageNameLength = (currentPageName as string).length; this.homePage.GetAppName().then((appName) => { this.agHelper.RemoveCharsNType( - this.locators.pageNameField, + this.locators._pageNameField, currentPageNameLength, newPageName, ); this.agHelper.PressEnter(); this.agHelper.ValidateNetworkStatus("@updatePage", 200); - checkUrl(appName as string, newPageName); + this.appSettings.CheckUrl(appName as string, pageNameToBeVerified); + if (reset) { + this.agHelper.RemoveCharsNType( + this.locators._pageNameField, + newPageName.length, + currentPageName as string, + ); + this.agHelper.PressEnter(); + this.agHelper.ValidateNetworkStatus("@updatePage", 200); + this.appSettings.CheckUrl(appName as string, currentPageName as string); + } }); }); } - changeCustomSlugAndVerifyUrl(customSlug: string) { + UpdateCustomSlugAndVerifyUrl(customSlug: string) { this.agHelper - .InvokeVal(this.locators.customSlugField) + .GetText(this.locators._customSlugField, "val") .then((currentCustomSlug) => { const currentCustomSlugLength = (currentCustomSlug as string).length; this.homePage.GetAppName().then((appName) => { if (currentCustomSlugLength === 0) { - this.agHelper.TypeText(this.locators.customSlugField, customSlug); + this.agHelper.TypeText(this.locators._customSlugField, customSlug); } else { this.agHelper.RemoveCharsNType( - this.locators.customSlugField, + this.locators._customSlugField, currentCustomSlugLength, customSlug, ); } this.agHelper.PressEnter(); this.agHelper.ValidateNetworkStatus("@updatePage", 200); - checkUrl(appName as string, "", customSlug); + this.appSettings.CheckUrl(appName as string, "", customSlug); }); }); } - changePageNavigationSetting() { + AssertPageErrorMessage(newPageName: string, errorMessage: string) { + this.appSettings.AssertErrorMessage( + this.locators._pageNameField, + newPageName, + errorMessage, + true, + ); + } + + TogglePageNavigation() { this.agHelper.GetSiblingNClick( - this.locators.showPageNavSwitch, - ".bp3-control-indicator", + this.locators._showPageNavSwitch, + this.locators._setHomePageToggle, ); this.agHelper.ValidateNetworkStatus("@updatePage", 200); } - setAsHomePage() { + ToggleHomePage() { this.agHelper.GetSiblingNClick( - this.locators.setAsHomePageSwitch, - ".bp3-control-indicator", + this.locators._setAsHomePageSwitch, + this.locators._setHomePageToggle, ); this.agHelper.ValidateNetworkStatus("@makePageDefault", 200); } - isHomePage(pageName: string) { - this.agHelper.AssertText(this.locators.homePageHeader, "text", pageName); + AssertHomePage(pageName: string) { + this.agHelper.AssertText(this.locators._homePageHeader, "text", pageName); } } diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts index 2afcaa6091..4308f6daa3 100644 --- a/app/client/src/ce/constants/messages.ts +++ b/app/client/src/ce/constants/messages.ts @@ -1391,12 +1391,14 @@ export const PAGE_SETTINGS_PAGE_URL_VERSION_UPDATE_3 = () => "your app URL to new readable format to change this"; export const PAGE_SETTINGS_SHOW_PAGE_NAV = () => "Show page navigation"; export const PAGE_SETTINGS_SHOW_PAGE_NAV_TOOLTIP = () => - "Hide or show the appsmith navbar containing the app name and page switcher"; + "Show or hide the page in the appsmith navbar in view mode"; export const PAGE_SETTINGS_SET_AS_HOMEPAGE = () => "Set as home page"; export const PAGE_SETTINGS_SET_AS_HOMEPAGE_TOOLTIP = () => "This is the current home page, you can change this by setting another page as the home page"; export const PAGE_SETTINGS_SET_AS_HOMEPAGE_TOOLTIP_NON_HOME_PAGE = () => "Set this page as your home page. This will override your previously set home page."; +export const PAGE_SETTINGS_ACTION_NAME_CONFLICT_ERROR = (name: string) => + `${name} is already being used.`; // Alert options and labels for showMessage types export const ALERT_STYLE_OPTIONS = [ diff --git a/app/client/src/entities/URLRedirect/URLAssembly.ts b/app/client/src/entities/URLRedirect/URLAssembly.ts index 50d81e181f..6950294984 100644 --- a/app/client/src/entities/URLRedirect/URLAssembly.ts +++ b/app/client/src/entities/URLRedirect/URLAssembly.ts @@ -169,7 +169,7 @@ export class URLBuilder { baseURLRegistry[URL_TYPE.CUSTOM_SLUG][APP_MODE.PUBLISHED]; return generatePath(urlPattern, { pageId, - customSlug: `${customSlug.replaceAll(" ", "-")}-`, + customSlug: `${customSlug}-`, }).toLowerCase(); } @@ -182,7 +182,7 @@ export class URLBuilder { const formattedParams = this.getFormattedParams(pageId); - formattedParams.pageSlug = `${pageName.replaceAll(" ", "-")}-`; + formattedParams.pageSlug = `${pageName}-`; return generatePath(urlPattern, formattedParams).toLowerCase(); } diff --git a/app/client/src/pages/Editor/AppSettingsPane/AppSettings/GeneralSettings.tsx b/app/client/src/pages/Editor/AppSettingsPane/AppSettings/GeneralSettings.tsx index 7a03176afd..f31903563d 100644 --- a/app/client/src/pages/Editor/AppSettingsPane/AppSettings/GeneralSettings.tsx +++ b/app/client/src/pages/Editor/AppSettingsPane/AppSettings/GeneralSettings.tsx @@ -4,7 +4,6 @@ import { GENERAL_SETTINGS_APP_ICON_LABEL, GENERAL_SETTINGS_APP_NAME_LABEL, GENERAL_SETTINGS_NAME_EMPTY_MESSAGE, - GENERAL_SETTINGS_NAME_SPECIAL_CHARACTER_ERROR, } from "ce/constants/messages"; import classNames from "classnames"; import { @@ -24,9 +23,7 @@ import { } from "selectors/applicationSelectors"; import { getCurrentApplicationId } from "selectors/editorSelectors"; import styled from "styled-components"; -import { checkRegex } from "utils/validation/CheckRegex"; import TextLoaderIcon from "../Components/TextLoaderIcon"; -import { appNameRegex } from "../Utils"; const IconSelectorWrapper = styled.div` position: relative; @@ -60,8 +57,8 @@ function GeneralSettings() { ); useEffect(() => { - setApplicationName(application?.name); - }, [application?.name]); + !isSavingAppName && setApplicationName(application?.name); + }, [application, application?.name, isSavingAppName]); const updateAppSettings = useCallback( debounce((icon?: AppIconName) => { @@ -106,13 +103,20 @@ function GeneralSettings() { }} placeholder="App name" type="input" - validator={checkRegex( - appNameRegex, - GENERAL_SETTINGS_NAME_SPECIAL_CHARACTER_ERROR(), - true, - setIsAppNameValid, - GENERAL_SETTINGS_NAME_EMPTY_MESSAGE(), - )} + validator={(value: string) => { + let result: { isValid: boolean; message?: string } = { + isValid: true, + }; + if (!value || value.trim().length === 0) { + setIsAppNameValid(false); + result = { + isValid: false, + message: GENERAL_SETTINGS_NAME_EMPTY_MESSAGE(), + }; + } + setIsAppNameValid(result.isValid); + return result; + }} value={applicationName} /> diff --git a/app/client/src/pages/Editor/AppSettingsPane/AppSettings/PageSettings.tsx b/app/client/src/pages/Editor/AppSettingsPane/AppSettings/PageSettings.tsx index 47fed5b0a6..d828ad59c6 100644 --- a/app/client/src/pages/Editor/AppSettingsPane/AppSettings/PageSettings.tsx +++ b/app/client/src/pages/Editor/AppSettingsPane/AppSettings/PageSettings.tsx @@ -13,7 +13,7 @@ import { PAGE_SETTINGS_NAME_EMPTY_MESSAGE, PAGE_SETTINGS_SHOW_PAGE_NAV_TOOLTIP, PAGE_SETTINGS_SET_AS_HOMEPAGE_TOOLTIP_NON_HOME_PAGE, - PAGE_SETTINGS_NAME_SPECIAL_CHARACTER_ERROR as PAGE_SETTINGS_SPECIAL_CHARACTER_ERROR, + PAGE_SETTINGS_ACTION_NAME_CONFLICT_ERROR, } from "ce/constants/messages"; import { Page } from "ce/constants/ReduxActionConstants"; import { hasManagePagePermission } from "@appsmith/utils/permissionHelpers"; @@ -24,7 +24,7 @@ import AdsSwitch from "design-system/build/Switch"; import ManualUpgrades from "pages/Editor/BottomBar/ManualUpgrades"; import PropertyHelpLabel from "pages/Editor/PropertyPane/PropertyHelpLabel"; import React, { useCallback, useEffect, useState } from "react"; -import { useDispatch, useSelector } from "react-redux"; +import { shallowEqual, useDispatch, useSelector } from "react-redux"; import { getCurrentApplicationId, selectApplicationVersion, @@ -32,9 +32,11 @@ import { import { getUpdatingEntity } from "selectors/explorerSelector"; import { getPageLoadingState } from "selectors/pageListSelectors"; import styled from "styled-components"; -import { checkRegex } from "utils/validation/CheckRegex"; import TextLoaderIcon from "../Components/TextLoaderIcon"; -import { getUrlPreview, specialCharacterCheckRegex } from "../Utils"; +import { getUrlPreview } from "../Utils"; +import { AppState } from "ce/reducers"; +import { getUsedActionNames } from "selectors/actionSelectors"; +import { isNameValid, resolveAsSpaceChar } from "utils/helpers"; const SwitchWrapper = styled.div` &&&&&&& @@ -79,6 +81,8 @@ const UrlPreviewScroll = styled.div` } `; +const specialCharacterCheckRegex = /^[A-Za-z0-9\s\-]+$/g; + function PageSettings(props: { page: Page }) { const dispatch = useDispatch(); const page = props.page; @@ -100,7 +104,6 @@ function PageSettings(props: { page: Page }) { const [isPageNameValid, setIsPageNameValid] = useState(true); const [customSlug, setCustomSlug] = useState(page.customSlug); - const [isCustomSlugValid, setIsCustomSlugValid] = useState(true); const [isCustomSlugSaving, setIsCustomSlugSaving] = useState(false); const [isShown, setIsShown] = useState(!!!page.isHidden); @@ -117,6 +120,16 @@ function PageSettings(props: { page: Page }) { page.customSlug, ])(page.pageId, pageName, page.pageName, customSlug, page.customSlug); + const conflictingNames = useSelector( + (state: AppState) => getUsedActionNames(state, ""), + shallowEqual, + ); + + const hasActionNameConflict = useCallback( + (name: string) => !isNameValid(name, conflictingNames), + [conflictingNames], + ); + useEffect(() => { setPageName(page.pageName); setCustomSlug(page.customSlug || ""); @@ -151,15 +164,14 @@ function PageSettings(props: { page: Page }) { }, [page.pageId, page.pageName, pageName, isPageNameValid]); const saveCustomSlug = useCallback(() => { - if (!canManagePages || !isCustomSlugValid || page.customSlug === customSlug) - return; + if (!canManagePages || page.customSlug === customSlug) return; const payload: UpdatePageRequest = { id: page.pageId, customSlug: customSlug || "", }; setIsCustomSlugSaving(true); dispatch(updatePage(payload)); - }, [page.pageId, page.customSlug, customSlug, isCustomSlugValid]); + }, [page.pageId, page.customSlug, customSlug]); const saveIsShown = useCallback( (isShown: boolean) => { @@ -190,7 +202,9 @@ function PageSettings(props: { page: Page }) { fill id="t--page-settings-name" onBlur={savePageName} - onChange={setPageName} + onChange={(value: string) => + setPageName(resolveAsSpaceChar(value, 30)) + } onKeyPress={(ev: React.KeyboardEvent) => { if (ev.key === "Enter") { savePageName(); @@ -198,13 +212,27 @@ function PageSettings(props: { page: Page }) { }} placeholder="Page name" type="input" - validator={checkRegex( - specialCharacterCheckRegex, - PAGE_SETTINGS_SPECIAL_CHARACTER_ERROR(), - true, - setIsPageNameValid, - PAGE_SETTINGS_NAME_EMPTY_MESSAGE(), - )} + validator={(value: string) => { + let result: { isValid: boolean; message?: string } = { + isValid: true, + }; + if (!value || value.trim().length === 0) { + result = { + isValid: false, + message: PAGE_SETTINGS_NAME_EMPTY_MESSAGE(), + }; + } else if ( + value !== page.pageName && + hasActionNameConflict(value) + ) { + result = { + isValid: false, + message: PAGE_SETTINGS_ACTION_NAME_CONFLICT_ERROR(value), + }; + } + setIsPageNameValid(result.isValid); + return result; + }} value={pageName} /> @@ -230,7 +258,6 @@ function PageSettings(props: { page: Page }) { className={classNames({ "py-1 relative": true, "pb-2": appNeedsUpdate, - "pb-6": !appNeedsUpdate && !isCustomSlugValid, })} > {isCustomSlugSaving && } @@ -240,7 +267,11 @@ function PageSettings(props: { page: Page }) { fill id="t--page-settings-custom-slug" onBlur={saveCustomSlug} - onChange={setCustomSlug} + onChange={(value: string) => + value.length > 0 + ? specialCharacterCheckRegex.test(value) && setCustomSlug(value) + : setCustomSlug(value) + } onKeyPress={(ev: React.KeyboardEvent) => { if (ev.key === "Enter") { saveCustomSlug(); @@ -249,12 +280,6 @@ function PageSettings(props: { page: Page }) { placeholder="Page URL" readOnly={appNeedsUpdate} type="input" - validator={checkRegex( - specialCharacterCheckRegex, - PAGE_SETTINGS_SPECIAL_CHARACTER_ERROR(), - false, - setIsCustomSlugValid, - )} value={customSlug} /> diff --git a/app/client/src/pages/Editor/AppSettingsPane/Utils.ts b/app/client/src/pages/Editor/AppSettingsPane/Utils.ts index 2bda24f6d6..9a00aa9bc1 100644 --- a/app/client/src/pages/Editor/AppSettingsPane/Utils.ts +++ b/app/client/src/pages/Editor/AppSettingsPane/Utils.ts @@ -2,9 +2,6 @@ import { APP_MODE } from "entities/App"; import urlBuilder from "entities/URLRedirect/URLAssembly"; import { splitPathPreview } from "utils/helpers"; -export const specialCharacterCheckRegex = /^[A-Za-z0-9\s\-]+$/; -export const appNameRegex = /^[A-Za-z0-9\s\-()]+$/; - export const getUrlPreview = ( pageId: string, newPageName: string, @@ -14,6 +11,13 @@ export const getUrlPreview = ( ) => { let relativePath: string; + newPageName = filterAccentedAndSpecialCharacters(newPageName); + currentPageName = filterAccentedAndSpecialCharacters(currentPageName); + newCustomSlug && + (newCustomSlug = filterAccentedAndSpecialCharacters(newCustomSlug)); + currentCustomSlug && + (currentCustomSlug = filterAccentedAndSpecialCharacters(currentCustomSlug)); + // when page name is changed // and when custom slug doesn't exist if (!newCustomSlug && newPageName !== currentPageName) { @@ -41,3 +45,9 @@ export const getUrlPreview = ( splitRelativePath: splitPathPreview(relativePath, newCustomSlug), }; }; + +const filterAccentedAndSpecialCharacters = (value: string) => { + return decodeURI(value) + .replaceAll(" ", "-") + .replaceAll(/[^A-Za-z0-9-]/g, ""); +}; diff --git a/app/client/src/pages/Editor/CanvasPropertyPane/index.tsx b/app/client/src/pages/Editor/CanvasPropertyPane/index.tsx index dea09461d7..3ad79ef85d 100644 --- a/app/client/src/pages/Editor/CanvasPropertyPane/index.tsx +++ b/app/client/src/pages/Editor/CanvasPropertyPane/index.tsx @@ -39,7 +39,7 @@ export function CanvasPropertyPane() { position={PopoverPosition.BOTTOM} >