diff --git a/app/assets/javascripts/discourse/components/visible.js.es6 b/app/assets/javascripts/discourse/components/visible.js.es6
index 041f6131f9e..1c3532c2081 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 7c850ffb78b..465eae40465 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 6bb1d5ad909..5276b912d6a 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 98bb9e70e1b..462f87439b8 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 d4a1cb6f7ed..a7dd7b61eff 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 c69c8ef0518..a659ed57be6 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 03516b9d8b6..fc4f9c784c8 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 20443650bce..ed94f9f4507 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 5a92b7f011d..ce943932d54 100644
--- a/app/assets/javascripts/discourse/templates/discovery/topics.hbs
+++ b/app/assets/javascripts/discourse/templates/discovery/topics.hbs
@@ -67,7 +67,7 @@
{{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 3993fc912a0..03a121efe8e 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 c210ab64313..f985345da2c 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 63e0da86449..e663eeda5d9 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 06152caacc6..64547080b4c 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 bb32d28422f..1af1bb14eac 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 162ec08758b..afa3c86457c 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 d86f40dc1ca..bc613b930cc 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 */},