Add: #920 アンテナ・リストに「お気に入りに登録」設定 (#946)

* Add: #920 アンテナ・リストに「お気に入りに登録」設定

* Fix test

* Fix test

* Add fedibird capabilities

* Add kmyblue_favourite_antenna
This commit is contained in:
KMY(雪あすか) 2024-12-09 12:12:15 +09:00 committed by GitHub
parent ee49518125
commit 9201eb151b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 294 additions and 29 deletions

View file

@ -42,6 +42,6 @@ class Api::V1::AntennasController < Api::BaseController
end end
def antenna_params def antenna_params
params.permit(:title, :list_id, :insert_feeds, :stl, :ltl, :with_media_only, :ignore_reblog) params.permit(:title, :list_id, :insert_feeds, :stl, :ltl, :with_media_only, :ignore_reblog, :favourite)
end end
end end

View file

@ -38,6 +38,16 @@ class Api::V1::ListsController < Api::BaseController
render_empty render_empty
end end
def favourite
@list.favourite!
render json: @list, serializer: REST::ListSerializer
end
def unfavourite
@list.unfavourite!
render json: @list, serializer: REST::ListSerializer
end
private private
def set_list def set_list
@ -45,6 +55,6 @@ class Api::V1::ListsController < Api::BaseController
end end
def list_params def list_params
params.permit(:title, :replies_policy, :exclusive, :notify) params.permit(:title, :replies_policy, :exclusive, :notify, :favourite)
end end
end end

View file

@ -20,6 +20,8 @@ module KmyblueCapabilitiesHelper
kmyblue_circle_history kmyblue_circle_history
kmyblue_list_notification kmyblue_list_notification
kmyblue_server_features kmyblue_server_features
favourite_list
kmyblue_favourite_antenna
) )
capabilities << :full_text_search if Chewy.enabled? capabilities << :full_text_search if Chewy.enabled?

View file

@ -10,6 +10,7 @@ export interface ApiAntennaJSON {
insert_feeds: boolean; insert_feeds: boolean;
with_media_only: boolean; with_media_only: boolean;
ignore_reblog: boolean; ignore_reblog: boolean;
favourite: boolean;
list: ApiListJSON | null; list: ApiListJSON | null;
list_id: string | undefined; list_id: string | undefined;

View file

@ -10,5 +10,6 @@ export interface ApiListJSON {
exclusive: boolean; exclusive: boolean;
replies_policy: RepliesPolicyType; replies_policy: RepliesPolicyType;
notify: boolean; notify: boolean;
favourite: boolean;
antennas?: ApiAntennaJSON[]; antennas?: ApiAntennaJSON[];
} }

View file

@ -62,6 +62,7 @@ const NewAntenna: React.FC<{
const [ignoreReblog, setIgnoreReblog] = useState(false); const [ignoreReblog, setIgnoreReblog] = useState(false);
const [mode, setMode] = useState('filtering'); const [mode, setMode] = useState('filtering');
const [destination, setDestination] = useState('timeline'); const [destination, setDestination] = useState('timeline');
const [favourite, setFavourite] = useState(true);
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
useEffect(() => { useEffect(() => {
@ -80,6 +81,7 @@ const NewAntenna: React.FC<{
setListId(antenna.list?.id ?? '0'); setListId(antenna.list?.id ?? '0');
setWithMediaOnly(antenna.with_media_only); setWithMediaOnly(antenna.with_media_only);
setIgnoreReblog(antenna.ignore_reblog); setIgnoreReblog(antenna.ignore_reblog);
setFavourite(antenna.favourite);
if (antenna.stl) { if (antenna.stl) {
setMode('stl'); setMode('stl');
@ -109,6 +111,7 @@ const NewAntenna: React.FC<{
setIgnoreReblog, setIgnoreReblog,
setMode, setMode,
setDestination, setDestination,
setFavourite,
id, id,
antenna, antenna,
lists, lists,
@ -179,6 +182,13 @@ const NewAntenna: React.FC<{
[setIgnoreReblog], [setIgnoreReblog],
); );
const handleFavouriteChange = useCallback(
({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) => {
setFavourite(checked);
},
[setFavourite],
);
const handleSubmit = useCallback(() => { const handleSubmit = useCallback(() => {
setSubmitting(true); setSubmitting(true);
@ -193,6 +203,7 @@ const NewAntenna: React.FC<{
list_id: destination === 'list' ? listId : '0', list_id: destination === 'list' ? listId : '0',
with_media_only: withMediaOnly, with_media_only: withMediaOnly,
ignore_reblog: ignoreReblog, ignore_reblog: ignoreReblog,
favourite,
}), }),
).then(() => { ).then(() => {
setSubmitting(false); setSubmitting(false);
@ -208,6 +219,7 @@ const NewAntenna: React.FC<{
list_id: destination === 'list' ? listId : '0', list_id: destination === 'list' ? listId : '0',
with_media_only: withMediaOnly, with_media_only: withMediaOnly,
ignore_reblog: ignoreReblog, ignore_reblog: ignoreReblog,
favourite,
}), }),
).then((result) => { ).then((result) => {
setSubmitting(false); setSubmitting(false);
@ -233,6 +245,7 @@ const NewAntenna: React.FC<{
withMediaOnly, withMediaOnly,
ignoreReblog, ignoreReblog,
destination, destination,
favourite,
]); ]);
return ( return (
@ -460,6 +473,35 @@ const NewAntenna: React.FC<{
</> </>
)} )}
<div className='fields-group'>
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
<label className='app-form__toggle'>
<div className='app-form__toggle__label'>
<strong>
<FormattedMessage
id='antennas.favourite'
defaultMessage='Favorite'
/>
</strong>
<span className='hint'>
<FormattedMessage
id='antennas.favourite_hint'
defaultMessage='When opening the Web Client on a PC, this antenna appears in the navigation.'
/>
</span>
</div>
<div className='app-form__toggle__toggle'>
<div>
<Toggle
checked={favourite}
onChange={handleFavouriteChange}
/>
</div>
</div>
</label>
</div>
<div className='actions'> <div className='actions'>
<button className='button' type='submit'> <button className='button' type='submit'>
{submitting ? ( {submitting ? (

View file

@ -82,6 +82,7 @@ const NewList: React.FC<{
const [exclusive, setExclusive] = useState(false); const [exclusive, setExclusive] = useState(false);
const [repliesPolicy, setRepliesPolicy] = useState<RepliesPolicyType>('list'); const [repliesPolicy, setRepliesPolicy] = useState<RepliesPolicyType>('list');
const [notify, setNotify] = useState(false); const [notify, setNotify] = useState(false);
const [favourite, setFavourite] = useState(true);
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
useEffect(() => { useEffect(() => {
@ -96,8 +97,17 @@ const NewList: React.FC<{
setExclusive(list.exclusive); setExclusive(list.exclusive);
setRepliesPolicy(list.replies_policy); setRepliesPolicy(list.replies_policy);
setNotify(list.notify); setNotify(list.notify);
setFavourite(list.favourite);
} }
}, [setTitle, setExclusive, setRepliesPolicy, setNotify, id, list]); }, [
setTitle,
setExclusive,
setRepliesPolicy,
setNotify,
setFavourite,
id,
list,
]);
const handleTitleChange = useCallback( const handleTitleChange = useCallback(
({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => { ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
@ -127,6 +137,13 @@ const NewList: React.FC<{
[setNotify], [setNotify],
); );
const handleFavouriteChange = useCallback(
({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) => {
setFavourite(checked);
},
[setFavourite],
);
const handleSubmit = useCallback(() => { const handleSubmit = useCallback(() => {
setSubmitting(true); setSubmitting(true);
@ -138,6 +155,7 @@ const NewList: React.FC<{
exclusive, exclusive,
replies_policy: repliesPolicy, replies_policy: repliesPolicy,
notify, notify,
favourite,
}), }),
).then(() => { ).then(() => {
setSubmitting(false); setSubmitting(false);
@ -150,6 +168,7 @@ const NewList: React.FC<{
exclusive, exclusive,
replies_policy: repliesPolicy, replies_policy: repliesPolicy,
notify, notify,
favourite,
}), }),
).then((result) => { ).then((result) => {
setSubmitting(false); setSubmitting(false);
@ -171,6 +190,7 @@ const NewList: React.FC<{
exclusive, exclusive,
repliesPolicy, repliesPolicy,
notify, notify,
favourite,
]); ]);
return ( return (
@ -324,6 +344,35 @@ const NewList: React.FC<{
</label> </label>
</div> </div>
<div className='fields-group'>
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
<label className='app-form__toggle'>
<div className='app-form__toggle__label'>
<strong>
<FormattedMessage
id='lists.favourite'
defaultMessage='Favorite'
/>
</strong>
<span className='hint'>
<FormattedMessage
id='lists.favourite_hint'
defaultMessage='When opening the Web Client on a PC, this list appears in the navigation.'
/>
</span>
</div>
<div className='app-form__toggle__toggle'>
<div>
<Toggle
checked={favourite}
onChange={handleFavouriteChange}
/>
</div>
</div>
</label>
</div>
<div className='actions'> <div className='actions'>
<button className='button' type='submit'> <button className='button' type='submit'>
{submitting ? ( {submitting ? (

View file

@ -16,7 +16,7 @@ const getOrderedLists = createSelector([state => state.get('lists')], lists => {
return lists; return lists;
} }
return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(8); return lists.toList().filter(item => !!item && item.get('favourite')).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(8);
}); });
const getOrderedAntennas = createSelector([state => state.get('antennas')], antennas => { const getOrderedAntennas = createSelector([state => state.get('antennas')], antennas => {
@ -24,7 +24,7 @@ const getOrderedAntennas = createSelector([state => state.get('antennas')], ante
return antennas; return antennas;
} }
return antennas.toList().filter(item => !!item && !item.get('insert_feeds') && item.get('title') !== undefined).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(8); return antennas.toList().filter(item => !!item && item.get('favourite') && item.get('title') !== undefined).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(8);
}); });
export const ListPanel = () => { export const ListPanel = () => {

View file

@ -150,6 +150,8 @@
"antennas.exclude_domains": "Exclude domains", "antennas.exclude_domains": "Exclude domains",
"antennas.exclude_keywords": "Exclude keywords", "antennas.exclude_keywords": "Exclude keywords",
"antennas.exclude_tags": "Exclude tags", "antennas.exclude_tags": "Exclude tags",
"antennas.favourite": "Favorite",
"antennas.favourite_hint": "When opening the Web Client on a PC, this antenna appears in the navigation.",
"antennas.filter_items": "Move to antenna filter setting", "antennas.filter_items": "Move to antenna filter setting",
"antennas.filter_not": "Filter Not", "antennas.filter_not": "Filter Not",
"antennas.find_users_to_add": "Find users to add", "antennas.find_users_to_add": "Find users to add",
@ -630,6 +632,8 @@
"lists.edit": "Edit list", "lists.edit": "Edit list",
"lists.exclusive": "Hide members in Home", "lists.exclusive": "Hide members in Home",
"lists.exclusive_hint": "If someone is on this list, hide them in your Home feed to avoid seeing their posts twice.", "lists.exclusive_hint": "If someone is on this list, hide them in your Home feed to avoid seeing their posts twice.",
"lists.favourite": "Favorite",
"lists.favourite_hint": "When opening the Web Client on a PC, this list appears in the navigation.",
"lists.find_users_to_add": "Find users to add", "lists.find_users_to_add": "Find users to add",
"lists.list_members": "List members", "lists.list_members": "List members",
"lists.list_members_count": "{count, plural, one {# member} other {# members}}", "lists.list_members_count": "{count, plural, one {# member} other {# members}}",

View file

@ -145,6 +145,8 @@
"antennas.exclude_domains": "除外するドメイン", "antennas.exclude_domains": "除外するドメイン",
"antennas.exclude_keywords": "除外するキーワード", "antennas.exclude_keywords": "除外するキーワード",
"antennas.exclude_tags": "除外するタグ", "antennas.exclude_tags": "除外するタグ",
"antennas.favourite": "お気に入りに登録",
"antennas.favourite_hint": "お気に入りに登録したアンテナは、Webクライアントでナビゲーションに表示されます",
"antennas.filter_items": "絞り込み条件の設定に移動", "antennas.filter_items": "絞り込み条件の設定に移動",
"antennas.filter_not": "絞り込み条件の例外", "antennas.filter_not": "絞り込み条件の例外",
"antennas.ignore_reblog": "ブーストを除外", "antennas.ignore_reblog": "ブーストを除外",
@ -579,6 +581,8 @@
"lists.antennas": "関連付けられたアンテナ", "lists.antennas": "関連付けられたアンテナ",
"lists.delete": "リストを削除", "lists.delete": "リストを削除",
"lists.edit": "リストを編集", "lists.edit": "リストを編集",
"lists.favourite": "お気に入りに登録",
"lists.favourite_hint": "お気に入りに登録したリストは、Webクライアントでナビゲーションに表示されます",
"lists.memo_related_antenna": "アンテナ: {title}", "lists.memo_related_antenna": "アンテナ: {title}",
"lists.notify": "これらの投稿を通知する", "lists.notify": "これらの投稿を通知する",
"lists.replies_policy.followed": "フォロー中のユーザー全員", "lists.replies_policy.followed": "フォロー中のユーザー全員",

View file

@ -14,6 +14,7 @@ const AntennaFactory = Record<AntennaShape>({
insert_feeds: false, insert_feeds: false,
with_media_only: false, with_media_only: false,
ignore_reblog: false, ignore_reblog: false,
favourite: true,
list: null, list: null,
list_id: undefined, list_id: undefined,
}); });

View file

@ -12,6 +12,7 @@ const ListFactory = Record<ListShape>({
exclusive: false, exclusive: false,
replies_policy: 'list', replies_policy: 'list',
notify: false, notify: false,
favourite: true,
antennas: [], antennas: [],
}); });

View file

@ -5,27 +5,28 @@
# Table name: antennas # Table name: antennas
# #
# id :bigint(8) not null, primary key # id :bigint(8) not null, primary key
# account_id :bigint(8) not null
# list_id :bigint(8) not null
# title :string default(""), not null
# keywords :jsonb
# exclude_keywords :jsonb
# any_domains :boolean default(TRUE), not null
# any_tags :boolean default(TRUE), not null
# any_accounts :boolean default(TRUE), not null # any_accounts :boolean default(TRUE), not null
# any_domains :boolean default(TRUE), not null
# any_keywords :boolean default(TRUE), not null # any_keywords :boolean default(TRUE), not null
# any_tags :boolean default(TRUE), not null
# available :boolean default(TRUE), not null # available :boolean default(TRUE), not null
# created_at :datetime not null
# updated_at :datetime not null
# expires_at :datetime
# with_media_only :boolean default(FALSE), not null
# exclude_domains :jsonb
# exclude_accounts :jsonb # exclude_accounts :jsonb
# exclude_domains :jsonb
# exclude_keywords :jsonb
# exclude_tags :jsonb # exclude_tags :jsonb
# stl :boolean default(FALSE), not null # expires_at :datetime
# favourite :boolean default(TRUE), not null
# ignore_reblog :boolean default(FALSE), not null # ignore_reblog :boolean default(FALSE), not null
# insert_feeds :boolean default(FALSE), not null # insert_feeds :boolean default(FALSE), not null
# keywords :jsonb
# ltl :boolean default(FALSE), not null # ltl :boolean default(FALSE), not null
# stl :boolean default(FALSE), not null
# title :string default(""), not null
# with_media_only :boolean default(FALSE), not null
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint(8) not null
# list_id :bigint(8) not null
# #
class Antenna < ApplicationRecord class Antenna < ApplicationRecord
include Expireable include Expireable

View file

@ -5,13 +5,14 @@
# Table name: lists # Table name: lists
# #
# id :bigint(8) not null, primary key # id :bigint(8) not null, primary key
# account_id :bigint(8) not null # exclusive :boolean default(FALSE), not null
# favourite :boolean default(TRUE), not null
# notify :boolean default(FALSE), not null
# replies_policy :integer default("list"), not null
# title :string default(""), not null # title :string default(""), not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# replies_policy :integer default("list"), not null # account_id :bigint(8) not null
# exclusive :boolean default(FALSE), not null
# notify :boolean default(FALSE), not null
# #
class List < ApplicationRecord class List < ApplicationRecord
@ -35,6 +36,14 @@ class List < ApplicationRecord
before_destroy :clean_feed_manager before_destroy :clean_feed_manager
def favourite!
update!(favourite: true)
end
def unfavourite!
update!(favourite: false)
end
private private
def validate_account_lists_limit def validate_account_lists_limit

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class REST::AntennaSerializer < ActiveModel::Serializer class REST::AntennaSerializer < ActiveModel::Serializer
attributes :id, :title, :stl, :ltl, :insert_feeds, :with_media_only, :ignore_reblog, :accounts_count, :domains_count, :tags_count, :keywords_count attributes :id, :title, :stl, :ltl, :insert_feeds, :with_media_only, :ignore_reblog, :accounts_count, :domains_count, :tags_count, :keywords_count, :favourite
class ListSerializer < ActiveModel::Serializer class ListSerializer < ActiveModel::Serializer
attributes :id, :title attributes :id, :title

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class REST::ListSerializer < ActiveModel::Serializer class REST::ListSerializer < ActiveModel::Serializer
attributes :id, :title, :replies_policy, :exclusive, :notify attributes :id, :title, :replies_policy, :exclusive, :notify, :favourite
def id def id
object.id.to_s object.id.to_s

View file

@ -232,6 +232,11 @@ namespace :api, format: false do
resources :lists, only: [:index, :create, :show, :update, :destroy] do resources :lists, only: [:index, :create, :show, :update, :destroy] do
resource :accounts, only: [:show, :create, :destroy], module: :lists resource :accounts, only: [:show, :create, :destroy], module: :lists
member do
post :favourite
post :unfavourite
end
end end
resources :antennas, only: [:index, :create, :show, :update, :destroy] do resources :antennas, only: [:index, :create, :show, :update, :destroy] do

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
class AddFavouriteToListsAndAntennas < ActiveRecord::Migration[7.2]
class Antenna < ApplicationRecord; end
def up
add_column :lists, :favourite, :boolean, null: false, default: true
add_column :antennas, :favourite, :boolean, null: false, default: true
Antenna.where(insert_feeds: true).in_batches.update_all(favourite: false)
end
def down
remove_column :lists, :favourite
remove_column :antennas, :favourite
end
end

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.2].define(version: 2024_12_05_135925) do ActiveRecord::Schema[7.2].define(version: 2024_12_08_232829) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -329,6 +329,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_12_05_135925) do
t.boolean "ignore_reblog", default: false, null: false t.boolean "ignore_reblog", default: false, null: false
t.boolean "insert_feeds", default: false, null: false t.boolean "insert_feeds", default: false, null: false
t.boolean "ltl", default: false, null: false t.boolean "ltl", default: false, null: false
t.boolean "favourite", default: true, null: false
t.index ["account_id"], name: "index_antennas_on_account_id" t.index ["account_id"], name: "index_antennas_on_account_id"
t.index ["any_accounts"], name: "index_antennas_on_any_accounts" t.index ["any_accounts"], name: "index_antennas_on_any_accounts"
t.index ["any_domains"], name: "index_antennas_on_any_domains" t.index ["any_domains"], name: "index_antennas_on_any_domains"
@ -786,6 +787,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_12_05_135925) do
t.integer "replies_policy", default: 0, null: false t.integer "replies_policy", default: 0, null: false
t.boolean "exclusive", default: false, null: false t.boolean "exclusive", default: false, null: false
t.boolean "notify", default: false, null: false t.boolean "notify", default: false, null: false
t.boolean "favourite", default: true, null: false
t.index ["account_id"], name: "index_lists_on_account_id" t.index ["account_id"], name: "index_lists_on_account_id"
end end

View file

@ -14,6 +14,7 @@ namespace :dangerous do
end end
target_migrations = %w( target_migrations = %w(
20241208232829
20240828123604 20240828123604
20240709063700 20240709063700
20240426233435 20240426233435
@ -172,6 +173,7 @@ namespace :dangerous do
%w(domain_blocks reject_send_sensitive), %w(domain_blocks reject_send_sensitive),
%w(domain_blocks reject_straight_follow), %w(domain_blocks reject_straight_follow),
%w(favourites uri), %w(favourites uri),
%w(lists favourite),
%w(lists notify), %w(lists notify),
%w(statuses limited_scope), %w(statuses limited_scope),
%w(statuses markdown), %w(statuses markdown),

View file

@ -19,6 +19,7 @@ RSpec.describe 'Antennas' do
Fabricate(:antenna, account: user.account, title: 'second antenna', with_media_only: true), Fabricate(:antenna, account: user.account, title: 'second antenna', with_media_only: true),
Fabricate(:antenna, account: user.account, title: 'third antenna', stl: true), Fabricate(:antenna, account: user.account, title: 'third antenna', stl: true),
Fabricate(:antenna, account: user.account, title: 'fourth antenna', ignore_reblog: true), Fabricate(:antenna, account: user.account, title: 'fourth antenna', ignore_reblog: true),
Fabricate(:antenna, account: user.account, title: 'fourth antenna', favourite: false),
] ]
end end
@ -37,6 +38,7 @@ RSpec.describe 'Antennas' do
domains_count: 0, domains_count: 0,
tags_count: 0, tags_count: 0,
keywords_count: 0, keywords_count: 0,
favourite: antenna.favourite,
} }
end end
end end
@ -80,7 +82,8 @@ RSpec.describe 'Antennas' do
accounts_count: 0, accounts_count: 0,
domains_count: 0, domains_count: 0,
tags_count: 0, tags_count: 0,
keywords_count: 0 keywords_count: 0,
favourite: true
) )
end end
@ -137,7 +140,7 @@ RSpec.describe 'Antennas' do
end end
let(:antenna) { Fabricate(:antenna, account: user.account, title: 'my antenna') } let(:antenna) { Fabricate(:antenna, account: user.account, title: 'my antenna') }
let(:params) { { title: 'antenna', ignore_reblog: 'true', insert_feeds: 'true' } } let(:params) { { title: 'antenna', ignore_reblog: 'true', insert_feeds: 'true', favourite: 'false' } }
it_behaves_like 'forbidden for wrong scope', 'read read:lists' it_behaves_like 'forbidden for wrong scope', 'read read:lists'
@ -146,6 +149,7 @@ RSpec.describe 'Antennas' do
.to change_antenna_title .to change_antenna_title
.and change_antenna_ignore_reblog .and change_antenna_ignore_reblog
.and change_antenna_insert_feeds .and change_antenna_insert_feeds
.and change_antenna_favourite
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
antenna.reload antenna.reload
@ -162,7 +166,8 @@ RSpec.describe 'Antennas' do
accounts_count: 0, accounts_count: 0,
domains_count: 0, domains_count: 0,
tags_count: 0, tags_count: 0,
keywords_count: 0 keywords_count: 0,
favourite: false
) )
end end
@ -178,6 +183,10 @@ RSpec.describe 'Antennas' do
change { antenna.reload.insert_feeds }.from(false).to(true) change { antenna.reload.insert_feeds }.from(false).to(true)
end end
def change_antenna_favourite
change { antenna.reload.favourite }.from(true).to(false)
end
context 'when the antenna does not exist' do context 'when the antenna does not exist' do
it 'returns http not found' do it 'returns http not found' do
put '/api/v1/antennas/-1', headers: headers, params: params put '/api/v1/antennas/-1', headers: headers, params: params

View file

@ -20,6 +20,7 @@ RSpec.describe 'Lists' do
Fabricate(:list, account: user.account, title: 'third list', replies_policy: :none), Fabricate(:list, account: user.account, title: 'third list', replies_policy: :none),
Fabricate(:list, account: user.account, title: 'fourth list', exclusive: true), Fabricate(:list, account: user.account, title: 'fourth list', exclusive: true),
Fabricate(:list, account: user.account, title: 'fifth list', notify: true), Fabricate(:list, account: user.account, title: 'fifth list', notify: true),
Fabricate(:list, account: user.account, title: 'fifth list', favourite: false),
] ]
end end
@ -32,6 +33,7 @@ RSpec.describe 'Lists' do
exclusive: list.exclusive, exclusive: list.exclusive,
antennas: list.antennas, antennas: list.antennas,
notify: list.notify, notify: list.notify,
favourite: list.favourite,
} }
end end
end end
@ -74,6 +76,7 @@ RSpec.describe 'Lists' do
exclusive: list.exclusive, exclusive: list.exclusive,
antennas: list.antennas, antennas: list.antennas,
notify: list.notify, notify: list.notify,
favourite: true,
}) })
end end
@ -150,7 +153,7 @@ RSpec.describe 'Lists' do
end end
let(:list) { Fabricate(:list, account: user.account, title: 'my list') } let(:list) { Fabricate(:list, account: user.account, title: 'my list') }
let(:params) { { title: 'list', replies_policy: 'followed', exclusive: 'true' } } let(:params) { { title: 'list', replies_policy: 'followed', exclusive: 'true', favourite: 'false' } }
it_behaves_like 'forbidden for wrong scope', 'read read:lists' it_behaves_like 'forbidden for wrong scope', 'read read:lists'
@ -159,6 +162,7 @@ RSpec.describe 'Lists' do
.to change_list_title .to change_list_title
.and change_list_replies_policy .and change_list_replies_policy
.and change_list_exclusive .and change_list_exclusive
.and change_list_favourite
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(response.content_type) expect(response.content_type)
@ -172,6 +176,7 @@ RSpec.describe 'Lists' do
exclusive: list.exclusive, exclusive: list.exclusive,
antennas: list.antennas, antennas: list.antennas,
notify: list.notify, notify: list.notify,
favourite: false,
}) })
end end
@ -187,6 +192,10 @@ RSpec.describe 'Lists' do
change { list.reload.exclusive }.from(false).to(true) change { list.reload.exclusive }.from(false).to(true)
end end
def change_list_favourite
change { list.reload.favourite }.from(true).to(false)
end
context 'when the list does not exist' do context 'when the list does not exist' do
it 'returns http not found' do it 'returns http not found' do
put '/api/v1/lists/-1', headers: headers, params: params put '/api/v1/lists/-1', headers: headers, params: params
@ -210,6 +219,102 @@ RSpec.describe 'Lists' do
end end
end end
shared_examples 'check list permissions when post' do |path, params|
context 'when the list does not exist' do
it 'returns http not found' do
post path, headers: headers, params: params
expect(response).to have_http_status(404)
expect(response.content_type)
.to start_with('application/json')
end
end
context 'when the list belongs to another user' do
let(:list) { Fabricate(:list) }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
expect(response.content_type)
.to start_with('application/json')
end
end
end
describe 'POST /api/v1/lists/:id/favourite' do
subject do
post "/api/v1/lists/#{list.id}/favourite", headers: headers, params: {}
end
let(:list) { Fabricate(:list, account: user.account, title: 'my list', favourite: false) }
it_behaves_like 'forbidden for wrong scope', 'read read:lists'
it 'returns the updated list and updates values', :aggregate_failures do
expect { subject }
.to change_favourite
expect(response).to have_http_status(200)
expect(response.content_type)
.to start_with('application/json')
list.reload
expect(response.parsed_body).to match({
id: list.id.to_s,
title: list.title,
replies_policy: list.replies_policy,
exclusive: list.exclusive,
antennas: list.antennas,
notify: list.notify,
favourite: true,
})
end
def change_favourite
change { list.reload.favourite }.from(false).to(true)
end
it_behaves_like 'check list permissions when post', '/api/v1/lists/-1/favourite', {}
end
describe 'POST /api/v1/lists/:id/unfavourite' do
subject do
post "/api/v1/lists/#{list.id}/unfavourite", headers: headers, params: {}
end
let(:list) { Fabricate(:list, account: user.account, title: 'my list', favourite: true) }
it_behaves_like 'forbidden for wrong scope', 'read read:lists'
it 'returns the updated list and updates values', :aggregate_failures do
expect { subject }
.to change_favourite
expect(response).to have_http_status(200)
expect(response.content_type)
.to start_with('application/json')
list.reload
expect(response.parsed_body).to match({
id: list.id.to_s,
title: list.title,
replies_policy: list.replies_policy,
exclusive: list.exclusive,
antennas: list.antennas,
notify: list.notify,
favourite: false,
})
end
def change_favourite
change { list.reload.favourite }.from(true).to(false)
end
it_behaves_like 'check list permissions when post', '/api/v1/lists/-1/favourite', {}
end
describe 'DELETE /api/v1/lists/:id' do describe 'DELETE /api/v1/lists/:id' do
subject do subject do
delete "/api/v1/lists/#{list.id}", headers: headers delete "/api/v1/lists/#{list.id}", headers: headers