Skip to content

Commit

Permalink
Backport Train for Beta (#7803) (#7804)
Browse files Browse the repository at this point in the history
* deactivate broken partner tests

* feat: autotracking for reference id access (#7796)

* feat: autotracking for reference id access

* ensure references are torn down

* fix build

* add dep

* add to deps

* fix invalid json:api support and add valid json:api support

* autotracking tests and cleanup

* fix test failure, add comment

* skip tests when feature not available

* update test and fix lid reflection (#7800)

* update test and fix lid reflection

* remove debugger

* fix ff off branch

* add test and fix push of duplicate identifiers to a relationship (#7801)

* add test + fix for chained async has many (#7691)

* [bugfix]: fix for chained async has many

* add fix and update tests

* remove console.logs

* make work with flags off

* fix test for lts

Co-authored-by: Chris Thoburn <[email protected]>

* Fix: assign unknown properties in init after initialization is finished to ensure proper setup timing (#7771)

* Add failing test case which illustrates the createRecord bug

createRecord crashes when a setter which sets an attribute is involved
in the createRecord.

* update test location and add fix

Co-authored-by: Chris Thoburn <[email protected]>

* fix: A(PromiseManyArray) should have no-effect (#7802)

Co-authored-by: Sylvain Mina <[email protected]>
Co-authored-by: Andrey Fel <[email protected]>

Co-authored-by: Sylvain Mina <[email protected]>
Co-authored-by: Andrey Fel <[email protected]>
  • Loading branch information
3 people authored Dec 15, 2021
1 parent c5e6a0a commit bb0f036
Show file tree
Hide file tree
Showing 32 changed files with 1,018 additions and 230 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ module.exports = {
'packages/store/addon/-private/system/snapshot-record-array.ts',
'packages/store/addon/-private/system/schema-definition-service.ts',
'packages/store/addon/-private/system/request-cache.ts',
'packages/store/addon/-private/system/references/belongs-to.ts',
'packages/store/addon/-private/system/references/has-many.ts',
'packages/store/addon/-private/system/references/reference.ts',
'packages/store/addon/-private/system/references/record.ts',
'packages/store/addon/-private/system/record-notification-manager.ts',
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -313,10 +313,10 @@ jobs:
matrix:
partner:
[
ember-data-relationship-tracker,
# ember-data-relationship-tracker,
ember-m3,
ember-observer,
ember-resource-metadata,
# ember-resource-metadata,
factory-guy,
ilios-frontend,
model-fragments,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ module('tracking state flags on a record', function (hooks) {
enumerable: true,
configurable: true,
get() {
tag.reg; // subscribe
tag.rev; // subscribe
if (_isDirty && !_isUpdating) {
_isUpdating = true;
resolve(desc.get.call(this)).then((v) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,13 @@ module('Integration | Identifiers - lid reflection', function (hooks) {

class TestAdapter extends Adapter {
createRecord(store, ModelClass, snapshot) {
const lid = recordIdentifierFor(snapshot.record.ingredients.firstObject).lid;
const cakeLid = recordIdentifierFor(snapshot.record).lid;
const ingredientLid = recordIdentifierFor(snapshot.record.ingredients.firstObject).lid;
return resolve({
data: {
type: 'cake',
id: '1',
lid: cakeLid,
attributes: {
name: 'Cheesecake',
},
Expand All @@ -183,7 +185,7 @@ module('Integration | Identifiers - lid reflection', function (hooks) {
{
type: 'ingredient',
id: '2',
lid,
lid: ingredientLid,
},
],
},
Expand All @@ -193,7 +195,7 @@ module('Integration | Identifiers - lid reflection', function (hooks) {
{
type: 'ingredient',
id: '2',
lid,
lid: ingredientLid,
attributes: {
name: 'Cheese',
},
Expand All @@ -202,6 +204,7 @@ module('Integration | Identifiers - lid reflection', function (hooks) {
data: {
type: 'cake',
id: '1',
lid: cakeLid,
},
},
},
Expand All @@ -216,10 +219,17 @@ module('Integration | Identifiers - lid reflection', function (hooks) {
const cheese = store.createRecord('ingredient', { name: 'Cheese' });
const cake = store.createRecord('cake', { name: 'Cheesecake', ingredients: [cheese] });

// Consume ids before save() to check for update errors
assert.strictEqual(cake.id, null, 'cake id is initially null');
assert.strictEqual(cheese.id, null, 'cheese id is initially null');

await cake.save();

assert.deepEqual(cake.hasMany('ingredients').ids(), ['2']);
assert.equal(cake.ingredients.objectAt(0).name, 'Cheese');
assert.strictEqual(cake.ingredients.objectAt(0).name, 'Cheese');

assert.strictEqual(cake.id, '1', 'cake has the correct id');
assert.strictEqual(cheese.id, '2', 'cheese has the correct id');
});

test('belongsTo() has correct state after .save() on a newly created record with sideposted child record when lid is provided in the response payload', async function (assert) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,25 @@ module('Store.createRecord() coverage', function (hooks) {
store = owner.lookup('service:store');
});

test("createRecord doesn't crash when setter is involved", async function (assert) {
class User extends Model {
@attr() email;

get name() {
return this.email ? this.email.substring(0, this.email.indexOf('@')) : '';
}

set name(value) {
this.email = `${value.toLowerCase()}@ember.js`;
}
}
this.owner.register(`model:user`, User);
const store = this.owner.lookup('service:store');

const user = store.createRecord('user', { name: 'Robert' });
assert.strictEqual(user.email, '[email protected]');
});

test('unloading a newly created a record with a sync belongsTo relationship', async function (assert) {
let chris = store.push({
data: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -602,14 +602,16 @@ module('integration/records/relationship-changes - Relationship changes', functi
});

let person = store.peekRecord('person', 'wat');
let siblings = person.get('siblings');
let siblingsPromise = person.siblings;

await siblingsPromise;

// flush initial state since
// nothing is consuming us.
// else the test will fail because we will
// (correctly) not notify the array observer
// as there is still a pending notification
siblings.length;
siblingsPromise.length;

let observer = {
arrayWillChange(array, start, removing, adding) {
Expand All @@ -627,7 +629,7 @@ module('integration/records/relationship-changes - Relationship changes', functi
},
};

siblings.addArrayObserver(observer);
siblingsPromise.addArrayObserver(observer);

store.push({
data: {
Expand All @@ -646,7 +648,7 @@ module('integration/records/relationship-changes - Relationship changes', functi
assert.equal(willChangeCount, 1, 'willChange observer should be triggered once');
assert.equal(didChangeCount, 1, 'didChange observer should be triggered once');

siblings.removeArrayObserver(observer);
siblingsPromise.removeArrayObserver(observer);
},
{ id: 'array-observers', count: 2, when: { ember: '>=3.26.0' } }
);
Expand Down
154 changes: 154 additions & 0 deletions packages/-ember-data/tests/integration/references/autotracking-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import EmberObject from '@ember/object';
import { getRootElement, render } from '@ember/test-helpers';
import settled from '@ember/test-helpers/settled';

import hbs from 'htmlbars-inline-precompile';
import { module, test } from 'qunit';

import { setupRenderingTest } from 'ember-qunit';

import { CUSTOM_MODEL_CLASS } from '@ember-data/canary-features';
import Model, { attr, belongsTo, hasMany } from '@ember-data/model';
import { recordIdentifierFor } from '@ember-data/store';

if (CUSTOM_MODEL_CLASS) {
module('integration/references/autotracking', function (hooks) {
setupRenderingTest(hooks);

class User extends Model {
@attr name;
@belongsTo('user', { inverse: null, async: false })
bestFriend;
@hasMany('user', { inverse: null, async: false })
friends;
}

let store, user;
hooks.beforeEach(function () {
const { owner } = this;
owner.register('model:user', User);
store = owner.lookup('service:store');

owner.register(
'adapter:user',
class extends EmberObject {
createRecord() {
return { data: { id: '6', type: 'user' } };
}
}
);
owner.register(
'serializer:user',
class extends EmberObject {
normalizeResponse(_, __, data) {
return data;
}
}
);

user = store.push({
data: {
type: 'user',
id: '1',
attributes: {
name: 'Chris',
},
relationships: {
bestFriend: {
data: { type: 'user', id: '2' },
},
friends: {
data: [{ type: 'user', id: '2' }],
},
},
},
included: [
{ type: 'user', id: '2', attributes: { name: 'Igor' } },
{ type: 'user', id: '3', attributes: { name: 'David' } },
{ type: 'user', id: '4', attributes: { name: 'Scott' } },
{ type: 'user', id: '5', attributes: { name: 'Rob' } },
],
});
});

test('BelongsToReference.id() is autotracked', async function (assert) {
class TestContext {
user = user;

get bestFriendId() {
return this.user.belongsTo('bestFriend').id();
}
}

const testContext = new TestContext();
this.set('context', testContext);
await render(hbs`id: {{if this.context.bestFriendId this.context.bestFriendId 'null'}}`);

assert.strictEqual(getRootElement().textContent, 'id: 2', 'the id is initially correct');
assert.strictEqual(testContext.bestFriendId, '2', 'the id is initially correct');
user.bestFriend = store.createRecord('user', { name: 'Bill' });
await settled();
assert.strictEqual(getRootElement().textContent, 'id: null', 'the id updates to null');
assert.strictEqual(testContext.bestFriendId, null, 'the id is correct when we swap records');
await user.bestFriend.save();
await settled();
assert.strictEqual(getRootElement().textContent, 'id: 6', 'the id updates when the related record id updates');
assert.strictEqual(testContext.bestFriendId, '6', 'the id is correct when the record is saved');
});

test('HasManyReference.ids() is autotracked', async function (assert) {
class TestContext {
user = user;

get friendIds() {
return this.user.hasMany('friends').ids();
}
}
const testContext = new TestContext();
this.set('context', testContext);
await render(hbs`{{#each this.context.friendIds as |id|}}id: {{if id id 'null'}}, {{/each}}`);

assert.strictEqual(getRootElement().textContent, 'id: 2, ', 'the ids are initially correct');
assert.deepEqual(testContext.friendIds, ['2'], 'the ids are initially correct');
const bill = store.createRecord('user', { name: 'Bill' });
user.friends.pushObject(bill);
await settled();
assert.strictEqual(getRootElement().textContent, 'id: 2, id: null, ', 'the id is added for the new record');
assert.deepEqual(testContext.friendIds, ['2', null], 'the ids are correct when we add a new record');
await bill.save();
await settled();
assert.strictEqual(
getRootElement().textContent,
'id: 2, id: 6, ',
'the id updates when the related record id updates'
);
assert.deepEqual(testContext.friendIds, ['2', '6'], 'the ids are correct when the new record is saved');
});

test('RecordReference.id() is autotracked', async function (assert) {
const dan = store.createRecord('user', { name: 'Dan' });
const identifier = recordIdentifierFor(dan);
const reference = store.getReference(identifier);

class TestContext {
user = reference;

get id() {
return this.user.id();
}
}

const testContext = new TestContext();
this.set('context', testContext);

await render(hbs`id: {{if this.context.id this.context.id 'null'}}`);

assert.strictEqual(getRootElement().textContent, 'id: null', 'the id is null');
assert.strictEqual(testContext.id, null, 'the id is correct initially');
await dan.save();
await settled();
assert.strictEqual(getRootElement().textContent, 'id: 6', 'the id updates when the record id updates');
assert.strictEqual(testContext.id, '6', 'the id is correct when the record is saved');
});
});
}
Loading

0 comments on commit bb0f036

Please sign in to comment.