From 9850dd863310192680dfe3329981aa820202fe7e Mon Sep 17 00:00:00 2001 From: Ahmet Can Buyukyilmaz Date: Wed, 29 Jan 2025 19:41:02 +0300 Subject: [PATCH] test(images): added tests for header control buttons change source, select upstream images, stop image import and delete --- .../ImageListHeader/ImageListHeader.test.tsx | 354 +++++++++++++++++- .../ImageListHeader/ImageListHeader.tsx | 32 +- 2 files changed, 368 insertions(+), 18 deletions(-) diff --git a/src/app/images/views/ImageList/ImageListHeader/ImageListHeader.test.tsx b/src/app/images/views/ImageList/ImageListHeader/ImageListHeader.test.tsx index 4cd03b3ceb..48b2dd1f62 100644 --- a/src/app/images/views/ImageList/ImageListHeader/ImageListHeader.test.tsx +++ b/src/app/images/views/ImageList/ImageListHeader/ImageListHeader.test.tsx @@ -1,3 +1,6 @@ +import { render } from "@testing-library/react"; +import { Provider } from "react-redux"; +import { MemoryRouter } from "react-router-dom"; import configureStore from "redux-mock-store"; import { vi } from "vitest"; @@ -5,11 +8,21 @@ import ImageListHeader, { Labels as ImageListHeaderLabels, } from "./ImageListHeader"; +import * as sidePanelHooks from "@/app/base/side-panel-context"; +import { ImageSidePanelViews } from "@/app/images/constants"; +import { bootResourceActions } from "@/app/store/bootresource"; +import { BootResourceSourceType } from "@/app/store/bootresource/types"; import { configActions } from "@/app/store/config"; import { ConfigNames } from "@/app/store/config/types"; import type { RootState } from "@/app/store/root/types"; import * as factory from "@/testing/factories"; -import { userEvent, screen, renderWithBrowserRouter } from "@/testing/utils"; +import { + userEvent, + screen, + renderWithBrowserRouter, + within, + expectTooltipOnHover, +} from "@/testing/utils"; const mockStore = configureStore(); @@ -140,3 +153,342 @@ describe("ImageListHeader", () => { ).not.toBeInTheDocument(); }); }); + +describe("Change sources", () => { + const setSidePanelContent = vi.fn(); + + beforeEach(() => { + vi.spyOn(sidePanelHooks, "useSidePanel").mockReturnValue({ + setSidePanelContent, + sidePanelContent: null, + setSidePanelSize: vi.fn(), + sidePanelSize: "regular", + }); + }); + + it("can trigger change source side panel form", async () => { + const state = factory.rootState({ + bootresource: factory.bootResourceState({ + ubuntu: factory.bootResourceUbuntu({ + sources: [ + factory.bootResourceUbuntuSource({ + source_type: BootResourceSourceType.MAAS_IO, + }), + ], + }), + }), + }); + renderWithBrowserRouter( + {}} />, + { state } + ); + + await userEvent.click( + screen.getByRole("button", { name: "Change source" }) + ); + + expect(setSidePanelContent).toHaveBeenCalledWith({ + view: ImageSidePanelViews.CHANGE_SOURCE, + extras: { hasSources: true }, + }); + }); + + it("renders the change source form and disables closing it if no sources are detected", async () => { + const state = factory.rootState({ + bootresource: factory.bootResourceState({ + ubuntu: factory.bootResourceUbuntu({ sources: [] }), + }), + }); + renderWithBrowserRouter( + {}} />, + { state } + ); + + await userEvent.click( + screen.getByRole("button", { name: "Change source" }) + ); + expect(setSidePanelContent).toHaveBeenCalledWith({ + view: ImageSidePanelViews.CHANGE_SOURCE, + extras: { hasSources: false }, + }); + }); + + it("renders the correct text for a single default source", () => { + const state = factory.rootState({ + bootresource: factory.bootResourceState({ + ubuntu: factory.bootResourceUbuntu({ + sources: [ + factory.bootResourceUbuntuSource({ + source_type: BootResourceSourceType.MAAS_IO, + }), + ], + }), + }), + }); + renderWithBrowserRouter( + {}} />, + { state } + ); + const images_from = screen.getByText("Images synced from"); + expect(within(images_from).getByText("maas.io")).toBeInTheDocument(); + }); + + it("renders the correct text for a single custom source", () => { + const state = factory.rootState({ + bootresource: factory.bootResourceState({ + ubuntu: factory.bootResourceUbuntu({ + sources: [ + factory.bootResourceUbuntuSource({ + source_type: BootResourceSourceType.CUSTOM, + url: "www.url.com", + }), + ], + }), + }), + }); + renderWithBrowserRouter( + {}} />, + { state } + ); + const images_from = screen.getByText("Images synced from"); + expect(within(images_from).getByText("www.url.com")).toBeInTheDocument(); + }); + + it("renders the correct text for multiple sources", () => { + const state = factory.rootState({ + bootresource: factory.bootResourceState({ + ubuntu: factory.bootResourceUbuntu({ + sources: [ + factory.bootResourceUbuntuSource(), + factory.bootResourceUbuntuSource(), + ], + }), + }), + }); + renderWithBrowserRouter( + {}} />, + { state } + ); + const images_from = screen.getByText("Images synced from"); + expect(within(images_from).getByText("sources")).toBeInTheDocument(); + }); + + it("disables the button to change source if resources are downloading", async () => { + const state = factory.rootState({ + bootresource: factory.bootResourceState({ + resources: [factory.bootResource({ downloading: true })], + ubuntu: factory.bootResourceUbuntu({ + sources: [factory.bootResourceUbuntuSource()], + }), + }), + }); + renderWithBrowserRouter( + {}} />, + { state } + ); + expect( + screen.getByRole("button", { name: "Change source" }) + ).toBeAriaDisabled(); + + await expectTooltipOnHover( + screen.getByRole("button", { name: "Change source" }), + "Cannot change source while images are downloading." + ); + }); +}); + +describe("Select upstream images", () => { + const setSidePanelContent = vi.fn(); + + beforeEach(() => { + vi.spyOn(sidePanelHooks, "useSidePanel").mockReturnValue({ + setSidePanelContent, + sidePanelContent: null, + setSidePanelSize: vi.fn(), + sidePanelSize: "regular", + }); + }); + + it("can trigger select upstream images side panel form", async () => { + const state = factory.rootState({ + bootresource: factory.bootResourceState({ + ubuntu: factory.bootResourceUbuntu({ + sources: [ + factory.bootResourceUbuntuSource({ + source_type: BootResourceSourceType.MAAS_IO, + }), + ], + }), + }), + }); + renderWithBrowserRouter( + {}} />, + { state } + ); + + await userEvent.click( + screen.getByRole("button", { name: "Select upstream images" }) + ); + + expect(setSidePanelContent).toHaveBeenCalledWith({ + view: ImageSidePanelViews.DOWNLOAD_IMAGE, + }); + }); + + it("does not show a button to select upstream images if there are images already downloading", () => { + const state = factory.rootState({ + bootresource: factory.bootResourceState({ + resources: [ + factory.bootResource({ downloading: true, name: "ubuntu/focal" }), + factory.bootResource({ downloading: false, name: "centos/centos70" }), + ], + ubuntu: factory.bootResourceUbuntu(), + }), + }); + renderWithBrowserRouter( + {}} />, + { + state, + } + ); + + expect( + screen.queryByRole("button", { name: "Select upstream images" }) + ).toBeAriaDisabled(); + }); +}); + +describe("Stop import", () => { + const mockStore = configureStore(); + + it("does not show a button to stop importing ubuntu images if none are downloading", () => { + const state = factory.rootState({ + bootresource: factory.bootResourceState({ + resources: [ + factory.bootResource({ downloading: false, name: "ubuntu/focal" }), + factory.bootResource({ downloading: false, name: "centos/centos70" }), + ], + ubuntu: factory.bootResourceUbuntu(), + }), + }); + renderWithBrowserRouter( + {}} />, + { + state, + } + ); + + expect( + screen.queryByRole("button", { name: "Stop import" }) + ).not.toBeInTheDocument(); + }); + + it("can dispatch an action to stop importing ubuntu images if at least one is downloading", async () => { + const state = factory.rootState({ + bootresource: factory.bootResourceState({ + resources: [ + factory.bootResource({ downloading: true, name: "ubuntu/focal" }), + ], + ubuntu: factory.bootResourceUbuntu(), + }), + }); + const store = mockStore(state); + render( + + + {}} /> + + + ); + await userEvent.click( + screen.getByRole("button", { name: "Stop image import" }) + ); + + const expectedAction = bootResourceActions.stopImport(); + const actualActions = store.getActions(); + expect( + actualActions.find((action) => action.type === expectedAction.type) + ).toStrictEqual(expectedAction); + }); + + it("enables 'Stop import' button if images are saving", async () => { + const state = factory.rootState({ + bootresource: factory.bootResourceState({ + resources: [ + factory.bootResource({ downloading: true, name: "ubuntu/focal" }), + ], + ubuntu: factory.bootResourceUbuntu(), + statuses: factory.bootResourceStatuses({ savingUbuntu: true }), + }), + }); + renderWithBrowserRouter( + {}} />, + { state } + ); + const stopImportButton = screen.getByRole("button", { + name: "Stop image import", + }); + expect(stopImportButton).toBeEnabled(); + }); +}); + +describe("Delete", () => { + const setSidePanelContent = vi.fn(); + + beforeEach(() => { + vi.spyOn(sidePanelHooks, "useSidePanel").mockReturnValue({ + setSidePanelContent, + sidePanelContent: null, + setSidePanelSize: vi.fn(), + sidePanelSize: "regular", + }); + }); + + it("disables the button to delete images if no rows are selected", async () => { + const state = factory.rootState({ + bootresource: factory.bootResourceState({ + ubuntu: factory.bootResourceUbuntu({ + sources: [ + factory.bootResourceUbuntuSource({ + source_type: BootResourceSourceType.MAAS_IO, + }), + ], + }), + }), + }); + renderWithBrowserRouter( + {}} />, + { state } + ); + + expect(screen.getByRole("button", { name: "Delete" })).toBeAriaDisabled(); + }); + + it("can trigger delete images side panel form", async () => { + const state = factory.rootState({ + bootresource: factory.bootResourceState({ + ubuntu: factory.bootResourceUbuntu({ + sources: [ + factory.bootResourceUbuntuSource({ + source_type: BootResourceSourceType.MAAS_IO, + }), + ], + }), + }), + }); + renderWithBrowserRouter( + , + { state } + ); + + await userEvent.click(screen.getByRole("button", { name: "Delete" })); + + expect(setSidePanelContent).toHaveBeenCalledWith({ + view: ImageSidePanelViews.DELETE_MULTIPLE_IMAGES, + extras: { + rowSelection: { 1: true }, + setRowSelection: vi.fn, + }, + }); + }); +}); diff --git a/src/app/images/views/ImageList/ImageListHeader/ImageListHeader.tsx b/src/app/images/views/ImageList/ImageListHeader/ImageListHeader.tsx index cf9c91c95d..f4ddf8a60a 100644 --- a/src/app/images/views/ImageList/ImageListHeader/ImageListHeader.tsx +++ b/src/app/images/views/ImageList/ImageListHeader/ImageListHeader.tsx @@ -121,23 +121,6 @@ const ImageListHeader = ({ <> {" "} {Labels.RegionControllerImporting} - {canStopImport || stoppingImport ? ( - - ) : null} ) : rackImportRunning ? ( <> @@ -147,6 +130,21 @@ const ImageListHeader = ({ ) : null} )} + {canStopImport || stoppingImport ? ( + + ) : null} {!!ubuntu && (