draggableTabBar.spec.tsx 14 KB

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