import { act, render, screen } from '@testing-library/react'; import React, { Component } from 'react'; import { StoreState } from 'app/types'; import { Provider } from 'react-redux'; import configureStore from 'redux-mock-store'; import AppRootPage from './AppRootPage'; import { getPluginSettings } from './PluginSettingsCache'; import { importAppPlugin } from './plugin_loader'; import { getMockPlugin } from './__mocks__/pluginMocks'; import { AppPlugin, PluginType, AppRootProps, NavModelItem } from '@grafana/data'; import { updateLocation } from 'app/core/actions'; import { createRootReducer } from 'app/core/reducers/root'; import { createStore } from 'redux'; jest.mock('./PluginSettingsCache', () => ({ getPluginSettings: jest.fn(), })); jest.mock('./plugin_loader', () => ({ importAppPlugin: jest.fn(), })); const importAppPluginMock = importAppPlugin as jest.Mock< ReturnType, Parameters >; const getPluginSettingsMock = getPluginSettings as jest.Mock< ReturnType, Parameters >; const initialState: Partial = { location: { routeParams: { pluginId: 'my-awesome-plugin', slug: 'my-awesome-plugin', }, query: {}, path: '/a/my-awesome-plugin', url: '', replace: false, lastUpdated: 1, }, }; function renderWithStore(soreState: Partial = initialState) { const store = configureStore()(soreState as StoreState); render( ); return store; } describe('AppRootPage', () => { beforeEach(() => { jest.resetAllMocks(); }); it('should not mount plugin twice if nav is changed', async () => { // reproduces https://github.com/grafana/grafana/pull/28105 getPluginSettingsMock.mockResolvedValue( getMockPlugin({ type: PluginType.app, enabled: true, }) ); let timesMounted = 0; // a very basic component that does what most plugins do: // immediately update nav on mounting class RootComponent extends Component { componentDidMount() { timesMounted++; const node: NavModelItem = { text: 'My Great plugin', children: [ { text: 'A page', url: '/apage', id: 'a', }, { text: 'Another page', url: '/anotherpage', id: 'b', }, ], }; this.props.onNavChanged({ main: node, node, }); } render() { return

my great plugin

; } } const plugin = new AppPlugin(); plugin.root = RootComponent; importAppPluginMock.mockResolvedValue(plugin); renderWithStore(); // check that plugin and nav links were rendered, and plugin is mounted only once await screen.findByText('my great plugin'); await screen.findAllByRole('link', { name: /A page/ }); await screen.findAllByRole('link', { name: /Another page/ }); expect(timesMounted).toEqual(1); }); it('should not render component if not at plugin path', async () => { getPluginSettingsMock.mockResolvedValue( getMockPlugin({ type: PluginType.app, enabled: true, }) ); let timesRendered = 0; class RootComponent extends Component { render() { timesRendered += 1; return

my great component

; } } const plugin = new AppPlugin(); plugin.root = RootComponent; importAppPluginMock.mockResolvedValue(plugin); const store = createStore(createRootReducer()); store.dispatch(updateLocation({ path: '/a/foo' })); render( ); await screen.findByText('my great component'); // renders the first time expect(timesRendered).toEqual(1); await act(async () => { await store.dispatch( updateLocation({ path: '/foo', }) ); }); expect(timesRendered).toEqual(1); await act(async () => { await store.dispatch( updateLocation({ path: '/a/foo', }) ); }); expect(timesRendered).toEqual(2); }); });