diff options
Diffstat (limited to 'spec/frontend/invite_members')
5 files changed, 224 insertions, 40 deletions
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 84317da39e6..13985ce7d74 100644 --- a/spec/frontend/invite_members/components/invite_members_modal_spec.js +++ b/spec/frontend/invite_members/components/invite_members_modal_spec.js @@ -1,4 +1,4 @@ -import { GlLink, GlModal, GlSprintf } from '@gitlab/ui'; +import { GlLink, GlModal, GlSprintf, GlFormGroup } from '@gitlab/ui'; import MockAdapter from 'axios-mock-adapter'; import { nextTick } from 'vue'; import { stubComponent } from 'helpers/stub_component'; @@ -15,6 +15,7 @@ import { MEMBERS_MODAL_CELEBRATE_INTRO, MEMBERS_MODAL_CELEBRATE_TITLE, MEMBERS_PLACEHOLDER, + MEMBERS_PLACEHOLDER_DISABLED, MEMBERS_TO_PROJECT_CELEBRATE_INTRO_TEXT, LEARN_GITLAB, } from '~/invite_members/constants'; @@ -28,6 +29,8 @@ import { propsData, inviteSource, newProjectPath, + freeUsersLimit, + membersCount, user1, user2, user3, @@ -45,12 +48,13 @@ describe('InviteMembersModal', () => { let wrapper; let mock; - const createComponent = (props = {}) => { + const createComponent = (props = {}, stubs = {}) => { wrapper = shallowMountExtended(InviteMembersModal, { provide: { newProjectPath, }, propsData: { + usersLimitDataset: {}, ...propsData, ...props, }, @@ -62,16 +66,17 @@ describe('InviteMembersModal', () => { template: '<div><slot></slot><slot name="modal-footer"></slot></div>', }), GlEmoji, + ...stubs, }, }); }; - const createInviteMembersToProjectWrapper = () => { - createComponent({ isProject: true }); + const createInviteMembersToProjectWrapper = (usersLimitDataset = {}, stubs = {}) => { + createComponent({ usersLimitDataset, isProject: true }, stubs); }; - const createInviteMembersToGroupWrapper = () => { - createComponent({ isProject: false }); + const createInviteMembersToGroupWrapper = (usersLimitDataset = {}, stubs = {}) => { + createComponent({ usersLimitDataset, isProject: false }, stubs); }; beforeEach(() => { @@ -95,7 +100,7 @@ describe('InviteMembersModal', () => { const findMembersFormGroup = () => wrapper.findByTestId('members-form-group'); const membersFormGroupInvalidFeedback = () => findMembersFormGroup().attributes('invalid-feedback'); - const membersFormGroupDescription = () => findMembersFormGroup().attributes('description'); + const membersFormGroupText = () => findMembersFormGroup().text(); const findMembersSelect = () => wrapper.findComponent(MembersTokenSelect); const findTasksToBeDone = () => wrapper.findByTestId('invite-members-modal-tasks-to-be-done'); const findTasks = () => wrapper.findByTestId('invite-members-modal-tasks'); @@ -259,16 +264,33 @@ describe('InviteMembersModal', () => { expect(wrapper.findComponent(ModalConfetti).exists()).toBe(false); }); - it('includes the correct invitee, type, and formatted name', () => { + it('includes the correct invitee', () => { expect(findIntroText()).toBe("You're inviting members to the test name project."); expect(findCelebrationEmoji().exists()).toBe(false); - expect(membersFormGroupDescription()).toBe(MEMBERS_PLACEHOLDER); + }); + + describe('members form group description', () => { + it('renders correct description', () => { + createInviteMembersToProjectWrapper({ freeUsersLimit, membersCount }, { GlFormGroup }); + expect(membersFormGroupText()).toContain(MEMBERS_PLACEHOLDER); + }); + + describe('when reached user limit', () => { + it('renders correct description', () => { + createInviteMembersToProjectWrapper( + { freeUsersLimit, membersCount: 5 }, + { GlFormGroup }, + ); + + expect(membersFormGroupText()).toContain(MEMBERS_PLACEHOLDER_DISABLED); + }); + }); }); }); describe('when inviting members with celebration', () => { beforeEach(async () => { - createComponent({ isProject: true }); + createInviteMembersToProjectWrapper(); await triggerOpenModal({ mode: 'celebrate' }); }); @@ -285,7 +307,28 @@ describe('InviteMembersModal', () => { `${MEMBERS_TO_PROJECT_CELEBRATE_INTRO_TEXT} ${MEMBERS_MODAL_CELEBRATE_INTRO}`, ); expect(findCelebrationEmoji().exists()).toBe(true); - expect(membersFormGroupDescription()).toBe(MEMBERS_PLACEHOLDER); + }); + + describe('members form group description', () => { + it('renders correct description', async () => { + createInviteMembersToProjectWrapper({ freeUsersLimit, membersCount }, { GlFormGroup }); + await triggerOpenModal({ mode: 'celebrate' }); + + expect(membersFormGroupText()).toContain(MEMBERS_PLACEHOLDER); + }); + + describe('when reached user limit', () => { + it('renders correct description', async () => { + createInviteMembersToProjectWrapper( + { freeUsersLimit, membersCount: 5 }, + { GlFormGroup }, + ); + + await triggerOpenModal({ mode: 'celebrate' }); + + expect(membersFormGroupText()).toContain(MEMBERS_PLACEHOLDER_DISABLED); + }); + }); }); }); }); @@ -295,7 +338,20 @@ describe('InviteMembersModal', () => { createInviteMembersToGroupWrapper(); expect(findIntroText()).toBe("You're inviting members to the test name group."); - expect(membersFormGroupDescription()).toBe(MEMBERS_PLACEHOLDER); + }); + + describe('members form group description', () => { + it('renders correct description', () => { + createInviteMembersToGroupWrapper({ freeUsersLimit, membersCount }, { GlFormGroup }); + expect(membersFormGroupText()).toContain(MEMBERS_PLACEHOLDER); + }); + + describe('when reached user limit', () => { + it('renders correct description', () => { + createInviteMembersToGroupWrapper({ freeUsersLimit, membersCount: 5 }, { GlFormGroup }); + expect(membersFormGroupText()).toContain(MEMBERS_PLACEHOLDER_DISABLED); + }); + }); }); }); }); 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 8355ae67f20..010f7b999fc 100644 --- a/spec/frontend/invite_members/components/invite_modal_base_spec.js +++ b/spec/frontend/invite_members/components/invite_modal_base_spec.js @@ -6,18 +6,30 @@ import { GlSprintf, GlLink, GlModal, + GlIcon, } from '@gitlab/ui'; import { stubComponent } from 'helpers/stub_component'; +import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; import { shallowMountExtended } 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'; -import { CANCEL_BUTTON_TEXT, INVITE_BUTTON_TEXT } from '~/invite_members/constants'; -import { propsData } from '../mock_data/modal_base'; + +import { + CANCEL_BUTTON_TEXT, + INVITE_BUTTON_TEXT_DISABLED, + INVITE_BUTTON_TEXT, + CANCEL_BUTTON_TEXT_DISABLED, + ON_SHOW_TRACK_LABEL, + ON_CLOSE_TRACK_LABEL, + ON_SUBMIT_TRACK_LABEL, +} from '~/invite_members/constants'; + +import { propsData, membersPath, purchasePath } from '../mock_data/modal_base'; describe('InviteModalBase', () => { let wrapper; - const createComponent = (props = {}) => { + const createComponent = (props = {}, stubs = {}) => { wrapper = shallowMountExtended(InviteModalBase, { propsData: { ...propsData, @@ -33,8 +45,9 @@ describe('InviteModalBase', () => { GlDropdownItem: true, GlSprintf, GlFormGroup: stubComponent(GlFormGroup, { - props: ['state', 'invalidFeedback', 'description'], + props: ['state', 'invalidFeedback'], }), + ...stubs, }, }); }; @@ -48,8 +61,12 @@ describe('InviteModalBase', () => { const findDropdownItems = () => findDropdown().findAllComponents(GlDropdownItem); const findDatepicker = () => wrapper.findComponent(GlDatepicker); const findLink = () => wrapper.findComponent(GlLink); + const findIcon = () => wrapper.findComponent(GlIcon); const findIntroText = () => wrapper.findByTestId('modal-base-intro-text').text(); const findMembersFormGroup = () => wrapper.findByTestId('members-form-group'); + const findDisabledInput = () => wrapper.findByTestId('disabled-input'); + const findCancelButton = () => wrapper.find('.js-modal-action-cancel'); + const findActionButton = () => wrapper.find('.js-modal-action-primary'); describe('rendering the modal', () => { beforeEach(() => { @@ -106,11 +123,103 @@ describe('InviteModalBase', () => { it('renders the members form group', () => { expect(findMembersFormGroup().props()).toEqual({ - description: propsData.formGroupDescription, invalidFeedback: '', state: null, }); }); + + it('renders description', () => { + createComponent({}, { GlFormGroup }); + + expect(findMembersFormGroup().text()).toContain(propsData.formGroupDescription); + }); + + describe('when users limit is reached', () => { + let trackingSpy; + + const expectTracking = (action, label) => + expect(trackingSpy).toHaveBeenCalledWith('default', action, { + label, + category: 'default', + }); + + beforeEach(() => { + createComponent( + { usersLimitDataset: { membersPath, purchasePath }, reachedLimit: true }, + { GlModal, GlFormGroup }, + ); + }); + + it('renders correct blocks', () => { + expect(findIcon().exists()).toBe(true); + expect(findDisabledInput().exists()).toBe(true); + expect(findDropdown().exists()).toBe(false); + expect(findDatepicker().exists()).toBe(false); + }); + + it('renders correct buttons', () => { + const cancelButton = findCancelButton(); + const actionButton = findActionButton(); + + expect(cancelButton.attributes('href')).toBe(purchasePath); + expect(cancelButton.text()).toBe(CANCEL_BUTTON_TEXT_DISABLED); + expect(actionButton.attributes('href')).toBe(membersPath); + expect(actionButton.text()).toBe(INVITE_BUTTON_TEXT_DISABLED); + }); + + it('tracks actions', () => { + createComponent({ reachedLimit: true }, { GlFormGroup, GlModal }); + trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + + const modal = wrapper.findComponent(GlModal); + + modal.vm.$emit('shown'); + expectTracking('render', ON_SHOW_TRACK_LABEL); + + modal.vm.$emit('cancel', { preventDefault: jest.fn() }); + expectTracking('click_button', ON_CLOSE_TRACK_LABEL); + + modal.vm.$emit('primary', { preventDefault: jest.fn() }); + expectTracking('click_button', ON_SUBMIT_TRACK_LABEL); + + unmockTracking(); + }); + + describe('when free user namespace', () => { + it('hides cancel button', () => { + createComponent( + { + usersLimitDataset: { membersPath, purchasePath, userNamespace: true }, + reachedLimit: true, + }, + { GlModal, GlFormGroup }, + ); + + expect(findCancelButton().exists()).toBe(false); + }); + }); + }); + + describe('when users limit is not reached', () => { + const textRegex = /Select a role.+Read more about role permissions Access expiration date \(optional\)/; + + beforeEach(() => { + createComponent({ reachedLimit: false }, { GlModal, GlFormGroup }); + }); + + it('renders correct blocks', () => { + expect(findIcon().exists()).toBe(false); + expect(findDisabledInput().exists()).toBe(false); + expect(findDropdown().exists()).toBe(true); + expect(findDatepicker().exists()).toBe(true); + expect(wrapper.findComponent(GlModal).text()).toMatch(textRegex); + }); + + it('renders correct buttons', () => { + expect(findCancelButton().text()).toBe(CANCEL_BUTTON_TEXT); + expect(findActionButton().text()).toBe(INVITE_BUTTON_TEXT); + }); + }); }); it('with isLoading, shows loading for invite button', () => { @@ -127,7 +236,6 @@ describe('InviteModalBase', () => { }); expect(findMembersFormGroup().props()).toEqual({ - description: propsData.formGroupDescription, invalidFeedback: 'invalid message!', state: false, }); diff --git a/spec/frontend/invite_members/components/user_limit_notification_spec.js b/spec/frontend/invite_members/components/user_limit_notification_spec.js index c779cf2ee3f..4c9adbfcc44 100644 --- a/spec/frontend/invite_members/components/user_limit_notification_spec.js +++ b/spec/frontend/invite_members/components/user_limit_notification_spec.js @@ -2,21 +2,31 @@ import { GlAlert, GlSprintf } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import UserLimitNotification from '~/invite_members/components/user_limit_notification.vue'; +import { + REACHED_LIMIT_MESSAGE, + REACHED_LIMIT_UPGRADE_SUGGESTION_MESSAGE, +} from '~/invite_members/constants'; + +import { freeUsersLimit, membersCount } from '../mock_data/member_modal'; + describe('UserLimitNotification', () => { let wrapper; const findAlert = () => wrapper.findComponent(GlAlert); - const createComponent = (providers = {}) => { + const createComponent = (reachedLimit = false, usersLimitDataset = {}) => { wrapper = shallowMountExtended(UserLimitNotification, { - provide: { - name: 'my group', - newTrialRegistrationPath: 'newTrialRegistrationPath', - purchasePath: 'purchasePath', - freeUsersLimit: 5, - membersCount: 1, - ...providers, + propsData: { + reachedLimit, + usersLimitDataset: { + freeUsersLimit, + membersCount, + newTrialRegistrationPath: 'newTrialRegistrationPath', + purchasePath: 'purchasePath', + ...usersLimitDataset, + }, }, + provide: { name: 'my group' }, stubs: { GlSprintf }, }); }; @@ -26,21 +36,17 @@ describe('UserLimitNotification', () => { }); describe('when limit is not reached', () => { - beforeEach(() => { + it('renders empty block', () => { createComponent(); - }); - it('renders empty block', () => { expect(findAlert().exists()).toBe(false); }); }); describe('when close to limit', () => { - beforeEach(() => { - createComponent({ membersCount: 3 }); - }); - it("renders user's limit notification", () => { + createComponent(false, { membersCount: 3 }); + const alert = findAlert(); expect(alert.attributes('title')).toEqual( @@ -54,18 +60,27 @@ describe('UserLimitNotification', () => { }); describe('when limit is reached', () => { - beforeEach(() => { - createComponent({ membersCount: 5 }); - }); - it("renders user's limit notification", () => { + createComponent(true); + const alert = findAlert(); expect(alert.attributes('title')).toEqual("You've reached your 5 members limit for my group"); + expect(alert.text()).toEqual(REACHED_LIMIT_UPGRADE_SUGGESTION_MESSAGE); + }); - expect(alert.text()).toEqual( - 'New members will be unable to participate. You can manage your members by removing ones you no longer need. To get more members an owner of this namespace can start a trial or upgrade to a paid tier.', - ); + describe('when free user namespace', () => { + it("renders user's limit notification", () => { + createComponent(true, { userNamespace: true }); + + const alert = findAlert(); + + expect(alert.attributes('title')).toEqual( + "You've reached your 5 members limit for my group", + ); + + expect(alert.text()).toEqual(REACHED_LIMIT_MESSAGE); + }); }); }); }); diff --git a/spec/frontend/invite_members/mock_data/member_modal.js b/spec/frontend/invite_members/mock_data/member_modal.js index 1b0cc57fb5b..474234cfacb 100644 --- a/spec/frontend/invite_members/mock_data/member_modal.js +++ b/spec/frontend/invite_members/mock_data/member_modal.js @@ -18,6 +18,8 @@ export const propsData = { export const inviteSource = 'unknown'; export const newProjectPath = 'projects/new'; +export const freeUsersLimit = 5; +export const membersCount = 1; export const user1 = { id: 1, name: 'Name One', username: 'one_1', avatar_url: '' }; export const user2 = { id: 2, name: 'Name Two', username: 'one_2', avatar_url: '' }; diff --git a/spec/frontend/invite_members/mock_data/modal_base.js b/spec/frontend/invite_members/mock_data/modal_base.js index ea5a8d2b00d..565e8d4df1e 100644 --- a/spec/frontend/invite_members/mock_data/modal_base.js +++ b/spec/frontend/invite_members/mock_data/modal_base.js @@ -9,3 +9,6 @@ export const propsData = { labelSearchField: '_label_search_field_', formGroupDescription: '_form_group_description_', }; + +export const membersPath = '/members_path'; +export const purchasePath = '/purchase_path'; |