diff options
Diffstat (limited to 'spec/javascripts')
16 files changed, 744 insertions, 55 deletions
diff --git a/spec/javascripts/blob/create_branch_dropdown_spec.js b/spec/javascripts/blob/create_branch_dropdown_spec.js index c1179e572ae..9f0d373cb81 100644 --- a/spec/javascripts/blob/create_branch_dropdown_spec.js +++ b/spec/javascripts/blob/create_branch_dropdown_spec.js @@ -1,5 +1,4 @@ require('~/gl_dropdown'); -require('~/lib/utils/type_utility'); require('~/blob/create_branch_dropdown'); require('~/blob/target_branch_dropdown'); diff --git a/spec/javascripts/blob/target_branch_dropdown_spec.js b/spec/javascripts/blob/target_branch_dropdown_spec.js index bb436978a0f..76ed3dc1a2d 100644 --- a/spec/javascripts/blob/target_branch_dropdown_spec.js +++ b/spec/javascripts/blob/target_branch_dropdown_spec.js @@ -1,5 +1,4 @@ require('~/gl_dropdown'); -require('~/lib/utils/type_utility'); require('~/blob/create_branch_dropdown'); require('~/blob/target_branch_dropdown'); diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js index c207fb00a47..8f90ed69e64 100644 --- a/spec/javascripts/gl_dropdown_spec.js +++ b/spec/javascripts/gl_dropdown_spec.js @@ -2,7 +2,6 @@ require('~/gl_dropdown'); require('~/lib/utils/common_utils'); -require('~/lib/utils/type_utility'); require('~/lib/utils/url_utility'); (() => { @@ -44,21 +43,18 @@ require('~/lib/utils/url_utility'); preloadFixtures('static/gl_dropdown.html.raw'); loadJSONFixtures('projects.json'); - function initDropDown(hasRemote, isFilterable) { - this.dropdownButtonElement = $('#js-project-dropdown', this.dropdownContainerElement).glDropdown({ + function initDropDown(hasRemote, isFilterable, extraOpts = {}) { + const options = Object.assign({ selectable: true, filterable: isFilterable, data: hasRemote ? remoteMock.bind({}, this.projectsData) : this.projectsData, search: { fields: ['name'] }, - text: (project) => { - (project.name_with_namespace || project.name); - }, - id: (project) => { - project.id; - } - }); + text: project => (project.name_with_namespace || project.name), + id: project => project.id, + }, extraOpts); + this.dropdownButtonElement = $('#js-project-dropdown', this.dropdownContainerElement).glDropdown(options); } beforeEach(() => { @@ -80,6 +76,37 @@ require('~/lib/utils/url_utility'); expect(this.dropdownContainerElement).toHaveClass('open'); }); + it('escapes HTML as text', () => { + this.projectsData[0].name_with_namespace = '<script>alert("testing");</script>'; + + initDropDown.call(this, false); + + this.dropdownButtonElement.click(); + + expect( + $('.dropdown-content li:first-child').text(), + ).toBe('<script>alert("testing");</script>'); + }); + + it('should output HTML when highlighting', () => { + this.projectsData[0].name_with_namespace = 'testing'; + $('.dropdown-input .dropdown-input-field').val('test'); + + initDropDown.call(this, false, true, { + highlight: true, + }); + + this.dropdownButtonElement.click(); + + expect( + $('.dropdown-content li:first-child').text(), + ).toBe('testing'); + + expect( + $('.dropdown-content li:first-child a').html(), + ).toBe('<b>t</b><b>e</b><b>s</b><b>t</b>ing'); + }); + describe('that is open', () => { beforeEach(() => { initDropDown.call(this, false, false); diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/javascripts/labels_issue_sidebar_spec.js index 37e038c16da..53aba191b19 100644 --- a/spec/javascripts/labels_issue_sidebar_spec.js +++ b/spec/javascripts/labels_issue_sidebar_spec.js @@ -2,7 +2,6 @@ /* global IssuableContext */ /* global LabelsSelect */ -require('~/lib/utils/type_utility'); require('~/gl_dropdown'); require('select2'); require('vendor/jquery.nicescroll'); diff --git a/spec/javascripts/pipelines/graph/graph_component_spec.js b/spec/javascripts/pipelines/graph/graph_component_spec.js index a756617e65e..6bd0eb86263 100644 --- a/spec/javascripts/pipelines/graph/graph_component_spec.js +++ b/spec/javascripts/pipelines/graph/graph_component_spec.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import graphComponent from '~/pipelines/components/graph/graph_component.vue'; +import graphJSON from './mock_data'; describe('graph component', () => { preloadFixtures('static/graph.html.raw'); @@ -20,41 +21,7 @@ describe('graph component', () => { describe('with a successfull response', () => { const interceptor = (request, next) => { - next(request.respondWith(JSON.stringify({ - details: { - stages: [{ - name: 'test', - title: 'test: passed', - status: { - icon: 'icon_status_success', - text: 'passed', - label: 'passed', - details_path: '/root/ci-mock/pipelines/123#test', - }, - path: '/root/ci-mock/pipelines/123#test', - groups: [{ - name: 'test', - size: 1, - jobs: [{ - id: 4153, - name: 'test', - status: { - icon: 'icon_status_success', - text: 'passed', - label: 'passed', - details_path: '/root/ci-mock/builds/4153', - action: { - icon: 'icon_action_retry', - title: 'Retry', - path: '/root/ci-mock/builds/4153/retry', - method: 'post', - }, - }, - }], - }], - }], - }, - }), { + next(request.respondWith(JSON.stringify(graphJSON), { status: 200, })); }; @@ -73,6 +40,18 @@ describe('graph component', () => { setTimeout(() => { expect(component.$el.classList.contains('js-pipeline-graph')).toEqual(true); + expect( + component.$el.querySelector('.stage-column:first-child').classList.contains('no-margin'), + ).toEqual(true); + + expect( + component.$el.querySelector('.stage-column:nth-child(2)').classList.contains('left-margin'), + ).toEqual(true); + + expect( + component.$el.querySelector('.stage-column:nth-child(2) .build:nth-child(1)').classList.contains('left-connector'), + ).toEqual(true); + expect(component.$el.querySelector('loading-icon')).toBe(null); expect(component.$el.querySelector('.stage-column-list')).toBeDefined(); diff --git a/spec/javascripts/pipelines/graph/mock_data.js b/spec/javascripts/pipelines/graph/mock_data.js new file mode 100644 index 00000000000..56c522b7f77 --- /dev/null +++ b/spec/javascripts/pipelines/graph/mock_data.js @@ -0,0 +1,232 @@ +/* eslint-disable quote-props, quotes, comma-dangle */ +export default { + "id": 123, + "user": { + "name": "Root", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": null, + "web_url": "http://localhost:3000/root" + }, + "active": false, + "coverage": null, + "path": "/root/ci-mock/pipelines/123", + "details": { + "status": { + "icon": "icon_status_success", + "text": "passed", + "label": "passed", + "group": "success", + "has_details": true, + "details_path": "/root/ci-mock/pipelines/123", + "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico" + }, + "duration": 9, + "finished_at": "2017-04-19T14:30:27.542Z", + "stages": [{ + "name": "test", + "title": "test: passed", + "groups": [{ + "name": "test", + "size": 1, + "status": { + "icon": "icon_status_success", + "text": "passed", + "label": "passed", + "group": "success", + "has_details": true, + "details_path": "/root/ci-mock/builds/4153", + "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", + "action": { + "icon": "icon_action_retry", + "title": "Retry", + "path": "/root/ci-mock/builds/4153/retry", + "method": "post" + } + }, + "jobs": [{ + "id": 4153, + "name": "test", + "build_path": "/root/ci-mock/builds/4153", + "retry_path": "/root/ci-mock/builds/4153/retry", + "playable": false, + "created_at": "2017-04-13T09:25:18.959Z", + "updated_at": "2017-04-13T09:25:23.118Z", + "status": { + "icon": "icon_status_success", + "text": "passed", + "label": "passed", + "group": "success", + "has_details": true, + "details_path": "/root/ci-mock/builds/4153", + "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", + "action": { + "icon": "icon_action_retry", + "title": "Retry", + "path": "/root/ci-mock/builds/4153/retry", + "method": "post" + } + } + }] + }], + "status": { + "icon": "icon_status_success", + "text": "passed", + "label": "passed", + "group": "success", + "has_details": true, + "details_path": "/root/ci-mock/pipelines/123#test", + "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico" + }, + "path": "/root/ci-mock/pipelines/123#test", + "dropdown_path": "/root/ci-mock/pipelines/123/stage.json?stage=test" + }, { + "name": "deploy", + "title": "deploy: passed", + "groups": [{ + "name": "deploy to production", + "size": 1, + "status": { + "icon": "icon_status_success", + "text": "passed", + "label": "passed", + "group": "success", + "has_details": true, + "details_path": "/root/ci-mock/builds/4166", + "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", + "action": { + "icon": "icon_action_retry", + "title": "Retry", + "path": "/root/ci-mock/builds/4166/retry", + "method": "post" + } + }, + "jobs": [{ + "id": 4166, + "name": "deploy to production", + "build_path": "/root/ci-mock/builds/4166", + "retry_path": "/root/ci-mock/builds/4166/retry", + "playable": false, + "created_at": "2017-04-19T14:29:46.463Z", + "updated_at": "2017-04-19T14:30:27.498Z", + "status": { + "icon": "icon_status_success", + "text": "passed", + "label": "passed", + "group": "success", + "has_details": true, + "details_path": "/root/ci-mock/builds/4166", + "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", + "action": { + "icon": "icon_action_retry", + "title": "Retry", + "path": "/root/ci-mock/builds/4166/retry", + "method": "post" + } + } + }] + }, { + "name": "deploy to staging", + "size": 1, + "status": { + "icon": "icon_status_success", + "text": "passed", + "label": "passed", + "group": "success", + "has_details": true, + "details_path": "/root/ci-mock/builds/4159", + "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", + "action": { + "icon": "icon_action_retry", + "title": "Retry", + "path": "/root/ci-mock/builds/4159/retry", + "method": "post" + } + }, + "jobs": [{ + "id": 4159, + "name": "deploy to staging", + "build_path": "/root/ci-mock/builds/4159", + "retry_path": "/root/ci-mock/builds/4159/retry", + "playable": false, + "created_at": "2017-04-18T16:32:08.420Z", + "updated_at": "2017-04-18T16:32:12.631Z", + "status": { + "icon": "icon_status_success", + "text": "passed", + "label": "passed", + "group": "success", + "has_details": true, + "details_path": "/root/ci-mock/builds/4159", + "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", + "action": { + "icon": "icon_action_retry", + "title": "Retry", + "path": "/root/ci-mock/builds/4159/retry", + "method": "post" + } + } + }] + }], + "status": { + "icon": "icon_status_success", + "text": "passed", + "label": "passed", + "group": "success", + "has_details": true, + "details_path": "/root/ci-mock/pipelines/123#deploy", + "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico" + }, + "path": "/root/ci-mock/pipelines/123#deploy", + "dropdown_path": "/root/ci-mock/pipelines/123/stage.json?stage=deploy" + }], + "artifacts": [], + "manual_actions": [{ + "name": "deploy to production", + "path": "/root/ci-mock/builds/4166/play", + "playable": false + }] + }, + "flags": { + "latest": true, + "triggered": false, + "stuck": false, + "yaml_errors": false, + "retryable": false, + "cancelable": false + }, + "ref": { + "name": "master", + "path": "/root/ci-mock/tree/master", + "tag": false, + "branch": true + }, + "commit": { + "id": "798e5f902592192afaba73f4668ae30e56eae492", + "short_id": "798e5f90", + "title": "Merge branch 'new-branch' into 'master'\r", + "created_at": "2017-04-13T10:25:17.000+01:00", + "parent_ids": ["54d483b1ed156fbbf618886ddf7ab023e24f8738", "c8e2d38a6c538822e81c57022a6e3a0cfedebbcc"], + "message": "Merge branch 'new-branch' into 'master'\r\n\r\nAdd new file\r\n\r\nSee merge request !1", + "author_name": "Root", + "author_email": "admin@example.com", + "authored_date": "2017-04-13T10:25:17.000+01:00", + "committer_name": "Root", + "committer_email": "admin@example.com", + "committed_date": "2017-04-13T10:25:17.000+01:00", + "author": { + "name": "Root", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": null, + "web_url": "http://localhost:3000/root" + }, + "author_gravatar_url": null, + "commit_url": "http://localhost:3000/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492", + "commit_path": "/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492" + }, + "created_at": "2017-04-13T09:25:18.881Z", + "updated_at": "2017-04-19T14:30:27.561Z" +}; diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js index 3a1d4e2440f..5c51e855401 100644 --- a/spec/javascripts/project_title_spec.js +++ b/spec/javascripts/project_title_spec.js @@ -2,7 +2,6 @@ /* global Project */ require('select2/select2.js'); -require('~/lib/utils/type_utility'); require('~/gl_dropdown'); require('~/api'); require('~/project_select'); diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index aaf058bd755..fa52a8a0dd2 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -3,7 +3,6 @@ require('~/gl_dropdown'); require('~/search_autocomplete'); require('~/lib/utils/common_utils'); -require('~/lib/utils/type_utility'); require('vendor/fuzzaldrin-plus'); (function() { diff --git a/spec/javascripts/u2f/mock_u2f_device.js b/spec/javascripts/u2f/mock_u2f_device.js index 6677fe9c1ee..4eb8ad3d9e4 100644 --- a/spec/javascripts/u2f/mock_u2f_device.js +++ b/spec/javascripts/u2f/mock_u2f_device.js @@ -1,12 +1,10 @@ /* eslint-disable space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-expressions, no-return-assign, no-param-reassign, max-len */ (function() { - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; - this.MockU2FDevice = (function() { function MockU2FDevice() { - this.respondToAuthenticateRequest = bind(this.respondToAuthenticateRequest, this); - this.respondToRegisterRequest = bind(this.respondToRegisterRequest, this); + this.respondToAuthenticateRequest = this.respondToAuthenticateRequest.bind(this); + this.respondToRegisterRequest = this.respondToRegisterRequest.bind(this); window.u2f || (window.u2f = {}); window.u2f.register = (function(_this) { return function(appId, registerRequests, signRequests, callback) { diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js index 3d5f71babfb..2f971b39d16 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js @@ -9,6 +9,7 @@ const deploymentMockData = [ name: 'review/diplo', url: '/root/acets-review-apps/environments/15', stop_url: '/root/acets-review-apps/environments/15/stop', + metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics', external_url: 'http://diplo.', external_url_formatted: 'diplo.', deployed_at: '2017-03-22T22:44:42.258Z', @@ -156,6 +157,7 @@ describe('MRWidgetDeployment', () => { expect(el.querySelector('.js-deploy-url').getAttribute('href')).toEqual(deployment.external_url); expect(el.querySelector('.js-deploy-url').innerText).toContain(deployment.external_url_formatted); expect(el.querySelector('.js-deploy-time').innerText).toContain(vm.formatDate(deployment.deployed_at)); + expect(el.querySelector('.js-mr-memory-usage')).toBeDefined(); expect(el.querySelector('button')).toBeDefined(); }); @@ -165,6 +167,7 @@ describe('MRWidgetDeployment', () => { Vue.nextTick(() => { expect(el.querySelectorAll('.ci-widget').length).toEqual(3); + expect(el.querySelectorAll('.js-mr-memory-usage').length).toEqual(3); done(); }); }); @@ -176,6 +179,7 @@ describe('MRWidgetDeployment', () => { expect(el.querySelectorAll('.js-deploy-meta').length).toEqual(0); expect(el.querySelectorAll('.js-deploy-url').length).toEqual(0); expect(el.querySelectorAll('.js-deploy-time').length).toEqual(0); + expect(el.querySelectorAll('.js-mr-memory-usage').length).toEqual(0); expect(el.querySelectorAll('.button').length).toEqual(0); done(); }); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js new file mode 100644 index 00000000000..da9dff18ada --- /dev/null +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js @@ -0,0 +1,184 @@ +import Vue from 'vue'; +import memoryUsageComponent from '~/vue_merge_request_widget/components/mr_widget_memory_usage'; +import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; + +const url = '/root/acets-review-apps/environments/15/deployments/1/metrics'; + +const metricsMockData = { + success: true, + metrics: { + memory_values: [ + { + metric: {}, + values: [ + [1493716685, '4.30859375'], + ], + }, + ], + }, + last_update: '2017-05-02T12:34:49.628Z', + deployment_time: 1493718485, +}; + +const createComponent = () => { + const Component = Vue.extend(memoryUsageComponent); + + return new Component({ + el: document.createElement('div'), + propsData: { + metricsUrl: url, + memoryMetrics: [], + deploymentTime: 0, + hasMetrics: false, + loadFailed: false, + loadingMetrics: true, + backOffRequestCounter: 0, + }, + }); +}; + +const messages = { + loadingMetrics: 'Loading deployment statistics.', + hasMetrics: 'Deployment memory usage:', + loadFailed: 'Failed to load deployment statistics.', + metricsUnavailable: 'Deployment statistics are not available currently.', +}; + +describe('MemoryUsage', () => { + let vm; + let el; + + beforeEach(() => { + vm = createComponent(); + el = vm.$el; + }); + + describe('props', () => { + it('should have props with defaults', () => { + const { metricsUrl } = memoryUsageComponent.props; + const MetricsUrlTypeClass = metricsUrl.type; + + Vue.nextTick(() => { + expect(new MetricsUrlTypeClass() instanceof String).toBeTruthy(); + expect(metricsUrl.required).toBeTruthy(); + }); + }); + }); + + describe('data', () => { + it('should have default data', () => { + const data = memoryUsageComponent.data(); + + expect(Array.isArray(data.memoryMetrics)).toBeTruthy(); + expect(data.memoryMetrics.length).toBe(0); + + expect(typeof data.deploymentTime).toBe('number'); + expect(data.deploymentTime).toBe(0); + + expect(typeof data.hasMetrics).toBe('boolean'); + expect(data.hasMetrics).toBeFalsy(); + + expect(typeof data.loadFailed).toBe('boolean'); + expect(data.loadFailed).toBeFalsy(); + + expect(typeof data.loadingMetrics).toBe('boolean'); + expect(data.loadingMetrics).toBeTruthy(); + + expect(typeof data.backOffRequestCounter).toBe('number'); + expect(data.backOffRequestCounter).toBe(0); + }); + }); + + describe('methods', () => { + const { metrics, deployment_time } = metricsMockData; + + describe('computeGraphData', () => { + it('should populate sparkline graph', () => { + vm.computeGraphData(metrics, deployment_time); + const { hasMetrics, memoryMetrics, deploymentTime } = vm; + + expect(hasMetrics).toBeTruthy(); + expect(memoryMetrics.length > 0).toBeTruthy(); + expect(deploymentTime).toEqual(deployment_time); + }); + }); + + describe('loadMetrics', () => { + const returnServicePromise = () => new Promise((resolve) => { + resolve({ + json() { + return metricsMockData; + }, + }); + }); + + it('should load metrics data using MRWidgetService', (done) => { + spyOn(MRWidgetService, 'fetchMetrics').and.returnValue(returnServicePromise(true)); + spyOn(vm, 'computeGraphData'); + + vm.loadMetrics(); + setTimeout(() => { + expect(MRWidgetService.fetchMetrics).toHaveBeenCalledWith(url); + expect(vm.computeGraphData).toHaveBeenCalledWith(metrics, deployment_time); + done(); + }, 333); + }); + }); + }); + + describe('template', () => { + it('should render template elements correctly', () => { + expect(el.classList.contains('mr-memory-usage')).toBeTruthy(); + expect(el.querySelector('.js-usage-info')).toBeDefined(); + }); + + it('should show loading metrics message while metrics are being loaded', (done) => { + vm.loadingMetrics = true; + vm.hasMetrics = false; + vm.loadFailed = false; + + Vue.nextTick(() => { + expect(el.querySelector('.js-usage-info.usage-info-loading')).toBeDefined(); + expect(el.querySelector('.js-usage-info .usage-info-load-spinner')).toBeDefined(); + expect(el.querySelector('.js-usage-info').innerText).toContain(messages.loadingMetrics); + done(); + }); + }); + + it('should show deployment memory usage when metrics are loaded', (done) => { + vm.loadingMetrics = false; + vm.hasMetrics = true; + vm.loadFailed = false; + + Vue.nextTick(() => { + expect(el.querySelector('.memory-graph-container')).toBeDefined(); + expect(el.querySelector('.js-usage-info').innerText).toContain(messages.hasMetrics); + done(); + }); + }); + + it('should show failure message when metrics loading failed', (done) => { + vm.loadingMetrics = false; + vm.hasMetrics = false; + vm.loadFailed = true; + + Vue.nextTick(() => { + expect(el.querySelector('.js-usage-info.usage-info-failed')).toBeDefined(); + expect(el.querySelector('.js-usage-info').innerText).toContain(messages.loadFailed); + done(); + }); + }); + + it('should show metrics unavailable message when metrics loading failed', (done) => { + vm.loadingMetrics = false; + vm.hasMetrics = false; + vm.loadFailed = false; + + Vue.nextTick(() => { + expect(el.querySelector('.js-usage-info.usage-info-unavailable')).toBeDefined(); + expect(el.querySelector('.js-usage-info').innerText).toContain(messages.metricsUnavailable); + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/ci_action_icons_spec.js b/spec/javascripts/vue_shared/ci_action_icons_spec.js index 2e89a07e76e..3d53a5ab24d 100644 --- a/spec/javascripts/vue_shared/ci_action_icons_spec.js +++ b/spec/javascripts/vue_shared/ci_action_icons_spec.js @@ -2,6 +2,7 @@ import getActionIcon from '~/vue_shared/ci_action_icons'; import cancelSVG from 'icons/_icon_action_cancel.svg'; import retrySVG from 'icons/_icon_action_retry.svg'; import playSVG from 'icons/_icon_action_play.svg'; +import stopSVG from 'icons/_icon_action_stop.svg'; describe('getActionIcon', () => { it('should return an empty string', () => { @@ -19,4 +20,8 @@ describe('getActionIcon', () => { it('should return play svg', () => { expect(getActionIcon('icon_action_play')).toEqual(playSVG); }); + + it('should render stop svg', () => { + expect(getActionIcon('icon_action_stop')).toEqual(stopSVG); + }); }); diff --git a/spec/javascripts/vue_shared/components/loading_icon_spec.js b/spec/javascripts/vue_shared/components/loading_icon_spec.js new file mode 100644 index 00000000000..1baf3537741 --- /dev/null +++ b/spec/javascripts/vue_shared/components/loading_icon_spec.js @@ -0,0 +1,53 @@ +import Vue from 'vue'; +import loadingIcon from '~/vue_shared/components/loading_icon.vue'; + +describe('Loading Icon Component', () => { + let LoadingIconComponent; + + beforeEach(() => { + LoadingIconComponent = Vue.extend(loadingIcon); + }); + + it('should render a spinner font awesome icon', () => { + const component = new LoadingIconComponent().$mount(); + + expect( + component.$el.querySelector('i').getAttribute('class'), + ).toEqual('fa fa-spin fa-spinner fa-1x'); + + expect(component.$el.tagName).toEqual('DIV'); + expect(component.$el.classList.contains('text-center')).toEqual(true); + }); + + it('should render accessibility attributes', () => { + const component = new LoadingIconComponent().$mount(); + + const icon = component.$el.querySelector('i'); + expect(icon.getAttribute('aria-hidden')).toEqual('true'); + expect(icon.getAttribute('aria-label')).toEqual('Loading'); + }); + + it('should render the provided label', () => { + const component = new LoadingIconComponent({ + propsData: { + label: 'This is a loading icon', + }, + }).$mount(); + + expect( + component.$el.querySelector('i').getAttribute('aria-label'), + ).toEqual('This is a loading icon'); + }); + + it('should render the provided size', () => { + const component = new LoadingIconComponent({ + propsData: { + size: '2', + }, + }).$mount(); + + expect( + component.$el.querySelector('i').classList.contains('fa-2x'), + ).toEqual(true); + }); +}); diff --git a/spec/javascripts/vue_shared/components/memory_graph_spec.js b/spec/javascripts/vue_shared/components/memory_graph_spec.js new file mode 100644 index 00000000000..d46a3f2328e --- /dev/null +++ b/spec/javascripts/vue_shared/components/memory_graph_spec.js @@ -0,0 +1,143 @@ +import Vue from 'vue'; +import memoryGraphComponent from '~/vue_shared/components/memory_graph'; +import { mockMetrics, mockMedian, mockMedianIndex } from './mock_data'; + +const defaultHeight = '25'; +const defaultWidth = '100'; + +const createComponent = () => { + const Component = Vue.extend(memoryGraphComponent); + + return new Component({ + el: document.createElement('div'), + propsData: { + metrics: [], + deploymentTime: 0, + width: '', + height: '', + pathD: '', + pathViewBox: '', + dotX: '', + dotY: '', + }, + }); +}; + +describe('MemoryGraph', () => { + let vm; + let el; + + beforeEach(() => { + vm = createComponent(); + el = vm.$el; + }); + + describe('props', () => { + it('should have props with defaults', (done) => { + const { metrics, deploymentTime, width, height } = memoryGraphComponent.props; + + Vue.nextTick(() => { + const typeClassMatcher = (propItem, expectedType) => { + const PropItemTypeClass = propItem.type; + expect(new PropItemTypeClass() instanceof expectedType).toBeTruthy(); + expect(propItem.required).toBeTruthy(); + }; + + typeClassMatcher(metrics, Array); + typeClassMatcher(deploymentTime, Number); + typeClassMatcher(width, String); + typeClassMatcher(height, String); + done(); + }); + }); + }); + + describe('data', () => { + it('should have default data', () => { + const data = memoryGraphComponent.data(); + const dataValidator = (dataItem, expectedType, defaultVal) => { + expect(typeof dataItem).toBe(expectedType); + expect(dataItem).toBe(defaultVal); + }; + + dataValidator(data.pathD, 'string', ''); + dataValidator(data.pathViewBox, 'string', ''); + dataValidator(data.dotX, 'string', ''); + dataValidator(data.dotY, 'string', ''); + }); + }); + + describe('computed', () => { + describe('getFormattedMedian', () => { + it('should show human readable median value based on provided median timestamp', () => { + vm.deploymentTime = mockMedian; + const formattedMedian = vm.getFormattedMedian; + expect(formattedMedian.indexOf('Deployed') > -1).toBeTruthy(); + expect(formattedMedian.indexOf('ago') > -1).toBeTruthy(); + }); + }); + }); + + describe('methods', () => { + describe('getMedianMetricIndex', () => { + it('should return index of closest metric timestamp to that of median', () => { + const matchingIndex = vm.getMedianMetricIndex(mockMedian, mockMetrics); + expect(matchingIndex).toBe(mockMedianIndex); + }); + }); + + describe('getGraphPlotValues', () => { + it('should return Object containing values to plot graph', () => { + const plotValues = vm.getGraphPlotValues(mockMedian, mockMetrics); + expect(plotValues.pathD).toBeDefined(); + expect(Array.isArray(plotValues.pathD)).toBeTruthy(); + + expect(plotValues.pathViewBox).toBeDefined(); + expect(typeof plotValues.pathViewBox).toBe('object'); + + expect(plotValues.dotX).toBeDefined(); + expect(typeof plotValues.dotX).toBe('number'); + + expect(plotValues.dotY).toBeDefined(); + expect(typeof plotValues.dotY).toBe('number'); + }); + }); + }); + + describe('template', () => { + it('should render template elements correctly', () => { + expect(el.classList.contains('memory-graph-container')).toBeTruthy(); + expect(el.querySelector('svg')).toBeDefined(); + }); + + it('should render graph when renderGraph is called internally', (done) => { + const { pathD, pathViewBox, dotX, dotY } = vm.getGraphPlotValues(mockMedian, mockMetrics); + vm.height = defaultHeight; + vm.width = defaultWidth; + vm.pathD = `M ${pathD}`; + vm.pathViewBox = `0 0 ${pathViewBox.lineWidth} ${pathViewBox.diff}`; + vm.dotX = dotX; + vm.dotY = dotY; + + Vue.nextTick(() => { + const svgEl = el.querySelector('svg'); + expect(svgEl).toBeDefined(); + expect(svgEl.getAttribute('height')).toBe(defaultHeight); + expect(svgEl.getAttribute('width')).toBe(defaultWidth); + + const pathEl = el.querySelector('path'); + expect(pathEl).toBeDefined(); + expect(pathEl.getAttribute('d')).toBe(`M ${pathD}`); + expect(pathEl.getAttribute('viewBox')).toBe(`0 0 ${pathViewBox.lineWidth} ${pathViewBox.diff}`); + + const circleEl = el.querySelector('circle'); + expect(circleEl).toBeDefined(); + expect(circleEl.getAttribute('r')).toBe('1.5'); + expect(circleEl.getAttribute('tranform')).toBe('translate(0 -1)'); + expect(circleEl.getAttribute('cx')).toBe(`${dotX}`); + expect(circleEl.getAttribute('cy')).toBe(`${dotY}`); + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/mock_data.js b/spec/javascripts/vue_shared/components/mock_data.js new file mode 100644 index 00000000000..0d781bdca74 --- /dev/null +++ b/spec/javascripts/vue_shared/components/mock_data.js @@ -0,0 +1,69 @@ +/* eslint-disable */ + +export const mockMetrics = [ + [1493716685, '4.30859375'], + [1493716745, '4.30859375'], + [1493716805, '4.30859375'], + [1493716865, '4.30859375'], + [1493716925, '4.30859375'], + [1493716985, '4.30859375'], + [1493717045, '4.30859375'], + [1493717105, '4.30859375'], + [1493717165, '4.30859375'], + [1493717225, '4.30859375'], + [1493717285, '4.30859375'], + [1493717345, '4.30859375'], + [1493717405, '4.30859375'], + [1493717465, '4.30859375'], + [1493717525, '4.30859375'], + [1493717585, '4.30859375'], + [1493717645, '4.30859375'], + [1493717705, '4.30859375'], + [1493717765, '4.30859375'], + [1493717825, '4.30859375'], + [1493717885, '4.30859375'], + [1493717945, '4.30859375'], + [1493718005, '4.30859375'], + [1493718065, '4.30859375'], + [1493718125, '4.30859375'], + [1493718185, '4.30859375'], + [1493718245, '4.30859375'], + [1493718305, '4.234375'], + [1493718365, '4.234375'], + [1493718425, '4.234375'], + [1493718485, '4.234375'], + [1493718545, '4.243489583333333'], + [1493718605, '4.2109375'], + [1493718665, '4.2109375'], + [1493718725, '4.2109375'], + [1493718785, '4.26171875'], + [1493718845, '4.26171875'], + [1493718905, '4.26171875'], + [1493718965, '4.26171875'], + [1493719025, '4.26171875'], + [1493719085, '4.26171875'], + [1493719145, '4.26171875'], + [1493719205, '4.26171875'], + [1493719265, '4.26171875'], + [1493719325, '4.26171875'], + [1493719385, '4.26171875'], + [1493719445, '4.26171875'], + [1493719505, '4.26171875'], + [1493719565, '4.26171875'], + [1493719625, '4.26171875'], + [1493719685, '4.26171875'], + [1493719745, '4.26171875'], + [1493719805, '4.26171875'], + [1493719865, '4.26171875'], + [1493719925, '4.26171875'], + [1493719985, '4.26171875'], + [1493720045, '4.26171875'], + [1493720105, '4.26171875'], + [1493720165, '4.26171875'], + [1493720225, '4.26171875'], + [1493720285, '4.26171875'], +]; + +export const mockMedian = 1493718485; + +export const mockMedianIndex = 30; diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js index 96038718191..895e1c585b4 100644 --- a/spec/javascripts/vue_shared/components/table_pagination_spec.js +++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import paginationComp from '~/vue_shared/components/table_pagination'; +import paginationComp from '~/vue_shared/components/table_pagination.vue'; import '~/lib/utils/common_utils'; describe('Pagination component', () => { |