projectSourceMaps.spec.tsx 10 KB

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