PromucFlow_constructor/app/client/src/sagas/BuildingBlockSagas/PasteBuildingBlockWidgetSaga.test.ts

155 lines
4.4 KiB
TypeScript
Raw Normal View History

fix: allow building blocks to be dropped inside containers (#33660) ## Description **Problem** Previously, the implementation of building blocks was limited in functionality: building blocks could not be placed into containers. This restriction constrained building blocks to be solely draggable items on the canvas, reducing their versatility and utility. **Solution** To address this limitation, we have improved the paste logic for building blocks. The updated logic now allows building blocks to be dropped into other containers. **Key changes include:** 1. Simplification of the `pastingIntoWidgetId` determination process. The paste logic previously had multiple conditions to determine this ID. 2. Direct passing of the parent widget ID into the `pasteBuildingBlockWidgetsSaga` function, streamlining the process and reducing complexity. These enhancements significantly increase the flexibility of building blocks, enabling more complex and nested designs. Fixes #33606 ## Automation /ok-to-test tags="@tag.Templates, @tag.Widget" ### :mag: 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/9249397667> > Commit: e1f72697bd1bc0fe827895a0f09228440f8be0a8 > Cypress dashboard url: <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=9249397667&attempt=1" target="_blank">Click here!</a> <!-- end of auto-generated comment: Cypress test results --> ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [x] No
2024-05-27 06:41:59 +00:00
import type {
AllEffect,
CallEffect,
PutEffect,
SelectEffect,
} from "redux-saga/effects";
import { call, select } from "redux-saga/effects";
import { pasteBuildingBlockWidgetsSaga } from "./BuildingBlockAdditionSagas";
import { getCopiedWidgets } from "utils/storage";
import type { FlexLayer } from "layoutSystems/autolayout/utils/types";
import { getWidgets } from "sagas/selectors";
import {
getCanvasWidth,
getIsAutoLayoutMobileBreakPoint,
} from "selectors/editorSelectors";
import { getNewPositions } from "sagas/PasteWidgetUtils";
import { executeWidgetBlueprintBeforeOperations } from "sagas/WidgetBlueprintSagas";
import { BlueprintOperationTypes } from "WidgetProvider/constants";
import { cloneDeep } from "lodash";
import {
copiedWidgets,
leftMostWidget,
topMostWidget,
} from "./pasteWidgetAddition.fixture";
import type { NewPastePositionVariables } from "sagas/WidgetOperationUtils";
// Mock data for testing
const gridPosition = { top: 50, left: 500 };
const parentWidgetId = "parentWidgetId";
const totalWidth = 31;
const flexLayers: FlexLayer[] = [];
type GeneratorType = Generator<
| CallEffect<NewPastePositionVariables>
| SelectEffect
| Promise<any>
| CallEffect<void>
| AllEffect<any>
| PutEffect<any>,
void,
unknown
>;
describe("pasteBuildingBlockWidgetsSaga", () => {
const copiedWidgetsResponse = { widgets: copiedWidgets, flexLayers };
it("should handle pasting into a valid parent widget", () => {
const generator: GeneratorType = pasteBuildingBlockWidgetsSaga(
gridPosition,
parentWidgetId,
);
// Step 1: call getCopiedWidgets()
let result = generator.next();
expect(result.value).toEqual(getCopiedWidgets());
// Step 2: select getWidgets
result = generator.next(copiedWidgetsResponse);
expect(result.value).toEqual(select(getWidgets));
// Step 3: select getIsAutoLayoutMobileBreakPoint
const initialCanvasWidgets = {}; // Mock initial canvas widgets
result = generator.next(initialCanvasWidgets);
expect(result.value).toEqual(select(getIsAutoLayoutMobileBreakPoint));
// Step 4: select getCanvasWidth
result = generator.next(false); // Assume it's not the mobile breakpoint
expect(result.value).toEqual(select(getCanvasWidth));
// Mock data for the rest of the saga generator
const mainCanvasWidth = 1200;
const boundaryWidgets = {
leftMostWidget,
topMostWidget,
totalWidth,
};
const newPastingPositionMap = {
ppci5prygm: {
id: "ppci5prygm",
top: 1,
left: 10,
bottom: 65,
right: 41,
type: "CONTAINER_WIDGET",
},
};
const reflowedMovementMap = undefined;
const gridProps = {
parentColumnSpace: 12.828080177307129,
parentRowSpace: 10,
maxGridColumns: 64,
};
// Step 5: compute getNewPositions
result = generator.next(mainCanvasWidth);
expect(result.value).toEqual(
call(
getNewPositions,
copiedWidgets,
boundaryWidgets.totalWidth,
boundaryWidgets.topMostWidget.topRow,
boundaryWidgets.leftMostWidget.leftColumn,
{ gridPosition },
),
);
// Step 6: execute blueprint operations before paste
for (const widgetGroup of copiedWidgetsResponse.widgets) {
result = generator.next({
gridProps,
newPastingPositionMap,
reflowedMovementMap,
});
expect(result.value).toEqual(
call(
executeWidgetBlueprintBeforeOperations,
BlueprintOperationTypes.BEFORE_PASTE,
{
parentId: parentWidgetId,
widgetId: widgetGroup.widgetId,
widgets: initialCanvasWidgets,
widgetType: widgetGroup.list[0].type,
},
),
);
}
const newWidgetList = cloneDeep(copiedWidgets[0].list);
// Step 7: mock the entire copied widget handling logic
for (let i = 0; i < newWidgetList.length; i++) {
result = generator.next({ pageId: "pageId" });
}
result = generator.next();
expect(result.done).toBe(true);
});
it("should handle errors gracefully", () => {
const generator: GeneratorType = pasteBuildingBlockWidgetsSaga(
{ left: 0, top: 0 },
"testParentId",
);
generator.next();
// Introduce an error by throwing one manually
const error = new Error("Something went wrong");
try {
generator.throw(error);
} catch (err) {
expect(err).toBe(error);
}
});
});