diff --git a/assets/scripts/app/templates/jobs/log.hbs b/assets/scripts/app/templates/jobs/log.hbs index b0f49128..c601a99a 100644 --- a/assets/scripts/app/templates/jobs/log.hbs +++ b/assets/scripts/app/templates/jobs/log.hbs @@ -5,12 +5,7 @@ this should probably be refactored to use container view}} {{#with view.job.log}} {{#if id}} - {{#view view.PreView logBinding="view.context.log" logUrlBinding="view.logUrl"}} -

-          
-          
-        
- {{/view}} + {{view Travis.PreView logBinding="view.context.log" logUrlBinding="view.logUrl"}} {{/if}} {{/with}} diff --git a/assets/scripts/app/templates/jobs/pre.hbs b/assets/scripts/app/templates/jobs/pre.hbs new file mode 100644 index 00000000..72cb78d1 --- /dev/null +++ b/assets/scripts/app/templates/jobs/pre.hbs @@ -0,0 +1,4 @@ +

+  
+  
+
diff --git a/assets/scripts/app/views/job.coffee b/assets/scripts/app/views/job.coffee index 57c6b52b..5b400cb7 100644 --- a/assets/scripts/app/views/job.coffee +++ b/assets/scripts/app/views/job.coffee @@ -128,79 +128,111 @@ Travis.app.get('router').urlForEvent(event, repo, item) ).property('job.repo', 'parentView.currentItem') - PreView: Em.View.extend - init: -> - @_super.apply this, arguments - @set 'logManager', Travis.Log.create(target: this) + PreView: Em.View.extend + templateName: 'jobs/pre' + init: -> + @_super.apply this, arguments + @set 'logManager', Travis.Log.create(target: this) - didInsertElement: -> - @_super.apply this, arguments + didInsertElement: -> + @_super.apply this, arguments - Ember.run.next this, -> - if @get 'log.isInitialized' - @logDidChange() + Ember.run.next this, -> + if @get 'log.isInitialized' + @logDidChange() - willDestroy: -> - @get('logManager').destroy() - @get('log.parts').removeArrayObserver this, + willDestroy: -> + @get('logManager').destroy() + @get('log.parts').removeArrayObserver this, + didChange: 'logContentsDidChange' + willChange: 'logContentsWillChange' + + version: (-> + @rerender() + @set 'logManager', Travis.Log.create(target: this) + ).observes('log.version') + + logDidChange: (-> + if @get('log.isInitialized') && @state == 'inDOM' + @attachLogObservers() + ).observes('log', 'log.isInitialized') + + attachLogObservers: -> + return if @get('logPartsObserversAttached') == Ember.guidFor(@get('log')) + @set 'logPartsObserversAttached', Ember.guidFor(@get('log')) + + Ember.run.next this, -> + @get('logManager').append @get('log.parts') + + @get('log.parts').addArrayObserver this, didChange: 'logContentsDidChange' willChange: 'logContentsWillChange' - version: (-> - @rerender() - @set 'logManager', Travis.Log.create(target: this) - ).observes('log.version') + logContentsDidChange: (lines, index, removedCount, addedCount) -> + addedLines = lines.slice(index, index + addedCount) + @get('logManager').append addedLines - logDidChange: (-> - if @get('log.isInitialized') && @state == 'inDOM' - @attachLogObservers() - ).observes('log', 'log.isInitialized') + logContentsWillChange: (-> ) - attachLogObservers: -> - return if @get('logPartsObserversAttached') == Ember.guidFor(@get('log')) - @set 'logPartsObserversAttached', Ember.guidFor(@get('log')) + appendLog: (payloads) -> + url = @get('logUrl') - Ember.run.next this, -> - @get('logManager').append @get('log.parts') + leftOut = [] + fragment = document.createDocumentFragment() - @get('log.parts').addArrayObserver this, - didChange: 'logContentsDidChange' - willChange: 'logContentsWillChange' + # TODO: refactor this loop, it's getting messy + for payload in payloads + line = payload.content + number = payload.number - logContentsDidChange: (lines, index, removedCount, addedCount) -> - addedLines = lines.slice(index, index + addedCount) - @get('logManager').append addedLines + unless payload.append + pathWithNumber = "#{url}#L#{number}" + p = document.createElement('p') + p.innerHTML = '%@%@'.fmt(pathWithNumber, number, number, number, line) + line = p - logContentsWillChange: (-> ) + if payload.fold && !payload.foldContinuation + div = document.createElement('div') + div.appendChild line + div.className = "fold #{payload.fold} show-first-line" + line = div - appendLog: (payloads) -> - url = @get('logUrl') - - for payload in payloads - line = payload.content - number = payload.number - - unless payload.append - pathWithNumber = "#{url}#L#{number}" - line = '

%@%@

'.fmt(pathWithNumber, number, number, number, line) - - if payload.fold && !payload.foldContinuation - line = "
#{line}
" - - if payload.replace - this.$("#log #L#{number}").parent().replaceWith line - else if payload.append - this.$("#log #L#{number}").parent().append line - else if payload.foldContinuation - this.$("#log .fold.#{payload.fold}:last").append line + if payload.replace + if link = fragment.querySelector("#L#{number}") + link.parentElement.innerHTML = line.innerHTML else - this.$('#log').append(line) + this.$("#L#{number}").parent().replaceWith line + else if payload.append + if link = fragment.querySelector("#L#{number}") + link.parentElement.innerHTML += line + else + this.$("#L#{number}").parent().append line + else if payload.foldContinuation + folds = fragment.querySelectorAll(".fold.#{payload.fold}") + if fold = folds[folds.length - 1] + fold.appendChild line + else + this.$("#log .fold.#{payload.fold}:last").append line + else + fragment.appendChild(line) - if payload.openFold - this.$("#log .fold.#{payload.openFold}:last"). - removeClass('show-first-line'). - addClass('open') + if payload.openFold + folds = fragment.querySelectorAll(".fold.#{payload.fold}") + if fold = folds[folds.length - 1] + fold = $(fold) + else + fold = this.$(".fold.#{payload.fold}:last") - if payload.foldEnd - this.$("#log .fold.#{payload.fold}:last").removeClass('show-first-line') + fold.removeClass('show-first-line').addClass('open') + + if payload.foldEnd + folds = fragment.querySelectorAll(".fold.#{payload.fold}") + if fold = folds[folds.length - 1] + fold = $(fold) + else + fold = this.$(".fold.#{payload.fold}:last") + + fold.removeClass('show-first-line') + + this.$('#log')[0].appendChild fragment diff --git a/assets/scripts/spec/unit/log_spec.coffee b/assets/scripts/spec/unit/log_spec.coffee index 4c0d1ce9..a8ff7d1d 100644 --- a/assets/scripts/spec/unit/log_spec.coffee +++ b/assets/scripts/spec/unit/log_spec.coffee @@ -76,8 +76,8 @@ describe 'Travis.Log', -> { number: 8, fold: 'qux', foldContinuation: true }, { number: 9, fold: 'qux', foldContinuation: true, foldEnd: true }] - expect( lines[4] ).toEqual ['$ end', ''] - expect( options[4]).toEqual [{ number: 10 }, { number: 11 }] + expect( lines[4] ).toEqual ['$ end'] + expect( options[4]).toEqual [{ number: 10 }] it 'works properly when log is started with fold', -> fold = Em.Object.create name: 'foo', startPattern: /^\$ foo/, endPattern: /^\$/ @@ -97,8 +97,8 @@ describe 'Travis.Log', -> { number: 1, fold: 'foo' }, { number: 2, fold: 'foo', foldContinuation: true, foldEnd: true }] - expect( lines[1] ).toEqual ['$ bar', ''] - expect( options[1]).toEqual [{ number: 3 }, { number: 4 }] + expect( lines[1] ).toEqual ['$ bar'] + expect( options[1]).toEqual [{ number: 3 }] it 'works properly for 2 consecutive folds', -> fold = Em.Object.create name: 'foo', startPattern: /^\$ foo/, endPattern: /^\$/ @@ -124,8 +124,8 @@ describe 'Travis.Log', -> { number: 3, fold: 'foo' }, { number: 4, fold: 'foo', foldContinuation: true, foldEnd: true }] - expect( lines[2] ).toEqual ['$ bar', ''] - expect( options[2]).toEqual [{ number: 5 }, { number: 6 }] + expect( lines[2] ).toEqual ['$ bar'] + expect( options[2]).toEqual [{ number: 5 }] it 'works fine with not finalized fold', -> fold = Em.Object.create name: 'foo', startPattern: /^\$ foo/, endPattern: /^\$/ @@ -139,11 +139,10 @@ describe 'Travis.Log', -> lines = target.get('calls').map (call) -> call.lines options = target.get('calls').map (call) -> call.options - expect( lines[0] ).toEqual ['$ foo --foo', '1', ''] + expect( lines[0] ).toEqual ['$ foo --foo', '1'] expect( options[0]).toEqual [ { fold: 'foo', number: 1 }, - { fold: 'foo', number: 2, foldContinuation: true }, - { fold: 'foo', number: 3, foldContinuation: true }] + { fold: 'foo', number: 2, foldContinuation: true }] it 'allows to continue fold', -> fold = Em.Object.create name: 'foo', startPattern: /^\$ foo/, endPattern: /^\$/ @@ -164,25 +163,23 @@ describe 'Travis.Log', -> lines = target.get('calls').map (call) -> call.lines options = target.get('calls').map (call) -> call.options - expect( lines[0] ).toEqual ['$ foo --foo', '1', ''] + expect( lines[0] ).toEqual ['$ foo --foo', '1'] expect( options[0]).toEqual [ { fold: 'foo', number: 1 }, - { fold: 'foo', number: 2, foldContinuation: true }, - { fold: 'foo', number: 3, foldContinuation: true }] + { fold: 'foo', number: 2, foldContinuation: true }] - expect( lines[1] ).toEqual ['2', ''] + expect( lines[1] ).toEqual ['2'] expect( options[1]).toEqual [ - { fold: 'foo', number: 3, foldContinuation: true, append: true } - { fold: 'foo', number: 4, foldContinuation: true } + { fold: 'foo', number: 3, foldContinuation: true } ] expect( lines[2] ).toEqual ['3'] expect( options[2]).toEqual [ - { fold: 'foo', number: 4, foldContinuation: true, append: true, foldEnd: true } + { fold: 'foo', number: 4, foldContinuation: true, foldEnd: true } ] - expect( lines[3] ).toEqual ['$ bar', ''] - expect( options[3]).toEqual [{ number: 5 }, { number: 6 }] + expect( lines[3] ).toEqual ['$ bar'] + expect( options[3]).toEqual [{ number: 5 }] it 'notifies that the line should be appended', -> log.append '$ foo\n.' @@ -201,8 +198,8 @@ describe 'Travis.Log', -> expect( lines[1] ).toEqual ['...'] expect( options[1]).toEqual [{ append: true, number: 2 }] - expect( lines[2] ).toEqual ['..', '$ bar', ''] - expect( options[2]).toEqual [{ append: true, number: 2 }, { number: 3 }, { number: 4 }] + expect( lines[2] ).toEqual ['..', '$ bar'] + expect( options[2]).toEqual [{ append: true, number: 2 }, { number: 3 }] it 'notifies that the line should be replaced', -> log.append '$ foo\n' @@ -216,17 +213,17 @@ describe 'Travis.Log', -> lines = target.get('calls').map (call) -> call.lines options = target.get('calls').map (call) -> call.options - expect( lines[0] ).toEqual ['$ foo', ''] - expect( options[0]).toEqual [{ number: 1 }, { number: 2 }] + expect( lines[0] ).toEqual ['$ foo'] + expect( options[0]).toEqual [{ number: 1 }] expect( lines[1] ).toEqual ['', 'Downloading 50%'] - expect( options[1]).toEqual [{ number: 2, append: true }, { number: 2, replace: true }] + expect( options[1]).toEqual [{ number: 2 }, { number: 2, replace: true }] - expect( lines[2] ).toEqual ['', 'Downloading 100%', ''] - expect( options[2]).toEqual [{ number: 2, append: true }, { number: 2, replace: true }, { number: 3 }] + expect( lines[2] ).toEqual ['', 'Downloading 100%'] + expect( options[2]).toEqual [{ number: 2, append: true }, { number: 2, replace: true }] - expect( lines[3] ).toEqual ['$ bar', ''] - expect( options[3]).toEqual [{ number: 3, append: true }, { number: 4 }] + expect( lines[3] ).toEqual ['$ bar'] + expect( options[3]).toEqual [{ number: 3 }] it 'notifies that the line should be replaced even if carriage return is in the middle', -> diff --git a/assets/scripts/spec/unit/pre_view_spec.coffee b/assets/scripts/spec/unit/pre_view_spec.coffee new file mode 100644 index 00000000..c1a95ce5 --- /dev/null +++ b/assets/scripts/spec/unit/pre_view_spec.coffee @@ -0,0 +1,161 @@ +view = null +store = null +record = null + +describe 'Travis.PreView', -> + beforeEach -> + store = Travis.Store.create() + + afterEach -> + store.destroy() + view.remove() + view.destroy() + + it 'works fine with existing log, which is appended', -> + store.load Travis.Artifact, 1, { id: 1, body: '$ start\n' } + log = Travis.Artifact.find(1) + log.set('version', 1) + + Ember.run -> + view = Travis.PreView.create(log: null) + view.append() + + expect( view.$('#log').length ).toEqual 1 + + Ember.run -> + view.set 'log', log + log.set 'isLoaded', true + + waits 50 + runs -> + expect( view.$('#log p').length ).toEqual 1 + expect( view.$('#log p').text().trim() ).toEqual '1$ start' + + Ember.run -> + log.append('$ end') + + waits 50 + runs -> + expect( view.$('#log p').length ).toEqual 2 + expect( view.$('#log p').text().trim() ).toEqual '1$ start2$ end' + + it 'works fine with log already attahed to view', -> + store.load Travis.Artifact, 1, { id: 1, body: '$ start\n' } + log = Travis.Artifact.find(1) + + Ember.run -> + view = Travis.PreView.create() + view.set('log', log) + view.append() + + Ember.run -> + log.append('end') + + waits 50 + runs -> + expect( view.$('#log p').length ).toEqual 2 + expect( view.$('#log p').text().trim() ).toEqual '1$ start2end' + + it 'folds items', -> + store.load Travis.Artifact, 1, { id: 1, body: '$ start\n' } + log = Travis.Artifact.find(1) + + Ember.run -> + view = Travis.PreView.create() + view.set('log', log) + view.append() + + Ember.run -> + log.append '$ bundle install\n1\n2\n' + + Ember.run -> + log.append '3\n4\n$ something' + + waits 50 + runs -> + expect( view.$('#log > p').length ).toEqual 2 + expect( view.$('#log .fold.bundle').length ).toEqual 1 + expect( view.$('#log .fold.bundle > p').length ).toEqual 5 + + + it 'works properly with fragment document', -> + store.load Travis.Artifact, 1, { id: 1, body: '' } + log = Travis.Artifact.find(1) + + Ember.run -> + view = Travis.PreView.create() + view.set('log', log) + view.append() + + waits 50 + runs -> + payloads = [ + { number: 1, content: 'foo' } + { number: 1, content: 'bar', append: true } + ] + + # it should work even if we need to append to fragment in memory + view.appendLog(payloads) + + expect( view.$('#L1').parent().text().trim() ).toEqual '1foobar' + + # now, let's append more to this line, it's in DOM already + view.appendLog([ { number: 1, content: 'baz', append: true } ]) + + expect( view.$('#L1').parent().text().trim() ).toEqual '1foobarbaz' + + payloads = [ + { number: 1, content: 'foo', replace: true } + ] + # replace should work in DOM + view.appendLog(payloads) + expect( view.$('#L1').parent().text().trim() ).toEqual '1foo' + + payloads = [ + { number: 2, content: 'foo' } + { number: 2, content: 'bar', replace: true } + ] + # replace should work when element is in fragment + view.appendLog(payloads) + expect( view.$('#L2').parent().text().trim() ).toEqual '2bar' + + payloads = [ + { number: 3, content: '$ bundle install', fold: 'bundle' } + { number: 4, content: 'Installing rails', fold: 'bundle', foldContinuation: true } + ] + # folds should work properly with fragment + view.appendLog(payloads) + expect( view.$('.bundle #L3').parent().text().trim() ).toEqual '3$ bundle install' + expect( view.$('.bundle #L4').parent().text().trim() ).toEqual '4Installing rails' + expect( view.$('.bundle > p').length ).toEqual 2 + + payloads = [ + { number: 5, content: 'Installing travis', fold: 'bundle', foldContinuation: true } + ] + # folds should also work when already in DOM + view.appendLog(payloads) + expect( view.$('.bundle #L5').parent().text().trim() ).toEqual '5Installing travis' + expect( view.$('.bundle > p').length ).toEqual 3 + + # regular line append + view.appendLog([ { number: 6, content: 'next'} ]) + expect( view.$('#L6').parent().text().trim() ).toEqual '6next' + + # openFold when in fragment + payloads = [ + { number: 7, content: '$ install', fold: 'install' } + { number: 8, content: 'Installing foo', fold: 'install', foldContinuation: true } + { number: 9, content: 'error', openFold: true, fold: 'install', foldContinuation: true } + ] + # folds should work properly with fragment + view.appendLog(payloads) + expect( view.$('.install').hasClass('show-first-line') ).toEqual false + + # end fold when in fragment + payloads = [ + { number: 10, content: '$ install', fold: 'install2' } + { number: 11, content: 'Installing foo', fold: 'install2', foldEnd: true, foldContinuation: true } + ] + # folds should work properly with fragment + view.appendLog(payloads) + expect( view.$('.install2').hasClass('show-first-line') ).toEqual false diff --git a/assets/scripts/spec/unit/views/log_view_spec.coffee b/assets/scripts/spec/unit/views/log_view_spec.coffee deleted file mode 100644 index fc6394a2..00000000 --- a/assets/scripts/spec/unit/views/log_view_spec.coffee +++ /dev/null @@ -1,74 +0,0 @@ -view = null -store = null -record = null - -describe 'Travis.LogView', -> - beforeEach -> - store = Travis.Store.create() - - afterEach -> - store.destroy() - view.remove() - view.destroy() - - it 'works fine with existing log, which is appended', -> - store.load Travis.Artifact, 1, { id: 1, body: '$ start' } - log = Travis.Artifact.find(1) - - Ember.run -> - view = Travis.LogView.create(context: null) - view.append() - - expect( $('#log').length ).toEqual 1 - console.log $('#log') - - job = Ember.Object.create log: log, subscribe: (-> ) - - Ember.run -> - view.set 'context', job - log.set 'isLoaded', true - - expect( view.$('#log p').length ).toEqual 1 - expect( view.$('#log p').text().trim() ).toEqual '1$ start' - - Ember.run -> - log.append('$ end') - - expect( view.$('#log p').length ).toEqual 2 - expect( view.$('#log p').text().trim() ).toEqual '1$ start2$ end' - - it 'works fine with log already attahed to view', -> - store.load Travis.Artifact, 1, { id: 1, body: '$ start' } - log = Travis.Artifact.find(1) - job = Ember.Object.create log: log, subscribe: (-> ) - - Ember.run -> - view = Travis.LogView.create() - view.set('context', job) - view.append() - - Ember.run -> - log.append('end') - - expect( view.$('#log p').length ).toEqual 2 - expect( view.$('#log p').text().trim() ).toEqual '1$ start2end' - - it 'folds items', -> - store.load Travis.Artifact, 1, { id: 1, body: '$ start' } - log = Travis.Artifact.find(1) - job = Ember.Object.create log: log, subscribe: (-> ) - - Ember.run -> - view = Travis.LogView.create() - view.set('context', job) - view.append() - - Ember.run -> - log.append '$ bundle install\n1\n2' - - Ember.run -> - log.append '3\n4\n$ something' - - expect( view.$('#log > p').length ).toEqual 2 - expect( view.$('#log .fold.bundle').length ).toEqual 1 - expect( view.$('#log .fold.bundle > p').length ).toEqual 5