projectTeams.spec.jsx 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  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 TeamStore from 'sentry/stores/teamStore';
  10. import ProjectTeams from 'sentry/views/settings/project/projectTeams';
  11. describe('ProjectTeams', function () {
  12. let org;
  13. let project;
  14. let routerContext;
  15. const team1 = TestStubs.Team();
  16. const team2 = TestStubs.Team({
  17. id: '2',
  18. slug: 'team-slug-2',
  19. name: 'Team Name 2',
  20. hasAccess: true,
  21. });
  22. beforeEach(function () {
  23. const initialData = initializeOrg();
  24. org = initialData.organization;
  25. project = initialData.project;
  26. routerContext = initialData.routerContext;
  27. TeamStore.loadInitialData([team1, team2]);
  28. MockApiClient.addMockResponse({
  29. url: `/projects/${org.slug}/${project.slug}/`,
  30. method: 'GET',
  31. body: project,
  32. });
  33. MockApiClient.addMockResponse({
  34. url: `/projects/${org.slug}/${project.slug}/teams/`,
  35. method: 'GET',
  36. body: [team1],
  37. });
  38. MockApiClient.addMockResponse({
  39. url: `/organizations/${org.slug}/teams/`,
  40. method: 'GET',
  41. body: [team1, team2],
  42. });
  43. });
  44. afterEach(function () {
  45. MockApiClient.clearMockResponses();
  46. });
  47. it('renders', function () {
  48. const {container} = render(
  49. <ProjectTeams
  50. params={{projectId: project.slug}}
  51. organization={org}
  52. project={project}
  53. />
  54. );
  55. expect(container).toSnapshot();
  56. });
  57. it('can remove a team from project', async function () {
  58. MockApiClient.addMockResponse({
  59. url: `/projects/${org.slug}/${project.slug}/teams/`,
  60. method: 'GET',
  61. body: [team1, team2],
  62. });
  63. const endpoint = `/projects/${org.slug}/${project.slug}/teams/${team1.slug}/`;
  64. const mock = MockApiClient.addMockResponse({
  65. url: endpoint,
  66. method: 'DELETE',
  67. statusCode: 200,
  68. });
  69. const endpoint2 = `/projects/${org.slug}/${project.slug}/teams/${team2.slug}/`;
  70. const mock2 = MockApiClient.addMockResponse({
  71. url: endpoint2,
  72. method: 'DELETE',
  73. statusCode: 200,
  74. });
  75. render(
  76. <ProjectTeams
  77. params={{projectId: project.slug}}
  78. organization={org}
  79. project={project}
  80. />
  81. );
  82. expect(mock).not.toHaveBeenCalled();
  83. await userEvent.click(screen.getAllByRole('button', {name: 'Remove'})[0]);
  84. expect(mock).toHaveBeenCalledWith(
  85. endpoint,
  86. expect.objectContaining({
  87. method: 'DELETE',
  88. })
  89. );
  90. // Row should be removed
  91. expect(screen.queryByText('#team-slug')).not.toBeInTheDocument();
  92. // Remove second team
  93. await userEvent.click(screen.getAllByRole('button', {name: 'Remove'})[0]);
  94. // Modal opens because this is the last team in project
  95. renderGlobalModal();
  96. expect(screen.getByRole('dialog')).toBeInTheDocument();
  97. await userEvent.click(screen.getByTestId('confirm-button'));
  98. expect(mock2).toHaveBeenCalledWith(
  99. endpoint2,
  100. expect.objectContaining({
  101. method: 'DELETE',
  102. })
  103. );
  104. });
  105. it('removes team from project when project team is not in org list', async function () {
  106. MockApiClient.clearMockResponses();
  107. MockApiClient.addMockResponse({
  108. url: `/projects/${org.slug}/${project.slug}/teams/`,
  109. method: 'GET',
  110. body: [team1, team2],
  111. });
  112. const endpoint = `/projects/${org.slug}/${project.slug}/teams/${team1.slug}/`;
  113. const mock = MockApiClient.addMockResponse({
  114. url: endpoint,
  115. method: 'DELETE',
  116. });
  117. const endpoint2 = `/projects/${org.slug}/${project.slug}/teams/${team2.slug}/`;
  118. const mock2 = MockApiClient.addMockResponse({
  119. url: endpoint2,
  120. method: 'DELETE',
  121. });
  122. MockApiClient.addMockResponse({
  123. url: `/organizations/${org.slug}/teams/`,
  124. method: 'GET',
  125. body: [
  126. TestStubs.Team({
  127. id: '3',
  128. slug: 'team-slug-3',
  129. name: 'Team Name 3',
  130. hasAccess: true,
  131. }),
  132. ],
  133. });
  134. render(
  135. <ProjectTeams
  136. params={{projectId: project.slug}}
  137. organization={org}
  138. project={project}
  139. />
  140. );
  141. expect(mock).not.toHaveBeenCalled();
  142. // Click "Remove"
  143. await userEvent.click(screen.getAllByRole('button', {name: 'Remove'})[0]);
  144. expect(mock).toHaveBeenCalledWith(
  145. endpoint,
  146. expect.objectContaining({
  147. method: 'DELETE',
  148. })
  149. );
  150. expect(screen.queryByText('#team-slug')).not.toBeInTheDocument();
  151. // Remove second team
  152. await userEvent.click(screen.getAllByRole('button', {name: 'Remove'})[0]);
  153. // Modal opens because this is the last team in project
  154. renderGlobalModal();
  155. expect(screen.getByRole('dialog')).toBeInTheDocument();
  156. // Click confirm
  157. await userEvent.click(screen.getByTestId('confirm-button'));
  158. expect(mock2).toHaveBeenCalledWith(
  159. endpoint2,
  160. expect.objectContaining({
  161. method: 'DELETE',
  162. })
  163. );
  164. });
  165. it('can associate a team with project', async function () {
  166. const endpoint = `/projects/${org.slug}/${project.slug}/teams/${team2.slug}/`;
  167. const mock = MockApiClient.addMockResponse({
  168. url: endpoint,
  169. method: 'POST',
  170. statusCode: 200,
  171. });
  172. render(
  173. <ProjectTeams
  174. params={{projectId: project.slug}}
  175. organization={org}
  176. project={project}
  177. />
  178. );
  179. expect(mock).not.toHaveBeenCalled();
  180. // Add a team
  181. await userEvent.click(screen.getAllByRole('button', {name: 'Add Team'})[1]);
  182. await userEvent.click(screen.getByText('#team-slug-2'));
  183. expect(mock).toHaveBeenCalledWith(
  184. endpoint,
  185. expect.objectContaining({
  186. method: 'POST',
  187. })
  188. );
  189. });
  190. it('creates a new team adds it to current project using the "create team modal" in dropdown', async function () {
  191. MockApiClient.addMockResponse({
  192. url: '/internal/health/',
  193. body: {},
  194. });
  195. MockApiClient.addMockResponse({
  196. url: '/assistant/',
  197. body: {},
  198. });
  199. MockApiClient.addMockResponse({
  200. url: '/organizations/',
  201. body: [org],
  202. });
  203. const addTeamToProject = MockApiClient.addMockResponse({
  204. url: `/projects/${org.slug}/${project.slug}/teams/new-team/`,
  205. method: 'POST',
  206. });
  207. const createTeam = MockApiClient.addMockResponse({
  208. url: `/organizations/${org.slug}/teams/`,
  209. method: 'POST',
  210. body: {slug: 'new-team'},
  211. });
  212. render(
  213. <ProjectTeams
  214. params={{projectId: project.slug}}
  215. project={project}
  216. organization={org}
  217. />,
  218. {context: routerContext}
  219. );
  220. // Add new team
  221. await userEvent.click(screen.getAllByRole('button', {name: 'Add Team'})[1]);
  222. // XXX(epurkhiser): Create Team should really be a button
  223. await userEvent.click(screen.getByRole('link', {name: 'Create Team'}));
  224. renderGlobalModal();
  225. await screen.findByRole('dialog');
  226. await userEvent.type(screen.getByRole('textbox', {name: 'Team Name'}), 'new-team');
  227. await userEvent.click(screen.getByRole('button', {name: 'Create Team'}));
  228. await waitFor(() => expect(createTeam).toHaveBeenCalledTimes(1));
  229. expect(createTeam).toHaveBeenCalledWith(
  230. '/organizations/org-slug/teams/',
  231. expect.objectContaining({
  232. data: {slug: 'new-team'},
  233. })
  234. );
  235. expect(addTeamToProject).toHaveBeenCalledTimes(1);
  236. expect(addTeamToProject).toHaveBeenCalledWith(
  237. '/projects/org-slug/project-slug/teams/new-team/',
  238. expect.anything()
  239. );
  240. });
  241. });