Commit ff89a3dc authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch 'psi-new-new-column-column' into 'master'

Add column with list of labels and search field [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!52061
parents 77f2ad0a 07df00da
<script>
import {
GlButton,
GlFormGroup,
GlFormRadio,
GlFormRadioGroup,
GlLabel,
GlSearchBoxByType,
GlSkeletonLoader,
GlTooltipDirective as GlTooltip,
} from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex';
import { isScopedLabel } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import boardsStore from '../stores/boards_store';
export default {
i18n: {
add: __('Add'),
cancel: __('Cancel'),
formDescription: __('A label list displays all issues with the selected label.'),
newLabelList: __('New label list'),
noLabelSelected: __('No label selected'),
searchPlaceholder: __('Search labels'),
selectLabel: __('Select label'),
selected: __('Selected'),
},
components: {
GlButton,
GlFormGroup,
GlFormRadio,
GlFormRadioGroup,
GlLabel,
GlSearchBoxByType,
GlSkeletonLoader,
},
directives: {
GlTooltip,
},
inject: ['scopedLabelsAvailable'],
data() {
return {
searchTerm: '',
selectedLabelId: null,
};
},
computed: {
...mapState(['labels', 'labelsLoading']),
...mapGetters(['getListByLabelId', 'shouldUseGraphQL']),
selectedLabel() {
return this.labels.find(({ id }) => id === this.selectedLabelId);
},
},
created() {
this.filterLabels();
},
methods: {
...mapActions(['createList', 'fetchLabels', 'highlightList', 'setAddColumnFormVisibility']),
getListByLabel(label) {
if (this.shouldUseGraphQL) {
return this.getListByLabelId(label);
}
return boardsStore.findListByLabelId(label.id);
},
columnExists(label) {
return Boolean(this.getListByLabel(label));
},
highlight(listId) {
if (this.shouldUseGraphQL) {
this.highlightList(listId);
} else {
const list = boardsStore.state.lists.find(({ id }) => id === listId);
list.highlighted = true;
setTimeout(() => {
list.highlighted = false;
}, 2000);
}
},
addList() {
if (!this.selectedLabelId) {
return;
}
const label = this.selectedLabel;
if (!label) {
return;
}
this.setAddColumnFormVisibility(false);
if (this.columnExists({ id: this.selectedLabelId })) {
const listId = this.getListByLabel(label).id;
this.highlight(listId);
return;
}
if (this.shouldUseGraphQL) {
this.createList({ labelId: this.selectedLabelId });
} else {
boardsStore.new({
title: label.title,
position: boardsStore.state.lists.length - 2,
list_type: 'label',
label: {
id: label.id,
title: label.title,
color: label.color,
},
});
this.highlight(boardsStore.findListByLabelId(label.id).id);
}
},
filterLabels() {
this.fetchLabels(this.searchTerm);
},
showScopedLabels(label) {
return this.scopedLabelsAvailable && isScopedLabel(label);
},
},
};
</script>
<template>
<div
class="board-add-new-list board gl-display-inline-block gl-h-full gl-px-3 gl-vertical-align-top gl-white-space-normal gl-flex-shrink-0"
data-qa-selector="board_add_new_list"
>
<div
class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base gl-bg-white"
>
<h3
class="gl-font-base gl-px-5 gl-py-5 gl-m-0 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
data-testid="board-add-column-form-title"
>
{{ $options.i18n.newLabelList }}
</h3>
<div class="gl-display-flex gl-flex-direction-column gl-h-full gl-overflow-hidden">
<!-- selectbox is here in EE -->
<p class="gl-m-5">{{ $options.i18n.formDescription }}</p>
<div class="gl-px-5 gl-pb-4">
<label class="gl-mb-2">{{ $options.i18n.selected }}</label>
<div>
<gl-label
v-if="selectedLabel"
v-gl-tooltip
:title="selectedLabel.title"
:description="selectedLabel.description"
:background-color="selectedLabel.color"
:scoped="showScopedLabels(selectedLabel)"
/>
<div v-else class="gl-text-gray-500">{{ $options.i18n.noLabelSelected }}</div>
</div>
</div>
<gl-form-group
class="gl-mx-5 gl-mb-3"
:label="$options.i18n.selectLabel"
label-for="board-available-labels"
>
<gl-search-box-by-type
id="board-available-labels"
v-model.trim="searchTerm"
debounce="250"
:placeholder="$options.i18n.searchPlaceholder"
@input="filterLabels"
/>
</gl-form-group>
<div v-if="labelsLoading" class="gl-m-5">
<gl-skeleton-loader :width="500" :height="172">
<rect width="480" height="20" x="10" y="15" rx="4" />
<rect width="380" height="20" x="10" y="50" rx="4" />
<rect width="430" height="20" x="10" y="85" rx="4" />
</gl-skeleton-loader>
</div>
<gl-form-radio-group
v-else
v-model="selectedLabelId"
class="gl-overflow-y-auto gl-px-5 gl-pt-3"
>
<label
v-for="label in labels"
:key="label.id"
class="gl-display-flex gl-flex-align-items-center gl-mb-5 gl-font-weight-normal"
>
<gl-form-radio :value="label.id" class="gl-mb-0 gl-mr-3" />
<span
class="dropdown-label-box gl-top-0"
:style="{
backgroundColor: label.color,
}"
></span>
<span>{{ label.title }}</span>
</label>
</gl-form-radio-group>
</div>
<div
class="gl-display-flex gl-p-3 gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-bg-gray-10"
>
<gl-button
data-testid="cancelAddNewColumn"
class="gl-ml-auto gl-mr-3"
@click="setAddColumnFormVisibility(false)"
>{{ $options.i18n.cancel }}</gl-button
>
<gl-button
data-testid="addNewColumnButton"
:disabled="!selectedLabelId"
variant="success"
class="gl-mr-4"
@click="addList"
>{{ $options.i18n.add }}</gl-button
>
</div>
</div>
</div>
</template>
......@@ -46,11 +46,20 @@ export default {
watch: {
filterParams: {
handler() {
if (this.list.id) {
this.fetchItemsForList({ listId: this.list.id });
}
},
deep: true,
immediate: true,
},
'list.id': {
handler(id) {
if (id) {
this.fetchItemsForList({ listId: this.list.id });
}
},
},
highlighted: {
handler(highlighted) {
if (highlighted) {
......
......@@ -6,11 +6,13 @@ import { mapState, mapGetters, mapActions } from 'vuex';
import { sortableEnd, sortableStart } from '~/boards/mixins/sortable_default_options';
import defaultSortableConfig from '~/sortable/sortable_config';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import BoardAddNewColumn from './board_add_new_column.vue';
import BoardColumn from './board_column.vue';
import BoardColumnDeprecated from './board_column_deprecated.vue';
export default {
components: {
BoardAddNewColumn,
BoardColumn:
gon.features?.graphqlBoardLists || gon.features?.epicBoards
? BoardColumn
......@@ -36,8 +38,11 @@ export default {
},
},
computed: {
...mapState(['boardLists', 'error', 'isEpicBoard']),
...mapState(['boardLists', 'error', 'addColumnForm', 'isEpicBoard']),
...mapGetters(['isSwimlanesOn']),
addColumnFormVisible() {
return this.addColumnForm?.visible;
},
boardListsToUse() {
return this.glFeatures.graphqlBoardLists || this.isSwimlanesOn || this.isEpicBoard
? sortBy([...Object.values(this.boardLists)], 'position')
......@@ -65,6 +70,10 @@ export default {
},
methods: {
...mapActions(['moveList']),
afterFormEnters() {
const el = this.canDragColumns ? this.$refs.list.$el : this.$refs.list;
el.scrollTo({ left: el.scrollWidth, behavior: 'smooth' });
},
handleDragOnStart() {
sortableStart();
},
......@@ -103,13 +112,17 @@ export default {
@end="handleDragOnEnd"
>
<board-column
v-for="list in boardListsToUse"
:key="list.id"
v-for="(list, index) in boardListsToUse"
:key="index"
ref="board"
:can-admin-list="canAdminList"
:list="list"
:disabled="disabled"
/>
<transition name="slide" @after-enter="afterFormEnters">
<board-add-new-column v-if="addColumnFormVisible" />
</transition>
</component>
<epics-swimlanes
......
......@@ -7,14 +7,14 @@ query BoardLabels(
$isProject: Boolean = false
) {
group(fullPath: $fullPath) @include(if: $isGroup) {
labels(searchTerm: $searchTerm) {
labels(searchTerm: $searchTerm, onlyGroupLabels: true, includeAncestorGroups: true) {
nodes {
...Label
}
}
}
project(fullPath: $fullPath) @include(if: $isProject) {
labels(searchTerm: $searchTerm) {
labels(searchTerm: $searchTerm, includeAncestorGroups: true) {
nodes {
...Label
}
......
#import "ee_else_ce/boards/graphql/board_list.fragment.graphql"
#import "./board_list.fragment.graphql"
mutation CreateBoardList(
$boardId: BoardID!
$backlog: Boolean
$labelId: LabelID
$milestoneId: MilestoneID
$assigneeId: UserID
) {
boardListCreate(
input: {
boardId: $boardId
backlog: $backlog
labelId: $labelId
milestoneId: $milestoneId
assigneeId: $assigneeId
}
) {
mutation CreateBoardList($boardId: BoardID!, $backlog: Boolean, $labelId: LabelID) {
boardListCreate(input: { boardId: $boardId, backlog: $backlog, labelId: $labelId }) {
list {
...BoardListFragment
}
......
import { pick } from 'lodash';
import createBoardListMutation from 'ee_else_ce/boards/graphql/board_list_create.mutation.graphql';
import boardListsQuery from 'ee_else_ce/boards/graphql/board_lists.query.graphql';
import {
BoardType,
......@@ -21,7 +22,6 @@ import {
transformNotFilters,
} from '../boards_util';
import boardLabelsQuery from '../graphql/board_labels.query.graphql';
import createBoardListMutation from '../graphql/board_list_create.mutation.graphql';
import destroyBoardListMutation from '../graphql/board_list_destroy.mutation.graphql';
import updateBoardListMutation from '../graphql/board_list_update.mutation.graphql';
import groupProjectsQuery from '../graphql/group_projects.query.graphql';
......@@ -161,14 +161,17 @@ export default {
dispatch('highlightList', list.id);
}
})
.catch(() => commit(types.CREATE_LIST_FAILURE));
.catch((e) => {
commit(types.CREATE_LIST_FAILURE);
throw e;
});
},
addList: ({ commit }, list) => {
commit(types.RECEIVE_ADD_LIST_SUCCESS, updateListPosition(list));
},
fetchLabels: ({ state, commit }, searchTerm) => {
fetchLabels: ({ state, commit, getters }, searchTerm) => {
const { fullPath, boardType } = state;
const variables = {
......@@ -178,15 +181,29 @@ export default {
isProject: boardType === BoardType.project,
};
commit(types.RECEIVE_LABELS_REQUEST);
return gqlClient
.query({
query: boardLabelsQuery,
variables,
})
.then(({ data }) => {
const labels = data[boardType]?.labels.nodes;
let labels = data[boardType]?.labels.nodes;
if (!getters.shouldUseGraphQL) {
labels = labels.map((label) => ({
...label,
id: getIdFromGraphQLId(label.id),
}));
}
commit(types.RECEIVE_LABELS_SUCCESS, labels);
return labels;
})
.catch((e) => {
commit(types.RECEIVE_LABELS_FAILURE);
throw e;
});
},
......
......@@ -2,7 +2,9 @@ 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_LABELS_REQUEST = 'RECEIVE_LABELS_REQUEST';
export const RECEIVE_LABELS_SUCCESS = 'RECEIVE_LABELS_SUCCESS';
export const RECEIVE_LABELS_FAILURE = 'RECEIVE_LABELS_FAILURE';
export const GENERATE_DEFAULT_LISTS_FAILURE = 'GENERATE_DEFAULT_LISTS_FAILURE';
export const RECEIVE_BOARD_LISTS_SUCCESS = 'RECEIVE_BOARD_LISTS_SUCCESS';
export const RECEIVE_BOARD_LISTS_FAILURE = 'RECEIVE_BOARD_LISTS_FAILURE';
......
......@@ -64,8 +64,18 @@ export default {
state.error = s__('Boards|An error occurred while creating the list. Please try again.');
},
[mutationTypes.RECEIVE_LABELS_REQUEST]: (state) => {
state.labelsLoading = true;
},
[mutationTypes.RECEIVE_LABELS_SUCCESS]: (state, labels) => {
state.labels = labels;
state.labelsLoading = false;
},
[mutationTypes.RECEIVE_LABELS_FAILURE]: (state) => {
state.error = s__('Boards|An error occurred while fetching labels. Please reload the page.');
state.labelsLoading = false;
},
[mutationTypes.GENERATE_DEFAULT_LISTS_FAILURE]: (state) => {
......@@ -270,7 +280,7 @@ export default {
},
[mutationTypes.SET_ADD_COLUMN_FORM_VISIBLE]: (state, visible) => {
state.addColumnFormVisible = visible;
Vue.set(state.addColumnForm, 'visible', visible);
},
[mutationTypes.ADD_LIST_TO_HIGHLIGHTED_LISTS]: (state, listId) => {
......
import { inactiveId } from '~/boards/constants';
import { inactiveId, ListType } from '~/boards/constants';
export default () => ({
boardType: null,
......@@ -15,6 +15,7 @@ export default () => ({
boardItems: {},
filterParams: {},
boardConfig: {},
labelsLoading: false,
labels: [],
highlightedLists: [],
selectedBoardItems: [],
......@@ -26,7 +27,10 @@ export default () => ({
},
selectedProject: {},
error: undefined,
addColumnFormVisible: false,
addColumnForm: {
visible: false,
columnType: ListType.label,
},
// TODO: remove after ce/ee split of board_content.vue
isShowingEpicsSwimlanes: false,
});
......@@ -4,6 +4,7 @@ import Draggable from 'vuedraggable';
import { mapActions, mapGetters, mapState } from 'vuex';
import BoardListHeader from 'ee_else_ce/boards/components/board_list_header.vue';
import { isListDraggable } from '~/boards/boards_util';
import BoardAddNewColumn from '~/boards/components/board_add_new_column.vue';
import { n__ } from '~/locale';
import defaultSortableConfig from '~/sortable/sortable_config';
import { DRAGGABLE_TAG } from '../constants';
......@@ -12,6 +13,7 @@ import IssuesLaneList from './issues_lane_list.vue';
export default {
components: {
BoardAddNewColumn,
BoardListHeader,
EpicLane,
IssuesLaneList,
......@@ -37,8 +39,11 @@ export default {
},
},
computed: {
...mapState(['epics', 'pageInfoByListId', 'listsFlags']),
...mapState(['epics', 'pageInfoByListId', 'listsFlags', 'addColumnForm']),
...mapGetters(['getUnassignedIssues']),
addColumnFormVisible() {
return this.addColumnForm?.visible;
},
unassignedIssues() {
return (listId) => this.getUnassignedIssues(listId);
},
......@@ -98,6 +103,13 @@ export default {
isListDraggable(list) {
return isListDraggable(list);
},
afterFormEnters() {
const container = this.$refs.scrollableContainer;
container.scrollTo({
left: container.scrollWidth,
behavior: 'smooth',
});
},
},
};
</script>
......@@ -136,7 +148,7 @@ export default {
/>
</div>
</component>
<div class="board-epics-swimlanes gl-display-table gl-pb-5">
<div class="board-epics-swimlanes gl-display-table">
<epic-lane
v-for="epic in epics"
:key="epic.id"
......@@ -193,6 +205,12 @@ export default {
{{ s__('Board|Load more issues') }}
</gl-button>
</div>
<!-- placeholder for some space below lane lists -->
<div v-else class="gl-pb-5"></div>
</div>
<transition name="slide" @after-enter="afterFormEnters">
<board-add-new-column v-if="addColumnFormVisible" class="gl-sticky gl-top-5" />
</transition>
</div>
</template>
#import "./board_list.fragment.graphql"
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
}
errors
}
}
......@@ -91,6 +91,10 @@
.board-swimlanes {
overflow-x: auto;
.board-add-new-list {
height: calc(100% - 1rem);
}
}
$epic-icons-spacing: 40px;
......
......@@ -1305,6 +1305,9 @@ msgstr ""
msgid "A job artifact is an archive of files and directories saved by a job when it finishes."
msgstr ""
msgid "A label list displays all issues with the selected label."
msgstr ""
msgid "A limit of %{ci_project_subscriptions_limit} subscriptions to or from a project applies."
msgstr ""
......@@ -4814,6 +4817,9 @@ msgstr ""
msgid "Boards|An error occurred while fetching issues. Please reload the page."
msgstr ""
msgid "Boards|An error occurred while fetching labels. Please reload the page."
msgstr ""
msgid "Boards|An error occurred while fetching the board epics. Please reload the page."
msgstr ""
......@@ -20058,6 +20064,9 @@ msgstr ""
msgid "New label"
msgstr ""
msgid "New label list"
msgstr ""
msgid "New merge request"
msgstr ""
......@@ -20283,6 +20292,9 @@ msgstr ""
msgid "No label"
msgstr ""
msgid "No label selected"
msgstr ""
msgid "No labels with such name or description"
msgstr ""
......@@ -26026,6 +26038,9 @@ msgstr ""
msgid "Search forks"
msgstr ""
msgid "Search labels"
msgstr ""
msgid "Search merge requests"
msgstr ""
......@@ -26746,6 +26761,9 @@ msgstr ""
msgid "Select user"
msgstr ""
msgid "Selected"
msgstr ""
msgid "Selected commits"
msgstr ""
......
......@@ -13,8 +13,6 @@ RSpec.describe 'Issue Boards', :js do
let_it_be(:user2) { create(:user) }
before do
stub_feature_flags(board_new_list: false)
project.add_maintainer(user)
project.add_maintainer(user2)
......@@ -68,6 +66,8 @@ RSpec.describe 'Issue Boards', :js do
let_it_be(:issue10) { create(:labeled_issue, project: project, title: 'issue +', description: 'A+ great issue', labels: [a_plus]) }
before do
stub_feature_flags(board_new_list: false)
visit project_board_path(project, board)
wait_for_requests
......@@ -168,19 +168,6 @@ RSpec.describe 'Issue Boards', :js do
expect(page).to have_selector('.board', count: 3)
end
it 'removes checkmark in new list dropdown after deleting' do
click_button 'Add list'
wait_for_requests
find('.js-new-board-list').click
remove_list
wait_for_requests
expect(page).to have_selector('.board', count: 3)
end
it 'infinite scrolls list' do
create_list(:labeled_issue, 50, project: project, labels: [planning])
......@@ -321,6 +308,7 @@ RSpec.describe 'Issue Boards', :js do
context 'new list' do
it 'shows all labels in new list dropdown' do
click_button 'Add list'
wait_for_requests
page.within('.dropdown-menu-issues-board-new') do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User adds lists', :js do
using RSpec::Parameterized::TableSyntax
let_it_be(:group) { create(:group, :nested) }
let_it_be(:project) { create(:project, :public, namespace: group) }
let_it_be(:group_board) { create(:board, group: group) }
let_it_be(:project_board) { create(:board, project: project) }
let_it_be(:user) { create(:user) }
let_it_be(:milestone) { create(:milestone, project: project) }
let_it_be(:group_label) { create(:group_label, group: group) }
let_it_be(:project_label) { create(:label, project: project) }
let_it_be(:group_backlog_list) { create(:backlog_list, board: group_board) }
let_it_be(:project_backlog_list) { create(:backlog_list, board: project_board) }
let_it_be(:issue) { create(:labeled_issue, project: project, labels: [group_label, project_label]) }
before_all do
project.add_maintainer(user)
group.add_owner(user)
end
where(:board_type, :graphql_board_lists_enabled, :board_new_list_enabled) do
:project | true | true
:project | false | true
:project | true | false
:project | false | false
:group | true | true
:group | false | true
:group | true | false
:group | false | false
end
with_them do
before do
sign_in(user)
set_cookie('sidebar_collapsed', 'true')
stub_feature_flags(
graphql_board_lists: graphql_board_lists_enabled,
board_new_list: board_new_list_enabled
)
if board_type == :project
visit project_board_path(project, project_board)
elsif board_type == :group
visit group_board_path(group, group_board)
end
wait_for_all_requests
end
it 'creates new column for label containing labeled issue' do
click_button button_text(board_new_list_enabled)
wait_for_all_requests
select_label(board_new_list_enabled, group_label)
wait_for_all_requests
expect(page).to have_selector('.board', text: group_label.title)
expect(find('.board:nth-child(2) .board-card')).to have_content(issue.title)
end
end
def select_label(board_new_list_enabled, label)
if board_new_list_enabled
page.within('.board-add-new-list') do
find('label', text: label.title).click
click_button 'Add'
end
else
page.within('.dropdown-menu-issues-board-new') do
click_link label.title
end
end
end
def button_text(board_new_list_enabled)
if board_new_list_enabled
'Create list'
else
'Add list'
end
end
end
import { GlSearchBoxByType } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import BoardAddNewColumn from '~/boards/components/board_add_new_column.vue';
import defaultState from '~/boards/stores/state';
import { mockLabelList } from '../mock_data';
Vue.use(Vuex);
describe('Board card layout', () => {
let wrapper;
let shouldUseGraphQL;
const createStore = ({ actions = {}, getters = {}, state = {} } = {}) => {
return new Vuex.Store({
state: {
...defaultState,
...state,
},
actions,
getters,
});
};
const mountComponent = ({
selectedLabelId,
labels = [],
getListByLabelId = jest.fn(),
actions = {},
} = {}) => {
wrapper = extendedWrapper(
shallowMount(BoardAddNewColumn, {
stubs: {
GlFormGroup: true,
},
data() {
return {
selectedLabelId,
};
},
store: createStore({
actions: {
fetchLabels: jest.fn(),
setAddColumnFormVisibility: jest.fn(),
...actions,
},
getters: {
shouldUseGraphQL: () => shouldUseGraphQL,
getListByLabelId: () => getListByLabelId,
},
state: {
labels,
labelsLoading: false,
},
}),
provide: {
scopedLabelsAvailable: true,
},
}),
);
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
const formTitle = () => wrapper.findByTestId('board-add-column-form-title').text();
const findSearchInput = () => wrapper.find(GlSearchBoxByType);
const cancelButton = () => wrapper.findByTestId('cancelAddNewColumn');
const submitButton = () => wrapper.findByTestId('addNewColumnButton');
beforeEach(() => {
shouldUseGraphQL = true;
});
it('shows form title & search input', () => {
mountComponent();
expect(formTitle()).toEqual(BoardAddNewColumn.i18n.newLabelList);
expect(findSearchInput().exists()).toBe(true);
});
it('clicking cancel hides the form', () => {
const setAddColumnFormVisibility = jest.fn();
mountComponent({
actions: {
setAddColumnFormVisibility,
},
});
cancelButton().vm.$emit('click');
expect(setAddColumnFormVisibility).toHaveBeenCalledWith(expect.anything(), false);
});
describe('Add list button', () => {
it('is disabled if no item is selected', () => {
mountComponent();
expect(submitButton().props('disabled')).toBe(true);
});
it('adds a new list on click', async () => {
const labelId = mockLabelList.label.id;
const highlightList = jest.fn();
const createList = jest.fn();
mountComponent({
labels: [mockLabelList.label],
selectedLabelId: labelId,
actions: {
createList,
highlightList,
},
});
await nextTick();
submitButton().vm.$emit('click');
expect(highlightList).not.toHaveBeenCalled();
expect(createList).toHaveBeenCalledWith(expect.anything(), { labelId });
});
it('highlights existing list if trying to re-add', async () => {
const getListByLabelId = jest.fn().mockReturnValue(mockLabelList);
const highlightList = jest.fn();
const createList = jest.fn();
mountComponent({
labels: [mockLabelList.label],
selectedLabelId: mockLabelList.label.id,
getListByLabelId,
actions: {
createList,
highlightList,
},
});
await nextTick();
submitButton().vm.$emit('click');
expect(highlightList).toHaveBeenCalledWith(expect.anything(), mockLabelList.id);
expect(createList).not.toHaveBeenCalled();
});
});
});
......@@ -327,11 +327,15 @@ describe('fetchLabels', () => {
};
jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse);
await testAction({
action: actions.fetchLabels,
state: { boardType: 'group' },
expectedMutations: [{ type: types.RECEIVE_LABELS_SUCCESS, payload: labels }],
});
const commit = jest.fn();
const getters = {
shouldUseGraphQL: () => true,
};
const state = { boardType: 'group' };
await actions.fetchLabels({ getters, state, commit });
expect(commit).toHaveBeenCalledWith(types.RECEIVE_LABELS_SUCCESS, labels);
});
});
......
......@@ -109,11 +109,31 @@ describe('Board Store Mutations', () => {
});
});
describe('RECEIVE_LABELS_REQUEST', () => {
it('sets labelsLoading on state', () => {
mutations.RECEIVE_LABELS_REQUEST(state);
expect(state.labelsLoading).toEqual(true);
});
});
describe('RECEIVE_LABELS_SUCCESS', () => {
it('sets labels on state', () => {
mutations.RECEIVE_LABELS_SUCCESS(state, labels);
expect(state.labels).toEqual(labels);
expect(state.labelsLoading).toEqual(false);
});
});
describe('RECEIVE_LABELS_FAILURE', () => {
it('sets error message', () => {
mutations.RECEIVE_LABELS_FAILURE(state);
expect(state.error).toEqual(
'An error occurred while fetching labels. Please reload the page.',
);
expect(state.labelsLoading).toEqual(false);
});
});
......
......@@ -9,6 +9,8 @@ RSpec.shared_examples 'multiple issue boards' do
login_as(user)
stub_feature_flags(board_new_list: false)
visit boards_path
wait_for_requests
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