mirror of https://github.com/coder/coder.git
316 lines
12 KiB
TypeScript
316 lines
12 KiB
TypeScript
import { screen, waitFor, within } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { HttpResponse, http } from "msw";
|
|
import * as API from "api/api";
|
|
import type { Workspace } from "api/typesGenerated";
|
|
import {
|
|
MockStoppedWorkspace,
|
|
MockWorkspace,
|
|
MockDormantWorkspace,
|
|
MockDormantOutdatedWorkspace,
|
|
MockOutdatedWorkspace,
|
|
MockRunningOutdatedWorkspace,
|
|
MockWorkspacesResponse,
|
|
} from "testHelpers/entities";
|
|
import {
|
|
renderWithAuth,
|
|
waitForLoaderToBeRemoved,
|
|
} from "testHelpers/renderHelpers";
|
|
import { server } from "testHelpers/server";
|
|
import * as CreateDayString from "utils/createDayString";
|
|
import WorkspacesPage from "./WorkspacesPage";
|
|
|
|
describe("WorkspacesPage", () => {
|
|
beforeEach(() => {
|
|
// Mocking the dayjs module within the createDayString file
|
|
const mock = jest.spyOn(CreateDayString, "createDayString");
|
|
mock.mockImplementation(() => "a minute ago");
|
|
});
|
|
|
|
it("renders an empty workspaces page", async () => {
|
|
// Given
|
|
server.use(
|
|
http.get("/api/v2/workspaces", async () => {
|
|
return HttpResponse.json({ workspaces: [], count: 0 });
|
|
}),
|
|
);
|
|
|
|
// When
|
|
renderWithAuth(<WorkspacesPage />);
|
|
|
|
// Then
|
|
await screen.findByText("Create a workspace");
|
|
});
|
|
|
|
it("renders a filled workspaces page", async () => {
|
|
renderWithAuth(<WorkspacesPage />);
|
|
await screen.findByText(`${MockWorkspace.name}1`);
|
|
const templateDisplayNames = await screen.findAllByText(
|
|
`${MockWorkspace.template_display_name}`,
|
|
);
|
|
expect(templateDisplayNames).toHaveLength(MockWorkspacesResponse.count);
|
|
});
|
|
|
|
it("deletes only the selected workspaces", async () => {
|
|
const workspaces = [
|
|
{ ...MockWorkspace, id: "1" },
|
|
{ ...MockWorkspace, id: "2" },
|
|
{ ...MockWorkspace, id: "3" },
|
|
];
|
|
jest
|
|
.spyOn(API, "getWorkspaces")
|
|
.mockResolvedValue({ workspaces, count: workspaces.length });
|
|
const deleteWorkspace = jest.spyOn(API, "deleteWorkspace");
|
|
const user = userEvent.setup();
|
|
renderWithAuth(<WorkspacesPage />);
|
|
await waitForLoaderToBeRemoved();
|
|
|
|
await user.click(getWorkspaceCheckbox(workspaces[0]));
|
|
await user.click(getWorkspaceCheckbox(workspaces[1]));
|
|
|
|
await user.click(screen.getByRole("button", { name: /actions/i }));
|
|
const deleteButton = await screen.findByText(/delete/i);
|
|
await user.click(deleteButton);
|
|
|
|
// The button changes its text, and advances the content of the modal,
|
|
// but it is technically the same button being clicked 3 times.
|
|
const confirmButton = await screen.findByTestId("confirm-button");
|
|
await user.click(confirmButton);
|
|
await user.click(confirmButton);
|
|
await user.click(confirmButton);
|
|
|
|
await waitFor(() => {
|
|
expect(deleteWorkspace).toHaveBeenCalledTimes(2);
|
|
});
|
|
expect(deleteWorkspace).toHaveBeenCalledWith(workspaces[0].id);
|
|
expect(deleteWorkspace).toHaveBeenCalledWith(workspaces[1].id);
|
|
});
|
|
|
|
describe("batch update", () => {
|
|
it("ignores up-to-date workspaces", async () => {
|
|
const workspaces = [
|
|
{ ...MockWorkspace, id: "1" }, // running, not outdated. no warning.
|
|
{ ...MockDormantWorkspace, id: "2" }, // dormant, not outdated. no warning.
|
|
{ ...MockOutdatedWorkspace, id: "3" },
|
|
{ ...MockOutdatedWorkspace, id: "4" },
|
|
];
|
|
jest
|
|
.spyOn(API, "getWorkspaces")
|
|
.mockResolvedValue({ workspaces, count: workspaces.length });
|
|
const updateWorkspace = jest.spyOn(API, "updateWorkspace");
|
|
const user = userEvent.setup();
|
|
renderWithAuth(<WorkspacesPage />);
|
|
await waitForLoaderToBeRemoved();
|
|
|
|
for (const workspace of workspaces) {
|
|
await user.click(getWorkspaceCheckbox(workspace));
|
|
}
|
|
|
|
await user.click(screen.getByRole("button", { name: /actions/i }));
|
|
const updateButton = await screen.findByText(/update/i);
|
|
await user.click(updateButton);
|
|
|
|
// One click: no running workspaces warning, no dormant workspaces warning.
|
|
// There is a running workspace and a dormant workspace selected, but they
|
|
// are not outdated.
|
|
const confirmButton = await screen.findByTestId("confirm-button");
|
|
const dialog = await screen.findByRole("dialog");
|
|
expect(dialog).toHaveTextContent(/used by/i);
|
|
await user.click(confirmButton);
|
|
|
|
// `workspaces[0]` was up-to-date, and running
|
|
// `workspaces[1]` was dormant
|
|
await waitFor(() => {
|
|
expect(updateWorkspace).toHaveBeenCalledTimes(2);
|
|
});
|
|
expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2]);
|
|
expect(updateWorkspace).toHaveBeenCalledWith(workspaces[3]);
|
|
});
|
|
|
|
it("warns about and updates running workspaces", async () => {
|
|
const workspaces = [
|
|
{ ...MockRunningOutdatedWorkspace, id: "1" },
|
|
{ ...MockOutdatedWorkspace, id: "2" },
|
|
{ ...MockOutdatedWorkspace, id: "3" },
|
|
];
|
|
jest
|
|
.spyOn(API, "getWorkspaces")
|
|
.mockResolvedValue({ workspaces, count: workspaces.length });
|
|
const updateWorkspace = jest.spyOn(API, "updateWorkspace");
|
|
const user = userEvent.setup();
|
|
renderWithAuth(<WorkspacesPage />);
|
|
await waitForLoaderToBeRemoved();
|
|
|
|
for (const workspace of workspaces) {
|
|
await user.click(getWorkspaceCheckbox(workspace));
|
|
}
|
|
|
|
await user.click(screen.getByRole("button", { name: /actions/i }));
|
|
const updateButton = await screen.findByText(/update/i);
|
|
await user.click(updateButton);
|
|
|
|
// Two clicks: 1 running workspace, no dormant workspaces warning.
|
|
const confirmButton = await screen.findByTestId("confirm-button");
|
|
const dialog = await screen.findByRole("dialog");
|
|
expect(dialog).toHaveTextContent(/1 running workspace/i);
|
|
await user.click(confirmButton);
|
|
expect(dialog).toHaveTextContent(/used by/i);
|
|
await user.click(confirmButton);
|
|
|
|
await waitFor(() => {
|
|
expect(updateWorkspace).toHaveBeenCalledTimes(3);
|
|
});
|
|
expect(updateWorkspace).toHaveBeenCalledWith(workspaces[0]);
|
|
expect(updateWorkspace).toHaveBeenCalledWith(workspaces[1]);
|
|
expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2]);
|
|
});
|
|
|
|
it("warns about and ignores dormant workspaces", async () => {
|
|
const workspaces = [
|
|
{ ...MockDormantOutdatedWorkspace, id: "1" },
|
|
{ ...MockOutdatedWorkspace, id: "2" },
|
|
{ ...MockOutdatedWorkspace, id: "3" },
|
|
];
|
|
jest
|
|
.spyOn(API, "getWorkspaces")
|
|
.mockResolvedValue({ workspaces, count: workspaces.length });
|
|
const updateWorkspace = jest.spyOn(API, "updateWorkspace");
|
|
const user = userEvent.setup();
|
|
renderWithAuth(<WorkspacesPage />);
|
|
await waitForLoaderToBeRemoved();
|
|
|
|
for (const workspace of workspaces) {
|
|
await user.click(getWorkspaceCheckbox(workspace));
|
|
}
|
|
|
|
await user.click(screen.getByRole("button", { name: /actions/i }));
|
|
const updateButton = await screen.findByText(/update/i);
|
|
await user.click(updateButton);
|
|
|
|
// Two clicks: no running workspaces warning, 1 dormant workspace.
|
|
const confirmButton = await screen.findByTestId("confirm-button");
|
|
const dialog = await screen.findByRole("dialog");
|
|
expect(dialog).toHaveTextContent(/dormant/i);
|
|
await user.click(confirmButton);
|
|
expect(dialog).toHaveTextContent(/used by/i);
|
|
await user.click(confirmButton);
|
|
|
|
// `workspaces[0]` was dormant
|
|
await waitFor(() => {
|
|
expect(updateWorkspace).toHaveBeenCalledTimes(2);
|
|
});
|
|
expect(updateWorkspace).toHaveBeenCalledWith(workspaces[1]);
|
|
expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2]);
|
|
});
|
|
|
|
it("warns about running workspaces and then dormant workspaces", async () => {
|
|
const workspaces = [
|
|
{ ...MockRunningOutdatedWorkspace, id: "1" },
|
|
{ ...MockDormantOutdatedWorkspace, id: "2" },
|
|
{ ...MockOutdatedWorkspace, id: "3" },
|
|
{ ...MockOutdatedWorkspace, id: "4" },
|
|
{ ...MockWorkspace, id: "5" },
|
|
];
|
|
jest
|
|
.spyOn(API, "getWorkspaces")
|
|
.mockResolvedValue({ workspaces, count: workspaces.length });
|
|
const updateWorkspace = jest.spyOn(API, "updateWorkspace");
|
|
const user = userEvent.setup();
|
|
renderWithAuth(<WorkspacesPage />);
|
|
await waitForLoaderToBeRemoved();
|
|
|
|
for (const workspace of workspaces) {
|
|
await user.click(getWorkspaceCheckbox(workspace));
|
|
}
|
|
|
|
await user.click(screen.getByRole("button", { name: /actions/i }));
|
|
const updateButton = await screen.findByText(/update/i);
|
|
await user.click(updateButton);
|
|
|
|
// Three clicks: 1 running workspace, 1 dormant workspace.
|
|
const confirmButton = await screen.findByTestId("confirm-button");
|
|
const dialog = await screen.findByRole("dialog");
|
|
expect(dialog).toHaveTextContent(/1 running workspace/i);
|
|
await user.click(confirmButton);
|
|
expect(dialog).toHaveTextContent(/dormant/i);
|
|
await user.click(confirmButton);
|
|
expect(dialog).toHaveTextContent(/used by/i);
|
|
await user.click(confirmButton);
|
|
|
|
// `workspaces[1]` was dormant, and `workspaces[4]` was up-to-date
|
|
await waitFor(() => {
|
|
expect(updateWorkspace).toHaveBeenCalledTimes(3);
|
|
});
|
|
expect(updateWorkspace).toHaveBeenCalledWith(workspaces[0]);
|
|
expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2]);
|
|
expect(updateWorkspace).toHaveBeenCalledWith(workspaces[3]);
|
|
});
|
|
});
|
|
|
|
it("stops only the running and selected workspaces", async () => {
|
|
const workspaces = [
|
|
{ ...MockWorkspace, id: "1" },
|
|
{ ...MockWorkspace, id: "2" },
|
|
{ ...MockWorkspace, id: "3" },
|
|
];
|
|
jest
|
|
.spyOn(API, "getWorkspaces")
|
|
.mockResolvedValue({ workspaces, count: workspaces.length });
|
|
const stopWorkspace = jest.spyOn(API, "stopWorkspace");
|
|
const user = userEvent.setup();
|
|
renderWithAuth(<WorkspacesPage />);
|
|
await waitForLoaderToBeRemoved();
|
|
|
|
await user.click(getWorkspaceCheckbox(workspaces[0]));
|
|
await user.click(getWorkspaceCheckbox(workspaces[1]));
|
|
await user.click(screen.getByRole("button", { name: /actions/i }));
|
|
const stopButton = await screen.findByText(/stop/i);
|
|
await user.click(stopButton);
|
|
|
|
await waitFor(() => {
|
|
expect(stopWorkspace).toHaveBeenCalledTimes(2);
|
|
});
|
|
expect(stopWorkspace).toHaveBeenCalledWith(workspaces[0].id);
|
|
expect(stopWorkspace).toHaveBeenCalledWith(workspaces[1].id);
|
|
});
|
|
|
|
it("starts only the stopped and selected workspaces", async () => {
|
|
const workspaces = [
|
|
{ ...MockStoppedWorkspace, id: "1" },
|
|
{ ...MockStoppedWorkspace, id: "2" },
|
|
{ ...MockStoppedWorkspace, id: "3" },
|
|
];
|
|
jest
|
|
.spyOn(API, "getWorkspaces")
|
|
.mockResolvedValue({ workspaces, count: workspaces.length });
|
|
const startWorkspace = jest.spyOn(API, "startWorkspace");
|
|
const user = userEvent.setup();
|
|
renderWithAuth(<WorkspacesPage />);
|
|
await waitForLoaderToBeRemoved();
|
|
|
|
await user.click(getWorkspaceCheckbox(workspaces[0]));
|
|
await user.click(getWorkspaceCheckbox(workspaces[1]));
|
|
await user.click(screen.getByRole("button", { name: /actions/i }));
|
|
const startButton = await screen.findByText(/start/i);
|
|
await user.click(startButton);
|
|
|
|
await waitFor(() => {
|
|
expect(startWorkspace).toHaveBeenCalledTimes(2);
|
|
});
|
|
expect(startWorkspace).toHaveBeenCalledWith(
|
|
workspaces[0].id,
|
|
MockStoppedWorkspace.latest_build.template_version_id,
|
|
);
|
|
expect(startWorkspace).toHaveBeenCalledWith(
|
|
workspaces[1].id,
|
|
MockStoppedWorkspace.latest_build.template_version_id,
|
|
);
|
|
});
|
|
});
|
|
|
|
const getWorkspaceCheckbox = (workspace: Workspace) => {
|
|
return within(screen.getByTestId(`checkbox-${workspace.id}`)).getByRole(
|
|
"checkbox",
|
|
);
|
|
};
|