diff options
author | Kamil Trzciński <ayufan@ayufan.eu> | 2017-12-06 09:39:13 +0000 |
---|---|---|
committer | Kamil Trzciński <ayufan@ayufan.eu> | 2017-12-06 09:39:13 +0000 |
commit | 3b9102b200d25bf1164b15675db65db567d26b7b (patch) | |
tree | 445aaef220b399a3d88204fec130cbd270334990 /spec/javascripts | |
parent | cc0c61155561e5baa1b28319ceb6633858a7d9f8 (diff) | |
parent | 1bc1c2d0b2cc5c4c1de5ebdf4229edf6d13d6636 (diff) | |
download | gitlab-ce-list-multiple-clusters.tar.gz |
Merge branch 'multiple-clusters-single-list' into 'list-multiple-clusters'list-multiple-clusters
Use single list for multiple clusters
See merge request gitlab-org/gitlab-ce!15669
Diffstat (limited to 'spec/javascripts')
81 files changed, 2236 insertions, 581 deletions
diff --git a/spec/javascripts/behaviors/autosize_spec.js b/spec/javascripts/behaviors/autosize_spec.js index 67afba19190..960b731892a 100644 --- a/spec/javascripts/behaviors/autosize_spec.js +++ b/spec/javascripts/behaviors/autosize_spec.js @@ -1,21 +1,18 @@ -/* eslint-disable space-before-function-paren, no-var, comma-dangle, no-return-assign, max-len */ - import '~/behaviors/autosize'; -(function() { - describe('Autosize behavior', function() { - var load; - beforeEach(function() { - return setFixtures('<textarea class="js-autosize" style="resize: vertical"></textarea>'); - }); - it('does not overwrite the resize property', function() { - load(); - return expect($('textarea')).toHaveCss({ - resize: 'vertical' - }); +function load() { + $(document).trigger('load'); +} + +describe('Autosize behavior', () => { + beforeEach(() => { + setFixtures('<textarea class="js-autosize" style="resize: vertical"></textarea>'); + }); + + it('does not overwrite the resize property', () => { + load(); + expect($('textarea')).toHaveCss({ + resize: 'vertical', }); - return load = function() { - return $(document).trigger('load'); - }; }); -}).call(window); +}); diff --git a/spec/javascripts/behaviors/requires_input_spec.js b/spec/javascripts/behaviors/requires_input_spec.js index f9fa814b801..8287c58ac5a 100644 --- a/spec/javascripts/behaviors/requires_input_spec.js +++ b/spec/javascripts/behaviors/requires_input_spec.js @@ -1,39 +1,43 @@ -/* eslint-disable space-before-function-paren, no-var */ - import '~/behaviors/requires_input'; -(function() { - describe('requiresInput', function() { - preloadFixtures('branches/new_branch.html.raw'); - beforeEach(function() { - loadFixtures('branches/new_branch.html.raw'); - this.submitButton = $('button[type="submit"]'); - }); - it('disables submit when any field is required', function() { - $('.js-requires-input').requiresInput(); - return expect(this.submitButton).toBeDisabled(); - }); - it('enables submit when no field is required', function() { - $('*[required=required]').removeAttr('required'); - $('.js-requires-input').requiresInput(); - return expect(this.submitButton).not.toBeDisabled(); - }); - it('enables submit when all required fields are pre-filled', function() { - $('*[required=required]').remove(); - $('.js-requires-input').requiresInput(); - return expect($('.submit')).not.toBeDisabled(); - }); - it('enables submit when all required fields receive input', function() { - $('.js-requires-input').requiresInput(); - $('#required1').val('input1').change(); - expect(this.submitButton).toBeDisabled(); - $('#optional1').val('input1').change(); - expect(this.submitButton).toBeDisabled(); - $('#required2').val('input2').change(); - $('#required3').val('input3').change(); - $('#required4').val('input4').change(); - $('#required5').val('1').change(); - return expect($('.submit')).not.toBeDisabled(); - }); +describe('requiresInput', () => { + let submitButton; + preloadFixtures('branches/new_branch.html.raw'); + + beforeEach(() => { + loadFixtures('branches/new_branch.html.raw'); + submitButton = $('button[type="submit"]'); + }); + + it('disables submit when any field is required', () => { + $('.js-requires-input').requiresInput(); + expect(submitButton).toBeDisabled(); + }); + + it('enables submit when no field is required', () => { + $('*[required=required]').removeAttr('required'); + $('.js-requires-input').requiresInput(); + expect(submitButton).not.toBeDisabled(); + }); + + it('enables submit when all required fields are pre-filled', () => { + $('*[required=required]').remove(); + $('.js-requires-input').requiresInput(); + expect($('.submit')).not.toBeDisabled(); + }); + + it('enables submit when all required fields receive input', () => { + $('.js-requires-input').requiresInput(); + $('#required1').val('input1').change(); + expect(submitButton).toBeDisabled(); + + $('#optional1').val('input1').change(); + expect(submitButton).toBeDisabled(); + + $('#required2').val('input2').change(); + $('#required3').val('input3').change(); + $('#required4').val('input4').change(); + $('#required5').val('1').change(); + expect($('.submit')).not.toBeDisabled(); }); -}).call(window); +}); diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js index 83b13b06dc1..8f607899b20 100644 --- a/spec/javascripts/boards/board_card_spec.js +++ b/spec/javascripts/boards/board_card_spec.js @@ -9,10 +9,11 @@ import Vue from 'vue'; import '~/boards/models/assignee'; +import eventHub from '~/boards/eventhub'; import '~/boards/models/list'; import '~/boards/models/label'; import '~/boards/stores/boards_store'; -import boardCard from '~/boards/components/board_card'; +import boardCard from '~/boards/components/board_card.vue'; import './mock_data'; describe('Board card', () => { @@ -157,33 +158,35 @@ describe('Board card', () => { }); it('sets detail issue to card issue on mouse up', () => { + spyOn(eventHub, '$emit'); + triggerEvent('mousedown'); triggerEvent('mouseup'); - expect(gl.issueBoards.BoardsStore.detail.issue).toEqual(vm.issue); + expect(eventHub.$emit).toHaveBeenCalledWith('newDetailIssue', vm.issue); expect(gl.issueBoards.BoardsStore.detail.list).toEqual(vm.list); }); it('adds active class if detail issue is set', (done) => { - triggerEvent('mousedown'); - triggerEvent('mouseup'); - - setTimeout(() => { - expect(vm.$el.classList.contains('is-active')).toBe(true); - done(); - }, 0); + vm.detailIssue.issue = vm.issue; + + Vue.nextTick() + .then(() => { + expect(vm.$el.classList.contains('is-active')).toBe(true); + }) + .then(done) + .catch(done.fail); }); it('resets detail issue to empty if already set', () => { - triggerEvent('mousedown'); - triggerEvent('mouseup'); + spyOn(eventHub, '$emit'); - expect(gl.issueBoards.BoardsStore.detail.issue).toEqual(vm.issue); + gl.issueBoards.BoardsStore.detail.issue = vm.issue; triggerEvent('mousedown'); triggerEvent('mouseup'); - expect(gl.issueBoards.BoardsStore.detail.issue).toEqual({}); + expect(eventHub.$emit).toHaveBeenCalledWith('clearDetailIssue'); }); }); }); diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js index 022d286d5df..10b88878c2a 100644 --- a/spec/javascripts/boards/issue_spec.js +++ b/spec/javascripts/boards/issue_spec.js @@ -133,6 +133,25 @@ describe('Issue model', () => { expect(relativePositionIssue.position).toBe(1); }); + it('updates data', () => { + issue.updateData({ subscribed: true }); + expect(issue.subscribed).toBe(true); + }); + + it('sets fetching state', () => { + expect(issue.isFetching.subscriptions).toBe(true); + + issue.setFetchingState('subscriptions', false); + + expect(issue.isFetching.subscriptions).toBe(false); + }); + + it('sets loading state', () => { + issue.setLoadingState('foo', true); + + expect(issue.isLoading.foo).toBe(true); + }); + describe('update', () => { it('passes assignee ids when there are assignees', (done) => { spyOn(Vue.http, 'patch').and.callFake((url, data) => { diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js index 027e8001053..f5be9ea0fb2 100644 --- a/spec/javascripts/clusters/clusters_bundle_spec.js +++ b/spec/javascripts/clusters/clusters_bundle_spec.js @@ -28,7 +28,7 @@ describe('Clusters', () => { expect( cluster.toggleButton.classList, - ).not.toContain('checked'); + ).not.toContain('is-checked'); expect( cluster.toggleInput.getAttribute('value'), @@ -36,6 +36,20 @@ describe('Clusters', () => { }); }); + describe('showToken', () => { + it('should update tye field type', () => { + cluster.showTokenButton.click(); + expect( + cluster.tokenField.getAttribute('type'), + ).toEqual('text'); + + cluster.showTokenButton.click(); + expect( + cluster.tokenField.getAttribute('type'), + ).toEqual('password'); + }); + }); + describe('checkForNewInstalls', () => { const INITIAL_APP_MAP = { helm: { status: null, title: 'Helm Tiller' }, @@ -113,7 +127,7 @@ describe('Clusters', () => { }); describe('when cluster is created', () => { - it('should show the success container', () => { + it('should show the success container and fresh the page', () => { cluster.updateContainer(null, 'created'); expect( diff --git a/spec/javascripts/clusters/clusters_index_spec.js b/spec/javascripts/clusters/clusters_index_spec.js new file mode 100644 index 00000000000..0a8b63ed5b4 --- /dev/null +++ b/spec/javascripts/clusters/clusters_index_spec.js @@ -0,0 +1,58 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import setClusterTableToggles from '~/clusters/clusters_index'; +import { setTimeout } from 'core-js/library/web/timers'; + +describe('Clusters table', () => { + preloadFixtures('clusters/index_cluster.html.raw'); + let mock; + + beforeEach(() => { + loadFixtures('clusters/index_cluster.html.raw'); + mock = new MockAdapter(axios); + setClusterTableToggles(); + }); + + describe('update cluster', () => { + it('renders loading state while request is made', () => { + const button = document.querySelector('.js-toggle-cluster-list'); + + button.click(); + + expect(button.classList).toContain('is-loading'); + expect(button.getAttribute('disabled')).toEqual('true'); + }); + + afterEach(() => { + mock.restore(); + }); + + it('shows updated state after sucessfull request', (done) => { + mock.onPut().reply(200, {}, {}); + const button = document.querySelector('.js-toggle-cluster-list'); + button.click(); + + expect(button.classList).toContain('is-loading'); + + setTimeout(() => { + expect(button.classList).not.toContain('is-loading'); + expect(button.classList).not.toContain('is-checked'); + done(); + }, 0); + }); + + it('shows inital state after failed request', (done) => { + mock.onPut().reply(500, {}, {}); + const button = document.querySelector('.js-toggle-cluster-list'); + + button.click(); + expect(button.classList).toContain('is-loading'); + + setTimeout(() => { + expect(button.classList).not.toContain('is-loading'); + expect(button.classList).toContain('is-checked'); + done(); + }, 0); + }); + }); +}); diff --git a/spec/javascripts/datetime_utility_spec.js b/spec/javascripts/datetime_utility_spec.js index 3391cade541..0f7bf9ec712 100644 --- a/spec/javascripts/datetime_utility_spec.js +++ b/spec/javascripts/datetime_utility_spec.js @@ -1,4 +1,4 @@ -import { timeIntervalInWords } from '~/lib/utils/datetime_utility'; +import * as datetimeUtility from '~/lib/utils/datetime_utility'; (() => { describe('Date time utils', () => { @@ -89,10 +89,22 @@ import { timeIntervalInWords } from '~/lib/utils/datetime_utility'; describe('timeIntervalInWords', () => { it('should return string with number of minutes and seconds', () => { - expect(timeIntervalInWords(9.54)).toEqual('9 seconds'); - expect(timeIntervalInWords(1)).toEqual('1 second'); - expect(timeIntervalInWords(200)).toEqual('3 minutes 20 seconds'); - expect(timeIntervalInWords(6008)).toEqual('100 minutes 8 seconds'); + expect(datetimeUtility.timeIntervalInWords(9.54)).toEqual('9 seconds'); + expect(datetimeUtility.timeIntervalInWords(1)).toEqual('1 second'); + expect(datetimeUtility.timeIntervalInWords(200)).toEqual('3 minutes 20 seconds'); + expect(datetimeUtility.timeIntervalInWords(6008)).toEqual('100 minutes 8 seconds'); + }); + }); + + describe('dateInWords', () => { + const date = new Date('07/01/2016'); + + it('should return date in words', () => { + expect(datetimeUtility.dateInWords(date)).toEqual('July 1, 2016'); + }); + + it('should return abbreviated month name', () => { + expect(datetimeUtility.dateInWords(date, true)).toEqual('Jul 1, 2016'); }); }); })(); diff --git a/spec/javascripts/droplab/drop_down_spec.js b/spec/javascripts/droplab/drop_down_spec.js index 1ef494a00b8..1225fe2cb66 100644 --- a/spec/javascripts/droplab/drop_down_spec.js +++ b/spec/javascripts/droplab/drop_down_spec.js @@ -279,7 +279,12 @@ describe('DropDown', function () { describe('addEvents', function () { beforeEach(function () { this.list = { addEventListener: () => {} }; - this.dropdown = { list: this.list, clickEvent: () => {}, eventWrapper: {} }; + this.dropdown = { + list: this.list, + clickEvent: () => {}, + closeDropdown: () => {}, + eventWrapper: {}, + }; spyOn(this.list, 'addEventListener'); @@ -288,6 +293,7 @@ describe('DropDown', function () { it('should call .addEventListener', function () { expect(this.list.addEventListener).toHaveBeenCalledWith('click', jasmine.any(Function)); + expect(this.list.addEventListener).toHaveBeenCalledWith('keyup', jasmine.any(Function)); }); }); diff --git a/spec/javascripts/droplab/hook_spec.js b/spec/javascripts/droplab/hook_spec.js index 75bf5f3d611..3d39bd0812b 100644 --- a/spec/javascripts/droplab/hook_spec.js +++ b/spec/javascripts/droplab/hook_spec.js @@ -24,7 +24,7 @@ describe('Hook', function () { }); it('should call DropDown constructor', function () { - expect(dropdownSrc.default).toHaveBeenCalledWith(this.list); + expect(dropdownSrc.default).toHaveBeenCalledWith(this.list, this.config); }); it('should set .type', function () { diff --git a/spec/javascripts/environments/emtpy_state_spec.js b/spec/javascripts/environments/emtpy_state_spec.js new file mode 100644 index 00000000000..82de35933f5 --- /dev/null +++ b/spec/javascripts/environments/emtpy_state_spec.js @@ -0,0 +1,57 @@ + +import Vue from 'vue'; +import emptyState from '~/environments/components/empty_state.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('environments empty state', () => { + let vm; + let Component; + + beforeEach(() => { + Component = Vue.extend(emptyState); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('With permissions', () => { + beforeEach(() => { + vm = mountComponent(Component, { + newPath: 'foo', + canCreateEnvironment: true, + helpPath: 'bar', + }); + }); + + it('renders empty state and new environment button', () => { + expect( + vm.$el.querySelector('.js-blank-state-title').textContent.trim(), + ).toEqual('You don\'t have any environments right now.'); + + expect( + vm.$el.querySelector('.js-new-environment-button').getAttribute('href'), + ).toEqual('foo'); + }); + }); + + describe('Without permission', () => { + beforeEach(() => { + vm = mountComponent(Component, { + newPath: 'foo', + canCreateEnvironment: false, + helpPath: 'bar', + }); + }); + + it('renders empty state without new button', () => { + expect( + vm.$el.querySelector('.js-blank-state-title').textContent.trim(), + ).toEqual('You don\'t have any environments right now.'); + + expect( + vm.$el.querySelector('.js-new-environment-button'), + ).toBeNull(); + }); + }); +}); diff --git a/spec/javascripts/environments/environment_table_spec.js b/spec/javascripts/environments/environment_table_spec.js index 2862971bec4..9bd42863759 100644 --- a/spec/javascripts/environments/environment_table_spec.js +++ b/spec/javascripts/environments/environment_table_spec.js @@ -1,10 +1,17 @@ import Vue from 'vue'; import environmentTableComp from '~/environments/components/environments_table.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Environment table', () => { + let Component; + let vm; -describe('Environment item', () => { - preloadFixtures('static/environments/element.html.raw'); beforeEach(() => { - loadFixtures('static/environments/element.html.raw'); + Component = Vue.extend(environmentTableComp); + }); + + afterEach(() => { + vm.$destroy(); }); it('Should render a table', () => { @@ -17,18 +24,12 @@ describe('Environment item', () => { }, }; - const EnvironmentTable = Vue.extend(environmentTableComp); - - const component = new EnvironmentTable({ - el: document.querySelector('.test-dom-element'), - propsData: { - environments: [{ mockItem }], - canCreateDeployment: false, - canReadEnvironment: true, - service: {}, - }, - }).$mount(); + vm = mountComponent(Component, { + environments: [mockItem], + canCreateDeployment: false, + canReadEnvironment: true, + }); - expect(component.$el.getAttribute('class')).toContain('ci-table'); + expect(vm.$el.getAttribute('class')).toContain('ci-table'); }); }); diff --git a/spec/javascripts/environments/environment_spec.js b/spec/javascripts/environments/environments_app_spec.js index 0c8817a8148..d02adb25b4e 100644 --- a/spec/javascripts/environments/environment_spec.js +++ b/spec/javascripts/environments/environments_app_spec.js @@ -1,18 +1,24 @@ import Vue from 'vue'; -import '~/flash'; -import environmentsComponent from '~/environments/components/environment.vue'; +import environmentsComponent from '~/environments/components/environments_app.vue'; import { environment, folder } from './mock_data'; import { headersInterceptor } from '../helpers/vue_resource_helper'; +import mountComponent from '../helpers/vue_mount_component_helper'; describe('Environment', () => { - preloadFixtures('static/environments/environments.html.raw'); + const mockData = { + endpoint: 'environments.json', + canCreateEnvironment: true, + canCreateDeployment: true, + canReadEnvironment: true, + cssContainerClass: 'container', + newEnvironmentPath: 'environments/new', + helpPagePath: 'help', + }; let EnvironmentsComponent; let component; beforeEach(() => { - loadFixtures('static/environments/environments.html.raw'); - EnvironmentsComponent = Vue.extend(environmentsComponent); }); @@ -37,9 +43,7 @@ describe('Environment', () => { }); it('should render the empty state', (done) => { - component = new EnvironmentsComponent({ - el: document.querySelector('#environments-list-view'), - }); + component = mountComponent(EnvironmentsComponent, mockData); setTimeout(() => { expect( @@ -81,9 +85,7 @@ describe('Environment', () => { beforeEach(() => { Vue.http.interceptors.push(environmentsResponseInterceptor); Vue.http.interceptors.push(headersInterceptor); - component = new EnvironmentsComponent({ - el: document.querySelector('#environments-list-view'), - }); + component = mountComponent(EnvironmentsComponent, mockData); }); afterEach(() => { @@ -95,7 +97,7 @@ describe('Environment', () => { it('should render a table with environments', (done) => { setTimeout(() => { - expect(component.$el.querySelectorAll('table')).toBeDefined(); + expect(component.$el.querySelectorAll('table')).not.toBeNull(); expect( component.$el.querySelector('.environment-name').textContent.trim(), ).toEqual(environment.name); @@ -104,10 +106,6 @@ describe('Environment', () => { }); describe('pagination', () => { - afterEach(() => { - window.history.pushState({}, null, ''); - }); - it('should render pagination', (done) => { setTimeout(() => { expect( @@ -117,46 +115,23 @@ describe('Environment', () => { }, 0); }); - it('should update url when no search params are present', (done) => { - spyOn(gl.utils, 'visitUrl'); + it('should make an API request when page is clicked', (done) => { + spyOn(component, 'updateContent'); setTimeout(() => { component.$el.querySelector('.gl-pagination li:nth-child(5) a').click(); - expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2'); + expect(component.updateContent).toHaveBeenCalledWith({ scope: 'available', page: '2' }); done(); }, 0); }); - it('should update url when page is already present', (done) => { - spyOn(gl.utils, 'visitUrl'); - window.history.pushState({}, null, '?page=1'); - - setTimeout(() => { - component.$el.querySelector('.gl-pagination li:nth-child(5) a').click(); - expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2'); - done(); - }, 0); - }); - - it('should update url when page and scope are already present', (done) => { - spyOn(gl.utils, 'visitUrl'); - window.history.pushState({}, null, '?scope=all&page=1'); - + it('should make an API request when using tabs', (done) => { setTimeout(() => { - component.$el.querySelector('.gl-pagination li:nth-child(5) a').click(); - expect(gl.utils.visitUrl).toHaveBeenCalledWith('?scope=all&page=2'); - done(); - }, 0); - }); + spyOn(component, 'updateContent'); + component.$el.querySelector('.js-environments-tab-stopped').click(); - it('should update url when page and scope are already present and page is first param', (done) => { - spyOn(gl.utils, 'visitUrl'); - window.history.pushState({}, null, '?page=1&scope=all'); - - setTimeout(() => { - component.$el.querySelector('.gl-pagination li:nth-child(5) a').click(); - expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2&scope=all'); + expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' }); done(); - }, 0); + }); }); }); }); @@ -180,9 +155,7 @@ describe('Environment', () => { }); it('should render empty state', (done) => { - component = new EnvironmentsComponent({ - el: document.querySelector('#environments-list-view'), - }); + component = mountComponent(EnvironmentsComponent, mockData); setTimeout(() => { expect( @@ -214,9 +187,7 @@ describe('Environment', () => { beforeEach(() => { Vue.http.interceptors.push(environmentsResponseInterceptor); - component = new EnvironmentsComponent({ - el: document.querySelector('#environments-list-view'), - }); + component = mountComponent(EnvironmentsComponent, mockData); }); afterEach(() => { @@ -289,4 +260,59 @@ describe('Environment', () => { }); }); }); + + describe('methods', () => { + const environmentsEmptyResponseInterceptor = (request, next) => { + next(request.respondWith(JSON.stringify([]), { + status: 200, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(environmentsEmptyResponseInterceptor); + Vue.http.interceptors.push(headersInterceptor); + + component = mountComponent(EnvironmentsComponent, mockData); + spyOn(history, 'pushState').and.stub(); + }); + + afterEach(() => { + Vue.http.interceptors = _.without( + Vue.http.interceptors, environmentsEmptyResponseInterceptor, + ); + Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor); + }); + + describe('updateContent', () => { + it('should set given parameters', (done) => { + component.updateContent({ scope: 'stopped', page: '3' }) + .then(() => { + expect(component.page).toEqual('3'); + expect(component.scope).toEqual('stopped'); + expect(component.requestData.scope).toEqual('stopped'); + expect(component.requestData.page).toEqual('3'); + done(); + }) + .catch(done.fail); + }); + }); + + describe('onChangeTab', () => { + it('should set page to 1', () => { + spyOn(component, 'updateContent'); + component.onChangeTab('stopped'); + + expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' }); + }); + }); + + describe('onChangePage', () => { + it('should update page and keep scope', () => { + spyOn(component, 'updateContent'); + component.onChangePage(4); + + expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, page: '4' }); + }); + }); + }); }); diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js index 7e62d356bd2..4ea4d9d7499 100644 --- a/spec/javascripts/environments/folder/environments_folder_view_spec.js +++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js @@ -1,25 +1,28 @@ import Vue from 'vue'; -import '~/flash'; import environmentsFolderViewComponent from '~/environments/folder/environments_folder_view.vue'; import { environmentsList } from '../mock_data'; import { headersInterceptor } from '../../helpers/vue_resource_helper'; +import mountComponent from '../../helpers/vue_mount_component_helper'; describe('Environments Folder View', () => { - preloadFixtures('static/environments/environments_folder_view.html.raw'); - let EnvironmentsFolderViewComponent; + let Component; + let component; + const mockData = { + endpoint: 'environments.json', + folderName: 'review', + canCreateDeployment: true, + canReadEnvironment: true, + cssContainerClass: 'container', + }; beforeEach(() => { - loadFixtures('static/environments/environments_folder_view.html.raw'); - EnvironmentsFolderViewComponent = Vue.extend(environmentsFolderViewComponent); - window.history.pushState({}, null, 'environments/folders/build'); + Component = Vue.extend(environmentsFolderViewComponent); }); afterEach(() => { - window.history.pushState({}, null, '/'); + component.$destroy(); }); - let component; - describe('successfull request', () => { const environmentsResponseInterceptor = (request, next) => { next(request.respondWith(JSON.stringify({ @@ -31,10 +34,10 @@ describe('Environments Folder View', () => { headers: { 'X-nExt-pAge': '2', 'x-page': '1', - 'X-Per-Page': '1', + 'X-Per-Page': '2', 'X-Prev-Page': '', - 'X-TOTAL': '37', - 'X-Total-Pages': '2', + 'X-TOTAL': '20', + 'X-Total-Pages': '10', }, })); }; @@ -43,9 +46,7 @@ describe('Environments Folder View', () => { Vue.http.interceptors.push(environmentsResponseInterceptor); Vue.http.interceptors.push(headersInterceptor); - component = new EnvironmentsFolderViewComponent({ - el: document.querySelector('#environments-folder-list-view'), - }); + component = mountComponent(Component, mockData); }); afterEach(() => { @@ -57,7 +58,7 @@ describe('Environments Folder View', () => { it('should render a table with environments', (done) => { setTimeout(() => { - expect(component.$el.querySelectorAll('table')).toBeDefined(); + expect(component.$el.querySelectorAll('table')).not.toBeNull(); expect( component.$el.querySelector('.environment-name').textContent.trim(), ).toEqual(environmentsList[0].name); @@ -68,11 +69,11 @@ describe('Environments Folder View', () => { it('should render available tab with count', (done) => { setTimeout(() => { expect( - component.$el.querySelector('.js-available-environments-folder-tab').textContent, + component.$el.querySelector('.js-environments-tab-available').textContent, ).toContain('Available'); expect( - component.$el.querySelector('.js-available-environments-folder-tab .js-available-environments-count').textContent, + component.$el.querySelector('.js-environments-tab-available .badge').textContent, ).toContain('0'); done(); }, 0); @@ -81,11 +82,11 @@ describe('Environments Folder View', () => { it('should render stopped tab with count', (done) => { setTimeout(() => { expect( - component.$el.querySelector('.js-stopped-environments-folder-tab').textContent, + component.$el.querySelector('.js-environments-tab-stopped').textContent, ).toContain('Stopped'); expect( - component.$el.querySelector('.js-stopped-environments-folder-tab .js-stopped-environments-count').textContent, + component.$el.querySelector('.js-environments-tab-stopped .badge').textContent, ).toContain('1'); done(); }, 0); @@ -94,8 +95,8 @@ describe('Environments Folder View', () => { it('should render parent folder name', (done) => { setTimeout(() => { expect( - component.$el.querySelector('.js-folder-name').textContent, - ).toContain('Environments / build'); + component.$el.querySelector('.js-folder-name').textContent.trim(), + ).toContain('Environments / review'); done(); }, 0); }); @@ -104,52 +105,30 @@ describe('Environments Folder View', () => { it('should render pagination', (done) => { setTimeout(() => { expect( - component.$el.querySelectorAll('.gl-pagination li').length, - ).toEqual(5); + component.$el.querySelectorAll('.gl-pagination'), + ).not.toBeNull(); done(); }, 0); }); - it('should update url when no search params are present', (done) => { - spyOn(gl.utils, 'visitUrl'); + it('should make an API request when changing page', (done) => { + spyOn(component, 'updateContent'); setTimeout(() => { - component.$el.querySelector('.gl-pagination li:nth-child(5) a').click(); - expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2'); - done(); - }, 0); - }); - - it('should update url when page is already present', (done) => { - spyOn(gl.utils, 'visitUrl'); - window.history.pushState({}, null, '?page=1'); + component.$el.querySelector('.gl-pagination .js-last-button a').click(); - setTimeout(() => { - component.$el.querySelector('.gl-pagination li:nth-child(5) a').click(); - expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2'); + expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, page: '10' }); done(); }, 0); }); - it('should update url when page and scope are already present', (done) => { - spyOn(gl.utils, 'visitUrl'); - window.history.pushState({}, null, '?scope=all&page=1'); - + it('should make an API request when using tabs', (done) => { setTimeout(() => { - component.$el.querySelector('.gl-pagination li:nth-child(5) a').click(); - expect(gl.utils.visitUrl).toHaveBeenCalledWith('?scope=all&page=2'); - done(); - }, 0); - }); - - it('should update url when page and scope are already present and page is first param', (done) => { - spyOn(gl.utils, 'visitUrl'); - window.history.pushState({}, null, '?page=1&scope=all'); + spyOn(component, 'updateContent'); + component.$el.querySelector('.js-environments-tab-stopped').click(); - setTimeout(() => { - component.$el.querySelector('.gl-pagination li:nth-child(5) a').click(); - expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2&scope=all'); + expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' }); done(); - }, 0); + }); }); }); }); @@ -172,9 +151,7 @@ describe('Environments Folder View', () => { }); it('should not render a table', (done) => { - component = new EnvironmentsFolderViewComponent({ - el: document.querySelector('#environments-folder-list-view'), - }); + component = mountComponent(Component, mockData); setTimeout(() => { expect( @@ -187,11 +164,11 @@ describe('Environments Folder View', () => { it('should render available tab with count 0', (done) => { setTimeout(() => { expect( - component.$el.querySelector('.js-available-environments-folder-tab').textContent, + component.$el.querySelector('.js-environments-tab-available').textContent, ).toContain('Available'); expect( - component.$el.querySelector('.js-available-environments-folder-tab .js-available-environments-count').textContent, + component.$el.querySelector('.js-environments-tab-available .badge').textContent, ).toContain('0'); done(); }, 0); @@ -200,14 +177,70 @@ describe('Environments Folder View', () => { it('should render stopped tab with count 0', (done) => { setTimeout(() => { expect( - component.$el.querySelector('.js-stopped-environments-folder-tab').textContent, + component.$el.querySelector('.js-environments-tab-stopped').textContent, ).toContain('Stopped'); expect( - component.$el.querySelector('.js-stopped-environments-folder-tab .js-stopped-environments-count').textContent, + component.$el.querySelector('.js-environments-tab-stopped .badge').textContent, ).toContain('0'); done(); }, 0); }); }); + + describe('methods', () => { + const environmentsEmptyResponseInterceptor = (request, next) => { + next(request.respondWith(JSON.stringify([]), { + status: 200, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(environmentsEmptyResponseInterceptor); + Vue.http.interceptors.push(headersInterceptor); + + component = mountComponent(Component, mockData); + spyOn(history, 'pushState').and.stub(); + }); + + afterEach(() => { + Vue.http.interceptors = _.without( + Vue.http.interceptors, environmentsEmptyResponseInterceptor, + ); + Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor); + }); + + describe('updateContent', () => { + it('should set given parameters', (done) => { + component.updateContent({ scope: 'stopped', page: '4' }) + .then(() => { + expect(component.page).toEqual('4'); + expect(component.scope).toEqual('stopped'); + expect(component.requestData.scope).toEqual('stopped'); + expect(component.requestData.page).toEqual('4'); + done(); + }) + .catch(done.fail); + }); + }); + + describe('onChangeTab', () => { + it('should set page to 1', () => { + spyOn(component, 'updateContent'); + component.onChangeTab('stopped'); + + expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' }); + }); + }); + + describe('onChangePage', () => { + it('should update page and keep scope', () => { + spyOn(component, 'updateContent'); + + component.onChangePage(4); + + expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, page: '4' }); + }); + }); + }); }); diff --git a/spec/javascripts/fixtures/clusters.rb b/spec/javascripts/fixtures/clusters.rb index 8e74c4f859c..d26ea3febe8 100644 --- a/spec/javascripts/fixtures/clusters.rb +++ b/spec/javascripts/fixtures/clusters.rb @@ -31,4 +31,19 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle expect(response).to be_success store_frontend_fixture(response, example.description) end + + context 'rendering non-empty state' do + before do + cluster + end + + it 'clusters/index_cluster.html.raw' do |example| + get :index, + namespace_id: namespace, + project_id: project + + expect(response).to be_success + store_frontend_fixture(response, example.description) + end + end end diff --git a/spec/javascripts/fixtures/environments/element.html.haml b/spec/javascripts/fixtures/environments/element.html.haml deleted file mode 100644 index 8d7aeb23356..00000000000 --- a/spec/javascripts/fixtures/environments/element.html.haml +++ /dev/null @@ -1 +0,0 @@ -.test-dom-element diff --git a/spec/javascripts/fixtures/environments/environments.html.haml b/spec/javascripts/fixtures/environments/environments.html.haml deleted file mode 100644 index e6000fbb553..00000000000 --- a/spec/javascripts/fixtures/environments/environments.html.haml +++ /dev/null @@ -1,9 +0,0 @@ -%div - #environments-list-view{ data: { environments_data: "foo/environments", - "can-create-deployment" => "true", - "can-read-environment" => "true", - "can-create-environment" => "true", - "project-environments-path" => "https://gitlab.com/foo/environments", - "project-stopped-environments-path" => "https://gitlab.com/foo/environments?scope=stopped", - "new-environment-path" => "https://gitlab.com/foo/environments/new", - "help-page-path" => "https://gitlab.com/help_page"}} diff --git a/spec/javascripts/fixtures/environments/environments_folder_view.html.haml b/spec/javascripts/fixtures/environments/environments_folder_view.html.haml deleted file mode 100644 index aceec139730..00000000000 --- a/spec/javascripts/fixtures/environments/environments_folder_view.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -%div - #environments-folder-list-view{ data: { "can-create-deployment" => "true", - "can-read-environment" => "true", - "css-class" => "", - "commit-icon-svg" => custom_icon("icon_commit"), - "terminal-icon-svg" => custom_icon("icon_terminal"), - "play-icon-svg" => custom_icon("icon_play") } } diff --git a/spec/javascripts/flash_spec.js b/spec/javascripts/flash_spec.js index b669aabcee4..97e3ab682c5 100644 --- a/spec/javascripts/flash_spec.js +++ b/spec/javascripts/flash_spec.js @@ -278,7 +278,7 @@ describe('Flash', () => { removeFlashClickListener(flashEl, false); - flashEl.parentNode.click(); + flashEl.click(); setTimeout(() => { expect(document.querySelector('.flash')).toBeNull(); diff --git a/spec/javascripts/issuable_spec.js b/spec/javascripts/issuable_spec.js index ceee08d47c5..5a9112716f4 100644 --- a/spec/javascripts/issuable_spec.js +++ b/spec/javascripts/issuable_spec.js @@ -26,7 +26,7 @@ describe('Issuable', () => { document.body.appendChild(element); const input = document.createElement('input'); - input.setAttribute('id', 'issue_email'); + input.setAttribute('id', 'issuable_email'); document.body.appendChild(input); Issuable = new IssuableIndex('issue_'); diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js index 5662c7387fb..b47a8bf705f 100644 --- a/spec/javascripts/issue_show/components/app_spec.js +++ b/spec/javascripts/issue_show/components/app_spec.js @@ -35,11 +35,12 @@ describe('Issuable output', () => { canUpdate: true, canDestroy: true, endpoint: '/gitlab-org/gitlab-shell/issues/9/realtime_changes', + updateEndpoint: gl.TEST_HOST, issuableRef: '#1', initialTitleHtml: '', initialTitleText: '', - initialDescriptionHtml: '', - initialDescriptionText: '', + initialDescriptionHtml: 'test', + initialDescriptionText: 'test', markdownPreviewPath: '/', markdownDocsPath: '/', projectNamespace: '/', diff --git a/spec/javascripts/issue_show/components/description_spec.js b/spec/javascripts/issue_show/components/description_spec.js index 360691a3546..163e5cdd062 100644 --- a/spec/javascripts/issue_show/components/description_spec.js +++ b/spec/javascripts/issue_show/components/description_spec.js @@ -1,11 +1,22 @@ import Vue from 'vue'; import descriptionComponent from '~/issue_show/components/description.vue'; +import * as taskList from '~/task_list'; +import mountComponent from '../../helpers/vue_mount_component_helper'; describe('Description component', () => { let vm; + let DescriptionComponent; + const props = { + canUpdate: true, + descriptionHtml: 'test', + descriptionText: 'test', + updatedAt: new Date().toString(), + taskStatus: '', + updateUrl: gl.TEST_HOST, + }; beforeEach(() => { - const Component = Vue.extend(descriptionComponent); + DescriptionComponent = Vue.extend(descriptionComponent); if (!document.querySelector('.issuable-meta')) { const metaData = document.createElement('div'); @@ -15,15 +26,11 @@ describe('Description component', () => { document.body.appendChild(metaData); } - vm = new Component({ - propsData: { - canUpdate: true, - descriptionHtml: 'test', - descriptionText: 'test', - updatedAt: new Date().toString(), - taskStatus: '', - }, - }).$mount(); + vm = mountComponent(DescriptionComponent, props); + }); + + afterEach(() => { + vm.$destroy(); }); it('animates description changes', (done) => { @@ -44,34 +51,46 @@ describe('Description component', () => { }); }); - // TODO: gl.TaskList no longer exists. rewrite these tests once we have a way to rewire ES modules - - // it('re-inits the TaskList when description changed', (done) => { - // spyOn(gl, 'TaskList'); - // vm.descriptionHtml = 'changed'; - // - // setTimeout(() => { - // expect( - // gl.TaskList, - // ).toHaveBeenCalled(); - // - // done(); - // }); - // }); - - // it('does not re-init the TaskList when canUpdate is false', (done) => { - // spyOn(gl, 'TaskList'); - // vm.canUpdate = false; - // vm.descriptionHtml = 'changed'; - // - // setTimeout(() => { - // expect( - // gl.TaskList, - // ).not.toHaveBeenCalled(); - // - // done(); - // }); - // }); + describe('TaskList', () => { + beforeEach(() => { + vm = mountComponent(DescriptionComponent, Object.assign({}, props, { + issuableType: 'issuableType', + })); + spyOn(taskList, 'default'); + }); + + it('re-inits the TaskList when description changed', (done) => { + vm.descriptionHtml = 'changed'; + + setTimeout(() => { + expect(taskList.default).toHaveBeenCalled(); + done(); + }); + }); + + it('does not re-init the TaskList when canUpdate is false', (done) => { + vm.canUpdate = false; + vm.descriptionHtml = 'changed'; + + setTimeout(() => { + expect(taskList.default).not.toHaveBeenCalled(); + done(); + }); + }); + + it('calls with issuableType dataType', (done) => { + vm.descriptionHtml = 'changed'; + + setTimeout(() => { + expect(taskList.default).toHaveBeenCalledWith({ + dataType: 'issuableType', + fieldName: 'description', + selector: '.detail-page-description', + }); + done(); + }); + }); + }); describe('taskStatus', () => { it('adds full taskStatus', (done) => { @@ -126,4 +145,8 @@ describe('Description component', () => { }); }); }); + + it('sets data-update-url', () => { + expect(vm.$el.querySelector('textarea').dataset.updateUrl).toEqual(gl.TEST_HOST); + }); }); diff --git a/spec/javascripts/issue_show/components/title_spec.js b/spec/javascripts/issue_show/components/title_spec.js index c1edc785d0f..5370f4e1fea 100644 --- a/spec/javascripts/issue_show/components/title_spec.js +++ b/spec/javascripts/issue_show/components/title_spec.js @@ -80,19 +80,19 @@ describe('Title component', () => { }); it('should not show by default', () => { - expect(vm.$el.querySelector('.note-action-button')).toBeNull(); + expect(vm.$el.querySelector('.btn-edit')).toBeNull(); }); it('should not show if canUpdate is false', () => { vm.showInlineEditButton = true; vm.canUpdate = false; - expect(vm.$el.querySelector('.note-action-button')).toBeNull(); + expect(vm.$el.querySelector('.btn-edit')).toBeNull(); }); it('should show if showInlineEditButton and canUpdate', () => { vm.showInlineEditButton = true; vm.canUpdate = true; - expect(vm.$el.querySelector('.note-action-button')).toBeDefined(); + expect(vm.$el.querySelector('.btn-edit')).toBeDefined(); }); it('should trigger open.form event when clicked', () => { @@ -100,7 +100,7 @@ describe('Title component', () => { vm.canUpdate = true; Vue.nextTick(() => { - vm.$el.querySelector('.note-action-button').click(); + vm.$el.querySelector('.btn-edit').click(); expect(eventHub.$emit).toHaveBeenCalledWith('open.form'); }); }); diff --git a/spec/javascripts/jobs/job_details_mediator_spec.js b/spec/javascripts/jobs/job_details_mediator_spec.js index 1d7fa7e12fc..3069a0cd60e 100644 --- a/spec/javascripts/jobs/job_details_mediator_spec.js +++ b/spec/javascripts/jobs/job_details_mediator_spec.js @@ -1,39 +1,35 @@ -import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import JobMediator from '~/jobs/job_details_mediator'; import job from './mock_data'; describe('JobMediator', () => { let mediator; + let mock; beforeEach(() => { - mediator = new JobMediator({ endpoint: 'foo' }); + mediator = new JobMediator({ endpoint: 'jobs/40291672.json' }); + mock = new MockAdapter(axios); }); it('should set defaults', () => { expect(mediator.store).toBeDefined(); expect(mediator.service).toBeDefined(); - expect(mediator.options).toEqual({ endpoint: 'foo' }); + expect(mediator.options).toEqual({ endpoint: 'jobs/40291672.json' }); expect(mediator.state.isLoading).toEqual(false); }); describe('request and store data', () => { - const interceptor = (request, next) => { - next(request.respondWith(JSON.stringify(job), { - status: 200, - })); - }; - beforeEach(() => { - Vue.http.interceptors.push(interceptor); + mock.onGet().reply(200, job, {}); }); afterEach(() => { - Vue.http.interceptors = _.without(Vue.http.interceptor, interceptor); + mock.restore(); }); it('should store received data', (done) => { mediator.fetchJob(); - setTimeout(() => { expect(mediator.store.state.job).toEqual(job); done(); diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index 6dad5d6b6bd..0a9d815f469 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -142,47 +142,6 @@ describe('common_utils', () => { }); }); - describe('setParamInURL', () => { - afterEach(() => { - window.history.pushState({}, null, ''); - }); - - it('should return the parameter', () => { - window.history.replaceState({}, null, ''); - - expect(commonUtils.setParamInURL('page', 156)).toBe('?page=156'); - expect(commonUtils.setParamInURL('page', '156')).toBe('?page=156'); - }); - - it('should update the existing parameter when its a number', () => { - window.history.pushState({}, null, '?page=15'); - - expect(commonUtils.setParamInURL('page', 16)).toBe('?page=16'); - expect(commonUtils.setParamInURL('page', '16')).toBe('?page=16'); - expect(commonUtils.setParamInURL('page', true)).toBe('?page=true'); - }); - - it('should update the existing parameter when its a string', () => { - window.history.pushState({}, null, '?scope=all'); - - expect(commonUtils.setParamInURL('scope', 'finished')).toBe('?scope=finished'); - }); - - it('should update the existing parameter when more than one parameter exists', () => { - window.history.pushState({}, null, '?scope=all&page=15'); - - expect(commonUtils.setParamInURL('scope', 'finished')).toBe('?scope=finished&page=15'); - }); - - it('should add a new parameter to the end of the existing ones', () => { - window.history.pushState({}, null, '?scope=all'); - - expect(commonUtils.setParamInURL('page', 16)).toBe('?scope=all&page=16'); - expect(commonUtils.setParamInURL('page', '16')).toBe('?scope=all&page=16'); - expect(commonUtils.setParamInURL('page', true)).toBe('?scope=all&page=true'); - }); - }); - describe('historyPushState', () => { afterEach(() => { window.history.replaceState({}, null, null); diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js index b21bd958f90..1f46c225071 100644 --- a/spec/javascripts/lib/utils/text_utility_spec.js +++ b/spec/javascripts/lib/utils/text_utility_spec.js @@ -23,6 +23,14 @@ describe('text_utility', () => { }); }); + describe('capitalizeFirstCharacter', () => { + it('returns string with first letter capitalized', () => { + expect(textUtils.capitalizeFirstCharacter('gitlab')).toEqual('Gitlab'); + expect(textUtils.highCountTrim(105)).toBe('99+'); + expect(textUtils.highCountTrim(100)).toBe('99+'); + }); + }); + describe('humanize', () => { it('should remove underscores and uppercase the first letter', () => { expect(textUtils.humanize('foo_bar')).toEqual('Foo bar'); diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index 752fdfb4614..9885b8a790f 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -1,6 +1,8 @@ import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; import Dashboard from '~/monitoring/components/dashboard.vue'; -import { MonitorMockInterceptor } from './mock_data'; +import axios from '~/lib/utils/axios_utils'; +import { metricsGroupsAPIResponse, mockApiEndpoint } from './mock_data'; describe('Dashboard', () => { const fixtureName = 'environments/metrics/metrics.html.raw'; @@ -26,13 +28,17 @@ describe('Dashboard', () => { }); describe('requests information to the server', () => { + let mock; beforeEach(() => { document.querySelector('#prometheus-graphs').setAttribute('data-has-metrics', 'true'); - Vue.http.interceptors.push(MonitorMockInterceptor); + mock = new MockAdapter(axios); + mock.onGet(mockApiEndpoint).reply(200, { + metricsGroupsAPIResponse, + }); }); afterEach(() => { - Vue.http.interceptors = _.without(Vue.http.interceptors, MonitorMockInterceptor); + mock.reset(); }); it('shows up a loading state', (done) => { diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js index 7ceab657464..6b34855b8b2 100644 --- a/spec/javascripts/monitoring/mock_data.js +++ b/spec/javascripts/monitoring/mock_data.js @@ -2425,13 +2425,6 @@ const metricsGroupsAPIResponse = { export default metricsGroupsAPIResponse; -const responseMockData = { - 'GET': { - '/root/hello-prometheus/environments/30/additional_metrics.json': metricsGroupsAPIResponse, - 'http://test.host/frontend-fixtures/environments-project/environments/1/additional_metrics.json': metricsGroupsAPIResponse, // TODO: MAke sure this works in the monitoring_bundle_spec - }, -}; - export const deploymentData = [ { id: 111, @@ -8320,11 +8313,3 @@ export function convertDatesMultipleSeries(multipleSeries) { }); return convertedMultiple; } - -export function MonitorMockInterceptor(request, next) { - const body = responseMockData[request.method.toUpperCase()][request.url]; - - next(request.respondWith(JSON.stringify(body), { - status: 200, - })); -} diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js index c57f44dae17..50a5e4ff056 100644 --- a/spec/javascripts/new_branch_spec.js +++ b/spec/javascripts/new_branch_spec.js @@ -1,7 +1,6 @@ /* eslint-disable space-before-function-paren, one-var, no-var, one-var-declaration-per-line, no-return-assign, quotes, max-len */ -/* global NewBranchForm */ -import '~/new_branch_form'; +import NewBranchForm from '~/new_branch_form'; (function() { describe('Branch', function() { diff --git a/spec/javascripts/notes/components/issue_comment_form_spec.js b/spec/javascripts/notes/components/issue_comment_form_spec.js index db75262b562..04a7f8e32f1 100644 --- a/spec/javascripts/notes/components/issue_comment_form_spec.js +++ b/spec/javascripts/notes/components/issue_comment_form_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import Autosize from 'autosize'; import store from '~/notes/stores'; import issueCommentForm from '~/notes/components/issue_comment_form.vue'; -import { loggedOutIssueData, notesDataMock, userDataMock, issueDataMock } from '../mock_data'; +import { loggedOutnoteableData, notesDataMock, userDataMock, noteableDataMock } from '../mock_data'; import { keyboardDownEvent } from '../../issue_show/helpers'; describe('issue_comment_form component', () => { @@ -23,7 +23,7 @@ describe('issue_comment_form component', () => { describe('user is logged in', () => { beforeEach(() => { store.dispatch('setUserData', userDataMock); - store.dispatch('setIssueData', issueDataMock); + store.dispatch('setNoteableData', noteableDataMock); store.dispatch('setNotesData', notesDataMock); vm = mountComponent(); @@ -178,7 +178,7 @@ describe('issue_comment_form component', () => { describe('issue is confidential', () => { it('shows information warning', (done) => { - store.dispatch('setIssueData', Object.assign(issueDataMock, { confidential: true })); + store.dispatch('setNoteableData', Object.assign(noteableDataMock, { confidential: true })); Vue.nextTick(() => { expect(vm.$el.querySelector('.confidential-issue-warning')).toBeDefined(); done(); @@ -190,7 +190,7 @@ describe('issue_comment_form component', () => { describe('user is not logged in', () => { beforeEach(() => { store.dispatch('setUserData', null); - store.dispatch('setIssueData', loggedOutIssueData); + store.dispatch('setNoteableData', loggedOutnoteableData); store.dispatch('setNotesData', notesDataMock); vm = mountComponent(); diff --git a/spec/javascripts/notes/components/issue_discussion_spec.js b/spec/javascripts/notes/components/issue_discussion_spec.js index 05c6b57f93e..b6ae55d44f5 100644 --- a/spec/javascripts/notes/components/issue_discussion_spec.js +++ b/spec/javascripts/notes/components/issue_discussion_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import store from '~/notes/stores'; import issueDiscussion from '~/notes/components/issue_discussion.vue'; -import { issueDataMock, discussionMock, notesDataMock } from '../mock_data'; +import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data'; describe('issue_discussion component', () => { let vm; @@ -9,7 +9,7 @@ describe('issue_discussion component', () => { beforeEach(() => { const Component = Vue.extend(issueDiscussion); - store.dispatch('setIssueData', issueDataMock); + store.dispatch('setNoteableData', noteableDataMock); store.dispatch('setNotesData', notesDataMock); vm = new Component({ diff --git a/spec/javascripts/notes/components/issue_note_app_spec.js b/spec/javascripts/notes/components/issue_note_app_spec.js index 22e91c4c40f..8e43037f356 100644 --- a/spec/javascripts/notes/components/issue_note_app_spec.js +++ b/spec/javascripts/notes/components/issue_note_app_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import issueNotesApp from '~/notes/components/issue_notes_app.vue'; -import service from '~/notes/services/issue_notes_service'; +import service from '~/notes/services/notes_service'; import * as mockData from '../mock_data'; describe('issue_note_app', () => { @@ -24,7 +24,7 @@ describe('issue_note_app', () => { mountComponent = (data) => { const props = data || { - issueData: mockData.issueDataMock, + noteableData: mockData.noteableDataMock, notesData: mockData.notesDataMock, userData: mockData.userDataMock, }; @@ -60,7 +60,7 @@ describe('issue_note_app', () => { }); it('should set issue data', () => { - expect(vm.$store.state.issueData).toEqual(mockData.issueDataMock); + expect(vm.$store.state.noteableData).toEqual(mockData.noteableDataMock); }); it('should set user data', () => { diff --git a/spec/javascripts/notes/components/issue_note_body_spec.js b/spec/javascripts/notes/components/issue_note_body_spec.js index 81f07ed47cc..37aad50737b 100644 --- a/spec/javascripts/notes/components/issue_note_body_spec.js +++ b/spec/javascripts/notes/components/issue_note_body_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import store from '~/notes/stores'; import noteBody from '~/notes/components/issue_note_body.vue'; -import { issueDataMock, notesDataMock, note } from '../mock_data'; +import { noteableDataMock, notesDataMock, note } from '../mock_data'; describe('issue_note_body component', () => { let vm; @@ -10,7 +10,7 @@ describe('issue_note_body component', () => { beforeEach(() => { const Component = Vue.extend(noteBody); - store.dispatch('setIssueData', issueDataMock); + store.dispatch('setNoteableData', noteableDataMock); store.dispatch('setNotesData', notesDataMock); vm = new Component({ diff --git a/spec/javascripts/notes/components/issue_note_form_spec.js b/spec/javascripts/notes/components/issue_note_form_spec.js index a90dbcb72b5..d42ef239711 100644 --- a/spec/javascripts/notes/components/issue_note_form_spec.js +++ b/spec/javascripts/notes/components/issue_note_form_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import store from '~/notes/stores'; import issueNoteForm from '~/notes/components/issue_note_form.vue'; -import { issueDataMock, notesDataMock } from '../mock_data'; +import { noteableDataMock, notesDataMock } from '../mock_data'; import { keyboardDownEvent } from '../../issue_show/helpers'; describe('issue_note_form component', () => { @@ -11,7 +11,7 @@ describe('issue_note_form component', () => { beforeEach(() => { const Component = Vue.extend(issueNoteForm); - store.dispatch('setIssueData', issueDataMock); + store.dispatch('setNoteableData', noteableDataMock); store.dispatch('setNotesData', notesDataMock); props = { diff --git a/spec/javascripts/notes/components/issue_note_spec.js b/spec/javascripts/notes/components/issue_note_spec.js index 7ef85d5b4f0..73fd188dbe5 100644 --- a/spec/javascripts/notes/components/issue_note_spec.js +++ b/spec/javascripts/notes/components/issue_note_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import store from '~/notes/stores'; import issueNote from '~/notes/components/issue_note.vue'; -import { issueDataMock, notesDataMock, note } from '../mock_data'; +import { noteableDataMock, notesDataMock, note } from '../mock_data'; describe('issue_note', () => { let vm; @@ -10,7 +10,7 @@ describe('issue_note', () => { beforeEach(() => { const Component = Vue.extend(issueNote); - store.dispatch('setIssueData', issueDataMock); + store.dispatch('setNoteableData', noteableDataMock); store.dispatch('setNotesData', notesDataMock); vm = new Component({ diff --git a/spec/javascripts/notes/components/issue_note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js index 7bcc061f167..ab81aabb992 100644 --- a/spec/javascripts/notes/components/issue_note_actions_spec.js +++ b/spec/javascripts/notes/components/note_actions_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import store from '~/notes/stores'; -import issueActions from '~/notes/components/issue_note_actions.vue'; +import noteActions from '~/notes/components/note_actions.vue'; import { userDataMock } from '../mock_data'; describe('issse_note_actions component', () => { @@ -8,7 +8,7 @@ describe('issse_note_actions component', () => { let Component; beforeEach(() => { - Component = Vue.extend(issueActions); + Component = Vue.extend(noteActions); }); afterEach(() => { diff --git a/spec/javascripts/notes/components/issue_note_attachment_spec.js b/spec/javascripts/notes/components/note_attachment_spec.js index 8f33b874ad6..b14a518b622 100644 --- a/spec/javascripts/notes/components/issue_note_attachment_spec.js +++ b/spec/javascripts/notes/components/note_attachment_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import issueNoteAttachment from '~/notes/components/issue_note_attachment.vue'; +import noteAttachment from '~/notes/components/note_attachment.vue'; describe('issue note attachment', () => { it('should render properly', () => { @@ -11,7 +11,7 @@ describe('issue note attachment', () => { }, }; - const Component = Vue.extend(issueNoteAttachment); + const Component = Vue.extend(noteAttachment); const vm = new Component({ propsData: props, }).$mount(); diff --git a/spec/javascripts/notes/components/issue_note_awards_list_spec.js b/spec/javascripts/notes/components/note_awards_list_spec.js index 3b6c34f1494..15995ec5a05 100644 --- a/spec/javascripts/notes/components/issue_note_awards_list_spec.js +++ b/spec/javascripts/notes/components/note_awards_list_spec.js @@ -1,16 +1,16 @@ import Vue from 'vue'; import store from '~/notes/stores'; -import awardsNote from '~/notes/components/issue_note_awards_list.vue'; -import { issueDataMock, notesDataMock } from '../mock_data'; +import awardsNote from '~/notes/components/note_awards_list.vue'; +import { noteableDataMock, notesDataMock } from '../mock_data'; -describe('issue_note_awards_list component', () => { +describe('note_awards_list component', () => { let vm; let awardsMock; beforeEach(() => { const Component = Vue.extend(awardsNote); - store.dispatch('setIssueData', issueDataMock); + store.dispatch('setNoteableData', noteableDataMock); store.dispatch('setNotesData', notesDataMock); awardsMock = [ { diff --git a/spec/javascripts/notes/components/issue_note_edited_text_spec.js b/spec/javascripts/notes/components/note_edited_text_spec.js index 6603241eb64..e0b991c32ec 100644 --- a/spec/javascripts/notes/components/issue_note_edited_text_spec.js +++ b/spec/javascripts/notes/components/note_edited_text_spec.js @@ -1,12 +1,12 @@ import Vue from 'vue'; -import issueNoteEditedText from '~/notes/components/issue_note_edited_text.vue'; +import noteEditedText from '~/notes/components/note_edited_text.vue'; -describe('issue_note_edited_text', () => { +describe('note_edited_text', () => { let vm; let props; beforeEach(() => { - const Component = Vue.extend(issueNoteEditedText); + const Component = Vue.extend(noteEditedText); props = { actionText: 'Edited', className: 'foo-bar', diff --git a/spec/javascripts/notes/components/issue_note_header_spec.js b/spec/javascripts/notes/components/note_header_spec.js index 83ea18508ae..16a76b11321 100644 --- a/spec/javascripts/notes/components/issue_note_header_spec.js +++ b/spec/javascripts/notes/components/note_header_spec.js @@ -1,13 +1,13 @@ import Vue from 'vue'; -import issueNoteHeader from '~/notes/components/issue_note_header.vue'; +import noteHeader from '~/notes/components/note_header.vue'; import store from '~/notes/stores'; -describe('issue_note_header component', () => { +describe('note_header component', () => { let vm; let Component; beforeEach(() => { - Component = Vue.extend(issueNoteHeader); + Component = Vue.extend(noteHeader); }); afterEach(() => { diff --git a/spec/javascripts/notes/components/issue_note_signed_out_widget_spec.js b/spec/javascripts/notes/components/note_signed_out_widget_spec.js index f20d9ce9268..6cba8053888 100644 --- a/spec/javascripts/notes/components/issue_note_signed_out_widget_spec.js +++ b/spec/javascripts/notes/components/note_signed_out_widget_spec.js @@ -1,13 +1,13 @@ import Vue from 'vue'; -import issueNoteSignedOut from '~/notes/components/issue_note_signed_out_widget.vue'; +import noteSignedOut from '~/notes/components/note_signed_out_widget.vue'; import store from '~/notes/stores'; import { notesDataMock } from '../mock_data'; -describe('issue_note_signed_out_widget component', () => { +describe('note_signed_out_widget component', () => { let vm; beforeEach(() => { - const Component = Vue.extend(issueNoteSignedOut); + const Component = Vue.extend(noteSignedOut); store.dispatch('setNotesData', notesDataMock); vm = new Component({ diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js index 89ba3a002b7..42497de3c55 100644 --- a/spec/javascripts/notes/mock_data.js +++ b/spec/javascripts/notes/mock_data.js @@ -18,7 +18,7 @@ export const userDataMock = { username: 'root', }; -export const issueDataMock = { +export const noteableDataMock = { assignees: [], author_id: 1, branch_name: null, @@ -271,7 +271,7 @@ export const discussionMock = { individual_note: false, }; -export const loggedOutIssueData = { +export const loggedOutnoteableData = { "id": 98, "iid": 26, "author_id": 1, diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js index 3d1ca870ca4..e092320f9a3 100644 --- a/spec/javascripts/notes/stores/actions_spec.js +++ b/spec/javascripts/notes/stores/actions_spec.js @@ -1,6 +1,6 @@ import * as actions from '~/notes/stores/actions'; import testAction from '../../helpers/vuex_action_helper'; -import { discussionMock, notesDataMock, userDataMock, issueDataMock, individualNote } from '../mock_data'; +import { discussionMock, notesDataMock, userDataMock, noteableDataMock, individualNote } from '../mock_data'; describe('Actions Notes Store', () => { describe('setNotesData', () => { @@ -11,10 +11,10 @@ describe('Actions Notes Store', () => { }); }); - describe('setIssueData', () => { + describe('setNoteableData', () => { it('should set received issue data', (done) => { - testAction(actions.setIssueData, null, { issueData: {} }, [ - { type: 'SET_ISSUE_DATA', payload: issueDataMock }, + testAction(actions.setNoteableData, null, { noteableData: {} }, [ + { type: 'SET_NOTEABLE_DATA', payload: noteableDataMock }, ], done); }); }); diff --git a/spec/javascripts/notes/stores/getters_spec.js b/spec/javascripts/notes/stores/getters_spec.js index 48ee1bf9a52..c5a84b71788 100644 --- a/spec/javascripts/notes/stores/getters_spec.js +++ b/spec/javascripts/notes/stores/getters_spec.js @@ -1,5 +1,5 @@ import * as getters from '~/notes/stores/getters'; -import { notesDataMock, userDataMock, issueDataMock, individualNote } from '../mock_data'; +import { notesDataMock, userDataMock, noteableDataMock, individualNote } from '../mock_data'; describe('Getters Notes Store', () => { let state; @@ -11,7 +11,7 @@ describe('Getters Notes Store', () => { notesData: notesDataMock, userData: userDataMock, - issueData: issueDataMock, + noteableData: noteableDataMock, }; }); describe('notes', () => { @@ -32,9 +32,9 @@ describe('Getters Notes Store', () => { }); }); - describe('getIssueData', () => { - it('should return all data in `issueData`', () => { - expect(getters.getIssueData(state)).toEqual(issueDataMock); + describe('getNoteableData', () => { + it('should return all data in `noteableData`', () => { + expect(getters.getNoteableData(state)).toEqual(noteableDataMock); }); }); diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js index 1e22e03e178..22d99998a7d 100644 --- a/spec/javascripts/notes/stores/mutation_spec.js +++ b/spec/javascripts/notes/stores/mutation_spec.js @@ -1,5 +1,5 @@ import mutations from '~/notes/stores/mutations'; -import { note, discussionMock, notesDataMock, userDataMock, issueDataMock, individualNote } from '../mock_data'; +import { note, discussionMock, notesDataMock, userDataMock, noteableDataMock, individualNote } from '../mock_data'; describe('Mutation Notes Store', () => { describe('ADD_NEW_NOTE', () => { @@ -74,14 +74,14 @@ describe('Mutation Notes Store', () => { }); }); - describe('SET_ISSUE_DATA', () => { + describe('SET_NOTEABLE_DATA', () => { it('should set the issue data', () => { const state = { - issueData: {}, + noteableData: {}, }; - mutations.SET_ISSUE_DATA(state, issueDataMock); - expect(state.issueData).toEqual(issueDataMock); + mutations.SET_NOTEABLE_DATA(state, noteableDataMock); + expect(state.noteableData).toEqual(noteableDataMock); }); }); diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index 928a4b461cc..677a389b88f 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -5,7 +5,6 @@ import 'autosize'; import '~/gl_form'; import '~/lib/utils/text_utility'; import '~/render_gfm'; -import '~/render_math'; import '~/notes'; (function() { diff --git a/spec/javascripts/pipelines/graph/job_component_spec.js b/spec/javascripts/pipelines/graph/job_component_spec.js index 342ee6c1242..23c87610d83 100644 --- a/spec/javascripts/pipelines/graph/job_component_spec.js +++ b/spec/javascripts/pipelines/graph/job_component_spec.js @@ -1,8 +1,10 @@ import Vue from 'vue'; import jobComponent from '~/pipelines/components/graph/job_component.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; describe('pipeline graph job component', () => { let JobComponent; + let component; const mockJob = { id: 4256, @@ -13,6 +15,7 @@ describe('pipeline graph job component', () => { label: 'passed', group: 'success', details_path: '/root/ci-mock/builds/4256', + has_details: true, action: { icon: 'retry', title: 'Retry', @@ -26,13 +29,13 @@ describe('pipeline graph job component', () => { JobComponent = Vue.extend(jobComponent); }); + afterEach(() => { + component.$destroy(); + }); + describe('name with link', () => { it('should render the job name and status with a link', (done) => { - const component = new JobComponent({ - propsData: { - job: mockJob, - }, - }).$mount(); + component = mountComponent(JobComponent, { job: mockJob }); Vue.nextTick(() => { const link = component.$el.querySelector('a'); @@ -56,23 +59,23 @@ describe('pipeline graph job component', () => { describe('name without link', () => { it('it should render status and name', () => { - const component = new JobComponent({ - propsData: { - job: { - id: 4256, - name: 'test', - status: { - icon: 'icon_status_success', - text: 'passed', - label: 'passed', - group: 'success', - details_path: '/root/ci-mock/builds/4256', - }, + component = mountComponent(JobComponent, { + job: { + id: 4256, + name: 'test', + status: { + icon: 'icon_status_success', + text: 'passed', + label: 'passed', + group: 'success', + details_path: '/root/ci-mock/builds/4256', + has_details: false, }, }, - }).$mount(); + }); expect(component.$el.querySelector('.js-status-icon-success')).toBeDefined(); + expect(component.$el.querySelector('a')).toBeNull(); expect( component.$el.querySelector('.ci-status-text').textContent.trim(), @@ -82,11 +85,7 @@ describe('pipeline graph job component', () => { describe('action icon', () => { it('it should render the action icon', () => { - const component = new JobComponent({ - propsData: { - job: mockJob, - }, - }).$mount(); + component = mountComponent(JobComponent, { job: mockJob }); expect(component.$el.querySelector('a.ci-action-icon-container')).toBeDefined(); expect(component.$el.querySelector('i.ci-action-icon-wrapper')).toBeDefined(); @@ -95,24 +94,20 @@ describe('pipeline graph job component', () => { describe('dropdown', () => { it('should render the dropdown action icon', () => { - const component = new JobComponent({ - propsData: { - job: mockJob, - isDropdown: true, - }, - }).$mount(); + component = mountComponent(JobComponent, { + job: mockJob, + isDropdown: true, + }); expect(component.$el.querySelector('a.ci-action-icon-wrapper')).toBeDefined(); }); }); it('should render provided class name', () => { - const component = new JobComponent({ - propsData: { - job: mockJob, - cssClassJobName: 'css-class-job-name', - }, - }).$mount(); + component = mountComponent(JobComponent, { + job: mockJob, + cssClassJobName: 'css-class-job-name', + }); expect( component.$el.querySelector('a').classList.contains('css-class-job-name'), diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js index ff38bc1974d..367b42cefb0 100644 --- a/spec/javascripts/pipelines/pipelines_spec.js +++ b/spec/javascripts/pipelines/pipelines_spec.js @@ -176,45 +176,49 @@ describe('Pipelines', () => { }); }); - describe('updateContent', () => { - it('should set given parameters', () => { - component = mountComponent(PipelinesComponent, { - store: new Store(), - }); - component.updateContent({ scope: 'finished', page: '4' }); - - expect(component.page).toEqual('4'); - expect(component.scope).toEqual('finished'); - expect(component.requestData.scope).toEqual('finished'); - expect(component.requestData.page).toEqual('4'); + describe('methods', () => { + beforeEach(() => { + spyOn(history, 'pushState').and.stub(); }); - }); - describe('onChangeTab', () => { - it('should set page to 1', () => { - component = mountComponent(PipelinesComponent, { - store: new Store(), - }); + describe('updateContent', () => { + it('should set given parameters', () => { + component = mountComponent(PipelinesComponent, { + store: new Store(), + }); + component.updateContent({ scope: 'finished', page: '4' }); - spyOn(component, 'updateContent'); + expect(component.page).toEqual('4'); + expect(component.scope).toEqual('finished'); + expect(component.requestData.scope).toEqual('finished'); + expect(component.requestData.page).toEqual('4'); + }); + }); - component.onChangeTab('running'); + describe('onChangeTab', () => { + it('should set page to 1', () => { + component = mountComponent(PipelinesComponent, { + store: new Store(), + }); + spyOn(component, 'updateContent'); - expect(component.updateContent).toHaveBeenCalledWith({ scope: 'running', page: '1' }); - }); - }); + component.onChangeTab('running'); - describe('onChangePage', () => { - it('should update page and keep scope', () => { - component = mountComponent(PipelinesComponent, { - store: new Store(), + expect(component.updateContent).toHaveBeenCalledWith({ scope: 'running', page: '1' }); }); + }); - spyOn(component, 'updateContent'); + describe('onChangePage', () => { + it('should update page and keep scope', () => { + component = mountComponent(PipelinesComponent, { + store: new Store(), + }); + spyOn(component, 'updateContent'); - component.onChangePage(4); + component.onChangePage(4); - expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, page: '4' }); + expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, page: '4' }); + }); }); }); }); diff --git a/spec/javascripts/projects_dropdown/components/projects_list_item_spec.js b/spec/javascripts/projects_dropdown/components/projects_list_item_spec.js index 171629fcd6b..edef150dd1e 100644 --- a/spec/javascripts/projects_dropdown/components/projects_list_item_spec.js +++ b/spec/javascripts/projects_dropdown/components/projects_list_item_spec.js @@ -50,6 +50,18 @@ describe('ProjectsListItemComponent', () => { expect(vm.highlightedProjectName).toBe(mockProject.name); }); }); + + describe('truncatedNamespace', () => { + it('should truncate project name from namespace string', () => { + vm.namespace = 'platform / nokia-3310'; + expect(vm.truncatedNamespace).toBe('platform'); + }); + + it('should truncate namespace string from the middle if it includes more than two groups in path', () => { + vm.namespace = 'platform / hardware / broadcom / Wifi Group / Mobile Chipset / nokia-3310'; + expect(vm.truncatedNamespace).toBe('platform / ... / Mobile Chipset'); + }); + }); }); describe('template', () => { diff --git a/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js new file mode 100644 index 00000000000..f750061a6a1 --- /dev/null +++ b/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js @@ -0,0 +1,33 @@ +import Vue from 'vue'; +import store from '~/repo/stores'; +import listCollapsed from '~/repo/components/commit_sidebar/list_collapsed.vue'; +import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; +import { file } from '../../helpers'; + +describe('Multi-file editor commit sidebar list collapsed', () => { + let vm; + + beforeEach(() => { + const Component = Vue.extend(listCollapsed); + + vm = createComponentWithStore(Component, store); + + vm.$store.state.openFiles.push(file(), file()); + vm.$store.state.openFiles[0].tempFile = true; + vm.$store.state.openFiles.forEach((f) => { + Object.assign(f, { + changed: true, + }); + }); + + vm.$mount(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders added & modified files count', () => { + expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toBe('1 1'); + }); +}); diff --git a/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js new file mode 100644 index 00000000000..18c9b46fcd9 --- /dev/null +++ b/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js @@ -0,0 +1,53 @@ +import Vue from 'vue'; +import listItem from '~/repo/components/commit_sidebar/list_item.vue'; +import mountComponent from '../../../helpers/vue_mount_component_helper'; +import { file } from '../../helpers'; + +describe('Multi-file editor commit sidebar list item', () => { + let vm; + let f; + + beforeEach(() => { + const Component = Vue.extend(listItem); + + f = file(); + + vm = mountComponent(Component, { + file: f, + }); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders file path', () => { + expect(vm.$el.querySelector('.multi-file-commit-list-path').textContent.trim()).toBe(f.path); + }); + + describe('computed', () => { + describe('iconName', () => { + it('returns modified when not a tempFile', () => { + expect(vm.iconName).toBe('file-modified'); + }); + + it('returns addition when not a tempFile', () => { + f.tempFile = true; + + expect(vm.iconName).toBe('file-addition'); + }); + }); + + describe('iconClass', () => { + it('returns modified when not a tempFile', () => { + expect(vm.iconClass).toContain('multi-file-modified'); + }); + + it('returns addition when not a tempFile', () => { + f.tempFile = true; + + expect(vm.iconClass).toContain('multi-file-addition'); + }); + }); + }); +}); diff --git a/spec/javascripts/repo/components/commit_sidebar/list_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_spec.js new file mode 100644 index 00000000000..df7e3c5de21 --- /dev/null +++ b/spec/javascripts/repo/components/commit_sidebar/list_spec.js @@ -0,0 +1,72 @@ +import Vue from 'vue'; +import store from '~/repo/stores'; +import commitSidebarList from '~/repo/components/commit_sidebar/list.vue'; +import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; +import { file } from '../../helpers'; + +describe('Multi-file editor commit sidebar list', () => { + let vm; + + beforeEach(() => { + const Component = Vue.extend(commitSidebarList); + + vm = createComponentWithStore(Component, store, { + title: 'Staged', + fileList: [], + collapsed: false, + }).$mount(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('empty file list', () => { + it('renders no changes text', () => { + expect(vm.$el.querySelector('.help-block').textContent.trim()).toBe('No changes'); + }); + }); + + describe('with a list of files', () => { + beforeEach((done) => { + const f = file('file name'); + f.changed = true; + vm.fileList.push(f); + + Vue.nextTick(done); + }); + + it('renders list', () => { + expect(vm.$el.querySelectorAll('li').length).toBe(1); + }); + }); + + describe('collapsed', () => { + beforeEach((done) => { + vm.collapsed = true; + + Vue.nextTick(done); + }); + + it('adds collapsed class', () => { + expect(vm.$el.querySelector('.is-collapsed')).not.toBeNull(); + }); + + it('hides list', () => { + expect(vm.$el.querySelector('.list-unstyled')).toBeNull(); + expect(vm.$el.querySelector('.help-block')).toBeNull(); + }); + + it('hides collapse button', () => { + expect(vm.$el.querySelector('.multi-file-commit-panel-collapse-btn')).toBeNull(); + }); + }); + + it('clicking toggle collapse button emits toggle event', () => { + spyOn(vm, '$emit'); + + vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click(); + + expect(vm.$emit).toHaveBeenCalledWith('toggleCollapsed'); + }); +}); diff --git a/spec/javascripts/repo/components/repo_commit_section_spec.js b/spec/javascripts/repo/components/repo_commit_section_spec.js index 0f991e1b727..1c794123095 100644 --- a/spec/javascripts/repo/components/repo_commit_section_spec.js +++ b/spec/javascripts/repo/components/repo_commit_section_spec.js @@ -25,8 +25,12 @@ describe('RepoCommitSection', () => { return comp.$mount(); } - beforeEach(() => { + beforeEach((done) => { vm = createComponent(); + + vm.collapsed = false; + + Vue.nextTick(done); }); afterEach(() => { @@ -36,12 +40,11 @@ describe('RepoCommitSection', () => { }); it('renders a commit section', () => { - const changedFileElements = [...vm.$el.querySelectorAll('.changed-files > li')]; - const submitCommit = vm.$el.querySelector('.btn'); - const targetBranch = vm.$el.querySelector('.target-branch'); + const changedFileElements = [...vm.$el.querySelectorAll('.multi-file-commit-list li')]; + const submitCommit = vm.$el.querySelector('form .btn'); - expect(vm.$el.querySelector(':scope > form')).toBeTruthy(); - expect(vm.$el.querySelector('.staged-files').textContent.trim()).toEqual('Staged files (2)'); + expect(vm.$el.querySelector('.multi-file-commit-form')).not.toBeNull(); + expect(vm.$el.querySelector('.multi-file-commit-panel-section header').textContent.trim()).toEqual('Staged'); expect(changedFileElements.length).toEqual(2); changedFileElements.forEach((changedFile, i) => { @@ -49,10 +52,7 @@ describe('RepoCommitSection', () => { }); expect(submitCommit.disabled).toBeTruthy(); - expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeFalsy(); - expect(vm.$el.querySelector('.commit-summary').textContent.trim()).toEqual('Commit 2 files'); - expect(targetBranch.querySelector(':scope > label').textContent.trim()).toEqual('Target branch'); - expect(targetBranch.querySelector('.help-block').textContent.trim()).toEqual('master'); + expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeNull(); }); describe('when submitting', () => { @@ -69,7 +69,7 @@ describe('RepoCommitSection', () => { }); it('allows you to submit', () => { - expect(vm.$el.querySelector('.btn').disabled).toBeTruthy(); + expect(vm.$el.querySelector('form .btn').disabled).toBeTruthy(); }); it('submits commit', (done) => { diff --git a/spec/javascripts/repo/components/repo_editor_spec.js b/spec/javascripts/repo/components/repo_editor_spec.js index 979d2185076..81158cad639 100644 --- a/spec/javascripts/repo/components/repo_editor_spec.js +++ b/spec/javascripts/repo/components/repo_editor_spec.js @@ -1,12 +1,13 @@ import Vue from 'vue'; import store from '~/repo/stores'; import repoEditor from '~/repo/components/repo_editor.vue'; +import monacoLoader from '~/repo/monaco_loader'; import { file, resetStore } from '../helpers'; describe('RepoEditor', () => { let vm; - beforeEach(() => { + beforeEach((done) => { const f = file(); const RepoEditor = Vue.extend(repoEditor); @@ -21,6 +22,10 @@ describe('RepoEditor', () => { vm.monaco = true; vm.$mount(); + + monacoLoader(['vs/editor/editor.main'], () => { + setTimeout(done, 0); + }); }); afterEach(() => { @@ -32,7 +37,6 @@ describe('RepoEditor', () => { it('renders an ide container', (done) => { Vue.nextTick(() => { expect(vm.shouldHideEditor).toBeFalsy(); - expect(vm.$el.textContent.trim()).toBe(''); done(); }); @@ -50,7 +54,7 @@ describe('RepoEditor', () => { }); it('shows activeFile html', () => { - expect(vm.$el.textContent.trim()).toBe('testing'); + expect(vm.$el.textContent).toContain('testing'); }); }); }); diff --git a/spec/javascripts/repo/components/repo_sidebar_spec.js b/spec/javascripts/repo/components/repo_sidebar_spec.js index 7cb4dace491..df7cf8aabbb 100644 --- a/spec/javascripts/repo/components/repo_sidebar_spec.js +++ b/spec/javascripts/repo/components/repo_sidebar_spec.js @@ -29,7 +29,6 @@ describe('RepoSidebar', () => { const thead = vm.$el.querySelector('thead'); const tbody = vm.$el.querySelector('tbody'); - expect(vm.$el.id).toEqual('sidebar'); expect(vm.$el.classList.contains('sidebar-mini')).toBeFalsy(); expect(thead.querySelector('.name').textContent.trim()).toEqual('Name'); expect(thead.querySelector('.last-commit').textContent.trim()).toEqual('Last commit'); @@ -40,18 +39,6 @@ describe('RepoSidebar', () => { expect(tbody.querySelector('.file')).toBeTruthy(); }); - it('does not render a thead, renders repo-file-options and sets sidebar-mini class if isMini', (done) => { - vm.$store.state.openFiles.push(vm.$store.state.tree[0]); - - Vue.nextTick(() => { - expect(vm.$el.classList.contains('sidebar-mini')).toBeTruthy(); - expect(vm.$el.querySelector('thead')).toBeTruthy(); - expect(vm.$el.querySelector('thead .repo-file-options')).toBeTruthy(); - - done(); - }); - }); - it('renders 5 loading files if tree is loading', (done) => { vm.$store.state.tree = []; vm.$store.state.loading = true; diff --git a/spec/javascripts/repo/components/repo_tab_spec.js b/spec/javascripts/repo/components/repo_tab_spec.js index df0ca55aafc..7d2174196c9 100644 --- a/spec/javascripts/repo/components/repo_tab_spec.js +++ b/spec/javascripts/repo/components/repo_tab_spec.js @@ -24,8 +24,8 @@ describe('RepoTab', () => { tab: file(), }); vm.$store.state.openFiles.push(vm.tab); - const close = vm.$el.querySelector('.close-btn'); - const name = vm.$el.querySelector(`a[title="${vm.tab.url}"]`); + const close = vm.$el.querySelector('.multi-file-tab-close'); + const name = vm.$el.querySelector(`[title="${vm.tab.url}"]`); expect(close.querySelector('.fa-times')).toBeTruthy(); expect(name.textContent.trim()).toEqual(vm.tab.name); @@ -50,7 +50,7 @@ describe('RepoTab', () => { spyOn(vm, 'closeFile'); - vm.$el.querySelector('.close-btn').click(); + vm.$el.querySelector('.multi-file-tab-close').click(); expect(vm.closeFile).toHaveBeenCalledWith({ file: vm.tab }); }); @@ -62,7 +62,7 @@ describe('RepoTab', () => { tab, }); - expect(vm.$el.querySelector('.close-btn .fa-circle')).toBeTruthy(); + expect(vm.$el.querySelector('.multi-file-tab-close .fa-circle')).not.toBeNull(); }); describe('methods', () => { @@ -77,7 +77,7 @@ describe('RepoTab', () => { vm.$store.state.openFiles.push(tab); vm.$store.dispatch('setFileActive', tab); - vm.$el.querySelector('.close-btn').click(); + vm.$el.querySelector('.multi-file-tab-close').click(); vm.$nextTick(() => { expect(tab.opened).toBeTruthy(); @@ -95,7 +95,7 @@ describe('RepoTab', () => { vm.$store.state.openFiles.push(tab); vm.$store.dispatch('setFileActive', tab); - vm.$el.querySelector('.close-btn').click(); + vm.$el.querySelector('.multi-file-tab-close').click(); vm.$nextTick(() => { expect(tab.opened).toBeFalsy(); diff --git a/spec/javascripts/repo/components/repo_tabs_spec.js b/spec/javascripts/repo/components/repo_tabs_spec.js index d0246cc72e6..1fb2242c051 100644 --- a/spec/javascripts/repo/components/repo_tabs_spec.js +++ b/spec/javascripts/repo/components/repo_tabs_spec.js @@ -25,12 +25,11 @@ describe('RepoTabs', () => { vm.$store.state.openFiles = openedFiles; vm.$nextTick(() => { - const tabs = [...vm.$el.querySelectorAll(':scope > li')]; + const tabs = [...vm.$el.querySelectorAll('.multi-file-tab')]; - expect(tabs.length).toEqual(3); + expect(tabs.length).toEqual(2); expect(tabs[0].classList.contains('active')).toBeTruthy(); expect(tabs[1].classList.contains('active')).toBeFalsy(); - expect(tabs[2].classList.contains('tabs-divider')).toBeTruthy(); done(); }); diff --git a/spec/javascripts/repo/lib/common/disposable_spec.js b/spec/javascripts/repo/lib/common/disposable_spec.js new file mode 100644 index 00000000000..62c3913bf4d --- /dev/null +++ b/spec/javascripts/repo/lib/common/disposable_spec.js @@ -0,0 +1,44 @@ +import Disposable from '~/repo/lib/common/disposable'; + +describe('Multi-file editor library disposable class', () => { + let instance; + let disposableClass; + + beforeEach(() => { + instance = new Disposable(); + + disposableClass = { + dispose: jasmine.createSpy('dispose'), + }; + }); + + afterEach(() => { + instance.dispose(); + }); + + describe('add', () => { + it('adds disposable classes', () => { + instance.add(disposableClass); + + expect(instance.disposers.size).toBe(1); + }); + }); + + describe('dispose', () => { + beforeEach(() => { + instance.add(disposableClass); + }); + + it('calls dispose on all cached disposers', () => { + instance.dispose(); + + expect(disposableClass.dispose).toHaveBeenCalled(); + }); + + it('clears cached disposers', () => { + instance.dispose(); + + expect(instance.disposers.size).toBe(0); + }); + }); +}); diff --git a/spec/javascripts/repo/lib/common/model_manager_spec.js b/spec/javascripts/repo/lib/common/model_manager_spec.js new file mode 100644 index 00000000000..8c134f178c0 --- /dev/null +++ b/spec/javascripts/repo/lib/common/model_manager_spec.js @@ -0,0 +1,81 @@ +/* global monaco */ +import monacoLoader from '~/repo/monaco_loader'; +import ModelManager from '~/repo/lib/common/model_manager'; +import { file } from '../../helpers'; + +describe('Multi-file editor library model manager', () => { + let instance; + + beforeEach((done) => { + monacoLoader(['vs/editor/editor.main'], () => { + instance = new ModelManager(monaco); + + done(); + }); + }); + + afterEach(() => { + instance.dispose(); + }); + + describe('addModel', () => { + it('caches model', () => { + instance.addModel(file()); + + expect(instance.models.size).toBe(1); + }); + + it('caches model by file path', () => { + instance.addModel(file('path-name')); + + expect(instance.models.keys().next().value).toBe('path-name'); + }); + + it('adds model into disposable', () => { + spyOn(instance.disposable, 'add').and.callThrough(); + + instance.addModel(file()); + + expect(instance.disposable.add).toHaveBeenCalled(); + }); + + it('returns cached model', () => { + spyOn(instance.models, 'get').and.callThrough(); + + instance.addModel(file()); + instance.addModel(file()); + + expect(instance.models.get).toHaveBeenCalled(); + }); + }); + + describe('hasCachedModel', () => { + it('returns false when no models exist', () => { + expect(instance.hasCachedModel('path')).toBeFalsy(); + }); + + it('returns true when model exists', () => { + instance.addModel(file('path-name')); + + expect(instance.hasCachedModel('path-name')).toBeTruthy(); + }); + }); + + describe('dispose', () => { + it('clears cached models', () => { + instance.addModel(file()); + + instance.dispose(); + + expect(instance.models.size).toBe(0); + }); + + it('calls disposable dispose', () => { + spyOn(instance.disposable, 'dispose').and.callThrough(); + + instance.dispose(); + + expect(instance.disposable.dispose).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/javascripts/repo/lib/common/model_spec.js b/spec/javascripts/repo/lib/common/model_spec.js new file mode 100644 index 00000000000..d41ade237ca --- /dev/null +++ b/spec/javascripts/repo/lib/common/model_spec.js @@ -0,0 +1,84 @@ +/* global monaco */ +import monacoLoader from '~/repo/monaco_loader'; +import Model from '~/repo/lib/common/model'; +import { file } from '../../helpers'; + +describe('Multi-file editor library model', () => { + let model; + + beforeEach((done) => { + monacoLoader(['vs/editor/editor.main'], () => { + model = new Model(monaco, file('path')); + + done(); + }); + }); + + afterEach(() => { + model.dispose(); + }); + + it('creates original model & new model', () => { + expect(model.originalModel).not.toBeNull(); + expect(model.model).not.toBeNull(); + }); + + describe('path', () => { + it('returns file path', () => { + expect(model.path).toBe('path'); + }); + }); + + describe('getModel', () => { + it('returns model', () => { + expect(model.getModel()).toBe(model.model); + }); + }); + + describe('getOriginalModel', () => { + it('returns original model', () => { + expect(model.getOriginalModel()).toBe(model.originalModel); + }); + }); + + describe('onChange', () => { + it('caches event by path', () => { + model.onChange(() => {}); + + expect(model.events.size).toBe(1); + expect(model.events.keys().next().value).toBe('path'); + }); + + it('calls callback on change', (done) => { + const spy = jasmine.createSpy(); + model.onChange(spy); + + model.getModel().setValue('123'); + + setTimeout(() => { + expect(spy).toHaveBeenCalledWith(model.getModel(), jasmine.anything()); + done(); + }); + }); + }); + + describe('dispose', () => { + it('calls disposable dispose', () => { + spyOn(model.disposable, 'dispose').and.callThrough(); + + model.dispose(); + + expect(model.disposable.dispose).toHaveBeenCalled(); + }); + + it('clears events', () => { + model.onChange(() => {}); + + expect(model.events.size).toBe(1); + + model.dispose(); + + expect(model.events.size).toBe(0); + }); + }); +}); diff --git a/spec/javascripts/repo/lib/decorations/controller_spec.js b/spec/javascripts/repo/lib/decorations/controller_spec.js new file mode 100644 index 00000000000..2e32e8fa0bd --- /dev/null +++ b/spec/javascripts/repo/lib/decorations/controller_spec.js @@ -0,0 +1,120 @@ +/* global monaco */ +import monacoLoader from '~/repo/monaco_loader'; +import editor from '~/repo/lib/editor'; +import DecorationsController from '~/repo/lib/decorations/controller'; +import Model from '~/repo/lib/common/model'; +import { file } from '../../helpers'; + +describe('Multi-file editor library decorations controller', () => { + let editorInstance; + let controller; + let model; + + beforeEach((done) => { + monacoLoader(['vs/editor/editor.main'], () => { + editorInstance = editor.create(monaco); + editorInstance.createInstance(document.createElement('div')); + + controller = new DecorationsController(editorInstance); + model = new Model(monaco, file('path')); + + done(); + }); + }); + + afterEach(() => { + model.dispose(); + editorInstance.dispose(); + controller.dispose(); + }); + + describe('getAllDecorationsForModel', () => { + it('returns empty array when no decorations exist for model', () => { + const decorations = controller.getAllDecorationsForModel(model); + + expect(decorations).toEqual([]); + }); + + it('returns decorations by model URL', () => { + controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]); + + const decorations = controller.getAllDecorationsForModel(model); + + expect(decorations[0]).toEqual({ decoration: 'decorationValue' }); + }); + }); + + describe('addDecorations', () => { + it('caches decorations in a new map', () => { + controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]); + + expect(controller.decorations.size).toBe(1); + }); + + it('does not create new cache model', () => { + controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]); + controller.addDecorations(model, 'key', [{ decoration: 'decorationValue2' }]); + + expect(controller.decorations.size).toBe(1); + }); + + it('caches decorations by model URL', () => { + controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]); + + expect(controller.decorations.size).toBe(1); + expect(controller.decorations.keys().next().value).toBe('path'); + }); + + it('calls decorate method', () => { + spyOn(controller, 'decorate'); + + controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]); + + expect(controller.decorate).toHaveBeenCalled(); + }); + }); + + describe('decorate', () => { + it('sets decorations on editor instance', () => { + spyOn(controller.editor.instance, 'deltaDecorations'); + + controller.decorate(model); + + expect(controller.editor.instance.deltaDecorations).toHaveBeenCalledWith([], []); + }); + + it('caches decorations', () => { + spyOn(controller.editor.instance, 'deltaDecorations').and.returnValue([]); + + controller.decorate(model); + + expect(controller.editorDecorations.size).toBe(1); + }); + + it('caches decorations by model URL', () => { + spyOn(controller.editor.instance, 'deltaDecorations').and.returnValue([]); + + controller.decorate(model); + + expect(controller.editorDecorations.keys().next().value).toBe('path'); + }); + }); + + describe('dispose', () => { + it('clears cached decorations', () => { + controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]); + + controller.dispose(); + + expect(controller.decorations.size).toBe(0); + }); + + it('clears cached editorDecorations', () => { + controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]); + + controller.dispose(); + + expect(controller.editorDecorations.size).toBe(0); + }); + }); +}); diff --git a/spec/javascripts/repo/lib/diff/controller_spec.js b/spec/javascripts/repo/lib/diff/controller_spec.js new file mode 100644 index 00000000000..ed62e28d3a3 --- /dev/null +++ b/spec/javascripts/repo/lib/diff/controller_spec.js @@ -0,0 +1,176 @@ +/* global monaco */ +import monacoLoader from '~/repo/monaco_loader'; +import editor from '~/repo/lib/editor'; +import ModelManager from '~/repo/lib/common/model_manager'; +import DecorationsController from '~/repo/lib/decorations/controller'; +import DirtyDiffController, { getDiffChangeType, getDecorator } from '~/repo/lib/diff/controller'; +import { computeDiff } from '~/repo/lib/diff/diff'; +import { file } from '../../helpers'; + +describe('Multi-file editor library dirty diff controller', () => { + let editorInstance; + let controller; + let modelManager; + let decorationsController; + let model; + + beforeEach((done) => { + monacoLoader(['vs/editor/editor.main'], () => { + editorInstance = editor.create(monaco); + editorInstance.createInstance(document.createElement('div')); + + modelManager = new ModelManager(monaco); + decorationsController = new DecorationsController(editorInstance); + + model = modelManager.addModel(file()); + + controller = new DirtyDiffController(modelManager, decorationsController); + + done(); + }); + }); + + afterEach(() => { + controller.dispose(); + model.dispose(); + decorationsController.dispose(); + editorInstance.dispose(); + }); + + describe('getDiffChangeType', () => { + ['added', 'removed', 'modified'].forEach((type) => { + it(`returns ${type}`, () => { + const change = { + [type]: true, + }; + + expect(getDiffChangeType(change)).toBe(type); + }); + }); + }); + + describe('getDecorator', () => { + ['added', 'removed', 'modified'].forEach((type) => { + it(`returns with linesDecorationsClassName for ${type}`, () => { + const change = { + [type]: true, + }; + + expect( + getDecorator(change).options.linesDecorationsClassName, + ).toBe(`dirty-diff dirty-diff-${type}`); + }); + + it('returns with line numbers', () => { + const change = { + lineNumber: 1, + endLineNumber: 2, + [type]: true, + }; + + const range = getDecorator(change).range; + + expect(range.startLineNumber).toBe(1); + expect(range.endLineNumber).toBe(2); + expect(range.startColumn).toBe(1); + expect(range.endColumn).toBe(1); + }); + }); + }); + + describe('attachModel', () => { + it('adds change event callback', () => { + spyOn(model, 'onChange'); + + controller.attachModel(model); + + expect(model.onChange).toHaveBeenCalled(); + }); + + it('calls throttledComputeDiff on change', () => { + spyOn(controller, 'throttledComputeDiff'); + + controller.attachModel(model); + + model.getModel().setValue('123'); + + expect(controller.throttledComputeDiff).toHaveBeenCalled(); + }); + }); + + describe('computeDiff', () => { + it('posts to worker', () => { + spyOn(controller.dirtyDiffWorker, 'postMessage'); + + controller.computeDiff(model); + + expect(controller.dirtyDiffWorker.postMessage).toHaveBeenCalledWith({ + path: model.path, + originalContent: '', + newContent: '', + }); + }); + }); + + describe('reDecorate', () => { + it('calls decorations controller decorate', () => { + spyOn(controller.decorationsController, 'decorate'); + + controller.reDecorate(model); + + expect(controller.decorationsController.decorate).toHaveBeenCalledWith(model); + }); + }); + + describe('decorate', () => { + it('adds decorations into decorations controller', () => { + spyOn(controller.decorationsController, 'addDecorations'); + + controller.decorate({ data: { changes: [], path: 'path' } }); + + expect(controller.decorationsController.addDecorations).toHaveBeenCalledWith('path', 'dirtyDiff', jasmine.anything()); + }); + + it('adds decorations into editor', () => { + const spy = spyOn(controller.decorationsController.editor.instance, 'deltaDecorations'); + + controller.decorate({ data: { changes: computeDiff('123', '1234'), path: 'path' } }); + + expect(spy).toHaveBeenCalledWith([], [{ + range: new monaco.Range( + 1, 1, 1, 1, + ), + options: { + isWholeLine: true, + linesDecorationsClassName: 'dirty-diff dirty-diff-modified', + }, + }]); + }); + }); + + describe('dispose', () => { + it('calls disposable dispose', () => { + spyOn(controller.disposable, 'dispose').and.callThrough(); + + controller.dispose(); + + expect(controller.disposable.dispose).toHaveBeenCalled(); + }); + + it('terminates worker', () => { + spyOn(controller.dirtyDiffWorker, 'terminate').and.callThrough(); + + controller.dispose(); + + expect(controller.dirtyDiffWorker.terminate).toHaveBeenCalled(); + }); + + it('removes worker event listener', () => { + spyOn(controller.dirtyDiffWorker, 'removeEventListener').and.callThrough(); + + controller.dispose(); + + expect(controller.dirtyDiffWorker.removeEventListener).toHaveBeenCalledWith('message', jasmine.anything()); + }); + }); +}); diff --git a/spec/javascripts/repo/lib/diff/diff_spec.js b/spec/javascripts/repo/lib/diff/diff_spec.js new file mode 100644 index 00000000000..3269ec5d2c9 --- /dev/null +++ b/spec/javascripts/repo/lib/diff/diff_spec.js @@ -0,0 +1,80 @@ +import { computeDiff } from '~/repo/lib/diff/diff'; + +describe('Multi-file editor library diff calculator', () => { + describe('computeDiff', () => { + it('returns empty array if no changes', () => { + const diff = computeDiff('123', '123'); + + expect(diff).toEqual([]); + }); + + describe('modified', () => { + it('', () => { + const diff = computeDiff('123', '1234')[0]; + + expect(diff.added).toBeTruthy(); + expect(diff.modified).toBeTruthy(); + expect(diff.removed).toBeUndefined(); + }); + + it('', () => { + const diff = computeDiff('123\n123\n123', '123\n1234\n123')[0]; + + expect(diff.added).toBeTruthy(); + expect(diff.modified).toBeTruthy(); + expect(diff.removed).toBeUndefined(); + expect(diff.lineNumber).toBe(2); + }); + }); + + describe('added', () => { + it('', () => { + const diff = computeDiff('123', '123\n123')[0]; + + expect(diff.added).toBeTruthy(); + expect(diff.modified).toBeUndefined(); + expect(diff.removed).toBeUndefined(); + }); + + it('', () => { + const diff = computeDiff('123\n123\n123', '123\n123\n1234\n123')[0]; + + expect(diff.added).toBeTruthy(); + expect(diff.modified).toBeUndefined(); + expect(diff.removed).toBeUndefined(); + expect(diff.lineNumber).toBe(3); + }); + }); + + describe('removed', () => { + it('', () => { + const diff = computeDiff('123', '')[0]; + + expect(diff.added).toBeUndefined(); + expect(diff.modified).toBeUndefined(); + expect(diff.removed).toBeTruthy(); + }); + + it('', () => { + const diff = computeDiff('123\n123\n123', '123\n123')[0]; + + expect(diff.added).toBeUndefined(); + expect(diff.modified).toBeTruthy(); + expect(diff.removed).toBeTruthy(); + expect(diff.lineNumber).toBe(2); + }); + }); + + it('includes line number of change', () => { + const diff = computeDiff('123', '')[0]; + + expect(diff.lineNumber).toBe(1); + }); + + it('includes end line number of change', () => { + const diff = computeDiff('123', '')[0]; + + expect(diff.endLineNumber).toBe(1); + }); + }); +}); diff --git a/spec/javascripts/repo/lib/editor_options_spec.js b/spec/javascripts/repo/lib/editor_options_spec.js new file mode 100644 index 00000000000..b4887d063ed --- /dev/null +++ b/spec/javascripts/repo/lib/editor_options_spec.js @@ -0,0 +1,7 @@ +import editorOptions from '~/repo/lib/editor_options'; + +describe('Multi-file editor library editor options', () => { + it('returns an array', () => { + expect(editorOptions).toEqual(jasmine.any(Array)); + }); +}); diff --git a/spec/javascripts/repo/lib/editor_spec.js b/spec/javascripts/repo/lib/editor_spec.js new file mode 100644 index 00000000000..cd32832a232 --- /dev/null +++ b/spec/javascripts/repo/lib/editor_spec.js @@ -0,0 +1,128 @@ +/* global monaco */ +import monacoLoader from '~/repo/monaco_loader'; +import editor from '~/repo/lib/editor'; +import { file } from '../helpers'; + +describe('Multi-file editor library', () => { + let instance; + + beforeEach((done) => { + monacoLoader(['vs/editor/editor.main'], () => { + instance = editor.create(monaco); + + done(); + }); + }); + + afterEach(() => { + instance.dispose(); + }); + + it('creates instance of editor', () => { + expect(editor.editorInstance).not.toBeNull(); + }); + + describe('createInstance', () => { + let el; + + beforeEach(() => { + el = document.createElement('div'); + }); + + it('creates editor instance', () => { + spyOn(instance.monaco.editor, 'create').and.callThrough(); + + instance.createInstance(el); + + expect(instance.monaco.editor.create).toHaveBeenCalled(); + }); + + it('creates dirty diff controller', () => { + instance.createInstance(el); + + expect(instance.dirtyDiffController).not.toBeNull(); + }); + }); + + describe('createModel', () => { + it('calls model manager addModel', () => { + spyOn(instance.modelManager, 'addModel'); + + instance.createModel('FILE'); + + expect(instance.modelManager.addModel).toHaveBeenCalledWith('FILE'); + }); + }); + + describe('attachModel', () => { + let model; + + beforeEach(() => { + instance.createInstance(document.createElement('div')); + + model = instance.createModel(file()); + }); + + it('sets the current model on the instance', () => { + instance.attachModel(model); + + expect(instance.currentModel).toBe(model); + }); + + it('attaches the model to the current instance', () => { + spyOn(instance.instance, 'setModel'); + + instance.attachModel(model); + + expect(instance.instance.setModel).toHaveBeenCalledWith(model.getModel()); + }); + + it('attaches the model to the dirty diff controller', () => { + spyOn(instance.dirtyDiffController, 'attachModel'); + + instance.attachModel(model); + + expect(instance.dirtyDiffController.attachModel).toHaveBeenCalledWith(model); + }); + + it('re-decorates with the dirty diff controller', () => { + spyOn(instance.dirtyDiffController, 'reDecorate'); + + instance.attachModel(model); + + expect(instance.dirtyDiffController.reDecorate).toHaveBeenCalledWith(model); + }); + }); + + describe('clearEditor', () => { + it('resets the editor model', () => { + instance.createInstance(document.createElement('div')); + + spyOn(instance.instance, 'setModel'); + + instance.clearEditor(); + + expect(instance.instance.setModel).toHaveBeenCalledWith(null); + }); + }); + + describe('dispose', () => { + it('calls disposble dispose method', () => { + spyOn(instance.disposable, 'dispose').and.callThrough(); + + instance.dispose(); + + expect(instance.disposable.dispose).toHaveBeenCalled(); + }); + + it('resets instance', () => { + instance.createInstance(document.createElement('div')); + + expect(instance.instance).not.toBeNull(); + + instance.dispose(); + + expect(instance.instance).toBeNull(); + }); + }); +}); diff --git a/spec/javascripts/repo/stores/getters_spec.js b/spec/javascripts/repo/stores/getters_spec.js index a204b2386cd..952b8ec3a59 100644 --- a/spec/javascripts/repo/stores/getters_spec.js +++ b/spec/javascripts/repo/stores/getters_spec.js @@ -116,4 +116,31 @@ describe('Multi-file store getters', () => { expect(getters.canEditFile(localState)).toBeFalsy(); }); }); + + describe('modifiedFiles', () => { + it('returns a list of modified files', () => { + localState.openFiles.push(file()); + localState.openFiles.push(file('changed')); + localState.openFiles[1].changed = true; + + const modifiedFiles = getters.modifiedFiles(localState); + + expect(modifiedFiles.length).toBe(1); + expect(modifiedFiles[0].name).toBe('changed'); + }); + }); + + describe('addedFiles', () => { + it('returns a list of added files', () => { + localState.openFiles.push(file()); + localState.openFiles.push(file('added')); + localState.openFiles[1].changed = true; + localState.openFiles[1].tempFile = true; + + const modifiedFiles = getters.addedFiles(localState); + + expect(modifiedFiles.length).toBe(1); + expect(modifiedFiles[0].name).toBe('added'); + }); + }); }); diff --git a/spec/javascripts/sidebar/mock_data.js b/spec/javascripts/sidebar/mock_data.js index 0682b463043..3b094d20838 100644 --- a/spec/javascripts/sidebar/mock_data.js +++ b/spec/javascripts/sidebar/mock_data.js @@ -1,6 +1,6 @@ /* eslint-disable quote-props*/ -const sidebarMockData = { +const RESPONSE_MAP = { 'GET': { '/gitlab-org/gitlab-shell/issues/5.json': { id: 45, @@ -66,6 +66,65 @@ const sidebarMockData = { }, labels: [], }, + '/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar': { + assignees: [ + { + name: 'User 0', + username: 'user0', + id: 22, + state: 'active', + avatar_url: 'http: //www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon', + web_url: 'http: //localhost:3001/user0', + }, + { + name: 'Marguerite Bartell', + username: 'tajuana', + id: 18, + state: 'active', + avatar_url: 'http: //www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon', + web_url: 'http: //localhost:3001/tajuana', + }, + { + name: 'Laureen Ritchie', + username: 'michaele.will', + id: 16, + state: 'active', + avatar_url: 'http: //www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon', + web_url: 'http: //localhost:3001/michaele.will', + }, + ], + human_time_estimate: null, + human_total_time_spent: null, + participants: [ + { + name: 'User 0', + username: 'user0', + id: 22, + state: 'active', + avatar_url: 'http: //www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon', + web_url: 'http: //localhost:3001/user0', + }, + { + name: 'Marguerite Bartell', + username: 'tajuana', + id: 18, + state: 'active', + avatar_url: 'http: //www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon', + web_url: 'http: //localhost:3001/tajuana', + }, + { + name: 'Laureen Ritchie', + username: 'michaele.will', + id: 16, + state: 'active', + avatar_url: 'http: //www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon', + web_url: 'http: //localhost:3001/michaele.will', + }, + ], + subscribed: true, + time_estimate: 0, + total_time_spent: 0, + }, '/autocomplete/projects?project_id=15': [ { 'id': 0, @@ -113,9 +172,10 @@ const sidebarMockData = { }, }; -export default { +const mockData = { + responseMap: RESPONSE_MAP, mediator: { - endpoint: '/gitlab-org/gitlab-shell/issues/5.json', + endpoint: '/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar', toggleSubscriptionEndpoint: '/gitlab-org/gitlab-shell/issues/5/toggle_subscription', moveIssueEndpoint: '/gitlab-org/gitlab-shell/issues/5/move', projectsAutocompleteEndpoint: '/autocomplete/projects?project_id=15', @@ -141,12 +201,14 @@ export default { name: 'Administrator', username: 'root', }, +}; - sidebarMockInterceptor(request, next) { - const body = sidebarMockData[request.method.toUpperCase()][request.url]; +mockData.sidebarMockInterceptor = function (request, next) { + const body = this.responseMap[request.method.toUpperCase()][request.url]; - next(request.respondWith(JSON.stringify(body), { - status: 200, - })); - }, -}; + next(request.respondWith(JSON.stringify(body), { + status: 200, + })); +}.bind(mockData); + +export default mockData; diff --git a/spec/javascripts/sidebar/sidebar_mediator_spec.js b/spec/javascripts/sidebar/sidebar_mediator_spec.js index 7deb1fd2118..14c34d5a78c 100644 --- a/spec/javascripts/sidebar/sidebar_mediator_spec.js +++ b/spec/javascripts/sidebar/sidebar_mediator_spec.js @@ -33,10 +33,29 @@ describe('Sidebar mediator', () => { .catch(done.fail); }); - it('fetches the data', () => { - spyOn(this.mediator.service, 'get').and.callThrough(); - this.mediator.fetch(); - expect(this.mediator.service.get).toHaveBeenCalled(); + it('fetches the data', (done) => { + const mockData = Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar']; + spyOn(this.mediator, 'processFetchedData').and.callThrough(); + + this.mediator.fetch() + .then(() => { + expect(this.mediator.processFetchedData).toHaveBeenCalledWith(mockData); + }) + .then(done) + .catch(done.fail); + }); + + it('processes fetched data', () => { + const mockData = Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar']; + this.mediator.processFetchedData(mockData); + + expect(this.mediator.store.assignees).toEqual(mockData.assignees); + expect(this.mediator.store.humanTimeEstimate).toEqual(mockData.human_time_estimate); + expect(this.mediator.store.humanTotalTimeSpent).toEqual(mockData.human_total_time_spent); + expect(this.mediator.store.participants).toEqual(mockData.participants); + expect(this.mediator.store.subscribed).toEqual(mockData.subscribed); + expect(this.mediator.store.timeEstimate).toEqual(mockData.time_estimate); + expect(this.mediator.store.totalTimeSpent).toEqual(mockData.total_time_spent); }); it('sets moveToProjectId', () => { diff --git a/spec/javascripts/sidebar/sidebar_store_spec.js b/spec/javascripts/sidebar/sidebar_store_spec.js index 51dee64fb93..ea4eae1e23f 100644 --- a/spec/javascripts/sidebar/sidebar_store_spec.js +++ b/spec/javascripts/sidebar/sidebar_store_spec.js @@ -120,6 +120,12 @@ describe('Sidebar store', () => { expect(this.store.isFetching.participants).toEqual(false); }); + it('sets loading state', () => { + this.store.setLoadingState('assignees', true); + + expect(this.store.isLoading.assignees).toEqual(true); + }); + it('set time tracking data', () => { this.store.setTimeTrackingData(Mock.time); expect(this.store.timeEstimate).toEqual(Mock.time.time_estimate); diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js index 0795d0aaa82..1ad7c2d8efa 100644 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ b/spec/javascripts/vue_mr_widget/mock_data.js @@ -202,7 +202,6 @@ export default { "revert_in_fork_path": "/root/acets-app/forks?continue%5Bnotice%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+has+been+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.+Try+to+cherry-pick+this+commit+again.&continue%5Bnotice_now%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+is+being+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.&continue%5Bto%5D=%2Froot%2Facets-app%2Fmerge_requests%2F22&namespace_key=1", "email_patches_path": "/root/acets-app/merge_requests/22.patch", "plain_diff_path": "/root/acets-app/merge_requests/22.diff", - "ci_status_path": "/root/acets-app/merge_requests/22/ci_status", "status_path": "/root/acets-app/merge_requests/22.json", "merge_check_path": "/root/acets-app/merge_requests/22/merge_check", "ci_environments_status_url": "/root/acets-app/merge_requests/22/ci_environments_status", diff --git a/spec/javascripts/vue_shared/components/icon_spec.js b/spec/javascripts/vue_shared/components/icon_spec.js index 104da4473ce..a22b6bd3a67 100644 --- a/spec/javascripts/vue_shared/components/icon_spec.js +++ b/spec/javascripts/vue_shared/components/icon_spec.js @@ -11,7 +11,7 @@ describe('Sprite Icon Component', function () { icon = mountComponent(IconComponent, { name: 'test', - size: 99, + size: 32, cssClasses: 'extraclasses', }); }); @@ -34,12 +34,18 @@ describe('Sprite Icon Component', function () { }); it('should properly compute iconSizeClass', function () { - expect(icon.iconSizeClass).toBe('s99'); + expect(icon.iconSizeClass).toBe('s32'); + }); + + it('forbids invalid size prop', () => { + expect(icon.$options.props.size.validator(NaN)).toBeFalsy(); + expect(icon.$options.props.size.validator(0)).toBeFalsy(); + expect(icon.$options.props.size.validator(9001)).toBeFalsy(); }); it('should properly render img css', function () { const classList = icon.$el.classList; - const containsSizeClass = classList.contains('s99'); + const containsSizeClass = classList.contains('s32'); const containsCustomClass = classList.contains('extraclasses'); expect(containsSizeClass).toBe(true); expect(containsCustomClass).toBe(true); diff --git a/spec/javascripts/vue_shared/components/issue/issue_warning_spec.js b/spec/javascripts/vue_shared/components/issue/issue_warning_spec.js index 2cf4d8e00ed..24484796bf1 100644 --- a/spec/javascripts/vue_shared/components/issue/issue_warning_spec.js +++ b/spec/javascripts/vue_shared/components/issue/issue_warning_spec.js @@ -16,7 +16,7 @@ describe('Issue Warning Component', () => { isLocked: true, }); - expect(vm.$el.querySelector('i').className).toEqual('fa icon fa-lock'); + expect(vm.$el.querySelector('.icon use').href.baseVal).toMatch(/lock$/); expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual('This issue is locked. Only project members can comment.'); }); }); @@ -27,7 +27,7 @@ describe('Issue Warning Component', () => { isConfidential: true, }); - expect(vm.$el.querySelector('i').className).toEqual('fa icon fa-eye-slash'); + expect(vm.$el.querySelector('.icon use').href.baseVal).toMatch(/eye-slash$/); expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual('This is a confidential issue. Your comment will not be visible to the public.'); }); }); @@ -39,7 +39,7 @@ describe('Issue Warning Component', () => { isConfidential: true, }); - expect(vm.$el.querySelector('i')).toBeFalsy(); + expect(vm.$el.querySelector('.icon')).toBeFalsy(); expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual('This issue is confidential and locked. People without permission will never get a notification and won\'t be able to comment.'); }); }); diff --git a/spec/javascripts/vue_shared/components/loading_button_spec.js b/spec/javascripts/vue_shared/components/loading_button_spec.js index c1eabdede00..49bf8ee6f7c 100644 --- a/spec/javascripts/vue_shared/components/loading_button_spec.js +++ b/spec/javascripts/vue_shared/components/loading_button_spec.js @@ -98,7 +98,6 @@ describe('LoadingButton', function () { it('does not call given callback when disabled because of loading', () => { vm = mountComponent(LoadingButton, { loading: true, - indeterminate: true, }); spyOn(vm, '$emit'); diff --git a/spec/javascripts/vue_shared/components/markdown/toolbar_spec.js b/spec/javascripts/vue_shared/components/markdown/toolbar_spec.js new file mode 100644 index 00000000000..818ef0af3c2 --- /dev/null +++ b/spec/javascripts/vue_shared/components/markdown/toolbar_spec.js @@ -0,0 +1,37 @@ +import Vue from 'vue'; +import toolbar from '~/vue_shared/components/markdown/toolbar.vue'; +import mountComponent from '../../../helpers/vue_mount_component_helper'; + +describe('toolbar', () => { + let vm; + const Toolbar = Vue.extend(toolbar); + const props = { + markdownDocsPath: '', + }; + + afterEach(() => { + vm.$destroy(); + }); + + describe('user can attach file', () => { + beforeEach(() => { + vm = mountComponent(Toolbar, props); + }); + + it('should render uploading-container', () => { + expect(vm.$el.querySelector('.uploading-container')).not.toBeNull(); + }); + }); + + describe('user cannot attach file', () => { + beforeEach(() => { + vm = mountComponent(Toolbar, Object.assign({}, props, { + canAttachFile: false, + })); + }); + + it('should not render uploading-container', () => { + expect(vm.$el.querySelector('.uploading-container')).toBeNull(); + }); + }); +}); diff --git a/spec/javascripts/pipelines/navigation_tabs_spec.js b/spec/javascripts/vue_shared/components/navigation_tabs_spec.js index f125a2fa189..78e7d747b92 100644 --- a/spec/javascripts/pipelines/navigation_tabs_spec.js +++ b/spec/javascripts/vue_shared/components/navigation_tabs_spec.js @@ -1,8 +1,8 @@ import Vue from 'vue'; -import navigationTabs from '~/pipelines/components/navigation_tabs.vue'; -import mountComponent from '../helpers/vue_mount_component_helper'; +import navigationTabs from '~/vue_shared/components/navigation_tabs.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; -describe('navigation tabs pipeline component', () => { +describe('navigation tabs component', () => { let vm; let Component; let data; @@ -29,7 +29,7 @@ describe('navigation tabs pipeline component', () => { ]; Component = Vue.extend(navigationTabs); - vm = mountComponent(Component, { tabs: data }); + vm = mountComponent(Component, { tabs: data, scope: 'pipelines' }); }); afterEach(() => { @@ -52,4 +52,10 @@ describe('navigation tabs pipeline component', () => { it('should not render badge', () => { expect(vm.$el.querySelector('.js-pipelines-tab-running .badge')).toEqual(null); }); + + it('should trigger onTabClick', () => { + spyOn(vm, '$emit'); + vm.$el.querySelector('.js-pipelines-tab-pending').click(); + expect(vm.$emit).toHaveBeenCalledWith('onChangeTab', 'pending'); + }); }); diff --git a/spec/javascripts/vue_shared/components/pikaday_spec.js b/spec/javascripts/vue_shared/components/pikaday_spec.js new file mode 100644 index 00000000000..47af9534737 --- /dev/null +++ b/spec/javascripts/vue_shared/components/pikaday_spec.js @@ -0,0 +1,29 @@ +import Vue from 'vue'; +import datePicker from '~/vue_shared/components/pikaday.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('datePicker', () => { + let vm; + beforeEach(() => { + const DatePicker = Vue.extend(datePicker); + vm = mountComponent(DatePicker, { + label: 'label', + }); + }); + + it('should render label text', () => { + expect(vm.$el.querySelector('.dropdown-toggle-text').innerText.trim()).toEqual('label'); + }); + + it('should show calendar', () => { + expect(vm.$el.querySelector('.pika-single')).toBeDefined(); + }); + + it('should toggle when dropdown is clicked', () => { + const hidePicker = jasmine.createSpy(); + vm.$on('hidePicker', hidePicker); + + vm.$el.querySelector('.dropdown-menu-toggle').click(); + expect(hidePicker).toHaveBeenCalled(); + }); +}); diff --git a/spec/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js b/spec/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js new file mode 100644 index 00000000000..cce53193870 --- /dev/null +++ b/spec/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js @@ -0,0 +1,35 @@ +import Vue from 'vue'; +import collapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue'; +import mountComponent from '../../../helpers/vue_mount_component_helper'; + +describe('collapsedCalendarIcon', () => { + let vm; + beforeEach(() => { + const CollapsedCalendarIcon = Vue.extend(collapsedCalendarIcon); + vm = mountComponent(CollapsedCalendarIcon, { + containerClass: 'test-class', + text: 'text', + showIcon: false, + }); + }); + + it('should add class to container', () => { + expect(vm.$el.classList.contains('test-class')).toEqual(true); + }); + + it('should hide calendar icon if showIcon', () => { + expect(vm.$el.querySelector('.fa-calendar')).toBeNull(); + }); + + it('should render text', () => { + expect(vm.$el.querySelector('span').innerText.trim()).toEqual('text'); + }); + + it('should emit click event when container is clicked', () => { + const click = jasmine.createSpy(); + vm.$on('click', click); + + vm.$el.click(); + expect(click).toHaveBeenCalled(); + }); +}); diff --git a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js b/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js new file mode 100644 index 00000000000..20363e78094 --- /dev/null +++ b/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js @@ -0,0 +1,91 @@ +import Vue from 'vue'; +import collapsedGroupedDatePicker from '~/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue'; +import mountComponent from '../../../helpers/vue_mount_component_helper'; + +describe('collapsedGroupedDatePicker', () => { + let vm; + beforeEach(() => { + const CollapsedGroupedDatePicker = Vue.extend(collapsedGroupedDatePicker); + vm = mountComponent(CollapsedGroupedDatePicker, { + showToggleSidebar: true, + }); + }); + + it('should render toggle sidebar if showToggleSidebar', (done) => { + expect(vm.$el.querySelector('.issuable-sidebar-header')).toBeDefined(); + + vm.showToggleSidebar = false; + Vue.nextTick(() => { + expect(vm.$el.querySelector('.issuable-sidebar-header')).toBeNull(); + done(); + }); + }); + + it('toggleCollapse events', () => { + const toggleCollapse = jasmine.createSpy(); + + beforeEach((done) => { + vm.minDate = new Date('07/17/2016'); + Vue.nextTick(done); + }); + + it('should emit when sidebar is toggled', () => { + vm.$el.querySelector('.gutter-toggle').click(); + expect(toggleCollapse).toHaveBeenCalled(); + }); + + it('should emit when collapsed-calendar-icon is clicked', () => { + vm.$el.querySelector('.sidebar-collapsed-icon').click(); + expect(toggleCollapse).toHaveBeenCalled(); + }); + }); + + describe('minDate and maxDate', () => { + beforeEach((done) => { + vm.minDate = new Date('07/17/2016'); + vm.maxDate = new Date('07/17/2017'); + Vue.nextTick(done); + }); + + it('should render both collapsed-calendar-icon', () => { + const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon'); + expect(icons.length).toEqual(2); + expect(icons[0].innerText.trim()).toEqual('Jul 17 2016'); + expect(icons[1].innerText.trim()).toEqual('Jul 17 2017'); + }); + }); + + describe('minDate', () => { + beforeEach((done) => { + vm.minDate = new Date('07/17/2016'); + Vue.nextTick(done); + }); + + it('should render minDate in collapsed-calendar-icon', () => { + const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon'); + expect(icons.length).toEqual(1); + expect(icons[0].innerText.trim()).toEqual('From Jul 17 2016'); + }); + }); + + describe('maxDate', () => { + beforeEach((done) => { + vm.maxDate = new Date('07/17/2017'); + Vue.nextTick(done); + }); + + it('should render maxDate in collapsed-calendar-icon', () => { + const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon'); + expect(icons.length).toEqual(1); + expect(icons[0].innerText.trim()).toEqual('Until Jul 17 2017'); + }); + }); + + describe('no dates', () => { + it('should render None', () => { + const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon'); + expect(icons.length).toEqual(1); + expect(icons[0].innerText.trim()).toEqual('None'); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js b/spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js new file mode 100644 index 00000000000..926e11b4d30 --- /dev/null +++ b/spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js @@ -0,0 +1,117 @@ +import Vue from 'vue'; +import sidebarDatePicker from '~/vue_shared/components/sidebar/date_picker.vue'; +import mountComponent from '../../../helpers/vue_mount_component_helper'; + +describe('sidebarDatePicker', () => { + let vm; + beforeEach(() => { + const SidebarDatePicker = Vue.extend(sidebarDatePicker); + vm = mountComponent(SidebarDatePicker, { + label: 'label', + isLoading: true, + }); + }); + + it('should emit toggleCollapse when collapsed toggle sidebar is clicked', () => { + const toggleCollapse = jasmine.createSpy(); + vm.$on('toggleCollapse', toggleCollapse); + + vm.$el.querySelector('.issuable-sidebar-header .gutter-toggle').click(); + expect(toggleCollapse).toHaveBeenCalled(); + }); + + it('should render collapsed-calendar-icon', () => { + expect(vm.$el.querySelector('.sidebar-collapsed-icon')).toBeDefined(); + }); + + it('should render label', () => { + expect(vm.$el.querySelector('.title').innerText.trim()).toEqual('label'); + }); + + it('should render loading-icon when isLoading', () => { + expect(vm.$el.querySelector('.fa-spin')).toBeDefined(); + }); + + it('should render value when not editing', () => { + expect(vm.$el.querySelector('.value-content')).toBeDefined(); + }); + + it('should render None if there is no selectedDate', () => { + expect(vm.$el.querySelector('.value-content span').innerText.trim()).toEqual('None'); + }); + + it('should render date-picker when editing', (done) => { + vm.editing = true; + Vue.nextTick(() => { + expect(vm.$el.querySelector('.pika-label')).toBeDefined(); + done(); + }); + }); + + describe('editable', () => { + beforeEach((done) => { + vm.editable = true; + Vue.nextTick(done); + }); + + it('should render edit button', () => { + expect(vm.$el.querySelector('.title .btn-blank').innerText.trim()).toEqual('Edit'); + }); + + it('should enable editing when edit button is clicked', (done) => { + vm.isLoading = false; + Vue.nextTick(() => { + vm.$el.querySelector('.title .btn-blank').click(); + expect(vm.editing).toEqual(true); + done(); + }); + }); + }); + + it('should render date if selectedDate', (done) => { + vm.selectedDate = new Date('07/07/2017'); + Vue.nextTick(() => { + expect(vm.$el.querySelector('.value-content strong').innerText.trim()).toEqual('Jul 7, 2017'); + done(); + }); + }); + + describe('selectedDate and editable', () => { + beforeEach((done) => { + vm.selectedDate = new Date('07/07/2017'); + vm.editable = true; + Vue.nextTick(done); + }); + + it('should render remove button if selectedDate and editable', () => { + expect(vm.$el.querySelector('.value-content .btn-blank').innerText.trim()).toEqual('remove'); + }); + + it('should emit saveDate when remove button is clicked', () => { + const saveDate = jasmine.createSpy(); + vm.$on('saveDate', saveDate); + + vm.$el.querySelector('.value-content .btn-blank').click(); + expect(saveDate).toHaveBeenCalled(); + }); + }); + + describe('showToggleSidebar', () => { + beforeEach((done) => { + vm.showToggleSidebar = true; + Vue.nextTick(done); + }); + + it('should render toggle-sidebar when showToggleSidebar', () => { + expect(vm.$el.querySelector('.title .gutter-toggle')).toBeDefined(); + }); + + it('should emit toggleCollapse when toggle sidebar is clicked', () => { + const toggleCollapse = jasmine.createSpy(); + vm.$on('toggleCollapse', toggleCollapse); + + vm.$el.querySelector('.title .gutter-toggle').click(); + expect(toggleCollapse).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/sidebar/toggle_sidebar_spec.js b/spec/javascripts/vue_shared/components/sidebar/toggle_sidebar_spec.js new file mode 100644 index 00000000000..752a9e89d50 --- /dev/null +++ b/spec/javascripts/vue_shared/components/sidebar/toggle_sidebar_spec.js @@ -0,0 +1,32 @@ +import Vue from 'vue'; +import toggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue'; +import mountComponent from '../../../helpers/vue_mount_component_helper'; + +describe('toggleSidebar', () => { + let vm; + beforeEach(() => { + const ToggleSidebar = Vue.extend(toggleSidebar); + vm = mountComponent(ToggleSidebar, { + collapsed: true, + }); + }); + + it('should render << when collapsed', () => { + expect(vm.$el.querySelector('.fa').classList.contains('fa-angle-double-left')).toEqual(true); + }); + + it('should render >> when collapsed', () => { + vm.collapsed = false; + Vue.nextTick(() => { + expect(vm.$el.querySelector('.fa').classList.contains('fa-angle-double-right')).toEqual(true); + }); + }); + + it('should emit toggle event when button clicked', () => { + const toggle = jasmine.createSpy(); + vm.$on('toggle', toggle); + vm.$el.click(); + + expect(toggle).toHaveBeenCalled(); + }); +}); diff --git a/spec/javascripts/vue_shared/components/toggle_button_spec.js b/spec/javascripts/vue_shared/components/toggle_button_spec.js new file mode 100644 index 00000000000..447d74d4e08 --- /dev/null +++ b/spec/javascripts/vue_shared/components/toggle_button_spec.js @@ -0,0 +1,91 @@ +import Vue from 'vue'; +import toggleButton from '~/vue_shared/components/toggle_button.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Toggle Button', () => { + let vm; + let Component; + + beforeEach(() => { + Component = Vue.extend(toggleButton); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('render output', () => { + beforeEach(() => { + vm = mountComponent(Component, { + value: true, + name: 'foo', + }); + }); + + it('renders input with provided name', () => { + expect(vm.$el.querySelector('input').getAttribute('name')).toEqual('foo'); + }); + + it('renders input with provided value', () => { + expect(vm.$el.querySelector('input').getAttribute('value')).toEqual('true'); + }); + + it('renders Enabled and Disabled text data attributes', () => { + expect(vm.$el.querySelector('button').getAttribute('data-enabled-text')).toEqual('Enabled'); + expect(vm.$el.querySelector('button').getAttribute('data-disabled-text')).toEqual('Disabled'); + }); + }); + + describe('is-checked', () => { + beforeEach(() => { + vm = mountComponent(Component, { + value: true, + }); + + spyOn(vm, '$emit'); + }); + + it('renders is checked class', () => { + expect(vm.$el.querySelector('button').classList.contains('is-checked')).toEqual(true); + }); + + it('emits change event when clicked', () => { + vm.$el.querySelector('button').click(); + + expect(vm.$emit).toHaveBeenCalledWith('change', false); + }); + }); + + describe('is-disabled', () => { + beforeEach(() => { + vm = mountComponent(Component, { + value: true, + disabledInput: true, + }); + spyOn(vm, '$emit'); + }); + + it('renders disabled button', () => { + expect(vm.$el.querySelector('button').classList.contains('is-disabled')).toEqual(true); + }); + + it('does not emit change event when clicked', () => { + vm.$el.querySelector('button').click(); + + expect(vm.$emit).not.toHaveBeenCalled(); + }); + }); + + describe('is-loading', () => { + beforeEach(() => { + vm = mountComponent(Component, { + value: true, + isLoading: true, + }); + }); + + it('renders loading class', () => { + expect(vm.$el.querySelector('button').classList.contains('is-loading')).toEqual(true); + }); + }); +}); diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js index 7047053d131..45a0bb0650f 100644 --- a/spec/javascripts/zen_mode_spec.js +++ b/spec/javascripts/zen_mode_spec.js @@ -1,77 +1,93 @@ -/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, object-shorthand, comma-dangle, no-return-assign, new-cap, max-len */ /* global Mousetrap */ import Dropzone from 'dropzone'; import ZenMode from '~/zen_mode'; -(function() { - var enterZen, escapeKeydown, exitZen; - - describe('ZenMode', function() { - var fixtureName = 'merge_requests/merge_request_with_comment.html.raw'; - preloadFixtures(fixtureName); - beforeEach(function() { - loadFixtures(fixtureName); - spyOn(Dropzone, 'forElement').and.callFake(function() { - return { - enable: function() { - return true; - } - }; - // Stub Dropzone.forElement(...).enable() - }); - this.zen = new ZenMode(); - // Set this manually because we can't actually scroll the window - return this.zen.scroll_position = 456; +describe('ZenMode', () => { + let zen; + const fixtureName = 'merge_requests/merge_request_with_comment.html.raw'; + + preloadFixtures(fixtureName); + + function enterZen() { + $('.notes-form .js-zen-enter').click(); + } + + function exitZen() { + $('.notes-form .js-zen-leave').click(); + } + + function escapeKeydown() { + $('.notes-form textarea').trigger($.Event('keydown', { + keyCode: 27, + })); + } + + beforeEach(() => { + loadFixtures(fixtureName); + + spyOn(Dropzone, 'forElement').and.callFake(() => ({ + enable: () => true, + })); + zen = new ZenMode(); + + // Set this manually because we can't actually scroll the window + zen.scroll_position = 456; + }); + + describe('on enter', () => { + it('pauses Mousetrap', () => { + spyOn(Mousetrap, 'pause'); + enterZen(); + expect(Mousetrap.pause).toHaveBeenCalled(); }); - describe('on enter', function() { - it('pauses Mousetrap', function() { - spyOn(Mousetrap, 'pause'); - enterZen(); - return expect(Mousetrap.pause).toHaveBeenCalled(); - }); - return it('removes textarea styling', function() { - $('.notes-form textarea').attr('style', 'height: 400px'); - enterZen(); - return expect($('.notes-form textarea')).not.toHaveAttr('style'); - }); + + it('removes textarea styling', () => { + $('.notes-form textarea').attr('style', 'height: 400px'); + enterZen(); + expect($('.notes-form textarea')).not.toHaveAttr('style'); }); - describe('in use', function() { - beforeEach(function() { - return enterZen(); - }); - return it('exits on Escape', function() { - escapeKeydown(); - return expect($('.notes-form .zen-backdrop')).not.toHaveClass('fullscreen'); - }); + }); + + describe('in use', () => { + beforeEach(enterZen); + + it('exits on Escape', () => { + escapeKeydown(); + expect($('.notes-form .zen-backdrop')).not.toHaveClass('fullscreen'); + }); + }); + + describe('on exit', () => { + beforeEach(enterZen); + + it('unpauses Mousetrap', () => { + spyOn(Mousetrap, 'unpause'); + exitZen(); + expect(Mousetrap.unpause).toHaveBeenCalled(); }); - return describe('on exit', function() { - beforeEach(function() { - return enterZen(); - }); - it('unpauses Mousetrap', function() { - spyOn(Mousetrap, 'unpause'); - exitZen(); - return expect(Mousetrap.unpause).toHaveBeenCalled(); - }); - return it('restores the scroll position', function() { - spyOn(this.zen, 'scrollTo'); - exitZen(); - return expect(this.zen.scrollTo).toHaveBeenCalled(); - }); + + it('restores the scroll position', () => { + spyOn(zen, 'scrollTo'); + exitZen(); + expect(zen.scrollTo).toHaveBeenCalled(); }); }); - enterZen = function() { - return $('.notes-form .js-zen-enter').click(); - }; + describe('enabling dropzone', () => { + beforeEach(() => { + enterZen(); + }); - exitZen = function() { - return $('.notes-form .js-zen-leave').click(); - }; + it('should not call dropzone if element is not dropzone valid', () => { + $('.div-dropzone').addClass('js-invalid-dropzone'); + exitZen(); + expect(Dropzone.forElement).not.toHaveBeenCalled(); + }); - escapeKeydown = function() { - return $('.notes-form textarea').trigger($.Event('keydown', { - keyCode: 27 - })); - }; -}).call(window); + it('should call dropzone if element is dropzone valid', () => { + $('.div-dropzone').removeClass('js-invalid-dropzone'); + exitZen(); + expect(Dropzone.forElement).toHaveBeenCalled(); + }); + }); +}); |