diff --git a/webapp/channels/src/actions/websocket_actions.test.jsx b/webapp/channels/src/actions/websocket_actions.test.jsx index ace39595f77..9837ad85b97 100644 --- a/webapp/channels/src/actions/websocket_actions.test.jsx +++ b/webapp/channels/src/actions/websocket_actions.test.jsx @@ -957,14 +957,12 @@ describe('handleCloudSubscriptionChanged', () => { }); describe('handlePluginEnabled/handlePluginDisabled', () => { - const origLog = console.log; const origError = console.error; const origCreateElement = document.createElement; const origGetElementsByTagName = document.getElementsByTagName; const origWindowPlugins = window.plugins; afterEach(() => { - console.log = origLog; console.error = origError; document.createElement = origCreateElement; document.getElementsByTagName = origGetElementsByTagName; @@ -990,8 +988,7 @@ describe('handlePluginEnabled/handlePluginDisabled', () => { }; beforeEach(async () => { - console.log = jest.fn(); - console.error = jest.fn(); + console.error = jest.fn((...args) => origError(...args)); document.createElement = jest.fn(); document.getElementsByTagName = jest.fn(); @@ -1000,60 +997,36 @@ describe('handlePluginEnabled/handlePluginDisabled', () => { }]); }); - test('when a plugin is enabled', () => { + test('when a plugin is enabled', async () => { const manifest = { ...baseManifest, id: 'com.mattermost.demo-plugin', }; - const initialize = jest.fn(); - window.plugins = { - [manifest.id]: { - initialize, - }, - }; const mockScript = {}; document.createElement.mockReturnValue(mockScript); - expect(mockScript.onload).toBeUndefined(); handlePluginEnabled({data: {manifest}}); expect(document.createElement).toHaveBeenCalledWith('script'); expect(document.getElementsByTagName).toHaveBeenCalledTimes(1); expect(document.getElementsByTagName()[0].appendChild).toHaveBeenCalledTimes(1); - expect(mockScript.onload).toBeInstanceOf(Function); - // Pretend to be a browser, invoke onload - mockScript.onload(); - expect(initialize).toHaveBeenCalledWith(expect.anything(), store); - const registery = initialize.mock.calls[0][0]; - const mockComponent = 'mockRootComponent'; - registery.registerRootComponent(mockComponent); + expect(store.dispatch).toHaveBeenCalledTimes(1); let dispatchArg = store.dispatch.mock.calls[0][0]; expect(dispatchArg.type).toBe(ActionTypes.RECEIVED_WEBAPP_PLUGIN); expect(dispatchArg.data).toBe(manifest); - dispatchArg = store.dispatch.mock.calls[1][0]; - - expect(dispatchArg.type).toBe(ActionTypes.RECEIVED_PLUGIN_COMPONENT); - expect(dispatchArg.name).toBe('Root'); - expect(dispatchArg.data.component).toBe(mockComponent); - expect(dispatchArg.data.pluginId).toBe(manifest.id); + // Assert handlePluginEnabled is idempotent + handlePluginEnabled({data: {manifest}}); expect(store.dispatch).toHaveBeenCalledTimes(2); - // Assert handlePluginEnabled is idempotent - mockScript.onload = undefined; - handlePluginEnabled({data: {manifest}}); - expect(mockScript.onload).toBeUndefined(); - - dispatchArg = store.dispatch.mock.calls[2][0]; + dispatchArg = store.dispatch.mock.calls[1][0]; expect(dispatchArg.type).toBe(ActionTypes.RECEIVED_WEBAPP_PLUGIN); expect(dispatchArg.data).toBe(manifest); - expect(store.dispatch).toHaveBeenCalledTimes(3); - expect(console.error).toHaveBeenCalledTimes(0); }); @@ -1062,12 +1035,6 @@ describe('handlePluginEnabled/handlePluginDisabled', () => { ...baseManifest, id: 'com.mattermost.demo-2-plugin', }; - const initialize = jest.fn(); - window.plugins = { - [manifest.id]: { - initialize, - }, - }; const manifestv2 = { ...manifest, @@ -1080,69 +1047,39 @@ describe('handlePluginEnabled/handlePluginDisabled', () => { const mockScript = {}; document.createElement.mockReturnValue(mockScript); - expect(mockScript.onload).toBeUndefined(); handlePluginEnabled({data: {manifest}}); expect(document.createElement).toHaveBeenCalledWith('script'); expect(document.getElementsByTagName).toHaveBeenCalledTimes(1); expect(document.getElementsByTagName()[0].appendChild).toHaveBeenCalledTimes(1); - expect(mockScript.onload).toBeInstanceOf(Function); - - // Pretend to be a browser, invoke onload - mockScript.onload(); - expect(initialize).toHaveBeenCalledWith(expect.anything(), store); - const registry = initialize.mock.calls[0][0]; - const mockComponent = 'mockRootComponent'; - registry.registerRootComponent(mockComponent); let dispatchArg = store.dispatch.mock.calls[0][0]; expect(dispatchArg.type).toBe(ActionTypes.RECEIVED_WEBAPP_PLUGIN); expect(dispatchArg.data).toBe(manifest); - dispatchArg = store.dispatch.mock.calls[1][0]; - expect(dispatchArg.type).toBe(ActionTypes.RECEIVED_PLUGIN_COMPONENT); - expect(dispatchArg.name).toBe('Root'); - expect(dispatchArg.data.component).toBe(mockComponent); - expect(dispatchArg.data.pluginId).toBe(manifest.id); - // Upgrade plugin - mockScript.onload = undefined; handlePluginEnabled({data: {manifest: manifestv2}}); // Assert upgrade is idempotent handlePluginEnabled({data: {manifest: manifestv2}}); - expect(mockScript.onload).toBeInstanceOf(Function); expect(document.createElement).toHaveBeenCalledTimes(2); - mockScript.onload(); - expect(initialize).toHaveBeenCalledWith(expect.anything(), store); - expect(initialize).toHaveBeenCalledTimes(2); - const registry2 = initialize.mock.calls[0][0]; - const mockComponent2 = 'mockRootComponent2'; - registry2.registerRootComponent(mockComponent2); + dispatchArg = store.dispatch.mock.calls[1][0]; + expect(dispatchArg.type).toBe(ActionTypes.RECEIVED_WEBAPP_PLUGIN); + expect(dispatchArg.data).toBe(manifestv2); - dispatchArg = store.dispatch.mock.calls[2][0]; + expect(store.dispatch).toHaveBeenCalledTimes(4); + const dispatchRemovedArg = store.dispatch.mock.calls[2][0]; + expect(typeof dispatchRemovedArg).toBe('function'); + dispatchRemovedArg(store.dispatch); + + dispatchArg = store.dispatch.mock.calls[3][0]; expect(dispatchArg.type).toBe(ActionTypes.RECEIVED_WEBAPP_PLUGIN); expect(dispatchArg.data).toBe(manifestv2); expect(store.dispatch).toHaveBeenCalledTimes(6); - const dispatchRemovedArg = store.dispatch.mock.calls[3][0]; - expect(typeof dispatchRemovedArg).toBe('function'); - dispatchRemovedArg(store.dispatch); - - dispatchArg = store.dispatch.mock.calls[4][0]; - expect(dispatchArg.type).toBe(ActionTypes.RECEIVED_WEBAPP_PLUGIN); - expect(dispatchArg.data).toBe(manifestv2); - - const dispatchReceivedArg2 = store.dispatch.mock.calls[5][0]; - expect(dispatchReceivedArg2.type).toBe(ActionTypes.RECEIVED_PLUGIN_COMPONENT); - expect(dispatchReceivedArg2.name).toBe('Root'); - expect(dispatchReceivedArg2.data.component).toBe(mockComponent2); - expect(dispatchReceivedArg2.data.pluginId).toBe(manifest.id); - - expect(store.dispatch).toHaveBeenCalledTimes(8); - const dispatchReceivedArg4 = store.dispatch.mock.calls[7][0]; + const dispatchReceivedArg4 = store.dispatch.mock.calls[5][0]; expect(dispatchReceivedArg4.type).toBe(ActionTypes.REMOVED_WEBAPP_PLUGIN); expect(dispatchReceivedArg4.data).toBe(manifestv2); @@ -1170,7 +1107,6 @@ describe('handlePluginEnabled/handlePluginDisabled', () => { }; beforeEach(async () => { - console.log = jest.fn(); console.error = jest.fn(); document.createElement = jest.fn(); diff --git a/webapp/channels/src/plugins/index.ts b/webapp/channels/src/plugins/index.ts index 7b4b16f5a62..6eeb485c33a 100644 --- a/webapp/channels/src/plugins/index.ts +++ b/webapp/channels/src/plugins/index.ts @@ -67,6 +67,8 @@ function registerPlugin(id: string, plugin: Plugin): void { } window.plugins[id] = plugin; + + onPluginRegistered(id); } window.registerPlugin = registerPlugin; @@ -168,6 +170,8 @@ export function loadPlugin(manifest: PluginManifest): Promise { bundlePath = bundlePath.replace('/static/', '/static/plugins/'); } + addPluginRegisteredHandler(manifest.id, onLoad); + console.log('Loading ' + describePlugin(manifest)); //eslint-disable-line no-console const script = document.createElement('script'); @@ -175,7 +179,6 @@ export function loadPlugin(manifest: PluginManifest): Promise { script.type = 'text/javascript'; script.src = getSiteURL() + bundlePath; script.defer = true; - script.onload = onLoad; script.onerror = onError; document.getElementsByTagName('head')[0].appendChild(script); @@ -227,6 +230,26 @@ export function removePlugin(manifest: PluginManifest): void { console.log('Removed ' + describePlugin(manifest)); //eslint-disable-line no-console } +type PluginRegisteredListener = () => void; + +const pluginRegisteredHandlers = new Map(); + +function addPluginRegisteredHandler(pluginId: string, listener: PluginRegisteredListener) { + pluginRegisteredHandlers.set(pluginId, listener); +} + +function onPluginRegistered(pluginId: string) { + const listener = pluginRegisteredHandlers.get(pluginId); + + if (listener) { + pluginRegisteredHandlers.delete(pluginId); + listener(); + } else { + // eslint-disable-next-line no-console + console.error('A plugin was registered, but no listener has been registered for it. It won\'t be loaded correctly.'); + } +} + // loadPluginsIfNecessary synchronizes the current state of loaded plugins with that of the server, // loading any newly added plugins and unloading any removed ones. export async function loadPluginsIfNecessary(): Promise {