Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/boards')
-rw-r--r--spec/frontend/boards/board_blank_state_spec.js95
-rw-r--r--spec/frontend/boards/board_list_new_spec.js234
-rw-r--r--spec/frontend/boards/board_list_spec.js3
-rw-r--r--spec/frontend/boards/boards_store_spec.js19
-rw-r--r--spec/frontend/boards/components/board_configuration_options_spec.js59
-rw-r--r--spec/frontend/boards/components/board_content_spec.js3
-rw-r--r--spec/frontend/boards/components/sidebar/board_editable_item_spec.js26
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js143
-rw-r--r--spec/frontend/boards/mock_data.js8
-rw-r--r--spec/frontend/boards/stores/actions_spec.js236
-rw-r--r--spec/frontend/boards/stores/getters_spec.js30
-rw-r--r--spec/frontend/boards/stores/mutations_spec.js135
12 files changed, 804 insertions, 187 deletions
diff --git a/spec/frontend/boards/board_blank_state_spec.js b/spec/frontend/boards/board_blank_state_spec.js
deleted file mode 100644
index 3ffdda52f58..00000000000
--- a/spec/frontend/boards/board_blank_state_spec.js
+++ /dev/null
@@ -1,95 +0,0 @@
-import Vue from 'vue';
-import boardsStore from '~/boards/stores/boards_store';
-import BoardBlankState from '~/boards/components/board_blank_state.vue';
-
-describe('Boards blank state', () => {
- let vm;
- let fail = false;
-
- beforeEach(done => {
- const Comp = Vue.extend(BoardBlankState);
-
- boardsStore.create();
-
- jest.spyOn(boardsStore, 'addList').mockImplementation();
- jest.spyOn(boardsStore, 'removeList').mockImplementation();
- jest.spyOn(boardsStore, 'generateDefaultLists').mockImplementation(
- () =>
- new Promise((resolve, reject) => {
- if (fail) {
- reject();
- } else {
- resolve({
- data: [
- {
- id: 1,
- title: 'To Do',
- label: { id: 1 },
- },
- {
- id: 2,
- title: 'Doing',
- label: { id: 2 },
- },
- ],
- });
- }
- }),
- );
-
- vm = new Comp();
-
- setImmediate(() => {
- vm.$mount();
- done();
- });
- });
-
- it('renders pre-defined labels', () => {
- expect(vm.$el.querySelectorAll('.board-blank-state-list li').length).toBe(2);
-
- expect(vm.$el.querySelectorAll('.board-blank-state-list li')[0].textContent.trim()).toEqual(
- 'To Do',
- );
-
- expect(vm.$el.querySelectorAll('.board-blank-state-list li')[1].textContent.trim()).toEqual(
- 'Doing',
- );
- });
-
- it('clears blank state', done => {
- vm.$el.querySelector('.btn-default').click();
-
- setImmediate(() => {
- expect(boardsStore.welcomeIsHidden()).toBeTruthy();
-
- done();
- });
- });
-
- it('creates pre-defined labels', done => {
- vm.$el.querySelector('.btn-success').click();
-
- setImmediate(() => {
- expect(boardsStore.addList).toHaveBeenCalledTimes(2);
- expect(boardsStore.addList).toHaveBeenCalledWith(expect.objectContaining({ title: 'To Do' }));
-
- expect(boardsStore.addList).toHaveBeenCalledWith(expect.objectContaining({ title: 'Doing' }));
-
- done();
- });
- });
-
- it('resets the store if request fails', done => {
- fail = true;
-
- vm.$el.querySelector('.btn-success').click();
-
- setImmediate(() => {
- expect(boardsStore.welcomeIsHidden()).toBeFalsy();
- expect(boardsStore.removeList).toHaveBeenCalledWith(undefined, 'label');
-
- done();
- });
- });
-});
diff --git a/spec/frontend/boards/board_list_new_spec.js b/spec/frontend/boards/board_list_new_spec.js
new file mode 100644
index 00000000000..163611c2197
--- /dev/null
+++ b/spec/frontend/boards/board_list_new_spec.js
@@ -0,0 +1,234 @@
+/* global List */
+/* global ListIssue */
+
+import Vuex from 'vuex';
+import { useFakeRequestAnimationFrame } from 'helpers/fake_request_animation_frame';
+import { createLocalVue, mount } from '@vue/test-utils';
+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/issue';
+import '~/boards/models/list';
+import { listObj, mockIssuesByListId, issues } from './mock_data';
+import defaultState from '~/boards/stores/state';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+const actions = {
+ fetchIssuesForList: jest.fn(),
+};
+
+const createStore = (state = defaultState) => {
+ return new Vuex.Store({
+ state,
+ actions,
+ });
+};
+
+const createComponent = ({
+ listIssueProps = {},
+ componentProps = {},
+ listProps = {},
+ state = {},
+} = {}) => {
+ const store = createStore({
+ issuesByListId: mockIssuesByListId,
+ issues,
+ pageInfoByListId: {
+ 'gid://gitlab/List/1': { hasNextPage: true },
+ 'gid://gitlab/List/2': {},
+ },
+ listsFlags: {
+ 'gid://gitlab/List/1': {},
+ 'gid://gitlab/List/2': {},
+ },
+ ...state,
+ });
+
+ const list = new List({
+ ...listObj,
+ id: 'gid://gitlab/List/1',
+ ...listProps,
+ doNotFetchIssues: true,
+ });
+ const issue = new ListIssue({
+ title: 'Testing',
+ id: 1,
+ iid: 1,
+ confidential: false,
+ labels: [],
+ assignees: [],
+ ...listIssueProps,
+ });
+ if (!Object.prototype.hasOwnProperty.call(listProps, 'issuesSize')) {
+ list.issuesSize = 1;
+ }
+
+ const component = mount(BoardList, {
+ localVue,
+ propsData: {
+ disabled: false,
+ list,
+ issues: [issue],
+ ...componentProps,
+ },
+ store,
+ provide: {
+ groupId: null,
+ rootPath: '/',
+ },
+ });
+
+ return component;
+};
+
+describe('Board list component', () => {
+ let wrapper;
+ useFakeRequestAnimationFrame();
+
+ describe('When Expanded', () => {
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders component', () => {
+ expect(wrapper.find('.board-list-component').exists()).toBe(true);
+ });
+
+ it('renders loading icon', () => {
+ wrapper = createComponent({
+ state: { listsFlags: { 'gid://gitlab/List/1': { isLoading: true } } },
+ });
+
+ expect(wrapper.find('[data-testid="board_list_loading"').exists()).toBe(true);
+ });
+
+ it('renders issues', () => {
+ expect(wrapper.findAll(BoardCard).length).toBe(1);
+ });
+
+ it('sets data attribute with issue id', () => {
+ expect(wrapper.find('.board-card').attributes('data-issue-id')).toBe('1');
+ });
+
+ it('shows new issue form', async () => {
+ wrapper.vm.toggleForm();
+
+ await wrapper.vm.$nextTick();
+ expect(wrapper.find('.board-new-issue-form').exists()).toBe(true);
+ });
+
+ it('shows new issue form after eventhub event', async () => {
+ eventHub.$emit(`toggle-issue-form-${wrapper.vm.list.id}`);
+
+ await wrapper.vm.$nextTick();
+ expect(wrapper.find('.board-new-issue-form').exists()).toBe(true);
+ });
+
+ it('does not show new issue form for closed list', () => {
+ wrapper.setProps({ list: { type: 'closed' } });
+ wrapper.vm.toggleForm();
+
+ expect(wrapper.find('.board-new-issue-form').exists()).toBe(false);
+ });
+
+ it('shows count list item', async () => {
+ wrapper.vm.showCount = true;
+
+ await wrapper.vm.$nextTick();
+ expect(wrapper.find('.board-list-count').exists()).toBe(true);
+
+ expect(wrapper.find('.board-list-count').text()).toBe('Showing all issues');
+ });
+
+ it('sets data attribute with invalid id', async () => {
+ wrapper.vm.showCount = true;
+
+ await wrapper.vm.$nextTick();
+ expect(wrapper.find('.board-list-count').attributes('data-issue-id')).toBe('-1');
+ });
+
+ it('shows how many more issues to load', async () => {
+ wrapper.vm.showCount = true;
+ wrapper.setProps({ list: { issuesSize: 20 } });
+
+ await wrapper.vm.$nextTick();
+ expect(wrapper.find('.board-list-count').text()).toBe('Showing 1 of 20 issues');
+ });
+ });
+
+ describe('load more issues', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ listProps: { issuesSize: 25 },
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('loads more issues after scrolling', () => {
+ wrapper.vm.$refs.list.dispatchEvent(new Event('scroll'));
+
+ expect(actions.fetchIssuesForList).toHaveBeenCalled();
+ });
+
+ it('does not load issues if already loading', () => {
+ wrapper.vm.$refs.list.dispatchEvent(new Event('scroll'));
+ wrapper.vm.$refs.list.dispatchEvent(new Event('scroll'));
+
+ expect(actions.fetchIssuesForList).toHaveBeenCalledTimes(1);
+ });
+
+ it('shows loading more spinner', async () => {
+ wrapper.vm.showCount = true;
+ wrapper.vm.list.loadingMore = true;
+
+ await wrapper.vm.$nextTick();
+ expect(wrapper.find('.board-list-count .gl-spinner').exists()).toBe(true);
+ });
+ });
+
+ describe('max issue count warning', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ listProps: { issuesSize: 50 },
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when issue count exceeds max issue count', () => {
+ it('sets background to bg-danger-100', async () => {
+ wrapper.setProps({ list: { issuesSize: 4, maxIssueCount: 3 } });
+
+ await wrapper.vm.$nextTick();
+ expect(wrapper.find('.bg-danger-100').exists()).toBe(true);
+ });
+ });
+
+ 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 } });
+
+ expect(wrapper.find('.bg-danger-100').exists()).toBe(false);
+ });
+ });
+
+ describe('when list max issue count is 0', () => {
+ it('does not sets background to bg-danger-100', () => {
+ wrapper.setProps({ list: { maxIssueCount: 0 } });
+
+ expect(wrapper.find('.bg-danger-100').exists()).toBe(false);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/boards/board_list_spec.js b/spec/frontend/boards/board_list_spec.js
index 88883ae61d4..0fe3c88f518 100644
--- a/spec/frontend/boards/board_list_spec.js
+++ b/spec/frontend/boards/board_list_spec.js
@@ -44,7 +44,6 @@ const createComponent = ({ done, listIssueProps = {}, componentProps = {}, listP
disabled: false,
list,
issues: list.issues,
- loading: false,
...componentProps,
},
provide: {
@@ -94,7 +93,7 @@ describe('Board list component', () => {
});
it('renders loading icon', () => {
- component.loading = true;
+ component.list.loading = true;
return Vue.nextTick().then(() => {
expect(component.$el.querySelector('.board-list-loading')).not.toBeNull();
diff --git a/spec/frontend/boards/boards_store_spec.js b/spec/frontend/boards/boards_store_spec.js
index 41971137b95..e7c1cf79fdc 100644
--- a/spec/frontend/boards/boards_store_spec.js
+++ b/spec/frontend/boards/boards_store_spec.js
@@ -1,7 +1,7 @@
import AxiosMockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'helpers/test_constants';
import axios from '~/lib/utils/axios_utils';
-import boardsStore from '~/boards/stores/boards_store';
+import boardsStore, { gqlClient } from '~/boards/stores/boards_store';
import eventHub from '~/boards/eventhub';
import { listObj, listObjDuplicate } from './mock_data';
@@ -503,11 +503,15 @@ describe('boardsStore', () => {
beforeEach(() => {
requestSpy = jest.fn();
axiosMock.onPut(url).replyOnce(config => requestSpy(config));
+ jest.spyOn(gqlClient, 'mutate').mockReturnValue(Promise.resolve({}));
});
it('makes a request to update the board', () => {
requestSpy.mockReturnValue([200, dummyResponse]);
- const expectedResponse = expect.objectContaining({ data: dummyResponse });
+ const expectedResponse = [
+ expect.objectContaining({ data: dummyResponse }),
+ expect.objectContaining({}),
+ ];
return expect(
boardsStore.createBoard({
@@ -555,11 +559,12 @@ describe('boardsStore', () => {
beforeEach(() => {
requestSpy = jest.fn();
axiosMock.onPost(url).replyOnce(config => requestSpy(config));
+ jest.spyOn(gqlClient, 'mutate').mockReturnValue(Promise.resolve({}));
});
it('makes a request to create a new board', () => {
requestSpy.mockReturnValue([200, dummyResponse]);
- const expectedResponse = expect.objectContaining({ data: dummyResponse });
+ const expectedResponse = dummyResponse;
return expect(boardsStore.createBoard(board))
.resolves.toEqual(expectedResponse)
@@ -740,14 +745,6 @@ describe('boardsStore', () => {
expect(boardsStore.shouldAddBlankState()).toBe(true);
});
- it('adds the blank state', () => {
- boardsStore.addBlankState();
-
- const list = boardsStore.findList('type', 'blank', 'blank');
-
- expect(list).toBeDefined();
- });
-
it('removes list from state', () => {
boardsStore.addList(listObj);
diff --git a/spec/frontend/boards/components/board_configuration_options_spec.js b/spec/frontend/boards/components/board_configuration_options_spec.js
new file mode 100644
index 00000000000..e9a1cb6a4e8
--- /dev/null
+++ b/spec/frontend/boards/components/board_configuration_options_spec.js
@@ -0,0 +1,59 @@
+import { shallowMount } from '@vue/test-utils';
+import BoardConfigurationOptions from '~/boards/components/board_configuration_options.vue';
+
+describe('BoardConfigurationOptions', () => {
+ let wrapper;
+ const board = { hide_backlog_list: false, hide_closed_list: false };
+
+ const defaultProps = {
+ currentBoard: board,
+ board,
+ isNewForm: false,
+ };
+
+ const createComponent = () => {
+ wrapper = shallowMount(BoardConfigurationOptions, {
+ propsData: { ...defaultProps },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const backlogListCheckbox = el => el.find('[data-testid="backlog-list-checkbox"]');
+ const closedListCheckbox = el => el.find('[data-testid="closed-list-checkbox"]');
+
+ const checkboxAssert = (backlogCheckbox, closedCheckbox) => {
+ expect(backlogListCheckbox(wrapper).attributes('checked')).toEqual(
+ backlogCheckbox ? undefined : 'true',
+ );
+ expect(closedListCheckbox(wrapper).attributes('checked')).toEqual(
+ closedCheckbox ? undefined : 'true',
+ );
+ };
+
+ it.each`
+ backlogCheckboxValue | closedCheckboxValue
+ ${true} | ${true}
+ ${true} | ${false}
+ ${false} | ${true}
+ ${false} | ${false}
+ `(
+ 'renders two checkbox when one is $backlogCheckboxValue and other is $closedCheckboxValue',
+ async ({ backlogCheckboxValue, closedCheckboxValue }) => {
+ await wrapper.setData({
+ hideBacklogList: backlogCheckboxValue,
+ hideClosedList: closedCheckboxValue,
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ checkboxAssert(backlogCheckboxValue, closedCheckboxValue);
+ });
+ },
+ );
+});
diff --git a/spec/frontend/boards/components/board_content_spec.js b/spec/frontend/boards/components/board_content_spec.js
index df117d06cdf..09e38001e2e 100644
--- a/spec/frontend/boards/components/board_content_spec.js
+++ b/spec/frontend/boards/components/board_content_spec.js
@@ -23,9 +23,6 @@ describe('BoardContent', () => {
return new Vuex.Store({
getters,
state,
- actions: {
- fetchIssuesForAllLists: () => {},
- },
});
};
diff --git a/spec/frontend/boards/components/sidebar/board_editable_item_spec.js b/spec/frontend/boards/components/sidebar/board_editable_item_spec.js
index 1dbcbd06407..d7df2ff1563 100644
--- a/spec/frontend/boards/components/sidebar/board_editable_item_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_editable_item_spec.js
@@ -96,12 +96,34 @@ describe('boards sidebar remove issue', () => {
expect(findExpanded().isVisible()).toBe(false);
});
- it('emits changed event', async () => {
+ it('emits close event', async () => {
document.body.click();
await wrapper.vm.$nextTick();
- expect(wrapper.emitted().changed[1][0]).toBe(false);
+ expect(wrapper.emitted().close.length).toBe(1);
});
});
+
+ it('emits open when edit button is clicked and edit is initailized to false', async () => {
+ createComponent({ canUpdate: true });
+
+ findEditButton().vm.$emit('click');
+
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.emitted().open.length).toBe(1);
+ });
+
+ it('does not emits events when collapsing with false `emitEvent`', async () => {
+ createComponent({ canUpdate: true });
+
+ findEditButton().vm.$emit('click');
+
+ await wrapper.vm.$nextTick();
+
+ wrapper.vm.collapse({ emitEvent: false });
+
+ expect(wrapper.emitted().close).toBeUndefined();
+ });
});
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js
new file mode 100644
index 00000000000..da000d21f6a
--- /dev/null
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js
@@ -0,0 +1,143 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlLabel } from '@gitlab/ui';
+import { TEST_HOST } from 'helpers/test_constants';
+import { labels as TEST_LABELS, mockIssue as TEST_ISSUE } from 'jest/boards/mock_data';
+import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
+import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import { createStore } from '~/boards/stores';
+import createFlash from '~/flash';
+
+jest.mock('~/flash');
+
+const TEST_LABELS_PAYLOAD = TEST_LABELS.map(label => ({ ...label, set: true }));
+const TEST_LABELS_TITLES = TEST_LABELS.map(label => label.title);
+
+describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
+ let wrapper;
+ let store;
+
+ afterEach(() => {
+ wrapper.destroy();
+ store = null;
+ wrapper = null;
+ });
+
+ const createWrapper = ({ labels = [] } = {}) => {
+ store = createStore();
+ store.state.issues = { [TEST_ISSUE.id]: { ...TEST_ISSUE, labels } };
+ store.state.activeId = TEST_ISSUE.id;
+
+ wrapper = shallowMount(BoardSidebarLabelsSelect, {
+ store,
+ provide: {
+ canUpdate: true,
+ labelsFetchPath: TEST_HOST,
+ labelsManagePath: TEST_HOST,
+ labelsFilterBasePath: TEST_HOST,
+ },
+ stubs: {
+ 'board-editable-item': BoardEditableItem,
+ 'labels-select': '<div></div>',
+ },
+ });
+ };
+
+ const findLabelsSelect = () => wrapper.find({ ref: 'labelsSelect' });
+ const findLabelsTitles = () => wrapper.findAll(GlLabel).wrappers.map(item => item.props('title'));
+ const findCollapsed = () => wrapper.find('[data-testid="collapsed-content"]');
+
+ it('renders "None" when no labels are selected', () => {
+ createWrapper();
+
+ expect(findCollapsed().text()).toBe('None');
+ });
+
+ it('renders labels when set', () => {
+ createWrapper({ labels: TEST_LABELS });
+
+ expect(findLabelsTitles()).toEqual(TEST_LABELS_TITLES);
+ });
+
+ describe('when labels are submitted', () => {
+ beforeEach(async () => {
+ createWrapper();
+
+ jest.spyOn(wrapper.vm, 'setActiveIssueLabels').mockImplementation(() => TEST_LABELS);
+ findLabelsSelect().vm.$emit('updateSelectedLabels', TEST_LABELS_PAYLOAD);
+ store.state.issues[TEST_ISSUE.id].labels = TEST_LABELS;
+ await wrapper.vm.$nextTick();
+ });
+
+ it('collapses sidebar and renders labels', () => {
+ expect(findCollapsed().isVisible()).toBe(true);
+ expect(findLabelsTitles()).toEqual(TEST_LABELS_TITLES);
+ });
+
+ it('commits change to the server', () => {
+ expect(wrapper.vm.setActiveIssueLabels).toHaveBeenCalledWith({
+ addLabelIds: TEST_LABELS.map(label => label.id),
+ projectPath: 'gitlab-org/test-subgroup/gitlab-test',
+ removeLabelIds: [],
+ });
+ });
+ });
+
+ describe('when labels are updated over existing labels', () => {
+ const testLabelsPayload = [{ id: 5, set: true }, { id: 7, set: true }];
+ const expectedLabels = [{ id: 5 }, { id: 7 }];
+
+ beforeEach(async () => {
+ createWrapper({ labels: TEST_LABELS });
+
+ jest.spyOn(wrapper.vm, 'setActiveIssueLabels').mockImplementation(() => expectedLabels);
+ findLabelsSelect().vm.$emit('updateSelectedLabels', testLabelsPayload);
+ await wrapper.vm.$nextTick();
+ });
+
+ it('commits change to the server', () => {
+ expect(wrapper.vm.setActiveIssueLabels).toHaveBeenCalledWith({
+ addLabelIds: [5, 7],
+ removeLabelIds: [6],
+ projectPath: 'gitlab-org/test-subgroup/gitlab-test',
+ });
+ });
+ });
+
+ describe('when removing individual labels', () => {
+ const testLabel = TEST_LABELS[0];
+
+ beforeEach(async () => {
+ createWrapper({ labels: [testLabel] });
+
+ jest.spyOn(wrapper.vm, 'setActiveIssueLabels').mockImplementation(() => {});
+ });
+
+ it('commits change to the server', () => {
+ wrapper.find(GlLabel).vm.$emit('close', testLabel);
+
+ expect(wrapper.vm.setActiveIssueLabels).toHaveBeenCalledWith({
+ removeLabelIds: [getIdFromGraphQLId(testLabel.id)],
+ projectPath: 'gitlab-org/test-subgroup/gitlab-test',
+ });
+ });
+ });
+
+ describe('when the mutation fails', () => {
+ beforeEach(async () => {
+ createWrapper({ labels: TEST_LABELS });
+
+ jest.spyOn(wrapper.vm, 'setActiveIssueLabels').mockImplementation(() => {
+ throw new Error(['failed mutation']);
+ });
+ findLabelsSelect().vm.$emit('updateSelectedLabels', [{ id: '?' }]);
+ await wrapper.vm.$nextTick();
+ });
+
+ it('collapses sidebar and renders former issue weight', () => {
+ expect(findCollapsed().isVisible()).toBe(true);
+ expect(findLabelsTitles()).toEqual(TEST_LABELS_TITLES);
+ expect(createFlash).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js
index 5776332c499..50c0a85fc70 100644
--- a/spec/frontend/boards/mock_data.js
+++ b/spec/frontend/boards/mock_data.js
@@ -108,13 +108,19 @@ const assignees = [
},
];
-const labels = [
+export const labels = [
{
id: 'gid://gitlab/GroupLabel/5',
title: 'Cosync',
color: '#34ebec',
description: null,
},
+ {
+ id: 'gid://gitlab/GroupLabel/6',
+ title: 'Brock',
+ color: '#e082b6',
+ description: null,
+ },
];
export const rawIssue = {
diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js
index bdbcd435708..78e70161121 100644
--- a/spec/frontend/boards/stores/actions_spec.js
+++ b/spec/frontend/boards/stores/actions_spec.js
@@ -6,12 +6,14 @@ import {
mockIssueWithModel,
mockIssue2WithModel,
rawIssue,
+ mockIssues,
+ labels,
} from '../mock_data';
import actions, { gqlClient } from '~/boards/stores/actions';
import * as types from '~/boards/stores/mutation_types';
import { inactiveId, ListType } from '~/boards/constants';
import issueMoveListMutation from '~/boards/queries/issue_move_list.mutation.graphql';
-import { fullBoardId } from '~/boards/boards_util';
+import { fullBoardId, formatListIssues, formatBoardLists } from '~/boards/boards_util';
const expectNotImplemented = action => {
it('is not implemented', () => {
@@ -76,6 +78,80 @@ describe('setActiveId', () => {
});
});
+describe('fetchLists', () => {
+ const state = {
+ endpoints: {
+ fullPath: 'gitlab-org',
+ boardId: 1,
+ },
+ filterParams: {},
+ boardType: 'group',
+ };
+
+ let queryResponse = {
+ data: {
+ group: {
+ board: {
+ hideBacklogList: true,
+ lists: {
+ nodes: [mockLists[1]],
+ },
+ },
+ },
+ },
+ };
+
+ const formattedLists = formatBoardLists(queryResponse.data.group.board.lists);
+
+ it('should commit mutations RECEIVE_BOARD_LISTS_SUCCESS on success', done => {
+ jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse);
+
+ testAction(
+ actions.fetchLists,
+ {},
+ state,
+ [
+ {
+ type: types.RECEIVE_BOARD_LISTS_SUCCESS,
+ payload: formattedLists,
+ },
+ ],
+ [{ type: 'showWelcomeList' }],
+ done,
+ );
+ });
+
+ it('dispatch createList action when backlog list does not exist and is not hidden', done => {
+ queryResponse = {
+ data: {
+ group: {
+ board: {
+ hideBacklogList: false,
+ lists: {
+ nodes: [mockLists[1]],
+ },
+ },
+ },
+ },
+ };
+ jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse);
+
+ testAction(
+ actions.fetchLists,
+ {},
+ state,
+ [
+ {
+ type: types.RECEIVE_BOARD_LISTS_SUCCESS,
+ payload: formattedLists,
+ },
+ ],
+ [{ type: 'createList', payload: { backlog: true } }, { type: 'showWelcomeList' }],
+ done,
+ );
+ });
+});
+
describe('showWelcomeList', () => {
it('should dispatch addList action', done => {
const state = {
@@ -176,16 +252,26 @@ describe('createList', () => {
describe('moveList', () => {
it('should commit MOVE_LIST mutation and dispatch updateList action', done => {
+ const initialBoardListsState = {
+ 'gid://gitlab/List/1': mockListsWithModel[0],
+ 'gid://gitlab/List/2': mockListsWithModel[1],
+ };
+
const state = {
endpoints: { fullPath: 'gitlab-org', boardId: '1' },
boardType: 'group',
disabled: false,
- boardLists: mockListsWithModel,
+ boardLists: initialBoardListsState,
};
testAction(
actions.moveList,
- { listId: 'gid://gitlab/List/1', newIndex: 1, adjustmentValue: 1 },
+ {
+ listId: 'gid://gitlab/List/1',
+ replacedListId: 'gid://gitlab/List/2',
+ newIndex: 1,
+ adjustmentValue: 1,
+ },
state,
[
{
@@ -196,7 +282,11 @@ describe('moveList', () => {
[
{
type: 'updateList',
- payload: { listId: 'gid://gitlab/List/1', position: 0, backupList: mockListsWithModel },
+ payload: {
+ listId: 'gid://gitlab/List/1',
+ position: 0,
+ backupList: initialBoardListsState,
+ },
},
],
done,
@@ -237,6 +327,99 @@ describe('deleteList', () => {
expectNotImplemented(actions.deleteList);
});
+describe('fetchIssuesForList', () => {
+ const listId = mockLists[0].id;
+
+ const state = {
+ endpoints: {
+ fullPath: 'gitlab-org',
+ boardId: 1,
+ },
+ filterParams: {},
+ boardType: 'group',
+ };
+
+ const mockIssuesNodes = mockIssues.map(issue => ({ node: issue }));
+
+ const pageInfo = {
+ endCursor: '',
+ hasNextPage: false,
+ };
+
+ const queryResponse = {
+ data: {
+ group: {
+ board: {
+ lists: {
+ nodes: [
+ {
+ id: listId,
+ issues: {
+ edges: mockIssuesNodes,
+ pageInfo,
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ };
+
+ const formattedIssues = formatListIssues(queryResponse.data.group.board.lists);
+
+ const listPageInfo = {
+ [listId]: pageInfo,
+ };
+
+ it('should commit mutations REQUEST_ISSUES_FOR_LIST and RECEIVE_ISSUES_FOR_LIST_SUCCESS on success', done => {
+ jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse);
+
+ testAction(
+ actions.fetchIssuesForList,
+ { listId },
+ state,
+ [
+ {
+ type: types.REQUEST_ISSUES_FOR_LIST,
+ payload: { listId, fetchNext: false },
+ },
+ {
+ type: types.RECEIVE_ISSUES_FOR_LIST_SUCCESS,
+ payload: { listIssues: formattedIssues, listPageInfo, listId },
+ },
+ ],
+ [],
+ done,
+ );
+ });
+
+ it('should commit mutations REQUEST_ISSUES_FOR_LIST and RECEIVE_ISSUES_FOR_LIST_FAILURE on failure', done => {
+ jest.spyOn(gqlClient, 'query').mockResolvedValue(Promise.reject());
+
+ testAction(
+ actions.fetchIssuesForList,
+ { listId },
+ state,
+ [
+ {
+ type: types.REQUEST_ISSUES_FOR_LIST,
+ payload: { listId, fetchNext: false },
+ },
+ { type: types.RECEIVE_ISSUES_FOR_LIST_FAILURE, payload: listId },
+ ],
+ [],
+ done,
+ );
+ });
+});
+
+describe('resetIssues', () => {
+ it('commits RESET_ISSUES mutation', () => {
+ return testAction(actions.resetIssues, {}, {}, [{ type: types.RESET_ISSUES }], []);
+ });
+});
+
describe('moveIssue', () => {
const listIssues = {
'gid://gitlab/List/1': [436, 437],
@@ -418,6 +601,51 @@ describe('addListIssueFailure', () => {
});
});
+describe('setActiveIssueLabels', () => {
+ const state = { issues: { [mockIssue.id]: mockIssue } };
+ const getters = { getActiveIssue: mockIssue };
+ const testLabelIds = labels.map(label => label.id);
+ const input = {
+ addLabelIds: testLabelIds,
+ removeLabelIds: [],
+ projectPath: 'h/b',
+ };
+
+ it('should assign labels on success', done => {
+ jest
+ .spyOn(gqlClient, 'mutate')
+ .mockResolvedValue({ data: { updateIssue: { issue: { labels: { nodes: labels } } } } });
+
+ const payload = {
+ issueId: getters.getActiveIssue.id,
+ prop: 'labels',
+ value: labels,
+ };
+
+ testAction(
+ actions.setActiveIssueLabels,
+ input,
+ { ...state, ...getters },
+ [
+ {
+ type: types.UPDATE_ISSUE_BY_ID,
+ payload,
+ },
+ ],
+ [],
+ done,
+ );
+ });
+
+ it('throws error if fails', async () => {
+ jest
+ .spyOn(gqlClient, 'mutate')
+ .mockResolvedValue({ data: { updateIssue: { errors: ['failed mutation'] } } });
+
+ await expect(actions.setActiveIssueLabels({ getters }, input)).rejects.toThrow(Error);
+ });
+});
+
describe('fetchBacklog', () => {
expectNotImplemented(actions.fetchBacklog);
});
diff --git a/spec/frontend/boards/stores/getters_spec.js b/spec/frontend/boards/stores/getters_spec.js
index 288143a0f21..b987080abab 100644
--- a/spec/frontend/boards/stores/getters_spec.js
+++ b/spec/frontend/boards/stores/getters_spec.js
@@ -1,6 +1,13 @@
import getters from '~/boards/stores/getters';
import { inactiveId } from '~/boards/constants';
-import { mockIssue, mockIssue2, mockIssues, mockIssuesByListId, issues } from '../mock_data';
+import {
+ mockIssue,
+ mockIssue2,
+ mockIssues,
+ mockIssuesByListId,
+ issues,
+ mockListsWithModel,
+} from '../mock_data';
describe('Boards - Getters', () => {
describe('getLabelToggleState', () => {
@@ -130,4 +137,25 @@ describe('Boards - Getters', () => {
);
});
});
+
+ const boardsState = {
+ boardLists: {
+ 'gid://gitlab/List/1': mockListsWithModel[0],
+ 'gid://gitlab/List/2': mockListsWithModel[1],
+ },
+ };
+
+ describe('getListByLabelId', () => {
+ it('returns list for a given label id', () => {
+ expect(getters.getListByLabelId(boardsState)('gid://gitlab/GroupLabel/121')).toEqual(
+ mockListsWithModel[1],
+ );
+ });
+ });
+
+ describe('getListByTitle', () => {
+ it('returns list for a given list title', () => {
+ expect(getters.getListByTitle(boardsState)('To Do')).toEqual(mockListsWithModel[1]);
+ });
+ });
});
diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js
index a13a99a507e..6e53f184bb3 100644
--- a/spec/frontend/boards/stores/mutations_spec.js
+++ b/spec/frontend/boards/stores/mutations_spec.js
@@ -2,8 +2,6 @@ import mutations from '~/boards/stores/mutations';
import * as types from '~/boards/stores/mutation_types';
import defaultState from '~/boards/stores/state';
import {
- listObj,
- listObjDuplicate,
mockListsWithModel,
mockLists,
rawIssue,
@@ -22,6 +20,11 @@ const expectNotImplemented = action => {
describe('Board Store Mutations', () => {
let state;
+ const initialBoardListsState = {
+ 'gid://gitlab/List/1': mockListsWithModel[0],
+ 'gid://gitlab/List/2': mockListsWithModel[1],
+ };
+
beforeEach(() => {
state = defaultState();
});
@@ -56,11 +59,19 @@ describe('Board Store Mutations', () => {
describe('RECEIVE_BOARD_LISTS_SUCCESS', () => {
it('Should set boardLists to state', () => {
- const lists = [listObj, listObjDuplicate];
+ mutations[types.RECEIVE_BOARD_LISTS_SUCCESS](state, initialBoardListsState);
+
+ expect(state.boardLists).toEqual(initialBoardListsState);
+ });
+ });
- mutations[types.RECEIVE_BOARD_LISTS_SUCCESS](state, lists);
+ describe('RECEIVE_BOARD_LISTS_FAILURE', () => {
+ it('Should set error in state', () => {
+ mutations[types.RECEIVE_BOARD_LISTS_FAILURE](state);
- expect(state.boardLists).toEqual(lists);
+ expect(state.error).toEqual(
+ 'An error occurred while fetching the board lists. Please reload the page.',
+ );
});
});
@@ -95,7 +106,13 @@ describe('Board Store Mutations', () => {
});
describe('RECEIVE_ADD_LIST_SUCCESS', () => {
- expectNotImplemented(mutations.RECEIVE_ADD_LIST_SUCCESS);
+ it('adds list to boardLists state', () => {
+ mutations.RECEIVE_ADD_LIST_SUCCESS(state, mockListsWithModel[0]);
+
+ expect(state.boardLists).toEqual({
+ [mockListsWithModel[0].id]: mockListsWithModel[0],
+ });
+ });
});
describe('RECEIVE_ADD_LIST_ERROR', () => {
@@ -106,7 +123,7 @@ describe('Board Store Mutations', () => {
it('updates boardLists state with reordered lists', () => {
state = {
...state,
- boardLists: mockListsWithModel,
+ boardLists: initialBoardListsState,
};
mutations.MOVE_LIST(state, {
@@ -114,7 +131,10 @@ describe('Board Store Mutations', () => {
listAtNewIndex: mockListsWithModel[1],
});
- expect(state.boardLists).toEqual([mockListsWithModel[1], mockListsWithModel[0]]);
+ expect(state.boardLists).toEqual({
+ 'gid://gitlab/List/2': mockListsWithModel[1],
+ 'gid://gitlab/List/1': mockListsWithModel[0],
+ });
});
});
@@ -122,13 +142,16 @@ describe('Board Store Mutations', () => {
it('updates boardLists state with previous order and sets error message', () => {
state = {
...state,
- boardLists: [mockListsWithModel[1], mockListsWithModel[0]],
+ boardLists: {
+ 'gid://gitlab/List/2': mockListsWithModel[1],
+ 'gid://gitlab/List/1': mockListsWithModel[0],
+ },
error: undefined,
};
- mutations.UPDATE_LIST_FAILURE(state, mockListsWithModel);
+ mutations.UPDATE_LIST_FAILURE(state, initialBoardListsState);
- expect(state.boardLists).toEqual(mockListsWithModel);
+ expect(state.boardLists).toEqual(initialBoardListsState);
expect(state.error).toEqual('An error occurred while updating the list. Please try again.');
});
});
@@ -145,6 +168,23 @@ describe('Board Store Mutations', () => {
expectNotImplemented(mutations.RECEIVE_REMOVE_LIST_ERROR);
});
+ describe('RESET_ISSUES', () => {
+ it('should remove issues from issuesByListId state', () => {
+ const issuesByListId = {
+ 'gid://gitlab/List/1': [mockIssue.id],
+ };
+
+ state = {
+ ...state,
+ issuesByListId,
+ };
+
+ mutations[types.RESET_ISSUES](state);
+
+ expect(state.issuesByListId).toEqual({ 'gid://gitlab/List/1': [] });
+ });
+ });
+
describe('RECEIVE_ISSUES_FOR_LIST_SUCCESS', () => {
it('updates issuesByListId and issues on state', () => {
const listIssues = {
@@ -156,14 +196,23 @@ describe('Board Store Mutations', () => {
state = {
...state,
- isLoadingIssues: true,
- issuesByListId: {},
+ issuesByListId: {
+ 'gid://gitlab/List/1': [],
+ },
issues: {},
- boardLists: mockListsWithModel,
+ boardLists: initialBoardListsState,
+ };
+
+ const listPageInfo = {
+ 'gid://gitlab/List/1': {
+ endCursor: '',
+ hasNextPage: false,
+ },
};
mutations.RECEIVE_ISSUES_FOR_LIST_SUCCESS(state, {
listIssues: { listData: listIssues, issues },
+ listPageInfo,
listId: 'gid://gitlab/List/1',
});
@@ -172,21 +221,11 @@ describe('Board Store Mutations', () => {
});
});
- describe('REQUEST_ISSUES_FOR_ALL_LISTS', () => {
- it('sets isLoadingIssues to true', () => {
- expect(state.isLoadingIssues).toBe(false);
-
- mutations.REQUEST_ISSUES_FOR_ALL_LISTS(state);
-
- expect(state.isLoadingIssues).toBe(true);
- });
- });
-
describe('RECEIVE_ISSUES_FOR_LIST_FAILURE', () => {
it('sets error message', () => {
state = {
...state,
- boardLists: mockListsWithModel,
+ boardLists: initialBoardListsState,
error: undefined,
};
@@ -200,51 +239,10 @@ describe('Board Store Mutations', () => {
});
});
- describe('RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS', () => {
- it('sets isLoadingIssues to false and updates issuesByListId object', () => {
- const listIssues = {
- 'gid://gitlab/List/1': [mockIssue.id],
- };
- const issues = {
- '1': mockIssue,
- };
-
- state = {
- ...state,
- isLoadingIssues: true,
- issuesByListId: {},
- issues: {},
- };
-
- mutations.RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS(state, { listData: listIssues, issues });
-
- expect(state.isLoadingIssues).toBe(false);
- expect(state.issuesByListId).toEqual(listIssues);
- expect(state.issues).toEqual(issues);
- });
- });
-
describe('REQUEST_ADD_ISSUE', () => {
expectNotImplemented(mutations.REQUEST_ADD_ISSUE);
});
- describe('RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE', () => {
- it('sets isLoadingIssues to false and sets error message', () => {
- state = {
- ...state,
- isLoadingIssues: true,
- error: undefined,
- };
-
- mutations.RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE(state);
-
- expect(state.isLoadingIssues).toBe(false);
- expect(state.error).toEqual(
- 'An error occurred while fetching the board issues. Please reload the page.',
- );
- });
- });
-
describe('UPDATE_ISSUE_BY_ID', () => {
const issueId = '1';
const prop = 'id';
@@ -254,7 +252,6 @@ describe('Board Store Mutations', () => {
beforeEach(() => {
state = {
...state,
- isLoadingIssues: true,
error: undefined,
issues: {
...issue,
@@ -310,7 +307,7 @@ describe('Board Store Mutations', () => {
state = {
...state,
issuesByListId: listIssues,
- boardLists: mockListsWithModel,
+ boardLists: initialBoardListsState,
issues,
};
@@ -358,6 +355,7 @@ describe('Board Store Mutations', () => {
state = {
...state,
issuesByListId: listIssues,
+ boardLists: initialBoardListsState,
};
mutations.MOVE_ISSUE_FAILURE(state, {
@@ -425,6 +423,7 @@ describe('Board Store Mutations', () => {
...state,
issuesByListId: listIssues,
issues,
+ boardLists: initialBoardListsState,
};
mutations.ADD_ISSUE_TO_LIST_FAILURE(state, { list: mockLists[0], issue: mockIssue2 });