diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-18 12:11:05 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-18 12:11:05 +0300 |
commit | d7432b66ff241af3f39d82da581832a084983378 (patch) | |
tree | 02987f56a6d5119197867716faed5719e58b9880 /spec/frontend/boards | |
parent | 2931472c10363925ae473bc646f53f7f58afad36 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/boards')
13 files changed, 720 insertions, 636 deletions
diff --git a/spec/frontend/boards/board_list_deprecated_spec.js b/spec/frontend/boards/board_list_deprecated_spec.js new file mode 100644 index 00000000000..393d7f954b1 --- /dev/null +++ b/spec/frontend/boards/board_list_deprecated_spec.js @@ -0,0 +1,275 @@ +/* global List */ +/* global ListIssue */ + +import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import waitForPromises from 'helpers/wait_for_promises'; +import axios from '~/lib/utils/axios_utils'; +import eventHub from '~/boards/eventhub'; +import BoardList from '~/boards/components/board_list_deprecated.vue'; +import '~/boards/models/issue'; +import '~/boards/models/list'; +import { listObj, boardsMockInterceptor } from './mock_data'; +import store from '~/boards/stores'; +import boardsStore from '~/boards/stores/boards_store'; + +const createComponent = ({ done, listIssueProps = {}, componentProps = {}, listProps = {} }) => { + const el = document.createElement('div'); + + 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_list_helper.js b/spec/frontend/boards/board_list_helper.js index 80d7a72151d..f82b1f7ed5c 100644 --- a/spec/frontend/boards/board_list_helper.js +++ b/spec/frontend/boards/board_list_helper.js @@ -5,7 +5,7 @@ import MockAdapter from 'axios-mock-adapter'; import Vue from 'vue'; import Sortable from 'sortablejs'; import axios from '~/lib/utils/axios_utils'; -import BoardList from '~/boards/components/board_list.vue'; +import BoardList from '~/boards/components/board_list_deprecated.vue'; import '~/boards/models/issue'; import '~/boards/models/list'; diff --git a/spec/frontend/boards/board_list_new_spec.js b/spec/frontend/boards/board_list_new_spec.js deleted file mode 100644 index 56784611322..00000000000 --- a/spec/frontend/boards/board_list_new_spec.js +++ /dev/null @@ -1,268 +0,0 @@ -import Vuex from 'vuex'; -import { useFakeRequestAnimationFrame } from 'helpers/fake_request_animation_frame'; -import { createLocalVue, mount } from '@vue/test-utils'; -import eventHub from '~/boards/eventhub'; -import BoardList from '~/boards/components/board_list_new.vue'; -import BoardCard from '~/boards/components/board_card.vue'; -import '~/boards/models/list'; -import { mockList, mockIssuesByListId, issues, mockIssues } from './mock_data'; -import defaultState from '~/boards/stores/state'; - -const localVue = createLocalVue(); -localVue.use(Vuex); - -const actions = { - fetchIssuesForList: jest.fn(), -}; - -const createStore = (state = defaultState) => { - return new Vuex.Store({ - state, - actions, - }); -}; - -const createComponent = ({ - listIssueProps = {}, - componentProps = {}, - listProps = {}, - state = {}, -} = {}) => { - const store = createStore({ - issuesByListId: mockIssuesByListId, - issues, - pageInfoByListId: { - 'gid://gitlab/List/1': { hasNextPage: true }, - 'gid://gitlab/List/2': {}, - }, - listsFlags: { - 'gid://gitlab/List/1': {}, - 'gid://gitlab/List/2': {}, - }, - ...state, - }); - - const list = { - ...mockList, - ...listProps, - }; - const issue = { - title: 'Testing', - id: 1, - iid: 1, - confidential: false, - labels: [], - assignees: [], - ...listIssueProps, - }; - if (!Object.prototype.hasOwnProperty.call(listProps, 'issuesCount')) { - list.issuesCount = 1; - } - - const component = mount(BoardList, { - localVue, - propsData: { - disabled: false, - list, - issues: [issue], - canAdminList: true, - ...componentProps, - }, - store, - provide: { - groupId: null, - rootPath: '/', - weightFeatureAvailable: false, - boardWeight: null, - }, - }); - - return component; -}; - -describe('Board list component', () => { - let wrapper; - const findByTestId = (testId) => wrapper.find(`[data-testid="${testId}"]`); - useFakeRequestAnimationFrame(); - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - describe('When Expanded', () => { - beforeEach(() => { - wrapper = createComponent(); - }); - - it('renders component', () => { - expect(wrapper.find('.board-list-component').exists()).toBe(true); - }); - - it('renders loading icon', () => { - wrapper = createComponent({ - state: { listsFlags: { 'gid://gitlab/List/1': { isLoading: true } } }, - }); - - expect(findByTestId('board_list_loading').exists()).toBe(true); - }); - - it('renders issues', () => { - expect(wrapper.findAll(BoardCard).length).toBe(1); - }); - - it('sets data attribute with issue id', () => { - expect(wrapper.find('.board-card').attributes('data-issue-id')).toBe('1'); - }); - - it('shows new issue form', async () => { - wrapper.vm.toggleForm(); - - await wrapper.vm.$nextTick(); - expect(wrapper.find('.board-new-issue-form').exists()).toBe(true); - }); - - it('shows new issue form after eventhub event', async () => { - eventHub.$emit(`toggle-issue-form-${wrapper.vm.list.id}`); - - await wrapper.vm.$nextTick(); - expect(wrapper.find('.board-new-issue-form').exists()).toBe(true); - }); - - it('does not show new issue form for closed list', () => { - wrapper.setProps({ list: { type: 'closed' } }); - wrapper.vm.toggleForm(); - - expect(wrapper.find('.board-new-issue-form').exists()).toBe(false); - }); - - it('shows count list item', async () => { - wrapper.vm.showCount = true; - - await wrapper.vm.$nextTick(); - expect(wrapper.find('.board-list-count').exists()).toBe(true); - - expect(wrapper.find('.board-list-count').text()).toBe('Showing all issues'); - }); - - it('sets data attribute with invalid id', async () => { - wrapper.vm.showCount = true; - - await wrapper.vm.$nextTick(); - expect(wrapper.find('.board-list-count').attributes('data-issue-id')).toBe('-1'); - }); - - it('shows how many more issues to load', async () => { - wrapper.vm.showCount = true; - wrapper.setProps({ list: { issuesCount: 20 } }); - - await wrapper.vm.$nextTick(); - expect(wrapper.find('.board-list-count').text()).toBe('Showing 1 of 20 issues'); - }); - }); - - describe('load more issues', () => { - beforeEach(() => { - wrapper = createComponent({ - listProps: { issuesCount: 25 }, - }); - }); - - it('loads more issues after scrolling', () => { - wrapper.vm.listRef.dispatchEvent(new Event('scroll')); - - expect(actions.fetchIssuesForList).toHaveBeenCalled(); - }); - - it('does not load issues if already loading', () => { - wrapper = createComponent({ - state: { listsFlags: { 'gid://gitlab/List/1': { isLoadingMore: true } } }, - }); - wrapper.vm.listRef.dispatchEvent(new Event('scroll')); - - expect(actions.fetchIssuesForList).not.toHaveBeenCalled(); - }); - - it('shows loading more spinner', async () => { - wrapper = createComponent({ - state: { listsFlags: { 'gid://gitlab/List/1': { isLoadingMore: true } } }, - }); - wrapper.vm.showCount = true; - - await wrapper.vm.$nextTick(); - expect(wrapper.find('.board-list-count .gl-spinner').exists()).toBe(true); - }); - }); - - describe('max issue count warning', () => { - beforeEach(() => { - wrapper = createComponent({ - listProps: { issuesCount: 50 }, - }); - }); - - describe('when issue count exceeds max issue count', () => { - it('sets background to bg-danger-100', async () => { - wrapper.setProps({ list: { issuesCount: 4, maxIssueCount: 3 } }); - - await wrapper.vm.$nextTick(); - expect(wrapper.find('.bg-danger-100').exists()).toBe(true); - }); - }); - - describe('when list issue count does NOT exceed list max issue count', () => { - it('does not sets background to bg-danger-100', () => { - wrapper.setProps({ list: { issuesCount: 2, maxIssueCount: 3 } }); - - expect(wrapper.find('.bg-danger-100').exists()).toBe(false); - }); - }); - - describe('when list max issue count is 0', () => { - it('does not sets background to bg-danger-100', () => { - wrapper.setProps({ list: { maxIssueCount: 0 } }); - - expect(wrapper.find('.bg-danger-100').exists()).toBe(false); - }); - }); - }); - - describe('drag & drop issue', () => { - beforeEach(() => { - wrapper = createComponent(); - }); - - describe('handleDragOnStart', () => { - it('adds a class `is-dragging` to document body', () => { - expect(document.body.classList.contains('is-dragging')).toBe(false); - - findByTestId('tree-root-wrapper').vm.$emit('start'); - - expect(document.body.classList.contains('is-dragging')).toBe(true); - }); - }); - - describe('handleDragOnEnd', () => { - it('removes class `is-dragging` from document body', () => { - jest.spyOn(wrapper.vm, 'moveIssue').mockImplementation(() => {}); - document.body.classList.add('is-dragging'); - - findByTestId('tree-root-wrapper').vm.$emit('end', { - oldIndex: 1, - newIndex: 0, - item: { - dataset: { - issueId: mockIssues[0].id, - issueIid: mockIssues[0].iid, - issuePath: mockIssues[0].referencePath, - }, - }, - to: { children: [], dataset: { listId: 'gid://gitlab/List/1' } }, - from: { dataset: { listId: 'gid://gitlab/List/2' } }, - }); - - expect(document.body.classList.contains('is-dragging')).toBe(false); - }); - }); - }); -}); diff --git a/spec/frontend/boards/board_list_spec.js b/spec/frontend/boards/board_list_spec.js index 43755cd8885..1b62f25044e 100644 --- a/spec/frontend/boards/board_list_spec.js +++ b/spec/frontend/boards/board_list_spec.js @@ -1,29 +1,51 @@ -/* global List */ -/* global ListIssue */ - -import Vue from 'vue'; -import MockAdapter from 'axios-mock-adapter'; -import waitForPromises from 'helpers/wait_for_promises'; -import axios from '~/lib/utils/axios_utils'; +import Vuex from 'vuex'; +import { useFakeRequestAnimationFrame } from 'helpers/fake_request_animation_frame'; +import { createLocalVue, mount } from '@vue/test-utils'; import eventHub from '~/boards/eventhub'; import BoardList from '~/boards/components/board_list.vue'; -import '~/boards/models/issue'; -import '~/boards/models/list'; -import { listObj, boardsMockInterceptor } from './mock_data'; -import store from '~/boards/stores'; -import boardsStore from '~/boards/stores/boards_store'; - -const createComponent = ({ done, listIssueProps = {}, componentProps = {}, listProps = {} }) => { - const el = document.createElement('div'); - - 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({ +import BoardCard from '~/boards/components/board_card.vue'; +import { mockList, mockIssuesByListId, issues, mockIssues } from './mock_data'; +import defaultState from '~/boards/stores/state'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +const actions = { + fetchIssuesForList: jest.fn(), +}; + +const createStore = (state = defaultState) => { + return new Vuex.Store({ + state, + actions, + }); +}; + +const createComponent = ({ + listIssueProps = {}, + componentProps = {}, + listProps = {}, + state = {}, +} = {}) => { + const store = createStore({ + issuesByListId: mockIssuesByListId, + issues, + pageInfoByListId: { + 'gid://gitlab/List/1': { hasNextPage: true }, + 'gid://gitlab/List/2': {}, + }, + listsFlags: { + 'gid://gitlab/List/1': {}, + 'gid://gitlab/List/2': {}, + }, + ...state, + }); + + const list = { + ...mockList, + ...listProps, + }; + const issue = { title: 'Testing', id: 1, iid: 1, @@ -31,244 +53,214 @@ const createComponent = ({ done, listIssueProps = {}, componentProps = {}, listP labels: [], assignees: [], ...listIssueProps, - }); - if (!Object.prototype.hasOwnProperty.call(listProps, 'issuesSize')) { - list.issuesSize = 1; + }; + if (!Object.prototype.hasOwnProperty.call(listProps, 'issuesCount')) { + list.issuesCount = 1; } - list.issues.push(issue); - const component = new BoardListComp({ - el, - store, + const component = mount(BoardList, { + localVue, propsData: { disabled: false, list, - issues: list.issues, + issues: [issue], + canAdminList: true, ...componentProps, }, + store, provide: { groupId: null, rootPath: '/', + weightFeatureAvailable: false, + boardWeight: null, }, - }).$mount(); - - Vue.nextTick(() => { - done(); }); - return { component, mock }; + return component; }; 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); - } - } + let wrapper; + const findByTestId = (testId) => wrapper.find(`[data-testid="${testId}"]`); + useFakeRequestAnimationFrame(); - describe('When Expanded', () => { - beforeEach((done) => { - getIssues = jest.spyOn(List.prototype, 'getIssues').mockReturnValue(new Promise(() => {})); - ({ mock, component } = createComponent({ done })); - }); - - afterEach(() => { - mock.restore(); - component.$destroy(); - }); + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); - it('loads first page of issues', () => { - return waitForPromises().then(() => { - expect(getIssues).toHaveBeenCalled(); - }); + describe('When Expanded', () => { + beforeEach(() => { + wrapper = createComponent(); }); it('renders component', () => { - expect(component.$el.classList.contains('board-list-component')).toBe(true); + expect(wrapper.find('.board-list-component').exists()).toBe(true); }); it('renders loading icon', () => { - component.list.loading = true; - - return Vue.nextTick().then(() => { - expect(component.$el.querySelector('.board-list-loading')).not.toBeNull(); + wrapper = createComponent({ + state: { listsFlags: { 'gid://gitlab/List/1': { isLoading: true } } }, }); + + expect(findByTestId('board_list_loading').exists()).toBe(true); }); it('renders issues', () => { - expect(component.$el.querySelectorAll('.board-card').length).toBe(1); + expect(wrapper.findAll(BoardCard).length).toBe(1); }); it('sets data attribute with issue id', () => { - expect(component.$el.querySelector('.board-card').getAttribute('data-issue-id')).toBe('1'); + expect(wrapper.find('.board-card').attributes('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(); + it('shows new issue form', async () => { + wrapper.vm.toggleForm(); - expect(component.$el.querySelector('.is-smaller')).not.toBeNull(); - }); + await wrapper.vm.$nextTick(); + expect(wrapper.find('.board-new-issue-form').exists()).toBe(true); }); - 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(); + it('shows new issue form after eventhub event', async () => { + eventHub.$emit(`toggle-issue-form-${wrapper.vm.list.id}`); - expect(component.$el.querySelector('.is-smaller')).not.toBeNull(); - }); + await wrapper.vm.$nextTick(); + expect(wrapper.find('.board-new-issue-form').exists()).toBe(true); }); it('does not show new issue form for closed list', () => { - component.list.type = 'closed'; - component.toggleForm(); + wrapper.setProps({ list: { type: 'closed' } }); + wrapper.vm.toggleForm(); - return Vue.nextTick().then(() => { - expect(component.$el.querySelector('.board-new-issue-form')).toBeNull(); - }); + expect(wrapper.find('.board-new-issue-form').exists()).toBe(false); }); - it('shows count list item', () => { - component.showCount = true; + it('shows count list item', async () => { + wrapper.vm.showCount = true; - return Vue.nextTick().then(() => { - expect(component.$el.querySelector('.board-list-count')).not.toBeNull(); + await wrapper.vm.$nextTick(); + expect(wrapper.find('.board-list-count').exists()).toBe(true); - expect(component.$el.querySelector('.board-list-count').textContent.trim()).toBe( - 'Showing all issues', - ); - }); + expect(wrapper.find('.board-list-count').text()).toBe('Showing all issues'); }); - it('sets data attribute with invalid id', () => { - component.showCount = true; + it('sets data attribute with invalid id', async () => { + wrapper.vm.showCount = true; - return Vue.nextTick().then(() => { - expect(component.$el.querySelector('.board-list-count').getAttribute('data-issue-id')).toBe( - '-1', - ); - }); + await wrapper.vm.$nextTick(); + expect(wrapper.find('.board-list-count').attributes('data-issue-id')).toBe('-1'); }); - it('shows how many more issues to load', () => { - component.showCount = true; - component.list.issuesSize = 20; + it('shows how many more issues to load', async () => { + wrapper.vm.showCount = true; + wrapper.setProps({ list: { issuesCount: 20 } }); - return Vue.nextTick().then(() => { - expect(component.$el.querySelector('.board-list-count').textContent.trim()).toBe( - 'Showing 1 of 20 issues', - ); - }); + await wrapper.vm.$nextTick(); + expect(wrapper.find('.board-list-count').text()).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(); + describe('load more issues', () => { + beforeEach(() => { + wrapper = createComponent({ + listProps: { issuesCount: 25 }, }); }); - it('does not load issues if already loading', () => { - component.list.nextPage = jest - .spyOn(component.list, 'nextPage') - .mockReturnValue(new Promise(() => {})); - - component.onScroll(); - component.onScroll(); + it('loads more issues after scrolling', () => { + wrapper.vm.listRef.dispatchEvent(new Event('scroll')); - return waitForPromises().then(() => { - expect(component.list.nextPage).toHaveBeenCalledTimes(1); - }); + expect(actions.fetchIssuesForList).toHaveBeenCalled(); }); - 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(); + it('does not load issues if already loading', () => { + wrapper = createComponent({ + state: { listsFlags: { 'gid://gitlab/List/1': { isLoadingMore: true } } }, }); - }); - }); + wrapper.vm.listRef.dispatchEvent(new Event('scroll')); - 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); + expect(actions.fetchIssuesForList).not.toHaveBeenCalled(); }); - afterEach(() => { - mock.restore(); - component.$destroy(); - }); - - it('does not load all issues', () => { - return waitForPromises().then(() => { - // Initial getIssues from list constructor - expect(getIssues).toHaveBeenCalledTimes(1); + it('shows loading more spinner', async () => { + wrapper = createComponent({ + state: { listsFlags: { 'gid://gitlab/List/1': { isLoadingMore: true } } }, }); + wrapper.vm.showCount = true; + + await wrapper.vm.$nextTick(); + expect(wrapper.find('.board-list-count .gl-spinner').exists()).toBe(true); }); }); describe('max issue count warning', () => { - beforeEach((done) => { - ({ mock, component } = createComponent({ - done, - listProps: { type: 'closed', collapsed: true, issuesSize: 50 }, - })); - }); - - afterEach(() => { - mock.restore(); - component.$destroy(); + beforeEach(() => { + wrapper = createComponent({ + listProps: { issuesCount: 50 }, + }); }); describe('when issue count exceeds max issue count', () => { - it('sets background to bg-danger-100', () => { - component.list.issuesSize = 4; - component.list.maxIssueCount = 3; + it('sets background to bg-danger-100', async () => { + wrapper.setProps({ list: { issuesCount: 4, maxIssueCount: 3 } }); - return Vue.nextTick().then(() => { - expect(component.$el.querySelector('.bg-danger-100')).not.toBeNull(); - }); + await wrapper.vm.$nextTick(); + expect(wrapper.find('.bg-danger-100').exists()).toBe(true); }); }); 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; + wrapper.setProps({ list: { issuesCount: 2, maxIssueCount: 3 } }); - return Vue.nextTick().then(() => { - expect(component.$el.querySelector('.bg-danger-100')).toBeNull(); - }); + expect(wrapper.find('.bg-danger-100').exists()).toBe(false); }); }); describe('when list max issue count is 0', () => { it('does not sets background to bg-danger-100', () => { - component.list.maxIssueCount = 0; + wrapper.setProps({ list: { maxIssueCount: 0 } }); + + expect(wrapper.find('.bg-danger-100').exists()).toBe(false); + }); + }); + }); - return Vue.nextTick().then(() => { - expect(component.$el.querySelector('.bg-danger-100')).toBeNull(); + describe('drag & drop issue', () => { + beforeEach(() => { + wrapper = createComponent(); + }); + + describe('handleDragOnStart', () => { + it('adds a class `is-dragging` to document body', () => { + expect(document.body.classList.contains('is-dragging')).toBe(false); + + findByTestId('tree-root-wrapper').vm.$emit('start'); + + expect(document.body.classList.contains('is-dragging')).toBe(true); + }); + }); + + describe('handleDragOnEnd', () => { + it('removes class `is-dragging` from document body', () => { + jest.spyOn(wrapper.vm, 'moveIssue').mockImplementation(() => {}); + document.body.classList.add('is-dragging'); + + findByTestId('tree-root-wrapper').vm.$emit('end', { + oldIndex: 1, + newIndex: 0, + item: { + dataset: { + issueId: mockIssues[0].id, + issueIid: mockIssues[0].iid, + issuePath: mockIssues[0].referencePath, + }, + }, + to: { children: [], dataset: { listId: 'gid://gitlab/List/1' } }, + from: { dataset: { listId: 'gid://gitlab/List/2' } }, }); + + expect(document.body.classList.contains('is-dragging')).toBe(false); }); }); }); diff --git a/spec/frontend/boards/board_new_issue_spec.js b/spec/frontend/boards/board_new_issue_deprecated_spec.js index 3eebfeca965..8236b468189 100644 --- a/spec/frontend/boards/board_new_issue_spec.js +++ b/spec/frontend/boards/board_new_issue_deprecated_spec.js @@ -4,7 +4,7 @@ import Vue from 'vue'; import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; -import boardNewIssue from '~/boards/components/board_new_issue.vue'; +import boardNewIssue from '~/boards/components/board_new_issue_deprecated.vue'; import boardsStore from '~/boards/stores/boards_store'; import '~/boards/models/list'; diff --git a/spec/frontend/boards/components/board_column_new_spec.js b/spec/frontend/boards/components/board_column_deprecated_spec.js index 81c0e60f931..a703caca4eb 100644 --- a/spec/frontend/boards/components/board_column_new_spec.js +++ b/spec/frontend/boards/components/board_column_deprecated_spec.js @@ -1,40 +1,65 @@ +import Vue from 'vue'; import { shallowMount } from '@vue/test-utils'; +import AxiosMockAdapter from 'axios-mock-adapter'; +import { TEST_HOST } from 'helpers/test_constants'; import { listObj } from 'jest/boards/mock_data'; -import BoardColumn from '~/boards/components/board_column_new.vue'; +import Board from '~/boards/components/board_column_deprecated.vue'; +import List from '~/boards/models/list'; import { ListType } from '~/boards/constants'; -import { createStore } from '~/boards/stores'; +import axios from '~/lib/utils/axios_utils'; describe('Board Column Component', () => { let wrapper; - let store; + let axiosMock; + + beforeEach(() => { + window.gon = {}; + axiosMock = new AxiosMockAdapter(axios); + axiosMock.onGet(`${TEST_HOST}/lists/1/issues`).reply(200, { issues: [] }); + }); afterEach(() => { + axiosMock.restore(); + wrapper.destroy(); - wrapper = null; + + localStorage.clear(); }); - const createComponent = ({ listType = ListType.backlog, collapsed = false } = {}) => { + const createComponent = ({ + listType = ListType.backlog, + collapsed = false, + withLocalStorage = true, + } = {}) => { const boardId = '1'; const listMock = { ...listObj, - listType, + list_type: listType, collapsed, }; if (listType === ListType.assignee) { delete listMock.label; - listMock.assignee = {}; + listMock.user = {}; } - store = createStore(); + // Making List reactive + const list = Vue.observable(new List(listMock)); - wrapper = shallowMount(BoardColumn, { - store, + if (withLocalStorage) { + localStorage.setItem( + `boards.${boardId}.${list.type}.${list.id}.expanded`, + (!collapsed).toString(), + ); + } + + wrapper = shallowMount(Board, { propsData: { + boardId, disabled: false, - list: listMock, + list, }, provide: { boardId, @@ -57,7 +82,7 @@ describe('Board Column Component', () => { it('has class is-collapsed when list is collapsed', () => { createComponent({ collapsed: false }); - expect(isCollapsed()).toBe(false); + expect(wrapper.vm.list.isExpanded).toBe(true); }); it('does not have class is-collapsed when list is expanded', () => { diff --git a/spec/frontend/boards/components/board_column_spec.js b/spec/frontend/boards/components/board_column_spec.js index ba11225676b..1dcdad2b492 100644 --- a/spec/frontend/boards/components/board_column_spec.js +++ b/spec/frontend/boards/components/board_column_spec.js @@ -1,65 +1,40 @@ -import Vue from 'vue'; import { shallowMount } from '@vue/test-utils'; -import AxiosMockAdapter from 'axios-mock-adapter'; -import { TEST_HOST } from 'helpers/test_constants'; import { listObj } from 'jest/boards/mock_data'; -import Board from '~/boards/components/board_column.vue'; -import List from '~/boards/models/list'; +import BoardColumn from '~/boards/components/board_column.vue'; import { ListType } from '~/boards/constants'; -import axios from '~/lib/utils/axios_utils'; +import { createStore } from '~/boards/stores'; 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: [] }); - }); + let store; afterEach(() => { - axiosMock.restore(); - wrapper.destroy(); - - localStorage.clear(); + wrapper = null; }); - const createComponent = ({ - listType = ListType.backlog, - collapsed = false, - withLocalStorage = true, - } = {}) => { + const createComponent = ({ listType = ListType.backlog, collapsed = false } = {}) => { const boardId = '1'; const listMock = { ...listObj, - list_type: listType, + listType, collapsed, }; if (listType === ListType.assignee) { delete listMock.label; - listMock.user = {}; + listMock.assignee = {}; } - // Making List reactive - const list = Vue.observable(new List(listMock)); + store = createStore(); - if (withLocalStorage) { - localStorage.setItem( - `boards.${boardId}.${list.type}.${list.id}.expanded`, - (!collapsed).toString(), - ); - } - - wrapper = shallowMount(Board, { + wrapper = shallowMount(BoardColumn, { + store, propsData: { - boardId, disabled: false, - list, + list: listMock, }, provide: { boardId, @@ -82,7 +57,7 @@ describe('Board Column Component', () => { it('has class is-collapsed when list is collapsed', () => { createComponent({ collapsed: false }); - expect(wrapper.vm.list.isExpanded).toBe(true); + expect(isCollapsed()).toBe(false); }); it('does not have class is-collapsed when list is expanded', () => { diff --git a/spec/frontend/boards/components/board_content_spec.js b/spec/frontend/boards/components/board_content_spec.js index 291013c561e..98be02d7dbf 100644 --- a/spec/frontend/boards/components/board_content_spec.js +++ b/spec/frontend/boards/components/board_content_spec.js @@ -4,7 +4,7 @@ import { GlAlert } from '@gitlab/ui'; import Draggable from 'vuedraggable'; import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue'; import getters from 'ee_else_ce/boards/stores/getters'; -import BoardColumn from '~/boards/components/board_column.vue'; +import BoardColumnDeprecated from '~/boards/components/board_column_deprecated.vue'; import { mockLists, mockListsWithModel } from '../mock_data'; import BoardContent from '~/boards/components/board_content.vue'; @@ -17,6 +17,7 @@ const actions = { describe('BoardContent', () => { let wrapper; + window.gon = {}; const defaultState = { isShowingEpicsSwimlanes: false, @@ -56,10 +57,12 @@ describe('BoardContent', () => { wrapper.destroy(); }); - it('renders a BoardColumn component per list', () => { + it('renders a BoardColumnDeprecated component per list', () => { createComponent(); - expect(wrapper.findAll(BoardColumn)).toHaveLength(mockLists.length); + expect(wrapper.findAllComponents(BoardColumnDeprecated)).toHaveLength( + mockListsWithModel.length, + ); }); it('does not display EpicsSwimlanes component', () => { @@ -70,6 +73,13 @@ describe('BoardContent', () => { }); describe('graphqlBoardLists feature flag enabled', () => { + beforeEach(() => { + createComponent({ graphqlBoardListsEnabled: true }); + gon.features = { + graphqlBoardLists: true, + }; + }); + describe('can admin list', () => { beforeEach(() => { createComponent({ graphqlBoardListsEnabled: true, props: { canAdminList: true } }); @@ -85,7 +95,7 @@ describe('BoardContent', () => { createComponent({ graphqlBoardListsEnabled: true, props: { canAdminList: false } }); }); - it('renders draggable component', () => { + it('does not render draggable component', () => { expect(wrapper.find(Draggable).exists()).toBe(false); }); }); diff --git a/spec/frontend/boards/components/board_list_header_new_spec.js b/spec/frontend/boards/components/board_list_header_deprecated_spec.js index 7fae2108e52..6207724e6a9 100644 --- a/spec/frontend/boards/components/board_list_header_new_spec.js +++ b/spec/frontend/boards/components/board_list_header_deprecated_spec.js @@ -1,23 +1,28 @@ -import Vuex from 'vuex'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; - -import { mockLabelList } from 'jest/boards/mock_data'; -import BoardListHeader from '~/boards/components/board_list_header_new.vue'; +import Vue from 'vue'; +import { shallowMount } from '@vue/test-utils'; +import AxiosMockAdapter from 'axios-mock-adapter'; + +import { TEST_HOST } from 'helpers/test_constants'; +import { listObj } from 'jest/boards/mock_data'; +import BoardListHeader from '~/boards/components/board_list_header_deprecated.vue'; +import List from '~/boards/models/list'; import { ListType } from '~/boards/constants'; - -const localVue = createLocalVue(); - -localVue.use(Vuex); +import axios from '~/lib/utils/axios_utils'; describe('Board List Header Component', () => { let wrapper; - let store; + let axiosMock; - const updateListSpy = jest.fn(); + beforeEach(() => { + window.gon = {}; + axiosMock = new AxiosMockAdapter(axios); + axiosMock.onGet(`${TEST_HOST}/lists/1/issues`).reply(200, { issues: [] }); + }); afterEach(() => { + axiosMock.restore(); + wrapper.destroy(); - wrapper = null; localStorage.clear(); }); @@ -26,54 +31,45 @@ describe('Board List Header Component', () => { listType = ListType.backlog, collapsed = false, withLocalStorage = true, - currentUserId = null, } = {}) => { const boardId = '1'; const listMock = { - ...mockLabelList, - listType, + ...listObj, + list_type: listType, collapsed, }; if (listType === ListType.assignee) { delete listMock.label; - listMock.assignee = {}; + listMock.user = {}; } + // Making List reactive + const list = Vue.observable(new List(listMock)); + if (withLocalStorage) { localStorage.setItem( - `boards.${boardId}.${listMock.listType}.${listMock.id}.expanded`, + `boards.${boardId}.${list.type}.${list.id}.expanded`, (!collapsed).toString(), ); } - store = new Vuex.Store({ - state: {}, - actions: { updateList: updateListSpy }, - getters: {}, - }); - wrapper = shallowMount(BoardListHeader, { - store, - localVue, propsData: { disabled: false, - list: listMock, + list, }, provide: { boardId, - weightFeatureAvailable: false, - currentUserId, }, }); }; - const isCollapsed = () => wrapper.vm.list.collapsed; - const isExpanded = () => !isCollapsed; + const isCollapsed = () => !wrapper.props().list.isExpanded; + const isExpanded = () => wrapper.vm.list.isExpanded; const findAddIssueButton = () => wrapper.find({ ref: 'newIssueBtn' }); - const findTitle = () => wrapper.find('.board-title'); const findCaret = () => wrapper.find('.board-title-caret'); describe('Add issue button', () => { @@ -93,8 +89,6 @@ describe('Board List Header Component', () => { }); it('has a test for each list type', () => { - createComponent(); - Object.values(ListType).forEach((value) => { expect([...hasAddButton, ...hasNoAddButton]).toContain(value); }); @@ -108,80 +102,64 @@ describe('Board List Header Component', () => { }); describe('expanding / collapsing the column', () => { - it('does not collapse when clicking the header', async () => { + it('does not collapse when clicking the header', () => { createComponent(); expect(isCollapsed()).toBe(false); - wrapper.find('[data-testid="board-list-header"]').trigger('click'); - await wrapper.vm.$nextTick(); - - expect(isCollapsed()).toBe(false); + return wrapper.vm.$nextTick().then(() => { + expect(isCollapsed()).toBe(false); + }); }); - it('collapses expanded Column when clicking the collapse icon', async () => { + it('collapses expanded Column when clicking the collapse icon', () => { createComponent(); - expect(isCollapsed()).toBe(false); - + expect(isExpanded()).toBe(true); findCaret().vm.$emit('click'); - await wrapper.vm.$nextTick(); - - expect(isCollapsed()).toBe(true); + return wrapper.vm.$nextTick().then(() => { + expect(isCollapsed()).toBe(true); + }); }); - it('expands collapsed Column when clicking the expand icon', async () => { + it('expands collapsed Column when clicking the expand icon', () => { createComponent({ collapsed: true }); expect(isCollapsed()).toBe(true); - findCaret().vm.$emit('click'); - await wrapper.vm.$nextTick(); - - expect(isCollapsed()).toBe(false); + return wrapper.vm.$nextTick().then(() => { + expect(isCollapsed()).toBe(false); + }); }); - it("when logged in it calls list update and doesn't set localStorage", async () => { - createComponent({ withLocalStorage: false, currentUserId: 1 }); - - findCaret().vm.$emit('click'); - await wrapper.vm.$nextTick(); - - expect(updateListSpy).toHaveBeenCalledTimes(1); - expect(localStorage.getItem(`${wrapper.vm.uniqueKey}.expanded`)).toBe(null); - }); + it("when logged in it calls list update and doesn't set localStorage", () => { + jest.spyOn(List.prototype, 'update'); + window.gon.current_user_id = 1; - it("when logged out it doesn't call list update and sets localStorage", async () => { - createComponent(); + createComponent({ withLocalStorage: false }); findCaret().vm.$emit('click'); - await wrapper.vm.$nextTick(); - expect(updateListSpy).not.toHaveBeenCalled(); - expect(localStorage.getItem(`${wrapper.vm.uniqueKey}.expanded`)).toBe(String(isExpanded())); + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.list.update).toHaveBeenCalledTimes(1); + expect(localStorage.getItem(`${wrapper.vm.uniqueKey}.expanded`)).toBe(null); + }); }); - }); - describe('user can drag', () => { - const cannotDragList = [ListType.backlog, ListType.closed]; - const canDragList = [ListType.label, ListType.milestone, ListType.assignee]; + it("when logged out it doesn't call list update and sets localStorage", () => { + jest.spyOn(List.prototype, 'update'); - it.each(cannotDragList)( - 'does not have user-can-drag-class so user cannot drag list', - (listType) => { - createComponent({ listType }); - - expect(findTitle().classes()).not.toContain('user-can-drag'); - }, - ); + createComponent(); - it.each(canDragList)('has user-can-drag-class so user can drag list', (listType) => { - createComponent({ listType }); + findCaret().vm.$emit('click'); - expect(findTitle().classes()).toContain('user-can-drag'); + 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_list_header_spec.js b/spec/frontend/boards/components/board_list_header_spec.js index 58469a1ac9a..357d05ced02 100644 --- a/spec/frontend/boards/components/board_list_header_spec.js +++ b/spec/frontend/boards/components/board_list_header_spec.js @@ -1,28 +1,23 @@ -import Vue from 'vue'; -import { shallowMount } from '@vue/test-utils'; -import AxiosMockAdapter from 'axios-mock-adapter'; +import Vuex from 'vuex'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; -import { TEST_HOST } from 'helpers/test_constants'; -import { listObj } from 'jest/boards/mock_data'; +import { mockLabelList } from 'jest/boards/mock_data'; import BoardListHeader from '~/boards/components/board_list_header.vue'; -import List from '~/boards/models/list'; import { ListType } from '~/boards/constants'; -import axios from '~/lib/utils/axios_utils'; + +const localVue = createLocalVue(); + +localVue.use(Vuex); describe('Board List Header Component', () => { let wrapper; - let axiosMock; + let store; - beforeEach(() => { - window.gon = {}; - axiosMock = new AxiosMockAdapter(axios); - axiosMock.onGet(`${TEST_HOST}/lists/1/issues`).reply(200, { issues: [] }); - }); + const updateListSpy = jest.fn(); afterEach(() => { - axiosMock.restore(); - wrapper.destroy(); + wrapper = null; localStorage.clear(); }); @@ -31,45 +26,54 @@ describe('Board List Header Component', () => { listType = ListType.backlog, collapsed = false, withLocalStorage = true, + currentUserId = null, } = {}) => { const boardId = '1'; const listMock = { - ...listObj, - list_type: listType, + ...mockLabelList, + listType, collapsed, }; if (listType === ListType.assignee) { delete listMock.label; - listMock.user = {}; + listMock.assignee = {}; } - // Making List reactive - const list = Vue.observable(new List(listMock)); - if (withLocalStorage) { localStorage.setItem( - `boards.${boardId}.${list.type}.${list.id}.expanded`, + `boards.${boardId}.${listMock.listType}.${listMock.id}.expanded`, (!collapsed).toString(), ); } + store = new Vuex.Store({ + state: {}, + actions: { updateList: updateListSpy }, + getters: {}, + }); + wrapper = shallowMount(BoardListHeader, { + store, + localVue, propsData: { disabled: false, - list, + list: listMock, }, provide: { boardId, + weightFeatureAvailable: false, + currentUserId, }, }); }; - const isCollapsed = () => !wrapper.props().list.isExpanded; - const isExpanded = () => wrapper.vm.list.isExpanded; + const isCollapsed = () => wrapper.vm.list.collapsed; + const isExpanded = () => !isCollapsed; const findAddIssueButton = () => wrapper.find({ ref: 'newIssueBtn' }); + const findTitle = () => wrapper.find('.board-title'); const findCaret = () => wrapper.find('.board-title-caret'); describe('Add issue button', () => { @@ -89,6 +93,8 @@ describe('Board List Header Component', () => { }); it('has a test for each list type', () => { + createComponent(); + Object.values(ListType).forEach((value) => { expect([...hasAddButton, ...hasNoAddButton]).toContain(value); }); @@ -102,64 +108,80 @@ describe('Board List Header Component', () => { }); describe('expanding / collapsing the column', () => { - it('does not collapse when clicking the header', () => { + it('does not collapse when clicking the header', async () => { createComponent(); expect(isCollapsed()).toBe(false); + wrapper.find('[data-testid="board-list-header"]').trigger('click'); - return wrapper.vm.$nextTick().then(() => { - expect(isCollapsed()).toBe(false); - }); + await wrapper.vm.$nextTick(); + + expect(isCollapsed()).toBe(false); }); - it('collapses expanded Column when clicking the collapse icon', () => { + it('collapses expanded Column when clicking the collapse icon', async () => { createComponent(); - expect(isExpanded()).toBe(true); + expect(isCollapsed()).toBe(false); + findCaret().vm.$emit('click'); - return wrapper.vm.$nextTick().then(() => { - expect(isCollapsed()).toBe(true); - }); + await wrapper.vm.$nextTick(); + + expect(isCollapsed()).toBe(true); }); - it('expands collapsed Column when clicking the expand icon', () => { + it('expands collapsed Column when clicking the expand icon', async () => { createComponent({ collapsed: true }); expect(isCollapsed()).toBe(true); + findCaret().vm.$emit('click'); - return wrapper.vm.$nextTick().then(() => { - expect(isCollapsed()).toBe(false); - }); - }); + await wrapper.vm.$nextTick(); - it("when logged in it calls list update and doesn't set localStorage", () => { - jest.spyOn(List.prototype, 'update'); - window.gon.current_user_id = 1; + expect(isCollapsed()).toBe(false); + }); - createComponent({ withLocalStorage: false }); + it("when logged in it calls list update and doesn't set localStorage", async () => { + createComponent({ withLocalStorage: false, currentUserId: 1 }); findCaret().vm.$emit('click'); + await wrapper.vm.$nextTick(); - return wrapper.vm.$nextTick().then(() => { - expect(wrapper.vm.list.update).toHaveBeenCalledTimes(1); - expect(localStorage.getItem(`${wrapper.vm.uniqueKey}.expanded`)).toBe(null); - }); + expect(updateListSpy).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'); - + it("when logged out it doesn't call list update and sets localStorage", async () => { createComponent(); findCaret().vm.$emit('click'); + await wrapper.vm.$nextTick(); - return wrapper.vm.$nextTick().then(() => { - expect(wrapper.vm.list.update).not.toHaveBeenCalled(); - expect(localStorage.getItem(`${wrapper.vm.uniqueKey}.expanded`)).toBe(String(isExpanded())); - }); + expect(updateListSpy).not.toHaveBeenCalled(); + expect(localStorage.getItem(`${wrapper.vm.uniqueKey}.expanded`)).toBe(String(isExpanded())); + }); + }); + + describe('user can drag', () => { + const cannotDragList = [ListType.backlog, ListType.closed]; + const canDragList = [ListType.label, ListType.milestone, ListType.assignee]; + + it.each(cannotDragList)( + 'does not have user-can-drag-class so user cannot drag list', + (listType) => { + createComponent({ listType }); + + expect(findTitle().classes()).not.toContain('user-can-drag'); + }, + ); + + it.each(canDragList)('has user-can-drag-class so user can drag list', (listType) => { + createComponent({ listType }); + + expect(findTitle().classes()).toContain('user-can-drag'); }); }); }); diff --git a/spec/frontend/boards/components/board_new_issue_new_spec.js b/spec/frontend/boards/components/board_new_issue_spec.js index 3265ef40a2e..5a01221a5be 100644 --- a/spec/frontend/boards/components/board_new_issue_new_spec.js +++ b/spec/frontend/boards/components/board_new_issue_spec.js @@ -1,6 +1,6 @@ import Vuex from 'vuex'; import { shallowMount, createLocalVue } from '@vue/test-utils'; -import BoardNewIssue from '~/boards/components/board_new_issue_new.vue'; +import BoardNewIssue from '~/boards/components/board_new_issue.vue'; import '~/boards/models/list'; import { mockList, mockGroupProjects } from '../mock_data'; diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js index a963a92a366..d5cfb9b7d07 100644 --- a/spec/frontend/boards/mock_data.js +++ b/spec/frontend/boards/mock_data.js @@ -285,7 +285,7 @@ export const setMockEndpoints = (opts = {}) => { export const mockList = { id: 'gid://gitlab/List/1', title: 'Backlog', - position: null, + position: -Infinity, listType: 'backlog', collapsed: false, label: null, diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js index 72f81d6db29..e4209cd5e55 100644 --- a/spec/frontend/boards/stores/actions_spec.js +++ b/spec/frontend/boards/stores/actions_spec.js @@ -16,8 +16,14 @@ import * as types from '~/boards/stores/mutation_types'; import { inactiveId } from '~/boards/constants'; import issueMoveListMutation from '~/boards/graphql/issue_move_list.mutation.graphql'; import destroyBoardListMutation from '~/boards/graphql/board_list_destroy.mutation.graphql'; +import issueCreateMutation from '~/boards/graphql/issue_create.mutation.graphql'; import updateAssignees from '~/vue_shared/components/sidebar/queries/updateAssignees.mutation.graphql'; -import { fullBoardId, formatListIssues, formatBoardLists } from '~/boards/boards_util'; +import { + fullBoardId, + formatListIssues, + formatBoardLists, + formatIssueInput, +} from '~/boards/boards_util'; import createFlash from '~/flash'; jest.mock('~/flash'); @@ -728,6 +734,27 @@ describe('createNewIssue', () => { const state = { boardType: 'group', fullPath: 'gitlab-org/gitlab', + boardConfig: { + labelIds: [], + assigneeId: null, + milestoneId: -1, + }, + }; + + const stateWithBoardConfig = { + boardConfig: { + labels: [ + { + id: 5, + title: 'Test', + color: '#ff0000', + description: 'testing;', + textColor: 'white', + }, + ], + assigneeId: 2, + milestoneId: 3, + }, }; it('should return issue from API on success', async () => { @@ -744,11 +771,59 @@ describe('createNewIssue', () => { expect(result).toEqual(mockIssue); }); + it('should add board scope to the issue being created', async () => { + jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ + data: { + createIssue: { + issue: mockIssue, + errors: [], + }, + }, + }); + + await actions.createNewIssue({ state: stateWithBoardConfig }, mockIssue); + expect(gqlClient.mutate).toHaveBeenCalledWith({ + mutation: issueCreateMutation, + variables: { + input: formatIssueInput(mockIssue, stateWithBoardConfig.boardConfig), + }, + }); + }); + + it('should add board scope by merging attributes to the issue being created', async () => { + const issue = { + ...mockIssue, + assigneeIds: ['gid://gitlab/User/1'], + labelIds: ['gid://gitlab/GroupLabel/4'], + }; + + jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ + data: { + createIssue: { + issue, + errors: [], + }, + }, + }); + + const payload = formatIssueInput(issue, stateWithBoardConfig.boardConfig); + + await actions.createNewIssue({ state: stateWithBoardConfig }, issue); + expect(gqlClient.mutate).toHaveBeenCalledWith({ + mutation: issueCreateMutation, + variables: { + input: formatIssueInput(issue, stateWithBoardConfig.boardConfig), + }, + }); + expect(payload.labelIds).toEqual(['gid://gitlab/GroupLabel/4', 'gid://gitlab/GroupLabel/5']); + expect(payload.assigneeIds).toEqual(['gid://gitlab/User/1', 'gid://gitlab/User/2']); + }); + it('should commit CREATE_ISSUE_FAILURE mutation when API returns an error', (done) => { jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ data: { createIssue: { - issue: {}, + issue: mockIssue, errors: [{ foo: 'bar' }], }, }, |