| /** |
| * @license |
| * Copyright 2017 Google LLC |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| import '../../../test/common-test-setup'; |
| import {PLUGIN_LOADING_TIMEOUT_MS} from './gr-api-utils'; |
| import {PluginLoader} from './gr-plugin-loader'; |
| import {stubBaseUrl, waitEventLoop} from '../../../test/test-utils'; |
| import {addListenerForTest, stubRestApi} from '../../../test/test-utils'; |
| import {PluginApi} from '../../../api/plugin'; |
| import {SinonFakeTimers} from 'sinon'; |
| import {Timestamp} from '../../../api/rest-api'; |
| import {assert} from '@open-wc/testing'; |
| import {getAppContext} from '../../../services/app-context'; |
| |
| suite('gr-plugin-loader tests', () => { |
| let plugin: PluginApi; |
| |
| let url: string; |
| let pluginLoader: PluginLoader; |
| let clock: SinonFakeTimers; |
| let bodyStub: sinon.SinonStub; |
| |
| setup(() => { |
| clock = sinon.useFakeTimers(); |
| |
| stubRestApi('getAccount').returns( |
| Promise.resolve({name: 'Judy Hopps', registered_on: '' as Timestamp}) |
| ); |
| stubRestApi('send').returns( |
| Promise.resolve({...new Response(), status: 200}) |
| ); |
| pluginLoader = new PluginLoader( |
| getAppContext().reportingService, |
| getAppContext().restApiService |
| ); |
| bodyStub = sinon.stub(document.body, 'appendChild'); |
| url = window.location.origin; |
| }); |
| |
| teardown(() => { |
| clock.restore(); |
| }); |
| |
| test('reuse plugin for install calls', () => { |
| pluginLoader.install( |
| p => { |
| plugin = p; |
| }, |
| '0.1', |
| 'http://test.com/plugins/testplugin/static/test.js' |
| ); |
| |
| let otherPlugin; |
| pluginLoader.install( |
| p => { |
| otherPlugin = p; |
| }, |
| '0.1', |
| 'http://test.com/plugins/testplugin/static/test.js' |
| ); |
| assert.strictEqual(plugin, otherPlugin); |
| }); |
| |
| test('versioning', () => { |
| const callback = sinon.spy(); |
| pluginLoader.install(callback, '0.0pre-alpha'); |
| assert(callback.notCalled); |
| }); |
| |
| test('report pluginsLoaded', async () => { |
| const pluginsLoadedStub = sinon.stub( |
| getAppContext().reportingService, |
| 'pluginsLoaded' |
| ); |
| pluginsLoadedStub.reset(); |
| pluginLoader.loadPlugins([]); |
| await waitEventLoop(); |
| assert.isTrue(pluginsLoadedStub.called); |
| }); |
| |
| test('arePluginsLoaded', async () => { |
| assert.isFalse(pluginLoader.arePluginsLoaded()); |
| const plugins = [ |
| 'http://test.com/plugins/foo/static/test.js', |
| 'http://test.com/plugins/bar/static/test.js', |
| ]; |
| |
| pluginLoader.loadPlugins(plugins); |
| assert.isFalse(pluginLoader.arePluginsLoaded()); |
| // Timeout on loading plugins |
| clock.tick(PLUGIN_LOADING_TIMEOUT_MS * 2); |
| |
| await waitEventLoop(); |
| assert.isTrue(pluginLoader.arePluginsLoaded()); |
| }); |
| |
| test('plugins installed successfully', async () => { |
| sinon.stub(pluginLoader, 'loadJsPlugin').callsFake(url => { |
| pluginLoader.install(() => void 0, undefined, url); |
| }); |
| const pluginsLoadedStub = sinon.stub( |
| getAppContext().reportingService, |
| 'pluginsLoaded' |
| ); |
| |
| const plugins = [ |
| 'http://test.com/plugins/foo/static/test.js', |
| 'http://test.com/plugins/bar/static/test.js', |
| ]; |
| pluginLoader.loadPlugins(plugins); |
| |
| await waitEventLoop(); |
| assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo', 'bar'])); |
| assert.isTrue(pluginLoader.arePluginsLoaded()); |
| }); |
| |
| test('isPluginEnabled and isPluginLoaded', async () => { |
| sinon.stub(pluginLoader, 'loadJsPlugin').callsFake(url => { |
| pluginLoader.install(() => void 0, undefined, url); |
| }); |
| |
| const plugins = [ |
| 'http://test.com/plugins/foo/static/test.js', |
| 'http://test.com/plugins/bar/static/test.js', |
| 'bar/static/test.js', |
| ]; |
| pluginLoader.loadPlugins(plugins); |
| assert.isTrue( |
| plugins.every(plugin => pluginLoader.isPluginEnabled(plugin)) |
| ); |
| |
| await waitEventLoop(); |
| assert.isTrue(pluginLoader.arePluginsLoaded()); |
| assert.isTrue(plugins.every(plugin => pluginLoader.isPluginLoaded(plugin))); |
| }); |
| |
| test('plugins installed mixed result, 1 fail 1 succeed', async () => { |
| const plugins = [ |
| 'http://test.com/plugins/foo/static/test.js', |
| 'http://test.com/plugins/bar/static/test.js', |
| ]; |
| |
| const alertStub = sinon.stub(); |
| addListenerForTest(document, 'show-alert', alertStub); |
| |
| sinon.stub(pluginLoader, 'loadJsPlugin').callsFake(url => { |
| pluginLoader.install( |
| () => { |
| if (url === plugins[0]) { |
| throw new Error('failed'); |
| } |
| }, |
| undefined, |
| url |
| ); |
| }); |
| |
| const pluginsLoadedStub = sinon.stub( |
| getAppContext().reportingService, |
| 'pluginsLoaded' |
| ); |
| |
| pluginLoader.loadPlugins(plugins); |
| |
| await waitEventLoop(); |
| assert.isTrue(pluginsLoadedStub.calledWithExactly(['bar'])); |
| assert.isTrue(pluginLoader.arePluginsLoaded()); |
| assert.isTrue(alertStub.calledOnce); |
| }); |
| |
| test('isPluginEnabled and isPluginLoaded for mixed results', async () => { |
| const plugins = [ |
| 'http://test.com/plugins/foo/static/test.js', |
| 'http://test.com/plugins/bar/static/test.js', |
| ]; |
| |
| const alertStub = sinon.stub(); |
| addListenerForTest(document, 'show-alert', alertStub); |
| |
| sinon.stub(pluginLoader, 'loadJsPlugin').callsFake(url => { |
| pluginLoader.install( |
| () => { |
| if (url === plugins[0]) { |
| throw new Error('failed'); |
| } |
| }, |
| undefined, |
| url |
| ); |
| }); |
| |
| const pluginsLoadedStub = sinon.stub( |
| getAppContext().reportingService, |
| 'pluginsLoaded' |
| ); |
| |
| pluginLoader.loadPlugins(plugins); |
| assert.isTrue( |
| plugins.every(plugin => pluginLoader.isPluginEnabled(plugin)) |
| ); |
| |
| await waitEventLoop(); |
| assert.isTrue(pluginsLoadedStub.calledWithExactly(['bar'])); |
| assert.isTrue(pluginLoader.arePluginsLoaded()); |
| assert.isTrue(alertStub.calledOnce); |
| assert.isTrue(pluginLoader.isPluginLoaded(plugins[1])); |
| assert.isFalse(pluginLoader.isPluginLoaded(plugins[0])); |
| }); |
| |
| test('plugins installed all failed', async () => { |
| const plugins = [ |
| 'http://test.com/plugins/foo/static/test.js', |
| 'http://test.com/plugins/bar/static/test.js', |
| ]; |
| |
| const alertStub = sinon.stub(); |
| addListenerForTest(document, 'show-alert', alertStub); |
| |
| sinon.stub(pluginLoader, 'loadJsPlugin').callsFake(url => { |
| pluginLoader.install( |
| () => { |
| throw new Error('failed'); |
| }, |
| undefined, |
| url |
| ); |
| }); |
| |
| const pluginsLoadedStub = sinon.stub( |
| getAppContext().reportingService, |
| 'pluginsLoaded' |
| ); |
| |
| pluginLoader.loadPlugins(plugins); |
| |
| await waitEventLoop(); |
| assert.isTrue(pluginsLoadedStub.calledWithExactly([])); |
| assert.isTrue(pluginLoader.arePluginsLoaded()); |
| assert.isTrue(alertStub.calledTwice); |
| }); |
| |
| test('plugins installed failed because of wrong version', async () => { |
| const plugins = [ |
| 'http://test.com/plugins/foo/static/test.js', |
| 'http://test.com/plugins/bar/static/test.js', |
| ]; |
| |
| const alertStub = sinon.stub(); |
| addListenerForTest(document, 'show-alert', alertStub); |
| |
| sinon.stub(pluginLoader, 'loadJsPlugin').callsFake(url => { |
| pluginLoader.install(() => {}, url === plugins[0] ? '' : 'alpha', url); |
| }); |
| |
| const pluginsLoadedStub = sinon.stub( |
| getAppContext().reportingService, |
| 'pluginsLoaded' |
| ); |
| |
| pluginLoader.loadPlugins(plugins); |
| |
| await waitEventLoop(); |
| assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo'])); |
| assert.isTrue(pluginLoader.arePluginsLoaded()); |
| assert.isTrue(alertStub.calledOnce); |
| }); |
| |
| test('multiple assets for same plugin installed successfully', async () => { |
| sinon.stub(pluginLoader, 'loadJsPlugin').callsFake(url => { |
| pluginLoader.install(() => void 0, undefined, url); |
| }); |
| const pluginsLoadedStub = sinon.stub( |
| getAppContext().reportingService, |
| 'pluginsLoaded' |
| ); |
| |
| const plugins = [ |
| 'http://test.com/plugins/foo/static/test.js', |
| 'http://test.com/plugins/foo/static/test2.js', |
| 'http://test.com/plugins/bar/static/test.js', |
| ]; |
| pluginLoader.loadPlugins(plugins); |
| |
| await waitEventLoop(); |
| assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo', 'bar'])); |
| assert.isTrue(pluginLoader.arePluginsLoaded()); |
| }); |
| |
| suite('plugin path and url', () => { |
| let loadJsPluginStub: sinon.SinonStub; |
| setup(() => { |
| loadJsPluginStub = sinon.stub(); |
| sinon |
| .stub(pluginLoader, 'createScriptTag') |
| .callsFake((url: string, _onerror?: OnErrorEventHandler | undefined) => |
| loadJsPluginStub(url) |
| ); |
| }); |
| |
| test('invalid plugin path', () => { |
| const failToLoadStub = sinon.stub(); |
| sinon.stub(pluginLoader, 'failToLoad').callsFake((...args) => { |
| failToLoadStub(...args); |
| }); |
| |
| pluginLoader.loadPlugins(['foo/bar']); |
| |
| assert.isTrue(failToLoadStub.calledOnce); |
| assert.isTrue( |
| failToLoadStub.calledWithExactly( |
| 'Unrecognized plugin path foo/bar', |
| 'foo/bar' |
| ) |
| ); |
| }); |
| |
| test('relative path for plugins', () => { |
| pluginLoader.loadPlugins(['foo/bar.js']); |
| |
| assert.isTrue(loadJsPluginStub.calledOnce); |
| assert.isTrue(loadJsPluginStub.calledWithExactly(`${url}/foo/bar.js`)); |
| }); |
| |
| test('relative path should honor getBaseUrl', () => { |
| const testUrl = '/test'; |
| stubBaseUrl(testUrl); |
| |
| pluginLoader.loadPlugins(['foo/bar.js']); |
| |
| assert.isTrue(loadJsPluginStub.calledOnce); |
| assert.isTrue( |
| loadJsPluginStub.calledWithExactly(`${url}${testUrl}/foo/bar.js`) |
| ); |
| }); |
| |
| test('absolute path for plugins', () => { |
| pluginLoader.loadPlugins(['http://e.com/foo/bar.js']); |
| |
| assert.isTrue(loadJsPluginStub.calledOnce); |
| assert.isTrue( |
| loadJsPluginStub.calledWithExactly('http://e.com/foo/bar.js') |
| ); |
| }); |
| }); |
| |
| suite('With ASSETS_PATH', () => { |
| let loadJsPluginStub: sinon.SinonStub; |
| setup(() => { |
| window.ASSETS_PATH = 'https://cdn.com'; |
| loadJsPluginStub = sinon.stub(); |
| sinon |
| .stub(pluginLoader, 'createScriptTag') |
| .callsFake((url: string, _onerror?: OnErrorEventHandler | undefined) => |
| loadJsPluginStub(url) |
| ); |
| }); |
| |
| teardown(() => { |
| window.ASSETS_PATH = ''; |
| }); |
| |
| test('Should try load plugins from assets path instead', () => { |
| pluginLoader.loadPlugins(['foo/bar.js']); |
| |
| assert.isTrue(loadJsPluginStub.calledOnce); |
| assert.isTrue( |
| loadJsPluginStub.calledWithExactly('https://cdn.com/foo/bar.js') |
| ); |
| }); |
| |
| test('Should honor original path if exists', () => { |
| pluginLoader.loadPlugins(['http://e.com/foo/bar.js']); |
| |
| assert.isTrue(loadJsPluginStub.calledOnce); |
| assert.isTrue( |
| loadJsPluginStub.calledWithExactly('http://e.com/foo/bar.js') |
| ); |
| }); |
| |
| test('Should try replace current host with assetsPath', () => { |
| const host = window.location.origin; |
| pluginLoader.loadPlugins([`${host}/foo/bar.js`]); |
| |
| assert.isTrue(loadJsPluginStub.calledOnce); |
| assert.isTrue( |
| loadJsPluginStub.calledWithExactly('https://cdn.com/foo/bar.js') |
| ); |
| }); |
| }); |
| |
| test('adds js plugins will call the body', () => { |
| pluginLoader.loadPlugins([ |
| 'http://e.com/foo/bar.js', |
| 'http://e.com/bar/foo.js', |
| ]); |
| assert.isTrue(bodyStub.calledTwice); |
| }); |
| |
| test('can call awaitPluginsLoaded multiple times', async () => { |
| const plugins = ['http://e.com/foo/bar.js', 'http://e.com/bar/foo.js']; |
| |
| let installed = false; |
| function pluginCallback(url: string) { |
| if (url === plugins[1]) { |
| installed = true; |
| } |
| } |
| sinon.stub(pluginLoader, 'loadJsPlugin').callsFake(url => { |
| pluginLoader.install(() => pluginCallback(url), undefined, url); |
| }); |
| |
| pluginLoader.loadPlugins(plugins); |
| |
| await pluginLoader.awaitPluginsLoaded(); |
| assert.isTrue(installed); |
| await pluginLoader.awaitPluginsLoaded(); |
| }); |
| }); |