diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js index 40decbb5643c64c7ddb1a239ec572e4668c531de..b53890f8348b29c1491ce8054f22ded331090c0b 100644 --- a/spec/javascripts/jobs/components/job_app_spec.js +++ b/spec/javascripts/jobs/components/job_app_spec.js @@ -3,7 +3,9 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import jobApp from '~/jobs/components/job_app.vue'; import createStore from '~/jobs/store'; +import * as types from '~/jobs/store/mutation_types'; import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import { waitForMutation } from 'spec/helpers/vue_test_utils_helper'; import { resetStore } from '../store/helpers'; import job from '../mock_data'; @@ -26,6 +28,16 @@ describe('Job App ', () => { 'eyJvZmZzZXQiOjE3NDUxLCJuX29wZW5fdGFncyI6MCwiZmdfY29sb3IiOm51bGwsImJnX2NvbG9yIjpudWxsLCJzdHlsZV9tYXNrIjowfQ%3D%3D', }; + const waitForJobReceived = () => waitForMutation(store, types.RECEIVE_JOB_SUCCESS); + const setupAndMount = ({ jobData = {}, traceData = {} } = {}) => { + mock.onGet(props.endpoint).replyOnce(200, { ...job, ...jobData }); + mock.onGet(`${props.pagePath}/trace.json`).reply(200, traceData); + + vm = mountComponentWithStore(Component, { props, store }); + + return waitForJobReceived(); + }; + beforeEach(() => { mock = new MockAdapter(axios); store = createStore(); @@ -39,103 +51,81 @@ describe('Job App ', () => { describe('while loading', () => { beforeEach(() => { - mock.onGet(props.endpoint).reply(200, job, {}); - mock.onGet(`${props.pagePath}/trace.json`).reply(200, {}); - vm = mountComponentWithStore(Component, { props, store }); + setupAndMount(); }); - it('renders loading icon', done => { + it('renders loading icon', () => { expect(vm.$el.querySelector('.js-job-loading')).not.toBeNull(); expect(vm.$el.querySelector('.js-job-sidebar')).toBeNull(); expect(vm.$el.querySelector('.js-job-content')).toBeNull(); - - setTimeout(() => { - done(); - }, 0); }); }); describe('with successful request', () => { - beforeEach(() => { - mock.onGet(`${props.pagePath}/trace.json`).replyOnce(200, {}); - }); - describe('Header section', () => { describe('job callout message', () => { it('should not render the reason when reason is absent', done => { - mock.onGet(props.endpoint).replyOnce(200, job); - vm = mountComponentWithStore(Component, { props, store }); - - setTimeout(() => { - expect(vm.shouldRenderCalloutMessage).toBe(false); - - done(); - }, 0); + setupAndMount() + .then(() => { + expect(vm.shouldRenderCalloutMessage).toBe(false); + }) + .then(done) + .catch(done.fail); }); it('should render the reason when reason is present', done => { - mock.onGet(props.endpoint).replyOnce( - 200, - Object.assign({}, job, { - callout_message: 'There is an unknown failure, please try again', - }), - ); - - vm = mountComponentWithStore(Component, { props, store }); - setTimeout(() => { - expect(vm.shouldRenderCalloutMessage).toBe(true); - done(); - }, 0); + setupAndMount({ + jobData: { + callout_message: 'There is an unkown failure, please try again', + }, + }) + .then(() => { + expect(vm.shouldRenderCalloutMessage).toBe(true); + }) + .then(done) + .catch(done.fail); }); }); describe('triggered job', () => { - beforeEach(() => { + beforeEach(done => { const aYearAgo = new Date(); aYearAgo.setFullYear(aYearAgo.getFullYear() - 1); - mock - .onGet(props.endpoint) - .replyOnce(200, Object.assign({}, job, { started: aYearAgo.toISOString() })); - vm = mountComponentWithStore(Component, { props, store }); + setupAndMount({ jobData: { started: aYearAgo.toISOString() } }) + .then(done) + .catch(done.fail); }); - it('should render provided job information', done => { - setTimeout(() => { - expect( - vm.$el - .querySelector('.header-main-content') - .textContent.replace(/\s+/g, ' ') - .trim(), - ).toContain('passed Job #4757 triggered 1 year ago by Root'); - done(); - }, 0); + it('should render provided job information', () => { + expect( + vm.$el + .querySelector('.header-main-content') + .textContent.replace(/\s+/g, ' ') + .trim(), + ).toContain('passed Job #4757 triggered 1 year ago by Root'); }); - it('should render new issue link', done => { - setTimeout(() => { - expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual( - job.new_issue_path, - ); - done(); - }, 0); + 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', done => { - mock.onGet(props.endpoint).replyOnce(200, job); - vm = mountComponentWithStore(Component, { props, store }); - - setTimeout(() => { - expect( - vm.$el - .querySelector('.header-main-content') - .textContent.replace(/\s+/g, ' ') - .trim(), - ).toContain('passed Job #4757 created 3 weeks ago by Root'); - done(); - }, 0); + setupAndMount() + .then(() => { + expect( + vm.$el + .querySelector('.header-main-content') + .textContent.replace(/\s+/g, ' ') + .trim(), + ).toContain('passed Job #4757 created 3 weeks ago by Root'); + }) + .then(done) + .catch(done.fail); }); }); }); @@ -143,9 +133,8 @@ describe('Job App ', () => { describe('stuck block', () => { describe('without active runners availabl', () => { it('renders stuck block when there are no runners', done => { - mock.onGet(props.endpoint).replyOnce( - 200, - Object.assign({}, job, { + setupAndMount({ + jobData: { status: { group: 'pending', icon: 'status_pending', @@ -159,23 +148,23 @@ describe('Job App ', () => { online: false, }, tags: [], - }), - ); - vm = mountComponentWithStore(Component, { props, store }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-stuck')).not.toBeNull(); - expect(vm.$el.querySelector('.js-job-stuck .js-stuck-no-active-runner')).not.toBeNull(); - done(); - }, 0); + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-job-stuck')).not.toBeNull(); + expect( + vm.$el.querySelector('.js-job-stuck .js-stuck-no-active-runner'), + ).not.toBeNull(); + }) + .then(done) + .catch(done.fail); }); }); describe('when available runners can not run specified tag', () => { it('renders tags in stuck block when there are no runners', done => { - mock.onGet(props.endpoint).replyOnce( - 200, - Object.assign({}, job, { + setupAndMount({ + jobData: { status: { group: 'pending', icon: 'status_pending', @@ -188,27 +177,21 @@ describe('Job App ', () => { available: false, online: false, }, - }), - ); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]); - expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull(); - done(); - }, 0); + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]); + expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull(); + }) + .then(done) + .catch(done.fail); }); }); describe('when runners are offline and build has tags', () => { it('renders message about job being stuck because of no runners with the specified tags', done => { - mock.onGet(props.endpoint).replyOnce( - 200, - Object.assign({}, job, { + setupAndMount({ + jobData: { status: { group: 'pending', icon: 'status_pending', @@ -221,48 +204,35 @@ describe('Job App ', () => { available: true, online: true, }, - }), - ); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]); - expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull(); - done(); - }, 0); + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]); + expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull(); + }) + .then(done) + .catch(done.fail); }); }); it('does not renders stuck block when there are no runners', done => { - mock.onGet(props.endpoint).replyOnce( - 200, - Object.assign({}, job, { + setupAndMount({ + jobData: { runners: { available: true }, - }), - ); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-stuck')).toBeNull(); - - done(); - }, 0); + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-job-stuck')).toBeNull(); + }) + .then(done) + .catch(done.fail); }); }); describe('unmet prerequisites block', () => { it('renders unmet prerequisites block when there is an unmet prerequisites failure', done => { - mock.onGet(props.endpoint).replyOnce( - 200, - Object.assign({}, job, { + setupAndMount({ + jobData: { status: { group: 'failed', icon: 'status_failed', @@ -282,104 +252,81 @@ describe('Job App ', () => { available: true, }, tags: [], - }), - ); - vm = mountComponentWithStore(Component, { props, store }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-failed')).not.toBeNull(); - done(); - }, 0); + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-job-failed')).not.toBeNull(); + }) + .then(done) + .catch(done.fail); }); }); describe('environments block', () => { it('renders environment block when job has environment', done => { - mock.onGet(props.endpoint).replyOnce( - 200, - Object.assign({}, job, { + setupAndMount({ + jobData: { deployment_status: { environment: { environment_path: '/path', name: 'foo', }, }, - }), - ); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-environment')).not.toBeNull(); - - done(); - }, 0); + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-job-environment')).not.toBeNull(); + }) + .then(done) + .catch(done.fail); }); it('does not render environment block when job has environment', done => { - mock.onGet(props.endpoint).replyOnce(200, job); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-environment')).toBeNull(); - done(); - }, 0); + setupAndMount() + .then(() => { + expect(vm.$el.querySelector('.js-job-environment')).toBeNull(); + }) + .then(done) + .catch(done.fail); }); }); describe('erased block', () => { it('renders erased block when `erased` is true', done => { - mock.onGet(props.endpoint).replyOnce( - 200, - Object.assign({}, job, { + setupAndMount({ + jobData: { erased_by: { username: 'root', web_url: 'gitlab.com/root', }, erased_at: '2016-11-07T11:11:16.525Z', - }), - ); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-erased-block')).not.toBeNull(); - - done(); - }, 0); + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-job-erased-block')).not.toBeNull(); + }) + .then(done) + .catch(done.fail); }); it('does not render erased block when `erased` is false', done => { - mock.onGet(props.endpoint).replyOnce(200, Object.assign({}, job, { erased_at: null })); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-erased-block')).toBeNull(); - - done(); - }, 0); + setupAndMount({ + jobData: { + erased_at: null, + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-job-erased-block')).toBeNull(); + }) + .then(done) + .catch(done.fail); }); }); describe('empty states block', () => { it('renders empty state when job does not have trace and is not running', done => { - mock.onGet(props.endpoint).replyOnce( - 200, - Object.assign({}, job, { + setupAndMount({ + jobData: { has_trace: false, status: { group: 'pending', @@ -399,25 +346,18 @@ describe('Job App ', () => { path: '/path', }, }, - }), - ); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull(); - - done(); - }, 0); + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull(); + }) + .then(done) + .catch(done.fail); }); it('does not render empty state when job does not have trace but it is running', done => { - mock.onGet(props.endpoint).replyOnce( - 200, - Object.assign({}, job, { + setupAndMount({ + jobData: { has_trace: false, status: { group: 'running', @@ -426,34 +366,23 @@ describe('Job App ', () => { text: 'running', details_path: 'path', }, - }), - ); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull(); - - done(); - }, 0); + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull(); + }) + .then(done) + .catch(done.fail); }); it('does not render empty state when job has trace but it is not running', done => { - mock.onGet(props.endpoint).replyOnce(200, Object.assign({}, job, { has_trace: true })); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull(); - - done(); - }, 0); + setupAndMount({ jobData: { has_trace: true } }) + .then(() => { + expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull(); + }) + .then(done) + .catch(done.fail); + done(); }); it('displays remaining time for a delayed job', done => { @@ -461,37 +390,23 @@ describe('Job App ', () => { spyOn(Date, 'now').and.callFake( () => new Date(delayedJobFixture.scheduled_at).getTime() - oneHourInMilliseconds, ); - mock.onGet(props.endpoint).replyOnce(200, { ...delayedJobFixture }); + setupAndMount({ jobData: delayedJobFixture }) + .then(() => { + expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull(); - vm = mountComponentWithStore(Component, { - props, - store, - }); - - store.subscribeAction(action => { - if (action.type !== 'receiveJobSuccess') { - return; - } - - Vue.nextTick() - .then(() => { - expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull(); - - const title = vm.$el.querySelector('.js-job-empty-state-title'); + const title = vm.$el.querySelector('.js-job-empty-state-title'); - expect(title).toContainText('01:00:00'); - done(); - }) - .catch(done.fail); - }); + expect(title).toContainText('01:00:00'); + }) + .then(done) + .catch(done.fail); }); }); describe('sidebar', () => { it('has no blank blocks', done => { - mock.onGet(props.endpoint).replyOnce( - 200, - Object.assign({}, job, { + setupAndMount({ + jobData: { duration: null, finished_at: null, erased_at: null, @@ -500,106 +415,89 @@ describe('Job App ', () => { coverage: null, tags: [], cancel_path: null, - }), - ); - - vm.$nextTick(() => { - vm.$el.querySelectorAll('.blocks-container > *').forEach(block => { - expect(block.textContent.trim()).not.toBe(''); - }); - done(); - }); + }, + }) + .then(() => { + vm.$el.querySelectorAll('.blocks-container > *').forEach(block => { + expect(block.textContent.trim()).not.toBe(''); + }); + }) + .then(done) + .catch(done.fail); }); }); }); describe('archived job', () => { - beforeEach(() => { - mock.onGet(props.endpoint).reply(200, Object.assign({}, job, { archived: true }), {}); - vm = mountComponentWithStore(Component, { - props, - store, - }); + beforeEach(done => { + setupAndMount({ jobData: { archived: true } }) + .then(done) + .catch(done.fail); }); - it('renders warning about job being archived', done => { - setTimeout(() => { - expect(vm.$el.querySelector('.js-archived-job ')).not.toBeNull(); - done(); - }, 0); + it('renders warning about job being archived', () => { + expect(vm.$el.querySelector('.js-archived-job ')).not.toBeNull(); }); }); describe('non-archived job', () => { - beforeEach(() => { - mock.onGet(props.endpoint).reply(200, job, {}); - vm = mountComponentWithStore(Component, { - props, - store, - }); + beforeEach(done => { + setupAndMount() + .then(done) + .catch(done.fail); }); - it('does not warning about job being archived', done => { - setTimeout(() => { - expect(vm.$el.querySelector('.js-archived-job ')).toBeNull(); - done(); - }, 0); + it('does not warning about job being archived', () => { + expect(vm.$el.querySelector('.js-archived-job ')).toBeNull(); }); }); describe('trace output', () => { - beforeEach(() => { - mock.onGet(props.endpoint).reply(200, job, {}); - }); - describe('with append flag', () => { it('appends the log content to the existing one', done => { - mock.onGet(`${props.pagePath}/trace.json`).reply(200, { - html: 'More', - status: 'running', - state: 'newstate', - append: true, - complete: true, - }); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - vm.$store.state.trace = 'Update'; - - setTimeout(() => { - expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain('Update'); - - done(); - }, 0); + setupAndMount({ + traceData: { + html: 'More', + status: 'running', + state: 'newstate', + append: true, + complete: true, + }, + }) + .then(() => { + vm.$store.state.trace = 'Update'; + + return vm.$nextTick(); + }) + .then(() => { + expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain('Update'); + }) + .then(done) + .catch(done.fail); }); }); describe('without append flag', () => { it('replaces the trace', done => { - mock.onGet(`${props.pagePath}/trace.json`).reply(200, { - html: 'Different', - status: 'running', - append: false, - complete: true, - }); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - vm.$store.state.trace = 'Update'; - - setTimeout(() => { - expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).not.toContain( - 'Update', - ); + setupAndMount({ + traceData: { + html: 'Different', + status: 'running', + append: false, + complete: true, + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).not.toContain( + 'Update', + ); - expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain('Different'); - done(); - }, 0); + expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain( + 'Different', + ); + }) + .then(done) + .catch(done.fail); }); }); @@ -615,83 +513,76 @@ describe('Job App ', () => { complete: true, }); - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).toContain( - '50 bytes', - ); - done(); - }, 0); + setupAndMount({ + traceData: { + html: 'Update', + status: 'success', + append: false, + size: 50, + total: 100, + complete: true, + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).toContain( + '50 bytes', + ); + }) + .then(done) + .catch(done.fail); }); }); describe('when size is equal than total', () => { it('does not show the truncated information', done => { - mock.onGet(`${props.pagePath}/trace.json`).reply(200, { - html: 'Update', - status: 'success', - append: false, - size: 100, - total: 100, - complete: true, - }); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).not.toContain( - '50 bytes', - ); - done(); - }, 0); + setupAndMount({ + traceData: { + html: 'Update', + status: 'success', + append: false, + size: 100, + total: 100, + complete: true, + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).not.toContain( + '50 bytes', + ); + }) + .then(done) + .catch(done.fail); }); }); }); describe('trace controls', () => { - beforeEach(() => { - mock.onGet(`${props.pagePath}/trace.json`).reply(200, { - html: 'Update', - status: 'success', - append: false, - size: 50, - total: 100, - complete: true, - }); - - vm = mountComponentWithStore(Component, { - props, - store, - }); + beforeEach(done => { + setupAndMount({ + traceData: { + html: 'Update', + status: 'success', + append: false, + size: 50, + total: 100, + complete: true, + }, + }) + .then(done) + .catch(done.fail); }); - it('should render scroll buttons', done => { - setTimeout(() => { - expect(vm.$el.querySelector('.js-scroll-top')).not.toBeNull(); - expect(vm.$el.querySelector('.js-scroll-bottom')).not.toBeNull(); - done(); - }, 0); + it('should render scroll buttons', () => { + expect(vm.$el.querySelector('.js-scroll-top')).not.toBeNull(); + expect(vm.$el.querySelector('.js-scroll-bottom')).not.toBeNull(); }); - it('should render link to raw ouput', done => { - setTimeout(() => { - expect(vm.$el.querySelector('.js-raw-link-controller')).not.toBeNull(); - done(); - }, 0); + it('should render link to raw ouput', () => { + expect(vm.$el.querySelector('.js-raw-link-controller')).not.toBeNull(); }); - it('should render link to erase job', done => { - setTimeout(() => { - expect(vm.$el.querySelector('.js-erase-link')).not.toBeNull(); - done(); - }, 0); + it('should render link to erase job', () => { + expect(vm.$el.querySelector('.js-erase-link')).not.toBeNull(); }); }); });