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_deprecated_spec.js11
-rw-r--r--spec/frontend/boards/board_list_helper.js8
-rw-r--r--spec/frontend/boards/board_list_spec.js8
-rw-r--r--spec/frontend/boards/board_new_issue_deprecated_spec.js4
-rw-r--r--spec/frontend/boards/boards_store_spec.js6
-rw-r--r--spec/frontend/boards/boards_util_spec.js17
-rw-r--r--spec/frontend/boards/components/board_assignee_dropdown_spec.js380
-rw-r--r--spec/frontend/boards/components/board_card_layout_deprecated_spec.js158
-rw-r--r--spec/frontend/boards/components/board_card_layout_spec.js67
-rw-r--r--spec/frontend/boards/components/board_card_spec.js10
-rw-r--r--spec/frontend/boards/components/board_column_deprecated_spec.js16
-rw-r--r--spec/frontend/boards/components/board_column_spec.js13
-rw-r--r--spec/frontend/boards/components/board_configuration_options_spec.js15
-rw-r--r--spec/frontend/boards/components/board_content_spec.js6
-rw-r--r--spec/frontend/boards/components/board_form_spec.js55
-rw-r--r--spec/frontend/boards/components/board_list_header_deprecated_spec.js12
-rw-r--r--spec/frontend/boards/components/board_list_header_spec.js12
-rw-r--r--spec/frontend/boards/components/board_new_issue_spec.js2
-rw-r--r--spec/frontend/boards/components/board_settings_sidebar_spec.js10
-rw-r--r--spec/frontend/boards/components/boards_selector_deprecated_spec.js214
-rw-r--r--spec/frontend/boards/components/boards_selector_spec.js30
-rw-r--r--spec/frontend/boards/components/issue_time_estimate_spec.js2
-rw-r--r--spec/frontend/boards/components/sidebar/board_editable_item_spec.js2
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_due_date_spec.js4
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_issue_title_spec.js6
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js6
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js50
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js8
-rw-r--r--spec/frontend/boards/components/sidebar/remove_issue_spec.js2
-rw-r--r--spec/frontend/boards/issue_card_deprecated_spec.js4
-rw-r--r--spec/frontend/boards/issue_card_inner_spec.js6
-rw-r--r--spec/frontend/boards/issue_spec.js2
-rw-r--r--spec/frontend/boards/list_spec.js3
-rw-r--r--spec/frontend/boards/mock_data.js6
-rw-r--r--spec/frontend/boards/project_select_deprecated_spec.js9
-rw-r--r--spec/frontend/boards/project_select_spec.js7
-rw-r--r--spec/frontend/boards/stores/actions_spec.js219
-rw-r--r--spec/frontend/boards/stores/getters_spec.js20
-rw-r--r--spec/frontend/boards/stores/mutations_spec.js44
39 files changed, 786 insertions, 668 deletions
diff --git a/spec/frontend/boards/board_list_deprecated_spec.js b/spec/frontend/boards/board_list_deprecated_spec.js
index 393d7f954b1..b71564f7858 100644
--- a/spec/frontend/boards/board_list_deprecated_spec.js
+++ b/spec/frontend/boards/board_list_deprecated_spec.js
@@ -1,17 +1,16 @@
/* global List */
/* global ListIssue */
-
-import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
+import Vue from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
-import axios from '~/lib/utils/axios_utils';
-import eventHub from '~/boards/eventhub';
import BoardList from '~/boards/components/board_list_deprecated.vue';
+import eventHub from '~/boards/eventhub';
+import store from '~/boards/stores';
+import boardsStore from '~/boards/stores/boards_store';
+import axios from '~/lib/utils/axios_utils';
import '~/boards/models/issue';
import '~/boards/models/list';
import { listObj, boardsMockInterceptor } from './mock_data';
-import store from '~/boards/stores';
-import boardsStore from '~/boards/stores/boards_store';
const createComponent = ({ done, listIssueProps = {}, componentProps = {}, listProps = {} }) => {
const el = document.createElement('div');
diff --git a/spec/frontend/boards/board_list_helper.js b/spec/frontend/boards/board_list_helper.js
index f82b1f7ed5c..915b470df8d 100644
--- a/spec/frontend/boards/board_list_helper.js
+++ b/spec/frontend/boards/board_list_helper.js
@@ -1,17 +1,15 @@
/* global List */
/* global ListIssue */
-
import MockAdapter from 'axios-mock-adapter';
-import Vue from 'vue';
import Sortable from 'sortablejs';
-import axios from '~/lib/utils/axios_utils';
+import Vue from 'vue';
import BoardList from '~/boards/components/board_list_deprecated.vue';
-
import '~/boards/models/issue';
import '~/boards/models/list';
-import { listObj, boardsMockInterceptor } from './mock_data';
import store from '~/boards/stores';
import boardsStore from '~/boards/stores/boards_store';
+import axios from '~/lib/utils/axios_utils';
+import { listObj, boardsMockInterceptor } from './mock_data';
window.Sortable = Sortable;
diff --git a/spec/frontend/boards/board_list_spec.js b/spec/frontend/boards/board_list_spec.js
index 1b62f25044e..7ed20f20882 100644
--- a/spec/frontend/boards/board_list_spec.js
+++ b/spec/frontend/boards/board_list_spec.js
@@ -1,11 +1,11 @@
+import { createLocalVue, mount } from '@vue/test-utils';
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.vue';
import BoardCard from '~/boards/components/board_card.vue';
-import { mockList, mockIssuesByListId, issues, mockIssues } from './mock_data';
+import BoardList from '~/boards/components/board_list.vue';
+import eventHub from '~/boards/eventhub';
import defaultState from '~/boards/stores/state';
+import { mockList, mockIssuesByListId, issues, mockIssues } from './mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/boards/board_new_issue_deprecated_spec.js b/spec/frontend/boards/board_new_issue_deprecated_spec.js
index 8236b468189..1a29f680166 100644
--- a/spec/frontend/boards/board_new_issue_deprecated_spec.js
+++ b/spec/frontend/boards/board_new_issue_deprecated_spec.js
@@ -1,11 +1,11 @@
/* 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 Vue from 'vue';
import boardNewIssue from '~/boards/components/board_new_issue_deprecated.vue';
import boardsStore from '~/boards/stores/boards_store';
+import axios from '~/lib/utils/axios_utils';
import '~/boards/models/list';
import { listObj, boardsMockInterceptor } from './mock_data';
diff --git a/spec/frontend/boards/boards_store_spec.js b/spec/frontend/boards/boards_store_spec.js
index f1d249ff069..02881333273 100644
--- a/spec/frontend/boards/boards_store_spec.js
+++ b/spec/frontend/boards/boards_store_spec.js
@@ -1,12 +1,12 @@
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 eventHub from '~/boards/eventhub';
-import { listObj, listObjDuplicate } from './mock_data';
import ListIssue from '~/boards/models/issue';
import List from '~/boards/models/list';
+import boardsStore from '~/boards/stores/boards_store';
+import axios from '~/lib/utils/axios_utils';
+import { listObj, listObjDuplicate } from './mock_data';
jest.mock('js-cookie');
diff --git a/spec/frontend/boards/boards_util_spec.js b/spec/frontend/boards/boards_util_spec.js
new file mode 100644
index 00000000000..0feb1411003
--- /dev/null
+++ b/spec/frontend/boards/boards_util_spec.js
@@ -0,0 +1,17 @@
+import { transformNotFilters } from '~/boards/boards_util';
+
+describe('transformNotFilters', () => {
+ const filters = {
+ 'not[labelName]': ['label'],
+ 'not[assigneeUsername]': 'assignee',
+ };
+
+ it('formats not filters, transforms epicId to fullEpicId', () => {
+ const result = transformNotFilters(filters);
+
+ expect(result).toEqual({
+ labelName: ['label'],
+ assigneeUsername: 'assignee',
+ });
+ });
+});
diff --git a/spec/frontend/boards/components/board_assignee_dropdown_spec.js b/spec/frontend/boards/components/board_assignee_dropdown_spec.js
deleted file mode 100644
index e52c14f9783..00000000000
--- a/spec/frontend/boards/components/board_assignee_dropdown_spec.js
+++ /dev/null
@@ -1,380 +0,0 @@
-import { mount, createLocalVue } from '@vue/test-utils';
-import {
- GlDropdownItem,
- GlAvatarLink,
- GlAvatarLabeled,
- GlSearchBoxByType,
- GlLoadingIcon,
-} from '@gitlab/ui';
-import createMockApollo from 'helpers/mock_apollo_helper';
-import VueApollo from 'vue-apollo';
-import BoardAssigneeDropdown from '~/boards/components/board_assignee_dropdown.vue';
-import IssuableAssignees from '~/sidebar/components/assignees/issuable_assignees.vue';
-import MultiSelectDropdown from '~/vue_shared/components/sidebar/multiselect_dropdown.vue';
-import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
-import store from '~/boards/stores';
-import getIssueParticipants from '~/vue_shared/components/sidebar/queries/getIssueParticipants.query.graphql';
-import searchUsers from '~/boards/graphql/users_search.query.graphql';
-import { participants } from '../mock_data';
-
-const localVue = createLocalVue();
-
-localVue.use(VueApollo);
-
-describe('BoardCardAssigneeDropdown', () => {
- let wrapper;
- let fakeApollo;
- let getIssueParticipantsSpy;
- let getSearchUsersSpy;
- let dispatchSpy;
-
- const iid = '111';
- const activeIssueName = 'test';
- const anotherIssueName = 'hello';
-
- const createComponent = (search = '', loading = false) => {
- wrapper = mount(BoardAssigneeDropdown, {
- data() {
- return {
- search,
- selected: [],
- participants,
- };
- },
- store,
- provide: {
- canUpdate: true,
- rootPath: '',
- },
- mocks: {
- $apollo: {
- queries: {
- participants: {
- loading,
- },
- },
- },
- },
- });
- };
-
- const createComponentWithApollo = (search = '') => {
- fakeApollo = createMockApollo([
- [getIssueParticipants, getIssueParticipantsSpy],
- [searchUsers, getSearchUsersSpy],
- ]);
- wrapper = mount(BoardAssigneeDropdown, {
- localVue,
- apolloProvider: fakeApollo,
- data() {
- return {
- search,
- selected: [],
- participants,
- };
- },
- store,
- provide: {
- canUpdate: true,
- rootPath: '',
- },
- });
- };
-
- const unassign = async () => {
- wrapper.find('[data-testid="unassign"]').trigger('click');
-
- await wrapper.vm.$nextTick();
- };
-
- const openDropdown = async () => {
- wrapper.find('[data-testid="edit-button"]').trigger('click');
-
- await wrapper.vm.$nextTick();
- };
-
- const findByText = (text) => {
- return wrapper.findAll(GlDropdownItem).wrappers.find((node) => node.text().indexOf(text) === 0);
- };
-
- const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
-
- beforeEach(() => {
- store.state.activeId = '1';
- store.state.issues = {
- 1: {
- iid,
- assignees: [{ username: activeIssueName, name: activeIssueName, id: activeIssueName }],
- },
- };
-
- dispatchSpy = jest.spyOn(store, 'dispatch').mockResolvedValue();
- });
-
- afterEach(() => {
- window.gon = {};
- jest.restoreAllMocks();
- });
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- describe('when mounted', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it.each`
- text
- ${anotherIssueName}
- ${activeIssueName}
- `('finds item with $text', ({ text }) => {
- const item = findByText(text);
-
- expect(item.exists()).toBe(true);
- });
-
- it('renders gl-avatar-link in gl-dropdown-item', () => {
- const item = findByText('hello');
-
- expect(item.find(GlAvatarLink).exists()).toBe(true);
- });
-
- it('renders gl-avatar-labeled in gl-avatar-link', () => {
- const item = findByText('hello');
-
- expect(item.find(GlAvatarLink).find(GlAvatarLabeled).exists()).toBe(true);
- });
- });
-
- describe('when selected users are present', () => {
- it('renders a divider', () => {
- createComponent();
-
- expect(wrapper.find('[data-testid="selected-user-divider"]').exists()).toBe(true);
- });
- });
-
- describe('when collapsed', () => {
- it('renders IssuableAssignees', () => {
- createComponent();
-
- expect(wrapper.find(IssuableAssignees).isVisible()).toBe(true);
- expect(wrapper.find(MultiSelectDropdown).isVisible()).toBe(false);
- });
- });
-
- describe('when dropdown is open', () => {
- beforeEach(async () => {
- createComponent();
-
- await openDropdown();
- });
-
- it('shows assignees dropdown', async () => {
- expect(wrapper.find(IssuableAssignees).isVisible()).toBe(false);
- expect(wrapper.find(MultiSelectDropdown).isVisible()).toBe(true);
- });
-
- it('shows the issue returned as the activeIssue', async () => {
- expect(findByText(activeIssueName).props('isChecked')).toBe(true);
- });
-
- describe('when "Unassign" is clicked', () => {
- it('unassigns assignees', async () => {
- await unassign();
-
- expect(findByText('Unassign').props('isChecked')).toBe(true);
- });
- });
-
- describe('when an unselected item is clicked', () => {
- beforeEach(async () => {
- await unassign();
- });
-
- it('assigns assignee in the dropdown', async () => {
- wrapper.find('[data-testid="item_test"]').trigger('click');
-
- await wrapper.vm.$nextTick();
-
- expect(findByText(activeIssueName).props('isChecked')).toBe(true);
- });
-
- it('calls setAssignees with username list', async () => {
- wrapper.find('[data-testid="item_test"]').trigger('click');
-
- await wrapper.vm.$nextTick();
-
- document.body.click();
-
- await wrapper.vm.$nextTick();
-
- expect(store.dispatch).toHaveBeenCalledWith('setAssignees', [activeIssueName]);
- });
- });
-
- describe('when the user off clicks', () => {
- beforeEach(async () => {
- await unassign();
-
- document.body.click();
-
- await wrapper.vm.$nextTick();
- });
-
- it('calls setAssignees with username list', async () => {
- expect(store.dispatch).toHaveBeenCalledWith('setAssignees', []);
- });
-
- it('closes the dropdown', async () => {
- expect(wrapper.find(IssuableAssignees).isVisible()).toBe(true);
- });
- });
- });
-
- it('renders divider after unassign', () => {
- createComponent();
-
- expect(wrapper.find('[data-testid="unassign-divider"]').exists()).toBe(true);
- });
-
- it.each`
- assignees | expected
- ${[{ id: 5, username: '', name: '' }]} | ${'Assignee'}
- ${[{ id: 6, username: '', name: '' }, { id: 7, username: '', name: '' }]} | ${'2 Assignees'}
- `(
- 'when assignees have a length of $assignees.length, it renders $expected',
- ({ assignees, expected }) => {
- store.state.issues['1'].assignees = assignees;
-
- createComponent();
-
- expect(wrapper.find(BoardEditableItem).props('title')).toBe(expected);
- },
- );
-
- describe('when participants is loading', () => {
- beforeEach(() => {
- createComponent('', true);
- });
-
- it('finds a loading icon in the dropdown', () => {
- expect(findLoadingIcon().exists()).toBe(true);
- });
- });
-
- describe('when participants is loading is false', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('does not find GlLoading icon in the dropdown', () => {
- expect(findLoadingIcon().exists()).toBe(false);
- });
-
- it('finds at least 1 GlDropdownItem', () => {
- expect(wrapper.findAll(GlDropdownItem).length).toBeGreaterThan(0);
- });
- });
-
- describe('Apollo', () => {
- beforeEach(() => {
- getIssueParticipantsSpy = jest.fn().mockResolvedValue({
- data: {
- issue: {
- participants: {
- nodes: [
- {
- username: 'participant',
- name: 'participant',
- webUrl: '',
- avatarUrl: '',
- id: '',
- },
- ],
- },
- },
- },
- });
- getSearchUsersSpy = jest.fn().mockResolvedValue({
- data: {
- users: {
- nodes: [{ username: 'root', name: 'root', webUrl: '', avatarUrl: '', id: '' }],
- },
- },
- });
- });
-
- describe('when search is empty', () => {
- beforeEach(() => {
- createComponentWithApollo();
- });
-
- it('calls getIssueParticipants', async () => {
- jest.runOnlyPendingTimers();
- await wrapper.vm.$nextTick();
-
- expect(getIssueParticipantsSpy).toHaveBeenCalledWith({ id: 'gid://gitlab/Issue/111' });
- });
- });
-
- describe('when search is not empty', () => {
- beforeEach(() => {
- createComponentWithApollo('search term');
- });
-
- it('calls searchUsers', async () => {
- jest.runOnlyPendingTimers();
- await wrapper.vm.$nextTick();
-
- expect(getSearchUsersSpy).toHaveBeenCalledWith({ search: 'search term' });
- });
- });
- });
-
- it('finds GlSearchBoxByType', async () => {
- createComponent();
-
- await openDropdown();
-
- expect(wrapper.find(GlSearchBoxByType).exists()).toBe(true);
- });
-
- describe('when assign-self is emitted from IssuableAssignees', () => {
- const currentUser = { username: 'self', name: '', id: '' };
-
- beforeEach(() => {
- window.gon = { current_username: currentUser.username };
-
- dispatchSpy.mockResolvedValue([currentUser]);
- createComponent();
-
- wrapper.find(IssuableAssignees).vm.$emit('assign-self');
- });
-
- it('calls setAssignees with currentUser', () => {
- expect(store.dispatch).toHaveBeenCalledWith('setAssignees', currentUser.username);
- });
-
- it('adds the user to the selected list', async () => {
- expect(findByText(currentUser.username).exists()).toBe(true);
- });
- });
-
- describe('when setting an assignee', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('passes loading state from Vuex to BoardEditableItem', async () => {
- store.state.isSettingAssignees = true;
-
- await wrapper.vm.$nextTick();
-
- expect(wrapper.find(BoardEditableItem).props('loading')).toBe(true);
- });
- });
-});
diff --git a/spec/frontend/boards/components/board_card_layout_deprecated_spec.js b/spec/frontend/boards/components/board_card_layout_deprecated_spec.js
new file mode 100644
index 00000000000..426c5289ba6
--- /dev/null
+++ b/spec/frontend/boards/components/board_card_layout_deprecated_spec.js
@@ -0,0 +1,158 @@
+/* global List */
+/* global ListLabel */
+
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+
+import MockAdapter from 'axios-mock-adapter';
+import Vuex from 'vuex';
+import waitForPromises from 'helpers/wait_for_promises';
+
+import '~/boards/models/label';
+import '~/boards/models/assignee';
+import '~/boards/models/list';
+import BoardCardLayout from '~/boards/components/board_card_layout_deprecated.vue';
+import issueCardInner from '~/boards/components/issue_card_inner.vue';
+import { ISSUABLE } from '~/boards/constants';
+import boardsVuexStore from '~/boards/stores';
+import boardsStore from '~/boards/stores/boards_store';
+import axios from '~/lib/utils/axios_utils';
+import { listObj, boardsMockInterceptor, setMockEndpoints } from '../mock_data';
+
+describe('Board card layout', () => {
+ let wrapper;
+ let mock;
+ let list;
+ let store;
+
+ const localVue = createLocalVue();
+ localVue.use(Vuex);
+
+ const createStore = ({ getters = {}, actions = {} } = {}) => {
+ store = new Vuex.Store({
+ ...boardsVuexStore,
+ actions,
+ getters,
+ });
+ };
+
+ // this particular mount component needs to be used after the root beforeEach because it depends on list being initialized
+ const mountComponent = ({ propsData = {}, provide = {} } = {}) => {
+ wrapper = shallowMount(BoardCardLayout, {
+ localVue,
+ stubs: {
+ issueCardInner,
+ },
+ store,
+ propsData: {
+ list,
+ issue: list.issues[0],
+ disabled: false,
+ index: 0,
+ ...propsData,
+ },
+ provide: {
+ groupId: null,
+ rootPath: '/',
+ scopedLabelsAvailable: false,
+ ...provide,
+ },
+ });
+ };
+
+ 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 () => {
+ createStore();
+ mountComponent();
+
+ wrapper.trigger('mousedown');
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.showDetail).toBe(true);
+ });
+
+ it('sets showDetail to false on mousemove', async () => {
+ createStore();
+ 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);
+ });
+
+ it("calls 'setActiveId' when 'graphqlBoardLists' feature flag is turned on", async () => {
+ const setActiveId = jest.fn();
+ createStore({
+ actions: {
+ setActiveId,
+ },
+ });
+ mountComponent({
+ provide: {
+ glFeatures: { graphqlBoardLists: true },
+ },
+ });
+
+ wrapper.trigger('mouseup');
+ await wrapper.vm.$nextTick();
+
+ expect(setActiveId).toHaveBeenCalledTimes(1);
+ expect(setActiveId).toHaveBeenCalledWith(expect.any(Object), {
+ id: list.issues[0].id,
+ sidebarType: ISSUABLE,
+ });
+ });
+
+ it("calls 'setActiveId' when epic swimlanes is active", async () => {
+ const setActiveId = jest.fn();
+ const isSwimlanesOn = () => true;
+ createStore({
+ getters: { isSwimlanesOn },
+ actions: {
+ setActiveId,
+ },
+ });
+ mountComponent();
+
+ wrapper.trigger('mouseup');
+ await wrapper.vm.$nextTick();
+
+ expect(setActiveId).toHaveBeenCalledTimes(1);
+ expect(setActiveId).toHaveBeenCalledWith(expect.any(Object), {
+ id: list.issues[0].id,
+ sidebarType: ISSUABLE,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/boards/components/board_card_layout_spec.js b/spec/frontend/boards/components/board_card_layout_spec.js
index d8633871e8d..3fa8714807c 100644
--- a/spec/frontend/boards/components/board_card_layout_spec.js
+++ b/spec/frontend/boards/components/board_card_layout_spec.js
@@ -1,28 +1,14 @@
-/* global List */
-/* global ListLabel */
-
-import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
-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 boardsVuexStore 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';
-
+import IssueCardInner from '~/boards/components/issue_card_inner.vue';
import { ISSUABLE } from '~/boards/constants';
+import defaultState from '~/boards/stores/state';
+import { mockLabelList, mockIssue } from '../mock_data';
describe('Board card layout', () => {
let wrapper;
- let mock;
- let list;
let store;
const localVue = createLocalVue();
@@ -30,7 +16,7 @@ describe('Board card layout', () => {
const createStore = ({ getters = {}, actions = {} } = {}) => {
store = new Vuex.Store({
- ...boardsVuexStore,
+ state: defaultState,
actions,
getters,
});
@@ -41,12 +27,12 @@ describe('Board card layout', () => {
wrapper = shallowMount(BoardCardLayout, {
localVue,
stubs: {
- issueCardInner,
+ IssueCardInner,
},
store,
propsData: {
- list,
- issue: list.issues[0],
+ list: mockLabelList,
+ issue: mockIssue,
disabled: false,
index: 0,
...propsData,
@@ -60,34 +46,9 @@ describe('Board card layout', () => {
});
};
- 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', () => {
@@ -112,25 +73,21 @@ describe('Board card layout', () => {
expect(wrapper.vm.showDetail).toBe(false);
});
- it("calls 'setActiveId' when 'graphqlBoardLists' feature flag is turned on", async () => {
+ it("calls 'setActiveId'", async () => {
const setActiveId = jest.fn();
createStore({
actions: {
setActiveId,
},
});
- mountComponent({
- provide: {
- glFeatures: { graphqlBoardLists: true },
- },
- });
+ mountComponent();
wrapper.trigger('mouseup');
await wrapper.vm.$nextTick();
expect(setActiveId).toHaveBeenCalledTimes(1);
expect(setActiveId).toHaveBeenCalledWith(expect.any(Object), {
- id: list.issues[0].id,
+ id: mockIssue.id,
sidebarType: ISSUABLE,
});
});
@@ -151,7 +108,7 @@ describe('Board card layout', () => {
expect(setActiveId).toHaveBeenCalledTimes(1);
expect(setActiveId).toHaveBeenCalledWith(expect.any(Object), {
- id: list.issues[0].id,
+ id: mockIssue.id,
sidebarType: ISSUABLE,
});
});
diff --git a/spec/frontend/boards/components/board_card_spec.js b/spec/frontend/boards/components/board_card_spec.js
index 1084009caad..5f26ae1bb3b 100644
--- a/spec/frontend/boards/components/board_card_spec.js
+++ b/spec/frontend/boards/components/board_card_spec.js
@@ -6,17 +6,17 @@ import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
+import BoardCard from '~/boards/components/board_card.vue';
+import issueCardInner from '~/boards/components/issue_card_inner.vue';
+import eventHub from '~/boards/eventhub';
+import store from '~/boards/stores';
+import boardsStore from '~/boards/stores/boards_store';
import axios from '~/lib/utils/axios_utils';
-import eventHub from '~/boards/eventhub';
import sidebarEventHub from '~/sidebar/event_hub';
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 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';
diff --git a/spec/frontend/boards/components/board_column_deprecated_spec.js b/spec/frontend/boards/components/board_column_deprecated_spec.js
index a703caca4eb..e6d65e48c3f 100644
--- a/spec/frontend/boards/components/board_column_deprecated_spec.js
+++ b/spec/frontend/boards/components/board_column_deprecated_spec.js
@@ -1,12 +1,12 @@
-import Vue from 'vue';
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
+import Vue, { nextTick } from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import { listObj } from 'jest/boards/mock_data';
import Board from '~/boards/components/board_column_deprecated.vue';
-import List from '~/boards/models/list';
import { ListType } from '~/boards/constants';
+import List from '~/boards/models/list';
import axios from '~/lib/utils/axios_utils';
describe('Board Column Component', () => {
@@ -30,6 +30,7 @@ describe('Board Column Component', () => {
const createComponent = ({
listType = ListType.backlog,
collapsed = false,
+ highlighted = false,
withLocalStorage = true,
} = {}) => {
const boardId = '1';
@@ -37,6 +38,7 @@ describe('Board Column Component', () => {
const listMock = {
...listObj,
list_type: listType,
+ highlighted,
collapsed,
};
@@ -91,4 +93,14 @@ describe('Board Column Component', () => {
expect(isCollapsed()).toBe(true);
});
});
+
+ describe('highlighting', () => {
+ it('scrolls to column when highlighted', async () => {
+ createComponent({ highlighted: true });
+
+ await nextTick();
+
+ expect(wrapper.element.scrollIntoView).toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/frontend/boards/components/board_column_spec.js b/spec/frontend/boards/components/board_column_spec.js
index 1dcdad2b492..4e523d636cd 100644
--- a/spec/frontend/boards/components/board_column_spec.js
+++ b/spec/frontend/boards/components/board_column_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { listObj } from 'jest/boards/mock_data';
import BoardColumn from '~/boards/components/board_column.vue';
@@ -66,4 +67,16 @@ describe('Board Column Component', () => {
expect(isCollapsed()).toBe(true);
});
});
+
+ describe('highlighting', () => {
+ it('scrolls to column when highlighted', async () => {
+ createComponent();
+
+ store.state.highlightedLists.push(listObj.id);
+
+ await nextTick();
+
+ expect(wrapper.element.scrollIntoView).toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/frontend/boards/components/board_configuration_options_spec.js b/spec/frontend/boards/components/board_configuration_options_spec.js
index d9614c254e2..6f0971a9458 100644
--- a/spec/frontend/boards/components/board_configuration_options_spec.js
+++ b/spec/frontend/boards/components/board_configuration_options_spec.js
@@ -7,6 +7,7 @@ describe('BoardConfigurationOptions', () => {
const defaultProps = {
hideBacklogList: false,
hideClosedList: false,
+ readonly: false,
};
const createComponent = (props = {}) => {
@@ -61,4 +62,18 @@ describe('BoardConfigurationOptions', () => {
expect(wrapper.emitted('update:hideClosedList')).toEqual([[true]]);
});
+
+ it('renders checkboxes disabled when user does not have edit rights', () => {
+ createComponent({ readonly: true });
+
+ expect(closedListCheckbox().attributes('disabled')).toBe('true');
+ expect(backlogListCheckbox().attributes('disabled')).toBe('true');
+ });
+
+ it('renders checkboxes enabled when user has edit rights', () => {
+ createComponent();
+
+ expect(closedListCheckbox().attributes('disabled')).toBeUndefined();
+ expect(backlogListCheckbox().attributes('disabled')).toBeUndefined();
+ });
});
diff --git a/spec/frontend/boards/components/board_content_spec.js b/spec/frontend/boards/components/board_content_spec.js
index 98be02d7dbf..159b67ccc67 100644
--- a/spec/frontend/boards/components/board_content_spec.js
+++ b/spec/frontend/boards/components/board_content_spec.js
@@ -1,12 +1,12 @@
-import Vuex from 'vuex';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlAlert } from '@gitlab/ui';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
import Draggable from 'vuedraggable';
+import Vuex from 'vuex';
import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue';
import getters from 'ee_else_ce/boards/stores/getters';
import BoardColumnDeprecated from '~/boards/components/board_column_deprecated.vue';
-import { mockLists, mockListsWithModel } from '../mock_data';
import BoardContent from '~/boards/components/board_content.vue';
+import { mockLists, mockListsWithModel } from '../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js
index c34987a55de..858efea99ad 100644
--- a/spec/frontend/boards/components/board_form_spec.js
+++ b/spec/frontend/boards/components/board_form_spec.js
@@ -1,16 +1,15 @@
+import { GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-
import { TEST_HOST } from 'helpers/test_constants';
-import { GlModal } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
-import { visitUrl } from '~/lib/utils/url_utility';
-import boardsStore from '~/boards/stores/boards_store';
import BoardForm from '~/boards/components/board_form.vue';
-import updateBoardMutation from '~/boards/graphql/board_update.mutation.graphql';
+import { formType } from '~/boards/constants';
import createBoardMutation from '~/boards/graphql/board_create.mutation.graphql';
import destroyBoardMutation from '~/boards/graphql/board_destroy.mutation.graphql';
+import updateBoardMutation from '~/boards/graphql/board_update.mutation.graphql';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
+import { visitUrl } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility', () => ({
visitUrl: jest.fn().mockName('visitUrlMock'),
@@ -35,6 +34,7 @@ const defaultProps = {
labelsPath: `${TEST_HOST}/labels/path`,
labelsWebUrl: `${TEST_HOST}/-/labels`,
currentBoard,
+ currentPage: '',
};
describe('BoardForm', () => {
@@ -75,14 +75,12 @@ describe('BoardForm', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
- boardsStore.state.currentPage = null;
mutate = null;
});
describe('when user can not admin the board', () => {
beforeEach(() => {
- boardsStore.state.currentPage = 'new';
- createComponent();
+ createComponent({ currentPage: formType.new });
});
it('hides modal footer when user is not a board admin', () => {
@@ -100,8 +98,7 @@ describe('BoardForm', () => {
describe('when user can admin the board', () => {
beforeEach(() => {
- boardsStore.state.currentPage = 'new';
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.new });
});
it('shows modal footer when user is a board admin', () => {
@@ -118,13 +115,9 @@ describe('BoardForm', () => {
});
describe('when creating a new board', () => {
- beforeEach(() => {
- boardsStore.state.currentPage = 'new';
- });
-
describe('on non-scoped-board', () => {
beforeEach(() => {
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.new });
});
it('clears the form', () => {
@@ -165,7 +158,7 @@ describe('BoardForm', () => {
});
it('does not call API if board name is empty', async () => {
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.new });
findInput().trigger('keyup.enter', { metaKey: true });
await waitForPromises();
@@ -174,7 +167,7 @@ describe('BoardForm', () => {
});
it('calls a correct GraphQL mutation and redirects to correct page from existing board', async () => {
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.new });
fillForm();
await waitForPromises();
@@ -194,7 +187,7 @@ describe('BoardForm', () => {
it('shows an error flash if GraphQL mutation fails', async () => {
mutate = jest.fn().mockRejectedValue('Houston, we have a problem');
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.new });
fillForm();
await waitForPromises();
@@ -209,13 +202,9 @@ describe('BoardForm', () => {
});
describe('when editing a board', () => {
- beforeEach(() => {
- boardsStore.state.currentPage = 'edit';
- });
-
describe('on non-scoped-board', () => {
beforeEach(() => {
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.edit });
});
it('clears the form', () => {
@@ -247,7 +236,7 @@ describe('BoardForm', () => {
},
});
window.location = new URL('https://test/boards/1');
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.edit });
findInput().trigger('keyup.enter', { metaKey: true });
@@ -273,7 +262,7 @@ describe('BoardForm', () => {
},
});
window.location = new URL('https://test/boards/1?group_by=epic');
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.edit });
findInput().trigger('keyup.enter', { metaKey: true });
@@ -294,7 +283,7 @@ describe('BoardForm', () => {
it('shows an error flash if GraphQL mutation fails', async () => {
mutate = jest.fn().mockRejectedValue('Houston, we have a problem');
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.edit });
findInput().trigger('keyup.enter', { metaKey: true });
await waitForPromises();
@@ -308,24 +297,20 @@ describe('BoardForm', () => {
});
describe('when deleting a board', () => {
- beforeEach(() => {
- boardsStore.state.currentPage = 'delete';
- });
-
it('passes correct primary action text and variant', () => {
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.delete });
expect(findModalActionPrimary().text).toBe('Delete');
expect(findModalActionPrimary().attributes[0].variant).toBe('danger');
});
it('renders delete confirmation message', () => {
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.delete });
expect(findDeleteConfirmation().exists()).toBe(true);
});
it('calls a correct GraphQL mutation and redirects to correct page after deleting board', async () => {
mutate = jest.fn().mockResolvedValue({});
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.delete });
findModal().vm.$emit('primary');
await waitForPromises();
@@ -343,7 +328,7 @@ describe('BoardForm', () => {
it('shows an error flash if GraphQL mutation fails', async () => {
mutate = jest.fn().mockRejectedValue('Houston, we have a problem');
- createComponent({ canAdminBoard: true });
+ createComponent({ canAdminBoard: true, currentPage: formType.delete });
findModal().vm.$emit('primary');
await waitForPromises();
diff --git a/spec/frontend/boards/components/board_list_header_deprecated_spec.js b/spec/frontend/boards/components/board_list_header_deprecated_spec.js
index 6207724e6a9..fdc7cd2b1d4 100644
--- a/spec/frontend/boards/components/board_list_header_deprecated_spec.js
+++ b/spec/frontend/boards/components/board_list_header_deprecated_spec.js
@@ -1,12 +1,12 @@
-import Vue from 'vue';
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
+import Vue from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import { listObj } from 'jest/boards/mock_data';
import BoardListHeader from '~/boards/components/board_list_header_deprecated.vue';
-import List from '~/boards/models/list';
import { ListType } from '~/boards/constants';
+import List from '~/boards/models/list';
import axios from '~/lib/utils/axios_utils';
describe('Board List Header Component', () => {
@@ -74,7 +74,13 @@ describe('Board List Header Component', () => {
describe('Add issue button', () => {
const hasNoAddButton = [ListType.closed];
- const hasAddButton = [ListType.backlog, ListType.label, ListType.milestone, ListType.assignee];
+ const hasAddButton = [
+ ListType.backlog,
+ ListType.label,
+ ListType.milestone,
+ ListType.iteration,
+ ListType.assignee,
+ ];
it.each(hasNoAddButton)('does not render when List Type is `%s`', (listType) => {
createComponent({ listType });
diff --git a/spec/frontend/boards/components/board_list_header_spec.js b/spec/frontend/boards/components/board_list_header_spec.js
index 357d05ced02..f30e3792435 100644
--- a/spec/frontend/boards/components/board_list_header_spec.js
+++ b/spec/frontend/boards/components/board_list_header_spec.js
@@ -1,5 +1,5 @@
-import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import { mockLabelList } from 'jest/boards/mock_data';
import BoardListHeader from '~/boards/components/board_list_header.vue';
@@ -78,7 +78,13 @@ describe('Board List Header Component', () => {
describe('Add issue button', () => {
const hasNoAddButton = [ListType.closed];
- const hasAddButton = [ListType.backlog, ListType.label, ListType.milestone, ListType.assignee];
+ const hasAddButton = [
+ ListType.backlog,
+ ListType.label,
+ ListType.milestone,
+ ListType.iteration,
+ ListType.assignee,
+ ];
it.each(hasNoAddButton)('does not render when List Type is `%s`', (listType) => {
createComponent({ listType });
@@ -167,7 +173,7 @@ describe('Board List Header Component', () => {
describe('user can drag', () => {
const cannotDragList = [ListType.backlog, ListType.closed];
- const canDragList = [ListType.label, ListType.milestone, ListType.assignee];
+ const canDragList = [ListType.label, ListType.milestone, ListType.iteration, ListType.assignee];
it.each(cannotDragList)(
'does not have user-can-drag-class so user cannot drag list',
diff --git a/spec/frontend/boards/components/board_new_issue_spec.js b/spec/frontend/boards/components/board_new_issue_spec.js
index 5a01221a5be..ce8c95527e9 100644
--- a/spec/frontend/boards/components/board_new_issue_spec.js
+++ b/spec/frontend/boards/components/board_new_issue_spec.js
@@ -1,5 +1,5 @@
-import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import BoardNewIssue from '~/boards/components/board_new_issue.vue';
import '~/boards/models/list';
diff --git a/spec/frontend/boards/components/board_settings_sidebar_spec.js b/spec/frontend/boards/components/board_settings_sidebar_spec.js
index 12c9431f2d4..52b4d71f7b9 100644
--- a/spec/frontend/boards/components/board_settings_sidebar_spec.js
+++ b/spec/frontend/boards/components/board_settings_sidebar_spec.js
@@ -1,14 +1,14 @@
import '~/boards/models/list';
-import MockAdapter from 'axios-mock-adapter';
+import { GlDrawer, GlLabel } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
import Vuex from 'vuex';
-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 { inactiveId, LIST } from '~/boards/constants';
import { createStore } from '~/boards/stores';
+import boardsStore from '~/boards/stores/boards_store';
import sidebarEventHub from '~/sidebar/event_hub';
-import { inactiveId, LIST } from '~/boards/constants';
const localVue = createLocalVue();
diff --git a/spec/frontend/boards/components/boards_selector_deprecated_spec.js b/spec/frontend/boards/components/boards_selector_deprecated_spec.js
new file mode 100644
index 00000000000..cc078861d75
--- /dev/null
+++ b/spec/frontend/boards/components/boards_selector_deprecated_spec.js
@@ -0,0 +1,214 @@
+import { GlDropdown, GlLoadingIcon, GlDropdownSectionHeader } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import { TEST_HOST } from 'spec/test_constants';
+import BoardsSelector from '~/boards/components/boards_selector_deprecated.vue';
+import boardsStore from '~/boards/stores/boards_store';
+
+const throttleDuration = 1;
+
+function boardGenerator(n) {
+ return new Array(n).fill().map((board, index) => {
+ const id = `${index}`;
+ const name = `board${id}`;
+
+ return {
+ id,
+ name,
+ };
+ });
+}
+
+describe('BoardsSelector', () => {
+ let wrapper;
+ let allBoardsResponse;
+ let recentBoardsResponse;
+ const boards = boardGenerator(20);
+ const recentBoards = boardGenerator(5);
+
+ const fillSearchBox = (filterTerm) => {
+ const searchBox = wrapper.find({ ref: 'searchBox' });
+ const searchBoxInput = searchBox.find('input');
+ searchBoxInput.setValue(filterTerm);
+ searchBoxInput.trigger('input');
+ };
+
+ const getDropdownItems = () => wrapper.findAll('.js-dropdown-item');
+ const getDropdownHeaders = () => wrapper.findAll(GlDropdownSectionHeader);
+ const getLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const findDropdown = () => wrapper.find(GlDropdown);
+
+ beforeEach(() => {
+ const $apollo = {
+ queries: {
+ boards: {
+ loading: false,
+ },
+ },
+ };
+
+ boardsStore.setEndpoints({
+ boardsEndpoint: '',
+ recentBoardsEndpoint: '',
+ listsEndpoint: '',
+ bulkUpdatePath: '',
+ boardId: '',
+ });
+
+ allBoardsResponse = Promise.resolve({
+ data: {
+ group: {
+ boards: {
+ edges: boards.map((board) => ({ node: board })),
+ },
+ },
+ },
+ });
+ recentBoardsResponse = Promise.resolve({
+ data: recentBoards,
+ });
+
+ boardsStore.allBoards = jest.fn(() => allBoardsResponse);
+ boardsStore.recentBoards = jest.fn(() => recentBoardsResponse);
+
+ wrapper = mount(BoardsSelector, {
+ propsData: {
+ throttleDuration,
+ currentBoard: {
+ id: 1,
+ name: 'Development',
+ milestone_id: null,
+ weight: null,
+ assignee_id: null,
+ labels: [],
+ },
+ 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,
+ weights: [],
+ },
+ mocks: { $apollo },
+ attachTo: document.body,
+ });
+
+ wrapper.vm.$apollo.addSmartQuery = jest.fn((_, options) => {
+ wrapper.setData({
+ [options.loadingKey]: true,
+ });
+ });
+
+ // Emits gl-dropdown show event to simulate the dropdown is opened at initialization time
+ findDropdown().vm.$emit('show');
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('loading', () => {
+ // we are testing loading state, so don't resolve responses until after the tests
+ afterEach(() => {
+ return Promise.all([allBoardsResponse, recentBoardsResponse]).then(() => nextTick());
+ });
+
+ it('shows loading spinner', () => {
+ expect(getDropdownHeaders()).toHaveLength(0);
+ expect(getDropdownItems()).toHaveLength(0);
+ expect(getLoadingIcon().exists()).toBe(true);
+ });
+ });
+
+ describe('loaded', () => {
+ beforeEach(async () => {
+ await wrapper.setData({
+ loadingBoards: false,
+ });
+ return Promise.all([allBoardsResponse, recentBoardsResponse]).then(() => nextTick());
+ });
+
+ it('hides loading spinner', () => {
+ expect(getLoadingIcon().exists()).toBe(false);
+ });
+
+ describe('filtering', () => {
+ beforeEach(() => {
+ wrapper.setData({
+ boards,
+ });
+
+ return nextTick();
+ });
+
+ it('shows all boards without filtering', () => {
+ expect(getDropdownItems()).toHaveLength(boards.length + recentBoards.length);
+ });
+
+ it('shows only matching boards when filtering', () => {
+ const filterTerm = 'board1';
+ const expectedCount = boards.filter((board) => board.name.includes(filterTerm)).length;
+
+ fillSearchBox(filterTerm);
+
+ return nextTick().then(() => {
+ expect(getDropdownItems()).toHaveLength(expectedCount);
+ });
+ });
+
+ it('shows message if there are no matching boards', () => {
+ fillSearchBox('does not exist');
+
+ return nextTick().then(() => {
+ expect(getDropdownItems()).toHaveLength(0);
+ expect(wrapper.text().includes('No matching boards found')).toBe(true);
+ });
+ });
+ });
+
+ describe('recent boards section', () => {
+ it('shows only when boards are greater than 10', () => {
+ wrapper.setData({
+ boards,
+ });
+
+ return nextTick().then(() => {
+ expect(getDropdownHeaders()).toHaveLength(2);
+ });
+ });
+
+ it('does not show when boards are less than 10', () => {
+ wrapper.setData({
+ boards: boards.slice(0, 5),
+ });
+
+ return nextTick().then(() => {
+ expect(getDropdownHeaders()).toHaveLength(0);
+ });
+ });
+
+ it('does not show when recentBoards api returns empty array', () => {
+ wrapper.setData({
+ recentBoards: [],
+ });
+
+ return nextTick().then(() => {
+ expect(getDropdownHeaders()).toHaveLength(0);
+ });
+ });
+
+ it('does not show when search is active', () => {
+ fillSearchBox('Random string');
+
+ return nextTick().then(() => {
+ expect(getDropdownHeaders()).toHaveLength(0);
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/boards/components/boards_selector_spec.js b/spec/frontend/boards/components/boards_selector_spec.js
index 81575bf486a..bf317b51e83 100644
--- a/spec/frontend/boards/components/boards_selector_spec.js
+++ b/spec/frontend/boards/components/boards_selector_spec.js
@@ -1,9 +1,10 @@
-import { nextTick } from 'vue';
-import { mount } from '@vue/test-utils';
import { GlDropdown, GlLoadingIcon, GlDropdownSectionHeader } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import { TEST_HOST } from 'spec/test_constants';
import BoardsSelector from '~/boards/components/boards_selector.vue';
-import boardsStore from '~/boards/stores/boards_store';
+import axios from '~/lib/utils/axios_utils';
const throttleDuration = 1;
@@ -23,6 +24,7 @@ describe('BoardsSelector', () => {
let wrapper;
let allBoardsResponse;
let recentBoardsResponse;
+ let mock;
const boards = boardGenerator(20);
const recentBoards = boardGenerator(5);
@@ -39,6 +41,7 @@ describe('BoardsSelector', () => {
const findDropdown = () => wrapper.find(GlDropdown);
beforeEach(() => {
+ mock = new MockAdapter(axios);
const $apollo = {
queries: {
boards: {
@@ -47,14 +50,6 @@ describe('BoardsSelector', () => {
},
};
- boardsStore.setEndpoints({
- boardsEndpoint: '',
- recentBoardsEndpoint: '',
- listsEndpoint: '',
- bulkUpdatePath: '',
- boardId: '',
- });
-
allBoardsResponse = Promise.resolve({
data: {
group: {
@@ -68,9 +63,6 @@ describe('BoardsSelector', () => {
data: recentBoards,
});
- boardsStore.allBoards = jest.fn(() => allBoardsResponse);
- boardsStore.recentBoards = jest.fn(() => recentBoardsResponse);
-
wrapper = mount(BoardsSelector, {
propsData: {
throttleDuration,
@@ -95,6 +87,10 @@ describe('BoardsSelector', () => {
},
mocks: { $apollo },
attachTo: document.body,
+ provide: {
+ fullPath: '',
+ recentBoardsEndpoint: `${TEST_HOST}/recent`,
+ },
});
wrapper.vm.$apollo.addSmartQuery = jest.fn((_, options) => {
@@ -103,6 +99,8 @@ describe('BoardsSelector', () => {
});
});
+ mock.onGet(`${TEST_HOST}/recent`).replyOnce(200, recentBoards);
+
// Emits gl-dropdown show event to simulate the dropdown is opened at initialization time
findDropdown().vm.$emit('show');
});
@@ -110,6 +108,7 @@ describe('BoardsSelector', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
+ mock.restore();
});
describe('loading', () => {
@@ -133,7 +132,8 @@ describe('BoardsSelector', () => {
return Promise.all([allBoardsResponse, recentBoardsResponse]).then(() => nextTick());
});
- it('hides loading spinner', () => {
+ it('hides loading spinner', async () => {
+ await wrapper.vm.$nextTick();
expect(getLoadingIcon().exists()).toBe(false);
});
diff --git a/spec/frontend/boards/components/issue_time_estimate_spec.js b/spec/frontend/boards/components/issue_time_estimate_spec.js
index 9ac8fae3fcc..2e253d24125 100644
--- a/spec/frontend/boards/components/issue_time_estimate_spec.js
+++ b/spec/frontend/boards/components/issue_time_estimate_spec.js
@@ -1,5 +1,5 @@
-import { config as vueConfig } from 'vue';
import { shallowMount } from '@vue/test-utils';
+import { config as vueConfig } from 'vue';
import IssueTimeEstimate from '~/boards/components/issue_time_estimate.vue';
describe('Issue Time Estimate component', () => {
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 de414bb929e..12e9a9ba365 100644
--- a/spec/frontend/boards/components/sidebar/board_editable_item_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_editable_item_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import BoardSidebarItem from '~/boards/components/sidebar/board_editable_item.vue';
describe('boards sidebar remove issue', () => {
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_due_date_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_due_date_spec.js
index b034c8cb11d..7838b5a0b2f 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_due_date_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_due_date_spec.js
@@ -1,7 +1,7 @@
-import { shallowMount } from '@vue/test-utils';
import { GlDatepicker } from '@gitlab/ui';
-import BoardSidebarDueDate from '~/boards/components/sidebar/board_sidebar_due_date.vue';
+import { shallowMount } from '@vue/test-utils';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
+import BoardSidebarDueDate from '~/boards/components/sidebar/board_sidebar_due_date.vue';
import { createStore } from '~/boards/stores';
import createFlash from '~/flash';
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_issue_title_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_issue_title_spec.js
index 86895c648a4..bc7df1c76c6 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_issue_title_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_issue_title_spec.js
@@ -1,9 +1,9 @@
-import { shallowMount } from '@vue/test-utils';
import { GlAlert, GlFormInput, GlForm } from '@gitlab/ui';
-import BoardSidebarIssueTitle from '~/boards/components/sidebar/board_sidebar_issue_title.vue';
+import { shallowMount } from '@vue/test-utils';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
-import createFlash from '~/flash';
+import BoardSidebarIssueTitle from '~/boards/components/sidebar/board_sidebar_issue_title.vue';
import { createStore } from '~/boards/stores';
+import createFlash from '~/flash';
const TEST_TITLE = 'New issue title';
const TEST_ISSUE_A = {
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
index 2342caa9dfd..12b873ba7d8 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js
@@ -1,12 +1,12 @@
-import { shallowMount } from '@vue/test-utils';
import { GlLabel } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
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 BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
import { createStore } from '~/boards/stores';
import createFlash from '~/flash';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
jest.mock('~/flash');
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js
index 74d88d9f34c..8820ec7ae63 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js
@@ -1,8 +1,8 @@
+import { GlLoadingIcon, GlDropdown } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { GlLoadingIcon } from '@gitlab/ui';
import { mockMilestone as TEST_MILESTONE } from 'jest/boards/mock_data';
-import BoardSidebarMilestoneSelect from '~/boards/components/sidebar/board_sidebar_milestone_select.vue';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
+import BoardSidebarMilestoneSelect from '~/boards/components/sidebar/board_sidebar_milestone_select.vue';
import { createStore } from '~/boards/stores';
import createFlash from '~/flash';
@@ -20,7 +20,7 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () =>
wrapper = null;
});
- const createWrapper = ({ milestone = null } = {}) => {
+ const createWrapper = ({ milestone = null, loading = false } = {}) => {
store = createStore();
store.state.issues = { [TEST_ISSUE.id]: { ...TEST_ISSUE, milestone } };
store.state.activeId = TEST_ISSUE.id;
@@ -38,7 +38,7 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () =>
},
mocks: {
$apollo: {
- loading: false,
+ loading,
},
},
});
@@ -46,10 +46,42 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () =>
const findCollapsed = () => wrapper.find('[data-testid="collapsed-content"]');
const findLoader = () => wrapper.find(GlLoadingIcon);
+ const findDropdown = () => wrapper.find(GlDropdown);
+ const findBoardEditableItem = () => wrapper.find(BoardEditableItem);
const findDropdownItem = () => wrapper.find('[data-testid="milestone-item"]');
const findUnsetMilestoneItem = () => wrapper.find('[data-testid="no-milestone-item"]');
const findNoMilestonesFoundItem = () => wrapper.find('[data-testid="no-milestones-found"]');
+ describe('when not editing', () => {
+ it('opens the milestone dropdown on clicking edit', async () => {
+ createWrapper();
+ wrapper.vm.$refs.dropdown.show = jest.fn();
+
+ await findBoardEditableItem().vm.$emit('open');
+
+ expect(wrapper.vm.$refs.dropdown.show).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('when editing', () => {
+ beforeEach(() => {
+ createWrapper();
+ jest.spyOn(wrapper.vm.$refs.sidebarItem, 'collapse');
+ });
+
+ it('collapses BoardEditableItem on clicking edit', async () => {
+ await findBoardEditableItem().vm.$emit('close');
+
+ expect(wrapper.vm.$refs.sidebarItem.collapse).toHaveBeenCalledTimes(1);
+ });
+
+ it('collapses BoardEditableItem on hiding dropdown', async () => {
+ await findDropdown().vm.$emit('hide');
+
+ expect(wrapper.vm.$refs.sidebarItem.collapse).toHaveBeenCalledTimes(1);
+ });
+ });
+
it('renders "None" when no milestone is selected', () => {
createWrapper();
@@ -63,12 +95,7 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () =>
});
it('shows loader while Apollo is loading', async () => {
- createWrapper({ milestone: TEST_MILESTONE });
-
- expect(findLoader().exists()).toBe(false);
-
- wrapper.vm.$apollo.loading = true;
- await wrapper.vm.$nextTick();
+ createWrapper({ milestone: TEST_MILESTONE, loading: true });
expect(findLoader().exists()).toBe(true);
});
@@ -76,8 +103,7 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () =>
it('shows message when error or no milestones found', async () => {
createWrapper();
- wrapper.setData({ milestones: [] });
- await wrapper.vm.$nextTick();
+ await wrapper.setData({ milestones: [] });
expect(findNoMilestonesFoundItem().text()).toBe('No milestones found');
});
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js
index b1df0f2d771..3e6b0be0267 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js
@@ -1,11 +1,11 @@
-import Vuex from 'vuex';
-import { mount, createLocalVue } from '@vue/test-utils';
import { GlToggle, GlLoadingIcon } from '@gitlab/ui';
+import { mount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
import BoardSidebarSubscription from '~/boards/components/sidebar/board_sidebar_subscription.vue';
-import * as types from '~/boards/stores/mutation_types';
import { createStore } from '~/boards/stores';
-import { mockActiveIssue } from '../../mock_data';
+import * as types from '~/boards/stores/mutation_types';
import createFlash from '~/flash';
+import { mockActiveIssue } from '../../mock_data';
jest.mock('~/flash.js');
diff --git a/spec/frontend/boards/components/sidebar/remove_issue_spec.js b/spec/frontend/boards/components/sidebar/remove_issue_spec.js
index 1b7a78e6e58..1f740c10106 100644
--- a/spec/frontend/boards/components/sidebar/remove_issue_spec.js
+++ b/spec/frontend/boards/components/sidebar/remove_issue_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import RemoveIssue from '~/boards/components/sidebar/remove_issue.vue';
diff --git a/spec/frontend/boards/issue_card_deprecated_spec.js b/spec/frontend/boards/issue_card_deprecated_spec.js
index fd7b0edb97e..909be275030 100644
--- a/spec/frontend/boards/issue_card_deprecated_spec.js
+++ b/spec/frontend/boards/issue_card_deprecated_spec.js
@@ -1,14 +1,14 @@
/* global ListAssignee, ListLabel, ListIssue */
+import { GlLabel } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { range } from 'lodash';
import '~/boards/models/label';
import '~/boards/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
-import { GlLabel } from '@gitlab/ui';
import IssueCardInner from '~/boards/components/issue_card_inner_deprecated.vue';
-import { listObj } from './mock_data';
import store from '~/boards/stores';
+import { listObj } from './mock_data';
describe('Issue card component', () => {
const user = new ListAssignee({
diff --git a/spec/frontend/boards/issue_card_inner_spec.js b/spec/frontend/boards/issue_card_inner_spec.js
index f9ad78494af..b9f84fed6b3 100644
--- a/spec/frontend/boards/issue_card_inner_spec.js
+++ b/spec/frontend/boards/issue_card_inner_spec.js
@@ -1,11 +1,11 @@
+import { GlLabel } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { range } from 'lodash';
-import { GlLabel } from '@gitlab/ui';
import IssueCardInner from '~/boards/components/issue_card_inner.vue';
-import { mockLabelList } from './mock_data';
-import defaultStore from '~/boards/stores';
import eventHub from '~/boards/eventhub';
+import defaultStore from '~/boards/stores';
import { updateHistory } from '~/lib/utils/url_utility';
+import { mockLabelList } from './mock_data';
jest.mock('~/lib/utils/url_utility');
jest.mock('~/boards/eventhub');
diff --git a/spec/frontend/boards/issue_spec.js b/spec/frontend/boards/issue_spec.js
index d68e17c06a7..1f354fb04db 100644
--- a/spec/frontend/boards/issue_spec.js
+++ b/spec/frontend/boards/issue_spec.js
@@ -41,7 +41,7 @@ describe('Issue model', () => {
});
expect(issue.labels.length).toBe(1);
- expect(issue.labels[0].color).toBe('red');
+ expect(issue.labels[0].color).toBe('#F0AD4E');
});
it('adds other label with same title', () => {
diff --git a/spec/frontend/boards/list_spec.js b/spec/frontend/boards/list_spec.js
index db01f62c9a6..4d6a82bdff0 100644
--- a/spec/frontend/boards/list_spec.js
+++ b/spec/frontend/boards/list_spec.js
@@ -2,16 +2,15 @@
/* global ListAssignee */
/* global ListIssue */
/* global ListLabel */
-
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/issue';
import '~/boards/models/list';
import { ListType } from '~/boards/constants';
import boardsStore from '~/boards/stores/boards_store';
+import axios from '~/lib/utils/axios_utils';
import { listObj, listObjDuplicate, boardsMockInterceptor } from './mock_data';
describe('List model', () => {
diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js
index d5cfb9b7d07..e106b9235d6 100644
--- a/spec/frontend/boards/mock_data.js
+++ b/spec/frontend/boards/mock_data.js
@@ -1,7 +1,7 @@
/* global List */
-import Vue from 'vue';
import { keyBy } from 'lodash';
+import Vue from 'vue';
import '~/boards/models/list';
import boardsStore from '~/boards/stores/boards_store';
@@ -137,7 +137,7 @@ export const rawIssue = {
{
id: 1,
title: 'test',
- color: 'red',
+ color: '#F0AD4E',
description: 'testing',
},
],
@@ -165,7 +165,7 @@ export const mockIssue = {
{
id: 1,
title: 'test',
- color: 'red',
+ color: '#F0AD4E',
description: 'testing',
},
],
diff --git a/spec/frontend/boards/project_select_deprecated_spec.js b/spec/frontend/boards/project_select_deprecated_spec.js
index e4f8f96bd33..9042c4bf9ba 100644
--- a/spec/frontend/boards/project_select_deprecated_spec.js
+++ b/spec/frontend/boards/project_select_deprecated_spec.js
@@ -1,14 +1,13 @@
+import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlLoadingIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import axios from 'axios';
import AxiosMockAdapter from 'axios-mock-adapter';
-import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlLoadingIcon } from '@gitlab/ui';
-import httpStatus from '~/lib/utils/http_status';
-import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants';
+import ProjectSelect from '~/boards/components/project_select_deprecated.vue';
import { ListType } from '~/boards/constants';
import eventHub from '~/boards/eventhub';
import { deprecatedCreateFlash as flash } from '~/flash';
-
-import ProjectSelect from '~/boards/components/project_select_deprecated.vue';
+import httpStatus from '~/lib/utils/http_status';
+import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants';
import { listObj, mockRawGroupProjects } from './mock_data';
diff --git a/spec/frontend/boards/project_select_spec.js b/spec/frontend/boards/project_select_spec.js
index 14ddab3542b..aa71952c42b 100644
--- a/spec/frontend/boards/project_select_spec.js
+++ b/spec/frontend/boards/project_select_spec.js
@@ -1,9 +1,8 @@
-import Vuex from 'vuex';
-import { createLocalVue, mount } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlLoadingIcon } from '@gitlab/ui';
-import defaultState from '~/boards/stores/state';
-
+import { createLocalVue, mount } from '@vue/test-utils';
+import Vuex from 'vuex';
import ProjectSelect from '~/boards/components/project_select.vue';
+import defaultState from '~/boards/stores/state';
import { mockList, mockGroupProjects } from './mock_data';
diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js
index e4209cd5e55..32d0e7ae886 100644
--- a/spec/frontend/boards/stores/actions_spec.js
+++ b/spec/frontend/boards/stores/actions_spec.js
@@ -1,5 +1,17 @@
import testAction from 'helpers/vuex_action_helper';
import {
+ fullBoardId,
+ formatListIssues,
+ formatBoardLists,
+ formatIssueInput,
+} from '~/boards/boards_util';
+import { inactiveId } from '~/boards/constants';
+import destroyBoardListMutation from '~/boards/graphql/board_list_destroy.mutation.graphql';
+import issueCreateMutation from '~/boards/graphql/issue_create.mutation.graphql';
+import issueMoveListMutation from '~/boards/graphql/issue_move_list.mutation.graphql';
+import actions, { gqlClient } from '~/boards/stores/actions';
+import * as types from '~/boards/stores/mutation_types';
+import {
mockLists,
mockListsById,
mockIssue,
@@ -11,20 +23,6 @@ import {
mockActiveIssue,
mockGroupProjects,
} from '../mock_data';
-import actions, { gqlClient } from '~/boards/stores/actions';
-import * as types from '~/boards/stores/mutation_types';
-import { inactiveId } from '~/boards/constants';
-import issueMoveListMutation from '~/boards/graphql/issue_move_list.mutation.graphql';
-import destroyBoardListMutation from '~/boards/graphql/board_list_destroy.mutation.graphql';
-import issueCreateMutation from '~/boards/graphql/issue_create.mutation.graphql';
-import updateAssignees from '~/vue_shared/components/sidebar/queries/updateAssignees.mutation.graphql';
-import {
- fullBoardId,
- formatListIssues,
- formatBoardLists,
- formatIssueInput,
-} from '~/boards/boards_util';
-import createFlash from '~/flash';
jest.mock('~/flash');
@@ -71,7 +69,7 @@ describe('setFilters', () => {
actions.setFilters,
filters,
state,
- [{ type: types.SET_FILTERS, payload: filters }],
+ [{ type: types.SET_FILTERS, payload: { ...filters, not: {} } }],
[],
done,
);
@@ -186,7 +184,27 @@ describe('fetchLists', () => {
});
describe('createList', () => {
- it('should dispatch addList action when creating backlog list', (done) => {
+ let commit;
+ let dispatch;
+ let getters;
+ let state;
+
+ beforeEach(() => {
+ state = {
+ fullPath: 'gitlab-org',
+ boardId: '1',
+ boardType: 'group',
+ disabled: false,
+ boardLists: [{ type: 'closed' }],
+ };
+ commit = jest.fn();
+ dispatch = jest.fn();
+ getters = {
+ getListByLabelId: jest.fn(),
+ };
+ });
+
+ it('should dispatch addList action when creating backlog list', async () => {
const backlogList = {
id: 'gid://gitlab/List/1',
listType: 'backlog',
@@ -205,25 +223,35 @@ describe('createList', () => {
}),
);
- const state = {
- fullPath: 'gitlab-org',
- boardId: '1',
- boardType: 'group',
- disabled: false,
- boardLists: [{ type: 'closed' }],
+ await actions.createList({ getters, state, commit, dispatch }, { backlog: true });
+
+ expect(dispatch).toHaveBeenCalledWith('addList', backlogList);
+ });
+
+ it('dispatches highlightList after addList has succeeded', async () => {
+ const list = {
+ id: 'gid://gitlab/List/1',
+ listType: 'label',
+ title: 'Open',
+ labelId: '4',
};
- testAction(
- actions.createList,
- { backlog: true },
- state,
- [],
- [{ type: 'addList', payload: backlogList }],
- done,
- );
+ jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
+ data: {
+ boardListCreate: {
+ list,
+ errors: [],
+ },
+ },
+ });
+
+ await actions.createList({ getters, state, commit, dispatch }, { labelId: '4' });
+
+ expect(dispatch).toHaveBeenCalledWith('addList', list);
+ expect(dispatch).toHaveBeenCalledWith('highlightList', list.id);
});
- it('should commit CREATE_LIST_FAILURE mutation when API returns an error', (done) => {
+ it('should commit CREATE_LIST_FAILURE mutation when API returns an error', async () => {
jest.spyOn(gqlClient, 'mutate').mockReturnValue(
Promise.resolve({
data: {
@@ -235,22 +263,49 @@ describe('createList', () => {
}),
);
- const state = {
- fullPath: 'gitlab-org',
- boardId: '1',
- boardType: 'group',
- disabled: false,
- boardLists: [{ type: 'closed' }],
+ await actions.createList({ getters, state, commit, dispatch }, { backlog: true });
+
+ expect(commit).toHaveBeenCalledWith(types.CREATE_LIST_FAILURE);
+ });
+
+ it('highlights list and does not re-query if it already exists', async () => {
+ const existingList = {
+ id: 'gid://gitlab/List/1',
+ listType: 'label',
+ title: 'Some label',
+ position: 1,
};
- testAction(
- actions.createList,
- { backlog: true },
- state,
- [{ type: types.CREATE_LIST_FAILURE }],
- [],
- done,
- );
+ getters = {
+ getListByLabelId: jest.fn().mockReturnValue(existingList),
+ };
+
+ await actions.createList({ getters, state, commit, dispatch }, { backlog: true });
+
+ expect(dispatch).toHaveBeenCalledWith('highlightList', existingList.id);
+ expect(dispatch).toHaveBeenCalledTimes(1);
+ expect(commit).not.toHaveBeenCalled();
+ });
+});
+
+describe('fetchLabels', () => {
+ it('should commit mutation RECEIVE_LABELS_SUCCESS on success', async () => {
+ const queryResponse = {
+ data: {
+ group: {
+ labels: {
+ nodes: labels,
+ },
+ },
+ },
+ };
+ jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse);
+
+ await testAction({
+ action: actions.fetchLabels,
+ state: { boardType: 'group' },
+ expectedMutations: [{ type: types.RECEIVE_LABELS_SUCCESS, payload: labels }],
+ });
});
});
@@ -669,65 +724,27 @@ describe('moveIssue', () => {
describe('setAssignees', () => {
const node = { username: 'name' };
- const name = 'username';
const projectPath = 'h/h';
const refPath = `${projectPath}#3`;
const iid = '1';
describe('when succeeds', () => {
- beforeEach(() => {
- jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
- data: { issueSetAssignees: { issue: { assignees: { nodes: [{ ...node }] } } } },
- });
- });
-
- it('calls mutate with the correct values', async () => {
- await actions.setAssignees(
- { commit: () => {}, getters: { activeIssue: { iid, referencePath: refPath } } },
- [name],
- );
-
- expect(gqlClient.mutate).toHaveBeenCalledWith({
- mutation: updateAssignees,
- variables: { iid, assigneeUsernames: [name], projectPath },
- });
- });
-
it('calls the correct mutation with the correct values', (done) => {
testAction(
actions.setAssignees,
- {},
+ [node],
{ activeIssue: { iid, referencePath: refPath }, commit: () => {} },
[
- { type: types.SET_ASSIGNEE_LOADING, payload: true },
{
type: 'UPDATE_ISSUE_BY_ID',
payload: { prop: 'assignees', issueId: undefined, value: [node] },
},
- { type: types.SET_ASSIGNEE_LOADING, payload: false },
],
[],
done,
);
});
});
-
- describe('when fails', () => {
- beforeEach(() => {
- jest.spyOn(gqlClient, 'mutate').mockRejectedValue();
- });
-
- it('calls createFlash', async () => {
- await actions.setAssignees({
- commit: () => {},
- getters: { activeIssue: { iid, referencePath: refPath } },
- });
-
- expect(createFlash).toHaveBeenCalledWith({
- message: 'An error occurred while updating assignees.',
- });
- });
- });
});
describe('createNewIssue', () => {
@@ -1201,6 +1218,40 @@ describe('setSelectedProject', () => {
});
});
+describe('toggleBoardItemMultiSelection', () => {
+ const boardItem = mockIssue;
+
+ it('should commit mutation ADD_BOARD_ITEM_TO_SELECTION if item is not on selection state', () => {
+ testAction(
+ actions.toggleBoardItemMultiSelection,
+ boardItem,
+ { selectedBoardItems: [] },
+ [
+ {
+ type: types.ADD_BOARD_ITEM_TO_SELECTION,
+ payload: boardItem,
+ },
+ ],
+ [],
+ );
+ });
+
+ it('should commit mutation REMOVE_BOARD_ITEM_FROM_SELECTION if item is on selection state', () => {
+ testAction(
+ actions.toggleBoardItemMultiSelection,
+ boardItem,
+ { selectedBoardItems: [mockIssue] },
+ [
+ {
+ type: types.REMOVE_BOARD_ITEM_FROM_SELECTION,
+ payload: boardItem,
+ },
+ ],
+ [],
+ );
+ });
+});
+
describe('fetchBacklog', () => {
expectNotImplemented(actions.fetchBacklog);
});
diff --git a/spec/frontend/boards/stores/getters_spec.js b/spec/frontend/boards/stores/getters_spec.js
index 44b41b5667d..d5a19bf613f 100644
--- a/spec/frontend/boards/stores/getters_spec.js
+++ b/spec/frontend/boards/stores/getters_spec.js
@@ -1,5 +1,5 @@
-import getters from '~/boards/stores/getters';
import { inactiveId } from '~/boards/constants';
+import getters from '~/boards/stores/getters';
import {
mockIssue,
mockIssue2,
@@ -62,6 +62,22 @@ describe('Boards - Getters', () => {
});
});
+ describe('groupPathByIssueId', () => {
+ it('returns group path for the active issue', () => {
+ const mockActiveIssue = {
+ referencePath: 'gitlab-org/gitlab-test#1',
+ };
+ expect(getters.groupPathForActiveIssue({}, { activeIssue: mockActiveIssue })).toEqual(
+ 'gitlab-org',
+ );
+ });
+
+ it('returns empty string as group path when active issue is an empty object', () => {
+ const mockActiveIssue = {};
+ expect(getters.groupPathForActiveIssue({}, { activeIssue: mockActiveIssue })).toEqual('');
+ });
+ });
+
describe('projectPathByIssueId', () => {
it('returns project path for the active issue', () => {
const mockActiveIssue = {
@@ -72,7 +88,7 @@ describe('Boards - Getters', () => {
);
});
- it('returns empty string as project when active issue is an empty object', () => {
+ it('returns empty string as project path when active issue is an empty object', () => {
const mockActiveIssue = {};
expect(getters.projectPathForActiveIssue({}, { activeIssue: mockActiveIssue })).toEqual('');
});
diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js
index c5fe0e22c3c..9423f2ed583 100644
--- a/spec/frontend/boards/stores/mutations_spec.js
+++ b/spec/frontend/boards/stores/mutations_spec.js
@@ -1,7 +1,14 @@
-import mutations from '~/boards/stores/mutations';
import * as types from '~/boards/stores/mutation_types';
+import mutations from '~/boards/stores/mutations';
import defaultState from '~/boards/stores/state';
-import { mockLists, rawIssue, mockIssue, mockIssue2, mockGroupProjects } from '../mock_data';
+import {
+ mockLists,
+ rawIssue,
+ mockIssue,
+ mockIssue2,
+ mockGroupProjects,
+ labels,
+} from '../mock_data';
const expectNotImplemented = (action) => {
it('is not implemented', () => {
@@ -99,13 +106,11 @@ describe('Board Store Mutations', () => {
});
});
- describe('RECEIVE_LABELS_FAILURE', () => {
- it('sets error message', () => {
- mutations.RECEIVE_LABELS_FAILURE(state);
+ describe('RECEIVE_LABELS_SUCCESS', () => {
+ it('sets labels on state', () => {
+ mutations.RECEIVE_LABELS_SUCCESS(state, labels);
- expect(state.error).toEqual(
- 'An error occurred while fetching labels. Please reload the page.',
- );
+ expect(state.labels).toEqual(labels);
});
});
@@ -589,4 +594,27 @@ describe('Board Store Mutations', () => {
expect(state.selectedProject).toEqual(mockGroupProjects[0]);
});
});
+
+ describe('ADD_BOARD_ITEM_TO_SELECTION', () => {
+ it('Should add boardItem to selectedBoardItems state', () => {
+ expect(state.selectedBoardItems).toEqual([]);
+
+ mutations[types.ADD_BOARD_ITEM_TO_SELECTION](state, mockIssue);
+
+ expect(state.selectedBoardItems).toEqual([mockIssue]);
+ });
+ });
+
+ describe('REMOVE_BOARD_ITEM_FROM_SELECTION', () => {
+ it('Should remove boardItem to selectedBoardItems state', () => {
+ state = {
+ ...state,
+ selectedBoardItems: [mockIssue],
+ };
+
+ mutations[types.REMOVE_BOARD_ITEM_FROM_SELECTION](state, mockIssue);
+
+ expect(state.selectedBoardItems).toEqual([]);
+ });
+ });
});