From 07ea9d8a4ab3879ab8d7f62efef881377613b524 Mon Sep 17 00:00:00 2001 From: Amiel Martin Date: Fri, 30 Jun 2017 10:52:08 -0700 Subject: [PATCH 1/4] Use yarn in travis setup --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 72f90c9..e7db3ad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ before_install: - phantomjs --version install: - - npm install + - yarn install script: # Usually, it's ok to finish the test scenario without reverting From fdad7ae565401e6e8abe79f9fe177af07781f8cb Mon Sep 17 00:00:00 2001 From: Amiel Martin Date: Fri, 30 Jun 2017 16:35:31 -0700 Subject: [PATCH 2/4] Add test for queryRecord template --- tests/acceptance/basic-url-template-test-test.js | 16 +++++++++++++++- tests/dummy/app/adapters/post.js | 3 ++- tests/dummy/app/models/post.js | 1 + tests/dummy/app/router.js | 1 + tests/dummy/app/routes/post.js | 7 +++++++ tests/dummy/app/templates/post.hbs | 3 +++ tests/dummy/app/templates/posts.hbs | 2 +- tests/dummy/mirage/config.js | 3 +++ 8 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 tests/dummy/app/routes/post.js create mode 100644 tests/dummy/app/templates/post.hbs diff --git a/tests/acceptance/basic-url-template-test-test.js b/tests/acceptance/basic-url-template-test-test.js index c4be30e..5c76244 100644 --- a/tests/acceptance/basic-url-template-test-test.js +++ b/tests/acceptance/basic-url-template-test-test.js @@ -3,7 +3,7 @@ import moduleForAcceptance from '../../tests/helpers/module-for-acceptance'; moduleForAcceptance('Acceptance | basic url template test'); -test('visiting /posts', function(assert) { +test('it can use a simple custom url', function(assert) { server.createList('post', 5); visit('/posts'); @@ -12,3 +12,17 @@ test('visiting /posts', function(assert) { assert.equal(find('#posts .post').length, '5'); }); }); + +test('it can use a specific template for one type of call (queryRecord)', function(assert) { + server.create('post', { + slug: 'my-first-post', + title: 'This is my first post', + }); + + visit('/posts/my-first-post'); + + andThen(() => { + assert.equal(find('.post h2').text(), 'This is my first post'); + }); +}); + diff --git a/tests/dummy/app/adapters/post.js b/tests/dummy/app/adapters/post.js index 0515fad..95ab74d 100644 --- a/tests/dummy/app/adapters/post.js +++ b/tests/dummy/app/adapters/post.js @@ -1,6 +1,7 @@ import ApplicationAdapter from './application'; export default ApplicationAdapter.extend({ - urlTemplate: "{+host}{/namespace}/my-posts{/id}", + urlTemplate: "{+host}{/namespace}/my-posts", + queryRecordUrlTemplate: "{+host}{/namespace}/my-posts/{slug}", namespace: 'api', }); diff --git a/tests/dummy/app/models/post.js b/tests/dummy/app/models/post.js index ccdc48b..b2f8143 100644 --- a/tests/dummy/app/models/post.js +++ b/tests/dummy/app/models/post.js @@ -3,5 +3,6 @@ import DS from 'ember-data'; const { attr } = DS; export default DS.Model.extend({ + slug: attr('string'), title: attr('string'), }); diff --git a/tests/dummy/app/router.js b/tests/dummy/app/router.js index c55b4e4..e12dd93 100644 --- a/tests/dummy/app/router.js +++ b/tests/dummy/app/router.js @@ -8,6 +8,7 @@ const Router = Ember.Router.extend({ Router.map(function() { this.route('posts'); + this.route('post', { path: '/posts/:slug' }); }); export default Router; diff --git a/tests/dummy/app/routes/post.js b/tests/dummy/app/routes/post.js new file mode 100644 index 0000000..6396738 --- /dev/null +++ b/tests/dummy/app/routes/post.js @@ -0,0 +1,7 @@ +import Ember from 'ember'; + +export default Ember.Route.extend({ + model(params) { + return this.store.queryRecord('post', { slug: params.slug }); + }, +}); diff --git a/tests/dummy/app/templates/post.hbs b/tests/dummy/app/templates/post.hbs new file mode 100644 index 0000000..bd34fe9 --- /dev/null +++ b/tests/dummy/app/templates/post.hbs @@ -0,0 +1,3 @@ +
+

{{model.title}}

+
diff --git a/tests/dummy/app/templates/posts.hbs b/tests/dummy/app/templates/posts.hbs index 8a483a8..8e57a4e 100644 --- a/tests/dummy/app/templates/posts.hbs +++ b/tests/dummy/app/templates/posts.hbs @@ -3,7 +3,7 @@
{{#each model as |post|}}
- {{post.title}} + {{#link-to 'post' post.slug}}{{post.title}}{{/link-to}}
{{/each}}
diff --git a/tests/dummy/mirage/config.js b/tests/dummy/mirage/config.js index 0a18579..751c51b 100644 --- a/tests/dummy/mirage/config.js +++ b/tests/dummy/mirage/config.js @@ -1,6 +1,9 @@ export default function() { this.get('/api/my-posts', 'post'); + this.get('/api/my-posts/:slug', (schema, request) => { + return schema.posts.findBy({ slug: request.params.slug }); + }); // These comments are here to help you get started. Feel free to delete them. From 0b4e8909062b815f6ec4e193185e760733ba7e0b Mon Sep 17 00:00:00 2001 From: Amiel Martin Date: Mon, 3 Jul 2017 09:46:10 -0700 Subject: [PATCH 3/4] Support using attributes from the snapshot with the .attr snapshot api --- addon/mixins/url-templates.js | 8 +++++++- .../acceptance/basic-url-template-test-test.js | 17 +++++++++++++++++ tests/dummy/app/adapters/post.js | 1 + tests/dummy/app/controllers/post.js | 14 ++++++++++++++ tests/dummy/app/models/post.js | 1 + tests/dummy/app/templates/post.hbs | 8 ++++++-- tests/dummy/mirage/config.js | 7 +++++++ 7 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 tests/dummy/app/controllers/post.js diff --git a/addon/mixins/url-templates.js b/addon/mixins/url-templates.js index 43f4abf..d147594 100644 --- a/addon/mixins/url-templates.js +++ b/addon/mixins/url-templates.js @@ -73,7 +73,13 @@ export default Ember.Mixin.create({ unknownProperty(key) { return (type, id, snapshot, query) => { if (query && query[key]) { return query[key]; } - if (snapshot) { return snapshot[key]; } + if (snapshot) { + if (snapshot[key]) { + return snapshot[key]; + } else if (snapshot.attr) { + return snapshot.attr(key); + } + } }; } } diff --git a/tests/acceptance/basic-url-template-test-test.js b/tests/acceptance/basic-url-template-test-test.js index 5c76244..2e15e98 100644 --- a/tests/acceptance/basic-url-template-test-test.js +++ b/tests/acceptance/basic-url-template-test-test.js @@ -26,3 +26,20 @@ test('it can use a specific template for one type of call (queryRecord)', functi }); }); +test('it can use an attribute from the snapshot when generating a url', function(assert) { + server.create('post', { + slug: 'my-first-post', + title: 'This is my first post', + }); + + visit('/posts/my-first-post'); + + click('#publish-post'); + + visit('/posts/my-first-post'); + + andThen(() => { + assert.equal(find('#publish-post').length, 0); + }); +}); + diff --git a/tests/dummy/app/adapters/post.js b/tests/dummy/app/adapters/post.js index 95ab74d..3e06cfc 100644 --- a/tests/dummy/app/adapters/post.js +++ b/tests/dummy/app/adapters/post.js @@ -3,5 +3,6 @@ import ApplicationAdapter from './application'; export default ApplicationAdapter.extend({ urlTemplate: "{+host}{/namespace}/my-posts", queryRecordUrlTemplate: "{+host}{/namespace}/my-posts/{slug}", + updateRecordUrlTemplate: "{+host}{/namespace}/my-posts/{slug}", namespace: 'api', }); diff --git a/tests/dummy/app/controllers/post.js b/tests/dummy/app/controllers/post.js new file mode 100644 index 0000000..c7a230e --- /dev/null +++ b/tests/dummy/app/controllers/post.js @@ -0,0 +1,14 @@ +import Ember from 'ember'; + +const { computed } = Ember; + +export default Ember.Controller.extend({ + post: computed.readOnly('model'), + + actions: { + publishPost(post) { + post.set('isPublished', true); + post.save(); + }, + }, +}); diff --git a/tests/dummy/app/models/post.js b/tests/dummy/app/models/post.js index b2f8143..ae42444 100644 --- a/tests/dummy/app/models/post.js +++ b/tests/dummy/app/models/post.js @@ -5,4 +5,5 @@ const { attr } = DS; export default DS.Model.extend({ slug: attr('string'), title: attr('string'), + isPublished: attr('boolean'), }); diff --git a/tests/dummy/app/templates/post.hbs b/tests/dummy/app/templates/post.hbs index bd34fe9..31910c2 100644 --- a/tests/dummy/app/templates/post.hbs +++ b/tests/dummy/app/templates/post.hbs @@ -1,3 +1,7 @@ -
-

{{model.title}}

+
+

{{post.title}}

+ + {{#unless post.isPublished}} + Publish Post + {{/unless}}
diff --git a/tests/dummy/mirage/config.js b/tests/dummy/mirage/config.js index 751c51b..a53379b 100644 --- a/tests/dummy/mirage/config.js +++ b/tests/dummy/mirage/config.js @@ -5,6 +5,13 @@ export default function() { return schema.posts.findBy({ slug: request.params.slug }); }); + this.patch('/api/my-posts/:slug', (schema, request) => { + let post = schema.posts.findBy({ slug: request.params.slug }); + + // We could actually update the post, but YAGNI? *shrug* + return post; + }); + // These comments are here to help you get started. Feel free to delete them. /* From ca3fdbf0aaa001e97f50b18e58ea913b86e6e8fe Mon Sep 17 00:00:00 2001 From: Amiel Martin Date: Mon, 3 Jul 2017 12:59:20 -0700 Subject: [PATCH 4/4] Support belongsTo relationship ids automatically --- addon/mixins/url-templates.js | 12 ++++++++++ ...est-test.js => basic-url-template-test.js} | 21 ++--------------- tests/acceptance/simple-relationships-test.js | 23 +++++++++++++++++++ tests/acceptance/using-attributes-test.js | 21 +++++++++++++++++ tests/dummy/app/adapters/comment.js | 6 +++++ tests/dummy/app/models/comment.js | 4 ++++ tests/dummy/app/models/post.js | 3 ++- tests/dummy/app/templates/post.hbs | 8 +++++++ tests/dummy/mirage/config.js | 2 ++ tests/dummy/mirage/factories/comment.js | 7 ++++++ tests/dummy/mirage/factories/post.js | 3 +++ tests/dummy/mirage/models/comment.js | 5 ++++ tests/dummy/mirage/models/post.js | 3 ++- 13 files changed, 97 insertions(+), 21 deletions(-) rename tests/acceptance/{basic-url-template-test-test.js => basic-url-template-test.js} (54%) create mode 100644 tests/acceptance/simple-relationships-test.js create mode 100644 tests/acceptance/using-attributes-test.js create mode 100644 tests/dummy/app/adapters/comment.js create mode 100644 tests/dummy/mirage/factories/comment.js create mode 100644 tests/dummy/mirage/models/comment.js diff --git a/addon/mixins/url-templates.js b/addon/mixins/url-templates.js index d147594..e0cfc29 100644 --- a/addon/mixins/url-templates.js +++ b/addon/mixins/url-templates.js @@ -3,6 +3,8 @@ import UriTemplate from 'uri-templates'; const { isArray, copy, typeOf } = Ember; +const ID_KEY_RE = /(_id|Id)$/; + export default Ember.Mixin.create({ mergedProperties: ['urlSegments'], buildURL(type, id, snapshot, requestType, query) { @@ -76,6 +78,8 @@ export default Ember.Mixin.create({ if (snapshot) { if (snapshot[key]) { return snapshot[key]; + } else if (isIdKey(key) && snapshot.belongsTo) { + return snapshot.belongsTo(relationshipNameForKey(key), { id: true }); } else if (snapshot.attr) { return snapshot.attr(key); } @@ -88,3 +92,11 @@ export default Ember.Mixin.create({ function isObject(object) { return typeof object === 'object'; } + +function relationshipNameForKey(key) { + return key.replace(ID_KEY_RE, ''); +} + +function isIdKey(key) { + return ID_KEY_RE.test(key); +} diff --git a/tests/acceptance/basic-url-template-test-test.js b/tests/acceptance/basic-url-template-test.js similarity index 54% rename from tests/acceptance/basic-url-template-test-test.js rename to tests/acceptance/basic-url-template-test.js index 2e15e98..3ca8e0d 100644 --- a/tests/acceptance/basic-url-template-test-test.js +++ b/tests/acceptance/basic-url-template-test.js @@ -1,7 +1,7 @@ import { test } from 'qunit'; import moduleForAcceptance from '../../tests/helpers/module-for-acceptance'; -moduleForAcceptance('Acceptance | basic url template test'); +moduleForAcceptance('Acceptance | basic url template'); test('it can use a simple custom url', function(assert) { server.createList('post', 5); @@ -9,7 +9,7 @@ test('it can use a simple custom url', function(assert) { visit('/posts'); andThen(() => { - assert.equal(find('#posts .post').length, '5'); + assert.equal(find('#posts .post').length, 5); }); }); @@ -26,20 +26,3 @@ test('it can use a specific template for one type of call (queryRecord)', functi }); }); -test('it can use an attribute from the snapshot when generating a url', function(assert) { - server.create('post', { - slug: 'my-first-post', - title: 'This is my first post', - }); - - visit('/posts/my-first-post'); - - click('#publish-post'); - - visit('/posts/my-first-post'); - - andThen(() => { - assert.equal(find('#publish-post').length, 0); - }); -}); - diff --git a/tests/acceptance/simple-relationships-test.js b/tests/acceptance/simple-relationships-test.js new file mode 100644 index 0000000..aa4d9a8 --- /dev/null +++ b/tests/acceptance/simple-relationships-test.js @@ -0,0 +1,23 @@ +import Ember from 'ember'; +import { test } from 'qunit'; +import moduleForAcceptance from '../../tests/helpers/module-for-acceptance'; + +const { get } = Ember; + +moduleForAcceptance('Acceptance | simple relationships', { + beforeEach() { + this.post = server.create('post'); + this.comment = server.create('comment', { + post: this.post, + }); + + visit(`/posts/${get(this.post, 'slug')}`); + }, +}); + +test('it can use a belongsTo id from the snapshot when generating a url', function(assert) { + andThen(() => { + assert.equal(find('#comments p').length, 1); + }); +}); + diff --git a/tests/acceptance/using-attributes-test.js b/tests/acceptance/using-attributes-test.js new file mode 100644 index 0000000..9cb8778 --- /dev/null +++ b/tests/acceptance/using-attributes-test.js @@ -0,0 +1,21 @@ +import Ember from 'ember'; +import { test } from 'qunit'; +import moduleForAcceptance from '../../tests/helpers/module-for-acceptance'; + +const { get } = Ember; + +moduleForAcceptance('Acceptance | using attributes', { + beforeEach() { + this.post = server.create('post'); + }, +}); + +test('it can use an attribute from the snapshot when generating a url', function(assert) { + visit(`/posts/${get(this.post, 'slug')}`); + click('#publish-post'); + visit(`/posts/${get(this.post, 'slug')}`); + + andThen(() => { + assert.equal(find('#publish-post').length, 0); + }); +}); diff --git a/tests/dummy/app/adapters/comment.js b/tests/dummy/app/adapters/comment.js new file mode 100644 index 0000000..8752c8a --- /dev/null +++ b/tests/dummy/app/adapters/comment.js @@ -0,0 +1,6 @@ +import ApplicationAdapter from './application'; + +export default ApplicationAdapter.extend({ + namespace: 'api', + urlTemplate: "{/namespace}/posts/{postId}/comments{/id}", +}); diff --git a/tests/dummy/app/models/comment.js b/tests/dummy/app/models/comment.js index 7b5f428..dca4a22 100644 --- a/tests/dummy/app/models/comment.js +++ b/tests/dummy/app/models/comment.js @@ -1,4 +1,8 @@ import DS from 'ember-data'; +const { attr, belongsTo } = DS; + export default DS.Model.extend({ + post: belongsTo(), + message: attr('string'), }); diff --git a/tests/dummy/app/models/post.js b/tests/dummy/app/models/post.js index ae42444..f73c6c3 100644 --- a/tests/dummy/app/models/post.js +++ b/tests/dummy/app/models/post.js @@ -1,9 +1,10 @@ import DS from 'ember-data'; -const { attr } = DS; +const { attr, hasMany } = DS; export default DS.Model.extend({ slug: attr('string'), title: attr('string'), isPublished: attr('boolean'), + comments: hasMany(), }); diff --git a/tests/dummy/app/templates/post.hbs b/tests/dummy/app/templates/post.hbs index 31910c2..faa69e6 100644 --- a/tests/dummy/app/templates/post.hbs +++ b/tests/dummy/app/templates/post.hbs @@ -4,4 +4,12 @@ {{#unless post.isPublished}} Publish Post {{/unless}} + +

Comments

+ +
+ {{#each post.comments as |comment|}} +

{{comment.message}}

+ {{/each}} +
diff --git a/tests/dummy/mirage/config.js b/tests/dummy/mirage/config.js index a53379b..847aa4c 100644 --- a/tests/dummy/mirage/config.js +++ b/tests/dummy/mirage/config.js @@ -12,6 +12,8 @@ export default function() { return post; }); + this.get('/api/posts/:post_id/comments/:id'); + // These comments are here to help you get started. Feel free to delete them. /* diff --git a/tests/dummy/mirage/factories/comment.js b/tests/dummy/mirage/factories/comment.js new file mode 100644 index 0000000..515179d --- /dev/null +++ b/tests/dummy/mirage/factories/comment.js @@ -0,0 +1,7 @@ +import { Factory, faker } from 'ember-cli-mirage'; + +export default Factory.extend({ + message() { + return faker.lorem.sentence(); + }, +}); diff --git a/tests/dummy/mirage/factories/post.js b/tests/dummy/mirage/factories/post.js index 883862f..50879c7 100644 --- a/tests/dummy/mirage/factories/post.js +++ b/tests/dummy/mirage/factories/post.js @@ -1,6 +1,9 @@ import { Factory, faker } from 'ember-cli-mirage'; export default Factory.extend({ + slug() { + return faker.helpers.slugify(this.title); + }, title(i) { return `Post #${i}: ${faker.lorem.words()}`; }, diff --git a/tests/dummy/mirage/models/comment.js b/tests/dummy/mirage/models/comment.js new file mode 100644 index 0000000..21ba27e --- /dev/null +++ b/tests/dummy/mirage/models/comment.js @@ -0,0 +1,5 @@ +import { Model, belongsTo } from 'ember-cli-mirage'; + +export default Model.extend({ + post: belongsTo(), +}); diff --git a/tests/dummy/mirage/models/post.js b/tests/dummy/mirage/models/post.js index 1486a72..214a261 100644 --- a/tests/dummy/mirage/models/post.js +++ b/tests/dummy/mirage/models/post.js @@ -1,4 +1,5 @@ -import { Model } from 'ember-cli-mirage'; +import { Model, hasMany } from 'ember-cli-mirage'; export default Model.extend({ + comments: hasMany(), });