mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-27 12:51:41 +00:00
325 lines
11 KiB
TypeScript
325 lines
11 KiB
TypeScript
import "@testing-library/jest-dom";
|
|
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
import React from "react";
|
|
import { CopyJobMigrationType } from "../../Enums/CopyJobEnums";
|
|
import { CopyJobContextState } from "../../Types/CopyJobTypes";
|
|
import { useCopyJobNavigation } from "./useCopyJobNavigation";
|
|
|
|
jest.mock("../../../../hooks/useSidePanel", () => ({
|
|
useSidePanel: {
|
|
getState: jest.fn(() => ({
|
|
closeSidePanel: jest.fn(),
|
|
})),
|
|
},
|
|
}));
|
|
|
|
jest.mock("../../Actions/CopyJobActions", () => ({
|
|
submitCreateCopyJob: jest.fn(),
|
|
}));
|
|
|
|
jest.mock("../../Context/CopyJobContext", () => ({
|
|
useCopyJobContext: jest.fn(),
|
|
}));
|
|
|
|
jest.mock("./useCopyJobPrerequisitesCache", () => ({
|
|
useCopyJobPrerequisitesCache: jest.fn(),
|
|
}));
|
|
|
|
jest.mock("./useCreateCopyJobScreensList", () => ({
|
|
SCREEN_KEYS: {
|
|
SelectAccount: "SelectAccount",
|
|
AssignPermissions: "AssignPermissions",
|
|
SelectSourceAndTargetContainers: "SelectSourceAndTargetContainers",
|
|
CreateCollection: "CreateCollection",
|
|
PreviewCopyJob: "PreviewCopyJob",
|
|
},
|
|
useCreateCopyJobScreensList: jest.fn(),
|
|
}));
|
|
|
|
jest.mock("../../CopyJobUtils", () => ({
|
|
getContainerIdentifiers: jest.fn(),
|
|
isIntraAccountCopy: jest.fn(),
|
|
}));
|
|
|
|
import { useSidePanel } from "../../../../hooks/useSidePanel";
|
|
import { submitCreateCopyJob } from "../../Actions/CopyJobActions";
|
|
import { useCopyJobContext } from "../../Context/CopyJobContext";
|
|
import { getContainerIdentifiers, isIntraAccountCopy } from "../../CopyJobUtils";
|
|
import { useCopyJobPrerequisitesCache } from "./useCopyJobPrerequisitesCache";
|
|
import { SCREEN_KEYS, useCreateCopyJobScreensList } from "./useCreateCopyJobScreensList";
|
|
|
|
const TestComponent: React.FC<{
|
|
onHookResult?: (result: ReturnType<typeof useCopyJobNavigation>) => void;
|
|
}> = ({ onHookResult }) => {
|
|
const hookResult = useCopyJobNavigation();
|
|
|
|
React.useEffect(() => {
|
|
onHookResult?.(hookResult);
|
|
}, [hookResult, onHookResult]);
|
|
|
|
return (
|
|
<div>
|
|
<div data-testid="current-screen">{hookResult.currentScreen?.key}</div>
|
|
<div data-testid="primary-disabled">{hookResult.isPrimaryDisabled.toString()}</div>
|
|
<div data-testid="previous-disabled">{hookResult.isPreviousDisabled.toString()}</div>
|
|
<div data-testid="primary-btn-text">{hookResult.primaryBtnText}</div>
|
|
<button data-testid="primary-btn" onClick={hookResult.handlePrimary} disabled={hookResult.isPrimaryDisabled}>
|
|
{hookResult.primaryBtnText}
|
|
</button>
|
|
<button data-testid="previous-btn" onClick={hookResult.handlePrevious} disabled={hookResult.isPreviousDisabled}>
|
|
Previous
|
|
</button>
|
|
<button data-testid="cancel-btn" onClick={hookResult.handleCancel}>
|
|
Cancel
|
|
</button>
|
|
{hookResult.currentScreen?.key === SCREEN_KEYS.SelectSourceAndTargetContainers && (
|
|
<button data-testid="add-collection-btn" onClick={hookResult.showAddCollectionPanel}>
|
|
Show Collection Panel
|
|
</button>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
describe("useCopyJobNavigation", () => {
|
|
const createMockCopyJobState = (overrides?: Partial<CopyJobContextState>): CopyJobContextState => ({
|
|
jobName: "test-job",
|
|
migrationType: CopyJobMigrationType.Offline,
|
|
source: {
|
|
subscription: { subscriptionId: "source-sub-id" } as any,
|
|
account: { id: "source-account-id", name: "Account-1" } as any,
|
|
databaseId: "source-db",
|
|
containerId: "source-container",
|
|
},
|
|
target: {
|
|
subscriptionId: "target-sub-id",
|
|
account: { id: "target-account-id", name: "Account-2" } as any,
|
|
databaseId: "target-db",
|
|
containerId: "target-container",
|
|
},
|
|
...overrides,
|
|
});
|
|
|
|
const createMockScreen = (key: string, validations: any[] = []) => ({
|
|
key,
|
|
component: <div>{key} Screen</div>,
|
|
validations,
|
|
});
|
|
|
|
const mockResetCopyJobState = jest.fn();
|
|
const mockSetContextError = jest.fn();
|
|
const mockCloseSidePanel = jest.fn();
|
|
const mockCopyJobState = createMockCopyJobState();
|
|
const mockValidationCache = new Map([
|
|
["validation1", true],
|
|
["validation2", true],
|
|
]);
|
|
|
|
const setupMocks = (screensList: any[] = [], isIntraAccount = false) => {
|
|
(useCopyJobContext as jest.Mock).mockReturnValue({
|
|
copyJobState: mockCopyJobState,
|
|
resetCopyJobState: mockResetCopyJobState,
|
|
setContextError: mockSetContextError,
|
|
});
|
|
|
|
(useCopyJobPrerequisitesCache as unknown as jest.Mock).mockReturnValue({
|
|
validationCache: mockValidationCache,
|
|
});
|
|
|
|
(useCreateCopyJobScreensList as jest.Mock).mockReturnValue(
|
|
screensList.length > 0 ? screensList : [createMockScreen(SCREEN_KEYS.SelectAccount)],
|
|
);
|
|
|
|
(useSidePanel.getState as jest.Mock).mockReturnValue({
|
|
closeSidePanel: mockCloseSidePanel,
|
|
});
|
|
|
|
(getContainerIdentifiers as jest.Mock).mockImplementation((container) => ({
|
|
accountId: container.account?.id,
|
|
databaseId: container.databaseId,
|
|
containerId: container.containerId,
|
|
}));
|
|
|
|
(isIntraAccountCopy as jest.Mock).mockReturnValue(isIntraAccount);
|
|
};
|
|
|
|
const clickPrimaryButton = () => fireEvent.click(screen.getByTestId("primary-btn"));
|
|
const clickPreviousButton = () => fireEvent.click(screen.getByTestId("previous-btn"));
|
|
|
|
const expectScreen = (screenKey: string) => {
|
|
expect(screen.getByTestId("current-screen")).toHaveTextContent(screenKey);
|
|
};
|
|
|
|
const expectPrimaryButtonText = (text: string) => {
|
|
expect(screen.getByTestId("primary-btn-text")).toHaveTextContent(text);
|
|
};
|
|
|
|
const expectPrimaryDisabled = (disabled: boolean) => {
|
|
expect(screen.getByTestId("primary-disabled")).toHaveTextContent(disabled.toString());
|
|
};
|
|
|
|
const navigateToScreen = (screenKey: string, clicks: number) => {
|
|
for (let i = 0; i < clicks; i++) {
|
|
clickPrimaryButton();
|
|
}
|
|
expectScreen(screenKey);
|
|
};
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
setupMocks();
|
|
});
|
|
|
|
describe("Initial state and navigation", () => {
|
|
test("should start with SelectAccount screen and disable previous button", () => {
|
|
render(<TestComponent />);
|
|
|
|
expectScreen(SCREEN_KEYS.SelectAccount);
|
|
expect(screen.getByTestId("previous-disabled")).toHaveTextContent("true");
|
|
});
|
|
|
|
test("should show Next button text by default", () => {
|
|
render(<TestComponent />);
|
|
expectPrimaryButtonText("Next");
|
|
});
|
|
|
|
test("should navigate through screens and show Create button for CreateCollection", () => {
|
|
const screens = [
|
|
createMockScreen(SCREEN_KEYS.SelectAccount),
|
|
createMockScreen(SCREEN_KEYS.SelectSourceAndTargetContainers),
|
|
createMockScreen(SCREEN_KEYS.CreateCollection),
|
|
];
|
|
setupMocks(screens, true);
|
|
|
|
render(<TestComponent />);
|
|
|
|
expectScreen(SCREEN_KEYS.SelectAccount);
|
|
clickPrimaryButton();
|
|
|
|
expectScreen(SCREEN_KEYS.SelectSourceAndTargetContainers);
|
|
expectPrimaryButtonText("Next");
|
|
|
|
fireEvent.click(screen.getByTestId("add-collection-btn"));
|
|
expectScreen(SCREEN_KEYS.CreateCollection);
|
|
expectPrimaryButtonText("Create");
|
|
|
|
clickPreviousButton();
|
|
expectScreen(SCREEN_KEYS.SelectSourceAndTargetContainers);
|
|
expectPrimaryButtonText("Next");
|
|
});
|
|
});
|
|
|
|
describe("Validation logic", () => {
|
|
test("should disable primary button when validations fail", () => {
|
|
const invalidScreen = createMockScreen(SCREEN_KEYS.SelectAccount, [
|
|
{ validate: () => false, message: "Invalid state" },
|
|
]);
|
|
setupMocks([invalidScreen]);
|
|
|
|
render(<TestComponent />);
|
|
expectPrimaryDisabled(true);
|
|
});
|
|
|
|
test("should enable primary button when all validations pass", () => {
|
|
const validScreen = createMockScreen(SCREEN_KEYS.SelectAccount, [
|
|
{ validate: () => true, message: "Valid state" },
|
|
]);
|
|
setupMocks([validScreen]);
|
|
|
|
render(<TestComponent />);
|
|
expectPrimaryDisabled(false);
|
|
});
|
|
|
|
test("should prevent navigation when source and target containers are identical", () => {
|
|
const screens = [
|
|
createMockScreen(SCREEN_KEYS.SelectAccount),
|
|
createMockScreen(SCREEN_KEYS.SelectSourceAndTargetContainers, [
|
|
{ validate: () => true, message: "Valid containers" },
|
|
]),
|
|
];
|
|
setupMocks(screens, true);
|
|
|
|
(getContainerIdentifiers as jest.Mock).mockImplementation(() => ({
|
|
accountId: "same-account",
|
|
databaseId: "same-db",
|
|
containerId: "same-container",
|
|
}));
|
|
|
|
render(<TestComponent />);
|
|
|
|
navigateToScreen(SCREEN_KEYS.SelectSourceAndTargetContainers, 1);
|
|
clickPrimaryButton();
|
|
|
|
expectScreen(SCREEN_KEYS.SelectSourceAndTargetContainers);
|
|
expect(mockSetContextError).toHaveBeenCalledWith(
|
|
"Source and destination containers cannot be the same. Please select different containers to proceed.",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Copy job submission", () => {
|
|
const setupToPreviewScreen = () => {
|
|
const screens = [
|
|
createMockScreen(SCREEN_KEYS.SelectAccount),
|
|
createMockScreen(SCREEN_KEYS.SelectSourceAndTargetContainers),
|
|
createMockScreen(SCREEN_KEYS.PreviewCopyJob),
|
|
];
|
|
setupMocks(screens, true);
|
|
|
|
render(<TestComponent />);
|
|
navigateToScreen(SCREEN_KEYS.PreviewCopyJob, 2);
|
|
clickPrimaryButton();
|
|
};
|
|
|
|
test("should handle successful copy job submission", async () => {
|
|
(submitCreateCopyJob as jest.Mock).mockResolvedValue(undefined);
|
|
|
|
setupToPreviewScreen();
|
|
|
|
await waitFor(() => {
|
|
expect(submitCreateCopyJob).toHaveBeenCalledWith(mockCopyJobState, expect.any(Function));
|
|
});
|
|
});
|
|
|
|
test("should handle copy job submission error", async () => {
|
|
const error = new Error("Submission failed");
|
|
(submitCreateCopyJob as jest.Mock).mockRejectedValue(error);
|
|
setupToPreviewScreen();
|
|
|
|
await waitFor(() => {
|
|
expect(mockSetContextError).toHaveBeenCalledWith("Submission failed");
|
|
});
|
|
});
|
|
|
|
test("should handle unknown error during submission", async () => {
|
|
(submitCreateCopyJob as jest.Mock).mockRejectedValue("Unknown error");
|
|
|
|
setupToPreviewScreen();
|
|
|
|
await waitFor(() => {
|
|
expect(mockSetContextError).toHaveBeenCalledWith("Failed to create copy job. Please try again later.");
|
|
});
|
|
});
|
|
|
|
test("should disable buttons during loading", async () => {
|
|
let resolveSubmission: () => void;
|
|
const submissionPromise = new Promise<void>((resolve) => {
|
|
resolveSubmission = resolve;
|
|
});
|
|
(submitCreateCopyJob as jest.Mock).mockReturnValue(submissionPromise);
|
|
|
|
setupToPreviewScreen();
|
|
|
|
await waitFor(() => {
|
|
expectPrimaryDisabled(true);
|
|
});
|
|
|
|
resolveSubmission!();
|
|
|
|
await waitFor(() => {
|
|
expectPrimaryDisabled(false);
|
|
});
|
|
});
|
|
});
|
|
});
|