Commit c5b22ef8 authored by Kushal Pandya's avatar Kushal Pandya

Hide EEU features for Epics in EEP

Hides ability to use sub-epics (an EE Ultimate feature) for
users who are on EE Premium.
parent 33e7d73d
...@@ -30,6 +30,7 @@ export default { ...@@ -30,6 +30,7 @@ export default {
computed: { computed: {
...mapState([ ...mapState([
'canUpdate', 'canUpdate',
'allowSubEpics',
'sidebarCollapsed', 'sidebarCollapsed',
'participants', 'participants',
'startDateSourcingMilestoneTitle', 'startDateSourcingMilestoneTitle',
...@@ -186,7 +187,7 @@ export default { ...@@ -186,7 +187,7 @@ export default {
@toggleCollapse="toggleSidebar({ sidebarCollapsed })" @toggleCollapse="toggleSidebar({ sidebarCollapsed })"
/> />
<sidebar-labels :can-update="canUpdate" :sidebar-collapsed="sidebarCollapsed" /> <sidebar-labels :can-update="canUpdate" :sidebar-collapsed="sidebarCollapsed" />
<div class="block ancestors"> <div v-if="allowSubEpics" class="block ancestors">
<ancestors-tree :ancestors="ancestors" :is-fetching="false" /> <ancestors-tree :ancestors="ancestors" :is-fetching="false" />
</div> </div>
<div class="block participants"> <div class="block participants">
......
...@@ -3,7 +3,7 @@ import { mapActions } from 'vuex'; ...@@ -3,7 +3,7 @@ import { mapActions } from 'vuex';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
import createStore from './store'; import createStore from './store';
import EpicApp from './components/epic_app.vue'; import EpicApp from './components/epic_app.vue';
...@@ -54,7 +54,10 @@ export default (epicCreate = false) => { ...@@ -54,7 +54,10 @@ export default (epicCreate = false) => {
store, store,
components: { EpicApp }, components: { EpicApp },
created() { created() {
this.setEpicMeta(epicMeta); this.setEpicMeta({
...epicMeta,
allowSubEpics: parseBoolean(el.dataset.allowSubEpics),
});
this.setEpicData(epicData); this.setEpicData(epicData);
}, },
methods: { methods: {
......
import $ from 'jquery'; import $ from 'jquery';
import { parseBoolean } from '~/lib/utils/common_utils';
import initRelatedItemsTree from 'ee/related_items_tree/related_items_tree_bundle'; import initRelatedItemsTree from 'ee/related_items_tree/related_items_tree_bundle';
import initRoadmap from 'ee/roadmap/roadmap_bundle';
export default class EpicTabs { export default class EpicTabs {
constructor() { constructor() {
...@@ -8,12 +8,31 @@ export default class EpicTabs { ...@@ -8,12 +8,31 @@ export default class EpicTabs {
this.wrapper = document.querySelector('.content-wrapper .container-fluid:not(.breadcrumbs)'); this.wrapper = document.querySelector('.content-wrapper .container-fluid:not(.breadcrumbs)');
this.epicTabs = this.wrapper.querySelector('.js-epic-tabs-container'); this.epicTabs = this.wrapper.querySelector('.js-epic-tabs-container');
this.discussionFilterContainer = this.epicTabs.querySelector('.js-discussion-filter-container'); this.discussionFilterContainer = this.epicTabs.querySelector('.js-discussion-filter-container');
const allowSubEpics = parseBoolean(this.epicTabs.dataset.allowSubEpics);
initRelatedItemsTree(); initRelatedItemsTree();
this.roadmapTabLoaded = false; // We need to execute Roadmap tab related
// logic only when sub-epics feature is available.
if (allowSubEpics) {
this.roadmapTabLoaded = false;
this.bindEvents(); this.loadRoadmapBundle();
this.bindEvents();
}
}
/**
* This method loads Roadmap app bundle asynchronously.
*
* @param {boolean} allowSubEpics
*/
loadRoadmapBundle() {
import('ee/roadmap/roadmap_bundle')
.then(roadmapBundle => {
this.initRoadmap = roadmapBundle.default;
})
.catch(() => {});
} }
bindEvents() { bindEvents() {
...@@ -26,7 +45,7 @@ export default class EpicTabs { ...@@ -26,7 +45,7 @@ export default class EpicTabs {
onRoadmapShow() { onRoadmapShow() {
this.wrapper.classList.remove('container-limited'); this.wrapper.classList.remove('container-limited');
if (!this.roadmapTabLoaded) { if (!this.roadmapTabLoaded) {
initRoadmap(); this.initRoadmap();
this.roadmapTabLoaded = true; this.roadmapTabLoaded = true;
} }
} }
......
...@@ -19,6 +19,7 @@ export default () => ({ ...@@ -19,6 +19,7 @@ export default () => ({
canUpdate: false, canUpdate: false,
canDestroy: false, canDestroy: false,
canAdmin: false, canAdmin: false,
allowSubEpics: false,
// Epic Information // Epic Information
epicId: 0, epicId: 0,
......
...@@ -17,7 +17,7 @@ export default { ...@@ -17,7 +17,7 @@ export default {
EpicActionsSplitButton, EpicActionsSplitButton,
}, },
computed: { computed: {
...mapState(['parentItem', 'descendantCounts']), ...mapState(['parentItem', 'descendantCounts', 'allowSubEpics']),
totalEpicsCount() { totalEpicsCount() {
return this.descendantCounts.openedEpics + this.descendantCounts.closedEpics; return this.descendantCounts.openedEpics + this.descendantCounts.closedEpics;
}, },
...@@ -51,7 +51,7 @@ export default { ...@@ -51,7 +51,7 @@ export default {
<div class="card-header d-flex px-2"> <div class="card-header d-flex px-2">
<div class="d-inline-flex flex-grow-1 lh-100 align-middle"> <div class="d-inline-flex flex-grow-1 lh-100 align-middle">
<gl-tooltip :target="() => $refs.countBadge"> <gl-tooltip :target="() => $refs.countBadge">
<p class="font-weight-bold m-0"> <p v-if="allowSubEpics" class="font-weight-bold m-0">
{{ __('Epics') }} &#8226; {{ __('Epics') }} &#8226;
<span class="text-secondary-400 font-weight-normal" <span class="text-secondary-400 font-weight-normal"
>{{ >{{
...@@ -75,11 +75,11 @@ export default { ...@@ -75,11 +75,11 @@ export default {
</p> </p>
</gl-tooltip> </gl-tooltip>
<div ref="countBadge" class="issue-count-badge"> <div ref="countBadge" class="issue-count-badge">
<span class="d-inline-flex align-items-center"> <span v-if="allowSubEpics" class="d-inline-flex align-items-center">
<icon :size="16" name="epic" class="text-secondary mr-1" /> <icon :size="16" name="epic" class="text-secondary mr-1" />
{{ totalEpicsCount }} {{ totalEpicsCount }}
</span> </span>
<span class="ml-2 d-inline-flex align-items-center"> <span class="d-inline-flex align-items-center" :class="{ 'ml-2': allowSubEpics }">
<icon :size="16" name="issues" class="text-secondary mr-1" /> <icon :size="16" name="issues" class="text-secondary mr-1" />
{{ totalIssuesCount }} {{ totalIssuesCount }}
</span> </span>
...@@ -88,6 +88,7 @@ export default { ...@@ -88,6 +88,7 @@ export default {
<div class="d-inline-flex js-button-container"> <div class="d-inline-flex js-button-container">
<template v-if="parentItem.userPermissions.adminEpic"> <template v-if="parentItem.userPermissions.adminEpic">
<epic-actions-split-button <epic-actions-split-button
v-if="allowSubEpics"
class="qa-add-epics-button" class="qa-add-epics-button"
@showAddEpicForm="showAddEpicForm" @showAddEpicForm="showAddEpicForm"
@showCreateEpicForm="showCreateEpicForm" @showCreateEpicForm="showCreateEpicForm"
......
...@@ -16,7 +16,15 @@ export default () => { ...@@ -16,7 +16,15 @@ export default () => {
return false; return false;
} }
const { id, iid, fullPath, autoCompleteEpics, autoCompleteIssues, userSignedIn } = el.dataset; const {
id,
iid,
fullPath,
autoCompleteEpics,
autoCompleteIssues,
userSignedIn,
allowSubEpics,
} = el.dataset;
const initialData = JSON.parse(el.dataset.initial); const initialData = JSON.parse(el.dataset.initial);
Vue.component('tree-root', TreeRoot); Vue.component('tree-root', TreeRoot);
...@@ -46,6 +54,7 @@ export default () => { ...@@ -46,6 +54,7 @@ export default () => {
autoCompleteEpics: parseBoolean(autoCompleteEpics), autoCompleteEpics: parseBoolean(autoCompleteEpics),
autoCompleteIssues: parseBoolean(autoCompleteIssues), autoCompleteIssues: parseBoolean(autoCompleteIssues),
userSignedIn: parseBoolean(userSignedIn), userSignedIn: parseBoolean(userSignedIn),
allowSubEpics: parseBoolean(allowSubEpics),
}); });
}, },
methods: { methods: {
......
...@@ -12,6 +12,7 @@ export default { ...@@ -12,6 +12,7 @@ export default {
autoCompleteIssues, autoCompleteIssues,
projectsEndpoint, projectsEndpoint,
userSignedIn, userSignedIn,
allowSubEpics,
}, },
) { ) {
state.epicsEndpoint = epicsEndpoint; state.epicsEndpoint = epicsEndpoint;
...@@ -20,6 +21,7 @@ export default { ...@@ -20,6 +21,7 @@ export default {
state.autoCompleteIssues = autoCompleteIssues; state.autoCompleteIssues = autoCompleteIssues;
state.projectsEndpoint = projectsEndpoint; state.projectsEndpoint = projectsEndpoint;
state.userSignedIn = userSignedIn; state.userSignedIn = userSignedIn;
state.allowSubEpics = allowSubEpics;
}, },
[types.SET_INITIAL_PARENT_ITEM](state, data) { [types.SET_INITIAL_PARENT_ITEM](state, data) {
......
...@@ -36,6 +36,7 @@ export default () => ({ ...@@ -36,6 +36,7 @@ export default () => ({
showCreateEpicForm: false, showCreateEpicForm: false,
autoCompleteEpics: false, autoCompleteEpics: false,
autoCompleteIssues: false, autoCompleteIssues: false,
allowSubEpics: false,
removeItemModalProps: { removeItemModalProps: {
parentItem: {}, parentItem: {},
item: {}, item: {},
......
...@@ -3,6 +3,9 @@ ...@@ -3,6 +3,9 @@
- @content_class = "limit-container-width" unless fluid_layout - @content_class = "limit-container-width" unless fluid_layout
- epic_reference = @epic.to_reference - epic_reference = @epic.to_reference
- sub_epics_feature_available = @group.feature_available?(:subepics)
- allow_sub_epics = sub_epics_feature_available ? 'true' : 'false'
- add_to_breadcrumbs _("Epics"), group_epics_path(@group) - add_to_breadcrumbs _("Epics"), group_epics_path(@group)
- breadcrumb_title epic_reference - breadcrumb_title epic_reference
...@@ -11,17 +14,22 @@ ...@@ -11,17 +14,22 @@
- page_card_attributes @epic.card_attributes - page_card_attributes @epic.card_attributes
#epic-app-root{ data: epic_show_app_data(@epic) } #epic-app-root{ data: epic_show_app_data(@epic),
'data-allow-sub-epics' => allow_sub_epics }
.epic-tabs-holder .epic-tabs-holder
.epic-tabs-container.js-epic-tabs-container .epic-tabs-container.js-epic-tabs-container{ data: { allow_sub_epics: allow_sub_epics } }
%ul.epic-tabs.nav-tabs.nav.nav-links.scrolling-tabs %ul.epic-tabs.nav-tabs.nav.nav-links.scrolling-tabs
%li.tree-tab %li.tree-tab
%a#tree-tab.active{ href: '#tree', data: { toggle: 'tab' } } %a#tree-tab.active{ href: '#tree', data: { toggle: 'tab' } }
= _('Epics and Issues') - if sub_epics_feature_available
%li.roadmap-tab = _('Epics and Issues')
%a#roadmap-tab{ href: '#roadmap', data: { toggle: 'tab' } } - else
= _('Roadmap') = _('Issues')
- if sub_epics_feature_available
%li.roadmap-tab
%a#roadmap-tab{ href: '#roadmap', data: { toggle: 'tab' } }
= _('Roadmap')
.tab-content.epic-tabs-content.js-epic-tabs-content .tab-content.epic-tabs-content.js-epic-tabs-content
#tree.tab-pane.show.active #tree.tab-pane.show.active
...@@ -33,22 +41,24 @@ ...@@ -33,22 +41,24 @@
auto_complete_epics: 'true', auto_complete_epics: 'true',
auto_complete_issues: 'true', auto_complete_issues: 'true',
user_signed_in: current_user.present? ? 'true' : 'false', user_signed_in: current_user.present? ? 'true' : 'false',
allow_sub_epics: allow_sub_epics,
initial: issuable_initial_data(@epic).to_json } } initial: issuable_initial_data(@epic).to_json } }
#roadmap.tab-pane - if sub_epics_feature_available
.row #roadmap.tab-pane
%section.col-md-12 .row
#js-roadmap{ data: { epics_path: group_epics_path(@group, parent_id: @epic.id, format: :json), %section.col-md-12
group_id: @group.id, #js-roadmap{ data: { epics_path: group_epics_path(@group, parent_id: @epic.id, format: :json),
iid: @epic.iid, group_id: @group.id,
full_path: @group.full_path, iid: @epic.iid,
empty_state_illustration: image_path('illustrations/epics/roadmap.svg'), full_path: @group.full_path,
has_filters_applied: 'false', empty_state_illustration: image_path('illustrations/epics/roadmap.svg'),
new_epic_endpoint: group_epics_path(@group), has_filters_applied: 'false',
preset_type: roadmap_layout, new_epic_endpoint: group_epics_path(@group),
epics_state: 'all', preset_type: roadmap_layout,
sorted_by: roadmap_sort_order, epics_state: 'all',
inner_height: '600', sorted_by: roadmap_sort_order,
child_epics: 'true' } } inner_height: '600',
child_epics: 'true' } }
%hr.epic-discussion-separator.mt-1.mb-0 %hr.epic-discussion-separator.mt-1.mb-0
.d-flex.justify-content-between.content-block.content-block-small.emoji-list-container.js-noteable-awards .d-flex.justify-content-between.content-block.content-block-small.emoji-list-container.js-noteable-awards
= render 'award_emoji/awards_block', awardable: @epic, inline: true = render 'award_emoji/awards_block', awardable: @epic, inline: true
......
...@@ -5,7 +5,9 @@ require 'spec_helper' ...@@ -5,7 +5,9 @@ require 'spec_helper'
describe 'Epic show', :js do describe 'Epic show', :js do
let(:user) { create(:user, name: 'Rick Sanchez', username: 'rick.sanchez') } let(:user) { create(:user, name: 'Rick Sanchez', username: 'rick.sanchez') }
let(:group) { create(:group, :public) } let(:group) { create(:group, :public) }
let(:public_project) { create(:project, :public, group: group) }
let(:label) { create(:group_label, group: group, title: 'bug') } let(:label) { create(:group_label, group: group, title: 'bug') }
let(:public_issue) { create(:issue, project: public_project) }
let(:note_text) { 'Contemnit enim disserendi elegantiam.' } let(:note_text) { 'Contemnit enim disserendi elegantiam.' }
let(:epic_title) { 'Sample epic' } let(:epic_title) { 'Sample epic' }
...@@ -22,83 +24,116 @@ describe 'Epic show', :js do ...@@ -22,83 +24,116 @@ describe 'Epic show', :js do
let!(:not_child) { create(:epic, group: group, title: 'not child epic', description: markdown, author: user, start_date: 50.days.ago, end_date: 10.days.ago) } let!(:not_child) { create(:epic, group: group, title: 'not child epic', description: markdown, author: user, start_date: 50.days.ago, end_date: 10.days.ago) }
let!(:child_epic_a) { create(:epic, group: group, title: 'Child epic A', description: markdown, parent: epic, start_date: 50.days.ago, end_date: 10.days.ago) } let!(:child_epic_a) { create(:epic, group: group, title: 'Child epic A', description: markdown, parent: epic, start_date: 50.days.ago, end_date: 10.days.ago) }
let!(:child_epic_b) { create(:epic, group: group, title: 'Child epic B', description: markdown, parent: epic, start_date: 100.days.ago, end_date: 20.days.ago) } let!(:child_epic_b) { create(:epic, group: group, title: 'Child epic B', description: markdown, parent: epic, start_date: 100.days.ago, end_date: 20.days.ago) }
let!(:child_issue_a) { create(:epic_issue, epic: epic, issue: public_issue, relative_position: 1) }
before do before do
group.add_developer(user) group.add_developer(user)
stub_licensed_features(epics: true) stub_licensed_features(epics: true, subepics: true)
sign_in(user) sign_in(user)
visit group_epic_path(group, epic) visit group_epic_path(group, epic)
end end
describe 'Epic metadata' do describe 'when sub-epics feature is available' do
it 'shows epic status, date and author in header' do describe 'Epic metadata' do
page.within('.epic-page-container .detail-page-header-body') do it 'shows epic tabs `Epics and Issues` and `Roadmap`' do
expect(find('.issuable-status-box > span')).to have_content('Open') page.within('.js-epic-tabs-container') do
expect(find('.issuable-meta')).to have_content('Opened just now by') expect(find('.epic-tabs #tree-tab')).to have_content('Epics and Issues')
expect(find('.issuable-meta .js-user-avatar-link-username')).to have_content('Rick Sanchez') expect(find('.epic-tabs #roadmap-tab')).to have_content('Roadmap')
end
end end
end end
it 'shows epic title and description' do describe 'Epics and Issues tab' do
page.within('.epic-page-container .detail-page-description') do it 'shows Related items tree with child epics' do
expect(find('.title-container .title')).to have_content(epic_title) page.within('.js-epic-tabs-content #tree') do
expect(find('.description .md')).to have_content(markdown.squish) expect(page).to have_selector('.related-items-tree-container')
end
end
it 'shows epic tabs' do page.within('.related-items-tree-container') do
page.within('.js-epic-tabs-container') do expect(page.find('.issue-count-badge')).to have_content('2')
expect(find('.epic-tabs #tree-tab')).to have_content('Epics and Issues') expect(find('.tree-item:nth-child(1) .sortable-link')).to have_content('Child epic B')
expect(find('.epic-tabs #roadmap-tab')).to have_content('Roadmap') expect(find('.tree-item:nth-child(2) .sortable-link')).to have_content('Child epic A')
end
end
end end
end end
it 'shows epic thread filter dropdown' do describe 'Roadmap tab' do
page.within('.js-noteable-awards') do before do
expect(find('.js-discussion-filter-container #discussion-filter-dropdown')).to have_content('Show all activity') find('.js-epic-tabs-container #roadmap-tab').click
wait_for_requests
end end
end
end
describe 'Epics and Issues tab' do it 'shows Roadmap timeline with child epics' do
it 'shows Related items tree with child epics' do page.within('.js-epic-tabs-content #roadmap') do
page.within('.js-epic-tabs-content #tree') do expect(page).to have_selector('.roadmap-container .roadmap-shell')
expect(page).to have_selector('.related-items-tree-container')
page.within('.related-items-tree-container') do page.within('.roadmap-shell .epics-list-section') do
expect(page.find('.issue-count-badge')).to have_content('2') expect(page).not_to have_content(not_child.title)
expect(find('.tree-item:nth-child(1) .sortable-link')).to have_content('Child epic B') expect(find('.epics-list-item:nth-child(1) .epic-title a')).to have_content('Child epic B')
expect(find('.tree-item:nth-child(2) .sortable-link')).to have_content('Child epic A') expect(find('.epics-list-item:nth-child(2) .epic-title a')).to have_content('Child epic A')
end
end end
end end
it 'does not show thread filter dropdown' do
expect(find('.js-noteable-awards')).to have_selector('.js-discussion-filter-container', visible: false)
end
it 'has no limit on container width' do
expect(find('.content-wrapper .container-fluid:not(.breadcrumbs)')[:class]).not_to include('container-limited')
end
end end
end end
describe 'Roadmap tab' do describe 'when sub-epics feature not is available' do
before do before do
find('.js-epic-tabs-container #roadmap-tab').click stub_licensed_features(epics: true, subepics: false)
wait_for_requests
visit group_epic_path(group, epic)
end end
it 'shows Roadmap timeline with child epics' do describe 'Epic metadata' do
page.within('.js-epic-tabs-content #roadmap') do it 'shows epic tab `Issues`' do
expect(page).to have_selector('.roadmap-container .roadmap-shell') page.within('.js-epic-tabs-container') do
expect(find('.epic-tabs #tree-tab')).to have_content('Issues')
end
end
end
page.within('.roadmap-shell .epics-list-section') do describe 'Issues tab' do
expect(page).not_to have_content(not_child.title) it 'shows Related items tree with child epics' do
expect(find('.epics-list-item:nth-child(1) .epic-title a')).to have_content('Child epic B') page.within('.js-epic-tabs-content #tree') do
expect(find('.epics-list-item:nth-child(2) .epic-title a')).to have_content('Child epic A') expect(page).to have_selector('.related-items-tree-container')
page.within('.related-items-tree-container') do
expect(page.find('.issue-count-badge')).to have_content('1')
end
end end
end end
end end
end
it 'does not show thread filter dropdown' do describe 'Epic metadata' do
expect(find('.js-noteable-awards')).to have_selector('.js-discussion-filter-container', visible: false) it 'shows epic status, date and author in header' do
page.within('.epic-page-container .detail-page-header-body') do
expect(find('.issuable-status-box > span')).to have_content('Open')
expect(find('.issuable-meta')).to have_content('Opened just now by')
expect(find('.issuable-meta .js-user-avatar-link-username')).to have_content('Rick Sanchez')
end
end end
it 'has no limit on container width' do it 'shows epic title and description' do
expect(find('.content-wrapper .container-fluid:not(.breadcrumbs)')[:class]).not_to include('container-limited') page.within('.epic-page-container .detail-page-description') do
expect(find('.title-container .title')).to have_content(epic_title)
expect(find('.description .md')).to have_content(markdown.squish)
end
end
it 'shows epic thread filter dropdown' do
page.within('.js-noteable-awards') do
expect(find('.js-discussion-filter-container #discussion-filter-dropdown')).to have_content('Show all activity')
end
end end
end end
end end
...@@ -203,31 +203,50 @@ describe('EpicSidebarComponent', () => { ...@@ -203,31 +203,50 @@ describe('EpicSidebarComponent', () => {
expect(vm.$el.querySelector('.js-labels-block')).not.toBeNull(); expect(vm.$el.querySelector('.js-labels-block')).not.toBeNull();
}); });
it('renders ancestors list', done => { describe('when sub-epics feature is available', () => {
store.dispatch('toggleSidebarFlag', false); it('renders ancestors list', done => {
store.dispatch('toggleSidebarFlag', false);
store.dispatch('setEpicMeta', {
...mockEpicMeta,
allowSubEpics: false,
});
vm.$nextTick()
.then(() => {
expect(vm.$el.querySelector('.block.ancestors')).toBeNull();
})
.then(done)
.catch(done.fail);
});
});
vm.$nextTick() describe('when sub-epics feature is not available', () => {
.then(() => { it('does not render ancestors list', done => {
const ancestorsEl = vm.$el.querySelector('.block.ancestors'); store.dispatch('toggleSidebarFlag', false);
const reverseAncestors = [...mockAncestors].reverse(); vm.$nextTick()
.then(() => {
const ancestorsEl = vm.$el.querySelector('.block.ancestors');
const getEls = selector => Array.from(ancestorsEl.querySelectorAll(selector)); const reverseAncestors = [...mockAncestors].reverse();
expect(ancestorsEl).not.toBeNull(); const getEls = selector => Array.from(ancestorsEl.querySelectorAll(selector));
expect(getEls('li.vertical-timeline-row').length).toBe(reverseAncestors.length); expect(ancestorsEl).not.toBeNull();
expect(getEls('a').map(el => el.innerText.trim())).toEqual( expect(getEls('li.vertical-timeline-row').length).toBe(reverseAncestors.length);
reverseAncestors.map(a => a.title),
);
expect(getEls('li.vertical-timeline-row a').map(a => a.getAttribute('href'))).toEqual( expect(getEls('a').map(el => el.innerText.trim())).toEqual(
reverseAncestors.map(a => a.url), reverseAncestors.map(a => a.title),
); );
})
.then(done) expect(getEls('li.vertical-timeline-row a').map(a => a.getAttribute('href'))).toEqual(
.catch(done.fail); reverseAncestors.map(a => a.url),
);
})
.then(done)
.catch(done.fail);
});
}); });
it('renders participants list element', () => { it('renders participants list element', () => {
......
...@@ -5,9 +5,12 @@ const metaFixture = getJSONFixture('epic/mock_meta.json'); ...@@ -5,9 +5,12 @@ const metaFixture = getJSONFixture('epic/mock_meta.json');
const meta = JSON.parse(metaFixture.meta); const meta = JSON.parse(metaFixture.meta);
const initial = JSON.parse(metaFixture.initial); const initial = JSON.parse(metaFixture.initial);
export const mockEpicMeta = convertObjectPropsToCamelCase(meta, { export const mockEpicMeta = {
deep: true, ...convertObjectPropsToCamelCase(meta, {
}); deep: true,
}),
allowSubEpics: true,
};
export const mockEpicData = convertObjectPropsToCamelCase( export const mockEpicData = convertObjectPropsToCamelCase(
Object.assign({}, getJSONFixture('epic/mock_data.json'), initial, { Object.assign({}, getJSONFixture('epic/mock_data.json'), initial, {
......
...@@ -9,6 +9,7 @@ import EpicActionsSplitButton from 'ee/related_items_tree/components/epic_action ...@@ -9,6 +9,7 @@ import EpicActionsSplitButton from 'ee/related_items_tree/components/epic_action
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import { import {
mockInitialConfig,
mockParentItem, mockParentItem,
mockQueryResponse, mockQueryResponse,
} from '../../../javascripts/related_items_tree/mock_data'; } from '../../../javascripts/related_items_tree/mock_data';
...@@ -17,6 +18,7 @@ const createComponent = ({ slots } = {}) => { ...@@ -17,6 +18,7 @@ const createComponent = ({ slots } = {}) => {
const store = createDefaultStore(); const store = createDefaultStore();
const children = epicUtils.processQueryResponse(mockQueryResponse.data.group); const children = epicUtils.processQueryResponse(mockQueryResponse.data.group);
store.dispatch('setInitialConfig', mockInitialConfig);
store.dispatch('setInitialParentItem', mockParentItem); store.dispatch('setInitialParentItem', mockParentItem);
store.dispatch('setItemChildren', { store.dispatch('setItemChildren', {
parentItem: mockParentItem, parentItem: mockParentItem,
...@@ -167,13 +169,42 @@ describe('RelatedItemsTree', () => { ...@@ -167,13 +169,42 @@ describe('RelatedItemsTree', () => {
expect(badgesContainerEl.isVisible()).toBe(true); expect(badgesContainerEl.isVisible()).toBe(true);
}); });
it('renders epics count and icon', () => { describe('when sub-epics feature is available', () => {
const epicsEl = wrapper.findAll('.issue-count-badge > span').at(0); it('renders epics count and icon', () => {
const epicIcon = epicsEl.find(Icon); const epicsEl = wrapper.findAll('.issue-count-badge > span').at(0);
const epicIcon = epicsEl.find(Icon);
expect(epicsEl.text().trim()).toBe('2'); expect(epicsEl.text().trim()).toBe('2');
expect(epicIcon.isVisible()).toBe(true); expect(epicIcon.isVisible()).toBe(true);
expect(epicIcon.props('name')).toBe('epic'); expect(epicIcon.props('name')).toBe('epic');
});
it('renders `Add an epic` dropdown button', () => {
expect(findEpicsSplitButton().isVisible()).toBe(true);
});
});
describe('when sub-epics feature is not available', () => {
beforeEach(() => {
wrapper.vm.$store.commit('SET_INITIAL_CONFIG', {
...mockInitialConfig,
allowSubEpics: false,
});
return wrapper.vm.$nextTick();
});
it('does not render epics count and icon', () => {
const countBadgesEl = wrapper.findAll('.issue-count-badge > span');
const badgeIcon = countBadgesEl.at(0).find(Icon);
expect(countBadgesEl.length).toBe(1);
expect(badgeIcon.props('name')).toBe('issues');
});
it('does not render `Add an epic` dropdown button', () => {
expect(findEpicsSplitButton().exists()).toBe(false);
});
}); });
it('renders issues count and icon', () => { it('renders issues count and icon', () => {
...@@ -185,10 +216,6 @@ describe('RelatedItemsTree', () => { ...@@ -185,10 +216,6 @@ describe('RelatedItemsTree', () => {
expect(issueIcon.props('name')).toBe('issues'); expect(issueIcon.props('name')).toBe('issues');
}); });
it('renders `Add an epic` dropdown button', () => {
expect(findEpicsSplitButton().isVisible()).toBe(true);
});
it('renders `Add an issue` dropdown button', () => { it('renders `Add an issue` dropdown button', () => {
const addIssueBtn = findAddIssuesButton(); const addIssueBtn = findAddIssuesButton();
......
...@@ -19,6 +19,7 @@ describe('RelatedItemsTree', () => { ...@@ -19,6 +19,7 @@ describe('RelatedItemsTree', () => {
issuesEndpoint: '/bar', issuesEndpoint: '/bar',
autoCompleteEpics: true, autoCompleteEpics: true,
autoCompleteIssues: false, autoCompleteIssues: false,
allowSubEpics: true,
}; };
mutations[types.SET_INITIAL_CONFIG](state, data); mutations[types.SET_INITIAL_CONFIG](state, data);
...@@ -27,6 +28,7 @@ describe('RelatedItemsTree', () => { ...@@ -27,6 +28,7 @@ describe('RelatedItemsTree', () => {
expect(state).toHaveProperty('issuesEndpoint', '/bar'); expect(state).toHaveProperty('issuesEndpoint', '/bar');
expect(state).toHaveProperty('autoCompleteEpics', true); expect(state).toHaveProperty('autoCompleteEpics', true);
expect(state).toHaveProperty('autoCompleteIssues', false); expect(state).toHaveProperty('autoCompleteIssues', false);
expect(state).toHaveProperty('allowSubEpics', true);
}); });
}); });
......
...@@ -7,6 +7,7 @@ export const mockInitialConfig = { ...@@ -7,6 +7,7 @@ export const mockInitialConfig = {
autoCompleteEpics: true, autoCompleteEpics: true,
autoCompleteIssues: false, autoCompleteIssues: false,
userSignedIn: true, userSignedIn: true,
allowSubEpics: true,
}; };
export const mockParentItem = { export const mockParentItem = {
......
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