diff options
Diffstat (limited to 'spec/frontend/boards')
23 files changed, 235 insertions, 3646 deletions
diff --git a/spec/frontend/boards/board_card_inner_spec.js b/spec/frontend/boards/board_card_inner_spec.js index 7d3ecc773a6..e0446811f64 100644 --- a/spec/frontend/boards/board_card_inner_spec.js +++ b/spec/frontend/boards/board_card_inner_spec.js @@ -2,6 +2,7 @@ import { GlLabel, GlLoadingIcon, GlTooltip } from '@gitlab/ui'; import { range } from 'lodash'; import Vuex from 'vuex'; import setWindowLocation from 'helpers/set_window_location_helper'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import BoardBlockedIcon from '~/boards/components/board_blocked_icon.vue'; import BoardCardInner from '~/boards/components/board_card_inner.vue'; @@ -44,6 +45,7 @@ describe('Board card component', () => { const findEpicBadgeProgress = () => wrapper.findByTestId('epic-progress'); const findEpicCountablesTotalWeight = () => wrapper.findByTestId('epic-countables-total-weight'); const findEpicProgressTooltip = () => wrapper.findByTestId('epic-progress-tooltip-content'); + const findHiddenIssueIcon = () => wrapper.findByTestId('hidden-icon'); const createStore = ({ isEpicBoard = false, isProjectBoard = false } = {}) => { store = new Vuex.Store({ @@ -72,6 +74,9 @@ describe('Board card component', () => { GlLabel: true, GlLoadingIcon: true, }, + directives: { + GlTooltip: createMockDirective(), + }, mocks: { $apollo: { queries: { @@ -122,6 +127,10 @@ describe('Board card component', () => { expect(wrapper.find('.confidential-icon').exists()).toBe(false); }); + it('does not render hidden issue icon', () => { + expect(findHiddenIssueIcon().exists()).toBe(false); + }); + it('renders issue ID with #', () => { expect(wrapper.find('.board-card-number').text()).toContain(`#${issue.iid}`); }); @@ -184,6 +193,30 @@ describe('Board card component', () => { }); }); + describe('hidden issue', () => { + beforeEach(() => { + wrapper.setProps({ + item: { + ...wrapper.props('item'), + hidden: true, + }, + }); + }); + + it('renders hidden issue icon', () => { + expect(findHiddenIssueIcon().exists()).toBe(true); + }); + + it('displays a tooltip which explains the meaning of the icon', () => { + const tooltip = getBinding(findHiddenIssueIcon().element, 'gl-tooltip'); + + expect(tooltip).toBeDefined(); + expect(findHiddenIssueIcon().attributes('title')).toBe( + 'This issue is hidden because its author has been banned', + ); + }); + }); + describe('with assignee', () => { describe('with avatar', () => { beforeEach(() => { diff --git a/spec/frontend/boards/board_list_deprecated_spec.js b/spec/frontend/boards/board_list_deprecated_spec.js deleted file mode 100644 index b71564f7858..00000000000 --- a/spec/frontend/boards/board_list_deprecated_spec.js +++ /dev/null @@ -1,274 +0,0 @@ -/* global List */ -/* global ListIssue */ -import MockAdapter from 'axios-mock-adapter'; -import Vue from 'vue'; -import waitForPromises from 'helpers/wait_for_promises'; -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'; - -const createComponent = ({ done, listIssueProps = {}, componentProps = {}, listProps = {} }) => { - const el = document.createElement('div'); - - document.body.appendChild(el); - const mock = new MockAdapter(axios); - mock.onAny().reply(boardsMockInterceptor); - boardsStore.create(); - - const BoardListComp = Vue.extend(BoardList); - const list = new List({ ...listObj, ...listProps }); - const issue = new ListIssue({ - title: 'Testing', - id: 1, - iid: 1, - confidential: false, - labels: [], - assignees: [], - ...listIssueProps, - }); - if (!Object.prototype.hasOwnProperty.call(listProps, 'issuesSize')) { - list.issuesSize = 1; - } - list.issues.push(issue); - - const component = new BoardListComp({ - el, - store, - propsData: { - disabled: false, - list, - issues: list.issues, - ...componentProps, - }, - provide: { - groupId: null, - rootPath: '/', - }, - }).$mount(); - - Vue.nextTick(() => { - done(); - }); - - return { component, mock }; -}; - -describe('Board list component', () => { - let mock; - let component; - let getIssues; - function generateIssues(compWrapper) { - for (let i = 1; i < 20; i += 1) { - const issue = { ...compWrapper.list.issues[0] }; - issue.id += i; - compWrapper.list.issues.push(issue); - } - } - - describe('When Expanded', () => { - beforeEach((done) => { - getIssues = jest.spyOn(List.prototype, 'getIssues').mockReturnValue(new Promise(() => {})); - ({ mock, component } = createComponent({ done })); - }); - - afterEach(() => { - mock.restore(); - component.$destroy(); - }); - - it('loads first page of issues', () => { - return waitForPromises().then(() => { - expect(getIssues).toHaveBeenCalled(); - }); - }); - - it('renders component', () => { - expect(component.$el.classList.contains('board-list-component')).toBe(true); - }); - - it('renders loading icon', () => { - component.list.loading = true; - - return Vue.nextTick().then(() => { - expect(component.$el.querySelector('.board-list-loading')).not.toBeNull(); - }); - }); - - it('renders issues', () => { - expect(component.$el.querySelectorAll('.board-card').length).toBe(1); - }); - - it('sets data attribute with issue id', () => { - expect(component.$el.querySelector('.board-card').getAttribute('data-issue-id')).toBe('1'); - }); - - it('shows new issue form', () => { - component.toggleForm(); - - return Vue.nextTick().then(() => { - expect(component.$el.querySelector('.board-new-issue-form')).not.toBeNull(); - - expect(component.$el.querySelector('.is-smaller')).not.toBeNull(); - }); - }); - - it('shows new issue form after eventhub event', () => { - eventHub.$emit(`toggle-issue-form-${component.list.id}`); - - return Vue.nextTick().then(() => { - expect(component.$el.querySelector('.board-new-issue-form')).not.toBeNull(); - - expect(component.$el.querySelector('.is-smaller')).not.toBeNull(); - }); - }); - - it('does not show new issue form for closed list', () => { - component.list.type = 'closed'; - component.toggleForm(); - - return Vue.nextTick().then(() => { - expect(component.$el.querySelector('.board-new-issue-form')).toBeNull(); - }); - }); - - it('shows count list item', () => { - component.showCount = true; - - return Vue.nextTick().then(() => { - expect(component.$el.querySelector('.board-list-count')).not.toBeNull(); - - expect(component.$el.querySelector('.board-list-count').textContent.trim()).toBe( - 'Showing all issues', - ); - }); - }); - - it('sets data attribute with invalid id', () => { - component.showCount = true; - - return Vue.nextTick().then(() => { - expect(component.$el.querySelector('.board-list-count').getAttribute('data-issue-id')).toBe( - '-1', - ); - }); - }); - - it('shows how many more issues to load', () => { - component.showCount = true; - component.list.issuesSize = 20; - - return Vue.nextTick().then(() => { - expect(component.$el.querySelector('.board-list-count').textContent.trim()).toBe( - 'Showing 1 of 20 issues', - ); - }); - }); - - it('loads more issues after scrolling', () => { - jest.spyOn(component.list, 'nextPage').mockImplementation(() => {}); - generateIssues(component); - component.$refs.list.dispatchEvent(new Event('scroll')); - - return waitForPromises().then(() => { - expect(component.list.nextPage).toHaveBeenCalled(); - }); - }); - - it('does not load issues if already loading', () => { - component.list.nextPage = jest - .spyOn(component.list, 'nextPage') - .mockReturnValue(new Promise(() => {})); - - component.onScroll(); - component.onScroll(); - - return waitForPromises().then(() => { - expect(component.list.nextPage).toHaveBeenCalledTimes(1); - }); - }); - - it('shows loading more spinner', () => { - component.showCount = true; - component.list.loadingMore = true; - - return Vue.nextTick().then(() => { - expect(component.$el.querySelector('.board-list-count .gl-spinner')).not.toBeNull(); - }); - }); - }); - - describe('When Collapsed', () => { - beforeEach((done) => { - getIssues = jest.spyOn(List.prototype, 'getIssues').mockReturnValue(new Promise(() => {})); - ({ mock, component } = createComponent({ - done, - listProps: { type: 'closed', collapsed: true, issuesSize: 50 }, - })); - generateIssues(component); - component.scrollHeight = jest.spyOn(component, 'scrollHeight').mockReturnValue(0); - }); - - afterEach(() => { - mock.restore(); - component.$destroy(); - }); - - it('does not load all issues', () => { - return waitForPromises().then(() => { - // Initial getIssues from list constructor - expect(getIssues).toHaveBeenCalledTimes(1); - }); - }); - }); - - describe('max issue count warning', () => { - beforeEach((done) => { - ({ mock, component } = createComponent({ - done, - listProps: { type: 'closed', collapsed: true, issuesSize: 50 }, - })); - }); - - afterEach(() => { - mock.restore(); - component.$destroy(); - }); - - describe('when issue count exceeds max issue count', () => { - it('sets background to bg-danger-100', () => { - component.list.issuesSize = 4; - component.list.maxIssueCount = 3; - - return Vue.nextTick().then(() => { - expect(component.$el.querySelector('.bg-danger-100')).not.toBeNull(); - }); - }); - }); - - describe('when list issue count does NOT exceed list max issue count', () => { - it('does not sets background to bg-danger-100', () => { - component.list.issuesSize = 2; - component.list.maxIssueCount = 3; - - return Vue.nextTick().then(() => { - expect(component.$el.querySelector('.bg-danger-100')).toBeNull(); - }); - }); - }); - - describe('when list max issue count is 0', () => { - it('does not sets background to bg-danger-100', () => { - component.list.maxIssueCount = 0; - - return Vue.nextTick().then(() => { - expect(component.$el.querySelector('.bg-danger-100')).toBeNull(); - }); - }); - }); - }); -}); diff --git a/spec/frontend/boards/board_new_issue_deprecated_spec.js b/spec/frontend/boards/board_new_issue_deprecated_spec.js deleted file mode 100644 index 3beaf870bf5..00000000000 --- a/spec/frontend/boards/board_new_issue_deprecated_spec.js +++ /dev/null @@ -1,211 +0,0 @@ -/* global List */ - -import { mount } from '@vue/test-utils'; -import MockAdapter from 'axios-mock-adapter'; -import Vue from 'vue'; -import Vuex from 'vuex'; -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'; - -Vue.use(Vuex); - -describe('Issue boards new issue form', () => { - let wrapper; - let vm; - let list; - let mock; - let newIssueMock; - const promiseReturn = { - data: { - iid: 100, - }, - }; - - const submitIssue = () => { - const dummySubmitEvent = { - preventDefault() {}, - }; - wrapper.vm.$refs.submitButton = wrapper.find({ ref: 'submitButton' }); - return wrapper.vm.submit(dummySubmitEvent); - }; - - beforeEach(() => { - const BoardNewIssueComp = Vue.extend(boardNewIssue); - - mock = new MockAdapter(axios); - mock.onAny().reply(boardsMockInterceptor); - - boardsStore.create(); - - list = new List(listObj); - - newIssueMock = Promise.resolve(promiseReturn); - jest.spyOn(list, 'newIssue').mockImplementation(() => newIssueMock); - - const store = new Vuex.Store({ - getters: { isGroupBoard: () => false }, - }); - - wrapper = mount(BoardNewIssueComp, { - propsData: { - disabled: false, - list, - }, - store, - provide: { - groupId: null, - }, - }); - - vm = wrapper.vm; - - return Vue.nextTick(); - }); - - afterEach(() => { - wrapper.destroy(); - mock.restore(); - }); - - it('calls submit if submit button is clicked', () => { - jest.spyOn(wrapper.vm, 'submit').mockImplementation(); - vm.title = 'Testing Title'; - - return Vue.nextTick() - .then(submitIssue) - .then(() => { - expect(wrapper.vm.submit).toHaveBeenCalled(); - }); - }); - - it('disables submit button if title is empty', () => { - expect(wrapper.find({ ref: 'submitButton' }).props().disabled).toBe(true); - }); - - it('enables submit button if title is not empty', () => { - wrapper.setData({ title: 'Testing Title' }); - - return Vue.nextTick().then(() => { - expect(wrapper.find({ ref: 'input' }).element.value).toBe('Testing Title'); - expect(wrapper.find({ ref: 'submitButton' }).props().disabled).toBe(false); - }); - }); - - it('clears title after clicking cancel', () => { - wrapper.find({ ref: 'cancelButton' }).trigger('click'); - - return Vue.nextTick().then(() => { - expect(vm.title).toBe(''); - }); - }); - - it('does not create new issue if title is empty', () => { - return submitIssue().then(() => { - expect(list.newIssue).not.toHaveBeenCalled(); - }); - }); - - describe('submit success', () => { - it('creates new issue', () => { - wrapper.setData({ title: 'create issue' }); - - return Vue.nextTick() - .then(submitIssue) - .then(() => { - expect(list.newIssue).toHaveBeenCalled(); - }); - }); - - it('enables button after submit', () => { - jest.spyOn(wrapper.vm, 'submit').mockImplementation(); - wrapper.setData({ title: 'create issue' }); - - return Vue.nextTick() - .then(submitIssue) - .then(() => { - expect(wrapper.vm.$refs.submitButton.props().disabled).toBe(false); - }); - }); - - it('clears title after submit', () => { - wrapper.setData({ title: 'create issue' }); - - return Vue.nextTick() - .then(submitIssue) - .then(() => { - expect(vm.title).toBe(''); - }); - }); - - it('sets detail issue after submit', () => { - expect(boardsStore.detail.issue.title).toBe(undefined); - wrapper.setData({ title: 'create issue' }); - - return Vue.nextTick() - .then(submitIssue) - .then(() => { - expect(boardsStore.detail.issue.title).toBe('create issue'); - }); - }); - - it('sets detail list after submit', () => { - wrapper.setData({ title: 'create issue' }); - - return Vue.nextTick() - .then(submitIssue) - .then(() => { - expect(boardsStore.detail.list.id).toBe(list.id); - }); - }); - - it('sets detail weight after submit', () => { - boardsStore.weightFeatureAvailable = true; - wrapper.setData({ title: 'create issue' }); - - return Vue.nextTick() - .then(submitIssue) - .then(() => { - expect(boardsStore.detail.list.weight).toBe(list.weight); - }); - }); - - it('does not set detail weight after submit', () => { - boardsStore.weightFeatureAvailable = false; - wrapper.setData({ title: 'create issue' }); - - return Vue.nextTick() - .then(submitIssue) - .then(() => { - expect(boardsStore.detail.list.weight).toBe(list.weight); - }); - }); - }); - - describe('submit error', () => { - beforeEach(() => { - newIssueMock = Promise.reject(new Error('My hovercraft is full of eels!')); - vm.title = 'error'; - }); - - it('removes issue', () => { - const lengthBefore = list.issues.length; - return Vue.nextTick() - .then(submitIssue) - .then(() => { - expect(list.issues.length).toBe(lengthBefore); - }); - }); - - it('shows error', () => { - return Vue.nextTick() - .then(submitIssue) - .then(() => { - expect(vm.error).toBe(true); - }); - }); - }); -}); diff --git a/spec/frontend/boards/boards_store_spec.js b/spec/frontend/boards/boards_store_spec.js deleted file mode 100644 index 02881333273..00000000000 --- a/spec/frontend/boards/boards_store_spec.js +++ /dev/null @@ -1,1013 +0,0 @@ -import AxiosMockAdapter from 'axios-mock-adapter'; -import { TEST_HOST } from 'helpers/test_constants'; -import eventHub from '~/boards/eventhub'; - -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'); - -const createTestIssue = () => ({ - title: 'Testing', - id: 1, - iid: 1, - confidential: false, - labels: [], - assignees: [], -}); - -describe('boardsStore', () => { - const dummyResponse = "without type checking this doesn't matter"; - const boardId = 'dummy-board-id'; - const endpoints = { - boardsEndpoint: `${TEST_HOST}/boards`, - listsEndpoint: `${TEST_HOST}/lists`, - bulkUpdatePath: `${TEST_HOST}/bulk/update`, - recentBoardsEndpoint: `${TEST_HOST}/recent/boards`, - }; - - let axiosMock; - - beforeEach(() => { - axiosMock = new AxiosMockAdapter(axios); - boardsStore.setEndpoints({ - ...endpoints, - boardId, - }); - }); - - afterEach(() => { - axiosMock.restore(); - }); - - const setupDefaultResponses = () => { - axiosMock - .onGet(`${endpoints.listsEndpoint}/${listObj.id}/issues?id=${listObj.id}&page=1`) - .reply(200, { issues: [createTestIssue()] }); - axiosMock.onPost(endpoints.listsEndpoint).reply(200, listObj); - axiosMock.onPut(); - }; - - describe('all', () => { - it('makes a request to fetch lists', () => { - axiosMock.onGet(endpoints.listsEndpoint).replyOnce(200, dummyResponse); - const expectedResponse = expect.objectContaining({ data: dummyResponse }); - - return expect(boardsStore.all()).resolves.toEqual(expectedResponse); - }); - - it('fails for error response', () => { - axiosMock.onGet(endpoints.listsEndpoint).replyOnce(500); - - return expect(boardsStore.all()).rejects.toThrow(); - }); - }); - - describe('createList', () => { - const entityType = 'moorhen'; - const entityId = 'quack'; - const expectedRequest = expect.objectContaining({ - data: JSON.stringify({ list: { [entityType]: entityId } }), - }); - - let requestSpy; - - beforeEach(() => { - requestSpy = jest.fn(); - axiosMock.onPost(endpoints.listsEndpoint).replyOnce((config) => requestSpy(config)); - }); - - it('makes a request to create a list', () => { - requestSpy.mockReturnValue([200, dummyResponse]); - const expectedResponse = expect.objectContaining({ data: dummyResponse }); - - return expect(boardsStore.createList(entityId, entityType)) - .resolves.toEqual(expectedResponse) - .then(() => { - expect(requestSpy).toHaveBeenCalledWith(expectedRequest); - }); - }); - - it('fails for error response', () => { - requestSpy.mockReturnValue([500]); - - return expect(boardsStore.createList(entityId, entityType)) - .rejects.toThrow() - .then(() => { - expect(requestSpy).toHaveBeenCalledWith(expectedRequest); - }); - }); - }); - - describe('updateList', () => { - const id = 'David Webb'; - const position = 'unknown'; - const collapsed = false; - const expectedRequest = expect.objectContaining({ - data: JSON.stringify({ list: { position, collapsed } }), - }); - - let requestSpy; - - beforeEach(() => { - requestSpy = jest.fn(); - axiosMock.onPut(`${endpoints.listsEndpoint}/${id}`).replyOnce((config) => requestSpy(config)); - }); - - it('makes a request to update a list position', () => { - requestSpy.mockReturnValue([200, dummyResponse]); - const expectedResponse = expect.objectContaining({ data: dummyResponse }); - - return expect(boardsStore.updateList(id, position, collapsed)) - .resolves.toEqual(expectedResponse) - .then(() => { - expect(requestSpy).toHaveBeenCalledWith(expectedRequest); - }); - }); - - it('fails for error response', () => { - requestSpy.mockReturnValue([500]); - - return expect(boardsStore.updateList(id, position, collapsed)) - .rejects.toThrow() - .then(() => { - expect(requestSpy).toHaveBeenCalledWith(expectedRequest); - }); - }); - }); - - describe('destroyList', () => { - const id = '-42'; - - let requestSpy; - - beforeEach(() => { - requestSpy = jest.fn(); - axiosMock - .onDelete(`${endpoints.listsEndpoint}/${id}`) - .replyOnce((config) => requestSpy(config)); - }); - - it('makes a request to delete a list', () => { - requestSpy.mockReturnValue([200, dummyResponse]); - const expectedResponse = expect.objectContaining({ data: dummyResponse }); - - return expect(boardsStore.destroyList(id)) - .resolves.toEqual(expectedResponse) - .then(() => { - expect(requestSpy).toHaveBeenCalled(); - }); - }); - - it('fails for error response', () => { - requestSpy.mockReturnValue([500]); - - return expect(boardsStore.destroyList(id)) - .rejects.toThrow() - .then(() => { - expect(requestSpy).toHaveBeenCalled(); - }); - }); - }); - - describe('saveList', () => { - let list; - - beforeEach(() => { - list = new List(listObj); - setupDefaultResponses(); - }); - - it('makes a request to save a list', () => { - const expectedResponse = expect.objectContaining({ issues: [createTestIssue()] }); - const expectedListValue = { - id: listObj.id, - position: listObj.position, - type: listObj.list_type, - label: listObj.label, - }; - expect(list.id).toBe(listObj.id); - expect(list.position).toBe(listObj.position); - expect(list).toMatchObject(expectedListValue); - - return expect(boardsStore.saveList(list)).resolves.toEqual(expectedResponse); - }); - }); - - describe('getListIssues', () => { - let list; - - beforeEach(() => { - list = new List(listObj); - setupDefaultResponses(); - }); - - it('makes a request to get issues', () => { - const expectedResponse = expect.objectContaining({ issues: [createTestIssue()] }); - expect(list.issues).toEqual([]); - - return expect(boardsStore.getListIssues(list, true)).resolves.toEqual(expectedResponse); - }); - }); - - describe('getIssuesForList', () => { - const id = 'TOO-MUCH'; - const url = `${endpoints.listsEndpoint}/${id}/issues?id=${id}`; - - it('makes a request to fetch list issues', () => { - axiosMock.onGet(url).replyOnce(200, dummyResponse); - const expectedResponse = expect.objectContaining({ data: dummyResponse }); - - return expect(boardsStore.getIssuesForList(id)).resolves.toEqual(expectedResponse); - }); - - it('makes a request to fetch list issues with filter', () => { - const filter = { algal: 'scrubber' }; - axiosMock.onGet(`${url}&algal=scrubber`).replyOnce(200, dummyResponse); - const expectedResponse = expect.objectContaining({ data: dummyResponse }); - - return expect(boardsStore.getIssuesForList(id, filter)).resolves.toEqual(expectedResponse); - }); - - it('fails for error response', () => { - axiosMock.onGet(url).replyOnce(500); - - return expect(boardsStore.getIssuesForList(id)).rejects.toThrow(); - }); - }); - - describe('moveIssue', () => { - const urlRoot = 'potato'; - const id = 'over 9000'; - const fromListId = 'left'; - const toListId = 'right'; - const moveBeforeId = 'up'; - const moveAfterId = 'down'; - const expectedRequest = expect.objectContaining({ - data: JSON.stringify({ - from_list_id: fromListId, - to_list_id: toListId, - move_before_id: moveBeforeId, - move_after_id: moveAfterId, - }), - }); - - let requestSpy; - - beforeAll(() => { - global.gon.relative_url_root = urlRoot; - }); - - afterAll(() => { - delete global.gon.relative_url_root; - }); - - beforeEach(() => { - requestSpy = jest.fn(); - axiosMock - .onPut(`${urlRoot}/-/boards/${boardId}/issues/${id}`) - .replyOnce((config) => requestSpy(config)); - }); - - it('makes a request to move an issue between lists', () => { - requestSpy.mockReturnValue([200, dummyResponse]); - const expectedResponse = expect.objectContaining({ data: dummyResponse }); - - return expect(boardsStore.moveIssue(id, fromListId, toListId, moveBeforeId, moveAfterId)) - .resolves.toEqual(expectedResponse) - .then(() => { - expect(requestSpy).toHaveBeenCalledWith(expectedRequest); - }); - }); - - it('fails for error response', () => { - requestSpy.mockReturnValue([500]); - - return expect(boardsStore.moveIssue(id, fromListId, toListId, moveBeforeId, moveAfterId)) - .rejects.toThrow() - .then(() => { - expect(requestSpy).toHaveBeenCalledWith(expectedRequest); - }); - }); - }); - - describe('newIssue', () => { - const id = 1; - const issue = { some: 'issue data' }; - const url = `${endpoints.listsEndpoint}/${id}/issues`; - const expectedRequest = expect.objectContaining({ - data: JSON.stringify({ - issue, - }), - }); - - let requestSpy; - - beforeEach(() => { - requestSpy = jest.fn(); - axiosMock.onPost(url).replyOnce((config) => requestSpy(config)); - }); - - it('makes a request to create a new issue', () => { - requestSpy.mockReturnValue([200, dummyResponse]); - const expectedResponse = expect.objectContaining({ data: dummyResponse }); - - return expect(boardsStore.newIssue(id, issue)) - .resolves.toEqual(expectedResponse) - .then(() => { - expect(requestSpy).toHaveBeenCalledWith(expectedRequest); - }); - }); - - it('fails for error response', () => { - requestSpy.mockReturnValue([500]); - - return expect(boardsStore.newIssue(id, issue)) - .rejects.toThrow() - .then(() => { - expect(requestSpy).toHaveBeenCalledWith(expectedRequest); - }); - }); - }); - - describe('getBacklog', () => { - const urlRoot = 'deep'; - const url = `${urlRoot}/-/boards/${boardId}/issues.json?not=relevant`; - const requestParams = { - not: 'relevant', - }; - - beforeAll(() => { - global.gon.relative_url_root = urlRoot; - }); - - afterAll(() => { - delete global.gon.relative_url_root; - }); - - it('makes a request to fetch backlog', () => { - axiosMock.onGet(url).replyOnce(200, dummyResponse); - const expectedResponse = expect.objectContaining({ data: dummyResponse }); - - return expect(boardsStore.getBacklog(requestParams)).resolves.toEqual(expectedResponse); - }); - - it('fails for error response', () => { - axiosMock.onGet(url).replyOnce(500); - - return expect(boardsStore.getBacklog(requestParams)).rejects.toThrow(); - }); - }); - - describe('bulkUpdate', () => { - const issueIds = [1, 2, 3]; - const extraData = { moar: 'data' }; - const expectedRequest = expect.objectContaining({ - data: JSON.stringify({ - update: { - ...extraData, - issuable_ids: '1,2,3', - }, - }), - }); - - let requestSpy; - - beforeEach(() => { - requestSpy = jest.fn(); - axiosMock.onPost(endpoints.bulkUpdatePath).replyOnce((config) => requestSpy(config)); - }); - - it('makes a request to create a list', () => { - requestSpy.mockReturnValue([200, dummyResponse]); - const expectedResponse = expect.objectContaining({ data: dummyResponse }); - - return expect(boardsStore.bulkUpdate(issueIds, extraData)) - .resolves.toEqual(expectedResponse) - .then(() => { - expect(requestSpy).toHaveBeenCalledWith(expectedRequest); - }); - }); - - it('fails for error response', () => { - requestSpy.mockReturnValue([500]); - - return expect(boardsStore.bulkUpdate(issueIds, extraData)) - .rejects.toThrow() - .then(() => { - expect(requestSpy).toHaveBeenCalledWith(expectedRequest); - }); - }); - }); - - describe('getIssueInfo', () => { - const dummyEndpoint = `${TEST_HOST}/some/where`; - - it('makes a request to the given endpoint', () => { - axiosMock.onGet(dummyEndpoint).replyOnce(200, dummyResponse); - const expectedResponse = expect.objectContaining({ data: dummyResponse }); - - return expect(boardsStore.getIssueInfo(dummyEndpoint)).resolves.toEqual(expectedResponse); - }); - - it('fails for error response', () => { - axiosMock.onGet(dummyEndpoint).replyOnce(500); - - return expect(boardsStore.getIssueInfo(dummyEndpoint)).rejects.toThrow(); - }); - }); - - describe('toggleIssueSubscription', () => { - const dummyEndpoint = `${TEST_HOST}/some/where`; - - it('makes a request to the given endpoint', () => { - axiosMock.onPost(dummyEndpoint).replyOnce(200, dummyResponse); - const expectedResponse = expect.objectContaining({ data: dummyResponse }); - - return expect(boardsStore.toggleIssueSubscription(dummyEndpoint)).resolves.toEqual( - expectedResponse, - ); - }); - - it('fails for error response', () => { - axiosMock.onPost(dummyEndpoint).replyOnce(500); - - return expect(boardsStore.toggleIssueSubscription(dummyEndpoint)).rejects.toThrow(); - }); - }); - - describe('recentBoards', () => { - const url = `${endpoints.recentBoardsEndpoint}.json`; - - it('makes a request to fetch all boards', () => { - axiosMock.onGet(url).replyOnce(200, dummyResponse); - const expectedResponse = expect.objectContaining({ data: dummyResponse }); - - return expect(boardsStore.recentBoards()).resolves.toEqual(expectedResponse); - }); - - it('fails for error response', () => { - axiosMock.onGet(url).replyOnce(500); - - return expect(boardsStore.recentBoards()).rejects.toThrow(); - }); - }); - - describe('when created', () => { - beforeEach(() => { - setupDefaultResponses(); - - jest.spyOn(boardsStore, 'moveIssue').mockReturnValue(Promise.resolve()); - jest.spyOn(boardsStore, 'moveMultipleIssues').mockReturnValue(Promise.resolve()); - - boardsStore.create(); - }); - - it('starts with a blank state', () => { - expect(boardsStore.state.lists.length).toBe(0); - }); - - describe('addList', () => { - it('sorts by position', () => { - boardsStore.addList({ position: 2 }); - boardsStore.addList({ position: 1 }); - - expect(boardsStore.state.lists.map(({ position }) => position)).toEqual([1, 2]); - }); - }); - - describe('toggleFilter', () => { - const dummyFilter = 'x=42'; - let updateTokensSpy; - - beforeEach(() => { - updateTokensSpy = jest.fn(); - eventHub.$once('updateTokens', updateTokensSpy); - - // prevent using window.history - jest.spyOn(boardsStore, 'updateFiltersUrl').mockReturnValue(); - }); - - it('adds the filter if it is not present', () => { - boardsStore.filter.path = 'something'; - - boardsStore.toggleFilter(dummyFilter); - - expect(boardsStore.filter.path).toEqual(`something&${dummyFilter}`); - expect(updateTokensSpy).toHaveBeenCalled(); - expect(boardsStore.updateFiltersUrl).toHaveBeenCalled(); - }); - - it('removes the filter if it is present', () => { - boardsStore.filter.path = `something&${dummyFilter}`; - - boardsStore.toggleFilter(dummyFilter); - - expect(boardsStore.filter.path).toEqual('something'); - expect(updateTokensSpy).toHaveBeenCalled(); - expect(boardsStore.updateFiltersUrl).toHaveBeenCalled(); - }); - }); - - describe('lists', () => { - it('creates new list without persisting to DB', () => { - expect(boardsStore.state.lists.length).toBe(0); - - boardsStore.addList(listObj); - - expect(boardsStore.state.lists.length).toBe(1); - }); - - it('finds list by ID', () => { - boardsStore.addList(listObj); - const list = boardsStore.findList('id', listObj.id); - - expect(list.id).toBe(listObj.id); - }); - - it('finds list by type', () => { - boardsStore.addList(listObj); - const list = boardsStore.findList('type', 'label'); - - expect(list).toBeDefined(); - }); - - it('finds list by label ID', () => { - boardsStore.addList(listObj); - const list = boardsStore.findListByLabelId(listObj.label.id); - - expect(list.id).toBe(listObj.id); - }); - - it('gets issue when new list added', () => { - boardsStore.addList(listObj); - const list = boardsStore.findList('id', listObj.id); - - expect(boardsStore.state.lists.length).toBe(1); - - return axios.waitForAll().then(() => { - expect(list.issues.length).toBe(1); - expect(list.issues[0].id).toBe(1); - }); - }); - - it('persists new list', () => { - boardsStore.new({ - title: 'Test', - list_type: 'label', - label: { - id: 1, - title: 'Testing', - color: 'red', - description: 'testing;', - }, - }); - - expect(boardsStore.state.lists.length).toBe(1); - - return axios.waitForAll().then(() => { - const list = boardsStore.findList('id', listObj.id); - - expect(list).toEqual( - expect.objectContaining({ - id: listObj.id, - position: 0, - }), - ); - }); - }); - - it('removes list from state', () => { - boardsStore.addList(listObj); - - expect(boardsStore.state.lists.length).toBe(1); - - boardsStore.removeList(listObj.id); - - expect(boardsStore.state.lists.length).toBe(0); - }); - - it('moves the position of lists', () => { - const listOne = boardsStore.addList(listObj); - boardsStore.addList(listObjDuplicate); - - expect(boardsStore.state.lists.length).toBe(2); - - boardsStore.moveList(listOne, [listObjDuplicate.id, listObj.id]); - - expect(listOne.position).toBe(1); - }); - - it('moves an issue from one list to another', () => { - const listOne = boardsStore.addList(listObj); - const listTwo = boardsStore.addList(listObjDuplicate); - - expect(boardsStore.state.lists.length).toBe(2); - - return axios.waitForAll().then(() => { - expect(listOne.issues.length).toBe(1); - expect(listTwo.issues.length).toBe(1); - - boardsStore.moveIssueToList(listOne, listTwo, listOne.findIssue(1)); - - expect(listOne.issues.length).toBe(0); - expect(listTwo.issues.length).toBe(1); - }); - }); - - it('moves an issue from backlog to a list', () => { - const backlog = boardsStore.addList({ - ...listObj, - list_type: 'backlog', - }); - const listTwo = boardsStore.addList(listObjDuplicate); - - expect(boardsStore.state.lists.length).toBe(2); - - return axios.waitForAll().then(() => { - expect(backlog.issues.length).toBe(1); - expect(listTwo.issues.length).toBe(1); - - boardsStore.moveIssueToList(backlog, listTwo, backlog.findIssue(1)); - - expect(backlog.issues.length).toBe(0); - expect(listTwo.issues.length).toBe(1); - }); - }); - - it('moves issue to top of another list', () => { - const listOne = boardsStore.addList(listObj); - const listTwo = boardsStore.addList(listObjDuplicate); - - expect(boardsStore.state.lists.length).toBe(2); - - return axios.waitForAll().then(() => { - listOne.issues[0].id = 2; - - expect(listOne.issues.length).toBe(1); - expect(listTwo.issues.length).toBe(1); - - boardsStore.moveIssueToList(listOne, listTwo, listOne.findIssue(2), 0); - - expect(listOne.issues.length).toBe(0); - expect(listTwo.issues.length).toBe(2); - expect(listTwo.issues[0].id).toBe(2); - expect(boardsStore.moveIssue).toHaveBeenCalledWith(2, listOne.id, listTwo.id, null, 1); - }); - }); - - it('moves issue to bottom of another list', () => { - const listOne = boardsStore.addList(listObj); - const listTwo = boardsStore.addList(listObjDuplicate); - - expect(boardsStore.state.lists.length).toBe(2); - - return axios.waitForAll().then(() => { - listOne.issues[0].id = 2; - - expect(listOne.issues.length).toBe(1); - expect(listTwo.issues.length).toBe(1); - - boardsStore.moveIssueToList(listOne, listTwo, listOne.findIssue(2), 1); - - expect(listOne.issues.length).toBe(0); - expect(listTwo.issues.length).toBe(2); - expect(listTwo.issues[1].id).toBe(2); - expect(boardsStore.moveIssue).toHaveBeenCalledWith(2, listOne.id, listTwo.id, 1, null); - }); - }); - - it('moves issue in list', () => { - const issue = new ListIssue({ - title: 'Testing', - id: 2, - iid: 2, - confidential: false, - labels: [], - assignees: [], - }); - const list = boardsStore.addList(listObj); - - return axios.waitForAll().then(() => { - list.addIssue(issue); - - expect(list.issues.length).toBe(2); - - boardsStore.moveIssueInList(list, issue, 0, 1, [1, 2]); - - expect(list.issues[0].id).toBe(2); - expect(boardsStore.moveIssue).toHaveBeenCalledWith(2, null, null, 1, null); - }); - }); - }); - - describe('setListDetail', () => { - it('sets the list detail', () => { - boardsStore.detail.list = 'not a list'; - - const dummyValue = 'new list'; - boardsStore.setListDetail(dummyValue); - - expect(boardsStore.detail.list).toEqual(dummyValue); - }); - }); - - describe('clearDetailIssue', () => { - it('resets issue details', () => { - boardsStore.detail.issue = 'something'; - - boardsStore.clearDetailIssue(); - - expect(boardsStore.detail.issue).toEqual({}); - }); - }); - - describe('setIssueDetail', () => { - it('sets issue details', () => { - boardsStore.detail.issue = 'some details'; - - const dummyValue = 'new details'; - boardsStore.setIssueDetail(dummyValue); - - expect(boardsStore.detail.issue).toEqual(dummyValue); - }); - }); - - describe('startMoving', () => { - it('stores list and issue', () => { - const dummyIssue = 'some issue'; - const dummyList = 'some list'; - - boardsStore.startMoving(dummyList, dummyIssue); - - expect(boardsStore.moving.issue).toEqual(dummyIssue); - expect(boardsStore.moving.list).toEqual(dummyList); - }); - }); - - describe('setTimeTrackingLimitToHours', () => { - it('sets the timeTracking.LimitToHours option', () => { - boardsStore.timeTracking.limitToHours = false; - - boardsStore.setTimeTrackingLimitToHours('true'); - - expect(boardsStore.timeTracking.limitToHours).toEqual(true); - }); - }); - - describe('setCurrentBoard', () => { - const dummyBoard = 'hoverboard'; - - it('sets the current board', () => { - const { state } = boardsStore; - state.currentBoard = null; - - boardsStore.setCurrentBoard(dummyBoard); - - expect(state.currentBoard).toEqual(dummyBoard); - }); - }); - - describe('toggleMultiSelect', () => { - let basicIssueObj; - - beforeAll(() => { - basicIssueObj = { id: 987654 }; - }); - - afterEach(() => { - boardsStore.clearMultiSelect(); - }); - - it('adds issue when not present', () => { - boardsStore.toggleMultiSelect(basicIssueObj); - - const selectedIds = boardsStore.multiSelect.list.map(({ id }) => id); - - expect(selectedIds.includes(basicIssueObj.id)).toEqual(true); - }); - - it('removes issue when issue is present', () => { - boardsStore.toggleMultiSelect(basicIssueObj); - let selectedIds = boardsStore.multiSelect.list.map(({ id }) => id); - - expect(selectedIds.includes(basicIssueObj.id)).toEqual(true); - - boardsStore.toggleMultiSelect(basicIssueObj); - selectedIds = boardsStore.multiSelect.list.map(({ id }) => id); - - expect(selectedIds.includes(basicIssueObj.id)).toEqual(false); - }); - }); - - describe('clearMultiSelect', () => { - it('clears all the multi selected issues', () => { - const issue1 = { id: 12345 }; - const issue2 = { id: 12346 }; - - boardsStore.toggleMultiSelect(issue1); - boardsStore.toggleMultiSelect(issue2); - - expect(boardsStore.multiSelect.list.length).toEqual(2); - - boardsStore.clearMultiSelect(); - - expect(boardsStore.multiSelect.list.length).toEqual(0); - }); - }); - - describe('moveMultipleIssuesToList', () => { - it('move issues on the new index', () => { - const listOne = boardsStore.addList(listObj); - const listTwo = boardsStore.addList(listObjDuplicate); - - expect(boardsStore.state.lists.length).toBe(2); - - return axios.waitForAll().then(() => { - expect(listOne.issues.length).toBe(1); - expect(listTwo.issues.length).toBe(1); - - boardsStore.moveMultipleIssuesToList({ - listFrom: listOne, - listTo: listTwo, - issues: listOne.issues, - newIndex: 0, - }); - - expect(listTwo.issues.length).toBe(1); - }); - }); - }); - - describe('moveMultipleIssuesInList', () => { - it('moves multiple issues in list', () => { - const issueObj = { - title: 'Issue #1', - id: 12345, - iid: 2, - confidential: false, - labels: [], - assignees: [], - }; - const issue1 = new ListIssue(issueObj); - const issue2 = new ListIssue({ - ...issueObj, - title: 'Issue #2', - id: 12346, - }); - - const list = boardsStore.addList(listObj); - - return axios.waitForAll().then(() => { - list.addIssue(issue1); - list.addIssue(issue2); - - expect(list.issues.length).toBe(3); - expect(list.issues[0].id).not.toBe(issue2.id); - - boardsStore.moveMultipleIssuesInList({ - list, - issues: [issue1, issue2], - oldIndicies: [0], - newIndex: 1, - idArray: [1, 12345, 12346], - }); - - expect(list.issues[0].id).toBe(issue1.id); - - expect(boardsStore.moveMultipleIssues).toHaveBeenCalledWith({ - ids: [issue1.id, issue2.id], - fromListId: null, - toListId: null, - moveBeforeId: 1, - moveAfterId: null, - }); - }); - }); - }); - - describe('addListIssue', () => { - let list; - const issue1 = new ListIssue({ - title: 'Testing', - id: 2, - iid: 2, - confidential: false, - labels: [ - { - color: '#ff0000', - description: 'testing;', - id: 5000, - priority: undefined, - textColor: 'white', - title: 'Test', - }, - ], - assignees: [], - }); - const issue2 = new ListIssue({ - title: 'Testing', - id: 1, - iid: 1, - confidential: false, - labels: [ - { - id: 1, - title: 'test', - color: 'red', - description: 'testing', - }, - ], - assignees: [ - { - id: 1, - name: 'name', - username: 'username', - avatar_url: 'http://avatar_url', - }, - ], - real_path: 'path/to/issue', - }); - - beforeEach(() => { - list = new List(listObj); - list.addIssue(issue1); - setupDefaultResponses(); - }); - - it('adds issues that are not already on the list', () => { - expect(list.findIssue(issue2.id)).toBe(undefined); - expect(list.issues).toEqual([issue1]); - - boardsStore.addListIssue(list, issue2); - expect(list.findIssue(issue2.id)).toBe(issue2); - expect(list.issues.length).toBe(2); - expect(list.issues).toEqual([issue1, issue2]); - }); - }); - - describe('updateIssue', () => { - let issue; - let patchSpy; - - beforeEach(() => { - issue = new ListIssue({ - title: 'Testing', - id: 1, - iid: 1, - confidential: false, - labels: [ - { - id: 1, - title: 'test', - color: 'red', - description: 'testing', - }, - ], - assignees: [ - { - id: 1, - name: 'name', - username: 'username', - avatar_url: 'http://avatar_url', - }, - ], - real_path: 'path/to/issue', - }); - - patchSpy = jest.fn().mockReturnValue([200, { labels: [] }]); - axiosMock.onPatch(`path/to/issue.json`).reply(({ data }) => patchSpy(JSON.parse(data))); - }); - - it('passes assignee ids when there are assignees', () => { - boardsStore.updateIssue(issue); - return boardsStore.updateIssue(issue).then(() => { - expect(patchSpy).toHaveBeenCalledWith({ - issue: { - milestone_id: null, - assignee_ids: [1], - label_ids: [1], - }, - }); - }); - }); - - it('passes assignee ids of [0] when there are no assignees', () => { - issue.removeAllAssignees(); - - return boardsStore.updateIssue(issue).then(() => { - expect(patchSpy).toHaveBeenCalledWith({ - issue: { - milestone_id: null, - assignee_ids: [0], - label_ids: [1], - }, - }); - }); - }); - }); - }); -}); diff --git a/spec/frontend/boards/components/board_add_new_column_spec.js b/spec/frontend/boards/components/board_add_new_column_spec.js index 61f210f566b..5fae1c4359f 100644 --- a/spec/frontend/boards/components/board_add_new_column_spec.js +++ b/spec/frontend/boards/components/board_add_new_column_spec.js @@ -48,7 +48,6 @@ describe('Board card layout', () => { ...actions, }, getters: { - shouldUseGraphQL: () => true, getListByLabelId: () => getListByLabelId, }, state: { diff --git a/spec/frontend/boards/components/board_app_spec.js b/spec/frontend/boards/components/board_app_spec.js new file mode 100644 index 00000000000..dee097bfb08 --- /dev/null +++ b/spec/frontend/boards/components/board_app_spec.js @@ -0,0 +1,54 @@ +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; +import Vuex from 'vuex'; + +import BoardApp from '~/boards/components/board_app.vue'; + +describe('BoardApp', () => { + let wrapper; + let store; + + Vue.use(Vuex); + + const createStore = ({ mockGetters = {} } = {}) => { + store = new Vuex.Store({ + state: {}, + actions: { + performSearch: jest.fn(), + }, + getters: { + isSidebarOpen: () => true, + ...mockGetters, + }, + }); + }; + + const createComponent = ({ provide = { disabled: true } } = {}) => { + wrapper = shallowMount(BoardApp, { + store, + provide: { + ...provide, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + store = null; + }); + + it("should have 'is-compact' class when sidebar is open", () => { + createStore(); + createComponent(); + + expect(wrapper.classes()).toContain('is-compact'); + }); + + it("should not have 'is-compact' class when sidebar is closed", () => { + createStore({ mockGetters: { isSidebarOpen: () => false } }); + createComponent(); + + expect(wrapper.classes()).not.toContain('is-compact'); + }); +}); diff --git a/spec/frontend/boards/components/board_card_deprecated_spec.js b/spec/frontend/boards/components/board_card_deprecated_spec.js deleted file mode 100644 index 266cbc7106d..00000000000 --- a/spec/frontend/boards/components/board_card_deprecated_spec.js +++ /dev/null @@ -1,219 +0,0 @@ -/* global List */ -/* global ListAssignee */ -/* global ListLabel */ - -import { mount } from '@vue/test-utils'; - -import MockAdapter from 'axios-mock-adapter'; -import waitForPromises from 'helpers/wait_for_promises'; -import BoardCardDeprecated from '~/boards/components/board_card_deprecated.vue'; -import issueCardInner from '~/boards/components/issue_card_inner_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 sidebarEventHub from '~/sidebar/event_hub'; -import '~/boards/models/label'; -import '~/boards/models/assignee'; -import '~/boards/models/list'; -import userAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; -import { listObj, boardsMockInterceptor, setMockEndpoints } from '../mock_data'; - -describe('BoardCard', () => { - let wrapper; - let mock; - let list; - - const findIssueCardInner = () => wrapper.find(issueCardInner); - const findUserAvatarLink = () => wrapper.find(userAvatarLink); - - // this particular mount component needs to be used after the root beforeEach because it depends on list being initialized - const mountComponent = (propsData) => { - wrapper = mount(BoardCardDeprecated, { - stubs: { - issueCardInner, - }, - store, - propsData: { - list, - issue: list.issues[0], - disabled: false, - index: 0, - ...propsData, - }, - provide: { - groupId: null, - rootPath: '/', - scopedLabelsAvailable: false, - }, - }); - }; - - const setupData = async () => { - 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', - }); - await waitForPromises(); - - 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(); - }); - - it('when details issue is empty does not show the element', () => { - mountComponent(); - expect(wrapper.find('[data-testid="board_card"').classes()).not.toContain('is-active'); - }); - - it('when detailIssue is equal to card issue shows the element', () => { - [boardsStore.detail.issue] = list.issues; - mountComponent(); - - expect(wrapper.classes()).toContain('is-active'); - }); - - it('when multiSelect does not contain issue removes multi select class', () => { - mountComponent(); - expect(wrapper.classes()).not.toContain('multi-select'); - }); - - it('when multiSelect contain issue add multi select class', () => { - boardsStore.multiSelect.list = [list.issues[0]]; - mountComponent(); - - expect(wrapper.classes()).toContain('multi-select'); - }); - - it('adds user-can-drag class if not disabled', () => { - mountComponent(); - expect(wrapper.classes()).toContain('user-can-drag'); - }); - - it('does not add user-can-drag class disabled', () => { - mountComponent({ disabled: true }); - - expect(wrapper.classes()).not.toContain('user-can-drag'); - }); - - it('does not add disabled class', () => { - mountComponent(); - expect(wrapper.classes()).not.toContain('is-disabled'); - }); - - it('adds disabled class is disabled is true', () => { - mountComponent({ disabled: true }); - - expect(wrapper.classes()).toContain('is-disabled'); - }); - - describe('mouse events', () => { - it('does not set detail issue if showDetail is false', () => { - mountComponent(); - expect(boardsStore.detail.issue).toEqual({}); - }); - - it('does not set detail issue if link is clicked', () => { - mountComponent(); - findIssueCardInner().find('a').trigger('mouseup'); - - expect(boardsStore.detail.issue).toEqual({}); - }); - - it('does not set detail issue if img is clicked', () => { - mountComponent({ - issue: { - ...list.issues[0], - assignees: [ - new ListAssignee({ - id: 1, - name: 'testing 123', - username: 'test', - avatar: 'test_image', - }), - ], - }, - }); - - findUserAvatarLink().trigger('mouseup'); - - expect(boardsStore.detail.issue).toEqual({}); - }); - - it('does not set detail issue if showDetail is false after mouseup', () => { - mountComponent(); - wrapper.trigger('mouseup'); - - expect(boardsStore.detail.issue).toEqual({}); - }); - - it('sets detail issue to card issue on mouse up', () => { - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - - mountComponent(); - - wrapper.trigger('mousedown'); - wrapper.trigger('mouseup'); - - expect(eventHub.$emit).toHaveBeenCalledWith('newDetailIssue', wrapper.vm.issue, false); - expect(boardsStore.detail.list).toEqual(wrapper.vm.list); - }); - - it('resets detail issue to empty if already set', () => { - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - const [issue] = list.issues; - boardsStore.detail.issue = issue; - mountComponent(); - - wrapper.trigger('mousedown'); - wrapper.trigger('mouseup'); - - expect(eventHub.$emit).toHaveBeenCalledWith('clearDetailIssue', false); - }); - }); - - describe('sidebarHub events', () => { - it('closes all sidebars before showing an issue if no issues are opened', () => { - jest.spyOn(sidebarEventHub, '$emit').mockImplementation(() => {}); - boardsStore.detail.issue = {}; - mountComponent(); - - // sets conditional so that event is emitted. - wrapper.trigger('mousedown'); - - wrapper.trigger('mouseup'); - - expect(sidebarEventHub.$emit).toHaveBeenCalledWith('sidebar.closeAll'); - }); - - it('it does not closes all sidebars before showing an issue if an issue is opened', () => { - jest.spyOn(sidebarEventHub, '$emit').mockImplementation(() => {}); - const [issue] = list.issues; - boardsStore.detail.issue = issue; - mountComponent(); - - wrapper.trigger('mousedown'); - - expect(sidebarEventHub.$emit).not.toHaveBeenCalledWith('sidebar.closeAll'); - }); - }); -}); diff --git a/spec/frontend/boards/components/board_card_layout_deprecated_spec.js b/spec/frontend/boards/components/board_card_layout_deprecated_spec.js deleted file mode 100644 index 9853c9f434f..00000000000 --- a/spec/frontend/boards/components/board_card_layout_deprecated_spec.js +++ /dev/null @@ -1,158 +0,0 @@ -/* 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_deprecated.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_column_deprecated_spec.js b/spec/frontend/boards/components/board_column_deprecated_spec.js deleted file mode 100644 index e6d65e48c3f..00000000000 --- a/spec/frontend/boards/components/board_column_deprecated_spec.js +++ /dev/null @@ -1,106 +0,0 @@ -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 { ListType } from '~/boards/constants'; -import List from '~/boards/models/list'; -import axios from '~/lib/utils/axios_utils'; - -describe('Board Column Component', () => { - let wrapper; - let axiosMock; - - beforeEach(() => { - window.gon = {}; - axiosMock = new AxiosMockAdapter(axios); - axiosMock.onGet(`${TEST_HOST}/lists/1/issues`).reply(200, { issues: [] }); - }); - - afterEach(() => { - axiosMock.restore(); - - wrapper.destroy(); - - localStorage.clear(); - }); - - const createComponent = ({ - listType = ListType.backlog, - collapsed = false, - highlighted = false, - withLocalStorage = true, - } = {}) => { - const boardId = '1'; - - const listMock = { - ...listObj, - list_type: listType, - highlighted, - collapsed, - }; - - if (listType === ListType.assignee) { - delete listMock.label; - listMock.user = {}; - } - - // Making List reactive - const list = Vue.observable(new List(listMock)); - - if (withLocalStorage) { - localStorage.setItem( - `boards.${boardId}.${list.type}.${list.id}.expanded`, - (!collapsed).toString(), - ); - } - - wrapper = shallowMount(Board, { - propsData: { - boardId, - disabled: false, - list, - }, - provide: { - boardId, - }, - }); - }; - - const isExpandable = () => wrapper.classes('is-expandable'); - const isCollapsed = () => wrapper.classes('is-collapsed'); - - describe('Given different list types', () => { - it('is expandable when List Type is `backlog`', () => { - createComponent({ listType: ListType.backlog }); - - expect(isExpandable()).toBe(true); - }); - }); - - describe('expanded / collapsed column', () => { - it('has class is-collapsed when list is collapsed', () => { - createComponent({ collapsed: false }); - - expect(wrapper.vm.list.isExpanded).toBe(true); - }); - - it('does not have class is-collapsed when list is expanded', () => { - createComponent({ collapsed: true }); - - 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_content_spec.js b/spec/frontend/boards/components/board_content_spec.js index 5a799b6388e..f535679b8a0 100644 --- a/spec/frontend/boards/components/board_content_spec.js +++ b/spec/frontend/boards/components/board_content_spec.js @@ -5,9 +5,10 @@ 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 BoardColumn from '~/boards/components/board_column.vue'; import BoardContent from '~/boards/components/board_content.vue'; -import { mockLists, mockListsWithModel } from '../mock_data'; +import BoardContentSidebar from '~/boards/components/board_content_sidebar.vue'; +import { mockLists } from '../mock_data'; Vue.use(Vuex); @@ -23,6 +24,7 @@ describe('BoardContent', () => { isShowingEpicsSwimlanes: false, boardLists: mockLists, error: undefined, + issuableType: 'issue', }; const createStore = (state = defaultState) => { @@ -33,25 +35,19 @@ describe('BoardContent', () => { }); }; - const createComponent = ({ - state, - props = {}, - graphqlBoardListsEnabled = false, - canAdminList = true, - } = {}) => { + const createComponent = ({ state, props = {}, canAdminList = true } = {}) => { const store = createStore({ ...defaultState, ...state, }); wrapper = shallowMount(BoardContent, { propsData: { - lists: mockListsWithModel, + lists: mockLists, disabled: false, ...props, }, provide: { canAdminList, - glFeatures: { graphqlBoardLists: graphqlBoardListsEnabled }, }, store, }); @@ -61,53 +57,48 @@ describe('BoardContent', () => { wrapper.destroy(); }); - it('renders a BoardColumnDeprecated component per list', () => { - createComponent(); + describe('default', () => { + beforeEach(() => { + createComponent(); + }); - expect(wrapper.findAllComponents(BoardColumnDeprecated)).toHaveLength( - mockListsWithModel.length, - ); - }); + it('renders a BoardColumn component per list', () => { + expect(wrapper.findAllComponents(BoardColumn)).toHaveLength(mockLists.length); + }); - it('does not display EpicsSwimlanes component', () => { - createComponent(); + it('renders BoardContentSidebar', () => { + expect(wrapper.find(BoardContentSidebar).exists()).toBe(true); + }); - expect(wrapper.find(EpicsSwimlanes).exists()).toBe(false); - expect(wrapper.find(GlAlert).exists()).toBe(false); + it('does not display EpicsSwimlanes component', () => { + expect(wrapper.find(EpicsSwimlanes).exists()).toBe(false); + expect(wrapper.find(GlAlert).exists()).toBe(false); + }); }); - describe('graphqlBoardLists feature flag enabled', () => { + describe('when issuableType is not issue', () => { beforeEach(() => { - createComponent({ graphqlBoardListsEnabled: true }); - gon.features = { - graphqlBoardLists: true, - }; + createComponent({ state: { issuableType: 'foo' } }); }); - describe('can admin list', () => { - beforeEach(() => { - createComponent({ graphqlBoardListsEnabled: true, canAdminList: true }); - }); - - it('renders draggable component', () => { - expect(wrapper.find(Draggable).exists()).toBe(true); - }); + it('does not render BoardContentSidebar', () => { + expect(wrapper.find(BoardContentSidebar).exists()).toBe(false); }); + }); - describe('can not admin list', () => { - beforeEach(() => { - createComponent({ graphqlBoardListsEnabled: true, canAdminList: false }); - }); + describe('can admin list', () => { + beforeEach(() => { + createComponent({ canAdminList: true }); + }); - it('does not render draggable component', () => { - expect(wrapper.find(Draggable).exists()).toBe(false); - }); + it('renders draggable component', () => { + expect(wrapper.find(Draggable).exists()).toBe(true); }); }); - describe('graphqlBoardLists feature flag disabled', () => { + describe('can not admin list', () => { beforeEach(() => { - createComponent({ graphqlBoardListsEnabled: false }); + createComponent({ canAdminList: false }); }); it('does not render draggable component', () => { diff --git a/spec/frontend/boards/components/board_filtered_search_spec.js b/spec/frontend/boards/components/board_filtered_search_spec.js index 50f86e92adb..dc93890f27a 100644 --- a/spec/frontend/boards/components/board_filtered_search_spec.js +++ b/spec/frontend/boards/components/board_filtered_search_spec.js @@ -2,7 +2,6 @@ import { shallowMount } from '@vue/test-utils'; import Vue from 'vue'; import Vuex from 'vuex'; import BoardFilteredSearch from '~/boards/components/board_filtered_search.vue'; -import { createStore } from '~/boards/stores'; import * as urlUtility from '~/lib/utils/url_utility'; import { __ } from '~/locale'; import FilteredSearchBarRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; @@ -44,6 +43,12 @@ describe('BoardFilteredSearch', () => { ]; const createComponent = ({ initialFilterParams = {} } = {}) => { + store = new Vuex.Store({ + actions: { + performSearch: jest.fn(), + }, + }); + wrapper = shallowMount(BoardFilteredSearch, { provide: { initialFilterParams, fullPath: '' }, store, @@ -55,22 +60,15 @@ describe('BoardFilteredSearch', () => { const findFilteredSearch = () => wrapper.findComponent(FilteredSearchBarRoot); - beforeEach(() => { - // this needed for actions call for performSearch - window.gon = { features: {} }; - }); - afterEach(() => { wrapper.destroy(); }); describe('default', () => { beforeEach(() => { - store = createStore(); + createComponent(); jest.spyOn(store, 'dispatch'); - - createComponent(); }); it('renders FilteredSearch', () => { @@ -103,8 +101,6 @@ describe('BoardFilteredSearch', () => { describe('when searching', () => { beforeEach(() => { - store = createStore(); - createComponent(); jest.spyOn(wrapper.vm, 'performSearch').mockImplementation(); @@ -133,11 +129,9 @@ describe('BoardFilteredSearch', () => { describe('when url params are already set', () => { beforeEach(() => { - store = createStore(); + createComponent({ initialFilterParams: { authorUsername: 'root', labelName: ['label'] } }); jest.spyOn(store, 'dispatch'); - - createComponent({ initialFilterParams: { authorUsername: 'root', labelName: ['label'] } }); }); it('passes the correct props to FilterSearchBar', () => { diff --git a/spec/frontend/boards/components/board_list_header_deprecated_spec.js b/spec/frontend/boards/components/board_list_header_deprecated_spec.js deleted file mode 100644 index db79e67fe78..00000000000 --- a/spec/frontend/boards/components/board_list_header_deprecated_spec.js +++ /dev/null @@ -1,174 +0,0 @@ -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 { ListType } from '~/boards/constants'; -import List from '~/boards/models/list'; -import axios from '~/lib/utils/axios_utils'; - -describe('Board List Header Component', () => { - let wrapper; - let axiosMock; - - beforeEach(() => { - window.gon = {}; - axiosMock = new AxiosMockAdapter(axios); - axiosMock.onGet(`${TEST_HOST}/lists/1/issues`).reply(200, { issues: [] }); - }); - - afterEach(() => { - axiosMock.restore(); - - wrapper.destroy(); - - localStorage.clear(); - }); - - const createComponent = ({ - listType = ListType.backlog, - collapsed = false, - withLocalStorage = true, - currentUserId = 1, - } = {}) => { - const boardId = '1'; - - const listMock = { - ...listObj, - list_type: listType, - collapsed, - }; - - if (listType === ListType.assignee) { - delete listMock.label; - listMock.user = {}; - } - - // Making List reactive - const list = Vue.observable(new List(listMock)); - - if (withLocalStorage) { - localStorage.setItem( - `boards.${boardId}.${list.type}.${list.id}.expanded`, - (!collapsed).toString(), - ); - } - - wrapper = shallowMount(BoardListHeader, { - propsData: { - disabled: false, - list, - }, - provide: { - boardId, - currentUserId, - }, - }); - }; - - const isCollapsed = () => !wrapper.props().list.isExpanded; - const isExpanded = () => wrapper.vm.list.isExpanded; - - const findAddIssueButton = () => wrapper.find({ ref: 'newIssueBtn' }); - const findCaret = () => wrapper.find('.board-title-caret'); - - describe('Add issue button', () => { - const hasNoAddButton = [ListType.closed]; - 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 }); - - expect(findAddIssueButton().exists()).toBe(false); - }); - - it.each(hasAddButton)('does render when List Type is `%s`', (listType) => { - createComponent({ listType }); - - expect(findAddIssueButton().exists()).toBe(true); - }); - - it('has a test for each list type', () => { - Object.values(ListType).forEach((value) => { - expect([...hasAddButton, ...hasNoAddButton]).toContain(value); - }); - }); - - it('does not render when logged out', () => { - createComponent({ - currentUserId: null, - }); - - expect(findAddIssueButton().exists()).toBe(false); - }); - }); - - describe('expanding / collapsing the column', () => { - it('does not collapse when clicking the header', () => { - createComponent(); - - expect(isCollapsed()).toBe(false); - wrapper.find('[data-testid="board-list-header"]').trigger('click'); - - return wrapper.vm.$nextTick().then(() => { - expect(isCollapsed()).toBe(false); - }); - }); - - it('collapses expanded Column when clicking the collapse icon', () => { - createComponent(); - - expect(isExpanded()).toBe(true); - findCaret().vm.$emit('click'); - - return wrapper.vm.$nextTick().then(() => { - expect(isCollapsed()).toBe(true); - }); - }); - - it('expands collapsed Column when clicking the expand icon', () => { - createComponent({ collapsed: true }); - - expect(isCollapsed()).toBe(true); - findCaret().vm.$emit('click'); - - return wrapper.vm.$nextTick().then(() => { - expect(isCollapsed()).toBe(false); - }); - }); - - it("when logged in it calls list update and doesn't set localStorage", () => { - jest.spyOn(List.prototype, 'update'); - - createComponent({ withLocalStorage: false }); - - findCaret().vm.$emit('click'); - - return wrapper.vm.$nextTick().then(() => { - expect(wrapper.vm.list.update).toHaveBeenCalledTimes(1); - expect(localStorage.getItem(`${wrapper.vm.uniqueKey}.expanded`)).toBe(null); - }); - }); - - it("when logged out it doesn't call list update and sets localStorage", () => { - jest.spyOn(List.prototype, 'update'); - - createComponent({ currentUserId: null }); - - findCaret().vm.$emit('click'); - - return wrapper.vm.$nextTick().then(() => { - expect(wrapper.vm.list.update).not.toHaveBeenCalled(); - expect(localStorage.getItem(`${wrapper.vm.uniqueKey}.expanded`)).toBe(String(isExpanded())); - }); - }); - }); -}); diff --git a/spec/frontend/boards/components/board_settings_sidebar_spec.js b/spec/frontend/boards/components/board_settings_sidebar_spec.js index 20a08be6c19..46dd109ffb1 100644 --- a/spec/frontend/boards/components/board_settings_sidebar_spec.js +++ b/spec/frontend/boards/components/board_settings_sidebar_spec.js @@ -1,38 +1,55 @@ -import '~/boards/models/list'; import { GlDrawer, GlLabel } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import axios from 'axios'; -import MockAdapter from 'axios-mock-adapter'; +import { shallowMount } from '@vue/test-utils'; import { MountingPortal } from 'portal-vue'; +import Vue from 'vue'; import Vuex from 'vuex'; +import { stubComponent } from 'helpers/stub_component'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue'; import { inactiveId, LIST } from '~/boards/constants'; -import { createStore } from '~/boards/stores'; -import boardsStore from '~/boards/stores/boards_store'; +import actions from '~/boards/stores/actions'; +import getters from '~/boards/stores/getters'; +import mutations from '~/boards/stores/mutations'; import sidebarEventHub from '~/sidebar/event_hub'; +import { mockLabelList } from '../mock_data'; -const localVue = createLocalVue(); - -localVue.use(Vuex); +Vue.use(Vuex); describe('BoardSettingsSidebar', () => { let wrapper; - let mock; - let store; - const labelTitle = 'test'; - const labelColor = '#FFFF'; - const listId = 1; + const labelTitle = mockLabelList.label.title; + const labelColor = mockLabelList.label.color; + const listId = mockLabelList.id; const findRemoveButton = () => wrapper.findByTestId('remove-list'); - const createComponent = ({ canAdminList = false } = {}) => { + const createComponent = ({ + canAdminList = false, + list = {}, + sidebarType = LIST, + activeId = inactiveId, + } = {}) => { + const boardLists = { + [listId]: list, + }; + const store = new Vuex.Store({ + state: { sidebarType, activeId, boardLists }, + getters, + mutations, + actions, + }); + wrapper = extendedWrapper( shallowMount(BoardSettingsSidebar, { store, - localVue, provide: { canAdminList, + scopedLabelsAvailable: false, + }, + stubs: { + GlDrawer: stubComponent(GlDrawer, { + template: '<div><slot name="header"></slot><slot></slot></div>', + }), }, }), ); @@ -40,16 +57,10 @@ describe('BoardSettingsSidebar', () => { const findLabel = () => wrapper.find(GlLabel); const findDrawer = () => wrapper.find(GlDrawer); - beforeEach(() => { - store = createStore(); - store.state.activeId = inactiveId; - store.state.sidebarType = LIST; - boardsStore.create(); - }); - afterEach(() => { jest.restoreAllMocks(); wrapper.destroy(); + wrapper = null; }); it('finds a MountingPortal component', () => { @@ -100,86 +111,40 @@ describe('BoardSettingsSidebar', () => { }); describe('when activeId is greater than zero', () => { - beforeEach(() => { - mock = new MockAdapter(axios); - - boardsStore.addList({ - id: listId, - label: { title: labelTitle, color: labelColor }, - list_type: 'label', - }); - store.state.activeId = 1; - store.state.sidebarType = LIST; - }); - - afterEach(() => { - boardsStore.removeList(listId); - }); - - it('renders GlDrawer with open false', () => { - createComponent(); + it('renders GlDrawer with open true', () => { + createComponent({ list: mockLabelList, activeId: listId }); expect(findDrawer().props('open')).toBe(true); }); }); - describe('when activeId is in boardsStore', () => { - beforeEach(() => { - mock = new MockAdapter(axios); - - boardsStore.addList({ - id: listId, - label: { title: labelTitle, color: labelColor }, - list_type: 'label', - }); - - store.state.activeId = listId; - store.state.sidebarType = LIST; - - createComponent(); - }); - - afterEach(() => { - mock.restore(); - }); - + describe('when activeId is in state', () => { it('renders label title', () => { + createComponent({ list: mockLabelList, activeId: listId }); + expect(findLabel().props('title')).toBe(labelTitle); }); it('renders label background color', () => { + createComponent({ list: mockLabelList, activeId: listId }); + expect(findLabel().props('backgroundColor')).toBe(labelColor); }); }); - describe('when activeId is not in boardsStore', () => { - beforeEach(() => { - mock = new MockAdapter(axios); - - boardsStore.addList({ id: listId, label: { title: labelTitle, color: labelColor } }); - - store.state.activeId = inactiveId; - - createComponent(); - }); - - afterEach(() => { - mock.restore(); - }); - + describe('when activeId is not in state', () => { it('does not render GlLabel', () => { + createComponent({ list: mockLabelList }); + expect(findLabel().exists()).toBe(false); }); }); }); describe('when sidebarType is not List', () => { - beforeEach(() => { - store.state.sidebarType = ''; - createComponent(); - }); - it('does not render GlDrawer', () => { + createComponent({ sidebarType: '' }); + expect(findDrawer().exists()).toBe(false); }); }); @@ -191,20 +156,9 @@ describe('BoardSettingsSidebar', () => { }); describe('when user can admin the boards list', () => { - beforeEach(() => { - store.state.activeId = listId; - store.state.sidebarType = LIST; - - boardsStore.addList({ - id: listId, - label: { title: labelTitle, color: labelColor }, - list_type: 'label', - }); - - createComponent({ canAdminList: true }); - }); - it('renders "Remove list" button', () => { + createComponent({ canAdminList: true, activeId: listId, list: mockLabelList }); + expect(findRemoveButton().exists()).toBe(true); }); }); diff --git a/spec/frontend/boards/components/boards_selector_deprecated_spec.js b/spec/frontend/boards/components/boards_selector_deprecated_spec.js deleted file mode 100644 index cc078861d75..00000000000 --- a/spec/frontend/boards/components/boards_selector_deprecated_spec.js +++ /dev/null @@ -1,214 +0,0 @@ -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/issue_time_estimate_deprecated_spec.js b/spec/frontend/boards/components/issue_time_estimate_deprecated_spec.js deleted file mode 100644 index fafebaf3a4e..00000000000 --- a/spec/frontend/boards/components/issue_time_estimate_deprecated_spec.js +++ /dev/null @@ -1,64 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import IssueTimeEstimate from '~/boards/components/issue_time_estimate_deprecated.vue'; -import boardsStore from '~/boards/stores/boards_store'; - -describe('Issue Time Estimate component', () => { - let wrapper; - - beforeEach(() => { - boardsStore.create(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('when limitToHours is false', () => { - beforeEach(() => { - boardsStore.timeTracking.limitToHours = false; - wrapper = shallowMount(IssueTimeEstimate, { - propsData: { - estimate: 374460, - }, - }); - }); - - it('renders the correct time estimate', () => { - expect(wrapper.find('time').text().trim()).toEqual('2w 3d 1m'); - }); - - it('renders expanded time estimate in tooltip', () => { - expect(wrapper.find('.js-issue-time-estimate').text()).toContain('2 weeks 3 days 1 minute'); - }); - - it('prevents tooltip xss', (done) => { - const alertSpy = jest.spyOn(window, 'alert'); - wrapper.setProps({ estimate: 'Foo <script>alert("XSS")</script>' }); - wrapper.vm.$nextTick(() => { - expect(alertSpy).not.toHaveBeenCalled(); - expect(wrapper.find('time').text().trim()).toEqual('0m'); - expect(wrapper.find('.js-issue-time-estimate').text()).toContain('0m'); - done(); - }); - }); - }); - - describe('when limitToHours is true', () => { - beforeEach(() => { - boardsStore.timeTracking.limitToHours = true; - wrapper = shallowMount(IssueTimeEstimate, { - propsData: { - estimate: 374460, - }, - }); - }); - - it('renders the correct time estimate', () => { - expect(wrapper.find('time').text().trim()).toEqual('104h 1m'); - }); - - it('renders expanded time estimate in tooltip', () => { - expect(wrapper.find('.js-issue-time-estimate').text()).toContain('104 hours 1 minute'); - }); - }); -}); diff --git a/spec/frontend/boards/issue_card_deprecated_spec.js b/spec/frontend/boards/issue_card_deprecated_spec.js deleted file mode 100644 index 909be275030..00000000000 --- a/spec/frontend/boards/issue_card_deprecated_spec.js +++ /dev/null @@ -1,332 +0,0 @@ -/* 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 IssueCardInner from '~/boards/components/issue_card_inner_deprecated.vue'; -import store from '~/boards/stores'; -import { listObj } from './mock_data'; - -describe('Issue card component', () => { - const user = new ListAssignee({ - id: 1, - name: 'testing 123', - username: 'test', - avatar: 'test_image', - }); - - const label1 = new ListLabel({ - id: 3, - title: 'testing 123', - color: '#000CFF', - text_color: 'white', - description: 'test', - }); - - let wrapper; - let issue; - let list; - - beforeEach(() => { - list = { ...listObj, type: 'label' }; - issue = new ListIssue({ - title: 'Testing', - id: 1, - iid: 1, - confidential: false, - labels: [list.label], - assignees: [], - reference_path: '#1', - real_path: '/test/1', - weight: 1, - }); - wrapper = mount(IssueCardInner, { - propsData: { - list, - issue, - }, - store, - stubs: { - GlLabel: true, - }, - provide: { - groupId: null, - rootPath: '/', - }, - }); - }); - - it('renders issue title', () => { - expect(wrapper.find('.board-card-title').text()).toContain(issue.title); - }); - - it('includes issue base in link', () => { - expect(wrapper.find('.board-card-title a').attributes('href')).toContain('/test'); - }); - - it('includes issue title on link', () => { - expect(wrapper.find('.board-card-title a').attributes('title')).toBe(issue.title); - }); - - it('does not render confidential icon', () => { - expect(wrapper.find('.confidential-icon').exists()).toBe(false); - }); - - it('does not render blocked icon', () => { - expect(wrapper.find('.issue-blocked-icon').exists()).toBe(false); - }); - - it('renders confidential icon', (done) => { - wrapper.setProps({ - issue: { - ...wrapper.props('issue'), - confidential: true, - }, - }); - wrapper.vm.$nextTick(() => { - expect(wrapper.find('.confidential-icon').exists()).toBe(true); - done(); - }); - }); - - it('renders issue ID with #', () => { - expect(wrapper.find('.board-card-number').text()).toContain(`#${issue.id}`); - }); - - describe('assignee', () => { - it('does not render assignee', () => { - expect(wrapper.find('.board-card-assignee .avatar').exists()).toBe(false); - }); - - describe('exists', () => { - beforeEach((done) => { - wrapper.setProps({ - issue: { - ...wrapper.props('issue'), - assignees: [user], - updateData(newData) { - Object.assign(this, newData); - }, - }, - }); - - wrapper.vm.$nextTick(done); - }); - - it('renders assignee', () => { - expect(wrapper.find('.board-card-assignee .avatar').exists()).toBe(true); - }); - - it('sets title', () => { - expect(wrapper.find('.js-assignee-tooltip').text()).toContain(`${user.name}`); - }); - - it('sets users path', () => { - expect(wrapper.find('.board-card-assignee a').attributes('href')).toBe('/test'); - }); - - it('renders avatar', () => { - expect(wrapper.find('.board-card-assignee img').exists()).toBe(true); - }); - - it('renders the avatar using avatar_url property', (done) => { - wrapper.props('issue').updateData({ - ...wrapper.props('issue'), - assignees: [ - { - id: '1', - name: 'test', - state: 'active', - username: 'test_name', - avatar_url: 'test_image_from_avatar_url', - }, - ], - }); - - wrapper.vm.$nextTick(() => { - expect(wrapper.find('.board-card-assignee img').attributes('src')).toBe( - 'test_image_from_avatar_url?width=24', - ); - done(); - }); - }); - }); - - describe('assignee default avatar', () => { - beforeEach((done) => { - global.gon.default_avatar_url = 'default_avatar'; - - wrapper.setProps({ - issue: { - ...wrapper.props('issue'), - assignees: [ - new ListAssignee({ - id: 1, - name: 'testing 123', - username: 'test', - }), - ], - }, - }); - - wrapper.vm.$nextTick(done); - }); - - afterEach(() => { - global.gon.default_avatar_url = null; - }); - - it('displays defaults avatar if users avatar is null', () => { - expect(wrapper.find('.board-card-assignee img').exists()).toBe(true); - expect(wrapper.find('.board-card-assignee img').attributes('src')).toBe( - 'default_avatar?width=24', - ); - }); - }); - }); - - describe('multiple assignees', () => { - beforeEach((done) => { - wrapper.setProps({ - issue: { - ...wrapper.props('issue'), - assignees: [ - new ListAssignee({ - id: 2, - name: 'user2', - username: 'user2', - avatar: 'test_image', - }), - new ListAssignee({ - id: 3, - name: 'user3', - username: 'user3', - avatar: 'test_image', - }), - new ListAssignee({ - id: 4, - name: 'user4', - username: 'user4', - avatar: 'test_image', - }), - ], - }, - }); - - wrapper.vm.$nextTick(done); - }); - - it('renders all three assignees', () => { - expect(wrapper.findAll('.board-card-assignee .avatar').length).toEqual(3); - }); - - describe('more than three assignees', () => { - beforeEach((done) => { - const { assignees } = wrapper.props('issue'); - assignees.push( - new ListAssignee({ - id: 5, - name: 'user5', - username: 'user5', - avatar: 'test_image', - }), - ); - - wrapper.setProps({ - issue: { - ...wrapper.props('issue'), - assignees, - }, - }); - wrapper.vm.$nextTick(done); - }); - - it('renders more avatar counter', () => { - expect(wrapper.find('.board-card-assignee .avatar-counter').text().trim()).toEqual('+2'); - }); - - it('renders two assignees', () => { - expect(wrapper.findAll('.board-card-assignee .avatar').length).toEqual(2); - }); - - it('renders 99+ avatar counter', (done) => { - const assignees = [ - ...wrapper.props('issue').assignees, - ...range(5, 103).map( - (i) => - new ListAssignee({ - id: i, - name: 'name', - username: 'username', - avatar: 'test_image', - }), - ), - ]; - wrapper.setProps({ - issue: { - ...wrapper.props('issue'), - assignees, - }, - }); - - wrapper.vm.$nextTick(() => { - expect(wrapper.find('.board-card-assignee .avatar-counter').text().trim()).toEqual('99+'); - done(); - }); - }); - }); - }); - - describe('labels', () => { - beforeEach((done) => { - issue.addLabel(label1); - wrapper.setProps({ issue: { ...issue } }); - - wrapper.vm.$nextTick(done); - }); - - it('does not render list label but renders all other labels', () => { - expect(wrapper.findAll(GlLabel).length).toBe(1); - const label = wrapper.find(GlLabel); - expect(label.props('title')).toEqual(label1.title); - expect(label.props('description')).toEqual(label1.description); - expect(label.props('backgroundColor')).toEqual(label1.color); - }); - - it('does not render label if label does not have an ID', (done) => { - issue.addLabel( - new ListLabel({ - title: 'closed', - }), - ); - wrapper.setProps({ issue: { ...issue } }); - wrapper.vm - .$nextTick() - .then(() => { - expect(wrapper.findAll(GlLabel).length).toBe(1); - expect(wrapper.text()).not.toContain('closed'); - done(); - }) - .catch(done.fail); - }); - }); - - describe('blocked', () => { - beforeEach((done) => { - wrapper.setProps({ - issue: { - ...wrapper.props('issue'), - blocked: true, - }, - }); - wrapper.vm.$nextTick(done); - }); - - it('renders blocked icon if issue is blocked', () => { - expect(wrapper.find('.issue-blocked-icon').exists()).toBe(true); - }); - }); -}); diff --git a/spec/frontend/boards/issue_spec.js b/spec/frontend/boards/issue_spec.js deleted file mode 100644 index 1f354fb04db..00000000000 --- a/spec/frontend/boards/issue_spec.js +++ /dev/null @@ -1,162 +0,0 @@ -/* global ListIssue */ - -import '~/boards/models/label'; -import '~/boards/models/assignee'; -import '~/boards/models/issue'; -import '~/boards/models/list'; -import boardsStore from '~/boards/stores/boards_store'; -import { setMockEndpoints, mockIssue } from './mock_data'; - -describe('Issue model', () => { - let issue; - - beforeEach(() => { - setMockEndpoints(); - boardsStore.create(); - - issue = new ListIssue(mockIssue); - }); - - it('has label', () => { - expect(issue.labels.length).toBe(1); - }); - - it('add new label', () => { - issue.addLabel({ - id: 2, - title: 'bug', - color: 'blue', - description: 'bugs!', - }); - - expect(issue.labels.length).toBe(2); - }); - - it('does not add label if label id exists', () => { - issue.addLabel({ - id: 1, - title: 'test 2', - color: 'blue', - description: 'testing', - }); - - expect(issue.labels.length).toBe(1); - expect(issue.labels[0].color).toBe('#F0AD4E'); - }); - - it('adds other label with same title', () => { - issue.addLabel({ - id: 2, - title: 'test', - color: 'blue', - description: 'other test', - }); - - expect(issue.labels.length).toBe(2); - }); - - it('finds label', () => { - const label = issue.findLabel(issue.labels[0]); - - expect(label).toBeDefined(); - }); - - it('removes label', () => { - const label = issue.findLabel(issue.labels[0]); - issue.removeLabel(label); - - expect(issue.labels.length).toBe(0); - }); - - it('removes multiple labels', () => { - issue.addLabel({ - id: 2, - title: 'bug', - color: 'blue', - description: 'bugs!', - }); - - expect(issue.labels.length).toBe(2); - - issue.removeLabels([issue.labels[0], issue.labels[1]]); - - expect(issue.labels.length).toBe(0); - }); - - it('adds assignee', () => { - issue.addAssignee({ - id: 2, - name: 'Bruce Wayne', - username: 'batman', - avatar_url: 'http://batman', - }); - - expect(issue.assignees.length).toBe(2); - }); - - it('finds assignee', () => { - const assignee = issue.findAssignee(issue.assignees[0]); - - expect(assignee).toBeDefined(); - }); - - it('removes assignee', () => { - const assignee = issue.findAssignee(issue.assignees[0]); - issue.removeAssignee(assignee); - - expect(issue.assignees.length).toBe(0); - }); - - it('removes all assignees', () => { - issue.removeAllAssignees(); - - expect(issue.assignees.length).toBe(0); - }); - - it('sets position to infinity if no position is stored', () => { - expect(issue.position).toBe(Infinity); - }); - - it('sets position', () => { - const relativePositionIssue = new ListIssue({ - title: 'Testing', - iid: 1, - confidential: false, - relative_position: 1, - labels: [], - assignees: [], - }); - - expect(relativePositionIssue.position).toBe(1); - }); - - it('updates data', () => { - issue.updateData({ subscribed: true }); - - expect(issue.subscribed).toBe(true); - }); - - it('sets fetching state', () => { - expect(issue.isFetching.subscriptions).toBe(true); - - issue.setFetchingState('subscriptions', false); - - expect(issue.isFetching.subscriptions).toBe(false); - }); - - it('sets loading state', () => { - issue.setLoadingState('foo', true); - - expect(issue.isLoading.foo).toBe(true); - }); - - describe('update', () => { - it('passes update to boardsStore', () => { - jest.spyOn(boardsStore, 'updateIssue').mockImplementation(); - - issue.update(); - - expect(boardsStore.updateIssue).toHaveBeenCalledWith(issue); - }); - }); -}); diff --git a/spec/frontend/boards/list_spec.js b/spec/frontend/boards/list_spec.js deleted file mode 100644 index 4d6a82bdff0..00000000000 --- a/spec/frontend/boards/list_spec.js +++ /dev/null @@ -1,230 +0,0 @@ -/* global List */ -/* global ListAssignee */ -/* global ListIssue */ -/* global ListLabel */ -import MockAdapter from 'axios-mock-adapter'; -import waitForPromises from 'helpers/wait_for_promises'; -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', () => { - let list; - let mock; - - beforeEach(() => { - mock = new MockAdapter(axios); - mock.onAny().reply(boardsMockInterceptor); - boardsStore.create(); - boardsStore.setEndpoints({ - listsEndpoint: '/test/-/boards/1/lists', - }); - - list = new List(listObj); - return waitForPromises(); - }); - - afterEach(() => { - mock.restore(); - }); - - describe('list type', () => { - const notExpandableList = ['blank']; - - const table = Object.keys(ListType).map((k) => { - const value = ListType[k]; - return [value, !notExpandableList.includes(value)]; - }); - it.each(table)(`when list_type is %s boards isExpandable is %p`, (type, result) => { - expect(new List({ id: 1, list_type: type }).isExpandable).toBe(result); - }); - }); - - it('gets issues when created', () => { - expect(list.issues.length).toBe(1); - }); - - it('saves list and returns ID', () => { - list = new List({ - title: 'test', - label: { - id: 1, - title: 'test', - color: '#ff0000', - text_color: 'white', - }, - }); - return list.save().then(() => { - expect(list.id).toBe(listObj.id); - expect(list.type).toBe('label'); - expect(list.position).toBe(0); - expect(list.label).toEqual(listObj.label); - }); - }); - - it('destroys the list', () => { - boardsStore.addList(listObj); - list = boardsStore.findList('id', listObj.id); - - expect(boardsStore.state.lists.length).toBe(1); - list.destroy(); - - return waitForPromises().then(() => { - expect(boardsStore.state.lists.length).toBe(0); - }); - }); - - it('gets issue from list', () => { - const issue = list.findIssue(1); - - expect(issue).toBeDefined(); - }); - - it('removes issue', () => { - const issue = list.findIssue(1); - - expect(list.issues.length).toBe(1); - list.removeIssue(issue); - - expect(list.issues.length).toBe(0); - }); - - it('sends service request to update issue label', () => { - const listDup = new List(listObjDuplicate); - const issue = new ListIssue({ - title: 'Testing', - id: 1, - iid: 1, - confidential: false, - labels: [list.label, listDup.label], - assignees: [], - }); - - list.issues.push(issue); - listDup.issues.push(issue); - - jest.spyOn(boardsStore, 'moveIssue'); - - listDup.updateIssueLabel(issue, list); - - expect(boardsStore.moveIssue).toHaveBeenCalledWith( - issue.id, - list.id, - listDup.id, - undefined, - undefined, - ); - }); - - describe('page number', () => { - beforeEach(() => { - jest.spyOn(list, 'getIssues').mockImplementation(() => {}); - list.issues = []; - }); - - it('increase page number if current issue count is more than the page size', () => { - for (let i = 0; i < 30; i += 1) { - list.issues.push( - new ListIssue({ - title: 'Testing', - id: i, - iid: i, - confidential: false, - labels: [list.label], - assignees: [], - }), - ); - } - list.issuesSize = 50; - - expect(list.issues.length).toBe(30); - - list.nextPage(); - - expect(list.page).toBe(2); - expect(list.getIssues).toHaveBeenCalled(); - }); - - it('does not increase page number if issue count is less than the page size', () => { - list.issues.push( - new ListIssue({ - title: 'Testing', - id: 1, - confidential: false, - labels: [list.label], - assignees: [], - }), - ); - list.issuesSize = 2; - - list.nextPage(); - - expect(list.page).toBe(1); - expect(list.getIssues).toHaveBeenCalled(); - }); - }); - - describe('newIssue', () => { - beforeEach(() => { - jest.spyOn(boardsStore, 'newIssue').mockReturnValue( - Promise.resolve({ - data: { - id: 42, - subscribed: false, - assignable_labels_endpoint: '/issue/42/labels', - toggle_subscription_endpoint: '/issue/42/subscriptions', - issue_sidebar_endpoint: '/issue/42/sidebar_info', - }, - }), - ); - list.issues = []; - }); - - it('adds new issue to top of list', (done) => { - const user = new ListAssignee({ - id: 1, - name: 'testing 123', - username: 'test', - avatar: 'test_image', - }); - - list.issues.push( - new ListIssue({ - title: 'Testing', - id: 1, - confidential: false, - labels: [new ListLabel(list.label)], - assignees: [], - }), - ); - const dummyIssue = new ListIssue({ - title: 'new issue', - id: 2, - confidential: false, - labels: [new ListLabel(list.label)], - assignees: [user], - subscribed: false, - }); - - list - .newIssue(dummyIssue) - .then(() => { - expect(list.issues.length).toBe(2); - expect(list.issues[0]).toBe(dummyIssue); - expect(list.issues[0].subscribed).toBe(false); - expect(list.issues[0].assignableLabelsEndpoint).toBe('/issue/42/labels'); - expect(list.issues[0].toggleSubscriptionEndpoint).toBe('/issue/42/subscriptions'); - expect(list.issues[0].sidebarInfoEndpoint).toBe('/issue/42/sidebar_info'); - expect(list.issues[0].labels).toBe(dummyIssue.labels); - expect(list.issues[0].assignees).toBe(dummyIssue.assignees); - }) - .then(done) - .catch(done.fail); - }); - }); -}); diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js index 106f7b04c4b..6a4f344bbfb 100644 --- a/spec/frontend/boards/mock_data.js +++ b/spec/frontend/boards/mock_data.js @@ -1,12 +1,8 @@ -/* global List */ - import { GlFilteredSearchToken } from '@gitlab/ui'; import { keyBy } from 'lodash'; -import Vue from 'vue'; -import '~/boards/models/list'; import { ListType } from '~/boards/constants'; -import boardsStore from '~/boards/stores/boards_store'; import { __ } from '~/locale'; +import { DEFAULT_MILESTONES_GRAPHQL } from '~/vue_shared/components/filtered_search_bar/constants'; import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue'; import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue'; import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue'; @@ -196,8 +192,7 @@ export const mockIssue = { export const mockActiveIssue = { ...mockIssue, - fullId: 'gid://gitlab/Issue/436', - id: 436, + id: 'gid://gitlab/Issue/436', iid: '27', subscribed: false, emailsDisabled: false, @@ -289,20 +284,6 @@ export const boardsMockInterceptor = (config) => { return [200, body]; }; -export const setMockEndpoints = (opts = {}) => { - const boardsEndpoint = opts.boardsEndpoint || '/test/issue-boards/-/boards.json'; - const listsEndpoint = opts.listsEndpoint || '/test/-/boards/1/lists'; - const bulkUpdatePath = opts.bulkUpdatePath || ''; - const boardId = opts.boardId || '1'; - - boardsStore.setEndpoints({ - boardsEndpoint, - listsEndpoint, - bulkUpdatePath, - boardId, - }); -}; - export const mockList = { id: 'gid://gitlab/List/1', title: 'Open', @@ -335,14 +316,26 @@ export const mockLabelList = { issuesCount: 0, }; +export const mockMilestoneList = { + id: 'gid://gitlab/List/3', + title: 'To Do', + position: 0, + listType: 'milestone', + collapsed: false, + label: null, + assignee: null, + milestone: { + webUrl: 'https://gitlab.com/h5bp/html5-boilerplate/-/milestones/1', + title: 'Backlog', + }, + loading: false, + issuesCount: 0, +}; + export const mockLists = [mockList, mockLabelList]; export const mockListsById = keyBy(mockLists, 'id'); -export const mockListsWithModel = mockLists.map((listMock) => - Vue.observable(new List({ ...listMock, doNotFetchIssues: true })), -); - export const mockIssuesByListId = { 'gid://gitlab/List/1': [mockIssue.id, mockIssue3.id, mockIssue4.id], 'gid://gitlab/List/2': mockIssues.map(({ id }) => id), @@ -547,17 +540,17 @@ export const mockMoveData = { export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones) => [ { - icon: 'labels', - title: __('Label'), - type: 'label_name', + icon: 'user', + title: __('Assignee'), + type: 'assignee_username', operators: [ { value: '=', description: 'is' }, { value: '!=', description: 'is not' }, ], - token: LabelToken, - unique: false, - symbol: '~', - fetchLabels, + token: AuthorToken, + unique: true, + fetchAuthors, + preloadedAuthors: [], }, { icon: 'pencil', @@ -574,17 +567,27 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones) => [ preloadedAuthors: [], }, { - icon: 'user', - title: __('Assignee'), - type: 'assignee_username', + icon: 'labels', + title: __('Label'), + type: 'label_name', operators: [ { value: '=', description: 'is' }, { value: '!=', description: 'is not' }, ], - token: AuthorToken, + token: LabelToken, + unique: false, + symbol: '~', + fetchLabels, + }, + { + icon: 'clock', + title: __('Milestone'), + symbol: '%', + type: 'milestone_title', + token: MilestoneToken, unique: true, - fetchAuthors, - preloadedAuthors: [], + defaultMilestones: DEFAULT_MILESTONES_GRAPHQL, + fetchMilestones, }, { icon: 'issues', @@ -599,16 +602,6 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones) => [ ], }, { - icon: 'clock', - title: __('Milestone'), - symbol: '%', - type: 'milestone_title', - token: MilestoneToken, - unique: true, - defaultMilestones: [], - fetchMilestones, - }, - { icon: 'weight', title: __('Weight'), type: 'weight', diff --git a/spec/frontend/boards/project_select_deprecated_spec.js b/spec/frontend/boards/project_select_deprecated_spec.js deleted file mode 100644 index 4494de43083..00000000000 --- a/spec/frontend/boards/project_select_deprecated_spec.js +++ /dev/null @@ -1,263 +0,0 @@ -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 ProjectSelect from '~/boards/components/project_select_deprecated.vue'; -import { ListType } from '~/boards/constants'; -import eventHub from '~/boards/eventhub'; -import createFlash from '~/flash'; -import httpStatus from '~/lib/utils/http_status'; -import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants'; - -import { listObj, mockRawGroupProjects } from './mock_data'; - -jest.mock('~/boards/eventhub'); -jest.mock('~/flash'); - -const dummyGon = { - api_version: 'v4', - relative_url_root: '/gitlab', -}; - -const mockGroupId = 1; -const mockProjectsList1 = mockRawGroupProjects.slice(0, 1); -const mockProjectsList2 = mockRawGroupProjects.slice(1); -const mockDefaultFetchOptions = { - with_issues_enabled: true, - with_shared: false, - include_subgroups: true, - order_by: 'similarity', - archived: false, -}; - -const itemsPerPage = 20; - -describe('ProjectSelect component', () => { - let wrapper; - let axiosMock; - - const findLabel = () => wrapper.find("[data-testid='header-label']"); - const findGlDropdown = () => wrapper.find(GlDropdown); - const findGlDropdownLoadingIcon = () => - findGlDropdown().find('button:first-child').find(GlLoadingIcon); - const findGlSearchBoxByType = () => wrapper.find(GlSearchBoxByType); - const findGlDropdownItems = () => wrapper.findAll(GlDropdownItem); - const findFirstGlDropdownItem = () => findGlDropdownItems().at(0); - const findInMenuLoadingIcon = () => wrapper.find("[data-testid='dropdown-text-loading-icon']"); - const findEmptySearchMessage = () => wrapper.find("[data-testid='empty-result-message']"); - - const mockGetRequest = (data = [], statusCode = httpStatus.OK) => { - axiosMock - .onGet(`/gitlab/api/v4/groups/${mockGroupId}/projects.json`) - .replyOnce(statusCode, data); - }; - - const searchForProject = async (keyword, waitForAll = true) => { - findGlSearchBoxByType().vm.$emit('input', keyword); - - if (waitForAll) { - await axios.waitForAll(); - } - }; - - const createWrapper = async ({ list = listObj } = {}, waitForAll = true) => { - wrapper = mount(ProjectSelect, { - propsData: { - list, - }, - provide: { - groupId: 1, - }, - }); - - if (waitForAll) { - await axios.waitForAll(); - } - }; - - beforeEach(() => { - axiosMock = new AxiosMockAdapter(axios); - window.gon = dummyGon; - }); - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - axiosMock.restore(); - jest.clearAllMocks(); - }); - - it('displays a header title', async () => { - createWrapper({}); - - expect(findLabel().text()).toBe('Projects'); - }); - - it('renders a default dropdown text', async () => { - createWrapper({}); - - expect(findGlDropdown().exists()).toBe(true); - expect(findGlDropdown().text()).toContain('Select a project'); - }); - - describe('when mounted', () => { - it('displays a loading icon while projects are being fetched', async () => { - mockGetRequest([]); - - createWrapper({}, false); - - expect(findGlDropdownLoadingIcon().exists()).toBe(true); - - await axios.waitForAll(); - - expect(axiosMock.history.get[0].params).toMatchObject({ search: '' }); - expect(axiosMock.history.get[0].url).toBe( - `/gitlab/api/v4/groups/${mockGroupId}/projects.json`, - ); - - expect(findGlDropdownLoadingIcon().exists()).toBe(false); - }); - }); - - describe('when dropdown menu is open', () => { - describe('by default', () => { - beforeEach(async () => { - mockGetRequest(mockProjectsList1); - - await createWrapper(); - }); - - it('shows GlSearchBoxByType with default attributes', () => { - expect(findGlSearchBoxByType().exists()).toBe(true); - expect(findGlSearchBoxByType().vm.$attrs).toMatchObject({ - placeholder: 'Search projects', - debounce: '250', - }); - }); - - it("displays the fetched project's name", () => { - expect(findFirstGlDropdownItem().exists()).toBe(true); - expect(findFirstGlDropdownItem().text()).toContain(mockProjectsList1[0].name); - }); - - it("doesn't render loading icon in the menu", () => { - expect(findInMenuLoadingIcon().isVisible()).toBe(false); - }); - - it('renders empty search result message', async () => { - await createWrapper(); - - expect(findEmptySearchMessage().exists()).toBe(true); - }); - }); - - describe('when a project is selected', () => { - beforeEach(async () => { - mockGetRequest(mockProjectsList1); - - await createWrapper(); - - await findFirstGlDropdownItem().find('button').trigger('click'); - }); - - it('emits setSelectedProject with correct project metadata', () => { - expect(eventHub.$emit).toHaveBeenCalledWith('setSelectedProject', { - id: mockProjectsList1[0].id, - path: mockProjectsList1[0].path_with_namespace, - name: mockProjectsList1[0].name, - namespacedName: mockProjectsList1[0].name_with_namespace, - }); - }); - - it('renders the name of the selected project', () => { - expect(findGlDropdown().find('.gl-new-dropdown-button-text').text()).toBe( - mockProjectsList1[0].name, - ); - }); - }); - - describe('when user searches for a project', () => { - beforeEach(async () => { - mockGetRequest(mockProjectsList1); - - await createWrapper(); - }); - - it('calls API with correct parameters with default fetch options', async () => { - await searchForProject('foobar'); - - const expectedApiParams = { - search: 'foobar', - per_page: itemsPerPage, - ...mockDefaultFetchOptions, - }; - - expect(axiosMock.history.get[1].params).toMatchObject(expectedApiParams); - expect(axiosMock.history.get[1].url).toBe( - `/gitlab/api/v4/groups/${mockGroupId}/projects.json`, - ); - }); - - describe("when list type is defined and isn't backlog", () => { - it('calls API with an additional fetch option (min_access_level)', async () => { - axiosMock.reset(); - - await createWrapper({ list: { ...listObj, type: ListType.label } }); - - await searchForProject('foobar'); - - const expectedApiParams = { - search: 'foobar', - per_page: itemsPerPage, - ...mockDefaultFetchOptions, - min_access_level: featureAccessLevel.EVERYONE, - }; - - expect(axiosMock.history.get[1].params).toMatchObject(expectedApiParams); - expect(axiosMock.history.get[1].url).toBe( - `/gitlab/api/v4/groups/${mockGroupId}/projects.json`, - ); - }); - }); - - it('displays and hides gl-loading-icon while and after fetching data', async () => { - await searchForProject('some keyword', false); - - await wrapper.vm.$nextTick(); - - expect(findInMenuLoadingIcon().isVisible()).toBe(true); - - await axios.waitForAll(); - - expect(findInMenuLoadingIcon().isVisible()).toBe(false); - }); - - it('flashes an error message when fetching fails', async () => { - mockGetRequest([], httpStatus.INTERNAL_SERVER_ERROR); - - await searchForProject('foobar'); - - expect(createFlash).toHaveBeenCalledTimes(1); - expect(createFlash).toHaveBeenCalledWith({ - message: 'Something went wrong while fetching projects', - }); - }); - - describe('with non-empty search result', () => { - beforeEach(async () => { - mockGetRequest(mockProjectsList2); - - await searchForProject('foobar'); - }); - - it('displays the retrieved list of projects', async () => { - expect(findFirstGlDropdownItem().text()).toContain(mockProjectsList2[0].name); - }); - - it('does not render empty search result message', async () => { - expect(findEmptySearchMessage().exists()).toBe(false); - }); - }); - }); - }); -}); diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js index 1272a573d2f..62e0fa7a68a 100644 --- a/spec/frontend/boards/stores/actions_spec.js +++ b/spec/frontend/boards/stores/actions_spec.js @@ -26,7 +26,6 @@ import issueCreateMutation from '~/boards/graphql/issue_create.mutation.graphql' import actions, { gqlClient } from '~/boards/stores/actions'; import * as types from '~/boards/stores/mutation_types'; import mutations from '~/boards/stores/mutations'; -import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { mockLists, @@ -107,12 +106,7 @@ describe('setFilters', () => { }); describe('performSearch', () => { - it('should dispatch setFilters action', (done) => { - testAction(actions.performSearch, {}, {}, [], [{ type: 'setFilters', payload: {} }], done); - }); - - it('should dispatch setFilters, fetchLists and resetIssues action when graphqlBoardLists FF is on', (done) => { - window.gon = { features: { graphqlBoardLists: true } }; + it('should dispatch setFilters, fetchLists and resetIssues action', (done) => { testAction( actions.performSearch, {}, @@ -496,12 +490,9 @@ describe('fetchLabels', () => { jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse); const commit = jest.fn(); - const getters = { - shouldUseGraphQL: () => true, - }; const state = { boardType: 'group' }; - await actions.fetchLabels({ getters, state, commit }); + await actions.fetchLabels({ state, commit }); expect(commit).toHaveBeenCalledWith(types.RECEIVE_LABELS_SUCCESS, labels); }); @@ -954,7 +945,7 @@ describe('moveIssue', () => { }); describe('moveIssueCard and undoMoveIssueCard', () => { - describe('card should move without clonning', () => { + describe('card should move without cloning', () => { let state; let params; let moveMutations; @@ -1221,8 +1212,8 @@ describe('updateMovedIssueCard', () => { describe('updateIssueOrder', () => { const issues = { - 436: mockIssue, - 437: mockIssue2, + [mockIssue.id]: mockIssue, + [mockIssue2.id]: mockIssue2, }; const state = { @@ -1231,7 +1222,7 @@ describe('updateIssueOrder', () => { }; const moveData = { - itemId: 436, + itemId: mockIssue.id, fromListId: 'gid://gitlab/List/1', toListId: 'gid://gitlab/List/2', }; @@ -1490,7 +1481,7 @@ describe('addListNewIssue', () => { type: 'addListItem', payload: { list: fakeList, - item: formatIssue({ ...mockIssue, id: getIdFromGraphQLId(mockIssue.id) }), + item: formatIssue(mockIssue), position: 0, }, }, diff --git a/spec/frontend/boards/stores/getters_spec.js b/spec/frontend/boards/stores/getters_spec.js index c0774dd3ae1..b30968c45d7 100644 --- a/spec/frontend/boards/stores/getters_spec.js +++ b/spec/frontend/boards/stores/getters_spec.js @@ -77,12 +77,12 @@ describe('Boards - Getters', () => { }); describe('getBoardItemById', () => { - const state = { boardItems: { 1: 'issue' } }; + const state = { boardItems: { 'gid://gitlab/Issue/1': 'issue' } }; it.each` - id | expected - ${'1'} | ${'issue'} - ${''} | ${{}} + id | expected + ${'gid://gitlab/Issue/1'} | ${'issue'} + ${''} | ${{}} `('returns $expected when $id is passed to state', ({ id, expected }) => { expect(getters.getBoardItemById(state)(id)).toEqual(expected); }); @@ -90,11 +90,11 @@ describe('Boards - Getters', () => { describe('activeBoardItem', () => { it.each` - id | expected - ${'1'} | ${'issue'} - ${''} | ${{ id: '', iid: '', fullId: '' }} + id | expected + ${'gid://gitlab/Issue/1'} | ${'issue'} + ${''} | ${{ id: '', iid: '' }} `('returns $expected when $id is passed to state', ({ id, expected }) => { - const state = { boardItems: { 1: 'issue' }, activeId: id }; + const state = { boardItems: { 'gid://gitlab/Issue/1': 'issue' }, activeId: id }; expect(getters.activeBoardItem(state)).toEqual(expected); }); diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js index a2ba1e9eb5e..0e830258327 100644 --- a/spec/frontend/boards/stores/mutations_spec.js +++ b/spec/frontend/boards/stores/mutations_spec.js @@ -407,7 +407,7 @@ describe('Board Store Mutations', () => { describe('MUTATE_ISSUE_SUCCESS', () => { it('updates issue in issues state', () => { const issues = { - 436: { id: rawIssue.id }, + [rawIssue.id]: { id: rawIssue.id }, }; state = { @@ -419,7 +419,7 @@ describe('Board Store Mutations', () => { issue: rawIssue, }); - expect(state.boardItems).toEqual({ 436: { ...mockIssue, id: 436 } }); + expect(state.boardItems).toEqual({ [mockIssue.id]: mockIssue }); }); }); @@ -545,7 +545,7 @@ describe('Board Store Mutations', () => { expect(state.groupProjectsFlags.isLoading).toBe(true); }); - it('Should set isLoading in groupProjectsFlags to true in state when fetchNext is true', () => { + it('Should set isLoadingMore in groupProjectsFlags to true in state when fetchNext is true', () => { mutations[types.REQUEST_GROUP_PROJECTS](state, true); expect(state.groupProjectsFlags.isLoadingMore).toBe(true); |