Commit b23c3fd2 authored by Jacob Schatz's avatar Jacob Schatz

Merge branch 'epic-in-issue' into 'master'

Add epic information to issue sidebar

Closes #3696

See merge request gitlab-org/gitlab-ee!3579
parents 02bd4f66 3539abf3
import Flash from '../../../flash'; import Flash from '../../../flash';
import AssigneeTitle from './assignee_title'; import AssigneeTitle from './assignee_title';
import Assignees from './assignees'; import Assignees from './assignees';
import Store from '../../stores/sidebar_store'; import Store from '../../stores/sidebar_store';
import Mediator from '../../sidebar_mediator';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
export default { export default {
name: 'SidebarAssignees', name: 'SidebarAssignees',
data() { data() {
return { return {
mediator: new Mediator(),
store: new Store(), store: new Store(),
loading: false, loading: false,
field: '',
}; };
}, },
props: {
mediator: {
type: Object,
required: true,
},
field: {
type: String,
required: true,
},
signedIn: {
type: Boolean,
required: false,
default: false,
},
},
components: { components: {
'assignee-title': AssigneeTitle, 'assignee-title': AssigneeTitle,
assignees: Assignees, assignees: Assignees,
...@@ -61,10 +71,6 @@ export default { ...@@ -61,10 +71,6 @@ export default {
eventHub.$off('sidebar.removeAllAssignees', this.removeAllAssignees); eventHub.$off('sidebar.removeAllAssignees', this.removeAllAssignees);
eventHub.$off('sidebar.saveAssignees', this.saveAssignees); eventHub.$off('sidebar.saveAssignees', this.saveAssignees);
}, },
beforeMount() {
this.field = this.$el.dataset.field;
this.signedIn = typeof this.$el.dataset.signedIn !== 'undefined';
},
template: ` template: `
<div> <div>
<assignee-title <assignee-title
......
<script> <script>
import Store from '../../stores/sidebar_store'; import Store from '../../stores/sidebar_store';
import Mediator from '../../sidebar_mediator';
import participants from './participants.vue'; import participants from './participants.vue';
export default { export default {
data() { data() {
return { return {
mediator: new Mediator(),
store: new Store(), store: new Store(),
}; };
}, },
props: {
mediator: {
type: Object,
required: true,
},
},
components: { components: {
participants, participants,
}, },
...@@ -21,6 +25,7 @@ export default { ...@@ -21,6 +25,7 @@ export default {
<participants <participants
:loading="store.isFetching.participants" :loading="store.isFetching.participants"
:participants="store.participants" :participants="store.participants"
:number-of-less-participants="7" /> :number-of-less-participants="7"
/>
</div> </div>
</template> </template>
<script> <script>
import Store from '../../stores/sidebar_store'; import Store from '../../stores/sidebar_store';
import Mediator from '../../sidebar_mediator';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
import Flash from '../../../flash'; import Flash from '../../../flash';
import { __ } from '../../../locale'; import { __ } from '../../../locale';
...@@ -9,11 +8,15 @@ import subscriptions from './subscriptions.vue'; ...@@ -9,11 +8,15 @@ import subscriptions from './subscriptions.vue';
export default { export default {
data() { data() {
return { return {
mediator: new Mediator(),
store: new Store(), store: new Store(),
}; };
}, },
props: {
mediator: {
type: Object,
required: true,
},
},
components: { components: {
subscriptions, subscriptions,
}, },
......
...@@ -10,6 +10,27 @@ import Translate from '../vue_shared/translate'; ...@@ -10,6 +10,27 @@ import Translate from '../vue_shared/translate';
Vue.use(Translate); Vue.use(Translate);
function mountAssigneesComponent(mediator) {
const el = document.getElementById('js-vue-sidebar-assignees');
if (!el) return;
// eslint-disable-next-line no-new
new Vue({
el,
components: {
SidebarAssignees,
},
render: createElement => createElement('sidebar-assignees', {
props: {
mediator,
field: el.dataset.field,
signedIn: el.hasAttribute('data-signed-in'),
},
}),
});
}
function mountConfidentialComponent(mediator) { function mountConfidentialComponent(mediator) {
const el = document.getElementById('js-confidential-entry-point'); const el = document.getElementById('js-confidential-entry-point');
...@@ -49,9 +70,10 @@ function mountLockComponent(mediator) { ...@@ -49,9 +70,10 @@ function mountLockComponent(mediator) {
}).$mount(el); }).$mount(el);
} }
function mountParticipantsComponent() { function mountParticipantsComponent(mediator) {
const el = document.querySelector('.js-sidebar-participants-entry-point'); const el = document.querySelector('.js-sidebar-participants-entry-point');
// eslint-disable-next-line no-new
if (!el) return; if (!el) return;
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
...@@ -60,11 +82,15 @@ function mountParticipantsComponent() { ...@@ -60,11 +82,15 @@ function mountParticipantsComponent() {
components: { components: {
sidebarParticipants, sidebarParticipants,
}, },
render: createElement => createElement('sidebar-participants', {}), render: createElement => createElement('sidebar-participants', {
props: {
mediator,
},
}),
}); });
} }
function mountSubscriptionsComponent() { function mountSubscriptionsComponent(mediator) {
const el = document.querySelector('.js-sidebar-subscriptions-entry-point'); const el = document.querySelector('.js-sidebar-subscriptions-entry-point');
if (!el) return; if (!el) return;
...@@ -75,22 +101,35 @@ function mountSubscriptionsComponent() { ...@@ -75,22 +101,35 @@ function mountSubscriptionsComponent() {
components: { components: {
sidebarSubscriptions, sidebarSubscriptions,
}, },
render: createElement => createElement('sidebar-subscriptions', {}), render: createElement => createElement('sidebar-subscriptions', {
props: {
mediator,
},
}),
}); });
} }
function mount(mediator) { function mountTimeTrackingComponent() {
const sidebarAssigneesEl = document.getElementById('js-vue-sidebar-assignees'); const el = document.getElementById('issuable-time-tracker');
// Only create the sidebarAssignees vue app if it is found in the DOM
// We currently do not use sidebarAssignees for the MR page
if (sidebarAssigneesEl) {
new Vue(SidebarAssignees).$mount(sidebarAssigneesEl);
}
if (!el) return;
// eslint-disable-next-line no-new
new Vue({
el,
components: {
SidebarTimeTracking,
},
render: createElement => createElement('sidebar-time-tracking', {}),
});
}
export function mountSidebar(mediator) {
mountAssigneesComponent(mediator);
mountConfidentialComponent(mediator); mountConfidentialComponent(mediator);
mountLockComponent(mediator); mountLockComponent(mediator);
mountParticipantsComponent(); mountParticipantsComponent(mediator);
mountSubscriptionsComponent(); mountSubscriptionsComponent(mediator);
new SidebarMoveIssue( new SidebarMoveIssue(
mediator, mediator,
...@@ -98,7 +137,9 @@ function mount(mediator) { ...@@ -98,7 +137,9 @@ function mount(mediator) {
$('.js-move-issue-confirmation-button'), $('.js-move-issue-confirmation-button'),
).init(); ).init();
new Vue(SidebarTimeTracking).$mount('#issuable-time-tracker'); mountTimeTrackingComponent();
} }
export default mount; export function getSidebarOptions() {
return JSON.parse(document.querySelector('.js-sidebar-options').innerHTML);
}
import mountSidebarEE from 'ee/sidebar/mount_sidebar'; import Mediator from './sidebar_mediator';
import Mediator from 'ee/sidebar/sidebar_mediator'; import { mountSidebar, getSidebarOptions } from './mount_sidebar';
import mountSidebar from './mount_sidebar';
function domContentLoaded() { function domContentLoaded() {
const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML); const mediator = new Mediator(getSidebarOptions());
const mediator = new Mediator(sidebarOptions);
mediator.fetch(); mediator.fetch();
mountSidebar(mediator); mountSidebar(mediator);
mountSidebarEE(mediator);
} }
document.addEventListener('DOMContentLoaded', domContentLoaded); document.addEventListener('DOMContentLoaded', domContentLoaded);
......
...@@ -7,7 +7,6 @@ export default class SidebarMediator { ...@@ -7,7 +7,6 @@ export default class SidebarMediator {
if (!SidebarMediator.singleton) { if (!SidebarMediator.singleton) {
this.initSingleton(options); this.initSingleton(options);
} }
return SidebarMediator.singleton; return SidebarMediator.singleton;
} }
......
export default class SidebarStore { export default class SidebarStore {
constructor(store) { constructor(options) {
if (!SidebarStore.singleton) { if (!SidebarStore.singleton) {
const { currentUser, rootPath, editable } = store; this.initSingleton(options);
this.currentUser = currentUser;
this.rootPath = rootPath;
this.editable = editable;
this.timeEstimate = 0;
this.totalTimeSpent = 0;
this.humanTimeEstimate = '';
this.humanTimeSpent = '';
this.assignees = [];
this.isFetching = {
assignees: true,
participants: true,
subscriptions: true,
};
this.isLoading = {};
this.autocompleteProjects = [];
this.moveToProjectId = 0;
this.isLockDialogOpen = false;
this.participants = [];
this.subscribed = null;
SidebarStore.singleton = this;
} }
return SidebarStore.singleton; return SidebarStore.singleton;
} }
initSingleton(options) {
const { currentUser, rootPath, editable } = options;
this.currentUser = currentUser;
this.rootPath = rootPath;
this.editable = editable;
this.timeEstimate = 0;
this.totalTimeSpent = 0;
this.humanTimeEstimate = '';
this.humanTimeSpent = '';
this.assignees = [];
this.isFetching = {
assignees: true,
participants: true,
subscriptions: true,
};
this.isLoading = {};
this.autocompleteProjects = [];
this.moveToProjectId = 0;
this.isLockDialogOpen = false;
this.participants = [];
this.subscribed = null;
SidebarStore.singleton = this;
}
setAssigneeData(data) { setAssigneeData(data) {
this.isFetching.assignees = false; this.isFetching.assignees = false;
if (data.assignees) { if (data.assignees) {
......
...@@ -50,6 +50,11 @@ ...@@ -50,6 +50,11 @@
&:not(.disabled) { &:not(.disabled) {
cursor: pointer; cursor: pointer;
} }
svg {
width: $gl-padding;
height: $gl-padding;
}
} }
} }
......
...@@ -471,7 +471,8 @@ ...@@ -471,7 +471,8 @@
} }
} }
.milestone-title span { .milestone-title span,
.collapse-truncated-title {
@include str-truncated(100%); @include str-truncated(100%);
display: block; display: block;
margin: 0 4px; margin: 0 4px;
......
class IssueSidebarEntity < IssuableSidebarEntity class IssueSidebarEntity < IssuableSidebarEntity
prepend ::EE::IssueSidebarEntity
expose :assignees, using: API::Entities::UserBasic expose :assignees, using: API::Entities::UserBasic
end end
- todo = issuable_todo(issuable) - todo = issuable_todo(issuable)
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('sidebar') = page_specific_javascript_bundle_tag('ee_sidebar')
%aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { signed: { in: current_user.present? } }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' } %aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { signed: { in: current_user.present? } }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
.issuable-sidebar{ data: { endpoint: "#{issuable_json_path(issuable)}" } } .issuable-sidebar{ data: { endpoint: "#{issuable_json_path(issuable)}" } }
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
= render "shared/issuable/sidebar_todo", todo: todo, issuable: issuable, is_collapsed: true = render "shared/issuable/sidebar_todo", todo: todo, issuable: issuable, is_collapsed: true
.block.assignee .block.assignee
= render "shared/issuable/sidebar_assignees", issuable: issuable, can_edit_issuable: can_edit_issuable, signed_in: current_user.present? = render "shared/issuable/sidebar_assignees", issuable: issuable, can_edit_issuable: can_edit_issuable, signed_in: current_user.present?
= render "shared/issuable/sidebar_item_epic", issuable: issuable
.block.milestone .block.milestone
.sidebar-collapsed-icon .sidebar-collapsed-icon
= icon('clock-o', 'aria-hidden': 'true') = icon('clock-o', 'aria-hidden': 'true')
......
---
title: Add epic information to issue sidebar
merge_request:
author:
type: added
...@@ -84,6 +84,7 @@ var config = { ...@@ -84,6 +84,7 @@ var config = {
registry_list: './registry/index.js', registry_list: './registry/index.js',
repo: './repo/index.js', repo: './repo/index.js',
sidebar: './sidebar/sidebar_bundle.js', sidebar: './sidebar/sidebar_bundle.js',
ee_sidebar: 'ee/sidebar/sidebar_bundle.js',
schedule_form: './pipeline_schedules/pipeline_schedule_form_bundle.js', schedule_form: './pipeline_schedules/pipeline_schedule_form_bundle.js',
schedules_index: './pipeline_schedules/pipeline_schedules_index_bundle.js', schedules_index: './pipeline_schedules/pipeline_schedules_index_bundle.js',
snippet: './snippet/snippet_bundle.js', snippet: './snippet/snippet_bundle.js',
......
<script>
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import { spriteIcon } from '~/lib/utils/common_utils';
import Store from '../stores/sidebar_store';
export default {
name: 'sidebarItemEpic',
data() {
return {
store: new Store(),
};
},
components: {
LoadingIcon,
},
directives: {
tooltip,
},
computed: {
isLoading() {
return this.store.isFetching.epic;
},
epicIcon() {
return spriteIcon('epic');
},
epicUrl() {
return this.store.epic.url;
},
epicTitle() {
return this.store.epic.title;
},
hasEpic() {
return this.epicUrl && this.epicTitle;
},
collapsedTitle() {
return this.hasEpic ? this.epicTitle : 'None';
},
},
};
</script>
<template>
<div>
<div class="sidebar-collapsed-icon">
<div v-html="epicIcon"></div>
<span
v-if="!isLoading"
class="collapse-truncated-title"
:title="epicTitle"
data-container="body"
data-placement="left"
v-tooltip
>
{{collapsedTitle}}
</span>
</div>
<div class="title hide-collapsed">
Epic
<loading-icon
v-if="isLoading"
:inline="true"
/>
</div>
<div
v-if="!isLoading"
class="value hide-collapsed"
>
<a
v-if="hasEpic"
class="bold"
:href="epicUrl"
>
{{epicTitle}}
</a>
<span
v-else
class="no-value"
>
None
</span>
</div>
</div>
</template>
import Vue from 'vue'; import Vue from 'vue';
import * as CEMountSidebar from '~/sidebar/mount_sidebar';
import sidebarWeight from './components/weight/sidebar_weight.vue'; import sidebarWeight from './components/weight/sidebar_weight.vue';
import SidebarItemEpic from './components/sidebar_item_epic.vue';
function mountWeightComponent(mediator) { function mountWeightComponent(mediator) {
const el = document.querySelector('.js-sidebar-weight-entry-point'); const el = document.querySelector('.js-sidebar-weight-entry-point');
...@@ -20,8 +22,20 @@ function mountWeightComponent(mediator) { ...@@ -20,8 +22,20 @@ function mountWeightComponent(mediator) {
}); });
} }
function mount(mediator) { function mountEpic() {
mountWeightComponent(mediator); const el = document.querySelector('#js-vue-sidebar-item-epic');
return new Vue({
el,
components: {
SidebarItemEpic,
},
render: createElement => createElement('sidebar-item-epic', {}),
});
} }
export default mount; export default function mountSidebar(mediator) {
CEMountSidebar.mountSidebar(mediator);
mountWeightComponent(mediator);
mountEpic();
}
import { getSidebarOptions } from '~/sidebar/mount_sidebar';
import Mediator from './sidebar_mediator';
import mountSidebar from './mount_sidebar';
function domContentLoaded() {
const mediator = new Mediator(getSidebarOptions());
mediator.fetch();
mountSidebar(mediator);
}
document.addEventListener('DOMContentLoaded', domContentLoaded);
export default domContentLoaded;
...@@ -10,6 +10,7 @@ export default class SidebarMediator extends CESidebarMediator { ...@@ -10,6 +10,7 @@ export default class SidebarMediator extends CESidebarMediator {
processFetchedData(data) { processFetchedData(data) {
super.processFetchedData(data); super.processFetchedData(data);
this.store.setWeightData(data); this.store.setWeightData(data);
this.store.setEpicData(data);
} }
updateWeight(newWeight) { updateWeight(newWeight) {
......
import CESidebarStore from '~/sidebar/stores/sidebar_store'; import CESidebarStore from '~/sidebar/stores/sidebar_store';
export default class SidebarStore extends CESidebarStore { export default class SidebarStore extends CESidebarStore {
constructor(store) { initSingleton(options) {
super(store); super.initSingleton(options);
this.isFetching.weight = true; this.isFetching.weight = true;
this.isFetching.epic = true;
this.isLoading.weight = false; this.isLoading.weight = false;
this.weight = null; this.weight = null;
this.weightOptions = store.weightOptions; this.weightOptions = options.weightOptions;
this.weightNoneValue = store.weightNoneValue; this.weightNoneValue = options.weightNoneValue;
this.epic = {};
} }
setWeightData(data) { setWeightData(data) {
...@@ -19,4 +21,9 @@ export default class SidebarStore extends CESidebarStore { ...@@ -19,4 +21,9 @@ export default class SidebarStore extends CESidebarStore {
setWeight(newWeight) { setWeight(newWeight) {
this.weight = newWeight; this.weight = newWeight;
} }
setEpicData(data) {
this.isFetching.epic = false;
this.epic = data.epic || {};
}
} }
module EE
module IssueSidebarEntity
extend ActiveSupport::Concern
prepended do
expose :epic, using: EpicBaseEntity
end
end
end
class EpicBaseEntity < Grape::Entity
include RequestAwareEntity
expose :id
expose :title
expose :url do |epic|
group_epic_path(epic.group, epic)
end
end
- return unless issuable.project.group&.feature_available?(:epics)
- if issuable.is_a?(Issue)
.block.epic
#js-vue-sidebar-item-epic
.title.hide-collapsed
Epic
= icon('spinner spin')
require 'spec_helper'
describe 'Epic in issue sidebar', :js do
let(:user) { create(:user) }
let(:group) { create(:group, :public) }
let(:epic) { create(:epic, group: group) }
let(:project) { create(:project, :public, group: group) }
let(:issue) { create(:issue, project: project) }
let!(:epic_issue) { create(:epic_issue, epic: epic, issue: issue) }
context 'when epics available' do
before do
stub_licensed_features(epics: true)
sign_in(user)
visit project_issue_path(project, issue)
end
it 'shows epic in issue sidebar' do
expect(page.find('.block.epic .value')).to have_content(epic.title)
end
end
context 'when epics unavailable' do
before do
stub_licensed_features(epics: false)
sign_in(user)
visit project_issue_path(project, issue)
end
it 'does not show epic in issue sidebar' do
expect(page).not_to have_selector('.block.epic')
end
end
end
{ {
"type": "object", "type": "object",
"properties" : { "properties" : {
"id": { "type": "integer" },
"iid": { "type": "integer" },
"subscribed": { "type": "boolean" }, "subscribed": { "type": "boolean" },
"time_estimate": { "type": "integer" }, "time_estimate": { "type": "integer" },
"total_time_spent": { "type": "integer" }, "total_time_spent": { "type": "integer" },
...@@ -16,6 +14,14 @@ ...@@ -16,6 +14,14 @@
"type": "array", "type": "array",
"items": { "$ref": "../public_api/v4/user/basic.json" } "items": { "$ref": "../public_api/v4/user/basic.json" }
}, },
"epic": {
"type": ["object", "null"],
"properties": {
"id": { "type": "integer" },
"title": { "type": "string" },
"url": { "type": "string" }
}
},
"weight": { "type": ["integer", "null"] } "weight": { "type": ["integer", "null"] }
}, },
"additionalProperties": false "additionalProperties": false
......
import Vue from 'vue';
import CESidebarStore from '~/sidebar/stores/sidebar_store';
import SidebarStore from 'ee/sidebar/stores/sidebar_store';
import sidebarItemEpic from 'ee/sidebar/components/sidebar_item_epic.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
describe('sidebarItemEpic', () => {
let vm;
let sidebarStore;
beforeEach(() => {
sidebarStore = new SidebarStore({
currentUser: '',
rootPath: '',
editable: false,
});
const SidebarItemEpic = Vue.extend(sidebarItemEpic);
vm = mountComponent(SidebarItemEpic, {});
});
afterEach(() => {
vm.$destroy();
CESidebarStore.singleton = null;
});
describe('loading', () => {
it('shows loading icon', () => {
expect(vm.$el.querySelector('.fa-spin')).toBeDefined();
});
it('hides collapsed title', () => {
expect(vm.$el.querySelector('.sidebar-collapsed-icon .collapsed-truncated-title')).toBeNull();
});
});
describe('loaded', () => {
const epicTitle = 'epic title';
const url = 'https://gitlab.com/';
beforeEach((done) => {
sidebarStore.setEpicData({
epic: {
title: epicTitle,
id: 1,
url,
},
});
Vue.nextTick(done);
});
it('shows epic title', () => {
expect(vm.$el.querySelector('.value').innerText.trim()).toEqual(epicTitle);
});
it('links epic title to epic url', () => {
expect(vm.$el.querySelector('a').href).toEqual(url);
});
it('shows epic title as collapsed title tooltip', () => {
expect(vm.$el.querySelector('.collapse-truncated-title').getAttribute('title')).toBeDefined();
expect(vm.$el.querySelector('.collapse-truncated-title').getAttribute('data-original-title')).toEqual(epicTitle);
});
describe('no epic', () => {
beforeEach((done) => {
sidebarStore.epic = {};
Vue.nextTick(done);
});
it('shows none as the epic text', () => {
expect(vm.$el.querySelector('.value').innerText.trim()).toEqual('None');
});
it('shows none as the collapsed title', () => {
expect(vm.$el.querySelector('.collapse-truncated-title').innerText.trim()).toEqual('None');
});
it('hides collapsed title tooltip', () => {
expect(vm.$el.querySelector('.collapse-truncated-title').getAttribute('title')).toBeNull();
});
});
});
});
import Vue from 'vue'; import Vue from 'vue';
import SidebarMediator from 'ee/sidebar/sidebar_mediator'; import SidebarMediator from 'ee/sidebar/sidebar_mediator';
import SidebarStore from 'ee/sidebar/stores/sidebar_store'; import CESidebarMediator from '~/sidebar/sidebar_mediator';
import CESidebarStore from '~/sidebar/stores/sidebar_store';
import SidebarService from '~/sidebar/services/sidebar_service'; import SidebarService from '~/sidebar/services/sidebar_service';
import Mock from './ee_mock_data'; import Mock from './ee_mock_data';
...@@ -12,8 +13,8 @@ describe('EE Sidebar mediator', () => { ...@@ -12,8 +13,8 @@ describe('EE Sidebar mediator', () => {
afterEach(() => { afterEach(() => {
SidebarService.singleton = null; SidebarService.singleton = null;
SidebarStore.singleton = null; CESidebarStore.singleton = null;
SidebarMediator.singleton = null; CESidebarMediator.singleton = null;
Vue.http.interceptors = _.without(Vue.http.interceptors, Mock.sidebarMockInterceptor); Vue.http.interceptors = _.without(Vue.http.interceptors, Mock.sidebarMockInterceptor);
}); });
......
import SidebarStore from 'ee/sidebar/stores/sidebar_store'; import SidebarStore from 'ee/sidebar/stores/sidebar_store';
import CESidebarStore from '~/sidebar/stores/sidebar_store';
describe('EE Sidebar store', () => { describe('EE Sidebar store', () => {
let store;
beforeEach(() => { beforeEach(() => {
this.store = new SidebarStore({ store = new SidebarStore({
weight: null,
weightOptions: ['No Weight', 0, 1, 3], weightOptions: ['No Weight', 0, 1, 3],
weightNoneValue: 'No Weight', weightNoneValue: 'No Weight',
}); });
}); });
afterEach(() => { afterEach(() => {
SidebarStore.singleton = null; // Since CESidebarStore stores the actual singleton instance
// we need to clear that specific reference
CESidebarStore.singleton = null;
}); });
it('sets weight data', () => { it('sets weight data', () => {
expect(this.store.weight).toEqual(null); expect(store.weight).toEqual(null);
const weight = 3; const weight = 3;
this.store.setWeightData({ store.setWeightData({
weight, weight,
}); });
expect(this.store.isFetching.weight).toEqual(false); expect(store.isFetching.weight).toEqual(false);
expect(this.store.weight).toEqual(weight); expect(store.weight).toEqual(weight);
}); });
it('set weight', () => { it('set weight', () => {
const weight = 3; expect(store.weight).toEqual(null);
this.store.setWeight(weight); const weight = 1;
store.setWeight(weight);
expect(this.store.weight).toEqual(weight); expect(store.weight).toEqual(weight);
}); });
}); });
...@@ -4,20 +4,29 @@ import SidebarMediator from '~/sidebar/sidebar_mediator'; ...@@ -4,20 +4,29 @@ import SidebarMediator from '~/sidebar/sidebar_mediator';
import SidebarService from '~/sidebar/services/sidebar_service'; import SidebarService from '~/sidebar/services/sidebar_service';
import SidebarStore from '~/sidebar/stores/sidebar_store'; import SidebarStore from '~/sidebar/stores/sidebar_store';
import Mock from './mock_data'; import Mock from './mock_data';
import mountComponent from '../helpers/vue_mount_component_helper';
describe('sidebar assignees', () => { describe('sidebar assignees', () => {
let component; let vm;
let SidebarAssigneeComponent; let mediator;
let sidebarAssigneesEl;
preloadFixtures('issues/open-issue.html.raw'); preloadFixtures('issues/open-issue.html.raw');
beforeEach(() => { beforeEach(() => {
Vue.http.interceptors.push(Mock.sidebarMockInterceptor); Vue.http.interceptors.push(Mock.sidebarMockInterceptor);
SidebarAssigneeComponent = Vue.extend(SidebarAssignees);
spyOn(SidebarMediator.prototype, 'saveAssignees').and.callThrough();
spyOn(SidebarMediator.prototype, 'assignYourself').and.callThrough();
this.mediator = new SidebarMediator(Mock.mediator);
loadFixtures('issues/open-issue.html.raw'); loadFixtures('issues/open-issue.html.raw');
this.sidebarAssigneesEl = document.querySelector('#js-vue-sidebar-assignees');
mediator = new SidebarMediator(Mock.mediator);
spyOn(mediator, 'saveAssignees').and.callThrough();
spyOn(mediator, 'assignYourself').and.callThrough();
const SidebarAssigneeComponent = Vue.extend(SidebarAssignees);
sidebarAssigneesEl = document.querySelector('#js-vue-sidebar-assignees');
vm = mountComponent(SidebarAssigneeComponent, {
mediator,
field: sidebarAssigneesEl.dataset.field,
}, sidebarAssigneesEl);
}); });
afterEach(() => { afterEach(() => {
...@@ -28,30 +37,24 @@ describe('sidebar assignees', () => { ...@@ -28,30 +37,24 @@ describe('sidebar assignees', () => {
}); });
it('calls the mediator when saves the assignees', () => { it('calls the mediator when saves the assignees', () => {
component = new SidebarAssigneeComponent() vm.saveAssignees();
.$mount(this.sidebarAssigneesEl); expect(mediator.saveAssignees).toHaveBeenCalled();
component.saveAssignees();
expect(SidebarMediator.prototype.saveAssignees).toHaveBeenCalled();
}); });
it('calls the mediator when "assignSelf" method is called', () => { it('calls the mediator when "assignSelf" method is called', () => {
component = new SidebarAssigneeComponent() vm.assignSelf();
.$mount(this.sidebarAssigneesEl);
component.assignSelf();
expect(SidebarMediator.prototype.assignYourself).toHaveBeenCalled(); expect(mediator.assignYourself).toHaveBeenCalled();
expect(this.mediator.store.assignees.length).toEqual(1); expect(mediator.store.assignees.length).toEqual(1);
}); });
it('hides assignees until fetched', (done) => { it('hides assignees until fetched', (done) => {
component = new SidebarAssigneeComponent().$mount(this.sidebarAssigneesEl); const currentAssignee = sidebarAssigneesEl.querySelector('.value');
const currentAssignee = this.sidebarAssigneesEl.querySelector('.value');
expect(currentAssignee).toBe(null); expect(currentAssignee).toBe(null);
component.store.isFetching.assignees = false; vm.store.isFetching.assignees = false;
Vue.nextTick(() => { Vue.nextTick(() => {
expect(component.$el.querySelector('.value')).toBeVisible(); expect(vm.$el.querySelector('.value')).toBeVisible();
done(); done();
}); });
}); });
......
...@@ -26,11 +26,14 @@ describe('Sidebar Subscriptions', function () { ...@@ -26,11 +26,14 @@ describe('Sidebar Subscriptions', function () {
}); });
it('calls the mediator toggleSubscription on event', () => { it('calls the mediator toggleSubscription on event', () => {
spyOn(SidebarMediator.prototype, 'toggleSubscription').and.returnValue(Promise.resolve()); const mediator = new SidebarMediator();
vm = mountComponent(SidebarSubscriptions, {}); spyOn(mediator, 'toggleSubscription').and.returnValue(Promise.resolve());
vm = mountComponent(SidebarSubscriptions, {
mediator,
});
eventHub.$emit('toggleSubscription'); eventHub.$emit('toggleSubscription');
expect(SidebarMediator.prototype.toggleSubscription).toHaveBeenCalled(); expect(mediator.toggleSubscription).toHaveBeenCalled();
}); });
}); });
...@@ -20,8 +20,12 @@ describe IssueSerializer do ...@@ -20,8 +20,12 @@ describe IssueSerializer do
context 'sidebar issue serialization' do context 'sidebar issue serialization' do
let(:serializer) { 'sidebar' } let(:serializer) { 'sidebar' }
before do
create(:epic_issue, issue: resource)
end
it 'matches sidebar issue json schema' do it 'matches sidebar issue json schema' do
expect(json_entity).to match_schema('entities/issue_sidebar') expect(json_entity).to match_schema('entities/issue_sidebar', strict: true)
end end
end end
end end
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