Merge branch 'master' into branches-redux

This commit is contained in:
Lisa P 2015-09-07 11:51:30 +02:00
commit 8d5e7d5b87
46 changed files with 111 additions and 272 deletions

View File

@ -28,6 +28,5 @@
"strict": false,
"white": false,
"eqnull": true,
"esnext": true,
"unused": true
"esnext": true
}

View File

@ -3,19 +3,6 @@
`import loadInitializers from 'ember/load-initializers'`
`import config from './config/environment'`
`import mb from 'travis/helpers/mb'`
`import label from 'travis/helpers/label'`
`import travisField from 'travis/helpers/travis-field'`
`import travisErrors from 'travis/helpers/travis-errors'`
#`import input from 'travis/helpers/input'`
`import filterInput from 'travis/helpers/filter-input'`
Ember.HTMLBars._registerHelper('label', label)
Ember.HTMLBars._registerHelper('travis-field', travisField)
Ember.HTMLBars._registerHelper('travis-errors', travisErrors)
#Ember.Handlebars.registerHelper('input', input)
Ember.HTMLBars._registerHelper('filter-input', filterInput)
Ember.Handlebars.registerBoundHelper('mb', mb)
Ember.MODEL_FACTORY_INJECTIONS = true
App = Ember.Application.extend(Ember.Evented,

View File

@ -10,17 +10,17 @@ AddSshKeyComponent = Ember.Component.extend
didInsertElement: () ->
id = @get('repo.id')
model = @get('store').recordForId('sshKey', id)
model = @get('store').recordForId('ssh_key', id)
# TODO: this can be removed in favor of simply unloading record
# once https://github.com/emberjs/data/pull/2867
# and https://github.com/emberjs/data/pull/2870 are merged
if model
@get('store').dematerializeRecord(model)
typeMap = @get('store').typeMapFor('sshKey')
@get('store').dematerializeRecord(model._internalModel)
typeMap = @get('store').typeMapFor(model.constructor)
idToRecord = typeMap.idToRecord
delete idToRecord[id]
model = @get('store').createRecord('sshKey', id: id)
model = @get('store').createRecord('ssh_key', id: id)
@set('model', model)
isValid: () ->

View File

@ -1,34 +1,37 @@
`import Ember from 'ember'`
`import LimitedArray from 'travis/utils/limited-array'`
`import Broadcast from 'travis/models/broadcast'`
Controller = Ember.ArrayController.extend
needs: ['currentUser']
currentUserBinding: 'controllers.currentUser.model'
FlashDisplayComponent = Ember.Component.extend
auth: Ember.inject.service()
store: Ember.inject.service()
currentUserBinding: 'auth.currentUser'
classNames: ['flash']
tagName: 'ul'
init: ->
@_super.apply this, arguments
@set('flashes', LimitedArray.create(limit: 1, content: []))
model: (->
messages: (->
broadcasts = @get('unseenBroadcasts')
flashes = @get('flashes')
model = []
model.pushObjects(broadcasts) if broadcasts
model.pushObjects(flashes.toArray().reverse()) if flashes
model.uniq()
).property('unseenBroadcasts.[]', 'flashes.[]')
).property('unseenBroadcasts.[]', 'flashes.[]', 'unseenBroadcasts.length', 'flashes.length')
unseenBroadcasts: (->
@get('broadcasts').filter (broadcast) ->
!broadcast.get('isSeen')
).property('broadcasts.[]')
).property('broadcasts.[]', 'broadcasts.length')
broadcasts: (->
broadcasts = Ember.ArrayProxy.create(content: [])
if @get('currentUser.id')
@store.find('broadcast').then (result) ->
@get('store').find('broadcast').then (result) ->
broadcasts.pushObjects(result.toArray())
broadcasts
@ -36,20 +39,20 @@ Controller = Ember.ArrayController.extend
loadFlashes: (msgs) ->
for msg in msgs
type = Ember.keys(msg)[0]
type = Object.keys(msg)[0]
msg = { type: type, message: msg[type] }
@get('flashes').unshiftObject(msg)
Ember.run.later(this, (-> @get('flashes.content').removeObject(msg)), 15000)
close: (msg) ->
if msg instanceof Broadcast
if msg.constructor.modelName == "broadcast"
msg.setSeen()
@notifyPropertyChange('unseenBroadcasts')
else
@get('flashes').removeObject(msg)
actions:
close: (msg) ->
closeMessage: (msg) ->
@close(msg)
`export default Controller`
`export default FlashDisplayComponent`

View File

@ -1,6 +1,6 @@
`import BasicView from 'travis/views/basic'`
`import Ember from 'ember'`
View = BasicView.extend
FlashItemComponent = Ember.Component.extend
tagName: 'li'
classNameBindings: ['type']
@ -10,6 +10,6 @@ View = BasicView.extend
actions:
close: ->
@get('controller').close(@get('flash'))
this.attrs.close(@get('flash'))
`export default View`
`export default FlashItemComponent`

View File

@ -17,5 +17,4 @@ SshKeyComponent = Ember.Component.extend
@get('key').save().then(deletingDone, deletingDone).then =>
@sendAction('sshKeyDeleted')
`export default SshKeyComponent`

View File

@ -23,11 +23,11 @@ Controller = Ember.Controller.extend
tabOrIsLoadedDidChange: (->
@possiblyRedirectToGettingStartedPage()
).observes('isLoaded', 'tab', 'length')
).observes('isLoaded', 'tab', 'repos.length')
possiblyRedirectToGettingStartedPage: ->
Ember.run.scheduleOnce 'routerTransitions', this, ->
if @get('tab') == 'owned' && @get('isLoaded') && @get('length') == 0
if @get('tab') == 'owned' && @get('isLoaded') && @get('repos.length') == 0
@container.lookup('router:main').send('redirectToGettingStarted')
isLoadedBinding: 'repos.isLoaded'

View File

@ -1,73 +0,0 @@
`import Ember from 'ember'`
`import Validations from 'travis/utils/validations'`
Controller = Ember.ObjectController.extend Validations,
isEditing: false
isSaving: false
isDeleting: false
defaultKey: null
needs: ['repo']
repo: Ember.computed.alias('controllers.repo.repo')
validates:
value: ['presence']
reset: ->
@set('isEditing', false)
actions:
add: ->
id = @get('repo.id')
model = @store.recordForId('sshKey', id)
# TODO: this can be removed in favor of simply unloading record
# once https://github.com/emberjs/data/pull/2867
# and https://github.com/emberjs/data/pull/2870 are merged
if model
@store.dematerializeRecord(model)
typeMap = @store.typeMapFor('sshKey')
idToRecord = typeMap.idToRecord
delete idToRecord[id]
model = @store.createRecord('sshKey', id: id)
@set('model', model)
@set('isEditing', true)
save: ->
return if @get('isSaving')
@set('isSaving', true)
if @isValid()
@get('model').save().then =>
@set('isEditing', false)
@set('isSaving', false)
, (error) =>
@set('isSaving', false)
if error.errors
@addErrorsFromResponse(error.errors)
else
@set('isSaving', false)
delete: ->
return if @get('isDeleting')
@set('isDeleting', true)
deletingDone = => @set('isDeleting', false)
@get('model').deleteRecord()
@get('model').save().then(deletingDone, deletingDone).then =>
@set('model', null)
cancel: ->
if model = @get('model')
if model.get('currentState.stateName') == 'root.empty' ||
model.get('currentState.stateName').indexOf('root.loaded.created') != -1
@store.dematerializeRecord(model)
@set('model', null)
@set('isEditing', false)
edit: ->
@set('isEditing', true)
`export default Controller`

View File

@ -1,10 +0,0 @@
`import { safe } from 'travis/utils/helpers'`
`import Ember from "ember"`
helper = Ember.Handlebars.makeBoundHelper (value, options) ->
if value?
safe $.capitalize(value)
else
''
`export default helper`

View File

@ -1,6 +1,7 @@
`import { safe, formatCommit as formatCommitHelper } from 'travis/utils/helpers'`
helper = Ember.Handlebars.makeBoundHelper (commit) ->
helper = Ember.HTMLBars.makeBoundHelper (params) ->
commit = params[0]
safe formatCommitHelper(commit.get('sha'), commit.get('branch')) if commit
`export default helper`

View File

@ -1,7 +1,7 @@
`import { timeInWords, safe } from 'travis/utils/helpers'`
`import Ember from "ember"`
helper = Ember.Handlebars.makeBoundHelper (duration, options) ->
safe timeInWords(duration)
helper = Ember.HTMLBars.makeBoundHelper (params) ->
safe timeInWords(params[0])
`export default helper`

View File

@ -1,7 +1,7 @@
`import { formatSha as _formatSha, safe } from 'travis/utils/helpers'`
`import Ember from "ember"`
helper = Ember.Handlebars.makeBoundHelper (sha) ->
safe _formatSha(sha)
helper = Ember.HTMLBars.makeBoundHelper (params) ->
safe _formatSha(params[0])
`export default helper`

View File

@ -1,7 +1,7 @@
`import { timeAgoInWords, safe } from 'travis/utils/helpers'`
`import Ember from "ember"`
helper = Ember.Handlebars.makeBoundHelper (value, options) ->
safe timeAgoInWords(value) || '-'
helper = Ember.HTMLBars.makeBoundHelper (params) ->
safe timeAgoInWords(params[0]) || '-'
`export default helper`

View File

@ -1,7 +1,9 @@
`import { formatCommit, safe } from 'travis/utils/helpers'`
`import { githubCommit as githubCommitUrl } from 'travis/utils/urls'`
helper = Ember.Handlebars.makeBoundHelper (slug, commitSha) ->
helper = Ember.HTMLBars.makeBoundHelper (params) ->
slug = params[0]
commitSha = params[1]
return '' unless commitSha
sha = Ember.Handlebars.Utils.escapeExpression formatCommit(commitSha)
return sha unless slug

View File

@ -1,7 +1,8 @@
`import { safe } from 'travis/utils/helpers'`
`import Ember from "ember"`
helper = Ember.Handlebars.makeBoundHelper (state) ->
helper = Ember.HTMLBars.makeBoundHelper (params) ->
state = params[0]
if state == 'received'
'booting'
else

View File

@ -1,30 +0,0 @@
`import Ember from 'ember'`
originalInputHelper = Ember.Handlebars.helpers.input
input = (options) ->
# for now I can match label only with the property name
# passed here matches the label
name = (options.hash.value || options.hash.checked)
id = options.hash.id
# generate id only if it's not given
if name && !name.match(/\./) && !id
labels = @get('_labels')
unless labels
labels = Ember.Object.create()
@set('_labels', labels)
# for now I support only label + input in their own context
id = labels.get(name)
unless id
id = "#{name}-#{Math.round(Math.random() * 1000000)}"
labels.set(name, id)
options.hash.id = id
options.hashTypes.id = 'STRING'
options.hashContexts.id = this
originalInputHelper.call(this, options)
`export default input`

View File

@ -1,7 +1,7 @@
`import { timeAgoInWords, safe } from 'travis/utils/helpers'`
`import Ember from "ember"`
helper = Ember.Handlebars.makeBoundHelper (value, options) ->
safe timeAgoInWords(value) || 'currently running'
helper = Ember.HTMLBars.makeBoundHelper (params) ->
safe timeAgoInWords(params[0]) || 'currently running'
`export default helper`

View File

@ -1,7 +1,7 @@
`import { timeAgoInWords, safe } from 'travis/utils/helpers'`
`import Ember from "ember"`
helper = Ember.Handlebars.makeBoundHelper (value, options) ->
safe moment(value).format('MMMM D, YYYY H:mm:ss') || '-'
helper = Ember.HTMLBars.makeBoundHelper (params) ->
safe moment(params[0]).format('MMMM D, YYYY H:mm:ss') || '-'
`export default helper`

View File

@ -1,7 +1,8 @@
`import { pathFrom } from 'travis/utils/helpers'`
`import Ember from "ember"`
helper = Ember.Handlebars.makeBoundHelper (url, options) ->
helper = Ember.HTMLBars.makeBoundHelper (params) ->
url = params[0]
path = pathFrom(url)
if path.indexOf('...') >= 0
shas = path.split('...')

View File

@ -1,24 +0,0 @@
`import Ember from 'ember'`
ErrorsView = Ember.View.extend
tagName: 'span'
templateName: 'helpers/travis-errors'
classNames: ['error']
classNameBindings: ['codes', 'show']
codes: (->
@get('errors').mapBy('code')
).property('@errors', 'errors.length')
show: Ember.computed.notEmpty('errors.[]')
fn = (params, hash, options, env) ->
name = params[0]
controller = env.data.view.get('controller')
errors = controller.get('errors').for(name)
view = ErrorsView.create(
controller: controller
errors: errors
)
env.helpers.view.helperFunction.call(this, [view], hash, options, env)
`export default fn`

View File

@ -1,25 +0,0 @@
`import Ember from 'ember'`
FormFieldRowView = Ember.View.extend
invalid: Ember.computed.notEmpty('errors.[]')
classNameBindings: ['invalid']
classNames: 'field'
fn = (params, hash, options, env) ->
name = params[0]
controller = env.data.view.get('controller')
errors = controller.get('errors').for(name)
template = options.template
delete options.template
view = FormFieldRowView.create(
controller: controller
template: template
errors: errors
name: name
classNameBindings: ['name']
)
env.helpers.view.helperFunction.call(this, [view], hash, options, env)
`export default fn`

View File

@ -4,4 +4,4 @@ fn = (size) ->
if size
(size / 1024 / 1024).toFixed(2)
`export default fn`
`export default Ember.HTMLBars.makeBoundHelper(fn)`

View File

@ -9,7 +9,7 @@ initialize = (container, app) ->
app.inject('application', 'auth', 'auth:main')
app.inject('component', 'auth', 'auth:main')
app.inject('auth', 'store', 'store:main')
app.inject('auth', 'store', 'service:store')
AuthInitializer =
name: 'auth'

View File

@ -1,7 +1,7 @@
`import config from 'travis/config/environment'`
`import TravisPusher from 'travis/utils/pusher'`
initialize = (container, application) ->
initialize = (registry, application) ->
if config.pusher.key
application.pusher = new TravisPusher(config.pusher)
@ -9,9 +9,6 @@ initialize = (container, application) ->
application.inject('route', 'pusher', 'pusher:main')
application.pusher.store = container.lookup('store:main')
PusherInitializer =
name: 'pusher'
after: 'ember-data'

View File

@ -0,0 +1,10 @@
initialize = (data) ->
data.application.pusher.store = data.container.lookup('service:store')
PusherInitializer =
name: 'pusher'
after: 'ember-data'
initialize: initialize
`export {initialize}`
`export default PusherInitializer`

View File

@ -18,7 +18,7 @@ Route = TravisRoute.extend
fetchCustomSshKey: () ->
repo = @modelFor('repo')
self = this
@store.find('sshKey', repo.get('id')).then ( (result) -> result unless result.get('isNew') ), (xhr) ->
@store.find('ssh_key', repo.get('id')).then ( (result) -> result unless result.get('isNew') ), (xhr) ->
if xhr.status == 404
# if there is no model, just return null. I'm not sure if this is the
# best answer, maybe we should just redirect to different route, like

View File

@ -8,7 +8,7 @@ Route = TravisRoute.extend
model: (params) ->
repo = @modelFor('repo')
self = this
@store.find('sshKey', repo.get('id')).then ( (result) -> result unless result.get('isNew') ), (xhr) ->
@store.find('ssh_key', repo.get('id')).then ( (result) -> result unless result.get('isNew') ), (xhr) ->
if xhr.status == 404
# if there is no model, just return null. I'm not sure if this is the
# best answer, maybe we should just redirect to different route, like

View File

@ -87,6 +87,4 @@ Store = DS.Store.extend
data = json.repository || json.repo
@pushPayload(repos: [data])
`export default Store`

View File

@ -5,7 +5,7 @@
<p class="tile-item caches-date column">{{format-time cache.last_modified}}</p>
<p class="tile-item caches-size column">{{mb cache.size}}MB</p>
<p class="tile-item caches-size column">{{travis-mb cache.size}}MB</p>
<p class="tile-item caches-button column">
<a href="#" {{action "delete"}} class="{{if isDeleting 'deleting'}} delete-by-slug delete button--delete">

View File

@ -0,0 +1,3 @@
{{#each flash in messages}}
{{flash-item flash=flash close=(action 'closeMessage')}}
{{/each}}

View File

@ -0,0 +1,2 @@
<p>{{{flash.message}}}</p>
<a class="close" {{action "close"}}></a>

View File

@ -31,7 +31,7 @@
<div class="duration">
<span class="icon-calendar"></span>
<span class="build-status">{{repo.default_branch.last_build.state}}</span>
<span>{{format-time repo.default_branch.last_build.finished_at}}</span>
<span class="finished-at">{{format-time repo.default_branch.last_build.finished_at}}</span>
</div>
{{else}}
<p>there is no build</p>

View File

@ -1,6 +1 @@
{{#each flash in controller}}
{{#view "flash-item" flashBinding="flash"}}
<p>{{{flash.message}}}</p>
<a class="close" {{action "close" target=view}}></a>
{{/view}}
{{/each}}

View File

@ -4,7 +4,7 @@
{{render "top"}}
</header>
{{render "flash"}}
{{flash-display}}
<div class="wrapper-main">
<div id="main" role="main">

View File

@ -2,7 +2,7 @@
{{render "top"}}
</div>
{{render "flash"}}
{{flash-display}}
{{yield}}

View File

@ -6,7 +6,7 @@
</header>
<div class="centered">
{{render "flash"}}
{{flash-display}}
<div id="main" class="main" role="main">
{{yield}}
@ -17,4 +17,4 @@
<footer>
{{render "footer"}}
</footer>
</footer>

View File

@ -6,7 +6,7 @@
</header>
<div class="centered">
{{render "flash"}}
{{flash-display}}
<div id="main" class="main" role="main">
{{yield}}
</div>
@ -15,4 +15,4 @@
<footer>
{{render "footer"}}
</footer>
</footer>

View File

@ -48,6 +48,7 @@ Auth = Ember.Object.extend
if @get('state') == 'signed-in'
'a-token'
refreshUserData: (->)
refreshUserData: ->
return Ember.RSVP.Promise.resolve()
`export default Auth`

View File

@ -1,8 +0,0 @@
`import BasicView from 'travis/views/basic'`
View = BasicView.extend
classNames: ['flash']
tagName: 'ul'
templateName: 'layouts/flash'
`export default View`

View File

@ -2,11 +2,11 @@
"name": "travis",
"dependencies": {
"handlebars": "2.0.0",
"jquery": "^1.11.1",
"ember": "1.13.6",
"ember-data": "1.0.0-beta.16.1",
"jquery": "^1.11.3",
"ember": "1.13.8",
"ember-data": "1.13.9",
"ember-resolver": "~0.1.15",
"loader.js": "ember-cli/loader.js#1.0.1",
"loader.js": "ember-cli/loader.js#3.2.1",
"ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3",
"ember-cli-test-loader": "ember-cli/ember-cli-test-loader#0.1.0",
"ember-load-initializers": "ember-cli/ember-load-initializers#0.1.4",

View File

@ -19,27 +19,28 @@
"author": "",
"license": "MIT",
"devDependencies": {
"broccoli-asset-rev": "^2.0.2",
"broccoli-asset-rev": "^2.1.2",
"broccoli-sass": "0.6.6",
"ember-cli": "1.13.7",
"ember-cli-app-version": "0.4.0",
"ember-cli": "1.13.8",
"ember-cli-app-version": "0.5.0",
"ember-cli-autoprefixer": "^0.3.0",
"ember-cli-babel": "5.0.0",
"ember-cli-babel": "5.1.3",
"ember-cli-coffeescript": "0.10.0",
"ember-cli-content-security-policy": "0.4.0",
"ember-cli-dependency-checker": "0.0.8",
"ember-cli-dependency-checker": "1.0.1",
"ember-cli-document-title": "0.1.0",
"ember-cli-htmlbars": "0.7.9",
"ember-cli-htmlbars-inline-precompile": "^0.1.1",
"ember-cli-htmlbars-inline-precompile": "^0.2.0",
"ember-cli-ic-ajax": "0.2.1",
"ember-cli-inject-live-reload": "^1.3.0",
"ember-cli-inject-live-reload": "^1.3.1",
"ember-cli-inline-images": "^0.0.4",
"ember-cli-pretender": "0.3.1",
"ember-cli-qunit": "0.3.20",
"ember-cli-qunit": "1.0.0",
"ember-cli-sri": "^1.0.3",
"ember-cli-release": "0.2.3",
"ember-cli-sauce": "^1.1.0",
"ember-cli-uglify": "1.0.1",
"ember-data": "1.13.5",
"ember-cli-uglify": "1.2.0",
"ember-data": "1.13.9",
"ember-export-application-global": "^1.0.2",
"ember-try": "0.0.7"
}

View File

@ -36,6 +36,9 @@ test('it adds an env var on submit', function(assert) {
assert.equal(envVar.get('value'), 'bar', 'value should be set for the env var');
assert.equal(envVar.get('repo.slug'), 'travis-ci/travis-web', 'repo should be set for the env var');
assert.ok(!envVar.get('public'), 'env var should be private');
var done = assert.async();
setTimeout(function() { done(); }, 500);
});
test('it shows an error if no name is present', function(assert) {
@ -86,4 +89,7 @@ test('it adds a public env var on submit', function(assert) {
assert.equal(envVar.get('value'), 'bar', 'value should be set for the env var');
assert.equal(envVar.get('repo.slug'), 'travis-ci/travis-web', 'repo should be set for the env var');
assert.ok(envVar.get('public'), 'env var should be public');
var done = assert.async();
setTimeout(function() { done(); }, 500);
});

View File

@ -22,7 +22,7 @@ test('it adds an ssh key on submit', function(assert) {
this.render(hbs`{{add-ssh-key repo=repo sshKeyAdded="sshKeyAdded"}}`);
var sshKey = store.all('sshKey').objectAt(0);
var sshKey = store.all('ssh_key').objectAt(0);
assert.ok(! sshKey.get('description'), 'description should be blank');
assert.ok(! sshKey.get('value'), 'value should be blank');
@ -37,6 +37,8 @@ test('it adds an ssh key on submit', function(assert) {
assert.equal(sshKey.get('value'), 'bar', 'value should be set');
assert.equal(sshKey.get('id'), 1, 'ssh key id should still be repo id');
var done = assert.async();
setTimeout(function() { done(); }, 500);
});
@ -54,7 +56,7 @@ test('it throws an error if value for ssh key is blank', function(assert) {
this.render(hbs`{{add-ssh-key repo=repo sshKeyAdded="sshKeyAdded"}}`);
var sshKey = store.all('sshKey').objectAt(0);
var sshKey = store.all('ssh_key').objectAt(0);
assert.ok(! sshKey.get('description'), 'description should be blank');
assert.ok(! sshKey.get('value'), 'value should be blank');

View File

@ -59,6 +59,9 @@ test('it deletes a custom key if permissions are right', function(assert) {
assert.ok(key.get('isDeleted'), 'key should be deleted');
// we don't deal with saving records for now, so at least wait till it's done
var done = assert.async();
setTimeout(function() { done(); }, 500);
});
test('it does not delete the custom key if permissions are insufficient', function(assert) {
@ -76,4 +79,4 @@ test('it does not delete the custom key if permissions are insufficient', functi
assert.ok(Ember.isEmpty(this.$('.ssh-key-action').find('a')), 'delete link should not be displayed');
});
});

View File

@ -1,7 +1,7 @@
`import { test, moduleForComponent } from 'ember-qunit'`
moduleForComponent 'caches-item', 'CachesItemComponent', {
needs: ['helper:format-time', 'helper:mb']
needs: ['helper:format-time', 'helper:travis-mb']
}
test 'it renders', ->

View File

@ -35,10 +35,8 @@ test 'it renders', ->
@append()
ok component.$().hasClass('passed'), 'component should have state class (passed)'
ok component.$('.icon-status').hasClass('passed'), 'status icon should have state class (passed)'
ok component.$('.request-kind').hasClass('push'), 'reuqest icon should have event type class (push)'
equal component.$('.tile-main h2 a').text().trim(), 'travis-chat', 'should display correct repo name'
equal component.$('.build-status a').text().trim(), '25 passed', 'should display correct build number and state'
equal component.$('.tile-commit a').text().trim(), '16fff34', 'should display correct commit sha'
equal component.$('.tile-timeago').text().trim(), '2 years ago', 'should display correct build duration'
equal component.$('.tile-duration').text().trim(), '4 min 12 sec', 'should display correct build finished at time'
equal component.$('.repo-title a').text().trim(), 'travis-chat', 'should display correct repo name'
equal component.$('.build a').text().trim(), '25', 'should display correct build numbee'
equal component.$('.build-status').text().trim(), 'passed', 'should display a last build state'
equal component.$('.commit a').text().trim(), '16fff34', 'should display correct commit sha'
equal component.$('.finished-at').text().trim(), '2 years ago', 'should display correct build duration'