Commit da101c92 authored by Lukas Fülling's avatar Lukas Fülling 🗓

Merge branch 'master' of git.k40s.net:commnet/mastodon

 Conflicts:
	README.md
	app/javascript/mastodon/components/status_action_bar.js
	app/javascript/mastodon/features/getting_started/index.js
	app/javascript/mastodon/features/status/components/action_bar.js
	app/javascript/mastodon/features/status/components/detailed_status.js
	app/javascript/mastodon/features/ui/index.js
	app/lib/activitypub/activity.rb
	app/lib/activitypub/activity/announce.rb
	app/lib/activitypub/activity/create.rb
	config/locales/en.yml
	config/locales/simple_form.de.yml
	config/settings.yml
	package.json
	spec/lib/activitypub/activity/announce_spec.rb
	yarn.lock
parents b4a8d758 8037d1f2
......@@ -18,7 +18,7 @@ All commands are executed in the project dir!
First, install dependencies:
```
$ xcode-select --install
$ brew install imagemagick ffmpeg yarn postgresql redis rbenv nodejs protobuf libidn
$ brew install imagemagick ffmpeg yarn postgresql redis rbenv nodejs protobuf libidn libpqxx
$ rbenv init
$ rbenv install 2.5.3
$ gem update --system
......
......@@ -43,6 +43,7 @@ class Settings::PreferencesController < Settings::BaseController
:setting_display_media,
:setting_expand_spoilers,
:setting_reduce_motion,
:setting_enable_ui_tweaks,
:setting_system_font_ui,
:setting_noindex,
:setting_theme,
......
......@@ -35,6 +35,16 @@ const messages = defineMessages({
copy: { id: 'status.copy', defaultMessage: 'Copy link to status' },
});
const obfuscatedCount = count => {
if (count < 0) {
return 0;
} else if (count <= 1) {
return count;
} else {
return '1+';
}
};
export default @injectIntl
class StatusActionBar extends ImmutablePureComponent {
......@@ -239,9 +249,9 @@ class StatusActionBar extends ImmutablePureComponent {
return (
<div className='status__action-bar'>
<div className='status__action-bar__counter'><IconButton className='status__action-bar-button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /><span className='status__action-bar__counter__label' >{obfuscatedCount(status.get('replies_count'))}</span></div>
<IconButton className='status__action-bar-button' disabled={!publicStatus} active={status.get('reblogged')} pressed={status.get('reblogged')} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} />
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
<div className='status__action-bar__counter'><IconButton className='status__action-bar-button' disabled={anonymousAccess} title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /><span className='status__action-bar__counter__label' >{obfuscatedCount(status.get('replies_count'))}</span></div>
<IconButton className='status__action-bar-button' disabled={anonymousAccess || !publicStatus} active={status.get('reblogged')} pressed={status.get('reblogged')} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} />
<IconButton className='status__action-bar-button star-icon' disabled={anonymousAccess} animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
{shareButton}
<div className='status__action-bar-dropdown'>
......
......@@ -5,15 +5,14 @@ import classNames from 'classnames';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import { closeOnboarding } from '../../actions/onboarding';
import screenHello from '../../../images/screen_hello.svg';
import screenFederation from '../../../images/screen_federation.svg';
import screenInteractions from '../../../images/screen_interactions.svg';
import logoTransparent from '../../../images/logo_full.gif';
import aliens from '../../../images/aliens.png';
const FrameWelcome = ({ domain, onNext }) => (
<div className='introduction__frame'>
<div className='introduction__illustration' style={{ background: `url(${logoTransparent}) no-repeat center center / auto 80%` }}>
<img src={screenHello} alt='' />
<div className='introduction__illustration'>
<img src={aliens} alt='' />
</div>
<div className='introduction__text introduction__text--centered'>
......
......@@ -136,7 +136,7 @@ class SwitchingColumnsArea extends React.PureComponent {
setRef = c => {
this.node = c.getWrappedInstance();
}
};
render () {
const { children } = this.props;
......@@ -444,7 +444,7 @@ class UI extends React.PureComponent {
handleHotkeyGoToRequests = () => {
this.context.router.history.push('/follow_requests');
}
};
render () {
const { draggingOver } = this.state;
......@@ -473,7 +473,7 @@ class UI extends React.PureComponent {
return (
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef} attach={window} focused>
<AudioPlayer />
<AudioPlayer />
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef} style={{ pointerEvents: dropdownMenuIsOpen ? 'none' : null }}>
<TabsBar />
......
......@@ -14,6 +14,7 @@ export const me = getMeta('me');
export const searchEnabled = getMeta('search_enabled');
export const invitesEnabled = getMeta('invites_enabled');
export const playerEnabled = getMeta('player_enabled');
export const enableUiTweaks = getMeta('enable_ui_tweaks');
export const playerUrl = getMeta('player_url');
export const playerName = getMeta('player_name');
export const version = getMeta('version');
......
......@@ -2314,4 +2314,4 @@
],
"path": "app/javascript/mastodon/features/video/index.json"
}
]
]
\ No newline at end of file
......@@ -3,6 +3,7 @@ import { default as Mastodon, store } from './containers/mastodon';
import React from 'react';
import ReactDOM from 'react-dom';
import ready from './ready';
import {enableUiTweaks} from "./initial_state";
const perf = require('./performance');
......@@ -21,6 +22,10 @@ function main() {
const mountNode = document.getElementById('mastodon');
const props = JSON.parse(mountNode.getAttribute('data-props'));
if(enableUiTweaks === true){
document.body.classList.add('experimental');
}
ReactDOM.render(<Mastodon {...props} />, mountNode);
if (process.env.NODE_ENV === 'production') {
// avoid offline in dev mode because it's harder to debug
......
......@@ -25,3 +25,5 @@
@import 'mastodon/dashboard';
@import 'mastodon/rtl';
@import 'mastodon/accessibility';
@import 'vapor/tweaks';
......@@ -2,6 +2,7 @@
$ui-highlight-color: rgb(0, 136, 185);
$font-color: white;
$error-red: #df405a;
table,
td,
......
......@@ -30,13 +30,6 @@
@import 'vapor/diff';
// @import 'vapor/assistant';
//$vapor-pink: #C774E8;
$vapor-pink: #FF6AD5;
//$vapor-blue: #94D0FF;
$vapor-blue: #94D0FF;
//$vapor-highlight: #2b90d9;
$vapor-highlight: #2b90d9;
div.ui,
body.about-body > div.landing-page,
body.no-reduce-motion,
......@@ -76,6 +69,8 @@ div.onboarding-modal__paginator {
body.admin > div.admin-wrapper,
body.embed,
body.app-body,
body div.introduction,
body {
background-image: linear-gradient($vapor-pink, $vapor-blue) !important;
background-color: transparent !important;
......@@ -105,8 +100,8 @@ div.compose-form > div.compose-form__buttons-wrapper,
div.emoji-mart-bar:first-child,
div.emoji-picker-dropdown__menu > div.emoji-mart > div.emoji-mart-search > input,
div.emoji-mart > div.emoji-mart-search,
div.column > div.scrollable.detailed-status__wrapper div.detailed-status,
div.column > div.scrollable.detailed-status__wrapper div.detailed-status__action-bar,
div.detailed-status__wrapper div.detailed-status,
div.detailed-status__wrapper div.detailed-status__action-bar,
div.activity-stream > div.entry > div.detailed-status.detailed-status--flex,
input.spoiler-input__input#cw-spoiler-input,
div.activity-stream > div.entry,
......@@ -187,7 +182,14 @@ div.account-authorize__wrapper > div.account--panel,
.focusable:focus .status.status-direct:not(.read),
.focusable.detailed-status__wrapper > .detailed-status,
.focusable.detailed-status__wrapper > .detailed-status__action-bar,
.status > a.status-card:hover {
.status .status-card:hover,
.status .status-card.compact:hover,
.notification__filter-bar > button,
.notification__filter-bar,
.introduction__pager,
body div.introduction code,
div.public-layout > div.container > div.page-header,
.status-card .status-card__image {
background: rgba(217, 225, 232, 0.2);
}
......@@ -269,8 +271,8 @@ div.notification-bar.notification-bar-active {
body.app-body > div.privacy-dropdown__dropdown > div.privacy-dropdown__option:not(.active):hover,
ul > li.dropdown-menu__item:hover > a,
div.autosuggest-textarea > div.autosuggest-textarea__suggestions.autosuggest-textarea__suggestions--visible > div:hover,
div.detailed-status > a.status-card:hover,
div.activity-stream div.detailed-status.light a.status-card:hover,
div.detailed-status .status-card:hover,
div.activity-stream div.detailed-status.light .status-card:hover,
div.dashboard__counters > div > a:hover,
div.dashboard__counters > div > div:hover,
div.h-feed div.account__section-headline,
......@@ -351,6 +353,7 @@ a.button.button-alternative {
div.h-feed > data.p-name > div.activity-stream-tabs > a {
color: $dark-text-color;
&.active {
color: $black;
}
......@@ -459,6 +462,7 @@ textarea.text.optional::placeholder {
div.drawer__inner__mastodon {
background-color: transparent;
& > img {
opacity: 0.6;
}
......@@ -506,7 +510,11 @@ div > div.status_wrapper > div.status.status-direct,
div > div.status_wrapper > div.status.status-direct.status-reply,
div.activity-stream > div.entry > a.load-more.load-gap,
div.admin-wrapper div.content-wrapper > div.content h2,
.search-results__section > div {
.search-results__section > div,
.notification__filter-bar > button,
.notification__filter-bar > button.active,
.notification__filter-bar > button.active::after,
.notification__filter-bar {
border-bottom-color: $vapor-highlight;
}
......@@ -552,8 +560,8 @@ div.account__header__fields > dl > dd {
border-left-color: $vapor-highlight;
}
div.detailed-status > a.status-card,
div.detailed-status > div.status-card,
div.detailed-status .status-card,
div.status .status-card,
div.public-account-header > div.public-account-header__image,
div.public-account-header > div.public-account-header__image > img,
div.account-timeline__header > div.account__moved-note,
......@@ -572,11 +580,24 @@ div.item-list > article > div.account,
.focusable:focus .status.status-direct,
.status.status-direct:not(.read),
.focusable:focus .status.status-direct:not(.read),
.status__wrapper > .status > .status-card,
.status__wrapper > .status > .status-card.compact {
.status__wrapper > .status .status-card,
.status__wrapper > .status .status-card.compact,
.directory .accounts-table tbody td {
border-color: $vapor-highlight;
}
.introduction__dots > .introduction__dot {
border-color: $vapor-highlight;
&.active {
background-color: $vapor-highlight;
}
}
.public-layout .public-account-header__bar::before {
background-color: $vapor-highlight;
}
.landing-page .separator-or span {
background-color: $vapor-highlight;
color: white;
......@@ -607,6 +628,7 @@ form.table-form div.warning,
form.simple-form div.warning {
background: rgba(223, 43, 42, 0.6);
color: white;
& a {
color: white;
}
......
// experimental stuff, only enabled when the user is okay with it.
body.experimental {
/* anti clickjacking script by flussence (https://gitlab.com/flussence/masto-css/raw/stable/feed-noclick.user.css) */
#mastodon .status__action-bar {
opacity: 0;
}
#mastodon .status__action-bar:hover {
opacity: 1;
transition: opacity 1000ms ease-in-out;
}
#mastodon .status__action-bar:hover button {
animation: dontclickthis 1000ms step-end;
}
@keyframes dontclickthis {
from {
pointer-events: none;
}
to {
pointer-events: auto;
}
}
}
......@@ -39,3 +39,10 @@ $account-background-color: $white !default;
@function lighten($color, $amount) {
@return hsl(hue($color), saturation($color), lightness($color) - $amount);
}
//$vapor-pink: #C774E8;
$vapor-pink: #FF6AD5;
//$vapor-blue: #94D0FF;
$vapor-blue: #94D0FF;
//$vapor-highlight: #2b90d9;
$vapor-highlight: #2b90d9;
......@@ -150,7 +150,8 @@ class ActivityPub::Activity
end
end
fetch_remote_original_status
# If the status is not from the actor, try to fetch it
return fetch_remote_original_status if value_or_id(first_of_value(@json['attributedTo'])) == @account.uri
end
def fetch_remote_original_status
......
......@@ -2,11 +2,8 @@
class ActivityPub::Activity::Announce < ActivityPub::Activity
def perform
return reject_payload! if delete_arrived_first?(@json['id']) || !related_to_local_activity?
original_status = status_from_object
return reject_payload! if original_status.nil? || !announceable?(original_status)
return if original_status.nil? || delete_arrived_first?(@json['id']) || !announceable?(original_status)
status = Status.find_by(account: @account, reblog: original_status)
......
......@@ -2,7 +2,8 @@
class ActivityPub::Activity::Create < ActivityPub::Activity
def perform
return reject_payload! if unsupported_object_type? || invalid_origin?(@object['id']) || Tombstone.exists?(uri: @object['id']) || !related_to_local_activity?
return if unsupported_object_type? || invalid_origin?(@object['id']) || Tombstone.exists?(uri: @object['id']) || !related_to_local_activity?
RedisLock.acquire(lock_options) do |lock|
if lock.acquired?
......@@ -341,6 +342,18 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
responds_to_followed_account? || addresses_local_accounts?
end
def fetch?
!@options[:delivery]
end
def followed_by_local_accounts?
@account.passive_relationships.exists?
end
def requested_through_relay?
@options[:relayed_through_account] && Relay.find_by(inbox_url: @options[:relayed_through_account].inbox_url)&.enabled?
end
def responds_to_followed_account?
!replied_to_status.nil? && (replied_to_status.account.local? || replied_to_status.account.passive_relationships.exists?)
end
......
......@@ -27,6 +27,7 @@ class UserSettingsDecorator
user.settings['display_media'] = display_media_preference if change?('setting_display_media')
user.settings['expand_spoilers'] = expand_spoilers_preference if change?('setting_expand_spoilers')
user.settings['reduce_motion'] = reduce_motion_preference if change?('setting_reduce_motion')
user.settings['enable_ui_tweaks'] = enable_ui_tweaks_preference if change?('setting_enable_ui_tweaks')
user.settings['system_font_ui'] = system_font_ui_preference if change?('setting_system_font_ui')
user.settings['noindex'] = noindex_preference if change?('setting_noindex')
user.settings['theme'] = theme_preference if change?('setting_theme')
......@@ -83,6 +84,10 @@ class UserSettingsDecorator
boolean_cast_setting 'setting_reduce_motion'
end
def enable_ui_tweaks_preference
boolean_cast_setting 'setting_enable_ui_tweaks'
end
def noindex_preference
boolean_cast_setting 'setting_noindex'
end
......
......@@ -99,7 +99,7 @@ class User < ApplicationRecord
has_many :session_activations, dependent: :destroy
delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :delete_modal,
:reduce_motion, :system_font_ui, :noindex, :theme, :display_media, :hide_network,
:reduce_motion, :enable_ui_tweaks, :system_font_ui, :noindex, :theme, :display_media, :hide_network,
:expand_spoilers, :default_language, :aggregate_reblogs, :show_application, to: :settings, prefix: :setting, allow_nil: false
attr_reader :invite_code
......
......@@ -24,15 +24,16 @@ class InitialStateSerializer < ActiveModel::Serializer
}
if object.current_account
store[:me] = object.current_account.id.to_s
store[:unfollow_modal] = object.current_account.user.setting_unfollow_modal
store[:boost_modal] = object.current_account.user.setting_boost_modal
store[:delete_modal] = object.current_account.user.setting_delete_modal
store[:auto_play_gif] = object.current_account.user.setting_auto_play_gif
store[:display_media] = object.current_account.user.setting_display_media
store[:expand_spoilers] = object.current_account.user.setting_expand_spoilers
store[:reduce_motion] = object.current_account.user.setting_reduce_motion
store[:is_staff] = object.current_account.user.staff?
store[:me] = object.current_account.id.to_s
store[:unfollow_modal] = object.current_account.user.setting_unfollow_modal
store[:boost_modal] = object.current_account.user.setting_boost_modal
store[:delete_modal] = object.current_account.user.setting_delete_modal
store[:auto_play_gif] = object.current_account.user.setting_auto_play_gif
store[:display_media] = object.current_account.user.setting_display_media
store[:expand_spoilers] = object.current_account.user.setting_expand_spoilers
store[:reduce_motion] = object.current_account.user.setting_reduce_motion
store[:enable_ui_tweaks] = object.current_account.user.setting_enable_ui_tweaks
store[:is_staff] = object.current_account.user.staff?
end
store
......
......@@ -57,6 +57,7 @@
= f.input :setting_auto_play_gif, as: :boolean, wrapper: :with_label
= f.input :setting_expand_spoilers, as: :boolean, wrapper: :with_label
= f.input :setting_reduce_motion, as: :boolean, wrapper: :with_label
= f.input :setting_enable_ui_tweaks, as: :boolean, wrapper: :with_label
= f.input :setting_system_font_ui, as: :boolean, wrapper: :with_label
.actions
......
......@@ -395,6 +395,15 @@ en:
peers_api_enabled:
desc_html: Domain names this server has encountered in the fediverse
title: Publish list of discovered servers
player_enabled:
desc_html: If the player should be visible
title: Player Enabled
player_name:
desc_html: The name of the radio station
title: Player Name
player_url:
desc_html: The source URL of the radio player
title: Player URL
preview_sensitive_media:
desc_html: Link previews on other websites will display a thumbnail even if the media is marked as sensitive
title: Show sensitive media in OpenGraph previews
......
This diff is collapsed.
......@@ -98,6 +98,7 @@ de:
setting_hide_network: Blende dein Netzwerk aus
setting_noindex: Suchmaschinen-Indexierung verhindern
setting_reduce_motion: Bewegung in Animationen verringern
setting_enable_ui_tweaks: Experimentelle UI Tweaks aktivieren
setting_system_font_ui: Standardschriftart des Systems verwenden
setting_theme: Theme der Website
setting_unfollow_modal: Bestätigungsdialog anzeigen, bevor jemandem entfolgt wird
......
......@@ -103,6 +103,7 @@ en:
setting_hide_network: Hide your network
setting_noindex: Opt-out of search engine indexing
setting_reduce_motion: Reduce motion in animations
setting_enable_ui_tweaks: Enable experimental UI tweaks
setting_show_application: Disclose application used to send toots
setting_system_font_ui: Use system's default font
setting_theme: Site theme
......
......@@ -29,6 +29,7 @@ defaults: &defaults
player_url: '/sounds/boop.ogg'
player_name: 'Notification Boop'
reduce_motion: false
enable_ui_tweaks: false
show_application: true
system_font_ui: false
noindex: false
......
This diff is collapsed.
require 'rails_helper'
RSpec.describe ActivityPub::Activity::Announce do
let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers', uri: 'https://example.com/actor') }
let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers') }
let(:recipient) { Fabricate(:account) }
let(:status) { Fabricate(:status, account: recipient) }
......@@ -10,90 +10,23 @@ RSpec.describe ActivityPub::Activity::Announce do
'@context': 'https://www.w3.org/ns/activitystreams',
id: 'foo',
type: 'Announce',
actor: 'https://example.com/actor',
actor: ActivityPub::TagManager.instance.uri_for(sender),
object: object_json,
}.with_indifferent_access
end
let(:unknown_object_json) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: 'https://example.com/actor/hello-world',
type: 'Note',
attributedTo: 'https://example.com/actor',
content: 'Hello world',
to: 'http://example.com/followers',
}
end
subject { described_class.new(json, sender) }
describe '#perform' do
context 'when sender is followed by a local account' do
before do
Fabricate(:account).follow!(sender)
stub_request(:get, 'https://example.com/actor/hello-world').to_return(body: Oj.dump(unknown_object_json))
subject.perform
end
context 'a known status' do
let(:object_json) do
ActivityPub::TagManager.instance.uri_for(status)
end
it 'creates a reblog by sender of status' do
expect(sender.reblogged?(status)).to be true
end
end
context 'an unknown status' do
let(:object_json) { 'https://example.com/actor/hello-world' }
it 'creates a reblog by sender of status' do
reblog = sender.statuses.first
expect(reblog).to_not be_nil
expect(reblog.reblog.text).to eq 'Hello world'
end
end
context 'self-boost of a previously unknown status with missing attributedTo' do
let(:object_json) do
{
id: 'https://example.com/actor#bar',
type: 'Note',
content: 'Lorem ipsum',
to: 'http://example.com/followers',
}
end
it 'creates a reblog by sender of status' do
expect(sender.reblogged?(sender.statuses.first)).to be true
end
end
context 'self-boost of a previously unknown status with correct attributedTo' do
let(:object_json) do
{
id: 'https://example.com/actor#bar',
type: 'Note',
content: 'Lorem ipsum',
attributedTo: 'https://example.com/actor',
to: 'http://example.com/followers',
}
end
before do
sender.update(uri: ActivityPub::TagManager.instance.uri_for(sender))
end
it 'creates a reblog by sender of status' do
expect(sender.reblogged?(sender.statuses.first)).to be true
end
end
describe '#perform' do
before do
subject.perform
end
context 'when the status belongs to a local user' do
before do
subject.perform
end
context 'a known status' do
let(:object_json) do
ActivityPub::TagManager.instance.uri_for(status)
end
......@@ -103,68 +36,34 @@ RSpec.describe ActivityPub::Activity::Announce do
end
end
context 'when the sender is relayed' do
let!(:relay_account) { Fabricate(:account, inbox_url: 'https://relay.example.com/inbox') }
let!(:relay) { Fabricate(:relay, inbox_url: 'https://relay.example.com/inbox') }
subject { described_class.new(json, sender, relayed_through_account: relay_account) }
context 'and the relay is enabled' do
before do
relay.update(state: :accepted)
subject.perform
end
let(:object_json) do
{
id: 'https://example.com/actor#bar',
type: 'Note',
content: 'Lorem ipsum',
to: 'http://example.com/followers',
}
end
it 'creates a reblog by sender of status' do
expect(sender.statuses.count).to eq 2
end
context 'self-boost of a previously unknown status with missing attributedTo' do
let(:object_json) do
{
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
to: 'http://example.com/followers',
}
end
context 'and the relay is disabled' do
before do
subject.perform
end
let(:object_json) do
{
id: 'https://example.com/actor#bar',
type: 'Note',
content: 'Lorem ipsum',
to: 'http://example.com/followers',
}
end
it 'does not create anything' do
expect(sender.statuses.count).to eq 0
end
it 'creates a reblog by sender of status' do
expect(sender.reblogged?(sender.statuses.first)).to be true
end
end
context 'when the sender has no relevance to local activity' do
before do
subject.perform
end
context 'self-boost of a previously unknown status with correct attributedTo' do
let(:object_json) do
{
id: 'https://example.com/actor#bar',
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
attributedTo: ActivityPub::TagManager.instance.uri_for(sender),
to: 'http://example.com/followers',
}
end
it 'does not create anything' do
expect(sender.statuses.count).to eq 0
it 'creates a reblog by sender of status' do
expect(sender.reblogged?(sender.statuses.first)).to be true
end
end
end
......
......@@ -6,7 +6,7 @@ RSpec.describe Settings::ScopedSettings do
let(:object) { Fabricate(:user) }
let(:scoped_setting) { described_class.new(object) }
let(:val) { 'whatever' }
let(:methods) { %i(auto_play_gif default_sensitive unfollow_modal boost_modal delete_modal reduce_motion system_font_ui noindex theme) }
let(:methods) { %i(auto_play_gif default_sensitive unfollow_modal boost_modal delete_modal reduce_motion enable_ui_tweaks system_font_ui noindex theme) }
describe '.initialize' do
it 'sets @object' do
......