Commit 0dd7563b authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'ide-merge-requests-components' into 'master'

Show merge requests in IDE

Closes #45184

See merge request gitlab-org/gitlab-ce!19318
parents 6c5c3833 4223beff
......@@ -11,17 +11,20 @@ export default {
},
computed: {
...mapGetters(['currentMergeRequest']),
...mapState(['viewer']),
...mapState(['viewer', 'currentMergeRequestId']),
showLatestChangesText() {
return !this.currentMergeRequest || this.viewer === viewerTypes.diff;
return !this.currentMergeRequestId || this.viewer === viewerTypes.diff;
},
showMergeRequestText() {
return this.currentMergeRequest && this.viewer === viewerTypes.mr;
return this.currentMergeRequestId && this.viewer === viewerTypes.mr;
},
mergeRequestId() {
return `!${this.currentMergeRequest.iid}`;
},
},
mounted() {
this.$nextTick(() => {
this.updateViewer(this.currentMergeRequest ? viewerTypes.mr : viewerTypes.diff);
this.updateViewer(this.currentMergeRequestId ? viewerTypes.mr : viewerTypes.diff);
});
},
methods: {
......@@ -54,7 +57,11 @@ export default {
</template>
<template v-else-if="showMergeRequestText">
{{ __('Merge request') }}
(<a :href="currentMergeRequest.web_url">!{{ currentMergeRequest.iid }}</a>)
(<a
v-if="currentMergeRequest"
:href="currentMergeRequest.web_url"
v-text="mergeRequestId"
></a>)
</template>
</div>
</template>
......
<script>
import $ from 'jquery';
import { mapState, mapGetters } from 'vuex';
import ProjectAvatarImage from '~/vue_shared/components/project_avatar/image.vue';
import Icon from '~/vue_shared/components/icon.vue';
......@@ -13,6 +14,7 @@ import CommitSection from './repo_commit_section.vue';
import CommitForm from './commit_sidebar/form.vue';
import IdeReview from './ide_review.vue';
import SuccessMessage from './commit_sidebar/success_message.vue';
import MergeRequestDropdown from './merge_requests/dropdown.vue';
import { activityBarViews } from '../constants';
export default {
......@@ -32,10 +34,12 @@ export default {
CommitForm,
IdeReview,
SuccessMessage,
MergeRequestDropdown,
},
data() {
return {
showTooltip: false,
showMergeRequestsDropdown: false,
};
},
computed: {
......@@ -46,6 +50,7 @@ export default {
'changedFiles',
'stagedFiles',
'lastCommitMsg',
'currentMergeRequestId',
]),
...mapGetters(['currentProject', 'someUncommitedChanges']),
showSuccessMessage() {
......@@ -61,9 +66,39 @@ export default {
watch: {
currentBranchId() {
this.$nextTick(() => {
if (!this.$refs.branchId) return;
this.showTooltip = this.$refs.branchId.scrollWidth > this.$refs.branchId.offsetWidth;
});
},
loading() {
this.$nextTick(() => {
this.addDropdownListeners();
});
},
},
mounted() {
this.addDropdownListeners();
},
beforeDestroy() {
$(this.$refs.mergeRequestDropdown)
.off('show.bs.dropdown')
.off('hide.bs.dropdown');
},
methods: {
addDropdownListeners() {
if (!this.$refs.mergeRequestDropdown) return;
$(this.$refs.mergeRequestDropdown)
.on('show.bs.dropdown', () => {
this.toggleMergeRequestDropdown();
}).on('hide.bs.dropdown', () => {
this.toggleMergeRequestDropdown();
});
},
toggleMergeRequestDropdown() {
this.showMergeRequestsDropdown = !this.showMergeRequestsDropdown;
},
},
};
</script>
......@@ -88,9 +123,13 @@ export default {
</div>
</template>
<template v-else>
<div class="context-header ide-context-header">
<a
:href="currentProject.web_url"
<div
class="context-header ide-context-header dropdown"
ref="mergeRequestDropdown"
>
<button
type="button"
data-toggle="dropdown"
>
<div
v-if="currentProject.avatar_url"
......@@ -114,19 +153,41 @@ export default {
<div class="sidebar-context-title">
{{ currentProject.name }}
</div>
<div
class="sidebar-context-title ide-sidebar-branch-title"
ref="branchId"
v-tooltip
:title="branchTooltipTitle"
>
<icon
name="branch"
css-classes="append-right-5"
/>{{ currentBranchId }}
<div class="d-flex">
<div
v-if="currentBranchId"
class="sidebar-context-title ide-sidebar-branch-title"
ref="branchId"
v-tooltip
:title="branchTooltipTitle"
>
<icon
name="branch"
css-classes="append-right-5"
/>{{ currentBranchId }}
</div>
<div
v-if="currentMergeRequestId"
class="sidebar-context-title ide-sidebar-branch-title"
:class="{
'prepend-left-8': currentBranchId
}"
>
<icon
name="git-merge"
css-classes="append-right-5"
/>!{{ currentMergeRequestId }}
</div>
</div>
</div>
</a>
<icon
class="ml-auto"
name="chevron-down"
/>
</button>
<merge-request-dropdown
:show="showMergeRequestsDropdown"
/>
</div>
<div class="multi-file-commit-panel-inner-scroll">
<component
......
......@@ -35,9 +35,7 @@ export default {
},
watch: {
lastCommit() {
if (!this.isPollingInitialized) {
this.initPipelinePolling();
}
this.initPipelinePolling();
},
},
mounted() {
......@@ -47,9 +45,8 @@ export default {
if (this.intervalId) {
clearInterval(this.intervalId);
}
if (this.isPollingInitialized) {
this.stopPipelinePolling();
}
this.stopPipelinePolling();
},
methods: {
...mapActions('pipelines', ['fetchLatestPipeline', 'stopPipelinePolling']),
......@@ -59,8 +56,9 @@ export default {
}, 1000);
},
initPipelinePolling() {
this.fetchLatestPipeline();
this.isPollingInitialized = true;
if (this.lastCommit) {
this.fetchLatestPipeline();
}
},
commitAgeUpdate() {
if (this.lastCommit) {
......
<script>
import { mapGetters } from 'vuex';
import Tabs from '../../../vue_shared/components/tabs/tabs';
import Tab from '../../../vue_shared/components/tabs/tab.vue';
import List from './list.vue';
export default {
components: {
Tabs,
Tab,
List,
},
props: {
show: {
type: Boolean,
required: true,
},
},
computed: {
...mapGetters('mergeRequests', ['assignedData', 'createdData']),
createdMergeRequestLength() {
return this.createdData.mergeRequests.length;
},
assignedMergeRequestLength() {
return this.assignedData.mergeRequests.length;
},
},
};
</script>
<template>
<div class="dropdown-menu ide-merge-requests-dropdown p-0">
<tabs
v-if="show"
stop-propagation
>
<tab active>
<template slot="title">
{{ __('Created by me') }}
<span class="badge badge-pill">
{{ createdMergeRequestLength }}
</span>
</template>
<list
type="created"
:empty-text="__('You have not created any merge requests')"
/>
</tab>
<tab>
<template slot="title">
{{ __('Assigned to me') }}
<span class="badge badge-pill">
{{ assignedMergeRequestLength }}
</span>
</template>
<list
type="assigned"
:empty-text="__('You do not have any assigned merge requests')"
/>
</tab>
</tabs>
</div>
</template>
<script>
import Icon from '../../../vue_shared/components/icon.vue';
export default {
components: {
Icon,
},
props: {
item: {
type: Object,
required: true,
},
currentId: {
type: String,
required: true,
},
currentProjectId: {
type: String,
required: true,
},
},
computed: {
isActive() {
return (
this.item.iid === parseInt(this.currentId, 10) &&
this.currentProjectId === this.item.projectPathWithNamespace
);
},
pathWithID() {
return `${this.item.projectPathWithNamespace}!${this.item.iid}`;
},
},
methods: {
clickItem() {
this.$emit('click', this.item);
},
},
};
</script>
<template>
<button
type="button"
class="btn-link d-flex align-items-center"
@click="clickItem"
>
<span class="d-flex append-right-default ide-merge-request-current-icon">
<icon
v-if="isActive"
name="mobile-issue-close"
:size="18"
/>
</span>
<span>
<strong>
{{ item.title }}
</strong>
<span class="ide-merge-request-project-path d-block mt-1">
{{ pathWithID }}
</span>
</span>
</button>
</template>
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import _ from 'underscore';
import LoadingIcon from '../../../vue_shared/components/loading_icon.vue';
import Item from './item.vue';
export default {
components: {
LoadingIcon,
Item,
},
props: {
type: {
type: String,
required: true,
},
emptyText: {
type: String,
required: true,
},
},
data() {
return {
search: '',
};
},
computed: {
...mapGetters('mergeRequests', ['getData']),
...mapState(['currentMergeRequestId', 'currentProjectId']),
data() {
return this.getData(this.type);
},
isLoading() {
return this.data.isLoading;
},
mergeRequests() {
return this.data.mergeRequests;
},
hasMergeRequests() {
return this.mergeRequests.length !== 0;
},
hasNoSearchResults() {
return this.search !== '' && !this.hasMergeRequests;
},
},
watch: {
isLoading: {
handler: 'focusSearch',
},
},
mounted() {
this.loadMergeRequests();
},
methods: {
...mapActions('mergeRequests', ['fetchMergeRequests', 'openMergeRequest']),
loadMergeRequests() {
this.fetchMergeRequests({ type: this.type, search: this.search });
},
viewMergeRequest(item) {
this.openMergeRequest({
projectPath: item.projectPathWithNamespace,
id: item.iid,
});
},
searchMergeRequests: _.debounce(function debounceSearch() {
this.loadMergeRequests();
}, 250),
focusSearch() {
if (!this.isLoading) {
this.$nextTick(() => {
this.$refs.searchInput.focus();
});
}
},
},
};
</script>
<template>
<div>
<div class="dropdown-input mt-3 pb-3 mb-0 border-bottom">
<input
type="search"
class="dropdown-input-field"
:placeholder="__('Search merge requests')"
v-model="search"
@input="searchMergeRequests"
ref="searchInput"
/>
<i
aria-hidden="true"
class="fa fa-search dropdown-input-search"
></i>
</div>
<div class="dropdown-content ide-merge-requests-dropdown-content d-flex">
<loading-icon
class="mt-3 mb-3 align-self-center ml-auto mr-auto"
v-if="isLoading"
size="2"
/>
<ul
v-else
class="mb-3 w-100"
>
<template v-if="hasMergeRequests">
<li
v-for="item in mergeRequests"
:key="item.id"
>
<item
:item="item"
:current-id="currentMergeRequestId"
:current-project-id="currentProjectId"
@click="viewMergeRequest"
/>
</li>
</template>
<li
v-else
class="ide-merge-requests-empty d-flex align-items-center justify-content-center"
>
<template v-if="hasNoSearchResults">
{{ __('No merge requests found') }}
</template>
<template v-else>
{{ emptyText }}
</template>
</li>
</ul>
</div>
</div>
</template>
......@@ -17,9 +17,7 @@ export const getMergeRequestData = (
mergeRequestId,
mergeRequest: data,
});
if (!state.currentMergeRequestId) {
commit(types.SET_CURRENT_MERGE_REQUEST, mergeRequestId);
}
commit(types.SET_CURRENT_MERGE_REQUEST, mergeRequestId);
resolve(data);
})
.catch(() => {
......
......@@ -13,8 +13,7 @@ export const getProjectData = ({ commit, state }, { namespace, projectId, force
.then(data => {
commit(types.TOGGLE_LOADING, { entry: state });
commit(types.SET_PROJECT, { projectPath: `${namespace}/${projectId}`, project: data });
if (!state.currentProjectId)
commit(types.SET_CURRENT_PROJECT, `${namespace}/${projectId}`);
commit(types.SET_CURRENT_PROJECT, `${namespace}/${projectId}`);
resolve(data);
})
.catch(() => {
......
import { __ } from '../../../../locale';
import Api from '../../../../api';
import flash from '../../../../flash';
import router from '../../../ide_router';
import { scopes } from './constants';
import * as types from './mutation_types';
import * as rootTypes from '../../mutation_types';
export const requestMergeRequests = ({ commit }) => commit(types.REQUEST_MERGE_REQUESTS);
export const receiveMergeRequestsError = ({ commit }) => {
export const requestMergeRequests = ({ commit }, type) =>
commit(types.REQUEST_MERGE_REQUESTS, type);
export const receiveMergeRequestsError = ({ commit }, type) => {
flash(__('Error loading merge requests.'));
commit(types.RECEIVE_MERGE_REQUESTS_ERROR);
commit(types.RECEIVE_MERGE_REQUESTS_ERROR, type);
};
export const receiveMergeRequestsSuccess = ({ commit }, data) =>
commit(types.RECEIVE_MERGE_REQUESTS_SUCCESS, data);
export const receiveMergeRequestsSuccess = ({ commit }, { type, data }) =>
commit(types.RECEIVE_MERGE_REQUESTS_SUCCESS, { type, data });
export const fetchMergeRequests = ({ dispatch, state: { scope, state } }, search = '') => {
dispatch('requestMergeRequests');
dispatch('resetMergeRequests');
export const fetchMergeRequests = ({ dispatch, state: { state } }, { type, search = '' }) => {
const scope = scopes[type];
dispatch('requestMergeRequests', type);
dispatch('resetMergeRequests', type);
Api.mergeRequests({ scope, state, search })
.then(({ data }) => dispatch('receiveMergeRequestsSuccess', data))
.catch(() => dispatch('receiveMergeRequestsError'));
.then(({ data }) => dispatch('receiveMergeRequestsSuccess', { type, data }))
.catch(() => dispatch('receiveMergeRequestsError', type));
};
export const resetMergeRequests = ({ commit }) => commit(types.RESET_MERGE_REQUESTS);
export const resetMergeRequests = ({ commit }, type) => commit(types.RESET_MERGE_REQUESTS, type);
export const openMergeRequest = ({ commit, dispatch }, { projectPath, id }) => {
commit(rootTypes.CLEAR_PROJECTS, null, { root: true });
commit(rootTypes.SET_CURRENT_MERGE_REQUEST, `${id}`, { root: true });
commit(rootTypes.RESET_OPEN_FILES, null, { root: true });
dispatch('pipelines/stopPipelinePolling', null, { root: true });
dispatch('pipelines/clearEtagPoll', null, { root: true });
dispatch('pipelines/resetLatestPipeline', null, { root: true });
dispatch('setCurrentBranchId', '', { root: true });
router.push(`/project/${projectPath}/merge_requests/${id}`);
};
export default () => {};
export const scopes = {
assignedToMe: 'assigned-to-me',
createdByMe: 'created-by-me',
assigned: 'assigned-to-me',
created: 'created-by-me',
};
export const states = {
......
export const getData = state => type => state[type];
export const assignedData = state => state.assigned;
export const createdData = state => state.created;
import state from './state';
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
export default {
......@@ -7,4 +8,5 @@ export default {
state: state(),
actions,
mutations,
getters,
};
......@@ -2,15 +2,15 @@
import * as types from './mutation_types';
export default {
[types.REQUEST_MERGE_REQUESTS](state) {
state.isLoading = true;
[types.REQUEST_MERGE_REQUESTS](state, type) {
state[type].isLoading = true;
},
[types.RECEIVE_MERGE_REQUESTS_ERROR](state) {
state.isLoading = false;
[types.RECEIVE_MERGE_REQUESTS_ERROR](state, type) {
state[type].isLoading = false;
},
[types.RECEIVE_MERGE_REQUESTS_SUCCESS](state, data) {
state.isLoading = false;
state.mergeRequests = data.map(mergeRequest => ({
[types.RECEIVE_MERGE_REQUESTS_SUCCESS](state, { type, data }) {
state[type].isLoading = false;
state[type].mergeRequests = data.map(mergeRequest => ({
id: mergeRequest.id,
iid: mergeRequest.iid,
title: mergeRequest.title,
......@@ -20,7 +20,7 @@ export default {
.replace(`/merge_requests/${mergeRequest.iid}`, ''),
}));
},
[types.RESET_MERGE_REQUESTS](state) {
state.mergeRequests = [];
[types.RESET_MERGE_REQUESTS](state, type) {
state[type].mergeRequests = [];
},
};
import { scopes, states } from './constants';
import { states } from './constants';
export default () => ({
isLoading: false,
mergeRequests: [],
scope: scopes.assignedToMe,
created: {
isLoading: false,
mergeRequests: [],
},
assigned: {
isLoading: false,
mergeRequests: [],
},
state: states.opened,
});
......@@ -102,4 +102,7 @@ export const fetchJobTrace = ({ dispatch, state }) => {
.catch(() => dispatch('receiveJobTraceError'));
};
export const resetLatestPipeline = ({ commit }) =>
commit(types.RECEIVE_LASTEST_PIPELINE_SUCCESS, null);
export default () => {};
......@@ -68,3 +68,6 @@ export const TOGGLE_FILE_FINDER = 'TOGGLE_FILE_FINDER';
export const BURST_UNUSED_SEAL = 'BURST_UNUSED_SEAL';
export const SET_RIGHT_PANE = 'SET_RIGHT_PANE';
export const CLEAR_PROJECTS = 'CLEAR_PROJECTS';
export const RESET_OPEN_FILES = 'RESET_OPEN_FILES';
......@@ -157,6 +157,12 @@ export default {
[types.SET_LINKS](state, links) {
Object.assign(state, { links });
},
[types.CLEAR_PROJECTS](state) {
Object.assign(state, { projects: {}, trees: {} });
},
[types.RESET_OPEN_FILES](state) {
Object.assign(state, { openFiles: [] });
},
...projectMutations,
...mergeRequestMutation,
...fileMutations,
......
......@@ -26,6 +26,11 @@ export default {
created() {
this.isTab = true;
},
updated() {
if (this.$parent) {
this.$parent.$forceUpdate();
}
},
};
</script>
......
export default {
props: {
stopPropagation: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
currentIndex: 0,
......@@ -13,7 +20,12 @@ export default {
this.tabs = this.$children.filter(child => child.isTab);
this.currentIndex = this.tabs.findIndex(tab => tab.localActive);
},
setTab(index) {
setTab(e, index) {
if (this.stopPropagation) {
e.stopPropagation();
e.preventDefault();
}
this.tabs[this.currentIndex].localActive = false;
this.tabs[index].localActive = true;
......@@ -36,7 +48,7 @@ export default {
href: '#',
},
on: {
click: () => this.setTab(i),
click: e => this.setTab(e, i),
},
},
tab.$slots.title || tab.title,
......
......@@ -26,19 +26,25 @@
margin-right: 2px;
width: $contextual-sidebar-width;
a {
> a,
> button {
transition: padding $sidebar-transition-duration;
font-weight: $gl-font-weight-bold;
display: flex;
width: 100%;
align-items: center;
padding: 10px 16px 10px 10px;
color: $gl-text-color;
}
background-color: transparent;
border: 0;
text-align: left;
&:hover,
a:hover {
background-color: $link-hover-background;
color: $gl-text-color;
&:hover,
&:focus {
background-color: $link-hover-background;
color: $gl-text-color;
outline: 0;
}
}
.avatar-container {
......
......@@ -458,14 +458,10 @@
width: auto;
margin-right: 0;
a {
> a,
> button {
height: 60px;
}
a:hover,
a:focus {
text-decoration: none;
}
}
.projects-sidebar {
......@@ -1135,6 +1131,11 @@
.avatar {
flex: 0 0 40px;
}
.ide-merge-requests-dropdown.dropdown-menu {
width: 385px;
max-height: initial;
}
}
.ide-sidebar-project-title {
......@@ -1143,6 +1144,10 @@
.sidebar-context-title {
white-space: nowrap;
}
.ide-sidebar-branch-title {
min-width: 50px;
}
}
.ide-external-link {
......@@ -1274,3 +1279,52 @@
.ide-job-header {
min-height: 60px;
}
.ide-merge-requests-dropdown {
.nav-links li {
width: 50%;
padding-left: 0;
padding-right: 0;
a {
text-align: center;
&:not(.active) {
background-color: $gray-light;
}
}
}
.dropdown-input {
padding-left: $gl-padding;
padding-right: $gl-padding;
.fa {
right: 26px;
}
}
.btn-link {
padding-top: $gl-padding;
padding-bottom: $gl-padding;
}
}
.ide-merge-request-current-icon {
min-width: 18px;
}
.ide-merge-requests-empty {
height: 230px;
}
.ide-merge-requests-dropdown-content {
min-height: 230px;
max-height: 470px;
}
.ide-merge-request-project-path {
font-size: 12px;
line-height: 16px;
color: $gl-text-color-secondary;
}
import Vue from 'vue';
import { createStore } from '~/ide/stores';
import Dropdown from '~/ide/components/merge_requests/dropdown.vue';
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
import { mergeRequests } from '../../mock_data';
describe('IDE merge requests dropdown', () => {
const Component = Vue.extend(Dropdown);
let vm;
beforeEach(() => {
const store = createStore();
vm = createComponentWithStore(Component, store, { show: false }).$mount();
});
afterEach(() => {
vm.$destroy();
});
it('does not render tabs when show is false', () => {
expect(vm.$el.querySelector('.nav-links')).toBe(null);
});
describe('when show is true', () => {
beforeEach(done => {
vm.show = true;
vm.$store.state.mergeRequests.assigned.mergeRequests.push(mergeRequests[0]);
vm.$nextTick(done);
});
it('renders tabs', () => {
expect(vm.$el.querySelector('.nav-links')).not.toBe(null);
});
it('renders count for assigned & created data', () => {
expect(vm.$el.querySelector('.nav-links a').textContent).toContain('Created by me');
expect(vm.$el.querySelector('.nav-links a .badge').textContent).toContain('0');
expect(vm.$el.querySelectorAll('.nav-links a')[1].textContent).toContain('Assigned to me');
expect(
vm.$el.querySelectorAll('.nav-links a')[1].querySelector('.badge').textContent,
).toContain('1');
});
});
});
import Vue from 'vue';
import Item from '~/ide/components/merge_requests/item.vue';
import mountCompontent from '../../../helpers/vue_mount_component_helper';
describe('IDE merge request item', () => {
const Component = Vue.extend(Item);
let vm;
beforeEach(() => {
vm = mountCompontent(Component, {
item: {
iid: 1,
projectPathWithNamespace: 'gitlab-org/gitlab-ce',
title: 'Merge request title',
},
currentId: '1',
currentProjectId: 'gitlab-org/gitlab-ce',
});
});
afterEach(() => {
vm.$destroy();
});
it('renders merge requests data', () => {
expect(vm.$el.textContent).toContain('Merge request title');
expect(vm.$el.textContent).toContain('gitlab-org/gitlab-ce!1');
});
it('renders icon if ID matches currentId', () => {
expect(vm.$el.querySelector('.ic-mobile-issue-close')).not.toBe(null);
});
it('does not render icon if ID does not match currentId', done => {
vm.currentId = '2';
vm.$nextTick(() => {
expect(vm.$el.querySelector('.ic-mobile-issue-close')).toBe(null);
done();
});
});
it('does not render icon if project ID does not match', done => {
vm.currentProjectId = 'test/test';
vm.$nextTick(() => {
expect(vm.$el.querySelector('.ic-mobile-issue-close')).toBe(null);
done();
});
});
it('emits click event on click', () => {
spyOn(vm, '$emit');
vm.$el.click();
expect(vm.$emit).toHaveBeenCalledWith('click', vm.item);
});
});
import Vue from 'vue';
import store from '~/ide/stores';
import List from '~/ide/components/merge_requests/list.vue';
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
import { mergeRequests } from '../../mock_data';
import { resetStore } from '../../helpers';
describe('IDE merge requests list', () => {
const Component = Vue.extend(List);
let vm;
beforeEach(() => {
vm = createComponentWithStore(Component, store, {
type: 'created',
emptyText: 'empty text',
});
spyOn(vm, 'fetchMergeRequests');
vm.$mount();
});
afterEach(() => {
vm.$destroy();
resetStore(vm.$store);
});
it('calls fetch on mounted', () => {
expect(vm.fetchMergeRequests).toHaveBeenCalledWith({
type: 'created',
search: '',
});
});
it('renders loading icon', done => {
vm.$store.state.mergeRequests.created.isLoading = true;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
done();
});
});
it('renders empty text when no merge requests exist', () => {
expect(vm.$el.textContent).toContain('empty text');
});
it('renders no search results text when search is not empty', done => {
vm.search = 'testing';
vm.$nextTick(() => {
expect(vm.$el.textContent).toContain('No merge requests found');
done();
});
});
describe('with merge requests', () => {
beforeEach(done => {
vm.$store.state.mergeRequests.created.mergeRequests.push({
...mergeRequests[0],
projectPathWithNamespace: 'gitlab-org/gitlab-ce',
});
vm.$nextTick(done);
});
it('renders list', () => {
expect(vm.$el.querySelectorAll('li').length).toBe(1);
expect(vm.$el.querySelector('li').textContent).toContain(mergeRequests[0].title);
});
it('calls openMergeRequest when clicking merge request', done => {
spyOn(vm, 'openMergeRequest');
vm.$el.querySelector('li button').click();
vm.$nextTick(() => {
expect(vm.openMergeRequest).toHaveBeenCalledWith({
projectPath: 'gitlab-org/gitlab-ce',
id: 1,
});
done();
});
});
});
describe('focusSearch', () => {
it('focuses search input when loading is false', done => {
spyOn(vm.$refs.searchInput, 'focus');
vm.$store.state.mergeRequests.created.isLoading = false;
vm.focusSearch();
vm.$nextTick(() => {
expect(vm.$refs.searchInput.focus).toHaveBeenCalled();
done();
});
});
});
describe('searchMergeRequests', () => {
beforeEach(() => {
spyOn(vm, 'loadMergeRequests');
jasmine.clock().install();
});
afterEach(() => {
jasmine.clock().uninstall();
});
it('calls loadMergeRequests on input in search field', () => {
const event = new Event('input');
vm.$el.querySelector('input').dispatchEvent(event);
jasmine.clock().tick(300);
expect(vm.loadMergeRequests).toHaveBeenCalled();
});
});
});
......@@ -8,7 +8,9 @@ import actions, {
receiveMergeRequestsSuccess,
fetchMergeRequests,
resetMergeRequests,
openMergeRequest,
} from '~/ide/stores/modules/merge_requests/actions';
import router from '~/ide/ide_router';
import { mergeRequests } from '../../../mock_data';
import testAction from '../../../../helpers/vuex_action_helper';
......@@ -29,9 +31,9 @@ describe('IDE merge requests actions', () => {
it('should should commit request', done => {
testAction(
requestMergeRequests,
null,
'created',
mockedState,
[{ type: types.REQUEST_MERGE_REQUESTS }],
[{ type: types.REQUEST_MERGE_REQUESTS, payload: 'created' }],
[],
done,
);
......@@ -48,16 +50,16 @@ describe('IDE merge requests actions', () => {
it('should should commit error', done => {
testAction(
receiveMergeRequestsError,
null,
'created',
mockedState,
[{ type: types.RECEIVE_MERGE_REQUESTS_ERROR }],
[{ type: types.RECEIVE_MERGE_REQUESTS_ERROR, payload: 'created' }],
[],
done,
);
});
it('creates flash message', () => {
receiveMergeRequestsError({ commit() {} });
receiveMergeRequestsError({ commit() {} }, 'created');
expect(flashSpy).toHaveBeenCalled();
});
......@@ -67,9 +69,14 @@ describe('IDE merge requests actions', () => {
it('should commit received data', done => {
testAction(
receiveMergeRequestsSuccess,
'data',
{ type: 'created', data: 'data' },
mockedState,
[{ type: types.RECEIVE_MERGE_REQUESTS_SUCCESS, payload: 'data' }],
[
{
type: types.RECEIVE_MERGE_REQUESTS_SUCCESS,
payload: { type: 'created', data: 'data' },
},
],
[],
done,
);
......@@ -86,14 +93,14 @@ describe('IDE merge requests actions', () => {
mock.onGet(/\/api\/v4\/merge_requests(.*)$/).replyOnce(200, mergeRequests);
});
it('calls API with params from state', () => {
it('calls API with params', () => {
const apiSpy = spyOn(axios, 'get').and.callThrough();
fetchMergeRequests({ dispatch() {}, state: mockedState });
fetchMergeRequests({ dispatch() {}, state: mockedState }, { type: 'created' });
expect(apiSpy).toHaveBeenCalledWith(jasmine.anything(), {
params: {
scope: 'assigned-to-me',
scope: 'created-by-me',
state: 'opened',
search: '',
},
......@@ -103,11 +110,14 @@ describe('IDE merge requests actions', () => {
it('calls API with search', () => {
const apiSpy = spyOn(axios, 'get').and.callThrough();
fetchMergeRequests({ dispatch() {}, state: mockedState }, 'testing search');
fetchMergeRequests(
{ dispatch() {}, state: mockedState },
{ type: 'created', search: 'testing search' },
);
expect(apiSpy).toHaveBeenCalledWith(jasmine.anything(), {
params: {
scope: 'assigned-to-me',
scope: 'created-by-me',
state: 'opened',
search: 'testing search',
},
......@@ -117,7 +127,7 @@ describe('IDE merge requests actions', () => {
it('dispatches request', done => {
testAction(
fetchMergeRequests,
null,
{ type: 'created' },
mockedState,
[],
[
......@@ -132,13 +142,16 @@ describe('IDE merge requests actions', () => {
it('dispatches success with received data', done => {
testAction(
fetchMergeRequests,
null,
{ type: 'created' },
mockedState,
[],
[
{ type: 'requestMergeRequests' },
{ type: 'resetMergeRequests' },
{ type: 'receiveMergeRequestsSuccess', payload: mergeRequests },
{
type: 'receiveMergeRequestsSuccess',
payload: { type: 'created', data: mergeRequests },
},
],
done,
);
......@@ -153,7 +166,7 @@ describe('IDE merge requests actions', () => {
it('dispatches error', done => {
testAction(
fetchMergeRequests,
null,
{ type: 'created' },
mockedState,
[],
[
......@@ -171,12 +184,47 @@ describe('IDE merge requests actions', () => {
it('commits reset', done => {
testAction(
resetMergeRequests,
null,
'created',
mockedState,
[{ type: types.RESET_MERGE_REQUESTS }],
[{ type: types.RESET_MERGE_REQUESTS, payload: 'created' }],
[],
done,
);
});
});
describe('openMergeRequest', () => {
beforeEach(() => {
spyOn(router, 'push');
});
it('commits reset mutations and actions', done => {
testAction(
openMergeRequest,
{ projectPath: 'gitlab-org/gitlab-ce', id: '1' },
mockedState,
[
{ type: 'CLEAR_PROJECTS' },
{ type: 'SET_CURRENT_MERGE_REQUEST', payload: '1' },
{ type: 'RESET_OPEN_FILES' },
],
[
{ type: 'pipelines/stopPipelinePolling' },
{ type: 'pipelines/clearEtagPoll' },
{ type: 'pipelines/resetLatestPipeline' },
{ type: 'setCurrentBranchId', payload: '' },
],
done,
);
});
it('pushes new route', () => {
openMergeRequest(
{ commit() {}, dispatch() {} },
{ projectPath: 'gitlab-org/gitlab-ce', id: '1' },
);
expect(router.push).toHaveBeenCalledWith('/project/gitlab-org/gitlab-ce/merge_requests/1');
});
});
});
......@@ -12,26 +12,29 @@ describe('IDE merge requests mutations', () => {
describe(types.REQUEST_MERGE_REQUESTS, () => {
it('sets loading to true', () => {
mutations[types.REQUEST_MERGE_REQUESTS](mockedState);
mutations[types.REQUEST_MERGE_REQUESTS](mockedState, 'created');
expect(mockedState.isLoading).toBe(true);
expect(mockedState.created.isLoading).toBe(true);
});
});
describe(types.RECEIVE_MERGE_REQUESTS_ERROR, () => {
it('sets loading to false', () => {
mutations[types.RECEIVE_MERGE_REQUESTS_ERROR](mockedState);
mutations[types.RECEIVE_MERGE_REQUESTS_ERROR](mockedState, 'created');
expect(mockedState.isLoading).toBe(false);
expect(mockedState.created.isLoading).toBe(false);
});
});
describe(types.RECEIVE_MERGE_REQUESTS_SUCCESS, () => {
it('sets merge requests', () => {
gon.gitlab_url = gl.TEST_HOST;
mutations[types.RECEIVE_MERGE_REQUESTS_SUCCESS](mockedState, mergeRequests);
mutations[types.RECEIVE_MERGE_REQUESTS_SUCCESS](mockedState, {
type: 'created',
data: mergeRequests,
});
expect(mockedState.mergeRequests).toEqual([
expect(mockedState.created.mergeRequests).toEqual([
{
id: 1,
iid: 1,
......@@ -47,9 +50,9 @@ describe('IDE merge requests mutations', () => {
it('clears merge request array', () => {
mockedState.mergeRequests = ['test'];
mutations[types.RESET_MERGE_REQUESTS](mockedState);
mutations[types.RESET_MERGE_REQUESTS](mockedState, 'created');
expect(mockedState.mergeRequests).toEqual([]);
expect(mockedState.created.mergeRequests).toEqual([]);
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment