From e0054116f077d928c516b51507804dae3ed374f8 Mon Sep 17 00:00:00 2001 From: Derek Wickern Date: Wed, 7 Feb 2024 09:04:17 -0800 Subject: [PATCH] Fix #316 polymorphic create fragment (#483) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * FragmentArray.createFragment should respect `type` in polymorphic arrays #316 * Remove unused test * Update tests/unit/polymorphic_test.js typo Co-authored-by: Vincent MoliniƩ * refactor: convert tests to modern syntax * fix attribute name in test * fix test failure * fix FragmentArray property documentation FragmentArray#type was renamed to modelName FragmentArray#options are no longer used * reformat code * ensure that the fragment owner is set * add failing test for createRecord * fix createRecord for polymorphic fragment arrays --------- Co-authored-by: Stefan Fochler Co-authored-by: Ilya Radchenko Co-authored-by: Vincent MoliniƩ --- addon/array/fragment.js | 19 ++--- addon/record-data.js | 9 ++ tests/dummy/app/models/animal.js | 2 + tests/dummy/app/models/component.js | 8 +- tests/unit/polymorphic_test.js | 124 ++++++++++++++++++++++++++++ 5 files changed, 151 insertions(+), 11 deletions(-) diff --git a/addon/array/fragment.js b/addon/array/fragment.js index 51b5e1d7..36075f49 100644 --- a/addon/array/fragment.js +++ b/addon/array/fragment.js @@ -22,13 +22,11 @@ const FragmentArray = StatefulArray.extend({ /** The type of fragments the array contains - @property type + @property modelName @private @type {String} */ - type: null, - - options: null, + modelName: null, objectAt(index) { const recordData = this._super(index); @@ -55,9 +53,7 @@ const FragmentArray = StatefulArray.extend({ existing.setProperties(data); return recordDataFor(existing); } - const fragment = this.store.createFragment(this.modelName, data); - setFragmentOwner(fragment, this.recordData, this.key); - return recordDataFor(fragment); + return this.recordData._newFragmentRecordDataForKey(this.key, data); }, /** @@ -142,15 +138,18 @@ const FragmentArray = StatefulArray.extend({ /** Creates a new fragment of the fragment array's type and adds it to the end - of the fragment array + of the fragment array. @method createFragment @param {MF.Fragment} fragment @return {MF.Fragment} the newly added fragment */ createFragment(props) { - const fragment = this.store.createFragment(this.modelName, props); - setFragmentOwner(fragment, this.recordData, this.key); + const recordData = this.recordData._newFragmentRecordDataForKey( + this.key, + props + ); + const fragment = recordData._fragmentGetRecord(); return this.pushObject(fragment); }, }); diff --git a/addon/record-data.js b/addon/record-data.js index 6e981eb8..b8736f29 100644 --- a/addon/record-data.js +++ b/addon/record-data.js @@ -600,6 +600,15 @@ export default class FragmentRecordData extends RecordData { this._fragmentOwner = { recordData, key }; } + _newFragmentRecordDataForKey(key, attributes) { + const behavior = this._fragmentBehavior[key]; + assert( + `Attribute '${key}' for model '${this.modelName}' must be a fragment`, + behavior != null + ); + return this._newFragmentRecordData(behavior.definition, attributes); + } + _newFragmentRecordData(definition, attributes) { const type = getActualFragmentType( definition.modelName, diff --git a/tests/dummy/app/models/animal.js b/tests/dummy/app/models/animal.js index 1751726d..1ad97071 100644 --- a/tests/dummy/app/models/animal.js +++ b/tests/dummy/app/models/animal.js @@ -1,6 +1,8 @@ import Fragment from 'ember-data-model-fragments/fragment'; +import { fragmentOwner } from 'ember-data-model-fragments/attributes'; import { attr } from '@ember-data/model'; export default class Animal extends Fragment { @attr('string') name; + @fragmentOwner() zoo; } diff --git a/tests/dummy/app/models/component.js b/tests/dummy/app/models/component.js index 31ce5bdf..44570415 100644 --- a/tests/dummy/app/models/component.js +++ b/tests/dummy/app/models/component.js @@ -1,5 +1,5 @@ import Model, { attr } from '@ember-data/model'; -import { fragment } from 'ember-data-model-fragments/attributes'; +import { fragment, fragmentArray } from 'ember-data-model-fragments/attributes'; export default class Component extends Model { @attr('string') name; @@ -9,4 +9,10 @@ export default class Component extends Model { typeKey: (data, owner) => `component-options-${owner.type}`, }) options; + + @fragmentArray('component-options', { + polymorphic: true, + typeKey: (data, owner) => `component-options-${owner.type}`, + }) + optionsHistory; } diff --git a/tests/unit/polymorphic_test.js b/tests/unit/polymorphic_test.js index fc1ad6b5..cf0d24fe 100644 --- a/tests/unit/polymorphic_test.js +++ b/tests/unit/polymorphic_test.js @@ -3,6 +3,7 @@ import { setupApplicationTest } from '../helpers'; import Animal from 'dummy/models/animal'; import Lion from 'dummy/models/lion'; import Elephant from 'dummy/models/elephant'; +import ComponentOptionsText from 'dummy/models/component-options-text'; let store, zoo; module('unit - Polymorphism', function (hooks) { @@ -83,4 +84,127 @@ module('unit - Polymorphism', function (hooks) { assert.ok(second instanceof Elephant); assert.equal(second.trunkLength, 4, "elephant's trunk length is correct"); }); + + test("fragment array's createFragment supports polymorphism with string typeKey", async function (assert) { + store.push({ + data: { + type: 'zoo', + id: 1, + attributes: zoo, + }, + }); + + const record = await store.find('zoo', 1); + const animals = record.animals; + + const newLion = animals.createFragment({ + $type: 'lion', + name: 'Alex', + hasManes: 'true', + }); + + assert.ok(newLion instanceof Animal, 'new lion is an animal'); + assert.equal(newLion.name, 'Alex', "new animal's name is correct"); + assert.ok(newLion instanceof Lion, 'new lion is a lion'); + assert.ok(newLion.hasManes, 'lion has manes'); + assert.strictEqual(newLion.zoo, record, 'set the fragment owner'); + + const newElephant = animals.createFragment({ + $type: 'elephant', + name: 'Heffalump', + trunkLength: 7, + }); + + assert.ok(newElephant instanceof Animal, 'new elephant is an animal'); + assert.equal(newElephant.name, 'Heffalump', "new animal's name is correct"); + assert.ok(newElephant instanceof Elephant, 'new elephant is an elephant'); + assert.equal( + newElephant.trunkLength, + 7, + "elephant's trunk length is correct" + ); + assert.strictEqual(newElephant.zoo, record, 'set the fragment owner'); + }); + + test("fragment array's createFragment supports polymorphism with function typeKey", async function (assert) { + store.push({ + data: { + type: 'component', + id: 1, + attributes: { + type: 'text', + optionsHistory: [], + }, + }, + }); + + const component = await store.find('component', 1); + const textOptions = component.optionsHistory.createFragment({ + fontFamily: 'Verdana', + fontSize: 12, + }); + + assert.ok( + textOptions instanceof ComponentOptionsText, + 'options is ComponentOptionsText' + ); + assert.equal( + textOptions.fontFamily, + 'Verdana', + 'options has correct fontFamily attribute' + ); + assert.equal( + textOptions.fontSize, + 12, + 'options has correct fontSize attribute' + ); + assert.equal( + component.optionsHistory.length, + 1, + 'fragment object was added to fragment array' + ); + }); + + test('createRecord supports polymorphic typeKey for fragment and fragment arrays', async function (assert) { + const zoo = store.createRecord('zoo', { + star: { + $type: 'lion', + name: 'Mittens', + hasManes: true, + }, + animals: [ + { + $type: 'lion', + name: 'Alex', + hasManes: false, + }, + { + $type: 'elephant', + name: 'Heffalump', + trunkLength: 7, + }, + ], + }); + + const star = zoo.star; + assert.ok(star instanceof Lion, 'star is a lion'); + assert.strictEqual(star.name, 'Mittens', 'star name is correct'); + assert.strictEqual(star.hasManes, true, 'star has manes'); + assert.strictEqual(star.zoo, zoo, 'star fragment owner is correct'); + + const animals = zoo.animals; + assert.strictEqual(animals.length, 2); + + const lion = animals.firstObject; + assert.ok(lion instanceof Lion, 'first animal is a lion'); + assert.strictEqual(lion.name, 'Alex', 'lion name is correct'); + assert.false(lion.hasManes, 'lion does not have manes'); + assert.strictEqual(lion.zoo, zoo, 'lion fragment owner is correct'); + + const elephant = animals.lastObject; + assert.ok(elephant instanceof Elephant, 'second animal is an elephant'); + assert.strictEqual(elephant.name, 'Heffalump', 'elephant name is correct'); + assert.strictEqual(elephant.trunkLength, 7, 'trunk length is correct'); + assert.strictEqual(elephant.zoo, zoo, 'elephant fragment owner is correct'); + }); });