mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-22 11:14:05 +00:00
Added comprehensive unit test coverage for Container Copy jobs (#2275)
* copy job uts * unit test coverage * lint fix * normalize account dropdown id
This commit is contained in:
@@ -0,0 +1,330 @@
|
||||
import { fireEvent, render } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { CopyJobMigrationType } from "../../../../Enums/CopyJobEnums";
|
||||
import { CopyJobContextState, DropdownOptionType } from "../../../../Types/CopyJobTypes";
|
||||
import { dropDownChangeHandler } from "./DropDownChangeHandler";
|
||||
|
||||
const createMockInitialState = (): CopyJobContextState => ({
|
||||
jobName: "test-job",
|
||||
migrationType: CopyJobMigrationType.Offline,
|
||||
sourceReadAccessFromTarget: false,
|
||||
source: {
|
||||
subscription: {
|
||||
subscriptionId: "source-sub-id",
|
||||
displayName: "Source Subscription",
|
||||
state: "Enabled",
|
||||
subscriptionPolicies: {
|
||||
locationPlacementId: "test",
|
||||
quotaId: "test",
|
||||
spendingLimit: "Off",
|
||||
},
|
||||
authorizationSource: "test",
|
||||
},
|
||||
account: {
|
||||
id: "source-account-id",
|
||||
name: "source-account",
|
||||
location: "East US",
|
||||
type: "Microsoft.DocumentDB/databaseAccounts",
|
||||
kind: "DocumentDB",
|
||||
properties: {
|
||||
documentEndpoint: "https://source.documents.azure.com:443/",
|
||||
cassandraEndpoint: undefined,
|
||||
gremlinEndpoint: undefined,
|
||||
tableEndpoint: undefined,
|
||||
writeLocations: [],
|
||||
readLocations: [],
|
||||
enableMultipleWriteLocations: false,
|
||||
isVirtualNetworkFilterEnabled: false,
|
||||
enableFreeTier: false,
|
||||
enableAnalyticalStorage: false,
|
||||
backupPolicy: undefined,
|
||||
disableLocalAuth: false,
|
||||
capacity: undefined,
|
||||
enablePriorityBasedExecution: false,
|
||||
publicNetworkAccess: "Enabled",
|
||||
enableMaterializedViews: false,
|
||||
},
|
||||
systemData: undefined,
|
||||
},
|
||||
databaseId: "source-db",
|
||||
containerId: "source-container",
|
||||
},
|
||||
target: {
|
||||
subscriptionId: "target-sub-id",
|
||||
account: {
|
||||
id: "target-account-id",
|
||||
name: "target-account",
|
||||
location: "West US",
|
||||
type: "Microsoft.DocumentDB/databaseAccounts",
|
||||
kind: "DocumentDB",
|
||||
properties: {
|
||||
documentEndpoint: "https://target.documents.azure.com:443/",
|
||||
cassandraEndpoint: undefined,
|
||||
gremlinEndpoint: undefined,
|
||||
tableEndpoint: undefined,
|
||||
writeLocations: [],
|
||||
readLocations: [],
|
||||
enableMultipleWriteLocations: false,
|
||||
isVirtualNetworkFilterEnabled: false,
|
||||
enableFreeTier: false,
|
||||
enableAnalyticalStorage: false,
|
||||
backupPolicy: undefined,
|
||||
disableLocalAuth: false,
|
||||
capacity: undefined,
|
||||
enablePriorityBasedExecution: false,
|
||||
publicNetworkAccess: "Enabled",
|
||||
enableMaterializedViews: false,
|
||||
},
|
||||
systemData: undefined,
|
||||
},
|
||||
databaseId: "target-db",
|
||||
containerId: "target-container",
|
||||
},
|
||||
});
|
||||
|
||||
interface TestComponentProps {
|
||||
initialState: CopyJobContextState;
|
||||
onStateChange: (state: CopyJobContextState) => void;
|
||||
}
|
||||
|
||||
const TestComponent: React.FC<TestComponentProps> = ({ initialState, onStateChange }) => {
|
||||
const [state, setState] = React.useState<CopyJobContextState>(initialState);
|
||||
const handler = dropDownChangeHandler(setState);
|
||||
|
||||
React.useEffect(() => {
|
||||
onStateChange(state);
|
||||
}, [state, onStateChange]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
data-testid="source-database-btn"
|
||||
onClick={() =>
|
||||
handler("sourceDatabase")({} as React.FormEvent, { key: "new-source-db", text: "New Source DB", data: {} })
|
||||
}
|
||||
>
|
||||
Source Database
|
||||
</button>
|
||||
<button
|
||||
data-testid="source-container-btn"
|
||||
onClick={() =>
|
||||
handler("sourceContainer")({} as React.FormEvent, {
|
||||
key: "new-source-container",
|
||||
text: "New Source Container",
|
||||
data: {},
|
||||
})
|
||||
}
|
||||
>
|
||||
Source Container
|
||||
</button>
|
||||
<button
|
||||
data-testid="target-database-btn"
|
||||
onClick={() =>
|
||||
handler("targetDatabase")({} as React.FormEvent, { key: "new-target-db", text: "New Target DB", data: {} })
|
||||
}
|
||||
>
|
||||
Target Database
|
||||
</button>
|
||||
<button
|
||||
data-testid="target-container-btn"
|
||||
onClick={() =>
|
||||
handler("targetContainer")({} as React.FormEvent, {
|
||||
key: "new-target-container",
|
||||
text: "New Target Container",
|
||||
data: {},
|
||||
})
|
||||
}
|
||||
>
|
||||
Target Container
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
describe("dropDownChangeHandler", () => {
|
||||
let capturedState: CopyJobContextState;
|
||||
let initialState: CopyJobContextState;
|
||||
|
||||
beforeEach(() => {
|
||||
initialState = createMockInitialState();
|
||||
capturedState = initialState;
|
||||
});
|
||||
|
||||
const renderTestComponent = () => {
|
||||
return render(
|
||||
<TestComponent
|
||||
initialState={initialState}
|
||||
onStateChange={(state) => {
|
||||
capturedState = state;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
};
|
||||
|
||||
describe("sourceDatabase dropdown change", () => {
|
||||
it("should update source database and reset source container", () => {
|
||||
const { getByTestId } = renderTestComponent();
|
||||
|
||||
fireEvent.click(getByTestId("source-database-btn"));
|
||||
|
||||
expect(capturedState.source.databaseId).toBe("new-source-db");
|
||||
expect(capturedState.source.containerId).toBeUndefined();
|
||||
expect(capturedState.source.subscription).toEqual(initialState.source.subscription);
|
||||
expect(capturedState.source.account).toEqual(initialState.source.account);
|
||||
expect(capturedState.target).toEqual(initialState.target);
|
||||
});
|
||||
|
||||
it("should maintain other state properties when updating source database", () => {
|
||||
const { getByTestId } = renderTestComponent();
|
||||
|
||||
fireEvent.click(getByTestId("source-database-btn"));
|
||||
|
||||
expect(capturedState.jobName).toBe(initialState.jobName);
|
||||
expect(capturedState.migrationType).toBe(initialState.migrationType);
|
||||
expect(capturedState.sourceReadAccessFromTarget).toBe(initialState.sourceReadAccessFromTarget);
|
||||
});
|
||||
});
|
||||
|
||||
describe("sourceContainer dropdown change", () => {
|
||||
it("should update source container only", () => {
|
||||
const { getByTestId } = renderTestComponent();
|
||||
|
||||
fireEvent.click(getByTestId("source-container-btn"));
|
||||
|
||||
expect(capturedState.source.containerId).toBe("new-source-container");
|
||||
expect(capturedState.source.databaseId).toBe(initialState.source.databaseId);
|
||||
expect(capturedState.source.subscription).toEqual(initialState.source.subscription);
|
||||
expect(capturedState.source.account).toEqual(initialState.source.account);
|
||||
expect(capturedState.target).toEqual(initialState.target);
|
||||
});
|
||||
|
||||
it("should not affect database selection when updating container", () => {
|
||||
const { getByTestId } = renderTestComponent();
|
||||
|
||||
fireEvent.click(getByTestId("source-container-btn"));
|
||||
|
||||
expect(capturedState.source.databaseId).toBe("source-db");
|
||||
});
|
||||
});
|
||||
|
||||
describe("targetDatabase dropdown change", () => {
|
||||
it("should update target database and reset target container", () => {
|
||||
const { getByTestId } = renderTestComponent();
|
||||
|
||||
fireEvent.click(getByTestId("target-database-btn"));
|
||||
|
||||
expect(capturedState.target.databaseId).toBe("new-target-db");
|
||||
expect(capturedState.target.containerId).toBeUndefined();
|
||||
expect(capturedState.target.subscriptionId).toBe(initialState.target.subscriptionId);
|
||||
expect(capturedState.target.account).toEqual(initialState.target.account);
|
||||
expect(capturedState.source).toEqual(initialState.source);
|
||||
});
|
||||
|
||||
it("should maintain other state properties when updating target database", () => {
|
||||
const { getByTestId } = renderTestComponent();
|
||||
|
||||
fireEvent.click(getByTestId("target-database-btn"));
|
||||
|
||||
expect(capturedState.jobName).toBe(initialState.jobName);
|
||||
expect(capturedState.migrationType).toBe(initialState.migrationType);
|
||||
expect(capturedState.sourceReadAccessFromTarget).toBe(initialState.sourceReadAccessFromTarget);
|
||||
});
|
||||
});
|
||||
|
||||
describe("targetContainer dropdown change", () => {
|
||||
it("should update target container only", () => {
|
||||
const { getByTestId } = renderTestComponent();
|
||||
|
||||
fireEvent.click(getByTestId("target-container-btn"));
|
||||
|
||||
expect(capturedState.target.containerId).toBe("new-target-container");
|
||||
expect(capturedState.target.databaseId).toBe(initialState.target.databaseId);
|
||||
expect(capturedState.target.subscriptionId).toBe(initialState.target.subscriptionId);
|
||||
expect(capturedState.target.account).toEqual(initialState.target.account);
|
||||
expect(capturedState.source).toEqual(initialState.source);
|
||||
});
|
||||
|
||||
it("should not affect database selection when updating container", () => {
|
||||
const { getByTestId } = renderTestComponent();
|
||||
|
||||
fireEvent.click(getByTestId("target-container-btn"));
|
||||
|
||||
expect(capturedState.target.databaseId).toBe("target-db");
|
||||
});
|
||||
});
|
||||
|
||||
describe("edge cases and error scenarios", () => {
|
||||
it("should handle empty string keys", () => {
|
||||
renderTestComponent();
|
||||
|
||||
const handler = dropDownChangeHandler((updater) => {
|
||||
const newState = typeof updater === "function" ? updater(capturedState) : updater;
|
||||
capturedState = newState;
|
||||
return capturedState;
|
||||
});
|
||||
|
||||
const mockEvent = {} as React.FormEvent;
|
||||
const mockOption: DropdownOptionType = { key: "", text: "Empty Option", data: {} };
|
||||
|
||||
handler("sourceDatabase")(mockEvent, mockOption);
|
||||
|
||||
expect(capturedState.source.databaseId).toBe("");
|
||||
expect(capturedState.source.containerId).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should handle special characters in keys", () => {
|
||||
renderTestComponent();
|
||||
|
||||
const handler = dropDownChangeHandler((updater) => {
|
||||
const newState = typeof updater === "function" ? updater(capturedState) : updater;
|
||||
capturedState = newState;
|
||||
return capturedState;
|
||||
});
|
||||
|
||||
const mockEvent = {} as React.FormEvent;
|
||||
const mockOption: DropdownOptionType = {
|
||||
key: "test-db-with-special-chars-@#$%",
|
||||
text: "Special DB",
|
||||
data: {},
|
||||
};
|
||||
|
||||
handler("sourceDatabase")(mockEvent, mockOption);
|
||||
|
||||
expect(capturedState.source.databaseId).toBe("test-db-with-special-chars-@#$%");
|
||||
expect(capturedState.source.containerId).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should handle numeric keys", () => {
|
||||
renderTestComponent();
|
||||
|
||||
const handler = dropDownChangeHandler((updater) => {
|
||||
const newState = typeof updater === "function" ? updater(capturedState) : updater;
|
||||
capturedState = newState;
|
||||
return capturedState;
|
||||
});
|
||||
|
||||
const mockEvent = {} as React.FormEvent;
|
||||
const mockOption: DropdownOptionType = { key: "12345", text: "Numeric Option", data: {} };
|
||||
|
||||
handler("targetContainer")(mockEvent, mockOption);
|
||||
|
||||
expect(capturedState.target.containerId).toBe("12345");
|
||||
});
|
||||
|
||||
it.skip("should handle invalid dropdown type gracefully", () => {
|
||||
const handler = dropDownChangeHandler((updater) => {
|
||||
const newState = typeof updater === "function" ? updater(capturedState) : updater;
|
||||
capturedState = newState;
|
||||
return capturedState;
|
||||
});
|
||||
|
||||
const mockEvent = {} as React.FormEvent;
|
||||
const mockOption: DropdownOptionType = { key: "test-value", text: "Test Option", data: {} };
|
||||
|
||||
const invalidHandler = handler as any;
|
||||
invalidHandler("invalidType")(mockEvent, mockOption);
|
||||
|
||||
expect(capturedState).toEqual(initialState);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,484 @@
|
||||
import "@testing-library/jest-dom";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { DatabaseModel } from "Contracts/DataModels";
|
||||
import React from "react";
|
||||
import Explorer from "../../../../../Explorer/Explorer";
|
||||
import CopyJobContextProvider from "../../../Context/CopyJobContext";
|
||||
import { CopyJobMigrationType } from "../../../Enums/CopyJobEnums";
|
||||
import SelectSourceAndTargetContainers from "./SelectSourceAndTargetContainers";
|
||||
|
||||
jest.mock("../../../../../hooks/useDatabases", () => ({
|
||||
useDatabases: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../../../../../hooks/useDataContainers", () => ({
|
||||
useDataContainers: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../../../ContainerCopyMessages", () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
selectSourceAndTargetContainersDescription: "Select source and target containers for migration",
|
||||
sourceContainerSubHeading: "Source Container",
|
||||
targetContainerSubHeading: "Target Container",
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock("./Events/DropDownChangeHandler", () => ({
|
||||
dropDownChangeHandler: jest.fn(() => () => jest.fn()),
|
||||
}));
|
||||
|
||||
jest.mock("./memoizedData", () => ({
|
||||
useSourceAndTargetData: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("UserContext", () => ({
|
||||
userContext: {
|
||||
subscriptionId: "test-subscription-id",
|
||||
databaseAccount: {
|
||||
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDB/databaseAccounts/test-account",
|
||||
name: "test-account",
|
||||
location: "East US",
|
||||
kind: "GlobalDocumentDB",
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
import { useDatabases } from "../../../../../hooks/useDatabases";
|
||||
import { useDataContainers } from "../../../../../hooks/useDataContainers";
|
||||
import { dropDownChangeHandler } from "./Events/DropDownChangeHandler";
|
||||
import { useSourceAndTargetData } from "./memoizedData";
|
||||
|
||||
const mockUseDatabases = useDatabases as jest.MockedFunction<typeof useDatabases>;
|
||||
const mockUseDataContainers = useDataContainers as jest.MockedFunction<typeof useDataContainers>;
|
||||
const mockDropDownChangeHandler = dropDownChangeHandler as jest.MockedFunction<typeof dropDownChangeHandler>;
|
||||
const mockUseSourceAndTargetData = useSourceAndTargetData as jest.MockedFunction<typeof useSourceAndTargetData>;
|
||||
|
||||
describe("SelectSourceAndTargetContainers", () => {
|
||||
let mockExplorer: Explorer;
|
||||
let mockShowAddCollectionPanel: jest.Mock;
|
||||
let mockOnDropdownChange: jest.Mock;
|
||||
|
||||
const mockDatabases: DatabaseModel[] = [
|
||||
{ id: "db1", name: "Database1" } as DatabaseModel,
|
||||
{ id: "db2", name: "Database2" } as DatabaseModel,
|
||||
];
|
||||
|
||||
const mockContainers: DatabaseModel[] = [
|
||||
{ id: "container1", name: "Container1" } as DatabaseModel,
|
||||
{ id: "container2", name: "Container2" } as DatabaseModel,
|
||||
];
|
||||
|
||||
const mockCopyJobState = {
|
||||
jobName: "",
|
||||
migrationType: CopyJobMigrationType.Offline,
|
||||
source: {
|
||||
subscription: { subscriptionId: "test-subscription-id" },
|
||||
account: {
|
||||
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDB/databaseAccounts/test-account",
|
||||
name: "test-account",
|
||||
},
|
||||
databaseId: "db1",
|
||||
containerId: "container1",
|
||||
},
|
||||
target: {
|
||||
subscriptionId: "test-subscription-id",
|
||||
account: {
|
||||
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDB/databaseAccounts/test-account",
|
||||
name: "test-account",
|
||||
},
|
||||
databaseId: "db2",
|
||||
containerId: "container2",
|
||||
},
|
||||
sourceReadAccessFromTarget: false,
|
||||
};
|
||||
|
||||
const mockMemoizedData = {
|
||||
source: mockCopyJobState.source,
|
||||
target: mockCopyJobState.target,
|
||||
sourceDbParams: ["test-sub", "test-rg", "test-account", "SQL"] as const,
|
||||
sourceContainerParams: ["test-sub", "test-rg", "test-account", "db1", "SQL"] as const,
|
||||
targetDbParams: ["test-sub", "test-rg", "test-account", "SQL"] as const,
|
||||
targetContainerParams: ["test-sub", "test-rg", "test-account", "db2", "SQL"] as const,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockExplorer = {} as Explorer;
|
||||
mockShowAddCollectionPanel = jest.fn();
|
||||
mockOnDropdownChange = jest.fn();
|
||||
|
||||
mockUseDatabases.mockReturnValue(mockDatabases);
|
||||
mockUseDataContainers.mockReturnValue(mockContainers);
|
||||
mockUseSourceAndTargetData.mockReturnValue(mockMemoizedData as ReturnType<typeof useSourceAndTargetData>);
|
||||
mockDropDownChangeHandler.mockReturnValue(() => mockOnDropdownChange);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const renderWithContext = (component: React.ReactElement) => {
|
||||
return render(<CopyJobContextProvider explorer={mockExplorer}>{component}</CopyJobContextProvider>);
|
||||
};
|
||||
|
||||
describe("Component Rendering", () => {
|
||||
it("should render without crashing", () => {
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
expect(screen.getByText("Select source and target containers for migration")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render description text", () => {
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
expect(screen.getByText("Select source and target containers for migration")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render source container section", () => {
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
expect(screen.getByText("Source Container")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render target container section", () => {
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
expect(screen.getByText("Target Container")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should return null when source is not available", () => {
|
||||
mockUseSourceAndTargetData.mockReturnValue({
|
||||
...mockMemoizedData,
|
||||
source: null,
|
||||
} as ReturnType<typeof useSourceAndTargetData>);
|
||||
|
||||
const { container } = renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it("should call useDatabases hooks with correct parameters", () => {
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
|
||||
expect(mockUseDatabases).toHaveBeenCalledWith(...mockMemoizedData.sourceDbParams);
|
||||
expect(mockUseDatabases).toHaveBeenCalledWith(...mockMemoizedData.targetDbParams);
|
||||
});
|
||||
|
||||
it("should call useDataContainers hooks with correct parameters", () => {
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
|
||||
expect(mockUseDataContainers).toHaveBeenCalledWith(...mockMemoizedData.sourceContainerParams);
|
||||
expect(mockUseDataContainers).toHaveBeenCalledWith(...mockMemoizedData.targetContainerParams);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Database Options", () => {
|
||||
it("should create source database options from useDatabases data", () => {
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
expect(mockUseDatabases).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should create target database options from useDatabases data", () => {
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
|
||||
expect(mockUseDatabases).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle empty database list", () => {
|
||||
mockUseDatabases.mockReturnValue([]);
|
||||
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
expect(mockUseDatabases).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle undefined database list", () => {
|
||||
mockUseDatabases.mockReturnValue(undefined);
|
||||
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
expect(mockUseDatabases).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Container Options", () => {
|
||||
it("should create source container options from useDataContainers data", () => {
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
|
||||
expect(mockUseDataContainers).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should create target container options from useDataContainers data", () => {
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
|
||||
expect(mockUseDataContainers).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle empty container list", () => {
|
||||
mockUseDataContainers.mockReturnValue([]);
|
||||
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
expect(mockUseDataContainers).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle undefined container list", () => {
|
||||
mockUseDataContainers.mockReturnValue(undefined);
|
||||
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
expect(mockUseDataContainers).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Event Handlers", () => {
|
||||
it("should call dropDownChangeHandler with setCopyJobState", () => {
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
|
||||
expect(mockDropDownChangeHandler).toHaveBeenCalledWith(expect.any(Function));
|
||||
});
|
||||
|
||||
it("should create dropdown change handlers for different types", () => {
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
expect(mockDropDownChangeHandler).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Component Props", () => {
|
||||
it("should pass showAddCollectionPanel to DatabaseContainerSection", () => {
|
||||
renderWithContext(<SelectSourceAndTargetContainers showAddCollectionPanel={mockShowAddCollectionPanel} />);
|
||||
expect(screen.getByText("Target Container")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render without showAddCollectionPanel prop", () => {
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
|
||||
expect(screen.getByText("Source Container")).toBeInTheDocument();
|
||||
expect(screen.getByText("Target Container")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Memoization", () => {
|
||||
it("should memoize source database options", () => {
|
||||
const { rerender } = renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
|
||||
expect(mockUseDatabases).toHaveBeenCalled();
|
||||
rerender(
|
||||
<CopyJobContextProvider explorer={mockExplorer}>
|
||||
<SelectSourceAndTargetContainers />
|
||||
</CopyJobContextProvider>,
|
||||
);
|
||||
|
||||
expect(mockUseDatabases).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should memoize target database options", () => {
|
||||
const { rerender } = renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
|
||||
expect(mockUseDatabases).toHaveBeenCalled();
|
||||
|
||||
rerender(
|
||||
<CopyJobContextProvider explorer={mockExplorer}>
|
||||
<SelectSourceAndTargetContainers />
|
||||
</CopyJobContextProvider>,
|
||||
);
|
||||
|
||||
expect(mockUseDatabases).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should memoize source container options", () => {
|
||||
const { rerender } = renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
|
||||
expect(mockUseDataContainers).toHaveBeenCalled();
|
||||
|
||||
rerender(
|
||||
<CopyJobContextProvider explorer={mockExplorer}>
|
||||
<SelectSourceAndTargetContainers />
|
||||
</CopyJobContextProvider>,
|
||||
);
|
||||
|
||||
expect(mockUseDataContainers).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should memoize target container options", () => {
|
||||
const { rerender } = renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
|
||||
expect(mockUseDataContainers).toHaveBeenCalled();
|
||||
|
||||
rerender(
|
||||
<CopyJobContextProvider explorer={mockExplorer}>
|
||||
<SelectSourceAndTargetContainers />
|
||||
</CopyJobContextProvider>,
|
||||
);
|
||||
|
||||
expect(mockUseDataContainers).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Database Container Section Props", () => {
|
||||
it("should pass correct props to source DatabaseContainerSection", () => {
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
|
||||
expect(screen.getByText("Source Container")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should pass correct props to target DatabaseContainerSection", () => {
|
||||
renderWithContext(<SelectSourceAndTargetContainers showAddCollectionPanel={mockShowAddCollectionPanel} />);
|
||||
|
||||
expect(screen.getByText("Target Container")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should disable source container dropdown when no database is selected", () => {
|
||||
mockUseSourceAndTargetData.mockReturnValue({
|
||||
...mockMemoizedData,
|
||||
source: {
|
||||
...mockMemoizedData.source,
|
||||
databaseId: "",
|
||||
},
|
||||
} as ReturnType<typeof useSourceAndTargetData>);
|
||||
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
expect(screen.getByText("Source Container")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should disable target container dropdown when no database is selected", () => {
|
||||
mockUseSourceAndTargetData.mockReturnValue({
|
||||
...mockMemoizedData,
|
||||
target: {
|
||||
...mockMemoizedData.target,
|
||||
databaseId: "",
|
||||
},
|
||||
} as ReturnType<typeof useSourceAndTargetData>);
|
||||
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
expect(screen.getByText("Target Container")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Error Handling", () => {
|
||||
it("should handle hooks returning null gracefully", () => {
|
||||
mockUseDatabases.mockReturnValue(null);
|
||||
mockUseDataContainers.mockReturnValue(null);
|
||||
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
|
||||
expect(screen.getByText("Select source and target containers for migration")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should handle hooks throwing errors gracefully", () => {
|
||||
const originalError = console.error;
|
||||
console.error = jest.fn();
|
||||
|
||||
mockUseDatabases.mockImplementation(() => {
|
||||
throw new Error("Database fetch error");
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
}).toThrow();
|
||||
|
||||
console.error = originalError;
|
||||
});
|
||||
|
||||
it("should handle missing source data gracefully", () => {
|
||||
mockUseSourceAndTargetData.mockReturnValue({
|
||||
...mockMemoizedData,
|
||||
source: undefined,
|
||||
} as ReturnType<typeof useSourceAndTargetData>);
|
||||
|
||||
const { container } = renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Integration with CopyJobContext", () => {
|
||||
it("should use CopyJobContext for state management", () => {
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
|
||||
expect(mockUseSourceAndTargetData).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should respond to context state changes", () => {
|
||||
const { rerender } = renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
|
||||
mockUseSourceAndTargetData.mockReturnValue({
|
||||
...mockMemoizedData,
|
||||
source: {
|
||||
...mockMemoizedData.source,
|
||||
databaseId: "different-db",
|
||||
},
|
||||
} as ReturnType<typeof useSourceAndTargetData>);
|
||||
|
||||
rerender(
|
||||
<CopyJobContextProvider explorer={mockExplorer}>
|
||||
<SelectSourceAndTargetContainers />
|
||||
</CopyJobContextProvider>,
|
||||
);
|
||||
|
||||
expect(mockUseSourceAndTargetData).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Stack Layout", () => {
|
||||
it("should render with correct Stack className", () => {
|
||||
const { container } = renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
|
||||
const stackElement = container.querySelector(".selectSourceAndTargetContainers");
|
||||
expect(stackElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should apply correct spacing tokens", () => {
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
|
||||
expect(screen.getByText("Select source and target containers for migration")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Component Structure", () => {
|
||||
it("should render description, source section, and target section in correct order", () => {
|
||||
renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
|
||||
const description = screen.getByText("Select source and target containers for migration");
|
||||
const sourceSection = screen.getByText("Source Container");
|
||||
const targetSection = screen.getByText("Target Container");
|
||||
|
||||
expect(description).toBeInTheDocument();
|
||||
expect(sourceSection).toBeInTheDocument();
|
||||
expect(targetSection).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should maintain component hierarchy", () => {
|
||||
const { container } = renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
|
||||
const mainContainer = container.querySelector(".selectSourceAndTargetContainers");
|
||||
expect(mainContainer).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Performance", () => {
|
||||
it("should not cause unnecessary re-renders when props don't change", () => {
|
||||
const { rerender } = renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
|
||||
rerender(
|
||||
<CopyJobContextProvider explorer={mockExplorer}>
|
||||
<SelectSourceAndTargetContainers />
|
||||
</CopyJobContextProvider>,
|
||||
);
|
||||
|
||||
expect(mockUseSourceAndTargetData).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle rapid state changes efficiently", () => {
|
||||
const { rerender } = renderWithContext(<SelectSourceAndTargetContainers />);
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
mockUseSourceAndTargetData.mockReturnValue({
|
||||
...mockMemoizedData,
|
||||
source: {
|
||||
...mockMemoizedData.source,
|
||||
databaseId: `db-${i}`,
|
||||
},
|
||||
} as ReturnType<typeof useSourceAndTargetData>);
|
||||
|
||||
rerender(
|
||||
<CopyJobContextProvider explorer={mockExplorer}>
|
||||
<SelectSourceAndTargetContainers />
|
||||
</CopyJobContextProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
expect(mockUseSourceAndTargetData).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,452 @@
|
||||
import "@testing-library/jest-dom";
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
|
||||
import { DatabaseContainerSectionProps, DropdownOptionType } from "../../../../Types/CopyJobTypes";
|
||||
import { DatabaseContainerSection } from "./DatabaseContainerSection";
|
||||
|
||||
describe("DatabaseContainerSection", () => {
|
||||
const mockDatabaseOnChange = jest.fn();
|
||||
const mockContainerOnChange = jest.fn();
|
||||
const mockHandleOnDemandCreateContainer = jest.fn();
|
||||
|
||||
const mockDatabaseOptions: DropdownOptionType[] = [
|
||||
{ key: "db1", text: "Database 1", data: { id: "db1" } },
|
||||
{ key: "db2", text: "Database 2", data: { id: "db2" } },
|
||||
{ key: "db3", text: "Database 3", data: { id: "db3" } },
|
||||
];
|
||||
|
||||
const mockContainerOptions: DropdownOptionType[] = [
|
||||
{ key: "container1", text: "Container 1", data: { id: "container1" } },
|
||||
{ key: "container2", text: "Container 2", data: { id: "container2" } },
|
||||
{ key: "container3", text: "Container 3", data: { id: "container3" } },
|
||||
];
|
||||
|
||||
const defaultProps: DatabaseContainerSectionProps = {
|
||||
heading: "Source container",
|
||||
databaseOptions: mockDatabaseOptions,
|
||||
selectedDatabase: "db1",
|
||||
databaseDisabled: false,
|
||||
databaseOnChange: mockDatabaseOnChange,
|
||||
containerOptions: mockContainerOptions,
|
||||
selectedContainer: "container1",
|
||||
containerDisabled: false,
|
||||
containerOnChange: mockContainerOnChange,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("Component Rendering", () => {
|
||||
it("renders the component with correct structure", () => {
|
||||
const { container } = render(<DatabaseContainerSection {...defaultProps} />);
|
||||
|
||||
expect(container.firstChild).toHaveClass("databaseContainerSection");
|
||||
expect(screen.getByText("Source container")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders heading correctly", () => {
|
||||
render(<DatabaseContainerSection {...defaultProps} />);
|
||||
|
||||
const heading = screen.getByText("Source container");
|
||||
expect(heading).toBeInTheDocument();
|
||||
expect(heading.tagName).toBe("LABEL");
|
||||
expect(heading).toHaveClass("subHeading");
|
||||
});
|
||||
|
||||
it("renders database dropdown with correct properties", () => {
|
||||
render(<DatabaseContainerSection {...defaultProps} />);
|
||||
|
||||
const databaseDropdown = screen.getByRole("combobox", {
|
||||
name: ContainerCopyMessages.databaseDropdownLabel,
|
||||
});
|
||||
|
||||
expect(databaseDropdown).toBeInTheDocument();
|
||||
expect(databaseDropdown).toHaveAttribute("aria-label", ContainerCopyMessages.databaseDropdownLabel);
|
||||
expect(databaseDropdown).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it("renders container dropdown with correct properties", () => {
|
||||
render(<DatabaseContainerSection {...defaultProps} />);
|
||||
|
||||
const containerDropdown = screen.getByRole("combobox", {
|
||||
name: ContainerCopyMessages.containerDropdownLabel,
|
||||
});
|
||||
|
||||
expect(containerDropdown).toBeInTheDocument();
|
||||
expect(containerDropdown).toHaveAttribute("aria-label", ContainerCopyMessages.containerDropdownLabel);
|
||||
expect(containerDropdown).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it("renders database label correctly", () => {
|
||||
render(<DatabaseContainerSection {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText(`${ContainerCopyMessages.databaseDropdownLabel}:`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders container label correctly", () => {
|
||||
render(<DatabaseContainerSection {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText(`${ContainerCopyMessages.containerDropdownLabel}:`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render create container button when handleOnDemandCreateContainer is not provided", () => {
|
||||
render(<DatabaseContainerSection {...defaultProps} />);
|
||||
|
||||
expect(screen.queryByText(ContainerCopyMessages.createContainerButtonLabel)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders create container button when handleOnDemandCreateContainer is provided", () => {
|
||||
const propsWithCreateHandler = {
|
||||
...defaultProps,
|
||||
handleOnDemandCreateContainer: mockHandleOnDemandCreateContainer,
|
||||
};
|
||||
const { container } = render(<DatabaseContainerSection {...propsWithCreateHandler} />);
|
||||
const createButton = container.querySelector(".create-container-link-btn");
|
||||
|
||||
expect(createButton).toBeInTheDocument();
|
||||
expect(createButton).toHaveTextContent(ContainerCopyMessages.createContainerButtonLabel);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Dropdown States", () => {
|
||||
it("renders database dropdown as disabled when databaseDisabled is true", () => {
|
||||
const propsWithDisabledDatabase = {
|
||||
...defaultProps,
|
||||
databaseDisabled: true,
|
||||
};
|
||||
|
||||
render(<DatabaseContainerSection {...propsWithDisabledDatabase} />);
|
||||
|
||||
const databaseDropdown = screen.getByRole("combobox", {
|
||||
name: ContainerCopyMessages.databaseDropdownLabel,
|
||||
});
|
||||
|
||||
expect(databaseDropdown).toHaveAttribute("aria-disabled", "true");
|
||||
});
|
||||
|
||||
it("renders container dropdown as disabled when containerDisabled is true", () => {
|
||||
const propsWithDisabledContainer = {
|
||||
...defaultProps,
|
||||
containerDisabled: true,
|
||||
};
|
||||
|
||||
render(<DatabaseContainerSection {...propsWithDisabledContainer} />);
|
||||
|
||||
const containerDropdown = screen.getByRole("combobox", {
|
||||
name: ContainerCopyMessages.containerDropdownLabel,
|
||||
});
|
||||
|
||||
expect(containerDropdown).toHaveAttribute("aria-disabled", "true");
|
||||
});
|
||||
|
||||
it("handles falsy values for disabled props correctly", () => {
|
||||
const propsWithFalsyDisabled = {
|
||||
...defaultProps,
|
||||
databaseDisabled: undefined,
|
||||
containerDisabled: null,
|
||||
} as DatabaseContainerSectionProps;
|
||||
|
||||
render(<DatabaseContainerSection {...propsWithFalsyDisabled} />);
|
||||
|
||||
const databaseDropdown = screen.getByRole("combobox", {
|
||||
name: ContainerCopyMessages.databaseDropdownLabel,
|
||||
});
|
||||
const containerDropdown = screen.getByRole("combobox", {
|
||||
name: ContainerCopyMessages.containerDropdownLabel,
|
||||
});
|
||||
|
||||
expect(databaseDropdown).not.toHaveAttribute("aria-disabled", "true");
|
||||
expect(containerDropdown).not.toHaveAttribute("aria-disabled", "true");
|
||||
});
|
||||
});
|
||||
|
||||
describe("User Interactions", () => {
|
||||
it("calls databaseOnChange when database dropdown selection changes", () => {
|
||||
render(<DatabaseContainerSection {...defaultProps} />);
|
||||
const databaseDropdown = screen.getByRole("combobox", {
|
||||
name: ContainerCopyMessages.databaseDropdownLabel,
|
||||
});
|
||||
|
||||
fireEvent.click(databaseDropdown);
|
||||
expect(databaseDropdown).toHaveAttribute("aria-label", ContainerCopyMessages.databaseDropdownLabel);
|
||||
});
|
||||
|
||||
it("calls containerOnChange when container dropdown selection changes", () => {
|
||||
render(<DatabaseContainerSection {...defaultProps} />);
|
||||
const containerDropdown = screen.getByRole("combobox", {
|
||||
name: ContainerCopyMessages.containerDropdownLabel,
|
||||
});
|
||||
|
||||
fireEvent.click(containerDropdown);
|
||||
expect(containerDropdown).toHaveAttribute("aria-label", ContainerCopyMessages.containerDropdownLabel);
|
||||
});
|
||||
|
||||
it("calls handleOnDemandCreateContainer when create container button is clicked", () => {
|
||||
const propsWithCreateHandler = {
|
||||
...defaultProps,
|
||||
handleOnDemandCreateContainer: mockHandleOnDemandCreateContainer,
|
||||
};
|
||||
|
||||
render(<DatabaseContainerSection {...propsWithCreateHandler} />);
|
||||
|
||||
const createButton = screen.getByText(ContainerCopyMessages.createContainerButtonLabel);
|
||||
fireEvent.click(createButton);
|
||||
|
||||
expect(mockHandleOnDemandCreateContainer).toHaveBeenCalledTimes(1);
|
||||
expect(mockHandleOnDemandCreateContainer).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Props Validation", () => {
|
||||
it("renders with different heading text", () => {
|
||||
const propsWithDifferentHeading = {
|
||||
...defaultProps,
|
||||
heading: "Target container",
|
||||
};
|
||||
|
||||
render(<DatabaseContainerSection {...propsWithDifferentHeading} />);
|
||||
|
||||
expect(screen.getByText("Target container")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Source container")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders with different selected values", () => {
|
||||
const propsWithDifferentSelections = {
|
||||
...defaultProps,
|
||||
selectedDatabase: "db2",
|
||||
selectedContainer: "container3",
|
||||
};
|
||||
|
||||
render(<DatabaseContainerSection {...propsWithDifferentSelections} />);
|
||||
|
||||
expect(screen.getByText("Source container")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders with empty options arrays", () => {
|
||||
const propsWithEmptyOptions = {
|
||||
...defaultProps,
|
||||
databaseOptions: [],
|
||||
containerOptions: [],
|
||||
} as DatabaseContainerSectionProps;
|
||||
|
||||
render(<DatabaseContainerSection {...propsWithEmptyOptions} />);
|
||||
|
||||
const databaseDropdown = screen.getByRole("combobox", {
|
||||
name: ContainerCopyMessages.databaseDropdownLabel,
|
||||
});
|
||||
const containerDropdown = screen.getByRole("combobox", {
|
||||
name: ContainerCopyMessages.containerDropdownLabel,
|
||||
});
|
||||
|
||||
expect(databaseDropdown).toBeInTheDocument();
|
||||
expect(containerDropdown).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Accessibility", () => {
|
||||
it("has proper ARIA labels for dropdowns", () => {
|
||||
render(<DatabaseContainerSection {...defaultProps} />);
|
||||
|
||||
const databaseDropdown = screen.getByRole("combobox", {
|
||||
name: ContainerCopyMessages.databaseDropdownLabel,
|
||||
});
|
||||
const containerDropdown = screen.getByRole("combobox", {
|
||||
name: ContainerCopyMessages.containerDropdownLabel,
|
||||
});
|
||||
|
||||
expect(databaseDropdown).toHaveAttribute("aria-label", ContainerCopyMessages.databaseDropdownLabel);
|
||||
expect(containerDropdown).toHaveAttribute("aria-label", ContainerCopyMessages.containerDropdownLabel);
|
||||
});
|
||||
|
||||
it("has proper required attributes for dropdowns", () => {
|
||||
render(<DatabaseContainerSection {...defaultProps} />);
|
||||
|
||||
const databaseDropdown = screen.getByRole("combobox", {
|
||||
name: ContainerCopyMessages.databaseDropdownLabel,
|
||||
});
|
||||
const containerDropdown = screen.getByRole("combobox", {
|
||||
name: ContainerCopyMessages.containerDropdownLabel,
|
||||
});
|
||||
|
||||
expect(databaseDropdown).toHaveAttribute("aria-required", "true");
|
||||
expect(containerDropdown).toHaveAttribute("aria-required", "true");
|
||||
});
|
||||
|
||||
it("maintains proper label associations", () => {
|
||||
render(<DatabaseContainerSection {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText(`${ContainerCopyMessages.databaseDropdownLabel}:`)).toBeInTheDocument();
|
||||
expect(screen.getByText(`${ContainerCopyMessages.containerDropdownLabel}:`)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edge Cases", () => {
|
||||
it("handles undefined optional props gracefully", () => {
|
||||
const minimalProps: DatabaseContainerSectionProps = {
|
||||
heading: "Test Heading",
|
||||
databaseOptions: mockDatabaseOptions,
|
||||
selectedDatabase: "db1",
|
||||
databaseOnChange: mockDatabaseOnChange,
|
||||
containerOptions: mockContainerOptions,
|
||||
selectedContainer: "container1",
|
||||
containerOnChange: mockContainerOnChange,
|
||||
};
|
||||
|
||||
render(<DatabaseContainerSection {...minimalProps} />);
|
||||
|
||||
expect(screen.getByText("Test Heading")).toBeInTheDocument();
|
||||
expect(screen.queryByText(ContainerCopyMessages.createContainerButtonLabel)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("handles empty string selections", () => {
|
||||
const propsWithEmptySelections = {
|
||||
...defaultProps,
|
||||
selectedDatabase: "",
|
||||
selectedContainer: "",
|
||||
};
|
||||
|
||||
render(<DatabaseContainerSection {...propsWithEmptySelections} />);
|
||||
|
||||
expect(screen.getByText("Source container")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders correctly with long option texts", () => {
|
||||
const longOptions = [
|
||||
{
|
||||
key: "long1",
|
||||
text: "This is a very long database name that might wrap to multiple lines in the dropdown",
|
||||
data: { id: "long1" },
|
||||
},
|
||||
];
|
||||
|
||||
const propsWithLongOptions = {
|
||||
...defaultProps,
|
||||
databaseOptions: longOptions,
|
||||
containerOptions: longOptions,
|
||||
selectedDatabase: "long1",
|
||||
selectedContainer: "long1",
|
||||
};
|
||||
|
||||
render(<DatabaseContainerSection {...propsWithLongOptions} />);
|
||||
|
||||
expect(screen.getByText("Source container")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Component Structure", () => {
|
||||
it("has correct CSS classes applied", () => {
|
||||
const { container } = render(<DatabaseContainerSection {...defaultProps} />);
|
||||
|
||||
const mainContainer = container.querySelector(".databaseContainerSection");
|
||||
expect(mainContainer).toBeInTheDocument();
|
||||
|
||||
const subHeading = screen.getByText("Source container");
|
||||
expect(subHeading).toHaveClass("subHeading");
|
||||
});
|
||||
|
||||
it("maintains proper component hierarchy", () => {
|
||||
const { container } = render(<DatabaseContainerSection {...defaultProps} />);
|
||||
|
||||
const mainStack = container.querySelector(".databaseContainerSection");
|
||||
expect(mainStack).toBeInTheDocument();
|
||||
|
||||
const fieldRows = container.querySelectorAll(".flex-row");
|
||||
expect(fieldRows.length).toBe(2);
|
||||
});
|
||||
|
||||
it("renders create button in correct position when provided", () => {
|
||||
const propsWithCreateHandler = {
|
||||
...defaultProps,
|
||||
handleOnDemandCreateContainer: mockHandleOnDemandCreateContainer,
|
||||
};
|
||||
|
||||
const { container } = render(<DatabaseContainerSection {...propsWithCreateHandler} />);
|
||||
|
||||
const createButton = screen.getByText(ContainerCopyMessages.createContainerButtonLabel);
|
||||
expect(createButton).toBeInTheDocument();
|
||||
|
||||
const containerSection = container.querySelector(".databaseContainerSection");
|
||||
expect(containerSection).toContainElement(createButton);
|
||||
});
|
||||
|
||||
it("displays correct create container button label", () => {
|
||||
const propsWithCreateHandler = {
|
||||
...defaultProps,
|
||||
handleOnDemandCreateContainer: mockHandleOnDemandCreateContainer,
|
||||
};
|
||||
|
||||
render(<DatabaseContainerSection {...propsWithCreateHandler} />);
|
||||
|
||||
expect(screen.getByText(ContainerCopyMessages.createContainerButtonLabel)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Snapshot Testing", () => {
|
||||
it("matches snapshot with minimal props", () => {
|
||||
const minimalProps: DatabaseContainerSectionProps = {
|
||||
heading: "Source Container",
|
||||
databaseOptions: [{ key: "db1", text: "Database 1", data: { id: "db1" } }],
|
||||
selectedDatabase: "db1",
|
||||
databaseOnChange: jest.fn(),
|
||||
containerOptions: [{ key: "c1", text: "Container 1", data: { id: "c1" } }],
|
||||
selectedContainer: "c1",
|
||||
containerOnChange: jest.fn(),
|
||||
};
|
||||
|
||||
const { container } = render(<DatabaseContainerSection {...minimalProps} />);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("matches snapshot with all props including create container handler", () => {
|
||||
const fullProps: DatabaseContainerSectionProps = {
|
||||
heading: "Target Container",
|
||||
databaseOptions: mockDatabaseOptions,
|
||||
selectedDatabase: "db2",
|
||||
databaseDisabled: false,
|
||||
databaseOnChange: jest.fn(),
|
||||
containerOptions: mockContainerOptions,
|
||||
selectedContainer: "container2",
|
||||
containerDisabled: false,
|
||||
containerOnChange: jest.fn(),
|
||||
handleOnDemandCreateContainer: jest.fn(),
|
||||
};
|
||||
|
||||
const { container } = render(<DatabaseContainerSection {...fullProps} />);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("matches snapshot with disabled states", () => {
|
||||
const disabledProps: DatabaseContainerSectionProps = {
|
||||
heading: "Disabled Section",
|
||||
databaseOptions: mockDatabaseOptions,
|
||||
selectedDatabase: "db1",
|
||||
databaseDisabled: true,
|
||||
databaseOnChange: jest.fn(),
|
||||
containerOptions: mockContainerOptions,
|
||||
selectedContainer: "container1",
|
||||
containerDisabled: true,
|
||||
containerOnChange: jest.fn(),
|
||||
};
|
||||
|
||||
const { container } = render(<DatabaseContainerSection {...disabledProps} />);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("matches snapshot with empty options", () => {
|
||||
const emptyOptionsProps: DatabaseContainerSectionProps = {
|
||||
heading: "Empty Options",
|
||||
databaseOptions: [],
|
||||
selectedDatabase: "",
|
||||
databaseOnChange: jest.fn(),
|
||||
containerOptions: [],
|
||||
selectedContainer: "",
|
||||
containerOnChange: jest.fn(),
|
||||
};
|
||||
|
||||
const { container } = render(<DatabaseContainerSection {...emptyOptionsProps} />);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,518 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DatabaseContainerSection Snapshot Testing matches snapshot with all props including create container handler 1`] = `
|
||||
<div
|
||||
class="ms-Stack databaseContainerSection css-109"
|
||||
>
|
||||
<label
|
||||
class="subHeading"
|
||||
>
|
||||
Target Container
|
||||
</label>
|
||||
<div
|
||||
class="ms-Stack flex-row css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-111"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Database
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-111"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Database"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-required dropdown-112"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown98"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title title-134"
|
||||
id="Dropdown98-option"
|
||||
>
|
||||
Database 2
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-114"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-132"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ms-Stack flex-row css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-111"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Container
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-111"
|
||||
>
|
||||
<div
|
||||
class="ms-Stack css-133"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Container"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-required dropdown-112"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown99"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title title-134"
|
||||
id="Dropdown99-option"
|
||||
>
|
||||
Container 2
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-114"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-132"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="ms-Button ms-Button--action ms-Button--command create-container-link-btn root-135"
|
||||
data-is-focusable="true"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ms-Button-flexContainer flexContainer-136"
|
||||
data-automationid="splitbuttonprimary"
|
||||
>
|
||||
<span
|
||||
class="ms-Button-textContainer textContainer-137"
|
||||
>
|
||||
<span
|
||||
class="ms-Button-label label-139"
|
||||
id="id__100"
|
||||
>
|
||||
Create a new container
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`DatabaseContainerSection Snapshot Testing matches snapshot with disabled states 1`] = `
|
||||
<div
|
||||
class="ms-Stack databaseContainerSection css-109"
|
||||
>
|
||||
<label
|
||||
class="subHeading"
|
||||
>
|
||||
Disabled Section
|
||||
</label>
|
||||
<div
|
||||
class="ms-Stack flex-row css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-111"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Database
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-111"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-disabled="true"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Database"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-disabled is-required dropdown-143"
|
||||
data-is-focusable="false"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown103"
|
||||
role="combobox"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title title-148"
|
||||
id="Dropdown103-option"
|
||||
>
|
||||
Database 1
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-145"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-147"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ms-Stack flex-row css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-111"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Container
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-111"
|
||||
>
|
||||
<div
|
||||
class="ms-Stack css-133"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-disabled="true"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Container"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-disabled is-required dropdown-143"
|
||||
data-is-focusable="false"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown104"
|
||||
role="combobox"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title title-148"
|
||||
id="Dropdown104-option"
|
||||
>
|
||||
Container 1
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-145"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-147"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`DatabaseContainerSection Snapshot Testing matches snapshot with empty options 1`] = `
|
||||
<div
|
||||
class="ms-Stack databaseContainerSection css-109"
|
||||
>
|
||||
<label
|
||||
class="subHeading"
|
||||
>
|
||||
Empty Options
|
||||
</label>
|
||||
<div
|
||||
class="ms-Stack flex-row css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-111"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Database
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-111"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Database"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-required dropdown-112"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown105"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title ms-Dropdown-titleIsPlaceHolder title-113"
|
||||
id="Dropdown105-option"
|
||||
>
|
||||
Select a database
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-114"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-132"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ms-Stack flex-row css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-111"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Container
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-111"
|
||||
>
|
||||
<div
|
||||
class="ms-Stack css-133"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Container"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-required dropdown-112"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown106"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title ms-Dropdown-titleIsPlaceHolder title-113"
|
||||
id="Dropdown106-option"
|
||||
>
|
||||
Select a container
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-114"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-132"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`DatabaseContainerSection Snapshot Testing matches snapshot with minimal props 1`] = `
|
||||
<div
|
||||
class="ms-Stack databaseContainerSection css-109"
|
||||
>
|
||||
<label
|
||||
class="subHeading"
|
||||
>
|
||||
Source Container
|
||||
</label>
|
||||
<div
|
||||
class="ms-Stack flex-row css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-111"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Database
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-111"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Database"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-required dropdown-112"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown96"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title title-134"
|
||||
id="Dropdown96-option"
|
||||
>
|
||||
Database 1
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-114"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-132"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ms-Stack flex-row css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-111"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Container
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-111"
|
||||
>
|
||||
<div
|
||||
class="ms-Stack css-133"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Container"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-required dropdown-112"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown97"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title title-134"
|
||||
id="Dropdown97-option"
|
||||
>
|
||||
Container 1
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-114"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-132"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,387 @@
|
||||
import "@testing-library/jest-dom";
|
||||
import { render } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { DatabaseAccount, Subscription } from "../../../../../Contracts/DataModels";
|
||||
import { CopyJobMigrationType } from "../../../Enums/CopyJobEnums";
|
||||
import { CopyJobContextState } from "../../../Types/CopyJobTypes";
|
||||
import { useSourceAndTargetData } from "./memoizedData";
|
||||
|
||||
jest.mock("../../../CopyJobUtils", () => ({
|
||||
getAccountDetailsFromResourceId: jest.fn(),
|
||||
}));
|
||||
|
||||
import { getAccountDetailsFromResourceId } from "../../../CopyJobUtils";
|
||||
|
||||
const mockGetAccountDetailsFromResourceId = getAccountDetailsFromResourceId as jest.MockedFunction<
|
||||
typeof getAccountDetailsFromResourceId
|
||||
>;
|
||||
|
||||
interface TestComponentProps {
|
||||
copyJobState: CopyJobContextState | null;
|
||||
onResult?: (result: any) => void;
|
||||
}
|
||||
|
||||
const TestComponent: React.FC<TestComponentProps> = ({ copyJobState, onResult }) => {
|
||||
const result = useSourceAndTargetData(copyJobState);
|
||||
|
||||
React.useEffect(() => {
|
||||
onResult?.(result);
|
||||
}, [result, onResult]);
|
||||
|
||||
return <div data-testid="test-component">Test Component</div>;
|
||||
};
|
||||
|
||||
describe("useSourceAndTargetData", () => {
|
||||
const mockSubscription: Subscription = {
|
||||
subscriptionId: "test-subscription-id",
|
||||
displayName: "Test Subscription",
|
||||
state: "Enabled",
|
||||
subscriptionPolicies: null,
|
||||
authorizationSource: "RoleBased",
|
||||
};
|
||||
|
||||
const mockSourceAccount: DatabaseAccount = {
|
||||
id: "/subscriptions/source-sub-id/resourceGroups/source-rg/providers/Microsoft.DocumentDB/databaseAccounts/source-account",
|
||||
name: "source-account",
|
||||
location: "East US",
|
||||
type: "Microsoft.DocumentDB/databaseAccounts",
|
||||
kind: "GlobalDocumentDB",
|
||||
properties: {
|
||||
documentEndpoint: "https://source-account.documents.azure.com:443/",
|
||||
capabilities: [],
|
||||
locations: [],
|
||||
},
|
||||
};
|
||||
|
||||
const mockTargetAccount: DatabaseAccount = {
|
||||
id: "/subscriptions/target-sub-id/resourceGroups/target-rg/providers/Microsoft.DocumentDB/databaseAccounts/target-account",
|
||||
name: "target-account",
|
||||
location: "West US",
|
||||
type: "Microsoft.DocumentDB/databaseAccounts",
|
||||
kind: "GlobalDocumentDB",
|
||||
properties: {
|
||||
documentEndpoint: "https://target-account.documents.azure.com:443/",
|
||||
capabilities: [],
|
||||
locations: [],
|
||||
},
|
||||
};
|
||||
|
||||
const mockCopyJobState: CopyJobContextState = {
|
||||
jobName: "test-job",
|
||||
migrationType: CopyJobMigrationType.Offline,
|
||||
sourceReadAccessFromTarget: false,
|
||||
source: {
|
||||
subscription: mockSubscription,
|
||||
account: mockSourceAccount,
|
||||
databaseId: "source-db",
|
||||
containerId: "source-container",
|
||||
},
|
||||
target: {
|
||||
subscriptionId: "target-subscription-id",
|
||||
account: mockTargetAccount,
|
||||
databaseId: "target-db",
|
||||
containerId: "target-container",
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockGetAccountDetailsFromResourceId.mockImplementation((accountId) => {
|
||||
if (accountId === mockSourceAccount.id) {
|
||||
return {
|
||||
subscriptionId: "source-sub-id",
|
||||
resourceGroup: "source-rg",
|
||||
accountName: "source-account",
|
||||
};
|
||||
} else if (accountId === mockTargetAccount.id) {
|
||||
return {
|
||||
subscriptionId: "target-sub-id",
|
||||
resourceGroup: "target-rg",
|
||||
accountName: "target-account",
|
||||
};
|
||||
}
|
||||
return null;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("Hook Execution", () => {
|
||||
it("should return correct data structure when copyJobState is provided", () => {
|
||||
let hookResult: any = null;
|
||||
const onResult = jest.fn((result) => {
|
||||
hookResult = result;
|
||||
});
|
||||
|
||||
render(<TestComponent copyJobState={mockCopyJobState} onResult={onResult} />);
|
||||
|
||||
expect(onResult).toHaveBeenCalled();
|
||||
expect(hookResult).toBeDefined();
|
||||
expect(hookResult).toHaveProperty("source");
|
||||
expect(hookResult).toHaveProperty("target");
|
||||
expect(hookResult).toHaveProperty("sourceDbParams");
|
||||
expect(hookResult).toHaveProperty("sourceContainerParams");
|
||||
expect(hookResult).toHaveProperty("targetDbParams");
|
||||
expect(hookResult).toHaveProperty("targetContainerParams");
|
||||
});
|
||||
|
||||
it("should call getAccountDetailsFromResourceId with correct parameters", () => {
|
||||
render(<TestComponent copyJobState={mockCopyJobState} />);
|
||||
|
||||
expect(mockGetAccountDetailsFromResourceId).toHaveBeenCalledWith(mockSourceAccount.id);
|
||||
expect(mockGetAccountDetailsFromResourceId).toHaveBeenCalledWith(mockTargetAccount.id);
|
||||
expect(mockGetAccountDetailsFromResourceId).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("should return source and target objects from copyJobState", () => {
|
||||
let hookResult: any = null;
|
||||
const onResult = jest.fn((result) => {
|
||||
hookResult = result;
|
||||
});
|
||||
|
||||
render(<TestComponent copyJobState={mockCopyJobState} onResult={onResult} />);
|
||||
|
||||
expect(hookResult.source).toEqual(mockCopyJobState.source);
|
||||
expect(hookResult.target).toEqual(mockCopyJobState.target);
|
||||
});
|
||||
|
||||
it("should construct sourceDbParams array correctly", () => {
|
||||
let hookResult: any = null;
|
||||
const onResult = jest.fn((result) => {
|
||||
hookResult = result;
|
||||
});
|
||||
|
||||
render(<TestComponent copyJobState={mockCopyJobState} onResult={onResult} />);
|
||||
|
||||
expect(hookResult.sourceDbParams).toEqual(["source-sub-id", "source-rg", "source-account", "SQL"]);
|
||||
});
|
||||
|
||||
it("should construct sourceContainerParams array correctly", () => {
|
||||
let hookResult: any = null;
|
||||
const onResult = jest.fn((result) => {
|
||||
hookResult = result;
|
||||
});
|
||||
|
||||
render(<TestComponent copyJobState={mockCopyJobState} onResult={onResult} />);
|
||||
|
||||
expect(hookResult.sourceContainerParams).toEqual([
|
||||
"source-sub-id",
|
||||
"source-rg",
|
||||
"source-account",
|
||||
"source-db",
|
||||
"SQL",
|
||||
]);
|
||||
});
|
||||
|
||||
it("should construct targetDbParams array correctly", () => {
|
||||
let hookResult: any = null;
|
||||
const onResult = jest.fn((result) => {
|
||||
hookResult = result;
|
||||
});
|
||||
|
||||
render(<TestComponent copyJobState={mockCopyJobState} onResult={onResult} />);
|
||||
|
||||
expect(hookResult.targetDbParams).toEqual(["target-sub-id", "target-rg", "target-account", "SQL"]);
|
||||
});
|
||||
|
||||
it("should construct targetContainerParams array correctly", () => {
|
||||
let hookResult: any = null;
|
||||
const onResult = jest.fn((result) => {
|
||||
hookResult = result;
|
||||
});
|
||||
|
||||
render(<TestComponent copyJobState={mockCopyJobState} onResult={onResult} />);
|
||||
|
||||
expect(hookResult.targetContainerParams).toEqual([
|
||||
"target-sub-id",
|
||||
"target-rg",
|
||||
"target-account",
|
||||
"target-db",
|
||||
"SQL",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Memoization and Performance", () => {
|
||||
it("should work with React strict mode (double invocation)", () => {
|
||||
let hookResult: any = null;
|
||||
const onResult = jest.fn((result) => {
|
||||
hookResult = result;
|
||||
});
|
||||
|
||||
const { rerender } = render(<TestComponent copyJobState={mockCopyJobState} onResult={onResult} />);
|
||||
const firstResult = { ...hookResult };
|
||||
|
||||
rerender(<TestComponent copyJobState={mockCopyJobState} onResult={onResult} />);
|
||||
const secondResult = { ...hookResult };
|
||||
|
||||
expect(firstResult).toEqual(secondResult);
|
||||
});
|
||||
|
||||
it("should handle component re-renders gracefully", () => {
|
||||
let renderCount = 0;
|
||||
const onResult = jest.fn(() => {
|
||||
renderCount++;
|
||||
});
|
||||
|
||||
const { rerender } = render(<TestComponent copyJobState={mockCopyJobState} onResult={onResult} />);
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
rerender(<TestComponent copyJobState={mockCopyJobState} onResult={onResult} />);
|
||||
}
|
||||
|
||||
expect(renderCount).toBeGreaterThan(0);
|
||||
expect(mockGetAccountDetailsFromResourceId).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should recalculate when copyJobState changes", () => {
|
||||
let hookResult: any = null;
|
||||
const onResult = jest.fn((result) => {
|
||||
hookResult = result;
|
||||
});
|
||||
|
||||
const { rerender } = render(<TestComponent copyJobState={mockCopyJobState} onResult={onResult} />);
|
||||
const firstResult = { ...hookResult };
|
||||
|
||||
const updatedState = {
|
||||
...mockCopyJobState,
|
||||
source: {
|
||||
...mockCopyJobState.source,
|
||||
databaseId: "updated-source-db",
|
||||
},
|
||||
};
|
||||
|
||||
rerender(<TestComponent copyJobState={updatedState} onResult={onResult} />);
|
||||
const secondResult = { ...hookResult };
|
||||
|
||||
expect(firstResult.sourceContainerParams[3]).toBe("source-db");
|
||||
expect(secondResult.sourceContainerParams[3]).toBe("updated-source-db");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Complex State Scenarios", () => {
|
||||
it("should handle state with only source defined", () => {
|
||||
const sourceOnlyState = {
|
||||
...mockCopyJobState,
|
||||
target: undefined as any,
|
||||
};
|
||||
|
||||
let hookResult: any = null;
|
||||
const onResult = jest.fn((result) => {
|
||||
hookResult = result;
|
||||
});
|
||||
|
||||
render(<TestComponent copyJobState={sourceOnlyState} onResult={onResult} />);
|
||||
|
||||
expect(hookResult.source).toBeDefined();
|
||||
expect(hookResult.target).toBeUndefined();
|
||||
expect(hookResult.sourceDbParams).toEqual(["source-sub-id", "source-rg", "source-account", "SQL"]);
|
||||
expect(hookResult.targetDbParams).toEqual([undefined, undefined, undefined, "SQL"]);
|
||||
});
|
||||
|
||||
it("should handle state with only target defined", () => {
|
||||
const targetOnlyState = {
|
||||
...mockCopyJobState,
|
||||
source: undefined as any,
|
||||
};
|
||||
|
||||
let hookResult: any = null;
|
||||
const onResult = jest.fn((result) => {
|
||||
hookResult = result;
|
||||
});
|
||||
|
||||
render(<TestComponent copyJobState={targetOnlyState} onResult={onResult} />);
|
||||
|
||||
expect(hookResult.source).toBeUndefined();
|
||||
expect(hookResult.target).toBeDefined();
|
||||
expect(hookResult.sourceDbParams).toEqual([undefined, undefined, undefined, "SQL"]);
|
||||
expect(hookResult.targetDbParams).toEqual(["target-sub-id", "target-rg", "target-account", "SQL"]);
|
||||
});
|
||||
|
||||
it("should handle state with missing database IDs", () => {
|
||||
const stateWithoutDbIds = {
|
||||
...mockCopyJobState,
|
||||
source: {
|
||||
...mockCopyJobState.source,
|
||||
databaseId: undefined as any,
|
||||
},
|
||||
target: {
|
||||
...mockCopyJobState.target,
|
||||
databaseId: undefined as any,
|
||||
},
|
||||
};
|
||||
|
||||
let hookResult: any = null;
|
||||
const onResult = jest.fn((result) => {
|
||||
hookResult = result;
|
||||
});
|
||||
|
||||
render(<TestComponent copyJobState={stateWithoutDbIds} onResult={onResult} />);
|
||||
|
||||
expect(hookResult.sourceContainerParams[3]).toBeUndefined();
|
||||
expect(hookResult.targetContainerParams[3]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should handle state with missing accounts", () => {
|
||||
const stateWithoutAccounts = {
|
||||
...mockCopyJobState,
|
||||
source: {
|
||||
...mockCopyJobState.source,
|
||||
account: undefined as any,
|
||||
},
|
||||
target: {
|
||||
...mockCopyJobState.target,
|
||||
account: undefined as any,
|
||||
},
|
||||
};
|
||||
|
||||
let hookResult: any = null;
|
||||
const onResult = jest.fn((result) => {
|
||||
hookResult = result;
|
||||
});
|
||||
|
||||
render(<TestComponent copyJobState={stateWithoutAccounts} onResult={onResult} />);
|
||||
|
||||
expect(mockGetAccountDetailsFromResourceId).toHaveBeenCalledWith(undefined);
|
||||
expect(hookResult.sourceDbParams).toEqual([undefined, undefined, undefined, "SQL"]);
|
||||
expect(hookResult.targetDbParams).toEqual([undefined, undefined, undefined, "SQL"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Hook Return Value Structure", () => {
|
||||
it("should return an object with exactly 6 properties", () => {
|
||||
let hookResult: any = null;
|
||||
const onResult = jest.fn((result) => {
|
||||
hookResult = result;
|
||||
});
|
||||
|
||||
render(<TestComponent copyJobState={mockCopyJobState} onResult={onResult} />);
|
||||
|
||||
const keys = Object.keys(hookResult);
|
||||
expect(keys).toHaveLength(6);
|
||||
expect(keys).toContain("source");
|
||||
expect(keys).toContain("target");
|
||||
expect(keys).toContain("sourceDbParams");
|
||||
expect(keys).toContain("sourceContainerParams");
|
||||
expect(keys).toContain("targetDbParams");
|
||||
expect(keys).toContain("targetContainerParams");
|
||||
});
|
||||
|
||||
it("should not return undefined properties when state is valid", () => {
|
||||
let hookResult: any = null;
|
||||
const onResult = jest.fn((result) => {
|
||||
hookResult = result;
|
||||
});
|
||||
|
||||
render(<TestComponent copyJobState={mockCopyJobState} onResult={onResult} />);
|
||||
|
||||
expect(hookResult.source).toBeDefined();
|
||||
expect(hookResult.target).toBeDefined();
|
||||
expect(hookResult.sourceDbParams).toBeDefined();
|
||||
expect(hookResult.sourceContainerParams).toBeDefined();
|
||||
expect(hookResult.targetDbParams).toBeDefined();
|
||||
expect(hookResult.targetContainerParams).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -9,12 +9,12 @@ export function useSourceAndTargetData(copyJobState: CopyJobContextState) {
|
||||
subscriptionId: sourceSubscriptionId,
|
||||
resourceGroup: sourceResourceGroup,
|
||||
accountName: sourceAccountName,
|
||||
} = getAccountDetailsFromResourceId(selectedSourceAccount?.id);
|
||||
} = getAccountDetailsFromResourceId(selectedSourceAccount?.id) || {};
|
||||
const {
|
||||
subscriptionId: targetSubscriptionId,
|
||||
resourceGroup: targetResourceGroup,
|
||||
accountName: targetAccountName,
|
||||
} = getAccountDetailsFromResourceId(selectedTargetAccount?.id);
|
||||
} = getAccountDetailsFromResourceId(selectedTargetAccount?.id) || {};
|
||||
|
||||
const sourceDbParams = [sourceSubscriptionId, sourceResourceGroup, sourceAccountName, "SQL"] as DatabaseParams;
|
||||
const sourceContainerParams = [
|
||||
|
||||
Reference in New Issue
Block a user