/** * @license * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import '../../../test/common-test-setup-karma.js'; import './gr-js-api-interface.js'; import {PRELOADED_PROTOCOL, PLUGIN_LOADING_TIMEOUT_MS} from './gr-api-utils.js'; import {_testOnly_resetPluginLoader} from './gr-plugin-loader.js'; import {resetPlugins, stubBaseUrl} from '../../../test/test-utils.js'; import {_testOnly_flushPreinstalls} from './gr-gerrit.js'; import {_testOnly_initGerritPluginApi} from './gr-gerrit.js'; const basicFixture = fixtureFromElement('gr-js-api-interface'); const pluginApi = _testOnly_initGerritPluginApi(); suite('gr-plugin-loader tests', () => { let plugin; let url; let sendStub; let pluginLoader; let clock; setup(() => { clock = sinon.useFakeTimers(); sendStub = sinon.stub().returns(Promise.resolve({status: 200})); stub('gr-rest-api-interface', { getAccount() { return Promise.resolve({name: 'Judy Hopps'}); }, send(...args) { return sendStub(...args); }, }); pluginLoader = _testOnly_resetPluginLoader(); sinon.stub(document.body, 'appendChild'); basicFixture.instantiate(); url = window.location.origin; }); teardown(() => { clock.restore(); resetPlugins(); }); test('reuse plugin for install calls', () => { pluginApi.install(p => { plugin = p; }, '0.1', 'http://test.com/plugins/testplugin/static/test.js'); let otherPlugin; pluginApi.install(p => { otherPlugin = p; }, '0.1', 'http://test.com/plugins/testplugin/static/test.js'); assert.strictEqual(plugin, otherPlugin); }); test('flushes preinstalls if provided', () => { assert.doesNotThrow(() => { _testOnly_flushPreinstalls(); }); window.Gerrit.flushPreinstalls = sinon.stub(); _testOnly_flushPreinstalls(); assert.isTrue(window.Gerrit.flushPreinstalls.calledOnce); delete window.Gerrit.flushPreinstalls; }); test('versioning', () => { const callback = sinon.spy(); pluginApi.install(callback, '0.0pre-alpha'); assert(callback.notCalled); }); test('report pluginsLoaded', async () => { const pluginsLoadedStub = sinon.stub(pluginLoader._getReporting(), 'pluginsLoaded'); pluginsLoadedStub.reset(); window.Gerrit._loadPlugins([]); await flush(); assert.isTrue(pluginsLoadedStub.called); }); test('arePluginsLoaded', () => { 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); flush(); assert.isTrue(pluginLoader.arePluginsLoaded()); }); test('plugins installed successfully', async () => { sinon.stub(pluginLoader, '_loadJsPlugin').callsFake( url => { pluginApi.install(() => void 0, undefined, url); }); const pluginsLoadedStub = sinon.stub(pluginLoader._getReporting(), 'pluginsLoaded'); const plugins = [ 'http://test.com/plugins/foo/static/test.js', 'http://test.com/plugins/bar/static/test.js', ]; pluginLoader.loadPlugins(plugins); await flush(); assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo', 'bar'])); assert.isTrue(pluginLoader.arePluginsLoaded()); }); test('isPluginEnabled and isPluginLoaded', () => { sinon.stub(pluginLoader, '_loadJsPlugin').callsFake( url => { pluginApi.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)) ); flush(); 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(); document.addEventListener('show-alert', alertStub); sinon.stub(pluginLoader, '_loadJsPlugin').callsFake( url => { pluginApi.install(() => { if (url === plugins[0]) { throw new Error('failed'); } }, undefined, url); }); const pluginsLoadedStub = sinon.stub(pluginLoader._getReporting(), 'pluginsLoaded'); pluginLoader.loadPlugins(plugins); await flush(); 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(); document.addEventListener('show-alert', alertStub); sinon.stub(pluginLoader, '_loadJsPlugin').callsFake( url => { pluginApi.install(() => { if (url === plugins[0]) { throw new Error('failed'); } }, undefined, url); }); const pluginsLoadedStub = sinon.stub(pluginLoader._getReporting(), 'pluginsLoaded'); pluginLoader.loadPlugins(plugins); assert.isTrue( plugins.every(plugin => pluginLoader.isPluginEnabled(plugin)) ); await flush(); 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(); document.addEventListener('show-alert', alertStub); sinon.stub(pluginLoader, '_loadJsPlugin').callsFake( url => { pluginApi.install(() => { throw new Error('failed'); }, undefined, url); }); const pluginsLoadedStub = sinon.stub(pluginLoader._getReporting(), 'pluginsLoaded'); pluginLoader.loadPlugins(plugins); await flush(); assert.isTrue(pluginsLoadedStub.calledWithExactly([])); assert.isTrue(pluginLoader.arePluginsLoaded()); assert.isTrue(alertStub.calledTwice); }); test('plugins installed failed becasue 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(); document.addEventListener('show-alert', alertStub); sinon.stub(pluginLoader, '_loadJsPlugin').callsFake( url => { pluginApi.install(() => { }, url === plugins[0] ? '' : 'alpha', url); }); const pluginsLoadedStub = sinon.stub(pluginLoader._getReporting(), 'pluginsLoaded'); pluginLoader.loadPlugins(plugins); await flush(); 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 => { pluginApi.install(() => void 0, undefined, url); }); const pluginsLoadedStub = sinon.stub(pluginLoader._getReporting(), '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 flush(); assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo', 'bar'])); assert.isTrue(pluginLoader.arePluginsLoaded()); }); suite('plugin path and url', () => { let importHtmlPluginStub; let loadJsPluginStub; setup(() => { importHtmlPluginStub = sinon.stub(); sinon.stub(pluginLoader, '_loadHtmlPlugin').callsFake( url => { importHtmlPluginStub(url); }); loadJsPluginStub = sinon.stub(); sinon.stub(pluginLoader, '_createScriptTag').callsFake( url => { 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', 'foo/bar.html', ]); assert.isTrue(importHtmlPluginStub.calledOnce); assert.isTrue( importHtmlPluginStub.calledWithExactly(`${url}/foo/bar.html`) ); 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', 'foo/bar.html', ]); assert.isTrue(importHtmlPluginStub.calledOnce); assert.isTrue(loadJsPluginStub.calledOnce); assert.isTrue( importHtmlPluginStub.calledWithExactly( `${url}${testUrl}/foo/bar.html` ) ); assert.isTrue( loadJsPluginStub.calledWithExactly(`${url}${testUrl}/foo/bar.js`) ); }); test('absolute path for plugins', () => { pluginLoader.loadPlugins([ 'http://e.com/foo/bar.js', 'http://e.com/foo/bar.html', ]); assert.isTrue(importHtmlPluginStub.calledOnce); assert.isTrue( importHtmlPluginStub.calledWithExactly(`http://e.com/foo/bar.html`) ); assert.isTrue(loadJsPluginStub.calledOnce); assert.isTrue( loadJsPluginStub.calledWithExactly(`http://e.com/foo/bar.js`) ); }); }); suite('With ASSETS_PATH', () => { let importHtmlPluginStub; let loadJsPluginStub; setup(() => { window.ASSETS_PATH = 'https://cdn.com'; importHtmlPluginStub = sinon.stub(); sinon.stub(pluginLoader, '_loadHtmlPlugin').callsFake( url => { importHtmlPluginStub(url); }); loadJsPluginStub = sinon.stub(); sinon.stub(pluginLoader, '_createScriptTag').callsFake( url => { loadJsPluginStub(url); }); }); teardown(() => { window.ASSETS_PATH = ''; }); test('Should try load plugins from assets path instead', () => { pluginLoader.loadPlugins([ 'foo/bar.js', 'foo/bar.html', ]); assert.isTrue(importHtmlPluginStub.calledOnce); assert.isTrue( importHtmlPluginStub.calledWithExactly(`https://cdn.com/foo/bar.html`) ); 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.html', 'http://e.com/foo/bar.js', ]); assert.isTrue(importHtmlPluginStub.calledOnce); assert.isTrue( importHtmlPluginStub.calledWithExactly(`http://e.com/foo/bar.html`) ); 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.html`, `${host}/foo/bar.js`, ]); assert.isTrue(importHtmlPluginStub.calledOnce); assert.isTrue( importHtmlPluginStub.calledWithExactly(`https://cdn.com/foo/bar.html`) ); 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(document.body.appendChild.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) { if (url === plugins[1]) { installed = true; } } sinon.stub(pluginLoader, '_loadJsPlugin').callsFake( url => { pluginApi.install(() => pluginCallback(url), undefined, url); }); pluginLoader.loadPlugins(plugins); await pluginLoader.awaitPluginsLoaded(); assert.isTrue(installed); await pluginLoader.awaitPluginsLoaded(); }); suite('preloaded plugins', () => { teardown(() => { window.Gerrit._preloadedPlugins = null; }); test('skips preloaded plugins when load plugins', () => { const importHtmlPluginStub = sinon.stub(); sinon.stub(pluginLoader, '_importHtmlPlugin').callsFake( url => { importHtmlPluginStub(url); }); const loadJsPluginStub = sinon.stub(); sinon.stub(pluginLoader, '_loadJsPlugin').callsFake( url => { loadJsPluginStub(url); }); window.Gerrit._preloadedPlugins = { foo: () => void 0, bar: () => void 0, }; pluginLoader.loadPlugins([ 'http://e.com/plugins/foo.js', 'plugins/bar.html', 'http://e.com/plugins/test/foo.js', ]); assert.isTrue(importHtmlPluginStub.notCalled); assert.isTrue(loadJsPluginStub.calledOnce); }); test('isPluginPreloaded', () => { window.Gerrit._preloadedPlugins = {baz: ()=>{}}; assert.isFalse(pluginLoader.isPluginPreloaded('plugins/foo/bar')); assert.isFalse(pluginLoader.isPluginPreloaded('http://a.com/42')); assert.isTrue( pluginLoader.isPluginPreloaded(PRELOADED_PROTOCOL + 'baz') ); }); test('preloaded plugins are installed', () => { const installStub = sinon.stub(); window.Gerrit._preloadedPlugins = {foo: installStub}; pluginLoader.installPreloadedPlugins(); assert.isTrue(installStub.called); const pluginApi = installStub.lastCall.args[0]; assert.strictEqual(pluginApi.getPluginName(), 'foo'); }); test('installing preloaded plugin', () => { let plugin; pluginApi.install(p => { plugin = p; }, '0.1', 'preloaded:foo'); assert.strictEqual(plugin.getPluginName(), 'foo'); assert.strictEqual(plugin.url('/some/thing.html'), `${window.location.origin}/plugins/foo/some/thing.html`); }); }); });