Merge branch 'master' of github.com:discourse/discourse
This commit is contained in:
commit
67b262b93e
14 changed files with 110 additions and 43 deletions
app
assets/javascripts
controllers
models
serializers
config
lib
spec
|
@ -24,6 +24,12 @@ window.Discourse = Ember.Application.createWithMixins(Discourse.Ajax, {
|
||||||
return u + url;
|
return u + url;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getURLWithCDN: function(url) {
|
||||||
|
url = this.getURL(url);
|
||||||
|
if (Discourse.CDN) { url = Discourse.CDN + url; }
|
||||||
|
return url;
|
||||||
|
},
|
||||||
|
|
||||||
Resolver: DiscourseResolver,
|
Resolver: DiscourseResolver,
|
||||||
|
|
||||||
_titleChanged: function() {
|
_titleChanged: function() {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
var esc = Handlebars.Utils.escapeExpression;
|
var esc = Handlebars.Utils.escapeExpression;
|
||||||
|
|
||||||
Discourse.BBCode.register('quote', {noWrap: true, singlePara: true}, function(contents, bbParams, options) {
|
Discourse.BBCode.register('quote', {noWrap: true, singlePara: true}, function(contents, bbParams, options) {
|
||||||
var params = {'class': 'quote'},
|
var params = {'class': 'quote'},
|
||||||
username = null;
|
username = null;
|
||||||
|
|
|
@ -72,10 +72,9 @@ Discourse.User = Discourse.Model.extend({
|
||||||
@type {String}
|
@type {String}
|
||||||
**/
|
**/
|
||||||
profileBackground: function() {
|
profileBackground: function() {
|
||||||
var background = this.get('profile_background');
|
var url = this.get('profile_background');
|
||||||
if(Em.isEmpty(background) || !Discourse.SiteSettings.allow_profile_backgrounds) { return; }
|
if (Em.isEmpty(url) || !Discourse.SiteSettings.allow_profile_backgrounds) { return; }
|
||||||
|
return 'background-image: url(' + Discourse.getURLWithCDN(url) + ')';
|
||||||
return 'background-image: url(' + background + ')';
|
|
||||||
}.property('profile_background'),
|
}.property('profile_background'),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -442,6 +441,7 @@ Discourse.User.reopenClass(Discourse.Singleton, {
|
||||||
|
|
||||||
avatarTemplate: function(username, uploadedAvatarId) {
|
avatarTemplate: function(username, uploadedAvatarId) {
|
||||||
var url;
|
var url;
|
||||||
|
|
||||||
if (uploadedAvatarId) {
|
if (uploadedAvatarId) {
|
||||||
url = "/user_avatar/" +
|
url = "/user_avatar/" +
|
||||||
Discourse.BaseUrl +
|
Discourse.BaseUrl +
|
||||||
|
@ -456,11 +456,7 @@ Discourse.User.reopenClass(Discourse.Singleton, {
|
||||||
Discourse.LetterAvatarVersion + ".png";
|
Discourse.LetterAvatarVersion + ".png";
|
||||||
}
|
}
|
||||||
|
|
||||||
url = Discourse.getURL(url);
|
return Discourse.getURLWithCDN(url);
|
||||||
if (Discourse.CDN) {
|
|
||||||
url = Discourse.CDN + url;
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -11,6 +11,7 @@ export default Discourse.View.extend(CleansUp, {
|
||||||
|
|
||||||
addBackground: function() {
|
addBackground: function() {
|
||||||
var url = this.get('controller.user.card_background');
|
var url = this.get('controller.user.card_background');
|
||||||
|
|
||||||
if (!this.get('allowBackgrounds')) { return; }
|
if (!this.get('allowBackgrounds')) { return; }
|
||||||
|
|
||||||
var $this = this.$();
|
var $this = this.$();
|
||||||
|
@ -19,7 +20,7 @@ export default Discourse.View.extend(CleansUp, {
|
||||||
if (Ember.isEmpty(url)) {
|
if (Ember.isEmpty(url)) {
|
||||||
$this.css('background-image', '').addClass('no-bg');
|
$this.css('background-image', '').addClass('no-bg');
|
||||||
} else {
|
} else {
|
||||||
$this.css('background-image', "url(" + url + ")").removeClass('no-bg');
|
$this.css('background-image', "url(" + Discourse.getURLWithCDN(url) + ")").removeClass('no-bg');
|
||||||
}
|
}
|
||||||
}.observes('controller.user.card_background'),
|
}.observes('controller.user.card_background'),
|
||||||
|
|
||||||
|
|
|
@ -155,7 +155,7 @@ class ApplicationController < ActionController::Base
|
||||||
# If we are rendering HTML, preload the session data
|
# If we are rendering HTML, preload the session data
|
||||||
def preload_json
|
def preload_json
|
||||||
# We don't preload JSON on xhr or JSON request
|
# We don't preload JSON on xhr or JSON request
|
||||||
return if request.xhr?
|
return if request.xhr? || request.format.json?
|
||||||
|
|
||||||
preload_anonymous_data
|
preload_anonymous_data
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ require_dependency 'distributed_memoizer'
|
||||||
class PostsController < ApplicationController
|
class PostsController < ApplicationController
|
||||||
|
|
||||||
# Need to be logged in for all actions here
|
# Need to be logged in for all actions here
|
||||||
before_filter :ensure_logged_in, except: [:show, :replies, :by_number, :short_link, :reply_history, :revisions, :latest_revision, :expand_embed, :markdown, :raw, :cooked]
|
before_filter :ensure_logged_in, except: [:show, :replies, :by_number, :short_link, :reply_history, :revisions, :latest_revision, :expand_embed, :markdown_id, :markdown_num, :cooked, :latest]
|
||||||
|
|
||||||
skip_before_filter :check_xhr, only: [:markdown_id, :markdown_num, :short_link]
|
skip_before_filter :check_xhr, only: [:markdown_id, :markdown_num, :short_link]
|
||||||
|
|
||||||
|
@ -25,6 +25,33 @@ class PostsController < ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def latest
|
||||||
|
params.permit(:before)
|
||||||
|
last_post_id = params[:before].to_i
|
||||||
|
last_post_id = Post.last.id if last_post_id <= 0
|
||||||
|
|
||||||
|
# last 50 post IDs only, to avoid counting deleted posts in security check
|
||||||
|
posts = Post.order(created_at: :desc)
|
||||||
|
.where('posts.id <= ?', last_post_id)
|
||||||
|
.where('posts.id > ?', last_post_id - 50)
|
||||||
|
.includes(topic: :category)
|
||||||
|
.includes(:user)
|
||||||
|
.limit(50)
|
||||||
|
# Remove posts the user doesn't have permission to see
|
||||||
|
# This isn't leaking any information we weren't already through the post ID numbers
|
||||||
|
posts = posts.reject { |post| !guardian.can_see?(post) }
|
||||||
|
|
||||||
|
counts = PostAction.counts_for(posts, current_user)
|
||||||
|
|
||||||
|
render_json_dump(serialize_data(posts,
|
||||||
|
PostSerializer,
|
||||||
|
scope: guardian,
|
||||||
|
root: 'latest_posts',
|
||||||
|
add_raw: true,
|
||||||
|
all_post_actions: counts)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def cooked
|
def cooked
|
||||||
post = find_post_from_params
|
post = find_post_from_params
|
||||||
render json: {cooked: post.cooked}
|
render json: {cooked: post.cooked}
|
||||||
|
|
|
@ -78,17 +78,6 @@ module HasCustomFields
|
||||||
!@custom_fields || @custom_fields_orig == @custom_fields
|
!@custom_fields || @custom_fields_orig == @custom_fields
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
def refresh_custom_fields_from_db
|
|
||||||
target = Hash.new
|
|
||||||
_custom_fields.pluck(:name,:value).each do |key, value|
|
|
||||||
self.class.append_custom_field(target, key, value)
|
|
||||||
end
|
|
||||||
@custom_fields_orig = target
|
|
||||||
@custom_fields = @custom_fields_orig.dup
|
|
||||||
end
|
|
||||||
|
|
||||||
def save_custom_fields
|
def save_custom_fields
|
||||||
if !custom_fields_clean?
|
if !custom_fields_clean?
|
||||||
dup = @custom_fields.dup
|
dup = @custom_fields.dup
|
||||||
|
@ -134,4 +123,16 @@ module HasCustomFields
|
||||||
refresh_custom_fields_from_db
|
refresh_custom_fields_from_db
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def refresh_custom_fields_from_db
|
||||||
|
target = Hash.new
|
||||||
|
_custom_fields.pluck(:name,:value).each do |key, value|
|
||||||
|
self.class.append_custom_field(target, key, value)
|
||||||
|
end
|
||||||
|
@custom_fields_orig = target
|
||||||
|
@custom_fields = @custom_fields_orig.dup
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -62,7 +62,7 @@ class User < ActiveRecord::Base
|
||||||
|
|
||||||
delegate :last_sent_email_address, :to => :email_logs
|
delegate :last_sent_email_address, :to => :email_logs
|
||||||
|
|
||||||
before_validation :downcase_email
|
before_validation :strip_downcase_email
|
||||||
|
|
||||||
validates_presence_of :username
|
validates_presence_of :username
|
||||||
validate :username_validator
|
validate :username_validator
|
||||||
|
@ -764,8 +764,11 @@ class User < ActiveRecord::Base
|
||||||
self.username_lower = username.downcase
|
self.username_lower = username.downcase
|
||||||
end
|
end
|
||||||
|
|
||||||
def downcase_email
|
def strip_downcase_email
|
||||||
self.email = self.email.downcase if self.email
|
if self.email
|
||||||
|
self.email = self.email.strip
|
||||||
|
self.email = self.email.downcase
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def username_validator
|
def username_validator
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
class PostSerializer < BasicPostSerializer
|
class PostSerializer < BasicPostSerializer
|
||||||
|
|
||||||
# To pass in additional information we might need
|
# To pass in additional information we might need
|
||||||
attr_accessor :topic_view,
|
INSTANCE_VARS = [:topic_view,
|
||||||
:parent_post,
|
:parent_post,
|
||||||
:add_raw,
|
:add_raw,
|
||||||
:single_post_link_counts,
|
:single_post_link_counts,
|
||||||
:draft_sequence,
|
:draft_sequence,
|
||||||
:post_actions
|
:post_actions,
|
||||||
|
:all_post_actions]
|
||||||
|
|
||||||
|
INSTANCE_VARS.each do |v|
|
||||||
|
self.send(:attr_accessor, v)
|
||||||
|
end
|
||||||
|
|
||||||
attributes :post_number,
|
attributes :post_number,
|
||||||
:post_type,
|
:post_type,
|
||||||
|
@ -54,6 +59,15 @@ class PostSerializer < BasicPostSerializer
|
||||||
:static_doc,
|
:static_doc,
|
||||||
:via_email
|
:via_email
|
||||||
|
|
||||||
|
def initialize(object, opts)
|
||||||
|
super(object, opts)
|
||||||
|
PostSerializer::INSTANCE_VARS.each do |name|
|
||||||
|
if opts.include? name
|
||||||
|
self.send("#{name}=", opts[name])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def topic_slug
|
def topic_slug
|
||||||
object.try(:topic).try(:slug)
|
object.try(:topic).try(:slug)
|
||||||
end
|
end
|
||||||
|
@ -155,6 +169,13 @@ class PostSerializer < BasicPostSerializer
|
||||||
scope.is_staff? && object.deleted_by.present?
|
scope.is_staff? && object.deleted_by.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Helper function to decide between #post_actions and @all_post_actions
|
||||||
|
def actions
|
||||||
|
return post_actions if post_actions.present?
|
||||||
|
return all_post_actions[object.id] if all_post_actions.present?
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
# Summary of the actions taken on this post
|
# Summary of the actions taken on this post
|
||||||
def actions_summary
|
def actions_summary
|
||||||
result = []
|
result = []
|
||||||
|
@ -168,7 +189,7 @@ class PostSerializer < BasicPostSerializer
|
||||||
id: id,
|
id: id,
|
||||||
count: count,
|
count: count,
|
||||||
hidden: (sym == :vote),
|
hidden: (sym == :vote),
|
||||||
can_act: scope.post_can_act?(object, sym, taken_actions: post_actions)
|
can_act: scope.post_can_act?(object, sym, taken_actions: actions)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sym == :notify_user && scope.current_user.present? && scope.current_user == object.user
|
if sym == :notify_user && scope.current_user.present? && scope.current_user == object.user
|
||||||
|
@ -183,9 +204,9 @@ class PostSerializer < BasicPostSerializer
|
||||||
active_flags[id].count > 0
|
active_flags[id].count > 0
|
||||||
end
|
end
|
||||||
|
|
||||||
if post_actions.present? && post_actions.has_key?(id)
|
if actions.present? && actions.has_key?(id)
|
||||||
action_summary[:acted] = true
|
action_summary[:acted] = true
|
||||||
action_summary[:can_undo] = scope.can_delete?(post_actions[id])
|
action_summary[:can_undo] = scope.can_delete?(actions[id])
|
||||||
end
|
end
|
||||||
|
|
||||||
# only show public data
|
# only show public data
|
||||||
|
@ -226,7 +247,7 @@ class PostSerializer < BasicPostSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
def include_bookmarked?
|
def include_bookmarked?
|
||||||
post_actions.present? && post_actions.keys.include?(PostActionType.types[:bookmark])
|
actions.present? && actions.keys.include?(PostActionType.types[:bookmark])
|
||||||
end
|
end
|
||||||
|
|
||||||
def include_display_username?
|
def include_display_username?
|
||||||
|
|
|
@ -1013,7 +1013,7 @@ en:
|
||||||
|
|
||||||
disable_edit_notifications: "Disables edit notifications by the system user when 'download_remote_images_to_local' is active."
|
disable_edit_notifications: "Disables edit notifications by the system user when 'download_remote_images_to_local' is active."
|
||||||
|
|
||||||
enable_names: "Allow showing user full names. Disable to hide full names."
|
enable_names: "Show the user's full name on their profile, user card, and emails. Disable to hide full name everywhere."
|
||||||
display_name_on_posts: "Show a user's full name on their posts in addition to their @username."
|
display_name_on_posts: "Show a user's full name on their posts in addition to their @username."
|
||||||
invites_per_page: "Default invites shown on the user page."
|
invites_per_page: "Default invites shown on the user page."
|
||||||
short_progress_text_threshold: "After the number of posts in a topic goes above this number, the progress bar will only show the current post number. If you change the progress bar's width, you may need to change this value."
|
short_progress_text_threshold: "After the number of posts in a topic goes above this number, the progress bar will only show the current post number. If you change the progress bar's width, you may need to change this value."
|
||||||
|
|
|
@ -269,6 +269,7 @@ Discourse::Application.routes.draw do
|
||||||
get "uploads/:site/:sha" => "uploads#show", constraints: { site: /\w+/, sha: /[a-z0-9]{40}/}
|
get "uploads/:site/:sha" => "uploads#show", constraints: { site: /\w+/, sha: /[a-z0-9]{40}/}
|
||||||
post "uploads" => "uploads#create"
|
post "uploads" => "uploads#create"
|
||||||
|
|
||||||
|
get "posts" => "posts#latest"
|
||||||
get "posts/by_number/:topic_id/:post_number" => "posts#by_number"
|
get "posts/by_number/:topic_id/:post_number" => "posts#by_number"
|
||||||
get "posts/:id/reply-history" => "posts#reply_history"
|
get "posts/:id/reply-history" => "posts#reply_history"
|
||||||
get "posts/:username/deleted" => "posts#deleted_posts", constraints: {username: USERNAME_ROUTE_FORMAT}
|
get "posts/:username/deleted" => "posts#deleted_posts", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
require 'v8'
|
require 'v8'
|
||||||
require 'nokogiri'
|
require 'nokogiri'
|
||||||
|
require_dependency 'url_helper'
|
||||||
require_dependency 'excerpt_parser'
|
require_dependency 'excerpt_parser'
|
||||||
require_dependency 'post'
|
require_dependency 'post'
|
||||||
|
|
||||||
module PrettyText
|
module PrettyText
|
||||||
|
|
||||||
class Helpers
|
class Helpers
|
||||||
|
include UrlHelper
|
||||||
|
|
||||||
def t(key, opts)
|
def t(key, opts)
|
||||||
key = "js." + key
|
key = "js." + key
|
||||||
|
@ -21,15 +23,15 @@ module PrettyText
|
||||||
# function here are available to v8
|
# function here are available to v8
|
||||||
def avatar_template(username)
|
def avatar_template(username)
|
||||||
return "" unless username
|
return "" unless username
|
||||||
|
|
||||||
user = User.find_by(username_lower: username.downcase)
|
user = User.find_by(username_lower: username.downcase)
|
||||||
user.avatar_template if user.present?
|
return "" unless user.present?
|
||||||
|
schemaless absolute user.avatar_template
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_username_valid(username)
|
def is_username_valid(username)
|
||||||
return false unless username
|
return false unless username
|
||||||
username = username.downcase
|
username = username.downcase
|
||||||
return User.exec_sql('SELECT 1 FROM users WHERE username_lower = ?', username).values.length == 1
|
User.exec_sql('SELECT 1 FROM users WHERE username_lower = ?', username).values.length == 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -128,7 +130,9 @@ module PrettyText
|
||||||
context.eval("Discourse.SiteSettings = #{SiteSetting.client_settings_json};")
|
context.eval("Discourse.SiteSettings = #{SiteSetting.client_settings_json};")
|
||||||
context.eval("Discourse.CDN = '#{Rails.configuration.action_controller.asset_host}';")
|
context.eval("Discourse.CDN = '#{Rails.configuration.action_controller.asset_host}';")
|
||||||
context.eval("Discourse.BaseUrl = 'http://#{RailsMultisite::ConnectionManagement.current_hostname}';")
|
context.eval("Discourse.BaseUrl = 'http://#{RailsMultisite::ConnectionManagement.current_hostname}';")
|
||||||
context.eval("Discourse.getURL = function(url) {return '#{Discourse::base_uri}' + url};")
|
|
||||||
|
context.eval("Discourse.getURL = function(url) { return '#{Discourse::base_uri}' + url };")
|
||||||
|
context.eval("Discourse.getURLWithCDN = function(url) { url = Discourse.getURL(url); if (Discourse.CDN) { url = Discourse.CDN + url; } return url; };")
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.markdown(text, opts=nil)
|
def self.markdown(text, opts=nil)
|
||||||
|
|
|
@ -12,20 +12,20 @@ describe PrettyText do
|
||||||
|
|
||||||
before(:each) do
|
before(:each) do
|
||||||
eviltrout = User.new
|
eviltrout = User.new
|
||||||
eviltrout.stubs(:avatar_template).returns("http://test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/{size}.png")
|
eviltrout.stubs(:avatar_template).returns("//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/{size}.png")
|
||||||
User.expects(:find_by).with(username_lower: "eviltrout").returns(eviltrout)
|
User.expects(:find_by).with(username_lower: "eviltrout").returns(eviltrout)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "produces a quote even with new lines in it" do
|
it "produces a quote even with new lines in it" do
|
||||||
expect(PrettyText.cook("[quote=\"EvilTrout, post:123, topic:456, full:true\"]ddd\n[/quote]")).to match_html "<aside class=\"quote\" data-post=\"123\" data-topic=\"456\" data-full=\"true\"><div class=\"title\">\n<div class=\"quote-controls\"></div>\n<img width=\"20\" height=\"20\" src=\"http://test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png\" class=\"avatar\">EvilTrout:</div>\n<blockquote><p>ddd</p></blockquote></aside>"
|
expect(PrettyText.cook("[quote=\"EvilTrout, post:123, topic:456, full:true\"]ddd\n[/quote]")).to match_html "<aside class=\"quote\" data-post=\"123\" data-topic=\"456\" data-full=\"true\"><div class=\"title\">\n<div class=\"quote-controls\"></div>\n<img width=\"20\" height=\"20\" src=\"//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png\" class=\"avatar\">EvilTrout:</div>\n<blockquote><p>ddd</p></blockquote></aside>"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should produce a quote" do
|
it "should produce a quote" do
|
||||||
expect(PrettyText.cook("[quote=\"EvilTrout, post:123, topic:456, full:true\"]ddd[/quote]")).to match_html "<aside class=\"quote\" data-post=\"123\" data-topic=\"456\" data-full=\"true\"><div class=\"title\">\n<div class=\"quote-controls\"></div>\n<img width=\"20\" height=\"20\" src=\"http://test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png\" class=\"avatar\">EvilTrout:</div>\n<blockquote><p>ddd</p></blockquote></aside>"
|
expect(PrettyText.cook("[quote=\"EvilTrout, post:123, topic:456, full:true\"]ddd[/quote]")).to match_html "<aside class=\"quote\" data-post=\"123\" data-topic=\"456\" data-full=\"true\"><div class=\"title\">\n<div class=\"quote-controls\"></div>\n<img width=\"20\" height=\"20\" src=\"//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png\" class=\"avatar\">EvilTrout:</div>\n<blockquote><p>ddd</p></blockquote></aside>"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "trims spaces on quote params" do
|
it "trims spaces on quote params" do
|
||||||
expect(PrettyText.cook("[quote=\"EvilTrout, post:555, topic: 666\"]ddd[/quote]")).to match_html "<aside class=\"quote\" data-post=\"555\" data-topic=\"666\"><div class=\"title\">\n<div class=\"quote-controls\"></div>\n<img width=\"20\" height=\"20\" src=\"http://test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png\" class=\"avatar\">EvilTrout:</div>\n<blockquote><p>ddd</p></blockquote></aside>"
|
expect(PrettyText.cook("[quote=\"EvilTrout, post:555, topic: 666\"]ddd[/quote]")).to match_html "<aside class=\"quote\" data-post=\"555\" data-topic=\"666\"><div class=\"title\">\n<div class=\"quote-controls\"></div>\n<img width=\"20\" height=\"20\" src=\"//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png\" class=\"avatar\">EvilTrout:</div>\n<blockquote><p>ddd</p></blockquote></aside>"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -261,8 +261,14 @@ describe User do
|
||||||
|
|
||||||
it "downcases email addresses" do
|
it "downcases email addresses" do
|
||||||
user = Fabricate.build(:user, email: 'Fancy.Caps.4.U@gmail.com')
|
user = Fabricate.build(:user, email: 'Fancy.Caps.4.U@gmail.com')
|
||||||
user.save
|
user.valid?
|
||||||
expect(user.reload.email).to eq('fancy.caps.4.u@gmail.com')
|
expect(user.email).to eq('fancy.caps.4.u@gmail.com')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "strips whitespace from email addresses" do
|
||||||
|
user = Fabricate.build(:user, email: ' example@gmail.com ')
|
||||||
|
user.valid?
|
||||||
|
expect(user.email).to eq('example@gmail.com')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Reference in a new issue