diff --git a/app/javascript/mastodon/features/explore/suggestions.js b/app/javascript/mastodon/features/explore/suggestions.js
index c094a8d934..0c6a7ef8a3 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 122b51c7cd..a36dc81b12 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 70e3cd6e88..8d47e479a8 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 89fd20c3f6..06c2b679c4 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 8e6b0cdd5b..eb6bdea990 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 73414785c2..0873ac3001 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 8f6d4b69af..d1d679ac25 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 a180df437a..81459f5ba5 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 e33fc79835..a719044ea2 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 ea7bb5113c..98eb1511cc 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 581101782f..d8015e50dc 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 8617b389cd..dfbe0b8bc1 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 0000000000..556aee032e
--- /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 3dd8a6fdd4..8ba2dd8fd2 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 06141f410e..6a848baee1 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 77685ad027..f657f1b278 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 6695a0ddf8..92e2c4f4bb 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 0000000000..c1d944b80b
--- /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 0000000000..0a7e923f80
--- /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 b452e49368..5db620b2d1 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 d5509f9464..2ac700fe68 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 645dd2de17..92de35a9fc 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 17fe790188..9bb1a9eddf 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 8159efb557..cf604d0432 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 21948b200b..b84d06c27b 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 19d59f1551..1a041ad481 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 2367d5ded4..1964285b05 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 d3461474b1..de74e0f611 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 c5e75b408c..b19b7891fd 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 167502c33a..3449fa03da 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 1f366d7d11..51016d3220 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 bcd4b91376..4ab68e8f32 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 302db6b68d..78f8df5383 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 0000000000..102ee46d6f
--- /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 36960e84ac..f8d85901d9 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 5bee70ea51..a3e9479098 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 00861df774..1e047d96c6 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 91b08813b3..d4c2e6cf2a 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 0f38b50e39..8082f32fb4 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 73d1240291..662a89927d 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 21a0339458..4d87f80ced 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 1722690db1..04f0364472 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 a2c7f336ff..a35957f24c 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 47d595c705..42c2d8a86f 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 bf79ee5202..3f61bbc0ba 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 439a4738df..7cc77f4308 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 31320349df..8d4a6f91ca 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 190dfad117..2eb30af74b 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 a6a6871f7a..851e58d60c 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 eb095cf30c..4d2a6e01a9 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 af5ce07870..bb6d221cac 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 9986efa51d..05fae67fab 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 f09e32ecc2..b9d38d8c68 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 2230f97103..cd9fb936cd 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 0000000000..03f0ffeb06
--- /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 3d29c02195..df32a7c9d5 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 029789a113..d3b23726dd 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 1933ee014f..731c041d11 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 0000000000..17be4b9715
--- /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 d52cb6cc00..0593beb6f2 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 75b413330f..0c4a14d1c9 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,