diff --git a/.travis.yml b/.travis.yml index e8630cad..4cf67870 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ env: - EMBER_TRY_SCENARIO=ember-1.10 - EMBER_TRY_SCENARIO=ember-1.11 - EMBER_TRY_SCENARIO=ember-1.12 + - EMBER_TRY_SCENARIO=ember-1.13 - EMBER_TRY_SCENARIO=ember-release - EMBER_TRY_SCENARIO=ember-beta - EMBER_TRY_SCENARIO=ember-canary @@ -20,6 +21,7 @@ env: matrix: fast_finish: true allow_failures: + - env: EMBER_TRY_SCENARIO=ember-release - env: EMBER_TRY_SCENARIO=ember-beta - env: EMBER_TRY_SCENARIO=ember-canary diff --git a/Brocfile.js b/Brocfile.js deleted file mode 100644 index 87a43945..00000000 --- a/Brocfile.js +++ /dev/null @@ -1,23 +0,0 @@ -/* jshint node: true */ -/* global require, module */ - -var EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); - -var app = new EmberAddon(); - -app.import('bower_components/babel-polyfill/browser-polyfill.js'); - -// Use `app.import` to add additional libraries to the generated -// output files. -// -// If you need to use different assets in different -// environments, specify an object as the first parameter. That -// object's keys should be the environment name and the values -// should be the asset to use in that environment. -// -// If the library that you are including contains AMD or ES6 -// modules that you would like to import into your application -// please specify an object with the list of modules as keys -// along with the exports of each module as its value. - -module.exports = app.toTree(); diff --git a/addon/mixins/route.js b/addon/mixins/route.js index 1e0b7ad3..17f60e84 100644 --- a/addon/mixins/route.js +++ b/addon/mixins/route.js @@ -1,7 +1,7 @@ import Ember from 'ember'; +import { emberDataVersionIs } from 'ember-version-is'; -const { get } = Ember; - +const keys = Object.keys || Ember.keys; /** The Ember Infinity Route Mixin enables an application route to load paginated records for the route `model` as triggered by the controller (or Infinity Loader @@ -98,9 +98,18 @@ export default Ember.Mixin.create({ * Path of the "total pages" param in * the HTTP response * @type {String} - * @default "meta.total_pages" + * @default "meta.total_pages" */ totalPagesParam: 'meta.total_pages', + + /** + * The supported findMethod name for + * the developers Ember Data version. + * Provided here for backwards compat. + * @type {String} + * @default "query" + */ + _storeFindMethod: 'query', /** @private @@ -126,10 +135,18 @@ export default Ember.Mixin.create({ */ infinityModel(modelName, options, boundParams) { - if (Ember.isEmpty(get(this, 'store')) || Ember.isEmpty(get(this, 'store').find)){ - throw new Ember.Error("Ember Data store is not available to infinityModel"); + if (emberDataVersionIs('greaterThan', '1.0.0-beta.19.2') && emberDataVersionIs('lessThan', '1.13.4')) { + throw new Ember.Error("Ember Infinity: You are using an unsupported version of Ember Data. Please upgrade to at least 1.13.4 or downgrade to 1.0.0-beta.19.2"); + } + + if (emberDataVersionIs('lessThan', '1.13.0')) { + this.set('_storeFindMethod', 'find'); + } + + if (Ember.isEmpty(this.store) || Ember.isEmpty(this.store[this._storeFindMethod])){ + throw new Ember.Error("Ember Infinity: Ember Data store is not available to infinityModel"); } else if (modelName === undefined) { - throw new Ember.Error("You must pass a Model Name to infinityModel"); + throw new Ember.Error("Ember Infinity: You must pass a Model Name to infinityModel"); } this.set('_infinityModelName', modelName); @@ -157,7 +174,7 @@ export default Ember.Mixin.create({ } var params = Ember.merge(requestPayloadBase, options); - var promise = get(this, 'store').find(modelName, params); + let promise = this.store[this._storeFindMethod](modelName, params); promise.then( infinityModel => { @@ -172,7 +189,7 @@ export default Ember.Mixin.create({ }); }, () => { - throw new Ember.Error("Could not fetch Infinity Model. Please check your serverside configuration."); + throw new Ember.Error("Ember Infinity: Could not fetch Infinity Model. Please check your serverside configuration."); } ); @@ -201,9 +218,9 @@ export default Ember.Mixin.create({ requestPayloadBase[this.get('pageParam')] = nextPage; options = this._includeBoundParams(options, boundParams); - - var params = Ember.merge(requestPayloadBase, options); - var promise = get(this, 'store').find(modelName, params); + var params = Ember.merge(requestPayloadBase, this.get('_extraParams')); + + let promise = this.store[this._storeFindMethod](modelName, params); promise.then( newObjects => { @@ -224,7 +241,7 @@ export default Ember.Mixin.create({ }, () => { this.set('_loadingMore', false); - throw new Ember.Error("Could not fetch Infinity Model. Please check your serverside configuration."); + throw new Ember.Error("Ember Infinity: Could not fetch Infinity Model. Please check your serverside configuration."); } ); } else { @@ -245,10 +262,8 @@ export default Ember.Mixin.create({ @return {Object} */ _includeBoundParams: function(options, boundParams) { - if (Ember.keys(boundParams).length > 0) { - Ember.keys(boundParams).forEach( (key) => { - options[key] = this.get(boundParams[key]); - }); + if (!Ember.isEmpty(boundParams)) { + keys(boundParams).forEach(k => options[k] = this.get(boundParams[k])); } return options; diff --git a/bower.json b/bower.json index a4a08b70..1ae0504b 100644 --- a/bower.json +++ b/bower.json @@ -1,19 +1,22 @@ { "name": "ember-infinity", "dependencies": { - "ember": "1.10.0", + "ember": "1.13.7", "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3", "ember-cli-test-loader": "ember-cli-test-loader#0.1.3", - "ember-data": "1.0.0-beta.19.1", + "ember-data": "1.13.8", "ember-load-initializers": "ember-cli/ember-load-initializers#0.1.5", - "ember-qunit": "0.4.1", + "ember-qunit": "0.4.9", "ember-qunit-notifications": "0.0.7", "ember-resolver": "~0.1.18", - "jquery": "^1.11.1", - "loader.js": "ember-cli/loader.js#3.2.0", - "pretender": "0.6.0", - "qunit": "~1.17.1", - "Faker": "~2.1.3", + "jquery": "^1.11.3", + "loader.js": "ember-cli/loader.js#3.2.1", + "qunit": "~1.18.0", + "pretender": "^0.9.0", + "Faker": "~3.0.1", "babel-polyfill": "~0.0.1" + }, + "resolutions": { + "FakeXMLHttpRequest": "~1.2.0" } } diff --git a/config/ember-try.js b/config/ember-try.js index 91ece79f..9fab8b98 100644 --- a/config/ember-try.js +++ b/config/ember-try.js @@ -7,46 +7,53 @@ module.exports = { { name: 'ember-1.10', dependencies: { - 'ember': '~1.10.0' + ember: '~1.10.0' } }, { name: 'ember-1.11', dependencies: { - 'ember': '~1.11.3' + ember: '~1.11.3' } }, { name: 'ember-1.12', dependencies: { - 'ember': '~1.12.0' + ember: '~1.12.0' + } + }, + { + name: 'ember-1.13', + dependencies: { + ember: '~1.13.0' } }, { name: 'ember-release', dependencies: { - 'ember': 'components/ember#release' + ember: 'components/ember#release' + }, resolutions: { - 'ember': 'release' + ember: 'release' } }, { name: 'ember-beta', dependencies: { - 'ember': 'components/ember#beta' + ember: 'components/ember#beta' }, resolutions: { - 'ember': 'beta' + ember: 'beta' } }, { name: 'ember-canary', dependencies: { - 'ember': 'components/ember#canary' + ember: 'components/ember#canary' }, resolutions: { - 'ember': 'canary' + ember: 'canary' } } ] diff --git a/index.js b/index.js index 2f2c3e11..07a8e3a6 100644 --- a/index.js +++ b/index.js @@ -12,9 +12,9 @@ module.exports = { included: function(app) { this.addons.forEach(function(addon){ - if (addon.name === "ember-version-is") { - addon.included.apply(addon, [app]); - } - }); + if (addon.name === "ember-version-is") { + addon.included.apply(addon, [app]); + } + }); } }; diff --git a/package.json b/package.json index 03b171a0..f67a9208 100644 --- a/package.json +++ b/package.json @@ -18,18 +18,18 @@ "author": "Hugh Francis", "license": "MIT", "devDependencies": { - "broccoli-asset-rev": "^2.0.2", - "ember-cli": "1.13.1", - "ember-cli-app-version": "~0.3.5", + "broccoli-asset-rev": "^2.1.2", + "ember-cli": "1.13.8", + "ember-cli-app-version": "0.5.0", "ember-cli-content-security-policy": "0.4.0", - "ember-cli-dependency-checker": "^1.0.0", + "ember-cli-dependency-checker": "^1.0.1", "ember-cli-github-pages": "0.0.6", "ember-cli-ic-ajax": "0.2.1", - "ember-cli-inject-live-reload": "^1.3.0", + "ember-cli-inject-live-reload": "^1.3.1", "ember-cli-pretender": "0.3.2", - "ember-cli-qunit": "0.3.15", - "ember-cli-uglify": "^1.0.1", - "ember-data": "1.0.0-beta.19.1", + "ember-cli-qunit": "^1.0.0", + "ember-cli-uglify": "^1.2.0", + "ember-data": "1.13.8", "ember-disable-prototype-extensions": "^1.0.0", "ember-disable-proxy-controllers": "^1.0.0", "ember-export-application-global": "^1.0.2", @@ -40,10 +40,10 @@ "ember-addon" ], "dependencies": { - "ember-cli-babel": "^5.0.0", + "ember-version-is": "0.0.3", + "ember-cli-babel": "^5.1.3", "ember-cli-htmlbars": "0.7.9", - "ember-cli-version-checker": "^1.0.2", - "ember-version-is": "0.0.3" + "ember-cli-version-checker": "^1.0.2" }, "ember-addon": { "configPath": "tests/dummy/config", diff --git a/tests/.jshintrc b/tests/.jshintrc index ea8b88f6..6ec0b7c1 100644 --- a/tests/.jshintrc +++ b/tests/.jshintrc @@ -26,7 +26,7 @@ "node": false, "browser": false, "boss": true, - "curly": false, + "curly": true, "debug": false, "devel": false, "eqeqeq": true, @@ -47,5 +47,6 @@ "strict": false, "white": false, "eqnull": true, - "esnext": true + "esnext": true, + "unused": true } diff --git a/tests/acceptance/infinity-route-without-meta-test.js b/tests/acceptance/infinity-route-without-meta-test.js index 91e68ad3..b8a1958e 100644 --- a/tests/acceptance/infinity-route-without-meta-test.js +++ b/tests/acceptance/infinity-route-without-meta-test.js @@ -9,7 +9,7 @@ module('Acceptance: Infinity Route', { setup() { App = startApp(); server = new Pretender(function() { - this.get('/posts', request => { + this.get('/posts', () => { var posts = [ { id: 1, name: "Squarepusher" }, { id: 2, name: "Aphex Twin" } diff --git a/tests/dummy/app/adapters/application.js b/tests/dummy/app/adapters/application.js new file mode 100644 index 00000000..be8f35d2 --- /dev/null +++ b/tests/dummy/app/adapters/application.js @@ -0,0 +1,5 @@ +import DS from 'ember-data'; + +export default DS.RESTAdapter.extend({ + shouldBackgroundReloadRecord() { return false; } +}); diff --git a/tests/dummy/app/app.js b/tests/dummy/app/app.js index 757df389..8d66b958 100644 --- a/tests/dummy/app/app.js +++ b/tests/dummy/app/app.js @@ -3,9 +3,11 @@ import Resolver from 'ember/resolver'; import loadInitializers from 'ember/load-initializers'; import config from './config/environment'; +var App; + Ember.MODEL_FACTORY_INJECTIONS = true; -var App = Ember.Application.extend({ +App = Ember.Application.extend({ modulePrefix: config.modulePrefix, podModulePrefix: config.podModulePrefix, Resolver: Resolver diff --git a/tests/dummy/app/routes/demo.js b/tests/dummy/app/routes/demo.js index 380c8ba2..100f5d6f 100644 --- a/tests/dummy/app/routes/demo.js +++ b/tests/dummy/app/routes/demo.js @@ -19,7 +19,6 @@ export default Ember.Route.extend(InfinityRoute, { var fakeData = generateFakeData(104); this.set('pretender', new Pretender()); this.get('pretender').get('/posts', request => { - var queryParams = request.queryParams; var fd = fakeData; var page = parseInt(request.queryParams.page, 10); var per = parseInt(request.queryParams.per_page, 10); diff --git a/tests/dummy/app/templates/demo.hbs b/tests/dummy/app/templates/demo.hbs index f8403fbb..8678181c 100644 --- a/tests/dummy/app/templates/demo.hbs +++ b/tests/dummy/app/templates/demo.hbs @@ -5,7 +5,7 @@

ember-infinity

- \ No newline at end of file + diff --git a/tests/helpers/start-app.js b/tests/helpers/start-app.js index e08b02d1..0f7aab1a 100644 --- a/tests/helpers/start-app.js +++ b/tests/helpers/start-app.js @@ -1,6 +1,5 @@ import Ember from 'ember'; import Application from '../../app'; -import Router from '../../router'; import config from '../../config/environment'; export default function startApp(attrs) { @@ -9,7 +8,7 @@ export default function startApp(attrs) { var attributes = Ember.merge({}, config.APP); attributes = Ember.merge(attributes, attrs); // use defaults, but you can override; - Ember.run(() => { + Ember.run(function() { application = Application.create(attributes); application.setupForTesting(); application.injectTestHelpers(); diff --git a/tests/unit/components/infinity-loader-test.js b/tests/unit/components/infinity-loader-test.js index 35697066..a02767e8 100644 --- a/tests/unit/components/infinity-loader-test.js +++ b/tests/unit/components/infinity-loader-test.js @@ -6,7 +6,9 @@ import { import Ember from 'ember'; import $ from 'jquery'; -moduleForComponent('infinity-loader'); +moduleForComponent('infinity-loader', { + unit: true +}); test('it renders', function(assert) { assert.expect(2); @@ -85,7 +87,7 @@ test('it uses the provided scrollable element', function(assert) { test('it throws error when scrollable element is not found', function(assert) { assert.expect(1); - var component = this.subject({scrollable: "#notfound"}); + assert.throws(function() { this.render(); }, Error, "Should raise error"); @@ -94,7 +96,7 @@ test('it throws error when scrollable element is not found', function(assert) { test('it throws error when multiple scrollable elements are found', function(assert) { assert.expect(1); $(document.body).append("
"); - var component = this.subject({scrollable: "div"}); + assert.throws(function() { this.render(); }, Error, "Should raise error"); diff --git a/tests/unit/mixins/route-test.js b/tests/unit/mixins/route-test.js index 52b9c3b6..fd9b8cf1 100644 --- a/tests/unit/mixins/route-test.js +++ b/tests/unit/mixins/route-test.js @@ -19,15 +19,33 @@ test('it can not use infinityModel without Ember Data Store', assert => { }); var route = RouteObject.create(); - var infinityError; - try { + assert.throws(() => { route.model(); - } catch(error) { - infinityError = error; - } + }, + /store is not available to infinityModel/, + 'It throws if a store property is not available to the Route.' + ); +}); + +test('it can not use infinityModel without the Store Property having the appropriate finder method', assert => { + var RouteObject = Ember.Route.extend(RouteMixin, { + store: { + notQuery() { + return null; + } + }, + model() { + return this.infinityModel('post'); + } + }); + var route = RouteObject.create(); - assert.ok(infinityError instanceof Error); - assert.equal(infinityError.message, "Ember Data store is not available to infinityModel"); + assert.throws(() => { + route.model(); + }, + /store is not available to infinityModel/, + 'It throws if the resolved store finder method is not availabe on the store.' + ); }); test('it can not use infinityModel without a Model Name', assert => { @@ -38,18 +56,15 @@ test('it can not use infinityModel without a Model Name', assert => { }); var route = RouteObject.create(); route.store = { - find() {} + query() {} }; - - var infinityError; - try { + + assert.throws(() => { route.model(); - } catch(error) { - infinityError = error; - } - - assert.ok(infinityError instanceof Error); - assert.equal(infinityError.message, "You must pass a Model Name to infinityModel"); + }, + /must pass a Model Name to infinityModel/, + 'It throws unless you pass a model name to the infinityModel function.' + ); }); test('it sets state before it reaches the end', assert => { @@ -62,7 +77,7 @@ test('it sets state before it reaches the end', assert => { var route = RouteObject.create(); var dummyStore = { - find() { + query() { return new Ember.RSVP.Promise(resolve => { Ember.run(this, resolve, Ember.Object.create({ items: [{id: 1, name: 'Test'}], @@ -101,7 +116,7 @@ test('it allows customizations of request params', assert => { var route = RouteObject.create(); var dummyStore = { - find(modelType, findQuery) { + query(modelType, findQuery) { assert.deepEqual(findQuery, {per: 25, p: 1}); return new Ember.RSVP.Promise(resolve => { Ember.run(this, resolve, Ember.Object.create({ @@ -131,7 +146,7 @@ test('it allows customizations of meta parsing params', assert => { var route = RouteObject.create(); var dummyStore = { - find(modelType, findQuery) { + query() { return new Ember.RSVP.Promise(resolve => { Ember.run(this, resolve, Ember.Object.create({ items: [{id: 1, name: 'Walter White'}], @@ -165,7 +180,7 @@ test('it sets state when it reaches the end', assert => { var route = RouteObject.create(); var dummyStore = { - find() { + query() { return new Ember.RSVP.Promise(resolve => { Ember.run(this, resolve, Ember.Object.create({ items: [{id: 1, name: 'Test'}], @@ -205,7 +220,7 @@ test('it uses extra params when loading more data', assert => { var route = RouteObject.create(); var dummyStore = { - find(name, params) { + query(name, params) { assert.equal('param', params.extra); return new Ember.RSVP.Promise(resolve => { Ember.run(this, resolve, Ember.Object.create({ @@ -266,7 +281,7 @@ test('it uses overridden params when loading more data', assert => { var route = RouteObject.create(); var dummyStore = { - find(name, params) { + query(name, params) { assert.equal(1, params.testPerPage); assert.ok(params.testPage); return new Ember.RSVP.Promise(resolve => { @@ -325,7 +340,7 @@ test('it uses bound params when loading more data', assert => { var route = RouteObject.create(); var dummyStore = { - find(name, params) { + query(name, params) { assert.equal(route.get('test'), params.category, 'dynamic param is equal to the value of the computed property'); return new Ember.RSVP.Promise(resolve => { Ember.run(this, resolve, Ember.Object.create({ @@ -391,7 +406,7 @@ test('it allows overrides/manual invocations of updateInfinityModel', assert => ]; var dummyStore = { - find(modelType, findQuery) { + query(modelType, findQuery) { var item = items[findQuery.page-1]; return new Ember.RSVP.Promise(resolve => { Ember.run(this, resolve, Ember.ArrayProxy.create({ @@ -440,3 +455,80 @@ test('it allows overrides/manual invocations of updateInfinityModel', assert => assert.equal(model.get('content.length'), 3); assert.equal(model.get('content.lastObject.title'), 'Tender Is the Night', 'updateInfinityModel can be invoked manually'); }); + +/* + * Compatibility Tests + */ +var dummyStore = { + _dummyFetch(modelType, findQuery) { + var items = [ + { id: 1, title: 'The Great Gatsby' }, + { id: 2, title: 'The Last Tycoon' } + ]; + var item = items[findQuery.page-1]; + return new Ember.RSVP.Promise(resolve => { + Ember.run(this, resolve, Ember.ArrayProxy.create({ + content: Ember.A([item]), + meta: { total_pages: 2 } + })); + }); + }, + query(modelType, findQuery) { + return this._dummyFetch(modelType, findQuery); + }, + find(modelType, findQuery) { + return this._dummyFetch(modelType, findQuery); + } +}; + +test('It uses Query for ED >= 1.13.4', assert => { + + var RouteObject = Ember.Route.extend(RouteMixin, { + store: dummyStore, + model() { + return this.infinityModel('item', { perPage: 1 }); + } + }); + + var route = RouteObject.create(); + + DS.VERSION = "1.13.4"; + return route.model().then(function() { + assert.equal(route.get('_storeFindMethod'), 'query'); + }); +}); + +test('It uses Find for ED <= 1.0.0-beta.19.2', assert => { + var RouteObject = Ember.Route.extend(RouteMixin, { + store: dummyStore, + model() { + return this.infinityModel('item', { perPage: 1 }); + } + }); + + var route = RouteObject.create(); + + DS.VERSION = "1.0.0-beta.19.2"; + return route.model().then(function() { + assert.equal(route.get('_storeFindMethod'), 'find'); + }); +}); + +test('It explodes when using an unsupported ED', assert => { + var RouteObject = Ember.Route.extend(RouteMixin, { + store: dummyStore, + model() { + return this.infinityModel('item', { perPage: 1 }); + } + }); + + var route = RouteObject.create(); + + DS.VERSION = "1.0.0-beta.19.3"; + assert.throws(() => { + route.model(); + }, + /unsupported version of Ember Data/, + 'Unsupported ember-data error message is shown for beta.19.3' + ); +});