Moved JSHint into Qunit suite. It's much harder to forget about now!

This commit is contained in:
Robin Ward
2013-06-21 14:06:20 -04:00
parent a4dceed379
commit 82c21868f3
59 changed files with 4320 additions and 181 deletions

View File

@ -1,63 +0,0 @@
{
"bitwise":true,
"newcap":true,
"eqeqeq":true,
"immed":false,
"nomen":false,
"onevar":false,
"plusplus":false,
"regexp":false,
"strict":false,
/*"undef":true,*/
"white":false,
"eqnull":false,
"debug":false,
"es5":false,
"evil":false,
"forin":false,
"laxbreak":false,
"sub":false,
"maxlen":200,
"indent":2,
"maxerr":50,
"passfail":false,
"predef":["Ember",
"jQuery",
"$",
"RSVP",
"Discourse",
"$LAB",
"Em",
"PreloadStore",
"Handlebars",
"I18n",
"bootbox",
"module",
"integration",
"test",
"ok",
"expect",
"equal",
"blank",
"present",
"visit",
"count",
"exists",
"asyncTest",
"find",
"resolvingPromise",
"sinon"],
"browser":true,
"rhino":false,
"devel":true,
"loopfunc":true,
"asi":true,
"boss":true,
"couch":true,
"curly":false,
"noarg":true,
"node":false,
"noempty":false,
"nonew":true,
"lastsemic":false
}

View File

@ -9,6 +9,6 @@ before_script:
- rake db:migrate - rake db:migrate
- export RUBY_GC_MALLOC_LIMIT=50000000 - export RUBY_GC_MALLOC_LIMIT=50000000
bundler_args: --without development bundler_args: --without development
script: 'rake jshint && rake spec && bundle exec rake qunit:test' script: 'rake spec && bundle exec rake qunit:test'
services: services:
- redis-server - redis-server

View File

@ -97,9 +97,7 @@ group :test do
end end
group :test, :development do group :test, :development do
gem 'jshint_on_rails'
gem 'listen', require: false gem 'listen', require: false
gem 'guard-jshint-on-rails', require: false
gem 'certified', require: false gem 'certified', require: false
gem 'fabrication', require: false gem 'fabrication', require: false
gem 'qunit-rails' gem 'qunit-rails'

View File

@ -199,9 +199,6 @@ GEM
lumberjack (>= 1.0.2) lumberjack (>= 1.0.2)
pry (>= 0.9.10) pry (>= 0.9.10)
thor (>= 0.14.6) thor (>= 0.14.6)
guard-jshint-on-rails (0.0.2)
guard (>= 1.0.0)
jshint_on_rails (>= 1.0.2)
guard-rspec (2.5.4) guard-rspec (2.5.4)
guard (>= 1.1) guard (>= 1.1)
rspec (~> 2.11) rspec (~> 2.11)
@ -226,7 +223,6 @@ GEM
image_sorcery (1.1.0) image_sorcery (1.1.0)
in_threads (1.1.1) in_threads (1.1.1)
journey (1.0.4) journey (1.0.4)
jshint_on_rails (1.0.2)
json (1.7.7) json (1.7.7)
jwt (0.1.8) jwt (0.1.8)
multi_json (>= 1.5) multi_json (>= 1.5)
@ -485,7 +481,6 @@ DEPENDENCIES
fast_xs fast_xs
fastimage fastimage
fog fog
guard-jshint-on-rails
guard-rspec guard-rspec
guard-spork guard-spork
handlebars-source (= 1.0.0.rc4) handlebars-source (= 1.0.0.rc4)
@ -494,7 +489,6 @@ DEPENDENCIES
hiredis hiredis
image_optim image_optim
image_sorcery image_sorcery
jshint_on_rails
librarian (>= 0.0.25) librarian (>= 0.0.25)
listen listen
lru_redux lru_redux

View File

@ -3,14 +3,6 @@ require 'terminal-notifier-guard' if RUBY_PLATFORM.include?('darwin')
phantom_path = File.expand_path('~/phantomjs/bin/phantomjs') phantom_path = File.expand_path('~/phantomjs/bin/phantomjs')
phantom_path = nil unless File.exists?(phantom_path) phantom_path = nil unless File.exists?(phantom_path)
# verify that we pass jshint
# see https://github.com/MrOrz/guard-jshint-on-rails
guard 'jshint-on-rails', config_path: 'config/jshint.yml' do
# watch for changes to application javascript files
watch(%r{^app/assets/javascripts/.*\.js$})
watch(%r{^spec/javascripts/.*\.js$})
end
unless ENV["USING_AUTOSPEC"] unless ENV["USING_AUTOSPEC"]
puts "Sam strongly recommends you Run: `bundle exec rake autospec` in favor of guard for specs, set USING_AUTOSPEC in .rvmrc to disable from Guard" puts "Sam strongly recommends you Run: `bundle exec rake autospec` in favor of guard for specs, set USING_AUTOSPEC in .rvmrc to disable from Guard"

View File

@ -15,7 +15,7 @@ Discourse.AdminEmailPreviewDigestController = Discourse.ObjectController.extend(
Discourse.EmailPreview.findDigest(this.get('lastSeen')).then(function (email) { Discourse.EmailPreview.findDigest(this.get('lastSeen')).then(function (email) {
model.setProperties(email.getProperties('html_content', 'text_content')); model.setProperties(email.getProperties('html_content', 'text_content'));
controller.set('loading', false); controller.set('loading', false);
}) });
} }
}); });

View File

@ -94,7 +94,7 @@ Discourse.AdminUsersListController = Ember.ArrayController.extend(Discourse.Pres
Discourse.AdminUser.findAll(this.get('query'), this.get('username')).then(function (result) { Discourse.AdminUser.findAll(this.get('query'), this.get('username')).then(function (result) {
adminUsersListController.set('content', result); adminUsersListController.set('content', result);
adminUsersListController.set('loading', false); adminUsersListController.set('loading', false);
}) });
}, },

View File

@ -14,7 +14,7 @@ Discourse.FlaggedPost = Discourse.Post.extend({
.map(function(v,k){ .map(function(v,k){
return Em.String.i18n("admin.flags.summary.action_type_" + k, {count: v.length}); return Em.String.i18n("admin.flags.summary.action_type_" + k, {count: v.length});
}) })
.join(",") .join(",");
}.property(), }.property(),
flaggers: function() { flaggers: function() {

View File

@ -20,7 +20,7 @@ Discourse.GithubCommit = Discourse.Model.extend({
}.property("sha"), }.property("sha"),
timeAgo: function() { timeAgo: function() {
return moment(this.get('commit.committer.date')).relativeAge({format: 'medium', leaveAgo: true}) return moment(this.get('commit.committer.date')).relativeAge({format: 'medium', leaveAgo: true});
}.property("commit.committer.date") }.property("commit.committer.date")
}); });

View File

@ -14,12 +14,12 @@ Discourse.Group = Discourse.Model.extend({
if(id && !this.get('loaded')) { if(id && !this.get('loaded')) {
var group = this; var group = this;
Discourse.ajax('/admin/groups/' + this.get('id') + '/users').then(function(payload){ Discourse.ajax('/admin/groups/' + this.get('id') + '/users').then(function(payload){
var users = Em.A() var users = Em.A();
_.each(payload,function(user){ _.each(payload,function(user){
users.addObject(Discourse.User.create(user)); users.addObject(Discourse.User.create(user));
}); });
group.set('users', users) group.set('users', users);
group.set('loaded', true) group.set('loaded', true);
}); });
} }
}, },
@ -30,7 +30,7 @@ Discourse.Group = Discourse.Model.extend({
if(users) { if(users) {
usernames = _.map(users, function(user){ usernames = _.map(users, function(user){
return user.get('username'); return user.get('username');
}).join(',') }).join(',');
} }
return usernames; return usernames;
}.property('users'), }.property('users'),

View File

@ -11,7 +11,7 @@ Discourse.AdminEmailIndexRoute = Discourse.Route.extend({
setupController: function(controller) { setupController: function(controller) {
Discourse.EmailSettings.find().then(function (model) { Discourse.EmailSettings.find().then(function (model) {
controller.set('model', model); controller.set('model', model);
}) });
}, },
renderTemplate: function() { renderTemplate: function() {

View File

@ -9,7 +9,7 @@
var oneWeekAgo = function() { var oneWeekAgo = function() {
return moment().subtract('days',7).format('YYYY-MM-DD'); return moment().subtract('days',7).format('YYYY-MM-DD');
} };
Discourse.AdminEmailPreviewDigestRoute = Discourse.Route.extend(Discourse.ModelReady, { Discourse.AdminEmailPreviewDigestRoute = Discourse.Route.extend(Discourse.ModelReady, {

View File

@ -36,7 +36,7 @@ Discourse.AdminSiteContentEditRoute = Discourse.Route.extend({
Discourse.SiteContent.find(Em.get(model, 'content_type')).then(function (sc) { Discourse.SiteContent.find(Em.get(model, 'content_type')).then(function (sc) {
controller.set('content', sc); controller.set('content', sc);
controller.set('loaded', true); controller.set('loaded', true);
}) });
} }

View File

@ -199,7 +199,7 @@ Discourse = Ember.Application.createWithMixins({
// Reloading will refresh unbound properties // Reloading will refresh unbound properties
Discourse.KeyValueStore.abandonLocal(); Discourse.KeyValueStore.abandonLocal();
window.location.reload(); window.location.reload();
}) });
}, },
authenticationComplete: function(options) { authenticationComplete: function(options) {
@ -255,7 +255,7 @@ Discourse = Ember.Application.createWithMixins({
if (fixture) { if (fixture) {
return Ember.Deferred.promise(function(promise) { return Ember.Deferred.promise(function(promise) {
promise.resolve(fixture); promise.resolve(fixture);
}) });
} }
return Ember.Deferred.promise(function (promise) { return Ember.Deferred.promise(function (promise) {
@ -263,7 +263,7 @@ Discourse = Ember.Application.createWithMixins({
args.success = function(xhr) { args.success = function(xhr) {
Ember.run(promise, promise.resolve, xhr); Ember.run(promise, promise.resolve, xhr);
if (oldSuccess) oldSuccess(xhr); if (oldSuccess) oldSuccess(xhr);
} };
var oldError = args.error; var oldError = args.error;
args.error = function(xhr) { args.error = function(xhr) {
@ -273,7 +273,7 @@ Discourse = Ember.Application.createWithMixins({
promise.reject(xhr); promise.reject(xhr);
if (oldError) oldError(xhr); if (oldError) oldError(xhr);
} };
// We default to JSON on GET. If we don't, sometimes if the server doesn't return the proper header // We default to JSON on GET. If we don't, sometimes if the server doesn't return the proper header
// it will not be parsed as an object. // it will not be parsed as an object.
@ -307,7 +307,7 @@ Discourse = Ember.Application.createWithMixins({
bus.subscribe("/categories", function(data){ bus.subscribe("/categories", function(data){
var site = Discourse.Site.instance(); var site = Discourse.Site.instance();
_.each(data.categories,function(c){ _.each(data.categories,function(c){
site.updateCategory(c) site.updateCategory(c);
}); });
}); });

View File

@ -158,7 +158,7 @@ Discourse.BBCode = {
extractQuotes: function(text) { extractQuotes: function(text) {
var result = {text: "" + text, replacements: []}; var result = {text: "" + text, replacements: []};
var replacements = [] var replacements = [];
var matches; var matches;
while (matches = Discourse.BBCode.QUOTE_REGEXP.exec(result.text)) { while (matches = Discourse.BBCode.QUOTE_REGEXP.exec(result.text)) {
@ -178,7 +178,7 @@ Discourse.BBCode = {
input = input.replace(r.key, val); input = input.replace(r.key, val);
}); });
return input; return input;
} };
return(result); return(result);
}, },
@ -192,21 +192,24 @@ Discourse.BBCode = {
**/ **/
formatQuote: function(text, opts) { formatQuote: function(text, opts) {
var args, matches, params, paramsSplit, paramsString, templateName, username; var args, matches, params, paramsSplit, paramsString, templateName, username;
var splitter = function(p,i) {
if (i > 0) {
var assignment = p.split(':');
if (assignment[0] && assignment[1]) {
return params.push({
key: assignment[0],
value: assignment[1].trim()
});
}
}
};
while (matches = this.QUOTE_REGEXP.exec(text)) { while (matches = this.QUOTE_REGEXP.exec(text)) {
paramsString = matches[1].replace(/\"/g, ''); paramsString = matches[1].replace(/\"/g, '');
paramsSplit = paramsString.split(/\, */); paramsSplit = paramsString.split(/\, */);
params = []; params = [];
_.each(paramsSplit,function(p,i) { _.each(paramsSplit, splitter);
if (i > 0) {
var assignment = p.split(':');
if (assignment[0] && assignment[1]) {
return params.push({
key: assignment[0],
value: assignment[1].trim()
});
}
}
});
username = paramsSplit[0]; username = paramsSplit[0];
// remove leading <br>s // remove leading <br>s

View File

@ -59,12 +59,12 @@ Discourse.debouncePromise = function(func, wait) {
timeout = Em.run.later(function () { timeout = Em.run.later(function () {
timeout = null; timeout = null;
func.apply(context, args).then(function (y) { func.apply(context, args).then(function (y) {
promise.resolve(y) promise.resolve(y);
}); });
}, wait); }, wait);
} }
return promise; return promise;
} };
}; };

View File

@ -1,3 +1,5 @@
/*jshint onecase:true */
Discourse.Formatter = (function(){ Discourse.Formatter = (function(){
var updateRelativeAge, autoUpdatingRelativeAge, relativeAge, relativeAgeTiny, var updateRelativeAge, autoUpdatingRelativeAge, relativeAge, relativeAgeTiny,
@ -15,7 +17,7 @@ Discourse.Formatter = (function(){
return str.replace(/\w\S*/g, function(txt){ return str.replace(/\w\S*/g, function(txt){
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
}); });
} };
longDate = function(dt) { longDate = function(dt) {
if (!dt) return; if (!dt) return;
@ -105,7 +107,7 @@ Discourse.Formatter = (function(){
var t = function(key, opts){ var t = function(key, opts){
return Ember.String.i18n("dates.medium" + (leaveAgo?"_with_ago":"") + "." + key, opts); return Ember.String.i18n("dates.medium" + (leaveAgo?"_with_ago":"") + "." + key, opts);
} };
switch(true){ switch(true){
case(distanceInMinutes >= 1 && distanceInMinutes <= 56): case(distanceInMinutes >= 1 && distanceInMinutes <= 56):

View File

@ -47,5 +47,5 @@ Discourse.KeyValueStore = {
} }
return localStorage[this.context + key]; return localStorage[this.context + key];
} }
} };

View File

@ -17,4 +17,4 @@ Discourse.Lightbox = {
}); });
}); });
} }
} };

View File

@ -58,7 +58,7 @@ Discourse.Onebox = {
var loadingFinished = function() { var loadingFinished = function() {
$elem.removeClass('loading-onebox'); $elem.removeClass('loading-onebox');
$elem.data('onebox-loaded'); $elem.data('onebox-loaded');
} };
var onebox = this; var onebox = this;
promise.then(function(html) { promise.then(function(html) {

View File

@ -110,8 +110,8 @@
} }
return r; return r;
} };
} };
clear(); clear();

View File

@ -21,7 +21,7 @@ Discourse.Search = {
if (!opts) opts = {}; if (!opts) opts = {};
// Only include the data we have // Only include the data we have
var data = { term: term } var data = { term: term };
if (opts.typeFilter) data.type_filter = opts.typeFilter; if (opts.typeFilter) data.type_filter = opts.typeFilter;
if (opts.searchContext) { if (opts.searchContext) {
@ -34,5 +34,5 @@ Discourse.Search = {
return Discourse.ajax('/search', { data: data }); return Discourse.ajax('/search', { data: data });
} }
} };

View File

@ -96,7 +96,7 @@ Discourse.EditCategoryController = Discourse.ObjectController.extend(Discourse.M
}.property(), }.property(),
showCategoryTopic: function() { showCategoryTopic: function() {
this.send('closeModal') this.send('closeModal');
Discourse.URL.routeTo(this.get('topic_url')); Discourse.URL.routeTo(this.get('topic_url'));
return false; return false;
}, },

View File

@ -27,7 +27,7 @@ Discourse.FlagActionTypeController = Discourse.ObjectController.extend({
showDescription: Em.computed.not('showMessageInput'), showDescription: Em.computed.not('showMessageInput'),
customMessageLengthClasses: function() { customMessageLengthClasses: function() {
return (this.get('message.length') < Discourse.SiteSettings.min_private_message_post_length) ? "too-short" : "ok" return (this.get('message.length') < Discourse.SiteSettings.min_private_message_post_length) ? "too-short" : "ok";
}.property('message.length'), }.property('message.length'),
customMessageLength: function() { customMessageLength: function() {

View File

@ -43,14 +43,14 @@ Discourse.FlagController = Discourse.ObjectController.extend(Discourse.ModalFunc
}.property('selected.is_custom_flag'), }.property('selected.is_custom_flag'),
takeAction: function() { takeAction: function() {
this.createFlag({takeAction: true}) this.createFlag({takeAction: true});
this.set('hidden', true); this.set('hidden', true);
}, },
createFlag: function(opts) { createFlag: function(opts) {
var flagController = this; var flagController = this;
var postAction = this.get('actionByName.' + this.get('selected.name_key')); var postAction = this.get('actionByName.' + this.get('selected.name_key'));
var params = this.get('selected.is_custom_flag') ? {message: this.get('message')} : {} var params = this.get('selected.is_custom_flag') ? {message: this.get('message')} : {};
if (opts) params = $.extend(params, opts); if (opts) params = $.extend(params, opts);

View File

@ -71,7 +71,7 @@ Discourse.LoginController = Discourse.Controller.extend(Discourse.ModalFunctiona
// Failed to login // Failed to login
loginController.flash(Em.String.i18n('login.error'), 'error'); loginController.flash(Em.String.i18n('login.error'), 'error');
loginController.set('loggingIn', false); loginController.set('loggingIn', false);
}) });
return false; return false;
}, },
@ -153,7 +153,7 @@ Discourse.LoginController = Discourse.Controller.extend(Discourse.ModalFunctiona
accountUsername: options.username, accountUsername: options.username,
accountName: options.name, accountName: options.name,
authOptions: Em.Object.create(options) authOptions: Em.Object.create(options)
}) });
this.send('showCreateAccount'); this.send('showCreateAccount');
} }

View File

@ -38,7 +38,7 @@ Discourse.SearchController = Em.ArrayController.extend(Discourse.Presence, {
var index = 0; var index = 0;
results = _(['topic', 'category', 'user']) results = _(['topic', 'category', 'user'])
.map(function(n){ .map(function(n){
return _(results).where({type: n}).first() return _(results).where({type: n}).first();
}) })
.compact() .compact()
.each(function(list){ .each(function(list){

View File

@ -223,7 +223,7 @@ Handlebars.registerHelper('editDate', function(property, options) {
**/ **/
Ember.Handlebars.registerHelper('percentile', function(property, options) { Ember.Handlebars.registerHelper('percentile', function(property, options) {
var percentile = Ember.Handlebars.get(this, property, options); var percentile = Ember.Handlebars.get(this, property, options);
return Math.round((1.0 - percentile) * 100) return Math.round((1.0 - percentile) * 100);
}); });
/** /**
@ -236,7 +236,7 @@ Ember.Handlebars.registerHelper('float', function(property, options) {
var x = Ember.Handlebars.get(this, property, options); var x = Ember.Handlebars.get(this, property, options);
if (!x) return "0"; if (!x) return "0";
if (Math.round(x) === x) return x; if (Math.round(x) === x) return x;
return x.toFixed(3) return x.toFixed(3);
}); });
/** /**

View File

@ -66,4 +66,4 @@ Discourse.ScrollingDOMMethods = {
$(document).unbind('touchmove.discourse'); $(document).unbind('touchmove.discourse');
} }
} };

View File

@ -49,10 +49,10 @@ Discourse.CategoryList.reopenClass({
var finder = null; var finder = null;
if (filter === 'categories') { if (filter === 'categories') {
finder = PreloadStore.getAndRemove("categories_list", function() { finder = PreloadStore.getAndRemove("categories_list", function() {
return Discourse.ajax("/categories.json") return Discourse.ajax("/categories.json");
}); });
} else { } else {
finder = Discourse.ajax("/" + filter + ".json") finder = Discourse.ajax("/" + filter + ".json");
} }
return finder.then(function(result) { return finder.then(function(result) {
@ -63,7 +63,7 @@ Discourse.CategoryList.reopenClass({
categories: route.categoriesFrom(result), categories: route.categoriesFrom(result),
draft_key: result.category_list.draft_key, draft_key: result.category_list.draft_key,
draft_sequence: result.category_list.draft_sequence draft_sequence: result.category_list.draft_sequence
}) });
return categoryList; return categoryList;
}); });
} }

View File

@ -10,4 +10,4 @@ Discourse.PostActionType = Discourse.Model.extend({});
Discourse.PostActionType.reopenClass({ Discourse.PostActionType.reopenClass({
MAX_MESSAGE_LENGTH: 500 MAX_MESSAGE_LENGTH: 500
}) });

View File

@ -1,24 +1,28 @@
// this allows you to track the selected item in an array, ghetto for now // this allows you to track the selected item in an array, ghetto for now
Discourse.SelectableArray = Em.ArrayProxy.extend({ Discourse.SelectableArray = Em.ArrayProxy.extend({
init: function() { init: function() {
this.content = []; this.content = [];
this._super(); this._super();
}, },
selectIndex: function(index){ selectIndex: function(index){
this.select(this[index]); this.select(this[index]);
}, },
select: function(selected){ select: function(selected){
_.each(this.content,function(item){ _.each(this.content,function(item){
if(item === selected){ if(item === selected){
Em.set(item, "active", true) Em.set(item, "active", true);
} else { } else {
if (item.get("active")) { if (item.get("active")) {
Em.set(item, "active", false) Em.set(item, "active", false);
} }
} }
}); });
this.set("active", selected); this.set("active", selected);
}, },
removeObject: function(object) { removeObject: function(object) {
if(object === this.get("active")){ if(object === this.get("active")){
this.set("active", null); this.set("active", null);
@ -27,4 +31,5 @@ Discourse.SelectableArray = Em.ArrayProxy.extend({
this._super(object); this._super(object);
} }
}); });

View File

@ -69,7 +69,7 @@ Discourse.Topic = Discourse.Model.extend({
// The last post in the topic // The last post in the topic
lastPost: function() { lastPost: function() {
var posts = this.get('posts') var posts = this.get('posts');
return posts[posts.length-1]; return posts[posts.length-1];
}, },
@ -287,7 +287,7 @@ Discourse.Topic = Discourse.Model.extend({
topic.set('allowed_users', Em.A(result.allowed_users)); topic.set('allowed_users', Em.A(result.allowed_users));
topic.set('loaded', true); topic.set('loaded', true);
} };
var errorLoadingTopic = function(result) { var errorLoadingTopic = function(result) {
@ -295,22 +295,22 @@ Discourse.Topic = Discourse.Model.extend({
// If the result was 404 the post is not found // If the result was 404 the post is not found
if (result.status === 404) { if (result.status === 404) {
topic.set('errorTitle', Em.String.i18n('topic.not_found.title')) topic.set('errorTitle', Em.String.i18n('topic.not_found.title'));
topic.set('message', Em.String.i18n('topic.not_found.description')); topic.set('message', Em.String.i18n('topic.not_found.description'));
return; return;
} }
// If the result is 403 it means invalid access // If the result is 403 it means invalid access
if (result.status === 403) { if (result.status === 403) {
topic.set('errorTitle', Em.String.i18n('topic.invalid_access.title')) topic.set('errorTitle', Em.String.i18n('topic.invalid_access.title'));
topic.set('message', Em.String.i18n('topic.invalid_access.description')); topic.set('message', Em.String.i18n('topic.invalid_access.description'));
return; return;
} }
// Otherwise supply a generic error message // Otherwise supply a generic error message
topic.set('errorTitle', Em.String.i18n('topic.server_error.title')) topic.set('errorTitle', Em.String.i18n('topic.server_error.title'));
topic.set('message', Em.String.i18n('topic.server_error.description')); topic.set('message', Em.String.i18n('topic.server_error.description'));
} };
// Finally, call our find method // Finally, call our find method
return Discourse.Topic.find(this.get('id'), { return Discourse.Topic.find(this.get('id'), {

View File

@ -148,7 +148,7 @@ Discourse.TopicList.reopenClass({
var url = Discourse.getURL("/") + filter + ".json"; var url = Discourse.getURL("/") + filter + ".json";
if (excludeCategory) { url += "?exclude_category=" + excludeCategory; } if (excludeCategory) { url += "?exclude_category=" + excludeCategory; }
return Discourse.ajax(url); return Discourse.ajax(url);
} };
return PreloadStore.getAndRemove("topic_list", finder).then(function(result) { return PreloadStore.getAndRemove("topic_list", finder).then(function(result) {
var topicList = Discourse.TopicList.create({ var topicList = Discourse.TopicList.create({

View File

@ -30,7 +30,7 @@ Discourse.Route.buildRoutes(function() {
}); });
// the homepage is the first item of the 'top_menu' site setting // the homepage is the first item of the 'top_menu' site setting
var settings = Discourse.SiteSettings || PreloadStore.get('siteSettings') var settings = Discourse.SiteSettings || PreloadStore.get('siteSettings');
var homepage = settings.top_menu.split("|")[0].split(",")[0]; var homepage = settings.top_menu.split("|")[0].split(",")[0];
this.route(homepage, { path: '/' }); this.route(homepage, { path: '/' });

View File

@ -18,8 +18,9 @@ var popstateReady = false;
Ember.DiscourseLocation = Ember.Object.extend({ Ember.DiscourseLocation = Ember.Object.extend({
init: function() { init: function() {
set(this, 'location', get(this, 'location') || window.location); set(this, 'location', get(this, 'location') || window.location);
if ( $.inArray('state', $.event.props) < 0 ) if ( $.inArray('state', $.event.props) < 0 ) {
jQuery.event.props.push('state') jQuery.event.props.push('state');
}
this.initState(); this.initState();
}, },

View File

@ -16,7 +16,7 @@ Discourse.ListCategoryRoute = Discourse.FilteredListRoute.extend({
var uncategorized = Discourse.Category.uncategorizedInstance(); var uncategorized = Discourse.Category.uncategorizedInstance();
if (slug === uncategorized.get('slug')) return uncategorized; if (slug === uncategorized.get('slug')) return uncategorized;
var category = categories.findProperty('slug', Em.get(params, 'slug')) var category = categories.findProperty('slug', Em.get(params, 'slug'));
// In case the slug didn't work, try to find it by id instead. // In case the slug didn't work, try to find it by id instead.
if (!category) { if (!category) {

View File

@ -34,7 +34,7 @@ Discourse.TopicRoute = Discourse.Route.extend({
}, },
showPrivateInvite: function() { showPrivateInvite: function() {
Discourse.Route.showModal(this, 'invitePrivate', this.modelFor('topic')) Discourse.Route.showModal(this, 'invitePrivate', this.modelFor('topic'));
this.controllerFor('invitePrivate').setProperties({ this.controllerFor('invitePrivate').setProperties({
email: null, email: null,
error: false, error: false,
@ -46,7 +46,7 @@ Discourse.TopicRoute = Discourse.Route.extend({
showHistory: function(post) { showHistory: function(post) {
Discourse.Route.showModal(this, 'history', post); Discourse.Route.showModal(this, 'history', post);
this.controllerFor('history').refresh(); this.controllerFor('history').refresh();
this.controllerFor('modal').set('modalClass', 'history-modal') this.controllerFor('modal').set('modalClass', 'history-modal');
}, },
mergeTopic: function() { mergeTopic: function() {

View File

@ -15,7 +15,7 @@ Discourse.AutoCloseFormView = Ember.View.extend({
autoCloseChanged: function() { autoCloseChanged: function() {
if( this.get('autoCloseDays') && this.get('autoCloseDays').length > 0 ) { if( this.get('autoCloseDays') && this.get('autoCloseDays').length > 0 ) {
this.set('autoCloseDays', this.get('autoCloseDays').replace(/[^\d]/g, '') ) this.set('autoCloseDays', this.get('autoCloseDays').replace(/[^\d]/g, '') );
} }
}.observes('autoCloseDays') }.observes('autoCloseDays')
}); });

View File

@ -14,7 +14,7 @@ Discourse.ClearPinButton = Discourse.ButtonView.extend({
// Hide the button if it becomes unpinned // Hide the button if it becomes unpinned
unpinned: function() { unpinned: function() {
// When not logged in don't show the button // When not logged in don't show the button
if (!Discourse.User.current()) return 'hidden' if (!Discourse.User.current()) return 'hidden';
return this.get('controller.pinned') ? null : 'hidden'; return this.get('controller.pinned') ? null : 'hidden';
}.property('controller.pinned'), }.property('controller.pinned'),

View File

@ -104,7 +104,7 @@ Discourse.HeaderView = Discourse.View.extend({
if(logo && logo.length > 1) { if(logo && logo.length > 1) {
result += "<img class='logo-big' src=\"" + logo + "\" alt=\"" + Discourse.SiteSettings.title + "\" id='site-logo'>"; result += "<img class='logo-big' src=\"" + logo + "\" alt=\"" + Discourse.SiteSettings.title + "\" id='site-logo'>";
} else { } else {
result += "<h2 class='text-logo' id='site-text-logo'>" + Discourse.SiteSettings.title + "</h2>" result += "<h2 class='text-logo' id='site-text-logo'>" + Discourse.SiteSettings.title + "</h2>";
} }
} }
result += "</a></div>"; result += "</a></div>";

View File

@ -66,7 +66,7 @@ Discourse.ListTopicsView = Discourse.View.extend(Discourse.Scrolling, {
if (!hasMoreResults) { if (!hasMoreResults) {
listTopicsView.get('eyeline').flushRest(); listTopicsView.get('eyeline').flushRest();
} }
}) });
}, },
// Remember where we were scrolled to // Remember where we were scrolled to

View File

@ -31,7 +31,7 @@ Discourse.PopupInputTipView = Discourse.View.extend({
bounce: function() { bounce: function() {
if( this.get('shownAt') ) { if( this.get('shownAt') ) {
var $elem = this.$() var $elem = this.$();
if( !this.animateAttribute ) { if( !this.animateAttribute ) {
this.animateAttribute = $elem.css('left') === 'auto' ? 'right' : 'left'; this.animateAttribute = $elem.css('left') === 'auto' ? 'right' : 'left';
} }

View File

@ -54,7 +54,7 @@ Discourse.TopicClosingView = Discourse.View.extend({
willDestroyElement: function() { willDestroyElement: function() {
if( this.delayedRerender ) { if( this.delayedRerender ) {
Em.run.cancel(this.delayedRerender) Em.run.cancel(this.delayedRerender);
} }
} }
}); });

View File

@ -46,7 +46,7 @@ Discourse.TopicSummaryView = Discourse.ContainerView.extend({
templateName: 'topic_summary/info', templateName: 'topic_summary/info',
topic: this.get('topic'), topic: this.get('topic'),
summaryView: this summaryView: this
}) });
this.trigger('appendSummaryInformation', this); this.trigger('appendSummaryInformation', this);
}, },

View File

@ -106,7 +106,7 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, {
this.resetExamineDockCache(); this.resetExamineDockCache();
// this happens after route exit, stuff could have trickled in // this happens after route exit, stuff could have trickled in
this.set('controller.controllers.header.showExtraInfo', false) this.set('controller.controllers.header.showExtraInfo', false);
}, },
didInsertElement: function(e) { didInsertElement: function(e) {

View File

@ -28,4 +28,4 @@ Discourse.View.reopenClass({
}); });
} }
}) });

View File

@ -20,7 +20,7 @@ class DiscourseIIFE < Sprockets::Processor
return data if path =~ /\.shbrs/ return data if path =~ /\.shbrs/
return data if path =~ /\.hbrs/ return data if path =~ /\.hbrs/
"(function () {\n\nvar $ = window.jQuery;\n\n#{data}\n\n})(this);" "(function () {\n\nvar $ = window.jQuery;\n// IIFE Wrapped Content Begins:\n\n#{data}\n\n// IIFE Wrapped Content Ends\n\n })(this);"
end end
end end

View File

@ -1,10 +1,10 @@
/*global md5:true */ /*global md5:true */
module("Discourse.BBCode"); module("Discourse.BBCode");
var format = function(input, expected, text) { var format = function(input, expected, text) {
// testing 1 2 3
equal(Discourse.BBCode.format(input, {lookupAvatar: false}), expected, text); equal(Discourse.BBCode.format(input, {lookupAvatar: false}), expected, text);
} };
test('basic bbcode', function() { test('basic bbcode', function() {
format("[b]strong[/b]", "<span class='bbcode-b'>strong</span>", "bolds text"); format("[b]strong[/b]", "<span class='bbcode-b'>strong</span>", "bolds text");
@ -50,7 +50,7 @@ test("quotes", function() {
var formatQuote = function(val, expected, text) { var formatQuote = function(val, expected, text) {
equal(Discourse.BBCode.buildQuoteBBCode(post, val), expected, text); equal(Discourse.BBCode.buildQuoteBBCode(post, val), expected, text);
} };
formatQuote(undefined, "", "empty string for undefined content"); formatQuote(undefined, "", "empty string for undefined content");
formatQuote(null, "", "empty string for null content"); formatQuote(null, "", "empty string for null content");

View File

@ -37,9 +37,10 @@ module("Discourse.ClickTrack", {
var track = Discourse.ClickTrack.trackClick; var track = Discourse.ClickTrack.trackClick;
// test
var generateClickEventOn = function(selector) { var generateClickEventOn = function(selector) {
return $.Event("click", { currentTarget: $(selector)[0] }); return $.Event("click", { currentTarget: $(selector)[0] });
} };
test("does not track clicks on lightboxes", function() { test("does not track clicks on lightboxes", function() {
var clickEvent = generateClickEventOn('.lightbox'); var clickEvent = generateClickEventOn('.lightbox');
@ -57,11 +58,11 @@ test("it calls preventDefault when clicking on an a", function() {
}); });
test("does not track clicks on back buttons", function() { test("does not track clicks on back buttons", function() {
ok(track(generateClickEventOn('.back'))) ok(track(generateClickEventOn('.back')));
}); });
test("does not track clicks on quote buttons", function() { test("does not track clicks on quote buttons", function() {
ok(track(generateClickEventOn('.quote-other-topic'))) ok(track(generateClickEventOn('.quote-other-topic')));
}); });
test("removes the href and put it as a data attribute", function() { test("removes the href and put it as a data attribute", function() {
@ -99,7 +100,7 @@ test("updates badge counts correctly", function() {
}); });
var trackRightClick = function() { var trackRightClick = function() {
var clickEvent = generateClickEventOn('a') var clickEvent = generateClickEventOn('a');
clickEvent.which = 3; clickEvent.which = 3;
return track(clickEvent); return track(clickEvent);
}; };

View File

@ -27,15 +27,15 @@ test("formating medium length dates", function() {
format = "medium"; format = "medium";
var strip = function(html){ var strip = function(html){
return $(html).text(); return $(html).text();
} };
var shortDate = function(days){ var shortDate = function(days){
return moment().subtract('days', days).format('D MMM'); return moment().subtract('days', days).format('D MMM');
} };
var shortDateYear = function(days){ var shortDateYear = function(days){
return moment().subtract('days', days).format('D MMM, YYYY'); return moment().subtract('days', days).format('D MMM, YYYY');
} };
leaveAgo = true; leaveAgo = true;
equal(strip(formatMins(1.4)), "1 min ago"); equal(strip(formatMins(1.4)), "1 min ago");

View File

@ -12,11 +12,10 @@ var cooked = function(input, expected, text) {
var cookedOptions = function(input, opts, expected, text) { var cookedOptions = function(input, opts, expected, text) {
equal(Discourse.Markdown.cook(input, opts), expected, text); equal(Discourse.Markdown.cook(input, opts), expected, text);
} };
test("basic cooking", function() { test("basic cooking", function() {
cooked("hello", "<p>hello</p>", "surrounds text with paragraphs"); cooked("hello", "<p>hello</p>", "surrounds text with paragraphs");
}); });
test("Line Breaks", function() { test("Line Breaks", function() {
@ -29,10 +28,10 @@ test("Line Breaks", function() {
cookedOptions(input, cookedOptions(input,
{traditional_markdown_linebreaks: true}, {traditional_markdown_linebreaks: true},
traditionalOutput, traditionalOutput,
"It supports traditional markdown via an option") "It supports traditional markdown via an option");
Discourse.SiteSettings.traditional_markdown_linebreaks = true; Discourse.SiteSettings.traditional_markdown_linebreaks = true;
cooked(input, traditionalOutput, "It supports traditional markdown via a Site Setting") cooked(input, traditionalOutput, "It supports traditional markdown via a Site Setting");
}); });

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,182 @@
module("JSHint");
var qHint = function(name, sourceFile, options, globals) {
if (sourceFile === undefined || typeof(sourceFile) == "object") {
// jsHintTest('file.js', [options])
globals = options;
options = sourceFile;
sourceFile = name;
}
return asyncTest(name, function() {
qHint.sendRequest(sourceFile, function(req) {
start();
if (req.status == 200) {
var text = req.responseText;
// Remove our generate IIFEs so we get the same line numbers as original
// files
text = text.replace(/^[^]*\/\/ IIFE Wrapped Content Begins:\n\n/m, "");
text = text.replace(/\n\n\/\/ IIFE Wrapped Content Ends[^]*$/m, "");
qHint.validateFile(text, options, globals);
} else {
ok(false, "HTTP error " + req.status +
" while fetching " + sourceFile);
}
});
});
};
qHint.validateFile = function (source, options, globals) {
var i, len, err;
if (JSHINT(source, options, globals)) {
ok(true);
return;
}
for (i = 0, len = JSHINT.errors.length; i < len; i++) {
err = JSHINT.errors[i];
if (!err) {
continue;
}
ok(false, err.reason +
" on line " + err.line +
", character " + err.character);
}
};
var XMLHttpFactories = [
function () { return new XMLHttpRequest(); },
function () { return new ActiveXObject("Msxml2.XMLHTTP"); },
function () { return new ActiveXObject("Msxml3.XMLHTTP"); },
function () { return new ActiveXObject("Microsoft.XMLHTTP"); }
];
function createXMLHTTPObject() {
for (var i = 0; i < XMLHttpFactories.length; i++) {
try {
return XMLHttpFactories[i]();
} catch (e) {}
}
return false;
}
// modified version of XHR script by PPK
// http://www.quirksmode.org/js/xmlhttp.html
// attached to qHint to allow substitution / mocking
qHint.sendRequest = function (url, callback) {
var req = createXMLHTTPObject();
if (!req) {
return;
}
var method = "GET";
req.open(method,url + "?" + (new Date().getTime()),true);
req.onreadystatechange = function () {
if (req.readyState != 4) {
return;
}
callback(req);
};
if (req.readyState == 4) {
return;
}
req.send();
};
var jsHintOpts = {
"predef":["Ember",
"jQuery",
"$",
"RSVP",
"Discourse",
"$LAB",
"Em",
"PreloadStore",
"Handlebars",
"I18n",
"bootbox",
"module",
"integration",
"test",
"ok",
"expect",
"equal",
"blank",
"present",
"visit",
"count",
"exists",
"asyncTest",
"find",
"resolvingPromise",
"sinon",
"moment",
"start",
"_",
"console",
"alert"],
"node" : false,
"browser" : true,
"boss" : true,
"curly": false,
"debug": false,
"devel": false,
"eqeqeq": true,
"evil": true,
"forin": false,
"immed": false,
"laxbreak": false,
"newcap": true,
"noarg": true,
"noempty": false,
"nonew": false,
"nomen": false,
"onevar": false,
"plusplus": false,
"regexp": false,
"undef": true,
"sub": true,
"strict": false,
"white": false,
"eqnull": true,
"lastsemic": true
};
<%
def jshint(dir, remove, to_ignore)
result = ""
Dir.glob(dir).each do |f|
filename = f.sub("#{Rails.root}#{remove}", "")
ok = true
to_ignore.each do |ig|
ok = false if (filename =~ ig)
end
result << "qHint('#{filename}', '/assets/#{filename}', jsHintOpts);\n" if ok
end
result
end
%>
<%= jshint("#{Rails.root}/test/**/*.js",
"/test/javascripts/",
[/helpers\//]) %>
<%= jshint("#{Rails.root}/app/assets/javascripts/**/*.js",
"/app/assets/javascripts/",
[/external\//,
/external_development\//,
/external_production\//,
/defer\//,
/locales\//]) %>

View File

@ -4,7 +4,7 @@ test('slugFor', function(){
var slugFor = function(args, val, text) { var slugFor = function(args, val, text) {
equal(Discourse.Category.slugFor(args), val, text); equal(Discourse.Category.slugFor(args), val, text);
} };
slugFor({slug: 'hello'}, "hello", "It calculates the proper slug for hello"); slugFor({slug: 'hello'}, "hello", "It calculates the proper slug for hello");
slugFor({id: 123, slug: ''}, "123-category", "It returns id-category for empty strings"); slugFor({id: 123, slug: ''}, "123-category", "It returns id-category for empty strings");

View File

@ -21,7 +21,7 @@ test('missingReplyCharacters', function() {
var missingReplyCharacters = function(val, isPM, expected, message) { var missingReplyCharacters = function(val, isPM, expected, message) {
var composer = Discourse.Composer.create({ reply: val, creatingPrivateMessage: isPM }); var composer = Discourse.Composer.create({ reply: val, creatingPrivateMessage: isPM });
equal(composer.get('missingReplyCharacters'), expected, message); equal(composer.get('missingReplyCharacters'), expected, message);
} };
missingReplyCharacters('hi', false, Discourse.SiteSettings.min_post_length - 2, 'too short public post'); missingReplyCharacters('hi', false, Discourse.SiteSettings.min_post_length - 2, 'too short public post');
missingReplyCharacters('hi', true, Discourse.SiteSettings.min_private_message_post_length - 2, 'too short private message'); missingReplyCharacters('hi', true, Discourse.SiteSettings.min_private_message_post_length - 2, 'too short private message');
@ -31,7 +31,7 @@ test('missingTitleCharacters', function() {
var missingTitleCharacters = function(val, isPM, expected, message) { var missingTitleCharacters = function(val, isPM, expected, message) {
var composer = Discourse.Composer.create({ title: val, creatingPrivateMessage: isPM }); var composer = Discourse.Composer.create({ title: val, creatingPrivateMessage: isPM });
equal(composer.get('missingTitleCharacters'), expected, message); equal(composer.get('missingTitleCharacters'), expected, message);
} };
missingTitleCharacters('hi', false, Discourse.SiteSettings.min_topic_title_length - 2, 'too short post title'); missingTitleCharacters('hi', false, Discourse.SiteSettings.min_topic_title_length - 2, 'too short post title');
missingTitleCharacters('z', true, Discourse.SiteSettings.min_private_message_title_length - 1, 'too short pm title'); missingTitleCharacters('z', true, Discourse.SiteSettings.min_private_message_title_length - 1, 'too short pm title');

View File

@ -32,8 +32,9 @@
//= require_tree ../../app/assets/javascripts/defer //= require_tree ../../app/assets/javascripts/defer
//= require sinon-1.7.1.js //= require sinon-1.7.1
//= require sinon-qunit-1.0.0.js //= require sinon-qunit-1.0.0
//= require jshint
//= require helpers/qunit_helpers //= require helpers/qunit_helpers
//= require helpers/assertions //= require helpers/assertions
@ -41,6 +42,7 @@
//= require_tree ./fixtures //= require_tree ./fixtures
//= require_tree . //= require_tree .
//= require_self //= require_self
//= require jshint_all
// sinon settings // sinon settings
sinon.config = { sinon.config = {

4023
vendor/assets/javascripts/jshint.js vendored Normal file

File diff suppressed because it is too large Load Diff