teamMembers.spec.jsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
  3. import {
  4. openInviteMembersModal,
  5. openTeamAccessRequestModal,
  6. } from 'sentry/actionCreators/modal';
  7. import {Client} from 'sentry/api';
  8. import TeamMembers from 'sentry/views/settings/organizationTeams/teamMembers';
  9. jest.mock('sentry/actionCreators/modal', () => ({
  10. openInviteMembersModal: jest.fn(),
  11. openTeamAccessRequestModal: jest.fn(),
  12. }));
  13. describe('TeamMembers', function () {
  14. let createMock;
  15. const organization = TestStubs.Organization();
  16. const team = TestStubs.Team();
  17. const managerTeam = TestStubs.Team({orgRole: 'manager'});
  18. const members = TestStubs.Members();
  19. const member = TestStubs.Member({
  20. id: '9',
  21. email: 'sentry9@test.com',
  22. name: 'Sentry 9 Name',
  23. });
  24. beforeEach(function () {
  25. Client.clearMockResponses();
  26. Client.addMockResponse({
  27. url: `/organizations/${organization.slug}/members/`,
  28. method: 'GET',
  29. body: [member],
  30. });
  31. Client.addMockResponse({
  32. url: `/teams/${organization.slug}/${team.slug}/members/`,
  33. method: 'GET',
  34. body: members,
  35. });
  36. Client.addMockResponse({
  37. url: `/teams/${organization.slug}/${team.slug}/`,
  38. method: 'GET',
  39. body: team,
  40. });
  41. Client.addMockResponse({
  42. url: `/teams/${organization.slug}/${managerTeam.slug}/`,
  43. method: 'GET',
  44. body: managerTeam,
  45. });
  46. createMock = Client.addMockResponse({
  47. url: `/organizations/${organization.slug}/members/${member.id}/teams/${team.slug}/`,
  48. method: 'POST',
  49. });
  50. });
  51. it('can add member to team with open membership', async function () {
  52. const org = TestStubs.Organization({access: [], openMembership: true});
  53. render(
  54. <TeamMembers
  55. params={{orgId: org.slug, teamId: team.slug}}
  56. organization={org}
  57. team={team}
  58. />
  59. );
  60. await userEvent.click(
  61. (
  62. await screen.findAllByRole('button', {name: 'Add Member'})
  63. )[0]
  64. );
  65. await userEvent.click(screen.getAllByTestId('letter_avatar-avatar')[0]);
  66. expect(createMock).toHaveBeenCalled();
  67. });
  68. it('can add member to team with team:admin permission', async function () {
  69. const org = TestStubs.Organization({access: ['team:admin'], openMembership: false});
  70. render(
  71. <TeamMembers
  72. params={{orgId: org.slug, teamId: team.slug}}
  73. organization={org}
  74. team={team}
  75. />
  76. );
  77. await userEvent.click(
  78. (
  79. await screen.findAllByRole('button', {name: 'Add Member'})
  80. )[0]
  81. );
  82. await userEvent.click(screen.getAllByTestId('letter_avatar-avatar')[0]);
  83. expect(createMock).toHaveBeenCalled();
  84. });
  85. it('can add member to team with org:write permission', async function () {
  86. const org = TestStubs.Organization({access: ['org:write'], openMembership: false});
  87. render(
  88. <TeamMembers
  89. params={{orgId: org.slug, teamId: team.slug}}
  90. organization={org}
  91. team={team}
  92. />
  93. );
  94. await userEvent.click(
  95. (
  96. await screen.findAllByRole('button', {name: 'Add Member'})
  97. )[0]
  98. );
  99. await userEvent.click(screen.getAllByTestId('letter_avatar-avatar')[0]);
  100. expect(createMock).toHaveBeenCalled();
  101. });
  102. it('can request access to add member to team without permission', async function () {
  103. const org = TestStubs.Organization({access: [], openMembership: false});
  104. render(
  105. <TeamMembers
  106. params={{orgId: org.slug, teamId: team.slug}}
  107. organization={org}
  108. team={team}
  109. />
  110. );
  111. await userEvent.click(
  112. (
  113. await screen.findAllByRole('button', {name: 'Add Member'})
  114. )[0]
  115. );
  116. await userEvent.click(screen.getAllByTestId('letter_avatar-avatar')[0]);
  117. expect(openTeamAccessRequestModal).toHaveBeenCalled();
  118. });
  119. it('can invite member from team dropdown with access', async function () {
  120. const {organization: org, routerContext} = initializeOrg({
  121. organization: TestStubs.Organization({
  122. access: ['team:admin'],
  123. openMembership: false,
  124. }),
  125. });
  126. render(
  127. <TeamMembers
  128. params={{orgId: org.slug, teamId: team.slug}}
  129. organization={org}
  130. team={team}
  131. />,
  132. {context: routerContext}
  133. );
  134. await userEvent.click(
  135. (
  136. await screen.findAllByRole('button', {name: 'Add Member'})
  137. )[0]
  138. );
  139. await userEvent.click(screen.getByTestId('invite-member'));
  140. expect(openInviteMembersModal).toHaveBeenCalled();
  141. });
  142. it('can invite member from team dropdown with access and `Open Membership` enabled', async function () {
  143. const {organization: org, routerContext} = initializeOrg({
  144. organization: TestStubs.Organization({
  145. access: ['team:admin'],
  146. openMembership: true,
  147. }),
  148. });
  149. render(
  150. <TeamMembers
  151. params={{orgId: org.slug, teamId: team.slug}}
  152. organization={org}
  153. team={team}
  154. />,
  155. {context: routerContext}
  156. );
  157. await userEvent.click(
  158. (
  159. await screen.findAllByRole('button', {name: 'Add Member'})
  160. )[0]
  161. );
  162. await userEvent.click(screen.getByTestId('invite-member'));
  163. expect(openInviteMembersModal).toHaveBeenCalled();
  164. });
  165. it('can invite member from team dropdown without access and `Open Membership` enabled', async function () {
  166. const {organization: org, routerContext} = initializeOrg({
  167. organization: TestStubs.Organization({access: [], openMembership: true}),
  168. });
  169. render(
  170. <TeamMembers
  171. params={{orgId: org.slug, teamId: team.slug}}
  172. organization={org}
  173. team={team}
  174. />,
  175. {context: routerContext}
  176. );
  177. await userEvent.click(
  178. (
  179. await screen.findAllByRole('button', {name: 'Add Member'})
  180. )[0]
  181. );
  182. await userEvent.click(screen.getByTestId('invite-member'));
  183. expect(openInviteMembersModal).toHaveBeenCalled();
  184. });
  185. it('can invite member from team dropdown without access and `Open Membership` disabled', async function () {
  186. const {organization: org, routerContext} = initializeOrg({
  187. organization: TestStubs.Organization({access: [], openMembership: false}),
  188. });
  189. render(
  190. <TeamMembers
  191. params={{orgId: org.slug, teamId: team.slug}}
  192. organization={org}
  193. team={team}
  194. />,
  195. {context: routerContext}
  196. );
  197. await userEvent.click(
  198. (
  199. await screen.findAllByRole('button', {name: 'Add Member'})
  200. )[0]
  201. );
  202. await userEvent.click(screen.getByTestId('invite-member'));
  203. expect(openInviteMembersModal).toHaveBeenCalled();
  204. });
  205. it('can remove member from team', async function () {
  206. const deleteMock = Client.addMockResponse({
  207. url: `/organizations/${organization.slug}/members/${members[0].id}/teams/${team.slug}/`,
  208. method: 'DELETE',
  209. });
  210. render(
  211. <TeamMembers
  212. params={{orgId: organization.slug, teamId: team.slug}}
  213. organization={organization}
  214. team={team}
  215. />
  216. );
  217. await screen.findAllByRole('button', {name: 'Add Member'});
  218. expect(deleteMock).not.toHaveBeenCalled();
  219. await userEvent.click(screen.getAllByRole('button', {name: 'Remove'})[0]);
  220. expect(deleteMock).toHaveBeenCalled();
  221. });
  222. it('can only remove self from team', async function () {
  223. const me = TestStubs.Member({
  224. id: '123',
  225. email: 'foo@example.com',
  226. });
  227. Client.addMockResponse({
  228. url: `/teams/${organization.slug}/${team.slug}/members/`,
  229. method: 'GET',
  230. body: [...members, me],
  231. });
  232. const deleteMock = Client.addMockResponse({
  233. url: `/organizations/${organization.slug}/members/${me.id}/teams/${team.slug}/`,
  234. method: 'DELETE',
  235. });
  236. const organizationMember = TestStubs.Organization({access: []});
  237. render(
  238. <TeamMembers
  239. params={{orgId: organization.slug, teamId: team.slug}}
  240. organization={organizationMember}
  241. team={team}
  242. />
  243. );
  244. await screen.findAllByRole('button', {name: 'Add Member'});
  245. expect(deleteMock).not.toHaveBeenCalled();
  246. expect(screen.getAllByTestId('letter_avatar-avatar')).toHaveLength(
  247. members.length + 1
  248. );
  249. // Can only remove self
  250. expect(screen.getByRole('button', {name: 'Remove'})).toBeInTheDocument();
  251. await userEvent.click(screen.getByRole('button', {name: 'Remove'}));
  252. expect(deleteMock).toHaveBeenCalled();
  253. });
  254. it('does not renders team-level roles', async function () {
  255. const me = TestStubs.Member({
  256. id: '123',
  257. email: 'foo@example.com',
  258. role: 'owner',
  259. });
  260. Client.addMockResponse({
  261. url: `/teams/${organization.slug}/${team.slug}/members/`,
  262. method: 'GET',
  263. body: [...members, me],
  264. });
  265. await render(
  266. <TeamMembers
  267. params={{orgId: organization.slug, teamId: team.slug}}
  268. organization={organization}
  269. team={team}
  270. />
  271. );
  272. const admins = screen.queryByText('Team Admin');
  273. expect(admins).not.toBeInTheDocument();
  274. const contributors = screen.queryByText('Team Contributor');
  275. expect(contributors).not.toBeInTheDocument();
  276. });
  277. it('renders team-level roles with flag', async function () {
  278. const manager = TestStubs.Member({
  279. id: '123',
  280. email: 'foo@example.com',
  281. orgRole: 'manager',
  282. });
  283. Client.addMockResponse({
  284. url: `/teams/${organization.slug}/${team.slug}/members/`,
  285. method: 'GET',
  286. body: [...members, manager],
  287. });
  288. const orgWithTeamRoles = TestStubs.Organization({features: ['team-roles']});
  289. await render(
  290. <TeamMembers
  291. params={{orgId: orgWithTeamRoles.slug, teamId: team.slug}}
  292. organization={orgWithTeamRoles}
  293. team={team}
  294. />
  295. );
  296. const admins = screen.queryAllByText('Team Admin');
  297. expect(admins).toHaveLength(3);
  298. const contributors = screen.queryAllByText('Contributor');
  299. expect(contributors).toHaveLength(2);
  300. });
  301. it('adding member to manager team makes them team admin', async function () {
  302. Client.addMockResponse({
  303. url: `/teams/${organization.slug}/${managerTeam.slug}/members/`,
  304. method: 'GET',
  305. body: [],
  306. });
  307. const orgWithTeamRoles = TestStubs.Organization({features: ['team-roles']});
  308. render(
  309. <TeamMembers
  310. params={{orgId: orgWithTeamRoles.slug, teamId: managerTeam.slug}}
  311. organization={orgWithTeamRoles}
  312. team={managerTeam}
  313. />
  314. );
  315. await userEvent.click(
  316. (
  317. await screen.findAllByRole('button', {name: 'Add Member'})
  318. )[0]
  319. );
  320. await userEvent.click(screen.getAllByTestId('letter_avatar-avatar')[0]);
  321. const admin = screen.queryByText('Team Admin');
  322. expect(admin).toBeInTheDocument();
  323. });
  324. it('cannot add or remove members if team is idp:provisioned', function () {
  325. const team2 = TestStubs.Team({
  326. flags: {
  327. 'idp:provisioned': true,
  328. },
  329. });
  330. const me = TestStubs.Member({
  331. id: '123',
  332. email: 'foo@example.com',
  333. role: 'owner',
  334. flags: {
  335. 'idp:provisioned': true,
  336. },
  337. });
  338. Client.clearMockResponses();
  339. Client.addMockResponse({
  340. url: `/organizations/${organization.slug}/members/`,
  341. method: 'GET',
  342. body: [...members, me],
  343. });
  344. Client.addMockResponse({
  345. url: `/teams/${organization.slug}/${team2.slug}/members/`,
  346. method: 'GET',
  347. body: members,
  348. });
  349. Client.addMockResponse({
  350. url: `/teams/${organization.slug}/${team2.slug}/`,
  351. method: 'GET',
  352. body: team2,
  353. });
  354. render(
  355. <TeamMembers
  356. params={{orgId: organization.slug, teamId: team2.slug}}
  357. organization={organization}
  358. team={team2}
  359. />
  360. );
  361. waitFor(() => {
  362. expect(screen.findByRole('button', {name: 'Add Member'})).toBeDisabled();
  363. expect(screen.findByRole('button', {name: 'Remove'})).toBeDisabled();
  364. });
  365. });
  366. it('cannot add or remove members or leave if team has org role and no access', function () {
  367. const team2 = TestStubs.Team({orgRole: 'manager'});
  368. const me = TestStubs.Member({
  369. id: '123',
  370. email: 'foo@example.com',
  371. role: 'member',
  372. });
  373. Client.clearMockResponses();
  374. Client.addMockResponse({
  375. url: `/organizations/${organization.slug}/members/`,
  376. method: 'GET',
  377. body: [...members, me],
  378. });
  379. Client.addMockResponse({
  380. url: `/teams/${organization.slug}/${team2.slug}/members/`,
  381. method: 'GET',
  382. body: members,
  383. });
  384. Client.addMockResponse({
  385. url: `/teams/${organization.slug}/${team2.slug}/`,
  386. method: 'GET',
  387. body: team2,
  388. });
  389. render(
  390. <TeamMembers
  391. params={{orgId: organization.slug, teamId: team2.slug}}
  392. organization={organization}
  393. team={team2}
  394. />
  395. );
  396. waitFor(() => {
  397. expect(screen.findByRole('button', {name: 'Add Member'})).toBeDisabled();
  398. expect(screen.findByRole('button', {name: 'Remove'})).toBeDisabled();
  399. expect(screen.findByRole('button', {name: 'Leave'})).toBeDisabled();
  400. });
  401. });
  402. });