From ea734de4d8d4ae8e89e5068cb7073004cf39d33e Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 1 Aug 2025 17:12:31 -0400 Subject: [PATCH] Add `StatusDescriptionPresenter` to summarize status detail --- app/helpers/statuses_helper.rb | 41 +-------- .../status_description_presenter.rb | 89 +++++++++++++++++++ spec/helpers/statuses_helper_spec.rb | 33 ------- .../status_description_presenter_spec.rb | 46 ++++++++++ 4 files changed, 136 insertions(+), 73 deletions(-) create mode 100644 app/presenters/status_description_presenter.rb create mode 100644 spec/presenters/status_description_presenter_spec.rb diff --git a/app/helpers/statuses_helper.rb b/app/helpers/statuses_helper.rb index 16b9d3fb531..a14e6c6ab8c 100644 --- a/app/helpers/statuses_helper.rb +++ b/app/helpers/statuses_helper.rb @@ -14,47 +14,8 @@ module StatusesHelper end end - def media_summary(status) - attachments = { image: 0, video: 0, audio: 0 } - - status.ordered_media_attachments.each do |media| - if media.video? - attachments[:video] += 1 - elsif media.audio? - attachments[:audio] += 1 - else - attachments[:image] += 1 - end - end - - text = attachments.to_a.reject { |_, value| value.zero? }.map { |key, value| I18n.t("statuses.attached.#{key}", count: value) }.join(' · ') - - return if text.blank? - - I18n.t('statuses.attached.description', attached: text) - end - - def status_text_summary(status) - return if status.spoiler_text.blank? - - I18n.t('statuses.content_warning', warning: status.spoiler_text) - end - - def poll_summary(status) - return unless status.preloadable_poll - - status.preloadable_poll.options.map { |o| "[ ] #{o}" }.join("\n") - end - def status_description(status) - components = [[media_summary(status), status_text_summary(status)].compact_blank.join(' · ')] - - if status.spoiler_text.blank? - components << status.text - components << poll_summary(status) - end - - components.compact_blank.join("\n\n") + StatusDescriptionPresenter.new(status).description end def visibility_icon(status) diff --git a/app/presenters/status_description_presenter.rb b/app/presenters/status_description_presenter.rb new file mode 100644 index 00000000000..3af9424af4f --- /dev/null +++ b/app/presenters/status_description_presenter.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +class StatusDescriptionPresenter + JOIN = ' · ' + + attr_reader :status + + def initialize(status) + @status = status + end + + def description + relevant_components + .compact_blank + .join("\n\n") + end + + private + + def relevant_components + [default_description].tap do |components| + unless status.spoiler_text? + components << status.text + components << poll_summary + end + end + end + + def default_description + [media_summary, spoiler_warning] + .compact_blank + .join(JOIN) + end + + def media_summary + return if media_attachment_text.blank? + + I18n.t('statuses.attached.description', attached: media_attachment_text) + end + + def media_attachment_text + media_attachment_counts + .reject { |_, value| value.zero? } + .map { |key, value| I18n.t("statuses.attached.#{key}", count: value) } + .join(JOIN) + end + + def media_attachment_counts + media_initial_counts.tap do |attachments| + status.ordered_media_attachments.each do |media| + attachments[media_type(media)] += 1 + end + end + end + + def media_initial_counts + %i(image video audio).index_with(0) + end + + def media_type(media) + if media.video? + :video + elsif media.audio? + :audio + else + :image + end + end + + def spoiler_warning + return unless status.spoiler_text? + + I18n.t('statuses.content_warning', warning: status.spoiler_text) + end + + def poll_summary + return unless status.preloadable_poll + + status + .preloadable_poll + .options + .map { |option| checkbox(option) } + .join("\n") + end + + def checkbox(option) + ['[ ]', option].join(' ') + end +end diff --git a/spec/helpers/statuses_helper_spec.rb b/spec/helpers/statuses_helper_spec.rb index 07ad72eda95..afd99f2d909 100644 --- a/spec/helpers/statuses_helper_spec.rb +++ b/spec/helpers/statuses_helper_spec.rb @@ -3,39 +3,6 @@ require 'rails_helper' RSpec.describe StatusesHelper do - describe 'status_text_summary' do - context 'with blank text' do - let(:status) { Status.new(spoiler_text: '') } - - it 'returns immediately with nil' do - result = helper.status_text_summary(status) - expect(result).to be_nil - end - end - - context 'with present text' do - let(:status) { Status.new(spoiler_text: 'SPOILERS!!!') } - - it 'returns the content warning' do - result = helper.status_text_summary(status) - expect(result).to eq(I18n.t('statuses.content_warning', warning: 'SPOILERS!!!')) - end - end - end - - describe '#media_summary' do - it 'describes the media on a status' do - status = Fabricate :status - Fabricate :media_attachment, status: status, type: :video - Fabricate :media_attachment, status: status, type: :audio - Fabricate :media_attachment, status: status, type: :image - - result = helper.media_summary(status) - - expect(result).to eq('Attached: 1 image · 1 video · 1 audio') - end - end - describe 'visibility_icon' do context 'with a status that is public' do let(:status) { Status.new(visibility: 'public') } diff --git a/spec/presenters/status_description_presenter_spec.rb b/spec/presenters/status_description_presenter_spec.rb new file mode 100644 index 00000000000..7bad27203ba --- /dev/null +++ b/spec/presenters/status_description_presenter_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe StatusDescriptionPresenter do + describe '#description' do + subject { described_class.new(status).description } + + context 'when status has blank text' do + let(:status) { Fabricate.build :status, text: '' } + + it { is_expected.to be_blank } + end + + context 'when status has text' do + let(:status) { Fabricate.build :status, text: 'Hello there' } + + it { is_expected.to eq('Hello there') } + end + + context 'when status has spoilers' do + let(:status) { Fabricate.build :status, text: 'Hello there', spoiler_text: 'SPOILERS!!!' } + + it { is_expected.to eq(I18n.t('statuses.content_warning', warning: 'SPOILERS!!!')) } + end + + context 'when status has media attachments' do + let(:status) { Fabricate.build :status, text: 'Hello there' } + + before do + Fabricate :media_attachment, status:, type: :video + Fabricate.times 2, :media_attachment, status:, type: :audio + Fabricate :media_attachment, status:, type: :image + end + + it { is_expected.to eq("Attached: 1 image · 1 video · 2 audio\n\nHello there") } + end + + context 'when status has a poll' do + let(:preloadable_poll) { Fabricate.build(:poll, options: %w(One Two)) } + let(:status) { Fabricate.build :status, text: 'Hello there', preloadable_poll: } + + it { is_expected.to eq("Hello there\n\n[ ] One\n[ ] Two") } + end + end +end