Merge remote-tracking branch 'parent/main' into upstream-20231120

This commit is contained in:
KMY 2023-11-20 12:50:02 +09:00
commit abec232dd7
85 changed files with 1314 additions and 458 deletions

View file

@ -229,7 +229,7 @@ jobs:
path: tmp/screenshots/
test-search:
name: Testing search
name: Elastic Search integration testing
runs-on: ubuntu-latest
needs:
@ -329,7 +329,7 @@ jobs:
- name: Load database schema
run: './bin/rails db:create db:schema:load db:seed'
- run: bundle exec rake spec:search
- run: bin/rspec --tag search
- name: Archive logs
uses: actions/upload-artifact@v3

View file

@ -38,6 +38,7 @@ RUN apt-get update && \
corepack enable
COPY Gemfile* package.json yarn.lock .yarnrc.yml /opt/mastodon/
COPY streaming/package.json /opt/mastodon/streaming/
COPY .yarn /opt/mastodon/.yarn
RUN bundle install -j"$(nproc)"

View file

@ -109,6 +109,9 @@ group :test do
# RSpec progress bar formatter
gem 'fuubar', '~> 2.5'
# RSpec helpers for email specs
gem 'email_spec'
# Extra RSpec extenion methods and helpers for sidekiq
gem 'rspec-sidekiq', '~> 4.0'

View file

@ -260,6 +260,10 @@ GEM
elasticsearch-transport (7.13.3)
faraday (~> 1)
multi_json
email_spec (2.2.2)
htmlentities (~> 4.3.3)
launchy (~> 2.1)
mail (~> 2.7)
encryptor (3.0.0)
erubi (1.12.0)
et-orbi (1.2.7)
@ -855,6 +859,7 @@ DEPENDENCIES
doorkeeper (~> 5.6)
dotenv-rails (~> 2.8)
ed25519 (~> 1.3)
email_spec
fabrication (~> 2.30)
faker (~> 3.2)
fast_blank (~> 1.0)

View file

@ -32,6 +32,9 @@ DB_USER=postgres DB_PASS=password foreman start
RAILS_ENV=test bundle exec rspec spec
# ElasticSearch連携テストを行う
RAILS_ENV=test ES_ENABLED=true bundle exec rspec --tag search
RAILS_ENV=test ES_ENABLED=true RUN_SEARCH_SPECS=true bundle exec rspec spec/search
```

View file

@ -5,8 +5,8 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { useHistory } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { useDispatch } from 'react-redux';
import { ReactComponent as AddPhotoAlternateIcon } from '@material-symbols/svg-600/outlined/add_photo_alternate.svg';
import { ReactComponent as EditIcon } from '@material-symbols/svg-600/outlined/edit.svg';
@ -33,7 +33,6 @@ export const Profile = () => {
const [avatar, setAvatar] = useState(null);
const [header, setHeader] = useState(null);
const [discoverable, setDiscoverable] = useState(account.get('discoverable'));
const [indexable, setIndexable] = useState(account.get('indexable'));
const [isSaving, setIsSaving] = useState(false);
const [errors, setErrors] = useState();
const avatarFileRef = createRef();
@ -54,10 +53,6 @@ export const Profile = () => {
setDiscoverable(e.target.checked);
}, [setDiscoverable]);
const handleIndexableChange = useCallback(e => {
setIndexable(e.target.checked);
}, [setIndexable]);
const handleAvatarChange = useCallback(e => {
setAvatar(e.target?.files?.[0]);
}, [setAvatar]);
@ -78,12 +73,12 @@ export const Profile = () => {
avatar,
header,
discoverable,
indexable,
indexable: discoverable,
})).then(() => history.push('/start/follows')).catch(err => {
setIsSaving(false);
setErrors(err.response.data.details);
});
}, [dispatch, displayName, note, avatar, header, discoverable, indexable, history]);
}, [dispatch, displayName, note, avatar, header, discoverable, history]);
return (
<>
@ -141,18 +136,21 @@ export const Profile = () => {
<textarea id='note' value={note} onChange={handleNoteChange} maxLength={500} />
</div>
</div>
<label className='app-form__toggle'>
<div className='app-form__toggle__label'>
<strong><FormattedMessage id='onboarding.profile.discoverable' defaultMessage='Make my profile discoverable' /></strong> <span className='recommended'><FormattedMessage id='recommended' defaultMessage='Recommended' /></span>
<span className='hint'><FormattedMessage id='onboarding.profile.discoverable_hint' defaultMessage='When you opt in to discoverability on Mastodon, your posts may appear in search results and trending, and your profile may be suggested to people with similar interests to you.' /></span>
</div>
<div className='app-form__toggle__toggle'>
<div>
<Toggle checked={discoverable} onChange={handleDiscoverableChange} />
</div>
</div>
</label>
</div>
<label className='report-dialog-modal__toggle'>
<Toggle checked={discoverable} onChange={handleDiscoverableChange} />
<FormattedMessage id='onboarding.profile.discoverable' defaultMessage='Feature profile and posts in discovery algorithms' />
</label>
<label className='report-dialog-modal__toggle'>
<Toggle checked={indexable} onChange={handleIndexableChange} />
<FormattedMessage id='onboarding.profile.indexable' defaultMessage='Include public posts in search results' />
</label>
<div className='onboarding__footer'>
<Button block onClick={handleSubmit} disabled={isSaving}>{isSaving ? <LoadingIndicator /> : <FormattedMessage id='onboarding.profile.save_and_continue' defaultMessage='Save and continue' />}</Button>
</div>

View file

@ -21,6 +21,7 @@
"account.blocked": "Заблакіраваны",
"account.browse_more_on_origin_server": "Глядзіце больш у арыгінальным профілі",
"account.cancel_follow_request": "Скасаваць запыт на падпіску",
"account.copy": "Скапіраваць спасылку на профіль",
"account.direct": "Згадаць асабіста @{name}",
"account.disable_notifications": "Не паведамляць мне пра публікацыі @{name}",
"account.domain_blocked": "Дамен заблакаваны",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Адзначыць прачытаным",
"conversation.open": "Прагледзець размову",
"conversation.with": "З {names}",
"copy_icon_button.copied": "Скапіявана ў буфер абмену",
"copypaste.copied": "Скапіравана",
"copypaste.copy_to_clipboard": "Капіраваць у буфер абмену",
"directory.federated": "З вядомага федэсвету",

View file

@ -21,6 +21,7 @@
"account.blocked": "Blokovaný",
"account.browse_more_on_origin_server": "Více na původním profilu",
"account.cancel_follow_request": "Zrušit žádost o sledování",
"account.copy": "Kopírovat odkaz na profil",
"account.direct": "Soukromě zmínit @{name}",
"account.disable_notifications": "Přestat mě upozorňovat, když @{name} zveřejní příspěvek",
"account.domain_blocked": "Doména blokována",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Označit jako přečtené",
"conversation.open": "Zobrazit konverzaci",
"conversation.with": "S {names}",
"copy_icon_button.copied": "Zkopírováno do schránky",
"copypaste.copied": "Zkopírováno",
"copypaste.copy_to_clipboard": "Zkopírovat do schránky",
"directory.federated": "Ze známého fedivesmíru",

View file

@ -191,6 +191,7 @@
"conversation.mark_as_read": "Nodi fel wedi'i ddarllen",
"conversation.open": "Gweld sgwrs",
"conversation.with": "Gyda {names}",
"copy_icon_button.copied": "Copïwyd i'r clipfwrdd",
"copypaste.copied": "Wedi ei gopïo",
"copypaste.copy_to_clipboard": "Copïo i'r clipfwrdd",
"directory.federated": "O'r ffedysawd cyfan",
@ -390,6 +391,7 @@
"lists.search": "Chwilio ymysg pobl rydych yn eu dilyn",
"lists.subheading": "Eich rhestrau",
"load_pending": "{count, plural, one {# eitem newydd} other {# eitem newydd}}",
"loading_indicator.label": "Yn llwytho…",
"media_gallery.toggle_visible": "{number, plural, one {Cuddio delwedd} other {Cuddio delwedd}}",
"moved_to_account_banner.text": "Ar hyn y bryd, mae eich cyfrif {disabledAccount} wedi ei analluogi am i chi symud i {movedToAccount}.",
"mute_modal.duration": "Hyd",
@ -478,6 +480,11 @@
"onboarding.follows.empty": "Yn anffodus, nid oes modd dangos unrhyw ganlyniadau ar hyn o bryd. Gallwch geisio defnyddio chwilio neu bori'r dudalen archwilio i ddod o hyd i bobl i'w dilyn, neu ceisio eto yn nes ymlaen.",
"onboarding.follows.lead": "Rydych chi'n curadu eich ffrwd gartref eich hun. Po fwyaf o bobl y byddwch chi'n eu dilyn, y mwyaf egnïol a diddorol fydd hi. Gall y proffiliau hyn fod yn fan cychwyn da - gallwch chi bob amser eu dad-ddilyn yn nes ymlaen:",
"onboarding.follows.title": "Yn boblogaidd ar Mastodon",
"onboarding.profile.display_name_hint": "Eich enw llawn neu'ch enw hwyl…",
"onboarding.profile.note": "Bywgraffiad",
"onboarding.profile.note_hint": "Gallwch @grybwyll pobl eraill neu #hashnodau…",
"onboarding.profile.save_and_continue": "Cadw a pharhau",
"onboarding.profile.title": "Gosodiad proffil",
"onboarding.share.lead": "Cofiwch ddweud wrth bobl sut y gallan nhw ddod o hyd i chi ar Mastodon!",
"onboarding.share.message": "Fi yw {username} ar #Mastodon! Dewch i'm dilyn i yn {url}",
"onboarding.share.next_steps": "Camau nesaf posib:",

View file

@ -21,6 +21,7 @@
"account.blocked": "Blokeret",
"account.browse_more_on_origin_server": "Se mere på den oprindelige profil",
"account.cancel_follow_request": "Annullér anmodning om at følge",
"account.copy": "Kopiér link til profil",
"account.direct": "Privat omtale @{name}",
"account.disable_notifications": "Advisér mig ikke længere, når @{name} poster",
"account.domain_blocked": "Domæne blokeret",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Markér som læst",
"conversation.open": "Vis samtale",
"conversation.with": "Med {names}",
"copy_icon_button.copied": "Kopieret til udklipsholderen",
"copypaste.copied": "Kopieret",
"copypaste.copy_to_clipboard": "Kopiér til udklipsholder",
"directory.federated": "Fra kendt fedivers",

View file

@ -21,6 +21,7 @@
"account.blocked": "Blockiert",
"account.browse_more_on_origin_server": "Mehr auf dem Originalprofil durchsuchen",
"account.cancel_follow_request": "Folgeanfrage zurückziehen",
"account.copy": "Link zum Profil kopieren",
"account.direct": "@{name} privat erwähnen",
"account.disable_notifications": "Höre auf mich zu benachrichtigen wenn @{name} etwas postet",
"account.domain_blocked": "Domain versteckt",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Als gelesen markieren",
"conversation.open": "Unterhaltung anzeigen",
"conversation.with": "Mit {names}",
"copy_icon_button.copied": "In die Zwischenablage kopiert",
"copypaste.copied": "Kopiert",
"copypaste.copy_to_clipboard": "In die Zwischenablage kopieren",
"directory.federated": "Aus bekanntem Fediverse",

View file

@ -506,10 +506,10 @@
"onboarding.follows.empty": "Unfortunately, no results can be shown right now. You can try using search or browsing the explore page to find people to follow, or try again later.",
"onboarding.follows.lead": "Your home feed is the primary way to experience Mastodon. The more people you follow, the more active and interesting it will be. To get you started, here are some suggestions:",
"onboarding.follows.title": "Personalize your home feed",
"onboarding.profile.discoverable": "Feature profile and posts in discovery algorithms",
"onboarding.profile.discoverable": "Make my profile discoverable",
"onboarding.profile.discoverable_hint": "When you opt in to discoverability on Mastodon, your posts may appear in search results and trending, and your profile may be suggested to people with similar interests to you.",
"onboarding.profile.display_name": "Display name",
"onboarding.profile.display_name_hint": "Your full name or your fun name…",
"onboarding.profile.indexable": "Include public posts in search results",
"onboarding.profile.lead": "You can always complete this later in the settings, where even more customization options are available.",
"onboarding.profile.note": "Bio",
"onboarding.profile.note_hint": "You can @mention other people or #hashtags…",
@ -570,6 +570,7 @@
"privacy_policy.title": "Privacy Policy",
"reaction_deck.add": "Add",
"reaction_deck.remove": "Remove",
"recommended": "Recommended",
"refresh": "Refresh",
"regeneration_indicator.label": "Loading…",
"regeneration_indicator.sublabel": "Your home feed is being prepared!",

View file

@ -21,6 +21,7 @@
"account.blocked": "Bloqueado",
"account.browse_more_on_origin_server": "Explorar más en el perfil original",
"account.cancel_follow_request": "Dejar de seguir",
"account.copy": "Copiar enlace al perfil",
"account.direct": "Mención privada a @{name}",
"account.disable_notifications": "Dejar de notificarme cuando @{name} envíe mensajes",
"account.domain_blocked": "Dominio bloqueado",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Marcar como leída",
"conversation.open": "Ver conversación",
"conversation.with": "Con {names}",
"copy_icon_button.copied": "Copiado en el portapapeles",
"copypaste.copied": "Copiado",
"copypaste.copy_to_clipboard": "Copiar al portapapeles",
"directory.federated": "Desde fediverso conocido",

View file

@ -21,6 +21,7 @@
"account.blocked": "Bloqueado",
"account.browse_more_on_origin_server": "Ver más en el perfil original",
"account.cancel_follow_request": "Retirar solicitud de seguimiento",
"account.copy": "Copiar enlace al perfil",
"account.direct": "Mención privada @{name}",
"account.disable_notifications": "Dejar de notificarme cuando @{name} publique algo",
"account.domain_blocked": "Dominio oculto",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Marcar como leído",
"conversation.open": "Ver conversación",
"conversation.with": "Con {names}",
"copy_icon_button.copied": "Copiado al portapapeles",
"copypaste.copied": "Copiado",
"copypaste.copy_to_clipboard": "Copiar al portapapeles",
"directory.federated": "Desde el fediverso conocido",

View file

@ -21,6 +21,7 @@
"account.blocked": "Bloqueado",
"account.browse_more_on_origin_server": "Ver más en el perfil original",
"account.cancel_follow_request": "Retirar solicitud de seguimiento",
"account.copy": "Copiar enlace al perfil",
"account.direct": "Mención privada a @{name}",
"account.disable_notifications": "Dejar de notificarme cuando @{name} publique algo",
"account.domain_blocked": "Dominio bloqueado",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Marcar como leído",
"conversation.open": "Ver conversación",
"conversation.with": "Con {names}",
"copy_icon_button.copied": "Copiado al portapapeles",
"copypaste.copied": "Copiado",
"copypaste.copy_to_clipboard": "Copiar al portapapeles",
"directory.federated": "Desde el fediverso conocido",
@ -479,7 +481,17 @@
"onboarding.follows.empty": "Desafortunadamente, no se pueden mostrar resultados en este momento. Puedes intentar usar la búsqueda o navegar por la página de exploración para encontrar personas a las que seguir, o inténtalo de nuevo más tarde.",
"onboarding.follows.lead": "Tu línea de inicio es la forma principal de experimentar Mastodon. Cuanta más personas sigas, más activa e interesante será. Para empezar, aquí hay algunas sugerencias:",
"onboarding.follows.title": "Personaliza tu línea de inicio",
"onboarding.profile.discoverable": "Destacar perfil y publicaciones en algoritmos de descubrimiento",
"onboarding.profile.display_name": "Nombre para mostrar",
"onboarding.profile.display_name_hint": "Tu nombre completo o tu apodo…",
"onboarding.profile.indexable": "Incluir publicaciones públicas en los resultados de búsqueda",
"onboarding.profile.lead": "Siempre puedes completar esto más tarde en los ajustes, donde hay aún más opciones de personalización disponibles.",
"onboarding.profile.note": "Biografía",
"onboarding.profile.note_hint": "Puedes @mencionar a otras personas o #etiquetas…",
"onboarding.profile.save_and_continue": "Guardar y continuar",
"onboarding.profile.title": "Configuración del perfil",
"onboarding.profile.upload_avatar": "Subir foto de perfil",
"onboarding.profile.upload_header": "Subir encabezado de perfil",
"onboarding.share.lead": "¡Cuéntale a otras personas cómo te pueden encontrar en Mastodon!",
"onboarding.share.message": "¡Soy {username} en #Mastodon! Ven a seguirme en {url}",
"onboarding.share.next_steps": "Posibles siguientes pasos:",

View file

@ -21,6 +21,7 @@
"account.blocked": "Blokeatuta",
"account.browse_more_on_origin_server": "Arakatu gehiago jatorrizko profilean",
"account.cancel_follow_request": "Baztertu jarraitzeko eskaera",
"account.copy": "Kopiatu profilerako esteka",
"account.direct": "Aipatu pribatuki @{name}",
"account.disable_notifications": "Utzi jakinarazteari @{name} erabiltzailearen bidalketetan",
"account.domain_blocked": "Ezkutatutako domeinua",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Markatu irakurrita bezala",
"conversation.open": "Ikusi elkarrizketa",
"conversation.with": "Hauekin: {names}",
"copy_icon_button.copied": "Arbelera kopiatu da",
"copypaste.copied": "Kopiatuta",
"copypaste.copy_to_clipboard": "Kopiatu arbelera",
"directory.federated": "Fedibertso ezagunekoak",

View file

@ -21,6 +21,7 @@
"account.blocked": "Estetty",
"account.browse_more_on_origin_server": "Selaile lisää alkuperäisellä palvelimella",
"account.cancel_follow_request": "Peruuta seurantapyyntö",
"account.copy": "Kopioi profiilin linkki",
"account.direct": "Mainitse @{name} yksityisesti",
"account.disable_notifications": "Lopeta ilmoittamasta minulle, kun @{name} julkaisee",
"account.domain_blocked": "Verkkotunnus estetty",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Merkitse luetuksi",
"conversation.open": "Näytä keskustelu",
"conversation.with": "{names} kanssa",
"copy_icon_button.copied": "Kopioitu leikepöydälle",
"copypaste.copied": "Kopioitu",
"copypaste.copy_to_clipboard": "Kopioi leikepöydälle",
"directory.federated": "Koko tunnettu fediversumi",

View file

@ -390,6 +390,7 @@
"lists.search": "Rechercher parmi les gens que vous suivez",
"lists.subheading": "Vos listes",
"load_pending": "{count, plural, one {# nouvel élément} other {# nouveaux éléments}}",
"loading_indicator.label": "Chargement…",
"media_gallery.toggle_visible": "{number, plural, one {Cacher limage} other {Cacher les images}}",
"moved_to_account_banner.text": "Votre compte {disabledAccount} est actuellement désactivé parce que vous avez déménagé sur {movedToAccount}.",
"mute_modal.duration": "Durée",
@ -478,6 +479,9 @@
"onboarding.follows.empty": "Malheureusement, aucun résultat ne peut être affiché pour le moment. Vous pouvez essayer de rechercher ou de parcourir la page \"Explorer\" pour trouver des personnes à suivre, ou réessayer plus tard.",
"onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
"onboarding.follows.title": "Popular on Mastodon",
"onboarding.profile.discoverable": "Autoriser des algorithmes de découverte à mettre en avant votre profil et vos messages",
"onboarding.profile.save_and_continue": "Enregistrer et continuer",
"onboarding.profile.upload_avatar": "Importer une photo de profil",
"onboarding.share.lead": "Faites savoir aux gens comment vous trouver sur Mastodon!",
"onboarding.share.message": "Je suis {username} sur #Mastodon! Suivez-moi sur {url}",
"onboarding.share.next_steps": "Étapes suivantes possibles:",

View file

@ -107,7 +107,7 @@
"closed_registrations_modal.preamble": "Mastodon est décentralisé : peu importe où vous créez votre compte, vous serez en mesure de suivre et d'interagir avec quiconque sur ce serveur. Vous pouvez même l'héberger !",
"closed_registrations_modal.title": "Inscription sur Mastodon",
"column.about": "À propos",
"column.blocks": "Comptes bloqués",
"column.blocks": "Utilisateurs bloqués",
"column.bookmarks": "Marque-pages",
"column.community": "Fil public local",
"column.direct": "Mentions privées",
@ -390,6 +390,7 @@
"lists.search": "Rechercher parmi les gens que vous suivez",
"lists.subheading": "Vos listes",
"load_pending": "{count, plural, one {# nouvel élément} other {# nouveaux éléments}}",
"loading_indicator.label": "Chargement…",
"media_gallery.toggle_visible": "{number, plural, one {Cacher limage} other {Cacher les images}}",
"moved_to_account_banner.text": "Votre compte {disabledAccount} est actuellement désactivé parce que vous l'avez déplacé à {movedToAccount}.",
"mute_modal.duration": "Durée",
@ -478,6 +479,9 @@
"onboarding.follows.empty": "Malheureusement, aucun résultat ne peut être affiché pour le moment. Vous pouvez essayer d'utiliser la recherche ou parcourir la page de découverte pour trouver des personnes à suivre, ou réessayez plus tard.",
"onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
"onboarding.follows.title": "Personnaliser votre flux principal",
"onboarding.profile.discoverable": "Autoriser des algorithmes de découverte à mettre en avant votre profil et vos messages",
"onboarding.profile.save_and_continue": "Enregistrer et continuer",
"onboarding.profile.upload_avatar": "Importer une photo de profil",
"onboarding.share.lead": "Faites savoir aux gens comment ils peuvent vous trouver sur Mastodon!",
"onboarding.share.message": "Je suis {username} sur #Mastodon! Suivez-moi sur {url}",
"onboarding.share.next_steps": "Étapes suivantes possibles :",

View file

@ -390,6 +390,7 @@
"lists.search": "Procurar entre as persoas que segues",
"lists.subheading": "As túas listaxes",
"load_pending": "{count, plural, one {# novo elemento} other {# novos elementos}}",
"loading_indicator.label": "Estase a cargar…",
"media_gallery.toggle_visible": "Agochar {number, plural, one {imaxe} other {imaxes}}",
"moved_to_account_banner.text": "A túa conta {disabledAccount} está actualmente desactivada porque movéchela a {movedToAccount}.",
"mute_modal.duration": "Duración",
@ -478,6 +479,17 @@
"onboarding.follows.empty": "Desgraciadamente agora mesmo non hai nada que mostrar. Podes intentalo coa busca ou na páxina descubrir para atopar persoas ás que seguir, ou intentalo máis tarde.",
"onboarding.follows.lead": "Podes facer que a túa cronoloxía de inicio sexa como ti a queres. Canta máis xente sigas máis interesante será. Estes perfís poderían axudarche a comezar —sempre poderás deixar de seguilos despois!",
"onboarding.follows.title": "Popular en Mastodon",
"onboarding.profile.discoverable": "Perfil destacado e publicacións nos algoritmos de descubrimento",
"onboarding.profile.display_name": "Nome público",
"onboarding.profile.display_name_hint": "O teu nome completo ou un nome divertido…",
"onboarding.profile.indexable": "Incluír publicacións públicas nos resultados das buscas",
"onboarding.profile.lead": "Sempre poderás incluír esta información mais tarde nos axustes, onde terás máis opcións dispoñibles.",
"onboarding.profile.note": "Acerca de ti",
"onboarding.profile.note_hint": "Podes @mencionar a outras persoas ou usar #cancelos…",
"onboarding.profile.save_and_continue": "Gardar e continuar",
"onboarding.profile.title": "Configuración do perfil",
"onboarding.profile.upload_avatar": "Subir imaxe do perfil",
"onboarding.profile.upload_header": "Subir cabeceira para o perfil",
"onboarding.share.lead": "Fai que as persoas saiban como atoparte en Mastodon!",
"onboarding.share.message": "Son {username} en #Mastodon! Ségueme en {url}",
"onboarding.share.next_steps": "Seguintes pasos:",

View file

@ -21,6 +21,7 @@
"account.blocked": "לחסום",
"account.browse_more_on_origin_server": "ראה יותר בפרופיל המקורי",
"account.cancel_follow_request": "משיכת בקשת מעקב",
"account.copy": "להעתיק קישור לפרופיל",
"account.direct": "הודעה פרטית אל @{name}",
"account.disable_notifications": "הפסק לשלוח לי התראות כש@{name} מפרסמים",
"account.domain_blocked": "הדומיין חסום",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "סמן כנקרא",
"conversation.open": "צפו בשיחה",
"conversation.with": "עם {names}",
"copy_icon_button.copied": "הועתק ללוח",
"copypaste.copied": "הועתק",
"copypaste.copy_to_clipboard": "העתקה ללוח הגזירים",
"directory.federated": "מהפדרציה הידועה",

View file

@ -21,6 +21,7 @@
"account.blocked": "Letiltva",
"account.browse_more_on_origin_server": "További böngészés az eredeti profilon",
"account.cancel_follow_request": "Követési kérés visszavonása",
"account.copy": "Hivatkozás másolása a profilba",
"account.direct": "@{name} személyes említése",
"account.disable_notifications": "Ne figyelmeztessen, ha @{name} bejegyzést tesz közzé",
"account.domain_blocked": "Letiltott domain",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Megjelölés olvasottként",
"conversation.open": "Beszélgetés megtekintése",
"conversation.with": "Velük: {names}",
"copy_icon_button.copied": "A szöveg a vágólapra másolva",
"copypaste.copied": "Másolva",
"copypaste.copy_to_clipboard": "Másolás vágólapra",
"directory.federated": "Az ismert fediverzumból",
@ -481,13 +483,13 @@
"onboarding.follows.title": "Népszerű a Mastodonon",
"onboarding.profile.discoverable": "Profil és bejegyzések szerepeltetése a felfedezési algoritmusokban",
"onboarding.profile.display_name": "Megjelenített név",
"onboarding.profile.display_name_hint": "Teljes név vagy becenév…",
"onboarding.profile.indexable": "Nyilvános bejegyzések is a keresési eredményekben",
"onboarding.profile.lead": "Ezt később bármikor elvégezhető a beállításoknál, ahol még több testreszabási lehetőség áll rendelkezésre.",
"onboarding.profile.note": "Biográfia",
"onboarding.profile.note_hint": "@említhetünk másokat vagy #hashtag elemeket…",
"onboarding.profile.display_name_hint": "Teljes neved vagy vicces neved…",
"onboarding.profile.indexable": "Nyilvános bejegyzések szerepeltetése a keresési eredményekben",
"onboarding.profile.lead": "Ezt később bármikor befejezheted a beállításokban, ahol még több testreszabási lehetőség áll rendelkezésre.",
"onboarding.profile.note": "Bemutatkozás",
"onboarding.profile.note_hint": "Megemlíthetsz @másokat vagy #hashtag-eket…",
"onboarding.profile.save_and_continue": "Mentés és folytatás",
"onboarding.profile.title": "Profil beüzemelés",
"onboarding.profile.title": "Profilbeállítás",
"onboarding.profile.upload_avatar": "Profilkép feltöltése",
"onboarding.profile.upload_header": "Profil fejléc feltöltése",
"onboarding.share.lead": "Tudassuk az emberekkel, hogyan találhatnak meg a Mastodonon!",

View file

@ -21,6 +21,7 @@
"account.blocked": "Útilokaður",
"account.browse_more_on_origin_server": "Skoða nánari upplýsingar á notandasniðinu",
"account.cancel_follow_request": "Taka fylgjendabeiðni til baka",
"account.copy": "Afrita tengil í notandasnið",
"account.direct": "Einkaspjall við @{name}",
"account.disable_notifications": "Hætta að láta mig vita þegar @{name} sendir inn",
"account.domain_blocked": "Lén útilokað",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Merkja sem lesið",
"conversation.open": "Skoða samtal",
"conversation.with": "Við {names}",
"copy_icon_button.copied": "Afritað á klippispjald",
"copypaste.copied": "Afritað",
"copypaste.copy_to_clipboard": "Afrita á klippispjald",
"directory.federated": "Frá samtengdum vefþjónum",

View file

@ -21,6 +21,7 @@
"account.blocked": "Bloccato",
"account.browse_more_on_origin_server": "Sfoglia di più sul profilo originale",
"account.cancel_follow_request": "Annulla la richiesta di seguire",
"account.copy": "Copia link del profilo",
"account.direct": "Menziona privatamente @{name}",
"account.disable_notifications": "Smetti di avvisarmi quando @{name} pubblica un post",
"account.domain_blocked": "Dominio bloccato",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Segna come letto",
"conversation.open": "Visualizza conversazione",
"conversation.with": "Con {names}",
"copy_icon_button.copied": "Copiato negli appunti",
"copypaste.copied": "Copiato",
"copypaste.copy_to_clipboard": "Copia negli Appunti",
"directory.federated": "Da un fediverse noto",

View file

@ -21,6 +21,7 @@
"account.blocked": "차단함",
"account.browse_more_on_origin_server": "원본 프로필에서 더 탐색하기",
"account.cancel_follow_request": "팔로우 취소",
"account.copy": "프로필 링크 복사",
"account.direct": "@{name} 님에게 개인적으로 멘션",
"account.disable_notifications": "@{name} 의 게시물 알림 끄기",
"account.domain_blocked": "도메인 차단함",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "읽은 상태로 표시",
"conversation.open": "대화 보기",
"conversation.with": "{names} 님과",
"copy_icon_button.copied": "클립보드에 복사함",
"copypaste.copied": "복사됨",
"copypaste.copy_to_clipboard": "클립보드에 복사",
"directory.federated": "알려진 연합우주로부터",

View file

@ -21,6 +21,7 @@
"account.blocked": "Užblokuota",
"account.browse_more_on_origin_server": "Naršyti daugiau originaliame profilyje",
"account.cancel_follow_request": "Atšaukti sekimą",
"account.copy": "Kopijuoti nuorodą į profilį",
"account.direct": "Privačiai paminėti @{name}",
"account.disable_notifications": "Nustoti man pranešti, kai @{name} paskelbia",
"account.domain_blocked": "Užblokuotas domenas",
@ -91,11 +92,28 @@
"bundle_column_error.routing.body": "Prašyto puslapio nepavyko rasti. Ar esi tikras (-a), kad adreso juostoje nurodytas URL adresas yra teisingas?",
"bundle_column_error.routing.title": "404",
"bundle_modal_error.close": "Uždaryti",
"bundle_modal_error.retry": "Bandyti dar kartą",
"closed_registrations.other_server_instructions": "Kadangi Mastodon yra decentralizuotas, gali susikurti paskyrą kitame serveryje ir vis tiek bendrauti su šiuo serveriu.",
"closed_registrations_modal.description": "Sukurti paskyrą {domain} šiuo metu neįmanoma, tačiau nepamiršk, kad norint naudotis Mastodon nebūtina turėti paskyrą {domain}.",
"closed_registrations_modal.find_another_server": "Rasti kitą serverį",
"column.domain_blocks": "Hidden domains",
"closed_registrations_modal.preamble": "Mastodon yra decentralizuotas, todėl nesvarbu, kur susikursi paskyrą, galėsi sekti ir bendrauti su bet kuriuo šiame serveryje esančiu asmeniu. Jį gali net savarankiškai talpinti!",
"closed_registrations_modal.title": "Užsiregistravimas į Mastodon",
"column.about": "Apie",
"column.blocks": "Užblokuoti naudotojai",
"column.bookmarks": "Žymės",
"column.community": "Vietinė laiko skalė",
"column.direct": "Privatūs paminėjimai",
"column.directory": "Naršyti profilius",
"column.domain_blocks": "Užblokuoti domenai",
"column.favourites": "Mėgstamiausi",
"column.firehose": "Tiesioginiai padavimai",
"column.follow_requests": "Sekti prašymus",
"column.home": "Pradžia",
"column.lists": "Sąrašai",
"column.mutes": "Užtildyti vartotojai",
"column.pins": "Pinned toot",
"column.mutes": "Užtildyti naudotojai",
"column.notifications": "Pranešimai",
"column.pins": "Prisegti įrašai",
"column.public": "Federacinė laiko skalė",
"column_back_button.label": "Atgal",
"column_header.hide_settings": "Slėpti nustatymus",
"column_header.pin": "Prisegti",
@ -106,6 +124,7 @@
"compose.language.change": "Keisti kalbą",
"compose.language.search": "Ieškoti kalbų...",
"compose.published.body": "Įrašas paskelbtas.",
"compose_form.direct_message_warning_learn_more": "Sužinoti daugiau",
"compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.",
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
"compose_form.placeholder": "Kas tavo mintyse?",
@ -137,6 +156,7 @@
"confirmations.reply.confirm": "Atsakyti",
"confirmations.reply.message": "Atsakydamas (-a) dabar perrašysi šiuo metu rašomą žinutę. Ar tikrai nori tęsti?",
"confirmations.unfollow.confirm": "Nebesekti",
"copy_icon_button.copied": "Nukopijuota į iškarpinę",
"dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
"dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
"embed.instructions": "Embed this status on your website by copying the code below.",

View file

@ -21,6 +21,7 @@
"account.blocked": "Geblokkeerd",
"account.browse_more_on_origin_server": "Zie meer op het originele profiel",
"account.cancel_follow_request": "Ontvolgen",
"account.copy": "Link naar profiel kopiëren",
"account.direct": "@{name} een privébericht sturen",
"account.disable_notifications": "Geen melding meer geven wanneer @{name} een bericht plaatst",
"account.domain_blocked": "Domein geblokkeerd",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Als gelezen markeren",
"conversation.open": "Gesprek tonen",
"conversation.with": "Met {names}",
"copy_icon_button.copied": "Gekopieerd naar klembord",
"copypaste.copied": "Gekopieerd",
"copypaste.copy_to_clipboard": "Naar klembord kopiëren",
"directory.federated": "Fediverse (wat bekend is)",

View file

@ -21,6 +21,7 @@
"account.blocked": "Blokkert",
"account.browse_more_on_origin_server": "Sjå gjennom meir på den opphavlege profilen",
"account.cancel_follow_request": "Trekk attende fylgeførespurnad",
"account.copy": "Kopier lenka til profilen",
"account.direct": "Nevn @{name} privat",
"account.disable_notifications": "Slutt å varsle meg når @{name} skriv innlegg",
"account.domain_blocked": "Domenet er sperra",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Marker som lesen",
"conversation.open": "Sjå samtale",
"conversation.with": "Med {names}",
"copy_icon_button.copied": "Kopiert til utklyppstavla",
"copypaste.copied": "Kopiert",
"copypaste.copy_to_clipboard": "Kopier til utklyppstavla",
"directory.federated": "Frå den kjende allheimen",
@ -390,7 +392,7 @@
"lists.search": "Søk blant folk du fylgjer",
"lists.subheading": "Listene dine",
"load_pending": "{count, plural, one {# nytt element} other {# nye element}}",
"loading_indicator.label": "Laster…",
"loading_indicator.label": "Lastar…",
"media_gallery.toggle_visible": "{number, plural, one {Skjul bilete} other {Skjul bilete}}",
"moved_to_account_banner.text": "Kontoen din, {disabledAccount} er for tida deaktivert fordi du har flytta til {movedToAccount}.",
"mute_modal.duration": "Varigheit",
@ -479,17 +481,17 @@
"onboarding.follows.empty": "Me kan ikkje visa deg nokon resultat no. Du kan prøva å søkja eller bla gjennom utforsk-sida for å finna folk å fylgja, eller du kan prøva att seinare.",
"onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
"onboarding.follows.title": "Popular on Mastodon",
"onboarding.profile.discoverable": "Fremhevede profiler og innlegg i oppdagelsealgoritmer",
"onboarding.profile.display_name": "Visningsnavn",
"onboarding.profile.display_name_hint": "Ditt fulle navn eller ditt morsomme navn…",
"onboarding.profile.indexable": "Inkluder offentlige innlegg i søkeresultatene",
"onboarding.profile.lead": "Du kan alltid fullføre dette senere i innstillingene, der enda flere tilpasningsalternativer er tilgjengelige.",
"onboarding.profile.discoverable": "Ta med profilen og innlegga i oppdagingsalgoritmar",
"onboarding.profile.display_name": "Synleg namn",
"onboarding.profile.display_name_hint": "Det fulle namnet eller kallenamnet ditt…",
"onboarding.profile.indexable": "Ta med offentlege innlegg i søkjeresultat",
"onboarding.profile.lead": "Du kan alltid fullføra dette seinare i innstillingane, og der er det endå fleire tilpassingsalternativ.",
"onboarding.profile.note": "Om meg",
"onboarding.profile.note_hint": "Du kan @nevne andre eller #emneknagger…",
"onboarding.profile.save_and_continue": "Lagre og fortsett",
"onboarding.profile.title": "Konfigurering av profil",
"onboarding.profile.upload_avatar": "Last opp profilbilde",
"onboarding.profile.upload_header": "Last opp profiltoppbilde",
"onboarding.profile.note_hint": "Du kan @nemna folk eller #emneknaggar…",
"onboarding.profile.save_and_continue": "Lagre og hald fram",
"onboarding.profile.title": "Profiloppsett",
"onboarding.profile.upload_avatar": "Last opp profilbilete",
"onboarding.profile.upload_header": "Last opp profiltoppbilete",
"onboarding.share.lead": "La folk vita korleis dei kan finna deg på Mastodon!",
"onboarding.share.message": "Eg er {username} på #Mastodon! Du kan fylgja meg på {url}",
"onboarding.share.next_steps": "Dette kan du gjera no:",

View file

@ -21,6 +21,7 @@
"account.blocked": "Blokkert",
"account.browse_more_on_origin_server": "Bla mer på den opprinnelige profilen",
"account.cancel_follow_request": "Avbryt følgeforespørselen",
"account.copy": "Kopier lenke til profil",
"account.direct": "Nevn @{name} privat",
"account.disable_notifications": "Slutt å varsle meg når @{name} legger ut innlegg",
"account.domain_blocked": "Domene blokkert",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Marker som lest",
"conversation.open": "Vis samtale",
"conversation.with": "Med {names}",
"copy_icon_button.copied": "Kopiert til utklippstavlen",
"copypaste.copied": "Kopiert",
"copypaste.copy_to_clipboard": "Kopier til utklippstavle",
"directory.federated": "Fra det kjente strømiverset",

View file

@ -21,6 +21,7 @@
"account.blocked": "Zablokowany(-a)",
"account.browse_more_on_origin_server": "Zobacz więcej na oryginalnym profilu",
"account.cancel_follow_request": "Wycofaj żądanie obserwowania",
"account.copy": "Skopiuj odnośnik do profilu",
"account.direct": "Prywatna wzmianka @{name}",
"account.disable_notifications": "Przestań powiadamiać mnie o wpisach @{name}",
"account.domain_blocked": "Ukryto domenę",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Oznacz jako przeczytane",
"conversation.open": "Zobacz konwersację",
"conversation.with": "Z {names}",
"copy_icon_button.copied": "Skopiowano do schowka",
"copypaste.copied": "Skopiowano",
"copypaste.copy_to_clipboard": "Skopiuj do schowka",
"directory.federated": "Ze znanego fediwersum",

View file

@ -21,6 +21,7 @@
"account.blocked": "Bloqueado(a)",
"account.browse_more_on_origin_server": "Encontrar mais no perfil original",
"account.cancel_follow_request": "Retirar pedido para seguir",
"account.copy": "Copiar hiperligação para o perfil",
"account.direct": "Mencionar @{name} em privado",
"account.disable_notifications": "Parar de me notificar das publicações de @{name}",
"account.domain_blocked": "Domínio bloqueado",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Marcar como lida",
"conversation.open": "Ver conversa",
"conversation.with": "Com {names}",
"copy_icon_button.copied": "Copiado para a área de transferência",
"copypaste.copied": "Copiado",
"copypaste.copy_to_clipboard": "Copiar para a área de transferência",
"directory.federated": "Do fediverso conhecido",

View file

@ -21,6 +21,7 @@
"account.blocked": "E bllokuar",
"account.browse_more_on_origin_server": "Shfletoni më tepër rreth profilit origjinal",
"account.cancel_follow_request": "Tërhiq mbrapsht kërkesë për ndjekje",
"account.copy": "Kopjoje lidhjen te profili",
"account.direct": "Përmendje private për @{name}",
"account.disable_notifications": "Resht së njoftuari mua, kur poston @{name}",
"account.domain_blocked": "Përkatësia u bllokua",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Vëri shenjë si të lexuar",
"conversation.open": "Shfaq bisedën",
"conversation.with": "Me {names}",
"copy_icon_button.copied": "U kopjua në të papastër",
"copypaste.copied": "U kopjua",
"copypaste.copy_to_clipboard": "Kopjoje në të papastër",
"directory.federated": "Nga fedivers i njohur",

View file

@ -21,6 +21,7 @@
"account.blocked": "Блокиран",
"account.browse_more_on_origin_server": "Прегледајте још на оригиналном профилу",
"account.cancel_follow_request": "Откажи праћење",
"account.copy": "Копирај везу у профил",
"account.direct": "Приватно помени @{name}",
"account.disable_notifications": "Заустави обавештавање за објаве корисника @{name}",
"account.domain_blocked": "Домен је блокиран",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Означи као прочитано",
"conversation.open": "Прикажи разговор",
"conversation.with": "Са {names}",
"copy_icon_button.copied": "Копирано",
"copypaste.copied": "Копирано",
"copypaste.copy_to_clipboard": "Копирај",
"directory.federated": "Са знаног федиверзума",
@ -390,6 +392,7 @@
"lists.search": "Претражи међу људима које пратите",
"lists.subheading": "Ваше листе",
"load_pending": "{count, plural, one {# нова ставка} few {# нове ставке} other {# нових ставки}}",
"loading_indicator.label": "Учитавање…",
"media_gallery.toggle_visible": "{number, plural, one {Сакриј слику} few {Сакриј слике} other {Сакриј слике}}",
"moved_to_account_banner.text": "Ваш налог {disabledAccount} је тренутно онемогућен јер сте прешли на {movedToAccount}.",
"mute_modal.duration": "Трајање",

View file

@ -21,6 +21,7 @@
"account.blocked": "Blockerad",
"account.browse_more_on_origin_server": "Läs mer på den ursprungliga profilen",
"account.cancel_follow_request": "Återkalla din begäran om att få följa",
"account.copy": "Kopiera länk till profil",
"account.direct": "Nämn @{name} privat",
"account.disable_notifications": "Sluta notifiera mig när @{name} gör inlägg",
"account.domain_blocked": "Domän blockerad",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Markera som läst",
"conversation.open": "Visa konversation",
"conversation.with": "Med {names}",
"copy_icon_button.copied": "Kopierad till urklipp",
"copypaste.copied": "Kopierad",
"copypaste.copy_to_clipboard": "Kopiera till urklipp",
"directory.federated": "Från känt fediversum",

View file

@ -21,6 +21,7 @@
"account.blocked": "ปิดกั้นอยู่",
"account.browse_more_on_origin_server": "เรียกดูเพิ่มเติมในโปรไฟล์ดั้งเดิม",
"account.cancel_follow_request": "ยกเลิกการติดตาม",
"account.copy": "คัดลอกลิงก์ไปยังโปรไฟล์",
"account.direct": "กล่าวถึง @{name} แบบส่วนตัว",
"account.disable_notifications": "หยุดแจ้งเตือนฉันเมื่อ @{name} โพสต์",
"account.domain_blocked": "ปิดกั้นโดเมนอยู่",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "ทำเครื่องหมายว่าอ่านแล้ว",
"conversation.open": "ดูการสนทนา",
"conversation.with": "กับ {names}",
"copy_icon_button.copied": "คัดลอกไปยังคลิปบอร์ดแล้ว",
"copypaste.copied": "คัดลอกแล้ว",
"copypaste.copy_to_clipboard": "คัดลอกไปยังคลิปบอร์ด",
"directory.federated": "จากจักรวาลสหพันธ์ที่รู้จัก",
@ -481,15 +483,15 @@
"onboarding.follows.title": "ปรับแต่งฟีดหน้าแรกของคุณ",
"onboarding.profile.discoverable": "แสดงโปรไฟล์และโพสต์ในอัลกอริทึมการค้นพบ",
"onboarding.profile.display_name": "ชื่อที่แสดง",
"onboarding.profile.display_name_hint": "ชื่อเต็มหรือชื่อแบบสนุกสนานของคุณ",
"onboarding.profile.display_name_hint": "ชื่อเต็มของคุณหรือชื่อแบบสนุกสนานของคุณ",
"onboarding.profile.indexable": "รวมโพสต์สาธารณะในผลลัพธ์การค้นหา",
"onboarding.profile.lead": "คุณสามารถกลับมาทำต่อได้เสมอในการตั้งค่า ซึ่งจะมีตัวเลือกในการปรับแต่งมากกว่า",
"onboarding.profile.note": "ชีวประวัติ",
"onboarding.profile.note_hint": "คุณสามารถ @กล่าวถึง ผู้คนอื่น ๆ หรือ #แฮชแท็ก",
"onboarding.profile.note_hint": "คุณสามารถ @กล่าวถึง ผู้คนอื่น ๆ หรือ #แฮชแท็ก",
"onboarding.profile.save_and_continue": "บันทึกและดำเนินการต่อ",
"onboarding.profile.title": "การตั้งค่าโปรไฟล์",
"onboarding.profile.upload_avatar": "อัปโหลดรูปโปรไฟล์",
"onboarding.profile.upload_header": "อัปโหลดรูปส่วนหัวโปรไฟล์",
"onboarding.profile.upload_avatar": "อัปโหลดรูปภาพโปรไฟล์",
"onboarding.profile.upload_header": "อัปโหลดส่วนหัวโปรไฟล์",
"onboarding.share.lead": "แจ้งให้ผู้คนทราบวิธีที่เขาสามารถค้นหาคุณใน Mastodon!",
"onboarding.share.message": "ฉันคือ {username} ใน #Mastodon! มาติดตามฉันที่ {url}",
"onboarding.share.next_steps": "ขั้นตอนถัดไปที่เป็นไปได้:",

View file

@ -21,6 +21,7 @@
"account.blocked": "Заблоковані",
"account.browse_more_on_origin_server": "Переглянути більше в оригінальному профілі",
"account.cancel_follow_request": "Відкликати запит на стеження",
"account.copy": "Копіювати посилання на профіль",
"account.direct": "Особиста згадка @{name}",
"account.disable_notifications": "Не повідомляти мене про дописи @{name}",
"account.domain_blocked": "Домен заблоковано",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Позначити як прочитане",
"conversation.open": "Переглянути бесіду",
"conversation.with": "З {names}",
"copy_icon_button.copied": "Скопійовано до буфера обміну",
"copypaste.copied": "Скопійовано",
"copypaste.copy_to_clipboard": "Копіювати до буфера обміну",
"directory.federated": "З відомого федесвіту",

View file

@ -21,6 +21,7 @@
"account.blocked": "已屏蔽",
"account.browse_more_on_origin_server": "在原始个人资料页面上浏览详情",
"account.cancel_follow_request": "撤回关注请求",
"account.copy": "复制资料卡链接",
"account.direct": "私下提及 @{name}",
"account.disable_notifications": "当 @{name} 发布嘟文时不要通知我",
"account.domain_blocked": "域名已屏蔽",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "标记为已读",
"conversation.open": "查看对话",
"conversation.with": "与 {names}",
"copy_icon_button.copied": "已复制到剪贴板",
"copypaste.copied": "已复制",
"copypaste.copy_to_clipboard": "复制到剪贴板",
"directory.federated": "来自已知的联邦宇宙",

View file

@ -21,6 +21,7 @@
"account.blocked": "已封鎖",
"account.browse_more_on_origin_server": "前往原始的個人檔案頁瀏覽更多",
"account.cancel_follow_request": "撤回追蹤請求",
"account.copy": "複製個人檔案連結",
"account.direct": "私下提及 @{name}",
"account.disable_notifications": "當 @{name} 發文時不要再通知我",
"account.domain_blocked": "網域被封鎖",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "標為已讀",
"conversation.open": "檢視對話",
"conversation.with": "與 {names}",
"copy_icon_button.copied": "已複製到剪貼簿",
"copypaste.copied": "已複製",
"copypaste.copy_to_clipboard": "複製到剪貼簿",
"directory.federated": "來自已知的聯盟網絡",

View file

@ -21,6 +21,7 @@
"account.blocked": "已封鎖",
"account.browse_more_on_origin_server": "在該伺服器上的個人檔案頁面瀏覽更多",
"account.cancel_follow_request": "收回跟隨請求",
"account.copy": "複製個人檔案連結",
"account.direct": "私訊 @{name}",
"account.disable_notifications": "取消來自 @{name} 嘟文的通知",
"account.domain_blocked": "已封鎖網域",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "標記為已讀",
"conversation.open": "檢視對話",
"conversation.with": "與 {names}",
"copy_icon_button.copied": "已複製到剪貼簿",
"copypaste.copied": "已複製",
"copypaste.copy_to_clipboard": "複製到剪貼簿",
"directory.federated": "來自已知聯邦宇宙",

View file

@ -2382,8 +2382,7 @@ $ui-header-height: 55px;
> .scrollable {
background: $ui-base-color;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-radius: 0 0 4px 4px;
}
}

View file

@ -1223,10 +1223,6 @@ code {
}
.app-form {
& > * {
margin-bottom: 16px;
}
&__avatar-input,
&__header-input {
display: block;
@ -1291,4 +1287,49 @@ code {
&__header-input {
aspect-ratio: 580/193;
}
&__toggle {
display: flex;
align-items: center;
gap: 16px;
color: $darker-text-color;
font-size: 14px;
line-height: 20px;
.icon {
flex: 0 0 auto;
}
.icon {
width: 24px;
height: 24px;
}
&__label {
flex: 1 1 auto;
strong {
color: $primary-text-color;
font-weight: 600;
}
.recommended {
position: absolute;
margin: 0 4px;
margin-top: -2px;
}
}
&__toggle {
flex: 0 0 auto;
display: flex;
align-items: center;
}
&__toggle > div {
display: flex;
border-inline-start: 1px solid lighten($ui-base-color, 8%);
padding-inline-start: 16px;
}
}
}

View file

@ -611,6 +611,7 @@ es:
created_at: Denunciado
delete_and_resolve: Eliminar publicaciones
forwarded: Reenviado
forwarded_replies_explanation: Este informe es de un usuario remoto y sobre contenido remoto. Se le ha enviado porque el contenido reportado es en respuesta a uno de sus usuarios.
forwarded_to: Reenviado a %{domain}
mark_as_resolved: Marcar como resuelto
mark_as_sensitive: Marcar como sensible

View file

@ -613,6 +613,7 @@ eu:
created_at: Salatua
delete_and_resolve: Ezabatu bidalketak
forwarded: Birbidalia
forwarded_replies_explanation: Salaketa hau beste instantzia bateko erabiltzaile baten edukiari buruzkoa da, eta zure instantziako erabiltzaile bati egidako erantzuna delako bidali dizugu mezu hau.
forwarded_to: 'Hona birbidalia: %{domain}'
mark_as_resolved: Markatu konpondutako gisa
mark_as_sensitive: Markatu hunkigarri gisa

View file

@ -384,7 +384,7 @@ gl:
domain_blocks:
add_new: Engadir novo bloqueo de dominio
confirm_suspension:
cancel: Cancelar
cancel: Desbotar
confirm: Suspender
permanent_action: Ao retirar a suspensión non restableces os datos ou a relación.
preamble_html: Vas suspender a <strong>%{domain}</strong> e os seus subdominios.
@ -611,6 +611,7 @@ gl:
created_at: Denunciado
delete_and_resolve: Eliminar publicacións
forwarded: Reenviado
forwarded_replies_explanation: Esta denuncia procede dunha usuaria remota e acerca de contido remoto. Enviouseche unha copia porque o contido denunciado é unha resposta a unha das túas usuarias.
forwarded_to: Reenviado a %{domain}
mark_as_resolved: Marcar como resolto
mark_as_sensitive: Marcar como sensible

View file

@ -611,7 +611,7 @@ hu:
created_at: Jelentve
delete_and_resolve: Bejegyzések törlése
forwarded: Továbbítva
forwarded_replies_explanation: Ez a jelentés egy távoli felhasználótól származik, és távoli tartalomról szól. Azért lett neked továbbítva, mert a jelentett tartalom az egyik felhasználódnak küldött válasz.
forwarded_replies_explanation: Ez a jelentés egy távoli flehasználótól származik, és távoli tartalomról szól. Azért lett neked továbbítva, mert a jelentett tartalom az egyik felhasználódnak küldött válasz.
forwarded_to: 'Továbbítva ide: %{domain}'
mark_as_resolved: Megjelölés megoldottként
mark_as_sensitive: Érzékenynek jelölés

View file

@ -763,7 +763,7 @@ ko:
open: 누구나 가입 할 수 있음
security:
authorized_fetch: 연합된 서버들에게서 인증 필수
authorized_fetch_hint: 연합된 서버들에게서 인증을 요구하는 것은 사용자 레벨과 서버 레벨의 차단 좀 더 확실하게 해줍니다. 한편으로는 성능적인 페널티, 답글의 전달 범위 감소, 몇몇 연합된 서비스들과의 호환성 문제가 있을 가능성이 있습니다. 추가적으로 이 기능은 전용 액터가 공개돤 게시물이나 계정을 페치하는 것은 막지 않습니다.
authorized_fetch_hint: 연합된 서버들에게서 인증을 요구하는 것은 사용자 레벨과 서버 레벨의 차단 좀 더 확실하게 해줍니다. 한편으로는 성능적인 페널티, 답글의 전달 범위 감소, 몇몇 연합된 서비스들과의 호환성 문제가 있을 가능성이 있습니다. 추가적으로 이 기능은 전용 액터가 공개돤 게시물이나 계정을 페치하는 것은 막지 않습니다.
authorized_fetch_overridden_hint: 현재 이 값은 환경변수에 의해 설정되어 있기에 설정을 변경할 수 없습니다.
federation_authentication: 연합 인증 필수
title: 서버 설정

View file

@ -534,7 +534,7 @@ nn:
total_reported: Rapportar om dei
total_storage: Medievedlegg
totals_time_period_hint_html: Totalsum vist nedanfor gjeld data for alle tidsperiodar.
unknown_instance: Dette domenet er ukjent for denne serveren.
unknown_instance: Domenet er ukjent for denne tenaren.
invites:
deactivate_all: Slå av alle
filter:
@ -611,7 +611,7 @@ nn:
created_at: Rapportert
delete_and_resolve: Slett innlegg
forwarded: Videresendt
forwarded_replies_explanation: Denne rapporten er fra en ekstern bruker og handler om eksternt innhold. Den er videresendt til deg fordi det rapporterte innholdet svarer til en av brukerne dine.
forwarded_replies_explanation: Denne rapporten gjeld innhald på ein annan nettstad. Rapporten er vidaresend til deg fordi det rapporterte innhaldet er eit svar på noko ein av brukarane på nettstaden din har skrive.
forwarded_to: Videresendt til %{domain}
mark_as_resolved: Merk som løyst
mark_as_sensitive: Marker som ømtolig
@ -1042,14 +1042,14 @@ nn:
hint_html: Berre ein ting til! Vi må bekrefte at du er et menneske (så vi kan halde spam ute!). Løys CAPTCHA-en nedanfor og klikk "Fortsett".
title: Sikkerheitssjekk
confirmations:
awaiting_review: Din e-post adresse er bekreftet! %{domain} ansatte gjennomgår nå registreringen din. Du vil motta en e-post hvis de godkjenner din konto!
awaiting_review_title: Din registrering blir vurdert
clicking_this_link: klikke på denne lenken
login_link: logg inn
proceed_to_login_html: Du kan nå fortsette til %{login_link}.
redirect_to_app_html: Du burde bli omdirigert til <strong>%{app_name}</strong> -appen. Hvis det ikke skjedde, kan du prøve %{clicking_this_link} eller manuelt gå tilbake til appen.
registration_complete: Registreringen på %{domain} er nå fullført!
welcome_title: Velkommen, %{name}!
awaiting_review: Epostadressa di er stadfesta! Styrarane på %{domain} ser gjennom registreringa di. Du får ein epost frå dei om dei godkjenner brukarkontoen din.
awaiting_review_title: Me går gjennom registreringa di
clicking_this_link: klikka på denne lenka
login_link: logga inn
proceed_to_login_html: No kan du %{login_link}.
redirect_to_app_html: Du skulle vorte vidaresend til <strong>%{app_name}</strong>-appen. Viss det ikkje skjedde, kan du prøva å %{clicking_this_link} eller manuelt gå tilbake til appen.
registration_complete: Du har registrert deg som brukar på %{domain}.
welcome_title: Velkomen, %{name}!
wrong_email_hint: Viss epostadressa er feil, kan du endra ho i kontoinnstillingane.
delete_account: Slett konto
delete_account_html: Om du vil sletta kontoen din, kan du <a href="%{path}">gå hit</a>. Du vert spurd etter stadfesting.
@ -1111,7 +1111,7 @@ nn:
functional: Kontoen din er fullt operativt.
pending: Søknaden din ventar på gjennomgang frå personalet vårt. Dette kan taka litt tid. Du får ein e-post om søknaden din vert godkjend.
redirecting_to: Kontoen din er inaktiv fordi den for øyeblikket omdirigerer til %{acct}.
self_destruct: Siden %{domain} stenger, vil du kun ha begrenset tilgang til kontoen din.
self_destruct: Av di %{domain} er i ferd med å stenga, vil du berre få avgrensa tilgang til brukarkontoen din.
view_strikes: Vis tidligere advarsler mot kontoen din
too_fast: Skjemaet ble sendt inn for raskt, prøv på nytt.
use_security_key: Bruk sikkerhetsnøkkel
@ -1583,8 +1583,8 @@ nn:
over_total_limit: Du har overskredet grensen på %{limit} planlagte tuter
too_soon: Den planlagte datoen må være i fremtiden
self_destruct:
lead_html: Dessverre stenger <strong>%{domain}</strong> for alltid. Hvis du hadde en konto der vil du ikke kunne fortsette å bruke den, men du kan fremdeles be om en sikkerhetskopi av dataene dine.
title: Denne serveren stenger
lead_html: Diverre stengjer <strong>%{domain}</strong> dørene for godt. Viss du hadde ein brukarkonto der, vil du ikkje kunna halda fram å bruka han, men du kan få ut ein tryggingskopi av dataa dine.
title: Denne tenaren stengjer
sessions:
activity: Siste aktivitet
browser: Nettlesar

View file

@ -1533,7 +1533,7 @@
posting_defaults: Innleggsstandarder
public_timelines: Offentlige tidslinjer
privacy:
hint_html: "<strong>Tilpass hvordan du vil at din profil og dine innlegg skal bli funnet.</strong> En rekke funksjoner i Mastodon kan hjelpe deg med å nå et bredere publikum når de aktiverte. Ta deg et øyeblikk til å vurdere disse innstillingene for å forsikre deg om at de passer deg og ditt bruk."
hint_html: "<strong>Tilpass hvordan du vil at din profil og dine innlegg skal bli funnet.</strong> En rekke funksjoner i Mastodon kan hjelpe deg med å nå et bredere publikum når det er aktivert. Ta deg et øyeblikk til å vurdere disse innstillingene for å forsikre deg om at de passer deg og ditt bruk."
privacy: Personvern
privacy_hint_html: Kontrollér hvor mye du ønsker å dele til fordel for andre. Folk oppdager interessante profiler og kule app'er ved å bla gjennom andres følgere og ved å se hvilke app'er de bruker, men du kan velge å holde det skjult.
reach: Rekkevidde

View file

@ -623,6 +623,7 @@ sr:
created_at: Пријављена
delete_and_resolve: Обриши објаве
forwarded: Прослеђено
forwarded_replies_explanation: Овај извештај је од удаљеног корисника и о удаљеном садржају. Прослеђен вам је јер је пријављени садржај у одговору једном од ваших корисника.
forwarded_to: Прослеђено ка %{domain}
mark_as_resolved: Означи као решену
mark_as_sensitive: Обележи као осетљиво

View file

@ -560,7 +560,7 @@ th:
enabled: เปิดใช้งานอยู่
inbox_url: URL ของรีเลย์
pending: กำลังรอการอนุมัติของรีเลย์
save_and_enable: บันทึกแล้วเปิดใช้งาน
save_and_enable: บันทึกแลเปิดใช้งาน
setup: ตั้งค่าการเชื่อมต่อรีเลย์
signatures_not_enabled: รีเลย์อาจทำงานไม่ถูกต้องขณะที่มีการเปิดใช้งานโหมดปลอดภัยหรือโหมดการติดต่อกับภายนอกแบบจำกัด
status: สถานะ
@ -599,7 +599,7 @@ th:
created_at: รายงานเมื่อ
delete_and_resolve: ลบโพสต์
forwarded: ส่งต่อแล้ว
forwarded_replies_explanation: รายงานนี้มาจากผู้ใช้ระยะไกล และเป็นรายงานเกี่ยวกับเนื้อหาระยะไกล ซึ่งถูกส่งต่อมาหาคุณเนื่องจากเนื้อหาที่ถูกรายงานอยู่ในการตอบกลับไปยังหนึ่งในผู้ใช้ของคุณ
forwarded_replies_explanation: รายงานนี้มาจากผู้ใช้ระยะไกลและเกี่ยวกับเนื้อหาระยะไกล มีการส่งต่อรายงานไปยังคุณเนื่องจากเนื้อหาที่รายงานอยู่ในการตอบกลับหนึ่งในผู้ใช้ของคุณ
forwarded_to: ส่งต่อไปยัง %{domain} แล้ว
mark_as_resolved: ทำเครื่องหมายว่าแก้ปัญหาแล้ว
mark_as_sensitive: ทำเครื่องหมายว่าละเอียดอ่อน

View file

@ -611,6 +611,7 @@ tr:
created_at: Şikayet edildi
delete_and_resolve: Gönderileri sil
forwarded: İletildi
forwarded_replies_explanation: Bu bildirim başka bir sunucudaki kullanıcı ve içerik ile ilgili. Bildirilen içerik kullanıcılarınızdan birine yanıt şeklinde olduğu için size yönlendirildi.
forwarded_to: "%{domain}'e iletildi"
mark_as_resolved: Giderildi olarak işaretle
mark_as_sensitive: Hassas olarak işaretle

View file

@ -599,6 +599,7 @@ zh-CN:
created_at: 举报时间
delete_and_resolve: 删除嘟文
forwarded: 已转发
forwarded_replies_explanation: 该举报来自外站用户,涉及外站内容。之所以转发给您,是因为被举报的内容是对您站点一位用户的回复。
forwarded_to: 转发举报至 %{domain}
mark_as_resolved: 标记为已处理
mark_as_sensitive: 标记为敏感内容

View file

@ -9,13 +9,3 @@ if Rake::Task.task_defined?('spec:system')
Rake::Task['spec:system'].enhance ['spec:enable_system_specs']
end
if Rake::Task.task_defined?('spec:search')
namespace :spec do
task :enable_search_specs do # rubocop:disable Rails/RakeEnvironment
ENV['RUN_SEARCH_SPECS'] = 'true'
end
end
Rake::Task['spec:search'].enhance ['spec:enable_search_specs']
end

View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
Fabricator(:account_deletion_request) do
account
end

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
Fabricator(:import) do
account
type :following
data { attachment_fixture('imports.txt') }
end

View file

@ -13,14 +13,13 @@ RSpec.describe AdminMailer do
recipient.user.update(locale: :en)
end
it 'renders the headers' do
expect(mail.subject).to eq("New report for cb6e6126.ngrok.io (##{report.id})")
expect(mail.to).to eq [recipient.user_email]
expect(mail.from).to eq ['notifications@localhost']
end
it 'renders the body' do
expect(mail.body.encoded).to eq("Mike,\r\n\r\nJohn has reported Mike\r\n\r\nView: https://cb6e6126.ngrok.io/admin/reports/#{report.id}\r\n")
it 'renders the email' do
expect(mail)
.to be_present
.and(deliver_to(recipient.user_email))
.and(deliver_from('notifications@localhost'))
.and(have_subject("New report for cb6e6126.ngrok.io (##{report.id})"))
.and(have_body_text("Mike,\r\n\r\nJohn has reported Mike\r\n\r\nView: https://cb6e6126.ngrok.io/admin/reports/#{report.id}\r\n"))
end
end
@ -33,14 +32,13 @@ RSpec.describe AdminMailer do
recipient.user.update(locale: :en)
end
it 'renders the headers' do
expect(mail.subject).to eq("#{appeal.account.username} is appealing a moderation decision on cb6e6126.ngrok.io")
expect(mail.to).to eq [recipient.user_email]
expect(mail.from).to eq ['notifications@localhost']
end
it 'renders the body' do
expect(mail.body.encoded).to match "#{appeal.account.username} is appealing a moderation decision by #{appeal.strike.account.username}"
it 'renders the email' do
expect(mail)
.to be_present
.and(deliver_to(recipient.user_email))
.and(deliver_from('notifications@localhost'))
.and(have_subject("#{appeal.account.username} is appealing a moderation decision on cb6e6126.ngrok.io"))
.and(have_body_text("#{appeal.account.username} is appealing a moderation decision by #{appeal.strike.account.username}"))
end
end
@ -53,14 +51,13 @@ RSpec.describe AdminMailer do
recipient.user.update(locale: :en)
end
it 'renders the headers' do
expect(mail.subject).to eq("New account up for review on cb6e6126.ngrok.io (#{user.account.username})")
expect(mail.to).to eq [recipient.user_email]
expect(mail.from).to eq ['notifications@localhost']
end
it 'renders the body' do
expect(mail.body.encoded).to match 'The details of the new account are below. You can approve or reject this application.'
it 'renders the email' do
expect(mail)
.to be_present
.and(deliver_to(recipient.user_email))
.and(deliver_from('notifications@localhost'))
.and(have_subject("New account up for review on cb6e6126.ngrok.io (#{user.account.username})"))
.and(have_body_text('The details of the new account are below. You can approve or reject this application.'))
end
end
@ -95,14 +92,13 @@ RSpec.describe AdminMailer do
recipient.user.update(locale: :en)
end
it 'renders the headers' do
expect(mail.subject).to eq('New trends up for review on cb6e6126.ngrok.io')
expect(mail.to).to eq [recipient.user_email]
expect(mail.from).to eq ['notifications@localhost']
end
it 'renders the body' do
expect(mail.body.encoded).to match 'The following items need a review before they can be displayed publicly'
it 'renders the email' do
expect(mail)
.to be_present
.and(deliver_to(recipient.user_email))
.and(deliver_from('notifications@localhost'))
.and(have_subject('New trends up for review on cb6e6126.ngrok.io'))
.and(have_body_text('The following items need a review before they can be displayed publicly'))
end
end
@ -114,14 +110,13 @@ RSpec.describe AdminMailer do
recipient.user.update(locale: :en)
end
it 'renders the headers' do
expect(mail.subject).to eq('New Mastodon versions are available for cb6e6126.ngrok.io!')
expect(mail.to).to eq [recipient.user_email]
expect(mail.from).to eq ['notifications@localhost']
end
it 'renders the body' do
expect(mail.body.encoded).to match 'New Mastodon versions have been released, you may want to update!'
it 'renders the email' do
expect(mail)
.to be_present
.and(deliver_to(recipient.user_email))
.and(deliver_from('notifications@localhost'))
.and(have_subject('New Mastodon versions are available for cb6e6126.ngrok.io!'))
.and(have_body_text('New Mastodon versions have been released, you may want to update!'))
end
end
@ -133,18 +128,16 @@ RSpec.describe AdminMailer do
recipient.user.update(locale: :en)
end
it 'renders the headers', :aggregate_failures do
expect(mail.subject).to eq('Critical Mastodon updates are available for cb6e6126.ngrok.io!')
expect(mail.to).to eq [recipient.user_email]
expect(mail.from).to eq ['notifications@localhost']
expect(mail['Importance'].value).to eq 'high'
expect(mail['Priority'].value).to eq 'urgent'
expect(mail['X-Priority'].value).to eq '1'
end
it 'renders the body' do
expect(mail.body.encoded).to match 'New critical versions of Mastodon have been released, you may want to update as soon as possible!'
it 'renders the email' do
expect(mail)
.to be_present
.and(deliver_to(recipient.user_email))
.and(deliver_from('notifications@localhost'))
.and(have_subject('Critical Mastodon updates are available for cb6e6126.ngrok.io!'))
.and(have_body_text('New critical versions of Mastodon have been released, you may want to update as soon as possible!'))
.and(have_header('Importance', 'high'))
.and(have_header('Priority', 'urgent'))
.and(have_header('X-Priority', '1'))
end
end
end

View file

@ -8,24 +8,27 @@ RSpec.describe NotificationMailer do
let(:foreign_status) { Fabricate(:status, account: sender, text: 'The body of the foreign status') }
let(:own_status) { Fabricate(:status, account: receiver.account, text: 'The body of the own status') }
shared_examples 'headers' do |type, thread|
it 'renders the to and from headers' do
expect(mail[:to].value).to eq "#{receiver.account.username} <#{receiver.email}>"
expect(mail.from).to eq ['notifications@localhost']
shared_examples 'standard headers' do |type|
it 'renders the email' do
expect(mail)
.to be_present
.and(have_header('To', "#{receiver.account.username} <#{receiver.email}>"))
.and(have_header('List-ID', "<#{type}.alice.cb6e6126.ngrok.io>"))
.and(have_header('List-Unsubscribe', %r{<https://cb6e6126.ngrok.io/unsubscribe\?token=.+>}))
.and(have_header('List-Unsubscribe', /&type=#{type}/))
.and(have_header('List-Unsubscribe-Post', 'List-Unsubscribe=One-Click'))
.and(deliver_to("#{receiver.account.username} <#{receiver.email}>"))
.and(deliver_from('notifications@localhost'))
end
end
it 'renders the list headers' do
expect(mail['List-ID'].value).to eq "<#{type}.alice.cb6e6126.ngrok.io>"
expect(mail['List-Unsubscribe'].value).to match(%r{<https://cb6e6126.ngrok.io/unsubscribe\?token=.+>})
expect(mail['List-Unsubscribe'].value).to match("&type=#{type}")
expect(mail['List-Unsubscribe-Post'].value).to eq 'List-Unsubscribe=One-Click'
end
if thread
it 'renders the thread headers' do
expect(mail['In-Reply-To'].value).to match(/<conversation-\d+.\d\d\d\d-\d\d-\d\d@cb6e6126.ngrok.io>/)
expect(mail['References'].value).to match(/<conversation-\d+.\d\d\d\d-\d\d-\d\d@cb6e6126.ngrok.io>/)
end
shared_examples 'thread headers' do
it 'renders the email with conversation thread headers' do
conversation_header_regex = /<conversation-\d+.\d\d\d\d-\d\d-\d\d@cb6e6126.ngrok.io>/
expect(mail)
.to be_present
.and(have_header('In-Reply-To', conversation_header_regex))
.and(have_header('References', conversation_header_regex))
end
end
@ -35,15 +38,15 @@ RSpec.describe NotificationMailer do
let(:mail) { prepared_mailer_for(receiver.account).mention }
include_examples 'localized subject', 'notification_mailer.mention.subject', name: 'bob'
include_examples 'headers', 'mention', true
include_examples 'standard headers', 'mention'
include_examples 'thread headers'
it 'renders the subject' do
expect(mail.subject).to eq('You were mentioned by bob')
end
it 'renders the body' do
expect(mail.body.encoded).to match('You were mentioned by bob')
expect(mail.body.encoded).to include 'The body of the foreign status'
it 'renders the email' do
expect(mail)
.to be_present
.and(have_subject('You were mentioned by bob'))
.and(have_body_text('You were mentioned by bob'))
.and(have_body_text('The body of the foreign status'))
end
end
@ -53,14 +56,13 @@ RSpec.describe NotificationMailer do
let(:mail) { prepared_mailer_for(receiver.account).follow }
include_examples 'localized subject', 'notification_mailer.follow.subject', name: 'bob'
include_examples 'headers', 'follow', false
include_examples 'standard headers', 'follow'
it 'renders the subject' do
expect(mail.subject).to eq('bob is now following you')
end
it 'renders the body' do
expect(mail.body.encoded).to match('bob is now following you')
it 'renders the email' do
expect(mail)
.to be_present
.and(have_subject('bob is now following you'))
.and(have_body_text('bob is now following you'))
end
end
@ -70,15 +72,15 @@ RSpec.describe NotificationMailer do
let(:mail) { prepared_mailer_for(own_status.account).favourite }
include_examples 'localized subject', 'notification_mailer.favourite.subject', name: 'bob'
include_examples 'headers', 'favourite', true
include_examples 'standard headers', 'favourite'
include_examples 'thread headers'
it 'renders the subject' do
expect(mail.subject).to eq('bob favorited your post')
end
it 'renders the body' do
expect(mail.body.encoded).to match('Your post was favorited by bob')
expect(mail.body.encoded).to include 'The body of the own status'
it 'renders the email' do
expect(mail)
.to be_present
.and(have_subject('bob favorited your post'))
.and(have_body_text('Your post was favorited by bob'))
.and(have_body_text('The body of the own status'))
end
end
@ -88,15 +90,15 @@ RSpec.describe NotificationMailer do
let(:mail) { prepared_mailer_for(own_status.account).reblog }
include_examples 'localized subject', 'notification_mailer.reblog.subject', name: 'bob'
include_examples 'headers', 'reblog', true
include_examples 'standard headers', 'reblog'
include_examples 'thread headers'
it 'renders the subject' do
expect(mail.subject).to eq('bob boosted your post')
end
it 'renders the body' do
expect(mail.body.encoded).to match('Your post was boosted by bob')
expect(mail.body.encoded).to include 'The body of the own status'
it 'renders the email' do
expect(mail)
.to be_present
.and(have_subject('bob boosted your post'))
.and(have_body_text('Your post was boosted by bob'))
.and(have_body_text('The body of the own status'))
end
end
@ -106,14 +108,13 @@ RSpec.describe NotificationMailer do
let(:mail) { prepared_mailer_for(receiver.account).follow_request }
include_examples 'localized subject', 'notification_mailer.follow_request.subject', name: 'bob'
include_examples 'headers', 'follow_request', false
include_examples 'standard headers', 'follow_request'
it 'renders the subject' do
expect(mail.subject).to eq('Pending follower: bob')
end
it 'renders the body' do
expect(mail.body.encoded).to match('bob has requested to follow you')
it 'renders the email' do
expect(mail)
.to be_present
.and(have_subject('Pending follower: bob'))
.and(have_body_text('bob has requested to follow you'))
end
end

View file

@ -10,9 +10,12 @@ describe UserMailer do
it 'renders confirmation instructions' do
receiver.update!(locale: nil)
expect(mail.body.encoded).to include I18n.t('devise.mailer.confirmation_instructions.title')
expect(mail.body.encoded).to include 'spec'
expect(mail.body.encoded).to include Rails.configuration.x.local_domain
expect(mail)
.to be_present
.and(have_body_text(I18n.t('devise.mailer.confirmation_instructions.title')))
.and(have_body_text('spec'))
.and(have_body_text(Rails.configuration.x.local_domain))
end
include_examples 'localized subject',
@ -25,13 +28,17 @@ describe UserMailer do
it 'renders reconfirmation instructions' do
receiver.update!(email: 'new-email@example.com', locale: nil)
expect(mail.body.encoded).to include I18n.t('devise.mailer.reconfirmation_instructions.title')
expect(mail.body.encoded).to include 'spec'
expect(mail.body.encoded).to include Rails.configuration.x.local_domain
expect(mail.subject).to eq I18n.t('devise.mailer.reconfirmation_instructions.subject',
instance: Rails.configuration.x.local_domain,
locale: I18n.default_locale)
expect(mail)
.to be_present
.and(have_body_text(I18n.t('devise.mailer.reconfirmation_instructions.title')))
.and(have_body_text('spec'))
.and(have_body_text(Rails.configuration.x.local_domain))
end
include_examples 'localized subject',
'devise.mailer.confirmation_instructions.subject',
instance: Rails.configuration.x.local_domain
end
describe '#reset_password_instructions' do
@ -39,8 +46,11 @@ describe UserMailer do
it 'renders reset password instructions' do
receiver.update!(locale: nil)
expect(mail.body.encoded).to include I18n.t('devise.mailer.reset_password_instructions.title')
expect(mail.body.encoded).to include 'spec'
expect(mail)
.to be_present
.and(have_body_text(I18n.t('devise.mailer.reset_password_instructions.title')))
.and(have_body_text('spec'))
end
include_examples 'localized subject',
@ -52,7 +62,10 @@ describe UserMailer do
it 'renders password change notification' do
receiver.update!(locale: nil)
expect(mail.body.encoded).to include I18n.t('devise.mailer.password_change.title')
expect(mail)
.to be_present
.and(have_body_text(I18n.t('devise.mailer.password_change.title')))
end
include_examples 'localized subject',
@ -64,7 +77,10 @@ describe UserMailer do
it 'renders email change notification' do
receiver.update!(locale: nil)
expect(mail.body.encoded).to include I18n.t('devise.mailer.email_changed.title')
expect(mail)
.to be_present
.and(have_body_text(I18n.t('devise.mailer.email_changed.title')))
end
include_examples 'localized subject',
@ -77,8 +93,11 @@ describe UserMailer do
it 'renders warning notification' do
receiver.update!(locale: nil)
expect(mail.body.encoded).to include I18n.t('user_mailer.warning.title.suspend', acct: receiver.account.acct)
expect(mail.body.encoded).to include strike.text
expect(mail)
.to be_present
.and(have_body_text(I18n.t('user_mailer.warning.title.suspend', acct: receiver.account.acct)))
.and(have_body_text(strike.text))
end
end
@ -88,7 +107,10 @@ describe UserMailer do
it 'renders webauthn credential deleted notification' do
receiver.update!(locale: nil)
expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_credential.deleted.title')
expect(mail)
.to be_present
.and(have_body_text(I18n.t('devise.mailer.webauthn_credential.deleted.title')))
end
include_examples 'localized subject',
@ -103,7 +125,10 @@ describe UserMailer do
it 'renders suspicious sign in notification' do
receiver.update!(locale: nil)
expect(mail.body.encoded).to include I18n.t('user_mailer.suspicious_sign_in.explanation')
expect(mail)
.to be_present
.and(have_body_text(I18n.t('user_mailer.suspicious_sign_in.explanation')))
end
include_examples 'localized subject',
@ -115,8 +140,10 @@ describe UserMailer do
let(:mail) { described_class.appeal_approved(receiver, appeal) }
it 'renders appeal_approved notification' do
expect(mail.subject).to eq I18n.t('user_mailer.appeal_approved.subject', date: I18n.l(appeal.created_at))
expect(mail.body.encoded).to include I18n.t('user_mailer.appeal_approved.title')
expect(mail)
.to be_present
.and(have_subject(I18n.t('user_mailer.appeal_approved.subject', date: I18n.l(appeal.created_at))))
.and(have_body_text(I18n.t('user_mailer.appeal_approved.title')))
end
end
@ -125,8 +152,10 @@ describe UserMailer do
let(:mail) { described_class.appeal_rejected(receiver, appeal) }
it 'renders appeal_rejected notification' do
expect(mail.subject).to eq I18n.t('user_mailer.appeal_rejected.subject', date: I18n.l(appeal.created_at))
expect(mail.body.encoded).to include I18n.t('user_mailer.appeal_rejected.title')
expect(mail)
.to be_present
.and(have_subject(I18n.t('user_mailer.appeal_rejected.subject', date: I18n.l(appeal.created_at))))
.and(have_body_text(I18n.t('user_mailer.appeal_rejected.title')))
end
end
@ -134,8 +163,10 @@ describe UserMailer do
let(:mail) { described_class.two_factor_enabled(receiver) }
it 'renders two_factor_enabled mail' do
expect(mail.subject).to eq I18n.t('devise.mailer.two_factor_enabled.subject')
expect(mail.body.encoded).to include I18n.t('devise.mailer.two_factor_enabled.explanation')
expect(mail)
.to be_present
.and(have_subject(I18n.t('devise.mailer.two_factor_enabled.subject')))
.and(have_body_text(I18n.t('devise.mailer.two_factor_enabled.explanation')))
end
end
@ -143,8 +174,10 @@ describe UserMailer do
let(:mail) { described_class.two_factor_disabled(receiver) }
it 'renders two_factor_disabled mail' do
expect(mail.subject).to eq I18n.t('devise.mailer.two_factor_disabled.subject')
expect(mail.body.encoded).to include I18n.t('devise.mailer.two_factor_disabled.explanation')
expect(mail)
.to be_present
.and(have_subject(I18n.t('devise.mailer.two_factor_disabled.subject')))
.and(have_body_text(I18n.t('devise.mailer.two_factor_disabled.explanation')))
end
end
@ -152,8 +185,10 @@ describe UserMailer do
let(:mail) { described_class.webauthn_enabled(receiver) }
it 'renders webauthn_enabled mail' do
expect(mail.subject).to eq I18n.t('devise.mailer.webauthn_enabled.subject')
expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_enabled.explanation')
expect(mail)
.to be_present
.and(have_subject(I18n.t('devise.mailer.webauthn_enabled.subject')))
.and(have_body_text(I18n.t('devise.mailer.webauthn_enabled.explanation')))
end
end
@ -161,8 +196,10 @@ describe UserMailer do
let(:mail) { described_class.webauthn_disabled(receiver) }
it 'renders webauthn_disabled mail' do
expect(mail.subject).to eq I18n.t('devise.mailer.webauthn_disabled.subject')
expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_disabled.explanation')
expect(mail)
.to be_present
.and(have_subject(I18n.t('devise.mailer.webauthn_disabled.subject')))
.and(have_body_text(I18n.t('devise.mailer.webauthn_disabled.explanation')))
end
end
@ -170,8 +207,10 @@ describe UserMailer do
let(:mail) { described_class.two_factor_recovery_codes_changed(receiver) }
it 'renders two_factor_recovery_codes_changed mail' do
expect(mail.subject).to eq I18n.t('devise.mailer.two_factor_recovery_codes_changed.subject')
expect(mail.body.encoded).to include I18n.t('devise.mailer.two_factor_recovery_codes_changed.explanation')
expect(mail)
.to be_present
.and(have_subject(I18n.t('devise.mailer.two_factor_recovery_codes_changed.subject')))
.and(have_body_text(I18n.t('devise.mailer.two_factor_recovery_codes_changed.explanation')))
end
end
@ -180,8 +219,10 @@ describe UserMailer do
let(:mail) { described_class.webauthn_credential_added(receiver, credential) }
it 'renders webauthn_credential_added mail' do
expect(mail.subject).to eq I18n.t('devise.mailer.webauthn_credential.added.subject')
expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_credential.added.explanation')
expect(mail)
.to be_present
.and(have_subject(I18n.t('devise.mailer.webauthn_credential.added.subject')))
.and(have_body_text(I18n.t('devise.mailer.webauthn_credential.added.explanation')))
end
end
@ -189,8 +230,10 @@ describe UserMailer do
let(:mail) { described_class.welcome(receiver) }
it 'renders welcome mail' do
expect(mail.subject).to eq I18n.t('user_mailer.welcome.subject')
expect(mail.body.encoded).to include I18n.t('user_mailer.welcome.explanation')
expect(mail)
.to be_present
.and(have_subject(I18n.t('user_mailer.welcome.subject')))
.and(have_body_text(I18n.t('user_mailer.welcome.explanation')))
end
end
@ -199,8 +242,10 @@ describe UserMailer do
let(:mail) { described_class.backup_ready(receiver, backup) }
it 'renders backup_ready mail' do
expect(mail.subject).to eq I18n.t('user_mailer.backup_ready.subject')
expect(mail.body.encoded).to include I18n.t('user_mailer.backup_ready.explanation')
expect(mail)
.to be_present
.and(have_subject(I18n.t('user_mailer.backup_ready.subject')))
.and(have_body_text(I18n.t('user_mailer.backup_ready.explanation')))
end
end
end

View file

@ -23,12 +23,14 @@ RSpec.describe AccountRelationshipsPresenter do
let(:options) { {} }
it 'sets default maps' do
expect(presenter.following).to eq default_map
expect(presenter.followed_by).to eq default_map
expect(presenter.blocking).to eq default_map
expect(presenter.muting).to eq default_map
expect(presenter.requested).to eq default_map
expect(presenter.domain_blocking).to eq default_map
expect(presenter).to have_attributes(
following: default_map,
followed_by: default_map,
blocking: default_map,
muting: default_map,
requested: default_map,
domain_blocking: default_map
)
end
end

View file

@ -22,9 +22,12 @@ RSpec.describe FamiliarFollowersPresenter do
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 contain_exactly(familiar_follower)
expect(result)
.to be_present
.and have_attributes(
id: requested_accounts.first.id,
accounts: contain_exactly(familiar_follower)
)
end
context 'when requested account hides followers' do
@ -35,9 +38,12 @@ RSpec.describe FamiliarFollowersPresenter do
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
expect(result)
.to be_present
.and have_attributes(
id: requested_accounts.first.id,
accounts: be_empty
)
end
end
@ -49,9 +55,12 @@ RSpec.describe FamiliarFollowersPresenter do
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
expect(result)
.to be_present
.and have_attributes(
id: requested_accounts.first.id,
accounts: be_empty
)
end
end
end

View file

@ -22,11 +22,13 @@ RSpec.describe StatusRelationshipsPresenter do
let(:options) { {} }
it 'sets default maps' do
expect(presenter.reblogs_map).to eq default_map
expect(presenter.favourites_map).to eq default_map
expect(presenter.bookmarks_map).to eq default_map
expect(presenter.mutes_map).to eq default_map
expect(presenter.pins_map).to eq default_map
expect(presenter).to have_attributes(
reblogs_map: eq(default_map),
favourites_map: eq(default_map),
bookmarks_map: eq(default_map),
mutes_map: eq(default_map),
pins_map: eq(default_map)
)
end
end
@ -80,18 +82,30 @@ RSpec.describe StatusRelationshipsPresenter do
it 'sets @filters_map to filter top-level status' do
matched_filters = presenter.filters_map[statuses[0].id]
expect(matched_filters.size).to eq 1
expect(matched_filters[0].filter.title).to eq 'filter1'
expect(matched_filters[0].keyword_matches).to eq ['banned']
expect(matched_filters)
.to be_an(Array)
.and have_attributes(size: 1)
.and contain_exactly(
have_attributes(
filter: have_attributes(title: 'filter1'),
keyword_matches: contain_exactly('banned')
)
)
end
it 'sets @filters_map to filter reblogged status' do
matched_filters = presenter.filters_map[statuses[1].reblog_of_id]
expect(matched_filters.size).to eq 1
expect(matched_filters[0].filter.title).to eq 'filter1'
expect(matched_filters[0].keyword_matches).to eq ['irrelevant']
expect(matched_filters)
.to be_an(Array)
.and have_attributes(size: 1)
.and contain_exactly(
have_attributes(
filter: have_attributes(title: 'filter1'),
keyword_matches: contain_exactly('irrelevant')
)
)
end
end
@ -107,18 +121,30 @@ RSpec.describe StatusRelationshipsPresenter do
it 'sets @filters_map to filter top-level status' do
matched_filters = presenter.filters_map[statuses[0].id]
expect(matched_filters.size).to eq 1
expect(matched_filters[0].filter.title).to eq 'filter1'
expect(matched_filters[0].status_matches).to eq [statuses[0].id]
expect(matched_filters)
.to be_an(Array)
.and have_attributes(size: 1)
.and contain_exactly(
have_attributes(
filter: have_attributes(title: 'filter1'),
status_matches: contain_exactly(statuses.first.id)
)
)
end
it 'sets @filters_map to filter reblogged status' do
matched_filters = presenter.filters_map[statuses[1].reblog_of_id]
expect(matched_filters.size).to eq 1
expect(matched_filters[0].filter.title).to eq 'filter1'
expect(matched_filters[0].status_matches).to eq [statuses[1].reblog_of_id]
expect(matched_filters)
.to be_an(Array)
.and have_attributes(size: 1)
.and contain_exactly(
have_attributes(
filter: have_attributes(title: 'filter1'),
status_matches: contain_exactly(statuses.second.reblog_of_id)
)
)
end
end
end

View file

@ -4,7 +4,6 @@ ENV['RAILS_ENV'] ||= 'test'
# This needs to be defined before Rails is initialized
RUN_SYSTEM_SPECS = ENV.fetch('RUN_SYSTEM_SPECS', false)
RUN_SEARCH_SPECS = ENV.fetch('RUN_SEARCH_SPECS', false)
if RUN_SYSTEM_SPECS
STREAMING_PORT = ENV.fetch('TEST_STREAMING_PORT', '4020')
@ -21,6 +20,7 @@ require 'webmock/rspec'
require 'paperclip/matchers'
require 'capybara/rspec'
require 'chewy/rspec'
require 'email_spec/rspec'
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }
@ -54,20 +54,28 @@ RSpec.configure do |config|
case type
when :system
!RUN_SYSTEM_SPECS
when :search
!RUN_SEARCH_SPECS
end
}
# By default, skip the elastic search integration specs
config.filter_run_excluding search: true
config.fixture_path = Rails.root.join('spec', 'fixtures')
config.use_transactional_fixtures = true
config.order = 'random'
config.infer_spec_type_from_file_location!
config.filter_rails_from_backtrace!
# Set type to `cli` for all CLI specs
config.define_derived_metadata(file_path: Regexp.new('spec/lib/mastodon/cli')) do |metadata|
metadata[:type] = :cli
end
# Set `search` metadata true for all specs in spec/search/
config.define_derived_metadata(file_path: Regexp.new('spec/search/*')) do |metadata|
metadata[:search] = true
end
config.include Devise::Test::ControllerHelpers, type: :controller
config.include Devise::Test::ControllerHelpers, type: :helper
config.include Devise::Test::ControllerHelpers, type: :view

View file

@ -1,53 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'GET /api/v1/accounts/{account_id}' do
it 'returns account entity as 200 OK' do
account = Fabricate(:account)
get "/api/v1/accounts/#{account.id}"
aggregate_failures do
expect(response).to have_http_status(200)
expect(body_as_json[:id]).to eq(account.id.to_s)
end
end
it 'returns 404 if account not found' do
get '/api/v1/accounts/1'
aggregate_failures do
expect(response).to have_http_status(404)
expect(body_as_json[:error]).to eq('Record not found')
end
end
context 'when with token' do
it 'returns account entity as 200 OK if token is valid' do
account = Fabricate(:account)
user = Fabricate(:user, account: account)
token = Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts').token
get "/api/v1/accounts/#{account.id}", headers: { Authorization: "Bearer #{token}" }
aggregate_failures do
expect(response).to have_http_status(200)
expect(body_as_json[:id]).to eq(account.id.to_s)
end
end
it 'returns 403 if scope of token is invalid' do
account = Fabricate(:account)
user = Fabricate(:user, account: account)
token = Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write:statuses').token
get "/api/v1/accounts/#{account.id}", headers: { Authorization: "Bearer #{token}" }
aggregate_failures do
expect(response).to have_http_status(403)
expect(body_as_json[:error]).to eq('This action is outside the authorized scopes')
end
end
end
end

View file

@ -2,65 +2,108 @@
require 'rails_helper'
RSpec.describe Api::V1::AccountsController do
render_views
describe '/api/v1/accounts' do
let(:user) { Fabricate(:user) }
let(:scopes) { '' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:user) { Fabricate(:user) }
let(:scopes) { '' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
describe 'GET /api/v1/accounts/:id' do
context 'when logged out' do
let(:account) { Fabricate(:account) }
before do
allow(controller).to receive(:doorkeeper_token) { token }
it 'returns account entity as 200 OK', :aggregate_failures do
get "/api/v1/accounts/#{account.id}"
expect(response).to have_http_status(200)
expect(body_as_json[:id]).to eq(account.id.to_s)
end
end
context 'when the account does not exist' do
it 'returns http not found' do
get '/api/v1/accounts/1'
expect(response).to have_http_status(404)
expect(body_as_json[:error]).to eq('Record not found')
end
end
context 'when logged in' do
subject do
get "/api/v1/accounts/#{account.id}", headers: headers
end
let(:account) { Fabricate(:account) }
let(:scopes) { 'read:accounts' }
it 'returns account entity as 200 OK', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(body_as_json[:id]).to eq(account.id.to_s)
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
end
end
describe 'POST #create' do
let(:app) { Fabricate(:application) }
let(:token) { Doorkeeper::AccessToken.find_or_create_for(application: app, resource_owner: nil, scopes: 'read write', use_refresh_token: false) }
let(:agreement) { nil }
before do
post :create, params: { username: 'test', password: '12345678', email: 'hello@world.tld', agreement: agreement }
describe 'POST /api/v1/accounts' do
subject do
post '/api/v1/accounts', headers: headers, params: { username: 'test', password: '12345678', email: 'hello@world.tld', agreement: agreement }
end
let(:client_app) { Fabricate(:application) }
let(:token) { Doorkeeper::AccessToken.find_or_create_for(application: client_app, resource_owner: nil, scopes: 'read write', use_refresh_token: false) }
let(:agreement) { nil }
context 'when given truthy agreement' do
let(:agreement) { 'true' }
it 'creates a user', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(body_as_json[:access_token]).to_not be_blank
user = User.find_by(email: 'hello@world.tld')
expect(user).to_not be_nil
expect(user.created_by_application_id).to eq app.id
expect(user.created_by_application_id).to eq client_app.id
end
end
context 'when given no agreement' do
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
end
describe 'POST #follow' do
describe 'POST /api/v1/accounts/:id/follow' do
let(:scopes) { 'write:follows' }
let(:my_actor_type) { 'Person' }
let(:lock_follow_from_bot) { false }
let(:other_account) { Fabricate(:account, username: 'bob', locked: locked) }
context 'when posting to an other account' do
subject do
post "/api/v1/accounts/#{other_account.id}/follow", headers: headers
end
before do
other_account.user.settings['lock_follow_from_bot'] = lock_follow_from_bot
other_account.user.save!
user.account.update!(actor_type: my_actor_type)
post :follow, params: { id: other_account.id }
end
context 'with unlocked account' do
let(:locked) { false }
it 'creates a following relation between user and target user', :aggregate_failures do
subject
expect(response).to have_http_status(200)
json = body_as_json
@ -78,6 +121,8 @@ RSpec.describe Api::V1::AccountsController do
let(:locked) { true }
it 'creates a follow request relation between user and target user', :aggregate_failures do
subject
expect(response).to have_http_status(200)
json = body_as_json
@ -97,10 +142,14 @@ RSpec.describe Api::V1::AccountsController do
let(:my_actor_type) { 'Service' }
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns JSON with following=false and requested=true' do
subject
json = body_as_json
expect(json[:following]).to be false
@ -108,6 +157,8 @@ RSpec.describe Api::V1::AccountsController do
end
it 'creates a follow request relation between user and target user' do
subject
expect(user.account.requested?(other_account)).to be true
end
@ -123,48 +174,53 @@ RSpec.describe Api::V1::AccountsController do
end
it 'changes reblogs option' do
post :follow, params: { id: other_account.id, reblogs: true }
post "/api/v1/accounts/#{other_account.id}/follow", headers: headers, params: { reblogs: true }
json = body_as_json
expect(json[:following]).to be true
expect(json[:showing_reblogs]).to be true
expect(json[:notifying]).to be false
expect(body_as_json).to include({
following: true,
showing_reblogs: true,
notifying: false,
})
end
it 'changes notify option' do
post :follow, params: { id: other_account.id, notify: true }
post "/api/v1/accounts/#{other_account.id}/follow", headers: headers, params: { notify: true }
json = body_as_json
expect(json[:following]).to be true
expect(json[:showing_reblogs]).to be false
expect(json[:notifying]).to be true
expect(body_as_json).to include({
following: true,
showing_reblogs: false,
notifying: true,
})
end
it 'changes languages option' do
post :follow, params: { id: other_account.id, languages: %w(en es) }
post "/api/v1/accounts/#{other_account.id}/follow", headers: headers, params: { languages: %w(en es) }
json = body_as_json
expect(json[:following]).to be true
expect(json[:showing_reblogs]).to be false
expect(json[:notifying]).to be false
expect(json[:languages]).to match_array %w(en es)
expect(body_as_json).to include({
following: true,
showing_reblogs: false,
notifying: false,
languages: match_array(%w(en es)),
})
end
end
end
describe 'POST #unfollow' do
describe 'POST /api/v1/accounts/:id/unfollow' do
subject do
post "/api/v1/accounts/#{other_account.id}/unfollow", headers: headers
end
let(:scopes) { 'write:follows' }
let(:other_account) { Fabricate(:account, username: 'bob') }
before do
user.account.follow!(other_account)
post :unfollow, params: { id: other_account.id }
end
it 'removes the following relation between user and target user', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.following?(other_account)).to be false
end
@ -172,16 +228,21 @@ RSpec.describe Api::V1::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end
describe 'POST #remove_from_followers' do
describe 'POST /api/v1/accounts/:id/remove_from_followers' do
subject do
post "/api/v1/accounts/#{other_account.id}/remove_from_followers", headers: headers
end
let(:scopes) { 'write:follows' }
let(:other_account) { Fabricate(:account, username: 'bob') }
before do
other_account.follow!(user.account)
post :remove_from_followers, params: { id: other_account.id }
end
it 'removes the followed relation between user and target user', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.followed_by?(other_account)).to be false
end
@ -189,16 +250,21 @@ RSpec.describe Api::V1::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end
describe 'POST #block' do
describe 'POST /api/v1/accounts/:id/block' do
subject do
post "/api/v1/accounts/#{other_account.id}/block", headers: headers
end
let(:scopes) { 'write:blocks' }
let(:other_account) { Fabricate(:account, username: 'bob') }
before do
user.account.follow!(other_account)
post :block, params: { id: other_account.id }
end
it 'creates a blocking relation', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.following?(other_account)).to be false
expect(user.account.blocking?(other_account)).to be true
@ -207,16 +273,21 @@ RSpec.describe Api::V1::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end
describe 'POST #unblock' do
describe 'POST /api/v1/accounts/:id/unblock' do
subject do
post "/api/v1/accounts/#{other_account.id}/unblock", headers: headers
end
let(:scopes) { 'write:blocks' }
let(:other_account) { Fabricate(:account, username: 'bob') }
before do
user.account.block!(other_account)
post :unblock, params: { id: other_account.id }
end
it 'removes the blocking relation between user and target user', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.blocking?(other_account)).to be false
end
@ -224,16 +295,21 @@ RSpec.describe Api::V1::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end
describe 'POST #mute' do
describe 'POST /api/v1/accounts/:id/mute' do
subject do
post "/api/v1/accounts/#{other_account.id}/mute", headers: headers
end
let(:scopes) { 'write:mutes' }
let(:other_account) { Fabricate(:account, username: 'bob') }
before do
user.account.follow!(other_account)
post :mute, params: { id: other_account.id }
end
it 'mutes notifications', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.following?(other_account)).to be true
expect(user.account.muting?(other_account)).to be true
@ -243,16 +319,21 @@ RSpec.describe Api::V1::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end
describe 'POST #mute with notifications set to false' do
describe 'POST /api/v1/accounts/:id/mute with notifications set to false' do
subject do
post "/api/v1/accounts/#{other_account.id}/mute", headers: headers, params: { notifications: false }
end
let(:scopes) { 'write:mutes' }
let(:other_account) { Fabricate(:account, username: 'bob') }
before do
user.account.follow!(other_account)
post :mute, params: { id: other_account.id, notifications: false }
end
it 'does not mute notifications', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.following?(other_account)).to be true
expect(user.account.muting?(other_account)).to be true
@ -262,16 +343,21 @@ RSpec.describe Api::V1::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end
describe 'POST #mute with nonzero duration set' do
describe 'POST /api/v1/accounts/:id/mute with nonzero duration set' do
subject do
post "/api/v1/accounts/#{other_account.id}/mute", headers: headers, params: { duration: 300 }
end
let(:scopes) { 'write:mutes' }
let(:other_account) { Fabricate(:account, username: 'bob') }
before do
user.account.follow!(other_account)
post :mute, params: { id: other_account.id, duration: 300 }
end
it 'mutes notifications', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.following?(other_account)).to be true
expect(user.account.muting?(other_account)).to be true
@ -281,16 +367,21 @@ RSpec.describe Api::V1::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end
describe 'POST #unmute' do
describe 'POST /api/v1/accounts/:id/unmute' do
subject do
post "/api/v1/accounts/#{other_account.id}/unmute", headers: headers
end
let(:scopes) { 'write:mutes' }
let(:other_account) { Fabricate(:account, username: 'bob') }
before do
user.account.mute!(other_account)
post :unmute, params: { id: other_account.id }
end
it 'removes the muting relation between user and target user', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.muting?(other_account)).to be false
end

View file

@ -8,18 +8,12 @@ RSpec.describe 'Account actions' do
let(:scopes) { 'admin:write admin:write:accounts' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:mailer) { instance_double(ActionMailer::MessageDelivery, deliver_later!: nil) }
before do
allow(UserMailer).to receive(:warning).with(target_account.user, anything).and_return(mailer)
end
shared_examples 'a successful notification delivery' do
it 'notifies the user about the action taken' do
subject
expect(UserMailer).to have_received(:warning).with(target_account.user, anything).once
expect(mailer).to have_received(:deliver_later!).once
expect { subject }
.to have_enqueued_job(ActionMailer::MailDeliveryJob)
.with('UserMailer', 'warning', 'deliver_now!', args: [User, AccountWarning])
end
end

View file

@ -2,24 +2,26 @@
require 'rails_helper'
RSpec.describe Api::V1::StatusesController do
render_views
let(:user) { Fabricate(:user) }
let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: app, scopes: scopes) }
describe '/api/v1/statuses' do
context 'with an oauth token' do
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
let(:user) { Fabricate(:user) }
let(:client_app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: client_app, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/statuses/:id' do
subject do
get "/api/v1/statuses/#{status.id}", headers: headers
end
describe 'GET #show' do
let(:scopes) { 'read:statuses' }
let(:status) { Fabricate(:status, account: user.account) }
it_behaves_like 'forbidden for wrong scope', 'write write:statuses'
it 'returns http success' do
get :show, params: { id: status.id }
subject
expect(response).to have_http_status(200)
end
@ -31,11 +33,10 @@ RSpec.describe Api::V1::StatusesController do
end
it 'returns filter information', :aggregate_failures do
get :show, params: { id: status.id }
json = body_as_json
subject
expect(response).to have_http_status(200)
expect(json[:filtered][0]).to include({
expect(body_as_json[:filtered][0]).to include({
filter: a_hash_including({
id: user.account.custom_filters.first.id.to_s,
title: 'filter1',
@ -55,11 +56,10 @@ RSpec.describe Api::V1::StatusesController do
end
it 'returns filter information', :aggregate_failures do
get :show, params: { id: status.id }
json = body_as_json
subject
expect(response).to have_http_status(200)
expect(json[:filtered][0]).to include({
expect(body_as_json[:filtered][0]).to include({
filter: a_hash_including({
id: user.account.custom_filters.first.id.to_s,
title: 'filter1',
@ -78,11 +78,10 @@ RSpec.describe Api::V1::StatusesController do
end
it 'returns filter information', :aggregate_failures do
get :show, params: { id: status.id }
json = body_as_json
subject
expect(response).to have_http_status(200)
expect(json[:reblog][:filtered][0]).to include({
expect(body_as_json[:reblog][:filtered][0]).to include({
filter: a_hash_including({
id: user.account.custom_filters.first.id.to_s,
title: 'filter1',
@ -94,7 +93,7 @@ RSpec.describe Api::V1::StatusesController do
end
end
describe 'GET #context' do
describe 'GET /api/v1/statuses/:id/context' do
let(:scopes) { 'read:statuses' }
let(:status) { Fabricate(:status, account: user.account) }
let!(:thread) { Fabricate(:status, account: user.account, thread: status) }
@ -142,7 +141,8 @@ RSpec.describe Api::V1::StatusesController do
end
it 'returns http success' do
get :context, params: { id: status.id }
get "/api/v1/statuses/#{status.id}/context", headers: headers
expect(response).to have_http_status(200)
end
@ -190,15 +190,20 @@ RSpec.describe Api::V1::StatusesController do
end
end
describe 'POST #create' do
describe 'POST /api/v1/statuses' do
subject do
post '/api/v1/statuses', headers: headers, params: params
end
let(:scopes) { 'write:statuses' }
let(:params) { { status: 'Hello world' } }
it_behaves_like 'forbidden for wrong scope', 'read read:statuses'
context 'with a basic status body' do
before do
post :create, params: { status: 'Hello world' }
end
it 'returns rate limit headers', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s
expect(response.headers['X-RateLimit-Remaining']).to eq (RateLimiter::FAMILIES[:statuses][:limit] - 1).to_s
@ -209,22 +214,22 @@ RSpec.describe Api::V1::StatusesController do
let!(:alice) { Fabricate(:account, username: 'alice') }
let!(:bob) { Fabricate(:account, username: 'bob') }
before do
post :create, params: { status: '@alice hm, @bob is really annoying lately', allowed_mentions: [alice.id] }
end
let(:params) { { status: '@alice hm, @bob is really annoying lately', allowed_mentions: [alice.id] } }
it 'returns serialized extra accounts in body', :aggregate_failures do
subject
expect(response).to have_http_status(422)
expect(body_as_json[:unexpected_accounts].map { |a| a.slice(:id, :acct) }).to eq [{ id: bob.id.to_s, acct: bob.acct }]
end
end
context 'with missing parameters' do
before do
post :create, params: {}
end
let(:params) { {} }
it 'returns rate limit headers', :aggregate_failures do
subject
expect(response).to have_http_status(422)
expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s
end
@ -234,10 +239,11 @@ RSpec.describe Api::V1::StatusesController do
before do
rate_limiter = RateLimiter.new(user.account, family: :statuses)
300.times { rate_limiter.record! }
post :create, params: { status: 'Hello world' }
end
it 'returns rate limit headers', :aggregate_failures do
subject
expect(response).to have_http_status(429)
expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s
expect(response.headers['X-RateLimit-Remaining']).to eq '0'
@ -245,29 +251,37 @@ RSpec.describe Api::V1::StatusesController do
end
end
describe 'DELETE #destroy' do
describe 'DELETE /api/v1/statuses/:id' do
subject do
delete "/api/v1/statuses/#{status.id}", headers: headers
end
let(:scopes) { 'write:statuses' }
let(:status) { Fabricate(:status, account: user.account) }
before do
post :destroy, params: { id: status.id }
end
it_behaves_like 'forbidden for wrong scope', 'read read:statuses'
it 'removes the status', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(Status.find_by(id: status.id)).to be_nil
end
end
describe 'PUT #update' do
describe 'PUT /api/v1/statuses/:id' do
subject do
put "/api/v1/statuses/#{status.id}", headers: headers, params: { status: 'I am updated' }
end
let(:scopes) { 'write:statuses' }
let(:status) { Fabricate(:status, account: user.account) }
before do
put :update, params: { id: status.id, status: 'I am updated' }
end
it_behaves_like 'forbidden for wrong scope', 'read read:statuses'
it 'updates the status', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(status.reload.text).to eq 'I am updated'
end
@ -275,49 +289,49 @@ RSpec.describe Api::V1::StatusesController do
end
context 'without an oauth token' do
before do
allow(controller).to receive(:doorkeeper_token).and_return(nil)
end
context 'with a private status' do
let(:status) { Fabricate(:status, account: user.account, visibility: :private) }
let(:status) { Fabricate(:status, visibility: :private) }
describe 'GET #show' do
describe 'GET /api/v1/statuses/:id' do
it 'returns http unauthorized' do
get :show, params: { id: status.id }
get "/api/v1/statuses/#{status.id}"
expect(response).to have_http_status(404)
end
end
describe 'GET #context' do
describe 'GET /api/v1/statuses/:id/context' do
before do
Fabricate(:status, account: user.account, thread: status)
Fabricate(:status, thread: status)
end
it 'returns http unauthorized' do
get :context, params: { id: status.id }
get "/api/v1/statuses/#{status.id}/context"
expect(response).to have_http_status(404)
end
end
end
context 'with a public status' do
let(:status) { Fabricate(:status, account: user.account, visibility: :public) }
let(:status) { Fabricate(:status, visibility: :public) }
describe 'GET #show' do
describe 'GET /api/v1/statuses/:id' do
it 'returns http success' do
get :show, params: { id: status.id }
get "/api/v1/statuses/#{status.id}"
expect(response).to have_http_status(200)
end
end
describe 'GET #context' do
describe 'GET /api/v1/statuses/:id/context' do
before do
Fabricate(:status, account: user.account, thread: status)
Fabricate(:status, thread: status)
end
it 'returns http success' do
get :context, params: { id: status.id }
get "/api/v1/statuses/#{status.id}/context"
expect(response).to have_http_status(200)
end
end

View file

@ -60,7 +60,7 @@ RSpec.configure do |config|
end
end
config.around :each, type: :search do |example|
config.around :each, :search do |example|
search_data_manager.populate_indexes
example.run
search_data_manager.remove_indexes
@ -73,6 +73,6 @@ RSpec.configure do |config|
end
def search_examples_present?
RUN_SEARCH_SPECS
RSpec.world.filtered_examples.values.flatten.any? { |example| example.metadata[:search] == true }
end
end

View file

@ -0,0 +1,52 @@
# frozen_string_literal: true
require 'rails_helper'
describe AccountRefreshWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(ResolveAccountService, call: true) }
describe '#perform' do
before do
allow(ResolveAccountService).to receive(:new).and_return(service)
end
context 'when account does not exist' do
it 'returns immediately without processing' do
worker.perform(123_123_123)
expect(service).to_not have_received(:call)
end
end
context 'when account exists' do
context 'when account does not need refreshing' do
let(:account) { Fabricate(:account, last_webfingered_at: recent_webfinger_at) }
it 'returns immediately without processing' do
worker.perform(account.id)
expect(service).to_not have_received(:call)
end
end
context 'when account needs refreshing' do
let(:account) { Fabricate(:account, last_webfingered_at: outdated_webfinger_at) }
it 'schedules an account update' do
worker.perform(account.id)
expect(service).to have_received(:call)
end
end
def recent_webfinger_at
(Account::BACKGROUND_REFRESH_INTERVAL - 3.days).ago
end
def outdated_webfinger_at
(Account::BACKGROUND_REFRESH_INTERVAL + 3.days).ago
end
end
end
end

View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
require 'rails_helper'
describe ActivityPub::PostUpgradeWorker do
let(:worker) { described_class.new }
describe '#perform' do
let(:domain) { 'host.example' }
it 'updates relevant values' do
account = Fabricate(:account, domain: domain, last_webfingered_at: 1.day.ago, protocol: :ostatus)
worker.perform(domain)
expect(account.reload.last_webfingered_at).to be_nil
end
end
end

View file

@ -0,0 +1,29 @@
# frozen_string_literal: true
require 'rails_helper'
describe ActivityPub::SynchronizeFeaturedTagsCollectionWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(ActivityPub::FetchFeaturedTagsCollectionService, call: true) }
describe '#perform' do
before do
allow(ActivityPub::FetchFeaturedTagsCollectionService).to receive(:new).and_return(service)
end
let(:account) { Fabricate(:account) }
let(:url) { 'https://host.example' }
it 'sends the account and url to the service' do
worker.perform(account.id, url)
expect(service).to have_received(:call).with(account, url)
end
it 'returns true for non-existent record' do
result = worker.perform(123_123_123, url)
expect(result).to be(true)
end
end
end

View file

@ -0,0 +1,28 @@
# frozen_string_literal: true
require 'rails_helper'
describe Admin::SuspensionWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(SuspendAccountService, call: true) }
describe '#perform' do
before do
allow(SuspendAccountService).to receive(:new).and_return(service)
end
let(:account) { Fabricate(:account) }
it 'sends the account to the service' do
worker.perform(account.id)
expect(service).to have_received(:call).with(account)
end
it 'returns true for non-existent record' do
result = worker.perform(123_123_123)
expect(result).to be(true)
end
end
end

View file

@ -0,0 +1,29 @@
# frozen_string_literal: true
require 'rails_helper'
describe AfterAccountDomainBlockWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(AfterBlockDomainFromAccountService, call: true) }
describe '#perform' do
before do
allow(AfterBlockDomainFromAccountService).to receive(:new).and_return(service)
end
let(:account) { Fabricate(:account) }
let(:domain) { 'host.example' }
it 'sends the account and domain to the service' do
worker.perform(account.id, domain)
expect(service).to have_received(:call).with(account, domain)
end
it 'returns true for non-existent record' do
result = worker.perform(123_123_123, domain)
expect(result).to be(true)
end
end
end

View file

@ -0,0 +1,36 @@
# frozen_string_literal: true
require 'rails_helper'
describe BackupWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(BackupService, call: true) }
describe '#perform' do
before do
allow(BackupService).to receive(:new).and_return(service)
end
let(:backup) { Fabricate(:backup) }
let!(:other_backup) { Fabricate(:backup, user: backup.user) }
it 'sends the backup to the service and removes other backups' do
expect do
worker.perform(backup.id)
end.to change(UserMailer.deliveries, :size).by(1)
expect(service).to have_received(:call).with(backup)
expect { other_backup.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
context 'when sidekiq retries are exhausted' do
it 'destroys the backup' do
described_class.within_sidekiq_retries_exhausted_block({ 'args' => [backup.id] }) do
worker.perform(backup.id)
end
expect { backup.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
end

View file

@ -0,0 +1,42 @@
# frozen_string_literal: true
require 'rails_helper'
describe DeleteMuteWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(UnmuteService, call: true) }
describe '#perform' do
before do
allow(UnmuteService).to receive(:new).and_return(service)
end
context 'with an expired mute' do
let(:mute) { Fabricate(:mute, expires_at: 1.day.ago) }
it 'sends the mute to the service' do
worker.perform(mute.id)
expect(service).to have_received(:call).with(mute.account, mute.target_account)
end
end
context 'with an unexpired mute' do
let(:mute) { Fabricate(:mute, expires_at: 1.day.from_now) }
it 'does not send the mute to the service' do
worker.perform(mute.id)
expect(service).to_not have_received(:call)
end
end
context 'with a non-existent mute' do
it 'does not send the mute to the service' do
worker.perform(123_123_123)
expect(service).to_not have_received(:call)
end
end
end
end

View file

@ -12,6 +12,7 @@ describe FeedInsertWorker do
describe 'perform' do
let(:follower) { Fabricate(:account) }
let(:status) { Fabricate(:status) }
let(:list) { Fabricate(:list) }
context 'when there are no records' do
it 'skips push with missing status' do
@ -46,11 +47,29 @@ describe FeedInsertWorker do
it 'pushes the status onto the home timeline without filter' do
instance = instance_double(FeedManager, push_to_home: nil, filter?: false)
allow(FeedManager).to receive(:instance).and_return(instance)
result = subject.perform(status.id, follower.id)
result = subject.perform(status.id, follower.id, :home)
expect(result).to be_nil
expect(instance).to have_received(:push_to_home).with(follower, status, update: nil)
end
it 'pushes the status onto the tags timeline without filter' do
instance = instance_double(FeedManager, push_to_home: nil, filter?: false)
allow(FeedManager).to receive(:instance).and_return(instance)
result = subject.perform(status.id, follower.id, :tags)
expect(result).to be_nil
expect(instance).to have_received(:push_to_home).with(follower, status, update: nil)
end
it 'pushes the status onto the list timeline without filter' do
instance = instance_double(FeedManager, push_to_list: nil, filter?: false)
allow(FeedManager).to receive(:instance).and_return(instance)
result = subject.perform(status.id, list.id, :list)
expect(result).to be_nil
expect(instance).to have_received(:push_to_list).with(list, status, update: nil)
end
end
context 'with notification' do

View file

@ -0,0 +1,23 @@
# frozen_string_literal: true
require 'rails_helper'
describe ImportWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(ImportService, call: true) }
describe '#perform' do
before do
allow(ImportService).to receive(:new).and_return(service)
end
let(:import) { Fabricate(:import) }
it 'sends the import to the service' do
worker.perform(import.id)
expect(service).to have_received(:call).with(import)
expect { import.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end

View file

@ -2,12 +2,38 @@
require 'rails_helper'
describe PostProcessMediaWorker do
describe PostProcessMediaWorker, :paperclip_processing do
let(:worker) { described_class.new }
describe 'perform' do
it 'runs without error for missing record' do
expect { worker.perform(nil) }.to_not raise_error
describe '#perform' do
let(:media_attachment) { Fabricate(:media_attachment) }
it 'reprocesses and updates the media attachment' do
worker.perform(media_attachment.id)
expect(media_attachment.processing).to eq('complete')
end
it 'returns true for non-existent record' do
result = worker.perform(123_123_123)
expect(result).to be(true)
end
context 'when sidekiq retries are exhausted' do
it 'sets state to failed' do
described_class.within_sidekiq_retries_exhausted_block({ 'args' => [media_attachment.id] }) do
worker.perform(media_attachment.id)
end
expect(media_attachment.reload.processing).to eq('failed')
end
it 'returns true for non-existent record' do
described_class.within_sidekiq_retries_exhausted_block({ 'args' => [123_123_123] }) do
expect(worker.perform(123_123_123)).to be(true)
end
end
end
end
end

View file

@ -0,0 +1,38 @@
# frozen_string_literal: true
require 'rails_helper'
describe PublishAnnouncementReactionWorker do
let(:worker) { described_class.new }
describe '#perform' do
before { Fabricate(:account, user: Fabricate(:user, current_sign_in_at: 1.hour.ago)) }
let(:announcement) { Fabricate(:announcement) }
let(:name) { 'name value' }
it 'sends the announcement and name to the service when subscribed' do
allow(redis).to receive(:exists?).and_return(true)
allow(redis).to receive(:publish)
worker.perform(announcement.id, name)
expect(redis).to have_received(:publish)
end
it 'does not send the announcement and name to the service when not subscribed' do
allow(redis).to receive(:exists?).and_return(false)
allow(redis).to receive(:publish)
worker.perform(announcement.id, name)
expect(redis).to_not have_received(:publish)
end
it 'returns true for non-existent record' do
result = worker.perform(123_123_123, name)
expect(result).to be(true)
end
end
end

View file

@ -5,9 +5,48 @@ require 'rails_helper'
describe RedownloadAvatarWorker do
let(:worker) { described_class.new }
describe 'perform' do
it 'runs without error for missing record' do
expect { worker.perform(nil) }.to_not raise_error
describe '#perform' do
it 'returns nil for non-existent record' do
result = worker.perform(123_123_123)
expect(result).to be_nil
end
it 'returns nil for suspended account' do
account = Fabricate(:account, suspended_at: 10.days.ago)
expect(worker.perform(account.id)).to be_nil
end
it 'returns nil with a domain block' do
account = Fabricate(:account, domain: 'host.example')
Fabricate(:domain_block, domain: account.domain, reject_media: true)
expect(worker.perform(account.id)).to be_nil
end
it 'returns nil without an avatar remote url' do
account = Fabricate(:account, avatar_remote_url: '')
expect(worker.perform(account.id)).to be_nil
end
it 'returns nil when avatar file name is present' do
stub_request(:get, 'https://example.host/file').to_return request_fixture('avatar.txt')
account = Fabricate(:account, avatar_remote_url: 'https://example.host/file', avatar_file_name: 'test.jpg')
expect(worker.perform(account.id)).to be_nil
end
it 'reprocesses a remote avatar' do
stub_request(:get, 'https://example.host/file').to_return request_fixture('avatar.txt')
account = Fabricate(:account, avatar_remote_url: 'https://example.host/file')
account.update_column(:avatar_file_name, nil) # rubocop:disable Rails/SkipsModelValidations
result = worker.perform(account.id)
expect(result).to be(true)
expect(account.reload.avatar_file_name).to_not be_nil
end
end
end

View file

@ -5,9 +5,48 @@ require 'rails_helper'
describe RedownloadHeaderWorker do
let(:worker) { described_class.new }
describe 'perform' do
it 'runs without error for missing record' do
expect { worker.perform(nil) }.to_not raise_error
describe '#perform' do
it 'returns nil for non-existent record' do
result = worker.perform(123_123_123)
expect(result).to be_nil
end
it 'returns nil for suspended account' do
account = Fabricate(:account, suspended_at: 10.days.ago)
expect(worker.perform(account.id)).to be_nil
end
it 'returns nil with a domain block' do
account = Fabricate(:account, domain: 'host.example')
Fabricate(:domain_block, domain: account.domain, reject_media: true)
expect(worker.perform(account.id)).to be_nil
end
it 'returns nil without an header remote url' do
account = Fabricate(:account, header_remote_url: '')
expect(worker.perform(account.id)).to be_nil
end
it 'returns nil when header file name is present' do
stub_request(:get, 'https://example.host/file').to_return request_fixture('avatar.txt')
account = Fabricate(:account, header_remote_url: 'https://example.host/file', header_file_name: 'test.jpg')
expect(worker.perform(account.id)).to be_nil
end
it 'reprocesses a remote header' do
stub_request(:get, 'https://example.host/file').to_return request_fixture('avatar.txt')
account = Fabricate(:account, header_remote_url: 'https://example.host/file')
account.update_column(:header_file_name, nil) # rubocop:disable Rails/SkipsModelValidations
result = worker.perform(account.id)
expect(result).to be(true)
expect(account.reload.header_file_name).to_not be_nil
end
end
end

View file

@ -0,0 +1,37 @@
# frozen_string_literal: true
require 'rails_helper'
describe RedownloadMediaWorker do
let(:worker) { described_class.new }
describe '#perform' do
it 'returns nil for non-existent record' do
result = worker.perform(123_123_123)
expect(result).to be_nil
end
it 'returns nil without a remote_url' do
media_attachment = Fabricate(:media_attachment, remote_url: '')
result = worker.perform(media_attachment.id)
expect(result).to be_nil
end
context 'with a valid remote url' do
let(:url) { 'https://example.host/file.txt' }
before { stub_request(:get, url).to_return(status: 200) }
it 'processes downloads for valid record' do
media_attachment = Fabricate(:media_attachment, remote_url: url)
worker.perform(media_attachment.id)
expect(a_request(:get, url)).to have_been_made
end
end
end
end

View file

@ -0,0 +1,28 @@
# frozen_string_literal: true
require 'rails_helper'
describe RemovalWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(RemoveStatusService, call: true) }
describe '#perform' do
before do
allow(RemoveStatusService).to receive(:new).and_return(service)
end
let(:status) { Fabricate(:status) }
it 'sends the status to the service' do
worker.perform(status.id)
expect(service).to have_received(:call).with(status)
end
it 'returns true for non-existent record' do
result = worker.perform(123_123_123)
expect(result).to be(true)
end
end
end

View file

@ -0,0 +1,60 @@
# frozen_string_literal: true
require 'rails_helper'
describe Scheduler::SelfDestructScheduler do
let(:worker) { described_class.new }
describe '#perform' do
let!(:account) { Fabricate(:account, domain: nil, suspended_at: nil) }
context 'when not in self destruct mode' do
before do
allow(SelfDestructHelper).to receive(:self_destruct?).and_return(false)
end
it 'returns without processing' do
worker.perform
expect(account.reload.suspended_at).to be_nil
end
end
context 'when in self-destruct mode' do
before do
allow(SelfDestructHelper).to receive(:self_destruct?).and_return(true)
end
context 'when sidekiq is overwhelmed' do
before do
stats = instance_double(Sidekiq::Stats, enqueued: described_class::MAX_ENQUEUED**2)
allow(Sidekiq::Stats).to receive(:new).and_return(stats)
end
it 'returns without processing' do
worker.perform
expect(account.reload.suspended_at).to be_nil
end
end
context 'when sidekiq is operational' do
it 'suspends local non-suspended accounts' do
worker.perform
expect(account.reload.suspended_at).to_not be_nil
end
it 'suspends local suspended accounts marked for deletion' do
account.update(suspended_at: 10.days.ago)
deletion_request = Fabricate(:account_deletion_request, account: account)
worker.perform
expect(account.reload.suspended_at).to be > 1.day.ago
expect { deletion_request.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
end
end

View file

@ -5,9 +5,21 @@ require 'rails_helper'
describe Webhooks::DeliveryWorker do
let(:worker) { described_class.new }
describe 'perform' do
it 'runs without error' do
expect { worker.perform(nil, nil) }.to_not raise_error
describe '#perform' do
let(:webhook) { Fabricate(:webhook) }
it 'reprocesses and updates the webhook' do
stub_request(:post, webhook.url).to_return(status: 200, body: '')
worker.perform(webhook.id, 'body')
expect(a_request(:post, webhook.url)).to have_been_made.at_least_once
end
it 'returns true for non-existent record' do
result = worker.perform(123_123_123, '')
expect(result).to be(true)
end
end
end

View file

@ -2569,9 +2569,9 @@ __metadata:
linkType: soft
"@material-symbols/svg-600@npm:^0.14.0":
version: 0.14.0
resolution: "@material-symbols/svg-600@npm:0.14.0"
checksum: e6547a9a0b2072f4109f2e4e0863367ea2507efce740c427a8544100db02ffff52f33608aac1a355f4977e2c0b2ce6cdd6bfee9177bb13cee0b28418f948b5a5
version: 0.14.1
resolution: "@material-symbols/svg-600@npm:0.14.1"
checksum: fb5252285bbeccc45a4b131e8b165470b5b57e146bc7ea586eb82e580037d1218f6dad5fee4e6822c357041ff547f34c9c7432cce0a811b14f7e41d8ae23009b
languageName: node
linkType: hard
@ -13812,17 +13812,17 @@ __metadata:
linkType: hard
"react-redux-loading-bar@npm:^5.0.4":
version: 5.0.4
resolution: "react-redux-loading-bar@npm:5.0.4"
version: 5.0.5
resolution: "react-redux-loading-bar@npm:5.0.5"
dependencies:
prop-types: "npm:^15.7.2"
react-lifecycles-compat: "npm:^3.0.4"
peerDependencies:
react: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
react-dom: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
react-redux: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
redux: ^3.0.0 || ^4.0.0
checksum: 11eea2ef6dfae232e278eceb83d07f9f57a2ece3ef23ce888dccf24964b669c9ee83a6db12b1f3c757b5b3410f7a7ccda96a4b4216c4ad9b42bf831ccea7a4a2
react-redux: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
redux: ^3.0.0 || ^4.0.0 || ^5.0.0
checksum: 0dbac046c5b8b6bd209ccfc25ccc55dc9158cd737b42b68fd1900dfe46a59c9c7e2b0082d8901b749e7cf2d7e23074590aae74f350a814f205105f47895a6214
languageName: node
linkType: hard