mirror of
https://github.com/penpot/penpot-plugins.git
synced 2025-01-04 13:50:13 -05:00
fix(plugins-runtime): prevent plugin execution after close
This commit is contained in:
parent
74b11f0587
commit
b65492ae29
2 changed files with 44 additions and 37 deletions
|
@ -1,10 +1,9 @@
|
|||
import { describe, it, vi, expect, beforeEach, afterEach, Mock } from 'vitest';
|
||||
import { describe, it, vi, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { loadPlugin, setContextBuilder } from './load-plugin.js';
|
||||
import { loadManifestCode } from './parse-manifest.js';
|
||||
import { createApi, themeChange } from './api/index.js';
|
||||
import type { PenpotContext, PenpotTheme } from '@penpot/plugin-types';
|
||||
import type { Manifest } from './models/manifest.model.js';
|
||||
import { ses } from './ses.js';
|
||||
|
||||
vi.mock('./parse-manifest.js', () => ({
|
||||
loadManifestCode: vi.fn(),
|
||||
|
@ -15,17 +14,25 @@ vi.mock('./api/index.js', () => ({
|
|||
themeChange: vi.fn(),
|
||||
}));
|
||||
|
||||
const evaluateMock = vi.fn();
|
||||
|
||||
vi.mock('./ses.js', () => ({
|
||||
ses: {
|
||||
hardenIntrinsics: vi.fn().mockReturnValue(null),
|
||||
createCompartment: vi.fn().mockReturnValue({
|
||||
evaluate: vi.fn(),
|
||||
createCompartment: vi.fn().mockImplementation((publicApi) => {
|
||||
return {
|
||||
evaluate: evaluateMock,
|
||||
globalThis: publicApi,
|
||||
};
|
||||
}),
|
||||
harden: vi.fn().mockImplementation((obj) => obj),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('loadPlugin', () => {
|
||||
vi.spyOn(globalThis, 'clearTimeout');
|
||||
vi.spyOn(globalThis.console, 'error').mockImplementation(() => {});
|
||||
|
||||
let mockContext: PenpotContext;
|
||||
let manifest: Manifest = {
|
||||
pluginId: 'test-plugin',
|
||||
|
@ -54,8 +61,8 @@ describe('loadPlugin', () => {
|
|||
closePlugin: vi.fn(),
|
||||
} as unknown as ReturnType<typeof createApi>;
|
||||
|
||||
(createApi as Mock).mockReturnValue(mockApi);
|
||||
(loadManifestCode as Mock).mockResolvedValue(
|
||||
vi.mocked(createApi).mockReturnValue(mockApi);
|
||||
vi.mocked(loadManifestCode).mockResolvedValue(
|
||||
'console.log("Plugin loaded");'
|
||||
);
|
||||
setContextBuilder(() => mockContext);
|
||||
|
@ -96,20 +103,6 @@ describe('loadPlugin', () => {
|
|||
expect(mockApi.closePlugin).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should clear timeouts on plugin close', async () => {
|
||||
await loadPlugin(manifest);
|
||||
|
||||
const clearTimeoutSpy = vi.spyOn(globalThis, 'clearTimeout');
|
||||
const setTimeoutSpy = vi.spyOn(globalThis, 'setTimeout');
|
||||
|
||||
const timeoutCallback = vi.fn();
|
||||
const timeoutId = setTimeout(timeoutCallback, 1000);
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
expect(clearTimeoutSpy).toHaveBeenCalledWith(timeoutId);
|
||||
expect(setTimeoutSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should remove finish event listener on plugin finish', async () => {
|
||||
await loadPlugin(manifest);
|
||||
|
||||
|
@ -123,16 +116,6 @@ describe('loadPlugin', () => {
|
|||
});
|
||||
|
||||
it('shoud clean setTimeout when plugin is closed', async () => {
|
||||
vi.spyOn(globalThis, 'clearTimeout');
|
||||
|
||||
let closedCallback = () => {};
|
||||
|
||||
(createApi as Mock).mockImplementation((context, manifest, closed) => {
|
||||
closedCallback = closed;
|
||||
|
||||
return mockApi;
|
||||
});
|
||||
|
||||
const plugin = await loadPlugin(manifest);
|
||||
|
||||
if (!plugin) {
|
||||
|
@ -144,6 +127,7 @@ describe('loadPlugin', () => {
|
|||
|
||||
expect(plugin.timeouts.size).toBe(2);
|
||||
|
||||
const closedCallback = vi.mocked(createApi).mock.calls[0][2];
|
||||
closedCallback();
|
||||
|
||||
expect(plugin.timeouts.size).toBe(0);
|
||||
|
@ -151,16 +135,32 @@ describe('loadPlugin', () => {
|
|||
});
|
||||
|
||||
it('should close plugin on evaluation error', async () => {
|
||||
ses.createCompartment = vi.fn().mockImplementation(() => {
|
||||
return {
|
||||
evaluate: vi.fn().mockImplementation(() => {
|
||||
throw new Error('Error in plugin');
|
||||
}),
|
||||
};
|
||||
evaluateMock.mockImplementationOnce(() => {
|
||||
throw new Error('Evaluation error');
|
||||
});
|
||||
|
||||
await loadPlugin(manifest);
|
||||
|
||||
expect(mockApi.closePlugin).toHaveBeenCalled();
|
||||
expect(console.error).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should prevent using the api after closing the plugin', async () => {
|
||||
const plugin = await loadPlugin(manifest);
|
||||
|
||||
if (!plugin) {
|
||||
throw new Error('Plugin not loaded');
|
||||
}
|
||||
|
||||
expect(
|
||||
Object.keys(plugin.compartment.globalThis).filter((it) => !!it).length
|
||||
).toBeGreaterThan(0);
|
||||
|
||||
const closedCallback = vi.mocked(createApi).mock.calls[0][2];
|
||||
closedCallback();
|
||||
|
||||
expect(
|
||||
Object.keys(plugin.compartment.globalThis).filter((it) => !!it).length
|
||||
).toBe(0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -44,8 +44,15 @@ export const loadPlugin = async function (manifest: Manifest) {
|
|||
}
|
||||
|
||||
const pluginApi = createApi(context, manifest, () => {
|
||||
createdApis = createdApis.filter((api) => api !== pluginApi);
|
||||
|
||||
timeouts.forEach(clearTimeout);
|
||||
timeouts.clear();
|
||||
|
||||
// Remove all public API from globalThis
|
||||
Object.keys(publicPluginApi).forEach((key) => {
|
||||
delete c.globalThis[key];
|
||||
});
|
||||
});
|
||||
|
||||
createdApis.push(pluginApi);
|
||||
|
@ -53,7 +60,7 @@ export const loadPlugin = async function (manifest: Manifest) {
|
|||
const timeouts = new Set<ReturnType<typeof setTimeout>>();
|
||||
|
||||
const publicPluginApi = {
|
||||
penpot: ses.harden(pluginApi),
|
||||
penpot: ses.harden(pluginApi) as typeof pluginApi,
|
||||
fetch: ses.harden((...args: Parameters<typeof fetch>) => {
|
||||
const requestArgs: RequestInit = {
|
||||
...args[1],
|
||||
|
|
Loading…
Reference in a new issue