diff --git a/app/assets/javascripts/discourse/adapters/topic-list.js.es6 b/app/assets/javascripts/discourse/adapters/topic-list.js.es6 new file mode 100644 index 00000000000..46a3a566837 --- /dev/null +++ b/app/assets/javascripts/discourse/adapters/topic-list.js.es6 @@ -0,0 +1,39 @@ +import RestAdapter from 'discourse/adapters/rest'; + +function finderFor(filter, params) { + return function() { + let url = Discourse.getURL("/") + filter + ".json"; + + if (params) { + const keys = Object.keys(params), + encoded = []; + + keys.forEach(function(p) { + const value = params[p]; + if (typeof value !== 'undefined') { + encoded.push(p + "=" + value); + } + }); + + if (encoded.length > 0) { + url += "?" + encoded.join('&'); + } + } + return Discourse.ajax(url); + }; +} + +export default RestAdapter.extend({ + + find(store, type, findArgs) { + const filter = findArgs.filter; + const params = findArgs.params; + + return PreloadStore.getAndRemove("topic_list_" + filter, finderFor(filter, params)).then(function(result) { + result.filter = filter; + result.params = params; + return result; + }); + } +}); + diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6 index 2a2f13cf4a9..d8c89807001 100644 --- a/app/assets/javascripts/discourse/controllers/composer.js.es6 +++ b/app/assets/javascripts/discourse/controllers/composer.js.es6 @@ -413,7 +413,7 @@ export default DiscourseController.extend({ composerModel.set('topic', opts.topic); } } else { - composerModel = composerModel || Discourse.Composer.create({ store: this.store }); + composerModel = composerModel || this.store.createRecord('composer'); composerModel.open(opts); } diff --git a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 index 27ae37afd96..293d9c826c7 100644 --- a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 +++ b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 @@ -48,7 +48,8 @@ var controllerOpts = { // router and ember throws an error due to missing `handlerInfos`. // Lesson learned: Don't call `loading` yourself. this.set('controllers.discovery.loading', true); - Discourse.TopicList.find(filter).then(function(list) { + + this.store.findFiltered('topicList', {filter}).then(function(list) { Discourse.TopicList.hideUniformCategory(list, self.get('category')); self.setProperties({ model: list }); diff --git a/app/assets/javascripts/discourse/models/composer.js.es6 b/app/assets/javascripts/discourse/models/composer.js.es6 index 8171432a0e0..8b0df348117 100644 --- a/app/assets/javascripts/discourse/models/composer.js.es6 +++ b/app/assets/javascripts/discourse/models/composer.js.es6 @@ -1,3 +1,7 @@ +import RestModel from 'discourse/models/rest'; +import Post from 'discourse/models/post'; +import Topic from 'discourse/models/topic'; + const CLOSED = 'closed', SAVING = 'saving', OPEN = 'open', @@ -26,7 +30,7 @@ const CLOSED = 'closed', categoryId: 'topic.category.id' }; -const Composer = Discourse.Model.extend({ +const Composer = RestModel.extend({ archetypes: function() { return this.site.get('archetypes'); @@ -420,7 +424,8 @@ const Composer = Discourse.Model.extend({ post.get('post_number') === 1 && this.get('topic.details.can_edit')) { const topicProps = this.getProperties(Object.keys(_edit_topic_serializer)); - promise = Discourse.Topic.update(this.get('topic'), topicProps); + + promise = Topic.update(this.get('topic'), topicProps); } else { promise = Ember.RSVP.resolve(); } @@ -468,7 +473,7 @@ const Composer = Discourse.Model.extend({ let addedToStream = false; // Build the post object - const createdPost = Discourse.Post.create({ + const createdPost = Post.create({ imageSizes: opts.imageSizes, cooked: this.getCookedHtml(), reply_count: 0, diff --git a/app/assets/javascripts/discourse/models/post-stream.js.es6 b/app/assets/javascripts/discourse/models/post-stream.js.es6 index 7c3ae0cf265..6ff9f73bdbe 100644 --- a/app/assets/javascripts/discourse/models/post-stream.js.es6 +++ b/app/assets/javascripts/discourse/models/post-stream.js.es6 @@ -1,4 +1,6 @@ -const PostStream = Ember.Object.extend({ +import RestModel from 'discourse/models/rest'; + +const PostStream = RestModel.extend({ loading: Em.computed.or('loadingAbove', 'loadingBelow', 'loadingFilter', 'stagingPost'), notLoading: Em.computed.not('loading'), filteredPostsCount: Em.computed.alias("stream.length"), @@ -420,8 +422,9 @@ const PostStream = Ember.Object.extend({ } else { // need to insert into stream const url = "/posts/" + postId; + const store = this.store; Discourse.ajax(url).then(function(p){ - const post = Discourse.Post.create(p); + const post = store.createRecord('post', p); const stream = self.get("stream"); const posts = self.get("posts"); self.storePost(post); @@ -461,9 +464,10 @@ const PostStream = Ember.Object.extend({ if(existing){ const url = "/posts/" + postId; + const store = this.store; Discourse.ajax(url).then( function(p){ - self.storePost(Discourse.Post.create(p)); + self.storePost(store.createRecord('post', p)); }, function(){ self.removePosts([existing]); @@ -480,8 +484,9 @@ const PostStream = Ember.Object.extend({ if (existing && existing.updated_at !== updatedAt) { const url = "/posts/" + postId; + const store = this.store; Discourse.ajax(url).then(function(p){ - self.storePost(Discourse.Post.create(p)); + self.storePost(store.createRecord('post', p)); }); } }, @@ -491,9 +496,10 @@ const PostStream = Ember.Object.extend({ const postStream = this, url = "/posts/" + post.get('id') + "/reply-history.json?max_replies=" + Discourse.SiteSettings.max_reply_history; + const store = this.store; return Discourse.ajax(url).then(function(result) { return result.map(function (p) { - return postStream.storePost(Discourse.Post.create(p)); + return postStream.storePost(store.createRecord('post', p)); }); }).then(function (replyHistory) { post.set('replyHistory', replyHistory); @@ -594,8 +600,9 @@ const PostStream = Ember.Object.extend({ this.set('gaps', null); if (postStreamData) { // Load posts if present + const store = this.store; postStreamData.posts.forEach(function(p) { - postStream.appendPost(Discourse.Post.create(p)); + postStream.appendPost(store.createRecord('post', p)); }); delete postStreamData.posts; @@ -671,11 +678,12 @@ const PostStream = Ember.Object.extend({ data = { post_ids: postIds }, postStream = this; + const store = this.store; return Discourse.ajax(url, {data: data}).then(function(result) { const posts = Em.get(result, "post_stream.posts"); if (posts) { posts.forEach(function (p) { - postStream.storePost(Discourse.Post.create(p)); + postStream.storePost(store.createRecord('post', p)); }); } }); @@ -751,6 +759,8 @@ PostStream.reopenClass({ url += "/" + opts.nearPost; } delete opts.nearPost; + delete opts.__type; + delete opts.store; return PreloadStore.getAndRemove("topic_" + topicId, function() { return Discourse.ajax(url + ".json", {data: opts}); diff --git a/app/assets/javascripts/discourse/models/post.js.es6 b/app/assets/javascripts/discourse/models/post.js.es6 index 627cc1aaf87..6a45675a058 100644 --- a/app/assets/javascripts/discourse/models/post.js.es6 +++ b/app/assets/javascripts/discourse/models/post.js.es6 @@ -394,32 +394,29 @@ const Post = RestModel.extend({ throw e; }); } + }); Post.reopenClass({ - createActionSummary(result) { - if (result.actions_summary) { + munge(json) { + if (json.actions_summary) { const lookup = Em.Object.create(); // this area should be optimized, it is creating way too many objects per post - result.actions_summary = result.actions_summary.map(function(a) { - a.post = result; + json.actions_summary = json.actions_summary.map(function(a) { + a.post = json; a.actionType = Discourse.Site.current().postActionTypeById(a.id); const actionSummary = Discourse.ActionSummary.create(a); lookup[a.actionType.name_key] = actionSummary; return actionSummary; }); - result.set('actionByName', lookup); + json.actionByName = lookup; } - }, - create(obj) { - const result = this._super.apply(this, arguments); - this.createActionSummary(result); - if (obj && obj.reply_to_user) { - result.set('reply_to_user', Discourse.User.create(obj.reply_to_user)); + if (json && json.reply_to_user) { + json.reply_to_user = Discourse.User.create(json.reply_to_user); } - return result; + return json; }, updateBookmark(postId, bookmarked) { diff --git a/app/assets/javascripts/discourse/models/rest.js.es6 b/app/assets/javascripts/discourse/models/rest.js.es6 index fc5eff01efe..d4ec50be0b5 100644 --- a/app/assets/javascripts/discourse/models/rest.js.es6 +++ b/app/assets/javascripts/discourse/models/rest.js.es6 @@ -1,16 +1,18 @@ import Presence from 'discourse/mixins/presence'; -export default Ember.Object.extend(Presence, { +const RestModel = Ember.Object.extend(Presence, { update(attrs) { const self = this, type = this.get('__type'); + + const munge = this.__munge; return this.store.update(type, this.get('id'), attrs).then(function(result) { if (result && result[type]) { Object.keys(result).forEach(function(k) { attrs[k] = result[k]; }); } - self.setProperties(attrs); + self.setProperties(munge(attrs)); return result; }); }, @@ -20,3 +22,25 @@ export default Ember.Object.extend(Presence, { return this.store.destroyRecord(type, this); } }); + +RestModel.reopenClass({ + + // Overwrite and JSON will be passed through here before `create` and `update` + munge(json) { + return json; + }, + + create(args) { + args = args || {}; + if (!args.store) { + const container = Discourse.__container__; + Ember.warn('Use `store.createRecord` to create records instead of `.create()`'); + args.store = container.lookup('store:main'); + } + + args.__munge = this.munge; + return this._super(this.munge(args, args.store)); + } +}); + +export default RestModel; diff --git a/app/assets/javascripts/discourse/models/store.js.es6 b/app/assets/javascripts/discourse/models/store.js.es6 index 72f36db3748..0430bc48782 100644 --- a/app/assets/javascripts/discourse/models/store.js.es6 +++ b/app/assets/javascripts/discourse/models/store.js.es6 @@ -1,7 +1,14 @@ import RestModel from 'discourse/models/rest'; import ResultSet from 'discourse/models/result-set'; -const _identityMap = {}; +let _identityMap; + +// You should only call this if you're a test scaffold +function flushMap() { + _identityMap = {}; +} + +flushMap(); export default Ember.Object.extend({ pluralize(thing) { @@ -16,6 +23,15 @@ export default Ember.Object.extend({ }); }, + // Mostly for legacy, things like TopicList without ResultSets + findFiltered(type, findArgs) { + const adapter = this.container.lookup('adapter:' + type) || this.container.lookup('adapter:rest'); + const self = this; + return adapter.find(this, type, findArgs).then(function(result) { + return self._build(type, result); + }); + }, + find(type, findArgs) { const adapter = this.container.lookup('adapter:' + type) || this.container.lookup('adapter:rest'); const self = this; @@ -60,7 +76,8 @@ export default Ember.Object.extend({ }, createRecord(type, attrs) { - return this._hydrate(type, attrs); + attrs = attrs || {}; + return !!attrs.id ? this._hydrate(type, attrs) : this._build(type, attrs); }, destroyRecord(type, record) { @@ -81,6 +98,19 @@ export default Ember.Object.extend({ return ResultSet.create({ content, totalRows, loadMoreUrl, store: this, __type: type }); }, + _build(type, obj) { + obj.store = this; + obj.__type = type; + + const klass = this.container.lookupFactory('model:' + type) || RestModel; + const model = klass.create(obj); + + if (obj.id) { + _identityMap[type][obj.id] = model; + } + return model; + }, + _hydrate(type, obj) { if (!obj) { throw "Can't hydrate " + type + " of `null`"; } if (!obj.id) { throw "Can't hydrate " + type + " without an `id`"; } @@ -88,18 +118,17 @@ export default Ember.Object.extend({ _identityMap[type] = _identityMap[type] || {}; const existing = _identityMap[type][obj.id]; + if (existing === obj) { return existing; } + if (existing) { delete obj.id; - existing.setProperties(obj); + const klass = this.container.lookupFactory('model:' + type) || RestModel; + existing.setProperties(klass.munge(obj)); return existing; } - obj.store = this; - obj.__type = type; - - const klass = this.container.lookupFactory('model:' + type) || RestModel; - const model = klass.create(obj); - _identityMap[type][obj.id] = model; - return model; + return this._build(type, obj); } }); + +export { flushMap }; diff --git a/app/assets/javascripts/discourse/models/topic-details.js.es6 b/app/assets/javascripts/discourse/models/topic-details.js.es6 index 6e6c1b05b2a..9c8ca30e279 100644 --- a/app/assets/javascripts/discourse/models/topic-details.js.es6 +++ b/app/assets/javascripts/discourse/models/topic-details.js.es6 @@ -2,7 +2,9 @@ A model representing a Topic's details that aren't always present, such as a list of participants. When showing topics in lists and such this information should not be required. **/ -const TopicDetails = Discourse.Model.extend({ +import RestModel from 'discourse/models/rest'; + +const TopicDetails = RestModel.extend({ loaded: false, updateFromJson(details) { @@ -15,8 +17,9 @@ const TopicDetails = Discourse.Model.extend({ } if (details.suggested_topics) { + const store = this.store; details.suggested_topics = details.suggested_topics.map(function (st) { - return Discourse.Topic.create(st); + return store.createRecord('topic', st); }); } diff --git a/app/assets/javascripts/discourse/models/topic-list.js.es6 b/app/assets/javascripts/discourse/models/topic-list.js.es6 new file mode 100644 index 00000000000..36b0ba67955 --- /dev/null +++ b/app/assets/javascripts/discourse/models/topic-list.js.es6 @@ -0,0 +1,163 @@ +import RestModel from 'discourse/models/rest'; +import Model from 'discourse/models/model'; + + +function topicsFrom(result, store) { + if (!result) { return; } + + // Stitch together our side loaded data + const categories = Discourse.Category.list(), + users = Model.extractByKey(result.users, Discourse.User); + + return result.topic_list.topics.map(function (t) { + t.category = categories.findBy('id', t.category_id); + t.posters.forEach(function(p) { + p.user = users[p.user_id]; + }); + if (t.participants) { + t.participants.forEach(function(p) { + p.user = users[p.user_id]; + }); + } + return store.createRecord('topic', t); + }); +} + +const TopicList = RestModel.extend({ + canLoadMore: Em.computed.notEmpty("more_topics_url"), + + forEachNew: function(topics, callback) { + const topicIds = []; + _.each(this.get('topics'),function(topic) { + topicIds[topic.get('id')] = true; + }); + + _.each(topics,function(topic) { + if(!topicIds[topic.id]) { + callback(topic); + } + }); + }, + + refreshSort: function(order, ascending) { + const self = this, + params = this.get('params'); + + params.order = order || params.order; + + if (ascending === undefined) { + params.ascending = ascending; + } else { + params.ascending = ascending; + } + + this.set('loaded', false); + const store = this.store; + store.findFiltered('topicList', {filter: this.get('filter'), params}).then(function(tl) { + const newTopics = tl.get('topics'), + topics = self.get('topics'); + + topics.clear(); + topics.pushObjects(newTopics); + self.setProperties({ loaded: true, more_topics_url: newTopics.get('more_topics_url') }); + }); + }, + + loadMore: function() { + if (this.get('loadingMore')) { return Ember.RSVP.resolve(); } + + const moreUrl = this.get('more_topics_url'); + if (moreUrl) { + const self = this; + this.set('loadingMore', true); + + const store = this.store; + return Discourse.ajax({url: moreUrl}).then(function (result) { + let topicsAdded = 0; + + if (result) { + // the new topics loaded from the server + const newTopics = topicsFrom(result, store), + topics = self.get("topics"); + + self.forEachNew(newTopics, function(t) { + t.set('highlight', topicsAdded++ === 0); + topics.pushObject(t); + }); + + self.setProperties({ + loadingMore: false, + more_topics_url: result.topic_list.more_topics_url + }); + + Discourse.Session.currentProp('topicList', self); + return self.get('more_topics_url'); + } + }); + } else { + // Return a promise indicating no more results + return Ember.RSVP.resolve(); + } + }, + + + // loads topics with these ids "before" the current topics + loadBefore: function(topic_ids){ + const topicList = this, + topics = this.get('topics'); + + // refresh dupes + topics.removeObjects(topics.filter(function(topic){ + return topic_ids.indexOf(topic.get('id')) >= 0; + })); + + const url = Discourse.getURL("/") + this.get('filter') + "?topic_ids=" + topic_ids.join(","); + + const store = this.store; + return Discourse.ajax({ url }).then(function(result) { + let i = 0; + topicList.forEachNew(topicsFrom(result, store), function(t) { + // highlight the first of the new topics so we can get a visual feedback + t.set('highlight', true); + topics.insertAt(i,t); + i++; + }); + Discourse.Session.currentProp('topicList', topicList); + }); + } +}); + +TopicList.reopenClass({ + + munge(json, store) { + json.inserted = json.inserted || []; + json.can_create_topic = json.topic_list.can_create_topic; + json.more_topics_url = json.topic_list.more_topics_url; + json.draft_key = json.topic_list.draft_key; + json.draft_sequence = json.topic_list.draft_sequence; + json.draft = json.topic_list.draft; + json.for_period = json.topic_list.for_period; + json.loaded = true; + json.per_page = json.topic_list.per_page; + json.topics = topicsFrom(json, store); + + if (json.topic_list.filtered_category) { + json.category = Discourse.Category.create(json.topic_list.filtered_category); + } + return json; + }, + + find(filter, params) { + const store = Discourse.__container__.lookup('store:main'); + return store.findFiltered('topicList', {filter, params}); + }, + + // Sets `hideCategory` if all topics in the last have a particular category + hideUniformCategory(list, category) { + const hideCategory = !list.get('topics').any(function (t) { return t.get('category') !== category; }); + list.set('hideCategory', hideCategory); + } + +}); + +export default TopicList; diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6 index a2e984ea30c..2b51a06d66d 100644 --- a/app/assets/javascripts/discourse/models/topic.js.es6 +++ b/app/assets/javascripts/discourse/models/topic.js.es6 @@ -1,7 +1,6 @@ -import TopicDetails from 'discourse/models/topic-details'; -import PostStream from 'discourse/models/post-stream'; +import RestModel from 'discourse/models/rest'; -const Topic = Discourse.Model.extend({ +const Topic = RestModel.extend({ // returns createdAt if there's no bumped date bumpedAt: function() { @@ -23,7 +22,7 @@ const Topic = Discourse.Model.extend({ }.property('created_at'), postStream: function() { - return PostStream.create({topic: this}); + return this.store.createRecord('postStream', {id: this.get('id'), topic: this}); }.property(), replyCount: function() { @@ -31,7 +30,7 @@ const Topic = Discourse.Model.extend({ }.property('posts_count'), details: function() { - return TopicDetails.create({topic: this}); + return this.store.createRecord('topicDetails', {id: this.get('id'), topic: this}); }.property(), invisible: Em.computed.not('visible'), diff --git a/app/assets/javascripts/discourse/models/topic_list.js b/app/assets/javascripts/discourse/models/topic_list.js deleted file mode 100644 index dce4524d0f4..00000000000 --- a/app/assets/javascripts/discourse/models/topic_list.js +++ /dev/null @@ -1,272 +0,0 @@ -function finderFor(filter, params) { - return function() { - var url = Discourse.getURL("/") + filter + ".json"; - - if (params) { - var keys = Object.keys(params), - encoded = []; - - keys.forEach(function(p) { - var value = params[p]; - if (typeof value !== 'undefined') { - encoded.push(p + "=" + value); - } - }); - - if (encoded.length > 0) { - url += "?" + encoded.join('&'); - } - } - return Discourse.ajax(url); - }; -} - -Discourse.TopicList = Discourse.Model.extend({ - canLoadMore: Em.computed.notEmpty("more_topics_url"), - - forEachNew: function(topics, callback) { - var topicIds = []; - _.each(this.get('topics'),function(topic) { - topicIds[topic.get('id')] = true; - }); - - _.each(topics,function(topic) { - if(!topicIds[topic.id]) { - callback(topic); - } - }); - }, - - refreshSort: function(order, ascending) { - var self = this, - params = this.get('params'); - - params.order = order || params.order; - - if (ascending === undefined) { - params.ascending = ascending; - } else { - params.ascending = ascending; - } - - this.set('loaded', false); - var finder = finderFor(this.get('filter'), params); - finder().then(function (result) { - var newTopics = Discourse.TopicList.topicsFrom(result), - topics = self.get('topics'); - - topics.clear(); - topics.pushObjects(newTopics); - self.setProperties({ loaded: true, more_topics_url: result.topic_list.more_topics_url }); - }); - }, - - loadMore: function() { - if (this.get('loadingMore')) { return Ember.RSVP.resolve(); } - - var moreUrl = this.get('more_topics_url'); - if (moreUrl) { - var self = this; - this.set('loadingMore', true); - - return Discourse.ajax({url: moreUrl}).then(function (result) { - var topicsAdded = 0; - if (result) { - // the new topics loaded from the server - var newTopics = Discourse.TopicList.topicsFrom(result), - topics = self.get("topics"); - - self.forEachNew(newTopics, function(t) { - t.set('highlight', topicsAdded++ === 0); - topics.pushObject(t); - }); - - self.setProperties({ - loadingMore: false, - more_topics_url: result.topic_list.more_topics_url - }); - - Discourse.Session.currentProp('topicList', self); - return self.get('more_topics_url'); - } - }); - } else { - // Return a promise indicating no more results - return Ember.RSVP.resolve(); - } - }, - - - // loads topics with these ids "before" the current topics - loadBefore: function(topic_ids){ - var topicList = this, - topics = this.get('topics'); - - // refresh dupes - topics.removeObjects(topics.filter(function(topic){ - return topic_ids.indexOf(topic.get('id')) >= 0; - })); - - Discourse.TopicList.loadTopics(topic_ids, this.get('filter')) - .then(function(newTopics){ - var i = 0; - topicList.forEachNew(newTopics, function(t) { - // highlight the first of the new topics so we can get a visual feedback - t.set('highlight', true); - topics.insertAt(i,t); - i++; - }); - Discourse.Session.currentProp('topicList', topicList); - }); - } -}); - -Discourse.TopicList.reopenClass({ - - loadTopics: function(topic_ids, filter) { - return new Ember.RSVP.Promise(function(resolve, reject) { - var url = Discourse.getURL("/") + filter + "?topic_ids=" + topic_ids.join(","); - - Discourse.ajax({url: url}).then(function (result) { - if (result) { - // the new topics loaded from the server - var newTopics = Discourse.TopicList.topicsFrom(result); - resolve(newTopics); - } else { - reject(); - } - }).catch(reject); - }); - }, - - /** - Stitch together side loaded topic data - - @method topicsFrom - @param {Object} result JSON object with topic data - @returns {Array} the list of topics - **/ - topicsFrom: function(result) { - // Stitch together our side loaded data - var categories = Discourse.Category.list(), - users = this.extractByKey(result.users, Discourse.User); - - return result.topic_list.topics.map(function (t) { - t.category = categories.findBy('id', t.category_id); - t.posters.forEach(function(p) { - p.user = users[p.user_id]; - }); - if (t.participants) { - t.participants.forEach(function(p) { - p.user = users[p.user_id]; - }); - } - return Discourse.Topic.create(t); - }); - }, - - from: function(result, filter, params) { - var topicList = Discourse.TopicList.create({ - inserted: [], - filter: filter, - params: params || {}, - topics: Discourse.TopicList.topicsFrom(result), - can_create_topic: result.topic_list.can_create_topic, - more_topics_url: result.topic_list.more_topics_url, - draft_key: result.topic_list.draft_key, - draft_sequence: result.topic_list.draft_sequence, - draft: result.topic_list.draft, - for_period: result.topic_list.for_period, - loaded: true, - per_page: result.topic_list.per_page - }); - - if (result.topic_list.filtered_category) { - topicList.set('category', Discourse.Category.create(result.topic_list.filtered_category)); - } - - return topicList; - }, - - /** - Lists topics on a given menu item - - @method list - @param {Object} filter The menu item to filter to - @param {Object} params Any additional params to pass to TopicList.find() - @param {Object} extras Additional finding options, such as caching - @returns {Promise} a promise that resolves to the list of topics - **/ - list: function(filter, filterParams, extras) { - var tracking = Discourse.TopicTrackingState.current(); - - extras = extras || {}; - return new Ember.RSVP.Promise(function(resolve) { - var session = Discourse.Session.current(); - - if (extras.cached) { - var cachedList = session.get('topicList'); - - // Try to use the cached version if it exists and is greater than the topics per page - if (cachedList && (cachedList.get('filter') === filter) && - (cachedList.get('topics.length') || 0) > cachedList.get('per_page') && - _.isEqual(cachedList.get('listParams'), filterParams)) { - cachedList.set('loaded', true); - - if (tracking) { - tracking.updateTopics(cachedList.get('topics')); - } - return resolve(cachedList); - } - session.set('topicList', null); - } else { - // Clear the cache - session.setProperties({topicList: null, topicListScrollPosition: null}); - } - - - // Clean up any string parameters that might slip through - filterParams = filterParams || {}; - Ember.keys(filterParams).forEach(function(k) { - var val = filterParams[k]; - if (val === "undefined" || val === "null" || val === 'false') { - filterParams[k] = undefined; - } - }); - - var findParams = {}; - Discourse.SiteSettings.top_menu.split('|').forEach(function (i) { - if (i.indexOf(filter) === 0) { - var exclude = i.split("-"); - if (exclude && exclude.length === 2) { - findParams.exclude_category = exclude[1]; - } - } - }); - return resolve(Discourse.TopicList.find(filter, _.extend(findParams, filterParams || {}))); - - }).then(function(list) { - list.set('listParams', filterParams); - if (tracking) { - tracking.sync(list, list.filter); - tracking.trackIncoming(list.filter); - } - Discourse.Session.currentProp('topicList', list); - return list; - }); - }, - - find: function(filter, params) { - return PreloadStore.getAndRemove("topic_list_" + filter, finderFor(filter, params)).then(function(result) { - return Discourse.TopicList.from(result, filter, params); - }); - }, - - // Sets `hideCategory` if all topics in the last have a particular category - hideUniformCategory: function(list, category) { - var hideCategory = !list.get('topics').any(function (t) { return t.get('category') !== category; }); - list.set('hideCategory', hideCategory); - } - -}); - diff --git a/app/assets/javascripts/discourse/routes/build-category-route.js.es6 b/app/assets/javascripts/discourse/routes/build-category-route.js.es6 index 8113104ea97..fe794b4482b 100644 --- a/app/assets/javascripts/discourse/routes/build-category-route.js.es6 +++ b/app/assets/javascripts/discourse/routes/build-category-route.js.es6 @@ -1,4 +1,4 @@ -import { queryParams, filterQueryParams } from 'discourse/routes/build-topic-route'; +import { queryParams, filterQueryParams, findTopicList } from 'discourse/routes/build-topic-route'; // A helper function to create a category route with parameters export default function(filter, params) { @@ -52,7 +52,7 @@ export default function(filter, params) { var findOpts = filterQueryParams(transition.queryParams, params), extras = { cached: this.isPoppedState(transition) }; - return Discourse.TopicList.list(listFilter, findOpts, extras).then(function(list) { + return findTopicList(this.store, listFilter, findOpts, extras).then(function(list) { Discourse.TopicList.hideUniformCategory(list, model); self.set('topics', list); }); diff --git a/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 b/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 index 4b21e9b0bb6..81b94b5a2d4 100644 --- a/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 +++ b/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 @@ -11,6 +11,65 @@ function filterQueryParams(params, defaultParams) { return findOpts; } +function findTopicList(store, filter, filterParams, extras) { + const tracking = Discourse.TopicTrackingState.current(); + + extras = extras || {}; + return new Ember.RSVP.Promise(function(resolve) { + const session = Discourse.Session.current(); + + if (extras.cached) { + const cachedList = session.get('topicList'); + + // Try to use the cached version if it exists and is greater than the topics per page + if (cachedList && (cachedList.get('filter') === filter) && + (cachedList.get('topics.length') || 0) > cachedList.get('per_page') && + _.isEqual(cachedList.get('listParams'), filterParams)) { + cachedList.set('loaded', true); + + if (tracking) { + tracking.updateTopics(cachedList.get('topics')); + } + return resolve(cachedList); + } + session.set('topicList', null); + } else { + // Clear the cache + session.setProperties({topicList: null, topicListScrollPosition: null}); + } + + + // Clean up any string parameters that might slip through + filterParams = filterParams || {}; + Ember.keys(filterParams).forEach(function(k) { + const val = filterParams[k]; + if (val === "undefined" || val === "null" || val === 'false') { + filterParams[k] = undefined; + } + }); + + const findParams = {}; + Discourse.SiteSettings.top_menu.split('|').forEach(function (i) { + if (i.indexOf(filter) === 0) { + const exclude = i.split("-"); + if (exclude && exclude.length === 2) { + findParams.exclude_category = exclude[1]; + } + } + }); + return resolve(store.findFiltered('topicList', { filter, params:_.extend(findParams, filterParams || {})})); + + }).then(function(list) { + list.set('listParams', filterParams); + if (tracking) { + tracking.sync(list, list.filter); + tracking.trackIncoming(list.filter); + } + Discourse.Session.currentProp('topicList', list); + return list; + }); +} + export default function(filter, extras) { extras = extras || {}; return Discourse.Route.extend({ @@ -28,7 +87,7 @@ export default function(filter, extras) { const findOpts = filterQueryParams(transition.queryParams), extras = { cached: this.isPoppedState(transition) }; - return Discourse.TopicList.list(filter, findOpts, extras); + return findTopicList(this.store, filter, findOpts, extras); }, titleToken() { @@ -72,4 +131,4 @@ export default function(filter, extras) { }, extras); } -export { filterQueryParams }; +export { filterQueryParams, findTopicList }; diff --git a/app/assets/javascripts/discourse/routes/build-user-topic-list-route.js.es6 b/app/assets/javascripts/discourse/routes/build-user-topic-list-route.js.es6 index ae1ff7ba3bc..c8c345e88b2 100644 --- a/app/assets/javascripts/discourse/routes/build-user-topic-list-route.js.es6 +++ b/app/assets/javascripts/discourse/routes/build-user-topic-list-route.js.es6 @@ -14,7 +14,7 @@ export default function (viewName, path) { }, model: function() { - return Discourse.TopicList.find('topics/' + path + '/' + this.modelFor('user').get('username_lower')); + return this.store.findFiltered('topicList', {filter: 'topics/' + path + '/' + this.modelFor('user').get('username_lower')}); }, setupController: function() { diff --git a/app/assets/javascripts/discourse/routes/topic.js.es6 b/app/assets/javascripts/discourse/routes/topic.js.es6 index 725c895ead2..0f23333859a 100644 --- a/app/assets/javascripts/discourse/routes/topic.js.es6 +++ b/app/assets/javascripts/discourse/routes/topic.js.es6 @@ -5,7 +5,6 @@ let isTransitioning = false, const SCROLL_DELAY = 500; import ShowFooter from "discourse/mixins/show-footer"; -import Topic from 'discourse/models/topic'; import showModal from 'discourse/lib/show-modal'; const TopicRoute = Discourse.Route.extend(ShowFooter, { @@ -153,7 +152,7 @@ const TopicRoute = Discourse.Route.extend(ShowFooter, { model(params, transition) { const queryParams = transition.queryParams; - const topic = this.modelFor('topic'); + let topic = this.modelFor('topic'); if (topic && (topic.get('id') === parseInt(params.id, 10))) { this.setupParams(topic, queryParams); // If we have the existing model, refresh it @@ -161,7 +160,8 @@ const TopicRoute = Discourse.Route.extend(ShowFooter, { return topic; }); } else { - return this.setupParams(Topic.create(_.omit(params, 'username_filters', 'filter')), queryParams); + topic = this.store.createRecord('topic', _.omit(params, 'username_filters', 'filter')); + return this.setupParams(topic, queryParams); } }, diff --git a/app/assets/javascripts/discourse/routes/user-activity-topics.js.es6 b/app/assets/javascripts/discourse/routes/user-activity-topics.js.es6 index ba5e6aa748d..65210df7a7e 100644 --- a/app/assets/javascripts/discourse/routes/user-activity-topics.js.es6 +++ b/app/assets/javascripts/discourse/routes/user-activity-topics.js.es6 @@ -4,6 +4,6 @@ export default UserTopicListRoute.extend({ userActionType: Discourse.UserAction.TYPES.topics, model: function() { - return Discourse.TopicList.find('topics/created-by/' + this.modelFor('user').get('username_lower')); + return this.store.findFiltered('topicList', {filter: 'topics/created-by/' + this.modelFor('user').get('username_lower') }); } }); diff --git a/app/assets/javascripts/main_include.js b/app/assets/javascripts/main_include.js index 597dbbdd937..e0c1a4ca776 100644 --- a/app/assets/javascripts/main_include.js +++ b/app/assets/javascripts/main_include.js @@ -27,11 +27,11 @@ //= require ./discourse/models/rest //= require ./discourse/models/model //= require ./discourse/models/post -//= require ./discourse/models/user_action -//= require ./discourse/models/composer //= require ./discourse/models/post-stream //= require ./discourse/models/topic-details //= require ./discourse/models/topic +//= require ./discourse/models/user_action +//= require ./discourse/models/composer //= require ./discourse/controllers/controller //= require ./discourse/controllers/discovery-sortable //= require ./discourse/controllers/object diff --git a/test/javascripts/helpers/create-store.js.es6 b/test/javascripts/helpers/create-store.js.es6 index 244fab70c7c..4be1b8feb07 100644 --- a/test/javascripts/helpers/create-store.js.es6 +++ b/test/javascripts/helpers/create-store.js.es6 @@ -1,8 +1,10 @@ import Store from "discourse/models/store"; import RestAdapter from 'discourse/adapters/rest'; +import Resolver from 'discourse/ember/resolver'; let _restAdapter; export default function() { + const resolver = Resolver.create(); return Store.create({ container: { lookup(type) { @@ -12,7 +14,10 @@ export default function() { } }, - lookupFactory: function() { } + lookupFactory(type) { + const split = type.split(':'); + return resolver.customResolve({type: split[0], fullNameWithoutType: split[1]}); + }, } }); } diff --git a/test/javascripts/models/post-stream-test.js.es6 b/test/javascripts/models/post-stream-test.js.es6 index a3f7e1319e5..a510c48398b 100644 --- a/test/javascripts/models/post-stream-test.js.es6 +++ b/test/javascripts/models/post-stream-test.js.es6 @@ -1,32 +1,34 @@ module("model:post-stream"); -import PostStream from 'discourse/models/post-stream'; -import Topic from 'discourse/models/topic'; +import createStore from 'helpers/create-store'; -var buildStream = function(id, stream) { - var topic = Topic.create({id: id, chunk_size: 5}); - var ps = topic.get('postStream'); +const buildStream = function(id, stream) { + const store = createStore(); + const topic = store.createRecord('topic', {id, chunk_size: 5}); + const ps = topic.get('postStream'); if (stream) { ps.set('stream', stream); } return ps; }; -var participant = {username: 'eviltrout'}; +const participant = {username: 'eviltrout'}; test('create', function() { - ok(PostStream.create(), 'it can be created with no parameters'); + const store = createStore(); + ok(store.createRecord('postStream'), 'it can be created with no parameters'); }); test('defaults', function() { - var postStream = buildStream(1234); + const postStream = buildStream(1234); blank(postStream.get('posts'), "there are no posts in a stream by default"); ok(!postStream.get('loaded'), "it has never loaded"); present(postStream.get('topic')); }); test('appending posts', function() { - var postStream = buildStream(4567, [1, 3, 4]); + const postStream = buildStream(4567, [1, 3, 4]); + const store = postStream.store; equal(postStream.get('lastPostId'), 4, "the last post id is 4"); @@ -35,25 +37,24 @@ test('appending posts', function() { ok(!postStream.get('loadedAllPosts'), "the last post is not loaded"); equal(postStream.get('posts.length'), 0, "it has no posts initially"); - postStream.appendPost(Discourse.Post.create({id: 2, post_number: 2})); + postStream.appendPost(store.createRecord('post', {id: 2, post_number: 2})); ok(!postStream.get('firstPostPresent'), "the first post is still not loaded"); equal(postStream.get('posts.length'), 1, "it has one post in the stream"); - postStream.appendPost(Discourse.Post.create({id: 4, post_number: 4})); + postStream.appendPost(store.createRecord('post', {id: 4, post_number: 4})); ok(!postStream.get('firstPostPresent'), "the first post is still loaded"); ok(postStream.get('loadedAllPosts'), "the last post is now loaded"); equal(postStream.get('posts.length'), 2, "it has two posts in the stream"); - postStream.appendPost(Discourse.Post.create({id: 4, post_number: 4})); + postStream.appendPost(store.createRecord('post', {id: 4, post_number: 4})); equal(postStream.get('posts.length'), 2, "it will not add the same post with id twice"); - var stagedPost = Discourse.Post.create({raw: 'incomplete post'}); + const stagedPost = store.createRecord('post', {raw: 'incomplete post'}); postStream.appendPost(stagedPost); equal(postStream.get('posts.length'), 3, "it can handle posts without ids"); postStream.appendPost(stagedPost); equal(postStream.get('posts.length'), 3, "it won't add the same post without an id twice"); - // change the stream postStream.set('stream', [1, 2, 4]); ok(!postStream.get('firstPostPresent'), "the first post no longer loaded since the stream changed."); @@ -61,12 +62,13 @@ test('appending posts', function() { }); test('closestPostNumberFor', function() { - var postStream = buildStream(1231); + const postStream = buildStream(1231); + const store = postStream.store; blank(postStream.closestPostNumberFor(1), "there is no closest post when nothing is loaded"); - postStream.appendPost(Discourse.Post.create({id: 1, post_number: 2})); - postStream.appendPost(Discourse.Post.create({id: 2, post_number: 3})); + postStream.appendPost(store.createRecord('post', {id: 1, post_number: 2})); + postStream.appendPost(store.createRecord('post', {id: 2, post_number: 3})); equal(postStream.closestPostNumberFor(2), 2, "If a post is in the stream it returns its post number"); equal(postStream.closestPostNumberFor(3), 3, "If a post is in the stream it returns its post number"); @@ -75,7 +77,7 @@ test('closestPostNumberFor', function() { }); test('updateFromJson', function() { - var postStream = buildStream(1231); + const postStream = buildStream(1231); postStream.updateFromJson({ posts: [{id: 1}], @@ -90,11 +92,12 @@ test('updateFromJson', function() { }); test("removePosts", function() { - var postStream = buildStream(10000001, [1,2,3]); + const postStream = buildStream(10000001, [1,2,3]); + const store = postStream.store; - var p1 = Discourse.Post.create({id: 1, post_number: 2}), - p2 = Discourse.Post.create({id: 2, post_number: 3}), - p3 = Discourse.Post.create({id: 3, post_number: 4}); + const p1 = store.createRecord('post', {id: 1, post_number: 2}), + p2 = store.createRecord('post', {id: 2, post_number: 3}), + p3 = store.createRecord('post', {id: 3, post_number: 4}); postStream.appendPost(p1); postStream.appendPost(p2); @@ -111,7 +114,7 @@ test("removePosts", function() { }); test("cancelFilter", function() { - var postStream = buildStream(1235); + const postStream = buildStream(1235); sandbox.stub(postStream, "refresh"); @@ -125,7 +128,7 @@ test("cancelFilter", function() { }); test("findPostIdForPostNumber", function() { - var postStream = buildStream(1234, [10, 20, 30, 40, 50, 60, 70]); + const postStream = buildStream(1234, [10, 20, 30, 40, 50, 60, 70]); postStream.set('gaps', { before: { 60: [55, 58] } }); equal(postStream.findPostIdForPostNumber(500), null, 'it returns null when the post cannot be found'); @@ -136,7 +139,7 @@ test("findPostIdForPostNumber", function() { }); test("toggleParticipant", function() { - var postStream = buildStream(1236); + const postStream = buildStream(1236); sandbox.stub(postStream, "refresh"); equal(postStream.get('userFilters.length'), 0, "by default no participants are toggled"); @@ -149,7 +152,7 @@ test("toggleParticipant", function() { }); test("streamFilters", function() { - var postStream = buildStream(1237); + const postStream = buildStream(1237); sandbox.stub(postStream, "refresh"); deepEqual(postStream.get('streamFilters'), {}, "there are no postFilters by default"); @@ -179,7 +182,7 @@ test("streamFilters", function() { }); test("loading", function() { - var postStream = buildStream(1234); + let postStream = buildStream(1234); ok(!postStream.get('loading'), "we're not loading by default"); postStream.set('loadingAbove', true); @@ -195,7 +198,7 @@ test("loading", function() { }); test("nextWindow", function() { - var postStream = buildStream(1234, [1,2,3,5,8,9,10,11,13,14,15,16]); + const postStream = buildStream(1234, [1,2,3,5,8,9,10,11,13,14,15,16]); blank(postStream.get('nextWindow'), 'With no posts loaded, the window is blank'); @@ -211,7 +214,7 @@ test("nextWindow", function() { }); test("previousWindow", function() { - var postStream = buildStream(1234, [1,2,3,5,8,9,10,11,13,14,15,16]); + const postStream = buildStream(1234, [1,2,3,5,8,9,10,11,13,14,15,16]); blank(postStream.get('previousWindow'), 'With no posts loaded, the window is blank'); @@ -227,21 +230,22 @@ test("previousWindow", function() { }); test("storePost", function() { - var postStream = buildStream(1234), - post = Discourse.Post.create({id: 1, post_number: 100, raw: 'initial value'}); + const postStream = buildStream(1234), + store = postStream.store, + post = store.createRecord('post', {id: 1, post_number: 100, raw: 'initial value'}); blank(postStream.get('topic.highest_post_number'), "it has no highest post number yet"); - var stored = postStream.storePost(post); + let stored = postStream.storePost(post); equal(post, stored, "it returns the post it stored"); equal(post.get('topic'), postStream.get('topic'), "it creates the topic reference properly"); equal(postStream.get('topic.highest_post_number'), 100, "it set the highest post number"); - var dupePost = Discourse.Post.create({id: 1, post_number: 100, raw: 'updated value'}); - var storedDupe = postStream.storePost(dupePost); + const dupePost = store.createRecord('post', {id: 1, post_number: 100, raw: 'updated value'}); + const storedDupe = postStream.storePost(dupePost); equal(storedDupe, post, "it returns the previously stored post instead to avoid dupes"); equal(storedDupe.get('raw'), 'updated value', 'it updates the previously stored post'); - var postWithoutId = Discourse.Post.create({raw: 'hello world'}); + const postWithoutId = store.createRecord('post', {raw: 'hello world'}); stored = postStream.storePost(postWithoutId); equal(stored, postWithoutId, "it returns the same post back"); equal(postStream.get('postIdentityMap.size'), 1, "it does not add a new entry into the identity map"); @@ -249,9 +253,11 @@ test("storePost", function() { }); test("identity map", function() { - var postStream = buildStream(1234); - var p1 = postStream.appendPost(Discourse.Post.create({id: 1, post_number: 1})); - postStream.appendPost(Discourse.Post.create({id: 3, post_number: 4})); + const postStream = buildStream(1234), + store = postStream.store; + + const p1 = postStream.appendPost(store.createRecord('post', {id: 1, post_number: 1})); + postStream.appendPost(store.createRecord('post', {id: 3, post_number: 4})); equal(postStream.findLoadedPost(1), p1, "it can return cached posts by id"); blank(postStream.findLoadedPost(4), "it can't find uncached posts"); @@ -262,7 +268,7 @@ test("identity map", function() { }); asyncTestDiscourse("loadIntoIdentityMap with no data", function() { - var postStream = buildStream(1234); + const postStream = buildStream(1234); expect(1); sandbox.stub(Discourse, "ajax"); @@ -273,7 +279,7 @@ asyncTestDiscourse("loadIntoIdentityMap with no data", function() { }); asyncTestDiscourse("loadIntoIdentityMap with post ids", function() { - var postStream = buildStream(1234); + const postStream = buildStream(1234); expect(1); sandbox.stub(Discourse, "ajax").returns(Ember.RSVP.resolve({ @@ -289,12 +295,12 @@ asyncTestDiscourse("loadIntoIdentityMap with post ids", function() { }); asyncTestDiscourse("loading a post's history", function() { - var postStream = buildStream(1234); + const postStream = buildStream(1234); + const store = postStream.store; expect(3); - var post = Discourse.Post.create({id: 4321}); - - var secondPost = Discourse.Post.create({id: 2222}); + const post = store.createRecord('post', {id: 4321}); + const secondPost = store.createRecord('post', {id: 2222}); sandbox.stub(Discourse, "ajax").returns(Ember.RSVP.resolve([secondPost])); postStream.findReplyHistory(post).then(function() { @@ -306,20 +312,22 @@ asyncTestDiscourse("loading a post's history", function() { }); test("staging and undoing a new post", function() { - var postStream = buildStream(10101, [1]); - postStream.appendPost(Discourse.Post.create({id: 1, post_number: 1, topic_id: 10101})); + const postStream = buildStream(10101, [1]); + const store = postStream.store; - var user = Discourse.User.create({username: 'eviltrout', name: 'eviltrout', id: 321}); - var stagedPost = Discourse.Post.create({ raw: 'hello world this is my new post', topic_id: 10101 }); + postStream.appendPost(store.createRecord('post', {id: 1, post_number: 1, topic_id: 10101})); - var topic = postStream.get('topic'); + const user = Discourse.User.create({username: 'eviltrout', name: 'eviltrout', id: 321}); + const stagedPost = store.createRecord('post', { raw: 'hello world this is my new post', topic_id: 10101 }); + + const topic = postStream.get('topic'); topic.setProperties({ posts_count: 1, highest_post_number: 1 }); // Stage the new post in the stream - var result = postStream.stagePost(stagedPost, user); + const result = postStream.stagePost(stagedPost, user); equal(result, "staged", "it returns staged"); equal(topic.get('highest_post_number'), 2, "it updates the highest_post_number"); ok(postStream.get('loading'), "it is loading while the post is being staged"); @@ -345,16 +353,18 @@ test("staging and undoing a new post", function() { }); test("staging and committing a post", function() { - var postStream = buildStream(10101, [1]); - postStream.appendPost(Discourse.Post.create({id: 1, post_number: 1, topic_id: 10101})); - var user = Discourse.User.create({username: 'eviltrout', name: 'eviltrout', id: 321}); - var stagedPost = Discourse.Post.create({ raw: 'hello world this is my new post', topic_id: 10101 }); + const postStream = buildStream(10101, [1]); + const store = postStream.store; - var topic = postStream.get('topic'); + postStream.appendPost(store.createRecord('post', {id: 1, post_number: 1, topic_id: 10101})); + const user = Discourse.User.create({username: 'eviltrout', name: 'eviltrout', id: 321}); + const stagedPost = store.createRecord('post', { raw: 'hello world this is my new post', topic_id: 10101 }); + + const topic = postStream.get('topic'); topic.set('posts_count', 1); // Stage the new post in the stream - var result = postStream.stagePost(stagedPost, user); + let result = postStream.stagePost(stagedPost, user); equal(result, "staged", "it returns staged"); ok(postStream.get('loading'), "it is loading while the post is being staged"); @@ -369,7 +379,7 @@ test("staging and committing a post", function() { equal(postStream.get('filteredPostsCount'), 2, "it increases the filteredPostsCount"); - var found = postStream.findLoadedPost(stagedPost.get('id')); + const found = postStream.findLoadedPost(stagedPost.get('id')); present(found, "the post is in the identity map"); ok(postStream.indexOf(stagedPost) > -1, "the post is in the stream"); equal(found.get('raw'), 'different raw value', 'it also updated the value in the stream'); @@ -377,7 +387,8 @@ test("staging and committing a post", function() { }); test('triggerNewPostInStream', function() { - var postStream = buildStream(225566); + const postStream = buildStream(225566); + const store = postStream.store; sandbox.stub(postStream, 'appendMore'); sandbox.stub(postStream, 'refresh'); @@ -399,7 +410,7 @@ test('triggerNewPostInStream', function() { ok(!postStream.appendMore.calledOnce, "it wont't delegate to appendMore because the last post is not loaded"); postStream.cancelFilter(); - postStream.appendPost(Discourse.Post.create({id: 1, post_number: 2})); + postStream.appendPost(store.createRecord('post', {id: 1, post_number: 2})); postStream.triggerNewPostInStream(2); ok(postStream.appendMore.calledOnce, "delegates to appendMore because the last post is loaded"); }); @@ -408,10 +419,11 @@ test('triggerNewPostInStream', function() { test("loadedAllPosts when the id changes", function() { // This can happen in a race condition between staging a post and it coming through on the // message bus. If the id of a post changes we should reconsider the loadedAllPosts property. - var postStream = buildStream(10101, [1, 2]); - var postWithoutId = Discourse.Post.create({ raw: 'hello world this is my new post' }); + const postStream = buildStream(10101, [1, 2]); + const store = postStream.store; + const postWithoutId = store.createRecord('post', { raw: 'hello world this is my new post' }); - postStream.appendPost(Discourse.Post.create({id: 1, post_number: 1})); + postStream.appendPost(store.createRecord('post', {id: 1, post_number: 1})); postStream.appendPost(postWithoutId); ok(!postStream.get('loadedAllPosts'), 'the last post is not loaded'); @@ -420,11 +432,12 @@ test("loadedAllPosts when the id changes", function() { }); test("comitting and triggerNewPostInStream race condition", function() { - var postStream = buildStream(4964); + const postStream = buildStream(4964); + const store = postStream.store; - postStream.appendPost(Discourse.Post.create({id: 1, post_number: 1})); - var user = Discourse.User.create({username: 'eviltrout', name: 'eviltrout', id: 321}); - var stagedPost = Discourse.Post.create({ raw: 'hello world this is my new post' }); + postStream.appendPost(store.createRecord('post', {id: 1, post_number: 1})); + const user = Discourse.User.create({username: 'eviltrout', name: 'eviltrout', id: 321}); + const stagedPost = store.createRecord('post', { raw: 'hello world this is my new post' }); postStream.stagePost(stagedPost, user); equal(postStream.get('filteredPostsCount'), 0, "it has no filteredPostsCount yet"); diff --git a/test/javascripts/models/rest-model-test.js.es6 b/test/javascripts/models/rest-model-test.js.es6 index 0f0ea282961..0362706c974 100644 --- a/test/javascripts/models/rest-model-test.js.es6 +++ b/test/javascripts/models/rest-model-test.js.es6 @@ -1,6 +1,21 @@ module('rest-model'); import createStore from 'helpers/create-store'; +import RestModel from 'discourse/models/rest'; + +test('munging', function() { + const store = createStore(); + const Grape = RestModel.extend(); + Grape.reopenClass({ + munge: function(json) { + json.inverse = 1 - json.percent; + return json; + } + }); + + var g = Grape.create({ store, percent: 0.4 }); + equal(g.get('inverse'), 0.6, 'it runs `munge` on `create`'); +}); test('update', function() { const store = createStore(); diff --git a/test/javascripts/models/store-test.js.es6 b/test/javascripts/models/store-test.js.es6 index d9e68fc7856..f47c99f1b28 100644 --- a/test/javascripts/models/store-test.js.es6 +++ b/test/javascripts/models/store-test.js.es6 @@ -9,6 +9,28 @@ test('createRecord', function() { equal(widget.get('id'), 111); }); +test('createRecord without an `id`', function() { + const store = createStore(); + const widget = store.createRecord('widget', {name: 'hello'}); + + ok(!widget.get('id'), 'there is no id'); +}); + +test('createRecord without attributes', function() { + const store = createStore(); + const widget = store.createRecord('widget'); + + ok(!widget.get('id'), 'there is no id'); +}); + +test('createRecord with a record as attributes returns that record from the map', function() { + const store = createStore(); + const widget = store.createRecord('widget', {id: 33}); + const secondWidget = store.createRecord('widget', {id: 33}); + + equal(widget, secondWidget, 'they should be the same'); +}); + test('find', function() { const store = createStore(); store.find('widget', 123).then(function(w) { diff --git a/test/javascripts/test_helper.js b/test/javascripts/test_helper.js index bc38f15d11f..b59d22af6ad 100644 --- a/test/javascripts/test_helper.js +++ b/test/javascripts/test_helper.js @@ -90,6 +90,7 @@ if (window.Logster) { var origDebounce = Ember.run.debounce, createPretendServer = require('helpers/create-pretender', null, null, false).default, fixtures = require('fixtures/site_fixtures', null, null, false).default, + flushMap = require('discourse/models/store', null, null, false).flushMap, server; QUnit.testStart(function(ctx) { @@ -120,6 +121,7 @@ QUnit.testDone(function() { // Destroy any modals $('.modal-backdrop').remove(); + flushMap(); server.shutdown(); });