Merge branch 'master' of github.com:discourse/discourse

This commit is contained in:
Sam Saffron 2013-02-12 13:45:24 +11:00
commit ad88487a32
27 changed files with 443 additions and 293 deletions

View File

@ -10,7 +10,7 @@ on Discourse with:
1. Install VirtualBox: https://www.virtualbox.org/wiki/Downloads
2. Install Vagrant: https://www.vagrantup.com/
3. Open a terminal
4. Clone the project: `git@github.com:discourse/discourse.git`
4. Clone the project: `git clone git@github.com:discourse/discourse.git`
5. Enter the project directory: `cd discourse`
### Using Vagrant
@ -51,7 +51,7 @@ bundle exec rails server
In a few seconds, rails will start server pages. To access them, open a web browser to http://localhost:4000 - if it all worked you should see discourse! Congratulations, you are ready to start working!
You can now edit files on your local file system, using your favorite text editor or IDE. When you reload your web browser, it should have the latest changed.
You can now edit files on your local file system, using your favorite text editor or IDE. When you reload your web browser, it should have the latest changes.
### Guard + Rspec
@ -71,6 +71,12 @@ Wait a minute while it runs all our unit tests. Once it has completed, live relo
### Sending Email
Mail is sent asynchronously by Sidekiq, so you'll need to have sidekiq running to process jobs. Run it with this command:
```
bundle exec sidekiq
```
Mailcatcher is used to avoid the whole issue of actually sending emails: https://github.com/sj26/mailcatcher
To start mailcatcher, run the following command in the vagrant image:

View File

@ -63,34 +63,34 @@ PATH
GEM
remote: https://rubygems.org/
specs:
actionmailer (3.2.11)
actionpack (= 3.2.11)
actionmailer (3.2.12)
actionpack (= 3.2.12)
mail (~> 2.4.4)
actionpack (3.2.11)
activemodel (= 3.2.11)
activesupport (= 3.2.11)
actionpack (3.2.12)
activemodel (= 3.2.12)
activesupport (= 3.2.12)
builder (~> 3.0.0)
erubis (~> 2.7.0)
journey (~> 1.0.4)
rack (~> 1.4.0)
rack (~> 1.4.5)
rack-cache (~> 1.2)
rack-test (~> 0.6.1)
sprockets (~> 2.2.1)
activemodel (3.2.11)
activesupport (= 3.2.11)
activemodel (3.2.12)
activesupport (= 3.2.12)
builder (~> 3.0.0)
activerecord (3.2.11)
activemodel (= 3.2.11)
activesupport (= 3.2.11)
activerecord (3.2.12)
activemodel (= 3.2.12)
activesupport (= 3.2.12)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
activerecord-postgres-hstore (0.7.1)
rails
rake
activeresource (3.2.11)
activemodel (= 3.2.11)
activesupport (= 3.2.11)
activesupport (3.2.11)
activeresource (3.2.12)
activemodel (= 3.2.12)
activesupport (= 3.2.12)
activesupport (3.2.12)
i18n (~> 0.6)
multi_json (~> 1.0)
acts_as_paranoid (0.4.1)
@ -194,7 +194,7 @@ GEM
jquery-rails (2.2.0)
railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0)
json (1.7.6)
json (1.7.7)
koala (1.6.0)
addressable (~> 2.2)
faraday (~> 0.8)
@ -208,10 +208,10 @@ GEM
treetop (~> 1.4.8)
metaclass (0.0.1)
method_source (0.8.1)
mime-types (1.20.1)
mime-types (1.21)
mocha (0.10.5)
metaclass (~> 0.0.1)
multi_json (1.5.0)
multi_json (1.5.1)
multipart-post (1.1.5)
mustache (0.99.4)
net-scp (1.0.4)
@ -239,17 +239,17 @@ GEM
rack
rack-test (0.6.2)
rack (>= 1.0)
rails (3.2.11)
actionmailer (= 3.2.11)
actionpack (= 3.2.11)
activerecord (= 3.2.11)
activeresource (= 3.2.11)
activesupport (= 3.2.11)
rails (3.2.12)
actionmailer (= 3.2.12)
actionpack (= 3.2.12)
activerecord (= 3.2.12)
activeresource (= 3.2.12)
activesupport (= 3.2.12)
bundler (~> 1.0)
railties (= 3.2.11)
railties (3.2.11)
actionpack (= 3.2.11)
activesupport (= 3.2.11)
railties (= 3.2.12)
railties (3.2.12)
actionpack (= 3.2.12)
activesupport (= 3.2.12)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
@ -258,7 +258,7 @@ GEM
rb-fsevent (0.9.3)
rb-inotify (0.8.8)
ffi (>= 0.5.0)
rdoc (3.12)
rdoc (3.12.1)
json (~> 1.4)
redis (3.0.2)
redis-actionpack (3.2.3)

View File

@ -84,9 +84,11 @@
ul = div.find('ul')
selectedOption = 0
markSelected()
ul.find('li').click ->
selectedOption = ul.find('li').index(this)
completeTerm(autocompleteOptions[selectedOption])
false
pos = null
if isInput

View File

@ -1,5 +1,4 @@
<h3><i class='icon icon-lock'></i> {{i18n private_message_info.title}}</h3>
<p>{{{i18n private_message_info.description}}}</p>
<div class='participants clearfix'>
{{#each content.allowed_users}}
<div class='user'>

View File

@ -38,6 +38,7 @@ window.Discourse.TopicFooterButtonsView = Ember.ContainerView.extend
@addObject Discourse.ButtonView.createWithMixins
classNames: ['btn', 'btn-primary', 'create']
attributeBindings: ['disabled']
text: (->
archetype = @get('controller.content.archetype')
return customTitle if customTitle = @get("parentView.replyButtonText#{archetype.capitalize()}")
@ -46,6 +47,7 @@ window.Discourse.TopicFooterButtonsView = Ember.ContainerView.extend
renderIcon: (buffer) -> buffer.push("<i class='icon icon-plus'></i>")
click: -> @get('controller').reply()
helpKey: 'topic.reply.help'
disabled: !@get('controller.content.can_create_post')
unless topic.get('isPrivateMessage')
@addObject Discourse.DropdownButtonView.createWithMixins

View File

@ -387,7 +387,7 @@ kbd {
z-index: 1;
}
button {
padding: 0;
padding: 0 1px;
cursor: pointer;
z-index: 1000;
position: absolute;

View File

@ -66,12 +66,12 @@ class ApplicationController < ActionController::Base
# for now do a simple remap, we may look at cleaner ways of doing the render
raise ActiveRecord::RecordNotFound
else
render file: 'public/404', layout: false, status: 404
render file: 'public/404', formats: [:html], layout: false, status: 404
end
end
rescue_from Discourse::InvalidAccess do
render file: 'public/403', layout: false, status: 403
render file: 'public/403', formats: [:html], layout: false, status: 403
end
def store_preloaded(key, json)

View File

@ -22,9 +22,14 @@ class SessionController < ApplicationController
# If their password is correct
if @user.confirm_password?(params[:password])
log_on_user(@user)
render_serialized(@user, UserSerializer)
return
if @user.email_confirmed?
log_on_user(@user)
render_serialized(@user, UserSerializer)
return
else
render :json => {error: I18n.t("login.not_activated")}
return
end
end
end

View File

@ -0,0 +1,7 @@
module CommonHelper
def render_google_analytics_code
if Rails.env == "production" && SiteSetting.ga_tracking_code.present?
render :partial => "common/google_analytics"
end
end
end

View File

@ -0,0 +1,10 @@
module TopicsHelper
def render_topic_title(topic)
link_to(topic.title,topic.relative_url)
end
def render_topic_next_page_link(topic, next_page)
link_to("next page", "#{topic.relative_url}?page=#{next_page}")
end
end

View File

@ -342,19 +342,21 @@ class Post < ActiveRecord::Base
# This calculates the geometric mean of the post timings and stores it along with
# each post.
def self.calculate_avg_time
exec_sql("UPDATE posts
SET avg_time = (x.gmean / 1000)
FROM (SELECT post_timings.topic_id,
post_timings.post_number,
round(exp(avg(ln(msecs)))) AS gmean
FROM post_timings
INNER JOIN posts AS p2
ON p2.post_number = post_timings.post_number
AND p2.topic_id = post_timings.topic_id
AND p2.user_id <> post_timings.user_id
GROUP BY post_timings.topic_id, post_timings.post_number) AS x
WHERE x.topic_id = posts.topic_id
AND x.post_number = posts.post_number")
retry_lock_error do
exec_sql("UPDATE posts
SET avg_time = (x.gmean / 1000)
FROM (SELECT post_timings.topic_id,
post_timings.post_number,
round(exp(avg(ln(msecs)))) AS gmean
FROM post_timings
INNER JOIN posts AS p2
ON p2.post_number = post_timings.post_number
AND p2.topic_id = post_timings.topic_id
AND p2.user_id <> post_timings.user_id
GROUP BY post_timings.topic_id, post_timings.post_number) AS x
WHERE x.topic_id = posts.topic_id
AND x.post_number = posts.post_number")
end
end
before_save do

View File

@ -47,6 +47,10 @@ class TopicLink < ActiveRecord::Base
internal = true
route = Rails.application.routes.recognize_path(parsed.path)
# We aren't interested in tracking internal links to users
next if route[:controller] == 'users'
topic_id = route[:topic_id]
post_number = route[:post_number] || 1
end

View File

@ -380,6 +380,10 @@ class User < ActiveRecord::Base
end
end
def email_confirmed?
email_tokens.where(email: self.email, confirmed: true).present?
end
protected

View File

@ -0,0 +1,25 @@
<%- if mini_profiler_enabled? %>
<%- Rack::MiniProfiler.step "application" do %>
<%= javascript_include_tag "application" %>
<%-end%>
<%- Rack::MiniProfiler.step "admin" do %>
<%= javascript_include_tag "admin"%>
<%-end%>
<%- else %>
<%= javascript_include_tag "application" %>
<%- if admin? %>
<%= javascript_include_tag "admin"%>
<%- end %>
<%- end%>
<script>
Discourse.CDN = '<%= Rails.configuration.action_controller.asset_host %>';
Discourse.BaseUrl = '<%= RailsMultisite::ConnectionManagement.current_hostname %>';
Discourse.Environment = '<%= Rails.env %>';
window.Discourse.Router.map(function() {
return Discourse.routeBuilder.call(this);
});
Discourse.start()
Discourse.initialize()
</script>

View File

@ -0,0 +1,12 @@
<%- unless SiteCustomization.override_default_style(session[:preview_style]) %>
<%=stylesheet_link_tag "application"%>
<%- end %>
<%- if mini_profiler_enabled? %>
<%- Rack::MiniProfiler.step "stylsheet" do%>
<%= stylesheet_link_tag "admin"%>
<%-end%>
<%- elsif admin? %>
<%= stylesheet_link_tag "admin"%>
<%-end%>
<%=SiteCustomization.custom_stylesheet(session[:preview_style])%>

View File

@ -0,0 +1,14 @@
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', '<%= SiteSetting.ga_tracking_code %>']);
_gaq.push(['_setCustomVar', 1, 'Anonymous', <%= !current_user %>, 2]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>

View File

@ -0,0 +1,31 @@
<%#
The fonts are loaded outside of the stylesheet so that we can dynamically change
the path. This is to get around CDN caching on the Origin:
https://forums.aws.amazon.com/thread.jspa?threadID=114646
%>
<% font_domain = "#{request.protocol}#{request.host_with_port}" %>
<style>
@font-face {
font-family: 'FontAwesome';
src: url('<%=asset_path "fontawesome-webfont.eot" %>?<%= font_domain %>');
src: url('<%=asset_path "fontawesome-webfont.eot" %>?<%= font_domain %>#iefix') format('embedded-opentype'),
url('<%=asset_path "fontawesome-webfont.woff" %>?<%= font_domain %>') format('woff'),
url('<%=asset_path "fontawesome-webfont.ttf" %>?<%= font_domain %>') format('truetype');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'zocial';
src: url('/assets/zocial-regular-webfont.woff?<%= font_domain %>') format('woff'),
url('/assets/zocial-regular-webfont.ttf?<%= font_domain %>') format('truetype'),
url('/assets/zocial-regular-webfont.svg?<%= font_domain %>#zocialregular') format('svg');
font-weight: normal;
font-style: normal;
}
</style>

View File

@ -19,48 +19,11 @@
<%= javascript_include_tag "preload_store" %>
<%#
The fonts are loaded outside of the stylesheet so that we can dynamically change
the path. This is to get around CDN caching on the Origin:
https://forums.aws.amazon.com/thread.jspa?threadID=114646
%>
<%= render :partial => "common/special_font_face" %>
<%= render :partial => "common/discourse_stylesheet" %>
<%- font_domain = "#{request.protocol}#{request.host_with_port}" %>
<style>
@font-face {
font-family: 'FontAwesome';
src: url('<%=asset_path "fontawesome-webfont.eot" %>?<%= font_domain %>');
src: url('<%=asset_path "fontawesome-webfont.eot" %>?<%= font_domain %>#iefix') format('embedded-opentype'),
url('<%=asset_path "fontawesome-webfont.woff" %>?<%= font_domain %>') format('woff'),
url('<%=asset_path "fontawesome-webfont.ttf" %>?<%= font_domain %>') format('truetype');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'zocial';
src: url('/assets/zocial-regular-webfont.woff?<%= font_domain %>') format('woff'),
url('/assets/zocial-regular-webfont.ttf?<%= font_domain %>') format('truetype'),
url('/assets/zocial-regular-webfont.svg?<%= font_domain %>#zocialregular') format('svg');
font-weight: normal;
font-style: normal;
}
</style>
<%- unless SiteCustomization.override_default_style(session[:preview_style]) %>
<%=stylesheet_link_tag "application"%>
<%- end %>
<%- if mini_profiler_enabled? %>
<%- Rack::MiniProfiler.step "stylsheet" do%>
<%= stylesheet_link_tag "admin"%>
<%-end%>
<%- elsif admin? %>
<%= stylesheet_link_tag "admin"%>
<%-end%>
<%=SiteCustomization.custom_stylesheet(session[:preview_style])%>
<%=csrf_meta_tags%>
</head>
@ -98,48 +61,8 @@
<footer id='bottom'></footer>
<%- if mini_profiler_enabled? %>
<%- Rack::MiniProfiler.step "application" do %>
<%= javascript_include_tag "application" %>
<%-end%>
<%- Rack::MiniProfiler.step "admin" do %>
<%= javascript_include_tag "admin"%>
<%-end%>
<%- else %>
<%= javascript_include_tag "application" %>
<%- if admin? %>
<%= javascript_include_tag "admin"%>
<%- end %>
<%- end%>
<script>
Discourse.CDN = '<%= Rails.configuration.action_controller.asset_host %>';
Discourse.BaseUrl = '<%= RailsMultisite::ConnectionManagement.current_hostname %>';
Discourse.Environment = '<%= Rails.env %>';
window.Discourse.Router.map(function() {
return Discourse.routeBuilder.call(this);
});
Discourse.start()
Discourse.initialize()
</script>
<%- if Rails.env == "production" and SiteSetting.ga_tracking_code.present? %>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', '<%= SiteSetting.ga_tracking_code %>']);
_gaq.push(['_setCustomVar', 1, 'Anonymous', <%= !current_user %>, 2]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
<%-end%>
<%= render :partial => "common/discourse_javascript" %>
<%= render_google_analytics_code %>
</body>
</html>

View File

@ -1,5 +1,6 @@
<h2>
<a href="<%= @topic_view.topic.relative_url %>"><%= @topic_view.topic.title %></a>
<%= render_topic_title(@topic_view.topic) %>
</h2>
<% (@post ? [@post] : @topic_view.posts).each do |post| %>
<div class='creator'>
@ -12,7 +13,7 @@
<% if @next_page%>
<p>
<b><a href="<%= @topic_view.topic.relative_url %>?page=<%= @next_page%>">next page</a></b>
<b><%= render_topic_next_page_link(@topic_view.topic, @next_page) %></b>
</p>
<% end %>

View File

@ -596,7 +596,6 @@ en:
private_message_info:
title: "Private Conversation"
description: "Participants in this private conversation"
invite: "Invite Others..."
email: 'Email'
@ -1039,6 +1038,7 @@ en:
wait_approval: "Thanks for signing up. We will notify you when your account has been approved."
active: "Your account is active and ready."
activate_email: "You're almost done! We sent an activation email to <b>%{email}</b>. Please follow the instructions in the email to activate your account."
not_activated: "You can't log in yet. We sent an activation email to you. Please follow the instructions in the email to activate your account."
errors: "Failed to create account: %{errors}"
not_available: "Not available. Try %{suggestion}?"

View File

@ -21,9 +21,9 @@ module CurrentUser
@not_logged_in = session[:current_user_id].blank?
if @current_user
@current_user.update_last_seen!
if @current_user.ip_address != request.remote_ip
if (@current_user.ip_address != request.remote_ip) and request.remote_ip.present?
@current_user.ip_address = request.remote_ip
User.exec_sql('update users set ip_address = ? where id = ?', request.remote_ip, @current_user.id)
@current_user.update_column(:ip_address, request.remote_ip)
end
end
@current_user

View File

@ -9,10 +9,21 @@ class DiscoursePluginRegistry
attr_accessor :stylesheets
end
def register_js(filename, options={})
self.class.javascripts ||= Set.new
self.class.server_side_javascripts ||= Set.new
# Default accessor values
#
def self.stylesheets
@stylesheets ||= Set.new
end
def self.javascripts
@javascripts ||= Set.new
end
def self.server_side_javascripts
@server_side_javascripts ||= Set.new
end
def register_js(filename, options={})
# If we have a server side option, add that too.
self.class.server_side_javascripts << options[:server_side] if options[:server_side].present?
@ -20,12 +31,11 @@ class DiscoursePluginRegistry
end
def register_css(filename)
self.class.stylesheets ||= Set.new
self.class.stylesheets << filename
end
def stylesheets
self.class.stylesheets || Set.new
self.class.stylesheets
end
def register_archetype(name, options={})
@ -33,17 +43,17 @@ class DiscoursePluginRegistry
end
def server_side_javascripts
self.class.javascripts || Set.new
self.class.javascripts
end
def javascripts
self.class.javascripts || Set.new
self.class.javascripts
end
def self.clear
self.stylesheets = Set.new
self.server_side_javascripts = Set.new
self.javascripts = Set.new
self.stylesheets = nil
self.server_side_javascripts = nil
self.javascripts = nil
end
def self.setup(plugin_class)
@ -52,6 +62,4 @@ class DiscoursePluginRegistry
plugin.setup
end
end

View File

@ -15,6 +15,24 @@ class ActiveRecord::Base
ActiveRecord::Base.exec_sql(*args)
end
# Executes the given block +retries+ times (or forever, if explicitly given nil),
# catching and retrying SQL Deadlock errors.
#
# Thanks to: http://stackoverflow.com/a/7427186/165668
#
def self.retry_lock_error(retries=5, &block)
begin
yield
rescue ActiveRecord::StatementInvalid => e
if e.message =~ /Deadlock found when trying to get lock/ and (retries.nil? || retries > 0)
retry_lock_error(retries ? retries - 1 : nil, &block)
else
raise e
end
end
end
# Support for psql. If we want to support multiple RDBMs in the future we can
# split this.
def exec_sql_row_count(*args)

View File

@ -4,6 +4,27 @@ require 'discourse_plugin_registry'
describe DiscoursePluginRegistry do
let(:registry) { DiscoursePluginRegistry.new }
context '#stylesheets' do
it 'defaults to an empty Set' do
DiscoursePluginRegistry.stylesheets = nil
DiscoursePluginRegistry.stylesheets.should == Set.new
end
end
context '#javascripts' do
it 'defaults to an empty Set' do
DiscoursePluginRegistry.javascripts = nil
DiscoursePluginRegistry.javascripts.should == Set.new
end
end
context '#server_side_javascripts' do
it 'defaults to an empty Set' do
DiscoursePluginRegistry.server_side_javascripts = nil
DiscoursePluginRegistry.server_side_javascripts.should == Set.new
end
end
context '.register_css' do
before do

View File

@ -6,77 +6,93 @@ describe SessionController do
let(:user) { Fabricate(:user) }
it "raises an error when the login isn't present" do
lambda { xhr :post, :create }.should raise_error(Discourse::InvalidParameters)
end
describe 'invalid password' do
it "should return an error with an invalid password" do
xhr :post, :create, login: user.username, password: 'sssss'
::JSON.parse(response.body)['error'].should be_present
end
end
describe 'success by username' do
context 'when email is confirmed' do
before do
xhr :post, :create, login: user.username, password: 'myawesomepassword'
user.reload
token = user.email_tokens.where(email: user.email).first
EmailToken.confirm(token.token)
end
it 'sets a session id' do
session[:current_user_id].should == user.id
it "raises an error when the login isn't present" do
lambda { xhr :post, :create }.should raise_error(Discourse::InvalidParameters)
end
it 'gives the user an auth token' do
user.auth_token.should be_present
describe 'invalid password' do
it "should return an error with an invalid password" do
xhr :post, :create, login: user.username, password: 'sssss'
::JSON.parse(response.body)['error'].should be_present
end
end
it 'sets a cookie with the auth token' do
cookies[:_t].should == user.auth_token
end
end
describe 'success by username' do
before do
xhr :post, :create, login: user.username, password: 'myawesomepassword'
user.reload
end
describe 'strips leading @ symbol' do
before do
xhr :post, :create, login: "@" + user.username, password: 'myawesomepassword'
user.reload
it 'sets a session id' do
session[:current_user_id].should == user.id
end
it 'gives the user an auth token' do
user.auth_token.should be_present
end
it 'sets a cookie with the auth token' do
cookies[:_t].should == user.auth_token
end
end
it 'sets a session id' do
session[:current_user_id].should == user.id
end
end
describe 'strips leading @ symbol' do
before do
xhr :post, :create, login: "@" + user.username, password: 'myawesomepassword'
user.reload
end
describe 'also allow login by email' do
before do
xhr :post, :create, login: user.email, password: 'myawesomepassword'
it 'sets a session id' do
session[:current_user_id].should == user.id
end
end
it 'sets a session id' do
session[:current_user_id].should == user.id
end
end
describe "when the site requires approval of users" do
before do
SiteSetting.expects(:must_approve_users?).returns(true)
end
context 'with an unapproved user' do
describe 'also allow login by email' do
before do
xhr :post, :create, login: user.email, password: 'myawesomepassword'
end
it "doesn't log in the user" do
session[:current_user_id].should be_blank
it 'sets a session id' do
session[:current_user_id].should == user.id
end
end
describe "when the site requires approval of users" do
before do
SiteSetting.expects(:must_approve_users?).returns(true)
end
context 'with an unapproved user' do
before do
xhr :post, :create, login: user.email, password: 'myawesomepassword'
end
it "doesn't log in the user" do
session[:current_user_id].should be_blank
end
end
end
end
context 'when email has not been confirmed' do
before do
xhr :post, :create, login: user.email, password: 'myawesomepassword'
end
it "doesn't log in the user" do
session[:current_user_id].should be_blank
end
it 'returns an error message' do
::JSON.parse(response.body)['error'].should be_present
end
end
end
describe '.destroy' do

View File

@ -44,125 +44,139 @@ describe TopicLink do
end
describe 'domain-less link' do
let(:post) { @topic.posts.create(user: @user, raw: "<a href='/users'>hello</a>") }
let!(:link) do
TopicLink.extract_from(post)
@topic.topic_links.first
end
it 'is extracted' do
link.should be_present
end
it 'has the correct domain' do
link.domain.should == test_uri.host
end
it "is not destroyed when we call extract from again" do
TopicLink.extract_from(post)
link.reload
link.should be_present
end
end
describe 'internal links' do
before do
@other_topic = Fabricate(:topic, user: @user)
@other_post = @other_topic.posts.create(user: @user, raw: "some content")
@url = "http://#{test_uri.host}/t/topic-slug/#{@other_topic.id}"
@topic.posts.create(user: @user, raw: 'initial post')
@post = @topic.posts.create(user: @user, raw: "Link to another topic: #{@url}")
TopicLink.extract_from(@post)
@link = @topic.topic_links.first
end
it 'extracted the link' do
@link.should be_present
end
it 'is set to internal' do
@link.should be_internal
end
it 'has the correct url' do
@link.url.should == @url
end
it 'has the extracted domain' do
@link.domain.should == test_uri.host
end
it 'should have the id of the linked forum' do
@link.link_topic_id == @other_topic.id
end
it 'should not be the reflection' do
@link.should_not be_reflection
end
describe 'reflection in the other topic' do
context 'topic link' do
before do
@reflection = @other_topic.topic_links.first
@other_topic = Fabricate(:topic, user: @user)
@other_post = @other_topic.posts.create(user: @user, raw: "some content")
@url = "http://#{test_uri.host}/t/topic-slug/#{@other_topic.id}"
@topic.posts.create(user: @user, raw: 'initial post')
@post = @topic.posts.create(user: @user, raw: "Link to another topic: #{@url}")
TopicLink.extract_from(@post)
@link = @topic.topic_links.first
end
it 'exists' do
@reflection.should be_present
it 'extracted the link' do
@link.should be_present
end
it 'is a reflection' do
@reflection.should be_reflection
end
it 'has a post_id' do
@reflection.post_id.should be_present
end
it 'has the correct host' do
@reflection.domain.should == test_uri.host
it 'is set to internal' do
@link.should be_internal
end
it 'has the correct url' do
@reflection.url.should == "http://#{test_uri.host}/t/unique-topic-name/#{@topic.id}/#{@post.post_number}"
@link.url.should == @url
end
it 'links to the original forum topic' do
@reflection.link_topic_id.should == @topic.id
it 'has the extracted domain' do
@link.domain.should == test_uri.host
end
it 'links to the original post' do
@reflection.link_post_id.should == @post.id
it 'should have the id of the linked forum' do
@link.link_topic_id == @other_topic.id
end
it 'has the user id of the original link' do
@reflection.user_id.should == @link.user_id
it 'should not be the reflection' do
@link.should_not be_reflection
end
describe 'reflection in the other topic' do
before do
@reflection = @other_topic.topic_links.first
end
it 'exists' do
@reflection.should be_present
end
it 'is a reflection' do
@reflection.should be_reflection
end
it 'has a post_id' do
@reflection.post_id.should be_present
end
it 'has the correct host' do
@reflection.domain.should == test_uri.host
end
it 'has the correct url' do
@reflection.url.should == "http://#{test_uri.host}/t/unique-topic-name/#{@topic.id}/#{@post.post_number}"
end
it 'links to the original forum topic' do
@reflection.link_topic_id.should == @topic.id
end
it 'links to the original post' do
@reflection.link_post_id.should == @post.id
end
it 'has the user id of the original link' do
@reflection.user_id.should == @link.user_id
end
end
context 'removing a link' do
before do
@post.revise(@post.user, "no more linkies")
TopicLink.extract_from(@post)
end
it 'should remove the link' do
@topic.topic_links.where(post_id: @post.id).should be_blank
end
it 'should remove the reflected link' do
@reflection = @other_topic.topic_links.should be_blank
end
end
end
context "link to a user on discourse" do
let(:post) { @topic.posts.create(user: @user, raw: "<a href='/users/#{@user.username_lower}'>user</a>") }
before do
TopicLink.extract_from(post)
end
it 'does not extract a link' do
@topic.topic_links.should be_blank
end
end
context 'removing a link' do
context "link to a discourse resource like a FAQ" do
let(:post) { @topic.posts.create(user: @user, raw: "<a href='/faq'>faq link here</a>") }
before do
TopicLink.extract_from(post)
end
it 'does not extract a link' do
@topic.topic_links.should be_present
end
end
context "@mention links" do
let(:post) { @topic.posts.create(user: @user, raw: "Hey @#{@user.username_lower}") }
before do
@post.revise(@post.user, "no more linkies")
TopicLink.extract_from(@post)
TopicLink.extract_from(post)
end
it 'should remove the link' do
@topic.topic_links.where(post_id: @post.id).should be_blank
it 'does not extract a link' do
@topic.topic_links.should be_blank
end
it 'should remove the reflected link' do
@reflection = @other_topic.topic_links.should be_blank
end
end
end
end
describe 'internal link from pm' do

View File

@ -617,4 +617,30 @@ describe User do
it { should_not be_active }
end
describe 'email_confirmed?' do
let(:user) { Fabricate(:user) }
context 'when email has not been confirmed yet' do
it 'should return false' do
user.email_confirmed?.should be_false
end
end
context 'when email has been confirmed' do
it 'should return true' do
token = user.email_tokens.where(email: user.email).first
EmailToken.confirm(token.token)
user.email_confirmed?.should be_true
end
end
context 'when user has no email tokens for some reason' do
it 'should return false' do
user.email_tokens.each {|t| t.destroy}
user.reload
user.email_confirmed?.should be_false
end
end
end
end