diff --git a/.jshintrc b/.jshintrc index e75832d8..64e26f6b 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,5 +1,6 @@ { "predef": [ + "server", "document", "window", "-Promise", diff --git a/app/adapters/application.js b/app/adapters/application.js index f3b4799e..58d1e98b 100644 --- a/app/adapters/application.js +++ b/app/adapters/application.js @@ -47,5 +47,11 @@ export default ActiveModelAdapter.extend({ } return this._super(...arguments); + }, + + // this can be removed once this PR is merged and live: + // https://github.com/emberjs/data/pull/4204 + findRecord(store, type, id, snapshot) { + return this.ajax(this.buildURL(type.modelName, id, snapshot, 'findRecord'), 'GET'); } }); diff --git a/app/adapters/v3.js b/app/adapters/v3.js index b693f760..7838dd4c 100644 --- a/app/adapters/v3.js +++ b/app/adapters/v3.js @@ -54,5 +54,11 @@ export default RESTAdapter.extend({ pathForType: function(modelName, id) { var underscored = Ember.String.underscore(modelName); return id ? underscored : Ember.String.pluralize(underscored); + }, + + // this can be removed once this PR is merged and live: + // https://github.com/emberjs/data/pull/4204 + findRecord(store, type, id, snapshot) { + return this.ajax(this.buildURL(type.modelName, id, snapshot, 'findRecord'), 'GET'); } }); diff --git a/app/components/job-log.js b/app/components/job-log.js index 8ba81be4..1b079c89 100644 --- a/app/components/job-log.js +++ b/app/components/job-log.js @@ -2,6 +2,7 @@ import Ember from 'ember'; export default Ember.Component.extend({ logBinding: 'job.log', + classNames: ['job-log'], didReceiveAttrs: function(options) { this._super(...arguments); @@ -25,7 +26,10 @@ export default Ember.Component.extend({ }, setupLog(job) { - job.get('log').fetch(); + this.set('error', false); + job.get('log').fetch().then(function() { }, () => { + this.set('error', true); + }); job.subscribe(); } }); diff --git a/app/models/log.js b/app/models/log.js index f0875206..45fbc6e2 100644 --- a/app/models/log.js +++ b/app/models/log.js @@ -28,12 +28,14 @@ var Request = Ember.Object.extend({ return $.ajax({ url: this.redirectTo(xhr), type: 'GET', - success: this.handlers.text + success: (body) => { + Ember.run(this, function() { this.handlers.text(body); }); + } }); } else if (this.isJson(xhr, body)) { - return this.handlers.json(body); + return Ember.run(this, function() { this.handlers.json(body); }); } else { - return this.handlers.text(body); + return Ember.run(this, function() { this.handlers.text(body); }); } }, @@ -44,6 +46,7 @@ var Request = Ember.Object.extend({ }, isJson(xhr, body) { + // Firefox can't see the Content-Type header on the xhr response due to the wrong // status code 204. Should be some redirect code but that doesn't work with CORS. var type = xhr.getResponseHeader('Content-Type') || ''; diff --git a/app/serializers/build.js b/app/serializers/build.js index 7aaf0886..19ca72a6 100644 --- a/app/serializers/build.js +++ b/app/serializers/build.js @@ -16,6 +16,14 @@ var Serializer = V2FallbackSerializer.extend({ return result; }, + normalizeSingleResponse: function(store, primaryModelClass, payload, id, requestType) { + if (payload.commit) { + payload.build.commit = payload.commit; + delete payload.build.commit_id; + } + return this._super(...arguments); + }, + normalizeArrayResponse: function(store, primaryModelClass, payload, id, requestType) { var result; if (payload.commits) { diff --git a/app/serializers/job.js b/app/serializers/job.js index 9f0e0e64..06d59894 100644 --- a/app/serializers/job.js +++ b/app/serializers/job.js @@ -25,6 +25,14 @@ export default V2FallbackSerializer.extend({ return this._super(modelClass, resourceHash); }, + normalizeSingleResponse: function(store, primaryModelClass, payload, id, requestType) { + if (payload.commit) { + payload.job.commit = payload.commit; + delete payload.job.commit_id; + } + return this._super(...arguments); + }, + normalizeArrayResponse: function(store, primaryModelClass, payload, id, requestType) { var result; if (payload.commits) { diff --git a/app/templates/components/job-log.hbs b/app/templates/components/job-log.hbs index 71a2be1e..c723753d 100644 --- a/app/templates/components/job-log.hbs +++ b/app/templates/components/job-log.hbs @@ -1,5 +1,9 @@ -{{#if log.isLoaded}} - {{log-content job=job log=log}} +{{#if error}} +

There was an error while trying to fetch the log.

{{else}} - {{loading-indicator}} + {{#if log.isLoaded}} + {{log-content job=job log=log}} + {{else}} + {{loading-indicator}} + {{/if}} {{/if}} diff --git a/bower.json b/bower.json index 1cd79336..42626fb9 100644 --- a/bower.json +++ b/bower.json @@ -15,8 +15,11 @@ "moment": "~2.9.0", "jquery-timeago": "~1.4.1", "pusher": "~2.2.3", - "pretender": "0.1.0", - "ember-resolver": "~0.1.20" + "ember-resolver": "~0.1.20", + "pretender": "~0.12.0", + "lodash": "~3.7.0", + "Faker": "~3.0.0", + "ceibo": "1.0.0" }, "resolutions": { "ember": "2.2.1", diff --git a/config/environment.js b/config/environment.js index ef8846f0..cf5e548a 100644 --- a/config/environment.js +++ b/config/environment.js @@ -2,7 +2,7 @@ module.exports = function(environment) { var ENV = { - useV3API: false, + useV3API: true, modulePrefix: 'travis', environment: environment, baseURL: '/', @@ -77,6 +77,9 @@ module.exports = function(environment) { // ENV.APP.LOG_TRANSITIONS = true; // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; // ENV.APP.LOG_VIEW_LOOKUPS = true; + ENV['ember-cli-mirage'] = { + enabled: false + } } if (environment === 'test') { @@ -91,10 +94,13 @@ module.exports = function(environment) { ENV.APP.rootElement = '#ember-testing'; ENV.apiEndpoint = ''; + ENV.statusPageStatusUrl = null; } if (environment === 'production') { - + ENV['ember-cli-mirage'] = { + enabled: false + } } // TODO: I insert values from ENV here, but in production diff --git a/mirage/config.js b/mirage/config.js new file mode 100644 index 00000000..2f97c285 --- /dev/null +++ b/mirage/config.js @@ -0,0 +1,139 @@ +import Ember from 'ember'; +import Mirage from 'ember-cli-mirage'; + +export default function() { + let _turnIntoV3Singular = function(type, record) { + if(record.attrs) { + record = record.attrs; + } + record['@type'] = type; + record['@href'] = `/${type}/${record.id}`; + + return record; + }; + + let turnIntoV3 = function(type, payload) { + let response; + if(Ember.isArray(payload)) { + let records = payload.map( (record) => { return _turnIntoV3Singular(type, record); } ); + + let pluralized = Ember.String.pluralize(type); + response = {}; + response['@type'] = pluralized; + response['@href'] = `/${pluralized}`; + response[pluralized] = records; + } else { + response = _turnIntoV3Singular(type, payload); + } + return response; + }; + + this.get('/repos', function(schema, request) { + return turnIntoV3('repository', schema.repository.all()); + }); + + this.get('/repo/:slug', function(schema, request) { + let repos = schema.repository.where({ slug: decodeURIComponent(request.params.slug) }); + return turnIntoV3('repository', repos[0]); + }); + this.get('/jobs/:id', function(schema, request) { + let job = schema.job.find(request.params.id).attrs; + return {job: job, commit: schema.commit.find(job.commit_id).attrs}; + }); + this.get('/jobs', function(schema, request) { + return {jobs: schema.job.all()}; + }); + this.get('/builds/:id', function(schema, request) { + let build = schema.build.find(request.params.id).attrs; + return {build: build, commit: schema.commit.find(build.commit_id).attrs}; + }); + this.get('/jobs/:id/log', function(schema, request) { + let log = schema.log.find(request.params.id); + + if(log) { + return { log: { parts: [{ id: log.attrs.id, number: 1, content: log.attrs.content}] }}; + } else { + return new Mirage.Response(404, {}, {}); + } + }); + + // These comments are here to help you get started. Feel free to delete them. + + /* + Config (with defaults). + + Note: these only affect routes defined *after* them! + */ + // this.urlPrefix = ''; // make this `http://localhost:8080`, for example, if your API is on a different server + // this.namespace = ''; // make this `api`, for example, if your API is namespaced + // this.timing = 400; // delay for each request, automatically set to 0 during testing + + /* + Route shorthand cheatsheet + */ + /* + GET shorthands + + // Collections + this.get('/contacts'); + this.get('/contacts', 'users'); + this.get('/contacts', ['contacts', 'addresses']); + + // Single objects + this.get('/contacts/:id'); + this.get('/contacts/:id', 'user'); + this.get('/contacts/:id', ['contact', 'addresses']); + */ + + /* + POST shorthands + + this.post('/contacts'); + this.post('/contacts', 'user'); // specify the type of resource to be created + */ + + /* + PUT shorthands + + this.put('/contacts/:id'); + this.put('/contacts/:id', 'user'); // specify the type of resource to be updated + */ + + /* + DELETE shorthands + + this.del('/contacts/:id'); + this.del('/contacts/:id', 'user'); // specify the type of resource to be deleted + + // Single object + related resources. Make sure parent resource is first. + this.del('/contacts/:id', ['contact', 'addresses']); + */ + + /* + Function fallback. Manipulate data in the db via + + - db.{collection} + - db.{collection}.find(id) + - db.{collection}.where(query) + - db.{collection}.update(target, attrs) + - db.{collection}.remove(target) + + // Example: return a single object with related models + this.get('/contacts/:id', function(db, request) { + var contactId = +request.params.id; + + return { + contact: db.contacts.find(contactId), + addresses: db.addresses.where({contact_id: contactId}) + }; + }); + + */ +} + +/* +You can optionally export a config that is only loaded during tests +export function testConfig() { + +} +*/ diff --git a/mirage/factories/branch.js b/mirage/factories/branch.js new file mode 100644 index 00000000..b7d84f8a --- /dev/null +++ b/mirage/factories/branch.js @@ -0,0 +1,4 @@ +import Mirage/*, {faker} */ from 'ember-cli-mirage'; + +export default Mirage.Factory.extend({ +}); diff --git a/mirage/factories/build.js b/mirage/factories/build.js new file mode 100644 index 00000000..b7d84f8a --- /dev/null +++ b/mirage/factories/build.js @@ -0,0 +1,4 @@ +import Mirage/*, {faker} */ from 'ember-cli-mirage'; + +export default Mirage.Factory.extend({ +}); diff --git a/mirage/factories/commit.js b/mirage/factories/commit.js new file mode 100644 index 00000000..b7d84f8a --- /dev/null +++ b/mirage/factories/commit.js @@ -0,0 +1,4 @@ +import Mirage/*, {faker} */ from 'ember-cli-mirage'; + +export default Mirage.Factory.extend({ +}); diff --git a/mirage/factories/contact.js b/mirage/factories/contact.js new file mode 100644 index 00000000..1b3a3eab --- /dev/null +++ b/mirage/factories/contact.js @@ -0,0 +1,20 @@ +/* + This is an example factory definition. + + Create more files in this directory to define additional factories. +*/ +import Mirage/*, {faker} */ from 'ember-cli-mirage'; + +export default Mirage.Factory.extend({ + // name: 'Pete', // strings + // age: 20, // numbers + // tall: true, // booleans + + // email: function(i) { // and functions + // return 'person' + i + '@test.com'; + // }, + + // firstName: faker.name.firstName, // using faker + // lastName: faker.name.firstName, + // zipCode: faker.address.zipCode +}); diff --git a/mirage/factories/job.js b/mirage/factories/job.js new file mode 100644 index 00000000..5488e596 --- /dev/null +++ b/mirage/factories/job.js @@ -0,0 +1,5 @@ +import Mirage/*, {faker} */ from 'ember-cli-mirage'; + +export default Mirage.Factory.extend({ + +}); diff --git a/mirage/factories/log.js b/mirage/factories/log.js new file mode 100644 index 00000000..e763ded8 --- /dev/null +++ b/mirage/factories/log.js @@ -0,0 +1,5 @@ +import Mirage/*, {faker} */ from 'ember-cli-mirage'; + +export default Mirage.Factory.extend({ + content: 'Hello log' +}); diff --git a/mirage/factories/repository.js b/mirage/factories/repository.js new file mode 100644 index 00000000..7001999c --- /dev/null +++ b/mirage/factories/repository.js @@ -0,0 +1,7 @@ +import Mirage from 'ember-cli-mirage'; + +export default Mirage.Factory.extend({ + slug: 'travis-ci/travis-web', + githubLanguage: 'ruby', + active: true +}); diff --git a/mirage/models/build.js b/mirage/models/build.js new file mode 100644 index 00000000..1486a724 --- /dev/null +++ b/mirage/models/build.js @@ -0,0 +1,4 @@ +import { Model } from 'ember-cli-mirage'; + +export default Model.extend({ +}); diff --git a/mirage/models/commit.js b/mirage/models/commit.js new file mode 100644 index 00000000..1486a724 --- /dev/null +++ b/mirage/models/commit.js @@ -0,0 +1,4 @@ +import { Model } from 'ember-cli-mirage'; + +export default Model.extend({ +}); diff --git a/mirage/models/job.js b/mirage/models/job.js new file mode 100644 index 00000000..1486a724 --- /dev/null +++ b/mirage/models/job.js @@ -0,0 +1,4 @@ +import { Model } from 'ember-cli-mirage'; + +export default Model.extend({ +}); diff --git a/mirage/models/log.js b/mirage/models/log.js new file mode 100644 index 00000000..1486a724 --- /dev/null +++ b/mirage/models/log.js @@ -0,0 +1,4 @@ +import { Model } from 'ember-cli-mirage'; + +export default Model.extend({ +}); diff --git a/mirage/models/repository.js b/mirage/models/repository.js new file mode 100644 index 00000000..1486a724 --- /dev/null +++ b/mirage/models/repository.js @@ -0,0 +1,4 @@ +import { Model } from 'ember-cli-mirage'; + +export default Model.extend({ +}); diff --git a/mirage/scenarios/default.js b/mirage/scenarios/default.js new file mode 100644 index 00000000..e07271cc --- /dev/null +++ b/mirage/scenarios/default.js @@ -0,0 +1,7 @@ +export default function(/* server */) { + + // Seed your development database using your factories. This + // data will not be loaded in your tests. + + // server.createList('contact', 10); +} diff --git a/mirage/serializers/application.js b/mirage/serializers/application.js new file mode 100644 index 00000000..6d47a366 --- /dev/null +++ b/mirage/serializers/application.js @@ -0,0 +1,4 @@ +import { JSONAPISerializer } from 'ember-cli-mirage'; + +export default JSONAPISerializer.extend({ +}); diff --git a/package.json b/package.json index 81138c0d..3f9a4434 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,8 @@ "ember-cli-htmlbars-inline-precompile": "^0.3.1", "ember-cli-inject-live-reload": "^1.3.1", "ember-cli-inline-images": "^0.0.4", + "ember-cli-mirage": "0.2.0-beta.7", + "ember-cli-page-object": "1.0.0", "ember-cli-pendo": "drogus/ember-cli-pendo", "ember-cli-pretender": "0.3.1", "ember-cli-qunit": "^1.2.1", diff --git a/tests/.jshintrc b/tests/.jshintrc index 51c88317..14325959 100644 --- a/tests/.jshintrc +++ b/tests/.jshintrc @@ -1,5 +1,6 @@ { "predef": [ + "server", "document", "window", "location", diff --git a/tests/acceptance/job-view-test.js b/tests/acceptance/job-view-test.js new file mode 100644 index 00000000..23041373 --- /dev/null +++ b/tests/acceptance/job-view-test.js @@ -0,0 +1,48 @@ +import { test } from 'qunit'; +import moduleForAcceptance from 'travis/tests/helpers/module-for-acceptance'; +import jobPage from 'travis/tests/pages/job'; + +moduleForAcceptance('Acceptance | job view'); + +test('visiting job-view', function(assert) { + + let repo = server.create('repository', {slug: 'travis-ci/travis-web'}); + let branch = server.create('branch', {}); + let commit = server.create('commit', {author_email: 'mrt@travis-ci.org', author_name: 'Mr T', committer_email: 'mrt@travis-ci.org', committer_name: 'Mr T', branch: 'acceptance-tests', message: 'This is a message', branch_is_default: true}); + let build = server.create('build', {repository_id: repo.id, state: 'passed', commit_id: commit.id}); + let job = server.create('job', {number: '1234.1', reposiptoy_id: repo.id, state: 'passed', build_id: build.id, commit_id: commit.id}); + let log = server.create('log', { id: job.id }); + + visit('/travis-ci/travis-web/jobs/'+ job.id); + + + andThen(function() { + assert.equal(jobPage.branch, 'acceptance-tests'); + assert.equal(jobPage.message, 'acceptance-tests This is a message'); + assert.equal(jobPage.state, '#1234.1 passed'); + assert.equal(jobPage.author, 'Mr T authored and committed'); + + assert.equal(jobPage.log, 'Hello log'); + }); +}); + + +test('handling log error', function(assert) { + + let repo = server.create('repository', {slug: 'travis-ci/travis-web'}); + let branch = server.create('branch', {}); + let commit = server.create('commit', {author_email: 'mrt@travis-ci.org', author_name: 'Mr T', committer_email: 'mrt@travis-ci.org', committer_name: 'Mr T', branch: 'acceptance-tests', message: 'This is a message', branch_is_default: true}); + let build = server.create('build', {repository_id: repo.id, state: 'passed', commit_id: commit.id}); + let job = server.create('job', {number: '1234.1', reposiptoy_id: repo.id, state: 'passed', build_id: build.id, commit_id: commit.id}); + + visit('/travis-ci/travis-web/jobs/'+ job.id); + + andThen(function() { + assert.equal(jobPage.branch, 'acceptance-tests'); + assert.equal(jobPage.message, 'acceptance-tests This is a message'); + assert.equal(jobPage.state, '#1234.1 passed'); + assert.equal(jobPage.author, 'Mr T authored and committed'); + + assert.equal(jobPage.logError, 'There was an error while trying to fetch the log.'); + }); +}); diff --git a/tests/helpers/destroy-app.js b/tests/helpers/destroy-app.js index c3d4d1ab..3a0114aa 100644 --- a/tests/helpers/destroy-app.js +++ b/tests/helpers/destroy-app.js @@ -2,4 +2,5 @@ import Ember from 'ember'; export default function destroyApp(application) { Ember.run(application, 'destroy'); + server.shutdown(); } diff --git a/tests/pages/job.js b/tests/pages/job.js new file mode 100644 index 00000000..c8f02b9d --- /dev/null +++ b/tests/pages/job.js @@ -0,0 +1,14 @@ +import PageObject from 'travis/tests/page-object'; + +let { + text +} = PageObject; + +export default PageObject.create({ + branch: text('.commit-branch'), + message: text('.build-title'), + state: text('.build-status'), + author: text('.commit-author'), + log: text('#log'), + logError: text('.job-log .notice') +}); diff --git a/tests/unit/components/travis-status-test.js b/tests/unit/components/travis-status-test.js index 19fc7962..7626cfbc 100644 --- a/tests/unit/components/travis-status-test.js +++ b/tests/unit/components/travis-status-test.js @@ -1,8 +1,6 @@ import { test, moduleForComponent } from 'ember-qunit'; import Ember from 'ember'; -var server = null; - moduleForComponent('travis-status', 'TravisStatusComponent', { unit: true }); @@ -11,6 +9,7 @@ test('adds incident class to .status-circle', function() { var component; expect(3); component = this.subject(); + component.statusPageStatusUrl = "https://status-url.example.com"; component.getStatus = function() { return new Ember.RSVP.Promise(function(resolve, reject) { return resolve({ @@ -20,6 +19,7 @@ test('adds incident class to .status-circle', function() { }); }); }; + ok(!component.get('status'), 'status is initially not set'); this.render(); equal(component.get('status'), 'major', 'status is updated from the API');