1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-07-16 00:14:34 +02:00

Chore: Apply changes from mobile plugins to lib/ and app-desktop/ (#10079)

This commit is contained in:
Henry Heino
2024-03-09 03:03:57 -08:00
committed by GitHub
parent 91004f5714
commit 25cd5affca
37 changed files with 418 additions and 205 deletions

View File

@ -0,0 +1,23 @@
import { PluginSettings } from '../../../../services/plugins/PluginService';
import { PluginManifest } from '../../../../services/plugins/utils/types';
export interface PluginItem {
manifest: PluginManifest;
enabled: boolean;
deleted: boolean;
devMode: boolean;
builtIn: boolean;
hasBeenUpdated: boolean;
}
export interface ItemEvent {
item: PluginItem;
}
export interface OnPluginSettingChangeEvent {
value: PluginSettings;
}
export type OnPluginSettingChangeHandler = (event: OnPluginSettingChangeEvent)=> void;

View File

@ -0,0 +1,40 @@
import { _ } from '../../../../locale';
import PluginService, { PluginSettings, defaultPluginSetting } from '../../../../services/plugins/PluginService';
import shim from '../../../../shim';
import produce from 'immer';
import { ItemEvent, OnPluginSettingChangeHandler } from './types';
const useOnDeleteHandler = (
pluginSettings: PluginSettings,
onSettingsChange: OnPluginSettingChangeHandler,
deleteNow: boolean,
) => {
const React = shim.react();
return React.useCallback(async (event: ItemEvent) => {
const item = event.item;
const confirmed = await shim.showConfirmationDialog(_('Delete plugin "%s"?', item.manifest.name));
if (!confirmed) return;
let newSettings = produce(pluginSettings, (draft: PluginSettings) => {
if (!draft[item.manifest.id]) draft[item.manifest.id] = defaultPluginSetting();
draft[item.manifest.id].deleted = true;
});
if (deleteNow) {
const pluginService = PluginService.instance();
// We first unload the plugin. This is done here rather than in pluginService.uninstallPlugins
// because unloadPlugin may not work on desktop.
const plugin = pluginService.plugins[item.manifest.id];
if (plugin) {
await pluginService.unloadPlugin(item.manifest.id);
}
newSettings = await pluginService.uninstallPlugins(newSettings);
}
onSettingsChange({ value: newSettings });
}, [pluginSettings, onSettingsChange, deleteNow]);
};
export default useOnDeleteHandler;

View File

@ -0,0 +1,91 @@
import useOnInstallHandler from './useOnInstallHandler';
import { renderHook } from '@testing-library/react-hooks';
import PluginService, { defaultPluginSetting } from '../../../../services/plugins/PluginService';
import { ItemEvent } from './types';
jest.mock('../../../../services/plugins/PluginService');
const pluginServiceInstance = {
updatePluginFromRepo: jest.fn(),
installPluginFromRepo: jest.fn(),
};
const pluginId = 'test.plugin';
const setInstallingPluginIds = jest.fn();
const repoApi = jest.fn();
const onPluginSettingsChange = jest.fn();
const itemEvent = ({
item: { manifest: { id: pluginId } },
} as ItemEvent);
const callHook = (isUpdate: boolean, pluginEnabled = true, pluginInstalledViaGUI = true) => () => useOnInstallHandler(
setInstallingPluginIds,
{
[pluginId]: pluginInstalledViaGUI ? {
enabled: pluginEnabled,
deleted: false,
hasBeenUpdated: false,
} : undefined,
},
repoApi,
onPluginSettingsChange,
isUpdate,
);
describe('useOnInstallHandler', () => {
beforeAll(() => {
(PluginService.instance as jest.Mock).mockReturnValue(pluginServiceInstance);
(defaultPluginSetting as jest.Mock).mockImplementation(
jest.requireActual('../../../../services/plugins/PluginService').defaultPluginSetting,
);
});
beforeEach(() => {
jest.clearAllMocks();
});
test('should report that the plugin is being updated', async () => {
const { result: { current: onUpdate } } = renderHook(callHook(true));
await onUpdate(itemEvent);
expect(setInstallingPluginIds).toHaveBeenCalledTimes(2);
expect(setInstallingPluginIds.mock.calls[0][0]({})).toMatchObject({ [pluginId]: true });
expect(setInstallingPluginIds.mock.calls[1][0]({})).toMatchObject({ [pluginId]: false });
});
test('should update the plugin when there is an update', async () => {
const { result: { current: onUpdate } } = renderHook(callHook(true));
await onUpdate(itemEvent);
expect(pluginServiceInstance.updatePluginFromRepo).toHaveBeenCalledWith(undefined, pluginId);
});
test('should install the plugin when it is not yet installed', async () => {
const { result: { current: onInstall } } = renderHook(callHook(false));
await onInstall(itemEvent);
expect(pluginServiceInstance.installPluginFromRepo).toHaveBeenCalledWith(undefined, pluginId);
});
test('should preserve the enabled flag when plugin is updated', async () => {
const { result: { current: onUpdate } } = renderHook(callHook(true, false));
await onUpdate(itemEvent);
const newSettings = onPluginSettingsChange.mock.calls[0][0].value;
expect(newSettings[pluginId].enabled).toBe(false);
});
test('should indicate it when plugin has been updated', async () => {
const { result: { current: onUpdate } } = renderHook(callHook(true));
await onUpdate(itemEvent);
const newSettings = onPluginSettingsChange.mock.calls[0][0].value;
expect(newSettings[pluginId].hasBeenUpdated).toBe(true);
});
test('should not fail when plugin was not installed through the GUI', async () => {
const { result: { current: onUpdate } } = renderHook(callHook(true, true, false));
await onUpdate(itemEvent);
});
});

View File

@ -0,0 +1,75 @@
import produce from 'immer';
import Logger from '@joplin/utils/Logger';
import { ItemEvent, OnPluginSettingChangeHandler } from './types';
import type * as React from 'react';
import shim from '../../../../shim';
import RepositoryApi from '../../../../services/plugins/RepositoryApi';
import PluginService, { PluginSettings, defaultPluginSetting } from '../../../../services/plugins/PluginService';
import { _ } from '../../../../locale';
const logger = Logger.create('useOnInstallHandler');
type GetRepoApiCallback = ()=> RepositoryApi;
const useOnInstallHandler = (
setInstallingPluginIds: React.Dispatch<React.SetStateAction<Record<string, boolean>>>,
pluginSettings: PluginSettings,
getRepoApi: GetRepoApiCallback|RepositoryApi,
onPluginSettingsChange: OnPluginSettingChangeHandler,
isUpdate: boolean,
) => {
const React = shim.react();
return React.useCallback(async (event: ItemEvent) => {
const pluginId = event.item.manifest.id;
setInstallingPluginIds((prev: any) => {
return {
...prev, [pluginId]: true,
};
});
let installError = null;
try {
const repoApi = typeof getRepoApi === 'function' ? getRepoApi() : getRepoApi;
if (isUpdate) {
await PluginService.instance().updatePluginFromRepo(repoApi, pluginId);
} else {
await PluginService.instance().installPluginFromRepo(repoApi, pluginId);
}
} catch (error) {
installError = error;
logger.error(error);
}
if (!installError) {
const newSettings = produce(pluginSettings, (draft: PluginSettings) => {
draft[pluginId] = defaultPluginSetting();
if (isUpdate) {
if (pluginSettings[pluginId]) {
draft[pluginId].enabled = pluginSettings[pluginId].enabled;
}
draft[pluginId].hasBeenUpdated = true;
}
});
onPluginSettingsChange({ value: newSettings });
}
setInstallingPluginIds((prev: any) => {
return {
...prev, [pluginId]: false,
};
});
if (installError) {
await shim.showMessageBox(
_('Could not install plugin: %s', installError.message),
{ buttons: [_('OK')] },
);
}
}, [getRepoApi, isUpdate, pluginSettings, onPluginSettingsChange, setInstallingPluginIds]);
};
export default useOnInstallHandler;