mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-23 02:41:39 +00:00
Compare commits
3 Commits
sung_playw
...
userssaksh
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
156f748b5e | ||
|
|
bc7e8a71ca | ||
|
|
d67c1a0464 |
@@ -14,7 +14,7 @@ export default defineConfig({
|
||||
trace: "off",
|
||||
video: "off",
|
||||
screenshot: "on",
|
||||
testIdAttribute: "data-testid",
|
||||
testIdAttribute: "data-test",
|
||||
contextOptions: {
|
||||
ignoreHTTPSErrors: true,
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -76,10 +76,12 @@ const mockedRbacUtils = RbacUtils as jest.Mocked<typeof RbacUtils>;
|
||||
const mockedCopyJobPrerequisitesCache = CopyJobPrerequisitesCacheModule as jest.Mocked<
|
||||
typeof CopyJobPrerequisitesCacheModule
|
||||
>;
|
||||
|
||||
interface TestWrapperProps {
|
||||
state: CopyJobContextState;
|
||||
onResult?: (result: PermissionGroupConfig[]) => void;
|
||||
}
|
||||
|
||||
const TestWrapper: React.FC<TestWrapperProps> = ({ state, onResult }) => {
|
||||
const result = usePermissionSections(state);
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -214,9 +214,9 @@ export const Dialog: FC = () => {
|
||||
{contentHtml}
|
||||
{progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />}
|
||||
<DialogFooter>
|
||||
<PrimaryButton {...primaryButtonProps} data-testid={`DialogButton:${primaryButtonText}`} />
|
||||
<PrimaryButton {...primaryButtonProps} data-test={`DialogButton:${primaryButtonText}`} />
|
||||
{secondaryButtonProps && (
|
||||
<DefaultButton {...secondaryButtonProps} data-testid={`DialogButton:${secondaryButtonText}`} />
|
||||
<DefaultButton {...secondaryButtonProps} data-test={`DialogButton:${secondaryButtonText}`} />
|
||||
)}
|
||||
</DialogFooter>
|
||||
</FluentDialog>
|
||||
|
||||
@@ -137,7 +137,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
||||
<Spinner size={SpinnerSize.large} className={this.props.spinnerClassName || "spinner"} />
|
||||
)}
|
||||
<div
|
||||
data-testid="EditorReact/Host/Unloaded"
|
||||
data-test="EditorReact/Host/Unloaded"
|
||||
className={this.props.className || "jsonEditor"}
|
||||
style={this.props.monacoContainerStyles}
|
||||
ref={(elt: HTMLElement) => this.setRef(elt)}
|
||||
@@ -148,7 +148,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
||||
|
||||
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
|
||||
this.editor = editor;
|
||||
this.rootNode.dataset["testid"] = "EditorReact/Host/Loaded";
|
||||
this.rootNode.dataset["test"] = "EditorReact/Host/Loaded";
|
||||
|
||||
// In development, we want to be able to access the editor instance from the console
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
|
||||
@@ -193,7 +193,7 @@ export const InputDataList: FC<InputDataListProps> = ({
|
||||
<>
|
||||
<Input
|
||||
id="filterInput"
|
||||
data-testid={"DocumentsTab/FilterInput"}
|
||||
data-test={"DocumentsTab/FilterInput"}
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
size="small"
|
||||
|
||||
@@ -1482,6 +1482,9 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
itemKey: SettingsV2TabTypes[tab.tab],
|
||||
style: { marginTop: 20 },
|
||||
headerText: getTabTitle(tab.tab),
|
||||
headerButtonProps: {
|
||||
"data-test": `settings-tab-header/${SettingsV2TabTypes[tab.tab]}`,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -127,9 +127,9 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
||||
};
|
||||
|
||||
private ttlChoiceGroupOptions: IChoiceGroupOption[] = [
|
||||
{ key: TtlType.Off, text: "Off" },
|
||||
{ key: TtlType.OnNoDefault, text: "On (no default)" },
|
||||
{ key: TtlType.On, text: "On" },
|
||||
{ key: TtlType.Off, text: "Off", ariaLabel: "ttl-off-option" },
|
||||
{ key: TtlType.OnNoDefault, text: "On (no default)", ariaLabel: "ttl-on-no-default-option" },
|
||||
{ key: TtlType.On, text: "On", ariaLabel: "ttl-on-option" },
|
||||
];
|
||||
|
||||
public getTtlValue = (value: string): TtlType => {
|
||||
@@ -223,6 +223,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
||||
onChange={this.onTimeToLiveSecondsChange}
|
||||
suffix="second(s)"
|
||||
ariaLabel={`Time to live in seconds`}
|
||||
data-test="ttl-input"
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -503,7 +503,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
<span style={{ float: "left", transform: "translateX(-50%)" }}>
|
||||
{this.props.instantMaximumThroughput.toLocaleString()}
|
||||
</span>
|
||||
<span style={{ float: "right" }}>{this.props.softAllowedMaximumThroughput.toLocaleString()}</span>
|
||||
<span style={{ float: "right" }} data-test="soft-allowed-maximum-throughput">
|
||||
{this.props.softAllowedMaximumThroughput.toLocaleString()}
|
||||
</span>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
<ProgressIndicator
|
||||
@@ -626,11 +628,12 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
min={autoPilotThroughput1K}
|
||||
onGetErrorMessage={(value: string) => {
|
||||
const sanitizedValue = getSanitizedInputValue(value);
|
||||
return sanitizedValue % 1000
|
||||
? "Throughput value must be in increments of 1000"
|
||||
: this.props.throughputError;
|
||||
const errorMessage: string =
|
||||
sanitizedValue % 1000 ? "Throughput value must be in increments of 1000" : this.props.throughputError;
|
||||
return <span data-test="autopilot-throughput-input-error">{errorMessage}</span>;
|
||||
}}
|
||||
validateOnLoad={false}
|
||||
data-test="autopilot-throughput-input"
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
@@ -650,7 +653,10 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
}
|
||||
onChange={this.onThroughputChange}
|
||||
min={this.props.minimum}
|
||||
errorMessage={this.props.throughputError}
|
||||
onGetErrorMessage={() => {
|
||||
return <span data-test="manual-throughput-input-error">{this.props.throughputError}</span>;
|
||||
}}
|
||||
data-test="manual-throughput-input"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -273,6 +273,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
||||
/>
|
||||
</Stack>
|
||||
<StyledTextFieldBase
|
||||
data-test="autopilot-throughput-input"
|
||||
disabled={true}
|
||||
id="autopilotInput"
|
||||
key="auto pilot throughput input"
|
||||
@@ -333,6 +334,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
||||
5,000
|
||||
</span>
|
||||
<span
|
||||
data-test="soft-allowed-maximum-throughput"
|
||||
style={
|
||||
{
|
||||
"float": "right",
|
||||
@@ -752,11 +754,13 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
||||
}
|
||||
>
|
||||
<StyledTextFieldBase
|
||||
data-test="manual-throughput-input"
|
||||
disabled={false}
|
||||
id="throughputInput"
|
||||
key="provisioned throughput input"
|
||||
min={10000}
|
||||
onChange={[Function]}
|
||||
onGetErrorMessage={[Function]}
|
||||
required={true}
|
||||
step={100}
|
||||
styles={
|
||||
@@ -811,6 +815,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
||||
5,000
|
||||
</span>
|
||||
<span
|
||||
data-test="soft-allowed-maximum-throughput"
|
||||
style={
|
||||
{
|
||||
"float": "right",
|
||||
@@ -1206,11 +1211,13 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
|
||||
}
|
||||
>
|
||||
<StyledTextFieldBase
|
||||
data-test="manual-throughput-input"
|
||||
disabled={false}
|
||||
id="throughputInput"
|
||||
key="provisioned throughput input"
|
||||
min={10000}
|
||||
onChange={[Function]}
|
||||
onGetErrorMessage={[Function]}
|
||||
required={true}
|
||||
step={100}
|
||||
styles={
|
||||
@@ -1265,6 +1272,7 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
|
||||
5,000
|
||||
</span>
|
||||
<span
|
||||
data-test="soft-allowed-maximum-throughput"
|
||||
style={
|
||||
{
|
||||
"float": "right",
|
||||
|
||||
@@ -22,14 +22,17 @@ exports[`SubSettingsComponent analyticalTimeToLive hidden 1`] = `
|
||||
options={
|
||||
[
|
||||
{
|
||||
"ariaLabel": "ttl-off-option",
|
||||
"key": "off",
|
||||
"text": "Off",
|
||||
},
|
||||
{
|
||||
"ariaLabel": "ttl-on-no-default-option",
|
||||
"key": "on-nodefault",
|
||||
"text": "On (no default)",
|
||||
},
|
||||
{
|
||||
"ariaLabel": "ttl-on-option",
|
||||
"key": "on",
|
||||
"text": "On",
|
||||
},
|
||||
@@ -63,6 +66,7 @@ exports[`SubSettingsComponent analyticalTimeToLive hidden 1`] = `
|
||||
/>
|
||||
<StyledTextFieldBase
|
||||
ariaLabel="Time to live in seconds"
|
||||
data-test="ttl-input"
|
||||
id="timeToLiveSeconds"
|
||||
max={2147483647}
|
||||
min={1}
|
||||
@@ -284,14 +288,17 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
|
||||
options={
|
||||
[
|
||||
{
|
||||
"ariaLabel": "ttl-off-option",
|
||||
"key": "off",
|
||||
"text": "Off",
|
||||
},
|
||||
{
|
||||
"ariaLabel": "ttl-on-no-default-option",
|
||||
"key": "on-nodefault",
|
||||
"text": "On (no default)",
|
||||
},
|
||||
{
|
||||
"ariaLabel": "ttl-on-option",
|
||||
"key": "on",
|
||||
"text": "On",
|
||||
},
|
||||
@@ -325,6 +332,7 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
|
||||
/>
|
||||
<StyledTextFieldBase
|
||||
ariaLabel="Time to live in seconds"
|
||||
data-test="ttl-input"
|
||||
id="timeToLiveSeconds"
|
||||
max={2147483647}
|
||||
min={1}
|
||||
@@ -601,14 +609,17 @@ exports[`SubSettingsComponent changeFeedPolicy hidden 1`] = `
|
||||
options={
|
||||
[
|
||||
{
|
||||
"ariaLabel": "ttl-off-option",
|
||||
"key": "off",
|
||||
"text": "Off",
|
||||
},
|
||||
{
|
||||
"ariaLabel": "ttl-on-no-default-option",
|
||||
"key": "on-nodefault",
|
||||
"text": "On (no default)",
|
||||
},
|
||||
{
|
||||
"ariaLabel": "ttl-on-option",
|
||||
"key": "on",
|
||||
"text": "On",
|
||||
},
|
||||
@@ -642,6 +653,7 @@ exports[`SubSettingsComponent changeFeedPolicy hidden 1`] = `
|
||||
/>
|
||||
<StyledTextFieldBase
|
||||
ariaLabel="Time to live in seconds"
|
||||
data-test="ttl-input"
|
||||
id="timeToLiveSeconds"
|
||||
max={2147483647}
|
||||
min={1}
|
||||
@@ -878,14 +890,17 @@ exports[`SubSettingsComponent renders 1`] = `
|
||||
options={
|
||||
[
|
||||
{
|
||||
"ariaLabel": "ttl-off-option",
|
||||
"key": "off",
|
||||
"text": "Off",
|
||||
},
|
||||
{
|
||||
"ariaLabel": "ttl-on-no-default-option",
|
||||
"key": "on-nodefault",
|
||||
"text": "On (no default)",
|
||||
},
|
||||
{
|
||||
"ariaLabel": "ttl-on-option",
|
||||
"key": "on",
|
||||
"text": "On",
|
||||
},
|
||||
@@ -919,6 +934,7 @@ exports[`SubSettingsComponent renders 1`] = `
|
||||
/>
|
||||
<StyledTextFieldBase
|
||||
ariaLabel="Time to live in seconds"
|
||||
data-test="ttl-input"
|
||||
id="timeToLiveSeconds"
|
||||
max={2147483647}
|
||||
min={1}
|
||||
@@ -1220,14 +1236,17 @@ exports[`SubSettingsComponent timeToLiveSeconds hidden 1`] = `
|
||||
options={
|
||||
[
|
||||
{
|
||||
"ariaLabel": "ttl-off-option",
|
||||
"key": "off",
|
||||
"text": "Off",
|
||||
},
|
||||
{
|
||||
"ariaLabel": "ttl-on-no-default-option",
|
||||
"key": "on-nodefault",
|
||||
"text": "On (no default)",
|
||||
},
|
||||
{
|
||||
"ariaLabel": "ttl-on-option",
|
||||
"key": "on",
|
||||
"text": "On",
|
||||
},
|
||||
|
||||
@@ -12,6 +12,11 @@ exports[`SettingsComponent renders 1`] = `
|
||||
selectedKey="ScaleTab"
|
||||
>
|
||||
<PivotItem
|
||||
headerButtonProps={
|
||||
{
|
||||
"data-test": "settings-tab-header/ScaleTab",
|
||||
}
|
||||
}
|
||||
headerText="Scale"
|
||||
itemKey="ScaleTab"
|
||||
key="ScaleTab"
|
||||
@@ -102,6 +107,11 @@ exports[`SettingsComponent renders 1`] = `
|
||||
/>
|
||||
</PivotItem>
|
||||
<PivotItem
|
||||
headerButtonProps={
|
||||
{
|
||||
"data-test": "settings-tab-header/SubSettingsTab",
|
||||
}
|
||||
}
|
||||
headerText="Settings"
|
||||
itemKey="SubSettingsTab"
|
||||
key="SubSettingsTab"
|
||||
@@ -201,6 +211,11 @@ exports[`SettingsComponent renders 1`] = `
|
||||
/>
|
||||
</PivotItem>
|
||||
<PivotItem
|
||||
headerButtonProps={
|
||||
{
|
||||
"data-test": "settings-tab-header/ContainerVectorPolicyTab",
|
||||
}
|
||||
}
|
||||
headerText="Container Policies"
|
||||
itemKey="ContainerVectorPolicyTab"
|
||||
key="ContainerVectorPolicyTab"
|
||||
@@ -227,6 +242,11 @@ exports[`SettingsComponent renders 1`] = `
|
||||
/>
|
||||
</PivotItem>
|
||||
<PivotItem
|
||||
headerButtonProps={
|
||||
{
|
||||
"data-test": "settings-tab-header/IndexingPolicyTab",
|
||||
}
|
||||
}
|
||||
headerText="Indexing Policy"
|
||||
itemKey="IndexingPolicyTab"
|
||||
key="IndexingPolicyTab"
|
||||
@@ -263,6 +283,11 @@ exports[`SettingsComponent renders 1`] = `
|
||||
/>
|
||||
</PivotItem>
|
||||
<PivotItem
|
||||
headerButtonProps={
|
||||
{
|
||||
"data-test": "settings-tab-header/PartitionKeyTab",
|
||||
}
|
||||
}
|
||||
headerText="Partition Keys (preview)"
|
||||
itemKey="PartitionKeyTab"
|
||||
key="PartitionKeyTab"
|
||||
@@ -370,6 +395,11 @@ exports[`SettingsComponent renders 1`] = `
|
||||
/>
|
||||
</PivotItem>
|
||||
<PivotItem
|
||||
headerButtonProps={
|
||||
{
|
||||
"data-test": "settings-tab-header/ComputedPropertiesTab",
|
||||
}
|
||||
}
|
||||
headerText="Computed Properties"
|
||||
itemKey="ComputedPropertiesTab"
|
||||
key="ComputedPropertiesTab"
|
||||
@@ -404,6 +434,11 @@ exports[`SettingsComponent renders 1`] = `
|
||||
/>
|
||||
</PivotItem>
|
||||
<PivotItem
|
||||
headerButtonProps={
|
||||
{
|
||||
"data-test": "settings-tab-header/GlobalSecondaryIndexTab",
|
||||
}
|
||||
}
|
||||
headerText="Global Secondary Index (Preview)"
|
||||
itemKey="GlobalSecondaryIndexTab"
|
||||
key="GlobalSecondaryIndexTab"
|
||||
|
||||
@@ -209,7 +209,6 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
checked={isAutoscaleSelected}
|
||||
type="radio"
|
||||
role="radio"
|
||||
data-testid="ThroughputInput/ThroughputMode:Autoscale"
|
||||
tabIndex={0}
|
||||
onChange={(e) => handleOnChangeMode(e, "Autoscale")}
|
||||
/>
|
||||
@@ -225,7 +224,6 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
type="radio"
|
||||
aria-required={true}
|
||||
role="radio"
|
||||
data-testid="ThroughputInput/ThroughputMode:Manual"
|
||||
tabIndex={0}
|
||||
onChange={(e) => handleOnChangeMode(e, "Manual")}
|
||||
/>
|
||||
@@ -288,7 +286,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
</Stack>
|
||||
<TextField
|
||||
id="autoscaleRUValueField"
|
||||
data-testid="ThroughputInput/AutoscaleRUInput"
|
||||
data-test="autoscaleRUInput"
|
||||
type="number"
|
||||
styles={{
|
||||
fieldGroup: { width: 100, height: 27, flexShrink: 0 },
|
||||
@@ -354,7 +352,6 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
}
|
||||
>
|
||||
<TextField
|
||||
data-testid="ThroughputInput/ManualThroughputInput"
|
||||
type="number"
|
||||
styles={{
|
||||
fieldGroup: { width: 300, height: 27 },
|
||||
|
||||
@@ -682,7 +682,6 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
aria-required={true}
|
||||
checked={true}
|
||||
className="throughputInputRadioBtn"
|
||||
data-testid="ThroughputInput/ThroughputMode:Autoscale"
|
||||
id="Autoscale-input"
|
||||
onChange={[Function]}
|
||||
role="radio"
|
||||
@@ -700,7 +699,6 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
aria-required={true}
|
||||
checked={false}
|
||||
className="throughputInputRadioBtn"
|
||||
data-testid="ThroughputInput/ThroughputMode:Manual"
|
||||
id="Manual-input"
|
||||
onChange={[Function]}
|
||||
role="radio"
|
||||
@@ -2146,7 +2144,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
</Stack>
|
||||
<StyledTextFieldBase
|
||||
ariaLabel="Container max RU/s"
|
||||
data-testid="ThroughputInput/AutoscaleRUInput"
|
||||
data-test="autoscaleRUInput"
|
||||
errorMessage=""
|
||||
id="autoscaleRUValueField"
|
||||
key=".0:$.$.1"
|
||||
@@ -2173,7 +2171,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
>
|
||||
<TextFieldBase
|
||||
ariaLabel="Container max RU/s"
|
||||
data-testid="ThroughputInput/AutoscaleRUInput"
|
||||
data-test="autoscaleRUInput"
|
||||
deferredValidationTime={200}
|
||||
errorMessage=""
|
||||
id="autoscaleRUValueField"
|
||||
@@ -2474,7 +2472,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
aria-invalid={false}
|
||||
aria-label="Container max RU/s"
|
||||
className="ms-TextField-field field-124"
|
||||
data-testid="ThroughputInput/AutoscaleRUInput"
|
||||
data-test="autoscaleRUInput"
|
||||
id="autoscaleRUValueField"
|
||||
max="9007199254740991"
|
||||
min={1000}
|
||||
|
||||
@@ -139,7 +139,7 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
|
||||
|
||||
const contextMenuItems = (node.contextMenu ?? []).map((menuItem) => (
|
||||
<MenuItem
|
||||
data-testid={`TreeNode/ContextMenuItem:${menuItem.label}`}
|
||||
data-test={`TreeNode/ContextMenuItem:${menuItem.label}`}
|
||||
disabled={menuItem.isDisabled}
|
||||
key={menuItem.label}
|
||||
onClick={() => menuItem.onClick(contextMenuRef)}
|
||||
@@ -160,14 +160,14 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
|
||||
const expandIcon = isLoading ? (
|
||||
<Spinner size="extra-tiny" />
|
||||
) : !isBranch ? undefined : openItems.includes(treeNodeId) ? (
|
||||
<ChevronDown20Regular data-testid="TreeNode/CollapseIcon" />
|
||||
<ChevronDown20Regular data-test="TreeNode/CollapseIcon" />
|
||||
) : (
|
||||
<ChevronRight20Regular data-testid="TreeNode/ExpandIcon" />
|
||||
<ChevronRight20Regular data-text="TreeNode/ExpandIcon" />
|
||||
);
|
||||
|
||||
const treeItem = (
|
||||
<TreeItem
|
||||
data-testid={`TreeNodeContainer:${treeNodeId}`}
|
||||
data-test={`TreeNodeContainer:${treeNodeId}`}
|
||||
value={treeNodeId}
|
||||
itemType={isBranch ? "branch" : "leaf"}
|
||||
onOpenChange={onOpenChange}
|
||||
@@ -179,7 +179,7 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
|
||||
shouldShowAsSelected && treeStyles.selectedItem,
|
||||
node.className && treeStyles[node.className],
|
||||
)}
|
||||
data-testid={`TreeNode:${treeNodeId}`}
|
||||
data-test={`TreeNode:${treeNodeId}`}
|
||||
actions={
|
||||
contextMenuItems.length > 0 && {
|
||||
className: treeStyles.actionsButtonContainer,
|
||||
@@ -189,13 +189,13 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
|
||||
<Button
|
||||
aria-label="More options"
|
||||
className={mergeClasses(treeStyles.actionsButton, shouldShowAsSelected && treeStyles.selectedItem)}
|
||||
data-testid="TreeNode/ContextMenuTrigger"
|
||||
data-test="TreeNode/ContextMenuTrigger"
|
||||
appearance="subtle"
|
||||
ref={contextMenuRef}
|
||||
icon={<MoreHorizontal20Regular />}
|
||||
/>
|
||||
</MenuTrigger>
|
||||
<MenuPopover data-testid={`TreeNode/ContextMenu:${treeNodeId}`}>
|
||||
<MenuPopover data-test={`TreeNode/ContextMenu:${treeNodeId}`}>
|
||||
<MenuList>{contextMenuItems}</MenuList>
|
||||
</MenuPopover>
|
||||
</Menu>
|
||||
@@ -208,7 +208,7 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
|
||||
<span className={treeStyles.nodeLabel}>{node.label}</span>
|
||||
</TreeItemLayout>
|
||||
{!node.isLoading && node.children?.length > 0 && (
|
||||
<Tree data-testid={`Tree:${treeNodeId}`} className={treeStyles.tree}>
|
||||
<Tree data-test={`Tree:${treeNodeId}`} className={treeStyles.tree}>
|
||||
{getSortedChildren(node).map((childNode: TreeNode) => (
|
||||
<TreeNodeComponent
|
||||
openItems={openItems}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`TreeNodeComponent does not render children if the node is loading 1`] = `
|
||||
<TreeItem
|
||||
className=""
|
||||
data-testid="TreeNodeContainer:root"
|
||||
data-test="TreeNodeContainer:root"
|
||||
itemType="branch"
|
||||
onOpenChange={[Function]}
|
||||
value="root"
|
||||
@@ -11,10 +11,10 @@ exports[`TreeNodeComponent does not render children if the node is loading 1`] =
|
||||
<TreeItemLayout
|
||||
actions={false}
|
||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root"
|
||||
data-test="TreeNode:root"
|
||||
expandIcon={
|
||||
<ChevronRight20Regular
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
/>
|
||||
}
|
||||
iconBefore={
|
||||
@@ -112,7 +112,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
>
|
||||
<TreeItem
|
||||
className=""
|
||||
data-testid="TreeNodeContainer:root"
|
||||
data-test="TreeNodeContainer:root"
|
||||
itemType="branch"
|
||||
onOpenChange={[Function]}
|
||||
value="root"
|
||||
@@ -122,7 +122,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
aria-level={0}
|
||||
className="fui-TreeItem r15xhw3a"
|
||||
data-fui-tree-item-value="root"
|
||||
data-testid="TreeNodeContainer:root"
|
||||
data-test="TreeNodeContainer:root"
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
@@ -144,7 +144,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
@@ -164,7 +164,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
"layoutRef": {
|
||||
"current": <div
|
||||
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root"
|
||||
data-test="TreeNode:root"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
@@ -173,7 +173,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
@@ -220,13 +220,13 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
aria-level="0"
|
||||
class="fui-TreeItem r15xhw3a"
|
||||
data-fui-tree-item-value="root"
|
||||
data-testid="TreeNodeContainer:root"
|
||||
data-test="TreeNodeContainer:root"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root"
|
||||
data-test="TreeNode:root"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
@@ -235,7 +235,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
@@ -270,7 +270,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
</div>
|
||||
<div
|
||||
class="fui-Tree rnv2ez3 ___17a32do_7zrvj80 f1acs6jw f11qra4b fepn2xe f1nbblvp f19d5ny4 fzz4f4n"
|
||||
data-testid="Tree:root"
|
||||
data-test="Tree:root"
|
||||
role="tree"
|
||||
>
|
||||
<div
|
||||
@@ -278,13 +278,13 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
aria-level="1"
|
||||
class="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
||||
data-fui-tree-item-value="root/child1Label"
|
||||
data-testid="TreeNodeContainer:root/child1Label"
|
||||
data-test="TreeNodeContainer:root/child1Label"
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root/child1Label"
|
||||
data-test="TreeNode:root/child1Label"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
@@ -293,7 +293,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
@@ -332,13 +332,13 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
aria-level="1"
|
||||
class="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
||||
data-fui-tree-item-value="root/child2LoadingLabel"
|
||||
data-testid="TreeNodeContainer:root/child2LoadingLabel"
|
||||
data-test="TreeNodeContainer:root/child2LoadingLabel"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root/child2LoadingLabel"
|
||||
data-test="TreeNode:root/child2LoadingLabel"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
@@ -347,7 +347,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
@@ -385,13 +385,13 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
aria-level="1"
|
||||
class="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
||||
data-fui-tree-item-value="root/child3ExpandingLabel"
|
||||
data-testid="TreeNodeContainer:root/child3ExpandingLabel"
|
||||
data-test="TreeNodeContainer:root/child3ExpandingLabel"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="fui-TreeItemLayout r1bx0xiv ___dxcrnh0_vz3p260 fk6fouc fkhj508 figsok6 f1i3iumi f1k1erfc fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root/child3ExpandingLabel"
|
||||
data-test="TreeNode:root/child3ExpandingLabel"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
@@ -441,10 +441,10 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
<TreeItemLayout
|
||||
actions={false}
|
||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root"
|
||||
data-test="TreeNode:root"
|
||||
expandIcon={
|
||||
<ChevronRight20Regular
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
/>
|
||||
}
|
||||
iconBefore={
|
||||
@@ -457,19 +457,19 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
>
|
||||
<div
|
||||
className="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root"
|
||||
data-test="TreeNode:root"
|
||||
>
|
||||
<div
|
||||
aria-hidden={true}
|
||||
className="fui-TreeItemLayout__expandIcon rh4pu5o"
|
||||
>
|
||||
<ChevronRight20Regular
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
>
|
||||
<svg
|
||||
aria-hidden={true}
|
||||
className="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
@@ -506,7 +506,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
</TreeItemLayout>
|
||||
<Tree
|
||||
className="___17a32do_0000000 f1acs6jw f11qra4b fepn2xe f1nbblvp f19d5ny4 fzz4f4n"
|
||||
data-testid="Tree:root"
|
||||
data-test="Tree:root"
|
||||
>
|
||||
<TreeProvider
|
||||
value={
|
||||
@@ -574,7 +574,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
>
|
||||
<div
|
||||
className="fui-Tree rnv2ez3 ___17a32do_7zrvj80 f1acs6jw f11qra4b fepn2xe f1nbblvp f19d5ny4 fzz4f4n"
|
||||
data-testid="Tree:root"
|
||||
data-test="Tree:root"
|
||||
role="tree"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
@@ -610,7 +610,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
>
|
||||
<TreeItem
|
||||
className=""
|
||||
data-testid="TreeNodeContainer:root/child1Label"
|
||||
data-test="TreeNodeContainer:root/child1Label"
|
||||
itemType="branch"
|
||||
onOpenChange={[Function]}
|
||||
value="root/child1Label"
|
||||
@@ -620,7 +620,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
aria-level={1}
|
||||
className="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
||||
data-fui-tree-item-value="root/child1Label"
|
||||
data-testid="TreeNodeContainer:root/child1Label"
|
||||
data-test="TreeNodeContainer:root/child1Label"
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
@@ -642,7 +642,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
@@ -662,7 +662,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
"layoutRef": {
|
||||
"current": <div
|
||||
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root/child1Label"
|
||||
data-test="TreeNode:root/child1Label"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
@@ -671,7 +671,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
@@ -718,13 +718,13 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
aria-level="1"
|
||||
class="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
||||
data-fui-tree-item-value="root/child1Label"
|
||||
data-testid="TreeNodeContainer:root/child1Label"
|
||||
data-test="TreeNodeContainer:root/child1Label"
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root/child1Label"
|
||||
data-test="TreeNode:root/child1Label"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
@@ -733,7 +733,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
@@ -775,10 +775,10 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
<TreeItemLayout
|
||||
actions={false}
|
||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root/child1Label"
|
||||
data-test="TreeNode:root/child1Label"
|
||||
expandIcon={
|
||||
<ChevronRight20Regular
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
/>
|
||||
}
|
||||
iconBefore={
|
||||
@@ -791,19 +791,19 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
>
|
||||
<div
|
||||
className="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root/child1Label"
|
||||
data-test="TreeNode:root/child1Label"
|
||||
>
|
||||
<div
|
||||
aria-hidden={true}
|
||||
className="fui-TreeItemLayout__expandIcon rh4pu5o"
|
||||
>
|
||||
<ChevronRight20Regular
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
>
|
||||
<svg
|
||||
aria-hidden={true}
|
||||
className="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
@@ -840,7 +840,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
</TreeItemLayout>
|
||||
<Tree
|
||||
className="___17a32do_0000000 f1acs6jw f11qra4b fepn2xe f1nbblvp f19d5ny4 fzz4f4n"
|
||||
data-testid="Tree:root/child1Label"
|
||||
data-test="Tree:root/child1Label"
|
||||
>
|
||||
<TreeProvider
|
||||
value={
|
||||
@@ -881,7 +881,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
>
|
||||
<TreeItem
|
||||
className=""
|
||||
data-testid="TreeNodeContainer:root/child2LoadingLabel"
|
||||
data-test="TreeNodeContainer:root/child2LoadingLabel"
|
||||
itemType="branch"
|
||||
onOpenChange={[Function]}
|
||||
value="root/child2LoadingLabel"
|
||||
@@ -891,7 +891,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
aria-level={1}
|
||||
className="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
||||
data-fui-tree-item-value="root/child2LoadingLabel"
|
||||
data-testid="TreeNodeContainer:root/child2LoadingLabel"
|
||||
data-test="TreeNodeContainer:root/child2LoadingLabel"
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
@@ -913,7 +913,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
@@ -933,7 +933,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
"layoutRef": {
|
||||
"current": <div
|
||||
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root/child2LoadingLabel"
|
||||
data-test="TreeNode:root/child2LoadingLabel"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
@@ -942,7 +942,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
@@ -989,13 +989,13 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
aria-level="1"
|
||||
class="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
||||
data-fui-tree-item-value="root/child2LoadingLabel"
|
||||
data-testid="TreeNodeContainer:root/child2LoadingLabel"
|
||||
data-test="TreeNodeContainer:root/child2LoadingLabel"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root/child2LoadingLabel"
|
||||
data-test="TreeNode:root/child2LoadingLabel"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
@@ -1004,7 +1004,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
@@ -1046,10 +1046,10 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
<TreeItemLayout
|
||||
actions={false}
|
||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root/child2LoadingLabel"
|
||||
data-test="TreeNode:root/child2LoadingLabel"
|
||||
expandIcon={
|
||||
<ChevronRight20Regular
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
/>
|
||||
}
|
||||
iconBefore={
|
||||
@@ -1062,19 +1062,19 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
>
|
||||
<div
|
||||
className="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root/child2LoadingLabel"
|
||||
data-test="TreeNode:root/child2LoadingLabel"
|
||||
>
|
||||
<div
|
||||
aria-hidden={true}
|
||||
className="fui-TreeItemLayout__expandIcon rh4pu5o"
|
||||
>
|
||||
<ChevronRight20Regular
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
>
|
||||
<svg
|
||||
aria-hidden={true}
|
||||
className="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
@@ -1140,7 +1140,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
>
|
||||
<TreeItem
|
||||
className=""
|
||||
data-testid="TreeNodeContainer:root/child3ExpandingLabel"
|
||||
data-test="TreeNodeContainer:root/child3ExpandingLabel"
|
||||
itemType="leaf"
|
||||
onOpenChange={[Function]}
|
||||
value="root/child3ExpandingLabel"
|
||||
@@ -1149,7 +1149,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
aria-level={1}
|
||||
className="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
||||
data-fui-tree-item-value="root/child3ExpandingLabel"
|
||||
data-testid="TreeNodeContainer:root/child3ExpandingLabel"
|
||||
data-test="TreeNodeContainer:root/child3ExpandingLabel"
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
@@ -1188,7 +1188,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
"layoutRef": {
|
||||
"current": <div
|
||||
class="fui-TreeItemLayout r1bx0xiv ___dxcrnh0_vz3p260 fk6fouc fkhj508 figsok6 f1i3iumi f1k1erfc fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root/child3ExpandingLabel"
|
||||
data-test="TreeNode:root/child3ExpandingLabel"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
@@ -1240,13 +1240,13 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
aria-level="1"
|
||||
class="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
||||
data-fui-tree-item-value="root/child3ExpandingLabel"
|
||||
data-testid="TreeNodeContainer:root/child3ExpandingLabel"
|
||||
data-test="TreeNodeContainer:root/child3ExpandingLabel"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="fui-TreeItemLayout r1bx0xiv ___dxcrnh0_vz3p260 fk6fouc fkhj508 figsok6 f1i3iumi f1k1erfc fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root/child3ExpandingLabel"
|
||||
data-test="TreeNode:root/child3ExpandingLabel"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
@@ -1294,7 +1294,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
<TreeItemLayout
|
||||
actions={false}
|
||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root/child3ExpandingLabel"
|
||||
data-test="TreeNode:root/child3ExpandingLabel"
|
||||
iconBefore={
|
||||
<img
|
||||
alt=""
|
||||
@@ -1305,7 +1305,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
>
|
||||
<div
|
||||
className="fui-TreeItemLayout r1bx0xiv ___dxcrnh0_vz3p260 fk6fouc fkhj508 figsok6 f1i3iumi f1k1erfc fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root/child3ExpandingLabel"
|
||||
data-test="TreeNode:root/child3ExpandingLabel"
|
||||
>
|
||||
<div
|
||||
aria-hidden={true}
|
||||
@@ -1345,7 +1345,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
||||
exports[`TreeNodeComponent renders a loading spinner if the node is loading: loaded 1`] = `
|
||||
<TreeItem
|
||||
className=""
|
||||
data-testid="TreeNodeContainer:root"
|
||||
data-test="TreeNodeContainer:root"
|
||||
itemType="leaf"
|
||||
onOpenChange={[Function]}
|
||||
value="root"
|
||||
@@ -1353,7 +1353,7 @@ exports[`TreeNodeComponent renders a loading spinner if the node is loading: loa
|
||||
<TreeItemLayout
|
||||
actions={false}
|
||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root"
|
||||
data-test="TreeNode:root"
|
||||
iconBefore={
|
||||
<img
|
||||
alt=""
|
||||
@@ -1374,7 +1374,7 @@ exports[`TreeNodeComponent renders a loading spinner if the node is loading: loa
|
||||
exports[`TreeNodeComponent renders a loading spinner if the node is loading: loading 1`] = `
|
||||
<TreeItem
|
||||
className=""
|
||||
data-testid="TreeNodeContainer:root"
|
||||
data-test="TreeNodeContainer:root"
|
||||
itemType="leaf"
|
||||
onOpenChange={[Function]}
|
||||
value="root"
|
||||
@@ -1382,7 +1382,7 @@ exports[`TreeNodeComponent renders a loading spinner if the node is loading: loa
|
||||
<TreeItemLayout
|
||||
actions={false}
|
||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root"
|
||||
data-test="TreeNode:root"
|
||||
expandIcon={
|
||||
<Spinner
|
||||
size="extra-tiny"
|
||||
@@ -1408,7 +1408,7 @@ exports[`TreeNodeComponent renders a loading spinner if the node is loading: loa
|
||||
exports[`TreeNodeComponent renders a node as expandable if it has empty, but defined, children array 1`] = `
|
||||
<TreeItem
|
||||
className=""
|
||||
data-testid="TreeNodeContainer:root"
|
||||
data-test="TreeNodeContainer:root"
|
||||
itemType="branch"
|
||||
onOpenChange={[Function]}
|
||||
value="root"
|
||||
@@ -1416,10 +1416,10 @@ exports[`TreeNodeComponent renders a node as expandable if it has empty, but def
|
||||
<TreeItemLayout
|
||||
actions={false}
|
||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root"
|
||||
data-test="TreeNode:root"
|
||||
expandIcon={
|
||||
<ChevronRight20Regular
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
/>
|
||||
}
|
||||
iconBefore={
|
||||
@@ -1450,7 +1450,7 @@ exports[`TreeNodeComponent renders a node with a menu 1`] = `
|
||||
>
|
||||
<TreeItem
|
||||
className=""
|
||||
data-testid="TreeNodeContainer:root"
|
||||
data-test="TreeNodeContainer:root"
|
||||
itemType="leaf"
|
||||
onOpenChange={[Function]}
|
||||
value="root"
|
||||
@@ -1468,22 +1468,22 @@ exports[`TreeNodeComponent renders a node with a menu 1`] = `
|
||||
appearance="subtle"
|
||||
aria-label="More options"
|
||||
className="___1pg0eu5_pgl3ex0 f1twygmj"
|
||||
data-testid="TreeNode/ContextMenuTrigger"
|
||||
data-test="TreeNode/ContextMenuTrigger"
|
||||
icon={<MoreHorizontal20Regular />}
|
||||
/>
|
||||
</MenuTrigger>
|
||||
<MenuPopover
|
||||
data-testid="TreeNode/ContextMenu:root"
|
||||
data-test="TreeNode/ContextMenu:root"
|
||||
>
|
||||
<MenuList>
|
||||
<MenuItem
|
||||
data-testid="TreeNode/ContextMenuItem:enabledItem"
|
||||
data-test="TreeNode/ContextMenuItem:enabledItem"
|
||||
onClick={[Function]}
|
||||
>
|
||||
enabledItem
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
data-testid="TreeNode/ContextMenuItem:disabledItem"
|
||||
data-test="TreeNode/ContextMenuItem:disabledItem"
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
>
|
||||
@@ -1496,7 +1496,7 @@ exports[`TreeNodeComponent renders a node with a menu 1`] = `
|
||||
}
|
||||
}
|
||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root"
|
||||
data-test="TreeNode:root"
|
||||
iconBefore={
|
||||
<img
|
||||
alt=""
|
||||
@@ -1516,14 +1516,14 @@ exports[`TreeNodeComponent renders a node with a menu 1`] = `
|
||||
<MenuPopover>
|
||||
<MenuList>
|
||||
<MenuItem
|
||||
data-testid="TreeNode/ContextMenuItem:enabledItem"
|
||||
data-test="TreeNode/ContextMenuItem:enabledItem"
|
||||
key="enabledItem"
|
||||
onClick={[Function]}
|
||||
>
|
||||
enabledItem
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
data-testid="TreeNode/ContextMenuItem:disabledItem"
|
||||
data-test="TreeNode/ContextMenuItem:disabledItem"
|
||||
disabled={true}
|
||||
key="disabledItem"
|
||||
onClick={[Function]}
|
||||
@@ -1538,7 +1538,7 @@ exports[`TreeNodeComponent renders a node with a menu 1`] = `
|
||||
exports[`TreeNodeComponent renders a single node 1`] = `
|
||||
<TreeItem
|
||||
className=""
|
||||
data-testid="TreeNodeContainer:root"
|
||||
data-test="TreeNodeContainer:root"
|
||||
itemType="leaf"
|
||||
onOpenChange={[Function]}
|
||||
value="root"
|
||||
@@ -1546,7 +1546,7 @@ exports[`TreeNodeComponent renders a single node 1`] = `
|
||||
<TreeItemLayout
|
||||
actions={false}
|
||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root"
|
||||
data-test="TreeNode:root"
|
||||
iconBefore={
|
||||
<img
|
||||
alt=""
|
||||
@@ -1567,7 +1567,7 @@ exports[`TreeNodeComponent renders a single node 1`] = `
|
||||
exports[`TreeNodeComponent renders an icon if the node has one 1`] = `
|
||||
<TreeItem
|
||||
className=""
|
||||
data-testid="TreeNodeContainer:root"
|
||||
data-test="TreeNodeContainer:root"
|
||||
itemType="leaf"
|
||||
onOpenChange={[Function]}
|
||||
value="root"
|
||||
@@ -1575,7 +1575,7 @@ exports[`TreeNodeComponent renders an icon if the node has one 1`] = `
|
||||
<TreeItemLayout
|
||||
actions={false}
|
||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root"
|
||||
data-test="TreeNode:root"
|
||||
iconBefore={
|
||||
<img
|
||||
alt=""
|
||||
@@ -1596,7 +1596,7 @@ exports[`TreeNodeComponent renders an icon if the node has one 1`] = `
|
||||
exports[`TreeNodeComponent renders selected parent node as selected if no descendant nodes are selected 1`] = `
|
||||
<TreeItem
|
||||
className=""
|
||||
data-testid="TreeNodeContainer:root"
|
||||
data-test="TreeNodeContainer:root"
|
||||
itemType="branch"
|
||||
onOpenChange={[Function]}
|
||||
value="root"
|
||||
@@ -1604,10 +1604,10 @@ exports[`TreeNodeComponent renders selected parent node as selected if no descen
|
||||
<TreeItemLayout
|
||||
actions={false}
|
||||
className="___rq9vxg0_1ykn2d2 fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1nfm20t f1do9gdl"
|
||||
data-testid="TreeNode:root"
|
||||
data-test="TreeNode:root"
|
||||
expandIcon={
|
||||
<ChevronRight20Regular
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
/>
|
||||
}
|
||||
iconBefore={
|
||||
@@ -1626,7 +1626,7 @@ exports[`TreeNodeComponent renders selected parent node as selected if no descen
|
||||
</TreeItemLayout>
|
||||
<Tree
|
||||
className="___17a32do_0000000 f1acs6jw f11qra4b fepn2xe f1nbblvp f19d5ny4 fzz4f4n"
|
||||
data-testid="Tree:root"
|
||||
data-test="Tree:root"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
key="child1Label"
|
||||
@@ -1679,7 +1679,7 @@ exports[`TreeNodeComponent renders selected parent node as selected if no descen
|
||||
exports[`TreeNodeComponent renders selected parent node as unselected if any descendant node is selected 1`] = `
|
||||
<TreeItem
|
||||
className=""
|
||||
data-testid="TreeNodeContainer:root"
|
||||
data-test="TreeNodeContainer:root"
|
||||
itemType="branch"
|
||||
onOpenChange={[Function]}
|
||||
value="root"
|
||||
@@ -1687,10 +1687,10 @@ exports[`TreeNodeComponent renders selected parent node as unselected if any des
|
||||
<TreeItemLayout
|
||||
actions={false}
|
||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||
data-testid="TreeNode:root"
|
||||
data-test="TreeNode:root"
|
||||
expandIcon={
|
||||
<ChevronRight20Regular
|
||||
data-testid="TreeNode/ExpandIcon"
|
||||
data-text="TreeNode/ExpandIcon"
|
||||
/>
|
||||
}
|
||||
iconBefore={
|
||||
@@ -1709,7 +1709,7 @@ exports[`TreeNodeComponent renders selected parent node as unselected if any des
|
||||
</TreeItemLayout>
|
||||
<Tree
|
||||
className="___17a32do_0000000 f1acs6jw f11qra4b fepn2xe f1nbblvp f19d5ny4 fzz4f4n"
|
||||
data-testid="Tree:root"
|
||||
data-test="Tree:root"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
key="child1Label"
|
||||
@@ -1763,7 +1763,7 @@ exports[`TreeNodeComponent renders selected parent node as unselected if any des
|
||||
exports[`TreeNodeComponent renders single selected leaf node as selected 1`] = `
|
||||
<TreeItem
|
||||
className=""
|
||||
data-testid="TreeNodeContainer:root"
|
||||
data-test="TreeNodeContainer:root"
|
||||
itemType="leaf"
|
||||
onOpenChange={[Function]}
|
||||
value="root"
|
||||
@@ -1771,7 +1771,7 @@ exports[`TreeNodeComponent renders single selected leaf node as selected 1`] = `
|
||||
<TreeItemLayout
|
||||
actions={false}
|
||||
className="___rq9vxg0_1ykn2d2 fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1nfm20t f1do9gdl"
|
||||
data-testid="TreeNode:root"
|
||||
data-test="TreeNode:root"
|
||||
iconBefore={
|
||||
<img
|
||||
alt=""
|
||||
|
||||
@@ -76,7 +76,7 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
|
||||
name: label,
|
||||
disabled: btn.disabled,
|
||||
ariaLabel: btn.ariaLabel,
|
||||
"data-testid": `CommandBar/Button:${label}`,
|
||||
"data-test": `CommandBar/Button:${label}`,
|
||||
buttonStyles: {
|
||||
root: {
|
||||
backgroundColor: backgroundColor,
|
||||
|
||||
@@ -127,13 +127,13 @@ export class NotificationConsoleComponent extends React.Component<
|
||||
</span>
|
||||
</span>
|
||||
<span className="consoleSplitter" />
|
||||
<span className="headerStatus">
|
||||
<span className="headerStatus" data-test="notification-console/header-status">
|
||||
<span className="headerStatusEllipsis" aria-live="assertive" aria-atomic="true">
|
||||
{this.state.headerStatus}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div className="expandCollapseButton" data-testid="NotificationConsole/ExpandCollapseButton">
|
||||
<div className="expandCollapseButton" data-test="NotificationConsole/ExpandCollapseButton">
|
||||
<img
|
||||
src={this.props.isConsoleExpanded ? ChevronDownIcon : ChevronUpIcon}
|
||||
alt={this.props.isConsoleExpanded ? "Collapse icon" : "Expand icon"}
|
||||
@@ -145,7 +145,7 @@ export class NotificationConsoleComponent extends React.Component<
|
||||
height={this.props.isConsoleExpanded ? "auto" : 0}
|
||||
onAnimationEnd={this.onConsoleWasExpanded}
|
||||
>
|
||||
<div data-testid="NotificationConsole/Contents" className="notificationConsoleContents">
|
||||
<div data-test="NotificationConsole/Contents" className="notificationConsoleContents">
|
||||
<div className="notificationConsoleControls">
|
||||
<Dropdown
|
||||
label="Filter:"
|
||||
|
||||
@@ -78,6 +78,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
||||
/>
|
||||
<span
|
||||
className="headerStatus"
|
||||
data-test="notification-console/header-status"
|
||||
>
|
||||
<span
|
||||
aria-atomic="true"
|
||||
@@ -88,7 +89,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
||||
</div>
|
||||
<div
|
||||
className="expandCollapseButton"
|
||||
data-testid="NotificationConsole/ExpandCollapseButton"
|
||||
data-test="NotificationConsole/ExpandCollapseButton"
|
||||
>
|
||||
<img
|
||||
alt="Expand icon"
|
||||
@@ -122,7 +123,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
||||
>
|
||||
<div
|
||||
className="notificationConsoleContents"
|
||||
data-testid="NotificationConsole/Contents"
|
||||
data-test="NotificationConsole/Contents"
|
||||
>
|
||||
<div
|
||||
className="notificationConsoleControls"
|
||||
@@ -261,6 +262,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
||||
/>
|
||||
<span
|
||||
className="headerStatus"
|
||||
data-test="notification-console/header-status"
|
||||
>
|
||||
<span
|
||||
aria-atomic="true"
|
||||
@@ -273,7 +275,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
||||
</div>
|
||||
<div
|
||||
className="expandCollapseButton"
|
||||
data-testid="NotificationConsole/ExpandCollapseButton"
|
||||
data-test="NotificationConsole/ExpandCollapseButton"
|
||||
>
|
||||
<img
|
||||
alt="Expand icon"
|
||||
@@ -307,7 +309,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
||||
>
|
||||
<div
|
||||
className="notificationConsoleContents"
|
||||
data-testid="NotificationConsole/Contents"
|
||||
data-test="NotificationConsole/Contents"
|
||||
>
|
||||
<div
|
||||
className="notificationConsoleControls"
|
||||
|
||||
@@ -56,16 +56,16 @@ export class StatusBar extends React.Component<Props> {
|
||||
|
||||
return (
|
||||
<BarContainer>
|
||||
<Bar data-testid="notebookStatusBar">
|
||||
<Bar data-test="notebookStatusBar">
|
||||
<RightStatus>
|
||||
{this.props.lastSaved ? (
|
||||
<p data-testid="saveStatus"> Last saved {distanceInWordsToNow(this.props.lastSaved)} </p>
|
||||
<p data-test="saveStatus"> Last saved {distanceInWordsToNow(this.props.lastSaved)} </p>
|
||||
) : (
|
||||
<p> Not saved yet </p>
|
||||
)}
|
||||
</RightStatus>
|
||||
<LeftStatus>
|
||||
<p data-testid="kernelStatus">
|
||||
<p data-test="kernelStatus">
|
||||
{name} | {this.props.kernelStatus}
|
||||
</p>
|
||||
</LeftStatus>
|
||||
|
||||
@@ -301,7 +301,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
type="radio"
|
||||
role="radio"
|
||||
id="databaseCreateNew"
|
||||
data-testid="AddCollectionPanel/DatabaseRadio:CreateNew"
|
||||
tabIndex={0}
|
||||
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
|
||||
/>
|
||||
@@ -315,7 +314,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
name="databaseType"
|
||||
type="radio"
|
||||
role="radio"
|
||||
data-testid="AddCollectionPanel/DatabaseRadio:UseExisting"
|
||||
tabIndex={0}
|
||||
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
|
||||
/>
|
||||
@@ -339,7 +337,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
size={40}
|
||||
className="panelTextField"
|
||||
aria-label="New database id, Type a new database id"
|
||||
data-testid="AddCollectionPanel/DatabaseId"
|
||||
tabIndex={0}
|
||||
value={this.state.newDatabaseId}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
@@ -349,20 +346,18 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
|
||||
{!isServerlessAccount() && (
|
||||
<Stack horizontal>
|
||||
<div data-testid="AddCollectionPanel/SharedThroughputCheckbox">
|
||||
<Checkbox
|
||||
label={`Share throughput across ${getCollectionName(true).toLocaleLowerCase()}`}
|
||||
checked={this.state.isSharedThroughputChecked}
|
||||
styles={{
|
||||
text: { fontSize: 12 },
|
||||
checkbox: { width: 12, height: 12 },
|
||||
label: { padding: 0, alignItems: "center" },
|
||||
}}
|
||||
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
|
||||
this.setState({ isSharedThroughputChecked: isChecked })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Checkbox
|
||||
label={`Share throughput across ${getCollectionName(true).toLocaleLowerCase()}`}
|
||||
checked={this.state.isSharedThroughputChecked}
|
||||
styles={{
|
||||
text: { fontSize: 12 },
|
||||
checkbox: { width: 12, height: 12 },
|
||||
label: { padding: 0, alignItems: "center" },
|
||||
}}
|
||||
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
|
||||
this.setState({ isSharedThroughputChecked: isChecked })
|
||||
}
|
||||
/>
|
||||
<TooltipHost
|
||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||
content={`Throughput configured at the database level will be shared across all ${getCollectionName(
|
||||
@@ -401,7 +396,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
{!this.state.createNewDatabase && (
|
||||
<Dropdown
|
||||
ariaLabel="Choose an existing database"
|
||||
data-testid="AddCollectionPanel/ExistingDatabaseDropdown"
|
||||
styles={{ title: { height: 27, lineHeight: 27 }, dropdownItem: { fontSize: 12 } }}
|
||||
style={{ width: 300, fontSize: 12 }}
|
||||
placeholder="Choose an existing database"
|
||||
@@ -449,7 +443,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
placeholder={`e.g., ${getCollectionName()}1`}
|
||||
size={40}
|
||||
className="panelTextField"
|
||||
data-testid="AddCollectionPanel/CollectionId"
|
||||
aria-label={`${getCollectionName()} id, Example ${getCollectionName()}1`}
|
||||
value={this.state.collectionId}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
@@ -583,7 +576,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
<input
|
||||
type="text"
|
||||
id="addCollection-partitionKeyValue"
|
||||
data-testid="AddCollectionPanel/PartitionKey"
|
||||
aria-required
|
||||
required
|
||||
size={40}
|
||||
@@ -620,7 +612,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
<input
|
||||
type="text"
|
||||
id="addCollection-partitionKeyValue"
|
||||
data-testid="AddCollectionPanel/PartitionKey"
|
||||
key={`addCollection-partitionKeyValue_${index}`}
|
||||
aria-required
|
||||
required
|
||||
@@ -738,7 +729,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
)}
|
||||
|
||||
{!isFabricNative() && userContext.apiType === "SQL" && (
|
||||
<Stack style={{ marginTop: -2, marginBottom: -4 }} data-testid="AddCollectionPanel/UniqueKeysSection">
|
||||
<Stack style={{ marginTop: -2, marginBottom: -4 }}>
|
||||
{UniqueKeysHeader()}
|
||||
{this.state.uniqueKeys.map((uniqueKey: string, i: number): JSX.Element => {
|
||||
return (
|
||||
@@ -752,7 +743,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
: "Comma separated paths e.g. /firstName,/address/zipCode"
|
||||
}
|
||||
className="panelTextField"
|
||||
data-testid="AddCollectionPanel/UniqueKey"
|
||||
value={uniqueKey}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const uniqueKeys = this.state.uniqueKeys.map((uniqueKey: string, j: number) => {
|
||||
@@ -779,7 +769,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
|
||||
<ActionButton
|
||||
iconProps={{ iconName: "Add" }}
|
||||
data-testid="AddCollectionPanel/AddUniqueKeyButton"
|
||||
styles={{ root: { padding: 0 }, label: { fontSize: 12 } }}
|
||||
onClick={() => this.setState({ uniqueKeys: [...this.state.uniqueKeys, ""] })}
|
||||
>
|
||||
|
||||
@@ -56,7 +56,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
aria-label="Create new database"
|
||||
checked={true}
|
||||
className="panelRadioBtn"
|
||||
data-testid="AddCollectionPanel/DatabaseRadio:CreateNew"
|
||||
id="databaseCreateNew"
|
||||
name="databaseType"
|
||||
onChange={[Function]}
|
||||
@@ -74,7 +73,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
aria-label="Use existing database"
|
||||
checked={false}
|
||||
className="panelRadioBtn"
|
||||
data-testid="AddCollectionPanel/DatabaseRadio:UseExisting"
|
||||
name="databaseType"
|
||||
onChange={[Function]}
|
||||
role="radio"
|
||||
@@ -96,7 +94,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
aria-required={true}
|
||||
autoComplete="off"
|
||||
className="panelTextField"
|
||||
data-testid="AddCollectionPanel/DatabaseId"
|
||||
id="newDatabaseId"
|
||||
name="newDatabaseId"
|
||||
onChange={[Function]}
|
||||
@@ -112,30 +109,26 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<div
|
||||
data-testid="AddCollectionPanel/SharedThroughputCheckbox"
|
||||
>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
label="Share throughput across containers"
|
||||
onChange={[Function]}
|
||||
styles={
|
||||
{
|
||||
"checkbox": {
|
||||
"height": 12,
|
||||
"width": 12,
|
||||
},
|
||||
"label": {
|
||||
"alignItems": "center",
|
||||
"padding": 0,
|
||||
},
|
||||
"text": {
|
||||
"fontSize": 12,
|
||||
},
|
||||
}
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
label="Share throughput across containers"
|
||||
onChange={[Function]}
|
||||
styles={
|
||||
{
|
||||
"checkbox": {
|
||||
"height": 12,
|
||||
"width": 12,
|
||||
},
|
||||
"label": {
|
||||
"alignItems": "center",
|
||||
"padding": 0,
|
||||
},
|
||||
"text": {
|
||||
"fontSize": 12,
|
||||
},
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<StyledTooltipHostBase
|
||||
content="Throughput configured at the database level will be shared across all containers within the database."
|
||||
directionalHint={4}
|
||||
@@ -198,7 +191,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
aria-required={true}
|
||||
autoComplete="off"
|
||||
className="panelTextField"
|
||||
data-testid="AddCollectionPanel/CollectionId"
|
||||
id="collectionId"
|
||||
name="collectionId"
|
||||
onChange={[Function]}
|
||||
@@ -260,7 +252,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
aria-label="Partition key"
|
||||
aria-required={true}
|
||||
className="panelTextField"
|
||||
data-testid="AddCollectionPanel/PartitionKey"
|
||||
id="addCollection-partitionKeyValue"
|
||||
onChange={[Function]}
|
||||
pattern=".*"
|
||||
@@ -313,7 +304,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
setThroughputValue={[Function]}
|
||||
/>
|
||||
<Stack
|
||||
data-testid="AddCollectionPanel/UniqueKeysSection"
|
||||
style={
|
||||
{
|
||||
"marginBottom": -4,
|
||||
@@ -348,7 +338,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
</StyledTooltipHostBase>
|
||||
</Stack>
|
||||
<CustomizedActionButton
|
||||
data-testid="AddCollectionPanel/AddUniqueKeyButton"
|
||||
iconProps={
|
||||
{
|
||||
"iconName": "Add",
|
||||
|
||||
@@ -199,7 +199,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
{keyspaceCreateNew && (
|
||||
<Stack className="panelGroupSpacing">
|
||||
<TextField
|
||||
data-testid="AddCollectionPanel/DatabaseId"
|
||||
aria-required="true"
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
@@ -216,20 +215,16 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
|
||||
{!isServerlessAccount() && (
|
||||
<Stack horizontal>
|
||||
<div data-testid="AddCollectionPanel/SharedThroughputCheckbox">
|
||||
<Checkbox
|
||||
label="Provision shared throughput"
|
||||
checked={isKeyspaceShared}
|
||||
styles={{
|
||||
text: { fontSize: 12 },
|
||||
checkbox: { width: 12, height: 12 },
|
||||
label: { padding: 0, alignItems: "center" },
|
||||
}}
|
||||
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
|
||||
setIsKeyspaceShared(isChecked)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Checkbox
|
||||
label="Provision shared throughput"
|
||||
checked={isKeyspaceShared}
|
||||
styles={{
|
||||
text: { fontSize: 12 },
|
||||
checkbox: { width: 12, height: 12 },
|
||||
label: { padding: 0, alignItems: "center" },
|
||||
}}
|
||||
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) => setIsKeyspaceShared(isChecked)}
|
||||
/>
|
||||
<InfoTooltip>
|
||||
Provisioned throughput at the keyspace level will be shared across unlimited number of tables within
|
||||
the keyspace
|
||||
@@ -292,7 +287,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||
{`CREATE TABLE ${keyspaceCreateNew ? newKeyspaceId : existingKeyspaceId}.`}
|
||||
</Text>
|
||||
<TextField
|
||||
data-testid="AddCollectionPanel/CollectionId"
|
||||
underlined
|
||||
styles={getTextFieldStyles({ fontSize: 12, width: 150 })}
|
||||
aria-required="true"
|
||||
|
||||
@@ -120,7 +120,6 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
||||
<Text variant="small">Confirm by typing the {collectionName.toLowerCase()} id</Text>
|
||||
<TextField
|
||||
id="confirmCollectionId"
|
||||
data-testid="DeleteCollectionConfirmationPane/ConfirmInput"
|
||||
autoFocus
|
||||
value={inputCollectionName}
|
||||
styles={{ fieldGroup: { width: 300 } }}
|
||||
|
||||
@@ -42,7 +42,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||
<StyledTextFieldBase
|
||||
ariaLabel="Confirm by typing the container id"
|
||||
autoFocus={true}
|
||||
data-testid="DeleteCollectionConfirmationPane/ConfirmInput"
|
||||
id="confirmCollectionId"
|
||||
onChange={[Function]}
|
||||
required={true}
|
||||
@@ -58,7 +57,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||
<TextFieldBase
|
||||
ariaLabel="Confirm by typing the container id"
|
||||
autoFocus={true}
|
||||
data-testid="DeleteCollectionConfirmationPane/ConfirmInput"
|
||||
deferredValidationTime={200}
|
||||
id="confirmCollectionId"
|
||||
onChange={[Function]}
|
||||
@@ -355,7 +353,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||
aria-label="Confirm by typing the container id"
|
||||
autoFocus={true}
|
||||
className="ms-TextField-field field-113"
|
||||
data-testid="DeleteCollectionConfirmationPane/ConfirmInput"
|
||||
id="confirmCollectionId"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
@@ -382,7 +379,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="OK"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="OK"
|
||||
@@ -393,7 +390,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="OK"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
styles={
|
||||
@@ -684,7 +681,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="OK"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -980,7 +977,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="OK"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -1274,7 +1271,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||
<BaseButton
|
||||
ariaLabel="OK"
|
||||
baseClassName="ms-Button"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -2160,7 +2157,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||
aria-label="OK"
|
||||
className="ms-Button ms-Button--primary root-122"
|
||||
data-is-focusable={true}
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
id="sidePanelOkButton"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
|
||||
@@ -135,7 +135,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
||||
<Text variant="small">{confirmDatabase}</Text>
|
||||
<TextField
|
||||
id="confirmDatabaseId"
|
||||
data-testid="DeleteDatabaseConfirmationPanel/ConfirmInput"
|
||||
data-test="Input:confirmDatabaseId"
|
||||
autoFocus
|
||||
styles={{ fieldGroup: { width: 300 } }}
|
||||
onChange={(event, newInput?: string) => {
|
||||
|
||||
@@ -5312,7 +5312,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="Execute"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Execute"
|
||||
@@ -5323,7 +5323,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="Execute"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
styles={
|
||||
@@ -5614,7 +5614,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="Execute"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -5910,7 +5910,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="Execute"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -6204,7 +6204,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
||||
<BaseButton
|
||||
ariaLabel="Execute"
|
||||
baseClassName="ms-Button"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -7090,7 +7090,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
||||
aria-label="Execute"
|
||||
className="ms-Button ms-Button--primary root-148"
|
||||
data-is-focusable={true}
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
id="sidePanelOkButton"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
|
||||
@@ -54,7 +54,7 @@ export class PanelContainerComponent extends React.Component<PanelContainerProps
|
||||
|
||||
return (
|
||||
<Panel
|
||||
data-testid={`Panel:${this.props.headerText}`}
|
||||
data-test={`Panel:${this.props.headerText}`}
|
||||
headerText={this.props.headerText}
|
||||
isOpen={this.props.isOpen}
|
||||
onDismiss={this.onDissmiss}
|
||||
|
||||
@@ -16,7 +16,7 @@ export const PanelFooterComponent: React.FunctionComponent<PanelFooterProps> = (
|
||||
<PrimaryButton
|
||||
type="submit"
|
||||
id="sidePanelOkButton"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
text={buttonLabel}
|
||||
ariaLabel={buttonLabel}
|
||||
disabled={!!isButtonDisabled}
|
||||
|
||||
@@ -21,7 +21,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="Load"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Load"
|
||||
@@ -32,7 +32,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="Load"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
styles={
|
||||
@@ -323,7 +323,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="Load"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -619,7 +619,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="Load"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -913,7 +913,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
||||
<BaseButton
|
||||
ariaLabel="Load"
|
||||
baseClassName="ms-Button"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -1799,7 +1799,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
||||
aria-label="Load"
|
||||
className="ms-Button ms-Button--primary root-109"
|
||||
data-is-focusable={true}
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
id="sidePanelOkButton"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
|
||||
@@ -688,7 +688,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="Create"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Create"
|
||||
@@ -699,7 +699,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="Create"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
styles={
|
||||
@@ -990,7 +990,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="Create"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -1286,7 +1286,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="Create"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -1580,7 +1580,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
<BaseButton
|
||||
ariaLabel="Create"
|
||||
baseClassName="ms-Button"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -2466,7 +2466,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||
aria-label="Create"
|
||||
className="ms-Button ms-Button--primary root-128"
|
||||
data-is-focusable={true}
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
id="sidePanelOkButton"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
|
||||
@@ -1258,7 +1258,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="OK"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="OK"
|
||||
@@ -1269,7 +1269,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="OK"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
styles={
|
||||
@@ -1560,7 +1560,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="OK"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -1856,7 +1856,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="OK"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -2150,7 +2150,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
||||
<BaseButton
|
||||
ariaLabel="OK"
|
||||
baseClassName="ms-Button"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -3036,7 +3036,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
||||
aria-label="OK"
|
||||
className="ms-Button ms-Button--primary root-125"
|
||||
data-is-focusable={true}
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
id="sidePanelOkButton"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
|
||||
@@ -369,7 +369,7 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="Add Entity"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Add Entity"
|
||||
@@ -380,7 +380,7 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="Add Entity"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
styles={
|
||||
@@ -671,7 +671,7 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="Add Entity"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -967,7 +967,7 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="Add Entity"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -1261,7 +1261,7 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
||||
<BaseButton
|
||||
ariaLabel="Add Entity"
|
||||
baseClassName="ms-Button"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -2147,7 +2147,7 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
||||
aria-label="Add Entity"
|
||||
className="ms-Button ms-Button--primary root-113"
|
||||
data-is-focusable={true}
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
id="sidePanelOkButton"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
|
||||
@@ -375,7 +375,7 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="Update"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="Update"
|
||||
@@ -386,7 +386,7 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="Update"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
styles={
|
||||
@@ -677,7 +677,7 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="Update"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -973,7 +973,7 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="Update"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -1267,7 +1267,7 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
||||
<BaseButton
|
||||
ariaLabel="Update"
|
||||
baseClassName="ms-Button"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -2153,7 +2153,7 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
||||
aria-label="Update"
|
||||
className="ms-Button ms-Button--primary root-113"
|
||||
data-is-focusable={true}
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
id="sidePanelOkButton"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
|
||||
@@ -367,7 +367,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
<StyledTextFieldBase
|
||||
ariaLabel="Confirm by typing the Database id (name)"
|
||||
autoFocus={true}
|
||||
data-testid="DeleteDatabaseConfirmationPanel/ConfirmInput"
|
||||
data-test="Input:confirmDatabaseId"
|
||||
id="confirmDatabaseId"
|
||||
onChange={[Function]}
|
||||
required={true}
|
||||
@@ -382,7 +382,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
<TextFieldBase
|
||||
ariaLabel="Confirm by typing the Database id (name)"
|
||||
autoFocus={true}
|
||||
data-testid="DeleteDatabaseConfirmationPanel/ConfirmInput"
|
||||
data-test="Input:confirmDatabaseId"
|
||||
deferredValidationTime={200}
|
||||
id="confirmDatabaseId"
|
||||
onChange={[Function]}
|
||||
@@ -678,7 +678,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
aria-label="Confirm by typing the Database id (name)"
|
||||
autoFocus={true}
|
||||
className="ms-TextField-field field-117"
|
||||
data-testid="DeleteDatabaseConfirmationPanel/ConfirmInput"
|
||||
data-test="Input:confirmDatabaseId"
|
||||
id="confirmDatabaseId"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
@@ -1054,7 +1054,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
ariaLabel="OK"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
text="OK"
|
||||
@@ -1065,7 +1065,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
>
|
||||
<PrimaryButton
|
||||
ariaLabel="OK"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
styles={
|
||||
@@ -1356,7 +1356,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
ariaLabel="OK"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -1652,7 +1652,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
>
|
||||
<DefaultButton
|
||||
ariaLabel="OK"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -1946,7 +1946,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
<BaseButton
|
||||
ariaLabel="OK"
|
||||
baseClassName="ms-Button"
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
disabled={false}
|
||||
id="sidePanelOkButton"
|
||||
onRenderDescription={[Function]}
|
||||
@@ -2832,7 +2832,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||
aria-label="OK"
|
||||
className="ms-Button ms-Button--primary root-130"
|
||||
data-is-focusable={true}
|
||||
data-testid="Panel/OkButton"
|
||||
data-test="Panel/OkButton"
|
||||
id="sidePanelOkButton"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
|
||||
@@ -4,7 +4,7 @@ exports[`PaneContainerComponent test should be resize if notification console is
|
||||
<StyledPanelBase
|
||||
closeButtonAriaLabel="Close test"
|
||||
customWidth="440px"
|
||||
data-testid="Panel:test"
|
||||
data-test="Panel:test"
|
||||
headerClassName="panelHeader"
|
||||
headerText="test"
|
||||
isFooterAtBottom={true}
|
||||
@@ -43,7 +43,7 @@ exports[`PaneContainerComponent test should not render console with panel 1`] =
|
||||
<StyledPanelBase
|
||||
closeButtonAriaLabel="Close test"
|
||||
customWidth="440px"
|
||||
data-testid="Panel:test"
|
||||
data-test="Panel:test"
|
||||
headerClassName="panelHeader"
|
||||
headerText="test"
|
||||
isFooterAtBottom={true}
|
||||
@@ -84,7 +84,7 @@ exports[`PaneContainerComponent test should render with panel content and header
|
||||
<StyledPanelBase
|
||||
closeButtonAriaLabel="Close test"
|
||||
customWidth="440px"
|
||||
data-testid="Panel:test"
|
||||
data-test="Panel:test"
|
||||
headerClassName="panelHeader"
|
||||
headerText="test"
|
||||
isFooterAtBottom={true}
|
||||
|
||||
@@ -242,14 +242,9 @@ const GlobalCommands: React.FC<GlobalCommandsProps> = ({ explorer }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.globalCommandsContainer} data-testid="GlobalCommands">
|
||||
<div className={styles.globalCommandsContainer} data-test="GlobalCommands">
|
||||
{actions.length === 1 ? (
|
||||
<Button
|
||||
data-testid={`GlobalCommands/Button:${primaryAction.label}`}
|
||||
icon={primaryAction.icon}
|
||||
onClick={onPrimaryActionClick}
|
||||
ref={primaryFocusableRef}
|
||||
>
|
||||
<Button icon={primaryAction.icon} onClick={onPrimaryActionClick} ref={primaryFocusableRef}>
|
||||
{primaryAction.label}
|
||||
</Button>
|
||||
) : (
|
||||
@@ -258,12 +253,8 @@ const GlobalCommands: React.FC<GlobalCommandsProps> = ({ explorer }) => {
|
||||
{(triggerProps: MenuButtonProps) => (
|
||||
<div ref={setGlobalCommandButton}>
|
||||
<SplitButton
|
||||
data-testid={`GlobalCommands/Button:${primaryAction.label}`}
|
||||
menuButton={{ ...triggerProps, "aria-label": "More commands" }}
|
||||
primaryActionButton={{
|
||||
onClick: onPrimaryActionClick,
|
||||
ref: primaryFocusableRef,
|
||||
}}
|
||||
primaryActionButton={{ onClick: onPrimaryActionClick, ref: primaryFocusableRef }}
|
||||
className={styles.globalCommandsSplitButton}
|
||||
icon={primaryAction.icon}
|
||||
>
|
||||
@@ -385,7 +376,7 @@ export const SidebarContainer: React.FC<SidebarProps> = ({ explorer }) => {
|
||||
{!isFabricNative() && (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="Sidebar/RefreshButton"
|
||||
data-test="Sidebar/RefreshButton"
|
||||
className={styles.floatingControlButton}
|
||||
disabled={loading}
|
||||
title="Refresh"
|
||||
|
||||
@@ -2146,8 +2146,8 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
||||
|
||||
return (
|
||||
<CosmosFluentProvider className={styles.container}>
|
||||
<div data-testid={"DocumentsTab"} className="tab-pane active" role="tabpanel" style={{ display: "flex" }}>
|
||||
<div data-testid={"DocumentsTab/Filter"} className={`${styles.filterRow} ${styles.smallScreenContent}`}>
|
||||
<div data-test={"DocumentsTab"} className="tab-pane active" role="tabpanel" style={{ display: "flex" }}>
|
||||
<div data-test={"DocumentsTab/Filter"} className={`${styles.filterRow} ${styles.smallScreenContent}`}>
|
||||
{!isPreferredApiMongoDB && <span> SELECT * FROM c </span>}
|
||||
<InputDataList
|
||||
dropdownOptions={getFilterChoices()}
|
||||
@@ -2164,7 +2164,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
||||
/>
|
||||
<Button
|
||||
appearance="primary"
|
||||
data-testid={"DocumentsTab/ApplyFilter"}
|
||||
data-test={"DocumentsTab/ApplyFilter"}
|
||||
size="small"
|
||||
onClick={() => {
|
||||
if (isExecuting) {
|
||||
@@ -2191,7 +2191,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
||||
>
|
||||
<Allotment.Pane preferredSize={`${tabStateData.leftPaneWidthPercent}%`} minSize={55}>
|
||||
<div
|
||||
data-testid={"DocumentsTab/DocumentsPane"}
|
||||
data-test={"DocumentsTab/DocumentsPane"}
|
||||
style={{ height: "100%", width: "100%", overflow: "hidden" }}
|
||||
ref={tableContainerRef}
|
||||
>
|
||||
@@ -2237,7 +2237,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
||||
{tableItems.length > 0 && (
|
||||
<a
|
||||
className={styles.loadMore}
|
||||
data-testid={"DocumentsTab/LoadMore"}
|
||||
data-test={"DocumentsTab/LoadMore"}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => loadNextPage(documentsIterator.iterator, false)}
|
||||
@@ -2249,7 +2249,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
||||
</div>
|
||||
</Allotment.Pane>
|
||||
<Allotment.Pane minSize={30}>
|
||||
<div data-testid={"DocumentsTab/ResultsPane"} style={{ height: "100%", width: "100%" }}>
|
||||
<div data-test={"DocumentsTab/ResultsPane"} style={{ height: "100%", width: "100%" }}>
|
||||
{isTabActive && selectedDocumentContent && selectedRows.size <= 1 && (
|
||||
<EditorReact
|
||||
language={"json"}
|
||||
|
||||
@@ -6,7 +6,7 @@ exports[`Documents tab (noSql API) when rendered should render the page 1`] = `
|
||||
>
|
||||
<div
|
||||
className="tab-pane active"
|
||||
data-testid="DocumentsTab"
|
||||
data-test="DocumentsTab"
|
||||
role="tabpanel"
|
||||
style={
|
||||
{
|
||||
@@ -16,7 +16,7 @@ exports[`Documents tab (noSql API) when rendered should render the page 1`] = `
|
||||
>
|
||||
<div
|
||||
className="___11ktxfv_0000000 f1o614cb fy9rknc f22iagw fsnqrgy f1f5gg8d fjodcmx f122n59 f1f09k3d fg706s2 frpde29 ___1ngl8o6_0000000 fz7mnu6 fl3egqs flhmrkm"
|
||||
data-testid="DocumentsTab/Filter"
|
||||
data-test="DocumentsTab/Filter"
|
||||
>
|
||||
<span>
|
||||
SELECT * FROM c
|
||||
@@ -51,7 +51,7 @@ exports[`Documents tab (noSql API) when rendered should render the page 1`] = `
|
||||
<Button
|
||||
appearance="primary"
|
||||
aria-label="Apply filter"
|
||||
data-testid="DocumentsTab/ApplyFilter"
|
||||
data-test="DocumentsTab/ApplyFilter"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
size="small"
|
||||
@@ -68,7 +68,7 @@ exports[`Documents tab (noSql API) when rendered should render the page 1`] = `
|
||||
preferredSize="35%"
|
||||
>
|
||||
<div
|
||||
data-testid="DocumentsTab/DocumentsPane"
|
||||
data-test="DocumentsTab/DocumentsPane"
|
||||
style={
|
||||
{
|
||||
"height": "100%",
|
||||
@@ -130,7 +130,7 @@ exports[`Documents tab (noSql API) when rendered should render the page 1`] = `
|
||||
minSize={30}
|
||||
>
|
||||
<div
|
||||
data-testid="DocumentsTab/ResultsPane"
|
||||
data-test="DocumentsTab/ResultsPane"
|
||||
style={
|
||||
{
|
||||
"height": "100%",
|
||||
|
||||
@@ -116,7 +116,7 @@ export const ErrorList: React.FC<{ errors: QueryError[] }> = ({ errors }) => {
|
||||
|
||||
return (
|
||||
<DataGrid
|
||||
data-testid="QueryTab/ResultsPane/ErrorList"
|
||||
data-test="QueryTab/ResultsPane/ErrorList"
|
||||
items={errors}
|
||||
columns={columns}
|
||||
sortable
|
||||
@@ -131,9 +131,9 @@ export const ErrorList: React.FC<{ errors: QueryError[] }> = ({ errors }) => {
|
||||
</DataGridHeader>
|
||||
<DataGridBody<QueryError>>
|
||||
{({ item, rowId }) => (
|
||||
<DataGridRow<QueryError> key={rowId} data-testid={`Row:${rowId}`}>
|
||||
<DataGridRow<QueryError> key={rowId} data-test={`Row:${rowId}`}>
|
||||
{({ columnId, renderCell }) => (
|
||||
<DataGridCell data-testid={`Row:${rowId}/Column:${columnId}`}>{renderCell(item)}</DataGridCell>
|
||||
<DataGridCell data-test={`Row:${rowId}/Column:${columnId}`}>{renderCell(item)}</DataGridCell>
|
||||
)}
|
||||
</DataGridRow>
|
||||
)}
|
||||
|
||||
@@ -3,13 +3,13 @@ import QueryError from "Common/QueryError";
|
||||
import { IndeterminateProgressBar } from "Explorer/Controls/IndeterminateProgressBar";
|
||||
import { MessageBanner } from "Explorer/Controls/MessageBanner";
|
||||
import { useQueryTabStyles } from "Explorer/Tabs/QueryTab/Styles";
|
||||
import useZoomLevel from "hooks/useZoomLevel";
|
||||
import React from "react";
|
||||
import { conditionalClass } from "Utils/StyleUtils";
|
||||
import RunQuery from "../../../../images/RunQuery.png";
|
||||
import { QueryResults } from "../../../Contracts/ViewModels";
|
||||
import { ErrorList } from "./ErrorList";
|
||||
import { ResultsView } from "./ResultsView";
|
||||
import useZoomLevel from "hooks/useZoomLevel";
|
||||
import { conditionalClass } from "Utils/StyleUtils";
|
||||
|
||||
export interface ResultsViewProps {
|
||||
isMongoDB: boolean;
|
||||
@@ -27,7 +27,7 @@ const ExecuteQueryCallToAction: React.FC = () => {
|
||||
const styles = useQueryTabStyles();
|
||||
const isZoomed = useZoomLevel();
|
||||
return (
|
||||
<div data-testid="QueryTab/ResultsPane/ExecuteCTA" className={styles.executeCallToAction}>
|
||||
<div data-test="QueryTab/ResultsPane/ExecuteCTA" className={styles.executeCallToAction}>
|
||||
<div>
|
||||
<p>
|
||||
<img
|
||||
@@ -54,7 +54,7 @@ export const QueryResultSection: React.FC<QueryResultProps> = ({
|
||||
const maybeSubQuery = queryEditorContent && /.*\(.*SELECT.*\)/i.test(queryEditorContent);
|
||||
|
||||
return (
|
||||
<div data-testid="QueryTab/ResultsPane" className={styles.queryResultsPanel}>
|
||||
<div data-test="QueryTab/ResultsPane" className={styles.queryResultsPanel}>
|
||||
{isExecuting && <IndeterminateProgressBar />}
|
||||
<MessageBanner
|
||||
messageId="QueryEditor.EmptyMongoQuery"
|
||||
|
||||
@@ -64,7 +64,7 @@ describe("QueryTabComponent", () => {
|
||||
|
||||
const { container } = render(<QueryTabComponent {...propsMock} />);
|
||||
|
||||
const launchCopilotButton = container.querySelector('[data-testid="QueryTab/ResultsPane/ExecuteCTA"]');
|
||||
const launchCopilotButton = container.querySelector('[data-test="QueryTab/ResultsPane/ExecuteCTA"]');
|
||||
fireEvent.keyDown(launchCopilotButton, { key: "c", altKey: true });
|
||||
|
||||
expect(mockStore.setShowCopilotSidebar).toHaveBeenCalledWith(true);
|
||||
|
||||
@@ -746,7 +746,7 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
||||
}}
|
||||
>
|
||||
<Allotment.Pane
|
||||
data-testid="QueryTab/EditorPane"
|
||||
data-test="QueryTab/EditorPane"
|
||||
preferredSize={
|
||||
this.state.queryViewSizePercent !== undefined ? `${this.state.queryViewSizePercent}%` : undefined
|
||||
}
|
||||
@@ -813,7 +813,7 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
||||
render(): JSX.Element {
|
||||
const shouldScaleElements = this.state.showCopilotSidebar && this.isCopilotTabActive;
|
||||
return (
|
||||
<div data-testid="QueryTab" style={{ display: "flex", flexDirection: "row", height: "100%" }}>
|
||||
<div data-test="QueryTab" style={{ display: "flex", flexDirection: "row", height: "100%" }}>
|
||||
<div style={{ width: shouldScaleElements ? "70%" : "100%", height: "100%" }}>
|
||||
{this.getEditorAndQueryResult()}
|
||||
</div>
|
||||
|
||||
@@ -489,7 +489,7 @@ const QueryStatsTab: React.FC<Pick<ResultsViewProps, "queryResults">> = ({ query
|
||||
return (
|
||||
<div className={styles.metricsGridContainer}>
|
||||
<DataGrid
|
||||
data-testid="QueryTab/ResultsPane/ResultsView/QueryStatsList"
|
||||
data-test="QueryTab/ResultsPane/ResultsView/QueryStatsList"
|
||||
className={styles.queryStatsGrid}
|
||||
items={generateQueryStatsItems()}
|
||||
columns={columns}
|
||||
@@ -504,9 +504,9 @@ const QueryStatsTab: React.FC<Pick<ResultsViewProps, "queryResults">> = ({ query
|
||||
</DataGridHeader>
|
||||
<DataGridBody<IDocument>>
|
||||
{({ item, rowId }) => (
|
||||
<DataGridRow<IDocument> key={rowId} data-testid={`Row:${rowId}`}>
|
||||
<DataGridRow<IDocument> key={rowId} data-test={`Row:${rowId}`}>
|
||||
{({ columnId, renderCell }) => (
|
||||
<DataGridCell data-testid={`Row:${rowId}/Column:${columnId}`}>{renderCell(item)}</DataGridCell>
|
||||
<DataGridCell data-test={`Row:${rowId}/Column:${columnId}`}>{renderCell(item)}</DataGridCell>
|
||||
)}
|
||||
</DataGridRow>
|
||||
)}
|
||||
@@ -532,17 +532,17 @@ export const ResultsView: React.FC<ResultsViewProps> = ({ isMongoDB, queryResult
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div data-testid="QueryTab/ResultsPane/ResultsView" className={styles.queryResultsTabPanel}>
|
||||
<div data-test="QueryTab/ResultsPane/ResultsView" className={styles.queryResultsTabPanel}>
|
||||
<TabList selectedValue={activeTab} onTabSelect={onTabSelect}>
|
||||
<Tab
|
||||
data-testid="QueryTab/ResultsPane/ResultsView/ResultsTab"
|
||||
data-test="QueryTab/ResultsPane/ResultsView/ResultsTab"
|
||||
id={ResultsTabs.Results}
|
||||
value={ResultsTabs.Results}
|
||||
>
|
||||
Results
|
||||
</Tab>
|
||||
<Tab
|
||||
data-testid="QueryTab/ResultsPane/ResultsView/QueryStatsTab"
|
||||
data-test="QueryTab/ResultsPane/ResultsView/QueryStatsTab"
|
||||
id={ResultsTabs.QueryStats}
|
||||
value={ResultsTabs.QueryStats}
|
||||
>
|
||||
|
||||
@@ -237,14 +237,14 @@ function TabPane({ tab, active }: { tab: Tab; active: boolean }) {
|
||||
if (tab) {
|
||||
if ("render" in tab) {
|
||||
return (
|
||||
<div data-testid={`Tab:${tab.tabId}`} {...attrs}>
|
||||
<div data-test={`Tab:${tab.tabId}`} {...attrs}>
|
||||
{tab.render()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return <div data-testid={`Tab:${tab.tabId}`} {...attrs} ref={ref} data-bind="html:html" />;
|
||||
return <div data-test={`Tab:${tab.tabId}`} {...attrs} ref={ref} data-bind="html:html" />;
|
||||
}
|
||||
|
||||
const onKeyPressReactTab = (e: KeyboardEvent, tabKind: ReactTabKind): void => {
|
||||
|
||||
@@ -129,7 +129,7 @@ const App: React.FunctionComponent = () => {
|
||||
// Setting key is needed so React will re-render this element on any account change
|
||||
key={databaseAccount?.id || encryptedTokenMetadata?.accountName || authType}
|
||||
ref={ref}
|
||||
data-testid="DataExplorerFrame"
|
||||
data-test="DataExplorerFrame"
|
||||
id="explorerMenu"
|
||||
name="explorer"
|
||||
className="iframe"
|
||||
|
||||
@@ -103,7 +103,7 @@ const App: React.FunctionComponent = () => {
|
||||
|
||||
return (
|
||||
<KeyboardShortcutRoot>
|
||||
<div className="flexContainer" aria-hidden="false" data-testid="DataExplorerRoot">
|
||||
<div className="flexContainer" aria-hidden="false" data-test="DataExplorerRoot">
|
||||
{userContext.features.enableContainerCopy && userContext.apiType === "SQL" ? (
|
||||
<ContainerCopyPanel explorer={explorer} />
|
||||
) : (
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
import { initializeIcons } from "@fluentui/react";
|
||||
import "bootstrap/dist/css/bootstrap.css";
|
||||
import React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { configContext, initializeConfiguration } from "../ConfigContext";
|
||||
import { GalleryHeaderComponent } from "../Explorer/Controls/Header/GalleryHeaderComponent";
|
||||
import { GalleryTab } from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
|
||||
import {
|
||||
NotebookViewerComponent,
|
||||
NotebookViewerComponentProps,
|
||||
} from "../Explorer/Controls/NotebookViewer/NotebookViewerComponent";
|
||||
import * as FileSystemUtil from "../Explorer/Notebook/FileSystemUtil";
|
||||
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
|
||||
import * as GalleryUtils from "../Utils/GalleryUtils";
|
||||
|
||||
const onInit = async () => {
|
||||
initializeIcons();
|
||||
await initializeConfiguration();
|
||||
const galleryViewerProps = GalleryUtils.getGalleryViewerProps(window.location.search);
|
||||
const notebookViewerProps = GalleryUtils.getNotebookViewerProps(window.location.search);
|
||||
let backNavigationText: string;
|
||||
let onBackClick: () => void;
|
||||
if (galleryViewerProps.selectedTab !== undefined) {
|
||||
backNavigationText = GalleryUtils.getTabTitle(galleryViewerProps.selectedTab);
|
||||
onBackClick = () =>
|
||||
(window.location.href = `${configContext.hostedExplorerURL}gallery.html?tab=${
|
||||
GalleryTab[galleryViewerProps.selectedTab]
|
||||
}`);
|
||||
}
|
||||
const hideInputs = notebookViewerProps.hideInputs;
|
||||
|
||||
const notebookUrl = decodeURIComponent(notebookViewerProps.notebookUrl);
|
||||
|
||||
const galleryItemId = notebookViewerProps.galleryItemId;
|
||||
let galleryItem: IGalleryItem;
|
||||
|
||||
if (galleryItemId) {
|
||||
const junoClient = new JunoClient();
|
||||
const galleryItemJunoResponse = await junoClient.getNotebookInfo(galleryItemId);
|
||||
galleryItem = galleryItemJunoResponse.data;
|
||||
}
|
||||
|
||||
// The main purpose of hiding the prompt is to hide everything when hiding inputs.
|
||||
// It is generally not very useful to just hide the prompt.
|
||||
const hidePrompts = hideInputs;
|
||||
|
||||
render(notebookUrl, backNavigationText, hideInputs, hidePrompts, galleryItem, onBackClick);
|
||||
};
|
||||
|
||||
const render = (
|
||||
notebookUrl: string,
|
||||
backNavigationText: string,
|
||||
hideInputs?: boolean,
|
||||
hidePrompts?: boolean,
|
||||
galleryItem?: IGalleryItem,
|
||||
onBackClick?: () => void,
|
||||
) => {
|
||||
const props: NotebookViewerComponentProps = {
|
||||
junoClient: galleryItem ? new JunoClient() : undefined,
|
||||
notebookUrl,
|
||||
galleryItem,
|
||||
backNavigationText,
|
||||
hideInputs,
|
||||
hidePrompts,
|
||||
onBackClick: onBackClick,
|
||||
onTagClick: undefined,
|
||||
};
|
||||
|
||||
if (galleryItem) {
|
||||
document.title = FileSystemUtil.stripExtension(galleryItem.name, "ipynb");
|
||||
}
|
||||
|
||||
const element = (
|
||||
<>
|
||||
<header>
|
||||
<GalleryHeaderComponent />
|
||||
</header>
|
||||
<div style={{ marginLeft: 120, marginRight: 120 }}>
|
||||
<NotebookViewerComponent {...props} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
ReactDOM.render(element, document.getElementById("notebookContent"));
|
||||
};
|
||||
|
||||
// Entry point
|
||||
window.addEventListener("load", onInit);
|
||||
@@ -114,7 +114,7 @@ export const ConnectExplorer: React.FunctionComponent<Props> = ({
|
||||
<div id="connectWithAad">
|
||||
<input className="filterbtnstyle" type="button" value="Sign In" onClick={login} />
|
||||
{enableConnectionStringLogin && (
|
||||
<p className="switchConnectTypeText" data-testid="Link:SwitchConnectionType" onClick={showForm}>
|
||||
<p className="switchConnectTypeText" data-test="Link:SwitchConnectionType" onClick={showForm}>
|
||||
Connect to your account with connection string
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -463,7 +463,7 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div style={{ overflowX: "auto" }} data-testid="DataExplorerRoot">
|
||||
<div style={{ overflowX: "auto" }} data-test="DataExplorerRoot">
|
||||
<Stack tokens={containerStackTokens}>
|
||||
<Stack.Item>
|
||||
<CommandBar styles={commandBarStyles} items={this.getCommandBarItems()} />
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
exports[`SelfServeComponent message bar and spinner snapshots 1`] = `
|
||||
<div
|
||||
data-testid="DataExplorerRoot"
|
||||
data-test="DataExplorerRoot"
|
||||
style={
|
||||
{
|
||||
"overflowX": "auto",
|
||||
@@ -339,7 +339,7 @@ exports[`SelfServeComponent message bar and spinner snapshots 1`] = `
|
||||
|
||||
exports[`SelfServeComponent message bar and spinner snapshots 2`] = `
|
||||
<div
|
||||
data-testid="DataExplorerRoot"
|
||||
data-test="DataExplorerRoot"
|
||||
style={
|
||||
{
|
||||
"overflowX": "auto",
|
||||
@@ -734,7 +734,7 @@ exports[`SelfServeComponent message bar and spinner snapshots 2`] = `
|
||||
|
||||
exports[`SelfServeComponent message bar and spinner snapshots 3`] = `
|
||||
<div
|
||||
data-testid="DataExplorerRoot"
|
||||
data-test="DataExplorerRoot"
|
||||
style={
|
||||
{
|
||||
"overflowX": "auto",
|
||||
@@ -835,7 +835,7 @@ exports[`SelfServeComponent message bar and spinner snapshots 4`] = `
|
||||
|
||||
exports[`SelfServeComponent should render and honor save, discard, refresh actions 1`] = `
|
||||
<div
|
||||
data-testid="DataExplorerRoot"
|
||||
data-test="DataExplorerRoot"
|
||||
style={
|
||||
{
|
||||
"overflowX": "auto",
|
||||
|
||||
@@ -40,13 +40,13 @@ To use this script, there are a few prerequisites that must be done at least onc
|
||||
5. Ensure you have a Resource Group _ready_ to deploy into, the deploy script requires an existing resource group. This resource group should be named `[username]-e2e-testing`, where `[username]` is your Windows username, (**Microsoft employees:** This should be your alias). The easiest way to do this is by running the `create-resource-group.ps1` script, specifying the Subscription (Name or ID) and Location in which you want to create the Resource Group. For example:
|
||||
|
||||
```powershell
|
||||
.\test\resources\create-resource-group.ps1 -SubscriptionName "My Subscription" -Location "West US 3"
|
||||
.\test\resources\create-resource-group.ps1 -SubscriptionId "My Subscription Id" -Location "West US 3"
|
||||
```
|
||||
|
||||
Then, whenever you want to create/update the resources, you can run the `deploy.ps1` script in the `resources` directory. As long as you're using the default naming convention (`[username]-e2e-testing`), you just need to specify the Subscription. For example:
|
||||
|
||||
```powershell
|
||||
.\test\resources\deploy.ps1 -SubscriptionName "My Subscription"
|
||||
.\test\resources\deploy.ps1 -Subscription "My Subscription"
|
||||
```
|
||||
|
||||
You'll get a confirmation prompt before anything is deployed:
|
||||
|
||||
@@ -1,114 +1,50 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
|
||||
import {
|
||||
deleteContainer,
|
||||
deleteKeyspace,
|
||||
openAndFillCreateCassandraTablePanel,
|
||||
} from "../helpers/containerCreationHelpers";
|
||||
|
||||
test("Cassandra: Keyspace and table CRUD", async ({ page }) => {
|
||||
const keyspaceId = generateUniqueName("keyspace");
|
||||
const tableId = generateUniqueName("table");
|
||||
test("Cassandra keyspace and table CRUD", async ({ page }) => {
|
||||
const keyspaceId = generateUniqueName("db");
|
||||
const tableId = "testtable"; // A unique table name isn't needed because the keyspace is unique
|
||||
|
||||
const explorer = await DataExplorer.open(page, TestAccount.Cassandra);
|
||||
|
||||
// Create
|
||||
await openAndFillCreateCassandraTablePanel(explorer, {
|
||||
keyspaceId,
|
||||
tableId,
|
||||
isAutoscale: true,
|
||||
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||
});
|
||||
await explorer.globalCommandButton("New Table").click();
|
||||
await explorer.whilePanelOpen(
|
||||
"Add Table",
|
||||
async (panel, okButton) => {
|
||||
await panel.getByPlaceholder("Type a new keyspace id").fill(keyspaceId);
|
||||
await panel.getByPlaceholder("Enter table Id").fill(tableId);
|
||||
await panel.getByTestId("autoscaleRUInput").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString());
|
||||
await okButton.click();
|
||||
},
|
||||
{ closeTimeout: 5 * 60 * 1000 },
|
||||
);
|
||||
|
||||
const keyspaceNode = await explorer.waitForNode(keyspaceId);
|
||||
const tableNode = await explorer.waitForContainerNode(keyspaceId, tableId);
|
||||
await expect(tableNode.element).toBeAttached();
|
||||
|
||||
// Delete table
|
||||
await deleteContainer(explorer, keyspaceId, tableId, "Delete Table");
|
||||
await tableNode.openContextMenu();
|
||||
await tableNode.contextMenuItem("Delete Table").click();
|
||||
await explorer.whilePanelOpen(
|
||||
"Delete Table",
|
||||
async (panel, okButton) => {
|
||||
await panel.getByRole("textbox", { name: "Confirm by typing the table id" }).fill(tableId);
|
||||
await okButton.click();
|
||||
},
|
||||
{ closeTimeout: 5 * 60 * 1000 },
|
||||
);
|
||||
await expect(tableNode.element).not.toBeAttached();
|
||||
|
||||
// Delete keyspace
|
||||
await deleteKeyspace(explorer, keyspaceId);
|
||||
await expect(keyspaceNode.element).not.toBeAttached();
|
||||
});
|
||||
|
||||
test("Cassandra: New keyspace shared throughput", async ({ page }) => {
|
||||
const keyspaceId = generateUniqueName("keyspace");
|
||||
const tableId = generateUniqueName("table");
|
||||
|
||||
const explorer = await DataExplorer.open(page, TestAccount.Cassandra);
|
||||
|
||||
await openAndFillCreateCassandraTablePanel(explorer, {
|
||||
keyspaceId,
|
||||
tableId,
|
||||
useSharedThroughput: true,
|
||||
});
|
||||
|
||||
const keyspaceNode = await explorer.waitForNode(keyspaceId);
|
||||
const tableNode = await explorer.waitForContainerNode(keyspaceId, tableId);
|
||||
|
||||
await expect(tableNode.element).toBeAttached();
|
||||
|
||||
// Cleanup
|
||||
await deleteKeyspace(explorer, keyspaceId);
|
||||
await expect(keyspaceNode.element).not.toBeAttached();
|
||||
});
|
||||
|
||||
test("Cassandra: Manual throughput", async ({ page }) => {
|
||||
const keyspaceId = generateUniqueName("keyspace");
|
||||
const tableId = generateUniqueName("table");
|
||||
const manualThroughput = 400;
|
||||
|
||||
const explorer = await DataExplorer.open(page, TestAccount.Cassandra);
|
||||
|
||||
await openAndFillCreateCassandraTablePanel(explorer, {
|
||||
keyspaceId,
|
||||
tableId,
|
||||
isAutoscale: false,
|
||||
throughputValue: manualThroughput,
|
||||
});
|
||||
|
||||
const keyspaceNode = await explorer.waitForNode(keyspaceId);
|
||||
const tableNode = await explorer.waitForContainerNode(keyspaceId, tableId);
|
||||
|
||||
await expect(tableNode.element).toBeAttached();
|
||||
|
||||
// Cleanup
|
||||
await deleteKeyspace(explorer, keyspaceId);
|
||||
await expect(keyspaceNode.element).not.toBeAttached();
|
||||
});
|
||||
|
||||
test("Cassandra: Multiple tables in keyspace", async ({ page }) => {
|
||||
const keyspaceId = generateUniqueName("keyspace");
|
||||
const table1Id = generateUniqueName("table");
|
||||
const table2Id = generateUniqueName("table");
|
||||
|
||||
const explorer = await DataExplorer.open(page, TestAccount.Cassandra);
|
||||
|
||||
// Create first table
|
||||
await openAndFillCreateCassandraTablePanel(explorer, {
|
||||
keyspaceId,
|
||||
tableId: table1Id,
|
||||
isAutoscale: true,
|
||||
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||
});
|
||||
|
||||
const keyspaceNode = await explorer.waitForNode(keyspaceId);
|
||||
await explorer.waitForContainerNode(keyspaceId, table1Id);
|
||||
|
||||
// Create second table in same keyspace
|
||||
await openAndFillCreateCassandraTablePanel(explorer, {
|
||||
keyspaceId,
|
||||
tableId: table2Id,
|
||||
isAutoscale: true,
|
||||
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||
});
|
||||
|
||||
await explorer.waitForContainerNode(keyspaceId, table2Id);
|
||||
|
||||
// Cleanup
|
||||
await deleteKeyspace(explorer, keyspaceId);
|
||||
await keyspaceNode.openContextMenu();
|
||||
await keyspaceNode.contextMenuItem("Delete Keyspace").click();
|
||||
await explorer.whilePanelOpen(
|
||||
"Delete Keyspace",
|
||||
async (panel, okButton) => {
|
||||
await panel.getByRole("textbox", { name: "Confirm by typing the Keyspace id" }).fill(keyspaceId);
|
||||
await okButton.click();
|
||||
},
|
||||
{ closeTimeout: 5 * 60 * 1000 },
|
||||
);
|
||||
|
||||
await expect(keyspaceNode.element).not.toBeAttached();
|
||||
});
|
||||
|
||||
31
test/fx.ts
31
test/fx.ts
@@ -1,6 +1,7 @@
|
||||
import { DefaultAzureCredential } from "@azure/identity";
|
||||
import { Frame, Locator, Page, expect } from "@playwright/test";
|
||||
import crypto from "crypto";
|
||||
import { TestContainerContext } from "./testData";
|
||||
|
||||
const RETRY_COUNT = 3;
|
||||
|
||||
@@ -55,6 +56,9 @@ export const defaultAccounts: Record<TestAccount, string> = {
|
||||
export const resourceGroupName = process.env.DE_TEST_RESOURCE_GROUP ?? "de-e2e-tests";
|
||||
export const subscriptionId = process.env.DE_TEST_SUBSCRIPTION_ID ?? "69e02f2d-f059-4409-9eac-97e8a276ae2c";
|
||||
export const TEST_AUTOSCALE_THROUGHPUT_RU = 1000;
|
||||
export const TEST_AUTOSCALE_MAX_THROUGHPUT_RU_2K = 2000;
|
||||
export const TEST_MANUAL_THROUGHPUT_RU_2K = 2000;
|
||||
export const ONE_MINUTE_MS: number = 60 * 1000;
|
||||
|
||||
function tryGetStandardName(accountType: TestAccount) {
|
||||
if (process.env.DE_TEST_ACCOUNT_PREFIX) {
|
||||
@@ -319,6 +323,11 @@ type PanelOpenOptions = {
|
||||
closeTimeout?: number;
|
||||
};
|
||||
|
||||
export enum CommandBarButton {
|
||||
Save = "Save",
|
||||
ExecuteQuery = "Execute Query",
|
||||
}
|
||||
|
||||
/** Helper class that provides locator methods for DataExplorer components, on top of a Frame */
|
||||
export class DataExplorer {
|
||||
constructor(public frame: Frame) {}
|
||||
@@ -344,12 +353,12 @@ export class DataExplorer {
|
||||
* There's only a single "primary" button, but we still require you to pass the label to confirm you're selecting the right button.
|
||||
*/
|
||||
globalCommandButton(label: string): Locator {
|
||||
return this.frame.getByTestId(`GlobalCommands/Button:${label}`);
|
||||
return this.frame.getByTestId("GlobalCommands").getByText(label);
|
||||
}
|
||||
|
||||
/** Select the command bar button with the specified label */
|
||||
commandBarButton(label: string): Locator {
|
||||
return this.frame.getByTestId(`CommandBar/Button:${label}`).and(this.frame.locator("css=button"));
|
||||
commandBarButton(commandBarButton: CommandBarButton): Locator {
|
||||
return this.frame.getByTestId(`CommandBar/Button:${commandBarButton}`).and(this.frame.locator("css=button"));
|
||||
}
|
||||
|
||||
dialogButton(label: string): Locator {
|
||||
@@ -445,6 +454,22 @@ export class DataExplorer {
|
||||
await panel.waitFor({ state: "detached", timeout: options.closeTimeout });
|
||||
}
|
||||
|
||||
/** Opens the Scale & Settings panel for the specified container */
|
||||
async openScaleAndSettings(context: TestContainerContext): Promise<void> {
|
||||
const containerNode = await this.waitForContainerNode(context.database.id, context.container.id);
|
||||
await containerNode.expand();
|
||||
|
||||
const scaleAndSettingsButton = this.frame.getByTestId(
|
||||
`TreeNode:${context.database.id}/${context.container.id}/Scale & Settings`,
|
||||
);
|
||||
await scaleAndSettingsButton.click();
|
||||
}
|
||||
|
||||
/** Gets the console message element */
|
||||
getConsoleMessage(): Locator {
|
||||
return this.frame.getByTestId("notification-console/header-status");
|
||||
}
|
||||
|
||||
/** Waits for the Data Explorer app to load */
|
||||
static async waitForExplorer(page: Page) {
|
||||
const iframeElement = await page.getByTestId("DataExplorerFrame").elementHandle();
|
||||
|
||||
@@ -1,108 +1,22 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
|
||||
import {
|
||||
GREMLIN_CONFIG,
|
||||
deleteContainer,
|
||||
deleteDatabase,
|
||||
openAndFillCreateContainerPanel,
|
||||
} from "../helpers/containerCreationHelpers";
|
||||
|
||||
test("Gremlin: Database and graph CRUD", async ({ page }) => {
|
||||
test("Gremlin graph CRUD", async ({ page }) => {
|
||||
const databaseId = generateUniqueName("db");
|
||||
const graphId = generateUniqueName("graph");
|
||||
|
||||
const explorer = await DataExplorer.open(page, TestAccount.Gremlin);
|
||||
|
||||
// Create
|
||||
await openAndFillCreateContainerPanel(explorer, GREMLIN_CONFIG, {
|
||||
databaseId,
|
||||
containerId: graphId,
|
||||
partitionKey: "/pk",
|
||||
isAutoscale: true,
|
||||
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||
});
|
||||
|
||||
const databaseNode = await explorer.waitForNode(databaseId);
|
||||
const graphNode = await explorer.waitForContainerNode(databaseId, graphId);
|
||||
await expect(graphNode.element).toBeAttached();
|
||||
|
||||
// Delete graph
|
||||
await deleteContainer(explorer, databaseId, graphId, "Delete Graph");
|
||||
await expect(graphNode.element).not.toBeAttached();
|
||||
|
||||
// Delete database
|
||||
await deleteDatabase(explorer, databaseId);
|
||||
await expect(databaseNode.element).not.toBeAttached();
|
||||
});
|
||||
|
||||
test("Gremlin: New database shared throughput", async ({ page }) => {
|
||||
const databaseId = generateUniqueName("db");
|
||||
const graphId = generateUniqueName("graph");
|
||||
|
||||
const explorer = await DataExplorer.open(page, TestAccount.Gremlin);
|
||||
|
||||
await openAndFillCreateContainerPanel(explorer, GREMLIN_CONFIG, {
|
||||
databaseId,
|
||||
containerId: graphId,
|
||||
partitionKey: "/pk",
|
||||
useSharedThroughput: true,
|
||||
});
|
||||
|
||||
const databaseNode = await explorer.waitForNode(databaseId);
|
||||
const graphNode = await explorer.waitForContainerNode(databaseId, graphId);
|
||||
|
||||
await expect(graphNode.element).toBeAttached();
|
||||
|
||||
// Cleanup
|
||||
await deleteDatabase(explorer, databaseId);
|
||||
await expect(databaseNode.element).not.toBeAttached();
|
||||
});
|
||||
|
||||
test("Gremlin: Manual throughput", async ({ page }) => {
|
||||
const databaseId = generateUniqueName("db");
|
||||
const graphId = generateUniqueName("graph");
|
||||
const manualThroughput = 400;
|
||||
|
||||
const explorer = await DataExplorer.open(page, TestAccount.Gremlin);
|
||||
|
||||
await openAndFillCreateContainerPanel(explorer, GREMLIN_CONFIG, {
|
||||
databaseId,
|
||||
containerId: graphId,
|
||||
partitionKey: "/pk",
|
||||
isAutoscale: false,
|
||||
throughputValue: manualThroughput,
|
||||
});
|
||||
|
||||
const databaseNode = await explorer.waitForNode(databaseId);
|
||||
const graphNode = await explorer.waitForContainerNode(databaseId, graphId);
|
||||
|
||||
await expect(graphNode.element).toBeAttached();
|
||||
|
||||
// Cleanup
|
||||
await deleteDatabase(explorer, databaseId);
|
||||
await expect(databaseNode.element).not.toBeAttached();
|
||||
});
|
||||
|
||||
test("Gremlin: No unique keys support", async ({ page }) => {
|
||||
const databaseId = generateUniqueName("db");
|
||||
const graphId = generateUniqueName("graph");
|
||||
const graphId = "testgraph"; // A unique graph name isn't needed because the database is unique
|
||||
|
||||
const explorer = await DataExplorer.open(page, TestAccount.Gremlin);
|
||||
|
||||
// Create new database and graph
|
||||
await explorer.globalCommandButton("New Graph").click();
|
||||
await explorer.whilePanelOpen(
|
||||
"New Graph",
|
||||
async (panel, okButton) => {
|
||||
await panel.getByTestId("AddCollectionPanel/DatabaseId").fill(databaseId);
|
||||
await panel.getByTestId("AddCollectionPanel/CollectionId").fill(graphId);
|
||||
await panel.getByPlaceholder("Type a new database id").fill(databaseId);
|
||||
await panel.getByRole("textbox", { name: "Graph id, Example Graph1" }).fill(graphId);
|
||||
await panel.getByRole("textbox", { name: "Partition key" }).fill("/pk");
|
||||
await panel.getByTestId("ThroughputInput/AutoscaleRUInput").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString());
|
||||
|
||||
// Verify unique key button is not present (Gremlin-specific API limitation)
|
||||
const uniqueKeyButton = panel.getByTestId("AddCollectionPanel/AddUniqueKeyButton");
|
||||
await expect(uniqueKeyButton).not.toBeVisible();
|
||||
|
||||
await panel.getByTestId("autoscaleRUInput").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString());
|
||||
await okButton.click();
|
||||
},
|
||||
{ closeTimeout: 5 * 60 * 1000 },
|
||||
@@ -110,9 +24,29 @@ test("Gremlin: No unique keys support", async ({ page }) => {
|
||||
|
||||
const databaseNode = await explorer.waitForNode(databaseId);
|
||||
const graphNode = await explorer.waitForContainerNode(databaseId, graphId);
|
||||
await expect(graphNode.element).toBeAttached();
|
||||
|
||||
// Cleanup
|
||||
await deleteDatabase(explorer, databaseId);
|
||||
await graphNode.openContextMenu();
|
||||
await graphNode.contextMenuItem("Delete Graph").click();
|
||||
await explorer.whilePanelOpen(
|
||||
"Delete Graph",
|
||||
async (panel, okButton) => {
|
||||
await panel.getByRole("textbox", { name: "Confirm by typing the graph id" }).fill(graphId);
|
||||
await okButton.click();
|
||||
},
|
||||
{ closeTimeout: 5 * 60 * 1000 },
|
||||
);
|
||||
await expect(graphNode.element).not.toBeAttached();
|
||||
|
||||
await databaseNode.openContextMenu();
|
||||
await databaseNode.contextMenuItem("Delete Database").click();
|
||||
await explorer.whilePanelOpen(
|
||||
"Delete Database",
|
||||
async (panel, okButton) => {
|
||||
await panel.getByRole("textbox", { name: "Confirm by typing the Database id" }).fill(databaseId);
|
||||
await okButton.click();
|
||||
},
|
||||
{ closeTimeout: 5 * 60 * 1000 },
|
||||
);
|
||||
|
||||
await expect(databaseNode.element).not.toBeAttached();
|
||||
});
|
||||
|
||||
@@ -1,323 +0,0 @@
|
||||
import { Locator } from "@playwright/test";
|
||||
import { DataExplorer, TestAccount } from "../fx";
|
||||
|
||||
/**
|
||||
* Container creation test API configuration
|
||||
* Defines labels and selectors specific to each Cosmos DB API
|
||||
*/
|
||||
export interface ApiConfig {
|
||||
account: TestAccount;
|
||||
commandLabel: string; // "New Container", "New Collection", "New Graph", "New Table"
|
||||
containerIdLabel: string; // "Container id", "Collection id", "Graph id", "Table id"
|
||||
panelTitle: string; // "New Container", "New Collection", "New Graph", "Add Table"
|
||||
databaseIdPlaceholder: string; // "Type a new keyspace id" for Cassandra, etc.
|
||||
containerIdPlaceholder: string;
|
||||
partitionKeyLabel?: string; // "Partition key", "Shard key", or undefined for Tables
|
||||
partitionKeyPlaceholder?: string;
|
||||
confirmDeleteLabel: string; // "Confirm by typing the [container/collection/table/graph] id"
|
||||
databaseName?: string; // "TablesDB" for Tables, undefined for others
|
||||
supportsUniqueKeys: boolean;
|
||||
}
|
||||
|
||||
export const SQL_CONFIG: ApiConfig = {
|
||||
account: TestAccount.SQL,
|
||||
commandLabel: "New Container",
|
||||
containerIdLabel: "Container id, Example Container1",
|
||||
panelTitle: "New Container",
|
||||
databaseIdPlaceholder: "Type a new database id",
|
||||
containerIdPlaceholder: "e.g., Container1",
|
||||
partitionKeyLabel: "Partition key",
|
||||
partitionKeyPlaceholder: "/pk",
|
||||
confirmDeleteLabel: "Confirm by typing the container id",
|
||||
supportsUniqueKeys: true,
|
||||
};
|
||||
|
||||
export const MONGO_CONFIG: ApiConfig = {
|
||||
account: TestAccount.Mongo,
|
||||
commandLabel: "New Collection",
|
||||
containerIdLabel: "Collection id, Example Collection1",
|
||||
panelTitle: "New Collection",
|
||||
databaseIdPlaceholder: "Type a new database id",
|
||||
containerIdPlaceholder: "e.g., Collection1",
|
||||
partitionKeyLabel: "Shard key",
|
||||
partitionKeyPlaceholder: "pk",
|
||||
confirmDeleteLabel: "Confirm by typing the collection id",
|
||||
supportsUniqueKeys: false,
|
||||
};
|
||||
|
||||
export const MONGO32_CONFIG: ApiConfig = {
|
||||
...MONGO_CONFIG,
|
||||
account: TestAccount.Mongo32,
|
||||
};
|
||||
|
||||
export const GREMLIN_CONFIG: ApiConfig = {
|
||||
account: TestAccount.Gremlin,
|
||||
commandLabel: "New Graph",
|
||||
containerIdLabel: "Graph id, Example Graph1",
|
||||
panelTitle: "New Graph",
|
||||
databaseIdPlaceholder: "Type a new database id",
|
||||
containerIdPlaceholder: "e.g., Graph1",
|
||||
partitionKeyLabel: "Partition key",
|
||||
partitionKeyPlaceholder: "/pk",
|
||||
confirmDeleteLabel: "Confirm by typing the graph id",
|
||||
supportsUniqueKeys: false,
|
||||
};
|
||||
|
||||
export const TABLES_CONFIG: ApiConfig = {
|
||||
account: TestAccount.Tables,
|
||||
commandLabel: "New Table",
|
||||
containerIdLabel: "Table id, Example Table1",
|
||||
panelTitle: "New Table",
|
||||
databaseIdPlaceholder: "", // Not used
|
||||
containerIdPlaceholder: "e.g., Table1",
|
||||
confirmDeleteLabel: "Confirm by typing the table id",
|
||||
databaseName: "TablesDB",
|
||||
supportsUniqueKeys: false,
|
||||
};
|
||||
|
||||
export const CASSANDRA_CONFIG: ApiConfig = {
|
||||
account: TestAccount.Cassandra,
|
||||
commandLabel: "New Table",
|
||||
containerIdLabel: "Enter table Id",
|
||||
panelTitle: "Add Table",
|
||||
databaseIdPlaceholder: "Type a new keyspace id",
|
||||
containerIdPlaceholder: "Enter table Id",
|
||||
confirmDeleteLabel: "Confirm by typing the table id",
|
||||
supportsUniqueKeys: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Fills database selection in the panel
|
||||
* Automatically selects "Create new" and fills the database ID
|
||||
*/
|
||||
export async function fillDatabaseSelection(panel: Locator, databaseId: string): Promise<void> {
|
||||
// Wait for the radio button to be visible and click it (more reliable than check for custom styled radios)
|
||||
await panel.getByTestId("AddCollectionPanel/DatabaseRadio:CreateNew").waitFor({ state: "visible" });
|
||||
await panel.getByTestId("AddCollectionPanel/DatabaseRadio:CreateNew").click();
|
||||
await panel.getByTestId("AddCollectionPanel/DatabaseId").fill(databaseId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills existing database selection
|
||||
* Selects "Use existing" and clicks the dropdown to select the database
|
||||
*/
|
||||
export async function fillExistingDatabaseSelection(panel: Locator, databaseId: string): Promise<void> {
|
||||
await panel.getByTestId("AddCollectionPanel/DatabaseRadio:UseExisting").waitFor({ state: "visible" });
|
||||
await panel.getByTestId("AddCollectionPanel/DatabaseRadio:UseExisting").click();
|
||||
await panel.getByTestId("AddCollectionPanel/ExistingDatabaseDropdown").click();
|
||||
await panel.locator(`text=${databaseId}`).click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills container/collection/graph/table details
|
||||
*/
|
||||
export async function fillContainerDetails(
|
||||
panel: Locator,
|
||||
containerId: string,
|
||||
partitionKey: string | undefined,
|
||||
): Promise<void> {
|
||||
await panel.getByTestId("AddCollectionPanel/CollectionId").fill(containerId);
|
||||
|
||||
if (partitionKey) {
|
||||
await panel.getByTestId("AddCollectionPanel/PartitionKey").first().fill(partitionKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills Cassandra-specific table details
|
||||
* (keyspace and table IDs are separate for Cassandra)
|
||||
*/
|
||||
export async function fillCassandraTableDetails(panel: Locator, keyspaceId: string, tableId: string): Promise<void> {
|
||||
await panel.getByTestId("AddCollectionPanel/DatabaseId").fill(keyspaceId);
|
||||
await panel.getByTestId("AddCollectionPanel/CollectionId").fill(tableId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets throughput mode and value
|
||||
* @param isAutoscale - if true, sets autoscale mode; if false, sets manual mode
|
||||
*/
|
||||
export async function setThroughput(panel: Locator, isAutoscale: boolean, throughputValue: number): Promise<void> {
|
||||
const testId = isAutoscale ? "ThroughputInput/ThroughputMode:Autoscale" : "ThroughputInput/ThroughputMode:Manual";
|
||||
await panel.getByTestId(testId).check();
|
||||
|
||||
if (isAutoscale) {
|
||||
await panel.getByTestId("ThroughputInput/AutoscaleRUInput").fill(throughputValue.toString());
|
||||
} else {
|
||||
await panel.getByTestId("ThroughputInput/ManualThroughputInput").fill(throughputValue.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a unique key to the container (SQL/Mongo only)
|
||||
*/
|
||||
export async function addUniqueKey(panel: Locator, uniqueKeyValue: string): Promise<void> {
|
||||
// Scroll to find the unique key section
|
||||
await panel.getByTestId("AddCollectionPanel/UniqueKeysSection").scrollIntoViewIfNeeded();
|
||||
|
||||
// Click the "Add unique key" button
|
||||
await panel.getByTestId("AddCollectionPanel/AddUniqueKeyButton").click();
|
||||
|
||||
// Fill in the unique key value
|
||||
const uniqueKeyInput = panel.getByTestId("AddCollectionPanel/UniqueKey").first();
|
||||
await uniqueKeyInput.fill(uniqueKeyValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a database and waits for it to disappear from the tree
|
||||
*/
|
||||
export async function deleteDatabase(
|
||||
explorer: DataExplorer,
|
||||
databaseId: string,
|
||||
databaseNodeName: string = databaseId,
|
||||
): Promise<void> {
|
||||
const databaseNode = await explorer.waitForNode(databaseNodeName);
|
||||
await databaseNode.openContextMenu();
|
||||
await databaseNode.contextMenuItem("Delete Database").click();
|
||||
await explorer.whilePanelOpen(
|
||||
"Delete Database",
|
||||
async (panel: Locator, okButton: Locator) => {
|
||||
await panel.getByTestId("DeleteDatabaseConfirmationPanel/ConfirmInput").fill(databaseId);
|
||||
await okButton.click();
|
||||
},
|
||||
{ closeTimeout: 5 * 60 * 1000 },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a keyspace (Cassandra only)
|
||||
*/
|
||||
export async function deleteKeyspace(explorer: DataExplorer, keyspaceId: string): Promise<void> {
|
||||
const keyspaceNode = await explorer.waitForNode(keyspaceId);
|
||||
await keyspaceNode.openContextMenu();
|
||||
await keyspaceNode.contextMenuItem("Delete Keyspace").click();
|
||||
await explorer.whilePanelOpen(
|
||||
"Delete Keyspace",
|
||||
async (panel: Locator, okButton: Locator) => {
|
||||
await panel.getByTestId("DeleteCollectionConfirmationPane/ConfirmInput").fill(keyspaceId);
|
||||
await okButton.click();
|
||||
},
|
||||
{ closeTimeout: 5 * 60 * 1000 },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a container/collection/graph/table
|
||||
*/
|
||||
export async function deleteContainer(
|
||||
explorer: DataExplorer,
|
||||
databaseId: string,
|
||||
containerId: string,
|
||||
deleteLabel: string, // "Delete Container", "Delete Collection", etc.
|
||||
): Promise<void> {
|
||||
const containerNode = await explorer.waitForContainerNode(databaseId, containerId);
|
||||
await containerNode.openContextMenu();
|
||||
await containerNode.contextMenuItem(deleteLabel).click();
|
||||
await explorer.whilePanelOpen(
|
||||
deleteLabel,
|
||||
async (panel: Locator, okButton: Locator) => {
|
||||
// All container/collection/graph/table deletes use same panel with test ID
|
||||
await panel.getByTestId("DeleteCollectionConfirmationPane/ConfirmInput").fill(containerId);
|
||||
await okButton.click();
|
||||
},
|
||||
{ closeTimeout: 5 * 60 * 1000 },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the create container dialog and fills in the form based on scenario
|
||||
*/
|
||||
export async function openAndFillCreateContainerPanel(
|
||||
explorer: DataExplorer,
|
||||
config: ApiConfig,
|
||||
options: {
|
||||
databaseId: string;
|
||||
containerId: string;
|
||||
partitionKey?: string;
|
||||
useExistingDatabase?: boolean;
|
||||
isAutoscale?: boolean;
|
||||
throughputValue?: number;
|
||||
uniqueKey?: string;
|
||||
useSharedThroughput?: boolean;
|
||||
},
|
||||
): Promise<void> {
|
||||
await explorer.globalCommandButton(config.commandLabel).click();
|
||||
await explorer.whilePanelOpen(
|
||||
config.panelTitle,
|
||||
async (panel, okButton) => {
|
||||
// Database selection
|
||||
if (options.useExistingDatabase) {
|
||||
await fillExistingDatabaseSelection(panel, options.databaseId);
|
||||
} else {
|
||||
await fillDatabaseSelection(panel, options.databaseId);
|
||||
}
|
||||
|
||||
// Shared throughput checkbox (if applicable)
|
||||
if (options.useSharedThroughput) {
|
||||
await panel
|
||||
.getByTestId("AddCollectionPanel/SharedThroughputCheckbox")
|
||||
.getByRole("checkbox")
|
||||
.check({ force: true });
|
||||
}
|
||||
|
||||
// Container details
|
||||
await fillContainerDetails(panel, options.containerId, options.partitionKey);
|
||||
|
||||
// Throughput (only if not using shared throughput)
|
||||
if (!options.useSharedThroughput) {
|
||||
const isAutoscale = options.isAutoscale !== false;
|
||||
const throughputValue = options.throughputValue || 1000;
|
||||
await setThroughput(panel, isAutoscale, throughputValue);
|
||||
}
|
||||
|
||||
// Unique keys (if applicable)
|
||||
if (options.uniqueKey && config.supportsUniqueKeys) {
|
||||
await addUniqueKey(panel, options.uniqueKey);
|
||||
}
|
||||
|
||||
await okButton.click();
|
||||
},
|
||||
{ closeTimeout: 5 * 60 * 1000 },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the create table dialog for Cassandra and fills in the form
|
||||
* Cassandra has a different UI pattern than other APIs
|
||||
*/
|
||||
export async function openAndFillCreateCassandraTablePanel(
|
||||
explorer: DataExplorer,
|
||||
options: {
|
||||
keyspaceId: string;
|
||||
tableId: string;
|
||||
isAutoscale?: boolean;
|
||||
throughputValue?: number;
|
||||
useSharedThroughput?: boolean;
|
||||
},
|
||||
): Promise<void> {
|
||||
await explorer.globalCommandButton("New Table").click();
|
||||
await explorer.whilePanelOpen(
|
||||
"Add Table",
|
||||
async (panel, okButton) => {
|
||||
// Fill Cassandra-specific table details
|
||||
await fillCassandraTableDetails(panel, options.keyspaceId, options.tableId);
|
||||
|
||||
// Shared throughput checkbox (if applicable)
|
||||
if (options.useSharedThroughput) {
|
||||
await panel
|
||||
.getByTestId("AddCollectionPanel/SharedThroughputCheckbox")
|
||||
.getByRole("checkbox")
|
||||
.check({ force: true });
|
||||
}
|
||||
|
||||
// Throughput (only if not using shared throughput)
|
||||
if (!options.useSharedThroughput) {
|
||||
const isAutoscale = options.isAutoscale !== false;
|
||||
const throughputValue = options.throughputValue || 1000;
|
||||
await setThroughput(panel, isAutoscale, throughputValue);
|
||||
}
|
||||
|
||||
await okButton.click();
|
||||
},
|
||||
{ closeTimeout: 5 * 60 * 1000 },
|
||||
);
|
||||
}
|
||||
@@ -1,118 +1,58 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
|
||||
import {
|
||||
MONGO32_CONFIG,
|
||||
MONGO_CONFIG,
|
||||
deleteContainer,
|
||||
deleteDatabase,
|
||||
openAndFillCreateContainerPanel,
|
||||
} from "../helpers/containerCreationHelpers";
|
||||
|
||||
(
|
||||
[
|
||||
["latest API version", MONGO_CONFIG],
|
||||
["3.2 API", MONGO32_CONFIG],
|
||||
] as [string, typeof MONGO_CONFIG][]
|
||||
).forEach(([apiVersionDescription, config]) => {
|
||||
test(`Mongo: Database and collection CRUD using ${apiVersionDescription}`, async ({ page }) => {
|
||||
["latest API version", TestAccount.Mongo],
|
||||
["3.2 API", TestAccount.Mongo32],
|
||||
] as [string, TestAccount][]
|
||||
).forEach(([apiVersionDescription, accountType]) => {
|
||||
test(`Mongo CRUD using ${apiVersionDescription}`, async ({ page }) => {
|
||||
const databaseId = generateUniqueName("db");
|
||||
const collectionId = generateUniqueName("collection");
|
||||
const collectionId = "testcollection"; // A unique collection name isn't needed because the database is unique
|
||||
|
||||
const explorer = await DataExplorer.open(page, config.account);
|
||||
const explorer = await DataExplorer.open(page, accountType);
|
||||
|
||||
// Create
|
||||
await openAndFillCreateContainerPanel(explorer, config, {
|
||||
databaseId,
|
||||
containerId: collectionId,
|
||||
partitionKey: "pk",
|
||||
isAutoscale: true,
|
||||
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||
});
|
||||
await explorer.globalCommandButton("New Collection").click();
|
||||
await explorer.whilePanelOpen(
|
||||
"New Collection",
|
||||
async (panel, okButton) => {
|
||||
await panel.getByPlaceholder("Type a new database id").fill(databaseId);
|
||||
await panel.getByRole("textbox", { name: "Collection id, Example Collection1" }).fill(collectionId);
|
||||
await panel.getByRole("textbox", { name: "Shard key" }).fill("pk");
|
||||
await panel.getByTestId("autoscaleRUInput").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString());
|
||||
await okButton.click();
|
||||
},
|
||||
{ closeTimeout: 5 * 60 * 1000 },
|
||||
);
|
||||
|
||||
const databaseNode = await explorer.waitForNode(databaseId);
|
||||
const collectionNode = await explorer.waitForContainerNode(databaseId, collectionId);
|
||||
await expect(collectionNode.element).toBeAttached();
|
||||
|
||||
// Delete collection
|
||||
await deleteContainer(explorer, databaseId, collectionId, "Delete Collection");
|
||||
await collectionNode.openContextMenu();
|
||||
await collectionNode.contextMenuItem("Delete Collection").click();
|
||||
await explorer.whilePanelOpen(
|
||||
"Delete Collection",
|
||||
async (panel, okButton) => {
|
||||
await panel.getByRole("textbox", { name: "Confirm by typing the collection id" }).fill(collectionId);
|
||||
await okButton.click();
|
||||
},
|
||||
{ closeTimeout: 5 * 60 * 1000 },
|
||||
);
|
||||
await expect(collectionNode.element).not.toBeAttached();
|
||||
|
||||
// Delete database
|
||||
await deleteDatabase(explorer, databaseId);
|
||||
await databaseNode.openContextMenu();
|
||||
await databaseNode.contextMenuItem("Delete Database").click();
|
||||
await explorer.whilePanelOpen(
|
||||
"Delete Database",
|
||||
async (panel, okButton) => {
|
||||
await panel.getByRole("textbox", { name: "Confirm by typing the Database id" }).fill(databaseId);
|
||||
await okButton.click();
|
||||
},
|
||||
{ closeTimeout: 5 * 60 * 1000 },
|
||||
);
|
||||
|
||||
await expect(databaseNode.element).not.toBeAttached();
|
||||
});
|
||||
});
|
||||
|
||||
test("Mongo: New database shared throughput", async ({ page }) => {
|
||||
const databaseId = generateUniqueName("db");
|
||||
const collectionId = generateUniqueName("collection");
|
||||
|
||||
const explorer = await DataExplorer.open(page, TestAccount.Mongo);
|
||||
|
||||
await openAndFillCreateContainerPanel(explorer, MONGO_CONFIG, {
|
||||
databaseId,
|
||||
containerId: collectionId,
|
||||
partitionKey: "pk",
|
||||
useSharedThroughput: true,
|
||||
});
|
||||
|
||||
const databaseNode = await explorer.waitForNode(databaseId);
|
||||
const collectionNode = await explorer.waitForContainerNode(databaseId, collectionId);
|
||||
|
||||
await expect(collectionNode.element).toBeAttached();
|
||||
|
||||
// Cleanup
|
||||
await deleteDatabase(explorer, databaseId);
|
||||
await expect(databaseNode.element).not.toBeAttached();
|
||||
});
|
||||
|
||||
test("Mongo: Unique keys", async ({ page }) => {
|
||||
const databaseId = generateUniqueName("db");
|
||||
const collectionId = generateUniqueName("collection");
|
||||
|
||||
const explorer = await DataExplorer.open(page, TestAccount.Mongo);
|
||||
|
||||
await openAndFillCreateContainerPanel(explorer, MONGO_CONFIG, {
|
||||
databaseId,
|
||||
containerId: collectionId,
|
||||
partitionKey: "pk",
|
||||
isAutoscale: true,
|
||||
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||
uniqueKey: "email",
|
||||
});
|
||||
|
||||
const databaseNode = await explorer.waitForNode(databaseId);
|
||||
const collectionNode = await explorer.waitForContainerNode(databaseId, collectionId);
|
||||
|
||||
await expect(collectionNode.element).toBeAttached();
|
||||
|
||||
// Cleanup
|
||||
await deleteDatabase(explorer, databaseId);
|
||||
await expect(databaseNode.element).not.toBeAttached();
|
||||
});
|
||||
|
||||
test("Mongo: Manual throughput", async ({ page }) => {
|
||||
const databaseId = generateUniqueName("db");
|
||||
const collectionId = generateUniqueName("collection");
|
||||
const manualThroughput = 400;
|
||||
|
||||
const explorer = await DataExplorer.open(page, TestAccount.Mongo);
|
||||
|
||||
await openAndFillCreateContainerPanel(explorer, MONGO_CONFIG, {
|
||||
databaseId,
|
||||
containerId: collectionId,
|
||||
partitionKey: "pk",
|
||||
isAutoscale: false,
|
||||
throughputValue: manualThroughput,
|
||||
});
|
||||
|
||||
const databaseNode = await explorer.waitForNode(databaseId);
|
||||
const collectionNode = await explorer.waitForContainerNode(databaseId, collectionId);
|
||||
|
||||
await expect(collectionNode.element).toBeAttached();
|
||||
|
||||
// Cleanup
|
||||
await deleteDatabase(explorer, databaseId);
|
||||
await expect(databaseNode.element).not.toBeAttached();
|
||||
});
|
||||
|
||||
@@ -1,110 +1,51 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
|
||||
import {
|
||||
SQL_CONFIG,
|
||||
deleteContainer,
|
||||
deleteDatabase,
|
||||
openAndFillCreateContainerPanel,
|
||||
} from "../helpers/containerCreationHelpers";
|
||||
|
||||
test("SQL: Database and container CRUD", async ({ page }) => {
|
||||
test("SQL database and container CRUD", async ({ page }) => {
|
||||
const databaseId = generateUniqueName("db");
|
||||
const containerId = generateUniqueName("container");
|
||||
const containerId = "testcontainer"; // A unique container name isn't needed because the database is unique
|
||||
|
||||
const explorer = await DataExplorer.open(page, TestAccount.SQL);
|
||||
|
||||
// Create
|
||||
await openAndFillCreateContainerPanel(explorer, SQL_CONFIG, {
|
||||
databaseId,
|
||||
containerId,
|
||||
partitionKey: "/pk",
|
||||
isAutoscale: true,
|
||||
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||
});
|
||||
await explorer.globalCommandButton("New Container").click();
|
||||
await explorer.whilePanelOpen(
|
||||
"New Container",
|
||||
async (panel, okButton) => {
|
||||
await panel.getByPlaceholder("Type a new database id").fill(databaseId);
|
||||
await panel.getByRole("textbox", { name: "Container id, Example Container1" }).fill(containerId);
|
||||
await panel.getByRole("textbox", { name: "Partition key" }).fill("/pk");
|
||||
await panel.getByTestId("autoscaleRUInput").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString());
|
||||
await okButton.click();
|
||||
},
|
||||
{ closeTimeout: 5 * 60 * 1000 },
|
||||
);
|
||||
|
||||
const databaseNode = await explorer.waitForNode(databaseId);
|
||||
const containerNode = await explorer.waitForContainerNode(databaseId, containerId);
|
||||
await expect(containerNode.element).toBeAttached();
|
||||
|
||||
// Delete container
|
||||
await deleteContainer(explorer, databaseId, containerId, "Delete Container");
|
||||
await containerNode.openContextMenu();
|
||||
await containerNode.contextMenuItem("Delete Container").click();
|
||||
await explorer.whilePanelOpen(
|
||||
"Delete Container",
|
||||
async (panel, okButton) => {
|
||||
await panel.getByRole("textbox", { name: "Confirm by typing the container id" }).fill(containerId);
|
||||
await okButton.click();
|
||||
},
|
||||
{ closeTimeout: 5 * 60 * 1000 },
|
||||
);
|
||||
await expect(containerNode.element).not.toBeAttached();
|
||||
|
||||
// Delete database
|
||||
await deleteDatabase(explorer, databaseId);
|
||||
await expect(databaseNode.element).not.toBeAttached();
|
||||
});
|
||||
|
||||
test("SQL: New database shared throughput", async ({ page }) => {
|
||||
const databaseId = generateUniqueName("db");
|
||||
const containerId = generateUniqueName("container");
|
||||
|
||||
const explorer = await DataExplorer.open(page, TestAccount.SQL);
|
||||
|
||||
await openAndFillCreateContainerPanel(explorer, SQL_CONFIG, {
|
||||
databaseId,
|
||||
containerId,
|
||||
partitionKey: "/pk",
|
||||
useSharedThroughput: true,
|
||||
});
|
||||
|
||||
const databaseNode = await explorer.waitForNode(databaseId);
|
||||
const containerNode = await explorer.waitForContainerNode(databaseId, containerId);
|
||||
|
||||
await expect(containerNode.element).toBeAttached();
|
||||
|
||||
// Cleanup
|
||||
await deleteDatabase(explorer, databaseId);
|
||||
await expect(databaseNode.element).not.toBeAttached();
|
||||
});
|
||||
|
||||
test("SQL: Unique keys", async ({ page }) => {
|
||||
const databaseId = generateUniqueName("db");
|
||||
const containerId = generateUniqueName("container");
|
||||
|
||||
const explorer = await DataExplorer.open(page, TestAccount.SQL);
|
||||
|
||||
await openAndFillCreateContainerPanel(explorer, SQL_CONFIG, {
|
||||
databaseId,
|
||||
containerId,
|
||||
partitionKey: "/pk",
|
||||
isAutoscale: true,
|
||||
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||
uniqueKey: "/email,/username",
|
||||
});
|
||||
|
||||
const databaseNode = await explorer.waitForNode(databaseId);
|
||||
const containerNode = await explorer.waitForContainerNode(databaseId, containerId);
|
||||
|
||||
await expect(containerNode.element).toBeAttached();
|
||||
|
||||
// Cleanup
|
||||
await deleteDatabase(explorer, databaseId);
|
||||
await expect(databaseNode.element).not.toBeAttached();
|
||||
});
|
||||
|
||||
test("SQL: Manual throughput", async ({ page }) => {
|
||||
const databaseId = generateUniqueName("db");
|
||||
const containerId = generateUniqueName("container");
|
||||
const manualThroughput = 400;
|
||||
|
||||
const explorer = await DataExplorer.open(page, TestAccount.SQL);
|
||||
|
||||
await openAndFillCreateContainerPanel(explorer, SQL_CONFIG, {
|
||||
databaseId,
|
||||
containerId,
|
||||
partitionKey: "/pk",
|
||||
isAutoscale: false,
|
||||
throughputValue: manualThroughput,
|
||||
});
|
||||
|
||||
const databaseNode = await explorer.waitForNode(databaseId);
|
||||
const containerNode = await explorer.waitForContainerNode(databaseId, containerId);
|
||||
|
||||
await expect(containerNode.element).toBeAttached();
|
||||
|
||||
// Cleanup
|
||||
await deleteDatabase(explorer, databaseId);
|
||||
await databaseNode.openContextMenu();
|
||||
await databaseNode.contextMenuItem("Delete Database").click();
|
||||
await explorer.whilePanelOpen(
|
||||
"Delete Database",
|
||||
async (panel, okButton) => {
|
||||
await panel.getByRole("textbox", { name: "Confirm by typing the database id" }).fill(databaseId);
|
||||
await okButton.click();
|
||||
},
|
||||
{ closeTimeout: 5 * 60 * 1000 },
|
||||
);
|
||||
|
||||
await expect(databaseNode.element).not.toBeAttached();
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { DataExplorer, Editor, QueryTab, TestAccount } from "../fx";
|
||||
import { CommandBarButton, DataExplorer, Editor, QueryTab, TestAccount } from "../fx";
|
||||
import { TestContainerContext, TestItem, createTestSQLContainer } from "../testData";
|
||||
|
||||
let context: TestContainerContext = null!;
|
||||
@@ -37,7 +37,7 @@ test.afterAll("Delete Test Database", async () => {
|
||||
test("Query results", async () => {
|
||||
// Run the query and verify the results
|
||||
await queryEditor.locator.click();
|
||||
const executeQueryButton = explorer.commandBarButton("Execute Query");
|
||||
const executeQueryButton = explorer.commandBarButton(CommandBarButton.ExecuteQuery);
|
||||
await executeQueryButton.click();
|
||||
await expect(queryTab.resultsEditor.locator).toBeAttached({ timeout: 60 * 1000 });
|
||||
|
||||
@@ -59,7 +59,7 @@ test("Query results", async () => {
|
||||
test("Query stats", async () => {
|
||||
// Run the query and verify the results
|
||||
await queryEditor.locator.click();
|
||||
const executeQueryButton = explorer.commandBarButton("Execute Query");
|
||||
const executeQueryButton = explorer.commandBarButton(CommandBarButton.ExecuteQuery);
|
||||
await executeQueryButton.click();
|
||||
await expect(queryTab.resultsEditor.locator).toBeAttached({ timeout: 60 * 1000 });
|
||||
|
||||
@@ -77,7 +77,7 @@ test("Query errors", async () => {
|
||||
await queryEditor.setText("SELECT\n glarb(c.id),\n blarg(c.id)\nFROM c");
|
||||
|
||||
// Run the query and verify the results
|
||||
const executeQueryButton = explorer.commandBarButton("Execute Query");
|
||||
const executeQueryButton = explorer.commandBarButton(CommandBarButton.ExecuteQuery);
|
||||
await executeQueryButton.click();
|
||||
|
||||
await expect(queryTab.errorList).toBeAttached({ timeout: 60 * 1000 });
|
||||
|
||||
129
test/sql/scaleAndSettings/scale.spec.ts
Normal file
129
test/sql/scaleAndSettings/scale.spec.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { expect, Locator, test } from "@playwright/test";
|
||||
import {
|
||||
CommandBarButton,
|
||||
DataExplorer,
|
||||
ONE_MINUTE_MS,
|
||||
TEST_AUTOSCALE_MAX_THROUGHPUT_RU_2K,
|
||||
TEST_MANUAL_THROUGHPUT_RU_2K,
|
||||
TestAccount,
|
||||
} from "../../fx";
|
||||
import { createTestSQLContainer, TestContainerContext } from "../../testData";
|
||||
|
||||
test.describe("Autoscale and Manual throughput", () => {
|
||||
let context: TestContainerContext = null!;
|
||||
let explorer: DataExplorer = null!;
|
||||
|
||||
test.beforeAll("Create Test Database", async () => {
|
||||
context = await createTestSQLContainer(true);
|
||||
});
|
||||
|
||||
test.beforeEach("Open container settings", async ({ page }) => {
|
||||
explorer = await DataExplorer.open(page, TestAccount.SQL);
|
||||
|
||||
// Click Scale & Settings and open Scale tab
|
||||
await explorer.openScaleAndSettings(context);
|
||||
const scaleTab = explorer.frame.getByTestId("settings-tab-header/ScaleTab");
|
||||
await scaleTab.click();
|
||||
});
|
||||
|
||||
test.afterAll("Delete Test Database", async () => {
|
||||
await context?.dispose();
|
||||
});
|
||||
|
||||
test("Update autoscale max throughput", async () => {
|
||||
// By default the created container has manual throughput (Containers created via JS SDK v4.7.0 cannot be created with autoscale throughput)
|
||||
await switchManualToAutoscaleThroughput();
|
||||
|
||||
// Update autoscale max throughput
|
||||
await getThroughputInput("autopilot").fill(TEST_AUTOSCALE_MAX_THROUGHPUT_RU_2K.toString());
|
||||
|
||||
// Save
|
||||
await explorer.commandBarButton(CommandBarButton.Save).click();
|
||||
|
||||
// Read console message
|
||||
await expect(explorer.getConsoleMessage()).toContainText(
|
||||
`Successfully updated offer for collection ${context.container.id}`,
|
||||
{
|
||||
timeout: 2 * ONE_MINUTE_MS,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("Update autoscale max throughput passed allowed limit", async () => {
|
||||
// By default the created container has manual throughput (Containers created via JS SDK v4.7.0 cannot be created with autoscale throughput)
|
||||
await switchManualToAutoscaleThroughput();
|
||||
|
||||
// Get soft allowed max throughput and remove commas
|
||||
const softAllowedMaxThroughputString = await explorer.frame
|
||||
.getByTestId("soft-allowed-maximum-throughput")
|
||||
.innerText();
|
||||
const softAllowedMaxThroughput = Number(softAllowedMaxThroughputString.replace(/,/g, ""));
|
||||
|
||||
// Try to set autoscale max throughput above allowed limit
|
||||
await getThroughputInput("autopilot").fill((softAllowedMaxThroughput * 10).toString());
|
||||
await expect(explorer.commandBarButton(CommandBarButton.Save)).toBeDisabled();
|
||||
await expect(getThroughputInputErrorMessage("autopilot")).toContainText(
|
||||
"This update isn't possible because it would increase the total throughput",
|
||||
);
|
||||
});
|
||||
|
||||
test("Update autoscale max throughput with invalid increment", async () => {
|
||||
// By default the created container has manual throughput (Containers created via JS SDK v4.7.0 cannot be created with autoscale throughput)
|
||||
await switchManualToAutoscaleThroughput();
|
||||
|
||||
// Try to set autoscale max throughput with invalid increment
|
||||
await getThroughputInput("autopilot").fill("1100");
|
||||
await expect(explorer.commandBarButton(CommandBarButton.Save)).toBeDisabled();
|
||||
await expect(getThroughputInputErrorMessage("autopilot")).toContainText(
|
||||
"Throughput value must be in increments of 1000",
|
||||
);
|
||||
});
|
||||
|
||||
test("Update manual throughput", async () => {
|
||||
await getThroughputInput("manual").fill(TEST_MANUAL_THROUGHPUT_RU_2K.toString());
|
||||
await explorer.commandBarButton(CommandBarButton.Save).click();
|
||||
await expect(explorer.getConsoleMessage()).toContainText(
|
||||
`Successfully updated offer for collection ${context.container.id}`,
|
||||
{
|
||||
timeout: 2 * ONE_MINUTE_MS,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("Update manual throughput passed allowed limit", async () => {
|
||||
// Get soft allowed max throughput and remove commas
|
||||
const softAllowedMaxThroughputString = await explorer.frame
|
||||
.getByTestId("soft-allowed-maximum-throughput")
|
||||
.innerText();
|
||||
const softAllowedMaxThroughput = Number(softAllowedMaxThroughputString.replace(/,/g, ""));
|
||||
|
||||
// Try to set manual throughput above allowed limit
|
||||
await getThroughputInput("manual").fill((softAllowedMaxThroughput * 10).toString());
|
||||
await expect(explorer.commandBarButton(CommandBarButton.Save)).toBeDisabled();
|
||||
await expect(getThroughputInputErrorMessage("manual")).toContainText(
|
||||
"This update isn't possible because it would increase the total throughput",
|
||||
);
|
||||
});
|
||||
|
||||
// Helper methods
|
||||
const getThroughputInput = (type: "manual" | "autopilot"): Locator => {
|
||||
return explorer.frame.getByTestId(`${type}-throughput-input`);
|
||||
};
|
||||
|
||||
const getThroughputInputErrorMessage = (type: "manual" | "autopilot"): Locator => {
|
||||
return explorer.frame.getByTestId(`${type}-throughput-input-error`);
|
||||
};
|
||||
|
||||
const switchManualToAutoscaleThroughput = async (): Promise<void> => {
|
||||
const autoscaleRadioButton = explorer.frame.getByText("Autoscale", { exact: true });
|
||||
await autoscaleRadioButton.click();
|
||||
await expect(explorer.commandBarButton(CommandBarButton.Save)).toBeEnabled();
|
||||
await explorer.commandBarButton(CommandBarButton.Save).click();
|
||||
await expect(explorer.getConsoleMessage()).toContainText(
|
||||
`Successfully updated offer for collection ${context.container.id}`,
|
||||
{
|
||||
timeout: ONE_MINUTE_MS,
|
||||
},
|
||||
);
|
||||
};
|
||||
});
|
||||
70
test/sql/scaleAndSettings/settings.spec.ts
Normal file
70
test/sql/scaleAndSettings/settings.spec.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { CommandBarButton, DataExplorer, ONE_MINUTE_MS, TestAccount } from "../../fx";
|
||||
import { createTestSQLContainer, TestContainerContext } from "../../testData";
|
||||
|
||||
test.describe("Settings under Scale & Settings", () => {
|
||||
let context: TestContainerContext = null!;
|
||||
let explorer: DataExplorer = null!;
|
||||
|
||||
test.beforeAll("Create Test Database", async () => {
|
||||
context = await createTestSQLContainer(true);
|
||||
});
|
||||
|
||||
test.beforeEach("Open Settings tab under Scale & Settings", async ({ page }) => {
|
||||
explorer = await DataExplorer.open(page, TestAccount.SQL);
|
||||
const containerNode = await explorer.waitForContainerNode(context.database.id, context.container.id);
|
||||
await containerNode.expand();
|
||||
|
||||
// Click Scale & Settings and open Scale tab
|
||||
await explorer.openScaleAndSettings(context);
|
||||
const settingsTab = explorer.frame.getByTestId("settings-tab-header/SubSettingsTab");
|
||||
await settingsTab.click();
|
||||
});
|
||||
|
||||
test.afterAll("Delete Test Database", async () => {
|
||||
await context?.dispose();
|
||||
});
|
||||
|
||||
test("Update TTL to On (no default)", async () => {
|
||||
const ttlOnNoDefaultRadioButton = explorer.frame.getByRole("radio", { name: "ttl-on-no-default-option" });
|
||||
await ttlOnNoDefaultRadioButton.click();
|
||||
|
||||
await explorer.commandBarButton(CommandBarButton.Save).click();
|
||||
await expect(explorer.getConsoleMessage()).toContainText(`Successfully updated container ${context.container.id}`, {
|
||||
timeout: ONE_MINUTE_MS,
|
||||
});
|
||||
});
|
||||
|
||||
test("Update TTL to On (with user entry)", async () => {
|
||||
const ttlOnRadioButton = explorer.frame.getByRole("radio", { name: "ttl-on-option" });
|
||||
await ttlOnRadioButton.click();
|
||||
|
||||
// Enter TTL seconds
|
||||
const ttlInput = explorer.frame.getByTestId("ttl-input");
|
||||
await ttlInput.fill("30000");
|
||||
|
||||
await explorer.commandBarButton(CommandBarButton.Save).click();
|
||||
await expect(explorer.getConsoleMessage()).toContainText(`Successfully updated container ${context.container.id}`, {
|
||||
timeout: ONE_MINUTE_MS,
|
||||
});
|
||||
});
|
||||
|
||||
test("Update TTL to Off", async () => {
|
||||
// By default TTL is set to off so we need to first set it to On
|
||||
const ttlOnNoDefaultRadioButton = explorer.frame.getByRole("radio", { name: "ttl-on-no-default-option" });
|
||||
await ttlOnNoDefaultRadioButton.click();
|
||||
await explorer.commandBarButton(CommandBarButton.Save).click();
|
||||
await expect(explorer.getConsoleMessage()).toContainText(`Successfully updated container ${context.container.id}`, {
|
||||
timeout: ONE_MINUTE_MS,
|
||||
});
|
||||
|
||||
// Set it to Off
|
||||
const ttlOffRadioButton = explorer.frame.getByRole("radio", { name: "ttl-off-option" });
|
||||
await ttlOffRadioButton.click();
|
||||
|
||||
await explorer.commandBarButton(CommandBarButton.Save).click();
|
||||
await expect(explorer.getConsoleMessage()).toContainText(`Successfully updated container ${context.container.id}`, {
|
||||
timeout: ONE_MINUTE_MS,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,116 +1,35 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
|
||||
import { TABLES_CONFIG, deleteContainer, openAndFillCreateContainerPanel } from "../helpers/containerCreationHelpers";
|
||||
|
||||
test("Tables: CRUD", async ({ page }) => {
|
||||
const tableId = generateUniqueName("table");
|
||||
test("Tables CRUD", async ({ page }) => {
|
||||
const tableId = generateUniqueName("table"); // A unique table name IS needed because the database is shared when using Table Storage.
|
||||
|
||||
const explorer = await DataExplorer.open(page, TestAccount.Tables);
|
||||
|
||||
// Create
|
||||
await openAndFillCreateContainerPanel(explorer, TABLES_CONFIG, {
|
||||
databaseId: "TablesDB",
|
||||
containerId: tableId,
|
||||
isAutoscale: true,
|
||||
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||
});
|
||||
await explorer.globalCommandButton("New Table").click();
|
||||
await explorer.whilePanelOpen(
|
||||
"New Table",
|
||||
async (panel, okButton) => {
|
||||
await panel.getByRole("textbox", { name: "Table id, Example Table1" }).fill(tableId);
|
||||
await panel.getByTestId("autoscaleRUInput").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString());
|
||||
await okButton.click();
|
||||
},
|
||||
{ closeTimeout: 5 * 60 * 1000 },
|
||||
);
|
||||
|
||||
const tableNode = await explorer.waitForContainerNode("TablesDB", tableId);
|
||||
await expect(tableNode.element).toBeAttached();
|
||||
|
||||
// Delete table
|
||||
await deleteContainer(explorer, "TablesDB", tableId, "Delete Table");
|
||||
await expect(tableNode.element).not.toBeAttached();
|
||||
});
|
||||
|
||||
test("Tables: New database shared throughput", async ({ page }) => {
|
||||
const tableId = generateUniqueName("table");
|
||||
|
||||
const explorer = await DataExplorer.open(page, TestAccount.Tables);
|
||||
|
||||
await openAndFillCreateContainerPanel(explorer, TABLES_CONFIG, {
|
||||
databaseId: "TablesDB",
|
||||
containerId: tableId,
|
||||
useSharedThroughput: true,
|
||||
});
|
||||
|
||||
const tableNode = await explorer.waitForContainerNode("TablesDB", tableId);
|
||||
await expect(tableNode.element).toBeAttached();
|
||||
|
||||
// Cleanup
|
||||
await deleteContainer(explorer, "TablesDB", tableId, "Delete Table");
|
||||
await expect(tableNode.element).not.toBeAttached();
|
||||
});
|
||||
|
||||
test("Tables: Manual throughput", async ({ page }) => {
|
||||
const tableId = generateUniqueName("table");
|
||||
const manualThroughput = 400;
|
||||
|
||||
const explorer = await DataExplorer.open(page, TestAccount.Tables);
|
||||
|
||||
await openAndFillCreateContainerPanel(explorer, TABLES_CONFIG, {
|
||||
databaseId: "TablesDB",
|
||||
containerId: tableId,
|
||||
isAutoscale: false,
|
||||
throughputValue: manualThroughput,
|
||||
});
|
||||
|
||||
const tableNode = await explorer.waitForContainerNode("TablesDB", tableId);
|
||||
await expect(tableNode.element).toBeAttached();
|
||||
|
||||
// Cleanup
|
||||
await deleteContainer(explorer, "TablesDB", tableId, "Delete Table");
|
||||
await expect(tableNode.element).not.toBeAttached();
|
||||
});
|
||||
|
||||
test("Tables: Multiple tables in TablesDB", async ({ page }) => {
|
||||
const table1Id = generateUniqueName("table");
|
||||
const table2Id = generateUniqueName("table");
|
||||
|
||||
const explorer = await DataExplorer.open(page, TestAccount.Tables);
|
||||
|
||||
// Create first table
|
||||
await openAndFillCreateContainerPanel(explorer, TABLES_CONFIG, {
|
||||
databaseId: "TablesDB",
|
||||
containerId: table1Id,
|
||||
isAutoscale: true,
|
||||
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||
});
|
||||
|
||||
await explorer.waitForContainerNode("TablesDB", table1Id);
|
||||
|
||||
// Create second table
|
||||
await openAndFillCreateContainerPanel(explorer, TABLES_CONFIG, {
|
||||
databaseId: "TablesDB",
|
||||
containerId: table2Id,
|
||||
isAutoscale: true,
|
||||
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||
});
|
||||
|
||||
await explorer.waitForContainerNode("TablesDB", table2Id);
|
||||
|
||||
// Cleanup
|
||||
await deleteContainer(explorer, "TablesDB", table1Id, "Delete Table");
|
||||
await deleteContainer(explorer, "TablesDB", table2Id, "Delete Table");
|
||||
});
|
||||
|
||||
test("Tables: No partition key support", async ({ page }) => {
|
||||
const tableId = generateUniqueName("table");
|
||||
|
||||
const explorer = await DataExplorer.open(page, TestAccount.Tables);
|
||||
|
||||
await openAndFillCreateContainerPanel(explorer, TABLES_CONFIG, {
|
||||
databaseId: "TablesDB",
|
||||
containerId: tableId,
|
||||
isAutoscale: true,
|
||||
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||
});
|
||||
|
||||
const tableNode = await explorer.waitForContainerNode("TablesDB", tableId);
|
||||
await expect(tableNode.element).toBeAttached();
|
||||
|
||||
// Cleanup
|
||||
await deleteContainer(explorer, "TablesDB", tableId, "Delete Table");
|
||||
await tableNode.openContextMenu();
|
||||
await tableNode.contextMenuItem("Delete Table").click();
|
||||
await explorer.whilePanelOpen(
|
||||
"Delete Table",
|
||||
async (panel, okButton) => {
|
||||
await panel.getByRole("textbox", { name: "Confirm by typing the table id" }).fill(tableId);
|
||||
await okButton.click();
|
||||
},
|
||||
{ closeTimeout: 5 * 60 * 1000 },
|
||||
);
|
||||
|
||||
await expect(tableNode.element).not.toBeAttached();
|
||||
});
|
||||
|
||||
@@ -134,7 +134,7 @@ const initTestExplorer = async (): Promise<void> => {
|
||||
);
|
||||
iframe.id = "explorerMenu";
|
||||
iframe.name = "explorer";
|
||||
iframe.setAttribute("data-testid", "DataExplorerFrame");
|
||||
iframe.setAttribute("data-test", "DataExplorerFrame");
|
||||
iframe.classList.add("iframe");
|
||||
iframe.title = "explorer";
|
||||
iframe.src = iframeSrc; // CodeQL [SM03712] Not used in production, only for testing purposes
|
||||
|
||||
Reference in New Issue
Block a user