Commit d2ad55fa authored by Kushal Pandya's avatar Kushal Pandya Committed by Jarka Košanová

Frontend for epic - epic relationship

Integrates frontend with API to enable
attaching epics to an epic
parent e70fc65a
...@@ -13,6 +13,7 @@ import { status, stateEvent } from '../../constants'; ...@@ -13,6 +13,7 @@ import { status, stateEvent } from '../../constants';
export default { export default {
name: 'EpicShowApp', name: 'EpicShowApp',
epicsPathIdSeparator: '&',
components: { components: {
epicHeader, epicHeader,
epicSidebar, epicSidebar,
...@@ -44,6 +45,11 @@ export default { ...@@ -44,6 +45,11 @@ export default {
required: true, required: true,
type: Boolean, type: Boolean,
}, },
subepicsSupported: {
type: Boolean,
required: false,
default: true,
},
markdownPreviewPath: { markdownPreviewPath: {
type: String, type: String,
required: true, required: true,
...@@ -150,9 +156,10 @@ export default { ...@@ -150,9 +156,10 @@ export default {
type: Array, type: Array,
required: true, required: true,
}, },
parentEpic: { parent: {
type: Object, type: Object,
required: true, required: false,
default: () => ({}),
}, },
participants: { participants: {
type: Array, type: Array,
...@@ -206,7 +213,9 @@ export default { ...@@ -206,7 +213,9 @@ export default {
return { return {
// Epics specific configuration // Epics specific configuration
issuableRef: '', issuableRef: '',
hasRelatedEpicsFeature: this.subepicsSupported && gon.features && gon.features.epicLinks,
projectPath: this.groupPath, projectPath: this.groupPath,
parentEpic: this.parent ? this.parent : {},
projectNamespace: '', projectNamespace: '',
service: new EpicsService({ service: new EpicsService({
endpoint: this.endpoint, endpoint: this.endpoint,
...@@ -301,7 +310,7 @@ export default { ...@@ -301,7 +310,7 @@ export default {
:initial-participants="participants" :initial-participants="participants"
:initial-subscribed="subscribed" :initial-subscribed="subscribed"
:initial-todo-exists="todoExists" :initial-todo-exists="todoExists"
:parent-epic="parentEpic" :parent="parentEpic"
:namespace="namespace" :namespace="namespace"
:update-path="updateEndpoint" :update-path="updateEndpoint"
:labels-path="labelsPath" :labels-path="labelsPath"
...@@ -312,18 +321,23 @@ export default { ...@@ -312,18 +321,23 @@ export default {
:epics-web-url="epicsWebUrl" :epics-web-url="epicsWebUrl"
/> />
<related-issues-root <related-issues-root
v-if="hasRelatedEpicsFeature"
:endpoint="epicLinksEndpoint" :endpoint="epicLinksEndpoint"
:can-admin="canAdmin" :can-admin="canAdmin"
:can-reorder="canAdmin" :can-reorder="canAdmin"
:allow-auto-complete="false" :allow-auto-complete="false"
title="Epics" :path-id-separator="$options.epicsPathIdSeparator"
:title="__('Epics')"
css-class="js-related-epics-block"
/> />
<related-issues-root <related-issues-root
:endpoint="issueLinksEndpoint" :endpoint="issueLinksEndpoint"
:can-admin="canAdmin" :can-admin="canAdmin"
:can-reorder="canAdmin" :can-reorder="canAdmin"
:allow-auto-complete="false" :allow-auto-complete="false"
title="Issues" :title="__('Issues')"
css-class="js-related-issues-block"
path-id-separator="#"
/> />
</div> </div>
</div> </div>
......
...@@ -15,16 +15,6 @@ export default () => { ...@@ -15,16 +15,6 @@ export default () => {
Cookies.set('collapsed_gutter', true); Cookies.set('collapsed_gutter', true);
} }
// TODO remove once API provides proper data
initialData.epicLinksEndpoint = '/';
metaData.parentEpic = {
id: 7,
title: 'Epic with out of range end date',
url: '/groups/gitlab-org/-/epics/7',
human_readable_timestamp: '<strong>30</strong> days remaining',
human_readable_end_date: 'Dec 28, 2018',
};
const props = Object.assign({}, initialData, metaData, el.dataset); const props = Object.assign({}, initialData, metaData, el.dataset);
return new Vue({ return new Vue({
......
...@@ -118,7 +118,7 @@ export default { ...@@ -118,7 +118,7 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
parentEpic: { parent: {
type: Object, type: Object,
required: true, required: true,
}, },
...@@ -468,7 +468,7 @@ export default { ...@@ -468,7 +468,7 @@ export default {
<div class="issuable-sidebar js-issuable-update"> <div class="issuable-sidebar js-issuable-update">
<div class="block issuable-sidebar-header"> <div class="block issuable-sidebar-header">
<span class="issuable-header-text hide-collapsed float-left">{{ __('Todo') }}</span> <span class="issuable-header-text hide-collapsed float-left">{{ __('Todo') }}</span>
<toggle-sidebar :collapsed="collapsed" css-classes="float-right" @toggle="toggleSidebar"/> <toggle-sidebar :collapsed="collapsed" css-classes="float-right" @toggle="toggleSidebar" />
<sidebar-todo <sidebar-todo
v-if="!collapsed" v-if="!collapsed"
:collapsed="collapsed" :collapsed="collapsed"
...@@ -553,11 +553,12 @@ export default { ...@@ -553,11 +553,12 @@ export default {
@onLabelClick="handleLabelClick" @onLabelClick="handleLabelClick"
@onDropdownClose="handleDropdownClose" @onDropdownClose="handleDropdownClose"
@toggleCollapse="toggleSidebarRevealLabelsDropdown" @toggleCollapse="toggleSidebarRevealLabelsDropdown"
>{{ __('None') }}</sidebar-labels-select> >{{ __('None') }}</sidebar-labels-select
>
<div class="block parent-epic"> <div class="block parent-epic">
<sidebar-item-epic :block-title="__('Parent epic')" :initial-epic="parentEpic"/> <sidebar-item-epic :block-title="__('Parent epic')" :initial-epic="parent" />
</div> </div>
<sidebar-participants :participants="initialParticipants" @toggleCollapse="toggleSidebar"/> <sidebar-participants :participants="initialParticipants" @toggleCollapse="toggleSidebar" />
<sidebar-subscriptions <sidebar-subscriptions
:loading="savingSubscription" :loading="savingSubscription"
:subscribed="store.subscribed" :subscribed="store.subscribed"
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
import $ from 'jquery'; import $ from 'jquery';
import GfmAutoComplete from '~/gfm_auto_complete'; import GfmAutoComplete from '~/gfm_auto_complete';
import { GlLoadingIcon } from '@gitlab/ui'; import { GlLoadingIcon } from '@gitlab/ui';
import eventHub from '../event_hub';
import issueToken from './issue_token.vue'; import issueToken from './issue_token.vue';
export default { export default {
...@@ -60,7 +59,6 @@ export default { ...@@ -60,7 +59,6 @@ export default {
mounted() { mounted() {
const $input = $(this.$refs.input); const $input = $(this.$refs.input);
if (this.allowAutoComplete) { if (this.allowAutoComplete) {
this.gfmAutoComplete = new GfmAutoComplete(this.autoCompleteSources); this.gfmAutoComplete = new GfmAutoComplete(this.autoCompleteSources);
this.gfmAutoComplete.setup($input, { this.gfmAutoComplete.setup($input, {
...@@ -83,7 +81,10 @@ export default { ...@@ -83,7 +81,10 @@ export default {
methods: { methods: {
onInput() { onInput() {
const { value } = this.$refs.input; const { value } = this.$refs.input;
eventHub.$emit('addIssuableFormInput', value, $(this.$refs.input).caret('pos')); this.$emit('addIssuableFormInput', {
newValue: value,
caretPos: $(this.$refs.input).caret('pos'),
});
}, },
onFocus() { onFocus() {
this.isInputFocused = true; this.isInputFocused = true;
...@@ -94,7 +95,7 @@ export default { ...@@ -94,7 +95,7 @@ export default {
// Avoid tokenizing partial input when clicking an autocomplete item // Avoid tokenizing partial input when clicking an autocomplete item
if (!this.isAutoCompleteOpen) { if (!this.isAutoCompleteOpen) {
const { value } = this.$refs.input; const { value } = this.$refs.input;
eventHub.$emit('addIssuableFormBlur', value); this.$emit('addIssuableFormBlur', value);
} }
}, },
onAutoCompleteToggled(isOpen) { onAutoCompleteToggled(isOpen) {
...@@ -103,12 +104,15 @@ export default { ...@@ -103,12 +104,15 @@ export default {
onInputWrapperClick() { onInputWrapperClick() {
this.$refs.input.focus(); this.$refs.input.focus();
}, },
onPendingIssuableRemoveRequest(params) {
this.$emit('pendingIssuableRemoveRequest', params);
},
onFormSubmit() { onFormSubmit() {
const { value } = this.$refs.input; const { value } = this.$refs.input;
eventHub.$emit('addIssuableFormSubmit', value); this.$emit('addIssuableFormSubmit', value);
}, },
onFormCancel() { onFormCancel() {
eventHub.$emit('addIssuableFormCancel'); this.$emit('addIssuableFormCancel');
}, },
}, },
}; };
...@@ -141,6 +145,7 @@ export default { ...@@ -141,6 +145,7 @@ export default {
:is-condensed="true" :is-condensed="true"
:path-id-separator="pathIdSeparator" :path-id-separator="pathIdSeparator"
event-namespace="pendingIssuable" event-namespace="pendingIssuable"
@pendingIssuableRemoveRequest="onPendingIssuableRemoveRequest"
/> />
</li> </li>
<li class="add-issuable-form-input-list-item"> <li class="add-issuable-form-input-list-item">
......
...@@ -89,7 +89,7 @@ export default { ...@@ -89,7 +89,7 @@ export default {
</div> </div>
<div class="item-meta-child d-flex align-items-center"> <div class="item-meta-child d-flex align-items-center">
<issue-milestone <issue-milestone
v-if="milestone" v-if="hasMilestone"
:milestone="milestone" :milestone="milestone"
class="d-flex align-items-center item-milestone" class="d-flex align-items-center item-milestone"
/> />
......
...@@ -4,7 +4,6 @@ import tooltip from '~/vue_shared/directives/tooltip'; ...@@ -4,7 +4,6 @@ import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import sortableConfig from 'ee/sortable/sortable_config'; import sortableConfig from 'ee/sortable/sortable_config';
import { GlLoadingIcon } from '@gitlab/ui'; import { GlLoadingIcon } from '@gitlab/ui';
import eventHub from '../event_hub';
import issueItem from './issue_item.vue'; import issueItem from './issue_item.vue';
import addIssuableForm from './add_issuable_form.vue'; import addIssuableForm from './add_issuable_form.vue';
...@@ -62,8 +61,7 @@ export default { ...@@ -62,8 +61,7 @@ export default {
}, },
pathIdSeparator: { pathIdSeparator: {
type: String, type: String,
required: false, required: true,
default: '#',
}, },
helpPath: { helpPath: {
type: String, type: String,
...@@ -110,9 +108,6 @@ export default { ...@@ -110,9 +108,6 @@ export default {
} }
}, },
methods: { methods: {
toggleAddRelatedIssuesForm() {
eventHub.$emit('toggleAddRelatedIssuesForm');
},
getBeforeAfterId(itemEl) { getBeforeAfterId(itemEl) {
const prevItemEl = itemEl.previousElementSibling; const prevItemEl = itemEl.previousElementSibling;
const nextItemEl = itemEl.nextElementSibling; const nextItemEl = itemEl.nextElementSibling;
...@@ -172,7 +167,7 @@ export default { ...@@ -172,7 +167,7 @@ export default {
class="js-issue-count-badge-add-button issue-count-badge-add-button btn btn-sm btn-default qa-add-issues-button" class="js-issue-count-badge-add-button issue-count-badge-add-button btn btn-sm btn-default qa-add-issues-button"
aria-label="Add an issue" aria-label="Add an issue"
data-placement="top" data-placement="top"
@click="toggleAddRelatedIssuesForm" @click="$emit('toggleAddRelatedIssuesForm', $event);"
> >
<i class="fa fa-plus" aria-hidden="true"></i> <i class="fa fa-plus" aria-hidden="true"></i>
</button> </button>
...@@ -192,6 +187,11 @@ export default { ...@@ -192,6 +187,11 @@ export default {
:pending-references="pendingReferences" :pending-references="pendingReferences"
:auto-complete-sources="autoCompleteSources" :auto-complete-sources="autoCompleteSources"
:path-id-separator="pathIdSeparator" :path-id-separator="pathIdSeparator"
@pendingIssuableRemoveRequest="$emit('pendingIssuableRemoveRequest', $event);"
@addIssuableFormInput="$emit('addIssuableFormInput', $event);"
@addIssuableFormBlur="$emit('addIssuableFormBlur', $event);"
@addIssuableFormSubmit="$emit('addIssuableFormSubmit', $event);"
@addIssuableFormCancel="$emit('addIssuableFormCancel', $event);"
/> />
</div> </div>
<div <div
...@@ -238,6 +238,7 @@ export default { ...@@ -238,6 +238,7 @@ export default {
:can-reorder="canReorder" :can-reorder="canReorder"
:path-id-separator="pathIdSeparator" :path-id-separator="pathIdSeparator"
event-namespace="relatedIssue" event-namespace="relatedIssue"
@relatedIssueRemoveRequest="$emit('relatedIssueRemoveRequest', $event);"
/> />
</li> </li>
</ul> </ul>
......
...@@ -25,7 +25,6 @@ Your caret can stop touching a `rawReference` can happen in a variety of ways: ...@@ -25,7 +25,6 @@ Your caret can stop touching a `rawReference` can happen in a variety of ways:
*/ */
import _ from 'underscore'; import _ from 'underscore';
import Flash from '~/flash'; import Flash from '~/flash';
import eventHub from '../event_hub';
import RelatedIssuesBlock from './related_issues_block.vue'; import RelatedIssuesBlock from './related_issues_block.vue';
import RelatedIssuesStore from '../stores/related_issues_store'; import RelatedIssuesStore from '../stores/related_issues_store';
import RelatedIssuesService from '../services/related_issues_service'; import RelatedIssuesService from '../services/related_issues_service';
...@@ -67,6 +66,16 @@ export default { ...@@ -67,6 +66,16 @@ export default {
required: false, required: false,
default: true, default: true,
}, },
pathIdSeparator: {
type: String,
required: false,
default: '#',
},
cssClass: {
type: String,
required: false,
default: '',
},
}, },
data() { data() {
this.store = new RelatedIssuesStore(); this.store = new RelatedIssuesStore();
...@@ -86,26 +95,9 @@ export default { ...@@ -86,26 +95,9 @@ export default {
}, },
}, },
created() { created() {
eventHub.$on('relatedIssue-removeRequest', this.onRelatedIssueRemoveRequest);
eventHub.$on('toggleAddRelatedIssuesForm', this.onToggleAddRelatedIssuesForm);
eventHub.$on('pendingIssuable-removeRequest', this.onPendingIssueRemoveRequest);
eventHub.$on('addIssuableFormSubmit', this.onPendingFormSubmit);
eventHub.$on('addIssuableFormCancel', this.onPendingFormCancel);
eventHub.$on('addIssuableFormInput', this.onInput);
eventHub.$on('addIssuableFormBlur', this.onBlur);
this.service = new RelatedIssuesService(this.endpoint); this.service = new RelatedIssuesService(this.endpoint);
this.fetchRelatedIssues(); this.fetchRelatedIssues();
}, },
beforeDestroy() {
eventHub.$off('relatedIssue-removeRequest', this.onRelatedIssueRemoveRequest);
eventHub.$off('toggleAddRelatedIssuesForm', this.onToggleAddRelatedIssuesForm);
eventHub.$off('pendingIssuable-removeRequest', this.onPendingIssueRemoveRequest);
eventHub.$off('addIssuableFormSubmit', this.onPendingFormSubmit);
eventHub.$off('addIssuableFormCancel', this.onPendingFormCancel);
eventHub.$off('addIssuableFormInput', this.onInput);
eventHub.$off('addIssuableFormBlur', this.onBlur);
},
methods: { methods: {
onRelatedIssueRemoveRequest(idToRemove) { onRelatedIssueRemoveRequest(idToRemove) {
const issueToRemove = _.find(this.state.relatedIssues, issue => issue.id === idToRemove); const issueToRemove = _.find(this.state.relatedIssues, issue => issue.id === idToRemove);
...@@ -198,7 +190,7 @@ export default { ...@@ -198,7 +190,7 @@ export default {
}); });
} }
}, },
onInput(newValue, caretPos) { onInput({ newValue, caretPos }) {
const rawReferences = newValue.split(/\s/); const rawReferences = newValue.split(/\s/);
let touchedReference; let touchedReference;
...@@ -235,6 +227,7 @@ export default { ...@@ -235,6 +227,7 @@ export default {
<template> <template>
<related-issues-block <related-issues-block
:class="cssClass"
:help-path="helpPath" :help-path="helpPath"
:is-fetching="isFetching" :is-fetching="isFetching"
:is-submitting="isSubmitting" :is-submitting="isSubmitting"
...@@ -246,7 +239,14 @@ export default { ...@@ -246,7 +239,14 @@ export default {
:input-value="inputValue" :input-value="inputValue"
:auto-complete-sources="autoCompleteSources" :auto-complete-sources="autoCompleteSources"
:title="title" :title="title"
path-id-separator="#" :path-id-separator="pathIdSeparator"
@saveReorder="saveIssueOrder" @saveReorder="saveIssueOrder"
@toggleAddRelatedIssuesForm="onToggleAddRelatedIssuesForm"
@addIssuableFormInput="onInput"
@addIssuableFormBlur="onBlur"
@addIssuableFormSubmit="onPendingFormSubmit"
@addIssuableFormCancel="onPendingFormCancel"
@pendingIssuableRemoveRequest="onPendingIssueRemoveRequest"
@relatedIssueRemoveRequest="onRelatedIssueRemoveRequest"
/> />
</template> </template>
import Vue from 'vue';
export default new Vue();
import _ from 'underscore';
import { formatDate } from '~/lib/utils/datetime_utility'; import { formatDate } from '~/lib/utils/datetime_utility';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
import icon from '~/vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago'; import timeagoMixin from '~/vue_shared/mixins/timeago';
import eventHub from '../event_hub';
const mixins = { const mixins = {
data() { data() {
...@@ -104,6 +104,9 @@ const mixins = { ...@@ -104,6 +104,9 @@ const mixins = {
hasTitle() { hasTitle() {
return this.title.length > 0; return this.title.length > 0;
}, },
hasMilestone() {
return !_.isEmpty(this.milestone);
},
iconName() { iconName() {
return this.isOpen ? 'issue-open-m' : 'issue-close'; return this.isOpen ? 'issue-open-m' : 'issue-close';
}, },
...@@ -139,10 +142,10 @@ const mixins = { ...@@ -139,10 +142,10 @@ const mixins = {
onRemoveRequest() { onRemoveRequest() {
let namespacePrefix = ''; let namespacePrefix = '';
if (this.eventNamespace && this.eventNamespace.length > 0) { if (this.eventNamespace && this.eventNamespace.length > 0) {
namespacePrefix = `${this.eventNamespace}-`; namespacePrefix = `${this.eventNamespace}`;
} }
eventHub.$emit(`${namespacePrefix}removeRequest`, this.idKey); this.$emit(`${namespacePrefix}RemoveRequest`, this.idKey);
this.removeDisabled = true; this.removeDisabled = true;
}, },
......
...@@ -22,29 +22,24 @@ export default { ...@@ -22,29 +22,24 @@ export default {
initialEpic: { initialEpic: {
type: Object, type: Object,
required: false, required: false,
default: () => {}, default: () => null,
}, },
}, },
data() { data() {
let store = {};
if (!this.initialEpic) {
store = new Store();
}
return { return {
store, store: !this.initialEpic ? new Store() : {},
}; };
}, },
computed: { computed: {
isLoading() { isLoading() {
return this.initialEpic ? false : this.store.isFetching.epic; return this.initialEpic ? false : this.store.isFetching.epic;
}, },
epicIcon() {
return spriteIcon('epic');
},
epic() { epic() {
return this.initialEpic || this.store.epic; return this.initialEpic || this.store.epic;
}, },
epicIcon() {
return spriteIcon('epic');
},
epicUrl() { epicUrl() {
return this.epic.url; return this.epic.url;
}, },
...@@ -55,7 +50,7 @@ export default { ...@@ -55,7 +50,7 @@ export default {
return this.epicUrl && this.epicTitle; return this.epicUrl && this.epicTitle;
}, },
collapsedTitle() { collapsedTitle() {
return this.hasEpic ? this.epicTitle : 'None'; return this.hasEpic ? this.epicTitle : __('None');
}, },
tooltipTitle() { tooltipTitle() {
if (!this.hasEpic) { if (!this.hasEpic) {
...@@ -95,11 +90,11 @@ export default { ...@@ -95,11 +90,11 @@ export default {
</div> </div>
<div class="title hide-collapsed"> <div class="title hide-collapsed">
{{ blockTitle }} {{ blockTitle }}
<gl-loading-icon v-if="isLoading" :inline="true"/> <gl-loading-icon v-if="isLoading" :inline="true" />
</div> </div>
<div v-if="!isLoading" class="value hide-collapsed"> <div v-if="!isLoading" class="value hide-collapsed">
<a v-if="hasEpic" :href="epicUrl" class="bold">{{ epicTitle }}</a> <a v-if="hasEpic" :href="epicUrl" class="bold">{{ epicTitle }}</a>
<span v-else class="no-value">None</span> <span v-else class="no-value">{{ __('None') }}</span>
</div> </div>
</div> </div>
</template> </template>
...@@ -32,7 +32,7 @@ describe 'Epic Issues', :js do ...@@ -32,7 +32,7 @@ describe 'Epic Issues', :js do
end end
it 'user can see issues from public project but cannot delete the associations' do it 'user can see issues from public project but cannot delete the associations' do
within('.related-issues-block ul.related-items-list') do within('.js-related-issues-block ul.related-items-list') do
expect(page).to have_selector('li', count: 1) expect(page).to have_selector('li', count: 1)
expect(page).to have_content(public_issue.title) expect(page).to have_content(public_issue.title)
expect(page).not_to have_selector('button.js-issue-item-remove-button') expect(page).not_to have_selector('button.js-issue-item-remove-button')
...@@ -40,11 +40,11 @@ describe 'Epic Issues', :js do ...@@ -40,11 +40,11 @@ describe 'Epic Issues', :js do
end end
it 'user cannot add new issues to the epic' do it 'user cannot add new issues to the epic' do
expect(page).not_to have_selector('.related-issues-block h3.card-title button') expect(page).not_to have_selector('.js-related-issues-block h3.card-title button')
end end
it 'user cannot reorder issues in epic' do it 'user cannot reorder issues in epic' do
expect(page).not_to have_selector('.js-related-issues-token-list-item.user-can-drag') expect(page).not_to have_selector('.js-related-issues-block .js-related-issues-token-list-item.user-can-drag')
end end
end end
...@@ -53,13 +53,13 @@ describe 'Epic Issues', :js do ...@@ -53,13 +53,13 @@ describe 'Epic Issues', :js do
let(:issue_invalid) { create(:issue) } let(:issue_invalid) { create(:issue) }
def add_issues(references) def add_issues(references)
find('.related-issues-block h3.card-title button').click find('.js-related-issues-block h3.card-title button').click
find('.js-add-issuable-form-input').set(references) find('.js-related-issues-block .js-add-issuable-form-input').set(references)
# When adding long references, for some reason the input gets stuck # When adding long references, for some reason the input gets stuck
# waiting for more text. Send a keystroke before clicking the button to # waiting for more text. Send a keystroke before clicking the button to
# get out of this mode. # get out of this mode.
find('.js-add-issuable-form-input').send_keys(:tab) find('.js-related-issues-block .js-add-issuable-form-input').send_keys(:tab)
find('.js-add-issuable-form-add-button').click find('.js-related-issues-block .js-add-issuable-form-add-button').click
wait_for_requests wait_for_requests
end end
...@@ -70,7 +70,7 @@ describe 'Epic Issues', :js do ...@@ -70,7 +70,7 @@ describe 'Epic Issues', :js do
end end
it 'user can see all issues of the group and delete the associations' do it 'user can see all issues of the group and delete the associations' do
within('.related-issues-block ul.related-items-list') do within('.js-related-issues-block ul.related-items-list') do
expect(page).to have_selector('li', count: 2) expect(page).to have_selector('li', count: 2)
expect(page).to have_content(public_issue.title) expect(page).to have_content(public_issue.title)
expect(page).to have_content(private_issue.title) expect(page).to have_content(private_issue.title)
...@@ -80,7 +80,7 @@ describe 'Epic Issues', :js do ...@@ -80,7 +80,7 @@ describe 'Epic Issues', :js do
wait_for_requests wait_for_requests
within('.related-issues-block ul.related-items-list') do within('.js-related-issues-block ul.related-items-list') do
expect(page).to have_selector('li', count: 1) expect(page).to have_selector('li', count: 1)
end end
end end
...@@ -100,20 +100,20 @@ describe 'Epic Issues', :js do ...@@ -100,20 +100,20 @@ describe 'Epic Issues', :js do
expect(page).not_to have_selector('.content-wrapper .alert-wrapper .flash-text') expect(page).not_to have_selector('.content-wrapper .alert-wrapper .flash-text')
expect(page).not_to have_content('No Issue found for given params') expect(page).not_to have_content('No Issue found for given params')
within('.related-issues-block ul.related-items-list') do within('.js-related-issues-block ul.related-items-list') do
expect(page).to have_selector('li', count: 3) expect(page).to have_selector('li', count: 3)
expect(page).to have_content(issue_to_add.title) expect(page).to have_content(issue_to_add.title)
end end
end end
it 'user can reorder issues in epic' do it 'user can reorder issues in epic' do
expect(first('.js-related-issues-token-list-item')).to have_content(public_issue.title) expect(first('.js-related-issues-block .js-related-issues-token-list-item')).to have_content(public_issue.title)
expect(page.all('.js-related-issues-token-list-item').last).to have_content(private_issue.title) expect(page.all('.js-related-issues-block .js-related-issues-token-list-item').last).to have_content(private_issue.title)
drag_to(selector: '.related-items-list', to_index: 1) drag_to(selector: '.js-related-issues-block .related-items-list', to_index: 1)
expect(first('.js-related-issues-token-list-item')).to have_content(private_issue.title) expect(first('.js-related-issues-block .js-related-issues-token-list-item')).to have_content(private_issue.title)
expect(page.all('.js-related-issues-token-list-item').last).to have_content(public_issue.title) expect(page.all('.js-related-issues-block .js-related-issues-token-list-item').last).to have_content(public_issue.title)
end end
end end
end end
...@@ -28,6 +28,10 @@ describe 'Epic in issue sidebar', :js do ...@@ -28,6 +28,10 @@ describe 'Epic in issue sidebar', :js do
context 'when epics available' do context 'when epics available' do
before do before do
stub_licensed_features(epics: true) stub_licensed_features(epics: true)
sign_in(user)
visit project_issue_path(project, issue)
wait_for_requests
end end
it_behaves_like 'epic in issue sidebar' it_behaves_like 'epic in issue sidebar'
......
...@@ -40,6 +40,7 @@ export const contentProps = { ...@@ -40,6 +40,7 @@ export const contentProps = {
markdownPreviewPath: '', markdownPreviewPath: '',
markdownDocsPath: '', markdownDocsPath: '',
issueLinksEndpoint: '/', issueLinksEndpoint: '/',
epicLinksEndpoint: '/',
groupPath: '', groupPath: '',
namespace: 'gitlab-org', namespace: 'gitlab-org',
labelsPath: '', labelsPath: '',
...@@ -71,6 +72,15 @@ export const contentProps = { ...@@ -71,6 +72,15 @@ export const contentProps = {
subscribed: true, subscribed: true,
todoExists: false, todoExists: false,
state: 'opened', state: 'opened',
parent: {
id: 12,
startDateIsFixed: true,
startDate: '2018-12-01',
dueDateIsFixed: true,
dueDateFixed: '2019-12-31',
title: 'Sample Parent Epic',
url: `${gl.TEST_HOST}/groups/gitlab-org/-/epics/12`,
},
}; };
export const headerProps = { export const headerProps = {
......
...@@ -19,6 +19,7 @@ describe('epicSidebar', () => { ...@@ -19,6 +19,7 @@ describe('epicSidebar', () => {
labelsWebUrl, labelsWebUrl,
epicsWebUrl, epicsWebUrl,
labels, labels,
parent,
participants, participants,
subscribed, subscribed,
toggleSubscriptionPath, toggleSubscriptionPath,
...@@ -55,6 +56,7 @@ describe('epicSidebar', () => { ...@@ -55,6 +56,7 @@ describe('epicSidebar', () => {
startDateSourcingMilestoneDates, startDateSourcingMilestoneDates,
dueDateSourcingMilestoneTitle, dueDateSourcingMilestoneTitle,
dueDateSourcingMilestoneDates, dueDateSourcingMilestoneDates,
parent,
toggleSubscriptionPath, toggleSubscriptionPath,
labelsPath, labelsPath,
labelsWebUrl, labelsWebUrl,
...@@ -115,6 +117,47 @@ describe('epicSidebar', () => { ...@@ -115,6 +117,47 @@ describe('epicSidebar', () => {
).toEqual('Jan 1, 2018'); ).toEqual('Jan 1, 2018');
}); });
describe('parent epic', () => {
it('should render parent epic information in sidebar when `parent` is present', () => {
const parentEpicEl = vm.$el.querySelector('.block.parent-epic');
expect(parentEpicEl).not.toBeNull();
expect(parentEpicEl.querySelector('.collapse-truncated-title').innerText.trim()).toBe(
parent.title,
);
expect(parentEpicEl.querySelector('.value a').innerText.trim()).toBe(parent.title);
});
it('should render parent epic as `none` when `parent` is empty', done => {
vm.parent = {};
Vue.nextTick()
.then(() => {
const parentEpicEl = vm.$el.querySelector('.block.parent-epic');
expect(parentEpicEl.querySelector('.collapse-truncated-title').innerText.trim()).toBe(
'None',
);
expect(parentEpicEl.querySelector('.value .no-value').innerText.trim()).toBe('None');
})
.then(done)
.catch(done.fail);
});
it('should render parent epic information icon when sidebar is collapsed', () => {
const parentEpicElCollapsed = vm.$el.querySelector(
'.block.parent-epic .sidebar-collapsed-icon',
);
expect(parentEpicElCollapsed).not.toBeNull();
expect(parentEpicElCollapsed.querySelector('svg use').getAttribute('xlink:href')).toContain(
'epic',
);
});
});
describe('computed prop', () => { describe('computed prop', () => {
const getComponent = ( const getComponent = (
customPropsData = { customPropsData = {
......
import $ from 'jquery'; import $ from 'jquery';
import Vue from 'vue'; import Vue from 'vue';
import eventHub from 'ee/related_issues/event_hub';
import addIssuableForm from 'ee/related_issues/components/add_issuable_form.vue'; import addIssuableForm from 'ee/related_issues/components/add_issuable_form.vue';
const issuable1 = { const issuable1 = {
...@@ -167,21 +166,7 @@ describe('AddIssuableForm', () => { ...@@ -167,21 +166,7 @@ describe('AddIssuableForm', () => {
}); });
describe('methods', () => { describe('methods', () => {
let addIssuableFormInputSpy;
let addIssuableFormBlurSpy;
let addIssuableFormSubmitSpy;
let addIssuableFormCancelSpy;
beforeEach(() => { beforeEach(() => {
addIssuableFormInputSpy = jasmine.createSpy('spy');
addIssuableFormBlurSpy = jasmine.createSpy('spy');
addIssuableFormSubmitSpy = jasmine.createSpy('spy');
addIssuableFormCancelSpy = jasmine.createSpy('spy');
eventHub.$on('addIssuableFormInput', addIssuableFormInputSpy);
eventHub.$on('addIssuableFormBlur', addIssuableFormBlurSpy);
eventHub.$on('addIssuableFormSubmit', addIssuableFormSubmitSpy);
eventHub.$on('addIssuableFormCancel', addIssuableFormCancelSpy);
const el = document.createElement('div'); const el = document.createElement('div');
// We need to append to body to get focus tests working // We need to append to body to get focus tests working
document.body.appendChild(el); document.body.appendChild(el);
...@@ -198,13 +183,6 @@ describe('AddIssuableForm', () => { ...@@ -198,13 +183,6 @@ describe('AddIssuableForm', () => {
}).$mount(el); }).$mount(el);
}); });
afterEach(() => {
eventHub.$off('addIssuableFormInput', addIssuableFormInputSpy);
eventHub.$off('addIssuableFormBlur', addIssuableFormBlurSpy);
eventHub.$off('addIssuableFormSubmit', addIssuableFormSubmitSpy);
eventHub.$off('addIssuableFormCancel', addIssuableFormCancelSpy);
});
it('when clicking somewhere on the input wrapper should focus the input', done => { it('when clicking somewhere on the input wrapper should focus the input', done => {
vm.onInputWrapperClick(); vm.onInputWrapperClick();
...@@ -219,18 +197,19 @@ describe('AddIssuableForm', () => { ...@@ -219,18 +197,19 @@ describe('AddIssuableForm', () => {
}); });
it('when filling in the input', () => { it('when filling in the input', () => {
expect(addIssuableFormInputSpy).not.toHaveBeenCalled(); spyOn(vm, '$emit');
const newInputValue = 'filling in things'; const newInputValue = 'filling in things';
vm.$refs.input.value = newInputValue; vm.$refs.input.value = newInputValue;
vm.onInput(); vm.onInput();
expect(addIssuableFormInputSpy).toHaveBeenCalledWith(newInputValue, newInputValue.length); expect(vm.$emit).toHaveBeenCalledWith('addIssuableFormInput', {
newValue: newInputValue,
caretPos: newInputValue.length,
});
}); });
it('when blurring the input', done => { it('when blurring the input', done => {
expect(addIssuableFormInputSpy).not.toHaveBeenCalled(); spyOn(vm, '$emit');
const newInputValue = 'filling in things'; const newInputValue = 'filling in things';
vm.$refs.input.value = newInputValue; vm.$refs.input.value = newInputValue;
vm.onBlur(); vm.onBlur();
...@@ -238,7 +217,7 @@ describe('AddIssuableForm', () => { ...@@ -238,7 +217,7 @@ describe('AddIssuableForm', () => {
setTimeout(() => { setTimeout(() => {
Vue.nextTick(() => { Vue.nextTick(() => {
expect(vm.$refs.issuableFormWrapper.classList.contains('focus')).toEqual(false); expect(vm.$refs.issuableFormWrapper.classList.contains('focus')).toEqual(false);
expect(addIssuableFormBlurSpy).toHaveBeenCalledWith(newInputValue); expect(vm.$emit).toHaveBeenCalledWith('addIssuableFormBlur', newInputValue);
done(); done();
}); });
...@@ -266,29 +245,25 @@ describe('AddIssuableForm', () => { ...@@ -266,29 +245,25 @@ describe('AddIssuableForm', () => {
setTimeout(() => { setTimeout(() => {
Vue.nextTick(() => { Vue.nextTick(() => {
expect(vm.$refs.input.value).toEqual(''); expect(vm.$refs.input.value).toEqual('');
expect(addIssuableFormInputSpy.calls.count()).toEqual(1);
done(); done();
}); });
}); });
}); });
it('when submitting pending issues', () => { it('when submitting pending issues', () => {
expect(addIssuableFormSubmitSpy).not.toHaveBeenCalled(); spyOn(vm, '$emit');
const newInputValue = 'filling in things'; const newInputValue = 'filling in things';
vm.$refs.input.value = newInputValue; vm.$refs.input.value = newInputValue;
vm.onFormSubmit(); vm.onFormSubmit();
expect(addIssuableFormSubmitSpy).toHaveBeenCalledWith(newInputValue); expect(vm.$emit).toHaveBeenCalledWith('addIssuableFormSubmit', newInputValue);
}); });
it('when canceling form to collapse', () => { it('when canceling form to collapse', () => {
expect(addIssuableFormCancelSpy).not.toHaveBeenCalled(); spyOn(vm, '$emit');
vm.onFormCancel(); vm.onFormCancel();
expect(addIssuableFormCancelSpy).toHaveBeenCalled(); expect(vm.$emit).toHaveBeenCalledWith('addIssuableFormCancel');
}); });
}); });
}); });
import Vue from 'vue'; import Vue from 'vue';
import issueItem from 'ee/related_issues/components/issue_item.vue'; import issueItem from 'ee/related_issues/components/issue_item.vue';
import eventHub from 'ee/related_issues/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { defaultMilestone, defaultAssignees } from '../mock_data'; import { defaultMilestone, defaultAssignees } from '../mock_data';
...@@ -18,6 +17,7 @@ describe('issueItem', () => { ...@@ -18,6 +17,7 @@ describe('issueItem', () => {
createdAt: '2018-12-01T00:00:00.00Z', createdAt: '2018-12-01T00:00:00.00Z',
milestone: defaultMilestone, milestone: defaultMilestone,
assignees: defaultAssignees, assignees: defaultAssignees,
eventNamespace: 'relatedIssue',
}; };
beforeEach(() => { beforeEach(() => {
...@@ -172,11 +172,10 @@ describe('issueItem', () => { ...@@ -172,11 +172,10 @@ describe('issueItem', () => {
}); });
it('triggers onRemoveRequest when clicked', () => { it('triggers onRemoveRequest when clicked', () => {
const spy = jasmine.createSpy('spy'); spyOn(vm, '$emit');
eventHub.$on('removeRequest', spy);
removeBtn.click(); removeBtn.click();
expect(spy).toHaveBeenCalled(); expect(vm.$emit).toHaveBeenCalledWith('relatedIssueRemoveRequest', props.idKey);
}); });
}); });
}); });
import Vue from 'vue'; import Vue from 'vue';
import eventHub from 'ee/related_issues/event_hub';
import issueToken from 'ee/related_issues/components/issue_token.vue'; import issueToken from 'ee/related_issues/components/issue_token.vue';
describe('IssueToken', () => { describe('IssueToken', () => {
...@@ -7,6 +6,7 @@ describe('IssueToken', () => { ...@@ -7,6 +6,7 @@ describe('IssueToken', () => {
const displayReference = 'foo/bar#123'; const displayReference = 'foo/bar#123';
const title = 'some title'; const title = 'some title';
const pathIdSeparator = '#'; const pathIdSeparator = '#';
const eventNamespace = 'pendingIssuable';
let IssueToken; let IssueToken;
let vm; let vm;
...@@ -25,6 +25,7 @@ describe('IssueToken', () => { ...@@ -25,6 +25,7 @@ describe('IssueToken', () => {
vm = new IssueToken({ vm = new IssueToken({
propsData: { propsData: {
idKey, idKey,
eventNamespace,
displayReference, displayReference,
pathIdSeparator, pathIdSeparator,
}, },
...@@ -46,6 +47,7 @@ describe('IssueToken', () => { ...@@ -46,6 +47,7 @@ describe('IssueToken', () => {
vm = new IssueToken({ vm = new IssueToken({
propsData: { propsData: {
idKey, idKey,
eventNamespace,
displayReference, displayReference,
pathIdSeparator, pathIdSeparator,
title, title,
...@@ -65,6 +67,7 @@ describe('IssueToken', () => { ...@@ -65,6 +67,7 @@ describe('IssueToken', () => {
vm = new IssueToken({ vm = new IssueToken({
propsData: { propsData: {
idKey, idKey,
eventNamespace,
displayReference, displayReference,
pathIdSeparator, pathIdSeparator,
title, title,
...@@ -84,6 +87,7 @@ describe('IssueToken', () => { ...@@ -84,6 +87,7 @@ describe('IssueToken', () => {
vm = new IssueToken({ vm = new IssueToken({
propsData: { propsData: {
idKey, idKey,
eventNamespace,
displayReference, displayReference,
pathIdSeparator, pathIdSeparator,
state: 'opened', state: 'opened',
...@@ -101,6 +105,7 @@ describe('IssueToken', () => { ...@@ -101,6 +105,7 @@ describe('IssueToken', () => {
vm = new IssueToken({ vm = new IssueToken({
propsData: { propsData: {
idKey, idKey,
eventNamespace,
displayReference, displayReference,
pathIdSeparator, pathIdSeparator,
state: 'reopened', state: 'reopened',
...@@ -118,6 +123,7 @@ describe('IssueToken', () => { ...@@ -118,6 +123,7 @@ describe('IssueToken', () => {
vm = new IssueToken({ vm = new IssueToken({
propsData: { propsData: {
idKey, idKey,
eventNamespace,
displayReference, displayReference,
pathIdSeparator, pathIdSeparator,
state: 'closed', state: 'closed',
...@@ -137,6 +143,7 @@ describe('IssueToken', () => { ...@@ -137,6 +143,7 @@ describe('IssueToken', () => {
vm = new IssueToken({ vm = new IssueToken({
propsData: { propsData: {
idKey, idKey,
eventNamespace,
displayReference, displayReference,
pathIdSeparator, pathIdSeparator,
title, title,
...@@ -160,6 +167,7 @@ describe('IssueToken', () => { ...@@ -160,6 +167,7 @@ describe('IssueToken', () => {
vm = new IssueToken({ vm = new IssueToken({
propsData: { propsData: {
idKey, idKey,
eventNamespace,
displayReference, displayReference,
pathIdSeparator, pathIdSeparator,
}, },
...@@ -176,6 +184,7 @@ describe('IssueToken', () => { ...@@ -176,6 +184,7 @@ describe('IssueToken', () => {
vm = new IssueToken({ vm = new IssueToken({
propsData: { propsData: {
idKey, idKey,
eventNamespace,
displayReference, displayReference,
pathIdSeparator, pathIdSeparator,
canRemove: true, canRemove: true,
...@@ -190,29 +199,22 @@ describe('IssueToken', () => { ...@@ -190,29 +199,22 @@ describe('IssueToken', () => {
}); });
describe('methods', () => { describe('methods', () => {
let removeRequestSpy;
beforeEach(() => { beforeEach(() => {
vm = new IssueToken({ vm = new IssueToken({
propsData: { propsData: {
idKey, idKey,
eventNamespace,
displayReference, displayReference,
pathIdSeparator, pathIdSeparator,
}, },
}).$mount(); }).$mount();
removeRequestSpy = jasmine.createSpy('spy');
eventHub.$on('removeRequest', removeRequestSpy);
});
afterEach(() => {
eventHub.$off('removeRequest', removeRequestSpy);
}); });
it('when getting checked', () => { it('when getting checked', () => {
expect(removeRequestSpy).not.toHaveBeenCalled(); spyOn(vm, '$emit');
vm.onRemoveRequest(); vm.onRemoveRequest();
expect(removeRequestSpy).toHaveBeenCalled(); expect(vm.$emit).toHaveBeenCalledWith('pendingIssuableRemoveRequest', vm.idKey);
}); });
}); });
}); });
import Vue from 'vue'; import Vue from 'vue';
import eventHub from 'ee/related_issues/event_hub';
import relatedIssuesBlock from 'ee/related_issues/components/related_issues_block.vue'; import relatedIssuesBlock from 'ee/related_issues/components/related_issues_block.vue';
import { issuable1, issuable2, issuable3, issuable4, issuable5 } from '../mock_data'; import { issuable1, issuable2, issuable3, issuable4, issuable5 } from '../mock_data';
...@@ -20,7 +19,11 @@ describe('RelatedIssuesBlock', () => { ...@@ -20,7 +19,11 @@ describe('RelatedIssuesBlock', () => {
describe('with defaults', () => { describe('with defaults', () => {
beforeEach(() => { beforeEach(() => {
vm = new RelatedIssuesBlock().$mount(); vm = new RelatedIssuesBlock({
propsData: {
pathIdSeparator: '#',
},
}).$mount();
}); });
it('unable to add new related issues', () => { it('unable to add new related issues', () => {
...@@ -40,6 +43,7 @@ describe('RelatedIssuesBlock', () => { ...@@ -40,6 +43,7 @@ describe('RelatedIssuesBlock', () => {
beforeEach(() => { beforeEach(() => {
vm = new RelatedIssuesBlock({ vm = new RelatedIssuesBlock({
propsData: { propsData: {
pathIdSeparator: '#',
isFetching: true, isFetching: true,
}, },
}).$mount(); }).$mount();
...@@ -58,6 +62,7 @@ describe('RelatedIssuesBlock', () => { ...@@ -58,6 +62,7 @@ describe('RelatedIssuesBlock', () => {
beforeEach(() => { beforeEach(() => {
vm = new RelatedIssuesBlock({ vm = new RelatedIssuesBlock({
propsData: { propsData: {
pathIdSeparator: '#',
canAdmin: true, canAdmin: true,
}, },
}).$mount(); }).$mount();
...@@ -72,6 +77,7 @@ describe('RelatedIssuesBlock', () => { ...@@ -72,6 +77,7 @@ describe('RelatedIssuesBlock', () => {
beforeEach(() => { beforeEach(() => {
vm = new RelatedIssuesBlock({ vm = new RelatedIssuesBlock({
propsData: { propsData: {
pathIdSeparator: '#',
isFormVisible: true, isFormVisible: true,
}, },
}).$mount(); }).$mount();
...@@ -86,6 +92,7 @@ describe('RelatedIssuesBlock', () => { ...@@ -86,6 +92,7 @@ describe('RelatedIssuesBlock', () => {
beforeEach(() => { beforeEach(() => {
vm = new RelatedIssuesBlock({ vm = new RelatedIssuesBlock({
propsData: { propsData: {
pathIdSeparator: '#',
relatedIssues: [issuable1, issuable2], relatedIssues: [issuable1, issuable2],
}, },
}).$mount(); }).$mount();
...@@ -97,20 +104,13 @@ describe('RelatedIssuesBlock', () => { ...@@ -97,20 +104,13 @@ describe('RelatedIssuesBlock', () => {
}); });
describe('methods', () => { describe('methods', () => {
let toggleAddRelatedIssuesFormSpy;
beforeEach(() => { beforeEach(() => {
vm = new RelatedIssuesBlock({ vm = new RelatedIssuesBlock({
propsData: { propsData: {
pathIdSeparator: '#',
relatedIssues: [issuable1, issuable2, issuable3, issuable4, issuable5], relatedIssues: [issuable1, issuable2, issuable3, issuable4, issuable5],
}, },
}).$mount(); }).$mount();
toggleAddRelatedIssuesFormSpy = jasmine.createSpy('spy');
eventHub.$on('toggleAddRelatedIssuesForm', toggleAddRelatedIssuesFormSpy);
});
afterEach(() => {
eventHub.$off('toggleAddRelatedIssuesForm', toggleAddRelatedIssuesFormSpy);
}); });
it('reorder item correctly when an item is moved to the top', () => { it('reorder item correctly when an item is moved to the top', () => {
...@@ -140,12 +140,5 @@ describe('RelatedIssuesBlock', () => { ...@@ -140,12 +140,5 @@ describe('RelatedIssuesBlock', () => {
expect(beforeAfterIds.beforeId).toBe(3); expect(beforeAfterIds.beforeId).toBe(3);
expect(beforeAfterIds.afterId).toBe(5); expect(beforeAfterIds.afterId).toBe(5);
}); });
it('when expanding add related issue form', () => {
expect(toggleAddRelatedIssuesFormSpy).not.toHaveBeenCalled();
vm.toggleAddRelatedIssuesForm();
expect(toggleAddRelatedIssuesFormSpy).toHaveBeenCalled();
});
}); });
}); });
...@@ -300,7 +300,10 @@ describe('RelatedIssuesRoot', () => { ...@@ -300,7 +300,10 @@ describe('RelatedIssuesRoot', () => {
it('fill in issue number reference and adds to pending related issues', () => { it('fill in issue number reference and adds to pending related issues', () => {
const input = '#123 '; const input = '#123 ';
vm.onInput(input, input.length); vm.onInput({
newValue: input,
caretPos: input.length,
});
expect(vm.state.pendingReferences.length).toEqual(1); expect(vm.state.pendingReferences.length).toEqual(1);
expect(vm.state.pendingReferences[0]).toEqual('#123'); expect(vm.state.pendingReferences[0]).toEqual('#123');
...@@ -308,7 +311,7 @@ describe('RelatedIssuesRoot', () => { ...@@ -308,7 +311,7 @@ describe('RelatedIssuesRoot', () => {
it('fill in with full reference', () => { it('fill in with full reference', () => {
const input = 'asdf/qwer#444 '; const input = 'asdf/qwer#444 ';
vm.onInput(input, input.length); vm.onInput({ newValue: input, caretPos: input.length });
expect(vm.state.pendingReferences.length).toEqual(1); expect(vm.state.pendingReferences.length).toEqual(1);
expect(vm.state.pendingReferences[0]).toEqual('asdf/qwer#444'); expect(vm.state.pendingReferences[0]).toEqual('asdf/qwer#444');
...@@ -317,7 +320,7 @@ describe('RelatedIssuesRoot', () => { ...@@ -317,7 +320,7 @@ describe('RelatedIssuesRoot', () => {
it('fill in with issue link', () => { it('fill in with issue link', () => {
const link = 'http://localhost:3000/foo/bar/issues/111'; const link = 'http://localhost:3000/foo/bar/issues/111';
const input = `${link} `; const input = `${link} `;
vm.onInput(input, input.length); vm.onInput({ newValue: input, caretPos: input.length });
expect(vm.state.pendingReferences.length).toEqual(1); expect(vm.state.pendingReferences.length).toEqual(1);
expect(vm.state.pendingReferences[0]).toEqual(link); expect(vm.state.pendingReferences[0]).toEqual(link);
...@@ -325,7 +328,7 @@ describe('RelatedIssuesRoot', () => { ...@@ -325,7 +328,7 @@ describe('RelatedIssuesRoot', () => {
it('fill in with multiple references', () => { it('fill in with multiple references', () => {
const input = 'asdf/qwer#444 #12 '; const input = 'asdf/qwer#444 #12 ';
vm.onInput(input, input.length); vm.onInput({ newValue: input, caretPos: input.length });
expect(vm.state.pendingReferences.length).toEqual(2); expect(vm.state.pendingReferences.length).toEqual(2);
expect(vm.state.pendingReferences[0]).toEqual('asdf/qwer#444'); expect(vm.state.pendingReferences[0]).toEqual('asdf/qwer#444');
...@@ -334,7 +337,7 @@ describe('RelatedIssuesRoot', () => { ...@@ -334,7 +337,7 @@ describe('RelatedIssuesRoot', () => {
it('fill in with some invalid things', () => { it('fill in with some invalid things', () => {
const input = 'something random '; const input = 'something random ';
vm.onInput(input, input.length); vm.onInput({ newValue: input, caretPos: input.length });
expect(vm.state.pendingReferences.length).toEqual(2); expect(vm.state.pendingReferences.length).toEqual(2);
expect(vm.state.pendingReferences[0]).toEqual('something'); expect(vm.state.pendingReferences[0]).toEqual('something');
...@@ -343,7 +346,7 @@ describe('RelatedIssuesRoot', () => { ...@@ -343,7 +346,7 @@ describe('RelatedIssuesRoot', () => {
it('fill in invalid and some legit references', () => { it('fill in invalid and some legit references', () => {
const input = 'something random #123 '; const input = 'something random #123 ';
vm.onInput(input, input.length); vm.onInput({ newValue: input, caretPos: input.length });
expect(vm.state.pendingReferences.length).toEqual(3); expect(vm.state.pendingReferences.length).toEqual(3);
expect(vm.state.pendingReferences[0]).toEqual('something'); expect(vm.state.pendingReferences[0]).toEqual('something');
...@@ -353,7 +356,7 @@ describe('RelatedIssuesRoot', () => { ...@@ -353,7 +356,7 @@ describe('RelatedIssuesRoot', () => {
it('keep reference piece in input while we are touching it', () => { it('keep reference piece in input while we are touching it', () => {
const input = 'a #123 b '; const input = 'a #123 b ';
vm.onInput(input, 3); vm.onInput({ newValue: input, caretPos: 3 });
expect(vm.state.pendingReferences.length).toEqual(2); expect(vm.state.pendingReferences.length).toEqual(2);
expect(vm.state.pendingReferences[0]).toEqual('a'); expect(vm.state.pendingReferences[0]).toEqual('a');
......
...@@ -16,7 +16,9 @@ describe('sidebarItemEpic', () => { ...@@ -16,7 +16,9 @@ describe('sidebarItemEpic', () => {
}); });
const SidebarItemEpic = Vue.extend(sidebarItemEpic); const SidebarItemEpic = Vue.extend(sidebarItemEpic);
vm = mountComponent(SidebarItemEpic, {}); vm = mountComponent(SidebarItemEpic, {
initialEpic: null,
});
}); });
afterEach(() => { afterEach(() => {
......
...@@ -6186,6 +6186,9 @@ msgstr "" ...@@ -6186,6 +6186,9 @@ msgstr ""
msgid "Pagination|« First" msgid "Pagination|« First"
msgstr "" msgstr ""
msgid "Parent epic"
msgstr ""
msgid "Part of merge request changes" msgid "Part of merge request changes"
msgstr "" msgstr ""
......
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