import {MemberFixture} from 'sentry-fixture/member'; import {MembersFixture} from 'sentry-fixture/members'; import {OrganizationFixture} from 'sentry-fixture/organization'; import {RouterFixture} from 'sentry-fixture/routerFixture'; import {TeamFixture} from 'sentry-fixture/team'; import {initializeOrg} from 'sentry-test/initializeOrg'; import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; import { openInviteMembersModal, openTeamAccessRequestModal, } from 'sentry/actionCreators/modal'; import TeamMembers from 'sentry/views/settings/organizationTeams/teamMembers'; jest.mock('sentry/actionCreators/modal', () => ({ openInviteMembersModal: jest.fn(), openTeamAccessRequestModal: jest.fn(), })); describe('TeamMembers', function () { let createMock; const organization = OrganizationFixture(); const team = TeamFixture(); const managerTeam = TeamFixture(); const members = MembersFixture(); const member = MemberFixture({ id: '9', email: 'sentry9@test.com', name: 'Sentry 9 Name', }); const router = RouterFixture(); const routerProps = { router, routes: router.routes, params: router.params, routeParams: router.params, route: router.routes[0], location: router.location, }; beforeEach(function () { MockApiClient.clearMockResponses(); MockApiClient.addMockResponse({ url: `/organizations/${organization.slug}/members/`, method: 'GET', body: [member], }); MockApiClient.addMockResponse({ url: `/teams/${organization.slug}/${team.slug}/members/`, method: 'GET', body: members, }); MockApiClient.addMockResponse({ url: `/teams/${organization.slug}/${team.slug}/`, method: 'GET', body: team, }); MockApiClient.addMockResponse({ url: `/teams/${organization.slug}/${managerTeam.slug}/`, method: 'GET', body: managerTeam, }); createMock = MockApiClient.addMockResponse({ url: `/organizations/${organization.slug}/members/${member.id}/teams/${team.slug}/`, method: 'POST', }); }); it('can add member to team with open membership', async function () { const org = OrganizationFixture({access: [], openMembership: true}); render( ); await userEvent.click( (await screen.findAllByRole('button', {name: 'Add Member'}))[0] ); await userEvent.click(screen.getAllByTestId('letter_avatar-avatar')[0]); expect(createMock).toHaveBeenCalled(); }); it('can add multiple members with one click on dropdown', async function () { const org = OrganizationFixture({access: [], openMembership: true}); render( ); await userEvent.click( (await screen.findAllByRole('button', {name: 'Add Member'}))[0] ); await userEvent.click(screen.getAllByTestId('letter_avatar-avatar')[0]); expect(createMock).toHaveBeenCalled(); expect(screen.getAllByTestId('add-member-menu')[0]).toBeVisible(); }); it('can add member to team with team:admin permission', async function () { const org = OrganizationFixture({access: ['team:admin'], openMembership: false}); render( ); await userEvent.click( (await screen.findAllByRole('button', {name: 'Add Member'}))[0] ); await userEvent.click(screen.getAllByTestId('letter_avatar-avatar')[0]); expect(createMock).toHaveBeenCalled(); }); it('can add member to team with org:write permission', async function () { const org = OrganizationFixture({access: ['org:write'], openMembership: false}); render( ); await userEvent.click( (await screen.findAllByRole('button', {name: 'Add Member'}))[0] ); await userEvent.click(screen.getAllByTestId('letter_avatar-avatar')[0]); expect(createMock).toHaveBeenCalled(); }); it('can request access to add member to team without permission', async function () { const org = OrganizationFixture({access: [], openMembership: false}); render( ); await userEvent.click( (await screen.findAllByRole('button', {name: 'Add Member'}))[0] ); await userEvent.click(screen.getAllByTestId('letter_avatar-avatar')[0]); expect(openTeamAccessRequestModal).toHaveBeenCalled(); }); it('can invite member from team dropdown with access', async function () { const {organization: org, routerContext} = initializeOrg({ organization: OrganizationFixture({ access: ['team:admin'], openMembership: false, }), }); render( , {context: routerContext} ); await userEvent.click( (await screen.findAllByRole('button', {name: 'Add Member'}))[0] ); await userEvent.click(screen.getByTestId('invite-member')); expect(openInviteMembersModal).toHaveBeenCalled(); }); it('can invite member from team dropdown with access and `Open Membership` enabled', async function () { const {organization: org, routerContext} = initializeOrg({ organization: OrganizationFixture({ access: ['team:admin'], openMembership: true, }), }); render( , {context: routerContext} ); await userEvent.click( (await screen.findAllByRole('button', {name: 'Add Member'}))[0] ); await userEvent.click(screen.getByTestId('invite-member')); expect(openInviteMembersModal).toHaveBeenCalled(); }); it('can invite member from team dropdown without access and `Open Membership` enabled', async function () { const {organization: org, routerContext} = initializeOrg({ organization: OrganizationFixture({access: [], openMembership: true}), }); render( , {context: routerContext} ); await userEvent.click( (await screen.findAllByRole('button', {name: 'Add Member'}))[0] ); await userEvent.click(screen.getByTestId('invite-member')); expect(openInviteMembersModal).toHaveBeenCalled(); }); it('can invite member from team dropdown without access and `Open Membership` disabled', async function () { const {organization: org, routerContext} = initializeOrg({ organization: OrganizationFixture({access: [], openMembership: false}), }); render( , {context: routerContext} ); await userEvent.click( (await screen.findAllByRole('button', {name: 'Add Member'}))[0] ); await userEvent.click(screen.getByTestId('invite-member')); expect(openInviteMembersModal).toHaveBeenCalled(); }); it('can remove member from team', async function () { const deleteMock = MockApiClient.addMockResponse({ url: `/organizations/${organization.slug}/members/${members[0].id}/teams/${team.slug}/`, method: 'DELETE', }); render( ); await screen.findAllByRole('button', {name: 'Add Member'}); expect(deleteMock).not.toHaveBeenCalled(); await userEvent.click(screen.getAllByRole('button', {name: 'Remove'})[0]); expect(deleteMock).toHaveBeenCalled(); }); it('can only remove self from team', async function () { const me = MemberFixture({ id: '123', email: 'foo@example.com', }); MockApiClient.addMockResponse({ url: `/teams/${organization.slug}/${team.slug}/members/`, method: 'GET', body: [...members, me], }); const deleteMock = MockApiClient.addMockResponse({ url: `/organizations/${organization.slug}/members/${me.id}/teams/${team.slug}/`, method: 'DELETE', }); const organizationMember = OrganizationFixture({access: []}); render( ); await screen.findAllByRole('button', {name: 'Add Member'}); expect(deleteMock).not.toHaveBeenCalled(); expect(screen.getAllByTestId('letter_avatar-avatar')).toHaveLength( members.length + 1 ); // Can only remove self expect(screen.getByRole('button', {name: 'Leave'})).toBeInTheDocument(); await userEvent.click(screen.getByRole('button', {name: 'Leave'})); expect(deleteMock).toHaveBeenCalled(); }); it('renders team-level roles without flag', async function () { const owner = MemberFixture({ id: '123', email: 'foo@example.com', orgRole: 'owner', role: 'owner', }); MockApiClient.addMockResponse({ url: `/teams/${organization.slug}/${team.slug}/members/`, method: 'GET', body: [...members, owner], }); render( ); const admins = await screen.findAllByText('Team Admin'); expect(admins).toHaveLength(3); const contributors = screen.queryAllByText('Contributor'); expect(contributors).toHaveLength(2); }); it('renders team-level roles with flag', async function () { const manager = MemberFixture({ id: '123', email: 'foo@example.com', orgRole: 'manager', role: 'manager', }); MockApiClient.addMockResponse({ url: `/teams/${organization.slug}/${team.slug}/members/`, method: 'GET', body: [...members, manager], }); const orgWithTeamRoles = OrganizationFixture({features: ['team-roles']}); render( ); const admins = await screen.findAllByText('Team Admin'); expect(admins).toHaveLength(3); const contributors = screen.queryAllByText('Contributor'); expect(contributors).toHaveLength(2); }); it('cannot add or remove members if team is idp:provisioned', async function () { const team2 = TeamFixture({ flags: { 'idp:provisioned': true, }, }); const me = MemberFixture({ id: '123', email: 'foo@example.com', role: 'owner', flags: { 'idp:provisioned': true, 'idp:role-restricted': false, 'member-limit:restricted': false, 'partnership:restricted': false, 'sso:invalid': false, 'sso:linked': false, }, }); const idpMembers = members.map(teamMember => ({ ...teamMember, flags: {...teamMember.flags, 'idp:provisioned': true}, })); MockApiClient.clearMockResponses(); MockApiClient.addMockResponse({ url: `/organizations/${organization.slug}/members/`, method: 'GET', body: [...idpMembers, me], }); MockApiClient.addMockResponse({ url: `/teams/${organization.slug}/${team2.slug}/members/`, method: 'GET', body: idpMembers, }); MockApiClient.addMockResponse({ url: `/teams/${organization.slug}/${team2.slug}/`, method: 'GET', body: team2, }); render( ); expect( (await screen.findAllByRole('button', {name: 'Add Member'})).at(1) ).toBeDisabled(); expect((await screen.findAllByRole('button', {name: 'Remove'})).at(0)).toBeDisabled(); }); });