summaryrefslogtreecommitdiff
path: root/spec/frontend/boards
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/boards')
-rw-r--r--spec/frontend/boards/board_list_helper.js6
-rw-r--r--spec/frontend/boards/board_list_spec.js6
-rw-r--r--spec/frontend/boards/board_new_issue_spec.js59
-rw-r--r--spec/frontend/boards/boards_store_spec.js2
-rw-r--r--spec/frontend/boards/components/board_card_layout_spec.js95
-rw-r--r--spec/frontend/boards/components/board_card_spec.js (renamed from spec/frontend/boards/board_card_spec.js)52
-rw-r--r--spec/frontend/boards/components/board_column_spec.js5
-rw-r--r--spec/frontend/boards/components/board_content_spec.js64
-rw-r--r--spec/frontend/boards/components/board_form_spec.js2
-rw-r--r--spec/frontend/boards/components/board_list_header_spec.js8
-rw-r--r--spec/frontend/boards/components/board_settings_sidebar_spec.js174
-rw-r--r--spec/frontend/boards/components/boards_selector_spec.js2
-rw-r--r--spec/frontend/boards/components/issuable_title_spec.js33
-rw-r--r--spec/frontend/boards/components/issue_count_spec.js2
-rw-r--r--spec/frontend/boards/components/sidebar/board_editable_item_spec.js107
-rw-r--r--spec/frontend/boards/issue_card_spec.js6
-rw-r--r--spec/frontend/boards/list_spec.js1
-rw-r--r--spec/frontend/boards/mock_data.js176
-rw-r--r--spec/frontend/boards/stores/actions_spec.js369
-rw-r--r--spec/frontend/boards/stores/getters_spec.js112
-rw-r--r--spec/frontend/boards/stores/mutations_spec.js330
21 files changed, 1406 insertions, 205 deletions
diff --git a/spec/frontend/boards/board_list_helper.js b/spec/frontend/boards/board_list_helper.js
index b51a82f2a35..80d7a72151d 100644
--- a/spec/frontend/boards/board_list_helper.js
+++ b/spec/frontend/boards/board_list_helper.js
@@ -52,10 +52,12 @@ export default function createComponent({
list,
issues: list.issues,
loading: false,
- issueLinkBase: '/issues',
- rootPath: '/',
...componentProps,
},
+ provide: {
+ groupId: null,
+ rootPath: '/',
+ },
}).$mount();
Vue.nextTick(() => {
diff --git a/spec/frontend/boards/board_list_spec.js b/spec/frontend/boards/board_list_spec.js
index 3a64b004847..88883ae61d4 100644
--- a/spec/frontend/boards/board_list_spec.js
+++ b/spec/frontend/boards/board_list_spec.js
@@ -45,10 +45,12 @@ const createComponent = ({ done, listIssueProps = {}, componentProps = {}, listP
list,
issues: list.issues,
loading: false,
- issueLinkBase: '/issues',
- rootPath: '/',
...componentProps,
},
+ provide: {
+ groupId: null,
+ rootPath: '/',
+ },
}).$mount();
Vue.nextTick(() => {
diff --git a/spec/frontend/boards/board_new_issue_spec.js b/spec/frontend/boards/board_new_issue_spec.js
index 94afc8a2b45..3eebfeca965 100644
--- a/spec/frontend/boards/board_new_issue_spec.js
+++ b/spec/frontend/boards/board_new_issue_spec.js
@@ -1,6 +1,7 @@
/* global List */
import Vue from 'vue';
+import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import boardNewIssue from '~/boards/components/board_new_issue.vue';
@@ -10,6 +11,7 @@ import '~/boards/models/list';
import { listObj, boardsMockInterceptor } from './mock_data';
describe('Issue boards new issue form', () => {
+ let wrapper;
let vm;
let list;
let mock;
@@ -24,13 +26,11 @@ describe('Issue boards new issue form', () => {
const dummySubmitEvent = {
preventDefault() {},
};
- vm.$refs.submitButton = vm.$el.querySelector('.btn-success');
- return vm.submit(dummySubmitEvent);
+ wrapper.vm.$refs.submitButton = wrapper.find({ ref: 'submitButton' });
+ return wrapper.vm.submit(dummySubmitEvent);
};
beforeEach(() => {
- setFixtures('<div class="test-container"></div>');
-
const BoardNewIssueComp = Vue.extend(boardNewIssue);
mock = new MockAdapter(axios);
@@ -43,46 +43,52 @@ describe('Issue boards new issue form', () => {
newIssueMock = Promise.resolve(promiseReturn);
jest.spyOn(list, 'newIssue').mockImplementation(() => newIssueMock);
- vm = new BoardNewIssueComp({
+ wrapper = mount(BoardNewIssueComp, {
propsData: {
+ disabled: false,
list,
},
- }).$mount(document.querySelector('.test-container'));
+ provide: {
+ groupId: null,
+ },
+ });
+
+ vm = wrapper.vm;
return Vue.nextTick();
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
mock.restore();
});
it('calls submit if submit button is clicked', () => {
- jest.spyOn(vm, 'submit').mockImplementation(e => e.preventDefault());
+ jest.spyOn(wrapper.vm, 'submit').mockImplementation();
vm.title = 'Testing Title';
- return Vue.nextTick().then(() => {
- vm.$el.querySelector('.btn-success').click();
-
- expect(vm.submit.mock.calls.length).toBe(1);
- });
+ return Vue.nextTick()
+ .then(submitIssue)
+ .then(() => {
+ expect(wrapper.vm.submit).toHaveBeenCalled();
+ });
});
it('disables submit button if title is empty', () => {
- expect(vm.$el.querySelector('.btn-success').disabled).toBe(true);
+ expect(wrapper.find({ ref: 'submitButton' }).props().disabled).toBe(true);
});
it('enables submit button if title is not empty', () => {
- vm.title = 'Testing Title';
+ wrapper.setData({ title: 'Testing Title' });
return Vue.nextTick().then(() => {
- expect(vm.$el.querySelector('.form-control').value).toBe('Testing Title');
- expect(vm.$el.querySelector('.btn-success').disabled).not.toBe(true);
+ expect(wrapper.find({ ref: 'input' }).element.value).toBe('Testing Title');
+ expect(wrapper.find({ ref: 'submitButton' }).props().disabled).toBe(false);
});
});
it('clears title after clicking cancel', () => {
- vm.$el.querySelector('.btn-default').click();
+ wrapper.find({ ref: 'cancelButton' }).trigger('click');
return Vue.nextTick().then(() => {
expect(vm.title).toBe('');
@@ -97,7 +103,7 @@ describe('Issue boards new issue form', () => {
describe('submit success', () => {
it('creates new issue', () => {
- vm.title = 'submit title';
+ wrapper.setData({ title: 'submit issue' });
return Vue.nextTick()
.then(submitIssue)
@@ -107,17 +113,18 @@ describe('Issue boards new issue form', () => {
});
it('enables button after submit', () => {
- vm.title = 'submit issue';
+ jest.spyOn(wrapper.vm, 'submit').mockImplementation();
+ wrapper.setData({ title: 'submit issue' });
return Vue.nextTick()
.then(submitIssue)
.then(() => {
- expect(vm.$el.querySelector('.btn-success').disabled).toBe(false);
+ expect(wrapper.vm.$refs.submitButton.props().disabled).toBe(false);
});
});
it('clears title after submit', () => {
- vm.title = 'submit issue';
+ wrapper.setData({ title: 'submit issue' });
return Vue.nextTick()
.then(submitIssue)
@@ -128,7 +135,7 @@ describe('Issue boards new issue form', () => {
it('sets detail issue after submit', () => {
expect(boardsStore.detail.issue.title).toBe(undefined);
- vm.title = 'submit issue';
+ wrapper.setData({ title: 'submit issue' });
return Vue.nextTick()
.then(submitIssue)
@@ -138,7 +145,7 @@ describe('Issue boards new issue form', () => {
});
it('sets detail list after submit', () => {
- vm.title = 'submit issue';
+ wrapper.setData({ title: 'submit issue' });
return Vue.nextTick()
.then(submitIssue)
@@ -149,7 +156,7 @@ describe('Issue boards new issue form', () => {
it('sets detail weight after submit', () => {
boardsStore.weightFeatureAvailable = true;
- vm.title = 'submit issue';
+ wrapper.setData({ title: 'submit issue' });
return Vue.nextTick()
.then(submitIssue)
@@ -160,7 +167,7 @@ describe('Issue boards new issue form', () => {
it('does not set detail weight after submit', () => {
boardsStore.weightFeatureAvailable = false;
- vm.title = 'submit issue';
+ wrapper.setData({ title: 'submit issue' });
return Vue.nextTick()
.then(submitIssue)
diff --git a/spec/frontend/boards/boards_store_spec.js b/spec/frontend/boards/boards_store_spec.js
index 29cc8f981bd..41971137b95 100644
--- a/spec/frontend/boards/boards_store_spec.js
+++ b/spec/frontend/boards/boards_store_spec.js
@@ -312,7 +312,7 @@ describe('boardsStore', () => {
});
describe('newIssue', () => {
- const id = 'not-creative';
+ const id = 1;
const issue = { some: 'issue data' };
const url = `${endpoints.listsEndpoint}/${id}/issues`;
const expectedRequest = expect.objectContaining({
diff --git a/spec/frontend/boards/components/board_card_layout_spec.js b/spec/frontend/boards/components/board_card_layout_spec.js
new file mode 100644
index 00000000000..80f649a1a96
--- /dev/null
+++ b/spec/frontend/boards/components/board_card_layout_spec.js
@@ -0,0 +1,95 @@
+/* global List */
+/* global ListLabel */
+
+import { shallowMount } from '@vue/test-utils';
+
+import MockAdapter from 'axios-mock-adapter';
+import waitForPromises from 'helpers/wait_for_promises';
+import axios from '~/lib/utils/axios_utils';
+
+import '~/boards/models/label';
+import '~/boards/models/assignee';
+import '~/boards/models/list';
+import store from '~/boards/stores';
+import boardsStore from '~/boards/stores/boards_store';
+import BoardCardLayout from '~/boards/components/board_card_layout.vue';
+import issueCardInner from '~/boards/components/issue_card_inner.vue';
+import { listObj, boardsMockInterceptor, setMockEndpoints } from '../mock_data';
+
+describe('Board card layout', () => {
+ let wrapper;
+ let mock;
+ let list;
+
+ // this particular mount component needs to be used after the root beforeEach because it depends on list being initialized
+ const mountComponent = propsData => {
+ wrapper = shallowMount(BoardCardLayout, {
+ stubs: {
+ issueCardInner,
+ },
+ store,
+ propsData: {
+ list,
+ issue: list.issues[0],
+ disabled: false,
+ index: 0,
+ ...propsData,
+ },
+ provide: {
+ groupId: null,
+ rootPath: '/',
+ },
+ });
+ };
+
+ const setupData = () => {
+ list = new List(listObj);
+ boardsStore.create();
+ boardsStore.detail.issue = {};
+ const label1 = new ListLabel({
+ id: 3,
+ title: 'testing 123',
+ color: '#000cff',
+ text_color: 'white',
+ description: 'test',
+ });
+ return waitForPromises().then(() => {
+ list.issues[0].labels.push(label1);
+ });
+ };
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onAny().reply(boardsMockInterceptor);
+ setMockEndpoints();
+ return setupData();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ list = null;
+ mock.restore();
+ });
+
+ describe('mouse events', () => {
+ it('sets showDetail to true on mousedown', async () => {
+ mountComponent();
+
+ wrapper.trigger('mousedown');
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.showDetail).toBe(true);
+ });
+
+ it('sets showDetail to false on mousemove', async () => {
+ mountComponent();
+ wrapper.trigger('mousedown');
+ await wrapper.vm.$nextTick();
+ expect(wrapper.vm.showDetail).toBe(true);
+ wrapper.trigger('mousemove');
+ await wrapper.vm.$nextTick();
+ expect(wrapper.vm.showDetail).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/boards/board_card_spec.js b/spec/frontend/boards/components/board_card_spec.js
index d01b895f996..a3ddcdf01b7 100644
--- a/spec/frontend/boards/board_card_spec.js
+++ b/spec/frontend/boards/components/board_card_spec.js
@@ -2,7 +2,7 @@
/* global ListAssignee */
/* global ListLabel */
-import { shallowMount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
@@ -15,12 +15,12 @@ import '~/boards/models/assignee';
import '~/boards/models/list';
import store from '~/boards/stores';
import boardsStore from '~/boards/stores/boards_store';
-import boardCard from '~/boards/components/board_card.vue';
+import BoardCard from '~/boards/components/board_card.vue';
import issueCardInner from '~/boards/components/issue_card_inner.vue';
import userAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
-import { listObj, boardsMockInterceptor, setMockEndpoints } from './mock_data';
+import { listObj, boardsMockInterceptor, setMockEndpoints } from '../mock_data';
-describe('Board card', () => {
+describe('BoardCard', () => {
let wrapper;
let mock;
let list;
@@ -30,7 +30,7 @@ describe('Board card', () => {
// this particular mount component needs to be used after the root beforeEach because it depends on list being initialized
const mountComponent = propsData => {
- wrapper = shallowMount(boardCard, {
+ wrapper = mount(BoardCard, {
stubs: {
issueCardInner,
},
@@ -38,16 +38,18 @@ describe('Board card', () => {
propsData: {
list,
issue: list.issues[0],
- issueLinkBase: '/',
disabled: false,
index: 0,
- rootPath: '/',
...propsData,
},
+ provide: {
+ groupId: null,
+ rootPath: '/',
+ },
});
};
- const setupData = () => {
+ const setupData = async () => {
list = new List(listObj);
boardsStore.create();
boardsStore.detail.issue = {};
@@ -58,9 +60,9 @@ describe('Board card', () => {
text_color: 'white',
description: 'test',
});
- return waitForPromises().then(() => {
- list.issues[0].labels.push(label1);
- });
+ await waitForPromises();
+
+ list.issues[0].labels.push(label1);
};
beforeEach(() => {
@@ -79,7 +81,7 @@ describe('Board card', () => {
it('when details issue is empty does not show the element', () => {
mountComponent();
- expect(wrapper.classes()).not.toContain('is-active');
+ expect(wrapper.find('[data-testid="board_card"').classes()).not.toContain('is-active');
});
it('when detailIssue is equal to card issue shows the element', () => {
@@ -124,29 +126,6 @@ describe('Board card', () => {
});
describe('mouse events', () => {
- it('sets showDetail to true on mousedown', () => {
- mountComponent();
- wrapper.trigger('mousedown');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.showDetail).toBe(true);
- });
- });
-
- it('sets showDetail to false on mousemove', () => {
- mountComponent();
- wrapper.trigger('mousedown');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- expect(wrapper.vm.showDetail).toBe(true);
- wrapper.trigger('mousemove');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(wrapper.vm.showDetail).toBe(false);
- });
- });
-
it('does not set detail issue if showDetail is false', () => {
mountComponent();
expect(boardsStore.detail.issue).toEqual({});
@@ -219,6 +198,9 @@ describe('Board card', () => {
boardsStore.detail.issue = {};
mountComponent();
+ // sets conditional so that event is emitted.
+ wrapper.trigger('mousedown');
+
wrapper.trigger('mouseup');
expect(sidebarEventHub.$emit).toHaveBeenCalledWith('sidebar.closeAll');
diff --git a/spec/frontend/boards/components/board_column_spec.js b/spec/frontend/boards/components/board_column_spec.js
index c06b7aceaad..2a4dbbb989e 100644
--- a/spec/frontend/boards/components/board_column_spec.js
+++ b/spec/frontend/boards/components/board_column_spec.js
@@ -59,10 +59,11 @@ describe('Board Column Component', () => {
propsData: {
boardId,
disabled: false,
- issueLinkBase: '/',
- rootPath: '/',
list,
},
+ provide: {
+ boardId,
+ },
});
};
diff --git a/spec/frontend/boards/components/board_content_spec.js b/spec/frontend/boards/components/board_content_spec.js
new file mode 100644
index 00000000000..df117d06cdf
--- /dev/null
+++ b/spec/frontend/boards/components/board_content_spec.js
@@ -0,0 +1,64 @@
+import Vuex from 'vuex';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { GlAlert } from '@gitlab/ui';
+import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue';
+import BoardColumn from 'ee_else_ce/boards/components/board_column.vue';
+import getters from 'ee_else_ce/boards/stores/getters';
+import { mockListsWithModel } from '../mock_data';
+import BoardContent from '~/boards/components/board_content.vue';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('BoardContent', () => {
+ let wrapper;
+
+ const defaultState = {
+ isShowingEpicsSwimlanes: false,
+ boardLists: mockListsWithModel,
+ error: undefined,
+ };
+
+ const createStore = (state = defaultState) => {
+ return new Vuex.Store({
+ getters,
+ state,
+ actions: {
+ fetchIssuesForAllLists: () => {},
+ },
+ });
+ };
+
+ const createComponent = state => {
+ const store = createStore({
+ ...defaultState,
+ ...state,
+ });
+ wrapper = shallowMount(BoardContent, {
+ localVue,
+ propsData: {
+ lists: mockListsWithModel,
+ canAdminList: true,
+ disabled: false,
+ },
+ store,
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders a BoardColumn component per list', () => {
+ expect(wrapper.findAll(BoardColumn)).toHaveLength(mockListsWithModel.length);
+ });
+
+ it('does not display EpicsSwimlanes component', () => {
+ expect(wrapper.find(EpicsSwimlanes).exists()).toBe(false);
+ expect(wrapper.find(GlAlert).exists()).toBe(false);
+ });
+});
diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js
index b1d277863e8..65d8070192c 100644
--- a/spec/frontend/boards/components/board_form_spec.js
+++ b/spec/frontend/boards/components/board_form_spec.js
@@ -11,7 +11,7 @@ describe('board_form.vue', () => {
const propsData = {
canAdminBoard: false,
labelsPath: `${TEST_HOST}/labels/path`,
- milestonePath: `${TEST_HOST}/milestone/path`,
+ labelsWebUrl: `${TEST_HOST}/-/labels`,
};
const findModal = () => wrapper.find(DeprecatedModal);
diff --git a/spec/frontend/boards/components/board_list_header_spec.js b/spec/frontend/boards/components/board_list_header_spec.js
index 76a3d5e71c8..2439c347bf0 100644
--- a/spec/frontend/boards/components/board_list_header_spec.js
+++ b/spec/frontend/boards/components/board_list_header_spec.js
@@ -57,12 +57,12 @@ describe('Board List Header Component', () => {
wrapper = shallowMount(BoardListHeader, {
propsData: {
- boardId,
disabled: false,
- issueLinkBase: '/',
- rootPath: '/',
list,
},
+ provide: {
+ boardId,
+ },
});
};
@@ -106,7 +106,7 @@ describe('Board List Header Component', () => {
createComponent();
expect(isCollapsed()).toBe(false);
- wrapper.find('[data-testid="board-list-header"]').vm.$emit('click');
+ wrapper.find('[data-testid="board-list-header"]').trigger('click');
return wrapper.vm.$nextTick().then(() => {
expect(isCollapsed()).toBe(false);
diff --git a/spec/frontend/boards/components/board_settings_sidebar_spec.js b/spec/frontend/boards/components/board_settings_sidebar_spec.js
index f39adc0fc49..12c9431f2d4 100644
--- a/spec/frontend/boards/components/board_settings_sidebar_spec.js
+++ b/spec/frontend/boards/components/board_settings_sidebar_spec.js
@@ -6,8 +6,9 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlDrawer, GlLabel } from '@gitlab/ui';
import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue';
import boardsStore from '~/boards/stores/boards_store';
+import { createStore } from '~/boards/stores';
import sidebarEventHub from '~/sidebar/event_hub';
-import { inactiveId } from '~/boards/constants';
+import { inactiveId, LIST } from '~/boards/constants';
const localVue = createLocalVue();
@@ -16,19 +17,12 @@ localVue.use(Vuex);
describe('BoardSettingsSidebar', () => {
let wrapper;
let mock;
- let storeActions;
+ let store;
const labelTitle = 'test';
const labelColor = '#FFFF';
const listId = 1;
- const createComponent = (state = { activeId: inactiveId }, actions = {}) => {
- storeActions = actions;
-
- const store = new Vuex.Store({
- state,
- actions: storeActions,
- });
-
+ const createComponent = () => {
wrapper = shallowMount(BoardSettingsSidebar, {
store,
localVue,
@@ -38,6 +32,9 @@ describe('BoardSettingsSidebar', () => {
const findDrawer = () => wrapper.find(GlDrawer);
beforeEach(() => {
+ store = createStore();
+ store.state.activeId = inactiveId;
+ store.state.sidebarType = LIST;
boardsStore.create();
});
@@ -46,114 +43,125 @@ describe('BoardSettingsSidebar', () => {
wrapper.destroy();
});
- it('finds a GlDrawer component', () => {
- createComponent();
+ describe('when sidebarType is "list"', () => {
+ it('finds a GlDrawer component', () => {
+ createComponent();
- expect(findDrawer().exists()).toBe(true);
- });
+ expect(findDrawer().exists()).toBe(true);
+ });
- describe('on close', () => {
- it('calls closeSidebar', async () => {
- const spy = jest.fn();
- createComponent({ activeId: inactiveId }, { setActiveId: spy });
+ describe('on close', () => {
+ it('closes the sidebar', async () => {
+ createComponent();
- findDrawer().vm.$emit('close');
+ findDrawer().vm.$emit('close');
- await wrapper.vm.$nextTick();
+ await wrapper.vm.$nextTick();
- expect(storeActions.setActiveId).toHaveBeenCalledWith(
- expect.anything(),
- inactiveId,
- undefined,
- );
- });
+ expect(wrapper.find(GlDrawer).exists()).toBe(false);
+ });
- it('calls closeSidebar on sidebar.closeAll event', async () => {
- createComponent({ activeId: inactiveId }, { setActiveId: jest.fn() });
+ it('closes the sidebar when emitting the correct event', async () => {
+ createComponent();
- sidebarEventHub.$emit('sidebar.closeAll');
+ sidebarEventHub.$emit('sidebar.closeAll');
- await wrapper.vm.$nextTick();
+ await wrapper.vm.$nextTick();
- expect(storeActions.setActiveId).toHaveBeenCalledWith(
- expect.anything(),
- inactiveId,
- undefined,
- );
+ expect(wrapper.find(GlDrawer).exists()).toBe(false);
+ });
});
- });
- describe('when activeId is zero', () => {
- it('renders GlDrawer with open false', () => {
- createComponent();
+ describe('when activeId is zero', () => {
+ it('renders GlDrawer with open false', () => {
+ createComponent();
- expect(findDrawer().props('open')).toBe(false);
+ expect(findDrawer().props('open')).toBe(false);
+ });
});
- });
- describe('when activeId is greater than zero', () => {
- beforeEach(() => {
- mock = new MockAdapter(axios);
+ describe('when activeId is greater than zero', () => {
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+
+ boardsStore.addList({
+ id: listId,
+ label: { title: labelTitle, color: labelColor },
+ list_type: 'label',
+ });
+ store.state.activeId = 1;
+ store.state.sidebarType = LIST;
+ });
- boardsStore.addList({
- id: listId,
- label: { title: labelTitle, color: labelColor },
- list_type: 'label',
+ afterEach(() => {
+ boardsStore.removeList(listId);
});
- });
- afterEach(() => {
- boardsStore.removeList(listId);
+ it('renders GlDrawer with open false', () => {
+ createComponent();
+
+ expect(findDrawer().props('open')).toBe(true);
+ });
});
- it('renders GlDrawer with open false', () => {
- createComponent({ activeId: 1 });
+ describe('when activeId is in boardsStore', () => {
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
- expect(findDrawer().props('open')).toBe(true);
- });
- });
+ boardsStore.addList({
+ id: listId,
+ label: { title: labelTitle, color: labelColor },
+ list_type: 'label',
+ });
- describe('when activeId is in boardsStore', () => {
- beforeEach(() => {
- mock = new MockAdapter(axios);
+ store.state.activeId = listId;
+ store.state.sidebarType = LIST;
- boardsStore.addList({
- id: listId,
- label: { title: labelTitle, color: labelColor },
- list_type: 'label',
+ createComponent();
});
- createComponent({ activeId: listId });
- });
+ afterEach(() => {
+ mock.restore();
+ });
- afterEach(() => {
- mock.restore();
- });
+ it('renders label title', () => {
+ expect(findLabel().props('title')).toBe(labelTitle);
+ });
- it('renders label title', () => {
- expect(findLabel().props('title')).toBe(labelTitle);
+ it('renders label background color', () => {
+ expect(findLabel().props('backgroundColor')).toBe(labelColor);
+ });
});
- it('renders label background color', () => {
- expect(findLabel().props('backgroundColor')).toBe(labelColor);
- });
- });
+ describe('when activeId is not in boardsStore', () => {
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
- describe('when activeId is not in boardsStore', () => {
- beforeEach(() => {
- mock = new MockAdapter(axios);
+ boardsStore.addList({ id: listId, label: { title: labelTitle, color: labelColor } });
+
+ store.state.activeId = inactiveId;
- boardsStore.addList({ id: listId, label: { title: labelTitle, color: labelColor } });
+ createComponent();
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
- createComponent({ activeId: inactiveId });
+ it('does not render GlLabel', () => {
+ expect(findLabel().exists()).toBe(false);
+ });
});
+ });
- afterEach(() => {
- mock.restore();
+ describe('when sidebarType is not List', () => {
+ beforeEach(() => {
+ store.state.sidebarType = '';
+ createComponent();
});
- it('does not render GlLabel', () => {
- expect(findLabel().exists()).toBe(false);
+ it('does not render GlDrawer', () => {
+ expect(findDrawer().exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/boards/components/boards_selector_spec.js b/spec/frontend/boards/components/boards_selector_spec.js
index f2d4de238d1..2b7605a3f7c 100644
--- a/spec/frontend/boards/components/boards_selector_spec.js
+++ b/spec/frontend/boards/components/boards_selector_spec.js
@@ -81,12 +81,12 @@ describe('BoardsSelector', () => {
assignee_id: null,
labels: [],
},
- milestonePath: `${TEST_HOST}/milestone/path`,
boardBaseUrl: `${TEST_HOST}/board/base/url`,
hasMissingBoards: false,
canAdminBoard: true,
multipleIssueBoardsAvailable: true,
labelsPath: `${TEST_HOST}/labels/path`,
+ labelsWebUrl: `${TEST_HOST}/labels`,
projectId: 42,
groupId: 19,
scopedIssueBoardFeatureEnabled: true,
diff --git a/spec/frontend/boards/components/issuable_title_spec.js b/spec/frontend/boards/components/issuable_title_spec.js
new file mode 100644
index 00000000000..4b7f491b998
--- /dev/null
+++ b/spec/frontend/boards/components/issuable_title_spec.js
@@ -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');
+ });
+});
diff --git a/spec/frontend/boards/components/issue_count_spec.js b/spec/frontend/boards/components/issue_count_spec.js
index 819d878f4e2..d1ff0bdbf88 100644
--- a/spec/frontend/boards/components/issue_count_spec.js
+++ b/spec/frontend/boards/components/issue_count_spec.js
@@ -29,7 +29,7 @@ describe('IssueCount', () => {
});
it('does not contains maxIssueCount in the template', () => {
- expect(vm.contains('.js-max-issue-size')).toBe(false);
+ expect(vm.find('.js-max-issue-size').exists()).toBe(false);
});
});
diff --git a/spec/frontend/boards/components/sidebar/board_editable_item_spec.js b/spec/frontend/boards/components/sidebar/board_editable_item_spec.js
new file mode 100644
index 00000000000..1dbcbd06407
--- /dev/null
+++ b/spec/frontend/boards/components/sidebar/board_editable_item_spec.js
@@ -0,0 +1,107 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
+import BoardSidebarItem from '~/boards/components/sidebar/board_editable_item.vue';
+
+describe('boards sidebar remove issue', () => {
+ let wrapper;
+
+ const findLoader = () => wrapper.find(GlLoadingIcon);
+ const findEditButton = () => wrapper.find('[data-testid="edit-button"]');
+ const findTitle = () => wrapper.find('[data-testid="title"]');
+ const findCollapsed = () => wrapper.find('[data-testid="collapsed-content"]');
+ const findExpanded = () => wrapper.find('[data-testid="expanded-content"]');
+
+ const createComponent = ({ props = {}, slots = {}, canUpdate = false } = {}) => {
+ wrapper = shallowMount(BoardSidebarItem, {
+ attachTo: document.body,
+ provide: { canUpdate },
+ propsData: props,
+ slots,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('template', () => {
+ it('renders title', () => {
+ const title = 'Sidebar item title';
+ createComponent({ props: { title } });
+
+ expect(findTitle().text()).toBe(title);
+ });
+
+ it('hides edit button, loader and expanded content by default', () => {
+ createComponent();
+
+ expect(findEditButton().exists()).toBe(false);
+ expect(findLoader().exists()).toBe(false);
+ expect(findExpanded().isVisible()).toBe(false);
+ });
+
+ it('shows "None" if empty collapsed slot', () => {
+ createComponent({});
+
+ expect(findCollapsed().text()).toBe('None');
+ });
+
+ it('renders collapsed content by default', () => {
+ const slots = { collapsed: '<div>Collapsed content</div>' };
+ createComponent({ slots });
+
+ expect(findCollapsed().text()).toBe('Collapsed content');
+ });
+
+ it('shows edit button if can update', () => {
+ createComponent({ canUpdate: true });
+
+ expect(findEditButton().exists()).toBe(true);
+ });
+
+ it('shows loading icon if loading', () => {
+ createComponent({ props: { loading: true } });
+
+ expect(findLoader().exists()).toBe(true);
+ });
+
+ it('shows expanded content and hides collapsed content when clicking edit button', async () => {
+ const slots = { default: '<div>Select item</div>' };
+ createComponent({ canUpdate: true, slots });
+ findEditButton().vm.$emit('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findCollapsed().isVisible()).toBe(false);
+ expect(findExpanded().isVisible()).toBe(true);
+ expect(findExpanded().text()).toBe('Select item');
+ });
+ });
+ });
+
+ describe('collapsing an item by offclicking', () => {
+ beforeEach(async () => {
+ createComponent({ canUpdate: true });
+ findEditButton().vm.$emit('click');
+ await wrapper.vm.$nextTick();
+ });
+
+ it('hides expanded section and displays collapsed section', async () => {
+ expect(findExpanded().isVisible()).toBe(true);
+ document.body.click();
+
+ await wrapper.vm.$nextTick();
+
+ expect(findCollapsed().isVisible()).toBe(true);
+ expect(findExpanded().isVisible()).toBe(false);
+ });
+
+ it('emits changed event', async () => {
+ document.body.click();
+
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.emitted().changed[1][0]).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/boards/issue_card_spec.js b/spec/frontend/boards/issue_card_spec.js
index dee8cb7b6e5..7e22e9647f0 100644
--- a/spec/frontend/boards/issue_card_spec.js
+++ b/spec/frontend/boards/issue_card_spec.js
@@ -47,13 +47,15 @@ describe('Issue card component', () => {
propsData: {
list,
issue,
- issueLinkBase: '/test',
- rootPath: '/',
},
store,
stubs: {
GlLabel: true,
},
+ provide: {
+ groupId: null,
+ rootPath: '/',
+ },
});
});
diff --git a/spec/frontend/boards/list_spec.js b/spec/frontend/boards/list_spec.js
index b731bb6e474..9c3a6e66ef4 100644
--- a/spec/frontend/boards/list_spec.js
+++ b/spec/frontend/boards/list_spec.js
@@ -184,6 +184,7 @@ describe('List model', () => {
}),
);
list.issues = [];
+ global.gon.features = { boardsWithSwimlanes: false };
});
it('adds new issue to top of list', done => {
diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js
index 8ef6efe23c7..5776332c499 100644
--- a/spec/frontend/boards/mock_data.js
+++ b/spec/frontend/boards/mock_data.js
@@ -1,3 +1,9 @@
+/* global ListIssue */
+/* global List */
+
+import Vue from 'vue';
+import '~/boards/models/list';
+import '~/boards/models/issue';
import boardsStore from '~/boards/stores/boards_store';
export const boardObj = {
@@ -92,11 +98,64 @@ 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: 'Issue 1',
+ id: 'gid://gitlab/Issue/436',
+ iid: 27,
+ dueDate: null,
+ timeEstimate: 0,
+ weight: null,
+ confidential: false,
+ referencePath: 'gitlab-org/test-subgroup/gitlab-test#27',
+ path: '/gitlab-org/test-subgroup/gitlab-test/-/issues/27',
+ labels: {
+ nodes: [
+ {
+ id: 1,
+ title: 'test',
+ color: 'red',
+ description: 'testing',
+ },
+ ],
+ },
+ assignees: {
+ 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/test-subgroup/gitlab-test#27',
+ path: '/gitlab-org/test-subgroup/gitlab-test/-/issues/27',
+ assignees,
labels: [
{
id: 1,
@@ -105,16 +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 = {
+ id: 'gid://gitlab/Issue/437',
+ iid: 28,
+ title: 'Issue 2',
+ dueDate: null,
+ timeEstimate: 0,
+ weight: null,
+ confidential: false,
+ referencePath: 'gitlab-org/test-subgroup/gitlab-test#28',
+ path: '/gitlab-org/test-subgroup/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': {
@@ -165,3 +272,50 @@ export const setMockEndpoints = (opts = {}) => {
boardId,
});
};
+
+export const mockLists = [
+ {
+ id: 'gid://gitlab/List/1',
+ title: 'Backlog',
+ position: null,
+ listType: 'backlog',
+ collapsed: false,
+ label: null,
+ assignee: null,
+ milestone: null,
+ loading: false,
+ },
+ {
+ id: 'gid://gitlab/List/2',
+ title: 'To Do',
+ position: 0,
+ listType: 'label',
+ collapsed: false,
+ label: {
+ id: 'gid://gitlab/GroupLabel/121',
+ title: 'To Do',
+ color: '#F0AD4E',
+ textColor: '#FFFFFF',
+ description: null,
+ },
+ 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,
+};
diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js
index d539cba76ca..bdbcd435708 100644
--- a/spec/frontend/boards/stores/actions_spec.js
+++ b/spec/frontend/boards/stores/actions_spec.js
@@ -1,7 +1,17 @@
import testAction from 'helpers/vuex_action_helper';
-import actions from '~/boards/stores/actions';
+import {
+ mockListsWithModel,
+ mockLists,
+ mockIssue,
+ mockIssueWithModel,
+ mockIssue2WithModel,
+ rawIssue,
+} from '../mock_data';
+import actions, { gqlClient } from '~/boards/stores/actions';
import * as types from '~/boards/stores/mutation_types';
-import { inactiveId } from '~/boards/constants';
+import { inactiveId, ListType } from '~/boards/constants';
+import issueMoveListMutation from '~/boards/queries/issue_move_list.mutation.graphql';
+import { fullBoardId } from '~/boards/boards_util';
const expectNotImplemented = action => {
it('is not implemented', () => {
@@ -9,6 +19,10 @@ const expectNotImplemented = action => {
});
};
+// We need this helper to make sure projectPath is including
+// subgroups when the movIssue action is called.
+const getProjectPath = path => path.split('#')[0];
+
describe('setInitialBoardData', () => {
it('sets data object', () => {
const mockData = {
@@ -26,6 +40,25 @@ describe('setInitialBoardData', () => {
});
});
+describe('setFilters', () => {
+ it('should commit mutation SET_FILTERS', done => {
+ const state = {
+ filters: {},
+ };
+
+ const filters = { labelName: 'label' };
+
+ testAction(
+ actions.setFilters,
+ filters,
+ state,
+ [{ type: types.SET_FILTERS, payload: filters }],
+ [],
+ done,
+ );
+ });
+});
+
describe('setActiveId', () => {
it('should commit mutation SET_ACTIVE_ID', done => {
const state = {
@@ -34,17 +67,40 @@ describe('setActiveId', () => {
testAction(
actions.setActiveId,
- 1,
+ { id: 1, sidebarType: 'something' },
state,
- [{ type: types.SET_ACTIVE_ID, payload: 1 }],
+ [{ type: types.SET_ACTIVE_ID, payload: { id: 1, sidebarType: 'something' } }],
[],
done,
);
});
});
-describe('fetchLists', () => {
- expectNotImplemented(actions.fetchLists);
+describe('showWelcomeList', () => {
+ it('should dispatch addList action', done => {
+ const state = {
+ endpoints: { fullPath: 'gitlab-org', boardId: '1' },
+ boardType: 'group',
+ disabled: false,
+ boardLists: [{ type: 'backlog' }, { type: 'closed' }],
+ };
+
+ const blankList = {
+ id: 'blank',
+ listType: ListType.blank,
+ title: 'Welcome to your issue board!',
+ position: 0,
+ };
+
+ testAction(
+ actions.showWelcomeList,
+ {},
+ state,
+ [],
+ [{ type: 'addList', payload: blankList }],
+ done,
+ );
+ });
});
describe('generateDefaultLists', () => {
@@ -52,29 +108,316 @@ describe('generateDefaultLists', () => {
});
describe('createList', () => {
- expectNotImplemented(actions.createList);
+ it('should dispatch addList action when creating backlog list', done => {
+ const backlogList = {
+ id: 'gid://gitlab/List/1',
+ listType: 'backlog',
+ title: 'Open',
+ position: 0,
+ };
+
+ jest.spyOn(gqlClient, 'mutate').mockReturnValue(
+ Promise.resolve({
+ data: {
+ boardListCreate: {
+ list: backlogList,
+ errors: [],
+ },
+ },
+ }),
+ );
+
+ const state = {
+ endpoints: { fullPath: 'gitlab-org', boardId: '1' },
+ boardType: 'group',
+ disabled: false,
+ boardLists: [{ type: 'closed' }],
+ };
+
+ testAction(
+ actions.createList,
+ { backlog: true },
+ state,
+ [],
+ [{ type: 'addList', payload: backlogList }],
+ done,
+ );
+ });
+
+ it('should commit CREATE_LIST_FAILURE mutation when API returns an error', done => {
+ jest.spyOn(gqlClient, 'mutate').mockReturnValue(
+ Promise.resolve({
+ data: {
+ boardListCreate: {
+ list: {},
+ errors: [{ foo: 'bar' }],
+ },
+ },
+ }),
+ );
+
+ const state = {
+ endpoints: { fullPath: 'gitlab-org', boardId: '1' },
+ boardType: 'group',
+ disabled: false,
+ boardLists: [{ type: 'closed' }],
+ };
+
+ testAction(
+ actions.createList,
+ { backlog: true },
+ state,
+ [{ type: types.CREATE_LIST_FAILURE }],
+ [],
+ done,
+ );
+ });
+});
+
+describe('moveList', () => {
+ it('should commit MOVE_LIST mutation and dispatch updateList action', done => {
+ const state = {
+ endpoints: { fullPath: 'gitlab-org', boardId: '1' },
+ boardType: 'group',
+ disabled: false,
+ boardLists: mockListsWithModel,
+ };
+
+ testAction(
+ actions.moveList,
+ { listId: 'gid://gitlab/List/1', newIndex: 1, adjustmentValue: 1 },
+ state,
+ [
+ {
+ type: types.MOVE_LIST,
+ payload: { movedList: mockListsWithModel[0], listAtNewIndex: mockListsWithModel[1] },
+ },
+ ],
+ [
+ {
+ type: 'updateList',
+ payload: { listId: 'gid://gitlab/List/1', position: 0, backupList: mockListsWithModel },
+ },
+ ],
+ done,
+ );
+ });
});
describe('updateList', () => {
- expectNotImplemented(actions.updateList);
+ it('should commit UPDATE_LIST_FAILURE mutation when API returns an error', done => {
+ jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
+ data: {
+ updateBoardList: {
+ list: {},
+ errors: [{ foo: 'bar' }],
+ },
+ },
+ });
+
+ const state = {
+ endpoints: { fullPath: 'gitlab-org', boardId: '1' },
+ boardType: 'group',
+ disabled: false,
+ boardLists: [{ type: 'closed' }],
+ };
+
+ testAction(
+ actions.updateList,
+ { listId: 'gid://gitlab/List/1', position: 1 },
+ state,
+ [{ type: types.UPDATE_LIST_FAILURE }],
+ [],
+ done,
+ );
+ });
});
describe('deleteList', () => {
expectNotImplemented(actions.deleteList);
});
-describe('fetchIssuesForList', () => {
- expectNotImplemented(actions.fetchIssuesForList);
-});
-
describe('moveIssue', () => {
- expectNotImplemented(actions.moveIssue);
+ const listIssues = {
+ 'gid://gitlab/List/1': [436, 437],
+ 'gid://gitlab/List/2': [],
+ };
+
+ const issues = {
+ '436': mockIssueWithModel,
+ '437': mockIssue2WithModel,
+ };
+
+ const state = {
+ endpoints: { fullPath: 'gitlab-org', boardId: '1' },
+ boardType: 'group',
+ disabled: false,
+ boardLists: mockListsWithModel,
+ issuesByListId: listIssues,
+ issues,
+ };
+
+ it('should commit MOVE_ISSUE mutation and MOVE_ISSUE_SUCCESS mutation when successful', done => {
+ jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
+ data: {
+ issueMoveList: {
+ issue: rawIssue,
+ errors: [],
+ },
+ },
+ });
+
+ testAction(
+ actions.moveIssue,
+ {
+ issueId: '436',
+ issueIid: mockIssue.iid,
+ issuePath: mockIssue.referencePath,
+ fromListId: 'gid://gitlab/List/1',
+ toListId: 'gid://gitlab/List/2',
+ },
+ state,
+ [
+ {
+ type: types.MOVE_ISSUE,
+ payload: {
+ originalIssue: mockIssueWithModel,
+ fromListId: 'gid://gitlab/List/1',
+ toListId: 'gid://gitlab/List/2',
+ },
+ },
+ {
+ type: types.MOVE_ISSUE_SUCCESS,
+ payload: { issue: rawIssue },
+ },
+ ],
+ [],
+ done,
+ );
+ });
+
+ it('calls mutate with the correct variables', () => {
+ const mutationVariables = {
+ mutation: issueMoveListMutation,
+ variables: {
+ projectPath: getProjectPath(mockIssue.referencePath),
+ boardId: fullBoardId(state.endpoints.boardId),
+ iid: mockIssue.iid,
+ fromListId: 1,
+ toListId: 2,
+ moveBeforeId: undefined,
+ moveAfterId: undefined,
+ },
+ };
+ jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
+ data: {
+ issueMoveList: {
+ issue: rawIssue,
+ errors: [],
+ },
+ },
+ });
+
+ actions.moveIssue(
+ { state, commit: () => {} },
+ {
+ issueId: mockIssue.id,
+ issueIid: mockIssue.iid,
+ issuePath: mockIssue.referencePath,
+ fromListId: 'gid://gitlab/List/1',
+ toListId: 'gid://gitlab/List/2',
+ },
+ );
+
+ expect(gqlClient.mutate).toHaveBeenCalledWith(mutationVariables);
+ });
+
+ it('should commit MOVE_ISSUE mutation and MOVE_ISSUE_FAILURE mutation when unsuccessful', done => {
+ jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
+ data: {
+ issueMoveList: {
+ issue: {},
+ errors: [{ foo: 'bar' }],
+ },
+ },
+ });
+
+ testAction(
+ actions.moveIssue,
+ {
+ issueId: '436',
+ issueIid: mockIssue.iid,
+ issuePath: mockIssue.referencePath,
+ fromListId: 'gid://gitlab/List/1',
+ toListId: 'gid://gitlab/List/2',
+ },
+ state,
+ [
+ {
+ type: types.MOVE_ISSUE,
+ payload: {
+ originalIssue: mockIssueWithModel,
+ fromListId: 'gid://gitlab/List/1',
+ toListId: 'gid://gitlab/List/2',
+ },
+ },
+ {
+ type: types.MOVE_ISSUE_FAILURE,
+ payload: {
+ originalIssue: mockIssueWithModel,
+ fromListId: 'gid://gitlab/List/1',
+ toListId: 'gid://gitlab/List/2',
+ originalIndex: 0,
+ },
+ },
+ ],
+ [],
+ done,
+ );
+ });
});
describe('createNewIssue', () => {
expectNotImplemented(actions.createNewIssue);
});
+describe('addListIssue', () => {
+ it('should commit UPDATE_LIST_FAILURE mutation when API returns an error', done => {
+ const payload = {
+ list: mockLists[0],
+ issue: mockIssue,
+ position: 0,
+ };
+
+ testAction(
+ actions.addListIssue,
+ payload,
+ {},
+ [{ type: types.ADD_ISSUE_TO_LIST, payload }],
+ [],
+ done,
+ );
+ });
+});
+
+describe('addListIssueFailure', () => {
+ it('should commit UPDATE_LIST_FAILURE mutation when API returns an error', done => {
+ const payload = {
+ list: mockLists[0],
+ issue: mockIssue,
+ };
+
+ testAction(
+ actions.addListIssueFailure,
+ payload,
+ {},
+ [{ type: types.ADD_ISSUE_TO_LIST_FAILURE, payload }],
+ [],
+ done,
+ );
+ });
+});
+
describe('fetchBacklog', () => {
expectNotImplemented(actions.fetchBacklog);
});
diff --git a/spec/frontend/boards/stores/getters_spec.js b/spec/frontend/boards/stores/getters_spec.js
index 38b2333e679..288143a0f21 100644
--- a/spec/frontend/boards/stores/getters_spec.js
+++ b/spec/frontend/boards/stores/getters_spec.js
@@ -1,4 +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', () => {
@@ -18,4 +20,114 @@ describe('Boards - Getters', () => {
expect(getters.getLabelToggleState(state)).toBe('off');
});
});
+
+ describe('isSidebarOpen', () => {
+ it('returns true when activeId is not equal to 0', () => {
+ const state = {
+ activeId: 1,
+ };
+
+ expect(getters.isSidebarOpen(state)).toBe(true);
+ });
+
+ it('returns false when activeId is equal to 0', () => {
+ const state = {
+ activeId: inactiveId,
+ };
+
+ expect(getters.isSidebarOpen(state)).toBe(false);
+ });
+ });
+
+ describe('isSwimlanesOn', () => {
+ afterEach(() => {
+ window.gon = { features: {} };
+ });
+
+ describe('when boardsWithSwimlanes is true', () => {
+ beforeEach(() => {
+ window.gon = { features: { boardsWithSwimlanes: true } };
+ });
+
+ describe('when isShowingEpicsSwimlanes is true', () => {
+ it('returns true', () => {
+ const state = {
+ isShowingEpicsSwimlanes: true,
+ };
+
+ expect(getters.isSwimlanesOn(state)).toBe(true);
+ });
+ });
+
+ describe('when isShowingEpicsSwimlanes is false', () => {
+ it('returns false', () => {
+ const state = {
+ isShowingEpicsSwimlanes: false,
+ };
+
+ expect(getters.isSwimlanesOn(state)).toBe(false);
+ });
+ });
+ });
+
+ describe('when boardsWithSwimlanes is false', () => {
+ describe('when isShowingEpicsSwimlanes is true', () => {
+ it('returns false', () => {
+ const state = {
+ isShowingEpicsSwimlanes: true,
+ };
+
+ expect(getters.isSwimlanesOn(state)).toBe(false);
+ });
+ });
+
+ describe('when isShowingEpicsSwimlanes is false', () => {
+ it('returns false', () => {
+ const state = {
+ isShowingEpicsSwimlanes: false,
+ };
+
+ expect(getters.isSwimlanesOn(state)).toBe(false);
+ });
+ });
+ });
+ });
+
+ describe('getIssueById', () => {
+ const state = { issues: { '1': 'issue' } };
+
+ it.each`
+ id | expected
+ ${'1'} | ${'issue'}
+ ${''} | ${{}}
+ `('returns $expected when $id is passed to state', ({ id, expected }) => {
+ expect(getters.getIssueById(state)(id)).toEqual(expected);
+ });
+ });
+
+ describe('getActiveIssue', () => {
+ it.each`
+ id | expected
+ ${'1'} | ${'issue'}
+ ${''} | ${{}}
+ `('returns $expected when $id is passed to state', ({ id, expected }) => {
+ const state = { issues: { '1': 'issue' }, activeId: id };
+
+ 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,
+ );
+ });
+ });
});
diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js
index c1f7f3dda6e..a13a99a507e 100644
--- a/spec/frontend/boards/stores/mutations_spec.js
+++ b/spec/frontend/boards/stores/mutations_spec.js
@@ -1,6 +1,17 @@
import mutations from '~/boards/stores/mutations';
+import * as types from '~/boards/stores/mutation_types';
import defaultState from '~/boards/stores/state';
-import { mockIssue } from '../mock_data';
+import {
+ listObj,
+ listObjDuplicate,
+ mockListsWithModel,
+ mockLists,
+ rawIssue,
+ mockIssue,
+ mockIssue2,
+ mockIssueWithModel,
+ mockIssue2WithModel,
+} from '../mock_data';
const expectNotImplemented = action => {
it('is not implemented', () => {
@@ -26,21 +37,56 @@ describe('Board Store Mutations', () => {
fullPath: 'gitlab-org',
};
const boardType = 'group';
+ const disabled = false;
+ const showPromotion = false;
- mutations.SET_INITIAL_BOARD_DATA(state, { ...endpoints, boardType });
+ mutations[types.SET_INITIAL_BOARD_DATA](state, {
+ ...endpoints,
+ boardType,
+ disabled,
+ showPromotion,
+ });
expect(state.endpoints).toEqual(endpoints);
expect(state.boardType).toEqual(boardType);
+ expect(state.disabled).toEqual(disabled);
+ expect(state.showPromotion).toEqual(showPromotion);
+ });
+ });
+
+ describe('RECEIVE_BOARD_LISTS_SUCCESS', () => {
+ it('Should set boardLists to state', () => {
+ const lists = [listObj, listObjDuplicate];
+
+ mutations[types.RECEIVE_BOARD_LISTS_SUCCESS](state, lists);
+
+ expect(state.boardLists).toEqual(lists);
});
});
describe('SET_ACTIVE_ID', () => {
- it('updates activeListId to be the value that is passed', () => {
- const expectedId = 1;
+ const expected = { id: 1, sidebarType: '' };
- mutations.SET_ACTIVE_ID(state, expectedId);
+ beforeEach(() => {
+ mutations.SET_ACTIVE_ID(state, expected);
+ });
+
+ it('updates aciveListId to be the value that is passed', () => {
+ expect(state.activeId).toBe(expected.id);
+ });
- expect(state.activeId).toBe(expectedId);
+ it('updates sidebarType to be the value that is passed', () => {
+ expect(state.sidebarType).toBe(expected.sidebarType);
+ });
+ });
+
+ describe('SET_FILTERS', () => {
+ it('updates filterParams to be the value that is passed', () => {
+ const filterParams = { labelName: 'label' };
+
+ mutations.SET_FILTERS(state, filterParams);
+
+ expect(state.filterParams).toBe(filterParams);
});
});
@@ -56,16 +102,35 @@ describe('Board Store Mutations', () => {
expectNotImplemented(mutations.RECEIVE_ADD_LIST_ERROR);
});
- describe('REQUEST_UPDATE_LIST', () => {
- expectNotImplemented(mutations.REQUEST_UPDATE_LIST);
- });
+ describe('MOVE_LIST', () => {
+ it('updates boardLists state with reordered lists', () => {
+ state = {
+ ...state,
+ boardLists: mockListsWithModel,
+ };
- describe('RECEIVE_UPDATE_LIST_SUCCESS', () => {
- expectNotImplemented(mutations.RECEIVE_UPDATE_LIST_SUCCESS);
+ mutations.MOVE_LIST(state, {
+ movedList: mockListsWithModel[0],
+ listAtNewIndex: mockListsWithModel[1],
+ });
+
+ expect(state.boardLists).toEqual([mockListsWithModel[1], mockListsWithModel[0]]);
+ });
});
- describe('RECEIVE_UPDATE_LIST_ERROR', () => {
- expectNotImplemented(mutations.RECEIVE_UPDATE_LIST_ERROR);
+ describe('UPDATE_LIST_FAILURE', () => {
+ it('updates boardLists state with previous order and sets error message', () => {
+ state = {
+ ...state,
+ boardLists: [mockListsWithModel[1], mockListsWithModel[0]],
+ error: undefined,
+ };
+
+ mutations.UPDATE_LIST_FAILURE(state, mockListsWithModel);
+
+ expect(state.boardLists).toEqual(mockListsWithModel);
+ expect(state.error).toEqual('An error occurred while updating the list. Please try again.');
+ });
});
describe('REQUEST_REMOVE_LIST', () => {
@@ -80,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);
@@ -90,22 +182,45 @@ 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 = {
- '1': [mockIssue],
+ 'gid://gitlab/List/1': [mockIssue.id],
+ };
+ const issues = {
+ '1': mockIssue,
};
state = {
...state,
isLoadingIssues: true,
issuesByListId: {},
+ issues: {},
};
- mutations.RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS(state, listIssues);
+ 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);
});
});
@@ -113,6 +228,65 @@ describe('Board Store Mutations', () => {
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';
+ const value = '2';
+ const issue = { [issueId]: { id: 1, title: 'Issue' } };
+
+ beforeEach(() => {
+ state = {
+ ...state,
+ isLoadingIssues: true,
+ error: undefined,
+ issues: {
+ ...issue,
+ },
+ };
+ });
+
+ describe('when the issue is in state', () => {
+ it('updates the property of the correct issue', () => {
+ mutations.UPDATE_ISSUE_BY_ID(state, {
+ issueId,
+ prop,
+ value,
+ });
+
+ expect(state.issues[issueId]).toEqual({ ...issue[issueId], id: '2' });
+ });
+ });
+
+ describe('when the issue is not in state', () => {
+ it('throws an error', () => {
+ expect(() => {
+ mutations.UPDATE_ISSUE_BY_ID(state, {
+ issueId: '3',
+ prop,
+ value,
+ });
+ }).toThrow(new Error('No issue found.'));
+ });
+ });
+ });
+
describe('RECEIVE_ADD_ISSUE_SUCCESS', () => {
expectNotImplemented(mutations.RECEIVE_ADD_ISSUE_SUCCESS);
});
@@ -121,16 +295,86 @@ describe('Board Store Mutations', () => {
expectNotImplemented(mutations.RECEIVE_ADD_ISSUE_ERROR);
});
- describe('REQUEST_MOVE_ISSUE', () => {
- expectNotImplemented(mutations.REQUEST_MOVE_ISSUE);
+ describe('MOVE_ISSUE', () => {
+ it('updates issuesByListId, moving issue between lists', () => {
+ const listIssues = {
+ 'gid://gitlab/List/1': [mockIssue.id, mockIssue2.id],
+ 'gid://gitlab/List/2': [],
+ };
+
+ const issues = {
+ '1': mockIssueWithModel,
+ '2': mockIssue2WithModel,
+ };
+
+ state = {
+ ...state,
+ issuesByListId: listIssues,
+ boardLists: mockListsWithModel,
+ issues,
+ };
+
+ mutations.MOVE_ISSUE(state, {
+ originalIssue: mockIssue2WithModel,
+ fromListId: 'gid://gitlab/List/1',
+ toListId: 'gid://gitlab/List/2',
+ });
+
+ const updatedListIssues = {
+ 'gid://gitlab/List/1': [mockIssue.id],
+ 'gid://gitlab/List/2': [mockIssue2.id],
+ };
+
+ expect(state.issuesByListId).toEqual(updatedListIssues);
+ });
});
- describe('RECEIVE_MOVE_ISSUE_SUCCESS', () => {
- expectNotImplemented(mutations.RECEIVE_MOVE_ISSUE_SUCCESS);
+ describe('MOVE_ISSUE_SUCCESS', () => {
+ it('updates issue in issues state', () => {
+ const issues = {
+ '436': { id: rawIssue.id },
+ };
+
+ state = {
+ ...state,
+ issues,
+ };
+
+ mutations.MOVE_ISSUE_SUCCESS(state, {
+ issue: rawIssue,
+ });
+
+ expect(state.issues).toEqual({ '436': { ...mockIssueWithModel, id: 436 } });
+ });
});
- describe('RECEIVE_MOVE_ISSUE_ERROR', () => {
- expectNotImplemented(mutations.RECEIVE_MOVE_ISSUE_ERROR);
+ describe('MOVE_ISSUE_FAILURE', () => {
+ it('updates issuesByListId, reverting moving issue between lists, and sets error message', () => {
+ const listIssues = {
+ 'gid://gitlab/List/1': [mockIssue.id],
+ 'gid://gitlab/List/2': [mockIssue2.id],
+ };
+
+ state = {
+ ...state,
+ issuesByListId: listIssues,
+ };
+
+ mutations.MOVE_ISSUE_FAILURE(state, {
+ originalIssue: mockIssue2,
+ fromListId: 'gid://gitlab/List/1',
+ toListId: 'gid://gitlab/List/2',
+ originalIndex: 1,
+ });
+
+ const updatedListIssues = {
+ 'gid://gitlab/List/1': [mockIssue.id, mockIssue2.id],
+ 'gid://gitlab/List/2': [],
+ };
+
+ expect(state.issuesByListId).toEqual(updatedListIssues);
+ expect(state.error).toEqual('An error occurred while moving the issue. Please try again.');
+ });
});
describe('REQUEST_UPDATE_ISSUE', () => {
@@ -145,6 +389,50 @@ describe('Board Store Mutations', () => {
expectNotImplemented(mutations.RECEIVE_UPDATE_ISSUE_ERROR);
});
+ describe('ADD_ISSUE_TO_LIST', () => {
+ it('adds issue to issues state and issue id in list in issuesByListId', () => {
+ const listIssues = {
+ 'gid://gitlab/List/1': [mockIssue.id],
+ };
+ const issues = {
+ '1': mockIssue,
+ };
+
+ state = {
+ ...state,
+ issuesByListId: listIssues,
+ issues,
+ };
+
+ 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);
+ });
+ });
+
+ describe('ADD_ISSUE_TO_LIST_FAILURE', () => {
+ it('removes issue id from list in issuesByListId', () => {
+ const listIssues = {
+ 'gid://gitlab/List/1': [mockIssue.id, mockIssue2.id],
+ };
+ const issues = {
+ '1': mockIssue,
+ '2': mockIssue2,
+ };
+
+ state = {
+ ...state,
+ issuesByListId: listIssues,
+ issues,
+ };
+
+ mutations.ADD_ISSUE_TO_LIST_FAILURE(state, { list: mockLists[0], issue: mockIssue2 });
+
+ expect(state.issuesByListId['gid://gitlab/List/1']).not.toContain(mockIssue2.id);
+ });
+ });
+
describe('SET_CURRENT_PAGE', () => {
expectNotImplemented(mutations.SET_CURRENT_PAGE);
});