diff options
Diffstat (limited to 'spec/javascripts/boards')
| -rw-r--r-- | spec/javascripts/boards/board_blank_state_spec.js | 19 | ||||
| -rw-r--r-- | spec/javascripts/boards/board_list_spec.js | 2 | ||||
| -rw-r--r-- | spec/javascripts/boards/boards_store_spec.js | 23 | ||||
| -rw-r--r-- | spec/javascripts/boards/components/board_form_spec.js | 56 | ||||
| -rw-r--r-- | spec/javascripts/boards/components/board_spec.js | 61 | ||||
| -rw-r--r-- | spec/javascripts/boards/components/boards_selector_spec.js | 206 | ||||
| -rw-r--r-- | spec/javascripts/boards/components/issue_time_estimate_spec.js | 70 | ||||
| -rw-r--r-- | spec/javascripts/boards/mock_data.js | 5 |
8 files changed, 384 insertions, 58 deletions
diff --git a/spec/javascripts/boards/board_blank_state_spec.js b/spec/javascripts/boards/board_blank_state_spec.js index 8e2a947b0dd..b64859ec32a 100644 --- a/spec/javascripts/boards/board_blank_state_spec.js +++ b/spec/javascripts/boards/board_blank_state_spec.js @@ -1,7 +1,6 @@ import Vue from 'vue'; import boardsStore from '~/boards/stores/boards_store'; import BoardBlankState from '~/boards/components/board_blank_state.vue'; -import { mockBoardService } from './mock_data'; describe('Boards blank state', () => { let vm; @@ -11,9 +10,10 @@ describe('Boards blank state', () => { const Comp = Vue.extend(BoardBlankState); boardsStore.create(); - gl.boardService = mockBoardService(); - spyOn(gl.boardService, 'generateDefaultLists').and.callFake( + spyOn(boardsStore, 'addList').and.stub(); + spyOn(boardsStore, 'removeList').and.stub(); + spyOn(boardsStore, 'generateDefaultLists').and.callFake( () => new Promise((resolve, reject) => { if (fail) { @@ -71,9 +71,14 @@ describe('Boards blank state', () => { vm.$el.querySelector('.btn-success').click(); setTimeout(() => { - expect(boardsStore.state.lists.length).toBe(2); - expect(boardsStore.state.lists[0].title).toEqual('To Do'); - expect(boardsStore.state.lists[1].title).toEqual('Doing'); + expect(boardsStore.addList).toHaveBeenCalledTimes(2); + expect(boardsStore.addList).toHaveBeenCalledWith( + jasmine.objectContaining({ title: 'To Do' }), + ); + + expect(boardsStore.addList).toHaveBeenCalledWith( + jasmine.objectContaining({ title: 'Doing' }), + ); done(); }); @@ -86,7 +91,7 @@ describe('Boards blank state', () => { setTimeout(() => { expect(boardsStore.welcomeIsHidden()).toBeFalsy(); - expect(boardsStore.state.lists.length).toBe(1); + expect(boardsStore.removeList).toHaveBeenCalledWith(undefined, 'label'); done(); }); diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js index 9c9b435d7fd..6774a46ed58 100644 --- a/spec/javascripts/boards/board_list_spec.js +++ b/spec/javascripts/boards/board_list_spec.js @@ -148,7 +148,7 @@ describe('Board list component', () => { component.list.loadingMore = true; Vue.nextTick(() => { - expect(component.$el.querySelector('.board-list-count .spinner')).not.toBeNull(); + expect(component.$el.querySelector('.board-list-count .gl-spinner')).not.toBeNull(); done(); }); diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index e81115e10c9..36bd7ada4f0 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -355,4 +355,27 @@ describe('Store', () => { 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); + }); + }); }); diff --git a/spec/javascripts/boards/components/board_form_spec.js b/spec/javascripts/boards/components/board_form_spec.js new file mode 100644 index 00000000000..e9014156a98 --- /dev/null +++ b/spec/javascripts/boards/components/board_form_spec.js @@ -0,0 +1,56 @@ +import $ from 'jquery'; +import Vue from 'vue'; +import boardsStore from '~/boards/stores/boards_store'; +import boardForm from '~/boards/components/board_form.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; + +describe('board_form.vue', () => { + const props = { + canAdminBoard: false, + labelsPath: `${gl.TEST_HOST}/labels/path`, + milestonePath: `${gl.TEST_HOST}/milestone/path`, + }; + let vm; + + beforeEach(() => { + spyOn($, 'ajax'); + boardsStore.state.currentPage = 'edit'; + const Component = Vue.extend(boardForm); + vm = mountComponent(Component, props); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('methods', () => { + describe('cancel', () => { + it('resets currentPage', done => { + vm.cancel(); + + Vue.nextTick() + .then(() => { + expect(boardsStore.state.currentPage).toBe(''); + }) + .then(done) + .catch(done.fail); + }); + }); + }); + + describe('buttons', () => { + it('cancel button triggers cancel()', done => { + spyOn(vm, 'cancel'); + + Vue.nextTick() + .then(() => { + const cancelButton = vm.$el.querySelector('button[data-dismiss="modal"]'); + cancelButton.click(); + + expect(vm.cancel).toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + }); +}); diff --git a/spec/javascripts/boards/components/board_spec.js b/spec/javascripts/boards/components/board_spec.js index d08ee41802b..683783334c6 100644 --- a/spec/javascripts/boards/components/board_spec.js +++ b/spec/javascripts/boards/components/board_spec.js @@ -1,7 +1,6 @@ import Vue from 'vue'; -import '~/boards/services/board_service'; import Board from '~/boards/components/board'; -import '~/boards/models/list'; +import List from '~/boards/models/list'; import { mockBoardService } from '../mock_data'; describe('Board component', () => { @@ -27,7 +26,6 @@ describe('Board component', () => { disabled: false, issueLinkBase: '/', rootPath: '/', - // eslint-disable-next-line no-undef list: new List({ id: 1, position: 0, @@ -53,57 +51,62 @@ describe('Board component', () => { expect(vm.$el.classList.contains('is-expandable')).toBe(true); }); - it('board is expandable when list type is closed', done => { - vm.list.type = 'closed'; - - Vue.nextTick(() => { - expect(vm.$el.classList.contains('is-expandable')).toBe(true); - - done(); - }); + it('board is expandable when list type is closed', () => { + expect(new List({ id: 1, list_type: 'closed' }).isExpandable).toBe(true); }); - it('board is not expandable when list type is label', done => { - vm.list.type = 'label'; - vm.list.isExpandable = false; - - Vue.nextTick(() => { - expect(vm.$el.classList.contains('is-expandable')).toBe(false); + it('board is expandable when list type is label', () => { + expect(new List({ id: 1, list_type: 'closed' }).isExpandable).toBe(true); + }); - done(); - }); + it('board is not expandable when list type is blank', () => { + expect(new List({ id: 1, list_type: 'blank' }).isExpandable).toBe(false); }); - it('collapses when clicking header', done => { + it('does not collapse when clicking header', done => { + vm.list.isExpanded = true; vm.$el.querySelector('.board-header').click(); Vue.nextTick(() => { - expect(vm.$el.classList.contains('is-collapsed')).toBe(true); + expect(vm.$el.classList.contains('is-collapsed')).toBe(false); done(); }); }); - it('created sets isExpanded to true from localStorage', done => { - vm.$el.querySelector('.board-header').click(); + it('collapses when clicking the collapse icon', done => { + vm.list.isExpanded = true; - return Vue.nextTick() + Vue.nextTick() + .then(() => { + vm.$el.querySelector('.board-title-caret').click(); + }) .then(() => { expect(vm.$el.classList.contains('is-collapsed')).toBe(true); + done(); + }) + .catch(done.fail); + }); - // call created manually - vm.$options.created[0].call(vm); + it('expands when clicking the expand icon', done => { + vm.list.isExpanded = false; - return Vue.nextTick(); + Vue.nextTick() + .then(() => { + vm.$el.querySelector('.board-title-caret').click(); }) .then(() => { - expect(vm.$el.classList.contains('is-collapsed')).toBe(true); - + expect(vm.$el.classList.contains('is-collapsed')).toBe(false); done(); }) .catch(done.fail); }); + it('is expanded when created', () => { + expect(vm.list.isExpanded).toBe(true); + expect(vm.$el.classList.contains('is-collapsed')).toBe(false); + }); + it('does render add issue button', () => { expect(vm.$el.querySelector('.issue-count-badge-add-button')).not.toBeNull(); }); diff --git a/spec/javascripts/boards/components/boards_selector_spec.js b/spec/javascripts/boards/components/boards_selector_spec.js new file mode 100644 index 00000000000..473cc0612ea --- /dev/null +++ b/spec/javascripts/boards/components/boards_selector_spec.js @@ -0,0 +1,206 @@ +import Vue from 'vue'; +import BoardService from '~/boards/services/board_service'; +import BoardsSelector from '~/boards/components/boards_selector.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import { TEST_HOST } from 'spec/test_constants'; +import boardsStore from '~/boards/stores/boards_store'; + +const throttleDuration = 1; + +function boardGenerator(n) { + return new Array(n).fill().map((board, id) => { + const name = `board${id}`; + + return { + id, + name, + }; + }); +} + +describe('BoardsSelector', () => { + let vm; + let allBoardsResponse; + let recentBoardsResponse; + let fillSearchBox; + const boards = boardGenerator(20); + const recentBoards = boardGenerator(5); + + beforeEach(done => { + setFixtures('<div class="js-boards-selector"></div>'); + window.gl = window.gl || {}; + + boardsStore.setEndpoints({ + boardsEndpoint: '', + recentBoardsEndpoint: '', + listsEndpoint: '', + bulkUpdatePath: '', + boardId: '', + }); + window.gl.boardService = new BoardService(); + + allBoardsResponse = Promise.resolve({ + data: boards, + }); + recentBoardsResponse = Promise.resolve({ + data: recentBoards, + }); + + spyOn(BoardService.prototype, 'allBoards').and.returnValue(allBoardsResponse); + spyOn(BoardService.prototype, 'recentBoards').and.returnValue(recentBoardsResponse); + + const Component = Vue.extend(BoardsSelector); + vm = mountComponent( + Component, + { + throttleDuration, + currentBoard: { + id: 1, + name: 'Development', + milestone_id: null, + weight: null, + assignee_id: null, + labels: [], + }, + milestonePath: `${TEST_HOST}/milestone/path`, + boardBaseUrl: `${TEST_HOST}/board/base/url`, + hasMissingBoards: false, + canAdminBoard: true, + multipleIssueBoardsAvailable: true, + labelsPath: `${TEST_HOST}/labels/path`, + projectId: 42, + groupId: 19, + scopedIssueBoardFeatureEnabled: true, + weights: [], + }, + document.querySelector('.js-boards-selector'), + ); + + // Emits gl-dropdown show event to simulate the dropdown is opened at initialization time + vm.$children[0].$emit('show'); + + Promise.all([allBoardsResponse, recentBoardsResponse]) + .then(() => vm.$nextTick()) + .then(done) + .catch(done.fail); + + fillSearchBox = filterTerm => { + const { searchBox } = vm.$refs; + const searchBoxInput = searchBox.$el.querySelector('input'); + searchBoxInput.value = filterTerm; + searchBoxInput.dispatchEvent(new Event('input')); + }; + }); + + afterEach(() => { + vm.$destroy(); + window.gl.boardService = undefined; + }); + + describe('filtering', () => { + it('shows all boards without filtering', done => { + vm.$nextTick() + .then(() => { + const dropdownItem = vm.$el.querySelectorAll('.js-dropdown-item'); + + expect(dropdownItem.length).toBe(boards.length + recentBoards.length); + }) + .then(done) + .catch(done.fail); + }); + + it('shows only matching boards when filtering', done => { + const filterTerm = 'board1'; + const expectedCount = boards.filter(board => board.name.includes(filterTerm)).length; + + fillSearchBox(filterTerm); + + vm.$nextTick() + .then(() => { + const dropdownItems = vm.$el.querySelectorAll('.js-dropdown-item'); + + expect(dropdownItems.length).toBe(expectedCount); + }) + .then(done) + .catch(done.fail); + }); + + it('shows message if there are no matching boards', done => { + fillSearchBox('does not exist'); + + vm.$nextTick() + .then(() => { + const dropdownItems = vm.$el.querySelectorAll('.js-dropdown-item'); + + expect(dropdownItems.length).toBe(0); + expect(vm.$el).toContainText('No matching boards found'); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('recent boards section', () => { + it('shows only when boards are greater than 10', done => { + vm.$nextTick() + .then(() => { + const headerEls = vm.$el.querySelectorAll('.dropdown-bold-header'); + + const expectedCount = 2; // Recent + All + + expect(expectedCount).toBe(headerEls.length); + }) + .then(done) + .catch(done.fail); + }); + + it('does not show when boards are less than 10', done => { + spyOn(vm, 'initScrollFade'); + spyOn(vm, 'setScrollFade'); + + vm.$nextTick() + .then(() => { + vm.boards = vm.boards.slice(0, 5); + }) + .then(vm.$nextTick) + .then(() => { + const headerEls = vm.$el.querySelectorAll('.dropdown-bold-header'); + const expectedCount = 0; + + expect(expectedCount).toBe(headerEls.length); + }) + .then(done) + .catch(done.fail); + }); + + it('does not show when recentBoards api returns empty array', done => { + vm.$nextTick() + .then(() => { + vm.recentBoards = []; + }) + .then(vm.$nextTick) + .then(() => { + const headerEls = vm.$el.querySelectorAll('.dropdown-bold-header'); + const expectedCount = 0; + + expect(expectedCount).toBe(headerEls.length); + }) + .then(done) + .catch(done.fail); + }); + + it('does not show when search is active', done => { + fillSearchBox('Random string'); + + vm.$nextTick() + .then(() => { + const headerEls = vm.$el.querySelectorAll('.dropdown-bold-header'); + const expectedCount = 0; + + expect(expectedCount).toBe(headerEls.length); + }) + .then(done) + .catch(done.fail); + }); + }); +}); diff --git a/spec/javascripts/boards/components/issue_time_estimate_spec.js b/spec/javascripts/boards/components/issue_time_estimate_spec.js index ba65d3287da..de48e3f6091 100644 --- a/spec/javascripts/boards/components/issue_time_estimate_spec.js +++ b/spec/javascripts/boards/components/issue_time_estimate_spec.js @@ -1,40 +1,70 @@ import Vue from 'vue'; import IssueTimeEstimate from '~/boards/components/issue_time_estimate.vue'; +import boardsStore from '~/boards/stores/boards_store'; import mountComponent from '../../helpers/vue_mount_component_helper'; -describe('Issue Tine Estimate component', () => { +describe('Issue Time Estimate component', () => { let vm; beforeEach(() => { - const Component = Vue.extend(IssueTimeEstimate); - vm = mountComponent(Component, { - estimate: 374460, - }); + boardsStore.create(); }); afterEach(() => { vm.$destroy(); }); - it('renders the correct time estimate', () => { - expect(vm.$el.querySelector('time').textContent.trim()).toEqual('2w 3d 1m'); - }); + describe('when limitToHours is false', () => { + beforeEach(() => { + boardsStore.timeTracking.limitToHours = false; + + const Component = Vue.extend(IssueTimeEstimate); + vm = mountComponent(Component, { + estimate: 374460, + }); + }); + + it('renders the correct time estimate', () => { + expect(vm.$el.querySelector('time').textContent.trim()).toEqual('2w 3d 1m'); + }); + + it('renders expanded time estimate in tooltip', () => { + expect(vm.$el.querySelector('.js-issue-time-estimate').textContent).toContain( + '2 weeks 3 days 1 minute', + ); + }); + + it('prevents tooltip xss', done => { + const alertSpy = spyOn(window, 'alert'); + vm.estimate = 'Foo <script>alert("XSS")</script>'; - it('renders expanded time estimate in tooltip', () => { - expect(vm.$el.querySelector('.js-issue-time-estimate').textContent).toContain( - '2 weeks 3 days 1 minute', - ); + vm.$nextTick(() => { + expect(alertSpy).not.toHaveBeenCalled(); + expect(vm.$el.querySelector('time').textContent.trim()).toEqual('0m'); + expect(vm.$el.querySelector('.js-issue-time-estimate').textContent).toContain('0m'); + done(); + }); + }); }); - it('prevents tooltip xss', done => { - const alertSpy = spyOn(window, 'alert'); - vm.estimate = 'Foo <script>alert("XSS")</script>'; + describe('when limitToHours is true', () => { + beforeEach(() => { + boardsStore.timeTracking.limitToHours = true; + + const Component = Vue.extend(IssueTimeEstimate); + vm = mountComponent(Component, { + estimate: 374460, + }); + }); + + it('renders the correct time estimate', () => { + expect(vm.$el.querySelector('time').textContent.trim()).toEqual('104h 1m'); + }); - vm.$nextTick(() => { - expect(alertSpy).not.toHaveBeenCalled(); - expect(vm.$el.querySelector('time').textContent.trim()).toEqual('0m'); - expect(vm.$el.querySelector('.js-issue-time-estimate').textContent).toContain('0m'); - done(); + it('renders expanded time estimate in tooltip', () => { + expect(vm.$el.querySelector('.js-issue-time-estimate').textContent).toContain( + '104 hours 1 minute', + ); }); }); }); diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js index 9854cf49e97..ea22ae5c4e7 100644 --- a/spec/javascripts/boards/mock_data.js +++ b/spec/javascripts/boards/mock_data.js @@ -1,4 +1,5 @@ import BoardService from '~/boards/services/board_service'; +import boardsStore from '~/boards/stores/boards_store'; export const boardObj = { id: 1, @@ -76,12 +77,14 @@ export const mockBoardService = (opts = {}) => { const bulkUpdatePath = opts.bulkUpdatePath || ''; const boardId = opts.boardId || '1'; - return new BoardService({ + boardsStore.setEndpoints({ boardsEndpoint, listsEndpoint, bulkUpdatePath, boardId, }); + + return new BoardService(); }; export const mockAssigneesList = [ |
