index.spec.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. import {ProjectFixture} from 'sentry-fixture/project';
  2. import {ReplayListFixture} from 'sentry-fixture/replayList';
  3. import {initializeOrg} from 'sentry-test/initializeOrg';
  4. import {render, screen, waitFor} from 'sentry-test/reactTestingLibrary';
  5. import {resetMockDate, setMockDate} from 'sentry-test/utils';
  6. import ProjectsStore from 'sentry/stores/projectsStore';
  7. import {
  8. SPAN_OP_BREAKDOWN_FIELDS,
  9. SPAN_OP_RELATIVE_BREAKDOWN_FIELD,
  10. } from 'sentry/utils/discover/fields';
  11. import TransactionReplays from 'sentry/views/performance/transactionSummary/transactionReplays';
  12. type InitializeOrgProps = {
  13. location?: {
  14. pathname?: string;
  15. query?: {[key: string]: string};
  16. };
  17. organizationProps?: {
  18. features?: string[];
  19. };
  20. };
  21. jest.mock('sentry/utils/useMedia', () => ({
  22. __esModule: true,
  23. default: jest.fn(() => true),
  24. }));
  25. const mockEventsUrl = '/organizations/org-slug/events/';
  26. const mockReplaysUrl = '/organizations/org-slug/replays/';
  27. const renderComponent = ({
  28. location,
  29. organizationProps = {features: ['performance-view', 'session-replay']},
  30. }: InitializeOrgProps = {}) => {
  31. const {organization, projects, router} = initializeOrg({
  32. organization: {
  33. ...organizationProps,
  34. },
  35. projects: [ProjectFixture()],
  36. router: {
  37. routes: [
  38. {path: '/'},
  39. {path: '/organizations/:orgId/performance/summary/'},
  40. {path: 'replays/'},
  41. ],
  42. location: {
  43. pathname: '/organizations/org-slug/replays/',
  44. ...location,
  45. query: {
  46. project: '1',
  47. transaction: 'Settings Page',
  48. ...location?.query,
  49. },
  50. },
  51. },
  52. });
  53. ProjectsStore.init();
  54. ProjectsStore.loadInitialData(projects);
  55. return render(<TransactionReplays />, {router, organization});
  56. };
  57. describe('TransactionReplays', () => {
  58. let eventsMockApi: jest.Mock<any, any>;
  59. let replaysMockApi: jest.Mock<any, any>;
  60. beforeEach(() => {
  61. MockApiClient.addMockResponse({
  62. method: 'GET',
  63. url: `/organizations/org-slug/sdk-updates/`,
  64. body: [],
  65. });
  66. MockApiClient.addMockResponse({
  67. url: '/organizations/org-slug/events-has-measurements/',
  68. body: {measurements: false},
  69. });
  70. MockApiClient.addMockResponse({
  71. url: '/organizations/org-slug/replay-count/',
  72. body: {
  73. data: [],
  74. },
  75. statusCode: 200,
  76. });
  77. eventsMockApi = MockApiClient.addMockResponse({
  78. url: '/organizations/org-slug/events/',
  79. body: {
  80. data: [],
  81. },
  82. statusCode: 200,
  83. });
  84. replaysMockApi = MockApiClient.addMockResponse({
  85. url: '/organizations/org-slug/replays/',
  86. body: {
  87. data: [],
  88. },
  89. statusCode: 200,
  90. });
  91. });
  92. afterEach(() => {
  93. MockApiClient.clearMockResponses();
  94. resetMockDate();
  95. });
  96. it('should query the events endpoint for replayIds of a transaction', async () => {
  97. renderComponent();
  98. await waitFor(() => {
  99. expect(eventsMockApi).toHaveBeenCalledWith(
  100. '/organizations/org-slug/events/',
  101. expect.objectContaining({
  102. query: expect.objectContaining({
  103. cursor: undefined,
  104. statsPeriod: '14d',
  105. project: ['1'],
  106. environment: [],
  107. field: expect.arrayContaining([
  108. 'replayId',
  109. 'count()',
  110. 'transaction.duration',
  111. 'trace',
  112. 'timestamp',
  113. ...SPAN_OP_BREAKDOWN_FIELDS,
  114. SPAN_OP_RELATIVE_BREAKDOWN_FIELD,
  115. ]),
  116. per_page: 50,
  117. query: 'event.type:transaction transaction:"Settings Page" !replayId:""',
  118. }),
  119. })
  120. );
  121. });
  122. });
  123. it('should snapshot empty state', async () => {
  124. const mockApi = MockApiClient.addMockResponse({
  125. url: mockReplaysUrl,
  126. body: {
  127. data: [],
  128. },
  129. statusCode: 200,
  130. });
  131. renderComponent();
  132. await waitFor(() => {
  133. expect(mockApi).toHaveBeenCalledTimes(1);
  134. });
  135. });
  136. it('should show empty message when no replays are found', async () => {
  137. renderComponent();
  138. await waitFor(() => {
  139. expect(replaysMockApi).toHaveBeenCalledTimes(1);
  140. expect(screen.getByText('There are no items to display')).toBeInTheDocument();
  141. });
  142. });
  143. it('should show loading indicator when loading replays', async () => {
  144. const mockApi = MockApiClient.addMockResponse({
  145. url: mockEventsUrl,
  146. statusCode: 200,
  147. body: {
  148. data: [],
  149. },
  150. });
  151. renderComponent();
  152. expect(screen.getByTestId('loading-indicator')).toBeInTheDocument();
  153. await waitFor(() => {
  154. expect(mockApi).toHaveBeenCalledTimes(1);
  155. });
  156. });
  157. it('should show a list of replays and have the correct values', async () => {
  158. const mockApi = MockApiClient.addMockResponse({
  159. url: mockReplaysUrl,
  160. statusCode: 200,
  161. body: {
  162. data: [
  163. {
  164. ...ReplayListFixture()[0],
  165. count_errors: 1,
  166. duration: 52346,
  167. finished_at: new Date('2022-09-15T06:54:00+00:00'),
  168. id: '346789a703f6454384f1de473b8b9fcc',
  169. started_at: new Date('2022-09-15T06:50:00+00:00'),
  170. urls: [
  171. 'https://dev.getsentry.net:7999/organizations/sentry-emerging-tech/replays/',
  172. '/organizations/sentry-emerging-tech/replays/?project=2',
  173. ],
  174. },
  175. {
  176. ...ReplayListFixture()[0],
  177. count_errors: 4,
  178. duration: 400,
  179. finished_at: new Date('2022-09-21T21:40:38+00:00'),
  180. id: 'b05dae9b6be54d21a4d5ad9f8f02b780',
  181. started_at: new Date('2022-09-21T21:30:44+00:00'),
  182. urls: [
  183. 'https://dev.getsentry.net:7999/organizations/sentry-emerging-tech/replays/?project=2&statsPeriod=24h',
  184. '/organizations/sentry-emerging-tech/issues/',
  185. '/organizations/sentry-emerging-tech/issues/?project=2',
  186. ],
  187. },
  188. ].map(hydrated => ({
  189. ...hydrated,
  190. started_at: hydrated.started_at.toString(),
  191. finished_at: hydrated.finished_at.toString(),
  192. })),
  193. },
  194. });
  195. // Mock the system date to be 2022-09-28
  196. setMockDate(new Date('Sep 28, 2022 11:29:13 PM UTC'));
  197. renderComponent({location: {query: {query: 'test'}}});
  198. await waitFor(() => {
  199. expect(mockApi).toHaveBeenCalledTimes(1);
  200. });
  201. // Expect the table to have 2 rows
  202. expect(screen.getAllByText('testDisplayName')).toHaveLength(2);
  203. const expectedQuery =
  204. 'project=1&query=test&referrer=%2Forganizations%2F%3AorgId%2Fperformance%2Fsummary%2Freplays%2F&statsPeriod=14d&yAxis=count%28%29';
  205. // Expect the first row to have the correct href
  206. expect(screen.getAllByRole('link', {name: 'testDisplayName'})[0]).toHaveAttribute(
  207. 'href',
  208. `/organizations/org-slug/replays/346789a703f6454384f1de473b8b9fcc/?${expectedQuery}`
  209. );
  210. // Expect the second row to have the correct href
  211. expect(screen.getAllByRole('link', {name: 'testDisplayName'})[1]).toHaveAttribute(
  212. 'href',
  213. `/organizations/org-slug/replays/b05dae9b6be54d21a4d5ad9f8f02b780/?${expectedQuery}`
  214. );
  215. // Expect the first row to have the correct duration
  216. expect(screen.getByText('14:32:26')).toBeInTheDocument();
  217. // Expect the second row to have the correct duration
  218. expect(screen.getByText('06:40')).toBeInTheDocument();
  219. // Expect the first row to have the correct errors
  220. expect(screen.getAllByTestId('replay-table-count-errors')[0]).toHaveTextContent('1');
  221. // Expect the second row to have the correct errors
  222. expect(screen.getAllByTestId('replay-table-count-errors')[1]).toHaveTextContent('4');
  223. // Expect the first row to have the correct date
  224. expect(screen.getByText('14 days ago')).toBeInTheDocument();
  225. // Expect the second row to have the correct date
  226. expect(screen.getByText('7 days ago')).toBeInTheDocument();
  227. });
  228. it("should show a message when the organization doesn't have access to the replay feature", async () => {
  229. renderComponent({
  230. organizationProps: {
  231. features: ['performance-view'],
  232. },
  233. });
  234. await waitFor(() => {
  235. expect(
  236. screen.getByText("You don't have access to this feature")
  237. ).toBeInTheDocument();
  238. });
  239. });
  240. });