From be2f4c5788975597dd7be1c8a3525549770c1216 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 24 Mar 2020 03:09:28 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- spec/frontend/boards/board_blank_state_spec.js | 95 ++++++++++ spec/frontend/boards/board_new_issue_spec.js | 202 +++++++++++++++++++++ spec/frontend/boards/components/board_form_spec.js | 47 +++++ spec/frontend/boards/issue_spec.js | 197 ++++++++++++++++++++ spec/frontend/boards/mock_data.js | 53 ++++++ 5 files changed, 594 insertions(+) create mode 100644 spec/frontend/boards/board_blank_state_spec.js create mode 100644 spec/frontend/boards/board_new_issue_spec.js create mode 100644 spec/frontend/boards/components/board_form_spec.js create mode 100644 spec/frontend/boards/issue_spec.js (limited to 'spec/frontend/boards') diff --git a/spec/frontend/boards/board_blank_state_spec.js b/spec/frontend/boards/board_blank_state_spec.js new file mode 100644 index 00000000000..3ffdda52f58 --- /dev/null +++ b/spec/frontend/boards/board_blank_state_spec.js @@ -0,0 +1,95 @@ +import Vue from 'vue'; +import boardsStore from '~/boards/stores/boards_store'; +import BoardBlankState from '~/boards/components/board_blank_state.vue'; + +describe('Boards blank state', () => { + let vm; + let fail = false; + + beforeEach(done => { + const Comp = Vue.extend(BoardBlankState); + + boardsStore.create(); + + jest.spyOn(boardsStore, 'addList').mockImplementation(); + jest.spyOn(boardsStore, 'removeList').mockImplementation(); + jest.spyOn(boardsStore, 'generateDefaultLists').mockImplementation( + () => + new Promise((resolve, reject) => { + if (fail) { + reject(); + } else { + resolve({ + data: [ + { + id: 1, + title: 'To Do', + label: { id: 1 }, + }, + { + id: 2, + title: 'Doing', + label: { id: 2 }, + }, + ], + }); + } + }), + ); + + vm = new Comp(); + + setImmediate(() => { + vm.$mount(); + done(); + }); + }); + + it('renders pre-defined labels', () => { + expect(vm.$el.querySelectorAll('.board-blank-state-list li').length).toBe(2); + + expect(vm.$el.querySelectorAll('.board-blank-state-list li')[0].textContent.trim()).toEqual( + 'To Do', + ); + + expect(vm.$el.querySelectorAll('.board-blank-state-list li')[1].textContent.trim()).toEqual( + 'Doing', + ); + }); + + it('clears blank state', done => { + vm.$el.querySelector('.btn-default').click(); + + setImmediate(() => { + expect(boardsStore.welcomeIsHidden()).toBeTruthy(); + + done(); + }); + }); + + it('creates pre-defined labels', done => { + vm.$el.querySelector('.btn-success').click(); + + setImmediate(() => { + expect(boardsStore.addList).toHaveBeenCalledTimes(2); + expect(boardsStore.addList).toHaveBeenCalledWith(expect.objectContaining({ title: 'To Do' })); + + expect(boardsStore.addList).toHaveBeenCalledWith(expect.objectContaining({ title: 'Doing' })); + + done(); + }); + }); + + it('resets the store if request fails', done => { + fail = true; + + vm.$el.querySelector('.btn-success').click(); + + setImmediate(() => { + expect(boardsStore.welcomeIsHidden()).toBeFalsy(); + expect(boardsStore.removeList).toHaveBeenCalledWith(undefined, 'label'); + + done(); + }); + }); +}); diff --git a/spec/frontend/boards/board_new_issue_spec.js b/spec/frontend/boards/board_new_issue_spec.js new file mode 100644 index 00000000000..4eb7f0c131e --- /dev/null +++ b/spec/frontend/boards/board_new_issue_spec.js @@ -0,0 +1,202 @@ +/* global List */ + +import $ from 'jquery'; +import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import boardNewIssue from '~/boards/components/board_new_issue.vue'; +import boardsStore from '~/boards/stores/boards_store'; + +import '~/boards/models/list'; +import { listObj, boardsMockInterceptor } from './mock_data'; + +describe('Issue boards new issue form', () => { + let vm; + let list; + let mock; + let newIssueMock; + const jQueryMock = { + enable: jest.fn(), + }; + const promiseReturn = { + data: { + iid: 100, + }, + }; + + const submitIssue = () => { + const dummySubmitEvent = { + preventDefault() {}, + }; + vm.$refs.submitButton = vm.$el.querySelector('.btn-success'); + return vm.submit(dummySubmitEvent); + }; + + beforeEach(() => { + setFixtures('
'); + + 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); + + vm = new BoardNewIssueComp({ + propsData: { + list, + }, + }).$mount(document.querySelector('.test-container')); + + $.fn.extend(jQueryMock); + + return Vue.nextTick(); + }); + + afterEach(() => { + vm.$destroy(); + mock.restore(); + }); + + it('calls submit if submit button is clicked', () => { + jest.spyOn(vm, 'submit').mockImplementation(e => e.preventDefault()); + vm.title = 'Testing Title'; + + return Vue.nextTick().then(() => { + vm.$el.querySelector('.btn-success').click(); + + expect(vm.submit.mock.calls.length).toBe(1); + }); + }); + + it('disables submit button if title is empty', () => { + expect(vm.$el.querySelector('.btn-success').disabled).toBe(true); + }); + + it('enables submit button if title is not empty', () => { + vm.title = 'Testing Title'; + + return Vue.nextTick().then(() => { + expect(vm.$el.querySelector('.form-control').value).toBe('Testing Title'); + expect(vm.$el.querySelector('.btn-success').disabled).not.toBe(true); + }); + }); + + it('clears title after clicking cancel', () => { + vm.$el.querySelector('.btn-default').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', () => { + vm.title = 'submit title'; + + return Vue.nextTick() + .then(submitIssue) + .then(() => { + expect(list.newIssue).toHaveBeenCalled(); + }); + }); + + it('enables button after submit', () => { + vm.title = 'submit issue'; + + return Vue.nextTick() + .then(submitIssue) + .then(() => { + expect(jQueryMock.enable).toHaveBeenCalled(); + }); + }); + + it('clears title after submit', () => { + vm.title = 'submit issue'; + + return Vue.nextTick() + .then(submitIssue) + .then(() => { + expect(vm.title).toBe(''); + }); + }); + + it('sets detail issue after submit', () => { + expect(boardsStore.detail.issue.title).toBe(undefined); + vm.title = 'submit issue'; + + return Vue.nextTick() + .then(submitIssue) + .then(() => { + expect(boardsStore.detail.issue.title).toBe('submit issue'); + }); + }); + + it('sets detail list after submit', () => { + vm.title = 'submit issue'; + + return Vue.nextTick() + .then(submitIssue) + .then(() => { + expect(boardsStore.detail.list.id).toBe(list.id); + }); + }); + + it('sets detail weight after submit', () => { + boardsStore.weightFeatureAvailable = true; + vm.title = 'submit 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; + vm.title = 'submit 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/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js new file mode 100644 index 00000000000..ee427bc2154 --- /dev/null +++ b/spec/frontend/boards/components/board_form_spec.js @@ -0,0 +1,47 @@ +import { mount } from '@vue/test-utils'; + +import boardsStore from '~/boards/stores/boards_store'; +import boardForm from '~/boards/components/board_form.vue'; +import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; + +describe('board_form.vue', () => { + let wrapper; + + const propsData = { + canAdminBoard: false, + labelsPath: `${gl.TEST_HOST}/labels/path`, + milestonePath: `${gl.TEST_HOST}/milestone/path`, + }; + + const findModal = () => wrapper.find(DeprecatedModal); + + beforeEach(() => { + boardsStore.state.currentPage = 'edit'; + wrapper = mount(boardForm, { propsData }); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + describe('methods', () => { + describe('cancel', () => { + it('resets currentPage', () => { + wrapper.vm.cancel(); + expect(boardsStore.state.currentPage).toBe(''); + }); + }); + }); + + describe('buttons', () => { + it('cancel button triggers cancel()', () => { + wrapper.setMethods({ cancel: jest.fn() }); + findModal().vm.$emit('cancel'); + + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.cancel).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/spec/frontend/boards/issue_spec.js b/spec/frontend/boards/issue_spec.js new file mode 100644 index 00000000000..ff72edaa695 --- /dev/null +++ b/spec/frontend/boards/issue_spec.js @@ -0,0 +1,197 @@ +/* global ListIssue */ + +import axios from '~/lib/utils/axios_utils'; +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 } from './mock_data'; + +describe('Issue model', () => { + let issue; + + beforeEach(() => { + setMockEndpoints(); + boardsStore.create(); + + 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', + }, + ], + }); + }); + + 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('red'); + }); + + 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 assignee ids when there are assignees', done => { + jest.spyOn(axios, 'patch').mockImplementation((url, data) => { + expect(data.issue.assignee_ids).toEqual([1]); + done(); + return Promise.resolve(); + }); + + issue.update('url'); + }); + + it('passes assignee ids of [0] when there are no assignees', done => { + jest.spyOn(axios, 'patch').mockImplementation((url, data) => { + expect(data.issue.assignee_ids).toEqual([0]); + done(); + return Promise.resolve(); + }); + + issue.removeAllAssignees(); + issue.update('url'); + }); + }); +}); diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js index 05f73e0d8dc..fa4154676a2 100644 --- a/spec/frontend/boards/mock_data.js +++ b/spec/frontend/boards/mock_data.js @@ -1,3 +1,5 @@ +import boardsStore from '~/boards/stores/boards_store'; + export const boardObj = { id: 1, name: 'test', @@ -89,3 +91,54 @@ export const mockMilestone = { start_date: '2018-01-01', due_date: '2019-12-31', }; + +export const BoardsMockData = { + GET: { + '/test/-/boards/1/lists/300/issues?id=300&page=1': { + issues: [ + { + title: 'Testing', + id: 1, + iid: 1, + confidential: false, + labels: [], + assignees: [], + }, + ], + }, + '/test/issue-boards/-/milestones.json': [ + { + id: 1, + title: 'test', + }, + ], + }, + POST: { + '/test/-/boards/1/lists': listObj, + }, + PUT: { + '/test/issue-boards/-/board/1/lists{/id}': {}, + }, + DELETE: { + '/test/issue-boards/-/board/1/lists{/id}': {}, + }, +}; + +export const boardsMockInterceptor = config => { + const body = BoardsMockData[config.method.toUpperCase()][config.url]; + 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, + }); +}; -- cgit v1.2.1