Commit a249542e authored by Florie Guibert's avatar Florie Guibert

Swimlanes - Reorder lists

Use drag & drop to reorder lists on board swimlanes
parent b221119c
<script> <script>
import { mapActions } from 'vuex';
import { import {
GlButton, GlButton,
GlButtonGroup, GlButtonGroup,
...@@ -17,6 +18,7 @@ import boardsStore from '../stores/boards_store'; ...@@ -17,6 +18,7 @@ import boardsStore from '../stores/boards_store';
import eventHub from '../eventhub'; import eventHub from '../eventhub';
import { ListType } from '../constants'; import { ListType } from '../constants';
import { isScopedLabel } from '~/lib/utils/common_utils'; import { isScopedLabel } from '~/lib/utils/common_utils';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default { export default {
components: { components: {
...@@ -32,7 +34,7 @@ export default { ...@@ -32,7 +34,7 @@ export default {
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
mixins: [isWipLimitsOn], mixins: [isWipLimitsOn, glFeatureFlagMixin()],
props: { props: {
list: { list: {
type: Object, type: Object,
...@@ -128,6 +130,7 @@ export default { ...@@ -128,6 +130,7 @@ export default {
}, },
}, },
methods: { methods: {
...mapActions(['updateList']),
showScopedLabels(label) { showScopedLabels(label) {
return boardsStore.scopedLabels.enabled && isScopedLabel(label); return boardsStore.scopedLabels.enabled && isScopedLabel(label);
}, },
...@@ -144,7 +147,11 @@ export default { ...@@ -144,7 +147,11 @@ export default {
} }
if (this.isLoggedIn) { if (this.isLoggedIn) {
this.list.update(); if (this.glFeatures.boardsWithSwimlanes && this.isSwimlanesHeader) {
this.updateList({ listId: this.list.id, collapsed: !this.list.isExpanded });
} else {
this.list.update();
}
} }
// When expanding/collapsing, the tooltip on the caret button sometimes stays open. // When expanding/collapsing, the tooltip on the caret button sometimes stays open.
......
...@@ -47,7 +47,7 @@ class List { ...@@ -47,7 +47,7 @@ class List {
this.loading = true; this.loading = true;
this.loadingMore = false; this.loadingMore = false;
this.issues = obj.issues || []; this.issues = obj.issues || [];
this.issuesSize = obj.issuesSize ? obj.issuesSize : 0; this.issuesSize = obj.issuesSize || obj.issuesCount || 0;
this.maxIssueCount = obj.maxIssueCount || obj.max_issue_count || 0; this.maxIssueCount = obj.maxIssueCount || obj.max_issue_count || 0;
if (obj.label) { if (obj.label) {
......
...@@ -4,6 +4,7 @@ fragment BoardListShared on BoardList { ...@@ -4,6 +4,7 @@ fragment BoardListShared on BoardList {
position position
listType listType
collapsed collapsed
issuesCount
label { label {
id id
title title
......
#import "./board_list.fragment.graphql"
mutation UpdateBoardList($listId: ID!, $position: Int, $collapsed: Boolean) {
updateBoardList(input: { listId: $listId, position: $position, collapsed: $collapsed }) {
list {
...BoardListFragment
}
errors
}
}
...@@ -15,6 +15,7 @@ import projectListsIssuesQuery from '../queries/project_lists_issues.query.graph ...@@ -15,6 +15,7 @@ import projectListsIssuesQuery from '../queries/project_lists_issues.query.graph
import projectBoardQuery from '../queries/project_board.query.graphql'; import projectBoardQuery from '../queries/project_board.query.graphql';
import groupBoardQuery from '../queries/group_board.query.graphql'; import groupBoardQuery from '../queries/group_board.query.graphql';
import createBoardListMutation from '../queries/board_list_create.mutation.graphql'; import createBoardListMutation from '../queries/board_list_create.mutation.graphql';
import updateBoardListMutation from '../queries/board_list_update.mutation.graphql';
const notImplemented = () => { const notImplemented = () => {
/* eslint-disable-next-line @gitlab/require-i18n-strings */ /* eslint-disable-next-line @gitlab/require-i18n-strings */
...@@ -147,8 +148,40 @@ export default { ...@@ -147,8 +148,40 @@ export default {
notImplemented(); notImplemented();
}, },
updateList: () => { moveList: ({ state, commit, dispatch }, { listId, position, moveNewIndexListUp }) => {
notImplemented(); const { boardLists } = state;
const movedList = boardLists.find(({ id }) => id === listId);
const listAtNewIndex = boardLists[position + 1];
movedList.position = position;
listAtNewIndex.position = moveNewIndexListUp
? listAtNewIndex.position + 1
: listAtNewIndex.position - 1;
commit(types.MOVE_LIST, {
movedList,
listAtNewIndex,
});
dispatch('updateList', { listId, position });
},
updateList: ({ commit }, { listId, position, collapsed }) => {
gqlClient
.mutate({
mutation: updateBoardListMutation,
variables: {
listId,
position,
collapsed,
},
})
.then(({ data }) => {
if (data?.updateBoardList?.errors.length) {
commit(types.UPDATE_LIST_FAILURE);
}
})
.catch(() => {
commit(types.UPDATE_LIST_FAILURE);
});
}, },
deleteList: () => { deleteList: () => {
......
...@@ -858,21 +858,6 @@ const boardsStore = { ...@@ -858,21 +858,6 @@ const boardsStore = {
}, },
refreshIssueData(issue, obj) { refreshIssueData(issue, obj) {
// issue.id = obj.id;
// issue.iid = obj.iid;
// issue.title = obj.title;
// issue.confidential = obj.confidential;
// issue.dueDate = obj.due_date || obj.dueDate;
// issue.sidebarInfoEndpoint = obj.issue_sidebar_endpoint;
// issue.referencePath = obj.reference_path || obj.referencePath;
// issue.path = obj.real_path || obj.webUrl;
// issue.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
// issue.project_id = obj.project_id;
// issue.timeEstimate = obj.time_estimate || obj.timeEstimate;
// issue.assignableLabelsEndpoint = obj.assignable_labels_endpoint;
// issue.blocked = obj.blocked;
// issue.epic = obj.epic;
const convertedObj = convertObjectPropsToCamelCase(obj, { const convertedObj = convertObjectPropsToCamelCase(obj, {
dropKeys: ['issue_sidebar_endpoint', 'real_path', 'webUrl'], dropKeys: ['issue_sidebar_endpoint', 'real_path', 'webUrl'],
}); });
......
...@@ -7,9 +7,8 @@ export const SHOW_PROMOTION_LIST = 'SHOW_PROMOTION_LIST'; ...@@ -7,9 +7,8 @@ export const SHOW_PROMOTION_LIST = 'SHOW_PROMOTION_LIST';
export const REQUEST_ADD_LIST = 'REQUEST_ADD_LIST'; export const REQUEST_ADD_LIST = 'REQUEST_ADD_LIST';
export const RECEIVE_ADD_LIST_SUCCESS = 'RECEIVE_ADD_LIST_SUCCESS'; export const RECEIVE_ADD_LIST_SUCCESS = 'RECEIVE_ADD_LIST_SUCCESS';
export const RECEIVE_ADD_LIST_ERROR = 'RECEIVE_ADD_LIST_ERROR'; export const RECEIVE_ADD_LIST_ERROR = 'RECEIVE_ADD_LIST_ERROR';
export const REQUEST_UPDATE_LIST = 'REQUEST_UPDATE_LIST'; export const MOVE_LIST = 'MOVE_LIST';
export const RECEIVE_UPDATE_LIST_SUCCESS = 'RECEIVE_UPDATE_LIST_SUCCESS'; export const UPDATE_LIST_FAILURE = 'UPDATE_LIST_FAILURE';
export const RECEIVE_UPDATE_LIST_ERROR = 'RECEIVE_UPDATE_LIST_ERROR';
export const REQUEST_REMOVE_LIST = 'REQUEST_REMOVE_LIST'; export const REQUEST_REMOVE_LIST = 'REQUEST_REMOVE_LIST';
export const RECEIVE_REMOVE_LIST_SUCCESS = 'RECEIVE_REMOVE_LIST_SUCCESS'; export const RECEIVE_REMOVE_LIST_SUCCESS = 'RECEIVE_REMOVE_LIST_SUCCESS';
export const RECEIVE_REMOVE_LIST_ERROR = 'RECEIVE_REMOVE_LIST_ERROR'; export const RECEIVE_REMOVE_LIST_ERROR = 'RECEIVE_REMOVE_LIST_ERROR';
......
import Vue from 'vue';
import { sortBy } from 'lodash';
import * as mutationTypes from './mutation_types'; import * as mutationTypes from './mutation_types';
import { __ } from '~/locale'; import { __ } from '~/locale';
...@@ -44,16 +46,18 @@ export default { ...@@ -44,16 +46,18 @@ export default {
notImplemented(); notImplemented();
}, },
[mutationTypes.REQUEST_UPDATE_LIST]: () => { [mutationTypes.MOVE_LIST]: (state, { movedList, listAtNewIndex }) => {
notImplemented(); const { boardLists } = state;
state.boardListsPreviousState = boardLists;
const movedListIndex = state.boardLists.findIndex(l => l.id === movedList.id);
Vue.set(boardLists, movedListIndex, movedList);
Vue.set(boardLists, movedListIndex.position + 1, listAtNewIndex);
state.boardLists = sortBy(boardLists, 'position');
}, },
[mutationTypes.RECEIVE_UPDATE_LIST_SUCCESS]: () => { [mutationTypes.UPDATE_LIST_FAILURE]: state => {
notImplemented(); state.boardLists = state.boardListsPreviousState;
}, state.error = __('An error occurred while updating the list. Please try again.');
[mutationTypes.RECEIVE_UPDATE_LIST_ERROR]: () => {
notImplemented();
}, },
[mutationTypes.REQUEST_REMOVE_LIST]: () => { [mutationTypes.REQUEST_REMOVE_LIST]: () => {
......
...@@ -9,6 +9,7 @@ export default () => ({ ...@@ -9,6 +9,7 @@ export default () => ({
activeId: inactiveId, activeId: inactiveId,
sidebarType: '', sidebarType: '',
boardLists: [], boardLists: [],
boardListsPreviousState: [],
issuesByListId: {}, issuesByListId: {},
issues: {}, issues: {},
isLoadingIssues: false, isLoadingIssues: false,
......
<script> <script>
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import Draggable from 'vuedraggable';
import BoardListHeader from 'ee_else_ce/boards/components/board_list_header.vue'; import BoardListHeader from 'ee_else_ce/boards/components/board_list_header.vue';
import { draggableTag } from '../constants';
import defaultSortableConfig from '~/sortable/sortable_config';
import { n__ } from '~/locale'; import { n__ } from '~/locale';
import EpicLane from './epic_lane.vue'; import EpicLane from './epic_lane.vue';
import IssuesLaneList from './issues_lane_list.vue'; import IssuesLaneList from './issues_lane_list.vue';
...@@ -53,12 +56,44 @@ export default { ...@@ -53,12 +56,44 @@ export default {
unassignedIssuesCountTooltipText() { unassignedIssuesCountTooltipText() {
return n__(`%d unassigned issue`, `%d unassigned issues`, this.unassignedIssuesCount); return n__(`%d unassigned issue`, `%d unassigned issues`, this.unassignedIssuesCount);
}, },
treeRootWrapper() {
return this.canAdminList ? Draggable : draggableTag;
},
treeRootOptions() {
const options = {
...defaultSortableConfig,
fallbackOnBody: false,
group: 'board-swimlanes',
tag: draggableTag,
draggable: '.is-draggable',
'ghost-class': 'swimlane-header-drag-active',
value: this.lists,
};
return this.canAdminList ? options : {};
},
}, },
mounted() { mounted() {
this.fetchIssuesForAllLists(); this.fetchIssuesForAllLists();
}, },
methods: { methods: {
...mapActions(['fetchIssuesForAllLists']), ...mapActions(['fetchIssuesForAllLists', 'moveList']),
handleDragOnEnd(params) {
const { newIndex, oldIndex, item } = params;
const { listId } = item.dataset;
const newPosition = newIndex - 1;
let moveNewIndexListUp = false;
if (newIndex < oldIndex) {
moveNewIndexListUp = true;
}
this.moveList({
listId,
position: newPosition,
moveNewIndexListUp,
});
},
}, },
}; };
</script> </script>
...@@ -68,16 +103,22 @@ export default { ...@@ -68,16 +103,22 @@ export default {
class="board-swimlanes gl-white-space-nowrap gl-pb-5 gl-px-3" class="board-swimlanes gl-white-space-nowrap gl-pb-5 gl-px-3"
data_qa_selector="board_epics_swimlanes" data_qa_selector="board_epics_swimlanes"
> >
<div <component
:is="treeRootWrapper"
v-bind="treeRootOptions"
class="board-swimlanes-headers gl-display-table gl-sticky gl-pt-5 gl-bg-white gl-top-0 gl-z-index-3" class="board-swimlanes-headers gl-display-table gl-sticky gl-pt-5 gl-bg-white gl-top-0 gl-z-index-3"
@end="handleDragOnEnd"
> >
<div <div
v-for="list in lists" v-for="list in lists"
:key="list.id" :key="list.id"
:class="{ :class="{
'is-collapsed': !list.isExpanded, 'is-collapsed': !list.isExpanded,
'is-draggable': !list.preset,
}" }"
class="board gl-px-3 gl-vertical-align-top gl-white-space-normal" class="board gl-px-3 gl-vertical-align-top gl-white-space-normal"
:data-list-id="list.id"
data-testid="board-header-container"
> >
<board-list-header <board-list-header
:can-admin-list="canAdminList" :can-admin-list="canAdminList"
...@@ -87,7 +128,7 @@ export default { ...@@ -87,7 +128,7 @@ export default {
:is-swimlanes-header="true" :is-swimlanes-header="true"
/> />
</div> </div>
</div> </component>
<div class="board-epics-swimlanes gl-display-table"> <div class="board-epics-swimlanes gl-display-table">
<epic-lane <epic-lane
v-for="epic in epics" v-for="epic in epics"
......
export const draggableTag = 'div';
export default {
draggableTag,
};
...@@ -14,7 +14,7 @@ const EE_TYPES = { ...@@ -14,7 +14,7 @@ const EE_TYPES = {
class ListEE extends List { class ListEE extends List {
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.totalWeight = 0; this.totalWeight = args[0]?.totalWeight || 0;
} }
getTypeInfo(type) { getTypeInfo(type) {
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
fragment BoardListFragment on BoardList { fragment BoardListFragment on BoardList {
...BoardListShared ...BoardListShared
maxIssueCount maxIssueCount
totalWeight
assignee { assignee {
id id
name name
......
import Vuex from 'vuex'; import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import Draggable from 'vuedraggable';
import EpicsSwimlanes from 'ee/boards/components/epics_swimlanes.vue'; import EpicsSwimlanes from 'ee/boards/components/epics_swimlanes.vue';
import BoardListHeader from 'ee_else_ce/boards/components/board_list_header.vue'; import BoardListHeader from 'ee_else_ce/boards/components/board_list_header.vue';
import EpicLane from 'ee/boards/components/epic_lane.vue'; import EpicLane from 'ee/boards/components/epic_lane.vue';
import IssueLaneList from 'ee/boards/components/issues_lane_list.vue'; import IssueLaneList from 'ee/boards/components/issues_lane_list.vue';
import getters from 'ee/boards/stores/getters'; import getters from 'ee/boards/stores/getters';
import { draggableTag } from 'ee/boards/constants';
import { GlIcon } from '@gitlab/ui'; import { GlIcon } from '@gitlab/ui';
import { mockListsWithModel, mockEpics, mockIssuesByListId, issues } from '../mock_data'; import { mockListsWithModel, mockEpics, mockIssuesByListId, issues } from '../mock_data';
...@@ -29,7 +31,7 @@ describe('EpicsSwimlanes', () => { ...@@ -29,7 +31,7 @@ describe('EpicsSwimlanes', () => {
}); });
}; };
const createComponent = () => { const createComponent = (props = {}) => {
const store = createStore(); const store = createStore();
const defaultProps = { const defaultProps = {
lists: mockListsWithModel, lists: mockListsWithModel,
...@@ -40,7 +42,7 @@ describe('EpicsSwimlanes', () => { ...@@ -40,7 +42,7 @@ describe('EpicsSwimlanes', () => {
wrapper = shallowMount(EpicsSwimlanes, { wrapper = shallowMount(EpicsSwimlanes, {
localVue, localVue,
propsData: defaultProps, propsData: { ...defaultProps, ...props },
store, store,
}); });
}; };
...@@ -49,6 +51,49 @@ describe('EpicsSwimlanes', () => { ...@@ -49,6 +51,49 @@ describe('EpicsSwimlanes', () => {
wrapper.destroy(); wrapper.destroy();
}); });
describe('computed', () => {
beforeEach(() => {
createComponent();
});
describe('treeRootWrapper', () => {
it('should return Draggable reference when canAdminList prop is true', () => {
wrapper.setProps({ canAdminList: true });
expect(wrapper.vm.treeRootWrapper).toBe(Draggable);
});
it('should return string "div" when canAdminList prop is false', () => {
expect(wrapper.vm.treeRootWrapper).toBe(draggableTag);
});
});
describe('treeRootOptions', () => {
it('should return object containing Vue.Draggable config extended from `defaultSortableConfig` when canAdminList prop is true', () => {
wrapper.setProps({ canAdminList: true });
expect(wrapper.vm.treeRootOptions).toEqual(
expect.objectContaining({
animation: 200,
forceFallback: true,
fallbackClass: 'is-dragging',
fallbackOnBody: false,
ghostClass: 'is-ghost',
group: 'board-swimlanes',
tag: draggableTag,
draggable: '.is-draggable',
'ghost-class': 'swimlane-header-drag-active',
value: mockListsWithModel,
}),
);
});
it('should return an empty object when canAdminList prop is false', () => {
expect(wrapper.vm.treeRootOptions).toEqual(expect.objectContaining({}));
});
});
});
describe('template', () => { describe('template', () => {
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent();
...@@ -68,7 +113,25 @@ describe('EpicsSwimlanes', () => { ...@@ -68,7 +113,25 @@ describe('EpicsSwimlanes', () => {
it('displays issues icon and count for unassigned issue', () => { it('displays issues icon and count for unassigned issue', () => {
expect(wrapper.find(GlIcon).props('name')).toEqual('issues'); expect(wrapper.find(GlIcon).props('name')).toEqual('issues');
expect(wrapper.find('[data-testid="issues-lane-issue-count"').text()).toEqual('2'); expect(wrapper.find('[data-testid="issues-lane-issue-count"]').text()).toEqual('2');
});
it('makes non preset lists draggable', () => {
expect(
wrapper
.findAll('[data-testid="board-header-container"]')
.at(1)
.classes(),
).toContain('is-draggable');
});
it('does not make preset lists draggable', () => {
expect(
wrapper
.findAll('[data-testid="board-header-container"]')
.at(0)
.classes(),
).not.toContain('is-draggable');
}); });
}); });
}); });
...@@ -12,6 +12,7 @@ export const mockLists = [ ...@@ -12,6 +12,7 @@ export const mockLists = [
maxIssueCount: 0, maxIssueCount: 0,
assignee: null, assignee: null,
milestone: null, milestone: null,
preset: true,
}, },
{ {
id: 'gid://gitlab/List/2', id: 'gid://gitlab/List/2',
...@@ -29,6 +30,7 @@ export const mockLists = [ ...@@ -29,6 +30,7 @@ export const mockLists = [
maxIssueCount: 0, maxIssueCount: 0,
assignee: null, assignee: null,
milestone: null, milestone: null,
preset: false,
}, },
]; ];
......
...@@ -2870,6 +2870,9 @@ msgstr "" ...@@ -2870,6 +2870,9 @@ msgstr ""
msgid "An error occurred while updating the comment" msgid "An error occurred while updating the comment"
msgstr "" msgstr ""
msgid "An error occurred while updating the list. Please try again."
msgstr ""
msgid "An error occurred while validating group path" msgid "An error occurred while validating group path"
msgstr "" msgstr ""
......
import testAction from 'helpers/vuex_action_helper'; import testAction from 'helpers/vuex_action_helper';
import { mockListsWithModel } from '../mock_data';
import actions, { gqlClient } from '~/boards/stores/actions'; import actions, { gqlClient } from '~/boards/stores/actions';
import * as types from '~/boards/stores/mutation_types'; import * as types from '~/boards/stores/mutation_types';
import { inactiveId, ListType } from '~/boards/constants'; import { inactiveId, ListType } from '~/boards/constants';
...@@ -160,8 +161,58 @@ describe('createList', () => { ...@@ -160,8 +161,58 @@ describe('createList', () => {
}); });
}); });
describe('moveList', () => {
it('should commit MOVE_LIST mutation and dispatch updateList action', done => {
const state = {
endpoints: { fullPath: 'gitlab-org', boardId: '1' },
boardType: 'group',
disabled: false,
boardLists: mockListsWithModel,
};
testAction(
actions.moveList,
{ listId: 'gid://gitlab/List/1', position: 0, moveNewIndexListUp: true },
state,
[
{
type: types.MOVE_LIST,
payload: { movedList: mockListsWithModel[0], listAtNewIndex: mockListsWithModel[1] },
},
],
[{ type: 'updateList', payload: { listId: 'gid://gitlab/List/1', position: 0 } }],
done,
);
});
});
describe('updateList', () => { describe('updateList', () => {
expectNotImplemented(actions.updateList); it('should commit UPDATE_LIST_FAILURE mutation when API returns an error', done => {
jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
data: {
updateBoardList: {
list: {},
errors: [{ foo: 'bar' }],
},
},
});
const state = {
endpoints: { fullPath: 'gitlab-org', boardId: '1' },
boardType: 'group',
disabled: false,
boardLists: [{ type: 'closed' }],
};
testAction(
actions.updateList,
{ listId: 'gid://gitlab/List/1', position: 1 },
state,
[{ type: types.UPDATE_LIST_FAILURE }],
[],
done,
);
});
}); });
describe('deleteList', () => { describe('deleteList', () => {
......
import mutations from '~/boards/stores/mutations'; import mutations from '~/boards/stores/mutations';
import * as types from '~/boards/stores/mutation_types'; import * as types from '~/boards/stores/mutation_types';
import defaultState from '~/boards/stores/state'; import defaultState from '~/boards/stores/state';
import { listObj, listObjDuplicate, mockIssue } from '../mock_data'; import { listObj, listObjDuplicate, mockIssue, mockListsWithModel } from '../mock_data';
const expectNotImplemented = action => { const expectNotImplemented = action => {
it('is not implemented', () => { it('is not implemented', () => {
...@@ -92,16 +92,37 @@ describe('Board Store Mutations', () => { ...@@ -92,16 +92,37 @@ describe('Board Store Mutations', () => {
expectNotImplemented(mutations.RECEIVE_ADD_LIST_ERROR); expectNotImplemented(mutations.RECEIVE_ADD_LIST_ERROR);
}); });
describe('REQUEST_UPDATE_LIST', () => { describe('MOVE_LIST', () => {
expectNotImplemented(mutations.REQUEST_UPDATE_LIST); it('updates boardLists state with reordered lists and saves previous order', () => {
}); state = {
...state,
boardLists: mockListsWithModel,
};
describe('RECEIVE_UPDATE_LIST_SUCCESS', () => { mutations.MOVE_LIST(state, {
expectNotImplemented(mutations.RECEIVE_UPDATE_LIST_SUCCESS); movedList: mockListsWithModel[0],
listAtNewIndex: mockListsWithModel[1],
});
expect(state.boardLists).toEqual([mockListsWithModel[1], mockListsWithModel[0]]);
expect(state.boardListsPreviousState).toEqual(mockListsWithModel);
});
}); });
describe('RECEIVE_UPDATE_LIST_ERROR', () => { describe('UPDATE_LIST_FAILURE', () => {
expectNotImplemented(mutations.RECEIVE_UPDATE_LIST_ERROR); it('updates boardLists state with previous order and sets error message', () => {
state = {
...state,
boardLists: [mockListsWithModel[1], mockListsWithModel[0]],
boardListsPreviousState: mockListsWithModel,
error: undefined,
};
mutations.UPDATE_LIST_FAILURE(state);
expect(state.boardLists).toEqual(mockListsWithModel);
expect(state.error).toEqual('An error occurred while updating the list. Please try again.');
});
}); });
describe('REQUEST_REMOVE_LIST', () => { describe('REQUEST_REMOVE_LIST', () => {
......
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