diff --git a/app/assets/javascripts/admin/addon/controllers/admin-badges/show.js b/app/assets/javascripts/admin/addon/controllers/admin-badges/show.js
index 76cd13c24a3..dc7d5735b26 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-badges/show.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-badges/show.js
@@ -27,6 +27,7 @@ const FORM_FIELDS = [
"badge_grouping_id",
"trigger",
"badge_type_id",
+ "show_in_post_header",
];
export default class AdminBadgesShowController extends Controller {
@@ -40,8 +41,6 @@ export default class AdminBadgesShowController extends Controller {
@tracked model;
@tracked previewLoading = false;
@tracked selectedGraphicType = null;
- @tracked userBadges;
- @tracked userBadgesAll;
@cached
get formData() {
@@ -80,6 +79,17 @@ export default class AdminBadgesShowController extends Controller {
return this.model.system;
}
+ @action
+ postHeaderDescription(data) {
+ return this.disableBadgeOnPosts(data) && !data.system;
+ }
+
+ @action
+ disableBadgeOnPosts(data) {
+ const { listable, show_posts } = data;
+ return !listable || !show_posts;
+ }
+
setup() {
// this is needed because the model doesnt have default values
// Using `set` here isn't ideal, but we don't know that tracking is set up on the model yet.
diff --git a/app/assets/javascripts/admin/addon/templates/admin-badges/show.hbs b/app/assets/javascripts/admin/addon/templates/admin-badges/show.hbs
index 67735c6bf64..1548b18feae 100644
--- a/app/assets/javascripts/admin/addon/templates/admin-badges/show.hbs
+++ b/app/assets/javascripts/admin/addon/templates/admin-badges/show.hbs
@@ -245,7 +245,7 @@
-
+
+
+
+
+
+
+ {{#if (this.postHeaderDescription data)}}
+ {{i18n "admin.badges.show_in_post_header_disabled"}}
+ {{/if}}
+
+
diff --git a/app/assets/javascripts/discourse/app/components/badge-button.gjs b/app/assets/javascripts/discourse/app/components/badge-button.gjs
index 2abddb36646..c5d22a5bca5 100644
--- a/app/assets/javascripts/discourse/app/components/badge-button.gjs
+++ b/app/assets/javascripts/discourse/app/components/badge-button.gjs
@@ -10,6 +10,10 @@ export default class BadgeButton extends Component {
}
}
+ get showName() {
+ return this.args.showName ?? true;
+ }
+
{{iconOrImage @badge}}
- {{@badge.name}}
+ {{#if this.showName}}
+ {{@badge.name}}
+ {{/if}}
{{yield}}
diff --git a/app/assets/javascripts/discourse/app/components/user-badge.gjs b/app/assets/javascripts/discourse/app/components/user-badge.gjs
index a16286d6faf..0c2f3b0056d 100644
--- a/app/assets/javascripts/discourse/app/components/user-badge.gjs
+++ b/app/assets/javascripts/discourse/app/components/user-badge.gjs
@@ -15,7 +15,7 @@ export default class UserBadge extends Component {
-
+
{{#if this.showGrantCount}}
(×{{@count}})
{{/if}}
diff --git a/app/assets/javascripts/discourse/app/lib/transform-post.js b/app/assets/javascripts/discourse/app/lib/transform-post.js
index 1347f2b6d7c..82a2b3e3d94 100644
--- a/app/assets/javascripts/discourse/app/lib/transform-post.js
+++ b/app/assets/javascripts/discourse/app/lib/transform-post.js
@@ -1,5 +1,6 @@
import { isEmpty } from "@ember/utils";
import { userPath } from "discourse/lib/url";
+import Badge from "discourse/models/badge";
import getURL from "discourse-common/lib/get-url";
import { i18n } from "discourse-i18n";
@@ -37,6 +38,9 @@ export function transformBasicPost(post) {
user_id: post.user_id,
usernameUrl: userPath(post.username),
username: post.username,
+ badgesGranted: post.badges_granted?.map(
+ (badge) => Badge.createFromJson(badge)[0]
+ ),
avatar_template: post.avatar_template,
bookmarked: post.bookmarked,
bookmarkReminderAt: post.bookmark_reminder_at,
diff --git a/app/assets/javascripts/discourse/app/widgets/poster-name.js b/app/assets/javascripts/discourse/app/widgets/poster-name.js
index b3ed5346963..b1a35456974 100644
--- a/app/assets/javascripts/discourse/app/widgets/poster-name.js
+++ b/app/assets/javascripts/discourse/app/widgets/poster-name.js
@@ -1,6 +1,8 @@
+import { hbs } from "ember-cli-htmlbars";
import { h } from "virtual-dom";
import { prioritizeNameInUx } from "discourse/lib/settings";
import { formatUsername } from "discourse/lib/utilities";
+import RenderGlimmer from "discourse/widgets/render-glimmer";
import { applyDecorators, createWidget } from "discourse/widgets/widget";
import getURL from "discourse-common/lib/get-url";
import { iconNode } from "discourse-common/lib/icon-library";
@@ -139,6 +141,29 @@ export default createWidget("poster-name", {
}
}
+ if (attrs.badgesGranted) {
+ const badges = [];
+ attrs.badgesGranted.forEach((badge) => {
+ // Alter the badge description to show that the badge was granted for this post.
+ badge.description = i18n("post.badge_granted_tooltip", {
+ username: attrs.username,
+ badge_name: badge.name,
+ });
+
+ const badgeIcon = new RenderGlimmer(
+ this,
+ `span.user-badge-button-${badge.slug}`,
+ hbs``,
+ {
+ badge,
+ user: attrs.user,
+ }
+ );
+ badges.push(badgeIcon);
+ });
+ nameContents.push(h("span.user-badge-buttons", badges));
+ }
+
const afterNameContents =
applyDecorators(this, "after-name", attrs, this.state) || [];
diff --git a/app/assets/javascripts/discourse/tests/integration/components/badge-button-test.js b/app/assets/javascripts/discourse/tests/integration/components/badge-button-test.js
index 54a5a5b1c9b..a1aa8186803 100644
--- a/app/assets/javascripts/discourse/tests/integration/components/badge-button-test.js
+++ b/app/assets/javascripts/discourse/tests/integration/components/badge-button-test.js
@@ -77,4 +77,22 @@ module("Integration | Component | badge-button", function (hooks) {
assert.dom(".user-badge.foo").exists();
});
+
+ test("setting showName to false hides the name", async function (assert) {
+ this.set("badge", { name: "foo" });
+
+ await render(
+ hbs``
+ );
+
+ assert.dom(".badge-display-name").doesNotExist();
+ });
+
+ test("showName defaults to true", async function (assert) {
+ this.set("badge", { name: "foo" });
+
+ await render(hbs``);
+
+ assert.dom(".badge-display-name").exists();
+ });
});
diff --git a/app/assets/javascripts/discourse/tests/integration/components/widgets/poster-name-test.js b/app/assets/javascripts/discourse/tests/integration/components/widgets/poster-name-test.js
index 14ea46f737c..0aac7839563 100644
--- a/app/assets/javascripts/discourse/tests/integration/components/widgets/poster-name-test.js
+++ b/app/assets/javascripts/discourse/tests/integration/components/widgets/poster-name-test.js
@@ -1,6 +1,8 @@
import { render } from "@ember/test-helpers";
import { hbs } from "ember-cli-htmlbars";
import { module, test } from "qunit";
+import Badge from "discourse/models/badge";
+import User from "discourse/models/user";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
module("Integration | Component | Widget | poster-name", function (hooks) {
@@ -70,4 +72,38 @@ module("Integration | Component | Widget | poster-name", function (hooks) {
assert.dom(".second").doesNotExist();
});
+
+ test("renders badges that are passed in", async function (assert) {
+ this.set("args", {
+ username: "eviltrout",
+ usernameUrl: "/u/eviltrout",
+ user: User.create({
+ username: "eviltrout",
+ }),
+ badgesGranted: [
+ { id: 1, icon: "heart", slug: "badge1", name: "Badge One" },
+ { id: 2, icon: "target", slug: "badge2", name: "Badge Two" },
+ ].map((badge) => Badge.createFromJson({ badges: [badge] })[0]),
+ });
+
+ await render(
+ hbs``
+ );
+
+ // Check that the custom CSS classes are set
+ assert.dom("span.user-badge-button-badge1").exists();
+ assert.dom("span.user-badge-button-badge2").exists();
+
+ // Check that the custom titles are set
+ assert.dom("span.user-badge[title*='Badge One']").exists();
+ assert.dom("span.user-badge[title*='Badge Two']").exists();
+
+ // Check that the badges link to the correct badge page
+ assert
+ .dom("a.user-card-badge-link[href='/badges/1/badge1?username=eviltrout']")
+ .exists();
+ assert
+ .dom("a.user-card-badge-link[href='/badges/2/badge2?username=eviltrout']")
+ .exists();
+ });
});
diff --git a/app/assets/stylesheets/common/base/topic-post.scss b/app/assets/stylesheets/common/base/topic-post.scss
index c0b1387c2d0..d0331bbfdc4 100644
--- a/app/assets/stylesheets/common/base/topic-post.scss
+++ b/app/assets/stylesheets/common/base/topic-post.scss
@@ -259,6 +259,19 @@
margin-right: auto;
}
+ .user-badge-buttons {
+ margin-left: 15px;
+
+ a {
+ background: none;
+ }
+
+ .user-badge {
+ background: none;
+ border: none;
+ }
+ }
+
.post-infos {
display: flex;
flex: 0 0 auto;
diff --git a/app/models/badge.rb b/app/models/badge.rb
index 4ee2257d781..2a58c1242d3 100644
--- a/app/models/badge.rb
+++ b/app/models/badge.rb
@@ -344,27 +344,28 @@ end
#
# Table name: badges
#
-# id :integer not null, primary key
-# name :string not null
-# description :text
-# badge_type_id :integer not null
-# grant_count :integer default(0), not null
-# created_at :datetime not null
-# updated_at :datetime not null
-# allow_title :boolean default(FALSE), not null
-# multiple_grant :boolean default(FALSE), not null
-# icon :string default("fa-certificate")
-# listable :boolean default(TRUE)
-# target_posts :boolean default(FALSE)
-# query :text
-# enabled :boolean default(TRUE), not null
-# auto_revoke :boolean default(TRUE), not null
-# badge_grouping_id :integer default(5), not null
-# trigger :integer
-# show_posts :boolean default(FALSE), not null
-# system :boolean default(FALSE), not null
-# long_description :text
-# image_upload_id :integer
+# id :integer not null, primary key
+# name :string not null
+# description :text
+# badge_type_id :integer not null
+# grant_count :integer default(0), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# allow_title :boolean default(FALSE), not null
+# multiple_grant :boolean default(FALSE), not null
+# icon :string default("fa-certificate")
+# listable :boolean default(TRUE)
+# target_posts :boolean default(FALSE)
+# query :text
+# enabled :boolean default(TRUE), not null
+# auto_revoke :boolean default(TRUE), not null
+# badge_grouping_id :integer default(5), not null
+# trigger :integer
+# show_posts :boolean default(FALSE), not null
+# system :boolean default(FALSE), not null
+# show_in_post_header :boolean default(FALSE), not null
+# long_description :text
+# image_upload_id :integer
#
# Indexes
#
diff --git a/app/models/user_badge.rb b/app/models/user_badge.rb
index b75160363d0..1263a047708 100644
--- a/app/models/user_badge.rb
+++ b/app/models/user_badge.rb
@@ -35,6 +35,19 @@ class UserBadge < ActiveRecord::Base
scope :for_enabled_badges,
-> { where("user_badges.badge_id IN (SELECT id FROM badges WHERE enabled)") }
+ scope :by_post_and_user,
+ ->(posts) do
+ posts.reduce(UserBadge.none) do |scope, post|
+ scope.or(UserBadge.where(user_id: post.user_id, post_id: post.id))
+ end
+ end
+ scope :for_post_header_badges,
+ ->(posts) do
+ by_post_and_user(posts).where(
+ "user_badges.badge_id IN (SELECT id FROM badges WHERE show_posts AND enabled AND listable AND show_in_post_header)",
+ )
+ end
+
validates :badge_id, presence: true, uniqueness: { scope: :user_id }, if: :single_grant_badge?
validates :user_id, presence: true
diff --git a/app/serializers/badge_serializer.rb b/app/serializers/badge_serializer.rb
index 004f0193230..3e89dd16485 100644
--- a/app/serializers/badge_serializer.rb
+++ b/app/serializers/badge_serializer.rb
@@ -16,7 +16,8 @@ class BadgeSerializer < ApplicationSerializer
:long_description,
:slug,
:has_badge,
- :manually_grantable?
+ :manually_grantable?,
+ :show_in_post_header
has_one :badge_type
diff --git a/app/serializers/post_serializer.rb b/app/serializers/post_serializer.rb
index 4fe30f814bf..d98be8d6813 100644
--- a/app/serializers/post_serializer.rb
+++ b/app/serializers/post_serializer.rb
@@ -38,6 +38,7 @@ class PostSerializer < BasicPostSerializer
:flair_bg_color,
:flair_color,
:flair_group_id,
+ :badges_granted,
:version,
:can_edit,
:can_delete,
@@ -223,6 +224,18 @@ class PostSerializer < BasicPostSerializer
object.user&.flair_group_id
end
+ def badges_granted
+ return [] unless SiteSetting.enable_badges && SiteSetting.show_badges_in_post_header
+
+ if @topic_view
+ user_badges = @topic_view.post_user_badges[object.id] || []
+ else
+ user_badges = UserBadge.for_post_header_badges([object])
+ end
+
+ user_badges.map { |user_badge| BasicUserBadgeSerializer.new(user_badge, scope: scope).as_json }
+ end
+
def link_counts
return @single_post_link_counts if @single_post_link_counts.present?
diff --git a/app/serializers/web_hook_post_serializer.rb b/app/serializers/web_hook_post_serializer.rb
index 1095d5c46ce..f7d2066c6c4 100644
--- a/app/serializers/web_hook_post_serializer.rb
+++ b/app/serializers/web_hook_post_serializer.rb
@@ -30,6 +30,7 @@ class WebHookPostSerializer < PostSerializer
flair_color
notice
mentioned_users
+ badges_granted
].each { |attr| define_method("include_#{attr}?") { false } }
def topic_posts
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 368817a5fd9..10d6ee82b5d 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -3816,6 +3816,8 @@ en:
in_reply_to: "Load parent post"
view_all_posts: "View all posts"
+ badge_granted_tooltip: "%{username} earned the '%{badge_name}' badge for this post!"
+
errors:
create: "Sorry, there was an error creating your post. Please try again."
edit: "Sorry, there was an error editing your post. Please try again."
@@ -7195,9 +7197,13 @@ en:
no_user_badges: "%{name} has not been granted any badges."
no_badges: There are no badges that can be granted.
none_selected: "Select a badge to get started"
+ usage_heading: Usage
allow_title: Allow badge to be used as a title
multiple_grant: Can be granted multiple times
+ visibility_heading: Visibility
listable: Show badge on the public badges page
+ show_in_post_header: Show badge on the post it was granted for
+ show_in_post_header_disabled: Requires both 'Show badge on the public badges page' and 'Show post granting badge on badge page' to be enabled.
enabled: enabled
disabled: disabled
icon: Icon
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 1a0b62bcbc0..257842f6cac 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1942,6 +1942,7 @@ en:
email_token_valid_hours: "Forgot password / activate account tokens are valid for (n) hours."
enable_badges: "Enable the badge system, which is a form of gamification to reinforce positive user actions. See What are Badges? on Discourse Meta for more information."
+ show_badges_in_post_header: "When a user earns a badge for a particular post, display the badge in the post header."
max_favorite_badges: "Maximum number of badges that user can select"
whispers_allowed_groups: "Allow private communication within topics for members of specified groups."
hidden_post_visible_groups: "Allow members of these groups to view hidden posts. Staff users can always view hidden posts."
diff --git a/config/site_settings.yml b/config/site_settings.yml
index 656b74ec71c..757c3986380 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -342,6 +342,9 @@ basic:
enable_badges:
client: true
default: true
+ show_badges_in_post_header:
+ client: true
+ default: true
enable_badge_sql:
client: true
default: false
diff --git a/db/migrate/20241121000131_add_post_header_to_badge.rb b/db/migrate/20241121000131_add_post_header_to_badge.rb
new file mode 100644
index 00000000000..4b5fec8ec3d
--- /dev/null
+++ b/db/migrate/20241121000131_add_post_header_to_badge.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+class AddPostHeaderToBadge < ActiveRecord::Migration[7.1]
+ def change
+ add_column :badges, :show_in_post_header, :boolean, default: false, null: false
+ end
+end
diff --git a/lib/topic_view.rb b/lib/topic_view.rb
index f7e4745997d..d8882e219e1 100644
--- a/lib/topic_view.rb
+++ b/lib/topic_view.rb
@@ -205,6 +205,25 @@ class TopicView
{ users: user_badge_mapping, badges: indexed_badges }
end
+ def post_user_badges
+ return [] unless SiteSetting.enable_badges && SiteSetting.show_badges_in_post_header
+
+ @post_user_badges ||=
+ begin
+ UserBadge
+ .for_post_header_badges(@posts)
+ .reduce({}) do |hash, user_badge|
+ hash[user_badge.post_id] ||= []
+ hash[user_badge.post_id] << user_badge
+ hash
+ end
+ end
+
+ return [] unless @post_user_badges
+
+ @post_user_badges
+ end
+
def show_read_indicator?
return false if !@user || !topic.private_message?
diff --git a/spec/requests/api/schemas/json/badge_create_response.json b/spec/requests/api/schemas/json/badge_create_response.json
index 57408db5f25..fdb256e8369 100644
--- a/spec/requests/api/schemas/json/badge_create_response.json
+++ b/spec/requests/api/schemas/json/badge_create_response.json
@@ -106,6 +106,9 @@
},
"badge_type_id": {
"type": "integer"
+ },
+ "show_in_post_header": {
+ "type": "boolean"
}
},
"required": [
@@ -130,7 +133,8 @@
"auto_revoke",
"show_posts",
"badge_type_id",
- "image_upload_id"
+ "image_upload_id",
+ "show_in_post_header"
]
}
},
diff --git a/spec/requests/api/schemas/json/badge_list_response.json b/spec/requests/api/schemas/json/badge_list_response.json
index 3229e08c641..040ea612f53 100644
--- a/spec/requests/api/schemas/json/badge_list_response.json
+++ b/spec/requests/api/schemas/json/badge_list_response.json
@@ -91,6 +91,9 @@
},
"badge_type_id": {
"type": "integer"
+ },
+ "show_in_post_header": {
+ "type": "boolean"
}
},
"required": [
@@ -115,7 +118,8 @@
"auto_revoke",
"show_posts",
"badge_type_id",
- "image_upload_id"
+ "image_upload_id",
+ "show_in_post_header"
]
}
},
diff --git a/spec/requests/api/schemas/json/badge_update_response.json b/spec/requests/api/schemas/json/badge_update_response.json
index 57408db5f25..fdb256e8369 100644
--- a/spec/requests/api/schemas/json/badge_update_response.json
+++ b/spec/requests/api/schemas/json/badge_update_response.json
@@ -106,6 +106,9 @@
},
"badge_type_id": {
"type": "integer"
+ },
+ "show_in_post_header": {
+ "type": "boolean"
}
},
"required": [
@@ -130,7 +133,8 @@
"auto_revoke",
"show_posts",
"badge_type_id",
- "image_upload_id"
+ "image_upload_id",
+ "show_in_post_header"
]
}
},
diff --git a/spec/requests/api/schemas/json/post_update_response.json b/spec/requests/api/schemas/json/post_update_response.json
index da253ae720d..bb004f4ca06 100644
--- a/spec/requests/api/schemas/json/post_update_response.json
+++ b/spec/requests/api/schemas/json/post_update_response.json
@@ -98,6 +98,10 @@
"null"
]
},
+ "badges_granted": {
+ "type": "array",
+ "items": {}
+ },
"version": {
"type": "integer"
},
diff --git a/spec/requests/api/schemas/json/topic_create_response.json b/spec/requests/api/schemas/json/topic_create_response.json
index 6cfbaac4f37..5cb486c8508 100644
--- a/spec/requests/api/schemas/json/topic_create_response.json
+++ b/spec/requests/api/schemas/json/topic_create_response.json
@@ -109,6 +109,10 @@
"null"
]
},
+ "badges_granted": {
+ "type": "array",
+ "items": {}
+ },
"version": {
"type": "integer"
},
diff --git a/spec/serializers/post_serializer_spec.rb b/spec/serializers/post_serializer_spec.rb
index 73be5f6f1bc..49693328c81 100644
--- a/spec/serializers/post_serializer_spec.rb
+++ b/spec/serializers/post_serializer_spec.rb
@@ -438,6 +438,201 @@ RSpec.describe PostSerializer do
end
end
+ describe "#badges_granted" do
+ fab!(:user)
+ fab!(:user2) { Fabricate(:user) }
+ fab!(:post) { Fabricate(:post, user: user) }
+ fab!(:post2) { Fabricate(:post, user: user) }
+
+ # Create twp badges that have all required flags set to true
+ fab!(:badge1) do
+ Badge.create!(
+ name: "SomeBadge",
+ badge_type_id: BadgeType::Bronze,
+ listable: true,
+ show_posts: true,
+ show_in_post_header: true,
+ multiple_grant: true,
+ )
+ end
+ fab!(:ub1) do
+ UserBadge.create!(
+ badge_id: badge1.id,
+ user: user,
+ granted_by: Discourse.system_user,
+ granted_at: Time.now,
+ post_id: post.id,
+ )
+ end
+
+ fab!(:badge2) do
+ Badge.create!(
+ name: "SomeOtherBadge",
+ badge_type_id: BadgeType::Bronze,
+ listable: true,
+ show_posts: true,
+ show_in_post_header: true,
+ multiple_grant: true,
+ )
+ end
+ fab!(:ub2) do
+ UserBadge.create!(
+ badge_id: badge2.id,
+ user: user,
+ granted_by: Discourse.system_user,
+ granted_at: Time.now,
+ post_id: post.id,
+ )
+ end
+
+ # Create a badge that has the show_posts flag set to false
+ fab!(:badge3) do
+ Badge.create!(
+ name: "YetAnotherBadge",
+ badge_type_id: BadgeType::Bronze,
+ listable: true,
+ show_posts: false,
+ show_in_post_header: true,
+ )
+ end
+ fab!(:ub3) do
+ UserBadge.create!(
+ badge_id: badge3.id,
+ user: user,
+ granted_by: Discourse.system_user,
+ granted_at: Time.now,
+ post_id: post.id,
+ )
+ end
+
+ # Re-use our first badge, but on a different post
+ fab!(:ub4) do
+ UserBadge.create!(
+ badge_id: badge1.id,
+ user: user,
+ granted_by: Discourse.system_user,
+ granted_at: Time.now,
+ post_id: post2.id,
+ )
+ end
+
+ # Now re-use our first badge, but on a different user
+ fab!(:ub5) do
+ UserBadge.create!(
+ badge_id: badge1.id,
+ user: user2,
+ granted_by: Discourse.system_user,
+ granted_at: Time.now,
+ post_id: post.id,
+ )
+ end
+
+ # Create a badge that has the listable flag set to false
+ fab!(:badge4) do
+ Badge.create!(
+ name: "WeirdBadge",
+ badge_type_id: BadgeType::Bronze,
+ listable: false,
+ show_posts: true,
+ show_in_post_header: true,
+ )
+ end
+ fab!(:ub6) do
+ UserBadge.create!(
+ badge_id: badge4.id,
+ user: user,
+ granted_by: Discourse.system_user,
+ granted_at: Time.now,
+ post_id: post.id,
+ )
+ end
+
+ # Create a badge that has the show_in_post_header flag set to false
+ fab!(:badge5) do
+ Badge.create!(
+ name: "StrangeBadge",
+ badge_type_id: BadgeType::Bronze,
+ listable: true,
+ show_posts: true,
+ show_in_post_header: false,
+ )
+ end
+ fab!(:ub7) do
+ UserBadge.create!(
+ badge_id: badge5.id,
+ user: user,
+ granted_by: Discourse.system_user,
+ granted_at: Time.now,
+ post_id: post.id,
+ )
+ end
+
+ let(:serializer) { described_class.new(post, scope: Guardian.new(user), root: false) }
+
+ it "doesn't include badges when `enable_badges` site setting is disabled" do
+ SiteSetting.enable_badges = false
+ expect(serializer.as_json[:badges_granted]).to eq([])
+ end
+
+ it "doesn't include badges when `show_badges_in_post_header` site setting is disabled" do
+ SiteSetting.enable_badges = true
+ SiteSetting.show_badges_in_post_header = false
+ expect(serializer.as_json[:badges_granted]).to eq([])
+ end
+
+ context "when `enable_badges` and `show_badges_in_post_header` site settings are enabled" do
+ before do
+ SiteSetting.enable_badges = true
+ SiteSetting.show_badges_in_post_header = true
+ end
+
+ it "includes badges that were granted for this user on this post" do
+ json = serializer.as_json
+
+ expect(json[:badges_granted].length).to eq(2)
+ expect(json[:badges_granted].map { |b| b[:badges][0][:id] }).to contain_exactly(
+ ub1.badge_id,
+ ub2.badge_id,
+ )
+
+ expect(json[:badges_granted].map { |b| b[:basic_user_badge][:id] }).to contain_exactly(
+ ub1.id,
+ ub2.id,
+ )
+ end
+
+ it "does not return a user badge that has the show_posts flag set to false" do
+ json = serializer.as_json
+
+ expect(json[:badges_granted].map { |b| b[:basic_user_badge][:id] }).not_to include(ub3.id)
+ end
+
+ it "does not return a user badge that was not granted for this post" do
+ json = serializer.as_json
+
+ expect(json[:badges_granted].map { |b| b[:basic_user_badge][:id] }).not_to include(ub4.id)
+ end
+
+ it "does not return a user badge that was granted for a different user" do
+ json = serializer.as_json
+
+ expect(json[:badges_granted].map { |b| b[:basic_user_badge][:id] }).not_to include(ub5.id)
+ end
+
+ it "does not return a user badge that has the listable flag set to false" do
+ json = serializer.as_json
+
+ expect(json[:badges_granted].map { |b| b[:basic_user_badge][:id] }).not_to include(ub6.id)
+ end
+
+ it "does not return a user badge that has the show_in_post_header flag set to false" do
+ json = serializer.as_json
+
+ expect(json[:badges_granted].map { |b| b[:basic_user_badge][:id] }).not_to include(ub7.id)
+ end
+ end
+ end
+
def serialized_post(u)
s = PostSerializer.new(post, scope: Guardian.new(u), root: false)
s.add_raw = true
diff --git a/spec/system/grant_badge_spec.rb b/spec/system/grant_badge_spec.rb
index 3065c7eb79b..108119e0947 100644
--- a/spec/system/grant_badge_spec.rb
+++ b/spec/system/grant_badge_spec.rb
@@ -33,4 +33,43 @@ describe "Granting Badges", type: :system do
expect(granted_badge.post_id).to eq post.id
end
end
+
+ context "when granting a badge that shows in the post header" do
+ fab!(:user)
+ fab!(:post) { Fabricate(:post, user: user) }
+
+ let(:topic_page) { PageObjects::Pages::Topic.new }
+
+ fab!(:badge) do
+ Fabricate(
+ :manually_grantable_badge,
+ name: "SomeBadge",
+ listable: true,
+ show_posts: true,
+ show_in_post_header: true,
+ )
+ end
+ fab!(:user_badge) do
+ UserBadge.create!(
+ badge_id: badge.id,
+ user: user,
+ granted_by: Discourse.system_user,
+ granted_at: Time.now,
+ post_id: post.id,
+ )
+ end
+
+ it "shows badge in post header" do
+ topic_page.visit_topic(post.topic)
+ expect(topic_page.post_by_number(post).find(".user-badge-buttons")).to have_css(
+ ".user-badge-button-somebadge",
+ )
+ end
+
+ it "doesn't show badge in post header when `show_badges_in_post_header` site setting is disabled" do
+ SiteSetting.show_badges_in_post_header = false
+ topic_page.visit_topic(post.topic)
+ expect(topic_page.post_by_number(post)).to_not have_css(".user-badge-buttons")
+ end
+ end
end