0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-07 13:19:19 +08:00
discourse/spec/serializers/category_serializer_spec.rb
Kris ffcd045019
FIX: only show first paragraph of localized category descriptions (#41511)
Reported here:
https://meta.discourse.org/t/category-description-truncates-to-first-paragraph-for-primary-base-language-but-not-for-localizations-panel-languages/406801

In a number of places we only show the first paragraph of a category
description by default, but when someone manually adds a category
description translation we show the full content. This results in
translated content being much longer in some places where only a single
paragraph is expected (/categories pages, category headers, etc).

This change cooks the translated description and applies the same single
paragraph rule where relevant.

`Category.first_paragraph_description` has been extracted to be used in
both the default and translated descriptions so the behavior remains
consistent.
2026-07-10 16:59:40 -04:00

414 lines
13 KiB
Ruby
Vendored

# frozen_string_literal: true
RSpec.describe CategorySerializer do
fab!(:user)
fab!(:admin)
fab!(:group)
fab!(:category)
fab!(:category_moderation_group) { Fabricate(:category_moderation_group, category:, group:) }
it "includes the reviewable by group name if enabled" do
SiteSetting.enable_category_group_moderation = true
json = described_class.new(category, scope: Guardian.new, root: false).as_json
expect(json[:moderating_group_ids]).to eq([group.id])
end
it "doesn't include the reviewable by group name if disabled" do
SiteSetting.enable_category_group_moderation = false
json = described_class.new(category, scope: Guardian.new, root: false).as_json
expect(json[:moderating_group_ids]).to be_blank
end
it "includes custom fields" do
json = described_class.new(category, scope: Guardian.new, root: false).as_json
expect(json[:custom_fields]).to be_empty
category.custom_fields["enable_marketplace"] = true
category.save_custom_fields
json = described_class.new(category, scope: Guardian.new, root: false).as_json
expect(json[:custom_fields]).to be_present
end
it "does not include the default notification level when there is no user" do
json = described_class.new(category, scope: Guardian.new, root: false).as_json
expect(json.key?(:notification_level)).to eq(false)
end
describe "user notification level" do
it "includes the user's notification level" do
CategoryUser.set_notification_level_for_category(
user,
NotificationLevels.all[:watching],
category.id,
)
json = described_class.new(category, scope: Guardian.new(user), root: false).as_json
expect(json[:notification_level]).to eq(NotificationLevels.all[:watching])
end
end
describe "#group_permissions" do
fab!(:private_group) do
Fabricate(:group, visibility_level: Group.visibility_levels[:staff], name: "bbb")
end
fab!(:user_group) do
Fabricate(:group, visibility_level: Group.visibility_levels[:members], name: "ccc").tap do |g|
g.add(user)
end
end
before do
group.update!(name: "aaa")
category.set_permissions(
:everyone => :readonly,
group.name => :readonly,
user_group.name => :full,
private_group.name => :full,
)
category.save!
end
it "does not include the attribute for an anon user" do
json = described_class.new(category, scope: Guardian.new, root: false).as_json
expect(json[:group_permissions]).to eq(nil)
end
it "does not include the attribute for a regular user" do
json = described_class.new(category, scope: Guardian.new(user), root: false).as_json
expect(json[:group_permissions]).to eq(nil)
end
it "returns the right category group permissions for a user that can edit the category" do
SiteSetting.moderators_manage_categories = true
user.update!(moderator: true)
json = described_class.new(category, scope: Guardian.new(user), root: false).as_json
expect(json[:group_permissions]).to eq(
[
{
permission_type: CategoryGroup.permission_types[:readonly],
group_name: group.name,
group_id: group.id,
},
{
permission_type: CategoryGroup.permission_types[:full],
group_name: private_group.name,
group_id: private_group.id,
},
{
permission_type: CategoryGroup.permission_types[:full],
group_name: user_group.name,
group_id: user_group.id,
},
{
permission_type: CategoryGroup.permission_types[:readonly],
group_name: "everyone",
group_id: Group::AUTO_GROUPS[:everyone],
},
],
)
end
end
describe "available groups" do
it "not included for a regular user" do
json = described_class.new(category, scope: Guardian.new(user), root: false).as_json
expect(json[:available_groups]).to eq(nil)
end
it "included for an admin" do
json = described_class.new(category, scope: Guardian.new(admin), root: false).as_json
expect(json[:available_groups]).to eq(Group.order(:name).pluck(:name) - ["everyone"])
end
end
describe "name and description" do
fab!(:category_with_localization) do
Fabricate(:category, name: "Original Name", description: "Original Description", locale: "en")
end
before do
CategoryLocalization.create!(
category: category_with_localization,
locale: "ja",
name: "日本語名",
description: "<p>最初の段落</p><p>二番目の段落</p>",
)
end
it "returns untranslated name and description for CategorySerializer" do
json =
described_class.new(
category_with_localization,
scope: Guardian.new(user),
root: false,
).as_json
expect(json[:name]).to eq("Original Name")
expect(json[:description]).to eq("Original Description")
end
it "returns only the first paragraph of the translated description for SiteCategorySerializer when enabled" do
SiteSetting.content_localization_enabled = true
user.update!(locale: "ja")
I18n.with_locale("ja") do
json =
SiteCategorySerializer.new(
category_with_localization,
scope: Guardian.new(user),
root: false,
).as_json
expect(json[:name]).to eq("日本語名")
expect(json[:description]).to eq("最初の段落")
expect(json[:description_text]).to eq("最初の段落")
expect(json[:description_excerpt]).to eq("最初の段落")
end
end
it "keeps only the first paragraph of a plain text translated description" do
SiteSetting.content_localization_enabled = true
category_with_localization
.category_localizations
.find_by(locale: "ja")
.update!(description: "最初の段落\n\n二番目の段落")
user.update!(locale: "ja")
I18n.with_locale("ja") do
json =
SiteCategorySerializer.new(
category_with_localization,
scope: Guardian.new(user),
root: false,
).as_json
expect(json[:description]).to eq("最初の段落")
expect(json[:description_text]).to eq("最初の段落")
expect(json[:description_excerpt]).to eq("最初の段落")
end
end
it "falls back to the untranslated description when the translated description has no paragraph" do
SiteSetting.content_localization_enabled = true
category_with_localization
.category_localizations
.find_by(locale: "ja")
.update!(description: "- 一つ\n- 二つ")
user.update!(locale: "ja")
I18n.with_locale("ja") do
json =
SiteCategorySerializer.new(
category_with_localization,
scope: Guardian.new(user),
root: false,
).as_json
expect(json[:name]).to eq("日本語名")
expect(json[:description]).to eq("Original Description")
expect(json[:description_text]).to eq(category_with_localization.description_text)
expect(json[:description_excerpt]).to eq(category_with_localization.description_excerpt)
end
end
it "returns untranslated attributes for SiteCategorySerializer when disabled" do
SiteSetting.content_localization_enabled = false
user.update!(locale: "ja")
I18n.with_locale("ja") do
json =
SiteCategorySerializer.new(
category_with_localization,
scope: Guardian.new(user),
root: false,
).as_json
expect(json[:name]).to eq("Original Name")
expect(json[:description]).to eq("Original Description")
expect(json[:description_text]).to eq(category_with_localization.description_text)
expect(json[:description_excerpt]).to eq(category_with_localization.description_excerpt)
end
end
it "returns untranslated name and description for BasicCategorySerializer" do
json =
BasicCategorySerializer.new(
category_with_localization,
scope: Guardian.new(user),
root: false,
).as_json
expect(json[:name]).to eq("Original Name")
expect(json[:description]).to eq("Original Description")
end
end
describe "#topic_posting_review_group_ids" do
it "returns group ids when groups exist" do
category.update!(
topic_posting_review_mode: :everyone_except,
topic_posting_review_group_ids: [group.id],
)
json = described_class.new(category, scope: Guardian.new, root: false).as_json
expect(json[:topic_posting_review_group_ids]).to eq([group.id])
end
it "returns empty array when no groups are associated" do
json = described_class.new(category, scope: Guardian.new, root: false).as_json
expect(json[:topic_posting_review_group_ids]).to eq([])
end
end
describe "#reply_posting_review_group_ids" do
it "returns group ids when groups exist" do
category.update!(
reply_posting_review_mode: :no_one_except,
reply_posting_review_group_ids: [group.id],
)
json = described_class.new(category, scope: Guardian.new, root: false).as_json
expect(json[:reply_posting_review_group_ids]).to eq([group.id])
end
it "returns empty array when no groups are associated" do
json = described_class.new(category, scope: Guardian.new, root: false).as_json
expect(json[:reply_posting_review_group_ids]).to eq([])
end
end
describe "#require_topic_approval" do
it "returns false when topic approval is not required" do
category.require_topic_approval = false
category.save!
json = described_class.new(category, scope: Guardian.new(admin), root: false).as_json
expect(json[:category_setting][:require_topic_approval]).to eq(false)
end
it "returns true when topic approval is required" do
category.require_topic_approval = true
category.save!
json = described_class.new(category, scope: Guardian.new(admin), root: false).as_json
expect(json[:category_setting][:require_topic_approval]).to eq(true)
end
end
describe "#require_reply_approval" do
it "returns false when reply approval is not required" do
category.require_reply_approval = false
category.save!
json = described_class.new(category, scope: Guardian.new(admin), root: false).as_json
expect(json[:category_setting][:require_reply_approval]).to eq(false)
end
it "returns true when reply approval is required" do
category.require_reply_approval = true
category.save!
json = described_class.new(category, scope: Guardian.new(admin), root: false).as_json
expect(json[:category_setting][:require_reply_approval]).to eq(true)
end
end
describe "#category_types" do
it "returns the category types" do
json = described_class.new(category, scope: admin.guardian, root: false).as_json
expect(json[:category_types]).to eq(
{ discussion: Categories::TypeRegistry.all[:discussion].metadata },
)
end
end
describe "#available_category_types" do
class MockCategoryType < ::Categories::Types::Base
type_id :mock_type
class << self
def category_matches?(category)
true
end
def find_matches
Category.none
end
def visible?
false
end
end
end
before { Categories::TypeRegistry.register(MockCategoryType) }
after { Categories::TypeRegistry.reset! }
it "returns the available visible category types" do
json = described_class.new(category, scope: admin.guardian, root: false).as_json
expect(json[:available_category_types]).to eq(
[Categories::TypeRegistry.all[:discussion].metadata],
)
end
end
describe "#category_type_settings" do
let(:type_a) do
Class.new(Categories::Types::Base) do
type_id :test_type_a
def self.category_matches?(_category)
true
end
def self.read_category_settings(_category)
{ foo: "from_a" }
end
end
end
let(:type_b) do
Class.new(Categories::Types::Base) do
type_id :test_type_b
def self.category_matches?(_category)
true
end
def self.read_category_settings(_category)
{ bar: "from_b" }
end
end
end
let(:non_matching_type) do
Class.new(Categories::Types::Base) do
type_id :test_type_off
def self.category_matches?(_category)
false
end
def self.read_category_settings(_category)
{ baz: "should_not_appear" }
end
end
end
before do
Categories::TypeRegistry.stubs(:all).returns(a: type_a, b: type_b, off: non_matching_type)
end
it "merges values from each matching type and skips non-matching types" do
json = described_class.new(category, scope: admin.guardian, root: false).as_json
expect(json[:category_type_settings]).to eq(foo: "from_a", bar: "from_b")
end
end
end