Commit 493e0e17 authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch '285073-boards-remove-the-use-of-list-model-from-graphql-boards' into 'master'

Boards - Remove List model from GraphQL boards [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!48854
parents d8d942c1 a2edabc8
......@@ -2,20 +2,28 @@ import { sortBy } from 'lodash';
import axios from '~/lib/utils/axios_utils';
import { ListType } from './constants';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import boardsStore from '~/boards/stores/boards_store';
export function getMilestone() {
return null;
}
export function updateListPosition(listObj) {
const { listType } = listObj;
let { position } = listObj;
if (listType === ListType.closed) {
position = Infinity;
} else if (listType === ListType.backlog) {
position = -Infinity;
}
return { ...listObj, position };
}
export function formatBoardLists(lists) {
const formattedLists = lists.nodes.map(list =>
boardsStore.updateListPosition({ ...list, doNotFetchIssues: true }),
);
return formattedLists.reduce((map, list) => {
return lists.nodes.reduce((map, list) => {
return {
...map,
[list.id]: list,
[list.id]: updateListPosition(list),
};
}, {});
}
......@@ -85,22 +93,22 @@ export function fullLabelId(label) {
export function moveIssueListHelper(issue, fromList, toList) {
const updatedIssue = issue;
if (
toList.type === ListType.label &&
toList.listType === ListType.label &&
!updatedIssue.labels.find(label => label.id === toList.label.id)
) {
updatedIssue.labels.push(toList.label);
}
if (fromList?.label && fromList.type === ListType.label) {
if (fromList?.label && fromList.listType === ListType.label) {
updatedIssue.labels = updatedIssue.labels.filter(label => fromList.label.id !== label.id);
}
if (
toList.type === ListType.assignee &&
toList.listType === ListType.assignee &&
!updatedIssue.assignees.find(assignee => assignee.id === toList.assignee.id)
) {
updatedIssue.assignees.push(toList.assignee);
}
if (fromList?.assignee && fromList.type === ListType.assignee) {
if (fromList?.assignee && fromList.listType === ListType.assignee) {
updatedIssue.assignees = updatedIssue.assignees.filter(
assignee => assignee.id !== fromList.assignee.id,
);
......@@ -118,6 +126,10 @@ export function getBoardsPath(endpoint, board) {
return axios.post(path, { board });
}
export function isListDraggable(list) {
return list.listType !== ListType.backlog && list.listType !== ListType.closed;
}
export default {
getMilestone,
formatIssue,
......@@ -125,4 +137,5 @@ export default {
fullBoardId,
fullLabelId,
getBoardsPath,
isListDraggable,
};
......@@ -2,6 +2,7 @@
import { mapGetters, mapActions, mapState } from 'vuex';
import BoardListHeader from 'ee_else_ce/boards/components/board_list_header_new.vue';
import BoardList from './board_list_new.vue';
import { isListDraggable } from '../boards_util';
export default {
components: {
......@@ -35,6 +36,9 @@ export default {
listIssues() {
return this.getIssuesByList(this.list.id);
},
isListDraggable() {
return isListDraggable(this.list);
},
},
watch: {
filterParams: {
......@@ -47,7 +51,6 @@ export default {
},
methods: {
...mapActions(['fetchIssuesForList']),
// TODO: Reordering of lists https://gitlab.com/gitlab-org/gitlab/-/issues/280515
},
};
</script>
......@@ -55,13 +58,12 @@ export default {
<template>
<div
:class="{
'is-draggable': !list.preset,
'is-expandable': list.isExpandable,
'is-collapsed': !list.isExpanded,
'board-type-assignee': list.type === 'assignee',
'is-draggable': isListDraggable,
'is-collapsed': list.collapsed,
'board-type-assignee': list.listType === 'assignee',
}"
:data-id="list.id"
class="board gl-display-inline-block gl-h-full gl-px-3 gl-vertical-align-top gl-white-space-normal"
class="board gl-display-inline-block gl-h-full gl-px-3 gl-vertical-align-top gl-white-space-normal is-expandable"
data-qa-selector="board_list"
>
<div
......
......@@ -103,9 +103,6 @@ export default {
:key="list.id"
ref="board"
:can-admin-list="canAdminList"
:class="{
'is-draggable': !list.preset,
}"
:list="list"
:disabled="disabled"
/>
......
......@@ -9,15 +9,22 @@ import {
GlSprintf,
GlTooltipDirective,
} from '@gitlab/ui';
import { n__, s__ } from '~/locale';
import { n__, s__, __ } from '~/locale';
import AccessorUtilities from '../../lib/utils/accessor';
import IssueCount from './issue_count.vue';
import eventHub from '../eventhub';
import sidebarEventHub from '~/sidebar/event_hub';
import { inactiveId, LIST, ListType } from '../constants';
import { isScopedLabel } from '~/lib/utils/common_utils';
import { isListDraggable } from '~/boards/boards_util';
export default {
i18n: {
newIssue: __('New issue'),
listSettings: __('List settings'),
expand: s__('Boards|Expand'),
collapse: s__('Boards|Collapse'),
},
components: {
GlButtonGroup,
GlButton,
......@@ -66,47 +73,47 @@ export default {
return Boolean(this.currentUserId);
},
listType() {
return this.list.type;
return this.list.listType;
},
listAssignee() {
return this.list?.assignee?.username || '';
},
listTitle() {
return this.list?.label?.description || this.list.title || '';
return this.list?.label?.description || this.list?.assignee?.name || this.list.title || '';
},
showListHeaderButton() {
return !this.disabled && this.listType !== ListType.closed;
},
showMilestoneListDetails() {
return (
this.list.type === ListType.milestone &&
this.listType === ListType.milestone &&
this.list.milestone &&
(this.list.isExpanded || !this.isSwimlanesHeader)
(!this.list.collapsed || !this.isSwimlanesHeader)
);
},
showAssigneeListDetails() {
return (
this.list.type === ListType.assignee && (this.list.isExpanded || !this.isSwimlanesHeader)
this.listType === ListType.assignee && (!this.list.collapsed || !this.isSwimlanesHeader)
);
},
issuesCount() {
return this.list.issuesSize;
return this.list.issuesCount;
},
issuesTooltipLabel() {
return n__(`%d issue`, `%d issues`, this.issuesCount);
},
chevronTooltip() {
return this.list.isExpanded ? s__('Boards|Collapse') : s__('Boards|Expand');
return this.list.collapsed ? this.$options.i18n.expand : this.$options.i18n.collapse;
},
chevronIcon() {
return this.list.isExpanded ? 'chevron-right' : 'chevron-down';
return this.list.collapsed ? 'chevron-down' : 'chevron-right';
},
isNewIssueShown() {
return this.listType === ListType.backlog || this.showListHeaderButton;
},
isSettingsShown() {
return (
this.listType !== ListType.backlog && this.showListHeaderButton && this.list.isExpanded
this.listType !== ListType.backlog && this.showListHeaderButton && !this.list.collapsed
);
},
uniqueKey() {
......@@ -119,6 +126,9 @@ export default {
headerStyle() {
return { borderTopColor: this.list?.label?.color };
},
userCanDrag() {
return !this.disabled && isListDraggable(this.list);
},
},
methods: {
...mapActions(['updateList', 'setActiveId']),
......@@ -137,7 +147,7 @@ export default {
eventHub.$emit(`toggle-issue-form-${this.list.id}`);
},
toggleExpanded() {
this.list.isExpanded = !this.list.isExpanded;
this.list.collapsed = !this.list.collapsed;
if (!this.isLoggedIn) {
this.addToLocalStorage();
......@@ -151,11 +161,11 @@ export default {
},
addToLocalStorage() {
if (AccessorUtilities.isLocalStorageAccessSafe()) {
localStorage.setItem(`${this.uniqueKey}.expanded`, this.list.isExpanded);
localStorage.setItem(`${this.uniqueKey}.expanded`, !this.list.collapsed);
}
},
updateListFunction() {
this.updateList({ listId: this.list.id, collapsed: !this.list.isExpanded });
this.updateList({ listId: this.list.id, collapsed: this.list.collapsed });
},
},
};
......@@ -165,7 +175,7 @@ export default {
<header
:class="{
'has-border': list.label && list.label.color,
'gl-h-full': !list.isExpanded,
'gl-h-full': list.collapsed,
'board-inner gl-rounded-top-left-base gl-rounded-top-right-base': isSwimlanesHeader,
}"
:style="headerStyle"
......@@ -175,16 +185,15 @@ export default {
>
<h3
:class="{
'user-can-drag': !disabled && !list.preset,
'gl-py-3 gl-h-full': !list.isExpanded && !isSwimlanesHeader,
'gl-border-b-0': !list.isExpanded || isSwimlanesHeader,
'gl-py-2': !list.isExpanded && isSwimlanesHeader,
'gl-flex-direction-column': !list.isExpanded,
'user-can-drag': userCanDrag,
'gl-py-3 gl-h-full': list.collapsed && !isSwimlanesHeader,
'gl-border-b-0': list.collapsed || isSwimlanesHeader,
'gl-py-2': list.collapsed && isSwimlanesHeader,
'gl-flex-direction-column': list.collapsed,
}"
class="board-title gl-m-0 gl-display-flex gl-align-items-center gl-font-base gl-px-3 js-board-handle"
>
<gl-button
v-if="list.isExpandable"
v-gl-tooltip.hover
:aria-label="chevronTooltip"
:title="chevronTooltip"
......@@ -200,8 +209,8 @@ export default {
aria-hidden="true"
class="milestone-icon"
:class="{
'gl-mt-3 gl-rotate-90': !list.isExpanded,
'gl-mr-2': list.isExpanded,
'gl-mt-3 gl-rotate-90': list.collapsed,
'gl-mr-2': !list.collapsed,
}"
>
<gl-icon name="timer" />
......@@ -209,17 +218,17 @@ export default {
<a
v-if="showAssigneeListDetails"
:href="list.assignee.path"
:href="list.assignee.webUrl"
class="user-avatar-link js-no-trigger"
:class="{
'gl-mt-3 gl-rotate-90': !list.isExpanded,
'gl-mt-3 gl-rotate-90': list.collapsed,
}"
>
<img
v-gl-tooltip.hover.bottom
:title="listAssignee"
:alt="list.assignee.name"
:src="list.assignee.avatar"
:src="list.assignee.avatarUrl"
class="avatar s20"
height="20"
width="20"
......@@ -229,9 +238,9 @@ export default {
<div
class="board-title-text"
:class="{
'gl-display-none': !list.isExpanded && isSwimlanesHeader,
'gl-flex-grow-0 gl-my-3 gl-mx-0': !list.isExpanded,
'gl-flex-grow-1': list.isExpanded,
'gl-display-none': list.collapsed && isSwimlanesHeader,
'gl-flex-grow-0 gl-my-3 gl-mx-0': list.collapsed,
'gl-flex-grow-1': !list.collapsed,
}"
>
<!-- EE start -->
......@@ -239,16 +248,16 @@ export default {
v-if="listType !== 'label'"
v-gl-tooltip.hover
:class="{
'gl-display-block': !list.isExpanded || listType === 'milestone',
'gl-display-block': list.collapsed || listType === 'milestone',
}"
:title="listTitle"
class="board-title-main-text gl-text-truncate"
>
{{ list.title }}
{{ listTitle }}
</span>
<span
v-if="listType === 'assignee'"
v-show="list.isExpanded"
v-show="!list.collapsed"
class="gl-ml-2 gl-font-weight-normal gl-text-gray-500"
>
@{{ listAssignee }}
......@@ -260,21 +269,21 @@ export default {
:background-color="list.label.color"
:description="list.label.description"
:scoped="showScopedLabels(list.label)"
:size="!list.isExpanded ? 'sm' : ''"
:size="list.collapsed ? 'sm' : ''"
:title="list.label.title"
/>
</div>
<!-- EE start -->
<span
v-if="isSwimlanesHeader && !list.isExpanded"
v-if="isSwimlanesHeader && list.collapsed"
ref="collapsedInfo"
aria-hidden="true"
class="board-header-collapsed-info-icon gl-cursor-pointer gl-text-gray-500"
>
<gl-icon name="information" />
</span>
<gl-tooltip v-if="isSwimlanesHeader && !list.isExpanded" :target="() => $refs.collapsedInfo">
<gl-tooltip v-if="isSwimlanesHeader && list.collapsed" :target="() => $refs.collapsedInfo">
<div class="gl-font-weight-bold gl-pb-2">{{ collapsedTooltipTitle }}</div>
<div v-if="list.maxIssueCount !== 0">
......@@ -296,8 +305,8 @@ export default {
<div
class="issue-count-badge gl-display-inline-flex gl-pr-0 no-drag gl-text-gray-500"
:class="{
'gl-display-none!': !list.isExpanded && isSwimlanesHeader,
'gl-p-0': !list.isExpanded,
'gl-display-none!': list.collapsed && isSwimlanesHeader,
'gl-p-0': list.collapsed,
}"
>
<span class="gl-display-inline-flex">
......@@ -323,11 +332,11 @@ export default {
>
<gl-button
v-if="isNewIssueShown"
v-show="list.isExpanded"
v-show="!list.collapsed"
ref="newIssueBtn"
v-gl-tooltip.hover
:aria-label="__('New issue')"
:title="__('New issue')"
:aria-label="$options.i18n.newIssue"
:title="$options.i18n.newIssue"
class="issue-count-badge-add-button no-drag"
icon="plus"
@click="showNewIssueForm"
......@@ -337,13 +346,13 @@ export default {
v-if="isSettingsShown"
ref="settingsBtn"
v-gl-tooltip.hover
:aria-label="__('List settings')"
:aria-label="$options.i18n.listSettings"
class="no-drag js-board-settings-button"
:title="__('List settings')"
:title="$options.i18n.listSettings"
icon="settings"
@click="openSidebarSettings"
/>
<gl-tooltip :target="() => $refs.settingsBtn">{{ __('List settings') }}</gl-tooltip>
<gl-tooltip :target="() => $refs.settingsBtn">{{ $options.i18n.listSettings }}</gl-tooltip>
</gl-button-group>
</h3>
</header>
......
......@@ -12,6 +12,11 @@ import { sprintf, __ } from '~/locale';
export default {
name: 'BoardList',
i18n: {
loadingIssues: __('Loading issues'),
loadingMoreissues: __('Loading more issues'),
showingAllIssues: __('Showing all issues'),
},
components: {
BoardCard,
BoardNewIssue,
......@@ -49,11 +54,11 @@ export default {
paginatedIssueText() {
return sprintf(__('Showing %{pageSize} of %{total} issues'), {
pageSize: this.issues.length,
total: this.list.issuesSize,
total: this.list.issuesCount,
});
},
issuesSizeExceedsMax() {
return this.list.maxIssueCount > 0 && this.list.issuesSize > this.list.maxIssueCount;
return this.list.maxIssueCount > 0 && this.list.issuesCount > this.list.maxIssueCount;
},
hasNextPage() {
return this.pageInfoByListId[this.list.id].hasNextPage;
......@@ -61,10 +66,16 @@ export default {
loading() {
return this.listsFlags[this.list.id]?.isLoading;
},
loadingMore() {
return this.listsFlags[this.list.id]?.isLoadingMore;
},
listRef() {
// When list is draggable, the reference to the list needs to be accessed differently
return this.canAdminList ? this.$refs.list.$el : this.$refs.list;
},
showingAllIssues() {
return this.issues.length === this.list.issuesCount;
},
treeRootWrapper() {
return this.canAdminList ? Draggable : 'ul';
},
......@@ -72,7 +83,7 @@ export default {
const options = {
...defaultSortableConfig,
fallbackOnBody: false,
group: 'boards-list',
group: 'board-list',
tag: 'ul',
'ghost-class': 'board-card-drag-active',
'data-list-id': this.list.id,
......@@ -85,7 +96,6 @@ export default {
watch: {
filters: {
handler() {
this.list.loadingMore = false;
this.listRef.scrollTop = 0;
},
deep: true,
......@@ -124,13 +134,7 @@ export default {
this.listRef.scrollTop = 0;
},
loadNextPage() {
const loadingDone = () => {
this.list.loadingMore = false;
};
this.list.loadingMore = true;
this.fetchIssuesForList({ listId: this.list.id, fetchNext: true })
.then(loadingDone)
.catch(loadingDone);
this.fetchIssuesForList({ listId: this.list.id, fetchNext: true });
},
toggleForm() {
this.showIssueForm = !this.showIssueForm;
......@@ -138,7 +142,7 @@ export default {
onScroll() {
window.requestAnimationFrame(() => {
if (
!this.list.loadingMore &&
!this.loadingMore &&
this.scrollTop() > this.scrollHeight() - this.scrollOffset &&
this.hasNextPage
) {
......@@ -198,26 +202,26 @@ export default {
<template>
<div
v-show="list.isExpanded"
v-show="!list.collapsed"
class="board-list-component gl-relative gl-h-full gl-display-flex gl-flex-direction-column"
data-qa-selector="board_list_cards_area"
>
<div
v-if="loading"
class="gl-mt-4 gl-text-center"
:aria-label="__('Loading issues')"
:aria-label="$options.i18n.loadingIssues"
data-testid="board_list_loading"
>
<gl-loading-icon />
</div>
<board-new-issue v-if="list.type !== 'closed' && showIssueForm" :list="list" />
<board-new-issue v-if="list.listType !== 'closed' && showIssueForm" :list="list" />
<component
:is="treeRootWrapper"
v-show="!loading"
ref="list"
v-bind="treeRootOptions"
:data-board="list.id"
:data-board-type="list.type"
:data-board-type="list.listType"
:class="{ 'bg-danger-100': issuesSizeExceedsMax }"
class="board-list gl-w-full gl-h-full gl-list-style-none gl-mb-0 gl-p-2 js-board-list"
data-testid="tree-root-wrapper"
......@@ -234,8 +238,8 @@ export default {
:disabled="disabled"
/>
<li v-if="showCount" class="board-list-count gl-text-center" data-issue-id="-1">
<gl-loading-icon v-show="list.loadingMore" label="Loading more issues" />
<span v-if="issues.length === list.issuesSize">{{ __('Showing all issues') }}</span>
<gl-loading-icon v-if="loadingMore" :label="$options.i18n.loadingMoreissues" />
<span v-if="showingAllIssues">{{ $options.i18n.showingAllIssues }}</span>
<span v-else>{{ paginatedIssueText }}</span>
</li>
</component>
......
......@@ -53,7 +53,7 @@ export default {
return this.activeList.label;
},
boardListType() {
return this.activeList.type || null;
return this.activeList.type || this.activeList.listType || null;
},
listTypeTitle() {
return this.$options.labelListText;
......
......@@ -10,6 +10,7 @@ import IssueDueDate from './issue_due_date.vue';
import IssueTimeEstimate from './issue_time_estimate.vue';
import boardsStore from '../stores/boards_store';
import { isScopedLabel } from '~/lib/utils/common_utils';
import { ListType } from '../constants';
export default {
components: {
......@@ -122,7 +123,13 @@ export default {
return true;
},
isNonListLabel(label) {
return label.id && !(this.list.type === 'label' && this.list.title === label.title);
return (
label.id &&
!(
(this.list.type || this.list.listType) === ListType.label &&
this.list.title === label.title
)
);
},
filterByLabel(label) {
if (!this.updateFilters) return;
......
......@@ -7,6 +7,7 @@ import eventHub from '../eventhub';
import Api from '../../api';
import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
import { ListType } from '../constants';
export default {
name: 'BoardProjectSelect',
......@@ -53,7 +54,7 @@ export default {
this.loading = true;
const additionalAttrs = {};
if (this.list.type && this.list.type !== 'backlog') {
if ((this.list.type || this.list.listType) !== ListType.backlog) {
additionalAttrs.min_access_level = featureAccessLevel.EVERYONE;
}
......
......@@ -12,7 +12,6 @@ import {
formatListsPageInfo,
formatIssue,
} from '../boards_util';
import boardStore from '~/boards/stores/boards_store';
import createFlash from '~/flash';
import { __ } from '~/locale';
import updateAssigneesMutation from '~/vue_shared/components/sidebar/queries/updateAssignees.mutation.graphql';
......@@ -119,11 +118,7 @@ export default {
},
addList: ({ commit }, list) => {
// Temporarily using positioning logic from boardStore
commit(
types.RECEIVE_ADD_LIST_SUCCESS,
boardStore.updateListPosition({ ...list, doNotFetchIssues: true }),
);
commit(types.RECEIVE_ADD_LIST_SUCCESS, list);
},
fetchLabels: ({ state, commit }, searchTerm) => {
......
......@@ -13,7 +13,7 @@ const notImplemented = () => {
export const removeIssueFromList = ({ state, listId, issueId }) => {
Vue.set(state.issuesByListId, listId, pull(state.issuesByListId[listId], issueId));
const list = state.boardLists[listId];
Vue.set(state.boardLists, listId, { ...list, issuesSize: list.issuesSize - 1 });
Vue.set(state.boardLists, listId, { ...list, issuesCount: list.issuesCount - 1 });
};
export const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfterId, atIndex }) => {
......@@ -27,7 +27,7 @@ export const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfter
listIssues.splice(newIndex, 0, issueId);
Vue.set(state.issuesByListId, listId, listIssues);
const list = state.boardLists[listId];
Vue.set(state.boardLists, listId, { ...list, issuesSize: list.issuesSize + 1 });
Vue.set(state.boardLists, listId, { ...list, issuesCount: list.issuesCount + 1 });
};
export default {
......
......@@ -8,6 +8,7 @@ import defaultSortableConfig from '~/sortable/sortable_config';
import { n__ } from '~/locale';
import EpicLane from './epic_lane.vue';
import IssuesLaneList from './issues_lane_list.vue';
import { isListDraggable } from '~/boards/boards_util';
export default {
components: {
......@@ -94,6 +95,9 @@ export default {
}
});
},
isListDraggable(list) {
return isListDraggable(list);
},
},
};
</script>
......@@ -115,8 +119,8 @@ export default {
v-for="list in lists"
:key="list.id"
:class="{
'is-collapsed': !list.isExpanded,
'is-draggable': !list.preset,
'is-collapsed': list.collapsed,
'is-draggable': isListDraggable(list),
}"
class="board gl-display-inline-block gl-px-3 gl-vertical-align-top gl-white-space-normal"
:data-list-id="list.id"
......
......@@ -155,7 +155,7 @@ export default {
<template>
<div
class="board gl-px-3 gl-vertical-align-top gl-white-space-normal gl-display-flex gl-flex-shrink-0"
:class="{ 'is-collapsed': !list.isExpanded }"
:class="{ 'is-collapsed': list.collapsed }"
>
<div class="board-inner gl-rounded-base gl-relative gl-w-full">
<board-new-issue
......@@ -164,7 +164,7 @@ export default {
/>
<component
:is="treeRootWrapper"
v-if="list.isExpanded"
v-if="!list.collapsed"
v-bind="treeRootOptions"
class="board-cell gl-p-2 gl-m-0 gl-h-full"
data-testid="tree-root-wrapper"
......
......@@ -212,7 +212,7 @@ export default {
const list = data.boardListUpdateLimitMetrics?.list;
commit(types.UPDATE_LIST_SUCCESS, {
listId,
list: boardsStore.updateListPosition({ ...list, doNotFetchIssues: true }),
list,
});
}
})
......
......@@ -3,9 +3,8 @@ import Vuex from 'vuex';
import BoardListHeader from 'ee/boards/components/board_list_header_new.vue';
import getters from 'ee/boards/stores/getters';
import { listObj } from 'jest/boards/mock_data';
import { mockLabelList } from 'jest/boards/mock_data';
import { ListType, inactiveId } from '~/boards/constants';
import List from '~/boards/models/list';
import sidebarEventHub from '~/sidebar/event_hub';
const localVue = createLocalVue();
......@@ -38,21 +37,19 @@ describe('Board List Header Component', () => {
const boardId = '1';
const listMock = {
...listObj,
list_type: listType,
...mockLabelList,
listType,
collapsed,
};
if (listType === ListType.assignee) {
delete listMock.label;
listMock.user = {};
listMock.assignee = {};
}
const list = new List({ ...listMock, doNotFetchIssues: true });
if (withLocalStorage) {
localStorage.setItem(
`boards.${boardId}.${list.type}.${list.id}.expanded`,
`boards.${boardId}.${listMock.listType}.${listMock.id}.expanded`,
(!collapsed).toString(),
);
}
......@@ -62,7 +59,7 @@ describe('Board List Header Component', () => {
localVue,
propsData: {
disabled: false,
list,
list: listMock,
isSwimlanesHeader,
},
provide: {
......@@ -112,7 +109,7 @@ describe('Board List Header Component', () => {
});
it('does not emit event when there is an active List', () => {
store.state.activeId = listObj.id;
store.state.activeId = mockLabelList.id;
createComponent({ listType: hasSettings[0] });
wrapper.vm.openSidebarSettings();
......
......@@ -4,7 +4,7 @@ import Vuex from 'vuex';
import EpicLane from 'ee/boards/components/epic_lane.vue';
import IssuesLaneList from 'ee/boards/components/issues_lane_list.vue';
import getters from 'ee/boards/stores/getters';
import { mockEpic, mockListsWithModel, mockIssuesByListId, issues } from '../mock_data';
import { mockEpic, mockLists, mockIssuesByListId, issues } from '../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
......@@ -42,7 +42,7 @@ describe('EpicLane', () => {
const defaultProps = {
epic: mockEpic,
lists: mockListsWithModel,
lists: mockLists,
disabled: false,
};
......
......@@ -7,7 +7,7 @@ import EpicsSwimlanes from 'ee/boards/components/epics_swimlanes.vue';
import IssueLaneList from 'ee/boards/components/issues_lane_list.vue';
import getters from 'ee/boards/stores/getters';
import BoardListHeader from 'ee_else_ce/boards/components/board_list_header_new.vue';
import { mockListsWithModel, mockEpics, mockIssuesByListId, issues } from '../mock_data';
import { mockLists, mockEpics, mockIssuesByListId, issues } from '../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
......@@ -41,7 +41,7 @@ describe('EpicsSwimlanes', () => {
const createComponent = (props = {}) => {
const store = createStore();
const defaultProps = {
lists: mockListsWithModel,
lists: mockLists,
disabled: false,
};
......
import { shallowMount } from '@vue/test-utils';
import IssuesLaneList from 'ee/boards/components/issues_lane_list.vue';
import { listObj } from 'jest/boards/mock_data';
import { mockList } from 'jest/boards/mock_data';
import BoardCard from '~/boards/components/board_card_layout.vue';
import { ListType } from '~/boards/constants';
import List from '~/boards/models/list';
import { createStore } from '~/boards/stores';
import { mockIssues } from '../mock_data';
......@@ -13,8 +12,8 @@ describe('IssuesLaneList', () => {
const createComponent = ({ listType = ListType.backlog, collapsed = false } = {}) => {
const listMock = {
...listObj,
list_type: listType,
...mockList,
listType,
collapsed,
};
......@@ -23,12 +22,10 @@ describe('IssuesLaneList', () => {
listMock.user = {};
}
const list = new List({ ...listMock, doNotFetchIssues: true });
wrapper = shallowMount(IssuesLaneList, {
store,
propsData: {
list,
list: listMock,
issues: mockIssues,
disabled: false,
canAdminList: true,
......
......@@ -10,14 +10,7 @@ import { formatListIssues } from '~/boards/boards_util';
import * as typesCE from '~/boards/stores/mutation_types';
import * as commonUtils from '~/lib/utils/common_utils';
import { mergeUrlParams, removeParams } from '~/lib/utils/url_utility';
import {
mockLists,
mockIssue,
mockIssue2,
mockEpic,
rawIssue,
mockListsWithModel,
} from '../mock_data';
import { mockLists, mockIssue, mockIssue2, mockEpic, rawIssue } from '../mock_data';
const expectNotImplemented = action => {
it('is not implemented', () => {
......@@ -605,7 +598,7 @@ describe('moveIssue', () => {
endpoints: { fullPath: 'gitlab-org', boardId: '1' },
boardType: 'group',
disabled: false,
boardLists: mockListsWithModel,
boardLists: mockLists,
issuesByListId: listIssues,
issues,
};
......
import mutations from 'ee/boards/stores/mutations';
import { mockIssue, mockIssue2, mockEpics, mockEpic, mockListsWithModel } from '../mock_data';
import { mockIssue, mockIssue2, mockEpics, mockEpic, mockLists } from '../mock_data';
const expectNotImplemented = action => {
it('is not implemented', () => {
......@@ -10,8 +10,8 @@ const expectNotImplemented = action => {
const epicId = mockEpic.id;
const initialBoardListsState = {
'gid://gitlab/List/1': mockListsWithModel[0],
'gid://gitlab/List/2': mockListsWithModel[1],
'gid://gitlab/List/1': mockLists[0],
'gid://gitlab/List/2': mockLists[1],
};
let state = {
......
......@@ -16504,6 +16504,9 @@ msgstr ""
msgid "Loading issues"
msgstr ""
msgid "Loading more issues"
msgstr ""
msgid "Loading snippet"
msgstr ""
......
/* global List */
import Vuex from 'vuex';
import { useFakeRequestAnimationFrame } from 'helpers/fake_request_animation_frame';
import { createLocalVue, mount } from '@vue/test-utils';
......@@ -7,7 +5,7 @@ import eventHub from '~/boards/eventhub';
import BoardList from '~/boards/components/board_list_new.vue';
import BoardCard from '~/boards/components/board_card.vue';
import '~/boards/models/list';
import { listObj, mockIssuesByListId, issues, mockIssues } from './mock_data';
import { mockList, mockIssuesByListId, issues, mockIssues } from './mock_data';
import defaultState from '~/boards/stores/state';
const localVue = createLocalVue();
......@@ -44,12 +42,10 @@ const createComponent = ({
...state,
});
const list = new List({
...listObj,
id: 'gid://gitlab/List/1',
const list = {
...mockList,
...listProps,
doNotFetchIssues: true,
});
};
const issue = {
title: 'Testing',
id: 1,
......@@ -59,8 +55,8 @@ const createComponent = ({
assignees: [],
...listIssueProps,
};
if (!Object.prototype.hasOwnProperty.call(listProps, 'issuesSize')) {
list.issuesSize = 1;
if (!Object.prototype.hasOwnProperty.call(listProps, 'issuesCount')) {
list.issuesCount = 1;
}
const component = mount(BoardList, {
......@@ -158,7 +154,7 @@ describe('Board list component', () => {
it('shows how many more issues to load', async () => {
wrapper.vm.showCount = true;
wrapper.setProps({ list: { issuesSize: 20 } });
wrapper.setProps({ list: { issuesCount: 20 } });
await wrapper.vm.$nextTick();
expect(wrapper.find('.board-list-count').text()).toBe('Showing 1 of 20 issues');
......@@ -168,7 +164,7 @@ describe('Board list component', () => {
describe('load more issues', () => {
beforeEach(() => {
wrapper = createComponent({
listProps: { issuesSize: 25 },
listProps: { issuesCount: 25 },
});
});
......@@ -179,15 +175,19 @@ describe('Board list component', () => {
});
it('does not load issues if already loading', () => {
wrapper.vm.listRef.dispatchEvent(new Event('scroll'));
wrapper = createComponent({
state: { listsFlags: { 'gid://gitlab/List/1': { isLoadingMore: true } } },
});
wrapper.vm.listRef.dispatchEvent(new Event('scroll'));
expect(actions.fetchIssuesForList).toHaveBeenCalledTimes(1);
expect(actions.fetchIssuesForList).not.toHaveBeenCalled();
});
it('shows loading more spinner', async () => {
wrapper = createComponent({
state: { listsFlags: { 'gid://gitlab/List/1': { isLoadingMore: true } } },
});
wrapper.vm.showCount = true;
wrapper.vm.list.loadingMore = true;
await wrapper.vm.$nextTick();
expect(wrapper.find('.board-list-count .gl-spinner').exists()).toBe(true);
......@@ -197,13 +197,13 @@ describe('Board list component', () => {
describe('max issue count warning', () => {
beforeEach(() => {
wrapper = createComponent({
listProps: { issuesSize: 50 },
listProps: { issuesCount: 50 },
});
});
describe('when issue count exceeds max issue count', () => {
it('sets background to bg-danger-100', async () => {
wrapper.setProps({ list: { issuesSize: 4, maxIssueCount: 3 } });
wrapper.setProps({ list: { issuesCount: 4, maxIssueCount: 3 } });
await wrapper.vm.$nextTick();
expect(wrapper.find('.bg-danger-100').exists()).toBe(true);
......@@ -212,7 +212,7 @@ describe('Board list component', () => {
describe('when list issue count does NOT exceed list max issue count', () => {
it('does not sets background to bg-danger-100', () => {
wrapper.setProps({ list: { issuesSize: 2, maxIssueCount: 3 } });
wrapper.setProps({ list: { issuesCount: 2, maxIssueCount: 3 } });
expect(wrapper.find('.bg-danger-100').exists()).toBe(false);
});
......
......@@ -2,7 +2,6 @@ import { shallowMount } from '@vue/test-utils';
import { listObj } from 'jest/boards/mock_data';
import BoardColumn from '~/boards/components/board_column_new.vue';
import List from '~/boards/models/list';
import { ListType } from '~/boards/constants';
import { createStore } from '~/boards/stores';
......@@ -20,24 +19,22 @@ describe('Board Column Component', () => {
const listMock = {
...listObj,
list_type: listType,
listType,
collapsed,
};
if (listType === ListType.assignee) {
delete listMock.label;
listMock.user = {};
listMock.assignee = {};
}
const list = new List({ ...listMock, doNotFetchIssues: true });
store = createStore();
wrapper = shallowMount(BoardColumn, {
store,
propsData: {
disabled: false,
list,
list: listMock,
},
provide: {
boardId,
......@@ -60,7 +57,7 @@ describe('Board Column Component', () => {
it('has class is-collapsed when list is collapsed', () => {
createComponent({ collapsed: false });
expect(wrapper.vm.list.isExpanded).toBe(true);
expect(isCollapsed()).toBe(false);
});
it('does not have class is-collapsed when list is expanded', () => {
......
......@@ -5,7 +5,7 @@ import Draggable from 'vuedraggable';
import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue';
import getters from 'ee_else_ce/boards/stores/getters';
import BoardColumn from '~/boards/components/board_column.vue';
import { mockListsWithModel } from '../mock_data';
import { mockLists, mockListsWithModel } from '../mock_data';
import BoardContent from '~/boards/components/board_content.vue';
const localVue = createLocalVue();
......@@ -20,7 +20,7 @@ describe('BoardContent', () => {
const defaultState = {
isShowingEpicsSwimlanes: false,
boardLists: mockListsWithModel,
boardLists: mockLists,
error: undefined,
};
......@@ -59,7 +59,7 @@ describe('BoardContent', () => {
it('renders a BoardColumn component per list', () => {
createComponent();
expect(wrapper.findAll(BoardColumn)).toHaveLength(mockListsWithModel.length);
expect(wrapper.findAll(BoardColumn)).toHaveLength(mockLists.length);
});
it('does not display EpicsSwimlanes component', () => {
......
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { listObj } from 'jest/boards/mock_data';
import { mockLabelList } from 'jest/boards/mock_data';
import BoardListHeader from '~/boards/components/board_list_header_new.vue';
import List from '~/boards/models/list';
import { ListType } from '~/boards/constants';
const localVue = createLocalVue();
......@@ -32,21 +31,19 @@ describe('Board List Header Component', () => {
const boardId = '1';
const listMock = {
...listObj,
list_type: listType,
...mockLabelList,
listType,
collapsed,
};
if (listType === ListType.assignee) {
delete listMock.label;
listMock.user = {};
listMock.assignee = {};
}
const list = new List({ ...listMock, doNotFetchIssues: true });
if (withLocalStorage) {
localStorage.setItem(
`boards.${boardId}.${list.type}.${list.id}.expanded`,
`boards.${boardId}.${listMock.listType}.${listMock.id}.expanded`,
(!collapsed).toString(),
);
}
......@@ -62,7 +59,7 @@ describe('Board List Header Component', () => {
localVue,
propsData: {
disabled: false,
list,
list: listMock,
},
provide: {
boardId,
......@@ -72,10 +69,11 @@ describe('Board List Header Component', () => {
});
};
const isExpanded = () => wrapper.vm.list.isExpanded;
const isCollapsed = () => !isExpanded();
const isCollapsed = () => wrapper.vm.list.collapsed;
const isExpanded = () => !isCollapsed;
const findAddIssueButton = () => wrapper.find({ ref: 'newIssueBtn' });
const findTitle = () => wrapper.find('.board-title');
const findCaret = () => wrapper.find('.board-title-caret');
describe('Add issue button', () => {
......@@ -125,7 +123,7 @@ describe('Board List Header Component', () => {
it('collapses expanded Column when clicking the collapse icon', async () => {
createComponent();
expect(isExpanded()).toBe(true);
expect(isCollapsed()).toBe(false);
findCaret().vm.$emit('click');
......@@ -166,4 +164,24 @@ describe('Board List Header Component', () => {
expect(localStorage.getItem(`${wrapper.vm.uniqueKey}.expanded`)).toBe(String(isExpanded()));
});
});
describe('user can drag', () => {
const cannotDragList = [ListType.backlog, ListType.closed];
const canDragList = [ListType.label, ListType.milestone, ListType.assignee];
it.each(cannotDragList)(
'does not have user-can-drag-class so user cannot drag list',
listType => {
createComponent({ listType });
expect(findTitle().classes()).not.toContain('user-can-drag');
},
);
it.each(canDragList)('has user-can-drag-class so user can drag list', listType => {
createComponent({ listType });
expect(findTitle().classes()).toContain('user-can-drag');
});
});
});
......@@ -3,7 +3,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import BoardNewIssue from '~/boards/components/board_new_issue_new.vue';
import '~/boards/models/list';
import { mockListsWithModel } from '../mock_data';
import { mockList } from '../mock_data';
const localVue = createLocalVue();
......@@ -37,7 +37,7 @@ describe('Issue boards new issue form', () => {
wrapper = shallowMount(BoardNewIssue, {
propsData: {
disabled: false,
list: mockListsWithModel[0],
list: mockList,
},
store,
localVue,
......
......@@ -97,7 +97,7 @@ export const mockMilestone = {
due_date: '2019-12-31',
};
const assignees = [
export const assignees = [
{
id: 'gid://gitlab/User/2',
username: 'angelina.herman',
......@@ -282,8 +282,7 @@ export const setMockEndpoints = (opts = {}) => {
});
};
export const mockLists = [
{
export const mockList = {
id: 'gid://gitlab/List/1',
title: 'Backlog',
position: null,
......@@ -293,9 +292,10 @@ export const mockLists = [
assignee: null,
milestone: null,
loading: false,
issuesSize: 1,
},
{
issuesCount: 1,
};
export const mockLabelList = {
id: 'gid://gitlab/List/2',
title: 'To Do',
position: 0,
......@@ -311,9 +311,10 @@ export const mockLists = [
assignee: null,
milestone: null,
loading: false,
issuesSize: 0,
},
];
issuesCount: 0,
};
export const mockLists = [mockList, mockLabelList];
export const mockListsById = keyBy(mockLists, 'id');
......
import testAction from 'helpers/vuex_action_helper';
import {
mockListsWithModel,
mockLists,
mockListsById,
mockIssue,
......@@ -229,8 +228,8 @@ 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],
'gid://gitlab/List/1': mockLists[0],
'gid://gitlab/List/2': mockLists[1],
};
const state = {
......@@ -252,7 +251,7 @@ describe('moveList', () => {
[
{
type: types.MOVE_LIST,
payload: { movedList: mockListsWithModel[0], listAtNewIndex: mockListsWithModel[1] },
payload: { movedList: mockLists[0], listAtNewIndex: mockLists[1] },
},
],
[
......@@ -271,8 +270,8 @@ describe('moveList', () => {
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],
'gid://gitlab/List/1': mockLists[0],
'gid://gitlab/List/2': mockLists[1],
};
const state = {
......@@ -512,7 +511,7 @@ describe('moveIssue', () => {
endpoints: { fullPath: 'gitlab-org', boardId: '1' },
boardType: 'group',
disabled: false,
boardLists: mockListsWithModel,
boardLists: mockLists,
issuesByListId: listIssues,
issues,
};
......
......@@ -6,7 +6,7 @@ import {
mockIssues,
mockIssuesByListId,
issues,
mockListsWithModel,
mockLists,
} from '../mock_data';
describe('Boards - Getters', () => {
......@@ -94,22 +94,22 @@ describe('Boards - Getters', () => {
const boardsState = {
boardLists: {
'gid://gitlab/List/1': mockListsWithModel[0],
'gid://gitlab/List/2': mockListsWithModel[1],
'gid://gitlab/List/1': mockLists[0],
'gid://gitlab/List/2': mockLists[1],
},
};
describe('getListByLabelId', () => {
it('returns list for a given label id', () => {
expect(getters.getListByLabelId(boardsState)('gid://gitlab/GroupLabel/121')).toEqual(
mockListsWithModel[1],
mockLists[1],
);
});
});
describe('getListByTitle', () => {
it('returns list for a given list title', () => {
expect(getters.getListByTitle(boardsState)('To Do')).toEqual(mockListsWithModel[1]);
expect(getters.getListByTitle(boardsState)('To Do')).toEqual(mockLists[1]);
});
});
});
import mutations from '~/boards/stores/mutations';
import * as types from '~/boards/stores/mutation_types';
import defaultState from '~/boards/stores/state';
import { mockListsWithModel, mockLists, rawIssue, mockIssue, mockIssue2 } from '../mock_data';
import { mockLists, rawIssue, mockIssue, mockIssue2 } from '../mock_data';
const expectNotImplemented = action => {
it('is not implemented', () => {
......@@ -13,8 +13,8 @@ describe('Board Store Mutations', () => {
let state;
const initialBoardListsState = {
'gid://gitlab/List/1': mockListsWithModel[0],
'gid://gitlab/List/2': mockListsWithModel[1],
'gid://gitlab/List/1': mockLists[0],
'gid://gitlab/List/2': mockLists[1],
};
beforeEach(() => {
......@@ -124,10 +124,10 @@ describe('Board Store Mutations', () => {
describe('RECEIVE_ADD_LIST_SUCCESS', () => {
it('adds list to boardLists state', () => {
mutations.RECEIVE_ADD_LIST_SUCCESS(state, mockListsWithModel[0]);
mutations.RECEIVE_ADD_LIST_SUCCESS(state, mockLists[0]);
expect(state.boardLists).toEqual({
[mockListsWithModel[0].id]: mockListsWithModel[0],
[mockLists[0].id]: mockLists[0],
});
});
});
......@@ -144,13 +144,13 @@ describe('Board Store Mutations', () => {
};
mutations.MOVE_LIST(state, {
movedList: mockListsWithModel[0],
listAtNewIndex: mockListsWithModel[1],
movedList: mockLists[0],
listAtNewIndex: mockLists[1],
});
expect(state.boardLists).toEqual({
'gid://gitlab/List/2': mockListsWithModel[1],
'gid://gitlab/List/1': mockListsWithModel[0],
'gid://gitlab/List/2': mockLists[1],
'gid://gitlab/List/1': mockLists[0],
});
});
});
......@@ -160,8 +160,8 @@ describe('Board Store Mutations', () => {
state = {
...state,
boardLists: {
'gid://gitlab/List/2': mockListsWithModel[1],
'gid://gitlab/List/1': mockListsWithModel[0],
'gid://gitlab/List/2': mockLists[1],
'gid://gitlab/List/1': mockLists[0],
},
error: undefined,
};
......@@ -175,7 +175,7 @@ describe('Board Store Mutations', () => {
describe('REMOVE_LIST', () => {
it('removes list from boardLists', () => {
const [list, secondList] = mockListsWithModel;
const [list, secondList] = mockLists;
const expected = {
[secondList.id]: secondList,
};
......@@ -455,13 +455,13 @@ describe('Board Store Mutations', () => {
boardLists: initialBoardListsState,
};
expect(state.boardLists['gid://gitlab/List/1'].issuesSize).toBe(1);
expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(1);
mutations.ADD_ISSUE_TO_LIST(state, { list: mockListsWithModel[0], issue: mockIssue2 });
mutations.ADD_ISSUE_TO_LIST(state, { list: mockLists[0], issue: mockIssue2 });
expect(state.issuesByListId['gid://gitlab/List/1']).toContain(mockIssue2.id);
expect(state.issues[mockIssue2.id]).toEqual(mockIssue2);
expect(state.boardLists['gid://gitlab/List/1'].issuesSize).toBe(2);
expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(2);
});
});
......
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