Commit 9cba2ad9 authored by Simon Knox's avatar Simon Knox Committed by Kushal Pandya

Use vuex and draggable for moving board lists

Still needs some specs for start/end behaviour
and maybe some kind of integration ones would be worthwhile
rather than testing draggable internals
parent 99382092
<script>
import Draggable from 'vuedraggable';
import { mapState, mapGetters, mapActions } from 'vuex';
import { sortBy } from 'lodash';
import { GlAlert } from '@gitlab/ui';
import BoardColumn from 'ee_else_ce/boards/components/board_column.vue';
import BoardColumnNew from './board_column_new.vue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import defaultSortableConfig from '~/sortable/sortable_config';
import { sortableEnd, sortableStart } from '~/boards/mixins/sortable_default_options';
export default {
components: {
......@@ -36,6 +39,25 @@ export default {
? sortBy([...Object.values(this.boardLists)], 'position')
: this.lists;
},
canDragColumns() {
return this.glFeatures.graphqlBoardLists && this.canAdminList;
},
boardColumnWrapper() {
return this.canDragColumns ? Draggable : 'div';
},
draggableOptions() {
const options = {
...defaultSortableConfig,
disabled: this.disabled,
draggable: '.is-draggable',
fallbackOnBody: false,
group: 'boards-list',
tag: 'div',
value: this.lists,
};
return this.canDragColumns ? options : {};
},
},
mounted() {
if (this.glFeatures.graphqlBoardLists) {
......@@ -43,7 +65,26 @@ export default {
}
},
methods: {
...mapActions(['showPromotionList']),
...mapActions(['moveList', 'showPromotionList']),
handleDragOnStart() {
sortableStart();
},
handleDragOnEnd(params) {
sortableEnd();
const { item, newIndex, oldIndex, to } = params;
const listId = item.dataset.id;
const replacedListId = to.children[newIndex].dataset.id;
this.moveList({
listId,
replacedListId,
newIndex,
adjustmentValue: newIndex < oldIndex ? 1 : -1,
});
},
},
};
</script>
......@@ -53,16 +94,28 @@ export default {
<gl-alert v-if="error" variant="danger" :dismissible="false">
{{ error }}
</gl-alert>
<div v-if="!isSwimlanesOn" class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap">
<component
:is="boardColumnWrapper"
v-if="!isSwimlanesOn"
ref="list"
v-bind="draggableOptions"
class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap"
data-qa-selector="boards_list"
@start="handleDragOnStart"
@end="handleDragOnEnd"
>
<board-column
v-for="list in boardListsToUse"
:key="list.id"
ref="board"
:can-admin-list="canAdminList"
:class="{
'is-draggable': !list.preset,
}"
:list="list"
:disabled="disabled"
/>
</div>
</component>
<template v-else>
<epics-swimlanes
......
......@@ -180,6 +180,10 @@ export default {
{ state, commit, dispatch },
{ listId, replacedListId, newIndex, adjustmentValue },
) => {
if (listId === replacedListId) {
return;
}
const { boardLists } = state;
const backupList = { ...boardLists };
const movedList = boardLists[listId];
......
import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlAlert } from '@gitlab/ui';
import Draggable from 'vuedraggable';
import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue';
import BoardColumn from 'ee_else_ce/boards/components/board_column.vue';
import getters from 'ee_else_ce/boards/stores/getters';
......@@ -10,6 +11,11 @@ import BoardContent from '~/boards/components/board_content.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
const actions = {
moveList: jest.fn(),
showPromotionList: jest.fn(),
};
describe('BoardContent', () => {
let wrapper;
......@@ -21,12 +27,13 @@ describe('BoardContent', () => {
const createStore = (state = defaultState) => {
return new Vuex.Store({
actions,
getters,
state,
});
};
const createComponent = state => {
const createComponent = ({ state, props = {}, graphqlBoardListsEnabled = false } = {}) => {
const store = createStore({
...defaultState,
...state,
......@@ -37,25 +44,61 @@ describe('BoardContent', () => {
lists: mockListsWithModel,
canAdminList: true,
disabled: false,
...props,
},
provide: {
glFeatures: { graphqlBoardLists: graphqlBoardListsEnabled },
},
store,
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('renders a BoardColumn component per list', () => {
createComponent();
expect(wrapper.findAll(BoardColumn)).toHaveLength(mockListsWithModel.length);
});
it('does not display EpicsSwimlanes component', () => {
createComponent();
expect(wrapper.find(EpicsSwimlanes).exists()).toBe(false);
expect(wrapper.find(GlAlert).exists()).toBe(false);
});
describe('graphqlBoardLists feature flag enabled', () => {
describe('can admin list', () => {
beforeEach(() => {
createComponent({ graphqlBoardListsEnabled: true, props: { canAdminList: true } });
});
it('renders draggable component', () => {
expect(wrapper.find(Draggable).exists()).toBe(true);
});
});
describe('can not admin list', () => {
beforeEach(() => {
createComponent({ graphqlBoardListsEnabled: true, props: { canAdminList: false } });
});
it('renders draggable component', () => {
expect(wrapper.find(Draggable).exists()).toBe(false);
});
});
});
describe('graphqlBoardLists feature flag disabled', () => {
beforeEach(() => {
createComponent({ graphqlBoardListsEnabled: false });
});
it('does not render draggable component', () => {
expect(wrapper.find(Draggable).exists()).toBe(false);
});
});
});
......@@ -290,6 +290,33 @@ describe('moveList', () => {
done,
);
});
it('should not commit MOVE_LIST or dispatch updateList if listId and replacedListId are the same', () => {
const initialBoardListsState = {
'gid://gitlab/List/1': mockListsWithModel[0],
'gid://gitlab/List/2': mockListsWithModel[1],
};
const state = {
endpoints: { fullPath: 'gitlab-org', boardId: '1' },
boardType: 'group',
disabled: false,
boardLists: initialBoardListsState,
};
testAction(
actions.moveList,
{
listId: 'gid://gitlab/List/1',
replacedListId: 'gid://gitlab/List/1',
newIndex: 1,
adjustmentValue: 1,
},
state,
[],
[],
);
});
});
describe('updateList', () => {
......
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