diff --git a/package.json b/package.json index fa7bc1e2176..b31b9079cbe 100644 --- a/package.json +++ b/package.json @@ -282,7 +282,8 @@ }, "workspaces": { "packages": [ - "packages/*" + "packages/*", + "plugins-bundled/internal/*" ], "nohoist": [ "**/@types/*", diff --git a/packages/grafana-toolkit/package.json b/packages/grafana-toolkit/package.json index e2bf38db11c..fc5adc9acb8 100644 --- a/packages/grafana-toolkit/package.json +++ b/packages/grafana-toolkit/package.json @@ -67,6 +67,7 @@ "execa": "^1.0.0", "expect-puppeteer": "4.1.1", "file-loader": "^4.0.0", + "fork-ts-checker-webpack-plugin": "1.0.0", "fs-extra": "^8.1.0", "globby": "^10.0.1", "html-loader": "0.5.5", diff --git a/packages/grafana-toolkit/src/cli/index.ts b/packages/grafana-toolkit/src/cli/index.ts index 4fe7fd3e463..9fc493f2a4d 100644 --- a/packages/grafana-toolkit/src/cli/index.ts +++ b/packages/grafana-toolkit/src/cli/index.ts @@ -18,6 +18,7 @@ import { githubPublishTask } from './tasks/plugin.utils'; import { ciBuildPluginTask, ciBuildPluginDocsTask, ciPackagePluginTask, ciPluginReportTask } from './tasks/plugin.ci'; import { buildPackageTask } from './tasks/package.build'; import { pluginCreateTask } from './tasks/plugin.create'; +import { bundleManagedTask } from './tasks/plugin/bundle.managed'; export const run = (includeInternalScripts = false) => { if (includeInternalScripts) { @@ -195,6 +196,13 @@ export const run = (includeInternalScripts = false) => { }); }); + program + .command('plugin:bundle-managed') + .description('Builds managed plugins') + .action(async cmd => { + await execTask(bundleManagedTask)({}); + }); + program .command('plugin:github-publish') .option('--dryrun', 'Do a dry run only', false) diff --git a/packages/grafana-toolkit/src/cli/tasks/manifest.test.ts b/packages/grafana-toolkit/src/cli/tasks/manifest.test.ts index 7e9d22c164e..5183f037041 100644 --- a/packages/grafana-toolkit/src/cli/tasks/manifest.test.ts +++ b/packages/grafana-toolkit/src/cli/tasks/manifest.test.ts @@ -13,6 +13,7 @@ describe('Manifest', () => { "manifest.ts", "nodeVersionChecker.ts", "package.build.ts", + "plugin/bundle.managed.ts", "plugin/bundle.ts", "plugin/create.ts", "plugin/tests.ts", diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin/bundle.managed.ts b/packages/grafana-toolkit/src/cli/tasks/plugin/bundle.managed.ts new file mode 100644 index 00000000000..f8698e21fb2 --- /dev/null +++ b/packages/grafana-toolkit/src/cli/tasks/plugin/bundle.managed.ts @@ -0,0 +1,38 @@ +import { Task, TaskRunner } from '../task'; +import { restoreCwd } from '../../utils/cwd'; +import execa = require('execa'); +const fs = require('fs'); +const util = require('util'); + +const readdirPromise = util.promisify(fs.readdir); + +interface BundeManagedOptions {} + +const MANAGED_PLUGINS_PATH = `${process.cwd()}/plugins-bundled`; +const MANAGED_PLUGINS_SCOPES = ['internal', 'external']; + +const bundleManagedPluginsRunner: TaskRunner = async () => { + await Promise.all( + MANAGED_PLUGINS_SCOPES.map(async scope => { + try { + const plugins = await readdirPromise(`${MANAGED_PLUGINS_PATH}/${scope}`); + if (plugins.length > 0) { + for (const plugin of plugins) { + process.chdir(`${MANAGED_PLUGINS_PATH}/${scope}/${plugin}`); + try { + await execa('yarn', ['dev']); + console.log(`[${scope}]: ${plugin} bundled`); + } catch (e) { + console.log(e.stdout); + } + } + } + } catch (e) { + console.log(e); + } + }) + ); + restoreCwd(); +}; + +export const bundleManagedTask = new Task('Bundle managed plugins', bundleManagedPluginsRunner); diff --git a/packages/grafana-toolkit/src/config/webpack.plugin.config.ts b/packages/grafana-toolkit/src/config/webpack.plugin.config.ts index cb88145aa43..2aed1e8981f 100644 --- a/packages/grafana-toolkit/src/config/webpack.plugin.config.ts +++ b/packages/grafana-toolkit/src/config/webpack.plugin.config.ts @@ -7,6 +7,7 @@ const TerserPlugin = require('terser-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); const readdirPromise = util.promisify(fs.readdir); const accessPromise = util.promisify(fs.access); @@ -123,6 +124,11 @@ const getCommonPlugins = (options: WebpackConfigurationOptions) => { ], }, ]), + new ForkTsCheckerWebpackPlugin({ + tsconfig: path.join(process.cwd(), 'tsconfig.json'), + // Only report problems in detected in plugin's code + reportFiles: ['**/*.{ts,tsx}'], + }), ]; }; @@ -205,7 +211,10 @@ const getBaseWebpackConfig: WebpackConfigurationGetter = async options => { }, { loader: 'ts-loader', - options: { onlyCompileBundledFiles: true }, + options: { + onlyCompileBundledFiles: true, + transpileOnly: true, + }, }, ], exclude: /(node_modules)/, diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 30d2a64d4c1..78680dff11e 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -78,6 +78,14 @@ func (pm *PluginManager) Init() error { return errutil.Wrapf(err, "Failed to scan main plugin directory '%s'", plugDir) } + pm.log.Info("Checking Bundled Plugins") + plugDir = path.Join(setting.HomePath, "plugins-bundled") + if _, err := os.Stat(plugDir); !os.IsNotExist(err) { + if err := pm.scan(plugDir); err != nil { + return errutil.Wrapf(err, "failed to scan bundled plugin directory '%s'", plugDir) + } + } + // check if plugins dir exists if _, err := os.Stat(setting.PluginsPath); os.IsNotExist(err) { if err = os.MkdirAll(setting.PluginsPath, os.ModePerm); err != nil { diff --git a/plugins-bundled/.gitignore b/plugins-bundled/.gitignore new file mode 100644 index 00000000000..f6b56a2cb7c --- /dev/null +++ b/plugins-bundled/.gitignore @@ -0,0 +1,6 @@ +# packaged by toolkit +dist +coverage + +# Ignore external git configs +external diff --git a/plugins-bundled/README.md b/plugins-bundled/README.md new file mode 100644 index 00000000000..11a5929e30e --- /dev/null +++ b/plugins-bundled/README.md @@ -0,0 +1,6 @@ +Bundled Plugins +=============== + +Bundled plugins are built as true plugins, and managed by the grafana install. + +TODO: the packaging system should move all `dist` items to the root and remove sources. diff --git a/plugins-bundled/internal/input-datasource/LICENSE b/plugins-bundled/internal/input-datasource/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/plugins-bundled/internal/input-datasource/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/public/app/plugins/datasource/input/README.md b/plugins-bundled/internal/input-datasource/README.md similarity index 100% rename from public/app/plugins/datasource/input/README.md rename to plugins-bundled/internal/input-datasource/README.md diff --git a/plugins-bundled/internal/input-datasource/package.json b/plugins-bundled/internal/input-datasource/package.json new file mode 100644 index 00000000000..7835a546ef0 --- /dev/null +++ b/plugins-bundled/internal/input-datasource/package.json @@ -0,0 +1,22 @@ +{ + "name": "@grafana-plugins/input-datasource", + "version": "6.6.0-pre", + "description": "Input Datasource", + "repository": { + "type": "git", + "url": "http://github.com/grafana/grafana.git" + }, + "scripts": { + "build": "grafana-toolkit plugin:build", + "test": "grafana-toolkit plugin:test", + "dev": "grafana-toolkit plugin:dev", + "watch": "grafana-toolkit plugin:dev --watch" + }, + "author": "Grafana Labs", + "license": "Apache-2.0", + "devDependencies": { + "@grafana/data": "^6.6.0-pre", + "@grafana/ui": "^6.6.0-pre", + "@grafana/toolkit": "^6.6.0-pre" + } +} diff --git a/public/app/plugins/datasource/input/InputConfigEditor.tsx b/plugins-bundled/internal/input-datasource/src/InputConfigEditor.tsx similarity index 100% rename from public/app/plugins/datasource/input/InputConfigEditor.tsx rename to plugins-bundled/internal/input-datasource/src/InputConfigEditor.tsx diff --git a/public/app/plugins/datasource/input/InputDatasource.test.ts b/plugins-bundled/internal/input-datasource/src/InputDatasource.test.ts similarity index 95% rename from public/app/plugins/datasource/input/InputDatasource.test.ts rename to plugins-bundled/internal/input-datasource/src/InputDatasource.test.ts index 4fcde2a0eaf..23ff3a17b9a 100644 --- a/public/app/plugins/datasource/input/InputDatasource.test.ts +++ b/plugins-bundled/internal/input-datasource/src/InputDatasource.test.ts @@ -9,7 +9,7 @@ import { readCSV, } from '@grafana/data'; -import { getQueryOptions } from 'test/helpers/getQueryOptions'; +import { getQueryOptions } from './testHelpers'; describe('InputDatasource', () => { const data = readCSV('a,b,c\n1,2,3\n4,5,6'); diff --git a/public/app/plugins/datasource/input/InputDatasource.ts b/plugins-bundled/internal/input-datasource/src/InputDatasource.ts similarity index 98% rename from public/app/plugins/datasource/input/InputDatasource.ts rename to plugins-bundled/internal/input-datasource/src/InputDatasource.ts index 5aadbcdd38b..b4b887de4cb 100644 --- a/public/app/plugins/datasource/input/InputDatasource.ts +++ b/plugins-bundled/internal/input-datasource/src/InputDatasource.ts @@ -99,7 +99,7 @@ function getLength(data?: DataFrameDTO | DataFrame) { if (data.hasOwnProperty('length')) { return (data as DataFrame).length; } - return data.fields[0].values.length; + return data.fields[0].values!.length; } export function describeDataFrame(data: Array): string { diff --git a/public/app/plugins/datasource/input/InputQueryEditor.tsx b/plugins-bundled/internal/input-datasource/src/InputQueryEditor.tsx similarity index 100% rename from public/app/plugins/datasource/input/InputQueryEditor.tsx rename to plugins-bundled/internal/input-datasource/src/InputQueryEditor.tsx diff --git a/public/app/plugins/datasource/input/img/input.svg b/plugins-bundled/internal/input-datasource/src/img/input.svg similarity index 100% rename from public/app/plugins/datasource/input/img/input.svg rename to plugins-bundled/internal/input-datasource/src/img/input.svg diff --git a/public/app/plugins/datasource/input/module.ts b/plugins-bundled/internal/input-datasource/src/module.ts similarity index 100% rename from public/app/plugins/datasource/input/module.ts rename to plugins-bundled/internal/input-datasource/src/module.ts diff --git a/public/app/plugins/datasource/input/plugin.json b/plugins-bundled/internal/input-datasource/src/plugin.json similarity index 85% rename from public/app/plugins/datasource/input/plugin.json rename to plugins-bundled/internal/input-datasource/src/plugin.json index dbfa0ad489a..05b5a647a8b 100644 --- a/public/app/plugins/datasource/input/plugin.json +++ b/plugins-bundled/internal/input-datasource/src/plugin.json @@ -5,9 +5,6 @@ "state": "alpha", "metrics": true, - "alerting": false, - "annotations": false, - "logs": false, "info": { "description": "Data source that supports manual table & CSV input", diff --git a/plugins-bundled/internal/input-datasource/src/testHelpers.ts b/plugins-bundled/internal/input-datasource/src/testHelpers.ts new file mode 100644 index 00000000000..47b3308f60c --- /dev/null +++ b/plugins-bundled/internal/input-datasource/src/testHelpers.ts @@ -0,0 +1,28 @@ +import { DataQueryRequest, DataQuery, CoreApp } from '@grafana/data'; +import { dateTime } from '@grafana/data'; + +export function getQueryOptions( + options: Partial> +): DataQueryRequest { + const raw = { from: 'now', to: 'now-1h' }; + const range = { from: dateTime(), to: dateTime(), raw: raw }; + + const defaults: DataQueryRequest = { + requestId: 'TEST', + app: CoreApp.Dashboard, + range: range, + targets: [], + scopedVars: {}, + timezone: 'browser', + panelId: 1, + dashboardId: 1, + interval: '60s', + intervalMs: 60000, + maxDataPoints: 500, + startTime: 0, + }; + + Object.assign(defaults, options); + + return defaults; +} diff --git a/public/app/plugins/datasource/input/types.ts b/plugins-bundled/internal/input-datasource/src/types.ts similarity index 100% rename from public/app/plugins/datasource/input/types.ts rename to plugins-bundled/internal/input-datasource/src/types.ts diff --git a/public/app/plugins/datasource/input/utils.ts b/plugins-bundled/internal/input-datasource/src/utils.ts similarity index 100% rename from public/app/plugins/datasource/input/utils.ts rename to plugins-bundled/internal/input-datasource/src/utils.ts diff --git a/plugins-bundled/internal/input-datasource/tsconfig.json b/plugins-bundled/internal/input-datasource/tsconfig.json new file mode 100644 index 00000000000..1a8b6a087a4 --- /dev/null +++ b/plugins-bundled/internal/input-datasource/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../packages/grafana-toolkit/src/config/tsconfig.plugin.json", + "include": ["src", "types"], + "compilerOptions": { + "rootDir": "./src", + "baseUrl": "./src", + "typeRoots": ["./node_modules/@types"] + } +} diff --git a/public/app/features/plugins/built_in_plugins.ts b/public/app/features/plugins/built_in_plugins.ts index ec35b5cade1..78058438ebe 100644 --- a/public/app/features/plugins/built_in_plugins.ts +++ b/public/app/features/plugins/built_in_plugins.ts @@ -27,8 +27,6 @@ const mssqlPlugin = async () => await import(/* webpackChunkName: "mssqlPlugin" */ 'app/plugins/datasource/mssql/module'); const testDataDSPlugin = async () => await import(/* webpackChunkName: "testDataDSPlugin" */ 'app/plugins/datasource/testdata/module'); -const inputDatasourcePlugin = async () => - await import(/* webpackChunkName: "inputDatasourcePlugin" */ 'app/plugins/datasource/input/module'); const stackdriverPlugin = async () => await import(/* webpackChunkName: "stackdriverPlugin" */ 'app/plugins/datasource/stackdriver/module'); const azureMonitorPlugin = async () => @@ -73,7 +71,6 @@ const builtInPlugins: any = { 'app/plugins/datasource/mssql/module': mssqlPlugin, 'app/plugins/datasource/prometheus/module': prometheusPlugin, 'app/plugins/datasource/testdata/module': testDataDSPlugin, - 'app/plugins/datasource/input/module': inputDatasourcePlugin, 'app/plugins/datasource/stackdriver/module': stackdriverPlugin, 'app/plugins/datasource/grafana-azure-monitor-datasource/module': azureMonitorPlugin, diff --git a/public/app/features/plugins/plugin_loader.test.ts b/public/app/features/plugins/plugin_loader.test.ts new file mode 100644 index 00000000000..2d365233710 --- /dev/null +++ b/public/app/features/plugins/plugin_loader.test.ts @@ -0,0 +1,68 @@ +// Use the real plugin_loader (stubbed by default) +jest.unmock('app/features/plugins/plugin_loader'); + +(global as any).ace = { + define: jest.fn(), +}; + +jest.mock('app/core/core', () => { + return { + coreModule: { + directive: jest.fn(), + }, + }; +}); + +import { SystemJS } from '@grafana/runtime'; +import { AppPluginMeta, PluginMetaInfo, PluginType, AppPlugin } from '@grafana/data'; + +// Loaded after the `unmock` abve +import { importAppPlugin } from './plugin_loader'; + +class MyCustomApp extends AppPlugin { + initWasCalled = false; + calledTwice = false; + + init(meta: AppPluginMeta) { + this.initWasCalled = true; + this.calledTwice = this.meta === meta; + } +} + +describe('Load App', () => { + const app = new MyCustomApp(); + const modulePath = 'my/custom/plugin/module'; + + beforeAll(() => { + SystemJS.set(modulePath, SystemJS.newModule({ plugin: app })); + }); + + afterAll(() => { + SystemJS.delete(modulePath); + }); + + it('should call init and set meta', async () => { + const meta: AppPluginMeta = { + id: 'test-app', + module: modulePath, + baseUrl: 'xxx', + info: {} as PluginMetaInfo, + type: PluginType.app, + name: 'test', + }; + + // Check that we mocked the import OK + const m = await SystemJS.import(modulePath); + expect(m.plugin).toBe(app); + + const loaded = await importAppPlugin(meta); + expect(loaded).toBe(app); + expect(app.meta).toBe(meta); + expect(app.initWasCalled).toBeTruthy(); + expect(app.calledTwice).toBeFalsy(); + + const again = await importAppPlugin(meta); + expect(again).toBe(app); + expect(app.calledTwice).toBeTruthy(); + }); +});