From ed398e65e09afba4b74b8ec52b53e3f4589aeef4 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 30 Apr 2015 16:04:58 -0400 Subject: [PATCH] Fixes issues with composer --- .../discourse/components/visible.js.es6 | 5 +- .../discourse/controllers/composer.js.es6 | 16 +++- .../discourse/controllers/discovery.js.es6 | 2 +- .../controllers/discovery/topics.js.es6 | 2 +- .../discourse/initializers/banner.js.es6 | 1 + .../javascripts/discourse/mixins/ajax.js | 8 +- .../discourse/mixins/open-composer.js.es6 | 4 +- .../discourse/templates/composer.hbs | 2 +- .../discourse/templates/discovery/topics.hbs | 2 +- .../discourse/views/cloaked-collection.js.es6 | 2 +- .../discourse/views/composer.js.es6 | 2 +- .../controllers/notification-test.js.es6 | 38 +++++----- test/javascripts/helpers/qunit-helpers.js.es6 | 2 + test/javascripts/models/nav-item-test.js.es6 | 8 +- vendor/assets/javascripts/favcount.js | 6 +- vendor/assets/javascripts/pretender.js | 76 ++++++++++++++++--- 16 files changed, 121 insertions(+), 55 deletions(-) diff --git a/app/assets/javascripts/discourse/components/visible.js.es6 b/app/assets/javascripts/discourse/components/visible.js.es6 index 041f6131f..1c3532c20 100644 --- a/app/assets/javascripts/discourse/components/visible.js.es6 +++ b/app/assets/javascripts/discourse/components/visible.js.es6 @@ -4,9 +4,8 @@ export default Ember.Component.extend({ }.observes("visible"), render: function(buffer){ - if(!this.get("visible")){ - return; - } + if (this._state !== 'inDOM' && this._state !== 'preRender') { return; } + if (!this.get("visible")) { return; } return this._super(buffer); } diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6 index 7c850ffb7..465eae404 100644 --- a/app/assets/javascripts/discourse/controllers/composer.js.es6 +++ b/app/assets/javascripts/discourse/controllers/composer.js.es6 @@ -1,6 +1,6 @@ import DiscourseController from 'discourse/controllers/controller'; -export default DiscourseController.extend({ +export default Ember.ObjectController.extend({ needs: ['modal', 'topic', 'composer-messages', 'application'], replyAsNewTopicDraft: Em.computed.equal('model.draftKey', Discourse.Composer.REPLY_AS_NEW_TOPIC_KEY), @@ -10,6 +10,14 @@ export default DiscourseController.extend({ editReason: null, maxTitleLength: Discourse.computed.setting('max_topic_title_length'), scopedCategoryId: null, + similarTopics: null, + similarTopicsMessage: null, + lastSimilaritySearch: null, + + topic: null, + + // TODO: Remove this, very bad + view: null, _initializeSimilar: function() { this.set('similarTopics', []); @@ -183,7 +191,7 @@ export default DiscourseController.extend({ // for now handle a very narrow use case // if we are replying to a topic AND not on the topic pop the window up if (!force && composer.get('replyingToTopic')) { - const topic = this.get('topic'); + const topic = this.get('model.topic'); if (!topic || topic.get('id') !== composer.get('topic.id')) { const message = I18n.t("composer.posting_not_on_topic"); @@ -285,7 +293,7 @@ export default DiscourseController.extend({ // Checks to see if a reply has been typed. // This is signaled by a keyUp event in a view. checkReplyLength() { - if (this.present('model.reply')) { + if (!Ember.isEmpty('model.reply')) { // Notify the composer messages controller that a reply has been typed. Some // messages only appear after typing. this.get('controllers.composer-messages').typedReply(); @@ -469,7 +477,7 @@ export default DiscourseController.extend({ // View a new reply we've made viewNewReply() { - Discourse.URL.routeTo(this.get('createdPost.url')); + Discourse.URL.routeTo(this.get('model.createdPost.url')); this.close(); return false; }, diff --git a/app/assets/javascripts/discourse/controllers/discovery.js.es6 b/app/assets/javascripts/discourse/controllers/discovery.js.es6 index 6bb1d5ad9..5276b912d 100644 --- a/app/assets/javascripts/discourse/controllers/discovery.js.es6 +++ b/app/assets/javascripts/discourse/controllers/discovery.js.es6 @@ -1,4 +1,4 @@ -export default Ember.Controller.extend({ +export default Ember.ObjectController.extend({ needs: ['navigation/category', 'discovery/topics', 'application'], loading: false, diff --git a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 index 98bb9e70e..462f87439 100644 --- a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 +++ b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 @@ -116,7 +116,7 @@ var controllerOpts = { if( category ) { return I18n.t('topics.bottom.category', {category: category.get('name')}); } else { - var split = (this.get('filter') || '').split('/'); + var split = (this.get('model.filter') || '').split('/'); if (this.get('topics.length') === 0) { return I18n.t("topics.none." + split[0], { category: split[1] diff --git a/app/assets/javascripts/discourse/initializers/banner.js.es6 b/app/assets/javascripts/discourse/initializers/banner.js.es6 index d4a1cb6f7..a7dd7b61e 100644 --- a/app/assets/javascripts/discourse/initializers/banner.js.es6 +++ b/app/assets/javascripts/discourse/initializers/banner.js.es6 @@ -3,6 +3,7 @@ export default { after: "message-bus", initialize(container) { + const banner = Em.Object.create(PreloadStore.get("banner")), site = container.lookup('site:main'); diff --git a/app/assets/javascripts/discourse/mixins/ajax.js b/app/assets/javascripts/discourse/mixins/ajax.js index c69c8ef05..a659ed57b 100644 --- a/app/assets/javascripts/discourse/mixins/ajax.js +++ b/app/assets/javascripts/discourse/mixins/ajax.js @@ -60,7 +60,7 @@ Discourse.Ajax = Em.Mixin.create({ Ember.run(null, resolve, data); }; - args.error = function(xhr, textStatus) { + args.error = function(xhr, textStatus, errorThrown) { // note: for bad CSRF we don't loop an extra request right away. // this allows us to eliminate the possibility of having a loop. if (xhr.status === 403 && xhr.responseText === "['BAD CSRF']") { @@ -74,7 +74,11 @@ Discourse.Ajax = Em.Mixin.create({ xhr.jqTextStatus = textStatus; xhr.requestedUrl = url; - Ember.run(null, reject, xhr); + Ember.run(null, reject, { + jqXHR: xhr, + textStatus: textStatus, + errorThrown: errorThrown + }); }; // We default to JSON on GET. If we don't, sometimes if the server doesn't return the proper header diff --git a/app/assets/javascripts/discourse/mixins/open-composer.js.es6 b/app/assets/javascripts/discourse/mixins/open-composer.js.es6 index 03516b9d8..fc4f9c784 100644 --- a/app/assets/javascripts/discourse/mixins/open-composer.js.es6 +++ b/app/assets/javascripts/discourse/mixins/open-composer.js.es6 @@ -6,8 +6,8 @@ export default Ember.Mixin.create({ this.controllerFor('composer').open({ categoryId: controller.get('category.id'), action: Discourse.Composer.CREATE_TOPIC, - draftKey: controller.get('draft_key'), - draftSequence: controller.get('draft_sequence') + draftKey: controller.get('model.draft_key'), + draftSequence: controller.get('model.draft_sequence') }); }, diff --git a/app/assets/javascripts/discourse/templates/composer.hbs b/app/assets/javascripts/discourse/templates/composer.hbs index 20443650b..ed94f9f45 100644 --- a/app/assets/javascripts/discourse/templates/composer.hbs +++ b/app/assets/javascripts/discourse/templates/composer.hbs @@ -84,7 +84,7 @@ so I'm going to stop rendering it until we figure out what's up
-
+
{{{model.toggleText}}} diff --git a/app/assets/javascripts/discourse/templates/discovery/topics.hbs b/app/assets/javascripts/discourse/templates/discovery/topics.hbs index 5a92b7f01..ce943932d 100644 --- a/app/assets/javascripts/discourse/templates/discovery/topics.hbs +++ b/app/assets/javascripts/discourse/templates/discovery/topics.hbs @@ -67,7 +67,7 @@

{{footerMessage}} - {{#if can_create_topic}}{{i18n 'topic.suggest_create_topic'}}{{/if}} + {{#if can_create_topic}}{{i18n 'topic.suggest_create_topic'}}{{/if}}

{{else}} {{#if top}} diff --git a/app/assets/javascripts/discourse/views/cloaked-collection.js.es6 b/app/assets/javascripts/discourse/views/cloaked-collection.js.es6 index 3993fc912..03a121efe 100644 --- a/app/assets/javascripts/discourse/views/cloaked-collection.js.es6 +++ b/app/assets/javascripts/discourse/views/cloaked-collection.js.es6 @@ -282,5 +282,5 @@ const CloakedCollectionView = Ember.CollectionView.extend({ }.on('willDestroyElement') }); -Ember.Handlebars.helper('cloaked-collection', CloakedCollectionView); +Ember.Handlebars.helper('cloaked-collection', Ember.testing ? Ember.CollectionView : CloakedCollectionView); export default CloakedCollectionView; diff --git a/app/assets/javascripts/discourse/views/composer.js.es6 b/app/assets/javascripts/discourse/views/composer.js.es6 index c210ab643..f985345da 100644 --- a/app/assets/javascripts/discourse/views/composer.js.es6 +++ b/app/assets/javascripts/discourse/views/composer.js.es6 @@ -36,7 +36,7 @@ const ComposerView = Discourse.View.extend(Ember.Evented, { }.observes('loading'), postMade: function() { - return this.present('controller.createdPost') ? 'created-post' : null; + return this.present('model.createdPost') ? 'created-post' : null; }.property('model.createdPost'), refreshPreview: Discourse.debounce(function() { diff --git a/test/javascripts/controllers/notification-test.js.es6 b/test/javascripts/controllers/notification-test.js.es6 index 63e0da864..e663eeda5 100644 --- a/test/javascripts/controllers/notification-test.js.es6 +++ b/test/javascripts/controllers/notification-test.js.es6 @@ -1,54 +1,56 @@ import Site from 'discourse/models/site'; -const notificationFixture = { - notification_type: 1, //mentioned - post_number: 1, - topic_id: 1234, - slug: "a-slug", - data: { - topic_title: "some title", - display_username: "velesin" - }, - site: Site.current() -}; +function buildFixture() { + return { + notification_type: 1, //mentioned + post_number: 1, + topic_id: 1234, + slug: "a-slug", + data: { + topic_title: "some title", + display_username: "velesin" + }, + site: Site.current() + }; +} moduleFor("controller:notification"); test("scope property is correct", function() { - const controller = this.subject(notificationFixture); + const controller = this.subject(buildFixture()); equal(controller.get("scope"), "notifications.mentioned"); }); test("username property is correct", function() { - const controller = this.subject(notificationFixture); + const controller = this.subject(buildFixture()); equal(controller.get("username"), "velesin"); }); test("description property returns badge name when there is one", function() { - const fixtureWithBadgeName = _.extend({}, notificationFixture, { data: { badge_name: "badge" } }); + const fixtureWithBadgeName = _.extend({}, buildFixture(), { data: { badge_name: "badge" } }); const controller = this.subject(fixtureWithBadgeName); equal(controller.get("description"), "badge"); }); test("description property returns empty string when there is no topic title", function() { - const fixtureWithEmptyTopicTitle = _.extend({}, notificationFixture, { data: { topic_title: "" } }); + const fixtureWithEmptyTopicTitle = _.extend({}, buildFixture(), { data: { topic_title: "" } }); const controller = this.subject(fixtureWithEmptyTopicTitle); equal(controller.get("description"), ""); }); test("description property returns topic title", function() { - const fixtureWithTopicTitle = _.extend({}, notificationFixture, { data: { topic_title: "topic" } }); + const fixtureWithTopicTitle = _.extend({}, buildFixture(), { data: { topic_title: "topic" } }); const controller = this.subject(fixtureWithTopicTitle); equal(controller.get("description"), "topic"); }); test("url property returns badge's url when there is a badge", function() { - const fixtureWithBadge = _.extend({}, notificationFixture, { data: { badge_id: 1, badge_name: "Badge Name"} }); + const fixtureWithBadge = _.extend({}, buildFixture(), { data: { badge_id: 1, badge_name: "Badge Name"} }); const controller = this.subject(fixtureWithBadge); equal(controller.get("url"), "/badges/1/badge-name"); }); test("url property returns topic's url when there is a topic", function() { - const controller = this.subject(notificationFixture); + const controller = this.subject(buildFixture()); equal(controller.get("url"), "/t/a-slug/1234"); }); diff --git a/test/javascripts/helpers/qunit-helpers.js.es6 b/test/javascripts/helpers/qunit-helpers.js.es6 index 06152caac..64547080b 100644 --- a/test/javascripts/helpers/qunit-helpers.js.es6 +++ b/test/javascripts/helpers/qunit-helpers.js.es6 @@ -70,6 +70,8 @@ function acceptance(name, options) { if (options && options.teardown) { options.teardown.call(this); } + Discourse.User.resetCurrent(); + Discourse.Site.resetCurrent(Discourse.Site.create(fixtures['site.json'].site)); Discourse.Utilities.avatarImg = oldAvatar; Discourse.reset(); diff --git a/test/javascripts/models/nav-item-test.js.es6 b/test/javascripts/models/nav-item-test.js.es6 index bb32d2842..1af1bb14e 100644 --- a/test/javascripts/models/nav-item-test.js.es6 +++ b/test/javascripts/models/nav-item-test.js.es6 @@ -1,16 +1,10 @@ -var asianCategory = Discourse.Category.create({name: '确实是这样', id: 343434}); module("Discourse.NavItem", { setup: function() { Ember.run(function() { + const asianCategory = Discourse.Category.create({name: '确实是这样', id: 343434}); Discourse.Site.currentProp('categories').addObject(asianCategory); }); - }, - - teardown: function() { - Em.run(function() { - Discourse.Site.currentProp('categories').removeObject(asianCategory); - }); } }); diff --git a/vendor/assets/javascripts/favcount.js b/vendor/assets/javascripts/favcount.js index 162ec0875..afa3c8645 100644 --- a/vendor/assets/javascripts/favcount.js +++ b/vendor/assets/javascripts/favcount.js @@ -19,6 +19,8 @@ var self = this, img = document.createElement('img'); + if (Ember.testing) { return; } + if (self.canvas.getContext) { img.crossOrigin = "anonymous"; @@ -94,9 +96,7 @@ head.appendChild(favicon); } + Favcount.VERSION = '1.5.0'; this.Favcount = Favcount; }).call(this); -(function(){ - Favcount.VERSION = '1.5.0'; -}).call(this); diff --git a/vendor/assets/javascripts/pretender.js b/vendor/assets/javascripts/pretender.js index d86f40dc1..bc613b930 100644 --- a/vendor/assets/javascripts/pretender.js +++ b/vendor/assets/javascripts/pretender.js @@ -3,9 +3,10 @@ var isNode = typeof process !== 'undefined' && process.toString() === '[object process]'; var RouteRecognizer = isNode ? require('route-recognizer')['default'] : window.RouteRecognizer; var FakeXMLHttpRequest = isNode ? require('./bower_components/FakeXMLHttpRequest/fake_xml_http_request') : window.FakeXMLHttpRequest; +var slice = [].slice; -function Pretender(maps){ - maps = maps || function(){}; +function Pretender(/* routeMap1, routeMap2, ...*/){ + maps = slice.call(arguments); // Herein we keep track of RouteRecognizer instances // keyed by HTTP method. Feel free to add more as needed. this.registry = { @@ -21,6 +22,7 @@ function Pretender(maps){ this.handledRequests = []; this.passthroughRequests = []; this.unhandledRequests = []; + this.requestReferences = []; // reference the native XMLHttpRequest object so // it can be restored later @@ -34,7 +36,9 @@ function Pretender(maps){ this.running = true; // trigger the route map DSL. - maps.call(this); + for(i=0; i < arguments.length; i++){ + this.map(arguments[i]); + } } function interceptor(pretender) { @@ -51,8 +55,8 @@ function interceptor(pretender) { 'a pretender earlier than you intended to'); } + FakeXMLHttpRequest.prototype.send.apply(this, arguments); if (!pretender.checkPassthrough(this)) { - FakeXMLHttpRequest.prototype.send.apply(this, arguments); pretender.handleRequest(this); } else { @@ -86,6 +90,9 @@ function interceptor(pretender) { xhr.open(fakeXHR.method, fakeXHR.url, fakeXHR.async, fakeXHR.username, fakeXHR.password); xhr.timeout = fakeXHR.timeout; xhr.withCredentials = fakeXHR.withCredentials; + for (var h in fakeXHR.requestHeaders) { + xhr.setRequestHeader(h, fakeXHR.requestHeaders[h]); + } return xhr; } proto._passthroughCheck = function(method, arguments) { @@ -109,8 +116,8 @@ function interceptor(pretender) { } function verbify(verb){ - return function(path, handler){ - this.register(verb, path, handler); + return function(path, handler, async){ + this.register(verb, path, handler, async); }; } @@ -137,8 +144,16 @@ Pretender.prototype = { 'delete': verbify('DELETE'), patch: verbify('PATCH'), head: verbify('HEAD'), - register: function register(verb, path, handler){ + map: function(maps){ + maps.call(this); + }, + register: function register(verb, path, handler, async){ + if (!handler) { + throw new Error("The function you tried passing to Pretender to handle " + verb + " " + path + " is undefined or missing."); + } + handler.numberOfCalls = 0; + handler.async = async; this.handlers.push(handler); var registry = this.registry[verb]; @@ -171,24 +186,65 @@ Pretender.prototype = { if (handler) { handler.handler.numberOfCalls++; + var async = handler.handler.async; this.handledRequests.push(request); try { var statusHeadersAndBody = handler.handler(request), status = statusHeadersAndBody[0], headers = this.prepareHeaders(statusHeadersAndBody[1]), - body = this.prepareBody(statusHeadersAndBody[2]); - request.respond(status, headers, body); + body = this.prepareBody(statusHeadersAndBody[2]), + pretender = this; - this.handledRequest(verb, path, request); + this.handleResponse(request, async, function() { + request.respond(status, headers, body); + pretender.handledRequest(verb, path, request); + }); } catch (error) { this.erroredRequest(verb, path, request, error); + this.resolve(request); } } else { this.unhandledRequests.push(request); this.unhandledRequest(verb, path, request); } }, + handleResponse: function handleResponse(request, strategy, callback) { + strategy = typeof strategy === 'function' ? strategy() : strategy; + + if (strategy === false) { + callback(); + } else { + var pretender = this; + pretender.requestReferences.push({ + request: request, + callback: callback + }); + + if (strategy !== true) { + setTimeout(function() { + pretender.resolve(request); + }, typeof strategy === 'number' ? strategy : 0); + } + } + }, + resolve: function resolve(request) { + for(var i = 0, len = this.requestReferences.length; i < len; i++) { + var res = this.requestReferences[i]; + if (res.request === request) { + res.callback(); + this.requestReferences.splice(i, 1); + break; + } + } + }, + requiresManualResolution: function(verb, path) { + var handler = this._handlerFor(verb.toUpperCase(), path, {}); + if (!handler) { return false; } + + var async = handler.handler.async; + return typeof async === 'function' ? async() === true : async === true; + }, prepareBody: function(body) { return body; }, prepareHeaders: function(headers) { return headers; }, handledRequest: function(verb, path, request) { /* no-op */},