projectTeams.spec.jsx 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. import {mountWithTheme} from 'sentry-test/enzyme';
  2. import {mountGlobalModal} from 'sentry-test/modal';
  3. import {act} from 'sentry-test/reactTestingLibrary';
  4. import * as modals from 'sentry/actionCreators/modal';
  5. import TeamStore from 'sentry/stores/teamStore';
  6. import App from 'sentry/views/app';
  7. import ProjectTeams from 'sentry/views/settings/project/projectTeams';
  8. jest.unmock('sentry/actionCreators/modal');
  9. describe('ProjectTeams', function () {
  10. let org;
  11. let project;
  12. const team1 = TestStubs.Team();
  13. const team2 = TestStubs.Team({
  14. id: '2',
  15. slug: 'team-slug-2',
  16. name: 'Team Name 2',
  17. hasAccess: true,
  18. });
  19. beforeEach(function () {
  20. jest.spyOn(modals, 'openCreateTeamModal');
  21. org = TestStubs.Organization();
  22. project = TestStubs.ProjectDetails();
  23. act(() => void TeamStore.loadInitialData([team1, team2]));
  24. MockApiClient.addMockResponse({
  25. url: `/projects/${org.slug}/${project.slug}/`,
  26. method: 'GET',
  27. body: project,
  28. });
  29. MockApiClient.addMockResponse({
  30. url: `/projects/${org.slug}/${project.slug}/teams/`,
  31. method: 'GET',
  32. body: [team1],
  33. });
  34. MockApiClient.addMockResponse({
  35. url: `/organizations/${org.slug}/teams/`,
  36. method: 'GET',
  37. body: [team1, team2],
  38. });
  39. });
  40. afterEach(function () {
  41. MockApiClient.clearMockResponses();
  42. modals.openCreateTeamModal.mockRestore();
  43. });
  44. it('renders', async function () {
  45. const wrapper = mountWithTheme(
  46. <ProjectTeams
  47. params={{orgId: org.slug, projectId: project.slug}}
  48. organization={org}
  49. />,
  50. TestStubs.routerContext()
  51. );
  52. // Wait for team list to fetch.
  53. await wrapper.update();
  54. expect(wrapper).toSnapshot();
  55. });
  56. it('can remove a team from project', async function () {
  57. MockApiClient.addMockResponse({
  58. url: `/projects/${org.slug}/${project.slug}/teams/`,
  59. method: 'GET',
  60. body: [team1, team2],
  61. });
  62. const endpoint = `/projects/${org.slug}/${project.slug}/teams/${team1.slug}/`;
  63. const mock = MockApiClient.addMockResponse({
  64. url: endpoint,
  65. method: 'DELETE',
  66. statusCode: 200,
  67. });
  68. const endpoint2 = `/projects/${org.slug}/${project.slug}/teams/${team2.slug}/`;
  69. const mock2 = MockApiClient.addMockResponse({
  70. url: endpoint2,
  71. method: 'DELETE',
  72. statusCode: 200,
  73. });
  74. const wrapper = mountWithTheme(
  75. <ProjectTeams
  76. params={{orgId: org.slug, projectId: project.slug}}
  77. organization={org}
  78. />,
  79. TestStubs.routerContext()
  80. );
  81. // Wait for team list to fetch.
  82. await wrapper.update();
  83. expect(mock).not.toHaveBeenCalled();
  84. // Click "Remove"
  85. wrapper.find('PanelBody Button').first().simulate('click');
  86. expect(mock).toHaveBeenCalledWith(
  87. endpoint,
  88. expect.objectContaining({
  89. method: 'DELETE',
  90. })
  91. );
  92. await tick();
  93. // Remove second team
  94. wrapper.update().find('PanelBody Button').first().simulate('click');
  95. // Modal opens because this is the last team in project
  96. // Click confirm
  97. const modal = await mountGlobalModal();
  98. modal.find('Button[priority="primary"]').simulate('click');
  99. expect(mock2).toHaveBeenCalledWith(
  100. endpoint2,
  101. expect.objectContaining({
  102. method: 'DELETE',
  103. })
  104. );
  105. });
  106. it('removes team from project when project team is not in org list', async function () {
  107. MockApiClient.clearMockResponses();
  108. MockApiClient.addMockResponse({
  109. url: `/projects/${org.slug}/${project.slug}/teams/`,
  110. method: 'GET',
  111. body: [team1, team2],
  112. });
  113. const endpoint = `/projects/${org.slug}/${project.slug}/teams/${team1.slug}/`;
  114. const mock = MockApiClient.addMockResponse({
  115. url: endpoint,
  116. method: 'DELETE',
  117. });
  118. const endpoint2 = `/projects/${org.slug}/${project.slug}/teams/${team2.slug}/`;
  119. const mock2 = MockApiClient.addMockResponse({
  120. url: endpoint2,
  121. method: 'DELETE',
  122. });
  123. MockApiClient.addMockResponse({
  124. url: `/organizations/${org.slug}/teams/`,
  125. method: 'GET',
  126. body: [
  127. TestStubs.Team({
  128. id: '3',
  129. slug: 'team-slug-3',
  130. name: 'Team Name 3',
  131. hasAccess: true,
  132. }),
  133. ],
  134. });
  135. const wrapper = mountWithTheme(
  136. <ProjectTeams
  137. params={{orgId: org.slug, projectId: project.slug}}
  138. organization={org}
  139. />,
  140. TestStubs.routerContext()
  141. );
  142. // Wait for team list to fetch.
  143. await wrapper.update();
  144. expect(mock).not.toHaveBeenCalled();
  145. // Click "Remove"
  146. wrapper.find('PanelBody Button').first().simulate('click');
  147. expect(mock).toHaveBeenCalledWith(
  148. endpoint,
  149. expect.objectContaining({
  150. method: 'DELETE',
  151. })
  152. );
  153. await tick();
  154. // Remove second team
  155. wrapper.update().find('PanelBody Button').first().simulate('click');
  156. // Modal opens because this is the last team in project
  157. // Click confirm
  158. const modal = await mountGlobalModal();
  159. modal.find('Button[priority="primary"]').simulate('click');
  160. expect(mock2).toHaveBeenCalledWith(
  161. endpoint2,
  162. expect.objectContaining({
  163. method: 'DELETE',
  164. })
  165. );
  166. });
  167. it('can associate a team with project', async function () {
  168. const endpoint = `/projects/${org.slug}/${project.slug}/teams/${team2.slug}/`;
  169. const mock = MockApiClient.addMockResponse({
  170. url: endpoint,
  171. method: 'POST',
  172. statusCode: 200,
  173. });
  174. const wrapper = mountWithTheme(
  175. <ProjectTeams
  176. params={{orgId: org.slug, projectId: project.slug}}
  177. organization={org}
  178. />,
  179. TestStubs.routerContext()
  180. );
  181. // Wait for team list to fetch.
  182. await wrapper.update();
  183. expect(mock).not.toHaveBeenCalled();
  184. // open dropdown
  185. wrapper.find('DropdownButton').simulate('click');
  186. // click a team
  187. const el = wrapper.find('AutoCompleteItem').first();
  188. el.simulate('click');
  189. expect(mock).toHaveBeenCalledWith(
  190. endpoint,
  191. expect.objectContaining({
  192. method: 'POST',
  193. })
  194. );
  195. });
  196. it('creates a new team adds it to current project using the "create team modal" in dropdown', async function () {
  197. MockApiClient.addMockResponse({
  198. url: '/internal/health/',
  199. body: {},
  200. });
  201. MockApiClient.addMockResponse({
  202. url: '/assistant/?v2',
  203. body: {},
  204. });
  205. MockApiClient.addMockResponse({
  206. url: '/organizations/',
  207. body: [org],
  208. });
  209. const addTeamToProject = MockApiClient.addMockResponse({
  210. url: `/projects/${org.slug}/${project.slug}/teams/new-team/`,
  211. method: 'POST',
  212. });
  213. const createTeam = MockApiClient.addMockResponse({
  214. url: `/organizations/${org.slug}/teams/`,
  215. method: 'POST',
  216. body: {slug: 'new-team'},
  217. });
  218. const wrapper = mountWithTheme(
  219. <App params={{orgId: org.slug}}>
  220. <ProjectTeams
  221. params={{orgId: org.slug, projectId: project.slug}}
  222. project={project}
  223. organization={org}
  224. />
  225. </App>,
  226. TestStubs.routerContext()
  227. );
  228. // Wait for team list to fetch.
  229. await wrapper.update();
  230. // Open the dropdown
  231. wrapper.find('TeamSelect DropdownButton').simulate('click');
  232. // Click "Create Team" inside of dropdown
  233. wrapper.find('TeamSelect StyledCreateTeamLink').simulate('click');
  234. // action creator to open "create team modal" is called
  235. expect(modals.openCreateTeamModal).toHaveBeenCalledWith(
  236. expect.objectContaining({
  237. project: expect.objectContaining({
  238. slug: project.slug,
  239. }),
  240. organization: expect.objectContaining({
  241. slug: org.slug,
  242. }),
  243. })
  244. );
  245. // Two ticks are required
  246. await act(tick);
  247. await act(tick);
  248. wrapper.update();
  249. wrapper.find('input[name="slug"]').simulate('change', {target: {value: 'new-team'}});
  250. wrapper.find('[data-test-id="create-team-form"] form').simulate('submit');
  251. expect(createTeam).toHaveBeenCalledTimes(1);
  252. expect(createTeam).toHaveBeenCalledWith(
  253. '/organizations/org-slug/teams/',
  254. expect.objectContaining({
  255. data: {slug: 'new-team'},
  256. })
  257. );
  258. await tick();
  259. expect(addTeamToProject).toHaveBeenCalledTimes(1);
  260. expect(addTeamToProject).toHaveBeenCalledWith(
  261. '/projects/org-slug/project-slug/teams/new-team/',
  262. expect.anything()
  263. );
  264. });
  265. });