eventDetails.spec.tsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import {EventFixture} from 'sentry-fixture/event';
  2. import {EventsStatsFixture} from 'sentry-fixture/events';
  3. import {GroupFixture} from 'sentry-fixture/group';
  4. import {LocationFixture} from 'sentry-fixture/locationFixture';
  5. import {OrganizationFixture} from 'sentry-fixture/organization';
  6. import {ProjectFixture} from 'sentry-fixture/project';
  7. import {RouterFixture} from 'sentry-fixture/routerFixture';
  8. import {TagsFixture} from 'sentry-fixture/tags';
  9. import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  10. import PageFiltersStore from 'sentry/stores/pageFiltersStore';
  11. import ProjectsStore from 'sentry/stores/projectsStore';
  12. import {EventDetails} from 'sentry/views/issueDetails/streamline/eventDetails';
  13. import {MOCK_EVENTS_TABLE_DATA} from 'sentry/views/performance/transactionSummary/transactionEvents/testUtils';
  14. jest.mock('sentry/views/issueDetails/groupEventDetails/groupEventDetailsContent');
  15. jest.mock('sentry/views/issueDetails/streamline/issueContent');
  16. jest.mock('screenfull', () => ({
  17. enabled: true,
  18. isFullscreen: false,
  19. request: jest.fn(),
  20. exit: jest.fn(),
  21. on: jest.fn(),
  22. off: jest.fn(),
  23. }));
  24. const mockUseNavigate = jest.fn();
  25. jest.mock('sentry/utils/useNavigate', () => ({
  26. useNavigate: () => mockUseNavigate,
  27. }));
  28. describe('EventDetails', function () {
  29. const organization = OrganizationFixture();
  30. const project = ProjectFixture();
  31. const group = GroupFixture();
  32. const event = EventFixture({id: 'event-id'});
  33. const defaultProps = {project, group, event};
  34. let mockActionableItems: jest.Mock;
  35. let mockTags: jest.Mock;
  36. let mockStats: jest.Mock;
  37. let mockList: jest.Mock;
  38. let mockListMeta: jest.Mock;
  39. beforeEach(function () {
  40. PageFiltersStore.init();
  41. PageFiltersStore.onInitializeUrlState(
  42. {
  43. projects: [],
  44. environments: [],
  45. datetime: {start: null, end: null, period: '14d', utc: null},
  46. },
  47. new Set(['environments'])
  48. );
  49. ProjectsStore.loadInitialData([project]);
  50. MockApiClient.clearMockResponses();
  51. mockActionableItems = MockApiClient.addMockResponse({
  52. url: `/projects/${organization.slug}/${project.slug}/events/${event.id}/actionable-items/`,
  53. body: {errors: []},
  54. method: 'GET',
  55. });
  56. mockTags = MockApiClient.addMockResponse({
  57. url: `/organizations/${organization.slug}/issues/${group.id}/tags/`,
  58. body: TagsFixture(),
  59. method: 'GET',
  60. });
  61. mockStats = MockApiClient.addMockResponse({
  62. url: `/organizations/${organization.slug}/events-stats/`,
  63. body: {'count()': EventsStatsFixture(), 'count_unique(user)': EventsStatsFixture()},
  64. method: 'GET',
  65. });
  66. mockList = MockApiClient.addMockResponse({
  67. url: '/organizations/org-slug/events/',
  68. headers: {
  69. Link:
  70. `<http://localhost/api/0/organizations/${organization.slug}/events/?cursor=2:0:0>; rel="next"; results="true"; cursor="2:0:0",` +
  71. `<http://localhost/api/0/organizations/${organization.slug}/events/?cursor=1:0:0>; rel="previous"; results="false"; cursor="1:0:0"`,
  72. },
  73. body: {
  74. data: MOCK_EVENTS_TABLE_DATA,
  75. },
  76. match: [
  77. (_url, options) => {
  78. return options.query?.field?.includes('user.display');
  79. },
  80. ],
  81. });
  82. mockListMeta = MockApiClient.addMockResponse({
  83. url: '/organizations/org-slug/events/',
  84. headers: {
  85. Link:
  86. `<http://localhost/api/0/organizations/${organization.slug}/events/?cursor=2:0:0>; rel="next"; results="true"; cursor="2:0:0",` +
  87. `<http://localhost/api/0/organizations/${organization.slug}/events/?cursor=1:0:0>; rel="previous"; results="false"; cursor="1:0:0"`,
  88. },
  89. body: {
  90. data: [{'count()': 100}],
  91. },
  92. match: [
  93. (_url, options) => {
  94. return options.query?.field?.includes('count()');
  95. },
  96. ],
  97. });
  98. });
  99. it('displays all basic components', async function () {
  100. render(<EventDetails {...defaultProps} />, {organization});
  101. await screen.findByText(event.id);
  102. // Filtering
  103. expect(mockTags).toHaveBeenCalled();
  104. expect(screen.getByTestId('page-filter-environment-selector')).toBeInTheDocument();
  105. expect(screen.getByLabelText('Search events')).toBeInTheDocument();
  106. expect(screen.getByTestId('page-filter-timerange-selector')).toBeInTheDocument();
  107. // Graph
  108. expect(mockStats).toHaveBeenCalled();
  109. expect(screen.getByRole('figure')).toBeInTheDocument();
  110. // Navigation
  111. expect(screen.getByRole('tab', {name: 'Recommended Event'})).toBeInTheDocument();
  112. expect(screen.getByRole('tab', {name: 'First Event'})).toBeInTheDocument();
  113. expect(screen.getByRole('button', {name: 'Next Event'})).toBeInTheDocument();
  114. expect(screen.getByRole('button', {name: 'View All Events'})).toBeInTheDocument();
  115. // Content
  116. expect(mockActionableItems).toHaveBeenCalled();
  117. // All Events (should not query initially)
  118. expect(mockList).not.toHaveBeenCalled();
  119. expect(mockListMeta).not.toHaveBeenCalled();
  120. });
  121. it('should display the events list', async function () {
  122. const router = RouterFixture({
  123. location: LocationFixture({
  124. pathname: `/organizations/${organization.slug}/issues/${group.id}/events/`,
  125. }),
  126. routes: [{name: '', path: 'events/'}],
  127. });
  128. render(<EventDetails {...defaultProps} />, {organization, router});
  129. expect(await screen.findByRole('button', {name: 'Close'})).toBeInTheDocument();
  130. expect(screen.getByText('All Events')).toBeInTheDocument();
  131. expect(mockList).toHaveBeenCalled();
  132. expect(mockListMeta).toHaveBeenCalled();
  133. });
  134. it('displays error messages from bad queries', async function () {
  135. const errorMessage = 'wrong, try again';
  136. mockStats = MockApiClient.addMockResponse({
  137. url: `/organizations/${organization.slug}/events-stats/`,
  138. body: {detail: errorMessage},
  139. method: 'GET',
  140. statusCode: 400,
  141. });
  142. render(<EventDetails {...defaultProps} />, {organization});
  143. await screen.findByText(event.id);
  144. expect(mockStats).toHaveBeenCalled();
  145. expect(screen.getByText(errorMessage)).toBeInTheDocument();
  146. // Omit the graph
  147. expect(screen.queryByRole('figure')).not.toBeInTheDocument();
  148. });
  149. it('updates the query params with search tokens', async function () {
  150. const [tagKey, tagValue] = ['user.email', 'leander.rodrigues@sentry.io'];
  151. const locationQuery = {
  152. query: {
  153. query: `${tagKey}:${tagValue}`,
  154. },
  155. };
  156. MockApiClient.addMockResponse({
  157. url: `/organizations/${organization.slug}/tags/${tagKey}/values/`,
  158. body: [
  159. {
  160. key: tagKey,
  161. name: tagValue,
  162. value: tagValue,
  163. },
  164. ],
  165. method: 'GET',
  166. });
  167. render(<EventDetails {...defaultProps} />, {organization});
  168. await screen.findByText(event.id);
  169. const search = screen.getAllByRole('combobox', {name: 'Add a search term'})[0];
  170. await userEvent.type(search, `${tagKey}:`);
  171. await userEvent.keyboard(`${tagValue}{enter}{enter}`);
  172. expect(mockUseNavigate).toHaveBeenCalledWith(expect.objectContaining(locationQuery), {
  173. replace: true,
  174. });
  175. });
  176. });