diff options
Diffstat (limited to 'spec/frontend/boards/components')
23 files changed, 546 insertions, 534 deletions
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'; |