diff --git a/addon/adapters/localforage.js b/addon/adapters/localforage.js index ccff533..45cd271 100644 --- a/addon/adapters/localforage.js +++ b/addon/adapters/localforage.js @@ -29,33 +29,14 @@ export default DS.Adapter.extend(Ember.Evented, { */ findRecord: function (store, type, id, snapshot) { return new Ember.RSVP.Promise((resolve, reject) => { - var allowRecursive = true; this._namespaceForType(type).then((namespace) => { - /** - * In the case where there are relationships, this method is called again - * for each relation. Given the relations have references to the main - * object, we use allowRecursive to avoid going further into infinite - * recursiveness. - * - * Concept from ember-indexdb-adapter - */ - if (snapshot && typeof snapshot.allowRecursive !== 'undefined') { - allowRecursive = snapshot.allowRecursive; - } - var record = namespace.records[id]; if (!record) { reject(); return; } - if (allowRecursive) { - this.loadRelationships(store, type, record).then(function (finalRecord) { - resolve(finalRecord); - }); - } else { - resolve(record); - } + resolve(record); }); }); }, @@ -64,19 +45,19 @@ export default DS.Adapter.extend(Ember.Evented, { return new Ember.RSVP.Promise((resolve, reject) => { this._namespaceForType(type).then(function (namespace) { var results = []; + var record; for (var i = 0; i < ids.length; i++) { - results.push(Ember.copy(namespace.records[ids[i]])); + record = namespace.records[ids[i]]; + if (!record) { + reject(); + return; + } + results.push(Ember.copy(record)); } resolve(results); }); - }).then((records) => { - if (records.get('length')) { - return this.loadRelationshipsForMany(store, type, records); - } else { - return records; - } }); }, @@ -98,10 +79,6 @@ export default DS.Adapter.extend(Ember.Evented, { this._namespaceForType(type).then((namespace) => { var results = this._query(namespace.records, query); - if (results.get('length')) { - results = this.loadRelationshipsForMany(store, type, results); - } - resolve(results); }); }); @@ -112,13 +89,12 @@ export default DS.Adapter.extend(Ember.Evented, { return new Ember.RSVP.Promise((resolve, reject) => { this._namespaceForType(type).then((namespace) => { var result = this._query(namespace.records, query, true); - - if (result) { - result = this.loadRelationships(store, type, result); - resolve(result); - } else { + if (!result) { reject(); + return; } + + resolve(result); }); }); @@ -163,6 +139,7 @@ export default DS.Adapter.extend(Ember.Evented, { for (var id in namespace.records) { results.push(Ember.copy(namespace.records[id])); } + resolve(results); }); }); @@ -252,258 +229,6 @@ export default DS.Adapter.extend(Ember.Evented, { modelNamespace: function (type) { return type.url || type.modelName; }, - - - /** - * This takes a record, then analyzes the model relationships and replaces - * ids with the actual values. - * - * Stolen from ember-indexdb-adapter - * - * Consider the following JSON is entered: - * - * ```js - * { - * "id": 1, - * "title": "Rails Rambo", - * "comments": [1, 2] - * } - * - * This will return: - * - * ```js - * { - * "id": 1, - * "title": "Rails Rambo", - * "comments": [1, 2] - * - * "_embedded": { - * "comment": [{ - * "_id": 1, - * "comment_title": "FIRST" - * }, { - * "_id": 2, - * "comment_title": "Rails is unagi" - * }] - * } - * } - * - * This way, whenever a resource returned, its relationships will be also - * returned. - * - * @method loadRelationships - * @private - * @param {DS.Store} store - * @param {DS.Model} type - * @param {Object} record - */ - loadRelationships: function (store, type, record) { - return new Ember.RSVP.Promise((resolve, reject) => { - var resultJSON = {}, - modelName = type.modelName, - relationshipNames, relationships, - relationshipPromises = []; - - relationshipNames = Ember.get(type, 'relationshipNames'); - relationships = relationshipNames.belongsTo; - relationships = relationships.concat(relationshipNames.hasMany); - - relationships.forEach((relationName) => { - var relationModel = type.typeForRelationship(relationName, store), - relationEmbeddedId = record[relationName], - relationProp = this.relationshipProperties(type, relationName), - relationType = relationProp.kind, - promise, embedPromise; - - var opts = {allowRecursive: false}; - - /** - * embeddedIds are ids of relations that are included in the main - * payload, such as: - * - * { - * cart: { - * id: "s85fb", - * customer: "rld9u" - * } - * } - * - * In this case, cart belongsTo customer and its id is present in the - * main payload. We find each of these records and add them to _embedded. - */ - var embeddedAlways = this.isEmbeddedAlways(store, type.modelName, relationProp.key); - - // For embeddedAlways-style data, we assume the data to be present already, so no further loading is needed. - if (relationEmbeddedId && !embeddedAlways) { - if (relationType === 'belongsTo' || relationType === 'hasOne') { - promise = this.findRecord(store, relationModel, relationEmbeddedId, opts); - } else if (relationType === 'hasMany') { - promise = this.findMany(store, relationModel, relationEmbeddedId, opts); - } - - embedPromise = new Ember.RSVP.Promise((resolve, reject) => { - promise.then((relationRecord) => { - resolve(this.addEmbeddedPayload(record, relationName, relationRecord)); - }); - }); - - relationshipPromises.push(embedPromise); - } - }); - - Ember.RSVP.all(relationshipPromises).then(function () { - resolve(record); - }); - }); - }, - - - /** - * Given the following payload, - * - * { - * cart: { - * id: "1", - * customer: "2" - * } - * } - * - * With `relationshipName` being `customer` and `relationshipRecord` - * - * {id: "2", name: "Rambo"} - * - * This method returns the following payload: - * - * { - * cart: { - * id: "1", - * customer: "2" - * }, - * _embedded: { - * customer: { - * id: "2", - * name: "Rambo" - * } - * } - * } - * - * which is then treated by the serializer later. - * - * @method addEmbeddedPayload - * @private - * @param {Object} payload - * @param {String} relationshipName - * @param {Object} relationshipRecord - */ - addEmbeddedPayload: function (payload, relationshipName, relationshipRecord) { - var objectHasId = (relationshipRecord && relationshipRecord.id), - arrayHasIds = (relationshipRecord.length && relationshipRecord.isEvery("id")), - isValidRelationship = (objectHasId || arrayHasIds); - - if (isValidRelationship) { - if (!payload._embedded) { - payload._embedded = {}; - } - - payload._embedded[relationshipName] = relationshipRecord; - if (relationshipRecord.length) { - payload[relationshipName] = relationshipRecord.mapBy('id'); - } else { - payload[relationshipName] = relationshipRecord.id; - } - } - - if (this.isArray(payload[relationshipName])) { - payload[relationshipName] = payload[relationshipName].filter(function (id) { - return id; - }); - } - - return payload; - }, - - - isArray: function (value) { - return Object.prototype.toString.call(value) === '[object Array]'; - }, - - /** - * Same as `loadRelationships`, but for an array of records. - * - * @method loadRelationshipsForMany - * @private - * @param {DS.Store} store - * @param {DS.Model} type - * @param {Object} recordsArray - */ - loadRelationshipsForMany: function (store, type, recordsArray) { - return new Ember.RSVP.Promise((resolve, reject) => { - var recordsWithRelationships = [], - recordsToBeLoaded = [], - promises = []; - - /** - * Some times Ember puts some stuff in arrays. We want to clean it so - * we know exactly what to iterate over. - */ - for (var i in recordsArray) { - if (recordsArray.hasOwnProperty(i)) { - recordsToBeLoaded.push(recordsArray[i]); - } - } - - var loadNextRecord = (record) => { - /** - * Removes the first item from recordsToBeLoaded - */ - recordsToBeLoaded = recordsToBeLoaded.slice(1); - - var promise = this.loadRelationships(store, type, record); - - promise.then(function (recordWithRelationships) { - recordsWithRelationships.push(recordWithRelationships); - - if (recordsToBeLoaded[0]) { - loadNextRecord(recordsToBeLoaded[0]); - } else { - resolve(recordsWithRelationships); - } - }); - }; - - /** - * We start by the first record - */ - loadNextRecord(recordsToBeLoaded[0]); - }); - }, - - - /** - * - * @method relationshipProperties - * @private - * @param {DS.Model} type - * @param {String} relationName - */ - relationshipProperties: function (type, relationName) { - var relationships = Ember.get(type, 'relationshipsByName'); - if (relationName) { - return relationships.get(relationName); - } else { - return relationships; - } - }, - - isEmbeddedAlways: function (store, modelName, relationKey) { - if (store === undefined || store === null) { - return false; - } - - var serializer = store.serializerFor(modelName); - return typeof(serializer.hasEmbeddedAlwaysOption) === 'function' && - serializer.hasEmbeddedAlwaysOption(relationKey); - } }); function updateOrCreate(store, type, snapshot) { diff --git a/addon/serializers/localforage.js b/addon/serializers/localforage.js index b6d6df0..d2701e1 100644 --- a/addon/serializers/localforage.js +++ b/addon/serializers/localforage.js @@ -3,8 +3,6 @@ import DS from 'ember-data'; export default DS.JSONSerializer.extend({ - isNewSerializerAPI: true, - _shouldSerializeHasMany: function (snapshot, key, relationship) { var relationshipType = snapshot.type.determineRelationshipType(relationship, this.store); if (this._mustSerialize(key)) { @@ -39,79 +37,39 @@ export default DS.JSONSerializer.extend({ } }, - /** - * Normalize whatever was returned from the adapter. - * - * If the adapter returns relationships in an embedded way, such as follows: - * - * ```js - * { - * "id": 1, - * "title": "Rails Rambo", - * - * "_embedded": { - * "comment": [{ - * "id": 1, - * "comment_title": "FIRST" - * }, { - * "id": 2, - * "comment_title": "Rails is unagi" - * }] - * } - * } - * - * this method will create separated JSON for each resource and then push - * them individually to the Store. - * - * In the end, only the main resource will remain, containing the ids of its - * relationships. Given the relations are already in the Store, we will - * return a JSON with the main resource alone. The Store will sort out the - * associations by itself. - * - * @method normalize - * @param {DS.Model} primaryModelClass the type/model - * @param {Object} payload returned JSON - */ - normalize: function (primaryModelClass, payload) { - var normalizedPayload = this._normalizeEmbeddedPayload(primaryModelClass, payload); - return this._super(primaryModelClass, normalizedPayload); - }, - - _normalizeEmbeddedPayload: function (primaryModelClass, payload) { - if (payload && payload._embedded) { - for (var relation in payload._embedded) { - var relModelClass = primaryModelClass.typeForRelationship(relation, this.store); - var typeName = relModelClass.modelName, - embeddedPayload = payload._embedded[relation]; + // Remove the undefined hasMany relationships which will fail at normalization + // (see https://github.com/emberjs/data/issues/3736) + // TODO: this override will be unecessary after merge of the following PR: + // https://github.com/emberjs/data/pull/3747 + extractRelationships: function(modelClass, resourceHash) { + let relationships = {}; - if (embeddedPayload) { - var relSerializer = this.store.serializerFor(typeName); - if (Ember.isArray(embeddedPayload)) { - for (var i = 0; i < embeddedPayload.length; i++) { - this.store.push(relSerializer.normalize(relModelClass, embeddedPayload[i])); - } - } else { - this.store.push(relSerializer.normalize(relModelClass, embeddedPayload)); - } + modelClass.eachRelationship((key, relationshipMeta) => { + let relationship = null; + let relationshipKey = this.keyForRelationship(key, relationshipMeta.kind, 'deserialize'); + if (resourceHash.hasOwnProperty(relationshipKey)) { + let data = null; + let relationshipHash = resourceHash[relationshipKey]; + if (relationshipMeta.kind === 'belongsTo') { + data = this.extractRelationship(relationshipMeta.type, relationshipHash); + } else if (relationshipMeta.kind === 'hasMany') { + data = Ember.isNone(relationshipHash) ? null : relationshipHash.map((item) => this.extractRelationship(relationshipMeta.type, item)); } + relationship = { data }; } - delete payload._embedded; - } - - // Remove the undefined hasMany relationships which will fail at normalization - // (see https://github.com/emberjs/data/issues/3736) - // TODO: this block will be unecessary after merge of the following PR: - // https://github.com/emberjs/data/pull/3747 - var relationshipNames = Ember.get(primaryModelClass, 'relationshipNames'); - var relationships = relationshipNames.hasMany; + let linkKey = this.keyForLink(key, relationshipMeta.kind); + if (resourceHash.links && resourceHash.links.hasOwnProperty(linkKey)) { + let related = resourceHash.links[linkKey]; + relationship = relationship || {}; + relationship.links = { related }; + } - relationships.forEach((relationName) => { - if (Ember.isNone(payload[relationName])) { - delete payload[relationName]; + if (relationship) { + relationships[key] = relationship; } }); - return payload; - }, + return relationships; + } }); diff --git a/tests/dummy/app/models/item.js b/tests/dummy/app/models/item.js index ac3c87b..311abf4 100644 --- a/tests/dummy/app/models/item.js +++ b/tests/dummy/app/models/item.js @@ -5,5 +5,5 @@ var belongsTo = DS.belongsTo; export default DS.Model.extend({ name: attr('string'), - list: belongsTo('list', { async: false }) + list: belongsTo('list') }); \ No newline at end of file diff --git a/tests/dummy/app/models/ledger.js b/tests/dummy/app/models/ledger.js index 88c89d2..dffd7b7 100644 --- a/tests/dummy/app/models/ledger.js +++ b/tests/dummy/app/models/ledger.js @@ -3,6 +3,6 @@ import DS from 'ember-data'; export default DS.Model.extend({ title: DS.attr('string'), - purchases: DS.hasMany('purchase', {async: true}), - players: DS.hasMany('player', {async: true}) + purchases: DS.hasMany('purchase'), + players: DS.hasMany('player') }); \ No newline at end of file diff --git a/tests/dummy/app/models/list.js b/tests/dummy/app/models/list.js index 1c7d815..048bb7b 100644 --- a/tests/dummy/app/models/list.js +++ b/tests/dummy/app/models/list.js @@ -6,6 +6,6 @@ var hasMany = DS.hasMany; export default DS.Model.extend({ name: attr('string'), b: attr('boolean'), - items: hasMany('item', { async: false }), + items: hasMany('item'), day: attr('day') }); \ No newline at end of file diff --git a/tests/dummy/app/models/order.js b/tests/dummy/app/models/order.js index a40afeb..076387e 100644 --- a/tests/dummy/app/models/order.js +++ b/tests/dummy/app/models/order.js @@ -6,5 +6,5 @@ var hasMany = DS.hasMany; export default DS.Model.extend({ name: attr('string'), b: attr('boolean'), - hours: hasMany('hour', { async: false }) + hours: hasMany('hour') }); diff --git a/tests/dummy/app/models/player.js b/tests/dummy/app/models/player.js index f2f167e..49765e2 100644 --- a/tests/dummy/app/models/player.js +++ b/tests/dummy/app/models/player.js @@ -4,6 +4,6 @@ export default DS.Model.extend({ name: DS.attr('string'), balance: DS.attr('number'), - ledger: DS.belongsTo('ledger',{ async: true }), - purchases: DS.hasMany('purchase', {async: true}) + ledger: DS.belongsTo('ledger'), + purchases: DS.hasMany('purchase') }); \ No newline at end of file diff --git a/tests/dummy/app/models/purchase.js b/tests/dummy/app/models/purchase.js index ba6a1a5..4bf98f4 100644 --- a/tests/dummy/app/models/purchase.js +++ b/tests/dummy/app/models/purchase.js @@ -4,6 +4,6 @@ export default DS.Model.extend({ name: DS.attr('string'), amount: DS.attr('number'), - ledger: DS.belongsTo('ledger', {async: true}), - player: DS.belongsTo('player', {async: true}) + ledger: DS.belongsTo('ledger'), + player: DS.belongsTo('player') }); \ No newline at end of file diff --git a/tests/integration/crud-test.js b/tests/integration/crud-test.js index 1dd390f..7d5bf4a 100644 --- a/tests/integration/crud-test.js +++ b/tests/integration/crud-test.js @@ -1,5 +1,8 @@ import Ember from 'ember'; -import { test } from 'ember-qunit'; +import { + test +} +from 'ember-qunit'; import startApp from '../helpers/start-app'; import FIXTURES from '../helpers/fixtures/crud'; @@ -11,16 +14,16 @@ var run = Ember.run; var get = Ember.get; var set = Ember.set; -module('CRUD', { - setup: function () { +module("CRUD", { + setup: function() { stop(); - run(function () { - window.localforage.setItem('DS.LFAdapter', FIXTURES).then(function () { + run(function() { + window.localforage.setItem('DS.LFAdapter', FIXTURES).then(function() { start(); }); }); - run(function () { + run(function() { App = startApp(); store = App.__container__.lookup('service:store'); adapter = App.__container__.lookup('adapter:application'); @@ -28,239 +31,91 @@ module('CRUD', { }); }, - teardown: function () { + teardown: function() { run(App, 'destroy'); } }); +// Lifecycle methods +// ----------------------------------------------------------------------------- -test('findRecord with id', function () { - expect(4); - - stop(); - run(function () { - store.findRecord('list', 'l1').then(function (list) { - equal(get(list, 'id'), 'l1', 'id is loaded correctly'); - equal(get(list, 'name'), 'one', 'name is loaded correctly'); - equal(get(list, 'b'), true, 'b is loaded correctly'); - equal(get(list, 'day'), 1, 'day is loaded correctly'); - start(); - }); - }); -}); - - -test('query', function () { - - stop(); - run(function () { - store.query('list', {name: /one|two/}).then(function (records) { - equal(get(records, 'length'), 2, 'found results for /one|two/'); - start(); - }); - }); - - stop(); - run(function () { - store.query('list', {name: /.+/, id: /l1/}).then(function (records) { - equal(get(records, 'length'), 1, 'found results for {name: /.+/, id: /l1/}'); - start(); - }); - }); - - stop(); - run(function () { - store.query('list', {name: 'one'}).then(function (records) { - equal(get(records, 'length'), 1, 'found results for name "one"'); - start(); - }); - }); - - stop(); - run(function () { - store.query('list', {b: true}).then(function (records) { - equal(get(records, 'length'), 1, 'found results for {b: true}'); - start(); - }); - }); - - stop(); - run(function () { - store.query('list', {name: 'two', b: false}).then(function (records) { - equal(get(records, 'length'), 1, 'found results for multiple criteria'); - start(); - }); - }); - - stop(); - run(function () { - store.query('list', {name: 'four', b: false}).then(function (records) { - equal(get(records, 'length'), 0, 'found no results when only criteria matches'); - start(); - }); - }); - - stop(); - run(function () { - store.query('list', {whatever: "dude"}).then(function (records) { - equal(get(records, 'length'), 0, 'didn\'t find results for nonsense'); - start(); - }); - }); -}); - - -test('queryRecord', function () { - +test("push", function() { + expect(3); stop(); - run(function () { - store.queryRecord('list', {name: 'one'}).then(function (list) { - equal(get(list, 'id'), 'l1', 'id is loaded correctly'); - equal(get(list, 'name'), 'one', 'name is loaded correctly'); - equal(get(list, 'b'), true, 'b is loaded correctly'); - equal(get(list, 'day'), 1, 'day is loaded correctly'); - start(); - }); - }); - stop(); - run(function () { - store.queryRecord('list', {whatever: "dude"}).catch(function (err) { - ok(true, "didn't find record for nonsense"); - start(); + run(function() { + var list = store.push({ + type: 'list', + id: adapter.generateIdForRecord(), + attributes: { + name: 'Rambo' } - ); - }); -}); - -test('findAll', function () { - expect(7); - - stop(); - run(function () { - store.findAll('list').then(function (records) { - var firstRecord = records.objectAt(0), - secondRecord = records.objectAt(1), - thirdRecord = records.objectAt(2); - - equal(get(records, 'length'), 3, "3 items were found"); - - equal(get(firstRecord, 'name'), "one", "First item's name is one"); - equal(get(secondRecord, 'name'), "two", "Second item's name is two"); - equal(get(thirdRecord, 'name'), "three", "Third item's name is three"); - - equal(get(firstRecord, 'day'), 1, "First item's day is 1"); - equal(get(secondRecord, 'day'), 2, "Second item's day is 2"); - equal(get(thirdRecord, 'day'), 3, "Third item's day is 3"); - - start(); }); - }); -}); - - -test('queryMany', function () { - expect(11); - stop(); - run(function () { - store.query('order', {b: true}).then(function (records) { - var firstRecord = records.objectAt(0), - secondRecord = records.objectAt(1), - thirdRecord = records.objectAt(2); - - equal(get(records, 'length'), 3, "3 orders were found"); - equal(get(firstRecord, 'name'), "one", "First order's name is one"); - equal(get(secondRecord, 'name'), "three", "Second order's name is three"); - equal(get(thirdRecord, 'name'), "four", "Third order's name is four"); - var firstHours = firstRecord.get('hours'), - secondHours = secondRecord.get('hours'), - thirdHours = thirdRecord.get('hours'); - - equal(get(firstHours, 'length'), 2, "Order one has two hours"); - equal(get(secondHours, 'length'), 2, "Order three has two hours"); - equal(get(thirdHours, 'length'), 0, "Order four has no hours"); - - var hourOne = firstHours.objectAt(0), - hourTwo = firstHours.objectAt(1), - hourThree = secondHours.objectAt(0), - hourFour = secondHours.objectAt(1); - equal(get(hourOne, 'amount'), 4, "Hour one has amount of 4"); - equal(get(hourTwo, 'amount'), 3, "Hour two has amount of 3"); - equal(get(hourThree, 'amount'), 2, "Hour three has amount of 2"); - equal(get(hourFour, 'amount'), 1, "Hour four has amount of 1"); + list.save().then(function(record) { + return store.query('list', { + name: 'Rambo' + }); + }).then(function(records) { + var record = records.objectAt(0); + equal(get(records, 'length'), 1, "Only Rambo was found"); + equal(get(record, 'name'), "Rambo", "Correct name"); + equal(get(record, 'id'), list.id, "Correct, original id"); start(); }); }); }); -test('push', function () { +test("createRecord", function() { expect(3); stop(); - run(function () { - var list = store.push({type: 'list', id: adapter.generateIdForRecord(), attributes: {name: 'Rambo'}}); - - list.save().then(function (record) { - - - store.query('list', {name: 'Rambo'}).then(function (records) { - var record = records.objectAt(0); - - equal(get(records, 'length'), 1, "Only Rambo was found"); - equal(get(record, 'name'), "Rambo", "Correct name"); - equal(get(record, 'id'), list.id, "Correct, original id"); - start(); - }); + run(function() { + var list = store.createRecord('list', { + name: 'Rambo' }); - }); -}); - -test('createRecord', function () { - expect(3); - stop(); - - run(function () { - var list = store.createRecord('list', {name: 'Rambo'}); - - list.save().then(function (record) { - - store.query('list', {name: 'Rambo'}).then(function (records) { - var record = records.objectAt(0); - - equal(get(records, 'length'), 1, "Only Rambo was found"); - equal(get(record, 'name'), "Rambo", "Correct name"); - equal(get(record, 'id'), list.id, "Correct, original id"); - start(); + list.save().then(function(record) { + return store.query('list', { + name: 'Rambo' }); + }).then(function(records) { + var record = records.objectAt(0); + equal(get(records, 'length'), 1, "Only Rambo was found"); + equal(get(record, 'name'), "Rambo", "Correct name"); + equal(get(record, 'id'), list.id, "Correct, original id"); + start(); }); }); }); -test('updateRecords', function () { +test("updateRecord", function() { expect(3); stop(); - run(function () { - var list = store.createRecord('list', {name: 'Rambo'}); + run(function() { + var list = store.createRecord('list', { + name: 'Rambo' + }); - var UpdateList = function (list) { - return store.query('list', {name: 'Rambo'}).then(function (records) { + var UpdateList = function(list) { + return store.query('list', { + name: 'Rambo' + }).then(function(records) { var record = records.objectAt(0); record.set('name', 'Macgyver'); return record.save(); }); }; - var AssertListIsUpdated = function () { - return store.query('list', {name: 'Macgyver'}).then(function (records) { + var AssertListIsUpdated = function() { + return store.query('list', { + name: 'Macgyver' + }).then(function(records) { var record = records.objectAt(0); - equal(get(records, 'length'), 1, "Only one record was found"); equal(get(record, 'name'), "Macgyver", "Updated name shows up"); equal(get(record, 'id'), list.id, "Correct, original id"); - start(); }); }; @@ -269,24 +124,25 @@ test('updateRecords', function () { }); }); - -test('deleteRecord', function () { +test("deleteRecord", function() { expect(2); stop(); - run(function () { - var AssertListIsDeleted = function () { - return store.query('list', {name: 'one'}).then(function (records) { + run(function() { + var AssertListIsDeleted = function() { + return store.query('list', { + name: 'one' + }).then(function(records) { equal(get(records, 'length'), 0, "No record was found"); start(); }); }; - store.query('list', {name: 'one'}).then(function (lists) { + store.query('list', { + name: 'one' + }).then(function(lists) { var list = lists.objectAt(0); - equal(get(list, "id"), "l1", "Item exists"); - list.deleteRecord(); list.on("didDelete", AssertListIsDeleted); list.save(); @@ -294,270 +150,465 @@ test('deleteRecord', function () { }); }); -test('changes in bulk', function () { - stop(); - run(function () { +// Find methods +// ----------------------------------------------------------------------------- - var listToUpdate = new Ember.RSVP.Promise(function (resolve, reject) { - store.findRecord('list', 'l1').then(function (list) { - list.set('name', 'updated'); - list.save().then(function () { - resolve(); - }); - }); - }); +test("findAll", function() { + expect(7); - var listToCreate = new Ember.RSVP.Promise(function (resolve, reject) { - store.createRecord('list', {name: 'Rambo'}).save().then(function () { - resolve(); - }); - }); + stop(); + run(function() { + store.findAll('list').then(function(records) { + var firstRecord = records.objectAt(0); + var secondRecord = records.objectAt(1); + var thirdRecord = records.objectAt(2); - var listToDelete = new Ember.RSVP.Promise(function (resolve, reject) { - store.findRecord('list', 'l2').then(function (list) { - list.destroyRecord().then(function () { - resolve(); - }); - }); - }); + equal(get(records, 'length'), 3, "3 items were found"); - var promises = [ - listToUpdate, - listToCreate, - listToDelete - ]; + equal(get(firstRecord, 'name'), "one", "First item's name is one"); + equal(get(secondRecord, 'name'), "two", "Second item's name is two"); + equal(get(thirdRecord, 'name'), "three", "Third item's name is three"); - Ember.RSVP.all(promises).then(function () { + equal(get(firstRecord, 'day'), 1, "First item's day is 1"); + equal(get(secondRecord, 'day'), 2, "Second item's day is 2"); + equal(get(thirdRecord, 'day'), 3, "Third item's day is 3"); - promises = Ember.A(); + start(); + }); + }); +}); - promises.push( - new Ember.RSVP.Promise(function (resolve, reject) { - store.findRecord('list', 'l1').then(function (list) { - equal(get(list, 'name'), 'updated', "Record was updated successfully"); - resolve(); - }); - }) - ); +test("findRecord", function() { + expect(4); - promises.push( - new Ember.RSVP.Promise(function (resolve, reject) { - store.query('list', {name: 'Rambo'}).then(function (lists) { - equal(get(lists, 'length'), 1, "Record was created successfully"); - resolve(); - }); - }) - ); + stop(); + run(function() { + store.findRecord('list', 'l1').then(function(list) { + equal(get(list, 'id'), 'l1', "id is loaded correctly"); + equal(get(list, 'name'), 'one', "name is loaded correctly"); + equal(get(list, 'b'), true, "b is loaded correctly"); + equal(get(list, 'day'), 1, "day is loaded correctly"); + start(); + }); + }); +}); - promises.push( - new Ember.RSVP.Promise(function (resolve, reject) { - store.findRecord('list', 'l2').catch(function (err) { - ok(true, "Record was deleted successfully"); - resolve(); - } - ); - }) - ); +// Query methods +// ----------------------------------------------------------------------------- - Ember.RSVP.all(promises).then(function () { - start(); - }); +test("query", function() { + + stop(); + run(function() { + store.query('list', { + name: /one|two/ + }).then(function(records) { + equal(get(records, 'length'), 2, "found results for /one|two/"); + start(); }); }); -}); + stop(); + run(function() { + store.query('list', { + name: /.+/, + id: /l1/ + }).then(function(records) { + equal(get(records, 'length'), 1, "found results for { name: /.+/, id: /l1/ }"); + start(); + }); + }); -test('load hasMany association', function () { - expect(4); stop(); + run(function() { + store.query('list', { + name: 'one' + }).then(function(records) { + equal(get(records, 'length'), 1, "found results for name 'one'"); + start(); + }); + }); - run(function () { - store.findRecord('list', 'l1').then(function (list) { - var items = list.get('items'); + stop(); + run(function() { + store.query('list', { + b: true + }).then(function(records) { + equal(get(records, 'length'), 1, "found results for { b: true }"); + start(); + }); + }); - var item1 = items.get('firstObject'), - item2 = items.get('lastObject'); + stop(); + run(function() { + store.query('list', { + name: 'two', + b: false + }).then(function(records) { + equal(get(records, 'length'), 1, "found results for multiple criteria"); + start(); + }); + }); - equal(get(item1, 'id'), 'i1', 'first item id is loaded correctly'); - equal(get(item1, 'name'), 'one', 'first item name is loaded correctly'); - equal(get(item2, 'id'), 'i2', 'first item id is loaded correctly'); - equal(get(item2, 'name'), 'two', 'first item name is loaded correctly'); + stop(); + run(function() { + store.query('list', { + name: 'four', + b: false + }).then(function(records) { + equal(get(records, 'length'), 0, "found no results when only criteria matches"); + start(); + }); + }); + stop(); + run(function() { + store.query('list', { + whatever: "dude" + }).then(function(records) { + equal(get(records, 'length'), 0, "didn't find results for nonsense"); start(); }); }); }); +test("queryRecord", function() { -test('load belongsTo association', function () { stop(); - run(function () { - store.findRecord('item', 'i1').then(function (item) { - return new Ember.RSVP.Promise(function (resolve) { - resolve(get(item, 'list')); - }); - }).then(function (list) { + run(function() { + store.queryRecord('list', { + name: 'one' + }).then(function(list) { equal(get(list, 'id'), 'l1', "id is loaded correctly"); equal(get(list, 'name'), 'one', "name is loaded correctly"); - + equal(get(list, 'b'), true, "b is loaded correctly"); + equal(get(list, 'day'), 1, "day is loaded correctly"); start(); }); }); -}); - - -test('saves belongsTo', function () { - var item, - listId = 'l2'; stop(); - run(function () { - store.findRecord('list', listId).then(function (list) { - item = store.createRecord('item', {name: 'three thousand'}); - item.set('list', list); - - return item.save(); - }).then(function (item) { - store.unloadAll('item'); - return store.findRecord('item', item.get('id')); - }).then(function (item) { - var list = item.get('list'); - ok(item.get('list'), 'list is present'); - equal(list.id, listId, 'list is retrieved correctly'); + run(function() { + store.queryRecord('list', { + whatever: "dude" + }).catch(function(err) { + ok(true, "didn't find record for nonsense"); start(); }); }); }); -test('saves hasMany', function () { - var item, list, - listId = 'l2'; +// Relationship loading +//------------------------------------------------------------------------------ +test("load hasMany relationships when finding a single record", function() { + expect(4); stop(); - run(function () { - store.findRecord('list', listId).then(function (list) { - item = store.createRecord('item', {name: 'three thousand'}); - list.get('items').pushObject(item); - - return list.save(); - }).then(function (list) { - return item.save(); - }).then(function (item) { - store.unloadAll('list'); - return store.findRecord('list', listId); - }).then(function (list) { - var items = list.get('items'), - item1 = items.objectAt(0); + run(function() { + store.findRecord('list', 'l1').then(function(list) { + list.get('items').then(function(items) { + var item1 = items.get('firstObject'); + var item2 = items.get('lastObject'); + equal(get(item1, 'id'), 'i1', "first item id is loaded correctly"); + equal(get(item1, 'name'), 'one', "first item name is loaded correctly"); + equal(get(item2, 'id'), 'i2', "first item id is loaded correctly"); + equal(get(item2, 'name'), 'two', "first item name is loaded correctly"); + start(); + }); + }); + }); +}); - equal(item1.get('name'), 'three thousand', 'item is saved'); - start(); +test("load belongsTo relationships when finding a single record", function() { + stop(); + run(function() { + store.findRecord('item', 'i1').then(function(item) { + item.get('list').then(function(list) { + equal(get(list, 'id'), 'l1', "id is loaded correctly"); + equal(get(list, 'name'), 'one', "name is loaded correctly"); + start(); + }); }); }); }); -test("loads embedded hasMany in a 'find with id' operation", function () { +test("load embedded hasMany relationships when finding a single record", function() { expect(5); stop(); - run(function () { - store.findRecord('customer', '1').then(function (customer) { + run(function() { + store.findRecord('customer', '1').then(function(customer) { var addresses = customer.get('addresses'); - equal(addresses.length, 2); - var address1 = addresses.get('firstObject'), - address2 = addresses.get('lastObject'); + var address1 = addresses.get('firstObject'); + var address2 = addresses.get('lastObject'); equal(get(address1, 'id'), '1', - 'first address id is loaded correctly'); + "first address id is loaded correctly"); equal(get(address1, 'addressNumber'), '12345', - 'first address number is loaded correctly'); + "first address number is loaded correctly"); equal(get(address2, 'id'), '2', - 'first address id is loaded correctly'); + "first address id is loaded correctly"); equal(get(address2, 'addressNumber'), '54321', - 'first address number is loaded correctly'); + "first address number is loaded correctly"); start(); }); }); }); -test("loads embedded hasMany in a 'find all' operation", function () { +test("load embedded hasMany relationships when finding multiple records", function() { expect(6); stop(); - run(function () { - store.findAll('customer').then(function (customers) { + run(function() { + store.findAll('customer').then(function(customers) { equal(get(customers, 'length'), 1, 'one customer was retrieved'); var customer = customers.objectAt(0); var addresses = customer.get('addresses'); - equal(addresses.length, 2); - var address1 = addresses.get('firstObject'), - address2 = addresses.get('lastObject'); + var address1 = addresses.get('firstObject'); + var address2 = addresses.get('lastObject'); equal(get(address1, 'id'), '1', - 'first address id is loaded correctly'); + "first address id is loaded correctly"); equal(get(address1, 'addressNumber'), '12345', - 'first address number is loaded correctly'); + "first address number is loaded correctly"); equal(get(address2, 'id'), '2', - 'first address id is loaded correctly'); + "first address id is loaded correctly"); equal(get(address2, 'addressNumber'), '54321', - 'first address number is loaded correctly'); + "first address number is loaded correctly"); start(); }); }); }); -test("loads embedded hasMany in a 'find many' operation", function () { +test("load embedded hasMany relationships when querying multiple records", function() { expect(6); stop(); - run(function () { - store.query('customer', {customerNumber: '123'}).then(function (customers) { + run(function() { + store.query('customer', { + customerNumber: '123' + }).then(function(customers) { equal(get(customers, 'length'), 1); var customer = customers.objectAt(0); var addresses = customer.get('addresses'); - equal(addresses.length, 2); - var address1 = addresses.get('firstObject'), - address2 = addresses.get('lastObject'); + var address1 = addresses.get('firstObject'); + var address2 = addresses.get('lastObject'); equal(get(address1, 'id'), '1', - 'first address id is loaded correctly'); + "first address id is loaded correctly"); equal(get(address1, 'addressNumber'), '12345', - 'first address number is loaded correctly'); + "first address number is loaded correctly"); equal(get(address2, 'id'), '2', - 'first address id is loaded correctly'); + "first address id is loaded correctly"); equal(get(address2, 'addressNumber'), '54321', - 'first address number is loaded correctly'); + "first address number is loaded correctly"); start(); }); }); }); -test("loads embedded belongsTo in a 'find with id' operation", function () { +test("load embedded belongsTo relationships when finding a single record", function() { expect(2); stop(); - run(function () { - store.findRecord('customer', '1').then(function (customer) { + run(function() { + store.findRecord('customer', '1').then(function(customer) { var hour = customer.get('hour'); - equal(get(hour, 'id'), 'h5', - 'hour id is loaded correctly'); + "hour id is loaded correctly"); equal(get(hour, 'name'), 'five', - 'hour name is loaded correctly'); + "hour name is loaded correctly"); start(); }); }); }); + +test("load hasMany relationships when querying multiple records", function() { + expect(11); + stop(); + run(function() { + store.query('order', { + b: true + }).then(function(records) { + var firstRecord = records.objectAt(0); + var secondRecord = records.objectAt(1); + var thirdRecord = records.objectAt(2); + equal(get(records, 'length'), 3, "3 orders were found"); + equal(get(firstRecord, 'name'), "one", "First order's name is one"); + equal(get(secondRecord, 'name'), "three", "Second order's name is three"); + equal(get(thirdRecord, 'name'), "four", "Third order's name is four"); + + + Ember.RSVP.all([ + firstRecord.get('hours'), + secondRecord.get('hours'), + thirdRecord.get('hours') + ]).then(function(hours) { + var firstHours = hours[0]; + var secondHours = hours[1]; + var thirdHours = hours[2]; + equal(get(firstHours, 'length'), 2, "Order one has two hours"); + equal(get(secondHours, 'length'), 2, "Order three has two hours"); + equal(get(thirdHours, 'length'), 0, "Order four has no hours"); + + var hourOne = firstHours.objectAt(0); + var hourTwo = firstHours.objectAt(1); + var hourThree = secondHours.objectAt(0); + var hourFour = secondHours.objectAt(1); + equal(get(hourOne, 'amount'), 4, "Hour one has amount of 4"); + equal(get(hourTwo, 'amount'), 3, "Hour two has amount of 3"); + equal(get(hourThree, 'amount'), 2, "Hour three has amount of 2"); + equal(get(hourFour, 'amount'), 1, "Hour four has amount of 1"); + + start(); + }); + }); + }); +}); + +// Relationship saving +//------------------------------------------------------------------------------ + +test("save belongsTo relationships", function() { + var listId = 'l2'; + + stop(); + run(function() { + store.findRecord('list', listId).then(function(list) { + var item = store.createRecord('item', { + name: 'three thousand' + }); + item.set('list', list); + return item.save(); + }).then(function(item) { + store.unloadAll('item'); + return store.findRecord('item', item.get('id')); + }).then(function(item) { + item.get('list').then(function(list) { + ok(item.get('list'), "list is present"); + equal(list.id, listId, "list is retrieved correctly"); + start(); + }); + }); + }); +}); + +test("save hasMany relationships", function() { + var listId = 'l2'; + + stop(); + run(function() { + store.findRecord('list', listId).then(function(list) { + var item = store.createRecord('item', { + name: 'three thousand' + }); + return list.get('items').then(function(items) { + items.pushObject(item); + return item.save().then(function() { + return list.save(); + }); + }); + }).then(function() { + store.unloadAll('list'); + return store.findRecord('list', listId); + }).then(function(list) { + list.get('items').then(function(items) { + var item1 = items.objectAt(0); + equal(item1.get('name'), 'three thousand', "item is saved"); + start(); + }); + }); + }); +}); + +// Bulk operations +//------------------------------------------------------------------------------ + +test("perform multiple changes in bulk", function() { + stop(); + run(function() { + + var listToUpdate = new Ember.RSVP.Promise(function(resolve, reject) { + store.findRecord('list', 'l1').then(function(list) { + list.set('name', 'updated'); + list.save().then(function() { + resolve(); + }); + }); + }); + + var listToCreate = new Ember.RSVP.Promise(function(resolve, reject) { + store.createRecord('list', { + name: 'Rambo' + }).save().then(function() { + resolve(); + }); + }); + + var listToDelete = new Ember.RSVP.Promise(function(resolve, reject) { + store.findRecord('list', 'l2').then(function(list) { + list.destroyRecord().then(function() { + resolve(); + }); + }); + }); + + var promises = [ + listToUpdate, + listToCreate, + listToDelete + ]; + + Ember.RSVP.all(promises).then(function() { + + promises = Ember.A(); + + promises.push( + new Ember.RSVP.Promise(function(resolve, reject) { + store.findRecord('list', 'l1').then(function(list) { + equal(get(list, 'name'), 'updated', "Record was updated successfully"); + resolve(); + }); + }) + ); + + promises.push( + new Ember.RSVP.Promise(function(resolve, reject) { + store.query('list', { + name: 'Rambo' + }).then(function(lists) { + equal(get(lists, 'length'), 1, "Record was created successfully"); + resolve(); + }); + }) + ); + + promises.push( + new Ember.RSVP.Promise(function(resolve, reject) { + store.findRecord('list', 'l2').catch(function(err) { + ok(true, "Record was deleted successfully"); + resolve(); + }); + }) + ); + + Ember.RSVP.all(promises).then(function() { + start(); + }); + }); + }); +}); \ No newline at end of file diff --git a/tests/integration/display-deep-model-test.js b/tests/integration/display-deep-model-test.js index cf12e6a..a134124 100644 --- a/tests/integration/display-deep-model-test.js +++ b/tests/integration/display-deep-model-test.js @@ -38,9 +38,13 @@ test('find customer -> hour -> order', function () { visit('/purchase/1'); andThen(function () { - equal(find('div.name').text(), 'credits'); - equal(find('div.amount').text(), '10'); - equal(find('div.player').text(), 'one'); - equal(find('div.ledger').text(), 'payable'); + stop(); + run.later(function() { + equal(find('div.name').text(), 'credits'); + equal(find('div.amount').text(), '10'); + equal(find('div.player').text(), 'one'); + equal(find('div.ledger').text(), 'payable'); + start(); + }, 300); }); }); \ No newline at end of file