diff --git a/src/renderer/components/+extensions/__tests__/extensions.test.tsx b/src/renderer/components/+extensions/__tests__/extensions.test.tsx
index b5a036ad88..f02f157ffb 100644
--- a/src/renderer/components/+extensions/__tests__/extensions.test.tsx
+++ b/src/renderer/components/+extensions/__tests__/extensions.test.tsx
@@ -1,11 +1,22 @@
import '@testing-library/jest-dom/extend-expect';
-import { fireEvent, render, screen } from '@testing-library/react';
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
+import fse from "fs-extra";
import React from 'react';
import { extensionDiscovery } from "../../../../extensions/extension-discovery";
import { ConfirmDialog } from "../../confirm-dialog";
import { Notifications } from "../../notifications";
import { Extensions } from "../extensions";
+jest.mock("fs-extra");
+
+jest.mock("../../../../common/utils", () => ({
+ ...jest.requireActual("../../../../common/utils"),
+ downloadFile: jest.fn(() => ({
+ promise: Promise.resolve()
+ })),
+ extractTar: jest.fn(() => Promise.resolve())
+}));
+
jest.mock("../../../../extensions/extension-discovery", () => ({
...jest.requireActual("../../../../extensions/extension-discovery"),
extensionDiscovery: {
@@ -70,10 +81,30 @@ describe("Extensions", () => {
// Approve confirm dialog
fireEvent.click(screen.getByText("Yes"));
- setTimeout(() => {
+ waitFor(() => {
expect(screen.getByText("Disable").closest("button")).not.toBeDisabled();
expect(screen.getByText("Uninstall").closest("button")).not.toBeDisabled();
expect(Notifications.error).toHaveBeenCalledTimes(1);
- }, 100);
+ });
+ });
+
+ it("disables install button while installing", () => {
+ render(
Extension {displayName} successfully uninstalled!
); + this.extensionState.delete(id); }); - removedUninstalling.forEach(({ id }) => { + this.addedInstalling.forEach(({ id, displayName }) => { + Notifications.ok( +Extension {displayName} successfully installed!
+ ); this.extensionState.delete(id); + this.installPath = ""; }); }) ); @@ -91,6 +103,7 @@ export class Extensions extends React.Component { @computed get extensions() { const searchText = this.search.toLowerCase(); + return Array.from(extensionLoader.userExtensions.values()).filter(ext => { const { name, description } = ext.manifest; return [ @@ -123,6 +136,7 @@ export class Extensions extends React.Component { { name: "tarball", extensions: this.supportedFormats } ] }); + if (!canceled && filePaths.length) { this.requestInstall( filePaths.map(filePath => ({ @@ -137,6 +151,7 @@ export class Extensions extends React.Component { const { installPath } = this; if (!installPath) return; const fileName = path.basename(installPath); + try { // install via url // fixme: improve error messages for non-tar-file URLs @@ -172,13 +187,13 @@ export class Extensions extends React.Component { await Promise.all( requests .filter(req => !req.data && req.filePath) - .map(req => { - return fse.readFile(req.filePath).then(data => { - req.data = data; - preloadedRequests.push(req); + .map(request => { + return fse.readFile(request.filePath).then(data => { + request.data = data; + preloadedRequests.push(request); }).catch(error => { if (showError) { - Notifications.error(`Error while reading "${req.filePath}": ${String(error)}`); + Notifications.error(`Error while reading "${request.filePath}": ${String(error)}`); } }); }) @@ -198,11 +213,13 @@ export class Extensions extends React.Component { if (!tarFiles.includes(manifestLocation)) { throw new Error(`invalid extension bundle, ${manifestFilename} not found`); } + const manifest = await readFileFromTarExtension {extName} successfully installed!
- ); } catch (error) { Notifications.error( -Installing extension {extName} has failed: {error}
+Installing extension {displayName} has failed: {error}
); + // Remove install state on install failure + if (this.extensionState.get(extensionId)?.state === "installing") { + this.extensionState.delete(extensionId); + } } finally { // clean up fse.remove(unpackingTempFolder).catch(Function); @@ -340,7 +372,9 @@ export class Extensions extends React.Component {Uninstalling extension {displayName} has failed: {error?.message ?? ""}
); // Remove uninstall state on uninstall failure - this.extensionState.delete(extension.id); + if (this.extensionState.get(extension.id)?.state === "uninstalling") { + this.extensionState.delete(extension.id); + } } } @@ -394,9 +428,17 @@ export class Extensions extends React.Component { }); } + /** + * True if at least one extension is in installing state + */ + @computed get isInstalling() { + return [...this.extensionState.values()].some(extension => extension.state === "installing"); + } + render() { const topHeader =