diff --git a/packages/store/addon/-private/system/core-store.ts b/packages/store/addon/-private/system/core-store.ts index 93049b8e7c0..4e910d6e724 100644 --- a/packages/store/addon/-private/system/core-store.ts +++ b/packages/store/addon/-private/system/core-store.ts @@ -1626,6 +1626,7 @@ abstract class CoreStore extends Service { if (!resource) { return resolve([]); } + let adapter = this.adapterFor(relationshipMeta.type); let { relationshipIsStale, @@ -1639,6 +1640,7 @@ abstract class CoreStore extends Service { let shouldFindViaLink = resource.links && resource.links.related && + (typeof adapter.findHasMany === 'function' || typeof resource.data === 'undefined') && (shouldForceReload || hasDematerializedInverse || relationshipIsStale || diff --git a/packages/unpublished-adapter-encapsulation-test-app/tests/integration/belongs-to-test.js b/packages/unpublished-adapter-encapsulation-test-app/tests/integration/belongs-to-test.js new file mode 100644 index 00000000000..21588e0ca79 --- /dev/null +++ b/packages/unpublished-adapter-encapsulation-test-app/tests/integration/belongs-to-test.js @@ -0,0 +1,526 @@ +import { setupTest } from 'ember-qunit'; +import { module, test } from 'qunit'; +import testInDebug from '@ember-data/unpublished-test-infra/test-support/test-in-debug'; +import EmberObject from '@ember/object'; +import Store from 'adapter-encapsulation-test-app/services/store'; +import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; +import deepCopy from '@ember-data/unpublished-test-infra/test-support/deep-copy'; +import { resolve } from 'rsvp'; + +class MinimalSerializer extends EmberObject { + normalizeResponse(_, __, data) { + return data; + } + + serialize(snapshot) { + let json = { + data: { + id: snapshot.id, + type: snapshot.modelName, + attributes: snapshot.attributes(), + relationships: {}, + }, + }; + + snapshot.eachRelationship((key, relationship) => { + if (relationship.kind === 'belongsTo') { + this.serializeBelongsTo(snapshot, json, relationship); + } else if (relationship.kind === 'hasMany') { + this.serializeHasMany(snapshot, json, relationship); + } + }); + + if (Object.keys(json.data.relationships).length === 0) { + delete json.data.relationships; + } + + return json; + } + + // minimal implementation, not json-api compliant + serializeBelongsTo(snapshot, json, relationship) { + let key = relationship.key; + let belongsTo = snapshot.belongsTo(key); + + if (belongsTo) { + let value = { + data: { + id: belongsTo.id, + type: belongsTo.modelName, + }, + }; + json.data.relationships[key] = value; + } + } + + // minimal implementation, not json-api compliant + serializeHasMany(snapshot, json, relationship) { + let key = relationship.key; + let hasMany = snapshot.hasMany(key); + + if (hasMany && hasMany.length) { + let value = { + data: hasMany.map(snap => ({ + id: snap.id, + type: snap.modelName, + })), + }; + json.data.relationships[key] = value; + } + } +} + +class Post extends Model { + @attr + text; + + @hasMany('comments') + comments; +} + +class Comment extends Model { + @attr + text; + + @belongsTo('post') + post; +} + +module('integration/belongs-to - Belongs To Tests', function(hooks) { + setupTest(hooks); + + hooks.beforeEach(function() { + this.owner.register('service:store', Store); + this.owner.register('serializer:application', MinimalSerializer); + this.owner.register('model:post', Post); + this.owner.register('model:comment', Comment); + }); + + test('if a belongsTo relationship has a link but no data (findBelongsTo is defined)', async function(assert) { + let findRecordCalled = 0; + let findBelongsToCalled = 0; + + let initialRecord = { + data: { + id: '3', + type: 'comment', + attributes: { + text: 'You rock', + }, + relationships: { + post: { + links: { + related: 'https://example.com/api/post/2', + }, + }, + }, + }, + }; + + let expectedResult = { + data: { + id: '2', + type: 'post', + attributes: { + text: "I'm awesome", + }, + relationships: { + comments: { + data: [ + { + id: '3', + type: 'comment', + }, + ], + }, + }, + }, + }; + + let { owner } = this; + let store = owner.lookup('service:store'); + + // This code is a workaround for issue https://github.com/emberjs/data/issues/6758 + // expectedResult is unexpectedly mutated during store.findRecord + // if IDENTIFIERS is turned on + let expectedResultCopy = deepCopy(expectedResult); + + class TestFindBelongsToAdapter extends EmberObject { + findRecord() { + findRecordCalled++; + } + + findBelongsTo(passedStore, snapshot, url, relationship) { + findBelongsToCalled++; + + assert.equal(passedStore, store, 'instance of store is passed to findBelongsTo'); + + let expectedURL = initialRecord.data.relationships.post.links.related; + assert.equal(url, expectedURL, 'url is passed to findBelongsTo'); + assert.equal(relationship.meta.key, 'post', 'relationship is passed to findBelongsTo'); + + assert.equal(snapshot.modelName, 'comment', 'snapshot is passed to findBelongsTo with correct modelName'); + assert.equal(snapshot.id, '3', 'snapshot is passed to findBelongsTo with correct id'); + + return resolve(expectedResultCopy); + } + } + + owner.register('adapter:application', TestFindBelongsToAdapter); + + let comment = store.push(initialRecord); + + let post = await comment.get('post'); + + assert.equal(findRecordCalled, 0, 'findRecord is not called'); + assert.equal(findBelongsToCalled, 1, 'findBelongsTo is called once'); + assert.deepEqual(post.serialize(), expectedResult, 'findBelongsTo returns expected result'); + }); + + testInDebug('if a belongsTo relationship has a link but no data (findBelongsTo is undefined)', async function( + assert + ) { + let initialRecord = { + data: { + id: '3', + type: 'comment', + attributes: { + text: 'You rock', + }, + relationships: { + post: { + links: { + related: 'https://example.com/api/post/2', + }, + }, + }, + }, + }; + + let { owner } = this; + let store = owner.lookup('service:store'); + + class TestFindBelongsToAdapter extends EmberObject {} + + owner.register('adapter:application', TestFindBelongsToAdapter); + + let comment = store.push(initialRecord); + + await assert.expectAssertion(async function() { + await comment.get('post'); + }, /You tried to load a belongsTo relationship from a specified 'link' in the original payload but your adapter does not implement 'findBelongsTo'/); + }); + + test('if a belongsTo relationship has data but not a link (findBelongsTo is defined)', async function(assert) { + let findRecordCalled = 0; + let findBelongsToCalled = 0; + + let initialRecord = { + data: { + id: '3', + type: 'comment', + attributes: { + text: 'You rock', + }, + relationships: { + post: { + data: { + id: '2', + type: 'post', + }, + }, + }, + }, + }; + + let expectedResult = { + data: { + id: '2', + type: 'post', + attributes: { + text: "I'm awesome", + }, + relationships: { + comments: { + data: [ + { + id: '3', + type: 'comment', + }, + ], + }, + }, + }, + }; + + let { owner } = this; + let store = owner.lookup('service:store'); + + // This code is a workaround for issue https://github.com/emberjs/data/issues/6758 + // expectedResult is unexpectedly mutated during store.findRecord + // if IDENTIFIERS is turned on + let expectedResultCopy = deepCopy(expectedResult); + + class TestFindRecordAdapter extends EmberObject { + findRecord(passedStore, type, id, snapshot) { + findRecordCalled++; + + assert.equal(passedStore, store, 'instance of store is passed to findRecord'); + assert.equal(type, Post, 'model is passed to findRecord'); + assert.equal(id, '2', 'id is passed to findRecord'); + + assert.equal(snapshot.modelName, 'post', 'snapshot is passed to findRecord with correct modelName'); + assert.equal(snapshot.id, '2', 'snapshot is passed to findRecord with correct id'); + + return resolve(expectedResultCopy); + } + + findBelongsTo() { + findBelongsToCalled++; + } + } + + owner.register('adapter:application', TestFindRecordAdapter); + + let comment = store.push(initialRecord); + + let post = await comment.get('post'); + + assert.equal(findRecordCalled, 1, 'findRecord is called once'); + assert.equal(findBelongsToCalled, 0, 'findBelongsTo is not called'); + assert.deepEqual(post.serialize(), expectedResult, 'findRecord returns expected result'); + }); + + test('if a belongsTo relationship has data but not a link (findBelongsTo is not defined)', async function(assert) { + let findRecordCalled = 0; + + let initialRecord = { + data: { + id: '3', + type: 'comment', + attributes: { + text: 'You rock', + }, + relationships: { + post: { + data: { + id: '2', + type: 'post', + }, + }, + }, + }, + }; + + let expectedResult = { + data: { + id: '2', + type: 'post', + attributes: { + text: "I'm awesome", + }, + relationships: { + comments: { + data: [ + { + id: '3', + type: 'comment', + }, + ], + }, + }, + }, + }; + + let { owner } = this; + let store = owner.lookup('service:store'); + + // This code is a workaround for issue https://github.com/emberjs/data/issues/6758 + // expectedResult is unexpectedly mutated during store.findRecord + // if IDENTIFIERS is turned on + let expectedResultCopy = deepCopy(expectedResult); + + class TestFindRecordAdapter extends EmberObject { + findRecord(passedStore, type, id, snapshot) { + findRecordCalled++; + + assert.equal(passedStore, store, 'instance of store is passed to findRecord'); + assert.equal(type, Post, 'model is passed to findRecord'); + assert.equal(id, '2', 'id is passed to findRecord'); + + assert.equal(snapshot.modelName, 'post', 'snapshot is passed to findRecord with correct modelName'); + assert.equal(snapshot.id, '2', 'snapshot is passed to findRecord with correct id'); + + return resolve(expectedResultCopy); + } + } + + owner.register('adapter:application', TestFindRecordAdapter); + + let comment = store.push(initialRecord); + + let post = await comment.get('post'); + + assert.equal(findRecordCalled, 1, 'findRecord is called once'); + assert.deepEqual(post.serialize(), expectedResult, 'findRecord returns expected result'); + }); + + test('if a belongsTo relationship has a link and data (findBelongsTo is defined)', async function(assert) { + let findRecordCalled = 0; + let findBelongsToCalled = 0; + + let initialRecord = { + data: { + id: '3', + type: 'comment', + attributes: { + text: 'You rock', + }, + relationships: { + post: { + data: { + id: '2', + type: 'post', + }, + links: { + related: 'https://example.com/api/post/2', + }, + }, + }, + }, + }; + + let expectedResult = { + data: { + id: '2', + type: 'post', + attributes: { + text: "I'm awesome", + }, + relationships: { + comments: { + data: [ + { + id: '3', + type: 'comment', + }, + ], + }, + }, + }, + }; + + let { owner } = this; + let store = owner.lookup('service:store'); + + // This code is a workaround for issue https://github.com/emberjs/data/issues/6758 + // expectedResult is unexpectedly mutated during store.findRecord + // if IDENTIFIERS is turned on + let expectedResultCopy = deepCopy(expectedResult); + + class TestFindBelongsToAdapter extends EmberObject { + findRecord() { + findRecordCalled++; + } + + findBelongsTo(passedStore, snapshot, url, relationship) { + findBelongsToCalled++; + + assert.equal(passedStore, store, 'instance of store is passed to findBelongsTo'); + + let expectedURL = initialRecord.data.relationships.post.links.related; + assert.equal(url, expectedURL, 'url is passed to findBelongsTo'); + assert.equal(relationship.meta.key, 'post', 'relationship is passed to findBelongsTo'); + + assert.equal(snapshot.modelName, 'comment', 'snapshot is passed to findBelongsTo with correct modelName'); + assert.equal(snapshot.id, '3', 'snapshot is passed to findBelongsTo with correct id'); + + return resolve(expectedResultCopy); + } + } + + owner.register('adapter:application', TestFindBelongsToAdapter); + + let comment = store.push(initialRecord); + + let post = await comment.get('post'); + + assert.equal(findRecordCalled, 0, 'findRecord is not called'); + assert.equal(findBelongsToCalled, 1, 'findBelongsTo is called once'); + assert.deepEqual(post.serialize(), expectedResult, 'findBelongsTo returns expected result'); + }); + + test('if a belongsTo relationship has link and data (findBelongsTo is not defined)', async function(assert) { + let findRecordCalled = 0; + + let initialRecord = { + data: { + id: '3', + type: 'comment', + attributes: { + text: 'You rock', + }, + relationships: { + post: { + data: { + id: '2', + type: 'post', + }, + }, + }, + }, + }; + + let expectedResult = { + data: { + id: '2', + type: 'post', + attributes: { + text: "I'm awesome", + }, + relationships: { + comments: { + data: [ + { + id: '3', + type: 'comment', + }, + ], + }, + }, + }, + }; + + let { owner } = this; + let store = owner.lookup('service:store'); + + // This code is a workaround for issue https://github.com/emberjs/data/issues/6758 + // expectedResult is unexpectedly mutated during store.findRecord + // if IDENTIFIERS is turned on + let expectedResultCopy = deepCopy(expectedResult); + + class TestFindRecordAdapter extends EmberObject { + findRecord(passedStore, type, id, snapshot) { + findRecordCalled++; + + assert.equal(passedStore, store, 'instance of store is passed to findRecord'); + assert.equal(type, Post, 'model is passed to findRecord'); + assert.equal(id, '2', 'id is passed to findRecord'); + + assert.equal(snapshot.modelName, 'post', 'snapshot is passed to findRecord with correct modelName'); + assert.equal(snapshot.id, '2', 'snapshot is passed to findRecord with correct id'); + + return resolve(expectedResultCopy); + } + } + + owner.register('adapter:application', TestFindRecordAdapter); + + let comment = store.push(initialRecord); + + let post = await comment.get('post'); + + assert.equal(findRecordCalled, 1, 'findRecord is called once'); + assert.deepEqual(post.serialize(), expectedResult, 'findRecord returns expected result'); + }); +}); diff --git a/packages/unpublished-adapter-encapsulation-test-app/tests/integration/coalescing-test.js b/packages/unpublished-adapter-encapsulation-test-app/tests/integration/coalescing-test.js index c0f1064bc12..28951ee17e5 100644 --- a/packages/unpublished-adapter-encapsulation-test-app/tests/integration/coalescing-test.js +++ b/packages/unpublished-adapter-encapsulation-test-app/tests/integration/coalescing-test.js @@ -3,6 +3,7 @@ import { module, test } from 'qunit'; import EmberObject from '@ember/object'; import Store from 'adapter-encapsulation-test-app/services/store'; import Model, { attr } from '@ember-data/model'; +import deepCopy from '@ember-data/unpublished-test-infra/test-support/deep-copy'; import { resolve, all } from 'rsvp'; class MinimalSerializer extends EmberObject { @@ -70,7 +71,7 @@ module('integration/coalescing - Coalescing Tests', function(hooks) { // This code is a workaround for issue https://github.com/emberjs/data/issues/6758 // expectedResult is unexpectedly mutated during store.findRecord // if IDENTIFIERS is turned on - let expectedResultsCopy = JSON.parse(JSON.stringify(expectedResults)); + let expectedResultsCopy = deepCopy(expectedResults); class TestFindRecordAdapter extends EmberObject { coalesceFindRequests = true; @@ -132,7 +133,7 @@ module('integration/coalescing - Coalescing Tests', function(hooks) { // This code is a workaround for issue https://github.com/emberjs/data/issues/6758 // expectedResult is unexpectedly mutated during store.findRecord // if IDENTIFIERS is turned on - let expectedResultsCopy = JSON.parse(JSON.stringify(expectedResults)); + let expectedResultsCopy = deepCopy(expectedResults); class TestFindRecordAdapter extends EmberObject { coalesceFindRequests = true; @@ -212,7 +213,7 @@ module('integration/coalescing - Coalescing Tests', function(hooks) { // This code is a workaround for issue https://github.com/emberjs/data/issues/6758 // expectedResult is unexpectedly mutated during store.findRecord // if IDENTIFIERS is turned on - let expectedResultsCopy = JSON.parse(JSON.stringify(expectedResults)); + let expectedResultsCopy = deepCopy(expectedResults); class TestFindRecordAdapter extends EmberObject { coalesceFindRequests = false; diff --git a/packages/unpublished-adapter-encapsulation-test-app/tests/integration/has-many-test.js b/packages/unpublished-adapter-encapsulation-test-app/tests/integration/has-many-test.js new file mode 100644 index 00000000000..db44f4af289 --- /dev/null +++ b/packages/unpublished-adapter-encapsulation-test-app/tests/integration/has-many-test.js @@ -0,0 +1,777 @@ +import { setupTest } from 'ember-qunit'; +import { module, test } from 'qunit'; +import testInDebug from '@ember-data/unpublished-test-infra/test-support/test-in-debug'; +import EmberObject from '@ember/object'; +import Store from 'adapter-encapsulation-test-app/services/store'; +import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; +import deepCopy from '@ember-data/unpublished-test-infra/test-support/deep-copy'; +import { resolve } from 'rsvp'; + +class MinimalSerializer extends EmberObject { + normalizeResponse(_, __, data) { + return data; + } + + serialize(snapshot) { + let json = { + data: { + id: snapshot.id, + type: snapshot.modelName, + attributes: snapshot.attributes(), + relationships: {}, + }, + }; + + snapshot.eachRelationship((key, relationship) => { + if (relationship.kind === 'belongsTo') { + this.serializeBelongsTo(snapshot, json, relationship); + } else if (relationship.kind === 'hasMany') { + this.serializeHasMany(snapshot, json, relationship); + } + }); + + if (Object.keys(json.data.relationships).length === 0) { + delete json.data.relationships; + } + + return json; + } + + // minimal implementation, not json-api compliant + serializeBelongsTo(snapshot, json, relationship) { + let key = relationship.key; + let belongsTo = snapshot.belongsTo(key); + + if (belongsTo) { + let value = { + data: { + id: belongsTo.id, + type: belongsTo.modelName, + }, + }; + json.data.relationships[key] = value; + } + } + + // minimal implementation, not json-api compliant + serializeHasMany(snapshot, json, relationship) { + let key = relationship.key; + let hasMany = snapshot.hasMany(key); + + if (hasMany && hasMany.length) { + let value = { + data: hasMany.map(snap => ({ + id: snap.id, + type: snap.modelName, + })), + }; + json.data.relationships[key] = value; + } + } +} + +class Post extends Model { + @attr + text; + + @hasMany('comments') + comments; +} + +class Comment extends Model { + @attr + text; + + @belongsTo('post') + post; +} + +let expectedResult = { + data: [ + { + id: '3', + type: 'comment', + attributes: { + text: 'You rock', + }, + relationships: { + post: { + data: { + id: '2', + type: 'post', + }, + }, + }, + }, + { + id: '4', + type: 'comment', + attributes: { + text: 'You rock too', + }, + relationships: { + post: { + data: { + id: '2', + type: 'post', + }, + }, + }, + }, + ], +}; + +module('integration/has-many - Has Many Tests', function(hooks) { + setupTest(hooks); + + hooks.beforeEach(function() { + this.owner.register('service:store', Store); + this.owner.register('serializer:application', MinimalSerializer); + this.owner.register('model:post', Post); + this.owner.register('model:comment', Comment); + }); + + test('if a hasMany relationship has a link but no data (findHasMany is defined)', async function(assert) { + let findRecordCalled = 0; + let findManyCalled = 0; + let findHasManyCalled = 0; + + let initialRecord = { + data: { + id: '2', + type: 'post', + attributes: { + text: "I'm awesome", + }, + relationships: { + comments: { + links: { + related: 'https://example.com/api/post/2/comments', + }, + }, + }, + }, + }; + + let { owner } = this; + let store = owner.lookup('service:store'); + + // This code is a workaround for issue https://github.com/emberjs/data/issues/6758 + // expectedResult is unexpectedly mutated during store.findRecord + // if IDENTIFIERS is turned on + let expectedResultCopy = deepCopy(expectedResult); + + class TestFindHasManyAdapter extends EmberObject { + findRecord() { + findRecordCalled++; + } + + findMany() { + findManyCalled++; + } + + findHasMany(passedStore, snapshot, url, relationship) { + findHasManyCalled++; + + assert.equal(passedStore, store, 'instance of store is passed to findHasMany'); + + let expectedURL = initialRecord.data.relationships.comments.links.related; + assert.equal(url, expectedURL, 'url is passed to findHasMany'); + assert.equal(relationship.meta.key, 'comments', 'relationship is passed to findHasMany'); + + assert.equal(snapshot.modelName, 'post', 'snapshot is passed to findHasMany with correct modelName'); + assert.equal(snapshot.id, '2', 'snapshot is passed to findHasMany with correct id'); + + return resolve(expectedResultCopy); + } + } + + owner.register('adapter:application', TestFindHasManyAdapter); + + let post = store.push(initialRecord); + + let comments = await post.get('comments'); + let serializedComments = { + data: comments.toArray().map(comment => comment.serialize().data), + }; + + assert.equal(findRecordCalled, 0, 'findRecord is not called'); + assert.equal(findManyCalled, 0, 'findMany is not called'); + assert.equal(findHasManyCalled, 1, 'findHasMany is called once'); + assert.deepEqual(serializedComments, expectedResult, 'findHasMany returns expected result'); + }); + + testInDebug('if a hasMany relationship has a link but no data (findHasMany is undefined)', async function(assert) { + let initialRecord = { + data: { + id: '2', + type: 'post', + attributes: { + text: "I'm awesome", + }, + relationships: { + comments: { + links: { + related: 'https://example.com/api/post/2/comments', + }, + }, + }, + }, + }; + + let { owner } = this; + let store = owner.lookup('service:store'); + + class TestFindHasManyAdapter extends EmberObject {} + + owner.register('adapter:application', TestFindHasManyAdapter); + + let post = store.push(initialRecord); + + await assert.expectAssertion(async function() { + await post.get('comments'); + }, /You tried to load a hasMany relationship from a specified 'link' in the original payload but your adapter does not implement 'findHasMany'/); + }); + + test('if a hasMany relationship has data but not a link (coalescing is off, findHasMany is defined)', async function(assert) { + let findRecordCalled = 0; + let findManyCalled = 0; + let findHasManyCalled = 0; + + let initialRecord = { + data: { + id: '2', + type: 'post', + attributes: { + text: "I'm awesome", + }, + relationships: { + comments: { + data: [ + { + id: '3', + type: 'comment', + }, + { + id: '4', + type: 'comment', + }, + ], + }, + }, + }, + }; + + let { owner } = this; + let store = owner.lookup('service:store'); + + // This code is a workaround for issue https://github.com/emberjs/data/issues/6758 + // expectedResult is unexpectedly mutated during store.findRecord + // if IDENTIFIERS is turned on + let expectedResultCopy = deepCopy(expectedResult); + + class TestFindRecordAdapter extends EmberObject { + coalesceFindRequests = false; + + findRecord(passedStore, type, id, snapshot) { + let index = findRecordCalled++; + let expectedId = initialRecord.data.relationships.comments.data[index].id; + + assert.equal(passedStore, store, 'instance of store is passed to findRecord'); + assert.equal(type, Comment, 'model is passed to findRecord'); + assert.equal(id, expectedId, 'id is passed to findRecord'); + + assert.equal(snapshot.modelName, 'comment', 'snapshot is passed to findRecord with correct modelName'); + assert.equal(snapshot.id, expectedId, 'snapshot is passed to findRecord with correct id'); + + return resolve({ data: expectedResultCopy.data[index] }); + } + + findMany() { + findManyCalled++; + } + + findHasMany() { + findHasManyCalled++; + } + } + + owner.register('adapter:application', TestFindRecordAdapter); + + let post = store.push(initialRecord); + let comments = await post.get('comments'); + let serializedComments = { + data: comments.toArray().map(comment => comment.serialize().data), + }; + + assert.equal(findRecordCalled, 2, 'findRecord is called twice'); + assert.equal(findManyCalled, 0, 'findMany is not called'); + assert.equal(findHasManyCalled, 0, 'findHasMany is not called'); + assert.deepEqual(serializedComments, expectedResult, 'get returns expected result'); + }); + + test('if a hasMany relationship has data but not a link (coalescing is off, findHasMany is not defined)', async function(assert) { + let findRecordCalled = 0; + let findManyCalled = 0; + + let initialRecord = { + data: { + id: '2', + type: 'post', + attributes: { + text: "I'm awesome", + }, + relationships: { + comments: { + data: [ + { + id: '3', + type: 'comment', + }, + { + id: '4', + type: 'comment', + }, + ], + }, + }, + }, + }; + + let { owner } = this; + let store = owner.lookup('service:store'); + + // This code is a workaround for issue https://github.com/emberjs/data/issues/6758 + // expectedResult is unexpectedly mutated during store.findRecord + // if IDENTIFIERS is turned on + let expectedResultCopy = deepCopy(expectedResult); + + class TestFindRecordAdapter extends EmberObject { + coalesceFindRequests = false; + + findRecord(passedStore, type, id, snapshot) { + let index = findRecordCalled++; + let expectedId = initialRecord.data.relationships.comments.data[index].id; + + assert.equal(passedStore, store, 'instance of store is passed to findRecord'); + assert.equal(type, Comment, 'model is passed to findRecord'); + assert.equal(id, expectedId, 'id is passed to findRecord'); + + assert.equal(snapshot.modelName, 'comment', 'snapshot is passed to findRecord with correct modelName'); + assert.equal(snapshot.id, expectedId, 'snapshot is passed to findRecord with correct id'); + + return resolve({ data: expectedResultCopy.data[index] }); + } + + findMany() { + findManyCalled++; + } + } + + owner.register('adapter:application', TestFindRecordAdapter); + + let post = store.push(initialRecord); + let comments = await post.get('comments'); + let serializedComments = { + data: comments.toArray().map(comment => comment.serialize().data), + }; + + assert.equal(findRecordCalled, 2, 'findRecord is called twice'); + assert.equal(findManyCalled, 0, 'findMany is not called'); + assert.deepEqual(serializedComments, expectedResult, 'get returns expected result'); + }); + + test('if a hasMany relationship has data but not a link (coalescing is on, findHasMany is defined)', async function(assert) { + let findRecordCalled = 0; + let findManyCalled = 0; + let findHasManyCalled = 0; + + let initialRecord = { + data: { + id: '2', + type: 'post', + attributes: { + text: "I'm awesome", + }, + relationships: { + comments: { + data: [ + { + id: '3', + type: 'comment', + }, + { + id: '4', + type: 'comment', + }, + ], + }, + }, + }, + }; + + let { owner } = this; + let store = owner.lookup('service:store'); + + // This code is a workaround for issue https://github.com/emberjs/data/issues/6758 + // expectedResult is unexpectedly mutated during store.findRecord + // if IDENTIFIERS is turned on + let expectedResultCopy = deepCopy(expectedResult); + + class TestFindManyAdapter extends EmberObject { + coalesceFindRequests = true; + + findRecord() { + findRecordCalled++; + } + + findHasMany() { + findHasManyCalled++; + } + + groupRecordsForFindMany(store, snapshots) { + return [snapshots]; + } + + findMany(passedStore, type, ids, snapshots) { + findManyCalled++; + + assert.equal(passedStore, store, 'instance of store is passed to findMany'); + assert.equal(type, Comment, 'model is passed to findMany'); + + let expectedIds = expectedResultCopy.data.map(record => record.id); + assert.deepEqual(ids, expectedIds, 'ids are passed to findMany'); + + snapshots.forEach((snapshot, index) => { + assert.equal(snapshot.modelName, 'comment', 'snapshot is passed to findMany with correct modelName'); + assert.equal(snapshot.id, expectedIds[index], 'snapshot is passed to findMany with correct id'); + }); + + return resolve(expectedResultCopy); + } + } + + owner.register('adapter:application', TestFindManyAdapter); + + let post = store.push(initialRecord); + let comments = await post.get('comments'); + let serializedComments = { + data: comments.toArray().map(comment => comment.serialize().data), + }; + + assert.equal(findRecordCalled, 0, 'findRecord is not called'); + assert.equal(findManyCalled, 1, 'findMany is called once'); + assert.equal(findHasManyCalled, 0, 'findHasMany is not called'); + assert.deepEqual(serializedComments, expectedResult, 'get returns expected result'); + }); + + test('if a hasMany relationship has data but not a link (coalescing is on, findHasMany is not defined)', async function(assert) { + let findRecordCalled = 0; + let findManyCalled = 0; + + let initialRecord = { + data: { + id: '2', + type: 'post', + attributes: { + text: "I'm awesome", + }, + relationships: { + comments: { + data: [ + { + id: '3', + type: 'comment', + }, + { + id: '4', + type: 'comment', + }, + ], + }, + }, + }, + }; + + let { owner } = this; + let store = owner.lookup('service:store'); + + // This code is a workaround for issue https://github.com/emberjs/data/issues/6758 + // expectedResult is unexpectedly mutated during store.findRecord + // if IDENTIFIERS is turned on + let expectedResultCopy = deepCopy(expectedResult); + + class TestFindManyAdapter extends EmberObject { + coalesceFindRequests = true; + + findRecord() { + findRecordCalled++; + } + + groupRecordsForFindMany(store, snapshots) { + return [snapshots]; + } + + findMany(passedStore, type, ids, snapshots) { + findManyCalled++; + + assert.equal(passedStore, store, 'instance of store is passed to findMany'); + assert.equal(type, Comment, 'model is passed to findMany'); + + let expectedIds = expectedResultCopy.data.map(record => record.id); + assert.deepEqual(ids, expectedIds, 'ids are passed to findMany'); + + snapshots.forEach((snapshot, index) => { + assert.equal(snapshot.modelName, 'comment', 'snapshot is passed to findMany with correct modelName'); + assert.equal(snapshot.id, expectedIds[index], 'snapshot is passed to findMany with correct id'); + }); + + return resolve(expectedResultCopy); + } + } + + owner.register('adapter:application', TestFindManyAdapter); + + let post = store.push(initialRecord); + let comments = await post.get('comments'); + let serializedComments = { + data: comments.toArray().map(comment => comment.serialize().data), + }; + + assert.equal(findRecordCalled, 0, 'findRecord is not called'); + assert.equal(findManyCalled, 1, 'findMany is called once'); + assert.deepEqual(serializedComments, expectedResult, 'get returns expected result'); + }); + + test('if a hasMany relationship has link and data (findHasMany is defined)', async function(assert) { + let findRecordCalled = 0; + let findManyCalled = 0; + let findHasManyCalled = 0; + + let initialRecord = { + data: { + id: '2', + type: 'post', + attributes: { + text: "I'm awesome", + }, + relationships: { + comments: { + links: { + related: 'https://example.com/api/post/2/comments', + }, + data: [ + { + id: '3', + type: 'comment', + }, + { + id: '4', + type: 'comment', + }, + ], + }, + }, + }, + }; + + let { owner } = this; + let store = owner.lookup('service:store'); + + // This code is a workaround for issue https://github.com/emberjs/data/issues/6758 + // expectedResult is unexpectedly mutated during store.findRecord + // if IDENTIFIERS is turned on + let expectedResultCopy = deepCopy(expectedResult); + + class TestFindHasManyAdapter extends EmberObject { + findRecord() { + findRecordCalled++; + } + + findMany() { + findManyCalled++; + } + + findHasMany(passedStore, snapshot, url, relationship) { + findHasManyCalled++; + + assert.equal(passedStore, store, 'instance of store is passed to findHasMany'); + + let expectedURL = initialRecord.data.relationships.comments.links.related; + assert.equal(url, expectedURL, 'url is passed to findHasMany'); + assert.equal(relationship.meta.key, 'comments', 'relationship is passed to findHasMany'); + + assert.equal(snapshot.modelName, 'post', 'snapshot is passed to findHasMany with correct modelName'); + assert.equal(snapshot.id, '2', 'snapshot is passed to findHasMany with correct id'); + + return resolve(expectedResultCopy); + } + } + + owner.register('adapter:application', TestFindHasManyAdapter); + + let post = store.push(initialRecord); + + let comments = await post.get('comments'); + let serializedComments = { + data: comments.toArray().map(comment => comment.serialize().data), + }; + + assert.equal(findRecordCalled, 0, 'findRecord is not called'); + assert.equal(findManyCalled, 0, 'findMany is not called'); + assert.equal(findHasManyCalled, 1, 'findHasMany is called once'); + assert.deepEqual(serializedComments, expectedResult, 'findHasMany returns expected result'); + }); + + test('if a hasMany relationship has link and data (coalescing is on, findHasMany is not defined)', async function(assert) { + let findRecordCalled = 0; + let findManyCalled = 0; + + let initialRecord = { + data: { + id: '2', + type: 'post', + attributes: { + text: "I'm awesome", + }, + relationships: { + comments: { + links: { + related: 'https://example.com/api/post/2/comments', + }, + data: [ + { + id: '3', + type: 'comment', + }, + { + id: '4', + type: 'comment', + }, + ], + }, + }, + }, + }; + + let { owner } = this; + let store = owner.lookup('service:store'); + + // This code is a workaround for issue https://github.com/emberjs/data/issues/6758 + // expectedResult is unexpectedly mutated during store.findRecord + // if IDENTIFIERS is turned on + let expectedResultCopy = deepCopy(expectedResult); + + class TestFindManyAdapter extends EmberObject { + coalesceFindRequests = true; + + findRecord() { + findRecordCalled++; + } + + groupRecordsForFindMany(store, snapshots) { + return [snapshots]; + } + + findMany(passedStore, type, ids, snapshots) { + findManyCalled++; + + assert.equal(passedStore, store, 'instance of store is passed to findMany'); + assert.equal(type, Comment, 'model is passed to findMany'); + + let expectedIds = expectedResultCopy.data.map(record => record.id); + assert.deepEqual(ids, expectedIds, 'ids are passed to findMany'); + + snapshots.forEach((snapshot, index) => { + assert.equal(snapshot.modelName, 'comment', 'snapshot is passed to findMany with correct modelName'); + assert.equal(snapshot.id, expectedIds[index], 'snapshot is passed to findMany with correct id'); + }); + + return resolve(expectedResultCopy); + } + } + + owner.register('adapter:application', TestFindManyAdapter); + + let post = store.push(initialRecord); + let comments = await post.get('comments'); + let serializedComments = { + data: comments.toArray().map(comment => comment.serialize().data), + }; + + assert.equal(findRecordCalled, 0, 'findRecord is not called'); + assert.equal(findManyCalled, 1, 'findMany is called once'); + assert.deepEqual(serializedComments, expectedResult, 'get returns expected result'); + }); + + test('if a hasMany relationship has link and data (coalescing is off, findHasMany is not defined)', async function(assert) { + let findRecordCalled = 0; + let findManyCalled = 0; + + let initialRecord = { + data: { + id: '2', + type: 'post', + attributes: { + text: "I'm awesome", + }, + relationships: { + comments: { + data: [ + { + id: '3', + type: 'comment', + }, + { + id: '4', + type: 'comment', + }, + ], + }, + }, + }, + }; + + let { owner } = this; + let store = owner.lookup('service:store'); + + // This code is a workaround for issue https://github.com/emberjs/data/issues/6758 + // expectedResult is unexpectedly mutated during store.findRecord + // if IDENTIFIERS is turned on + let expectedResultCopy = deepCopy(expectedResult); + + class TestFindRecordAdapter extends EmberObject { + coalesceFindRequests = false; + + findRecord(passedStore, type, id, snapshot) { + let index = findRecordCalled++; + let expectedId = initialRecord.data.relationships.comments.data[index].id; + + assert.equal(passedStore, store, 'instance of store is passed to findRecord'); + assert.equal(type, Comment, 'model is passed to findRecord'); + assert.equal(id, expectedId, 'id is passed to findRecord'); + + assert.equal(snapshot.modelName, 'comment', 'snapshot is passed to findRecord with correct modelName'); + assert.equal(snapshot.id, expectedId, 'snapshot is passed to findRecord with correct id'); + + return resolve({ data: expectedResultCopy.data[index] }); + } + + findMany() { + findManyCalled++; + } + } + + owner.register('adapter:application', TestFindRecordAdapter); + + let post = store.push(initialRecord); + let comments = await post.get('comments'); + let serializedComments = { + data: comments.toArray().map(comment => comment.serialize().data), + }; + + assert.equal(findRecordCalled, 2, 'findRecord is called twice'); + assert.equal(findManyCalled, 0, 'findMany is not called'); + assert.deepEqual(serializedComments, expectedResult, 'get returns expected result'); + }); +}); diff --git a/packages/unpublished-adapter-encapsulation-test-app/tests/test-helper.js b/packages/unpublished-adapter-encapsulation-test-app/tests/test-helper.js index 0382a848dd0..00c05dc1857 100644 --- a/packages/unpublished-adapter-encapsulation-test-app/tests/test-helper.js +++ b/packages/unpublished-adapter-encapsulation-test-app/tests/test-helper.js @@ -2,6 +2,9 @@ import Application from '../app'; import config from '../config/environment'; import { setApplication } from '@ember/test-helpers'; import { start } from 'ember-qunit'; +import configureAsserts from '@ember-data/unpublished-test-infra/test-support/qunit-asserts'; + +configureAsserts(); setApplication(Application.create(config.APP));