index.test.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. import { act, render, screen, waitFor } from '@testing-library/react';
  2. import userEvent from '@testing-library/user-event';
  3. import React, { ReactNode } from 'react';
  4. import { Provider } from 'react-redux';
  5. import { DataQuery } from '@grafana/data';
  6. import { locationService, setEchoSrv } from '@grafana/runtime';
  7. import { backendSrv } from 'app/core/services/backend_srv';
  8. import { Echo } from 'app/core/services/echo/Echo';
  9. import * as initDashboard from 'app/features/dashboard/state/initDashboard';
  10. import { DashboardSearchItemType } from 'app/features/search/types';
  11. import { configureStore } from 'app/store/configureStore';
  12. import { ExploreId, ExploreState } from 'app/types';
  13. import { createEmptyQueryResponse } from '../state/utils';
  14. import * as api from './addToDashboard';
  15. import { AddToDashboard } from '.';
  16. const setup = (children: ReactNode, queries: DataQuery[] = [{ refId: 'A' }]) => {
  17. const store = configureStore({
  18. explore: {
  19. left: {
  20. queries,
  21. queryResponse: createEmptyQueryResponse(),
  22. },
  23. } as ExploreState,
  24. });
  25. return render(<Provider store={store}>{children}</Provider>);
  26. };
  27. const openModal = async () => {
  28. await userEvent.click(screen.getByRole('button', { name: /add to dashboard/i }));
  29. expect(await screen.findByRole('dialog', { name: 'Add panel to dashboard' })).toBeInTheDocument();
  30. };
  31. describe('AddToDashboardButton', () => {
  32. beforeAll(() => {
  33. setEchoSrv(new Echo());
  34. });
  35. it('Is disabled if explore pane has no queries', async () => {
  36. setup(<AddToDashboard exploreId={ExploreId.left} />, []);
  37. const button = await screen.findByRole('button', { name: /add to dashboard/i });
  38. expect(button).toBeDisabled();
  39. await userEvent.click(button);
  40. expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
  41. });
  42. describe('Success path', () => {
  43. const addToDashboardResponse = Promise.resolve();
  44. const waitForAddToDashboardResponse = async () => {
  45. return act(async () => {
  46. await addToDashboardResponse;
  47. });
  48. };
  49. beforeEach(() => {
  50. jest.spyOn(api, 'setDashboardInLocalStorage').mockReturnValue(addToDashboardResponse);
  51. });
  52. afterEach(() => {
  53. jest.restoreAllMocks();
  54. });
  55. it('Opens and closes the modal correctly', async () => {
  56. setup(<AddToDashboard exploreId={ExploreId.left} />);
  57. await openModal();
  58. await userEvent.click(screen.getByRole('button', { name: /cancel/i }));
  59. expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
  60. });
  61. describe('navigation', () => {
  62. it('Navigates to dashboard when clicking on "Open"', async () => {
  63. // @ts-expect-error global.open should return a Window, but is not implemented in js-dom.
  64. const openSpy = jest.spyOn(global, 'open').mockReturnValue(true);
  65. const pushSpy = jest.spyOn(locationService, 'push');
  66. setup(<AddToDashboard exploreId={ExploreId.left} />);
  67. await openModal();
  68. await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i }));
  69. await waitForAddToDashboardResponse();
  70. expect(screen.queryByRole('dialog', { name: 'Add panel to dashboard' })).not.toBeInTheDocument();
  71. expect(pushSpy).toHaveBeenCalled();
  72. expect(openSpy).not.toHaveBeenCalled();
  73. });
  74. it('Navigates to dashboard in a new tab when clicking on "Open in a new tab"', async () => {
  75. // @ts-expect-error global.open should return a Window, but is not implemented in js-dom.
  76. const openSpy = jest.spyOn(global, 'open').mockReturnValue(true);
  77. const pushSpy = jest.spyOn(locationService, 'push');
  78. setup(<AddToDashboard exploreId={ExploreId.left} />);
  79. await openModal();
  80. await userEvent.click(screen.getByRole('button', { name: /open in new tab/i }));
  81. await waitForAddToDashboardResponse();
  82. expect(openSpy).toHaveBeenCalledWith(expect.anything(), '_blank');
  83. expect(pushSpy).not.toHaveBeenCalled();
  84. });
  85. });
  86. describe('Save to new dashboard', () => {
  87. describe('Navigate to correct dashboard when saving', () => {
  88. it('Opens the new dashboard in a new tab', async () => {
  89. // @ts-expect-error global.open should return a Window, but is not implemented in js-dom.
  90. const openSpy = jest.spyOn(global, 'open').mockReturnValue(true);
  91. setup(<AddToDashboard exploreId={ExploreId.left} />);
  92. await openModal();
  93. await userEvent.click(screen.getByRole('button', { name: /open in new tab/i }));
  94. await waitForAddToDashboardResponse();
  95. expect(openSpy).toHaveBeenCalledWith('dashboard/new', '_blank');
  96. });
  97. it('Navigates to the new dashboard', async () => {
  98. const pushSpy = jest.spyOn(locationService, 'push');
  99. setup(<AddToDashboard exploreId={ExploreId.left} />);
  100. await openModal();
  101. await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i }));
  102. await waitForAddToDashboardResponse();
  103. expect(screen.queryByRole('dialog', { name: 'Add panel to dashboard' })).not.toBeInTheDocument();
  104. expect(pushSpy).toHaveBeenCalledWith('dashboard/new');
  105. });
  106. });
  107. });
  108. describe('Save to existing dashboard', () => {
  109. it('Renders the dashboard picker when switching to "Existing Dashboard"', async () => {
  110. setup(<AddToDashboard exploreId={ExploreId.left} />);
  111. await openModal();
  112. expect(screen.queryByRole('combobox', { name: /dashboard/ })).not.toBeInTheDocument();
  113. await userEvent.click(screen.getByRole<HTMLInputElement>('radio', { name: /existing dashboard/i }));
  114. expect(screen.getByRole('combobox', { name: /dashboard/ })).toBeInTheDocument();
  115. });
  116. it('Does not submit if no dashboard is selected', async () => {
  117. locationService.push = jest.fn();
  118. setup(<AddToDashboard exploreId={ExploreId.left} />);
  119. await openModal();
  120. await userEvent.click(screen.getByRole<HTMLInputElement>('radio', { name: /existing dashboard/i }));
  121. await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i }));
  122. await waitForAddToDashboardResponse();
  123. expect(locationService.push).not.toHaveBeenCalled();
  124. });
  125. describe('Navigate to correct dashboard when saving', () => {
  126. it('Opens the selected dashboard in a new tab', async () => {
  127. // @ts-expect-error global.open should return a Window, but is not implemented in js-dom.
  128. const openSpy = jest.spyOn(global, 'open').mockReturnValue(true);
  129. jest.spyOn(backendSrv, 'getDashboardByUid').mockResolvedValue({
  130. dashboard: { templating: { list: [] }, title: 'Dashboard Title', uid: 'someUid' },
  131. meta: {},
  132. });
  133. jest.spyOn(backendSrv, 'search').mockResolvedValue([
  134. {
  135. id: 1,
  136. uid: 'someUid',
  137. isStarred: false,
  138. items: [],
  139. title: 'Dashboard Title',
  140. tags: [],
  141. type: DashboardSearchItemType.DashDB,
  142. uri: 'someUri',
  143. url: 'someUrl',
  144. },
  145. ]);
  146. setup(<AddToDashboard exploreId={ExploreId.left} />);
  147. await openModal();
  148. await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i }));
  149. await userEvent.click(screen.getByRole('combobox', { name: /dashboard/i }));
  150. await waitFor(async () => {
  151. await screen.findByLabelText('Select option');
  152. });
  153. await userEvent.click(screen.getByLabelText('Select option'));
  154. await userEvent.click(screen.getByRole('button', { name: /open in new tab/i }));
  155. await waitFor(async () => {
  156. expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
  157. });
  158. expect(openSpy).toBeCalledWith('d/someUid', '_blank');
  159. });
  160. it('Navigates to the selected dashboard', async () => {
  161. const pushSpy = jest.spyOn(locationService, 'push');
  162. jest.spyOn(backendSrv, 'getDashboardByUid').mockResolvedValue({
  163. dashboard: { templating: { list: [] }, title: 'Dashboard Title', uid: 'someUid' },
  164. meta: {},
  165. });
  166. jest.spyOn(backendSrv, 'search').mockResolvedValue([
  167. {
  168. id: 1,
  169. uid: 'someUid',
  170. isStarred: false,
  171. items: [],
  172. title: 'Dashboard Title',
  173. tags: [],
  174. type: DashboardSearchItemType.DashDB,
  175. uri: 'someUri',
  176. url: 'someUrl',
  177. },
  178. ]);
  179. setup(<AddToDashboard exploreId={ExploreId.left} />);
  180. await openModal();
  181. await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i }));
  182. await userEvent.click(screen.getByRole('combobox', { name: /dashboard/i }));
  183. await waitFor(async () => {
  184. await screen.findByLabelText('Select option');
  185. });
  186. await userEvent.click(screen.getByLabelText('Select option'));
  187. await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i }));
  188. await waitFor(async () => {
  189. expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
  190. });
  191. expect(pushSpy).toBeCalledWith('d/someUid');
  192. });
  193. });
  194. });
  195. });
  196. describe('Error handling', () => {
  197. afterEach(() => {
  198. jest.restoreAllMocks();
  199. });
  200. it('Shows an error if opening a new tab fails', async () => {
  201. jest.spyOn(global, 'open').mockReturnValue(null);
  202. const removeDashboardSpy = jest.spyOn(initDashboard, 'removeDashboardToFetchFromLocalStorage');
  203. setup(<AddToDashboard exploreId={ExploreId.left} />);
  204. await openModal();
  205. expect(screen.queryByRole('alert')).not.toBeInTheDocument();
  206. await userEvent.click(screen.getByRole('button', { name: /open in new tab/i }));
  207. await waitFor(async () => {
  208. expect(await screen.findByRole('alert')).toBeInTheDocument();
  209. });
  210. expect(removeDashboardSpy).toHaveBeenCalled();
  211. });
  212. it('Shows an error if saving to localStorage fails', async () => {
  213. jest.spyOn(initDashboard, 'setDashboardToFetchFromLocalStorage').mockImplementation(() => {
  214. throw 'SOME ERROR';
  215. });
  216. setup(<AddToDashboard exploreId={ExploreId.left} />);
  217. await openModal();
  218. expect(screen.queryByRole('alert')).not.toBeInTheDocument();
  219. await userEvent.click(screen.getByRole('button', { name: /open in new tab/i }));
  220. await waitFor(async () => {
  221. expect(await screen.findByRole('alert')).toBeInTheDocument();
  222. });
  223. });
  224. it('Shows an error if fetching dashboard fails', async () => {
  225. jest.spyOn(backendSrv, 'getDashboardByUid').mockRejectedValue('SOME ERROR');
  226. jest.spyOn(backendSrv, 'search').mockResolvedValue([
  227. {
  228. id: 1,
  229. uid: 'someUid',
  230. isStarred: false,
  231. items: [],
  232. title: 'Dashboard Title',
  233. tags: [],
  234. type: DashboardSearchItemType.DashDB,
  235. uri: 'someUri',
  236. url: 'someUrl',
  237. },
  238. ]);
  239. setup(<AddToDashboard exploreId={ExploreId.left} />);
  240. await openModal();
  241. expect(screen.queryByRole('alert')).not.toBeInTheDocument();
  242. await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i }));
  243. await userEvent.click(screen.getByRole('combobox', { name: /dashboard/i }));
  244. await waitFor(async () => {
  245. await screen.findByLabelText('Select option');
  246. });
  247. await userEvent.click(screen.getByLabelText('Select option'));
  248. await userEvent.click(screen.getByRole('button', { name: /open in new tab/i }));
  249. await waitFor(async () => {
  250. expect(await screen.findByRole('alert')).toBeInTheDocument();
  251. });
  252. });
  253. it('Shows an error if an unknown error happens', async () => {
  254. jest.spyOn(api, 'setDashboardInLocalStorage').mockRejectedValue('SOME ERROR');
  255. setup(<AddToDashboard exploreId={ExploreId.left} />);
  256. await openModal();
  257. expect(screen.queryByRole('alert')).not.toBeInTheDocument();
  258. await userEvent.click(screen.getByRole('button', { name: /open in new tab/i }));
  259. await waitFor(async () => {
  260. expect(await screen.findByRole('alert')).toBeInTheDocument();
  261. });
  262. });
  263. });
  264. });