Replace jest.mock's with overriding dependencies (#6014)

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne Savolainen authored and GitHub committed 2022-08-23 13:04:00 +03:00
1 parent 03d2389f01
commit 81f7bd3c2c
50 files changed
+788 -605

No files matched your search

@@ -10,8 +10,10 @@ import extensionLoaderInjectable from "../extension-loader/extension-loader.inje
import { runInAction } from "mobx";
import updateExtensionsStateInjectable from "../extension-loader/update-extensions-state/update-extensions-state.injectable";
import mockFs from "mock-fs";
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
import { delay } from "../../renderer/utils";
import { getDiForUnitTesting } from "../../renderer/getDiForUnitTesting";
import ipcRendererInjectable from "../../renderer/utils/channel/ipc-renderer.injectable";
import type { IpcRenderer } from "electron";
console = new Console(stdout, stderr);
@@ -19,10 +21,14 @@ const manifestPath = "manifest/path";
const manifestPath2 = "manifest/path2";
const manifestPath3 = "manifest/path3";
jest.mock(
"electron",
() => ({
ipcRenderer: {
describe("ExtensionLoader", () => {
let extensionLoader: ExtensionLoader;
let updateExtensionStateMock: jest.Mock;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(ipcRendererInjectable, () => ({
invoke: jest.fn(async (channel: string) => {
if (channel === "extension-loader:main:state") {
return [
@@ -59,59 +65,46 @@ jest.mock(
return [];
}),
on: jest.fn(
(channel: string, listener: (event: any, ...args: any[]) => void) => {
if (channel === "extension-loader:main:state") {
// First initialize with extensions 1 and 2
// and then broadcast event to remove extension 2 and add extension number 3
setTimeout(() => {
listener({}, [
[
on: (channel: string, listener: (event: any, ...args: any[]) => void) => {
if (channel === "extension-loader:main:state") {
// First initialize with extensions 1 and 2
// and then broadcast event to remove extension 2 and add extension number 3
setTimeout(() => {
listener({}, [
[
manifestPath,
{
manifest: {
name: "TestExtension",
version: "1.0.0",
},
id: manifestPath,
absolutePath: "/test/1",
manifestPath,
{
manifest: {
name: "TestExtension",
version: "1.0.0",
},
id: manifestPath,
absolutePath: "/test/1",
manifestPath,
isBundled: false,
isEnabled: true,
isBundled: false,
isEnabled: true,
},
],
[
manifestPath3,
{
manifest: {
name: "TestExtension3",
version: "3.0.0",
},
],
[
manifestPath3,
{
manifest: {
name: "TestExtension3",
version: "3.0.0",
},
id: manifestPath3,
absolutePath: "/test/3",
manifestPath: manifestPath3,
isBundled: false,
isEnabled: true,
},
],
]);
}, 10);
}
},
),
},
}),
{
virtual: true,
},
);
describe("ExtensionLoader", () => {
let extensionLoader: ExtensionLoader;
let updateExtensionStateMock: jest.Mock;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
id: manifestPath3,
absolutePath: "/test/3",
manifestPath: manifestPath3,
isBundled: false,
isEnabled: true,
},
],
]);
}, 10);
}
},
}) as unknown as IpcRenderer);
mockFs();
@@ -13,6 +13,10 @@ import installExtensionInjectable from "../extension-installer/install-extension
import extensionPackageRootDirectoryInjectable from "../extension-installer/extension-package-root-directory/extension-package-root-directory.injectable";
import installExtensionsInjectable from "../extension-installer/install-extensions/install-extensions.injectable";
import staticFilesDirectoryInjectable from "../../common/vars/static-files-directory.injectable";
import readJsonFileInjectable from "../../common/fs/read-json-file.injectable";
import loggerInjectable from "../../common/logger.injectable";
import pathExistsInjectable from "../../common/fs/path-exists.injectable";
import watchInjectable from "../../common/fs/watch/watch.injectable";
const extensionDiscoveryInjectable = getInjectable({
id: "extension-discovery",
@@ -40,6 +44,10 @@ const extensionDiscoveryInjectable = getInjectable({
),
staticFilesDirectory: di.inject(staticFilesDirectoryInjectable),
readJsonFile: di.inject(readJsonFileInjectable),
pathExists: di.inject(pathExistsInjectable),
watch: di.inject(watchInjectable),
logger: di.inject(loggerInjectable),
}),
});
@@ -4,53 +4,29 @@
*/
import type { FSWatcher } from "chokidar";
import { watch } from "chokidar";
import path from "path";
import os from "os";
import { Console } from "console";
import * as fse from "fs-extra";
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
import extensionDiscoveryInjectable from "../extension-discovery/extension-discovery.injectable";
import type { ExtensionDiscovery } from "../extension-discovery/extension-discovery";
import installExtensionInjectable
from "../extension-installer/install-extension/install-extension.injectable";
import directoryForUserDataInjectable
from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import installExtensionInjectable from "../extension-installer/install-extension/install-extension.injectable";
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import mockFs from "mock-fs";
import { delay } from "../../renderer/utils";
import { observable, when } from "mobx";
import appVersionInjectable from "../../common/vars/app-version.injectable";
jest.setTimeout(60_000);
jest.mock("../../common/ipc");
jest.mock("chokidar", () => ({
watch: jest.fn(),
}));
jest.mock("fs-extra");
jest.mock("electron", () => ({
app: {
getVersion: () => "99.99.99",
getName: () => "lens",
setName: jest.fn(),
setPath: jest.fn(),
getPath: () => "tmp",
getLocale: () => "en",
setLoginItemSettings: jest.fn(),
},
ipcMain: {
on: jest.fn(),
handle: jest.fn(),
},
}));
import readJsonFileInjectable from "../../common/fs/read-json-file.injectable";
import pathExistsInjectable from "../../common/fs/path-exists.injectable";
import watchInjectable from "../../common/fs/watch/watch.injectable";
console = new Console(process.stdout, process.stderr); // fix mockFS
const mockedWatch = watch as jest.MockedFunction<typeof watch>;
const mockedFse = fse as jest.Mocked<typeof fse>;
describe("ExtensionDiscovery", () => {
let extensionDiscovery: ExtensionDiscovery;
let readJsonFileMock: jest.Mock;
let pathExistsMock: jest.Mock;
let watchMock: jest.Mock;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
@@ -59,6 +35,15 @@ describe("ExtensionDiscovery", () => {
di.override(installExtensionInjectable, () => () => Promise.resolve());
di.override(appVersionInjectable, () => "5.0.0");
readJsonFileMock = jest.fn();
di.override(readJsonFileInjectable, () => readJsonFileMock);
pathExistsMock = jest.fn(() => Promise.resolve(true));
di.override(pathExistsInjectable, () => pathExistsMock);
watchMock = jest.fn();
di.override(watchInjectable, () => watchMock);
mockFs();
extensionDiscovery = di.inject(extensionDiscoveryInjectable);
@@ -72,7 +57,7 @@ describe("ExtensionDiscovery", () => {
const letTestFinish = observable.box(false);
let addHandler!: (filePath: string) => void;
mockedFse.readJson.mockImplementation((p) => {
readJsonFileMock.mockImplementation((p) => {
expect(p).toBe(path.join(os.homedir(), ".k8slens/extensions/my-extension/package.json"));
return {
@@ -84,8 +69,6 @@ describe("ExtensionDiscovery", () => {
};
});
mockedFse.pathExists.mockImplementation(() => true);
const mockWatchInstance = {
on: jest.fn((event: string, handler: typeof addHandler) => {
if (event === "add") {
@@ -96,7 +79,7 @@ describe("ExtensionDiscovery", () => {
}),
} as unknown as FSWatcher;
mockedWatch.mockImplementationOnce(() => mockWatchInstance);
watchMock.mockImplementationOnce(() => mockWatchInstance);
// Need to force isLoaded to be true so that the file watching is started
extensionDiscovery.isLoaded = true;
@@ -139,7 +122,7 @@ describe("ExtensionDiscovery", () => {
}),
} as unknown as FSWatcher;
mockedWatch.mockImplementationOnce(() => mockWatchInstance);
watchMock.mockImplementationOnce(() => mockWatchInstance);
// Need to force isLoaded to be true so that the file watching is started
extensionDiscovery.isLoaded = true;
@@ -3,7 +3,6 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { watch } from "chokidar";
import { ipcRenderer } from "electron";
import { EventEmitter } from "events";
import fse from "fs-extra";
@@ -12,7 +11,6 @@ import os from "os";
import path from "path";
import { broadcastMessage, ipcMainHandle, ipcRendererOn } from "../../common/ipc";
import { isErrnoException, toJS } from "../../common/utils";
import logger from "../../main/logger";
import type { ExtensionsStore } from "../extensions-store/extensions-store";
import type { ExtensionLoader } from "../extension-loader";
import type { LensExtensionId, LensExtensionManifest } from "../lens-extension";
@@ -21,6 +19,10 @@ import type { ExtensionInstallationStateStore } from "../extension-installation-
import type { PackageJson } from "type-fest";
import { extensionDiscoveryStateChannel } from "../../common/ipc/extension-handling";
import { requestInitialExtensionDiscovery } from "../../renderer/ipc";
import type { ReadJson } from "../../common/fs/read-json-file.injectable";
import type { Logger } from "../../common/logger";
import type { PathExists } from "../../common/fs/path-exists.injectable";
import type { Watch } from "../../common/fs/watch/watch.injectable";
interface Dependencies {
extensionLoader: ExtensionLoader;
@@ -35,6 +37,10 @@ interface Dependencies {
installExtensions: (packageJsonPath: string, packagesJson: PackageJson) => Promise<void>;
extensionPackageRootDirectory: string;
staticFilesDirectory: string;
readJsonFile: ReadJson;
pathExists: PathExists;
watch: Watch;
logger: Logger;
}
export interface InstalledExtension {
@@ -155,13 +161,12 @@ export class ExtensionDiscovery {
* Dependencies are installed automatically after an extension folder is copied.
*/
async watchExtensions(): Promise<void> {
logger.info(`${logModule} watching extension add/remove in ${this.localFolderPath}`);
this.dependencies.logger.info(`${logModule} watching extension add/remove in ${this.localFolderPath}`);
// Wait until .load() has been called and has been resolved
await this.whenLoaded;
// chokidar works better than fs.watch
watch(this.localFolderPath, {
this.dependencies.watch(this.localFolderPath, {
// For adding and removing symlinks to work, the depth has to be 1.
depth: 1,
ignoreInitial: true,
@@ -206,11 +211,11 @@ export class ExtensionDiscovery {
await this.dependencies.installExtension(extension.absolutePath);
this.extensions.set(extension.id, extension);
logger.info(`${logModule} Added extension ${extension.manifest.name}`);
this.dependencies.logger.info(`${logModule} Added extension ${extension.manifest.name}`);
this.events.emit("add", extension);
}
} catch (error) {
logger.error(`${logModule}: failed to add extension: ${error}`, { error });
this.dependencies.logger.error(`${logModule}: failed to add extension: ${error}`, { error });
} finally {
this.dependencies.extensionInstallationStateStore.clearInstallingFromMain(manifestPath);
}
@@ -247,13 +252,13 @@ export class ExtensionDiscovery {
const lensExtensionId = extension.manifestPath;
this.extensions.delete(extension.id);
logger.info(`${logModule} removed extension ${extensionName}`);
this.dependencies.logger.info(`${logModule} removed extension ${extensionName}`);
this.events.emit("remove", lensExtensionId);
return;
}
logger.warn(`${logModule} extension ${extensionFolderName} not found, can't remove`);
this.dependencies.logger.warn(`${logModule} extension ${extensionFolderName} not found, can't remove`);
};
/**
@@ -275,12 +280,12 @@ export class ExtensionDiscovery {
const extension = this.extensions.get(extensionId) ?? this.dependencies.extensionLoader.getExtension(extensionId);
if (!extension) {
return void logger.warn(`${logModule} could not uninstall extension, not found`, { id: extensionId });
return void this.dependencies.logger.warn(`${logModule} could not uninstall extension, not found`, { id: extensionId });
}
const { manifest, absolutePath } = extension;
logger.info(`${logModule} Uninstalling ${manifest.name}`);
this.dependencies.logger.info(`${logModule} Uninstalling ${manifest.name}`);
await this.removeSymlinkByPackageName(manifest.name);
@@ -296,7 +301,7 @@ export class ExtensionDiscovery {
this.loadStarted = true;
logger.info(
this.dependencies.logger.info(
`${logModule} loading extensions from ${this.dependencies.extensionPackageRootDirectory}`,
);
@@ -358,12 +363,12 @@ export class ExtensionDiscovery {
*/
protected async getByManifest(manifestPath: string, { isBundled = false } = {}): Promise<InstalledExtension | null> {
try {
const manifest = await fse.readJson(manifestPath) as LensExtensionManifest;
const manifest = await this.dependencies.readJsonFile(manifestPath) as unknown as LensExtensionManifest;
const id = this.getInstalledManifestPath(manifest.name);
const isEnabled = this.dependencies.extensionsStore.isEnabled({ id, isBundled });
const extensionDir = path.dirname(manifestPath);
const npmPackage = path.join(extensionDir, `${manifest.name}-${manifest.version}.tgz`);
const absolutePath = (isProduction && await fse.pathExists(npmPackage)) ? npmPackage : extensionDir;
const absolutePath = (isProduction && await this.dependencies.pathExists(npmPackage)) ? npmPackage : extensionDir;
const isCompatible = (isBundled && this.dependencies.isCompatibleBundledExtension(manifest)) || this.dependencies.isCompatibleExtension(manifest);
return {
@@ -378,9 +383,9 @@ export class ExtensionDiscovery {
} catch (error) {
if (isErrnoException(error) && error.code === "ENOTDIR") {
// ignore this error, probably from .DS_Store file
logger.debug(`${logModule}: failed to load extension manifest through a not-dir-like at ${manifestPath}`);
this.dependencies.logger.debug(`${logModule}: failed to load extension manifest through a not-dir-like at ${manifestPath}`);
} else {
logger.error(`${logModule}: can't load extension manifest at ${manifestPath}: ${error}`);
this.dependencies.logger.error(`${logModule}: can't load extension manifest at ${manifestPath}: ${error}`);
}
return null;
@@ -395,7 +400,7 @@ export class ExtensionDiscovery {
const userExtensions = await this.loadFromFolder(this.localFolderPath, bundledExtensions.map((extension) => extension.manifest.name));
for (const extension of userExtensions) {
if ((await fse.pathExists(extension.manifestPath)) === false) {
if (!(await this.dependencies.pathExists(extension.manifestPath))) {
try {
await this.dependencies.installExtension(extension.absolutePath);
} catch (error) {
@@ -404,7 +409,7 @@ export class ExtensionDiscovery {
: String(error || "unknown error");
const { name, version } = extension.manifest;
logger.error(`${logModule}: failed to install user extension ${name}@${version}: ${message}`);
this.dependencies.logger.error(`${logModule}: failed to install user extension ${name}@${version}: ${message}`);
}
}
}
@@ -438,7 +443,7 @@ export class ExtensionDiscovery {
extensions.push(extension);
}
}
logger.debug(`${logModule}: ${extensions.length} extensions loaded`, { folderPath, extensions });
this.dependencies.logger.debug(`${logModule}: ${extensions.length} extensions loaded`, { folderPath, extensions });
return extensions;
}
@@ -473,7 +478,7 @@ export class ExtensionDiscovery {
}
}
logger.debug(`${logModule}: ${extensions.length} extensions loaded`, { folderPath, extensions });
this.dependencies.logger.debug(`${logModule}: ${extensions.length} extensions loaded`, { folderPath, extensions });
return extensions;
}
@@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { ipcRenderer } from "electron";
import { ipcMain, ipcRenderer } from "electron";
import { isEqual } from "lodash";
import type { ObservableMap } from "mobx";
import { action, computed, makeObservable, observable, observe, reaction, when } from "mobx";
@@ -127,10 +127,10 @@ export class ExtensionLoader {
@action
async init() {
if (ipcRenderer) {
await this.initRenderer();
} else {
if (ipcMain) {
await this.initMain();
} else {
await this.initRenderer();
}
await Promise.all([this.whenLoaded]);