chore: add unit test suites for partial export modal (#30260)

## 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**
`  <WidgetsExport />
`    
    ✓ renders the component with correct props
    ✓ handles "Select All" checkbox click
    ✓ handles "Select All" and widget click
    ✓ handles widget checkbox click

`  <JSObjectsNQueriesExport />`
    ✓ renders the component with correct props
    ✓ toggles the query list when datasource is clicked
    
`  <EntityCheckboxSelector />`
    ✓ renders the component with correct props
    ✓ checks the selected checkbox
    ✓ handles checkbox click
    ✓ handles checkbox uncheck

**Main Component Suite**
`  <PartialExportModal />`
    ✓ 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


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

## 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.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Jacques Ikot 2024-01-12 09:57:41 +01:00 committed by GitHub
parent 891d766030
commit 4bd46aee2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 21095 additions and 3 deletions

View File

@ -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("<EntityCheckboxSelector />", () => {
const baseProps = {
entities: [
{ id: "1", name: "Entity1" },
{ id: "2", name: "Entity2" },
],
onEntityChecked: jest.fn(),
selectedIds: ["1"],
};
it("renders the component with correct props", () => {
render(<EntityCheckboxSelector {...baseProps} />);
expect(screen.getByText("Entity1")).toBeInTheDocument();
expect(screen.getByText("Entity2")).toBeInTheDocument();
});
it("checks the selected checkbox", () => {
render(<EntityCheckboxSelector {...baseProps} />);
const entityCheckbox = screen.getByLabelText("Entity1");
expect(entityCheckbox).toBeChecked();
});
it("handles checkbox click", () => {
render(<EntityCheckboxSelector {...baseProps} />);
const entityCheckbox = screen.getByLabelText("Entity2");
fireEvent.click(entityCheckbox);
expect(baseProps.onEntityChecked).toHaveBeenCalledWith("2", true);
});
it("handles checkbox uncheck", () => {
render(<EntityCheckboxSelector {...baseProps} />);
const entityCheckbox = screen.getByLabelText("Entity1");
fireEvent.click(entityCheckbox);
expect(baseProps.onEntityChecked).toHaveBeenCalledWith("1", false);
});
});

View File

@ -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 = () => (
<ThemeProvider theme={lightTheme}>
<JSObjectsNQueriesExport {...baseProps} />
</ThemeProvider>
);
describe("<JSObjectsNQueriesExport />", () => {
it("renders the component with correct props", () => {
render(<BaseComponentRender />);
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(<BaseComponentRender />);
const moviesDatasource = screen.getByText("Movies");
fireEvent.click(moviesDatasource);
expect(screen.getByText("Query1")).not.toBeVisible();
fireEvent.click(moviesDatasource);
expect(screen.getByText("Query1")).toBeVisible();
});
});

View File

@ -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: () => <div />,
}));
describe("<PartialExportModal />", () => {
let store: any;
beforeEach(() => {
store = mockStore(defaultAppState);
});
const BaseComponentRender = () => (
<Provider store={store}>
<ThemeProvider theme={lightTheme}>
<PartialExportModal handleModalClose={() => jest.fn()} isModalOpen />
</ThemeProvider>
</Provider>
);
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(<BaseComponentRender />);
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(<BaseComponentRender />);
testEntityReset({
entityTitle: createMessage(
PARTIAL_IMPORT_EXPORT.export.sections.jsObjects,
),
entityItemTitle: "JSObject1",
});
});
it("resets Databases after an item has been clicked", () => {
render(<BaseComponentRender />);
testEntityReset({
entityTitle: createMessage(
PARTIAL_IMPORT_EXPORT.export.sections.databases,
),
entityItemTitle: "Movies",
});
});
it("resets Queries after an item has been clicked", () => {
render(<BaseComponentRender />);
testEntityReset({
entityTitle: createMessage(PARTIAL_IMPORT_EXPORT.export.sections.queries),
entityItemTitle: "Query1",
});
});
it("resets Custom libraries after an item has been clicked", () => {
render(<BaseComponentRender />);
testEntityReset({
entityTitle: createMessage(
PARTIAL_IMPORT_EXPORT.export.sections.customLibs,
),
entityItemTitle: "jspdf",
});
});
it("resets Widgets after an item has been clicked", () => {
render(<BaseComponentRender />);
testEntityReset({
entityTitle: createMessage(PARTIAL_IMPORT_EXPORT.export.sections.widgets),
entityItemTitle: "txt_userFullName",
});
});
it("triggers onExportClick with correct action and payload", async () => {
render(<BaseComponentRender />);
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: [],
},
},
]),
);
});
});
});

View File

@ -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: () => <div />,
}));
describe("<WidgetsExport />", () => {
const baseProps = {
selectAllchecked: false,
selectedWidgetIds: [],
updateSelectAllChecked: jest.fn(),
updateSelectedWidgets: jest.fn(),
};
const BaseComponentRender = () => (
<ThemeProvider theme={lightTheme}>
<WidgetsExport {...baseProps} widgets={mockWidgetsProps} />
</ThemeProvider>
);
it("renders the component with correct props", () => {
render(<BaseComponentRender />);
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(<BaseComponentRender />);
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(<BaseComponentRender />);
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(<WidgetsExport {...baseProps} widgets={mockWidgetsProps} />);
const widgetCheckbox = screen.getByTestId(
`t--partial-export-modal-widget-select-${mockTblUserInfoWidgetId}`,
);
fireEvent.click(widgetCheckbox);
expect(baseProps.updateSelectedWidgets).toHaveBeenCalledWith([
mockTblUserInfoWidgetId,
]);
});
});

View File

@ -80,9 +80,13 @@ function WidgetSelector({
) {
const isSelected = selectedWidgetIds.includes(widget.widgetId);
return (
<div style={{ marginLeft: level > 0 ? level + 8 : level }}>
<div
key={widget.widgetId}
style={{ marginLeft: level > 0 ? level + 8 : level }}
>
<CheckboxContainer>
<Checkbox
data-testid={`t--partial-export-modal-widget-select-${widget.widgetId}`}
isDisabled={isParentSelected}
isSelected={isSelected}
onChange={(checked) => toggleNode(widget, checked)}
@ -113,6 +117,7 @@ function WidgetSelector({
<CheckboxWrapper>
<Checkbox
className="mb-4"
data-testid="t--partial-export-modal-widget-select-all"
isSelected={selectAllchecked}
onChange={handleSelectAllClick}
>

View File

@ -273,7 +273,7 @@ const PartiaExportModel = ({ handleModalClose, isModalOpen }: Props) => {
<ScrollableSection>
{entities.map(
({ content, icon, onResetClick, shouldShowReset, title }) => (
<>
<React.Fragment key={title}>
<Collapsible className="mt-4" key={title}>
<CollapsibleHeader>
<div className="w-full flex justify-between">
@ -291,6 +291,7 @@ const PartiaExportModel = ({ handleModalClose, isModalOpen }: Props) => {
{shouldShowReset && (
<Button
className="mr-2"
data-testid={`t--partial-export-modal-reset-${title}`}
endIcon="restart-line"
kind="tertiary"
onClick={onResetClick}
@ -304,12 +305,13 @@ const PartiaExportModel = ({ handleModalClose, isModalOpen }: Props) => {
<CollapsibleContent>{content}</CollapsibleContent>
</Collapsible>
<Bar />
</>
</React.Fragment>
),
)}
</ScrollableSection>
<ModalFooter>
<Button
data-testid="t-partial-export-entities-btn"
isDisabled={disableExportCTA}
isLoading={partialImportExportLoadingState.isExporting}
onClick={onExportClick}