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 <aishwarya@appsmith.com>
This commit is contained in:
Anand Srinivasan 2022-12-12 10:21:14 +05:30 committed by GitHub
parent f67f8571ec
commit 284571803b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 495 additions and 246 deletions

View File

@ -1,17 +1,9 @@
import { ObjectsRegistry } from "../../../../support/Objects/Registry"; import * as _objects from "../../../../support/Objects/ObjectsCore"
const {
AggregateHelper: agHelper,
DeployMode: deployMode,
EntityExplorer: ee,
JSEditor: jsEditor,
PropertyPane: propPane,
} = ObjectsRegistry;
describe("clearStore Action test", () => { describe("clearStore Action test", () => {
before(() => { before(() => {
ee.DragDropWidgetNVerify("buttonwidget", 100, 100); _objects.ee.DragDropWidgetNVerify("buttonwidget", 100, 100);
ee.NavigateToSwitcher("explorer"); _objects.ee.NavigateToSwitcher("explorer");
}); });
it("1. Feature 11639 : Clear all store value", function() { it("1. Feature 11639 : Clear all store value", function() {
@ -24,7 +16,7 @@ describe("clearStore Action test", () => {
storeValue('val3', 'value 3'), storeValue('val3', 'value 3'),
]; ];
await Promise.all(values); await Promise.all(values);
await showAlert(JSON.stringify(appsmith.store)); await showAlert(JSON.stringify(appsmith.store));
}, },
clearStore: async () => { clearStore: async () => {
await clearStore(); await clearStore();
@ -33,7 +25,7 @@ describe("clearStore Action test", () => {
}`; }`;
// Create js object // Create js object
jsEditor.CreateJSObject(JS_OBJECT_BODY, { _objects.jsEditor.CreateJSObject(JS_OBJECT_BODY, {
paste: true, paste: true,
completeReplace: true, completeReplace: true,
toRun: false, toRun: false,
@ -41,40 +33,40 @@ describe("clearStore Action test", () => {
shouldCreateNewJSObj: true, shouldCreateNewJSObj: true,
}); });
ee.SelectEntityByName("Button1", "Widgets"); _objects.ee.SelectEntityByName("Button1", "Widgets");
propPane.UpdatePropertyFieldValue("Label", ""); _objects.propPane.UpdatePropertyFieldValue("Label", "");
propPane.TypeTextIntoField("Label", "StoreValue"); _objects.propPane.TypeTextIntoField("Label", "StoreValue");
cy.get("@jsObjName").then((jsObj: any) => { cy.get("@jsObjName").then((jsObj: any) => {
propPane.SelectJSFunctionToExecute( _objects.propPane.SelectJSFunctionToExecute(
"onClick", "onClick",
jsObj as string, jsObj as string,
"storeValue", "storeValue",
); );
}); });
ee.DragDropWidgetNVerify("buttonwidget", 100, 200); _objects.ee.DragDropWidgetNVerify("buttonwidget", 100, 200);
ee.SelectEntityByName("Button2", "Widgets"); _objects.ee.SelectEntityByName("Button2", "Widgets");
propPane.UpdatePropertyFieldValue("Label", ""); _objects.propPane.UpdatePropertyFieldValue("Label", "");
propPane.TypeTextIntoField("Label", "ClearStore"); _objects.propPane.TypeTextIntoField("Label", "ClearStore");
cy.get("@jsObjName").then((jsObj: any) => { cy.get("@jsObjName").then((jsObj: any) => {
propPane.SelectJSFunctionToExecute( _objects.propPane.SelectJSFunctionToExecute(
"onClick", "onClick",
jsObj as string, jsObj as string,
"clearStore", "clearStore",
); );
}); });
deployMode.DeployApp(); _objects.deployMode.DeployApp();
agHelper.ClickButton("StoreValue"); _objects.agHelper.ClickButton("StoreValue");
agHelper.AssertContains( _objects.agHelper.AssertContains(
JSON.stringify({ JSON.stringify({
val1: "value 1", val1: "value 1",
val2: "value 2", val2: "value 2",
val3: "value 3", val3: "value 3",
}), }),
); );
agHelper.ClickButton("ClearStore"); _objects.agHelper.ClickButton("ClearStore");
agHelper.AssertContains(JSON.stringify({})); _objects.agHelper.AssertContains(JSON.stringify({}));
deployMode.NavigateBacktoEditor(); _objects.deployMode.NavigateBacktoEditor();
}); });
}); });

View File

@ -71,7 +71,7 @@ describe("Git import flow ", function() {
cy.connectToGitRepo(repoName); 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 ", () => { 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() .next()
.click(); .click();
cy.importAppFromGit(repoName); cy.importAppFromGit(repoName);
cy.wait(100); cy.wait(5000);
cy.get(reconnectDatasourceModal.Modal).should("be.visible"); cy.get(reconnectDatasourceModal.Modal).should("be.visible");
cy.ReconnectDatasource("TEDPostgres"); cy.ReconnectDatasource("TEDPostgres");
cy.wait(500); cy.wait(500);
@ -142,7 +142,8 @@ describe("Git import flow ", function() {
// verify js object binded to input widget // verify js object binded to input widget
cy.xpath("//input[@value='Success']").should("be.visible"); 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", () => { it.skip("4. Create a new branch, clone page and validate data on that branch in view and edit mode", () => {
cy.createGitBranch(newBranch); cy.createGitBranch(newBranch);
cy.get(".tbody") cy.get(".tbody")
@ -213,6 +214,7 @@ describe("Git import flow ", function() {
cy.wait(2000); cy.wait(2000);
}); });
// skipping this due to open bug #18776
it.skip("5. Switch to master and verify data in edit and view mode", () => { it.skip("5. Switch to master and verify data in edit and view mode", () => {
cy.switchGitBranch("master"); cy.switchGitBranch("master");
cy.wait(2000); cy.wait(2000);
@ -236,6 +238,7 @@ describe("Git import flow ", function() {
cy.wait(2000); 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", () => { it.skip("6. Add widget to master, merge then checkout to child branch and verify data", () => {
cy.get(explorer.widgetSwitchId).click(); cy.get(explorer.widgetSwitchId).click();
cy.wait(2000); // wait for transition cy.wait(2000); // wait for transition

View File

@ -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() { 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(); appSettings.GoToThemeSettings();
// apply theme on master branch and deploy // apply theme on master branch and deploy
cy.get(commonlocators.changeThemeBtn).click({ force: true }); cy.get(commonlocators.changeThemeBtn).click({ force: true });
@ -65,7 +65,7 @@ describe("Git with Theming:", function() {
cy.wait(1000); cy.wait(1000);
cy.get("body").click(300, 300); cy.get("body").click(300, 300);
// change theme on tempBranch // change theme on tempBranch
appSettings.OpenPaneFromCta(); appSettings.OpenAppSettings();
appSettings.GoToThemeSettings(); appSettings.GoToThemeSettings();
cy.get(commonlocators.changeThemeBtn).click({ force: true }); cy.get(commonlocators.changeThemeBtn).click({ force: true });

View File

@ -1,25 +1,43 @@
import { ObjectsRegistry } from "../../../../support/Objects/Registry"; import * as _ from "../../../../support/Objects/ObjectsCore";
import { checkUrl } from "../../../../support/Pages/AppSettings/Utils";
const appSettings = ObjectsRegistry.AppSettings,
deployMode = ObjectsRegistry.DeployMode,
homePage = ObjectsRegistry.HomePage;
let guid: string;
describe("General Settings", () => { describe("General Settings", () => {
it("App name change updates URL", () => { before(() => {
appSettings.OpenPaneFromCta(); _.agHelper.GenerateUUID();
appSettings.GoToGeneralSettings(); cy.get("@guid").then((uid: any) => {
appSettings.general.changeAppNameAndVerifyUrl(true, "myapp"); guid = uid;
homePage.GetAppName().then((appName) => {
deployMode.DeployApp();
checkUrl(appName as string, "Page1", undefined, false);
deployMode.NavigateBacktoEditor();
}); });
}); });
it("Handles app icon change", () => { it("1. App name change updates URL", () => {
appSettings.OpenPaneFromCta(); _.appSettings.OpenAppSettings();
appSettings.GoToGeneralSettings(); _.appSettings.GoToGeneralSettings();
appSettings.general.changeAppIcon(); _.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();
}); });
}); });

View File

@ -1,55 +1,101 @@
import { ObjectsRegistry } from "../../../../support/Objects/Registry"; import * as _ from "../../../../support/Objects/ObjectsCore";
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;
describe("Page Settings", () => { describe("Page Settings", () => {
it("Page name change updates URL", () => { it("1. Page name change updates URL", () => {
appSettings.OpenPaneFromCta(); _.appSettings.OpenAppSettings();
appSettings.GoToPageSettings("Page1"); _.appSettings.GoToPageSettings("Page1");
appSettings.page.changePageNameAndVerifyUrl("Page2"); _.pageSettings.UpdatePageNameAndVerifyUrl("Page2", undefined, false);
homePage.GetAppName().then((appName) => { _.homePage.GetAppName().then((appName) => {
deployMode.DeployApp(); _.deployMode.DeployApp();
checkUrl(appName as string, "Page2", undefined, false); _.appSettings.CheckUrl(appName as string, "Page2", undefined, false);
deployMode.NavigateBacktoEditor(); _.deployMode.NavigateBacktoEditor();
}); });
cy.wait(2000); _.agHelper.Sleep();
}); });
it("Custom slug change updates URL", () => { it("2. Custom slug change updates URL", () => {
appSettings.OpenPaneFromCta(); _.appSettings.OpenAppSettings();
appSettings.GoToPageSettings("Page2"); _.appSettings.GoToPageSettings("Page2");
appSettings.page.changeCustomSlugAndVerifyUrl("custom"); _.pageSettings.UpdateCustomSlugAndVerifyUrl("custom");
homePage.GetAppName().then((appName) => { _.homePage.GetAppName().then((appName) => {
deployMode.DeployApp(); _.deployMode.DeployApp();
checkUrl(appName as string, "Page2", "custom", false); _.appSettings.CheckUrl(appName as string, "Page2", "custom", false);
deployMode.NavigateBacktoEditor(); _.deployMode.NavigateBacktoEditor();
}); });
cy.wait(2000); _.agHelper.Sleep();
}); });
it("Check default page is updated", () => { it("3. Check SetAsHome page setting", () => {
ee.AddNewPage(); _.ee.AddNewPage();
appSettings.OpenPaneFromCta(); _.appSettings.OpenAppSettings();
appSettings.GoToPageSettings("Page3"); _.appSettings.GoToPageSettings("Page3");
appSettings.page.setAsHomePage(); _.pageSettings.ToggleHomePage();
appSettings.page.isHomePage("Page3"); _.pageSettings.AssertHomePage("Page3");
}); });
it("Check page navigation is updated", () => { it("4. Check SetPageNavigation settings", () => {
agHelper.GetNClick(commonLocators._previewModeToggle); _.agHelper.GetNClick(_.locators._previewModeToggle);
agHelper.AssertElementExist(commonLocators._deployedPage); _.agHelper.AssertElementExist(_.locators._deployedPage);
agHelper.GetNClick(commonLocators._editModeToggle); _.agHelper.GetNClick(_.locators._editModeToggle);
appSettings.OpenPaneFromCta(); _.appSettings.OpenAppSettings();
appSettings.GoToPageSettings("Page2"); _.appSettings.GoToPageSettings("Page2");
appSettings.page.changePageNavigationSetting(); _.pageSettings.TogglePageNavigation();
agHelper.GetNClick(commonLocators._previewModeToggle); _.agHelper.GetNClick(_.locators._previewModeToggle);
agHelper.AssertElementAbsence(commonLocators._deployedPage); _.agHelper.AssertElementAbsence(_.locators._deployedPage);
agHelper.GetNClick(commonLocators._editModeToggle); _.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();
}); });
}); });

View File

@ -26,7 +26,7 @@ describe("App Theming funtionality", function() {
themesSection(sectionName, themeName) + "/following-sibling::button"; themesSection(sectionName, themeName) + "/following-sibling::button";
it("1. Checks if theme can be changed to one of the existing themes", function() { it("1. Checks if theme can be changed to one of the existing themes", function() {
appSettings.OpenPaneFromCta(); appSettings.OpenAppSettings();
appSettings.GoToThemeSettings(); appSettings.GoToThemeSettings();
cy.get(commonlocators.changeThemeBtn).click({ force: true }); cy.get(commonlocators.changeThemeBtn).click({ force: true });
@ -70,7 +70,7 @@ describe("App Theming funtionality", function() {
.first(0) .first(0)
.trigger("click", { force: true }); .trigger("click", { force: true });
appSettings.OpenPaneFromCta(); appSettings.OpenAppSettings();
appSettings.GoToThemeSettings(); appSettings.GoToThemeSettings();
//Click the back button //Commenting below since expanded by default //Click the back button //Commenting below since expanded by default
@ -219,7 +219,7 @@ describe("App Theming funtionality", function() {
.first(0) .first(0)
.trigger("click", { force: true }); .trigger("click", { force: true });
appSettings.OpenPaneFromCta(); appSettings.OpenAppSettings();
appSettings.GoToThemeSettings(); appSettings.GoToThemeSettings();
//#region Change Font & verify widgets: //#region Change Font & verify widgets:
// cy.contains("Font") // cy.contains("Font")
@ -1009,7 +1009,7 @@ describe("App Theming funtionality", function() {
.first(0) .first(0)
.trigger("click", { force: true }); .trigger("click", { force: true });
appSettings.OpenPaneFromCta(); appSettings.OpenAppSettings();
appSettings.GoToThemeSettings(); appSettings.GoToThemeSettings();
cy.get(commonlocators.changeThemeBtn).click({ force: true }); cy.get(commonlocators.changeThemeBtn).click({ force: true });

View File

@ -24,7 +24,7 @@ describe("Theme validation usecases", function() {
// click on canvas to see the theming pane // click on canvas to see the theming pane
cy.get("#canvas-selection-0").click({ force: true }); cy.get("#canvas-selection-0").click({ force: true });
appSettings.OpenPaneFromCta(); appSettings.OpenAppSettings();
appSettings.GoToThemeSettings(); appSettings.GoToThemeSettings();
// reset theme // reset theme
cy.contains("Theme Properties") cy.contains("Theme Properties")

View File

@ -1,18 +1,14 @@
import { ObjectsRegistry } from "../../../../support/Objects/Registry"; 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 widgetsPage = require("../../../../locators/Widgets.json");
const explorer = require("../../../../locators/explorerlocators.json"); const explorer = require("../../../../locators/explorerlocators.json");
const commonlocators = require("../../../../locators/commonlocators.json"); const commonlocators = require("../../../../locators/commonlocators.json");
const formWidgetsPage = require("../../../../locators/FormWidgets.json"); const formWidgetsPage = require("../../../../locators/FormWidgets.json");
const publish = require("../../../../locators/publishWidgetspage.json");
const themelocator = require("../../../../locators/ThemeLocators.json"); const themelocator = require("../../../../locators/ThemeLocators.json");
const appSettings = ObjectsRegistry.AppSettings; const appSettings = ObjectsRegistry.AppSettings;
let themeBackgroudColor; let themeBackgroudColor;
let themeFont;
describe("Theme validation for default data", function() { 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() { 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.get(themelocator.canvas).click({ force: true });
cy.wait(2000); cy.wait(2000);
appSettings.OpenPaneFromCta(); appSettings.OpenAppSettings();
appSettings.GoToThemeSettings(); appSettings.GoToThemeSettings();
//Border validation //Border validation
//cy.contains("Border").click({ force: true }); //cy.contains("Border").click({ force: true });
@ -93,7 +89,7 @@ describe("Theme validation for default data", function() {
.should("have.css", "background-color") .should("have.css", "background-color")
.and("eq", "rgb(21, 128, 61)"); .and("eq", "rgb(21, 128, 61)");
cy.get("#canvas-selection-0").click({ force: true }); cy.get("#canvas-selection-0").click({ force: true });
appSettings.OpenPaneFromCta(); appSettings.OpenAppSettings();
appSettings.GoToThemeSettings(); appSettings.GoToThemeSettings();
//Change the Theme //Change the Theme
cy.get(commonlocators.changeThemeBtn).click({ force: true }); cy.get(commonlocators.changeThemeBtn).click({ force: true });

View File

@ -30,7 +30,7 @@ describe("Theme validation usecases", function() {
cy.get(themelocator.canvas).click({ force: true }); cy.get(themelocator.canvas).click({ force: true });
cy.wait(2000); cy.wait(2000);
appSettings.OpenPaneFromCta(); appSettings.OpenAppSettings();
appSettings.GoToThemeSettings(); appSettings.GoToThemeSettings();
//Border validation //Border validation
//cy.contains("Border").click({ force: true }); //cy.contains("Border").click({ force: true });
@ -185,7 +185,7 @@ describe("Theme validation usecases", function() {
.and("eq", "rgb(21, 128, 61)"); .and("eq", "rgb(21, 128, 61)");
cy.get("#canvas-selection-0").click({ force: true }); cy.get("#canvas-selection-0").click({ force: true });
appSettings.OpenPaneFromCta(); appSettings.OpenAppSettings();
appSettings.GoToThemeSettings(); appSettings.GoToThemeSettings();
//Change the Theme //Change the Theme
cy.get(commonlocators.changeThemeBtn).click({ force: true }); cy.get(commonlocators.changeThemeBtn).click({ force: true });
@ -247,7 +247,7 @@ describe("Theme validation usecases", function() {
cy.get("#canvas-selection-0").click({ force: true }); cy.get("#canvas-selection-0").click({ force: true });
appSettings.OpenPaneFromCta(); appSettings.OpenAppSettings();
appSettings.GoToThemeSettings(); appSettings.GoToThemeSettings();
//Change the Theme //Change the Theme
cy.get(commonlocators.changeThemeBtn).click({ force: true }); cy.get(commonlocators.changeThemeBtn).click({ force: true });

View File

@ -17,7 +17,7 @@ describe("Theme validation usecase for multi-select widget", function() {
cy.get(themelocator.canvas).click({ force: true }); cy.get(themelocator.canvas).click({ force: true });
cy.wait(2000); cy.wait(2000);
appSettings.OpenPaneFromCta(); appSettings.OpenAppSettings();
appSettings.GoToThemeSettings(); appSettings.GoToThemeSettings();
//Border validation //Border validation
//cy.contains("Border").click({ force: true }); //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() { it("3. Validate current theme feature", function() {
cy.get("#canvas-selection-0").click({ force: true }); cy.get("#canvas-selection-0").click({ force: true });
appSettings.OpenPaneFromCta(); appSettings.OpenAppSettings();
appSettings.GoToThemeSettings(); appSettings.GoToThemeSettings();
//Change the Theme //Change the Theme
cy.get(commonlocators.changeThemeBtn).click({ force: true }); cy.get(commonlocators.changeThemeBtn).click({ force: true });

View File

@ -15,7 +15,7 @@ describe("Checkbox Widget Functionality", function() {
// Click on canvas to get global theme settings // Click on canvas to get global theme settings
cy.get(commonlocators.canvas).click({ force: true }); cy.get(commonlocators.canvas).click({ force: true });
appSettings.OpenPaneFromCta(); appSettings.OpenAppSettings();
appSettings.GoToThemeSettings(); appSettings.GoToThemeSettings();
cy.get(commonlocators.themeAppBorderRadiusBtn) cy.get(commonlocators.themeAppBorderRadiusBtn)
.last() .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; // 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 }); cy.get(commonlocators.canvas).click({ force: true });
appSettings.OpenPaneFromCta(); appSettings.OpenAppSettings();
appSettings.GoToThemeSettings(); appSettings.GoToThemeSettings();
cy.get(commonlocators.themeAppBorderRadiusBtn) cy.get(commonlocators.themeAppBorderRadiusBtn)
.eq(1) .eq(1)
@ -85,7 +85,7 @@ describe("Checkbox Widget Functionality", function() {
// Change the global theme primary color // Change the global theme primary color
cy.get(commonlocators.canvas).click({ force: true }); cy.get(commonlocators.canvas).click({ force: true });
cy.wait(300); cy.wait(300);
appSettings.OpenPaneFromCta(); appSettings.OpenAppSettings();
appSettings.GoToThemeSettings(); appSettings.GoToThemeSettings();
cy.get(themeLocator.inputColor).click({ force: true }); 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(".uppy-Dashboard-close").click({ force: true });
cy.get(commonlocators.canvas).click({ force: true }); cy.get(commonlocators.canvas).click({ force: true });
cy.wait(300); cy.wait(300);
appSettings.OpenPaneFromCta(); appSettings.OpenAppSettings();
appSettings.GoToThemeSettings(); appSettings.GoToThemeSettings();
cy.get(themeLocator.fontsSelected).click({ force: true }); cy.get(themeLocator.fontsSelected).click({ force: true });

View File

@ -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;

View File

@ -13,6 +13,9 @@ import { GitSync } from "../Pages/GitSync";
import { FakerHelper } from "../Pages/FakerHelper"; import { FakerHelper } from "../Pages/FakerHelper";
import { DebuggerHelper } from "../Pages/DebuggerHelper"; import { DebuggerHelper } from "../Pages/DebuggerHelper";
import { AppSettings } from "../Pages/AppSettings/AppSettings"; 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 { export class ObjectsRegistry {
private static aggregateHelper__: AggregateHelper; private static aggregateHelper__: AggregateHelper;
@ -119,20 +122,44 @@ export class ObjectsRegistry {
return ObjectsRegistry.fakerHelper__; return ObjectsRegistry.fakerHelper__;
} }
private static DebuggerHelper__: DebuggerHelper; private static debuggerHelper__: DebuggerHelper;
static get DebuggerHelper(): DebuggerHelper { static get DebuggerHelper(): DebuggerHelper {
if (ObjectsRegistry.DebuggerHelper__ === undefined) { if (ObjectsRegistry.debuggerHelper__ === undefined) {
ObjectsRegistry.DebuggerHelper__ = new DebuggerHelper(); ObjectsRegistry.debuggerHelper__ = new DebuggerHelper();
} }
return ObjectsRegistry.DebuggerHelper__; return ObjectsRegistry.debuggerHelper__;
} }
private static AppSettings__: AppSettings; private static appSettings__: AppSettings;
static get AppSettings(): AppSettings { static get AppSettings(): AppSettings {
if (ObjectsRegistry.AppSettings__ === undefined) { if (ObjectsRegistry.appSettings__ === undefined) {
ObjectsRegistry.AppSettings__ = new AppSettings(); 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__;
} }
} }

View File

@ -526,10 +526,6 @@ export class AggregateHelper {
this.GetElement(selector).clear(); this.GetElement(selector).clear();
} }
public InvokeVal(selector: string) {
return cy.get(selector).invoke("val");
}
public TypeText( public TypeText(
selector: string, selector: string,
value: string, value: string,

View File

@ -1,12 +1,10 @@
import { ObjectsRegistry } from "../../Objects/Registry"; import { ObjectsRegistry } from "../../Objects/Registry";
import { ThemeSettings } from "./ThemeSettings";
import { GeneralSettings } from "./GeneralSettings";
import { PageSettings } from "./PageSettings";
export class AppSettings { export class AppSettings {
private agHelper = ObjectsRegistry.AggregateHelper; private agHelper = ObjectsRegistry.AggregateHelper;
private theme = ObjectsRegistry.ThemeSettings;
private locators = { private locators = {
_appSettings_cta: "#t--app-settings-cta", _appSettings: "#t--app-settings-cta",
_closeSettings: "#t--close-app-settings-pane", _closeSettings: "#t--close-app-settings-pane",
_themeSettingsHeader: "#t--theme-settings-header", _themeSettingsHeader: "#t--theme-settings-header",
_generalSettingsHeader: "#t--general-settings-header", _generalSettingsHeader: "#t--general-settings-header",
@ -14,12 +12,13 @@ export class AppSettings {
`#t--page-settings-${pageName}`, `#t--page-settings-${pageName}`,
}; };
public readonly theme = new ThemeSettings(); public errorMessageSelector = (fieldId: string) => {
public readonly general = new GeneralSettings(); fieldId = fieldId[0] === "#" ? fieldId.slice(1, fieldId.length) : fieldId;
public readonly page = new PageSettings(); return `//input[@id='${fieldId}']/following-sibling::div/span`;
};
public OpenPaneFromCta() { public OpenAppSettings() {
this.agHelper.GetNClick(this.locators._appSettings_cta); this.agHelper.GetNClick(this.locators._appSettings);
} }
public ClosePane() { public ClosePane() {
@ -39,7 +38,7 @@ export class AppSettings {
} }
public OpenPaneAndChangeTheme(themeName: string) { public OpenPaneAndChangeTheme(themeName: string) {
this.OpenPaneFromCta(); this.OpenAppSettings();
this.GoToThemeSettings(); this.GoToThemeSettings();
this.theme.ChangeTheme(themeName); this.theme.ChangeTheme(themeName);
this.ClosePane(); this.ClosePane();
@ -49,10 +48,70 @@ export class AppSettings {
primaryColorIndex: number, primaryColorIndex: number,
backgroundColorIndex: number, backgroundColorIndex: number,
) { ) {
this.OpenPaneFromCta(); this.OpenAppSettings();
this.GoToThemeSettings(); this.GoToThemeSettings();
this.theme.ChangeThemeColor(primaryColorIndex, "Primary"); this.theme.ChangeThemeColor(primaryColorIndex, "Primary");
this.theme.ChangeThemeColor(backgroundColorIndex, "Background"); this.theme.ChangeThemeColor(backgroundColorIndex, "Background");
this.ClosePane(); 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,
);
}
});
}
} }

View File

@ -1,45 +1,58 @@
import { ObjectsRegistry } from "../../Objects/Registry"; import { ObjectsRegistry } from "../../Objects/Registry";
import { checkUrl } from "./Utils";
export class GeneralSettings { export class GeneralSettings {
private agHelper = ObjectsRegistry.AggregateHelper; private agHelper = ObjectsRegistry.AggregateHelper;
private appSettings = ObjectsRegistry.AppSettings;
private locators = { private locators = {
appNameField: "#t--general-settings-app-name", _appNameField: "#t--general-settings-app-name",
appNonSelectedIcon: ".t--icon-not-selected", _appNonSelectedIcon: ".t--icon-not-selected",
appIconSelector: "#t--general-settings-app-icon", _appIconSelector: "#t--general-settings-app-icon",
}; };
changeAppNameAndVerifyUrl( UpdateAppNameAndVerifyUrl(
reset: boolean, reset: boolean,
newAppName: string, newAppName: string,
verifyAppNameAs?: string,
pageName = "page1", pageName = "page1",
) { ) {
const appNameToBeVerified = verifyAppNameAs ?? newAppName;
this.agHelper this.agHelper
.InvokeVal(this.locators.appNameField) .GetText(this.locators._appNameField, "val")
.then((currentAppName) => { .then((currentAppName) => {
this.agHelper.RemoveCharsNType( this.agHelper.RemoveCharsNType(
this.locators.appNameField, this.locators._appNameField,
(currentAppName as string).length, (currentAppName as string).length,
newAppName, newAppName,
); );
this.agHelper.PressEnter(); this.agHelper.PressEnter();
this.agHelper.Sleep();
this.agHelper.ValidateNetworkStatus("@updateApplication", 200); this.agHelper.ValidateNetworkStatus("@updateApplication", 200);
checkUrl(newAppName, pageName); this.appSettings.CheckUrl(appNameToBeVerified, pageName);
if (reset) { if (reset) {
this.agHelper.RemoveCharsNType( this.agHelper.RemoveCharsNType(
this.locators.appNameField, this.locators._appNameField,
newAppName.length, newAppName.length,
currentAppName as string, currentAppName as string,
); );
this.agHelper.PressEnter(); this.agHelper.PressEnter();
this.agHelper.ValidateNetworkStatus("@updateApplication", 200); this.agHelper.ValidateNetworkStatus("@updateApplication", 200);
checkUrl(currentAppName as string, pageName); this.appSettings.CheckUrl(currentAppName as string, pageName);
} }
}); });
} }
changeAppIcon() { AssertAppErrorMessage(newAppName: string, errorMessage: string) {
this.agHelper.GetNClick(this.locators.appNonSelectedIcon, 0); this.appSettings.AssertErrorMessage(
this.locators._appNameField,
newAppName,
errorMessage,
true,
);
}
UpdateAppIcon() {
this.agHelper.GetNClick(this.locators._appNonSelectedIcon, 0);
this.agHelper.ValidateNetworkStatus("@updateApplication", 200); this.agHelper.ValidateNetworkStatus("@updateApplication", 200);
} }
} }

View File

@ -1,76 +1,145 @@
import { ObjectsRegistry } from "../../Objects/Registry"; import { ObjectsRegistry } from "../../Objects/Registry";
import { checkUrl } from "./Utils";
export class PageSettings { export class PageSettings {
private agHelper = ObjectsRegistry.AggregateHelper; private agHelper = ObjectsRegistry.AggregateHelper;
private homePage = ObjectsRegistry.HomePage; private homePage = ObjectsRegistry.HomePage;
private appSettings = ObjectsRegistry.AppSettings;
private locators = { private locators = {
pageNameField: "#t--page-settings-name", _pageNameField: "#t--page-settings-name",
customSlugField: "#t--page-settings-custom-slug", _customSlugField: "#t--page-settings-custom-slug",
showPageNavSwitch: "#t--page-settings-show-nav-control", _showPageNavSwitch: "#t--page-settings-show-nav-control",
setAsHomePageSwitch: "#t--page-settings-home-page-control", _setAsHomePageSwitch: "#t--page-settings-home-page-control",
homePageHeader: "#t--page-settings-default-page", _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 this.agHelper
.InvokeVal(this.locators.pageNameField) .GetText(this.locators._pageNameField, "val")
.then((currentPageName) => { .then((currentPageName) => {
const currentPageNameLength = (currentPageName as string).length; const currentPageNameLength = (currentPageName as string).length;
this.homePage.GetAppName().then((appName) => { this.homePage.GetAppName().then((appName) => {
this.agHelper.RemoveCharsNType( this.agHelper.RemoveCharsNType(
this.locators.pageNameField, this.locators._pageNameField,
currentPageNameLength, currentPageNameLength,
newPageName, newPageName,
); );
this.agHelper.PressEnter(); this.agHelper.PressEnter();
this.agHelper.ValidateNetworkStatus("@updatePage", 200); 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 this.agHelper
.InvokeVal(this.locators.customSlugField) .GetText(this.locators._customSlugField, "val")
.then((currentCustomSlug) => { .then((currentCustomSlug) => {
const currentCustomSlugLength = (currentCustomSlug as string).length; const currentCustomSlugLength = (currentCustomSlug as string).length;
this.homePage.GetAppName().then((appName) => { this.homePage.GetAppName().then((appName) => {
if (currentCustomSlugLength === 0) { if (currentCustomSlugLength === 0) {
this.agHelper.TypeText(this.locators.customSlugField, customSlug); this.agHelper.TypeText(this.locators._customSlugField, customSlug);
} else { } else {
this.agHelper.RemoveCharsNType( this.agHelper.RemoveCharsNType(
this.locators.customSlugField, this.locators._customSlugField,
currentCustomSlugLength, currentCustomSlugLength,
customSlug, customSlug,
); );
} }
this.agHelper.PressEnter(); this.agHelper.PressEnter();
this.agHelper.ValidateNetworkStatus("@updatePage", 200); 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.agHelper.GetSiblingNClick(
this.locators.showPageNavSwitch, this.locators._showPageNavSwitch,
".bp3-control-indicator", this.locators._setHomePageToggle,
); );
this.agHelper.ValidateNetworkStatus("@updatePage", 200); this.agHelper.ValidateNetworkStatus("@updatePage", 200);
} }
setAsHomePage() { ToggleHomePage() {
this.agHelper.GetSiblingNClick( this.agHelper.GetSiblingNClick(
this.locators.setAsHomePageSwitch, this.locators._setAsHomePageSwitch,
".bp3-control-indicator", this.locators._setHomePageToggle,
); );
this.agHelper.ValidateNetworkStatus("@makePageDefault", 200); this.agHelper.ValidateNetworkStatus("@makePageDefault", 200);
} }
isHomePage(pageName: string) { AssertHomePage(pageName: string) {
this.agHelper.AssertText(this.locators.homePageHeader, "text", pageName); this.agHelper.AssertText(this.locators._homePageHeader, "text", pageName);
} }
} }

View File

@ -1391,12 +1391,14 @@ export const PAGE_SETTINGS_PAGE_URL_VERSION_UPDATE_3 = () =>
"your app URL to new readable format to change this"; "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 = () => "Show page navigation";
export const PAGE_SETTINGS_SHOW_PAGE_NAV_TOOLTIP = () => 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 = () => "Set as home page";
export const PAGE_SETTINGS_SET_AS_HOMEPAGE_TOOLTIP = () => 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"; "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 = () => 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."; "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 // Alert options and labels for showMessage types
export const ALERT_STYLE_OPTIONS = [ export const ALERT_STYLE_OPTIONS = [

View File

@ -169,7 +169,7 @@ export class URLBuilder {
baseURLRegistry[URL_TYPE.CUSTOM_SLUG][APP_MODE.PUBLISHED]; baseURLRegistry[URL_TYPE.CUSTOM_SLUG][APP_MODE.PUBLISHED];
return generatePath(urlPattern, { return generatePath(urlPattern, {
pageId, pageId,
customSlug: `${customSlug.replaceAll(" ", "-")}-`, customSlug: `${customSlug}-`,
}).toLowerCase(); }).toLowerCase();
} }
@ -182,7 +182,7 @@ export class URLBuilder {
const formattedParams = this.getFormattedParams(pageId); const formattedParams = this.getFormattedParams(pageId);
formattedParams.pageSlug = `${pageName.replaceAll(" ", "-")}-`; formattedParams.pageSlug = `${pageName}-`;
return generatePath(urlPattern, formattedParams).toLowerCase(); return generatePath(urlPattern, formattedParams).toLowerCase();
} }

View File

@ -4,7 +4,6 @@ import {
GENERAL_SETTINGS_APP_ICON_LABEL, GENERAL_SETTINGS_APP_ICON_LABEL,
GENERAL_SETTINGS_APP_NAME_LABEL, GENERAL_SETTINGS_APP_NAME_LABEL,
GENERAL_SETTINGS_NAME_EMPTY_MESSAGE, GENERAL_SETTINGS_NAME_EMPTY_MESSAGE,
GENERAL_SETTINGS_NAME_SPECIAL_CHARACTER_ERROR,
} from "ce/constants/messages"; } from "ce/constants/messages";
import classNames from "classnames"; import classNames from "classnames";
import { import {
@ -24,9 +23,7 @@ import {
} from "selectors/applicationSelectors"; } from "selectors/applicationSelectors";
import { getCurrentApplicationId } from "selectors/editorSelectors"; import { getCurrentApplicationId } from "selectors/editorSelectors";
import styled from "styled-components"; import styled from "styled-components";
import { checkRegex } from "utils/validation/CheckRegex";
import TextLoaderIcon from "../Components/TextLoaderIcon"; import TextLoaderIcon from "../Components/TextLoaderIcon";
import { appNameRegex } from "../Utils";
const IconSelectorWrapper = styled.div` const IconSelectorWrapper = styled.div`
position: relative; position: relative;
@ -60,8 +57,8 @@ function GeneralSettings() {
); );
useEffect(() => { useEffect(() => {
setApplicationName(application?.name); !isSavingAppName && setApplicationName(application?.name);
}, [application?.name]); }, [application, application?.name, isSavingAppName]);
const updateAppSettings = useCallback( const updateAppSettings = useCallback(
debounce((icon?: AppIconName) => { debounce((icon?: AppIconName) => {
@ -106,13 +103,20 @@ function GeneralSettings() {
}} }}
placeholder="App name" placeholder="App name"
type="input" type="input"
validator={checkRegex( validator={(value: string) => {
appNameRegex, let result: { isValid: boolean; message?: string } = {
GENERAL_SETTINGS_NAME_SPECIAL_CHARACTER_ERROR(), isValid: true,
true, };
setIsAppNameValid, if (!value || value.trim().length === 0) {
GENERAL_SETTINGS_NAME_EMPTY_MESSAGE(), setIsAppNameValid(false);
)} result = {
isValid: false,
message: GENERAL_SETTINGS_NAME_EMPTY_MESSAGE(),
};
}
setIsAppNameValid(result.isValid);
return result;
}}
value={applicationName} value={applicationName}
/> />
</div> </div>

View File

@ -13,7 +13,7 @@ import {
PAGE_SETTINGS_NAME_EMPTY_MESSAGE, PAGE_SETTINGS_NAME_EMPTY_MESSAGE,
PAGE_SETTINGS_SHOW_PAGE_NAV_TOOLTIP, PAGE_SETTINGS_SHOW_PAGE_NAV_TOOLTIP,
PAGE_SETTINGS_SET_AS_HOMEPAGE_TOOLTIP_NON_HOME_PAGE, 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"; } from "ce/constants/messages";
import { Page } from "ce/constants/ReduxActionConstants"; import { Page } from "ce/constants/ReduxActionConstants";
import { hasManagePagePermission } from "@appsmith/utils/permissionHelpers"; 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 ManualUpgrades from "pages/Editor/BottomBar/ManualUpgrades";
import PropertyHelpLabel from "pages/Editor/PropertyPane/PropertyHelpLabel"; import PropertyHelpLabel from "pages/Editor/PropertyPane/PropertyHelpLabel";
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux"; import { shallowEqual, useDispatch, useSelector } from "react-redux";
import { import {
getCurrentApplicationId, getCurrentApplicationId,
selectApplicationVersion, selectApplicationVersion,
@ -32,9 +32,11 @@ import {
import { getUpdatingEntity } from "selectors/explorerSelector"; import { getUpdatingEntity } from "selectors/explorerSelector";
import { getPageLoadingState } from "selectors/pageListSelectors"; import { getPageLoadingState } from "selectors/pageListSelectors";
import styled from "styled-components"; import styled from "styled-components";
import { checkRegex } from "utils/validation/CheckRegex";
import TextLoaderIcon from "../Components/TextLoaderIcon"; 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` 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 }) { function PageSettings(props: { page: Page }) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const page = props.page; const page = props.page;
@ -100,7 +104,6 @@ function PageSettings(props: { page: Page }) {
const [isPageNameValid, setIsPageNameValid] = useState(true); const [isPageNameValid, setIsPageNameValid] = useState(true);
const [customSlug, setCustomSlug] = useState(page.customSlug); const [customSlug, setCustomSlug] = useState(page.customSlug);
const [isCustomSlugValid, setIsCustomSlugValid] = useState(true);
const [isCustomSlugSaving, setIsCustomSlugSaving] = useState(false); const [isCustomSlugSaving, setIsCustomSlugSaving] = useState(false);
const [isShown, setIsShown] = useState(!!!page.isHidden); const [isShown, setIsShown] = useState(!!!page.isHidden);
@ -117,6 +120,16 @@ function PageSettings(props: { page: Page }) {
page.customSlug, page.customSlug,
])(page.pageId, pageName, page.pageName, customSlug, 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(() => { useEffect(() => {
setPageName(page.pageName); setPageName(page.pageName);
setCustomSlug(page.customSlug || ""); setCustomSlug(page.customSlug || "");
@ -151,15 +164,14 @@ function PageSettings(props: { page: Page }) {
}, [page.pageId, page.pageName, pageName, isPageNameValid]); }, [page.pageId, page.pageName, pageName, isPageNameValid]);
const saveCustomSlug = useCallback(() => { const saveCustomSlug = useCallback(() => {
if (!canManagePages || !isCustomSlugValid || page.customSlug === customSlug) if (!canManagePages || page.customSlug === customSlug) return;
return;
const payload: UpdatePageRequest = { const payload: UpdatePageRequest = {
id: page.pageId, id: page.pageId,
customSlug: customSlug || "", customSlug: customSlug || "",
}; };
setIsCustomSlugSaving(true); setIsCustomSlugSaving(true);
dispatch(updatePage(payload)); dispatch(updatePage(payload));
}, [page.pageId, page.customSlug, customSlug, isCustomSlugValid]); }, [page.pageId, page.customSlug, customSlug]);
const saveIsShown = useCallback( const saveIsShown = useCallback(
(isShown: boolean) => { (isShown: boolean) => {
@ -190,7 +202,9 @@ function PageSettings(props: { page: Page }) {
fill fill
id="t--page-settings-name" id="t--page-settings-name"
onBlur={savePageName} onBlur={savePageName}
onChange={setPageName} onChange={(value: string) =>
setPageName(resolveAsSpaceChar(value, 30))
}
onKeyPress={(ev: React.KeyboardEvent) => { onKeyPress={(ev: React.KeyboardEvent) => {
if (ev.key === "Enter") { if (ev.key === "Enter") {
savePageName(); savePageName();
@ -198,13 +212,27 @@ function PageSettings(props: { page: Page }) {
}} }}
placeholder="Page name" placeholder="Page name"
type="input" type="input"
validator={checkRegex( validator={(value: string) => {
specialCharacterCheckRegex, let result: { isValid: boolean; message?: string } = {
PAGE_SETTINGS_SPECIAL_CHARACTER_ERROR(), isValid: true,
true, };
setIsPageNameValid, if (!value || value.trim().length === 0) {
PAGE_SETTINGS_NAME_EMPTY_MESSAGE(), 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} value={pageName}
/> />
</div> </div>
@ -230,7 +258,6 @@ function PageSettings(props: { page: Page }) {
className={classNames({ className={classNames({
"py-1 relative": true, "py-1 relative": true,
"pb-2": appNeedsUpdate, "pb-2": appNeedsUpdate,
"pb-6": !appNeedsUpdate && !isCustomSlugValid,
})} })}
> >
{isCustomSlugSaving && <TextLoaderIcon />} {isCustomSlugSaving && <TextLoaderIcon />}
@ -240,7 +267,11 @@ function PageSettings(props: { page: Page }) {
fill fill
id="t--page-settings-custom-slug" id="t--page-settings-custom-slug"
onBlur={saveCustomSlug} onBlur={saveCustomSlug}
onChange={setCustomSlug} onChange={(value: string) =>
value.length > 0
? specialCharacterCheckRegex.test(value) && setCustomSlug(value)
: setCustomSlug(value)
}
onKeyPress={(ev: React.KeyboardEvent) => { onKeyPress={(ev: React.KeyboardEvent) => {
if (ev.key === "Enter") { if (ev.key === "Enter") {
saveCustomSlug(); saveCustomSlug();
@ -249,12 +280,6 @@ function PageSettings(props: { page: Page }) {
placeholder="Page URL" placeholder="Page URL"
readOnly={appNeedsUpdate} readOnly={appNeedsUpdate}
type="input" type="input"
validator={checkRegex(
specialCharacterCheckRegex,
PAGE_SETTINGS_SPECIAL_CHARACTER_ERROR(),
false,
setIsCustomSlugValid,
)}
value={customSlug} value={customSlug}
/> />
</div> </div>

View File

@ -2,9 +2,6 @@ import { APP_MODE } from "entities/App";
import urlBuilder from "entities/URLRedirect/URLAssembly"; import urlBuilder from "entities/URLRedirect/URLAssembly";
import { splitPathPreview } from "utils/helpers"; import { splitPathPreview } from "utils/helpers";
export const specialCharacterCheckRegex = /^[A-Za-z0-9\s\-]+$/;
export const appNameRegex = /^[A-Za-z0-9\s\-()]+$/;
export const getUrlPreview = ( export const getUrlPreview = (
pageId: string, pageId: string,
newPageName: string, newPageName: string,
@ -14,6 +11,13 @@ export const getUrlPreview = (
) => { ) => {
let relativePath: string; let relativePath: string;
newPageName = filterAccentedAndSpecialCharacters(newPageName);
currentPageName = filterAccentedAndSpecialCharacters(currentPageName);
newCustomSlug &&
(newCustomSlug = filterAccentedAndSpecialCharacters(newCustomSlug));
currentCustomSlug &&
(currentCustomSlug = filterAccentedAndSpecialCharacters(currentCustomSlug));
// when page name is changed // when page name is changed
// and when custom slug doesn't exist // and when custom slug doesn't exist
if (!newCustomSlug && newPageName !== currentPageName) { if (!newCustomSlug && newPageName !== currentPageName) {
@ -41,3 +45,9 @@ export const getUrlPreview = (
splitRelativePath: splitPathPreview(relativePath, newCustomSlug), splitRelativePath: splitPathPreview(relativePath, newCustomSlug),
}; };
}; };
const filterAccentedAndSpecialCharacters = (value: string) => {
return decodeURI(value)
.replaceAll(" ", "-")
.replaceAll(/[^A-Za-z0-9-]/g, "");
};

View File

@ -39,7 +39,7 @@ export function CanvasPropertyPane() {
position={PopoverPosition.BOTTOM} position={PopoverPosition.BOTTOM}
> >
<Button <Button
category={Category.tertiary} category={Category.secondary}
fill fill
id="t--app-settings-cta" id="t--app-settings-cta"
onClick={openAppSettingsPane} onClick={openAppSettingsPane}

View File

@ -69,7 +69,7 @@ export const GetNavigationMenuData = ({
return [ return [
{ {
text: "Go to dashboard", text: "Home",
onClick: () => history.replace(APPLICATIONS_URL), onClick: () => history.replace(APPLICATIONS_URL),
type: MenuTypes.MENU, type: MenuTypes.MENU,
isVisible: true, isVisible: true,

View File

@ -155,11 +155,6 @@ export function PageContextMenu(props: {
</CustomLabel> </CustomLabel>
) as ReactNode) as string, ) as ReactNode) as string,
}, },
{
value: "settings",
onSelect: openAppSettingsPane,
label: createMessage(CONTEXT_SETTINGS),
},
!props.isDefaultPage && !props.isDefaultPage &&
canManagePages && { canManagePages && {
value: "setdefault", value: "setdefault",
@ -173,6 +168,11 @@ export function PageContextMenu(props: {
value: "setdefault", value: "setdefault",
label: createMessage(CONTEXT_SET_AS_HOME_PAGE), label: createMessage(CONTEXT_SET_AS_HOME_PAGE),
}, },
{
value: "settings",
onSelect: openAppSettingsPane,
label: createMessage(CONTEXT_SETTINGS),
},
!props.isDefaultPage && !props.isDefaultPage &&
canDeletePages && { canDeletePages && {
className: "t--apiFormDeleteBtn single-select", className: "t--apiFormDeleteBtn single-select",

View File

@ -1,24 +0,0 @@
export const checkRegex = (
regex: RegExp,
errorMessage: string,
checkEmpty = true,
callback?: (isValid: boolean) => void,
emptyMessage = "Cannot be empty",
) => {
return (value: string) => {
const isEmpty = value.length === 0;
const regexMismatch = !isEmpty && !regex.test(value);
const hasError = (checkEmpty && isEmpty) || regexMismatch;
callback?.(!hasError);
let message = "";
if (checkEmpty && isEmpty) message = emptyMessage;
else if (regexMismatch) message = errorMessage;
return {
isValid: !hasError,
message,
};
};
};