Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/invite_members')
-rw-r--r--spec/frontend/invite_members/components/invite_groups_modal_spec.js10
-rw-r--r--spec/frontend/invite_members/components/invite_members_modal_spec.js17
-rw-r--r--spec/frontend/invite_members/components/invite_members_trigger_spec.js13
-rw-r--r--spec/frontend/invite_members/components/invite_modal_base_spec.js55
-rw-r--r--spec/frontend/invite_members/components/members_token_select_spec.js94
-rw-r--r--spec/frontend/invite_members/mock_data/member_modal.js3
-rw-r--r--spec/frontend/invite_members/mock_data/modal_base.js2
7 files changed, 140 insertions, 54 deletions
diff --git a/spec/frontend/invite_members/components/invite_groups_modal_spec.js b/spec/frontend/invite_members/components/invite_groups_modal_spec.js
index 4136de75545..358d70d8117 100644
--- a/spec/frontend/invite_members/components/invite_groups_modal_spec.js
+++ b/spec/frontend/invite_members/components/invite_groups_modal_spec.js
@@ -77,6 +77,16 @@ describe('InviteGroupsModal', () => {
const clickInviteButton = emitClickFromModal('invite-modal-submit');
const clickCancelButton = emitClickFromModal('invite-modal-cancel');
+ describe('passes correct props to InviteModalBase', () => {
+ it('set accessLevel', () => {
+ createInviteGroupToProjectWrapper();
+
+ expect(findBase().props('accessLevels')).toMatchObject({
+ validRoles: propsData.accessLevels,
+ });
+ });
+ });
+
describe('displaying the correct introText and form group description', () => {
describe('when inviting to a project', () => {
it('includes the correct type, and formatted intro text', () => {
diff --git a/spec/frontend/invite_members/components/invite_members_modal_spec.js b/spec/frontend/invite_members/components/invite_members_modal_spec.js
index 19b7fad5fc8..ad3174b8946 100644
--- a/spec/frontend/invite_members/components/invite_members_modal_spec.js
+++ b/spec/frontend/invite_members/components/invite_members_modal_spec.js
@@ -128,6 +128,7 @@ describe('InviteMembersModal', () => {
});
const findModal = () => wrapper.findComponent(GlModal);
+ const findBase = () => wrapper.findComponent(InviteModalBase);
const findIntroText = () => wrapper.findByTestId('modal-base-intro-text').text();
const findEmptyInvitesAlert = () => wrapper.findByTestId('empty-invites-alert');
const findMemberErrorAlert = () => wrapper.findByTestId('alert-member-error');
@@ -168,6 +169,22 @@ describe('InviteMembersModal', () => {
await nextTick();
};
+ describe('passes correct props to InviteModalBase', () => {
+ it('set defaultMemberRoleId', () => {
+ createInviteMembersToProjectWrapper();
+
+ expect(findBase().props('defaultMemberRoleId')).toBeNull();
+ });
+
+ it('set accessLevel', () => {
+ createInviteMembersToProjectWrapper();
+
+ expect(findBase().props('accessLevels')).toMatchObject({
+ validRoles: propsData.accessLevels,
+ });
+ });
+ });
+
describe('rendering with tracking considerations', () => {
describe('when inviting to a project', () => {
describe('when inviting members', () => {
diff --git a/spec/frontend/invite_members/components/invite_members_trigger_spec.js b/spec/frontend/invite_members/components/invite_members_trigger_spec.js
index 58c40a49b3c..f14d24538d8 100644
--- a/spec/frontend/invite_members/components/invite_members_trigger_spec.js
+++ b/spec/frontend/invite_members/components/invite_members_trigger_spec.js
@@ -4,7 +4,6 @@ import InviteMembersTrigger from '~/invite_members/components/invite_members_tri
import eventHub from '~/invite_members/event_hub';
import {
TRIGGER_ELEMENT_BUTTON,
- TRIGGER_DEFAULT_QA_SELECTOR,
TRIGGER_ELEMENT_WITH_EMOJI,
TRIGGER_ELEMENT_DROPDOWN_WITH_EMOJI,
TRIGGER_ELEMENT_DISCLOSURE_DROPDOWN,
@@ -66,18 +65,6 @@ describe.each(triggerItems)('with triggerElement as %s', (triggerItem) => {
expect(findButton().text()).toBe(displayText);
});
-
- it('uses the default qa selector value', () => {
- createComponent();
-
- expect(findButton().attributes('data-qa-selector')).toBe(TRIGGER_DEFAULT_QA_SELECTOR);
- });
-
- it('sets the qa selector value', () => {
- createComponent({ qaSelector: '_qaSelector_' });
-
- expect(findButton().attributes('data-qa-selector')).toBe('_qaSelector_');
- });
});
describe('clicking the link', () => {
diff --git a/spec/frontend/invite_members/components/invite_modal_base_spec.js b/spec/frontend/invite_members/components/invite_modal_base_spec.js
index e70c83a424e..c26d1d921a5 100644
--- a/spec/frontend/invite_members/components/invite_modal_base_spec.js
+++ b/spec/frontend/invite_members/components/invite_modal_base_spec.js
@@ -1,5 +1,5 @@
import {
- GlFormSelect,
+ GlCollapsibleListbox,
GlDatepicker,
GlFormGroup,
GlLink,
@@ -7,9 +7,14 @@ import {
GlModal,
GlIcon,
} from '@gitlab/ui';
+import { nextTick } from 'vue';
import { stubComponent } from 'helpers/stub_component';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
-import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import {
+ mountExtended,
+ shallowMountExtended,
+ extendedWrapper,
+} from 'helpers/vue_test_utils_helper';
import InviteModalBase from '~/invite_members/components/invite_modal_base.vue';
import ContentTransition from '~/vue_shared/components/content_transition.vue';
@@ -31,7 +36,7 @@ describe('InviteModalBase', () => {
? {}
: {
ContentTransition,
- GlFormSelect: true,
+ GlCollapsibleListbox: true,
GlSprintf,
GlFormGroup: stubComponent(GlFormGroup, {
props: ['state', 'invalidFeedback'],
@@ -41,6 +46,7 @@ describe('InviteModalBase', () => {
wrapper = mountFn(InviteModalBase, {
propsData: {
...propsData,
+ accessLevels: { validRoles: propsData.accessLevels },
...props,
},
stubs: {
@@ -54,8 +60,8 @@ describe('InviteModalBase', () => {
});
};
- const findFormSelect = () => wrapper.findComponent(GlFormSelect);
- const findFormSelectOptions = () => findFormSelect().findAllComponents('option');
+ const findCollapsibleListbox = () => extendedWrapper(wrapper.findComponent(GlCollapsibleListbox));
+ const findCollapsibleListboxOptions = () => findCollapsibleListbox().findAllByRole('option');
const findDatepicker = () => wrapper.findComponent(GlDatepicker);
const findLink = () => wrapper.findComponent(GlLink);
const findIcon = () => wrapper.findComponent(GlIcon);
@@ -91,7 +97,6 @@ describe('InviteModalBase', () => {
const actionButton = findActionButton();
expect(actionButton.text()).toBe(INVITE_BUTTON_TEXT);
- expect(actionButton.attributes('data-qa-selector')).toBe('invite_button');
expect(actionButton.props()).toMatchObject({
variant: 'confirm',
@@ -103,17 +108,47 @@ describe('InviteModalBase', () => {
describe('rendering the access levels dropdown', () => {
beforeEach(() => {
createComponent({
+ props: { isLoadingRoles: true },
mountFn: mountExtended,
});
});
+ it('passes `isLoadingRoles` prop to the dropdown', () => {
+ expect(findCollapsibleListbox().props('loading')).toBe(true);
+ });
+
it('sets the default dropdown text to the default access level name', () => {
- expect(findFormSelect().exists()).toBe(true);
- expect(findFormSelect().element.value).toBe('10');
+ expect(findCollapsibleListbox().exists()).toBe(true);
+ const option = findCollapsibleListbox().find('[aria-selected]');
+ expect(option.text()).toBe('Reporter');
+ });
+
+ it('updates the selection base on changes in the dropdown', async () => {
+ wrapper.setProps({ accessLevels: { validRoles: [] } });
+ expect(findCollapsibleListbox().props('selected')).not.toHaveLength(0);
+ await nextTick();
+
+ expect(findCollapsibleListboxOptions()).toHaveLength(0);
+ expect(findCollapsibleListbox().props('selected')).toHaveLength(0);
+ });
+
+ it('reset the dropdown to the default option', async () => {
+ const developerOption = findCollapsibleListboxOptions().at(2);
+ await developerOption.trigger('click');
+
+ let option;
+ option = findCollapsibleListbox().find('[aria-selected]');
+ expect(option.text()).toBe('Developer');
+
+ // Reset the dropdown by clicking cancel button
+ await findCancelButton().trigger('click');
+
+ option = findCollapsibleListbox().find('[aria-selected]');
+ expect(option.text()).toBe('Reporter');
});
it('renders dropdown items for each accessLevel', () => {
- expect(findFormSelectOptions()).toHaveLength(5);
+ expect(findCollapsibleListboxOptions()).toHaveLength(5);
});
});
@@ -211,7 +246,7 @@ describe('InviteModalBase', () => {
it('renders correct blocks', () => {
expect(findIcon().exists()).toBe(false);
expect(findDisabledInput().exists()).toBe(false);
- expect(findFormSelect().exists()).toBe(true);
+ expect(findCollapsibleListbox().exists()).toBe(true);
expect(findDatepicker().exists()).toBe(true);
expect(wrapper.findComponent(GlModal).text()).toMatch(textRegex);
});
diff --git a/spec/frontend/invite_members/components/members_token_select_spec.js b/spec/frontend/invite_members/components/members_token_select_spec.js
index a4b8a8b0197..a2b21367388 100644
--- a/spec/frontend/invite_members/components/members_token_select_spec.js
+++ b/spec/frontend/invite_members/components/members_token_select_spec.js
@@ -6,23 +6,32 @@ import waitForPromises from 'helpers/wait_for_promises';
import * as UserApi from '~/api/user_api';
import MembersTokenSelect from '~/invite_members/components/members_token_select.vue';
import { VALID_TOKEN_BACKGROUND, INVALID_TOKEN_BACKGROUND } from '~/invite_members/constants';
+import * as Sentry from '~/sentry/sentry_browser_wrapper';
const label = 'testgroup';
const placeholder = 'Search for a member';
+const rootGroupId = '31';
const user1 = { id: 1, name: 'John Smith', username: 'one_1', avatar_url: '' };
const user2 = { id: 2, name: 'Jane Doe', username: 'two_2', avatar_url: '' };
const allUsers = [user1, user2];
+const handleEnterSpy = jest.fn();
-const createComponent = (props) => {
+const createComponent = (props = {}, glFeatures = {}) => {
return shallowMount(MembersTokenSelect, {
propsData: {
ariaLabelledby: label,
invalidMembers: {},
placeholder,
+ rootGroupId,
...props,
},
+ provide: { glFeatures },
stubs: {
- GlTokenSelector: stubComponent(GlTokenSelector),
+ GlTokenSelector: stubComponent(GlTokenSelector, {
+ methods: {
+ handleEnter: handleEnterSpy,
+ },
+ }),
},
});
};
@@ -84,23 +93,11 @@ describe('MembersTokenSelect', () => {
wrapper = createComponent();
});
- describe('when input is focused for the first time (modal auto-focus)', () => {
- it('does not call the API', async () => {
- findTokenSelector().vm.$emit('focus');
-
- await waitForPromises();
-
- expect(UserApi.getUsers).not.toHaveBeenCalled();
- });
- });
-
describe('when input is manually focused', () => {
it('calls the API and sets dropdown items as request result', async () => {
const tokenSelector = findTokenSelector();
tokenSelector.vm.$emit('focus');
- tokenSelector.vm.$emit('blur');
- tokenSelector.vm.$emit('focus');
await waitForPromises();
@@ -173,6 +170,29 @@ describe('MembersTokenSelect', () => {
});
});
});
+
+ describe('when API search fails', () => {
+ beforeEach(() => {
+ jest.spyOn(Sentry, 'captureException');
+ jest.spyOn(UserApi, 'getUsers').mockRejectedValue('error');
+ });
+
+ it('reports to sentry', async () => {
+ tokenSelector.vm.$emit('text-input', 'Den');
+
+ await waitForPromises();
+
+ expect(Sentry.captureException).toHaveBeenCalledWith('error');
+ });
+ });
+
+ it('allows tab to function as enter', () => {
+ tokenSelector.vm.$emit('text-input', 'username');
+
+ tokenSelector.vm.$emit('keydown', new KeyboardEvent('keydown', { key: 'Tab' }));
+
+ expect(handleEnterSpy).toHaveBeenCalled();
+ });
});
describe('when user is selected', () => {
@@ -215,31 +235,45 @@ describe('MembersTokenSelect', () => {
});
});
- describe('when component is mounted for a group using a saml provider', () => {
+ describe('when component is mounted for a group using a SAML provider', () => {
const searchParam = 'name';
- const samlProviderId = 123;
- let resolveApiRequest;
beforeEach(() => {
- jest.spyOn(UserApi, 'getUsers').mockImplementation(
- () =>
- new Promise((resolve) => {
- resolveApiRequest = resolve;
- }),
- );
+ jest.spyOn(UserApi, 'getGroupUsers').mockResolvedValue({ data: allUsers });
- wrapper = createComponent({ filterId: samlProviderId, usersFilter: 'saml_provider_id' });
+ wrapper = createComponent({ usersFilter: 'saml_provider_id' }, { groupUserSaml: true });
findTokenSelector().vm.$emit('text-input', searchParam);
});
- it('calls the API with the saml provider ID param', () => {
- resolveApiRequest({ data: allUsers });
-
- expect(UserApi.getUsers).toHaveBeenCalledWith(searchParam, {
+ it('calls the group API with correct parameters', () => {
+ expect(UserApi.getGroupUsers).toHaveBeenCalledWith(searchParam, rootGroupId, {
active: true,
- without_project_bots: true,
- saml_provider_id: samlProviderId,
+ include_saml_users: true,
+ include_service_accounts: true,
+ });
+ });
+ });
+
+ describe('when group_user_saml feature flag is disabled', () => {
+ describe('when component is mounted for a group using a SAML provider', () => {
+ const searchParam = 'name';
+ const samlProviderId = 123;
+
+ beforeEach(() => {
+ jest.spyOn(UserApi, 'getUsers').mockResolvedValue({ data: allUsers });
+
+ wrapper = createComponent({ filterId: samlProviderId, usersFilter: 'saml_provider_id' });
+
+ findTokenSelector().vm.$emit('text-input', searchParam);
+ });
+
+ it('calls the API with the saml provider ID param', () => {
+ expect(UserApi.getUsers).toHaveBeenCalledWith(searchParam, {
+ active: true,
+ without_project_bots: true,
+ saml_provider_id: samlProviderId,
+ });
});
});
});
diff --git a/spec/frontend/invite_members/mock_data/member_modal.js b/spec/frontend/invite_members/mock_data/member_modal.js
index 8cde13bf69c..0c0e669b894 100644
--- a/spec/frontend/invite_members/mock_data/member_modal.js
+++ b/spec/frontend/invite_members/mock_data/member_modal.js
@@ -40,6 +40,7 @@ export const user6 = {
export const postData = {
user_id: `${user1.id},${user2.id}`,
access_level: propsData.defaultAccessLevel,
+ member_role_id: null,
expires_at: undefined,
invite_source: inviteSource,
format: 'json',
@@ -47,6 +48,7 @@ export const postData = {
export const emailPostData = {
access_level: propsData.defaultAccessLevel,
+ member_role_id: null,
expires_at: undefined,
email: `${user3.name}`,
invite_source: inviteSource,
@@ -55,6 +57,7 @@ export const emailPostData = {
export const singleUserPostData = {
access_level: propsData.defaultAccessLevel,
+ member_role_id: null,
expires_at: undefined,
user_id: `${user1.id}`,
email: `${user3.name}`,
diff --git a/spec/frontend/invite_members/mock_data/modal_base.js b/spec/frontend/invite_members/mock_data/modal_base.js
index 565e8d4df1e..c44e890da3d 100644
--- a/spec/frontend/invite_members/mock_data/modal_base.js
+++ b/spec/frontend/invite_members/mock_data/modal_base.js
@@ -3,7 +3,7 @@ export const propsData = {
modalId: '_modal_id_',
name: '_name_',
accessLevels: { Guest: 10, Reporter: 20, Developer: 30, Maintainer: 40, Owner: 50 },
- defaultAccessLevel: 10,
+ defaultAccessLevel: 20,
helpLink: 'https://example.com',
labelIntroText: '_label_intro_text_',
labelSearchField: '_label_search_field_',