From 6540f3ac5b0171aec0ca0aed27e5f7fb49c0e147 Mon Sep 17 00:00:00 2001
From: Piotr Sarnacki <drogus@gmail.com>
Date: Fri, 20 Mar 2015 09:08:23 +0100
Subject: [PATCH] Refactor jobs list and add some unit tests for it

---
 app/components/jobs-item.coffee               | 21 +++++
 app/components/jobs-list.coffee               | 11 +++
 app/controllers/build.coffee                  |  5 ++
 app/templates/build.hbs                       | 10 ++-
 app/templates/components/jobs-item.hbs        | 46 ++++++++++
 app/templates/components/jobs-list.hbs        | 21 +++++
 app/templates/jobs.hbs                        | 85 -------------------
 app/views/jobs-item.coffee                    | 26 ------
 app/views/jobs.coffee                         | 23 -----
 testem.json                                   |  3 -
 tests/index.html                              |  9 ++
 tests/unit/components/jobs-item-test.coffee   | 39 +++++++++
 tests/unit/components/jobs-list-test.coffee   | 25 ++++++
 .../unit/components/running-jobs-test.coffee  | 17 ++++
 14 files changed, 202 insertions(+), 139 deletions(-)
 create mode 100644 app/components/jobs-item.coffee
 create mode 100644 app/components/jobs-list.coffee
 create mode 100644 app/templates/components/jobs-item.hbs
 create mode 100644 app/templates/components/jobs-list.hbs
 delete mode 100644 app/templates/jobs.hbs
 delete mode 100644 app/views/jobs-item.coffee
 delete mode 100644 app/views/jobs.coffee
 create mode 100644 tests/unit/components/jobs-item-test.coffee
 create mode 100644 tests/unit/components/jobs-list-test.coffee
 create mode 100644 tests/unit/components/running-jobs-test.coffee

diff --git a/app/components/jobs-item.coffee b/app/components/jobs-item.coffee
new file mode 100644
index 00000000..ca085418
--- /dev/null
+++ b/app/components/jobs-item.coffee
@@ -0,0 +1,21 @@
+`import Ember from 'ember'`
+`import { colorForState } from 'travis/utils/helpers'`
+`import { languageConfigKeys } from 'travis/utils/keys-map';`
+
+JobsItemComponent = Ember.Component.extend
+  tagName: 'li'
+  classNameBindings: ['job.state']
+  classNames: ['tile', 'tile--jobs', 'row']
+
+  languages: (->
+    output = []
+
+    if config = @get('job.config')
+      for key, languageName of languageConfigKeys
+        if version = config[key]
+          output.push(languageName + ': ' + version)
+
+    output.join(' ')
+  ).property('job.config')
+
+`export default JobsItemComponent`
diff --git a/app/components/jobs-list.coffee b/app/components/jobs-list.coffee
new file mode 100644
index 00000000..65e26d5d
--- /dev/null
+++ b/app/components/jobs-list.coffee
@@ -0,0 +1,11 @@
+`import Ember from 'ember'`
+
+JobsListComponent = Ember.Component.extend
+  jobTableId: Ember.computed(->
+    if @get('required')
+      'jobs'
+    else
+      'allowed_failure_jobs'
+  )
+
+`export default JobsListComponent`
diff --git a/app/controllers/build.coffee b/app/controllers/build.coffee
index 1f86f62c..4e868705 100644
--- a/app/controllers/build.coffee
+++ b/app/controllers/build.coffee
@@ -12,6 +12,11 @@ Controller = Ember.Controller.extend GithubUrlPropertievs,
 
   currentItemBinding: 'build'
 
+  jobsLoaded: (->
+    if jobs = @get('build.jobs')
+      jobs.everyBy('config')
+  ).property('build.jobs.@each.config')
+
   loading: (->
     @get('build.isLoading')
   ).property('build.isLoading')
diff --git a/app/templates/build.hbs b/app/templates/build.hbs
index cba093f5..bc4998ae 100644
--- a/app/templates/build.hbs
+++ b/app/templates/build.hbs
@@ -76,8 +76,14 @@
   {{/unless}}
 
   {{#if build.isMatrix}}
-    {{view 'jobs' jobs=build.requiredJobs required="true"}}
-    {{view 'jobs' jobs=build.allowedFailureJobs}}
+    {{#if jobsLoaded}}
+      {{jobs-list jobs=build.requiredJobs required="true"}}
+      {{jobs-list jobs=build.allowedFailureJobs}}
+    {{else}}
+      <div class="spinner-container">
+        <span class="sync-spinner sync-spinner--grey"><i></i><i></i><i></i></span>
+      </div>
+    {{/if}}
   {{else}}
     {{view 'log' job=build.jobs.firstObject}}
   {{/if}}
diff --git a/app/templates/components/jobs-item.hbs b/app/templates/components/jobs-item.hbs
new file mode 100644
index 00000000..e6ec8359
--- /dev/null
+++ b/app/templates/components/jobs-item.hbs
@@ -0,0 +1,46 @@
+{{#link-to "job" job.repo job}}
+  <div class="tile-status tile-status--job">
+    <span {{bind-attr class=":icon :icon--job job.state"}}></span>
+  </div>
+
+  <p class="job-id jobs-item build-status">
+    <span class="icon icon--hash-dark"></span>
+    {{job.number}}
+  </p>
+
+  <p {{bind-attr class=":job-os :jobs-item :build-os job.config.os"}}>
+    <span {{bind-attr class=":icon job.config.os"}}></span>
+  </p>
+
+  {{#if languages}}
+    <p class="job-lang jobs-item build-lang">
+      <span class="icon icon--lang"></span>
+      {{languages}}
+    </p>
+  {{else}}
+    <p class="job-lang jobs-item build-lang is-empty">
+      <span class="icon icon--lang"></span>
+      no language set
+    </p>
+  {{/if}}
+
+  <div class="job-anchor jobs-item">
+    {{#if job.config.env}}
+      <p class="job-env jobs-item build-env">
+        <span class="icon icon--env"></span>
+        {{job.config.env}}
+      </p>
+    {{else}}
+      <p class="job-env jobs-item build-env is-empty">
+        <span class="icon icon--env"></span>
+        no environment variables set
+      </p>
+    {{/if}}
+
+    <p class="job-duration jobs-item" {{bind-attr title="job.startedAt"}}>
+      <span class="icon icon--clock-dark"></span>
+      {{format-duration job.duration}}
+    </p>
+
+  </div>
+{{/link-to}}
diff --git a/app/templates/components/jobs-list.hbs b/app/templates/components/jobs-list.hbs
new file mode 100644
index 00000000..ff8a0243
--- /dev/null
+++ b/app/templates/components/jobs-list.hbs
@@ -0,0 +1,21 @@
+{{#if jobs.length}}
+  <section>
+
+  {{#if required}}
+    <h2 class="build-title">Build Jobs</h2>
+  {{else}}
+    <h2 class="build-title">Allowed Failures
+      <span class="icon icon--question"></span>
+      <div class="tooltip">
+        <p class="tooltip-inner">These are jobs you can allow to fail without failing your entire build</p>
+      </div>
+    </h2>
+  {{/if}}
+
+    <ul>
+    {{#each job in jobs}}
+      {{jobs-item job=job}}
+    {{/each}}
+    </ul>
+  </section>
+{{/if}}
diff --git a/app/templates/jobs.hbs b/app/templates/jobs.hbs
deleted file mode 100644
index 8c9fc3a2..00000000
--- a/app/templates/jobs.hbs
+++ /dev/null
@@ -1,85 +0,0 @@
-{{#if view.jobs.length}}
-  <section {{bind-attr id=view.jobTableId}}>
-
-  {{#if view.required}}
-    <h2 class="build-title">Build Jobs</h2>
-  {{else}}
-    <h2 class="build-title">Allowed Failures
-      <span class="icon icon--question"></span>
-      <div class="tooltip">
-        <p class="tooltip-inner">These are jobs you can allow to fail without failing your entire build</p>
-      </div>
-    </h2>
-  {{/if}}
-
-    {{#each job in view.jobs}}
-      {{#view 'jobs-item' context=job}}
-        <div {{bind-attr class=":tile :tile--jobs :row job.state" }}>
-          {{#if job.config}}
-            {{#link-to "job" job.repo job}}
-
-              <div class="tile-status tile-status--job">
-                <span {{bind-attr class=":icon :icon--job job.state"}}></span>
-              </div>
-
-              <p class="job-id jobs-item build-status">
-                <span class="icon icon--hash-dark"></span>
-                {{#if job.id}}
-                  {{#if job.repo.slug}}
-                    {{number}}
-                  {{/if}}
-                {{/if}}
-              </p>
-
-            <p {{bind-attr class=":job-os :jobs-item :build-os config.os"}}>
-              <span {{bind-attr class=":icon config.os"}}></span>
-              {{!-- {{config.os}} --}}
-            </p>
-
-            {{#if view.languages}}
-              <p class="job-lang jobs-item build-lang">
-                <span class="icon icon--lang"></span>
-                {{view.languages}}
-              </p>
-            {{else}}
-              <p class="job-lang jobs-item build-lang is-empty">
-                <span class="icon icon--lang"></span>
-                no language set
-              </p>
-            {{/if}}
-            
-            <div class="job-anchor jobs-item">
-              {{#if config.env}}
-                <p class="job-env jobs-item build-env">
-                  <span class="icon icon--env"></span>
-                  {{config.env}}
-                </p>
-              {{else}}
-                <p class="job-env jobs-item build-env is-empty">
-                  <span class="icon icon--env"></span>
-                  no environment variables set
-                </p>
-              {{/if}}
-
-              <p class="job-duration jobs-item" {{bind-attr title="startedAt"}}>
-                <span class="icon icon--clock-dark"></span>
-                {{format-duration duration}}
-              </p>
-
-            </div>
-
-              {{!-- <p class="" {{bind-attr title="formattedFinishedAt"}}>
-                <span class="icon icon--cal"></span>
-                {{format-time finishedAt}}
-              </p> --}}
-
-            {{/link-to}}
-          {{else}}
-            <span class="sync-spinner sync-spinner--grey"><i></i><i></i><i></i></span>
-          {{/if}}
-        </div>
-      {{/view}}
-      {{!-- {{job.configKeys}} --}}
-    {{/each}}
-  </section>
-{{/if}}
diff --git a/app/views/jobs-item.coffee b/app/views/jobs-item.coffee
deleted file mode 100644
index df04e841..00000000
--- a/app/views/jobs-item.coffee
+++ /dev/null
@@ -1,26 +0,0 @@
-`import BasicView from 'travis/views/basic'`
-`import { colorForState } from 'travis/utils/helpers'`
-`import { languageConfigKeys } from 'travis/utils/keys-map';`
-
-View = BasicView.extend
-    tagName: 'div'
-    classNameBindings: ['color']
-    repoBinding: 'context.repo'
-    jobBinding: 'context'
-
-    color: (->
-      colorForState(@get('job.state'))
-    ).property('job.state')
-
-    languages: (->
-      output = []
-
-      if config = @get('job.config')
-        for key, languageName of languageConfigKeys
-          if version = config[key]
-            output.push(languageName + ': ' + version)
-
-      output.join(' ')
-    ).property()
-
-`export default View`
diff --git a/app/views/jobs.coffee b/app/views/jobs.coffee
deleted file mode 100644
index 241294f9..00000000
--- a/app/views/jobs.coffee
+++ /dev/null
@@ -1,23 +0,0 @@
-`import Ember from 'ember'`
-`import BasicView from 'travis/views/basic'`
-
-View = BasicView.extend
-  templateName: 'jobs'
-  buildBinding: 'controller.build'
-
-  jobTableId: Ember.computed(->
-    if @get('required')
-      'jobs'
-    else
-      'allowed_failure_jobs'
-  )
-
-  actions:
-    popupClose: ->
-      @popupCloseAll()
-
-    openHelpPopup: ->
-      @popupCloseAll()
-      @popup('help-allowed_failures')
-
-`export default View`
diff --git a/testem.json b/testem.json
index 23afdf07..7f180924 100644
--- a/testem.json
+++ b/testem.json
@@ -6,9 +6,6 @@
     "SL_firefox"
   ],
   "launch_in_dev": [
-    "PhantomJS",
-    "Chrome",
-    "Firefox"
   ],
   "launchers": {
     "SL_chrome": {
diff --git a/tests/index.html b/tests/index.html
index 86513a02..8827643b 100644
--- a/tests/index.html
+++ b/tests/index.html
@@ -13,6 +13,15 @@
     <link rel="stylesheet" href="assets/vendor.css">
     <link rel="stylesheet" href="assets/travis.css">
     <link rel="stylesheet" href="assets/test-support.css">
+    <style>
+      #ember-testing-container {
+        top: 100px;
+        right: -620px;
+      }
+      #ember-testing-container:hover {
+        right: 0;
+      }
+    </style>
 
     {{content-for 'head-footer'}}
     {{content-for 'test-head-footer'}}
diff --git a/tests/unit/components/jobs-item-test.coffee b/tests/unit/components/jobs-item-test.coffee
new file mode 100644
index 00000000..31f0e9aa
--- /dev/null
+++ b/tests/unit/components/jobs-item-test.coffee
@@ -0,0 +1,39 @@
+`import { test, moduleForComponent } from 'ember-qunit'`
+
+moduleForComponent 'jobs-item', 'JobsItemComponent', {
+  # specify the other units that are required for this test
+  needs: ['helper:format-duration']
+}
+
+test 'it renders', ->
+  attributes = {
+    id: 10
+    state: 'passed'
+    number: '2'
+    config: {
+      rvm: '2.1.2'
+      jdk: 'openjdk6'
+      os: 'linux',
+      env: 'TESTS=unit'
+    },
+    duration: 100
+  }
+  job = Ember.Object.create(attributes)
+  component = @subject(job: job)
+  @append()
+
+  ok component.$().hasClass('passed'), 'component should have a state class (passed)'
+  equal component.$('.job-id').text().trim(), '2', 'job number should be displayed'
+  equal component.$('.job-lang').text().trim(), 'JDK: openjdk6 Ruby: 2.1.2', 'langauges list should be displayed'
+  equal component.$('.job-env').text().trim(), 'TESTS=unit', 'env should be displayed'
+  ok component.$('.job-os').hasClass('linux'), 'OS class should be added for OS icon'
+  equal component.$('.job-duration').text().trim(), '1 min 40 sec', 'duration should be displayed'
+
+test 'ouputs info on not set properties', ->
+  job = Ember.Object.create()
+
+  component = @subject(job: job)
+  @append()
+
+  ok component.$('.job-env').text().match(/no environment variables set/), 'a message for no env vars should be displayed'
+  ok component.$('.job-lang').text().match(/no language set/), 'a message about no language being set should be displayed'
diff --git a/tests/unit/components/jobs-list-test.coffee b/tests/unit/components/jobs-list-test.coffee
new file mode 100644
index 00000000..76a40abe
--- /dev/null
+++ b/tests/unit/components/jobs-list-test.coffee
@@ -0,0 +1,25 @@
+`import { test, moduleForComponent } from 'ember-qunit'`
+
+moduleForComponent 'jobs-list', 'JobsListComponent', {
+  needs: ['helper:format-duration', 'component:jobs-item']
+}
+
+test 'it renders a list of jobs', ->
+  jobs = [Ember.Object.create(id: 1, state: 'passed'),
+          Ember.Object.create(id: 1, state: 'failed')]
+
+  component = @subject(jobs: jobs, required: true)
+  @append()
+
+  equal component.$('.build-title').text().trim(), 'Build Jobs'
+  equal component.$('li').length, 2, 'there should be 2 job items'
+  ok component.$('li:nth(0)').hasClass('passed'), 'passed class should be applied to a job'
+  ok component.$('li:nth(1)').hasClass('failed'), 'failed class should be applied to a job'
+
+test 'it renders "Allowed Failures" version without a `required` property', ->
+  jobs = [Ember.Object.create(id: 1)]
+
+  component = @subject(jobs: jobs)
+  @append()
+
+  ok component.$('.build-title').text().match /Allowed Failures/
diff --git a/tests/unit/components/running-jobs-test.coffee b/tests/unit/components/running-jobs-test.coffee
new file mode 100644
index 00000000..a0fd1658
--- /dev/null
+++ b/tests/unit/components/running-jobs-test.coffee
@@ -0,0 +1,17 @@
+`import { test, moduleForComponent } from 'ember-qunit'`
+
+moduleForComponent 'running-jobs', 'RunningJobsComponent', {
+  # specify the other units that are required for this test
+  # needs: ['component:foo', 'helper:bar']
+}
+
+test 'it renders', ->
+  expect 2
+
+  # creates the component instance
+  component = @subject()
+  equal component._state, 'preRender'
+
+  # appends the component to the page
+  @append()
+  equal component._state, 'inDOM'