import {mountWithTheme} from 'sentry-test/enzyme'; import InviteMembersModal from 'sentry/components/modals/inviteMembersModal'; import TeamStore from 'sentry/stores/teamStore'; describe('InviteMembersModal', function () { const team = TestStubs.Team(); const org = TestStubs.Organization({access: ['member:write'], teams: [team]}); TeamStore.loadInitialData([team]); const modalProps = { Body: p => p.children, Header: p => p.children, Footer: p => p.children, }; const noWriteOrg = TestStubs.Organization({ access: [], }); const roles = [ { id: 'admin', name: 'Admin', desc: 'This is the admin role', allowed: true, }, { id: 'member', name: 'Member', desc: 'This is the member role', allowed: true, }, ]; MockApiClient.addMockResponse({ url: `/organizations/${org.slug}/members/me/`, method: 'GET', body: {roles}, }); it('renders', function () { const wrapper = mountWithTheme( ); // Starts with one invite row expect(wrapper.find('StyledInviteRow')).toHaveLength(1); // We have two roles loaded from the members/me endpoint, defaulting to the // 'member' role. expect(wrapper.find('RoleSelectControl').props().roles).toHaveLength(roles.length); expect(wrapper.find('RoleSelectControl SingleValue').first().text()).toBe('Member'); }); it('renders without organization.access', function () { const organization = TestStubs.Organization({access: undefined}); const wrapper = mountWithTheme( ); expect(wrapper.find('StyledInviteRow').exists()).toBe(true); }); it('can add a second row', function () { const wrapper = mountWithTheme( ); expect(wrapper.find('StyledInviteRow')).toHaveLength(1); wrapper.find('AddButton').simulate('click'); expect(wrapper.find('StyledInviteRow')).toHaveLength(2); }); it('errors on duplicate emails', function () { const wrapper = mountWithTheme( ); wrapper.find('AddButton').simulate('click'); expect(wrapper.find('StyledInviteRow')).toHaveLength(2); const rows = wrapper.find('StyledInviteRow'); rows .at(0) .props() .onChangeEmails([{value: 'test@test.com'}]); rows .at(1) .props() .onChangeEmails([{value: 'test@test.com'}]); wrapper.update(); expect(wrapper.find('StatusMessage[status="error"]').text()).toBe( 'Duplicate emails between invite rows.' ); }); it('indicates the total invites on the invite button', function () { const wrapper = mountWithTheme( ); wrapper .find('StyledInviteRow') .first() .props() .onChangeEmails([{value: 'test1@test.com'}, {value: 'test2@test.com'}]); wrapper.update(); expect(wrapper.find('Button[data-test-id="send-invites"]').text()).toBe( 'Send invites (2)' ); }); it('can be closed', function () { const close = jest.fn(); const wrapper = mountWithTheme( ); wrapper.find('Button[data-test-id="cancel"]').simulate('click'); expect(close).toHaveBeenCalled(); }); it('sends all successful invites', async function () { const createMemberMock = MockApiClient.addMockResponse({ url: `/organizations/${org.slug}/members/`, method: 'POST', }); const wrapper = mountWithTheme( ); wrapper.find('AddButton').simulate('click'); // Setup two rows, one email each, the first with a admin role. const inviteRowProps = wrapper.find('StyledInviteRow').first().props(); inviteRowProps.onChangeEmails([{value: 'test1@test.com'}]); inviteRowProps.onChangeRole({value: 'admin'}); inviteRowProps.onChangeTeams([{value: 'team1'}]); wrapper .find('StyledInviteRow') .at(1) .props() .onChangeEmails([{value: 'test2@test.com'}]); wrapper.update(); wrapper.find('FooterContent Button[priority="primary"]').simulate('click'); // Verify data sent to the backend expect(createMemberMock).toHaveBeenCalledTimes(2); expect(createMemberMock).toHaveBeenNthCalledWith( 1, `/organizations/${org.slug}/members/`, expect.objectContaining({ data: {email: 'test1@test.com', role: 'admin', teams: ['team1']}, }) ); expect(createMemberMock).toHaveBeenNthCalledWith( 2, `/organizations/${org.slug}/members/`, expect.objectContaining({ data: {email: 'test2@test.com', role: 'member', teams: []}, }) ); // Pending invites being created.. expect( wrapper.find('InviteRowControl SelectControl EmailLabel LoadingIndicator') ).toHaveLength(2); expect(wrapper.find('Button[data-test-id="cancel"][disabled]').exists()).toBe(true); expect(wrapper.find('Button[data-test-id="send-invites"][disabled]').exists()).toBe( true ); expect(wrapper.find('StatusMessage LoadingIndicator').exists()).toBe(true); // Await request completion await tick(); wrapper.update(); expect(wrapper.find('StatusMessage').text()).toBe('Sent 2 invites'); expect(wrapper.find('Button[data-test-id="close"]').exists()).toBe(true); expect(wrapper.find('Button[data-test-id="send-more"]').exists()).toBe(true); expect(wrapper.find('SelectControl EmailLabel IconCheckmark').exists()).toBe(true); // Send more reset the modal wrapper.find('Button[data-test-id="send-more"]').simulate('click'); expect(wrapper.find('InviteRowControl SelectControl EmailLabel').exists()).toBe( false ); }); it('marks failed invites', async function () { const faildCreateMemberMock = MockApiClient.addMockResponse({ url: `/organizations/${org.slug}/members/`, method: 'POST', statusCode: 400, }); const wrapper = mountWithTheme( ); const inviteRowProps = wrapper.find('StyledInviteRow').first().props(); inviteRowProps.onChangeEmails([{value: 'bademail'}]); wrapper.update(); wrapper.find('FooterContent Button[priority="primary"]').simulate('click'); expect(faildCreateMemberMock).toHaveBeenCalled(); // Await request completion await tick(); wrapper.update(); expect(wrapper.find('StatusMessage').text()).toBe( 'Sent 0 invites, 1 failed to send.' ); expect(wrapper.find('SelectControl EmailLabel IconWarning').exists()).toBe(true); }); it('can send initial email', async function () { const createMemberMock = MockApiClient.addMockResponse({ url: `/organizations/${org.slug}/members/`, method: 'POST', }); const initialEmail = 'test@gmail.com'; const initialData = [{emails: new Set([initialEmail])}]; const wrapper = mountWithTheme( ); expect(wrapper.find('MultiValue').first().text().includes(initialEmail)).toBe(true); wrapper.find('FooterContent Button[priority="primary"]').simulate('click'); await tick(); wrapper.update(); expect(createMemberMock).toHaveBeenCalledWith( `/organizations/${org.slug}/members/`, expect.objectContaining({ data: {email: initialEmail, role: 'member', teams: []}, }) ); expect(wrapper.find('StatusMessage').text()).toBe('Sent 1 invite'); }); it('can send initial email with role and team', async function () { const createMemberMock = MockApiClient.addMockResponse({ url: `/organizations/${org.slug}/members/`, method: 'POST', }); const initialEmail = 'test@gmail.com'; const role = 'admin'; const initialData = [ {emails: new Set([initialEmail]), role, teams: new Set([team.slug])}, ]; const wrapper = mountWithTheme( ); expect( wrapper .find('SelectControl[data-test-id="select-emails"]') .text() .includes(initialEmail) ).toBe(true); expect( wrapper.find('SelectControl[data-test-id="select-role"]').text().toLowerCase() ).toBe(role); expect( wrapper .find('SelectControl[data-test-id="select-teams"]') .text() .includes(team.slug) ).toBe(true); wrapper.find('FooterContent Button[priority="primary"]').simulate('click'); await tick(); wrapper.update(); expect(createMemberMock).toHaveBeenCalledWith( `/organizations/${org.slug}/members/`, expect.objectContaining({ data: {email: initialEmail, role, teams: [team.slug]}, }) ); expect(wrapper.find('StatusMessage').text()).toBe('Sent 1 invite'); }); describe('member invite request mode', function () { it('has adjusted wording', function () { const wrapper = mountWithTheme( ); expect(wrapper.find('Button[data-test-id="send-invites"]').text()).toBe( 'Send invite request' ); expect(wrapper.find('Heading Tooltip').exists()).toBe(true); }); it('POSTS to the invite-request endpoint', function () { const createInviteRequestMock = MockApiClient.addMockResponse({ url: `/organizations/${org.slug}/invite-requests/`, method: 'POST', }); const wrapper = mountWithTheme( ); const inviteRowProps = wrapper.find('StyledInviteRow').first().props(); inviteRowProps.onChangeEmails([{value: 'test1@test.com'}]); inviteRowProps.onChangeRole({value: 'admin'}); inviteRowProps.onChangeTeams([{value: 'team1'}]); wrapper .find('StyledInviteRow') .first() .props() .onChangeEmails([{value: 'test2@test.com'}]); wrapper.update(); wrapper.find('FooterContent Button[priority="primary"]').simulate('click'); expect(createInviteRequestMock).toHaveBeenCalledTimes(1); }); }); });