organizationMemberDetail.spec.tsx 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  1. import {UserEnrolledAuthenticatorFixture} from 'sentry-fixture/authenticators';
  2. import {MemberFixture} from 'sentry-fixture/member';
  3. import {OrganizationFixture} from 'sentry-fixture/organization';
  4. import {OrgRoleListFixture} from 'sentry-fixture/roleList';
  5. import {TeamFixture} from 'sentry-fixture/team';
  6. import {UserFixture} from 'sentry-fixture/user';
  7. import {initializeOrg} from 'sentry-test/initializeOrg';
  8. import {
  9. cleanup,
  10. render,
  11. renderGlobalModal,
  12. screen,
  13. userEvent,
  14. within,
  15. } from 'sentry-test/reactTestingLibrary';
  16. import selectEvent from 'sentry-test/selectEvent';
  17. import {updateMember} from 'sentry/actionCreators/members';
  18. import TeamStore from 'sentry/stores/teamStore';
  19. import type {Member} from 'sentry/types/organization';
  20. import OrganizationMemberDetail from './organizationMemberDetail';
  21. jest.mock('sentry/actionCreators/members', () => ({
  22. updateMember: jest.fn().mockReturnValue(new Promise(() => {})),
  23. }));
  24. describe('OrganizationMemberDetail', function () {
  25. const team = TeamFixture();
  26. const idpTeam = TeamFixture({
  27. id: '3',
  28. slug: 'idp-member-team',
  29. name: 'Idp Member Team',
  30. isMember: true,
  31. flags: {
  32. 'idp:provisioned': true,
  33. },
  34. });
  35. const teams = [
  36. team,
  37. TeamFixture({
  38. id: '2',
  39. slug: 'new-team',
  40. name: 'New Team',
  41. isMember: false,
  42. }),
  43. idpTeam,
  44. ];
  45. const teamAssignment = {
  46. teams: [team.slug],
  47. teamRoles: [
  48. {
  49. teamSlug: team.slug,
  50. role: null,
  51. },
  52. ],
  53. };
  54. const member = MemberFixture({
  55. roles: OrgRoleListFixture(),
  56. dateCreated: new Date().toISOString(),
  57. ...teamAssignment,
  58. });
  59. const pendingMember = MemberFixture({
  60. id: '2',
  61. roles: OrgRoleListFixture(),
  62. dateCreated: new Date().toISOString(),
  63. ...teamAssignment,
  64. invite_link: 'http://example.com/i/abc123',
  65. pending: true,
  66. });
  67. const expiredMember = MemberFixture({
  68. id: '3',
  69. roles: OrgRoleListFixture(),
  70. dateCreated: new Date().toISOString(),
  71. ...teamAssignment,
  72. invite_link: 'http://example.com/i/abc123',
  73. pending: true,
  74. expired: true,
  75. });
  76. const idpTeamMember = MemberFixture({
  77. id: '4',
  78. roles: OrgRoleListFixture(),
  79. dateCreated: new Date().toISOString(),
  80. teams: [idpTeam.slug],
  81. teamRoles: [
  82. {
  83. teamSlug: idpTeam.slug,
  84. role: null,
  85. },
  86. ],
  87. });
  88. const managerMember = MemberFixture({
  89. id: '6',
  90. roles: OrgRoleListFixture(),
  91. role: 'manager',
  92. });
  93. beforeEach(() => {
  94. MockApiClient.clearMockResponses();
  95. TeamStore.loadInitialData(teams);
  96. });
  97. describe('Can Edit', function () {
  98. const organization = OrganizationFixture({features: ['team-roles']});
  99. beforeEach(function () {
  100. TeamStore.init();
  101. TeamStore.loadInitialData(teams);
  102. jest.resetAllMocks();
  103. MockApiClient.clearMockResponses();
  104. MockApiClient.addMockResponse({
  105. url: `/organizations/${organization.slug}/members/${member.id}/`,
  106. body: member,
  107. });
  108. MockApiClient.addMockResponse({
  109. url: `/organizations/${organization.slug}/members/${pendingMember.id}/`,
  110. body: pendingMember,
  111. });
  112. MockApiClient.addMockResponse({
  113. url: `/organizations/${organization.slug}/members/${expiredMember.id}/`,
  114. body: expiredMember,
  115. });
  116. MockApiClient.addMockResponse({
  117. url: `/organizations/${organization.slug}/members/${idpTeamMember.id}/`,
  118. body: idpTeamMember,
  119. });
  120. MockApiClient.addMockResponse({
  121. url: `/organizations/${organization.slug}/members/${managerMember.id}/`,
  122. body: managerMember,
  123. });
  124. MockApiClient.addMockResponse({
  125. url: `/organizations/${organization.slug}/teams/`,
  126. body: teams,
  127. });
  128. });
  129. it('changes org role to owner', async function () {
  130. const {router} = initializeOrg({
  131. organization,
  132. router: {params: {memberId: member.id}},
  133. });
  134. render(<OrganizationMemberDetail />, {
  135. router,
  136. organization,
  137. });
  138. // Should have 5 roles
  139. const radios = await screen.findAllByRole('radio');
  140. expect(radios).toHaveLength(5);
  141. // Click last radio
  142. await userEvent.click(radios.at(-1) as Element);
  143. expect(radios.at(-1)).toBeChecked();
  144. // Save Member
  145. await userEvent.click(screen.getByRole('button', {name: 'Save Member'}));
  146. expect(updateMember).toHaveBeenCalledWith(
  147. expect.anything(),
  148. expect.objectContaining({
  149. data: expect.objectContaining({
  150. orgRole: 'owner',
  151. }),
  152. })
  153. );
  154. });
  155. it('leaves a team', async function () {
  156. const {router} = initializeOrg({
  157. organization,
  158. router: {params: {memberId: member.id}},
  159. });
  160. render(<OrganizationMemberDetail />, {
  161. router,
  162. organization,
  163. });
  164. // Remove our one team
  165. await userEvent.click(await screen.findByRole('button', {name: 'Remove'}));
  166. // Save Member
  167. await userEvent.click(screen.getByRole('button', {name: 'Save Member'}));
  168. expect(updateMember).toHaveBeenCalledWith(
  169. expect.anything(),
  170. expect.objectContaining({
  171. data: expect.objectContaining({
  172. teamRoles: [],
  173. }),
  174. })
  175. );
  176. });
  177. it('cannot leave idp-provisioned team', async function () {
  178. const {router} = initializeOrg({
  179. organization,
  180. router: {params: {memberId: idpTeamMember.id}},
  181. });
  182. render(<OrganizationMemberDetail />, {
  183. router,
  184. organization,
  185. });
  186. expect(await screen.findByRole('button', {name: 'Remove'})).toBeDisabled();
  187. });
  188. it('joins a team and assign a team-role', async function () {
  189. const {router} = initializeOrg({
  190. organization,
  191. router: {params: {memberId: member.id}},
  192. });
  193. render(<OrganizationMemberDetail />, {
  194. router,
  195. organization,
  196. });
  197. // Should have one team enabled
  198. expect(await screen.findByTestId('team-row-for-member')).toBeInTheDocument();
  199. // Select new team to join
  200. // Open the dropdown
  201. await userEvent.click(screen.getByText('Add Team'));
  202. // Click the first item
  203. await userEvent.click(screen.getByText('#new-team'));
  204. // Assign as admin to new team
  205. const teamRoleSelect = screen.getAllByText('Contributor')[0];
  206. await selectEvent.select(teamRoleSelect, ['Team Admin']);
  207. // Save Member
  208. await userEvent.click(screen.getByRole('button', {name: 'Save Member'}));
  209. expect(updateMember).toHaveBeenCalledWith(
  210. expect.anything(),
  211. expect.objectContaining({
  212. data: expect.objectContaining({
  213. teamRoles: [
  214. {teamSlug: 'team-slug', role: null},
  215. {teamSlug: 'new-team', role: 'admin'},
  216. ],
  217. }),
  218. })
  219. );
  220. });
  221. it('cannot join idp-provisioned team', async function () {
  222. const {router} = initializeOrg({
  223. organization,
  224. router: {params: {memberId: member.id}},
  225. });
  226. render(<OrganizationMemberDetail />, {
  227. router,
  228. organization,
  229. });
  230. await userEvent.click(await screen.findByText('Add Team'));
  231. await userEvent.hover(screen.getByText('#idp-member-team'));
  232. expect(
  233. await screen.findByText(
  234. "Membership to this team is managed through your organization's identity provider."
  235. )
  236. ).toBeInTheDocument();
  237. });
  238. });
  239. describe('Cannot Edit', function () {
  240. const organization = OrganizationFixture({access: ['org:read']});
  241. beforeEach(function () {
  242. TeamStore.init();
  243. TeamStore.loadInitialData(teams);
  244. jest.resetAllMocks();
  245. MockApiClient.clearMockResponses();
  246. MockApiClient.addMockResponse({
  247. url: `/organizations/${organization.slug}/members/${member.id}/`,
  248. body: member,
  249. });
  250. MockApiClient.addMockResponse({
  251. url: `/organizations/${organization.slug}/members/${pendingMember.id}/`,
  252. body: pendingMember,
  253. });
  254. MockApiClient.addMockResponse({
  255. url: `/organizations/${organization.slug}/members/${expiredMember.id}/`,
  256. body: expiredMember,
  257. });
  258. MockApiClient.addMockResponse({
  259. url: `/organizations/${organization.slug}/teams/`,
  260. body: teams,
  261. });
  262. });
  263. it('can not change roles, teams, or save', async function () {
  264. const {router} = initializeOrg({
  265. organization,
  266. router: {params: {memberId: member.id}},
  267. });
  268. render(<OrganizationMemberDetail />, {
  269. router,
  270. organization,
  271. });
  272. // Should have 4 roles
  273. const radios = await screen.findAllByRole('radio');
  274. expect(radios.at(0)).toHaveAttribute('readonly');
  275. // Save Member
  276. expect(screen.getByRole('button', {name: 'Save Member'})).toBeDisabled();
  277. });
  278. });
  279. describe('Display status', function () {
  280. const organization = OrganizationFixture({access: ['org:read']});
  281. beforeEach(function () {
  282. TeamStore.init();
  283. TeamStore.loadInitialData(teams);
  284. jest.resetAllMocks();
  285. MockApiClient.clearMockResponses();
  286. MockApiClient.addMockResponse({
  287. url: `/organizations/${organization.slug}/members/${member.id}/`,
  288. body: member,
  289. });
  290. MockApiClient.addMockResponse({
  291. url: `/organizations/${organization.slug}/members/${pendingMember.id}/`,
  292. body: pendingMember,
  293. });
  294. MockApiClient.addMockResponse({
  295. url: `/organizations/${organization.slug}/members/${expiredMember.id}/`,
  296. body: expiredMember,
  297. });
  298. MockApiClient.addMockResponse({
  299. url: `/organizations/${organization.slug}/teams/`,
  300. body: teams,
  301. });
  302. });
  303. it('display pending status', async function () {
  304. const {router} = initializeOrg({
  305. organization,
  306. router: {params: {memberId: pendingMember.id}},
  307. });
  308. render(<OrganizationMemberDetail />, {
  309. router,
  310. organization,
  311. });
  312. expect(await screen.findByTestId('member-status')).toHaveTextContent(
  313. 'Invitation Pending'
  314. );
  315. });
  316. it('display expired status', async function () {
  317. const {router} = initializeOrg({
  318. organization,
  319. router: {params: {memberId: expiredMember.id}},
  320. });
  321. render(<OrganizationMemberDetail />, {
  322. router,
  323. organization,
  324. });
  325. expect(await screen.findByTestId('member-status')).toHaveTextContent(
  326. 'Invitation Expired'
  327. );
  328. });
  329. });
  330. describe('Show resend button', function () {
  331. const organization = OrganizationFixture({access: ['org:read']});
  332. beforeEach(function () {
  333. TeamStore.init();
  334. TeamStore.loadInitialData(teams);
  335. jest.resetAllMocks();
  336. MockApiClient.clearMockResponses();
  337. MockApiClient.addMockResponse({
  338. url: `/organizations/${organization.slug}/members/${member.id}/`,
  339. body: member,
  340. });
  341. MockApiClient.addMockResponse({
  342. url: `/organizations/${organization.slug}/members/${pendingMember.id}/`,
  343. body: pendingMember,
  344. });
  345. MockApiClient.addMockResponse({
  346. url: `/organizations/${organization.slug}/members/${expiredMember.id}/`,
  347. body: expiredMember,
  348. });
  349. MockApiClient.addMockResponse({
  350. url: `/organizations/${organization.slug}/teams/`,
  351. body: teams,
  352. });
  353. });
  354. it('shows for pending', async function () {
  355. const {router} = initializeOrg({
  356. organization,
  357. router: {params: {memberId: pendingMember.id}},
  358. });
  359. render(<OrganizationMemberDetail />, {
  360. router,
  361. organization,
  362. });
  363. expect(
  364. await screen.findByRole('button', {name: 'Resend Invite'})
  365. ).toBeInTheDocument();
  366. });
  367. it('does not show for expired', async function () {
  368. const {router} = initializeOrg({
  369. organization,
  370. router: {params: {memberId: expiredMember.id}},
  371. });
  372. render(<OrganizationMemberDetail />, {
  373. router,
  374. organization,
  375. });
  376. await screen.findAllByRole('radio');
  377. expect(
  378. screen.queryByRole('button', {name: 'Resend Invite'})
  379. ).not.toBeInTheDocument();
  380. });
  381. });
  382. describe('Reset member 2FA', function () {
  383. const fields = {
  384. roles: OrgRoleListFixture(),
  385. dateCreated: new Date().toISOString(),
  386. ...teamAssignment,
  387. };
  388. const noAccess = MemberFixture({
  389. ...fields,
  390. id: '4',
  391. user: UserFixture({has2fa: false, authenticators: undefined}),
  392. });
  393. const no2fa = MemberFixture({
  394. ...fields,
  395. id: '5',
  396. user: UserFixture({has2fa: false, authenticators: [], canReset2fa: true}),
  397. });
  398. const has2fa = MemberFixture({
  399. ...fields,
  400. id: '6',
  401. user: UserFixture({
  402. has2fa: true,
  403. authenticators: [
  404. UserEnrolledAuthenticatorFixture({type: 'totp', id: 'totp'}),
  405. UserEnrolledAuthenticatorFixture({type: 'sms', id: 'sms'}),
  406. UserEnrolledAuthenticatorFixture({type: 'u2f', id: 'u2f'}),
  407. ],
  408. canReset2fa: true,
  409. }),
  410. });
  411. const multipleOrgs = MemberFixture({
  412. ...fields,
  413. id: '7',
  414. user: UserFixture({
  415. has2fa: true,
  416. authenticators: [UserEnrolledAuthenticatorFixture({type: 'totp', id: 'totp'})],
  417. canReset2fa: false,
  418. }),
  419. });
  420. const organization = OrganizationFixture();
  421. beforeEach(function () {
  422. MockApiClient.clearMockResponses();
  423. MockApiClient.addMockResponse({
  424. url: `/organizations/${organization.slug}/members/${pendingMember.id}/`,
  425. body: pendingMember,
  426. });
  427. MockApiClient.addMockResponse({
  428. url: `/organizations/${organization.slug}/members/${noAccess.id}/`,
  429. body: noAccess,
  430. });
  431. MockApiClient.addMockResponse({
  432. url: `/organizations/${organization.slug}/members/${no2fa.id}/`,
  433. body: no2fa,
  434. });
  435. MockApiClient.addMockResponse({
  436. url: `/organizations/${organization.slug}/members/${has2fa.id}/`,
  437. body: has2fa,
  438. });
  439. MockApiClient.addMockResponse({
  440. url: `/organizations/${organization.slug}/members/${multipleOrgs.id}/`,
  441. body: multipleOrgs,
  442. });
  443. MockApiClient.addMockResponse({
  444. url: `/organizations/${organization.slug}/teams/`,
  445. body: teams,
  446. });
  447. });
  448. const tooltip = () => screen.queryByTestId('reset-2fa-tooltip');
  449. const expectButtonEnabled = async () => {
  450. const button = await screen.findByRole('button', {
  451. name: 'Reset two-factor authentication',
  452. });
  453. expect(button).toHaveTextContent('Reset two-factor authentication');
  454. expect(button).toBeEnabled();
  455. expect(tooltip()).not.toBeInTheDocument();
  456. };
  457. const expectButtonDisabled = async (title: string) => {
  458. const button = await screen.findByRole('button', {
  459. name: 'Reset two-factor authentication',
  460. });
  461. expect(button).toHaveTextContent('Reset two-factor authentication');
  462. expect(button).toBeDisabled();
  463. await userEvent.hover(button);
  464. expect(await screen.findByText(title)).toBeInTheDocument();
  465. };
  466. it('does not show for pending member', async function () {
  467. const {router} = initializeOrg({
  468. organization,
  469. router: {params: {memberId: pendingMember.id}},
  470. });
  471. render(<OrganizationMemberDetail />, {
  472. router,
  473. organization,
  474. });
  475. expect(
  476. await screen.findByRole('button', {name: 'Resend Invite'})
  477. ).toBeInTheDocument();
  478. expect(
  479. screen.queryByRole('button', {name: 'Reset two-factor authentication'})
  480. ).not.toBeInTheDocument();
  481. });
  482. it('shows tooltip for joined member without permission to edit', async function () {
  483. const {router} = initializeOrg({
  484. organization,
  485. router: {params: {memberId: noAccess.id}},
  486. });
  487. render(<OrganizationMemberDetail />, {
  488. router,
  489. organization,
  490. });
  491. await expectButtonDisabled('You do not have permission to perform this action');
  492. });
  493. it('shows tooltip for member without 2fa', async function () {
  494. const {router} = initializeOrg({
  495. organization,
  496. router: {params: {memberId: no2fa.id}},
  497. });
  498. render(<OrganizationMemberDetail />, {
  499. router,
  500. organization,
  501. });
  502. await expectButtonDisabled('Not enrolled in two-factor authentication');
  503. });
  504. it('can reset member 2FA', async function () {
  505. const {router} = initializeOrg({
  506. organization,
  507. router: {params: {memberId: has2fa.id}},
  508. });
  509. const deleteMocks = (has2fa.user?.authenticators || []).map(auth =>
  510. MockApiClient.addMockResponse({
  511. url: `/users/${has2fa.user?.id}/authenticators/${auth.id}/`,
  512. method: 'DELETE',
  513. })
  514. );
  515. render(<OrganizationMemberDetail />, {
  516. router,
  517. organization,
  518. });
  519. renderGlobalModal();
  520. await expectButtonEnabled();
  521. await userEvent.click(
  522. screen.getByRole('button', {name: 'Reset two-factor authentication'})
  523. );
  524. await userEvent.click(screen.getByRole('button', {name: 'Confirm'}));
  525. deleteMocks.forEach(deleteMock => {
  526. expect(deleteMock).toHaveBeenCalled();
  527. });
  528. });
  529. it('shows tooltip for member in multiple orgs', async function () {
  530. const {router} = initializeOrg({
  531. organization,
  532. router: {params: {memberId: multipleOrgs.id}},
  533. });
  534. render(<OrganizationMemberDetail />, {
  535. router,
  536. organization,
  537. });
  538. await expectButtonDisabled(
  539. 'Cannot be reset since user is in more than one organization'
  540. );
  541. });
  542. it('shows tooltip for member in 2FA required org', async function () {
  543. organization.require2FA = true;
  544. const {router} = initializeOrg({
  545. organization,
  546. router: {params: {memberId: has2fa.id}},
  547. });
  548. MockApiClient.addMockResponse({
  549. url: `/organizations/${organization.slug}/members/${has2fa.id}/`,
  550. body: has2fa,
  551. });
  552. render(<OrganizationMemberDetail />, {
  553. router,
  554. organization,
  555. });
  556. await expectButtonDisabled(
  557. 'Cannot be reset since two-factor is required for this organization'
  558. );
  559. });
  560. });
  561. describe('Org Roles affect Team Roles', () => {
  562. // Org Admin will be deprecated
  563. const admin = MemberFixture({
  564. id: '4',
  565. role: 'admin',
  566. roleName: 'Admin',
  567. orgRole: 'admin',
  568. ...teamAssignment,
  569. });
  570. const manager = MemberFixture({
  571. id: '5',
  572. role: 'manager',
  573. roleName: 'Manager',
  574. orgRole: 'manager',
  575. ...teamAssignment,
  576. });
  577. const owner = MemberFixture({
  578. id: '6',
  579. role: 'owner',
  580. roleName: 'Owner',
  581. orgRole: 'owner',
  582. ...teamAssignment,
  583. });
  584. const organization = OrganizationFixture({features: ['team-roles']});
  585. beforeEach(() => {
  586. MockApiClient.clearMockResponses();
  587. MockApiClient.addMockResponse({
  588. url: `/organizations/${organization.slug}/members/${member.id}/`,
  589. body: member,
  590. });
  591. MockApiClient.addMockResponse({
  592. url: `/organizations/${organization.slug}/members/${admin.id}/`,
  593. body: admin,
  594. });
  595. MockApiClient.addMockResponse({
  596. url: `/organizations/${organization.slug}/members/${manager.id}/`,
  597. body: manager,
  598. });
  599. MockApiClient.addMockResponse({
  600. url: `/organizations/${organization.slug}/members/${owner.id}/`,
  601. body: owner,
  602. });
  603. });
  604. it('does not overwrite team-roles for org members', async () => {
  605. const {router} = initializeOrg({
  606. organization,
  607. router: {params: {memberId: member.id}},
  608. });
  609. render(<OrganizationMemberDetail />, {
  610. router,
  611. organization,
  612. });
  613. // Role info box is hidden
  614. expect(screen.queryByTestId('alert-role-overwrite')).not.toBeInTheDocument();
  615. // Dropdown has correct value set
  616. const teamRow = await screen.findByTestId('team-row-for-member');
  617. const teamRoleSelect = within(teamRow).getByText('Contributor');
  618. // Dropdown options are not visible
  619. expect(screen.queryAllByText('...').length).toBe(0);
  620. // Dropdown can be opened
  621. await selectEvent.openMenu(teamRoleSelect);
  622. expect(screen.queryAllByText('...').length).toBe(2);
  623. // Dropdown value can be changed
  624. await userEvent.click(screen.getByLabelText('Team Admin'));
  625. expect(teamRoleSelect).toHaveTextContent('Team Admin');
  626. });
  627. it('overwrite team-roles for org admin/manager/owner', async () => {
  628. async function testForOrgRole(testMember: Member) {
  629. const {router} = initializeOrg({
  630. organization,
  631. router: {params: {memberId: testMember.id}},
  632. });
  633. cleanup();
  634. render(<OrganizationMemberDetail />, {
  635. router,
  636. organization,
  637. });
  638. // Role info box is showed
  639. expect(await screen.findByTestId('alert-role-overwrite')).toBeInTheDocument();
  640. // Dropdown has correct value set
  641. const teamRow = screen.getByTestId('team-row-for-member');
  642. const teamRoleSelect = within(teamRow).getByText('Team Admin');
  643. // Dropdown options are not visible
  644. expect(screen.queryAllByText('...').length).toBe(0);
  645. // Dropdown cannot be opened
  646. await selectEvent.openMenu(teamRoleSelect);
  647. expect(screen.queryAllByText('...').length).toBe(0);
  648. }
  649. for (const role of [admin, manager, owner]) {
  650. await testForOrgRole(role);
  651. }
  652. });
  653. it('overwrites when changing from member to manager', async () => {
  654. const {router} = initializeOrg({
  655. organization,
  656. router: {params: {memberId: member.id}},
  657. });
  658. render(<OrganizationMemberDetail />, {
  659. router,
  660. organization,
  661. });
  662. // Dropdown has correct value set
  663. const teamRow = await screen.findByTestId('team-row-for-member');
  664. const teamRoleSelect = within(teamRow).getByText('Contributor');
  665. // Role info box is hidden
  666. expect(screen.queryByTestId('alert-role-overwrite')).not.toBeInTheDocument();
  667. // Change member to owner
  668. const orgRoleRadio = screen.getAllByRole('radio');
  669. expect(orgRoleRadio).toHaveLength(5);
  670. await userEvent.click(orgRoleRadio.at(-1) as Element);
  671. expect(orgRoleRadio.at(-1)).toBeChecked();
  672. // Role info box is shown
  673. expect(screen.queryByTestId('alert-role-overwrite')).toBeInTheDocument();
  674. // Dropdown has correct value set
  675. within(teamRow).getByText('Team Admin');
  676. // Dropdown options are not visible
  677. expect(screen.queryAllByText('...').length).toBe(0);
  678. // Dropdown cannot be opened
  679. await selectEvent.openMenu(teamRoleSelect);
  680. expect(screen.queryAllByText('...').length).toBe(0);
  681. });
  682. });
  683. });