| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383 |
- /*
- 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 <http://www.gnu.org/licenses/>.
- */
- import React from "react";
- import { Endpoint } from "@src/@types/Endpoint";
- import { ThemePalette } from "@src/components/Theme";
- import notificationStore from "@src/stores/NotificationStore";
- import FileUtils from "@src/utils/FileUtils";
- import { fireEvent, render, waitFor } from "@testing-library/react";
- import TestUtils from "@tests/TestUtils";
- import ChooseProvider from "./ChooseProvider";
- const OPENSTACK_ENDPOINT: Endpoint = {
- name: "Openstack",
- type: "openstack",
- id: "1",
- description: "",
- created_at: new Date().toISOString(),
- mapped_regions: ["region_1"],
- connection_info: {},
- };
- const mockDataTransfer = (files: any[]) => ({
- dataTransfer: {
- dropEffect: "none",
- files,
- items: files.map(file => ({
- kind: "file",
- type: file.type,
- getAsFile: () => file,
- })),
- types: ["Files"],
- },
- });
- jest.mock("react-router", () => ({ Link: "a" }));
- jest.mock("@src/stores/NotificationStore", () => ({
- alert: jest.fn(),
- }));
- jest.mock("@src/utils/Config", () => ({
- config: {
- providerSortPriority: {},
- providerNames: {
- openstack: "OpenStack",
- vmware_vsphere: "VMware vSphere",
- },
- },
- }));
- describe("ChooseProvider", () => {
- let defaultProps: ChooseProvider["props"];
- let readContentFromFileListMock: jest.SpyInstance;
- beforeEach(() => {
- jest.useFakeTimers();
- readContentFromFileListMock = jest.spyOn(
- FileUtils,
- "readContentFromFileList",
- );
- readContentFromFileListMock.mockResolvedValue([
- {
- name: "openstack.endpoint",
- content: JSON.stringify(OPENSTACK_ENDPOINT),
- },
- ]);
- defaultProps = {
- providers: ["openstack", "vmware_vsphere"],
- regions: [
- {
- id: "region_1",
- name: "Region 1",
- description: "",
- enabled: true,
- mapped_endpoints: [],
- },
- ],
- onCancelClick: jest.fn(),
- onProviderClick: jest.fn(),
- onUploadEndpoint: jest.fn(),
- loading: false,
- onValidateMultipleEndpoints: jest.fn(),
- multiValidating: false,
- multiValidation: [],
- onRemoveEndpoint: jest.fn(),
- onResetValidation: jest.fn(),
- };
- });
- afterEach(() => {
- jest.clearAllTimers();
- });
- it("renders without crashing", () => {
- render(<ChooseProvider {...defaultProps} />);
- expect(TestUtils.select("Button__")?.textContent).toBe("Cancel");
- });
- it('calls "onProviderClick" when a provider logo is clicked', () => {
- render(<ChooseProvider {...defaultProps} />);
- const providerButton = TestUtils.select("EndpointLogos__Wrapper")!;
- fireEvent.click(providerButton);
- expect(defaultProps.onProviderClick).toHaveBeenCalledWith("openstack");
- });
- it("shows loading state when loading is true", () => {
- render(<ChooseProvider {...defaultProps} loading />);
- expect(TestUtils.select("ChooseProvider__LoadingWrapper")).toBeTruthy();
- });
- it("handles file uploads and parses single files", async () => {
- const onUploadEndpointMock = jest.fn();
- render(
- <ChooseProvider
- {...defaultProps}
- onUploadEndpoint={onUploadEndpointMock}
- />,
- );
- const fileInput = document.querySelector('input[type="file"]')!;
- const file = new File(
- [JSON.stringify(OPENSTACK_ENDPOINT)],
- "openstack.endpoint",
- {
- type: "application/json",
- },
- );
- expect(fileInput).toBeTruthy();
- expect(defaultProps.onUploadEndpoint).not.toHaveBeenCalled();
- fireEvent.change(fileInput, { target: { files: [file] } });
- await waitFor(() => expect(onUploadEndpointMock).toHaveBeenCalled());
- });
- it("highlights dropzone on drag enter", () => {
- render(<ChooseProvider {...defaultProps} />);
- jest.advanceTimersByTime(1000);
- const uploadArea = TestUtils.select("ChooseProvider__Upload")!;
- const style = () => window.getComputedStyle(uploadArea);
- expect(style().border).toBe(`1px dashed white`);
- fireEvent.dragEnter(window, mockDataTransfer([]));
- expect(style().border).toBe(
- `1px dashed ${ThemePalette.primary.toLowerCase()}`,
- );
- });
- it("removes highlight from dropzone on drag leave", () => {
- render(<ChooseProvider {...defaultProps} />);
- jest.advanceTimersByTime(1000);
- const uploadArea = TestUtils.select("ChooseProvider__Upload")!;
- const style = () => window.getComputedStyle(uploadArea);
- fireEvent.dragLeave(window, mockDataTransfer([]));
- expect(style().border).toBe(`1px dashed white`);
- });
- it("processes file on drop", async () => {
- render(<ChooseProvider {...defaultProps} />);
- jest.advanceTimersByTime(1000);
- const file = new File(["endpoint content"], "openstack.endpoint", {
- type: "application/json",
- });
- fireEvent.drop(window, mockDataTransfer([file]));
- await waitFor(() =>
- expect(FileUtils.readContentFromFileList).toHaveBeenCalled(),
- );
- });
- it("displays error notification for invalid file content", async () => {
- readContentFromFileListMock.mockResolvedValue([
- {
- name: "invalid.endpoint",
- content: "invalid content",
- },
- ]);
- render(<ChooseProvider {...defaultProps} />);
- jest.advanceTimersByTime(1000);
- const file = new File(["invalid content"], "invalid.endpoint", {
- type: "application/json",
- });
- fireEvent.drop(window, mockDataTransfer([file]));
- await waitFor(() =>
- expect(notificationStore.alert).toHaveBeenCalledWith(
- "Invalid .endpoint file",
- "error",
- ),
- );
- });
- it("displays error notification if endpoint has no name", async () => {
- readContentFromFileListMock.mockResolvedValue([
- {
- name: "invalid.endpoint",
- content: JSON.stringify({
- OPENSTACK_ENDPOINT,
- name: "",
- }),
- },
- ]);
- render(<ChooseProvider {...defaultProps} />);
- jest.advanceTimersByTime(1000);
- const file = new File(["invalid content"], "invalid.endpoint", {
- type: "application/json",
- });
- fireEvent.drop(window, mockDataTransfer([file]));
- await waitFor(() =>
- expect(notificationStore.alert).toHaveBeenCalledWith(
- "Invalid .endpoint file",
- "error",
- ),
- );
- });
- it("processes multiple files and handles unique names", async () => {
- const multipleFilesMeta = [
- {
- name: "file1.endpoint",
- content: JSON.stringify(OPENSTACK_ENDPOINT),
- },
- {
- name: "file2.endpoint",
- content: JSON.stringify(OPENSTACK_ENDPOINT),
- },
- ];
- const multipleFiles = multipleFilesMeta.map(
- ({ name, content }) =>
- new File([content], name, {
- type: "application/json",
- }),
- );
- readContentFromFileListMock.mockResolvedValue(multipleFilesMeta);
- let setStateObj: any = {};
- jest
- .spyOn(ChooseProvider.prototype, "setState")
- .mockImplementationOnce((obj: any) => {
- setStateObj = obj;
- });
- render(<ChooseProvider {...defaultProps} />);
- const fileInput = document.querySelector('input[type="file"]')!;
- fireEvent.change(fileInput, { target: { files: multipleFiles } });
- await waitFor(() => {
- expect(defaultProps.onResetValidation).toHaveBeenCalledTimes(1);
- expect(setStateObj.multipleUploadedEndpoints[0].name).toBe(
- OPENSTACK_ENDPOINT.name,
- );
- expect(setStateObj.multipleUploadedEndpoints[1].name).toBe(
- `${OPENSTACK_ENDPOINT.name} (1)`,
- );
- });
- });
- it("fires onresizeupdate when multipleUploadedEndpoints changes", async () => {
- const onResizeUpdateMock = jest.fn();
- const multipleFilesMeta = [
- {
- name: "file1.endpoint",
- content: JSON.stringify(OPENSTACK_ENDPOINT),
- },
- {
- name: "file2.endpoint",
- content: JSON.stringify(OPENSTACK_ENDPOINT),
- },
- ];
- const multipleFiles = multipleFilesMeta.map(
- ({ name, content }) =>
- new File([content], name, {
- type: "application/json",
- }),
- );
- readContentFromFileListMock.mockResolvedValue(multipleFilesMeta);
- render(
- <ChooseProvider {...defaultProps} onResizeUpdate={onResizeUpdateMock} />,
- );
- const fileInput = document.querySelector('input[type="file"]')!;
- fireEvent.change(fileInput, { target: { files: multipleFiles } });
- await waitFor(() => {
- expect(onResizeUpdateMock).toHaveBeenCalled();
- });
- });
- it("adds drop effect on drag over", async () => {
- render(<ChooseProvider {...defaultProps} />);
- jest.advanceTimersByTime(1000);
- const transfer = mockDataTransfer([]);
- fireEvent.dragOver(window, transfer);
- await waitFor(() => {
- expect(transfer.dataTransfer.dropEffect).toBe("copy");
- });
- });
- it("shows warning for unindetified regions", async () => {
- readContentFromFileListMock.mockResolvedValue([
- {
- name: "openstack.endpoint",
- content: JSON.stringify({
- ...OPENSTACK_ENDPOINT,
- mapped_regions: ["region_2"],
- }),
- },
- ]);
- render(<ChooseProvider {...defaultProps} />);
- jest.advanceTimersByTime(1000);
- const file = new File(["invalid content"], "openstack.endpoint", {
- type: "application/json",
- });
- fireEvent.drop(window, mockDataTransfer([file]));
- await waitFor(() =>
- expect(notificationStore.alert).toHaveBeenCalledWith(
- "1 Coriolis Region couldn't be mapped",
- "warning",
- ),
- );
- });
- it("processes remove endpoint", () => {
- const chooseProviderInstance = new ChooseProvider(defaultProps);
- jest
- .spyOn(chooseProviderInstance, "setState")
- .mockImplementation((callback: any) => {
- callback({ multipleUploadedEndpoints: [OPENSTACK_ENDPOINT] });
- });
- chooseProviderInstance.handleRemoveUploadedEndpoint(
- OPENSTACK_ENDPOINT,
- true,
- );
- expect(defaultProps.onRemoveEndpoint).toHaveBeenCalledWith(
- OPENSTACK_ENDPOINT,
- );
- });
- it("handles regions change", () => {
- const chooseProviderInstance = new ChooseProvider(defaultProps);
- let nextState: any = {};
- jest
- .spyOn(chooseProviderInstance, "setState")
- .mockImplementation((callback: any) => {
- nextState = callback({
- multipleUploadedEndpoints: [OPENSTACK_ENDPOINT],
- });
- });
- chooseProviderInstance.handleRegionsChange(OPENSTACK_ENDPOINT, [
- "region_2",
- ]);
- expect(nextState.multipleUploadedEndpoints[0].mapped_regions).toEqual([
- "region_2",
- ]);
- });
- });
|