diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb
index b3fc4e5612b..4676f60de7c 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 73d6baaceaa..fbaebf786dd 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 febda7219a8..3474250feb3 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 0e3007ce848..724b10980aa 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 344f6749d47..5a5136dd186 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 311ccae5b35..b225402041a 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 01e18928e4b..8e50ec405cc 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 f420f0abf4a..79d86370ec4 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 00000000000..73e48cf09b1
--- /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 8f7b91d218b..39663d5cab3 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 e6514487118..17c87035150 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 00000000000..a96232dbd23
--- /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 e4504f54334..0ded6f15967 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 b26520f5bd5..55ad812b227 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 4174a35234c..105696da63a 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 132369484d7..9b7cbd81f26 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 6a24a824782..8a77f2f38ac 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 00000000000..0410938c9b8
--- /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 bf319ce5566..2d763e2f4fc 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 c770649ecdd..053c53e5af1 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 3e6fa887b2a..97d6c277382 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 00000000000..a468549d831
--- /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 8097cb250e6..800140b6ff6 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 58ee66dedf2..fad0dd36955 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)