From 645412f04b4ce84df8864ceb0d3b8880118b6ad6 Mon Sep 17 00:00:00 2001 From: hborchardt <66408901+hborchardt@users.noreply.github.com> Date: Thu, 29 Oct 2020 09:51:25 +0100 Subject: [PATCH] Dashboard: Fix navigation from one SoloPanelPage to another one (#28578) * Add test for SoloPanelPage.tsx * Fix navigation from one SoloPanelPage to another one The panel did not update because it assumed that the dashboard was already fully loaded. --- .../containers/SoloPanelPage.test.tsx | 142 ++++++++++++++++++ .../dashboard/containers/SoloPanelPage.tsx | 8 +- 2 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 public/app/features/dashboard/containers/SoloPanelPage.test.tsx diff --git a/public/app/features/dashboard/containers/SoloPanelPage.test.tsx b/public/app/features/dashboard/containers/SoloPanelPage.test.tsx new file mode 100644 index 00000000000..a050a7d8256 --- /dev/null +++ b/public/app/features/dashboard/containers/SoloPanelPage.test.tsx @@ -0,0 +1,142 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { SoloPanelPage, Props } from './SoloPanelPage'; +import { Props as DashboardPanelProps } from '../dashgrid/DashboardPanel'; +import { DashboardModel } from '../state'; +import { DashboardRouteInfo } from 'app/types'; + +jest.mock('app/features/dashboard/components/DashboardSettings/SettingsCtrl', () => ({})); +jest.mock('app/features/dashboard/dashgrid/DashboardPanel', () => { + class DashboardPanel extends React.Component { + render() { + // In this test we only check whether a new panel has arrived in the props + return <>{this.props.panel?.title}; + } + } + + return { DashboardPanel }; +}); + +interface ScenarioContext { + dashboard?: DashboardModel | null; + secondaryDashboard?: DashboardModel | null; + setDashboard: (overrides?: any, metaOverrides?: any) => void; + setSecondaryDashboard: (overrides?: any, metaOverrides?: any) => void; + mount: (propOverrides?: Partial) => void; + rerender: (propOverrides?: Partial) => void; + setup: (fn: () => void) => void; +} + +function getTestDashboard(overrides?: any, metaOverrides?: any): DashboardModel { + const data = Object.assign( + { + title: 'My dashboard', + panels: [ + { + id: 1, + type: 'graph', + title: 'My graph', + gridPos: { x: 0, y: 0, w: 1, h: 1 }, + }, + ], + }, + overrides + ); + + const meta = Object.assign({ canSave: true, canEdit: true }, metaOverrides); + return new DashboardModel(data, meta); +} + +function soloPanelPageScenario(description: string, scenarioFn: (ctx: ScenarioContext) => void) { + describe(description, () => { + let setupFn: () => void; + + const ctx: ScenarioContext = { + setup: fn => { + setupFn = fn; + }, + setDashboard: (overrides?: any, metaOverrides?: any) => { + ctx.dashboard = getTestDashboard(overrides, metaOverrides); + }, + setSecondaryDashboard: (overrides?: any, metaOverrides?: any) => { + ctx.secondaryDashboard = getTestDashboard(overrides, metaOverrides); + }, + mount: (propOverrides?: Partial) => { + const props: Props = { + urlSlug: 'my-dash', + $scope: {}, + urlUid: '11', + urlPanelId: '1', + $injector: {}, + routeInfo: DashboardRouteInfo.Normal, + initDashboard: jest.fn(), + dashboard: null, + }; + + Object.assign(props, propOverrides); + + ctx.dashboard = props.dashboard; + let { rerender } = render(); + // prop updates will be submitted by rerendering the same component with different props + ctx.rerender = (newProps: Partial) => { + Object.assign(props, newProps); + rerender(); + }; + }, + rerender: () => { + // will be replaced while mount() is called + }, + }; + + beforeEach(() => { + setupFn(); + }); + + scenarioFn(ctx); + }); +} + +describe('SoloPanelPage', () => { + soloPanelPageScenario('Given initial state', ctx => { + ctx.setup(() => { + ctx.mount(); + }); + + it('Should render nothing', () => { + expect(screen.queryByText(/Loading/)).not.toBeNull(); + }); + }); + + soloPanelPageScenario('Dashboard init completed ', ctx => { + ctx.setup(() => { + ctx.mount(); + ctx.setDashboard(); + expect(ctx.dashboard).not.toBeNull(); + // the componentDidMount will change the dashboard prop to the new dashboard + // emulate this by rerendering with new props + ctx.rerender({ dashboard: ctx.dashboard }); + }); + + it('Should render dashboard grid', async () => { + // check if the panel title has arrived in the DashboardPanel mock + expect(screen.queryByText(/My graph/)).not.toBeNull(); + }); + }); + + soloPanelPageScenario('When user navigates to other SoloPanelPage', ctx => { + ctx.setup(() => { + ctx.mount(); + ctx.setDashboard({ uid: 1, panels: [{ id: 1, type: 'graph', title: 'Panel 1' }] }); + ctx.setSecondaryDashboard({ uid: 2, panels: [{ id: 1, type: 'graph', title: 'Panel 2' }] }); + }); + + it('Should show other graph', () => { + // check that the title in the DashboardPanel has changed + ctx.rerender({ dashboard: ctx.dashboard }); + expect(screen.queryByText(/Panel 1/)).not.toBeNull(); + ctx.rerender({ dashboard: ctx.secondaryDashboard }); + expect(screen.queryByText(/Panel 1/)).toBeNull(); + expect(screen.queryByText(/Panel 2/)).not.toBeNull(); + }); + }); +}); diff --git a/public/app/features/dashboard/containers/SoloPanelPage.tsx b/public/app/features/dashboard/containers/SoloPanelPage.tsx index 6fa93e8e04f..7f755ba4987 100644 --- a/public/app/features/dashboard/containers/SoloPanelPage.tsx +++ b/public/app/features/dashboard/containers/SoloPanelPage.tsx @@ -13,7 +13,7 @@ import { initDashboard } from '../state/initDashboard'; import { StoreState, DashboardRouteInfo } from 'app/types'; import { PanelModel, DashboardModel } from 'app/features/dashboard/state'; -interface Props { +export interface Props { urlPanelId: string; urlUid?: string; urlSlug?: string; @@ -25,7 +25,7 @@ interface Props { dashboard: DashboardModel | null; } -interface State { +export interface State { panel: PanelModel | null; notFound: boolean; } @@ -57,8 +57,8 @@ export class SoloPanelPage extends Component { return; } - // we just got the dashboard! - if (!prevProps.dashboard) { + // we just got a new dashboard + if (!prevProps.dashboard || prevProps.dashboard.uid !== dashboard.uid) { const panelId = parseInt(urlPanelId, 10); // need to expand parent row if this panel is inside a row