diff options
Diffstat (limited to 'spec/frontend/add_context_commits_modal')
5 files changed, 670 insertions, 0 deletions
diff --git a/spec/frontend/add_context_commits_modal/components/__snapshots__/add_context_commits_modal_spec.js.snap b/spec/frontend/add_context_commits_modal/components/__snapshots__/add_context_commits_modal_spec.js.snap new file mode 100644 index 00000000000..5fad0d07f97 --- /dev/null +++ b/spec/frontend/add_context_commits_modal/components/__snapshots__/add_context_commits_modal_spec.js.snap @@ -0,0 +1,50 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AddContextCommitsModal renders modal with 2 tabs 1`] = ` +<gl-modal-stub + body-class="add-review-item pt-0" + cancel-variant="light" + modalclass="" + modalid="add-review-item" + ok-disabled="true" + ok-title="Save changes" + scrollable="true" + size="md" + title="Add or remove previously merged commits" + titletag="h4" +> + <gl-tabs-stub + contentclass="pt-0" + theme="indigo" + value="0" + > + <gl-tab-stub> + + <div + class="mt-2" + > + <gl-search-box-by-type-stub + clearbuttontitle="Clear" + placeholder="Search by commit title or SHA" + value="" + /> + + <review-tab-container-stub + commits="" + emptylisttext="Your search didn't match any commits. Try a different query." + loadingfailedtext="Unable to load commits. Try again later." + /> + </div> + </gl-tab-stub> + + <gl-tab-stub> + + <review-tab-container-stub + commits="" + emptylisttext="Commits you select appear here. Go to the first tab and select commits to add to this merge request." + loadingfailedtext="Unable to load commits. Try again later." + /> + </gl-tab-stub> + </gl-tabs-stub> +</gl-modal-stub> +`; diff --git a/spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js b/spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js new file mode 100644 index 00000000000..6904e34db5d --- /dev/null +++ b/spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js @@ -0,0 +1,174 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; +import { GlModal, GlSearchBoxByType } from '@gitlab/ui'; +import AddReviewItemsModal from '~/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue'; +import getDiffWithCommit from '../../diffs/mock_data/diff_with_commit'; + +import defaultState from '~/add_context_commits_modal/store/state'; +import mutations from '~/add_context_commits_modal/store/mutations'; +import * as actions from '~/add_context_commits_modal/store/actions'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('AddContextCommitsModal', () => { + let wrapper; + let store; + const createContextCommits = jest.fn(); + const removeContextCommits = jest.fn(); + const resetModalState = jest.fn(); + const searchCommits = jest.fn(); + const { commit } = getDiffWithCommit(); + + const createWrapper = (props = {}) => { + store = new Vuex.Store({ + mutations, + state: { + ...defaultState(), + }, + actions: { + ...actions, + searchCommits, + createContextCommits, + removeContextCommits, + resetModalState, + }, + }); + + wrapper = shallowMount(AddReviewItemsModal, { + localVue, + store, + propsData: { + contextCommitsPath: '', + targetBranch: 'master', + mergeRequestIid: 1, + projectId: 1, + ...props, + }, + }); + return wrapper; + }; + + const findModal = () => wrapper.find(GlModal); + const findSearch = () => wrapper.find(GlSearchBoxByType); + + beforeEach(() => { + wrapper = createWrapper(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders modal with 2 tabs', () => { + expect(wrapper.element).toMatchSnapshot(); + }); + + it('an ok button labeled "Save changes"', () => { + expect(findModal().attributes('ok-title')).toEqual('Save changes'); + }); + + describe('when in first tab, renders a modal with', () => { + it('renders the search box component', () => { + expect(findSearch().exists()).toBe(true); + }); + + it('when user starts entering text in search box, it calls action "searchCommits" after waiting for 500s', () => { + const searchText = 'abcd'; + findSearch().vm.$emit('input', searchText); + expect(searchCommits).not.toBeCalled(); + jest.advanceTimersByTime(500); + expect(searchCommits).toHaveBeenCalledWith(expect.anything(), searchText, undefined); + }); + + it('disabled ok button when no row is selected', () => { + expect(findModal().attributes('ok-disabled')).toBe('true'); + }); + + it('enabled ok button when atleast one row is selected', () => { + wrapper.vm.$store.state.selectedCommits = [{ ...commit, isSelected: true }]; + return wrapper.vm.$nextTick().then(() => { + expect(findModal().attributes('ok-disabled')).toBeFalsy(); + }); + }); + }); + + describe('when in second tab, renders a modal with', () => { + beforeEach(() => { + wrapper.vm.$store.state.tabIndex = 1; + }); + it('a disabled ok button when no row is selected', () => { + expect(findModal().attributes('ok-disabled')).toBe('true'); + }); + + it('an enabled ok button when atleast one row is selected', () => { + wrapper.vm.$store.state.selectedCommits = [{ ...commit, isSelected: true }]; + return wrapper.vm.$nextTick().then(() => { + expect(findModal().attributes('ok-disabled')).toBeFalsy(); + }); + }); + + it('a disabled ok button in first tab, when row is selected in second tab', () => { + createWrapper({ selectedContextCommits: [commit] }); + expect(wrapper.find(GlModal).attributes('ok-disabled')).toBe('true'); + }); + }); + + describe('has an ok button when clicked calls action', () => { + it('"createContextCommits" when only new commits to be added ', () => { + wrapper.vm.$store.state.selectedCommits = [{ ...commit, isSelected: true }]; + findModal().vm.$emit('ok'); + return wrapper.vm.$nextTick().then(() => { + expect(createContextCommits).toHaveBeenCalledWith( + expect.anything(), + { commits: [{ ...commit, isSelected: true }], forceReload: true }, + undefined, + ); + }); + }); + it('"removeContextCommits" when only added commits are to be removed ', () => { + wrapper.vm.$store.state.toRemoveCommits = [commit.short_id]; + findModal().vm.$emit('ok'); + return wrapper.vm.$nextTick().then(() => { + expect(removeContextCommits).toHaveBeenCalledWith(expect.anything(), true, undefined); + }); + }); + it('"createContextCommits" and "removeContextCommits" when new commits are to be added and old commits are to be removed', () => { + wrapper.vm.$store.state.selectedCommits = [{ ...commit, isSelected: true }]; + wrapper.vm.$store.state.toRemoveCommits = [commit.short_id]; + findModal().vm.$emit('ok'); + return wrapper.vm.$nextTick().then(() => { + expect(createContextCommits).toHaveBeenCalledWith( + expect.anything(), + { commits: [{ ...commit, isSelected: true }] }, + undefined, + ); + expect(removeContextCommits).toHaveBeenCalledWith(expect.anything(), undefined, undefined); + }); + }); + }); + + describe('has a cancel button when clicked', () => { + it('does not call "createContextCommits" or "removeContextCommits"', () => { + findModal().vm.$emit('cancel'); + expect(createContextCommits).not.toHaveBeenCalled(); + expect(removeContextCommits).not.toHaveBeenCalled(); + }); + it('"resetModalState" to reset all the modal state', () => { + findModal().vm.$emit('cancel'); + expect(resetModalState).toHaveBeenCalledWith(expect.anything(), undefined, undefined); + }); + }); + + describe('when model is closed by clicking the "X" button or by pressing "ESC" key', () => { + it('does not call "createContextCommits" or "removeContextCommits"', () => { + findModal().vm.$emit('close'); + expect(createContextCommits).not.toHaveBeenCalled(); + expect(removeContextCommits).not.toHaveBeenCalled(); + }); + it('"resetModalState" to reset all the modal state', () => { + findModal().vm.$emit('close'); + expect(resetModalState).toHaveBeenCalledWith(expect.anything(), undefined, undefined); + }); + }); +}); diff --git a/spec/frontend/add_context_commits_modal/components/review_tab_container_spec.js b/spec/frontend/add_context_commits_modal/components/review_tab_container_spec.js new file mode 100644 index 00000000000..4e65713a680 --- /dev/null +++ b/spec/frontend/add_context_commits_modal/components/review_tab_container_spec.js @@ -0,0 +1,51 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlLoadingIcon } from '@gitlab/ui'; +import ReviewTabContainer from '~/add_context_commits_modal/components/review_tab_container.vue'; +import CommitItem from '~/diffs/components/commit_item.vue'; +import getDiffWithCommit from '../../diffs/mock_data/diff_with_commit'; + +describe('ReviewTabContainer', () => { + let wrapper; + const { commit } = getDiffWithCommit(); + + const createWrapper = (props = {}) => { + wrapper = shallowMount(ReviewTabContainer, { + propsData: { + tab: 'commits', + isLoading: false, + loadingError: false, + loadingFailedText: 'Failed to load commits', + commits: [], + selectedRow: [], + ...props, + }, + }); + }; + + beforeEach(() => { + createWrapper(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('shows loading icon when commits are being loaded', () => { + createWrapper({ isLoading: true }); + expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + }); + + it('shows loading error text when API call fails', () => { + createWrapper({ loadingError: true }); + expect(wrapper.text()).toContain('Failed to load commits'); + }); + + it('shows "No commits present here" when commits are not present', () => { + expect(wrapper.text()).toContain('No commits present here'); + }); + + it('renders all passed commits as list', () => { + createWrapper({ commits: [commit] }); + expect(wrapper.findAll(CommitItem).length).toBe(1); + }); +}); diff --git a/spec/frontend/add_context_commits_modal/store/actions_spec.js b/spec/frontend/add_context_commits_modal/store/actions_spec.js new file mode 100644 index 00000000000..24948dd6073 --- /dev/null +++ b/spec/frontend/add_context_commits_modal/store/actions_spec.js @@ -0,0 +1,239 @@ +import MockAdapter from 'axios-mock-adapter'; +import { TEST_HOST } from 'helpers/test_constants'; +import axios from '~/lib/utils/axios_utils'; +import { + setBaseConfig, + setTabIndex, + setCommits, + createContextCommits, + fetchContextCommits, + setContextCommits, + removeContextCommits, + setSelectedCommits, + setSearchText, + setToRemoveCommits, + resetModalState, +} from '~/add_context_commits_modal/store/actions'; +import * as types from '~/add_context_commits_modal/store/mutation_types'; +import testAction from '../../helpers/vuex_action_helper'; + +describe('AddContextCommitsModalStoreActions', () => { + const contextCommitEndpoint = + '/api/v4/projects/gitlab-org%2fgitlab/merge_requests/1/context_commits'; + const mergeRequestIid = 1; + const projectId = 1; + const projectPath = 'gitlab-org/gitlab'; + const contextCommitsPath = `${TEST_HOST}/gitlab-org/gitlab/-/merge_requests/1/context_commits.json`; + const dummyCommit = { + id: 1, + title: 'dummy commit', + short_id: 'abcdef', + committed_date: '2020-06-12', + }; + gon.api_version = 'v4'; + let mock; + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + }); + + describe('setBaseConfig', () => { + it('commits SET_BASE_CONFIG', done => { + const options = { contextCommitsPath, mergeRequestIid, projectId }; + testAction( + setBaseConfig, + options, + { + contextCommitsPath: '', + mergeRequestIid, + projectId, + }, + [ + { + type: types.SET_BASE_CONFIG, + payload: options, + }, + ], + [], + done, + ); + }); + }); + + describe('setTabIndex', () => { + it('commits SET_TABINDEX', done => { + testAction( + setTabIndex, + { tabIndex: 1 }, + { tabIndex: 0 }, + [{ type: types.SET_TABINDEX, payload: { tabIndex: 1 } }], + [], + done, + ); + }); + }); + + describe('setCommits', () => { + it('commits SET_COMMITS', done => { + testAction( + setCommits, + { commits: [], silentAddition: false }, + { isLoadingCommits: false, commits: [] }, + [{ type: types.SET_COMMITS, payload: [] }], + [], + done, + ); + }); + + it('commits SET_COMMITS_SILENT', done => { + testAction( + setCommits, + { commits: [], silentAddition: true }, + { isLoadingCommits: true, commits: [] }, + [{ type: types.SET_COMMITS_SILENT, payload: [] }], + [], + done, + ); + }); + }); + + describe('createContextCommits', () => { + it('calls API to create context commits', done => { + mock.onPost(contextCommitEndpoint).reply(200, {}); + + testAction(createContextCommits, { commits: [] }, {}, [], [], done); + + createContextCommits( + { state: { projectId, mergeRequestIid }, commit: () => null }, + { commits: [] }, + ) + .then(() => { + done(); + }) + .catch(done.fail); + }); + }); + + describe('fetchContextCommits', () => { + beforeEach(() => { + mock + .onGet( + `/api/${gon.api_version}/projects/gitlab-org%2Fgitlab/merge_requests/1/context_commits`, + ) + .reply(200, [dummyCommit]); + }); + it('commits FETCH_CONTEXT_COMMITS', done => { + const contextCommit = { ...dummyCommit, isSelected: true }; + testAction( + fetchContextCommits, + null, + { + mergeRequestIid, + projectId: projectPath, + isLoadingContextCommits: false, + contextCommitsLoadingError: false, + commits: [], + }, + [{ type: types.FETCH_CONTEXT_COMMITS }], + [ + { type: 'setContextCommits', payload: [contextCommit] }, + { type: 'setCommits', payload: { commits: [contextCommit], silentAddition: true } }, + { type: 'setSelectedCommits', payload: [contextCommit] }, + ], + done, + ); + }); + }); + + describe('setContextCommits', () => { + it('commits SET_CONTEXT_COMMITS', done => { + testAction( + setContextCommits, + { data: [] }, + { contextCommits: [], isLoadingContextCommits: false }, + [{ type: types.SET_CONTEXT_COMMITS, payload: { data: [] } }], + [], + done, + ); + }); + }); + + describe('removeContextCommits', () => { + beforeEach(() => { + mock + .onDelete('/api/v4/projects/gitlab-org%2Fgitlab/merge_requests/1/context_commits') + .reply(204); + }); + it('calls API to remove context commits', done => { + testAction( + removeContextCommits, + { forceReload: false }, + { mergeRequestIid, projectId, toRemoveCommits: [] }, + [], + [], + done, + ); + }); + }); + + describe('setSelectedCommits', () => { + it('commits SET_SELECTED_COMMITS', done => { + testAction( + setSelectedCommits, + [dummyCommit], + { selectedCommits: [] }, + [{ type: types.SET_SELECTED_COMMITS, payload: [dummyCommit] }], + [], + done, + ); + }); + }); + + describe('setSearchText', () => { + it('commits SET_SEARCH_TEXT', done => { + const searchText = 'Dummy Text'; + testAction( + setSearchText, + searchText, + { searchText: '' }, + [{ type: types.SET_SEARCH_TEXT, payload: searchText }], + [], + done, + ); + }); + }); + + describe('setToRemoveCommits', () => { + it('commits SET_TO_REMOVE_COMMITS', done => { + const commitId = 'abcde'; + + testAction( + setToRemoveCommits, + [commitId], + { toRemoveCommits: [] }, + [{ type: types.SET_TO_REMOVE_COMMITS, payload: [commitId] }], + [], + done, + ); + }); + }); + + describe('resetModalState', () => { + it('commits RESET_MODAL_STATE', done => { + const commitId = 'abcde'; + + testAction( + resetModalState, + null, + { toRemoveCommits: [commitId] }, + [{ type: types.RESET_MODAL_STATE }], + [], + done, + ); + }); + }); +}); diff --git a/spec/frontend/add_context_commits_modal/store/mutations_spec.js b/spec/frontend/add_context_commits_modal/store/mutations_spec.js new file mode 100644 index 00000000000..22f82570ab1 --- /dev/null +++ b/spec/frontend/add_context_commits_modal/store/mutations_spec.js @@ -0,0 +1,156 @@ +import { TEST_HOST } from 'helpers/test_constants'; +import mutations from '~/add_context_commits_modal/store/mutations'; +import * as types from '~/add_context_commits_modal/store/mutation_types'; +import getDiffWithCommit from '../../diffs/mock_data/diff_with_commit'; + +describe('AddContextCommitsModalStoreMutations', () => { + const { commit } = getDiffWithCommit(); + describe('SET_BASE_CONFIG', () => { + it('should set contextCommitsPath, mergeRequestIid and projectId', () => { + const state = {}; + const contextCommitsPath = `${TEST_HOST}/gitlab-org/gitlab/-/merge_requests/1/context_commits.json`; + const mergeRequestIid = 1; + const projectId = 1; + + mutations[types.SET_BASE_CONFIG](state, { contextCommitsPath, mergeRequestIid, projectId }); + + expect(state.contextCommitsPath).toEqual(contextCommitsPath); + expect(state.mergeRequestIid).toEqual(mergeRequestIid); + expect(state.projectId).toEqual(projectId); + }); + }); + + describe('SET_TABINDEX', () => { + it('sets tabIndex to specific index', () => { + const state = { tabIndex: 0 }; + + mutations[types.SET_TABINDEX](state, 1); + + expect(state.tabIndex).toBe(1); + }); + }); + + describe('FETCH_COMMITS', () => { + it('sets isLoadingCommits to true', () => { + const state = { isLoadingCommits: false }; + + mutations[types.FETCH_COMMITS](state); + + expect(state.isLoadingCommits).toBe(true); + }); + }); + + describe('SET_COMMITS', () => { + it('sets commits to passed data and stop loading', () => { + const state = { commits: [], isLoadingCommits: true }; + + mutations[types.SET_COMMITS](state, [commit]); + + expect(state.commits).toStrictEqual([commit]); + expect(state.isLoadingCommits).toBe(false); + }); + }); + + describe('SET_COMMITS_SILENT', () => { + it('sets commits to passed data and loading continues', () => { + const state = { commits: [], isLoadingCommits: true }; + + mutations[types.SET_COMMITS_SILENT](state, [commit]); + + expect(state.commits).toStrictEqual([commit]); + expect(state.isLoadingCommits).toBe(true); + }); + }); + + describe('FETCH_COMMITS_ERROR', () => { + it('sets commitsLoadingError to true', () => { + const state = { commitsLoadingError: false }; + + mutations[types.FETCH_COMMITS_ERROR](state); + + expect(state.commitsLoadingError).toBe(true); + }); + }); + + describe('FETCH_CONTEXT_COMMITS', () => { + it('sets isLoadingContextCommits to true', () => { + const state = { isLoadingContextCommits: false }; + + mutations[types.FETCH_CONTEXT_COMMITS](state); + + expect(state.isLoadingContextCommits).toBe(true); + }); + }); + + describe('SET_CONTEXT_COMMITS', () => { + it('sets contextCommit to passed data and stop loading', () => { + const state = { contextCommits: [], isLoadingContextCommits: true }; + + mutations[types.SET_CONTEXT_COMMITS](state, [commit]); + + expect(state.contextCommits).toStrictEqual([commit]); + expect(state.isLoadingContextCommits).toBe(false); + }); + }); + + describe('FETCH_CONTEXT_COMMITS_ERROR', () => { + it('sets contextCommitsLoadingError to true', () => { + const state = { contextCommitsLoadingError: false }; + + mutations[types.FETCH_CONTEXT_COMMITS_ERROR](state); + + expect(state.contextCommitsLoadingError).toBe(true); + }); + }); + + describe('SET_SELECTED_COMMITS', () => { + it('sets selectedCommits to specified value', () => { + const state = { selectedCommits: [] }; + + mutations[types.SET_SELECTED_COMMITS](state, [commit]); + + expect(state.selectedCommits).toStrictEqual([commit]); + }); + }); + + describe('SET_SEARCH_TEXT', () => { + it('sets searchText to specified value', () => { + const searchText = 'Test'; + const state = { searchText: '' }; + + mutations[types.SET_SEARCH_TEXT](state, searchText); + + expect(state.searchText).toBe(searchText); + }); + }); + + describe('SET_TO_REMOVE_COMMITS', () => { + it('sets searchText to specified value', () => { + const state = { toRemoveCommits: [] }; + + mutations[types.SET_TO_REMOVE_COMMITS](state, [commit.short_id]); + + expect(state.toRemoveCommits).toStrictEqual([commit.short_id]); + }); + }); + + describe('RESET_MODAL_STATE', () => { + it('sets searchText to specified value', () => { + const state = { + commits: [commit], + contextCommits: [commit], + selectedCommits: [commit], + toRemoveCommits: [commit.short_id], + searchText: 'Test', + }; + + mutations[types.RESET_MODAL_STATE](state); + + expect(state.commits).toStrictEqual([]); + expect(state.contextCommits).toStrictEqual([]); + expect(state.selectedCommits).toStrictEqual([]); + expect(state.toRemoveCommits).toStrictEqual([]); + expect(state.searchText).toBe(''); + }); + }); +}); |