diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js index c02e10128e2bb22c810b891ed8feaf7bb08e8f96..e8b3cf2f7292fd3c0bf521179ac46944bda6a687 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js @@ -17,6 +17,9 @@ export default { return hasCI && !ciStatus; }, + hasPipeline() { + return Object.keys(this.mr.pipeline || {}).length > 0; + }, svg() { return statusIconEntityMap.icon_status_failed; }, @@ -30,7 +33,11 @@ export default { template: ` <div class="mr-widget-heading"> <div class="ci-widget"> - <template v-if="hasCIError"> + <template v-if="!hasPipeline"> + <i class="fa fa-spinner fa-spin append-right-10" aria-hidden="true"></i> + Waiting for pipeline... + </template> + <template v-else-if="hasCIError"> <div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error"> <span class="js-icon-link icon-link"> <span diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js index 647b59520f8a1779ba35072082dbc03fe7efe0d8..4b6f171c8d6daffccb5abace8f7134e3c80ed785 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js @@ -76,6 +76,28 @@ describe('MRWidgetPipeline', () => { el = vm.$el; }); + afterEach(() => { + vm.$destroy(); + }); + + describe('without a pipeline', () => { + beforeEach(() => { + vm.mr = { pipeline: null }; + }); + + it('should render message with spinner', (done) => { + Vue.nextTick() + .then(() => { + expect(el.querySelector('.pipeline-id')).toBe(null); + expect(el.innerText.trim()).toBe('Waiting for pipeline...'); + expect(el.querySelectorAll('i.fa.fa-spinner.fa-spin').length).toBe(1); + done(); + }) + .then(done) + .catch(done.fail); + }); + }); + it('should render template elements correctly', () => { expect(el.classList.contains('mr-widget-heading')).toBeTruthy(); expect(el.querySelectorAll('.ci-status-icon.ci-status-icon-success').length).toEqual(1); @@ -93,39 +115,47 @@ describe('MRWidgetPipeline', () => { it('should list single stage', (done) => { pipeline.details.stages.splice(0, 1); - Vue.nextTick(() => { - expect(el.querySelectorAll('.stage-container button').length).toEqual(1); - expect(el.innerText).toContain('with stage'); - done(); - }); + Vue.nextTick() + .then(() => { + expect(el.querySelectorAll('.stage-container button').length).toEqual(1); + expect(el.innerText).toContain('with stage'); + }) + .then(done) + .catch(done.fail); }); it('should not have stages when there is no stage', (done) => { vm.mr.pipeline.details.stages = []; - Vue.nextTick(() => { - expect(el.querySelectorAll('.stage-container button').length).toEqual(0); - done(); - }); + Vue.nextTick() + .then(() => { + expect(el.querySelectorAll('.stage-container button').length).toEqual(0); + }) + .then(done) + .catch(done.fail); }); it('should not have coverage text when pipeline has no coverage info', (done) => { vm.mr.pipeline.coverage = null; - Vue.nextTick(() => { - expect(el.querySelector('.js-mr-coverage')).toEqual(null); - done(); - }); + Vue.nextTick() + .then(() => { + expect(el.querySelector('.js-mr-coverage')).toEqual(null); + }) + .then(done) + .catch(done.fail); }); it('should show CI error when there is a CI error', (done) => { vm.mr.ciStatus = null; - Vue.nextTick(() => { - expect(el.querySelectorAll('.js-ci-error').length).toEqual(1); - expect(el.innerText).toContain('Could not connect to the CI server'); - done(); - }); + Vue.nextTick() + .then(() => { + expect(el.querySelectorAll('.js-ci-error').length).toEqual(1); + expect(el.innerText).toContain('Could not connect to the CI server'); + }) + .then(done) + .catch(done.fail); }); }); });