index.spec.jsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import {mountWithTheme} from 'sentry-test/enzyme';
  2. import {initializeOrg} from 'sentry-test/initializeOrg';
  3. import ProjectsStore from 'app/stores/projectsStore';
  4. import IncidentsList from 'app/views/alerts/list';
  5. describe('IncidentsList', function () {
  6. let routerContext;
  7. let router;
  8. let organization;
  9. let projectMock;
  10. let wrapper;
  11. let projects;
  12. const projects1 = ['a', 'b', 'c'];
  13. const projects2 = ['c', 'd'];
  14. const createWrapper = async (props = {}) => {
  15. wrapper = mountWithTheme(
  16. <IncidentsList
  17. params={{orgId: organization.slug}}
  18. location={{query: {}, search: ''}}
  19. router={router}
  20. {...props}
  21. />,
  22. routerContext
  23. );
  24. await tick();
  25. wrapper.update();
  26. return wrapper;
  27. };
  28. beforeEach(function () {
  29. const context = initializeOrg({
  30. organization: {
  31. features: ['incidents'],
  32. },
  33. });
  34. routerContext = context.routerContext;
  35. router = context.router;
  36. organization = context.organization;
  37. MockApiClient.addMockResponse({
  38. url: '/organizations/org-slug/incidents/',
  39. body: [
  40. TestStubs.Incident({
  41. id: '123',
  42. identifier: '1',
  43. title: 'First incident',
  44. projects: projects1,
  45. }),
  46. TestStubs.Incident({
  47. id: '342',
  48. identifier: '2',
  49. title: 'Second incident',
  50. projects: projects2,
  51. }),
  52. ],
  53. });
  54. MockApiClient.addMockResponse({
  55. url: '/organizations/org-slug/incidents/2/stats/',
  56. body: TestStubs.IncidentStats({
  57. totalEvents: 1000,
  58. uniqueUsers: 32,
  59. eventStats: {
  60. data: [[1591390293327, [{count: 42}]]],
  61. },
  62. }),
  63. });
  64. projects = [
  65. TestStubs.Project({slug: 'a', platform: 'javascript'}),
  66. TestStubs.Project({slug: 'b'}),
  67. TestStubs.Project({slug: 'c'}),
  68. TestStubs.Project({slug: 'd'}),
  69. ];
  70. projectMock = MockApiClient.addMockResponse({
  71. url: '/organizations/org-slug/projects/',
  72. body: projects,
  73. });
  74. });
  75. afterEach(function () {
  76. wrapper.unmount();
  77. ProjectsStore.reset();
  78. MockApiClient.clearMockResponses();
  79. });
  80. it('displays list', async function () {
  81. ProjectsStore.loadInitialData(projects);
  82. wrapper = await createWrapper();
  83. await tick();
  84. await tick();
  85. await tick();
  86. wrapper.update();
  87. const items = wrapper.find('IncidentPanelItem');
  88. expect(items).toHaveLength(2);
  89. expect(items.at(0).text()).toContain('First incident');
  90. expect(items.at(1).text()).toContain('Second incident');
  91. // GlobalSelectionHeader loads projects + the Projects render-prop
  92. // component to load projects for all rows.
  93. expect(projectMock).toHaveBeenCalledTimes(2);
  94. expect(projectMock).toHaveBeenLastCalledWith(
  95. expect.anything(),
  96. expect.objectContaining({
  97. query: expect.objectContaining({query: 'slug:a slug:b slug:c'}),
  98. })
  99. );
  100. expect(items.at(0).find('IdBadge').prop('project')).toMatchObject({
  101. slug: 'a',
  102. });
  103. });
  104. it('displays empty state (first time experience)', async function () {
  105. MockApiClient.addMockResponse({
  106. url: '/organizations/org-slug/incidents/',
  107. body: [],
  108. });
  109. const rulesMock = MockApiClient.addMockResponse({
  110. url: '/organizations/org-slug/alert-rules/',
  111. body: [],
  112. });
  113. const promptsMock = MockApiClient.addMockResponse({
  114. url: '/prompts-activity/',
  115. body: {data: {dismissed_ts: null}},
  116. });
  117. const promptsUpdateMock = MockApiClient.addMockResponse({
  118. url: '/prompts-activity/',
  119. method: 'PUT',
  120. });
  121. wrapper = await createWrapper();
  122. expect(rulesMock).toHaveBeenCalledTimes(1);
  123. expect(promptsMock).toHaveBeenCalledTimes(1);
  124. expect(promptsUpdateMock).toHaveBeenCalledTimes(1);
  125. await tick();
  126. wrapper.update();
  127. expect(wrapper.find('PanelItem')).toHaveLength(0);
  128. expect(wrapper.find('Onboarding').text()).toContain('More signal, less noise');
  129. });
  130. it('displays empty state (rules not yet created)', async function () {
  131. MockApiClient.addMockResponse({
  132. url: '/organizations/org-slug/incidents/',
  133. body: [],
  134. });
  135. const rulesMock = MockApiClient.addMockResponse({
  136. url: '/organizations/org-slug/alert-rules/',
  137. body: [],
  138. });
  139. const promptsMock = MockApiClient.addMockResponse({
  140. url: '/prompts-activity/',
  141. body: {data: {dismissed_ts: Math.floor(Date.now() / 1000)}},
  142. });
  143. wrapper = await createWrapper();
  144. expect(rulesMock).toHaveBeenCalledTimes(1);
  145. expect(promptsMock).toHaveBeenCalledTimes(1);
  146. await tick();
  147. wrapper.update();
  148. expect(wrapper.find('PanelItem')).toHaveLength(0);
  149. expect(wrapper.text()).toContain('No incidents exist for the current query');
  150. });
  151. it('displays empty state (rules created)', async function () {
  152. MockApiClient.addMockResponse({
  153. url: '/organizations/org-slug/incidents/',
  154. body: [],
  155. });
  156. const rulesMock = MockApiClient.addMockResponse({
  157. url: '/organizations/org-slug/alert-rules/',
  158. body: [{id: 1}],
  159. });
  160. const promptsMock = MockApiClient.addMockResponse({
  161. url: '/prompts-activity/',
  162. body: {data: {dismissed_ts: Math.floor(Date.now() / 1000)}},
  163. });
  164. wrapper = await createWrapper();
  165. expect(rulesMock).toHaveBeenCalledTimes(1);
  166. expect(promptsMock).toHaveBeenCalledTimes(0);
  167. await tick();
  168. wrapper.update();
  169. expect(wrapper.find('PanelItem')).toHaveLength(0);
  170. expect(wrapper.text()).toContain('No incidents exist for the current query.');
  171. });
  172. it('filters by opened issues', async function () {
  173. ProjectsStore.loadInitialData(projects);
  174. wrapper = await createWrapper();
  175. wrapper.find('[data-test-id="filter-button"]').at(1).simulate('click');
  176. const resolved = wrapper.find('Filter').find('ListItem').at(1);
  177. expect(resolved.text()).toBe('Resolved');
  178. expect(resolved.find('[data-test-id="checkbox-fancy"]').props()['aria-checked']).toBe(
  179. false
  180. );
  181. wrapper.setProps({
  182. location: {query: {status: ['closed']}, search: '?status=closed`'},
  183. });
  184. expect(
  185. wrapper
  186. .find('Filter')
  187. .find('ListItem')
  188. .at(1)
  189. .find('[data-test-id="checkbox-fancy"]')
  190. .props()['aria-checked']
  191. ).toBe(true);
  192. });
  193. it('disables the new alert button for those without alert:write', async function () {
  194. const noAccessOrg = {
  195. ...organization,
  196. access: [],
  197. };
  198. wrapper = await createWrapper({organization: noAccessOrg});
  199. const addButton = wrapper.find('button[aria-label="Create Alert Rule"]');
  200. expect(addButton.props()['aria-disabled']).toBe(true);
  201. // Enabled with access
  202. wrapper.unmount();
  203. wrapper = await createWrapper();
  204. const addLink = wrapper.find('button[aria-label="Create Alert Rule"]');
  205. expect(addLink.props()['aria-disabled']).toBe(false);
  206. });
  207. it('searches by name', async () => {
  208. wrapper = await createWrapper();
  209. expect(wrapper.find('StyledSearchBar').exists()).toBe(true);
  210. const testQuery = 'test name';
  211. wrapper
  212. .find('StyledSearchBar')
  213. .find('input')
  214. .simulate('change', {target: {value: testQuery}})
  215. .simulate('submit', {preventDefault() {}});
  216. expect(router.push).toHaveBeenCalledWith(
  217. expect.objectContaining({
  218. query: {
  219. title: testQuery,
  220. team: ['myteams', 'unassigned'],
  221. },
  222. })
  223. );
  224. });
  225. });