organizationMemberRow.spec.tsx 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. import {Organization} from 'sentry-fixture/organization';
  2. import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
  3. import OrganizationMemberRow from 'sentry/views/settings/organizationMembers/organizationMemberRow';
  4. describe('OrganizationMemberRow', function () {
  5. const member = TestStubs.Member({
  6. id: '1',
  7. email: '',
  8. name: '',
  9. orgRole: 'member',
  10. roleName: 'Member',
  11. pending: false,
  12. flags: {
  13. 'sso:linked': false,
  14. },
  15. user: {
  16. id: '',
  17. has2fa: false,
  18. name: 'sentry@test.com',
  19. },
  20. groupOrgRoles: [],
  21. });
  22. const managerTeam = TestStubs.Team({
  23. orgRole: 'manager',
  24. });
  25. const memberOnManagerTeam = TestStubs.Member({
  26. id: '2',
  27. orgRole: 'member',
  28. teams: [managerTeam.slug],
  29. groupOrgRoles: [
  30. {
  31. teamSlug: managerTeam.slug,
  32. role: {name: 'Manager'},
  33. },
  34. ],
  35. });
  36. const currentUser = TestStubs.User({
  37. id: '2',
  38. email: 'currentUser@email.com',
  39. });
  40. const defaultProps: React.ComponentProps<typeof OrganizationMemberRow> = {
  41. organization: Organization(),
  42. status: '',
  43. requireLink: false,
  44. memberCanLeave: false,
  45. canAddMembers: false,
  46. canRemoveMembers: false,
  47. member,
  48. currentUser,
  49. onSendInvite: () => {},
  50. onRemove: () => {},
  51. onLeave: () => {},
  52. };
  53. function resendButton() {
  54. return screen.queryByRole('button', {name: 'Resend invite'});
  55. }
  56. function resendSsoButton() {
  57. return screen.queryByRole('button', {name: 'Resend SSO link'});
  58. }
  59. function leaveButton() {
  60. return screen.queryByRole('button', {name: 'Leave'});
  61. }
  62. function removeButton() {
  63. return screen.queryByRole('button', {name: 'Remove'});
  64. }
  65. describe('two factor', function () {
  66. it('does not have 2fa warning if user has 2fa', function () {
  67. render(
  68. <OrganizationMemberRow
  69. {...defaultProps}
  70. member={TestStubs.Member({
  71. ...member,
  72. user: TestStubs.User({...member.user, has2fa: true}),
  73. })}
  74. />
  75. );
  76. expect(screen.getByText('2FA Enabled')).toBeInTheDocument();
  77. expect(screen.queryByText('2FA Not Enabled')).not.toBeInTheDocument();
  78. });
  79. it('has 2fa warning if user does not have 2fa enabled', function () {
  80. render(
  81. <OrganizationMemberRow
  82. {...defaultProps}
  83. member={{
  84. ...member,
  85. user: {...member.user, has2fa: false},
  86. }}
  87. />
  88. );
  89. expect(screen.getByText('2FA Not Enabled')).toBeInTheDocument();
  90. expect(screen.queryByText('2FA Enabled')).not.toBeInTheDocument();
  91. });
  92. });
  93. describe('Pending user', function () {
  94. const props = {
  95. ...defaultProps,
  96. member: {...member, pending: true},
  97. };
  98. it('has "Invited" status, no "Resend Invite"', function () {
  99. render(<OrganizationMemberRow {...props} />);
  100. expect(screen.getByTestId('member-role')).toHaveTextContent('Invited Member');
  101. expect(resendButton()).toBeDisabled();
  102. });
  103. it('has "Resend Invite" button only if `canAddMembers` is true', function () {
  104. render(<OrganizationMemberRow {...props} canAddMembers />);
  105. expect(screen.getByTestId('member-role')).toHaveTextContent('Invited Member');
  106. expect(resendButton()).toBeEnabled();
  107. });
  108. it('has the right inviting states', function () {
  109. render(<OrganizationMemberRow {...props} canAddMembers />);
  110. expect(resendButton()).toBeInTheDocument();
  111. });
  112. it('has loading state', function () {
  113. render(<OrganizationMemberRow {...props} canAddMembers status="loading" />);
  114. // Should have loader
  115. expect(screen.getByTestId('loading-indicator')).toBeInTheDocument();
  116. // No Resend Invite button
  117. expect(resendButton()).not.toBeInTheDocument();
  118. });
  119. it('has success status', function () {
  120. render(<OrganizationMemberRow {...props} canAddMembers status="success" />);
  121. // Should not have loader
  122. expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument();
  123. // No Resend Invite button
  124. expect(resendButton()).not.toBeInTheDocument();
  125. expect(screen.getByTestId('member-status')).toHaveTextContent('Sent!');
  126. });
  127. });
  128. describe('Expired user', function () {
  129. it('has "Expired" status', function () {
  130. render(
  131. <OrganizationMemberRow
  132. {...defaultProps}
  133. canAddMembers
  134. member={{...member, pending: true, expired: true}}
  135. />
  136. );
  137. expect(screen.getByTestId('member-role')).toHaveTextContent('Expired Invite');
  138. expect(resendButton()).toBeEnabled();
  139. });
  140. });
  141. describe('Requires SSO Link', function () {
  142. const props = {
  143. ...defaultProps,
  144. flags: {'sso:link': false},
  145. requireLink: true,
  146. };
  147. it('shows "Invited" status if user has not registered and not linked', function () {
  148. render(
  149. <OrganizationMemberRow
  150. {...props}
  151. canAddMembers
  152. member={{...member, pending: true}}
  153. />
  154. );
  155. expect(screen.getByTestId('member-role')).toHaveTextContent('Invited Member');
  156. expect(resendButton()).toBeEnabled();
  157. });
  158. it('shows "missing SSO link" message if user is registered and needs link', function () {
  159. render(<OrganizationMemberRow {...props} />);
  160. expect(screen.getByTestId('member-role')).toHaveTextContent('Member');
  161. expect(resendSsoButton()).toBeDisabled();
  162. });
  163. it('has "Resend SSO link" button only if `canAddMembers` is true and no link', function () {
  164. render(<OrganizationMemberRow {...props} canAddMembers />);
  165. expect(resendSsoButton()).toBeEnabled();
  166. });
  167. it('has 2fa warning if user is linked does not have 2fa enabled', function () {
  168. render(
  169. <OrganizationMemberRow
  170. {...defaultProps}
  171. member={{
  172. ...member,
  173. flags: {'sso:linked': true},
  174. user: {...member.user, has2fa: false},
  175. }}
  176. />
  177. );
  178. expect(screen.getByText('2FA Not Enabled')).toBeInTheDocument();
  179. expect(screen.queryByText('2FA Enabled')).not.toBeInTheDocument();
  180. });
  181. });
  182. describe('Is Current User', function () {
  183. const props = {
  184. ...defaultProps,
  185. member: {...member, email: 'currentUser@email.com'},
  186. };
  187. it('has button to leave organization and no button to remove', function () {
  188. render(<OrganizationMemberRow {...props} memberCanLeave />);
  189. expect(leaveButton()).toBeInTheDocument();
  190. expect(removeButton()).not.toBeInTheDocument();
  191. });
  192. it('has disabled button to leave organization and no button to remove when member can not leave', function () {
  193. render(<OrganizationMemberRow {...props} memberCanLeave={false} />);
  194. expect(leaveButton()).toBeDisabled();
  195. expect(removeButton()).not.toBeInTheDocument();
  196. });
  197. });
  198. describe('IDP flags permissions', function () {
  199. member.flags['idp:provisioned'] = true;
  200. it('current user cannot leave if idp:provisioned', function () {
  201. const props = {
  202. ...defaultProps,
  203. member: {...member, email: 'currentUser@email.com'},
  204. };
  205. render(
  206. <OrganizationMemberRow
  207. {...props}
  208. memberCanLeave={!member.flags['idp:provisioned']}
  209. />
  210. );
  211. expect(leaveButton()).toBeDisabled();
  212. });
  213. it('cannot remove member if member is idp:provisioned', function () {
  214. render(<OrganizationMemberRow {...defaultProps} />);
  215. expect(removeButton()).toBeDisabled();
  216. });
  217. });
  218. describe('Not Current User', function () {
  219. const props = {
  220. ...defaultProps,
  221. };
  222. it('does not have Leave button', function () {
  223. render(<OrganizationMemberRow {...props} memberCanLeave />);
  224. expect(leaveButton()).not.toBeInTheDocument();
  225. });
  226. it('has Remove disabled button when `canRemoveMembers` is false', function () {
  227. member.flags['idp:provisioned'] = false;
  228. render(<OrganizationMemberRow {...props} />);
  229. expect(removeButton()).toBeDisabled();
  230. });
  231. it('has Remove button when `canRemoveMembers` is true', function () {
  232. member.flags['idp:provisioned'] = false;
  233. render(<OrganizationMemberRow {...props} canRemoveMembers />);
  234. expect(removeButton()).toBeEnabled();
  235. });
  236. });
  237. describe('render org role', function () {
  238. it('renders org role without tooltip if no org roles from team membership', function () {
  239. render(
  240. <OrganizationMemberRow
  241. {...defaultProps}
  242. member={{...member, user: {...member.user}}}
  243. />
  244. );
  245. expect(screen.getByText('Member')).toBeInTheDocument();
  246. const questionTooltip = screen.queryByTestId('more-information');
  247. expect(questionTooltip).not.toBeInTheDocument();
  248. });
  249. });
  250. it('renders org role info tooltip if member has org roles from team membership', async function () {
  251. render(
  252. <OrganizationMemberRow
  253. {...defaultProps}
  254. member={{...memberOnManagerTeam, user: {...memberOnManagerTeam.user}}}
  255. />
  256. );
  257. const questionTooltip = screen.getByTestId('more-information');
  258. expect(questionTooltip).toBeInTheDocument();
  259. await userEvent.hover(questionTooltip);
  260. await waitFor(() => {
  261. expect(screen.getByText(`#${managerTeam.slug}`)).toBeInTheDocument();
  262. expect(screen.getByText(': Manager')).toBeInTheDocument();
  263. });
  264. });
  265. });