diff options
Diffstat (limited to 'spec/frontend/boards')
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([]); + }); + }); }); |