AnnoListPanel.test.tsx 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. import { render, screen, waitFor } from '@testing-library/react';
  2. import userEvent from '@testing-library/user-event';
  3. import React from 'react';
  4. import { AnnotationEvent, FieldConfigSource, getDefaultTimeRange, LoadingState } from '@grafana/data';
  5. import { locationService } from '@grafana/runtime';
  6. import { silenceConsoleOutput } from '../../../../test/core/utils/silenceConsoleOutput';
  7. import { backendSrv } from '../../../core/services/backend_srv';
  8. import { setDashboardSrv } from '../../../features/dashboard/services/DashboardSrv';
  9. import { AnnoListPanel, Props } from './AnnoListPanel';
  10. import { AnnoOptions } from './types';
  11. jest.mock('@grafana/runtime', () => ({
  12. ...(jest.requireActual('@grafana/runtime') as unknown as object),
  13. getBackendSrv: () => backendSrv,
  14. }));
  15. const defaultOptions: AnnoOptions = {
  16. limit: 10,
  17. navigateAfter: '10m',
  18. navigateBefore: '10m',
  19. navigateToPanel: true,
  20. onlyFromThisDashboard: true,
  21. onlyInTimeRange: false,
  22. showTags: true,
  23. showTime: true,
  24. showUser: true,
  25. tags: ['tag A', 'tag B'],
  26. };
  27. const defaultResult: any = {
  28. text: 'Result text',
  29. userId: 1,
  30. login: 'Result login',
  31. email: 'Result email',
  32. avatarUrl: 'Result avatarUrl',
  33. tags: ['Result tag A', 'Result tag B'],
  34. time: Date.UTC(2021, 0, 1, 0, 0, 0, 0),
  35. panelId: 13,
  36. dashboardId: 14, // deliberately different from panelId
  37. id: 14,
  38. url: '/d/asdkjhajksd/some-dash',
  39. };
  40. async function setupTestContext({
  41. options = defaultOptions,
  42. results = [defaultResult],
  43. }: { options?: AnnoOptions; results?: AnnotationEvent[] } = {}) {
  44. jest.clearAllMocks();
  45. const getMock = jest.spyOn(backendSrv, 'get');
  46. getMock.mockResolvedValue(results);
  47. const dash: any = { id: 1, formatDate: (time: number) => new Date(time).toISOString() };
  48. const dashSrv: any = { getCurrent: () => dash };
  49. setDashboardSrv(dashSrv);
  50. const pushSpy = jest.spyOn(locationService, 'push');
  51. const props: Props = {
  52. data: { state: LoadingState.Done, timeRange: getDefaultTimeRange(), series: [] },
  53. eventBus: {
  54. subscribe: jest.fn(),
  55. getStream: () =>
  56. ({
  57. subscribe: jest.fn(),
  58. } as any),
  59. publish: jest.fn(),
  60. removeAllListeners: jest.fn(),
  61. newScopedBus: jest.fn(),
  62. },
  63. fieldConfig: {} as unknown as FieldConfigSource,
  64. height: 400,
  65. id: 1,
  66. onChangeTimeRange: jest.fn(),
  67. onFieldConfigChange: jest.fn(),
  68. onOptionsChange: jest.fn(),
  69. options,
  70. renderCounter: 1,
  71. replaceVariables: (str: string) => str,
  72. timeRange: getDefaultTimeRange(),
  73. timeZone: 'utc',
  74. title: 'Test Title',
  75. transparent: false,
  76. width: 320,
  77. };
  78. const { rerender } = render(<AnnoListPanel {...props} />);
  79. await waitFor(() => expect(getMock).toHaveBeenCalledTimes(1));
  80. return { props, rerender, getMock, pushSpy };
  81. }
  82. describe('AnnoListPanel', () => {
  83. describe('when mounted', () => {
  84. it('then it should fetch annotations', async () => {
  85. const { getMock } = await setupTestContext();
  86. expect(getMock).toHaveBeenCalledWith(
  87. '/api/annotations',
  88. {
  89. dashboardId: 1,
  90. limit: 10,
  91. tags: ['tag A', 'tag B'],
  92. type: 'annotation',
  93. },
  94. 'anno-list-panel-1'
  95. );
  96. });
  97. });
  98. describe('when there are no annotations', () => {
  99. it('then it should show a no annotations message', async () => {
  100. await setupTestContext({ results: [] });
  101. expect(screen.getByText(/no annotations found/i)).toBeInTheDocument();
  102. });
  103. });
  104. describe('when there are annotations', () => {
  105. it('then it renders the annotations correctly', async () => {
  106. await setupTestContext();
  107. expect(screen.queryByText(/no annotations found/i)).not.toBeInTheDocument();
  108. expect(screen.queryByText(/result email/i)).not.toBeInTheDocument();
  109. expect(screen.getByText(/result text/i)).toBeInTheDocument();
  110. expect(screen.getByRole('img')).toBeInTheDocument();
  111. expect(screen.getByText('Result tag A')).toBeInTheDocument();
  112. expect(screen.getByText('Result tag B')).toBeInTheDocument();
  113. expect(screen.getByText(/2021-01-01T00:00:00.000Z/i)).toBeInTheDocument();
  114. });
  115. describe('and login property is missing in annotation', () => {
  116. it('then it renders the annotations correctly', async () => {
  117. await setupTestContext({ results: [{ ...defaultResult, login: undefined }] });
  118. expect(screen.queryByRole('img')).not.toBeInTheDocument();
  119. expect(screen.getByText(/result text/i)).toBeInTheDocument();
  120. expect(screen.getByText('Result tag A')).toBeInTheDocument();
  121. expect(screen.getByText('Result tag B')).toBeInTheDocument();
  122. expect(screen.getByText(/2021-01-01T00:00:00.000Z/i)).toBeInTheDocument();
  123. });
  124. });
  125. describe('and property is missing in annotation', () => {
  126. it('then it renders the annotations correctly', async () => {
  127. await setupTestContext({ results: [{ ...defaultResult, time: undefined }] });
  128. expect(screen.queryByText(/2021-01-01T00:00:00.000Z/i)).not.toBeInTheDocument();
  129. expect(screen.getByText(/result text/i)).toBeInTheDocument();
  130. expect(screen.getByRole('img')).toBeInTheDocument();
  131. expect(screen.getByText('Result tag A')).toBeInTheDocument();
  132. expect(screen.getByText('Result tag B')).toBeInTheDocument();
  133. });
  134. });
  135. describe('and show user option is off', () => {
  136. it('then it renders the annotations correctly', async () => {
  137. await setupTestContext({
  138. options: { ...defaultOptions, showUser: false },
  139. });
  140. expect(screen.queryByRole('img')).not.toBeInTheDocument();
  141. expect(screen.getByText(/result text/i)).toBeInTheDocument();
  142. expect(screen.getByText('Result tag A')).toBeInTheDocument();
  143. expect(screen.getByText('Result tag B')).toBeInTheDocument();
  144. expect(screen.getByText(/2021-01-01T00:00:00.000Z/i)).toBeInTheDocument();
  145. });
  146. });
  147. describe('and show time option is off', () => {
  148. it('then it renders the annotations correctly', async () => {
  149. await setupTestContext({
  150. options: { ...defaultOptions, showTime: false },
  151. });
  152. expect(screen.queryByText(/2021-01-01T00:00:00.000Z/i)).not.toBeInTheDocument();
  153. expect(screen.getByText(/result text/i)).toBeInTheDocument();
  154. expect(screen.getByRole('img')).toBeInTheDocument();
  155. expect(screen.getByText('Result tag A')).toBeInTheDocument();
  156. expect(screen.getByText('Result tag B')).toBeInTheDocument();
  157. });
  158. });
  159. describe('and show tags option is off', () => {
  160. it('then it renders the annotations correctly', async () => {
  161. await setupTestContext({
  162. options: { ...defaultOptions, showTags: false },
  163. });
  164. expect(screen.queryByText('Result tag A')).not.toBeInTheDocument();
  165. expect(screen.queryByText('Result tag B')).not.toBeInTheDocument();
  166. expect(screen.getByText(/result text/i)).toBeInTheDocument();
  167. expect(screen.getByRole('img')).toBeInTheDocument();
  168. expect(screen.getByText(/2021-01-01T00:00:00.000Z/i)).toBeInTheDocument();
  169. });
  170. });
  171. describe('and the user clicks on the annotation', () => {
  172. it('then it should navigate to the dashboard connected to the annotation', async () => {
  173. const { getMock, pushSpy } = await setupTestContext();
  174. getMock.mockClear();
  175. expect(screen.getByText(/result text/i)).toBeInTheDocument();
  176. await userEvent.click(screen.getByText(/result text/i));
  177. await waitFor(() => expect(getMock).toHaveBeenCalledTimes(1));
  178. expect(getMock).toHaveBeenCalledWith('/api/search', { dashboardIds: 14 });
  179. expect(pushSpy).toHaveBeenCalledTimes(1);
  180. expect(pushSpy).toHaveBeenCalledWith('/d/asdkjhajksd/some-dash?from=1609458600000&to=1609459800000');
  181. });
  182. });
  183. describe('and the user clicks on a tag', () => {
  184. it('then it should navigate to the dashboard connected to the annotation', async () => {
  185. const { getMock } = await setupTestContext();
  186. getMock.mockClear();
  187. expect(screen.getByText('Result tag B')).toBeInTheDocument();
  188. await userEvent.click(screen.getByText('Result tag B'));
  189. expect(getMock).toHaveBeenCalledTimes(1);
  190. expect(getMock).toHaveBeenCalledWith(
  191. '/api/annotations',
  192. {
  193. dashboardId: 1,
  194. limit: 10,
  195. tags: ['tag A', 'tag B', 'Result tag B'],
  196. type: 'annotation',
  197. },
  198. 'anno-list-panel-1'
  199. );
  200. expect(screen.getByText(/filter:/i)).toBeInTheDocument();
  201. expect(screen.getAllByText(/result tag b/i)).toHaveLength(2);
  202. });
  203. });
  204. describe('and the user clicks on the user avatar', () => {
  205. it('then it should filter annotations by login and the filter should show', async () => {
  206. const { getMock } = await setupTestContext();
  207. getMock.mockClear();
  208. expect(screen.getByRole('img')).toBeInTheDocument();
  209. await userEvent.click(screen.getByRole('img'));
  210. expect(getMock).toHaveBeenCalledTimes(1);
  211. expect(getMock).toHaveBeenCalledWith(
  212. '/api/annotations',
  213. {
  214. dashboardId: 1,
  215. limit: 10,
  216. tags: ['tag A', 'tag B'],
  217. type: 'annotation',
  218. userId: 1,
  219. },
  220. 'anno-list-panel-1'
  221. );
  222. expect(screen.getByText(/filter:/i)).toBeInTheDocument();
  223. expect(screen.getByText(/result email/i)).toBeInTheDocument();
  224. });
  225. });
  226. describe('and the user hovers over the user avatar', () => {
  227. silenceConsoleOutput(); // Popper throws an act error, but if we add act around the hover here it doesn't matter
  228. it('then it should filter annotations by login', async () => {
  229. const { getMock } = await setupTestContext();
  230. getMock.mockClear();
  231. expect(screen.getByRole('img')).toBeInTheDocument();
  232. });
  233. });
  234. });
  235. });