From 4bd46aee2b45bbed9cdc5cae3051d4c3006e5703 Mon Sep 17 00:00:00 2001 From: Jacques Ikot Date: Fri, 12 Jan 2024 09:57:41 +0100 Subject: [PATCH] chore: add unit test suites for partial export modal (#30260) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Added unit tests for the `EntityCheckboxSelector`, `JSObjectsNQueriesExport`, `PartialExportModal`, and `WidgetsExport` components to ensure proper rendering and interaction handling. Refactor Updated the WidgetsExport and `PartialExportModal` components with additional props for improved testability. **Sub component test suites** ` ` ✓ renders the component with correct props ✓ handles "Select All" checkbox click ✓ handles "Select All" and widget click ✓ handles widget checkbox click ` ` ✓ renders the component with correct props ✓ toggles the query list when datasource is clicked ` ` ✓ renders the component with correct props ✓ checks the selected checkbox ✓ handles checkbox click ✓ handles checkbox uncheck **Main Component Suite** ` ` ✓ renders the component with correct props ✓ resets JS objects after an item has been clicked ✓ resets Databases after an item has been clicked ✓ resets Queries after an item has been clicked ✓ resets Custom libraries after an item has been clicked ✓ resets Widgets after an item has been clicked ✓ triggers onExportClick with correct action and payload #### PR fixes following issue(s) Fixes #30028 #### Type of change > Please delete options that are not relevant. - New feature (non-breaking change which adds functionality) ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [ ] Manual - [ ] JUnit - [x] Jest - [ ] Cypress #### Test Plan > Add Testsmith test cases links that relate to this PR > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed ## Summary by CodeRabbit - **Tests** - Added unit tests for the `EntityCheckboxSelector`, `JSObjectsNQueriesExport`, `PartialExportModal`, and `WidgetsExport` components to ensure proper rendering and interaction handling. - **Refactor** - Updated the `WidgetsExport` and `PartialExportModal` components with additional props for improved testability. --- .../EntityCheckboxSelector.test.tsx | 41 + .../JSObjectsNQueriesExport.test.tsx | 52 + .../PartialExportModal.test.tsx | 183 + .../PartialExportModal/WidgetsExport.test.tsx | 81 + .../PartialExportModal/WidgetsExport.tsx | 7 +- .../PartialExportModal/index.tsx | 6 +- .../PartialExportModal/unitTestUtils.ts | 20728 ++++++++++++++++ 7 files changed, 21095 insertions(+), 3 deletions(-) create mode 100644 app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/EntityCheckboxSelector.test.tsx create mode 100644 app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/JSObjectsNQueriesExport.test.tsx create mode 100644 app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/PartialExportModal.test.tsx create mode 100644 app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/WidgetsExport.test.tsx create mode 100644 app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/unitTestUtils.ts diff --git a/app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/EntityCheckboxSelector.test.tsx b/app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/EntityCheckboxSelector.test.tsx new file mode 100644 index 0000000000..62245b85da --- /dev/null +++ b/app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/EntityCheckboxSelector.test.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import { render, screen, fireEvent } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import EntityCheckboxSelector from "./EntityCheckboxSelector"; + +describe("", () => { + const baseProps = { + entities: [ + { id: "1", name: "Entity1" }, + { id: "2", name: "Entity2" }, + ], + onEntityChecked: jest.fn(), + selectedIds: ["1"], + }; + + it("renders the component with correct props", () => { + render(); + expect(screen.getByText("Entity1")).toBeInTheDocument(); + expect(screen.getByText("Entity2")).toBeInTheDocument(); + }); + + it("checks the selected checkbox", () => { + render(); + const entityCheckbox = screen.getByLabelText("Entity1"); + expect(entityCheckbox).toBeChecked(); + }); + + it("handles checkbox click", () => { + render(); + const entityCheckbox = screen.getByLabelText("Entity2"); + fireEvent.click(entityCheckbox); + expect(baseProps.onEntityChecked).toHaveBeenCalledWith("2", true); + }); + + it("handles checkbox uncheck", () => { + render(); + const entityCheckbox = screen.getByLabelText("Entity1"); + fireEvent.click(entityCheckbox); + expect(baseProps.onEntityChecked).toHaveBeenCalledWith("1", false); + }); +}); diff --git a/app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/JSObjectsNQueriesExport.test.tsx b/app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/JSObjectsNQueriesExport.test.tsx new file mode 100644 index 0000000000..1a29163ce1 --- /dev/null +++ b/app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/JSObjectsNQueriesExport.test.tsx @@ -0,0 +1,52 @@ +import React from "react"; +import { render, screen, fireEvent } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import JSObjectsNQueriesExport from "./JSObjectsNQueriesExport"; +import { lightTheme } from "selectors/themeSelectors"; +import { mockAppDSProps, mockDataBaseProps } from "./unitTestUtils"; +import { ThemeProvider } from "styled-components"; + +jest.mock("react-redux", () => { + const originalModule = jest.requireActual("react-redux"); + return { + ...originalModule, + useDispatch: () => jest.fn(), + useSelector: () => jest.fn(), + }; +}); + +const baseProps = { + appDS: mockAppDSProps, + data: mockDataBaseProps, + selectedQueries: [], + updateSelectedQueries: jest.fn(), +}; + +const BaseComponentRender = () => ( + + + +); + +describe("", () => { + it("renders the component with correct props", () => { + render(); + const moviesDatabase = screen.getByText("Movies"); + expect(moviesDatabase).toBeInTheDocument(); + const usersDatabase = screen.getByText("users"); + expect(usersDatabase).toBeInTheDocument(); + const moviesQuery = screen.getByText("Query1"); + expect(moviesQuery).toBeInTheDocument(); + const usersQuery = screen.getByText("getUsers"); + expect(usersQuery).toBeInTheDocument(); + }); + + it("toggles the query list when datasource is clicked", () => { + render(); + const moviesDatasource = screen.getByText("Movies"); + fireEvent.click(moviesDatasource); + expect(screen.getByText("Query1")).not.toBeVisible(); + fireEvent.click(moviesDatasource); + expect(screen.getByText("Query1")).toBeVisible(); + }); +}); diff --git a/app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/PartialExportModal.test.tsx b/app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/PartialExportModal.test.tsx new file mode 100644 index 0000000000..9f2efa6599 --- /dev/null +++ b/app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/PartialExportModal.test.tsx @@ -0,0 +1,183 @@ +import React from "react"; +import { render, screen, fireEvent, waitFor } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import PartialExportModal from "./index"; +import { lightTheme } from "selectors/themeSelectors"; +import { ThemeProvider } from "styled-components"; +import configureStore from "redux-mock-store"; +import { Provider } from "react-redux"; +import { defaultAppState } from "./unitTestUtils"; +import { + PARTIAL_IMPORT_EXPORT, + createMessage, +} from "@appsmith/constants/messages"; +import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; + +interface TestEntityResetProps { + entityTitle: string; + entityItemTitle: string; +} + +const mockStore = configureStore([]); + +jest.mock("pages/Editor/Explorer/Widgets/WidgetIcon", () => ({ + __esModule: true, + default: () =>
, +})); + +describe("", () => { + let store: any; + + beforeEach(() => { + store = mockStore(defaultAppState); + }); + + const BaseComponentRender = () => ( + + + jest.fn()} isModalOpen /> + + + ); + + const testEntityReset = (props: TestEntityResetProps) => { + const entityResetTestId = `t--partial-export-modal-reset-${props.entityTitle}`; + const entity = screen.getByText(props.entityTitle); + const entityItem = screen.getByLabelText(props.entityItemTitle); + const exportButton = screen.getByTestId("t-partial-export-entities-btn"); + + expect(screen.queryByTestId(entityResetTestId)).not.toBeInTheDocument(); + fireEvent.click(entity); + fireEvent.click(entityItem); + expect(screen.getByTestId(entityResetTestId)).toBeInTheDocument(); + expect(exportButton).not.toBeDisabled(); + fireEvent.click(screen.getByTestId(entityResetTestId)); + expect(screen.getByLabelText(props.entityItemTitle)).not.toBeChecked(); + expect(screen.queryByTestId(entityResetTestId)).not.toBeInTheDocument(); + expect(exportButton).toBeDisabled(); + }; + + it("renders the component with correct props", () => { + render(); + const pageList = defaultAppState.entities.pageList; + const currentPageName = pageList.pages.find( + (page: any) => page.pageId === pageList.currentPageId, + )?.pageName; + expect(screen.getByText(`Export - ${currentPageName}`)).toBeInTheDocument(); + expect( + screen.getByText( + createMessage(PARTIAL_IMPORT_EXPORT.export.modalSubHeading), + ), + ).toBeInTheDocument(); + expect( + screen.getByText( + createMessage(PARTIAL_IMPORT_EXPORT.export.sections.jsObjects), + ), + ).toBeInTheDocument(); + expect( + screen.getByText( + createMessage(PARTIAL_IMPORT_EXPORT.export.sections.databases), + ), + ).toBeInTheDocument(); + expect( + screen.getByText( + createMessage(PARTIAL_IMPORT_EXPORT.export.sections.queries), + ), + ).toBeInTheDocument(); + expect( + screen.getByText( + createMessage(PARTIAL_IMPORT_EXPORT.export.sections.customLibs), + ), + ).toBeInTheDocument(); + expect( + screen.getByText( + createMessage(PARTIAL_IMPORT_EXPORT.export.sections.widgets), + ), + ).toBeInTheDocument(); + expect; + expect( + screen.getByText(createMessage(PARTIAL_IMPORT_EXPORT.export.cta)), + ).toBeInTheDocument(); + expect(screen.getByTestId("t-partial-export-entities-btn")).toBeDisabled(); + }); + + it("resets JS objects after an item has been clicked", () => { + render(); + testEntityReset({ + entityTitle: createMessage( + PARTIAL_IMPORT_EXPORT.export.sections.jsObjects, + ), + entityItemTitle: "JSObject1", + }); + }); + + it("resets Databases after an item has been clicked", () => { + render(); + testEntityReset({ + entityTitle: createMessage( + PARTIAL_IMPORT_EXPORT.export.sections.databases, + ), + entityItemTitle: "Movies", + }); + }); + + it("resets Queries after an item has been clicked", () => { + render(); + testEntityReset({ + entityTitle: createMessage(PARTIAL_IMPORT_EXPORT.export.sections.queries), + entityItemTitle: "Query1", + }); + }); + + it("resets Custom libraries after an item has been clicked", () => { + render(); + testEntityReset({ + entityTitle: createMessage( + PARTIAL_IMPORT_EXPORT.export.sections.customLibs, + ), + entityItemTitle: "jspdf", + }); + }); + + it("resets Widgets after an item has been clicked", () => { + render(); + testEntityReset({ + entityTitle: createMessage(PARTIAL_IMPORT_EXPORT.export.sections.widgets), + entityItemTitle: "txt_userFullName", + }); + }); + + it("triggers onExportClick with correct action and payload", async () => { + render(); + const exportButton = screen.getByTestId("t-partial-export-entities-btn"); + const jsObjectsEntity = screen.getByText( + createMessage(PARTIAL_IMPORT_EXPORT.export.sections.jsObjects), + ); + const jsObjectsEntityItem = screen.getByLabelText( + defaultAppState.entities.jsActions[0].config.name, + ); + const jsObjectsEntityItemId = + defaultAppState.entities.jsActions[0].config.id; + + fireEvent.click(jsObjectsEntity); + fireEvent.click(jsObjectsEntityItem); + fireEvent.click(exportButton); + + await waitFor(() => { + expect(store.getActions()).toEqual( + expect.arrayContaining([ + { + type: ReduxActionTypes.PARTIAL_EXPORT_INIT, + payload: { + jsObjects: [jsObjectsEntityItemId], + datasources: [], + customJSLibs: [], + widgets: [], + queries: [], + }, + }, + ]), + ); + }); + }); +}); diff --git a/app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/WidgetsExport.test.tsx b/app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/WidgetsExport.test.tsx new file mode 100644 index 0000000000..25b0035b09 --- /dev/null +++ b/app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/WidgetsExport.test.tsx @@ -0,0 +1,81 @@ +import React from "react"; +import { render, screen, fireEvent } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import WidgetsExport from "./WidgetsExport"; +import { ThemeProvider } from "styled-components"; +import { lightTheme } from "selectors/themeSelectors"; +import { + getAllWidgetIds, + mockTblUserInfoWidgetId, + mockWidgetsProps, +} from "./unitTestUtils"; + +jest.mock("pages/Editor/Explorer/Widgets/WidgetIcon", () => ({ + __esModule: true, + default: () =>
, +})); + +describe("", () => { + const baseProps = { + selectAllchecked: false, + selectedWidgetIds: [], + updateSelectAllChecked: jest.fn(), + updateSelectedWidgets: jest.fn(), + }; + + const BaseComponentRender = () => ( + + + + ); + + it("renders the component with correct props", () => { + render(); + expect(screen.getByText("Select All")).toBeInTheDocument(); + expect(screen.getByText("tbl_userInfo")).toBeInTheDocument(); + expect(screen.getByText("txt_pageTitle")).toBeInTheDocument(); + }); + + it('handles "Select All" checkbox click', () => { + render(); + const widgetIds = getAllWidgetIds(mockWidgetsProps); + const selectAllCheckbox = screen.getByTestId( + "t--partial-export-modal-widget-select-all", + ); + fireEvent.click(selectAllCheckbox!); + expect(baseProps.updateSelectAllChecked).toHaveBeenCalledWith(true); + expect(baseProps.updateSelectedWidgets).toHaveBeenCalledWith( + widgetIds.slice(1), + ); + }); + + it('handles "Select All" and widget click', () => { + render(); + const widgetIds = getAllWidgetIds(mockWidgetsProps); + const selectAllCheckbox = screen.getByTestId( + "t--partial-export-modal-widget-select-all", + ); + const widgetCheckbox = screen.getByTestId( + `t--partial-export-modal-widget-select-${mockTblUserInfoWidgetId}`, + ); + fireEvent.click(selectAllCheckbox!); + expect(baseProps.updateSelectAllChecked).toHaveBeenCalledWith(true); + expect(baseProps.updateSelectedWidgets).toHaveBeenCalledWith( + widgetIds.slice(1), + ); + fireEvent.click(widgetCheckbox); + expect(selectAllCheckbox).not.toBeChecked(); + expect(widgetCheckbox).not.toBeChecked(); + }); + + it("handles widget checkbox click", () => { + render(); + const widgetCheckbox = screen.getByTestId( + `t--partial-export-modal-widget-select-${mockTblUserInfoWidgetId}`, + ); + fireEvent.click(widgetCheckbox); + expect(baseProps.updateSelectedWidgets).toHaveBeenCalledWith([ + mockTblUserInfoWidgetId, + ]); + }); +}); diff --git a/app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/WidgetsExport.tsx b/app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/WidgetsExport.tsx index 8102ee280a..73c2127273 100644 --- a/app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/WidgetsExport.tsx +++ b/app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/WidgetsExport.tsx @@ -80,9 +80,13 @@ function WidgetSelector({ ) { const isSelected = selectedWidgetIds.includes(widget.widgetId); return ( -
0 ? level + 8 : level }}> +
0 ? level + 8 : level }} + > toggleNode(widget, checked)} @@ -113,6 +117,7 @@ function WidgetSelector({ diff --git a/app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/index.tsx b/app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/index.tsx index 37dc788647..e71dbed2fa 100644 --- a/app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/index.tsx +++ b/app/client/src/components/editorComponents/PartialImportExport/PartialExportModal/index.tsx @@ -273,7 +273,7 @@ const PartiaExportModel = ({ handleModalClose, isModalOpen }: Props) => { {entities.map( ({ content, icon, onResetClick, shouldShowReset, title }) => ( - <> +
@@ -291,6 +291,7 @@ const PartiaExportModel = ({ handleModalClose, isModalOpen }: Props) => { {shouldShowReset && (