From f18618d7f92553c33bbd0b09bf52401000b7b8e6 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 17 Jul 2023 13:13:43 +0200 Subject: [PATCH 01/18] Fix some incorrect tests (#26035) --- spec/services/batched_remove_status_service_spec.rb | 4 ++-- spec/services/remove_status_service_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/services/batched_remove_status_service_spec.rb b/spec/services/batched_remove_status_service_spec.rb index 1363c81d05..8201c9d51f 100644 --- a/spec/services/batched_remove_status_service_spec.rb +++ b/spec/services/batched_remove_status_service_spec.rb @@ -34,11 +34,11 @@ RSpec.describe BatchedRemoveStatusService, type: :service do end it 'removes statuses from author\'s home feed' do - expect(HomeFeed.new(alice).get(10)).to_not include([status_alice_hello.id, status_alice_other.id]) + expect(HomeFeed.new(alice).get(10).pluck(:id)).to_not include(status_alice_hello.id, status_alice_other.id) end it 'removes statuses from local follower\'s home feed' do - expect(HomeFeed.new(jeff).get(10)).to_not include([status_alice_hello.id, status_alice_other.id]) + expect(HomeFeed.new(jeff).get(10).pluck(:id)).to_not include(status_alice_hello.id, status_alice_other.id) end it 'notifies streaming API of followers' do diff --git a/spec/services/remove_status_service_spec.rb b/spec/services/remove_status_service_spec.rb index 77b01d3072..c19b4fac15 100644 --- a/spec/services/remove_status_service_spec.rb +++ b/spec/services/remove_status_service_spec.rb @@ -28,12 +28,12 @@ RSpec.describe RemoveStatusService, type: :service do it 'removes status from author\'s home feed' do subject.call(@status) - expect(HomeFeed.new(alice).get(10)).to_not include(@status.id) + expect(HomeFeed.new(alice).get(10).pluck(:id)).to_not include(@status.id) end it 'removes status from local follower\'s home feed' do subject.call(@status) - expect(HomeFeed.new(jeff).get(10)).to_not include(@status.id) + expect(HomeFeed.new(jeff).get(10).pluck(:id)).to_not include(@status.id) end it 'sends Delete activity to followers' do From 943f27f4377cd74bc07794f15299ad05ef8a7d4f Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 17 Jul 2023 13:56:28 +0200 Subject: [PATCH 02/18] Remove unfollowed hashtag posts from home feed (#26028) --- app/controllers/api/v1/tags_controller.rb | 1 + app/lib/feed_manager.rb | 20 ++++++++++++ app/workers/tag_unmerge_worker.rb | 21 ++++++++++++ spec/workers/tag_unmerge_worker_spec.rb | 39 +++++++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 app/workers/tag_unmerge_worker.rb create mode 100644 spec/workers/tag_unmerge_worker_spec.rb diff --git a/app/controllers/api/v1/tags_controller.rb b/app/controllers/api/v1/tags_controller.rb index 284ec85937..672535a018 100644 --- a/app/controllers/api/v1/tags_controller.rb +++ b/app/controllers/api/v1/tags_controller.rb @@ -19,6 +19,7 @@ class Api::V1::TagsController < Api::BaseController def unfollow TagFollow.find_by(account: current_account, tag: @tag)&.destroy! + TagUnmergeWorker.perform_async(@tag.id, current_account.id) render json: @tag, serializer: REST::TagSerializer end diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index 7423d2d092..ad686c1f1a 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -180,6 +180,26 @@ class FeedManager end end + # Remove a tag's statuses from a home feed + # @param [Tag] from_tag + # @param [Account] into_account + # @return [void] + def unmerge_tag_from_home(from_tag, into_account) + timeline_key = key(:home, into_account.id) + timeline_status_ids = redis.zrange(timeline_key, 0, -1) + + # This is a bit tricky because we need posts tagged with this hashtag that are not + # also tagged with another followed hashtag or from a followed user + scope = from_tag.statuses + .where(id: timeline_status_ids) + .where.not(account: into_account.following) + .tagged_with_none(TagFollow.where(account: into_account).pluck(:tag_id)) + + scope.select('id, reblog_of_id').reorder(nil).find_each do |status| + remove_from_feed(:home, into_account.id, status, aggregate_reblogs: into_account.user&.aggregates_reblogs?) + end + end + # Clear all statuses from or mentioning target_account from a home feed # @param [Account] account # @param [Account] target_account diff --git a/app/workers/tag_unmerge_worker.rb b/app/workers/tag_unmerge_worker.rb new file mode 100644 index 0000000000..1c2a6d1e76 --- /dev/null +++ b/app/workers/tag_unmerge_worker.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class TagUnmergeWorker + include Sidekiq::Worker + include DatabaseHelper + + sidekiq_options queue: 'pull' + + def perform(from_tag_id, into_account_id) + with_primary do + @from_tag = Tag.find(from_tag_id) + @into_account = Account.find(into_account_id) + end + + with_read_replica do + FeedManager.instance.unmerge_tag_from_home(@from_tag, @into_account) + end + rescue ActiveRecord::RecordNotFound + true + end +end diff --git a/spec/workers/tag_unmerge_worker_spec.rb b/spec/workers/tag_unmerge_worker_spec.rb new file mode 100644 index 0000000000..5d3a12c449 --- /dev/null +++ b/spec/workers/tag_unmerge_worker_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe TagUnmergeWorker do + subject { described_class.new } + + describe 'perform' do + let(:follower) { Fabricate(:account) } + let(:followed) { Fabricate(:account) } + let(:followed_tag) { Fabricate(:tag) } + let(:unchanged_followed_tag) { Fabricate(:tag) } + let(:status_from_followed) { Fabricate(:status, created_at: 2.hours.ago, account: followed) } + let(:tagged_status) { Fabricate(:status, created_at: 1.hour.ago) } + let(:unchanged_tagged_status) { Fabricate(:status) } + + before do + tagged_status.tags << followed_tag + unchanged_tagged_status.tags << followed_tag + unchanged_tagged_status.tags << unchanged_followed_tag + + tag_follow = TagFollow.create_with(rate_limit: false).find_or_create_by!(tag: followed_tag, account: follower) + TagFollow.create_with(rate_limit: false).find_or_create_by!(tag: unchanged_followed_tag, account: follower) + + FeedManager.instance.push_to_home(follower, status_from_followed, update: false) + FeedManager.instance.push_to_home(follower, tagged_status, update: false) + FeedManager.instance.push_to_home(follower, unchanged_tagged_status, update: false) + + tag_follow.destroy! + end + + it 'removes the expected status from the feed' do + expect { subject.perform(followed_tag.id, follower.id) } + .to change { HomeFeed.new(follower).get(10).pluck(:id) } + .from([unchanged_tagged_status.id, tagged_status.id, status_from_followed.id]) + .to([unchanged_tagged_status.id, status_from_followed.id]) + end + end +end From 2a9063e36ad107b021b85b0ce937b95974a9cd70 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 14:07:36 +0200 Subject: [PATCH 03/18] Update dependency react-select to v5.7.4 (#26033) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6f31752b08..0523961667 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1279,17 +1279,17 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.42.0.tgz#484a1d638de2911e6f5a30c12f49c7e4a3270fb6" integrity sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw== -"@floating-ui/core@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.3.0.tgz#113bc85fa102cf890ae801668f43ee265c547a09" - integrity sha512-vX1WVAdPjZg9DkDkC+zEx/tKtnST6/qcNpwcjeBgco3XRNHz5PUA+ivi/yr6G3o0kMR60uKBJcfOdfzOFI7PMQ== +"@floating-ui/core@^1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.3.1.tgz#4d795b649cc3b1cbb760d191c80dcb4353c9a366" + integrity sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g== "@floating-ui/dom@^1.0.1": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.3.0.tgz#69456f2164fc3d33eb40837686eaf71537235ac9" - integrity sha512-qIAwejE3r6NeA107u4ELDKkH8+VtgRKdXqtSPaKflL2S2V+doyN+Wt9s5oHKXPDo4E8TaVXaHT3+6BbagH31xw== + version "1.4.5" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.4.5.tgz#336dfb9870c98b471ff5802002982e489b8bd1c5" + integrity sha512-96KnRWkRnuBSSFbj0sFGwwOUd8EkiecINVl0O9wiZlZ64EkpyAOG3Xc2vKKNJmru0Z7RqWNymA+6b8OZqjgyyw== dependencies: - "@floating-ui/core" "^1.3.0" + "@floating-ui/core" "^1.3.1" "@formatjs/cli@^6.1.1": version "6.1.3" @@ -9686,9 +9686,9 @@ react-router@^4.3.1: warning "^4.0.1" react-select@*, react-select@^5.7.3: - version "5.7.3" - resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.7.3.tgz#fa0dc9a23cad6ff3871ad3829f6083a4b54961a2" - integrity sha512-z8i3NCuFFWL3w27xq92rBkVI2onT0jzIIPe480HlBjXJ3b5o6Q+Clp4ydyeKrj9DZZ3lrjawwLC5NGl0FSvUDg== + version "5.7.4" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.7.4.tgz#d8cad96e7bc9d6c8e2709bdda8f4363c5dd7ea7d" + integrity sha512-NhuE56X+p9QDFh4BgeygHFIvJJszO1i1KSkg/JPcIJrbovyRtI+GuOEa4XzFCEpZRAEoEI8u/cAHK+jG/PgUzQ== dependencies: "@babel/runtime" "^7.12.0" "@emotion/cache" "^11.4.0" From 5096deb818f416c9afa8d5b612fabe267e546239 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 17 Jul 2023 08:08:56 -0400 Subject: [PATCH 04/18] Fix haml lint Rubocop `Style/RedundantStringCoercion` cop (#25975) --- .haml-lint_todo.yml | 4 ++-- app/views/settings/imports/show.html.haml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index 31b79f7db2..ce214bfa51 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -1,13 +1,13 @@ # This configuration was generated by # `haml-lint --auto-gen-config` -# on 2023-07-11 23:58:05 +0200 using Haml-Lint version 0.48.0. +# on 2023-07-13 11:24:52 -0400 using Haml-Lint version 0.48.0. # The point is for the user to remove these configuration records # one by one as the lints are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of Haml-Lint, may require this file to be generated again. linters: - # Offense count: 94 + # Offense count: 93 RuboCop: enabled: false diff --git a/app/views/settings/imports/show.html.haml b/app/views/settings/imports/show.html.haml index 65954e3e1e..893c5c8d29 100644 --- a/app/views/settings/imports/show.html.haml +++ b/app/views/settings/imports/show.html.haml @@ -1,13 +1,13 @@ - content_for :page_title do - = t("imports.titles.#{@bulk_import.type.to_s}") + = t("imports.titles.#{@bulk_import.type}") - if @bulk_import.likely_mismatched? .flash-message.warning= t("imports.mismatched_types_warning") - if @bulk_import.overwrite? - %p.hint= t("imports.overwrite_preambles.#{@bulk_import.type.to_s}_html", filename: @bulk_import.original_filename, total_items: @bulk_import.total_items) + %p.hint= t("imports.overwrite_preambles.#{@bulk_import.type}_html", filename: @bulk_import.original_filename, total_items: @bulk_import.total_items) - else - %p.hint= t("imports.preambles.#{@bulk_import.type.to_s}_html", filename: @bulk_import.original_filename, total_items: @bulk_import.total_items) + %p.hint= t("imports.preambles.#{@bulk_import.type}_html", filename: @bulk_import.original_filename, total_items: @bulk_import.total_items) .simple_form .actions From 361dd432354279281cde6a6fdf99eae68787a421 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 17 Jul 2023 09:07:29 -0400 Subject: [PATCH 05/18] Fix haml-lint Rubocop `lambda` cop (#25946) --- .haml-lint_todo.yml | 4 ++-- app/views/admin/domain_blocks/edit.html.haml | 2 +- app/views/admin/domain_blocks/new.html.haml | 2 +- app/views/admin/ip_blocks/new.html.haml | 4 ++-- app/views/admin/roles/_form.html.haml | 2 +- app/views/admin/settings/about/show.html.haml | 4 ++-- app/views/admin/settings/appearance/show.html.haml | 2 +- app/views/admin/settings/registrations/show.html.haml | 2 +- app/views/filters/_filter_fields.html.haml | 4 ++-- app/views/invites/_form.html.haml | 4 ++-- app/views/settings/applications/_fields.html.haml | 2 +- app/views/settings/preferences/appearance/show.html.haml | 6 +++--- app/views/settings/preferences/other/show.html.haml | 6 +++--- app/views/statuses_cleanup/show.html.haml | 2 +- 14 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index ce214bfa51..f13be210b7 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -1,13 +1,13 @@ # This configuration was generated by # `haml-lint --auto-gen-config` -# on 2023-07-13 11:24:52 -0400 using Haml-Lint version 0.48.0. +# on 2023-07-17 08:54:02 -0400 using Haml-Lint version 0.48.0. # The point is for the user to remove these configuration records # one by one as the lints are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of Haml-Lint, may require this file to be generated again. linters: - # Offense count: 93 + # Offense count: 71 RuboCop: enabled: false diff --git a/app/views/admin/domain_blocks/edit.html.haml b/app/views/admin/domain_blocks/edit.html.haml index 39c6d108a7..66bc0e241a 100644 --- a/app/views/admin/domain_blocks/edit.html.haml +++ b/app/views/admin/domain_blocks/edit.html.haml @@ -12,7 +12,7 @@ = f.input :domain, wrapper: :with_label, label: t('admin.domain_blocks.domain'), hint: t('admin.domain_blocks.new.hint'), required: true, readonly: true, disabled: true .fields-row__column.fields-row__column-6.fields-group - = f.input :severity, collection: DomainBlock.severities.keys, wrapper: :with_label, include_blank: false, label_method: lambda { |type| t("admin.domain_blocks.new.severity.#{type}") }, hint: t('admin.domain_blocks.new.severity.desc_html') + = f.input :severity, collection: DomainBlock.severities.keys, wrapper: :with_label, include_blank: false, label_method: ->(type) { t("admin.domain_blocks.new.severity.#{type}") }, hint: t('admin.domain_blocks.new.severity.desc_html') .fields-group = f.input :reject_media, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_media'), hint: I18n.t('admin.domain_blocks.reject_media_hint') diff --git a/app/views/admin/domain_blocks/new.html.haml b/app/views/admin/domain_blocks/new.html.haml index bcaa331b56..5c28508cfa 100644 --- a/app/views/admin/domain_blocks/new.html.haml +++ b/app/views/admin/domain_blocks/new.html.haml @@ -12,7 +12,7 @@ = f.input :domain, wrapper: :with_label, label: t('admin.domain_blocks.domain'), hint: t('.hint'), required: true .fields-row__column.fields-row__column-6.fields-group - = f.input :severity, collection: DomainBlock.severities.keys, wrapper: :with_label, include_blank: false, label_method: lambda { |type| t(".severity.#{type}") }, hint: t('.severity.desc_html') + = f.input :severity, collection: DomainBlock.severities.keys, wrapper: :with_label, include_blank: false, label_method: ->(type) { t(".severity.#{type}") }, hint: t('.severity.desc_html') .fields-group = f.input :reject_media, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_media'), hint: I18n.t('admin.domain_blocks.reject_media_hint') diff --git a/app/views/admin/ip_blocks/new.html.haml b/app/views/admin/ip_blocks/new.html.haml index 69f6b98b9b..405c73c90e 100644 --- a/app/views/admin/ip_blocks/new.html.haml +++ b/app/views/admin/ip_blocks/new.html.haml @@ -8,10 +8,10 @@ = f.input :ip, as: :string, wrapper: :with_block_label, input_html: { placeholder: '192.0.2.0/24' } .fields-group - = f.input :expires_in, wrapper: :with_block_label, collection: [1.day, 2.weeks, 1.month, 6.months, 1.year, 3.years].map(&:to_i), label_method: lambda { |i| I18n.t("admin.ip_blocks.expires_in.#{i}") }, prompt: I18n.t('invites.expires_in_prompt') + = f.input :expires_in, wrapper: :with_block_label, collection: [1.day, 2.weeks, 1.month, 6.months, 1.year, 3.years].map(&:to_i), label_method: ->(i) { I18n.t("admin.ip_blocks.expires_in.#{i}") }, prompt: I18n.t('invites.expires_in_prompt') .fields-group - = f.input :severity, as: :radio_buttons, collection: IpBlock.severities.keys, include_blank: false, wrapper: :with_block_label, label_method: lambda { |severity| safe_join([I18n.t("simple_form.labels.ip_block.severities.#{severity}"), content_tag(:span, I18n.t("simple_form.hints.ip_block.severities.#{severity}"), class: 'hint')]) } + = f.input :severity, as: :radio_buttons, collection: IpBlock.severities.keys, include_blank: false, wrapper: :with_block_label, label_method: ->(severity) { safe_join([I18n.t("simple_form.labels.ip_block.severities.#{severity}"), content_tag(:span, I18n.t("simple_form.hints.ip_block.severities.#{severity}"), class: 'hint')]) } .fields-group = f.input :comment, as: :string, wrapper: :with_block_label diff --git a/app/views/admin/roles/_form.html.haml b/app/views/admin/roles/_form.html.haml index 31f78f2405..3cbec0d0b5 100644 --- a/app/views/admin/roles/_form.html.haml +++ b/app/views/admin/roles/_form.html.haml @@ -32,7 +32,7 @@ - (@role.everyone? ? UserRole::Flags::CATEGORIES.slice(:invites) : UserRole::Flags::CATEGORIES).each do |category, permissions| %h4= t(category, scope: 'admin.roles.categories') - = f.input :permissions_as_keys, collection: permissions, wrapper: :with_block_label, include_blank: false, label_method: lambda { |privilege| safe_join([t("admin.roles.privileges.#{privilege}"), content_tag(:span, t("admin.roles.privileges.#{privilege}_description"), class: 'hint')]) }, required: false, as: :check_boxes, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li', label: false, hint: false, disabled: permissions.filter { |privilege| UserRole::FLAGS[privilege] & current_user.role.computed_permissions == 0 } + = f.input :permissions_as_keys, collection: permissions, wrapper: :with_block_label, include_blank: false, label_method: ->(privilege) { safe_join([t("admin.roles.privileges.#{privilege}"), content_tag(:span, t("admin.roles.privileges.#{privilege}_description"), class: 'hint')]) }, required: false, as: :check_boxes, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li', label: false, hint: false, disabled: permissions.filter { |privilege| UserRole::FLAGS[privilege] & current_user.role.computed_permissions == 0 } %hr.spacer/ diff --git a/app/views/admin/settings/about/show.html.haml b/app/views/admin/settings/about/show.html.haml index 2aaa64abe7..7b1b907ee2 100644 --- a/app/views/admin/settings/about/show.html.haml +++ b/app/views/admin/settings/about/show.html.haml @@ -22,9 +22,9 @@ .fields-row .fields-row__column.fields-row__column-6.fields-group - = f.input :show_domain_blocks, wrapper: :with_label, collection: %i(disabled users all), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li' + = f.input :show_domain_blocks, wrapper: :with_label, collection: %i(disabled users all), label_method: ->(value) { t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li' .fields-row__column.fields-row__column-6.fields-group - = f.input :show_domain_blocks_rationale, wrapper: :with_label, collection: %i(disabled users all), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li' + = f.input :show_domain_blocks_rationale, wrapper: :with_label, collection: %i(disabled users all), label_method: ->(value) { t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li' .fields-group = f.input :status_page_url, wrapper: :with_block_label, input_html: { placeholder: "https://status.#{Rails.configuration.x.local_domain}" } diff --git a/app/views/admin/settings/appearance/show.html.haml b/app/views/admin/settings/appearance/show.html.haml index d321c4b04b..1e73ab0a24 100644 --- a/app/views/admin/settings/appearance/show.html.haml +++ b/app/views/admin/settings/appearance/show.html.haml @@ -14,7 +14,7 @@ %p.lead= t('admin.settings.appearance.preamble') .fields-group - = f.input :theme, collection: Themes.instance.names, label_method: lambda { |theme| I18n.t("themes.#{theme}", default: theme) }, wrapper: :with_label, include_blank: false + = f.input :theme, collection: Themes.instance.names, label_method: ->(theme) { I18n.t("themes.#{theme}", default: theme) }, wrapper: :with_label, include_blank: false .fields-group = f.input :custom_css, wrapper: :with_block_label, as: :text, input_html: { rows: 8 } diff --git a/app/views/admin/settings/registrations/show.html.haml b/app/views/admin/settings/registrations/show.html.haml index 84492a08a1..e06385bc81 100644 --- a/app/views/admin/settings/registrations/show.html.haml +++ b/app/views/admin/settings/registrations/show.html.haml @@ -15,7 +15,7 @@ .fields-row .fields-row__column.fields-row__column-6.fields-group - = f.input :registrations_mode, collection: %w(open approved none), wrapper: :with_label, include_blank: false, label_method: lambda { |mode| I18n.t("admin.settings.registrations_mode.modes.#{mode}") } + = f.input :registrations_mode, collection: %w(open approved none), wrapper: :with_label, include_blank: false, label_method: ->(mode) { I18n.t("admin.settings.registrations_mode.modes.#{mode}") } .fields-row__column.fields-row__column-6.fields-group = f.input :require_invite_text, as: :boolean, wrapper: :with_label, disabled: !approved_registrations? diff --git a/app/views/filters/_filter_fields.html.haml b/app/views/filters/_filter_fields.html.haml index a554b55ff6..0690e8dd59 100644 --- a/app/views/filters/_filter_fields.html.haml +++ b/app/views/filters/_filter_fields.html.haml @@ -2,10 +2,10 @@ .fields-row__column.fields-row__column-6.fields-group = f.input :title, as: :string, wrapper: :with_label, hint: false .fields-row__column.fields-row__column-6.fields-group - = f.input :expires_in, wrapper: :with_label, collection: [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].map(&:to_i), label_method: lambda { |i| I18n.t("invites.expires_in.#{i}") }, include_blank: I18n.t('invites.expires_in_prompt') + = f.input :expires_in, wrapper: :with_label, collection: [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].map(&:to_i), label_method: ->(i) { I18n.t("invites.expires_in.#{i}") }, include_blank: I18n.t('invites.expires_in_prompt') .fields-group - = f.input :context, wrapper: :with_block_label, collection: CustomFilter::VALID_CONTEXTS, as: :check_boxes, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li', label_method: lambda { |context| I18n.t("filters.contexts.#{context}") }, include_blank: false + = f.input :context, wrapper: :with_block_label, collection: CustomFilter::VALID_CONTEXTS, as: :check_boxes, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li', label_method: ->(context) { I18n.t("filters.contexts.#{context}") }, include_blank: false %hr.spacer/ diff --git a/app/views/invites/_form.html.haml b/app/views/invites/_form.html.haml index 3a2a5ef0e1..7ea521ebc7 100644 --- a/app/views/invites/_form.html.haml +++ b/app/views/invites/_form.html.haml @@ -3,9 +3,9 @@ .fields-row .fields-row__column.fields-row__column-6.fields-group - = f.input :max_uses, wrapper: :with_label, collection: [1, 5, 10, 25, 50, 100], label_method: lambda { |num| I18n.t('invites.max_uses', count: num) }, prompt: I18n.t('invites.max_uses_prompt') + = f.input :max_uses, wrapper: :with_label, collection: [1, 5, 10, 25, 50, 100], label_method: ->(num) { I18n.t('invites.max_uses', count: num) }, prompt: I18n.t('invites.max_uses_prompt') .fields-row__column.fields-row__column-6.fields-group - = f.input :expires_in, wrapper: :with_label, collection: [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].map(&:to_i), label_method: lambda { |i| I18n.t("invites.expires_in.#{i}") }, prompt: I18n.t('invites.expires_in_prompt') + = f.input :expires_in, wrapper: :with_label, collection: [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].map(&:to_i), label_method: ->(i) { I18n.t("invites.expires_in.#{i}") }, prompt: I18n.t('invites.expires_in_prompt') .fields-group = f.input :autofollow, wrapper: :with_label diff --git a/app/views/settings/applications/_fields.html.haml b/app/views/settings/applications/_fields.html.haml index ffd2491d29..f4deb5b6ff 100644 --- a/app/views/settings/applications/_fields.html.haml +++ b/app/views/settings/applications/_fields.html.haml @@ -15,4 +15,4 @@ %span.hint= t('simple_form.hints.defaults.scopes') - Doorkeeper.configuration.scopes.group_by { |s| s.split(':').first }.each do |k, v| - = f.input :scopes, label: false, hint: false, collection: v.sort, wrapper: :with_block_label, include_blank: false, label_method: lambda { |scope| safe_join([content_tag(:samp, scope, class: class_for_scope(scope)), content_tag(:span, t("doorkeeper.scopes.#{scope}"), class: 'hint')]) }, selected: f.object.scopes.all, required: false, as: :check_boxes, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li' + = f.input :scopes, label: false, hint: false, collection: v.sort, wrapper: :with_block_label, include_blank: false, label_method: ->(scope) { safe_join([content_tag(:samp, scope, class: class_for_scope(scope)), content_tag(:span, t("doorkeeper.scopes.#{scope}"), class: 'hint')]) }, selected: f.object.scopes.all, required: false, as: :check_boxes, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li' diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml index ce3a30c5ee..ec7a3d1401 100644 --- a/app/views/settings/preferences/appearance/show.html.haml +++ b/app/views/settings/preferences/appearance/show.html.haml @@ -7,13 +7,13 @@ = simple_form_for current_user, url: settings_preferences_appearance_path, html: { method: :put, id: 'edit_user' } do |f| .fields-row .fields-group.fields-row__column.fields-row__column-6 - = f.input :locale, collection: I18n.available_locales, wrapper: :with_label, include_blank: false, label_method: lambda { |locale| native_locale_name(locale) }, selected: I18n.locale, hint: false + = f.input :locale, collection: I18n.available_locales, wrapper: :with_label, include_blank: false, label_method: ->(locale) { native_locale_name(locale) }, selected: I18n.locale, hint: false .fields-group.fields-row__column.fields-row__column-6 = f.input :time_zone, wrapper: :with_label, collection: ActiveSupport::TimeZone.all.map { |tz| ["(GMT#{tz.formatted_offset}) #{tz.name}", tz.tzinfo.name] }, hint: false .fields-group = f.simple_fields_for :settings, current_user.settings do |ff| - = ff.input :theme, collection: Themes.instance.names, label_method: lambda { |theme| I18n.t("themes.#{theme}", default: theme) }, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_theme'), include_blank: false, hint: false + = ff.input :theme, collection: Themes.instance.names, label_method: ->(theme) { I18n.t("themes.#{theme}", default: theme) }, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_theme'), include_blank: false, hint: false - unless I18n.locale == :en .flash-message.translation-prompt @@ -57,7 +57,7 @@ %h4= t 'appearance.sensitive_content' .fields-group - = ff.input :'web.display_media', collection: ['default', 'show_all', 'hide_all'],label_method: lambda { |item| t("simple_form.hints.defaults.setting_display_media_#{item}") }, hint: false, as: :radio_buttons, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li', wrapper: :with_floating_label, label: I18n.t('simple_form.labels.defaults.setting_display_media') + = ff.input :'web.display_media', collection: ['default', 'show_all', 'hide_all'], label_method: ->(item) { t("simple_form.hints.defaults.setting_display_media_#{item}") }, hint: false, as: :radio_buttons, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li', wrapper: :with_floating_label, label: I18n.t('simple_form.labels.defaults.setting_display_media') .fields-group = ff.input :'web.use_blurhash', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_use_blurhash'), hint: I18n.t('simple_form.hints.defaults.setting_use_blurhash') diff --git a/app/views/settings/preferences/other/show.html.haml b/app/views/settings/preferences/other/show.html.haml index 6590ec7c21..b8beed394c 100644 --- a/app/views/settings/preferences/other/show.html.haml +++ b/app/views/settings/preferences/other/show.html.haml @@ -18,10 +18,10 @@ .fields-row .fields-group.fields-row__column.fields-row__column-6 - = ff.input :default_privacy, collection: Status.selectable_visibilities, wrapper: :with_label, include_blank: false, label_method: lambda { |visibility| safe_join([I18n.t("statuses.visibilities.#{visibility}"), I18n.t("statuses.visibilities.#{visibility}_long")], ' - ') }, required: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_privacy') + = ff.input :default_privacy, collection: Status.selectable_visibilities, wrapper: :with_label, include_blank: false, label_method: ->(visibility) { safe_join([I18n.t("statuses.visibilities.#{visibility}"), I18n.t("statuses.visibilities.#{visibility}_long")], ' - ') }, required: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_privacy') .fields-group.fields-row__column.fields-row__column-6 - = ff.input :default_language, collection: [nil] + filterable_languages, wrapper: :with_label, label_method: lambda { |locale| locale.nil? ? I18n.t('statuses.default_language') : native_locale_name(locale) }, required: false, include_blank: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_language') + = ff.input :default_language, collection: [nil] + filterable_languages, wrapper: :with_label, label_method: ->(locale) { locale.nil? ? I18n.t('statuses.default_language') : native_locale_name(locale) }, required: false, include_blank: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_language') .fields-group = ff.input :default_sensitive, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_default_sensitive'), hint: I18n.t('simple_form.hints.defaults.setting_default_sensitive') @@ -32,7 +32,7 @@ %h4= t 'preferences.public_timelines' .fields-group - = f.input :chosen_languages, collection: filterable_languages, wrapper: :with_block_label, include_blank: false, label_method: lambda { |locale| native_locale_name(locale) }, required: false, as: :check_boxes, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li' + = f.input :chosen_languages, collection: filterable_languages, wrapper: :with_block_label, include_blank: false, label_method: ->(locale) { native_locale_name(locale) }, required: false, as: :check_boxes, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li' .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/statuses_cleanup/show.html.haml b/app/views/statuses_cleanup/show.html.haml index 59de4b5aa6..99b9ddab93 100644 --- a/app/views/statuses_cleanup/show.html.haml +++ b/app/views/statuses_cleanup/show.html.haml @@ -10,7 +10,7 @@ .fields-row__column.fields-row__column-6.fields-group = f.input :enabled, as: :boolean, wrapper: :with_label, label: t('statuses_cleanup.enabled'), hint: t('statuses_cleanup.enabled_hint') .fields-row__column.fields-row__column-6.fields-group - = f.input :min_status_age, wrapper: :with_label, label: t('statuses_cleanup.min_age_label'), collection: AccountStatusesCleanupPolicy::ALLOWED_MIN_STATUS_AGE.map(&:to_i), label_method: lambda { |i| t("statuses_cleanup.min_age.#{i}") }, include_blank: false, hint: false + = f.input :min_status_age, wrapper: :with_label, label: t('statuses_cleanup.min_age_label'), collection: AccountStatusesCleanupPolicy::ALLOWED_MIN_STATUS_AGE.map(&:to_i), label_method: ->(i) { t("statuses_cleanup.min_age.#{i}") }, include_blank: false, hint: false .flash-message= t('statuses_cleanup.explanation') From bd33efdf1624d484c82bb328a56df1f1ab8a878a Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 17 Jul 2023 09:38:04 -0400 Subject: [PATCH 06/18] Fix haml-lint Rubocop `Style/MinMaxComparison` cop (#25974) --- .haml-lint_todo.yml | 6 +++--- app/views/statuses/_poll.html.haml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index f13be210b7..e1314742a6 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -1,17 +1,17 @@ # This configuration was generated by # `haml-lint --auto-gen-config` -# on 2023-07-17 08:54:02 -0400 using Haml-Lint version 0.48.0. +# on 2023-07-17 09:15:24 -0400 using Haml-Lint version 0.48.0. # The point is for the user to remove these configuration records # one by one as the lints are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of Haml-Lint, may require this file to be generated again. linters: - # Offense count: 71 + # Offense count: 70 RuboCop: enabled: false - # Offense count: 960 + # Offense count: 959 LineLength: enabled: false diff --git a/app/views/statuses/_poll.html.haml b/app/views/statuses/_poll.html.haml index 248c6058cb..0805c48958 100644 --- a/app/views/statuses/_poll.html.haml +++ b/app/views/statuses/_poll.html.haml @@ -17,7 +17,7 @@ %span.poll__voted %i.poll__voted__mark.fa.fa-check - %progress{ max: 100, value: percent < 1 ? 1 : percent, 'aria-hidden': 'true' } + %progress{ max: 100, value: [percent, 1].max, 'aria-hidden': 'true' } %span.poll__chart - else %label.poll__option>< From 664b0ca8cb10383c4c64964f830c41ddef4acb27 Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Mon, 17 Jul 2023 22:51:30 +0900 Subject: [PATCH 07/18] Check if json body is null on Activitipub::ProcessingWorker (#26021) --- app/services/activitypub/process_collection_service.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/services/activitypub/process_collection_service.rb b/app/services/activitypub/process_collection_service.rb index 52f48bd49d..4f049a5ae9 100644 --- a/app/services/activitypub/process_collection_service.rb +++ b/app/services/activitypub/process_collection_service.rb @@ -8,6 +8,8 @@ class ActivityPub::ProcessCollectionService < BaseService @json = original_json = Oj.load(body, mode: :strict) @options = options + return unless @json.is_a?(Hash) + begin @json = compact(@json) if @json['signature'].is_a?(Hash) rescue JSON::LD::JsonLdError => e From c80ecf2ff776ba14df0bff5e72e36da621cefc2b Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Mon, 17 Jul 2023 10:10:43 -0400 Subject: [PATCH 08/18] Increase PR Rebase job retries (#25926) --- .github/workflows/rebase-needed.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rebase-needed.yml b/.github/workflows/rebase-needed.yml index 295039c414..06d835c090 100644 --- a/.github/workflows/rebase-needed.yml +++ b/.github/workflows/rebase-needed.yml @@ -23,5 +23,5 @@ jobs: repoToken: '${{ secrets.GITHUB_TOKEN }}' commentOnClean: This pull request has resolved merge conflicts and is ready for review. commentOnDirty: This pull request has merge conflicts that must be resolved before it can be merged. - retryMax: 10 + retryMax: 30 continueOnMissingPermissions: false From 8a1aabaac1f22684bd80512f734636ec5d8a3642 Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Mon, 17 Jul 2023 11:20:11 -0300 Subject: [PATCH 09/18] Migrate to request specs in `/api/v1/timelines/home` (#25743) --- .../api/v1/timelines/home_controller_spec.rb | 44 -------- spec/requests/api/v1/timelines/home_spec.rb | 101 ++++++++++++++++++ 2 files changed, 101 insertions(+), 44 deletions(-) delete mode 100644 spec/controllers/api/v1/timelines/home_controller_spec.rb create mode 100644 spec/requests/api/v1/timelines/home_spec.rb diff --git a/spec/controllers/api/v1/timelines/home_controller_spec.rb b/spec/controllers/api/v1/timelines/home_controller_spec.rb deleted file mode 100644 index bb46d0aba4..0000000000 --- a/spec/controllers/api/v1/timelines/home_controller_spec.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe Api::V1::Timelines::HomeController do - render_views - - let(:user) { Fabricate(:user, current_sign_in_at: 1.day.ago) } - - before do - allow(controller).to receive(:doorkeeper_token) { token } - end - - context 'with a user context' do - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:statuses') } - - describe 'GET #show' do - before do - follow = Fabricate(:follow, account: user.account) - PostStatusService.new.call(follow.target_account, text: 'New status for user home timeline.') - end - - it 'returns http success' do - get :show - - expect(response).to have_http_status(200) - expect(response.headers['Link'].links.size).to eq(2) - end - end - end - - context 'without a user context' do - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil, scopes: 'read') } - - describe 'GET #show' do - it 'returns http unprocessable entity' do - get :show - - expect(response).to have_http_status(422) - expect(response.headers['Link']).to be_nil - end - end - end -end diff --git a/spec/requests/api/v1/timelines/home_spec.rb b/spec/requests/api/v1/timelines/home_spec.rb new file mode 100644 index 0000000000..5834b90955 --- /dev/null +++ b/spec/requests/api/v1/timelines/home_spec.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Home' do + let(:user) { Fabricate(:user) } + let(:scopes) { 'read:statuses' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/timelines/home' do + subject do + get '/api/v1/timelines/home', headers: headers, params: params + end + + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'write write:statuses' + + context 'when the timeline is available' do + let(:home_statuses) { bob.statuses + ana.statuses } + let!(:bob) { Fabricate(:account) } + let!(:tim) { Fabricate(:account) } + let!(:ana) { Fabricate(:account) } + + before do + user.account.follow!(bob) + user.account.follow!(ana) + PostStatusService.new.call(bob, text: 'New toot from bob.') + PostStatusService.new.call(tim, text: 'New toot from tim.') + PostStatusService.new.call(ana, text: 'New toot from ana.') + end + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the statuses of followed users' do + subject + + expect(body_as_json.pluck(:id)).to match_array(home_statuses.map { |status| status.id.to_s }) + end + + context 'with limit param' do + let(:params) { { limit: 1 } } + + it 'returns only the requested number of statuses' do + subject + + expect(body_as_json.size).to eq(params[:limit]) + end + + it 'sets the correct pagination headers', :aggregate_failures do + subject + + headers = response.headers['Link'] + + expect(headers.find_link(%w(rel prev)).href).to eq(api_v1_timelines_home_url(limit: 1, min_id: ana.statuses.first.id.to_s)) + expect(headers.find_link(%w(rel next)).href).to eq(api_v1_timelines_home_url(limit: 1, max_id: ana.statuses.first.id.to_s)) + end + end + end + + context 'when the timeline is regenerating' do + let(:timeline) { instance_double(HomeFeed, regenerating?: true, get: []) } + + before do + allow(HomeFeed).to receive(:new).and_return(timeline) + end + + it 'returns http partial content' do + subject + + expect(response).to have_http_status(206) + end + end + + context 'without an authorization header' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + + context 'without a user context' do + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil, scopes: scopes) } + + it 'returns http unprocessable entity', :aggregate_failures do + subject + + expect(response).to have_http_status(422) + expect(response.headers['Link']).to be_nil + end + end + end +end From 6cdc8408a9ac9d0fd157d18b1ff173c8f416cb8d Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Mon, 17 Jul 2023 11:22:33 -0300 Subject: [PATCH 10/18] Migrate to request specs in `/api/v1/emails/confirmations` (#25686) --- .../api/v1/emails/confirmations_spec.rb} | 124 +++++++++++------- 1 file changed, 75 insertions(+), 49 deletions(-) rename spec/{controllers/api/v1/emails/confirmations_controller_spec.rb => requests/api/v1/emails/confirmations_spec.rb} (50%) diff --git a/spec/controllers/api/v1/emails/confirmations_controller_spec.rb b/spec/requests/api/v1/emails/confirmations_spec.rb similarity index 50% rename from spec/controllers/api/v1/emails/confirmations_controller_spec.rb rename to spec/requests/api/v1/emails/confirmations_spec.rb index 80d6c8799d..8f5171ee78 100644 --- a/spec/controllers/api/v1/emails/confirmations_controller_spec.rb +++ b/spec/requests/api/v1/emails/confirmations_spec.rb @@ -2,27 +2,34 @@ require 'rails_helper' -RSpec.describe Api::V1::Emails::ConfirmationsController do +RSpec.describe 'Confirmations' do let(:confirmed_at) { nil } let(:user) { Fabricate(:user, confirmed_at: confirmed_at) } - let(:app) { Fabricate(:application) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes, application: app) } - let(:scopes) { 'write' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'read:accounts write:accounts' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'POST /api/v1/emails/confirmations' do + subject do + post '/api/v1/emails/confirmations', headers: headers, params: params + end + + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'read read:accounts' - describe '#create' do context 'with an oauth token' do - before do - allow(controller).to receive(:doorkeeper_token) { token } - end + context 'when user was created by a different application' do + let(:user) { Fabricate(:user, confirmed_at: confirmed_at, created_by_application: Fabricate(:application)) } - context 'when from a random app' do it 'returns http forbidden' do - post :create + subject + expect(response).to have_http_status(403) end end - context 'when from an app that created the account' do + context 'when user was created by the same application' do before do user.update(created_by_application: token.application) end @@ -31,55 +38,79 @@ RSpec.describe Api::V1::Emails::ConfirmationsController do let(:confirmed_at) { Time.now.utc } it 'returns http forbidden' do - post :create + subject + expect(response).to have_http_status(403) end - context 'with user changed e-mail and has not confirmed it' do + context 'when user changed e-mail and has not confirmed it' do before do user.update(email: 'foo@bar.com') end it 'returns http success' do - post :create - expect(response).to have_http_status(:success) + subject + + expect(response).to have_http_status(200) end end end context 'when the account is unconfirmed' do it 'returns http success' do - post :create - expect(response).to have_http_status(:success) + subject + + expect(response).to have_http_status(200) + end + end + + context 'with email param' do + let(:params) { { email: 'foo@bar.com' } } + + it "updates the user's e-mail address", :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.reload.unconfirmed_email).to eq('foo@bar.com') + end + end + + context 'with invalid email param' do + let(:params) { { email: 'invalid' } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) end end end end context 'without an oauth token' do + let(:headers) { {} } + it 'returns http unauthorized' do - post :create + subject + expect(response).to have_http_status(401) end end end - describe '#check' do - let(:scopes) { 'read' } + describe 'GET /api/v1/emails/check_confirmation' do + subject do + get '/api/v1/emails/check_confirmation', headers: headers + end + + it_behaves_like 'forbidden for wrong scope', 'write' context 'with an oauth token' do - before do - allow(controller).to receive(:doorkeeper_token) { token } - end - context 'when the account is not confirmed' do - it 'returns http success' do - get :check - expect(response).to have_http_status(200) - end + it 'returns the confirmation status successfully', :aggregate_failures do + subject - it 'returns false' do - get :check + expect(response).to have_http_status(200) expect(body_as_json).to be false end end @@ -87,31 +118,27 @@ RSpec.describe Api::V1::Emails::ConfirmationsController do context 'when the account is confirmed' do let(:confirmed_at) { Time.now.utc } - it 'returns http success' do - get :check - expect(response).to have_http_status(200) - end + it 'returns the confirmation status successfully', :aggregate_failures do + subject - it 'returns true' do - get :check + expect(response).to have_http_status(200) expect(body_as_json).to be true end end end context 'with an authentication cookie' do + let(:headers) { {} } + before do sign_in user, scope: :user end context 'when the account is not confirmed' do - it 'returns http success' do - get :check - expect(response).to have_http_status(200) - end + it 'returns the confirmation status successfully', :aggregate_failures do + subject - it 'returns false' do - get :check + expect(response).to have_http_status(200) expect(body_as_json).to be false end end @@ -119,21 +146,20 @@ RSpec.describe Api::V1::Emails::ConfirmationsController do context 'when the account is confirmed' do let(:confirmed_at) { Time.now.utc } - it 'returns http success' do - get :check - expect(response).to have_http_status(200) - end + it 'returns the confirmation status successfully', :aggregate_failures do + subject - it 'returns true' do - get :check + expect(response).to have_http_status(200) expect(body_as_json).to be true end end end context 'without an oauth token and an authentication cookie' do + let(:headers) { {} } + it 'returns http unauthorized' do - get :check + subject expect(response).to have_http_status(401) end From 1aea938d3d43207b82ac59e0c0f982583875c5ea Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Mon, 17 Jul 2023 11:24:05 -0300 Subject: [PATCH 11/18] Migrate to request specs in `/api/v1/statuses/:status_id/pin` (#25635) --- .../api/v1/statuses/pins_controller_spec.rb | 57 -------- spec/requests/api/v1/statuses/pins_spec.rb | 131 ++++++++++++++++++ 2 files changed, 131 insertions(+), 57 deletions(-) delete mode 100644 spec/controllers/api/v1/statuses/pins_controller_spec.rb create mode 100644 spec/requests/api/v1/statuses/pins_spec.rb diff --git a/spec/controllers/api/v1/statuses/pins_controller_spec.rb b/spec/controllers/api/v1/statuses/pins_controller_spec.rb deleted file mode 100644 index 8bdaf8b548..0000000000 --- a/spec/controllers/api/v1/statuses/pins_controller_spec.rb +++ /dev/null @@ -1,57 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe Api::V1::Statuses::PinsController 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, scopes: 'write:accounts', application: app) } - - context 'with an oauth token' do - before do - allow(controller).to receive(:doorkeeper_token) { token } - end - - describe 'POST #create' do - let(:status) { Fabricate(:status, account: user.account) } - - before do - post :create, params: { status_id: status.id } - end - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'updates the pinned attribute' do - expect(user.account.pinned?(status)).to be true - end - - it 'return json with updated attributes' do - hash_body = body_as_json - - expect(hash_body[:id]).to eq status.id.to_s - expect(hash_body[:pinned]).to be true - end - end - - describe 'POST #destroy' do - let(:status) { Fabricate(:status, account: user.account) } - - before do - Fabricate(:status_pin, status: status, account: user.account) - post :destroy, params: { status_id: status.id } - end - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'updates the pinned attribute' do - expect(user.account.pinned?(status)).to be false - end - end - end -end diff --git a/spec/requests/api/v1/statuses/pins_spec.rb b/spec/requests/api/v1/statuses/pins_spec.rb new file mode 100644 index 0000000000..db07fa424f --- /dev/null +++ b/spec/requests/api/v1/statuses/pins_spec.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Pins' do + let(:user) { Fabricate(:user) } + let(:scopes) { 'write:accounts' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'POST /api/v1/statuses/:status_id/pin' do + subject do + post "/api/v1/statuses/#{status.id}/pin", headers: headers + end + + let(:status) { Fabricate(:status, account: user.account) } + + it_behaves_like 'forbidden for wrong scope', 'read read:accounts' + + context 'when the status is public' do + it 'pins the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.pinned?(status)).to be true + end + + it 'return json with updated attributes' do + subject + + expect(body_as_json).to match( + a_hash_including(id: status.id.to_s, pinned: true) + ) + end + end + + context 'when the status is private' do + let(:status) { Fabricate(:status, account: user.account, visibility: :private) } + + it 'pins the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.pinned?(status)).to be true + end + end + + context 'when the status belongs to somebody else' do + let(:status) { Fabricate(:status) } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'when the status does not exist' do + it 'returns http not found' do + post '/api/v1/statuses/-1/pin', headers: headers + + expect(response).to have_http_status(404) + end + end + + context 'without an authorization header' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + end + + describe 'POST /api/v1/statuses/:status_id/unpin' do + subject do + post "/api/v1/statuses/#{status.id}/unpin", headers: headers + end + + let(:status) { Fabricate(:status, account: user.account) } + + context 'when the status is pinned' do + before do + Fabricate(:status_pin, status: status, account: user.account) + end + + it 'unpins the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.pinned?(status)).to be false + end + + it 'return json with updated attributes' do + subject + + expect(body_as_json).to match( + a_hash_including(id: status.id.to_s, pinned: false) + ) + end + end + + context 'when the status is not pinned' do + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + end + + context 'when the status does not exist' do + it 'returns http not found' do + post '/api/v1/statuses/-1/unpin', headers: headers + + expect(response).to have_http_status(404) + end + end + + context 'without an authorization header' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + end +end From 4859958a0c1b405bb7a16646430d044e82f5e923 Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Mon, 17 Jul 2023 11:50:00 -0300 Subject: [PATCH 12/18] Migrate to request specs in `/api/v1/polls` (#25596) --- .../api/v1/polls_controller_spec.rb | 37 --------------- spec/requests/api/v1/polls_spec.rb | 47 +++++++++++++++++++ 2 files changed, 47 insertions(+), 37 deletions(-) delete mode 100644 spec/controllers/api/v1/polls_controller_spec.rb create mode 100644 spec/requests/api/v1/polls_spec.rb diff --git a/spec/controllers/api/v1/polls_controller_spec.rb b/spec/controllers/api/v1/polls_controller_spec.rb deleted file mode 100644 index 3aae5496db..0000000000 --- a/spec/controllers/api/v1/polls_controller_spec.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Api::V1::PollsController do - render_views - - let(:user) { Fabricate(:user) } - let(:scopes) { 'read:statuses' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - - before { allow(controller).to receive(:doorkeeper_token) { token } } - - describe 'GET #show' do - let(:poll) { Fabricate(:poll, status: Fabricate(:status, visibility: visibility)) } - - before do - get :show, params: { id: poll.id } - end - - context 'when parent status is public' do - let(:visibility) { 'public' } - - it 'returns http success' do - expect(response).to have_http_status(200) - end - end - - context 'when parent status is private' do - let(:visibility) { 'private' } - - it 'returns http not found' do - expect(response).to have_http_status(404) - end - end - end -end diff --git a/spec/requests/api/v1/polls_spec.rb b/spec/requests/api/v1/polls_spec.rb new file mode 100644 index 0000000000..1c8a818d59 --- /dev/null +++ b/spec/requests/api/v1/polls_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Polls' do + let(:user) { Fabricate(:user) } + let(:scopes) { 'read:statuses' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/polls/:id' do + subject do + get "/api/v1/polls/#{poll.id}", headers: headers + end + + let(:poll) { Fabricate(:poll, status: Fabricate(:status, visibility: visibility)) } + let(:visibility) { 'public' } + + it_behaves_like 'forbidden for wrong scope', 'write write:statuses' + + context 'when parent status is public' do + it 'returns the poll data successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json).to match( + a_hash_including( + id: poll.id.to_s, + voted: false, + voters_count: poll.voters_count, + votes_count: poll.votes_count + ) + ) + end + end + + context 'when parent status is private' do + let(:visibility) { 'private' } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end +end From 6fb4a756ffa97197c47859f09acb5ff29ef0175a Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Mon, 17 Jul 2023 11:51:49 -0300 Subject: [PATCH 13/18] Migrate to request specs in `/api/v1/statuses/:status_id/bookmark` (#25624) --- .../v1/statuses/bookmarks_controller_spec.rb | 113 ------------- .../api/v1/statuses/bookmarks_spec.rb | 155 ++++++++++++++++++ 2 files changed, 155 insertions(+), 113 deletions(-) delete mode 100644 spec/controllers/api/v1/statuses/bookmarks_controller_spec.rb create mode 100644 spec/requests/api/v1/statuses/bookmarks_spec.rb diff --git a/spec/controllers/api/v1/statuses/bookmarks_controller_spec.rb b/spec/controllers/api/v1/statuses/bookmarks_controller_spec.rb deleted file mode 100644 index 46d7b6c0a5..0000000000 --- a/spec/controllers/api/v1/statuses/bookmarks_controller_spec.rb +++ /dev/null @@ -1,113 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe Api::V1::Statuses::BookmarksController 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, scopes: 'write:bookmarks', application: app) } - - context 'with an oauth token' do - before do - allow(controller).to receive(:doorkeeper_token) { token } - end - - describe 'POST #create' do - let(:status) { Fabricate(:status, account: user.account) } - - before do - post :create, params: { status_id: status.id } - end - - context 'with public status' do - it 'returns http success' do - expect(response).to have_http_status(:success) - end - - it 'updates the bookmarked attribute' do - expect(user.account.bookmarked?(status)).to be true - end - - it 'returns json with updated attributes' do - hash_body = body_as_json - - expect(hash_body[:id]).to eq status.id.to_s - expect(hash_body[:bookmarked]).to be true - end - end - - context 'with private status of not-followed account' do - let(:status) { Fabricate(:status, visibility: :private) } - - it 'returns http not found' do - expect(response).to have_http_status(404) - end - end - end - - describe 'POST #destroy' do - context 'with public status' do - let(:status) { Fabricate(:status, account: user.account) } - - before do - Bookmark.find_or_create_by!(account: user.account, status: status) - post :destroy, params: { status_id: status.id } - end - - it 'returns http success' do - expect(response).to have_http_status(:success) - end - - it 'updates the bookmarked attribute' do - expect(user.account.bookmarked?(status)).to be false - end - - it 'returns json with updated attributes' do - hash_body = body_as_json - - expect(hash_body[:id]).to eq status.id.to_s - expect(hash_body[:bookmarked]).to be false - end - end - - context 'with public status when blocked by its author' do - let(:status) { Fabricate(:status) } - - before do - Bookmark.find_or_create_by!(account: user.account, status: status) - status.account.block!(user.account) - post :destroy, params: { status_id: status.id } - end - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'updates the bookmarked attribute' do - expect(user.account.bookmarked?(status)).to be false - end - - it 'returns json with updated attributes' do - hash_body = body_as_json - - expect(hash_body[:id]).to eq status.id.to_s - expect(hash_body[:bookmarked]).to be false - end - end - - context 'with private status that was not bookmarked' do - let(:status) { Fabricate(:status, visibility: :private) } - - before do - post :destroy, params: { status_id: status.id } - end - - it 'returns http not found' do - expect(response).to have_http_status(404) - end - end - end - end -end diff --git a/spec/requests/api/v1/statuses/bookmarks_spec.rb b/spec/requests/api/v1/statuses/bookmarks_spec.rb new file mode 100644 index 0000000000..d3007740a5 --- /dev/null +++ b/spec/requests/api/v1/statuses/bookmarks_spec.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Bookmarks' do + let(:user) { Fabricate(:user) } + let(:scopes) { 'write:bookmarks' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'POST /api/v1/statuses/:status_id/bookmark' do + subject do + post "/api/v1/statuses/#{status.id}/bookmark", headers: headers + end + + let(:status) { Fabricate(:status) } + + it_behaves_like 'forbidden for wrong scope', 'read' + + context 'with public status' do + it 'bookmarks the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.bookmarked?(status)).to be true + end + + it 'returns json with updated attributes' do + subject + + expect(body_as_json).to match( + a_hash_including(id: status.id.to_s, bookmarked: true) + ) + end + end + + context 'with private status of not-followed account' do + let(:status) { Fabricate(:status, visibility: :private) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + + context 'with private status of followed account' do + let(:status) { Fabricate(:status, visibility: :private) } + + before do + user.account.follow!(status.account) + end + + it 'bookmarks the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.bookmarked?(status)).to be true + end + end + + context 'when the status does not exist' do + it 'returns http not found' do + post '/api/v1/statuses/-1/bookmark', headers: headers + + expect(response).to have_http_status(404) + end + end + + context 'without an authorization header' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + end + + describe 'POST /api/v1/statuses/:status_id/unbookmark' do + subject do + post "/api/v1/statuses/#{status.id}/unbookmark", headers: headers + end + + let(:status) { Fabricate(:status) } + + it_behaves_like 'forbidden for wrong scope', 'read' + + context 'with public status' do + context 'when the status was previously bookmarked' do + before do + Bookmark.find_or_create_by!(account: user.account, status: status) + end + + it 'unbookmarks the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.bookmarked?(status)).to be false + end + + it 'returns json with updated attributes' do + subject + + expect(body_as_json).to match( + a_hash_including(id: status.id.to_s, bookmarked: false) + ) + end + end + + context 'when the requesting user was blocked by the status author' do + let(:status) { Fabricate(:status) } + + before do + Bookmark.find_or_create_by!(account: user.account, status: status) + status.account.block!(user.account) + end + + it 'unbookmarks the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.bookmarked?(status)).to be false + end + + it 'returns json with updated attributes' do + subject + + expect(body_as_json).to match( + a_hash_including(id: status.id.to_s, bookmarked: false) + ) + end + end + + context 'when the status is not bookmarked' do + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + end + end + + context 'with private status that was not bookmarked' do + let(:status) { Fabricate(:status, visibility: :private) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end +end From 19208aa4226cf257aedc20dfae5971f1076b1f24 Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Mon, 17 Jul 2023 11:53:57 -0300 Subject: [PATCH 14/18] Migrate to request specs in `/api/v1/statuses/:status_id/favourite` (#25626) --- .../v1/statuses/favourites_controller_spec.rb | 123 --------------- .../api/v1/statuses/favourites_spec.rb | 143 ++++++++++++++++++ 2 files changed, 143 insertions(+), 123 deletions(-) delete mode 100644 spec/controllers/api/v1/statuses/favourites_controller_spec.rb create mode 100644 spec/requests/api/v1/statuses/favourites_spec.rb diff --git a/spec/controllers/api/v1/statuses/favourites_controller_spec.rb b/spec/controllers/api/v1/statuses/favourites_controller_spec.rb deleted file mode 100644 index 609957e3ef..0000000000 --- a/spec/controllers/api/v1/statuses/favourites_controller_spec.rb +++ /dev/null @@ -1,123 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe Api::V1::Statuses::FavouritesController 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, scopes: 'write:favourites', application: app) } - - context 'with an oauth token' do - before do - allow(controller).to receive(:doorkeeper_token) { token } - end - - describe 'POST #create' do - let(:status) { Fabricate(:status, account: user.account) } - - before do - post :create, params: { status_id: status.id } - end - - context 'with public status' do - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'updates the favourites count' do - expect(status.favourites.count).to eq 1 - end - - it 'updates the favourited attribute' do - expect(user.account.favourited?(status)).to be true - end - - it 'returns json with updated attributes' do - hash_body = body_as_json - - expect(hash_body[:id]).to eq status.id.to_s - expect(hash_body[:favourites_count]).to eq 1 - expect(hash_body[:favourited]).to be true - end - end - - context 'with private status of not-followed account' do - let(:status) { Fabricate(:status, visibility: :private) } - - it 'returns http not found' do - expect(response).to have_http_status(404) - end - end - end - - describe 'POST #destroy' do - context 'with public status' do - let(:status) { Fabricate(:status, account: user.account) } - - before do - FavouriteService.new.call(user.account, status) - post :destroy, params: { status_id: status.id } - end - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'updates the favourites count' do - expect(status.favourites.count).to eq 0 - end - - it 'updates the favourited attribute' do - expect(user.account.favourited?(status)).to be false - end - - it 'returns json with updated attributes' do - hash_body = body_as_json - - expect(hash_body[:id]).to eq status.id.to_s - expect(hash_body[:favourites_count]).to eq 0 - expect(hash_body[:favourited]).to be false - end - end - - context 'with public status when blocked by its author' do - let(:status) { Fabricate(:status) } - - before do - FavouriteService.new.call(user.account, status) - status.account.block!(user.account) - post :destroy, params: { status_id: status.id } - end - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'updates the favourite attribute' do - expect(user.account.favourited?(status)).to be false - end - - it 'returns json with updated attributes' do - hash_body = body_as_json - - expect(hash_body[:id]).to eq status.id.to_s - expect(hash_body[:favourited]).to be false - end - end - - context 'with private status that was not favourited' do - let(:status) { Fabricate(:status, visibility: :private) } - - before do - post :destroy, params: { status_id: status.id } - end - - it 'returns http not found' do - expect(response).to have_http_status(404) - end - end - end - end -end diff --git a/spec/requests/api/v1/statuses/favourites_spec.rb b/spec/requests/api/v1/statuses/favourites_spec.rb new file mode 100644 index 0000000000..021b8806ed --- /dev/null +++ b/spec/requests/api/v1/statuses/favourites_spec.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Favourites' do + let(:user) { Fabricate(:user) } + let(:scopes) { 'write:favourites' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'POST /api/v1/statuses/:status_id/favourite' do + subject do + post "/api/v1/statuses/#{status.id}/favourite", headers: headers + end + + let(:status) { Fabricate(:status) } + + it_behaves_like 'forbidden for wrong scope', 'read read:favourites' + + context 'with public status' do + it 'favourites the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.favourited?(status)).to be true + end + + it 'returns json with updated attributes' do + subject + + expect(body_as_json).to match( + a_hash_including(id: status.id.to_s, favourites_count: 1, favourited: true) + ) + end + end + + context 'with private status of not-followed account' do + let(:status) { Fabricate(:status, visibility: :private) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + + context 'with private status of followed account' do + let(:status) { Fabricate(:status, visibility: :private) } + + before do + user.account.follow!(status.account) + end + + it 'favourites the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.favourited?(status)).to be true + end + end + + context 'without an authorization header' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + end + + describe 'POST /api/v1/statuses/:status_id/unfavourite' do + subject do + post "/api/v1/statuses/#{status.id}/unfavourite", headers: headers + end + + let(:status) { Fabricate(:status) } + + it_behaves_like 'forbidden for wrong scope', 'read read:favourites' + + context 'with public status' do + before do + FavouriteService.new.call(user.account, status) + end + + it 'unfavourites the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.favourited?(status)).to be false + end + + it 'returns json with updated attributes' do + subject + + expect(body_as_json).to match( + a_hash_including(id: status.id.to_s, favourites_count: 0, favourited: false) + ) + end + end + + context 'when the requesting user was blocked by the status author' do + before do + FavouriteService.new.call(user.account, status) + status.account.block!(user.account) + end + + it 'unfavourites the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.favourited?(status)).to be false + end + + it 'returns json with updated attributes' do + subject + + expect(body_as_json).to match( + a_hash_including(id: status.id.to_s, favourites_count: 0, favourited: false) + ) + end + end + + context 'when status is not favourited' do + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + end + + context 'with private status that was not favourited' do + let(:status) { Fabricate(:status, visibility: :private) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end +end From d0f00206dc115cb3a21281b532c59a166c21ce71 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 17 Jul 2023 10:57:18 -0400 Subject: [PATCH 15/18] Fix haml-lint Rubocop `Style/StringLiterals` cop (#25948) --- .haml-lint_todo.yml | 4 ++-- app/views/settings/imports/show.html.haml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index e1314742a6..f9debfd129 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -1,13 +1,13 @@ # This configuration was generated by # `haml-lint --auto-gen-config` -# on 2023-07-17 09:15:24 -0400 using Haml-Lint version 0.48.0. +# on 2023-07-17 09:59:21 -0400 using Haml-Lint version 0.48.0. # The point is for the user to remove these configuration records # one by one as the lints are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of Haml-Lint, may require this file to be generated again. linters: - # Offense count: 70 + # Offense count: 69 RuboCop: enabled: false diff --git a/app/views/settings/imports/show.html.haml b/app/views/settings/imports/show.html.haml index 893c5c8d29..4d50049d3d 100644 --- a/app/views/settings/imports/show.html.haml +++ b/app/views/settings/imports/show.html.haml @@ -2,7 +2,7 @@ = t("imports.titles.#{@bulk_import.type}") - if @bulk_import.likely_mismatched? - .flash-message.warning= t("imports.mismatched_types_warning") + .flash-message.warning= t('imports.mismatched_types_warning') - if @bulk_import.overwrite? %p.hint= t("imports.overwrite_preambles.#{@bulk_import.type}_html", filename: @bulk_import.original_filename, total_items: @bulk_import.total_items) From a442a1d1c69e5d477ca1c05f0bc5fc0f6894b223 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 17 Jul 2023 17:32:46 +0200 Subject: [PATCH 16/18] =?UTF-8?q?Fix=20=E2=80=9CBack=E2=80=9D=20button=20s?= =?UTF-8?q?ometimes=20redirecting=20out=20of=20Mastodon=20(#25281)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/column_back_button.jsx | 4 +-- .../mastodon/components/column_header.jsx | 11 ++++--- app/javascript/mastodon/components/router.tsx | 31 ++++++++++++++++--- app/javascript/mastodon/features/ui/index.jsx | 8 +++-- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/app/javascript/mastodon/components/column_back_button.jsx b/app/javascript/mastodon/components/column_back_button.jsx index 38ffa607a9..74a03b093a 100644 --- a/app/javascript/mastodon/components/column_back_button.jsx +++ b/app/javascript/mastodon/components/column_back_button.jsx @@ -23,9 +23,7 @@ export default class ColumnBackButton extends PureComponent { if (onClick) { onClick(); - // Check if there is a previous page in the app to go back to per https://stackoverflow.com/a/70532858/9703201 - // When upgrading to V6, check `location.key !== 'default'` instead per https://github.com/remix-run/history/blob/main/docs/api-reference.md#location - } else if (router.route.location.key) { + } else if (router.history.location?.state?.fromMastodon) { router.history.goBack(); } else { router.history.push('/'); diff --git a/app/javascript/mastodon/components/column_header.jsx b/app/javascript/mastodon/components/column_header.jsx index 89eade262e..9d29bbae03 100644 --- a/app/javascript/mastodon/components/column_header.jsx +++ b/app/javascript/mastodon/components/column_header.jsx @@ -63,10 +63,12 @@ class ColumnHeader extends PureComponent { }; handleBackClick = () => { - if (window.history && window.history.state) { - this.context.router.history.goBack(); + const { router } = this.context; + + if (router.history.location?.state?.fromMastodon) { + router.history.goBack(); } else { - this.context.router.history.push('/'); + router.history.push('/'); } }; @@ -83,6 +85,7 @@ class ColumnHeader extends PureComponent { }; render () { + const { router } = this.context; const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props; const { collapsed, animating } = this.state; @@ -126,7 +129,7 @@ class ColumnHeader extends PureComponent { pinButton = ; } - if (!pinned && (multiColumn || showBackButton)) { + if (!pinned && ((multiColumn && router.history.location?.state?.fromMastodon) || showBackButton)) { backButton = ( ; } - if (!pinned && (multiColumn || showBackButton)) { + if (!pinned && ((multiColumn && router.history.location?.state?.fromMastodon) || showBackButton)) { backButton = (