Skip to content

Commit

Permalink
fix tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mshustov committed Apr 27, 2020
1 parent f2c8d51 commit 9f46398
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 73 deletions.
6 changes: 3 additions & 3 deletions src/core/public/plugins/plugin.test.mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export const mockPlugin = {
};
export const mockInitializer = jest.fn(() => mockPlugin);

export const mockPluginLoader = jest.fn().mockResolvedValue(mockInitializer);
export const mockPluginReader = jest.fn(() => mockInitializer);

jest.mock('./plugin_loader', () => ({
loadPluginBundle: mockPluginLoader,
jest.mock('./plugin_reader', () => ({
read: mockPluginReader,
}));
32 changes: 14 additions & 18 deletions src/core/public/plugins/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { mockInitializer, mockPlugin, mockPluginLoader } from './plugin.test.mocks';
import { mockInitializer, mockPlugin, mockPluginReader } from './plugin.test.mocks';

import { DiscoveredPlugin } from '../../server';
import { coreMock } from '../mocks';
Expand All @@ -40,20 +40,14 @@ const opaqueId = Symbol();
const initializerContext = coreMock.createPluginInitializerContext();

beforeEach(() => {
mockPluginLoader.mockClear();
mockPluginReader.mockClear();
mockPlugin.setup.mockClear();
mockPlugin.start.mockClear();
mockPlugin.stop.mockClear();
plugin = new PluginWrapper(createManifest('plugin-a'), opaqueId, initializerContext);
});

describe('PluginWrapper', () => {
test('`setup` fails if load is not called first', async () => {
await expect(plugin.setup({} as any, {} as any)).rejects.toThrowErrorMatchingInlineSnapshot(
`"Plugin \\"plugin-a\\" can't be setup since its bundle isn't loaded."`
);
});

test('`setup` fails if plugin.setup is not a function', async () => {
mockInitializer.mockReturnValueOnce({ start: jest.fn() } as any);
await expect(plugin.setup({} as any, {} as any)).rejects.toThrowErrorMatchingInlineSnapshot(
Expand Down Expand Up @@ -102,19 +96,21 @@ describe('PluginWrapper', () => {
};

let startDependenciesResolved = false;
mockPluginLoader.mockResolvedValueOnce(() => ({
setup: jest.fn(),
start: async () => {
// Add small delay to ensure startDependencies is not resolved until after the plugin instance's start resolves.
await new Promise(resolve => setTimeout(resolve, 10));
expect(startDependenciesResolved).toBe(false);
return pluginStartContract;
},
}));
mockPluginReader.mockReturnValueOnce(
jest.fn(() => ({
setup: jest.fn(),
start: jest.fn(async () => {
// Add small delay to ensure startDependencies is not resolved until after the plugin instance's start resolves.
await new Promise(resolve => setTimeout(resolve, 10));
expect(startDependenciesResolved).toBe(false);
return pluginStartContract;
}),
stop: jest.fn(),
}))
);
await plugin.setup({} as any, {} as any);
const context = { any: 'thing' } as any;
const deps = { otherDep: 'value' };

// Add promise callback prior to calling `start` to ensure calls in `setup` will not resolve before `start` is
// called.
const startDependenciesCheck = plugin.startDependencies.then(res => {
Expand Down
44 changes: 8 additions & 36 deletions src/core/public/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,9 @@ import { Subject } from 'rxjs';
import { first } from 'rxjs/operators';
import { DiscoveredPlugin, PluginOpaqueId } from '../../server';
import { PluginInitializerContext } from './plugin_context';
import { read } from './plugin_reader';
import { CoreStart, CoreSetup } from '..';

/**
* Unknown variant for internal use only for when plugins are not known.
* @internal
*/
export type UnknownPluginInitializer = PluginInitializer<unknown, Record<string, unknown>>;

/**
* Custom window type for loading bundles. Do not extend global Window to avoid leaking these types.
* @internal
*/
export interface CoreWindow {
__kbnBundles__: {
[pluginBundleName: string]: { plugin: UnknownPluginInitializer } | undefined;
};
}

/**
* The interface that should be returned by a `PluginInitializer`.
*
Expand Down Expand Up @@ -100,25 +85,6 @@ export class PluginWrapper<
this.optionalPlugins = discoveredPlugin.optionalPlugins;
}

/**
* Reads the plugin's bundle declared in the global context.
*/
private read() {
const coreWindow = (window as unknown) as CoreWindow;
const exportId = `plugin/${this.name}`;
const pluginExport = coreWindow.__kbnBundles__[exportId];
if (typeof pluginExport!.plugin !== 'function') {
throw new Error(`Definition of plugin "${this.name}" should be a function.`);
} else {
return pluginExport!.plugin as PluginInitializer<
TSetup,
TStart,
TPluginsSetup,
TPluginsStart
>;
}
}

/**
* Instantiates plugin and calls `setup` function exposed by the plugin initializer.
* @param setupContext Context that consists of various core services tailored specifically
Expand Down Expand Up @@ -167,7 +133,13 @@ export class PluginWrapper<
}

private async createPluginInstance() {
const initializer = this.read();
const initializer = read(this.name) as PluginInitializer<
TSetup,
TStart,
TPluginsSetup,
TPluginsStart
>;

const instance = initializer(this.initializerContext);

if (typeof instance.setup !== 'function') {
Expand Down
50 changes: 50 additions & 0 deletions src/core/public/plugins/plugin_reader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you 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 { PluginInitializer } from './plugin';

/**
* Unknown variant for internal use only for when plugins are not known.
* @internal
*/
export type UnknownPluginInitializer = PluginInitializer<unknown, Record<string, unknown>>;

/**
* Custom window type for loading bundles. Do not extend global Window to avoid leaking these types.
* @internal
*/
export interface CoreWindow {
__kbnBundles__: {
[pluginBundleName: string]: { plugin: UnknownPluginInitializer } | undefined;
};
}

/**
* Reads the plugin's bundle declared in the global context.
*/
export function read(name: string) {
const coreWindow = (window as unknown) as CoreWindow;
const exportId = `plugin/${name}`;
const pluginExport = coreWindow.__kbnBundles__[exportId];
if (typeof pluginExport!.plugin !== 'function') {
throw new Error(`Definition of plugin "${name}" should be a function.`);
} else {
return pluginExport!.plugin;
}
}
8 changes: 7 additions & 1 deletion src/core/public/plugins/plugins_service.test.mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,10 @@ export type MockedPluginInitializer = jest.Mock<Plugin<unknown, Record<string, u
export const mockPluginInitializerProvider: jest.Mock<
MockedPluginInitializer,
[PluginName]
> = jest.fn().mockRejectedValue(new Error('No provider specified'));
> = jest.fn().mockImplementation(() => () => {
throw new Error('No provider specified');
});

jest.mock('./plugin_reader', () => ({
read: mockPluginInitializerProvider,
}));
16 changes: 1 addition & 15 deletions src/core/public/plugins/plugins_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
PluginsServiceStartDeps,
PluginsServiceSetupDeps,
} from './plugins_service';

import { InjectedPluginMetadata } from '../injected_metadata';
import { notificationServiceMock } from '../notifications/notifications_service.mock';
import { applicationServiceMock } from '../application/application_service.mock';
Expand Down Expand Up @@ -151,10 +152,6 @@ describe('PluginsService', () => {
] as unknown) as [[PluginName, any]]);
});

afterEach(() => {
// mockLoadPluginBundle.mockClear();
});

describe('#getOpaqueIds()', () => {
it('returns dependency tree of symbols', () => {
const pluginsService = new PluginsService(mockCoreContext, plugins);
Expand All @@ -173,16 +170,6 @@ describe('PluginsService', () => {
});

describe('#setup()', () => {
it('fails if any bundle cannot be loaded', async () => {
// TODO
// mockLoadPluginBundle.mockRejectedValueOnce(new Error('Could not load bundle'));

const pluginsService = new PluginsService(mockCoreContext, plugins);
await expect(pluginsService.setup(mockSetupDeps)).rejects.toThrowErrorMatchingInlineSnapshot(
`"Could not load bundle"`
);
});

it('fails if any plugin instance does not have a setup function', async () => {
mockPluginInitializers.set('pluginA', (() => ({})) as any);
const pluginsService = new PluginsService(mockCoreContext, plugins);
Expand Down Expand Up @@ -283,7 +270,6 @@ describe('PluginsService', () => {
const pluginsService = new PluginsService(mockCoreContext, plugins);
const promise = pluginsService.setup(mockSetupDeps);

jest.runAllTimers(); // load plugin bundles
await flushPromises();
jest.runAllTimers(); // setup plugins

Expand Down

0 comments on commit 9f46398

Please sign in to comment.