diff --git a/app/controllers/api/v1/lists_controller.rb b/app/controllers/api/v1/lists_controller.rb index 4071c34799..0bacd7fdb0 100644 --- a/app/controllers/api/v1/lists_controller.rb +++ b/app/controllers/api/v1/lists_controller.rb @@ -45,6 +45,6 @@ class Api::V1::ListsController < Api::BaseController end def list_params - params.permit(:title, :replies_policy, :exclusive) + params.permit(:title, :replies_policy, :exclusive, :notify) end end diff --git a/app/javascript/mastodon/actions/lists.js b/app/javascript/mastodon/actions/lists.js index e5c606f4ec..8d1bff209e 100644 --- a/app/javascript/mastodon/actions/lists.js +++ b/app/javascript/mastodon/actions/lists.js @@ -151,10 +151,15 @@ export const createListFail = error => ({ error, }); -export const updateList = (id, title, shouldReset, isExclusive, replies_policy) => (dispatch, getState) => { +export const updateList = (id, title, shouldReset, isExclusive, replies_policy, notify) => (dispatch, getState) => { dispatch(updateListRequest(id)); - api(getState).put(`/api/v1/lists/${id}`, { title, replies_policy, exclusive: typeof isExclusive === 'undefined' ? undefined : !!isExclusive }).then(({ data }) => { + api(getState).put(`/api/v1/lists/${id}`, { + title, + replies_policy, + exclusive: typeof isExclusive === 'undefined' ? undefined : !!isExclusive, + notify: typeof notify === 'undefined' ? undefined : !!notify, + }).then(({ data }) => { dispatch(updateListSuccess(data)); if (shouldReset) { diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index e80131f979..39343d4796 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -148,6 +148,7 @@ const excludeTypesFromFilter = filter => { 'mention', 'poll', 'status', + 'list_status', 'update', 'admin.sign_up', 'admin.report', diff --git a/app/javascript/mastodon/features/list_timeline/index.jsx b/app/javascript/mastodon/features/list_timeline/index.jsx index bebbe1574b..c4a5f155c7 100644 --- a/app/javascript/mastodon/features/list_timeline/index.jsx +++ b/app/javascript/mastodon/features/list_timeline/index.jsx @@ -154,13 +154,19 @@ class ListTimeline extends PureComponent { handleRepliesPolicyChange = ({ target }) => { const { dispatch } = this.props; const { id } = this.props.params; - dispatch(updateList(id, undefined, false, undefined, target.value)); + dispatch(updateList(id, undefined, false, undefined, target.value, undefined)); }; onExclusiveToggle = ({ target }) => { const { dispatch } = this.props; const { id } = this.props.params; - dispatch(updateList(id, undefined, false, target.checked, undefined)); + dispatch(updateList(id, undefined, false, target.checked, undefined, undefined)); + }; + + onNotifyToggle = ({ target }) => { + const { dispatch } = this.props; + const { id } = this.props.params; + dispatch(updateList(id, undefined, false, undefined, undefined, target.checked)); }; render () { @@ -170,6 +176,7 @@ class ListTimeline extends PureComponent { const title = list ? list.get('title') : id; const replies_policy = list ? list.get('replies_policy') : undefined; const isExclusive = list ? list.get('exclusive') : undefined; + const isNotify = list ? list.get('notify') : undefined; const antennas = list ? (list.get('antennas')?.toArray() || []) : []; if (typeof list === 'undefined') { @@ -216,6 +223,13 @@ class ListTimeline extends PureComponent { +
+ + +
+ { replies_policy !== undefined && (
diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.jsx b/app/javascript/mastodon/features/notifications/components/column_settings.jsx index f9c734c18f..6c90ff5779 100644 --- a/app/javascript/mastodon/features/notifications/components/column_settings.jsx +++ b/app/javascript/mastodon/features/notifications/components/column_settings.jsx @@ -190,6 +190,17 @@ export default class ColumnSettings extends PureComponent {
+
+ + +
+ + {showPushSettings && } + + +
+
+
diff --git a/app/javascript/mastodon/features/notifications/components/notification.jsx b/app/javascript/mastodon/features/notifications/components/notification.jsx index 8bb31b3956..2a472d35c5 100644 --- a/app/javascript/mastodon/features/notifications/components/notification.jsx +++ b/app/javascript/mastodon/features/notifications/components/notification.jsx @@ -13,6 +13,7 @@ import { ReactComponent as FlagIcon } from '@material-symbols/svg-600/outlined/f import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg'; import { ReactComponent as InsertChartIcon } from '@material-symbols/svg-600/outlined/insert_chart.svg'; import { ReactComponent as ReferenceIcon } from '@material-symbols/svg-600/outlined/link.svg'; +import { ReactComponent as ListAltIcon } from '@material-symbols/svg-600/outlined/list_alt.svg'; import { ReactComponent as PersonIcon } from '@material-symbols/svg-600/outlined/person-fill.svg'; import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add-fill.svg'; import { ReactComponent as RepeatIcon } from '@material-symbols/svg-600/outlined/repeat.svg'; @@ -38,6 +39,7 @@ const messages = defineMessages({ poll: { id: 'notification.poll', defaultMessage: 'A poll you have voted in has ended' }, reblog: { id: 'notification.reblog', defaultMessage: '{name} boosted your status' }, status: { id: 'notification.status', defaultMessage: '{name} just posted' }, + listStatus: { id: 'notification.list_status', defaultMessage: '{name} post is added on {listName}' }, statusReference: { id: 'notification.status_reference', defaultMessage: '{name} refered' }, update: { id: 'notification.update', defaultMessage: '{name} edited a post' }, warning: { id: 'notification.warning', defaultMessage: 'You have been warned and "{action}" has been executed. Check your mailbox' }, @@ -358,6 +360,42 @@ class Notification extends ImmutablePureComponent { ); } + renderListStatus (notification, listLink, link) { + const { intl, unread, status } = this.props; + + if (!status) { + return null; + } + + return ( + +
+
+ + + + + +
+ +
+
+ ); + } + renderUpdate (notification, link) { const { intl, unread, status } = this.props; @@ -531,6 +569,10 @@ class Notification extends ImmutablePureComponent { return this.renderStatusReference(notification, link); case 'status': return this.renderStatus(notification, link); + case 'list_status': + const list = notification.get('list'); + const listLink = {list.get('title')}; + return this.renderListStatus(notification, listLink, link); case 'update': return this.renderUpdate(notification, link); case 'poll': diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 8fbc847d1a..5a4a508b25 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -399,6 +399,7 @@ "lists.exclusive": "Hide list or antenna account posts from home", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", + "lists.notify": "Notify these posts", "lists.replies_policy.followed": "Any followed user", "lists.replies_policy.list": "Members of the list", "lists.replies_policy.none": "No one", @@ -448,6 +449,7 @@ "notification.favourite": "{name} favorited your post", "notification.follow": "{name} followed you", "notification.follow_request": "{name} has requested to follow you", + "notification.list_status": "{name} post is added to {listName}", "notification.mention": "{name} mentioned you", "notification.own_poll": "Your poll has ended", "notification.poll": "A poll you have voted in has ended", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 1477974d3a..e6f3cb557b 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -474,6 +474,7 @@ "lists.exclusive": "ホームからリスト・アンテナに登録されたアカウントの投稿を非表示にする", "lists.new.create": "リストを作成", "lists.new.title_placeholder": "新規リスト名", + "lists.notify": "これらの投稿を通知する", "lists.replies_policy.followed": "フォロー中のユーザー全員", "lists.replies_policy.list": "リストのメンバー", "lists.replies_policy.none": "表示しない", @@ -525,6 +526,7 @@ "notification.favourite": "{name}さんがお気に入りしました", "notification.follow": "{name}さんにフォローされました", "notification.follow_request": "{name}さんがあなたにフォローリクエストしました", + "notification.list_status": "{name}さんの投稿が{listName}に追加されました", "notification.mention": "{name}さんがあなたに返信しました", "notification.own_poll": "アンケートが終了しました", "notification.poll": "アンケートが終了しました", diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js index 8ba9cf3c18..531645a5dd 100644 --- a/app/javascript/mastodon/reducers/notifications.js +++ b/app/javascript/mastodon/reducers/notifications.js @@ -55,6 +55,7 @@ const notificationToMap = notification => ImmutableMap({ created_at: notification.created_at, emoji_reaction: ImmutableMap(notification.emoji_reaction), status: notification.status ? notification.status.id : null, + list: notification.list ? ImmutableMap(notification.list) : null, report: notification.report ? fromJS(notification.report) : null, account_warning: notification.account_warning ? ImmutableMap(notification.account_warning) : null, }); diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js index 5cdba8008f..fcb75bfb74 100644 --- a/app/javascript/mastodon/reducers/settings.js +++ b/app/javascript/mastodon/reducers/settings.js @@ -42,6 +42,7 @@ const initialState = ImmutableMap({ mention: false, poll: false, status: false, + list_status: false, update: false, emoji_reaction: false, status_reference: false, @@ -66,6 +67,7 @@ const initialState = ImmutableMap({ mention: true, poll: true, status: true, + list_status: true, update: true, emoji_reaction: true, status_reference: true, @@ -81,6 +83,7 @@ const initialState = ImmutableMap({ mention: true, poll: true, status: true, + list_status: true, update: true, emoji_reaction: true, status_reference: true, diff --git a/app/lib/vacuum/list_statuses_vacuum.rb b/app/lib/vacuum/list_statuses_vacuum.rb new file mode 100644 index 0000000000..41dca7602f --- /dev/null +++ b/app/lib/vacuum/list_statuses_vacuum.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class Vacuum::ListStatusesVacuum + include Redisable + + LIST_STATUS_LIFE_DURATION = 1.day.freeze + + def perform + vacuum_list_statuses! + end + + private + + def vacuum_list_statuses! + ListStatus.where('created_at < ?', LIST_STATUS_LIFE_DURATION.ago).in_batches.destroy_all + end +end diff --git a/app/models/list.rb b/app/models/list.rb index cf8f47963a..5d349a731a 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -11,6 +11,7 @@ # updated_at :datetime not null # replies_policy :integer default("list"), not null # exclusive :boolean default(FALSE), not null +# notify :boolean default(FALSE), not null # class List < ApplicationRecord @@ -25,6 +26,8 @@ class List < ApplicationRecord has_many :list_accounts, inverse_of: :list, dependent: :destroy has_many :accounts, through: :list_accounts has_many :antennas, inverse_of: :list, dependent: :destroy + has_many :list_statuses, inverse_of: :list, dependent: :destroy + has_many :statuses, through: :list_statuses validates :title, presence: true diff --git a/app/models/list_status.rb b/app/models/list_status.rb new file mode 100644 index 0000000000..a51e4fc331 --- /dev/null +++ b/app/models/list_status.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: list_statuses +# +# id :bigint(8) not null, primary key +# list_id :bigint(8) not null +# status_id :bigint(8) not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class ListStatus < ApplicationRecord + belongs_to :list + belongs_to :status + + has_one :notification, as: :activity, dependent: :destroy + + validates :status, uniqueness: { scope: :list } +end diff --git a/app/models/notification.rb b/app/models/notification.rb index 50e45f05b3..ba63833e22 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -22,6 +22,7 @@ class Notification < ApplicationRecord LEGACY_TYPE_CLASS_MAP = { 'Mention' => :mention, 'Status' => :reblog, + 'ListStatus' => :list_status, 'Follow' => :follow, 'FollowRequest' => :follow_request, 'Favourite' => :favourite, @@ -34,6 +35,7 @@ class Notification < ApplicationRecord TYPES = %i( mention status + list_status reblog status_reference follow @@ -50,6 +52,7 @@ class Notification < ApplicationRecord TARGET_STATUS_INCLUDES_BY_TYPE = { status: :status, + list_status: [list_status: :status], reblog: [status: :reblog], status_reference: [status_reference: :status], mention: [mention: :status], @@ -68,6 +71,7 @@ class Notification < ApplicationRecord with_options foreign_key: 'activity_id', optional: true do belongs_to :mention, inverse_of: :notification belongs_to :status, inverse_of: :notification + belongs_to :list_status, inverse_of: :notification belongs_to :follow, inverse_of: :notification belongs_to :follow_request, inverse_of: :notification belongs_to :favourite, inverse_of: :notification @@ -90,6 +94,8 @@ class Notification < ApplicationRecord case type when :status, :update status + when :list_status + list_status&.status when :reblog status&.reblog when :status_reference @@ -143,6 +149,8 @@ class Notification < ApplicationRecord case notification.type when :status, :update notification.status = cached_status + when :list_status + notification.list_status.status = cached_status when :reblog notification.status.reblog = cached_status when :status_reference @@ -182,7 +190,7 @@ class Notification < ApplicationRecord case activity_type when 'Status', 'Follow', 'Favourite', 'EmojiReaction', 'EmojiReact', 'FollowRequest', 'Poll', 'Report', 'AccountWarning' self.from_account_id = activity&.account_id - when 'Mention', 'StatusReference' + when 'Mention', 'StatusReference', 'ListStatus' self.from_account_id = activity&.status&.account_id when 'Account' self.from_account_id = activity&.id diff --git a/app/models/status.rb b/app/models/status.rb index cdfd7b9555..5e6df5972f 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -111,6 +111,7 @@ class Status < ApplicationRecord has_one :trend, class_name: 'StatusTrend', inverse_of: :status has_one :scheduled_expiration_status, inverse_of: :status, dependent: :destroy has_one :circle_status, inverse_of: :status, dependent: :destroy + has_many :list_status, inverse_of: :status, dependent: :destroy validates :uri, uniqueness: true, presence: true, unless: :local? validates :text, presence: true, unless: -> { with_media? || reblog? } diff --git a/app/serializers/rest/list_serializer.rb b/app/serializers/rest/list_serializer.rb index c6e420fca2..2415fe7283 100644 --- a/app/serializers/rest/list_serializer.rb +++ b/app/serializers/rest/list_serializer.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class REST::ListSerializer < ActiveModel::Serializer - attributes :id, :title, :replies_policy, :exclusive + attributes :id, :title, :replies_policy, :exclusive, :notify def id object.id.to_s diff --git a/app/serializers/rest/notification_serializer.rb b/app/serializers/rest/notification_serializer.rb index 71b5d69fc5..f0a3d96eb6 100644 --- a/app/serializers/rest/notification_serializer.rb +++ b/app/serializers/rest/notification_serializer.rb @@ -8,13 +8,14 @@ class REST::NotificationSerializer < ActiveModel::Serializer belongs_to :report, if: :report_type?, serializer: REST::ReportSerializer belongs_to :emoji_reaction, if: :emoji_reaction_type?, serializer: REST::NotifyEmojiReactionSerializer belongs_to :account_warning, if: :warning_type?, serializer: REST::AccountWarningSerializer + belongs_to :list, if: :list_status_type?, serializer: REST::ListSerializer def id object.id.to_s end def status_type? - [:favourite, :emoji_reaction, :reaction, :reblog, :status_reference, :status, :mention, :poll, :update].include?(object.type) + [:favourite, :emoji_reaction, :reaction, :reblog, :status_reference, :status, :list_status, :mention, :poll, :update].include?(object.type) end def report_type? @@ -28,4 +29,12 @@ class REST::NotificationSerializer < ActiveModel::Serializer def emoji_reaction_type? object.type == :emoji_reaction end + + def list_status_type? + object.type == :list_status + end + + def list + object.list_status.list + end end diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index eaa5ce233b..9968d3d616 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -10,6 +10,8 @@ class NotifyService < BaseService poll emoji_reaction status_reference + status + list_status warning ).freeze diff --git a/app/workers/feed_insert_worker.rb b/app/workers/feed_insert_worker.rb index 22acd0aa6f..c0a1722c10 100644 --- a/app/workers/feed_insert_worker.rb +++ b/app/workers/feed_insert_worker.rb @@ -38,6 +38,7 @@ class FeedInsertWorker else perform_push perform_notify if notify? + perform_notify_for_list if notify_for_list? end end @@ -58,6 +59,12 @@ class FeedInsertWorker Follow.find_by(account: @follower, target_account: @status.account)&.notify? end + def notify_for_list? + return false unless @type == :list + + @list.notify? + end + def perform_push if @antenna.nil? || @antenna.insert_feeds case @type @@ -90,6 +97,11 @@ class FeedInsertWorker LocalNotificationWorker.perform_async(@follower.id, @status.id, 'Status', 'status') end + def perform_notify_for_list + list_status = ListStatus.create!(list: @list, status: @status) + LocalNotificationWorker.perform_async(@list.account_id, list_status.id, 'ListStatus', 'list_status') + end + def update? @options[:update] end diff --git a/app/workers/scheduler/vacuum_scheduler.rb b/app/workers/scheduler/vacuum_scheduler.rb index 1c9a2aabe3..eb4d2f5b10 100644 --- a/app/workers/scheduler/vacuum_scheduler.rb +++ b/app/workers/scheduler/vacuum_scheduler.rb @@ -25,6 +25,7 @@ class Scheduler::VacuumScheduler applications_vacuum, feeds_vacuum, imports_vacuum, + list_statuses_vacuum, ] end @@ -32,6 +33,10 @@ class Scheduler::VacuumScheduler Vacuum::StatusesVacuum.new(content_retention_policy.content_cache_retention_period) end + def list_statuses_vacuum + Vacuum::ListStatusesVacuum.new + end + def media_attachments_vacuum Vacuum::MediaAttachmentsVacuum.new(content_retention_policy.media_cache_retention_period) end diff --git a/db/migrate/20231028004612_create_list_statuses.rb b/db/migrate/20231028004612_create_list_statuses.rb new file mode 100644 index 0000000000..30cd2da9df --- /dev/null +++ b/db/migrate/20231028004612_create_list_statuses.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require Rails.root.join('lib', 'mastodon', 'migration_helpers') + +class CreateListStatuses < ActiveRecord::Migration[7.1] + include Mastodon::MigrationHelpers + + disable_ddl_transaction! + + def change + safety_assured do + create_table :list_statuses do |t| + t.belongs_to :list, null: false, foreign_key: { on_delete: :cascade } + t.belongs_to :status, null: false, foreign_key: { on_delete: :cascade } + t.datetime :created_at, null: false + t.datetime :updated_at, null: false + end + + add_index :list_statuses, [:list_id, :status_id], unique: true + end + end +end diff --git a/db/migrate/20231028005948_add_notify_to_list.rb b/db/migrate/20231028005948_add_notify_to_list.rb new file mode 100644 index 0000000000..df8bd473da --- /dev/null +++ b/db/migrate/20231028005948_add_notify_to_list.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require Rails.root.join('lib', 'mastodon', 'migration_helpers') + +class AddNotifyToList < ActiveRecord::Migration[7.0] + include Mastodon::MigrationHelpers + + disable_ddl_transaction! + + def change + safety_assured do + add_column_with_default :lists, :notify, :boolean, default: false, allow_null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index e43428edf8..6bd862ac02 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[7.0].define(version: 2023_10_23_083359) do +ActiveRecord::Schema[7.1].define(version: 2023_10_28_005948) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -761,6 +761,16 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_23_083359) do t.index ["list_id", "account_id"], name: "index_list_accounts_on_list_id_and_account_id" end + create_table "list_statuses", force: :cascade do |t| + t.bigint "list_id", null: false + t.bigint "status_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["list_id", "status_id"], name: "index_list_statuses_on_list_id_and_status_id", unique: true + t.index ["list_id"], name: "index_list_statuses_on_list_id" + t.index ["status_id"], name: "index_list_statuses_on_status_id" + end + create_table "lists", force: :cascade do |t| t.bigint "account_id", null: false t.string "title", default: "", null: false @@ -768,6 +778,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_23_083359) do t.datetime "updated_at", precision: nil, null: false t.integer "replies_policy", default: 0, null: false t.boolean "exclusive", default: false, null: false + t.boolean "notify", default: false, null: false t.index ["account_id"], name: "index_lists_on_account_id" end @@ -1480,6 +1491,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_23_083359) do add_foreign_key "list_accounts", "follow_requests", on_delete: :cascade add_foreign_key "list_accounts", "follows", on_delete: :cascade add_foreign_key "list_accounts", "lists", on_delete: :cascade + add_foreign_key "list_statuses", "lists", on_delete: :cascade + add_foreign_key "list_statuses", "statuses", on_delete: :cascade add_foreign_key "lists", "accounts", on_delete: :cascade add_foreign_key "login_activities", "users", on_delete: :cascade add_foreign_key "markers", "users", on_delete: :cascade diff --git a/spec/fabricators/list_status_fabricator.rb b/spec/fabricators/list_status_fabricator.rb new file mode 100644 index 0000000000..6324d92558 --- /dev/null +++ b/spec/fabricators/list_status_fabricator.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +Fabricator(:list_status) do + list { Fabricate.build(:list) } + status +end diff --git a/spec/lib/vacuum/list_statuses_vacuum_spec.rb b/spec/lib/vacuum/list_statuses_vacuum_spec.rb new file mode 100644 index 0000000000..d28b1ad8d9 --- /dev/null +++ b/spec/lib/vacuum/list_statuses_vacuum_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Vacuum::ListStatusesVacuum do + subject { described_class.new } + + describe '#perform' do + let!(:local_status_old) { Fabricate(:status, created_at: 2.days.ago) } + let!(:local_status_recent) { Fabricate(:status, created_at: 5.hours.ago) } + let!(:list_status_old) { Fabricate(:list_status, status: local_status_old, created_at: local_status_old.created_at) } + let!(:list_status_recent) { Fabricate(:list_status, status: local_status_recent, created_at: local_status_recent.created_at) } + + before do + subject.perform + end + + it 'deletes old list status' do + expect { list_status_old.reload }.to raise_error ActiveRecord::RecordNotFound + end + + it 'does not delete recent status' do + expect { list_status_recent.reload }.to_not raise_error + end + + it 'statuses are remain' do + expect { local_status_old }.to_not raise_error + end + end +end diff --git a/spec/requests/api/v1/lists_spec.rb b/spec/requests/api/v1/lists_spec.rb index a2466060c0..d515dd48c9 100644 --- a/spec/requests/api/v1/lists_spec.rb +++ b/spec/requests/api/v1/lists_spec.rb @@ -19,6 +19,7 @@ RSpec.describe 'Lists' do Fabricate(:list, account: user.account, title: 'second list', replies_policy: :list), Fabricate(:list, account: user.account, title: 'third list', replies_policy: :none), Fabricate(:list, account: user.account, title: 'fourth list', exclusive: true), + Fabricate(:list, account: user.account, title: 'fourth list', notify: true), ] end @@ -30,6 +31,7 @@ RSpec.describe 'Lists' do replies_policy: list.replies_policy, exclusive: list.exclusive, antennas: list.antennas, + notify: list.notify, } end end @@ -67,6 +69,7 @@ RSpec.describe 'Lists' do replies_policy: list.replies_policy, exclusive: list.exclusive, antennas: list.antennas, + notify: list.notify, }) end @@ -149,6 +152,7 @@ RSpec.describe 'Lists' do replies_policy: list.replies_policy, exclusive: list.exclusive, antennas: list.antennas, + notify: list.notify, }) end diff --git a/spec/workers/feed_insert_worker_spec.rb b/spec/workers/feed_insert_worker_spec.rb index 97c73c5999..c172dc4c1e 100644 --- a/spec/workers/feed_insert_worker_spec.rb +++ b/spec/workers/feed_insert_worker_spec.rb @@ -5,6 +5,10 @@ require 'rails_helper' describe FeedInsertWorker do subject { described_class.new } + def notify?(account, type, activity_id) + Notification.exists?(account: account, type: type, activity_id: activity_id) + end + describe 'perform' do let(:follower) { Fabricate(:account) } let(:status) { Fabricate(:status) } @@ -48,5 +52,43 @@ describe FeedInsertWorker do expect(instance).to have_received(:push_to_home).with(follower, status, update: nil) end end + + context 'with notification' do + it 'skips notification when unset' do + subject.perform(status.id, follower.id) + expect(notify?(follower, 'status', status.id)).to be false + end + + it 'pushes notification when read status is set' do + Fabricate(:follow, account: follower, target_account: status.account, notify: true) + + subject.perform(status.id, follower.id) + expect(notify?(follower, 'status', status.id)).to be true + end + + it 'skips notification when the account is registered list but not notify' do + follower.follow!(status.account) + list = Fabricate(:list, account: follower) + Fabricate(:list_account, list: list, account: status.account) + + subject.perform(status.id, list.id, 'list') + + list_status = ListStatus.find_by(list: list, status: status) + + expect(list_status).to be_nil + end + + it 'pushes notification when the account is registered list' do + follower.follow!(status.account) + list = Fabricate(:list, account: follower, notify: true) + Fabricate(:list_account, list: list, account: status.account) + + subject.perform(status.id, list.id, 'list') + list_status = ListStatus.find_by(list: list, status: status) + + expect(list_status).to_not be_nil + expect(notify?(follower, 'list_status', list_status.id)).to be true + end + end end end