Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-09-15 09:09:30 +00:00
parent cc74c1d821
commit 03c7356304
29 changed files with 367 additions and 153 deletions

View file

@ -1,9 +1,11 @@
<script>
import { mapGetters, mapActions } from 'vuex';
import Sortable from 'sortablejs';
import isWipLimitsOn from 'ee_else_ce/boards/mixins/is_wip_limits';
import BoardListHeader from 'ee_else_ce/boards/components/board_list_header.vue';
import Tooltip from '~/vue_shared/directives/tooltip';
import EmptyComponent from '~/vue_shared/components/empty_component';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import BoardBlankState from './board_blank_state.vue';
import BoardList from './board_list.vue';
import boardsStore from '../stores/boards_store';
@ -21,7 +23,7 @@ export default {
directives: {
Tooltip,
},
mixins: [isWipLimitsOn],
mixins: [isWipLimitsOn, glFeatureFlagMixin()],
props: {
list: {
type: Object,
@ -62,6 +64,7 @@ export default {
};
},
computed: {
...mapGetters(['getIssues']),
showBoardListAndBoardInfo() {
return this.list.type !== ListType.blank && this.list.type !== ListType.promotion;
},
@ -69,19 +72,36 @@ export default {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `boards.${this.boardId}.${this.list.type}.${this.list.id}`;
},
listIssues() {
if (!this.glFeatures.graphqlBoardLists) {
return this.list.issues;
}
return this.getIssues(this.list.id);
},
shouldFetchIssues() {
return this.glFeatures.graphqlBoardLists && this.list.type !== ListType.blank;
},
},
watch: {
filter: {
handler() {
this.list.page = 1;
this.list.getIssues(true).catch(() => {
// TODO: handle request error
});
if (this.shouldFetchIssues) {
this.fetchIssuesForList(this.list.id);
} else {
this.list.page = 1;
this.list.getIssues(true).catch(() => {
// TODO: handle request error
});
}
},
deep: true,
},
},
mounted() {
if (this.shouldFetchIssues) {
this.fetchIssuesForList(this.list.id);
}
const instance = this;
const sortableOptions = getBoardSortableDefaultOptions({
@ -108,6 +128,7 @@ export default {
Sortable.create(this.$el.parentNode, sortableOptions);
},
methods: {
...mapActions(['fetchIssuesForList']),
showListNewIssueForm(listId) {
eventHub.$emit('showForm', listId);
},
@ -142,7 +163,7 @@ export default {
:disabled="disabled"
:group-id="groupId || null"
:issue-link-base="issueLinkBase"
:issues="list.issues"
:issues="listIssues"
:list="list"
:loading="list.loading"
:root-path="rootPath"

View file

@ -6,6 +6,7 @@ import boardCard from './board_card.vue';
import eventHub from '../eventhub';
import boardsStore from '../stores/boards_store';
import { sprintf, __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import {
getBoardSortableDefaultOptions,
@ -24,6 +25,7 @@ export default {
boardNewIssue,
GlLoadingIcon,
},
mixins: [glFeatureFlagMixin()],
props: {
groupId: {
type: Number,
@ -83,6 +85,7 @@ export default {
deep: true,
},
issues() {
if (this.glFeatures.graphqlBoardLists) return;
this.$nextTick(() => {
if (
this.scrollHeight() <= this.listHeight() &&
@ -413,6 +416,8 @@ export default {
this.showIssueForm = !this.showIssueForm;
},
onScroll() {
if (this.glFeatures.graphqlBoardLists) return;
if (!this.list.loadingMore && this.scrollTop() > this.scrollHeight() - this.scrollOffset) {
this.loadNextPage();
}

View file

@ -129,6 +129,9 @@ export default {
collapsedTooltipTitle() {
return this.listTitle || this.listAssignee;
},
shouldDisplaySwimlanes() {
return this.glFeatures.boardsWithSwimlanes && this.isSwimlanesOn;
},
},
methods: {
...mapActions(['updateList']),
@ -158,7 +161,7 @@ export default {
}
},
updateListFunction() {
if (this.glFeatures.boardsWithSwimlanes && this.isSwimlanesHeader) {
if (this.shouldDisplaySwimlanes || this.glFeatures.graphqlBoardLists) {
this.updateList({ listId: this.list.id, collapsed: !this.list.isExpanded });
} else {
this.list.update();
@ -184,7 +187,7 @@ export default {
<h3
:class="{
'user-can-drag': !disabled && !list.preset,
'gl-py-3': !list.isExpanded && !isSwimlanesHeader,
'gl-py-3 gl-h-full': !list.isExpanded && !isSwimlanesHeader,
'gl-border-b-0': !list.isExpanded || isSwimlanesHeader,
'gl-py-2': !list.isExpanded && isSwimlanesHeader,
}"

View file

@ -42,6 +42,9 @@ export default {
}
return this.title === '';
},
shouldDisplaySwimlanes() {
return this.glFeatures.boardsWithSwimlanes && this.isSwimlanesOn;
},
},
mounted() {
this.$refs.input.focus();
@ -75,7 +78,7 @@ export default {
eventHub.$emit(`scroll-board-list-${this.list.id}`);
this.cancel();
if (this.glFeatures.boardsWithSwimlanes && this.isSwimlanesOn) {
if (this.shouldDisplaySwimlanes || this.glFeatures.graphqlBoardLists) {
this.addListIssue({ list: this.list, issue, position: 0 });
}
@ -85,7 +88,7 @@ export default {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$refs.submitButton).enable();
if (!this.glFeatures.boardsWithSwimlanes || !this.isSwimlanesOn) {
if (!this.shouldDisplaySwimlanes && !this.glFeatures.graphqlBoardLists) {
boardsStore.setIssueDetail(issue);
boardsStore.setListDetail(this.list);
}
@ -95,7 +98,7 @@ export default {
$(this.$refs.submitButton).enable();
// Remove the issue
if (this.glFeatures.boardsWithSwimlanes && this.isSwimlanesOn) {
if (this.shouldDisplaySwimlanes || this.glFeatures.graphqlBoardLists) {
this.addListIssueFailure({ list: this.list, issue });
} else {
this.list.removeIssue(issue);

View file

@ -0,0 +1,21 @@
<script>
export default {
props: {
title: {
type: String,
required: true,
},
refPath: {
type: String,
required: true,
},
},
};
</script>
<template>
<div data-testid="issue-title">
<p class="gl-font-weight-bold">{{ title }}</p>
<p class="gl-mb-0">{{ refPath }}</p>
</div>
</template>

View file

@ -27,7 +27,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
updateObject(path) {
this.store.path = path.substr(1);
if (gon.features.boardsWithSwimlanes) {
if (gon.features.boardsWithSwimlanes || gon.features.graphqlBoardLists) {
boardsStore.updateFiltersUrl();
boardsStore.performSearch();
}

View file

@ -7,15 +7,10 @@ fragment IssueNode on Issue {
referencePath: reference(full: true)
dueDate
timeEstimate
weight
confidential
webUrl
subscribed
blocked
relativePosition
epic {
id
}
assignees {
nodes {
...User

View file

@ -1,15 +1,16 @@
#import "./issue.fragment.graphql"
#import "ee_else_ce/boards/queries/issue.fragment.graphql"
query ListIssues(
$fullPath: ID!
$boardId: ID!
$id: ID
$filters: BoardIssueInput
$isGroup: Boolean = false
$isProject: Boolean = false
) {
group(fullPath: $fullPath) @include(if: $isGroup) {
board(id: $boardId) {
lists {
lists(id: $id) {
nodes {
id
issues(filters: $filters) {
@ -23,7 +24,7 @@ query ListIssues(
}
project(fullPath: $fullPath) @include(if: $isProject) {
board(id: $boardId) {
lists {
lists(id: $id) {
nodes {
id
issues(filters: $filters) {

View file

@ -79,10 +79,10 @@ export default {
lists = lists.nodes.map(list =>
boardStore.updateListPosition({
...list,
id: getIdFromGraphQLId(list.id),
doNotFetchIssues: true,
}),
);
commit(types.RECEIVE_LISTS, sortBy(lists, 'position'));
commit(types.RECEIVE_BOARD_LISTS_SUCCESS, sortBy(lists, 'position'));
// Backlog list needs to be created if it doesn't exist
if (!lists.find(l => l.type === ListType.backlog)) {
dispatch('createList', { backlog: true });
@ -113,7 +113,7 @@ export default {
commit(types.CREATE_LIST_FAILURE);
} else {
const list = data.boardListCreate?.list;
dispatch('addList', { ...list, id: getIdFromGraphQLId(list.id) });
dispatch('addList', list);
}
})
.catch(() => {
@ -124,8 +124,8 @@ export default {
addList: ({ state, commit }, list) => {
const lists = state.boardLists;
// Temporarily using positioning logic from boardStore
lists.push(boardStore.updateListPosition(list));
commit(types.RECEIVE_LISTS, sortBy(lists, 'position'));
lists.push(boardStore.updateListPosition({ ...list, doNotFetchIssues: true }));
commit(types.RECEIVE_BOARD_LISTS_SUCCESS, sortBy(lists, 'position'));
},
showWelcomeList: ({ state, dispatch }) => {
@ -197,8 +197,33 @@ export default {
notImplemented();
},
fetchIssuesForList: () => {
notImplemented();
fetchIssuesForList: ({ state, commit }, listId) => {
const { endpoints, boardType, filterParams } = state;
const { fullPath, boardId } = endpoints;
const variables = {
fullPath,
boardId: fullBoardId(boardId),
id: listId,
filters: filterParams,
isGroup: boardType === BoardType.group,
isProject: boardType === BoardType.project,
};
return gqlClient
.query({
query: listsIssuesQuery,
context: {
isSingleRequest: true,
},
variables,
})
.then(({ data }) => {
const { lists } = data[boardType]?.board;
const listIssues = formatListIssues(lists);
commit(types.RECEIVE_ISSUES_FOR_LIST_SUCCESS, { listIssues, listId });
})
.catch(() => commit(types.RECEIVE_ISSUES_FOR_LIST_FAILURE, listId));
},
fetchIssuesForAllLists: ({ state, commit }) => {

View file

@ -304,7 +304,11 @@ const boardsStore = {
onNewListIssueResponse(list, issue, data) {
issue.refreshData(data);
if (!gon.features.boardsWithSwimlanes && list.issuesSize > 1) {
if (
!gon.features.boardsWithSwimlanes &&
!gon.features.graphqlBoardLists &&
list.issues.length > 1
) {
const moveBeforeId = list.issues[1].id;
this.moveIssue(issue.id, null, null, null, moveBeforeId);
}
@ -723,6 +727,10 @@ const boardsStore = {
newListIssue(list, issue) {
list.addIssue(issue, null, 0);
list.issuesSize += 1;
let listId = list.id;
if (typeof listId === 'string') {
listId = getIdFromGraphQLId(listId);
}
return this.newIssue(list.id, issue)
.then(res => res.data)

View file

@ -14,6 +14,11 @@ export default {
return state.issues[id] || {};
},
getIssues: (state, getters) => listId => {
const listIssueIds = state.issuesByListId[listId] || [];
return listIssueIds.map(id => getters.getIssueById(id));
},
getActiveIssue: state => {
return state.issues[state.activeId] || {};
},

View file

@ -2,7 +2,7 @@ export const SET_INITIAL_BOARD_DATA = 'SET_INITIAL_BOARD_DATA';
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_LISTS = 'RECEIVE_LISTS';
export const RECEIVE_BOARD_LISTS_SUCCESS = 'RECEIVE_BOARD_LISTS_SUCCESS';
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';
@ -13,6 +13,8 @@ export const REQUEST_REMOVE_LIST = 'REQUEST_REMOVE_LIST';
export const RECEIVE_REMOVE_LIST_SUCCESS = 'RECEIVE_REMOVE_LIST_SUCCESS';
export const RECEIVE_REMOVE_LIST_ERROR = 'RECEIVE_REMOVE_LIST_ERROR';
export const REQUEST_ISSUES_FOR_ALL_LISTS = 'REQUEST_ISSUES_FOR_ALL_LISTS';
export const RECEIVE_ISSUES_FOR_LIST_FAILURE = 'RECEIVE_ISSUES_FOR_LIST_FAILURE';
export const RECEIVE_ISSUES_FOR_LIST_SUCCESS = 'RECEIVE_ISSUES_FOR_LIST_SUCCESS';
export const RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS = 'RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS';
export const RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE = 'RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE';
export const REQUEST_ADD_ISSUE = 'REQUEST_ADD_ISSUE';

View file

@ -35,7 +35,7 @@ export default {
state.showPromotion = showPromotion;
},
[mutationTypes.RECEIVE_LISTS]: (state, lists) => {
[mutationTypes.RECEIVE_BOARD_LISTS_SUCCESS]: (state, lists) => {
state.boardLists = lists;
},
@ -89,6 +89,20 @@ export default {
notImplemented();
},
[mutationTypes.RECEIVE_ISSUES_FOR_LIST_SUCCESS]: (state, { listIssues, listId }) => {
const { listData, issues } = listIssues;
Vue.set(state, 'issues', { ...state.issues, ...issues });
Vue.set(state.issuesByListId, listId, listData[listId]);
const listIndex = state.boardLists.findIndex(l => l.id === listId);
Vue.set(state.boardLists[listIndex], 'loading', false);
},
[mutationTypes.RECEIVE_ISSUES_FOR_LIST_FAILURE]: (state, listId) => {
state.error = __('An error occurred while fetching the board issues. Please reload the page.');
const listIndex = state.boardLists.findIndex(l => l.id === listId);
Vue.set(state.boardLists[listIndex], 'loading', false);
},
[mutationTypes.REQUEST_ISSUES_FOR_ALL_LISTS]: state => {
state.isLoadingIssues = true;
},

View file

@ -1,32 +1,26 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import { escape } from 'lodash';
import { GlButton, GlLoadingIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import { GlLoadingIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { sprintf } from '~/locale';
import { __, sprintf } from '~/locale';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { hasDiff } from '~/helpers/diffs_helper';
import eventHub from '../../notes/event_hub';
import DiffFileHeader from './diff_file_header.vue';
import DiffContent from './diff_content.vue';
import { diffViewerErrors } from '~/ide/constants';
import { GENERIC_ERROR, DIFF_FILE } from '../i18n';
export default {
components: {
DiffFileHeader,
DiffContent,
GlButton,
GlLoadingIcon,
},
directives: {
SafeHtml,
},
mixins: [glFeatureFlagsMixin()],
i18n: {
genericError: GENERIC_ERROR,
...DIFF_FILE,
},
props: {
file: {
type: Object,
@ -59,7 +53,7 @@ export default {
...mapGetters('diffs', ['getDiffFileDiscussions']),
viewBlobLink() {
return sprintf(
this.i18n.blobView,
__('You can %{linkStart}view the blob%{linkEnd} instead.'),
{
linkStart: `<a href="${escape(this.file.view_path)}">`,
linkEnd: '</a>',
@ -81,7 +75,9 @@ export default {
},
forkMessage() {
return sprintf(
this.i18n.editInFork,
__(
"You're not allowed to %{tag_start}edit%{tag_end} files in this project directly. Please fork this project, make your changes there, and submit a merge request.",
),
{
tag_start: '<span class="js-file-fork-suggestion-section-action">',
tag_end: '</span>',
@ -152,7 +148,7 @@ export default {
})
.catch(() => {
this.isLoadingCollapsedDiff = false;
createFlash(this.i18n.genericError);
createFlash(__('Something went wrong on our end. Please try again!'));
});
},
showForkMessage() {
@ -192,14 +188,14 @@ export default {
<a
:href="file.fork_path"
class="js-fork-suggestion-button btn btn-grouped btn-inverted btn-success"
>{{ $options.i18n.fork }}</a
>{{ __('Fork') }}</a
>
<button
class="js-cancel-fork-suggestion-button btn btn-grouped"
type="button"
@click="hideForkMessage"
>
{{ $options.i18n.cancel }}
{{ __('Cancel') }}
</button>
</div>
<gl-loading-icon v-if="showLoadingIcon" class="diff-content loading" />
@ -209,17 +205,11 @@ export default {
<div v-safe-html="errorMessage" class="nothing-here-block"></div>
</div>
<template v-else>
<div v-show="isCollapsed" class="gl-p-7 gl-text-center collapsed-file-warning">
<p class="gl-mb-8 gl-mt-5">
{{ $options.i18n.collapsed }}
</p>
<gl-button
class="gl-alert-action gl-mb-5"
data-testid="expandButton"
@click="handleToggle"
>
{{ $options.i18n.expand }}
</gl-button>
<div v-show="isCollapsed" class="nothing-here-block diff-collapsed">
{{ __('This diff is collapsed.') }}
<a class="click-to-expand js-click-to-expand" href="#" @click.prevent="handleToggle">{{
__('Click to expand it.')
}}</a>
</div>
<diff-content
v-show="!isCollapsed && !isFileTooLarge"

View file

@ -1,14 +0,0 @@
import { __ } from '~/locale';
export const GENERIC_ERROR = __('Something went wrong on our end. Please try again!');
export const DIFF_FILE = {
blobView: __('You can %{linkStart}view the blob%{linkEnd} instead.'),
editInFork: __(
"You're not allowed to %{tag_start}edit%{tag_end} files in this project directly. Please fork this project, make your changes there, and submit a merge request.",
),
fork: __('Fork'),
cancel: __('Cancel'),
collapsed: __('This file is collapsed.'),
expand: __('Expand file'),
};

View file

@ -116,7 +116,6 @@
.board-title {
flex-direction: column;
height: 100%;
}
.board-title-caret {

View file

@ -23,4 +23,3 @@
= render "registrations/welcome/button"
- else
= f.submit _('Get started!'), class: 'btn-register btn btn-block gl-mb-0 gl-p-3'

View file

@ -19,7 +19,7 @@
#board-app.boards-app.position-relative{ "v-cloak" => "true", data: board_data, ":class" => "{ 'is-compact': detailIssueVisible }" }
= render 'shared/issuable/search_bar', type: :boards, board: board
- if Feature.enabled?(:boards_with_swimlanes, current_board_parent)
- if Feature.enabled?(:boards_with_swimlanes, current_board_parent) || Feature.enabled?(:graphql_board_lists, current_board_parent)
%board-content{ "v-cloak" => "true",
"ref" => "board_content",
":lists" => "state.lists",

View file

@ -1,5 +0,0 @@
---
title: Expand the visible highlight for collapsed diffs
merge_request: 41393
author:
type: other

View file

@ -300,7 +300,7 @@ include Gitlab::Database::MigrationHelpers
def up
with_lock_retries do
add_foreign_key :imports, :projects, column: :project_id, on_delete: :cascade
add_foreign_key :imports, :projects, column: :project_id, on_delete: :cascade # rubocop:disable Migration/AddConcurrentForeignKey
end
end
@ -318,7 +318,7 @@ include Gitlab::Database::MigrationHelpers
def up
with_lock_retries do
add_foreign_key :imports, :users, column: :user_id, on_delete: :cascade
add_foreign_key :imports, :users, column: :user_id, on_delete: :cascade # rubocop:disable Migration/AddConcurrentForeignKey
end
end

View file

@ -10357,9 +10357,6 @@ msgstr ""
msgid "Expand dropdown"
msgstr ""
msgid "Expand file"
msgstr ""
msgid "Expand milestones"
msgstr ""
@ -25733,9 +25730,6 @@ msgstr ""
msgid "This field is required."
msgstr ""
msgid "This file is collapsed."
msgstr ""
msgid "This group"
msgstr ""

View file

@ -19,6 +19,8 @@ RSpec.describe 'Group Issue Boards', :js do
let(:card) { find('.board:nth-child(1)').first('.board-card') }
before do
# stubbing until sidebar work is done: https://gitlab.com/gitlab-org/gitlab/-/issues/230711
stub_feature_flags(graphql_board_lists: false)
sign_in(user)
visit group_board_path(group, board)

View file

@ -17,11 +17,11 @@ RSpec.describe 'User expands diff', :js do
it 'allows user to expand diff' do
page.within find('[id="19763941ab80e8c09871c0a425f0560d9053bcb3"]') do
find('[data-testid="expandButton"]').click
click_link 'Click to expand it.'
wait_for_requests
expect(page).not_to have_content('Expand File')
expect(page).not_to have_content('Click to expand it.')
expect(page).to have_selector('.code')
end
end

View file

@ -0,0 +1,33 @@
import { shallowMount } from '@vue/test-utils';
import IssuableTitle from '~/boards/components/issuable_title.vue';
describe('IssuableTitle', () => {
let wrapper;
const defaultProps = {
title: 'One',
refPath: 'path',
};
const createComponent = () => {
wrapper = shallowMount(IssuableTitle, {
propsData: { ...defaultProps },
});
};
const findIssueContent = () => wrapper.find('[data-testid="issue-title"]');
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('renders a title of an issue in the sidebar', () => {
expect(findIssueContent().text()).toContain('One');
});
it('renders a referencePath of an issue in the sidebar', () => {
expect(findIssueContent().text()).toContain('path');
});
});

View file

@ -98,12 +98,35 @@ export const mockMilestone = {
due_date: '2019-12-31',
};
const assignees = [
{
id: 'gid://gitlab/User/2',
username: 'angelina.herman',
name: 'Bernardina Bosco',
avatar: 'https://www.gravatar.com/avatar/eb7b664b13a30ad9f9ba4b61d7075470?s=80&d=identicon',
webUrl: 'http://127.0.0.1:3000/angelina.herman',
},
];
const labels = [
{
id: 'gid://gitlab/GroupLabel/5',
title: 'Cosync',
color: '#34ebec',
description: null,
},
];
export const rawIssue = {
title: 'Testing',
id: 'gid://gitlab/Issue/1',
iid: 1,
title: 'Issue 1',
id: 'gid://gitlab/Issue/436',
iid: 27,
dueDate: null,
timeEstimate: 0,
weight: null,
confidential: false,
referencePath: 'gitlab-org/gitlab-test#1',
referencePath: 'gitlab-org/gitlab-test#27',
path: '/gitlab-org/gitlab-test/-/issues/27',
labels: {
nodes: [
{
@ -115,23 +138,24 @@ export const rawIssue = {
],
},
assignees: {
nodes: [
{
id: 1,
name: 'name',
username: 'username',
avatar_url: 'http://avatar_url',
},
],
nodes: assignees,
},
epic: {
id: 'gid://gitlab/Epic/41',
},
};
export const mockIssue = {
title: 'Testing',
id: 1,
iid: 1,
id: 'gid://gitlab/Issue/436',
iid: 27,
title: 'Issue 1',
dueDate: null,
timeEstimate: 0,
weight: null,
confidential: false,
referencePath: 'gitlab-org/gitlab-test#1',
referencePath: 'gitlab-org/gitlab-test#27',
path: '/gitlab-org/gitlab-test/-/issues/27',
assignees,
labels: [
{
id: 1,
@ -140,44 +164,64 @@ export const mockIssue = {
description: 'testing',
},
],
assignees: [
{
id: 1,
name: 'name',
username: 'username',
avatar_url: 'http://avatar_url',
},
],
epic: {
id: 'gid://gitlab/Epic/41',
},
};
export const mockIssueWithModel = new ListIssue(mockIssue);
export const mockIssue2 = {
title: 'Planning',
id: 2,
iid: 2,
id: 'gid://gitlab/Issue/437',
iid: 28,
title: 'Issue 2',
dueDate: null,
timeEstimate: 0,
weight: null,
confidential: false,
referencePath: 'gitlab-org/gitlab-test#2',
labels: [
{
id: 1,
title: 'plan',
color: 'blue',
description: 'planning',
},
],
assignees: [
{
id: 1,
name: 'name',
username: 'username',
avatar_url: 'http://avatar_url',
},
],
path: '/gitlab-org/gitlab-test/-/issues/28',
assignees,
labels,
epic: {
id: 'gid://gitlab/Epic/40',
},
};
export const mockIssue2WithModel = new ListIssue(mockIssue2);
export const mockIssue3 = {
id: 'gid://gitlab/Issue/438',
iid: 29,
title: 'Issue 3',
referencePath: '#29',
dueDate: null,
timeEstimate: 0,
weight: null,
confidential: false,
path: '/gitlab-org/gitlab-test/-/issues/28',
assignees,
labels,
epic: null,
};
export const mockIssue4 = {
id: 'gid://gitlab/Issue/439',
iid: 30,
title: 'Issue 4',
referencePath: '#30',
dueDate: null,
timeEstimate: 0,
weight: null,
confidential: false,
path: '/gitlab-org/gitlab-test/-/issues/28',
assignees,
labels,
epic: null,
};
export const mockIssues = [mockIssue, mockIssue2];
export const BoardsMockData = {
GET: {
'/test/-/boards/1/lists/300/issues?id=300&page=1': {
@ -239,6 +283,7 @@ export const mockLists = [
label: null,
assignee: null,
milestone: null,
loading: false,
},
{
id: 'gid://gitlab/List/2',
@ -255,9 +300,22 @@ export const mockLists = [
},
assignee: null,
milestone: null,
loading: false,
},
];
export const mockListsWithModel = mockLists.map(listMock =>
Vue.observable(new List({ ...listMock, doNotFetchIssues: true })),
);
export const mockIssuesByListId = {
'gid://gitlab/List/1': [mockIssue.id, mockIssue3.id, mockIssue4.id],
'gid://gitlab/List/2': mockIssues.map(({ id }) => id),
};
export const issues = {
[mockIssue.id]: mockIssue,
[mockIssue2.id]: mockIssue2,
[mockIssue3.id]: mockIssue3,
[mockIssue4.id]: mockIssue4,
};

View file

@ -3,7 +3,6 @@ import {
mockListsWithModel,
mockLists,
mockIssue,
mockIssue2,
mockIssueWithModel,
mockIssue2WithModel,
rawIssue,
@ -134,7 +133,7 @@ describe('createList', () => {
{ backlog: true },
state,
[],
[{ type: 'addList', payload: { ...backlogList, id: 1 } }],
[{ type: 'addList', payload: backlogList }],
done,
);
});
@ -232,19 +231,15 @@ describe('deleteList', () => {
expectNotImplemented(actions.deleteList);
});
describe('fetchIssuesForList', () => {
expectNotImplemented(actions.fetchIssuesForList);
});
describe('moveIssue', () => {
const listIssues = {
'gid://gitlab/List/1': [mockIssue.id, mockIssue2.id],
'gid://gitlab/List/1': [436, 437],
'gid://gitlab/List/2': [],
};
const issues = {
'1': mockIssueWithModel,
'2': mockIssue2WithModel,
'436': mockIssueWithModel,
'437': mockIssue2WithModel,
};
const state = {
@ -269,7 +264,7 @@ describe('moveIssue', () => {
testAction(
actions.moveIssue,
{
issueId: mockIssue.id,
issueId: '436',
issueIid: mockIssue.iid,
issuePath: mockIssue.referencePath,
fromListId: 'gid://gitlab/List/1',
@ -308,7 +303,7 @@ describe('moveIssue', () => {
testAction(
actions.moveIssue,
{
issueId: mockIssue.id,
issueId: '436',
issueIid: mockIssue.iid,
issuePath: mockIssue.referencePath,
fromListId: 'gid://gitlab/List/1',

View file

@ -1,5 +1,6 @@
import getters from '~/boards/stores/getters';
import { inactiveId } from '~/boards/constants';
import { mockIssue, mockIssue2, mockIssues, mockIssuesByListId, issues } from '../mock_data';
describe('Boards - Getters', () => {
describe('getLabelToggleState', () => {
@ -115,4 +116,18 @@ describe('Boards - Getters', () => {
expect(getters.getActiveIssue(state)).toEqual(expected);
});
});
describe('getIssues', () => {
const boardsState = {
issuesByListId: mockIssuesByListId,
issues,
};
it('returns issues for a given listId', () => {
const getIssueById = issueId => [mockIssue, mockIssue2].find(({ id }) => id === issueId);
expect(getters.getIssues(boardsState, { getIssueById })('gid://gitlab/List/2')).toEqual(
mockIssues,
);
});
});
});

View file

@ -54,11 +54,11 @@ describe('Board Store Mutations', () => {
});
});
describe('RECEIVE_LISTS', () => {
describe('RECEIVE_BOARD_LISTS_SUCCESS', () => {
it('Should set boardLists to state', () => {
const lists = [listObj, listObjDuplicate];
mutations[types.RECEIVE_LISTS](state, lists);
mutations[types.RECEIVE_BOARD_LISTS_SUCCESS](state, lists);
expect(state.boardLists).toEqual(lists);
});
@ -145,6 +145,33 @@ describe('Board Store Mutations', () => {
expectNotImplemented(mutations.RECEIVE_REMOVE_LIST_ERROR);
});
describe('RECEIVE_ISSUES_FOR_LIST_SUCCESS', () => {
it('updates issuesByListId and issues on state', () => {
const listIssues = {
'gid://gitlab/List/1': [mockIssue.id],
};
const issues = {
'1': mockIssue,
};
state = {
...state,
isLoadingIssues: true,
issuesByListId: {},
issues: {},
boardLists: mockListsWithModel,
};
mutations.RECEIVE_ISSUES_FOR_LIST_SUCCESS(state, {
listIssues: { listData: listIssues, issues },
listId: 'gid://gitlab/List/1',
});
expect(state.issuesByListId).toEqual(listIssues);
expect(state.issues).toEqual(issues);
});
});
describe('REQUEST_ISSUES_FOR_ALL_LISTS', () => {
it('sets isLoadingIssues to true', () => {
expect(state.isLoadingIssues).toBe(false);
@ -155,10 +182,28 @@ describe('Board Store Mutations', () => {
});
});
describe('RECEIVE_ISSUES_FOR_LIST_FAILURE', () => {
it('sets error message', () => {
state = {
...state,
boardLists: mockListsWithModel,
error: undefined,
};
const listId = 'gid://gitlab/List/1';
mutations.RECEIVE_ISSUES_FOR_LIST_FAILURE(state, listId);
expect(state.error).toEqual(
'An error occurred while fetching the board issues. Please reload the page.',
);
});
});
describe('RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS', () => {
it('sets isLoadingIssues to false and updates issuesByListId object', () => {
const listIssues = {
'': [mockIssue.id],
'gid://gitlab/List/1': [mockIssue.id],
};
const issues = {
'1': mockIssue,
@ -287,7 +332,7 @@ describe('Board Store Mutations', () => {
describe('MOVE_ISSUE_SUCCESS', () => {
it('updates issue in issues state', () => {
const issues = {
'1': { id: rawIssue.id },
'436': { id: rawIssue.id },
};
state = {
@ -299,7 +344,7 @@ describe('Board Store Mutations', () => {
issue: rawIssue,
});
expect(state.issues).toEqual({ '1': { ...mockIssueWithModel, id: 1 } });
expect(state.issues).toEqual({ '436': { ...mockIssueWithModel, id: 436 } });
});
});

View file

@ -90,8 +90,8 @@ describe('DiffFile', () => {
vm.isCollapsed = true;
vm.$nextTick(() => {
expect(vm.$el.innerText).toContain('This file is collapsed.');
expect(vm.$el.querySelector('[data-testid="expandButton"]')).not.toBeFalsy();
expect(vm.$el.innerText).toContain('This diff is collapsed');
expect(vm.$el.querySelectorAll('.js-click-to-expand').length).toEqual(1);
done();
});
@ -102,8 +102,8 @@ describe('DiffFile', () => {
vm.isCollapsed = true;
vm.$nextTick(() => {
expect(vm.$el.innerText).toContain('This file is collapsed.');
expect(vm.$el.querySelector('[data-testid="expandButton"]')).not.toBeFalsy();
expect(vm.$el.innerText).toContain('This diff is collapsed');
expect(vm.$el.querySelectorAll('.js-click-to-expand').length).toEqual(1);
done();
});
@ -121,8 +121,8 @@ describe('DiffFile', () => {
vm.isCollapsed = true;
vm.$nextTick(() => {
expect(vm.$el.innerText).toContain('This file is collapsed.');
expect(vm.$el.querySelector('[data-testid="expandButton"]')).not.toBeFalsy();
expect(vm.$el.innerText).toContain('This diff is collapsed');
expect(vm.$el.querySelectorAll('.js-click-to-expand').length).toEqual(1);
done();
});
@ -135,7 +135,7 @@ describe('DiffFile', () => {
vm.file.viewer.name = diffViewerModes.renamed;
vm.$nextTick(() => {
expect(vm.$el.innerText).not.toContain('This file is collapsed.');
expect(vm.$el.innerText).not.toContain('This diff is collapsed');
done();
});
@ -148,7 +148,7 @@ describe('DiffFile', () => {
vm.file.viewer.name = diffViewerModes.mode_changed;
vm.$nextTick(() => {
expect(vm.$el.innerText).not.toContain('This file is collapsed.');
expect(vm.$el.innerText).not.toContain('This diff is collapsed');
done();
});