Commit 02d8146f authored by Florie Guibert's avatar Florie Guibert

Swimlanes - Add list to board

Store lists by id on VueX state
parent 18b27142
......@@ -2,11 +2,24 @@ import { sortBy } from 'lodash';
import ListIssue from 'ee_else_ce/boards/models/issue';
import { ListType } from './constants';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import boardsStore from '~/boards/stores/boards_store';
export function getMilestone() {
return null;
}
export function formatBoardLists(lists) {
const formattedLists = lists.nodes.map(list =>
boardsStore.updateListPosition({ ...list, doNotFetchIssues: true }),
);
return formattedLists.reduce((map, list) => {
return {
...map,
[list.id]: list,
};
}, {});
}
export function formatIssue(issue) {
return new ListIssue({
...issue,
......@@ -62,6 +75,13 @@ export function fullBoardId(boardId) {
return `gid://gitlab/Board/${boardId}`;
}
export function fullLabelId(label) {
if (label.project_id !== null) {
return `gid://gitlab/ProjectLabel/${label.id}`;
}
return `gid://gitlab/GroupLabel/${label.id}`;
}
export function moveIssueListHelper(issue, fromList, toList) {
if (toList.type === ListType.label) {
issue.addLabel(toList.label);
......@@ -85,4 +105,5 @@ export default {
formatIssue,
formatListIssues,
fullBoardId,
fullLabelId,
};
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
import { sortBy } from 'lodash';
import BoardColumn from 'ee_else_ce/boards/components/board_column.vue';
import { GlAlert } from '@gitlab/ui';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
......@@ -30,7 +31,9 @@ export default {
...mapState(['boardLists', 'error']),
...mapGetters(['isSwimlanesOn']),
boardListsToUse() {
return this.glFeatures.graphqlBoardLists ? this.boardLists : this.lists;
const lists =
this.glFeatures.graphqlBoardLists || this.isSwimlanesOn ? this.boardLists : this.lists;
return sortBy([...Object.values(lists)], 'position');
},
},
mounted() {
......@@ -68,7 +71,7 @@ export default {
<template v-else>
<epics-swimlanes
ref="swimlanes"
:lists="boardLists"
:lists="boardListsToUse"
:can-admin-list="canAdminList"
:disabled="disabled"
/>
......
......@@ -34,7 +34,7 @@ export default {
referencing a List Model class. Reactivity only applies to plain JS objects
*/
if (this.glFeatures.graphqlBoardLists) {
return this.boardLists.find(({ id }) => id === this.activeId);
return this.boardLists[this.activeId];
}
return boardsStore.state.lists.find(({ id }) => id === this.activeId);
},
......
......@@ -6,8 +6,14 @@ import axios from '~/lib/utils/axios_utils';
import { deprecatedCreateFlash as flash } from '~/flash';
import CreateLabelDropdown from '../../create_label';
import boardsStore from '../stores/boards_store';
import { fullLabelId } from '../boards_util';
import store from '~/boards/stores';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
function shouldCreateListGraphQL(label) {
return store.getters.shouldUseGraphQL && !store.getters.getListByLabelId(fullLabelId(label));
}
$(document)
.off('created.label')
.on('created.label', (e, label, addNewList) => {
......@@ -15,16 +21,20 @@ $(document)
return;
}
boardsStore.new({
title: label.title,
position: boardsStore.state.lists.length - 2,
list_type: 'label',
label: {
id: label.id,
if (shouldCreateListGraphQL(label)) {
store.dispatch('createList', { labelId: fullLabelId(label) });
} else {
boardsStore.new({
title: label.title,
color: label.color,
},
});
position: boardsStore.state.lists.length - 2,
list_type: 'label',
label: {
id: label.id,
title: label.title,
color: label.color,
},
});
}
});
export default function initNewListDropdown() {
......@@ -74,7 +84,9 @@ export default function initNewListDropdown() {
const label = options.selectedObj;
e.preventDefault();
if (!boardsStore.findListByLabelId(label.id)) {
if (shouldCreateListGraphQL(label)) {
store.dispatch('createList', { labelId: fullLabelId(label) });
} else if (!boardsStore.findListByLabelId(label.id)) {
boardsStore.new({
title: label.title,
position: boardsStore.state.lists.length - 2,
......
#import "./board_list.fragment.graphql"
#import "ee_else_ce/boards/queries/board_list.fragment.graphql"
mutation CreateBoardList($boardId: BoardID!, $backlog: Boolean) {
boardListCreate(input: { boardId: $boardId, backlog: $backlog }) {
mutation CreateBoardList(
$boardId: BoardID!
$backlog: Boolean
$labelId: LabelID
$milestoneId: MilestoneID
$assigneeId: UserID
) {
boardListCreate(
input: {
boardId: $boardId
backlog: $backlog
labelId: $labelId
milestoneId: $milestoneId
assigneeId: $assigneeId
}
) {
list {
...BoardListFragment
}
......
import Cookies from 'js-cookie';
import { sortBy, pick } from 'lodash';
import createFlash from '~/flash';
import { pick } from 'lodash';
import { __ } from '~/locale';
import { parseBoolean } from '~/lib/utils/common_utils';
import createGqClient, { fetchPolicies } from '~/lib/graphql';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { BoardType, ListType, inactiveId } from '~/boards/constants';
import * as types from './mutation_types';
import { formatListIssues, fullBoardId, formatListsPageInfo } from '../boards_util';
import {
formatBoardLists,
formatListIssues,
fullBoardId,
formatListsPageInfo,
} from '../boards_util';
import boardStore from '~/boards/stores/boards_store';
import listsIssuesQuery from '../queries/lists_issues.query.graphql';
......@@ -71,38 +75,29 @@ export default {
variables,
})
.then(({ data }) => {
let { lists } = data[boardType]?.board;
// Temporarily using positioning logic from boardStore
lists = lists.nodes.map(list =>
boardStore.updateListPosition({
...list,
doNotFetchIssues: true,
}),
);
commit(types.RECEIVE_BOARD_LISTS_SUCCESS, sortBy(lists, 'position'));
const { lists } = data[boardType]?.board;
commit(types.RECEIVE_BOARD_LISTS_SUCCESS, formatBoardLists(lists));
// Backlog list needs to be created if it doesn't exist
if (!lists.find(l => l.type === ListType.backlog)) {
if (!lists.nodes.find(l => l.listType === ListType.backlog)) {
dispatch('createList', { backlog: true });
}
dispatch('showWelcomeList');
})
.catch(() => {
createFlash(
__('An error occurred while fetching the board lists. Please reload the page.'),
);
});
.catch(() => commit(types.RECEIVE_BOARD_LISTS_FAILURE));
},
// This action only supports backlog list creation at this stage
// Future iterations will add the ability to create other list types
createList: ({ state, commit, dispatch }, { backlog = false }) => {
createList: ({ state, commit, dispatch }, { backlog, labelId, milestoneId, assigneeId }) => {
const { boardId } = state.endpoints;
gqlClient
.mutate({
mutation: createBoardListMutation,
variables: {
boardId: fullBoardId(boardId),
backlog,
labelId,
milestoneId,
assigneeId,
},
})
.then(({ data }) => {
......@@ -113,16 +108,15 @@ export default {
dispatch('addList', list);
}
})
.catch(() => {
commit(types.CREATE_LIST_FAILURE);
});
.catch(() => commit(types.CREATE_LIST_FAILURE));
},
addList: ({ state, commit }, list) => {
const lists = state.boardLists;
addList: ({ commit }, list) => {
// Temporarily using positioning logic from boardStore
lists.push(boardStore.updateListPosition({ ...list, doNotFetchIssues: true }));
commit(types.RECEIVE_BOARD_LISTS_SUCCESS, sortBy(lists, 'position'));
commit(
types.RECEIVE_ADD_LIST_SUCCESS,
boardStore.updateListPosition({ ...list, doNotFetchIssues: true }),
);
},
showWelcomeList: ({ state, dispatch }) => {
......@@ -130,7 +124,9 @@ export default {
return;
}
if (
state.boardLists.find(list => list.type !== ListType.backlog && list.type !== ListType.closed)
Object.entries(state.boardLists).find(
([, list]) => list.type !== ListType.backlog && list.type !== ListType.closed,
)
) {
return;
}
......@@ -152,13 +148,16 @@ export default {
notImplemented();
},
moveList: ({ state, commit, dispatch }, { listId, newIndex, adjustmentValue }) => {
moveList: (
{ state, commit, dispatch },
{ listId, replacedListId, newIndex, adjustmentValue },
) => {
const { boardLists } = state;
const backupList = [...boardLists];
const movedList = boardLists.find(({ id }) => id === listId);
const backupList = { ...boardLists };
const movedList = boardLists[listId];
const newPosition = newIndex - 1;
const listAtNewIndex = boardLists[newIndex];
const listAtNewIndex = boardLists[replacedListId];
movedList.position = newPosition;
listAtNewIndex.position += adjustmentValue;
......
import { find } from 'lodash';
import { inactiveId } from '../constants';
export default {
......@@ -22,4 +23,16 @@ export default {
getActiveIssue: state => {
return state.issues[state.activeId] || {};
},
getListByLabelId: state => labelId => {
return find(state.boardLists, l => l.label?.id === labelId);
},
getListByTitle: state => title => {
return find(state.boardLists, l => l.title === title);
},
shouldUseGraphQL: () => {
return gon?.features?.graphqlBoardLists;
},
};
......@@ -3,6 +3,7 @@ export const SET_FILTERS = 'SET_FILTERS';
export const CREATE_LIST_SUCCESS = 'CREATE_LIST_SUCCESS';
export const CREATE_LIST_FAILURE = 'CREATE_LIST_FAILURE';
export const RECEIVE_BOARD_LISTS_SUCCESS = 'RECEIVE_BOARD_LISTS_SUCCESS';
export const RECEIVE_BOARD_LISTS_FAILURE = 'RECEIVE_BOARD_LISTS_FAILURE';
export const SHOW_PROMOTION_LIST = 'SHOW_PROMOTION_LIST';
export const REQUEST_ADD_LIST = 'REQUEST_ADD_LIST';
export const RECEIVE_ADD_LIST_SUCCESS = 'RECEIVE_ADD_LIST_SUCCESS';
......
import Vue from 'vue';
import { sortBy, pull, union } from 'lodash';
import { pull, union } from 'lodash';
import { formatIssue, moveIssueListHelper } from '../boards_util';
import * as mutationTypes from './mutation_types';
import { s__ } from '~/locale';
......@@ -10,16 +10,10 @@ const notImplemented = () => {
throw new Error('Not implemented!');
};
const getListById = ({ state, listId }) => {
const listIndex = state.boardLists.findIndex(l => l.id === listId);
const list = state.boardLists[listIndex];
return { listIndex, list };
};
export const removeIssueFromList = ({ state, listId, issueId }) => {
Vue.set(state.issuesByListId, listId, pull(state.issuesByListId[listId], issueId));
const { listIndex, list } = getListById({ state, listId });
Vue.set(state.boardLists, listIndex, { ...list, issuesSize: list.issuesSize - 1 });
const list = state.boardLists[listId];
Vue.set(state.boardLists, listId, { ...list, issuesSize: list.issuesSize - 1 });
};
export const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfterId, atIndex }) => {
......@@ -32,8 +26,8 @@ export const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfter
}
listIssues.splice(newIndex, 0, issueId);
Vue.set(state.issuesByListId, listId, listIssues);
const { listIndex, list } = getListById({ state, listId });
Vue.set(state.boardLists, listIndex, { ...list, issuesSize: list.issuesSize + 1 });
const list = state.boardLists[listId];
Vue.set(state.boardLists, listId, { ...list, issuesSize: list.issuesSize + 1 });
};
export default {
......@@ -49,6 +43,12 @@ export default {
state.boardLists = lists;
},
[mutationTypes.RECEIVE_BOARD_LISTS_FAILURE]: state => {
state.error = s__(
'Boards|An error occurred while fetching the board lists. Please reload the page.',
);
},
[mutationTypes.SET_ACTIVE_ID](state, { id, sidebarType }) {
state.activeId = id;
state.sidebarType = sidebarType;
......@@ -66,8 +66,8 @@ export default {
notImplemented();
},
[mutationTypes.RECEIVE_ADD_LIST_SUCCESS]: () => {
notImplemented();
[mutationTypes.RECEIVE_ADD_LIST_SUCCESS]: (state, list) => {
Vue.set(state.boardLists, list.id, list);
},
[mutationTypes.RECEIVE_ADD_LIST_ERROR]: () => {
......@@ -76,10 +76,8 @@ export default {
[mutationTypes.MOVE_LIST]: (state, { movedList, listAtNewIndex }) => {
const { boardLists } = state;
const movedListIndex = state.boardLists.findIndex(l => l.id === movedList.id);
Vue.set(boardLists, movedListIndex, movedList);
Vue.set(boardLists, movedListIndex.position + 1, listAtNewIndex);
Vue.set(state, 'boardLists', sortBy(boardLists, 'position'));
Vue.set(boardLists, movedList.id, movedList);
Vue.set(boardLists, listAtNewIndex.id, listAtNewIndex);
},
[mutationTypes.UPDATE_LIST_FAILURE]: (state, backupList) => {
......@@ -156,8 +154,8 @@ export default {
state,
{ originalIssue, fromListId, toListId, moveBeforeId, moveAfterId },
) => {
const fromList = state.boardLists.find(l => l.id === fromListId);
const toList = state.boardLists.find(l => l.id === toListId);
const fromList = state.boardLists[fromListId];
const toList = state.boardLists[toListId];
const issue = moveIssueListHelper(originalIssue, fromList, toList);
Vue.set(state.issues, issue.id, issue);
......
......@@ -8,7 +8,7 @@ export default () => ({
isShowingLabels: true,
activeId: inactiveId,
sidebarType: '',
boardLists: [],
boardLists: {},
listsFlags: {},
issuesByListId: {},
pageInfoByListId: {},
......
......@@ -7,7 +7,7 @@ class LabelEntity < Grape::Entity
expose :color
expose :description
expose :group_id
expose :project_id
expose :project_id, if: ->(label, _) { !label.is_a?(GlobalLabel) }
expose :template
expose :text_color
expose :created_at
......
......@@ -4,6 +4,6 @@ class LabelSerializer < BaseSerializer
entity LabelEntity
def represent_appearance(resource)
represent(resource, { only: [:id, :title, :color, :text_color] })
represent(resource, { only: [:id, :title, :color, :text_color, :project_id] })
end
end
......@@ -6,7 +6,17 @@ export function fullEpicId(epicId) {
return `gid://gitlab/Epic/${epicId}`;
}
export function fullMilestoneId(milestoneId) {
return `gid://gitlab/Milestone/${milestoneId}`;
}
export function fullUserId(userId) {
return `gid://gitlab/User/${userId}`;
}
export default {
getMilestone,
fullEpicId,
fullMilestoneId,
fullUserId,
};
import Vue from 'vue';
import boardsStore from '~/boards/stores/boards_store';
import vuexStore from '~/boards/stores';
import ListContainer from './list_container.vue';
import { fullMilestoneId, fullUserId } from '../../boards_util';
export default Vue.extend({
components: {
......@@ -20,6 +22,7 @@ export default Vue.extend({
return {
loading: true,
store: boardsStore,
vuexStore,
};
},
mounted() {
......@@ -46,19 +49,33 @@ export default Vue.extend({
return foundName || username.indexOf(query) > -1;
});
},
handleItemClick(item) {
if (!this.store.findList('title', item.name)) {
const list = {
title: item.name,
position: this.store.state.lists.length - 2,
list_type: this.listType,
};
prepareListObject(item) {
const list = {
title: item.name,
position: this.store.state.lists.length - 2,
list_type: this.listType,
};
if (this.listType === 'milestones') {
list.milestone = item;
} else if (this.listType === 'assignees') {
list.user = item;
}
return list;
},
handleItemClick(item) {
if (
this.vuexStore.getters.shouldUseGraphQL &&
!this.vuexStore.getters.getListByTitle(item.title)
) {
if (this.listType === 'milestones') {
list.milestone = item;
this.vuexStore.dispatch('createList', { milestoneId: fullMilestoneId(item.id) });
} else if (this.listType === 'assignees') {
list.user = item;
this.vuexStore.dispatch('createList', { assigneeId: fullUserId(item.id) });
}
} else if (!this.store.findList('title', item.title)) {
const list = this.prepareListObject(item);
this.store.new(list);
}
......
......@@ -73,11 +73,13 @@ export default {
methods: {
...mapActions(['moveList', 'fetchIssuesForList']),
handleDragOnEnd(params) {
const { newIndex, oldIndex, item } = params;
const { newIndex, oldIndex, item, to } = params;
const { listId } = item.dataset;
const replacedListId = to.children[newIndex].dataset.listId;
this.moveList({
listId,
replacedListId,
newIndex,
adjustmentValue: newIndex < oldIndex ? 1 : -1,
});
......
import $ from 'jquery';
import initNewListDropdown from '~/boards/components/new_list_dropdown';
import AssigneeList from './assignees_list_slector';
import AssigneeList from './assignees_list_selector';
import MilestoneList from './milestone_list_selector';
const handleDropdownHide = e => {
......@@ -12,7 +12,7 @@ const handleDropdownHide = e => {
};
let assigneeList;
let milstoneList;
let milestoneList;
const handleDropdownTabClick = e => {
const $addListEl = $('#js-add-list');
......@@ -21,8 +21,8 @@ const handleDropdownTabClick = e => {
assigneeList = AssigneeList();
}
if (e.target.dataset.action === 'tab-milestones' && !milstoneList) {
milstoneList = MilestoneList();
if (e.target.dataset.action === 'tab-milestones' && !milestoneList) {
milestoneList = MilestoneList();
}
};
......
import { sortBy, pick } from 'lodash';
import { pick } from 'lodash';
import Cookies from 'js-cookie';
import axios from '~/lib/utils/axios_utils';
import boardsStore from '~/boards/stores/boards_store';
......@@ -10,7 +10,12 @@ import { EpicFilterType } from '../constants';
import boardsStoreEE from './boards_store_ee';
import * as types from './mutation_types';
import { fullEpicId } from '../boards_util';
import { formatListIssues, formatListsPageInfo, fullBoardId } from '~/boards/boards_util';
import {
formatBoardLists,
formatListIssues,
formatListsPageInfo,
fullBoardId,
} from '~/boards/boards_util';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import eventHub from '~/boards/eventhub';
......@@ -112,11 +117,7 @@ export default {
commit(types.RECEIVE_EPICS_SUCCESS, epicsFormatted);
} else {
if (lists) {
let boardLists = lists.nodes.map(list =>
boardsStore.updateListPosition({ ...list, doNotFetchIssues: true }),
);
boardLists = sortBy([...boardLists], 'position');
commit(types.RECEIVE_BOARD_LISTS_SUCCESS, boardLists);
commit(types.RECEIVE_BOARD_LISTS_SUCCESS, formatBoardLists(lists));
}
if (epicsFormatted) {
......
......@@ -14,4 +14,11 @@ export default {
getEpicById: state => epicId => {
return state.epics.find(epic => epic.id === epicId);
},
shouldUseGraphQL: state => {
return (
(gon?.features?.boardsWithSwimlanes && state.isShowingEpicsSwimlanes) ||
gon?.features?.graphqlBoardLists
);
},
};
......@@ -141,8 +141,8 @@ export default {
state,
{ originalIssue, fromListId, toListId, moveBeforeId, moveAfterId, epicId },
) => {
const fromList = state.boardLists.find(l => l.id === fromListId);
const toList = state.boardLists.find(l => l.id === toListId);
const fromList = state.boardLists[fromListId];
const toList = state.boardLists[toListId];
const issue = moveIssueListHelper(originalIssue, fromList, toList);
......
......@@ -7,8 +7,15 @@ import { mockAssigneesList } from 'jest/boards/mock_data';
import { TEST_HOST } from 'spec/test_constants';
import axios from '~/lib/utils/axios_utils';
import boardsStore from '~/boards/stores/boards_store';
import { createStore } from '~/boards/stores';
describe('BoardListSelector', () => {
global.gon.features = {
...(global.gon.features || {}),
boardsWithSwimlanes: false,
graphqlBoardLists: false,
};
const dummyEndpoint = `${TEST_HOST}/users.json`;
const createComponent = () =>
......@@ -28,6 +35,7 @@ describe('BoardListSelector', () => {
setFixtures('<div class="flash-container"></div>');
vm = createComponent();
vm.vuexStore = createStore();
});
afterEach(() => {
......@@ -86,8 +94,8 @@ describe('BoardListSelector', () => {
});
describe('handleItemClick', () => {
it('creates new list in a store instance', () => {
jest.spyOn(vm.store, 'new').mockImplementation(() => {});
it('graphqlBoardLists FF off - creates new list in a store instance', () => {
jest.spyOn(vm.store, 'new').mockReturnValue({});
const assignee = mockAssigneesList[0];
expect(vm.store.findList('title', assignee.name)).not.toBeDefined();
......@@ -95,6 +103,20 @@ describe('BoardListSelector', () => {
expect(vm.store.new).toHaveBeenCalledWith(expect.any(Object));
});
it('graphqlBoardLists FF on - creates new list in a store instance', () => {
global.gon.features.graphqlBoardLists = true;
jest.spyOn(vm.vuexStore, 'dispatch').mockReturnValue({});
const assignee = mockAssigneesList[0];
expect(vm.vuexStore.getters.getListByTitle(assignee.name)).not.toBeDefined();
vm.handleItemClick(assignee);
expect(vm.vuexStore.dispatch).toHaveBeenCalledWith('createList', {
assigneeId: 'gid://gitlab/User/2',
});
});
});
});
});
import mutations from 'ee/boards/stores/mutations';
import {
mockLists,
mockIssue,
mockIssue2,
mockEpics,
......@@ -18,10 +17,15 @@ const expectNotImplemented = action => {
const epicId = mockEpic.id;
const initialBoardListsState = {
'gid://gitlab/List/1': mockListsWithModel[0],
'gid://gitlab/List/2': mockListsWithModel[1],
};
let state = {
issuesByListId: {},
issues: {},
boardLists: mockListsWithModel,
boardLists: initialBoardListsState,
epicsFlags: {
[epicId]: { isLoading: true },
},
......@@ -182,10 +186,10 @@ describe('RECEIVE_BOARD_LISTS_SUCCESS', () => {
boardLists: {},
};
mutations.RECEIVE_BOARD_LISTS_SUCCESS(state, mockLists);
mutations.RECEIVE_BOARD_LISTS_SUCCESS(state, initialBoardListsState);
expect(state.epicsSwimlanesFetchInProgress).toBe(false);
expect(state.boardLists).toEqual(mockLists);
expect(state.boardLists).toEqual(initialBoardListsState);
});
});
......@@ -273,7 +277,6 @@ describe('MOVE_ISSUE', () => {
state = {
...state,
issuesByListId: listIssues,
boardLists: mockListsWithModel,
issues,
};
});
......
......@@ -2851,9 +2851,6 @@ msgstr ""
msgid "An error occurred while fetching the Service Desk address."
msgstr ""
msgid "An error occurred while fetching the board lists. Please reload the page."
msgstr ""
msgid "An error occurred while fetching the board lists. Please try again."
msgstr ""
......@@ -4185,6 +4182,9 @@ msgstr ""
msgid "Boards|An error occurred while fetching the board issues. Please reload the page."
msgstr ""
msgid "Boards|An error occurred while fetching the board lists. Please reload the page."
msgstr ""
msgid "Boards|An error occurred while fetching the board swimlanes. Please reload the page."
msgstr ""
......
......@@ -177,16 +177,26 @@ describe('createList', () => {
describe('moveList', () => {
it('should commit MOVE_LIST mutation and dispatch updateList action', done => {
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: mockListsWithModel,
boardLists: initialBoardListsState,
};
testAction(
actions.moveList,
{ listId: 'gid://gitlab/List/1', newIndex: 1, adjustmentValue: 1 },
{
listId: 'gid://gitlab/List/1',
replacedListId: 'gid://gitlab/List/2',
newIndex: 1,
adjustmentValue: 1,
},
state,
[
{
......@@ -197,7 +207,11 @@ describe('moveList', () => {
[
{
type: 'updateList',
payload: { listId: 'gid://gitlab/List/1', position: 0, backupList: mockListsWithModel },
payload: {
listId: 'gid://gitlab/List/1',
position: 0,
backupList: initialBoardListsState,
},
},
],
done,
......
import getters from '~/boards/stores/getters';
import { inactiveId } from '~/boards/constants';
import { mockIssue, mockIssue2, mockIssues, mockIssuesByListId, issues } from '../mock_data';
import {
mockIssue,
mockIssue2,
mockIssues,
mockIssuesByListId,
issues,
mockListsWithModel,
} from '../mock_data';
describe('Boards - Getters', () => {
describe('getLabelToggleState', () => {
......@@ -130,4 +137,25 @@ describe('Boards - Getters', () => {
);
});
});
const boardsState = {
boardLists: {
'gid://gitlab/List/1': mockListsWithModel[0],
'gid://gitlab/List/2': mockListsWithModel[1],
},
};
describe('getListByLabelId', () => {
it('returns list for a given label id', () => {
expect(getters.getListByLabelId(boardsState)('gid://gitlab/GroupLabel/121')).toEqual(
mockListsWithModel[1],
);
});
});
describe('getListByTitle', () => {
it('returns list for a given list title', () => {
expect(getters.getListByTitle(boardsState)('To Do')).toEqual(mockListsWithModel[1]);
});
});
});
......@@ -2,8 +2,6 @@ import mutations from '~/boards/stores/mutations';
import * as types from '~/boards/stores/mutation_types';
import defaultState from '~/boards/stores/state';
import {
listObj,
listObjDuplicate,
mockListsWithModel,
mockLists,
rawIssue,
......@@ -22,6 +20,11 @@ const expectNotImplemented = action => {
describe('Board Store Mutations', () => {
let state;
const initialBoardListsState = {
'gid://gitlab/List/1': mockListsWithModel[0],
'gid://gitlab/List/2': mockListsWithModel[1],
};
beforeEach(() => {
state = defaultState();
});
......@@ -56,11 +59,19 @@ describe('Board Store Mutations', () => {
describe('RECEIVE_BOARD_LISTS_SUCCESS', () => {
it('Should set boardLists to state', () => {
const lists = [listObj, listObjDuplicate];
mutations[types.RECEIVE_BOARD_LISTS_SUCCESS](state, initialBoardListsState);
expect(state.boardLists).toEqual(initialBoardListsState);
});
});
mutations[types.RECEIVE_BOARD_LISTS_SUCCESS](state, lists);
describe('RECEIVE_BOARD_LISTS_FAILURE', () => {
it('Should set error in state', () => {
mutations[types.RECEIVE_BOARD_LISTS_FAILURE](state);
expect(state.boardLists).toEqual(lists);
expect(state.error).toEqual(
'An error occurred while fetching the board lists. Please reload the page.',
);
});
});
......@@ -95,7 +106,13 @@ describe('Board Store Mutations', () => {
});
describe('RECEIVE_ADD_LIST_SUCCESS', () => {
expectNotImplemented(mutations.RECEIVE_ADD_LIST_SUCCESS);
it('adds list to boardLists state', () => {
mutations.RECEIVE_ADD_LIST_SUCCESS(state, mockListsWithModel[0]);
expect(state.boardLists).toEqual({
[mockListsWithModel[0].id]: mockListsWithModel[0],
});
});
});
describe('RECEIVE_ADD_LIST_ERROR', () => {
......@@ -106,7 +123,7 @@ describe('Board Store Mutations', () => {
it('updates boardLists state with reordered lists', () => {
state = {
...state,
boardLists: mockListsWithModel,
boardLists: initialBoardListsState,
};
mutations.MOVE_LIST(state, {
......@@ -114,7 +131,10 @@ describe('Board Store Mutations', () => {
listAtNewIndex: mockListsWithModel[1],
});
expect(state.boardLists).toEqual([mockListsWithModel[1], mockListsWithModel[0]]);
expect(state.boardLists).toEqual({
'gid://gitlab/List/2': mockListsWithModel[1],
'gid://gitlab/List/1': mockListsWithModel[0],
});
});
});
......@@ -122,13 +142,16 @@ describe('Board Store Mutations', () => {
it('updates boardLists state with previous order and sets error message', () => {
state = {
...state,
boardLists: [mockListsWithModel[1], mockListsWithModel[0]],
boardLists: {
'gid://gitlab/List/2': mockListsWithModel[1],
'gid://gitlab/List/1': mockListsWithModel[0],
},
error: undefined,
};
mutations.UPDATE_LIST_FAILURE(state, mockListsWithModel);
mutations.UPDATE_LIST_FAILURE(state, initialBoardListsState);
expect(state.boardLists).toEqual(mockListsWithModel);
expect(state.boardLists).toEqual(initialBoardListsState);
expect(state.error).toEqual('An error occurred while updating the list. Please try again.');
});
});
......@@ -177,7 +200,7 @@ describe('Board Store Mutations', () => {
'gid://gitlab/List/1': [],
},
issues: {},
boardLists: mockListsWithModel,
boardLists: initialBoardListsState,
};
const listPageInfo = {
......@@ -202,7 +225,7 @@ describe('Board Store Mutations', () => {
it('sets error message', () => {
state = {
...state,
boardLists: mockListsWithModel,
boardLists: initialBoardListsState,
error: undefined,
};
......@@ -284,7 +307,7 @@ describe('Board Store Mutations', () => {
state = {
...state,
issuesByListId: listIssues,
boardLists: mockListsWithModel,
boardLists: initialBoardListsState,
issues,
};
......@@ -332,7 +355,7 @@ describe('Board Store Mutations', () => {
state = {
...state,
issuesByListId: listIssues,
boardLists: mockListsWithModel,
boardLists: initialBoardListsState,
};
mutations.MOVE_ISSUE_FAILURE(state, {
......@@ -400,7 +423,7 @@ describe('Board Store Mutations', () => {
...state,
issuesByListId: listIssues,
issues,
boardLists: mockListsWithModel,
boardLists: initialBoardListsState,
};
mutations.ADD_ISSUE_TO_LIST_FAILURE(state, { list: mockLists[0], issue: mockIssue2 });
......
......@@ -37,11 +37,12 @@ RSpec.describe LabelSerializer do
subject { serializer.represent_appearance(resource) }
it 'serializes only attributes used for appearance' do
expect(subject.keys).to eq([:id, :title, :color, :text_color])
expect(subject.keys).to eq([:id, :title, :color, :project_id, :text_color])
expect(subject[:id]).to eq(resource.id)
expect(subject[:title]).to eq(resource.title)
expect(subject[:color]).to eq(resource.color)
expect(subject[:text_color]).to eq(resource.text_color)
expect(subject[:project_id]).to eq(resource.project_id)
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