Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Kris Aubuchon 2014-07-25 00:19:38 -04:00
commit 328f9a4a39
24 changed files with 246 additions and 122 deletions

View File

@ -2057,7 +2057,13 @@ var html = (function(html4) {
}
// Discourse modification: give us more flexibility with whitelists
if (opt_nmTokenPolicy && opt_nmTokenPolicy(tagName, attribName, value)) { continue; }
if (opt_nmTokenPolicy) {
var newValue = opt_nmTokenPolicy(tagName, attribName, value);
if (newValue) {
attribs[i + 1] = newValue;
continue;
}
}
if (atype !== null) {
switch (atype) {

View File

@ -14,7 +14,8 @@ var PosterNameComponent = Em.Component.extend({
var name = post.get('name'),
username = post.get('username'),
linkClass = 'username',
primaryGroupName = post.get('primary_group_name');
primaryGroupName = post.get('primary_group_name'),
url = post.get('usernameUrl');
if (post.get('staff')) { linkClass += ' staff'; }
if (post.get('admin')) { linkClass += ' admin'; }
@ -25,7 +26,7 @@ var PosterNameComponent = Em.Component.extend({
linkClass += ' ' + primaryGroupName;
}
// Main link
buffer.push("<span class='" + linkClass + "'><a href='#'>" + username + "</a>");
buffer.push("<span class='" + linkClass + "'><a href='" + url + "' data-auto-route='true'>" + username + "</a>");
// Add a glyph if we have one
var glyph = this.posterGlyph(post);
@ -37,7 +38,7 @@ var PosterNameComponent = Em.Component.extend({
// Are we showing full names?
if (name && this.get('displayNameOnPosts') && (this.sanitizeName(name) !== this.sanitizeName(username))) {
name = Handlebars.Utils.escapeExpression(name);
buffer.push("<span class='full-name'><a href='#'>" + name + "</a></span>");
buffer.push("<span class='full-name'><a href='" + url + "' data-auto-route='true'>" + name + "</a></span>");
}
// User titles
@ -60,9 +61,10 @@ var PosterNameComponent = Em.Component.extend({
click: function(e) {
var $target = $(e.target),
href = $target.attr('href');
href = $target.attr('href'),
url = this.get('post.usernameUrl');
if (!Em.isEmpty(href) && href !== '#') {
if (!Em.isEmpty(href) && href !== url) {
return true;
} else {
this.appEvents.trigger('poster:expand', $target);

View File

@ -34,7 +34,11 @@ export default Discourse.ObjectController.extend({
var currentUsername = this.get('username'),
wasVisible = this.get('visible');
if (uploadedAvatarId) {
this.set('avatar', {username: username, uploaded_avatar_id: uploadedAvatarId});
} else {
this.set('avatar', null);
}
this.setProperties({visible: true, username: username});

View File

@ -4,6 +4,8 @@ function daysSinceEpoch(dt) {
return dt.getTime() / 86400000;
}
var safe = Handlebars.SafeString;
/**
Converts a date to a coldmap class
@ -68,7 +70,7 @@ function categoryLinkHTML(category, options) {
categoryOptions.categories = Em.Handlebars.get(this, options.hash.categories, options);
}
}
return new Handlebars.SafeString(Discourse.HTML.categoryBadge(category, categoryOptions));
return new safe(Discourse.HTML.categoryBadge(category, categoryOptions));
}
/**
@ -171,7 +173,7 @@ Handlebars.registerHelper('avatar', function(user, options) {
var uploadedAvatarId = Em.get(user, 'uploaded_avatar_id') || Em.get(user, 'user.uploaded_avatar_id');
var avatarTemplate = Discourse.User.avatarTemplate(username,uploadedAvatarId);
return new Handlebars.SafeString(Discourse.Utilities.avatarImg({
return new safe(Discourse.Utilities.avatarImg({
size: options.hash.imageSize,
extraClasses: Em.get(user, 'extras') || options.hash.extraClasses,
title: title || username,
@ -189,7 +191,9 @@ Handlebars.registerHelper('avatar', function(user, options) {
@for Handlebars
**/
Em.Handlebars.helper('bound-avatar', function(user, size, uploadId) {
if (Em.isEmpty(user)) { return; }
if (Em.isEmpty(user)) {
return new safe("<div class='avatar-placeholder'></div>");
}
var username = Em.get(user, 'username');
if(arguments.length < 4){
@ -198,7 +202,7 @@ Em.Handlebars.helper('bound-avatar', function(user, size, uploadId) {
var avatarTemplate = Discourse.User.avatarTemplate(username, uploadId);
return new Handlebars.SafeString(Discourse.Utilities.avatarImg({
return new safe(Discourse.Utilities.avatarImg({
size: size,
avatarTemplate: avatarTemplate
}));
@ -208,7 +212,7 @@ Em.Handlebars.helper('bound-avatar', function(user, size, uploadId) {
* Used when we only have a template
*/
Em.Handlebars.helper('bound-avatar-template', function(avatarTemplate, size) {
return new Handlebars.SafeString(Discourse.Utilities.avatarImg({
return new safe(Discourse.Utilities.avatarImg({
size: size,
avatarTemplate: avatarTemplate
}));
@ -243,7 +247,7 @@ Em.Handlebars.helper('bound-raw-date', function (date) {
**/
Handlebars.registerHelper('age', function(property, options) {
var dt = new Date(Ember.Handlebars.get(this, property, options));
return new Handlebars.SafeString(Discourse.Formatter.autoUpdatingRelativeAge(dt));
return new safe(Discourse.Formatter.autoUpdatingRelativeAge(dt));
});
/**
@ -254,7 +258,7 @@ Handlebars.registerHelper('age', function(property, options) {
**/
Handlebars.registerHelper('age-with-tooltip', function(property, options) {
var dt = new Date(Ember.Handlebars.get(this, property, options));
return new Handlebars.SafeString(Discourse.Formatter.autoUpdatingRelativeAge(dt, {title: true}));
return new safe(Discourse.Formatter.autoUpdatingRelativeAge(dt, {title: true}));
});
/**
@ -286,7 +290,7 @@ Handlebars.registerHelper('number', function(property, options) {
}
result += ">" + n + "</span>";
return new Handlebars.SafeString(result);
return new safe(result);
});
/**
@ -310,12 +314,12 @@ Handlebars.registerHelper('date', function(property, options) {
var val = Ember.Handlebars.get(this, property, options);
if (val) {
var date = new Date(val);
return new Handlebars.SafeString(Discourse.Formatter.autoUpdatingRelativeAge(date, {format: 'medium', title: true, leaveAgo: leaveAgo}));
return new safe(Discourse.Formatter.autoUpdatingRelativeAge(date, {format: 'medium', title: true, leaveAgo: leaveAgo}));
}
});
Em.Handlebars.helper('bound-date', function(dt) {
return new Handlebars.SafeString(Discourse.Formatter.autoUpdatingRelativeAge(new Date(dt), {format: 'medium', title: true }));
return new safe(Discourse.Formatter.autoUpdatingRelativeAge(new Date(dt), {format: 'medium', title: true }));
});
/**
@ -337,7 +341,7 @@ Handlebars.registerHelper('custom-html', function(name, contextString, options)
});
Em.Handlebars.helper('human-size', function(size) {
return new Handlebars.SafeString(I18n.toHumanSize(size));
return new safe(I18n.toHumanSize(size));
});
/**
@ -356,7 +360,7 @@ Handlebars.registerHelper('link-domain', function(property, options) {
if (!Em.isEmpty(domain)) {
var s = domain.split('.');
domain = s[s.length-2] + "." + s[s.length-1];
return new Handlebars.SafeString("<span class='domain'>" + domain + "</span>");
return new safe("<span class='domain'>" + domain + "</span>");
}
}
}
@ -378,5 +382,5 @@ Handlebars.registerHelper('icon', function(icon, options) {
if (labelKey) {
html += "<span class='sr-only'>" + I18n.t(labelKey) + "</span>";
}
return new Handlebars.SafeString(html);
return new safe(html);
});

View File

@ -1,13 +1,15 @@
export default {
name: "inject-app-events",
initialize: function(container, application) {
var AppEvents = Ember.Object.extend(Ember.Evented);
application.register('app-events:main', AppEvents, { singleton: true });
var appEvents = Ember.Object.createWithMixins(Ember.Evented);
application.register('app-events:main', appEvents, { instantiate: false });
application.inject('controller', 'appEvents', 'app-events:main');
application.inject('component', 'appEvents', 'app-events:main');
application.inject('route', 'appEvents', 'app-events:main');
application.inject('view', 'appEvents', 'app-events:main');
application.inject('model', 'appEvents', 'app-events:main');
Discourse.URL.appEvents = appEvents;
}
};

View File

@ -14,15 +14,6 @@ var _validClasses = {},
function validateAttribute(tagName, attribName, value) {
var tag = _validTags[tagName];
// Handle possible attacks
// if you include html in your markdown, it better be valid
//
// We are SUPER strict cause nokogiri will sometimes "correct"
// this stuff "incorrectly"
if(/[<>"'`]/.test(value)){
return;
}
// Handle classes
if (attribName === "class") {
if (_validClasses[value]) { return value; }

View File

@ -71,6 +71,20 @@ Discourse.URL = Em.Object.createWithMixins({
}
},
// Scroll to the same page, different anchor
scrollToId: function(id) {
if (Em.isEmpty(id)) { return; }
jumpScheduled = true;
Em.run.schedule('afterRender', function() {
var $elem = $(id);
if ($elem.length > 0) {
$('html,body').scrollTop($elem.offset().top - $('header').height() - 15);
jumpScheduled = false;
}
});
},
/**
Our custom routeTo method is used to intelligently overwrite default routing
behavior.
@ -98,12 +112,7 @@ Discourse.URL = Em.Object.createWithMixins({
// Scroll to the same page, different anchor
if (path.indexOf('#') === 0) {
var $elem = $(path);
if ($elem.length > 0) {
Em.run.schedule('afterRender', function() {
$('html,body').scrollTop($elem.offset().top - $('header').height() - 15);
});
}
this.scrollToId(path);
return;
}
@ -136,8 +145,10 @@ Discourse.URL = Em.Object.createWithMixins({
// TODO: Extract into rules we can inject into the URL handler
if (this.navigatedToHome(oldPath, path)) { return; }
if (path.match(/^\/?users\/[^\/]+$/)) {
path += "/activity";
if (oldPath === path) {
// If navigating to the same path send an app event. Views can watch it
// and tell their controllers to refresh
this.appEvents.trigger('url:refresh');
}
return this.handleURL(path);
@ -235,12 +246,7 @@ Discourse.URL = Em.Object.createWithMixins({
var homepage = Discourse.Utilities.defaultHomepage();
if (window.history && window.history.pushState && path === "/" && (oldPath === "/" || oldPath === "/" + homepage)) {
// refresh the list
switch (homepage) {
case "top" : { this.controllerFor('discovery/top').send('refresh'); break; }
case "categories": { this.controllerFor('discovery/categories').send('refresh'); break; }
default: { this.controllerFor('discovery/topics').send('refresh'); break; }
}
this.appEvents.trigger('url:refresh');
return true;
}

View File

@ -0,0 +1,19 @@
// A Mixin that a view can use to listen for 'url:refresh' when
// it is on screen, and will send an action to the controller to
// refresh its data.
//
// This is useful if you want to get around Ember's default
// behavior of not refreshing when navigating to the same place.
export default Em.Mixin.create({
_initURLRefresh: function() {
this.appEvents.on('url:refresh', this, '_urlRefresh');
}.on('didInsertElement'),
_tearDownURLRefresh: function() {
this.appEvents.off('url:refresh', this, '_urlRefresh');
}.on('willDestroyElement'),
_urlRefresh: function() {
this.get('controller').send('refresh');
}
});

View File

@ -32,7 +32,15 @@ Ember.DiscourseLocation = Ember.Object.extend({
*/
initState: function() {
set(this, 'history', get(this, 'history') || window.history);
this.replaceState(this.formatURL(this.getURL()));
var url = this.formatURL(this.getURL()),
loc = get(this, 'location');
if (loc && loc.hash) {
url += loc.hash;
}
this.replaceState(url);
},
/**

View File

@ -21,6 +21,13 @@ Discourse.StaticController.PAGES.forEach(function(page) {
}
},
activate: function() {
this._super();
// Scroll to an element if exists
Discourse.URL.scrollToId(document.location.hash);
},
model: function() {
return Discourse.StaticPage.find(page);
},

View File

@ -98,7 +98,11 @@ Discourse.TopicRoute = Discourse.Route.extend({
}
},
willTransition: function() { isTransitioning = true; return true; }
willTransition: function() {
Em.run.cancel(scheduledReplace);
isTransitioning = true;
return true;
}
},
// replaceState can be very slow on Android Chrome. This function debounces replaceState

View File

@ -1,4 +1,6 @@
export default Discourse.View.extend({
import UrlRefresh from 'discourse/mixins/url-refresh';
export default Discourse.View.extend(UrlRefresh, {
orderingChanged: function(){
if (this.get("controller.ordering")) {

View File

@ -1 +1,3 @@
export default Discourse.View.extend(Discourse.ScrollTop);
import UrlRefresh from 'discourse/mixins/url-refresh';
export default Discourse.View.extend(Discourse.ScrollTop, UrlRefresh);

View File

@ -1,13 +1,6 @@
/**
This view handles rendering of a list of topics under discovery, with support
for loading more as well as remembering your scroll position.
import UrlRefresh from 'discourse/mixins/url-refresh';
@class DiscoveryTopicsView
@extends Discourse.View
@namespace Discourse
@module Discourse
**/
export default Discourse.View.extend(Discourse.LoadMore, {
export default Discourse.View.extend(Discourse.LoadMore, UrlRefresh, {
eyelineSelector: '.topic-list-item',
actions: {

View File

@ -5,11 +5,26 @@ export default Discourse.View.extend({
classNameBindings: ['controller.visible::hidden', 'controller.showBadges'],
_setup: function() {
var self = this;
this.appEvents.on('poster:expand', this, '_posterExpand');
$('html').off(clickOutsideEventName).on(clickOutsideEventName, function(e) {
if (self.get('controller.visible')) {
var $target = $(e.target);
if ($target.closest('.trigger-expansion').length > 0) { return; }
if (self.$().has(e.target).length !== 0) { return; }
self.get('controller').close();
}
return true;
});
}.on('didInsertElement'),
_posterExpand: function(target) {
if (!target) { return; }
var self = this,
width = this.$().width();
this.appEvents.on('poster:expand', function(target) {
if (!target) { return; }
Em.run.schedule('afterRender', function() {
if (target) {
var position = target.offset();
@ -25,24 +40,11 @@ export default Discourse.View.extend({
}
}
});
});
$('html').off(clickOutsideEventName).on(clickOutsideEventName, function(e) {
if (self.get('controller.visible')) {
var $target = $(e.target);
if ($target.closest('.trigger-expansion').length > 0) { return; }
if (self.$().has(e.target).length !== 0) { return; }
self.get('controller').close();
}
return true;
});
}.on('didInsertElement'),
},
_removeEvents: function() {
$('html').off(clickOutsideEventName);
this.appEvents.off('poster:expand');
this.appEvents.off('poster:expand', this, '_posterExpand');
}.on('willDestroyElement')
});

View File

@ -11,6 +11,13 @@
padding: 12px 12px 5px 12px;
border: 1px solid scale-color-diff();
.avatar-placeholder {
width: 120px;
height: 120px;
float: left;
padding-right: 10px;
}
h1 {
display: inline-block;
min-width: 120px;

View File

@ -0,0 +1,15 @@
module Jobs
class CalculateAvgTime < Jobs::Scheduled
every 1.day
# PERF: these calculations can become exceedingly expnsive
# they run a huge gemoetric mean and are hard to optimise
# defer to only run once a day
def execute(args)
# Update the average times
Post.calculate_avg_time(2.days.ago)
Topic.calculate_avg_time(2.days.ago)
end
end
end

View File

@ -8,9 +8,6 @@ module Jobs
every 15.minutes
def execute(args)
# Update the average times
Post.calculate_avg_time(1.day.ago)
Topic.calculate_avg_time(1.day.ago)
# Feature topics in categories
CategoryFeaturedTopic.feature_topics

View File

@ -23,8 +23,15 @@ class ExcerptParser < Nokogiri::XML::SAX::Document
me.excerpt
end
def escape_attribute(v)
v.gsub("&", "&amp;")
.gsub("\"", "&#34;")
.gsub("<", "&lt;")
.gsub(">", "&gt;")
end
def include_tag(name, attributes)
characters("<#{name} #{attributes.map{|k,v| "#{k}='#{v}'"}.join(' ')}>", false, false, false)
characters("<#{name} #{attributes.map{|k,v| "#{k}=\"#{escape_attribute(v)}\""}.join(' ')}>", false, false, false)
end
def start_element(name, attributes=[])

View File

@ -264,8 +264,21 @@ module SiteSettingExtension
refresh_settings.include?(name.to_sym)
end
def filter_value(name, value)
# filter domain name
if %w[disabled_image_download_domains onebox_domains_whitelist exclude_rel_nofollow_domains email_domains_blacklist email_domains_whitelist white_listed_spam_host_domains].include? name
domain_array = []
value.split('|').each { |url|
domain_array.push(get_hostname(url))
}
value = domain_array.join("|")
end
return value
end
def set(name, value)
if has_setting?(name)
value = filter_value(name, value)
self.send("#{name}=", value)
Discourse.request_refresh! if requires_refresh?(name)
else
@ -365,5 +378,13 @@ module SiteSettingExtension
enums[name]
end
def get_hostname(url)
unless (URI.parse(url).scheme rescue nil).nil?
url = "http://#{url}" if URI.parse(url).scheme.nil?
url = URI.parse(url).host
end
return url
end
end

View File

@ -75,6 +75,15 @@ describe PrettyText do
describe "Excerpt" do
it "sanitizes attempts to inject invalid attributes" do
spinner = "<a href=\"http://thedailywtf.com/\" data-bbcode=\"' class='fa fa-spin\">WTF</a>"
PrettyText.excerpt(spinner, 20).should match_html spinner
spinner = %q{<a href="http://thedailywtf.com/" title="' class=&quot;fa fa-spin&quot;&gt;&lt;img src='http://thedailywtf.com/Resources/Images/Primary/logo.gif"></a>}
PrettyText.excerpt(spinner, 20).should match_html spinner
end
context "images" do
it "should dump images" do
@ -94,8 +103,8 @@ describe PrettyText do
end
it "should keep spoilers" do
PrettyText.excerpt("<div class='spoiler'><img src='http://cnn.com/a.gif'></div>", 100).should == "<span class='spoiler'>[image]</span>"
PrettyText.excerpt("<span class='spoiler'>spoiler</div>", 100).should == "<span class='spoiler'>spoiler</span>"
PrettyText.excerpt("<div class='spoiler'><img src='http://cnn.com/a.gif'></div>", 100).should match_html "<span class='spoiler'>[image]</span>"
PrettyText.excerpt("<span class='spoiler'>spoiler</div>", 100).should match_html "<span class='spoiler'>spoiler</span>"
end
end
@ -104,7 +113,7 @@ describe PrettyText do
end
it "should preserve links" do
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",100).should == "<a href='http://cnn.com'>cnn</a>"
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",100).should match_html "<a href='http://cnn.com'>cnn</a>"
end
it "should deal with special keys properly" do
@ -125,15 +134,15 @@ describe PrettyText do
end
it "should not count the surrounds of a link" do
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",3).should == "<a href='http://cnn.com'>cnn</a>"
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",3).should match_html "<a href='http://cnn.com'>cnn</a>"
end
it "uses an ellipsis instead of html entities if provided with the option" do
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>", 2, text_entities: true).should == "<a href='http://cnn.com'>cn...</a>"
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>", 2, text_entities: true).should match_html "<a href='http://cnn.com'>cn...</a>"
end
it "should truncate links" do
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",2).should == "<a href='http://cnn.com'>cn&hellip;</a>"
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",2).should match_html "<a href='http://cnn.com'>cn&hellip;</a>"
end
it "doesn't extract empty quotes as links" do
@ -294,9 +303,6 @@ describe PrettyText do
PrettyText.cook("**你hello**").should match_html "<p><strong>你hello</strong></p>"
end
it "sanitizes attempts to inject invalid attributes" do
PrettyText.cook("<a href=\"http://thedailywtf.com/\" data-bbcode=\"' class='fa fa-spin\">WTF</a>").should == "<p><a href=\"http://thedailywtf.com/\" rel=\"nofollow\">WTF</a></p>"
end
end
end

View File

@ -322,4 +322,21 @@ describe SiteSettingExtension do
end
end
describe "filter domain name" do
before do
settings.setting(:white_listed_spam_host_domains, "www.example.com")
settings.refresh!
end
it "filters domain" do
settings.set("white_listed_spam_host_domains", "http://www.discourse.org/")
settings.white_listed_spam_host_domains.should == "www.discourse.org"
end
it "returns invalid domain as is, without throwing exception" do
settings.set("white_listed_spam_host_domains", "test!url")
settings.white_listed_spam_host_domains.should == "test!url"
end
end
end

View File

@ -57,7 +57,7 @@ describe UserProfile do
end
context 'with a user that has a link in their bio' do
let(:user_profile) { Fabricate.build(:user_profile, bio_raw: "im sissy and i love http://ponycorns.com") }
let(:user_profile) { Fabricate.build(:user_profile, bio_raw: "I love http://discourse.org") }
let(:user) do
user = Fabricate.build(:user, user_profile: user_profile)
user_profile.user = user
@ -66,22 +66,22 @@ describe UserProfile do
let(:created_user) do
user = Fabricate(:user)
user.user_profile.bio_raw = 'im sissy and i love http://ponycorns.com'
user.user_profile.bio_raw = 'I love http://discourse.org'
user.user_profile.save!
user
end
it 'includes the link as nofollow if the user is not new' do
user.user_profile.send(:cook)
expect(user_profile.bio_excerpt).to eq("im sissy and i love <a href='http://ponycorns.com' rel='nofollow'>http://ponycorns.com</a>")
expect(user_profile.bio_processed).to eq("<p>im sissy and i love <a href=\"http://ponycorns.com\" rel=\"nofollow\">http://ponycorns.com</a></p>")
expect(user_profile.bio_excerpt).to match_html("I love <a href='http://discourse.org' rel='nofollow'>http://discourse.org</a>")
expect(user_profile.bio_processed).to match_html("<p>I love <a href=\"http://discourse.org\" rel=\"nofollow\">http://discourse.org</a></p>")
end
it 'removes the link if the user is new' do
user.trust_level = TrustLevel.levels[:newuser]
user_profile.send(:cook)
expect(user_profile.bio_excerpt).to eq("im sissy and i love http://ponycorns.com")
expect(user_profile.bio_processed).to eq("<p>im sissy and i love http://ponycorns.com</p>")
expect(user_profile.bio_excerpt).to match_html("I love http://discourse.org")
expect(user_profile.bio_processed).to eq("<p>I love http://discourse.org</p>")
end
context 'leader_links_no_follow is false' do
@ -90,22 +90,22 @@ describe UserProfile do
it 'includes the link without nofollow if the user is trust level 3 or higher' do
user.trust_level = TrustLevel.levels[:leader]
user_profile.send(:cook)
expect(user_profile.bio_excerpt).to eq("im sissy and i love <a href='http://ponycorns.com'>http://ponycorns.com</a>")
expect(user_profile.bio_processed).to eq("<p>im sissy and i love <a href=\"http://ponycorns.com\">http://ponycorns.com</a></p>")
expect(user_profile.bio_excerpt).to match_html("I love <a href='http://discourse.org'>http://discourse.org</a>")
expect(user_profile.bio_processed).to match_html("<p>I love <a href=\"http://discourse.org\">http://discourse.org</a></p>")
end
it 'removes nofollow from links in bio when trust level is increased' do
created_user.change_trust_level!(:leader)
expect(created_user.user_profile.bio_excerpt).to eq("im sissy and i love <a href='http://ponycorns.com'>http://ponycorns.com</a>")
expect(created_user.user_profile.bio_processed).to eq("<p>im sissy and i love <a href=\"http://ponycorns.com\">http://ponycorns.com</a></p>")
expect(created_user.user_profile.bio_excerpt).to match_html("I love <a href='http://discourse.org'>http://discourse.org</a>")
expect(created_user.user_profile.bio_processed).to match_html("<p>I love <a href=\"http://discourse.org\">http://discourse.org</a></p>")
end
it 'adds nofollow to links in bio when trust level is decreased' do
created_user.trust_level = TrustLevel.levels[:leader]
created_user.save
created_user.change_trust_level!(:regular)
expect(created_user.user_profile.bio_excerpt).to eq("im sissy and i love <a href='http://ponycorns.com' rel='nofollow'>http://ponycorns.com</a>")
expect(created_user.user_profile.bio_processed).to eq("<p>im sissy and i love <a href=\"http://ponycorns.com\" rel=\"nofollow\">http://ponycorns.com</a></p>")
expect(created_user.user_profile.bio_excerpt).to match_html("I love <a href='http://discourse.org' rel='nofollow'>http://discourse.org</a>")
expect(created_user.user_profile.bio_processed).to match_html("<p>I love <a href=\"http://discourse.org\" rel=\"nofollow\">http://discourse.org</a></p>")
end
end
@ -115,8 +115,8 @@ describe UserProfile do
it 'includes the link with nofollow if the user is trust level 3 or higher' do
user.trust_level = TrustLevel.levels[:leader]
user_profile.send(:cook)
expect(user_profile.bio_excerpt).to eq("im sissy and i love <a href='http://ponycorns.com' rel='nofollow'>http://ponycorns.com</a>")
expect(user_profile.bio_processed).to eq("<p>im sissy and i love <a href=\"http://ponycorns.com\" rel=\"nofollow\">http://ponycorns.com</a></p>")
expect(user_profile.bio_excerpt).to match_html("I love <a href='http://discourse.org' rel='nofollow'>http://discourse.org</a>")
expect(user_profile.bio_processed).to match_html("<p>I love <a href=\"http://discourse.org\" rel=\"nofollow\">http://discourse.org</a></p>")
end
end
end

View File

@ -28,19 +28,21 @@ test("isInternal with a HTTPS url", function() {
// ok(Discourse.URL.routeTo("/t/topic-title/42"), "can route relative");
// });
test("navigatedToHome", function() {
var fakeDiscoveryController = { send: function() { return true; } };
var mock = sinon.mock(fakeDiscoveryController);
this.stub(Discourse.URL, "controllerFor").returns(fakeDiscoveryController);
// TODO pending: this works but the test is too mocky and needs to be fixed
mock.expects("send").withArgs('refresh').twice();
ok(Discourse.URL.navigatedToHome("/", "/"));
var homepage = "/" + Discourse.Utilities.defaultHomepage();
ok(Discourse.URL.navigatedToHome(homepage, "/"));
not(Discourse.URL.navigatedToHome("/old", "/new"));
// make sure we called the .refresh() method
mock.verify();
});
// test("navigatedToHome", function() {
// var fakeDiscoveryController = { send: function() { return true; } };
// var mock = sinon.mock(fakeDiscoveryController);
// this.stub(Discourse.URL, "controllerFor").returns(fakeDiscoveryController);
//
// mock.expects("send").withArgs('refresh').twice();
// ok(Discourse.URL.navigatedToHome("/", "/"));
//
// var homepage = "/" + Discourse.Utilities.defaultHomepage();
// ok(Discourse.URL.navigatedToHome(homepage, "/"));
//
// not(Discourse.URL.navigatedToHome("/old", "/new"));
//
// // make sure we called the .refresh() method
// mock.verify();
// });