diff options
Diffstat (limited to 'spec/frontend/projects/commit')
7 files changed, 581 insertions, 0 deletions
diff --git a/spec/frontend/projects/commit/components/branches_dropdown_spec.js b/spec/frontend/projects/commit/components/branches_dropdown_spec.js new file mode 100644 index 00000000000..9fa7d658405 --- /dev/null +++ b/spec/frontend/projects/commit/components/branches_dropdown_spec.js @@ -0,0 +1,166 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; +import { shallowMount } from '@vue/test-utils'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import { GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui'; +import BranchesDropdown from '~/projects/commit/components/branches_dropdown.vue'; + +Vue.use(Vuex); + +describe('BranchesDropdown', () => { + let wrapper; + let store; + const spyFetchBranches = jest.fn(); + + const createComponent = (term, state = { isFetching: false }) => { + store = new Vuex.Store({ + getters: { + joinedBranches: () => ['_master_', '_branch_1_', '_branch_2_'], + }, + actions: { + fetchBranches: spyFetchBranches, + }, + state, + }); + + wrapper = extendedWrapper( + shallowMount(BranchesDropdown, { + store, + propsData: { + value: term, + }, + }), + ); + }; + + const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); + const findSearchBoxByType = () => wrapper.findComponent(GlSearchBoxByType); + const findDropdownItemByIndex = (index) => wrapper.findAllComponents(GlDropdownItem).at(index); + const findNoResults = () => wrapper.findByTestId('empty-result-message'); + const findLoading = () => wrapper.findByTestId('dropdown-text-loading-icon'); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + spyFetchBranches.mockReset(); + }); + + describe('On mount', () => { + beforeEach(() => { + createComponent(''); + }); + + it('invokes fetchBranches', () => { + expect(spyFetchBranches).toHaveBeenCalled(); + }); + }); + + describe('Loading states', () => { + it('shows loading icon while fetching', () => { + createComponent('', { isFetching: true }); + + expect(findLoading().isVisible()).toBe(true); + }); + + it('does not show loading icon', () => { + createComponent(''); + + expect(findLoading().isVisible()).toBe(false); + }); + }); + + describe('No branches found', () => { + beforeEach(() => { + createComponent('_non_existent_branch_'); + }); + + it('renders empty results message', () => { + expect(findNoResults().text()).toBe('No matching results'); + }); + + it('shows GlSearchBoxByType with default attributes', () => { + expect(findSearchBoxByType().exists()).toBe(true); + expect(findSearchBoxByType().vm.$attrs).toMatchObject({ + placeholder: 'Search branches', + debounce: 250, + }); + }); + }); + + describe('Search term is empty', () => { + beforeEach(() => { + createComponent(''); + }); + + it('renders all branches when search term is empty', () => { + expect(findAllDropdownItems()).toHaveLength(3); + expect(findDropdownItemByIndex(0).text()).toBe('_master_'); + expect(findDropdownItemByIndex(1).text()).toBe('_branch_1_'); + expect(findDropdownItemByIndex(2).text()).toBe('_branch_2_'); + }); + + it('should not be selected on the inactive branch', () => { + expect(wrapper.vm.isSelected('_master_')).toBe(false); + }); + }); + + describe('When searching', () => { + beforeEach(() => { + createComponent(''); + }); + + it('invokes fetchBranches', async () => { + const spy = jest.spyOn(wrapper.vm, 'fetchBranches'); + + findSearchBoxByType().vm.$emit('input', '_anything_'); + + await wrapper.vm.$nextTick(); + + expect(spy).toHaveBeenCalledWith('_anything_'); + expect(wrapper.vm.searchTerm).toBe('_anything_'); + }); + }); + + describe('Branches found', () => { + beforeEach(() => { + createComponent('_branch_1_', { branch: '_branch_1_' }); + }); + + it('renders only the branch searched for', () => { + expect(findAllDropdownItems()).toHaveLength(1); + expect(findDropdownItemByIndex(0).text()).toBe('_branch_1_'); + }); + + it('should not display empty results message', () => { + expect(findNoResults().exists()).toBe(false); + }); + + it('should signify this branch is selected', () => { + expect(wrapper.vm.isSelected('_branch_1_')).toBe(true); + }); + + it('should signify the branch is not selected', () => { + expect(wrapper.vm.isSelected('_not_selected_branch_')).toBe(false); + }); + + describe('Custom events', () => { + it('should emit selectBranch if an branch is clicked', () => { + findDropdownItemByIndex(0).vm.$emit('click'); + + expect(wrapper.emitted('selectBranch')).toEqual([['_branch_1_']]); + expect(wrapper.vm.searchTerm).toBe('_branch_1_'); + }); + }); + }); + + describe('Case insensitive for search term', () => { + beforeEach(() => { + createComponent('_BrAnCh_1_'); + }); + + it('renders only the branch searched for', () => { + expect(findAllDropdownItems()).toHaveLength(1); + expect(findDropdownItemByIndex(0).text()).toBe('_branch_1_'); + }); + }); +}); diff --git a/spec/frontend/projects/commit/components/form_modal_spec.js b/spec/frontend/projects/commit/components/form_modal_spec.js new file mode 100644 index 00000000000..1c37b82fed3 --- /dev/null +++ b/spec/frontend/projects/commit/components/form_modal_spec.js @@ -0,0 +1,155 @@ +import MockAdapter from 'axios-mock-adapter'; +import { shallowMount, mount, createWrapper } from '@vue/test-utils'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import { GlModal, GlForm, GlFormCheckbox, GlSprintf } from '@gitlab/ui'; +import { within } from '@testing-library/dom'; +import axios from '~/lib/utils/axios_utils'; +import eventHub from '~/projects/commit/event_hub'; +import CommitFormModal from '~/projects/commit/components/form_modal.vue'; +import BranchesDropdown from '~/projects/commit/components/branches_dropdown.vue'; +import createStore from '~/projects/commit/store'; +import mockData from '../mock_data'; + +describe('CommitFormModal', () => { + let wrapper; + let store; + let axiosMock; + + const createComponent = (method, state = {}, provide = {}) => { + store = createStore({ ...mockData.mockModal, ...state }); + wrapper = extendedWrapper( + method(CommitFormModal, { + provide, + propsData: { ...mockData.modalPropsData }, + store, + attrs: { + static: true, + visible: true, + }, + }), + ); + }; + + const findModal = () => wrapper.findComponent(GlModal); + const findStartBranch = () => wrapper.find('#start_branch'); + const findDropdown = () => wrapper.findComponent(BranchesDropdown); + const findForm = () => findModal().findComponent(GlForm); + const findCheckBox = () => findForm().findComponent(GlFormCheckbox); + const findPrependedText = () => wrapper.findByTestId('prepended-text'); + const findAppendedText = () => wrapper.findByTestId('appended-text'); + const getByText = (text, options) => + createWrapper(within(findModal().element).getByText(text, options)); + + beforeEach(() => { + axiosMock = new MockAdapter(axios); + }); + + afterEach(() => { + wrapper.destroy(); + axiosMock.restore(); + }); + + describe('Basic interactions', () => { + it('Listens for opening of modal on mount', () => { + jest.spyOn(eventHub, '$on'); + + createComponent(shallowMount); + + expect(eventHub.$on).toHaveBeenCalledWith(mockData.modalPropsData.openModal, wrapper.vm.show); + }); + + it('Shows modal', () => { + createComponent(shallowMount); + const rootEmit = jest.spyOn(wrapper.vm.$root, '$emit'); + + wrapper.vm.show(); + + expect(rootEmit).toHaveBeenCalledWith('bv::show::modal', mockData.modalPropsData.modalId); + }); + + it('Clears the modal state once modal is hidden', () => { + createComponent(shallowMount); + jest.spyOn(store, 'dispatch').mockImplementation(); + wrapper.vm.checked = false; + + findModal().vm.$emit('hidden'); + + expect(store.dispatch).toHaveBeenCalledWith('clearModal'); + expect(store.dispatch).toHaveBeenCalledWith('setSelectedBranch', ''); + expect(wrapper.vm.checked).toBe(true); + }); + + it('Shows the checkbox for new merge request', () => { + createComponent(shallowMount); + + expect(findCheckBox().exists()).toBe(true); + }); + + it('Shows the prepended text', () => { + createComponent(shallowMount, {}, { prependedText: '_prepended_text_' }); + + expect(findPrependedText().exists()).toBe(true); + expect(findPrependedText().find(GlSprintf).attributes('message')).toBe('_prepended_text_'); + }); + + it('Does not show prepended text', () => { + createComponent(shallowMount); + + expect(findPrependedText().exists()).toBe(false); + }); + + it('Does not show extra message text', () => { + createComponent(shallowMount); + + expect(findModal().find('[data-testid="appended-text"]').exists()).toBe(false); + }); + + it('Does not show the checkbox for new merge request', () => { + createComponent(shallowMount, { pushCode: false }); + + expect(findCheckBox().exists()).toBe(false); + }); + + it('Shows the branch in fork message', () => { + createComponent(shallowMount, { pushCode: false }); + + expect(findAppendedText().exists()).toBe(true); + expect(findAppendedText().find(GlSprintf).attributes('message')).toContain( + mockData.modalPropsData.i18n.branchInFork, + ); + }); + + it('Shows the branch collaboration message', () => { + createComponent(shallowMount, { pushCode: false, branchCollaboration: true }); + + expect(findAppendedText().exists()).toBe(true); + expect(findAppendedText().find(GlSprintf).attributes('message')).toContain( + mockData.modalPropsData.i18n.existingBranch, + ); + }); + }); + + describe('Taking action on the form', () => { + beforeEach(() => { + createComponent(mount); + }); + + it('Action primary button dispatches submit action', () => { + const submitSpy = jest.spyOn(findForm().element, 'submit'); + + getByText(mockData.modalPropsData.i18n.actionPrimaryText).trigger('click'); + + expect(submitSpy).toHaveBeenCalled(); + + submitSpy.mockRestore(); + }); + + it('Changes the start_branch input value', async () => { + findDropdown().vm.$emit('selectBranch', '_changed_branch_value_'); + + await wrapper.vm.$nextTick(); + + expect(findStartBranch().attributes('value')).toBe('_changed_branch_value_'); + }); + }); +}); diff --git a/spec/frontend/projects/commit/components/form_trigger_spec.js b/spec/frontend/projects/commit/components/form_trigger_spec.js new file mode 100644 index 00000000000..ca51419d6a5 --- /dev/null +++ b/spec/frontend/projects/commit/components/form_trigger_spec.js @@ -0,0 +1,44 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlLink } from '@gitlab/ui'; +import FormTrigger from '~/projects/commit/components/form_trigger.vue'; +import eventHub from '~/projects/commit/event_hub'; + +const displayText = '_display_text_'; + +const createComponent = () => { + return shallowMount(FormTrigger, { + provide: { displayText }, + propsData: { openModal: '_open_modal_' }, + }); +}; + +describe('FormTrigger', () => { + let wrapper; + let spy; + + beforeEach(() => { + spy = jest.spyOn(eventHub, '$emit'); + wrapper = createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + const findLink = () => wrapper.find(GlLink); + + describe('displayText', () => { + it('includes the correct displayText for the link', () => { + expect(findLink().text()).toBe(displayText); + }); + }); + + describe('clicking the link', () => { + it('emits openModal', () => { + findLink().vm.$emit('click'); + + expect(spy).toHaveBeenCalledWith('_open_modal_'); + }); + }); +}); diff --git a/spec/frontend/projects/commit/mock_data.js b/spec/frontend/projects/commit/mock_data.js new file mode 100644 index 00000000000..2b3b5a14c98 --- /dev/null +++ b/spec/frontend/projects/commit/mock_data.js @@ -0,0 +1,27 @@ +import { I18N_MODAL } from '~/projects/commit/constants'; + +export default { + mockModal: { + modalTitle: '_modal_title_', + endpoint: '_endpoint_', + branch: '_branch_', + pushCode: true, + defaultBranch: '_branch_', + existingBranch: '_existing_branch', + branchesEndpoint: '_branches_endpoint_', + }, + modalPropsData: { + i18n: { + branchLabel: '_branch_label_', + actionPrimaryText: '_action_primary_text_', + startMergeRequest: '_start_merge_request_', + existingBranch: I18N_MODAL.existingBranch, + branchInFork: '_new_branch_in_fork_message_', + newMergeRequest: '_new merge request_', + actionCancelText: '_action_cancel_text_', + }, + modalId: '_modal_id_', + openModal: '_open_modal_', + }, + mockBranches: ['_branch_1', '_abc_', '_master_'], +}; diff --git a/spec/frontend/projects/commit/store/actions_spec.js b/spec/frontend/projects/commit/store/actions_spec.js new file mode 100644 index 00000000000..ec528d4ee88 --- /dev/null +++ b/spec/frontend/projects/commit/store/actions_spec.js @@ -0,0 +1,111 @@ +import MockAdapter from 'axios-mock-adapter'; +import testAction from 'helpers/vuex_action_helper'; +import axios from '~/lib/utils/axios_utils'; +import createFlash from '~/flash'; +import getInitialState from '~/projects/commit/store/state'; +import * as actions from '~/projects/commit/store/actions'; +import * as types from '~/projects/commit/store/mutation_types'; +import mockData from '../mock_data'; +import { PROJECT_BRANCHES_ERROR } from '~/projects/commit/constants'; + +jest.mock('~/flash.js'); + +describe('Commit form modal store actions', () => { + let axiosMock; + let state; + + beforeEach(() => { + axiosMock = new MockAdapter(axios); + state = getInitialState(); + }); + + afterEach(() => { + axiosMock.restore(); + }); + + describe('clearModal', () => { + it('commits CLEAR_MODAL mutation', () => { + testAction(actions.clearModal, {}, {}, [ + { + type: types.CLEAR_MODAL, + }, + ]); + }); + }); + + describe('requestBranches', () => { + it('commits REQUEST_BRANCHES mutation', () => { + testAction(actions.requestBranches, {}, {}, [ + { + type: types.REQUEST_BRANCHES, + }, + ]); + }); + }); + + describe('fetchBranches', () => { + it('dispatch correct actions on fetchBranches', (done) => { + jest + .spyOn(axios, 'get') + .mockImplementation(() => Promise.resolve({ data: mockData.mockBranches })); + + testAction( + actions.fetchBranches, + {}, + state, + [ + { + type: types.RECEIVE_BRANCHES_SUCCESS, + payload: mockData.mockBranches, + }, + ], + [{ type: 'requestBranches' }], + () => { + done(); + }, + ); + }); + + it('should show flash error and set error in state on fetchBranches failure', (done) => { + jest.spyOn(axios, 'get').mockRejectedValue(); + + testAction(actions.fetchBranches, {}, state, [], [{ type: 'requestBranches' }], () => { + expect(createFlash).toHaveBeenCalledWith({ message: PROJECT_BRANCHES_ERROR }); + done(); + }); + }); + }); + + describe('setBranch', () => { + it('commits SET_BRANCH mutation', () => { + testAction( + actions.setBranch, + {}, + {}, + [ + { + type: types.SET_BRANCH, + payload: {}, + }, + ], + [ + { + type: 'setSelectedBranch', + payload: {}, + }, + ], + ); + }); + }); + + describe('setSelectedBranch', () => { + it('commits SET_SELECTED_BRANCH mutation', () => { + testAction(actions.setSelectedBranch, {}, {}, [ + { + type: types.SET_SELECTED_BRANCH, + payload: {}, + }, + ]); + }); + }); +}); diff --git a/spec/frontend/projects/commit/store/getters_spec.js b/spec/frontend/projects/commit/store/getters_spec.js new file mode 100644 index 00000000000..bd0cb356854 --- /dev/null +++ b/spec/frontend/projects/commit/store/getters_spec.js @@ -0,0 +1,21 @@ +import * as getters from '~/projects/commit/store/getters'; +import mockData from '../mock_data'; + +describe('Commit form modal getters', () => { + describe('joinedBranches', () => { + it('should join fetched branches with variable branches', () => { + const state = { + branches: mockData.mockBranches, + }; + + expect(getters.joinedBranches(state)).toEqual(mockData.mockBranches.sort()); + }); + + it('should provide a uniq list of branches', () => { + const branches = ['_branch_', '_branch_', '_different_branch']; + const state = { branches }; + + expect(getters.joinedBranches(state)).toEqual(branches.slice(1)); + }); + }); +}); diff --git a/spec/frontend/projects/commit/store/mutations_spec.js b/spec/frontend/projects/commit/store/mutations_spec.js new file mode 100644 index 00000000000..59ab3d9a74a --- /dev/null +++ b/spec/frontend/projects/commit/store/mutations_spec.js @@ -0,0 +1,57 @@ +import mutations from '~/projects/commit/store/mutations'; +import * as types from '~/projects/commit/store/mutation_types'; + +describe('Commit form modal mutations', () => { + let stateCopy; + + describe('REQUEST_BRANCHES', () => { + it('should set isFetching to true', () => { + stateCopy = { isFetching: false }; + + mutations[types.REQUEST_BRANCHES](stateCopy); + + expect(stateCopy.isFetching).toBe(true); + }); + }); + + describe('RECEIVE_BRANCHES_SUCCESS', () => { + it('should set branches', () => { + stateCopy = { branch: '_existing_branch_', isFetching: true }; + + mutations[types.RECEIVE_BRANCHES_SUCCESS](stateCopy, ['_branch_1_', '_branch_2_']); + + expect(stateCopy.branches).toEqual(['_existing_branch_', '_branch_1_', '_branch_2_']); + expect(stateCopy.isFetching).toEqual(false); + }); + }); + + describe('CLEAR_MODAL', () => { + it('should clear modal state ', () => { + stateCopy = { branch: '_master_', defaultBranch: '_default_branch_' }; + + mutations[types.CLEAR_MODAL](stateCopy); + + expect(stateCopy.branch).toEqual('_default_branch_'); + }); + }); + + describe('SET_BRANCH', () => { + it('should set branch', () => { + stateCopy = { branch: '_master_' }; + + mutations[types.SET_BRANCH](stateCopy, '_changed_branch_'); + + expect(stateCopy.branch).toBe('_changed_branch_'); + }); + }); + + describe('SET_SELECTED_BRANCH', () => { + it('should set selectedBranch', () => { + stateCopy = { selectedBranch: '_master_' }; + + mutations[types.SET_SELECTED_BRANCH](stateCopy, '_changed_branch_'); + + expect(stateCopy.selectedBranch).toBe('_changed_branch_'); + }); + }); +}); |