Skip to content

Commit

Permalink
chore: Anvil on canvas UI tests (#35342)
Browse files Browse the repository at this point in the history
## Description
### Unit
`index.test.tsx` - (Main connected component that uses floating ui)
- [x] Test rendering of widget name component (floating-ui) based on
"select", "focus" and "none" states from redux selector
- [x] Test rendering of widget name component (floating-ui) based on
error count from redux selector

`AnvilWidgetNameComponent.test.tsx` (Standalone component that renders
content for floating ui to use)
- [x] Test rendering of standalone widget name component based on props.
- [x] Test calling of `onDragStart` callback from the standalone widget
name component.

`SplitButton.test.tsx` (Low level UI component - child of
`AnvilWidgetNameComponent`)
- [x] Test left and right toggle click triggers
- [x] Test if component handles props correctly


### E2E
- [x] Test Dragging via widget name component
- [x] Test widget name component when multiple widgets are selected
- [x] Test Hover and Selection to show widget name component

Fixes #33979

## Automation

/ok-to-test tags="@tag.All"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/10349986042>
> Commit: 8c9e6b9
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=10349986042&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.All`
> Spec:
> <hr>Mon, 12 Aug 2024 11:13:52 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [x] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Summary by CodeRabbit

- **New Features**
- Introduced a comprehensive suite of end-to-end tests for the Anvil
editor's UI in canvas layout mode.
- Enhanced flexibility in widget manipulation on the canvas with an
updated method.
	- Expanded selectors for more precise targeting of UI components.
- Added unit tests for the `AnvilWidgetNameComponent` and `SplitButton`
components to ensure expected functionality.

- **Bug Fixes**
- Improved responsiveness of the `AnvilWidgetName` component to state
changes.

- **Documentation**
- Updated comments and structure for clarity in test scenarios and
component functionality.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
riodeuno authored Aug 12, 2024
1 parent 35839c6 commit 31be835
Show file tree
Hide file tree
Showing 9 changed files with 513 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { ANVIL_EDITOR_TEST } from "../../../../support/Constants";
import { agHelper, anvilLayout } from "../../../../support/Objects/ObjectsCore";
import { featureFlagIntercept } from "../../../../support/Objects/FeatureFlags";
import { WIDGET } from "../../../../locators/WidgetLocators";
import { anvilLocators } from "../../../../support/Pages/Anvil/Locators";

describe(
`${ANVIL_EDITOR_TEST}: Testing On Canvas UI in Anvil Layout Mode`,
{ tags: ["@tag.Anvil"] },
function () {
before(() => {
// intercept features call for Anvil + WDS tests
featureFlagIntercept({
release_anvil_enabled: true,
});
});
it("1. Widget name component shows and positioned correctly", () => {
const horizontalPixelOnCanvasMouseMovement = 5;
let verticalPixelOnCanvasMouseMovement = 20;
// Dnd Button widget into the existing zone
anvilLayout.dnd.DragDropNewAnvilWidgetNVerify(
anvilLocators.WDSBUTTON,
horizontalPixelOnCanvasMouseMovement,
verticalPixelOnCanvasMouseMovement,
{
skipWidgetSearch: true,
},
);
// Unselect all widgets by clicking on the canvas
agHelper.GetNClick(`${anvilLocators.mainCanvasSelector}`);

// hover over button widget
agHelper.HoverElement(anvilLocators.anvilWidgetNameSelector("Button1"));
// Make sure there is one widget name showing on the canvas
agHelper.AssertElementLength(
anvilLocators.anvilOnCanvasWidgetNameSelector,
1,
);

// Make sure the widget name is positioned above the widget
cy.get(anvilLocators.anvilWidgetNameSelector("Button1")).then(($el) => {
const el = $el[0];
const buttonRect = el.getBoundingClientRect();
cy.get(anvilLocators.anvilOnCanvasWidgetNameSelector).then(
($onCanvasEL) => {
const el = $onCanvasEL[0];
const widgetNameRect = el.getBoundingClientRect();
expect(buttonRect.top).to.equal(widgetNameRect.bottom);
},
);
});
verticalPixelOnCanvasMouseMovement = 200;
// Dnd Button widget into the existing zone
anvilLayout.dnd.DragDropNewAnvilWidgetNVerify(
anvilLocators.WDSBUTTON,
horizontalPixelOnCanvasMouseMovement,
verticalPixelOnCanvasMouseMovement,
{
skipWidgetSearch: true,
},
);
// Click on the button widget
agHelper.GetNClick(anvilLocators.anvilWidgetNameSelector("Button1"));
// Make sure there is one widget name showing on the canvas
agHelper.AssertElementLength(
anvilLocators.anvilOnCanvasWidgetNameSelector,
1,
);
// Make sure the widget name is positioned above the widget
cy.get(anvilLocators.anvilWidgetNameSelector("Button1")).then(($el) => {
const el = $el[0];
const buttonRect = el.getBoundingClientRect();
cy.get(anvilLocators.anvilOnCanvasWidgetNameSelector).then(
($onCanvasEL) => {
const el = $onCanvasEL[0];
const widgetNameRect = el.getBoundingClientRect();
expect(buttonRect.top).to.equal(widgetNameRect.bottom);
},
);
});

// Move the widget and check that the widget has moved into a new zone
anvilLayout.dnd.MoveAnvilWidget(undefined, 0, 80);
agHelper.AssertAutoSave();
anvilLayout.verifyParentChildRelationship("Zone2", "Button1");
});
it("2. Multiple widget names should show when multiple widgets are selected", () => {
// Cleanup the canvas before test
agHelper.PressEscape();
agHelper.SelectAllWidgets();
agHelper.PressDelete();
const horizontalPixelOnCanvasMouseMovement = 5;
const verticalPixelOnCanvasMouseMovement = 20;
// DnD Switch widget
anvilLayout.dnd.DragDropNewAnvilWidgetNVerify(
anvilLocators.WDSSWITCH,
horizontalPixelOnCanvasMouseMovement,
verticalPixelOnCanvasMouseMovement,
{
skipWidgetSearch: true,
},
);

// DnD Switch widget into the existing zone
anvilLayout.dnd.DragDropNewAnvilWidgetNVerify(
anvilLocators.WDSSWITCH,
horizontalPixelOnCanvasMouseMovement,
verticalPixelOnCanvasMouseMovement,
{
skipWidgetSearch: true,
dropTargetDetails: {
name: "Zone1",
},
},
);
// Dnd Button widget into the existing zone
anvilLayout.dnd.DragDropNewAnvilWidgetNVerify(
anvilLocators.WDSBUTTON,
horizontalPixelOnCanvasMouseMovement,
verticalPixelOnCanvasMouseMovement,
{
skipWidgetSearch: true,
dropTargetDetails: {
name: "Zone1",
},
},
);
// Select an existing switch widget
agHelper.GetNClick(
anvilLocators.anvilWidgetNameSelector("Switch1"),
0,
false,
500,
true,
);
// Make sure there are two widget names showing on the canvas
agHelper.AssertElementLength(
anvilLocators.anvilOnCanvasWidgetNameSelector,
2,
);
});
},
);
14 changes: 11 additions & 3 deletions app/client/cypress/support/Pages/Anvil/AnvilDnDHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ export class AnvilDnDHelper {
this.startDraggingWidgetFromPane(widgetType);
this.performDnDInAnvil(x, y, options);
}

/* This function will drag and drop a new widget of the specified type to the specified x and y pixel coordinates.
The x and y coordinates are relative to the main canvas's top left corner on the viewport */
public DragDropNewAnvilWidgetNVerify(
widgetType: string,
x = 300,
Expand All @@ -123,8 +126,12 @@ export class AnvilDnDHelper {
});
}

/* If __only one__ widget is selected on the canvas,
this function will move the widget to the specified x and y coordinates using the widget name component.
In this case, the widgetName argument is not necessary */
// This function uses the widget name on canvas UI to drag and drop the widget
public MoveAnvilWidget(
widgetName: string,
widgetName?: string,
x = 300,
y = 100,
options = {} as DragDropWidgetOptions,
Expand All @@ -134,8 +141,9 @@ export class AnvilDnDHelper {
.then((mainCanvas) => {
const mainCanvasX = mainCanvas.position().left;
const mainCanvasY = mainCanvas.position().top;
const widgetSelector =
anvilLocators.anvilWidgetNameSelector(widgetName);
const widgetSelector = widgetName
? anvilLocators.anvilWidgetNameSelector(widgetName)
: anvilLocators.anvilOnCanvasWidgetNameSelector;
// perform mouseover to focus the widget before drag to allow dragging
cy.get(widgetSelector).first().trigger("mouseover", { force: true });
cy.get(widgetSelector).first().trigger("dragstart", { force: true });
Expand Down
6 changes: 6 additions & 0 deletions app/client/cypress/support/Pages/Anvil/Locators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ const anvilModalWidgetSelectors = {
},
};

const anvilOnCanvasUISelectors = {
anvilOnCanvasWidgetNameSelector:
"[data-testid=t--anvil-draggable-widget-name]",
};

// sections and zones based selectors
const anvilSectionAndZonesBasedSelectors = {
anvilZoneDistributionValue: "[data-testid=t--anvil-zone-distribution-value]",
Expand Down Expand Up @@ -68,4 +73,5 @@ export const anvilLocators = {
...anvilWidgetsLocators,
...anvilSectionAndZonesBasedSelectors,
...anvilDnDBasedSelectors,
...anvilOnCanvasUISelectors,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { fireEvent, render, screen } from "@testing-library/react";
import { AnvilWidgetNameComponent } from "./AnvilWidgetNameComponent";
import "@testing-library/jest-dom";
import React from "react";
import { Provider } from "react-redux";
import store from "store";

const handler = {
log: jest.fn(),
};
const logSpy = jest.spyOn(handler, "log");
const props = {
name: "WidgetName",
widgetId: "widgetId",
selectionBGCSSVar: "--something",
selectionColorCSSVar: "--something",
bGCSSVar: "--something",
colorCSSVar: "--something",
disableParentSelection: false,
showError: false,
onDragStart: () => {},
};

describe("AnvilWidgetNameComponent", () => {
it("should show SplitButton", async () => {
render(
<Provider store={store}>
<AnvilWidgetNameComponent {...props} />
</Provider>,
);
expect(screen.getByText("WidgetName")).toBeInTheDocument();
});

it("should show Call the drag event handler on drag", async () => {
const _props = {
...props,
onDragStart: () => {
handler.log("Dragged!");
},
};
render(
<Provider store={store}>
<AnvilWidgetNameComponent {..._props} />
</Provider>,
);

const draggableNameComponent = screen.getByTestId(
"t--anvil-draggable-widget-name",
);
expect(draggableNameComponent).toBeInTheDocument();
fireEvent.dragStart(draggableNameComponent);

expect(logSpy).toHaveBeenCalledWith("Dragged!");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,19 @@ export function _AnvilWidgetNameComponent(
const { selectWidget } = useWidgetSelection();
const handleSelectParent = useCallback(() => {
parentId && selectWidget(SelectionRequestType.One, [parentId]);
}, [parentId]);
}, [parentId, selectWidget]);

const handleSelectWidget = useCallback(() => {
selectWidget(
SelectionRequestType.One,
[props.widgetId],
NavigationMethod.CanvasClick,
);
}, [props.widgetId]);
}, [props.widgetId, selectWidget]);

const handleDebugClick = useCallback(() => {
dispatch(debugWidget(props.widgetId));
}, [props.widgetId]);
}, [props.widgetId, dispatch]);
/** EO Widget Selection Handlers */

const leftToggle = useMemo(() => {
Expand All @@ -93,7 +93,13 @@ export function _AnvilWidgetNameComponent(
}, [props.showError, handleDebugClick]);

return (
<div draggable onDragStart={props.onDragStart} ref={ref} style={styles}>
<div
data-testid="t--anvil-draggable-widget-name"
draggable
onDragStart={props.onDragStart}
ref={ref}
style={styles}
>
<SplitButton
bGCSSVar={props.bGCSSVar}
colorCSSVar={props.colorCSSVar}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import React from "react";
import { SplitButton } from "./SplitButton";
import userEvent from "@testing-library/user-event";

const handler = {
log: jest.fn(),
};
const logSpy = jest.spyOn(handler, "log");

const props = {
text: "widgetName",
onClick: () => handler.log("Clicked!"),
bGCSSVar: "--something",
colorCSSVar: "--something",
leftToggle: {
disable: false,
onClick: () => handler.log("Left Toggle Clicked!"),
title: "Left Toggle Title",
},
rightToggle: {
disable: false,
onClick: () => handler.log("Right Toggle Clicked!"),
title: "Right Toggle Title",
},
};

describe("SplitButton", () => {
it("should show SplitButton", async () => {
render(<SplitButton {...props} />);
expect(screen.getByText("widgetName")).toBeInTheDocument();
expect(screen.queryByTestId("t--splitbutton")).toHaveStyle(
`color: var(--something)`,
);
});

it("should show and call the click event handler on click", async () => {
render(<SplitButton {...props} />);
const clickableButton = screen.getByTestId(
"t--splitbutton-clickable-button",
);
expect(clickableButton).toBeInTheDocument();
await userEvent.click(clickableButton);
expect(logSpy).toHaveBeenCalledWith("Clicked!");
});

it("should show and call the click event handler on click of left toggle", async () => {
render(<SplitButton {...props} />);
const clickableButton = screen.getByTestId("t--splitbutton-left-toggle");
expect(clickableButton).toBeInTheDocument();
await userEvent.click(clickableButton);
expect(logSpy).toHaveBeenCalledWith("Left Toggle Clicked!");
});

it("should show and call the click event handler on click of right toggle", async () => {
render(<SplitButton {...props} />);
const clickableButton = screen.getByTestId("t--splitbutton-right-toggle");
expect(clickableButton).toBeInTheDocument();
await userEvent.click(clickableButton);
expect(logSpy).toHaveBeenCalledWith("Right Toggle Clicked!");
});

it("should hide left and right toggle based on props", async () => {
const _props = {
...props,
leftToggle: { ...props.leftToggle, disable: true },
rightToggle: { ...props.rightToggle, disable: true },
};
render(<SplitButton {..._props} />);
expect(
screen.queryByTestId("t--splitbutton-left-toggle"),
).not.toBeInTheDocument();
expect(
screen.queryByTestId("t--splitbutton-right-toggle"),
).not.toBeInTheDocument();
});
});
Loading

0 comments on commit 31be835

Please sign in to comment.