diff --git a/src/Explorer/ContainerCopy/Actions/CopyJobActions.test.tsx b/src/Explorer/ContainerCopy/Actions/CopyJobActions.test.tsx index a1885e062..ce71ff56e 100644 --- a/src/Explorer/ContainerCopy/Actions/CopyJobActions.test.tsx +++ b/src/Explorer/ContainerCopy/Actions/CopyJobActions.test.tsx @@ -41,8 +41,10 @@ describe("CopyJobActions", () => { const mockExplorer = {} as Explorer; const mockSetPanelHasConsole = jest.fn(); const mockOpenSidePanel = jest.fn(); + const mockSetLightDismiss = jest.fn(); (useSidePanel.getState as jest.Mock).mockReturnValue({ + setLightDismiss: mockSetLightDismiss, setPanelHasConsole: mockSetPanelHasConsole, openSidePanel: mockOpenSidePanel, }); @@ -50,6 +52,7 @@ describe("CopyJobActions", () => { openCreateCopyJobPanel(mockExplorer); expect(mockSetPanelHasConsole).toHaveBeenCalledWith(false); + expect(mockSetLightDismiss).toHaveBeenCalledWith(false); expect(mockOpenSidePanel).toHaveBeenCalledWith(expect.any(String), expect.any(Object), "650px"); }); @@ -59,6 +62,7 @@ describe("CopyJobActions", () => { (useSidePanel.getState as jest.Mock).mockReturnValue({ setPanelHasConsole: jest.fn(), + setLightDismiss: jest.fn(), openSidePanel: mockOpenSidePanel, }); @@ -94,9 +98,11 @@ describe("CopyJobActions", () => { }; const mockSetPanelHasConsole = jest.fn(); + const mockSetLightDismiss = jest.fn(); const mockOpenSidePanel = jest.fn(); (useSidePanel.getState as jest.Mock).mockReturnValue({ + setLightDismiss: mockSetLightDismiss, setPanelHasConsole: mockSetPanelHasConsole, openSidePanel: mockOpenSidePanel, }); @@ -104,6 +110,7 @@ describe("CopyJobActions", () => { openCopyJobDetailsPanel(mockJob); expect(mockSetPanelHasConsole).toHaveBeenCalledWith(false); + expect(mockSetLightDismiss).toHaveBeenCalledWith(true); expect(mockOpenSidePanel).toHaveBeenCalledWith(expect.stringContaining("test-job"), expect.any(Object), "650px"); }); @@ -133,6 +140,7 @@ describe("CopyJobActions", () => { (useSidePanel.getState as jest.Mock).mockReturnValue({ setPanelHasConsole: jest.fn(), + setLightDismiss: jest.fn(), openSidePanel: mockOpenSidePanel, }); diff --git a/src/Explorer/ContainerCopy/Actions/CopyJobActions.tsx b/src/Explorer/ContainerCopy/Actions/CopyJobActions.tsx index e2e6b6fc7..713238dea 100644 --- a/src/Explorer/ContainerCopy/Actions/CopyJobActions.tsx +++ b/src/Explorer/ContainerCopy/Actions/CopyJobActions.tsx @@ -34,6 +34,7 @@ import { CopyJobContextState, CopyJobError, CopyJobErrorType, CopyJobType } from export const openCreateCopyJobPanel = (explorer: Explorer) => { const sidePanelState = useSidePanel.getState(); sidePanelState.setPanelHasConsole(false); + sidePanelState.setLightDismiss(false); sidePanelState.openSidePanel( ContainerCopyMessages.createCopyJobPanelTitle, , @@ -44,6 +45,7 @@ export const openCreateCopyJobPanel = (explorer: Explorer) => { export const openCopyJobDetailsPanel = (job: CopyJobType) => { const sidePanelState = useSidePanel.getState(); sidePanelState.setPanelHasConsole(false); + sidePanelState.setLightDismiss(true); sidePanelState.openSidePanel( ContainerCopyMessages.copyJobDetailsPanelTitle(job.Name), , diff --git a/src/Explorer/ContainerCopy/ContainerCopyMessages.ts b/src/Explorer/ContainerCopy/ContainerCopyMessages.ts index 27175de68..6950a5e7e 100644 --- a/src/Explorer/ContainerCopy/ContainerCopyMessages.ts +++ b/src/Explorer/ContainerCopy/ContainerCopyMessages.ts @@ -25,7 +25,22 @@ export default { subscriptionDropdownPlaceholder: "Select a subscription", sourceAccountDropdownLabel: "Account", sourceAccountDropdownPlaceholder: "Select an account", - migrationTypeCheckboxLabel: "Copy container in offline mode", + migrationTypeOptions: { + offline: { + title: "Offline mode", + description: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", + learnMoreText: "Learn more", + learnMoreHref: "https://learn.microsoft.com/en-us/azure/cosmos-db/container-copy?tabs=offline-copy", + }, + online: { + title: "Online mode", + description: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", + learnMoreText: "Learn more", + learnMoreHref: "https://learn.microsoft.com/en-us/azure/cosmos-db/container-copy?tabs=offline-copy", + }, + }, // Select Source and Target Containers Screen selectSourceAndTargetContainersDescription: @@ -173,5 +188,10 @@ export default { Skipped: "Cancelled", Cancelled: "Cancelled", }, + dialog: { + heading: "Confirm Action", + confirmButtonText: "OK", + cancelButtonText: "Cancel", + }, }, }; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/MigrationType.test.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/MigrationType.test.tsx new file mode 100644 index 000000000..b2b41005a --- /dev/null +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/MigrationType.test.tsx @@ -0,0 +1,238 @@ +import "@testing-library/jest-dom"; +import { fireEvent, render, screen } from "@testing-library/react"; +import React from "react"; +import ContainerCopyMessages from "../../../../ContainerCopyMessages"; +import { useCopyJobContext } from "../../../../Context/CopyJobContext"; +import { CopyJobMigrationType } from "../../../../Enums/CopyJobEnums"; +import { MigrationType } from "./MigrationType"; + +jest.mock("../../../../Context/CopyJobContext", () => ({ + useCopyJobContext: jest.fn(), +})); + +describe("MigrationType", () => { + const mockSetCopyJobState = jest.fn(); + + const defaultContextValue = { + copyJobState: { + jobName: "", + migrationType: CopyJobMigrationType.Online, + source: { + subscription: null as any, + account: null as any, + databaseId: "", + containerId: "", + }, + target: { + subscriptionId: "", + account: null as any, + databaseId: "", + containerId: "", + }, + sourceReadAccessFromTarget: false, + }, + setCopyJobState: mockSetCopyJobState, + flow: { currentScreen: "selectAccount" }, + setFlow: jest.fn(), + contextError: "", + setContextError: jest.fn(), + explorer: {} as any, + resetCopyJobState: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + (useCopyJobContext as jest.Mock).mockReturnValue(defaultContextValue); + }); + + describe("Component Rendering", () => { + it("should render migration type component with radio buttons", () => { + const { container } = render(); + + expect(screen.getByTestId("migration-type")).toBeInTheDocument(); + expect(screen.getByRole("radiogroup")).toBeInTheDocument(); + + const offlineRadio = screen.getByRole("radio", { + name: ContainerCopyMessages.migrationTypeOptions.offline.title, + }); + const onlineRadio = screen.getByRole("radio", { name: ContainerCopyMessages.migrationTypeOptions.online.title }); + + expect(offlineRadio).toBeInTheDocument(); + expect(onlineRadio).toBeInTheDocument(); + expect(container).toMatchSnapshot(); + }); + + it("should render with online mode selected by default", () => { + render(); + + const onlineRadio = screen.getByRole("radio", { name: ContainerCopyMessages.migrationTypeOptions.online.title }); + const offlineRadio = screen.getByRole("radio", { + name: ContainerCopyMessages.migrationTypeOptions.offline.title, + }); + + expect(onlineRadio).toBeChecked(); + expect(offlineRadio).not.toBeChecked(); + }); + + it("should render with offline mode selected when state is offline", () => { + (useCopyJobContext as jest.Mock).mockReturnValue({ + ...defaultContextValue, + copyJobState: { + ...defaultContextValue.copyJobState, + migrationType: CopyJobMigrationType.Offline, + }, + }); + + render(); + + const offlineRadio = screen.getByRole("radio", { + name: ContainerCopyMessages.migrationTypeOptions.offline.title, + }); + const onlineRadio = screen.getByRole("radio", { name: ContainerCopyMessages.migrationTypeOptions.online.title }); + + expect(offlineRadio).toBeChecked(); + expect(onlineRadio).not.toBeChecked(); + }); + }); + + describe("Descriptions and Learn More Links", () => { + it("should render online description and learn more link when online is selected", () => { + render(); + + expect(screen.getByText(ContainerCopyMessages.migrationTypeOptions.online.description)).toBeInTheDocument(); + expect(screen.getByTestId("migration-type-description-online")).toBeInTheDocument(); + + const learnMoreLink = screen.getByRole("link", { + name: ContainerCopyMessages.migrationTypeOptions.online.learnMoreText, + }); + expect(learnMoreLink).toBeInTheDocument(); + expect(learnMoreLink).toHaveAttribute("href", ContainerCopyMessages.migrationTypeOptions.online.learnMoreHref); + expect(learnMoreLink).toHaveAttribute("target", "_blank"); + expect(learnMoreLink).toHaveAttribute("rel", "noopener noreferrer"); + }); + + it("should render offline description and learn more link when offline is selected", () => { + (useCopyJobContext as jest.Mock).mockReturnValue({ + ...defaultContextValue, + copyJobState: { + ...defaultContextValue.copyJobState, + migrationType: CopyJobMigrationType.Offline, + }, + }); + + render(); + + expect(screen.getByText(ContainerCopyMessages.migrationTypeOptions.offline.description)).toBeInTheDocument(); + expect(screen.getByTestId("migration-type-description-offline")).toBeInTheDocument(); + + const learnMoreLink = screen.getByRole("link", { + name: ContainerCopyMessages.migrationTypeOptions.offline.learnMoreText, + }); + expect(learnMoreLink).toBeInTheDocument(); + expect(learnMoreLink).toHaveAttribute("href", ContainerCopyMessages.migrationTypeOptions.offline.learnMoreHref); + }); + }); + + describe("User Interactions", () => { + it("should call setCopyJobState when offline radio button is clicked", () => { + render(); + + const offlineRadio = screen.getByRole("radio", { + name: ContainerCopyMessages.migrationTypeOptions.offline.title, + }); + fireEvent.click(offlineRadio); + + expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function)); + + const updateFunction = mockSetCopyJobState.mock.calls[0][0]; + const result = updateFunction(defaultContextValue.copyJobState); + + expect(result).toEqual({ + ...defaultContextValue.copyJobState, + migrationType: CopyJobMigrationType.Offline, + }); + }); + + it("should call setCopyJobState when online radio button is clicked", () => { + (useCopyJobContext as jest.Mock).mockReturnValue({ + ...defaultContextValue, + copyJobState: { + ...defaultContextValue.copyJobState, + migrationType: CopyJobMigrationType.Offline, + }, + }); + + render(); + + const onlineRadio = screen.getByRole("radio", { name: ContainerCopyMessages.migrationTypeOptions.online.title }); + fireEvent.click(onlineRadio); + + expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function)); + + const updateFunction = mockSetCopyJobState.mock.calls[0][0]; + const result = updateFunction({ + ...defaultContextValue.copyJobState, + migrationType: CopyJobMigrationType.Offline, + }); + + expect(result).toEqual({ + ...defaultContextValue.copyJobState, + migrationType: CopyJobMigrationType.Online, + }); + }); + }); + + describe("Accessibility", () => { + it("should have proper ARIA attributes", () => { + render(); + + const choiceGroup = screen.getByRole("radiogroup"); + expect(choiceGroup).toBeInTheDocument(); + expect(choiceGroup).toHaveAttribute("aria-labelledby", "migrationTypeChoiceGroup"); + }); + + it("should have proper radio button labels", () => { + render(); + + expect( + screen.getByRole("radio", { name: ContainerCopyMessages.migrationTypeOptions.offline.title }), + ).toBeInTheDocument(); + expect( + screen.getByRole("radio", { name: ContainerCopyMessages.migrationTypeOptions.online.title }), + ).toBeInTheDocument(); + }); + }); + + describe("Edge Cases", () => { + it("should handle undefined migration type gracefully", () => { + (useCopyJobContext as jest.Mock).mockReturnValue({ + ...defaultContextValue, + copyJobState: { + ...defaultContextValue.copyJobState, + migrationType: undefined, + }, + }); + + render(); + + expect(screen.getByTestId("migration-type")).toBeInTheDocument(); + expect( + screen.getByRole("radio", { name: ContainerCopyMessages.migrationTypeOptions.offline.title }), + ).toBeInTheDocument(); + expect( + screen.getByRole("radio", { name: ContainerCopyMessages.migrationTypeOptions.online.title }), + ).toBeInTheDocument(); + }); + + it("should handle null copyJobState gracefully", () => { + (useCopyJobContext as jest.Mock).mockReturnValue({ + ...defaultContextValue, + copyJobState: null, + }); + + render(); + + expect(screen.getByTestId("migration-type")).toBeInTheDocument(); + }); + }); +}); diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/MigrationType.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/MigrationType.tsx new file mode 100644 index 000000000..ad94d0bc3 --- /dev/null +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/MigrationType.tsx @@ -0,0 +1,65 @@ +/* eslint-disable react/prop-types */ +/* eslint-disable react/display-name */ +import { ChoiceGroup, IChoiceGroupOption, Link, Stack, Text } from "@fluentui/react"; +import { useCopyJobContext } from "Explorer/ContainerCopy/Context/CopyJobContext"; +import React from "react"; +import ContainerCopyMessages from "../../../../ContainerCopyMessages"; +import { CopyJobMigrationType } from "../../../../Enums/CopyJobEnums"; + +interface MigrationTypeProps {} +const options: IChoiceGroupOption[] = [ + { + key: CopyJobMigrationType.Offline, + text: ContainerCopyMessages.migrationTypeOptions.offline.title, + styles: { root: { width: "33%" } }, + }, + { + key: CopyJobMigrationType.Online, + text: ContainerCopyMessages.migrationTypeOptions.online.title, + styles: { root: { width: "33%" } }, + }, +]; + +export const MigrationType: React.FC = React.memo(() => { + const { copyJobState, setCopyJobState } = useCopyJobContext(); + const handleChange = (_ev?: React.FormEvent, option?: IChoiceGroupOption) => { + if (option) { + setCopyJobState((prevState) => ({ + ...prevState, + migrationType: option.key as CopyJobMigrationType, + })); + } + }; + + const selectedKey = copyJobState?.migrationType ?? ""; + const selectedKeyLowercase = selectedKey.toLowerCase() as keyof typeof ContainerCopyMessages.migrationTypeOptions; + const selectedKeyContent = ContainerCopyMessages.migrationTypeOptions[selectedKeyLowercase]; + + return ( + + + + + {selectedKeyContent && ( + + + {selectedKeyContent.description}{" "} + + {selectedKeyContent.learnMoreText} + + + + )} + + ); +}); diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/MigrationTypeCheckbox.test.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/MigrationTypeCheckbox.test.tsx deleted file mode 100644 index 67289fe39..000000000 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/MigrationTypeCheckbox.test.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import "@testing-library/jest-dom"; -import { render, screen } from "@testing-library/react"; -import React from "react"; -import { MigrationTypeCheckbox } from "./MigrationTypeCheckbox"; - -describe("MigrationTypeCheckbox", () => { - const mockOnChange = jest.fn(); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe("Component Rendering", () => { - it("should render with default props (unchecked state)", () => { - const { container } = render(); - - expect(container.firstChild).toMatchSnapshot(); - }); - - it("should render in checked state", () => { - const { container } = render(); - - expect(container.firstChild).toMatchSnapshot(); - }); - - it("should display the correct label text", () => { - render(); - - const checkbox = screen.getByRole("checkbox"); - expect(checkbox).toBeInTheDocument(); - - const label = screen.getByText("Copy container in offline mode"); - expect(label).toBeInTheDocument(); - }); - - it("should have correct accessibility attributes when checked", () => { - render(); - - const checkbox = screen.getByRole("checkbox"); - expect(checkbox).toBeChecked(); - expect(checkbox).toHaveAttribute("checked"); - }); - }); - - describe("FluentUI Integration", () => { - it("should render FluentUI Checkbox component correctly", () => { - render(); - - const checkbox = screen.getByRole("checkbox"); - expect(checkbox).toBeInTheDocument(); - expect(checkbox).toHaveAttribute("type", "checkbox"); - }); - - it("should render FluentUI Stack component correctly", () => { - render(); - - const stackContainer = document.querySelector(".migrationTypeRow"); - expect(stackContainer).toBeInTheDocument(); - }); - - it("should apply FluentUI Stack horizontal alignment correctly", () => { - const { container } = render(); - - const stackContainer = container.querySelector(".migrationTypeRow"); - expect(stackContainer).toBeInTheDocument(); - }); - }); -}); diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/MigrationTypeCheckbox.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/MigrationTypeCheckbox.tsx deleted file mode 100644 index a72965fc6..000000000 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/MigrationTypeCheckbox.tsx +++ /dev/null @@ -1,16 +0,0 @@ -/* eslint-disable react/prop-types */ -/* eslint-disable react/display-name */ -import { Checkbox, Stack } from "@fluentui/react"; -import React from "react"; -import ContainerCopyMessages from "../../../../ContainerCopyMessages"; - -interface MigrationTypeCheckboxProps { - checked: boolean; - onChange: (_ev?: React.FormEvent, checked?: boolean) => void; -} - -export const MigrationTypeCheckbox: React.FC = React.memo(({ checked, onChange }) => ( - - - -)); diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/__snapshots__/MigrationType.test.tsx.snap b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/__snapshots__/MigrationType.test.tsx.snap new file mode 100644 index 000000000..f436c624a --- /dev/null +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/__snapshots__/MigrationType.test.tsx.snap @@ -0,0 +1,98 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MigrationType Component Rendering should render migration type component with radio buttons 1`] = ` +
+
+
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+
+ + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + + + Learn more + + +
+
+
+`; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/__snapshots__/MigrationTypeCheckbox.test.tsx.snap b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/__snapshots__/MigrationTypeCheckbox.test.tsx.snap deleted file mode 100644 index 4e1c653ef..000000000 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Components/__snapshots__/MigrationTypeCheckbox.test.tsx.snap +++ /dev/null @@ -1,80 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`MigrationTypeCheckbox Component Rendering should render in checked state 1`] = ` -
-
- - -
-
-`; - -exports[`MigrationTypeCheckbox Component Rendering should render with default props (unchecked state) 1`] = ` -
-
- - -
-
-`; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/SelectAccount.test.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/SelectAccount.test.tsx index 5fb556c3c..65529338e 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/SelectAccount.test.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/SelectAccount.test.tsx @@ -1,5 +1,5 @@ import "@testing-library/jest-dom"; -import { fireEvent, render, screen } from "@testing-library/react"; +import { render, screen } from "@testing-library/react"; import React from "react"; import { useCopyJobContext } from "../../../Context/CopyJobContext"; import { CopyJobMigrationType } from "../../../Enums/CopyJobEnums"; @@ -18,19 +18,8 @@ jest.mock("./Components/AccountDropdown", () => ({ AccountDropdown: jest.fn(() =>
Account Dropdown
), })); -jest.mock("./Components/MigrationTypeCheckbox", () => ({ - MigrationTypeCheckbox: jest.fn(({ checked, onChange }: { checked: boolean; onChange: () => void }) => ( -
- - Copy container in offline mode -
- )), +jest.mock("./Components/MigrationType", () => ({ + MigrationType: jest.fn(() =>
Migration Type
), })); describe("SelectAccount", () => { @@ -83,7 +72,7 @@ describe("SelectAccount", () => { expect(screen.getByTestId("subscription-dropdown")).toBeInTheDocument(); expect(screen.getByTestId("account-dropdown")).toBeInTheDocument(); - expect(screen.getByTestId("migration-type-checkbox")).toBeInTheDocument(); + expect(screen.getByTestId("migration-type")).toBeInTheDocument(); }); it("should render correctly with snapshot", () => { @@ -93,78 +82,20 @@ describe("SelectAccount", () => { }); describe("Migration Type Functionality", () => { - it("should display migration type checkbox as unchecked when migrationType is Online", () => { - (useCopyJobContext as jest.Mock).mockReturnValue({ - ...defaultContextValue, - copyJobState: { - ...defaultContextValue.copyJobState, - migrationType: CopyJobMigrationType.Online, - }, - }); - + it("should render migration type component", () => { render(); - const checkbox = screen.getByTestId("migration-checkbox-input"); - expect(checkbox).not.toBeChecked(); - }); - - it("should display migration type checkbox as checked when migrationType is Offline", () => { - (useCopyJobContext as jest.Mock).mockReturnValue({ - ...defaultContextValue, - copyJobState: { - ...defaultContextValue.copyJobState, - migrationType: CopyJobMigrationType.Offline, - }, - }); - - render(); - - const checkbox = screen.getByTestId("migration-checkbox-input"); - expect(checkbox).toBeChecked(); - }); - - it("should call setCopyJobState with Online migration type when checkbox is unchecked", () => { - (useCopyJobContext as jest.Mock).mockReturnValue({ - ...defaultContextValue, - copyJobState: { - ...defaultContextValue.copyJobState, - migrationType: CopyJobMigrationType.Offline, - }, - }); - - render(); - - const checkbox = screen.getByTestId("migration-checkbox-input"); - fireEvent.click(checkbox); - - expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function)); - - const updateFunction = mockSetCopyJobState.mock.calls[0][0]; - const previousState = { - ...defaultContextValue.copyJobState, - migrationType: CopyJobMigrationType.Offline, - }; - const result = updateFunction(previousState); - - expect(result).toEqual({ - ...previousState, - migrationType: CopyJobMigrationType.Online, - }); + const migrationTypeComponent = screen.getByTestId("migration-type"); + expect(migrationTypeComponent).toBeInTheDocument(); }); }); describe("Performance and Optimization", () => { - it("should maintain referential equality of handler functions between renders", async () => { + it("should render without performance issues", () => { const { rerender } = render(); - - const migrationCheckbox = (await import("./Components/MigrationTypeCheckbox")).MigrationTypeCheckbox as jest.Mock; - const firstRenderHandler = migrationCheckbox.mock.calls[migrationCheckbox.mock.calls.length - 1][0].onChange; - rerender(); - const secondRenderHandler = migrationCheckbox.mock.calls[migrationCheckbox.mock.calls.length - 1][0].onChange; - - expect(firstRenderHandler).toBe(secondRenderHandler); + expect(screen.getByTestId("migration-type")).toBeInTheDocument(); }); }); }); diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/SelectAccount.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/SelectAccount.tsx index ba1072de7..b6b9b25ec 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/SelectAccount.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/SelectAccount.tsx @@ -1,24 +1,11 @@ import { Stack, Text } from "@fluentui/react"; import React from "react"; import ContainerCopyMessages from "../../../ContainerCopyMessages"; -import { useCopyJobContext } from "../../../Context/CopyJobContext"; -import { CopyJobMigrationType } from "../../../Enums/CopyJobEnums"; import { AccountDropdown } from "./Components/AccountDropdown"; -import { MigrationTypeCheckbox } from "./Components/MigrationTypeCheckbox"; +import { MigrationType } from "./Components/MigrationType"; import { SubscriptionDropdown } from "./Components/SubscriptionDropdown"; const SelectAccount = React.memo(() => { - const { copyJobState, setCopyJobState } = useCopyJobContext(); - - const handleMigrationTypeChange = (_ev?: React.FormEvent, checked?: boolean) => { - setCopyJobState((prevState) => ({ - ...prevState, - migrationType: checked ? CopyJobMigrationType.Offline : CopyJobMigrationType.Online, - })); - }; - - const migrationTypeChecked = copyJobState?.migrationType === CopyJobMigrationType.Offline; - return ( {ContainerCopyMessages.selectAccountDescription} @@ -27,7 +14,7 @@ const SelectAccount = React.memo(() => { - + ); }); diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/__snapshots__/SelectAccount.test.tsx.snap b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/__snapshots__/SelectAccount.test.tsx.snap index 90a8ddc2b..e423c14d7 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/__snapshots__/SelectAccount.test.tsx.snap +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/__snapshots__/SelectAccount.test.tsx.snap @@ -21,14 +21,9 @@ exports[`SelectAccount Component Rendering should render correctly with snapshot Account Dropdown
- - Copy container in offline mode + Migration Type
`; diff --git a/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobActionMenu.test.tsx b/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobActionMenu.test.tsx index f8cad1cd5..14f700a36 100644 --- a/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobActionMenu.test.tsx +++ b/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobActionMenu.test.tsx @@ -5,6 +5,20 @@ import { CopyJobActions, CopyJobMigrationType, CopyJobStatusType } from "../../E import { CopyJobType, HandleJobActionClickType } from "../../Types/CopyJobTypes"; import CopyJobActionMenu from "./CopyJobActionMenu"; +const mockShowOkCancelModalDialog = jest.fn(); +const mockCloseDialog = jest.fn(); +const mockOpenDialog = jest.fn(); + +jest.mock("../../../Controls/Dialog", () => ({ + useDialog: { + getState: () => ({ + showOkCancelModalDialog: mockShowOkCancelModalDialog, + closeDialog: mockCloseDialog, + openDialog: mockOpenDialog, + }), + }, +})); + jest.mock("../../ContainerCopyMessages", () => ({ __esModule: true, default: { @@ -18,6 +32,11 @@ jest.mock("../../ContainerCopyMessages", () => ({ cancel: "Cancel", complete: "Complete", }, + dialog: { + heading: "Confirm Action", + confirmButtonText: "OK", + cancelButtonText: "Cancel", + }, }, }, })); @@ -50,6 +69,9 @@ describe("CopyJobActionMenu", () => { beforeEach(() => { jest.clearAllMocks(); + mockShowOkCancelModalDialog.mockClear(); + mockCloseDialog.mockClear(); + mockOpenDialog.mockClear(); }); describe("Component Rendering", () => { @@ -266,7 +288,29 @@ describe("CopyJobActionMenu", () => { expect(mockHandleClick).toHaveBeenCalledWith(job, CopyJobActions.pause, expect.any(Function)); }); - it("should call handleClick when cancel action is clicked", () => { + it("should show confirmation dialog when cancel action is clicked", () => { + const job = createMockJob({ Name: "Test Job", Status: CopyJobStatusType.InProgress }); + + render(); + + const actionButton = screen.getByRole("button", { name: "Actions" }); + fireEvent.click(actionButton); + + const cancelButton = screen.getByText("Cancel"); + fireEvent.click(cancelButton); + + expect(mockShowOkCancelModalDialog).toHaveBeenCalledWith( + "Confirm Action", + null, + "OK", + expect.any(Function), + "Cancel", + null, + expect.any(Object), // dialogBody content + ); + }); + + it("should call handleClick when dialog is confirmed for cancel action", () => { const job = createMockJob({ Status: CopyJobStatusType.InProgress }); render(); @@ -277,6 +321,9 @@ describe("CopyJobActionMenu", () => { const cancelButton = screen.getByText("Cancel"); fireEvent.click(cancelButton); + const [, , , onOkCallback] = mockShowOkCancelModalDialog.mock.calls[0]; + onOkCallback(); + expect(mockHandleClick).toHaveBeenCalledWith(job, CopyJobActions.cancel, expect.any(Function)); }); @@ -294,7 +341,33 @@ describe("CopyJobActionMenu", () => { expect(mockHandleClick).toHaveBeenCalledWith(job, CopyJobActions.resume, expect.any(Function)); }); - it("should call handleClick when complete action is clicked", () => { + it("should show confirmation dialog when complete action is clicked", () => { + const job = createMockJob({ + Name: "Test Online Job", + Status: CopyJobStatusType.InProgress, + Mode: CopyJobMigrationType.Online, + }); + + render(); + + const actionButton = screen.getByRole("button", { name: "Actions" }); + fireEvent.click(actionButton); + + const completeButton = screen.getByText("Complete"); + fireEvent.click(completeButton); + + expect(mockShowOkCancelModalDialog).toHaveBeenCalledWith( + "Confirm Action", + null, + "OK", + expect.any(Function), + "Cancel", + null, + expect.any(Object), // dialogBody content + ); + }); + + it("should call handleClick when dialog is confirmed for complete action", () => { const job = createMockJob({ Status: CopyJobStatusType.InProgress, Mode: CopyJobMigrationType.Online, @@ -308,10 +381,87 @@ describe("CopyJobActionMenu", () => { const completeButton = screen.getByText("Complete"); fireEvent.click(completeButton); + const [, , , onOkCallback] = mockShowOkCancelModalDialog.mock.calls[0]; + onOkCallback(); + expect(mockHandleClick).toHaveBeenCalledWith(job, CopyJobActions.complete, expect.any(Function)); }); }); + describe("Dialog Body Content", () => { + it("should pass correct dialog body content for cancel action", () => { + const job = createMockJob({ Name: "MyTestJob", Status: CopyJobStatusType.InProgress }); + + render(); + + const actionButton = screen.getByRole("button", { name: "Actions" }); + fireEvent.click(actionButton); + + const cancelButton = screen.getByText("Cancel"); + fireEvent.click(cancelButton); + + expect(mockShowOkCancelModalDialog).toHaveBeenCalledWith( + "Confirm Action", + null, + "OK", + expect.any(Function), + "Cancel", + null, + expect.objectContaining({ + props: expect.objectContaining({ + tokens: expect.any(Object), + children: expect.any(Array), + }), + }), + ); + }); + + it("should pass correct dialog body content for complete action", () => { + const job = createMockJob({ + Name: "OnlineTestJob", + Status: CopyJobStatusType.InProgress, + Mode: CopyJobMigrationType.Online, + }); + + render(); + + const actionButton = screen.getByRole("button", { name: "Actions" }); + fireEvent.click(actionButton); + + const completeButton = screen.getByText("Complete"); + fireEvent.click(completeButton); + + expect(mockShowOkCancelModalDialog).toHaveBeenCalledWith( + "Confirm Action", + null, + "OK", + expect.any(Function), + "Cancel", + null, + expect.objectContaining({ + props: expect.objectContaining({ + tokens: expect.any(Object), + children: expect.any(Array), + }), + }), + ); + }); + + it("should not show dialog body for actions without confirmation", () => { + const job = createMockJob({ Status: CopyJobStatusType.InProgress }); + + render(); + + const actionButton = screen.getByRole("button", { name: "Actions" }); + fireEvent.click(actionButton); + + const pauseButton = screen.getByText("Pause"); + fireEvent.click(pauseButton); + + expect(mockShowOkCancelModalDialog).not.toHaveBeenCalled(); + }); + }); + describe("Disabled States During Updates", () => { const TestComponentWrapper: React.FC<{ job: CopyJobType; @@ -339,8 +489,13 @@ describe("CopyJobActionMenu", () => { const pauseButton = screen.getByText("Pause"); fireEvent.click(pauseButton); fireEvent.click(actionButton); - const pauseButtonAfterClick = screen.getByText("Pause"); + + const pauseButtonAfterClick = screen.getByText("Pause").closest("button"); expect(pauseButtonAfterClick).toBeInTheDocument(); + expect(pauseButtonAfterClick).toHaveAttribute("aria-disabled", "true"); + + const cancelButtonAfterClick = screen.getByText("Cancel").closest("button"); + expect(cancelButtonAfterClick).toHaveAttribute("aria-disabled", "true"); }); it("should not disable actions for different jobs when one is updating", () => { @@ -360,22 +515,6 @@ describe("CopyJobActionMenu", () => { expect(screen.getByText("Cancel")).toBeInTheDocument(); }); - it("should properly handle multiple action types being disabled for the same job", () => { - const job = createMockJob({ Status: CopyJobStatusType.InProgress }); - render(); - const actionButton = screen.getByRole("button", { name: "Actions" }); - - fireEvent.click(actionButton); - fireEvent.click(screen.getByText("Pause")); - - fireEvent.click(actionButton); - fireEvent.click(screen.getByText("Cancel")); - - fireEvent.click(actionButton); - expect(screen.getByText("Pause")).toBeInTheDocument(); - expect(screen.getByText("Cancel")).toBeInTheDocument(); - }); - it("should handle complete action disabled state for online jobs", () => { const job = createMockJob({ Status: CopyJobStatusType.InProgress, @@ -462,6 +601,7 @@ describe("CopyJobActionMenu", () => { expect(actionButton).toHaveAttribute("aria-label", "Actions"); expect(actionButton).toHaveAttribute("title", "Actions"); + expect(actionButton).toHaveAttribute("role", "button"); const moreIcon = actionButton.querySelector('[data-icon-name="More"]'); expect(moreIcon || actionButton).toBeInTheDocument(); @@ -608,4 +748,129 @@ describe("CopyJobActionMenu", () => { }).not.toThrow(); }); }); + + describe("Complete Coverage Tests", () => { + it("should handle all possible dialog scenarios", () => { + const dialogTests = [ + { action: CopyJobActions.cancel, status: CopyJobStatusType.InProgress, shouldShowDialog: true }, + { + action: CopyJobActions.complete, + status: CopyJobStatusType.InProgress, + mode: CopyJobMigrationType.Online, + shouldShowDialog: true, + }, + { action: CopyJobActions.pause, status: CopyJobStatusType.InProgress, shouldShowDialog: false }, + { action: CopyJobActions.resume, status: CopyJobStatusType.Paused, shouldShowDialog: false }, + ]; + + dialogTests.forEach(({ action, status, mode = CopyJobMigrationType.Offline, shouldShowDialog }, index) => { + jest.clearAllMocks(); + + const job = createMockJob({ Status: status, Mode: mode, Name: `DialogTestJob${index}` }); + const { unmount } = render(); + + const actionButton = screen.getByRole("button", { name: "Actions" }); + fireEvent.click(actionButton); + + const actionText = action.charAt(0).toUpperCase() + action.slice(1); + if (screen.queryByText(actionText)) { + fireEvent.click(screen.getByText(actionText)); + + if (shouldShowDialog) { + expect(mockShowOkCancelModalDialog).toHaveBeenCalled(); + } else { + expect(mockShowOkCancelModalDialog).not.toHaveBeenCalled(); + expect(mockHandleClick).toHaveBeenCalled(); + } + } + + unmount(); + }); + }); + + it("should verify component handles state updates correctly", () => { + const job = createMockJob({ Status: CopyJobStatusType.InProgress }); + const stateUpdater = jest.fn(); + + const testHandleClick: HandleJobActionClickType = (job, action, setUpdatingJobAction) => { + setUpdatingJobAction({ jobName: job.Name, action }); + stateUpdater(job.Name, action); + }; + + render(); + + const actionButton = screen.getByRole("button", { name: "Actions" }); + fireEvent.click(actionButton); + + const pauseButton = screen.getByText("Pause"); + fireEvent.click(pauseButton); + + expect(stateUpdater).toHaveBeenCalledWith(job.Name, CopyJobActions.pause); + }); + }); + + describe("Full Integration Coverage", () => { + it("should test complete workflow for cancel action with dialog", () => { + const job = createMockJob({ Name: "Integration Test Job", Status: CopyJobStatusType.InProgress }); + render(); + + const actionButton = screen.getByRole("button", { name: "Actions" }); + expect(actionButton).toHaveAttribute("data-test", "CopyJobActionMenu/Button:Integration Test Job"); + fireEvent.click(actionButton); + + const cancelButton = screen.getByText("Cancel"); + fireEvent.click(cancelButton); + + expect(mockShowOkCancelModalDialog).toHaveBeenCalledWith( + "Confirm Action", // title + null, // subText + "OK", // okLabel + expect.any(Function), // onOk + "Cancel", // cancelLabel + null, // onCancel + expect.any(Object), // contentHtml (dialogBody) + ); + + const onOkCallback = mockShowOkCancelModalDialog.mock.calls[0][3]; + onOkCallback(); + + expect(mockHandleClick).toHaveBeenCalledWith(job, CopyJobActions.cancel, expect.any(Function)); + }); + + it("should test complete workflow for complete action with dialog", () => { + const job = createMockJob({ + Name: "Online Integration Job", + Status: CopyJobStatusType.Running, + Mode: CopyJobMigrationType.Online, + }); + + render(); + + const actionButton = screen.getByRole("button", { name: "Actions" }); + fireEvent.click(actionButton); + + const completeButton = screen.getByText("Complete"); + fireEvent.click(completeButton); + + expect(mockShowOkCancelModalDialog).toHaveBeenCalled(); + + const dialogContent = mockShowOkCancelModalDialog.mock.calls[0][6]; + expect(dialogContent).toBeTruthy(); + + const onOkCallback = mockShowOkCancelModalDialog.mock.calls[0][3]; + onOkCallback(); + + expect(mockHandleClick).toHaveBeenCalledWith(job, CopyJobActions.complete, expect.any(Function)); + }); + + it("should maintain proper component lifecycle", () => { + const job = createMockJob({ Status: CopyJobStatusType.InProgress }); + const { rerender, unmount } = render(); + + rerender(); + expect(screen.getByRole("button", { name: "Actions" })).toBeInTheDocument(); + + expect(() => unmount()).not.toThrow(); + }); + }); }); diff --git a/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobActionMenu.tsx b/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobActionMenu.tsx index b43307be9..dcceab3e6 100644 --- a/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobActionMenu.tsx +++ b/src/Explorer/ContainerCopy/MonitorCopyJobs/Components/CopyJobActionMenu.tsx @@ -1,5 +1,6 @@ -import { IconButton, IContextualMenuProps } from "@fluentui/react"; +import { DirectionalHint, IconButton, IContextualMenuProps, Stack } from "@fluentui/react"; import React from "react"; +import { useDialog } from "../../../Controls/Dialog"; import ContainerCopyMessages from "../../ContainerCopyMessages"; import { CopyJobActions, CopyJobMigrationType, CopyJobStatusType } from "../../Enums/CopyJobEnums"; import { CopyJobType, HandleJobActionClickType } from "../../Types/CopyJobTypes"; @@ -9,6 +10,35 @@ interface CopyJobActionMenuProps { handleClick: HandleJobActionClickType; } +const dialogBody = { + [CopyJobActions.cancel]: (jobName: string) => ( + + + You are about to cancel {jobName}: + + + Cancelling this job will stop it immediately. Any running or pending steps will not be completed. + + + This action cannot be undone. Do you want to continue? + + + ), + [CopyJobActions.complete]: (jobName: string) => ( + + + You are about to complete {jobName}: + + + Completing this job will stop the continuous data synchronization between the source and destination containers. + + + This action cannot be undone. Do you want to continue? + + + ), +}; + const CopyJobActionMenu: React.FC = ({ job, handleClick }) => { const [updatingJobAction, setUpdatingJobAction] = React.useState<{ jobName: string; action: string } | null>(null); if ( @@ -22,9 +52,22 @@ const CopyJobActionMenu: React.FC = ({ job, handleClick return null; } + const showActionConfirmationDialog = (job: CopyJobType, action: CopyJobActions): void => { + useDialog + .getState() + .showOkCancelModalDialog( + ContainerCopyMessages.MonitorJobs.dialog.heading, + null, + ContainerCopyMessages.MonitorJobs.dialog.confirmButtonText, + () => handleClick(job, action, setUpdatingJobAction), + ContainerCopyMessages.MonitorJobs.dialog.cancelButtonText, + null, + action in dialogBody ? dialogBody[action as keyof typeof dialogBody](job.Name) : null, + ); + }; + const getMenuItems = (): IContextualMenuProps["items"] => { const isThisJobUpdating = updatingJobAction?.jobName === job.Name; - const updatingAction = updatingJobAction?.action; const baseItems = [ { @@ -32,21 +75,21 @@ const CopyJobActionMenu: React.FC = ({ job, handleClick text: ContainerCopyMessages.MonitorJobs.Actions.pause, iconProps: { iconName: "Pause" }, onClick: () => handleClick(job, CopyJobActions.pause, setUpdatingJobAction), - disabled: isThisJobUpdating && updatingAction === CopyJobActions.pause, + disabled: isThisJobUpdating, }, { key: CopyJobActions.cancel, text: ContainerCopyMessages.MonitorJobs.Actions.cancel, iconProps: { iconName: "Cancel" }, - onClick: () => handleClick(job, CopyJobActions.cancel, setUpdatingJobAction), - disabled: isThisJobUpdating && updatingAction === CopyJobActions.cancel, + onClick: () => showActionConfirmationDialog(job, CopyJobActions.cancel), + disabled: isThisJobUpdating, }, { key: CopyJobActions.resume, text: ContainerCopyMessages.MonitorJobs.Actions.resume, iconProps: { iconName: "Play" }, onClick: () => handleClick(job, CopyJobActions.resume, setUpdatingJobAction), - disabled: isThisJobUpdating && updatingAction === CopyJobActions.resume, + disabled: isThisJobUpdating, }, ]; @@ -67,8 +110,8 @@ const CopyJobActionMenu: React.FC = ({ job, handleClick key: CopyJobActions.complete, text: ContainerCopyMessages.MonitorJobs.Actions.complete, iconProps: { iconName: "CheckMark" }, - onClick: () => handleClick(job, CopyJobActions.complete, setUpdatingJobAction), - disabled: isThisJobUpdating && updatingAction === CopyJobActions.complete, + onClick: () => showActionConfirmationDialog(job, CopyJobActions.complete), + disabled: isThisJobUpdating, }); } return filteredItems; @@ -85,8 +128,8 @@ const CopyJobActionMenu: React.FC = ({ job, handleClick diff --git a/src/Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobs.tsx b/src/Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobs.tsx index 56ec498f8..f261a423f 100644 --- a/src/Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobs.tsx +++ b/src/Explorer/ContainerCopy/MonitorCopyJobs/MonitorCopyJobs.tsx @@ -10,7 +10,7 @@ import CopyJobsNotFound from "../MonitorCopyJobs/Components/CopyJobs.NotFound"; import { CopyJobType, JobActionUpdatorType } from "../Types/CopyJobTypes"; import CopyJobsList from "./Components/CopyJobsList"; -const FETCH_INTERVAL_MS = 30 * 1000; +const FETCH_INTERVAL_MS = 60 * 1000; const SHIMMER_INDENT_LEVELS: IndentLevel[] = Array(7).fill({ level: 0, width: "100%" }); interface MonitorCopyJobsProps { diff --git a/src/Explorer/ContainerCopy/containerCopyStyles.less b/src/Explorer/ContainerCopy/containerCopyStyles.less index 05d9facec..503ea3e13 100644 --- a/src/Explorer/ContainerCopy/containerCopyStyles.less +++ b/src/Explorer/ContainerCopy/containerCopyStyles.less @@ -121,6 +121,9 @@ &:hover { background-color: @BaseMediumLow; } + .ms-DetailsHeader-cellTitle { + padding-left: 20px; + } } } diff --git a/src/Explorer/Panes/PanelContainerComponent.test.tsx b/src/Explorer/Panes/PanelContainerComponent.test.tsx index c0a53abce..117172f58 100644 --- a/src/Explorer/Panes/PanelContainerComponent.test.tsx +++ b/src/Explorer/Panes/PanelContainerComponent.test.tsx @@ -8,6 +8,7 @@ describe("PaneContainerComponent test", () => { headerText: "test", panelContent:
, isOpen: true, + isLightDismiss: true, hasConsole: false, isConsoleExpanded: false, }; @@ -20,6 +21,7 @@ describe("PaneContainerComponent test", () => { headerText: "test", panelContent:
, isOpen: true, + isLightDismiss: true, hasConsole: true, isConsoleExpanded: false, }; @@ -32,6 +34,7 @@ describe("PaneContainerComponent test", () => { headerText: "test", panelContent: undefined, isOpen: true, + isLightDismiss: true, hasConsole: true, isConsoleExpanded: false, }; @@ -44,6 +47,7 @@ describe("PaneContainerComponent test", () => { headerText: "test", panelContent:
, isOpen: true, + isLightDismiss: true, hasConsole: true, isConsoleExpanded: true, }; diff --git a/src/Explorer/Panes/PanelContainerComponent.tsx b/src/Explorer/Panes/PanelContainerComponent.tsx index fe8846f11..20ee1a3f6 100644 --- a/src/Explorer/Panes/PanelContainerComponent.tsx +++ b/src/Explorer/Panes/PanelContainerComponent.tsx @@ -9,6 +9,7 @@ export interface PanelContainerProps { isConsoleExpanded: boolean; isOpen: boolean; hasConsole: boolean; + isLightDismiss: boolean; isConsoleAnimationFinished?: boolean; panelWidth?: string; onRenderNavigationContent?: IRenderFunction; @@ -58,7 +59,7 @@ export class PanelContainerComponent extends React.Component { const isConsoleExpanded = useNotificationConsole((state) => state.isExpanded); const isConsoleAnimationFinished = useNotificationConsole((state) => state.consoleAnimationFinished); - const { isOpen, hasConsole, panelContent, panelWidth, headerText } = useSidePanel((state) => { + const { isOpen, hasConsole, isLightDismiss, panelContent, panelWidth, headerText } = useSidePanel((state) => { return { isOpen: state.isOpen, hasConsole: state.hasConsole, + isLightDismiss: state.isLightDismiss, panelContent: state.panelContent, headerText: state.headerText, panelWidth: state.panelWidth, @@ -154,6 +156,7 @@ export const SidePanel: React.FC = () => { return ( void; openSidePanel: (headerText: string, panelContent: JSX.Element, panelWidth?: string, onClose?: () => void) => void; closeSidePanel: () => void; setPanelHasConsole: (hasConsole: boolean) => void; + setLightDismiss: (isLightDismiss: boolean) => void; getRef?: React.RefObject; // Optional ref for focusing the last element. } export const useSidePanel: UseStore = create((set) => ({ isOpen: false, panelWidth: "440px", hasConsole: true, + isLightDismiss: true, setHeaderText: (headerText: string) => set((state) => ({ ...state, headerText })), setPanelHasConsole: (hasConsole: boolean) => set((state) => ({ ...state, hasConsole })), + setLightDismiss: (isLightDismiss: boolean) => set((state) => ({ ...state, isLightDismiss })), openSidePanel: (headerText, panelContent, panelWidth = "440px") => set((state) => ({ ...state, headerText, panelContent, panelWidth, isOpen: true })), closeSidePanel: () => {