Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions app/client/cypress/e2e/Regression/ClientSide/Anvil/AnvilModal_spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import {
agHelper,
anvilLayout,
locators,
propPane,
} from "../../../../support/Objects/ObjectsCore";
import { ANVIL_EDITOR_TEST } from "../../../../support/Constants";
import { anvilLocators } from "../../../../support/Pages/Anvil/Locators";
import EditorNavigation, {
EntityType,
} from "../../../../support/Pages/EditorNavigation";

describe(
`${ANVIL_EDITOR_TEST}: Anvil tests for Modals`,
{ tags: ["@tag.Anvil"] },
() => {
before(() => {
// Cleanup the canvas before each test
agHelper.SelectAllWidgets();
agHelper.PressDelete();
});
it("1. Verify opening a modal by clicking on a button", () => {
// drop a modal widget
anvilLayout.dnd.DragDropNewAnvilWidgetNVerify(
anvilLocators.WDSMODAL,
10,
10,
{
skipWidgetSearch: true,
dropTargetDetails: {
dropModal: true,
},
},
);
// press escape and close modal
agHelper.PressEscape();
// add a button
anvilLayout.dnd.DragDropNewAnvilWidgetNVerify(
anvilLocators.WDSBUTTON,
10,
10,
{
skipWidgetSearch: true,
},
);
propPane.EnterJSContext("onClick", "{{showModal(Modal1.name);}}");
agHelper.GetNClick(locators._enterPreviewMode);
agHelper.GetNClick(anvilLocators.anvilWidgetNameSelector("Button1"));
agHelper.AssertElementExist(
anvilLocators.anvilWidgetNameSelector("Modal1"),
);
});
it("2. Verify closing a modal using the close icon button", () => {
agHelper.GetNClick(
anvilLocators.anvilModalCloseIconButtonSelector("Modal1"),
);
agHelper.AssertElementAbsence(
anvilLocators.anvilWidgetNameSelector("Modal1"),
);
});
it("3. Verify closing a modal by clicking outside the modal area", () => {
// open modal
agHelper.GetNClick(anvilLocators.anvilWidgetNameSelector("Button1"));
agHelper.AssertElementExist(
anvilLocators.anvilWidgetNameSelector("Modal1"),
);
// click on overlay top position
agHelper.GetNClick(
anvilLocators.anvilModalOverlay,
0,
false,
500,
false,
false,
"top",
);
agHelper.AssertElementAbsence(
anvilLocators.anvilWidgetNameSelector("Modal1"),
);
});
it("4. Verify closing a modal using the ESC key", () => {
// open modal
agHelper.GetNClick(anvilLocators.anvilWidgetNameSelector("Button1"));
agHelper.AssertElementExist(
anvilLocators.anvilWidgetNameSelector("Modal1"),
);
// press escape
agHelper.PressEscape();
agHelper.AssertElementAbsence(
anvilLocators.anvilWidgetNameSelector("Modal1"),
);
agHelper.GetNClick(locators._exitPreviewMode);
});
it("5. verify onClose function of Modal", () => {
EditorNavigation.SelectEntityByName("Modal1", EntityType.Widget);
propPane.EnterJSContext("onClose", "{{showAlert('onCloseTest');}}");
agHelper.GetNClick(locators._enterPreviewMode);
//close modal via footer close button
agHelper.GetNClick(
anvilLocators.anvilModalFooterCloseButtonSelector("Modal1"),
);
//verify alert
agHelper.ValidateToastMessage("onCloseTest");
agHelper.GetNClick(locators._exitPreviewMode);
});
it("6. Verify onSubmit function on Modal", () => {
EditorNavigation.SelectEntityByName("Modal1", EntityType.Widget);
propPane.EnterJSContext("onSubmit", "{{showAlert('onSubmitTest');}}");
agHelper.GetNClick(locators._enterPreviewMode);
//close modal via submit button
agHelper.GetNClick(
anvilLocators.anvilModalFooterSubmitButtonSelector("Modal1"),
);
//verify alert
agHelper.ValidateToastMessage("onSubmitTest");
agHelper.GetNClick(locators._exitPreviewMode);
});
it("7. Verify DnD on Modal", () => {
EditorNavigation.SelectEntityByName("Modal1", EntityType.Widget);
// add a widget to modal
anvilLayout.dnd.DragDropNewAnvilWidgetNVerify(
anvilLocators.WDSBUTTON,
10,
10,
{
skipWidgetSearch: true,
dropTargetDetails: {
name: "Modal1",
},
},
);
// verify newly added button
agHelper.AssertElementExist(
anvilLocators.anvilWidgetNameSelector("Button2"),
);
});
it("8. Verify different modal sizes", () => {
// select all widgets and delete
agHelper.PressEscape();
agHelper.SelectAllWidgets();
agHelper.PressDelete();
// add a modal widget
anvilLayout.dnd.DragDropNewAnvilWidgetNVerify(
anvilLocators.WDSMODAL,
10,
10,
{
skipWidgetSearch: true,
dropTargetDetails: {
dropModal: true,
},
},
);
agHelper
.GetElement(anvilLocators.anvilWidgetNameSelector("Modal1"))
.matchImageSnapshot("anvilModalMediumSize");
propPane.SelectPropertiesDropDown("size", "Small");
agHelper
.GetElement(anvilLocators.anvilWidgetNameSelector("Modal1"))
.matchImageSnapshot("anvilModalSmallSize");
propPane.SelectPropertiesDropDown("size", "Large");
agHelper
.GetElement(anvilLocators.anvilWidgetNameSelector("Modal1"))
.matchImageSnapshot("anvilModalLargeSize");
});
},
);
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 9 additions & 2 deletions app/client/cypress/support/Pages/Anvil/AnvilDnDHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { anvilLocators } from "./Locators";
interface DropTargetDetails {
id?: string;
name?: string;
dropModal?: boolean;
}

interface DragDropWidgetOptions {
Expand All @@ -26,11 +27,14 @@ export class AnvilDnDHelper {
dropTarget?: DropTargetDetails,
) => {
if (dropTarget) {
if (dropTarget.dropModal) {
return anvilLocators.anvilDetachedWidgetsDropArena;
}
if (dropTarget.id) {
return `#${dropTarget.id}`;
}
if (dropTarget.name) {
return `${getWidgetSelector(dropTarget.name.toLowerCase() as any)} ${
return `${anvilLocators.anvilWidgetNameSelector(dropTarget.name)} ${
anvilLocators.anvilDnDListener
}`;
}
Expand Down Expand Up @@ -64,7 +68,10 @@ export class AnvilDnDHelper {
eventConstructor: "MouseEvent",
force: true,
});
cy.get(this.locator._anvilDnDHighlight);
if (!options.dropTargetDetails?.dropModal) {
// no need to show highlight for modal drop
cy.get(this.locator._anvilDnDHighlight);
}
cy.get(dropAreaSelector).first().trigger("mouseup", xPos, yPos, {
eventConstructor: "MouseEvent",
force: true,
Expand Down
5 changes: 5 additions & 0 deletions app/client/cypress/support/Pages/Anvil/AnvilLayout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ export class AnvilLayout {
const widgetSelector = anvilLocators.anvilWidgetNameSelector(widgetName);
cy.get(widgetSelector).should("not.exist");
}

public verifyAnvilModalIsClosed(widgetName: string) {
this.verifyWidgetDoesNotExist(widgetName);
}

public verifyParentChildRelationship(parentName: string, childName: string) {
const parentWidgetSelector =
anvilLocators.anvilWidgetNameSelector(parentName);
Expand Down
17 changes: 17 additions & 0 deletions app/client/cypress/support/Pages/Anvil/Locators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,25 @@ const anvilWidgetBasedSelectors = {
anvilWidgetNameSelector: (widgetName: string) => {
return `[${AnvilDataAttributes.WIDGET_NAME}="${widgetName}"]`;
},
anvilModalOverlay: 'div[data-floating-ui-portal] > div[data-status="open"]',
anvilSelectedWidget: `${anvilWidgetSelector}[data-selected=true]`,
anvilWidgetTypeSelector: (widgetType: string) => {
return `.t--widget-${widgetType}`;
},
};

const anvilModalWidgetSelectors = {
anvilModalCloseIconButtonSelector: (widgetName: string) => {
return `${anvilWidgetBasedSelectors.anvilWidgetNameSelector(widgetName)} > div > div > button[data-icon-button]`;
},
anvilModalFooterCloseButtonSelector: (widgetName: string) => {
return `${anvilWidgetBasedSelectors.anvilWidgetNameSelector(widgetName)} > div > div:last-child > button[data-button]:first-child`;
},
anvilModalFooterSubmitButtonSelector: (widgetName: string) => {
return `${anvilWidgetBasedSelectors.anvilWidgetNameSelector(widgetName)} > div > div:last-child > button[data-button]:last-child`;
},
};

// sections and zones based selectors
const anvilSectionAndZonesBasedSelectors = {
anvilZoneDistributionValue: "[data-testid=t--anvil-zone-distribution-value]",
Expand All @@ -29,6 +42,8 @@ const anvilSectionAndZonesBasedSelectors = {
// dnd based selectors
const anvilDnDBasedSelectors = {
anvilDnDListener: "[data-type=anvil-dnd-listener]",
anvilDetachedWidgetsDropArena:
"[data-testid=t--anvil-detached-widgets-drop-arena]",
mainCanvasSelector: `#${getAnvilCanvasId(MAIN_CONTAINER_WIDGET_ID)}`,
};

Expand All @@ -39,12 +54,14 @@ const anvilWidgetsLocators = {
WDSINPUT: "wdsinputwidget",
WDSSWITCH: "wdsswitchwidget",
WDSCHECKBOX: "wdscheckboxwidget",
WDSMODAL: "wdsmodalwidget",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

Codebase Verification

The search results indicate that the new widget type WDSMODAL is only defined in index.ts and referenced in test files, but there are no indications of its handling in the main widget system logic.

  • app/client/cypress/support/Pages/Anvil/Locators/index.ts: Definition of WDSMODAL.
  • No other occurrences in the main codebase excluding test files.

This suggests that the new widget type WDSMODAL might not be integrated into the widget system logic yet.

Analysis chain

Added a new widget type for modals. Confirm that this widget type is integrated into the widget system and handled appropriately in widget-related logic.


Let's address the issue with the rg command by specifying a recognized file type and ensuring we capture all relevant instances of WDSMODAL.


The search results show that the new widget type WDSMODAL is defined in index.ts and referenced in the AnvilModal_spec.ts test files. However, to confirm proper integration and handling in the widget system logic, we need to verify its usage beyond just test files.

Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify the integration of the new modal widget type.

# Test: Search for the handling of the new widget type. Expect: Proper handling in the widget system logic.
rg --type typescript $'WDSMODAL'

Length of output: 66



Script:

#!/bin/bash
# Description: Verify the integration of the new modal widget type.

# Correcting the file type and searching for the handling of the new widget type in TypeScript files.
rg --type-add 'ts:*.ts' --type ts 'WDSMODAL'

Length of output: 335



Script:

#!/bin/bash
# Description: Verify the integration of the new modal widget type in the widget system logic.

# Searching for the handling of the new widget type in the entire codebase, excluding test files.
rg --type-add 'ts:*.ts' --type ts 'WDSMODAL' --glob '!**/*spec.ts'

Length of output: 153

SECTION: "sectionwidget",
ZONE: "zonewidget",
};

export const anvilLocators = {
...anvilWidgetBasedSelectors,
...anvilModalWidgetSelectors,
...anvilWidgetsLocators,
...anvilSectionAndZonesBasedSelectors,
...anvilDnDBasedSelectors,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import clsx from "clsx";
export const Modal = (props: ModalProps) => {
const {
children,
dataAttributes = {},
overlayClassName,
size = "medium",
triggerRef,
...rest
} = props;
Expand All @@ -17,7 +17,7 @@ export const Modal = (props: ModalProps) => {
// don't forget to change the transition-duration CSS as well
<Popover duration={200} modal triggerRef={triggerRef} {...rest}>
<PopoverModalContent
data-size={size}
{...dataAttributes}
overlayClassName={clsx(styles.overlay, overlayClassName)}
>
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type {
PopoverProps,
} from "@design-system/headless";
import type { ReactNode } from "react";
import type { SIZES } from "../../../shared";

export interface ModalProps
extends Pick<
Expand All @@ -16,10 +15,7 @@ export interface ModalProps
| "dismissClickOutside"
>,
Pick<PopoverModalContentProps, "overlayClassName"> {
/** Size of the Modal
* @default medium
*/
size?: keyof typeof SIZES;
dataAttributes?: Record<string, string>;
/** The children of the component. */
children: ReactNode;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ export const ModalExamples = () => {
}}
/>
<Modal
dataAttributes={{ "data-size": "large" }}
initialFocus={2}
isOpen={isLargeOpen}
setOpen={setLargeOpen}
size="large"
triggerRef={largeRef}
>
<Unstyled>
Expand Down Expand Up @@ -121,10 +121,10 @@ export const ModalExamples = () => {
</Unstyled>
</Modal>
<Modal
dataAttributes={{ "data-size": "small" }}
initialFocus={2}
isOpen={isSmallOpen}
setOpen={setSmallOpen}
size="small"
triggerRef={smallRef}
>
<Unstyled>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,10 @@ export const ComplexForm = () => {
Ok
</Button>
<Modal
dataAttributes={{ "data-size": "small" }}
initialFocus={2}
isOpen={isModalOpen}
setOpen={setModalOpen}
size="small"
triggerRef={submitRef}
>
<ModalContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Layers } from "constants/Layers";
import { noop } from "utils/AppsmithUtils";
import { convertFlexGrowToFlexBasis } from "../sectionSpaceDistributor/utils/spaceDistributionEditorUtils";
import styles from "./styles.module.css";
import { AnvilDataAttributes } from "widgets/anvil/constants";

const anvilWidgetStyleProps: CSSProperties = {
position: "relative",
Expand Down Expand Up @@ -44,13 +45,13 @@ export const AnvilFlexComponent = forwardRef(
onClick = noop,
onClickCapture = noop,
widgetId,
widgetName,
widgetSize,
widgetType,
}: AnvilFlexComponentProps,
ref: any,
) => {
const _className = `${className} ${styles.anvilWidgetWrapper}`;

const widgetConfigProps = useMemo(() => {
const widgetConfig:
| (Partial<WidgetProps> & WidgetConfigProps & { type: string })
Expand Down Expand Up @@ -99,12 +100,15 @@ export const AnvilFlexComponent = forwardRef(
}
return data;
}, [widgetConfigProps, widgetSize, flexGrow]);

const testDataAttributes = {
[AnvilDataAttributes.WIDGET_NAME]: widgetName,
};
// Render the Anvil Flex Component using the Flex component from WDS
return (
<Flex
isInner
{...flexProps}
{...testDataAttributes}
className={_className}
data-testid="t--anvil-widget-wrapper"
data-widget-wrapper=""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ export const DetachedWidgetsDropArena = (props: {
});
};
return props.anvilGlobalDragStates.activateOverlayWidgetDrop ? (
<DetachedWidgetsDropArenaWrapper onMouseUp={onMouseUp}>
<DetachedWidgetsDropArenaWrapper
data-testid="t--anvil-detached-widgets-drop-arena"
onMouseUp={onMouseUp}
>
<Popover isOpen modal>
<PopoverModalContent
contentClassName={styles.detachedWidgetsDropOverlayContent}
Expand Down
Loading