diff --git a/app/client/src/sagas/BuildingBlockSagas/tests/AddAndMoveBuildingBlockToCanvasSaga.test.ts b/app/client/src/sagas/BuildingBlockSagas/tests/AddAndMoveBuildingBlockToCanvasSaga.test.ts new file mode 100644 index 0000000000..2b94e7d1e3 --- /dev/null +++ b/app/client/src/sagas/BuildingBlockSagas/tests/AddAndMoveBuildingBlockToCanvasSaga.test.ts @@ -0,0 +1,116 @@ +import { getCurrentWorkspaceId } from "@appsmith/selectors/selectedWorkspaceSelectors"; +import type { DragDetails } from "reducers/uiReducers/dragResizeReducer"; +import type { CallEffect, PutEffect, SelectEffect } from "redux-saga/effects"; +import { call, select } from "redux-saga/effects"; +import { addWidgetAndMoveWidgetsSaga } from "sagas/CanvasSagas/DraggingCanvasSagas"; +import { getDragDetails, getWidgetByName } from "sagas/selectors"; +import { getCurrentApplicationId } from "selectors/editorSelectors"; +import { initiateBuildingBlockDropEvent } from "utils/buildingBlockUtils"; +import { + addAndMoveBuildingBlockToCanvasSaga, + loadBuildingBlocksIntoApplication, +} from "../BuildingBlockAdditionSagas"; +import { actionPayload, skeletonWidget } from "./fixtures"; + +type GeneratorType = Generator< + CallEffect | SelectEffect | PutEffect, + void, + unknown +>; + +describe("addAndMoveBuildingBlockToCanvasSaga", () => { + it("1. should add a skeleton widget and move existing widgets appropriately", () => { + const generator: GeneratorType = + addAndMoveBuildingBlockToCanvasSaga(actionPayload); + + // Step 1: select getCurrentApplicationId + let result = generator.next(); + expect(result.value).toEqual(select(getCurrentApplicationId)); + + // Mock return value of getCurrentApplicationId + const applicationId = "app1"; + result = generator.next(applicationId); + expect(result.value).toEqual(select(getCurrentWorkspaceId)); + + // Step 2: select getCurrentWorkspaceId + const workspaceId = "workspace1"; + result = generator.next(workspaceId); + expect(result.value).toEqual(select(getDragDetails)); + + // Step 3: select getDragDetails + const dragDetails: DragDetails = { + newWidget: { + displayName: "TestWidget", + }, + }; + result = generator.next(dragDetails); + + // Generating the skeletonWidgetName + const buildingblockName = dragDetails.newWidget.displayName; + const skeletonWidgetName = `loading_${buildingblockName.toLowerCase().replace(/ /g, "_")}`; + + const updatedActionPayload = { + ...actionPayload, + payload: { + ...actionPayload.payload, + shouldReplay: false, + newWidget: { + ...actionPayload.payload.newWidget, + type: "SKELETON_WIDGET", + widgetName: skeletonWidgetName, + }, + }, + }; + + // Step 4: call addWidgetAndMoveWidgetsSaga + expect(result.value).toEqual( + call(addWidgetAndMoveWidgetsSaga, updatedActionPayload), + ); + + // Step 5: call initiateBuildingBlockDropEvent + result = generator.next(); + expect(result.value).toEqual( + call(initiateBuildingBlockDropEvent, { + applicationId, + workspaceId, + buildingblockName, + }), + ); + + // Step 6: select getWidgetByName + result = generator.next(); + expect(result.value).toEqual(select(getWidgetByName, skeletonWidgetName)); + + result = generator.next(skeletonWidget); + + // Step 7: call loadBuildingBlocksIntoApplication + expect(result.value).toEqual( + call( + loadBuildingBlocksIntoApplication, + { + ...actionPayload.payload.newWidget, + widgetId: actionPayload.payload.canvasId, + }, + skeletonWidget.widgetId, + ), + ); + + // Complete the generator + result = generator.next(); + expect(result.done).toBe(true); + }); + + it("2. should handle errors gracefully", () => { + const generator: GeneratorType = + addAndMoveBuildingBlockToCanvasSaga(actionPayload); + + 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); + } + }); +}); diff --git a/app/client/src/sagas/BuildingBlockSagas/tests/AddBuildingBlockToCanvasSaga.test.ts b/app/client/src/sagas/BuildingBlockSagas/tests/AddBuildingBlockToCanvasSaga.test.ts new file mode 100644 index 0000000000..bae92dc145 --- /dev/null +++ b/app/client/src/sagas/BuildingBlockSagas/tests/AddBuildingBlockToCanvasSaga.test.ts @@ -0,0 +1,111 @@ +import type { CallEffect, PutEffect, SelectEffect } from "redux-saga/effects"; +import { call, select } from "redux-saga/effects"; +import { + addBuildingBlockToCanvasSaga, + loadBuildingBlocksIntoApplication, +} from "../BuildingBlockAdditionSagas"; +import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants"; +import { getCurrentWorkspaceId } from "@appsmith/selectors/selectedWorkspaceSelectors"; +import type { WidgetAddChild } from "actions/pageActions"; +import { addChildSaga } from "sagas/WidgetAdditionSagas"; +import { getDragDetails, getWidgetByName } from "sagas/selectors"; +import { getCurrentApplicationId } from "selectors/editorSelectors"; +import { initiateBuildingBlockDropEvent } from "utils/buildingBlockUtils"; +import { addEntityAction, skeletonWidget } from "./fixtures"; + +type GeneratorType = Generator< + CallEffect | SelectEffect | PutEffect, + void, + unknown +>; + +describe("addBuildingBlockToCanvasSaga", () => { + it("1. should add a skeleton widget and initiate a building block drop", () => { + const generator: GeneratorType = + addBuildingBlockToCanvasSaga(addEntityAction); + + // Step 1: select getCurrentApplicationId + let result = generator.next(); + expect(result.value).toEqual(select(getCurrentApplicationId)); + + // Mock return value of getCurrentApplicationId + const applicationId = "app1"; + result = generator.next(applicationId); + expect(result.value).toEqual(select(getCurrentWorkspaceId)); + + // Step 2: select getCurrentWorkspaceId + const workspaceId = "workspace1"; + result = generator.next(workspaceId); + expect(result.value).toEqual(select(getDragDetails)); + + // Step 3: select getDragDetails + const dragDetails = { + newWidget: { + displayName: "TestWidget", + }, + }; + result = generator.next(dragDetails); + + // Generating the skeletonWidgetName + const buildingblockName = dragDetails.newWidget.displayName; + const skeletonWidgetName = `loading_${buildingblockName.toLowerCase().replace(/ /g, "_")}`; + + const addSkeletonWidgetAction: ReduxAction< + WidgetAddChild & { shouldReplay: boolean } + > = { + ...addEntityAction, + payload: { + ...addEntityAction.payload, + type: "SKELETON_WIDGET", + widgetName: skeletonWidgetName, + shouldReplay: false, + }, + }; + + // Step 4: call initiateBuildingBlockDropEvent + expect(result.value).toEqual( + call(initiateBuildingBlockDropEvent, { + applicationId, + workspaceId, + buildingblockName, + }), + ); + + // Step 5: call addChildSaga + result = generator.next(); + expect(result.value).toEqual(call(addChildSaga, addSkeletonWidgetAction)); + + // Step 6: select getWidgetByName + result = generator.next(); + expect(result.value).toEqual(select(getWidgetByName, skeletonWidgetName)); + + result = generator.next(skeletonWidget); + + // Step 7: call loadBuildingBlocksIntoApplication + expect(result.value).toEqual( + call( + loadBuildingBlocksIntoApplication, + addEntityAction.payload, + skeletonWidget.widgetId, + ), + ); + + // Complete the generator + result = generator.next(); + expect(result.done).toBe(true); + }); + + it("2. should handle errors gracefully", () => { + const generator: GeneratorType = + addBuildingBlockToCanvasSaga(addEntityAction); + + 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); + } + }); +}); diff --git a/app/client/src/sagas/BuildingBlockSagas/PasteBuildingBlockWidgetSaga.test.ts b/app/client/src/sagas/BuildingBlockSagas/tests/PasteBuildingBlockWidgetSaga.test.ts similarity index 97% rename from app/client/src/sagas/BuildingBlockSagas/PasteBuildingBlockWidgetSaga.test.ts rename to app/client/src/sagas/BuildingBlockSagas/tests/PasteBuildingBlockWidgetSaga.test.ts index 2dc8ba3cba..8f747e22ea 100644 --- a/app/client/src/sagas/BuildingBlockSagas/PasteBuildingBlockWidgetSaga.test.ts +++ b/app/client/src/sagas/BuildingBlockSagas/tests/PasteBuildingBlockWidgetSaga.test.ts @@ -5,7 +5,7 @@ import type { SelectEffect, } from "redux-saga/effects"; import { call, select } from "redux-saga/effects"; -import { pasteBuildingBlockWidgetsSaga } from "./BuildingBlockAdditionSagas"; +import { pasteBuildingBlockWidgetsSaga } from "../BuildingBlockAdditionSagas"; import { getCopiedWidgets } from "utils/storage"; import type { FlexLayer } from "layoutSystems/autolayout/utils/types"; import { getWidgets } from "sagas/selectors"; @@ -21,7 +21,7 @@ import { copiedWidgets, leftMostWidget, topMostWidget, -} from "./pasteWidgetAddition.fixture"; +} from "../pasteWidgetAddition.fixture"; import type { NewPastePositionVariables } from "sagas/WidgetOperationUtils"; // Mock data for testing diff --git a/app/client/src/sagas/BuildingBlockSagas/tests/fixtures.ts b/app/client/src/sagas/BuildingBlockSagas/tests/fixtures.ts new file mode 100644 index 0000000000..59f838f523 --- /dev/null +++ b/app/client/src/sagas/BuildingBlockSagas/tests/fixtures.ts @@ -0,0 +1,103 @@ +import type { WidgetAddChild } from "actions/pageActions"; +import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants"; +import type { WidgetDraggingUpdateParams } from "layoutSystems/common/canvasArenas/ArenaTypes"; +import type { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer"; + +// Mock return value of getWidgetByName +export const skeletonWidget: FlattenedWidgetProps = { + needsErrorInfo: false, + mobileBottomRow: 69, + widgetName: "loading_table_lookup", + displayName: "Skeleton", + topRow: 6, + bottomRow: 69, + parentRowSpace: 10, + type: "SKELETON_WIDGET", + hideCard: true, + mobileRightColumn: 46, + parentColumnSpace: 13.40625, + leftColumn: 15, + dynamicBindingPathList: [], + key: "k0u7iidinm", + isDeprecated: false, + rightColumn: 46, + widgetId: "ndw2y4zajv", + onCanvasUI: { + selectionBGCSSVar: "--on-canvas-ui-widget-selection", + focusBGCSSVar: "--on-canvas-ui-widget-focus", + selectionColorCSSVar: "--on-canvas-ui-widget-focus", + focusColorCSSVar: "--on-canvas-ui-widget-selection", + disableParentSelection: false, + }, + isVisible: true, + version: 1, + parentId: "0", + isLoading: false, + renderMode: "CANVAS", + mobileTopRow: 6, + mobileLeftColumn: 15, +}; + +export const actionPayload: ReduxAction<{ + newWidget: WidgetAddChild; + draggedBlocksToUpdate: WidgetDraggingUpdateParams[]; + canvasId: string; +}> = { + type: "WIDGETS_ADD_CHILD_AND_MOVE", + payload: { + newWidget: { + type: "BUILDING_BLOCK", + leftColumn: 21, + topRow: 147, + columns: 31, + rows: 63, + parentRowSpace: 10, + parentColumnSpace: 13.40625, + newWidgetId: "9lg3rb7mi2", + widgetId: "0", + tabId: "0", + }, + draggedBlocksToUpdate: [ + { + left: 388.78125, + top: 1430, + width: 214.5, + height: 40, + columnWidth: 13.40625, + rowHeight: 10, + widgetId: "6b6kauwlxa", + isNotColliding: true, + type: "BUTTON_WIDGET", + updateWidgetParams: { + operation: "MOVE", + widgetId: "6b6kauwlxa", + payload: { + leftColumn: 29, + topRow: 143, + bottomRow: 147, + rightColumn: 45, + parentId: "0", + newParentId: "0", + }, + }, + }, + ], + canvasId: "0", + }, +}; + +export const addEntityAction: ReduxAction = { + type: "WIDGET_ADD_CHILD", + payload: { + widgetId: "0", + type: "BUILDING_BLOCK", + leftColumn: 15, + topRow: 6, + columns: 31, + rows: 63, + parentRowSpace: 10, + parentColumnSpace: 13.40625, + newWidgetId: "ndw2y4zajv", + tabId: "0", + }, +};