Merge remote-tracking branch 'upstream/main' into default-time-zone

# Conflicts:
#	app/mailers/notification_mailer.rb
This commit is contained in:
Christian Schmidt 2024-09-19 17:17:34 +02:00
commit 46536de358
42 changed files with 181 additions and 356 deletions

View File

@ -100,8 +100,8 @@ GEM
attr_required (1.0.2)
awrence (1.2.1)
aws-eventstream (1.3.0)
aws-partitions (1.974.0)
aws-sdk-core (3.205.0)
aws-partitions (1.977.0)
aws-sdk-core (3.206.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.9)
@ -109,11 +109,11 @@ GEM
aws-sdk-kms (1.91.0)
aws-sdk-core (~> 3, >= 3.205.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.162.0)
aws-sdk-s3 (1.163.0)
aws-sdk-core (~> 3, >= 3.205.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.9.1)
aws-sigv4 (1.10.0)
aws-eventstream (~> 1, >= 1.0.2)
azure-storage-blob (2.0.3)
azure-storage-common (~> 2.0)
@ -197,7 +197,7 @@ GEM
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
devise-two-factor (5.1.0)
devise-two-factor (6.0.0)
activesupport (~> 7.0)
devise (~> 4.0)
railties (~> 7.0)
@ -609,7 +609,7 @@ GEM
psych (5.1.2)
stringio
public_suffix (6.0.1)
puma (6.4.2)
puma (6.4.3)
nio4r (~> 2.0)
pundit (2.4.0)
activesupport (>= 3.0.0)

View File

@ -4,8 +4,6 @@ import { Children, cloneElement, useCallback } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { supportsPassiveEvents } from 'detect-passive-events';
import { scrollRight } from '../../../scroll';
import BundleContainer from '../containers/bundle_container';
import {
@ -71,10 +69,6 @@ export default class ColumnsArea extends ImmutablePureComponent {
};
componentDidMount() {
if (!this.props.singleColumn) {
this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false);
}
if (this.mediaQuery) {
if (this.mediaQuery.addEventListener) {
this.mediaQuery.addEventListener('change', this.handleLayoutChange);
@ -87,23 +81,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl');
}
UNSAFE_componentWillUpdate(nextProps) {
if (this.props.singleColumn !== nextProps.singleColumn && nextProps.singleColumn) {
this.node.removeEventListener('wheel', this.handleWheel);
}
}
componentDidUpdate(prevProps) {
if (this.props.singleColumn !== prevProps.singleColumn && !this.props.singleColumn) {
this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false);
}
}
componentWillUnmount () {
if (!this.props.singleColumn) {
this.node.removeEventListener('wheel', this.handleWheel);
}
if (this.mediaQuery) {
if (this.mediaQuery.removeEventListener) {
this.mediaQuery.removeEventListener('change', this.handleLayoutChange);
@ -116,7 +94,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
handleChildrenContentChange() {
if (!this.props.singleColumn) {
const modifier = this.isRtlLayout ? -1 : 1;
this._interruptScrollAnimation = scrollRight(this.node, (this.node.scrollWidth - window.innerWidth) * modifier);
scrollRight(this.node, (this.node.scrollWidth - window.innerWidth) * modifier);
}
}
@ -124,14 +102,6 @@ export default class ColumnsArea extends ImmutablePureComponent {
this.setState({ renderComposePanel: !e.matches });
};
handleWheel = () => {
if (typeof this._interruptScrollAnimation !== 'function') {
return;
}
this._interruptScrollAnimation();
};
setRef = (node) => {
this.node = node;
};

View File

@ -778,6 +778,7 @@
"status.bookmark": "북마크",
"status.cancel_reblog_private": "부스트 취소",
"status.cannot_reblog": "이 게시물은 부스트 할 수 없습니다",
"status.continued_thread": "이어지는 글타래",
"status.copy": "게시물 링크 복사",
"status.delete": "삭제",
"status.detailed_status": "대화 자세히 보기",
@ -786,6 +787,7 @@
"status.edit": "수정",
"status.edited": "{date}에 마지막으로 편집됨",
"status.edited_x_times": "{count, plural, other {{count}}} 번 수정됨",
"status.embed": "임베드 코드 받기",
"status.favourite": "좋아요",
"status.favourites": "{count, plural, other {좋아요}}",
"status.filter": "이 게시물을 필터",
@ -810,6 +812,7 @@
"status.reblogs.empty": "아직 아무도 이 게시물을 부스트하지 않았습니다. 부스트 한 사람들이 여기에 표시 됩니다.",
"status.redraft": "지우고 다시 쓰기",
"status.remove_bookmark": "북마크 삭제",
"status.replied_in_thread": "글타래에 답장",
"status.replied_to": "{name} 님에게",
"status.reply": "답장",
"status.replyAll": "글타래에 답장",

View File

@ -409,6 +409,7 @@
"lists.subheading": "Tavi saraksti",
"load_pending": "{count, plural, one {# jauna lieta} other {# jaunas lietas}}",
"loading_indicator.label": "Ielādē…",
"media_gallery.hide": "Paslēpt",
"moved_to_account_banner.text": "Tavs konts {disabledAccount} pašlaik ir atspējots, jo Tu pārcēlies uz kontu {movedToAccount}.",
"mute_modal.hide_from_notifications": "Paslēpt paziņojumos",
"mute_modal.hide_options": "Paslēpt iespējas",

View File

@ -38,13 +38,20 @@ const scroll = (
const isScrollBehaviorSupported =
'scrollBehavior' in document.documentElement.style;
export const scrollRight = (node: Element, position: number) => {
if (isScrollBehaviorSupported)
node.scrollTo({ left: position, behavior: 'smooth' });
else scroll(node, 'scrollLeft', position);
};
export const scrollRight = (node: Element, position: number) =>
requestIdleCallback(() => {
if (isScrollBehaviorSupported) {
node.scrollTo({ left: position, behavior: 'smooth' });
} else {
scroll(node, 'scrollLeft', position);
}
});
export const scrollTop = (node: Element) => {
if (isScrollBehaviorSupported) node.scrollTo({ top: 0, behavior: 'smooth' });
else scroll(node, 'scrollTop', 0);
};
export const scrollTop = (node: Element) =>
requestIdleCallback(() => {
if (isScrollBehaviorSupported) {
node.scrollTo({ top: 0, behavior: 'smooth' });
} else {
scroll(node, 'scrollTop', 0);
}
});

View File

@ -209,7 +209,6 @@ const KNOWN_EVENT_TYPES = [
'notification',
'conversation',
'filters_changed',
'encrypted_message',
'announcement',
'announcement.delete',
'announcement.reaction',

View File

@ -8,6 +8,14 @@ import { render as rtlRender } from '@testing-library/react';
import { IdentityContext } from './identity_context';
beforeEach(() => {
global.requestIdleCallback = jest
.fn()
.mockImplementation((fn: () => void) => {
fn();
});
});
function render(
ui: React.ReactElement,
{

View File

@ -56,9 +56,11 @@ class AdminMailer < ApplicationMailer
def new_critical_software_updates
@software_updates = SoftwareUpdate.where(urgent: true).to_a.sort_by(&:gem_version)
headers['Priority'] = 'urgent'
headers['X-Priority'] = '1'
headers['Importance'] = 'high'
headers(
'Importance' => 'high',
'Priority' => 'urgent',
'X-Priority' => '1'
)
with_user_settings(@user) do
mail subject: default_i18n_subject(instance: @instance)

View File

@ -18,8 +18,10 @@ class ApplicationMailer < ActionMailer::Base
end
def set_autoreply_headers!
headers['Precedence'] = 'list'
headers['X-Auto-Response-Suppress'] = 'All'
headers['Auto-Submitted'] = 'auto-generated'
headers(
'Auto-Submitted' => 'auto-generated',
'Precedence' => 'list',
'X-Auto-Response-Suppress' => 'All'
)
end
end

View File

@ -6,7 +6,10 @@ class NotificationMailer < ApplicationMailer
:routing
before_action :process_params
before_action :set_status, only: [:mention, :favourite, :reblog]
with_options only: %i(mention favourite reblog) do
before_action :set_status
after_action :thread_by_conversation!
end
before_action :set_account, only: [:follow, :favourite, :reblog, :follow_request]
after_action :set_list_headers!
@ -18,7 +21,6 @@ class NotificationMailer < ApplicationMailer
return unless @user.functional? && @status.present?
with_user_settings(@user) do
thread_by_conversation(@status.conversation)
mail subject: default_i18n_subject(name: @status.account.acct)
end
end
@ -35,7 +37,6 @@ class NotificationMailer < ApplicationMailer
return unless @user.functional? && @status.present?
with_user_settings(@user) do
thread_by_conversation(@status.conversation)
mail subject: default_i18n_subject(name: @account.acct)
end
end
@ -44,7 +45,6 @@ class NotificationMailer < ApplicationMailer
return unless @user.functional? && @status.present?
with_user_settings(@user) do
thread_by_conversation(@status.conversation)
mail subject: default_i18n_subject(name: @account.acct)
end
end
@ -76,17 +76,21 @@ class NotificationMailer < ApplicationMailer
end
def set_list_headers!
headers['List-ID'] = "<#{@type}.#{@me.username}.#{Rails.configuration.x.local_domain}>"
headers['List-Unsubscribe'] = "<#{@unsubscribe_url}>"
headers['List-Unsubscribe-Post'] = 'List-Unsubscribe=One-Click'
headers(
'List-ID' => "<#{@type}.#{@me.username}.#{Rails.configuration.x.local_domain}>",
'List-Unsubscribe-Post' => 'List-Unsubscribe=One-Click',
'List-Unsubscribe' => "<#{@unsubscribe_url}>"
)
end
def thread_by_conversation(conversation)
return if conversation.nil?
def thread_by_conversation!
return if @status.conversation.nil?
msg_id = "<conversation-#{conversation.id}.#{conversation.created_at.strftime('%Y-%m-%d')}@#{Rails.configuration.x.local_domain}>"
conversation_message_id = "<conversation-#{@status.conversation.id}.#{@status.conversation.created_at.to_date}@#{Rails.configuration.x.local_domain}>"
headers['In-Reply-To'] = msg_id
headers['References'] = msg_id
headers(
'In-Reply-To' => conversation_message_id,
'References' => conversation_message_id
)
end
end

View File

@ -26,16 +26,5 @@ class ActivityPub::ActivityPresenter < ActiveModelSerializers::Model
end
end
end
def from_encrypted_message(encrypted_message)
new.tap do |presenter|
presenter.id = ActivityPub::TagManager.instance.generate_uri_for(nil)
presenter.type = 'Create'
presenter.actor = ActivityPub::TagManager.instance.uri_for(encrypted_message.source_account)
presenter.published = Time.now.utc
presenter.to = ActivityPub::TagManager.instance.uri_for(encrypted_message.target_account)
presenter.virtual_object = encrypted_message
end
end
end
end

View File

@ -15,6 +15,12 @@ ko:
user/invite_request:
text: 이유
errors:
attributes:
domain:
invalid: 올바른 도메인 네임이 아닙니다
messages:
invalid_domain_on_line: "%{value}는 올바른 도메인 네임이 아닙니다"
too_many_lines: "%{limit}줄 제한을 초과합니다"
models:
account:
attributes:

View File

@ -22,6 +22,7 @@ ko:
admin:
account_actions:
action: 조치 취하기
already_silenced: 이 계정은 이미 제한되었습니다.
already_suspended: 이 계정은 이미 정지되었습니다.
title: "%{acct} 계정에 중재 취하기"
account_moderation_notes:
@ -1143,6 +1144,12 @@ ko:
view_strikes: 내 계정에 대한 과거 중재 기록 보기
too_fast: 너무 빠르게 양식이 제출되었습니다, 다시 시도하세요.
use_security_key: 보안 키 사용
author_attribution:
example_title: 예시 텍스트
hint_html: 링크가 마스토돈에 공유될 때 내가 어떻게 표시될 지를 제어합니다.
more_from_html: "%{name}의 게시물 더 보기"
s_blog: "%{name}의 블로그"
title: 작성자 기여
challenge:
confirm: 계속
hint_html: "<strong>팁:</strong> 한 시간 동안 다시 암호를 묻지 않을 것입니다."
@ -1906,6 +1913,7 @@ ko:
instructions_html: 웹사이트에 아래 코드를 복사해 붙여 넣으세요. 그리고 "프로필 수정" 탭에서 그 웹사이트 주소를 프로필의 추가 필드 중 하나에 넣고 변경사항을 저장하세요.
verification: 검증
verified_links: 인증된 링크들
website_verification: 웹사이트 인증
webauthn_credentials:
add: 보안 키 추가
create:

View File

@ -1114,7 +1114,7 @@ lv:
new_confirmation_instructions_sent: Pēc dažām minūtēm saņemsi jaunu e-pasta ziņojumu ar apstiprinājuma saiti.
title: Pārbaudi savu iesūtni
sign_in:
preamble_html: Jāpiesakās ar saviem <strong>%{domain}</strong> piekļuves datiem. Ja Tavs konts tiek mitināts citā serverī, Tu nevarēsi šeit pieteikties.
preamble_html: Jāpiesakās ar saviem <strong>%{domain}</strong> piekļuves datiem. Ja konts tiek mitināts citā serverī, šeit nevarēs pieteikties.
title: Pierakstīties %{domain}
sign_up:
manual_review: Reģistrācijas domēnā %{domain} manuāli pārbauda mūsu moderatori. Lai palīdzētu mums apstrādāt tavu reģistrāciju, uzraksti mazliet par sevi un to, kāpēc vēlies kontu %{domain}.

View File

@ -3,6 +3,7 @@ ko:
simple_form:
hints:
account:
attribution_domains_as_text: 가짜 기여로부터 보호합니다.
discoverable: 내 공개 게시물과 프로필이 마스토돈의 다양한 추천 기능에 나타날 수 있고 프로필이 다른 사용자에게 제안될 수 있습니다
display_name: 진짜 이름 또는 재미난 이름.
fields: 홈페이지, 호칭, 나이, 뭐든지 적고 싶은 것들.
@ -143,6 +144,7 @@ ko:
url: 이벤트가 어디로 전송될 지
labels:
account:
attribution_domains_as_text: 특정 웹사이트만 허용하기
discoverable: 발견하기 알고리즘에 프로필과 게시물을 추천하기
fields:
name: 라벨

View File

@ -1,47 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Settings::ExportsController do
render_views
describe 'GET #show' do
context 'when signed in' do
let(:user) { Fabricate(:user) }
before do
sign_in user, scope: :user
get :show
end
it 'returns http success with private cache control headers', :aggregate_failures do
expect(response).to have_http_status(200)
expect(response.headers['Cache-Control']).to include('private, no-store')
end
end
context 'when not signed in' do
it 'redirects' do
get :show
expect(response).to redirect_to '/auth/sign_in'
end
end
end
describe 'POST #create' do
before do
sign_in Fabricate(:user), scope: :user
end
it 'redirects to settings_export_path' do
post :create
expect(response).to redirect_to(settings_export_path)
end
it 'queues BackupWorker job by 1' do
expect do
post :create
end.to change(BackupWorker.jobs, :size).by(1)
end
end
end

View File

@ -4,6 +4,8 @@ require 'rails_helper'
require 'devise_two_factor/spec_helpers'
RSpec.describe User do
subject { described_class.new(account: account) }
let(:password) { 'abcd1234' }
let(:account) { Fabricate(:account, username: 'alice') }

View File

@ -193,15 +193,11 @@ RSpec.describe 'Accounts' do
it_behaves_like 'forbidden for wrong scope', 'write write:accounts read admin:read'
it_behaves_like 'forbidden for wrong role', ''
it 'removes the user successfully', :aggregate_failures do
it 'removes the user successfully and logs action', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(User.where(id: account.user.id)).to_not exist
end
it 'logs action', :aggregate_failures do
subject
expect(latest_admin_action_log)
.to be_present

View File

@ -73,14 +73,10 @@ RSpec.describe 'Tags' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
it 'returns http success and expected tag content' do
subject
expect(response).to have_http_status(200)
end
it 'returns expected tag content' do
subject
expect(response.parsed_body[:id].to_i).to eq(tag.id)
expect(response.parsed_body[:name]).to eq(tag.name)
@ -107,14 +103,10 @@ RSpec.describe 'Tags' do
it_behaves_like 'forbidden for wrong scope', 'admin:read'
it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
it 'returns http success and updates tag' do
subject
expect(response).to have_http_status(200)
end
it 'returns updated tag' do
subject
expect(response.parsed_body[:id].to_i).to eq(tag.id)
expect(response.parsed_body[:name]).to eq(tag.name.upcase)

View File

@ -47,14 +47,10 @@ RSpec.describe 'Credentials' do
let(:token) { Fabricate(:accessible_access_token, application: application) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
it 'returns http success' do
it 'returns http success and returns app information' do
subject
expect(response).to have_http_status(200)
end
it 'returns the app information correctly' do
subject
expect(response.parsed_body).to match(
a_hash_including(
@ -108,14 +104,10 @@ RSpec.describe 'Credentials' do
let(:token) { Fabricate(:accessible_access_token, application: application) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}-invalid" } }
it 'returns http authorization error' do
it 'returns http authorization error with json error' do
subject
expect(response).to have_http_status(401)
end
it 'returns the error in the json response' do
subject
expect(response.parsed_body).to match(
a_hash_including(

View File

@ -32,15 +32,10 @@ RSpec.describe 'Blocks' do
context 'with limit param' do
let(:params) { { limit: 2 } }
it 'returns only the requested number of blocked accounts' do
it 'returns only the requested number of blocked accounts and sets link header pagination' do
subject
expect(response.parsed_body.size).to eq(params[:limit])
end
it 'sets correct link header pagination' do
subject
expect(response)
.to include_pagination_headers(
prev: api_v1_blocks_url(limit: params[:limit], since_id: blocks.last.id),

View File

@ -24,15 +24,10 @@ RSpec.describe 'Bookmarks' do
it_behaves_like 'forbidden for wrong scope', 'write'
it 'returns http success' do
it 'returns http success and the bookmarked statuses' do
subject
expect(response).to have_http_status(200)
end
it 'returns the bookmarked statuses' do
subject
expect(response.parsed_body).to match_array(expected_response)
end

View File

@ -24,30 +24,20 @@ RSpec.describe 'Favourites' do
it_behaves_like 'forbidden for wrong scope', 'write'
it 'returns http success' do
it 'returns http success and includes the favourites' do
subject
expect(response).to have_http_status(200)
end
it 'returns the favourites' do
subject
expect(response.parsed_body).to match_array(expected_response)
end
context 'with limit param' do
let(:params) { { limit: 1 } }
it 'returns only the requested number of favourites' do
it 'returns only the requested number of favourites and sets pagination headers' do
subject
expect(response.parsed_body.size).to eq(params[:limit])
end
it 'sets the correct pagination headers' do
subject
expect(response)
.to include_pagination_headers(
prev: api_v1_favourites_url(limit: params[:limit], min_id: favourites.last.id),

View File

@ -58,15 +58,10 @@ RSpec.describe 'FeaturedTags' do
describe 'POST /api/v1/featured_tags' do
let(:params) { { name: 'tag' } }
it 'returns http success' do
it 'returns http success and includes correct tag name' do
post '/api/v1/featured_tags', headers: headers, params: params
expect(response).to have_http_status(200)
end
it 'returns the correct tag name' do
post '/api/v1/featured_tags', headers: headers, params: params
expect(response.parsed_body)
.to include(
name: params[:name]
@ -132,23 +127,13 @@ RSpec.describe 'FeaturedTags' do
let!(:featured_tag) { FeaturedTag.create(name: 'tag', account: user.account) }
let(:id) { featured_tag.id }
it 'returns http success' do
it 'returns http success with an empty body and deletes the featured tag', :inline_jobs do
delete "/api/v1/featured_tags/#{id}", headers: headers
expect(response).to have_http_status(200)
end
it 'returns an empty body' do
delete "/api/v1/featured_tags/#{id}", headers: headers
expect(response.parsed_body).to be_empty
end
it 'deletes the featured tag', :inline_jobs do
delete "/api/v1/featured_tags/#{id}", headers: headers
featured_tag = FeaturedTag.find_by(id: id)
expect(featured_tag).to be_nil
end

View File

@ -28,15 +28,10 @@ RSpec.describe 'Followed tags' do
it_behaves_like 'forbidden for wrong scope', 'write write:follows'
it 'returns http success' do
it 'returns http success and includes followed tags' do
subject
expect(response).to have_http_status(:success)
end
it 'returns the followed tags correctly' do
subject
expect(response.parsed_body).to match_array(expected_response)
end

View File

@ -8,11 +8,8 @@ RSpec.describe 'Languages' do
get '/api/v1/instance/languages'
end
it 'returns http success' do
it 'returns http success and includes supported languages' do
expect(response).to have_http_status(200)
end
it 'returns the supported languages' do
expect(response.parsed_body.pluck(:code)).to match_array LanguagesHelper::SUPPORTED_LOCALES.keys.map(&:to_s)
end
end

View File

@ -139,16 +139,11 @@ RSpec.describe 'Accounts' do
list.accounts << [bob, peter]
end
it 'removes the specified account from the list', :aggregate_failures do
it 'removes the specified account from the list but keeps other accounts in the list', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(list.accounts).to_not include(bob)
end
it 'does not remove any other account from the list' do
subject
expect(list.accounts).to include(peter)
end

View File

@ -17,15 +17,10 @@ RSpec.describe 'Media' do
it_behaves_like 'forbidden for wrong scope', 'read'
it 'returns http success' do
it 'returns http success with media information' do
subject
expect(response).to have_http_status(200)
end
it 'returns the media information' do
subject
expect(response.parsed_body).to match(
a_hash_including(
id: media.id.to_s,

View File

@ -18,32 +18,22 @@ RSpec.describe 'Mutes' do
it_behaves_like 'forbidden for wrong scope', 'write write:mutes'
it 'returns http success' do
it 'returns http success with muted accounts' do
subject
expect(response).to have_http_status(200)
end
it 'returns the muted accounts' do
subject
muted_accounts = mutes.map(&:target_account)
expect(response.parsed_body.pluck(:id)).to match_array(muted_accounts.map { |account| account.id.to_s })
end
context 'with limit param' do
let(:params) { { limit: 1 } }
it 'returns only the requested number of muted accounts' do
it 'returns only the requested number of muted accounts with pagination headers' do
subject
expect(response.parsed_body.size).to eq(params[:limit])
end
it 'sets the correct pagination headers', :aggregate_failures do
subject
expect(response)
.to include_pagination_headers(
prev: api_v1_mutes_url(limit: params[:limit], since_id: mutes.last.id),

View File

@ -39,15 +39,10 @@ RSpec.describe 'Requests' do
it_behaves_like 'forbidden for wrong scope', 'read read:notifications'
it 'returns http success' do
it 'returns http success and creates notification permission' do
subject
expect(response).to have_http_status(200)
end
it 'creates notification permission' do
subject
expect(NotificationPermission.find_by(account: notification_request.account, from_account: notification_request.from_account)).to_not be_nil
end

View File

@ -28,31 +28,14 @@ RSpec.describe 'Deleting profile images' do
it_behaves_like 'forbidden for wrong scope', 'read'
end
it 'returns http success' do
it 'returns http success and deletes the avatar, preserves the header, queues up distribution' do
delete '/api/v1/profile/avatar', headers: headers
expect(response).to have_http_status(200)
end
it 'deletes the avatar' do
delete '/api/v1/profile/avatar', headers: headers
account.reload
expect(account.avatar).to_not exist
end
it 'does not delete the header' do
delete '/api/v1/profile/avatar', headers: headers
account.reload
expect(account.header).to exist
end
it 'queues up an account update distribution' do
delete '/api/v1/profile/avatar', headers: headers
expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id)
end
end
@ -66,31 +49,14 @@ RSpec.describe 'Deleting profile images' do
it_behaves_like 'forbidden for wrong scope', 'read'
end
it 'returns http success' do
it 'returns http success, preserves the avatar, deletes the header, queues up distribution' do
delete '/api/v1/profile/header', headers: headers
expect(response).to have_http_status(200)
end
it 'does not delete the avatar' do
delete '/api/v1/profile/header', headers: headers
account.reload
expect(account.avatar).to exist
end
it 'deletes the header' do
delete '/api/v1/profile/header', headers: headers
account.reload
expect(account.header).to_not exist
end
it 'queues up an account update distribution' do
delete '/api/v1/profile/header', headers: headers
expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id)
end
end

View File

@ -18,15 +18,11 @@ RSpec.describe 'Bookmarks' do
it_behaves_like 'forbidden for wrong scope', 'read'
context 'with public status' do
it 'bookmarks the status successfully', :aggregate_failures do
it 'bookmarks the status successfully and includes updated json', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.bookmarked?(status)).to be true
end
it 'returns json with updated attributes' do
subject
expect(response.parsed_body).to match(
a_hash_including(id: status.id.to_s, bookmarked: true)
@ -93,15 +89,11 @@ RSpec.describe 'Bookmarks' do
Bookmark.find_or_create_by!(account: user.account, status: status)
end
it 'unbookmarks the status successfully', :aggregate_failures do
it 'unbookmarks the status successfully and includes updated json', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.bookmarked?(status)).to be false
end
it 'returns json with updated attributes' do
subject
expect(response.parsed_body).to match(
a_hash_including(id: status.id.to_s, bookmarked: false)
@ -117,15 +109,11 @@ RSpec.describe 'Bookmarks' do
status.account.block!(user.account)
end
it 'unbookmarks the status successfully', :aggregate_failures do
it 'unbookmarks the status successfully and includes updated json', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.bookmarked?(status)).to be false
end
it 'returns json with updated attributes' do
subject
expect(response.parsed_body).to match(
a_hash_including(id: status.id.to_s, bookmarked: false)

View File

@ -18,15 +18,11 @@ RSpec.describe 'Favourites', :inline_jobs do
it_behaves_like 'forbidden for wrong scope', 'read read:favourites'
context 'with public status' do
it 'favourites the status successfully', :aggregate_failures do
it 'favourites the status successfully and includes updated json', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.favourited?(status)).to be true
end
it 'returns json with updated attributes' do
subject
expect(response.parsed_body).to match(
a_hash_including(id: status.id.to_s, favourites_count: 1, favourited: true)
@ -84,16 +80,12 @@ RSpec.describe 'Favourites', :inline_jobs do
FavouriteService.new.call(user.account, status)
end
it 'unfavourites the status successfully', :aggregate_failures do
it 'unfavourites the status successfully and includes updated json', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.favourited?(status)).to be false
end
it 'returns json with updated attributes' do
subject
expect(response.parsed_body).to match(
a_hash_including(id: status.id.to_s, favourites_count: 0, favourited: false)
@ -107,16 +99,12 @@ RSpec.describe 'Favourites', :inline_jobs do
status.account.block!(user.account)
end
it 'unfavourites the status successfully', :aggregate_failures do
it 'unfavourites the status successfully and includes updated json', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.favourited?(status)).to be false
end
it 'returns json with updated attributes' do
subject
expect(response.parsed_body).to match(
a_hash_including(id: status.id.to_s, favourites_count: 0, favourited: false)

View File

@ -18,15 +18,11 @@ RSpec.describe 'Pins' do
it_behaves_like 'forbidden for wrong scope', 'read read:accounts'
context 'when the status is public' do
it 'pins the status successfully', :aggregate_failures do
it 'pins the status successfully and returns updated json', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.pinned?(status)).to be true
end
it 'return json with updated attributes' do
subject
expect(response.parsed_body).to match(
a_hash_including(id: status.id.to_s, pinned: true)
@ -86,15 +82,11 @@ RSpec.describe 'Pins' do
Fabricate(:status_pin, status: status, account: user.account)
end
it 'unpins the status successfully', :aggregate_failures do
it 'unpins the status successfully and includes updated json', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.pinned?(status)).to be false
end
it 'return json with updated attributes' do
subject
expect(response.parsed_body).to match(
a_hash_including(id: status.id.to_s, pinned: false)

View File

@ -23,15 +23,10 @@ RSpec.describe 'Suggestions' do
it_behaves_like 'forbidden for wrong scope', 'write'
it 'returns http success' do
it 'returns http success with accounts' do
subject
expect(response).to have_http_status(200)
end
it 'returns accounts' do
subject
expect(response.parsed_body)
.to contain_exactly(include(id: bob.id.to_s), include(id: jeff.id.to_s))
end
@ -72,15 +67,10 @@ RSpec.describe 'Suggestions' do
it_behaves_like 'forbidden for wrong scope', 'read'
it 'returns http success' do
it 'returns http success and removes suggestion' do
subject
expect(response).to have_http_status(200)
end
it 'removes the specified suggestion' do
subject
expect(FollowRecommendationMute.exists?(account: user.account, target_account: jeff)).to be true
end

View File

@ -31,14 +31,10 @@ RSpec.describe 'Home', :inline_jobs do
PostStatusService.new.call(ana, text: 'New toot from ana.')
end
it 'returns http success' do
it 'returns http success and statuses of followed users' do
subject
expect(response).to have_http_status(200)
end
it 'returns the statuses of followed users' do
subject
expect(response.parsed_body.pluck(:id)).to match_array(home_statuses.map { |status| status.id.to_s })
end
@ -46,14 +42,10 @@ RSpec.describe 'Home', :inline_jobs do
context 'with limit param' do
let(:params) { { limit: 1 } }
it 'returns only the requested number of statuses' do
it 'returns only the requested number of statuses with pagination headers', :aggregate_failures do
subject
expect(response.parsed_body.size).to eq(params[:limit])
end
it 'sets the correct pagination headers', :aggregate_failures do
subject
expect(response)
.to include_pagination_headers(

View File

@ -123,15 +123,11 @@ RSpec.describe 'Link' do
context 'with limit param' do
let(:params) { { limit: 1, url: url } }
it 'returns only the requested number of statuses', :aggregate_failures do
it 'returns only the requested number of statuses with pagination headers', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(response.parsed_body.size).to eq(params[:limit])
end
it 'sets the correct pagination headers', :aggregate_failures do
subject
expect(response)
.to include_pagination_headers(

View File

@ -49,14 +49,10 @@ RSpec.describe 'Filters' do
context 'with valid params' do
let(:params) { { title: 'magic', context: %w(home), filter_action: 'hide', keywords_attributes: [keyword: 'magic'] } }
it 'returns http success' do
it 'returns http success with a filter with keywords in json and creates a filter', :aggregate_failures do
subject
expect(response).to have_http_status(200)
end
it 'returns a filter with keywords', :aggregate_failures do
subject
expect(response.parsed_body)
.to include(
@ -67,10 +63,6 @@ RSpec.describe 'Filters' do
include(keyword: 'magic', whole_word: true)
)
)
end
it 'creates a filter', :aggregate_failures do
subject
filter = user.account.custom_filters.first
@ -189,20 +181,12 @@ RSpec.describe 'Filters' do
allow(redis).to receive_messages(publish: nil)
end
it 'returns http success' do
it 'returns http success and updates keyword and sends a filters_changed event' do
subject
expect(response).to have_http_status(200)
end
it 'updates the keyword' do
subject
expect(keyword.reload.keyword).to eq 'updated'
end
it 'sends exactly one filters_changed event' do
subject
expect(redis).to have_received(:publish).with("timeline:#{user.account.id}", Oj.dump(event: :filters_changed)).once
end
@ -229,14 +213,10 @@ RSpec.describe 'Filters' do
it_behaves_like 'forbidden for wrong scope', 'read read:filters'
it_behaves_like 'unauthorized for invalid token'
it 'returns http success' do
it 'returns http success and removes the filter' do
subject
expect(response).to have_http_status(200)
end
it 'removes the filter' do
subject
expect { filter.reload }.to raise_error ActiveRecord::RecordNotFound
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Settings / Exports' do
context 'when not signed in' do
describe 'GET /settings/export' do
it 'redirects to sign in page' do
get settings_export_path
expect(response)
.to redirect_to new_user_session_path
end
end
describe 'POST /settings/export' do
it 'redirects to sign in page' do
post settings_export_path
expect(response)
.to redirect_to new_user_session_path
end
end
end
end

View File

@ -0,0 +1,40 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Export page' do
let(:user) { Fabricate :user }
before { sign_in user }
describe 'Viewing the export page' do
context 'when signed in' do
it 'shows the export page', :aggregate_failures do
visit settings_export_path
expect(page)
.to have_content(takeout_summary)
.and have_private_cache_control
end
end
end
describe 'Creating a new archive' do
it 'queues a worker and redirects' do
visit settings_export_path
expect { request_archive }
.to change(BackupWorker.jobs, :size).by(1)
expect(page)
.to have_content(takeout_summary)
end
def request_archive
click_on I18n.t('exports.archive_takeout.request')
end
end
def takeout_summary
I18n.t('settings.export')
end
end