index.spec.jsx 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {act, fireEvent, mountWithTheme, screen} from 'sentry-test/reactTestingLibrary';
  3. import OrganizationStore from 'app/stores/organizationStore';
  4. import ProjectsStore from 'app/stores/projectsStore';
  5. import {trackAnalyticsEvent} from 'app/utils/analytics';
  6. import AlertRulesList from 'app/views/alerts/rules';
  7. import {IncidentStatus} from 'app/views/alerts/types';
  8. jest.mock('app/utils/analytics');
  9. describe('OrganizationRuleList', () => {
  10. const {routerContext, organization, router} = initializeOrg();
  11. let rulesMock;
  12. let projectMock;
  13. const getComponent = props => (
  14. <AlertRulesList
  15. organization={organization}
  16. params={{orgId: organization.slug}}
  17. location={{query: {}, search: ''}}
  18. router={router}
  19. {...props}
  20. />
  21. );
  22. const createWrapper = props =>
  23. mountWithTheme(getComponent(props), {context: routerContext});
  24. beforeEach(() => {
  25. rulesMock = MockApiClient.addMockResponse({
  26. url: '/organizations/org-slug/combined-rules/',
  27. body: [
  28. TestStubs.ProjectAlertRule({
  29. id: '123',
  30. name: 'First Issue Alert',
  31. projects: ['earth'],
  32. createdBy: {name: 'Samwise', id: 1, email: ''},
  33. }),
  34. TestStubs.IncidentRule({
  35. id: '345',
  36. projects: ['earth'],
  37. latestIncident: TestStubs.Incident({
  38. status: IncidentStatus.CRITICAL,
  39. }),
  40. }),
  41. TestStubs.IncidentRule({
  42. id: '678',
  43. projects: ['earth'],
  44. latestIncident: null,
  45. }),
  46. ],
  47. });
  48. projectMock = MockApiClient.addMockResponse({
  49. url: '/organizations/org-slug/projects/',
  50. body: [TestStubs.Project({slug: 'earth', platform: 'javascript'})],
  51. });
  52. act(() => OrganizationStore.onUpdate(organization, {replace: true}));
  53. act(() => ProjectsStore.loadInitialData([]));
  54. });
  55. afterEach(() => {
  56. act(() => ProjectsStore.reset());
  57. MockApiClient.clearMockResponses();
  58. trackAnalyticsEvent.mockClear();
  59. });
  60. it('displays list', async () => {
  61. createWrapper();
  62. expect(await screen.findByText('First Issue Alert')).toBeInTheDocument();
  63. expect(projectMock).toHaveBeenLastCalledWith(
  64. expect.anything(),
  65. expect.objectContaining({
  66. query: expect.objectContaining({query: 'slug:earth'}),
  67. })
  68. );
  69. expect(screen.getAllByTestId('badge-display-name')[0]).toHaveTextContent('earth');
  70. expect(trackAnalyticsEvent).toHaveBeenCalledWith({
  71. eventKey: 'alert_rules.viewed',
  72. eventName: 'Alert Rules: Viewed',
  73. organization_id: '3',
  74. sort: 'incident_status,date_triggered',
  75. });
  76. });
  77. it('displays empty state', async () => {
  78. MockApiClient.addMockResponse({
  79. url: '/organizations/org-slug/combined-rules/',
  80. body: [],
  81. });
  82. createWrapper();
  83. expect(
  84. await screen.findByText('No alert rules found for the current query.')
  85. ).toBeInTheDocument();
  86. expect(rulesMock).toHaveBeenCalledTimes(0);
  87. });
  88. it('sorts by date created', async () => {
  89. const {rerender} = createWrapper();
  90. // The created column is not used for sorting
  91. expect(await screen.findByText('Created')).toHaveAttribute('aria-sort', 'none');
  92. // Sort by created (date_added)
  93. rerender(
  94. getComponent({
  95. location: {
  96. query: {asc: '1', sort: 'date_added'},
  97. search: '?asc=1&sort=date_added`',
  98. },
  99. })
  100. );
  101. expect(await screen.findByText('Created')).toHaveAttribute('aria-sort', 'ascending');
  102. expect(rulesMock).toHaveBeenCalledTimes(2);
  103. expect(rulesMock).toHaveBeenCalledWith(
  104. '/organizations/org-slug/combined-rules/',
  105. expect.objectContaining({
  106. query: expect.objectContaining({asc: '1'}),
  107. })
  108. );
  109. });
  110. it('sorts by name', async () => {
  111. const {rerender} = createWrapper();
  112. // The name column is not used for sorting
  113. expect(await screen.findByText('Alert Rule')).toHaveAttribute('aria-sort', 'none');
  114. // Sort by the name column
  115. rerender(
  116. getComponent({
  117. location: {
  118. query: {asc: '1', sort: 'name'},
  119. search: '?asc=1&sort=name`',
  120. },
  121. })
  122. );
  123. expect(await screen.findByText('Alert Rule')).toHaveAttribute(
  124. 'aria-sort',
  125. 'ascending'
  126. );
  127. expect(rulesMock).toHaveBeenCalledTimes(2);
  128. expect(rulesMock).toHaveBeenCalledWith(
  129. '/organizations/org-slug/combined-rules/',
  130. expect.objectContaining({
  131. query: expect.objectContaining({sort: 'name', asc: '1'}),
  132. })
  133. );
  134. });
  135. it('disables the new alert button for members', async () => {
  136. const noAccessOrg = {
  137. ...organization,
  138. access: [],
  139. };
  140. const {rerender} = createWrapper({organization: noAccessOrg});
  141. expect(await screen.findByLabelText('Create Alert Rule')).toBeDisabled();
  142. // Enabled with access
  143. rerender(getComponent());
  144. expect(await screen.findByLabelText('Create Alert Rule')).toBeEnabled();
  145. });
  146. it('searches by name', async () => {
  147. createWrapper();
  148. const search = await screen.findByPlaceholderText('Search by name');
  149. expect(search).toBeInTheDocument();
  150. const testQuery = 'test name';
  151. fireEvent.change(search, {target: {value: testQuery}});
  152. fireEvent.submit(search);
  153. expect(router.push).toHaveBeenCalledWith(
  154. expect.objectContaining({
  155. query: {
  156. name: testQuery,
  157. expand: ['latestIncident'],
  158. sort: ['incident_status', 'date_triggered'],
  159. team: ['myteams', 'unassigned'],
  160. },
  161. })
  162. );
  163. });
  164. it('uses empty team query parameter when removing all teams', async () => {
  165. const {rerender} = createWrapper();
  166. expect(await screen.findByText('First Issue Alert')).toBeInTheDocument();
  167. rerender(
  168. getComponent({location: {query: {team: 'myteams'}, search: '?team=myteams`'}})
  169. );
  170. fireEvent.click(await screen.findByTestId('filter-button'));
  171. // Uncheck myteams
  172. fireEvent.click(await screen.findByText('My Teams'));
  173. expect(router.push).toHaveBeenCalledWith(
  174. expect.objectContaining({
  175. query: {
  176. expand: ['latestIncident'],
  177. sort: ['incident_status', 'date_triggered'],
  178. team: '',
  179. },
  180. })
  181. );
  182. });
  183. it('displays alert status', async () => {
  184. createWrapper();
  185. const rules = await screen.findAllByText('My Incident Rule');
  186. expect(rules[0]).toBeInTheDocument();
  187. expect(screen.getByText('Triggered')).toBeInTheDocument();
  188. expect(screen.getByText('Above 70')).toBeInTheDocument();
  189. expect(screen.getByText('Below 36')).toBeInTheDocument();
  190. expect(screen.getAllByTestId('alert-badge')[0]).toBeInTheDocument();
  191. });
  192. it('sorts by alert rule', async () => {
  193. createWrapper({organization});
  194. expect(await screen.findByText('First Issue Alert')).toBeInTheDocument();
  195. expect(rulesMock).toHaveBeenCalledWith(
  196. '/organizations/org-slug/combined-rules/',
  197. expect.objectContaining({
  198. query: {
  199. expand: ['latestIncident'],
  200. sort: ['incident_status', 'date_triggered'],
  201. team: ['myteams', 'unassigned'],
  202. },
  203. })
  204. );
  205. });
  206. });