Skip to content

Commit

Permalink
Fix #316 polymorphic create fragment (#483)
Browse files Browse the repository at this point in the history
* FragmentArray.createFragment should respect `type` in polymorphic arrays #316

* Remove unused test

* Update tests/unit/polymorphic_test.js typo

Co-authored-by: Vincent Molinié <vincentm@forestadmin.com>

* 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 <istefo@me.com>
Co-authored-by: Ilya Radchenko <knownasilya@gmail.com>
Co-authored-by: Vincent Molinié <vincentm@forestadmin.com>
  • Loading branch information
4 people authored Feb 7, 2024
1 parent c9f6ca5 commit e005411
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 11 deletions.
19 changes: 9 additions & 10 deletions addon/array/fragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
},

/**
Expand Down Expand Up @@ -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);
},
});
Expand Down
9 changes: 9 additions & 0 deletions addon/record-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions tests/dummy/app/models/animal.js
Original file line number Diff line number Diff line change
@@ -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;
}
8 changes: 7 additions & 1 deletion tests/dummy/app/models/component.js
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
}
124 changes: 124 additions & 0 deletions tests/unit/polymorphic_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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');
});
});

0 comments on commit e005411

Please sign in to comment.