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";
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();
});
});

View File

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

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() {
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 });

View File

@ -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();
});
});

View File

@ -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();
});
});

View File

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

View File

@ -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")

View File

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

View File

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

View File

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

View File

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

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 { 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__;
}
}

View File

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

View File

@ -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,
);
}
});
}
}

View File

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

View File

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

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";
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 = [

View File

@ -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();
}

View File

@ -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}
/>
</div>

View File

@ -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}
/>
</div>
@ -230,7 +258,6 @@ function PageSettings(props: { page: Page }) {
className={classNames({
"py-1 relative": true,
"pb-2": appNeedsUpdate,
"pb-6": !appNeedsUpdate && !isCustomSlugValid,
})}
>
{isCustomSlugSaving && <TextLoaderIcon />}
@ -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}
/>
</div>

View File

@ -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, "");
};

View File

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

View File

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

View File

@ -155,11 +155,6 @@ export function PageContextMenu(props: {
</CustomLabel>
) as ReactNode) as string,
},
{
value: "settings",
onSelect: openAppSettingsPane,
label: createMessage(CONTEXT_SETTINGS),
},
!props.isDefaultPage &&
canManagePages && {
value: "setdefault",
@ -173,6 +168,11 @@ export function PageContextMenu(props: {
value: "setdefault",
label: createMessage(CONTEXT_SET_AS_HOME_PAGE),
},
{
value: "settings",
onSelect: openAppSettingsPane,
label: createMessage(CONTEXT_SETTINGS),
},
!props.isDefaultPage &&
canDeletePages && {
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,
};
};
};