DashboardPage.test.tsx 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. import { render, screen } from '@testing-library/react';
  2. import React from 'react';
  3. import { Provider } from 'react-redux';
  4. import { Router } from 'react-router-dom';
  5. import { useEffectOnce } from 'react-use';
  6. import { AutoSizerProps } from 'react-virtualized-auto-sizer';
  7. import { mockToolkitActionCreator } from 'test/core/redux/mocks';
  8. import { createTheme } from '@grafana/data';
  9. import { selectors } from '@grafana/e2e-selectors';
  10. import { locationService, setDataSourceSrv } from '@grafana/runtime';
  11. import { notifyApp } from 'app/core/actions';
  12. import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
  13. import { DashboardInitPhase, DashboardRoutes } from 'app/types';
  14. import { configureStore } from '../../../store/configureStore';
  15. import { Props as LazyLoaderProps } from '../dashgrid/LazyLoader';
  16. import { setDashboardSrv } from '../services/DashboardSrv';
  17. import { DashboardModel } from '../state';
  18. import { Props, UnthemedDashboardPage } from './DashboardPage';
  19. jest.mock('app/features/dashboard/dashgrid/LazyLoader', () => {
  20. const LazyLoader = ({ children, onLoad }: Pick<LazyLoaderProps, 'children' | 'onLoad'>) => {
  21. useEffectOnce(() => {
  22. onLoad?.();
  23. });
  24. return <>{typeof children === 'function' ? children({ isInView: true }) : children}</>;
  25. };
  26. return { LazyLoader };
  27. });
  28. jest.mock('app/features/dashboard/components/DashboardSettings/GeneralSettings', () => {
  29. class GeneralSettings extends React.Component<{}, {}> {
  30. render() {
  31. return <>general settings</>;
  32. }
  33. }
  34. return { GeneralSettings };
  35. });
  36. jest.mock('app/features/query/components/QueryGroup', () => {
  37. return {
  38. QueryGroup: () => null,
  39. };
  40. });
  41. jest.mock('app/core/core', () => ({
  42. appEvents: {
  43. subscribe: () => {
  44. return { unsubscribe: () => {} };
  45. },
  46. },
  47. }));
  48. jest.mock('react-virtualized-auto-sizer', () => {
  49. // The size of the children need to be small enough to be outside the view.
  50. // So it does not trigger the query to be run by the PanelQueryRunner.
  51. return ({ children }: AutoSizerProps) => children({ height: 1, width: 1 });
  52. });
  53. // the mock below gets rid of this warning from recompose:
  54. // Warning: React.createFactory() is deprecated and will be removed in a future major release. Consider using JSX or use React.createElement() directly instead.
  55. jest.mock('@jaegertracing/jaeger-ui-components', () => ({}));
  56. interface ScenarioContext {
  57. dashboard?: DashboardModel | null;
  58. container?: HTMLElement;
  59. mount: (propOverrides?: Partial<Props>) => void;
  60. unmount: () => void;
  61. props: Props;
  62. rerender: (propOverrides?: Partial<Props>) => void;
  63. setup: (fn: () => void) => void;
  64. }
  65. function getTestDashboard(overrides?: any, metaOverrides?: any): DashboardModel {
  66. const data = Object.assign(
  67. {
  68. title: 'My dashboard',
  69. panels: [
  70. {
  71. id: 1,
  72. type: 'timeseries',
  73. title: 'My panel title',
  74. gridPos: { x: 0, y: 0, w: 1, h: 1 },
  75. },
  76. ],
  77. },
  78. overrides
  79. );
  80. const meta = Object.assign({ canSave: true, canEdit: true }, metaOverrides);
  81. return new DashboardModel(data, meta);
  82. }
  83. function dashboardPageScenario(description: string, scenarioFn: (ctx: ScenarioContext) => void) {
  84. describe(description, () => {
  85. let setupFn: () => void;
  86. const ctx: ScenarioContext = {
  87. setup: (fn) => {
  88. setupFn = fn;
  89. },
  90. mount: (propOverrides?: Partial<Props>) => {
  91. const store = configureStore();
  92. const props: Props = {
  93. ...getRouteComponentProps({
  94. match: { params: { slug: 'my-dash', uid: '11' } } as any,
  95. route: { routeName: DashboardRoutes.Normal } as any,
  96. }),
  97. initPhase: DashboardInitPhase.NotStarted,
  98. initError: null,
  99. initDashboard: jest.fn(),
  100. notifyApp: mockToolkitActionCreator(notifyApp),
  101. cleanUpDashboardAndVariables: jest.fn(),
  102. cancelVariables: jest.fn(),
  103. templateVarsChangedInUrl: jest.fn(),
  104. dashboard: null,
  105. theme: createTheme(),
  106. };
  107. Object.assign(props, propOverrides);
  108. ctx.props = props;
  109. ctx.dashboard = props.dashboard;
  110. const { container, rerender, unmount } = render(
  111. <Provider store={store}>
  112. <Router history={locationService.getHistory()}>
  113. <UnthemedDashboardPage {...props} />
  114. </Router>
  115. </Provider>
  116. );
  117. ctx.container = container;
  118. ctx.rerender = (newProps?: Partial<Props>) => {
  119. Object.assign(props, newProps);
  120. rerender(
  121. <Provider store={store}>
  122. <Router history={locationService.getHistory()}>
  123. <UnthemedDashboardPage {...props} />
  124. </Router>
  125. </Provider>
  126. );
  127. };
  128. ctx.unmount = unmount;
  129. },
  130. props: {} as Props,
  131. rerender: () => {},
  132. unmount: () => {},
  133. };
  134. beforeEach(() => {
  135. setupFn();
  136. });
  137. scenarioFn(ctx);
  138. });
  139. }
  140. describe('DashboardPage', () => {
  141. dashboardPageScenario('Given initial state', (ctx) => {
  142. ctx.setup(() => {
  143. ctx.mount();
  144. });
  145. it('Should call initDashboard on mount', () => {
  146. expect(ctx.props.initDashboard).toBeCalledWith({
  147. fixUrl: true,
  148. routeName: 'normal-dashboard',
  149. urlSlug: 'my-dash',
  150. urlUid: '11',
  151. });
  152. });
  153. });
  154. dashboardPageScenario('Given a simple dashboard', (ctx) => {
  155. ctx.setup(() => {
  156. ctx.mount();
  157. ctx.rerender({ dashboard: getTestDashboard() });
  158. });
  159. it('Should render panels', () => {
  160. expect(screen.getByText('My panel title')).toBeInTheDocument();
  161. });
  162. it('Should update title', () => {
  163. expect(document.title).toBe('My dashboard - Grafana');
  164. });
  165. });
  166. dashboardPageScenario('When going into view mode', (ctx) => {
  167. ctx.setup(() => {
  168. setDataSourceSrv({
  169. get: jest.fn().mockResolvedValue({ getRef: jest.fn(), query: jest.fn().mockResolvedValue([]) }),
  170. getInstanceSettings: jest.fn().mockReturnValue({ meta: {} }),
  171. getList: jest.fn(),
  172. reload: jest.fn(),
  173. });
  174. setDashboardSrv({
  175. getCurrent: () => getTestDashboard(),
  176. } as any);
  177. ctx.mount({
  178. dashboard: getTestDashboard(),
  179. queryParams: { viewPanel: '1' },
  180. });
  181. });
  182. it('Should render panel in view mode', () => {
  183. expect(ctx.dashboard?.panelInView).toBeDefined();
  184. expect(ctx.dashboard?.panels[0].isViewing).toBe(true);
  185. });
  186. it('Should reset state when leaving', () => {
  187. ctx.rerender({ queryParams: {} });
  188. expect(ctx.dashboard?.panelInView).toBeUndefined();
  189. expect(ctx.dashboard?.panels[0].isViewing).toBe(false);
  190. });
  191. });
  192. dashboardPageScenario('When going into edit mode', (ctx) => {
  193. ctx.setup(() => {
  194. ctx.mount({
  195. dashboard: getTestDashboard(),
  196. queryParams: { editPanel: '1' },
  197. });
  198. });
  199. it('Should render panel in edit mode', () => {
  200. expect(ctx.dashboard?.panelInEdit).toBeDefined();
  201. });
  202. it('Should render panel editor', () => {
  203. expect(screen.getByTitle('Apply changes and go back to dashboard')).toBeInTheDocument();
  204. });
  205. it('Should reset state when leaving', () => {
  206. ctx.rerender({ queryParams: {} });
  207. expect(screen.queryByTitle('Apply changes and go back to dashboard')).not.toBeInTheDocument();
  208. });
  209. });
  210. dashboardPageScenario('When dashboard unmounts', (ctx) => {
  211. ctx.setup(() => {
  212. ctx.mount();
  213. ctx.rerender({ dashboard: getTestDashboard() });
  214. ctx.unmount();
  215. });
  216. it('Should call close action', () => {
  217. expect(ctx.props.cleanUpDashboardAndVariables).toHaveBeenCalledTimes(1);
  218. });
  219. });
  220. dashboardPageScenario('When dashboard changes', (ctx) => {
  221. ctx.setup(() => {
  222. ctx.mount();
  223. ctx.rerender({ dashboard: getTestDashboard() });
  224. ctx.rerender({
  225. match: {
  226. params: { uid: 'new-uid' },
  227. } as any,
  228. dashboard: getTestDashboard({ title: 'Another dashboard' }),
  229. });
  230. });
  231. it('Should call clean up action and init', () => {
  232. expect(ctx.props.cleanUpDashboardAndVariables).toHaveBeenCalledTimes(1);
  233. expect(ctx.props.initDashboard).toHaveBeenCalledTimes(2);
  234. });
  235. });
  236. dashboardPageScenario('No kiosk mode tv', (ctx) => {
  237. ctx.setup(() => {
  238. ctx.mount({ dashboard: getTestDashboard() });
  239. ctx.rerender({ dashboard: ctx.dashboard });
  240. });
  241. it('should render dashboard page toolbar and submenu', () => {
  242. expect(screen.queryAllByTestId(selectors.pages.Dashboard.DashNav.navV2)).toHaveLength(1);
  243. expect(screen.queryAllByLabelText(selectors.pages.Dashboard.SubMenu.submenu)).toHaveLength(1);
  244. });
  245. });
  246. dashboardPageScenario('When in full kiosk mode', (ctx) => {
  247. ctx.setup(() => {
  248. locationService.partial({ kiosk: true });
  249. ctx.mount({
  250. queryParams: {},
  251. dashboard: getTestDashboard(),
  252. });
  253. ctx.rerender({ dashboard: ctx.dashboard });
  254. });
  255. it('should not render page toolbar and submenu', () => {
  256. expect(screen.queryAllByTestId(selectors.pages.Dashboard.DashNav.navV2)).toHaveLength(0);
  257. expect(screen.queryAllByLabelText(selectors.pages.Dashboard.SubMenu.submenu)).toHaveLength(0);
  258. });
  259. });
  260. });