diff --git a/Gemfile b/Gemfile
index 78af7f5db69a8c290e73a9c40ba881b5dc695973..94e2129f3c6f6be52bfd67420663763c306eff27 100644
--- a/Gemfile
+++ b/Gemfile
@@ -10,9 +10,6 @@ end
 
 gem "rails", "~> 4.1.0"
 
-# Make links from text
-gem 'rails_autolink', '~> 1.1'
-
 # Default values for AR models
 gem "default_value_for", "~> 3.0.0"
 
diff --git a/Gemfile.lock b/Gemfile.lock
index bbc5639c84fa0735cbe8e78f4fd4c879bc791f4c..80ae41dc8fcddda913bee0f3d86320f502cd02de 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -448,8 +448,6 @@ GEM
       sprockets-rails (~> 2.0)
     rails-observers (0.1.2)
       activemodel (~> 4.0)
-    rails_autolink (1.1.6)
-      rails (> 3.1)
     railties (4.1.9)
       actionpack (= 4.1.9)
       activesupport (= 4.1.9)
@@ -779,7 +777,6 @@ DEPENDENCIES
   rack-mini-profiler
   rack-oauth2 (~> 1.0.5)
   rails (~> 4.1.0)
-  rails_autolink (~> 1.1)
   raphael-rails (~> 2.1.2)
   rb-fsevent
   rb-inotify
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 12489ccc2d70abdd2ef1f7a50f6b249590efefd2..b93ea0f020eee0d1805919cea4498f2e628218ee 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -48,14 +48,16 @@
     }
 
     .project-home-desc {
+      color: $gray;
+      float: left;
       font-size: 16px;
       line-height: 1.3;
       margin-right: 250px;
-    }
 
-    .project-home-desc {
-      float: left;
-      color: $gray;
+      // Render Markdown-generated HTML inline for this block
+      p {
+        display: inline;
+      }
     }
   }
 
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 89dcdf57798251e0b70a8c91b133ffaf04f3279a..a539ec49f7afe2be25affd9fabf7576e824622e0 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -279,10 +279,6 @@ def add_nofollow(link, html_options = {})
     html_options
   end
 
-  def escaped_autolink(text)
-    auto_link ERB::Util.html_escape(text), link: :urls
-  end
-
   def promo_host
     'about.gitlab.com'
   end
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 1678311141e618ff54b76f88eb24641919b3e943..0687840af395ac92db8665205afd3f27f9710d4f 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -11,7 +11,7 @@
       @#{@group.path}
     - if @group.description.present?
       .description
-        = escaped_autolink(@group.description)
+        = markdown(@group.description, pipeline: :description)
   %hr
 
   = render 'shared/show_aside'
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index f9cdda4a3babb3d2e93e6fcfe287bd42fba3bb63..076afb11a9d6235237e3a1f7f1c162ff849f4eee 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -5,7 +5,7 @@
   .project-home-row.project-home-row-top
     .project-home-desc
       - if @project.description.present?
-        = escaped_autolink(@project.description)
+        = markdown(@project.description, pipeline: :description)
       - if can?(current_user, :admin_project, @project)
         –
         = link_to 'Edit', edit_namespace_project_path
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index 5db1566f55deac9b9fb63c22606caf1950dd4736..fa9c0975bb8271775a272c03b7dbb860522367d1 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -57,6 +57,9 @@ def gfm_with_options(text, options = {}, html_options = {})
       pipeline = HTML::Pipeline.new(filters)
 
       context = {
+        # SanitizationFilter
+        pipeline: options[:pipeline],
+
         # EmojiFilter
         asset_root: Gitlab.config.gitlab.url,
         asset_host: Gitlab::Application.config.asset_host,
diff --git a/lib/gitlab/markdown/sanitization_filter.rb b/lib/gitlab/markdown/sanitization_filter.rb
index 88781fea0c8d96460dd32d0105ff44ccecf1aaab..74b3a8d274f62d6073c3a41bd5610ac9836567db 100644
--- a/lib/gitlab/markdown/sanitization_filter.rb
+++ b/lib/gitlab/markdown/sanitization_filter.rb
@@ -8,33 +8,54 @@ module Markdown
     # Extends HTML::Pipeline::SanitizationFilter with a custom whitelist.
     class SanitizationFilter < HTML::Pipeline::SanitizationFilter
       def whitelist
-        whitelist = super
+        # Descriptions are more heavily sanitized, allowing only a few elements.
+        # See http://git.io/vkuAN
+        if pipeline == :description
+          whitelist = LIMITED
+          whitelist[:elements] -= %w(pre code img ol ul li)
+        else
+          whitelist = super
+        end
+
+        customize_whitelist(whitelist)
+
+        whitelist
+      end
 
+      private
+
+      def pipeline
+        context[:pipeline] || :default
+      end
+
+      def customized?(transformers)
+        transformers.last.source_location[0] == __FILE__
+      end
+
+      def customize_whitelist(whitelist)
         # Only push these customizations once
-        unless customized?(whitelist[:transformers])
-          # Allow code highlighting
-          whitelist[:attributes]['pre'] = %w(class)
-          whitelist[:attributes]['span'] = %w(class)
+        return if customized?(whitelist[:transformers])
 
-          # Allow table alignment
-          whitelist[:attributes]['th'] = %w(style)
-          whitelist[:attributes]['td'] = %w(style)
+        # Allow code highlighting
+        whitelist[:attributes]['pre'] = %w(class)
+        whitelist[:attributes]['span'] = %w(class)
 
-          # Allow span elements
-          whitelist[:elements].push('span')
+        # Allow table alignment
+        whitelist[:attributes]['th'] = %w(style)
+        whitelist[:attributes]['td'] = %w(style)
 
-          # Remove `rel` attribute from `a` elements
-          whitelist[:transformers].push(remove_rel)
+        # Allow span elements
+        whitelist[:elements].push('span')
 
-          # Remove `class` attribute from non-highlight spans
-          whitelist[:transformers].push(clean_spans)
-        end
+        # Remove `rel` attribute from `a` elements
+        whitelist[:transformers].push(remove_rel)
+
+        # Remove `class` attribute from non-highlight spans
+        whitelist[:transformers].push(clean_spans)
 
         whitelist
       end
 
-      private
-
       def remove_rel
         lambda do |env|
           if env[:node_name] == 'a'
@@ -53,10 +74,6 @@ def clean_spans
           end
         end
       end
-
-      def customized?(transformers)
-        transformers.last.source_location[0] == __FILE__
-      end
     end
   end
 end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..edc1c63a0aab3f90f38786fc79f9d8063791f6b7
--- /dev/null
+++ b/spec/features/groups_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+feature 'Group' do
+  describe 'description' do
+    let(:group) { create(:group) }
+    let(:path)  { group_path(group) }
+
+    before do
+      login_as(:admin)
+    end
+
+    it 'parses Markdown' do
+      group.update_attribute(:description, 'This is **my** group')
+      visit path
+      expect(page).to have_css('.description > p > strong')
+    end
+
+    it 'passes through html-pipeline' do
+      group.update_attribute(:description, 'This group is the :poop:')
+      visit path
+      expect(page).to have_css('.description > p > img')
+    end
+
+    it 'sanitizes unwanted tags' do
+      group.update_attribute(:description, '# Group Description')
+      visit path
+      expect(page).not_to have_css('.description h1')
+    end
+
+    it 'permits `rel` attribute on links' do
+      group.update_attribute(:description, 'https://google.com/')
+      visit path
+      expect(page).to have_css('.description a[rel]')
+    end
+  end
+end
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index ee1b3bf749d1f7ec53b6735f5e181017b7ba0c0e..902968cebcb6924bec6ced442c707f7840281942 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -18,11 +18,13 @@
 #         -> `gfm_with_options` helper
 #           -> HTML::Pipeline
 #             -> Sanitize
+#             -> RelativeLink
 #             -> Emoji
 #             -> Table of Contents
 #             -> Autolinks
 #               -> Rinku (http, https, ftp)
 #               -> Other schemes
+#             -> ExternalLink
 #             -> References
 #             -> TaskList
 #           -> `html_safe`
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index cae11be7cdd32b06c30738d919ae1e077e5ba681..56523f6e1a8e70d18b8eb04d690c075c84d8784f 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -1,24 +1,56 @@
 require 'spec_helper'
 
-describe "Projects", feature: true, js: true do
-  before { login_as :user }
+feature 'Project' do
+  describe 'description' do
+    let(:project) { create(:project) }
+    let(:path)    { namespace_project_path(project.namespace, project) }
 
-  describe "DELETE /projects/:id" do
     before do
-      @project = create(:project, namespace: @user.namespace)
-      @project.team << [@user, :master]
-      visit edit_namespace_project_path(@project.namespace, @project)
+      login_as(:admin)
     end
 
-    it "should remove project" do
+    it 'parses Markdown' do
+      project.update_attribute(:description, 'This is **my** project')
+      visit path
+      expect(page).to have_css('.project-home-desc > p > strong')
+    end
+
+    it 'passes through html-pipeline' do
+      project.update_attribute(:description, 'This project is the :poop:')
+      visit path
+      expect(page).to have_css('.project-home-desc > p > img')
+    end
+
+    it 'sanitizes unwanted tags' do
+      project.update_attribute(:description, '# Project Description')
+      visit path
+      expect(page).not_to have_css('.project-home-desc h1')
+    end
+
+    it 'permits `rel` attribute on links' do
+      project.update_attribute(:description, 'https://google.com/')
+      visit path
+      expect(page).to have_css('.project-home-desc a[rel]')
+    end
+  end
+
+  describe 'removal', js: true do
+    let(:user)    { create(:user) }
+    let(:project) { create(:project, namespace: user.namespace) }
+
+    before do
+      login_with(user)
+      project.team << [user, :master]
+      visit edit_namespace_project_path(project.namespace, project)
+    end
+
+    it 'should remove project' do
       expect { remove_project }.to change {Project.count}.by(-1)
     end
 
     it 'should delete the project from disk' do
-      expect(GitlabShellWorker).to(
-        receive(:perform_async).with(:remove_repository,
-                                     /#{@project.path_with_namespace}/)
-      ).twice
+      expect(GitlabShellWorker).to receive(:perform_async).
+        with(:remove_repository, /#{project.path_with_namespace}/).twice
 
       remove_project
     end
@@ -26,7 +58,7 @@
 
   def remove_project
     click_link "Remove project"
-    fill_in 'confirm_name_input', with: @project.path
+    fill_in 'confirm_name_input', with: project.path
     click_button 'Confirm'
   end
 end
diff --git a/spec/lib/gitlab/markdown/autolink_filter_spec.rb b/spec/lib/gitlab/markdown/autolink_filter_spec.rb
index 0bbdc11a9798f6b82c3a7f8632ac63e4f6fe09f8..a14cb2da089cdc1fe4b36e449ee882189d54bc6b 100644
--- a/spec/lib/gitlab/markdown/autolink_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/autolink_filter_spec.rb
@@ -2,11 +2,9 @@
 
 module Gitlab::Markdown
   describe AutolinkFilter do
-    let(:link) { 'http://about.gitlab.com/' }
+    include FilterSpecHelper
 
-    def filter(html, options = {})
-      described_class.call(html, options)
-    end
+    let(:link) { 'http://about.gitlab.com/' }
 
     it 'does nothing when :autolink is false' do
       exp = act = link
diff --git a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
index d3695ee46d0e6297cdb225f3b5d73eb6d0a68132..e8391cc7aca045f021c4087bc9047c124e7834ad 100644
--- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
@@ -2,7 +2,7 @@
 
 module Gitlab::Markdown
   describe CommitRangeReferenceFilter do
-    include ReferenceFilterSpecHelper
+    include FilterSpecHelper
 
     let(:project) { create(:project) }
     let(:commit1) { project.commit }
diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
index a0d2cd7e22b82786ec98062688d6db14132fa998..a10d43c9a027d82ed6e53e713e50da1d35505338 100644
--- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
@@ -2,7 +2,7 @@
 
 module Gitlab::Markdown
   describe CommitReferenceFilter do
-    include ReferenceFilterSpecHelper
+    include FilterSpecHelper
 
     let(:project) { create(:project) }
     let(:commit)  { project.commit }
diff --git a/spec/lib/gitlab/markdown/emoji_filter_spec.rb b/spec/lib/gitlab/markdown/emoji_filter_spec.rb
index 18d55c4818fba947685b17cfa7cf24434978fa41..11efd9bb4cd33f36205fa7d7d02d7d018ff3f42e 100644
--- a/spec/lib/gitlab/markdown/emoji_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/emoji_filter_spec.rb
@@ -2,9 +2,7 @@
 
 module Gitlab::Markdown
   describe EmojiFilter do
-    def filter(html, contexts = {})
-      described_class.call(html, contexts)
-    end
+    include FilterSpecHelper
 
     before do
       ActionController::Base.asset_host = 'https://foo.com'
diff --git a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
index bf9409589faad95023333cf09b027b11ba38ff67..f16095bc2b21b386261b9a926f1380bbe93b4ba1 100644
--- a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
@@ -2,7 +2,7 @@
 
 module Gitlab::Markdown
   describe ExternalIssueReferenceFilter do
-    include ReferenceFilterSpecHelper
+    include FilterSpecHelper
 
     def helper
       IssuesHelper
diff --git a/spec/lib/gitlab/markdown/external_link_filter_spec.rb b/spec/lib/gitlab/markdown/external_link_filter_spec.rb
index c2ff4f80a42b4a0348aa898271c67c1c9ef8a2e2..a040b34577b958c033bfa8fc14d6d14877192937 100644
--- a/spec/lib/gitlab/markdown/external_link_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/external_link_filter_spec.rb
@@ -2,9 +2,7 @@
 
 module Gitlab::Markdown
   describe ExternalLinkFilter do
-    def filter(html, options = {})
-      described_class.call(html, options)
-    end
+    include FilterSpecHelper
 
     it 'ignores elements without an href attribute' do
       exp = act = %q(<a id="ignored">Ignore Me</a>)
diff --git a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
index a838d7570c84641e4f6eb2dc7a248745e80d076f..fa43d33794d5d76c6ff4206a0449e7a7f6c4e8fe 100644
--- a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
@@ -2,7 +2,7 @@
 
 module Gitlab::Markdown
   describe IssueReferenceFilter do
-    include ReferenceFilterSpecHelper
+    include FilterSpecHelper
 
     def helper
       IssuesHelper
diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
index 41987f57bca4dae9d4fb49b3f4406506de31156b..cf3337b1ba1abebbeea49f062aa013e2def7667b 100644
--- a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
@@ -3,7 +3,7 @@
 
 module Gitlab::Markdown
   describe LabelReferenceFilter do
-    include ReferenceFilterSpecHelper
+    include FilterSpecHelper
 
     let(:project)   { create(:empty_project) }
     let(:label)     { create(:label, project: project) }
diff --git a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
index 6aeb10936022ca2af549a027dd0b1c4518ae8aac..5945302a2da33ed5a153837031315be2cd3a6e24 100644
--- a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
@@ -2,7 +2,7 @@
 
 module Gitlab::Markdown
   describe MergeRequestReferenceFilter do
-    include ReferenceFilterSpecHelper
+    include FilterSpecHelper
 
     let(:project) { create(:project) }
     let(:merge)   { create(:merge_request, source_project: project) }
diff --git a/spec/lib/gitlab/markdown/sanitization_filter_spec.rb b/spec/lib/gitlab/markdown/sanitization_filter_spec.rb
index 4a1aa766149abd400db56a371a4a48cbaf8e1c73..e50c82d0b3c44fa38625edb59f8999e0f6024290 100644
--- a/spec/lib/gitlab/markdown/sanitization_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/sanitization_filter_spec.rb
@@ -2,9 +2,7 @@
 
 module Gitlab::Markdown
   describe SanitizationFilter do
-    def filter(html, options = {})
-      described_class.call(html, options)
-    end
+    include FilterSpecHelper
 
     describe 'default whitelist' do
       it 'sanitizes tags that are not whitelisted' do
@@ -42,6 +40,13 @@ def filter(html, options = {})
     end
 
     describe 'custom whitelist' do
+      it 'customizes the whitelist only once' do
+        instance = described_class.new('Foo')
+        3.times { instance.whitelist }
+
+        expect(instance.whitelist[:transformers].size).to eq 4
+      end
+
       it 'allows syntax highlighting' do
         exp = act = %q{<pre class="code highlight white c"><code><span class="k">def</span></code></pre>}
         expect(filter(act).to_html).to eq exp
@@ -87,5 +92,27 @@ def filter(html, options = {})
         expect(doc.at_css('a')['href']).to be_nil
       end
     end
+
+    context 'when pipeline is :description' do
+      it 'uses a stricter whitelist' do
+        doc = filter('<h1>Description</h1>', pipeline: :description)
+        expect(doc.to_html.strip).to eq 'Description'
+      end
+
+      %w(pre code img ol ul li).each do |elem|
+        it "removes '#{elem}' elements" do
+          act = "<#{elem}>Description</#{elem}>"
+          expect(filter(act, pipeline: :description).to_html.strip).
+            to eq 'Description'
+        end
+      end
+
+      %w(b i strong em a ins del sup sub p).each do |elem|
+        it "still allows '#{elem}' elements" do
+          exp = act = "<#{elem}>Description</#{elem}>"
+          expect(filter(act, pipeline: :description).to_html).to eq exp
+        end
+      end
+    end
   end
 end
diff --git a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
index 07ece66e9034aa3dfe675490d5b5a4bbf6f5c029..38619a3c07f65471452cdb346946669eb7fd2184 100644
--- a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
@@ -2,7 +2,7 @@
 
 module Gitlab::Markdown
   describe SnippetReferenceFilter do
-    include ReferenceFilterSpecHelper
+    include FilterSpecHelper
 
     let(:project)   { create(:empty_project) }
     let(:snippet)   { create(:project_snippet, project: project) }
diff --git a/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb b/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb
index f383a5850d5419ca307b45f317225d31f993d938..ddf583a72c12501bd059b36a94f47a07011a5a2d 100644
--- a/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb
@@ -4,9 +4,7 @@
 
 module Gitlab::Markdown
   describe TableOfContentsFilter do
-    def filter(html, options = {})
-      described_class.call(html, options)
-    end
+    include FilterSpecHelper
 
     def header(level, text)
       "<h#{level}>#{text}</h#{level}>\n"
diff --git a/spec/lib/gitlab/markdown/task_list_filter_spec.rb b/spec/lib/gitlab/markdown/task_list_filter_spec.rb
index 2a1e1cc5127ddf15de70b9ee2d614d2bd7914060..94f39cc966ed55ed6903c85fd01842a19d08acae 100644
--- a/spec/lib/gitlab/markdown/task_list_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/task_list_filter_spec.rb
@@ -2,9 +2,7 @@
 
 module Gitlab::Markdown
   describe TaskListFilter do
-    def filter(html, options = {})
-      described_class.call(html, options)
-    end
+    include FilterSpecHelper
 
     it 'does not apply `task-list` class to non-task lists' do
       exp = act = %(<ul><li>Item</li></ul>)
diff --git a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
index 0ecbdee9b9eb8074053a313afcc5000cdeb3a579..08e6941028f5db93ab3fa5c9294bf0ba3f167961 100644
--- a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
@@ -2,7 +2,7 @@
 
 module Gitlab::Markdown
   describe UserReferenceFilter do
-    include ReferenceFilterSpecHelper
+    include FilterSpecHelper
 
     let(:project)   { create(:empty_project) }
     let(:user)      { create(:user) }
diff --git a/spec/support/reference_filter_spec_helper.rb b/spec/support/filter_spec_helper.rb
similarity index 75%
rename from spec/support/reference_filter_spec_helper.rb
rename to spec/support/filter_spec_helper.rb
index afbea55ab99ef2a726814f41416f7334813320e6..755964e9a3d3819416b52715ac508c1bf41775b1 100644
--- a/spec/support/reference_filter_spec_helper.rb
+++ b/spec/support/filter_spec_helper.rb
@@ -1,46 +1,23 @@
-# Common methods and setup for Gitlab::Markdown reference filter specs
+# Helper methods for Gitlab::Markdown filter specs
 #
 # Must be included into specs manually
-module ReferenceFilterSpecHelper
+module FilterSpecHelper
   extend ActiveSupport::Concern
 
-  # Shortcut to Rails' auto-generated routes helpers, to avoid including the
-  # module
-  def urls
-    Rails.application.routes.url_helpers
-  end
-
-  # Modify a String reference to make it invalid
-  #
-  # Commit SHAs get reversed, IDs get incremented by 1, all other Strings get
-  # their word characters reversed.
-  #
-  # reference - String reference to modify
-  #
-  # Returns a String
-  def invalidate_reference(reference)
-    if reference =~ /\A(.+)?.\d+\z/
-      # Integer-based reference with optional project prefix
-      reference.gsub(/\d+\z/) { |i| i.to_i + 1 }
-    elsif reference =~ /\A(.+@)?(\h{6,40}\z)/
-      # SHA-based reference with optional prefix
-      reference.gsub(/\h{6,40}\z/) { |v| v.reverse }
-    else
-      reference.gsub(/\w+\z/) { |v| v.reverse }
-    end
-  end
-
   # Perform `call` on the described class
   #
-  # Automatically passes the current `project` value to the context if none is
-  # provided.
+  # Automatically passes the current `project` value, if defined, to the context
+  # if none is provided.
   #
-  # html     - String text to pass to the filter's `call` method.
+  # html     - HTML String to pass to the filter's `call` method.
   # contexts - Hash context for the filter. (default: {project: project})
   #
-  # Returns the String text returned by the filter's `call` method.
+  # Returns a Nokogiri::XML::DocumentFragment
   def filter(html, contexts = {})
-    contexts.reverse_merge!(project: project)
+    if defined?(project)
+      contexts.reverse_merge!(project: project)
+    end
+
     described_class.call(html, contexts)
   end
 
@@ -50,7 +27,7 @@ def filter(html, contexts = {})
   # body     - String text to run through the pipeline
   # contexts - Hash context for the filter. (default: {project: project})
   #
-  # Returns the Hash of the pipeline result
+  # Returns the Hash
   def pipeline_result(body, contexts = {})
     contexts.reverse_merge!(project: project)
 
@@ -58,13 +35,43 @@ def pipeline_result(body, contexts = {})
     pipeline.call(body)
   end
 
+  # Modify a String reference to make it invalid
+  #
+  # Commit SHAs get reversed, IDs get incremented by 1, all other Strings get
+  # their word characters reversed.
+  #
+  # reference - String reference to modify
+  #
+  # Returns a String
+  def invalidate_reference(reference)
+    if reference =~ /\A(.+)?.\d+\z/
+      # Integer-based reference with optional project prefix
+      reference.gsub(/\d+\z/) { |i| i.to_i + 1 }
+    elsif reference =~ /\A(.+@)?(\h{6,40}\z)/
+      # SHA-based reference with optional prefix
+      reference.gsub(/\h{6,40}\z/) { |v| v.reverse }
+    else
+      reference.gsub(/\w+\z/) { |v| v.reverse }
+    end
+  end
+
+  # Stub CrossProjectReference#user_can_reference_project? to return true for
+  # the current test
   def allow_cross_reference!
     allow_any_instance_of(described_class).
       to receive(:user_can_reference_project?).and_return(true)
   end
 
+  # Stub CrossProjectReference#user_can_reference_project? to return false for
+  # the current test
   def disallow_cross_reference!
     allow_any_instance_of(described_class).
       to receive(:user_can_reference_project?).and_return(false)
   end
+
+  # Shortcut to Rails' auto-generated routes helpers, to avoid including the
+  # module
+  def urls
+    Rails.application.routes.url_helpers
+  end
 end