import { GlDropdown, GlDropdownItem, GlDatepicker, GlFormGroup, 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_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 = {}, stubs = {}) => { wrapper = shallowMountExtended(InviteModalBase, { propsData: { ...propsData, ...props, }, stubs: { ContentTransition, GlModal: stubComponent(GlModal, { template: '
', }), GlDropdown: true, GlDropdownItem: true, GlSprintf, GlFormGroup: stubComponent(GlFormGroup, { props: ['state', 'invalidFeedback'], }), ...stubs, }, }); }; afterEach(() => { wrapper.destroy(); wrapper = null; }); const findDropdown = () => wrapper.findComponent(GlDropdown); 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(() => { createComponent(); }); it('renders the modal with the correct title', () => { expect(wrapper.findComponent(GlModal).props('title')).toBe(propsData.modalTitle); }); it('displays the introText', () => { expect(findIntroText()).toBe(propsData.labelIntroText); }); it('renders the Cancel button text correctly', () => { expect(wrapper.findComponent(GlModal).props('actionCancel')).toMatchObject({ text: CANCEL_BUTTON_TEXT, }); }); it('renders the Invite button correctly', () => { expect(wrapper.findComponent(GlModal).props('actionPrimary')).toMatchObject({ text: INVITE_BUTTON_TEXT, attributes: { variant: 'confirm', disabled: false, loading: false, 'data-qa-selector': 'invite_button', }, }); }); describe('rendering the access levels dropdown', () => { it('sets the default dropdown text to the default access level name', () => { expect(findDropdown().attributes('text')).toBe('Guest'); }); it('renders dropdown items for each accessLevel', () => { expect(findDropdownItems()).toHaveLength(5); }); }); describe('rendering the help link', () => { it('renders the correct link', () => { expect(findLink().attributes('href')).toBe(propsData.helpLink); }); }); describe('rendering the access expiration date field', () => { it('renders the datepicker', () => { expect(findDatepicker().exists()).toBe(true); }); }); it('renders the members form group', () => { expect(findMembersFormGroup().props()).toEqual({ 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 user limit is close on a personal namespace', () => { beforeEach(() => { createComponent( { closeToLimit: true, reachedLimit: false, usersLimitDataset: { membersPath, userNamespace: true }, }, { GlModal, GlFormGroup }, ); }); it('renders correct buttons', () => { const cancelButton = findCancelButton(); const actionButton = findActionButton(); expect(cancelButton.text()).toBe(INVITE_BUTTON_TEXT_DISABLED); expect(cancelButton.attributes('href')).toBe(membersPath); expect(actionButton.text()).toBe(INVITE_BUTTON_TEXT); expect(actionButton.attributes('href')).toBe(); // default submit button }); }); 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', () => { createComponent({ isLoading: true, }); expect(wrapper.findComponent(GlModal).props('actionPrimary').attributes.loading).toBe(true); }); it('with invalidFeedbackMessage, set members form group validation state', () => { createComponent({ invalidFeedbackMessage: 'invalid message!', }); expect(findMembersFormGroup().props()).toEqual({ invalidFeedback: 'invalid message!', state: false, }); }); });