projectSourceMaps.spec.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {
  3. render,
  4. renderGlobalModal,
  5. screen,
  6. userEvent,
  7. waitFor,
  8. } from 'sentry-test/reactTestingLibrary';
  9. import {ProjectSourceMaps} from 'sentry/views/settings/projectSourceMaps/projectSourceMaps';
  10. function renderReleaseBundlesMockRequests({
  11. orgSlug,
  12. projectSlug,
  13. empty,
  14. }: {
  15. orgSlug: string;
  16. projectSlug: string;
  17. empty?: boolean;
  18. }) {
  19. const sourceMaps = MockApiClient.addMockResponse({
  20. url: `/projects/${orgSlug}/${projectSlug}/files/source-maps/`,
  21. body: empty
  22. ? []
  23. : [
  24. TestStubs.SourceMapArchive(),
  25. TestStubs.SourceMapArchive({
  26. id: 2,
  27. name: 'abc',
  28. fileCount: 3,
  29. date: '2023-05-06T13:41:00Z',
  30. }),
  31. ],
  32. });
  33. return {sourceMaps};
  34. }
  35. function renderDebugIdBundlesMockRequests({
  36. orgSlug,
  37. projectSlug,
  38. empty,
  39. }: {
  40. orgSlug: string;
  41. projectSlug: string;
  42. empty?: boolean;
  43. }) {
  44. const artifactBundles = MockApiClient.addMockResponse({
  45. url: `/projects/${orgSlug}/${projectSlug}/files/artifact-bundles/`,
  46. body: empty ? [] : TestStubs.SourceMapsDebugIDBundles(),
  47. });
  48. const artifactBundlesDeletion = MockApiClient.addMockResponse({
  49. url: `/projects/${orgSlug}/${projectSlug}/files/artifact-bundles/`,
  50. method: 'DELETE',
  51. });
  52. return {artifactBundles, artifactBundlesDeletion};
  53. }
  54. describe('ProjectSourceMaps', function () {
  55. describe('Release Bundles', function () {
  56. it('renders default state', async function () {
  57. const {organization, route, project, router, routerContext} = initializeOrg({
  58. router: {
  59. location: {
  60. query: {},
  61. pathname: `/settings/${initializeOrg().organization.slug}/projects/${
  62. initializeOrg().project.slug
  63. }/source-maps/release-bundles/`,
  64. },
  65. params: {},
  66. },
  67. });
  68. const mockRequests = renderReleaseBundlesMockRequests({
  69. orgSlug: organization.slug,
  70. projectSlug: project.slug,
  71. });
  72. render(
  73. <ProjectSourceMaps
  74. location={routerContext.context.location}
  75. project={project}
  76. route={route}
  77. routeParams={{orgId: organization.slug, projectId: project.slug}}
  78. router={router}
  79. routes={[]}
  80. params={{orgId: organization.slug, projectId: project.slug}}
  81. />,
  82. {context: routerContext, organization}
  83. );
  84. // Title
  85. expect(screen.getByRole('heading', {name: 'Source Maps'})).toBeInTheDocument();
  86. // Active tab
  87. const tabs = screen.getAllByRole('listitem');
  88. expect(tabs).toHaveLength(2);
  89. // Tab 1
  90. expect(tabs[0]).toHaveTextContent('Artifact Bundles');
  91. expect(tabs[0]).not.toHaveClass('active');
  92. // Tab 2
  93. expect(tabs[1]).toHaveTextContent('Release Bundles');
  94. expect(tabs[1]).toHaveClass('active');
  95. // Search bar
  96. expect(screen.getByPlaceholderText('Filter by Name')).toBeInTheDocument();
  97. // Date Uploaded can be sorted
  98. await userEvent.hover(screen.getByTestId('icon-arrow'));
  99. expect(await screen.findByText('Switch to ascending order')).toBeInTheDocument();
  100. await userEvent.click(screen.getByTestId('icon-arrow'));
  101. await waitFor(() => {
  102. expect(mockRequests.sourceMaps).toHaveBeenLastCalledWith(
  103. '/projects/org-slug/project-slug/files/source-maps/',
  104. expect.objectContaining({
  105. query: expect.objectContaining({
  106. sortBy: '-date_added',
  107. }),
  108. })
  109. );
  110. });
  111. // Active tab contains correct link
  112. expect(screen.getByRole('link', {name: /Release Bundles/})).toHaveAttribute(
  113. 'href',
  114. '/settings/org-slug/projects/project-slug/source-maps/release-bundles/'
  115. );
  116. // Name
  117. expect(await screen.findByRole('link', {name: '1234'})).toBeInTheDocument();
  118. // Artifacts
  119. expect(screen.getByText('0')).toBeInTheDocument();
  120. // Date
  121. expect(screen.getByText('May 6, 2020 1:41 PM UTC')).toBeInTheDocument();
  122. // Delete buttons (this example renders 2 rows)
  123. expect(screen.getAllByRole('button', {name: 'Remove All Artifacts'})).toHaveLength(
  124. 2
  125. );
  126. expect(
  127. screen.getAllByRole('button', {name: 'Remove All Artifacts'})[0]
  128. ).toBeEnabled();
  129. renderGlobalModal();
  130. // Delete item displays a confirmation modal
  131. await userEvent.click(
  132. screen.getAllByRole('button', {name: 'Remove All Artifacts'})[0]
  133. );
  134. expect(
  135. await screen.findByText(
  136. 'Are you sure you want to remove all artifacts in this archive?'
  137. )
  138. ).toBeInTheDocument();
  139. // Close modal
  140. await userEvent.click(screen.getByRole('button', {name: 'Cancel'}));
  141. // Switch tab
  142. await userEvent.click(screen.getByRole('link', {name: 'Artifact Bundles'}));
  143. expect(router.push).toHaveBeenCalledWith({
  144. pathname:
  145. '/settings/org-slug/projects/project-slug/source-maps/artifact-bundles/',
  146. query: undefined,
  147. });
  148. });
  149. it('renders empty state', async function () {
  150. const {organization, route, project, router, routerContext} = initializeOrg({
  151. router: {
  152. location: {
  153. query: {},
  154. pathname: `/settings/${initializeOrg().organization.slug}/projects/${
  155. initializeOrg().project.slug
  156. }/source-maps/release-bundles/`,
  157. },
  158. params: {},
  159. },
  160. });
  161. renderReleaseBundlesMockRequests({
  162. orgSlug: organization.slug,
  163. projectSlug: project.slug,
  164. empty: true,
  165. });
  166. render(
  167. <ProjectSourceMaps
  168. location={routerContext.context.location}
  169. project={project}
  170. route={route}
  171. routeParams={{orgId: organization.slug, projectId: project.slug}}
  172. router={router}
  173. routes={[]}
  174. params={{orgId: organization.slug, projectId: project.slug}}
  175. />,
  176. {context: routerContext, organization}
  177. );
  178. expect(
  179. await screen.findByText('No release bundles found for this project.')
  180. ).toBeInTheDocument();
  181. });
  182. });
  183. describe('Artifact Bundles', function () {
  184. it('renders default state', async function () {
  185. const {organization, route, project, router, routerContext} = initializeOrg({
  186. router: {
  187. location: {
  188. query: {},
  189. pathname: `/settings/${initializeOrg().organization.slug}/projects/${
  190. initializeOrg().project.slug
  191. }/source-maps/artifact-bundles/`,
  192. },
  193. params: {},
  194. },
  195. });
  196. const mockRequests = renderDebugIdBundlesMockRequests({
  197. orgSlug: organization.slug,
  198. projectSlug: project.slug,
  199. });
  200. render(
  201. <ProjectSourceMaps
  202. location={routerContext.context.location}
  203. project={project}
  204. route={route}
  205. routeParams={{orgId: organization.slug, projectId: project.slug}}
  206. router={router}
  207. routes={[]}
  208. params={{orgId: organization.slug, projectId: project.slug}}
  209. />,
  210. {context: routerContext, organization}
  211. );
  212. // Title
  213. expect(screen.getByRole('heading', {name: 'Source Maps'})).toBeInTheDocument();
  214. // Active tab
  215. const tabs = screen.getAllByRole('listitem');
  216. expect(tabs).toHaveLength(2);
  217. // Tab 1
  218. expect(tabs[0]).toHaveTextContent('Artifact Bundles');
  219. expect(tabs[0]).toHaveClass('active');
  220. // Tab 2
  221. expect(tabs[1]).toHaveTextContent('Release Bundles');
  222. expect(tabs[1]).not.toHaveClass('active');
  223. // Search bar
  224. expect(screen.getByPlaceholderText('Filter by Bundle ID')).toBeInTheDocument();
  225. // Date Uploaded can be sorted
  226. await userEvent.hover(screen.getByTestId('icon-arrow'));
  227. expect(await screen.findByText('Switch to ascending order')).toBeInTheDocument();
  228. await userEvent.click(screen.getByTestId('icon-arrow'));
  229. await waitFor(() => {
  230. expect(mockRequests.artifactBundles).toHaveBeenLastCalledWith(
  231. '/projects/org-slug/project-slug/files/artifact-bundles/',
  232. expect.objectContaining({
  233. query: expect.objectContaining({
  234. sortBy: '-date_added',
  235. }),
  236. })
  237. );
  238. });
  239. // Artifacts
  240. expect(screen.getByText('39')).toBeInTheDocument();
  241. // Date Uploaded
  242. expect(screen.getByText('Mar 8, 2023 9:53 AM UTC')).toBeInTheDocument();
  243. // Delete button
  244. expect(screen.getByRole('button', {name: 'Remove All Artifacts'})).toBeEnabled();
  245. // Click on bundle id
  246. await userEvent.click(
  247. screen.getByRole('link', {name: 'b916a646-2c6b-4e45-af4c-409830a44e0e'})
  248. );
  249. expect(router.push).toHaveBeenLastCalledWith(
  250. '/settings/org-slug/projects/project-slug/source-maps/artifact-bundles/b916a646-2c6b-4e45-af4c-409830a44e0e'
  251. );
  252. renderGlobalModal();
  253. // Delete item displays a confirmation modal
  254. await userEvent.click(screen.getByRole('button', {name: 'Remove All Artifacts'}));
  255. expect(
  256. await screen.findByText(
  257. 'Are you sure you want to remove all artifacts in this archive?'
  258. )
  259. ).toBeInTheDocument();
  260. // Close modal
  261. await userEvent.click(screen.getByRole('button', {name: 'Confirm'}));
  262. await waitFor(() => {
  263. expect(mockRequests.artifactBundlesDeletion).toHaveBeenLastCalledWith(
  264. '/projects/org-slug/project-slug/files/artifact-bundles/',
  265. expect.objectContaining({
  266. query: expect.objectContaining({
  267. bundleId: 'b916a646-2c6b-4e45-af4c-409830a44e0e',
  268. }),
  269. })
  270. );
  271. });
  272. // Switch tab
  273. await userEvent.click(screen.getByRole('link', {name: /Release Bundles/}));
  274. expect(router.push).toHaveBeenCalledWith({
  275. pathname: '/settings/org-slug/projects/project-slug/source-maps/release-bundles/',
  276. query: undefined,
  277. });
  278. });
  279. it('renders empty state', async function () {
  280. const {organization, route, project, router, routerContext} = initializeOrg({
  281. router: {
  282. location: {
  283. query: {},
  284. pathname: `/settings/${initializeOrg().organization.slug}/projects/${
  285. initializeOrg().project.slug
  286. }/source-maps/artifact-bundles/`,
  287. },
  288. params: {},
  289. },
  290. });
  291. renderDebugIdBundlesMockRequests({
  292. orgSlug: organization.slug,
  293. projectSlug: project.slug,
  294. empty: true,
  295. });
  296. render(
  297. <ProjectSourceMaps
  298. location={routerContext.context.location}
  299. project={project}
  300. route={route}
  301. routeParams={{orgId: organization.slug, projectId: project.slug}}
  302. router={router}
  303. routes={[]}
  304. params={{orgId: organization.slug, projectId: project.slug}}
  305. />,
  306. {context: routerContext, organization}
  307. );
  308. expect(
  309. await screen.findByText('No debug ID bundles found for this project.')
  310. ).toBeInTheDocument();
  311. });
  312. });
  313. });