mirror of https://github.com/grafana/grafana.git
313 lines
9.8 KiB
TypeScript
313 lines
9.8 KiB
TypeScript
import { act, render, screen, waitFor } from '@testing-library/react';
|
|
import { renderHook } from '@testing-library/react-hooks';
|
|
|
|
import { PluginContextProvider, PluginLoadingStrategy, PluginMeta, PluginType } from '@grafana/data';
|
|
import { config } from '@grafana/runtime';
|
|
|
|
import { ExtensionRegistriesProvider } from './ExtensionRegistriesContext';
|
|
import { setupPluginExtensionRegistries } from './registry/setup';
|
|
import { PluginExtensionRegistries } from './registry/types';
|
|
import { usePluginComponent } from './usePluginComponent';
|
|
import { isGrafanaDevMode, wrapWithPluginContext } from './utils';
|
|
|
|
jest.mock('app/features/plugins/pluginSettings', () => ({
|
|
getPluginSettings: jest.fn().mockResolvedValue({
|
|
id: 'my-app-plugin',
|
|
enabled: true,
|
|
jsonData: {},
|
|
type: 'panel',
|
|
name: 'My App Plugin',
|
|
module: 'app/plugins/my-app-plugin/module',
|
|
}),
|
|
}));
|
|
|
|
jest.mock('./utils', () => ({
|
|
...jest.requireActual('./utils'),
|
|
|
|
// Manually set the dev mode to false
|
|
// (to make sure that by default we are testing a production scneario)
|
|
isGrafanaDevMode: jest.fn().mockReturnValue(false),
|
|
wrapWithPluginContext: jest.fn().mockImplementation((_, component: React.ReactNode) => component),
|
|
}));
|
|
|
|
describe('usePluginComponent()', () => {
|
|
let registries: PluginExtensionRegistries;
|
|
let wrapper: ({ children }: { children: React.ReactNode }) => JSX.Element;
|
|
let pluginMeta: PluginMeta;
|
|
let consoleWarnSpy: jest.SpyInstance;
|
|
const originalApps = config.apps;
|
|
const pluginId = 'myorg-extensions-app';
|
|
const exposedComponentId = `${pluginId}/exposed-component/v1`;
|
|
const exposedComponentConfig = {
|
|
id: exposedComponentId,
|
|
title: 'Exposed component',
|
|
description: 'Exposed component description',
|
|
component: () => <div>Hello World</div>,
|
|
};
|
|
const appPluginConfig = {
|
|
id: pluginId,
|
|
path: '',
|
|
version: '',
|
|
preload: false,
|
|
angular: {
|
|
detected: false,
|
|
hideDeprecation: false,
|
|
},
|
|
loadingStrategy: PluginLoadingStrategy.fetch,
|
|
dependencies: {
|
|
grafanaVersion: '8.0.0',
|
|
plugins: [],
|
|
extensions: {
|
|
exposedComponents: [],
|
|
},
|
|
},
|
|
extensions: {
|
|
addedLinks: [],
|
|
addedComponents: [],
|
|
// This is necessary, so we can register exposed components to the registry during the tests
|
|
// (Otherwise the registry would reject it in the imitated production mode)
|
|
exposedComponents: [exposedComponentConfig],
|
|
extensionPoints: [],
|
|
},
|
|
};
|
|
|
|
beforeEach(() => {
|
|
registries = setupPluginExtensionRegistries();
|
|
jest.mocked(isGrafanaDevMode).mockReturnValue(false);
|
|
consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
|
|
|
jest.mocked(wrapWithPluginContext).mockClear();
|
|
|
|
pluginMeta = {
|
|
id: pluginId,
|
|
name: 'Extensions App',
|
|
type: PluginType.app,
|
|
module: '',
|
|
baseUrl: '',
|
|
info: {
|
|
author: {
|
|
name: 'MyOrg',
|
|
},
|
|
description: 'App for testing extensions',
|
|
links: [],
|
|
logos: {
|
|
large: '',
|
|
small: '',
|
|
},
|
|
screenshots: [],
|
|
updated: '2023-10-26T18:25:01Z',
|
|
version: '1.0.0',
|
|
},
|
|
extensions: {
|
|
addedLinks: [],
|
|
addedComponents: [],
|
|
exposedComponents: [],
|
|
extensionPoints: [],
|
|
},
|
|
dependencies: {
|
|
grafanaVersion: '8.0.0',
|
|
plugins: [],
|
|
extensions: {
|
|
exposedComponents: [],
|
|
},
|
|
},
|
|
};
|
|
|
|
config.apps = {
|
|
[pluginId]: appPluginConfig,
|
|
};
|
|
|
|
wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
<ExtensionRegistriesProvider registries={registries}>{children}</ExtensionRegistriesProvider>
|
|
);
|
|
});
|
|
|
|
afterEach(() => {
|
|
config.apps = originalApps;
|
|
});
|
|
|
|
it('should return null if there are no component exposed for the id', () => {
|
|
const { result } = renderHook(() => usePluginComponent('foo/bar'), { wrapper });
|
|
|
|
expect(result.current.component).toEqual(null);
|
|
expect(result.current.isLoading).toEqual(false);
|
|
});
|
|
|
|
it('should return component, that can be rendered, from the registry', async () => {
|
|
registries.exposedComponentsRegistry.register({
|
|
pluginId,
|
|
configs: [exposedComponentConfig],
|
|
});
|
|
|
|
const { result } = renderHook(() => usePluginComponent(exposedComponentId), { wrapper });
|
|
const Component = result.current.component;
|
|
|
|
act(() => {
|
|
render(Component && <Component />);
|
|
});
|
|
|
|
expect(result.current.isLoading).toEqual(false);
|
|
expect(result.current.component).not.toBeNull();
|
|
expect(await screen.findByText('Hello World')).toBeVisible();
|
|
});
|
|
|
|
it('should dynamically update when component is registered to the registry', async () => {
|
|
const { result, rerender } = renderHook(() => usePluginComponent(exposedComponentId), { wrapper });
|
|
|
|
// No extensions yet
|
|
expect(result.current.component).toBeNull();
|
|
expect(result.current.isLoading).toEqual(false);
|
|
|
|
// Add extensions to the registry
|
|
act(() => {
|
|
registries.exposedComponentsRegistry.register({
|
|
pluginId,
|
|
configs: [exposedComponentConfig],
|
|
});
|
|
});
|
|
|
|
// Check if the hook returns the new extensions
|
|
rerender();
|
|
|
|
const Component = result.current.component;
|
|
expect(result.current.isLoading).toEqual(false);
|
|
expect(result.current.component).not.toBeNull();
|
|
|
|
act(() => {
|
|
render(Component && <Component />);
|
|
});
|
|
|
|
expect(await screen.findByText('Hello World')).toBeVisible();
|
|
});
|
|
|
|
it('should only render the hook once', async () => {
|
|
// Add extensions to the registry
|
|
act(() => {
|
|
registries.exposedComponentsRegistry.register({
|
|
pluginId,
|
|
configs: [exposedComponentConfig],
|
|
});
|
|
});
|
|
|
|
expect(wrapWithPluginContext).toHaveBeenCalledTimes(0);
|
|
renderHook(() => usePluginComponent(exposedComponentId), { wrapper });
|
|
await waitFor(() => expect(wrapWithPluginContext).toHaveBeenCalledTimes(1));
|
|
});
|
|
|
|
it('should not validate the meta-info in production mode', () => {
|
|
// Empty list of exposed component ids in the plugin meta (from plugin.json)
|
|
wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
<PluginContextProvider
|
|
meta={{
|
|
...pluginMeta,
|
|
dependencies: {
|
|
...pluginMeta.dependencies!,
|
|
extensions: {
|
|
exposedComponents: [],
|
|
},
|
|
},
|
|
}}
|
|
>
|
|
<ExtensionRegistriesProvider registries={registries}>{children}</ExtensionRegistriesProvider>
|
|
</PluginContextProvider>
|
|
);
|
|
|
|
registries.exposedComponentsRegistry.register({
|
|
pluginId,
|
|
configs: [exposedComponentConfig],
|
|
});
|
|
|
|
// Trying to render an exposed component that is not defined in the plugin meta
|
|
// (No restrictions due to isGrafanaDevMode() = false)
|
|
let { result } = renderHook(() => usePluginComponent(exposedComponentId), { wrapper });
|
|
expect(result.current.component).not.toBe(null);
|
|
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not validate the meta-info in core Grafana', () => {
|
|
// Imitate running in dev mode
|
|
jest.mocked(isGrafanaDevMode).mockReturnValue(true);
|
|
|
|
// No plugin context -> used in Grafana core
|
|
wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
<ExtensionRegistriesProvider registries={registries}>{children}</ExtensionRegistriesProvider>
|
|
);
|
|
|
|
registries.exposedComponentsRegistry.register({
|
|
pluginId,
|
|
configs: [exposedComponentConfig],
|
|
});
|
|
|
|
// Trying to render an extension point that is not defined in the plugin meta
|
|
// (No restrictions due to isGrafanaDevMode() = false)
|
|
let { result } = renderHook(() => usePluginComponent(exposedComponentId), {
|
|
wrapper,
|
|
});
|
|
|
|
expect(result.current.component).not.toBe(null);
|
|
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should validate the meta-info in dev mode and if inside a plugin', () => {
|
|
// Imitate running in dev mode
|
|
jest.mocked(isGrafanaDevMode).mockReturnValue(true);
|
|
|
|
// Empty list of exposed component ids in the plugin meta (from plugin.json)
|
|
wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
<PluginContextProvider
|
|
meta={{
|
|
...pluginMeta,
|
|
dependencies: {
|
|
...pluginMeta.dependencies!,
|
|
extensions: {
|
|
exposedComponents: [],
|
|
},
|
|
},
|
|
}}
|
|
>
|
|
<ExtensionRegistriesProvider registries={registries}>{children}</ExtensionRegistriesProvider>
|
|
</PluginContextProvider>
|
|
);
|
|
|
|
registries.exposedComponentsRegistry.register({
|
|
pluginId,
|
|
configs: [exposedComponentConfig],
|
|
});
|
|
|
|
// Shouldn't return the component, as it's not present in the plugin.json dependencies
|
|
let { result } = renderHook(() => usePluginComponent(exposedComponentId), { wrapper });
|
|
expect(result.current.component).toBe(null);
|
|
expect(consoleWarnSpy).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should return the exposed component if the meta-info is correct and in dev mode', () => {
|
|
// Imitate running in dev mode
|
|
jest.mocked(isGrafanaDevMode).mockReturnValue(true);
|
|
|
|
wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
<PluginContextProvider
|
|
meta={{
|
|
...pluginMeta,
|
|
dependencies: {
|
|
...pluginMeta.dependencies!,
|
|
extensions: {
|
|
exposedComponents: [exposedComponentId],
|
|
},
|
|
},
|
|
}}
|
|
>
|
|
<ExtensionRegistriesProvider registries={registries}>{children}</ExtensionRegistriesProvider>
|
|
</PluginContextProvider>
|
|
);
|
|
|
|
registries.exposedComponentsRegistry.register({
|
|
pluginId,
|
|
configs: [exposedComponentConfig],
|
|
});
|
|
|
|
let { result } = renderHook(() => usePluginComponent(exposedComponentId), { wrapper });
|
|
expect(result.current.component).not.toBe(null);
|
|
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
|
});
|
|
});
|