+ ),
+}));
+
+describe("EndpointDuplicateOptions", () => {
+ let defaultProps: EndpointDuplicateOptions["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ projects: [
+ { id: "1", name: "admin" },
+ { id: "2", name: "admin2" },
+ ],
+ selectedProjectId: "2",
+ duplicating: false,
+ onCancelClick: jest.fn(),
+ onDuplicateClick: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render(
+
+ );
+ expect(getByText("Duplicate To Project - 2")).toBeTruthy();
+ });
+
+ it("handles submit on Enter key press", () => {
+ render();
+
+ fireEvent.keyDown(document, { key: "Enter" });
+
+ expect(defaultProps.onDuplicateClick).toHaveBeenCalledWith("2");
+ });
+
+ it("shows duplicating status", () => {
+ const { getByText } = render(
+
+ );
+
+ expect(getByText("Duplicating Endpoint")).toBeTruthy();
+ });
+
+ it("changes project", () => {
+ const { getByTestId } = render(
+
+ );
+
+ fireEvent.click(getByTestId("FieldInput__Wrapper"));
+ expect(getByTestId("FieldInput__Wrapper").textContent).toBe(
+ "Duplicate To Project - 1"
+ );
+ });
+
+ it("handles duplicate click", () => {
+ const { getByText } = render(
+
+ );
+
+ fireEvent.click(getByText("Duplicate"));
+ expect(defaultProps.onDuplicateClick).toHaveBeenCalledWith("2");
+ });
+});
diff --git a/src/components/modules/EndpointModule/EndpointDuplicateOptions/test.tsx b/src/components/modules/EndpointModule/EndpointDuplicateOptions/test.tsx
deleted file mode 100644
index 59883945..00000000
--- a/src/components/modules/EndpointModule/EndpointDuplicateOptions/test.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import sinon from "sinon";
-import TW from "@src/utils/TestWrapper";
-import type { Project } from "@src/@types/Project";
-import EndpointDuplicateOptions from ".";
-
-type Props = {
- projects: Project[];
- selectedProjectId: string;
- duplicating: boolean;
- onCancelClick: () => void;
- onDuplicateClick: (projectId: string) => void;
-};
-
-const wrap = (props: Props) =>
- new TW(shallow(), "edOptions");
-const projects: Project[] = [
- { id: "project-1", name: "Project 1" },
- { id: "project-2", name: "Project 2" },
-];
-describe("EndpointDuplicateOptions Component", () => {
- it("renders projects", () => {
- const wrapper = wrap({
- projects,
- selectedProjectId: "project-2",
- duplicating: false,
- onCancelClick: () => {},
- onDuplicateClick: () => {},
- });
- expect(wrapper.find("field-project").prop("enum")[1].name).toBe(
- projects[1].name
- );
- expect(wrapper.find("field-project").prop("value")).toBe("project-2");
- expect(wrapper.find("loading").length).toBe(0);
- });
-
- it("dispatches duplicate", () => {
- const onDuplicateClick = sinon.spy();
- const wrapper = wrap({
- projects,
- selectedProjectId: "project-2",
- duplicating: false,
- onCancelClick: () => {},
- onDuplicateClick,
- });
- wrapper.find("duplicateButton").click();
- expect(onDuplicateClick.args[0][0]).toBe("project-2");
- });
-
- it("renders loading", () => {
- const wrapper = wrap({
- projects,
- selectedProjectId: "project-2",
- duplicating: true,
- onCancelClick: () => {},
- onDuplicateClick: () => {},
- });
- expect(wrapper.find("loading").length).toBe(1);
- });
-});
diff --git a/src/components/modules/EndpointModule/EndpointListItem/EndpointListItem.spec.tsx b/src/components/modules/EndpointModule/EndpointListItem/EndpointListItem.spec.tsx
new file mode 100644
index 00000000..e01a2327
--- /dev/null
+++ b/src/components/modules/EndpointModule/EndpointListItem/EndpointListItem.spec.tsx
@@ -0,0 +1,91 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { Endpoint } from "@src/@types/Endpoint";
+import { render } from "@testing-library/react";
+import TestUtils from "@tests/TestUtils";
+
+import EndpointListItem from "./EndpointListItem";
+
+jest.mock("@src/components/modules/EndpointModule/EndpointLogos", () => ({
+ __esModule: true,
+ default: (props: any) => (
+
{props.endpoint}
+ ),
+}));
+
+const OPENSTACK_ENDPOINT: Endpoint = {
+ name: "Openstack",
+ type: "openstack",
+ id: "1",
+ description: "openstack description",
+ created_at: new Date().toISOString(),
+ mapped_regions: [],
+ connection_info: {},
+};
+
+describe("EndpointListItem", () => {
+ let defaultProps: EndpointListItem["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ item: OPENSTACK_ENDPOINT,
+ onClick: jest.fn(),
+ selected: false,
+ onSelectedChange: jest.fn(),
+ getUsage: jest.fn().mockImplementation(() => ({
+ replicasCount: 3,
+ migrationsCount: 2,
+ })),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText, getByTestId } = render(
+
+ );
+ expect(getByText(OPENSTACK_ENDPOINT.name)).toBeTruthy();
+ expect(getByText(OPENSTACK_ENDPOINT.description)).toBeTruthy();
+ expect(getByTestId("EndpointLogos").textContent).toBe(
+ OPENSTACK_ENDPOINT.type
+ );
+ });
+
+ it("renders usage", () => {
+ const { getByText } = render();
+ expect(defaultProps.getUsage).toHaveBeenCalledWith(OPENSTACK_ENDPOINT);
+ expect(getByText("2 migrations, 3 replicas")).toBeTruthy();
+ });
+
+ it("renders N/A when no description", () => {
+ const newProps = {
+ ...defaultProps,
+ item: {
+ ...OPENSTACK_ENDPOINT,
+ description: "",
+ },
+ };
+ const { getByText } = render();
+ expect(getByText("N/A")).toBeTruthy();
+ });
+
+ it("renders selected checkbox", () => {
+ render();
+ const checkbox = TestUtils.selectContains("EndpointListItem__Checkbox")!;
+ const style = window.getComputedStyle(checkbox);
+ expect(style.opacity).toBe("1");
+ });
+});
diff --git a/src/components/modules/EndpointModule/EndpointListItem/test.tsx b/src/components/modules/EndpointModule/EndpointListItem/test.tsx
deleted file mode 100644
index d572bdad..00000000
--- a/src/components/modules/EndpointModule/EndpointListItem/test.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import sinon from "sinon";
-import TestWrapper from "@src/utils/TestWrapper";
-import EndpointListItem from ".";
-
-const wrap = props =>
- new TestWrapper(shallow(), "endpointListItem");
-
-describe("EndpointListItem Component", () => {
- it("renders item properties", () => {
- const wrapper = wrap({
- item: { name: "name-to-test", description: "description-to-test" },
- getUsage: () => {
- return {};
- },
- });
- expect(wrapper.findText("name")).toBe("name-to-test");
- expect(wrapper.findText("description")).toBe("description-to-test");
- });
-
- it("renders usage count", () => {
- const wrapper = wrap({
- item: {},
- getUsage: () => {
- return { replicasCount: 12, migrationsCount: 11 };
- },
- });
- expect(wrapper.findText("usageCount")).toBe("11 migrations, 12 replicas");
- });
-
- it("dispatches onClick", () => {
- const onClick = sinon.spy();
- const wrapper = wrap({
- item: { name: "t" },
- getUsage: () => {
- return {};
- },
- onClick,
- });
- wrapper.find("content-t").simulate("click");
- expect(onClick.calledOnce).toBe(true);
- });
-});
diff --git a/src/components/modules/EndpointModule/EndpointLogos/EndpointLogos.spec.tsx b/src/components/modules/EndpointModule/EndpointLogos/EndpointLogos.spec.tsx
new file mode 100644
index 00000000..97dde15e
--- /dev/null
+++ b/src/components/modules/EndpointModule/EndpointLogos/EndpointLogos.spec.tsx
@@ -0,0 +1,58 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+import TestUtils from "@tests/TestUtils";
+
+import EndpointLogos from "./EndpointLogos";
+
+jest.mock("@src/utils/Config", () => ({
+ config: {
+ providerSortPriority: {},
+ providerNames: {
+ openstack: "OpenStack",
+ vmware_vsphere: "VMware vSphere",
+ },
+ },
+}));
+
+describe("EndpointLogos", () => {
+ let defaultProps: EndpointLogos["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ endpoint: "openstack",
+ height: 64,
+ };
+ });
+
+ it("renders without crashing", () => {
+ render();
+ expect(TestUtils.select("EndpointLogos__Logo")).toBeTruthy();
+ });
+
+ it("renders generic logo", () => {
+ const { getByText } = render(
+
+ );
+ expect(getByText("new-endpoint")).toBeTruthy();
+ });
+
+ it("doesn't render for unsupported height", () => {
+ render();
+ expect(TestUtils.select("EndpointLogos__Logo")).toBeFalsy();
+ });
+});
diff --git a/src/components/modules/EndpointModule/EndpointLogos/resources/Generic.spec.tsx b/src/components/modules/EndpointModule/EndpointLogos/resources/Generic.spec.tsx
new file mode 100644
index 00000000..2bc5ce2f
--- /dev/null
+++ b/src/components/modules/EndpointModule/EndpointLogos/resources/Generic.spec.tsx
@@ -0,0 +1,89 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { ThemePalette } from "@src/components/Theme";
+import { render } from "@testing-library/react";
+import TestUtils from "@tests/TestUtils";
+
+import Generic from "./Generic";
+
+describe("Generic", () => {
+ let defaultProps: Generic["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ name: "Generic",
+ size: { w: 64, h: 64 },
+ disabled: false,
+ white: false,
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText(defaultProps.name)).toBeTruthy();
+ });
+
+ it.each`
+ height | expectedFontSize
+ ${32} | ${"14px"}
+ ${42} | ${"18px"}
+ `(
+ "renders with height $height and font size $expectedFontSize",
+ ({ height, expectedFontSize }) => {
+ render();
+ const wrapper = TestUtils.select("Generic__Wrapper")!;
+ const style = window.getComputedStyle(wrapper);
+ expect(style.fontSize).toBe(expectedFontSize);
+ }
+ );
+
+ it.each`
+ height | expectedLogoWidth | expectedLogoHeight
+ ${64} | ${"49px"} | ${"43px"}
+ ${128} | ${"80px"} | ${"70px"}
+ `(
+ "renders with height $height and logo width $expectedLogoWidth and logo height $expectedLogoHeight",
+ ({ height, expectedLogoWidth, expectedLogoHeight }) => {
+ render();
+ const wrapper = TestUtils.select("Generic__Logo")!;
+ const style = window.getComputedStyle(wrapper);
+ expect(style.maxWidth).toBe(expectedLogoWidth);
+ expect(style.maxHeight).toBe(expectedLogoHeight);
+ }
+ );
+
+ it("renders 32px with white color", () => {
+ render();
+ const wrapper = TestUtils.select("Generic__Wrapper")!;
+ const style = window.getComputedStyle(wrapper);
+ expect(style.color).toBe("white");
+ });
+
+ it("renders 128px with disabled color", () => {
+ render();
+ const wrapper = TestUtils.select("Generic__Wrapper")!;
+ const style = window.getComputedStyle(wrapper);
+ expect(TestUtils.rgbToHex(style.color)).toBe(ThemePalette.grayscale[3]);
+ });
+
+ it("doesn't render unsupported size", () => {
+ const { container } = render(
+
+ );
+ expect(container.firstChild).toBeNull();
+ });
+});
diff --git a/src/components/modules/EndpointModule/EndpointLogos/test.tsx b/src/components/modules/EndpointModule/EndpointLogos/test.tsx
deleted file mode 100644
index 55f12ba5..00000000
--- a/src/components/modules/EndpointModule/EndpointLogos/test.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import TestWrapper from "@src/utils/TestWrapper";
-import EndpointLogos from ".";
-
-const wrap = props =>
- new TestWrapper(shallow(), "endpointLogos");
-
-describe("EndpointLogos Component", () => {
- it("renders 32px aws", () => {
- const wrapper = wrap({ height: 32, endpoint: "aws" });
- const logo = wrapper.find("logo");
- expect(logo.prop("url")).toBe("/api/logos/aws/32");
- });
-
- it("renders 128px azure disabled", () => {
- const wrapper = wrap({ height: 128, endpoint: "azure", disabled: true });
- const logo = wrapper.find("logo");
- expect(logo.prop("url")).toBe("/api/logos/azure/128/disabled");
- });
-
- it("renders 64px generic logo", () => {
- const wrapper = wrap({ height: 64, endpoint: "generic" });
- const logo = wrapper.find("genericLogo");
- expect(logo.prop("name")).toBe("generic");
- expect(logo.prop("size").h).toBe(64);
- });
-});
diff --git a/src/components/modules/EndpointModule/EndpointValidation/EndpointValidation.spec.tsx b/src/components/modules/EndpointModule/EndpointValidation/EndpointValidation.spec.tsx
new file mode 100644
index 00000000..2da2dca4
--- /dev/null
+++ b/src/components/modules/EndpointModule/EndpointValidation/EndpointValidation.spec.tsx
@@ -0,0 +1,117 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import DomUtils from "@src/utils/DomUtils";
+import { render } from "@testing-library/react";
+
+import EndpointValidation from "./EndpointValidation";
+
+jest.mock("@src/components/ui/StatusComponents/StatusImage", () => ({
+ __esModule: true,
+ default: (props: any) => (
+
+ ),
+}));
+
+jest.mock("@src/utils/DomUtils", () => ({
+ copyTextToClipboard: jest.fn(),
+}));
+
+describe("EndpointValidation", () => {
+ let defaultProps: EndpointValidation["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ loading: false,
+ validation: {
+ valid: true,
+ message: "",
+ },
+ onCancelClick: jest.fn(),
+ onRetryClick: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText, getByTestId } = render(
+
+ );
+ expect(getByText("Endpoint is Valid")).toBeTruthy();
+ expect(getByTestId("StatusImage").textContent).toBe(
+ "Status: COMPLETED, Loading: false"
+ );
+ });
+
+ it("renders loading", () => {
+ const { getByTestId, getByText } = render(
+
+ );
+ expect(getByTestId("StatusImage").textContent).toBe(
+ "Status: -, Loading: true"
+ );
+ expect(getByText("Validating Endpoint")).toBeTruthy();
+ });
+
+ it("renders failed validation", () => {
+ const { getByTestId, getByText } = render(
+
+ );
+ expect(getByTestId("StatusImage").textContent).toBe(
+ "Status: ERROR, Loading: false"
+ );
+ expect(getByText("connection error")).toBeTruthy();
+ });
+
+ it("renders generic error message", () => {
+ const { getByTestId, getByText } = render(
+
+ );
+ expect(getByTestId("StatusImage").textContent).toBe(
+ "Status: ERROR, Loading: false"
+ );
+ expect(getByText("An unexpected error occurred.")).toBeTruthy();
+ });
+
+ it("copies the error message to clipboard", () => {
+ const { getByText } = render(
+
+ );
+ getByText("connection error").click();
+ expect(DomUtils.copyTextToClipboard).toHaveBeenCalledWith(
+ "connection error"
+ );
+ });
+});
diff --git a/src/components/modules/EndpointModule/EndpointValidation/test.tsx b/src/components/modules/EndpointModule/EndpointValidation/test.tsx
deleted file mode 100644
index 7aacb0d7..00000000
--- a/src/components/modules/EndpointModule/EndpointValidation/test.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import TW from "@src/utils/TestWrapper";
-import EndpointValidation from ".";
-
-const wrap = props =>
- new TW(shallow(), "eValidation");
-
-describe("EndpointValidation Component", () => {
- it("renders loading", () => {
- const wrapper = wrap({ loading: true });
- expect(wrapper.find("status").prop("loading")).toBe(true);
- expect(wrapper.findText("title")).toBe("Validating Endpoint");
- });
-
- it("renders valid", () => {
- const wrapper = wrap({ validation: { valid: true } });
- expect(wrapper.find("status").prop("status")).toBe("COMPLETED");
- expect(wrapper.findText("title")).toBe("Endpoint is Valid");
- });
-
- it("renders failed with default message", () => {
- const wrapper = wrap({ validation: {} });
- expect(wrapper.find("status").prop("status")).toBe("ERROR");
- expect(wrapper.findText("title")).toBe("Validation Failed");
- expect(wrapper.findText("errorMessage")).toBe(
- "An unexpected error occurred."
- );
- });
-
- it("renders failed with custom message", () => {
- const wrapper = wrap({ validation: { message: "custom_message" } });
- expect(wrapper.find("status").prop("status")).toBe("ERROR");
- expect(wrapper.findText("title")).toBe("Validation Failed");
- expect(wrapper.findText("errorMessage")).toBe(
- "custom_message"
- );
- });
-});
diff --git a/src/components/modules/LicenceModule/LicenceModule.spec.tsx b/src/components/modules/LicenceModule/LicenceModule.spec.tsx
new file mode 100644
index 00000000..185e5155
--- /dev/null
+++ b/src/components/modules/LicenceModule/LicenceModule.spec.tsx
@@ -0,0 +1,167 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import { DateTime } from "luxon";
+import React from "react";
+
+import { Licence, LicenceServerStatus } from "@src/@types/Licence";
+import { fireEvent, render, waitFor } from "@testing-library/react";
+import TestUtils from "@tests/TestUtils";
+
+import LicenceModule from "./LicenceModule";
+
+jest.mock("@src/components/ui/StatusComponents/StatusImage", () => ({
+ __esModule: true,
+ default: (props: any) => (
+
+ {props.loading ? "loading" : props.status}
+
+ ),
+}));
+
+const FUTURE_LICENCE: Licence = {
+ applianceId: "test-id",
+ earliestLicenceExpiryDate: DateTime.now().plus({ years: 1 }).toJSDate(),
+ latestLicenceExpiryDate: DateTime.now().plus({ years: 1 }).toJSDate(),
+ currentPerformedReplicas: 5,
+ currentPerformedMigrations: 3,
+ lifetimePerformedMigrations: 4,
+ lifetimePerformedReplicas: 6,
+ currentAvailableReplicas: 10,
+ currentAvailableMigrations: 5,
+ lifetimeAvailableReplicas: 15,
+ lifetimeAvailableMigrations: 10,
+};
+
+const SERVER_STATUS: LicenceServerStatus = {
+ hostname: "test-hostname",
+ multi_appliance: false,
+ supported_licence_versions: ["v2"],
+ server_local_time: DateTime.now().toISO()!,
+};
+
+describe("LicenceModule", () => {
+ let defaultProps: LicenceModule["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ licenceInfo: FUTURE_LICENCE,
+ licenceServerStatus: SERVER_STATUS,
+ licenceError: null,
+ loadingLicenceInfo: false,
+ addMode: false,
+ addingLicence: false,
+ backButtonText: "Back",
+ onAddLicence: jest.fn(),
+ onRequestClose: jest.fn(),
+ onAddModeChange: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ getByText("test-id-licencev2");
+ });
+
+ it("changes to add mode when add button is clicked", () => {
+ const { getByText } = render();
+ getByText("Add Licence").click();
+ expect(defaultProps.onAddModeChange).toHaveBeenCalledWith(true);
+ });
+
+ it("renders add mode", () => {
+ const { getByText } = render();
+ expect(getByText("Drag the Licence file", { exact: false })).toBeTruthy();
+ });
+
+ it("validates invalid licence", async () => {
+ const { getByText } = render();
+ fireEvent.change(document.querySelector("textarea")!, {
+ target: { value: "test" },
+ });
+ await waitFor(() => {
+ const addLicenceButton = getByText("Add Licence");
+ expect(addLicenceButton.hasAttribute("disabled")).toBeTruthy();
+ });
+ });
+
+ it("validates valid licence", async () => {
+ const { getByText } = render();
+ fireEvent.change(document.querySelector("textarea")!, {
+ target: {
+ value: `-----BEGIN CORIOLIS LICENCE-----
+Version: 2.0
+-----END CORIOLIS LICENCE-----`,
+ },
+ });
+ await waitFor(() => {
+ const addLicenceButton = getByText("Add Licence");
+ expect(addLicenceButton.hasAttribute("disabled")).toBeFalsy();
+ });
+ });
+
+ it("shows loading", () => {
+ const { getByTestId } = render(
+
+ );
+ expect(getByTestId("StatusImage").textContent).toBe("loading");
+ });
+
+ it("shows licence expires today", () => {
+ render(
+
+ );
+
+ expect(
+ TestUtils.selectAll("LicenceModule__LicenceRowDescription")[0].textContent
+ ).toContain("today at");
+ });
+
+ it("shows licence expired", () => {
+ render(
+
+ );
+
+ expect(
+ TestUtils.select("LicenceModule__LicenceRowContent")?.textContent
+ ).toContain("Please contact Cloudbase Solutions");
+ });
+
+ it("renders licence error", () => {
+ const { getByText } = render(
+
+ );
+ expect(getByText("test-error")).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/LoginModule/LoginForm/LoginForm.spec.tsx b/src/components/modules/LoginModule/LoginForm/LoginForm.spec.tsx
new file mode 100644
index 00000000..8667d33c
--- /dev/null
+++ b/src/components/modules/LoginModule/LoginForm/LoginForm.spec.tsx
@@ -0,0 +1,93 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import notificationStore from "@src/stores/NotificationStore";
+import { fireEvent, render } from "@testing-library/react";
+import TestUtils from "@tests/TestUtils";
+
+import LoginForm from "./LoginForm";
+
+jest.mock("@src/stores/NotificationStore", () => ({
+ alert: jest.fn(),
+}));
+
+describe("LoginForm", () => {
+ let defaultProps: LoginForm["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ className: "class-custom-name",
+ showUserDomainInput: false,
+ loading: false,
+ loginFailedResponse: null,
+ domain: "default",
+ onDomainChange: jest.fn(),
+ onFormSubmit: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText("Username")).toBeTruthy();
+ expect(getByText("Password")).toBeTruthy();
+ });
+
+ it("submits username and password", () => {
+ render();
+ const userInput = TestUtils.selectAll("TextInput__Input")[0];
+ const passwordInput = TestUtils.selectAll("TextInput__Input")[1];
+ fireEvent.change(userInput, { target: { value: "username" } });
+ fireEvent.change(passwordInput, { target: { value: "password" } });
+ fireEvent.submit(document.querySelector("form")!);
+ expect(defaultProps.onFormSubmit).toHaveBeenCalledWith({
+ username: "username",
+ password: "password",
+ });
+ });
+
+ it("submits domain", () => {
+ render();
+ const domainInput = TestUtils.selectAll("TextInput__Input")[0];
+ expect((domainInput as HTMLInputElement).value).toBe(defaultProps.domain);
+ fireEvent.change(domainInput, { target: { value: "new-domain" } });
+ expect(defaultProps.onDomainChange).toHaveBeenCalledWith("new-domain");
+ });
+
+ it("shows fill all fields error", () => {
+ render();
+ fireEvent.submit(document.querySelector("form")!);
+ expect(notificationStore.alert).toHaveBeenCalledWith(
+ "Please fill in all fields"
+ );
+ });
+
+ it("renders incorrect crediantials message", () => {
+ const { getByText } = render(
+
+ );
+ expect(getByText("Incorrect credentials", { exact: false })).toBeTruthy();
+ });
+
+ it("renders other error message", () => {
+ const { getByText } = render(
+
+ );
+ expect(getByText("other error", { exact: false })).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/LoginModule/LoginForm/LoginForm.tsx b/src/components/modules/LoginModule/LoginForm/LoginForm.tsx
index 04684bca..31c4e8d7 100644
--- a/src/components/modules/LoginModule/LoginForm/LoginForm.tsx
+++ b/src/components/modules/LoginModule/LoginForm/LoginForm.tsx
@@ -87,7 +87,7 @@ type Props = {
className: string;
showUserDomainInput: boolean;
loading: boolean;
- loginFailedResponse: { status: string | number; message?: string };
+ loginFailedResponse: { status: string | number; message?: string } | null;
domain: string;
onDomainChange: (domain: string) => void;
onFormSubmit: (credentials: { username: string; password: string }) => void;
diff --git a/src/components/modules/LoginModule/LoginForm/test.tsx b/src/components/modules/LoginModule/LoginForm/test.tsx
deleted file mode 100644
index 3d407573..00000000
--- a/src/components/modules/LoginModule/LoginForm/test.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import sinon from "sinon";
-import TW from "@src/utils/TestWrapper";
-import LoginForm from ".";
-
-const wrap = props =>
- new TW(shallow(), "loginForm");
-
-describe("LoginForm Component", () => {
- it("renders incorrect credentials", () => {
- const wrapper = wrap({ loginFailedResponse: { status: 401 } });
- expect(
- wrapper.find("errorText").prop("dangerouslySetInnerHTML").__html
- ).toBe("Incorrect credentials. Please try again."); // eslint-disable-line
- });
-
- it("renders server error", () => {
- const wrapper = wrap({ loginFailedResponse: {} });
- expect(
- wrapper.find("errorText").prop("dangerouslySetInnerHTML").__html
- ).toBe(
- "Request failed, there might be a problem with the connection to the server."
- ); // eslint-disable-line
- });
-
- it("submits correct info", () => {
- const onFormSubmit = sinon.spy();
- const wrapper = wrap({ onFormSubmit });
- wrapper
- .find("usernameField")
- .simulate("change", { target: { value: "usr" } });
- wrapper
- .find("passwordField")
- .simulate("change", { target: { value: "pswd" } });
- wrapper.shallow.simulate("submit", { preventDefault: () => {} });
- expect(onFormSubmit.args[0][0].username).toBe("usr");
- expect(onFormSubmit.args[0][0].password).toBe("pswd");
- });
-});
diff --git a/src/components/modules/LoginModule/LoginFormField/test.tsx b/src/components/modules/LoginModule/LoginFormField/test.tsx
deleted file mode 100644
index 94ce93cb..00000000
--- a/src/components/modules/LoginModule/LoginFormField/test.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import sinon from "sinon";
-import TestWrapper from "@src/utils/TestWrapper";
-import LoginFormField from ".";
-
-const wrap = props =>
- new TestWrapper(shallow(), "loginFormField");
-
-describe("LoginFormField Component", () => {
- it("renders with correct label", () => {
- const wrapper = wrap({ label: "Username" });
- expect(wrapper.findText("label")).toBe("Username");
- });
-
- it("dispatches change on input change", () => {
- const onChange = sinon.spy();
- const wrapper = wrap({ label: "Username", onChange });
- wrapper.find("input").simulate("change", { t: "t" });
- expect(onChange.args[0][0].t).toBe("t");
- });
-});
diff --git a/src/components/modules/LoginModule/LoginOptions/LoginOptions.spec.tsx b/src/components/modules/LoginModule/LoginOptions/LoginOptions.spec.tsx
new file mode 100644
index 00000000..baaaa4d7
--- /dev/null
+++ b/src/components/modules/LoginModule/LoginOptions/LoginOptions.spec.tsx
@@ -0,0 +1,59 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+
+import LoginOptions, { Props } from "./LoginOptions";
+
+describe("LoginForm", () => {
+ let defaultProps: Props;
+
+ beforeEach(() => {
+ defaultProps = {
+ buttons: [
+ {
+ id: "google",
+ name: "Google",
+ },
+ {
+ id: "microsoft",
+ name: "Microsoft",
+ },
+ {
+ id: "facebook",
+ name: "Facebook",
+ },
+ {
+ id: "github",
+ name: "GitHub",
+ },
+ ],
+ };
+ });
+
+ it("renders without crashing", () => {
+ render();
+ expect(document.querySelectorAll("div").length).toBe(1);
+ });
+
+ it("renders all buttons", () => {
+ const { getByText } = render();
+ expect(getByText("Sign in with Google")).toBeTruthy();
+ expect(getByText("Sign in with Microsoft")).toBeTruthy();
+ expect(getByText("Sign in with Facebook")).toBeTruthy();
+ expect(getByText("Sign in with GitHub")).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/LoginModule/LoginOptions/LoginOptions.tsx b/src/components/modules/LoginModule/LoginOptions/LoginOptions.tsx
index 751c1688..7817204b 100644
--- a/src/components/modules/LoginModule/LoginOptions/LoginOptions.tsx
+++ b/src/components/modules/LoginModule/LoginOptions/LoginOptions.tsx
@@ -97,7 +97,7 @@ const Logo = styled.div`
margin: 0 8px 0 8px;
${props => buttonStyle(props.id, true)}
`;
-type Props = {
+export type Props = {
buttons?: { name: string; id: string }[];
};
const LoginOptions = (props: Props) => {
diff --git a/src/components/modules/LoginModule/LoginOptions/test.tsx b/src/components/modules/LoginModule/LoginOptions/test.tsx
deleted file mode 100644
index 04464ac3..00000000
--- a/src/components/modules/LoginModule/LoginOptions/test.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import TestWrapper from "@src/utils/TestWrapper";
-import LoginOptions from ".";
-
-const wrap = props =>
- new TestWrapper(shallow(), "loginOptions");
-
-const buttons = [
- {
- name: "Google",
- id: "google",
- url: "",
- },
- {
- name: "Microsoft",
- id: "microsoft",
- url: "",
- },
- {
- name: "Facebook",
- id: "facebook",
- url: "",
- },
- {
- name: "GitHub",
- id: "github",
- url: "",
- },
-];
-
-describe("LoginOptions Component", () => {
- it("renders with all buttons", () => {
- const wrapper = wrap({ buttons });
- expect(wrapper.findPartialId("button").length).toBe(4);
- buttons.forEach(button => {
- expect(wrapper.findText(`button-${button.id}`)).toBe(
- `Sign in with ${button.name}`
- );
- expect(wrapper.find(`logo-${button.id}`).prop("id")).toBe(button.id);
- });
- });
-});
diff --git a/src/components/modules/MetalHubModule/MetalHubListHeader/MetalHubListHeader.spec.tsx b/src/components/modules/MetalHubModule/MetalHubListHeader/MetalHubListHeader.spec.tsx
new file mode 100644
index 00000000..85816eec
--- /dev/null
+++ b/src/components/modules/MetalHubModule/MetalHubListHeader/MetalHubListHeader.spec.tsx
@@ -0,0 +1,39 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+
+import MetalHubListHeader from "./MetalHubListHeader";
+
+describe("MetalHubListHeader", () => {
+ let defaultProps: MetalHubListHeader["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ hideButton: false,
+ error: "",
+ fingerprint: "e4:95:6e:5c:2a:11:d4:1f:a2",
+ visible: true,
+ onCreateClick: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText("e4:95:6e:5c:...:11:d4:1f:a2")).toBeTruthy();
+ expect(getByText("Add a Bare Metal Server")).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/MetalHubModule/MetalHubListItem/MetalHubListItem.spec.tsx b/src/components/modules/MetalHubModule/MetalHubListItem/MetalHubListItem.spec.tsx
new file mode 100644
index 00000000..8a3855aa
--- /dev/null
+++ b/src/components/modules/MetalHubModule/MetalHubListItem/MetalHubListItem.spec.tsx
@@ -0,0 +1,65 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import DateUtils from "@src/utils/DateUtils";
+import { render } from "@testing-library/react";
+import { METALHUB_SERVER_MOCK } from "@tests/mocks/MetalHubServerMock";
+
+import MetalHubListItem from "./MetalHubListItem";
+
+describe("MetalHubListItem", () => {
+ let defaultProps: MetalHubListItem["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ item: METALHUB_SERVER_MOCK,
+ selected: false,
+ onSelectedChange: jest.fn(),
+ onClick: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText, getAllByText } = render(
+
+ );
+ expect(getByText(METALHUB_SERVER_MOCK.hostname!)).toBeTruthy();
+ expect(getByText("Active")).toBeTruthy();
+ expect(getByText(METALHUB_SERVER_MOCK.api_endpoint)).toBeTruthy();
+ expect(
+ getAllByText(
+ DateUtils.getLocalDate(METALHUB_SERVER_MOCK.created_at).toFormat(
+ "yyyy-LL-dd HH:mm:ss"
+ )
+ ).length
+ ).toBe(2);
+ });
+
+ it("renders default hostname when hostname is empty and inactive status", () => {
+ const { getByText } = render(
+
+ );
+ expect(getByText("No Hostname")).toBeTruthy();
+ expect(getByText("Inactive")).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/MetalHubModule/MetalHubModal/MetalHubModal.spec.tsx b/src/components/modules/MetalHubModule/MetalHubModal/MetalHubModal.spec.tsx
new file mode 100644
index 00000000..14062834
--- /dev/null
+++ b/src/components/modules/MetalHubModule/MetalHubModal/MetalHubModal.spec.tsx
@@ -0,0 +1,126 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import metalHubStore from "@src/stores/MetalHubStore";
+import { fireEvent, render, waitFor } from "@testing-library/react";
+import { METALHUB_SERVER_MOCK } from "@tests/mocks/MetalHubServerMock";
+
+import MetalHubModal from "./MetalHubModal";
+
+describe("MetalHubModal", () => {
+ let defaultProps: MetalHubModal["props"];
+ let metalHubStoreSpies: {
+ clearValidationError: jest.SpyInstance;
+ patchServer: jest.SpyInstance;
+ validateServer: jest.SpyInstance;
+ addServer: jest.SpyInstance;
+ };
+
+ beforeEach(() => {
+ metalHubStoreSpies = {
+ clearValidationError: jest.spyOn(metalHubStore, "clearValidationError"),
+ patchServer: jest.spyOn(metalHubStore, "patchServer").mockResolvedValue(),
+ validateServer: jest
+ .spyOn(metalHubStore, "validateServer")
+ .mockResolvedValue(true),
+ addServer: jest.spyOn(metalHubStore, "addServer").mockResolvedValue({
+ ...METALHUB_SERVER_MOCK,
+ }),
+ };
+ defaultProps = {
+ onRequestClose: jest.fn(),
+ onUpdateDone: jest.fn(),
+ };
+ });
+
+ it("renders without crashing and clears validation error on unmount", () => {
+ const { getByText, unmount } = render();
+ expect(getByText("Add Coriolis Bare Metal Server")).toBeTruthy();
+ unmount();
+ expect(metalHubStoreSpies.clearValidationError).toHaveBeenCalledTimes(1);
+ });
+
+ it("shows the server for editing", () => {
+ const { getByText } = render(
+
+ );
+ expect(getByText("Update Coriolis Bare Metal Server")).toBeTruthy();
+ const testInput = (label: string, value: string) => {
+ const input =
+ getByText(label).parentElement!.parentElement!.querySelector("input");
+ expect(input).toBeTruthy();
+ expect(input!.value).toBe(value);
+ };
+ testInput(
+ "Host",
+ METALHUB_SERVER_MOCK.api_endpoint!.split(":")[1].replace("//", "")
+ );
+ testInput(
+ "Port",
+ METALHUB_SERVER_MOCK.api_endpoint!.split(":")[2].replace(/\/.*/, "")
+ );
+ });
+
+ it("renders validation error", () => {
+ metalHubStore.validationError = ["Validation error", "Validation error 2"];
+ const { getByText } = render(
+
+ );
+ expect(getByText("Validation error")).toBeTruthy();
+ expect(getByText("Validation error 2")).toBeTruthy();
+ metalHubStore.validationError = [];
+ });
+
+ it("triggers submit on enter key", async () => {
+ render(
+
+ );
+ const input = document.querySelector("input")!;
+ fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
+ await waitFor(() => {
+ expect(metalHubStoreSpies.patchServer).toHaveBeenCalledTimes(1);
+ });
+ expect(metalHubStoreSpies.validateServer).toHaveBeenCalledTimes(1);
+ });
+
+ it("highlights invalid fields", () => {
+ const { getByText, getAllByText } = render(
+
+ );
+ fireEvent.click(getByText("Validate and save"));
+ expect(getAllByText("Required field").length).toBeGreaterThan(0);
+ });
+
+ it("adds new server", async () => {
+ const { getByText } = render();
+ const getInput = (label: string): HTMLInputElement =>
+ getByText(label).parentElement!.parentElement!.querySelector("input")!;
+
+ fireEvent.change(getInput("Host"), {
+ target: { value: "api.example.com" },
+ });
+ fireEvent.change(getInput("Port"), { target: { value: "5566" } });
+ fireEvent.click(getByText("Validate and save"));
+ await waitFor(() => {
+ expect(metalHubStoreSpies.addServer).toHaveBeenCalledWith(
+ "https://api.example.com:5566/api/v1"
+ );
+ });
+ expect(metalHubStoreSpies.validateServer).toHaveBeenCalledWith(
+ METALHUB_SERVER_MOCK.id
+ );
+ });
+});
diff --git a/src/components/modules/MetalHubModule/MetalHubModal/MetalHubModal.tsx b/src/components/modules/MetalHubModule/MetalHubModal/MetalHubModal.tsx
index 05127df4..a2e36b07 100644
--- a/src/components/modules/MetalHubModule/MetalHubModal/MetalHubModal.tsx
+++ b/src/components/modules/MetalHubModule/MetalHubModal/MetalHubModal.tsx
@@ -12,22 +12,23 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
-import React from "react";
import { observer } from "mobx-react";
+import React from "react";
import styled, { css } from "styled-components";
-import type { Field as FieldType } from "@src/@types/Field";
+import { MetalHubServer } from "@src/@types/MetalHub";
+import { ThemeProps } from "@src/components/Theme";
import Button from "@src/components/ui/Button";
-import Modal from "@src/components/ui/Modal";
import FieldInput from "@src/components/ui/FieldInput";
-
-import KeyboardManager from "@src/utils/KeyboardManager";
-import { ThemeProps } from "@src/components/Theme";
import LoadingButton from "@src/components/ui/LoadingButton";
-import { MetalHubServer } from "@src/@types/MetalHub";
-import image from "./images/server.svg";
-import metalHubStore from "@src/stores/MetalHubStore";
+import Modal from "@src/components/ui/Modal";
import StatusIcon from "@src/components/ui/StatusComponents/StatusIcon";
+import metalHubStore from "@src/stores/MetalHubStore";
+import KeyboardManager from "@src/utils/KeyboardManager";
+
+import image from "./images/server.svg";
+
+import type { Field as FieldType } from "@src/@types/Field";
const Wrapper = styled.div`
padding: 48px 32px 32px 32px;
@@ -302,7 +303,7 @@ class MetalHubModal extends React.Component {
const message = this.state.saving
? "Validating ..."
: metalHubStore.validationError.length
- ? metalHubStore.validationError.map(e =>
{e}
)
+ ? metalHubStore.validationError.map(e =>
{e}
)
: "Validation successful";
const status = this.state.saving
? "RUNNING"
diff --git a/src/components/modules/MetalHubModule/MetalHubServerDetailsContent/MetalHubServerDetailsContent.spec.tsx b/src/components/modules/MetalHubModule/MetalHubServerDetailsContent/MetalHubServerDetailsContent.spec.tsx
new file mode 100644
index 00000000..503ca0fc
--- /dev/null
+++ b/src/components/modules/MetalHubModule/MetalHubServerDetailsContent/MetalHubServerDetailsContent.spec.tsx
@@ -0,0 +1,101 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import DateUtils from "@src/utils/DateUtils";
+import { render } from "@testing-library/react";
+import { METALHUB_SERVER_MOCK } from "@tests/mocks/MetalHubServerMock";
+import TestUtils from "@tests/TestUtils";
+
+import MetalHubServerDetailsContent from "./MetalHubServerDetailsContent";
+
+jest.mock("@src/components/ui/Arrow", () => ({
+ __esModule: true,
+ default: (props: any) => (
+
Orientation: {props.orientation}
+ ),
+}));
+
+describe("MetalHubServerDetailsContent", () => {
+ let defaultProps: MetalHubServerDetailsContent["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ server: { ...METALHUB_SERVER_MOCK },
+ loading: false,
+ creatingMigration: false,
+ creatingReplica: false,
+ onCreateReplicaClick: jest.fn(),
+ onCreateMigrationClick: jest.fn(),
+ onDeleteClick: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ render();
+ const getText = (text: string) => {
+ let element: Element | null = null;
+ document.querySelectorAll("*").forEach(el => {
+ if (el.textContent && el.textContent.includes(text)) {
+ element = el;
+ }
+ });
+ if (!element) throw new Error(`Element with text "${text}" not found`);
+ return element;
+ };
+ expect(getText(METALHUB_SERVER_MOCK.hostname!)).toBeTruthy();
+ expect(getText(METALHUB_SERVER_MOCK.api_endpoint)).toBeTruthy();
+ expect(
+ getText(
+ DateUtils.getLocalDate(METALHUB_SERVER_MOCK.created_at).toFormat(
+ "yyyy-LL-dd HH:mm:ss"
+ )
+ )
+ ).toBeTruthy();
+
+ expect(
+ getText(`${METALHUB_SERVER_MOCK.physical_cores} physical`)
+ ).toBeTruthy();
+ expect(
+ getText(`${METALHUB_SERVER_MOCK.logical_cores} logical`)
+ ).toBeTruthy();
+ expect(
+ getText(
+ `${METALHUB_SERVER_MOCK.os_info.os_name} ${METALHUB_SERVER_MOCK.os_info.os_version}`
+ )
+ ).toBeTruthy();
+ });
+
+ it("handles row click", () => {
+ const { getAllByTestId } = render(
+
+ );
+ const row = TestUtils.select("TransferDetailsTable__Row-")!;
+ expect(row).toBeTruthy();
+ expect(getAllByTestId("Arrow")[0].textContent).toBe("Orientation: down");
+ row.click();
+ expect(getAllByTestId("Arrow")[0].textContent).toBe("Orientation: up");
+
+ row.click();
+ expect(getAllByTestId("Arrow")[0].textContent).toBe("Orientation: down");
+ });
+
+ it("renders loading", () => {
+ render();
+ expect(
+ TestUtils.select("MetalHubServerDetailsContent__LoadingWrapper")
+ ).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/MinionModule/MinionEndpointModal/MinionEndpointModal.spec.tsx b/src/components/modules/MinionModule/MinionEndpointModal/MinionEndpointModal.spec.tsx
new file mode 100644
index 00000000..03cc0202
--- /dev/null
+++ b/src/components/modules/MinionModule/MinionEndpointModal/MinionEndpointModal.spec.tsx
@@ -0,0 +1,92 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+import {
+ OPENSTACK_ENDPOINT_MOCK,
+ VMWARE_ENDPOINT_MOCK,
+} from "@tests/mocks/EndpointsMock";
+import { PROVIDERS_MOCK } from "@tests/mocks/ProvidersMock";
+import TestUtils from "@tests/TestUtils";
+
+import MinionEndpointModal from "./MinionEndpointModal";
+
+jest.mock("@src/components/modules/EndpointModule/EndpointLogos", () => ({
+ __esModule: true,
+ default: (props: any) => (
+
,
+}));
+
+describe("MinionEndpointModal", () => {
+ let defaultProps: MinionEndpointModal["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ providers: PROVIDERS_MOCK,
+ endpoints: [OPENSTACK_ENDPOINT_MOCK, VMWARE_ENDPOINT_MOCK],
+ loading: false,
+ onRequestClose: jest.fn(),
+ onSelectEndpoint: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByTestId } = render();
+ expect(getByTestId("EndpointLogos").textContent).toBe("vmware_vsphere");
+ });
+
+ it("renders no endpoints if provider doesn't have support for minions", () => {
+ render(
+
+ );
+ expect(
+ TestUtils.select("MinionEndpointModal__NoEndpoints")?.textContent
+ ).toContain("Please create a Coriolis Endpoint");
+ });
+
+ it("renders no endpoints if no providers", () => {
+ render();
+ expect(
+ TestUtils.select("MinionEndpointModal__NoEndpoints")?.textContent
+ ).toContain("Please create a Coriolis Endpoint");
+ });
+
+ it("selects an endpoint", () => {
+ const { getByText } = render();
+ TestUtils.select("DropdownButton__Wrapper")?.click();
+ TestUtils.select("Dropdown__ListItem-")?.click();
+ getByText("Next").click();
+ expect(defaultProps.onSelectEndpoint).toHaveBeenCalledWith(
+ VMWARE_ENDPOINT_MOCK,
+ "source"
+ );
+ });
+
+ it("renders loading", () => {
+ render();
+ expect(
+ TestUtils.select("MinionEndpointModal__LoadingWrapper")
+ ).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/MinionModule/MinionEndpointModal/MinionEndpointModal.tsx b/src/components/modules/MinionModule/MinionEndpointModal/MinionEndpointModal.tsx
index 1d55cc1d..4e6bef15 100644
--- a/src/components/modules/MinionModule/MinionEndpointModal/MinionEndpointModal.tsx
+++ b/src/components/modules/MinionModule/MinionEndpointModal/MinionEndpointModal.tsx
@@ -183,7 +183,7 @@ class MinionEndpointModal extends React.Component {
: providerTypes.DESTINATION_MINION_POOL;
const types =
this.props.providers?.[providerName].types.indexOf(providerType);
- return types && types > -1;
+ return types != null && types > -1;
}
);
diff --git a/src/components/modules/MinionModule/MinionPoolConfirmationModal/MinionPoolConfirmationModal.spec.tsx b/src/components/modules/MinionModule/MinionPoolConfirmationModal/MinionPoolConfirmationModal.spec.tsx
new file mode 100644
index 00000000..60dacc3b
--- /dev/null
+++ b/src/components/modules/MinionModule/MinionPoolConfirmationModal/MinionPoolConfirmationModal.spec.tsx
@@ -0,0 +1,75 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { fireEvent, render } from "@testing-library/react";
+
+import MinionPoolConfirmationModal from "./MinionPoolConfirmationModal";
+
+jest.mock("@src/components/ui/FieldInput", () => ({
+ __esModule: true,
+ default: (props: any) => (
+
+ ),
+}));
+
+describe("MinionPoolConfirmationModal", () => {
+ let defaultProps: MinionPoolConfirmationModal["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ onCancelClick: jest.fn(),
+ onExecuteClick: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ render();
+ let element: Element | null = null;
+ document.querySelectorAll("*").forEach(el => {
+ if (el.textContent && el.textContent.includes("Are you sure")) {
+ element = el;
+ }
+ });
+ if (!element) throw new Error(`Element not found`);
+ expect(element).toBeTruthy();
+ });
+
+ it("handles submit on Enter key press", () => {
+ render();
+
+ fireEvent.keyDown(document, { key: "Enter" });
+
+ expect(defaultProps.onExecuteClick).toHaveBeenCalledWith(false);
+ });
+
+ it("executes with force flag", () => {
+ const { getByTestId } = render(
+
+ );
+
+ fireEvent.click(getByTestId("FieldInput"));
+ fireEvent.click(document.querySelectorAll("button")[1]);
+
+ expect(defaultProps.onExecuteClick).toHaveBeenCalledWith(true);
+ });
+});
diff --git a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolDetailsContent.spec.tsx b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolDetailsContent.spec.tsx
new file mode 100644
index 00000000..6c325f2b
--- /dev/null
+++ b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolDetailsContent.spec.tsx
@@ -0,0 +1,102 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+import { OPENSTACK_ENDPOINT_MOCK } from "@tests/mocks/EndpointsMock";
+import { MINION_POOL_DETAILS_MOCK } from "@tests/mocks/MinionPoolMock";
+import { REPLICA_MOCK } from "@tests/mocks/TransferMock";
+import TestUtils from "@tests/TestUtils";
+
+import MinionPoolDetailsContent from "./MinionPoolDetailsContent";
+
+jest.mock("react-router-dom", () => ({ Link: "a" }));
+jest.mock("@src/components/modules/EndpointModule/EndpointLogos", () => ({
+ __esModule: true,
+ default: (props: any) => (
+
{props.endpoint}
+ ),
+}));
+jest.mock(
+ "@src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolEvents",
+ () => ({
+ __esModule: true,
+ default: () => ,
+ })
+);
+
+describe("MinionPoolDetailsContent", () => {
+ let defaultProps: MinionPoolDetailsContent["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ item: MINION_POOL_DETAILS_MOCK,
+ itemId: "minion-pool-id",
+ replicas: [REPLICA_MOCK],
+ migrations: [],
+ endpoints: [OPENSTACK_ENDPOINT_MOCK],
+ schema: [
+ {
+ name: "name",
+ label: "Name",
+ type: "text",
+ required: true,
+ disabled: false,
+ },
+ ],
+ schemaLoading: false,
+ loading: false,
+ page: "",
+ onAllocate: jest.fn(),
+ onDeleteMinionPoolClick: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render(
+
+ );
+ expect(getByText(MINION_POOL_DETAILS_MOCK.id)).toBeTruthy();
+ expect(getByText(MINION_POOL_DETAILS_MOCK.notes!)).toBeTruthy();
+ });
+
+ it("calls allocate callback", () => {
+ const { getByText } = render(
+
+ );
+ getByText("Allocate").click();
+ expect(defaultProps.onAllocate).toHaveBeenCalled();
+ });
+
+ it("renders loading", () => {
+ render();
+ expect(TestUtils.select("MinionPoolDetailsContent__Loading")).toBeTruthy();
+ });
+
+ it("renders machines page", () => {
+ render();
+ expect(TestUtils.select("MinionPoolMachines")).toBeTruthy();
+ });
+
+ it("renders events page", () => {
+ const { getByTestId } = render(
+
+ );
+ expect(getByTestId("MinionPoolEvents")).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolEvents.spec.tsx b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolEvents.spec.tsx
new file mode 100644
index 00000000..934ea2b7
--- /dev/null
+++ b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolEvents.spec.tsx
@@ -0,0 +1,137 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+import { MINION_POOL_DETAILS_MOCK } from "@tests/mocks/MinionPoolMock";
+import TestUtils from "@tests/TestUtils";
+
+import MinionPoolEvents from "./MinionPoolEvents";
+
+jest.mock("@src/utils/Config", () => ({
+ config: {
+ maxMinionPoolEventsPerPage: 1,
+ },
+}));
+
+describe("MinionPoolEvents", () => {
+ let defaultProps: MinionPoolEvents["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ item: MINION_POOL_DETAILS_MOCK,
+ };
+ });
+
+ it("renders without crashing", () => {
+ render();
+ expect(TestUtils.select("MinionPoolEvents__Message")?.textContent).toBe(
+ MINION_POOL_DETAILS_MOCK.events[0].message
+ );
+ });
+
+ it.each`
+ fromLabel | toLabel
+ ${"Events"} | ${"Progress Updates"}
+ ${"Events"} | ${"Events & Progress Updates"}
+ ${"INFO Event Level"} | ${"DEBUG Event Level"}
+ ${"INFO Event Level"} | ${"ERROR Event Level"}
+ ${"Descending Order"} | ${"Ascending Order"}
+ `("filters by $fromLabel to $toLabel", ({ fromLabel, toLabel }) => {
+ render();
+ let filterDropdown: HTMLElement | null = null;
+ TestUtils.selectAll("DropdownLink__Label").forEach(element => {
+ if (element.textContent === fromLabel) {
+ filterDropdown = element;
+ }
+ });
+ expect(filterDropdown).toBeTruthy();
+ filterDropdown!.click();
+ let listItem: HTMLElement | null = null;
+ TestUtils.selectAll("DropdownLink__ListItem-").forEach(element => {
+ if (element.textContent === toLabel) {
+ listItem = element;
+ }
+ });
+ expect(listItem).toBeTruthy();
+ listItem!.click();
+
+ TestUtils.selectAll("DropdownLink__Label").forEach(element => {
+ if (element.textContent === toLabel) {
+ filterDropdown = element;
+ }
+ });
+ expect(filterDropdown).toBeTruthy();
+ });
+
+ describe("Pagination", () => {
+ const showAllEvents = () => {
+ let showAllEvents: HTMLElement | null = null;
+ TestUtils.selectAll("DropdownLink__Label").forEach(element => {
+ if (element.textContent === "Events") {
+ showAllEvents = element;
+ }
+ });
+ expect(showAllEvents).toBeTruthy();
+ showAllEvents!.click();
+ let listItem: HTMLElement | null = null;
+ TestUtils.selectAll("DropdownLink__ListItem-").forEach(element => {
+ if (element.textContent === "Events & Progress Updates") {
+ listItem = element;
+ }
+ });
+ expect(listItem).toBeTruthy();
+ listItem!.click();
+ };
+
+ it("has pagination", () => {
+ render();
+
+ // pagination is not visible for 1 event
+ expect(TestUtils.select("Pagination__Wrapper")!).toBeFalsy();
+
+ showAllEvents();
+
+ // pagination is visible for more than 1 event
+ expect(TestUtils.select("Pagination__Wrapper")!).toBeTruthy();
+ });
+
+ it("goes to next page and back", () => {
+ render();
+
+ showAllEvents();
+
+ expect(
+ TestUtils.select("Pagination__PagePrevious")!.hasAttribute("disabled")
+ ).toBeTruthy();
+ expect(TestUtils.select("Pagination__PageNumber")!.textContent).toBe(
+ "1 of 3"
+ );
+
+ TestUtils.select("Pagination__PageNext")!.click();
+ expect(
+ TestUtils.select("Pagination__PagePrevious")!.hasAttribute("disabled")
+ ).toBeFalsy();
+ expect(TestUtils.select("Pagination__PageNumber")!.textContent).toBe(
+ "2 of 3"
+ );
+
+ TestUtils.select("Pagination__PagePrevious")!.click();
+ expect(TestUtils.select("Pagination__PageNumber")!.textContent).toBe(
+ "1 of 3"
+ );
+ });
+ });
+});
diff --git a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolEvents.tsx b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolEvents.tsx
index e338c67e..611eec42 100644
--- a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolEvents.tsx
+++ b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolEvents.tsx
@@ -93,17 +93,6 @@ type State = {
orderDir: OrderDir;
};
class MinionPoolEvents extends React.Component {
- static sortData(
- data: MinionPoolEventProgressUpdate[]
- ): MinionPoolEventProgressUpdate[] {
- return data
- .slice()
- .sort(
- (a, b) =>
- new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
- );
- }
-
state = {
allEvents: [] as MinionPoolEventProgressUpdate[],
prevLenghts: [0, 0],
diff --git a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMachines.spec.tsx b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMachines.spec.tsx
new file mode 100644
index 00000000..b1f19668
--- /dev/null
+++ b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMachines.spec.tsx
@@ -0,0 +1,106 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+import { MINION_POOL_MOCK } from "@tests/mocks/MinionPoolMock";
+import { MIGRATION_MOCK, REPLICA_MOCK } from "@tests/mocks/TransferMock";
+import TestUtils from "@tests/TestUtils";
+
+import MinionPoolMachines from "./MinionPoolMachines";
+
+jest.mock("react-router-dom", () => ({ Link: "a" }));
+
+describe("MinionPoolMachines", () => {
+ let defaultProps: MinionPoolMachines["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ item: MINION_POOL_MOCK,
+ replicas: [REPLICA_MOCK],
+ migrations: [MIGRATION_MOCK],
+ };
+ });
+
+ const filterBy = (fromLabel: string, toLabel: string) => {
+ let filterDropdown: HTMLElement | null = null;
+ TestUtils.selectAll("DropdownLink__Label").forEach(element => {
+ if (element.textContent === fromLabel) {
+ filterDropdown = element;
+ }
+ });
+ expect(filterDropdown).toBeTruthy();
+
+ filterDropdown!.click();
+
+ let filterItem: HTMLElement | null = null;
+ TestUtils.selectAll("DropdownLink__ListItem-").forEach(element => {
+ if (element.textContent === toLabel) {
+ filterItem = element;
+ }
+ });
+ expect(filterItem).toBeTruthy();
+ filterItem!.click();
+ };
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(
+ TestUtils.select("MinionPoolMachines__HeaderText")?.textContent
+ ).toBe("1 minion machine, 1 allocated");
+ expect(
+ getByText(`ID: ${MINION_POOL_MOCK.minion_machines[0].id}`)
+ ).toBeTruthy();
+ });
+
+ it("filters correctly", () => {
+ render();
+ filterBy("All", "Allocated");
+ expect(
+ TestUtils.selectAll("MinionPoolMachines__MachineWrapper").length
+ ).toBe(1);
+
+ filterBy("Allocated", "Not Allocated");
+ expect(
+ TestUtils.selectAll("MinionPoolMachines__MachineWrapper").length
+ ).toBe(0);
+ });
+
+ it("renders no machines", () => {
+ render(
+
+ );
+ expect(TestUtils.select("MinionPoolMachines__NoMachines")).toBeTruthy();
+ });
+
+ it("handles row click", () => {
+ render();
+ const arrow = TestUtils.select(
+ "Arrow__Wrapper",
+ TestUtils.select("MinionPoolMachines__Row-")!
+ );
+ expect(arrow).toBeTruthy();
+ expect(arrow!.attributes.getNamedItem("orientation")!.value).toBe("down");
+
+ TestUtils.select("MinionPoolMachines__Row-")!.click();
+ expect(arrow!.attributes.getNamedItem("orientation")!.value).toBe("up");
+
+ TestUtils.select("MinionPoolMachines__Row-")!.click();
+ expect(arrow!.attributes.getNamedItem("orientation")!.value).toBe("down");
+ });
+});
diff --git a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMainDetails.spec.tsx b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMainDetails.spec.tsx
new file mode 100644
index 00000000..9b71a09e
--- /dev/null
+++ b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMainDetails.spec.tsx
@@ -0,0 +1,74 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+import { OPENSTACK_ENDPOINT_MOCK } from "@tests/mocks/EndpointsMock";
+import { MINION_POOL_MOCK } from "@tests/mocks/MinionPoolMock";
+import { MIGRATION_MOCK, REPLICA_MOCK } from "@tests/mocks/TransferMock";
+
+import MinionPoolMainDetails from "./MinionPoolMainDetails";
+
+jest.mock("react-router-dom", () => ({ Link: "a" }));
+jest.mock("@src/components/modules/EndpointModule/EndpointLogos", () => ({
+ __esModule: true,
+ default: (props: any) => (
+
,
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText, getByTestId } = render(
+
+ );
+ expect(getByText(MINION_POOL_MOCK.notes!)).toBeTruthy();
+ expect(getByText(OPENSTACK_ENDPOINT_MOCK.name)).toBeTruthy();
+ expect(getByTestId("bottom-controls")).toBeTruthy();
+ expect(
+ getByText(MINION_POOL_MOCK.environment_options.option_1)
+ ).toBeTruthy();
+ expect(getByText("Object Option - Object Option 1")).toBeTruthy();
+ expect(
+ getByText(MINION_POOL_MOCK.environment_options.array_option[0])
+ ).toBeTruthy();
+ expect(getByText("source_value=destination_value")).toBeTruthy();
+ });
+
+ it("renders missing endpoint", () => {
+ render();
+ let missingEndpoint: Element | null = null;
+ document.querySelectorAll("*").forEach(element => {
+ if (element.textContent === "Endpoint is missing") {
+ missingEndpoint = element;
+ }
+ });
+ expect(missingEndpoint).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/MinionModule/MinionPoolListItem/MinionPoolListItem.spec.tsx b/src/components/modules/MinionModule/MinionPoolListItem/MinionPoolListItem.spec.tsx
new file mode 100644
index 00000000..d9e38412
--- /dev/null
+++ b/src/components/modules/MinionModule/MinionPoolListItem/MinionPoolListItem.spec.tsx
@@ -0,0 +1,39 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+import { MINION_POOL_MOCK } from "@tests/mocks/MinionPoolMock";
+
+import MinionPoolListItem from "./MinionPoolListItem";
+
+describe("MinionPoolListItem", () => {
+ let defaultProps: MinionPoolListItem["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ item: MINION_POOL_MOCK,
+ selected: false,
+ onClick: jest.fn(),
+ endpointType: jest.fn(),
+ onSelectedChange: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText(MINION_POOL_MOCK.name)).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/MinionModule/MinionPoolModal/MinionPoolModalContent.spec.tsx b/src/components/modules/MinionModule/MinionPoolModal/MinionPoolModalContent.spec.tsx
new file mode 100644
index 00000000..df1678df
--- /dev/null
+++ b/src/components/modules/MinionModule/MinionPoolModal/MinionPoolModalContent.spec.tsx
@@ -0,0 +1,113 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { fireEvent, render } from "@testing-library/react";
+import { OPENSTACK_ENDPOINT_MOCK } from "@tests/mocks/EndpointsMock";
+import TestUtils from "@tests/TestUtils";
+
+import MinionPoolModalContent from "./MinionPoolModalContent";
+
+jest.mock("@src/plugins/default/ContentPlugin", () => jest.fn(() => null));
+
+jest.mock("@src/components/modules/EndpointModule/EndpointLogos", () => ({
+ __esModule: true,
+ default: (props: any) => ,
+}));
+
+const DEFAULT_SCHEMA_MOCK = [
+ {
+ name: "name",
+ type: "string",
+ required: true,
+ },
+ {
+ name: "endpoint_id",
+ type: "string",
+ required: true,
+ },
+ {
+ name: "platform",
+ type: "string",
+ required: true,
+ },
+];
+
+const ENV_SCHEMA_MOCK = [
+ {
+ name: "env_option",
+ type: "string",
+ },
+ {
+ name: "required_env_option",
+ type: "string",
+ required: true,
+ },
+];
+
+describe("MinionPoolModalContent", () => {
+ let defaultProps: MinionPoolModalContent["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ envOptionsDisabled: false,
+ defaultSchema: [...DEFAULT_SCHEMA_MOCK],
+ envSchema: [...ENV_SCHEMA_MOCK],
+ invalidFields: [ENV_SCHEMA_MOCK[1].name],
+ endpoint: OPENSTACK_ENDPOINT_MOCK,
+ platform: "source",
+ optionsLoading: false,
+ optionsLoadingSkipFields: [],
+ disabled: false,
+ cancelButtonText: "Cancel",
+ getFieldValue: jest.fn(),
+ onFieldChange: jest.fn(),
+ onResizeUpdate: jest.fn(),
+ scrollableRef: jest.fn(),
+ onCreateClick: jest.fn(),
+ onCancelClick: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText("Environment Options")).toBeTruthy();
+ });
+
+ it("calls resize on simple / advanced toggle", () => {
+ const { getByText } = render();
+ getByText("Advanced").click();
+ expect(defaultProps.onResizeUpdate).toHaveBeenCalled();
+ });
+
+ it("filters non required fields", () => {
+ const { getByText } = render();
+ expect(TestUtils.selectAll("FieldInput__LabelText")[1].textContent).toBe(
+ "Required Env Option"
+ );
+ getByText("Advanced").click();
+ expect(TestUtils.selectAll("FieldInput__LabelText")[1].textContent).toBe(
+ "Env Option"
+ );
+ });
+
+ it("fires onFieldChange", () => {
+ render();
+ fireEvent.change(TestUtils.select("TextInput__Input")!, {
+ target: { value: "test" },
+ });
+ expect(defaultProps.onFieldChange).toHaveBeenCalled();
+ });
+});
diff --git a/src/components/modules/NavigationModule/DetailsNavigation/test.tsx b/src/components/modules/NavigationModule/DetailsNavigation/test.tsx
deleted file mode 100644
index 94a565be..00000000
--- a/src/components/modules/NavigationModule/DetailsNavigation/test.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import TestWrapper from "@src/utils/TestWrapper";
-import DetailsNavigation from ".";
-
-const wrap = props =>
- new TestWrapper(
- shallow(),
- "detailsNavigation"
- );
-const items = [
- { label: "Item 1", value: "item-1" },
- { label: "Item 2", value: "item-2" },
- { label: "Item 3", value: "item-3" },
-];
-
-describe("DetailsNavigation Component", () => {
- // it('renders 3 items', () => {
- // let wrapper = wrap({ items})
- // console.log(wrapper.find('dn-wrapper').debug())
- // // items.forEach(item => {
- // // expect(wrapper.find(item.value).shallow.dive().dive()).toBe(item.label)
- // // })
- // })
-
- it("has items with correct href attribute", () => {
- const wrapper = wrap({ items, itemType: "replica", itemId: "item-id" });
- expect(wrapper.find(items[0].value).prop("to")).toBe(
- "/replica/item-1/item-id"
- );
- });
-
- it("has items with correct href attribute, if items have no value", () => {
- const wrapper = wrap({
- items: [{ label: "Item 1", value: "" }],
- itemType: "migration",
- itemId: "item-id",
- });
- expect(wrapper.find("").prop("to")).toBe("/migration/item-id");
- });
-});
diff --git a/src/components/modules/NavigationModule/Navigation/Navigation.tsx b/src/components/modules/NavigationModule/Navigation/Navigation.tsx
index ca1c71c0..dc85271c 100644
--- a/src/components/modules/NavigationModule/Navigation/Navigation.tsx
+++ b/src/components/modules/NavigationModule/Navigation/Navigation.tsx
@@ -229,7 +229,7 @@ const CbsLogoSmall = styled.a`
display: flex;
transition: opacity ${ANIMATION};
`;
-export const TEST_ID = "navigation";
+
type Props = {
currentPage?: string;
className?: string;
diff --git a/src/components/modules/NavigationModule/NavigationMini/NavigationMini.spec.tsx b/src/components/modules/NavigationModule/NavigationMini/NavigationMini.spec.tsx
new file mode 100644
index 00000000..308b16c7
--- /dev/null
+++ b/src/components/modules/NavigationModule/NavigationMini/NavigationMini.spec.tsx
@@ -0,0 +1,42 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+
+import NavigationMini from "./";
+import TestUtils from "@tests/TestUtils";
+
+jest.mock("react-router-dom", () => ({ Link: "a" }));
+jest.mock("@src/components/modules/NavigationModule/Navigation", () => ({
+ __esModule: true,
+ default: (props: any) => (
+
open: {String(props.open)}
+ ),
+}));
+
+describe("NavigationMini", () => {
+ it("renders without crasing", () => {
+ render();
+ expect(TestUtils.select("NavigationMini__Wrapper")).toBeTruthy();
+ });
+
+ it("toggles the menu", () => {
+ const { getByTestId } = render();
+ expect(getByTestId("navigation").textContent).toBe("open: false");
+ TestUtils.select("NavigationMini__MenuImage")!.click();
+ expect(getByTestId("navigation").textContent).toBe("open: true");
+ });
+});
diff --git a/src/components/modules/NavigationModule/NavigationMini/NavigationMini.tsx b/src/components/modules/NavigationModule/NavigationMini/NavigationMini.tsx
index 982974b3..8c0edaff 100644
--- a/src/components/modules/NavigationModule/NavigationMini/NavigationMini.tsx
+++ b/src/components/modules/NavigationModule/NavigationMini/NavigationMini.tsx
@@ -71,8 +71,6 @@ const NavigationStyled = styled(Navigation)`
z-index: 9;
`;
-export const TEST_ID = "navigationMini";
-
type State = {
open: boolean;
};
diff --git a/src/components/modules/ProjectModule/ProjectDetailsContent/ProjectDetailsContent.spec.tsx b/src/components/modules/ProjectModule/ProjectDetailsContent/ProjectDetailsContent.spec.tsx
new file mode 100644
index 00000000..d3fe309c
--- /dev/null
+++ b/src/components/modules/ProjectModule/ProjectDetailsContent/ProjectDetailsContent.spec.tsx
@@ -0,0 +1,197 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { Project, RoleAssignment } from "@src/@types/Project";
+import { render } from "@testing-library/react";
+
+import ProjectDetailsContent from "./";
+import { User } from "@src/@types/User";
+import TestUtils from "@tests/TestUtils";
+
+jest.mock("react-router-dom", () => ({ Link: "a" }));
+
+const PROJECT: Project = {
+ id: "project-id",
+ name: "project-name",
+};
+const USER: User = {
+ id: "user-id",
+ name: "user-name",
+ project: PROJECT,
+ email: "user-email",
+ enabled: true,
+};
+const ROLE_ASSIGNMENT: RoleAssignment = {
+ scope: {
+ project: PROJECT,
+ },
+ role: {
+ id: "role-id",
+ name: "role-name",
+ },
+ user: USER,
+};
+
+describe("ProjectDetailsContent", () => {
+ let defaultProps: ProjectDetailsContent["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ project: PROJECT,
+ loading: false,
+ users: [USER],
+ usersLoading: false,
+ roleAssignments: [ROLE_ASSIGNMENT],
+ roles: [ROLE_ASSIGNMENT.role],
+ loggedUserId: "admin",
+ onEnableUser: jest.fn(),
+ onRemoveUser: jest.fn(),
+ onUserRoleChange: jest.fn(),
+ onAddMemberClick: jest.fn(),
+ onDeleteClick: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText(PROJECT.name)).toBeTruthy();
+ expect(getByText(PROJECT.id)).toBeTruthy();
+ });
+
+ describe("user actions", () => {
+ const openActionsDropdown = () => {
+ const dropdowns = TestUtils.selectAll("DropdownLink__LinkButton");
+ let actionsDropdown;
+
+ for (const dropdown of dropdowns) {
+ if (dropdown.textContent?.includes("Actions")) {
+ actionsDropdown = dropdown;
+ break;
+ }
+ }
+
+ expect(actionsDropdown).toBeTruthy();
+ actionsDropdown?.click();
+ };
+
+ const clickAction = (action: string) => {
+ const items = TestUtils.selectAll("DropdownLink__ListItem-");
+ let actionItem;
+
+ for (const item of items) {
+ if (item.textContent?.includes(action)) {
+ actionItem = item;
+ break;
+ }
+ }
+
+ expect(actionItem).toBeTruthy();
+ actionItem?.click();
+ };
+
+ it("removes user", () => {
+ render();
+
+ openActionsDropdown();
+ clickAction("Remove");
+
+ expect(TestUtils.select("AlertModal__Message")).toBeTruthy();
+ TestUtils.select("AlertModal__Buttons")
+ ?.querySelectorAll("button")[1]
+ .click();
+
+ expect(defaultProps.onRemoveUser).toBeCalled();
+ });
+
+ it("cancels removing user", () => {
+ render();
+
+ openActionsDropdown();
+ clickAction("Remove");
+
+ expect(TestUtils.select("AlertModal__Message")).toBeTruthy();
+ TestUtils.select("AlertModal__Buttons")
+ ?.querySelectorAll("button")[0]
+ .click();
+
+ expect(defaultProps.onRemoveUser).not.toBeCalled();
+ });
+
+ it("enables user", () => {
+ render(
+
+ );
+
+ openActionsDropdown();
+ clickAction("Enable");
+
+ expect(defaultProps.onEnableUser).toBeCalled();
+ });
+
+ it("handles invalid action", () => {
+ const component = new ProjectDetailsContent(defaultProps);
+ component.handleUserAction(USER, { label: "Invalid", value: "invalid" });
+
+ expect(defaultProps.onEnableUser).not.toBeCalled();
+ expect(defaultProps.onRemoveUser).not.toBeCalled();
+ });
+ });
+
+ it("renders loading", () => {
+ render();
+ expect(
+ TestUtils.select("ProjectDetailsContent__LoadingWrapper")
+ ).toBeTruthy();
+ });
+
+ it("changes user role", () => {
+ render();
+ const dropdowns = TestUtils.selectAll("DropdownLink__LinkButton");
+ let roleDropdown;
+
+ for (const dropdown of dropdowns) {
+ if (dropdown.textContent?.includes(ROLE_ASSIGNMENT.role.name)) {
+ roleDropdown = dropdown;
+ break;
+ }
+ }
+
+ expect(roleDropdown).toBeTruthy();
+ roleDropdown?.click();
+
+ const items = TestUtils.selectAll("DropdownLink__ListItem-");
+ let roleItem;
+
+ for (const item of items) {
+ if (item.textContent?.includes(ROLE_ASSIGNMENT.role.name)) {
+ roleItem = item;
+ break;
+ }
+ }
+
+ expect(roleItem).toBeTruthy();
+ roleItem?.click();
+
+ expect(defaultProps.onUserRoleChange).toBeCalledWith(
+ USER,
+ ROLE_ASSIGNMENT.role.id,
+ false
+ );
+ });
+});
diff --git a/src/components/modules/ProjectModule/ProjectDetailsContent/ProjectDetailsContent.tsx b/src/components/modules/ProjectModule/ProjectDetailsContent/ProjectDetailsContent.tsx
index fced7f61..b4421931 100644
--- a/src/components/modules/ProjectModule/ProjectDetailsContent/ProjectDetailsContent.tsx
+++ b/src/components/modules/ProjectModule/ProjectDetailsContent/ProjectDetailsContent.tsx
@@ -12,22 +12,22 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
+import { observer } from "mobx-react";
import React from "react";
import { Link } from "react-router-dom";
-import { observer } from "mobx-react";
import styled, { css } from "styled-components";
+import { ThemePalette, ThemeProps } from "@src/components/Theme";
import AlertModal from "@src/components/ui/AlertModal";
-import Table from "@src/components/ui/Table";
-import CopyValue from "@src/components/ui/CopyValue";
+import Button from "@src/components/ui/Button";
import CopyMultilineValue from "@src/components/ui/CopyMultilineValue";
-import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
+import CopyValue from "@src/components/ui/CopyValue";
import DropdownLink from "@src/components/ui/Dropdowns/DropdownLink";
-import Button from "@src/components/ui/Button";
+import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
+import Table from "@src/components/ui/Table";
import type { Project, RoleAssignment, Role } from "@src/@types/Project";
import type { User } from "@src/@types/User";
-import { ThemePalette, ThemeProps } from "@src/components/Theme";
const Wrapper = styled.div`
${ThemeProps.exactWidth(ThemeProps.contentWidth)}
@@ -170,13 +170,7 @@ class ProjectDetailsContent extends React.Component {
-
diff --git a/src/components/modules/ProjectModule/ProjectDetailsContent/test.tsx b/src/components/modules/ProjectModule/ProjectDetailsContent/test.tsx
deleted file mode 100644
index b8b1e0eb..00000000
--- a/src/components/modules/ProjectModule/ProjectDetailsContent/test.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import TW from "@src/utils/TestWrapper";
-import type { Project, Role, RoleAssignment } from "@src/@types/Project";
-import type { User } from "@src/@types/User";
-import ProjectDetailsContent from ".";
-
-type Props = {
- project: ?Project;
- loading: boolean;
- users: User[];
- usersLoading: boolean;
- deleteDisabled: boolean;
- roleAssignments: RoleAssignment[];
- roles: Role[];
- loggedUserId: string;
-};
-const wrap = (props: Props) =>
- new TW(
- shallow(
- {}}
- onDeleteClick={() => {}}
- onEditProjectClick={() => {}}
- onEnableUser={() => {}}
- onRemoveUser={() => {}}
- onUserRoleChange={() => {}}
- {...props}
- />
- ),
- "pdContent"
- );
-const projects: Project[] = [
- { id: "project-1", name: "Project 1" },
- { id: "project-2", name: "Project 2" },
-];
-const users: User[] = [
- { id: "user-1", name: "User 1", email: "email1", project: projects[0] },
- { id: "user-2", name: "User 2", email: "email2", project: projects[1] },
-];
-const roles: Role[] = [
- { id: "role-1", name: "Role 1" },
- { id: "role-2", name: "Role 2" },
-];
-const roleAssignments: RoleAssignment[] = [
- { user: users[0], role: roles[0], scope: { project: projects[0] } },
- { user: users[1], role: roles[1], scope: { project: projects[0] } },
-];
-describe("ProjectDetailsContent Component", () => {
- it("renders info", () => {
- const wrapper = wrap({
- project: projects[0],
- loading: false,
- users,
- usersLoading: false,
- deleteDisabled: false,
- roleAssignments,
- roles,
- loggedUserId: "user-1",
- });
- expect(wrapper.find("name").prop("value")).toBe("Project 1");
- expect(wrapper.find("id").prop("value")).toBe("project-1");
- const rows = wrapper.find("members").prop("items");
- expect(rows[0][1].props.selectedItems.length).toBe(1);
- expect(rows[0][1].props.selectedItems[0]).toBe("role-1");
- expect(rows[1][1].props.selectedItems.length).toBe(1);
- expect(rows[1][1].props.selectedItems[0]).toBe("role-2");
- });
-});
diff --git a/src/components/modules/ProjectModule/ProjectListItem/ProjectListItem.spec.tsx b/src/components/modules/ProjectModule/ProjectListItem/ProjectListItem.spec.tsx
new file mode 100644
index 00000000..143e3acd
--- /dev/null
+++ b/src/components/modules/ProjectModule/ProjectListItem/ProjectListItem.spec.tsx
@@ -0,0 +1,55 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { fireEvent, render } from "@testing-library/react";
+
+import ProjectListItem from ".";
+
+describe("ProjectListItem", () => {
+ let defaultProps: ProjectListItem["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ item: {
+ id: "project-id",
+ name: "project-name",
+ },
+ onClick: jest.fn(),
+ getMembers: jest.fn(),
+ isCurrentProject: jest.fn(),
+ onSwitchProjectClick: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText(defaultProps.item.name)).toBeTruthy();
+ });
+
+ it("switches project", () => {
+ render();
+ const switchProjectButton = Array.from(
+ document.querySelectorAll("button")
+ ).find(el => el.textContent?.includes("Switch"));
+ expect(switchProjectButton).toBeTruthy();
+
+ fireEvent.mouseDown(switchProjectButton!);
+ fireEvent.mouseUp(switchProjectButton!);
+
+ switchProjectButton!.click();
+ expect(defaultProps.onSwitchProjectClick).toHaveBeenCalled();
+ });
+});
diff --git a/src/components/modules/ProjectModule/ProjectListItem/test.tsx b/src/components/modules/ProjectModule/ProjectListItem/test.tsx
deleted file mode 100644
index 65cbab15..00000000
--- a/src/components/modules/ProjectModule/ProjectListItem/test.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import sinon from "sinon";
-import TW from "@src/utils/TestWrapper";
-import ProjectListItem from ".";
-import type { Project } from "@src/@types/Project";
-
-type Props = {
- item: Project;
- onClick: () => void;
- getMembers: (projectId: string) => number;
- isCurrentProject: (projectId: string) => boolean;
- onSwitchProjectClick: (projectId: string) => void;
-};
-
-const wrap = (props: Props) =>
- new TW(shallow(), "plItem");
-
-const item: Project = {
- id: "p_id",
- name: "p_name",
- description: "p_description",
- enabled: true,
-};
-describe("ProjectListItem Component", () => {
- it("renders with correct data", () => {
- const wrapper = wrap({
- item,
- onClick: () => {},
- getMembers: () => 3,
- isCurrentProject: () => true,
- onSwitchProjectClick: () => {},
- });
- expect(wrapper.findText("name")).toBe(item.name);
- expect(wrapper.findText("description")).toBe(item.description);
- expect(wrapper.findText("members")).toBe("3");
- expect(wrapper.findText("enabled")).toBe("Yes");
- expect(wrapper.findText("currentButton", false, true)).toBe("Current");
- });
-
- it("dispatches click", () => {
- const onClick = sinon.spy();
- const wrapper = wrap({
- item,
- onClick,
- getMembers: () => 3,
- isCurrentProject: () => true,
- onSwitchProjectClick: () => {},
- });
- wrapper.find("content").click();
- expect(onClick.calledOnce).toBe(true);
- });
-
- it("dispatches switch project click", () => {
- const onSwitchProjectClick = sinon.spy();
- const wrapper = wrap({
- item,
- onClick: () => {},
- getMembers: () => 3,
- isCurrentProject: () => true,
- onSwitchProjectClick,
- });
- wrapper.find("currentButton").click();
- expect(onSwitchProjectClick.calledOnce).toBe(true);
- expect(onSwitchProjectClick.args[0][0]).toBe("p_id");
- });
-});
diff --git a/src/components/modules/ProjectModule/ProjectMemberModal/test.tsx b/src/components/modules/ProjectModule/ProjectMemberModal/test.tsx
deleted file mode 100644
index ffdeca6b..00000000
--- a/src/components/modules/ProjectModule/ProjectMemberModal/test.tsx
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import sinon from "sinon";
-import TW from "@src/utils/TestWrapper";
-import type { User } from "@src/@types/User";
-import type { Project, Role } from "@src/@types/Project";
-import ProjectMemberModal from ".";
-
-type Props = {
- loading: boolean;
- users: User[];
- projects: Project[];
- onRequestClose: () => void;
- onAddClick: (user: User, isNew: boolean, roles: Role[]) => void;
- roles: Role[];
-};
-const wrap = (props: Props) =>
- new TW(shallow(), "pmModal");
-const users: User[] = [
- { id: "user-1", name: "User 1", email: "", project: { id: "", name: "" } },
- { id: "user-2", name: "User 2", email: "", project: { id: "", name: "" } },
-];
-const projects: Project[] = [
- { id: "project-1", name: "Project 1" },
- { id: "project-2", name: "Project 2" },
-];
-const roles: Role[] = [
- { id: "role-1", name: "Role 1" },
- { id: "role-2", name: "Role 2" },
- { id: "role-3", name: "Role 3" },
-];
-describe("ProjectMemberModal Component", () => {
- it("renders existing user form", () => {
- const wrapper = wrap({
- loading: false,
- users,
- projects,
- roles,
- onRequestClose: () => {},
- onAddClick: () => {},
- });
- expect(wrapper.find("users").prop("items")[1].value).toBe(users[1].id);
- expect(wrapper.find("roles").prop("enum")[1].id).toBe(roles[1].id);
- expect(wrapper.find("users").prop("highlight")).toBe(false);
- expect(wrapper.find("roles").prop("highlight")).toBe(false);
- expect(wrapper.find("users").prop("disabled")).toBe(false);
- expect(wrapper.find("roles").prop("disabled")).toBe(false);
- });
-
- it("highlights required fields in existing user form", () => {
- const wrapper = wrap({
- loading: false,
- users,
- projects,
- roles,
- onRequestClose: () => {},
- onAddClick: () => {},
- });
- expect(wrapper.find("users").length).toBe(1);
- wrapper.find("addButton").click();
- expect(wrapper.find("users").prop("highlight")).toBe(true);
- expect(wrapper.find("roles").prop("highlight")).toBe(true);
- });
-
- it("renders new user form and highlights required", () => {
- const wrapper = wrap({
- loading: false,
- users,
- projects,
- roles,
- onRequestClose: () => {},
- onAddClick: () => {},
- });
- wrapper.find("formToggle").simulate("change", { value: "new" });
- expect(wrapper.find("users").length).toBe(0);
- expect(wrapper.find("field-username").prop("highlight")).toBe(false);
- expect(wrapper.find("field-description").prop("highlight")).toBe(false);
- expect(wrapper.find("field-Primary Project").prop("highlight")).toBe(false);
- expect(wrapper.find("roles").prop("highlight")).toBe(false);
- expect(wrapper.find("field-password").prop("highlight")).toBe(false);
- expect(wrapper.find("field-confirm_password").prop("highlight")).toBe(
- false
- );
- expect(wrapper.find("field-Email").prop("highlight")).toBe(false);
- wrapper.find("addButton").click();
- expect(wrapper.find("field-username").prop("highlight")).toBe(true);
- expect(wrapper.find("field-description").prop("highlight")).toBe(false);
- expect(wrapper.find("field-Primary Project").prop("highlight")).toBe(false);
- expect(wrapper.find("roles").prop("highlight")).toBe(true);
- expect(wrapper.find("field-password").prop("highlight")).toBe(true);
- expect(wrapper.find("field-confirm_password").prop("highlight")).toBe(
- false
- );
- expect(wrapper.find("field-Email").prop("highlight")).toBe(false);
- });
-
- it("dispatches add click with correct data", () => {
- const onAddClick = sinon.spy();
- const wrapper = wrap({
- loading: false,
- users,
- projects,
- roles,
- onRequestClose: () => {},
- onAddClick,
- });
- wrapper.find("formToggle").simulate("change", { value: "new" });
- wrapper.find("field-username").simulate("change", "new-username");
- wrapper.find("roles").simulate("change", "role-2");
- wrapper.find("roles").simulate("change", "role-1");
- wrapper.find("roles").simulate("change", "role-2");
- wrapper.find("roles").simulate("change", "role-3");
- wrapper.find("field-password").simulate("change", "new-password");
- wrapper.find("field-confirm_password").simulate("change", "new-password");
- wrapper.find("addButton").click();
- const userArg = onAddClick.args[0][0];
- const rolesArg: Role[] = onAddClick.args[0][2];
- expect(userArg.name).toBe("new-username");
- expect(userArg.password).toBe("new-password");
- expect(rolesArg.length).toBe(2);
- expect(rolesArg[0].id).toBe("role-1");
- expect(rolesArg[1].id).toBe("role-3");
- });
-
- it("disabled on loading", () => {
- const wrapper = wrap({
- loading: true,
- users,
- projects,
- roles,
- onRequestClose: () => {},
- onAddClick: () => {},
- });
- expect(wrapper.find("users").prop("disabled")).toBe(true);
- expect(wrapper.find("roles").prop("disabled")).toBe(true);
- });
-});
diff --git a/src/components/modules/ProjectModule/ProjectModal/test.tsx b/src/components/modules/ProjectModule/ProjectModal/test.tsx
deleted file mode 100644
index 64374ddf..00000000
--- a/src/components/modules/ProjectModule/ProjectModal/test.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import sinon from "sinon";
-import TW from "@src/utils/TestWrapper";
-import type { Project } from "@src/@types/Project";
-import ProjectModal from ".";
-
-type Props = {
- project?: ?Project;
- isNewProject?: boolean;
- loading: boolean;
- onRequestClose: () => void;
- onUpdateClick: (project: Project) => void;
-};
-
-const wrap = (props: Props) =>
- new TW(shallow(), "projectModal");
-
-describe("ProjectModal Component", () => {
- it("doesn't dispatch click if project name is not filled", () => {
- const onUpdateClick = sinon.spy();
- const wrapper = wrap({
- isNewProject: true,
- loading: false,
- onRequestClose: () => {},
- onUpdateClick,
- });
- expect(wrapper.findText("updateButton", false, true)).toBe("New Project");
- wrapper.find("updateButton").click();
- expect(onUpdateClick.called).toBe(false);
- expect(wrapper.find("field-project_name").prop("highlight")).toBe(true);
- });
-
- it("dispatches click if project is filled", () => {
- const onUpdateClick = sinon.spy();
- const wrapper = wrap({
- isNewProject: false,
- project: { id: "project", name: "Project Name" },
- loading: false,
- onRequestClose: () => {},
- onUpdateClick,
- });
- expect(wrapper.findText("updateButton", false, true)).toBe(
- "Update Project"
- );
- wrapper.find("updateButton").click();
- expect(onUpdateClick.called).toBe(true);
- });
-
- it("has disabled fields on loading", () => {
- const wrapper = wrap({
- isNewProject: false,
- project: { id: "project", name: "Project Name" },
- loading: true,
- onRequestClose: () => {},
- onUpdateClick: () => {},
- });
- expect(wrapper.find("updateButton").prop("disabled")).toBe(true);
- expect(wrapper.find("field-project_name").prop("disabled")).toBe(true);
- });
-});
diff --git a/src/components/modules/SetupModule/SetupPageEmailBody/SetupPageEmailBody.spec.tsx b/src/components/modules/SetupModule/SetupPageEmailBody/SetupPageEmailBody.spec.tsx
new file mode 100644
index 00000000..e433d7e8
--- /dev/null
+++ b/src/components/modules/SetupModule/SetupPageEmailBody/SetupPageEmailBody.spec.tsx
@@ -0,0 +1,83 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { CustomerInfoBasic, CustomerInfoTrial } from "@src/@types/InitialSetup";
+import DomUtils from "@src/utils/DomUtils";
+import { render } from "@testing-library/react";
+import TestUtils from "@tests/TestUtils";
+
+import SetupPageEmailBody from "./";
+
+jest.mock("@src/utils/Config", () => ({
+ config: {
+ providerSortPriority: {},
+ providerNames: {
+ openstack: "OpenStack",
+ vmware_vsphere: "VMware vSphere",
+ },
+ },
+}));
+
+jest.mock("@src/utils/DomUtils", () => ({
+ copyTextToClipboard: jest.fn(),
+}));
+
+const CUSTOMER_INFO_BASIC: CustomerInfoBasic = {
+ fullName: "John Doe",
+ email: "email@email.com",
+ company: "Company",
+ country: "Country",
+};
+
+const customerInfoTrial: CustomerInfoTrial = {
+ interestedIn: "migrations",
+ sourcePlatform: "vmware_vsphere",
+ destinationPlatform: "openstack",
+};
+
+describe("SetupPageEmailBody", () => {
+ let defaultProps: SetupPageEmailBody["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ customerInfoBasic: CUSTOMER_INFO_BASIC,
+ customerInfoTrial: customerInfoTrial,
+ licenceType: "trial",
+ applianceId: "appliance-id",
+ };
+ });
+
+ it("renders without crashing", () => {
+ render();
+ expect(
+ Array.from(document.querySelectorAll("*")).find(el =>
+ el.textContent?.includes(CUSTOMER_INFO_BASIC.fullName)
+ )
+ ).toBeTruthy();
+ });
+
+ it("handles copy", () => {
+ render();
+ TestUtils.select("CopyButton__Wrapper")!.click();
+ expect(DomUtils.copyTextToClipboard).toHaveBeenCalled();
+ });
+
+ it("copy is not called if no email template", () => {
+ const component = new SetupPageEmailBody(defaultProps);
+ component.handleCopy();
+ expect(DomUtils.copyTextToClipboard).not.toHaveBeenCalled();
+ });
+});
diff --git a/src/components/modules/SetupModule/SetupPageEmailBody/SetupPageEmailBody.tsx b/src/components/modules/SetupModule/SetupPageEmailBody/SetupPageEmailBody.tsx
index 3ffaf207..4810aad8 100644
--- a/src/components/modules/SetupModule/SetupPageEmailBody/SetupPageEmailBody.tsx
+++ b/src/components/modules/SetupModule/SetupPageEmailBody/SetupPageEmailBody.tsx
@@ -12,20 +12,21 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
-import * as React from "react";
import { observer } from "mobx-react";
+import * as React from "react";
import styled from "styled-components";
+
import {
CustomerInfoBasic,
CustomerInfoTrial,
SetupPageLicenceType,
} from "@src/@types/InitialSetup";
-import { customerInfoSetupStoreValueToString } from "@src/stores/SetupStore";
-import notificationStore from "@src/stores/NotificationStore";
-import { ThemePalette } from "@src/components/Theme";
-import SetupPageServerError from "@src/components/modules/SetupModule/ui/SetupPageServerError";
import SetupPageInputWrapper from "@src/components/modules/SetupModule/ui/SetupPageInputWrapper";
+import SetupPageServerError from "@src/components/modules/SetupModule/ui/SetupPageServerError";
+import { ThemePalette } from "@src/components/Theme";
import CopyButton from "@src/components/ui/CopyButton";
+import { customerInfoSetupStoreValueToString } from "@src/stores/SetupStore";
+import DomUtils from "@src/utils/DomUtils";
const Wrapper = styled.div``;
const Link = styled.a`
@@ -59,28 +60,18 @@ type Props = {
class SetupPageEmailBody extends React.Component {
emailTemplate: HTMLElement | null = null;
- handleCopy(event?: React.ClipboardEvent) {
+ async handleCopy(event?: React.ClipboardEvent) {
event?.preventDefault();
if (!this.emailTemplate) {
return;
}
- try {
- const range = document.createRange();
- range.selectNode(this.emailTemplate);
- window.getSelection()?.removeAllRanges();
- window.getSelection()?.addRange(range);
- document.execCommand("copy");
- if (!event) {
- notificationStore.alert(
- "The email body was succesfully copied to clipboard",
- "success"
- );
- }
- } catch (err) {
- notificationStore.alert("Error copying to clipboard", "error");
- }
+ const range = document.createRange();
+ range.selectNode(this.emailTemplate);
+ window.getSelection()?.removeAllRanges();
+ window.getSelection()?.addRange(range);
+ await DomUtils.copyTextToClipboard(window.getSelection()?.toString() || "");
}
render() {
diff --git a/src/components/modules/NavigationModule/Navigation/test.tsx b/src/components/modules/SetupModule/SetupPageHelp/SetupPageHelp.spec.tsx
similarity index 53%
rename from src/components/modules/NavigationModule/Navigation/test.tsx
rename to src/components/modules/SetupModule/SetupPageHelp/SetupPageHelp.spec.tsx
index 4f825648..838e5a0b 100644
--- a/src/components/modules/NavigationModule/Navigation/test.tsx
+++ b/src/components/modules/SetupModule/SetupPageHelp/SetupPageHelp.spec.tsx
@@ -1,5 +1,5 @@
/*
-Copyright (C) 2017 Cloudbase Solutions SRL
+Copyright (C) 2023 Cloudbase Solutions SRL
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
@@ -13,17 +13,14 @@ along with this program. If not, see .
*/
import React from "react";
-import { shallow } from "enzyme";
-import TW from "@src/utils/TestWrapper";
-import Navigation from ".";
-const wrap = props => new TW(shallow(), "navigation");
+import { render } from "@testing-library/react";
-describe("Navigation Component", () => {
- it("selects the current page", () => {
- const wrapper = wrap({ currentPage: "endpoints" });
- expect(wrapper.find("item-endpoints").prop("selected")).toBe(true);
- expect(wrapper.find("item-replicas").prop("selected")).toBe(false);
- expect(wrapper.find("item-migrations").prop("selected")).toBe(false);
+import SetupPageHelp from ".";
+
+describe("SetupPageHelp", () => {
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText("Coriolis® Help")).toBeTruthy();
});
});
diff --git a/src/components/modules/SetupModule/SetupPageHelp/SetupPageHelp.tsx b/src/components/modules/SetupModule/SetupPageHelp/SetupPageHelp.tsx
index f401f79b..fe0119b0 100644
--- a/src/components/modules/SetupModule/SetupPageHelp/SetupPageHelp.tsx
+++ b/src/components/modules/SetupModule/SetupPageHelp/SetupPageHelp.tsx
@@ -36,7 +36,7 @@ const OpenInNewIconWrapper = styled.div`
transform: scale(0.6);
`;
type Props = {
- style: React.CSSProperties;
+ style?: React.CSSProperties;
};
@observer
@@ -49,7 +49,7 @@ class SetupPageHelp extends React.Component {
Click the link below to view the Coriolis® documentation. There you
can find all the help you need to get you started.
-
+
Coriolis® Documentation
.
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+import TestUtils from "@tests/TestUtils";
+
+import SetupPageLegal from "./";
+
+jest.mock("@src/utils/Config", () => ({
+ config: {
+ providerSortPriority: {
+ openstack: 2,
+ vmware_vsphere: 1,
+ },
+ providerNames: {
+ openstack: "OpenStack",
+ vmware_vsphere: "VMware vSphere",
+ },
+ },
+}));
+
+describe("SetupPageLegal", () => {
+ let defaultProps: SetupPageLegal["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ licenceType: "trial",
+ customerInfoTrial: {
+ interestedIn: "migrations",
+ sourcePlatform: "vmware_vsphere",
+ destinationPlatform: "openstack",
+ },
+ onCustomerInfoChange: jest.fn(),
+ onLegalChange: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText("Coriolis® Trial License")).toBeTruthy();
+ });
+
+ it("fires interestedIn change event", () => {
+ const { rerender } = render();
+ const findInputByLabel = (label: string) =>
+ Array.from(document.querySelectorAll("label"))
+ .find(el => el.textContent?.includes(label))!
+ .querySelector("input")!;
+
+ const replicasInput = findInputByLabel("Replicas");
+ expect(replicasInput).toBeTruthy();
+ replicasInput.click();
+
+ expect(defaultProps.onCustomerInfoChange).toHaveBeenCalledWith(
+ "interestedIn",
+ "replicas"
+ );
+
+ const bothInput = findInputByLabel("Both");
+ expect(bothInput).toBeTruthy();
+ bothInput.click();
+
+ expect(defaultProps.onCustomerInfoChange).toHaveBeenCalledWith(
+ "interestedIn",
+ "both"
+ );
+
+ rerender(
+
+ );
+
+ const migrationsInput = findInputByLabel("Migrations");
+ expect(migrationsInput).toBeTruthy();
+ migrationsInput.click();
+
+ expect(defaultProps.onCustomerInfoChange).toHaveBeenCalledWith(
+ "interestedIn",
+ "migrations"
+ );
+ });
+
+ it("fires legal agreement change event", () => {
+ render();
+ const findCheckboxByText = (text: string) =>
+ Array.from(TestUtils.selectAll("Checkbox__Wrapper")).find(el =>
+ el.parentElement?.textContent?.includes(text)
+ )!;
+
+ const privacyCheckbox = findCheckboxByText("Privacy Policy");
+ expect(privacyCheckbox).toBeTruthy();
+
+ privacyCheckbox.click();
+ expect(defaultProps.onLegalChange).toHaveBeenCalledWith(false);
+
+ const eulaCheckbox = findCheckboxByText("EULA");
+ expect(eulaCheckbox).toBeTruthy();
+
+ eulaCheckbox.click();
+ expect(defaultProps.onLegalChange).toHaveBeenCalledWith(true);
+
+ const findLabelByText = (text: string) =>
+ Array.from(TestUtils.selectAll("SetupPageLegal__CheckboxLabel")).find(
+ el => el.textContent?.includes(text)
+ )!;
+
+ const privacyLabel = findLabelByText("Privacy Policy");
+ expect(privacyLabel).toBeTruthy();
+ privacyLabel.click();
+ expect(defaultProps.onLegalChange).toHaveBeenCalledWith(false);
+
+ const eulaLabel = findLabelByText("EULA");
+ expect(eulaLabel).toBeTruthy();
+ eulaLabel.click();
+ expect(defaultProps.onLegalChange).toHaveBeenCalledWith(false);
+ });
+
+ it.each`
+ platformType | itemIndex | expectedProvider
+ ${"Source"} | ${2} | ${"openstack"}
+ ${"Destination"} | ${3} | ${"aws"}
+ `(
+ "fires $platformType platform change event",
+ ({ platformType, itemIndex, expectedProvider }) => {
+ render();
+ const platformDropdown = Array.from(
+ TestUtils.selectAll("Dropdown__Wrapper")
+ ).find(el =>
+ el.parentElement?.parentElement?.textContent?.includes(
+ `${platformType} Platform`
+ )
+ )!;
+
+ expect(platformDropdown).toBeTruthy();
+ TestUtils.select("DropdownButton__Wrapper", platformDropdown)!.click();
+ TestUtils.selectAll("Dropdown__ListItem-")[itemIndex].click();
+
+ expect(defaultProps.onCustomerInfoChange).toHaveBeenCalledWith(
+ `${platformType.toLowerCase()}Platform`,
+ expectedProvider
+ );
+ }
+ );
+});
diff --git a/src/components/modules/SetupModule/SetupPageLicence/SetupPageLicence.spec.tsx b/src/components/modules/SetupModule/SetupPageLicence/SetupPageLicence.spec.tsx
new file mode 100644
index 00000000..f3874ebf
--- /dev/null
+++ b/src/components/modules/SetupModule/SetupPageLicence/SetupPageLicence.spec.tsx
@@ -0,0 +1,99 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { fireEvent, render } from "@testing-library/react";
+import TestUtils from "@tests/TestUtils";
+
+import SetupPageLicence from "./";
+
+describe("SetupPageLicence", () => {
+ let defaultProps: SetupPageLicence["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ customerInfo: {
+ fullName: "John Doe",
+ email: "email@email.com",
+ company: "Company",
+ country: "RO",
+ },
+ highlightEmail: false,
+ highlightEmptyFields: false,
+ licenceType: "trial",
+ onUpdateCustomerInfo: jest.fn(),
+ onSubmit: jest.fn(),
+ onLicenceTypeChange: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ render();
+ const fullNameInput = Array.from(
+ TestUtils.selectAll("SetupPageInputWrapper__Label")
+ )
+ .find(el => el.textContent?.includes("Full name"))!
+ .parentElement?.querySelector("input")!;
+
+ expect(fullNameInput).toBeTruthy();
+ expect(fullNameInput.value).toBe("John Doe");
+ });
+
+ it("submits form", () => {
+ render();
+ fireEvent.submit(document.querySelector("form")!);
+
+ expect(defaultProps.onSubmit).toHaveBeenCalled();
+ });
+
+ it.each`
+ label | fieldName | newValue
+ ${"Full name"} | ${"fullName"} | ${"New Name"}
+ ${"Email"} | ${"email"} | ${"new@email.com"}
+ ${"Company"} | ${"company"} | ${"New Company"}
+ `("fires $label change event", ({ label, fieldName, newValue }) => {
+ render();
+ const findInputByLabel = (label: string) =>
+ Array.from(TestUtils.selectAll("SetupPageInputWrapper__Label"))
+ .find(el => el.textContent?.includes(label))!
+ .parentElement!.querySelector("input")!;
+
+ const input = findInputByLabel(label);
+ fireEvent.change(input, { target: { value: newValue } });
+
+ expect(defaultProps.onUpdateCustomerInfo).toHaveBeenCalledWith(
+ fieldName,
+ newValue
+ );
+ });
+
+ it("fires country change event", async () => {
+ render();
+ const countryInput = Array.from(
+ TestUtils.selectAll("SetupPageInputWrapper__Label")
+ )
+ .find(el => el.textContent?.includes("Country"))!
+ .parentElement?.querySelector("input")!;
+
+ fireEvent.change(countryInput, { target: { value: "Unite" } });
+
+ fireEvent.click(TestUtils.selectAll("AutocompleteDropdown__ListItem-")[1]);
+
+ expect(defaultProps.onUpdateCustomerInfo).toHaveBeenCalledWith(
+ "country",
+ "United Arab Emirates"
+ );
+ });
+});
diff --git a/src/components/modules/WizardModule/WizardPageContent/test.tsx b/src/components/modules/SetupModule/SetupPageModuleWrapper/SetupPageModuleWrapper.spec.tsx
similarity index 50%
rename from src/components/modules/WizardModule/WizardPageContent/test.tsx
rename to src/components/modules/SetupModule/SetupPageModuleWrapper/SetupPageModuleWrapper.spec.tsx
index b34ba4f7..d408db44 100644
--- a/src/components/modules/WizardModule/WizardPageContent/test.tsx
+++ b/src/components/modules/SetupModule/SetupPageModuleWrapper/SetupPageModuleWrapper.spec.tsx
@@ -1,5 +1,5 @@
/*
-Copyright (C) 2017 Cloudbase Solutions SRL
+Copyright (C) 2023 Cloudbase Solutions SRL
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
@@ -13,25 +13,24 @@ along with this program. If not, see .
*/
import React from "react";
-import { shallow } from "enzyme";
-import TW from "@src/utils/TestWrapper";
-import WizardPageContent from ".";
-const wrap = (props: any) =>
- new TW(
- shallow(
- {}} {...props} />
- ),
- "wpContent"
- );
+import { render } from "@testing-library/react";
-describe("WizardPageContent Component", () => {
- it("renders wizard type page", () => {
- const wrapper = wrap({
- page: { id: "type", title: "Wizard Type" },
- type: "replica",
- });
- expect(wrapper.findText("header")).toBe("Wizard Type Replica");
- expect(wrapper.shallow.find("WizardType").prop("selected")).toBe("replica");
+import SetupPageModuleWrapper from ".";
+
+describe("SetupPageModuleWrapper", () => {
+ let defaultProps: SetupPageModuleWrapper["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ children:
children
,
+ actions:
actions
,
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText("children")).toBeTruthy();
+ expect(getByText("actions")).toBeTruthy();
});
});
diff --git a/src/components/modules/NavigationModule/NavigationMini/test.tsx b/src/components/modules/SetupModule/SetupPageWelcome/SetupPageWelcome.spec.tsx
similarity index 56%
rename from src/components/modules/NavigationModule/NavigationMini/test.tsx
rename to src/components/modules/SetupModule/SetupPageWelcome/SetupPageWelcome.spec.tsx
index 6e5ad4ec..6e56054d 100644
--- a/src/components/modules/NavigationModule/NavigationMini/test.tsx
+++ b/src/components/modules/SetupModule/SetupPageWelcome/SetupPageWelcome.spec.tsx
@@ -1,5 +1,5 @@
/*
-Copyright (C) 2017 Cloudbase Solutions SRL
+Copyright (C) 2023 Cloudbase Solutions SRL
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
@@ -13,19 +13,21 @@ along with this program. If not, see .
*/
import React from "react";
-import { shallow } from "enzyme";
-import TW from "@src/utils/TestWrapper";
-import NavigationMini, { TEST_ID } from ".";
+import { render } from "@testing-library/react";
+import TestUtils from "@tests/TestUtils";
-const wrap = () => new TW(shallow(), TEST_ID);
+import SetupPageWelcome from "./";
-describe("NavigationMini Component", () => {
- it("toggles the navigation state", () => {
- const wrapper = wrap();
- const button = () => wrapper.find("toggleButton");
- expect(button().prop("open")).toBe(false);
- button().simulate("click");
- expect(button().prop("open")).toBe(true);
+describe("SetupPageWelcome", () => {
+ let defaultProps: SetupPageWelcome["props"];
+
+ beforeEach(() => {
+ defaultProps = {};
+ });
+
+ it("renders without crashing", () => {
+ render();
+ expect(TestUtils.select("SetupPageWelcome__Wrapper")).toBeTruthy();
});
});
diff --git a/src/components/modules/SetupModule/ui/SetupPageBackButton/SetupPageBackButton.spec.tsx b/src/components/modules/SetupModule/ui/SetupPageBackButton/SetupPageBackButton.spec.tsx
new file mode 100644
index 00000000..b5f8202a
--- /dev/null
+++ b/src/components/modules/SetupModule/ui/SetupPageBackButton/SetupPageBackButton.spec.tsx
@@ -0,0 +1,37 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+import TestUtils from "@tests/TestUtils";
+
+import SetupPageBackButton from "./";
+
+describe("SetupPageBackButton", () => {
+ let defaultProps: SetupPageBackButton["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ onClick: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ render();
+ expect(
+ TestUtils.select("SetupPageBackButton__Wrapper")?.textContent
+ ).toContain("Back");
+ });
+});
diff --git a/src/components/modules/SetupModule/ui/SetupPagePasswordStrength/SetupPagePasswordStrength.spec.tsx b/src/components/modules/SetupModule/ui/SetupPagePasswordStrength/SetupPagePasswordStrength.spec.tsx
new file mode 100644
index 00000000..9a70eab2
--- /dev/null
+++ b/src/components/modules/SetupModule/ui/SetupPagePasswordStrength/SetupPagePasswordStrength.spec.tsx
@@ -0,0 +1,50 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { ThemePalette } from "@src/components/Theme";
+import { render } from "@testing-library/react";
+import TestUtils from "@tests/TestUtils";
+
+import SetupPagePasswordStrength from "./";
+
+describe("SetupPagePasswordStrength", () => {
+ let defaultProps: SetupPagePasswordStrength["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ value: "password",
+ };
+ });
+
+ it("renders without crashing", () => {
+ render();
+ expect(TestUtils.select("SetupPagePasswordStrength__Wrapper")).toBeTruthy();
+ });
+
+ it.each`
+ password | status | color
+ ${"a"} | ${"VERY_WEAK"} | ${ThemePalette.alert}
+ ${"A###d1!"} | ${"WEAK"} | ${ThemePalette.alert}
+ ${"A###$d123!"} | ${"REASONABLE"} | ${ThemePalette.warning}
+ ${"AmweyQe$d123!"} | ${"STRONG"} | ${"#758400"}
+ ${"AmwueyQe$d123!"} | ${"VERY_STRONG"} | ${"green"}
+ `("renders $color for $status password: $password", ({ password, color }) => {
+ render();
+ const bar = TestUtils.select("SetupPagePasswordStrength__Bar")!;
+ const background = window.getComputedStyle(bar).background;
+ expect(TestUtils.rgbToHex(background)).toBe(color);
+ });
+});
diff --git a/src/components/modules/TemplateModule/DetailsTemplate/DetailsTemplate.spec.tsx b/src/components/modules/TemplateModule/DetailsTemplate/DetailsTemplate.spec.tsx
new file mode 100644
index 00000000..aff7e44c
--- /dev/null
+++ b/src/components/modules/TemplateModule/DetailsTemplate/DetailsTemplate.spec.tsx
@@ -0,0 +1,38 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+
+import DetailsTemplate, { Props } from ".";
+
+describe("DetailsTemplate", () => {
+ let defaultProps: Props;
+
+ beforeEach(() => {
+ defaultProps = {
+ pageHeaderComponent:
pageHeaderComponent
,
+ contentHeaderComponent:
contentHeaderComponent
,
+ contentComponent:
contentComponent
,
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText("pageHeaderComponent")).toBeTruthy();
+ expect(getByText("contentHeaderComponent")).toBeTruthy();
+ expect(getByText("contentComponent")).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/TemplateModule/DetailsTemplate/DetailsTemplate.tsx b/src/components/modules/TemplateModule/DetailsTemplate/DetailsTemplate.tsx
index a2baaae5..31f55d85 100644
--- a/src/components/modules/TemplateModule/DetailsTemplate/DetailsTemplate.tsx
+++ b/src/components/modules/TemplateModule/DetailsTemplate/DetailsTemplate.tsx
@@ -27,7 +27,7 @@ const Content = styled.div`
flex-direction: column;
min-height: 0;
`;
-type Props = {
+export type Props = {
pageHeaderComponent: React.ReactNode;
contentHeaderComponent: React.ReactNode;
contentComponent: React.ReactNode;
diff --git a/src/components/modules/TemplateModule/EmptyTemplate/EmptyTemplate.spec.tsx b/src/components/modules/TemplateModule/EmptyTemplate/EmptyTemplate.spec.tsx
new file mode 100644
index 00000000..f2008a4f
--- /dev/null
+++ b/src/components/modules/TemplateModule/EmptyTemplate/EmptyTemplate.spec.tsx
@@ -0,0 +1,30 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+
+import EmptyTemplate from ".";
+
+describe("EmptyTemplate", () => {
+ it("renders without crashing", () => {
+ const { getByText } = render(
+
+
contentComponent
+
+ );
+ expect(getByText("contentComponent")).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/TemplateModule/MainTemplate/MainTemplate.spec.tsx b/src/components/modules/TemplateModule/MainTemplate/MainTemplate.spec.tsx
new file mode 100644
index 00000000..9b3bc803
--- /dev/null
+++ b/src/components/modules/TemplateModule/MainTemplate/MainTemplate.spec.tsx
@@ -0,0 +1,34 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+
+import MainTemplate from ".";
+
+describe("MainTemplate", () => {
+ it("renders without crashing", () => {
+ const { getByText } = render(
+ navigationComponent}
+ headerComponent={
headerComponent
}
+ listComponent={
listComponent
}
+ />
+ );
+ expect(getByText("navigationComponent")).toBeTruthy();
+ expect(getByText("headerComponent")).toBeTruthy();
+ expect(getByText("listComponent")).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/TemplateModule/WizardTemplate/WizardTemplate.spec.tsx b/src/components/modules/TemplateModule/WizardTemplate/WizardTemplate.spec.tsx
new file mode 100644
index 00000000..42d78693
--- /dev/null
+++ b/src/components/modules/TemplateModule/WizardTemplate/WizardTemplate.spec.tsx
@@ -0,0 +1,32 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+
+import WizardTemplate from ".";
+
+describe("WizardTemplate", () => {
+ it("renders without crashing", () => {
+ const { getByText } = render(
+ pageHeaderComponent}
+ pageContentComponent={
pageContentComponent
}
+ />
+ );
+ expect(getByText("pageHeaderComponent")).toBeTruthy();
+ expect(getByText("pageContentComponent")).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/TransferModule/DeleteReplicaModal/DeleteReplicaModal.spec.tsx b/src/components/modules/TransferModule/DeleteReplicaModal/DeleteReplicaModal.spec.tsx
new file mode 100644
index 00000000..eb5348de
--- /dev/null
+++ b/src/components/modules/TransferModule/DeleteReplicaModal/DeleteReplicaModal.spec.tsx
@@ -0,0 +1,59 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+import TestUtils from "@tests/TestUtils";
+
+import DeleteReplicaModal from "./";
+
+describe("DeleteReplicaModal", () => {
+ let defaultProps: DeleteReplicaModal["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ hasDisks: false,
+ onDeleteReplica: jest.fn(),
+ onDeleteDisks: jest.fn(),
+ onRequestClose: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText("Delete Replica")).toBeTruthy();
+ });
+
+ it("renders with disks", () => {
+ render();
+ expect(
+ TestUtils.select("DeleteReplicaModal__ExtraMessage")?.textContent
+ ).toContain("has been executed at least once");
+ });
+
+ it("is multiple replica selection with disks", () => {
+ render(
+
+ );
+ expect(
+ TestUtils.select("DeleteReplicaModal__ExtraMessage")?.textContent
+ ).toContain("have been executed at least once");
+ });
+
+ it("renders loading", () => {
+ render();
+ expect(TestUtils.select("DeleteReplicaModal__Loading")).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/TransferModule/DeleteReplicaModal/DeleteReplicaModal.tsx b/src/components/modules/TransferModule/DeleteReplicaModal/DeleteReplicaModal.tsx
index d19fcd53..fa085c0b 100644
--- a/src/components/modules/TransferModule/DeleteReplicaModal/DeleteReplicaModal.tsx
+++ b/src/components/modules/TransferModule/DeleteReplicaModal/DeleteReplicaModal.tsx
@@ -12,16 +12,15 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
-import React from "react";
import { observer } from "mobx-react";
+import React from "react";
import styled from "styled-components";
-import Modal from "@src/components/ui/Modal";
+import { ThemePalette } from "@src/components/Theme";
import Button from "@src/components/ui/Button";
+import Modal from "@src/components/ui/Modal";
import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
-import { ThemePalette } from "@src/components/Theme";
-
const Wrapper = styled.div`
display: flex;
flex-direction: column;
diff --git a/src/components/modules/TransferModule/Executions/Executions.spec.tsx b/src/components/modules/TransferModule/Executions/Executions.spec.tsx
new file mode 100644
index 00000000..93b1f11d
--- /dev/null
+++ b/src/components/modules/TransferModule/Executions/Executions.spec.tsx
@@ -0,0 +1,230 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+import {
+ EXECUTION_MOCK,
+ EXECUTION_TASKS_MOCK,
+} from "@tests/mocks/ExecutionsMock";
+import { INSTANCE_MOCK } from "@tests/mocks/InstancesMock";
+import TestUtils from "@tests/TestUtils";
+
+import Executions from "./";
+
+describe("Executions", () => {
+ let defaultProps: Executions["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ executions: [EXECUTION_MOCK],
+ executionsTasks: [EXECUTION_TASKS_MOCK],
+ loading: false,
+ tasksLoading: false,
+ instancesDetails: [INSTANCE_MOCK],
+ onChange: jest.fn(),
+ onCancelExecutionClick: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText(EXECUTION_TASKS_MOCK.tasks[0].id)).toBeTruthy();
+ expect(getByText(EXECUTION_MOCK.id)).toBeTruthy();
+ });
+
+ it("sets selected execution on new props", () => {
+ const { getByText, rerender } = render();
+ rerender(
+
+ );
+ expect(getByText("new-id")).toBeTruthy();
+
+ rerender(
+
+ );
+ expect(getByText("new-id-2")).toBeTruthy();
+
+ rerender();
+ expect(getByText(EXECUTION_MOCK.id)).toBeTruthy();
+ });
+
+ it("renders with no executions", () => {
+ const { getByText, rerender } = render();
+ expect(getByText(EXECUTION_MOCK.id)).toBeTruthy();
+
+ rerender();
+ expect(getByText("This replica has not been executed yet.")).toBeTruthy();
+ });
+
+ it("doesn't dispatch onChange if no executions", () => {
+ const { rerender } = render(
+
+ );
+ rerender();
+ expect(defaultProps.onChange).not.toHaveBeenCalled();
+ });
+
+ it("handles previous executions", () => {
+ const { rerender } = render(
+
+ );
+ const previousArrow = () =>
+ TestUtils.selectAll(
+ "Arrow__Wrapper",
+ TestUtils.select("Timeline__Wrapper")!
+ )[0];
+ previousArrow().click();
+ expect(defaultProps.onChange).toHaveBeenLastCalledWith(EXECUTION_MOCK.id);
+
+ rerender();
+ previousArrow().click();
+ expect(defaultProps.onChange).toHaveBeenLastCalledWith(EXECUTION_MOCK.id);
+ });
+
+ it("doesn't handle previous executions in edge cases", () => {
+ const executionsComponent = new Executions(defaultProps);
+ executionsComponent.handlePreviousExecutionClick();
+ expect(defaultProps.onChange).not.toHaveBeenCalled();
+ });
+
+ it("handles next executions", () => {
+ render(
+
+ );
+ const nextArrow = TestUtils.selectAll(
+ "Arrow__Wrapper",
+ TestUtils.select("Timeline__Wrapper")!
+ )[1];
+ nextArrow.click();
+ expect(defaultProps.onChange).toHaveBeenLastCalledWith("new-id");
+
+ const previousArrow = () =>
+ TestUtils.selectAll(
+ "Arrow__Wrapper",
+ TestUtils.select("Timeline__Wrapper")!
+ )[0];
+ previousArrow().click();
+
+ nextArrow.click();
+ expect(defaultProps.onChange).toHaveBeenLastCalledWith("new-id");
+ });
+
+ it("doesn't handle next executions in edge cases", () => {
+ const executionsComponent = new Executions(defaultProps);
+ executionsComponent.handleNextExecutionClick();
+ expect(defaultProps.onChange).not.toHaveBeenCalled();
+ });
+
+ it("handles timeline item click", () => {
+ render(
+
+ );
+ const timelineItem = TestUtils.select("Timeline__Item-");
+ expect(timelineItem).toBeTruthy();
+ timelineItem!.click();
+ expect(defaultProps.onChange).toHaveBeenLastCalledWith(EXECUTION_MOCK.id);
+ });
+
+ it("handles cancel execution click", () => {
+ const newExecution = { ...EXECUTION_MOCK, id: "new-id", status: "RUNNING" };
+ render(
+
+ );
+ const cancelExecutionButton = Array.from(
+ document.querySelectorAll("button")
+ ).find(el => el.textContent === "Cancel Execution");
+ expect(cancelExecutionButton).toBeTruthy();
+ cancelExecutionButton!.click();
+ expect(defaultProps.onCancelExecutionClick).toHaveBeenCalledWith(
+ newExecution
+ );
+ });
+
+ it("force cancels execution", () => {
+ const newExecution = {
+ ...EXECUTION_MOCK,
+ id: "new-id",
+ status: "CANCELLING",
+ };
+ render(
+
+ );
+ const cancelExecutionButton = Array.from(
+ document.querySelectorAll("button")
+ ).find(el => el.textContent === "Force Cancel Execution");
+ expect(cancelExecutionButton).toBeTruthy();
+ cancelExecutionButton!.click();
+ expect(defaultProps.onCancelExecutionClick).toHaveBeenCalledWith(
+ newExecution,
+ true
+ );
+ });
+
+ it("renders loading", () => {
+ render();
+ expect(TestUtils.select("Executions__LoadingWrapper")).toBeTruthy();
+ });
+
+ it("deletes execution", () => {
+ const deleteExecution = jest.fn();
+ render(
+
+ );
+ const deleteExecutionButton = Array.from(
+ document.querySelectorAll("button")
+ ).find(el => el.textContent === "Delete Execution");
+ expect(deleteExecutionButton).toBeTruthy();
+ deleteExecutionButton!.click();
+ expect(deleteExecution).toHaveBeenCalledWith(EXECUTION_MOCK);
+ });
+});
diff --git a/src/components/modules/TransferModule/Executions/Executions.tsx b/src/components/modules/TransferModule/Executions/Executions.tsx
index 83040df3..9583a64a 100644
--- a/src/components/modules/TransferModule/Executions/Executions.tsx
+++ b/src/components/modules/TransferModule/Executions/Executions.tsx
@@ -129,16 +129,12 @@ class Executions extends React.Component {
if (this.props.executions.length > props.executions.length) {
const isSelectedAvailable = props.executions.find(
- e =>
- this.state.selectedExecution &&
- e.id === this.state.selectedExecution.id
+ e => e.id === this.state.selectedExecution?.id
);
if (!isSelectedAvailable) {
const lastIndex = this.props.executions
? this.props.executions.findIndex(
- e =>
- this.state.selectedExecution &&
- e.id === this.state.selectedExecution.id
+ e => e.id === this.state.selectedExecution?.id
)
: -1;
if (props.executions.length) {
diff --git a/src/components/modules/TransferModule/Executions/test.tsx b/src/components/modules/TransferModule/Executions/test.tsx
deleted file mode 100644
index a42e981f..00000000
--- a/src/components/modules/TransferModule/Executions/test.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import sinon from "sinon";
-import TW from "@src/utils/TestWrapper";
-import Executions from ".";
-
-const wrap = props => new TW(shallow(), "executions");
-
-const item = {
- executions: [
- { id: "execution-1", number: 1, status: "ERROR", created_at: new Date() },
- {
- id: "execution-2",
- number: 2,
- status: "COMPLETED",
- created_at: new Date(),
- },
- {
- id: "execution-3",
- number: 3,
- status: "CANCELED",
- created_at: new Date(),
- },
- { id: "execution-4", number: 4, status: "RUNNING", created_at: new Date() },
- ],
-};
-
-describe("Executions Component", () => {
- it("selects last execution by default", () => {
- const wrapper = wrap({ item });
- expect(wrapper.findText("number")).toBe("Execution #4");
- });
-
- it("selects previous execution on previous click", () => {
- const wrapper = wrap({ item });
- wrapper.find("timeline").simulate("previousClick");
- expect(wrapper.findText("number")).toBe("Execution #3");
- wrapper.find("timeline").simulate("previousClick");
- expect(wrapper.findText("number")).toBe("Execution #2");
- });
-
- it("selects next execution on next click", () => {
- const wrapper = wrap({ item });
- wrapper.find("timeline").simulate("previousClick");
- wrapper.find("timeline").simulate("previousClick");
- wrapper.find("timeline").simulate("nextClick");
- expect(wrapper.findText("number")).toBe("Execution #3");
- });
-
- it("doesn't select next execution on next click if not possible", () => {
- const wrapper = wrap({ item });
- wrapper.find("timeline").simulate("nextClick");
- expect(wrapper.findText("number")).toBe("Execution #4");
- });
-
- it("shows cancel button on running executions", () => {
- const wrapper = wrap({ item });
- expect(wrapper.find("cancelButton").length).toBe(1);
- expect(wrapper.find("deleteButton").length).toBe(0);
- });
-
- it("shows delete button on non-running executions", () => {
- const wrapper = wrap({ item });
- wrapper.find("timeline").simulate("previousClick");
- expect(wrapper.find("cancelButton").length).toBe(0);
- expect(wrapper.find("deleteButton").length).toBe(1);
- });
-
- it("dispatches cancel click", () => {
- const onCancelExecutionClick = sinon.spy();
- const wrapper = wrap({ item, onCancelExecutionClick });
- wrapper.find("cancelButton").simulate("click");
- expect(onCancelExecutionClick.calledOnce).toBe(true);
- });
-
- it("dispatches delete click", () => {
- const onDeleteExecutionClick = sinon.spy();
- const wrapper = wrap({ item, onDeleteExecutionClick });
- wrapper.find("timeline").simulate("previousClick");
- wrapper.find("deleteButton").simulate("click");
- expect(onDeleteExecutionClick.calledOnce).toBe(true);
- });
-
- it("renders no executions", () => {
- const wrapper = wrap({ item: {} });
- expect(wrapper.findText("noExTitle")).toBe(
- "It looks like there are no executions in this replica."
- );
- });
-
- it("dispatches execute click", () => {
- const onExecuteClick = sinon.spy();
- const wrapper = wrap({ item: {}, onExecuteClick });
- wrapper.find("executeButton").simulate("click");
- expect(onExecuteClick.calledOnce).toBe(true);
- });
-});
diff --git a/src/components/modules/TransferModule/MainDetails/MainDetails.spec.tsx b/src/components/modules/TransferModule/MainDetails/MainDetails.spec.tsx
new file mode 100644
index 00000000..5e61d724
--- /dev/null
+++ b/src/components/modules/TransferModule/MainDetails/MainDetails.spec.tsx
@@ -0,0 +1,105 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+import {
+ OPENSTACK_ENDPOINT_MOCK,
+ VMWARE_ENDPOINT_MOCK,
+} from "@tests/mocks/EndpointsMock";
+import { INSTANCE_MOCK } from "@tests/mocks/InstancesMock";
+import { MINION_POOL_MOCK } from "@tests/mocks/MinionPoolMock";
+import { STORAGE_BACKEND_MOCK } from "@tests/mocks/StoragesMock";
+import { REPLICA_MOCK } from "@tests/mocks/TransferMock";
+import TestUtils from "@tests/TestUtils";
+
+import MainDetails from "./";
+
+jest.mock("@src/components/modules/EndpointModule/EndpointLogos", () => ({
+ __esModule: true,
+ default: (props: any) =>
,
+ loading: false,
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText(REPLICA_MOCK.id)).toBeTruthy();
+ expect(getByText("Bottom controls")).toBeTruthy();
+ });
+
+ it("renders missing endpoint", () => {
+ const { getByText } = render(
+
+ );
+ expect(getByText("Endpoint is missing")).toBeTruthy();
+ });
+
+ it("renders loading", () => {
+ render();
+ expect(TestUtils.select("MainDetails__Loading")).toBeTruthy();
+ });
+
+ it("renders allocating minions error", () => {
+ render(
+
+ );
+ expect(
+ Array.from(document.querySelectorAll("*")).find(el =>
+ el.textContent?.includes("error allocating minion machines")
+ )
+ ).toBeTruthy();
+ });
+
+ it("shows password", () => {
+ const { getByText } = render();
+ const passwordEl = TestUtils.select("PasswordValue__Wrapper")!;
+ expect(passwordEl).toBeTruthy();
+ expect(passwordEl.textContent).toBe("•••••••••");
+
+ passwordEl.click();
+ expect(
+ getByText(REPLICA_MOCK.destination_environment.password)
+ ).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/TransferModule/MainDetails/test.tsx b/src/components/modules/TransferModule/MainDetails/test.tsx
deleted file mode 100644
index e6c624f8..00000000
--- a/src/components/modules/TransferModule/MainDetails/test.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import moment from "moment";
-import TW from "@src/utils/TestWrapper";
-import MainDetails from ".";
-
-const wrap = props =>
- new TW(shallow(), "mainDetails");
-
-const endpoints = [
- { id: "endpoint-1", name: "Endpoint OPS", type: "openstack" },
- { id: "endpoint-2", name: "Endpoint AZURE", type: "azure" },
-];
-const item = {
- origin_endpoint_id: "endpoint-1",
- destination_endpoint_id: "endpoint-2",
- id: "item-id",
- created_at: new Date(2017, 10, 24, 16, 15),
- instances: ["instance_1"],
- type: "Replica",
- notes: "A description",
-};
-const instancesDetails = [
- {
- instance_name: "instance_1",
- devices: { nics: [{ network_name: "network_1" }] },
- },
-];
-
-describe("MainDetails Component", () => {
- it("renders with endpoint missing", () => {
- const wrapper = wrap({ item: {}, endpoints: [] });
- expect(wrapper.findText("missing-source")).toBe(
- "Endpoint is missing"
- );
- expect(wrapper.findText("missing-target")).toBe(
- "Endpoint is missing"
- );
- });
-
- it("renders endpoint info", () => {
- const wrapper = wrap({ item, endpoints, instancesDetails });
- expect(wrapper.find("id").prop("value")).toBe("item-id");
- const localDate = moment(item.created_at).add(
- -new Date().getTimezoneOffset(),
- "minutes"
- );
- expect(wrapper.find("created").prop("value")).toBe(
- localDate.format("YYYY-MM-DD HH:mm:ss")
- );
- // expect(wrapper.find('name-source').shallow.dive().dive().text()).toBe('Endpoint OPS')
- // expect(wrapper.findText('name-target')).toBe('Endpoint AZURE')
- expect(wrapper.find("description").prop("value")).toBe("A description");
- });
-
- it("renders endpoints logos", () => {
- const wrapper = wrap({ item, endpoints, instancesDetails });
- expect(wrapper.find("sourceLogo").prop("endpoint")).toBe("openstack");
- expect(wrapper.find("targetLogo").prop("endpoint")).toBe("azure");
- });
-
- it("renders loading", () => {
- const wrapper = wrap({ item: {}, endpoints: [], loading: true });
- expect(wrapper.find("loading").length).toBe(1);
- });
-});
diff --git a/src/components/modules/TransferModule/MigrationDetailsContent/MigrationDetailsContent.spec.tsx b/src/components/modules/TransferModule/MigrationDetailsContent/MigrationDetailsContent.spec.tsx
new file mode 100644
index 00000000..c2e4bc9a
--- /dev/null
+++ b/src/components/modules/TransferModule/MigrationDetailsContent/MigrationDetailsContent.spec.tsx
@@ -0,0 +1,74 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+
+import MigrationDetailsContent from ".";
+import { MIGRATION_ITEM_DETAILS_MOCK } from "@tests/mocks/TransferMock";
+import { MINION_POOL_MOCK } from "@tests/mocks/MinionPoolMock";
+import { STORAGE_BACKEND_MOCK } from "@tests/mocks/StoragesMock";
+import { INSTANCE_MOCK } from "@tests/mocks/InstancesMock";
+import { NETWORK_MOCK } from "@tests/mocks/NetworksMock";
+import {
+ OPENSTACK_ENDPOINT_MOCK,
+ VMWARE_ENDPOINT_MOCK,
+} from "@tests/mocks/EndpointsMock";
+
+jest.mock("@src/components/modules/EndpointModule/EndpointLogos", () => ({
+ __esModule: true,
+ default: (props: any) =>
{props.endpoint}
,
+}));
+jest.mock("react-router-dom", () => ({ Link: "a" }));
+
+describe("MigrationDetailsContent", () => {
+ let defaultProps: MigrationDetailsContent["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ item: MIGRATION_ITEM_DETAILS_MOCK,
+ itemId: MIGRATION_ITEM_DETAILS_MOCK.id,
+ minionPools: [MINION_POOL_MOCK],
+ detailsLoading: false,
+ storageBackends: [STORAGE_BACKEND_MOCK],
+ instancesDetails: [INSTANCE_MOCK],
+ instancesDetailsLoading: false,
+ networks: [NETWORK_MOCK],
+ sourceSchema: [],
+ sourceSchemaLoading: false,
+ destinationSchema: [],
+ destinationSchemaLoading: false,
+ endpoints: [OPENSTACK_ENDPOINT_MOCK, VMWARE_ENDPOINT_MOCK],
+ page: "",
+ onDeleteMigrationClick: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText(MIGRATION_ITEM_DETAILS_MOCK.id)).toBeTruthy();
+ });
+
+ it("renders tasks page", () => {
+ const { getByText } = render(
+
+ );
+ expect(
+ getByText(
+ MIGRATION_ITEM_DETAILS_MOCK.tasks[0].task_type.replace("_", " ")
+ )
+ ).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/TransferModule/MigrationDetailsContent/MigrationDetailsContent.tsx b/src/components/modules/TransferModule/MigrationDetailsContent/MigrationDetailsContent.tsx
index e931c1ca..27cb7711 100644
--- a/src/components/modules/TransferModule/MigrationDetailsContent/MigrationDetailsContent.tsx
+++ b/src/components/modules/TransferModule/MigrationDetailsContent/MigrationDetailsContent.tsx
@@ -12,23 +12,22 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
-import React from "react";
import { observer } from "mobx-react";
+import React from "react";
import styled from "styled-components";
-import Button from "@src/components/ui/Button";
+import { MigrationItemDetails } from "@src/@types/MainItem";
+import { MinionPool } from "@src/@types/MinionPool";
+import { Network } from "@src/@types/Network";
import DetailsNavigation from "@src/components/modules/NavigationModule/DetailsNavigation";
import MainDetails from "@src/components/modules/TransferModule/MainDetails";
import Tasks from "@src/components/modules/TransferModule/Tasks";
+import { ThemeProps } from "@src/components/Theme";
+import Button from "@src/components/ui/Button";
import type { Instance } from "@src/@types/Instance";
import type { Endpoint, StorageBackend } from "@src/@types/Endpoint";
import type { Field } from "@src/@types/Field";
-import { MigrationItemDetails } from "@src/@types/MainItem";
-import { MinionPool } from "@src/@types/MinionPool";
-import { Network } from "@src/@types/Network";
-import { ThemeProps } from "@src/components/Theme";
-
const Wrapper = styled.div`
display: flex;
justify-content: center;
diff --git a/src/components/modules/TransferModule/MigrationDetailsContent/test.tsx b/src/components/modules/TransferModule/MigrationDetailsContent/test.tsx
deleted file mode 100644
index 9b17ec00..00000000
--- a/src/components/modules/TransferModule/MigrationDetailsContent/test.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import sinon from "sinon";
-import TW from "@src/utils/TestWrapper";
-import MigrationDetailsContent from ".";
-
-const wrap = props =>
- new TW(shallow(), "mdContent");
-
-const tasks = [
- {
- progress_updates: [
- { message: "the task has a progress of 50%", created_at: new Date() },
- { message: "the task is almost done", created_at: new Date() },
- ],
- exception_details: "Exception details",
- status: "RUNNING",
- created_at: new Date(),
- depends_on: ["depends on id"],
- id: "task-2",
- task_type: "Task name 2",
- },
-];
-const endpoints = [
- { id: "endpoint-1", name: "Endpoint OPS", type: "openstack" },
- { id: "endpoint-2", name: "Endpoint AZURE", type: "azure" },
-];
-const item = {
- origin_endpoint_id: "endpoint-1",
- destination_endpoint_id: "endpoint-2",
- id: "item-id",
- created_at: new Date(2017, 10, 24, 16, 15),
- tasks,
- destination_environment: { description: "A description" },
- type: "Migration",
-};
-
-describe("MigrationDetailsContent Component", () => {
- it("renders main details page", () => {
- const wrapper = wrap({ endpoints, item, page: "" });
- expect(wrapper.find("mainDetails").prop("item").id).toBe("item-id");
- });
-
- it("renders tasks page", () => {
- const wrapper = wrap({ endpoints, item, page: "tasks" });
- expect(wrapper.find("tasks").prop("items")[0].id).toBe("task-2");
- });
-
- it("renders details loading", () => {
- const wrapper = wrap({ endpoints, item, page: "", detailsLoading: true });
- expect(wrapper.find("mainDetails").prop("loading")).toBe(true);
- });
-
- it("dispatches delete click", () => {
- const onDeleteMigrationClick = sinon.spy();
- const wrapper = wrap({ endpoints, item, page: "", onDeleteMigrationClick });
- wrapper
- .find("mainDetails")
- .prop("bottomControls")
- .props.children.props.onClick();
- expect(onDeleteMigrationClick.calledOnce).toBe(true);
- });
-});
diff --git a/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.spec.tsx b/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.spec.tsx
new file mode 100644
index 00000000..12728dd4
--- /dev/null
+++ b/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.spec.tsx
@@ -0,0 +1,131 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import Schedule from "@src/components/modules/TransferModule/Schedule";
+import ScheduleStore from "@src/stores/ScheduleStore";
+import { render } from "@testing-library/react";
+import {
+ OPENSTACK_ENDPOINT_MOCK,
+ VMWARE_ENDPOINT_MOCK,
+} from "@tests/mocks/EndpointsMock";
+import {
+ EXECUTION_MOCK,
+ EXECUTION_TASKS_MOCK,
+} from "@tests/mocks/ExecutionsMock";
+import { INSTANCE_MOCK } from "@tests/mocks/InstancesMock";
+import { MINION_POOL_MOCK } from "@tests/mocks/MinionPoolMock";
+import { NETWORK_MOCK } from "@tests/mocks/NetworksMock";
+import { STORAGE_BACKEND_MOCK } from "@tests/mocks/StoragesMock";
+import { REPLICA_ITEM_DETAILS_MOCK } from "@tests/mocks/TransferMock";
+
+import ReplicaDetailsContent from "./";
+
+const scheduleStoreMock = jest.createMockFromModule(
+ "@src/stores/ScheduleStore"
+);
+
+jest.mock("@src/components/modules/EndpointModule/EndpointLogos", () => ({
+ __esModule: true,
+ default: (props: any) =>
+ ),
+}));
+
+describe("Tasks", () => {
+ let defaultProps: Tasks["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ items: [
+ { ...TASK_MOCK },
+ { ...TASK_MOCK, id: "task-2", depends_on: ["task-id"] },
+ ],
+ instancesDetails: [INSTANCE_MOCK],
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText("Task")).toBeTruthy();
+ });
+
+ it("opens running task, closes when not running", () => {
+ const { rerender, getByTestId } = render();
+ const taskItem = getByTestId("TaskItem-task-2");
+ expect(taskItem).toBeTruthy();
+ expect(taskItem.textContent).toContain("Open: false");
+
+ rerender(
+
+ );
+ expect(taskItem.textContent).toContain("Open: true");
+
+ rerender();
+ expect(taskItem.textContent).toContain("Open: false");
+ });
+
+ it("handles a little drag as mouse click", () => {
+ const { getByTestId } = render();
+ const taskItem = getByTestId("TaskItem-task-id");
+ expect(taskItem).toBeTruthy();
+ expect(taskItem.textContent).toContain("Open: false");
+
+ fireEvent.mouseDown(taskItem);
+ fireEvent.mouseUp(taskItem);
+ expect(taskItem.textContent).toContain("Open: true");
+
+ fireEvent.mouseDown(taskItem);
+ fireEvent.mouseUp(taskItem);
+ expect(taskItem.textContent).toContain("Open: false");
+ });
+
+ it("handles depends on click", () => {
+ const { getByTestId } = render();
+ const firstTaskItem = getByTestId("TaskItem-task-id");
+ const secondTaskItem = getByTestId("TaskItem-task-2");
+ expect(firstTaskItem).toBeTruthy();
+ expect(secondTaskItem).toBeTruthy();
+ expect(firstTaskItem.textContent).toContain("Open: false");
+ expect(secondTaskItem.textContent).toContain("Open: false");
+
+ fireEvent.mouseDown(secondTaskItem);
+ fireEvent.mouseUp(secondTaskItem);
+ expect(secondTaskItem.textContent).toContain("Open: true");
+ const dependsOn = secondTaskItem.querySelector(
+ "[data-testid='TaskItem-DependsOn']"
+ ) as HTMLElement;
+ expect(dependsOn).toBeTruthy();
+ dependsOn!.click();
+ expect(firstTaskItem.textContent).toContain("Open: true");
+ });
+
+ it("renders loading", () => {
+ render();
+ expect(TestUtils.select("Tasks__LoadingWrapper")).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/TransferModule/Tasks/Tasks.tsx b/src/components/modules/TransferModule/Tasks/Tasks.tsx
index 04bd0655..7ebe415a 100644
--- a/src/components/modules/TransferModule/Tasks/Tasks.tsx
+++ b/src/components/modules/TransferModule/Tasks/Tasks.tsx
@@ -12,18 +12,17 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
-import React from "react";
import { observer } from "mobx-react";
+import React from "react";
import styled from "styled-components";
+import { Instance } from "@src/@types/Instance";
import TaskItem from "@src/components/modules/TransferModule/TaskItem";
-
-import type { Task } from "@src/@types/Task";
import { ThemePalette, ThemeProps } from "@src/components/Theme";
import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
-import { Instance } from "@src/@types/Instance";
-const ColumnWidths = ["26%", "18%", "36%", "20%"];
+import type { Task } from "@src/@types/Task";
+const COLUMN_WIDTHS = ["26%", "18%", "36%", "20%"];
const Wrapper = styled.div``;
const ContentWrapper = styled.div`
@@ -145,10 +144,10 @@ class Tasks extends React.Component {
renderHeader() {
return (
- Task
- Instance
- Latest Message
- Timestamp
+ Task
+ Instance
+ Latest Message
+ Timestamp
);
}
@@ -164,7 +163,7 @@ class Tasks extends React.Component {
item={item}
otherItems={this.props.items.filter(i => i.id !== item.id)}
instancesDetails={this.props.instancesDetails}
- columnWidths={ColumnWidths}
+ columnWidths={COLUMN_WIDTHS}
open={Boolean(this.state.openedItems.find(i => i.id === item.id))}
onDependsOnClick={id => {
this.handleDependsOnClick(id);
diff --git a/src/components/modules/TransferModule/Tasks/test.tsx b/src/components/modules/TransferModule/Tasks/test.tsx
deleted file mode 100644
index 7541774f..00000000
--- a/src/components/modules/TransferModule/Tasks/test.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import TW from "@src/utils/TestWrapper";
-import Tasks from ".";
-
-const wrap = props => new TW(shallow(), "tasks");
-
-const items = [
- {
- progress_updates: [
- { message: "the task has a progress of 10%", created_at: new Date() },
- ],
- exception_details: "Exception details",
- status: "COMPLETED",
- created_at: new Date(),
- depends_on: ["depends on id"],
- id: "task-1",
- task_type: "Task name 1",
- },
- {
- progress_updates: [
- { message: "the task has a progress of 50%", created_at: new Date() },
- { message: "the task is almost done", created_at: new Date() },
- ],
- exception_details: "Exception details",
- status: "CANCELED",
- created_at: new Date(),
- depends_on: ["depends on id"],
- id: "task-2",
- task_type: "Task name 2",
- },
- {
- progress_updates: [
- { message: "the task has a progress of 50%", created_at: new Date() },
- { message: "the task is almost done", created_at: new Date() },
- ],
- exception_details: "Exception details",
- status: "ERROR",
- created_at: new Date(),
- depends_on: ["depends on id"],
- id: "task-3",
- task_type: "Task name 3",
- },
- {
- progress_updates: [
- { message: "the task has a progress of 50%", created_at: new Date() },
- { message: "the task is almost done", created_at: new Date() },
- ],
- exception_details: "Exception details",
- status: "RUNNING",
- created_at: new Date(),
- depends_on: ["depends on id"],
- id: "task-4",
- task_type: "Task name 4",
- },
- {
- progress_updates: [
- { message: "the task has a progress of 50%", created_at: new Date() },
- { message: "the task is almost done", created_at: new Date() },
- ],
- exception_details: "Exception details",
- status: "PENDING",
- created_at: new Date(),
- depends_on: ["depends on id"],
- id: "task-5",
- task_type: "Task name 5",
- },
-];
-
-describe("Tasks Component", () => {
- it("renders correct number of task items", () => {
- const wrapper = wrap({ items });
- items.forEach(item => {
- expect(wrapper.find(`item-${item.id}`).prop("item").id).toBe(item.id);
- });
- });
-
- it("renders only running task opened", () => {
- const wrapper = wrap({ items });
- expect(wrapper.find("item-task-1").prop("open")).toBe(false);
- expect(wrapper.find("item-task-2").prop("open")).toBe(false);
- expect(wrapper.find("item-task-3").prop("open")).toBe(false);
- expect(wrapper.find("item-task-4").prop("open")).toBe(true);
- expect(wrapper.find("item-task-5").prop("open")).toBe(false);
- });
-
- it("renders correct info in task item", () => {
- const wrapper = wrap({ items });
- expect(wrapper.find("item-task-3").prop("item").id).toBe("task-3");
- expect(wrapper.find("item-task-5").prop("item").task_type).toBe(
- "Task name 5"
- );
- expect(wrapper.find("item-task-1").prop("item").status).toBe("COMPLETED");
- });
-});
diff --git a/src/components/modules/TransferModule/Timeline/test.tsx b/src/components/modules/TransferModule/Timeline/test.tsx
deleted file mode 100644
index f629e565..00000000
--- a/src/components/modules/TransferModule/Timeline/test.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import sinon from "sinon";
-import moment from "moment";
-import TW from "@src/utils/TestWrapper";
-import Timeline from ".";
-
-const wrap = props => new TW(shallow(), "timeline");
-
-const items = [
- { id: "item-1", status: "ERROR", created_at: new Date(2017, 1, 2) },
- { id: "item-2", status: "COMPLETED", created_at: new Date(2017, 2, 3) },
- { id: "item-3", status: "RUNNING", created_at: new Date(2017, 3, 4) },
-];
-
-describe("Timeline Component", () => {
- it("renders with correct dates", () => {
- const wrapper = wrap({ items, selectedItem: items[2] });
- expect(wrapper.findPartialId("label-").length).toBe(items.length);
- items.forEach(item => {
- expect(wrapper.findText(`label-${item.id}`)).toBe(
- moment(item.created_at).format("DD MMM YYYY")
- );
- });
- });
-
- it("dispatches item click", () => {
- const onItemClick = sinon.spy();
- const wrapper = wrap({ items, selectedItem: items[2], onItemClick });
- wrapper.find(`item-${items[1].id}`).simulate("click");
- expect(onItemClick.args[0][0].id).toBe("item-2");
- });
-
- it("dispatches next and previous click", () => {
- const onPreviousClick = sinon.spy();
- const onNextClick = sinon.spy();
- const wrapper = wrap({
- items,
- selectedItem: items[2],
- onPreviousClick,
- onNextClick,
- });
- wrapper.find("previous").simulate("click");
- wrapper.find("next").simulate("click");
- expect(onPreviousClick.calledOnce).toBe(true);
- expect(onNextClick.calledOnce).toBe(true);
- });
-});
diff --git a/src/components/modules/TransferModule/TransferDetailsTable/TransferDetailsTable.spec.tsx b/src/components/modules/TransferModule/TransferDetailsTable/TransferDetailsTable.spec.tsx
new file mode 100644
index 00000000..9ef58b3f
--- /dev/null
+++ b/src/components/modules/TransferModule/TransferDetailsTable/TransferDetailsTable.spec.tsx
@@ -0,0 +1,91 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+import { INSTANCE_MOCK } from "@tests/mocks/InstancesMock";
+import { MINION_POOL_MOCK } from "@tests/mocks/MinionPoolMock";
+import { NETWORK_MOCK } from "@tests/mocks/NetworksMock";
+import { STORAGE_BACKEND_MOCK } from "@tests/mocks/StoragesMock";
+import { REPLICA_MOCK } from "@tests/mocks/TransferMock";
+import TestUtils from "@tests/TestUtils";
+
+import TransferDetailsTable from "./";
+
+describe("TransferDetailsTable", () => {
+ let defaultProps: TransferDetailsTable["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ item: REPLICA_MOCK,
+ instancesDetails: [INSTANCE_MOCK],
+ networks: [NETWORK_MOCK],
+ minionPools: [MINION_POOL_MOCK],
+ storageBackends: [STORAGE_BACKEND_MOCK],
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText("Source")).toBeTruthy();
+ expect(getByText("Target")).toBeTruthy();
+ });
+
+ it("renders without crashing when no transfer result and no disabled disks", () => {
+ const { getByText } = render(
+
+ );
+ expect(getByText("Source")).toBeTruthy();
+ expect(getByText("Target")).toBeTruthy();
+ });
+
+ it("handles row click", () => {
+ render();
+ const rows = TestUtils.selectAll("TransferDetailsTable__Row-");
+ expect(rows[0]).toBeTruthy();
+ expect(rows[1]).toBeTruthy();
+ const firstArrow = () => TestUtils.select("Arrow__Wrapper-", rows[0])!;
+ const secondArrow = () => TestUtils.select("Arrow__Wrapper-", rows[1])!;
+ expect(firstArrow()).toBeTruthy();
+ expect(secondArrow()).toBeTruthy();
+
+ expect(firstArrow().getAttribute("orientation")).toBe("down");
+ rows[0].click();
+ expect(firstArrow().getAttribute("orientation")).toBe("up");
+
+ expect(secondArrow().getAttribute("orientation")).toBe("down");
+ rows[1].click();
+ expect(secondArrow().getAttribute("orientation")).toBe("up");
+ rows[1].click();
+ expect(secondArrow().getAttribute("orientation")).toBe("down");
+ });
+});
diff --git a/src/components/modules/TransferModule/TransferDetailsTable/TransferDetailsTable.tsx b/src/components/modules/TransferModule/TransferDetailsTable/TransferDetailsTable.tsx
index b8c8302d..9f1ca9f7 100644
--- a/src/components/modules/TransferModule/TransferDetailsTable/TransferDetailsTable.tsx
+++ b/src/components/modules/TransferModule/TransferDetailsTable/TransferDetailsTable.tsx
@@ -154,7 +154,6 @@ export const ArrowIcon = styled.div`
background: url("${arrowIcon}") center no-repeat;
margin-left: 16px;
`;
-export const TEST_ID = "mainDetailsTable";
export type Props = {
item?: TransferItem | null;
diff --git a/src/components/modules/TransferModule/TransferDetailsTable/test.tsx b/src/components/modules/TransferModule/TransferDetailsTable/test.tsx
deleted file mode 100644
index f5a86030..00000000
--- a/src/components/modules/TransferModule/TransferDetailsTable/test.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-
-import type { MainItem } from "@src/@types/MainItem";
-import type { Instance } from "@src/@types/Instance";
-import TW from "@src/utils/TestWrapper";
-import Component, { TEST_ID } from "./TransferDetailsTable";
-import type { Props } from "./TransferDetailsTable";
-
-const defaultInstance: Instance = {
- id: "instance1id",
- name: "instance1name",
- flavor_name: "instance1flavorname",
- instance_name: "instance1instancename",
- num_cpu: 2,
- memory_mb: 2048,
- os_type: "windows",
- devices: {
- nics: [
- {
- id: "instance1nic1",
- network_name: "network1",
- mac_address: "instance1macaddress",
- network_id: "network1",
- },
- ],
- disks: [
- {
- id: "instance1disk1",
- name: "instance1disk1name",
- storage_backend_identifier: "asdaf",
- },
- ],
- },
-};
-
-const defaultItem: MainItem = {
- id: "id",
- executions: [],
- name: "name",
- notes: "notes",
- status: "COMPLETED",
- tasks: [],
- created_at: new Date(2019, 2, 18, 13, 19, 10),
- updated_at: new Date(2019, 2, 19, 14, 18, 55),
- origin_endpoint_id: "origin",
- destination_endpoint_id: "destination",
- instances: ["instance1"],
- type: "replica",
- info: {
- instance1: {
- export_info: {
- devices: {
- nics: [{ network_name: "network1" }, { network_name: "network2" }],
- },
- },
- },
- },
- destination_environment: { option1: "value1" },
- source_environment: { option1: "value1" },
- transfer_result: {
- instance1: defaultInstance,
- },
- storage_mappings: {
- backend_mappings: [
- {
- destination: "asdaf",
- source: "asdaf1",
- },
- ],
- default: "asdaf",
- disk_mappings: [
- {
- destination: "asdaf",
- disk_id: "instance1disk1",
- },
- ],
- },
- network_map: {
- network1: "network2",
- },
-};
-
-const defaultProps: Props = {
- item: defaultItem,
- instancesDetails: [defaultInstance],
-};
-const wrap = (props: Props) =>
- new TW(shallow(), TEST_ID);
-
-describe("MainDetailsTable Component", () => {
- it("renders basic info", () => {
- const wrapper = wrap(defaultProps);
- defaultProps.instancesDetails.forEach(i => {
- expect(wrapper.findText(`instanceName-${i.name}`)).toBe(i.name);
- });
- expect(wrapper.findText("source-instance")).toBe("instance1");
- expect(wrapper.findText("destination-instance")).toBe("instance1");
-
- expect(wrapper.findText("source-network")).toBe("instance1macaddress");
- expect(wrapper.findText("destination-network")).toBe("network2");
-
- expect(wrapper.findText("source-storage")).toBe("instance1disk1");
- expect(wrapper.findText("destination-storage")).toBe("instance1disk1name");
- });
-});
diff --git a/src/components/modules/TransferModule/TransferListItem/TransferListItem.spec.tsx b/src/components/modules/TransferModule/TransferListItem/TransferListItem.spec.tsx
new file mode 100644
index 00000000..1663bcc0
--- /dev/null
+++ b/src/components/modules/TransferModule/TransferListItem/TransferListItem.spec.tsx
@@ -0,0 +1,42 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+
+import TransferListItem from ".";
+import { REPLICA_MOCK } from "@tests/mocks/TransferMock";
+
+describe("TransferListItem", () => {
+ let defaultProps: TransferListItem["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ item: REPLICA_MOCK,
+ selected: false,
+ image: "image",
+ userNameLoading: false,
+ onSelectedChange: jest.fn(),
+ endpointType: jest.fn(),
+ getUserName: jest.fn(),
+ onClick: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText(REPLICA_MOCK.notes!)).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/TransferModule/TransferListItem/test.tsx b/src/components/modules/TransferModule/TransferListItem/test.tsx
deleted file mode 100644
index 88d1d974..00000000
--- a/src/components/modules/TransferModule/TransferListItem/test.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import sinon from "sinon";
-import TestWrapper from "@src/utils/TestWrapper";
-import MainListItem from ".";
-
-const wrap = props =>
- new TestWrapper(shallow(), "mainListItem");
-
-const item = {
- origin_endpoint_id: "openstack",
- destination_endpoint_id: "azure",
- instances: ["instance name"],
- executions: [{ status: "COMPLETED", created_at: new Date() }],
-};
-const endpointType = id => id;
-
-describe("MainListItem Component", () => {
- it("renders with given status", () => {
- const wrapper = wrap({ item, endpointType });
- expect(wrapper.findPartialId("statusPill").at(0).prop("status")).toBe(
- "COMPLETED"
- );
- });
-
- it("renders with given endpoints", () => {
- const wrapper = wrap({ item, endpointType });
- expect(wrapper.find("sourceLogo").prop("endpoint")).toBe(
- item.origin_endpoint_id
- );
- expect(wrapper.find("destLogo").prop("endpoint")).toBe(
- item.destination_endpoint_id
- );
- });
-
- it("renders with selected", () => {
- const wrapper = wrap({ item, endpointType, selected: true });
- expect(wrapper.find("checkbox").prop("checked")).toBe(true);
- });
-
- it("dispatched item click", () => {
- const onClick = sinon.spy();
- const wrapper = wrap({ item, endpointType, onClick });
- wrapper.find("content").simulate("click");
- expect(onClick.calledOnce).toBe(true);
- });
-});
diff --git a/src/components/modules/UserModule/UserDetailsContent/UserDetailsContent.spec.tsx b/src/components/modules/UserModule/UserDetailsContent/UserDetailsContent.spec.tsx
new file mode 100644
index 00000000..f8a76dda
--- /dev/null
+++ b/src/components/modules/UserModule/UserDetailsContent/UserDetailsContent.spec.tsx
@@ -0,0 +1,65 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+import { USER_MOCK } from "@tests/mocks/UsersMock";
+import TestUtils from "@tests/TestUtils";
+
+import UserDetailsContent from "./";
+
+jest.mock("react-router-dom", () => ({ Link: "a" }));
+
+describe("UserDetailsContent", () => {
+ let defaultProps: UserDetailsContent["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ user: USER_MOCK,
+ loading: false,
+ projects: [USER_MOCK.project],
+ userProjects: [USER_MOCK.project],
+ isLoggedUser: false,
+ onUpdatePasswordClick: jest.fn(),
+ onDeleteClick: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText(USER_MOCK.name)).toBeTruthy();
+ });
+
+ it("renders loading", () => {
+ render();
+ expect(TestUtils.select("UserDetailsContent__LoadingWrapper")).toBeTruthy();
+ });
+
+ it("fires delete click", () => {
+ const { getByText } = render();
+ getByText("Delete user").click();
+ expect(defaultProps.onDeleteClick).toHaveBeenCalled();
+ });
+
+ it("renders without crashing when user projects are not in projects", () => {
+ const { getByText } = render(
+
+ );
+ expect(getByText(USER_MOCK.name)).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/UserModule/UserDetailsContent/UserDetailsContent.tsx b/src/components/modules/UserModule/UserDetailsContent/UserDetailsContent.tsx
index df6ac19a..8ef3c736 100644
--- a/src/components/modules/UserModule/UserDetailsContent/UserDetailsContent.tsx
+++ b/src/components/modules/UserModule/UserDetailsContent/UserDetailsContent.tsx
@@ -12,20 +12,19 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
+import { observer } from "mobx-react";
import React from "react";
import { Link } from "react-router-dom";
-import { observer } from "mobx-react";
import styled from "styled-components";
-import CopyValue from "@src/components/ui/CopyValue";
+import { ThemePalette, ThemeProps } from "@src/components/Theme";
+import Button from "@src/components/ui/Button";
import CopyMultilineValue from "@src/components/ui/CopyMultilineValue";
+import CopyValue from "@src/components/ui/CopyValue";
import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
-import Button from "@src/components/ui/Button";
import type { User } from "@src/@types/User";
import type { Project } from "@src/@types/Project";
-import { ThemePalette, ThemeProps } from "@src/components/Theme";
-
const Wrapper = styled.div`
${ThemeProps.exactWidth(ThemeProps.contentWidth)}
margin: 0 auto;
@@ -77,8 +76,6 @@ const ButtonsColumn = styled.div`
}
`;
-export const TEST_ID = "userDetailsContent";
-
export type Props = {
user: User | null;
loading: boolean;
diff --git a/src/components/modules/UserModule/UserDetailsContent/test.tsx b/src/components/modules/UserModule/UserDetailsContent/test.tsx
deleted file mode 100644
index efa21a9c..00000000
--- a/src/components/modules/UserModule/UserDetailsContent/test.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import sinon from "sinon";
-
-import type { User } from "@src/@types/User";
-import type { Project } from "@src/@types/Project";
-import TW from "@src/utils/TestWrapper";
-import Component, { TEST_ID } from ".";
-import type { Props } from ".";
-
-const anotherProject: Project = {
- id: "project2-id",
- name: "project2-name",
- enabled: false,
- description: "project2-description",
-};
-
-const defaultProject: Project = {
- id: "project-id",
- name: "project-name",
- enabled: true,
- description: "project-description",
-};
-
-const defaultUser: User = {
- project: defaultProject,
- email: "user@email.com",
- name: "name",
- id: "id",
- description: "description",
- enabled: true,
- project_id: "project-id",
- domain_id: "default",
- isAdmin: true,
- password: "password",
-};
-const defaultProps: Props = {
- user: defaultUser,
- loading: false,
- projects: [defaultProject, anotherProject],
- userProjects: [defaultProject, anotherProject],
- isLoggedUser: true,
- onDeleteClick: () => {},
- onUpdatePasswordClick: () => {},
-};
-const wrap = (props: Props) =>
- new TW(shallow(), TEST_ID);
-
-describe("UserDetailsContent Component", () => {
- it("renders info", () => {
- const wrapper = wrap(defaultProps);
- expect(wrapper.find("name").prop("value")).toBe(defaultUser.name);
- expect(wrapper.find("id").prop("value")).toBe(defaultUser.id);
- expect(wrapper.find("email").prop("value")).toBe(defaultUser.email);
- expect(wrapper.find("primaryProject").prop("value")).toBe(
- defaultProject.name
- );
- expect(wrapper.findText("enabled")).toBe("Yes");
- });
-
- it("renders project membership", () => {
- const wrapper = wrap(defaultProps);
- expect(wrapper.find("project-project-id").prop("to")).toBe(
- "/project/project-id"
- );
- expect(wrapper.find("project-project2-id").prop("to")).toBe(
- "/project/project2-id"
- );
- });
-
- it("dispatches delete an update clicks", () => {
- const newProps: Props = { ...defaultProps };
- newProps.onDeleteClick = sinon.spy();
- newProps.onUpdatePasswordClick = sinon.spy();
- const wrapper = wrap(newProps);
- const deleteButton = wrapper.find("deleteUserButton");
- deleteButton.simulate("click");
- const updateButton = wrapper.find("updateButton");
- updateButton.simulate("click");
-
- expect(newProps.onDeleteClick.called).toBe(true);
- expect(newProps.onUpdatePasswordClick.called).toBe(true);
- });
-
- it("has delete disabled if is the logged in user", () => {
- let wrapper = wrap(defaultProps);
- expect(wrapper.find("deleteUserButton").prop("disabled")).toBe(true);
- wrapper = wrap({ ...defaultProps, isLoggedUser: false });
- expect(wrapper.find("deleteUserButton").prop("disabled")).toBe(false);
- });
-});
diff --git a/src/components/modules/UserModule/UserListItem/UserListItem.spec.tsx b/src/components/modules/UserModule/UserListItem/UserListItem.spec.tsx
new file mode 100644
index 00000000..091e5c31
--- /dev/null
+++ b/src/components/modules/UserModule/UserListItem/UserListItem.spec.tsx
@@ -0,0 +1,37 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+import { USER_MOCK } from "@tests/mocks/UsersMock";
+
+import UserListItem from "./";
+
+describe("UserListItem", () => {
+ let defaultProps: UserListItem["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ item: USER_MOCK,
+ onClick: jest.fn(),
+ getProjectName: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText(USER_MOCK.name)).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/UserModule/UserListItem/UserListItem.tsx b/src/components/modules/UserModule/UserListItem/UserListItem.tsx
index f2764a91..7914f6da 100644
--- a/src/components/modules/UserModule/UserListItem/UserListItem.tsx
+++ b/src/components/modules/UserModule/UserListItem/UserListItem.tsx
@@ -12,15 +12,15 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
+import { observer } from "mobx-react";
import React from "react";
import styled from "styled-components";
-import { observer } from "mobx-react";
-import type { User } from "@src/@types/User";
import { ThemePalette, ThemeProps } from "@src/components/Theme";
import userImage from "./images/user.svg";
+import type { User } from "@src/@types/User";
const Content = styled.div`
display: flex;
align-items: center;
diff --git a/src/components/modules/UserModule/UserListItem/test.tsx b/src/components/modules/UserModule/UserListItem/test.tsx
deleted file mode 100644
index 17001e45..00000000
--- a/src/components/modules/UserModule/UserListItem/test.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import sinon from "sinon";
-import TW from "@src/utils/TestWrapper";
-import type { User } from "@src/@types/User";
-import UserListItem from ".";
-
-type Props = {
- item: User;
- onClick: () => void;
- getProjectName: (projectId: string | null) => string;
-};
-
-const wrap = (props: Props) =>
- new TW(shallow(), "ulItem");
-
-const user = {
- id: "id",
- name: "User Name",
- description: "user description",
- email: "user@email.com",
- project_id: "project_id",
- enabled: true,
- project: { name: "", id: "" },
-};
-describe("UserListItem Component", () => {
- it("renders with correct data", () => {
- const wrapper = wrap({
- item: user,
- onClick: () => {},
- getProjectName: id => `project ${id || ""}`,
- });
- expect(wrapper.findText("name")).toBe(user.name);
- expect(wrapper.findText("description")).toBe(user.description);
- expect(wrapper.findText("email")).toBe(user.email);
- expect(wrapper.findText("project")).toBe("project project_id");
- expect(wrapper.findText("enabled")).toBe("Yes");
- });
-
- it("dispatches click", () => {
- const onClick = sinon.spy();
- const wrapper = wrap({ item: user, onClick, getProjectName: () => "" });
- wrapper.find("content").click();
- expect(onClick.calledOnce).toBe(true);
- });
-});
diff --git a/src/components/modules/UserModule/UserModal/test.tsx b/src/components/modules/UserModule/UserModal/test.tsx
deleted file mode 100644
index b8b90477..00000000
--- a/src/components/modules/UserModule/UserModal/test.tsx
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import sinon from "sinon";
-import TW from "@src/utils/TestWrapper";
-import type { Project } from "@src/@types/Project";
-import type { User } from "@src/@types/User";
-import UserModal from ".";
-
-type Props = {
- user?: User;
- isLoggedUser?: boolean;
- loading: boolean;
- isNewUser?: boolean;
- projects: Project[];
- editPassword?: boolean;
- onRequestClose: () => void;
- onUpdateClick: (user: User) => void;
-};
-
-const wrap = (props: Props) =>
- new TW(shallow(), "userModal");
-const projects: Project[] = [
- { id: "project-1", name: "Project 1" },
- { id: "project-2", name: "Project 2" },
-];
-describe("UserModal Component", () => {
- it("doesn't dispatch click if required fields are not filled", () => {
- const onUpdateClick = sinon.spy();
- const wrapper = wrap({
- isNewUser: true,
- isLoggedUser: false,
- loading: false,
- projects,
- onRequestClose: () => {},
- onUpdateClick,
- });
- expect(wrapper.findText("updateButton", false, true)).toBe("New User");
- wrapper.find("updateButton").click();
- expect(onUpdateClick.called).toBe(false);
- expect(wrapper.find("field-username").prop("highlight")).toBe(true);
- expect(wrapper.find("field-new_password").prop("highlight")).toBe(true);
- });
-
- it("dispatches click if project is filled", () => {
- const onUpdateClick = sinon.spy();
- const wrapper = wrap({
- user: {
- id: "user-1",
- name: "User 1",
- email: "email",
- project: projects[0],
- },
- isNewUser: false,
- isLoggedUser: false,
- loading: false,
- projects,
- onRequestClose: () => {},
- onUpdateClick,
- });
- expect(wrapper.findText("updateButton", false, true)).toBe("Update User");
- wrapper.find("updateButton").click();
- expect(onUpdateClick.called).toBe(true);
- });
-
- it("has disabled fields on loading", () => {
- const wrapper = wrap({
- user: {
- id: "user-1",
- name: "User 1",
- email: "email",
- project: projects[0],
- },
- isNewUser: false,
- isLoggedUser: false,
- loading: true,
- projects,
- onRequestClose: () => {},
- onUpdateClick: () => {},
- });
- expect(wrapper.find("updateButton").prop("disabled")).toBe(true);
- expect(wrapper.find("field-username").prop("disabled")).toBe(true);
- expect(wrapper.find("field-new_password").length).toBe(0);
- expect(wrapper.find("field-confirm_password").length).toBe(0);
- });
-
- it("renders change password form", () => {
- const wrapper = wrap({
- user: {
- id: "user-1",
- name: "User 1",
- email: "email",
- project: projects[0],
- },
- isNewUser: false,
- isLoggedUser: false,
- loading: true,
- projects,
- editPassword: true,
- onRequestClose: () => {},
- onUpdateClick: () => {},
- });
-
- expect(wrapper.findText("updateButton", false, true)).toBe(
- "Change Password"
- );
- expect(wrapper.find("field-new_password").length).toBe(1);
- expect(wrapper.find("field-confirm_password").length).toBe(1);
- });
-});
diff --git a/src/components/modules/WizardModule/WizardBreadcrumbs/test.tsx b/src/components/modules/WizardModule/WizardBreadcrumbs/WizardBreadcrumbs.spec.tsx
similarity index 52%
rename from src/components/modules/WizardModule/WizardBreadcrumbs/test.tsx
rename to src/components/modules/WizardModule/WizardBreadcrumbs/WizardBreadcrumbs.spec.tsx
index 517b3551..71f00629 100644
--- a/src/components/modules/WizardModule/WizardBreadcrumbs/test.tsx
+++ b/src/components/modules/WizardModule/WizardBreadcrumbs/WizardBreadcrumbs.spec.tsx
@@ -1,5 +1,5 @@
/*
-Copyright (C) 2017 Cloudbase Solutions SRL
+Copyright (C) 2023 Cloudbase Solutions SRL
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
@@ -13,29 +13,26 @@ along with this program. If not, see .
*/
import React from "react";
-import { shallow } from "enzyme";
+
+import { render } from "@testing-library/react";
+
import WizardBreadcrumbs from ".";
-import TW from "@src/utils/TestWrapper";
-import { wizardPages } from "@src/constants";
-const wrap = props =>
- new TW(
- shallow(
-
- ),
- "wBreadCrumbs"
- );
+describe("WizardBreadcrumbs", () => {
+ let defaultProps: WizardBreadcrumbs["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ selected: { id: "2" },
+ pages: [
+ { id: "1", title: "The title 1", breadcrumb: "The breadcrumb 1" },
+ { id: "2", title: "The title 2", breadcrumb: "The breadcrumb 2" },
+ ],
+ };
+ });
-describe("WizardBreadcrumbs Component", () => {
- it("has correct page selected", () => {
- const wrapper = wrap({ selected: wizardPages[3] });
- expect(wrapper.findText(`name-${wizardPages[3].id}`)).toBe(
- wizardPages[3].breadcrumb
- );
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText("The breadcrumb 1")).toBeTruthy();
});
});
diff --git a/src/components/modules/WizardModule/WizardBreadcrumbs/WizardBreadcrumbs.tsx b/src/components/modules/WizardModule/WizardBreadcrumbs/WizardBreadcrumbs.tsx
index 53e35253..569841d1 100644
--- a/src/components/modules/WizardModule/WizardBreadcrumbs/WizardBreadcrumbs.tsx
+++ b/src/components/modules/WizardModule/WizardBreadcrumbs/WizardBreadcrumbs.tsx
@@ -12,13 +12,13 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
-import React from "react";
import { observer } from "mobx-react";
+import React from "react";
import styled from "styled-components";
+import { ThemePalette } from "@src/components/Theme";
import Arrow from "@src/components/ui/Arrow";
-import { ThemePalette } from "@src/components/Theme";
import type { WizardPage } from "@src/@types/WizardData";
const Wrapper = styled.div`
diff --git a/src/components/modules/WizardModule/WizardEndpointList/WizardEndpointList.spec.tsx b/src/components/modules/WizardModule/WizardEndpointList/WizardEndpointList.spec.tsx
new file mode 100644
index 00000000..ef600b6e
--- /dev/null
+++ b/src/components/modules/WizardModule/WizardEndpointList/WizardEndpointList.spec.tsx
@@ -0,0 +1,48 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+import {
+ OPENSTACK_ENDPOINT_MOCK,
+ VMWARE_ENDPOINT_MOCK,
+} from "@tests/mocks/EndpointsMock";
+
+import WizardEndpointList from "./";
+
+jest.mock("@src/components/modules/EndpointModule/EndpointLogos", () => ({
+ __esModule: true,
+ default: (props: any) =>
{props.endpoint}
,
+}));
+
+describe("WizardEndpointList", () => {
+ let defaultProps: WizardEndpointList["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ providers: ["vmware_vsphere", "openstack"],
+ endpoints: [VMWARE_ENDPOINT_MOCK, OPENSTACK_ENDPOINT_MOCK],
+ loading: false,
+ onChange: jest.fn(),
+ onAddEndpoint: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText("vmware_vsphere")).toBeTruthy();
+ expect(getByText("openstack")).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/WizardModule/WizardEndpointList/WizardEndpointList.tsx b/src/components/modules/WizardModule/WizardEndpointList/WizardEndpointList.tsx
index 9793cb77..6f7ed6b3 100644
--- a/src/components/modules/WizardModule/WizardEndpointList/WizardEndpointList.tsx
+++ b/src/components/modules/WizardModule/WizardEndpointList/WizardEndpointList.tsx
@@ -12,18 +12,17 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
-import React from "react";
import { observer } from "mobx-react";
+import React from "react";
import styled, { css } from "styled-components";
+import { ProviderTypes } from "@src/@types/Providers";
import EndpointLogos from "@src/components/modules/EndpointModule/EndpointLogos";
+import Button from "@src/components/ui/Button";
import Dropdown from "@src/components/ui/Dropdowns/Dropdown";
import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
-import Button from "@src/components/ui/Button";
import type { Endpoint } from "@src/@types/Endpoint";
-import { ProviderTypes } from "@src/@types/Providers";
-
const Wrapper = styled.div`
display: flex;
flex-direction: column;
diff --git a/src/components/modules/WizardModule/WizardEndpointList/test.tsx b/src/components/modules/WizardModule/WizardEndpointList/test.tsx
deleted file mode 100644
index 8aebe826..00000000
--- a/src/components/modules/WizardModule/WizardEndpointList/test.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import TW from "@src/utils/TestWrapper";
-import WizardEndpointList from ".";
-
-const wrap = props =>
- new TW(shallow(), "wEndpointList");
-
-const providers = [
- "openstack",
- "azure",
- "aws",
- "opc",
- "oracle_vm",
- "vmware_vsphere",
-];
-
-const endpoints = [
- { id: "e-1", name: "An endpoint", type: "openstack" },
- { id: "e-2", name: "Another endpoint", type: "azure" },
- { id: "e-3", name: "Yet another endpoint", type: "azure" },
-];
-
-describe("WizardEndpointList Component", () => {
- it("renders correct number of providers", () => {
- const wrapper = wrap({ endpoints, providers });
- expect(wrapper.findPartialId("logo-").length).toBe(providers.length);
- });
-
- it("renders correct providers type", () => {
- const wrapper = wrap({ endpoints, providers });
- expect(wrapper.find(`logo-${providers[0]}`).prop("endpoint")).toBe(
- providers[0]
- );
- expect(wrapper.find(`logo-${providers[1]}`).prop("endpoint")).toBe(
- providers[1]
- );
- expect(wrapper.find(`logo-${providers[2]}`).prop("endpoint")).toBe(
- providers[2]
- );
- expect(wrapper.find(`logo-${providers[3]}`).prop("endpoint")).toBe(
- providers[3]
- );
- expect(wrapper.find(`logo-${providers[4]}`).prop("endpoint")).toBe(
- providers[4]
- );
- expect(wrapper.find(`logo-${providers[5]}`).prop("endpoint")).toBe(
- providers[5]
- );
- });
-
- it("has providers with correct enpoints available", () => {
- const wrapper = wrap({ endpoints, providers });
- expect(wrapper.find("dropdown-openstack").prop("items").length).toBe(2);
- expect(wrapper.find("dropdown-openstack").prop("items")[0].id).toBe("e-1");
- expect(wrapper.find("dropdown-azure").prop("items").length).toBe(3);
- expect(wrapper.find("dropdown-azure").prop("items")[0].id).toBe("e-2");
- expect(wrapper.find("dropdown-azure").prop("items")[1].id).toBe("e-3");
- });
-
- it("renders add new", () => {
- const wrapper = wrap({ endpoints, providers });
- expect(wrapper.find("addButton-opc").length).toBe(1);
- expect(wrapper.find("addButton-aws").length).toBe(1);
- expect(wrapper.find("addButton-oracle_vm").length).toBe(1);
- expect(wrapper.find("addButton-vmware_vsphere").length).toBe(1);
- expect(wrapper.find("addButton-openstack").length).toBe(0);
- expect(wrapper.find("addButton-azure").length).toBe(0);
- });
-
- // it('renders loading', () => {
- // let wrapper = wrap({ endpoints, providers, loading: true })
- // expect(wrapper.find('StatusImage').prop('loading')).toBe(true)
- // })
-
- // it('renders dropdown as primary if endpoint is selected', () => {
- // let wrapper = wrap({ endpoints, providers, selectedEndpoint: { ...endpoints[1] } })
- // expect(wrapper.find('Dropdown').at(1).prop('primary')).toBe(true)
- // expect(wrapper.find('Dropdown').at(0).prop('primary')).toBe(false)
- // })
-
- // it('doesn\'t render endpoint if another endpoint is supplied', () => {
- // let wrapper = wrap({ endpoints, providers, otherEndpoint: { ...endpoints[1] } })
- // expect(wrapper.find('Dropdown').at(1).prop('items').length).toBe(2)
- // expect(wrapper.find('Dropdown').at(1).prop('items')[0].id).toBe('e-3')
- // expect(wrapper.find('Dropdown').at(1).prop('items')[1].id).toBe('addNew')
- // })
-});
diff --git a/src/components/modules/WizardModule/WizardInstances/WizardInstances.spec.tsx b/src/components/modules/WizardModule/WizardInstances/WizardInstances.spec.tsx
new file mode 100644
index 00000000..f236f893
--- /dev/null
+++ b/src/components/modules/WizardModule/WizardInstances/WizardInstances.spec.tsx
@@ -0,0 +1,48 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+
+import WizardInstances from ".";
+import { INSTANCE_MOCK } from "@tests/mocks/InstancesMock";
+
+describe("WizardInstances", () => {
+ let defaultProps: WizardInstances["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ instances: [INSTANCE_MOCK],
+ currentPage: 1,
+ instancesPerPage: 10,
+ loading: false,
+ chunksLoading: false,
+ searching: false,
+ searchNotFound: false,
+ reloading: false,
+ hasSourceOptions: true,
+ onSearchInputChange: jest.fn(),
+ onReloadClick: jest.fn(),
+ onInstanceClick: jest.fn(),
+ onPageClick: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText(INSTANCE_MOCK.name)).toBeTruthy();
+ expect(getByText(INSTANCE_MOCK.instance_name!)).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/WizardModule/WizardInstances/WizardInstances.tsx b/src/components/modules/WizardModule/WizardInstances/WizardInstances.tsx
index 69685002..88323a4d 100644
--- a/src/components/modules/WizardModule/WizardInstances/WizardInstances.tsx
+++ b/src/components/modules/WizardModule/WizardInstances/WizardInstances.tsx
@@ -12,25 +12,25 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
-import React from "react";
import { observer } from "mobx-react";
+import React from "react";
import styled from "styled-components";
-import Checkbox from "@src/components/ui/Checkbox";
-import ReloadButton from "@src/components/ui/ReloadButton";
-import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
+import { ThemePalette, ThemeProps } from "@src/components/Theme";
import Button from "@src/components/ui/Button";
-import SearchInput from "@src/components/ui/SearchInput";
+import Checkbox from "@src/components/ui/Checkbox";
import InfoIcon from "@src/components/ui/InfoIcon";
import Pagination from "@src/components/ui/Pagination";
+import ReloadButton from "@src/components/ui/ReloadButton";
+import SearchInput from "@src/components/ui/SearchInput";
+import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
-import { ThemePalette, ThemeProps } from "@src/components/Theme";
-import type { Instance as InstanceType } from "@src/@types/Instance";
-
-import instanceImage from "./images/instance.svg";
+import bigInstanceImage from "./images/instance-big.svg";
import instanceLinuxImage from "./images/instance-linux.svg";
import instanceWindowsImage from "./images/instance-windows.svg";
-import bigInstanceImage from "./images/instance-big.svg";
+import instanceImage from "./images/instance.svg";
+
+import type { Instance as InstanceType } from "@src/@types/Instance";
const mbToGbString = (mb: number) =>
mb >= 1024 ? `${(mb / 1024).toFixed(2)} GB` : `${mb} MB`;
diff --git a/src/components/modules/WizardModule/WizardInstances/test.tsx b/src/components/modules/WizardModule/WizardInstances/test.tsx
deleted file mode 100644
index d87d3429..00000000
--- a/src/components/modules/WizardModule/WizardInstances/test.tsx
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import sinon from "sinon";
-import TW from "@src/utils/TestWrapper";
-import WizardInstances from ".";
-
-const wrap = props =>
- new TW(
- shallow(),
- "wInstances"
- );
-
-const instances = [
- {
- id: "i-1",
- flavor_name: "Flavor name",
- instance_name: "Instance name 1",
- num_cpu: 3,
- memory_mb: 1024,
- },
- {
- id: "i-2",
- flavor_name: "Flavor name",
- instance_name: "Instance name 2",
- num_cpu: 3,
- memory_mb: 1024,
- },
- {
- id: "i-3",
- flavor_name: "Flavor name",
- instance_name: "Instance name 3",
- num_cpu: 3,
- memory_mb: 1024,
- },
-];
-describe("WizardInstances Component", () => {
- it("has correct number of instances", () => {
- const wrapper = wrap({ instances, currentPage: 1 });
- expect(wrapper.findPartialId("item-").length).toBe(instances.length);
- });
-
- it("has correct instances info", () => {
- const wrapper = wrap({ instances, currentPage: 1 });
- instances.forEach(instance => {
- expect(wrapper.find(`item-${instance.id}`).findText("itemName")).toBe(
- instance.instance_name
- );
- expect(wrapper.find(`item-${instance.id}`).findText("itemDetails")).toBe(
- `${instance.num_cpu} vCPU | ${instance.memory_mb} MB RAM | ${instance.flavor_name}`
- );
- });
- });
-
- it("renders selected instances", () => {
- const wrapper = wrap({
- instances,
- currentPage: 1,
- selectedInstances: [{ ...instances[0] }, { ...instances[2] }],
- instancesPerPage: 3,
- });
- expect(wrapper.findText("selInfo")).toBe("2 instances selected");
- expect(wrapper.find("item-i-1").prop("selected")).toBe(true);
- expect(wrapper.find("item-i-2").prop("selected")).toBe(false);
- expect(wrapper.find("item-i-3").prop("selected")).toBe(true);
- });
-
- it("renders current page", () => {
- const wrapper = wrap({ instances, currentPage: 2, instancesPerPage: 2 });
- expect(wrapper.findText("currentPage")).toBe("2 of 2");
- });
-
- it("renders previous page disabled if page is 1", () => {
- const wrapper = wrap({ instances, currentPage: 1 });
- expect(wrapper.find("prevPageButton").prop("disabled")).toBe(true);
- });
-
- it("renders previous page enabled if page is greater than 1", () => {
- const wrapper = wrap({ instances, currentPage: 3 });
- expect(wrapper.find("prevPageButton").prop("disabled")).toBeFalsy();
- expect(wrapper.find("loadingStatus").length).toBe(0);
- });
-
- it("renders loading", () => {
- const wrapper = wrap({ instances, currentPage: 1, loading: true });
- expect(wrapper.find("loadingStatus").length).toBe(1);
- });
-
- it("renders searching", () => {
- const wrapper = wrap({ instances, currentPage: 1, searching: true });
- expect(wrapper.find("searchInput").prop("loading")).toBe(true);
- });
-
- it("renders search not found", () => {
- const wrapper = wrap({
- instances: [],
- currentPage: 1,
- searchNotFound: true,
- });
- expect(wrapper.findText("notFoundText")).toBe(
- "Your search returned no results"
- );
- expect(wrapper.find("loadingChunks").length).toBe(0);
- });
-
- it("renders loading page", () => {
- const wrapper = wrap({ instances, currentPage: 1, chunksLoading: true });
- expect(wrapper.find("loadingChunks").length).toBe(1);
- });
-
- it("enabled next page", () => {
- let wrapper = wrap({ instances, currentPage: 1 });
- expect(wrapper.find("nextPageButton").prop("disabled")).toBe(true);
- wrapper = wrap({ instances, currentPage: 1, instancesPerPage: 2 });
- expect(wrapper.find("nextPageButton").prop("disabled")).toBeFalsy();
- });
-
- it("dispatches next and previous page click, if enabled", () => {
- const onPageClick = sinon.spy();
- let wrapper = wrap({ instances, currentPage: 1, onPageClick });
- wrapper.find("nextPageButton").click();
- wrapper.find("prevPageButton").click();
- expect(onPageClick.callCount).toBe(0);
- wrapper = wrap({
- instances,
- currentPage: 2,
- onPageClick,
- instancesPerPage: 1,
- });
- wrapper.find("nextPageButton").click();
- wrapper.find("prevPageButton").click();
- expect(onPageClick.callCount).toBe(2);
- });
-
- it("dispaches reload click", () => {
- const onReloadClick = sinon.spy();
- const wrapper = wrap({ instances, currentPage: 1, onReloadClick });
- wrapper.find("reloadButton").click();
- expect(onReloadClick.calledOnce).toBe(true);
- });
-});
diff --git a/src/components/modules/WizardModule/WizardNetworks/WizardNetworks.spec.tsx b/src/components/modules/WizardModule/WizardNetworks/WizardNetworks.spec.tsx
new file mode 100644
index 00000000..b8a3ae9c
--- /dev/null
+++ b/src/components/modules/WizardModule/WizardNetworks/WizardNetworks.spec.tsx
@@ -0,0 +1,40 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+import { INSTANCE_MOCK } from "@tests/mocks/InstancesMock";
+import { NETWORK_MOCK } from "@tests/mocks/NetworksMock";
+
+import WizardNetworks from "./";
+
+describe("WizardNetworks", () => {
+ let defaultProps: WizardNetworks["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ loading: false,
+ loadingInstancesDetails: false,
+ networks: [NETWORK_MOCK],
+ instancesDetails: [INSTANCE_MOCK],
+ onChange: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText(NETWORK_MOCK.name)).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/WizardModule/WizardNetworks/WizardNetworks.tsx b/src/components/modules/WizardModule/WizardNetworks/WizardNetworks.tsx
index 356c3fd7..423e0a5d 100644
--- a/src/components/modules/WizardModule/WizardNetworks/WizardNetworks.tsx
+++ b/src/components/modules/WizardModule/WizardNetworks/WizardNetworks.tsx
@@ -12,21 +12,21 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
-import React from "react";
import { observer } from "mobx-react";
+import React from "react";
import styled from "styled-components";
+import { Instance, InstanceUtils, Nic as NicType } from "@src/@types/Instance";
+import { ThemePalette, ThemeProps } from "@src/components/Theme";
import AutocompleteDropdown from "@src/components/ui/Dropdowns/AutocompleteDropdown";
-import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
import Dropdown from "@src/components/ui/Dropdowns/Dropdown";
+import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
-import { ThemePalette, ThemeProps } from "@src/components/Theme";
-import { Instance, InstanceUtils, Nic as NicType } from "@src/@types/Instance";
-import type { Network, NetworkMap, SecurityGroup } from "@src/@types/Network";
-
-import networkImage from "./images/network.svg";
-import bigNetworkImage from "./images/network-big.svg";
import arrowImage from "./images/arrow.svg";
+import bigNetworkImage from "./images/network-big.svg";
+import networkImage from "./images/network.svg";
+
+import type { Network, NetworkMap, SecurityGroup } from "@src/@types/Network";
const Wrapper = styled.div`
width: 100%;
diff --git a/src/components/modules/WizardModule/WizardNetworks/test.tsx b/src/components/modules/WizardModule/WizardNetworks/test.tsx
deleted file mode 100644
index 4d54cbcd..00000000
--- a/src/components/modules/WizardModule/WizardNetworks/test.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import TW from "@src/utils/TestWrapper";
-import WizardNetworks from ".";
-
-const wrap = props =>
- new TW(shallow(), "wNetworks");
-
-const networks = [
- { name: "network 1", value: "n-1" },
- { name: "network 2", value: "n-2" },
-];
-
-const instancesDetails = [
- {
- devices: { nics: [{ network_name: "network 1", id: "n-1" }] },
- instance_name: "Instance name 1",
- },
- {
- devices: { nics: [{ network_name: "network 2", id: "n-2" }] },
- instance_name: "Instance name 2",
- },
- {
- devices: { nics: [{ network_name: "network 3", id: "n-3" }] },
- instance_name: "Instance name 3",
- },
-];
-
-const selectedNetworks = [
- {
- sourceNic: { id: "n-2", network_name: "network 2" },
- targetNetwork: { name: "network 1" },
- },
-];
-
-describe("WizardNetworks Component", () => {
- it("renders correct number of instance details", () => {
- const wrapper = wrap({ networks, instancesDetails });
- expect(wrapper.findPartialId("dropdown-").length).toBe(
- instancesDetails.length
- );
- });
-
- it("renders correct info for instance details", () => {
- const wrapper = wrap({ networks, instancesDetails });
- expect(wrapper.findText("connectedTo-n-1")).toBe(
- "Connected to Instance name 1"
- );
- expect(wrapper.findText("connectedTo-n-2")).toBe(
- "Connected to Instance name 2"
- );
- expect(wrapper.findText("connectedTo-n-3")).toBe(
- "Connected to Instance name 3"
- );
- expect(wrapper.findText("networkName-n-1")).toBe("network 1");
- expect(wrapper.findText("networkName-n-2")).toBe("network 2");
- expect(wrapper.findText("networkName-n-3")).toBe("network 3");
- });
-
- it("has dropdown with correct number of networks", () => {
- const wrapper = wrap({ networks, instancesDetails });
- expect(wrapper.find("dropdown-n-1").prop("items").length).toBe(
- networks.length
- );
- expect(wrapper.find("dropdown-n-2").prop("items").length).toBe(
- networks.length
- );
- expect(wrapper.find("dropdown-n-3").prop("items").length).toBe(
- networks.length
- );
- });
-
- it("has dropdown with correct networks info", () => {
- const wrapper = wrap({ networks, instancesDetails });
- expect(wrapper.find("dropdown-n-1").prop("items")[0].name).toBe(
- "network 1"
- );
- expect(wrapper.find("dropdown-n-2").prop("items")[1].name).toBe(
- "network 2"
- );
- });
-
- it("renders selected networks", () => {
- const wrapper = wrap({ networks, instancesDetails, selectedNetworks });
- expect(wrapper.find("dropdown-n-1").prop("selectedItem")).toBeFalsy();
- expect(wrapper.find("dropdown-n-2").prop("selectedItem").name).toBe(
- "network 1"
- );
- expect(wrapper.find("dropdown-n-3").prop("selectedItem")).toBeFalsy();
- expect(wrapper.find("noNics").length).toBe(0);
- });
-
- it("renders no nics message", () => {
- const wrapper = wrap({
- networks,
- instancesDetails: [{ ...instancesDetails[0], devices: { nics: [] } }],
- });
- expect(wrapper.find("noNics").length).toBe(1);
- });
-});
diff --git a/src/components/modules/WizardModule/WizardOptions/WizardOptions.spec.tsx b/src/components/modules/WizardModule/WizardOptions/WizardOptions.spec.tsx
new file mode 100644
index 00000000..fb547ef3
--- /dev/null
+++ b/src/components/modules/WizardModule/WizardOptions/WizardOptions.spec.tsx
@@ -0,0 +1,50 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+import { MINION_POOL_MOCK } from "@tests/mocks/MinionPoolMock";
+
+import WizardOptions from "./";
+
+jest.mock("@src/plugins/default/ContentPlugin", () => jest.fn(() => null));
+jest.mock("@src/utils/Config", () => ({
+ config: {
+ passwordFields: ["secret_key"],
+ },
+}));
+jest.mock("react-transition-group", () => ({
+ CSSTransitionGroup: (props: any) =>
{props.children}
,
+}));
+
+describe("WizardOptions", () => {
+ let defaultProps: WizardOptions["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ fields: [{ name: "field1", label: "Field 1", type: "string" }],
+ minionPools: [MINION_POOL_MOCK],
+ hasStorageMap: false,
+ wizardType: "replica",
+ dictionaryKey: "replica",
+ onChange: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText("Execute Now")).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/WizardModule/WizardOptions/test.tsx b/src/components/modules/WizardModule/WizardOptions/test.tsx
deleted file mode 100644
index e30906dd..00000000
--- a/src/components/modules/WizardModule/WizardOptions/test.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import TW from "@src/utils/TestWrapper";
-import WizardOptions from ".";
-
-import configLoader from "@src/utils/Config";
-
-const wrap = props => new TW(shallow(), "wOptions");
-
-const fields = [
- {
- name: "string_field",
- type: "string",
- },
- {
- name: "string_field_with_default",
- type: "string",
- default: "default",
- },
- {
- required: true,
- name: "required_string_field",
- type: "string",
- },
- {
- name: "enum_field",
- type: "string",
-
- enum: ["enum 1", "enum 2", "enum 3"],
- },
- {
- name: "boolean_field",
- type: "boolean",
- },
- {
- name: "strict_boolean_field",
- type: "strict-boolean",
- },
-];
-
-describe("WizardOptions Component", () => {
- beforeAll(() => {
- configLoader.config = { passwordFields: [] };
- });
-
- it("has description and required field in simple tab", () => {
- const wrapper = wrap({
- fields,
- selectedInstances: [],
- wizardType: "migration",
- });
- expect(wrapper.findPartialId("field-").length).toBe(5);
- expect(wrapper.find("field-description").length).toBe(1);
- expect(wrapper.find("field-required_string_field").length).toBe(1);
- });
-
- it("renders execute now for replica", () => {
- const wrapper = wrap({
- fields,
- selectedInstances: [],
- wizardType: "replica",
- });
- expect(wrapper.find("field-execute_now").length).toBe(1);
- expect(wrapper.find("field-execute_now_options").length).toBe(1);
- });
-
- it("renders skip os morphing for migration", () => {
- const wrapper = wrap({
- fields,
- selectedInstances: [],
- wizardType: "migration",
- });
- expect(wrapper.find("field-skip_os_morphing").length).toBe(1);
- });
-
- it("renders separate / vm if multiple instances are selected", () => {
- const wrapper = wrap({ fields, selectedInstances: [{}, {}] });
- expect(wrapper.find("field-separate_vm").length).toBe(1);
- });
-
- it("renders correct number of fields in advanced tab", () => {
- const wrapper = wrap({
- fields,
- selectedInstances: [],
- useAdvancedOptions: true,
- wizardType: "migration",
- });
- expect(wrapper.findPartialId("field-").length).toBe(fields.length + 4);
- });
-
- it("renders correct field info", () => {
- const wrapper = wrap({
- fields,
- selectedInstances: [],
- useAdvancedOptions: true,
- wizardType: "migration",
- });
-
- expect(wrapper.find("field-description").prop("type")).toBe("string");
- expect(wrapper.find("field-required_string_field").prop("required")).toBe(
- true
- );
- expect(wrapper.find("field-string_field").prop("type")).toBe("string");
- expect(wrapper.find("field-string_field_with_default").prop("value")).toBe(
- "default"
- );
- expect(wrapper.find("field-enum_field").prop("enum")[0]).toBe("enum 1");
- expect(wrapper.find("field-enum_field").prop("enum")[1]).toBe("enum 2");
- expect(wrapper.find("field-boolean_field").prop("type")).toBe("boolean");
- expect(wrapper.find("field-strict_boolean_field").prop("type")).toBe(
- "strict-boolean"
- );
- });
-
- it("renders data into field", () => {
- const wrapper = wrap({
- fields,
- selectedInstances: [],
- useAdvancedOptions: true,
- data: { string_field: "new data" },
- });
- expect(wrapper.find("field-string_field").prop("value")).toBe("new data");
- });
-});
diff --git a/src/components/modules/WizardModule/WizardPageContent/WizardPageContent.tsx b/src/components/modules/WizardModule/WizardPageContent/WizardPageContent.tsx
index 65a7bd89..7601ef48 100644
--- a/src/components/modules/WizardModule/WizardPageContent/WizardPageContent.tsx
+++ b/src/components/modules/WizardModule/WizardPageContent/WizardPageContent.tsx
@@ -12,46 +12,44 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
+import { observer } from "mobx-react";
import React from "react";
import styled from "styled-components";
-import { observer } from "mobx-react";
+import { Endpoint, EndpointUtils, StorageMap } from "@src/@types/Endpoint";
+import { ProviderTypes } from "@src/@types/Providers";
import EndpointLogos from "@src/components/modules/EndpointModule/EndpointLogos";
-import WizardType from "@src/components/modules/WizardModule/WizardType";
-import Button from "@src/components/ui/Button";
-import InfoIcon from "@src/components/ui/InfoIcon";
+import Schedule from "@src/components/modules/TransferModule/Schedule";
import WizardBreadcrumbs from "@src/components/modules/WizardModule/WizardBreadcrumbs";
import WizardEndpointList from "@src/components/modules/WizardModule/WizardEndpointList";
import WizardInstances from "@src/components/modules/WizardModule/WizardInstances";
import WizardNetworks, {
WizardNetworksChangeObject,
} from "@src/components/modules/WizardModule/WizardNetworks";
-import WizardStorage from "@src/components/modules/WizardModule/WizardStorage";
import WizardOptions from "@src/components/modules/WizardModule/WizardOptions";
import WizardScripts from "@src/components/modules/WizardModule/WizardScripts";
-import Schedule from "@src/components/modules/TransferModule/Schedule";
+import WizardStorage from "@src/components/modules/WizardModule/WizardStorage";
import WizardSummary from "@src/components/modules/WizardModule/WizardSummary";
-
+import WizardType from "@src/components/modules/WizardModule/WizardType";
import { ThemePalette, ThemeProps } from "@src/components/Theme";
-import { providerTypes, wizardPages, migrationFields } from "@src/constants";
+import Button from "@src/components/ui/Button";
+import InfoIcon from "@src/components/ui/InfoIcon";
+import LoadingButton from "@src/components/ui/LoadingButton";
+import { migrationFields, providerTypes, wizardPages } from "@src/constants";
+import endpointStore from "@src/stores/EndpointStore";
+import instanceStore from "@src/stores/InstanceStore";
+import minionPoolStore from "@src/stores/MinionPoolStore";
+import networkStore from "@src/stores/NetworkStore";
+import notificationStore from "@src/stores/NotificationStore";
+import providerStore from "@src/stores/ProviderStore";
import configLoader from "@src/utils/Config";
+import transferItemIcon from "./images/transferItemIcon";
+
import type { WizardData, WizardPage } from "@src/@types/WizardData";
-import { Endpoint, EndpointUtils, StorageMap } from "@src/@types/Endpoint";
import type { Instance, InstanceScript } from "@src/@types/Instance";
import type { Field } from "@src/@types/Field";
import type { Schedule as ScheduleType } from "@src/@types/Schedule";
-import instanceStore from "@src/stores/InstanceStore";
-import providerStore from "@src/stores/ProviderStore";
-import endpointStore from "@src/stores/EndpointStore";
-import networkStore from "@src/stores/NetworkStore";
-
-import { ProviderTypes } from "@src/@types/Providers";
-import minionPoolStore from "@src/stores/MinionPoolStore";
-import LoadingButton from "@src/components/ui/LoadingButton";
-import notificationStore from "@src/stores/NotificationStore";
-import transferItemIcon from "./images/transferItemIcon";
-
const Wrapper = styled.div`
${ThemeProps.exactWidth(`${parseInt(ThemeProps.contentWidth, 10) + 64}px`)}
margin: 64px auto 32px auto;
diff --git a/src/components/modules/WizardModule/WizardScripts/WizardScripts.spec.tsx b/src/components/modules/WizardModule/WizardScripts/WizardScripts.spec.tsx
new file mode 100644
index 00000000..6f5cc0df
--- /dev/null
+++ b/src/components/modules/WizardModule/WizardScripts/WizardScripts.spec.tsx
@@ -0,0 +1,41 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+import { INSTANCE_MOCK } from "@tests/mocks/InstancesMock";
+
+import WizardScripts from "./";
+
+describe("WizardScripts", () => {
+ let defaultProps: WizardScripts["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ instances: [INSTANCE_MOCK],
+ uploadedScripts: [],
+ removedScripts: [],
+ userScriptData: null,
+ onScriptUpload: jest.fn(),
+ onCancelScript: jest.fn(),
+ onScriptDataRemove: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText(INSTANCE_MOCK.name)).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/WizardModule/WizardScripts/WizardScripts.tsx b/src/components/modules/WizardModule/WizardScripts/WizardScripts.tsx
index db03e283..3900a883 100644
--- a/src/components/modules/WizardModule/WizardScripts/WizardScripts.tsx
+++ b/src/components/modules/WizardModule/WizardScripts/WizardScripts.tsx
@@ -12,23 +12,22 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
-import React from "react";
import { observer } from "mobx-react";
+import React from "react";
import styled, { css } from "styled-components";
-import InfoIcon from "@src/components/ui/InfoIcon";
-import { Close as InputClose } from "@src/components/ui/TextInput";
+import { UserScriptData } from "@src/@types/MainItem";
import { InstanceImage } from "@src/components/modules/WizardModule/WizardInstances";
-import StatusIcon from "@src/components/ui/StatusComponents/StatusIcon";
-
import { ThemePalette, ThemeProps } from "@src/components/Theme";
+import InfoIcon from "@src/components/ui/InfoIcon";
+import StatusIcon from "@src/components/ui/StatusComponents/StatusIcon";
+import { Close as InputClose } from "@src/components/ui/TextInput";
+import DomUtils from "@src/utils/DomUtils";
import FileUtils from "@src/utils/FileUtils";
-import type { Instance, InstanceScript } from "@src/@types/Instance";
-import { UserScriptData } from "@src/@types/MainItem";
-import DomUtils from "@src/utils/DomUtils";
import scriptItemImage from "./images/script-item.svg";
+import type { Instance, InstanceScript } from "@src/@types/Instance";
const Wrapper = styled.div`
width: 100%;
display: flex;
diff --git a/src/components/modules/WizardModule/WizardStorage/WizardStorage.spec.tsx b/src/components/modules/WizardModule/WizardStorage/WizardStorage.spec.tsx
new file mode 100644
index 00000000..b084543d
--- /dev/null
+++ b/src/components/modules/WizardModule/WizardStorage/WizardStorage.spec.tsx
@@ -0,0 +1,43 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+import { INSTANCE_MOCK } from "@tests/mocks/InstancesMock";
+import { STORAGE_BACKEND_MOCK } from "@tests/mocks/StoragesMock";
+
+import WizardStorage from "./";
+
+describe("WizardStorage", () => {
+ let defaultProps: WizardStorage["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ storageBackends: [STORAGE_BACKEND_MOCK],
+ loading: false,
+ instancesDetails: [INSTANCE_MOCK],
+ storageMap: null,
+ defaultStorageLayout: "page",
+ defaultStorage: { value: STORAGE_BACKEND_MOCK.id },
+ onDefaultStorageChange: jest.fn(),
+ onChange: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText("Storage Backend Mapping")).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/WizardModule/WizardStorage/WizardStorage.tsx b/src/components/modules/WizardModule/WizardStorage/WizardStorage.tsx
index f1179ace..da5c169a 100644
--- a/src/components/modules/WizardModule/WizardStorage/WizardStorage.tsx
+++ b/src/components/modules/WizardModule/WizardStorage/WizardStorage.tsx
@@ -12,24 +12,24 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
-import React from "react";
import { observer } from "mobx-react";
+import React from "react";
import styled from "styled-components";
+import { Disk, Instance, InstanceUtils } from "@src/@types/Instance";
+import { ThemePalette, ThemeProps } from "@src/components/Theme";
+import Button from "@src/components/ui/Button";
import AutocompleteDropdown from "@src/components/ui/Dropdowns/AutocompleteDropdown";
import Dropdown from "@src/components/ui/Dropdowns/Dropdown";
import InfoIcon from "@src/components/ui/InfoIcon";
-
-import { ThemePalette, ThemeProps } from "@src/components/Theme";
-import { Instance, Disk, InstanceUtils } from "@src/@types/Instance";
-import type { StorageBackend, StorageMap } from "@src/@types/Endpoint";
-
import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
-import Button from "@src/components/ui/Button";
+
+import arrowImage from "./images/arrow.svg";
import backendImage from "./images/backend.svg";
import diskImage from "./images/disk.svg";
import bigStorageImage from "./images/storage-big.svg";
-import arrowImage from "./images/arrow.svg";
+
+import type { StorageBackend, StorageMap } from "@src/@types/Endpoint";
const Wrapper = styled.div`
width: 100%;
@@ -178,8 +178,6 @@ export const getDisks = (
return disks;
};
-export const TEST_ID = "wizardStorage";
-
export type Props = {
storageBackends: StorageBackend[];
loading: boolean;
diff --git a/src/components/modules/WizardModule/WizardStorage/test.tsx b/src/components/modules/WizardModule/WizardStorage/test.tsx
deleted file mode 100644
index 32812d79..00000000
--- a/src/components/modules/WizardModule/WizardStorage/test.tsx
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import sinon from "sinon";
-
-import type { Instance } from "@src/@types/Instance";
-import TW from "@src/utils/TestWrapper";
-import Component, { TEST_ID } from ".";
-import type { Props } from ".";
-
-const defaultInstance: Instance = {
- id: "instance1id",
- name: "instance1name",
- flavor_name: "instance1flavorname",
- instance_name: "instance1instancename",
- num_cpu: 2,
- memory_mb: 2048,
- os_type: "windows",
- devices: {
- nics: [
- {
- id: "instance1nic1",
- network_name: "network1",
- mac_address: "instance1macaddress",
- network_id: "network1",
- },
- ],
- disks: [
- {
- id: "disk1-id",
- name: "disk1-name",
- storage_backend_identifier: "sback2",
- },
- ],
- },
-};
-
-const defaultProps: Props = {
- storageBackends: [
- {
- id: "sback1",
- name: "sback1-name",
- },
- {
- id: "sback2",
- name: "sback2-name",
- },
- ],
- instancesDetails: [defaultInstance],
- storageMap: [
- {
- type: "disk",
- source: { id: "disk1-id" },
- target: { id: "sback2", name: "sback2-name" },
- },
- {
- type: "backend",
- source: { id: "sback2", storage_backend_identifier: "sback2" },
- target: { id: "sback1", name: "sback1-name" },
- },
- ],
- defaultStorage: "sback1",
- onChange: () => {},
- defaultStorageLayout: "page",
- onDefaultStorageChange: () => {},
- storageConfigDefault: null,
-};
-const wrap = (props: Props) =>
- new TW(shallow(), TEST_ID);
-
-describe("WizardStorage Component", () => {
- it("renders backend mapping", () => {
- const wrapper = wrap(defaultProps);
-
- expect(wrapper.findText("backend-source")).toBe("sback2");
- expect(wrapper.findText("backend-connectedTo")).toBe(
- "Connected to instance1instancename"
- );
-
- expect(wrapper.find("backend-destination").prop("selectedItem").id).toBe(
- "sback1"
- );
-
- expect(wrapper.find("backend-destination").prop("items")[0].id).toBe(null);
- expect(wrapper.find("backend-destination").prop("items")[0].name).toBe(
- "Default"
- );
- expect(wrapper.find("backend-destination").prop("items")[1].id).toBe(
- "sback1"
- );
- expect(wrapper.find("backend-destination").prop("items")[2].id).toBe(
- "sback2"
- );
- });
-
- it("renders disk mapping", () => {
- const wrapper = wrap(defaultProps);
- expect(wrapper.findText("disk-source")).toBe("disk1-id");
- expect(wrapper.findText("disk-connectedTo")).toBe(
- "Connected to instance1instancename"
- );
-
- expect(wrapper.find("disk-destination").prop("selectedItem").id).toBe(
- "sback2"
- );
-
- expect(wrapper.find("disk-destination").prop("items")[0].id).toBe(null);
- expect(wrapper.find("disk-destination").prop("items")[0].name).toBe(
- "Default"
- );
- expect(wrapper.find("disk-destination").prop("items")[1].id).toBe("sback1");
- expect(wrapper.find("disk-destination").prop("items")[2].id).toBe("sback2");
- });
-
- it("renders no storage message", () => {
- const newProps: Props = { ...defaultProps };
- newProps.storageBackends = [];
- let wrapper = wrap(newProps);
- expect(wrapper.find("noStorage").length).toBe(1);
- wrapper = wrap(defaultProps);
- expect(wrapper.find("noStorage").length).toBe(0);
- });
-
- it("dispatches change", () => {
- const newProps: Props = { ...defaultProps, onChange: sinon.spy() };
- const wrapper = wrap(newProps);
- wrapper
- .find("disk-destination")
- .simulate("change", { id: "sback2", name: "sback2-name" });
-
- const arg = newProps.onChange.args[0];
-
- expect(arg[0].id).toBe("disk1-id");
- expect(arg[1].id).toBe("sback2");
- expect(arg[2]).toBe("disk");
- });
-});
diff --git a/src/components/modules/WizardModule/WizardSummary/WizardSummary.spec.tsx b/src/components/modules/WizardModule/WizardSummary/WizardSummary.spec.tsx
new file mode 100644
index 00000000..cd6ea071
--- /dev/null
+++ b/src/components/modules/WizardModule/WizardSummary/WizardSummary.spec.tsx
@@ -0,0 +1,71 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+import {
+ OPENSTACK_ENDPOINT_MOCK,
+ VMWARE_ENDPOINT_MOCK,
+} from "@tests/mocks/EndpointsMock";
+import { INSTANCE_MOCK } from "@tests/mocks/InstancesMock";
+import { MINION_POOL_MOCK } from "@tests/mocks/MinionPoolMock";
+import { SCHEDULE_MOCK } from "@tests/mocks/SchedulesMock";
+
+import WizardSummary from "./";
+
+jest.mock("@src/utils/Config", () => ({
+ config: {
+ providerSortPriority: {},
+ providerNames: {
+ openstack: "OpenStack",
+ vmware_vsphere: "VMware vSphere",
+ },
+ },
+}));
+
+describe("WizardSummary", () => {
+ let defaultProps: WizardSummary["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ data: {
+ destOptions: { option_1: "option_1" },
+ sourceOptions: { option_2: "option_2" },
+ selectedInstances: [INSTANCE_MOCK],
+ source: VMWARE_ENDPOINT_MOCK,
+ target: OPENSTACK_ENDPOINT_MOCK,
+ },
+ wizardType: "replica",
+ schedules: [SCHEDULE_MOCK],
+ minionPools: [MINION_POOL_MOCK],
+ defaultStorage: { value: "defaultStorage" },
+ instancesDetails: [INSTANCE_MOCK],
+ storageMap: [],
+ sourceSchema: [{ name: "option_1", label: "Option 1", type: "string" }],
+ destinationSchema: [
+ { name: "option_2", label: "Option 2", type: "string" },
+ ],
+ uploadedUserScripts: [],
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText("Overview")).toBeTruthy();
+ expect(getByText("REPLICA")).toBeTruthy();
+ expect(getByText(VMWARE_ENDPOINT_MOCK.name)).toBeTruthy();
+ expect(getByText(INSTANCE_MOCK.name)).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/WizardModule/WizardSummary/test.tsx b/src/components/modules/WizardModule/WizardSummary/test.tsx
deleted file mode 100644
index f2c4c2ec..00000000
--- a/src/components/modules/WizardModule/WizardSummary/test.tsx
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import TW from "@src/utils/TestWrapper";
-import WizardSummary from ".";
-
-const wrap = props =>
- new TW(
- shallow(
-
- ),
- "wSummary"
- );
-
-const schedules = [
- {
- id: "s-1",
- schedule: {
- month: 2,
- dom: 14,
- dow: 3,
- minute: 0,
- hour: 17,
- },
- },
-];
-
-const data = {
- destOptions: {
- description: "A description",
- field_name: "Field name value",
- },
- selectedInstances: [
- {
- flavor_name: "flavor_name",
- id: "i-1",
- name: "name",
- num_cpu: 2,
- memory_mb: 1024,
- },
- ],
- networks: [
- {
- sourceNic: { id: "s-1", network_name: "n-1" },
- targetNetwork: { name: "target network" },
- },
- ],
- source: {
- type: "openstack",
- name: "source name",
- },
- target: {
- type: "azure",
- name: "target name",
- },
-};
-
-describe("WizardSummary Component", () => {
- it("renders overview section", () => {
- const wrapper = wrap({ data, wizardType: "replica" });
- expect(wrapper.findText("source")).toBe("source name");
- expect(wrapper.find("sourcePill").prop("label")).toBe("OPENSTACK");
- expect(wrapper.findText("target")).toBe("target name");
- expect(wrapper.find("targetPill").prop("label")).toBe("AZURE");
- expect(wrapper.find("typePill").prop("label")).toBe("REPLICA");
- });
-
- it("renders instances section", () => {
- const wrapper = wrap({ data, wizardType: "replica" });
- expect(wrapper.findText("instance-i-1")).toBe("name");
- });
-
- it("renders networks section", () => {
- const wrapper = wrap({ data, wizardType: "replica" });
- expect(wrapper.findText("networkSource")).toBe("n-1");
- expect(wrapper.findText("networkTarget")).toBe("target network");
- });
-
- it("renders options section", () => {
- const wrapper = wrap({ data, wizardType: "replica" });
- expect(wrapper.findText("optionLabel-description")).toBe("Description");
- expect(wrapper.findText("optionValue-description")).toBe("A description");
- expect(wrapper.findText("optionLabel-field_name")).toBe("Field Name");
- expect(wrapper.findText("optionValue-field_name")).toBe("Field name value");
- });
-
- it("renders schedule section", () => {
- const wrapper = wrap({ data, schedules, wizardType: "replica" });
- expect(wrapper.findText(`scheduleItem-${schedules[0].id}`)).toBe(
- "Every February, every 14th, every Wednesday, at 17:00 UTC"
- );
- });
-});
diff --git a/src/components/modules/WizardModule/WizardType/WizardType.spec.tsx b/src/components/modules/WizardModule/WizardType/WizardType.spec.tsx
new file mode 100644
index 00000000..84a3c4b2
--- /dev/null
+++ b/src/components/modules/WizardModule/WizardType/WizardType.spec.tsx
@@ -0,0 +1,35 @@
+/*
+Copyright (C) 2023 Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import React from "react";
+
+import { render } from "@testing-library/react";
+
+import WizardType from "./";
+
+describe("WizardType", () => {
+ let defaultProps: WizardType["props"];
+
+ beforeEach(() => {
+ defaultProps = {
+ selected: "replica",
+ onChange: jest.fn(),
+ };
+ });
+
+ it("renders without crashing", () => {
+ const { getByText } = render();
+ expect(getByText("Coriolis Replica")).toBeTruthy();
+ });
+});
diff --git a/src/components/modules/WizardModule/WizardType/WizardType.tsx b/src/components/modules/WizardModule/WizardType/WizardType.tsx
index c7369929..fadd36a1 100644
--- a/src/components/modules/WizardModule/WizardType/WizardType.tsx
+++ b/src/components/modules/WizardModule/WizardType/WizardType.tsx
@@ -12,13 +12,12 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
-import React from "react";
import { observer } from "mobx-react";
+import React from "react";
import styled from "styled-components";
-import Switch from "@src/components/ui/Switch";
-
import { ThemePalette, ThemeProps } from "@src/components/Theme";
+import Switch from "@src/components/ui/Switch";
import migrationImage from "./images/migration";
diff --git a/src/components/modules/WizardModule/WizardType/test.tsx b/src/components/modules/WizardModule/WizardType/test.tsx
deleted file mode 100644
index a607ccdc..00000000
--- a/src/components/modules/WizardModule/WizardType/test.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import { shallow } from "enzyme";
-import sinon from "sinon";
-import TW from "@src/utils/TestWrapper";
-import WizardType from ".";
-
-const wrap = props =>
- new TW(shallow( {}} {...props} />), "wType");
-
-describe("WizardType Component", () => {
- it("renders with the correct type selected", () => {
- let wrapper = wrap({ selected: "migration" });
- expect(wrapper.find("switch").prop("checked")).toBe(false);
- wrapper = wrap({ selected: "replica" });
- expect(wrapper.find("switch").prop("checked")).toBe(true);
- });
-
- it("dispatches change", () => {
- const onChange = sinon.spy();
- const wrapper = wrap({ selected: "replica", onChange });
- wrapper.find("switch").simulate("change", { passed: true });
- expect(onChange.args[0][0].passed).toBe(true);
- });
-});
diff --git a/src/components/ui/AlertModal/AlertModal.spec.tsx b/src/components/ui/AlertModal/AlertModal.spec.tsx
index 2763a5f8..37030252 100644
--- a/src/components/ui/AlertModal/AlertModal.spec.tsx
+++ b/src/components/ui/AlertModal/AlertModal.spec.tsx
@@ -1,5 +1,5 @@
/*
-Copyright (C) 2017 Cloudbase Solutions SRL
+Copyright (C) 2023 Cloudbase Solutions SRL
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
@@ -13,59 +13,175 @@ along with this program. If not, see .
*/
import React from "react";
-import { render } from "@testing-library/react";
-import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
-import TestUtils from "@tests/TestUtils";
+import { render, fireEvent } from "@testing-library/react";
import AlertModal from "./AlertModal";
+import KeyboardManager from "@src/utils/KeyboardManager";
-jest.mock("../StatusComponents/StatusImage/StatusImage", () =>
- jest.fn(() => null)
-);
+jest.mock("@src/utils/KeyboardManager", () => ({
+ onEnter: jest.fn(),
+ removeKeyDown: jest.fn(),
+}));
describe("AlertModal", () => {
- it("renders confirmation as default with message and extra message", () => {
- const message = "message";
- const extraMessage = "extra message";
- const { queryByText } = render(
-
+ it("renders with default props", () => {
+ const { getByText } = render();
+ expect(getByText("Yes")).toBeTruthy();
+ expect(getByText("No")).toBeTruthy();
+ });
+
+ it("adds and removes a keyboard listener on mount and unmount", () => {
+ const { unmount } = render();
+
+ expect(KeyboardManager.onEnter).toHaveBeenCalled();
+ unmount();
+ expect(KeyboardManager.removeKeyDown).toHaveBeenCalled();
+ });
+
+ it("calls onConfirmation when Enter is pressed", () => {
+ const onConfirmationMock = jest.fn();
+ render();
+
+ // @ts-ignore
+ const mockCallback = KeyboardManager.onEnter.mock.calls[0][1];
+ mockCallback();
+
+ expect(onConfirmationMock).toHaveBeenCalled();
+ });
+
+ it("renders error type correctly", () => {
+ const { queryByText, getByText } = render(
+
+ );
+
+ expect(getByText("Dismiss")).toBeTruthy();
+ expect(queryByText("Yes")).not.toBeTruthy();
+ expect(queryByText("No")).not.toBeTruthy();
+ });
+
+ it("renders confirmation type correctly", () => {
+ const { getByText } = render();
+
+ expect(getByText("Yes")).toBeTruthy();
+ expect(getByText("No")).toBeTruthy();
+ });
+
+ it("renders loading type correctly", () => {
+ const { queryByText } = render();
+
+ expect(queryByText("Yes")).not.toBeTruthy();
+ expect(queryByText("No")).not.toBeTruthy();
+ expect(queryByText("Dismiss")).not.toBeTruthy();
+ });
+
+ it("calls onRequestClose when No or Dismiss is clicked", () => {
+ const onRequestCloseMock = jest.fn();
+
+ const { getByText } = render(
+
+ );
+
+ fireEvent.click(getByText("Dismiss"));
+ expect(onRequestCloseMock).toHaveBeenCalled();
+
+ const { getByText: getByTextConfirmation } = render(
+
+ );
+
+ fireEvent.click(getByTextConfirmation("No"));
+ expect(onRequestCloseMock).toHaveBeenCalledTimes(2);
+ });
+
+ it("calls onConfirmation when Yes is clicked", () => {
+ const onConfirmationMock = jest.fn();
+
+ const { getByText } = render(
+
);
- expect(TestUtils.select("AlertModal__Message")?.innerHTML).toBe(message);
- expect(TestUtils.select("AlertModal__ExtraMessage")?.textContent).toBe(
- extraMessage
+
+ fireEvent.click(getByText("Yes"));
+ expect(onConfirmationMock).toHaveBeenCalled();
+ });
+
+ it("renders the message when provided", () => {
+ const customMessage = "This is a custom message";
+ const { getByText } = render();
+ expect(getByText(customMessage)).toBeTruthy();
+ });
+
+ it("does not render the message when not provided", () => {
+ const { queryByText } = render();
+ expect(queryByText(/This is a custom message/i)).not.toBeTruthy();
+ });
+
+ it("renders the extraMessage when provided", () => {
+ const customExtraMessage = "This is an extra message";
+ const { getByText } = render(
+
);
+ expect(getByText(customExtraMessage)).toBeTruthy();
+ });
+
+ it("does not render the extraMessage when not provided", () => {
+ const { queryByText } = render();
+ expect(queryByText(/This is an extra message/i)).not.toBeTruthy();
+ });
+
+ it("renders confirmation buttons when type is confirmation", () => {
+ const { getByText } = render();
- expect(queryByText("No")).toBeTruthy();
- expect(queryByText("Yes")).toBeTruthy();
- expect(queryByText("Dismiss")).toBeNull();
- expect(StatusImage).toHaveBeenCalledWith({ status: "confirmation" }, {});
+ expect(getByText("Yes")).toBeTruthy();
+ expect(getByText("No")).toBeTruthy();
});
- it("has correct buttons for errors", () => {
- const { queryByText } = render(
+ it("renders message and extraMessage for confirmation", () => {
+ const customMessage = "Confirm this action?";
+ const customExtraMessage = "This will perform a special task.";
+ const { getByText } = render(
);
- expect(queryByText("Dismiss")).toBeTruthy();
- expect(queryByText("No")).toBeNull();
- expect(queryByText("Yes")).toBeNull();
+
+ expect(getByText(customMessage)).toBeTruthy();
+ expect(getByText(customExtraMessage)).toBeTruthy();
});
- it("renders loading", () => {
- const { queryByText } = render(
+ it("calls onConfirmation when Yes is clicked", () => {
+ const onConfirmationMock = jest.fn();
+ const { getByText } = render(
);
- expect(queryByText("Dismiss")).toBeNull();
- expect(queryByText("No")).toBeNull();
- expect(queryByText("Yes")).toBeNull();
- expect(StatusImage).toHaveBeenCalledWith({ status: "RUNNING" }, {});
+
+ fireEvent.click(getByText("Yes"));
+ expect(onConfirmationMock).toHaveBeenCalled();
+ });
+
+ it("calls onRequestClose when No is clicked", () => {
+ const onRequestCloseMock = jest.fn();
+ const { getByText } = render(
+
+ );
+
+ fireEvent.click(getByText("No"));
+ expect(onRequestCloseMock).toHaveBeenCalled();
});
});
diff --git a/src/components/ui/AutocompleteInput/test.tsx b/src/components/ui/AutocompleteInput/test.tsx
deleted file mode 100644
index 4e504f35..00000000
--- a/src/components/ui/AutocompleteInput/test.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
-Copyright (C) 2017 Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
-
-import React from "react";
-import sinon from "sinon";
-import { shallow } from "enzyme";
-import TW from "@src/utils/TestWrapper";
-import AutocompleteInput from ".";
-
-type Props = {
- value: string;
- customRef?: (ref: HTMLElement) => void;
- ref?: (ref: HTMLElement) => void;
- onChange: (value: string) => void;
- onClick?: () => void;
- disabled?: boolean;
- width?: number;
- large?: boolean;
- onFocus?: () => void;
- highlight?: boolean;
-};
-
-const wrap = (props: Props) =>
- new TW(
- shallow(
- // eslint-disable-next-line react/jsx-props-no-spreading
-
- ),
- "acInput"
- );
-
-describe("AutocompleteInput Component", () => {
- it("renders input with correct data", () => {
- const wrapper = wrap({
- value: "value",
- onChange: () => {},
- });
-
- expect(wrapper.find("text").prop("embedded")).toBe(true);
- expect(wrapper.find("text").prop("value")).toBe("value");
- });
-
- it("dispatches click", () => {
- const onClick = sinon.spy();
- const wrapper = wrap({
- value: "value",
- onChange: () => {},
- onClick,
- });
- wrapper.find("arrow").click();
- expect(onClick.calledOnce).toBe(true);
- });
-});
diff --git a/src/components/ui/Dropdowns/ActionDropdown/ActionDropdown.tsx b/src/components/ui/Dropdowns/ActionDropdown/ActionDropdown.tsx
index 159f0d1d..d640df09 100644
--- a/src/components/ui/Dropdowns/ActionDropdown/ActionDropdown.tsx
+++ b/src/components/ui/Dropdowns/ActionDropdown/ActionDropdown.tsx
@@ -12,20 +12,19 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
+import autobind from "autobind-decorator";
+import { observer } from "mobx-react";
import React from "react";
import ReactDOM from "react-dom";
-import { observer } from "mobx-react";
import styled, { css } from "styled-components";
-import autobind from "autobind-decorator";
+import { ThemePalette, ThemeProps } from "@src/components/Theme";
import DropdownButton from "@src/components/ui/Dropdowns/DropdownButton";
import {
List,
ListItems,
Tip,
} from "@src/components/ui/Dropdowns/DropdownLink";
-
-import { ThemePalette, ThemeProps } from "@src/components/Theme";
import StatusIcon from "@src/components/ui/StatusComponents/StatusIcon";
const Wrapper = styled.div`
@@ -64,7 +63,7 @@ const ListStyle = css`
${ThemeProps.boxShadow}
border: none;
`;
-export const TEST_ID = "actionDropdown";
+
export type DropdownAction = {
label: string;
color?: string;
diff --git a/src/components/ui/Logo/Logo.spec.tsx b/src/components/ui/Logo/Logo.spec.tsx
index 09a067b3..a4edc1d8 100644
--- a/src/components/ui/Logo/Logo.spec.tsx
+++ b/src/components/ui/Logo/Logo.spec.tsx
@@ -13,8 +13,9 @@ along with this program. If not, see .
*/
import React from "react";
-import { render } from "@testing-library/react";
+
import Logo from "@src/components/ui/Logo";
+import { render } from "@testing-library/react";
import TestUtils from "@tests/TestUtils";
jest.mock("react-router-dom", () => ({ Link: "a" }));
diff --git a/src/components/ui/SmallLoading/SmallLoading.tsx b/src/components/ui/SmallLoading/SmallLoading.tsx
index a4815276..444a6185 100644
--- a/src/components/ui/SmallLoading/SmallLoading.tsx
+++ b/src/components/ui/SmallLoading/SmallLoading.tsx
@@ -12,8 +12,8 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
-import React from "react";
import { observer } from "mobx-react";
+import React from "react";
import styled, { css } from "styled-components";
import { ThemePalette, ThemeProps } from "@src/components/Theme";
@@ -53,8 +53,6 @@ const ProgressText = styled.div`
`;
const CircleProgressBar = styled.circle``;
-export const TEST_ID = "smallLoading";
-
export type Props = {
loadingProgress: number;
};
diff --git a/tests/TestUtils.ts b/tests/TestUtils.ts
index c311559c..af335f32 100644
--- a/tests/TestUtils.ts
+++ b/tests/TestUtils.ts
@@ -1,20 +1,27 @@
-const className = (classNameStartsWith: string, useContains?: boolean) => `[class${useContains ? '*' : '^'}=${classNameStartsWith}]`
+const className = (classNameStartsWith: string, useContains?: boolean) =>
+ `[class${useContains ? "*" : "^"}=${classNameStartsWith}]`;
export default {
- select: (name: string, parent?: Element) => (parent || document).querySelector(className(name)),
- selectInput: (name: string, parent?: Element) => (parent || document).querySelector(className(name)),
- selectContains: (name: string, parent?: Element) => (parent || document).querySelector(className(name, true)),
- selectAll: (name: string, parent?: Element) => (parent || document).querySelectorAll(className(name)),
+ select: (name: string, parent?: Element) =>
+ (parent || document).querySelector(className(name)),
+ selectInput: (name: string, parent?: Element) =>
+ (parent || document).querySelector(className(name)),
+ selectContains: (name: string, parent?: Element) =>
+ (parent || document).querySelector(className(name, true)),
+ selectAll: (name: string, parent?: Element) =>
+ (parent || document).querySelectorAll(className(name)),
rgbToHex: (rgb: string) => {
const componentToHex = (c: number) => {
- const hex = c.toString(16).toUpperCase()
- return hex.length === 1 ? `0${hex}` : hex
- }
- const matches = /rgb\((\d+), (\d+), (\d+)\)/.exec(rgb)
+ const hex = c.toString(16).toUpperCase();
+ return hex.length === 1 ? `0${hex}` : hex;
+ };
+ const matches = /rgb\((\d+), (\d+), (\d+)\)/.exec(rgb);
if (matches) {
- const transform = (match: string) => componentToHex(parseInt(match, 10))
- return `#${transform(matches[1])}${transform(matches[2])}${transform(matches[3])}`
+ const transform = (match: string) => componentToHex(parseInt(match, 10));
+ return `#${transform(matches[1])}${transform(matches[2])}${transform(
+ matches[3]
+ )}`;
}
- return rgb
+ return rgb;
},
-}
+};
diff --git a/tests/mocks/EndpointsMock.ts b/tests/mocks/EndpointsMock.ts
new file mode 100644
index 00000000..2225787c
--- /dev/null
+++ b/tests/mocks/EndpointsMock.ts
@@ -0,0 +1,32 @@
+import { Endpoint } from "@src/@types/Endpoint";
+
+export const OPENSTACK_ENDPOINT_MOCK: Endpoint = {
+ id: "openstack",
+ name: "OpenStack",
+ description: "Openstack endpoint",
+ type: "openstack",
+ created_at: "2023-11-26T12:00:00Z",
+ mapped_regions: ["us-east-1"],
+ connection_info: {
+ host: "https://api.example.com:1234/path",
+ username: "admin",
+ password: "password",
+ project_name: "admin",
+ project_domain_name: "Default",
+ user_domain_name: "Default",
+ },
+};
+
+export const VMWARE_ENDPOINT_MOCK: Endpoint = {
+ id: "vmware",
+ name: "VMware",
+ description: "VMware endpoint",
+ type: "vmware_vsphere",
+ created_at: "2023-11-26T12:00:00Z",
+ mapped_regions: ["us-east-1"],
+ connection_info: {
+ host: "https://api.example.com:1234/path",
+ username: "admin",
+ password: "password",
+ },
+};
diff --git a/tests/mocks/ExecutionsMock.ts b/tests/mocks/ExecutionsMock.ts
new file mode 100644
index 00000000..0ae229ef
--- /dev/null
+++ b/tests/mocks/ExecutionsMock.ts
@@ -0,0 +1,36 @@
+import { Execution, ExecutionTasks } from "@src/@types/Execution";
+import { ProgressUpdate, Task } from "@src/@types/Task";
+
+export const EXECUTION_MOCK: Execution = {
+ id: "execution-id",
+ number: 1,
+ status: "COMPLETED",
+ created_at: "2023-11-26T12:00:00Z",
+ updated_at: "2023-11-26T12:00:00Z",
+ type: "replica_execution",
+};
+
+export const PROGRESS_UPDATE_MOCK: ProgressUpdate = {
+ index: 1,
+ message: "message progress 66%",
+ created_at: "2023-11-26T12:00:00Z",
+ total_steps: 1,
+ current_step: 1,
+};
+
+export const TASK_MOCK: Task = {
+ id: "task-id",
+ status: "COMPLETED",
+ created_at: "2023-11-26T12:00:00Z",
+ updated_at: "2023-11-26T12:00:00Z",
+ progress_updates: [PROGRESS_UPDATE_MOCK],
+ task_type: "replica_execution",
+ instance: "instance-id",
+ depends_on: [],
+ exception_details: "exception-details",
+};
+
+export const EXECUTION_TASKS_MOCK: ExecutionTasks = {
+ ...EXECUTION_MOCK,
+ tasks: [TASK_MOCK],
+};
diff --git a/tests/mocks/InstancesMock.ts b/tests/mocks/InstancesMock.ts
new file mode 100644
index 00000000..04e07489
--- /dev/null
+++ b/tests/mocks/InstancesMock.ts
@@ -0,0 +1,24 @@
+import { Instance } from "@src/@types/Instance";
+import { DISK_MOCK } from "@tests/mocks/StoragesMock";
+
+export const INSTANCE_MOCK: Instance = {
+ id: "instance-id",
+ name: "instance-name",
+ flavor_name: "instance-flavor-name",
+ instance_name: "instance-instance-name",
+ num_cpu: 1,
+ memory_mb: 1024,
+ os_type: "instance-os-type",
+ devices: {
+ nics: [
+ {
+ id: "nic-id",
+ network_name: "network-name",
+ ip_addresses: ["nic-ip-addresses"],
+ mac_address: "nic-mac-address",
+ network_id: "network-id",
+ },
+ ],
+ disks: [DISK_MOCK],
+ },
+};
diff --git a/tests/mocks/MetalHubServerMock.ts b/tests/mocks/MetalHubServerMock.ts
new file mode 100644
index 00000000..883f334a
--- /dev/null
+++ b/tests/mocks/MetalHubServerMock.ts
@@ -0,0 +1,52 @@
+import { MetalHubServer } from "@src/@types/MetalHub";
+
+export const METALHUB_SERVER_MOCK: MetalHubServer = {
+ id: 12345,
+ active: true,
+ hostname: "server01.example.com",
+ created_at: "2023-11-26T12:00:00Z",
+ updated_at: "2023-11-26T12:00:00Z",
+ api_endpoint: "https://api.example.com:1234/path",
+ firmware_type: "UEFI",
+ memory: 32768, // in MB
+ os_info: {
+ os_name: "Ubuntu",
+ os_version: "20.04",
+ },
+ disks: [
+ {
+ id: "disk1",
+ path: "/dev/sda",
+ name: "Main Disk",
+ size: 1024000, // in MB
+ physical_sector_size: 512,
+ partitions: [
+ {
+ name: "boot",
+ path: "/dev/sda1",
+ partition_uuid: "uuid-boot",
+ sectors: 2048,
+ start_sector: 0,
+ end_sector: 2047,
+ },
+ {
+ name: "root",
+ path: "/dev/sda2",
+ sectors: 1021952,
+ start_sector: 2048,
+ end_sector: 1024000,
+ },
+ ],
+ },
+ ],
+ nics: [
+ {
+ interface_type: "Ethernet",
+ ip_addresses: ["192.168.1.10", "192.168.1.11"],
+ mac_address: "00:1B:44:11:3A:B7",
+ nic_name: "eth0",
+ },
+ ],
+ physical_cores: 8,
+ logical_cores: 16,
+};
diff --git a/tests/mocks/MinionPoolMock.ts b/tests/mocks/MinionPoolMock.ts
new file mode 100644
index 00000000..d46b1f80
--- /dev/null
+++ b/tests/mocks/MinionPoolMock.ts
@@ -0,0 +1,83 @@
+import {
+ MinionMachine,
+ MinionPool,
+ MinionPoolDetails,
+} from "@src/@types/MinionPool";
+
+export const MINION_MACHINE_MOCK: MinionMachine = {
+ id: "minion-machine-id",
+ created_at: "2023-11-26T12:00:00Z",
+ updated_at: "2023-11-26T12:00:00Z",
+ allocation_status: "ALLOCATED",
+ connection_info: {},
+ power_status: "on",
+ provider_properties: {},
+ last_used_at: "2023-11-26T12:00:00Z",
+ allocated_action: "replica-id",
+};
+
+export const MINION_POOL_MOCK: MinionPool = {
+ id: "minion-pool-id",
+ created_at: "2023-11-26T12:00:00Z",
+ updated_at: "2023-11-26T12:00:00Z",
+ name: "minion-pool-name",
+ os_type: "linux",
+ status: "ACTIVE",
+ minimum_minions: 1,
+ maximum_minions: 10,
+ environment_options: {
+ option_1: "option_1_value",
+ object_option: {
+ object_option_1: "object_option_1_value",
+ },
+ array_option: ["array_option_1_value", "array_option_2_value"],
+ object_with_mappings: {
+ mappings: [
+ {
+ source: "source_value",
+ destination: "destination_value",
+ },
+ ],
+ },
+ },
+ endpoint_id: "openstack",
+ notes: "minion-pool-notes",
+ platform: "source",
+ minion_machines: [{ ...MINION_MACHINE_MOCK }],
+ minion_retention_strategy: "poweroff",
+ minion_max_idle_time: 0,
+};
+
+export const MINION_POOL_DETAILS_MOCK: MinionPoolDetails = {
+ ...MINION_POOL_MOCK,
+ events: [
+ {
+ id: "minion-pool-event-id",
+ index: 0,
+ level: "INFO",
+ message: "minion-pool-event-message",
+ created_at: "2023-11-26T12:00:00Z",
+ },
+ {
+ id: "minion-pool-event-id-2",
+ index: 1,
+ level: "DEBUG",
+ message: "minion-pool-event-message-debug",
+ created_at: "2023-11-25T12:00:00Z",
+ },
+ ],
+ progress_updates: [
+ {
+ id: "minion-pool-progress-update-id",
+ current_step: 0,
+ message: "minion-pool-progress-update-message",
+ created_at: "2023-11-26T12:00:00Z",
+ },
+ {
+ id: "minion-pool-progress-update-2",
+ current_step: 1,
+ message: "minion-pool-progress-update-message-2",
+ created_at: "2023-11-25T12:00:00Z",
+ },
+ ],
+};
diff --git a/tests/mocks/NetworksMock.ts b/tests/mocks/NetworksMock.ts
new file mode 100644
index 00000000..6d4e1d2d
--- /dev/null
+++ b/tests/mocks/NetworksMock.ts
@@ -0,0 +1,8 @@
+import { Network } from "@src/@types/Network";
+
+export const NETWORK_MOCK: Network = {
+ name: "network-name",
+ id: "network-id",
+ security_groups: ["security-group-id"],
+ port_keys: ["port-key"],
+};
diff --git a/tests/mocks/ProvidersMock.ts b/tests/mocks/ProvidersMock.ts
new file mode 100644
index 00000000..e6677ba6
--- /dev/null
+++ b/tests/mocks/ProvidersMock.ts
@@ -0,0 +1,50 @@
+import { Providers } from "@src/@types/Providers";
+import { providerTypes } from "@src/constants";
+
+export const PROVIDERS_MOCK: Providers = {
+ aws: {
+ types: [],
+ },
+ azure: {
+ types: [],
+ },
+ openstack: {
+ types: [providerTypes.DESTINATION_MINION_POOL],
+ },
+ opc: {
+ types: [],
+ },
+ opca: {
+ types: [],
+ },
+ oracle_vm: {
+ types: [],
+ },
+ vmware_vsphere: {
+ types: [providerTypes.SOURCE_MINION_POOL],
+ },
+ oci: {
+ types: [],
+ },
+ "hyper-v": {
+ types: [],
+ },
+ scvmm: {
+ types: [],
+ },
+ olvm: {
+ types: [],
+ },
+ kubevirt: {
+ types: [],
+ },
+ metal: {
+ types: [],
+ },
+ rhev: {
+ types: [],
+ },
+ lxd: {
+ types: [],
+ },
+};
diff --git a/tests/mocks/SchedulesMock.ts b/tests/mocks/SchedulesMock.ts
new file mode 100644
index 00000000..ad0b72b5
--- /dev/null
+++ b/tests/mocks/SchedulesMock.ts
@@ -0,0 +1,15 @@
+import { DateTime } from "luxon";
+
+import { Schedule } from "@src/@types/Schedule";
+
+export const SCHEDULE_MOCK: Schedule = {
+ id: "schedule-1",
+ enabled: true,
+ schedule: {
+ hour: 1,
+ dow: 1,
+ dom: 1,
+ },
+ expiration_date: DateTime.now().plus({ years: 1 }).toJSDate(),
+ shutdown_instances: true,
+};
diff --git a/tests/mocks/StoragesMock.ts b/tests/mocks/StoragesMock.ts
new file mode 100644
index 00000000..6623a923
--- /dev/null
+++ b/tests/mocks/StoragesMock.ts
@@ -0,0 +1,23 @@
+import { StorageBackend } from "@src/@types/Endpoint";
+import { Disk } from "@src/@types/Instance";
+
+export const DISK_MOCK: Disk = {
+ id: "disk-id",
+ name: "disk-name",
+ storage_backend_identifier: "disk-storage-backend-identifier",
+ format: "disk-format",
+ guest_device: "disk-guest-device",
+ size_bytes: 1024,
+ disabled: {
+ message: "disk-disabled-message",
+ info: "disk-disabled-info",
+ },
+};
+
+export const STORAGE_BACKEND_MOCK: StorageBackend = {
+ id: "storage-backend-id",
+ name: "storage-backend-name",
+ additional_provider_properties: {
+ supported_bus_types: ["storage-backend-supported-bus-types"],
+ },
+};
diff --git a/tests/mocks/TransferMock.ts b/tests/mocks/TransferMock.ts
new file mode 100644
index 00000000..a6c82495
--- /dev/null
+++ b/tests/mocks/TransferMock.ts
@@ -0,0 +1,97 @@
+import {
+ MigrationItem,
+ MigrationItemDetails,
+ ReplicaItem,
+ ReplicaItemDetails,
+} from "@src/@types/MainItem";
+import { EXECUTION_MOCK, TASK_MOCK } from "@tests/mocks/ExecutionsMock";
+import { INSTANCE_MOCK } from "@tests/mocks/InstancesMock";
+
+export const REPLICA_MOCK: ReplicaItem = {
+ id: "replica-id",
+ name: "replica-name",
+ type: "replica",
+ description: "replica-description",
+ notes: "replica-notes",
+ created_at: "2023-11-26T12:00:00Z",
+ updated_at: "2023-11-26T12:00:00Z",
+ origin_endpoint_id: "vmware",
+ destination_endpoint_id: "openstack",
+ origin_minion_pool_id: "origin-minion-pool-id",
+ destination_minion_pool_id: "destination-minion-pool-id",
+ instances: ["instance-id"],
+ info: {},
+ destination_environment: {
+ option_1: "option_1_value",
+ object_option: {
+ object_option_1: "object_option_1_value",
+ },
+ array_option: ["array_option_1_value", "array_option_2_value"],
+ object_with_mappings: {
+ mappings: [
+ {
+ source: "source_value",
+ destination: "destination_value",
+ },
+ ],
+ disk_mappings: {},
+ },
+ password: "password-value",
+ },
+ source_environment: {},
+ transfer_result: {
+ "instance-id": { ...INSTANCE_MOCK },
+ },
+ last_execution_status: "COMPLETED",
+ user_id: "user-id",
+ network_map: {
+ // @ts-ignore
+ "network-name": "network-name",
+ },
+ storage_mappings: {
+ backend_mappings: [
+ {
+ destination: "destination_value",
+ source: "source_value",
+ },
+ ],
+ default: "default_value",
+ disk_mappings: [
+ {
+ destination: "destination_value",
+ disk_id: "disk_id_value",
+ },
+ ],
+ },
+};
+
+export const REPLICA_ITEM_DETAILS_MOCK: ReplicaItemDetails = {
+ ...REPLICA_MOCK,
+ executions: [EXECUTION_MOCK],
+};
+
+export const MIGRATION_MOCK: MigrationItem = {
+ id: "migration-id",
+ name: "migration-name",
+ type: "migration",
+ description: "migration-description",
+ notes: "migration-notes",
+ created_at: "2023-11-26T12:00:00Z",
+ updated_at: "2023-11-26T12:00:00Z",
+ origin_endpoint_id: "openstack",
+ destination_endpoint_id: "vmware",
+ origin_minion_pool_id: "origin-minion-pool-id",
+ destination_minion_pool_id: "destination-minion-pool-id",
+ instances: ["instance-id"],
+ info: {},
+ destination_environment: {},
+ source_environment: {},
+ transfer_result: {},
+ last_execution_status: "COMPLETED",
+ user_id: "user-id",
+};
+
+export const MIGRATION_ITEM_DETAILS_MOCK: MigrationItemDetails = {
+ ...MIGRATION_MOCK,
+ tasks: [{ ...TASK_MOCK, task_type: "migration_task" }],
+};
diff --git a/tests/mocks/UsersMock.ts b/tests/mocks/UsersMock.ts
new file mode 100644
index 00000000..86dc2bb3
--- /dev/null
+++ b/tests/mocks/UsersMock.ts
@@ -0,0 +1,17 @@
+import { User } from "@src/@types/User";
+
+export const USER_MOCK: User = {
+ scoped: true,
+ project: {
+ id: "project-id",
+ name: "project-name",
+ },
+ email: "user-email",
+ name: "user-name",
+ id: "user-id",
+ description: "user-description",
+ enabled: true,
+ project_id: "project-id",
+ domain_id: "domain-id",
+ isAdmin: true,
+};
diff --git a/tests/setup.js b/tests/setup.js
index 2b2db689..9865dc88 100644
--- a/tests/setup.js
+++ b/tests/setup.js
@@ -1,5 +1,12 @@
-window.scroll = jest.fn()
+import "jest-canvas-mock";
-const originalErr = console.error.bind(console.error)
-// @TODO fix components which throw these warnings
-console.error = (...args) => !args[0].toString().includes('Unknown event handler property') && originalErr(...args)
+window.scroll = jest.fn();
+const originalWarn = console.warn.bind(console.warn);
+console.warn = (...args) =>
+ !args[0].toString().includes("observer class") && originalWarn(...args);
+
+const originalErr = console.error.bind(console.error);
+console.error = (...args) =>
+ // @TODO fix components which throw these warnings
+ !args[0].toString().includes("Unknown event handler property") &&
+ originalErr(...args);
diff --git a/tests/testCoverage.js b/tests/testCoverage.js
deleted file mode 100644
index 3a5d750c..00000000
--- a/tests/testCoverage.js
+++ /dev/null
@@ -1,62 +0,0 @@
-const fs = require('fs')
-
-const SKIP_FOLDERS = [
- {
- pattern: 'smart',
- reason: 'No smart components testing yet',
- },
- {
- pattern: 'AssessmentModule',
- reason: 'Assessment module is not in use for now',
- },
-]
-
-const main = async () => {
- let coveredComponents = 0
- let uncoveredComponents = 0
- let skippedComponents = 0
- const skippedMessageLog = []
-
- const readDir = async dir => {
- const exists = fs.existsSync(`${dir}/package.json`)
- if (exists) {
- const skippedPattern = SKIP_FOLDERS.find(skip => dir.includes(`/${skip.pattern}/`))
- if (skippedPattern) {
- skippedComponents += 1
- if (!skippedMessageLog.find(skip => skip.pattern === skippedPattern.pattern)) {
- skippedMessageLog.push(skippedPattern)
- }
- return
- }
-
- const files = await fs.promises.readdir(dir)
- const tsxFiles = files.filter(file => file.endsWith('.tsx') && !file.endsWith('.spec.tsx'))
- const specFiles = files.filter(file => file.endsWith('.spec.tsx'))
-
- if (tsxFiles.length > 0 && specFiles.length === 0) {
- uncoveredComponents += 1
- console.log(`\x1b[31m${dir}\x1b[0m`)
- } else if (tsxFiles.length > 0 && specFiles.length > 0) {
- coveredComponents += 1
- }
- } else {
- const files = await fs.promises.readdir(dir, { withFileTypes: true })
- for (const file of files) {
- if (file.isDirectory()) {
- await readDir(`${dir}/${file.name}`)
- }
- }
- }
- }
-
- await readDir('./src')
-
- skippedMessageLog.forEach(skip => console.log(`Skipping '${skip.pattern}' pattern. ${skip.reason}`))
-
- const percentage = (coveredComponents / (coveredComponents + uncoveredComponents)) * 100
- console.log(`\nSkipped components: ${skippedComponents}`)
- console.log(`Covered components: ${coveredComponents} / ${coveredComponents + uncoveredComponents}`)
- console.log(`\x1b[34mCoverage: ${percentage.toFixed(2)} %\x1b[0m`)
-}
-
-main()
diff --git a/yarn.lock b/yarn.lock
index 980b2b12..5821746f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3461,10 +3461,10 @@ __metadata:
languageName: node
linkType: hard
-"@types/luxon@npm:^3.3.2":
- version: 3.3.2
- resolution: "@types/luxon@npm:3.3.2"
- checksum: b9111132720eae0269538872a5a496b29587ecfc8edc3b0ff7d269aa93a5ff00a131b23d1e9d1f12ec39f2c779ad21bd8d9f90b122c85a182771aabde7f676b8
+"@types/luxon@npm:^3.3.3":
+ version: 3.3.3
+ resolution: "@types/luxon@npm:3.3.3"
+ checksum: 072dd39eea3f63453788fab2fcfc83eb33917afcaffe178ce08ecd8b016824b8ab3bfa991f66266f2fc1927768a56b4334945f2eb1d83638e325c0c43d7d0e86
languageName: node
linkType: hard
@@ -6317,7 +6317,7 @@ __metadata:
languageName: node
linkType: hard
-"color-name@npm:~1.1.4":
+"color-name@npm:^1.1.4, color-name@npm:~1.1.4":
version: 1.1.4
resolution: "color-name@npm:1.1.4"
checksum: b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610
@@ -6635,7 +6635,7 @@ __metadata:
"@types/file-saver": ^2.0.1
"@types/jest": ^27.0.2
"@types/js-cookie": ^2.2.6
- "@types/luxon": ^3.3.2
+ "@types/luxon": ^3.3.3
"@types/moment-timezone": ^0.5.13
"@types/react": ^16.13.1
"@types/react-collapse": ^5.0.0
@@ -6669,6 +6669,7 @@ __metadata:
fs: ^0.0.1-security
html-webpack-plugin: ^3.2.0
jest: ^27.3.1
+ jest-canvas-mock: ^2.5.2
js-base64: ^3.5.2
js-cookie: ^2.2.1
jszip: ^3.8.0
@@ -6919,6 +6920,13 @@ __metadata:
languageName: node
linkType: hard
+"cssfontparser@npm:^1.2.1":
+ version: 1.2.1
+ resolution: "cssfontparser@npm:1.2.1"
+ checksum: 952d487cddab591fb944f2a4c326a7736bc963784a6d92b6ad4051f3bf5ee49a732eff62e29a52ff085197cb07f5bd66525a2245ded7fd356113ac81be9238b9
+ languageName: node
+ linkType: hard
+
"cssom@npm:^0.4.4":
version: 0.4.4
resolution: "cssom@npm:0.4.4"
@@ -7990,8 +7998,8 @@ __metadata:
"eslint-plugin-coriolis-web@file:./src/utils/eslint-plugin-coriolis-web::locator=coriolis-web%40workspace%3A.":
version: 0.0.0
- resolution: "eslint-plugin-coriolis-web@file:./src/utils/eslint-plugin-coriolis-web#./src/utils/eslint-plugin-coriolis-web::hash=2f2a4c&locator=coriolis-web%40workspace%3A."
- checksum: 53270112c732af777cdf8044e3f0c8af4bf377506dd298d924583c79635991d785ed4831e38cb3d4f55d311f0918ef7048c051a147d39f854308b85362f2c300
+ resolution: "eslint-plugin-coriolis-web@file:./src/utils/eslint-plugin-coriolis-web#./src/utils/eslint-plugin-coriolis-web::hash=11b726&locator=coriolis-web%40workspace%3A."
+ checksum: bb3a1dd0feae7faeac9e86b3b3be805a73ca7019130839e2fc5fa247276ddb8a3fd9c1fcca7390cdf1d65201483300df4beb8767c1de7e0d843aad494105c11a
languageName: node
linkType: hard
@@ -11054,6 +11062,16 @@ __metadata:
languageName: node
linkType: hard
+"jest-canvas-mock@npm:^2.5.2":
+ version: 2.5.2
+ resolution: "jest-canvas-mock@npm:2.5.2"
+ dependencies:
+ cssfontparser: ^1.2.1
+ moo-color: ^1.0.2
+ checksum: a3004d2e96473049045e49dcf98e5ea6011494048ab42b5422b3089d9ff406aaca8353e79587055d840fa145541668eb8f78613765f252ad5901a8217e91ea5d
+ languageName: node
+ linkType: hard
+
"jest-changed-files@npm:^27.5.1":
version: 27.5.1
resolution: "jest-changed-files@npm:27.5.1"
@@ -12778,6 +12796,15 @@ __metadata:
languageName: node
linkType: hard
+"moo-color@npm:^1.0.2":
+ version: 1.0.3
+ resolution: "moo-color@npm:1.0.3"
+ dependencies:
+ color-name: ^1.1.4
+ checksum: 02bf59b6bbd5e86641bc062e2dc0843e6e579e18ef67e1c8e93bfc01945df578f20e66ce16aa9632db2aa0e16806e0914a26eb345a804f45fff1ae12a8906a29
+ languageName: node
+ linkType: hard
+
"move-concurrently@npm:^1.0.1":
version: 1.0.1
resolution: "move-concurrently@npm:1.0.1"