From 52d545d8f9ae531ed325392e3551b8260c800ce3 Mon Sep 17 00:00:00 2001 From: selvaa89 Date: Thu, 19 Jul 2018 21:20:50 +0800 Subject: [PATCH 1/5] Adding acceptence test for belongs-to --- .../relationships/belongs-to-test.js | 217 ++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 tests/acceptance/relationships/belongs-to-test.js diff --git a/tests/acceptance/relationships/belongs-to-test.js b/tests/acceptance/relationships/belongs-to-test.js new file mode 100644 index 00000000000..e90785a9434 --- /dev/null +++ b/tests/acceptance/relationships/belongs-to-test.js @@ -0,0 +1,217 @@ +import { module } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import JSONAPIAdapter from 'ember-data/adapters/json-api'; +import Model from 'ember-data/model'; +import { render } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; +import { attr, hasMany, belongsTo } from '@ember-decorators/data'; +import JSONAPISerializer from 'ember-data/serializers/json-api'; +import Store from 'ember-data/store'; +import { resolve, reject } from 'rsvp'; +import { ServerError } from 'ember-data/adapters/errors'; +import { skipRecordData as test } from '../../helpers/test-in-debug'; + +class Person extends Model { + @attr name; + @hasMany('person', { async: true, inverse: 'parent' }) + children; + @belongsTo('person', { async: true, inverse: 'children' }) + parent; +} + +class TestAdapter extends JSONAPIAdapter { + setupPayloads(assert, arr) { + this.assert = assert; + this._payloads = arr; + } + + shouldBackgroundReload() { + return false; + } + + _nextPayload() { + let payload = this._payloads.shift(); + + if (payload === undefined) { + this.assert.ok(false, 'Too many adapter requests have been made!'); + return resolve({ data: null }); + } + + if (payload instanceof ServerError) { + return reject(payload); + } + return resolve(payload); + } + + // find by link + findHasMany() { + return this._nextPayload(); + } + + // find by data with coalesceFindRequests set to true + findMany() { + return this._nextPayload(); + } + + // find by partial data / individual records + findRecord() { + return this._nextPayload(); + } + + deleteRecord() { + resolve(); + } +} + +function makePeopleWithRelationshipData() { + let people = [ + { + type: 'person', + id: '1:no-children-or-parent', + attributes: { name: 'Chris Has No Children or Parent' }, + relationships: { + children: { data: [] }, + parent: { data: null }, + }, + }, + { + type: 'person', + id: '2:has-1-child-no-parent', + attributes: { + name: 'James has one child and no parent', + }, + relationships: { + children: { + data: [{ type: 'person', id: '3:has-2-children-and-parent' }], + }, + parent: { data: null }, + }, + }, + { + type: 'person', + id: '3:has-2-children-and-parent', + attributes: { + name: 'Kevin has two children and one parent', + }, + relationships: { + children: { + data: [ + { type: 'person', id: '4:has-parent-no-children' }, + { type: 'person', id: '5:has-parent-no-children' }, + ], + }, + parent: { + data: { + type: 'person', + id: '2:has-1-child-no-parent', + }, + }, + }, + }, + { + type: 'person', + id: '4:has-parent-no-children', + attributes: { + name: 'Selena has a parent', + }, + relationships: { + children: { + data: [], + }, + parent: { + data: { + type: 'person', + id: '3:has-2-children-and-parent', + }, + }, + }, + }, + { + type: 'person', + id: '5:has-parent-no-children', + attributes: { + name: 'Sedona has a parent', + }, + relationships: { + children: { + data: [], + }, + parent: { + data: { + type: 'person', + id: '3:has-2-children-and-parent', + }, + }, + }, + }, + ]; + + let peopleHash = {}; + people.forEach(person => { + peopleHash[person.id] = person; + }); + + return { + dict: peopleHash, + all: people, + }; +} + +module('async belongs-to rendering tests', function(hooks) { + let store; + let adapter; + setupRenderingTest(hooks); + + hooks.beforeEach(function() { + let { owner } = this; + owner.register('model:person', Person); + owner.register('adapter:application', TestAdapter); + owner.register('serializer:application', JSONAPISerializer); + owner.register('service:store', Store); + store = owner.lookup('service:store'); + adapter = store.adapterFor('application'); + }); + + module('for data-no-link scenarios', function() { + test('We can render an async belongs-to', async function(assert) { + let people = makePeopleWithRelationshipData(); + let sedona = store.push({ + data: people.dict['5:has-parent-no-children'], + }); + + adapter.setupPayloads(assert, [{ data: people.dict['3:has-2-children-and-parent'] }]); + + // render + this.set('sedona', sedona); + + await render(hbs` +

{{sedona.parent.name}}

+ `); + + assert.equal(this.element.textContent.trim(), 'Kevin has two children and one parent'); + }); + + // Failing test for https://github.com/emberjs/data/issues/5523 + test('We can delete async belongs-to', async function(assert) { + let people = makePeopleWithRelationshipData(); + let sedona = store.push({ + data: people.dict['5:has-parent-no-children'], + }); + + adapter.setupPayloads(assert, [{ data: people.dict['3:has-2-children-and-parent'] }]); + + // render + this.set('sedona', sedona); + await render(hbs` +

{{sedona.parent.name}}

+ `); + + await sedona + .belongsTo('parent') + .value() + .destroyRecord(); + assert.equal(this.element.textContent.trim(), ''); + }); + + }); +}); From fa9ec1cdc652a8b30a340a71a00a56858d71767b Mon Sep 17 00:00:00 2001 From: selvaa89 Date: Fri, 20 Jul 2018 00:23:00 +0800 Subject: [PATCH 2/5] Removed unnecessary newline --- tests/acceptance/relationships/belongs-to-test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/acceptance/relationships/belongs-to-test.js b/tests/acceptance/relationships/belongs-to-test.js index e90785a9434..7a0e5b86b2e 100644 --- a/tests/acceptance/relationships/belongs-to-test.js +++ b/tests/acceptance/relationships/belongs-to-test.js @@ -212,6 +212,5 @@ module('async belongs-to rendering tests', function(hooks) { .destroyRecord(); assert.equal(this.element.textContent.trim(), ''); }); - }); }); From 7358186c72ad21ff9221be803d98b067539457e6 Mon Sep 17 00:00:00 2001 From: selvaa89 Date: Fri, 20 Jul 2018 01:25:57 +0800 Subject: [PATCH 3/5] added failing test for failing test for #5517 --- .../relationships/belongs-to-test.js | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/acceptance/relationships/belongs-to-test.js b/tests/acceptance/relationships/belongs-to-test.js index 7a0e5b86b2e..9de4be8267f 100644 --- a/tests/acceptance/relationships/belongs-to-test.js +++ b/tests/acceptance/relationships/belongs-to-test.js @@ -212,5 +212,30 @@ module('async belongs-to rendering tests', function(hooks) { .destroyRecord(); assert.equal(this.element.textContent.trim(), ''); }); + + // failing test for https://github.com/emberjs/data/issues/5517 + test('Re-rendering an async belongsTo does not cause a new fetch', async function(assert) { + let people = makePeopleWithRelationshipData(); + let sedona = store.push({ + data: people.dict['5:has-parent-no-children'], + }); + + adapter.setupPayloads(assert, [{ data: people.dict['3:has-2-children-and-parent'] }]); + + // render + this.set('sedona', sedona); + + await render(hbs` +

{{sedona.parent.name}}

+ `); + + assert.equal(this.element.textContent.trim(), 'Kevin has two children and one parent'); + + this.set('sedona.parent', null); + assert.equal(this.element.textContent.trim(), ''); + + this.set('sedona.parent', { data: people.dict['3:has-2-children-and-parent'] }); + assert.equal(this.element.textContent.trim(), 'Kevin has two children and one parent'); + }); }); }); From b9c9155eeb3669e26cb92313471af89ee859240d Mon Sep 17 00:00:00 2001 From: Chris Thoburn Date: Mon, 13 Aug 2018 13:29:00 -0700 Subject: [PATCH 4/5] cleanup belongsTo tests --- .../relationships/belongs-to-test.js | 125 +++++++++++++++--- 1 file changed, 110 insertions(+), 15 deletions(-) diff --git a/tests/acceptance/relationships/belongs-to-test.js b/tests/acceptance/relationships/belongs-to-test.js index 9de4be8267f..323afd41372 100644 --- a/tests/acceptance/relationships/belongs-to-test.js +++ b/tests/acceptance/relationships/belongs-to-test.js @@ -2,7 +2,7 @@ import { module } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import JSONAPIAdapter from 'ember-data/adapters/json-api'; import Model from 'ember-data/model'; -import { render } from '@ember/test-helpers'; +import { render, settled } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; import { attr, hasMany, belongsTo } from '@ember-decorators/data'; import JSONAPISerializer from 'ember-data/serializers/json-api'; @@ -10,6 +10,7 @@ import Store from 'ember-data/store'; import { resolve, reject } from 'rsvp'; import { ServerError } from 'ember-data/adapters/errors'; import { skipRecordData as test } from '../../helpers/test-in-debug'; +import Ember from 'ember'; class Person extends Model { @attr name; @@ -59,7 +60,7 @@ class TestAdapter extends JSONAPIAdapter { } deleteRecord() { - resolve(); + return resolve({ data: null }); } } @@ -166,7 +167,9 @@ module('async belongs-to rendering tests', function(hooks) { let { owner } = this; owner.register('model:person', Person); owner.register('adapter:application', TestAdapter); - owner.register('serializer:application', JSONAPISerializer); + owner.register('serializer:application', JSONAPISerializer.extend({ + normalizeResponse(_, __, jsonApi) { return jsonApi } + })); owner.register('service:store', Store); store = owner.lookup('service:store'); adapter = store.adapterFor('application'); @@ -191,36 +194,44 @@ module('async belongs-to rendering tests', function(hooks) { assert.equal(this.element.textContent.trim(), 'Kevin has two children and one parent'); }); - // Failing test for https://github.com/emberjs/data/issues/5523 - test('We can delete async belongs-to', async function(assert) { + test('We can delete an async belongs-to', async function(assert) { let people = makePeopleWithRelationshipData(); let sedona = store.push({ data: people.dict['5:has-parent-no-children'], }); - adapter.setupPayloads(assert, [{ data: people.dict['3:has-2-children-and-parent'] }]); + adapter.setupPayloads(assert, [ + { data: people.dict['3:has-2-children-and-parent'] } + ]); // render this.set('sedona', sedona); + await render(hbs`

{{sedona.parent.name}}

`); - await sedona - .belongsTo('parent') - .value() - .destroyRecord(); - assert.equal(this.element.textContent.trim(), ''); + let parent = await sedona.get('parent'); + await parent.destroyRecord(); + + let newParent = await sedona.get('parent'); + + await settled(); + + assert.ok(newParent === null, 'We no longer have a parent'); + assert.equal(this.element.textContent.trim(), '', "We no longer render our parent's name because we no longer have a parent"); + }); - // failing test for https://github.com/emberjs/data/issues/5517 test('Re-rendering an async belongsTo does not cause a new fetch', async function(assert) { let people = makePeopleWithRelationshipData(); let sedona = store.push({ data: people.dict['5:has-parent-no-children'], }); - adapter.setupPayloads(assert, [{ data: people.dict['3:has-2-children-and-parent'] }]); + adapter.setupPayloads(assert, [ + { data: people.dict['3:has-2-children-and-parent'] } + ]); // render this.set('sedona', sedona); @@ -231,11 +242,95 @@ module('async belongs-to rendering tests', function(hooks) { assert.equal(this.element.textContent.trim(), 'Kevin has two children and one parent'); - this.set('sedona.parent', null); + this.set('sedona', null); assert.equal(this.element.textContent.trim(), ''); - this.set('sedona.parent', { data: people.dict['3:has-2-children-and-parent'] }); + this.set('sedona', sedona); assert.equal(this.element.textContent.trim(), 'Kevin has two children and one parent'); }); + + + test('Rendering an async belongs-to whose fetch fails does not trigger a new request', async function(assert) { + assert.expect(15); + let people = makePeopleWithRelationshipData(); + let sedona = store.push({ + data: people.dict['5:has-parent-no-children'], + }); + + adapter.setupPayloads(assert, [ + new ServerError([], 'hard error while finding 5:has-parent-no-children'), + ]); + + // render + this.set('sedona', sedona); + + let originalOnError = Ember.onerror; + Ember.onerror = function(e) { + assert.ok(true, 'Children promise did reject'); + assert.equal( + e.message, + 'hard error while finding 5:has-parent-no-children', + 'Rejection has the correct message' + ); + }; + + // needed for LTS 2.12 and 2.16 + Ember.Test.adapter.exception = e => { + assert.ok(true, 'Children promise did reject'); + assert.equal( + e.message, + 'hard error while finding 5:has-parent-no-children', + 'Rejection has the correct message' + ); + }; + + await render(hbs` +

{{sedona.parent.name}}

+ `); + + assert.equal(this.element.textContent.trim(), '', 'we have no parent'); + + let relationshipState = sedona.belongsTo('parent').belongsToRelationship; + + assert.equal(relationshipState.isAsync, true, 'The relationship is async'); + assert.equal(relationshipState.relationshipIsEmpty, false, 'The relationship is not empty'); + assert.equal(relationshipState.relationshipIsStale, true, 'The relationship is still stale'); + assert.equal( + relationshipState.allInverseRecordsAreLoaded, + false, + 'The relationship is missing some or all related resources' + ); + assert.equal( + relationshipState.hasAnyRelationshipData, + true, + 'The relationship knows which record it needs' + ); + assert.equal( + relationshipState.fetchPromise === null, + true, + 'The relationship has no fetchPromise' + ); + assert.equal( + relationshipState.hasFailedLoadAttempt === true, + true, + 'The relationship has attempted a load' + ); + assert.equal( + relationshipState.shouldForceReload === false, + true, + 'The relationship will not force a reload' + ); + assert.equal( + relationshipState._promiseProxy !== null, + true, + 'The relationship has a loadingPromise' + ); + assert.equal(!!relationshipState.link, false, 'The relationship does not have a link'); + assert.equal(relationshipState.shouldMakeRequest(), false, 'The relationship does not need to make a request'); + let result = await sedona.get('parent'); + assert.ok(result === null, 're-access is safe'); + + Ember.onerror = originalOnError; + }); }); }); From 52e479432828ddd786c74247fdee64273459efcd Mon Sep 17 00:00:00 2001 From: Chris Thoburn Date: Mon, 13 Aug 2018 13:40:25 -0700 Subject: [PATCH 5/5] prettier to the prettier gods --- .../relationships/belongs-to-test.js | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/tests/acceptance/relationships/belongs-to-test.js b/tests/acceptance/relationships/belongs-to-test.js index 323afd41372..afa9c6c8e86 100644 --- a/tests/acceptance/relationships/belongs-to-test.js +++ b/tests/acceptance/relationships/belongs-to-test.js @@ -13,7 +13,8 @@ import { skipRecordData as test } from '../../helpers/test-in-debug'; import Ember from 'ember'; class Person extends Model { - @attr name; + @attr + name; @hasMany('person', { async: true, inverse: 'parent' }) children; @belongsTo('person', { async: true, inverse: 'children' }) @@ -167,9 +168,14 @@ module('async belongs-to rendering tests', function(hooks) { let { owner } = this; owner.register('model:person', Person); owner.register('adapter:application', TestAdapter); - owner.register('serializer:application', JSONAPISerializer.extend({ - normalizeResponse(_, __, jsonApi) { return jsonApi } - })); + owner.register( + 'serializer:application', + JSONAPISerializer.extend({ + normalizeResponse(_, __, jsonApi) { + return jsonApi; + }, + }) + ); owner.register('service:store', Store); store = owner.lookup('service:store'); adapter = store.adapterFor('application'); @@ -200,9 +206,7 @@ module('async belongs-to rendering tests', function(hooks) { data: people.dict['5:has-parent-no-children'], }); - adapter.setupPayloads(assert, [ - { data: people.dict['3:has-2-children-and-parent'] } - ]); + adapter.setupPayloads(assert, [{ data: people.dict['3:has-2-children-and-parent'] }]); // render this.set('sedona', sedona); @@ -219,8 +223,11 @@ module('async belongs-to rendering tests', function(hooks) { await settled(); assert.ok(newParent === null, 'We no longer have a parent'); - assert.equal(this.element.textContent.trim(), '', "We no longer render our parent's name because we no longer have a parent"); - + assert.equal( + this.element.textContent.trim(), + '', + "We no longer render our parent's name because we no longer have a parent" + ); }); test('Re-rendering an async belongsTo does not cause a new fetch', async function(assert) { @@ -229,9 +236,7 @@ module('async belongs-to rendering tests', function(hooks) { data: people.dict['5:has-parent-no-children'], }); - adapter.setupPayloads(assert, [ - { data: people.dict['3:has-2-children-and-parent'] } - ]); + adapter.setupPayloads(assert, [{ data: people.dict['3:has-2-children-and-parent'] }]); // render this.set('sedona', sedona); @@ -249,7 +254,6 @@ module('async belongs-to rendering tests', function(hooks) { assert.equal(this.element.textContent.trim(), 'Kevin has two children and one parent'); }); - test('Rendering an async belongs-to whose fetch fails does not trigger a new request', async function(assert) { assert.expect(15); let people = makePeopleWithRelationshipData(); @@ -326,7 +330,11 @@ module('async belongs-to rendering tests', function(hooks) { 'The relationship has a loadingPromise' ); assert.equal(!!relationshipState.link, false, 'The relationship does not have a link'); - assert.equal(relationshipState.shouldMakeRequest(), false, 'The relationship does not need to make a request'); + assert.equal( + relationshipState.shouldMakeRequest(), + false, + 'The relationship does not need to make a request' + ); let result = await sedona.get('parent'); assert.ok(result === null, 're-access is safe');