about summary refs log tree commit diff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/api/v1/accounts/credentials_controller_spec.rb4
-rw-r--r--spec/controllers/api/v1/accounts/relationships_controller_spec.rb4
-rw-r--r--spec/controllers/api/v1/accounts_controller_spec.rb37
-rw-r--r--spec/controllers/api/v1/mutes_controller_spec.rb22
-rw-r--r--spec/controllers/settings/applications_controller_spec.rb10
-rw-r--r--spec/controllers/settings/keyword_mutes_controller_spec.rb5
-rw-r--r--spec/fabricators/glitch_keyword_mute_fabricator.rb2
-rw-r--r--spec/fabricators/setting_fabricator.rb4
-rw-r--r--spec/fixtures/requests/attachment1.txtbin192053 -> 192052 bytes
-rw-r--r--spec/fixtures/requests/attachment2.txtbin109078 -> 109077 bytes
-rw-r--r--spec/fixtures/requests/avatar.txtbin109962 -> 109961 bytes
-rw-r--r--spec/fixtures/requests/idn.txt14
-rw-r--r--spec/helpers/settings/keyword_mutes_helper_spec.rb15
-rw-r--r--spec/helpers/stream_entries_helper_spec.rb4
-rw-r--r--spec/lib/feed_manager_spec.rb59
-rw-r--r--spec/lib/settings/scoped_settings_spec.rb35
-rw-r--r--spec/lib/user_settings_decorator_spec.rb2
-rw-r--r--spec/models/account_spec.rb8
-rw-r--r--spec/models/concerns/account_interactions_spec.rb75
-rw-r--r--spec/models/email_domain_block_spec.rb5
-rw-r--r--spec/models/follow_request_spec.rb23
-rw-r--r--spec/models/glitch/keyword_mute_spec.rb96
-rw-r--r--spec/models/remote_follow_spec.rb82
-rw-r--r--spec/models/setting_spec.rb184
-rw-r--r--spec/models/site_upload_spec.rb8
-rw-r--r--spec/models/status_spec.rb102
-rw-r--r--spec/models/stream_entry_spec.rb115
-rw-r--r--spec/models/tag_spec.rb7
-rw-r--r--spec/models/user_spec.rb39
-rw-r--r--spec/policies/status_policy_spec.rb6
-rw-r--r--spec/services/activitypub/fetch_remote_status_service_spec.rb2
-rw-r--r--spec/services/follow_service_spec.rb58
-rw-r--r--spec/services/mute_service_spec.rb32
-rw-r--r--spec/services/notify_service_spec.rb63
-rw-r--r--spec/support/matchers/model/model_have_error_on_field.rb2
-rw-r--r--spec/validators/status_length_validator_spec.rb23
-rw-r--r--spec/views/about/show.html.haml_spec.rb6
37 files changed, 1021 insertions, 132 deletions
diff --git a/spec/controllers/api/v1/accounts/credentials_controller_spec.rb b/spec/controllers/api/v1/accounts/credentials_controller_spec.rb
index 461b8b34b..247420d08 100644
--- a/spec/controllers/api/v1/accounts/credentials_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/credentials_controller_spec.rb
@@ -51,7 +51,9 @@ describe Api::V1::Accounts::CredentialsController do
 
       describe 'with invalid data' do
         before do
-          patch :update, params: { note: 'This is too long. ' * 10 }
+          note = 'This is too long. '
+          note = note + 'a' * (Account::MAX_NOTE_LENGTH - note.length + 1)
+          patch :update, params: { note: note }
         end
 
         it 'returns http unprocessable entity' do
diff --git a/spec/controllers/api/v1/accounts/relationships_controller_spec.rb b/spec/controllers/api/v1/accounts/relationships_controller_spec.rb
index 431fc2194..f25b86ac1 100644
--- a/spec/controllers/api/v1/accounts/relationships_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/relationships_controller_spec.rb
@@ -32,7 +32,7 @@ describe Api::V1::Accounts::RelationshipsController do
         json = body_as_json
 
         expect(json).to be_a Enumerable
-        expect(json.first[:following]).to be true
+        expect(json.first[:following]).to be_truthy
         expect(json.first[:followed_by]).to be false
       end
     end
@@ -51,7 +51,7 @@ describe Api::V1::Accounts::RelationshipsController do
 
         expect(json).to be_a Enumerable
         expect(json.first[:id]).to eq simon.id.to_s
-        expect(json.first[:following]).to be true
+        expect(json.first[:following]).to be_truthy
         expect(json.first[:followed_by]).to be false
         expect(json.first[:muting]).to be false
         expect(json.first[:requested]).to be false
diff --git a/spec/controllers/api/v1/accounts_controller_spec.rb b/spec/controllers/api/v1/accounts_controller_spec.rb
index c770649ec..f3b879421 100644
--- a/spec/controllers/api/v1/accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts_controller_spec.rb
@@ -31,10 +31,10 @@ RSpec.describe Api::V1::AccountsController, type: :controller do
         expect(response).to have_http_status(:success)
       end
 
-      it 'returns JSON with following=true and requested=false' do
+      it 'returns JSON with following=truthy and requested=false' do
         json = body_as_json
 
-        expect(json[:following]).to be true
+        expect(json[:following]).to be_truthy
         expect(json[:requested]).to be false
       end
 
@@ -50,11 +50,11 @@ RSpec.describe Api::V1::AccountsController, type: :controller do
         expect(response).to have_http_status(:success)
       end
 
-      it 'returns JSON with following=false and requested=true' do
+      it 'returns JSON with following=false and requested=truthy' do
         json = body_as_json
 
         expect(json[:following]).to be false
-        expect(json[:requested]).to be true
+        expect(json[:requested]).to be_truthy
       end
 
       it 'creates a follow request relation between user and target user' do
@@ -137,6 +137,35 @@ RSpec.describe Api::V1::AccountsController, type: :controller do
     it 'creates a muting relation' do
       expect(user.account.muting?(other_account)).to be true
     end
+
+    it 'mutes notifications' do
+      expect(user.account.muting_notifications?(other_account)).to be true
+    end
+  end
+
+  describe 'POST #mute with notifications set to false' do
+    let(:other_account) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+
+    before do
+      user.account.follow!(other_account)
+      post :mute, params: {id: other_account.id, notifications: false }
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(:success)
+    end
+
+    it 'does not remove the following relation between user and target user' do
+      expect(user.account.following?(other_account)).to be true
+    end
+
+    it 'creates a muting relation' do
+      expect(user.account.muting?(other_account)).to be true
+    end
+
+    it 'does not mute notifications' do
+      expect(user.account.muting_notifications?(other_account)).to be false
+    end
   end
 
   describe 'POST #unmute' do
diff --git a/spec/controllers/api/v1/mutes_controller_spec.rb b/spec/controllers/api/v1/mutes_controller_spec.rb
index 3e6fa887b..7387b9d2d 100644
--- a/spec/controllers/api/v1/mutes_controller_spec.rb
+++ b/spec/controllers/api/v1/mutes_controller_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Api::V1::MutesController, type: :controller do
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'follow') }
 
   before do
-    Fabricate(:mute, account: user.account)
+    Fabricate(:mute, account: user.account, hide_notifications: false)
     allow(controller).to receive(:doorkeeper_token) { token }
   end
 
@@ -18,4 +18,24 @@ RSpec.describe Api::V1::MutesController, type: :controller do
       expect(response).to have_http_status(:success)
     end
   end
+
+  describe 'GET #details' do
+    before do
+      get :details, params: { limit: 1 }
+    end
+
+    let(:mutes) { JSON.parse(response.body) }
+
+    it 'returns http success' do
+      expect(response).to have_http_status(:success)
+    end
+
+    it 'returns one mute' do
+      expect(mutes.size).to be(1)
+    end
+
+    it 'returns whether the mute hides notifications' do
+      expect(mutes.first["hide_notifications"]).to be(false)
+    end 
+  end
 end
diff --git a/spec/controllers/settings/applications_controller_spec.rb b/spec/controllers/settings/applications_controller_spec.rb
index ca66f8d23..90e6a63d5 100644
--- a/spec/controllers/settings/applications_controller_spec.rb
+++ b/spec/controllers/settings/applications_controller_spec.rb
@@ -2,10 +2,10 @@ require 'rails_helper'
 
 describe Settings::ApplicationsController do
   render_views
-  
+
   let!(:user) { Fabricate(:user) }
   let!(:app) { Fabricate(:application, owner: user) }
-  
+
   before do
     sign_in user, scope: :user
   end
@@ -21,7 +21,7 @@ describe Settings::ApplicationsController do
     end
   end
 
-  
+
   describe 'GET #show' do
     it 'returns http success' do
       get :show, params: { id: app.id }
@@ -110,7 +110,7 @@ describe Settings::ApplicationsController do
       end
     end
   end
-  
+
   describe 'PATCH #update' do
     context 'success' do
       let(:opts) {
@@ -131,7 +131,7 @@ describe Settings::ApplicationsController do
         call_update
         expect(app.reload.website).to eql(opts[:website])
       end
-      
+
       it 'redirects back to applications page' do
         expect(call_update).to redirect_to(settings_applications_path)
       end
diff --git a/spec/controllers/settings/keyword_mutes_controller_spec.rb b/spec/controllers/settings/keyword_mutes_controller_spec.rb
new file mode 100644
index 000000000..a8c37a072
--- /dev/null
+++ b/spec/controllers/settings/keyword_mutes_controller_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Settings::KeywordMutesController, type: :controller do
+
+end
diff --git a/spec/fabricators/glitch_keyword_mute_fabricator.rb b/spec/fabricators/glitch_keyword_mute_fabricator.rb
new file mode 100644
index 000000000..20d393320
--- /dev/null
+++ b/spec/fabricators/glitch_keyword_mute_fabricator.rb
@@ -0,0 +1,2 @@
+Fabricator('Glitch::KeywordMute') do
+end
diff --git a/spec/fabricators/setting_fabricator.rb b/spec/fabricators/setting_fabricator.rb
new file mode 100644
index 000000000..336d7c355
--- /dev/null
+++ b/spec/fabricators/setting_fabricator.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+Fabricator(:setting) do
+end
diff --git a/spec/fixtures/requests/attachment1.txt b/spec/fixtures/requests/attachment1.txt
index 77fd9c836..30bd456be 100644
--- a/spec/fixtures/requests/attachment1.txt
+++ b/spec/fixtures/requests/attachment1.txt
Binary files differdiff --git a/spec/fixtures/requests/attachment2.txt b/spec/fixtures/requests/attachment2.txt
index 917a1d398..2a252d2de 100644
--- a/spec/fixtures/requests/attachment2.txt
+++ b/spec/fixtures/requests/attachment2.txt
Binary files differdiff --git a/spec/fixtures/requests/avatar.txt b/spec/fixtures/requests/avatar.txt
index d57b0984f..d771f5dda 100644
--- a/spec/fixtures/requests/avatar.txt
+++ b/spec/fixtures/requests/avatar.txt
Binary files differdiff --git a/spec/fixtures/requests/idn.txt b/spec/fixtures/requests/idn.txt
index 3c76c59c0..5d07f2b79 100644
--- a/spec/fixtures/requests/idn.txt
+++ b/spec/fixtures/requests/idn.txt
@@ -6,7 +6,7 @@ Content-Length: 38111
 Last-Modified: Wed, 20 Jul 2016 02:50:52 GMT

 Connection: keep-alive

 Accept-Ranges: bytes

-

+
 <!DOCTYPE html>

 <html>

 	<head>

@@ -21,16 +21,16 @@ Accept-Ranges: bytes
             var s = document.getElementsByTagName("script")[0]; 

             s.parentNode.insertBefore(hm, s);

           })();

-	

+
     	</script>

-

-

+
+
 		<link rel="stylesheet" type="text/css" href="css/common.css"/>

 		<script src="js/jquery-1.11.1.min.js" type="text/javascript" charset="utf-8"></script>

 		<script src="js/common.js" type="text/javascript" charset="utf-8"></script>

 		<script src="js/carousel.js" type="text/javascript" charset="utf-8"></script>

 		<title>中国域名网站</title>

-

+
 	</head>

 	<body>

 		<div class="head-tips" id="headTip">

@@ -453,7 +453,7 @@ Accept-Ranges: bytes
 					<li><a href="http://新疆农业大学.中国" target="_blank">新疆农业大学.中国</a></li>

 					<li><a href="http://浙江万里学院.中国" target="_blank">浙江万里学院.中国</a></li>

 					<li><a href="http://重庆大学.中国" target="_blank">重庆大学.中国</a></li>

-					

+
 				</ul>

 			</div>

 		</div>

@@ -472,7 +472,7 @@ Accept-Ranges: bytes
 	<script>

 	$("#headTip").hide()

 	var hostname = window.location.hostname || "";

-

+
 	var tips =  "您所访问的域名 <font size='' color='#ff0000'>" + hostname +"</font> 无法到达,您可以尝试重新访问,或使用搜索相关信息"

 	if (hostname != "导航.中国") {

 		$("#headTip").html(tips);

diff --git a/spec/helpers/settings/keyword_mutes_helper_spec.rb b/spec/helpers/settings/keyword_mutes_helper_spec.rb
new file mode 100644
index 000000000..a19d518dd
--- /dev/null
+++ b/spec/helpers/settings/keyword_mutes_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the Settings::KeywordMutesHelper. For example:
+#
+# describe Settings::KeywordMutesHelper do
+#   describe "string concat" do
+#     it "concats two strings with spaces" do
+#       expect(helper.concat_strings("this","that")).to eq("this that")
+#     end
+#   end
+# end
+RSpec.describe Settings::KeywordMutesHelper, type: :helper do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/helpers/stream_entries_helper_spec.rb b/spec/helpers/stream_entries_helper_spec.rb
index 2c0d7b239..1de6691ba 100644
--- a/spec/helpers/stream_entries_helper_spec.rb
+++ b/spec/helpers/stream_entries_helper_spec.rb
@@ -77,7 +77,7 @@ RSpec.describe StreamEntriesHelper, type: :helper do
     params[:controller] = StreamEntriesHelper::EMBEDDED_CONTROLLER
     params[:action] = StreamEntriesHelper::EMBEDDED_ACTION
   end
-  
+
   describe '#style_classes' do
     it do
       status = double(reblog?: false)
@@ -202,7 +202,7 @@ RSpec.describe StreamEntriesHelper, type: :helper do
       expect(css_class).to eq 'h-cite'
     end
   end
-  
+
   describe '#rtl?' do
     it 'is false if text is empty' do
       expect(helper).not_to be_rtl ''
diff --git a/spec/lib/feed_manager_spec.rb b/spec/lib/feed_manager_spec.rb
index 0f97a579e..715d85306 100644
--- a/spec/lib/feed_manager_spec.rb
+++ b/spec/lib/feed_manager_spec.rb
@@ -56,6 +56,13 @@ RSpec.describe FeedManager do
         expect(FeedManager.instance.filter?(:home, reblog, bob.id)).to be true
       end
 
+      it 'returns true for reblog from account with reblogs disabled' do
+        status = Fabricate(:status, text: 'Hello world', account: jeff)
+        reblog = Fabricate(:status, reblog: status, account: alice)
+        bob.follow!(alice, reblogs: false)
+        expect(FeedManager.instance.filter?(:home, reblog, bob.id)).to be true
+      end
+
       it 'returns false for reply by followee to another followee' do
         status = Fabricate(:status, text: 'Hello world', account: jeff)
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: alice)
@@ -105,6 +112,13 @@ RSpec.describe FeedManager do
         expect(FeedManager.instance.filter?(:home, status, bob.id)).to be true
       end
 
+      it 'returns true for status by followee mentioning muted account' do
+        bob.mute!(jeff)
+        bob.follow!(alice)
+        status = PostStatusService.new.call(alice, 'Hey @jeff')
+        expect(FeedManager.instance.filter?(:home, status, bob.id)).to be true
+      end
+
       it 'returns true for reblog of a personally blocked domain' do
         alice.block_domain!('example.com')
         alice.follow!(jeff)
@@ -112,6 +126,44 @@ RSpec.describe FeedManager do
         reblog = Fabricate(:status, reblog: status, account: jeff)
         expect(FeedManager.instance.filter?(:home, reblog, alice.id)).to be true
       end
+
+      it 'returns true for a status containing a muted keyword' do
+        Fabricate('Glitch::KeywordMute', account: alice, keyword: 'take')
+        status = Fabricate(:status, text: 'This is a hot take', account: bob)
+
+        expect(FeedManager.instance.filter?(:home, status, alice.id)).to be true
+      end
+
+      it 'returns true for a reply containing a muted keyword' do
+        Fabricate('Glitch::KeywordMute', account: alice, keyword: 'take')
+        s1 = Fabricate(:status, text: 'Something', account: alice)
+        s2 = Fabricate(:status, text: 'This is a hot take', thread: s1, account: bob)
+
+        expect(FeedManager.instance.filter?(:home, s2, alice.id)).to be true
+      end
+
+      it 'returns true for a status whose spoiler text contains a muted keyword' do
+        Fabricate('Glitch::KeywordMute', account: alice, keyword: 'take')
+        status = Fabricate(:status, spoiler_text: 'This is a hot take', account: bob)
+
+        expect(FeedManager.instance.filter?(:home, status, alice.id)).to be true
+      end
+
+      it 'returns true for a reblog containing a muted keyword' do
+        Fabricate('Glitch::KeywordMute', account: alice, keyword: 'take')
+        status = Fabricate(:status, text: 'This is a hot take', account: bob)
+        reblog = Fabricate(:status, reblog: status, account: jeff)
+
+        expect(FeedManager.instance.filter?(:home, reblog, alice.id)).to be true
+      end
+
+      it 'returns true for a reblog whose spoiler text contains a muted keyword' do
+        Fabricate('Glitch::KeywordMute', account: alice, keyword: 'take')
+        status = Fabricate(:status, spoiler_text: 'This is a hot take', account: bob)
+        reblog = Fabricate(:status, reblog: status, account: jeff)
+
+        expect(FeedManager.instance.filter?(:home, reblog, alice.id)).to be true
+      end
     end
 
     context 'for mentions feed' do
@@ -140,6 +192,13 @@ RSpec.describe FeedManager do
         bob.follow!(alice)
         expect(FeedManager.instance.filter?(:mentions, status, bob.id)).to be false
       end
+
+      it 'returns true for status that contains a muted keyword' do
+        Fabricate('Glitch::KeywordMute', account: bob, keyword: 'take')
+        status = Fabricate(:status, text: 'This is a hot take', account: alice)
+        bob.follow!(alice)
+        expect(FeedManager.instance.filter?(:mentions, status, bob.id)).to be true
+      end
     end
   end
 
diff --git a/spec/lib/settings/scoped_settings_spec.rb b/spec/lib/settings/scoped_settings_spec.rb
new file mode 100644
index 000000000..7566685b4
--- /dev/null
+++ b/spec/lib/settings/scoped_settings_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Settings::ScopedSettings do
+  let(:object)         { Fabricate(:user) }
+  let(:scoped_setting) { described_class.new(object) }
+  let(:val)            { 'whatever' }
+  let(:methods)        { %i(auto_play_gif default_sensitive unfollow_modal boost_modal delete_modal reduce_motion system_font_ui noindex theme) }
+
+  describe '.initialize' do
+    it 'sets @object' do
+      scoped_setting = described_class.new(object)
+      expect(scoped_setting.instance_variable_get(:@object)).to be object
+    end
+  end
+
+  describe '#method_missing' do
+    it 'sets scoped_setting.method_name = val' do
+      methods.each do |key|
+        scoped_setting.send("#{key}=", val)
+        expect(scoped_setting.send(key)).to eq val
+      end
+    end
+  end
+
+  describe '#[]= and #[]' do
+    it 'sets [key] = val' do
+      methods.each do |key|
+        scoped_setting[key] = val
+        expect(scoped_setting[key]).to eq val
+      end
+    end
+  end
+end
diff --git a/spec/lib/user_settings_decorator_spec.rb b/spec/lib/user_settings_decorator_spec.rb
index 6fbf6536b..fee875373 100644
--- a/spec/lib/user_settings_decorator_spec.rb
+++ b/spec/lib/user_settings_decorator_spec.rb
@@ -62,7 +62,7 @@ describe UserSettingsDecorator do
       settings.update(values)
       expect(user.settings['auto_play_gif']).to eq false
     end
-    
+
     it 'updates the user settings value for system font in UI' do
       values = { 'setting_system_font_ui' => '0' }
 
diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb
index 9c1492c90..7501c498c 100644
--- a/spec/models/account_spec.rb
+++ b/spec/models/account_spec.rb
@@ -636,8 +636,8 @@ RSpec.describe Account, type: :model do
         expect(account).to model_have_error_on_field(:display_name)
       end
 
-      it 'is invalid if the note is longer than 160 characters' do
-        account = Fabricate.build(:account, note: Faker::Lorem.characters(161))
+      it 'is invalid if the note is longer than 500 characters' do
+        account = Fabricate.build(:account, note: Faker::Lorem.characters(501))
         account.valid?
         expect(account).to model_have_error_on_field(:note)
       end
@@ -676,8 +676,8 @@ RSpec.describe Account, type: :model do
         expect(account).not_to model_have_error_on_field(:display_name)
       end
 
-      it 'is valid even if the note is longer than 160 characters' do
-        account = Fabricate.build(:account, domain: 'domain', note: Faker::Lorem.characters(161))
+      it 'is valid even if the note is longer than 500 characters' do
+        account = Fabricate.build(:account, domain: 'domain', note: Faker::Lorem.characters(501))
         account.valid?
         expect(account).not_to model_have_error_on_field(:note)
       end
diff --git a/spec/models/concerns/account_interactions_spec.rb b/spec/models/concerns/account_interactions_spec.rb
new file mode 100644
index 000000000..1e238e27c
--- /dev/null
+++ b/spec/models/concerns/account_interactions_spec.rb
@@ -0,0 +1,75 @@
+require 'rails_helper'
+
+describe AccountInteractions do
+  describe 'muting an account' do
+    let(:me) { Fabricate(:account, username: 'Me') }
+    let(:you) { Fabricate(:account, username: 'You') }
+
+    context 'with the notifications option unspecified' do
+      before do
+        me.mute!(you)
+      end
+
+      it 'defaults to muting notifications' do
+        expect(me.muting_notifications?(you)).to be true
+      end
+    end
+
+    context 'with the notifications option set to false' do
+      before do
+        me.mute!(you, notifications: false)
+      end
+
+      it 'does not mute notifications' do
+        expect(me.muting_notifications?(you)).to be false
+      end
+    end
+
+    context 'with the notifications option set to true' do
+      before do
+        me.mute!(you, notifications: true)
+      end
+
+      it 'does mute notifications' do
+        expect(me.muting_notifications?(you)).to be true 
+      end
+    end
+  end
+
+  describe 'ignoring reblogs from an account' do
+    before do
+      @me = Fabricate(:account, username: 'Me')
+      @you = Fabricate(:account, username: 'You')
+    end
+
+    context 'with the reblogs option unspecified' do
+      before do
+        @me.follow!(@you)
+      end
+
+      it 'defaults to showing reblogs' do
+        expect(@me.muting_reblogs?(@you)).to be(false)
+      end
+    end
+
+    context 'with the reblogs option set to false' do
+      before do
+        @me.follow!(@you, reblogs: false)
+      end
+
+      it 'does mute reblogs' do
+        expect(@me.muting_reblogs?(@you)).to be(true)
+      end
+    end
+
+    context 'with the reblogs option set to true' do
+      before do
+        @me.follow!(@you, reblogs: true)
+      end
+
+      it 'does not mute reblogs' do
+        expect(@me.muting_reblogs?(@you)).to be(false)
+      end
+    end
+  end
+end
diff --git a/spec/models/email_domain_block_spec.rb b/spec/models/email_domain_block_spec.rb
index 5f5d189d9..efd2853a9 100644
--- a/spec/models/email_domain_block_spec.rb
+++ b/spec/models/email_domain_block_spec.rb
@@ -13,9 +13,10 @@ RSpec.describe EmailDomainBlock, type: :model do
       Fabricate(:email_domain_block, domain: 'example.com')
       expect(EmailDomainBlock.block?('nyarn@example.com')).to eq true
     end
+
     it 'returns true if the domain is not registed' do
-      Fabricate(:email_domain_block, domain: 'domain')
-      expect(EmailDomainBlock.block?('example')).to eq false
+      Fabricate(:email_domain_block, domain: 'example.com')
+      expect(EmailDomainBlock.block?('nyarn@example.net')).to eq false
     end
   end
 end
diff --git a/spec/models/follow_request_spec.rb b/spec/models/follow_request_spec.rb
index 1436501e9..7bc93a2aa 100644
--- a/spec/models/follow_request_spec.rb
+++ b/spec/models/follow_request_spec.rb
@@ -7,10 +7,31 @@ RSpec.describe FollowRequest, type: :model do
     let(:target_account) { Fabricate(:account) }
 
     it 'calls Account#follow!, MergeWorker.perform_async, and #destroy!' do
-      expect(account).to        receive(:follow!).with(target_account)
+      expect(account).to        receive(:follow!).with(target_account, reblogs: true)
       expect(MergeWorker).to    receive(:perform_async).with(target_account.id, account.id)
       expect(follow_request).to receive(:destroy!)
       follow_request.authorize!
     end
+
+    it 'generates a Follow' do
+      follow_request = Fabricate.create(:follow_request)
+      follow_request.authorize!
+      target = follow_request.target_account
+      expect(follow_request.account.following?(target)).to be true
+    end
+
+    it 'correctly passes show_reblogs when true' do
+      follow_request = Fabricate.create(:follow_request, show_reblogs: true)
+      follow_request.authorize!
+      target = follow_request.target_account
+      expect(follow_request.account.muting_reblogs?(target)).to be false
+    end
+
+    it 'correctly passes show_reblogs when false' do
+      follow_request = Fabricate.create(:follow_request, show_reblogs: false)
+      follow_request.authorize!
+      target = follow_request.target_account
+      expect(follow_request.account.muting_reblogs?(target)).to be true
+    end
   end
 end
diff --git a/spec/models/glitch/keyword_mute_spec.rb b/spec/models/glitch/keyword_mute_spec.rb
new file mode 100644
index 000000000..9685c6493
--- /dev/null
+++ b/spec/models/glitch/keyword_mute_spec.rb
@@ -0,0 +1,96 @@
+require 'rails_helper'
+
+RSpec.describe Glitch::KeywordMute, type: :model do
+  let(:alice) { Fabricate(:account, username: 'alice').tap(&:save!) }
+  let(:bob) { Fabricate(:account, username: 'bob').tap(&:save!) }
+
+  describe '.matcher_for' do
+    let(:matcher) { Glitch::KeywordMute.matcher_for(alice) }
+
+    describe 'with no mutes' do
+      before do
+        Glitch::KeywordMute.delete_all
+      end
+
+      it 'does not match' do
+        expect(matcher =~ 'This is a hot take').to be_falsy
+      end
+    end
+
+    describe 'with mutes' do
+      it 'does not match keywords set by a different account' do
+        Glitch::KeywordMute.create!(account: bob, keyword: 'take')
+
+        expect(matcher =~ 'This is a hot take').to be_falsy
+      end
+
+      it 'does not match if no keywords match the status text' do
+        Glitch::KeywordMute.create!(account: alice, keyword: 'cold')
+
+        expect(matcher =~ 'This is a hot take').to be_falsy
+      end
+
+      it 'considers word boundaries when matching' do
+        Glitch::KeywordMute.create!(account: alice, keyword: 'bob', whole_word: true)
+
+        expect(matcher =~ 'bobcats').to be_falsy
+      end
+
+      it 'matches substrings if whole_word is false' do
+        Glitch::KeywordMute.create!(account: alice, keyword: 'take', whole_word: false)
+
+        expect(matcher =~ 'This is a shiitake mushroom').to be_truthy
+      end
+
+      it 'matches keywords at the beginning of the text' do
+        Glitch::KeywordMute.create!(account: alice, keyword: 'take')
+
+        expect(matcher =~ 'Take this').to be_truthy
+      end
+
+      it 'matches keywords at the end of the text' do
+        Glitch::KeywordMute.create!(account: alice, keyword: 'take')
+
+        expect(matcher =~ 'This is a hot take').to be_truthy
+      end
+
+      it 'matches if at least one keyword case-insensitively matches the text' do
+        Glitch::KeywordMute.create!(account: alice, keyword: 'hot')
+
+        expect(matcher =~ 'This is a HOT take').to be_truthy
+      end
+
+      it 'maintains case-insensitivity when combining keywords into a single matcher' do
+        Glitch::KeywordMute.create!(account: alice, keyword: 'hot')
+        Glitch::KeywordMute.create!(account: alice, keyword: 'cold')
+
+        expect(matcher =~ 'This is a HOT take').to be_truthy
+      end
+
+      it 'matches keywords surrounded by non-alphanumeric ornamentation' do
+        Glitch::KeywordMute.create!(account: alice, keyword: 'hot')
+
+        expect(matcher =~ '(hot take)').to be_truthy
+      end
+
+      it 'escapes metacharacters in keywords' do
+        Glitch::KeywordMute.create!(account: alice, keyword: '(hot take)')
+
+        expect(matcher =~ '(hot take)').to be_truthy
+      end
+
+      it 'uses case-folding rules appropriate for more than just English' do
+        Glitch::KeywordMute.create!(account: alice, keyword: 'großeltern')
+
+        expect(matcher =~ 'besuch der grosseltern').to be_truthy
+      end
+
+      it 'matches keywords that are composed of multiple words' do
+        Glitch::KeywordMute.create!(account: alice, keyword: 'a shiitake')
+
+        expect(matcher =~ 'This is a shiitake').to be_truthy
+        expect(matcher =~ 'This is shiitake').to_not be_truthy
+      end
+    end
+  end
+end
diff --git a/spec/models/remote_follow_spec.rb b/spec/models/remote_follow_spec.rb
index 0b3adc9f9..72c580f9f 100644
--- a/spec/models/remote_follow_spec.rb
+++ b/spec/models/remote_follow_spec.rb
@@ -3,83 +3,65 @@
 require 'rails_helper'
 
 RSpec.describe RemoteFollow do
+  before do
+    stub_request(:get, 'https://quitter.no/.well-known/webfinger?resource=acct:gargron@quitter.no').to_return(request_fixture('webfinger.txt'))
+  end
+
+  let(:attrs)         { nil }
+  let(:remote_follow) { described_class.new(attrs) }
+
   describe '.initialize' do
-    let(:remote_follow) { RemoteFollow.new(option) }
+    subject { remote_follow.acct }
 
-    context 'option with acct' do
-      let(:option) { { acct: 'hoge@example.com' } }
+    context 'attrs with acct' do
+      let(:attrs) { { acct: 'gargron@quitter.no' } }
 
-      it 'sets acct' do
-        expect(remote_follow.acct).to eq 'hoge@example.com'
+      it 'returns acct' do
+        is_expected.to eq 'gargron@quitter.no'
       end
     end
 
-    context 'option without acct' do
-      let(:option) { {} }
+    context 'attrs without acct' do
+      let(:attrs) { {} }
 
-      it 'does not set acct' do
-        expect(remote_follow.acct).to be_nil
+      it do
+        is_expected.to be_nil
       end
     end
   end
 
   describe '#valid?' do
-    let(:remote_follow) { RemoteFollow.new }
-
-    context 'super is falsy' do
-      module InvalidSuper
-        def valid?
-          nil
-        end
-      end
+    subject { remote_follow.valid? }
 
-      before do
-        class RemoteFollow
-          include InvalidSuper
-        end
-      end
+    context 'attrs with acct' do
+      let(:attrs) { { acct: 'gargron@quitter.no' }}
 
-      it 'returns false without calling #populate_template and #errors' do
-        expect(remote_follow).not_to receive(:populate_template)
-        expect(remote_follow).not_to receive(:errors)
-        expect(remote_follow.valid?).to be false
+      it do
+        is_expected.to be true
       end
     end
 
-    context 'super is truthy' do
-      module ValidSuper
-        def valid?
-          true
-        end
-      end
+    context 'attrs without acct' do
+      let(:attrs) { { } }
 
-      before do
-        class RemoteFollow
-          include ValidSuper
-        end
-      end
-
-      it 'calls #populate_template and #errors.empty?' do
-        expect(remote_follow).to receive(:populate_template)
-        expect(remote_follow).to receive_message_chain(:errors, :empty?)
-        remote_follow.valid?
+      it do
+        is_expected.to be false
       end
     end
   end
 
   describe '#subscribe_address_for' do
     before do
-      allow(remote_follow).to receive(:addressable_template).and_return(addressable_template)
+      remote_follow.valid?
     end
 
-    let(:account)                   { instance_double('Account', local_username_and_domain: local_username_and_domain) }
-    let(:addressable_template)      { instance_double('Addressable::Template') }
-    let(:local_username_and_domain) { 'hoge@example.com' }
-    let(:remote_follow)             { RemoteFollow.new }
+    let(:attrs)   { { acct: 'gargron@quitter.no' } }
+    let(:account) { Fabricate(:account, username: 'alice') }
+
+    subject { remote_follow.subscribe_address_for(account) }
 
-    it 'calls Addressable::Template#expand.to_s' do
-      expect(addressable_template).to receive_message_chain(:expand, :to_s).with(uri: local_username_and_domain).with(no_args)
-      remote_follow.subscribe_address_for(account)
+    it 'returns subscribe address' do
+      is_expected.to eq 'https://quitter.no/main/ostatussub?profile=alice%40cb6e6126.ngrok.io'
     end
   end
 end
diff --git a/spec/models/setting_spec.rb b/spec/models/setting_spec.rb
new file mode 100644
index 000000000..e99dfc0d7
--- /dev/null
+++ b/spec/models/setting_spec.rb
@@ -0,0 +1,184 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Setting, type: :model do
+  describe '#to_param' do
+    let(:setting) { Fabricate(:setting, var: var) }
+    let(:var)     { 'var' }
+
+    it 'returns setting.var' do
+      expect(setting.to_param).to eq var
+    end
+  end
+
+  describe '.[]' do
+    before do
+      allow(described_class).to receive(:rails_initialized?).and_return(rails_initialized)
+    end
+
+    let(:key) { 'key' }
+
+    context 'rails_initialized? is falsey' do
+      let(:rails_initialized) { false }
+
+      it 'calls RailsSettings::Base#[]' do
+        expect(RailsSettings::Base).to receive(:[]).with(key)
+        described_class[key]
+      end
+    end
+
+    context 'rails_initialized? is truthy' do
+      before do
+        allow(RailsSettings::Base).to receive(:cache_key).with(key, nil).and_return(cache_key)
+      end
+
+      let(:rails_initialized) { true }
+      let(:cache_key)         { 'cache-key' }
+      let(:cache_value)       { 'cache-value' }
+
+      it 'calls not RailsSettings::Base#[]' do
+        expect(RailsSettings::Base).not_to receive(:[]).with(key)
+        described_class[key]
+      end
+
+      it 'calls Rails.cache.fetch' do
+        expect(Rails).to receive_message_chain(:cache, :fetch).with(cache_key)
+        described_class[key]
+      end
+
+      context 'Rails.cache does not exists' do
+        before do
+          allow(RailsSettings::Settings).to receive(:object).with(key).and_return(object)
+          allow(described_class).to receive(:default_settings).and_return(default_settings)
+          allow_any_instance_of(Settings::ScopedSettings).to receive(:thing_scoped).and_return(records)
+          Rails.cache.clear(cache_key)
+        end
+
+        let(:object)           { nil }
+        let(:default_value)    { 'default_value' }
+        let(:default_settings) { { key => default_value } }
+        let(:records)          { [Fabricate(:setting, var: key, value: nil)] }
+
+        it 'calls RailsSettings::Settings.object' do
+          expect(RailsSettings::Settings).to receive(:object).with(key)
+          described_class[key]
+        end
+
+        context 'RailsSettings::Settings.object returns truthy' do
+          let(:object) { db_val }
+          let(:db_val) { double(value: 'db_val') }
+
+          context 'default_value is a Hash' do
+            let(:default_value) { { default_value: 'default_value' } }
+
+            it 'calls default_value.with_indifferent_access.merge!' do
+              expect(default_value).to receive_message_chain(:with_indifferent_access, :merge!)
+                .with(db_val.value)
+
+              described_class[key]
+            end
+          end
+
+          context 'default_value is not a Hash' do
+            let(:default_value) { 'default_value' }
+
+            it 'returns db_val.value' do
+              expect(described_class[key]).to be db_val.value
+            end
+          end
+        end
+
+        context 'RailsSettings::Settings.object returns falsey' do
+          let(:object) { nil }
+
+          it 'returns default_settings[key]' do
+            expect(described_class[key]).to be default_settings[key]
+          end
+        end
+      end
+
+      context 'Rails.cache exists' do
+        before do
+          Rails.cache.write(cache_key, cache_value)
+        end
+
+        it 'returns the cached value' do
+          expect(described_class[key]).to eq cache_value
+        end
+      end
+    end
+  end
+
+  describe '.all_as_records' do
+    before do
+      allow_any_instance_of(Settings::ScopedSettings).to receive(:thing_scoped).and_return(records)
+      allow(described_class).to receive(:default_settings).and_return(default_settings)
+    end
+
+    let(:key)              { 'key' }
+    let(:default_value)    { 'default_value' }
+    let(:default_settings) { { key => default_value } }
+    let(:original_setting) { Fabricate(:setting, var: key, value: nil) }
+    let(:records)          { [original_setting] }
+
+    it 'returns a Hash' do
+      expect(described_class.all_as_records).to be_kind_of Hash
+    end
+
+    context 'records includes Setting with var as the key' do
+      let(:records) { [original_setting] }
+
+      it 'includes the original Setting' do
+        setting = described_class.all_as_records[key]
+        expect(setting).to eq original_setting
+      end
+    end
+
+    context 'records includes nothing' do
+      let(:records) { [] }
+
+      context 'default_value is not a Hash' do
+        it 'includes Setting with value of default_value' do
+          setting = described_class.all_as_records[key]
+
+          expect(setting).to be_kind_of Setting
+          expect(setting).to have_attributes(var: key)
+          expect(setting).to have_attributes(value: 'default_value')
+        end
+      end
+
+      context 'default_value is a Hash' do
+        let(:default_value) { { 'foo' => 'fuga' } }
+
+        it 'returns {}' do
+          expect(described_class.all_as_records).to eq({})
+        end
+      end
+    end
+  end
+
+  describe '.default_settings' do
+    before do
+      allow(RailsSettings::Default).to receive(:enabled?).and_return(enabled)
+    end
+
+    subject { described_class.default_settings }
+
+    context 'RailsSettings::Default.enabled? is false' do
+      let(:enabled) { false }
+
+      it 'returns {}' do
+        is_expected.to eq({})
+      end
+    end
+
+    context 'RailsSettings::Settings.enabled? is true' do
+      let(:enabled) { true }
+
+      it 'returns instance of RailsSettings::Default' do
+        is_expected.to be_kind_of RailsSettings::Default
+      end
+    end
+  end
+end
diff --git a/spec/models/site_upload_spec.rb b/spec/models/site_upload_spec.rb
index 8745d54b8..f7ea06921 100644
--- a/spec/models/site_upload_spec.rb
+++ b/spec/models/site_upload_spec.rb
@@ -1,5 +1,13 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe SiteUpload, type: :model do
+  describe '#cache_key' do
+    let(:site_upload) { SiteUpload.new(var: 'var') }
 
+    it 'returns cache_key' do
+      expect(site_upload.cache_key).to eq 'site_uploads/var'
+    end
+  end
 end
diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb
index 9cb71d715..89ad3adcf 100644
--- a/spec/models/status_spec.rb
+++ b/spec/models/status_spec.rb
@@ -47,8 +47,27 @@ RSpec.describe Status, type: :model do
   end
 
   describe '#verb' do
-    it 'is always post' do
-      expect(subject.verb).to be :post
+    context 'if destroyed?' do
+      it 'returns :delete' do
+        subject.destroy!
+        expect(subject.verb).to be :delete
+      end
+    end
+
+    context 'unless destroyed?' do
+      context 'if reblog?' do
+        it 'returns :share' do
+          subject.reblog = other
+          expect(subject.verb).to be :share
+        end
+      end
+
+      context 'unless reblog?' do
+        it 'returns :post' do
+          subject.reblog = nil
+          expect(subject.verb).to be :post
+        end
+      end
     end
   end
 
@@ -69,6 +88,36 @@ RSpec.describe Status, type: :model do
     end
   end
 
+  describe '#hidden?' do
+    context 'if private_visibility?' do
+      it 'returns true' do
+        subject.visibility = :private
+        expect(subject.hidden?).to be true
+      end
+    end
+
+    context 'if direct_visibility?' do
+      it 'returns true' do
+        subject.visibility = :direct
+        expect(subject.hidden?).to be true
+      end
+    end
+
+    context 'if public_visibility?' do
+      it 'returns false' do
+        subject.visibility = :public
+        expect(subject.hidden?).to be false
+      end
+    end
+
+    context 'if unlisted_visibility?' do
+      it 'returns false' do
+        subject.visibility = :unlisted
+        expect(subject.hidden?).to be false
+      end
+    end
+  end
+
   describe '#content' do
     it 'returns the text of the status if it is not a reblog' do
       expect(subject.content).to eql subject.text
@@ -232,6 +281,55 @@ RSpec.describe Status, type: :model do
     end
   end
 
+  describe '.as_direct_timeline' do
+    let(:account) { Fabricate(:account) }
+    let(:followed) { Fabricate(:account) }
+    let(:not_followed) { Fabricate(:account) }
+
+    before do
+      Fabricate(:follow, account: account, target_account: followed)
+
+      @self_public_status = Fabricate(:status, account: account, visibility: :public)
+      @self_direct_status = Fabricate(:status, account: account, visibility: :direct)
+      @followed_public_status = Fabricate(:status, account: followed, visibility: :public)
+      @followed_direct_status = Fabricate(:status, account: followed, visibility: :direct)
+      @not_followed_direct_status = Fabricate(:status, account: not_followed, visibility: :direct)
+
+      @results = Status.as_direct_timeline(account)
+    end
+
+    it 'does not include public statuses from self' do
+      expect(@results).to_not include(@self_public_status)
+    end
+
+    it 'includes direct statuses from self' do
+      expect(@results).to include(@self_direct_status)
+    end
+
+    it 'does not include public statuses from followed' do
+      expect(@results).to_not include(@followed_public_status)
+    end
+
+    it 'includes direct statuses mentioning recipient from followed' do
+      Fabricate(:mention, account: account, status: @followed_direct_status)
+      expect(@results).to include(@followed_direct_status)
+    end
+
+    it 'does not include direct statuses not mentioning recipient from followed' do
+      expect(@results).to_not include(@followed_direct_status)
+    end
+
+    it 'includes direct statuses mentioning recipient from non-followed' do
+      Fabricate(:mention, account: account, status: @not_followed_direct_status)
+      expect(@results).to include(@not_followed_direct_status)
+    end
+
+    it 'does not include direct statuses not mentioning recipient from non-followed' do
+      expect(@results).to_not include(@not_followed_direct_status)
+    end
+
+  end
+
   describe '.as_public_timeline' do
     it 'only includes statuses with public visibility' do
       public_status = Fabricate(:status, visibility: :public)
diff --git a/spec/models/stream_entry_spec.rb b/spec/models/stream_entry_spec.rb
index 3b7ff5143..8f8bfbd58 100644
--- a/spec/models/stream_entry_spec.rb
+++ b/spec/models/stream_entry_spec.rb
@@ -6,6 +6,121 @@ RSpec.describe StreamEntry, type: :model do
   let(:status)    { Fabricate(:status, account: alice) }
   let(:reblog)    { Fabricate(:status, account: bob, reblog: status) }
   let(:reply)     { Fabricate(:status, account: bob, thread: status) }
+  let(:stream_entry) { Fabricate(:stream_entry, activity: activity) }
+  let(:activity)     { reblog }
+
+  describe '#object_type' do
+    before do
+      allow(stream_entry).to receive(:orphaned?).and_return(orphaned)
+      allow(stream_entry).to receive(:targeted?).and_return(targeted)
+    end
+
+    subject { stream_entry.object_type }
+
+    context 'orphaned? is true' do
+      let(:orphaned) { true }
+      let(:targeted) { false }
+
+      it 'returns :activity' do
+        is_expected.to be :activity
+      end
+    end
+
+    context 'targeted? is true' do
+      let(:orphaned) { false }
+      let(:targeted) { true }
+
+      it 'returns :activity' do
+        is_expected.to be :activity
+      end
+    end
+
+    context 'orphaned? and targeted? are false' do
+      let(:orphaned) { false }
+      let(:targeted) { false }
+
+      context 'activity is reblog' do
+        let(:activity) { reblog }
+
+        it 'returns :note' do
+          is_expected.to be :note
+        end
+      end
+
+      context 'activity is reply' do
+        let(:activity) { reply }
+
+        it 'returns :comment' do
+          is_expected.to be :comment
+        end
+      end
+    end
+  end
+
+  describe '#verb' do
+    before do
+      allow(stream_entry).to receive(:orphaned?).and_return(orphaned)
+    end
+
+    subject { stream_entry.verb }
+
+    context 'orphaned? is true' do
+      let(:orphaned) { true }
+
+      it 'returns :delete' do
+        is_expected.to be :delete
+      end
+    end
+
+    context 'orphaned? is false' do
+      let(:orphaned) { false }
+
+      context 'activity is reblog' do
+        let(:activity) { reblog }
+
+        it 'returns :share' do
+          is_expected.to be :share
+        end
+      end
+
+      context 'activity is reply' do
+        let(:activity) { reply }
+
+        it 'returns :post' do
+          is_expected.to be :post
+        end
+      end
+    end
+  end
+
+  describe '#mentions' do
+    before do
+      allow(stream_entry).to receive(:orphaned?).and_return(orphaned)
+    end
+
+    subject { stream_entry.mentions }
+
+    context 'orphaned? is true' do
+      let(:orphaned) { true }
+
+      it 'returns []' do
+        is_expected.to eq []
+      end
+    end
+
+    context 'orphaned? is false' do
+      before do
+        reblog.mentions << Fabricate(:mention, account: alice)
+        reblog.mentions << Fabricate(:mention, account: bob)
+      end
+
+      let(:orphaned) { false }
+
+      it 'returns [Account] includes alice and bob' do
+        is_expected.to eq [alice, bob]
+      end
+    end
+  end
 
   describe '#targeted?' do
     it 'returns true for a reblog' do
diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb
index f727fa1dd..1ca50cc29 100644
--- a/spec/models/tag_spec.rb
+++ b/spec/models/tag_spec.rb
@@ -35,6 +35,13 @@ RSpec.describe Tag, type: :model do
     end
   end
 
+  describe '#to_param' do
+    it 'returns name' do
+      tag = Fabricate(:tag, name: 'foo')
+      expect(tag.to_param).to eq 'foo'
+    end
+  end
+
   describe '.search_for' do
     it 'finds tag records with matching names' do
       tag = Fabricate(:tag, name: "match")
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 99aeca01b..77a12c26d 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -177,27 +177,10 @@ RSpec.describe User, type: :model do
     end
   end
 
-  describe '#setting_auto_play_gif' do
-    it 'returns auto-play gif setting' do
+  describe 'settings' do
+    it 'is instance of Settings::ScopedSettings' do
       user = Fabricate(:user)
-      user.settings[:auto_play_gif] = false
-      expect(user.setting_auto_play_gif).to eq false
-    end
-  end
-  
-  describe '#setting_system_font_ui' do
-    it 'returns system font ui setting' do
-      user = Fabricate(:user)
-      user.settings[:system_font_ui] = false
-      expect(user.setting_system_font_ui).to eq false
-    end
-  end
-
-  describe '#setting_boost_modal' do
-    it 'returns boost modal setting' do
-      user = Fabricate(:user)
-      user.settings[:boost_modal] = false
-      expect(user.setting_boost_modal).to eq false
+      expect(user.settings).to be_kind_of Settings::ScopedSettings
     end
   end
 
@@ -219,22 +202,6 @@ RSpec.describe User, type: :model do
     end
   end
 
-  describe '#setting_unfollow_modal' do
-    it 'returns unfollow modal setting' do
-      user = Fabricate(:user)
-      user.settings[:unfollow_modal] = true
-      expect(user.setting_unfollow_modal).to eq true
-    end
-  end
-
-  describe '#setting_delete_modal' do
-    it 'returns delete modal setting' do
-      user = Fabricate(:user)
-      user.settings[:delete_modal] = false
-      expect(user.setting_delete_modal).to eq false
-    end
-  end
-
   describe 'whitelist' do
     around(:each) do |example|
       old_whitelist = Rails.configuration.x.email_whitelist
diff --git a/spec/policies/status_policy_spec.rb b/spec/policies/status_policy_spec.rb
index bacb8fd9e..a90e22aad 100644
--- a/spec/policies/status_policy_spec.rb
+++ b/spec/policies/status_policy_spec.rb
@@ -71,6 +71,12 @@ RSpec.describe StatusPolicy, type: :model do
 
       expect(subject).to_not permit(viewer, status)
     end
+
+    it 'denies access when local-only and the viewer is not logged in' do
+      allow(status).to receive(:local_only?) { true }
+
+      expect(subject).to_not permit(nil, status)
+    end
   end
 
   permissions :reblog? do
diff --git a/spec/services/activitypub/fetch_remote_status_service_spec.rb b/spec/services/activitypub/fetch_remote_status_service_spec.rb
index ebf422392..51f3fe3a1 100644
--- a/spec/services/activitypub/fetch_remote_status_service_spec.rb
+++ b/spec/services/activitypub/fetch_remote_status_service_spec.rb
@@ -27,7 +27,7 @@ RSpec.describe ActivityPub::FetchRemoteStatusService do
 
       it 'creates status' do
         status = sender.statuses.first
-        
+
         expect(status).to_not be_nil
         expect(status.text).to eq 'Lorem ipsum'
       end
diff --git a/spec/services/follow_service_spec.rb b/spec/services/follow_service_spec.rb
index ceb39e5e6..e59a2f1a6 100644
--- a/spec/services/follow_service_spec.rb
+++ b/spec/services/follow_service_spec.rb
@@ -13,8 +13,20 @@ RSpec.describe FollowService do
         subject.call(sender, bob.acct)
       end
 
-      it 'creates a follow request' do
-        expect(FollowRequest.find_by(account: sender, target_account: bob)).to_not be_nil
+      it 'creates a follow request with reblogs' do
+        expect(FollowRequest.find_by(account: sender, target_account: bob, show_reblogs: true)).to_not be_nil
+      end
+    end
+
+    describe 'locked account, no reblogs' do
+      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, locked: true, username: 'bob')).account }
+
+      before do
+        subject.call(sender, bob.acct, reblogs: false)
+      end
+
+      it 'creates a follow request without reblogs' do
+        expect(FollowRequest.find_by(account: sender, target_account: bob, show_reblogs: false)).to_not be_nil
       end
     end
 
@@ -25,8 +37,22 @@ RSpec.describe FollowService do
         subject.call(sender, bob.acct)
       end
 
-      it 'creates a following relation' do
+      it 'creates a following relation with reblogs' do
+        expect(sender.following?(bob)).to be true
+        expect(sender.muting_reblogs?(bob)).to be false
+      end
+    end
+
+    describe 'unlocked account, no reblogs' do
+      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+
+      before do
+        subject.call(sender, bob.acct, reblogs: false)
+      end
+
+      it 'creates a following relation without reblogs' do
         expect(sender.following?(bob)).to be true
+        expect(sender.muting_reblogs?(bob)).to be true
       end
     end
 
@@ -42,6 +68,32 @@ RSpec.describe FollowService do
         expect(sender.following?(bob)).to be true
       end
     end
+
+    describe 'already followed account, turning reblogs off' do
+      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+
+      before do
+        sender.follow!(bob, reblogs: true)
+        subject.call(sender, bob.acct, reblogs: false)
+      end
+
+      it 'disables reblogs' do
+        expect(sender.muting_reblogs?(bob)).to be true
+      end
+    end
+
+    describe 'already followed account, turning reblogs on' do
+      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+
+      before do
+        sender.follow!(bob, reblogs: false)
+        subject.call(sender, bob.acct, reblogs: true)
+      end
+
+      it 'disables reblogs' do
+        expect(sender.muting_reblogs?(bob)).to be false
+      end
+    end
   end
 
   context 'remote OStatus account' do
diff --git a/spec/services/mute_service_spec.rb b/spec/services/mute_service_spec.rb
index 8097cb250..800140b6f 100644
--- a/spec/services/mute_service_spec.rb
+++ b/spec/services/mute_service_spec.rb
@@ -32,4 +32,36 @@ RSpec.describe MuteService do
       account.muting?(target_account)
     }.from(false).to(true)
   end
+
+  context 'without specifying a notifications parameter' do
+    it 'mutes notifications from the account' do
+      is_expected.to change {
+        account.muting_notifications?(target_account)
+      }.from(false).to(true)
+    end
+  end
+
+  context 'with a true notifications parameter' do
+    subject do
+      -> { described_class.new.call(account, target_account, notifications: true) }
+    end
+
+    it 'mutes notifications from the account' do
+      is_expected.to change {
+        account.muting_notifications?(target_account)
+      }.from(false).to(true)
+    end
+  end
+
+  context 'with a false notifications parameter' do
+    subject do
+      -> { described_class.new.call(account, target_account, notifications: false) }
+    end
+
+    it 'does not mute notifications from the account' do
+      is_expected.to_not change {
+        account.muting_notifications?(target_account)
+      }.from(false)
+    end
+  end
 end
diff --git a/spec/services/notify_service_spec.rb b/spec/services/notify_service_spec.rb
index 7a66bd0fe..a8ebc16b8 100644
--- a/spec/services/notify_service_spec.rb
+++ b/spec/services/notify_service_spec.rb
@@ -17,6 +17,16 @@ RSpec.describe NotifyService do
     is_expected.to_not change(Notification, :count)
   end
 
+  it 'does not notify when sender is muted with hide_notifications' do
+    recipient.mute!(sender, notifications: true)
+    is_expected.to_not change(Notification, :count)
+  end
+
+  it 'does notify when sender is muted without hide_notifications' do
+    recipient.mute!(sender, notifications: false)
+    is_expected.to change(Notification, :count)
+  end
+
   it 'does not notify when sender\'s domain is blocked' do
     recipient.block_domain!(sender.domain)
     is_expected.to_not change(Notification, :count)
@@ -38,6 +48,59 @@ RSpec.describe NotifyService do
     is_expected.to_not change(Notification, :count)
   end
 
+  describe 'reblogs' do
+    let(:status)   { Fabricate(:status, account: Fabricate(:account)) }
+    let(:activity) { Fabricate(:status, account: sender, reblog: status) }
+
+    it 'shows reblogs by default' do
+      recipient.follow!(sender)
+      is_expected.to change(Notification, :count)
+    end
+
+    it 'shows reblogs when explicitly enabled' do
+      recipient.follow!(sender, reblogs: true)
+      is_expected.to change(Notification, :count)
+    end
+
+    it 'hides reblogs when disabled' do
+      recipient.follow!(sender, reblogs: false)
+      is_expected.to_not change(Notification, :count)
+    end
+  end
+  
+  context 'for direct messages' do
+    let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct)) }
+
+    before do
+      user.settings.interactions = user.settings.interactions.merge('must_be_following_dm' => enabled)
+    end
+
+    context 'if recipient is supposed to be following sender' do
+      let(:enabled) { true }
+
+      it 'does not notify' do
+        is_expected.to_not change(Notification, :count)
+      end
+
+      context 'if the message chain initiated by recipient' do
+        let(:reply_to) { Fabricate(:status, account: recipient) }
+        let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: reply_to)) }
+
+        it 'does notify' do
+          is_expected.to change(Notification, :count)
+        end
+      end
+    end
+
+    context 'if recipient is NOT supposed to be following sender' do
+      let(:enabled) { false }
+
+      it 'does notify' do
+        is_expected.to change(Notification, :count)
+      end
+    end
+  end
+
   context do
     let(:asshole)  { Fabricate(:account, username: 'asshole') }
     let(:reply_to) { Fabricate(:status, account: asshole) }
diff --git a/spec/support/matchers/model/model_have_error_on_field.rb b/spec/support/matchers/model/model_have_error_on_field.rb
index 5d5fe1c7b..a5dfbf457 100644
--- a/spec/support/matchers/model/model_have_error_on_field.rb
+++ b/spec/support/matchers/model/model_have_error_on_field.rb
@@ -9,7 +9,7 @@ RSpec::Matchers.define :model_have_error_on_field do |expected|
 
   failure_message do |record|
     keys = record.errors.keys
-    
+
     "expect record.errors(#{keys}) to include #{expected}"
   end
 end
diff --git a/spec/validators/status_length_validator_spec.rb b/spec/validators/status_length_validator_spec.rb
index e2d1a15ec..9355c7e3f 100644
--- a/spec/validators/status_length_validator_spec.rb
+++ b/spec/validators/status_length_validator_spec.rb
@@ -7,26 +7,31 @@ describe StatusLengthValidator do
     it 'does not add errors onto remote statuses'
     it 'does not add errors onto local reblogs'
 
-    it 'adds an error when content warning is over 500 characters' do
-      status = double(spoiler_text: 'a' * 520, text: '', errors: double(add: nil), local?: true, reblog?: false)
+    it 'adds an error when content warning is over MAX_CHARS characters' do
+      chars = StatusLengthValidator::MAX_CHARS + 1
+      status = double(spoiler_text: 'a' * chars, text: '', errors: double(add: nil), local?: true, reblog?: false)
       subject.validate(status)
       expect(status.errors).to have_received(:add)
     end
 
-    it 'adds an error when text is over 500 characters' do
-      status = double(spoiler_text: '', text: 'a' * 520, errors: double(add: nil), local?: true, reblog?: false)
+    it 'adds an error when text is over MAX_CHARS characters' do
+      chars = StatusLengthValidator::MAX_CHARS + 1
+      status = double(spoiler_text: '', text: 'a' * chars, errors: double(add: nil), local?: true, reblog?: false)
       subject.validate(status)
       expect(status.errors).to have_received(:add)
     end
 
-    it 'adds an error when text and content warning are over 500 characters total' do
-      status = double(spoiler_text: 'a' * 250, text: 'b' * 251, errors: double(add: nil), local?: true, reblog?: false)
+    it 'adds an error when text and content warning are over MAX_CHARS characters total' do
+      chars1 = 20
+      chars2 = StatusLengthValidator::MAX_CHARS + 1 - chars1
+      status = double(spoiler_text: 'a' * chars1, text: 'b' * chars2, errors: double(add: nil), local?: true, reblog?: false)
       subject.validate(status)
       expect(status.errors).to have_received(:add)
     end
 
     it 'counts URLs as 23 characters flat' do
-      text   = ('a' * 476) + " http://#{'b' * 30}.com/example"
+      chars = StatusLengthValidator::MAX_CHARS - 1 - 23
+      text   = ('a' * chars) + " http://#{'b' * 30}.com/example"
       status = double(spoiler_text: '', text: text, errors: double(add: nil), local?: true, reblog?: false)
 
       subject.validate(status)
@@ -34,7 +39,9 @@ describe StatusLengthValidator do
     end
 
     it 'counts only the front part of remote usernames' do
-      text   = ('a' * 475) + " @alice@#{'b' * 30}.com"
+      username = '@alice'
+      chars = StatusLengthValidator::MAX_CHARS - 1 - username.length
+      text   = ('a' * 475) + " #{username}@#{'b' * 30}.com"
       status = double(spoiler_text: '', text: text, errors: double(add: nil), local?: true, reblog?: false)
 
       subject.validate(status)
diff --git a/spec/views/about/show.html.haml_spec.rb b/spec/views/about/show.html.haml_spec.rb
index 724643cbc..ca59fa9e3 100644
--- a/spec/views/about/show.html.haml_spec.rb
+++ b/spec/views/about/show.html.haml_spec.rb
@@ -3,6 +3,8 @@
 require 'rails_helper'
 
 describe 'about/show.html.haml', without_verify_partial_doubles: true do
+  let(:commit_hash) { '8925731c9869f55780644304e4420a1998e52607' }
+
   before do
     allow(view).to receive(:site_hostname).and_return('example.com')
     allow(view).to receive(:site_title).and_return('example site')
@@ -16,7 +18,9 @@ describe 'about/show.html.haml', without_verify_partial_doubles: true do
                                 source_url: 'https://github.com/tootsuite/mastodon',
                                 open_registrations: false,
                                 thumbnail: nil,
-                                closed_registrations_message: 'yes')
+                                closed_registrations_message: 'yes',
+                                commit_hash: commit_hash)
+
     assign(:instance_presenter, instance_presenter)
     render