draggableTabBar.spec.tsx 14 KB

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