diff --git a/app/components/common/busy-model-wrapper.js b/app/components/common/busy-model-wrapper.js new file mode 100644 index 000000000..5cb95ebfb --- /dev/null +++ b/app/components/common/busy-model-wrapper.js @@ -0,0 +1,11 @@ +import Ember from 'ember'; + +const { Component } = Ember; + +export default Component.extend({ + tagName:'', + model: null, + + onSaving: 'Saving...', + onDeleting: 'Deleting...' +}); diff --git a/app/components/github-repo.js b/app/components/github-repo.js new file mode 100644 index 000000000..6276789bc --- /dev/null +++ b/app/components/github-repo.js @@ -0,0 +1,21 @@ +import Ember from 'ember'; + +const { + computed: { alias, notEmpty }, + Component +} = Ember; + +export default Component.extend({ + classNames: ['github-repo'], + classNameBindings: ['isConnected:github-repo--connected'], + tagName: 'li', + + model: null, + + githubRepo: alias('model.githubRepo'), + projectGithubRepo: alias('model.projectGithubRepo'), + isConnected: notEmpty('model.projectGithubRepo'), + + isLoading: alias('githubRepo.isLoading'), + name: alias('githubRepo.name') +}); diff --git a/app/components/github/connected-installation.js b/app/components/github/connected-installation.js new file mode 100644 index 000000000..6a4ceab27 --- /dev/null +++ b/app/components/github/connected-installation.js @@ -0,0 +1,36 @@ +import Ember from 'ember'; + +const { + computed, + computed: { alias }, + Component, + get, + getProperties +} = Ember; + +export default Component.extend({ + classNames: ['github-app-installation connected'], + + organizationGithubAppInstallation: null, + project: null, + + githubAppInstallation: alias('organizationGithubAppInstallation.githubAppInstallation'), + + organization: alias('project.organization'), + githubRepos: alias('githubAppInstallation.githubRepos'), + + projectGithubRepos: alias('project.projectGithubRepos'), + + repos: computed('githubRepos.@each', 'githubRepos.isFulfilled', 'projectGithubRepos.@each', 'projectGithubRepos.isFulfilled', function() { + let { githubRepos, projectGithubRepos } + = getProperties(this, 'githubRepos', 'projectGithubRepos'); + + return githubRepos.map((githubRepo) => { + let projectGithubRepo = projectGithubRepos.find((projectGithubRepo) => { + return get(githubRepo, 'id') == projectGithubRepo.belongsTo('githubRepo').id(); + }); + + return { githubRepo, projectGithubRepo }; + }); + }) +}); diff --git a/app/components/github/unconnected-installation.js b/app/components/github/unconnected-installation.js new file mode 100644 index 000000000..b8d143ded --- /dev/null +++ b/app/components/github/unconnected-installation.js @@ -0,0 +1,9 @@ +import Ember from 'ember'; + +const { + Component +} = Ember; + +export default Component.extend({ + classNames: ['github-app-installation unconnected'] +}); diff --git a/app/components/project-skills-list.js b/app/components/project-skills-list.js index a806d1a0f..d3db56a73 100644 --- a/app/components/project-skills-list.js +++ b/app/components/project-skills-list.js @@ -54,10 +54,11 @@ export default Component.extend({ * @property skills */ skills: computed('projectSkills.@each', 'excludedSkills.@each', function() { - // NOTE: In a perfect case, we would use `Ember.computed.setDif` - // However, project.projectSkills.@each.skill is fetched assinchronously, so - // each item in `projectSkills` is a `DS.PromiseObject` instead of a - // `DS.Model`, so we have to compare by id instead. + // NOTE: In a perfect case, we would use `Ember.computed.setDiff` + // However, project.projectSkills.@each.skill is fetched + // asynchronously, so each item in `projectSkills` is a + // `DS.PromiseObject` instead of a `DS.Model`, so we have to + // compare by id instead. let { projectSkills, excludedSkills } = getProperties(this, 'projectSkills', 'excludedSkills'); return projectSkills.filter((skill) => { return !excludedSkills || !excludedSkills.isAny('id', get(skill, 'id')); diff --git a/app/controllers/project/settings/integrations.js b/app/controllers/project/settings/integrations.js index 8912b4813..076a755ee 100644 --- a/app/controllers/project/settings/integrations.js +++ b/app/controllers/project/settings/integrations.js @@ -1,26 +1,80 @@ import Ember from 'ember'; +import ENV from 'code-corps-ember/config/environment'; const { Controller, + computed: { alias, mapBy }, + computed, + get, + getProperties, inject: { service } } = Ember; export default Controller.extend({ + githubAppUrl: ENV.github.appUrl, store: service(), + organization: alias('project.organization'), + organizationGithubAppInstallations: alias('organization.organizationGithubAppInstallations'), + + userInstallations: alias('user.githubAppInstallations'), + connectedInstallations: mapBy('organizationGithubAppInstallations', 'githubAppInstallation'), + + unconnectedInstallations: computed('userInstallations', 'connectedInstallations', function() { + let { userInstallations, connectedInstallations } + = getProperties(this, 'userInstallations', 'connectedInstallations'); + + let connectedInstallationIds + = connectedInstallations.map((installation) => get(installation, 'id')); + + return userInstallations.filter((installation) => { + return connectedInstallationIds.indexOf(get(installation, 'id')) === -1; + }); + }), + actions: { /** - * Action which calls to create a new GitHub App installation record - * and an organization GitHub App Installation record for the current - * project + * Connects an organization to a githubAppInstallation by creating a + * through record called orgaizationGithubAppInstallation * * Triggered when user clicks a button to install the GitHub App * - * @method createGithubAppInstallation - * @param {DS.Model} project A project record to initialize a new installation. + * @method connect + * @param {DS.Model} organization The organization to connect an installation with + * @param {DS.Model} githubAppInstallation The installation to connect an organization with */ - createGithubAppInstallation(/* project */) { - // .createRecord(); + connect(organization, githubAppInstallation) { + let store = get(this, 'store'); + let record = store.createRecord( + 'organizationGithubAppInstallation', + { organization, githubAppInstallation } + ); + + return record.save(); + }, + + /** + * Disconnects an organization from a githubAppInstallation by deleting the + * specified organizationGithubAppInstallation + * + * Triggered when user clicks a button to "remove" the GitHub App + * + * @method disconnect + * @param {DS.Model} organizationGithubAppInstallation The link record that gets deleted + */ + disconnect(organizationGithubAppInstallation) { + return organizationGithubAppInstallation.destroyRecord(); + }, + + connectRepo(githubRepo, project) { + let projectGithubRepo = get(this, 'store').createRecord( + 'project-github-repo', { project, githubRepo } + ); + return projectGithubRepo.save(); + }, + + disconnectRepo(projectGithubRepo) { + return projectGithubRepo.destroyRecord(); } } }); diff --git a/app/models/github-app-installation.js b/app/models/github-app-installation.js index 937234934..59c1e9976 100644 --- a/app/models/github-app-installation.js +++ b/app/models/github-app-installation.js @@ -3,6 +3,10 @@ import attr from 'ember-data/attr'; import { belongsTo, hasMany } from 'ember-data/relationships'; export default Model.extend({ + githubAccountAvatarUrl: attr(), + githubAccountId: attr(), + githubAccountLogin: attr(), + githubAccountType: attr(), githubId: attr(), insertedAt: attr(), installed: attr(), @@ -11,6 +15,5 @@ export default Model.extend({ githubRepos: hasMany('github-repo', { async: true }), organizationGithubAppInstallations: hasMany('organization-github-app-installation', { async: true }), - project: belongsTo('project', { async: true }), user: belongsTo('user', { async: true }) }); diff --git a/app/models/github-repo.js b/app/models/github-repo.js index 767079c62..b83a6d641 100644 --- a/app/models/github-repo.js +++ b/app/models/github-repo.js @@ -1,6 +1,6 @@ import Model from 'ember-data/model'; import attr from 'ember-data/attr'; -import { belongsTo } from 'ember-data/relationships'; +import { belongsTo, hasMany } from 'ember-data/relationships'; export default Model.extend({ githubAccountAvatarUrl: attr(), @@ -12,5 +12,6 @@ export default Model.extend({ name: attr(), updatedAt: attr(), - githubAppInstallation: belongsTo('github-app-installation', { async: true }) + githubAppInstallation: belongsTo('github-app-installation', { async: true }), + projectGithubRepos: hasMany('project-github-repo', { async: true }) }); diff --git a/app/models/organization.js b/app/models/organization.js index a60a7b23f..a897994dd 100644 --- a/app/models/organization.js +++ b/app/models/organization.js @@ -10,6 +10,7 @@ export default Model.extend({ name: attr(), slug: attr(), + organizationGithubAppInstallations: hasMany('organization-github-app-installation', { async: true }), owner: belongsTo('user', { async: true }), projects: hasMany('project', { async: true }), stripeConnectAccount: belongsTo('stripe-connect-account', { async: true }) diff --git a/app/models/user.js b/app/models/user.js index e15a55313..cffded76c 100644 --- a/app/models/user.js +++ b/app/models/user.js @@ -27,6 +27,8 @@ export default Model.extend({ stateTransition: attr(), + githubAppInstallations: hasMany('github-app-installation', { async: true }), + projectUsers: hasMany('project-user', { async: true }), stripeConnectSubscriptions: hasMany('stripe-connect-subscription', { async: true }), diff --git a/app/routes/project/settings/integrations.js b/app/routes/project/settings/integrations.js index 9acab2118..79fb4a4c0 100644 --- a/app/routes/project/settings/integrations.js +++ b/app/routes/project/settings/integrations.js @@ -16,13 +16,12 @@ export default Route.extend({ model() { let project = this.modelFor('project'); - let organization = get(project, 'organization'); let user = get(this, 'currentUser.user'); - return RSVP.hash({ project, organization, user }); + return RSVP.hash({ project, user }); }, - setupController(controller, { project, organization, user }) { - setProperties(controller, { project, organization, user }); + setupController(controller, { project, user }) { + setProperties(controller, { project, user }); } }); diff --git a/app/styles/_icons.scss b/app/styles/_icons.scss index d3793d4f9..a833377fd 100644 --- a/app/styles/_icons.scss +++ b/app/styles/_icons.scss @@ -73,7 +73,7 @@ $github-light: 16px 16px $spriteURL 0 -369px $spritex2URL; $github-dark: 16px 16px $spriteURL -16px -369px $spritex2URL; $repo: 12px 16px $spriteURL 0 -385px $spritex2URL; $repo-blue: 12px 16px $spriteURL -12px -385px $spritex2URL; -$sync: 18px 16px $spriteURL -24px -385px $spritex2URL; +$sync: 28px 16px $spriteURL -24px -385px $spritex2URL; .box-icon { @include sprite($box); diff --git a/app/styles/app.scss b/app/styles/app.scss index f6c133218..dbd502bc7 100644 --- a/app/styles/app.scss +++ b/app/styles/app.scss @@ -109,6 +109,8 @@ // // COMPONENTS - GITHUB // +@import "components/github-app-installation"; +@import "components/github-repo"; @import "components/github-connect-state"; // diff --git a/app/styles/components/github-app-installation.scss b/app/styles/components/github-app-installation.scss new file mode 100644 index 000000000..ea463c5b8 --- /dev/null +++ b/app/styles/components/github-app-installation.scss @@ -0,0 +1,42 @@ +.github-app-installation { + border: 1px solid $gray--lightest; + border-radius: 4px; + margin-bottom: 20px; + + &__details { + display: flex; + justify-content: space-between; + padding: 10px 20px; + + > div { + display: flex; + } + + h4 { + font-weight: 700; + line-height: 40px; + margin: 0 0 0 10px; + } + + img { + border-radius: 4px; + height: 40px; + width: 40px; + } + + a, span { + color: $text--lightest; + font-size: $body-font-size-small; + line-height: 40px; + + &:hover { + color: $red; + } + } + } + + &__repos { + border-top: 1px solid $gray--lightest; + padding: 10px 20px; + } +} diff --git a/app/styles/components/github-repo.scss b/app/styles/components/github-repo.scss new file mode 100644 index 000000000..763afa0cc --- /dev/null +++ b/app/styles/components/github-repo.scss @@ -0,0 +1,52 @@ +.github-repo { + display: flex; + justify-content: space-between; + line-height: 30px; + margin-bottom: 10px; + vertical-align: middle; + + &__name { + &:before { + content: ""; + display: inline-block; + margin: 0 10px 0 0; + vertical-align: middle; + @include sprite($repo); + } + } + + &__actions { + display: flex; + font-size: $body-font-size-small; + } + + &__remove-link { + color: $text--lightest; + &:hover { + color: $red; + } + } +} + +.github-repo--connected { + .github-repo__name { + color: $blue; + + &:before { + @include sprite($repo-blue); + } + } + + .github-repo__sync { + color: $blue; + margin-right: 10px; + + &:before { + content: ""; + display: inline-block; + margin: -1px 5px 0 0; + vertical-align: middle; + @include sprite($sync); + } + } +} diff --git a/app/templates/components/common/busy-model-wrapper.hbs b/app/templates/components/common/busy-model-wrapper.hbs new file mode 100644 index 000000000..c12e1b24d --- /dev/null +++ b/app/templates/components/common/busy-model-wrapper.hbs @@ -0,0 +1,9 @@ +{{#if model.isSaving}} + {{#if model.isDeleted}} + {{onDeleting}} + {{else}} + {{onSaving}} + {{/if}} +{{else}} + {{yield}} +{{/if}} diff --git a/app/templates/components/github-repo.hbs b/app/templates/components/github-repo.hbs new file mode 100644 index 000000000..a22b19c6f --- /dev/null +++ b/app/templates/components/github-repo.hbs @@ -0,0 +1,14 @@ +{{#if (and githubRepo.isLoaded (or (not isConnected) projectGithubRepo.isLoaded))}} +
- Install on GitHub Add a GitHub account to the {{project.organization.name}} organization by installing on GitHub.
++ The button will open a new tab, so come back here and refresh the page when you're done. +
+ {{else}} - Connect to GitHub in your {{link-to data-test-integrations-link 'user integration settings' 'settings.integrations'}} before installing GitHub on the {{project.organization.name}} organization. + Connect to GitHub in your {{link-to data-test-integrations-link 'user integration settings' 'settings.integrations'}} before adding a GitHub account to the {{project.organization.name}} organization. {{/if}} diff --git a/app/utils/records-list.js b/app/utils/records-list.js index ff3921ce0..0d00c154f 100644 --- a/app/utils/records-list.js +++ b/app/utils/records-list.js @@ -59,8 +59,8 @@ export default { // Get the id of the found model instance and // get the id of the found model's relationship model instance - let foundId = found.belongsTo(targetModelName).id(); - let foundRelationshipId = found.belongsTo(relationshipModelName).id(); + let foundId = found.belongsTo(targetModelName.camelize()).id(); + let foundRelationshipId = found.belongsTo(relationshipModelName.camelize()).id(); return (foundRelationshipId === relationshipId) // relationships match && (foundId === targetId); // found matches target diff --git a/config/environment.js b/config/environment.js index 66373045e..d7d633a9e 100644 --- a/config/environment.js +++ b/config/environment.js @@ -26,7 +26,8 @@ module.exports = function(environment) { cloudinary: {}, github: { - scope: 'public_repo,admin:org,user:email' + scope: 'public_repo,admin:org,user:email', + appUrl: 'https://github.com/apps/code-corps-local' }, flashMessageDefaults: { diff --git a/mirage/config.js b/mirage/config.js index 72e3d9538..2ee47d19c 100644 --- a/mirage/config.js +++ b/mirage/config.js @@ -89,16 +89,9 @@ export default function() { * Categories */ - // GET /categories this.get('/categories', { coalesce: true }); - - // POST /categories this.post('/categories'); - - // GET /categories/:id this.get('/categories/:id'); - - // PATCH /categories this.patch('/categories/:id'); /** @@ -119,10 +112,8 @@ export default function() { * Comments */ - // GET /comments this.get('/comments', { coalesce: true }); - // POST /comments this.post('/comments', function(schema) { let attrs = this.normalizedRequestAttrs(); // the API takes takes markdown and renders body @@ -130,10 +121,8 @@ export default function() { return schema.create('comment', attrs); }); - // GET /comments/:id this.get('/comments/:id'); - // PATCH /comments/:id this.patch('/comments/:id', function(schema) { let attrs = this.normalizedRequestAttrs(); let comment = schema.comments.find(attrs.id); @@ -154,27 +143,43 @@ export default function() { this.patch('/donation-goals/:id'); this.post('/donation-goals'); + /** + * Github App Installations + */ + + this.get('/github-app-installations', { coalesce: true }); + this.post('/github-app-installations'); + this.get('/github-app-installations/:id'); + + /** + * Github Repos + */ + + this.get('/github-repos', { coalesce: true }); + this.get('/github-repos/:id'); + /** * Organizations */ - // GET /organizations this.get('/organizations', { coalesce: true }); - - // POST /organizations this.post('/organizations'); - - // GET /organizations/:id this.get('/organizations/:id'); - - // PATCH /organizations/:id this.patch('/organizations/:id'); + /** + * Organization Github App Installations + */ + + this.get('/organization-github-app-installations', { coalesce: true }); + this.post('/organization-github-app-installations'); + this.get('/organization-github-app-installations/:id'); + this.delete('/organization-github-app-installations/:id'); + /** * Password */ - // POST /password/forgot this.post('/password/forgot', () => { // just return something? @@ -213,7 +218,6 @@ export default function() { * Previews */ - // POST /previews this.post('/previews', function(schema) { let attrs = this.normalizedRequestAttrs(); // the API takes takes markdown and renders body @@ -225,7 +229,6 @@ export default function() { * Preview user mentions */ - // GET /preview-user-mentions this.get('/preview-user-mentions', (schema, request) => { let previewId = request.queryParams.preview_id; let preview = schema.previews.find(previewId); @@ -239,32 +242,27 @@ export default function() { * Project categories */ - // GET /project-categories this.get('/project-categories', { coalesce: true }); - - // POST /project-categories this.post('/project-categories'); - - // GET /project-categories/:id this.get('/project-categories/:id'); - - // DELETE /project-categories/:id this.delete('/project-categories/:id'); + /** + * Project Github Repos + */ + + this.get('/project-github-repos', { coalesce: true }); + this.post('/project-github-repos'); + this.get('/project-github-repos/:id'); + this.delete('/project-github-repos/:id'); + /** * Project skills */ - // GET /project-skills this.get('/project-skills', { coalesce: true }); - - // POST /project-skills this.post('/project-skills'); - - // GET /project-skills/:id this.get('/project-skills/:id'); - - // DELETE /project-skills/:id this.delete('/project-skills/:id'); /** @@ -281,16 +279,10 @@ export default function() { * Projects */ - // GET /projects this.get('/projects', { coalesce: true }); - - // POST /projects this.post('/projects'); - - // GET /projects/:id this.get('/projects/:id'); - // GET project/:id/tasks this.get('/projects/:projectId/tasks', (schema, request) => { let { projectId } = request.params; let taskStatus = request.queryParams.status; @@ -324,7 +316,6 @@ export default function() { return tasksPage; }); - // GET /projects/:id/task/:number this.get('/projects/:projectId/tasks/:number', (schema, request) => { let projectId = parseInt(request.params.projectId); let number = parseInt(request.params.number); @@ -341,7 +332,6 @@ export default function() { return task; }); - // PATCH /projects/:id this.patch('/projects/:id', function(schema) { // the API takes takes markdown and renders body let attrs = this.normalizedRequestAttrs(); @@ -354,29 +344,17 @@ export default function() { * Roles */ - // GET /roles this.get('/roles', { coalesce: true }); - - // POST /roles this.post('/roles'); - - // GET /roles/:id this.get('/roles/:id'); /** * Role Skills */ - // GET /role-skills this.get('/role-skills', { coalesce: true }); - - // POST /role-skills this.post('/role-skills'); - - // GET /role-skills/:id this.get('/role-skills/:id'); - - // DELETE /role-skills/:id this.delete('/role-skills/:id'); /** @@ -413,13 +391,8 @@ export default function() { * Skills */ - // GET /skills this.get('/skills', { coalesce: true }); - - // POST /skills this.post('/skills'); - - // GET /skills/:id this.get('/skills/:id'); /** @@ -493,10 +466,7 @@ export default function() { * Task lists */ - // GET /task-lists this.get('/task-lists', { coalesce: true }); - - // GET /task-lists/:id this.get('/task-lists/:id'); /** @@ -672,37 +642,24 @@ export default function() { * User roles */ - // GET /user-roles this.get('/user-roles', { coalesce: true }); - - // POST /user-roles this.post('/user-roles'); - - // GET /user-roles this.get('/user-roles/:id'); - - // DELETE /user-roles/:id this.delete('/user-roles/:id'); /** * User skills */ - // GET /user-skills this.get('/user-skills', { coalesce: true }); - - // POST /user-skills this.post('/user-skills'); - - // GET /user-skills this.get('/user-skills/:id'); - - // DELETE /user-skills/:id this.delete('/user-skills/:id'); /** * User tasks */ + this.get('/user-tasks', { coalesce: true }); this.patch('/user-tasks/:id'); this.post('/user-tasks'); diff --git a/mirage/factories/github-app-installation.js b/mirage/factories/github-app-installation.js new file mode 100644 index 000000000..b2281d1d4 --- /dev/null +++ b/mirage/factories/github-app-installation.js @@ -0,0 +1,7 @@ +import { Factory, faker } from 'ember-cli-mirage'; + +export default Factory.extend({ + githubAccountAvatarUrl: faker.image.imageUrl, + githubAccountLogin: faker.internet.domainWord, + githubAccountType: faker.list.cycle('Organization', 'User') +}); diff --git a/mirage/models/category.js b/mirage/models/category.js index ab97d4543..9016ded80 100644 --- a/mirage/models/category.js +++ b/mirage/models/category.js @@ -1,6 +1,6 @@ import { Model, hasMany } from 'ember-cli-mirage'; export default Model.extend({ - projectCategories: hasMany(), - userCategories: hasMany() + projectCategories: hasMany('project-category'), + userCategories: hasMany('user-category') }); diff --git a/mirage/models/comment.js b/mirage/models/comment.js index a6504e3b9..e8ad0750a 100644 --- a/mirage/models/comment.js +++ b/mirage/models/comment.js @@ -1,7 +1,7 @@ import { Model, belongsTo, hasMany } from 'ember-cli-mirage'; export default Model.extend({ - commentUserMentions: hasMany(), + commentUserMentions: hasMany('comment-user-mention'), task: belongsTo(), user: belongsTo() }); diff --git a/mirage/models/github-app-installation.js b/mirage/models/github-app-installation.js index d38c274e1..5db8e326f 100644 --- a/mirage/models/github-app-installation.js +++ b/mirage/models/github-app-installation.js @@ -1,8 +1,7 @@ import { Model, belongsTo, hasMany } from 'ember-cli-mirage'; export default Model.extend({ - githubRepos: hasMany(), - organizationGithubAppInstallations: hasMany(), - project: belongsTo(), + githubRepos: hasMany('github-repo'), + organizationGithubAppInstallations: hasMany('organization-github-app-installation'), user: belongsTo() }); diff --git a/mirage/models/github-repo.js b/mirage/models/github-repo.js index abe5ab466..14b42154a 100644 --- a/mirage/models/github-repo.js +++ b/mirage/models/github-repo.js @@ -1,5 +1,5 @@ import { Model, belongsTo } from 'ember-cli-mirage'; export default Model.extend({ - githubAppInstallation: belongsTo() + githubAppInstallation: belongsTo('github-app-installation') }); diff --git a/mirage/models/organization-github-app-installation.js b/mirage/models/organization-github-app-installation.js index 639648a2c..53c839a12 100644 --- a/mirage/models/organization-github-app-installation.js +++ b/mirage/models/organization-github-app-installation.js @@ -1,6 +1,6 @@ import { Model, belongsTo } from 'ember-cli-mirage'; export default Model.extend({ - githubAppInstallation: belongsTo(), + githubAppInstallation: belongsTo('github-app-installation'), organization: belongsTo() }); diff --git a/mirage/models/organization.js b/mirage/models/organization.js index 69c3f588e..1cdc06886 100644 --- a/mirage/models/organization.js +++ b/mirage/models/organization.js @@ -1,7 +1,8 @@ import { Model, belongsTo, hasMany } from 'ember-cli-mirage'; export default Model.extend({ + organizationGithubAppInstallations: hasMany('organization-github-app-installation'), owner: belongsTo('user'), projects: hasMany(), - stripeConnectAccount: belongsTo() + stripeConnectAccount: belongsTo('stripe-connect-account') }); diff --git a/mirage/models/preview.js b/mirage/models/preview.js index 6c4837606..bacc50dcd 100644 --- a/mirage/models/preview.js +++ b/mirage/models/preview.js @@ -1,6 +1,6 @@ import { Model, belongsTo, hasMany } from 'ember-cli-mirage'; export default Model.extend({ - previewUserMentions: hasMany(), + previewUserMentions: hasMany('preview-user-mention'), user: belongsTo() }); diff --git a/mirage/models/project-github-repo.js b/mirage/models/project-github-repo.js index 2a3d93c46..385b35262 100644 --- a/mirage/models/project-github-repo.js +++ b/mirage/models/project-github-repo.js @@ -1,6 +1,6 @@ import { Model, belongsTo } from 'ember-cli-mirage'; export default Model.extend({ - githubRepo: belongsTo(), + githubRepo: belongsTo('github-repo'), project: belongsTo() }); diff --git a/mirage/models/project.js b/mirage/models/project.js index ccd80f79b..39ef5a3db 100644 --- a/mirage/models/project.js +++ b/mirage/models/project.js @@ -1,12 +1,13 @@ import { Model, belongsTo, hasMany } from 'ember-cli-mirage'; export default Model.extend({ - donationGoals: hasMany(), + donationGoals: hasMany('donation-goal'), organization: belongsTo(), - projectCategories: hasMany(), - projectSkills: hasMany(), - projectUsers: hasMany(), - stripeConnectPlan: belongsTo(), - taskLists: hasMany(), + projectCategories: hasMany('project-category'), + projectGithubRepos: hasMany('project-github-repo'), + projectSkills: hasMany('project-skill'), + projectUsers: hasMany('project-user'), + stripeConnectPlan: belongsTo('stripe-connect-plan'), + taskLists: hasMany('task-list'), tasks: hasMany() }); diff --git a/mirage/models/role.js b/mirage/models/role.js index 8bc38dcf1..8d4c0a4dc 100644 --- a/mirage/models/role.js +++ b/mirage/models/role.js @@ -1,5 +1,5 @@ import { Model, hasMany } from 'ember-cli-mirage'; export default Model.extend({ - userRoles: hasMany() + userRoles: hasMany('user-role') }); diff --git a/mirage/models/skill.js b/mirage/models/skill.js index b37297480..fc405ce2a 100644 --- a/mirage/models/skill.js +++ b/mirage/models/skill.js @@ -1,5 +1,5 @@ import { Model, hasMany } from 'ember-cli-mirage'; export default Model.extend({ - userSkills: hasMany() + userSkills: hasMany('user-skill') }); diff --git a/mirage/models/task.js b/mirage/models/task.js index e06d28ef3..a33d72e17 100644 --- a/mirage/models/task.js +++ b/mirage/models/task.js @@ -2,9 +2,9 @@ import { Model, belongsTo, hasMany } from 'ember-cli-mirage'; export default Model.extend({ comments: hasMany(), - commentUserMentions: hasMany(), + commentUserMentions: hasMany('comment-user-mention'), taskList: belongsTo(), - taskUserMentions: hasMany(), + taskUserMentions: hasMany('task-user-mention'), project: belongsTo(), user: belongsTo(), userTask: belongsTo() diff --git a/mirage/models/user.js b/mirage/models/user.js index 7587ec8e4..ccf62d237 100644 --- a/mirage/models/user.js +++ b/mirage/models/user.js @@ -1,12 +1,13 @@ import { Model, belongsTo, hasMany } from 'ember-cli-mirage'; export default Model.extend({ + githubAppInstallations: hasMany('github-app-installation'), projectUsers: hasMany(), sluggedRoute: belongsTo(), stripeConnectSubscriptions: hasMany('stripe-connect-subscription'), stripePlatformCard: belongsTo('stripe-platform-card'), stripePlatformCustomer: belongsTo('stripe-platform-customer'), - userCategories: hasMany(), - userRoles: hasMany(), - userSkills: hasMany() + userCategories: hasMany('user-category'), + userRoles: hasMany('user-role'), + userSkills: hasMany('user-skill') }); diff --git a/mirage/scenarios/default.js b/mirage/scenarios/default.js index 13525e5c7..6cdb08a7f 100644 --- a/mirage/scenarios/default.js +++ b/mirage/scenarios/default.js @@ -164,6 +164,8 @@ export default function(server) { let owner = server.create('user', { email: 'owner@codecorps.org', + githubId: 12345, + githubUsername: 'codecorps-owner', password: 'password', username: 'codecorps-owner' }); @@ -220,6 +222,32 @@ export default function(server) { username: 'random' }); + let connectedInstallation = server.create('github-app-installation', { + githubAccountAvatarUrl: 'https://avatars0.githubusercontent.com/u/12991115?v=4', + githubAccountLogin: 'code-corps', + user: owner + }); + + server.create('organization-github-app-installation', { + githubAppInstallation: connectedInstallation, + organization + }); + + let githubRepo = server.create('github-repo', { + githubAppInstallation: connectedInstallation, + name: 'code-corps-api' + }); + + server.create('project-github-repo', { + githubRepo, + project + }); + + server.create('github-repo', { + githubAppInstallation: connectedInstallation, + name: 'code-corps-ember' + }); + let inboxTaskList = server.create('task-list', { name: 'Inbox', position: 0, diff --git a/package.json b/package.json index 8141eea6d..2142bbc4f 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "ember-cli-inject-live-reload": "^1.4.1", "ember-cli-jwt-decode": "0.0.3", "ember-cli-meta-tags": "3.1.0", - "ember-cli-mirage": "^0.3.1", + "ember-cli-mirage": "^0.3.4", "ember-cli-moment-shim": "3.3.2", "ember-cli-neat": "^0.0.6", "ember-cli-page-object": "1.9.0", diff --git a/tests/acceptance/project-settings-integrations-test.js b/tests/acceptance/project-settings-integrations-test.js index 3d6f94089..92d2794c2 100644 --- a/tests/acceptance/project-settings-integrations-test.js +++ b/tests/acceptance/project-settings-integrations-test.js @@ -1,7 +1,7 @@ import { test } from 'qunit'; import moduleForAcceptance from 'code-corps-ember/tests/helpers/module-for-acceptance'; import { authenticateSession } from 'code-corps-ember/tests/helpers/ember-simple-auth'; -import projectIntegrationsPage from '../pages/project/settings/integrations'; +import projectIntegrationsPage from 'code-corps-ember/tests/pages/project/settings/integrations'; moduleForAcceptance('Acceptance | Project Settings - Integrations'); @@ -57,3 +57,128 @@ test('it renders link to user integrations page if user is not connected to GitH assert.notOk(projectIntegrationsPage.installationLink.isVisible, 'The link to GitHub app installation does not render'); }); }); + +test('it allows connecting and unconnecting installations', function(assert) { + assert.expect(5); + + let user = server.create('user', { githubId: null }); + let { project } = server.create('project-user', { role: 'owner', user }); + + server.create('github-app-installation', { user }); + + authenticateSession(this.application, { user_id: user.id }); + + projectIntegrationsPage.visit({ + organization: project.organization.slug, + project: project.slug + }); + + andThen(() => { + assert.equal( + projectIntegrationsPage.unconnectedInstallations().count, 1, + 'Installation is initially rendered as unconnected.' + ); + projectIntegrationsPage.unconnectedInstallations(0).connect.click(); + }); + + andThen(() => { + assert.equal( + projectIntegrationsPage.unconnectedInstallations().count, 0, + 'Upon hitting "connect", installation is no longer rendered as unconnected.' + ); + assert.equal( + projectIntegrationsPage.connectedInstallations().count, 1, + 'Upon hitting "connect", installation is now rendered as connected.' + ); + projectIntegrationsPage.connectedInstallations(0).disconnect.click(); + }); + + andThen(() => { + assert.equal( + projectIntegrationsPage.connectedInstallations().count, 0, + 'Upon hitting "disconnect", installation is no longer rendered as connected.' + ); + assert.equal( + projectIntegrationsPage.unconnectedInstallations().count, 1, + 'Upon hitting "disconnect", innstallation is now rendered as unconnected again.' + ); + }); +}); + +test('it allows connecting and unconnecting repos for a connected installation', function(assert) { + assert.expect(11); + let user = server.create('user', { githubId: null }); + let { project } = server.create('project-user', { role: 'owner', user }); + let { organization } = project; + let githubAppInstallation = server.create('github-app-installation', { user }); + + server.create('organization-github-app-installation', { organization, githubAppInstallation }); + server.create('github-repo', { githubAppInstallation }); + + authenticateSession(this.application, { user_id: user.id }); + + projectIntegrationsPage.visit({ + organization: project.organization.slug, + project: project.slug + }); + + andThen(() => { + assert.equal( + projectIntegrationsPage.connectedInstallations().count, 1, + 'Installation is initially rendered as connected.' + ); + + projectIntegrationsPage.connectedInstallations(0).as((installation) => { + assert.equal(installation.githubRepos().count, 1, 'Github repo is rendered'); + assert.ok( + installation.githubRepos(0).actions.connect.isVisible, + 'Github repo is unconnected, so "connect" is visible' + ); + installation.githubRepos(0).actions.connect.click(); + }); + }); + + andThen(() => { + assert.equal( + projectIntegrationsPage.connectedInstallations().count, 1, + 'Installation is still rendered as connected, after connecting repo.' + ); + + projectIntegrationsPage.connectedInstallations(0).as((installation) => { + assert.equal( + installation.githubRepos().count, 1, + 'Github repo is still rendered after connecting it.' + ); + assert.notOk( + installation.githubRepos(0).actions.connect.isVisible, + 'Github repo is now connected, so "connect" is no longer visible' + ); + assert.ok( + installation.githubRepos(0).actions.disconnect.isVisible, + 'Github repo is now connected, so "disconnect" is longer visible' + ); + installation.githubRepos(0).actions.disconnect.click(); + }); + }); + + andThen(() => { + assert.equal( + projectIntegrationsPage.connectedInstallations().count, 1, + 'Installation is still rendered as connected, after disconnecting again.' + ); + + projectIntegrationsPage.connectedInstallations(0).as((installation) => { + assert.equal( + installation.githubRepos().count, 1, + 'Github repo is still rendered, after disconnecting it again.'); + assert.ok( + installation.githubRepos(0).actions.connect.isVisible, + 'Github repo is now disconnected again, so "connect" is longer visible' + ); + assert.notOk( + installation.githubRepos(0).actions.disconnect.isVisible, + 'Github repo is now disconnected again, so "disconnect" is no longer visible' + ); + }); + }); +}); diff --git a/tests/integration/components/common/busy-model-wrapper-test.js b/tests/integration/components/common/busy-model-wrapper-test.js new file mode 100644 index 000000000..b9a4868a1 --- /dev/null +++ b/tests/integration/components/common/busy-model-wrapper-test.js @@ -0,0 +1,25 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('common/busy-model-wrapper', 'Integration | Component | common/busy model wrapper', { + integration: true +}); + +test('it renders', function(assert) { + + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.on('myAction', function(val) { ... }); + + this.render(hbs`{{common/busy-model-wrapper}}`); + + assert.equal(this.$().text().trim(), ''); + + // Template block usage: + this.render(hbs` + {{#common/busy-model-wrapper}} + template block text + {{/common/busy-model-wrapper}} + `); + + assert.equal(this.$().text().trim(), 'template block text'); +}); diff --git a/tests/integration/components/github-repo-test.js b/tests/integration/components/github-repo-test.js new file mode 100644 index 000000000..f50d551a6 --- /dev/null +++ b/tests/integration/components/github-repo-test.js @@ -0,0 +1,78 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; +import Ember from 'ember'; +import PageObject from 'ember-cli-page-object'; +import githubRepoComponent from 'code-corps-ember/tests/pages/components/github-repo'; + +let page = PageObject.create(githubRepoComponent); + +const { + set, + run +} = Ember; + +function renderPage() { + page.render(hbs` + {{#github-repo model=model}} + Block Content + {{/github-repo}} + `); +} + +moduleForComponent('github-repo', 'Integration | Component | github repo', { + integration: true, + beforeEach() { + page.setContext(this); + }, + afterEach() { + page.removeContext(); + } +}); + +test('it renders the github repo name', function(assert) { + assert.expect(1); + let githubRepo = { name: 'code-corps-ember', isLoaded: true }; + set(this, 'model', { githubRepo }); + renderPage(); + assert.equal(page.name.text, 'code-corps-ember'); +}); + +test('it changes state based on loading, presence of records', function(assert) { + assert.expect(4); + + set(this, 'model', {}); + + renderPage(); + + run(() => set(this, 'model.githubRepo', { isLoaded: false })); + assert.equal(page.text, 'Loading...', 'With github repo loading and no project github repo, state should be loading.'); + + run(() => set(this, 'model.githubRepo.isLoaded', true)); + assert.equal(page.text, 'Block Content', 'With github repo loaded and no project github repo, state should be loaded.'); + + run(() => set(this, 'model.projectGithubRepo', { isLoaded: false })); + assert.equal(page.text, 'Loading...', 'With project github repo loading, state should be loading.'); + + run(() => set(this, 'model.projectGithubRepo.isLoaded', true)); + assert.equal(page.text, 'Block Content', 'With project github repo loaded, state should be loaded.'); +}); + +test('it changes state based on projectGithubRepo model state', function(assert) { + assert.expect(3); + + set(this, 'model', {}); + + renderPage(); + + run(() => set(this, 'model.githubRepo', { isLoaded: true })); + + run(() => set(this, 'model.projectGithubRepo', { isLoaded: true, isSaving: false, isDeleted: false })); + assert.equal(page.text, 'Block Content', 'With project github repo in default state, state should be default.'); + + run(() => set(this, 'model.projectGithubRepo', { isLoaded: true, isSaving: true, isDeleted: false })); + assert.equal(page.text, 'Connecting...', 'With project github repo in saving state, state should be saving.'); + + run(() => set(this, 'model.projectGithubRepo', { isLoaded: true, isSaving: true, isDeleted: true })); + assert.equal(page.text, 'Disconnecting...', 'With project github repo in deleted and saving state, state should be deleting.'); + +}); diff --git a/tests/integration/components/github/connected-installation-test.js b/tests/integration/components/github/connected-installation-test.js new file mode 100644 index 000000000..d069d2ba9 --- /dev/null +++ b/tests/integration/components/github/connected-installation-test.js @@ -0,0 +1,122 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; +import Ember from 'ember'; +import PageObject from 'ember-cli-page-object'; +import component from 'code-corps-ember/tests/pages/components/github/connected-installation'; + +const { set, setProperties, run } = Ember; + +let page = PageObject.create(component); + +function renderPage() { + page.render(hbs` + {{github/connected-installation + disconnect=(action disconnect) + connectRepo=(action connectRepo) + disconnectRepo=(action disconnectRepo) + organizationGithubAppInstallation=organizationGithubAppInstallation + project=project + }} + `); +} + +function setHandlers(context, { + disconnect = () => {}, + connectRepo = () => {}, + disconnectRepo = () => {} +} = {}) { + set(context, 'disconnect', disconnect); + set(context, 'connectRepo', connectRepo); + set(context, 'disconnectRepo', disconnectRepo); +} + +moduleForComponent('github/connected-installation', 'Integration | Component | github/connected installation', { + integration: true, + beforeEach() { + page.setContext(this); + setHandlers(this); + }, + afterEach() { + page.removeContext(); + } +}); + +const connectedRepo = { id: 1, name: 'Connected Repo', isLoaded: true }; +const unconnectedRepo = { id: 2, name: 'Unconnected Repo', isLoaded: true }; +const loadingRepo = { id: 3, isLoaded: false }; + +const githubAppInstallation = { + githubAccountLogin: 'foo-login', + githubAccountAvatarUrl: 'foo-url', + githubRepos: [connectedRepo, unconnectedRepo, loadingRepo] +}; + +const organizationGithubAppInstallation = { githubAppInstallation, organization }; + +const organization = { name: 'Organization' }; + +const projectGithubRepo = { + githubRepo: connectedRepo, + id: 'project-github-repo', + isLoaded: true, + belongsTo() { + return { id: () => 1 }; + } +}; + +const project = { + id: 'project', + organization, + projectGithubRepos: [projectGithubRepo] +}; + +test('renders correct elements for provided github app installation', function(assert) { + assert.expect(15); + + setProperties(this, { organizationGithubAppInstallation, project }); + renderPage(); + + assert.ok(page.avatar.url.indexOf('foo-url') > -1, 'Avatar url is rendered.'); + assert.equal(page.login.text, 'foo-login', 'Account login is rendered.'); + assert.ok(page.disconnect.text.indexOf(organization.name, 'Organization name is rendered on button.') > -1); + + assert.equal(page.githubRepos().count, 3, 'All repos are rendered.'); + + assert.equal(page.githubRepos(0).name.text, 'Connected Repo'); + assert.notOk(page.githubRepos(0).inLoadingState, 'Repo is not in loading state.'); + assert.notOk(page.githubRepos(0).actions.connect.isVisible, 'Repo is connected, so connect button is hidden.'); + assert.ok(page.githubRepos(0).actions.disconnect.isVisible, 'Repo is connected, so disconnect button is visible.'); + + assert.equal(page.githubRepos(1).name.text, 'Unconnected Repo'); + assert.notOk(page.githubRepos(1).inLoadingState, 'Repo is not in loading state.'); + assert.ok(page.githubRepos(1).actions.connect.isVisible, 'Repo is unconnected, so connect button is visible.'); + assert.notOk(page.githubRepos(1).actions.disconnect.isVisible, 'Repo is unconnected, so disconnect button is hidden.'); + + assert.ok(page.githubRepos(2).inLoadingState, 'Repo is in loading state.'); + assert.notOk(page.githubRepos(2).actions.connect.isVisible, 'Repo is in loading state. Neither button should render.'); + assert.notOk(page.githubRepos(2).actions.disconnect.isVisible, 'Repo is in loading state. Neither button should render.'); +}); + +test('triggers/passes out all actions action', function(assert) { + assert.expect(4); + + setProperties(this, { organizationGithubAppInstallation, project }); + setHandlers(this, { + disconnect: () => { + assert.ok('Disconnect handler was called.'); + }, + connectRepo: (repo, project) => { + assert.equal(repo.id, 2, 'Correct repo was passed out on called action.'); + assert.equal(project.id, 'project', 'Correct project was passed out on called action.'); + }, + disconnectRepo: (projectGithubRepo) => { + assert.equal(projectGithubRepo.id, 'project-github-repo', 'Correct repo was passed out on called action.'); + } + }); + + renderPage(); + + run(() => page.disconnect.click()); + run(() => page.githubRepos(0).actions.disconnect.click()); + run(() => page.githubRepos(1).actions.connect.click()); +}); diff --git a/tests/integration/components/github/unconnected-installation-test.js b/tests/integration/components/github/unconnected-installation-test.js new file mode 100644 index 000000000..51bc753fe --- /dev/null +++ b/tests/integration/components/github/unconnected-installation-test.js @@ -0,0 +1,66 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; +import Ember from 'ember'; +import PageObject from 'ember-cli-page-object'; +import component from 'code-corps-ember/tests/pages/components/github/unconnected-installation'; + +const { set, setProperties, run } = Ember; + +let page = PageObject.create(component); + +function renderPage() { + page.render(hbs` + {{github/unconnected-installation + connect=(action connect) + githubAppInstallation=githubAppInstallation + organization=organization + }} + `); +} + +function setHandlers(context, connect = () => {}) { + set(context, 'connect', connect); +} + +moduleForComponent('github/unconnected-installation', 'Integration | Component | github/unconnected installation', { + integration: true, + beforeEach() { + page.setContext(this); + setHandlers(this); + }, + afterEach() { + page.removeContext(); + } +}); + +const githubAppInstallation = { + githubAccountLogin: 'foo-login', + githubAccountAvatarUrl: 'foo-url', + githubRepos: [] +}; + +const organization = { name: 'Organization' }; + +test('renders correct elements for provided github app installation', function(assert) { + assert.expect(3); + + setProperties(this, { githubAppInstallation, organization }); + renderPage(); + + assert.ok(page.avatar.url.indexOf('foo-url') > -1, 'Avatar url is rendered.'); + assert.equal(page.login.text, 'foo-login', 'Account login is rendered.'); + assert.ok(page.connect.text.indexOf(organization.name, 'Organization name is rendered on button.') > -1); +}); + +test('triggers/passes out all actions action', function(assert) { + assert.expect(1); + + setProperties(this, { githubAppInstallation, organization }); + setHandlers(this, () => { + assert.ok('Connect action was called.'); + }); + + renderPage(); + + run(() => page.connect.click()); +}); diff --git a/tests/pages/components/github-repo.js b/tests/pages/components/github-repo.js new file mode 100644 index 000000000..698075bf3 --- /dev/null +++ b/tests/pages/components/github-repo.js @@ -0,0 +1,28 @@ +import testSelector from 'ember-test-selectors'; + +export default { + scope: '.github-repo', + + name: { + scope: testSelector('github-repo-name') + }, + + inLoadingState: { + isDescriptor: true, + get() { + return this.text.indexOf('Loading') > -1; + } + }, + + actions: { + scope: testSelector('github-repo-actions'), + + connect: { + scope: testSelector('connect-repo') + }, + + disconnect: { + scope: testSelector('disconnect-repo') + } + } +}; diff --git a/tests/pages/components/github/connected-installation.js b/tests/pages/components/github/connected-installation.js new file mode 100644 index 000000000..92a027fda --- /dev/null +++ b/tests/pages/components/github/connected-installation.js @@ -0,0 +1,25 @@ +import { attribute, collection } from 'ember-cli-page-object'; +import testSelector from 'ember-test-selectors'; +import githubRepo from 'code-corps-ember/tests/pages/components/github-repo'; + +export default { + scope: '.github-app-installation.connected', + + avatar: { + scope: testSelector('avatar'), + url: attribute('src') + }, + + login: { + scope: testSelector('login') + }, + + disconnect: { + scope: testSelector('disconnect') + }, + + githubRepos: collection({ + itemScope: '.github-repo', + item: githubRepo + }) +}; diff --git a/tests/pages/components/github/unconnected-installation.js b/tests/pages/components/github/unconnected-installation.js new file mode 100644 index 000000000..19f861881 --- /dev/null +++ b/tests/pages/components/github/unconnected-installation.js @@ -0,0 +1,19 @@ +import { attribute } from 'ember-cli-page-object'; +import testSelector from 'ember-test-selectors'; + +export default { + scope: '.github-app-installation.unconnected', + + avatar: { + scope: testSelector('avatar'), + url: attribute('src') + }, + + login: { + scope: testSelector('login') + }, + + connect: { + scope: testSelector('connect') + } +}; diff --git a/tests/pages/project/settings/integrations.js b/tests/pages/project/settings/integrations.js index a6e941ae7..a8f2f3f3c 100644 --- a/tests/pages/project/settings/integrations.js +++ b/tests/pages/project/settings/integrations.js @@ -1,12 +1,26 @@ -import { create, visitable } from 'ember-cli-page-object'; +import { collection, create, visitable } from 'ember-cli-page-object'; import testSelector from 'ember-test-selectors'; +import connectedInstallation from 'code-corps-ember/tests/pages/components/github/connected-installation'; +import unconnectedInstallation from 'code-corps-ember/tests/pages/components/github/unconnected-installation'; export default create({ visit: visitable(':organization/:project/settings/integrations'), + integrationsLink: { scope: testSelector('integrations-link') }, + installationLink: { scope: testSelector('installation-link') - } + }, + + connectedInstallations: collection({ + itemScope: '.github-app-installation.connected', + item: connectedInstallation + }), + + unconnectedInstallations: collection({ + itemScope: '.github-app-installation.unconnected', + item: unconnectedInstallation + }) }); diff --git a/tests/unit/models/github-app-installation-test.js b/tests/unit/models/github-app-installation-test.js index 00015b548..6f31068d5 100644 --- a/tests/unit/models/github-app-installation-test.js +++ b/tests/unit/models/github-app-installation-test.js @@ -8,7 +8,6 @@ moduleForModel('github-app-installation', 'Unit | Model | github-app-installatio needs: [ 'model:github-repo', 'model:organization-github-app-installation', - 'model:project', 'model:user' ] }); @@ -19,6 +18,10 @@ test('it exists', function(assert) { }); testForAttributes('github-app-installation', [ + 'githubAccountAvatarUrl', + 'githubAccountId', + 'githubAccountLogin', + 'githubAccountType', 'githubId', 'insertedAt', 'installed', @@ -27,6 +30,5 @@ testForAttributes('github-app-installation', [ ]); testForHasMany('github-app-installation', 'organizationGithubAppInstallations'); -testForBelongsTo('github-app-installation', 'project'); testForBelongsTo('github-app-installation', 'user'); testForHasMany('github-app-installation', 'githubRepos'); diff --git a/tests/unit/models/github-repo-test.js b/tests/unit/models/github-repo-test.js index 3b33703a7..25cabce44 100644 --- a/tests/unit/models/github-repo-test.js +++ b/tests/unit/models/github-repo-test.js @@ -6,7 +6,8 @@ import '../../helpers/has-attributes'; moduleForModel('github-repo', 'Unit | Model | github-repo', { // Specify the other units that are required for this test. needs: [ - 'model:github-app-installation' + 'model:github-app-installation', + 'model:project-github-repo' ] }); diff --git a/yarn.lock b/yarn.lock index 8432dcd15..f3475a90f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1641,7 +1641,7 @@ broccoli-sri-hash@^2.1.0: sri-toolbox "^0.2.0" symlink-or-copy "^1.0.1" -broccoli-stew@^1.2.0, broccoli-stew@^1.3.3, broccoli-stew@^1.4.0, broccoli-stew@^1.4.2: +broccoli-stew@^1.2.0, broccoli-stew@^1.3.3, broccoli-stew@^1.4.0, broccoli-stew@^1.4.2, broccoli-stew@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/broccoli-stew/-/broccoli-stew-1.5.0.tgz#d7af8c18511dce510e49d308a62e5977f461883c" dependencies: @@ -2809,13 +2809,13 @@ ember-cli-meta-tags@3.1.0: ember-cli-htmlbars "^1.1.1" ember-runtime-enumerable-includes-polyfill "^2.0.0" -ember-cli-mirage@^0.3.1: - version "0.3.3" - resolved "https://registry.yarnpkg.com/ember-cli-mirage/-/ember-cli-mirage-0.3.3.tgz#42b8779d857859341cbb219050ea2fec4d72ea88" +ember-cli-mirage@^0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/ember-cli-mirage/-/ember-cli-mirage-0.3.4.tgz#eeb9d6e02c0c49c81915762178bab9a42d86ada8" dependencies: broccoli-funnel "^1.0.2" broccoli-merge-trees "^1.1.0" - broccoli-unwatched-tree "^0.1.1" + broccoli-stew "^1.5.0" chalk "^1.1.1" ember-cli-babel "^5.1.7" ember-cli-node-assets "^0.1.4" @@ -5772,7 +5772,7 @@ node-sass@^3.4: request "^2.61.0" sass-graph "^2.1.1" -node-sass@^4.1.0, node-sass@^4.5.3: +node-sass@^4.1.0: version "4.5.3" resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.5.3.tgz#d09c9d1179641239d1b97ffc6231fdcec53e1568" dependencies: