organizationTeams.spec.jsx 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {act, render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  3. import {openCreateTeamModal} from 'sentry/actionCreators/modal';
  4. import TeamStore from 'sentry/stores/teamStore';
  5. import recreateRoute from 'sentry/utils/recreateRoute';
  6. import OrganizationTeams from 'sentry/views/settings/organizationTeams/organizationTeams';
  7. recreateRoute.mockReturnValue('');
  8. jest.mock('sentry/actionCreators/modal', () => ({
  9. openCreateTeamModal: jest.fn(),
  10. }));
  11. describe('OrganizationTeams', function () {
  12. describe('Open Membership', function () {
  13. const {organization, project} = initializeOrg({
  14. organization: {
  15. openMembership: true,
  16. },
  17. });
  18. const createWrapper = props =>
  19. render(
  20. <OrganizationTeams
  21. params={{orgId: organization.slug, projectId: project.slug}}
  22. routes={[]}
  23. features={new Set(['open-membership'])}
  24. access={new Set(['project:admin'])}
  25. organization={organization}
  26. {...props}
  27. />
  28. );
  29. it('opens "create team modal" when creating a new team from header', function () {
  30. createWrapper();
  31. // Click "Create Team" in Panel Header
  32. userEvent.click(screen.getByLabelText('Create Team'));
  33. // action creator to open "create team modal" is called
  34. expect(openCreateTeamModal).toHaveBeenCalledWith(
  35. expect.objectContaining({
  36. organization: expect.objectContaining({
  37. slug: organization.slug,
  38. }),
  39. })
  40. );
  41. });
  42. it('can join team and have link to details', function () {
  43. const mockTeams = [TestStubs.Team({hasAccess: true, isMember: false})];
  44. act(() => void TeamStore.loadInitialData(mockTeams, false, null));
  45. createWrapper({
  46. access: new Set([]),
  47. });
  48. expect(screen.getByLabelText('Join Team')).toBeInTheDocument();
  49. // Should also link to details
  50. expect(screen.getByTestId('team-link')).toBeInTheDocument();
  51. });
  52. it('reloads projects after joining a team', async function () {
  53. const team = TestStubs.Team({hasAccess: true, isMember: false});
  54. const getOrgMock = MockApiClient.addMockResponse({
  55. url: '/organizations/org-slug/',
  56. body: TestStubs.Organization(),
  57. });
  58. MockApiClient.addMockResponse({
  59. url: `/organizations/org-slug/members/me/teams/${team.slug}/`,
  60. method: 'POST',
  61. body: {...team, isMember: true},
  62. });
  63. const mockTeams = [team];
  64. act(() => void TeamStore.loadInitialData(mockTeams, false, null));
  65. createWrapper({access: new Set([])});
  66. act(() => void userEvent.click(screen.getByLabelText('Join Team')));
  67. await act(() => tick());
  68. expect(getOrgMock).toHaveBeenCalledTimes(1);
  69. });
  70. });
  71. describe('Closed Membership', function () {
  72. const {organization, project} = initializeOrg({
  73. organization: {
  74. openMembership: false,
  75. },
  76. });
  77. const createWrapper = props =>
  78. render(
  79. <OrganizationTeams
  80. params={{orgId: organization.slug, projectId: project.slug}}
  81. routes={[]}
  82. features={new Set([])}
  83. access={new Set([])}
  84. allTeams={[]}
  85. activeTeams={[]}
  86. organization={organization}
  87. {...props}
  88. />
  89. );
  90. it('can request access to team and does not have link to details', function () {
  91. const mockTeams = [TestStubs.Team({hasAccess: false, isMember: false})];
  92. act(() => void TeamStore.loadInitialData(mockTeams, false, null));
  93. createWrapper({access: new Set([])});
  94. expect(screen.getByLabelText('Request Access')).toBeInTheDocument();
  95. // Should also not link to details because of lack of access
  96. expect(screen.queryByTestId('team-link')).not.toBeInTheDocument();
  97. });
  98. it('can leave team when you are a member', function () {
  99. const mockTeams = [TestStubs.Team({hasAccess: true, isMember: true})];
  100. act(() => void TeamStore.loadInitialData(mockTeams, false, null));
  101. createWrapper({
  102. access: new Set([]),
  103. });
  104. expect(screen.getByLabelText('Leave Team')).toBeInTheDocument();
  105. });
  106. });
  107. describe('Team Requests', function () {
  108. const orgId = 'org-slug';
  109. const {organization, project} = initializeOrg({
  110. organization: {
  111. openMembership: false,
  112. },
  113. });
  114. const accessRequest = TestStubs.AccessRequest();
  115. const requester = TestStubs.User({
  116. id: '9',
  117. username: 'requester@example.com',
  118. email: 'requester@example.com',
  119. name: 'Requester',
  120. });
  121. const requestList = [accessRequest, TestStubs.AccessRequest({id: '4', requester})];
  122. const createWrapper = props =>
  123. render(
  124. <OrganizationTeams
  125. params={{orgId: organization.slug, projectId: project.slug}}
  126. routes={[]}
  127. features={new Set([])}
  128. access={new Set([])}
  129. allTeams={[]}
  130. activeTeams={[]}
  131. organization={organization}
  132. requestList={requestList}
  133. {...props}
  134. />
  135. );
  136. it('renders team request panel', function () {
  137. createWrapper();
  138. expect(screen.getByText('Pending Team Requests')).toBeInTheDocument();
  139. expect(screen.queryAllByTestId('request-message')).toHaveLength(2);
  140. expect(screen.queryAllByTestId('request-message')[0]).toHaveTextContent(
  141. `${accessRequest.member.user.name} requests access to the #${accessRequest.team.slug} team`
  142. );
  143. });
  144. it('can approve', async function () {
  145. const onUpdateRequestListMock = jest.fn();
  146. const approveMock = MockApiClient.addMockResponse({
  147. url: `/organizations/${orgId}/access-requests/${accessRequest.id}/`,
  148. method: 'PUT',
  149. });
  150. createWrapper({
  151. onRemoveAccessRequest: onUpdateRequestListMock,
  152. });
  153. userEvent.click(screen.getAllByLabelText('Approve')[0]);
  154. await tick();
  155. expect(approveMock).toHaveBeenCalledWith(
  156. expect.anything(),
  157. expect.objectContaining({
  158. data: {
  159. isApproved: true,
  160. },
  161. })
  162. );
  163. expect(onUpdateRequestListMock).toHaveBeenCalledWith(accessRequest.id, true);
  164. });
  165. it('can deny', async function () {
  166. const onUpdateRequestListMock = jest.fn();
  167. const denyMock = MockApiClient.addMockResponse({
  168. url: `/organizations/${orgId}/access-requests/${accessRequest.id}/`,
  169. method: 'PUT',
  170. });
  171. createWrapper({
  172. onRemoveAccessRequest: onUpdateRequestListMock,
  173. });
  174. userEvent.click(screen.getAllByLabelText('Deny')[0]);
  175. await tick();
  176. expect(denyMock).toHaveBeenCalledWith(
  177. expect.anything(),
  178. expect.objectContaining({
  179. data: {
  180. isApproved: false,
  181. },
  182. })
  183. );
  184. expect(onUpdateRequestListMock).toHaveBeenCalledWith(accessRequest.id, false);
  185. });
  186. });
  187. describe('Team Roles', function () {
  188. const features = new Set(['team-roles']);
  189. const access = new Set();
  190. it('does not render alert without feature flag', function () {
  191. const {organization, project} = initializeOrg({organization: {orgRole: 'admin'}});
  192. render(
  193. <OrganizationTeams
  194. params={{orgId: organization.slug, projectId: project.slug}}
  195. routes={[]}
  196. features={new Set()}
  197. access={access}
  198. organization={organization}
  199. />
  200. );
  201. expect(screen.queryByText('a minimum team-level role of')).not.toBeInTheDocument();
  202. });
  203. it('renders alert with elevated org role', function () {
  204. const {organization, project} = initializeOrg({organization: {orgRole: 'admin'}});
  205. render(
  206. <OrganizationTeams
  207. params={{orgId: organization.slug, projectId: project.slug}}
  208. routes={[]}
  209. features={features}
  210. access={access}
  211. organization={organization}
  212. />
  213. );
  214. expect(screen.getByText('a minimum team-level role of')).toBeInTheDocument();
  215. });
  216. it('does not render alert with lowest org role', function () {
  217. const {organization, project} = initializeOrg({organization: {orgRole: 'member'}});
  218. render(
  219. <OrganizationTeams
  220. params={{orgId: organization.slug, projectId: project.slug}}
  221. routes={[]}
  222. features={features}
  223. access={access}
  224. organization={organization}
  225. />
  226. );
  227. expect(screen.queryByText('a minimum team-level role of')).not.toBeInTheDocument();
  228. });
  229. });
  230. });