mirror of
https://git.kescher.at/CatCatNya/catstodon.git
synced 2024-11-25 17:51:36 +01:00
Merge commit 'f99da81ef8b14a851347503d4177f83322c16d9a' into glitch-soc/stable-4.3
This commit is contained in:
commit
b40adb4a89
17 changed files with 159 additions and 94 deletions
6
.github/workflows/build-push-pr.yml
vendored
6
.github/workflows/build-push-pr.yml
vendored
|
@ -21,9 +21,11 @@ jobs:
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- id: version_vars
|
- id: version_vars
|
||||||
run: |
|
run: |
|
||||||
echo mastodon_version_metadata=pr-${{ github.event.pull_request.number }}-$(git rev-parse --short HEAD) >> $GITHUB_OUTPUT
|
echo mastodon_version_metadata=pr-${{ github.event.pull_request.number }}-$(git rev-parse --short ${{github.event.pull_request.head.sha}}) >> $GITHUB_OUTPUT
|
||||||
|
echo mastodon_short_sha=$(git rev-parse --short ${{github.event.pull_request.head.sha}}) >> $GITHUB_OUTPUT
|
||||||
outputs:
|
outputs:
|
||||||
metadata: ${{ steps.version_vars.outputs.mastodon_version_metadata }}
|
metadata: ${{ steps.version_vars.outputs.mastodon_version_metadata }}
|
||||||
|
short_sha: ${{ steps.version_vars.outputs.mastodon_short_sha }}
|
||||||
|
|
||||||
build-image:
|
build-image:
|
||||||
needs: compute-suffix
|
needs: compute-suffix
|
||||||
|
@ -39,6 +41,7 @@ jobs:
|
||||||
latest=auto
|
latest=auto
|
||||||
tags: |
|
tags: |
|
||||||
type=ref,event=pr
|
type=ref,event=pr
|
||||||
|
type=ref,event=pr,suffix=-${{ needs.compute-suffix.outputs.short_sha }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
build-image-streaming:
|
build-image-streaming:
|
||||||
|
@ -55,4 +58,5 @@ jobs:
|
||||||
latest=auto
|
latest=auto
|
||||||
tags: |
|
tags: |
|
||||||
type=ref,event=pr
|
type=ref,event=pr
|
||||||
|
type=ref,event=pr,suffix=-${{ needs.compute-suffix.outputs.short_sha }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
2
.github/workflows/build-releases.yml
vendored
2
.github/workflows/build-releases.yml
vendored
|
@ -22,7 +22,7 @@ jobs:
|
||||||
# Only tag with latest when ran against the latest stable branch
|
# Only tag with latest when ran against the latest stable branch
|
||||||
# This needs to be updated after each minor version release
|
# This needs to be updated after each minor version release
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=${{ startsWith(github.ref, 'refs/tags/v4.2.') }}
|
latest=${{ startsWith(github.ref, 'refs/tags/v4.3.') }}
|
||||||
tags: |
|
tags: |
|
||||||
type=pep440,pattern={{raw}}
|
type=pep440,pattern={{raw}}
|
||||||
type=pep440,pattern=v{{major}}.{{minor}}
|
type=pep440,pattern=v{{major}}.{{minor}}
|
||||||
|
|
|
@ -52,7 +52,7 @@ class Api::V1::Notifications::RequestsController < Api::BaseController
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_requests
|
def load_requests
|
||||||
requests = NotificationRequest.where(account: current_account).includes(:last_status, from_account: [:account_stat, :user]).to_a_paginated_by_id(
|
requests = NotificationRequest.where(account: current_account).without_suspended.includes(:last_status, from_account: [:account_stat, :user]).to_a_paginated_by_id(
|
||||||
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||||
params_slice(:max_id, :since_id, :min_id)
|
params_slice(:max_id, :since_id, :min_id)
|
||||||
)
|
)
|
||||||
|
|
|
@ -13,7 +13,7 @@ export interface ApiAccountRoleJSON {
|
||||||
}
|
}
|
||||||
|
|
||||||
// See app/serializers/rest/account_serializer.rb
|
// See app/serializers/rest/account_serializer.rb
|
||||||
export interface ApiAccountJSON {
|
export interface BaseApiAccountJSON {
|
||||||
acct: string;
|
acct: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
avatar_static: string;
|
avatar_static: string;
|
||||||
|
@ -45,3 +45,12 @@ export interface ApiAccountJSON {
|
||||||
memorial?: boolean;
|
memorial?: boolean;
|
||||||
hide_collections: boolean;
|
hide_collections: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See app/serializers/rest/muted_account_serializer.rb
|
||||||
|
export interface ApiMutedAccountJSON extends BaseApiAccountJSON {
|
||||||
|
mute_expires_at?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For now, we have the same type representing both `Account` and `MutedAccount`
|
||||||
|
// objects, but we should refactor this in the future.
|
||||||
|
export type ApiAccountJSON = ApiMutedAccountJSON;
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
import type { IconProp } from 'mastodon/components/icon';
|
import type { IconProp } from 'mastodon/components/icon';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
import Status from 'mastodon/containers/status_container';
|
import Status from 'mastodon/containers/status_container';
|
||||||
|
import { getStatusHidden } from 'mastodon/selectors/filters';
|
||||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||||
|
|
||||||
import { DisplayedName } from './displayed_name';
|
import { DisplayedName } from './displayed_name';
|
||||||
|
@ -48,6 +49,12 @@ export const NotificationWithStatus: React.FC<{
|
||||||
(state) => state.statuses.getIn([statusId, 'visibility']) === 'direct',
|
(state) => state.statuses.getIn([statusId, 'visibility']) === 'direct',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isFiltered = useAppSelector(
|
||||||
|
(state) =>
|
||||||
|
statusId &&
|
||||||
|
getStatusHidden(state, { id: statusId, contextType: 'notifications' }),
|
||||||
|
);
|
||||||
|
|
||||||
const handlers = useMemo(
|
const handlers = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
open: () => {
|
open: () => {
|
||||||
|
@ -73,7 +80,7 @@ export const NotificationWithStatus: React.FC<{
|
||||||
[dispatch, statusId],
|
[dispatch, statusId],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!statusId) return null;
|
if (!statusId || isFiltered) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HotKeys handlers={handlers}>
|
<HotKeys handlers={handlers}>
|
||||||
|
|
|
@ -95,6 +95,9 @@ export const accountDefaultValues: AccountShape = {
|
||||||
limited: false,
|
limited: false,
|
||||||
moved: null,
|
moved: null,
|
||||||
hide_collections: false,
|
hide_collections: false,
|
||||||
|
// This comes from `ApiMutedAccountJSON`, but we should eventually
|
||||||
|
// store that in a different object.
|
||||||
|
mute_expires_at: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const AccountFactory = ImmutableRecord<AccountShape>(accountDefaultValues);
|
const AccountFactory = ImmutableRecord<AccountShape>(accountDefaultValues);
|
||||||
|
|
|
@ -559,7 +559,10 @@ export const notificationGroupsReducer = createReducer<NotificationGroupsState>(
|
||||||
compareId(state.lastReadId, mostRecentGroup.page_max_id) < 0
|
compareId(state.lastReadId, mostRecentGroup.page_max_id) < 0
|
||||||
)
|
)
|
||||||
state.lastReadId = mostRecentGroup.page_max_id;
|
state.lastReadId = mostRecentGroup.page_max_id;
|
||||||
commitLastReadId(state);
|
|
||||||
|
// We don't call `commitLastReadId`, because that is conditional
|
||||||
|
// and we want to unconditionally update the state instead.
|
||||||
|
state.readMarkerId = state.lastReadId;
|
||||||
})
|
})
|
||||||
.addCase(fetchMarkers.fulfilled, (state, action) => {
|
.addCase(fetchMarkers.fulfilled, (state, action) => {
|
||||||
if (
|
if (
|
||||||
|
|
50
app/javascript/mastodon/selectors/filters.ts
Normal file
50
app/javascript/mastodon/selectors/filters.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
import type { RootState } from 'mastodon/store';
|
||||||
|
import { toServerSideType } from 'mastodon/utils/filters';
|
||||||
|
|
||||||
|
// TODO: move to `app/javascript/mastodon/models` and use more globally
|
||||||
|
type Filter = Immutable.Map<string, unknown>;
|
||||||
|
|
||||||
|
// TODO: move to `app/javascript/mastodon/models` and use more globally
|
||||||
|
type FilterResult = Immutable.Map<string, unknown>;
|
||||||
|
|
||||||
|
export const getFilters = createSelector(
|
||||||
|
[
|
||||||
|
(state: RootState) => state.filters as Immutable.Map<string, Filter>,
|
||||||
|
(_, { contextType }: { contextType: string }) => contextType,
|
||||||
|
],
|
||||||
|
(filters, contextType) => {
|
||||||
|
if (!contextType) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const serverSideType = toServerSideType(contextType);
|
||||||
|
|
||||||
|
return filters.filter((filter) => {
|
||||||
|
const context = filter.get('context') as Immutable.List<string>;
|
||||||
|
const expiration = filter.get('expires_at') as Date | null;
|
||||||
|
return (
|
||||||
|
context.includes(serverSideType) &&
|
||||||
|
(expiration === null || expiration > now)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getStatusHidden = (
|
||||||
|
state: RootState,
|
||||||
|
{ id, contextType }: { id: string; contextType: string },
|
||||||
|
) => {
|
||||||
|
const filters = getFilters(state, { contextType });
|
||||||
|
if (filters === null) return false;
|
||||||
|
|
||||||
|
const filtered = state.statuses.getIn([id, 'filtered']) as
|
||||||
|
| Immutable.List<FilterResult>
|
||||||
|
| undefined;
|
||||||
|
return filtered?.some(
|
||||||
|
(result) =>
|
||||||
|
filters.getIn([result.get('filter'), 'filter_action']) === 'hide',
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,23 +1,12 @@
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
|
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
|
||||||
|
|
||||||
import { toServerSideType } from 'mastodon/utils/filters';
|
|
||||||
|
|
||||||
import { me } from '../initial_state';
|
import { me } from '../initial_state';
|
||||||
|
|
||||||
|
import { getFilters } from './filters';
|
||||||
|
|
||||||
export { makeGetAccount } from "./accounts";
|
export { makeGetAccount } from "./accounts";
|
||||||
|
|
||||||
const getFilters = createSelector([state => state.get('filters'), (_, { contextType }) => contextType], (filters, contextType) => {
|
|
||||||
if (!contextType) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const serverSideType = toServerSideType(contextType);
|
|
||||||
|
|
||||||
return filters.filter(filter => filter.get('context').includes(serverSideType) && (filter.get('expires_at') === null || filter.get('expires_at') > now));
|
|
||||||
});
|
|
||||||
|
|
||||||
export const makeGetStatus = () => {
|
export const makeGetStatus = () => {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
[
|
[
|
||||||
|
|
|
@ -1045,6 +1045,12 @@ a.name-tag,
|
||||||
color: var(--user-role-accent);
|
color: var(--user-role-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.applications-list {
|
||||||
|
.icon {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.announcements-list,
|
.announcements-list,
|
||||||
.filters-list {
|
.filters-list {
|
||||||
border: 1px solid var(--background-border-color);
|
border: 1px solid var(--background-border-color);
|
||||||
|
|
|
@ -2863,7 +2863,7 @@ $ui-header-logo-wordmark-width: 99px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column {
|
.column {
|
||||||
width: 400px;
|
width: clamp(380px, calc((100% - 350px) / 4), 400px);
|
||||||
position: relative;
|
position: relative;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -7988,79 +7988,23 @@ noscript {
|
||||||
background: rgba($base-overlay-background, 0.5);
|
background: rgba($base-overlay-background, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list-adder,
|
||||||
.list-editor {
|
.list-editor {
|
||||||
background: $ui-base-color;
|
backdrop-filter: var(--background-filter);
|
||||||
|
background: var(--modal-background-color);
|
||||||
|
border: 1px solid var(--modal-border-color);
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
|
||||||
width: 380px;
|
width: 380px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
@media screen and (width <= 420px) {
|
@media screen and (width <= 420px) {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
|
||||||
padding: 15px 0;
|
|
||||||
background: lighten($ui-base-color, 13%);
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 16px;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 8px 8px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer__pager {
|
|
||||||
height: 50vh;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer__inner {
|
|
||||||
border-radius: 0 0 8px 8px;
|
|
||||||
|
|
||||||
&.backdrop {
|
|
||||||
width: calc(100% - 60px);
|
|
||||||
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
|
||||||
border-radius: 0 0 0 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__accounts {
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account__display-name {
|
|
||||||
&:hover strong {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.account__avatar {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-adder {
|
.list-adder {
|
||||||
background: $ui-base-color;
|
|
||||||
flex-direction: column;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
|
||||||
width: 380px;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
@media screen and (width <= 420px) {
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__account {
|
|
||||||
background: lighten($ui-base-color, 13%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__lists {
|
&__lists {
|
||||||
background: lighten($ui-base-color, 13%);
|
|
||||||
height: 50vh;
|
height: 50vh;
|
||||||
border-radius: 0 0 8px 8px;
|
border-radius: 0 0 8px 8px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
@ -8081,6 +8025,52 @@ noscript {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-editor {
|
||||||
|
h4 {
|
||||||
|
padding: 15px 0;
|
||||||
|
background: lighten($ui-base-color, 13%);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 16px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer__pager {
|
||||||
|
height: 50vh;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer__inner {
|
||||||
|
&.backdrop {
|
||||||
|
width: calc(100% - 60px);
|
||||||
|
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
||||||
|
border-radius: 0 0 0 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__accounts {
|
||||||
|
background: unset;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account__display-name {
|
||||||
|
&:hover strong {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.account__avatar {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,6 @@ class NotificationPolicy < ApplicationRecord
|
||||||
private
|
private
|
||||||
|
|
||||||
def pending_notification_requests
|
def pending_notification_requests
|
||||||
@pending_notification_requests ||= notification_requests.limit(MAX_MEANINGFUL_COUNT).pick(Arel.sql('count(*), coalesce(sum(notifications_count), 0)::bigint'))
|
@pending_notification_requests ||= notification_requests.without_suspended.limit(MAX_MEANINGFUL_COUNT).pick(Arel.sql('count(*), coalesce(sum(notifications_count), 0)::bigint'))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -26,6 +26,8 @@ class NotificationRequest < ApplicationRecord
|
||||||
|
|
||||||
before_save :prepare_notifications_count
|
before_save :prepare_notifications_count
|
||||||
|
|
||||||
|
scope :without_suspended, -> { joins(:from_account).merge(Account.without_suspended) }
|
||||||
|
|
||||||
def self.preload_cache_collection(requests)
|
def self.preload_cache_collection(requests)
|
||||||
cached_statuses_by_id = yield(requests.filter_map(&:last_status)).index_by(&:id) # Call cache_collection in block
|
cached_statuses_by_id = yield(requests.filter_map(&:last_status)).index_by(&:id) # Call cache_collection in block
|
||||||
|
|
||||||
|
|
|
@ -55,12 +55,8 @@ class Web::PushNotificationWorker
|
||||||
end
|
end
|
||||||
|
|
||||||
def push_notification_json
|
def push_notification_json
|
||||||
Oj.dump(serialized_notification_in_subscription_locale.as_json)
|
|
||||||
end
|
|
||||||
|
|
||||||
def serialized_notification_in_subscription_locale
|
|
||||||
I18n.with_locale(@subscription.locale.presence || I18n.default_locale) do
|
I18n.with_locale(@subscription.locale.presence || I18n.default_locale) do
|
||||||
serialized_notification
|
Oj.dump(serialized_notification.as_json)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ require_relative 'base'
|
||||||
|
|
||||||
module Mastodon::CLI
|
module Mastodon::CLI
|
||||||
class IpBlocks < Base
|
class IpBlocks < Base
|
||||||
option :severity, required: true, enum: %w(no_access sign_up_requires_approval sign_up_block), desc: 'Severity of the block'
|
option :severity, required: true, enum: IpBlock.severities.keys, desc: 'Severity of the block'
|
||||||
option :comment, aliases: [:c], desc: 'Optional comment'
|
option :comment, aliases: [:c], desc: 'Optional comment'
|
||||||
option :duration, aliases: [:d], type: :numeric, desc: 'Duration of the block in seconds'
|
option :duration, aliases: [:d], type: :numeric, desc: 'Duration of the block in seconds'
|
||||||
option :force, type: :boolean, aliases: [:f], desc: 'Overwrite existing blocks'
|
option :force, type: :boolean, aliases: [:f], desc: 'Overwrite existing blocks'
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace :db do
|
||||||
desc 'Generate a set of keys for configuring Active Record encryption in a given environment'
|
desc 'Generate a set of keys for configuring Active Record encryption in a given environment'
|
||||||
task :init do # rubocop:disable Rails/RakeEnvironment
|
task :init do # rubocop:disable Rails/RakeEnvironment
|
||||||
puts <<~MSG
|
puts <<~MSG
|
||||||
Add these secret environment variables to your Mastodon environment (e.g. .env.production):#{' '}
|
Add the following secret environment variables to your Mastodon environment (e.g. .env.production), ensure they are shared across all your nodes and do not change them after they are set:#{' '}
|
||||||
|
|
||||||
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=#{SecureRandom.alphanumeric(32)}
|
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=#{SecureRandom.alphanumeric(32)}
|
||||||
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=#{SecureRandom.alphanumeric(32)}
|
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=#{SecureRandom.alphanumeric(32)}
|
||||||
|
|
|
@ -7,19 +7,25 @@ RSpec.describe NotificationPolicy do
|
||||||
subject { Fabricate(:notification_policy) }
|
subject { Fabricate(:notification_policy) }
|
||||||
|
|
||||||
let(:sender) { Fabricate(:account) }
|
let(:sender) { Fabricate(:account) }
|
||||||
|
let(:suspended_sender) { Fabricate(:account) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
Fabricate.times(2, :notification, account: subject.account, activity: Fabricate(:status, account: sender), filtered: true, type: :mention)
|
Fabricate.times(2, :notification, account: subject.account, activity: Fabricate(:status, account: sender), filtered: true, type: :mention)
|
||||||
Fabricate(:notification_request, account: subject.account, from_account: sender)
|
Fabricate(:notification_request, account: subject.account, from_account: sender)
|
||||||
|
|
||||||
|
Fabricate(:notification, account: subject.account, activity: Fabricate(:status, account: suspended_sender), filtered: true, type: :mention)
|
||||||
|
Fabricate(:notification_request, account: subject.account, from_account: suspended_sender)
|
||||||
|
|
||||||
|
suspended_sender.suspend!
|
||||||
|
|
||||||
subject.summarize!
|
subject.summarize!
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets pending_requests_count' do
|
it 'sets pending_requests_count and pending_notifications_count' do
|
||||||
expect(subject.pending_requests_count).to eq 1
|
expect(subject).to have_attributes(
|
||||||
end
|
pending_requests_count: 1,
|
||||||
|
pending_notifications_count: 2
|
||||||
it 'sets pending_notifications_count' do
|
)
|
||||||
expect(subject.pending_notifications_count).to eq 2
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue