From 15c0f6ae56e31ced750fa0ae06bf39d72355a477 Mon Sep 17 00:00:00 2001 From: ysksn Date: Sun, 5 Nov 2017 17:20:05 +0900 Subject: Implement tests for Account#possibly_stale? (#5591) --- spec/models/account_spec.rb | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'spec/models') diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index aef0c3082..2144bffee 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -123,6 +123,34 @@ RSpec.describe Account, type: :model do end end + describe '#possibly_stale?' do + let(:account) { Fabricate(:account, last_webfingered_at: last_webfingered_at) } + + context 'last_webfingered_at is nil' do + let(:last_webfingered_at) { nil } + + it 'returns true' do + expect(account.possibly_stale?).to be true + end + end + + context 'last_webfingered_at is more than 24 hours before' do + let(:last_webfingered_at) { 25.hours.ago } + + it 'returns true' do + expect(account.possibly_stale?).to be true + end + end + + context 'last_webfingered_at is less than 24 hours before' do + let(:last_webfingered_at) { 23.hours.ago } + + it 'returns false' do + expect(account.possibly_stale?).to be false + end + end + end + describe '#to_param' do it 'returns username' do account = Fabricate(:account, username: 'alice') -- cgit From cf01326cc130bc6255aab5ce1e961ff8810758ae Mon Sep 17 00:00:00 2001 From: ysksn Date: Mon, 6 Nov 2017 13:54:12 +0900 Subject: Add test for Account#save_with_optional_media! (#5603) There was a test when some of the properties are invalid, but none when all of them are valid. --- spec/models/account_spec.rb | 49 +++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 13 deletions(-) (limited to 'spec/models') diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 2144bffee..5165082d1 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -93,21 +93,44 @@ RSpec.describe Account, type: :model do end describe '#save_with_optional_media!' do - it 'sets default avatar, header, avatar_remote_url, and header_remote_url if some of them are invalid' do + before do stub_request(:get, 'https://remote/valid_avatar').to_return(request_fixture('avatar.txt')) stub_request(:get, 'https://remote/invalid_avatar').to_return(request_fixture('feed.txt')) - account = Fabricate(:account, - avatar_remote_url: 'https://remote/valid_avatar', - header_remote_url: 'https://remote/valid_avatar') - - account.avatar_remote_url = 'https://remote/invalid_avatar' - account.save_with_optional_media! - - account.reload - expect(account.avatar_remote_url).to eq '' - expect(account.header_remote_url).to eq '' - expect(account.avatar_file_name).to eq nil - expect(account.header_file_name).to eq nil + end + + let(:account) do + Fabricate(:account, + avatar_remote_url: 'https://remote/valid_avatar', + header_remote_url: 'https://remote/valid_avatar') + end + + let!(:expectation) { account.dup } + + context 'with valid properties' do + before do + account.save_with_optional_media! + end + + it 'unchanges avatar, header, avatar_remote_url, and header_remote_url' do + expect(account.avatar_remote_url).to eq expectation.avatar_remote_url + expect(account.header_remote_url).to eq expectation.header_remote_url + expect(account.avatar_file_name).to eq expectation.avatar_file_name + expect(account.header_file_name).to eq expectation.header_file_name + end + end + + context 'with invalid properties' do + before do + account.avatar_remote_url = 'https://remote/invalid_avatar' + account.save_with_optional_media! + end + + it 'sets default avatar, header, avatar_remote_url, and header_remote_url' do + expect(account.avatar_remote_url).to eq '' + expect(account.header_remote_url).to eq '' + expect(account.avatar_file_name).to eq nil + expect(account.header_file_name).to eq nil + end end end -- cgit From d307ee79e9b2f783fbdc6fd2e174ec91c47e8e5e Mon Sep 17 00:00:00 2001 From: ysksn Date: Mon, 6 Nov 2017 13:54:41 +0900 Subject: Implement tests for Account#refresh! (#5601) --- spec/models/account_spec.rb | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'spec/models') diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 5165082d1..9c1492c90 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -174,6 +174,33 @@ RSpec.describe Account, type: :model do end end + describe '#refresh!' do + let(:account) { Fabricate(:account, domain: domain) } + let(:acct) { account.acct } + + context 'domain is nil' do + let(:domain) { nil } + + it 'returns nil' do + expect(account.refresh!).to be_nil + end + + it 'calls not ResolveRemoteAccountService#call' do + expect_any_instance_of(ResolveRemoteAccountService).not_to receive(:call).with(acct) + account.refresh! + end + end + + context 'domain is present' do + let(:domain) { 'example.com' } + + it 'calls ResolveRemoteAccountService#call' do + expect_any_instance_of(ResolveRemoteAccountService).to receive(:call).with(acct).once + account.refresh! + end + end + end + describe '#to_param' do it 'returns username' do account = Fabricate(:account, username: 'alice') -- cgit From 97fc2da2e044980de01ed88eb7f0836fc718fb75 Mon Sep 17 00:00:00 2001 From: ysksn Date: Wed, 8 Nov 2017 15:28:17 +0900 Subject: Add tests for CustomEmoji#local? and #object_type (#5621) --- spec/models/custom_emoji_spec.rb | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'spec/models') diff --git a/spec/models/custom_emoji_spec.rb b/spec/models/custom_emoji_spec.rb index cb51e9519..bb150b837 100644 --- a/spec/models/custom_emoji_spec.rb +++ b/spec/models/custom_emoji_spec.rb @@ -1,6 +1,35 @@ require 'rails_helper' RSpec.describe CustomEmoji, type: :model do + describe '#local?' do + let(:custom_emoji) { Fabricate(:custom_emoji, domain: domain) } + + subject { custom_emoji.local? } + + context 'domain is nil' do + let(:domain) { nil } + + it 'returns true' do + is_expected.to be true + end + end + + context 'domain is present' do + let(:domain) { 'example.com' } + + it 'returns false' do + is_expected.to be false + end + end + end + + describe '#object_type' do + it 'returns :emoji' do + custom_emoji = Fabricate(:custom_emoji) + expect(custom_emoji.object_type).to be :emoji + end + end + describe '.from_text' do let!(:emojo) { Fabricate(:custom_emoji) } -- cgit From 64cc129225566deb605738cec5f787dcd7dca5fc Mon Sep 17 00:00:00 2001 From: ysksn Date: Wed, 8 Nov 2017 15:29:07 +0900 Subject: Add tests for MediaAttachment (#5620) - `#local?` - `#needs_redownload?` - `#to_param` --- spec/models/media_attachment_spec.rb | 77 ++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) (limited to 'spec/models') diff --git a/spec/models/media_attachment_spec.rb b/spec/models/media_attachment_spec.rb index 435b4f326..b40a641f7 100644 --- a/spec/models/media_attachment_spec.rb +++ b/spec/models/media_attachment_spec.rb @@ -1,6 +1,83 @@ require 'rails_helper' RSpec.describe MediaAttachment, type: :model do + describe 'local?' do + let(:media_attachment) { Fabricate(:media_attachment, remote_url: remote_url) } + + subject { media_attachment.local? } + + context 'remote_url is blank' do + let(:remote_url) { '' } + + it 'returns true' do + is_expected.to be true + end + end + + context 'remote_url is present' do + let(:remote_url) { 'remote_url' } + + it 'returns false' do + is_expected.to be false + end + end + end + + describe 'needs_redownload?' do + let(:media_attachment) { Fabricate(:media_attachment, remote_url: remote_url, file: file) } + + subject { media_attachment.needs_redownload? } + + context 'file is blank' do + let(:file) { nil } + + context 'remote_url is blank' do + let(:remote_url) { '' } + + it 'returns false' do + is_expected.to be false + end + end + + context 'remote_url is present' do + let(:remote_url) { 'remote_url' } + + it 'returns true' do + is_expected.to be true + end + end + end + + context 'file is present' do + let(:file) { attachment_fixture('avatar.gif') } + + context 'remote_url is blank' do + let(:remote_url) { '' } + + it 'returns false' do + is_expected.to be false + end + end + + context 'remote_url is present' do + let(:remote_url) { 'remote_url' } + + it 'returns true' do + is_expected.to be false + end + end + end + end + + describe '#to_param' do + let(:media_attachment) { Fabricate(:media_attachment) } + let(:shortcode) { media_attachment.shortcode } + + it 'returns shortcode' do + expect(media_attachment.to_param).to eq shortcode + end + end + describe 'animated gif conversion' do let(:media) { MediaAttachment.create(account: Fabricate(:account), file: attachment_fixture('avatar.gif')) } -- cgit From 54b42901df938257ae660168958fffe58b1a0287 Mon Sep 17 00:00:00 2001 From: ysksn Date: Thu, 9 Nov 2017 22:36:52 +0900 Subject: Add and Remove tests for FollowRequest (#5622) * Add a test for FollowRequest#authorize! * Remove tests There is no need to test ActiveModel::Validations::ClassMethods#validates. * Make an alias of destroy! as reject! Instead of defining the method, make an alias of destroy! as reject! because of reducing test. --- app/models/follow_request.rb | 4 +--- spec/models/follow_request_spec.rb | 27 +++++++++------------------ 2 files changed, 10 insertions(+), 21 deletions(-) (limited to 'spec/models') diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb index 60036d903..458bcd28a 100644 --- a/app/models/follow_request.rb +++ b/app/models/follow_request.rb @@ -27,7 +27,5 @@ class FollowRequest < ApplicationRecord destroy! end - def reject! - destroy! - end + alias reject! destroy! end diff --git a/spec/models/follow_request_spec.rb b/spec/models/follow_request_spec.rb index cc6f8ee62..1436501e9 100644 --- a/spec/models/follow_request_spec.rb +++ b/spec/models/follow_request_spec.rb @@ -1,25 +1,16 @@ require 'rails_helper' RSpec.describe FollowRequest, type: :model do - describe '#authorize!' - describe '#reject!' + describe '#authorize!' do + let(:follow_request) { Fabricate(:follow_request, account: account, target_account: target_account) } + let(:account) { Fabricate(:account) } + let(:target_account) { Fabricate(:account) } - describe 'validations' do - it 'has a valid fabricator' do - follow_request = Fabricate.build(:follow_request) - expect(follow_request).to be_valid - end - - it 'is invalid without an account' do - follow_request = Fabricate.build(:follow_request, account: nil) - follow_request.valid? - expect(follow_request).to model_have_error_on_field(:account) - end - - it 'is invalid without a target account' do - follow_request = Fabricate.build(:follow_request, target_account: nil) - follow_request.valid? - expect(follow_request).to model_have_error_on_field(:target_account) + it 'calls Account#follow!, MergeWorker.perform_async, and #destroy!' do + expect(account).to receive(:follow!).with(target_account) + expect(MergeWorker).to receive(:perform_async).with(target_account.id, account.id) + expect(follow_request).to receive(:destroy!) + follow_request.authorize! end end end -- cgit From 07cca6e364c0b8acf7c1bc4429113cab035d6f30 Mon Sep 17 00:00:00 2001 From: ysksn Date: Thu, 9 Nov 2017 22:37:10 +0900 Subject: Add tests for Notification (#5640) * Add tests for Notification#target_status * Add tests for Notification#browserable? * Add tests for Notification.reload_stale_associations! --- spec/models/notification_spec.rb | 113 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) (limited to 'spec/models') diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb index 97e8095cd..763b1523f 100644 --- a/spec/models/notification_spec.rb +++ b/spec/models/notification_spec.rb @@ -5,6 +5,74 @@ RSpec.describe Notification, type: :model do pending end + describe '#target_status' do + before do + allow(notification).to receive(:type).and_return(type) + allow(notification).to receive(:activity).and_return(activity) + end + + let(:notification) { Fabricate(:notification) } + let(:status) { instance_double('Status') } + let(:favourite) { instance_double('Favourite') } + let(:mention) { instance_double('Mention') } + + context 'type is :reblog' do + let(:type) { :reblog } + let(:activity) { status } + + it 'calls activity.reblog' do + expect(activity).to receive(:reblog) + notification.target_status + end + end + + context 'type is :favourite' do + let(:type) { :favourite } + let(:activity) { favourite } + + it 'calls activity.status' do + expect(activity).to receive(:status) + notification.target_status + end + end + + context 'type is :mention' do + let(:type) { :mention } + let(:activity) { mention } + + it 'calls activity.status' do + expect(activity).to receive(:status) + notification.target_status + end + end + end + + describe '#browserable?' do + let(:notification) { Fabricate(:notification) } + + subject { notification.browserable? } + + context 'type is :follow_request' do + before do + allow(notification).to receive(:type).and_return(:follow_request) + end + + it 'returns false' do + is_expected.to be false + end + end + + context 'type is not :follow_request' do + before do + allow(notification).to receive(:type).and_return(:else) + end + + it 'returns true' do + is_expected.to be true + end + end + end + describe '#type' do it 'returns :reblog for a Status' do notification = Notification.new(activity: Status.new) @@ -26,4 +94,49 @@ RSpec.describe Notification, type: :model do expect(notification.type).to eq :follow end end + + describe '.reload_stale_associations!' do + context 'account_ids are empty' do + let(:cached_items) { [] } + + subject { described_class.reload_stale_associations!(cached_items) } + + it 'returns nil' do + is_expected.to be nil + end + end + + context 'account_ids are present' do + before do + allow(accounts_with_ids).to receive(:[]).with(stale_account1.id).and_return(account1) + allow(accounts_with_ids).to receive(:[]).with(stale_account2.id).and_return(account2) + allow(Account).to receive_message_chain(:where, :map, :to_h).and_return(accounts_with_ids) + end + + let(:cached_items) do + [ + Fabricate(:notification, activity: Fabricate(:status)), + Fabricate(:notification, activity: Fabricate(:follow)), + ] + end + + let(:stale_account1) { cached_items[0].from_account } + let(:stale_account2) { cached_items[1].from_account } + + let(:account1) { Fabricate(:account) } + let(:account2) { Fabricate(:account) } + + let(:accounts_with_ids) { { account1.id => account1, account2.id => account2 } } + + it 'reloads associations' do + expect(cached_items[0].from_account).to be stale_account1 + expect(cached_items[1].from_account).to be stale_account2 + + described_class.reload_stale_associations!(cached_items) + + expect(cached_items[0].from_account).to be account1 + expect(cached_items[1].from_account).to be account2 + end + end + end end -- cgit From 56720ba590bf988a5feba22fc6c8ace473ff30a4 Mon Sep 17 00:00:00 2001 From: ysksn Date: Sat, 11 Nov 2017 00:56:02 +0900 Subject: Add tests for RemoteFollow (#5651) * Add tests for RemoteFollow.initialize * Add tests for RemoteFollow#valid? * Add tests for RemoteFollow#subscribe_address_for --- spec/models/remote_follow_spec.rb | 85 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 spec/models/remote_follow_spec.rb (limited to 'spec/models') diff --git a/spec/models/remote_follow_spec.rb b/spec/models/remote_follow_spec.rb new file mode 100644 index 000000000..0b3adc9f9 --- /dev/null +++ b/spec/models/remote_follow_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe RemoteFollow do + describe '.initialize' do + let(:remote_follow) { RemoteFollow.new(option) } + + context 'option with acct' do + let(:option) { { acct: 'hoge@example.com' } } + + it 'sets acct' do + expect(remote_follow.acct).to eq 'hoge@example.com' + end + end + + context 'option without acct' do + let(:option) { {} } + + it 'does not set acct' do + expect(remote_follow.acct).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 + + before do + class RemoteFollow + include InvalidSuper + end + end + + 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 + end + end + + context 'super is truthy' do + module ValidSuper + def valid? + true + end + end + + 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? + end + end + end + + describe '#subscribe_address_for' do + before do + allow(remote_follow).to receive(:addressable_template).and_return(addressable_template) + 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 } + + 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) + end + end +end -- cgit From 2fb722397d05ebec3ccae359d72100cf17ec766e Mon Sep 17 00:00:00 2001 From: ysksn Date: Sun, 12 Nov 2017 16:23:31 +0900 Subject: Add tests for RemoteProfile (#5665) --- spec/models/remote_profile_spec.rb | 143 +++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 spec/models/remote_profile_spec.rb (limited to 'spec/models') diff --git a/spec/models/remote_profile_spec.rb b/spec/models/remote_profile_spec.rb new file mode 100644 index 000000000..da5048f0a --- /dev/null +++ b/spec/models/remote_profile_spec.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe RemoteProfile do + let(:remote_profile) { RemoteProfile.new(body) } + let(:body) do + <<-XML + + John + XML + end + + describe '.initialize' do + it 'calls Nokogiri::XML.parse' do + expect(Nokogiri::XML).to receive(:parse).with(body, nil, 'utf-8') + RemoteProfile.new(body) + end + + it 'sets document' do + remote_profile = RemoteProfile.new(body) + expect(remote_profile).not_to be nil + end + end + + describe '#root' do + let(:document) { remote_profile.document } + + it 'callse document.at_xpath' do + expect(document).to receive(:at_xpath).with( + '/atom:feed|/atom:entry', + atom: OStatus::TagManager::XMLNS + ) + + remote_profile.root + end + end + + describe '#author' do + let(:root) { remote_profile.root } + + it 'calls root.at_xpath' do + expect(root).to receive(:at_xpath).with( + './atom:author|./dfrn:owner', + atom: OStatus::TagManager::XMLNS, + dfrn: OStatus::TagManager::DFRN_XMLNS + ) + + remote_profile.author + end + end + + describe '#hub_link' do + let(:root) { remote_profile.root } + + it 'calls #link_href_from_xml' do + expect(remote_profile).to receive(:link_href_from_xml).with(root, 'hub') + remote_profile.hub_link + end + end + + describe '#display_name' do + let(:author) { remote_profile.author } + + it 'calls author.at_xpath.content' do + expect(author).to receive_message_chain(:at_xpath, :content).with( + './poco:displayName', + poco: OStatus::TagManager::POCO_XMLNS + ).with(no_args) + + remote_profile.display_name + end + end + + describe '#note' do + let(:author) { remote_profile.author } + + it 'calls author.at_xpath.content' do + expect(author).to receive_message_chain(:at_xpath, :content).with( + './atom:summary|./poco:note', + atom: OStatus::TagManager::XMLNS, + poco: OStatus::TagManager::POCO_XMLNS + ).with(no_args) + + remote_profile.note + end + end + + describe '#scope' do + let(:author) { remote_profile.author } + + it 'calls author.at_xpath.content' do + expect(author).to receive_message_chain(:at_xpath, :content).with( + './mastodon:scope', + mastodon: OStatus::TagManager::MTDN_XMLNS + ).with(no_args) + + remote_profile.scope + end + end + + describe '#avatar' do + let(:author) { remote_profile.author } + + it 'calls #link_href_from_xml' do + expect(remote_profile).to receive(:link_href_from_xml).with(author, 'avatar') + remote_profile.avatar + end + end + + describe '#header' do + let(:author) { remote_profile.author } + + it 'calls #link_href_from_xml' do + expect(remote_profile).to receive(:link_href_from_xml).with(author, 'header') + remote_profile.header + end + end + + describe '#locked?' do + before do + allow(remote_profile).to receive(:scope).and_return(scope) + end + + subject { remote_profile.locked? } + + context 'scope is private' do + let(:scope) { 'private' } + + it 'returns true' do + is_expected.to be true + end + end + + context 'scope is not private' do + let(:scope) { 'public' } + + it 'returns false' do + is_expected.to be false + end + end + end +end -- cgit From 60f247c2e795f9611323f5595e8851886cff723d Mon Sep 17 00:00:00 2001 From: ysksn Date: Mon, 13 Nov 2017 09:54:48 +0900 Subject: Add tests for SessionActivation (#5668) * Fabricate SessionActivation not only user_id but user association. * Add tests for SessionActivation --- spec/fabricators/session_activation_fabricator.rb | 2 +- spec/models/session_activation_spec.rb | 124 +++++++++++++++++++++- 2 files changed, 124 insertions(+), 2 deletions(-) (limited to 'spec/models') diff --git a/spec/fabricators/session_activation_fabricator.rb b/spec/fabricators/session_activation_fabricator.rb index 46050bdab..526faaec2 100644 --- a/spec/fabricators/session_activation_fabricator.rb +++ b/spec/fabricators/session_activation_fabricator.rb @@ -1,4 +1,4 @@ Fabricator(:session_activation) do - user_id 1 + user session_id "MyString" end diff --git a/spec/models/session_activation_spec.rb b/spec/models/session_activation_spec.rb index 49c72fbd4..2aa695037 100644 --- a/spec/models/session_activation_spec.rb +++ b/spec/models/session_activation_spec.rb @@ -1,5 +1,127 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe SessionActivation, type: :model do - pending "add some examples to (or delete) #{__FILE__}" + describe '#detection' do + let(:session_activation) { Fabricate(:session_activation, user_agent: 'Chrome/62.0.3202.89') } + + it 'sets a Browser instance as detection' do + expect(session_activation.detection).to be_kind_of Browser::Chrome + end + end + + describe '#browser' do + before do + allow(session_activation).to receive(:detection).and_return(detection) + end + + let(:detection) { double(id: 1) } + let(:session_activation) { Fabricate(:session_activation) } + + it 'returns detection.id' do + expect(session_activation.browser).to be 1 + end + end + + describe '#platform' do + before do + allow(session_activation).to receive(:detection).and_return(detection) + end + + let(:session_activation) { Fabricate(:session_activation) } + let(:detection) { double(platform: double(id: 1)) } + + it 'returns detection.platform.id' do + expect(session_activation.platform).to be 1 + end + end + + describe '.active?' do + subject { described_class.active?(id) } + + context 'id is absent' do + let(:id) { nil } + + it 'returns nil' do + is_expected.to be nil + end + end + + context 'id is present' do + let(:id) { '1' } + let!(:session_activation) { Fabricate(:session_activation, session_id: id) } + + context 'id exists as session_id' do + it 'returns true' do + is_expected.to be true + end + end + + context 'id does not exist as session_id' do + before do + session_activation.update!(session_id: '2') + end + + it 'returns false' do + is_expected.to be false + end + end + end + end + + describe '.activate' do + let(:options) { { user: Fabricate(:user), session_id: '1' } } + + it 'calls create! and purge_old' do + expect(described_class).to receive(:create!).with(options) + expect(described_class).to receive(:purge_old) + described_class.activate(options) + end + + it 'returns an instance of SessionActivation' do + expect(described_class.activate(options)).to be_kind_of SessionActivation + end + end + + describe '.deactivate' do + context 'id is absent' do + let(:id) { nil } + + it 'returns nil' do + expect(described_class.deactivate(id)).to be nil + end + end + + context 'id exists' do + let(:id) { '1' } + + it 'calls where.destroy_all' do + expect(described_class).to receive_message_chain(:where, :destroy_all) + .with(session_id: id).with(no_args) + + described_class.deactivate(id) + end + end + end + + describe '.purge_old' do + it 'calls order.offset.destroy_all' do + expect(described_class).to receive_message_chain(:order, :offset, :destroy_all) + .with('created_at desc').with(Rails.configuration.x.max_session_activations).with(no_args) + + described_class.purge_old + end + end + + describe '.exclusive' do + let(:id) { '1' } + + it 'calls where.destroy_all' do + expect(described_class).to receive_message_chain(:where, :destroy_all) + .with('session_id != ?', id).with(no_args) + + described_class.exclusive(id) + end + end end -- cgit From 4112a0631f96a7ebf3923a6c3a83039963c79f6e Mon Sep 17 00:00:00 2001 From: ysksn Date: Tue, 14 Nov 2017 11:08:04 +0900 Subject: Add tests for Setting (#5683) --- spec/fabricators/setting_fabricator.rb | 4 + spec/models/setting_spec.rb | 184 +++++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 spec/fabricators/setting_fabricator.rb create mode 100644 spec/models/setting_spec.rb (limited to 'spec/models') 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/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 -- cgit From 20150659e6f084d6c6fb4080d08c4104b8ac0570 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Wed, 15 Nov 2017 04:37:17 +0900 Subject: Add uniqueness to block email domains (#5692) --- app/models/email_domain_block.rb | 23 ++++++++++++++++++++-- ...0328_add_index_domain_to_email_domain_blocks.rb | 8 ++++++++ db/schema.rb | 5 +++-- spec/models/email_domain_block_spec.rb | 5 +++-- 4 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 db/migrate/20171114080328_add_index_domain_to_email_domain_blocks.rb (limited to 'spec/models') diff --git a/app/models/email_domain_block.rb b/app/models/email_domain_block.rb index 51410605b..2c348197c 100644 --- a/app/models/email_domain_block.rb +++ b/app/models/email_domain_block.rb @@ -4,14 +4,33 @@ # Table name: email_domain_blocks # # id :bigint not null, primary key -# domain :string not null +# domain :string default(""), not null # created_at :datetime not null # updated_at :datetime not null # class EmailDomainBlock < ApplicationRecord + before_validation :normalize_domain + + validates :domain, presence: true, uniqueness: true + def self.block?(email) - domain = email.gsub(/.+@([^.]+)/, '\1') + _, domain = email.split('@', 2) + + return true if domain.nil? + + begin + domain = TagManager.instance.normalize_domain(domain) + rescue Addressable::URI::InvalidURIError + return true + end + where(domain: domain).exists? end + + private + + def normalize_domain + self.domain = TagManager.instance.normalize_domain(domain) + end end diff --git a/db/migrate/20171114080328_add_index_domain_to_email_domain_blocks.rb b/db/migrate/20171114080328_add_index_domain_to_email_domain_blocks.rb new file mode 100644 index 000000000..84a341510 --- /dev/null +++ b/db/migrate/20171114080328_add_index_domain_to_email_domain_blocks.rb @@ -0,0 +1,8 @@ +class AddIndexDomainToEmailDomainBlocks < ActiveRecord::Migration[5.1] + disable_ddl_transaction! + + def change + add_index :email_domain_blocks, :domain, algorithm: :concurrently, unique: true + change_column_default :email_domain_blocks, :domain, from: nil, to: '' + end +end diff --git a/db/schema.rb b/db/schema.rb index f16b24fd6..bf319ce55 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171109012327) do +ActiveRecord::Schema.define(version: 20171114080328) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -126,9 +126,10 @@ ActiveRecord::Schema.define(version: 20171109012327) do end create_table "email_domain_blocks", force: :cascade do |t| - t.string "domain", null: false + t.string "domain", default: "", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.index ["domain"], name: "index_email_domain_blocks_on_domain", unique: true end create_table "favourites", force: :cascade do |t| 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 -- cgit From 1f1838420fdaf15f3d4a61b22f4a98b9793c54a6 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Wed, 15 Nov 2017 04:41:17 +0900 Subject: Refactor remote_follow_spec.rb (#5690) --- spec/models/remote_follow_spec.rb | 84 +++++++++++++++------------------------ 1 file changed, 33 insertions(+), 51 deletions(-) (limited to 'spec/models') diff --git a/spec/models/remote_follow_spec.rb b/spec/models/remote_follow_spec.rb index 0b3adc9f9..7a4597ee7 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 } + subject { remote_follow.valid? } - context 'super is falsy' do - module InvalidSuper - def valid? - nil - end - end - - before do - class RemoteFollow - include InvalidSuper - end - end - - 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 + context 'attrs with acct' do + let(:attrs) { { acct: 'gargron@quitter.no' }} + + 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 -- cgit From 48e27c47a71e93a501de77e1507f8b25dcdc6fb0 Mon Sep 17 00:00:00 2001 From: ysksn Date: Wed, 15 Nov 2017 04:44:11 +0900 Subject: Add a test for SiteUpload#cache_key (#5685) --- spec/models/site_upload_spec.rb | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'spec/models') 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 -- cgit From 6d7e05ec1f27d2592cf15519c39141de857f65de Mon Sep 17 00:00:00 2001 From: ysksn Date: Wed, 15 Nov 2017 10:00:58 +0900 Subject: Add tests for StreamEntry (#5687) * Add tests for StreamEntry - `#object_type` - `#verb` - `#mentions` * Fix to test results instead of implementations --- spec/models/stream_entry_spec.rb | 115 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) (limited to 'spec/models') 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 -- cgit From 031a5a8f922d422ff087ad2fca82e3557a1b29d9 Mon Sep 17 00:00:00 2001 From: Surinna Curtis Date: Tue, 14 Nov 2017 20:56:41 -0600 Subject: Optional notification muting (#5087) * Add a hide_notifications column to mutes * Add muting_notifications? and a notifications argument to mute! * block notifications in notify_service from hard muted accounts * Add specs for how mute! interacts with muting_notifications? * specs testing that hide_notifications in mutes actually hides notifications * Add support for muting notifications in MuteService * API support for muting notifications (and specs) * Less gross passing of notifications flag * Break out a separate mute modal with a hide-notifications checkbox. * Convert profile header mute to use mute modal * Satisfy eslint. * specs for MuteService notifications params * add trailing newlines to files for Pork :) * Put the label for the hide notifications checkbox in a label element. * Add a /api/v1/mutes/details route that just returns the array of mutes. * Define a serializer for /api/v1/mutes/details * Add more specs for the /api/v1/mutes/details endpoint * Expose whether a mute hides notifications in the api/v1/relationships endpoint * Show whether muted users' notifications are muted in account lists * Allow modifying the hide_notifications of a mute with the /api/v1/accounts/:id/mute endpoint * make the hide/unhide notifications buttons work * satisfy eslint * In probably dead code, replace a dispatch of muteAccount that was skipping the modal with launching the mute modal. * fix a missing import * add an explanatory comment to AccountInteractions * Refactor handling of default params for muting to make code cleaner * minor code style fixes oops * Fixed a typo that was breaking the account mute API endpoint * Apply white-space: nowrap to account relationships icons * Fix code style issues * Remove superfluous blank line * Rename /api/v1/mutes/details -> /api/v2/mutes * Don't serialize "account" in MuteSerializer Doing so is somewhat unnecessary since it's always the current user's account. * Fix wrong variable name in api/v2/mutes * Use Toggle in place of checkbox in the mute modal. * Make the Toggle in the mute modal look better * Code style changes in specs and removed an extra space * Code review suggestions from akihikodaki Also fixed a syntax error in tests for AccountInteractions. * Make AddHideNotificationsToMute Concurrent It's not clear how much this will benefit instances in practice, as the number of mutes tends to be pretty small, but this should prevent any blocking migrations nonetheless. * Fix up migration things * Remove /api/v2/mutes --- app/controllers/api/v1/accounts_controller.rb | 2 +- app/javascript/mastodon/actions/accounts.js | 4 +- app/javascript/mastodon/actions/mutes.js | 21 +++++ app/javascript/mastodon/components/account.js | 23 ++++- .../mastodon/containers/account_container.js | 7 +- .../mastodon/containers/status_container.js | 13 +-- .../containers/header_container.js | 9 +- .../mastodon/features/ui/components/modal_root.js | 2 + .../mastodon/features/ui/components/mute_modal.js | 105 +++++++++++++++++++++ .../mastodon/features/ui/util/async-components.js | 4 + app/javascript/mastodon/reducers/index.js | 2 + app/javascript/mastodon/reducers/mutes.js | 29 ++++++ app/javascript/styles/mastodon/components.scss | 20 +++- app/models/concerns/account_interactions.rb | 19 +++- app/models/mute.rb | 11 ++- app/services/mute_service.rb | 5 +- app/services/notify_service.rb | 2 +- ...0170716191202_add_hide_notifications_to_mute.rb | 15 +++ db/schema.rb | 1 + .../controllers/api/v1/accounts_controller_spec.rb | 29 ++++++ spec/controllers/api/v1/mutes_controller_spec.rb | 2 +- spec/models/concerns/account_interactions_spec.rb | 38 ++++++++ spec/services/mute_service_spec.rb | 32 +++++++ spec/services/notify_service_spec.rb | 10 ++ 24 files changed, 368 insertions(+), 37 deletions(-) create mode 100644 app/javascript/mastodon/features/ui/components/mute_modal.js create mode 100644 app/javascript/mastodon/reducers/mutes.js create mode 100644 db/migrate/20170716191202_add_hide_notifications_to_mute.rb create mode 100644 spec/models/concerns/account_interactions_spec.rb (limited to 'spec/models') diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index b3fc4e561..4676f60de 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -26,7 +26,7 @@ class Api::V1::AccountsController < Api::BaseController end def mute - MuteService.new.call(current_user.account, @account) + MuteService.new.call(current_user.account, @account, notifications: params[:notifications]) render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships end diff --git a/app/javascript/mastodon/actions/accounts.js b/app/javascript/mastodon/actions/accounts.js index 73d6baace..fbaebf786 100644 --- a/app/javascript/mastodon/actions/accounts.js +++ b/app/javascript/mastodon/actions/accounts.js @@ -241,11 +241,11 @@ export function unblockAccountFail(error) { }; -export function muteAccount(id) { +export function muteAccount(id, notifications) { return (dispatch, getState) => { dispatch(muteAccountRequest(id)); - api(getState).post(`/api/v1/accounts/${id}/mute`).then(response => { + api(getState).post(`/api/v1/accounts/${id}/mute`, { notifications }).then(response => { // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers dispatch(muteAccountSuccess(response.data, getState().get('statuses'))); }).catch(error => { diff --git a/app/javascript/mastodon/actions/mutes.js b/app/javascript/mastodon/actions/mutes.js index febda7219..3474250fe 100644 --- a/app/javascript/mastodon/actions/mutes.js +++ b/app/javascript/mastodon/actions/mutes.js @@ -1,5 +1,6 @@ import api, { getLinks } from '../api'; import { fetchRelationships } from './accounts'; +import { openModal } from '../../mastodon/actions/modal'; export const MUTES_FETCH_REQUEST = 'MUTES_FETCH_REQUEST'; export const MUTES_FETCH_SUCCESS = 'MUTES_FETCH_SUCCESS'; @@ -9,6 +10,9 @@ export const MUTES_EXPAND_REQUEST = 'MUTES_EXPAND_REQUEST'; export const MUTES_EXPAND_SUCCESS = 'MUTES_EXPAND_SUCCESS'; export const MUTES_EXPAND_FAIL = 'MUTES_EXPAND_FAIL'; +export const MUTES_INIT_MODAL = 'MUTES_INIT_MODAL'; +export const MUTES_TOGGLE_HIDE_NOTIFICATIONS = 'MUTES_TOGGLE_HIDE_NOTIFICATIONS'; + export function fetchMutes() { return (dispatch, getState) => { dispatch(fetchMutesRequest()); @@ -80,3 +84,20 @@ export function expandMutesFail(error) { error, }; }; + +export function initMuteModal(account) { + return dispatch => { + dispatch({ + type: MUTES_INIT_MODAL, + account, + }); + + dispatch(openModal('MUTE')); + }; +} + +export function toggleHideNotifications() { + return dispatch => { + dispatch({ type: MUTES_TOGGLE_HIDE_NOTIFICATIONS }); + }; +} \ No newline at end of file diff --git a/app/javascript/mastodon/components/account.js b/app/javascript/mastodon/components/account.js index 0e3007ce8..724b10980 100644 --- a/app/javascript/mastodon/components/account.js +++ b/app/javascript/mastodon/components/account.js @@ -15,6 +15,8 @@ const messages = defineMessages({ requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }, unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, + mute_notifications: { id: 'account.mute_notifications', defaultMessage: 'Mute notifications from @{name}' }, + unmute_notifications: { id: 'account.unmute_notifications', defaultMessage: 'Unmute notifications from @{name}' }, }); @injectIntl @@ -41,6 +43,14 @@ export default class Account extends ImmutablePureComponent { this.props.onMute(this.props.account); } + handleMuteNotifications = () => { + this.props.onMuteNotifications(this.props.account, true); + } + + handleUnmuteNotifications = () => { + this.props.onMuteNotifications(this.props.account, false); + } + render () { const { account, intl, hidden } = this.props; @@ -70,7 +80,18 @@ export default class Account extends ImmutablePureComponent { } else if (blocking) { buttons = ; } else if (muting) { - buttons = ; + let hidingNotificationsButton; + if (muting.get('notifications')) { + hidingNotificationsButton = ; + } else { + hidingNotificationsButton = ; + } + buttons = ( +
+ + {hidingNotificationsButton} +
+ ); } else { buttons = ; } diff --git a/app/javascript/mastodon/containers/account_container.js b/app/javascript/mastodon/containers/account_container.js index 344f6749d..5a5136dd1 100644 --- a/app/javascript/mastodon/containers/account_container.js +++ b/app/javascript/mastodon/containers/account_container.js @@ -12,6 +12,7 @@ import { unmuteAccount, } from '../actions/accounts'; import { openModal } from '../actions/modal'; +import { initMuteModal } from '../actions/mutes'; import { unfollowModal } from '../initial_state'; const messages = defineMessages({ @@ -58,10 +59,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ if (account.getIn(['relationship', 'muting'])) { dispatch(unmuteAccount(account.get('id'))); } else { - dispatch(muteAccount(account.get('id'))); + dispatch(initMuteModal(account)); } }, + + onMuteNotifications (account, notifications) { + dispatch(muteAccount(account.get('id'), notifications)); + }, }); export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Account)); diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js index 311ccae5b..b22540204 100644 --- a/app/javascript/mastodon/containers/status_container.js +++ b/app/javascript/mastodon/containers/status_container.js @@ -14,11 +14,9 @@ import { pin, unpin, } from '../actions/interactions'; -import { - blockAccount, - muteAccount, -} from '../actions/accounts'; +import { blockAccount } from '../actions/accounts'; import { muteStatus, unmuteStatus, deleteStatus } from '../actions/statuses'; +import { initMuteModal } from '../actions/mutes'; import { initReport } from '../actions/reports'; import { openModal } from '../actions/modal'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; @@ -28,7 +26,6 @@ const messages = defineMessages({ deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }, blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' }, - muteConfirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' }, }); const makeMapStateToProps = () => { @@ -120,11 +117,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }, onMute (account) { - dispatch(openModal('CONFIRM', { - message: @{account.get('acct')} }} />, - confirm: intl.formatMessage(messages.muteConfirm), - onConfirm: () => dispatch(muteAccount(account.get('id'))), - })); + dispatch(initMuteModal(account)); }, onMuteConversation (status) { diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.js b/app/javascript/mastodon/features/account_timeline/containers/header_container.js index 01e18928e..8e50ec405 100644 --- a/app/javascript/mastodon/features/account_timeline/containers/header_container.js +++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.js @@ -7,10 +7,10 @@ import { unfollowAccount, blockAccount, unblockAccount, - muteAccount, unmuteAccount, } from '../../../actions/accounts'; import { mentionCompose } from '../../../actions/compose'; +import { initMuteModal } from '../../../actions/mutes'; import { initReport } from '../../../actions/reports'; import { openModal } from '../../../actions/modal'; import { blockDomain, unblockDomain } from '../../../actions/domain_blocks'; @@ -20,7 +20,6 @@ import { unfollowModal } from '../../../initial_state'; const messages = defineMessages({ unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' }, blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' }, - muteConfirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' }, blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' }, }); @@ -76,11 +75,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ if (account.getIn(['relationship', 'muting'])) { dispatch(unmuteAccount(account.get('id'))); } else { - dispatch(openModal('CONFIRM', { - message: @{account.get('acct')} }} />, - confirm: intl.formatMessage(messages.muteConfirm), - onConfirm: () => dispatch(muteAccount(account.get('id'))), - })); + dispatch(initMuteModal(account)); } }, diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.js index f420f0abf..79d86370e 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.js +++ b/app/javascript/mastodon/features/ui/components/modal_root.js @@ -10,6 +10,7 @@ import BoostModal from './boost_modal'; import ConfirmationModal from './confirmation_modal'; import { OnboardingModal, + MuteModal, ReportModal, EmbedModal, } from '../../../features/ui/util/async-components'; @@ -20,6 +21,7 @@ const MODAL_COMPONENTS = { 'VIDEO': () => Promise.resolve({ default: VideoModal }), 'BOOST': () => Promise.resolve({ default: BoostModal }), 'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }), + 'MUTE': MuteModal, 'REPORT': ReportModal, 'ACTIONS': () => Promise.resolve({ default: ActionsModal }), 'EMBED': EmbedModal, diff --git a/app/javascript/mastodon/features/ui/components/mute_modal.js b/app/javascript/mastodon/features/ui/components/mute_modal.js new file mode 100644 index 000000000..73e48cf09 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/mute_modal.js @@ -0,0 +1,105 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { injectIntl, FormattedMessage } from 'react-intl'; +import Toggle from 'react-toggle'; +import Button from '../../../components/button'; +import { closeModal } from '../../../actions/modal'; +import { muteAccount } from '../../../actions/accounts'; +import { toggleHideNotifications } from '../../../actions/mutes'; + + +const mapStateToProps = state => { + return { + isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']), + account: state.getIn(['mutes', 'new', 'account']), + notifications: state.getIn(['mutes', 'new', 'notifications']), + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onConfirm(account, notifications) { + dispatch(muteAccount(account.get('id'), notifications)); + }, + + onClose() { + dispatch(closeModal()); + }, + + onToggleNotifications() { + dispatch(toggleHideNotifications()); + }, + }; +}; + +@connect(mapStateToProps, mapDispatchToProps) +@injectIntl +export default class MuteModal extends React.PureComponent { + + static propTypes = { + isSubmitting: PropTypes.bool.isRequired, + account: PropTypes.object.isRequired, + notifications: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, + onConfirm: PropTypes.func.isRequired, + onToggleNotifications: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + componentDidMount() { + this.button.focus(); + } + + handleClick = () => { + this.props.onClose(); + this.props.onConfirm(this.props.account, this.props.notifications); + } + + handleCancel = () => { + this.props.onClose(); + } + + setRef = (c) => { + this.button = c; + } + + toggleNotifications = () => { + this.props.onToggleNotifications(); + } + + render () { + const { account, notifications } = this.props; + + return ( +
+
+

+ @{account.get('acct')} }} + /> +

+
+ +
+
+ +
+ + +
+
+ ); + } + +} diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index 8f7b91d21..39663d5ca 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -86,6 +86,10 @@ export function OnboardingModal () { return import(/* webpackChunkName: "modals/onboarding_modal" */'../components/onboarding_modal'); } +export function MuteModal () { + return import(/* webpackChunkName: "modals/mute_modal" */'../components/mute_modal'); +} + export function ReportModal () { return import(/* webpackChunkName: "modals/report_modal" */'../components/report_modal'); } diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js index e65144871..17c870351 100644 --- a/app/javascript/mastodon/reducers/index.js +++ b/app/javascript/mastodon/reducers/index.js @@ -13,6 +13,7 @@ import settings from './settings'; import push_notifications from './push_notifications'; import status_lists from './status_lists'; import cards from './cards'; +import mutes from './mutes'; import reports from './reports'; import contexts from './contexts'; import compose from './compose'; @@ -37,6 +38,7 @@ const reducers = { settings, push_notifications, cards, + mutes, reports, contexts, compose, diff --git a/app/javascript/mastodon/reducers/mutes.js b/app/javascript/mastodon/reducers/mutes.js new file mode 100644 index 000000000..a96232dbd --- /dev/null +++ b/app/javascript/mastodon/reducers/mutes.js @@ -0,0 +1,29 @@ +import Immutable from 'immutable'; + +import { + MUTES_INIT_MODAL, + MUTES_TOGGLE_HIDE_NOTIFICATIONS, +} from '../actions/mutes'; + +const initialState = Immutable.Map({ + new: Immutable.Map({ + isSubmitting: false, + account: null, + notifications: true, + }), +}); + +export default function mutes(state = initialState, action) { + switch (action.type) { + case MUTES_INIT_MODAL: + return state.withMutations((state) => { + state.setIn(['new', 'isSubmitting'], false); + state.setIn(['new', 'account'], action.account); + state.setIn(['new', 'notifications'], true); + }); + case MUTES_TOGGLE_HIDE_NOTIFICATIONS: + return state.updateIn(['new', 'notifications'], (old) => !old); + default: + return state; + } +} diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index e4504f543..0ded6f159 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -906,6 +906,7 @@ .account__relationship { height: 18px; padding: 10px; + white-space: nowrap; } .account__header { @@ -3515,7 +3516,8 @@ button.icon-button.active i.fa-retweet { .boost-modal, .confirmation-modal, .report-modal, -.actions-modal { +.actions-modal, +.mute-modal { background: lighten($ui-secondary-color, 8%); color: $ui-base-color; border-radius: 8px; @@ -3565,6 +3567,7 @@ button.icon-button.active i.fa-retweet { .boost-modal__action-bar, .confirmation-modal__action-bar, +.mute-modal__action-bar, .report-modal__action-bar { display: flex; justify-content: space-between; @@ -3601,6 +3604,14 @@ button.icon-button.active i.fa-retweet { } } +.mute-modal { + line-height: 24px; +} + +.mute-modal .react-toggle { + vertical-align: middle; +} + .report-modal__statuses, .report-modal__comment { padding: 10px; @@ -3673,8 +3684,10 @@ button.icon-button.active i.fa-retweet { } } -.confirmation-modal__action-bar { - .confirmation-modal__cancel-button { +.confirmation-modal__action-bar, +.mute-modal__action-bar { + .confirmation-modal__cancel-button, + .mute-modal__cancel-button { background-color: transparent; color: darken($ui-secondary-color, 34%); font-size: 14px; @@ -3689,6 +3702,7 @@ button.icon-button.active i.fa-retweet { } .confirmation-modal__container, +.mute-modal__container, .report-modal__target { padding: 30px; font-size: 16px; diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb index b26520f5b..55ad812b2 100644 --- a/app/models/concerns/account_interactions.rb +++ b/app/models/concerns/account_interactions.rb @@ -17,7 +17,11 @@ module AccountInteractions end def muting_map(target_account_ids, account_id) - follow_mapping(Mute.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id) + Mute.where(target_account_id: target_account_ids, account_id: account_id).each_with_object({}) do |mute, mapping| + mapping[mute.target_account_id] = { + notifications: mute.hide_notifications?, + } + end end def requested_map(target_account_ids, account_id) @@ -70,8 +74,13 @@ module AccountInteractions block_relationships.find_or_create_by!(target_account: other_account) end - def mute!(other_account) - mute_relationships.find_or_create_by!(target_account: other_account) + def mute!(other_account, notifications: nil) + notifications = true if notifications.nil? + mute = mute_relationships.create_with(hide_notifications: notifications).find_or_create_by!(target_account: other_account) + # When toggling a mute between hiding and allowing notifications, the mute will already exist, so the find_or_create_by! call will return the existing Mute without updating the hide_notifications attribute. Therefore, we check that hide_notifications? is what we want and set it if it isn't. + if mute.hide_notifications? != notifications + mute.update!(hide_notifications: notifications) + end end def mute_conversation!(conversation) @@ -127,6 +136,10 @@ module AccountInteractions conversation_mutes.where(conversation: conversation).exists? end + def muting_notifications?(other_account) + mute_relationships.where(target_account: other_account, hide_notifications: true).exists? + end + def requested?(other_account) follow_requests.where(target_account: other_account).exists? end diff --git a/app/models/mute.rb b/app/models/mute.rb index 4174a3523..105696da6 100644 --- a/app/models/mute.rb +++ b/app/models/mute.rb @@ -3,11 +3,12 @@ # # Table name: mutes # -# created_at :datetime not null -# updated_at :datetime not null -# account_id :bigint not null -# id :bigint not null, primary key -# target_account_id :bigint not null +# id :integer not null, primary key +# created_at :datetime not null +# updated_at :datetime not null +# account_id :integer not null +# target_account_id :integer not null +# hide_notifications :boolean default(TRUE), not null # class Mute < ApplicationRecord diff --git a/app/services/mute_service.rb b/app/services/mute_service.rb index 132369484..9b7cbd81f 100644 --- a/app/services/mute_service.rb +++ b/app/services/mute_service.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true class MuteService < BaseService - def call(account, target_account) + def call(account, target_account, notifications: nil) return if account.id == target_account.id - mute = account.mute!(target_account) + FeedManager.instance.clear_from_timeline(account, target_account) + mute = account.mute!(target_account, notifications: notifications) BlockWorker.perform_async(account.id, target_account.id) mute end diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index 6a24a8247..8a77f2f38 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -81,7 +81,7 @@ class NotifyService < BaseService blocked ||= from_self? # Skip for interactions with self blocked ||= domain_blocking? # Skip for domain blocked accounts blocked ||= @recipient.blocking?(@notification.from_account) # Skip for blocked accounts - blocked ||= @recipient.muting?(@notification.from_account) # Skip for muted accounts + blocked ||= @recipient.muting_notifications?(@notification.from_account) blocked ||= hellbanned? # Hellban blocked ||= optional_non_follower? # Options blocked ||= optional_non_following? # Options diff --git a/db/migrate/20170716191202_add_hide_notifications_to_mute.rb b/db/migrate/20170716191202_add_hide_notifications_to_mute.rb new file mode 100644 index 000000000..0410938c9 --- /dev/null +++ b/db/migrate/20170716191202_add_hide_notifications_to_mute.rb @@ -0,0 +1,15 @@ +require Rails.root.join('lib', 'mastodon', 'migration_helpers') + +class AddHideNotificationsToMute < ActiveRecord::Migration[5.1] + include Mastodon::MigrationHelpers + + disable_ddl_transaction! + + def up + add_column_with_default :mutes, :hide_notifications, :boolean, default: true, allow_null: false + end + + def down + remove_column :mutes, :hide_notifications + end +end diff --git a/db/schema.rb b/db/schema.rb index bf319ce55..2d763e2f4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -203,6 +203,7 @@ ActiveRecord::Schema.define(version: 20171114080328) do t.datetime "updated_at", null: false t.bigint "account_id", null: false t.bigint "target_account_id", null: false + t.boolean "hide_notifications", default: true, null: false t.index ["account_id", "target_account_id"], name: "index_mutes_on_account_id_and_target_account_id", unique: true end diff --git a/spec/controllers/api/v1/accounts_controller_spec.rb b/spec/controllers/api/v1/accounts_controller_spec.rb index c770649ec..053c53e5a 100644 --- a/spec/controllers/api/v1/accounts_controller_spec.rb +++ b/spec/controllers/api/v1/accounts_controller_spec.rb @@ -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..97d6c2773 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 diff --git a/spec/models/concerns/account_interactions_spec.rb b/spec/models/concerns/account_interactions_spec.rb new file mode 100644 index 000000000..a468549d8 --- /dev/null +++ b/spec/models/concerns/account_interactions_spec.rb @@ -0,0 +1,38 @@ +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 +end 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 58ee66ded..fad0dd369 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) -- cgit From 7d7df877ef042cda2095f35061d65a9051ad987b Mon Sep 17 00:00:00 2001 From: ysksn Date: Thu, 16 Nov 2017 00:04:41 +0900 Subject: Add a test for Tag#to_param (#5705) --- spec/models/tag_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'spec/models') 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") -- cgit From 19e8b861a2c97240e0ca9c47d13bb3d7c5cb7520 Mon Sep 17 00:00:00 2001 From: ysksn Date: Thu, 16 Nov 2017 00:05:20 +0900 Subject: Delegate some methods of User to @settings (#5706) * Move some tests of User into Settings::ScopedSettings * Add a test for User@settings --- app/models/user.rb | 40 ++++--------------------------- spec/lib/settings/scoped_settings_spec.rb | 35 +++++++++++++++++++++++++++ spec/models/user_spec.rb | 39 +++--------------------------- 3 files changed, 42 insertions(+), 72 deletions(-) create mode 100644 spec/lib/settings/scoped_settings_spec.rb (limited to 'spec/models') diff --git a/app/models/user.rb b/app/models/user.rb index ebe768c52..326b871a1 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -73,6 +73,10 @@ class User < ApplicationRecord has_many :session_activations, dependent: :destroy + delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :delete_modal, + :reduce_motion, :system_font_ui, :noindex, :theme, + to: :settings, prefix: :setting, allow_nil: false + def confirmed? confirmed_at.present? end @@ -136,42 +140,6 @@ class User < ApplicationRecord settings.default_privacy || (account.locked? ? 'private' : 'public') end - def setting_default_sensitive - settings.default_sensitive - end - - def setting_unfollow_modal - settings.unfollow_modal - end - - def setting_boost_modal - settings.boost_modal - end - - def setting_delete_modal - settings.delete_modal - end - - def setting_auto_play_gif - settings.auto_play_gif - end - - def setting_reduce_motion - settings.reduce_motion - end - - def setting_system_font_ui - settings.system_font_ui - end - - def setting_noindex - settings.noindex - end - - def setting_theme - settings.theme - end - def token_for_app(a) return nil if a.nil? || a.owner != self Doorkeeper::AccessToken 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/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 -- cgit From 30237259367a0ef2b20908518b86bbeb358999b5 Mon Sep 17 00:00:00 2001 From: ysksn Date: Thu, 16 Nov 2017 11:07:27 +0900 Subject: Add tests for Status#hidden? (#5719) --- spec/models/status_spec.rb | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'spec/models') diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb index 9cb71d715..0b8ed66f6 100644 --- a/spec/models/status_spec.rb +++ b/spec/models/status_spec.rb @@ -69,6 +69,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 -- cgit