diff options
Diffstat (limited to 'spec/javascripts')
53 files changed, 914 insertions, 549 deletions
diff --git a/spec/javascripts/blob/notebook/index_spec.js b/spec/javascripts/blob/notebook/index_spec.js index c3e67550f05..df1b2c9960b 100644 --- a/spec/javascripts/blob/notebook/index_spec.js +++ b/spec/javascripts/blob/notebook/index_spec.js @@ -1,4 +1,5 @@ -import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import renderNotebook from '~/blob/notebook'; describe('iPython notebook renderer', () => { @@ -17,8 +18,11 @@ describe('iPython notebook renderer', () => { }); describe('successful response', () => { - const response = (request, next) => { - next(request.respondWith(JSON.stringify({ + let mock; + + beforeEach((done) => { + mock = new MockAdapter(axios); + mock.onGet('/test').reply(200, { cells: [{ cell_type: 'markdown', source: ['# test'], @@ -31,13 +35,7 @@ describe('iPython notebook renderer', () => { ], outputs: [], }], - }), { - status: 200, - })); - }; - - beforeEach((done) => { - Vue.http.interceptors.push(response); + }); renderNotebook(); @@ -47,9 +45,7 @@ describe('iPython notebook renderer', () => { }); afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, response, - ); + mock.reset(); }); it('does not show loading icon', () => { @@ -86,14 +82,11 @@ describe('iPython notebook renderer', () => { }); describe('error in JSON response', () => { - const response = (request, next) => { - next(request.respondWith('{ "cells": [{"cell_type": "markdown"} }', { - status: 200, - })); - }; + let mock; beforeEach((done) => { - Vue.http.interceptors.push(response); + mock = new MockAdapter(axios); + mock.onGet('/test').reply(() => Promise.reject({ status: 200, data: '{ "cells": [{"cell_type": "markdown"} }' })); renderNotebook(); @@ -103,9 +96,7 @@ describe('iPython notebook renderer', () => { }); afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, response, - ); + mock.reset(); }); it('does not show loading icon', () => { @@ -122,14 +113,11 @@ describe('iPython notebook renderer', () => { }); describe('error getting file', () => { - const response = (request, next) => { - next(request.respondWith('', { - status: 500, - })); - }; + let mock; beforeEach((done) => { - Vue.http.interceptors.push(response); + mock = new MockAdapter(axios); + mock.onGet('/test').reply(500, ''); renderNotebook(); @@ -139,9 +127,7 @@ describe('iPython notebook renderer', () => { }); afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, response, - ); + mock.reset(); }); it('does not show loading icon', () => { diff --git a/spec/javascripts/boards/board_blank_state_spec.js b/spec/javascripts/boards/board_blank_state_spec.js index 2ee3792dd65..f757dadfada 100644 --- a/spec/javascripts/boards/board_blank_state_spec.js +++ b/spec/javascripts/boards/board_blank_state_spec.js @@ -1,9 +1,8 @@ /* global BoardService */ -/* global mockBoardService */ import Vue from 'vue'; import '~/boards/stores/boards_store'; import boardBlankState from '~/boards/components/board_blank_state'; -import './mock_data'; +import { mockBoardService } from './mock_data'; describe('Boards blank state', () => { let vm; @@ -20,17 +19,15 @@ describe('Boards blank state', () => { reject(); } else { resolve({ - json() { - return [{ - id: 1, - title: 'To Do', - label: { id: 1 }, - }, { - id: 2, - title: 'Doing', - label: { id: 2 }, - }]; - }, + data: [{ + id: 1, + title: 'To Do', + label: { id: 1 }, + }, { + id: 2, + title: 'Doing', + label: { id: 2 }, + }], }); } })); diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js index 8f607899b20..4e73fa1fe87 100644 --- a/spec/javascripts/boards/board_card_spec.js +++ b/spec/javascripts/boards/board_card_spec.js @@ -1,12 +1,11 @@ /* global List */ /* global ListAssignee */ /* global ListLabel */ -/* global listObj */ -/* global boardsMockInterceptor */ /* global BoardService */ -/* global mockBoardService */ import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import '~/boards/models/assignee'; import eventHub from '~/boards/eventhub'; @@ -14,13 +13,15 @@ import '~/boards/models/list'; import '~/boards/models/label'; import '~/boards/stores/boards_store'; import boardCard from '~/boards/components/board_card.vue'; -import './mock_data'; +import { listObj, boardsMockInterceptor, mockBoardService } from './mock_data'; describe('Board card', () => { let vm; + let mock; beforeEach((done) => { - Vue.http.interceptors.push(boardsMockInterceptor); + mock = new MockAdapter(axios); + mock.onAny().reply(boardsMockInterceptor); gl.boardService = mockBoardService(); gl.issueBoards.BoardsStore.create(); @@ -54,7 +55,7 @@ describe('Board card', () => { }); afterEach(() => { - Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor); + mock.reset(); }); it('returns false when detailIssue is empty', () => { diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js index 6bd00943a8f..7c5888b6d82 100644 --- a/spec/javascripts/boards/board_list_spec.js +++ b/spec/javascripts/boards/board_list_spec.js @@ -1,11 +1,9 @@ /* global BoardService */ -/* global boardsMockInterceptor */ /* global List */ -/* global listObj */ /* global ListIssue */ -/* global mockBoardService */ import Vue from 'vue'; -import _ from 'underscore'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import Sortable from 'vendor/Sortable'; import BoardList from '~/boards/components/board_list'; import eventHub from '~/boards/eventhub'; @@ -13,18 +11,20 @@ import '~/boards/mixins/sortable_default_options'; import '~/boards/models/issue'; import '~/boards/models/list'; import '~/boards/stores/boards_store'; -import './mock_data'; +import { listObj, boardsMockInterceptor, mockBoardService } from './mock_data'; window.Sortable = Sortable; describe('Board list component', () => { + let mock; let component; beforeEach((done) => { const el = document.createElement('div'); document.body.appendChild(el); - Vue.http.interceptors.push(boardsMockInterceptor); + mock = new MockAdapter(axios); + mock.onAny().reply(boardsMockInterceptor); gl.boardService = mockBoardService(); gl.issueBoards.BoardsStore.create(); gl.IssueBoardsApp = new Vue(); @@ -60,7 +60,7 @@ describe('Board list component', () => { }); afterEach(() => { - Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor); + mock.reset(); }); it('renders component', () => { diff --git a/spec/javascripts/boards/board_new_issue_spec.js b/spec/javascripts/boards/board_new_issue_spec.js index 02e6692dda8..c62c537841c 100644 --- a/spec/javascripts/boards/board_new_issue_spec.js +++ b/spec/javascripts/boards/board_new_issue_spec.js @@ -1,24 +1,22 @@ -/* global boardsMockInterceptor */ /* global BoardService */ /* global List */ -/* global listObj */ -/* global mockBoardService */ 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'; import '~/boards/models/list'; -import './mock_data'; +import { listObj, boardsMockInterceptor, mockBoardService } from './mock_data'; describe('Issue boards new issue form', () => { let vm; let list; + let mock; let newIssueMock; const promiseReturn = { - json() { - return { - iid: 100, - }; + data: { + iid: 100, }, }; @@ -35,7 +33,9 @@ describe('Issue boards new issue form', () => { const BoardNewIssueComp = Vue.extend(boardNewIssue); - Vue.http.interceptors.push(boardsMockInterceptor); + mock = new MockAdapter(axios); + mock.onAny().reply(boardsMockInterceptor); + gl.boardService = mockBoardService(); gl.issueBoards.BoardsStore.create(); gl.IssueBoardsApp = new Vue(); @@ -56,7 +56,10 @@ describe('Issue boards new issue form', () => { .catch(done.fail); }); - afterEach(() => vm.$destroy()); + afterEach(() => { + vm.$destroy(); + mock.reset(); + }); it('calls submit if submit button is clicked', (done) => { spyOn(vm, 'submit').and.callFake(e => e.preventDefault()); diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index 0e656858182..49fb20f4c84 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -1,12 +1,10 @@ /* eslint-disable comma-dangle, one-var, no-unused-vars */ /* global BoardService */ -/* global boardsMockInterceptor */ -/* global listObj */ -/* global listObjDuplicate */ /* global ListIssue */ -/* global mockBoardService */ import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import Cookies from 'js-cookie'; import '~/boards/models/issue'; @@ -15,11 +13,14 @@ import '~/boards/models/list'; import '~/boards/models/assignee'; import '~/boards/services/board_service'; import '~/boards/stores/boards_store'; -import './mock_data'; +import { listObj, listObjDuplicate, boardsMockInterceptor, mockBoardService } from './mock_data'; describe('Store', () => { + let mock; + beforeEach(() => { - Vue.http.interceptors.push(boardsMockInterceptor); + mock = new MockAdapter(axios); + mock.onAny().reply(boardsMockInterceptor); gl.boardService = mockBoardService(); gl.issueBoards.BoardsStore.create(); @@ -34,7 +35,7 @@ describe('Store', () => { }); afterEach(() => { - Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor); + mock.reset(); }); it('starts with a blank state', () => { diff --git a/spec/javascripts/boards/components/board_spec.js b/spec/javascripts/boards/components/board_spec.js index 8dacac20cad..19346e305cf 100644 --- a/spec/javascripts/boards/components/board_spec.js +++ b/spec/javascripts/boards/components/board_spec.js @@ -1,9 +1,8 @@ -/* global mockBoardService */ import Vue from 'vue'; import '~/boards/services/board_service'; import '~/boards/components/board'; import '~/boards/models/list'; -import '../mock_data'; +import { mockBoardService } from '../mock_data'; describe('Board component', () => { let vm; diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js index 7d430ec35e2..8ef221257be 100644 --- a/spec/javascripts/boards/issue_card_spec.js +++ b/spec/javascripts/boards/issue_card_spec.js @@ -1,6 +1,5 @@ /* global ListAssignee */ /* global ListLabel */ -/* global listObj */ /* global ListIssue */ import Vue from 'vue'; @@ -11,7 +10,7 @@ import '~/boards/models/list'; import '~/boards/models/assignee'; import '~/boards/stores/boards_store'; import '~/boards/components/issue_card_inner'; -import './mock_data'; +import { listObj } from './mock_data'; describe('Issue card component', () => { const user = new ListAssignee({ diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js index 41dcb19df3c..dbbe14fe3e0 100644 --- a/spec/javascripts/boards/issue_spec.js +++ b/spec/javascripts/boards/issue_spec.js @@ -1,7 +1,6 @@ /* eslint-disable comma-dangle */ /* global BoardService */ /* global ListIssue */ -/* global mockBoardService */ import Vue from 'vue'; import '~/boards/models/issue'; @@ -10,7 +9,7 @@ import '~/boards/models/list'; import '~/boards/models/assignee'; import '~/boards/services/board_service'; import '~/boards/stores/boards_store'; -import './mock_data'; +import { mockBoardService } from './mock_data'; describe('Issue model', () => { let issue; diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js index eead396ca7e..645ce831b53 100644 --- a/spec/javascripts/boards/list_spec.js +++ b/spec/javascripts/boards/list_spec.js @@ -1,13 +1,10 @@ /* eslint-disable comma-dangle */ -/* global boardsMockInterceptor */ /* global BoardService */ -/* global mockBoardService */ /* global List */ /* global ListIssue */ -/* global listObj */ -/* global listObjDuplicate */ -import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import '~/boards/models/issue'; import '~/boards/models/label'; @@ -15,13 +12,15 @@ import '~/boards/models/list'; import '~/boards/models/assignee'; import '~/boards/services/board_service'; import '~/boards/stores/boards_store'; -import './mock_data'; +import { listObj, listObjDuplicate, boardsMockInterceptor, mockBoardService } from './mock_data'; describe('List model', () => { let list; + let mock; beforeEach(() => { - Vue.http.interceptors.push(boardsMockInterceptor); + mock = new MockAdapter(axios); + mock.onAny().reply(boardsMockInterceptor); gl.boardService = mockBoardService({ bulkUpdatePath: '/test/issue-boards/board/1/lists', }); @@ -31,7 +30,7 @@ describe('List model', () => { }); afterEach(() => { - Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor); + mock.reset(); }); it('gets issues when created', (done) => { @@ -158,10 +157,8 @@ describe('List model', () => { describe('newIssue', () => { beforeEach(() => { spyOn(gl.boardService, 'newIssue').and.returnValue(Promise.resolve({ - json() { - return { - id: 42, - }; + data: { + id: 42, }, })); }); diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js index 0a93086985e..9ae2d535398 100644 --- a/spec/javascripts/boards/mock_data.js +++ b/spec/javascripts/boards/mock_data.js @@ -1,20 +1,20 @@ /* global BoardService */ /* eslint-disable comma-dangle, no-unused-vars, quote-props */ -const listObj = { - id: _.random(10000), +export const listObj = { + id: 300, position: 0, title: 'Test', list_type: 'label', label: { - id: _.random(10000), + id: 5000, title: 'Testing', color: 'red', description: 'testing;' } }; -const listObjDuplicate = { +export const listObjDuplicate = { id: listObj.id, position: 1, title: 'Test', @@ -27,9 +27,9 @@ const listObjDuplicate = { } }; -const BoardsMockData = { +export const BoardsMockData = { 'GET': { - '/test/boards/1{/id}/issues': { + '/test/-/boards/1/lists/300/issues?id=300&page=1&=': { issues: [{ title: 'Testing', id: 1, @@ -41,7 +41,7 @@ const BoardsMockData = { } }, 'POST': { - '/test/boards/1{/id}': listObj + '/test/-/boards/1/lists': listObj }, 'PUT': { '/test/issue-boards/board/1/lists{/id}': {} @@ -51,17 +51,14 @@ const BoardsMockData = { } }; -const boardsMockInterceptor = (request, next) => { - const body = BoardsMockData[request.method][request.url]; - - next(request.respondWith(JSON.stringify(body), { - status: 200 - })); +export const boardsMockInterceptor = (config) => { + const body = BoardsMockData[config.method.toUpperCase()][config.url]; + return [200, body]; }; -const mockBoardService = (opts = {}) => { - const boardsEndpoint = opts.boardsEndpoint || '/test/issue-boards/board'; - const listsEndpoint = opts.listsEndpoint || '/test/boards/1'; +export const mockBoardService = (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'; @@ -72,9 +69,3 @@ const mockBoardService = (opts = {}) => { boardId, }); }; - -window.listObj = listObj; -window.listObjDuplicate = listObjDuplicate; -window.BoardsMockData = BoardsMockData; -window.boardsMockInterceptor = boardsMockInterceptor; -window.mockBoardService = mockBoardService; diff --git a/spec/javascripts/clusters/components/applications_spec.js b/spec/javascripts/clusters/components/applications_spec.js index 7460da031c4..1a8affad4e3 100644 --- a/spec/javascripts/clusters/components/applications_spec.js +++ b/spec/javascripts/clusters/components/applications_spec.js @@ -21,6 +21,7 @@ describe('Applications', () => { helm: { title: 'Helm Tiller' }, ingress: { title: 'Ingress' }, runner: { title: 'GitLab Runner' }, + prometheus: { title: 'Prometheus' }, }, }); }); @@ -33,6 +34,10 @@ describe('Applications', () => { expect(vm.$el.querySelector('.js-cluster-application-row-ingress')).toBeDefined(); }); + it('renders a row for Prometheus', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).toBeDefined(); + }); + /* * / it('renders a row for GitLab Runner', () => { expect(vm.$el.querySelector('.js-cluster-application-row-runner')).toBeDefined(); diff --git a/spec/javascripts/clusters/services/mock_data.js b/spec/javascripts/clusters/services/mock_data.js index af6b6a73819..253b3c45243 100644 --- a/spec/javascripts/clusters/services/mock_data.js +++ b/spec/javascripts/clusters/services/mock_data.js @@ -22,6 +22,11 @@ const CLUSTERS_MOCK_DATA = { name: 'runner', status: APPLICATION_INSTALLING, status_reason: null, + }, + { + name: 'prometheus', + status: APPLICATION_ERROR, + status_reason: 'Cannot connect', }], }, }, @@ -30,6 +35,7 @@ const CLUSTERS_MOCK_DATA = { '/gitlab-org/gitlab-shell/clusters/1/applications/helm': { }, '/gitlab-org/gitlab-shell/clusters/1/applications/ingress': { }, '/gitlab-org/gitlab-shell/clusters/1/applications/runner': { }, + '/gitlab-org/gitlab-shell/clusters/1/applications/prometheus': { }, }, }; diff --git a/spec/javascripts/clusters/stores/clusters_store_spec.js b/spec/javascripts/clusters/stores/clusters_store_spec.js index cb8b3d38e2e..ec2889355e6 100644 --- a/spec/javascripts/clusters/stores/clusters_store_spec.js +++ b/spec/javascripts/clusters/stores/clusters_store_spec.js @@ -82,6 +82,13 @@ describe('Clusters Store', () => { requestStatus: null, requestReason: null, }, + prometheus: { + title: 'Prometheus', + status: mockResponseData.applications[3].status, + statusReason: mockResponseData.applications[3].status_reason, + requestStatus: null, + requestReason: null, + }, }, }); }); diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_manager_spec.js index 5111632d681..b8890e4cda1 100644 --- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js @@ -252,6 +252,7 @@ describe('Filtered Search Manager', () => { it('removes last token', () => { spyOn(gl.FilteredSearchVisualTokens, 'removeLastTokenPartial').and.callThrough(); dispatchBackspaceEvent(input, 'keyup'); + dispatchBackspaceEvent(input, 'keyup'); expect(gl.FilteredSearchVisualTokens.removeLastTokenPartial).toHaveBeenCalled(); }); @@ -259,6 +260,7 @@ describe('Filtered Search Manager', () => { it('sets the input', () => { spyOn(gl.FilteredSearchVisualTokens, 'getLastTokenPartial').and.callThrough(); dispatchDeleteEvent(input, 'keyup'); + dispatchDeleteEvent(input, 'keyup'); expect(gl.FilteredSearchVisualTokens.getLastTokenPartial).toHaveBeenCalled(); expect(input.value).toEqual('~bug'); @@ -276,6 +278,18 @@ describe('Filtered Search Manager', () => { expect(gl.FilteredSearchVisualTokens.getLastTokenPartial).not.toHaveBeenCalled(); expect(input.value).toEqual('text'); }); + + it('does not remove previous token on single backspace press', () => { + spyOn(gl.FilteredSearchVisualTokens, 'removeLastTokenPartial').and.callThrough(); + spyOn(gl.FilteredSearchVisualTokens, 'getLastTokenPartial').and.callThrough(); + + input.value = 't'; + dispatchDeleteEvent(input, 'keyup'); + + expect(gl.FilteredSearchVisualTokens.removeLastTokenPartial).not.toHaveBeenCalled(); + expect(gl.FilteredSearchVisualTokens.getLastTokenPartial).not.toHaveBeenCalled(); + expect(input.value).toEqual('t'); + }); }); describe('removeToken', () => { diff --git a/spec/javascripts/fixtures/pipelines.html.haml b/spec/javascripts/fixtures/pipelines.html.haml index 85ee61f0b54..0161c0550d1 100644 --- a/spec/javascripts/fixtures/pipelines.html.haml +++ b/spec/javascripts/fixtures/pipelines.html.haml @@ -7,4 +7,6 @@ "new-pipeline-path" => 'foo', "can-create-pipeline" => 'true', "has-ci" => 'foo', - "ci-lint-path" => 'foo' } } + "ci-lint-path" => 'foo', + "reset-cache-path" => 'foo' } } + diff --git a/spec/javascripts/groups/components/item_actions_spec.js b/spec/javascripts/groups/components/item_actions_spec.js index 7a5c1da4d1d..6d6fb410859 100644 --- a/spec/javascripts/groups/components/item_actions_spec.js +++ b/spec/javascripts/groups/components/item_actions_spec.js @@ -47,17 +47,11 @@ describe('ItemActionsComponent', () => { it('should change `modalStatus` prop to `false` and emit `leaveGroup` event with required params when called with `leaveConfirmed` as `true`', () => { spyOn(eventHub, '$emit'); vm.modalStatus = true; - vm.leaveGroup(true); - expect(vm.modalStatus).toBeFalsy(); - expect(eventHub.$emit).toHaveBeenCalledWith('leaveGroup', vm.group, vm.parentGroup); - }); - it('should change `modalStatus` prop to `false` and should NOT emit `leaveGroup` event when called with `leaveConfirmed` as `false`', () => { - spyOn(eventHub, '$emit'); - vm.modalStatus = true; - vm.leaveGroup(false); + vm.leaveGroup(); + expect(vm.modalStatus).toBeFalsy(); - expect(eventHub.$emit).not.toHaveBeenCalled(); + expect(eventHub.$emit).toHaveBeenCalledWith('leaveGroup', vm.group, vm.parentGroup); }); }); }); diff --git a/spec/javascripts/groups/components/item_caret_spec.js b/spec/javascripts/groups/components/item_caret_spec.js index 4310a07e6e6..8faad455825 100644 --- a/spec/javascripts/groups/components/item_caret_spec.js +++ b/spec/javascripts/groups/components/item_caret_spec.js @@ -16,24 +16,20 @@ describe('ItemCaretComponent', () => { describe('template', () => { it('should render component template correctly', () => { const vm = createComponent(); - vm.$mount(); expect(vm.$el.classList.contains('folder-caret')).toBeTruthy(); + expect(vm.$el.querySelectorAll('svg').length).toBe(1); vm.$destroy(); }); it('should render caret down icon if `isGroupOpen` prop is `true`', () => { const vm = createComponent(true); - vm.$mount(); - expect(vm.$el.querySelectorAll('i.fa.fa-caret-down').length).toBe(1); - expect(vm.$el.querySelectorAll('i.fa.fa-caret-right').length).toBe(0); + expect(vm.$el.querySelector('svg use').getAttribute('xlink:href')).toContain('angle-down'); vm.$destroy(); }); it('should render caret right icon if `isGroupOpen` prop is `false`', () => { const vm = createComponent(); - vm.$mount(); - expect(vm.$el.querySelectorAll('i.fa.fa-caret-down').length).toBe(0); - expect(vm.$el.querySelectorAll('i.fa.fa-caret-right').length).toBe(1); + expect(vm.$el.querySelector('svg use').getAttribute('xlink:href')).toContain('angle-right'); vm.$destroy(); }); }); diff --git a/spec/javascripts/groups/components/item_stats_spec.js b/spec/javascripts/groups/components/item_stats_spec.js index e200f9f08bd..55a7a713ca6 100644 --- a/spec/javascripts/groups/components/item_stats_spec.js +++ b/spec/javascripts/groups/components/item_stats_spec.js @@ -26,7 +26,6 @@ describe('ItemStatsComponent', () => { Object.keys(VISIBILITY_TYPE_ICON).forEach((visibility) => { const item = Object.assign({}, mockParentGroupItem, { visibility }); const vm = createComponent(item); - vm.$mount(); expect(vm.visibilityIcon).toBe(VISIBILITY_TYPE_ICON[visibility]); vm.$destroy(); }); @@ -41,7 +40,6 @@ describe('ItemStatsComponent', () => { type: ITEM_TYPE.GROUP, }); const vm = createComponent(item); - vm.$mount(); expect(vm.visibilityTooltip).toBe(GROUP_VISIBILITY_TYPE[visibility]); vm.$destroy(); }); @@ -54,7 +52,6 @@ describe('ItemStatsComponent', () => { type: ITEM_TYPE.PROJECT, }); const vm = createComponent(item); - vm.$mount(); expect(vm.visibilityTooltip).toBe(PROJECT_VISIBILITY_TYPE[visibility]); vm.$destroy(); }); @@ -68,13 +65,11 @@ describe('ItemStatsComponent', () => { item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.PROJECT }); vm = createComponent(item); - vm.$mount(); expect(vm.isProject).toBeTruthy(); vm.$destroy(); item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.GROUP }); vm = createComponent(item); - vm.$mount(); expect(vm.isProject).toBeFalsy(); vm.$destroy(); }); @@ -87,13 +82,11 @@ describe('ItemStatsComponent', () => { item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.GROUP }); vm = createComponent(item); - vm.$mount(); expect(vm.isGroup).toBeTruthy(); vm.$destroy(); item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.PROJECT }); vm = createComponent(item); - vm.$mount(); expect(vm.isGroup).toBeFalsy(); vm.$destroy(); }); @@ -101,57 +94,37 @@ describe('ItemStatsComponent', () => { }); describe('template', () => { - it('should render component template correctly', () => { + it('renders component container element correctly', () => { const vm = createComponent(); - vm.$mount(); - const visibilityIconEl = vm.$el.querySelector('.item-visibility'); - expect(vm.$el.classList.contains('.stats')).toBeDefined(); - expect(visibilityIconEl).toBeDefined(); - expect(visibilityIconEl.dataset.originalTitle).toBe(vm.visibilityTooltip); - expect(visibilityIconEl.querySelector('i.fa')).toBeDefined(); + expect(vm.$el.classList.contains('stats')).toBeTruthy(); vm.$destroy(); }); - it('should render stat icons if `item.type` is Group', () => { - const item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.GROUP }); - const vm = createComponent(item); - vm.$mount(); - - const subgroupIconEl = vm.$el.querySelector('span.number-subgroups'); - expect(subgroupIconEl).toBeDefined(); - expect(subgroupIconEl.dataset.originalTitle).toBe('Subgroups'); - expect(subgroupIconEl.querySelector('i.fa.fa-folder')).toBeDefined(); - expect(subgroupIconEl.innerText.trim()).toBe(`${vm.item.subgroupCount}`); - - const projectsIconEl = vm.$el.querySelector('span.number-projects'); - expect(projectsIconEl).toBeDefined(); - expect(projectsIconEl.dataset.originalTitle).toBe('Projects'); - expect(projectsIconEl.querySelector('i.fa.fa-bookmark')).toBeDefined(); - expect(projectsIconEl.innerText.trim()).toBe(`${vm.item.projectCount}`); - - const membersIconEl = vm.$el.querySelector('span.number-users'); - expect(membersIconEl).toBeDefined(); - expect(membersIconEl.dataset.originalTitle).toBe('Members'); - expect(membersIconEl.querySelector('i.fa.fa-users')).toBeDefined(); - expect(membersIconEl.innerText.trim()).toBe(`${vm.item.memberCount}`); + it('renders item visibility icon and tooltip correctly', () => { + const vm = createComponent(); + + const visibilityIconEl = vm.$el.querySelector('.item-visibility'); + expect(visibilityIconEl).not.toBe(null); + expect(visibilityIconEl.dataset.originalTitle).toBe(vm.visibilityTooltip); + expect(visibilityIconEl.querySelectorAll('svg').length > 0).toBeTruthy(); vm.$destroy(); }); - it('should render stat icons if `item.type` is Project', () => { + it('renders start count and last updated information for project item correctly', () => { const item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.PROJECT, starCount: 4, }); const vm = createComponent(item); - vm.$mount(); const projectStarIconEl = vm.$el.querySelector('.project-stars'); - expect(projectStarIconEl).toBeDefined(); - expect(projectStarIconEl.querySelector('i.fa.fa-star')).toBeDefined(); - expect(projectStarIconEl.innerText.trim()).toBe(`${vm.item.starCount}`); + expect(projectStarIconEl).not.toBe(null); + expect(projectStarIconEl.querySelectorAll('svg').length > 0).toBeTruthy(); + expect(projectStarIconEl.querySelectorAll('.stat-value').length > 0).toBeTruthy(); + expect(vm.$el.querySelectorAll('.last-updated').length > 0).toBeTruthy(); vm.$destroy(); }); diff --git a/spec/javascripts/groups/components/item_stats_value_spec.js b/spec/javascripts/groups/components/item_stats_value_spec.js new file mode 100644 index 00000000000..e990870aaa6 --- /dev/null +++ b/spec/javascripts/groups/components/item_stats_value_spec.js @@ -0,0 +1,81 @@ +import Vue from 'vue'; + +import itemStatsValueComponent from '~/groups/components/item_stats_value.vue'; + +import mountComponent from '../../helpers/vue_mount_component_helper'; + +const createComponent = ({ title, cssClass, iconName, tooltipPlacement, value }) => { + const Component = Vue.extend(itemStatsValueComponent); + + return mountComponent(Component, { + title, + cssClass, + iconName, + tooltipPlacement, + value, + }); +}; + +describe('ItemStatsValueComponent', () => { + describe('computed', () => { + let vm; + const itemConfig = { + title: 'Subgroups', + cssClass: 'number-subgroups', + iconName: 'folder', + tooltipPlacement: 'left', + }; + + describe('isValuePresent', () => { + it('returns true if non-empty `value` is present', () => { + vm = createComponent(Object.assign({}, itemConfig, { value: 10 })); + expect(vm.isValuePresent).toBeTruthy(); + }); + + it('returns false if empty `value` is present', () => { + vm = createComponent(itemConfig); + expect(vm.isValuePresent).toBeFalsy(); + }); + + afterEach(() => { + vm.$destroy(); + }); + }); + }); + + describe('template', () => { + let vm; + beforeEach(() => { + vm = createComponent({ + title: 'Subgroups', + cssClass: 'number-subgroups', + iconName: 'folder', + tooltipPlacement: 'left', + value: 10, + }); + }); + + it('renders component element correctly', () => { + expect(vm.$el.classList.contains('number-subgroups')).toBeTruthy(); + expect(vm.$el.querySelectorAll('svg').length > 0).toBeTruthy(); + expect(vm.$el.querySelectorAll('.stat-value').length > 0).toBeTruthy(); + }); + + it('renders element tooltip correctly', () => { + expect(vm.$el.dataset.originalTitle).toBe('Subgroups'); + expect(vm.$el.dataset.placement).toBe('left'); + }); + + it('renders element icon correctly', () => { + expect(vm.$el.querySelector('svg use').getAttribute('xlink:href')).toContain('folder'); + }); + + it('renders value count correctly', () => { + expect(vm.$el.querySelector('.stat-value').innerText.trim()).toContain('10'); + }); + + afterEach(() => { + vm.$destroy(); + }); + }); +}); diff --git a/spec/javascripts/groups/components/item_type_icon_spec.js b/spec/javascripts/groups/components/item_type_icon_spec.js index 528e6ed1b4c..495cc97b475 100644 --- a/spec/javascripts/groups/components/item_type_icon_spec.js +++ b/spec/javascripts/groups/components/item_type_icon_spec.js @@ -28,12 +28,12 @@ describe('ItemTypeIconComponent', () => { vm = createComponent(ITEM_TYPE.GROUP, true); vm.$mount(); - expect(vm.$el.querySelector('i.fa.fa-folder-open')).toBeDefined(); + expect(vm.$el.querySelector('use').getAttribute('xlink:href')).toContain('folder-open'); vm.$destroy(); vm = createComponent(ITEM_TYPE.GROUP); vm.$mount(); - expect(vm.$el.querySelector('i.fa.fa-folder')).toBeDefined(); + expect(vm.$el.querySelector('use').getAttribute('xlink:href')).toContain('folder'); vm.$destroy(); }); @@ -42,12 +42,12 @@ describe('ItemTypeIconComponent', () => { vm = createComponent(ITEM_TYPE.PROJECT); vm.$mount(); - expect(vm.$el.querySelectorAll('i.fa.fa-bookmark').length).toBe(1); + expect(vm.$el.querySelector('use').getAttribute('xlink:href')).toContain('bookmark'); vm.$destroy(); vm = createComponent(ITEM_TYPE.GROUP); vm.$mount(); - expect(vm.$el.querySelectorAll('i.fa.fa-bookmark').length).toBe(0); + expect(vm.$el.querySelector('use').getAttribute('xlink:href')).not.toContain('bookmark'); vm.$destroy(); }); }); diff --git a/spec/javascripts/groups/mock_data.js b/spec/javascripts/groups/mock_data.js index 6184d671790..8bf6417487d 100644 --- a/spec/javascripts/groups/mock_data.js +++ b/spec/javascripts/groups/mock_data.js @@ -18,9 +18,9 @@ export const PROJECT_VISIBILITY_TYPE = { }; export const VISIBILITY_TYPE_ICON = { - public: 'fa-globe', - internal: 'fa-shield', - private: 'fa-lock', + public: 'earth', + internal: 'shield', + private: 'lock', }; export const mockParentGroupItem = { @@ -46,6 +46,7 @@ export const mockParentGroupItem = { isOpen: true, isChildrenLoading: false, isBeingRemoved: false, + updatedAt: '2017-04-09T18:40:39.101Z', }; export const mockRawChildren = [ @@ -69,6 +70,7 @@ export const mockRawChildren = [ subgroup_count: 2, can_leave: false, children: [], + updated_at: '2017-04-09T18:40:39.101Z', }, ]; @@ -96,6 +98,7 @@ export const mockChildren = [ isOpen: true, isChildrenLoading: false, isBeingRemoved: false, + updatedAt: '2017-04-09T18:40:39.101Z', }, ]; @@ -119,6 +122,7 @@ export const mockGroups = [ project_count: 2, subgroup_count: 0, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', }, { id: 67, @@ -139,6 +143,7 @@ export const mockGroups = [ project_count: 0, subgroup_count: 0, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', }, { id: 54, @@ -159,6 +164,7 @@ export const mockGroups = [ project_count: 0, subgroup_count: 1, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', }, { id: 5, @@ -179,6 +185,7 @@ export const mockGroups = [ project_count: 1, subgroup_count: 0, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', }, { id: 4, @@ -199,6 +206,7 @@ export const mockGroups = [ project_count: 2, subgroup_count: 0, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', }, { id: 3, @@ -219,6 +227,7 @@ export const mockGroups = [ project_count: 1, subgroup_count: 0, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', }, { id: 2, @@ -239,6 +248,7 @@ export const mockGroups = [ project_count: 4, subgroup_count: 0, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', }, ]; @@ -262,6 +272,7 @@ export const mockSearchedGroups = [ project_count: 1, subgroup_count: 2, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', children: [ { id: 57, @@ -282,6 +293,7 @@ export const mockSearchedGroups = [ project_count: 4, subgroup_count: 2, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', children: [ { id: 60, @@ -302,6 +314,7 @@ export const mockSearchedGroups = [ project_count: 0, subgroup_count: 1, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', children: [ { id: 61, @@ -322,6 +335,7 @@ export const mockSearchedGroups = [ project_count: 2, subgroup_count: 0, can_leave: false, + updated_at: '2017-04-09T18:40:39.101Z', children: [ { id: 17, @@ -336,6 +350,7 @@ export const mockSearchedGroups = [ permission: null, edit_path: '/platform/hardware/bsp/kernel/common/v4.4/edit', star_count: 0, + updated_at: '2017-09-12T06:37:04.925Z', }, { id: 16, @@ -350,6 +365,7 @@ export const mockSearchedGroups = [ permission: null, edit_path: '/platform/hardware/bsp/kernel/common/v4.1/edit', star_count: 0, + updated_at: '2017-04-09T18:41:03.112Z', }, ], }, diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js index 7159148f8fa..1454ca52018 100644 --- a/spec/javascripts/issue_show/components/app_spec.js +++ b/spec/javascripts/issue_show/components/app_spec.js @@ -1,4 +1,6 @@ import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import '~/render_math'; import '~/render_gfm'; import * as urlUtils from '~/lib/utils/url_utility'; @@ -11,26 +13,29 @@ function formatText(text) { return text.trim().replace(/\s\s+/g, ' '); } +const REALTIME_REQUEST_STACK = [ + issueShowData.initialRequest, + issueShowData.secondRequest, +]; + describe('Issuable output', () => { - let requestData = issueShowData.initialRequest; + let mock; + let realtimeRequestCount = 0; + let vm; document.body.innerHTML = '<span id="task_status"></span>'; - const interceptor = (request, next) => { - next(request.respondWith(JSON.stringify(requestData), { - status: 200, - })); - }; - - let vm; - beforeEach((done) => { spyOn(eventHub, '$emit'); const IssuableDescriptionComponent = Vue.extend(issuableApp); - requestData = issueShowData.initialRequest; - Vue.http.interceptors.push(interceptor); + mock = new MockAdapter(axios); + mock.onGet('/gitlab-org/gitlab-shell/issues/9/realtime_changes/realtime_changes').reply(() => { + const res = Promise.resolve([200, REALTIME_REQUEST_STACK[realtimeRequestCount]]); + realtimeRequestCount += 1; + return res; + }); vm = new IssuableDescriptionComponent({ propsData: { @@ -54,10 +59,10 @@ describe('Issuable output', () => { }); afterEach(() => { - Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor); + mock.reset(); + realtimeRequestCount = 0; vm.poll.stop(); - vm.$destroy(); }); @@ -77,7 +82,6 @@ describe('Issuable output', () => { expect(editedText.querySelector('time')).toBeTruthy(); }) .then(() => { - requestData = issueShowData.secondRequest; vm.poll.makeRequest(); }) .then(() => new Promise(resolve => setTimeout(resolve))) @@ -141,24 +145,19 @@ describe('Issuable output', () => { spyOn(vm.service, 'getData').and.callThrough(); spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => { resolve({ - json() { - return { - confidential: false, - web_url: location.pathname, - }; + data: { + confidential: false, + web_url: location.pathname, }, }); })); - vm.updateIssuable(); - - setTimeout(() => { - expect( - vm.service.getData, - ).toHaveBeenCalled(); - - done(); - }); + vm.updateIssuable() + .then(() => { + expect(vm.service.getData).toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); }); it('correctly updates issuable data', (done) => { @@ -166,29 +165,22 @@ describe('Issuable output', () => { resolve(); })); - vm.updateIssuable(); - - setTimeout(() => { - expect( - vm.service.updateIssuable, - ).toHaveBeenCalledWith(vm.formState); - expect( - eventHub.$emit, - ).toHaveBeenCalledWith('close.form'); - - done(); - }); + vm.updateIssuable() + .then(() => { + expect(vm.service.updateIssuable).toHaveBeenCalledWith(vm.formState); + expect(eventHub.$emit).toHaveBeenCalledWith('close.form'); + }) + .then(done) + .catch(done.fail); }); it('does not redirect if issue has not moved', (done) => { spyOn(urlUtils, 'visitUrl'); spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => { resolve({ - json() { - return { - web_url: location.pathname, - confidential: vm.isConfidential, - }; + data: { + web_url: location.pathname, + confidential: vm.isConfidential, }, }); })); @@ -208,11 +200,9 @@ describe('Issuable output', () => { spyOn(urlUtils, 'visitUrl'); spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => { resolve({ - json() { - return { - web_url: '/testing-issue-move', - confidential: vm.isConfidential, - }; + data: { + web_url: '/testing-issue-move', + confidential: vm.isConfidential, }, }); })); @@ -283,10 +273,8 @@ describe('Issuable output', () => { let modal; const promise = new Promise((resolve) => { resolve({ - json() { - return { - recaptcha_html: '<div class="g-recaptcha">recaptcha_html</div>', - }; + data: { + recaptcha_html: '<div class="g-recaptcha">recaptcha_html</div>', }, }); }); @@ -323,8 +311,8 @@ describe('Issuable output', () => { spyOn(urlUtils, 'visitUrl'); spyOn(vm.service, 'deleteIssuable').and.callFake(() => new Promise((resolve) => { resolve({ - json() { - return { web_url: '/test' }; + data: { + web_url: '/test', }, }); })); @@ -345,8 +333,8 @@ describe('Issuable output', () => { spyOn(vm.poll, 'stop').and.callThrough(); spyOn(vm.service, 'deleteIssuable').and.callFake(() => new Promise((resolve) => { resolve({ - json() { - return { web_url: '/test' }; + data: { + web_url: '/test', }, }); })); @@ -385,22 +373,21 @@ describe('Issuable output', () => { describe('open form', () => { it('shows locked warning if form is open & data is different', (done) => { - Vue.nextTick() + vm.$nextTick() .then(() => { vm.openForm(); - requestData = issueShowData.secondRequest; vm.poll.makeRequest(); }) - .then(() => new Promise(resolve => setTimeout(resolve))) + // Wait for the request + .then(vm.$nextTick) + // Wait for the successCallback to update the store state + .then(vm.$nextTick) + // Wait for the new state to flow to the Vue components + .then(vm.$nextTick) .then(() => { - expect( - vm.formState.lockedWarningVisible, - ).toBeTruthy(); - - expect( - vm.$el.querySelector('.alert'), - ).not.toBeNull(); + expect(vm.formState.lockedWarningVisible).toEqual(true); + expect(vm.$el.querySelector('.alert')).not.toBeNull(); }) .then(done) .catch(done.fail); diff --git a/spec/javascripts/issue_show/mock_data.js b/spec/javascripts/issue_show/mock_data.js index eb3111412a7..74b3efb014b 100644 --- a/spec/javascripts/issue_show/mock_data.js +++ b/spec/javascripts/issue_show/mock_data.js @@ -19,14 +19,4 @@ export default { updated_by_name: 'Other User', updated_by_path: '/other_user', }, - issueSpecRequest: { - title: '<p>this is a title</p>', - title_text: 'this is a title', - description: '<li class="task-list-item enabled"><input type="checkbox" class="task-list-item-checkbox">Task List Item</li>', - description_text: '- [ ] Task List Item', - task_status: '0 of 1 completed', - updated_at: '2017-05-15T12:31:04.428Z', - updated_by_name: 'Last User', - updated_by_path: '/last_user', - }, }; diff --git a/spec/javascripts/job_spec.js b/spec/javascripts/job_spec.js index 4f06237deb5..b740c9ed893 100644 --- a/spec/javascripts/job_spec.js +++ b/spec/javascripts/job_spec.js @@ -1,4 +1,4 @@ -import { bytesToKiB } from '~/lib/utils/number_utils'; +import { numberToHumanSize } from '~/lib/utils/number_utils'; import * as urlUtils from '~/lib/utils/url_utility'; import '~/lib/utils/datetime_utility'; import Job from '~/job'; @@ -169,7 +169,7 @@ describe('Job', () => { expect( document.querySelector('.js-truncated-info-size').textContent.trim(), - ).toEqual(`${bytesToKiB(size)}`); + ).toEqual(`${numberToHumanSize(size)}`); }); it('shows incremented size', () => { @@ -195,7 +195,7 @@ describe('Job', () => { expect( document.querySelector('.js-truncated-info-size').textContent.trim(), - ).toEqual(`${bytesToKiB(50)}`); + ).toEqual(`${numberToHumanSize(50)}`); jasmine.clock().tick(4001); @@ -209,7 +209,7 @@ describe('Job', () => { expect( document.querySelector('.js-truncated-info-size').textContent.trim(), - ).toEqual(`${bytesToKiB(60)}`); + ).toEqual(`${numberToHumanSize(60)}`); }); it('renders the raw link', () => { diff --git a/spec/javascripts/jobs/header_spec.js b/spec/javascripts/jobs/header_spec.js index 4a210faa017..83395ea451e 100644 --- a/spec/javascripts/jobs/header_spec.js +++ b/spec/javascripts/jobs/header_spec.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import headerComponent from '~/jobs/components/header.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; describe('Job details header', () => { let HeaderComponent; @@ -35,7 +36,7 @@ describe('Job details header', () => { isLoading: false, }; - vm = new HeaderComponent({ propsData: props }).$mount(); + vm = mountComponent(HeaderComponent, props); }); afterEach(() => { diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js index 1f46c225071..6f8dad6b835 100644 --- a/spec/javascripts/lib/utils/text_utility_spec.js +++ b/spec/javascripts/lib/utils/text_utility_spec.js @@ -62,4 +62,14 @@ describe('text_utility', () => { expect(textUtils.slugify('João')).toEqual('joão'); }); }); + + describe('stripeHtml', () => { + it('replaces html tag with the default replacement', () => { + expect(textUtils.stripeHtml('This is a text with <p>html</p>.')).toEqual('This is a text with html.'); + }); + + it('replaces html tags with the provided replacement', () => { + expect(textUtils.stripeHtml('This is a text with <p>html</p>.', ' ')).toEqual('This is a text with html .'); + }); + }); }); diff --git a/spec/javascripts/monitoring/graph/deployment_spec.js b/spec/javascripts/monitoring/graph/deployment_spec.js index bf6ada8185e..d07db871d69 100644 --- a/spec/javascripts/monitoring/graph/deployment_spec.js +++ b/spec/javascripts/monitoring/graph/deployment_spec.js @@ -11,168 +11,38 @@ const createComponent = (propsData) => { }; describe('MonitoringDeployment', () => { - const reducedDeploymentData = [deploymentData[0]]; - reducedDeploymentData[0].ref = reducedDeploymentData[0].ref.name; - reducedDeploymentData[0].xPos = 10; - reducedDeploymentData[0].time = new Date(reducedDeploymentData[0].created_at); describe('Methods', () => { - it('refText shows the ref when a tag is available', () => { - reducedDeploymentData[0].tag = '1.0'; - const component = createComponent({ - showDeployInfo: false, - deploymentData: reducedDeploymentData, - graphWidth: 440, - graphHeight: 300, - graphHeightOffset: 120, - }); - - expect( - component.refText(reducedDeploymentData[0]), - ).toEqual(reducedDeploymentData[0].ref); - }); - - it('refText shows the sha when no tag is available', () => { - reducedDeploymentData[0].tag = null; - const component = createComponent({ - showDeployInfo: false, - deploymentData: reducedDeploymentData, - graphHeight: 300, - graphWidth: 440, - graphHeightOffset: 120, - }); - - expect( - component.refText(reducedDeploymentData[0]), - ).toContain('f5bcd1'); - }); - - it('nameDeploymentClass creates a class with the prefix deploy-info-', () => { + it('should contain a hidden gradient', () => { const component = createComponent({ - showDeployInfo: false, - deploymentData: reducedDeploymentData, + showDeployInfo: true, + deploymentData, graphHeight: 300, graphWidth: 440, graphHeightOffset: 120, }); - expect( - component.nameDeploymentClass(reducedDeploymentData[0]), - ).toContain('deploy-info'); + expect(component.$el.querySelector('#shadow-gradient')).not.toBeNull(); }); it('transformDeploymentGroup translates an available deployment', () => { const component = createComponent({ showDeployInfo: false, - deploymentData: reducedDeploymentData, + deploymentData, graphHeight: 300, graphWidth: 440, graphHeightOffset: 120, }); expect( - component.transformDeploymentGroup(reducedDeploymentData[0]), + component.transformDeploymentGroup({ xPos: 16 }), ).toContain('translate(11, 20)'); }); - it('hides the deployment flag', () => { - reducedDeploymentData[0].showDeploymentFlag = false; - const component = createComponent({ - showDeployInfo: true, - deploymentData: reducedDeploymentData, - graphWidth: 440, - graphHeight: 300, - graphHeightOffset: 120, - }); - - expect(component.$el.querySelector('.js-deploy-info-box')).toBeNull(); - }); - - it('positions the flag to the left when the xPos is too far right', () => { - reducedDeploymentData[0].showDeploymentFlag = false; - reducedDeploymentData[0].xPos = 250; - const component = createComponent({ - showDeployInfo: true, - deploymentData: reducedDeploymentData, - graphWidth: 440, - graphHeight: 300, - graphHeightOffset: 120, - }); - - expect( - component.positionFlag(reducedDeploymentData[0]), - ).toBeLessThan(0); - }); - - it('shows the deployment flag', () => { - reducedDeploymentData[0].showDeploymentFlag = true; - const component = createComponent({ - showDeployInfo: true, - deploymentData: reducedDeploymentData, - graphHeight: 300, - graphWidth: 440, - graphHeightOffset: 120, - }); - - expect( - component.$el.querySelector('.js-deploy-info-box').style.display, - ).not.toEqual('display: none;'); - }); - - it('contains date, refs and the "deployed" text', () => { - reducedDeploymentData[0].showDeploymentFlag = true; - const component = createComponent({ - showDeployInfo: true, - deploymentData: reducedDeploymentData, - graphHeight: 300, - graphWidth: 440, - graphHeightOffset: 120, - }); - - expect( - component.$el.querySelectorAll('.deploy-info-text'), - ).toContainText('Deployed'); - - expect( - component.$el.querySelectorAll('.deploy-info-text'), - ).toContainText('Wed, May 31'); - - expect( - component.$el.querySelectorAll('.deploy-info-text'), - ).toContainText(component.refText(reducedDeploymentData[0])); - }); - - it('contains a link to the commit contents', () => { - reducedDeploymentData[0].showDeploymentFlag = true; - const component = createComponent({ - showDeployInfo: true, - deploymentData: reducedDeploymentData, - graphHeight: 300, - graphWidth: 440, - graphHeightOffset: 120, - }); - - expect( - component.$el.querySelectorAll('.deploy-info-text-link')[0].parentElement.getAttribute('xlink:href'), - ).not.toEqual(''); - }); - - it('should contain a hidden gradient', () => { - const component = createComponent({ - showDeployInfo: true, - deploymentData: reducedDeploymentData, - graphHeight: 300, - graphWidth: 440, - graphHeightOffset: 120, - }); - - expect(component.$el.querySelector('#shadow-gradient')).not.toBeNull(); - }); - describe('Computed props', () => { it('calculatedHeight', () => { const component = createComponent({ showDeployInfo: true, - deploymentData: reducedDeploymentData, + deploymentData, graphHeight: 300, graphWidth: 440, graphHeightOffset: 120, diff --git a/spec/javascripts/monitoring/graph/flag_spec.js b/spec/javascripts/monitoring/graph/flag_spec.js index 8ee1171419d..2d474e9092f 100644 --- a/spec/javascripts/monitoring/graph/flag_spec.js +++ b/spec/javascripts/monitoring/graph/flag_spec.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import GraphFlag from '~/monitoring/components/graph/flag.vue'; +import { deploymentData } from '../mock_data'; const createComponent = (propsData) => { const Component = Vue.extend(GraphFlag); @@ -9,11 +10,6 @@ const createComponent = (propsData) => { }).$mount(); }; -function getCoordinate(component, selector, coordinate) { - const coordinateVal = component.$el.querySelector(selector).getAttribute(coordinate); - return parseInt(coordinateVal, 10); -} - const defaultValuesComponent = { currentXCoordinate: 200, currentYCoordinate: 100, @@ -25,31 +21,111 @@ const defaultValuesComponent = { graphHeight: 300, graphHeightOffset: 120, showFlagContent: true, + realPixelRatio: 1, + timeSeries: [{ + values: [{ + time: new Date('2017-06-04T18:17:33.501Z'), + value: '1.49609375', + }], + }], + unitOfDisplay: 'ms', + currentDataIndex: 0, + legendTitle: 'Average', +}; + +const deploymentFlagData = { + ...deploymentData[0], + ref: deploymentData[0].ref.name, + xPos: 10, + time: new Date(deploymentData[0].created_at), }; describe('GraphFlag', () => { - it('has a line and a circle located at the currentXCoordinate and currentYCoordinate', () => { - const component = createComponent(defaultValuesComponent); + let component; - expect(getCoordinate(component, '.selected-metric-line', 'x1')) - .toEqual(component.currentXCoordinate); - expect(getCoordinate(component, '.selected-metric-line', 'x2')) - .toEqual(component.currentXCoordinate); + it('has a line at the currentXCoordinate', () => { + component = createComponent(defaultValuesComponent); + + expect(component.$el.style.left) + .toEqual(`${70 + component.currentXCoordinate}px`); }); - it('has a SVG with the class rect-text-metric at the currentFlagPosition', () => { - const component = createComponent(defaultValuesComponent); + describe('Deployment flag', () => { + it('shows a deployment flag when deployment data provided', () => { + const deploymentFlagComponent = createComponent({ + ...defaultValuesComponent, + deploymentFlagData, + }); + + expect( + deploymentFlagComponent.$el.querySelector('.popover-title'), + ).toContainText('Deployed'); + }); + + it('contains the ref when a tag is available', () => { + const deploymentFlagComponent = createComponent({ + ...defaultValuesComponent, + deploymentFlagData: { + ...deploymentFlagData, + sha: 'f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187', + tag: true, + ref: '1.0', + }, + }); + + expect( + deploymentFlagComponent.$el.querySelector('.deploy-meta-content'), + ).toContainText('f5bcd1d9'); + + expect( + deploymentFlagComponent.$el.querySelector('.deploy-meta-content'), + ).toContainText('1.0'); + }); + + it('does not contain the ref when a tag is unavailable', () => { + const deploymentFlagComponent = createComponent({ + ...defaultValuesComponent, + deploymentFlagData: { + ...deploymentFlagData, + sha: 'f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187', + tag: false, + ref: '1.0', + }, + }); + + expect( + deploymentFlagComponent.$el.querySelector('.deploy-meta-content'), + ).toContainText('f5bcd1d9'); - const svg = component.$el.querySelector('.rect-text-metric'); - expect(svg.tagName).toEqual('svg'); - expect(parseInt(svg.getAttribute('x'), 10)).toEqual(component.currentFlagPosition); + expect( + deploymentFlagComponent.$el.querySelector('.deploy-meta-content'), + ).not.toContainText('1.0'); + }); }); describe('Computed props', () => { - it('calculatedHeight', () => { - const component = createComponent(defaultValuesComponent); + beforeEach(() => { + component = createComponent(defaultValuesComponent); + }); + + it('formatTime', () => { + expect(component.formatTime).toMatch(/\d:17PM/); + }); + + it('formatDate', () => { + expect(component.formatDate).toEqual('Sun, Jun 4'); + }); + + it('cursorStyle', () => { + expect(component.cursorStyle).toEqual({ + top: '20px', + left: '270px', + height: '180px', + }); + }); - expect(component.calculatedHeight).toEqual(180); + it('flagOrientation', () => { + expect(component.flagOrientation).toEqual('left'); }); }); }); diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js index 1f4e858e731..2bbe963e393 100644 --- a/spec/javascripts/monitoring/mock_data.js +++ b/spec/javascripts/monitoring/mock_data.js @@ -1,6 +1,8 @@ /* eslint-disable quote-props, indent, comma-dangle */ -const metricsGroupsAPIResponse = { +export const mockApiEndpoint = `${gl.TEST_HOST}/monitoring/mock`; + +export const metricsGroupsAPIResponse = { 'success': true, 'data': [ { diff --git a/spec/javascripts/pipelines/nav_controls_spec.js b/spec/javascripts/pipelines/nav_controls_spec.js index f1697840fcd..09a0c14d96c 100644 --- a/spec/javascripts/pipelines/nav_controls_spec.js +++ b/spec/javascripts/pipelines/nav_controls_spec.js @@ -14,6 +14,7 @@ describe('Pipelines Nav Controls', () => { hasCiEnabled: true, helpPagePath: 'foo', ciLintPath: 'foo', + resetCachePath: 'foo', canCreatePipeline: true, }; @@ -31,6 +32,7 @@ describe('Pipelines Nav Controls', () => { hasCiEnabled: true, helpPagePath: 'foo', ciLintPath: 'foo', + resetCachePath: 'foo', canCreatePipeline: false, }; @@ -41,12 +43,31 @@ describe('Pipelines Nav Controls', () => { expect(component.$el.querySelector('.btn-create')).toEqual(null); }); + it('should render link for resetting runner caches', () => { + const mockData = { + newPipelinePath: 'foo', + hasCiEnabled: true, + helpPagePath: 'foo', + ciLintPath: 'foo', + resetCachePath: 'foo', + canCreatePipeline: false, + }; + + const component = new NavControlsComponent({ + propsData: mockData, + }).$mount(); + + expect(component.$el.querySelectorAll('.btn-default')[0].textContent).toContain('Clear runner caches'); + expect(component.$el.querySelectorAll('.btn-default')[0].getAttribute('href')).toEqual(mockData.resetCachePath); + }); + it('should render link for CI lint', () => { const mockData = { newPipelinePath: 'foo', hasCiEnabled: true, helpPagePath: 'foo', ciLintPath: 'foo', + resetCachePath: 'foo', canCreatePipeline: true, }; @@ -54,8 +75,8 @@ describe('Pipelines Nav Controls', () => { propsData: mockData, }).$mount(); - expect(component.$el.querySelector('.btn-default').textContent).toContain('CI Lint'); - expect(component.$el.querySelector('.btn-default').getAttribute('href')).toEqual(mockData.ciLintPath); + expect(component.$el.querySelectorAll('.btn-default')[1].textContent).toContain('CI Lint'); + expect(component.$el.querySelectorAll('.btn-default')[1].getAttribute('href')).toEqual(mockData.ciLintPath); }); it('should render link to help page when CI is not enabled', () => { @@ -64,6 +85,7 @@ describe('Pipelines Nav Controls', () => { hasCiEnabled: false, helpPagePath: 'foo', ciLintPath: 'foo', + resetCachePath: 'foo', canCreatePipeline: true, }; @@ -81,6 +103,7 @@ describe('Pipelines Nav Controls', () => { hasCiEnabled: true, helpPagePath: 'foo', ciLintPath: 'foo', + resetCachePath: 'foo', canCreatePipeline: true, }; diff --git a/spec/javascripts/profile/account/components/delete_account_modal_spec.js b/spec/javascripts/profile/account/components/delete_account_modal_spec.js index 2e94948cfb2..588b61196a5 100644 --- a/spec/javascripts/profile/account/components/delete_account_modal_spec.js +++ b/spec/javascripts/profile/account/components/delete_account_modal_spec.js @@ -51,7 +51,7 @@ describe('DeleteAccountModal component', () => { Vue.nextTick() .then(() => { expect(vm.enteredPassword).toBe(input.value); - expect(submitButton).toHaveClass('disabled'); + expect(submitButton).toHaveAttr('disabled', 'disabled'); submitButton.click(); expect(form.submit).not.toHaveBeenCalled(); }) @@ -68,7 +68,7 @@ describe('DeleteAccountModal component', () => { Vue.nextTick() .then(() => { expect(vm.enteredPassword).toBe(input.value); - expect(submitButton).not.toHaveClass('disabled'); + expect(submitButton).not.toHaveAttr('disabled', 'disabled'); submitButton.click(); expect(form.submit).toHaveBeenCalled(); }) @@ -101,7 +101,7 @@ describe('DeleteAccountModal component', () => { Vue.nextTick() .then(() => { expect(vm.enteredUsername).toBe(input.value); - expect(submitButton).toHaveClass('disabled'); + expect(submitButton).toHaveAttr('disabled', 'disabled'); submitButton.click(); expect(form.submit).not.toHaveBeenCalled(); }) @@ -118,7 +118,7 @@ describe('DeleteAccountModal component', () => { Vue.nextTick() .then(() => { expect(vm.enteredUsername).toBe(input.value); - expect(submitButton).not.toHaveClass('disabled'); + expect(submitButton).not.toHaveAttr('disabled', 'disabled'); submitButton.click(); expect(form.submit).toHaveBeenCalled(); }) diff --git a/spec/javascripts/projects/project_new_spec.js b/spec/javascripts/projects/project_new_spec.js index 850768f0e4f..c314ca8ab72 100644 --- a/spec/javascripts/projects/project_new_spec.js +++ b/spec/javascripts/projects/project_new_spec.js @@ -6,8 +6,12 @@ describe('New Project', () => { beforeEach(() => { setFixtures(` - <input id="project_import_url" /> - <input id="project_path" /> + <div class='toggle-import-form'> + <div class='import-url-data'> + <input id="project_import_url" /> + <input id="project_path" /> + </div> + </div> `); $projectImportUrl = $('#project_import_url'); @@ -25,7 +29,7 @@ describe('New Project', () => { it('does not change project path for disabled $projectImportUrl', () => { $projectImportUrl.attr('disabled', true); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual(dummyImportUrl); }); @@ -38,7 +42,7 @@ describe('New Project', () => { it('does not change project path if it is set by user', () => { $projectPath.keyup(); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual(dummyImportUrl); }); @@ -46,7 +50,7 @@ describe('New Project', () => { it('does not change project path for empty $projectImportUrl', () => { $projectImportUrl.val(''); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual(dummyImportUrl); }); @@ -54,7 +58,7 @@ describe('New Project', () => { it('does not change project path for whitespace $projectImportUrl', () => { $projectImportUrl.val(' '); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual(dummyImportUrl); }); @@ -62,7 +66,7 @@ describe('New Project', () => { it('does not change project path for $projectImportUrl without slashes', () => { $projectImportUrl.val('has-no-slash'); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual(dummyImportUrl); }); @@ -70,7 +74,7 @@ describe('New Project', () => { it('changes project path to last $projectImportUrl component', () => { $projectImportUrl.val('/this/is/last'); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual('last'); }); @@ -78,7 +82,7 @@ describe('New Project', () => { it('ignores trailing slashes in $projectImportUrl', () => { $projectImportUrl.val('/has/trailing/slash/'); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual('slash'); }); @@ -86,7 +90,7 @@ describe('New Project', () => { it('ignores fragment identifier in $projectImportUrl', () => { $projectImportUrl.val('/this/has/a#fragment-identifier/'); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual('a'); }); @@ -94,7 +98,7 @@ describe('New Project', () => { it('ignores query string in $projectImportUrl', () => { $projectImportUrl.val('/url/with?query=string'); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual('with'); }); @@ -102,7 +106,7 @@ describe('New Project', () => { it('ignores trailing .git in $projectImportUrl', () => { $projectImportUrl.val('/repository.git'); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual('repository'); }); @@ -110,7 +114,7 @@ describe('New Project', () => { it('changes project path for HTTPS URL in $projectImportUrl', () => { $projectImportUrl.val('https://username:password@gitlab.company.com/group/project.git'); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual('project'); }); @@ -118,7 +122,7 @@ describe('New Project', () => { it('changes project path for SSH URL in $projectImportUrl', () => { $projectImportUrl.val('git@gitlab.com:gitlab-org/gitlab-ce.git'); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual('gitlab-ce'); }); diff --git a/spec/javascripts/repo/components/ide_repo_tree_spec.js b/spec/javascripts/repo/components/ide_repo_tree_spec.js index b6f70f585cd..e3bbda514da 100644 --- a/spec/javascripts/repo/components/ide_repo_tree_spec.js +++ b/spec/javascripts/repo/components/ide_repo_tree_spec.js @@ -41,11 +41,11 @@ describe('IdeRepoTree', () => { expect(tbody.querySelector('.file')).toBeTruthy(); }); - it('renders 5 loading files if tree is loading', (done) => { - vm.$store.state.loading = true; + it('renders 3 loading files if tree is loading', (done) => { + vm.treeId = '123'; Vue.nextTick(() => { - expect(vm.$el.querySelectorAll('tbody .loading-file').length).toEqual(5); + expect(vm.$el.querySelectorAll('.multi-file-loading-container').length).toEqual(3); done(); }); diff --git a/spec/javascripts/repo/components/ide_spec.js b/spec/javascripts/repo/components/ide_spec.js index 20b8dc25dcb..acfd63eb8de 100644 --- a/spec/javascripts/repo/components/ide_spec.js +++ b/spec/javascripts/repo/components/ide_spec.js @@ -10,7 +10,9 @@ describe('ide component', () => { beforeEach(() => { const Component = Vue.extend(ide); - vm = createComponentWithStore(Component, store).$mount(); + vm = createComponentWithStore(Component, store, { + emptyStateSvgPath: 'svg', + }).$mount(); }); afterEach(() => { diff --git a/spec/javascripts/repo/components/new_dropdown/index_spec.js b/spec/javascripts/repo/components/new_dropdown/index_spec.js index b001c1655b4..6efbbf6d75e 100644 --- a/spec/javascripts/repo/components/new_dropdown/index_spec.js +++ b/spec/javascripts/repo/components/new_dropdown/index_spec.js @@ -57,16 +57,17 @@ describe('new dropdown component', () => { }); }); - describe('toggleModalOpen', () => { + describe('hideModal', () => { + beforeAll((done) => { + vm.openModal = true; + Vue.nextTick(done); + }); + it('closes modal after toggling', (done) => { - vm.toggleModalOpen(); + vm.hideModal(); Vue.nextTick() .then(() => { - expect(vm.$el.querySelector('.modal')).not.toBeNull(); - }) - .then(vm.toggleModalOpen) - .then(() => { expect(vm.$el.querySelector('.modal')).toBeNull(); }) .then(done) diff --git a/spec/javascripts/repo/components/repo_file_spec.js b/spec/javascripts/repo/components/repo_file_spec.js index e8b370f97b4..0810da87e80 100644 --- a/spec/javascripts/repo/components/repo_file_spec.js +++ b/spec/javascripts/repo/components/repo_file_spec.js @@ -32,13 +32,9 @@ describe('RepoFile', () => { vm.$mount(); const name = vm.$el.querySelector('.repo-file-name'); - const fileIcon = vm.$el.querySelector('.file-icon'); - expect(vm.$el.querySelector(`.${vm.file.icon}`).style.marginLeft).toEqual('0px'); expect(name.href).toMatch(''); expect(name.textContent.trim()).toEqual(vm.file.name); - expect(fileIcon.classList.contains(vm.file.icon)).toBeTruthy(); - expect(fileIcon.style.marginLeft).toEqual(`${vm.file.level * 10}px`); }); it('does render if hasFiles is true and is loading tree', () => { @@ -49,17 +45,6 @@ describe('RepoFile', () => { expect(vm.$el.querySelector('.fa-spin.fa-spinner')).toBeFalsy(); }); - it('renders a spinner if the file is loading', () => { - const f = file(); - f.loading = true; - vm = createComponent({ - file: f, - }); - - expect(vm.$el.querySelector('.fa-spin.fa-spinner')).not.toBeNull(); - expect(vm.$el.querySelector('.fa-spin.fa-spinner').style.marginLeft).toEqual(`${vm.file.level * 16}px`); - }); - it('does not render commit message and datetime if mini', (done) => { vm = createComponent({ file: file(), diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js index db7d083065b..6a59dc3c87e 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js @@ -95,10 +95,8 @@ describe('MRWidgetDeployment', () => { const url = '/foo/bar'; const returnPromise = () => new Promise((resolve) => { resolve({ - json() { - return { - redirect_url: url, - }; + data: { + redirect_url: url, }, }); }); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js index 2ae3adc1f93..07ed7f7f532 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js @@ -155,9 +155,7 @@ describe('MemoryUsage', () => { describe('loadMetrics', () => { const returnServicePromise = () => new Promise((resolve) => { resolve({ - json() { - return metricsMockData; - }, + data: metricsMockData, }); }); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js new file mode 100644 index 00000000000..66ecaa316c8 --- /dev/null +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js @@ -0,0 +1,115 @@ +import Vue from 'vue'; +import eventHub from '~/vue_merge_request_widget/event_hub'; +import component from '~/vue_merge_request_widget/components/states/mr_widget_rebase.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Merge request widget rebase component', () => { + let Component; + let vm; + beforeEach(() => { + Component = Vue.extend(component); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('While rebasing', () => { + it('should show progress message', () => { + vm = mountComponent(Component, { + mr: { rebaseInProgress: true }, + service: {}, + }); + + expect( + vm.$el.querySelector('.rebase-state-find-class-convention span').textContent.trim(), + ).toContain('Rebase in progress'); + }); + }); + + describe('With permissions', () => { + beforeEach(() => { + vm = mountComponent(Component, { + mr: { + rebaseInProgress: false, + canPushToSourceBranch: true, + }, + service: {}, + }); + }); + + it('it should render rebase button and warning message', () => { + const text = vm.$el.querySelector('.rebase-state-find-class-convention span').textContent.trim(); + expect(text).toContain('Fast-forward merge is not possible.'); + expect(text).toContain('Rebase the source branch onto the target branch or merge target'); + expect(text).toContain('branch into source branch to allow this merge request to be merged.'); + }); + + it('it should render error message when it fails', (done) => { + vm.rebasingError = 'Something went wrong!'; + + Vue.nextTick(() => { + expect( + vm.$el.querySelector('.rebase-state-find-class-convention span').textContent.trim(), + ).toContain('Something went wrong!'); + done(); + }); + }); + }); + + describe('Without permissions', () => { + it('should render a message explaining user does not have permissions', () => { + vm = mountComponent(Component, { + mr: { + rebaseInProgress: false, + canPushToSourceBranch: false, + targetBranch: 'foo', + }, + service: {}, + }); + + const text = vm.$el.querySelector('.rebase-state-find-class-convention span').textContent.trim(); + + expect(text).toContain('Fast-forward merge is not possible.'); + expect(text).toContain('Rebase the source branch onto'); + expect(text).toContain('foo'); + expect(text).toContain('to allow this merge request to be merged.'); + }); + }); + + describe('methods', () => { + it('checkRebaseStatus', (done) => { + spyOn(eventHub, '$emit'); + vm = mountComponent(Component, { + mr: {}, + service: { + rebase() { + return Promise.resolve(); + }, + poll() { + return Promise.resolve({ + data: { + rebase_in_progress: false, + merge_error: null, + }, + }); + }, + }, + }); + + vm.rebase(); + + // Wait for the rebase request + vm.$nextTick() + // Wait for the polling request + .then(vm.$nextTick()) + // Wait for the eventHub to be called + .then(vm.$nextTick()) + .then(() => { + expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); + }) + .then(done) + .catch(done.fail); + }); + }); +}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js index d23b558f4ea..1bf97bbf093 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js @@ -4,13 +4,16 @@ import closedComponent from '~/vue_merge_request_widget/components/states/mr_wid const mr = { targetBranch: 'good-branch', targetBranchPath: '/good-branch', - closedEvent: { - author: { + metrics: { + mergedBy: {}, + mergedAt: 'mergedUpdatedAt', + closedBy: { name: 'Fatih Acet', username: 'fatihacet', }, - updatedAt: 'closedEventUpdatedAt', - formattedUpdatedAt: '', + closedAt: 'closedEventUpdatedAt', + readableMergedAt: '', + readableClosedAt: '', }, updatedAt: 'mrUpdatedAt', closedAt: '1 day ago', @@ -56,7 +59,7 @@ describe('MRWidgetClosed', () => { it('should have correct elements', () => { expect(el.querySelector('h4').textContent).toContain('Closed by'); - expect(el.querySelector('h4').textContent).toContain(mr.closedEvent.author.name); + expect(el.querySelector('h4').textContent).toContain(mr.metrics.closedBy.name); expect(el.textContent).toContain('The changes were not merged into'); expect(el.querySelector('.label-branch').getAttribute('href')).toEqual(mr.targetBranchPath); expect(el.querySelector('.label-branch').textContent).toContain(mr.targetBranch); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js index 9a71d0b47d7..5f4df15bcd6 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js @@ -108,9 +108,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => { spyOn(eventHub, '$emit'); spyOn(vm.service, 'cancelAutomaticMerge').and.returnValue(new Promise((resolve) => { resolve({ - json() { - return mrObj; - }, + data: mrObj, }); })); @@ -129,10 +127,8 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => { spyOn(eventHub, '$emit'); spyOn(vm.service.mergeResource, 'save').and.returnValue(new Promise((resolve) => { resolve({ - json() { - return { - status: 'merge_when_pipeline_succeeds', - }; + data: { + status: 'merge_when_pipeline_succeeds', }, }); })); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js index 2714e8294fa..2dc3b72ea40 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js @@ -14,10 +14,13 @@ const createComponent = () => { canRevertInCurrentMR: true, canRemoveSourceBranch: true, sourceBranchRemoved: true, - mergedEvent: { - author: {}, - updatedAt: 'mergedUpdatedAt', - formattedUpdatedAt: '', + metrics: { + mergedBy: {}, + mergedAt: 'mergedUpdatedAt', + readableMergedAt: '', + closedBy: {}, + closedAt: 'mergedUpdatedAt', + readableClosedAt: '', }, updatedAt: 'mrUpdatedAt', targetBranch, @@ -111,10 +114,8 @@ describe('MRWidgetMerged', () => { spyOn(eventHub, '$emit'); spyOn(vm.service, 'removeSourceBranch').and.returnValue(new Promise((resolve) => { resolve({ - json() { - return { - message: 'Branch was removed', - }; + data: { + message: 'Branch was removed', }, }); })); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js index df3d29ee1f9..1127576617b 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -292,8 +292,8 @@ describe('MRWidgetReadyToMerge', () => { describe('handleMergeButtonClick', () => { const returnPromise = status => new Promise((resolve) => { resolve({ - json() { - return { status }; + data: { + status, }, }); }); @@ -364,8 +364,9 @@ describe('MRWidgetReadyToMerge', () => { describe('handleMergePolling', () => { const returnPromise = state => new Promise((resolve) => { resolve({ - json() { - return { state, source_branch_exists: true }; + data: { + state, + source_branch_exists: true, }, }); }); @@ -422,8 +423,8 @@ describe('MRWidgetReadyToMerge', () => { describe('handleRemoveBranchPolling', () => { const returnPromise = state => new Promise((resolve) => { resolve({ - json() { - return { source_branch_exists: state }; + data: { + source_branch_exists: state, }, }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js index 2cb3aaa6951..98ab61a0367 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js @@ -50,9 +50,7 @@ describe('MRWidgetWIP', () => { spyOn(eventHub, '$emit'); spyOn(vm.service, 'removeWIP').and.returnValue(new Promise((resolve) => { resolve({ - json() { - return mrObj; - }, + data: mrObj, }); })); diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js index 1ad7c2d8efa..ca29c9fee32 100644 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ b/spec/javascripts/vue_mr_widget/mock_data.js @@ -33,8 +33,8 @@ export default { "source_project_id": 19, "target_branch": "master", "target_project_id": 19, - "merge_event": { - "author": { + "metrics": { + "merged_by": { "name": "Administrator", "username": "root", "id": 1, @@ -42,9 +42,10 @@ export default { "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", "web_url": "http://localhost:3000/root" }, - "updated_at": "2017-04-07T15:39:25.696Z" + "merged_at": "2017-04-07T15:39:25.696Z", + "closed_by": null, + "closed_at": null }, - "closed_event": null, "author": { "name": "Administrator", "username": "root", diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js index 74b343c573e..cd00d0a39a3 100644 --- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js @@ -8,10 +8,7 @@ import mountComponent from '../helpers/vue_mount_component_helper'; const returnPromise = data => new Promise((resolve) => { resolve({ - json() { - return data; - }, - body: data, + data, }); }); diff --git a/spec/javascripts/vue_shared/components/commit_spec.js b/spec/javascripts/vue_shared/components/commit_spec.js index d5754aaa9e7..fdead874209 100644 --- a/spec/javascripts/vue_shared/components/commit_spec.js +++ b/spec/javascripts/vue_shared/components/commit_spec.js @@ -10,7 +10,7 @@ describe('Commit component', () => { CommitComponent = Vue.extend(commitComp); }); - it('should render a code-fork icon if it does not represent a tag', () => { + it('should render a fork icon if it does not represent a tag', () => { component = new CommitComponent({ propsData: { tag: false, @@ -30,7 +30,7 @@ describe('Commit component', () => { }, }).$mount(); - expect(component.$el.querySelector('.icon-container i').classList).toContain('fa-code-fork'); + expect(component.$el.querySelector('.icon-container').children).toContain('svg'); }); describe('Given all the props', () => { diff --git a/spec/javascripts/vue_shared/components/expand_button_spec.js b/spec/javascripts/vue_shared/components/expand_button_spec.js new file mode 100644 index 00000000000..a33ab689dd1 --- /dev/null +++ b/spec/javascripts/vue_shared/components/expand_button_spec.js @@ -0,0 +1,32 @@ +import Vue from 'vue'; +import expandButton from '~/vue_shared/components/expand_button.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('expand button', () => { + let vm; + + beforeEach(() => { + const Component = Vue.extend(expandButton); + vm = mountComponent(Component, { + slots: { + expanded: '<p>Expanded!</p>', + }, + }); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders a collpased button', () => { + expect(vm.$el.textContent.trim()).toEqual('...'); + }); + + it('hides expander on click', (done) => { + vm.$el.querySelector('button').click(); + vm.$nextTick(() => { + expect(vm.$el.querySelector('button').getAttribute('style')).toEqual('display: none;'); + done(); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/file_icon_spec.js b/spec/javascripts/vue_shared/components/file_icon_spec.js new file mode 100644 index 00000000000..d99b17bdc79 --- /dev/null +++ b/spec/javascripts/vue_shared/components/file_icon_spec.js @@ -0,0 +1,83 @@ +import Vue from 'vue'; +import fileIcon from '~/vue_shared/components/file_icon.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('File Icon component', () => { + let vm; + let FileIcon; + + beforeEach(() => { + FileIcon = Vue.extend(fileIcon); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should render a span element with an svg', () => { + vm = mountComponent(FileIcon, { + fileName: 'test.js', + }); + + expect(vm.$el.tagName).toEqual('SPAN'); + expect(vm.$el.querySelector('span > svg')).toBeDefined(); + }); + + it('should render a javascript icon based on file ending', () => { + vm = mountComponent(FileIcon, { + fileName: 'test.js', + }); + + expect(vm.$el.firstChild.firstChild.getAttribute('xlink:href')).toBe(`${gon.sprite_file_icons}#javascript`); + }); + + it('should render a image icon based on file ending', () => { + vm = mountComponent(FileIcon, { + fileName: 'test.png', + }); + + expect(vm.$el.firstChild.firstChild.getAttribute('xlink:href')).toBe(`${gon.sprite_file_icons}#image`); + }); + + it('should render a webpack icon based on file namer', () => { + vm = mountComponent(FileIcon, { + fileName: 'webpack.js', + }); + + expect(vm.$el.firstChild.firstChild.getAttribute('xlink:href')).toBe(`${gon.sprite_file_icons}#webpack`); + }); + + it('should render a standard folder icon', () => { + vm = mountComponent(FileIcon, { + fileName: 'js', + folder: true, + }); + + expect(vm.$el.querySelector('span > svg > use').getAttribute('xlink:href')).toBe(`${gon.sprite_file_icons}#folder`); + }); + + it('should render a loading icon', () => { + vm = mountComponent(FileIcon, { + fileName: 'test.js', + loading: true, + }); + + expect( + vm.$el.querySelector('i').getAttribute('class'), + ).toEqual('fa fa-spin fa-spinner fa-1x'); + }); + + it('should add a special class and a size class', () => { + vm = mountComponent(FileIcon, { + fileName: 'test.js', + cssClasses: 'extraclasses', + size: 120, + }); + + const classList = vm.$el.firstChild.classList; + const containsSizeClass = classList.contains('s120'); + const containsCustomClass = classList.contains('extraclasses'); + expect(containsSizeClass).toBe(true); + expect(containsCustomClass).toBe(true); + }); +}); diff --git a/spec/javascripts/vue_shared/components/header_ci_component_spec.js b/spec/javascripts/vue_shared/components/header_ci_component_spec.js index b4553acb341..b378a0bd896 100644 --- a/spec/javascripts/vue_shared/components/header_ci_component_spec.js +++ b/spec/javascripts/vue_shared/components/header_ci_component_spec.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import headerCi from '~/vue_shared/components/header_ci_component.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; describe('Header CI Component', () => { let HeaderCi; @@ -8,7 +9,6 @@ describe('Header CI Component', () => { beforeEach(() => { HeaderCi = Vue.extend(headerCi); - props = { status: { group: 'failed', @@ -45,54 +45,65 @@ describe('Header CI Component', () => { ], hasSidebarButton: true, }; - - vm = new HeaderCi({ - propsData: props, - }).$mount(); }); afterEach(() => { vm.$destroy(); }); - it('should render status badge', () => { - expect(vm.$el.querySelector('.ci-failed')).toBeDefined(); - expect(vm.$el.querySelector('.ci-status-icon-failed svg')).toBeDefined(); - expect( - vm.$el.querySelector('.ci-failed').getAttribute('href'), - ).toEqual(props.status.details_path); - }); + describe('render', () => { + beforeEach(() => { + vm = mountComponent(HeaderCi, props); + }); - it('should render item name and id', () => { - expect(vm.$el.querySelector('strong').textContent.trim()).toEqual('job #123'); - }); + it('should render status badge', () => { + expect(vm.$el.querySelector('.ci-failed')).toBeDefined(); + expect(vm.$el.querySelector('.ci-status-icon-failed svg')).toBeDefined(); + expect( + vm.$el.querySelector('.ci-failed').getAttribute('href'), + ).toEqual(props.status.details_path); + }); - it('should render timeago date', () => { - expect(vm.$el.querySelector('time')).toBeDefined(); - }); + it('should render item name and id', () => { + expect(vm.$el.querySelector('strong').textContent.trim()).toEqual('job #123'); + }); - it('should render user icon and name', () => { - expect(vm.$el.querySelector('.js-user-link').textContent.trim()).toEqual(props.user.name); - }); + it('should render timeago date', () => { + expect(vm.$el.querySelector('time')).toBeDefined(); + }); - it('should render provided actions', () => { - expect(vm.$el.querySelector('.btn').tagName).toEqual('BUTTON'); - expect(vm.$el.querySelector('.btn').textContent.trim()).toEqual(props.actions[0].label); - expect(vm.$el.querySelector('.link').tagName).toEqual('A'); - expect(vm.$el.querySelector('.link').textContent.trim()).toEqual(props.actions[1].label); - expect(vm.$el.querySelector('.link').getAttribute('href')).toEqual(props.actions[0].path); - }); + it('should render user icon and name', () => { + expect(vm.$el.querySelector('.js-user-link').textContent.trim()).toEqual(props.user.name); + }); + + it('should render provided actions', () => { + expect(vm.$el.querySelector('.btn').tagName).toEqual('BUTTON'); + expect(vm.$el.querySelector('.btn').textContent.trim()).toEqual(props.actions[0].label); + expect(vm.$el.querySelector('.link').tagName).toEqual('A'); + expect(vm.$el.querySelector('.link').textContent.trim()).toEqual(props.actions[1].label); + expect(vm.$el.querySelector('.link').getAttribute('href')).toEqual(props.actions[0].path); + }); - it('should show loading icon', (done) => { - vm.actions[0].isLoading = true; + it('should show loading icon', (done) => { + vm.actions[0].isLoading = true; - Vue.nextTick(() => { - expect(vm.$el.querySelector('.btn .fa-spinner').getAttribute('style')).toBeFalsy(); - done(); + Vue.nextTick(() => { + expect(vm.$el.querySelector('.btn .fa-spinner').getAttribute('style')).toBeFalsy(); + done(); + }); + }); + + it('should render sidebar toggle button', () => { + expect(vm.$el.querySelector('.js-sidebar-build-toggle')).toBeDefined(); }); }); - it('should render sidebar toggle button', () => { - expect(vm.$el.querySelector('.js-sidebar-build-toggle')).toBeDefined(); + describe('shouldRenderTriggeredLabel', () => { + it('should rendered created keyword when the shouldRenderTriggeredLabel is false', () => { + vm = mountComponent(HeaderCi, { ...props, shouldRenderTriggeredLabel: false }); + + expect(vm.$el.textContent).toContain('created'); + expect(vm.$el.textContent).not.toContain('triggered'); + }); }); }); diff --git a/spec/javascripts/vue_shared/components/modal_spec.js b/spec/javascripts/vue_shared/components/modal_spec.js index 721f4044659..fe75a86cac8 100644 --- a/spec/javascripts/vue_shared/components/modal_spec.js +++ b/spec/javascripts/vue_shared/components/modal_spec.js @@ -2,11 +2,65 @@ import Vue from 'vue'; import modal from '~/vue_shared/components/modal.vue'; import mountComponent from '../../helpers/vue_mount_component_helper'; +const modalComponent = Vue.extend(modal); + describe('Modal', () => { - it('does not render a primary button if no primaryButtonLabel', () => { - const modalComponent = Vue.extend(modal); - const vm = mountComponent(modalComponent); + let vm; + + afterEach(() => { + vm.$destroy(); + }); + + describe('props', () => { + describe('without primaryButtonLabel', () => { + beforeEach(() => { + vm = mountComponent(modalComponent, { + primaryButtonLabel: null, + }); + }); + + it('does not render a primary button', () => { + expect(vm.$el.querySelector('.js-primary-button')).toBeNull(); + }); + }); + + describe('with id', () => { + it('does not render a primary button', () => { + beforeEach(() => { + vm = mountComponent(modalComponent, { + id: 'my-modal', + }); + }); + + it('assigns the id to the modal', () => { + expect(vm.$el.querySelector('#my-modal.modal')).not.toBeNull(); + }); + + it('does not show the modal immediately', () => { + expect(vm.$el.querySelector('#my-modal.modal')).not.toHaveClass('show'); + }); + + it('does not show a backdrop', () => { + expect(vm.$el.querySelector('modal-backdrop')).toBeNull(); + }); + }); + }); + + it('works with data-toggle="modal"', (done) => { + setFixtures(` + <button id="modal-button" data-toggle="modal" data-target="#my-modal"></button> + <div id="modal-container"></div> + `); + + const modalContainer = document.getElementById('modal-container'); + const modalButton = document.getElementById('modal-button'); + vm = mountComponent(modalComponent, { + id: 'my-modal', + }, modalContainer); + const modalElement = vm.$el.querySelector('#my-modal'); + $(modalElement).on('shown.bs.modal', () => done()); - expect(vm.$el.querySelector('.js-primary-button')).toBeNull(); + modalButton.click(); + }); }); }); diff --git a/spec/javascripts/vue_shared/components/panel_resizer_spec.js b/spec/javascripts/vue_shared/components/panel_resizer_spec.js new file mode 100644 index 00000000000..70ce3dffaba --- /dev/null +++ b/spec/javascripts/vue_shared/components/panel_resizer_spec.js @@ -0,0 +1,59 @@ +import Vue from 'vue'; +import panelResizer from '~/vue_shared/components/panel_resizer.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Panel Resizer component', () => { + let vm; + let PanelResizer; + + const triggerEvent = (eventName, el = vm.$el, clientX = 0) => { + const event = document.createEvent('MouseEvents'); + event.initMouseEvent(eventName, true, true, window, 1, clientX, 0, clientX, 0, false, false, + false, false, 0, null); + + el.dispatchEvent(event); + }; + + beforeEach(() => { + PanelResizer = Vue.extend(panelResizer); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should render a div element with the correct classes and styles', () => { + vm = mountComponent(PanelResizer, { + startSize: 100, + side: 'left', + }); + + expect(vm.$el.tagName).toEqual('DIV'); + expect(vm.$el.getAttribute('class')).toBe('dragHandle dragleft'); + expect(vm.$el.getAttribute('style')).toBe('cursor: ew-resize;'); + }); + + it('should render a div element with the correct classes for a right side panel', () => { + vm = mountComponent(PanelResizer, { + startSize: 100, + side: 'right', + }); + + expect(vm.$el.tagName).toEqual('DIV'); + expect(vm.$el.getAttribute('class')).toBe('dragHandle dragright'); + }); + + it('drag the resizer', () => { + vm = mountComponent(PanelResizer, { + startSize: 100, + side: 'left', + }); + + spyOn(vm, '$emit'); + triggerEvent('mousedown', vm.$el); + triggerEvent('mousemove', document); + triggerEvent('mouseup', document); + expect(vm.$emit.calls.allArgs()).toEqual([['resize-start', 100], ['update:size', 100], ['resize-end', 100]]); + expect(vm.size).toBe(100); + }); +}); |