header.spec.jsx 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  2. import {trackAnalyticsEvent} from 'sentry/utils/analytics';
  3. import IssueListHeader from 'sentry/views/issueList/header';
  4. import {Query} from 'sentry/views/issueList/utils';
  5. jest.mock('sentry/utils/analytics', () => ({
  6. trackAnalyticsEvent: jest.fn(),
  7. }));
  8. const queryCounts = {
  9. 'is:unresolved is:for_review assigned_or_suggested:[me, none]': {
  10. count: 22,
  11. hasMore: false,
  12. },
  13. 'is:unresolved': {
  14. count: 1,
  15. hasMore: false,
  16. },
  17. 'is:ignored': {
  18. count: 0,
  19. hasMore: false,
  20. },
  21. 'is:reprocessing': {
  22. count: 0,
  23. hasMore: false,
  24. },
  25. };
  26. const queryCountsMaxed = {
  27. 'is:unresolved is:for_review assigned_or_suggested:[me, none]': {
  28. count: 321,
  29. hasMore: false,
  30. },
  31. 'is:unresolved': {
  32. count: 100,
  33. hasMore: true,
  34. },
  35. 'is:ignored': {
  36. count: 100,
  37. hasMore: true,
  38. },
  39. };
  40. describe('IssueListHeader', () => {
  41. let organization;
  42. beforeEach(() => {
  43. organization = TestStubs.Organization();
  44. });
  45. afterEach(() => {
  46. jest.resetAllMocks();
  47. });
  48. it('renders active tab with count when query matches inbox', () => {
  49. render(
  50. <IssueListHeader
  51. organization={organization}
  52. query="is:unresolved is:for_review assigned_or_suggested:[me, none]"
  53. queryCount={0}
  54. queryCounts={queryCounts}
  55. projectIds={[]}
  56. savedSearchList={[]}
  57. />
  58. );
  59. expect(screen.getByText('For Review')).toHaveTextContent('For Review 22');
  60. });
  61. it('renders reprocessing tab', () => {
  62. organization.features = ['reprocessing-v2'];
  63. render(
  64. <IssueListHeader
  65. organization={organization}
  66. query=""
  67. queryCount={0}
  68. queryCounts={{
  69. ...queryCounts,
  70. 'is:reprocessing': {
  71. count: 1,
  72. hasMore: false,
  73. },
  74. }}
  75. displayReprocessingTab
  76. projectIds={[]}
  77. savedSearchList={[]}
  78. />
  79. );
  80. expect(screen.getByText('Reprocessing')).toHaveTextContent('Reprocessing 1');
  81. });
  82. it("renders all tabs inactive when query doesn't match", () => {
  83. render(
  84. <IssueListHeader
  85. organization={organization}
  86. query=""
  87. queryCounts={queryCounts}
  88. projectIds={[]}
  89. savedSearchList={[]}
  90. />
  91. );
  92. expect(screen.getByText('Custom Search')).toBeInTheDocument();
  93. });
  94. it('renders all tabs with counts', () => {
  95. render(
  96. <IssueListHeader
  97. organization={organization}
  98. query=""
  99. queryCount={0}
  100. queryCounts={queryCounts}
  101. projectIds={[]}
  102. savedSearchList={[]}
  103. />
  104. );
  105. const tabs = screen.getAllByRole('listitem');
  106. expect(tabs[0]).toHaveTextContent('All Unresolved 1');
  107. expect(tabs[1]).toHaveTextContent('For Review 22');
  108. expect(tabs[2]).toHaveTextContent('Ignored');
  109. });
  110. it('renders limited counts for tabs and exact for selected', () => {
  111. render(
  112. <IssueListHeader
  113. organization={organization}
  114. query=""
  115. queryCount={0}
  116. queryCounts={queryCountsMaxed}
  117. projectIds={[]}
  118. savedSearchList={[]}
  119. />
  120. );
  121. const tabs = screen.getAllByRole('listitem');
  122. expect(tabs[0]).toHaveTextContent('All Unresolved 99+');
  123. expect(tabs[1]).toHaveTextContent('For Review 321');
  124. expect(tabs[2]).toHaveTextContent('Ignored 99+');
  125. });
  126. it('transitions to new query on tab click', () => {
  127. const routerContext = TestStubs.routerContext();
  128. render(
  129. <IssueListHeader
  130. organization={organization}
  131. queryCounts={queryCounts}
  132. projectIds={[]}
  133. savedSearchList={[]}
  134. />,
  135. {context: routerContext}
  136. );
  137. const pathname = '/organizations/org-slug/issues/';
  138. userEvent.click(screen.getByText('All Unresolved'));
  139. expect(routerContext.context.router.push).toHaveBeenCalledWith({
  140. pathname,
  141. query: {query: 'is:unresolved'},
  142. });
  143. userEvent.click(screen.getByText('For Review'));
  144. expect(routerContext.context.router.push).toHaveBeenCalledWith({
  145. pathname,
  146. query: {
  147. query: 'is:unresolved is:for_review assigned_or_suggested:[me, none]',
  148. sort: 'inbox',
  149. },
  150. });
  151. });
  152. it('removes inbox sort for non-inbox tabs', () => {
  153. const routerContext = TestStubs.routerContext();
  154. render(
  155. <IssueListHeader
  156. organization={organization}
  157. queryCounts={queryCounts}
  158. projectIds={[]}
  159. savedSearchList={[]}
  160. router={TestStubs.router({
  161. location: {
  162. pathname: '/test/',
  163. query: {sort: 'inbox'},
  164. },
  165. })}
  166. />,
  167. {context: routerContext}
  168. );
  169. const pathname = '/organizations/org-slug/issues/';
  170. userEvent.click(screen.getByText('All Unresolved'));
  171. expect(routerContext.context.router.push).toHaveBeenCalledWith({
  172. pathname,
  173. query: {query: 'is:unresolved'},
  174. });
  175. userEvent.click(screen.getByText('For Review'));
  176. expect(routerContext.context.router.push).toHaveBeenCalledWith({
  177. pathname,
  178. query: {
  179. query: 'is:unresolved is:for_review assigned_or_suggested:[me, none]',
  180. sort: 'inbox',
  181. },
  182. });
  183. });
  184. it('changes sort for inbox tab', () => {
  185. const routerContext = TestStubs.routerContext();
  186. render(
  187. <IssueListHeader
  188. organization={organization}
  189. queryCounts={queryCounts}
  190. projectIds={[]}
  191. savedSearchList={[]}
  192. router={TestStubs.router({
  193. location: {
  194. pathname: '/test/',
  195. query: {sort: 'date'},
  196. },
  197. })}
  198. />,
  199. {context: routerContext}
  200. );
  201. userEvent.click(screen.getByText('For Review'));
  202. expect(routerContext.context.router.push).toHaveBeenCalledWith({
  203. pathname: '/organizations/org-slug/issues/',
  204. query: {
  205. query: 'is:unresolved is:for_review assigned_or_suggested:[me, none]',
  206. sort: 'inbox',
  207. },
  208. });
  209. });
  210. it('tracks clicks on inbox tab', () => {
  211. render(
  212. <IssueListHeader
  213. organization={organization}
  214. query={Query.UNRESOLVED}
  215. queryCounts={queryCounts}
  216. projectIds={[]}
  217. savedSearchList={[]}
  218. />,
  219. {context: TestStubs.routerContext()}
  220. );
  221. userEvent.click(screen.getByText('For Review'));
  222. expect(trackAnalyticsEvent).toHaveBeenCalledTimes(1);
  223. });
  224. it('ignores clicks on inbox tab when already on inbox tab', () => {
  225. render(
  226. <IssueListHeader
  227. organization={organization}
  228. query={Query.FOR_REVIEW}
  229. queryCounts={queryCounts}
  230. projectIds={[]}
  231. savedSearchList={[]}
  232. />,
  233. {context: TestStubs.routerContext()}
  234. );
  235. userEvent.click(screen.getByText('For Review'));
  236. expect(trackAnalyticsEvent).toHaveBeenCalledTimes(0);
  237. });
  238. it('should indicate when query is a custom search and display count', () => {
  239. render(
  240. <IssueListHeader
  241. organization={organization}
  242. queryCounts={queryCounts}
  243. projectIds={[]}
  244. savedSearchList={[]}
  245. query="not a saved search"
  246. queryCount={13}
  247. />
  248. );
  249. expect(screen.getByRole('button', {name: 'Custom Search 13'})).toBeInTheDocument();
  250. });
  251. it('should display saved search name and count', () => {
  252. const query = 'saved search query';
  253. render(
  254. <IssueListHeader
  255. organization={organization}
  256. queryCounts={queryCounts}
  257. projectIds={[]}
  258. savedSearchList={[
  259. {
  260. id: '789',
  261. query,
  262. name: 'Saved Search',
  263. isPinned: false,
  264. isGlobal: true,
  265. },
  266. ]}
  267. query={query}
  268. queryCount={13}
  269. />
  270. );
  271. expect(screen.getByRole('button', {name: 'Saved Search 13'})).toBeInTheDocument();
  272. });
  273. it('lists saved searches in dropdown', () => {
  274. render(
  275. <IssueListHeader
  276. organization={organization}
  277. queryCounts={queryCounts}
  278. projectIds={[]}
  279. savedSearchList={[
  280. {
  281. id: '789',
  282. query: 'is:unresolved TypeError',
  283. name: 'Unresolved TypeError',
  284. isPinned: false,
  285. isGlobal: true,
  286. },
  287. ]}
  288. query="is:unresolved"
  289. />
  290. );
  291. expect(screen.queryByText('Unresolved TypeError')).not.toBeInTheDocument();
  292. userEvent.click(screen.getByText('Saved Searches'));
  293. expect(screen.getByText('Unresolved TypeError')).toBeInTheDocument();
  294. });
  295. });