diff options
Diffstat (limited to 'spec/javascripts/jobs')
20 files changed, 2850 insertions, 511 deletions
diff --git a/spec/javascripts/jobs/components/artifacts_block_spec.js b/spec/javascripts/jobs/components/artifacts_block_spec.js index a06d287b3fa..2fa7ff653fe 100644 --- a/spec/javascripts/jobs/components/artifacts_block_spec.js +++ b/spec/javascripts/jobs/components/artifacts_block_spec.js @@ -11,6 +11,19 @@ describe('Artifacts block', () => { const timeago = getTimeago(); const formatedDate = timeago.format(expireAt); + const expiredArtifact = { + expire_at: expireAt, + expired: true, + }; + + const nonExpiredArtifact = { + download_path: '/gitlab-org/gitlab-ce/-/jobs/98314558/artifacts/download', + browse_path: '/gitlab-org/gitlab-ce/-/jobs/98314558/artifacts/browse', + keep_path: '/gitlab-org/gitlab-ce/-/jobs/98314558/artifacts/keep', + expire_at: expireAt, + expired: false, + }; + afterEach(() => { vm.$destroy(); }); @@ -18,100 +31,87 @@ describe('Artifacts block', () => { describe('with expired artifacts', () => { it('renders expired artifact date and info', () => { vm = mountComponent(Component, { - haveArtifactsExpired: true, - willArtifactsExpire: false, - expireAt, + artifact: expiredArtifact, }); expect(vm.$el.querySelector('.js-artifacts-removed')).not.toBeNull(); expect(vm.$el.querySelector('.js-artifacts-will-be-removed')).toBeNull(); expect(vm.$el.textContent).toContain(formatedDate); + expect(vm.$el.querySelector('.js-artifacts-removed').textContent.trim()).toEqual( + 'The artifacts were removed', + ); }); }); describe('with artifacts that will expire', () => { it('renders will expire artifact date and info', () => { vm = mountComponent(Component, { - haveArtifactsExpired: false, - willArtifactsExpire: true, - expireAt, + artifact: nonExpiredArtifact, }); expect(vm.$el.querySelector('.js-artifacts-removed')).toBeNull(); expect(vm.$el.querySelector('.js-artifacts-will-be-removed')).not.toBeNull(); expect(vm.$el.textContent).toContain(formatedDate); + expect(vm.$el.querySelector('.js-artifacts-will-be-removed').textContent.trim()).toEqual( + 'The artifacts will be removed in', + ); }); }); - describe('when the user can keep the artifacts', () => { + describe('with keep path', () => { it('renders the keep button', () => { vm = mountComponent(Component, { - haveArtifactsExpired: true, - willArtifactsExpire: false, - expireAt, - keepArtifactsPath: '/keep', + artifact: nonExpiredArtifact, }); expect(vm.$el.querySelector('.js-keep-artifacts')).not.toBeNull(); }); }); - describe('when the user can not keep the artifacts', () => { + describe('without keep path', () => { it('does not render the keep button', () => { vm = mountComponent(Component, { - haveArtifactsExpired: true, - willArtifactsExpire: false, - expireAt, + artifact: expiredArtifact, }); expect(vm.$el.querySelector('.js-keep-artifacts')).toBeNull(); }); }); - describe('when the user can download the artifacts', () => { + describe('with download path', () => { it('renders the download button', () => { vm = mountComponent(Component, { - haveArtifactsExpired: true, - willArtifactsExpire: false, - expireAt, - downloadArtifactsPath: '/download', + artifact: nonExpiredArtifact, }); expect(vm.$el.querySelector('.js-download-artifacts')).not.toBeNull(); }); }); - describe('when the user can not download the artifacts', () => { + describe('without download path', () => { it('does not render the keep button', () => { vm = mountComponent(Component, { - haveArtifactsExpired: true, - willArtifactsExpire: false, - expireAt, + artifact: expiredArtifact, }); expect(vm.$el.querySelector('.js-download-artifacts')).toBeNull(); }); }); - describe('when the user can browse the artifacts', () => { + describe('with browse path', () => { it('does not render the browse button', () => { vm = mountComponent(Component, { - haveArtifactsExpired: true, - willArtifactsExpire: false, - expireAt, - browseArtifactsPath: '/browse', + artifact: nonExpiredArtifact, }); expect(vm.$el.querySelector('.js-browse-artifacts')).not.toBeNull(); }); }); - describe('when the user can not browse the artifacts', () => { + describe('without browse path', () => { it('does not render the browse button', () => { vm = mountComponent(Component, { - haveArtifactsExpired: true, - willArtifactsExpire: false, - expireAt, + artifact: expiredArtifact, }); expect(vm.$el.querySelector('.js-browse-artifacts')).toBeNull(); diff --git a/spec/javascripts/jobs/components/commit_block_spec.js b/spec/javascripts/jobs/components/commit_block_spec.js index e21fa9c2874..0bcc4ff940f 100644 --- a/spec/javascripts/jobs/components/commit_block_spec.js +++ b/spec/javascripts/jobs/components/commit_block_spec.js @@ -7,11 +7,16 @@ describe('Commit block', () => { let vm; const props = { - pipelineShortSha: '1f0fb84f', - pipelineShaPath: 'commit/1f0fb84fb6770d74d97eee58118fd3909cd4f48c', - mergeRequestReference: '!21244', - mergeRequestPath: 'merge_requests/21244', - gitCommitTitlte: 'Regenerate pot files', + commit: { + short_id: '1f0fb84f', + commit_path: 'commit/1f0fb84fb6770d74d97eee58118fd3909cd4f48c', + title: 'Update README.md', + }, + mergeRequest: { + iid: '!21244', + path: 'merge_requests/21244', + }, + isLastBlock: true, }; afterEach(() => { @@ -26,12 +31,18 @@ describe('Commit block', () => { }); it('renders pipeline short sha link', () => { - expect(vm.$el.querySelector('.js-commit-sha').getAttribute('href')).toEqual(props.pipelineShaPath); - expect(vm.$el.querySelector('.js-commit-sha').textContent.trim()).toEqual(props.pipelineShortSha); + expect(vm.$el.querySelector('.js-commit-sha').getAttribute('href')).toEqual( + props.commit.commit_path, + ); + expect(vm.$el.querySelector('.js-commit-sha').textContent.trim()).toEqual( + props.commit.short_id, + ); }); it('renders clipboard button', () => { - expect(vm.$el.querySelector('button').getAttribute('data-clipboard-text')).toEqual(props.pipelineShortSha); + expect(vm.$el.querySelector('button').getAttribute('data-clipboard-text')).toEqual( + props.commit.short_id, + ); }); }); @@ -41,17 +52,19 @@ describe('Commit block', () => { ...props, }); - expect(vm.$el.querySelector('.js-link-commit').getAttribute('href')).toEqual(props.mergeRequestPath); - expect(vm.$el.querySelector('.js-link-commit').textContent.trim()).toEqual(props.mergeRequestReference); - + expect(vm.$el.querySelector('.js-link-commit').getAttribute('href')).toEqual( + props.mergeRequest.path, + ); + expect(vm.$el.querySelector('.js-link-commit').textContent.trim()).toEqual( + `!${props.mergeRequest.iid}`, + ); }); }); describe('without merge request', () => { it('does not render merge request', () => { const copyProps = Object.assign({}, props); - delete copyProps.mergeRequestPath; - delete copyProps.mergeRequestReference; + delete copyProps.mergeRequest; vm = mountComponent(Component, { ...copyProps, @@ -67,7 +80,7 @@ describe('Commit block', () => { ...props, }); - expect(vm.$el.textContent).toContain(props.gitCommitTitlte); + expect(vm.$el.textContent).toContain(props.commit.title); }); }); }); diff --git a/spec/javascripts/jobs/components/empty_state_spec.js b/spec/javascripts/jobs/components/empty_state_spec.js index dcc2b3d8a20..73488eaab9b 100644 --- a/spec/javascripts/jobs/components/empty_state_spec.js +++ b/spec/javascripts/jobs/components/empty_state_spec.js @@ -66,8 +66,8 @@ describe('Empty State', () => { ...props, content, action: { - link: 'runner', - title: 'Check runner', + path: 'runner', + button_title: 'Check runner', method: 'post', }, }); diff --git a/spec/javascripts/jobs/components/environments_block_spec.js b/spec/javascripts/jobs/components/environments_block_spec.js index 015c26be9fc..7d836129b13 100644 --- a/spec/javascripts/jobs/components/environments_block_spec.js +++ b/spec/javascripts/jobs/components/environments_block_spec.js @@ -5,19 +5,16 @@ import mountComponent from '../../helpers/vue_mount_component_helper'; describe('Environments block', () => { const Component = Vue.extend(component); let vm; - const icon = { + const status = { group: 'success', icon: 'status_success', label: 'passed', text: 'passed', tooltip: 'passed', }; - const deployment = { - path: 'deployment', - name: 'deployment name', - }; + const environment = { - path: '/environment', + environment_path: '/environment', name: 'environment', }; @@ -25,15 +22,14 @@ describe('Environments block', () => { vm.$destroy(); }); - describe('with latest deployment', () => { + describe('with last deployment', () => { it('renders info for most recent deployment', () => { vm = mountComponent(Component, { deploymentStatus: { - status: 'latest', - icon, - deployment, + status: 'last', environment, }, + iconStatus: status, }); expect(vm.$el.textContent.trim()).toEqual( @@ -48,17 +44,17 @@ describe('Environments block', () => { vm = mountComponent(Component, { deploymentStatus: { status: 'out_of_date', - icon, - deployment, environment: Object.assign({}, environment, { - last_deployment: { name: 'deployment', path: 'last_deployment' }, + last_deployment: { iid: 'deployment', deployable: { build_path: 'bar' } }, }), }, + iconStatus: status, }); expect(vm.$el.textContent.trim()).toEqual( - 'This job is an out-of-date deployment to environment. View the most recent deployment deployment.', + 'This job is an out-of-date deployment to environment. View the most recent deployment #deployment.', ); + expect(vm.$el.querySelector('.js-job-deployment-link').getAttribute('href')).toEqual('bar'); }); }); @@ -67,10 +63,9 @@ describe('Environments block', () => { vm = mountComponent(Component, { deploymentStatus: { status: 'out_of_date', - icon, - deployment: null, environment, }, + iconStatus: status, }); expect(vm.$el.textContent.trim()).toEqual( @@ -85,10 +80,9 @@ describe('Environments block', () => { vm = mountComponent(Component, { deploymentStatus: { status: 'failed', - icon, - deployment: null, environment, }, + iconStatus: status, }); expect(vm.$el.textContent.trim()).toEqual( @@ -99,21 +93,24 @@ describe('Environments block', () => { describe('creating deployment', () => { describe('with last deployment', () => { - it('renders info about creating deployment and overriding lastest deployment', () => { + it('renders info about creating deployment and overriding latest deployment', () => { vm = mountComponent(Component, { deploymentStatus: { status: 'creating', - icon, - deployment, environment: Object.assign({}, environment, { - last_deployment: { name: 'deployment', path: 'last_deployment' }, + last_deployment: { + iid: 'deployment', + deployable: { build_path: 'foo' }, + }, }), }, + iconStatus: status, }); expect(vm.$el.textContent.trim()).toEqual( - 'This job is creating a deployment to environment and will overwrite the last deployment.', + 'This job is creating a deployment to environment and will overwrite the latest deployment.', ); + expect(vm.$el.querySelector('.js-job-deployment-link').getAttribute('href')).toEqual('foo'); }); }); @@ -122,10 +119,9 @@ describe('Environments block', () => { vm = mountComponent(Component, { deploymentStatus: { status: 'creating', - icon, - deployment: null, environment, }, + iconStatus: status, }); expect(vm.$el.textContent.trim()).toEqual( @@ -133,5 +129,18 @@ describe('Environments block', () => { ); }); }); + + describe('without environment', () => { + it('does not render environment link', () => { + vm = mountComponent(Component, { + deploymentStatus: { + status: 'creating', + environment: null, + }, + iconStatus: status, + }); + expect(vm.$el.querySelector('.js-environment-link')).toBeNull(); + }); + }); }); }); diff --git a/spec/javascripts/jobs/components/erased_block_spec.js b/spec/javascripts/jobs/components/erased_block_spec.js index a4ae0c7c81e..8e0433d3fb7 100644 --- a/spec/javascripts/jobs/components/erased_block_spec.js +++ b/spec/javascripts/jobs/components/erased_block_spec.js @@ -18,9 +18,10 @@ describe('Erased block', () => { describe('with job erased by user', () => { beforeEach(() => { vm = mountComponent(Component, { - erasedByUser: true, - username: 'root', - linkToUser: 'gitlab.com/root', + user: { + username: 'root', + web_url: 'gitlab.com/root', + }, erasedAt, }); }); @@ -40,7 +41,6 @@ describe('Erased block', () => { describe('with erased job', () => { beforeEach(() => { vm = mountComponent(Component, { - erasedByUser: false, erasedAt, }); }); diff --git a/spec/javascripts/jobs/components/header_spec.js b/spec/javascripts/jobs/components/header_spec.js deleted file mode 100644 index e21e2c6d6e3..00000000000 --- a/spec/javascripts/jobs/components/header_spec.js +++ /dev/null @@ -1,98 +0,0 @@ -import Vue from 'vue'; -import headerComponent from '~/jobs/components/header.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -describe('Job details header', () => { - let HeaderComponent; - let vm; - let props; - - beforeEach(() => { - HeaderComponent = Vue.extend(headerComponent); - - const threeWeeksAgo = new Date(); - threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21); - - const twoDaysAgo = new Date(); - twoDaysAgo.setDate(twoDaysAgo.getDate() - 2); - - props = { - job: { - status: { - group: 'failed', - icon: 'status_failed', - label: 'failed', - text: 'failed', - details_path: 'path', - }, - id: 123, - created_at: threeWeeksAgo.toISOString(), - user: { - web_url: 'path', - name: 'Foo', - username: 'foobar', - email: 'foo@bar.com', - avatar_url: 'link', - }, - started: twoDaysAgo.toISOString(), - new_issue_path: 'path', - }, - isLoading: false, - }; - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('job reason', () => { - it('should not render the reason when reason is absent', () => { - vm = mountComponent(HeaderComponent, props); - - expect(vm.shouldRenderReason).toBe(false); - }); - - it('should render the reason when reason is present', () => { - props.job.callout_message = 'There is an unknown failure, please try again'; - - vm = mountComponent(HeaderComponent, props); - - expect(vm.shouldRenderReason).toBe(true); - }); - }); - - describe('triggered job', () => { - beforeEach(() => { - vm = mountComponent(HeaderComponent, props); - }); - - it('should render provided job information', () => { - expect( - vm.$el - .querySelector('.header-main-content') - .textContent.replace(/\s+/g, ' ') - .trim(), - ).toEqual('failed Job #123 triggered 2 days ago by Foo'); - }); - - it('should render new issue link', () => { - expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual( - props.job.new_issue_path, - ); - }); - }); - - describe('created job', () => { - it('should render created key', () => { - props.job.started = false; - vm = mountComponent(HeaderComponent, props); - - expect( - vm.$el - .querySelector('.header-main-content') - .textContent.replace(/\s+/g, ' ') - .trim(), - ).toEqual('failed Job #123 created 3 weeks ago by Foo'); - }); - }); -}); diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js new file mode 100644 index 00000000000..e02eb9723fe --- /dev/null +++ b/spec/javascripts/jobs/components/job_app_spec.js @@ -0,0 +1,324 @@ +import Vue from 'vue'; +import jobApp from '~/jobs/components/job_app.vue'; +import createStore from '~/jobs/store'; +import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; + +describe('Job App ', () => { + const Component = Vue.extend(jobApp); + let store; + let vm; + + const threeWeeksAgo = new Date(); + threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21); + + const twoDaysAgo = new Date(); + twoDaysAgo.setDate(twoDaysAgo.getDate() - 2); + + const job = { + status: { + group: 'failed', + icon: 'status_failed', + label: 'failed', + text: 'failed', + details_path: 'path', + }, + id: 123, + created_at: threeWeeksAgo.toISOString(), + user: { + web_url: 'path', + name: 'Foo', + username: 'foobar', + email: 'foo@bar.com', + avatar_url: 'link', + }, + started: twoDaysAgo.toISOString(), + new_issue_path: 'path', + runners: { + available: false, + }, + tags: ['docker'], + has_trace: true, + }; + + const props = { + runnerHelpUrl: 'help/runners', + }; + + beforeEach(() => { + store = createStore(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('Header section', () => { + describe('job callout message', () => { + it('should not render the reason when reason is absent', () => { + store.dispatch('receiveJobSuccess', job); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.shouldRenderCalloutMessage).toBe(false); + }); + + it('should render the reason when reason is present', () => { + store.dispatch( + 'receiveJobSuccess', + Object.assign({}, job, { + callout_message: 'There is an unknown failure, please try again', + }), + ); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.shouldRenderCalloutMessage).toBe(true); + }); + }); + + describe('triggered job', () => { + beforeEach(() => { + store.dispatch('receiveJobSuccess', job); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + }); + + it('should render provided job information', () => { + expect( + vm.$el + .querySelector('.header-main-content') + .textContent.replace(/\s+/g, ' ') + .trim(), + ).toEqual('failed Job #123 triggered 2 days ago by Foo'); + }); + + it('should render new issue link', () => { + expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual( + job.new_issue_path, + ); + }); + }); + + describe('created job', () => { + it('should render created key', () => { + store.dispatch('receiveJobSuccess', Object.assign({}, job, { started: false })); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect( + vm.$el + .querySelector('.header-main-content') + .textContent.replace(/\s+/g, ' ') + .trim(), + ).toEqual('failed Job #123 created 3 weeks ago by Foo'); + }); + }); + }); + + describe('stuck block', () => { + it('renders stuck block when there are no runners', () => { + store.dispatch( + 'receiveJobSuccess', + Object.assign({}, job, { + status: { + group: 'pending', + icon: 'status_pending', + label: 'pending', + text: 'pending', + details_path: 'path', + }, + }), + ); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-stuck')).not.toBeNull(); + }); + + it('renders tags in stuck block when there are no runners', () => { + store.dispatch( + 'receiveJobSuccess', + Object.assign({}, job, { + status: { + group: 'pending', + icon: 'status_pending', + label: 'pending', + text: 'pending', + details_path: 'path', + }, + }), + ); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]); + }); + + it(' does not renders stuck block when there are no runners', () => { + store.dispatch('receiveJobSuccess', Object.assign({}, job, { runners: { available: true } })); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-stuck')).toBeNull(); + }); + }); + + describe('environments block', () => { + it('renders environment block when job has environment', () => { + store.dispatch( + 'receiveJobSuccess', + Object.assign({}, job, { + deployment_status: { + environment: { + environment_path: '/path', + name: 'foo', + }, + }, + }), + ); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-environment')).not.toBeNull(); + }); + + it('does not render environment block when job has environment', () => { + store.dispatch('receiveJobSuccess', job); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-environment')).toBeNull(); + }); + }); + + describe('erased block', () => { + it('renders erased block when `erased` is true', () => { + store.dispatch( + 'receiveJobSuccess', + Object.assign({}, job, { + erased: true, + erased_by: { + username: 'root', + web_url: 'gitlab.com/root', + }, + erased_at: '2016-11-07T11:11:16.525Z', + }), + ); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-erased')).not.toBeNull(); + }); + + it('does not render erased block when `erased` is false', () => { + store.dispatch('receiveJobSuccess', Object.assign({}, job, { erased: false })); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-erased')).toBeNull(); + }); + }); + + describe('empty states block', () => { + it('renders empty state when job does not have trace and is not running', () => { + store.dispatch( + 'receiveJobSuccess', + Object.assign({}, job, { + has_trace: false, + status: { + group: 'pending', + icon: 'status_pending', + label: 'pending', + text: 'pending', + details_path: 'path', + illustration: { + image: 'path', + size: '340', + title: 'Empty State', + content: 'This is an empty state', + }, + action: { + button_title: 'Retry job', + method: 'post', + path: '/path', + }, + }, + }), + ); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull(); + }); + + it('does not render empty state when job does not have trace but it is running', () => { + store.dispatch( + 'receiveJobSuccess', + Object.assign({}, job, { + has_trace: false, + status: { + group: 'running', + icon: 'status_running', + label: 'running', + text: 'running', + details_path: 'path', + }, + }), + ); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull(); + }); + + it('does not render empty state when job has trace but it is not running', () => { + store.dispatch('receiveJobSuccess', Object.assign({}, job, { has_trace: true })); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull(); + }); + }); +}); diff --git a/spec/javascripts/jobs/components/job_details_mediator_spec.js b/spec/javascripts/jobs/components/job_details_mediator_spec.js deleted file mode 100644 index 3e2fb7bfbbb..00000000000 --- a/spec/javascripts/jobs/components/job_details_mediator_spec.js +++ /dev/null @@ -1,39 +0,0 @@ -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: 'jobs/40291672.json' }); - mock = new MockAdapter(axios); - }); - - afterEach(() => { - mock.restore(); - }); - - it('should set defaults', () => { - expect(mediator.store).toBeDefined(); - expect(mediator.service).toBeDefined(); - expect(mediator.options).toEqual({ endpoint: 'jobs/40291672.json' }); - expect(mediator.state.isLoading).toEqual(false); - }); - - describe('request and store data', () => { - beforeEach(() => { - mock.onGet().reply(200, job, {}); - }); - - it('should store received data', (done) => { - mediator.fetchJob(); - setTimeout(() => { - expect(mediator.store.state.job).toEqual(job); - done(); - }, 0); - }); - }); -}); diff --git a/spec/javascripts/jobs/components/job_log_controllers_spec.js b/spec/javascripts/jobs/components/job_log_controllers_spec.js index 416dfab8a48..099aca602c4 100644 --- a/spec/javascripts/jobs/components/job_log_controllers_spec.js +++ b/spec/javascripts/jobs/components/job_log_controllers_spec.js @@ -10,50 +10,51 @@ describe('Job log controllers', () => { vm.$destroy(); }); - describe('Truncate information', () => { + const props = { + rawPath: '/raw', + erasePath: '/erase', + size: 511952, + isScrollTopDisabled: false, + isScrollBottomDisabled: false, + isScrollingDown: true, + isTraceSizeVisible: true, + }; - beforeEach(() => { - vm = mountComponent(Component, { - rawTracePath: '/raw', - canEraseJob: true, - size: 511952, - canScrollToTop: true, - canScrollToBottom: true, + describe('Truncate information', () => { + describe('with isTraceSizeVisible', () => { + beforeEach(() => { + vm = mountComponent(Component, props); + }); + it('renders size information', () => { + expect(vm.$el.querySelector('.js-truncated-info').textContent).toContain('499.95 KiB'); }); - }); - - it('renders size information', () => { - expect(vm.$el.querySelector('.js-truncated-info').textContent).toContain('499.95 KiB'); - }); - it('renders link to raw trace', () => { - expect(vm.$el.querySelector('.js-raw-link').getAttribute('href')).toEqual('/raw'); + it('renders link to raw trace', () => { + expect(vm.$el.querySelector('.js-raw-link').getAttribute('href')).toEqual('/raw'); + }); }); - }); describe('links section', () => { describe('with raw trace path', () => { it('renders raw trace link', () => { - vm = mountComponent(Component, { - rawTracePath: '/raw', - canEraseJob: true, - size: 511952, - canScrollToTop: true, - canScrollToBottom: true, - }); + vm = mountComponent(Component, props); - expect(vm.$el.querySelector('.js-raw-link-controller').getAttribute('href')).toEqual('/raw'); + expect(vm.$el.querySelector('.js-raw-link-controller').getAttribute('href')).toEqual( + '/raw', + ); }); }); describe('without raw trace path', () => { it('does not render raw trace link', () => { vm = mountComponent(Component, { - canEraseJob: true, + erasePath: '/erase', size: 511952, - canScrollToTop: true, - canScrollToBottom: true, + isScrollTopDisabled: true, + isScrollBottomDisabled: true, + isScrollingDown: false, + isTraceSizeVisible: true, }); expect(vm.$el.querySelector('.js-raw-link-controller')).toBeNull(); @@ -62,52 +63,23 @@ describe('Job log controllers', () => { describe('when is erasable', () => { beforeEach(() => { - vm = mountComponent(Component, { - rawTracePath: '/raw', - canEraseJob: true, - size: 511952, - canScrollToTop: true, - canScrollToBottom: true, - }); + vm = mountComponent(Component, props); }); - it('renders erase job button', () => { + it('renders erase job link', () => { expect(vm.$el.querySelector('.js-erase-link')).not.toBeNull(); }); - - describe('on click', () => { - describe('when user confirms action', () => { - it('emits eraseJob event', () => { - spyOn(window, 'confirm').and.returnValue(true); - spyOn(vm, '$emit'); - - vm.$el.querySelector('.js-erase-link').click(); - - expect(vm.$emit).toHaveBeenCalledWith('eraseJob'); - }); - }); - - describe('when user does not confirm action', () => { - it('does not emit eraseJob event', () => { - spyOn(window, 'confirm').and.returnValue(false); - spyOn(vm, '$emit'); - - vm.$el.querySelector('.js-erase-link').click(); - - expect(vm.$emit).not.toHaveBeenCalledWith('eraseJob'); - }); - }); - }); }); describe('when it is not erasable', () => { it('does not render erase button', () => { vm = mountComponent(Component, { - rawTracePath: '/raw', - canEraseJob: false, + rawPath: '/raw', size: 511952, - canScrollToTop: true, - canScrollToBottom: true, + isScrollTopDisabled: true, + isScrollBottomDisabled: true, + isScrollingDown: false, + isTraceSizeVisible: true, }); expect(vm.$el.querySelector('.js-erase-link')).toBeNull(); @@ -119,13 +91,7 @@ describe('Job log controllers', () => { describe('scroll top button', () => { describe('when user can scroll top', () => { beforeEach(() => { - vm = mountComponent(Component, { - rawTracePath: '/raw', - canEraseJob: true, - size: 511952, - canScrollToTop: true, - canScrollToBottom: true, - }); + vm = mountComponent(Component, props); }); it('renders enabled scroll top button', () => { @@ -143,16 +109,20 @@ describe('Job log controllers', () => { describe('when user can not scroll top', () => { beforeEach(() => { vm = mountComponent(Component, { - rawTracePath: '/raw', - canEraseJob: true, + rawPath: '/raw', + erasePath: '/erase', size: 511952, - canScrollToTop: false, - canScrollToBottom: true, + isScrollTopDisabled: true, + isScrollBottomDisabled: false, + isScrollingDown: false, + isTraceSizeVisible: true, }); }); it('renders disabled scroll top button', () => { - expect(vm.$el.querySelector('.js-scroll-top').getAttribute('disabled')).toEqual('disabled'); + expect(vm.$el.querySelector('.js-scroll-top').getAttribute('disabled')).toEqual( + 'disabled', + ); }); it('does not emit scrollJobLogTop event on click', () => { @@ -167,13 +137,7 @@ describe('Job log controllers', () => { describe('scroll bottom button', () => { describe('when user can scroll bottom', () => { beforeEach(() => { - vm = mountComponent(Component, { - rawTracePath: '/raw', - canEraseJob: true, - size: 511952, - canScrollToTop: true, - canScrollToBottom: true, - }); + vm = mountComponent(Component, props); }); it('renders enabled scroll bottom button', () => { @@ -191,17 +155,20 @@ describe('Job log controllers', () => { describe('when user can not scroll bottom', () => { beforeEach(() => { vm = mountComponent(Component, { - rawTracePath: '/raw', - canEraseJob: true, + rawPath: '/raw', + erasePath: '/erase', size: 511952, - canScrollToTop: true, - canScrollToBottom: false, + isScrollTopDisabled: false, + isScrollBottomDisabled: true, + isScrollingDown: false, + isTraceSizeVisible: true, }); }); it('renders disabled scroll bottom button', () => { - expect(vm.$el.querySelector('.js-scroll-bottom').getAttribute('disabled')).toEqual('disabled'); - + expect(vm.$el.querySelector('.js-scroll-bottom').getAttribute('disabled')).toEqual( + 'disabled', + ); }); it('does not emit scrollJobLogBottom event on click', () => { @@ -211,7 +178,29 @@ describe('Job log controllers', () => { expect(vm.$emit).not.toHaveBeenCalledWith('scrollJobLogBottom'); }); }); + + describe('while isScrollingDown is true', () => { + it('renders animate class for the scroll down button', () => { + vm = mountComponent(Component, props); + + expect(vm.$el.querySelector('.js-scroll-bottom').className).toContain('animate'); + }); + }); + + describe('while isScrollingDown is false', () => { + it('does not render animate class for the scroll down button', () => { + vm = mountComponent(Component, { + rawPath: '/raw', + erasePath: '/erase', + size: 511952, + isScrollTopDisabled: true, + isScrollBottomDisabled: false, + isScrollingDown: false, + isTraceSizeVisible: true, + }); + expect(vm.$el.querySelector('.js-scroll-bottom').className).not.toContain('animate'); + }); + }); }); }); }); - diff --git a/spec/javascripts/jobs/components/job_log_spec.js b/spec/javascripts/jobs/components/job_log_spec.js index 6a5b375a26c..1011512360d 100644 --- a/spec/javascripts/jobs/components/job_log_spec.js +++ b/spec/javascripts/jobs/components/job_log_spec.js @@ -15,7 +15,7 @@ describe('Job Log', () => { it('renders provided trace', () => { vm = mountComponent(Component, { trace, - isReceivingBuildTrace: true, + isComplete: true, }); expect(vm.$el.querySelector('code').textContent).toContain('Running with gitlab-runner 11.1.0 (081978aa)'); @@ -25,7 +25,7 @@ describe('Job Log', () => { it('renders animation', () => { vm = mountComponent(Component, { trace, - isReceivingBuildTrace: true, + isComplete: true, }); expect(vm.$el.querySelector('.js-log-animation')).not.toBeNull(); @@ -36,7 +36,7 @@ describe('Job Log', () => { it('does not render animation', () => { vm = mountComponent(Component, { trace, - isReceivingBuildTrace: false, + isComplete: false, }); expect(vm.$el.querySelector('.js-log-animation')).toBeNull(); diff --git a/spec/javascripts/jobs/components/job_store_spec.js b/spec/javascripts/jobs/components/job_store_spec.js deleted file mode 100644 index 0dad5111b32..00000000000 --- a/spec/javascripts/jobs/components/job_store_spec.js +++ /dev/null @@ -1,26 +0,0 @@ -import JobStore from '~/jobs/stores/job_store'; -import job from '../mock_data'; - -describe('Job Store', () => { - let store; - - beforeEach(() => { - store = new JobStore(); - }); - - it('should set defaults', () => { - expect(store.state.job).toEqual({}); - }); - - describe('storeJob', () => { - it('should store empty object if none is provided', () => { - store.storeJob(); - expect(store.state.job).toEqual({}); - }); - - it('should store provided argument', () => { - store.storeJob(job); - expect(store.state.job).toEqual(job); - }); - }); -}); diff --git a/spec/javascripts/jobs/components/jobs_container_spec.js b/spec/javascripts/jobs/components/jobs_container_spec.js index f3f8ff0d031..fa3a2c4c266 100644 --- a/spec/javascripts/jobs/components/jobs_container_spec.js +++ b/spec/javascripts/jobs/components/jobs_container_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import component from '~/jobs/components/jobs_container.vue'; import mountComponent from '../../helpers/vue_mount_component_helper'; -describe('Artifacts block', () => { +describe('Jobs List block', () => { const Component = Vue.extend(component); let vm; @@ -16,8 +16,7 @@ describe('Artifacts block', () => { text: 'passed', tooltip: 'passed', }, - path: 'job/233432756', - id: '233432756', + id: 233432756, tooltip: 'build - passed', retried: true, }; @@ -33,8 +32,7 @@ describe('Artifacts block', () => { text: 'passed', tooltip: 'passed', }, - path: 'job/2322756', - id: '2322756', + id: 2322756, tooltip: 'build - passed', active: true, }; @@ -50,8 +48,7 @@ describe('Artifacts block', () => { text: 'passed', tooltip: 'passed', }, - path: 'job/232153', - id: '232153', + id: 232153, tooltip: 'build - passed', }; @@ -62,14 +59,16 @@ describe('Artifacts block', () => { it('renders list of jobs', () => { vm = mountComponent(Component, { jobs: [job, retried, active], + jobId: 12313, }); expect(vm.$el.querySelectorAll('a').length).toEqual(3); }); - it('renders arrow right when job is active', () => { + it('renders arrow right when job id matches `jobId`', () => { vm = mountComponent(Component, { jobs: [active], + jobId: active.id, }); expect(vm.$el.querySelector('a .js-arrow-right')).not.toBeNull(); @@ -78,6 +77,7 @@ describe('Artifacts block', () => { it('does not render arrow right when job is not active', () => { vm = mountComponent(Component, { jobs: [job], + jobId: active.id, }); expect(vm.$el.querySelector('a .js-arrow-right')).toBeNull(); @@ -86,6 +86,7 @@ describe('Artifacts block', () => { it('renders job name when present', () => { vm = mountComponent(Component, { jobs: [job], + jobId: active.id, }); expect(vm.$el.querySelector('a').textContent.trim()).toContain(job.name); @@ -95,6 +96,7 @@ describe('Artifacts block', () => { it('renders job id when job name is not available', () => { vm = mountComponent(Component, { jobs: [retried], + jobId: active.id, }); expect(vm.$el.querySelector('a').textContent.trim()).toContain(retried.id); @@ -103,14 +105,16 @@ describe('Artifacts block', () => { it('links to the job page', () => { vm = mountComponent(Component, { jobs: [job], + jobId: active.id, }); - expect(vm.$el.querySelector('a').getAttribute('href')).toEqual(job.path); + expect(vm.$el.querySelector('a').getAttribute('href')).toEqual(job.status.details_path); }); it('renders retry icon when job was retried', () => { vm = mountComponent(Component, { jobs: [retried], + jobId: active.id, }); expect(vm.$el.querySelector('.js-retry-icon')).not.toBeNull(); @@ -119,6 +123,7 @@ describe('Artifacts block', () => { it('does not render retry icon when job was not retried', () => { vm = mountComponent(Component, { jobs: [job], + jobId: active.id, }); expect(vm.$el.querySelector('.js-retry-icon')).toBeNull(); diff --git a/spec/javascripts/jobs/components/sidebar_details_block_spec.js b/spec/javascripts/jobs/components/sidebar_details_block_spec.js deleted file mode 100644 index ba19534dac2..00000000000 --- a/spec/javascripts/jobs/components/sidebar_details_block_spec.js +++ /dev/null @@ -1,139 +0,0 @@ -import Vue from 'vue'; -import sidebarDetailsBlock from '~/jobs/components/sidebar_details_block.vue'; -import job from '../mock_data'; -import mountComponent from '../../helpers/vue_mount_component_helper'; - -describe('Sidebar details block', () => { - let SidebarComponent; - let vm; - - function trimWhitespace(element) { - return element.textContent.replace(/\s+/g, ' ').trim(); - } - - beforeEach(() => { - SidebarComponent = Vue.extend(sidebarDetailsBlock); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('when it is loading', () => { - it('should render a loading spinner', () => { - vm = mountComponent(SidebarComponent, { - job: {}, - isLoading: true, - }); - expect(vm.$el.querySelector('.fa-spinner')).toBeDefined(); - }); - }); - - describe('when there is no retry path retry', () => { - it('should not render a retry button', () => { - vm = mountComponent(SidebarComponent, { - job: {}, - isLoading: false, - }); - - expect(vm.$el.querySelector('.js-retry-job')).toBeNull(); - }); - }); - - describe('without terminal path', () => { - it('does not render terminal link', () => { - vm = mountComponent(SidebarComponent, { - job, - isLoading: false, - }); - - expect(vm.$el.querySelector('.js-terminal-link')).toBeNull(); - }); - }); - - describe('with terminal path', () => { - it('renders terminal link', () => { - vm = mountComponent(SidebarComponent, { - job, - isLoading: false, - terminalPath: 'job/43123/terminal', - }); - - expect(vm.$el.querySelector('.js-terminal-link')).not.toBeNull(); - }); - }); - - beforeEach(() => { - vm = mountComponent(SidebarComponent, { - job, - isLoading: false, - }); - }); - - describe('actions', () => { - it('should render link to new issue', () => { - expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual( - job.new_issue_path, - ); - expect(vm.$el.querySelector('.js-new-issue').textContent.trim()).toEqual('New issue'); - }); - - it('should render link to retry job', () => { - expect(vm.$el.querySelector('.js-retry-job').getAttribute('href')).toEqual(job.retry_path); - }); - - it('should render link to cancel job', () => { - expect(vm.$el.querySelector('.js-cancel-job').getAttribute('href')).toEqual(job.cancel_path); - }); - }); - - describe('information', () => { - it('should render merge request link', () => { - expect(trimWhitespace(vm.$el.querySelector('.js-job-mr'))).toEqual('Merge Request: !2'); - - expect(vm.$el.querySelector('.js-job-mr a').getAttribute('href')).toEqual( - job.merge_request.path, - ); - }); - - it('should render job duration', () => { - expect(trimWhitespace(vm.$el.querySelector('.js-job-duration'))).toEqual( - 'Duration: 6 seconds', - ); - }); - - it('should render erased date', () => { - expect(trimWhitespace(vm.$el.querySelector('.js-job-erased'))).toEqual('Erased: 3 weeks ago'); - }); - - it('should render finished date', () => { - expect(trimWhitespace(vm.$el.querySelector('.js-job-finished'))).toEqual( - 'Finished: 3 weeks ago', - ); - }); - - it('should render queued date', () => { - expect(trimWhitespace(vm.$el.querySelector('.js-job-queued'))).toEqual('Queued: 9 seconds'); - }); - - it('should render runner ID', () => { - expect(trimWhitespace(vm.$el.querySelector('.js-job-runner'))).toEqual( - 'Runner: local ci runner (#1)', - ); - }); - - it('should render timeout information', () => { - expect(trimWhitespace(vm.$el.querySelector('.js-job-timeout'))).toEqual( - 'Timeout: 1m 40s (from runner)', - ); - }); - - it('should render coverage', () => { - expect(trimWhitespace(vm.$el.querySelector('.js-job-coverage'))).toEqual('Coverage: 20%'); - }); - - it('should render tags', () => { - expect(trimWhitespace(vm.$el.querySelector('.js-job-tags'))).toEqual('Tags: tag'); - }); - }); -}); diff --git a/spec/javascripts/jobs/components/sidebar_spec.js b/spec/javascripts/jobs/components/sidebar_spec.js new file mode 100644 index 00000000000..2f5c4245ced --- /dev/null +++ b/spec/javascripts/jobs/components/sidebar_spec.js @@ -0,0 +1,196 @@ +import Vue from 'vue'; +import sidebarDetailsBlock from '~/jobs/components/sidebar.vue'; +import createStore from '~/jobs/store'; +import job, { stages, jobsInStage } from '../mock_data'; +import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper'; +import { trimText } from '../../helpers/vue_component_helper'; + +describe('Sidebar details block', () => { + const SidebarComponent = Vue.extend(sidebarDetailsBlock); + let vm; + let store; + + beforeEach(() => { + store = createStore(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('when it is loading', () => { + it('should render a loading spinner', () => { + store.dispatch('requestJob'); + vm = mountComponentWithStore(SidebarComponent, { store }); + + expect(vm.$el.querySelector('.fa-spinner')).toBeDefined(); + }); + }); + + describe('when there is no retry path retry', () => { + it('should not render a retry button', () => { + const copy = Object.assign({}, job); + delete copy.retry_path; + + store.dispatch('receiveJobSuccess', copy); + vm = mountComponentWithStore(SidebarComponent, { + store, + }); + + expect(vm.$el.querySelector('.js-retry-job')).toBeNull(); + }); + }); + + describe('without terminal path', () => { + it('does not render terminal link', () => { + store.dispatch('receiveJobSuccess', job); + vm = mountComponentWithStore(SidebarComponent, { store }); + + expect(vm.$el.querySelector('.js-terminal-link')).toBeNull(); + }); + }); + + describe('with terminal path', () => { + it('renders terminal link', () => { + store.dispatch('receiveJobSuccess', job); + vm = mountComponentWithStore(SidebarComponent, { + store, + props: { + terminalPath: 'job/43123/terminal', + }, + }); + + expect(vm.$el.querySelector('.js-terminal-link')).not.toBeNull(); + }); + }); + + beforeEach(() => { + store.dispatch('receiveJobSuccess', job); + vm = mountComponentWithStore(SidebarComponent, { store }); + }); + + describe('actions', () => { + it('should render link to new issue', () => { + expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual( + job.new_issue_path, + ); + expect(vm.$el.querySelector('.js-new-issue').textContent.trim()).toEqual('New issue'); + }); + + it('should render link to retry job', () => { + expect(vm.$el.querySelector('.js-retry-job').getAttribute('href')).toEqual(job.retry_path); + }); + + it('should render link to cancel job', () => { + expect(vm.$el.querySelector('.js-cancel-job').getAttribute('href')).toEqual(job.cancel_path); + }); + }); + + describe('information', () => { + it('should render merge request link', () => { + expect(trimText(vm.$el.querySelector('.js-job-mr').textContent)).toEqual('Merge Request: !2'); + + expect(vm.$el.querySelector('.js-job-mr a').getAttribute('href')).toEqual( + job.merge_request.path, + ); + }); + + it('should render job duration', () => { + expect(trimText(vm.$el.querySelector('.js-job-duration').textContent)).toEqual( + 'Duration: 6 seconds', + ); + }); + + it('should render erased date', () => { + expect(trimText(vm.$el.querySelector('.js-job-erased').textContent)).toEqual( + 'Erased: 3 weeks ago', + ); + }); + + it('should render finished date', () => { + expect(trimText(vm.$el.querySelector('.js-job-finished').textContent)).toEqual( + 'Finished: 3 weeks ago', + ); + }); + + it('should render queued date', () => { + expect(trimText(vm.$el.querySelector('.js-job-queued').textContent)).toEqual( + 'Queued: 9 seconds', + ); + }); + + it('should render runner ID', () => { + expect(trimText(vm.$el.querySelector('.js-job-runner').textContent)).toEqual( + 'Runner: local ci runner (#1)', + ); + }); + + it('should render timeout information', () => { + expect(trimText(vm.$el.querySelector('.js-job-timeout').textContent)).toEqual( + 'Timeout: 1m 40s (from runner)', + ); + }); + + it('should render coverage', () => { + expect(trimText(vm.$el.querySelector('.js-job-coverage').textContent)).toEqual( + 'Coverage: 20%', + ); + }); + + it('should render tags', () => { + expect(trimText(vm.$el.querySelector('.js-job-tags').textContent)).toEqual('Tags: tag'); + }); + }); + + describe('stages dropdown', () => { + beforeEach(() => { + store.dispatch('receiveJobSuccess', job); + }); + + describe('while fetching stages', () => { + it('renders dropdown with More label', () => { + vm = mountComponentWithStore(SidebarComponent, { store }); + + expect(vm.$el.querySelector('.js-selected-stage').textContent.trim()).toEqual('More'); + }); + }); + + describe('with stages', () => { + beforeEach(() => { + store.dispatch('receiveStagesSuccess', stages); + vm = mountComponentWithStore(SidebarComponent, { store }); + }); + + it('renders first stage as selected', () => { + expect(vm.$el.querySelector('.js-selected-stage').textContent.trim()).toEqual( + stages[0].name, + ); + }); + }); + + describe('without jobs for stages', () => { + beforeEach(() => { + store.dispatch('receiveJobSuccess', job); + store.dispatch('receiveStagesSuccess', stages); + vm = mountComponentWithStore(SidebarComponent, { store }); + }); + + it('does not render job container', () => { + expect(vm.$el.querySelector('.js-jobs-container')).toBeNull(); + }); + }); + + describe('with jobs for stages', () => { + beforeEach(() => { + store.dispatch('receiveJobSuccess', job); + store.dispatch('receiveStagesSuccess', stages); + store.dispatch('receiveJobsForStageSuccess', jobsInStage.latest_statuses); + vm = mountComponentWithStore(SidebarComponent, { store }); + }); + + it('renders list of jobs', () => { + expect(vm.$el.querySelector('.js-jobs-container')).not.toBeNull(); + }); + }); + }); +}); diff --git a/spec/javascripts/jobs/components/stages_dropdown_spec.js b/spec/javascripts/jobs/components/stages_dropdown_spec.js index 402289345aa..aa6cc0f1b1a 100644 --- a/spec/javascripts/jobs/components/stages_dropdown_spec.js +++ b/spec/javascripts/jobs/components/stages_dropdown_spec.js @@ -8,10 +8,25 @@ describe('Artifacts block', () => { beforeEach(() => { vm = mountComponent(Component, { - pipelineId: 28029444, - pipelinePath: 'pipeline/28029444', - pipelineRef: '50101-truncated-job-information', - pipelineRefPath: 'commits/50101-truncated-job-information', + pipeline: { + id: 28029444, + details: { + status: { + details_path: '/gitlab-org/gitlab-ce/pipelines/28029444', + group: 'success', + has_details: true, + icon: 'status_success', + label: 'passed', + text: 'passed', + tooltip: 'passed', + }, + }, + path: 'pipeline/28029444', + }, + ref: { + path: 'commits/50101-truncated-job-information', + name: '50101-truncated-job-information', + }, stages: [ { name: 'build', @@ -20,15 +35,6 @@ describe('Artifacts block', () => { name: 'test', }, ], - pipelineStatus: { - details_path: '/gitlab-org/gitlab-ce/pipelines/28029444', - group: 'success', - has_details: true, - icon: 'status_success', - label: 'passed', - text: 'passed', - tooltip: 'passed', - }, }); }); diff --git a/spec/javascripts/jobs/components/trigger_value_spec.js b/spec/javascripts/jobs/components/trigger_block_spec.js index 3d41a3cfac1..e1b9898393e 100644 --- a/spec/javascripts/jobs/components/trigger_value_spec.js +++ b/spec/javascripts/jobs/components/trigger_block_spec.js @@ -13,7 +13,9 @@ describe('Trigger block', () => { describe('with short token', () => { it('renders short token', () => { vm = mountComponent(Component, { - shortToken: '0a666b2', + trigger: { + short_token: '0a666b2', + }, }); expect(vm.$el.querySelector('.js-short-token').textContent).toContain('0a666b2'); @@ -22,7 +24,7 @@ describe('Trigger block', () => { describe('without short token', () => { it('does not render short token', () => { - vm = mountComponent(Component, {}); + vm = mountComponent(Component, { trigger: {} }); expect(vm.$el.querySelector('.js-short-token')).toBeNull(); }); @@ -32,9 +34,12 @@ describe('Trigger block', () => { describe('reveal variables', () => { it('reveals variables on click', done => { vm = mountComponent(Component, { - variables: { - key: 'value', - variable: 'foo', + trigger: { + short_token: 'bd7e', + variables: [ + { key: 'UPLOAD_TO_GCS', value: 'false', public: false }, + { key: 'UPLOAD_TO_S3', value: 'true', public: false }, + ], }, }); @@ -44,10 +49,10 @@ describe('Trigger block', () => { .$nextTick() .then(() => { expect(vm.$el.querySelector('.js-build-variables')).not.toBeNull(); - expect(vm.$el.querySelector('.js-build-variables').textContent).toContain('key'); - expect(vm.$el.querySelector('.js-build-variables').textContent).toContain('value'); - expect(vm.$el.querySelector('.js-build-variables').textContent).toContain('variable'); - expect(vm.$el.querySelector('.js-build-variables').textContent).toContain('foo'); + expect(vm.$el.querySelector('.js-build-variables').textContent).toContain('UPLOAD_TO_GCS'); + expect(vm.$el.querySelector('.js-build-variables').textContent).toContain('false'); + expect(vm.$el.querySelector('.js-build-variables').textContent).toContain('UPLOAD_TO_S3'); + expect(vm.$el.querySelector('.js-build-variables').textContent).toContain('true'); }) .then(done) .catch(done.fail); @@ -57,7 +62,7 @@ describe('Trigger block', () => { describe('without variables', () => { it('does not render variables', () => { - vm = mountComponent(Component); + vm = mountComponent(Component, { trigger: {} }); expect(vm.$el.querySelector('.js-reveal-variables')).toBeNull(); expect(vm.$el.querySelector('.js-build-variables')).toBeNull(); diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js index 8fdd9b309b7..4269b42e8b6 100644 --- a/spec/javascripts/jobs/mock_data.js +++ b/spec/javascripts/jobs/mock_data.js @@ -20,7 +20,8 @@ export default { group: 'success', has_details: true, details_path: '/root/ci-mock/-/jobs/4757', - favicon: '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', + favicon: + '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', action: { icon: 'retry', title: 'Retry', @@ -37,7 +38,8 @@ export default { username: 'root', id: 1, state: 'active', - avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', web_url: 'http://localhost:3000/root', }, erase_path: '/root/ci-mock/-/jobs/4757/erase', @@ -54,7 +56,8 @@ export default { username: 'root', id: 1, state: 'active', - avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', web_url: 'http://localhost:3000/root', }, active: false, @@ -78,7 +81,8 @@ export default { group: 'success', has_details: true, details_path: '/root/ci-mock/pipelines/140', - favicon: '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', + favicon: + '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', }, duration: 6, finished_at: '2017-06-01T17:32:00.042Z', @@ -107,11 +111,14 @@ export default { username: 'root', id: 1, state: 'active', - avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', web_url: 'http://localhost:3000/root', }, - author_gravatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', - commit_url: 'http://localhost:3000/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', + author_gravatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + commit_url: + 'http://localhost:3000/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', }, }, @@ -125,3 +132,1029 @@ export default { }, raw_path: '/root/ci-mock/builds/4757/raw', }; + +export const stages = [ + { + name: 'build', + title: 'build: running', + groups: [ + { + name: 'build:linux', + size: 1, + status: { + icon: 'status_pending', + text: 'pending', + label: 'pending', + group: 'pending', + tooltip: 'pending', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/1180', + illustration: { + image: 'illustrations/pending_job_empty.svg', + size: 'svg-430', + title: 'This job has not started yet', + content: 'This job is in pending state and is waiting to be picked by a runner', + }, + favicon: + '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', + method: 'post', + }, + }, + jobs: [ + { + id: 1180, + name: 'build:linux', + started: false, + build_path: '/gitlab-org/gitlab-shell/-/jobs/1180', + cancel_path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', + playable: false, + created_at: '2018-09-28T11:09:57.229Z', + updated_at: '2018-09-28T11:09:57.503Z', + status: { + icon: 'status_pending', + text: 'pending', + label: 'pending', + group: 'pending', + tooltip: 'pending', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/1180', + illustration: { + image: 'illustrations/pending_job_empty.svg', + size: 'svg-430', + title: 'This job has not started yet', + content: 'This job is in pending state and is waiting to be picked by a runner', + }, + favicon: + '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', + method: 'post', + }, + }, + }, + ], + }, + { + name: 'build:osx', + size: 1, + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/444', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', + method: 'post', + }, + }, + jobs: [ + { + id: 444, + name: 'build:osx', + started: '2018-05-18T05:32:20.655Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/444', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', + playable: false, + created_at: '2018-05-18T15:32:54.364Z', + updated_at: '2018-05-18T15:32:54.364Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/444', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', + method: 'post', + }, + }, + }, + ], + }, + ], + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + tooltip: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/pipelines/27#build', + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png', + }, + path: '/gitlab-org/gitlab-shell/pipelines/27#build', + dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=build', + }, + { + name: 'test', + title: 'test: passed with warnings', + groups: [ + { + name: 'jenkins', + size: 1, + status: { + icon: 'status_success', + text: 'passed', + label: null, + group: 'success', + tooltip: null, + has_details: false, + details_path: null, + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + }, + jobs: [ + { + id: 459, + name: 'jenkins', + started: '2018-05-18T09:32:20.658Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/459', + playable: false, + created_at: '2018-05-18T15:32:55.330Z', + updated_at: '2018-05-18T15:32:55.330Z', + status: { + icon: 'status_success', + text: 'passed', + label: null, + group: 'success', + tooltip: null, + has_details: false, + details_path: null, + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + }, + }, + ], + }, + { + name: 'rspec:linux', + size: 3, + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: false, + details_path: null, + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + }, + jobs: [ + { + id: 445, + name: 'rspec:linux 0 3', + started: '2018-05-18T07:32:20.655Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/445', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/445/retry', + playable: false, + created_at: '2018-05-18T15:32:54.425Z', + updated_at: '2018-05-18T15:32:54.425Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/445', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/445/retry', + method: 'post', + }, + }, + }, + { + id: 446, + name: 'rspec:linux 1 3', + started: '2018-05-18T07:32:20.655Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/446', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/446/retry', + playable: false, + created_at: '2018-05-18T15:32:54.506Z', + updated_at: '2018-05-18T15:32:54.506Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/446', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/446/retry', + method: 'post', + }, + }, + }, + { + id: 447, + name: 'rspec:linux 2 3', + started: '2018-05-18T07:32:20.656Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/447', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/447/retry', + playable: false, + created_at: '2018-05-18T15:32:54.572Z', + updated_at: '2018-05-18T15:32:54.572Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/447', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/447/retry', + method: 'post', + }, + }, + }, + ], + }, + { + name: 'rspec:osx', + size: 1, + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/452', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/452/retry', + method: 'post', + }, + }, + jobs: [ + { + id: 452, + name: 'rspec:osx', + started: '2018-05-18T07:32:20.657Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/452', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/452/retry', + playable: false, + created_at: '2018-05-18T15:32:54.920Z', + updated_at: '2018-05-18T15:32:54.920Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/452', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/452/retry', + method: 'post', + }, + }, + }, + ], + }, + { + name: 'rspec:windows', + size: 3, + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: false, + details_path: null, + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + }, + jobs: [ + { + id: 448, + name: 'rspec:windows 0 3', + started: '2018-05-18T07:32:20.656Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/448', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/448/retry', + playable: false, + created_at: '2018-05-18T15:32:54.639Z', + updated_at: '2018-05-18T15:32:54.639Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/448', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/448/retry', + method: 'post', + }, + }, + }, + { + id: 449, + name: 'rspec:windows 1 3', + started: '2018-05-18T07:32:20.656Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/449', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/449/retry', + playable: false, + created_at: '2018-05-18T15:32:54.703Z', + updated_at: '2018-05-18T15:32:54.703Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/449', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/449/retry', + method: 'post', + }, + }, + }, + { + id: 451, + name: 'rspec:windows 2 3', + started: '2018-05-18T07:32:20.657Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/451', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/451/retry', + playable: false, + created_at: '2018-05-18T15:32:54.853Z', + updated_at: '2018-05-18T15:32:54.853Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/451', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/451/retry', + method: 'post', + }, + }, + }, + ], + }, + { + name: 'spinach:linux', + size: 1, + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/453', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/453/retry', + method: 'post', + }, + }, + jobs: [ + { + id: 453, + name: 'spinach:linux', + started: '2018-05-18T07:32:20.657Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/453', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/453/retry', + playable: false, + created_at: '2018-05-18T15:32:54.993Z', + updated_at: '2018-05-18T15:32:54.993Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/453', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/453/retry', + method: 'post', + }, + }, + }, + ], + }, + { + name: 'spinach:osx', + size: 1, + status: { + icon: 'status_warning', + text: 'failed', + label: 'failed (allowed to fail)', + group: 'failed_with_warnings', + tooltip: 'failed - (unknown failure) (allowed to fail)', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/454', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/454/retry', + method: 'post', + }, + }, + jobs: [ + { + id: 454, + name: 'spinach:osx', + started: '2018-05-18T07:32:20.657Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/454', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/454/retry', + playable: false, + created_at: '2018-05-18T15:32:55.053Z', + updated_at: '2018-05-18T15:32:55.053Z', + status: { + icon: 'status_warning', + text: 'failed', + label: 'failed (allowed to fail)', + group: 'failed_with_warnings', + tooltip: 'failed - (unknown failure) (allowed to fail)', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/454', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/454/retry', + method: 'post', + }, + }, + callout_message: 'There is an unknown failure, please try again', + recoverable: true, + }, + ], + }, + ], + status: { + icon: 'status_warning', + text: 'passed', + label: 'passed with warnings', + group: 'success_with_warnings', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/pipelines/27#test', + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + }, + path: '/gitlab-org/gitlab-shell/pipelines/27#test', + dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=test', + }, + { + name: 'deploy', + title: 'deploy: running', + groups: [ + { + name: 'production', + size: 1, + status: { + icon: 'status_created', + text: 'created', + label: 'created', + group: 'created', + tooltip: 'created', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/457', + illustration: { + image: 'illustrations/job_not_triggered.svg', + size: 'svg-306', + title: 'This job has not been triggered yet', + content: + 'This job depends on upstream jobs that need to succeed in order for this job to be triggered', + }, + favicon: + '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/gitlab-org/gitlab-shell/-/jobs/457/cancel', + method: 'post', + }, + }, + jobs: [ + { + id: 457, + name: 'production', + started: false, + build_path: '/gitlab-org/gitlab-shell/-/jobs/457', + cancel_path: '/gitlab-org/gitlab-shell/-/jobs/457/cancel', + playable: false, + created_at: '2018-05-18T15:32:55.259Z', + updated_at: '2018-09-28T11:09:57.454Z', + status: { + icon: 'status_created', + text: 'created', + label: 'created', + group: 'created', + tooltip: 'created', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/457', + illustration: { + image: 'illustrations/job_not_triggered.svg', + size: 'svg-306', + title: 'This job has not been triggered yet', + content: + 'This job depends on upstream jobs that need to succeed in order for this job to be triggered', + }, + favicon: + '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/gitlab-org/gitlab-shell/-/jobs/457/cancel', + method: 'post', + }, + }, + }, + ], + }, + { + name: 'staging', + size: 1, + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/455', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/455/retry', + method: 'post', + }, + }, + jobs: [ + { + id: 455, + name: 'staging', + started: '2018-05-18T09:32:20.658Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/455', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/455/retry', + playable: false, + created_at: '2018-05-18T15:32:55.119Z', + updated_at: '2018-05-18T15:32:55.119Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/455', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/455/retry', + method: 'post', + }, + }, + }, + ], + }, + { + name: 'stop staging', + size: 1, + status: { + icon: 'status_created', + text: 'created', + label: 'created', + group: 'created', + tooltip: 'created', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/456', + illustration: { + image: 'illustrations/job_not_triggered.svg', + size: 'svg-306', + title: 'This job has not been triggered yet', + content: + 'This job depends on upstream jobs that need to succeed in order for this job to be triggered', + }, + favicon: + '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/gitlab-org/gitlab-shell/-/jobs/456/cancel', + method: 'post', + }, + }, + jobs: [ + { + id: 456, + name: 'stop staging', + started: false, + build_path: '/gitlab-org/gitlab-shell/-/jobs/456', + cancel_path: '/gitlab-org/gitlab-shell/-/jobs/456/cancel', + playable: false, + created_at: '2018-05-18T15:32:55.205Z', + updated_at: '2018-09-28T11:09:57.396Z', + status: { + icon: 'status_created', + text: 'created', + label: 'created', + group: 'created', + tooltip: 'created', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/456', + illustration: { + image: 'illustrations/job_not_triggered.svg', + size: 'svg-306', + title: 'This job has not been triggered yet', + content: + 'This job depends on upstream jobs that need to succeed in order for this job to be triggered', + }, + favicon: + '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/gitlab-org/gitlab-shell/-/jobs/456/cancel', + method: 'post', + }, + }, + }, + ], + }, + ], + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + tooltip: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/pipelines/27#deploy', + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png', + }, + path: '/gitlab-org/gitlab-shell/pipelines/27#deploy', + dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=deploy', + }, + { + name: 'notify', + title: 'notify: manual action', + groups: [ + { + name: 'slack', + size: 1, + status: { + icon: 'status_manual', + text: 'manual', + label: 'manual play action', + group: 'manual', + tooltip: 'manual action', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/458', + illustration: { + image: 'illustrations/manual_action.svg', + size: 'svg-394', + title: 'This job requires a manual action', + content: + 'This job depends on a user to trigger its process. Often they are used to deploy code to production environments', + }, + favicon: + '/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png', + action: { + icon: 'play', + title: 'Play', + path: '/gitlab-org/gitlab-shell/-/jobs/458/play', + method: 'post', + }, + }, + jobs: [ + { + id: 458, + name: 'slack', + started: null, + build_path: '/gitlab-org/gitlab-shell/-/jobs/458', + play_path: '/gitlab-org/gitlab-shell/-/jobs/458/play', + playable: true, + created_at: '2018-05-18T15:32:55.303Z', + updated_at: '2018-05-18T15:34:08.535Z', + status: { + icon: 'status_manual', + text: 'manual', + label: 'manual play action', + group: 'manual', + tooltip: 'manual action', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/458', + illustration: { + image: 'illustrations/manual_action.svg', + size: 'svg-394', + title: 'This job requires a manual action', + content: + 'This job depends on a user to trigger its process. Often they are used to deploy code to production environments', + }, + favicon: + '/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png', + action: { + icon: 'play', + title: 'Play', + path: '/gitlab-org/gitlab-shell/-/jobs/458/play', + method: 'post', + }, + }, + }, + ], + }, + ], + status: { + icon: 'status_manual', + text: 'manual', + label: 'manual action', + group: 'manual', + tooltip: 'manual action', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/pipelines/27#notify', + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png', + }, + path: '/gitlab-org/gitlab-shell/pipelines/27#notify', + dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=notify', + }, +]; + +export const jobsInStage = { + name: 'build', + title: 'build: running', + latest_statuses: [ + { + id: 1180, + name: 'build:linux', + started: false, + build_path: '/gitlab-org/gitlab-shell/-/jobs/1180', + cancel_path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', + playable: false, + created_at: '2018-09-28T11:09:57.229Z', + updated_at: '2018-09-28T11:09:57.503Z', + status: { + icon: 'status_pending', + text: 'pending', + label: 'pending', + group: 'pending', + tooltip: 'pending', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/1180', + illustration: { + image: 'illustrations/pending_job_empty.svg', + size: 'svg-430', + title: 'This job has not started yet', + content: 'This job is in pending state and is waiting to be picked by a runner', + }, + favicon: + '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', + method: 'post', + }, + }, + }, + { + id: 444, + name: 'build:osx', + started: '2018-05-18T05:32:20.655Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/444', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', + playable: false, + created_at: '2018-05-18T15:32:54.364Z', + updated_at: '2018-05-18T15:32:54.364Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/444', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', + method: 'post', + }, + }, + }, + ], + retried: [ + { + id: 443, + name: 'build:linux', + started: '2018-05-18T06:32:20.655Z', + build_path: '/gitlab-org/gitlab-shell/-/jobs/443', + retry_path: '/gitlab-org/gitlab-shell/-/jobs/443/retry', + playable: false, + created_at: '2018-05-18T15:32:54.296Z', + updated_at: '2018-05-18T15:32:54.296Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed (retried)', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/-/jobs/443', + illustration: { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + action: { + icon: 'retry', + title: 'Retry', + path: '/gitlab-org/gitlab-shell/-/jobs/443/retry', + method: 'post', + }, + }, + }, + ], + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + tooltip: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-shell/pipelines/27#build', + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png', + }, + path: '/gitlab-org/gitlab-shell/pipelines/27#build', + dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=build', +}; diff --git a/spec/javascripts/jobs/store/actions_spec.js b/spec/javascripts/jobs/store/actions_spec.js new file mode 100644 index 00000000000..5ab1f75d0d6 --- /dev/null +++ b/spec/javascripts/jobs/store/actions_spec.js @@ -0,0 +1,613 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import { + setJobEndpoint, + setTraceEndpoint, + setStagesEndpoint, + setJobsEndpoint, + clearEtagPoll, + stopPolling, + requestJob, + fetchJob, + receiveJobSuccess, + receiveJobError, + scrollTop, + scrollBottom, + requestTrace, + fetchTrace, + stopPollingTrace, + receiveTraceSuccess, + receiveTraceError, + fetchFavicon, + requestStatusFavicon, + receiveStatusFaviconSuccess, + requestStatusFaviconError, + requestStages, + fetchStages, + receiveStagesSuccess, + receiveStagesError, + requestJobsForStage, + fetchJobsForStage, + receiveJobsForStageSuccess, + receiveJobsForStageError, +} from '~/jobs/store/actions'; +import state from '~/jobs/store/state'; +import * as types from '~/jobs/store/mutation_types'; +import testAction from 'spec/helpers/vuex_action_helper'; +import { TEST_HOST } from 'spec/test_constants'; + +describe('Job State actions', () => { + let mockedState; + + beforeEach(() => { + mockedState = state(); + }); + + describe('setJobEndpoint', () => { + it('should commit SET_JOB_ENDPOINT mutation', done => { + testAction( + setJobEndpoint, + 'job/872324.json', + mockedState, + [{ type: types.SET_JOB_ENDPOINT, payload: 'job/872324.json' }], + [], + done, + ); + }); + }); + + describe('setTraceEndpoint', () => { + it('should commit SET_TRACE_ENDPOINT mutation', done => { + testAction( + setTraceEndpoint, + 'job/872324/trace.json', + mockedState, + [{ type: types.SET_TRACE_ENDPOINT, payload: 'job/872324/trace.json' }], + [], + done, + ); + }); + }); + + describe('setStagesEndpoint', () => { + it('should commit SET_STAGES_ENDPOINT mutation', done => { + testAction( + setStagesEndpoint, + 'job/872324/stages.json', + mockedState, + [{ type: types.SET_STAGES_ENDPOINT, payload: 'job/872324/stages.json' }], + [], + done, + ); + }); + }); + + describe('setJobsEndpoint', () => { + it('should commit SET_JOBS_ENDPOINT mutation', done => { + testAction( + setJobsEndpoint, + 'job/872324/stages/build.json', + mockedState, + [{ type: types.SET_JOBS_ENDPOINT, payload: 'job/872324/stages/build.json' }], + [], + done, + ); + }); + }); + + describe('requestJob', () => { + it('should commit REQUEST_JOB mutation', done => { + testAction(requestJob, null, mockedState, [{ type: types.REQUEST_JOB }], [], done); + }); + }); + + describe('fetchJob', () => { + let mock; + + beforeEach(() => { + mockedState.jobEndpoint = `${TEST_HOST}/endpoint.json`; + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + stopPolling(); + clearEtagPoll(); + }); + + describe('success', () => { + it('dispatches requestJob and receiveJobSuccess ', done => { + mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, { id: 121212, name: 'karma' }); + + testAction( + fetchJob, + null, + mockedState, + [], + [ + { + type: 'requestJob', + }, + { + payload: { id: 121212, name: 'karma' }, + type: 'receiveJobSuccess', + }, + ], + done, + ); + }); + }); + + describe('error', () => { + beforeEach(() => { + mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500); + }); + + it('dispatches requestJob and receiveJobError ', done => { + testAction( + fetchJob, + null, + mockedState, + [], + [ + { + type: 'requestJob', + }, + { + type: 'receiveJobError', + }, + ], + done, + ); + }); + }); + }); + + describe('receiveJobSuccess', () => { + it('should commit RECEIVE_JOB_SUCCESS mutation', done => { + testAction( + receiveJobSuccess, + { id: 121232132 }, + mockedState, + [{ type: types.RECEIVE_JOB_SUCCESS, payload: { id: 121232132 } }], + [], + done, + ); + }); + }); + + describe('receiveJobError', () => { + it('should commit RECEIVE_JOB_ERROR mutation', done => { + testAction(receiveJobError, null, mockedState, [{ type: types.RECEIVE_JOB_ERROR }], [], done); + }); + }); + + describe('scrollTop', () => { + it('should commit SCROLL_TO_TOP mutation', done => { + testAction(scrollTop, null, mockedState, [{ type: types.SCROLL_TO_TOP }], [], done); + }); + }); + + describe('scrollBottom', () => { + it('should commit SCROLL_TO_BOTTOM mutation', done => { + testAction(scrollBottom, null, mockedState, [{ type: types.SCROLL_TO_BOTTOM }], [], done); + }); + }); + + describe('requestTrace', () => { + it('should commit REQUEST_TRACE mutation', done => { + testAction(requestTrace, null, mockedState, [{ type: types.REQUEST_TRACE }], [], done); + }); + }); + + describe('fetchTrace', () => { + let mock; + + beforeEach(() => { + mockedState.traceEndpoint = `${TEST_HOST}/endpoint`; + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + stopPolling(); + clearEtagPoll(); + }); + + describe('success', () => { + it('dispatches requestTrace, fetchFavicon, receiveTraceSuccess and stopPollingTrace when job is complete', done => { + mock.onGet(`${TEST_HOST}/endpoint/trace.json`).replyOnce(200, { + html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :', + complete: true, + }); + + testAction( + fetchTrace, + null, + mockedState, + [], + [ + { + type: 'requestTrace', + }, + { + type: 'fetchFavicon', + }, + { + payload: { + html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :', + complete: true, + }, + type: 'receiveTraceSuccess', + }, + { + type: 'stopPollingTrace', + }, + ], + done, + ); + }); + }); + + describe('error', () => { + beforeEach(() => { + mock.onGet(`${TEST_HOST}/endpoint/trace.json`).reply(500); + }); + + it('dispatches requestTrace and receiveTraceError ', done => { + testAction( + fetchTrace, + null, + mockedState, + [], + [ + { + type: 'requestTrace', + }, + { + type: 'receiveTraceError', + }, + ], + done, + ); + }); + }); + }); + + describe('stopPollingTrace', () => { + it('should commit STOP_POLLING_TRACE mutation ', done => { + testAction( + stopPollingTrace, + null, + mockedState, + [{ type: types.STOP_POLLING_TRACE }], + [], + done, + ); + }); + }); + + describe('receiveTraceSuccess', () => { + it('should commit RECEIVE_TRACE_SUCCESS mutation ', done => { + testAction( + receiveTraceSuccess, + 'hello world', + mockedState, + [{ type: types.RECEIVE_TRACE_SUCCESS, payload: 'hello world' }], + [], + done, + ); + }); + }); + + describe('receiveTraceError', () => { + it('should commit RECEIVE_TRACE_ERROR mutation ', done => { + testAction( + receiveTraceError, + null, + mockedState, + [{ type: types.RECEIVE_TRACE_ERROR }], + [], + done, + ); + }); + }); + + describe('fetchFavicon', () => { + let mock; + + beforeEach(() => { + mockedState.pagePath = `${TEST_HOST}/endpoint`; + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + }); + + describe('success', () => { + it('dispatches requestStatusFavicon and receiveStatusFaviconSuccess ', done => { + mock.onGet(`${TEST_HOST}/endpoint/status.json`).replyOnce(200); + + testAction( + fetchFavicon, + null, + mockedState, + [], + [ + { + type: 'requestStatusFavicon', + }, + { + type: 'receiveStatusFaviconSuccess', + }, + ], + done, + ); + }); + }); + + describe('error', () => { + beforeEach(() => { + mock.onGet(`${TEST_HOST}/endpoint/status.json`).replyOnce(500); + }); + + it('dispatches requestStatusFavicon and requestStatusFaviconError ', done => { + testAction( + fetchFavicon, + null, + mockedState, + [], + [ + { + type: 'requestStatusFavicon', + }, + { + type: 'requestStatusFaviconError', + }, + ], + done, + ); + }); + }); + }); + + describe('requestStatusFavicon', () => { + it('should commit REQUEST_STATUS_FAVICON mutation ', done => { + testAction( + requestStatusFavicon, + null, + mockedState, + [{ type: types.REQUEST_STATUS_FAVICON }], + [], + done, + ); + }); + }); + + describe('receiveStatusFaviconSuccess', () => { + it('should commit RECEIVE_STATUS_FAVICON_SUCCESS mutation ', done => { + testAction( + receiveStatusFaviconSuccess, + null, + mockedState, + [{ type: types.RECEIVE_STATUS_FAVICON_SUCCESS }], + [], + done, + ); + }); + }); + + describe('requestStatusFaviconError', () => { + it('should commit RECEIVE_STATUS_FAVICON_ERROR mutation ', done => { + testAction( + requestStatusFaviconError, + null, + mockedState, + [{ type: types.RECEIVE_STATUS_FAVICON_ERROR }], + [], + done, + ); + }); + }); + + describe('requestStages', () => { + it('should commit REQUEST_STAGES mutation ', done => { + testAction(requestStages, null, mockedState, [{ type: types.REQUEST_STAGES }], [], done); + }); + }); + + describe('fetchStages', () => { + let mock; + + beforeEach(() => { + mockedState.job.pipeline = { + path: `${TEST_HOST}/endpoint.json/stages`, + }; + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + }); + + describe('success', () => { + it('dispatches requestStages and receiveStagesSuccess, fetchJobsForStage ', done => { + mock + .onGet(`${TEST_HOST}/endpoint.json/stages`) + .replyOnce(200, { details: { stages: [{ id: 121212, name: 'build' }] } }); + + testAction( + fetchStages, + null, + mockedState, + [], + [ + { + type: 'requestStages', + }, + { + payload: [{ id: 121212, name: 'build' }], + type: 'receiveStagesSuccess', + }, + { + payload: { id: 121212, name: 'build' }, + type: 'fetchJobsForStage', + }, + ], + done, + ); + }); + }); + + describe('error', () => { + beforeEach(() => { + mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500); + }); + + it('dispatches requestStages and receiveStagesError ', done => { + testAction( + fetchStages, + null, + mockedState, + [], + [ + { + type: 'requestStages', + }, + { + type: 'receiveStagesError', + }, + ], + done, + ); + }); + }); + }); + + describe('receiveStagesSuccess', () => { + it('should commit RECEIVE_STAGES_SUCCESS mutation ', done => { + testAction( + receiveStagesSuccess, + {}, + mockedState, + [{ type: types.RECEIVE_STAGES_SUCCESS, payload: {} }], + [], + done, + ); + }); + }); + + describe('receiveStagesError', () => { + it('should commit RECEIVE_STAGES_ERROR mutation ', done => { + testAction( + receiveStagesError, + null, + mockedState, + [{ type: types.RECEIVE_STAGES_ERROR }], + [], + done, + ); + }); + }); + + describe('requestJobsForStage', () => { + it('should commit REQUEST_JOBS_FOR_STAGE mutation ', done => { + testAction( + requestJobsForStage, + null, + mockedState, + [{ type: types.REQUEST_JOBS_FOR_STAGE }], + [], + done, + ); + }); + }); + + describe('fetchJobsForStage', () => { + let mock; + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + }); + + describe('success', () => { + it('dispatches requestJobsForStage and receiveJobsForStageSuccess ', done => { + mock + .onGet(`${TEST_HOST}/jobs.json`) + .replyOnce(200, { latest_statuses: [{ id: 121212, name: 'build' }], retried: [] }); + + testAction( + fetchJobsForStage, + { dropdown_path: `${TEST_HOST}/jobs.json` }, + mockedState, + [], + [ + { + type: 'requestJobsForStage', + }, + { + payload: [{ id: 121212, name: 'build' }], + type: 'receiveJobsForStageSuccess', + }, + ], + done, + ); + }); + }); + + describe('error', () => { + beforeEach(() => { + mock.onGet(`${TEST_HOST}/jobs.json`).reply(500); + }); + + it('dispatches requestJobsForStage and receiveJobsForStageError', done => { + testAction( + fetchJobsForStage, + { dropdown_path: `${TEST_HOST}/jobs.json` }, + mockedState, + [], + [ + { + type: 'requestJobsForStage', + }, + { + type: 'receiveJobsForStageError', + }, + ], + done, + ); + }); + }); + }); + + describe('receiveJobsForStageSuccess', () => { + it('should commit RECEIVE_JOBS_FOR_STAGE_SUCCESS mutation ', done => { + testAction( + receiveJobsForStageSuccess, + [{ id: 121212, name: 'karma' }], + mockedState, + [{ type: types.RECEIVE_JOBS_FOR_STAGE_SUCCESS, payload: [{ id: 121212, name: 'karma' }] }], + [], + done, + ); + }); + }); + + describe('receiveJobsForStageError', () => { + it('should commit RECEIVE_JOBS_FOR_STAGE_ERROR mutation ', done => { + testAction( + receiveJobsForStageError, + null, + mockedState, + [{ type: types.RECEIVE_JOBS_FOR_STAGE_ERROR }], + [], + done, + ); + }); + }); +}); diff --git a/spec/javascripts/jobs/store/getters_spec.js b/spec/javascripts/jobs/store/getters_spec.js new file mode 100644 index 00000000000..160b2f4b34a --- /dev/null +++ b/spec/javascripts/jobs/store/getters_spec.js @@ -0,0 +1,213 @@ +import * as getters from '~/jobs/store/getters'; +import state from '~/jobs/store/state'; + +describe('Job Store Getters', () => { + let localState; + + beforeEach(() => { + localState = state(); + }); + + describe('headerActions', () => { + describe('with new issue path', () => { + it('returns an array with action to create a new issue', () => { + localState.job.new_issue_path = 'issues/new'; + + expect(getters.headerActions(localState)).toEqual([ + { + label: 'New issue', + path: localState.job.new_issue_path, + cssClass: + 'js-new-issue btn btn-success btn-inverted d-none d-md-block d-lg-block d-xl-block', + type: 'link', + }, + ]); + }); + }); + + describe('without new issue path', () => { + it('returns an empty array', () => { + expect(getters.headerActions(localState)).toEqual([]); + }); + }); + }); + + describe('headerTime', () => { + describe('when the job has started key', () => { + it('returns started key', () => { + const started = '2018-08-31T16:20:49.023Z'; + localState.job.started = started; + + expect(getters.headerTime(localState)).toEqual(started); + }); + }); + + describe('when the job does not have started key', () => { + it('returns created_at key', () => { + const created = '2018-08-31T16:20:49.023Z'; + localState.job.created_at = created; + expect(getters.headerTime(localState)).toEqual(created); + }); + }); + }); + + describe('shouldRenderCalloutMessage', () => { + describe('with status and callout message', () => { + it('returns true', () => { + localState.job.callout_message = 'Callout message'; + localState.job.status = { icon: 'passed' }; + + expect(getters.shouldRenderCalloutMessage(localState)).toEqual(true); + }); + }); + + describe('without status & with callout message', () => { + it('returns false', () => { + localState.job.callout_message = 'Callout message'; + expect(getters.shouldRenderCalloutMessage(localState)).toEqual(false); + }); + }); + + describe('with status & without callout message', () => { + it('returns false', () => { + localState.job.status = { icon: 'passed' }; + + expect(getters.shouldRenderCalloutMessage(localState)).toEqual(false); + }); + }); + }); + + describe('jobHasStarted', () => { + describe('when started equals false', () => { + it('returns false', () => { + localState.job.started = false; + expect(getters.jobHasStarted(localState)).toEqual(false); + }); + }); + + describe('when started equals string', () => { + it('returns true', () => { + localState.job.started = '2018-08-31T16:20:49.023Z'; + expect(getters.jobHasStarted(localState)).toEqual(true); + }); + }); + }); + + describe('hasEnvironment', () => { + describe('without `deployment_status`', () => { + it('returns false', () => { + expect(getters.hasEnvironment(localState)).toEqual(false); + }); + }); + + describe('with an empty object for `deployment_status`', () => { + it('returns false', () => { + localState.job.deployment_status = {}; + expect(getters.hasEnvironment(localState)).toEqual(false); + }); + }); + + describe('when `deployment_status` is defined and not empty', () => { + it('returns true', () => { + localState.job.deployment_status = { + status: 'creating', + environment: { + last_deployment: {}, + }, + }; + + expect(getters.hasEnvironment(localState)).toEqual(true); + }); + }); + }); + + describe('hasTrace', () => { + describe('when has_trace is true', () => { + it('returns true', () => { + localState.job.has_trace = true; + localState.job.status = {}; + + expect(getters.hasTrace(localState)).toEqual(true); + }); + }); + + describe('when job is running', () => { + it('returns true', () => { + localState.job.has_trace = false; + localState.job.status = { group: 'running' }; + + expect(getters.hasTrace(localState)).toEqual(true); + }); + }); + + describe('when has_trace is false and job is not running', () => { + it('returns false', () => { + localState.job.has_trace = false; + localState.job.status = { group: 'pending' }; + + expect(getters.hasTrace(localState)).toEqual(false); + }); + }); + }); + + describe('emptyStateIllustration', () => { + describe('with defined illustration', () => { + it('returns the state illustration object', () => { + localState.job.status = { + illustration: { + path: 'foo', + }, + }; + + expect(getters.emptyStateIllustration(localState)).toEqual({ path: 'foo' }); + }); + }); + + describe('when illustration is not defined', () => { + it('returns an empty object', () => { + expect(getters.emptyStateIllustration(localState)).toEqual({}); + }); + }); + }); + + describe('isJobStuck', () => { + describe('when job is pending and runners are not available', () => { + it('returns true', () => { + localState.job.status = { + group: 'pending', + }; + localState.job.runners = { + available: false, + }; + + expect(getters.isJobStuck(localState)).toEqual(true); + }); + }); + + describe('when job is not pending', () => { + it('returns false', () => { + localState.job.status = { + group: 'running', + }; + localState.job.runners = { + available: false, + }; + + expect(getters.isJobStuck(localState)).toEqual(false); + }); + }); + + describe('when runners are available', () => { + it('returns false', () => { + localState.job.status = { + group: 'pending', + }; + localState.job.runners = { + available: true, + }; + + expect(getters.isJobStuck(localState)).toEqual(false); + }); + }); + }); +}); diff --git a/spec/javascripts/jobs/store/mutations_spec.js b/spec/javascripts/jobs/store/mutations_spec.js new file mode 100644 index 00000000000..9ba543d32a8 --- /dev/null +++ b/spec/javascripts/jobs/store/mutations_spec.js @@ -0,0 +1,235 @@ +import state from '~/jobs/store/state'; +import mutations from '~/jobs/store/mutations'; +import * as types from '~/jobs/store/mutation_types'; + +describe('Jobs Store Mutations', () => { + let stateCopy; + + const html = + 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- : Writing /builds/ab89e95b0fa0b9272ea0c797b76908f24d36992630e9325273a4ce3.png<br>I'; + + beforeEach(() => { + stateCopy = state(); + }); + + describe('SET_JOB_ENDPOINT', () => { + it('should set jobEndpoint', () => { + mutations[types.SET_JOB_ENDPOINT](stateCopy, 'job/21312321.json'); + expect(stateCopy.jobEndpoint).toEqual('job/21312321.json'); + }); + }); + + describe('REQUEST_STATUS_FAVICON', () => { + it('should set fetchingStatusFavicon to true', () => { + mutations[types.REQUEST_STATUS_FAVICON](stateCopy); + expect(stateCopy.fetchingStatusFavicon).toEqual(true); + }); + }); + + describe('RECEIVE_STATUS_FAVICON_SUCCESS', () => { + it('should set fetchingStatusFavicon to false', () => { + mutations[types.RECEIVE_STATUS_FAVICON_SUCCESS](stateCopy); + expect(stateCopy.fetchingStatusFavicon).toEqual(false); + }); + }); + + describe('RECEIVE_STATUS_FAVICON_ERROR', () => { + it('should set fetchingStatusFavicon to false', () => { + mutations[types.RECEIVE_STATUS_FAVICON_ERROR](stateCopy); + expect(stateCopy.fetchingStatusFavicon).toEqual(false); + }); + }); + + describe('RECEIVE_TRACE_SUCCESS', () => { + describe('when trace has state', () => { + it('sets traceState', () => { + const stateLog = + 'eyJvZmZzZXQiOjczNDQ1MSwibl9vcGVuX3RhZ3MiOjAsImZnX2NvbG9yIjpudWxsLCJiZ19jb2xvciI6bnVsbCwic3R5bGVfbWFzayI6MH0='; + mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, { + state: stateLog, + }); + expect(stateCopy.traceState).toEqual(stateLog); + }); + }); + + describe('when traceSize is smaller than the total size', () => { + it('sets isTraceSizeVisible to true', () => { + mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, { total: 51184600, size: 1231 }); + + expect(stateCopy.isTraceSizeVisible).toEqual(true); + }); + }); + + describe('when traceSize is bigger than the total size', () => { + it('sets isTraceSizeVisible to false', () => { + const copy = Object.assign({}, stateCopy, { traceSize: 5118460, size: 2321312 }); + + mutations[types.RECEIVE_TRACE_SUCCESS](copy, { total: 511846 }); + + expect(copy.isTraceSizeVisible).toEqual(false); + }); + }); + + it('sets trace, trace size and isTraceComplete', () => { + mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, { + append: true, + html, + size: 511846, + complete: true, + }); + expect(stateCopy.trace).toEqual(html); + expect(stateCopy.traceSize).toEqual(511846); + expect(stateCopy.isTraceComplete).toEqual(true); + }); + }); + + describe('STOP_POLLING_TRACE', () => { + it('sets isTraceComplete to true', () => { + mutations[types.STOP_POLLING_TRACE](stateCopy); + expect(stateCopy.isTraceComplete).toEqual(true); + }); + }); + + describe('RECEIVE_TRACE_ERROR', () => { + it('resets trace state and sets error to true', () => { + mutations[types.RECEIVE_TRACE_ERROR](stateCopy); + expect(stateCopy.isLoadingTrace).toEqual(false); + expect(stateCopy.isTraceComplete).toEqual(true); + expect(stateCopy.hasTraceError).toEqual(true); + }); + }); + + describe('REQUEST_JOB', () => { + it('sets isLoading to true', () => { + mutations[types.REQUEST_JOB](stateCopy); + + expect(stateCopy.isLoading).toEqual(true); + }); + }); + + describe('RECEIVE_JOB_SUCCESS', () => { + beforeEach(() => { + mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 }); + }); + + it('sets is loading to false', () => { + expect(stateCopy.isLoading).toEqual(false); + }); + + it('sets hasError to false', () => { + expect(stateCopy.hasError).toEqual(false); + }); + + it('sets job data', () => { + expect(stateCopy.job).toEqual({ id: 1312321 }); + }); + }); + + describe('RECEIVE_JOB_ERROR', () => { + it('resets job data', () => { + mutations[types.RECEIVE_JOB_ERROR](stateCopy); + + expect(stateCopy.isLoading).toEqual(false); + expect(stateCopy.hasError).toEqual(true); + expect(stateCopy.job).toEqual({}); + }); + }); + + describe('SCROLL_TO_TOP', () => { + beforeEach(() => { + mutations[types.SCROLL_TO_TOP](stateCopy); + }); + + it('sets isTraceScrolledToBottom to false', () => { + expect(stateCopy.isTraceScrolledToBottom).toEqual(false); + }); + + it('sets hasBeenScrolled to true', () => { + expect(stateCopy.hasBeenScrolled).toEqual(true); + }); + }); + + describe('SCROLL_TO_BOTTOM', () => { + beforeEach(() => { + mutations[types.SCROLL_TO_BOTTOM](stateCopy); + }); + + it('sets isTraceScrolledToBottom to true', () => { + expect(stateCopy.isTraceScrolledToBottom).toEqual(true); + }); + + it('sets hasBeenScrolled to true', () => { + expect(stateCopy.hasBeenScrolled).toEqual(true); + }); + }); + + describe('REQUEST_STAGES', () => { + it('sets isLoadingStages to true', () => { + mutations[types.REQUEST_STAGES](stateCopy); + expect(stateCopy.isLoadingStages).toEqual(true); + }); + }); + + describe('RECEIVE_STAGES_SUCCESS', () => { + beforeEach(() => { + mutations[types.RECEIVE_STAGES_SUCCESS](stateCopy, [{ name: 'build' }]); + }); + + it('sets isLoadingStages to false', () => { + expect(stateCopy.isLoadingStages).toEqual(false); + }); + + it('sets stages', () => { + expect(stateCopy.stages).toEqual([{ name: 'build' }]); + }); + }); + + describe('RECEIVE_STAGES_ERROR', () => { + beforeEach(() => { + mutations[types.RECEIVE_STAGES_ERROR](stateCopy); + }); + + it('sets isLoadingStages to false', () => { + expect(stateCopy.isLoadingStages).toEqual(false); + }); + + it('resets stages', () => { + expect(stateCopy.stages).toEqual([]); + }); + }); + + describe('REQUEST_JOBS_FOR_STAGE', () => { + it('sets isLoadingStages to true', () => { + mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy); + expect(stateCopy.isLoadingJobs).toEqual(true); + }); + }); + + describe('RECEIVE_JOBS_FOR_STAGE_SUCCESS', () => { + beforeEach(() => { + mutations[types.RECEIVE_JOBS_FOR_STAGE_SUCCESS](stateCopy, [{ name: 'karma' }]); + }); + + it('sets isLoadingJobs to false', () => { + expect(stateCopy.isLoadingJobs).toEqual(false); + }); + + it('sets jobs', () => { + expect(stateCopy.jobs).toEqual([{ name: 'karma' }]); + }); + }); + + describe('RECEIVE_JOBS_FOR_STAGE_ERROR', () => { + beforeEach(() => { + mutations[types.RECEIVE_JOBS_FOR_STAGE_ERROR](stateCopy); + }); + + it('sets isLoadingJobs to false', () => { + expect(stateCopy.isLoadingJobs).toEqual(false); + }); + + it('resets jobs', () => { + expect(stateCopy.jobs).toEqual([]); + }); + }); +}); |