test('DS.hasMany proxy is destroyed', function(assert) { const Tag = DS.Model.extend({ name: DS.attr('string'), people: DS.hasMany('person') }); const Person = DS.Model.extend({ name: DS.attr('string'), tag: DS.belongsTo('tag', { async: false }) }); let { store } = setupStore({ tag: Tag, person: Person }); let tag = run(() => store.createRecord('tag')); let peopleProxy = tag.get('people'); return peopleProxy.then(people => { Ember.run(() => { tag.unloadRecord(); assert.equal(people.isDestroying, false, 'people is NOT destroying sync after unloadRecord'); assert.equal(people.isDestroyed, false, 'people is NOT destroyed sync after unloadRecord'); assert.equal(peopleProxy.isDestroying, false, 'peopleProxy is destroying sync after unloadRecord'); assert.equal(peopleProxy.isDestroyed, false, 'peopleProxy is NOT YET destroyed sync after unloadRecord'); }); assert.equal(peopleProxy.isDestroying, true, 'peopleProxy is destroying after the run post unloadRecord'); assert.equal(peopleProxy.isDestroyed, true, 'peopleProxy is destroyed after the run post unloadRecord'); }) });
testInDebug('checks if passed array only contains instances of DS.Model', function(assert) { const Person = DS.Model.extend(); const Tag = DS.Model.extend({ people: DS.hasMany('person') }); let env = setupStore({ tag: Tag, person: Person }); env.adapter.findRecord = function() { return { data: { type: 'person', id: 1 } }; }; let tag, person; run(() => { tag = env.store.createRecord('tag'); person = env.store.findRecord('person', 1); }); run(() => { assert.expectAssertion(() => { tag.set('people', [person]); }, /All elements of a hasMany relationship must be instances of DS.Model/); }); });
test('Find with query calls the correct normalizeResponse', function(assert) { let passedQuery = { page: 1 }; const Person = DS.Model.extend({ name: DS.attr('string') }); const Adapter = TestAdapter.extend({ query(store, type, query) { return Ember.RSVP.resolve([]); } }); let callCount = 0; const ApplicationSerializer = DS.JSONSerializer.extend({ normalizeQueryResponse() { callCount++; return this._super(...arguments); } }); let env = setupStore({ adapter: Adapter, person: Person }); let { store } = env; env.registry.register('serializer:application', ApplicationSerializer); run(() => store.query('person', passedQuery)); assert.equal(callCount, 1, 'normalizeQueryResponse was called'); });
test('DS.hasMany is stable', function(assert) { const Tag = DS.Model.extend({ name: DS.attr('string'), people: DS.hasMany('person') }); const Person = DS.Model.extend({ name: DS.attr('string'), tag: DS.belongsTo('tag', { async: false }) }); let { store } = setupStore({ tag: Tag, person: Person }); let tag = run(() => store.createRecord('tag')); let people = tag.get('people'); let peopleCached = tag.get('people'); assert.equal(people, peopleCached); tag.notifyPropertyChange('people'); let notifiedPeople = tag.get('people'); assert.equal(people, notifiedPeople); return Ember.RSVP.Promise.all([ people ]); });
test('initial values of belongsTo can be passed in as the third argument to find as ids', function(assert) { assert.expect(1); const Adapter = TestAdapter.extend({ findRecord(store, type, id, snapshot) { return { data: { id, type: 'person' } }; } }); let env = setupStore({ adapter: Adapter }); let { store } = env; const Person = DS.Model.extend({ name: DS.attr('string'), friend: DS.belongsTo('person', { async: true, inverse: null }) }); env.registry.register('model:person', Person); return run(() => { return store.findRecord('person', 1, { preload: { friend: 2 } }).then(() => { return store.peekRecord('person', 1).get('friend').then(friend => { assert.equal(friend.get('id'), '2', 'Preloaded belongsTo set'); }); }); }); });
test("When a record's belongsTo relationship is set, it can specify the inverse hasMany to which the new child should be added", function(assert) { Post = DS.Model.extend({ meComments: DS.hasMany('comment', { async: false }), youComments: DS.hasMany('comment', { async: false }), everyoneWeKnowComments: DS.hasMany('comment', { async: false }) }); Comment = DS.Model.extend({ post: DS.belongsTo('post', { inverse: 'youComments', async: false }) }); var env = setupStore({ post: Post, comment: Comment }); var store = env.store; var comment, post; run(function() { comment = store.createRecord('comment'); post = store.createRecord('post'); assert.equal(post.get('meComments.length'), 0, "meComments has no posts"); assert.equal(post.get('youComments.length'), 0, "youComments has no posts"); assert.equal(post.get('everyoneWeKnowComments.length'), 0, "everyoneWeKnowComments has no posts"); comment.set('post', post); }); assert.equal(comment.get('post'), post, 'The post that was set can be retrieved'); assert.equal(post.get('meComments.length'), 0, "meComments has no posts"); assert.equal(post.get('youComments.length'), 1, "youComments had the post added"); assert.equal(post.get('everyoneWeKnowComments.length'), 0, "everyoneWeKnowComments has no posts"); });
test('Serializer should map `attrs` attributes directly when keyForAttribute also has a transform', function(assert) { Post = DS.Model.extend({ authorName: DS.attr('string'), }); env = setupStore({ post: Post, }); env.owner.register( 'serializer:post', DS.JSONSerializer.extend({ keyForAttribute: underscore, attrs: { authorName: 'author_name_key', }, }) ); var jsonHash = { id: '1', author_name_key: 'DHH', }; var post = env.store .serializerFor('post') .normalizeResponse(env.store, Post, jsonHash, '1', 'findRecord'); assert.equal(post.data.attributes.authorName, 'DHH'); });
test("When setting a belongsTo, the OneToOne invariant is respected even when other records have been previously used", function(assert) { Post = DS.Model.extend({ bestComment: DS.belongsTo('comment', { async: false }) }); Comment = DS.Model.extend({ post: DS.belongsTo('post', { async: false }) }); var env = setupStore({ post: Post, comment: Comment }); var store = env.store; let comment = store.createRecord('comment'); let post = store.createRecord('post'); let post2 = store.createRecord('post'); run(function() { comment.set('post', post); post2.set('bestComment', null); }); assert.equal(comment.get('post'), post); assert.equal(post.get('bestComment'), comment); assert.strictEqual(post2.get('bestComment'), null); run(function() { comment.set('post', post2); }); assert.equal(comment.get('post'), post2); assert.strictEqual(post.get('bestComment'), null); assert.equal(post2.get('bestComment'), comment); });
test("OneToNone relationship works", function(assert) { assert.expect(3); Post = DS.Model.extend({ name: DS.attr('string') }); Comment = DS.Model.extend({ post: DS.belongsTo('post', { async: false }) }); var env = setupStore({ post: Post, comment: Comment }); var store = env.store; let comment = store.createRecord('comment'); let post1 = store.createRecord('post'); let post2 = store.createRecord('post'); run(function() { comment.set('post', post1); }); assert.equal(comment.get('post'), post1, 'the post is set to the first one'); run(function() { comment.set('post', post2); }); assert.equal(comment.get('post'), post2, 'the post is set to the second one'); run(function() { comment.set('post', post1); }); assert.equal(comment.get('post'), post1, 'the post is re-set to the first one'); });
test('ID mutation (complicated)', function(assert) { assert.expect(5); let idChange = 0; const Person = DS.Model.extend({ name: DS.attr('string'), idComputed: Ember.computed('id', function() {}), idDidChange: Ember.observer('id', () => idChange++) }); let { store } = setupStore({ person: Person.extend() }); run(() => { let person = store.createRecord('person'); person.get('idComputed'); assert.equal(idChange, 0); assert.equal(person.get('id'), null, 'initial created model id should be null'); assert.equal(idChange, 0); store.updateId(person._internalModel, { id: 'john' }); assert.equal(idChange, 1); assert.equal(person.get('id'), 'john', 'new id should be correctly set.'); }); });
test("When a record is added to a has-many relationship, the inverse belongsTo can be set explicitly", function(assert) { Post = DS.Model.extend({ comments: DS.hasMany('comment', { inverse: 'redPost', async: false }) }); Comment = DS.Model.extend({ onePost: DS.belongsTo('post', { async: false }), twoPost: DS.belongsTo('post', { async: false }), redPost: DS.belongsTo('post', { async: false }), bluePost: DS.belongsTo('post', { async: false }) }); var env = setupStore({ post: Post, comment: Comment }); var store = env.store; let comment = store.createRecord('comment'); let post = store.createRecord('post'); assert.equal(comment.get('onePost'), null, "onePost has not been set on the comment"); assert.equal(comment.get('twoPost'), null, "twoPost has not been set on the comment"); assert.equal(comment.get('redPost'), null, "redPost has not been set on the comment"); assert.equal(comment.get('bluePost'), null, "bluePost has not been set on the comment"); run(function() { post.get('comments').pushObject(comment); }); assert.equal(comment.get('onePost'), null, "onePost has not been set on the comment"); assert.equal(comment.get('twoPost'), null, "twoPost has not been set on the comment"); assert.equal(comment.get('redPost'), post, "redPost has been set on the comment"); assert.equal(comment.get('bluePost'), null, "bluePost has not been set on the comment"); });
test('it is possible to add a new item to a relationship', function(assert) { assert.expect(2); const Tag = DS.Model.extend({ name: DS.attr('string'), people: DS.belongsTo('person', { async: false }) }); const Person = DS.Model.extend({ name: DS.attr('string'), tags: DS.hasMany('tag', { async: false }) }); let env = setupStore({ tag: Tag, person: Person }); env.adapter.shouldBackgroundReloadRecord = () => false; let { store } = env; run(() => { store.push({ data: [{ type: 'person', id: '1', attributes: { name: 'Tom Dale' }, relationships: { tags: { data: [ { type: 'tag', id: '1' } ] } } }, { type: 'tag', id: '1', attributes: { name: 'ember' } }] }); }); return run(() => { return store.findRecord('person', 1).then(person =>{ let tag = get(person, 'tags').objectAt(0); assert.equal(get(tag, 'name'), 'ember', 'precond - relationships work'); tag = store.createRecord('tag', { name: 'js' }); get(person, 'tags').pushObject(tag); assert.equal(get(person, 'tags').objectAt(1), tag, 'newly added relationship works'); }); }); });
function initializeStore(adapter) { env = setupStore({ adapter: adapter, }); store = env.store; env.registry.register('model:car', Car); env.registry.register('model:person', Person); }
test('hasMany relationships work when the data hash has not been loaded', function(assert) { assert.expect(8); const Tag = DS.Model.extend({ name: DS.attr('string'), person: DS.belongsTo('person', { async: false }) }); const Person = DS.Model.extend({ name: DS.attr('string'), tags: DS.hasMany('tag', { async: true }) }); let env = setupStore({ tag: Tag, person: Person }); let { store } = env; env.adapter.coalesceFindRequests = true; env.adapter.findMany = function(store, type, ids, snapshots) { assert.equal(type, Tag, 'type should be Tag'); assert.deepEqual(ids, ['5', '2'], 'ids should be 5 and 2'); return { data: [ { id: 5, type: 'tag', attributes: { name: 'friendly' } }, { id: 2, type: 'tag', attributes: { name: 'smarmy' } } ]}; }; env.adapter.findRecord = function(store, type, id, snapshot) { assert.equal(type, Person, 'type should be Person'); assert.equal(id, 1, 'id should be 1'); return { data: { id: 1, type: 'person', attributes: { name: 'Tom Dale' }, relationships: { tags: { data: [{ id: 5, type: 'tag'}, { id: 2, type: 'tag'}] } } } }; }; return run(() => { return store.findRecord('person', 1).then(person => { assert.equal(get(person, 'name'), 'Tom Dale', 'The person is now populated'); return run(() => person.get('tags')); }).then(tags => { assert.equal(get(tags, 'length'), 2, 'the tags object still exists'); assert.equal(get(tags.objectAt(0), 'name'), 'friendly', 'Tom Dale is now friendly'); assert.equal(get(tags.objectAt(0), 'isLoaded'), true, 'Tom Dale is now loaded'); }); }); });
test('it is possible to remove an item from a relationship', function(assert) { assert.expect(2); const Tag = DS.Model.extend({ name: DS.attr('string'), person: DS.belongsTo('person', { async: false }) }); const Person = DS.Model.extend({ name: DS.attr('string'), tags: DS.hasMany('tag', { async: false }) }); let env = setupStore({ tag: Tag, person: Person }); let { store } = env; env.adapter.shouldBackgroundReloadRecord = () => false; run(() => { store.push({ data: [{ type: 'person', id: '1', attributes: { name: 'Tom Dale' }, relationships: { tags: { data: [ { type: 'tag', id: '1' } ] } } }, { type: 'tag', id: '1', attributes: { name: 'ember' } }] }); }); return run(() => { return store.findRecord('person', 1).then(person => { let tag = get(person, 'tags').objectAt(0); assert.equal(get(tag, 'name'), 'ember', 'precond - relationships work'); run(() => get(person, 'tags').removeObject(tag)); assert.equal(get(person, 'tags.length'), 0, 'object is removed from the relationship'); }); }); });
beforeEach: function() { HomePlanet = DS.Model.extend({ name: DS.attr('string'), superVillains: DS.hasMany('super-villain', { async: false }) }); SuperVillain = DS.Model.extend({ firstName: DS.attr('string'), lastName: DS.attr('string'), homePlanet: DS.belongsTo('home-planet', { async: false }), evilMinions: DS.hasMany('evil-minion', { async: false }) }); EvilMinion = DS.Model.extend({ superVillain: DS.belongsTo('super-villain', { async: false }), name: DS.attr('string') }); YellowMinion = EvilMinion.extend({ eyes: DS.attr('number') }); DoomsdayDevice = DS.Model.extend({ name: DS.attr('string'), evilMinion: DS.belongsTo('evil-minion', { polymorphic: true, async: true }) }); Comment = DS.Model.extend({ body: DS.attr('string'), root: DS.attr('boolean'), children: DS.hasMany('comment', { inverse: null, async: false }) }); Basket = DS.Model.extend({ type: DS.attr('string'), size: DS.attr('number') }); Container = DS.Model.extend({ type: DS.belongsTo('basket', { async: true }), volume: DS.attr('string') }); env = setupStore({ superVillain: SuperVillain, homePlanet: HomePlanet, evilMinion: EvilMinion, yellowMinion: YellowMinion, doomsdayDevice: DoomsdayDevice, comment: Comment, basket: Basket, container: Container }); env.store.modelFor('super-villain'); env.store.modelFor('home-planet'); env.store.modelFor('evil-minion'); env.store.modelFor('yellow-minion'); env.store.modelFor('doomsday-device'); env.store.modelFor('comment'); env.store.modelFor('basket'); env.store.modelFor('container'); },
beforeEach: function() { Person = DS.Model.extend({ firstName: attr('string'), lastName: attr('string'), }); env = setupStore({ person: Person, }); store = env.store; },
testInDebug("Inverse null relationships with models that don't exist throw a nice error", function(assert) { User = DS.Model.extend({ post: DS.belongsTo('post', { inverse: null }) }); var env = setupStore({ user: User }); assert.throws(function() { run(function() { env.store.createRecord('user'); }); }, /No model was found for 'post'/); });
beforeEach: function() { Person = DS.Model.extend({ updatedAt: DS.attr('string'), name: DS.attr('string'), firstName: DS.attr('string'), lastName: DS.attr('string') }); env = setupStore({ person: Person }); store = env.store; },
test("belongsTo gives a warning when provided with an embedded option", function(assert) { var Hobby = DS.Model.extend({ name: DS.attr('string') }); var Person = DS.Model.extend({ name: DS.attr('string'), hobby: DS.belongsTo('hobby', { embedded: true, async: true }) }); var env = setupStore({ hobby: Hobby, person: Person }); var store = env.store; env.adapter.shouldBackgroundReloadRecord = () => false; run(function() { store.push({ data: [{ type: 'hobby', id: '1', attributes: { name: 'fishing' } }, { type: 'hobby', id: '2', attributes: { name: 'coding' } }, { type: 'person', id: '1', attributes: { name: 'Tom Dale' }, relationships: { hobby: { data: { type: 'hobby', id: '1' } } } }] }); }); run(function() { store.findRecord('person', 1).then(assert.wait(function(person) { assert.expectWarning(function() { get(person, 'hobby'); }, /You provided an embedded option on the "hobby" property in the "person" class, this belongs in the serializer. See DS.EmbeddedRecordsMixin/); })); }); });
test('An async hasMany relationship with links should not trigger shouldBackgroundReloadRecord', function(assert) { const Post = DS.Model.extend({ name: DS.attr('string'), comments: DS.hasMany('comment', { async: true }), }); const Comment = DS.Model.extend({ name: DS.attr('string'), }); env = setupStore({ post: Post, comment: Comment, adapter: DS.RESTAdapter.extend({ findRecord() { return { posts: { id: 1, name: 'Rails is omakase', links: { comments: '/posts/1/comments' }, }, }; }, findHasMany() { return resolve({ comments: [ { id: 1, name: 'FIRST' }, { id: 2, name: 'Rails is unagi' }, { id: 3, name: 'What is omakase?' }, ], }); }, shouldBackgroundReloadRecord() { assert.ok(false, 'shouldBackgroundReloadRecord should not be called'); }, }), }); store = env.store; return run(() => store .findRecord('post', '1') .then(post => { return post.get('comments'); }) .then(comments => { assert.equal(comments.get('length'), 3); }) ); });
testInDebug("Inverse null relationships with models that don't exist throw a nice error if trying to use that relationship", function(assert) { User = DS.Model.extend({ post: DS.belongsTo('post', { inverse: null }) }); let env = setupStore({ user: User }); assert.expectAssertion(() => { env.store.createRecord('user', { post: {}}); }, /No model was found for/) // but don't error if the relationship is not used env.store.createRecord('user', {}); });
function(assert) { User = DS.Model.extend({ post: DS.belongsTo('post', { inverse: null }), }); let env = setupStore({ user: User }); assert.expectAssertion(() => { env.store.createRecord('user', { post: null }); }, /No model was found for/); // but don't error if the relationship is not used env.store.createRecord('user', {}); }
test(`when destroying a record setup the record state to invalid, the record's attributes can be rollbacked`, function(assert) { const Dog = DS.Model.extend({ name: DS.attr(), }); let error = new DS.InvalidError([ { detail: 'is invalid', source: { pointer: 'data/attributes/name' }, }, ]); let adapter = DS.RESTAdapter.extend({ ajax(url, type, hash) { return reject(error); }, }); env = setupStore({ dog: Dog, adapter: adapter }); let dog = run(() => { env.store.push({ data: { type: 'dog', id: '1', attributes: { name: 'Pluto', }, }, }); return env.store.peekRecord('dog', 1); }); return run(() => { return dog.destroyRecord().catch(reason => { assert.equal(reason, error); assert.equal(dog.get('isError'), false, 'must not be error'); assert.equal(dog.get('isDeleted'), true, 'must be deleted'); assert.equal(dog.get('isValid'), false, 'must not be valid'); assert.ok(dog.get('errors.length') > 0, 'must have errors'); dog.rollbackAttributes(); assert.equal(dog.get('isError'), false, 'must not be error after `rollbackAttributes`'); assert.equal(dog.get('isDeleted'), false, 'must not be deleted after `rollbackAttributes`'); assert.equal(dog.get('isValid'), true, 'must be valid after `rollbackAttributes`'); assert.ok(dog.get('errors.length') === 0, 'must not have errors'); }); }); });
test("When a record is added to or removed from a polymorphic has-many relationship, the inverse belongsTo can be set explicitly", function(assert) { User = DS.Model.extend({ messages: DS.hasMany('message', { async: false, inverse: 'redUser', polymorphic: true }) }); Message = DS.Model.extend({ oneUser: DS.belongsTo('user', { async: false }), twoUser: DS.belongsTo('user', { async: false }), redUser: DS.belongsTo('user', { async: false }), blueUser: DS.belongsTo('user', { async: false }) }); Post = Message.extend(); var env = setupStore({ user: User, message: Message, post: Post }); var store = env.store; var post, user; run(function() { post = store.createRecord('post'); user = store.createRecord('user'); }); assert.equal(post.get('oneUser'), null, "oneUser has not been set on the user"); assert.equal(post.get('twoUser'), null, "twoUser has not been set on the user"); assert.equal(post.get('redUser'), null, "redUser has not been set on the user"); assert.equal(post.get('blueUser'), null, "blueUser has not been set on the user"); run(function() { user.get('messages').pushObject(post); }); assert.equal(post.get('oneUser'), null, "oneUser has not been set on the user"); assert.equal(post.get('twoUser'), null, "twoUser has not been set on the user"); assert.equal(post.get('redUser'), user, "redUser has been set on the user"); assert.equal(post.get('blueUser'), null, "blueUser has not been set on the user"); run(function() { user.get('messages').popObject(); }); assert.equal(post.get('oneUser'), null, "oneUser has not been set on the user"); assert.equal(post.get('twoUser'), null, "twoUser has not been set on the user"); assert.equal(post.get('redUser'), null, "redUser has bot been set on the user"); assert.equal(post.get('blueUser'), null, "blueUser has not been set on the user"); });
test('should be able to retrieve the type for a hasMany relationship specified using a string from its metadata', function(assert) { const Tag = DS.Model.extend({}); const Person = DS.Model.extend({ tags: DS.hasMany('tag', { async: false }) }); let env = setupStore({ tag: Tag, person: Person }); assert.equal(env.store.modelFor('person').typeForRelationship('tags', env.store), Tag, 'returns the relationship type'); });
test('should be able to retrieve the type for a belongsTo relationship without specifying a type from its metadata', function(assert) { const Tag = DS.Model.extend({}); const Person = DS.Model.extend({ tag: DS.belongsTo('tag', { async: false }) }); let env = setupStore({ tag: Tag, person: Person }); assert.equal(env.store.modelFor('person').typeForRelationship('tag', env.store), Tag, 'returns the relationship type'); });
test("async belongsTo relationships work when the data hash has already been loaded", function(assert) { assert.expect(3); var Tag = DS.Model.extend({ name: DS.attr('string') }); var Person = DS.Model.extend({ name: DS.attr('string'), tag: DS.belongsTo('tag', { async: true }) }); var env = setupStore({ tag: Tag, person: Person }); var store = env.store; run(function() { store.push({ data: [{ type: 'tag', id: '2', attributes: { name: 'friendly' } }, { type: 'person', id: '1', attributes: { name: 'Tom Dale' }, relationships: { tag: { data: { type: 'tag', id: '2' } } } }] }); }); run(function() { var person = store.peekRecord('person', 1); assert.equal(get(person, 'name'), "Tom Dale", "The person is now populated"); return run(function() { return get(person, 'tag'); }).then(assert.wait(function(tag) { assert.equal(get(tag, 'name'), "friendly", "Tom Dale is now friendly"); assert.equal(get(tag, 'isLoaded'), true, "Tom Dale is now loaded"); })); }); });
test(`invalid record's attributes can be rollbacked after multiple failed calls - #3677`, function(assert) { let adapter = DS.RESTAdapter.extend({ ajax(url, type, hash) { let error = new DS.InvalidError(); return reject(error); }, }); env = setupStore({ person: Person, adapter: adapter }); let person; run(() => { person = env.store.push({ data: { type: 'person', id: 1, attributes: { firstName: 'original name', }, }, }); person.set('firstName', 'updated name'); }); return run(() => { assert.equal(person.get('firstName'), 'updated name', 'precondition: firstName is changed'); return person .save() .catch(() => { assert.equal(person.get('hasDirtyAttributes'), true, 'has dirty attributes'); assert.equal(person.get('firstName'), 'updated name', 'firstName is still changed'); return person.save(); }) .catch(() => { run(() => person.rollbackAttributes()); assert.equal(person.get('hasDirtyAttributes'), false, 'has no dirty attributes'); assert.equal( person.get('firstName'), 'original name', 'after rollbackAttributes() firstName has the original value' ); assert.equal(person.get('rolledBackCount'), 1); }); }); });
function(assert) { env = setupStore({ person: Person, adapter: DS.RESTAdapter }); store = env.store; adapter = env.adapter; adapter.query = function(store, type, query, recordArray) { assert.equal(type, Person, 'the query method is called with the correct type'); return resolve({ data: [{ id: 1, type: 'person', attributes: { name: 'Peter Wagenet' } }] }); }; assert.expectAssertion(() => { run(() => store.query('person', { page: 1 })); }, /The response to store.query is expected to be an array but it was a single record/); }