mirror of
https://github.com/penpot/penpot.git
synced 2026-10-30 14:30:37 +00:00
Unloading or replacing a plugin while its code was still being fetched could leave a late instance running, or block reopening a plugin that closed during startup. Each load now carries an AbortController. Unloading, a newer load of the same plugin or logout aborts it, and the plugin manager closes itself on abort, also while it fetches its code. createPlugin returns nothing for a plugin closed before its sandbox exists, and failed loads clear their pending state so the plugin can be loaded again.
130 lines
3.7 KiB
TypeScript
130 lines
3.7 KiB
TypeScript
import { describe, it, vi, expect, beforeEach, afterEach } from 'vitest';
|
|
import { createPlugin } from './create-plugin';
|
|
import { createPluginManager } from './plugin-manager.js';
|
|
import { createSandbox } from './create-sandbox.js';
|
|
import type { Context } from '@penpot/plugin-types';
|
|
import type { Manifest } from './models/manifest.model.js';
|
|
|
|
vi.mock('./plugin-manager.js', () => ({
|
|
createPluginManager: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('./create-sandbox.js', () => ({
|
|
createSandbox: vi.fn(),
|
|
markPluginError: vi.fn(),
|
|
}));
|
|
|
|
describe('createPlugin', () => {
|
|
let mockContext: Context;
|
|
let manifest: Manifest;
|
|
let onCloseCallback: () => void;
|
|
let mockPluginManager: Awaited<ReturnType<typeof createPluginManager>>;
|
|
let mockSandbox: ReturnType<typeof createSandbox>;
|
|
|
|
beforeEach(() => {
|
|
manifest = {
|
|
pluginId: 'test-plugin',
|
|
name: 'Test Plugin',
|
|
host: 'https://example.com',
|
|
code: '',
|
|
permissions: [
|
|
'content:read',
|
|
'content:write',
|
|
'library:read',
|
|
'library:write',
|
|
'user:read',
|
|
'comment:read',
|
|
'comment:write',
|
|
'allow:downloads',
|
|
'allow:localstorage',
|
|
],
|
|
};
|
|
|
|
mockPluginManager = {
|
|
close: vi.fn(),
|
|
openModal: vi.fn(),
|
|
getModal: vi.fn(),
|
|
registerListener: vi.fn(),
|
|
registerMessageCallback: vi.fn(),
|
|
sendMessage: vi.fn(),
|
|
destroyListener: vi.fn(),
|
|
context: mockContext,
|
|
manifest,
|
|
timeouts: new Set(),
|
|
code: 'console.log("Plugin running");',
|
|
} as unknown as Awaited<ReturnType<typeof createPluginManager>>;
|
|
|
|
mockSandbox = {
|
|
evaluate: vi.fn(),
|
|
cleanGlobalThis: vi.fn(),
|
|
compartment: {},
|
|
} as unknown as ReturnType<typeof createSandbox>;
|
|
|
|
mockContext = {
|
|
addListener: vi.fn(),
|
|
removeListener: vi.fn(),
|
|
} as unknown as Context;
|
|
|
|
onCloseCallback = vi.fn();
|
|
|
|
vi.mocked(createPluginManager).mockResolvedValue(mockPluginManager);
|
|
vi.mocked(createSandbox).mockReturnValue(mockSandbox);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('should create the plugin manager and sandbox, then evaluate the plugin code', async () => {
|
|
const result = await createPlugin(mockContext, manifest, onCloseCallback);
|
|
|
|
expect(createPluginManager).toHaveBeenCalledWith(
|
|
mockContext,
|
|
manifest,
|
|
expect.any(Function),
|
|
expect.any(Function),
|
|
undefined,
|
|
);
|
|
expect(createSandbox).toHaveBeenCalledWith(mockPluginManager, undefined);
|
|
expect(mockSandbox.evaluate).toHaveBeenCalled();
|
|
expect(result).toEqual({
|
|
plugin: mockPluginManager,
|
|
compartment: mockSandbox,
|
|
manifest,
|
|
});
|
|
});
|
|
|
|
it('should clean globalThis and call onCloseCallback when plugin is closed', async () => {
|
|
await createPlugin(mockContext, manifest, onCloseCallback);
|
|
|
|
const onClose = vi.mocked(createPluginManager).mock.calls[0][2];
|
|
onClose();
|
|
|
|
expect(mockSandbox.cleanGlobalThis).toHaveBeenCalled();
|
|
expect(onCloseCallback).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should re-evaluate the plugin code when the modal is reloaded', async () => {
|
|
await createPlugin(mockContext, manifest, onCloseCallback);
|
|
|
|
const onReloadModal = vi.mocked(createPluginManager).mock.calls[0][3];
|
|
onReloadModal('');
|
|
|
|
expect(mockSandbox.evaluate).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should call plugin.close when there is an exception during sandbox evaluation', async () => {
|
|
vi.mocked(mockSandbox.evaluate).mockImplementation(() => {
|
|
throw new Error('Evaluation error');
|
|
});
|
|
|
|
try {
|
|
await createPlugin(mockContext, manifest, onCloseCallback);
|
|
} catch (err) {
|
|
expect.assert(err);
|
|
}
|
|
|
|
expect(mockPluginManager.close).toHaveBeenCalled();
|
|
});
|
|
});
|