diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 54fed3dd573ed4ceee5ee905b7a500b54919460c..3d284e5846f90810b0a27c3f0cce6e380885de8d 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -37,6 +37,7 @@ this.initScrollButtonAffix(); } if (this.buildStatus === "running" || this.buildStatus === "pending") { + // Bind autoscroll button to follow build output $('#autoscroll-button').on('click', function() { var state; state = $(this).data("state"); @@ -47,20 +48,15 @@ $(this).data("state", "enabled"); return $(this).text("Disable autoscroll"); } - // - // Bind autoscroll button to follow build output - // }); Build.interval = setInterval((function(_this) { + // Check for new build output if user still watching build page + // Only valid for runnig build when output changes during time return function() { - if (window.location.href.split("#").first() === _this.pageUrl) { + if (_this.location() === _this.pageUrl) { return _this.getBuildTrace(); } }; - // - // Check for new build output if user still watching build page - // Only valid for runnig build when output changes during time - // })(this), 4000); } } @@ -79,6 +75,10 @@ this.$document.off('scroll.translateSidebar').on('scroll.translateSidebar', this.translateSidebar.bind(this)); }; + Build.prototype.location = function() { + return window.location.href.split("#")[0]; + }; + Build.prototype.getInitialBuildTrace = function() { var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped'] diff --git a/spec/javascripts/build_spec.js.es6 b/spec/javascripts/build_spec.js.es6 new file mode 100644 index 0000000000000000000000000000000000000000..44485c7745cc19feedfbd1c825ce767ce019ba76 --- /dev/null +++ b/spec/javascripts/build_spec.js.es6 @@ -0,0 +1,174 @@ +/* eslint-disable */ +//= require build +//= require breakpoints +//= require jquery.nicescroll +//= require turbolinks + +(() => { + describe('Build', () => { + fixture.preload('build.html'); + + beforeEach(function() { + fixture.load('build.html'); + spyOn($, 'ajax'); + }); + + describe('constructor', () => { + beforeEach(function() { + jasmine.clock().install(); + }); + + afterEach(() => { + jasmine.clock().uninstall(); + }); + + describe('setup', function() { + beforeEach(function() { + this.build = new Build(); + }); + + it('copies build options', function() { + expect(this.build.pageUrl).toBe('http://example.com/root/test-build/builds/2'); + expect(this.build.buildUrl).toBe('http://example.com/root/test-build/builds/2.json'); + expect(this.build.buildStatus).toBe('passed'); + expect(this.build.buildStage).toBe('test'); + expect(this.build.state).toBe('buildstate'); + }); + + it('only shows the jobs matching the current stage', function() { + expect($('.build-job[data-stage="build"]').is(':visible')).toBe(false); + expect($('.build-job[data-stage="test"]').is(':visible')).toBe(true); + expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false); + }); + + it('selects the current stage in the build dropdown menu', function() { + expect($('.stage-selection').text()).toBe('test'); + }); + + it('updates the jobs when the build dropdown changes', function() { + $('.stage-item:contains("build")').click(); + + expect($('.stage-selection').text()).toBe('build'); + expect($('.build-job[data-stage="build"]').is(':visible')).toBe(true); + expect($('.build-job[data-stage="test"]').is(':visible')).toBe(false); + expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false); + }); + }); + + describe('initial build trace', function() { + beforeEach(function() { + new Build(); + }); + + it('displays the initial build trace', function() { + expect($.ajax.calls.count()).toBe(1); + const [{url, dataType, success, context}] = $.ajax.calls.argsFor(0); + expect(url).toBe('http://example.com/root/test-build/builds/2.json'); + expect(dataType).toBe('json'); + expect(success).toEqual(jasmine.any(Function)); + + success.call(context, {trace_html: '<span>Example</span>', status: 'running'}); + + expect($('#build-trace .js-build-output').text()).toMatch(/Example/); + }); + + it('removes the spinner', function() { + const [{success, context}] = $.ajax.calls.argsFor(0); + success.call(context, {trace_html: '<span>Example</span>', status: 'success'}); + + expect($('.js-build-refresh').length).toBe(0); + }); + }); + + describe('running build', function() { + beforeEach(function() { + $('.js-build-options').data('buildStatus', 'running'); + this.build = new Build(); + spyOn(this.build, 'location') + .and.returnValue('http://example.com/root/test-build/builds/2'); + }); + + it('updates the build trace on an interval', function() { + jasmine.clock().tick(4001); + + expect($.ajax.calls.count()).toBe(2); + let [{url, dataType, success, context}] = $.ajax.calls.argsFor(1); + expect(url).toBe( + 'http://example.com/root/test-build/builds/2/trace.json?state=buildstate' + ); + expect(dataType).toBe('json'); + expect(success).toEqual(jasmine.any(Function)); + + success.call(context, { + html: '<span>Update<span>', + status: 'running', + state: 'newstate', + append: true + }); + + expect($('#build-trace .js-build-output').text()).toMatch(/Update/); + expect(this.build.state).toBe('newstate'); + + jasmine.clock().tick(4001); + + expect($.ajax.calls.count()).toBe(3); + [{url, dataType, success, context}] = $.ajax.calls.argsFor(2); + expect(url).toBe( + 'http://example.com/root/test-build/builds/2/trace.json?state=newstate' + ); + expect(dataType).toBe('json'); + expect(success).toEqual(jasmine.any(Function)); + + success.call(context, { + html: '<span>More</span>', + status: 'running', + state: 'finalstate', + append: true + }); + + expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/); + expect(this.build.state).toBe('finalstate'); + }); + + it('replaces the entire build trace', function() { + jasmine.clock().tick(4001); + let [{success, context}] = $.ajax.calls.argsFor(1); + success.call(context, { + html: '<span>Update</span>', + status: 'running', + append: true + }); + + expect($('#build-trace .js-build-output').text()).toMatch(/Update/); + + jasmine.clock().tick(4001); + [{success, context}] = $.ajax.calls.argsFor(2); + success.call(context, { + html: '<span>Different</span>', + status: 'running', + append: false + }); + + expect($('#build-trace .js-build-output').text()).not.toMatch(/Update/); + expect($('#build-trace .js-build-output').text()).toMatch(/Different/); + }); + + it('reloads the page when the build is done', function() { + spyOn(Turbolinks, 'visit'); + + jasmine.clock().tick(4001); + let [{success, context}] = $.ajax.calls.argsFor(1); + success.call(context, { + html: '<span>Final</span>', + status: 'passed', + append: true + }); + + expect(Turbolinks.visit).toHaveBeenCalledWith( + 'http://example.com/root/test-build/builds/2' + ); + }); + }); + }); + }); +})(); diff --git a/spec/javascripts/fixtures/build.html.haml b/spec/javascripts/fixtures/build.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..a2bc81c6be7f93b1dd496d09f1377419eb8728f5 --- /dev/null +++ b/spec/javascripts/fixtures/build.html.haml @@ -0,0 +1,57 @@ +.build-page + .prepend-top-default + .autoscroll-container + %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll + #js-build-scroll.scroll-controls + %a.btn{href: '#build-trace'} + %i.fa.fa-angle-up + %a.btn{href: '#down-build-trace'} + %i.fa.fa-angle-down + %pre.build-trace#build-trace + %code.bash.js-build-output + %i.fa.fa-refresh.fa-spin.js-build-refresh + +%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar + .block.build-sidebar-header.visible-xs-block.visible-sm-block.append-bottom-default + Build + %strong #1 + %a.gutter-toggle.pull-right.js-sidebar-build-toggle{ href: "#" } + %i.fa.fa-angle-double-right + .blocks-container + .dropdown.build-dropdown + .title Stage + %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} + %span.stage-selection More + %i.fa.fa-caret-down + %ul.dropdown-menu + %li + %a.stage-item build + %li + %a.stage-item test + %li + %a.stage-item deploy + .builds-container + .build-job{data: {stage: 'build'}} + %a{href: 'http://example.com/root/test-build/builds/1'} + %i.fa.fa-check + %i.fa.fa-check-circle-o + %span + Setup + .build-job{data: {stage: 'test'}} + %a{href: 'http://example.com/root/test-build/builds/2'} + %i.fa.fa-check + %i.fa.fa-check-circle-o + %span + Tests + .build-job{data: {stage: 'deploy'}} + %a{href: 'http://example.com/root/test-build/builds/3'} + %i.fa.fa-check + %i.fa.fa-check-circle-o + %span + Deploy + +.js-build-options{ data: { page_url: 'http://example.com/root/test-build/builds/2', + build_url: 'http://example.com/root/test-build/builds/2.json', + build_status: 'passed', + build_stage: 'test', + state1: 'buildstate' }}