draggableTabBar.spec.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. import {RouterFixture} from 'sentry-fixture/routerFixture';
  2. import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  3. import {
  4. DraggableTabBar,
  5. type Tab,
  6. } from 'sentry/views/issueList/groupSearchViewTabs/draggableTabBar';
  7. import {IssueSortOptions} from 'sentry/views/issueList/utils';
  8. describe('DraggableTabBar', () => {
  9. const mockOnTabRenamed = jest.fn();
  10. const mockOnAddView = jest.fn();
  11. const mockOnDelete = jest.fn();
  12. const mockOnDiscard = jest.fn();
  13. const mockOnDuplicate = jest.fn();
  14. const mockOnSave = jest.fn();
  15. const mockOnDiscardTempView = jest.fn();
  16. const mockOnSaveTempView = jest.fn();
  17. const router = RouterFixture({
  18. location: {
  19. pathname: 'test',
  20. },
  21. });
  22. const tabs: Tab[] = [
  23. {
  24. id: '1',
  25. key: '1',
  26. label: 'Prioritized',
  27. queryCount: 20,
  28. query: 'priority:high',
  29. querySort: IssueSortOptions.DATE,
  30. unsavedChanges: ['priority:low', IssueSortOptions.DATE],
  31. },
  32. {
  33. id: '2',
  34. key: '2',
  35. label: 'For Review',
  36. queryCount: 1001,
  37. query: 'is:unassigned',
  38. querySort: IssueSortOptions.DATE,
  39. },
  40. {
  41. id: '3',
  42. key: '3',
  43. label: 'Regressed',
  44. query: 'is:regressed',
  45. querySort: IssueSortOptions.DATE,
  46. },
  47. ];
  48. describe('Tabs render as expected', () => {
  49. it('should render tabs from props', () => {
  50. render(
  51. <DraggableTabBar
  52. tabs={tabs}
  53. setTabs={jest.fn()}
  54. onTabRenamed={mockOnTabRenamed}
  55. orgSlug={'test-org'}
  56. router={router}
  57. />
  58. );
  59. expect(screen.getAllByRole('tab').length).toBe(tabs.length);
  60. // The query count is included in the tab name here
  61. expect(screen.getByRole('tab', {name: 'Prioritized 20'})).toBeInTheDocument();
  62. expect(screen.getByRole('tab', {name: 'For Review 1000+'})).toBeInTheDocument();
  63. expect(screen.getByRole('tab', {name: 'Regressed'})).toBeInTheDocument();
  64. });
  65. });
  66. // Skipping this and next tests due to excessive unexplainable flakiness
  67. // biome-ignore lint/suspicious/noSkippedTests: <explanation>
  68. describe.skip('Tab menu options', () => {
  69. it('should render the correct set of actions for changed tabs', async () => {
  70. render(
  71. <DraggableTabBar
  72. tabs={tabs}
  73. setTabs={jest.fn()}
  74. onTabRenamed={mockOnTabRenamed}
  75. orgSlug={'test-org'}
  76. router={router}
  77. />
  78. );
  79. await userEvent.click(
  80. screen.getByRole('button', {name: 'Prioritized Tab Options'})
  81. );
  82. expect(
  83. await screen.findByRole('menuitemradio', {name: 'Save Changes'})
  84. ).toBeInTheDocument();
  85. expect(
  86. await screen.findByRole('menuitemradio', {name: 'Discard Changes'})
  87. ).toBeInTheDocument();
  88. expect(
  89. await screen.findByRole('menuitemradio', {name: 'Rename'})
  90. ).toBeInTheDocument();
  91. expect(
  92. await screen.findByRole('menuitemradio', {name: 'Duplicate'})
  93. ).toBeInTheDocument();
  94. expect(
  95. await screen.findByRole('menuitemradio', {name: 'Delete'})
  96. ).toBeInTheDocument();
  97. });
  98. it('should render the correct set of actions for unchanged tabs', async () => {
  99. render(
  100. <DraggableTabBar
  101. tabs={tabs}
  102. setTabs={jest.fn()}
  103. onTabRenamed={mockOnTabRenamed}
  104. orgSlug={'test-org'}
  105. router={router}
  106. />
  107. );
  108. // We need to explicitly click on the For Review tab since it is not the default (first) tab in props
  109. await userEvent.click(screen.getByRole('tab', {name: 'For Review 1000+'}));
  110. await userEvent.click(
  111. await screen.findByRole('button', {name: 'For Review Tab Options'})
  112. );
  113. expect(
  114. await screen.findByRole('menuitemradio', {name: 'Rename'})
  115. ).toBeInTheDocument();
  116. expect(
  117. await screen.findByRole('menuitemradio', {name: 'Duplicate'})
  118. ).toBeInTheDocument();
  119. expect(
  120. await screen.findByRole('menuitemradio', {name: 'Delete'})
  121. ).toBeInTheDocument();
  122. expect(
  123. screen.queryByRole('menuitemradio', {name: 'Save Changes'})
  124. ).not.toBeInTheDocument();
  125. expect(
  126. screen.queryByRole('menuitemradio', {name: 'Discard Changes'})
  127. ).not.toBeInTheDocument();
  128. });
  129. it('should render the correct set of actions for a single tab', async () => {
  130. render(
  131. <DraggableTabBar
  132. tabs={[tabs[1]]}
  133. setTabs={jest.fn()}
  134. onTabRenamed={mockOnTabRenamed}
  135. orgSlug={'test-org'}
  136. router={router}
  137. />
  138. );
  139. await userEvent.click(screen.getByRole('button', {name: 'For Review Tab Options'}));
  140. expect(
  141. await screen.findByRole('menuitemradio', {name: 'Rename'})
  142. ).toBeInTheDocument();
  143. expect(
  144. await screen.findByRole('menuitemradio', {name: 'Duplicate'})
  145. ).toBeInTheDocument();
  146. // Delete should not be present since there is only one tab
  147. expect(
  148. screen.queryByRole('menuitemradio', {name: 'Delete'})
  149. ).not.toBeInTheDocument();
  150. });
  151. it('should render the correct set of actions for temporary tabs', async () => {
  152. render(
  153. <DraggableTabBar
  154. tabs={tabs}
  155. setTabs={jest.fn()}
  156. onTabRenamed={mockOnTabRenamed}
  157. orgSlug={'test-org'}
  158. router={router}
  159. />
  160. );
  161. // We need to explicitly click on the For Review tab since it is not the default (first) tab in props
  162. await userEvent.click(screen.getByRole('tab', {name: 'Unsaved'}));
  163. await userEvent.click(
  164. await screen.findByRole('button', {name: 'Unsaved Tab Options'})
  165. );
  166. expect(
  167. await screen.findByRole('menuitemradio', {name: 'Save View'})
  168. ).toBeInTheDocument();
  169. expect(
  170. await screen.findByRole('menuitemradio', {name: 'Discard'})
  171. ).toBeInTheDocument();
  172. });
  173. });
  174. // biome-ignore lint/suspicious/noSkippedTests: <explanation>
  175. describe.skip('Tab actions', () => {
  176. it('should allow renaming a tab', async () => {
  177. render(
  178. <DraggableTabBar
  179. tabs={tabs}
  180. setTabs={jest.fn()}
  181. onTabRenamed={mockOnTabRenamed}
  182. orgSlug={'test-org'}
  183. router={router}
  184. />
  185. );
  186. await userEvent.click(screen.getByRole('tab', {name: 'For Review 1000+'}));
  187. await userEvent.click(
  188. await screen.findByRole('button', {name: 'For Review Tab Options'})
  189. );
  190. await userEvent.click(await screen.findByRole('menuitemradio', {name: 'Rename'}));
  191. // Ctrl+A to select all text, then backspace to delete it
  192. // (We purposely do not highlight the text when hitting rename)
  193. await userEvent.keyboard('{Control>}A{/Control}{Backspace}');
  194. await userEvent.paste('New Name');
  195. await userEvent.keyboard('{enter}');
  196. expect(mockOnTabRenamed).toHaveBeenCalledWith('2', 'New Name');
  197. });
  198. it('should not allow renaming a tab to empty string', async () => {
  199. render(
  200. <DraggableTabBar
  201. tabs={tabs}
  202. setTabs={jest.fn()}
  203. onTabRenamed={mockOnTabRenamed}
  204. orgSlug={'test-org'}
  205. router={router}
  206. />
  207. );
  208. await userEvent.click(screen.getByRole('tab', {name: 'For Review 1000+'}));
  209. await userEvent.click(
  210. await screen.findByRole('button', {name: 'For Review Tab Options'})
  211. );
  212. await userEvent.click(await screen.findByRole('menuitemradio', {name: 'Rename'}));
  213. await userEvent.keyboard('{Control>}A{/Control}{Backspace}');
  214. await userEvent.keyboard('{enter}');
  215. // Tab name should not have changed
  216. expect(screen.getByRole('tab', {name: 'For Review 1000+'})).toBeInTheDocument();
  217. expect(mockOnTabRenamed).not.toHaveBeenCalled();
  218. });
  219. it('should discard changes if esc is pressed while renaming', async () => {
  220. render(
  221. <DraggableTabBar
  222. tabs={tabs}
  223. setTabs={jest.fn()}
  224. onTabRenamed={mockOnTabRenamed}
  225. orgSlug={'test-org'}
  226. router={router}
  227. />
  228. );
  229. await userEvent.click(screen.getByRole('tab', {name: 'For Review 1000+'}));
  230. await userEvent.click(
  231. await screen.findByRole('button', {name: 'For Review Tab Options'})
  232. );
  233. await userEvent.click(await screen.findByRole('menuitemradio', {name: 'Rename'}));
  234. await userEvent.keyboard('{Control>}A{/Control}{Backspace}');
  235. await userEvent.paste('New Name');
  236. await userEvent.keyboard('{esc}');
  237. expect(mockOnTabRenamed).not.toHaveBeenCalled();
  238. });
  239. it('should fire the onSave callback when save changes is pressed', async () => {
  240. render(
  241. <DraggableTabBar
  242. tabs={tabs}
  243. setTabs={jest.fn()}
  244. onSave={mockOnSave}
  245. orgSlug={'test-org'}
  246. router={router}
  247. />
  248. );
  249. await userEvent.click(screen.getByRole('tab', {name: 'Prioritized 20'}));
  250. await userEvent.click(
  251. await screen.findByRole('button', {name: 'Prioritized Tab Options'})
  252. );
  253. await userEvent.click(
  254. await screen.findByRole('menuitemradio', {name: 'Save Changes'})
  255. );
  256. expect(mockOnSave).toHaveBeenCalledWith('1');
  257. });
  258. it('should fire the onDiscard callback when discard is pressed', async () => {
  259. render(
  260. <DraggableTabBar
  261. tabs={tabs}
  262. setTabs={jest.fn()}
  263. onDiscard={mockOnDiscard}
  264. orgSlug={'test-org'}
  265. router={router}
  266. />
  267. );
  268. await userEvent.click(screen.getByRole('tab', {name: 'Prioritized 20'}));
  269. await userEvent.click(
  270. await screen.findByRole('button', {name: 'Prioritized Tab Options'})
  271. );
  272. await userEvent.click(
  273. await screen.findByRole('menuitemradio', {name: 'Discard Changes'})
  274. );
  275. expect(mockOnDiscard).toHaveBeenCalledWith('1');
  276. });
  277. it('should fire the onDelete callback when delete is pressed', async () => {
  278. render(
  279. <DraggableTabBar
  280. tabs={tabs}
  281. setTabs={jest.fn()}
  282. onDelete={mockOnDelete}
  283. orgSlug={'test-org'}
  284. router={router}
  285. />
  286. );
  287. await userEvent.click(screen.getByRole('tab', {name: 'For Review 1000+'}));
  288. await userEvent.click(
  289. await screen.findByRole('button', {name: 'For Review Tab Options'})
  290. );
  291. await userEvent.click(await screen.findByRole('menuitemradio', {name: 'Delete'}));
  292. expect(mockOnDelete).toHaveBeenCalledWith('2');
  293. });
  294. it('should fire the onDuplicate callback when duplicate is pressed', async () => {
  295. render(
  296. <DraggableTabBar
  297. tabs={tabs}
  298. setTabs={jest.fn()}
  299. onDuplicate={mockOnDuplicate}
  300. orgSlug={'test-org'}
  301. router={router}
  302. />
  303. );
  304. await userEvent.click(screen.getByRole('tab', {name: 'For Review 1000+'}));
  305. await userEvent.click(
  306. await screen.findByRole('button', {name: 'For Review Tab Options'})
  307. );
  308. await userEvent.click(
  309. await screen.findByRole('menuitemradio', {name: 'Duplicate'})
  310. );
  311. expect(mockOnDuplicate).toHaveBeenCalledWith('2');
  312. });
  313. it('should fire the onDiscardTempView callback when the discard button is pressed for a temp view', async () => {
  314. render(
  315. <DraggableTabBar
  316. tabs={tabs}
  317. setTabs={jest.fn()}
  318. onDiscardTempView={mockOnDiscardTempView}
  319. orgSlug={'test-org'}
  320. router={router}
  321. />
  322. );
  323. await userEvent.click(screen.getByRole('tab', {name: 'Unsaved'}));
  324. await userEvent.click(
  325. await screen.findByRole('button', {name: 'Unsaved Tab Options'})
  326. );
  327. await userEvent.click(await screen.findByRole('menuitemradio', {name: 'Discard'}));
  328. expect(mockOnDiscardTempView).toHaveBeenCalled();
  329. });
  330. it('should fire the onSaveTempView callback when the discard button is pressed for a temp view', async () => {
  331. render(
  332. <DraggableTabBar
  333. tabs={tabs}
  334. setTabs={jest.fn()}
  335. onSaveTempView={mockOnSaveTempView}
  336. orgSlug={'test-org'}
  337. router={router}
  338. />
  339. );
  340. await userEvent.click(screen.getByRole('tab', {name: 'Unsaved'}));
  341. await userEvent.click(
  342. await screen.findByRole('button', {name: 'Unsaved Tab Options'})
  343. );
  344. await userEvent.click(
  345. await screen.findByRole('menuitemradio', {name: 'Save View'})
  346. );
  347. expect(mockOnSaveTempView).toHaveBeenCalled();
  348. });
  349. it('should fire the onAddView callback when the add view button is pressed', async () => {
  350. render(
  351. <DraggableTabBar
  352. tabs={tabs}
  353. setTabs={jest.fn()}
  354. onAddView={mockOnAddView}
  355. orgSlug={'test-org'}
  356. router={router}
  357. />
  358. );
  359. await userEvent.click(screen.getByRole('button', {name: 'Add View'}));
  360. expect(mockOnAddView).toHaveBeenCalled();
  361. });
  362. });
  363. });