/*
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 { 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();
expect(TestUtils.select("Button__")?.textContent).toBe("Cancel");
});
it('calls "onProviderClick" when a provider logo is clicked', () => {
render();
const providerButton = TestUtils.select("EndpointLogos__Wrapper")!;
fireEvent.click(providerButton);
expect(defaultProps.onProviderClick).toHaveBeenCalledWith("openstack");
});
it("shows loading state when loading is true", () => {
render();
expect(TestUtils.select("ChooseProvider__LoadingWrapper")).toBeTruthy();
});
it("handles file uploads and parses single files", async () => {
const onUploadEndpointMock = jest.fn();
render(
,
);
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();
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();
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();
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();
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();
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();
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(
,
);
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();
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();
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",
]);
});
});