/** * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ import "@testing-library/jest-dom/extend-expect"; import { fireEvent, screen, waitFor } from "@testing-library/react"; import fse from "fs-extra"; import React from "react"; import type { ExtensionDiscovery } from "../../../../extensions/extension-discovery/extension-discovery"; import type { ExtensionLoader } from "../../../../extensions/extension-loader"; import { ConfirmDialog } from "../../confirm-dialog"; import { Extensions } from "../extensions"; import mockFs from "mock-fs"; import { mockWindow } from "../../../../../__mocks__/windowMock"; import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable"; import type { DiRender } from "../../test-utils/renderFor"; import { renderFor } from "../../test-utils/renderFor"; import extensionDiscoveryInjectable from "../../../../extensions/extension-discovery/extension-discovery.injectable"; import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import directoryForDownloadsInjectable from "../../../../common/app-paths/directory-for-downloads/directory-for-downloads.injectable"; import getConfigurationFileModelInjectable from "../../../../common/get-configuration-file-model/get-configuration-file-model.injectable"; import appVersionInjectable from "../../../../common/get-configuration-file-model/app-version/app-version.injectable"; import assert from "assert"; import type { InstallFromInput } from "../install-from-input/install-from-input"; import installFromInputInjectable from "../install-from-input/install-from-input.injectable"; import type { ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store"; import extensionInstallationStateStoreInjectable from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable"; import { observable, when } from "mobx"; mockWindow(); jest.setTimeout(30000); jest.mock("fs-extra"); jest.mock("../../notifications"); jest.mock("../../../../common/utils/downloadFile", () => ({ downloadFile: jest.fn(({ url }) => ({ promise: Promise.resolve(), url, cancel: () => {}, })), downloadJson: jest.fn(({ url }) => ({ promise: Promise.resolve({}), url, cancel: () => { }, })), })); jest.mock("../../../../common/utils/tar"); describe("Extensions", () => { let extensionLoader: ExtensionLoader; let extensionDiscovery: ExtensionDiscovery; let installFromInput: jest.MockedFunction; let extensionInstallationStateStore: ExtensionInstallationStateStore; let render: DiRender; beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); di.override(directoryForDownloadsInjectable, () => "some-directory-for-downloads"); di.permitSideEffects(getConfigurationFileModelInjectable); di.permitSideEffects(appVersionInjectable); mockFs({ "some-directory-for-user-data": {}, }); render = renderFor(di); installFromInput = jest.fn(); di.override(installFromInputInjectable, () => installFromInput); extensionLoader = di.inject(extensionLoaderInjectable); extensionDiscovery = di.inject(extensionDiscoveryInjectable); extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable); extensionLoader.addExtension({ id: "extensionId", manifest: { name: "test", version: "1.2.3", engines: { lens: "^5.5.0" }, }, absolutePath: "/absolute/path", manifestPath: "/symlinked/path/package.json", isBundled: false, isEnabled: true, isCompatible: true, }); extensionDiscovery.uninstallExtension = jest.fn(() => Promise.resolve()); }); afterEach(() => { mockFs.restore(); }); it("disables uninstall and disable buttons while uninstalling", async () => { extensionDiscovery.isLoaded = true; render(( <> )); const table = await screen.findByTestId("extensions-table"); const menuTrigger = table.querySelector(".table div[role='rowgroup'] .actions .Icon"); assert(menuTrigger); fireEvent.click(menuTrigger); expect(await screen.findByText("Disable")).toHaveAttribute("aria-disabled", "false"); expect(await screen.findByText("Uninstall")).toHaveAttribute("aria-disabled", "false"); fireEvent.click(await screen.findByText("Uninstall")); // Approve confirm dialog fireEvent.click(await screen.findByText("Yes")); await waitFor(() => { expect(extensionDiscovery.uninstallExtension).toHaveBeenCalled(); fireEvent.click(menuTrigger); expect(screen.getByText("Disable")).toHaveAttribute("aria-disabled", "true"); expect(screen.getByText("Uninstall")).toHaveAttribute("aria-disabled", "true"); }, { timeout: 30000, }); }); it("disables install button while installing", async () => { render(); const resolveInstall = observable.box(false); (fse.unlink as jest.MockedFunction).mockReturnValue(Promise.resolve()); installFromInput.mockImplementation(async (input) => { expect(input).toBe("https://test.extensionurl/package.tgz"); const clear = extensionInstallationStateStore.startPreInstall(); await when(() => resolveInstall.get()); clear(); }); fireEvent.change(await screen.findByPlaceholderText("File path or URL", { exact: false, }), { target: { value: "https://test.extensionurl/package.tgz", }, }); fireEvent.click(await screen.findByText("Install")); expect((await screen.findByText("Install")).closest("button")).toBeDisabled(); resolveInstall.set(true); }); it("displays spinner while extensions are loading", () => { extensionDiscovery.isLoaded = false; const { container } = render(); expect(container.querySelector(".Spinner")).toBeInTheDocument(); }); it("does not display the spinner while extensions are not loading", async () => { extensionDiscovery.isLoaded = true; const { container } = render(); expect(container.querySelector(".Spinner")).not.toBeInTheDocument(); }); });