Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend test coverage to sync relationships #409

Merged
merged 4 commits into from
Jan 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions addon/-private/validated-changeset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,29 @@ const AFTER_VALIDATION_EVENT = 'afterValidation';
const AFTER_ROLLBACK_EVENT = 'afterRollback';
const defaultValidatorFn = () => true;
const defaultOptions = { skipValidate: false };
const isEmberDataObject = (obj: Object) => {
let keys = Object.keys(obj || {})
return keys.indexOf('content') !== -1 &&
keys.indexOf('isFulfilled') !== -1 &&
keys.indexOf('isRejected') !== -1

// TODO: Get rid of this heuristic check by passing an option to the Changeset constructor
const isBelongsToRelationship = (obj: Object) => {
if (!obj) {
return false;
}

if (obj.hasOwnProperty('content') &&
obj.hasOwnProperty('isFulfilled') &&
obj.hasOwnProperty('isRejected')) {
// Async belongsTo()
return true;
}

if ('isLoading' in obj &&
'isLoaded' in obj &&
'isNew' in obj &&
'hasDirtyAttributes' in obj) {
// Sync belongsTo()
return true;
}

return false;
}

export class BufferedChangeset implements IChangeset {
Expand Down Expand Up @@ -865,7 +883,7 @@ export class BufferedChangeset implements IChangeset {
// requested key is the top most nested property and we have changes in of the properties, we need to
// merge the original model data with the changes to have the complete object.
// eg. model = { user: { name: 'not changed', email: '[email protected]'} }
if (!Array.isArray(result) && isObject(content[baseKey]) && !isEmberDataObject(content[baseKey])) {
if (!Array.isArray(result) && isObject(content[baseKey]) && !isBelongsToRelationship(content[baseKey])) {
let data: Record<string, any> = {}
Object.keys(content[baseKey]).forEach(k => {
data[k] = this.getDeep(content, `${baseKey}.${k}`)
Expand Down
7 changes: 7 additions & 0 deletions tests/dummy/app/models/sync-user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import DS from 'ember-data';
import { belongsTo, hasMany } from 'ember-data/relationships';

export default DS.Model.extend({
profile: belongsTo('profile', { async: false }),
dogs: hasMany('dog', { async: false }),
});
120 changes: 93 additions & 27 deletions tests/integration/main-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,23 @@ module('Integration | main', function(hooks) {
setupTest(hooks);

hooks.beforeEach(function() {
// for backwards compatibility with pre 3.0 versions of ember
let container = this.owner || this.application.__container__;
this.store = container.lookup('service:store');

let profile = this.store.createRecord('profile');
let user = this.store.createRecord('user', { profile });
this.dummyUser = user;

for (let i = 0; i < 2; i++) {
user.get('dogs').addObject(this.store.createRecord('dog'))
this.store = this.owner.lookup('service:store');

this.createUser = (userType, withDogs) => {
let profile = this.store.createRecord('profile');
let user = this.store.createRecord(userType, { profile });

if (withDogs) {
for (let i = 0; i < 2; i++) {
user.get('dogs').addObject(this.store.createRecord('dog'))
}
}
return user;
}
});

test('it works for belongsTo', async function(assert) {
let user = this.dummyUser;
async function testBasicBelongsTo(assert, userType) {
let user = this.createUser(userType, false);
let changeset = new Changeset(user);

assert.equal(changeset.get('profile'), user.get('profile'));
Expand All @@ -42,8 +44,7 @@ module('Integration | main', function(hooks) {
assert.equal(user.get('profile.lastName'), 'Ross', 'lastName after execute');
assert.equal(user.get('profile.nickname'), 'g', 'nickname after execute');

let profile;
profile = await this.store.createRecord('profile', { firstName: 'Terry', lastName: 'Bubblewinkles', nickname: 't' });
let profile = this.store.createRecord('profile', { firstName: 'Terry', lastName: 'Bubblewinkles', nickname: 't' });

changeset.set('profile', profile);

Expand All @@ -67,31 +68,45 @@ module('Integration | main', function(hooks) {
changeset.execute();

assert.equal(changeset.get('profile'), null, 'changeset profile relationship is still null');
assert.equal(user.get('profile').get('firstName'), null, 'underlying user profile firstName is null');
assert.ok(user.get('profile'), 'user has yet to call save so still present as proxy');
assert.equal(user.get('profile.firstName'), null, 'underlying user profile firstName is null');
}

test('it works for belongsTo', async function(assert) {
await testBasicBelongsTo.call(this, assert, 'user');
});

test('can save user', async function(assert) {
test('it works for sync belongsTo', async function(assert) {
await testBasicBelongsTo.call(this, assert, 'sync-user');
});

async function testSaveUser(assert, userType) {
assert.expect(1);

let profile = this.store.createRecord('profile');
let save = () => {
assert.ok(true, 'user save was called')
}
this.dummyUser = this.store.createRecord('user', { profile, save });

let user = this.dummyUser;
let user = this.store.createRecord(userType, { profile, save });
let changeset = new Changeset(user);

changeset.set('profile.firstName', 'Grace');
changeset.save();
}

test('can save user', async function(assert) {
await testSaveUser.call(this, assert, 'user');
});

test('can save sync user', async function(assert) {
await testSaveUser.call(this, assert, 'sync-user');
});

test('can save ember data model with multiple attributes', async function (assert) {
assert.expect(1);

let save = () => {
assert.ok(true, 'user save was called');
assert.ok(true, 'profile save was called');
};
let profile = this.store.createRecord('profile', { save });
let pet = this.store.createRecord('dog')
Expand All @@ -104,11 +119,10 @@ module('Integration | main', function(hooks) {
changeset.save();
});

test('can work with belongsTo via changeset', async function(assert) {
async function testBelongsToViaChangeset(assert, userType) {
let profile = this.store.createRecord('profile');
this.dummyUser = this.store.createRecord('user', { profile });
let user = this.store.createRecord(userType, { profile });

let user = this.dummyUser;
let changeset = new Changeset(user);

changeset.set('profile.firstName', 'Grace');
Expand All @@ -127,11 +141,18 @@ module('Integration | main', function(hooks) {

assert.equal(profile.firstName, 'Grace', 'profile has first name');
assert.equal(user.get('profile.firstName'), 'Grace', 'user now has profile has first name');
}

test('can work with belongsTo via changeset', async function(assert) {
await testBelongsToViaChangeset.call(this, assert, 'user');
});

test('it works for hasMany / firstObject', async function(assert) {
let user = this.dummyUser;
test('can work with sync belongsTo via changeset', async function(assert) {
await testBelongsToViaChangeset.call(this, assert, 'sync-user');
});

async function testHasMany(assert, userType) {
let user = this.createUser(userType, true);
let changeset = new Changeset(user);
let newDog = this.store.createRecord('dog', { breed: 'Münsterländer' });
let dogs = changeset.get('dogs');
Expand All @@ -155,10 +176,18 @@ module('Integration | main', function(hooks) {

dogs = user.get('dogs').toArray();
assert.equal(dogs.length, 0, 'dogs removed');
}

test('it works for hasMany / firstObject', async function(assert) {
await testHasMany.call(this, assert, 'user');
});

test('it can rollback hasMany', async function(assert) {
let user = this.dummyUser;
test('it works for sync hasMany / firstObject', async function(assert) {
await testHasMany.call(this, assert, 'sync-user');
});

async function testRollbackHasMany(assert, userType) {
let user = this.createUser(userType, true);

let changeset = new Changeset(user);
let newDog = this.store.createRecord('dog', { breed: 'Münsterländer' });
Expand All @@ -171,5 +200,42 @@ module('Integration | main', function(hooks) {

dogs = changeset.get('dogs');
assert.equal(dogs.length, 2, 'changeset has 2 dogs');
}

test('it can rollback hasMany', async function(assert) {
await testRollbackHasMany.call(this, assert, 'user');
});

test('it can rollback sync hasMany', async function(assert) {
await testRollbackHasMany.call(this, assert, 'sync-user');
});

async function testInitiallyEmptyRelationships(assert, userType) {
let profile = this.store.createRecord('profile');
let user = this.store.createRecord(userType);

let changeset = new Changeset(user);

changeset.set('profile', profile);
const dogs = [
this.store.createRecord('dog'),
this.store.createRecord('dog', { breed: 'Münsterländer' })
];

changeset.set('dogs', dogs);

changeset.execute();

assert.equal(user.get('profile.firstName'), 'Bob', 'Profile is set on user');
assert.equal(user.get('dogs.firstObject.breed'), 'rough collie');
assert.equal(user.get('dogs.lastObject.breed'), 'Münsterländer');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI I don't think these array accessors work (at the moment at least)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, they work for EmberArray and dogs is an EmberArray.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh you are right...we are passing in Ember's getter...

}

test('it sets relationships which were empty initially', async function(assert) {
await testInitiallyEmptyRelationships.call(this, assert, 'user');
});

test('it sets sync relationships which were empty initially', async function(assert) {
await testInitiallyEmptyRelationships.call(this, assert, 'sync-user');
});
});