diff --git a/app/client/package.json b/app/client/package.json index 6ede552bcf..11a438ff90 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -360,6 +360,7 @@ "redux-devtools": "^3.5.0", "redux-devtools-extension": "^2.13.8", "redux-mock-store": "^1.5.4", + "redux-saga-test-plan": "^4.0.6", "ts-jest": "27.0.0", "ts-jest-mock-import-meta": "^0.12.0", "ts-loader": "^9.4.1", diff --git a/app/client/src/ce/sagas/__tests__/PageSaga.test.ts b/app/client/src/ce/sagas/__tests__/PageSaga.test.ts new file mode 100644 index 0000000000..50a5127a6e --- /dev/null +++ b/app/client/src/ce/sagas/__tests__/PageSaga.test.ts @@ -0,0 +1,71 @@ +import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants"; +import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; +import { testSaga } from "redux-saga-test-plan"; +import { setupPageSaga, setupPublishedPageSaga } from "../PageSagas"; +import mockResponse from "./mockConsolidatedApiResponse.json"; +import type { FetchPageRequest, FetchPageResponse } from "api/PageApi"; +import { fetchPage, fetchPublishedPage } from "actions/pageActions"; + +describe("ce/PageSaga", () => { + it("should put setupPageSaga with pageWithMigratedDsl", () => { + const action: ReduxAction = { + type: ReduxActionTypes.SETUP_PAGE_INIT, + payload: { + id: "pageId", + pageWithMigratedDsl: mockResponse.data + .pageWithMigratedDsl as FetchPageResponse, + }, + }; + + testSaga(setupPageSaga, action) + .next() + .put( + fetchPage( + action.payload.id, + action.payload.isFirstLoad, + action.payload.pageWithMigratedDsl, + ), + ) + .next() + .take(ReduxActionTypes.FETCH_PAGE_SUCCESS) + .next() + .put({ type: ReduxActionTypes.SETUP_PAGE_SUCCESS }) + .next() + .isDone(); + }); + + it("should put setupPublishedPageSaga with pageWithMigratedDsl", () => { + const action: ReduxAction<{ + pageId: string; + bustCache: boolean; + firstLoad: boolean; + pageWithMigratedDsl?: FetchPageResponse; + }> = { + type: ReduxActionTypes.SETUP_PAGE_INIT, + payload: { + pageId: "pageId", + pageWithMigratedDsl: mockResponse.data + .pageWithMigratedDsl as FetchPageResponse, + bustCache: false, + firstLoad: true, + }, + }; + + testSaga(setupPublishedPageSaga, action) + .next() + .put( + fetchPublishedPage( + action.payload.pageId, + action.payload.bustCache, + action.payload.firstLoad, + action.payload.pageWithMigratedDsl, + ), + ) + .next() + .take(ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS) + .next() + .put({ type: ReduxActionTypes.SETUP_PUBLISHED_PAGE_SUCCESS }) + .next() + .isDone(); + }); +}); diff --git a/app/client/src/ce/sagas/__tests__/mockConsolidatedApiResponse.json b/app/client/src/ce/sagas/__tests__/mockConsolidatedApiResponse.json new file mode 100644 index 0000000000..2d4f531096 --- /dev/null +++ b/app/client/src/ce/sagas/__tests__/mockConsolidatedApiResponse.json @@ -0,0 +1,123 @@ +{ + "responseMeta": { + "status": 200, + "success": true + }, + "data": { + "pageWithMigratedDsl": { + "responseMeta": { + "status": 200, + "success": true + }, + "data": { + "id": "661c28791c2412092c170119", + "name": "Page1", + "slug": "page1", + "applicationId": "661c28791c2412092c170116", + "layouts": [ + { + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 4896, + "snapColumns": 64, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 380, + "containerStyle": "none", + "snapRows": 124, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 89, + "minHeight": 1292, + "dynamicTriggerPathList": [], + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "needsErrorInfo": false, + "mobileBottomRow": 12, + "widgetName": "Text1", + "displayName": "Text", + "iconSVG": "/static/media/icon.a55b701a2ae86aa2c6718ecb7b4083f0.svg", + "searchTags": ["typography", "paragraph", "label"], + "topRow": 8, + "bottomRow": 12, + "parentRowSpace": 10, + "type": "TEXT_WIDGET", + "hideCard": false, + "mobileRightColumn": 15, + "animateLoading": true, + "overflow": "NONE", + "fontFamily": "{{appsmith.theme.fontFamily.appFont}}", + "parentColumnSpace": 16.390625, + "dynamicTriggerPathList": [], + "leftColumn": 0, + "dynamicBindingPathList": [ + { + "key": "truncateButtonColor" + }, + { + "key": "fontFamily" + }, + { + "key": "borderRadius" + } + ], + "shouldTruncate": false, + "truncateButtonColor": "{{appsmith.theme.colors.primaryColor}}", + "text": "", + "key": "bz2lkn7wbc", + "isDeprecated": false, + "rightColumn": 15, + "thumbnailSVG": "/static/media/thumbnail.0c129b82c9b3e4cd4920563b289659ab.svg", + "textAlign": "LEFT", + "dynamicHeight": "AUTO_HEIGHT", + "widgetId": "xv30fhgoz3", + "minWidth": 450, + "isVisible": true, + "fontStyle": "BOLD", + "textColor": "#231F20", + "version": 1, + "parentId": "0", + "tags": ["Suggested", "Content"], + "renderMode": "CANVAS", + "isLoading": false, + "mobileTopRow": 8, + "responsiveBehavior": "fill", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "mobileLeftColumn": 0, + "maxDynamicHeight": 9000, + "fontSize": "1rem", + "minDynamicHeight": 4 + } + ] + }, + "layoutOnLoadActions": [], + "layoutOnLoadActionErrors": [], + "layoutActions": [], + "id": "661c28791c2412092c170117", + "userPermissions": [] + } + ], + "userPermissions": [ + "create:moduleInstancesInPage", + "read:pages", + "manage:pages", + "create:pageActions", + "delete:pages" + ], + "lastUpdatedTime": 1713194519, + "defaultResources": { + "applicationId": "661c28791c2412092c170116", + "pageId": "661c28791c2412092c170119" + } + }, + "errorDisplay": "" + } + }, + "errorDisplay": "" +} diff --git a/app/client/src/sagas/__tests__/initSagas.test.ts b/app/client/src/sagas/__tests__/initSagas.test.ts index aaee4d5074..548bcbe604 100644 --- a/app/client/src/sagas/__tests__/initSagas.test.ts +++ b/app/client/src/sagas/__tests__/initSagas.test.ts @@ -1,74 +1,83 @@ -import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; +import { + type ReduxAction, + ReduxActionTypes, +} from "@appsmith/constants/ReduxActionConstants"; import { APP_MODE } from "entities/App"; -import type AppEngine from "entities/Engine"; import AppEngineFactory from "entities/Engine/factory"; -import { call } from "redux-saga/effects"; import { getInitResponses } from "sagas/InitSagas"; import { startAppEngine } from "sagas/InitSagas"; +import type { AppEnginePayload } from "entities/Engine"; +import { testSaga } from "redux-saga-test-plan"; +import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions"; +import mockResponse from "./mockConsolidatedApiResponse.json"; jest.mock("../../api/Api", () => ({ __esModule: true, default: class Api {}, })); +// Mock the entire AppEngineFactory module +jest.mock("entities/Engine/factory", () => ({ + __esModule: true, + + default: class AppEngineFactory { + static create = jest.fn(); + }, +})); + describe("tests the sagas in initSagas", () => { + const action: ReduxAction = { + type: ReduxActionTypes.INITIALIZE_EDITOR, + payload: { + mode: APP_MODE.EDIT, + pageId: "pageId", + applicationId: "applicationId", + }, + }; + it("tests the order of execute in startAppEngine", () => { - const action = { - type: ReduxActionTypes.INITIALIZE_EDITOR, - payload: { - applicationId: "appId", - pageId: "pageId", - mode: APP_MODE.EDIT, - }, + const engine = { + startPerformanceTracking: jest.fn(), + setupEngine: jest.fn(), + loadAppData: jest.fn().mockResolvedValue({ + applicationId: action.payload.applicationId, + toLoadPageId: action.payload.pageId, + }), + loadAppURL: jest.fn(), + loadAppEntities: jest.fn(), + loadGit: jest.fn(), + completeChore: jest.fn(), + stopPerformanceTracking: jest.fn(), }; - const gen = startAppEngine(action); - const engine: AppEngine = AppEngineFactory.create( - APP_MODE.EDIT, - APP_MODE.EDIT, - ); - expect(JSON.stringify(gen.next().value)).toStrictEqual( - JSON.stringify(call(engine.setupEngine, action.payload)), - ); - expect(gen.next().value).toStrictEqual( - call(getInitResponses, action.payload), - ); - const someInitResponse = { - pages: { responseMeta: {}, data: {}, code: "232" }, - } as any; + (AppEngineFactory.create as jest.Mock).mockReturnValue(engine); - expect(JSON.stringify(gen.next(someInitResponse).value)).toStrictEqual( - JSON.stringify( - call(engine.loadAppData, action.payload, someInitResponse), - ), - ); - expect( - JSON.stringify( - gen.next({ - applicationId: action.payload.applicationId, - toLoadPageId: action.payload.pageId, - } as any).value, - ), - ).toStrictEqual( - JSON.stringify( - call(engine.loadAppURL, action.payload.pageId, action.payload.pageId), - ), - ); - expect(JSON.stringify(gen.next().value)).toStrictEqual( - JSON.stringify( - call( - engine.loadAppEntities, - action.payload.pageId, - action.payload.applicationId, - someInitResponse, - ), - ), - ); - expect(JSON.stringify(gen.next().value)).toStrictEqual( - JSON.stringify(call(engine.loadGit, action.payload.applicationId)), - ); - expect(JSON.stringify(gen.next().value)).toStrictEqual( - JSON.stringify(call(engine.completeChore)), - ); + testSaga(startAppEngine, action) + .next() + .call(engine.setupEngine, action.payload) + .next() + .call(getInitResponses, { ...action.payload }) + .next(mockResponse.data) + .call(engine.loadAppData, action.payload, mockResponse.data) + .next({ + applicationId: action.payload.applicationId, + toLoadPageId: action.payload.pageId, + }) + .call(engine.loadAppURL, action.payload.pageId, action.payload.pageId) + .next() + .call( + engine.loadAppEntities, + action.payload.pageId, + action.payload.applicationId, + mockResponse.data, + ) + .next() + .call(engine.loadGit, action.payload.applicationId) + .next() + .call(engine.completeChore) + .next() + .put(generateAutoHeightLayoutTreeAction(true, false)) + .next() + .isDone(); }); }); diff --git a/app/client/src/sagas/__tests__/mockConsolidatedApiResponse.json b/app/client/src/sagas/__tests__/mockConsolidatedApiResponse.json new file mode 100644 index 0000000000..f4a9cb2791 --- /dev/null +++ b/app/client/src/sagas/__tests__/mockConsolidatedApiResponse.json @@ -0,0 +1,122 @@ +{ + "responseMeta": { + "status": 200, + "success": true + }, + "data": { + "pageWithMigratedDsl": { + "responseMeta": { + "status": 200, + "success": true + }, + "data": { + "id": "661c28791c2412092c170119", + "name": "Page1", + "slug": "page1", + "applicationId": "661c28791c2412092c170116", + "layouts": [ + { + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 4896, + "snapColumns": 64, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 380, + "containerStyle": "none", + "snapRows": 124, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 89, + "minHeight": 1292, + "dynamicTriggerPathList": [], + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "needsErrorInfo": false, + "mobileBottomRow": 12, + "widgetName": "Text1", + "displayName": "Text", + "iconSVG": "/static/media/icon.a55b701a2ae86aa2c6718ecb7b4083f0.svg", + "searchTags": ["typography", "paragraph", "label"], + "topRow": 8, + "bottomRow": 12, + "parentRowSpace": 10, + "type": "TEXT_WIDGET", + "hideCard": false, + "mobileRightColumn": 15, + "animateLoading": true, + "overflow": "NONE", + "fontFamily": "{{appsmith.theme.fontFamily.appFont}}", + "parentColumnSpace": 16.390625, + "dynamicTriggerPathList": [], + "leftColumn": 0, + "dynamicBindingPathList": [ + { + "key": "truncateButtonColor" + }, + { + "key": "fontFamily" + }, + { + "key": "borderRadius" + } + ], + "shouldTruncate": false, + "truncateButtonColor": "{{appsmith.theme.colors.primaryColor}}", + "text": "", + "key": "bz2lkn7wbc", + "isDeprecated": false, + "rightColumn": 15, + "thumbnailSVG": "/static/media/thumbnail.0c129b82c9b3e4cd4920563b289659ab.svg", + "textAlign": "LEFT", + "dynamicHeight": "AUTO_HEIGHT", + "widgetId": "xv30fhgoz3", + "minWidth": 450, + "isVisible": true, + "fontStyle": "BOLD", + "textColor": "#231F20", + "version": 1, + "parentId": "0", + "tags": ["Suggested", "Content"], + "renderMode": "CANVAS", + "isLoading": false, + "mobileTopRow": 8, + "responsiveBehavior": "fill", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "mobileLeftColumn": 0, + "maxDynamicHeight": 9000, + "fontSize": "1rem", + "minDynamicHeight": 4 + } + ] + }, + "layoutOnLoadActions": [], + "layoutOnLoadActionErrors": [], + "id": "661c28791c2412092c170117", + "userPermissions": [] + } + ], + "userPermissions": [ + "create:moduleInstancesInPage", + "read:pages", + "manage:pages", + "create:pageActions", + "delete:pages" + ], + "lastUpdatedTime": 1713194519, + "defaultResources": { + "applicationId": "661c28791c2412092c170116", + "pageId": "661c28791c2412092c170119" + } + }, + "errorDisplay": "" + } + }, + "errorDisplay": "" +} diff --git a/app/client/yarn.lock b/app/client/yarn.lock index d396269dd1..e5a6408ca3 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -13395,6 +13395,7 @@ __metadata: redux-form: ^8.2.6 redux-mock-store: ^1.5.4 redux-saga: ^1.1.3 + redux-saga-test-plan: ^4.0.6 remixicon-react: ^1.0.0 reselect: ^4.0.0 sass: ^1.70.0 @@ -20055,6 +20056,13 @@ __metadata: languageName: node linkType: hard +"fsm-iterator@npm:^1.1.0": + version: 1.1.0 + resolution: "fsm-iterator@npm:1.1.0" + checksum: ca35f89a6607df2542711faac62b4b9fdd1c90887bd7fbb0ed106d0d658a92376e996bdfc6db605665e25a63c510e5ea5986d054e3836becf95696ee928b0f6b + languageName: node + linkType: hard + "fsu@npm:^1.1.1": version: 1.1.1 resolution: "fsu@npm:1.1.1" @@ -24138,6 +24146,13 @@ __metadata: languageName: node linkType: hard +"lodash.ismatch@npm:^4.4.0": + version: 4.4.0 + resolution: "lodash.ismatch@npm:4.4.0" + checksum: a393917578842705c7fc1a30fb80613d1ac42d20b67eb26a2a6004d6d61ee90b419f9eb320508ddcd608e328d91eeaa2651411727eaa9a12534ed6ccb02fc705 + languageName: node + linkType: hard + "lodash.isobject@npm:^3.0.2": version: 3.0.2 resolution: "lodash.isobject@npm:3.0.2" @@ -30086,6 +30101,21 @@ __metadata: languageName: node linkType: hard +"redux-saga-test-plan@npm:^4.0.6": + version: 4.0.6 + resolution: "redux-saga-test-plan@npm:4.0.6" + dependencies: + fsm-iterator: ^1.1.0 + lodash.isequal: ^4.5.0 + lodash.ismatch: ^4.4.0 + peerDependencies: + "@redux-saga/is": ^1.0.1 + "@redux-saga/symbols": ^1.0.1 + redux-saga: ^1.0.1 + checksum: a3220210a42a51271c0dc8740ceaacf5cd0aa84cf46b986d5839a89133de00954940e5dde80145e1b4c69d7c0d61b9578d86da8776d2c0b5151dc498bc595685 + languageName: node + linkType: hard + "redux-saga@npm:^1.1.3": version: 1.1.3 resolution: "redux-saga@npm:1.1.3"