diff --git a/app/javascript/mastodon/features/explore/suggestions.js b/app/javascript/mastodon/features/explore/suggestions.js
index c094a8d93..0c6a7ef8a 100644
--- a/app/javascript/mastodon/features/explore/suggestions.js
+++ b/app/javascript/mastodon/features/explore/suggestions.js
@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import Account from 'mastodon/containers/account_container';
+import AccountCard from 'mastodon/features/directory/components/account_card';
import LoadingIndicator from 'mastodon/components/loading_indicator';
import { connect } from 'react-redux';
import { fetchSuggestions } from 'mastodon/actions/suggestions';
@@ -29,9 +29,9 @@ class Suggestions extends React.PureComponent {
const { isLoading, suggestions } = this.props;
return (
-
- {isLoading ? (
) : suggestions.map(suggestion => (
-
+
+ {isLoading ?
: suggestions.map(suggestion => (
+
))}
);
diff --git a/app/javascript/mastodon/features/report/category.js b/app/javascript/mastodon/features/report/category.js
index 122b51c7c..a36dc81b1 100644
--- a/app/javascript/mastodon/features/report/category.js
+++ b/app/javascript/mastodon/features/report/category.js
@@ -8,7 +8,7 @@ const messages = defineMessages({
dislike: { id: 'report.reasons.dislike', defaultMessage: 'I don\'t like it' },
dislike_description: { id: 'report.reasons.dislike_description', defaultMessage: 'It is not something you want to see' },
spam: { id: 'report.reasons.spam', defaultMessage: 'It\'s spam' },
- spam_description: { id: 'report.reasons.spam_description', defaultMessage: 'Malicious links, fake engagement, or repetetive replies' },
+ spam_description: { id: 'report.reasons.spam_description', defaultMessage: 'Malicious links, fake engagement, or repetitive replies' },
violation: { id: 'report.reasons.violation', defaultMessage: 'It violates server rules' },
violation_description: { id: 'report.reasons.violation_description', defaultMessage: 'You are aware that it breaks specific rules' },
other: { id: 'report.reasons.other', defaultMessage: 'It\'s something else' },
diff --git a/app/javascript/mastodon/features/video/index.js b/app/javascript/mastodon/features/video/index.js
index 70e3cd6e8..8d47e479a 100644
--- a/app/javascript/mastodon/features/video/index.js
+++ b/app/javascript/mastodon/features/video/index.js
@@ -121,7 +121,7 @@ class Video extends React.PureComponent {
autoPlay: PropTypes.bool,
volume: PropTypes.number,
muted: PropTypes.bool,
- componetIndex: PropTypes.number,
+ componentIndex: PropTypes.number,
};
static defaultProps = {
@@ -502,7 +502,7 @@ class Video extends React.PureComponent {
startTime: this.video.currentTime,
autoPlay: !this.state.paused,
defaultVolume: this.state.volume,
- componetIndex: this.props.componetIndex,
+ componentIndex: this.props.componentIndex,
});
}
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 89fd20c3f..06c2b679c 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -414,7 +414,7 @@
"report.reasons.other": "It's something else",
"report.reasons.other_description": "The issue does not fit into other categories",
"report.reasons.spam": "It's spam",
- "report.reasons.spam_description": "Malicious links, fake engagement, or repetetive replies",
+ "report.reasons.spam_description": "Malicious links, fake engagement, or repetitive replies",
"report.reasons.violation": "It violates server rules",
"report.reasons.violation_description": "You are aware that it breaks specific rules",
"report.rules.subtitle": "Select all that apply",
diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss
index 8e6b0cdd5..eb6bdea99 100644
--- a/app/javascript/styles/mastodon-light/diff.scss
+++ b/app/javascript/styles/mastodon-light/diff.scss
@@ -40,19 +40,11 @@ html {
background: lighten($ui-base-color, 12%);
}
-.filter-form,
-.directory__card__bar {
+.filter-form {
background: $white;
border-bottom: 1px solid lighten($ui-base-color, 8%);
}
-.scrollable .directory__list {
- width: calc(100% + 2px);
- margin-left: -1px;
- margin-right: -1px;
-}
-
-.directory__card,
.table-of-contents {
border: 1px solid lighten($ui-base-color, 8%);
}
@@ -75,8 +67,7 @@ html {
.column-header__back-button,
.column-header__button,
.column-header__button.active,
-.account__header__bar,
-.directory__card__extra {
+.account__header__bar {
background: $white;
}
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index 73414785c..0873ac300 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -1236,6 +1236,11 @@ a.sparkline {
background: $ui-base-color;
border-radius: 4px;
+ &__permalink {
+ color: inherit;
+ text-decoration: none;
+ }
+
&__header {
padding: 4px;
border-radius: 4px;
@@ -1252,20 +1257,22 @@ a.sparkline {
}
&__title {
- margin-top: -25px;
+ margin-top: -(15px + 8px);
display: flex;
align-items: flex-end;
&__avatar {
- padding: 15px;
+ padding: 14px;
- img {
+ img,
+ .account__avatar {
display: block;
margin: 0;
width: 56px;
height: 56px;
- background: darken($ui-base-color, 8%);
+ background-color: darken($ui-base-color, 8%);
border-radius: 8px;
+ border: 1px solid $ui-base-color;
}
}
@@ -1273,30 +1280,34 @@ a.sparkline {
color: $darker-text-color;
padding-bottom: 15px;
font-size: 15px;
+ line-height: 20px;
bdi {
display: block;
color: $primary-text-color;
- font-weight: 500;
+ font-weight: 700;
}
}
}
&__bio {
padding: 0 15px;
+ margin: 8px 0;
overflow: hidden;
text-overflow: ellipsis;
word-wrap: break-word;
- max-height: 18px * 2;
+ max-height: 21px * 2;
position: relative;
+ font-size: 15px;
+ line-height: 21px;
&::after {
display: block;
content: "";
width: 50px;
- height: 18px;
+ height: 21px;
position: absolute;
- bottom: 0;
+ bottom: 8px;
right: 15px;
background: linear-gradient(to left, $ui-base-color, transparent);
pointer-events: none;
@@ -1309,10 +1320,6 @@ a.sparkline {
&:hover {
text-decoration: underline;
-
- .fa {
- color: lighten($dark-text-color, 7%);
- }
}
&.mention {
@@ -1329,12 +1336,21 @@ a.sparkline {
&__actions {
display: flex;
+ justify-content: space-between;
align-items: center;
- padding-top: 10px;
&__button {
- flex: 0 0 auto;
+ flex-shrink: 1;
padding: 0 15px;
+ overflow: hidden;
+
+ .button {
+ min-width: 0;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ max-width: 100%;
+ }
}
}
@@ -1343,19 +1359,23 @@ a.sparkline {
display: grid;
grid-auto-columns: minmax(0, 1fr);
grid-auto-flow: column;
+ max-width: 340px;
+ min-width: 65px * 3;
&__item {
- padding: 15px;
+ padding: 15px 0;
text-align: center;
color: $primary-text-color;
font-weight: 600;
font-size: 15px;
+ line-height: 21px;
small {
display: block;
color: $darker-text-color;
font-weight: 400;
font-size: 13px;
+ line-height: 18px;
}
}
}
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 8f6d4b69a..d1d679ac2 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -50,7 +50,7 @@
cursor: pointer;
display: inline-block;
font-family: inherit;
- font-size: 17px;
+ font-size: 15px;
font-weight: 500;
letter-spacing: 0;
line-height: 22px;
@@ -2333,17 +2333,7 @@ a.account__display-name {
padding: 0;
}
- .directory__list {
- display: grid;
- grid-gap: 10px;
- grid-template-columns: minmax(0, 50%) minmax(0, 50%);
-
- @media screen and (max-width: $no-gap-breakpoint) {
- display: block;
- }
- }
-
- .directory__card {
+ .account-card {
margin-bottom: 0;
}
@@ -4315,7 +4305,7 @@ a.status-card.compact:hover {
}
}
-.upload-progess__message {
+.upload-progress__message {
flex: 1 1 auto;
}
@@ -6219,136 +6209,20 @@ a.status-card.compact:hover {
}
}
-.directory {
- &__list {
- width: 100%;
- margin: 10px 0;
- transition: opacity 100ms ease-in;
+.scrollable .account-card {
+ margin: 10px;
+ background: lighten($ui-base-color, 8%);
+}
- &.loading {
- opacity: 0.7;
- }
-
- @media screen and (max-width: $no-gap-breakpoint) {
- margin: 0;
- }
+.scrollable .account-card__title__avatar {
+ img,
+ .account__avatar {
+ border-color: lighten($ui-base-color, 8%);
}
+}
- &__card {
- box-sizing: border-box;
- margin-bottom: 10px;
-
- &__img {
- height: 125px;
- position: relative;
- background: darken($ui-base-color, 12%);
- overflow: hidden;
-
- img {
- display: block;
- width: 100%;
- height: 100%;
- margin: 0;
- object-fit: cover;
- }
- }
-
- &__bar {
- display: flex;
- align-items: center;
- background: lighten($ui-base-color, 4%);
- padding: 10px;
-
- &__name {
- flex: 1 1 auto;
- display: flex;
- align-items: center;
- text-decoration: none;
- overflow: hidden;
- }
-
- &__relationship {
- width: 23px;
- min-height: 1px;
- flex: 0 0 auto;
- }
-
- .avatar {
- flex: 0 0 auto;
- width: 48px;
- height: 48px;
- padding-top: 2px;
-
- img {
- width: 100%;
- height: 100%;
- display: block;
- margin: 0;
- border-radius: 4px;
- background: darken($ui-base-color, 8%);
- object-fit: cover;
- }
- }
-
- .display-name {
- margin-left: 15px;
- text-align: left;
-
- strong {
- font-size: 15px;
- color: $primary-text-color;
- font-weight: 500;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- span {
- display: block;
- font-size: 14px;
- color: $darker-text-color;
- font-weight: 400;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- }
- }
-
- &__extra {
- background: $ui-base-color;
- display: flex;
- align-items: center;
- justify-content: center;
-
- .accounts-table__count {
- width: 33.33%;
- flex: 0 0 auto;
- padding: 15px 0;
- }
-
- .account__header__content {
- box-sizing: border-box;
- padding: 15px 10px;
- border-bottom: 1px solid lighten($ui-base-color, 8%);
- width: 100%;
- min-height: 18px + 30px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-
- p {
- display: none;
-
- &:first-child {
- display: inline;
- }
- }
-
- br {
- display: none;
- }
- }
- }
- }
+.scrollable .account-card__bio::after {
+ background: linear-gradient(to left, lighten($ui-base-color, 8%), transparent);
}
.account-gallery__container {
@@ -6452,6 +6326,7 @@ a.status-card.compact:hover {
&__column {
padding: 10px 15px;
+ padding-bottom: 0;
}
.radio-button {
diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss
index a180df437..81459f5ba 100644
--- a/app/javascript/styles/mastodon/containers.scss
+++ b/app/javascript/styles/mastodon/containers.scss
@@ -409,14 +409,6 @@
}
}
- .directory__card {
- border-radius: 4px;
-
- @media screen and (max-width: $no-gap-breakpoint) {
- border-radius: 0;
- }
- }
-
.page-header {
@media screen and (max-width: $no-gap-breakpoint) {
border-bottom: 0;
@@ -835,19 +827,21 @@
grid-gap: 10px;
grid-template-columns: minmax(0, 50%) minmax(0, 50%);
+ .account-card {
+ display: flex;
+ flex-direction: column;
+ }
+
@media screen and (max-width: $no-gap-breakpoint) {
display: block;
- }
- .icon-button {
- font-size: 18px;
+ .account-card {
+ margin-bottom: 10px;
+ display: block;
+ }
}
}
- .directory__card {
- margin-bottom: 0;
- }
-
.card-grid {
display: flex;
flex-wrap: wrap;
diff --git a/app/javascript/styles/mastodon/polls.scss b/app/javascript/styles/mastodon/polls.scss
index e33fc7983..a719044ea 100644
--- a/app/javascript/styles/mastodon/polls.scss
+++ b/app/javascript/styles/mastodon/polls.scss
@@ -69,7 +69,7 @@
display: none;
}
- .autossugest-input {
+ .autosuggest-input {
flex: 1 1 auto;
}
diff --git a/app/javascript/styles/mastodon/rtl.scss b/app/javascript/styles/mastodon/rtl.scss
index ea7bb5113..98eb1511c 100644
--- a/app/javascript/styles/mastodon/rtl.scss
+++ b/app/javascript/styles/mastodon/rtl.scss
@@ -12,11 +12,6 @@ body.rtl {
margin-left: 10px;
}
- .directory__card__bar .display-name {
- margin-left: 0;
- margin-right: 15px;
- }
-
.display-name,
.announcements__item {
text-align: right;
diff --git a/app/lib/user_settings_decorator.rb b/app/lib/user_settings_decorator.rb
index 581101782..d8015e50d 100644
--- a/app/lib/user_settings_decorator.rb
+++ b/app/lib/user_settings_decorator.rb
@@ -32,7 +32,6 @@ class UserSettingsDecorator
user.settings['system_font_ui'] = system_font_ui_preference if change?('setting_system_font_ui')
user.settings['system_emoji_font'] = system_emoji_font_preference if change?('setting_system_emoji_font')
user.settings['noindex'] = noindex_preference if change?('setting_noindex')
- user.settings['hide_followers_count']= hide_followers_count_preference if change?('setting_hide_followers_count')
user.settings['flavour'] = flavour_preference if change?('setting_flavour')
user.settings['skin'] = skin_preference if change?('setting_skin')
user.settings['hide_network'] = hide_network_preference if change?('setting_hide_network')
@@ -110,10 +109,6 @@ class UserSettingsDecorator
boolean_cast_setting 'setting_noindex'
end
- def hide_followers_count_preference
- boolean_cast_setting 'setting_hide_followers_count'
- end
-
def flavour_preference
settings['setting_flavour']
end
diff --git a/app/models/account.rb b/app/models/account.rb
index 8617b389c..dfbe0b8bc 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -351,11 +351,11 @@ class Account < ApplicationRecord
end
def hides_followers?
- hide_collections? || user_hides_network?
+ hide_collections?
end
def hides_following?
- hide_collections? || user_hides_network?
+ hide_collections?
end
def object_type
diff --git a/app/models/account_statuses_filter.rb b/app/models/account_statuses_filter.rb
new file mode 100644
index 000000000..556aee032
--- /dev/null
+++ b/app/models/account_statuses_filter.rb
@@ -0,0 +1,134 @@
+# frozen_string_literal: true
+
+class AccountStatusesFilter
+ KEYS = %i(
+ pinned
+ tagged
+ only_media
+ exclude_replies
+ exclude_reblogs
+ ).freeze
+
+ attr_reader :params, :account, :current_account
+
+ def initialize(account, current_account, params = {})
+ @account = account
+ @current_account = current_account
+ @params = params
+ end
+
+ def results
+ scope = initial_scope
+
+ scope.merge!(pinned_scope) if pinned?
+ scope.merge!(only_media_scope) if only_media?
+ scope.merge!(no_replies_scope) if exclude_replies?
+ scope.merge!(no_reblogs_scope) if exclude_reblogs?
+ scope.merge!(hashtag_scope) if tagged?
+
+ scope
+ end
+
+ private
+
+ def initial_scope
+ if suspended?
+ Status.none
+ elsif anonymous?
+ account.statuses.not_local_only.where(visibility: %i(public unlisted))
+ elsif author?
+ account.statuses.all # NOTE: #merge! does not work without the #all
+ elsif blocked?
+ Status.none
+ else
+ filtered_scope
+ end
+ end
+
+ def filtered_scope
+ scope = account.statuses.left_outer_joins(:mentions)
+
+ scope.merge!(scope.where(visibility: follower? ? %i(public unlisted private) : %i(public unlisted)).or(scope.where(mentions: { account_id: current_account.id })).group(Status.arel_table[:id]))
+ scope.merge!(filtered_reblogs_scope) if reblogs_may_occur?
+
+ scope
+ end
+
+ def filtered_reblogs_scope
+ Status.left_outer_joins(:reblog).where(reblog_of_id: nil).or(Status.where.not(reblogs_statuses: { account_id: current_account.excluded_from_timeline_account_ids }))
+ end
+
+ def only_media_scope
+ Status.joins(:media_attachments).merge(account.media_attachments.reorder(nil)).group(Status.arel_table[:id])
+ end
+
+ def no_replies_scope
+ Status.without_replies
+ end
+
+ def no_reblogs_scope
+ Status.without_reblogs
+ end
+
+ def pinned_scope
+ account.pinned_statuses.group(Status.arel_table[:id], StatusPin.arel_table[:created_at])
+ end
+
+ def hashtag_scope
+ tag = Tag.find_normalized(params[:tagged])
+
+ if tag
+ Status.tagged_with(tag.id)
+ else
+ Status.none
+ end
+ end
+
+ def suspended?
+ account.suspended?
+ end
+
+ def anonymous?
+ current_account.nil?
+ end
+
+ def author?
+ current_account.id == account.id
+ end
+
+ def blocked?
+ account.blocking?(current_account) || (current_account.domain.present? && account.domain_blocking?(current_account.domain))
+ end
+
+ def follower?
+ current_account.following?(account)
+ end
+
+ def reblogs_may_occur?
+ !exclude_reblogs? && !only_media? && !tagged?
+ end
+
+ def pinned?
+ truthy_param?(:pinned)
+ end
+
+ def only_media?
+ truthy_param?(:only_media)
+ end
+
+ def exclude_replies?
+ truthy_param?(:exclude_replies)
+ end
+
+ def exclude_reblogs?
+ truthy_param?(:exclude_reblogs)
+ end
+
+ def tagged?
+ params[:tagged].present?
+ end
+
+ def truthy_param?(key)
+ ActiveModel::Type::Boolean.new.cast(params[key])
+ end
+end
diff --git a/app/models/report.rb b/app/models/report.rb
index 3dd8a6fdd..8ba2dd8fd 100644
--- a/app/models/report.rb
+++ b/app/models/report.rb
@@ -129,6 +129,6 @@ class Report < ApplicationRecord
def validate_rule_ids
return unless violation?
- errors.add(:rule_ids, I18n.t('reports.errors.invalid_rules')) unless rules.size == rule_ids.size
+ errors.add(:rule_ids, I18n.t('reports.errors.invalid_rules')) unless rules.size == rule_ids&.size
end
end
diff --git a/app/models/status.rb b/app/models/status.rb
index 06141f410..6a848baee 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -399,28 +399,6 @@ class Status < ApplicationRecord
end
end
- def permitted_for(target_account, account)
- visibility = [:public, :unlisted]
-
- if account.nil?
- where(visibility: visibility).not_local_only
- elsif target_account.blocking?(account) || (account.domain.present? && target_account.domain_blocking?(account.domain)) # get rid of blocked peeps
- none
- elsif account.id == target_account.id # author can see own stuff
- all
- else
- # followers can see followers-only stuff, but also things they are mentioned in.
- # non-followers can see everything that isn't private/direct, but can see stuff they are mentioned in.
- visibility.push(:private) if account.following?(target_account)
-
- scope = left_outer_joins(:reblog)
-
- scope.where(visibility: visibility)
- .or(scope.where(id: account.mentions.select(:status_id)))
- .merge(scope.where(reblog_of_id: nil).or(scope.where.not(reblogs_statuses: { account_id: account.excluded_from_timeline_account_ids })))
- end
- end
-
def from_text(text)
return [] if text.blank?
diff --git a/app/models/user.rb b/app/models/user.rb
index 77685ad02..f657f1b27 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -126,7 +126,7 @@ class User < ApplicationRecord
has_many :session_activations, dependent: :destroy
delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :favourite_modal, :delete_modal,
- :reduce_motion, :system_font_ui, :noindex, :flavour, :skin, :display_media, :hide_network, :hide_followers_count,
+ :reduce_motion, :system_font_ui, :noindex, :flavour, :skin, :display_media, :hide_followers_count,
:expand_spoilers, :default_language, :aggregate_reblogs, :show_application,
:advanced_layout, :use_blurhash, :use_pending_items, :trends, :crop_images,
:disable_swiping, :default_content_type, :system_emoji_font,
@@ -281,10 +281,6 @@ class User < ApplicationRecord
settings.notification_emails['trending_status']
end
- def hides_network?
- @hides_network ||= settings.hide_network
- end
-
def aggregates_reblogs?
@aggregates_reblogs ||= settings.aggregate_reblogs
end
diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb
index 6695a0ddf..92e2c4f4b 100644
--- a/app/policies/user_policy.rb
+++ b/app/policies/user_policy.rb
@@ -42,7 +42,7 @@ class UserPolicy < ApplicationPolicy
end
def promote?
- admin? && promoteable?
+ admin? && promotable?
end
def demote?
@@ -51,7 +51,7 @@ class UserPolicy < ApplicationPolicy
private
- def promoteable?
+ def promotable?
record.approved? && (!record.staff? || !record.admin?)
end
diff --git a/app/presenters/familiar_followers_presenter.rb b/app/presenters/familiar_followers_presenter.rb
new file mode 100644
index 000000000..c1d944b80
--- /dev/null
+++ b/app/presenters/familiar_followers_presenter.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class FamiliarFollowersPresenter
+ class Result < ActiveModelSerializers::Model
+ attributes :id, :accounts
+ end
+
+ def initialize(accounts, current_account_id)
+ @accounts = accounts
+ @current_account_id = current_account_id
+ end
+
+ def accounts
+ map = Follow.includes(account: :account_stat).where(target_account_id: @accounts.map(&:id)).where(account_id: Follow.where(account_id: @current_account_id).joins(:target_account).merge(Account.where(hide_collections: [nil, false])).select(:target_account_id)).group_by(&:target_account_id)
+ @accounts.map { |account| Result.new(id: account.id, accounts: (account.hide_collections? ? [] : (map[account.id] || [])).map(&:account)) }
+ end
+end
diff --git a/app/serializers/rest/familiar_followers_serializer.rb b/app/serializers/rest/familiar_followers_serializer.rb
new file mode 100644
index 000000000..0a7e923f8
--- /dev/null
+++ b/app/serializers/rest/familiar_followers_serializer.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class REST::FamiliarFollowersSerializer < ActiveModel::Serializer
+ attribute :id
+
+ has_many :accounts, serializer: REST::AccountSerializer
+
+ def id
+ object.id.to_s
+ end
+end
diff --git a/app/views/about/_registration.html.haml b/app/views/about/_registration.html.haml
index b452e4936..5db620b2d 100644
--- a/app/views/about/_registration.html.haml
+++ b/app/views/about/_registration.html.haml
@@ -10,8 +10,8 @@
= account_fields.input :username, wrapper: :with_label, label: false, required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off', placeholder: t('simple_form.labels.defaults.username'), pattern: '[a-zA-Z0-9_]+', maxlength: 30 }, append: "@#{site_hostname}", hint: false, disabled: disabled
= f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' }, hint: false, disabled: disabled
- = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off', :minlength => User.password_length.first, :maxlength => User.password_length.last }, hint: false, disabled: disabled
- = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' }, hint: false, disabled: disabled
+ = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'new-password', :minlength => User.password_length.first, :maxlength => User.password_length.last }, hint: false, disabled: disabled
+ = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'new-password' }, hint: false, disabled: disabled
= f.input :confirm_password, as: :string, placeholder: t('simple_form.labels.defaults.honeypot', label: t('simple_form.labels.defaults.password')), required: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.honeypot', label: t('simple_form.labels.defaults.password')), :autocomplete => 'off' }, hint: false, disabled: disabled
= f.input :website, as: :url, placeholder: t('simple_form.labels.defaults.honeypot', label: 'Website'), required: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.honeypot', label: 'Website'), :autocomplete => 'off' }, hint: false, disabled: disabled
diff --git a/app/views/directories/index.html.haml b/app/views/directories/index.html.haml
index d5509f946..2ac700fe6 100644
--- a/app/views/directories/index.html.haml
+++ b/app/views/directories/index.html.haml
@@ -19,37 +19,36 @@
- else
.directory__list
- @accounts.each do |account|
- .directory__card
- .directory__card__img
- = image_tag account.header.url, alt: ''
- .directory__card__bar
- = link_to TagManager.instance.url_for(account), class: 'directory__card__bar__name' do
- .avatar
- = image_tag account.avatar.url, alt: '', class: 'u-photo'
-
+ .account-card
+ = link_to TagManager.instance.url_for(account), class: 'account-card__permalink' do
+ .account-card__header
+ = image_tag account.header.url, alt: ''
+ .account-card__title
+ .account-card__title__avatar
+ = image_tag account.avatar.url, alt: ''
.display-name
%bdi
%strong.emojify.p-name= display_name(account, custom_emojify: true)
- %span= acct(account)
- .directory__card__bar__relationship.account__relationship
- = minimal_account_action_button(account)
-
- .directory__card__extra
- .account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true)
-
- .directory__card__extra
- .accounts-table__count
- = friendly_number_to_human account.statuses_count
- %small= t('accounts.posts', count: account.statuses_count).downcase
- .accounts-table__count
- = hide_followers_count?(account) ? '-' : (friendly_number_to_human account.followers_count)
- %small= t('accounts.followers', count: account.followers_count).downcase
- .accounts-table__count
- - if account.last_status_at.present?
- %time.time-ago{ datetime: account.last_status_at.to_date.iso8601, title: l(account.last_status_at.to_date) }= l account.last_status_at.to_date
- - else
- = t('accounts.never_active')
-
- %small= t('accounts.last_active')
+ %span
+ = acct(account)
+ = fa_icon('lock') if account.locked?
+ - if account.note.present?
+ .account-card__bio.emojify
+ = Formatter.instance.simplified_format(account, custom_emojify: true)
+ - else
+ .flex-spacer
+ .account-card__actions
+ .account-card__counters
+ .account-card__counters__item
+ = friendly_number_to_human account.statuses_count
+ %small= t('accounts.posts', count: account.statuses_count).downcase
+ .account-card__counters__item
+ = hide_followers_count?(account) ? '-' : (friendly_number_to_human account.followers_count)
+ %small= t('accounts.followers', count: account.followers_count).downcase
+ .account-card__counters__item
+ = friendly_number_to_human account.following_count
+ %small= t('accounts.following', count: account.following_count).downcase
+ .account-card__actions__button
+ = account_action_button(account)
= paginate @accounts
diff --git a/app/views/follower_accounts/index.html.haml b/app/views/follower_accounts/index.html.haml
index 645dd2de1..92de35a9f 100644
--- a/app/views/follower_accounts/index.html.haml
+++ b/app/views/follower_accounts/index.html.haml
@@ -7,7 +7,7 @@
= render 'accounts/header', account: @account
-- if @account.user_hides_network?
+- if @account.hide_collections?
.nothing-here= t('accounts.network_hidden')
- elsif user_signed_in? && @account.blocking?(current_account)
.nothing-here= t('accounts.unavailable')
diff --git a/app/views/following_accounts/index.html.haml b/app/views/following_accounts/index.html.haml
index 17fe79018..9bb1a9edd 100644
--- a/app/views/following_accounts/index.html.haml
+++ b/app/views/following_accounts/index.html.haml
@@ -7,7 +7,7 @@
= render 'accounts/header', account: @account
-- if @account.user_hides_network?
+- if @account.hide_collections?
.nothing-here= t('accounts.network_hidden')
- elsif user_signed_in? && @account.blocking?(current_account)
.nothing-here= t('accounts.unavailable')
diff --git a/app/views/settings/preferences/other/show.html.haml b/app/views/settings/preferences/other/show.html.haml
index 8159efb55..cf604d043 100644
--- a/app/views/settings/preferences/other/show.html.haml
+++ b/app/views/settings/preferences/other/show.html.haml
@@ -10,9 +10,6 @@
.fields-group
= f.input :setting_noindex, as: :boolean, wrapper: :with_label
- .fields-group
- = f.input :setting_hide_network, as: :boolean, wrapper: :with_label
-
.fields-group
= f.input :setting_aggregate_reblogs, as: :boolean, wrapper: :with_label, recommended: true
diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml
index 21948b200..b84d06c27 100644
--- a/app/views/settings/profiles/show.html.haml
+++ b/app/views/settings/profiles/show.html.haml
@@ -30,7 +30,10 @@
= f.input :bot, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.bot')
.fields-group
- = f.input :discoverable, as: :boolean, wrapper: :with_label, hint: t(Setting.profile_directory ? 'simple_form.hints.defaults.discoverable' : 'simple_form.hints.defaults.discoverable_no_directory'), recommended: true
+ = f.input :discoverable, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.discoverable'), recommended: true
+
+ .fields-group
+ = f.input :hide_collections, as: :boolean, wrapper: :with_label, label: t('simple_form.labels.defaults.setting_hide_network'), hint: t('simple_form.hints.defaults.setting_hide_network')
%hr.spacer/
diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb
index 19d59f155..1a041ad48 100644
--- a/config/initializers/omniauth.rb
+++ b/config/initializers/omniauth.rb
@@ -1,5 +1,5 @@
Rails.application.config.middleware.use OmniAuth::Builder do
- # Vanilla omniauth stategies
+ # Vanilla omniauth strategies
end
Devise.setup do |config|
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 2367d5ded..1964285b0 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -72,7 +72,6 @@ en:
media: Media
moved_html: "%{name} has moved to %{new_profile_link}:"
network_hidden: This information is not available
- never_active: Never
nothing_here: There is nothing here!
people_followed_by: People whom %{name} follows
people_who_follow: People who follow %{name}
diff --git a/config/locales/en_GB.yml b/config/locales/en_GB.yml
index d3461474b..de74e0f61 100644
--- a/config/locales/en_GB.yml
+++ b/config/locales/en_GB.yml
@@ -986,7 +986,7 @@ en_GB:
enabled: Two-factor authentication is enabled
enabled_success: Two-factor authentication successfully enabled
generate_recovery_codes: Generate recovery codes
- instructions_html: "
Scan this QR code into Google Authenticator or a similiar TOTP app on your phone. From now on, that app will generate tokens that you will have to enter when logging in."
+ instructions_html: "
Scan this QR code into Google Authenticator or a similar TOTP app on your phone. From now on, that app will generate tokens that you will have to enter when logging in."
lost_recovery_codes: Recovery codes allow you to regain access to your account if you lose your phone. If you've lost your recovery codes, you can regenerate them here. Your old recovery codes will be invalidated.
manual_instructions: 'If you can''t scan the QR code and need to enter it manually, here is the plain-text secret:'
recovery_codes: Backup recovery codes
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index c5e75b408..b19b7891f 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -37,8 +37,7 @@ en:
current_password: For security purposes please enter the password of the current account
current_username: To confirm, please enter the username of the current account
digest: Only sent after a long period of inactivity and only if you have received any personal messages in your absence
- discoverable: Allow your account to be discovered by strangers through recommendations, profile directory and other features
- discoverable_no_directory: Allow your account to be discovered by strangers through recommendations and other features
+ discoverable: Allow your account to be discovered by strangers through recommendations, trends and other features
email: You will be sent a confirmation e-mail
fields: You can have up to 4 items displayed as a table on your profile
header: PNG, GIF or JPG. At most %{size}. Will be downscaled to %{dimensions}px
diff --git a/config/routes.rb b/config/routes.rb
index 167502c33..3449fa03d 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -498,6 +498,7 @@ Rails.application.routes.draw do
resource :search, only: :show, controller: :search
resource :lookup, only: :show, controller: :lookup
resources :relationships, only: :index
+ resources :familiar_followers, only: :index
end
resources :accounts, only: [:create, :show] do
diff --git a/config/settings.yml b/config/settings.yml
index 1f366d7d1..51016d322 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -17,7 +17,6 @@ defaults: &defaults
min_invite_role: 'admin'
show_staff_badge: true
default_sensitive: false
- hide_network: false
unfollow_modal: false
boost_modal: false
favourite_modal: false
diff --git a/db/migrate/20170920032311_fix_reblogs_in_feeds.rb b/db/migrate/20170920032311_fix_reblogs_in_feeds.rb
index bcd4b9137..4ab68e8f3 100644
--- a/db/migrate/20170920032311_fix_reblogs_in_feeds.rb
+++ b/db/migrate/20170920032311_fix_reblogs_in_feeds.rb
@@ -16,7 +16,7 @@ class FixReblogsInFeeds < ActiveRecord::Migration[5.1]
# is once again set to the reblogging status' ID, and the value
# is set to the reblogged status' ID). This is safe for Redis'
# float conversion because in this reblog tracking zset, we only
- # need the rebloggging status' ID to be able to stop tracking
+ # need the reblogging status' ID to be able to stop tracking
# entries after they have gotten too far down the feed, which
# does not require an exact value.
diff --git a/db/migrate/20180608213548_reject_following_blocked_users.rb b/db/migrate/20180608213548_reject_following_blocked_users.rb
index 302db6b68..78f8df538 100644
--- a/db/migrate/20180608213548_reject_following_blocked_users.rb
+++ b/db/migrate/20180608213548_reject_following_blocked_users.rb
@@ -22,13 +22,13 @@ class RejectFollowingBlockedUsers < ActiveRecord::Migration[5.2]
follows.each do |follow|
blocked_account = follow.account
- followed_acccount = follow.target_account
+ followed_account = follow.target_account
next follow.destroy! if blocked_account.local?
- reject_follow_json = Oj.dump(ActivityPub::LinkedDataSignature.new(ActiveModelSerializers::SerializableResource.new(follow, serializer: ActivityPub::RejectFollowSerializer, adapter: ActivityPub::Adapter).as_json).sign!(followed_acccount))
+ reject_follow_json = Oj.dump(ActivityPub::LinkedDataSignature.new(ActiveModelSerializers::SerializableResource.new(follow, serializer: ActivityPub::RejectFollowSerializer, adapter: ActivityPub::Adapter).as_json).sign!(followed_account))
- ActivityPub::DeliveryWorker.perform_async(reject_follow_json, followed_acccount, blocked_account.inbox_url)
+ ActivityPub::DeliveryWorker.perform_async(reject_follow_json, followed_account, blocked_account.inbox_url)
follow.destroy!
end
diff --git a/db/migrate/20220304195405_migrate_hide_network_preference.rb b/db/migrate/20220304195405_migrate_hide_network_preference.rb
new file mode 100644
index 000000000..102ee46d6
--- /dev/null
+++ b/db/migrate/20220304195405_migrate_hide_network_preference.rb
@@ -0,0 +1,37 @@
+class MigrateHideNetworkPreference < ActiveRecord::Migration[6.1]
+ disable_ddl_transaction!
+
+ # Dummy classes, to make migration possible across version changes
+ class Account < ApplicationRecord
+ has_one :user, inverse_of: :account
+ scope :local, -> { where(domain: nil) }
+ end
+
+ class User < ApplicationRecord
+ belongs_to :account
+ end
+
+ def up
+ Account.reset_column_information
+
+ Setting.unscoped.where(thing_type: 'User', var: 'hide_network').find_each do |setting|
+ account = User.find(setting.thing_id).account
+
+ ApplicationRecord.transaction do
+ account.update(hide_collections: setting.value)
+ setting.delete
+ end
+ rescue ActiveRecord::RecordNotFound
+ next
+ end
+ end
+
+ def down
+ Account.local.where(hide_collections: true).includes(:user).find_each do |account|
+ ApplicationRecord.transaction do
+ Setting.create(thing_type: 'User', thing_id: account.user.id, var: 'hide_network', value: account.hide_collections?)
+ account.update(hide_collections: nil)
+ end
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 36960e84a..f8d85901d 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: 2022_02_27_041951) do
+ActiveRecord::Schema.define(version: 2022_03_04_195405) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
diff --git a/lib/mastodon/emoji_cli.rb b/lib/mastodon/emoji_cli.rb
index 5bee70ea5..a3e947909 100644
--- a/lib/mastodon/emoji_cli.rb
+++ b/lib/mastodon/emoji_cli.rb
@@ -41,7 +41,7 @@ module Mastodon
Gem::Package::TarReader.new(Zlib::GzipReader.open(path)) do |tar|
tar.each do |entry|
- next unless entry.file? && entry.full_name.end_with?('.png')
+ next unless entry.file? && entry.full_name.end_with?('.png', '.gif')
filename = File.basename(entry.full_name, '.*')
diff --git a/lib/mastodon/maintenance_cli.rb b/lib/mastodon/maintenance_cli.rb
index 00861df77..1e047d96c 100644
--- a/lib/mastodon/maintenance_cli.rb
+++ b/lib/mastodon/maintenance_cli.rb
@@ -510,7 +510,7 @@ module Mastodon
accounts = accounts.sort_by(&:id).reverse
@prompt.warn "Multiple local accounts were found for username '#{accounts.first.username}'."
- @prompt.warn 'All those accounts are distinct accounts but only the most recently-created one is fully-functionnal.'
+ @prompt.warn 'All those accounts are distinct accounts but only the most recently-created one is fully-functional.'
accounts.each_with_index do |account, idx|
@prompt.say '%2d. %s: created at: %s; updated at: %s; last logged in at: %s; statuses: %5d; last status at: %s' % [idx, account.username, account.created_at, account.updated_at, account.user&.last_sign_in_at&.to_s || 'N/A', account.account_stat&.statuses_count || 0, account.account_stat&.last_status_at || 'N/A']
diff --git a/lib/mastodon/statuses_cli.rb b/lib/mastodon/statuses_cli.rb
index 91b08813b..d4c2e6cf2 100644
--- a/lib/mastodon/statuses_cli.rb
+++ b/lib/mastodon/statuses_cli.rb
@@ -156,7 +156,7 @@ module Mastodon
ActiveRecord::Base.connection.add_index(:statuses, :conversation_id, name: :index_statuses_conversation_id, algorithm: :concurrently, if_not_exists: true)
- say('Extract the deletion target from coversations... This might take a while...')
+ say('Extract the deletion target from conversations... This might take a while...')
ActiveRecord::Base.connection.create_table('conversations_to_be_deleted', force: true)
diff --git a/lib/tasks/tests.rake b/lib/tasks/tests.rake
index 0f38b50e3..8082f32fb 100644
--- a/lib/tasks/tests.rake
+++ b/lib/tasks/tests.rake
@@ -2,6 +2,50 @@
namespace :tests do
namespace :migrations do
+ desc 'Check that database state is consistent with a successful migration from populated data'
+ task check_database: :environment do
+ unless Account.find_by(username: 'admin', domain: nil)&.hide_collections? == false
+ puts 'Unexpected value for Account#hide_collections? for user @admin'
+ exit(1)
+ end
+
+ unless Account.find_by(username: 'user', domain: nil)&.hide_collections? == true
+ puts 'Unexpected value for Account#hide_collections? for user @user'
+ exit(1)
+ end
+
+ unless Account.find_by(username: 'evil', domain: 'activitypub.com')&.suspended?
+ puts 'Unexpected value for Account#suspended? for user @evil@activitypub.com'
+ exit(1)
+ end
+
+ unless Status.find(6).account_id == Status.find(7).account_id
+ puts 'Users @remote@remote.com and @Remote@remote.com not properly merged'
+ exit(1)
+ end
+
+ if Account.where(domain: Rails.configuration.x.local_domain).exists?
+ puts 'Faux remote accounts not properly claned up'
+ exit(1)
+ end
+
+ unless AccountConversation.first&.last_status_id == 11
+ puts 'AccountConversation records not created as expected'
+ exit(1)
+ end
+ end
+
+ desc 'Populate the database with test data for 2.4.0'
+ task populate_v2_4: :environment do
+ ActiveRecord::Base.connection.execute(<<~SQL)
+ INSERT INTO "settings"
+ (id, thing_type, thing_id, var, value, created_at, updated_at)
+ VALUES
+ (1, 'User', 1, 'hide_network', E'--- false\n', now(), now()),
+ (2, 'User', 2, 'hide_network', E'--- true\n', now(), now());
+ SQL
+ end
+
desc 'Populate the database with test data for 2.0.0'
task populate_v2: :environment do
admin_key = OpenSSL::PKey::RSA.new(2048)
@@ -34,7 +78,7 @@ namespace :tests do
'https://remote.com/@remote', 'https://remote.com/salmon/1'),
(4, 'Remote', 'remote.com', NULL, #{remote_public_key}, now(), now(),
'https://remote.com/@Remote', 'https://remote.com/salmon/1'),
- (5, 'REMOTE', 'Remote.com', NULL, #{remote_public_key2}, now(), now(),
+ (5, 'REMOTE', 'Remote.com', NULL, #{remote_public_key2}, now() - interval '1 year', now() - interval '1 year',
'https://remote.com/stale/@REMOTE', 'https://remote.com/stale/salmon/1');
INSERT INTO "accounts"
@@ -49,6 +93,13 @@ namespace :tests do
(7, 'user', #{local_domain}, #{user_private_key}, #{user_public_key}, now(), now()),
(8, 'pt_user', NULL, #{user_private_key}, #{user_public_key}, now(), now());
+ INSERT INTO "accounts"
+ (id, username, domain, private_key, public_key, created_at, updated_at, protocol, inbox_url, outbox_url, followers_url, suspended)
+ VALUES
+ (9, 'evil', 'activitypub.com', NULL, #{remote_public_key_ap}, now(), now(),
+ 1, 'https://activitypub.com/users/evil/inbox', 'https://activitypub.com/users/evil/outbox',
+ 'https://activitypub.com/users/evil/followers', true);
+
-- users
INSERT INTO "users"
@@ -62,6 +113,9 @@ namespace :tests do
VALUES
(3, 7, 'ptuser@localhost', now(), now(), false, 'pt');
+ -- conversations
+ INSERT INTO "conversations" (id, created_at, updated_at) VALUES (1, now(), now());
+
-- statuses
INSERT INTO "statuses"
@@ -97,14 +151,22 @@ namespace :tests do
VALUES
(9, 1, 2, now(), now());
+ INSERT INTO "statuses"
+ (id, account_id, text, in_reply_to_id, conversation_id, visibility, created_at, updated_at)
+ VALUES
+ (10, 2, '@admin hey!', NULL, 1, 3, now(), now()),
+ (11, 1, '@user hey!', 10, 1, 3, now(), now());
+
-- mentions (from previous statuses)
INSERT INTO "mentions"
- (status_id, account_id, created_at, updated_at)
+ (id, status_id, account_id, created_at, updated_at)
VALUES
- (2, 3, now(), now()),
- (3, 4, now(), now()),
- (4, 5, now(), now());
+ (1, 2, 3, now(), now()),
+ (2, 3, 4, now(), now()),
+ (3, 4, 5, now(), now()),
+ (4, 10, 1, now(), now()),
+ (5, 11, 2, now(), now());
-- stream entries
@@ -121,7 +183,6 @@ namespace :tests do
(8, 5, 'status', now(), now()),
(9, 1, 'status', now(), now());
-
-- custom emoji
INSERT INTO "custom_emojis"
@@ -161,12 +222,12 @@ namespace :tests do
-- follows
INSERT INTO "follows"
- (account_id, target_account_id, created_at, updated_at)
+ (id, account_id, target_account_id, created_at, updated_at)
VALUES
- (1, 5, now(), now()),
- (6, 2, now(), now()),
- (5, 2, now(), now()),
- (6, 1, now(), now());
+ (1, 1, 5, now(), now()),
+ (2, 6, 2, now(), now()),
+ (3, 5, 2, now(), now()),
+ (4, 6, 1, now(), now());
-- follow requests
@@ -175,6 +236,15 @@ namespace :tests do
VALUES
(2, 5, now(), now()),
(5, 1, now(), now());
+
+ -- notifications
+
+ INSERT INTO "notifications"
+ (id, from_account_id, account_id, activity_type, activity_id, created_at, updated_at)
+ VALUES
+ (1, 6, 2, 'Follow', 2, now(), now()),
+ (2, 2, 1, 'Mention', 4, now(), now()),
+ (3, 1, 2, 'Mention', 5, now(), now());
SQL
end
end
diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb
index 73d124029..662a89927 100644
--- a/spec/controllers/accounts_controller_spec.rb
+++ b/spec/controllers/accounts_controller_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe AccountsController, type: :controller do
let(:account) { Fabricate(:account) }
- shared_examples 'cachable response' do
+ shared_examples 'cacheable response' do
it 'does not set cookies' do
expect(response.cookies).to be_empty
expect(response.headers['Set-Cookies']).to be nil
@@ -374,7 +374,7 @@ RSpec.describe AccountsController, type: :controller do
expect(response.media_type).to eq 'application/activity+json'
end
- it_behaves_like 'cachable response'
+ it_behaves_like 'cacheable response'
it 'renders account' do
json = body_as_json
@@ -432,7 +432,7 @@ RSpec.describe AccountsController, type: :controller do
expect(response.media_type).to eq 'application/activity+json'
end
- it_behaves_like 'cachable response'
+ it_behaves_like 'cacheable response'
it 'renders account' do
json = body_as_json
@@ -499,7 +499,7 @@ RSpec.describe AccountsController, type: :controller do
expect(response).to have_http_status(200)
end
- it_behaves_like 'cachable response'
+ it_behaves_like 'cacheable response'
end
context do
diff --git a/spec/controllers/activitypub/collections_controller_spec.rb b/spec/controllers/activitypub/collections_controller_spec.rb
index 21a033945..4d87f80ce 100644
--- a/spec/controllers/activitypub/collections_controller_spec.rb
+++ b/spec/controllers/activitypub/collections_controller_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe ActivityPub::CollectionsController, type: :controller do
let!(:private_pinned) { Fabricate(:status, account: account, text: 'secret private stuff', visibility: :private) }
let(:remote_account) { nil }
- shared_examples 'cachable response' do
+ shared_examples 'cacheable response' do
it 'does not set cookies' do
expect(response.cookies).to be_empty
expect(response.headers['Set-Cookies']).to be nil
@@ -48,7 +48,7 @@ RSpec.describe ActivityPub::CollectionsController, type: :controller do
expect(response.media_type).to eq 'application/activity+json'
end
- it_behaves_like 'cachable response'
+ it_behaves_like 'cacheable response'
it 'returns orderedItems with pinned statuses' do
expect(body[:orderedItems]).to be_an Array
@@ -101,7 +101,7 @@ RSpec.describe ActivityPub::CollectionsController, type: :controller do
expect(response.media_type).to eq 'application/activity+json'
end
- it_behaves_like 'cachable response'
+ it_behaves_like 'cacheable response'
it 'returns orderedItems with pinned statuses' do
json = body_as_json
diff --git a/spec/controllers/activitypub/outboxes_controller_spec.rb b/spec/controllers/activitypub/outboxes_controller_spec.rb
index 1722690db..04f036447 100644
--- a/spec/controllers/activitypub/outboxes_controller_spec.rb
+++ b/spec/controllers/activitypub/outboxes_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
RSpec.describe ActivityPub::OutboxesController, type: :controller do
let!(:account) { Fabricate(:account) }
- shared_examples 'cachable response' do
+ shared_examples 'cacheable response' do
it 'does not set cookies' do
expect(response.cookies).to be_empty
expect(response.headers['Set-Cookies']).to be nil
@@ -53,7 +53,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
expect(body[:totalItems]).to eq 4
end
- it_behaves_like 'cachable response'
+ it_behaves_like 'cacheable response'
it 'does not have a Vary header' do
expect(response.headers['Vary']).to be_nil
@@ -98,7 +98,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
expect(body[:orderedItems].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) }).to be true
end
- it_behaves_like 'cachable response'
+ it_behaves_like 'cacheable response'
it 'returns Vary header with Signature' do
expect(response.headers['Vary']).to include 'Signature'
diff --git a/spec/controllers/activitypub/replies_controller_spec.rb b/spec/controllers/activitypub/replies_controller_spec.rb
index a2c7f336f..a35957f24 100644
--- a/spec/controllers/activitypub/replies_controller_spec.rb
+++ b/spec/controllers/activitypub/replies_controller_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe ActivityPub::RepliesController, type: :controller do
let(:remote_reply_id) { 'https://foobar.com/statuses/1234' }
let(:remote_querier) { nil }
- shared_examples 'cachable response' do
+ shared_examples 'cacheable response' do
it 'does not set cookies' do
expect(response.cookies).to be_empty
expect(response.headers['Set-Cookies']).to be nil
@@ -93,7 +93,7 @@ RSpec.describe ActivityPub::RepliesController, type: :controller do
expect(response.media_type).to eq 'application/activity+json'
end
- it_behaves_like 'cachable response'
+ it_behaves_like 'cacheable response'
context 'without only_other_accounts' do
it "returns items with thread author's replies" do
diff --git a/spec/controllers/api/v1/accounts/notes_controller_spec.rb b/spec/controllers/api/v1/accounts/notes_controller_spec.rb
index 47d595c70..42c2d8a86 100644
--- a/spec/controllers/api/v1/accounts/notes_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/notes_controller_spec.rb
@@ -31,7 +31,7 @@ describe Api::V1::Accounts::NotesController do
end
end
- context 'when account note exceends allowed length' do
+ context 'when account note exceeds allowed length' do
let(:comment) { 'a' * 2_001 }
it 'returns 422' do
diff --git a/spec/controllers/api/v1/admin/accounts_controller_spec.rb b/spec/controllers/api/v1/admin/accounts_controller_spec.rb
index bf79ee520..3f61bbc0b 100644
--- a/spec/controllers/api/v1/admin/accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/admin/accounts_controller_spec.rb
@@ -140,7 +140,7 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
expect(response).to have_http_status(200)
end
- it 'unsensitives account' do
+ it 'unsensitizes account' do
expect(account.reload.sensitized?).to be false
end
end
diff --git a/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb b/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb
index 439a4738d..7cc77f430 100644
--- a/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb
@@ -56,7 +56,7 @@ RSpec.describe Api::V1::Statuses::FavouritedByAccountsController, type: :control
Fabricate(:favourite, status: status)
end
- it 'returns http unautharized' do
+ it 'returns http unauthorized' do
get :index, params: { status_id: status.id }
expect(response).to have_http_status(404)
end
diff --git a/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb b/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb
index 31320349d..8d4a6f91c 100644
--- a/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb
@@ -56,7 +56,7 @@ RSpec.describe Api::V1::Statuses::RebloggedByAccountsController, type: :controll
Fabricate(:status, reblog_of_id: status.id)
end
- it 'returns http unautharized' do
+ it 'returns http unauthorized' do
get :index, params: { status_id: status.id }
expect(response).to have_http_status(404)
end
diff --git a/spec/controllers/api/v1/statuses_controller_spec.rb b/spec/controllers/api/v1/statuses_controller_spec.rb
index 190dfad11..2eb30af74 100644
--- a/spec/controllers/api/v1/statuses_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses_controller_spec.rb
@@ -130,7 +130,7 @@ RSpec.describe Api::V1::StatusesController, type: :controller do
let(:status) { Fabricate(:status, account: user.account, visibility: :private) }
describe 'GET #show' do
- it 'returns http unautharized' do
+ it 'returns http unauthorized' do
get :show, params: { id: status.id }
expect(response).to have_http_status(404)
end
@@ -141,7 +141,7 @@ RSpec.describe Api::V1::StatusesController, type: :controller do
Fabricate(:status, account: user.account, thread: status)
end
- it 'returns http unautharized' do
+ it 'returns http unauthorized' do
get :context, params: { id: status.id }
expect(response).to have_http_status(404)
end
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index a6a6871f7..851e58d60 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -191,30 +191,30 @@ describe ApplicationController, type: :controller do
controller do
before_action :require_admin!
- def sucesss
+ def success
head 200
end
end
before do
- routes.draw { get 'sucesss' => 'anonymous#sucesss' }
+ routes.draw { get 'success' => 'anonymous#success' }
end
it 'returns a 403 if current user is not admin' do
sign_in(Fabricate(:user, admin: false))
- get 'sucesss'
+ get 'success'
expect(response).to have_http_status(403)
end
it 'returns a 403 if current user is only a moderator' do
sign_in(Fabricate(:user, moderator: true))
- get 'sucesss'
+ get 'success'
expect(response).to have_http_status(403)
end
it 'does nothing if current user is admin' do
sign_in(Fabricate(:user, admin: true))
- get 'sucesss'
+ get 'success'
expect(response).to have_http_status(200)
end
end
@@ -223,30 +223,30 @@ describe ApplicationController, type: :controller do
controller do
before_action :require_staff!
- def sucesss
+ def success
head 200
end
end
before do
- routes.draw { get 'sucesss' => 'anonymous#sucesss' }
+ routes.draw { get 'success' => 'anonymous#success' }
end
it 'returns a 403 if current user is not admin or moderator' do
sign_in(Fabricate(:user, admin: false, moderator: false))
- get 'sucesss'
+ get 'success'
expect(response).to have_http_status(403)
end
it 'does nothing if current user is moderator' do
sign_in(Fabricate(:user, moderator: true))
- get 'sucesss'
+ get 'success'
expect(response).to have_http_status(200)
end
it 'does nothing if current user is admin' do
sign_in(Fabricate(:user, admin: true))
- get 'sucesss'
+ get 'success'
expect(response).to have_http_status(200)
end
end
diff --git a/spec/controllers/follower_accounts_controller_spec.rb b/spec/controllers/follower_accounts_controller_spec.rb
index eb095cf30..4d2a6e01a 100644
--- a/spec/controllers/follower_accounts_controller_spec.rb
+++ b/spec/controllers/follower_accounts_controller_spec.rb
@@ -103,7 +103,7 @@ describe FollowerAccountsController do
context 'when account hides their network' do
before do
- alice.user.settings.hide_network = true
+ alice.update(hide_collections: true)
end
it 'returns followers count' do
diff --git a/spec/controllers/following_accounts_controller_spec.rb b/spec/controllers/following_accounts_controller_spec.rb
index af5ce0787..bb6d221ca 100644
--- a/spec/controllers/following_accounts_controller_spec.rb
+++ b/spec/controllers/following_accounts_controller_spec.rb
@@ -103,7 +103,7 @@ describe FollowingAccountsController do
context 'when account hides their network' do
before do
- alice.user.settings.hide_network = true
+ alice.update(hide_collections: true)
end
it 'returns followers count' do
diff --git a/spec/controllers/statuses_controller_spec.rb b/spec/controllers/statuses_controller_spec.rb
index 9986efa51..05fae67fa 100644
--- a/spec/controllers/statuses_controller_spec.rb
+++ b/spec/controllers/statuses_controller_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
describe StatusesController do
render_views
- shared_examples 'cachable response' do
+ shared_examples 'cacheable response' do
it 'does not set cookies' do
expect(response.cookies).to be_empty
expect(response.headers['Set-Cookies']).to be nil
@@ -108,7 +108,7 @@ describe StatusesController do
expect(response.headers['Vary']).to eq 'Accept'
end
- it_behaves_like 'cachable response'
+ it_behaves_like 'cacheable response'
it 'returns Content-Type header' do
expect(response.headers['Content-Type']).to include 'application/activity+json'
@@ -496,7 +496,7 @@ describe StatusesController do
expect(response.headers['Vary']).to eq 'Accept'
end
- it_behaves_like 'cachable response'
+ it_behaves_like 'cacheable response'
it 'returns Content-Type header' do
expect(response.headers['Content-Type']).to include 'application/activity+json'
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index f09e32ecc..b9d38d8c6 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -60,7 +60,7 @@ describe ApplicationHelper do
end
describe 'favicon_path' do
- it 'returns /favicon.ico on production enviromnent' do
+ it 'returns /favicon.ico on production environment' do
expect(Rails.env).to receive(:production?).and_return(true)
expect(helper.favicon_path).to eq '/favicon.ico'
end
diff --git a/spec/lib/tag_manager_spec.rb b/spec/lib/tag_manager_spec.rb
index 2230f9710..cd9fb936c 100644
--- a/spec/lib/tag_manager_spec.rb
+++ b/spec/lib/tag_manager_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe TagManager do
around do |example|
original_local_domain = Rails.configuration.x.local_domain
- Rails.configuration.x.local_domain = 'domain.test'
+ Rails.configuration.x.local_domain = 'domain.example.com'
example.run
@@ -18,11 +18,11 @@ RSpec.describe TagManager do
end
it 'returns true if the slash-stripped string equals to local domain' do
- expect(TagManager.instance.local_domain?('DoMaIn.Test/')).to eq true
+ expect(TagManager.instance.local_domain?('DoMaIn.Example.com/')).to eq true
end
it 'returns false for irrelevant string' do
- expect(TagManager.instance.local_domain?('DoMaIn.Test!')).to eq false
+ expect(TagManager.instance.local_domain?('DoMaIn.Example.com!')).to eq false
end
end
@@ -31,7 +31,7 @@ RSpec.describe TagManager do
around do |example|
original_web_domain = Rails.configuration.x.web_domain
- Rails.configuration.x.web_domain = 'domain.test'
+ Rails.configuration.x.web_domain = 'domain.example.com'
example.run
@@ -43,11 +43,11 @@ RSpec.describe TagManager do
end
it 'returns true if the slash-stripped string equals to web domain' do
- expect(TagManager.instance.web_domain?('DoMaIn.Test/')).to eq true
+ expect(TagManager.instance.web_domain?('DoMaIn.Example.com/')).to eq true
end
it 'returns false for string with irrelevant characters' do
- expect(TagManager.instance.web_domain?('DoMaIn.Test!')).to eq false
+ expect(TagManager.instance.web_domain?('DoMaIn.Example.com!')).to eq false
end
end
@@ -57,7 +57,7 @@ RSpec.describe TagManager do
end
it 'returns normalized domain' do
- expect(TagManager.instance.normalize_domain('DoMaIn.Test/')).to eq 'domain.test'
+ expect(TagManager.instance.normalize_domain('DoMaIn.Example.com/')).to eq 'domain.example.com'
end
end
@@ -69,18 +69,18 @@ RSpec.describe TagManager do
end
it 'returns true if the normalized string with port is local URL' do
- Rails.configuration.x.web_domain = 'domain.test:42'
- expect(TagManager.instance.local_url?('https://DoMaIn.Test:42/')).to eq true
+ Rails.configuration.x.web_domain = 'domain.example.com:42'
+ expect(TagManager.instance.local_url?('https://DoMaIn.Example.com:42/')).to eq true
end
it 'returns true if the normalized string without port is local URL' do
- Rails.configuration.x.web_domain = 'domain.test'
- expect(TagManager.instance.local_url?('https://DoMaIn.Test/')).to eq true
+ Rails.configuration.x.web_domain = 'domain.example.com'
+ expect(TagManager.instance.local_url?('https://DoMaIn.Example.com/')).to eq true
end
it 'returns false for string with irrelevant characters' do
- Rails.configuration.x.web_domain = 'domain.test'
- expect(TagManager.instance.local_url?('https://domainn.test/')).to eq false
+ Rails.configuration.x.web_domain = 'domain.example.com'
+ expect(TagManager.instance.local_url?('https://domain.example.net/')).to eq false
end
end
end
diff --git a/spec/models/account_statuses_filter_spec.rb b/spec/models/account_statuses_filter_spec.rb
new file mode 100644
index 000000000..03f0ffeb0
--- /dev/null
+++ b/spec/models/account_statuses_filter_spec.rb
@@ -0,0 +1,229 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe AccountStatusesFilter do
+ let(:account) { Fabricate(:account) }
+ let(:current_account) { nil }
+ let(:params) { {} }
+
+ subject { described_class.new(account, current_account, params) }
+
+ def status!(visibility)
+ Fabricate(:status, account: account, visibility: visibility)
+ end
+
+ def status_with_tag!(visibility, tag)
+ Fabricate(:status, account: account, visibility: visibility, tags: [tag])
+ end
+
+ def status_with_parent!(visibility)
+ Fabricate(:status, account: account, visibility: visibility, thread: Fabricate(:status))
+ end
+
+ def status_with_reblog!(visibility)
+ Fabricate(:status, account: account, visibility: visibility, reblog: Fabricate(:status))
+ end
+
+ def status_with_mention!(visibility, mentioned_account = nil)
+ Fabricate(:status, account: account, visibility: visibility).tap do |status|
+ Fabricate(:mention, status: status, account: mentioned_account || Fabricate(:account))
+ end
+ end
+
+ def status_with_media_attachment!(visibility)
+ Fabricate(:status, account: account, visibility: visibility).tap do |status|
+ Fabricate(:media_attachment, account: account, status: status)
+ end
+ end
+
+ describe '#results' do
+ let(:tag) { Fabricate(:tag) }
+
+ before do
+ status!(:public)
+ status!(:unlisted)
+ status!(:private)
+ status_with_parent!(:public)
+ status_with_reblog!(:public)
+ status_with_tag!(:public, tag)
+ status_with_mention!(:direct)
+ status_with_media_attachment!(:public)
+ end
+
+ shared_examples 'filter params' do
+ context 'with only_media param' do
+ let(:params) { { only_media: true } }
+
+ it 'returns only statuses with media' do
+ expect(subject.results.all?(&:with_media?)).to be true
+ end
+ end
+
+ context 'with tagged param' do
+ let(:params) { { tagged: tag.name } }
+
+ it 'returns only statuses with tag' do
+ expect(subject.results.all? { |s| s.tags.include?(tag) }).to be true
+ end
+ end
+
+ context 'with exclude_replies param' do
+ let(:params) { { exclude_replies: true } }
+
+ it 'returns only statuses that are not replies' do
+ expect(subject.results.none?(&:reply?)).to be true
+ end
+ end
+
+ context 'with exclude_reblogs param' do
+ let(:params) { { exclude_reblogs: true } }
+
+ it 'returns only statuses that are not reblogs' do
+ expect(subject.results.none?(&:reblog?)).to be true
+ end
+ end
+ end
+
+ context 'when accessed anonymously' do
+ let(:current_account) { nil }
+ let(:direct_status) { nil }
+
+ it 'returns only public statuses' do
+ expect(subject.results.pluck(:visibility).uniq).to match_array %w(unlisted public)
+ end
+
+ it 'returns public replies' do
+ expect(subject.results.pluck(:in_reply_to_id)).to_not be_empty
+ end
+
+ it 'returns public reblogs' do
+ expect(subject.results.pluck(:reblog_of_id)).to_not be_empty
+ end
+
+ it_behaves_like 'filter params'
+ end
+
+ context 'when accessed with a blocked account' do
+ let(:current_account) { Fabricate(:account) }
+
+ before do
+ account.block!(current_account)
+ end
+
+ it 'returns nothing' do
+ expect(subject.results.to_a).to be_empty
+ end
+ end
+
+ context 'when accessed by self' do
+ let(:current_account) { account }
+
+ it 'returns everything' do
+ expect(subject.results.pluck(:visibility).uniq).to match_array %w(direct private unlisted public)
+ end
+
+ it 'returns replies' do
+ expect(subject.results.pluck(:in_reply_to_id)).to_not be_empty
+ end
+
+ it 'returns reblogs' do
+ expect(subject.results.pluck(:reblog_of_id)).to_not be_empty
+ end
+
+ it_behaves_like 'filter params'
+ end
+
+ context 'when accessed by a follower' do
+ let(:current_account) { Fabricate(:account) }
+
+ before do
+ current_account.follow!(account)
+ end
+
+ it 'returns private statuses' do
+ expect(subject.results.pluck(:visibility).uniq).to match_array %w(private unlisted public)
+ end
+
+ it 'returns replies' do
+ expect(subject.results.pluck(:in_reply_to_id)).to_not be_empty
+ end
+
+ it 'returns reblogs' do
+ expect(subject.results.pluck(:reblog_of_id)).to_not be_empty
+ end
+
+ context 'when there is a direct status mentioning the non-follower' do
+ let!(:direct_status) { status_with_mention!(:direct, current_account) }
+
+ it 'returns the direct status' do
+ expect(subject.results.pluck(:id)).to include(direct_status.id)
+ end
+ end
+
+ it_behaves_like 'filter params'
+ end
+
+ context 'when accessed by a non-follower' do
+ let(:current_account) { Fabricate(:account) }
+
+ it 'returns only public statuses' do
+ expect(subject.results.pluck(:visibility).uniq).to match_array %w(unlisted public)
+ end
+
+ it 'returns public replies' do
+ expect(subject.results.pluck(:in_reply_to_id)).to_not be_empty
+ end
+
+ it 'returns public reblogs' do
+ expect(subject.results.pluck(:reblog_of_id)).to_not be_empty
+ end
+
+ context 'when there is a private status mentioning the non-follower' do
+ let!(:private_status) { status_with_mention!(:private, current_account) }
+
+ it 'returns the private status' do
+ expect(subject.results.pluck(:id)).to include(private_status.id)
+ end
+ end
+
+ context 'when blocking a reblogged account' do
+ let(:reblog) { status_with_reblog!('public') }
+
+ before do
+ current_account.block!(reblog.reblog.account)
+ end
+
+ it 'does not return reblog of blocked account' do
+ expect(subject.results.pluck(:id)).to_not include(reblog.id)
+ end
+ end
+
+ context 'when muting a reblogged account' do
+ let(:reblog) { status_with_reblog!('public') }
+
+ before do
+ current_account.mute!(reblog.reblog.account)
+ end
+
+ it 'does not return reblog of muted account' do
+ expect(subject.results.pluck(:id)).to_not include(reblog.id)
+ end
+ end
+
+ context 'when blocked by a reblogged account' do
+ let(:reblog) { status_with_reblog!('public') }
+
+ before do
+ reblog.reblog.account.block!(current_account)
+ end
+
+ it 'does not return reblog of blocked-by account' do
+ expect(subject.results.pluck(:id)).to_not include(reblog.id)
+ end
+ end
+
+ it_behaves_like 'filter params'
+ end
+ end
+end
diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb
index 3d29c0219..df32a7c9d 100644
--- a/spec/models/report_spec.rb
+++ b/spec/models/report_spec.rb
@@ -119,7 +119,7 @@ describe Report do
end
end
- describe 'validatiions' do
+ describe 'validations' do
it 'has a valid fabricator' do
report = Fabricate(:report)
report.valid?
diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb
index 029789a11..d3b23726d 100644
--- a/spec/models/status_spec.rb
+++ b/spec/models/status_spec.rb
@@ -435,59 +435,6 @@ RSpec.describe Status, type: :model do
end
end
- describe '.permitted_for' do
- subject { described_class.permitted_for(target_account, account).pluck(:visibility) }
-
- let(:target_account) { alice }
- let(:account) { bob }
- let!(:public_status) { Fabricate(:status, account: target_account, visibility: 'public') }
- let!(:unlisted_status) { Fabricate(:status, account: target_account, visibility: 'unlisted') }
- let!(:private_status) { Fabricate(:status, account: target_account, visibility: 'private') }
-
- let!(:direct_status) do
- Fabricate(:status, account: target_account, visibility: 'direct').tap do |status|
- Fabricate(:mention, status: status, account: account)
- end
- end
-
- let!(:other_direct_status) do
- Fabricate(:status, account: target_account, visibility: 'direct').tap do |status|
- Fabricate(:mention, status: status)
- end
- end
-
- context 'given nil' do
- let(:account) { nil }
- let(:direct_status) { nil }
- it { is_expected.to eq(%w(unlisted public)) }
- end
-
- context 'given blocked account' do
- before do
- target_account.block!(account)
- end
-
- it { is_expected.to be_empty }
- end
-
- context 'given same account' do
- let(:account) { target_account }
- it { is_expected.to eq(%w(direct direct private unlisted public)) }
- end
-
- context 'given followed account' do
- before do
- account.follow!(target_account)
- end
-
- it { is_expected.to eq(%w(direct private unlisted public)) }
- end
-
- context 'given unfollowed account' do
- it { is_expected.to eq(%w(direct unlisted public)) }
- end
- end
-
describe 'before_validation' do
it 'sets account being replied to correctly over intermediary nodes' do
first_status = Fabricate(:status, account: bob)
diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb
index 1933ee014..731c041d1 100644
--- a/spec/policies/user_policy_spec.rb
+++ b/spec/policies/user_policy_spec.rb
@@ -114,13 +114,13 @@ RSpec.describe UserPolicy do
permissions :promote? do
context 'admin?' do
- context 'promoteable?' do
+ context 'promotable?' do
it 'permits' do
expect(subject).to permit(admin, john.user)
end
end
- context '!promoteable?' do
+ context '!promotable?' do
it 'denies' do
expect(subject).to_not permit(admin, admin.user)
end
diff --git a/spec/presenters/familiar_followers_presenter_spec.rb b/spec/presenters/familiar_followers_presenter_spec.rb
new file mode 100644
index 000000000..17be4b971
--- /dev/null
+++ b/spec/presenters/familiar_followers_presenter_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe FamiliarFollowersPresenter do
+ describe '#accounts' do
+ let(:account) { Fabricate(:account) }
+ let(:familiar_follower) { Fabricate(:account) }
+ let(:requested_accounts) { Fabricate.times(2, :account) }
+
+ subject { described_class.new(requested_accounts, account.id) }
+
+ before do
+ familiar_follower.follow!(requested_accounts.first)
+ account.follow!(familiar_follower)
+ end
+
+ it 'returns a result for each requested account' do
+ expect(subject.accounts.map(&:id)).to eq requested_accounts.map(&:id)
+ end
+
+ it 'returns followers you follow' do
+ result = subject.accounts.first
+
+ expect(result).to_not be_nil
+ expect(result.id).to eq requested_accounts.first.id
+ expect(result.accounts).to match_array([familiar_follower])
+ end
+
+ context 'when requested account hides followers' do
+ before do
+ requested_accounts.first.update(hide_collections: true)
+ end
+
+ it 'does not return followers you follow' do
+ result = subject.accounts.first
+
+ expect(result).to_not be_nil
+ expect(result.id).to eq requested_accounts.first.id
+ expect(result.accounts).to be_empty
+ end
+ end
+
+ context 'when familiar follower hides follows' do
+ before do
+ familiar_follower.update(hide_collections: true)
+ end
+
+ it 'does not return followers you follow' do
+ result = subject.accounts.first
+
+ expect(result).to_not be_nil
+ expect(result.id).to eq requested_accounts.first.id
+ expect(result.accounts).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/services/unsuspend_account_service_spec.rb b/spec/services/unsuspend_account_service_spec.rb
index d52cb6cc0..0593beb6f 100644
--- a/spec/services/unsuspend_account_service_spec.rb
+++ b/spec/services/unsuspend_account_service_spec.rb
@@ -63,20 +63,20 @@ RSpec.describe UnsuspendAccountService, type: :service do
describe 'unsuspending a remote account' do
include_examples 'common behavior' do
let!(:account) { Fabricate(:account, domain: 'bob.com', uri: 'https://bob.com', inbox_url: 'https://bob.com/inbox', protocol: :activitypub) }
- let!(:reslove_account_service) { double }
+ let!(:resolve_account_service) { double }
before do
- allow(ResolveAccountService).to receive(:new).and_return(reslove_account_service)
+ allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service)
end
context 'when the account is not remotely suspended' do
before do
- allow(reslove_account_service).to receive(:call).with(account).and_return(account)
+ allow(resolve_account_service).to receive(:call).with(account).and_return(account)
end
it 're-fetches the account' do
subject.call
- expect(reslove_account_service).to have_received(:call).with(account)
+ expect(resolve_account_service).to have_received(:call).with(account)
end
it "merges back into local followers' feeds" do
@@ -92,7 +92,7 @@ RSpec.describe UnsuspendAccountService, type: :service do
context 'when the account is remotely suspended' do
before do
- allow(reslove_account_service).to receive(:call).with(account) do |account|
+ allow(resolve_account_service).to receive(:call).with(account) do |account|
account.suspend!(origin: :remote)
account
end
@@ -100,7 +100,7 @@ RSpec.describe UnsuspendAccountService, type: :service do
it 're-fetches the account' do
subject.call
- expect(reslove_account_service).to have_received(:call).with(account)
+ expect(resolve_account_service).to have_received(:call).with(account)
end
it "does not merge back into local followers' feeds" do
@@ -116,12 +116,12 @@ RSpec.describe UnsuspendAccountService, type: :service do
context 'when the account is remotely deleted' do
before do
- allow(reslove_account_service).to receive(:call).with(account).and_return(nil)
+ allow(resolve_account_service).to receive(:call).with(account).and_return(nil)
end
it 're-fetches the account' do
subject.call
- expect(reslove_account_service).to have_received(:call).with(account)
+ expect(resolve_account_service).to have_received(:call).with(account)
end
it "does not merge back into local followers' feeds" do
diff --git a/spec/support/stories/profile_stories.rb b/spec/support/stories/profile_stories.rb
index 75b413330..0c4a14d1c 100644
--- a/spec/support/stories/profile_stories.rb
+++ b/spec/support/stories/profile_stories.rb
@@ -22,7 +22,7 @@ module ProfileStories
def with_alice_as_local_user
@alice_bio = '@alice and @bob are fictional characters commonly used as'\
'placeholder names in #cryptology, as well as #science and'\
- 'engineering 📖 literature. Not affilated with @pepe.'
+ 'engineering 📖 literature. Not affiliated with @pepe.'
@alice = Fabricate(
:user,