Commit 053b6d29 authored by Simon Knox's avatar Simon Knox

Merge branch '355130-consolidate-boards' into 'master'

Consolidate boards into one Vue app

See merge request gitlab-org/gitlab!83359
parents 1ed82e08 b8c60584
......@@ -2,11 +2,13 @@
import { mapActions, mapGetters } from 'vuex';
import BoardContent from '~/boards/components/board_content.vue';
import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue';
import BoardTopBar from '~/boards/components/board_top_bar.vue';
export default {
components: {
BoardContent,
BoardSettingsSidebar,
BoardTopBar,
},
inject: ['disabled'],
computed: {
......@@ -23,6 +25,7 @@ export default {
<template>
<div class="boards-app gl-relative" :class="{ 'is-compact': isSidebarOpen }">
<board-top-bar />
<board-content :disabled="disabled" />
<board-settings-sidebar />
</div>
......
......@@ -48,7 +48,7 @@ export default {
fullPath: {
default: '',
},
rootPath: {
boardBaseUrl: {
default: '',
},
},
......@@ -209,7 +209,7 @@ export default {
if (this.isDeleteForm) {
try {
await this.deleteBoard();
visitUrl(this.rootPath);
visitUrl(this.boardBaseUrl);
} catch {
this.setError({ message: this.$options.i18n.deleteErrorMessage });
} finally {
......
<script>
import { mapGetters } from 'vuex';
import BoardAddNewColumnTrigger from '~/boards/components/board_add_new_column_trigger.vue';
import BoardsSelector from 'ee_else_ce/boards/components/boards_selector.vue';
import IssueBoardFilteredSearch from 'ee_else_ce/boards/components/issue_board_filtered_search.vue';
import ConfigToggle from './config_toggle.vue';
import NewBoardButton from './new_board_button.vue';
import ToggleFocus from './toggle_focus.vue';
export default {
components: {
BoardAddNewColumnTrigger,
BoardsSelector,
IssueBoardFilteredSearch,
ConfigToggle,
NewBoardButton,
ToggleFocus,
ToggleLabels: () => import('ee_component/boards/components/toggle_labels.vue'),
ToggleEpicsSwimlanes: () => import('ee_component/boards/components/toggle_epics_swimlanes.vue'),
EpicBoardFilteredSearch: () =>
import('ee_component/boards/components/epic_filtered_search.vue'),
},
inject: ['swimlanesFeatureAvailable', 'canAdminList', 'isSignedIn'],
computed: {
...mapGetters(['isEpicBoard']),
},
};
</script>
<template>
<div class="issues-filters">
<div
class="issues-details-filters filtered-search-block gl-display-flex gl-flex-direction-column gl-lg-flex-direction-row row-content-block second-block"
>
<div
class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row gl-flex-grow-1 gl-lg-mb-0! mb-md-2 mb-sm-0 gl-w-full"
>
<boards-selector />
<new-board-button />
<epic-board-filtered-search v-if="isEpicBoard" />
<issue-board-filtered-search v-else />
</div>
<div
class="filter-dropdown-container gl-display-flex gl-flex-direction-column gl-md-flex-direction-row gl-align-items-flex-start"
>
<toggle-labels />
<toggle-epics-swimlanes v-if="swimlanesFeatureAvailable && isSignedIn" />
<config-toggle />
<board-add-new-column-trigger v-if="canAdminList" />
<toggle-focus />
</div>
</div>
</div>
</template>
......@@ -40,37 +40,21 @@ export default {
directives: {
GlModalDirective,
},
inject: ['fullPath'],
inject: [
'boardBaseUrl',
'fullPath',
'canAdminBoard',
'multipleIssueBoardsAvailable',
'hasMissingBoards',
'scopedIssueBoardFeatureEnabled',
'weights',
],
props: {
throttleDuration: {
type: Number,
default: 200,
required: false,
},
boardBaseUrl: {
type: String,
required: true,
},
hasMissingBoards: {
type: Boolean,
required: true,
},
canAdminBoard: {
type: Boolean,
required: true,
},
multipleIssueBoardsAvailable: {
type: Boolean,
required: true,
},
scopedIssueBoardFeatureEnabled: {
type: Boolean,
required: true,
},
weights: {
type: Array,
required: true,
},
},
data() {
return {
......
......@@ -14,16 +14,7 @@ export default {
GlModalDirective,
},
mixins: [Tracking.mixin()],
props: {
canAdminList: {
type: Boolean,
required: true,
},
hasScope: {
type: Boolean,
required: true,
},
},
inject: ['canAdminList', 'hasScope'],
computed: {
buttonText() {
return this.canAdminList ? s__('Boards|Edit board') : s__('Boards|View scope');
......
......@@ -41,17 +41,7 @@ export default {
confidential: __('Confidential'),
},
components: { BoardFilteredSearch },
inject: ['isSignedIn', 'releasesFetchPath'],
props: {
fullPath: {
type: String,
required: true,
},
boardType: {
type: String,
required: true,
},
},
inject: ['isSignedIn', 'releasesFetchPath', 'fullPath', 'boardType'],
computed: {
isGroupBoard() {
return this.boardType === BoardType.group;
......
......@@ -10,12 +10,6 @@ export default {
directives: {
GlTooltip,
},
props: {
issueBoardsContentSelector: {
type: String,
required: true,
},
},
data() {
return {
isFullscreen: false,
......@@ -25,7 +19,7 @@ export default {
toggleFocusMode() {
hide(this.$refs.toggleFocusModeButton);
const issueBoardsContent = document.querySelector(this.issueBoardsContentSelector);
const issueBoardsContent = document.querySelector('.content-wrapper > .js-focus-mode-board');
issueBoardsContent.classList.toggle('is-focused');
this.isFullscreen = !this.isFullscreen;
......
import PortalVue from 'portal-vue';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import toggleEpicsSwimlanes from 'ee_else_ce/boards/toggle_epics_swimlanes';
import toggleLabels from 'ee_else_ce/boards/toggle_labels';
import BoardAddNewColumnTrigger from '~/boards/components/board_add_new_column_trigger.vue';
import BoardApp from '~/boards/components/board_app.vue';
import '~/boards/filters/due_date_filters';
import { issuableTypes } from '~/boards/constants';
import initBoardsFilteredSearch from '~/boards/mount_filtered_search_issue_boards';
import store from '~/boards/stores';
import toggleFocusMode from '~/boards/toggle_focus';
import { NavigationType, isLoggedIn, parseBoolean } from '~/lib/utils/common_utils';
import {
NavigationType,
isLoggedIn,
parseBoolean,
convertObjectPropsToCamelCase,
} from '~/lib/utils/common_utils';
import { queryToObject } from '~/lib/utils/url_utility';
import { fullBoardId } from './boards_util';
import boardConfigToggle from './config_toggle';
import initNewBoard from './new_board';
import { gqlClient } from './graphql';
import mountMultipleBoardsSwitcher from './mount_multiple_boards_switcher';
Vue.use(VueApollo);
Vue.use(PortalVue);
......@@ -28,6 +26,12 @@ const apolloProvider = new VueApollo({
function mountBoardApp(el) {
const { boardId, groupId, fullPath, rootPath } = el.dataset;
const rawFilterParams = queryToObject(window.location.search, { gatherArrays: true });
const initialFilterParams = {
...convertObjectPropsToCamelCase(rawFilterParams),
};
store.dispatch('fetchBoard', {
fullPath,
fullBoardId: fullBoardId(boardId),
......@@ -54,26 +58,41 @@ function mountBoardApp(el) {
boardId,
groupId: Number(groupId),
rootPath,
fullPath,
initialFilterParams,
boardBaseUrl: el.dataset.boardBaseUrl,
boardType: el.dataset.parent,
currentUserId: gon.current_user_id || null,
canUpdate: parseBoolean(el.dataset.canUpdate),
canAdminList: parseBoolean(el.dataset.canAdminList),
boardWeight: el.dataset.boardWeight ? parseInt(el.dataset.boardWeight, 10) : null,
labelsManagePath: el.dataset.labelsManagePath,
labelsFilterBasePath: el.dataset.labelsFilterBasePath,
releasesFetchPath: el.dataset.releasesFetchPath,
timeTrackingLimitToHours: parseBoolean(el.dataset.timeTrackingLimitToHours),
issuableType: issuableTypes.issue,
emailsDisabled: parseBoolean(el.dataset.emailsDisabled),
hasScope: parseBoolean(el.dataset.hasScope),
hasMissingBoards: parseBoolean(el.dataset.hasMissingBoards),
weights: el.dataset.weights ? JSON.parse(el.dataset.weights) : [],
// Permissions
canUpdate: parseBoolean(el.dataset.canUpdate),
canAdminList: parseBoolean(el.dataset.canAdminList),
canAdminBoard: parseBoolean(el.dataset.canAdminBoard),
allowLabelCreate: parseBoolean(el.dataset.canUpdate),
allowLabelEdit: parseBoolean(el.dataset.canUpdate),
isSignedIn: isLoggedIn(),
// Features
multipleAssigneesFeatureAvailable: parseBoolean(el.dataset.multipleAssigneesFeatureAvailable),
epicFeatureAvailable: parseBoolean(el.dataset.epicFeatureAvailable),
iterationFeatureAvailable: parseBoolean(el.dataset.iterationFeatureAvailable),
weightFeatureAvailable: parseBoolean(el.dataset.weightFeatureAvailable),
boardWeight: el.dataset.boardWeight ? parseInt(el.dataset.boardWeight, 10) : null,
scopedLabelsAvailable: parseBoolean(el.dataset.scopedLabels),
milestoneListsAvailable: parseBoolean(el.dataset.milestoneListsAvailable),
assigneeListsAvailable: parseBoolean(el.dataset.assigneeListsAvailable),
iterationListsAvailable: parseBoolean(el.dataset.iterationListsAvailable),
issuableType: issuableTypes.issue,
emailsDisabled: parseBoolean(el.dataset.emailsDisabled),
allowLabelCreate: parseBoolean(el.dataset.canUpdate),
allowLabelEdit: parseBoolean(el.dataset.canUpdate),
allowScopedLabels: parseBoolean(el.dataset.scopedLabels),
swimlanesFeatureAvailable: gon.licensed_features?.swimlanes,
multipleIssueBoardsAvailable: parseBoolean(el.dataset.multipleBoardsAvailable),
scopedIssueBoardFeatureEnabled: parseBoolean(el.dataset.scopedIssueBoardFeatureEnabled),
},
render: (createComponent) => createComponent(BoardApp),
});
......@@ -92,47 +111,7 @@ export default () => {
}
});
const { releasesFetchPath, epicFeatureAvailable, iterationFeatureAvailable } = $boardApp.dataset;
initBoardsFilteredSearch(
apolloProvider,
isLoggedIn(),
releasesFetchPath,
parseBoolean(epicFeatureAvailable),
parseBoolean(iterationFeatureAvailable),
);
mountBoardApp($boardApp);
const createColumnTriggerEl = document.querySelector('.js-create-column-trigger');
if (createColumnTriggerEl) {
// eslint-disable-next-line no-new
new Vue({
el: createColumnTriggerEl,
name: 'BoardAddNewColumnTriggerRoot',
components: {
BoardAddNewColumnTrigger,
},
store,
render(createElement) {
return createElement('board-add-new-column-trigger');
},
});
}
boardConfigToggle();
initNewBoard();
toggleFocusMode();
toggleLabels();
if (gon.licensed_features?.swimlanes) {
toggleEpicsSwimlanes();
}
mountMultipleBoardsSwitcher({
fullPath: $boardApp.dataset.fullPath,
rootPath: $boardApp.dataset.boardsEndpoint,
allowScopedLabels: $boardApp.dataset.scopedLabels,
labelsManagePath: $boardApp.dataset.labelsManagePath,
});
};
......@@ -16,6 +16,7 @@ module BoardsHelper
bulk_update_path: @bulk_issues_path,
can_update: can_update?.to_s,
can_admin_list: can_admin_list?.to_s,
can_admin_board: can_admin_board?.to_s,
time_tracking_limit_to_hours: Gitlab::CurrentSettings.time_tracking_limit_to_hours.to_s,
parent: current_board_parent.model_name.param_key,
group_id: group_id,
......@@ -23,7 +24,11 @@ module BoardsHelper
labels_fetch_path: labels_fetch_path,
labels_manage_path: labels_manage_path,
releases_fetch_path: releases_fetch_path,
board_type: board.to_type
board_type: board.to_type,
has_scope: board.scoped?.to_s,
has_missing_boards: has_missing_boards?.to_s,
multiple_boards_available: multiple_boards_available?.to_s,
board_base_url: board_base_url
}
end
......@@ -85,6 +90,11 @@ module BoardsHelper
current_board_parent.multiple_issue_boards_available?
end
# Boards are hidden when extra boards were created but the license does not allow multiple boards
def has_missing_boards?
!multiple_boards_available? && current_board_parent.boards.size > 1
end
def current_board_path(board)
@current_board_path ||= if board.group_board?
group_board_path(current_board_parent, board)
......@@ -109,6 +119,10 @@ module BoardsHelper
can?(current_user, :admin_issue_board_list, current_board_parent)
end
def can_admin_board?
can?(current_user, :admin_issue_board, current_board_parent)
end
def can_admin_issue?
can?(current_user, :admin_issue, current_board_parent)
end
......
......@@ -16,6 +16,4 @@
- page_title("#{board.name}", _("Boards"))
- add_page_specific_style 'page_bundles/boards'
= render 'shared/issuable/search_bar', type: :boards, board: board
#js-issuable-board-app{ data: board_data }
......@@ -16,16 +16,7 @@ export default {
author: __('Author'),
},
components: { BoardFilteredSearch },
props: {
fullPath: {
type: String,
required: true,
},
boardType: {
type: String,
required: true,
},
},
inject: ['fullPath', 'boardType'],
computed: {
tokens() {
const { fetchLabels, fetchAuthors } = issueBoardFilter(
......
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import initFilteredSearch from 'ee/boards/epic_filtered_search';
import { fullEpicBoardId } from 'ee_component/boards/boards_util';
import toggleLabels from 'ee_component/boards/toggle_labels';
import BoardAddNewColumnTrigger from '~/boards/components/board_add_new_column_trigger.vue';
import BoardApp from '~/boards/components/board_app.vue';
import boardConfigToggle from '~/boards/config_toggle';
import { issuableTypes } from '~/boards/constants';
import mountMultipleBoardsSwitcher from '~/boards/mount_multiple_boards_switcher';
import store from '~/boards/stores';
import createDefaultClient from '~/lib/graphql';
import '~/boards/filters/due_date_filters';
import { NavigationType, parseBoolean } from '~/lib/utils/common_utils';
import {
NavigationType,
isLoggedIn,
parseBoolean,
convertObjectPropsToCamelCase,
} from '~/lib/utils/common_utils';
import { queryToObject } from '~/lib/utils/url_utility';
Vue.use(VueApollo);
......@@ -25,6 +26,12 @@ const apolloProvider = new VueApollo({
function mountBoardApp(el) {
const { boardId, groupId, fullPath, rootPath } = el.dataset;
const rawFilterParams = queryToObject(window.location.search, { gatherArrays: true });
const initialFilterParams = {
...convertObjectPropsToCamelCase(rawFilterParams),
};
store.dispatch('setInitialBoardData', {
allowSubEpics: parseBoolean(el.dataset.subEpicsFeatureAvailable),
boardType: el.dataset.parent,
......@@ -46,27 +53,41 @@ function mountBoardApp(el) {
boardId,
groupId: parseInt(groupId, 10),
rootPath,
fullPath,
initialFilterParams,
boardBaseUrl: el.dataset.boardBaseUrl,
boardType: el.dataset.parent,
currentUserId: gon.current_user_id || null,
canUpdate: parseBoolean(el.dataset.canUpdate),
canAdminList: parseBoolean(el.dataset.canAdminList),
labelsFetchPath: el.dataset.labelsFetchPath,
labelsManagePath: el.dataset.labelsManagePath,
labelsFilterBasePath: el.dataset.labelsFilterBasePath,
timeTrackingLimitToHours: parseBoolean(el.dataset.timeTrackingLimitToHours),
boardWeight: el.dataset.boardWeight ? parseInt(el.dataset.boardWeight, 10) : null,
issuableType: issuableTypes.epic,
emailsDisabled: parseBoolean(el.dataset.emailsDisabled),
hasMissingBoards: parseBoolean(el.dataset.hasMissingBoards),
hasScope: parseBoolean(el.dataset.hasScope),
weights: JSON.parse(el.dataset.weights),
// Permissions
canUpdate: parseBoolean(el.dataset.canUpdate),
canAdminList: parseBoolean(el.dataset.canAdminList),
canAdminBoard: parseBoolean(el.dataset.canAdminBoard),
allowLabelCreate: parseBoolean(el.dataset.canUpdate),
allowLabelEdit: parseBoolean(el.dataset.canUpdate),
allowScopedLabels: parseBoolean(el.dataset.scopedLabels),
isSignedIn: isLoggedIn(),
// Features
multipleAssigneesFeatureAvailable: parseBoolean(el.dataset.multipleAssigneesFeatureAvailable),
epicFeatureAvailable: parseBoolean(el.dataset.epicFeatureAvailable),
iterationFeatureAvailable: parseBoolean(el.dataset.iterationFeatureAvailable),
weightFeatureAvailable: parseBoolean(el.dataset.weightFeatureAvailable),
boardWeight: el.dataset.boardWeight ? parseInt(el.dataset.boardWeight, 10) : null,
scopedLabelsAvailable: parseBoolean(el.dataset.scopedLabels),
milestoneListsAvailable: false,
assigneeListsAvailable: false,
iterationListsAvailable: false,
issuableType: issuableTypes.epic,
emailsDisabled: parseBoolean(el.dataset.emailsDisabled),
allowLabelCreate: parseBoolean(el.dataset.canUpdate),
allowLabelEdit: parseBoolean(el.dataset.canUpdate),
allowScopedLabels: parseBoolean(el.dataset.scopedLabels),
swimlanesFeatureAvailable: false,
multipleIssueBoardsAvailable: true,
scopedIssueBoardFeatureEnabled: true,
},
render: (createComponent) => createComponent(BoardApp),
});
......@@ -85,31 +106,5 @@ export default () => {
}
});
initFilteredSearch(apolloProvider);
mountBoardApp($boardApp);
const createColumnTriggerEl = document.querySelector('.js-create-column-trigger');
if (createColumnTriggerEl) {
// eslint-disable-next-line no-new
new Vue({
el: createColumnTriggerEl,
name: 'BoardAddNewColumnTriggerRoot',
components: {
BoardAddNewColumnTrigger,
},
store,
render(createElement) {
return createElement(BoardAddNewColumnTrigger);
},
});
}
toggleLabels();
boardConfigToggle();
mountMultipleBoardsSwitcher({
fullPath: $boardApp.dataset.fullPath,
rootPath: $boardApp.dataset.boardsEndpoint,
});
};
......@@ -29,8 +29,10 @@ module EE
show_promotion: show_feature_promotion,
can_update: can_update?.to_s,
can_admin_list: can_admin_list?.to_s,
can_admin_board: can_admin_board?.to_s,
disabled: board.disabled_for?(current_user).to_s,
emails_disabled: current_board_parent.emails_disabled?.to_s
emails_disabled: current_board_parent.emails_disabled?.to_s,
weights: ::Issue.weight_options
}
super.merge(data).merge(licensed_features).merge(group_level_features)
......@@ -43,7 +45,8 @@ module EE
weight_feature_available: current_board_parent.feature_available?(:issue_weights).to_s,
milestone_lists_available: current_board_parent.feature_available?(:board_milestone_lists).to_s,
assignee_lists_available: current_board_parent.feature_available?(:board_assignee_lists).to_s,
scoped_labels: current_board_parent.feature_available?(:scoped_labels)&.to_s
scoped_labels: current_board_parent.feature_available?(:scoped_labels)&.to_s,
scoped_issue_board_feature_enabled: current_board_parent.feature_available?(:scoped_issue_board).to_s
}
end
......@@ -71,6 +74,13 @@ module EE
super
end
override :can_admin_board?
def can_admin_board?
return can?(current_user, :admin_epic_board, current_board_parent) if board.is_a?(::Boards::EpicBoard)
super
end
override :build_issue_link_base
def build_issue_link_base
return group_epics_path(@group) if board.is_a?(::Boards::EpicBoard)
......
......@@ -220,7 +220,7 @@ module EE
end
def weight_options
[WEIGHT_NONE] + WEIGHT_RANGE.to_a
[WEIGHT_NONE, WEIGHT_ANY] + WEIGHT_RANGE.to_a
end
end
......
......@@ -73,7 +73,7 @@ describe('BoardForm', () => {
wrapper = shallowMountExtended(BoardForm, {
propsData: { ...defaultProps, ...props },
provide: {
rootPath: 'root',
boardBaseUrl: 'root',
glFeatures: { iterationCadences },
},
mocks: {
......
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import ToggleEpicsSwimlanes from 'ee/boards/components/toggle_epics_swimlanes.vue';
import IssueBoardFilteredSearch from 'ee/boards/components/issue_board_filtered_search.vue';
import EpicBoardFilteredSearch from 'ee/boards/components/epic_filtered_search.vue';
import ToggleLabels from 'ee/boards/components/toggle_labels.vue';
import BoardTopBar from '~/boards/components/board_top_bar.vue';
import BoardsSelector from '~/boards/components/boards_selector.vue';
import ConfigToggle from '~/boards/components/config_toggle.vue';
import NewBoardButton from '~/boards/components/new_board_button.vue';
import ToggleFocus from '~/boards/components/toggle_focus.vue';
describe('BoardTopBar', () => {
let wrapper;
Vue.use(Vuex);
const createStore = ({ mockGetters = {} } = {}) => {
return new Vuex.Store({
state: {},
getters: {
isEpicBoard: () => false,
...mockGetters,
},
});
};
const createComponent = ({ provide = {}, mockGetters = {} } = {}) => {
const store = createStore({ mockGetters });
wrapper = shallowMount(BoardTopBar, {
store,
provide: {
swimlanesFeatureAvailable: false,
canAdminList: false,
isSignedIn: false,
fullPath: 'gitlab-org',
boardType: 'group',
releasesFetchPath: '/releases',
epicFeatureAvailable: true,
iterationFeatureAvailable: true,
...provide,
},
stubs: { IssueBoardFilteredSearch, EpicBoardFilteredSearch },
});
};
afterEach(() => {
wrapper.destroy();
});
describe('base template', () => {
beforeEach(() => {
createComponent();
});
it('renders BoardsSelector component', () => {
expect(wrapper.findComponent(BoardsSelector).exists()).toBe(true);
});
it('renders NewBoardButton component', () => {
expect(wrapper.findComponent(NewBoardButton).exists()).toBe(true);
});
it('renders ConfigToggle component', () => {
expect(wrapper.findComponent(ConfigToggle).exists()).toBe(true);
});
it('renders ToggleFocus component', () => {
expect(wrapper.findComponent(ToggleFocus).exists()).toBe(true);
});
it('renders ToggleLabels component', () => {
expect(wrapper.findComponent(ToggleLabels).exists()).toBe(true);
});
it('does not render ToggleEpicsSwimlanes component', () => {
expect(wrapper.findComponent(ToggleEpicsSwimlanes).exists()).toBe(false);
});
});
describe('filter bar', () => {
it.each`
isEpicBoard | filterBarComponent | filterBarName | otherFilterBar
${false} | ${IssueBoardFilteredSearch} | ${'IssueBoardFilteredSearch'} | ${EpicBoardFilteredSearch}
${true} | ${EpicBoardFilteredSearch} | ${'EpicBoardFilteredSearch'} | ${IssueBoardFilteredSearch}
`(
'renders $filterBarName when isEpicBoard is $isEpicBoard',
async ({ isEpicBoard, filterBarComponent, otherFilterBar }) => {
createComponent({ mockGetters: { isEpicBoard: () => isEpicBoard } });
await nextTick();
expect(wrapper.findComponent(filterBarComponent).exists()).toBe(true);
expect(wrapper.findComponent(otherFilterBar).exists()).toBe(false);
},
);
});
describe('when user is logged in and swimlanes are available', () => {
beforeEach(() => {
createComponent({
provide: {
swimlanesFeatureAvailable: true,
isSignedIn: true,
},
});
it('renders ToggleEpicsSwimlanes component', () => {
expect(wrapper.findComponent(ToggleEpicsSwimlanes).exists()).toBe(true);
});
});
});
});
......@@ -80,6 +80,10 @@ describe('BoardsSelector', () => {
apolloProvider: fakeApollo,
propsData: {
throttleDuration,
},
attachTo: document.body,
provide: {
fullPath: '',
boardBaseUrl: `${TEST_HOST}/board/base/url`,
hasMissingBoards: false,
canAdminBoard: true,
......@@ -87,10 +91,6 @@ describe('BoardsSelector', () => {
scopedIssueBoardFeatureEnabled: true,
weights: [],
},
attachTo: document.body,
provide: {
fullPath: '',
},
});
};
......
......@@ -13,11 +13,7 @@ describe('EpicFilteredSearch', () => {
const createComponent = ({ initialFilterParams = {} } = {}) => {
wrapper = shallowMount(EpicFilteredSearch, {
provide: { initialFilterParams },
props: {
fullPath: '',
boardType: '',
},
provide: { initialFilterParams, fullPath: '', boardType: '' },
});
};
......
......@@ -12,10 +12,11 @@ describe('IssueBoardFilter', () => {
const createComponent = () => {
wrapper = shallowMount(IssueBoardFilteredSpec, {
propsData: { fullPath: 'gitlab-org', boardType: 'group' },
provide: {
isSignedIn: true,
releasesFetchPath: '/releases',
fullPath: 'gitlab-org',
boardType: 'group',
epicFeatureAvailable: true,
iterationFeatureAvailable: true,
},
......
......@@ -74,6 +74,7 @@ RSpec.describe BoardsHelper do
allow(helper).to receive(:can?).with(user, :create_non_backlog_issues, project_board).and_return(true)
allow(helper).to receive(:can?).with(user, :admin_issue, project_board).and_return(true)
allow(helper).to receive(:can?).with(user, :admin_issue_board_list, project).and_return(false)
allow(helper).to receive(:can?).with(user, :admin_issue_board, project).and_return(false)
end
shared_examples 'serializes the availability of a licensed feature' do |feature_name, feature_key|
......@@ -123,7 +124,8 @@ RSpec.describe BoardsHelper do
[:issue_weights, :weight_feature_available],
[:board_milestone_lists, :milestone_lists_available],
[:board_assignee_lists, :assignee_lists_available],
[:scoped_labels, :scoped_labels]].each do |feature_name, feature_key|
[:scoped_labels, :scoped_labels],
[:scoped_issue_board, :scoped_issue_board_feature_enabled]].each do |feature_name, feature_key|
include_examples "serializes the availability of a licensed feature", feature_name, feature_key
end
end
......@@ -148,9 +150,11 @@ RSpec.describe BoardsHelper do
allow(helper).to receive(:can?).with(user, :create_non_backlog_issues, epic_board).and_return(false)
allow(helper).to receive(:can?).with(user, :admin_epic, epic_board).and_return(true)
allow(helper).to receive(:can?).with(user, :admin_epic_board_list, group).and_return(true)
allow(helper).to receive(:can?).with(user, :admin_epic_board, group).and_return(true)
allow(helper).to receive(:can?).with(user, :admin_issue, group).and_return(false)
allow(helper).to receive(:can?).with(user, :admin_issue_board_list, group).and_return(false)
allow(helper).to receive(:can?).with(user, :admin_issue_board, group).and_return(false)
end
it 'returns the correct permission for updating the board' do
......@@ -160,6 +164,10 @@ RSpec.describe BoardsHelper do
it 'returns the correct permission for administering the boards lists' do
expect(board_data[:can_admin_list]).to eq "true"
end
it 'returns the correct permission for administering the boards' do
expect(board_data[:can_admin_board]).to eq "true"
end
end
end
end
......@@ -62,7 +62,7 @@ describe('BoardForm', () => {
};
},
provide: {
rootPath: 'root',
boardBaseUrl: 'root',
},
mocks: {
$apollo: {
......
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import BoardTopBar from '~/boards/components/board_top_bar.vue';
import BoardAddNewColumnTrigger from '~/boards/components/board_add_new_column_trigger.vue';
import BoardsSelector from '~/boards/components/boards_selector.vue';
import ConfigToggle from '~/boards/components/config_toggle.vue';
import IssueBoardFilteredSearch from '~/boards/components/issue_board_filtered_search.vue';
import NewBoardButton from '~/boards/components/new_board_button.vue';
import ToggleFocus from '~/boards/components/toggle_focus.vue';
describe('BoardTopBar', () => {
let wrapper;
Vue.use(Vuex);
const createStore = ({ mockGetters = {} } = {}) => {
return new Vuex.Store({
state: {},
getters: {
isEpicBoard: () => false,
...mockGetters,
},
});
};
const createComponent = ({ provide = {}, mockGetters = {} } = {}) => {
const store = createStore({ mockGetters });
wrapper = shallowMount(BoardTopBar, {
store,
provide: {
swimlanesFeatureAvailable: false,
canAdminList: false,
isSignedIn: false,
fullPath: 'gitlab-org',
boardType: 'group',
releasesFetchPath: '/releases',
...provide,
},
stubs: { IssueBoardFilteredSearch },
});
};
afterEach(() => {
wrapper.destroy();
});
describe('base template', () => {
beforeEach(() => {
createComponent();
});
it('renders BoardsSelector component', () => {
expect(wrapper.findComponent(BoardsSelector).exists()).toBe(true);
});
it('renders IssueBoardFilteredSearch component', () => {
expect(wrapper.findComponent(IssueBoardFilteredSearch).exists()).toBe(true);
});
it('renders NewBoardButton component', () => {
expect(wrapper.findComponent(NewBoardButton).exists()).toBe(true);
});
it('renders ConfigToggle component', () => {
expect(wrapper.findComponent(ConfigToggle).exists()).toBe(true);
});
it('renders ToggleFocus component', () => {
expect(wrapper.findComponent(ToggleFocus).exists()).toBe(true);
});
it('does not render BoardAddNewColumnTrigger component', () => {
expect(wrapper.findComponent(BoardAddNewColumnTrigger).exists()).toBe(false);
});
});
describe('when user can admin list', () => {
beforeEach(() => {
createComponent({ provide: { canAdminList: true } });
});
it('renders BoardAddNewColumnTrigger component', () => {
expect(wrapper.findComponent(BoardAddNewColumnTrigger).exists()).toBe(true);
});
});
});
......@@ -105,6 +105,10 @@ describe('BoardsSelector', () => {
apolloProvider: fakeApollo,
propsData: {
throttleDuration,
},
attachTo: document.body,
provide: {
fullPath: '',
boardBaseUrl: `${TEST_HOST}/board/base/url`,
hasMissingBoards: false,
canAdminBoard: true,
......@@ -112,10 +116,6 @@ describe('BoardsSelector', () => {
scopedIssueBoardFeatureEnabled: true,
weights: [],
},
attachTo: document.body,
provide: {
fullPath: '',
},
});
};
......
......@@ -14,10 +14,11 @@ describe('IssueBoardFilter', () => {
const createComponent = ({ isSignedIn = false } = {}) => {
wrapper = shallowMount(IssueBoardFilteredSpec, {
propsData: { fullPath: 'gitlab-org', boardType: 'group' },
provide: {
isSignedIn,
releasesFetchPath: '/releases',
fullPath: 'gitlab-org',
boardType: 'group',
},
});
};
......
......@@ -102,6 +102,7 @@ RSpec.describe BoardsHelper do
allow(helper).to receive(:can?).with(user, :create_non_backlog_issues, project_board).and_return(true)
allow(helper).to receive(:can?).with(user, :admin_issue, project_board).and_return(true)
allow(helper).to receive(:can?).with(user, :admin_issue_board_list, project).and_return(false)
allow(helper).to receive(:can?).with(user, :admin_issue_board, project).and_return(false)
end
it 'returns a board_lists_path as lists_endpoint' do
......@@ -129,12 +130,23 @@ RSpec.describe BoardsHelper do
it 'returns can_admin_list as false by default' do
expect(helper.board_data[:can_admin_list]).to eq('false')
end
it 'returns can_admin_list as true when user can admin the board' do
it 'returns can_admin_list as true when user can admin the board lists' do
allow(helper).to receive(:can?).with(user, :admin_issue_board_list, project).and_return(true)
expect(helper.board_data[:can_admin_list]).to eq('true')
end
end
context 'can_admin_board' do
it 'returns can_admin_board as false by default' do
expect(helper.board_data[:can_admin_board]).to eq('false')
end
it 'returns can_admin_board as true when user can admin the board' do
allow(helper).to receive(:can?).with(user, :admin_issue_board, project).and_return(true)
expect(helper.board_data[:can_admin_board]).to eq('true')
end
end
end
context 'group board' do
......@@ -146,6 +158,7 @@ RSpec.describe BoardsHelper do
allow(helper).to receive(:can?).with(user, :create_non_backlog_issues, group_board).and_return(true)
allow(helper).to receive(:can?).with(user, :admin_issue, group_board).and_return(true)
allow(helper).to receive(:can?).with(user, :admin_issue_board_list, base_group).and_return(false)
allow(helper).to receive(:can?).with(user, :admin_issue_board, base_group).and_return(false)
end
it 'returns correct path for base group' do
......@@ -165,7 +178,7 @@ RSpec.describe BoardsHelper do
it 'returns can_admin_list as false by default' do
expect(helper.board_data[:can_admin_list]).to eq('false')
end
it 'returns can_admin_list as true when user can admin the board' do
it 'returns can_admin_list as true when user can admin the board lists' do
allow(helper).to receive(:can?).with(user, :admin_issue_board_list, base_group).and_return(true)
expect(helper.board_data[:can_admin_list]).to eq('true')
......
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