Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a3413e5
feature: (WIP) loading states for planes
DanielMicrosoft Oct 30, 2025
5e47936
feature: (WIP) with loading component
DanielMicrosoft Oct 30, 2025
3a225f5
feature: (WIP) adding todo comments
DanielMicrosoft Oct 30, 2025
69a08fc
Merge branch 'dev' of https://github.com/Azure/aaz-dev-tools into 345…
DanielMicrosoft Oct 31, 2025
06ad799
feature: refactor loading banner to new component
DanielMicrosoft Oct 31, 2025
f68737e
feature: refactor AsyncOperationBanner to use styled component
DanielMicrosoft Oct 31, 2025
6b04820
feature: add loading handling to WSEditorSwaggerPicker and WSEditorCl…
DanielMicrosoft Oct 31, 2025
bf9cef3
feature: fix contracting text colours
DanielMicrosoft Oct 31, 2025
3a6ce78
refactor: remove redundant comments
DanielMicrosoft Oct 31, 2025
e720808
refactor: remove skipped tests, and adjust to match new api module pa…
DanielMicrosoft Oct 31, 2025
c8dc071
refactor: remove redundant comment
DanielMicrosoft Oct 31, 2025
0776133
refactor: make GenerateDialog use new async pattern
DanielMicrosoft Oct 31, 2025
b58ffbf
refactor: change banner spinner to LinearProgress
DanielMicrosoft Nov 3, 2025
da5a243
refactor: remove loadingMessage string from cli api calls as they are…
DanielMicrosoft Nov 3, 2025
978b23e
refactor: GenerateDialog to use use async banner
DanielMicrosoft Nov 3, 2025
e8c4278
refactor: commandApi.deleteOperation to new pattern
DanielMicrosoft Nov 3, 2025
d827f1a
refactor: fix tests for commandApi.deleteOperation
DanielMicrosoft Nov 3, 2025
d21c985
refactor: update ExampleDialog loading handlers
DanielMicrosoft Nov 3, 2025
43922c7
refactor: update Outputdialog with new loading pattern
DanielMicrosoft Nov 3, 2025
1c91ae5
refactor: update ArgumentDialog to be using new pattern
DanielMicrosoft Nov 3, 2025
deba26f
refactor: update deleteWorkspace usages to new loading pattern
DanielMicrosoft Nov 3, 2025
dc395ba
refactor: update renameWorkspace to use new loading pattern
DanielMicrosoft Nov 3, 2025
6f9721e
refactor: update deleteCommandgroup to use new loading pattern
DanielMicrosoft Nov 3, 2025
52bf0f0
refactor: change loading message for loading resrouces
DanielMicrosoft Nov 3, 2025
f53ca93
refactor: change AddSubcommandDialog to use new loading pattern
DanielMicrosoft Nov 3, 2025
54563ff
refactor: COmmandGroupDialog to new loading pattenr
DanielMicrosoft Nov 3, 2025
c29bdbc
refactor: create uniform loading banner visual component
DanielMicrosoft Nov 4, 2025
d9a3b2e
refactor: update remaining elements to use uniform version of LoadingBar
DanielMicrosoft Nov 5, 2025
ac1c330
refactor: remove comment
DanielMicrosoft Nov 5, 2025
257a2d5
refactor: remove jsx example in useAsyncOperation
DanielMicrosoft Nov 5, 2025
93d6005
Refactor: remove unnecc {}
DanielMicrosoft Nov 5, 2025
02a1c3b
feature: remove lose of workspace creation on click outside
DanielMicrosoft Nov 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/web/src/__tests__/api/workspaceApi.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,18 @@ describe("Workspace API", () => {

describe("deleteWorkspace", () => {
it("should delete workspace by name", async () => {
await expect(workspaceApi.deleteWorkspace("test-workspace-1")).resolves.toBeUndefined();
const operation = workspaceApi.deleteWorkspace;
expect(operation.loadingMessage).toBe("Deleting workspace...");
await expect(operation.fn("test-workspace-1")).resolves.toBeUndefined();
});
});

describe("renameWorkspace", () => {
it("should rename workspace and return new name", async () => {
const result = await workspaceApi.renameWorkspace("/AAZ/Editor/Workspaces/test-workspace-1", "renamed-workspace");
const operation = workspaceApi.renameWorkspace;
expect(operation.loadingMessage).toBe("Renaming workspace...");

const result = await operation.fn("/AAZ/Editor/Workspaces/test-workspace-1", "renamed-workspace");

expect(result).toEqual({
name: "renamed-workspace",
Expand Down
11 changes: 9 additions & 2 deletions src/web/src/__tests__/components/WSEditorClientConfig.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ vi.mock("../../services", () => ({
},
specsApi: {
getPlanes: vi.fn(),
getSwaggerModules: vi.fn(),
getModulesForPlane: {
loadingMessage: "Loading modules for plane...",
fn: vi.fn(),
},
getResourcesForWorkspace: {
loadingMessage: "Loading resources...",
fn: vi.fn(),
},
getResourceProviders: vi.fn(),
getProviderResources: vi.fn(),
},
Expand Down Expand Up @@ -54,7 +61,7 @@ describe("WSEditorClientConfigDialog", () => {
beforeEach(() => {
vi.clearAllMocks();
(specsApi.getPlanes as any).mockResolvedValue(mockPlanes);
(specsApi.getSwaggerModules as any).mockResolvedValue(["storage", "compute"]);
(specsApi.getResourcesForWorkspace.fn as any).mockResolvedValue(["storage", "compute"]);
(specsApi.getResourceProviders as any).mockResolvedValue(mockResourceProviders);
(specsApi.getProviderResources as any).mockResolvedValue(mockProviderResources);
(errorHandlerApi.getErrorMessage as any).mockReturnValue("Mock error message");
Expand Down
35 changes: 29 additions & 6 deletions src/web/src/__tests__/components/WSEditorCommandContent.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,30 @@ describe("WSEditorCommandContent", () => {
vi.clearAllMocks();
vi.mocked(commandApi).getCommand.mockResolvedValue(mockCommand);
vi.mocked(commandApi).getCommandsForResource.mockResolvedValue([mockCommand]);
vi.mocked(commandApi).deleteResource.mockResolvedValue(undefined);
vi.mocked(commandApi).updateCommand.mockResolvedValue(mockCommand);
vi.mocked(commandApi).updateCommandExamples.mockResolvedValue(mockCommand);
vi.mocked(commandApi).updateCommandOutputs.mockResolvedValue(mockCommand);
vi.mocked(commandApi).deleteResource = {
loadingMessage: "Deleting commands...",
fn: vi.fn().mockResolvedValue(undefined),
};
vi.mocked(commandApi).updateCommand = {
loadingMessage: "Updating command...",
fn: vi.fn().mockResolvedValue(mockCommand),
};
vi.mocked(commandApi).renameCommand = {
loadingMessage: "Renaming command...",
fn: vi.fn().mockResolvedValue(mockCommand),
};
vi.mocked(commandApi).updateCommandExamples = {
loadingMessage: "Updating command examples...",
fn: vi.fn().mockResolvedValue(mockCommand),
};
vi.mocked(commandApi).generateSwaggerExamples = {
loadingMessage: "Generating examples from OpenAPI...",
fn: vi.fn().mockResolvedValue([]),
};
vi.mocked(commandApi).updateCommandOutputs = {
loadingMessage: "Updating command outputs...",
fn: vi.fn().mockResolvedValue(mockCommand),
};
});

describe("Core Rendering", () => {
Expand Down Expand Up @@ -470,7 +490,7 @@ describe("WSEditorCommandContent", () => {

it("handles example dialog close with changes", async () => {
const updatedCommand = { ...mockCommand, version: "2.0" };
vi.mocked(commandApi).updateCommandExamples.mockResolvedValue(updatedCommand);
(vi.mocked(commandApi).updateCommandExamples.fn as any).mockResolvedValue(updatedCommand);

render(<WSEditorCommandContent {...defaultProps} />);

Expand Down Expand Up @@ -500,7 +520,10 @@ describe("WSEditorCommandContent", () => {
});

it("handles delete dialog confirmation", async () => {
vi.mocked(commandApi).deleteResource.mockResolvedValue(undefined);
vi.mocked(commandApi).deleteResource = {
loadingMessage: "Deleting commands...",
fn: vi.fn().mockResolvedValue(undefined),
};

render(<WSEditorCommandContent {...defaultProps} />);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { render, screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { vi } from "vitest";
import WSEditorCommandGroupContent from "../../views/workspace/components/WSEditorCommandGroupContent/WSEditorCommandGroupContent";
import * as commandApi from "../../services/commandApi";
import { commandApi } from "../../services";

interface CommandGroup {
id: string;
Expand All @@ -15,10 +15,28 @@ interface CommandGroup {
canDelete: boolean;
}

vi.mock("../../services/commandApi");
vi.mock("../../services/errorHandlerApi");
vi.mock("../../services", () => ({
commandApi: {
deleteCommandGroup: {
loadingMessage: "Deleting command group...",
fn: vi.fn(),
},
updateCommandGroup: {
loadingMessage: "Updating command group...",
fn: vi.fn(),
},
renameCommandGroup: {
loadingMessage: "Renaming command group...",
fn: vi.fn(),
},
},
errorHandlerApi: {
getErrorMessage: vi.fn(),
},
}));

const mockCommandApi = commandApi as any;

describe("WSEditorCommandGroupContent", () => {
const mockWorkspaceUrl = "https://test-workspace.com/workspace/ws1";

Expand All @@ -37,6 +55,21 @@ describe("WSEditorCommandGroupContent", () => {

beforeEach(() => {
vi.clearAllMocks();
mockCommandApi.deleteCommandGroup.fn.mockResolvedValue(undefined);
mockCommandApi.updateCommandGroup.fn.mockResolvedValue({
id: "updated-group-id",
names: ["updated-group"],
stage: "Stable",
help: { short: "Updated help text" },
canDelete: true,
});
mockCommandApi.renameCommandGroup.fn.mockResolvedValue({
id: "renamed-group-id",
names: ["renamed-group"],
stage: "Stable",
help: { short: "Test help" },
canDelete: true,
});
});

describe("Core Rendering", () => {
Expand Down Expand Up @@ -160,8 +193,7 @@ describe("WSEditorCommandGroupContent", () => {
});
});

it.skip("saves changes and updates command group", async () => {
// @NOTE: will change approach once mocking setup changes
it("saves changes and updates command group", async () => {
const user = userEvent.setup();

render(
Expand All @@ -183,7 +215,7 @@ describe("WSEditorCommandGroupContent", () => {
await user.click(saveButton);

await waitFor(() =>
expect(mockCommandApi.updateCommandGroup).toHaveBeenCalledWith(
expect(mockCommandApi.updateCommandGroup.fn).toHaveBeenCalledWith(
expect.stringContaining(mockWorkspaceUrl),
expect.objectContaining({
help: expect.objectContaining({ short: "Updated help text" }),
Expand Down Expand Up @@ -270,7 +302,7 @@ describe("WSEditorCommandGroupContent", () => {
await user.click(confirmDeleteButton);

await waitFor(() => {
expect(mockCommandApi.deleteCommandGroup).toHaveBeenCalled();
expect(mockCommandApi.deleteCommandGroup.fn).toHaveBeenCalled();
expect(mockOnUpdateCommandGroup).toHaveBeenCalledWith(null);
});
});
Expand All @@ -282,7 +314,7 @@ describe("WSEditorCommandGroupContent", () => {
const user = userEvent.setup();
const mockError = new Error("Update failed");

mockCommandApi.updateCommandGroup.mockRejectedValue(mockError);
mockCommandApi.updateCommandGroup.fn.mockRejectedValue(mockError);

render(
<WSEditorCommandGroupContent
Expand All @@ -297,14 +329,14 @@ describe("WSEditorCommandGroupContent", () => {
await user.click(editButton);

await waitFor(() => {
expect(screen.getByText("Edit Command Group")).toBeInTheDocument();
expect(screen.getByText("Command Group")).toBeInTheDocument();
});

const saveButton = screen.getByRole("button", { name: /save/i });
await user.click(saveButton);

await waitFor(() => {
expect(mockCommandApi.updateCommandGroup).toHaveBeenCalled();
expect(mockCommandApi.updateCommandGroup.fn).toHaveBeenCalled();
});
});

Expand All @@ -313,7 +345,7 @@ describe("WSEditorCommandGroupContent", () => {
const user = userEvent.setup();
const mockError = new Error("Delete failed");

mockCommandApi.deleteCommandGroup.mockRejectedValue(mockError);
mockCommandApi.deleteCommandGroup.fn.mockRejectedValue(mockError);

render(
<WSEditorCommandGroupContent
Expand All @@ -335,7 +367,7 @@ describe("WSEditorCommandGroupContent", () => {
await user.click(confirmDeleteButton);

await waitFor(() => {
expect(mockCommandApi.deleteCommandGroup).toHaveBeenCalled();
expect(mockCommandApi.deleteCommandGroup.fn).toHaveBeenCalled();
});
});

Expand Down
80 changes: 5 additions & 75 deletions src/web/src/__tests__/components/WSEditorSwaggerPicker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@ describe("WSEditorSwaggerPicker", () => {
vi.mocked(workspaceApi).addSwaggerResources.mockResolvedValue(undefined);
vi.mocked(workspaceApi).addTypespecResources.mockResolvedValue(undefined);

vi.mocked(specsApi).getSwaggerModules.mockResolvedValue(mockModules);
vi.mocked(specsApi).getResourcesForWorkspace = {
loadingMessage: "Loading resources...",
fn: vi.fn().mockResolvedValue(mockModules),
};
vi.mocked(specsApi).getResourceProvidersWithType.mockResolvedValue(mockResourceProviders);
vi.mocked(specsApi).getProviderResources.mockResolvedValue(mockResources);
vi.mocked(specsApi).filterResourcesByPlane.mockResolvedValue({ resources: mockResources });
Expand Down Expand Up @@ -126,7 +129,7 @@ describe("WSEditorSwaggerPicker", () => {
render(<WSEditorSwaggerPicker {...defaultProps} />);

await waitFor(() => {
expect(vi.mocked(specsApi).getSwaggerModules).toHaveBeenCalledWith("ResourceManagement");
expect(vi.mocked(specsApi).getResourcesForWorkspace.fn).toHaveBeenCalledWith("ResourceManagement");
});
});

Expand Down Expand Up @@ -686,79 +689,6 @@ describe("WSEditorSwaggerPicker", () => {
expect(onCloseMock).toHaveBeenCalledWith(false);
});
});

describe("Error Handling", () => {
it.skip("displays error when swagger modules fail to load", async () => {
// @NOTE: will address once loading issues have been addressed
vi.mocked(specsApi).getSwaggerModules.mockRejectedValue(new Error("Failed to load modules"));

render(<WSEditorSwaggerPicker {...defaultProps} />);

await waitFor(() => {
expect(screen.getByText(/ResponseError/)).toBeInTheDocument();
});
});

it.skip("displays error when resource providers fail to load", async () => {
// @NOTE: will address once loading issues have been addressed
vi.mocked(specsApi).getResourceProvidersWithType.mockRejectedValue(new Error("Failed to load providers"));

render(<WSEditorSwaggerPicker {...defaultProps} />);

await waitFor(() => {
expect(screen.getByText(/ResponseError/)).toBeInTheDocument();
});
});

it.skip("displays error when resources fail to load", async () => {
// @NOTE: will address once loading issues have been addressed
vi.mocked(specsApi).getProviderResources.mockRejectedValue(new Error("Failed to load resources"));

render(<WSEditorSwaggerPicker {...defaultProps} />);

await waitFor(() => {
expect(screen.getByText(/ResponseError/)).toBeInTheDocument();
});
});

it.skip("displays error when submission fails", async () => {
// @NOTE: will address once loading issues have been addressed
vi.mocked(workspaceApi).addSwaggerResources.mockRejectedValue(new Error("Submission failed"));

render(<WSEditorSwaggerPicker {...defaultProps} />);

await waitFor(() => {
const resourceCheckbox = screen.getAllByRole("checkbox")[1];
fireEvent.click(resourceCheckbox);
});

const submitButton = screen.getByRole("button", { name: /submit/i });
fireEvent.click(submitButton);

await waitFor(() => {
expect(screen.getByText(/ResponseError/)).toBeInTheDocument();
});
});

it.skip("allows dismissing error messages", async () => {
// @NOTE: will address once loading issues have been addressed
vi.mocked(specsApi).getSwaggerModules.mockRejectedValue(new Error("Failed to load modules"));

render(<WSEditorSwaggerPicker {...defaultProps} />);

await waitFor(() => {
const errorAlert = screen.getByText(/ResponseError/);
expect(errorAlert).toBeInTheDocument();
});

const closeErrorButton = screen.getByLabelText(/close/i);
fireEvent.click(closeErrorButton);

await waitFor(() => {
expect(screen.queryByText(/ResponseError/)).not.toBeInTheDocument();
});
});
});
});

describe("SwaggerItemSelector", () => {
Expand Down
21 changes: 15 additions & 6 deletions src/web/src/__tests__/components/WorkspaceSelector.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -281,20 +281,29 @@ describe("Workspace Management", () => {
});

it("should delete workspace successfully", async () => {
(workspaceApi.deleteWorkspace as any).mockResolvedValue(undefined);
const mockOperation = {
loadingMessage: "Deleting workspace...",
fn: vi.fn().mockResolvedValue(undefined),
};
(workspaceApi.deleteWorkspace as any) = mockOperation;

await workspaceApi.deleteWorkspace("test-workspace-1");
await workspaceApi.deleteWorkspace.fn("test-workspace-1");

expect(workspaceApi.deleteWorkspace).toHaveBeenCalledWith("test-workspace-1");
expect(workspaceApi.deleteWorkspace.fn).toHaveBeenCalledWith("test-workspace-1");
expect(workspaceApi.deleteWorkspace.loadingMessage).toBe("Deleting workspace...");
});

it("should rename workspace successfully", async () => {
const expectedResult = { name: "renamed-workspace" };
(workspaceApi.renameWorkspace as any).mockResolvedValue(expectedResult);
const mockFn = vi.fn().mockResolvedValue(expectedResult);
(workspaceApi.renameWorkspace as any) = {
loadingMessage: "Renaming workspace...",
fn: mockFn,
};

const result = await workspaceApi.renameWorkspace("/workspace/test-workspace-1", "renamed-workspace");
const result = await workspaceApi.renameWorkspace.fn("/workspace/test-workspace-1", "renamed-workspace");

expect(workspaceApi.renameWorkspace).toHaveBeenCalledWith("/workspace/test-workspace-1", "renamed-workspace");
expect(mockFn).toHaveBeenCalledWith("/workspace/test-workspace-1", "renamed-workspace");
expect(result).toEqual(expectedResult);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ describe("WSEditorClientConfigDialog - Integration", () => {
});

describe("Complete User Workflows", () => {
it("should handle user inputs for relevant fields", async () => {
it("should handle user inputs for relevant fields", { timeout: 10000 }, async () => {
const user = userEvent.setup();
render(<WSEditorClientConfigDialog workspaceUrl={mockWorkspaceUrl} open={true} onClose={mockOnClose} />);

Expand Down
Loading