teamMembers.spec.jsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  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 members = TestStubs.Members();
  18. const member = TestStubs.Member({
  19. id: '9',
  20. email: 'sentry9@test.com',
  21. name: 'Sentry 9 Name',
  22. });
  23. beforeEach(function () {
  24. Client.clearMockResponses();
  25. Client.addMockResponse({
  26. url: `/organizations/${organization.slug}/members/`,
  27. method: 'GET',
  28. body: [member],
  29. });
  30. Client.addMockResponse({
  31. url: `/teams/${organization.slug}/${team.slug}/members/`,
  32. method: 'GET',
  33. body: members,
  34. });
  35. Client.addMockResponse({
  36. url: `/teams/${organization.slug}/${team.slug}/`,
  37. method: 'GET',
  38. body: team,
  39. });
  40. createMock = Client.addMockResponse({
  41. url: `/organizations/${organization.slug}/members/${member.id}/teams/${team.slug}/`,
  42. method: 'POST',
  43. });
  44. });
  45. it('can add member to team with open membership', async function () {
  46. const org = TestStubs.Organization({access: [], openMembership: true});
  47. render(
  48. <TeamMembers
  49. params={{orgId: org.slug, teamId: team.slug}}
  50. organization={org}
  51. team={team}
  52. />
  53. );
  54. userEvent.click((await screen.findAllByRole('button', {name: 'Add Member'}))[0]);
  55. userEvent.click(screen.getAllByTestId('letter_avatar-avatar')[0]);
  56. expect(createMock).toHaveBeenCalled();
  57. });
  58. it('can add member to team with team:admin permission', async function () {
  59. const org = TestStubs.Organization({access: ['team:admin'], openMembership: false});
  60. render(
  61. <TeamMembers
  62. params={{orgId: org.slug, teamId: team.slug}}
  63. organization={org}
  64. team={team}
  65. />
  66. );
  67. userEvent.click((await screen.findAllByRole('button', {name: 'Add Member'}))[0]);
  68. userEvent.click(screen.getAllByTestId('letter_avatar-avatar')[0]);
  69. expect(createMock).toHaveBeenCalled();
  70. });
  71. it('can add member to team with org:write permission', async function () {
  72. const org = TestStubs.Organization({access: ['org:write'], openMembership: false});
  73. render(
  74. <TeamMembers
  75. params={{orgId: org.slug, teamId: team.slug}}
  76. organization={org}
  77. team={team}
  78. />
  79. );
  80. userEvent.click((await screen.findAllByRole('button', {name: 'Add Member'}))[0]);
  81. userEvent.click(screen.getAllByTestId('letter_avatar-avatar')[0]);
  82. expect(createMock).toHaveBeenCalled();
  83. });
  84. it('can request access to add member to team without permission', async function () {
  85. const org = TestStubs.Organization({access: [], openMembership: false});
  86. render(
  87. <TeamMembers
  88. params={{orgId: org.slug, teamId: team.slug}}
  89. organization={org}
  90. team={team}
  91. />
  92. );
  93. userEvent.click((await screen.findAllByRole('button', {name: 'Add Member'}))[0]);
  94. userEvent.click(screen.getAllByTestId('letter_avatar-avatar')[0]);
  95. expect(openTeamAccessRequestModal).toHaveBeenCalled();
  96. });
  97. it('can invite member from team dropdown with access', async function () {
  98. const {organization: org, routerContext} = initializeOrg({
  99. organization: TestStubs.Organization({
  100. access: ['team:admin'],
  101. openMembership: false,
  102. }),
  103. });
  104. render(
  105. <TeamMembers
  106. params={{orgId: org.slug, teamId: team.slug}}
  107. organization={org}
  108. team={team}
  109. />,
  110. {context: routerContext}
  111. );
  112. userEvent.click((await screen.findAllByRole('button', {name: 'Add Member'}))[0]);
  113. userEvent.click(screen.getByTestId('invite-member'));
  114. expect(openInviteMembersModal).toHaveBeenCalled();
  115. });
  116. it('can invite member from team dropdown with access and `Open Membership` enabled', async function () {
  117. const {organization: org, routerContext} = initializeOrg({
  118. organization: TestStubs.Organization({
  119. access: ['team:admin'],
  120. openMembership: true,
  121. }),
  122. });
  123. render(
  124. <TeamMembers
  125. params={{orgId: org.slug, teamId: team.slug}}
  126. organization={org}
  127. team={team}
  128. />,
  129. {context: routerContext}
  130. );
  131. userEvent.click((await screen.findAllByRole('button', {name: 'Add Member'}))[0]);
  132. userEvent.click(screen.getByTestId('invite-member'));
  133. expect(openInviteMembersModal).toHaveBeenCalled();
  134. });
  135. it('can invite member from team dropdown without access and `Open Membership` enabled', async function () {
  136. const {organization: org, routerContext} = initializeOrg({
  137. organization: TestStubs.Organization({access: [], openMembership: true}),
  138. });
  139. render(
  140. <TeamMembers
  141. params={{orgId: org.slug, teamId: team.slug}}
  142. organization={org}
  143. team={team}
  144. />,
  145. {context: routerContext}
  146. );
  147. userEvent.click((await screen.findAllByRole('button', {name: 'Add Member'}))[0]);
  148. userEvent.click(screen.getByTestId('invite-member'));
  149. expect(openInviteMembersModal).toHaveBeenCalled();
  150. });
  151. it('can invite member from team dropdown without access and `Open Membership` disabled', async function () {
  152. const {organization: org, routerContext} = initializeOrg({
  153. organization: TestStubs.Organization({access: [], openMembership: false}),
  154. });
  155. render(
  156. <TeamMembers
  157. params={{orgId: org.slug, teamId: team.slug}}
  158. organization={org}
  159. team={team}
  160. />,
  161. {context: routerContext}
  162. );
  163. userEvent.click((await screen.findAllByRole('button', {name: 'Add Member'}))[0]);
  164. userEvent.click(screen.getByTestId('invite-member'));
  165. expect(openInviteMembersModal).toHaveBeenCalled();
  166. });
  167. it('can remove member from team', async function () {
  168. const deleteMock = Client.addMockResponse({
  169. url: `/organizations/${organization.slug}/members/${members[0].id}/teams/${team.slug}/`,
  170. method: 'DELETE',
  171. });
  172. render(
  173. <TeamMembers
  174. params={{orgId: organization.slug, teamId: team.slug}}
  175. organization={organization}
  176. team={team}
  177. />
  178. );
  179. await screen.findAllByRole('button', {name: 'Add Member'});
  180. expect(deleteMock).not.toHaveBeenCalled();
  181. userEvent.click(screen.getAllByRole('button', {name: 'Remove'})[0]);
  182. expect(deleteMock).toHaveBeenCalled();
  183. });
  184. it('can only remove self from team', async function () {
  185. const me = TestStubs.Member({
  186. id: '123',
  187. email: 'foo@example.com',
  188. });
  189. Client.addMockResponse({
  190. url: `/teams/${organization.slug}/${team.slug}/members/`,
  191. method: 'GET',
  192. body: [...members, me],
  193. });
  194. const deleteMock = Client.addMockResponse({
  195. url: `/organizations/${organization.slug}/members/${me.id}/teams/${team.slug}/`,
  196. method: 'DELETE',
  197. });
  198. const organizationMember = TestStubs.Organization({access: []});
  199. render(
  200. <TeamMembers
  201. params={{orgId: organization.slug, teamId: team.slug}}
  202. organization={organizationMember}
  203. team={team}
  204. />
  205. );
  206. await screen.findAllByRole('button', {name: 'Add Member'});
  207. expect(deleteMock).not.toHaveBeenCalled();
  208. expect(screen.getAllByTestId('letter_avatar-avatar')).toHaveLength(
  209. members.length + 1
  210. );
  211. // Can only remove self
  212. expect(screen.getByRole('button', {name: 'Remove'})).toBeInTheDocument();
  213. userEvent.click(screen.getByRole('button', {name: 'Remove'}));
  214. expect(deleteMock).toHaveBeenCalled();
  215. });
  216. it('does not renders team-level roles', async function () {
  217. const me = TestStubs.Member({
  218. id: '123',
  219. email: 'foo@example.com',
  220. role: 'owner',
  221. });
  222. Client.addMockResponse({
  223. url: `/teams/${organization.slug}/${team.slug}/members/`,
  224. method: 'GET',
  225. body: [...members, me],
  226. });
  227. await render(
  228. <TeamMembers
  229. params={{orgId: organization.slug, teamId: team.slug}}
  230. organization={organization}
  231. team={team}
  232. />
  233. );
  234. const admins = screen.queryByText('Team Admin');
  235. expect(admins).not.toBeInTheDocument();
  236. const contributors = screen.queryByText('Team Contributor');
  237. expect(contributors).not.toBeInTheDocument();
  238. });
  239. it('renders team-level roles with flag', async function () {
  240. const manager = TestStubs.Member({
  241. id: '123',
  242. email: 'foo@example.com',
  243. orgRole: 'manager',
  244. });
  245. Client.addMockResponse({
  246. url: `/teams/${organization.slug}/${team.slug}/members/`,
  247. method: 'GET',
  248. body: [...members, manager],
  249. });
  250. const orgWithTeamRoles = TestStubs.Organization({features: ['team-roles']});
  251. await render(
  252. <TeamMembers
  253. params={{orgId: orgWithTeamRoles.slug, teamId: team.slug}}
  254. organization={orgWithTeamRoles}
  255. team={team}
  256. />
  257. );
  258. const admins = screen.queryAllByText('Team Admin');
  259. expect(admins).toHaveLength(3);
  260. const contributors = screen.queryAllByText('Contributor');
  261. expect(contributors).toHaveLength(2);
  262. });
  263. it('cannot add or remove members if team is idp:provisioned', function () {
  264. const team2 = TestStubs.Team({
  265. flags: {
  266. 'idp:provisioned': true,
  267. },
  268. });
  269. const me = TestStubs.Member({
  270. id: '123',
  271. email: 'foo@example.com',
  272. role: 'owner',
  273. flags: {
  274. 'idp:provisioned': true,
  275. },
  276. });
  277. Client.clearMockResponses();
  278. Client.addMockResponse({
  279. url: `/organizations/${organization.slug}/members/`,
  280. method: 'GET',
  281. body: [...members, me],
  282. });
  283. Client.addMockResponse({
  284. url: `/teams/${organization.slug}/${team2.slug}/members/`,
  285. method: 'GET',
  286. body: members,
  287. });
  288. Client.addMockResponse({
  289. url: `/teams/${organization.slug}/${team.slug}/`,
  290. method: 'GET',
  291. body: team2,
  292. });
  293. render(
  294. <TeamMembers
  295. params={{orgId: organization.slug, teamId: team2.slug}}
  296. organization={organization}
  297. team={team2}
  298. />
  299. );
  300. waitFor(() => {
  301. expect(screen.findByRole('button', {name: 'Add Member'})).toBeDisabled();
  302. expect(screen.findByRole('button', {name: 'Remove'})).toBeDisabled();
  303. });
  304. });
  305. });