mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-18 16:31:31 +00:00
Refactor Container Copy dropdowns with integrated state management (#2279)
This commit is contained in:
@@ -433,7 +433,7 @@ describe("CopyJobActions", () => {
|
||||
(dataTransferService.listByDatabaseAccount as jest.Mock).mockRejectedValue(abortError);
|
||||
|
||||
await expect(getCopyJobs()).rejects.toMatchObject({
|
||||
message: expect.stringContaining("Please wait for the current fetch request to complete"),
|
||||
message: expect.stringContaining("Previous copy job request was cancelled."),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -124,8 +124,7 @@ export const getCopyJobs = async (): Promise<CopyJobType[]> => {
|
||||
const errorContent = JSON.stringify(error.content || error.message || error);
|
||||
if (errorContent.includes("signal is aborted without reason")) {
|
||||
throw {
|
||||
message:
|
||||
"Please wait for the current fetch request to complete. The previous copy job fetch request was aborted.",
|
||||
message: "Previous copy job request was cancelled.",
|
||||
};
|
||||
} else {
|
||||
throw error;
|
||||
|
||||
@@ -162,10 +162,10 @@ export default {
|
||||
viewDetails: "View Details",
|
||||
},
|
||||
Status: {
|
||||
Pending: "Pending",
|
||||
InProgress: "In Progress",
|
||||
Running: "In Progress",
|
||||
Partitioning: "In Progress",
|
||||
Pending: "Queued",
|
||||
InProgress: "Running",
|
||||
Running: "Running",
|
||||
Partitioning: "Running",
|
||||
Paused: "Paused",
|
||||
Completed: "Completed",
|
||||
Failed: "Failed",
|
||||
|
||||
@@ -59,15 +59,8 @@ describe("CopyJobContext", () => {
|
||||
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",
|
||||
location: "East US",
|
||||
kind: "GlobalDocumentDB",
|
||||
},
|
||||
subscription: null,
|
||||
account: null,
|
||||
databaseId: "",
|
||||
containerId: "",
|
||||
},
|
||||
@@ -605,8 +598,8 @@ describe("CopyJobContext", () => {
|
||||
</CopyJobContextProvider>,
|
||||
);
|
||||
|
||||
expect(contextValue.copyJobState.source.subscription.subscriptionId).toBe("test-subscription-id");
|
||||
expect(contextValue.copyJobState.source.account.name).toBe("test-account");
|
||||
expect(contextValue.copyJobState.source?.subscription?.subscriptionId).toBeUndefined();
|
||||
expect(contextValue.copyJobState.source?.account?.name).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should initialize target with userContext values", () => {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { Subscription } from "Contracts/DataModels";
|
||||
import React from "react";
|
||||
import { userContext } from "UserContext";
|
||||
import { CopyJobMigrationType } from "../Enums/CopyJobEnums";
|
||||
@@ -24,10 +23,8 @@ const getInitialCopyJobState = (): CopyJobContextState => {
|
||||
jobName: "",
|
||||
migrationType: CopyJobMigrationType.Offline,
|
||||
source: {
|
||||
subscription: {
|
||||
subscriptionId: userContext.subscriptionId || "",
|
||||
} as Subscription,
|
||||
account: userContext.databaseAccount || null,
|
||||
subscription: null,
|
||||
account: null,
|
||||
databaseId: "",
|
||||
containerId: "",
|
||||
},
|
||||
|
||||
@@ -147,7 +147,7 @@ export function isEqual(prevJobs: CopyJobType[], newJobs: CopyJobType[]): boolea
|
||||
}
|
||||
|
||||
const truncateLength = 5;
|
||||
const truncateName = (name: string, length: number = truncateLength): string => {
|
||||
export const truncateName = (name: string, length: number = truncateLength): string => {
|
||||
return name.length <= length ? name : name.slice(0, length);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,219 +1,409 @@
|
||||
import "@testing-library/jest-dom";
|
||||
import { render } from "@testing-library/react";
|
||||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { DropdownOptionType } from "../../../../Types/CopyJobTypes";
|
||||
import { configContext, Platform } from "../../../../../../ConfigContext";
|
||||
import { DatabaseAccount } from "../../../../../../Contracts/DataModels";
|
||||
import * as useDatabaseAccountsHook from "../../../../../../hooks/useDatabaseAccounts";
|
||||
import { apiType, userContext } from "../../../../../../UserContext";
|
||||
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
|
||||
import { CopyJobContext } from "../../../../Context/CopyJobContext";
|
||||
import { CopyJobMigrationType } from "../../../../Enums/CopyJobEnums";
|
||||
import { CopyJobContextProviderType, CopyJobContextState } from "../../../../Types/CopyJobTypes";
|
||||
import { AccountDropdown } from "./AccountDropdown";
|
||||
|
||||
describe("AccountDropdown", () => {
|
||||
const mockOnChange = jest.fn();
|
||||
jest.mock("../../../../../../hooks/useDatabaseAccounts");
|
||||
jest.mock("../../../../../../UserContext", () => ({
|
||||
userContext: {
|
||||
databaseAccount: null as DatabaseAccount | null,
|
||||
},
|
||||
apiType: jest.fn(),
|
||||
}));
|
||||
jest.mock("../../../../../../ConfigContext", () => ({
|
||||
configContext: {
|
||||
platform: "Portal",
|
||||
},
|
||||
Platform: {
|
||||
Portal: "Portal",
|
||||
Hosted: "Hosted",
|
||||
},
|
||||
}));
|
||||
|
||||
const mockAccountOptions: DropdownOptionType[] = [
|
||||
{
|
||||
key: "account-1",
|
||||
text: "Development Account",
|
||||
data: {
|
||||
id: "account-1",
|
||||
name: "Development Account",
|
||||
location: "East US",
|
||||
resourceGroup: "dev-rg",
|
||||
kind: "GlobalDocumentDB",
|
||||
properties: {
|
||||
documentEndpoint: "https://dev-account.documents.azure.com:443/",
|
||||
provisioningState: "Succeeded",
|
||||
consistencyPolicy: {
|
||||
defaultConsistencyLevel: "Session",
|
||||
},
|
||||
},
|
||||
const mockUseDatabaseAccounts = useDatabaseAccountsHook.useDatabaseAccounts as jest.MockedFunction<
|
||||
typeof useDatabaseAccountsHook.useDatabaseAccounts
|
||||
>;
|
||||
|
||||
describe("AccountDropdown", () => {
|
||||
const mockSetCopyJobState = jest.fn();
|
||||
const mockCopyJobState = {
|
||||
jobName: "",
|
||||
migrationType: CopyJobMigrationType.Offline,
|
||||
source: {
|
||||
subscription: {
|
||||
subscriptionId: "test-subscription-id",
|
||||
displayName: "Test Subscription",
|
||||
},
|
||||
account: null,
|
||||
databaseId: "",
|
||||
containerId: "",
|
||||
},
|
||||
{
|
||||
key: "account-2",
|
||||
text: "Production Account",
|
||||
data: {
|
||||
id: "account-2",
|
||||
name: "Production Account",
|
||||
location: "West US 2",
|
||||
resourceGroup: "prod-rg",
|
||||
kind: "GlobalDocumentDB",
|
||||
properties: {
|
||||
documentEndpoint: "https://prod-account.documents.azure.com:443/",
|
||||
provisioningState: "Succeeded",
|
||||
consistencyPolicy: {
|
||||
defaultConsistencyLevel: "Strong",
|
||||
},
|
||||
},
|
||||
},
|
||||
target: {
|
||||
subscriptionId: "",
|
||||
account: null,
|
||||
databaseId: "",
|
||||
containerId: "",
|
||||
},
|
||||
{
|
||||
key: "account-3",
|
||||
text: "Testing Account",
|
||||
data: {
|
||||
id: "account-3",
|
||||
name: "Testing Account",
|
||||
location: "Central US",
|
||||
resourceGroup: "test-rg",
|
||||
kind: "GlobalDocumentDB",
|
||||
properties: {
|
||||
documentEndpoint: "https://test-account.documents.azure.com:443/",
|
||||
provisioningState: "Succeeded",
|
||||
consistencyPolicy: {
|
||||
defaultConsistencyLevel: "Eventual",
|
||||
},
|
||||
},
|
||||
},
|
||||
sourceReadAccessFromTarget: false,
|
||||
} as CopyJobContextState;
|
||||
|
||||
const mockCopyJobContextValue = {
|
||||
copyJobState: mockCopyJobState,
|
||||
setCopyJobState: mockSetCopyJobState,
|
||||
flow: null,
|
||||
setFlow: jest.fn(),
|
||||
contextError: null,
|
||||
setContextError: jest.fn(),
|
||||
resetCopyJobState: jest.fn(),
|
||||
} as CopyJobContextProviderType;
|
||||
|
||||
const mockDatabaseAccount1: DatabaseAccount = {
|
||||
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDb/databaseAccounts/account1",
|
||||
name: "test-account-1",
|
||||
kind: "GlobalDocumentDB",
|
||||
location: "East US",
|
||||
type: "Microsoft.DocumentDB/databaseAccounts",
|
||||
tags: {},
|
||||
properties: {
|
||||
documentEndpoint: "https://account1.documents.azure.com:443/",
|
||||
capabilities: [],
|
||||
enableMultipleWriteLocations: false,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const mockDatabaseAccount2: DatabaseAccount = {
|
||||
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDb/databaseAccounts/account2",
|
||||
name: "test-account-2",
|
||||
kind: "GlobalDocumentDB",
|
||||
location: "West US",
|
||||
type: "Microsoft.DocumentDB/databaseAccounts",
|
||||
tags: {},
|
||||
properties: {
|
||||
documentEndpoint: "https://account2.documents.azure.com:443/",
|
||||
capabilities: [],
|
||||
enableMultipleWriteLocations: false,
|
||||
},
|
||||
};
|
||||
|
||||
const mockNonSqlAccount: DatabaseAccount = {
|
||||
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDb/databaseAccounts/mongo-account",
|
||||
name: "mongo-account",
|
||||
kind: "MongoDB",
|
||||
location: "Central US",
|
||||
type: "Microsoft.DocumentDB/databaseAccounts",
|
||||
tags: {},
|
||||
properties: {
|
||||
documentEndpoint: "https://mongo-account.documents.azure.com:443/",
|
||||
capabilities: [],
|
||||
enableMultipleWriteLocations: false,
|
||||
},
|
||||
};
|
||||
|
||||
const renderWithContext = (contextValue = mockCopyJobContextValue) => {
|
||||
return render(
|
||||
<CopyJobContext.Provider value={contextValue}>
|
||||
<AccountDropdown />
|
||||
</CopyJobContext.Provider>,
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(apiType as jest.MockedFunction<any>).mockImplementation((account: DatabaseAccount) => {
|
||||
return account.kind === "MongoDB" ? "MongoDB" : "SQL";
|
||||
});
|
||||
});
|
||||
|
||||
describe("Snapshot Testing", () => {
|
||||
it("matches snapshot with all account options", () => {
|
||||
const { container } = render(
|
||||
<AccountDropdown options={mockAccountOptions} disabled={false} onChange={mockOnChange} />,
|
||||
describe("Rendering", () => {
|
||||
it("should render dropdown with correct label and placeholder", () => {
|
||||
mockUseDatabaseAccounts.mockReturnValue([]);
|
||||
|
||||
renderWithContext();
|
||||
|
||||
expect(
|
||||
screen.getByText(`${ContainerCopyMessages.sourceAccountDropdownLabel}:`, { exact: true }),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole("combobox")).toHaveAttribute(
|
||||
"aria-label",
|
||||
ContainerCopyMessages.sourceAccountDropdownLabel,
|
||||
);
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("matches snapshot with selected account", () => {
|
||||
const { container } = render(
|
||||
<AccountDropdown
|
||||
options={mockAccountOptions}
|
||||
selectedKey="account-2"
|
||||
disabled={false}
|
||||
onChange={mockOnChange}
|
||||
/>,
|
||||
);
|
||||
it("should render disabled dropdown when no subscription is selected", () => {
|
||||
mockUseDatabaseAccounts.mockReturnValue([]);
|
||||
const contextWithoutSubscription = {
|
||||
...mockCopyJobContextValue,
|
||||
copyJobState: {
|
||||
...mockCopyJobState,
|
||||
source: {
|
||||
...mockCopyJobState.source,
|
||||
subscription: null,
|
||||
},
|
||||
} as CopyJobContextState,
|
||||
};
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
renderWithContext(contextWithoutSubscription);
|
||||
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toHaveAttribute("aria-disabled", "true");
|
||||
});
|
||||
|
||||
it("matches snapshot with disabled dropdown", () => {
|
||||
const { container } = render(
|
||||
<AccountDropdown
|
||||
options={mockAccountOptions}
|
||||
selectedKey="account-1"
|
||||
disabled={true}
|
||||
onChange={mockOnChange}
|
||||
/>,
|
||||
);
|
||||
it("should render disabled dropdown when no accounts are available", () => {
|
||||
mockUseDatabaseAccounts.mockReturnValue([]);
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
renderWithContext();
|
||||
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toHaveAttribute("aria-disabled", "true");
|
||||
});
|
||||
|
||||
it("matches snapshot with empty options", () => {
|
||||
const { container } = render(<AccountDropdown options={[]} disabled={false} onChange={mockOnChange} />);
|
||||
it("should render enabled dropdown when accounts are available", () => {
|
||||
mockUseDatabaseAccounts.mockReturnValue([mockDatabaseAccount1, mockDatabaseAccount2]);
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
renderWithContext();
|
||||
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toHaveAttribute("aria-disabled", "false");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Account filtering", () => {
|
||||
it("should filter accounts to only show SQL API accounts", () => {
|
||||
const allAccounts = [mockDatabaseAccount1, mockDatabaseAccount2, mockNonSqlAccount];
|
||||
mockUseDatabaseAccounts.mockReturnValue(allAccounts);
|
||||
|
||||
renderWithContext();
|
||||
|
||||
expect(mockUseDatabaseAccounts).toHaveBeenCalledWith("test-subscription-id");
|
||||
|
||||
expect(apiType as jest.MockedFunction<any>).toHaveBeenCalledWith(mockDatabaseAccount1);
|
||||
expect(apiType as jest.MockedFunction<any>).toHaveBeenCalledWith(mockDatabaseAccount2);
|
||||
expect(apiType as jest.MockedFunction<any>).toHaveBeenCalledWith(mockNonSqlAccount);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Account selection", () => {
|
||||
it("should auto-select the first SQL account when no account is currently selected", async () => {
|
||||
mockUseDatabaseAccounts.mockReturnValue([mockDatabaseAccount1, mockDatabaseAccount2]);
|
||||
|
||||
renderWithContext();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
||||
});
|
||||
|
||||
const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0];
|
||||
const newState = stateUpdateFunction(mockCopyJobState);
|
||||
expect(newState.source.account).toBe(mockDatabaseAccount1);
|
||||
});
|
||||
|
||||
it("matches snapshot with single option", () => {
|
||||
const { container } = render(
|
||||
<AccountDropdown
|
||||
options={[mockAccountOptions[0]]}
|
||||
selectedKey="account-1"
|
||||
disabled={false}
|
||||
onChange={mockOnChange}
|
||||
/>,
|
||||
);
|
||||
it("should auto-select predefined account from userContext if available", async () => {
|
||||
const userContextAccount = {
|
||||
...mockDatabaseAccount2,
|
||||
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDb/databaseAccounts/account2",
|
||||
};
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
(userContext as any).databaseAccount = userContextAccount;
|
||||
|
||||
mockUseDatabaseAccounts.mockReturnValue([mockDatabaseAccount1, mockDatabaseAccount2]);
|
||||
|
||||
renderWithContext();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
||||
});
|
||||
|
||||
const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0];
|
||||
const newState = stateUpdateFunction(mockCopyJobState);
|
||||
expect(newState.source.account).toBe(mockDatabaseAccount2);
|
||||
});
|
||||
|
||||
it("matches snapshot with special characters in options", () => {
|
||||
const specialOptions = [
|
||||
{
|
||||
key: "special",
|
||||
text: 'Account with & <special> "characters"',
|
||||
data: {
|
||||
id: "special",
|
||||
name: 'Account with & <special> "characters"',
|
||||
location: "East US",
|
||||
it("should keep current account if it exists in the filtered list", async () => {
|
||||
const contextWithSelectedAccount = {
|
||||
...mockCopyJobContextValue,
|
||||
copyJobState: {
|
||||
...mockCopyJobState,
|
||||
source: {
|
||||
...mockCopyJobState.source,
|
||||
account: mockDatabaseAccount1,
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const { container } = render(
|
||||
<AccountDropdown options={specialOptions} disabled={false} onChange={mockOnChange} />,
|
||||
);
|
||||
mockUseDatabaseAccounts.mockReturnValue([mockDatabaseAccount1, mockDatabaseAccount2]);
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
renderWithContext(contextWithSelectedAccount);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
||||
});
|
||||
|
||||
const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0];
|
||||
const newState = stateUpdateFunction(contextWithSelectedAccount.copyJobState);
|
||||
expect(newState).toBe(contextWithSelectedAccount.copyJobState);
|
||||
});
|
||||
|
||||
it("matches snapshot with long account name", () => {
|
||||
const longNameOption = [
|
||||
{
|
||||
key: "long",
|
||||
text: "This is an extremely long account name that tests how the component handles text overflow and layout constraints in the dropdown",
|
||||
data: {
|
||||
id: "long",
|
||||
name: "This is an extremely long account name that tests how the component handles text overflow and layout constraints in the dropdown",
|
||||
location: "North Central US",
|
||||
it("should handle account change when user selects different account", async () => {
|
||||
mockUseDatabaseAccounts.mockReturnValue([mockDatabaseAccount1, mockDatabaseAccount2]);
|
||||
|
||||
renderWithContext();
|
||||
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
fireEvent.click(dropdown);
|
||||
|
||||
await waitFor(() => {
|
||||
const option = screen.getByText("test-account-2");
|
||||
fireEvent.click(option);
|
||||
});
|
||||
|
||||
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
describe("ID normalization", () => {
|
||||
it("should normalize account ID for Portal platform", () => {
|
||||
const portalAccount = {
|
||||
...mockDatabaseAccount1,
|
||||
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDb/databaseAccounts/account1",
|
||||
};
|
||||
|
||||
(configContext as any).platform = Platform.Portal;
|
||||
mockUseDatabaseAccounts.mockReturnValue([portalAccount]);
|
||||
|
||||
const contextWithSelectedAccount = {
|
||||
...mockCopyJobContextValue,
|
||||
copyJobState: {
|
||||
...mockCopyJobState,
|
||||
source: {
|
||||
...mockCopyJobState.source,
|
||||
account: portalAccount,
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const { container } = render(
|
||||
<AccountDropdown options={longNameOption} selectedKey="long" disabled={false} onChange={mockOnChange} />,
|
||||
);
|
||||
renderWithContext(contextWithSelectedAccount);
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("matches snapshot with disabled state and no selection", () => {
|
||||
const { container } = render(
|
||||
<AccountDropdown options={mockAccountOptions} disabled={true} onChange={mockOnChange} />,
|
||||
);
|
||||
it("should normalize account ID for Hosted platform", () => {
|
||||
const hostedAccount = {
|
||||
...mockDatabaseAccount1,
|
||||
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDB/databaseAccounts/account1",
|
||||
};
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
(configContext as any).platform = Platform.Hosted;
|
||||
mockUseDatabaseAccounts.mockReturnValue([hostedAccount]);
|
||||
|
||||
const contextWithSelectedAccount = {
|
||||
...mockCopyJobContextValue,
|
||||
copyJobState: {
|
||||
...mockCopyJobState,
|
||||
source: {
|
||||
...mockCopyJobState.source,
|
||||
account: hostedAccount,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
renderWithContext(contextWithSelectedAccount);
|
||||
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edge cases", () => {
|
||||
it("should handle empty account list gracefully", () => {
|
||||
mockUseDatabaseAccounts.mockReturnValue([]);
|
||||
|
||||
renderWithContext();
|
||||
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toHaveAttribute("aria-disabled", "true");
|
||||
});
|
||||
|
||||
it("matches snapshot with multiple account types", () => {
|
||||
const mixedAccountOptions = [
|
||||
{
|
||||
key: "sql-account",
|
||||
text: "SQL API Account",
|
||||
data: {
|
||||
id: "sql-account",
|
||||
name: "SQL API Account",
|
||||
kind: "GlobalDocumentDB",
|
||||
location: "East US",
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "mongo-account",
|
||||
text: "MongoDB Account",
|
||||
data: {
|
||||
id: "mongo-account",
|
||||
name: "MongoDB Account",
|
||||
kind: "MongoDB",
|
||||
location: "West US",
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "cassandra-account",
|
||||
text: "Cassandra Account",
|
||||
data: {
|
||||
id: "cassandra-account",
|
||||
name: "Cassandra Account",
|
||||
kind: "Cassandra",
|
||||
location: "Central US",
|
||||
},
|
||||
},
|
||||
];
|
||||
it("should handle null account list gracefully", () => {
|
||||
mockUseDatabaseAccounts.mockReturnValue(null as any);
|
||||
|
||||
const { container } = render(
|
||||
<AccountDropdown
|
||||
options={mixedAccountOptions}
|
||||
selectedKey="mongo-account"
|
||||
disabled={false}
|
||||
onChange={mockOnChange}
|
||||
/>,
|
||||
);
|
||||
renderWithContext();
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toHaveAttribute("aria-disabled", "true");
|
||||
});
|
||||
|
||||
it("should handle undefined subscription ID", () => {
|
||||
const contextWithoutSubscription = {
|
||||
...mockCopyJobContextValue,
|
||||
copyJobState: {
|
||||
...mockCopyJobState,
|
||||
source: {
|
||||
...mockCopyJobState.source,
|
||||
subscription: null,
|
||||
},
|
||||
} as CopyJobContextState,
|
||||
};
|
||||
|
||||
mockUseDatabaseAccounts.mockReturnValue([]);
|
||||
|
||||
renderWithContext(contextWithoutSubscription);
|
||||
|
||||
expect(mockUseDatabaseAccounts).toHaveBeenCalledWith(undefined);
|
||||
});
|
||||
|
||||
it("should not update state if account is already selected and the same", async () => {
|
||||
const selectedAccount = mockDatabaseAccount1;
|
||||
const contextWithSelectedAccount = {
|
||||
...mockCopyJobContextValue,
|
||||
copyJobState: {
|
||||
...mockCopyJobState,
|
||||
source: {
|
||||
...mockCopyJobState.source,
|
||||
account: selectedAccount,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
mockUseDatabaseAccounts.mockReturnValue([mockDatabaseAccount1, mockDatabaseAccount2]);
|
||||
|
||||
renderWithContext(contextWithSelectedAccount);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
||||
});
|
||||
|
||||
const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0];
|
||||
const newState = stateUpdateFunction(contextWithSelectedAccount.copyJobState);
|
||||
expect(newState).toBe(contextWithSelectedAccount.copyJobState);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Accessibility", () => {
|
||||
it("should have proper aria-label", () => {
|
||||
mockUseDatabaseAccounts.mockReturnValue([mockDatabaseAccount1]);
|
||||
|
||||
renderWithContext();
|
||||
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toHaveAttribute("aria-label", ContainerCopyMessages.sourceAccountDropdownLabel);
|
||||
});
|
||||
|
||||
it("should have required attribute", () => {
|
||||
mockUseDatabaseAccounts.mockReturnValue([mockDatabaseAccount1]);
|
||||
|
||||
renderWithContext();
|
||||
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toHaveAttribute("aria-required", "true");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,31 +1,91 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
/* eslint-disable react/display-name */
|
||||
import { Dropdown } from "@fluentui/react";
|
||||
import React from "react";
|
||||
import { configContext, Platform } from "ConfigContext";
|
||||
import React, { useEffect } from "react";
|
||||
import { DatabaseAccount } from "../../../../../../Contracts/DataModels";
|
||||
import { useDatabaseAccounts } from "../../../../../../hooks/useDatabaseAccounts";
|
||||
import { apiType, userContext } from "../../../../../../UserContext";
|
||||
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
|
||||
import { DropdownOptionType } from "../../../../Types/CopyJobTypes";
|
||||
import { useCopyJobContext } from "../../../../Context/CopyJobContext";
|
||||
import FieldRow from "../../Components/FieldRow";
|
||||
|
||||
interface AccountDropdownProps {
|
||||
options: DropdownOptionType[];
|
||||
selectedKey?: string;
|
||||
disabled: boolean;
|
||||
onChange: (_ev?: React.FormEvent, option?: DropdownOptionType) => void;
|
||||
}
|
||||
interface AccountDropdownProps {}
|
||||
|
||||
export const AccountDropdown: React.FC<AccountDropdownProps> = React.memo(
|
||||
({ options, selectedKey, disabled, onChange }) => (
|
||||
const normalizeAccountId = (id: string) => {
|
||||
if (configContext.platform === Platform.Portal) {
|
||||
return id.replace("/Microsoft.DocumentDb/", "/Microsoft.DocumentDB/");
|
||||
} else if (configContext.platform === Platform.Hosted) {
|
||||
return id.replace("/Microsoft.DocumentDB/", "/Microsoft.DocumentDb/");
|
||||
} else {
|
||||
return id;
|
||||
}
|
||||
};
|
||||
|
||||
export const AccountDropdown: React.FC<AccountDropdownProps> = () => {
|
||||
const { copyJobState, setCopyJobState } = useCopyJobContext();
|
||||
|
||||
const selectedSubscriptionId = copyJobState?.source?.subscription?.subscriptionId;
|
||||
const allAccounts: DatabaseAccount[] = useDatabaseAccounts(selectedSubscriptionId);
|
||||
const sqlApiOnlyAccounts: DatabaseAccount[] = (allAccounts || []).filter((account) => apiType(account) === "SQL");
|
||||
|
||||
const updateCopyJobState = (newAccount: DatabaseAccount) => {
|
||||
setCopyJobState((prevState) => {
|
||||
if (prevState.source?.account?.id !== newAccount.id) {
|
||||
return {
|
||||
...prevState,
|
||||
source: {
|
||||
...prevState.source,
|
||||
account: newAccount,
|
||||
},
|
||||
};
|
||||
}
|
||||
return prevState;
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (sqlApiOnlyAccounts && sqlApiOnlyAccounts.length > 0 && selectedSubscriptionId) {
|
||||
const currentAccountId = copyJobState?.source?.account?.id;
|
||||
const predefinedAccountId = userContext.databaseAccount?.id;
|
||||
const selectedAccountId = currentAccountId || predefinedAccountId;
|
||||
|
||||
const targetAccount: DatabaseAccount | null =
|
||||
sqlApiOnlyAccounts.find((account) => account.id === selectedAccountId) || null;
|
||||
updateCopyJobState(targetAccount || sqlApiOnlyAccounts[0]);
|
||||
}
|
||||
}, [sqlApiOnlyAccounts?.length, selectedSubscriptionId]);
|
||||
|
||||
const accountOptions =
|
||||
sqlApiOnlyAccounts?.map((account) => ({
|
||||
key: normalizeAccountId(account.id),
|
||||
text: account.name,
|
||||
data: account,
|
||||
})) || [];
|
||||
|
||||
const handleAccountChange = (_ev?: React.FormEvent, option?: (typeof accountOptions)[0]) => {
|
||||
const selectedAccount = option?.data as DatabaseAccount;
|
||||
|
||||
if (selectedAccount) {
|
||||
updateCopyJobState(selectedAccount);
|
||||
}
|
||||
};
|
||||
|
||||
const isAccountDropdownDisabled = !selectedSubscriptionId || accountOptions.length === 0;
|
||||
const selectedAccountId = normalizeAccountId(copyJobState?.source?.account?.id ?? "");
|
||||
|
||||
return (
|
||||
<FieldRow label={ContainerCopyMessages.sourceAccountDropdownLabel}>
|
||||
<Dropdown
|
||||
placeholder={ContainerCopyMessages.sourceAccountDropdownPlaceholder}
|
||||
ariaLabel={ContainerCopyMessages.sourceAccountDropdownLabel}
|
||||
options={options}
|
||||
disabled={disabled}
|
||||
options={accountOptions}
|
||||
disabled={isAccountDropdownDisabled}
|
||||
required
|
||||
selectedKey={selectedKey}
|
||||
onChange={onChange}
|
||||
selectedKey={selectedAccountId}
|
||||
onChange={handleAccountChange}
|
||||
data-test="account-dropdown"
|
||||
/>
|
||||
</FieldRow>
|
||||
),
|
||||
(prev, next) => prev.options.length === next.options.length && prev.selectedKey === next.selectedKey,
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,118 +1,295 @@
|
||||
import "@testing-library/jest-dom";
|
||||
import { render } from "@testing-library/react";
|
||||
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { DropdownOptionType } from "../../../../Types/CopyJobTypes";
|
||||
import { Subscription } from "../../../../../../Contracts/DataModels";
|
||||
import Explorer from "../../../../../Explorer";
|
||||
import CopyJobContextProvider from "../../../../Context/CopyJobContext";
|
||||
import { SubscriptionDropdown } from "./SubscriptionDropdown";
|
||||
|
||||
describe("SubscriptionDropdown", () => {
|
||||
const mockOnChange = jest.fn();
|
||||
jest.mock("../../../../../../hooks/useSubscriptions");
|
||||
jest.mock("../../../../../../UserContext");
|
||||
jest.mock("../../../../ContainerCopyMessages");
|
||||
|
||||
const mockSubscriptionOptions: DropdownOptionType[] = [
|
||||
const mockUseSubscriptions = jest.requireMock("../../../../../../hooks/useSubscriptions").useSubscriptions;
|
||||
const mockUserContext = jest.requireMock("../../../../../../UserContext").userContext;
|
||||
const mockContainerCopyMessages = jest.requireMock("../../../../ContainerCopyMessages").default;
|
||||
|
||||
mockContainerCopyMessages.subscriptionDropdownLabel = "Subscription";
|
||||
mockContainerCopyMessages.subscriptionDropdownPlaceholder = "Select a subscription";
|
||||
|
||||
describe("SubscriptionDropdown", () => {
|
||||
let mockExplorer: Explorer;
|
||||
const mockSubscriptions: Subscription[] = [
|
||||
{
|
||||
key: "sub-1",
|
||||
text: "Development Subscription",
|
||||
data: {
|
||||
subscriptionId: "sub-1",
|
||||
displayName: "Development Subscription",
|
||||
authorizationSource: "RoleBased",
|
||||
subscriptionPolicies: {
|
||||
quotaId: "quota-1",
|
||||
spendingLimit: "Off",
|
||||
locationPlacementId: "loc-1",
|
||||
},
|
||||
},
|
||||
subscriptionId: "sub-1",
|
||||
displayName: "Subscription One",
|
||||
state: "Enabled",
|
||||
tenantId: "tenant-1",
|
||||
},
|
||||
{
|
||||
key: "sub-2",
|
||||
text: "Production Subscription",
|
||||
data: {
|
||||
subscriptionId: "sub-2",
|
||||
displayName: "Production Subscription",
|
||||
authorizationSource: "RoleBased",
|
||||
subscriptionPolicies: {
|
||||
quotaId: "quota-2",
|
||||
spendingLimit: "On",
|
||||
locationPlacementId: "loc-2",
|
||||
},
|
||||
},
|
||||
subscriptionId: "sub-2",
|
||||
displayName: "Subscription Two",
|
||||
state: "Enabled",
|
||||
tenantId: "tenant-1",
|
||||
},
|
||||
{
|
||||
key: "sub-3",
|
||||
text: "Testing Subscription",
|
||||
data: {
|
||||
subscriptionId: "sub-3",
|
||||
displayName: "Testing Subscription",
|
||||
authorizationSource: "Legacy",
|
||||
subscriptionPolicies: {
|
||||
quotaId: "quota-3",
|
||||
spendingLimit: "Off",
|
||||
locationPlacementId: "loc-3",
|
||||
},
|
||||
},
|
||||
subscriptionId: "sub-3",
|
||||
displayName: "Another Subscription",
|
||||
state: "Enabled",
|
||||
tenantId: "tenant-1",
|
||||
},
|
||||
];
|
||||
|
||||
const renderWithProvider = (children: React.ReactNode) => {
|
||||
return render(<CopyJobContextProvider explorer={mockExplorer}>{children}</CopyJobContextProvider>);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockExplorer = {} as Explorer;
|
||||
|
||||
mockUseSubscriptions.mockReturnValue(mockSubscriptions);
|
||||
mockUserContext.subscriptionId = "sub-1";
|
||||
});
|
||||
|
||||
describe("Snapshot Testing", () => {
|
||||
it("matches snapshot with all subscription options", () => {
|
||||
const { container } = render(<SubscriptionDropdown options={mockSubscriptionOptions} onChange={mockOnChange} />);
|
||||
describe("Rendering", () => {
|
||||
it("should render subscription dropdown with correct attributes", () => {
|
||||
renderWithProvider(<SubscriptionDropdown />);
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toBeInTheDocument();
|
||||
expect(dropdown).toHaveAttribute("aria-label", "Subscription");
|
||||
expect(dropdown).toHaveAttribute("data-test", "subscription-dropdown");
|
||||
expect(dropdown).toBeRequired();
|
||||
});
|
||||
|
||||
it("matches snapshot with selected subscription", () => {
|
||||
const { container } = render(
|
||||
<SubscriptionDropdown options={mockSubscriptionOptions} selectedKey="sub-2" onChange={mockOnChange} />,
|
||||
it("should render field label correctly", () => {
|
||||
renderWithProvider(<SubscriptionDropdown />);
|
||||
|
||||
expect(screen.getByText("Subscription:")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show placeholder when no subscription is selected", async () => {
|
||||
mockUserContext.subscriptionId = "";
|
||||
mockUseSubscriptions.mockReturnValue([]);
|
||||
|
||||
renderWithProvider(<SubscriptionDropdown />);
|
||||
|
||||
await waitFor(() => {
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toHaveTextContent("Select a subscription");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Subscription Options", () => {
|
||||
it("should populate dropdown with available subscriptions", async () => {
|
||||
renderWithProvider(<SubscriptionDropdown />);
|
||||
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
fireEvent.click(dropdown);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Subscription One", { selector: ".ms-Dropdown-optionText" })).toBeInTheDocument();
|
||||
expect(screen.getByText("Subscription Two", { selector: ".ms-Dropdown-optionText" })).toBeInTheDocument();
|
||||
expect(screen.getByText("Another Subscription", { selector: ".ms-Dropdown-optionText" })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle empty subscriptions list", () => {
|
||||
mockUseSubscriptions.mockReturnValue([]);
|
||||
|
||||
renderWithProvider(<SubscriptionDropdown />);
|
||||
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toBeInTheDocument();
|
||||
expect(dropdown).toHaveTextContent("Select a subscription");
|
||||
});
|
||||
|
||||
it("should handle undefined subscriptions", () => {
|
||||
mockUseSubscriptions.mockReturnValue(undefined);
|
||||
|
||||
renderWithProvider(<SubscriptionDropdown />);
|
||||
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toBeInTheDocument();
|
||||
expect(dropdown).toHaveTextContent("Select a subscription");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Selection Logic", () => {
|
||||
it("should auto-select subscription based on userContext.subscriptionId on mount", async () => {
|
||||
mockUserContext.subscriptionId = "sub-2";
|
||||
|
||||
renderWithProvider(<SubscriptionDropdown />);
|
||||
|
||||
await waitFor(() => {
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toHaveTextContent("Subscription Two");
|
||||
});
|
||||
});
|
||||
|
||||
it("should maintain current selection when subscriptions list updates with same subscription", async () => {
|
||||
renderWithProvider(<SubscriptionDropdown />);
|
||||
|
||||
await waitFor(() => {
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toHaveTextContent("Subscription One");
|
||||
});
|
||||
|
||||
act(() => {
|
||||
mockUseSubscriptions.mockReturnValue([...mockSubscriptions]);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toHaveTextContent("Subscription One");
|
||||
});
|
||||
});
|
||||
|
||||
it("should prioritize current copyJobState subscription over userContext subscription", async () => {
|
||||
mockUserContext.subscriptionId = "sub-2";
|
||||
|
||||
const { rerender } = renderWithProvider(<SubscriptionDropdown />);
|
||||
|
||||
await waitFor(() => {
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toHaveTextContent("Subscription Two");
|
||||
});
|
||||
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
fireEvent.click(dropdown);
|
||||
|
||||
await waitFor(() => {
|
||||
const option = screen.getByText("Another Subscription");
|
||||
fireEvent.click(option);
|
||||
});
|
||||
|
||||
rerender(
|
||||
<CopyJobContextProvider explorer={mockExplorer}>
|
||||
<SubscriptionDropdown />
|
||||
</CopyJobContextProvider>,
|
||||
);
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
await waitFor(() => {
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toHaveTextContent("Another Subscription");
|
||||
});
|
||||
});
|
||||
|
||||
it("matches snapshot with empty options", () => {
|
||||
const { container } = render(<SubscriptionDropdown options={[]} onChange={mockOnChange} />);
|
||||
it("should handle subscription selection change", async () => {
|
||||
renderWithProvider(<SubscriptionDropdown />);
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
fireEvent.click(dropdown);
|
||||
|
||||
await waitFor(() => {
|
||||
const option = screen.getByText("Subscription Two");
|
||||
fireEvent.click(option);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(dropdown).toHaveTextContent("Subscription Two");
|
||||
});
|
||||
});
|
||||
|
||||
it("matches snapshot with single option", () => {
|
||||
const { container } = render(
|
||||
<SubscriptionDropdown options={[mockSubscriptionOptions[0]]} selectedKey="sub-1" onChange={mockOnChange} />,
|
||||
);
|
||||
it("should not auto-select if target subscription not found in list", async () => {
|
||||
mockUserContext.subscriptionId = "non-existent-sub";
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
renderWithProvider(<SubscriptionDropdown />);
|
||||
|
||||
await waitFor(() => {
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toHaveTextContent("Select a subscription");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Context State Management", () => {
|
||||
it("should update copyJobState when subscription is selected", async () => {
|
||||
renderWithProvider(<SubscriptionDropdown />);
|
||||
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
fireEvent.click(dropdown);
|
||||
|
||||
await waitFor(() => {
|
||||
const option = screen.getByText("Subscription Two");
|
||||
fireEvent.click(option);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(dropdown).toHaveTextContent("Subscription Two");
|
||||
});
|
||||
});
|
||||
|
||||
it("matches snapshot with special characters in options", () => {
|
||||
const specialOptions = [
|
||||
{
|
||||
key: "special",
|
||||
text: 'Subscription with & <special> "characters"',
|
||||
data: { subscriptionId: "special" },
|
||||
},
|
||||
];
|
||||
it("should reset account when subscription changes", async () => {
|
||||
renderWithProvider(<SubscriptionDropdown />);
|
||||
|
||||
const { container } = render(<SubscriptionDropdown options={specialOptions} onChange={mockOnChange} />);
|
||||
await waitFor(() => {
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toHaveTextContent("Subscription One");
|
||||
});
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
fireEvent.click(dropdown);
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
await waitFor(() => {
|
||||
const option = screen.getByText("Subscription Two");
|
||||
fireEvent.click(option);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(dropdown).toHaveTextContent("Subscription Two");
|
||||
});
|
||||
});
|
||||
|
||||
it("matches snapshot with long subscription name", () => {
|
||||
const longNameOption = [
|
||||
{
|
||||
key: "long",
|
||||
text: "This is an extremely long subscription name that tests how the component handles text overflow and layout constraints",
|
||||
data: { subscriptionId: "long" },
|
||||
},
|
||||
];
|
||||
it("should not update state if same subscription is selected", async () => {
|
||||
renderWithProvider(<SubscriptionDropdown />);
|
||||
|
||||
const { container } = render(
|
||||
<SubscriptionDropdown options={longNameOption} selectedKey="long" onChange={mockOnChange} />,
|
||||
);
|
||||
await waitFor(() => {
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toHaveTextContent("Subscription One");
|
||||
});
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
fireEvent.click(dropdown);
|
||||
|
||||
await waitFor(() => {
|
||||
const option = screen.getByText("Subscription One", { selector: ".ms-Dropdown-optionText" });
|
||||
fireEvent.click(option);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(dropdown).toHaveTextContent("Subscription One");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edge Cases", () => {
|
||||
it("should handle subscription change event with option missing data", async () => {
|
||||
renderWithProvider(<SubscriptionDropdown />);
|
||||
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
fireEvent.click(dropdown);
|
||||
expect(dropdown).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should handle subscriptions loading state", () => {
|
||||
mockUseSubscriptions.mockReturnValue(undefined);
|
||||
|
||||
renderWithProvider(<SubscriptionDropdown />);
|
||||
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toBeInTheDocument();
|
||||
expect(dropdown).toHaveTextContent("Select a subscription");
|
||||
});
|
||||
|
||||
it("should work when both userContext.subscriptionId and copyJobState subscription are null", () => {
|
||||
mockUserContext.subscriptionId = "";
|
||||
|
||||
renderWithProvider(<SubscriptionDropdown />);
|
||||
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
expect(dropdown).toBeInTheDocument();
|
||||
expect(dropdown).toHaveTextContent("Select a subscription");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,29 +1,79 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
/* eslint-disable react/display-name */
|
||||
import { Dropdown } from "@fluentui/react";
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { Subscription } from "../../../../../../Contracts/DataModels";
|
||||
import { useSubscriptions } from "../../../../../../hooks/useSubscriptions";
|
||||
import { userContext } from "../../../../../../UserContext";
|
||||
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
|
||||
import { DropdownOptionType } from "../../../../Types/CopyJobTypes";
|
||||
import { useCopyJobContext } from "../../../../Context/CopyJobContext";
|
||||
import FieldRow from "../../Components/FieldRow";
|
||||
|
||||
interface SubscriptionDropdownProps {
|
||||
options: DropdownOptionType[];
|
||||
selectedKey?: string;
|
||||
onChange: (_ev?: React.FormEvent, option?: DropdownOptionType) => void;
|
||||
}
|
||||
interface SubscriptionDropdownProps {}
|
||||
|
||||
export const SubscriptionDropdown: React.FC<SubscriptionDropdownProps> = React.memo(
|
||||
({ options, selectedKey, onChange }) => (
|
||||
export const SubscriptionDropdown: React.FC<SubscriptionDropdownProps> = React.memo(() => {
|
||||
const { copyJobState, setCopyJobState } = useCopyJobContext();
|
||||
const subscriptions: Subscription[] = useSubscriptions();
|
||||
|
||||
const updateCopyJobState = (newSubscription: Subscription) => {
|
||||
setCopyJobState((prevState) => {
|
||||
if (prevState.source?.subscription?.subscriptionId !== newSubscription.subscriptionId) {
|
||||
return {
|
||||
...prevState,
|
||||
source: {
|
||||
...prevState.source,
|
||||
subscription: newSubscription,
|
||||
account: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
return prevState;
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (subscriptions && subscriptions.length > 0) {
|
||||
const currentSubscriptionId = copyJobState?.source?.subscription?.subscriptionId;
|
||||
const predefinedSubscriptionId = userContext.subscriptionId;
|
||||
const selectedSubscriptionId = currentSubscriptionId || predefinedSubscriptionId;
|
||||
|
||||
const targetSubscription: Subscription | null =
|
||||
subscriptions.find((sub) => sub.subscriptionId === selectedSubscriptionId) || null;
|
||||
|
||||
if (targetSubscription) {
|
||||
updateCopyJobState(targetSubscription);
|
||||
}
|
||||
}
|
||||
}, [subscriptions?.length]);
|
||||
|
||||
const subscriptionOptions =
|
||||
subscriptions?.map((sub) => ({
|
||||
key: sub.subscriptionId,
|
||||
text: sub.displayName,
|
||||
data: sub,
|
||||
})) || [];
|
||||
|
||||
const handleSubscriptionChange = (_ev?: React.FormEvent, option?: (typeof subscriptionOptions)[0]) => {
|
||||
const selectedSubscription = option?.data as Subscription;
|
||||
|
||||
if (selectedSubscription) {
|
||||
updateCopyJobState(selectedSubscription);
|
||||
}
|
||||
};
|
||||
|
||||
const selectedSubscriptionId = copyJobState?.source?.subscription?.subscriptionId;
|
||||
|
||||
return (
|
||||
<FieldRow label={ContainerCopyMessages.subscriptionDropdownLabel}>
|
||||
<Dropdown
|
||||
placeholder={ContainerCopyMessages.subscriptionDropdownPlaceholder}
|
||||
ariaLabel={ContainerCopyMessages.subscriptionDropdownLabel}
|
||||
options={options}
|
||||
data-test="subscription-dropdown"
|
||||
options={subscriptionOptions}
|
||||
required
|
||||
selectedKey={selectedKey}
|
||||
onChange={onChange}
|
||||
selectedKey={selectedSubscriptionId}
|
||||
onChange={handleSubscriptionChange}
|
||||
/>
|
||||
</FieldRow>
|
||||
),
|
||||
(prev, next) => prev.options.length === next.options.length && prev.selectedKey === next.selectedKey,
|
||||
);
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,514 +1,37 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AccountDropdown Snapshot Testing matches snapshot with all account options 1`] = `
|
||||
exports[`AccountDropdown ID normalization should normalize account ID for Portal platform 1`] = `
|
||||
<div
|
||||
class="ms-Stack flex-row css-109"
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Account"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-required dropdown-132"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
data-test="account-dropdown"
|
||||
id="Dropdown21"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-110"
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title title-137"
|
||||
id="Dropdown21-option"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Account
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-110"
|
||||
test-account-1
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-134"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-136"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
<div
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Account"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-required dropdown-111"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown0"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title ms-Dropdown-titleIsPlaceHolder title-112"
|
||||
id="Dropdown0-option"
|
||||
>
|
||||
Select an account
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-131"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`AccountDropdown Snapshot Testing matches snapshot with disabled dropdown 1`] = `
|
||||
<div
|
||||
class="ms-Stack flex-row css-109"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-110"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Account
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-disabled="true"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Account"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-disabled is-required dropdown-133"
|
||||
data-is-focusable="false"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown2"
|
||||
role="combobox"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title title-138"
|
||||
id="Dropdown2-option"
|
||||
>
|
||||
Development Account
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-135"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-137"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`AccountDropdown Snapshot Testing matches snapshot with disabled state and no selection 1`] = `
|
||||
<div
|
||||
class="ms-Stack flex-row css-109"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-110"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Account
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-disabled="true"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Account"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-disabled is-required dropdown-133"
|
||||
data-is-focusable="false"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown7"
|
||||
role="combobox"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title ms-Dropdown-titleIsPlaceHolder title-134"
|
||||
id="Dropdown7-option"
|
||||
>
|
||||
Select an account
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-135"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-137"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`AccountDropdown Snapshot Testing matches snapshot with empty options 1`] = `
|
||||
<div
|
||||
class="ms-Stack flex-row css-109"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-110"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Account
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Account"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-required dropdown-111"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown3"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title ms-Dropdown-titleIsPlaceHolder title-112"
|
||||
id="Dropdown3-option"
|
||||
>
|
||||
Select an account
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-131"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`AccountDropdown Snapshot Testing matches snapshot with long account name 1`] = `
|
||||
<div
|
||||
class="ms-Stack flex-row css-109"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-110"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Account
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Account"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-required dropdown-111"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown6"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title title-132"
|
||||
id="Dropdown6-option"
|
||||
>
|
||||
This is an extremely long account name that tests how the component handles text overflow and layout constraints in the dropdown
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-131"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`AccountDropdown Snapshot Testing matches snapshot with multiple account types 1`] = `
|
||||
<div
|
||||
class="ms-Stack flex-row css-109"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-110"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Account
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Account"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-required dropdown-111"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown8"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title title-132"
|
||||
id="Dropdown8-option"
|
||||
>
|
||||
MongoDB Account
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-131"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`AccountDropdown Snapshot Testing matches snapshot with selected account 1`] = `
|
||||
<div
|
||||
class="ms-Stack flex-row css-109"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-110"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Account
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Account"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-required dropdown-111"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown1"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title title-132"
|
||||
id="Dropdown1-option"
|
||||
>
|
||||
Production Account
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-131"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`AccountDropdown Snapshot Testing matches snapshot with single option 1`] = `
|
||||
<div
|
||||
class="ms-Stack flex-row css-109"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-110"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Account
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Account"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-required dropdown-111"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown4"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title title-132"
|
||||
id="Dropdown4-option"
|
||||
>
|
||||
Development Account
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-131"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`AccountDropdown Snapshot Testing matches snapshot with special characters in options 1`] = `
|
||||
<div
|
||||
class="ms-Stack flex-row css-109"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-110"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Account
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Account"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-required dropdown-111"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown5"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title ms-Dropdown-titleIsPlaceHolder title-112"
|
||||
id="Dropdown5-option"
|
||||
>
|
||||
Select an account
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-131"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1,337 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SubscriptionDropdown Snapshot Testing matches snapshot with all subscription options 1`] = `
|
||||
<div
|
||||
class="ms-Stack flex-row css-109"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-110"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Subscription
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Subscription"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-required dropdown-111"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown0"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title ms-Dropdown-titleIsPlaceHolder title-112"
|
||||
id="Dropdown0-option"
|
||||
>
|
||||
Select a subscription
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-131"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SubscriptionDropdown Snapshot Testing matches snapshot with empty options 1`] = `
|
||||
<div
|
||||
class="ms-Stack flex-row css-109"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-110"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Subscription
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Subscription"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-required dropdown-111"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown2"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title ms-Dropdown-titleIsPlaceHolder title-112"
|
||||
id="Dropdown2-option"
|
||||
>
|
||||
Select a subscription
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-131"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SubscriptionDropdown Snapshot Testing matches snapshot with long subscription name 1`] = `
|
||||
<div
|
||||
class="ms-Stack flex-row css-109"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-110"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Subscription
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Subscription"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-required dropdown-111"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown5"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title title-132"
|
||||
id="Dropdown5-option"
|
||||
>
|
||||
This is an extremely long subscription name that tests how the component handles text overflow and layout constraints
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-131"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SubscriptionDropdown Snapshot Testing matches snapshot with selected subscription 1`] = `
|
||||
<div
|
||||
class="ms-Stack flex-row css-109"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-110"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Subscription
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Subscription"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-required dropdown-111"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown1"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title title-132"
|
||||
id="Dropdown1-option"
|
||||
>
|
||||
Production Subscription
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-131"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SubscriptionDropdown Snapshot Testing matches snapshot with single option 1`] = `
|
||||
<div
|
||||
class="ms-Stack flex-row css-109"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-110"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Subscription
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Subscription"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-required dropdown-111"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown3"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title title-132"
|
||||
id="Dropdown3-option"
|
||||
>
|
||||
Development Subscription
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-131"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SubscriptionDropdown Snapshot Testing matches snapshot with special characters in options 1`] = `
|
||||
<div
|
||||
class="ms-Stack flex-row css-109"
|
||||
>
|
||||
<div
|
||||
class="ms-StackItem flex-fixed-width css-110"
|
||||
>
|
||||
<label
|
||||
class="field-label "
|
||||
>
|
||||
Subscription
|
||||
:
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ms-StackItem flex-grow-col css-110"
|
||||
>
|
||||
<div
|
||||
class="ms-Dropdown-container"
|
||||
>
|
||||
<div
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Subscription"
|
||||
aria-required="true"
|
||||
class="ms-Dropdown is-required dropdown-111"
|
||||
data-is-focusable="true"
|
||||
data-ktp-target="true"
|
||||
id="Dropdown4"
|
||||
role="combobox"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-invalid="false"
|
||||
class="ms-Dropdown-title ms-Dropdown-titleIsPlaceHolder title-112"
|
||||
id="Dropdown4-option"
|
||||
>
|
||||
Select a subscription
|
||||
</span>
|
||||
<span
|
||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="ms-Dropdown-caretDown caretDown-131"
|
||||
data-icon-name="ChevronDown"
|
||||
>
|
||||
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,480 +1,170 @@
|
||||
import "@testing-library/jest-dom";
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { apiType } from "UserContext";
|
||||
import { DatabaseAccount, Subscription } from "../../../../../Contracts/DataModels";
|
||||
import { useDatabaseAccounts } from "../../../../../hooks/useDatabaseAccounts";
|
||||
import { useSubscriptions } from "../../../../../hooks/useSubscriptions";
|
||||
import { useCopyJobContext } from "../../../Context/CopyJobContext";
|
||||
import { CopyJobMigrationType } from "../../../Enums/CopyJobEnums";
|
||||
import { CopyJobContextProviderType, CopyJobContextState } from "../../../Types/CopyJobTypes";
|
||||
import { CopyJobContextProviderType } from "../../../Types/CopyJobTypes";
|
||||
import SelectAccount from "./SelectAccount";
|
||||
|
||||
jest.mock("UserContext", () => ({
|
||||
apiType: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../../../../../hooks/useDatabaseAccounts");
|
||||
jest.mock("../../../../../hooks/useSubscriptions");
|
||||
jest.mock("../../../Context/CopyJobContext", () => ({
|
||||
useCopyJobContext: () => mockContextValue,
|
||||
}));
|
||||
|
||||
jest.mock("./Utils/selectAccountUtils", () => ({
|
||||
useDropdownOptions: jest.fn(),
|
||||
useEventHandlers: jest.fn(),
|
||||
useCopyJobContext: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("./Components/SubscriptionDropdown", () => ({
|
||||
SubscriptionDropdown: jest.fn(({ options, selectedKey, onChange, ...props }) => (
|
||||
<div data-testid="subscription-dropdown" data-selected={selectedKey} {...props}>
|
||||
{options?.map((option: any) => (
|
||||
<div
|
||||
key={option.key}
|
||||
data-testid={`subscription-option-${option.key}`}
|
||||
onClick={() => onChange?.(undefined, option)}
|
||||
>
|
||||
{option.text}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)),
|
||||
SubscriptionDropdown: jest.fn(() => <div data-testid="subscription-dropdown">Subscription Dropdown</div>),
|
||||
}));
|
||||
|
||||
jest.mock("./Components/AccountDropdown", () => ({
|
||||
AccountDropdown: jest.fn(({ options, selectedKey, disabled, onChange, ...props }) => (
|
||||
<div data-testid="account-dropdown" data-selected={selectedKey} data-disabled={disabled} {...props}>
|
||||
{options?.map((option: any) => (
|
||||
<div
|
||||
key={option.key}
|
||||
data-testid={`account-option-${option.key}`}
|
||||
onClick={() => onChange?.(undefined, option)}
|
||||
>
|
||||
{option.text}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)),
|
||||
AccountDropdown: jest.fn(() => <div data-testid="account-dropdown">Account Dropdown</div>),
|
||||
}));
|
||||
|
||||
jest.mock("./Components/MigrationTypeCheckbox", () => ({
|
||||
MigrationTypeCheckbox: jest.fn(({ checked, onChange, ...props }) => (
|
||||
<div data-testid="migration-type-checkbox" data-checked={checked} {...props}>
|
||||
MigrationTypeCheckbox: jest.fn(({ checked, onChange }: { checked: boolean; onChange: () => void }) => (
|
||||
<div data-testid="migration-type-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={checked}
|
||||
onChange={(e) => onChange?.(e, e.target.checked)}
|
||||
onChange={onChange}
|
||||
data-testid="migration-checkbox-input"
|
||||
aria-label="Migration Type Checkbox"
|
||||
/>
|
||||
Copy container in offline mode
|
||||
</div>
|
||||
)),
|
||||
}));
|
||||
|
||||
jest.mock("../../../ContainerCopyMessages", () => ({
|
||||
selectAccountDescription: "Select your source account and subscription",
|
||||
}));
|
||||
describe("SelectAccount", () => {
|
||||
const mockSetCopyJobState = jest.fn();
|
||||
|
||||
const mockUseDatabaseAccounts = useDatabaseAccounts as jest.MockedFunction<typeof useDatabaseAccounts>;
|
||||
const mockUseSubscriptions = useSubscriptions as jest.MockedFunction<typeof useSubscriptions>;
|
||||
const mockApiType = apiType as jest.MockedFunction<typeof apiType>;
|
||||
|
||||
import { useDropdownOptions, useEventHandlers } from "./Utils/selectAccountUtils";
|
||||
const mockUseDropdownOptions = useDropdownOptions as jest.MockedFunction<typeof useDropdownOptions>;
|
||||
const mockUseEventHandlers = useEventHandlers as jest.MockedFunction<typeof useEventHandlers>;
|
||||
|
||||
const mockSubscriptions = [
|
||||
{
|
||||
subscriptionId: "sub-1",
|
||||
displayName: "Test Subscription 1",
|
||||
authorizationSource: "RoleBased",
|
||||
subscriptionPolicies: {
|
||||
quotaId: "quota-1",
|
||||
spendingLimit: "Off",
|
||||
locationPlacementId: "loc-1",
|
||||
const defaultContextValue: CopyJobContextProviderType = {
|
||||
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,
|
||||
},
|
||||
},
|
||||
{
|
||||
subscriptionId: "sub-2",
|
||||
displayName: "Test Subscription 2",
|
||||
authorizationSource: "RoleBased",
|
||||
subscriptionPolicies: {
|
||||
quotaId: "quota-2",
|
||||
spendingLimit: "On",
|
||||
locationPlacementId: "loc-2",
|
||||
},
|
||||
},
|
||||
] as Subscription[];
|
||||
setCopyJobState: mockSetCopyJobState,
|
||||
flow: { currentScreen: "selectAccount" },
|
||||
setFlow: jest.fn(),
|
||||
contextError: null,
|
||||
setContextError: jest.fn(),
|
||||
explorer: {} as any,
|
||||
resetCopyJobState: jest.fn(),
|
||||
};
|
||||
|
||||
const mockAccounts = [
|
||||
{
|
||||
id: "/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1",
|
||||
name: "test-cosmos-account-1",
|
||||
location: "East US",
|
||||
kind: "GlobalDocumentDB",
|
||||
properties: {
|
||||
documentEndpoint: "https://account-1.documents.azure.com/",
|
||||
capabilities: [],
|
||||
enableFreeTier: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-2",
|
||||
name: "test-cosmos-account-2",
|
||||
location: "West US",
|
||||
kind: "MongoDB",
|
||||
properties: {
|
||||
documentEndpoint: "https://account-2.documents.azure.com/",
|
||||
capabilities: [],
|
||||
},
|
||||
},
|
||||
] as DatabaseAccount[];
|
||||
|
||||
const mockDropdownOptions = {
|
||||
subscriptionOptions: [
|
||||
{ key: "sub-1", text: "Test Subscription 1", data: mockSubscriptions[0] },
|
||||
{ key: "sub-2", text: "Test Subscription 2", data: mockSubscriptions[1] },
|
||||
],
|
||||
accountOptions: [{ key: mockAccounts[0].id, text: mockAccounts[0].name, data: mockAccounts[0] }],
|
||||
};
|
||||
|
||||
const mockEventHandlers = {
|
||||
handleSelectSourceAccount: jest.fn(),
|
||||
handleMigrationTypeChange: jest.fn(),
|
||||
};
|
||||
|
||||
let mockContextValue = {
|
||||
copyJobState: {
|
||||
jobName: "",
|
||||
migrationType: CopyJobMigrationType.Offline,
|
||||
source: {
|
||||
subscription: null,
|
||||
account: null,
|
||||
databaseId: "",
|
||||
containerId: "",
|
||||
},
|
||||
target: {
|
||||
subscriptionId: "",
|
||||
account: null,
|
||||
databaseId: "",
|
||||
containerId: "",
|
||||
},
|
||||
sourceReadAccessFromTarget: false,
|
||||
} as CopyJobContextState,
|
||||
setCopyJobState: jest.fn(),
|
||||
flow: null,
|
||||
setFlow: jest.fn(),
|
||||
contextError: null,
|
||||
setContextError: jest.fn(),
|
||||
resetCopyJobState: jest.fn(),
|
||||
explorer: {} as any,
|
||||
} as CopyJobContextProviderType;
|
||||
|
||||
describe("SelectAccount Component", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
mockContextValue = {
|
||||
copyJobState: {
|
||||
jobName: "",
|
||||
migrationType: CopyJobMigrationType.Offline,
|
||||
source: {
|
||||
subscription: null,
|
||||
account: null,
|
||||
databaseId: "",
|
||||
containerId: "",
|
||||
},
|
||||
target: {
|
||||
subscriptionId: "",
|
||||
account: null,
|
||||
databaseId: "",
|
||||
containerId: "",
|
||||
},
|
||||
sourceReadAccessFromTarget: false,
|
||||
} as CopyJobContextState,
|
||||
setCopyJobState: jest.fn(),
|
||||
flow: null,
|
||||
setFlow: jest.fn(),
|
||||
contextError: null,
|
||||
setContextError: jest.fn(),
|
||||
resetCopyJobState: jest.fn(),
|
||||
explorer: {} as any,
|
||||
};
|
||||
|
||||
mockUseSubscriptions.mockReturnValue(mockSubscriptions);
|
||||
mockUseDatabaseAccounts.mockReturnValue(mockAccounts);
|
||||
mockApiType.mockReturnValue("SQL");
|
||||
mockUseDropdownOptions.mockReturnValue(mockDropdownOptions);
|
||||
mockUseEventHandlers.mockReturnValue(mockEventHandlers);
|
||||
(useCopyJobContext as jest.Mock).mockReturnValue(defaultContextValue);
|
||||
});
|
||||
|
||||
describe("Rendering", () => {
|
||||
it("should render component with default state", () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("Component Rendering", () => {
|
||||
it("should render the component with all required elements", () => {
|
||||
const { container } = render(<SelectAccount />);
|
||||
|
||||
expect(screen.getByText("Select your source account and subscription")).toBeInTheDocument();
|
||||
expect(container.firstChild).toHaveAttribute("data-test", "Panel:SelectAccountContainer");
|
||||
expect(container.firstChild).toHaveClass("selectAccountContainer");
|
||||
|
||||
expect(screen.getByText(/Please select a source account from which to copy/i)).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByTestId("subscription-dropdown")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("account-dropdown")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("migration-type-checkbox")).toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render with selected subscription", () => {
|
||||
mockContextValue.copyJobState.source.subscription = mockSubscriptions[0];
|
||||
|
||||
it("should render correctly with snapshot", () => {
|
||||
const { container } = render(<SelectAccount />);
|
||||
|
||||
expect(screen.getByTestId("subscription-dropdown")).toHaveAttribute("data-selected", "sub-1");
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render with selected account", () => {
|
||||
mockContextValue.copyJobState.source.subscription = mockSubscriptions[0];
|
||||
mockContextValue.copyJobState.source.account = mockAccounts[0];
|
||||
|
||||
const { container } = render(<SelectAccount />);
|
||||
|
||||
expect(screen.getByTestId("account-dropdown")).toHaveAttribute("data-selected", mockAccounts[0].id);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render with offline migration type checked", () => {
|
||||
mockContextValue.copyJobState.migrationType = CopyJobMigrationType.Offline;
|
||||
|
||||
const { container } = render(<SelectAccount />);
|
||||
|
||||
expect(screen.getByTestId("migration-type-checkbox")).toHaveAttribute("data-checked", "true");
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render with online migration type unchecked", () => {
|
||||
mockContextValue.copyJobState.migrationType = CopyJobMigrationType.Online;
|
||||
|
||||
const { container } = render(<SelectAccount />);
|
||||
|
||||
expect(screen.getByTestId("migration-type-checkbox")).toHaveAttribute("data-checked", "false");
|
||||
expect(container).toMatchSnapshot();
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Hook Integration", () => {
|
||||
it("should call useSubscriptions hook", () => {
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
render(<SelectAccount />);
|
||||
expect(mockUseSubscriptions).toHaveBeenCalledTimes(1);
|
||||
|
||||
const checkbox = screen.getByTestId("migration-checkbox-input");
|
||||
expect(checkbox).not.toBeChecked();
|
||||
});
|
||||
|
||||
it("should call useDatabaseAccounts with selected subscription ID", () => {
|
||||
mockContextValue.copyJobState.source.subscription = mockSubscriptions[0];
|
||||
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(<SelectAccount />);
|
||||
|
||||
expect(mockUseDatabaseAccounts).toHaveBeenCalledWith("sub-1");
|
||||
const checkbox = screen.getByTestId("migration-checkbox-input");
|
||||
expect(checkbox).toBeChecked();
|
||||
});
|
||||
|
||||
it("should call useDatabaseAccounts with undefined when no subscription selected", () => {
|
||||
render(<SelectAccount />);
|
||||
expect(mockUseDatabaseAccounts).toHaveBeenCalledWith(undefined);
|
||||
});
|
||||
|
||||
it("should filter accounts to SQL API only", () => {
|
||||
mockApiType.mockReturnValueOnce("SQL").mockReturnValueOnce("Mongo");
|
||||
render(<SelectAccount />);
|
||||
|
||||
expect(mockApiType).toHaveBeenCalledTimes(2);
|
||||
expect(mockApiType).toHaveBeenCalledWith(mockAccounts[0]);
|
||||
expect(mockApiType).toHaveBeenCalledWith(mockAccounts[1]);
|
||||
});
|
||||
|
||||
it("should call useDropdownOptions with correct parameters", () => {
|
||||
const sqlOnlyAccounts = [mockAccounts[0]]; // Only SQL account
|
||||
mockApiType.mockImplementation((account) => (account === mockAccounts[0] ? "SQL" : "Mongo"));
|
||||
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(<SelectAccount />);
|
||||
|
||||
expect(mockUseDropdownOptions).toHaveBeenCalledWith(mockSubscriptions, sqlOnlyAccounts);
|
||||
});
|
||||
|
||||
it("should call useEventHandlers with setCopyJobState", () => {
|
||||
render(<SelectAccount />);
|
||||
expect(mockUseEventHandlers).toHaveBeenCalledWith(mockContextValue.setCopyJobState);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Event Handling", () => {
|
||||
it("should handle subscription selection", () => {
|
||||
render(<SelectAccount />);
|
||||
|
||||
const subscriptionOption = screen.getByTestId("subscription-option-sub-1");
|
||||
fireEvent.click(subscriptionOption);
|
||||
|
||||
expect(mockEventHandlers.handleSelectSourceAccount).toHaveBeenCalledWith("subscription", mockSubscriptions[0]);
|
||||
});
|
||||
|
||||
it("should handle account selection", () => {
|
||||
render(<SelectAccount />);
|
||||
|
||||
const accountOption = screen.getByTestId(`account-option-${mockAccounts[0].id}`);
|
||||
fireEvent.click(accountOption);
|
||||
|
||||
expect(mockEventHandlers.handleSelectSourceAccount).toHaveBeenCalledWith("account", mockAccounts[0]);
|
||||
});
|
||||
|
||||
it("should handle migration type change", () => {
|
||||
render(<SelectAccount />);
|
||||
const checkbox = screen.getByTestId("migration-checkbox-input");
|
||||
fireEvent.click(checkbox);
|
||||
|
||||
expect(mockEventHandlers.handleMigrationTypeChange).toHaveBeenCalledWith(expect.any(Object), false);
|
||||
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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Dropdown States", () => {
|
||||
it("should disable account dropdown when no subscription is selected", () => {
|
||||
render(<SelectAccount />);
|
||||
describe("Performance and Optimization", () => {
|
||||
it("should maintain referential equality of handler functions between renders", async () => {
|
||||
const { rerender } = render(<SelectAccount />);
|
||||
|
||||
expect(screen.getByTestId("account-dropdown")).toHaveAttribute("data-disabled", "true");
|
||||
});
|
||||
const migrationCheckbox = (await import("./Components/MigrationTypeCheckbox")).MigrationTypeCheckbox as jest.Mock;
|
||||
const firstRenderHandler = migrationCheckbox.mock.calls[migrationCheckbox.mock.calls.length - 1][0].onChange;
|
||||
|
||||
it("should enable account dropdown when subscription is selected", () => {
|
||||
mockContextValue.copyJobState.source.subscription = mockSubscriptions[0];
|
||||
rerender(<SelectAccount />);
|
||||
|
||||
render(<SelectAccount />);
|
||||
const secondRenderHandler = migrationCheckbox.mock.calls[migrationCheckbox.mock.calls.length - 1][0].onChange;
|
||||
|
||||
expect(screen.getByTestId("account-dropdown")).toHaveAttribute("data-disabled", "false");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Component Props", () => {
|
||||
it("should pass correct props to SubscriptionDropdown", () => {
|
||||
render(<SelectAccount />);
|
||||
|
||||
const dropdown = screen.getByTestId("subscription-dropdown");
|
||||
expect(dropdown).not.toHaveAttribute("data-selected");
|
||||
});
|
||||
|
||||
it("should pass selected subscription ID to SubscriptionDropdown", () => {
|
||||
mockContextValue.copyJobState.source.subscription = mockSubscriptions[0];
|
||||
|
||||
render(<SelectAccount />);
|
||||
|
||||
const dropdown = screen.getByTestId("subscription-dropdown");
|
||||
expect(dropdown).toHaveAttribute("data-selected", "sub-1");
|
||||
});
|
||||
|
||||
it("should pass correct props to AccountDropdown", () => {
|
||||
render(<SelectAccount />);
|
||||
|
||||
const dropdown = screen.getByTestId("account-dropdown");
|
||||
expect(dropdown).not.toHaveAttribute("data-selected");
|
||||
expect(dropdown).toHaveAttribute("data-disabled", "true");
|
||||
});
|
||||
|
||||
it("should pass selected account ID to AccountDropdown", () => {
|
||||
mockContextValue.copyJobState.source.account = mockAccounts[0];
|
||||
|
||||
render(<SelectAccount />);
|
||||
|
||||
const dropdown = screen.getByTestId("account-dropdown");
|
||||
expect(dropdown).toHaveAttribute("data-selected", mockAccounts[0].id);
|
||||
});
|
||||
|
||||
it("should pass correct checked state to MigrationTypeCheckbox", () => {
|
||||
mockContextValue.copyJobState.migrationType = CopyJobMigrationType.Offline;
|
||||
|
||||
render(<SelectAccount />);
|
||||
|
||||
const checkbox = screen.getByTestId("migration-type-checkbox");
|
||||
expect(checkbox).toHaveAttribute("data-checked", "true");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edge Cases", () => {
|
||||
it("should handle empty subscriptions array", () => {
|
||||
mockUseSubscriptions.mockReturnValue([]);
|
||||
mockUseDropdownOptions.mockReturnValue({
|
||||
subscriptionOptions: [],
|
||||
accountOptions: [],
|
||||
});
|
||||
|
||||
const { container } = render(<SelectAccount />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should handle empty accounts array", () => {
|
||||
mockUseDatabaseAccounts.mockReturnValue([]);
|
||||
mockUseDropdownOptions.mockReturnValue({
|
||||
subscriptionOptions: mockDropdownOptions.subscriptionOptions,
|
||||
accountOptions: [],
|
||||
});
|
||||
|
||||
const { container } = render(<SelectAccount />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should handle null subscription in context", () => {
|
||||
mockContextValue.copyJobState.source.subscription = null;
|
||||
|
||||
const { container } = render(<SelectAccount />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should handle null account in context", () => {
|
||||
mockContextValue.copyJobState.source.account = null;
|
||||
|
||||
const { container } = render(<SelectAccount />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should handle undefined subscriptions from hook", () => {
|
||||
mockUseSubscriptions.mockReturnValue(undefined as any);
|
||||
mockUseDropdownOptions.mockReturnValue({
|
||||
subscriptionOptions: [],
|
||||
accountOptions: [],
|
||||
});
|
||||
|
||||
const { container } = render(<SelectAccount />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should handle undefined accounts from hook", () => {
|
||||
mockUseDatabaseAccounts.mockReturnValue(undefined as any);
|
||||
mockUseDropdownOptions.mockReturnValue({
|
||||
subscriptionOptions: mockDropdownOptions.subscriptionOptions,
|
||||
accountOptions: [],
|
||||
});
|
||||
|
||||
const { container } = render(<SelectAccount />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should filter out non-SQL accounts correctly", () => {
|
||||
const mixedAccounts = [
|
||||
{ ...mockAccounts[0], kind: "GlobalDocumentDB" },
|
||||
{ ...mockAccounts[1], kind: "MongoDB" },
|
||||
];
|
||||
|
||||
mockUseDatabaseAccounts.mockReturnValue(mixedAccounts);
|
||||
mockApiType.mockImplementation((account) => (account.kind === "GlobalDocumentDB" ? "SQL" : "Mongo"));
|
||||
|
||||
render(<SelectAccount />);
|
||||
expect(mockApiType).toHaveBeenCalledTimes(2);
|
||||
|
||||
const sqlOnlyAccounts = mixedAccounts.filter((account) => apiType(account) === "SQL");
|
||||
expect(mockUseDropdownOptions).toHaveBeenCalledWith(mockSubscriptions, sqlOnlyAccounts);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Complete Workflow", () => {
|
||||
it("should render complete workflow with all selections", () => {
|
||||
mockContextValue.copyJobState.source.subscription = mockSubscriptions[0];
|
||||
mockContextValue.copyJobState.source.account = mockAccounts[0];
|
||||
mockContextValue.copyJobState.migrationType = CopyJobMigrationType.Online;
|
||||
|
||||
const { container } = render(<SelectAccount />);
|
||||
|
||||
expect(screen.getByTestId("subscription-dropdown")).toHaveAttribute("data-selected", "sub-1");
|
||||
expect(screen.getByTestId("account-dropdown")).toHaveAttribute("data-selected", mockAccounts[0].id);
|
||||
expect(screen.getByTestId("account-dropdown")).toHaveAttribute("data-disabled", "false");
|
||||
expect(screen.getByTestId("migration-type-checkbox")).toHaveAttribute("data-checked", "false");
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
expect(firstRenderHandler).toBe(secondRenderHandler);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,52 +1,37 @@
|
||||
/* eslint-disable react/display-name */
|
||||
import { Stack } from "@fluentui/react";
|
||||
import { Stack, Text } from "@fluentui/react";
|
||||
import React from "react";
|
||||
import { apiType } from "UserContext";
|
||||
import { DatabaseAccount, Subscription } from "../../../../../Contracts/DataModels";
|
||||
import { useDatabaseAccounts } from "../../../../../hooks/useDatabaseAccounts";
|
||||
import { useSubscriptions } from "../../../../../hooks/useSubscriptions";
|
||||
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 { SubscriptionDropdown } from "./Components/SubscriptionDropdown";
|
||||
import { useDropdownOptions, useEventHandlers } from "./Utils/selectAccountUtils";
|
||||
|
||||
const SelectAccount = React.memo(() => {
|
||||
const { copyJobState, setCopyJobState } = useCopyJobContext();
|
||||
const selectedSubscriptionId = copyJobState?.source?.subscription?.subscriptionId;
|
||||
const selectedSourceAccountId = copyJobState?.source?.account?.id;
|
||||
|
||||
const subscriptions: Subscription[] = useSubscriptions();
|
||||
const allAccounts: DatabaseAccount[] = useDatabaseAccounts(selectedSubscriptionId);
|
||||
const sqlApiOnlyAccounts: DatabaseAccount[] = allAccounts?.filter((account) => apiType(account) === "SQL");
|
||||
|
||||
const { subscriptionOptions, accountOptions } = useDropdownOptions(subscriptions, sqlApiOnlyAccounts);
|
||||
const { handleSelectSourceAccount, handleMigrationTypeChange } = useEventHandlers(setCopyJobState);
|
||||
const handleMigrationTypeChange = (_ev?: React.FormEvent<HTMLElement>, checked?: boolean) => {
|
||||
setCopyJobState((prevState) => ({
|
||||
...prevState,
|
||||
migrationType: checked ? CopyJobMigrationType.Offline : CopyJobMigrationType.Online,
|
||||
}));
|
||||
};
|
||||
|
||||
const migrationTypeChecked = copyJobState?.migrationType === CopyJobMigrationType.Offline;
|
||||
|
||||
return (
|
||||
<Stack className="selectAccountContainer" tokens={{ childrenGap: 15 }}>
|
||||
<span>{ContainerCopyMessages.selectAccountDescription}</span>
|
||||
<Stack data-test="Panel:SelectAccountContainer" className="selectAccountContainer" tokens={{ childrenGap: 15 }}>
|
||||
<Text>{ContainerCopyMessages.selectAccountDescription}</Text>
|
||||
|
||||
<SubscriptionDropdown
|
||||
options={subscriptionOptions}
|
||||
selectedKey={selectedSubscriptionId}
|
||||
onChange={(_ev, option) => handleSelectSourceAccount("subscription", option?.data)}
|
||||
/>
|
||||
<SubscriptionDropdown />
|
||||
|
||||
<AccountDropdown
|
||||
options={accountOptions}
|
||||
selectedKey={selectedSourceAccountId}
|
||||
disabled={!selectedSubscriptionId}
|
||||
onChange={(_ev, option) => handleSelectSourceAccount("account", option?.data)}
|
||||
/>
|
||||
<AccountDropdown />
|
||||
|
||||
<MigrationTypeCheckbox checked={migrationTypeChecked} onChange={handleMigrationTypeChange} />
|
||||
</Stack>
|
||||
);
|
||||
});
|
||||
|
||||
SelectAccount.displayName = "SelectAccount";
|
||||
|
||||
export default SelectAccount;
|
||||
|
||||
@@ -1,526 +0,0 @@
|
||||
import "@testing-library/jest-dom";
|
||||
import { fireEvent, render } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { noop } from "underscore";
|
||||
import { DatabaseAccount, Subscription } from "../../../../../../Contracts/DataModels";
|
||||
import { CopyJobMigrationType } from "../../../../Enums/CopyJobEnums";
|
||||
import { CopyJobContextState } from "../../../../Types/CopyJobTypes";
|
||||
import { useDropdownOptions, useEventHandlers } from "./selectAccountUtils";
|
||||
|
||||
jest.mock("../../../Utils/useCopyJobPrerequisitesCache", () => ({
|
||||
useCopyJobPrerequisitesCache: jest.fn(() => ({
|
||||
setValidationCache: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
const mockSubscriptions: Subscription[] = [
|
||||
{
|
||||
subscriptionId: "sub-1",
|
||||
displayName: "Test Subscription 1",
|
||||
state: "Enabled",
|
||||
subscriptionPolicies: {
|
||||
locationPlacementId: "test",
|
||||
quotaId: "test",
|
||||
spendingLimit: "Off",
|
||||
},
|
||||
},
|
||||
{
|
||||
subscriptionId: "sub-2",
|
||||
displayName: "Test Subscription 2",
|
||||
state: "Enabled",
|
||||
subscriptionPolicies: {
|
||||
locationPlacementId: "test",
|
||||
quotaId: "test",
|
||||
spendingLimit: "Off",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const mockAccounts: DatabaseAccount[] = [
|
||||
{
|
||||
id: "account-1",
|
||||
name: "Test Account 1",
|
||||
location: "East US",
|
||||
type: "Microsoft.DocumentDB/databaseAccounts",
|
||||
kind: "GlobalDocumentDB",
|
||||
properties: {
|
||||
documentEndpoint: "https://test1.documents.azure.com:443/",
|
||||
gremlinEndpoint: "https://test1.gremlin.cosmosdb.azure.com:443/",
|
||||
tableEndpoint: "https://test1.table.cosmosdb.azure.com:443/",
|
||||
cassandraEndpoint: "https://test1.cassandra.cosmosdb.azure.com:443/",
|
||||
capabilities: [],
|
||||
writeLocations: [],
|
||||
readLocations: [],
|
||||
locations: [],
|
||||
ipRules: [],
|
||||
enableMultipleWriteLocations: false,
|
||||
isVirtualNetworkFilterEnabled: false,
|
||||
enableFreeTier: false,
|
||||
enableAnalyticalStorage: false,
|
||||
publicNetworkAccess: "Enabled",
|
||||
defaultIdentity: "",
|
||||
disableLocalAuth: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "account-2",
|
||||
name: "Test Account 2",
|
||||
location: "West US",
|
||||
type: "Microsoft.DocumentDB/databaseAccounts",
|
||||
kind: "GlobalDocumentDB",
|
||||
properties: {
|
||||
documentEndpoint: "https://test2.documents.azure.com:443/",
|
||||
gremlinEndpoint: "https://test2.gremlin.cosmosdb.azure.com:443/",
|
||||
tableEndpoint: "https://test2.table.cosmosdb.azure.com:443/",
|
||||
cassandraEndpoint: "https://test2.cassandra.cosmosdb.azure.com:443/",
|
||||
capabilities: [],
|
||||
writeLocations: [],
|
||||
readLocations: [],
|
||||
locations: [],
|
||||
enableMultipleWriteLocations: false,
|
||||
isVirtualNetworkFilterEnabled: false,
|
||||
enableFreeTier: false,
|
||||
enableAnalyticalStorage: false,
|
||||
publicNetworkAccess: "Enabled",
|
||||
defaultIdentity: "",
|
||||
disableLocalAuth: false,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const DropdownOptionsTestComponent: React.FC<{
|
||||
subscriptions: Subscription[];
|
||||
accounts: DatabaseAccount[];
|
||||
onResult?: (result: { subscriptionOptions: any[]; accountOptions: any[] }) => void;
|
||||
}> = ({ subscriptions, accounts, onResult }) => {
|
||||
const result = useDropdownOptions(subscriptions, accounts);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (onResult) {
|
||||
onResult(result);
|
||||
}
|
||||
}, [result, onResult]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="subscription-options-count">{result.subscriptionOptions.length}</div>
|
||||
<div data-testid="account-options-count">{result.accountOptions.length}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const EventHandlersTestComponent: React.FC<{
|
||||
setCopyJobState: jest.Mock;
|
||||
onResult?: (result: any) => void;
|
||||
}> = ({ setCopyJobState, onResult }) => {
|
||||
const result = useEventHandlers(setCopyJobState);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (onResult) {
|
||||
onResult(result);
|
||||
}
|
||||
}, [result, onResult]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
data-testid="select-subscription-button"
|
||||
onClick={() => result.handleSelectSourceAccount("subscription", mockSubscriptions[0] as any)}
|
||||
>
|
||||
Select Subscription
|
||||
</button>
|
||||
<button
|
||||
data-testid="select-account-button"
|
||||
onClick={() => result.handleSelectSourceAccount("account", mockAccounts[0] as any)}
|
||||
>
|
||||
Select Account
|
||||
</button>
|
||||
<button data-testid="migration-type-button" onClick={(e) => result.handleMigrationTypeChange(e, true)}>
|
||||
Change Migration Type
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
describe("selectAccountUtils", () => {
|
||||
describe("useDropdownOptions", () => {
|
||||
it("should return empty arrays when subscriptions and accounts are undefined", () => {
|
||||
let capturedResult: any;
|
||||
|
||||
render(
|
||||
<DropdownOptionsTestComponent
|
||||
subscriptions={undefined as any}
|
||||
accounts={undefined as any}
|
||||
onResult={(result) => {
|
||||
capturedResult = result;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(capturedResult).toEqual({
|
||||
subscriptionOptions: [],
|
||||
accountOptions: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("should return empty arrays when subscriptions and accounts are empty arrays", () => {
|
||||
let capturedResult: any;
|
||||
|
||||
render(
|
||||
<DropdownOptionsTestComponent
|
||||
subscriptions={[]}
|
||||
accounts={[]}
|
||||
onResult={(result) => {
|
||||
capturedResult = result;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(capturedResult).toEqual({
|
||||
subscriptionOptions: [],
|
||||
accountOptions: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("should transform subscriptions into dropdown options correctly", () => {
|
||||
let capturedResult: any;
|
||||
|
||||
render(
|
||||
<DropdownOptionsTestComponent
|
||||
subscriptions={mockSubscriptions}
|
||||
accounts={[]}
|
||||
onResult={(result) => {
|
||||
capturedResult = result;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(capturedResult.subscriptionOptions).toHaveLength(2);
|
||||
expect(capturedResult.subscriptionOptions[0]).toEqual({
|
||||
key: "sub-1",
|
||||
text: "Test Subscription 1",
|
||||
data: mockSubscriptions[0],
|
||||
});
|
||||
expect(capturedResult.subscriptionOptions[1]).toEqual({
|
||||
key: "sub-2",
|
||||
text: "Test Subscription 2",
|
||||
data: mockSubscriptions[1],
|
||||
});
|
||||
});
|
||||
|
||||
it("should transform accounts into dropdown options correctly", () => {
|
||||
let capturedResult: any;
|
||||
|
||||
render(
|
||||
<DropdownOptionsTestComponent
|
||||
subscriptions={[]}
|
||||
accounts={mockAccounts}
|
||||
onResult={(result) => {
|
||||
capturedResult = result;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(capturedResult.accountOptions).toHaveLength(2);
|
||||
expect(capturedResult.accountOptions[0]).toEqual({
|
||||
key: "account-1",
|
||||
text: "Test Account 1",
|
||||
data: mockAccounts[0],
|
||||
});
|
||||
expect(capturedResult.accountOptions[1]).toEqual({
|
||||
key: "account-2",
|
||||
text: "Test Account 2",
|
||||
data: mockAccounts[1],
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle both subscriptions and accounts correctly", () => {
|
||||
let capturedResult: any;
|
||||
|
||||
render(
|
||||
<DropdownOptionsTestComponent
|
||||
subscriptions={mockSubscriptions}
|
||||
accounts={mockAccounts}
|
||||
onResult={(result) => {
|
||||
capturedResult = result;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(capturedResult.subscriptionOptions).toHaveLength(2);
|
||||
expect(capturedResult.accountOptions).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("useEventHandlers", () => {
|
||||
let mockSetCopyJobState: jest.Mock;
|
||||
let mockSetValidationCache: jest.Mock;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockSetCopyJobState = jest.fn();
|
||||
mockSetValidationCache = jest.fn();
|
||||
|
||||
const { useCopyJobPrerequisitesCache } = await import("../../../Utils/useCopyJobPrerequisitesCache");
|
||||
(useCopyJobPrerequisitesCache as unknown as jest.Mock).mockReturnValue({
|
||||
setValidationCache: mockSetValidationCache,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should handle subscription selection correctly", () => {
|
||||
const { getByTestId } = render(
|
||||
<EventHandlersTestComponent setCopyJobState={mockSetCopyJobState} onResult={noop} />,
|
||||
);
|
||||
|
||||
fireEvent.click(getByTestId("select-subscription-button"));
|
||||
|
||||
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
||||
expect(mockSetValidationCache).toHaveBeenCalledWith(new Map<string, boolean>());
|
||||
|
||||
const stateUpdater = mockSetCopyJobState.mock.calls[0][0];
|
||||
const mockPrevState: CopyJobContextState = {
|
||||
source: {
|
||||
subscription: null,
|
||||
account: { id: "existing-account" } as any,
|
||||
},
|
||||
migrationType: CopyJobMigrationType.Online,
|
||||
} as any;
|
||||
|
||||
const newState = stateUpdater(mockPrevState);
|
||||
expect(newState).toEqual({
|
||||
source: {
|
||||
subscription: mockSubscriptions[0],
|
||||
account: null,
|
||||
},
|
||||
migrationType: CopyJobMigrationType.Online,
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle account selection correctly", () => {
|
||||
const { getByTestId } = render(
|
||||
<EventHandlersTestComponent setCopyJobState={mockSetCopyJobState} onResult={noop} />,
|
||||
);
|
||||
|
||||
fireEvent.click(getByTestId("select-account-button"));
|
||||
|
||||
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
||||
expect(mockSetValidationCache).toHaveBeenCalledWith(new Map<string, boolean>());
|
||||
|
||||
const stateUpdater = mockSetCopyJobState.mock.calls[0][0];
|
||||
const mockPrevState: CopyJobContextState = {
|
||||
source: {
|
||||
subscription: { subscriptionId: "existing-sub" } as any,
|
||||
account: null,
|
||||
},
|
||||
migrationType: CopyJobMigrationType.Online,
|
||||
} as any;
|
||||
|
||||
const newState = stateUpdater(mockPrevState);
|
||||
expect(newState).toEqual({
|
||||
source: {
|
||||
subscription: { subscriptionId: "existing-sub" },
|
||||
account: mockAccounts[0],
|
||||
},
|
||||
migrationType: CopyJobMigrationType.Online,
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle subscription selection with undefined data", () => {
|
||||
let capturedHandlers: any;
|
||||
|
||||
render(
|
||||
<EventHandlersTestComponent
|
||||
setCopyJobState={mockSetCopyJobState}
|
||||
onResult={(result) => {
|
||||
capturedHandlers = result;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
capturedHandlers.handleSelectSourceAccount("subscription", undefined);
|
||||
|
||||
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
||||
|
||||
const stateUpdater = mockSetCopyJobState.mock.calls[0][0];
|
||||
const mockPrevState: CopyJobContextState = {
|
||||
source: {
|
||||
subscription: { subscriptionId: "existing-sub" } as any,
|
||||
account: { id: "existing-account" } as any,
|
||||
},
|
||||
migrationType: CopyJobMigrationType.Online,
|
||||
} as any;
|
||||
|
||||
const newState = stateUpdater(mockPrevState);
|
||||
expect(newState).toEqual({
|
||||
source: {
|
||||
subscription: null,
|
||||
account: null,
|
||||
},
|
||||
migrationType: CopyJobMigrationType.Online,
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle account selection with undefined data", () => {
|
||||
let capturedHandlers: any;
|
||||
|
||||
render(
|
||||
<EventHandlersTestComponent
|
||||
setCopyJobState={mockSetCopyJobState}
|
||||
onResult={(result) => {
|
||||
capturedHandlers = result;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
capturedHandlers.handleSelectSourceAccount("account", undefined);
|
||||
|
||||
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
||||
|
||||
const stateUpdater = mockSetCopyJobState.mock.calls[0][0];
|
||||
const mockPrevState: CopyJobContextState = {
|
||||
source: {
|
||||
subscription: { subscriptionId: "existing-sub" } as any,
|
||||
account: { id: "existing-account" } as any,
|
||||
},
|
||||
migrationType: CopyJobMigrationType.Online,
|
||||
} as any;
|
||||
|
||||
const newState = stateUpdater(mockPrevState);
|
||||
expect(newState).toEqual({
|
||||
source: {
|
||||
subscription: { subscriptionId: "existing-sub" },
|
||||
account: null,
|
||||
},
|
||||
migrationType: CopyJobMigrationType.Online,
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle migration type change to offline", () => {
|
||||
const { getByTestId } = render(<EventHandlersTestComponent setCopyJobState={mockSetCopyJobState} />);
|
||||
|
||||
fireEvent.click(getByTestId("migration-type-button"));
|
||||
|
||||
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
||||
expect(mockSetValidationCache).toHaveBeenCalledWith(new Map<string, boolean>());
|
||||
|
||||
const stateUpdater = mockSetCopyJobState.mock.calls[0][0];
|
||||
const mockPrevState: CopyJobContextState = {
|
||||
source: {
|
||||
subscription: null,
|
||||
account: null,
|
||||
},
|
||||
migrationType: CopyJobMigrationType.Online,
|
||||
} as any;
|
||||
|
||||
const newState = stateUpdater(mockPrevState);
|
||||
expect(newState).toEqual({
|
||||
source: {
|
||||
subscription: null,
|
||||
account: null,
|
||||
},
|
||||
migrationType: CopyJobMigrationType.Offline,
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle migration type change to online when checked is false", () => {
|
||||
let capturedHandlers: any;
|
||||
|
||||
render(
|
||||
<EventHandlersTestComponent
|
||||
setCopyJobState={mockSetCopyJobState}
|
||||
onResult={(result) => {
|
||||
capturedHandlers = result;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
capturedHandlers.handleMigrationTypeChange(undefined, false);
|
||||
|
||||
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
||||
|
||||
const stateUpdater = mockSetCopyJobState.mock.calls[0][0];
|
||||
const mockPrevState: CopyJobContextState = {
|
||||
source: {
|
||||
subscription: null,
|
||||
account: null,
|
||||
},
|
||||
migrationType: CopyJobMigrationType.Offline,
|
||||
} as any;
|
||||
|
||||
const newState = stateUpdater(mockPrevState);
|
||||
expect(newState).toEqual({
|
||||
source: {
|
||||
subscription: null,
|
||||
account: null,
|
||||
},
|
||||
migrationType: CopyJobMigrationType.Online,
|
||||
});
|
||||
});
|
||||
|
||||
it("should preserve other state properties when updating", () => {
|
||||
let capturedHandlers: any;
|
||||
|
||||
render(
|
||||
<EventHandlersTestComponent
|
||||
setCopyJobState={mockSetCopyJobState}
|
||||
onResult={(result) => {
|
||||
capturedHandlers = result;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
capturedHandlers.handleSelectSourceAccount("subscription", mockSubscriptions[0] as Subscription);
|
||||
|
||||
const stateUpdater = mockSetCopyJobState.mock.calls[0][0];
|
||||
const mockPrevState = {
|
||||
jobName: "Test Job",
|
||||
source: {
|
||||
subscription: null,
|
||||
account: null,
|
||||
databaseId: "test-database-id",
|
||||
containerId: "test-container-id",
|
||||
},
|
||||
migrationType: CopyJobMigrationType.Online,
|
||||
target: {
|
||||
account: { id: "dest-account" } as DatabaseAccount,
|
||||
databaseId: "test-database-id",
|
||||
containerId: "test-container-id",
|
||||
subscriptionId: "dest-sub-id",
|
||||
},
|
||||
} as CopyJobContextState;
|
||||
|
||||
const newState = stateUpdater(mockPrevState);
|
||||
expect(newState.target).toEqual(mockPrevState.target);
|
||||
});
|
||||
|
||||
it("should return the same state for unknown selection type", () => {
|
||||
let capturedHandlers: any;
|
||||
|
||||
render(
|
||||
<EventHandlersTestComponent
|
||||
setCopyJobState={mockSetCopyJobState}
|
||||
onResult={(result) => {
|
||||
capturedHandlers = result;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
capturedHandlers.handleSelectSourceAccount("unknown" as any, mockSubscriptions[0] as any);
|
||||
|
||||
const stateUpdater = mockSetCopyJobState.mock.calls[0][0];
|
||||
const mockPrevState: CopyJobContextState = {
|
||||
source: {
|
||||
subscription: { subscriptionId: "existing-sub" } as any,
|
||||
account: { id: "existing-account" } as any,
|
||||
},
|
||||
migrationType: CopyJobMigrationType.Online,
|
||||
} as any;
|
||||
|
||||
const newState = stateUpdater(mockPrevState);
|
||||
expect(newState).toEqual(mockPrevState);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,80 +0,0 @@
|
||||
import React from "react";
|
||||
import { DatabaseAccount, Subscription } from "../../../../../../Contracts/DataModels";
|
||||
import { CopyJobMigrationType } from "../../../../Enums/CopyJobEnums";
|
||||
import { CopyJobContextProviderType, CopyJobContextState, DropdownOptionType } from "../../../../Types/CopyJobTypes";
|
||||
import { useCopyJobPrerequisitesCache } from "../../../Utils/useCopyJobPrerequisitesCache";
|
||||
|
||||
export function useDropdownOptions(
|
||||
subscriptions: Subscription[],
|
||||
accounts: DatabaseAccount[],
|
||||
): {
|
||||
subscriptionOptions: DropdownOptionType[];
|
||||
accountOptions: DropdownOptionType[];
|
||||
} {
|
||||
const subscriptionOptions =
|
||||
subscriptions?.map((sub) => ({
|
||||
key: sub.subscriptionId,
|
||||
text: sub.displayName,
|
||||
data: sub,
|
||||
})) || [];
|
||||
|
||||
const normalizeAccountId = (id: string) => {
|
||||
if (!id) {
|
||||
return id;
|
||||
}
|
||||
return id.replace(/\/Microsoft\.DocumentDb\//i, "/Microsoft.DocumentDB/");
|
||||
};
|
||||
|
||||
const accountOptions =
|
||||
accounts?.map((account) => ({
|
||||
key: normalizeAccountId(account.id),
|
||||
text: account.name,
|
||||
data: account,
|
||||
})) || [];
|
||||
|
||||
return { subscriptionOptions, accountOptions };
|
||||
}
|
||||
|
||||
type setCopyJobStateType = CopyJobContextProviderType["setCopyJobState"];
|
||||
|
||||
export function useEventHandlers(setCopyJobState: setCopyJobStateType) {
|
||||
const { setValidationCache } = useCopyJobPrerequisitesCache();
|
||||
const handleSelectSourceAccount = (
|
||||
type: "subscription" | "account",
|
||||
data: (Subscription & DatabaseAccount) | undefined,
|
||||
) => {
|
||||
setCopyJobState((prevState: CopyJobContextState) => {
|
||||
if (type === "subscription") {
|
||||
return {
|
||||
...prevState,
|
||||
source: {
|
||||
...prevState.source,
|
||||
subscription: data || null,
|
||||
account: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (type === "account") {
|
||||
return {
|
||||
...prevState,
|
||||
source: {
|
||||
...prevState.source,
|
||||
account: data || null,
|
||||
},
|
||||
};
|
||||
}
|
||||
return prevState;
|
||||
});
|
||||
setValidationCache(new Map<string, boolean>());
|
||||
};
|
||||
|
||||
const handleMigrationTypeChange = React.useCallback((_ev?: React.FormEvent<HTMLElement>, checked?: boolean) => {
|
||||
setCopyJobState((prevState: CopyJobContextState) => ({
|
||||
...prevState,
|
||||
migrationType: checked ? CopyJobMigrationType.Offline : CopyJobMigrationType.Online,
|
||||
}));
|
||||
setValidationCache(new Map<string, boolean>());
|
||||
}, []);
|
||||
|
||||
return { handleSelectSourceAccount, handleMigrationTypeChange };
|
||||
}
|
||||
@@ -1,510 +1,34 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SelectAccount Component Complete Workflow should render complete workflow with all selections 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack selectAccountContainer css-109"
|
||||
exports[`SelectAccount Component Rendering should render correctly with snapshot 1`] = `
|
||||
<div
|
||||
class="ms-Stack selectAccountContainer css-109"
|
||||
data-test="Panel:SelectAccountContainer"
|
||||
>
|
||||
<span
|
||||
class="css-110"
|
||||
>
|
||||
<span>
|
||||
Select your source account and subscription
|
||||
</span>
|
||||
<div
|
||||
data-selected="sub-1"
|
||||
data-testid="subscription-dropdown"
|
||||
>
|
||||
<div
|
||||
data-testid="subscription-option-sub-1"
|
||||
>
|
||||
Test Subscription 1
|
||||
</div>
|
||||
<div
|
||||
data-testid="subscription-option-sub-2"
|
||||
>
|
||||
Test Subscription 2
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-disabled="false"
|
||||
data-selected="/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1"
|
||||
data-testid="account-dropdown"
|
||||
>
|
||||
<div
|
||||
data-testid="account-option-/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1"
|
||||
>
|
||||
test-cosmos-account-1
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-checked="false"
|
||||
data-testid="migration-type-checkbox"
|
||||
>
|
||||
<input
|
||||
data-testid="migration-checkbox-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
Please select a source account from which to copy.
|
||||
</span>
|
||||
<div
|
||||
data-testid="subscription-dropdown"
|
||||
>
|
||||
Subscription Dropdown
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SelectAccount Component Edge Cases should handle empty accounts array 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack selectAccountContainer css-109"
|
||||
data-testid="account-dropdown"
|
||||
>
|
||||
<span>
|
||||
Select your source account and subscription
|
||||
</span>
|
||||
<div
|
||||
data-testid="subscription-dropdown"
|
||||
>
|
||||
<div
|
||||
data-testid="subscription-option-sub-1"
|
||||
>
|
||||
Test Subscription 1
|
||||
</div>
|
||||
<div
|
||||
data-testid="subscription-option-sub-2"
|
||||
>
|
||||
Test Subscription 2
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-disabled="true"
|
||||
data-testid="account-dropdown"
|
||||
Account Dropdown
|
||||
</div>
|
||||
<div
|
||||
data-testid="migration-type-checkbox"
|
||||
>
|
||||
<input
|
||||
aria-label="Migration Type Checkbox"
|
||||
data-testid="migration-checkbox-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
<div
|
||||
data-checked="true"
|
||||
data-testid="migration-type-checkbox"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
data-testid="migration-checkbox-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SelectAccount Component Edge Cases should handle empty subscriptions array 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack selectAccountContainer css-109"
|
||||
>
|
||||
<span>
|
||||
Select your source account and subscription
|
||||
</span>
|
||||
<div
|
||||
data-testid="subscription-dropdown"
|
||||
/>
|
||||
<div
|
||||
data-disabled="true"
|
||||
data-testid="account-dropdown"
|
||||
/>
|
||||
<div
|
||||
data-checked="true"
|
||||
data-testid="migration-type-checkbox"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
data-testid="migration-checkbox-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SelectAccount Component Edge Cases should handle null account in context 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack selectAccountContainer css-109"
|
||||
>
|
||||
<span>
|
||||
Select your source account and subscription
|
||||
</span>
|
||||
<div
|
||||
data-testid="subscription-dropdown"
|
||||
>
|
||||
<div
|
||||
data-testid="subscription-option-sub-1"
|
||||
>
|
||||
Test Subscription 1
|
||||
</div>
|
||||
<div
|
||||
data-testid="subscription-option-sub-2"
|
||||
>
|
||||
Test Subscription 2
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-disabled="true"
|
||||
data-testid="account-dropdown"
|
||||
>
|
||||
<div
|
||||
data-testid="account-option-/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1"
|
||||
>
|
||||
test-cosmos-account-1
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-checked="true"
|
||||
data-testid="migration-type-checkbox"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
data-testid="migration-checkbox-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SelectAccount Component Edge Cases should handle null subscription in context 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack selectAccountContainer css-109"
|
||||
>
|
||||
<span>
|
||||
Select your source account and subscription
|
||||
</span>
|
||||
<div
|
||||
data-testid="subscription-dropdown"
|
||||
>
|
||||
<div
|
||||
data-testid="subscription-option-sub-1"
|
||||
>
|
||||
Test Subscription 1
|
||||
</div>
|
||||
<div
|
||||
data-testid="subscription-option-sub-2"
|
||||
>
|
||||
Test Subscription 2
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-disabled="true"
|
||||
data-testid="account-dropdown"
|
||||
>
|
||||
<div
|
||||
data-testid="account-option-/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1"
|
||||
>
|
||||
test-cosmos-account-1
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-checked="true"
|
||||
data-testid="migration-type-checkbox"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
data-testid="migration-checkbox-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SelectAccount Component Edge Cases should handle undefined accounts from hook 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack selectAccountContainer css-109"
|
||||
>
|
||||
<span>
|
||||
Select your source account and subscription
|
||||
</span>
|
||||
<div
|
||||
data-testid="subscription-dropdown"
|
||||
>
|
||||
<div
|
||||
data-testid="subscription-option-sub-1"
|
||||
>
|
||||
Test Subscription 1
|
||||
</div>
|
||||
<div
|
||||
data-testid="subscription-option-sub-2"
|
||||
>
|
||||
Test Subscription 2
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-disabled="true"
|
||||
data-testid="account-dropdown"
|
||||
/>
|
||||
<div
|
||||
data-checked="true"
|
||||
data-testid="migration-type-checkbox"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
data-testid="migration-checkbox-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SelectAccount Component Edge Cases should handle undefined subscriptions from hook 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack selectAccountContainer css-109"
|
||||
>
|
||||
<span>
|
||||
Select your source account and subscription
|
||||
</span>
|
||||
<div
|
||||
data-testid="subscription-dropdown"
|
||||
/>
|
||||
<div
|
||||
data-disabled="true"
|
||||
data-testid="account-dropdown"
|
||||
/>
|
||||
<div
|
||||
data-checked="true"
|
||||
data-testid="migration-type-checkbox"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
data-testid="migration-checkbox-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SelectAccount Component Rendering should render component with default state 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack selectAccountContainer css-109"
|
||||
>
|
||||
<span>
|
||||
Select your source account and subscription
|
||||
</span>
|
||||
<div
|
||||
data-testid="subscription-dropdown"
|
||||
>
|
||||
<div
|
||||
data-testid="subscription-option-sub-1"
|
||||
>
|
||||
Test Subscription 1
|
||||
</div>
|
||||
<div
|
||||
data-testid="subscription-option-sub-2"
|
||||
>
|
||||
Test Subscription 2
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-disabled="true"
|
||||
data-testid="account-dropdown"
|
||||
>
|
||||
<div
|
||||
data-testid="account-option-/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1"
|
||||
>
|
||||
test-cosmos-account-1
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-checked="true"
|
||||
data-testid="migration-type-checkbox"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
data-testid="migration-checkbox-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SelectAccount Component Rendering should render with offline migration type checked 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack selectAccountContainer css-109"
|
||||
>
|
||||
<span>
|
||||
Select your source account and subscription
|
||||
</span>
|
||||
<div
|
||||
data-testid="subscription-dropdown"
|
||||
>
|
||||
<div
|
||||
data-testid="subscription-option-sub-1"
|
||||
>
|
||||
Test Subscription 1
|
||||
</div>
|
||||
<div
|
||||
data-testid="subscription-option-sub-2"
|
||||
>
|
||||
Test Subscription 2
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-disabled="true"
|
||||
data-testid="account-dropdown"
|
||||
>
|
||||
<div
|
||||
data-testid="account-option-/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1"
|
||||
>
|
||||
test-cosmos-account-1
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-checked="true"
|
||||
data-testid="migration-type-checkbox"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
data-testid="migration-checkbox-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SelectAccount Component Rendering should render with online migration type unchecked 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack selectAccountContainer css-109"
|
||||
>
|
||||
<span>
|
||||
Select your source account and subscription
|
||||
</span>
|
||||
<div
|
||||
data-testid="subscription-dropdown"
|
||||
>
|
||||
<div
|
||||
data-testid="subscription-option-sub-1"
|
||||
>
|
||||
Test Subscription 1
|
||||
</div>
|
||||
<div
|
||||
data-testid="subscription-option-sub-2"
|
||||
>
|
||||
Test Subscription 2
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-disabled="true"
|
||||
data-testid="account-dropdown"
|
||||
>
|
||||
<div
|
||||
data-testid="account-option-/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1"
|
||||
>
|
||||
test-cosmos-account-1
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-checked="false"
|
||||
data-testid="migration-type-checkbox"
|
||||
>
|
||||
<input
|
||||
data-testid="migration-checkbox-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SelectAccount Component Rendering should render with selected account 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack selectAccountContainer css-109"
|
||||
>
|
||||
<span>
|
||||
Select your source account and subscription
|
||||
</span>
|
||||
<div
|
||||
data-selected="sub-1"
|
||||
data-testid="subscription-dropdown"
|
||||
>
|
||||
<div
|
||||
data-testid="subscription-option-sub-1"
|
||||
>
|
||||
Test Subscription 1
|
||||
</div>
|
||||
<div
|
||||
data-testid="subscription-option-sub-2"
|
||||
>
|
||||
Test Subscription 2
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-disabled="false"
|
||||
data-selected="/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1"
|
||||
data-testid="account-dropdown"
|
||||
>
|
||||
<div
|
||||
data-testid="account-option-/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1"
|
||||
>
|
||||
test-cosmos-account-1
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-checked="true"
|
||||
data-testid="migration-type-checkbox"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
data-testid="migration-checkbox-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SelectAccount Component Rendering should render with selected subscription 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ms-Stack selectAccountContainer css-109"
|
||||
>
|
||||
<span>
|
||||
Select your source account and subscription
|
||||
</span>
|
||||
<div
|
||||
data-selected="sub-1"
|
||||
data-testid="subscription-dropdown"
|
||||
>
|
||||
<div
|
||||
data-testid="subscription-option-sub-1"
|
||||
>
|
||||
Test Subscription 1
|
||||
</div>
|
||||
<div
|
||||
data-testid="subscription-option-sub-2"
|
||||
>
|
||||
Test Subscription 2
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-disabled="false"
|
||||
data-testid="account-dropdown"
|
||||
>
|
||||
<div
|
||||
data-testid="account-option-/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1"
|
||||
>
|
||||
test-cosmos-account-1
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-checked="true"
|
||||
data-testid="migration-type-checkbox"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
data-testid="migration-checkbox-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
Copy container in offline mode
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -39,6 +39,7 @@ export function useCopyJobNavigation() {
|
||||
const [state, dispatch] = useReducer(navigationReducer, { screenHistory: [SCREEN_KEYS.SelectAccount] });
|
||||
|
||||
const handlePrevious = useCallback(() => {
|
||||
setContextError(null);
|
||||
dispatch({ type: "PREVIOUS" });
|
||||
}, [dispatch]);
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ describe("CopyJobStatusWithIcon", () => {
|
||||
|
||||
const spinner = container.querySelector('[class*="ms-Spinner"]');
|
||||
expect(spinner).toBeInTheDocument();
|
||||
expect(container).toHaveTextContent("In Progress");
|
||||
expect(container).toHaveTextContent("Running");
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -83,18 +83,18 @@ describe("CopyJobStatusWithIcon", () => {
|
||||
it("provides meaningful text content for screen readers", () => {
|
||||
const { container } = render(<CopyJobStatusWithIcon status={CopyJobStatusType.InProgress} />);
|
||||
|
||||
expect(container).toHaveTextContent("In Progress");
|
||||
expect(container).toHaveTextContent("Running");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Icon and Status Mapping", () => {
|
||||
it("renders correct status text based on mapping", () => {
|
||||
const statusMappings = [
|
||||
{ status: CopyJobStatusType.Pending, expectedText: "Pending" },
|
||||
{ status: CopyJobStatusType.Pending, expectedText: "Queued" },
|
||||
{ status: CopyJobStatusType.Paused, expectedText: "Paused" },
|
||||
{ status: CopyJobStatusType.Failed, expectedText: "Failed" },
|
||||
{ status: CopyJobStatusType.Completed, expectedText: "Completed" },
|
||||
{ status: CopyJobStatusType.Running, expectedText: "In Progress" },
|
||||
{ status: CopyJobStatusType.Running, expectedText: "Running" },
|
||||
];
|
||||
|
||||
statusMappings.forEach(({ status, expectedText }) => {
|
||||
|
||||
@@ -15,7 +15,7 @@ exports[`CopyJobStatusWithIcon Spinner Status Types renders InProgress with spin
|
||||
<span
|
||||
class="css-112"
|
||||
>
|
||||
In Progress
|
||||
Running
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
@@ -35,7 +35,7 @@ exports[`CopyJobStatusWithIcon Spinner Status Types renders Partitioning with sp
|
||||
<span
|
||||
class="css-112"
|
||||
>
|
||||
In Progress
|
||||
Running
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
@@ -55,7 +55,7 @@ exports[`CopyJobStatusWithIcon Spinner Status Types renders Running with spinner
|
||||
<span
|
||||
class="css-112"
|
||||
>
|
||||
In Progress
|
||||
Running
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
@@ -181,7 +181,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
|
||||
<span
|
||||
class="css-112"
|
||||
>
|
||||
Pending
|
||||
Queued
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -44,7 +44,9 @@ const MonitorCopyJobs = forwardRef<MonitorCopyJobsRef, MonitorCopyJobsProps>(({
|
||||
return isEqual(prevJobs, normalizedResponse) ? prevJobs : normalizedResponse;
|
||||
});
|
||||
} catch (error) {
|
||||
setError(error.message || "Failed to load copy jobs. Please try again later.");
|
||||
if (error.message !== "Previous copy job request was cancelled.") {
|
||||
setError(error.message || "Failed to load copy jobs. Please try again later.");
|
||||
}
|
||||
} finally {
|
||||
if (isFirstFetchRef.current) {
|
||||
setLoading(false);
|
||||
|
||||
@@ -56,14 +56,14 @@ export interface CopyJobContextState {
|
||||
migrationType: CopyJobMigrationType;
|
||||
sourceReadAccessFromTarget?: boolean;
|
||||
source: {
|
||||
subscription: Subscription;
|
||||
account: DatabaseAccount;
|
||||
subscription: Subscription | null;
|
||||
account: DatabaseAccount | null;
|
||||
databaseId: string;
|
||||
containerId: string;
|
||||
};
|
||||
target: {
|
||||
subscriptionId: string;
|
||||
account: DatabaseAccount;
|
||||
account: DatabaseAccount | null;
|
||||
databaseId: string;
|
||||
containerId: string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user