FEATURE: user status (#16875)

This commit is contained in:
Andrei Prigorshnev 2022-05-27 13:15:14 +04:00 committed by GitHub
parent ac59168dde
commit 5c596273a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 766 additions and 7 deletions

View File

@ -238,6 +238,8 @@ const SiteHeaderComponent = MountWidget.extend(
this.appEvents.on("dom:clean", this, "_cleanDom");
this.appEvents.on("user-status:changed", () => this.queueRerender());
if (
this.currentUser &&
!this.get("currentUser.read_first_notification")

View File

@ -13,6 +13,7 @@ import { getURLWithCDN } from "discourse-common/lib/get-url";
import { isEmpty } from "@ember/utils";
import { prioritizeNameInUx } from "discourse/lib/settings";
import { dasherize } from "@ember/string";
import { emojiUnescape } from "discourse/lib/text";
export default Component.extend(CardContentsBase, CanCheckEmails, CleansUp, {
elementId: "user-card",
@ -49,6 +50,17 @@ export default Component.extend(CardContentsBase, CanCheckEmails, CleansUp, {
return user.location || user.website_name || this.userTimezone;
},
@discourseComputed("user.status")
hasStatus() {
return this.siteSettings.enable_user_status && this.user.status;
},
@discourseComputed("user.status")
userStatusEmoji() {
const emoji = this.user.status.emoji ?? "mega";
return emojiUnescape(`:${emoji}:`);
},
isSuspendedOrHasBio: or("user.suspend_reason", "user.bio_excerpt"),
showCheckEmail: and("user.staged", "canCheckEmails"),

View File

@ -0,0 +1,61 @@
import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { action } from "@ember/object";
import { notEmpty } from "@ember/object/computed";
import { inject as service } from "@ember/service";
import { popupAjaxError } from "discourse/lib/ajax-error";
import bootbox from "bootbox";
export default Controller.extend(ModalFunctionality, {
userStatusService: service("user-status"),
description: null,
statusIsSet: notEmpty("description"),
showDeleteButton: false,
onShow() {
if (this.currentUser.status) {
this.setProperties({
description: this.currentUser.status.description,
showDeleteButton: true,
});
}
},
@action
delete() {
this.userStatusService
.clear()
.then(() => {
this._resetModal();
this.send("closeModal");
})
.catch((e) => this._handleError(e));
},
@action
saveAndClose() {
if (this.description) {
const status = { description: this.description };
this.userStatusService
.set(status)
.then(() => {
this.send("closeModal");
})
.catch((e) => this._handleError(e));
}
},
_handleError(e) {
if (typeof e === "string") {
bootbox.alert(e);
} else {
popupAjaxError(e);
}
},
_resetModal() {
this.set("description", null);
this.set("showDeleteButton", false);
},
});

View File

@ -252,6 +252,13 @@ const ApplicationRoute = DiscourseRoute.extend(OpenComposer, {
hasGroups,
});
},
setUserStatus() {
showModal("user-status", {
title: "user_status.set_custom_status",
modalClass: "user-status",
});
},
},
renderTemplate() {

View File

@ -0,0 +1,27 @@
import Service, { inject as service } from "@ember/service";
import { ajax } from "discourse/lib/ajax";
export default class UserStatusService extends Service {
@service appEvents;
async set(status) {
await ajax({
url: "/user-status.json",
type: "PUT",
data: { description: status.description },
});
this.currentUser.set("status", status);
this.appEvents.trigger("do-not-disturb:changed");
}
async clear() {
await ajax({
url: "/user-status.json",
type: "DELETE",
});
this.currentUser.set("status", null);
this.appEvents.trigger("do-not-disturb:changed");
}
}

View File

@ -10,6 +10,7 @@
toggleAnonymous=(route-action "toggleAnonymous")
logout=(route-action "logout")
toggleSidebar=(route-action "toggleSidebar")
setUserStatus=(route-action "setUserStatus")
}}
{{software-update-prompt}}

View File

@ -62,6 +62,9 @@
{{#if this.user.staged}}
<h2 class="staged">{{i18n "user.staged"}}</h2>
{{/if}}
{{#if this.hasStatus}}
<h3 class="user-status">{{html-safe this.userStatusEmoji}} {{this.user.status.description}}</h3>
{{/if}}
{{plugin-outlet name="user-card-post-names" connectorTagName="div" args=(hash user=this.user) tagName="div"}}
</div>
<ul class="usercard-controls">

View File

@ -0,0 +1,24 @@
{{#d-modal-body}}
{{#conditional-loading-spinner condition=loading}}
<div class="control-group user-status-description-wrap">
{{input
class="user-status-description"
placeholder=(i18n "user_status.what_are_you_doing")
value=description}}
</div>
<div class="modal-footer control-group">
{{d-button
label="user_status.save"
class="btn-primary"
disabled=(not statusIsSet)
action=(action "saveAndClose")}}
{{d-modal-cancel close=(action "closeModal")}}
{{#if showDeleteButton}}
{{d-button
icon="trash-alt"
class="delete-status btn-danger"
action=(action "delete")}}
{{/if}}
</div>
{{/conditional-loading-spinner}}
{{/d-modal-body}}

View File

@ -86,9 +86,13 @@ export const ButtonClass = {
html(attrs) {
const contents = [];
const left = !attrs.iconRight;
if (attrs.icon && left) {
contents.push(this._buildIcon(attrs));
}
if (attrs.emoji && left) {
contents.push(this.attach("emoji", { name: attrs.emoji }));
}
if (attrs.label) {
contents.push(
h("span.d-button-label", I18n.t(attrs.label, attrs.labelOptions))
@ -106,6 +110,9 @@ export const ButtonClass = {
if (attrs.contents) {
contents.push(attrs.contents);
}
if (attrs.emoji && !left) {
contents.push(this.attach("emoji", { name: attrs.emoji }));
}
if (attrs.icon && !left) {
contents.push(this._buildIcon(attrs));
}

View File

@ -68,6 +68,14 @@ createWidget("header-notifications", {
),
];
if (this.currentUser.status) {
contents.push(
this.attach("user-status-bubble", {
emoji: this.currentUser.status.emoji,
})
);
}
if (user.isInDoNotDisturb()) {
contents.push(h("div.do-not-disturb-background", iconNode("moon")));
} else {

View File

@ -16,13 +16,40 @@ createWidgetFrom(QuickAccessItem, "logout-item", {
html() {
return this.attach("flat-button", {
action: "logout",
content: I18n.t("user.log_out"),
icon: "sign-out-alt",
label: "user.log_out",
});
},
});
createWidgetFrom(QuickAccessItem, "user-status-item", {
tagName: "li.user-status",
html() {
const action = "hideMenuAndSetStatus";
const userStatus = this.currentUser.status;
if (userStatus) {
const emoji = userStatus.emoji ?? "mega";
return this.attach("flat-button", {
action,
emoji,
translatedLabel: userStatus.description,
});
} else {
return this.attach("flat-button", {
action,
icon: "plus-circle",
label: "user_status.set_custom_status",
});
}
},
hideMenuAndSetStatus() {
this.sendWidgetAction("toggleUserMenu");
this.sendWidgetAction("setUserStatus");
},
});
createWidgetFrom(QuickAccessPanel, "quick-access-profile", {
tagName: "div.quick-access-panel.quick-access-profile",
@ -43,11 +70,16 @@ createWidgetFrom(QuickAccessPanel, "quick-access-profile", {
},
_getItems() {
let items = this._getDefaultItems();
const items = [];
if (this.siteSettings.enable_user_status) {
items.push({ widget: "user-status-item" });
}
items.push(...this._getDefaultItems());
if (this._showToggleAnonymousButton()) {
items.push(this._toggleAnonymousButton());
}
items = items.concat(_extraItems);
items.push(..._extraItems);
if (this.attrs.showLogoutButton) {
items.push({ widget: "logout-item" });

View File

@ -0,0 +1,10 @@
import { createWidget } from "discourse/widgets/widget";
export default createWidget("user-status-bubble", {
tagName: "div.user-status-background",
html(attrs) {
const emoji = attrs.emoji ?? "mega";
return this.attach("emoji", { name: emoji });
},
});

View File

@ -76,3 +76,30 @@ acceptance(
});
}
);
acceptance("User Card - User Status", function (needs) {
needs.user();
needs.pretender((server, helper) => {
const response = cloneJSON(userFixtures["/u/charlie/card.json"]);
response.user.status = { description: "off to dentist" };
server.get("/u/charlie/card.json", () => helper.response(response));
});
test("shows user status if enabled", async function (assert) {
this.siteSettings.enable_user_status = true;
await visit("/t/internationalization-localization/280");
await click('a[data-user-card="charlie"]');
assert.ok(exists(".user-card h3.user-status"));
});
test("doesn't show user status if disabled", async function (assert) {
this.siteSettings.enable_user_status = false;
await visit("/t/internationalization-localization/280");
await click('a[data-user-card="charlie"]');
assert.notOk(exists(".user-card h3.user-status"));
});
});

View File

@ -0,0 +1,167 @@
import {
acceptance,
exists,
query,
updateCurrentUser,
} from "discourse/tests/helpers/qunit-helpers";
import { click, fillIn, visit } from "@ember/test-helpers";
import { test } from "qunit";
acceptance("User Status", function (needs) {
needs.user();
needs.pretender((server, helper) => {
server.put("/user-status.json", () => helper.response({ success: true }));
server.delete("/user-status.json", () =>
helper.response({ success: true })
);
});
const userStatusFallbackEmoji = "mega";
const userStatus = "off to dentist";
test("doesn't show the user status button on the menu by default", async function (assert) {
this.siteSettings.enable_user_status = false;
await visit("/");
await click(".header-dropdown-toggle.current-user");
await click(".menu-links-row .user-preferences-link");
assert.notOk(exists("div.quick-access-panel li.user-status"));
});
test("shows the user status button on the menu when disabled in settings", async function (assert) {
this.siteSettings.enable_user_status = true;
await visit("/");
await click(".header-dropdown-toggle.current-user");
await click(".menu-links-row .user-preferences-link");
assert.ok(
exists("div.quick-access-panel li.user-status"),
"shows the button"
);
assert.ok(
exists("div.quick-access-panel li.user-status svg.d-icon-plus-circle"),
"shows the icon on the button"
);
});
test("shows user status on loaded page", async function (assert) {
this.siteSettings.enable_user_status = true;
updateCurrentUser({ status: { description: userStatus } });
await visit("/");
await click(".header-dropdown-toggle.current-user");
await click(".menu-links-row .user-preferences-link");
assert.equal(
query("div.quick-access-panel li.user-status span.d-button-label")
.innerText,
userStatus,
"shows user status description on the menu"
);
assert.equal(
query("div.quick-access-panel li.user-status img.emoji").alt,
`:${userStatusFallbackEmoji}:`,
"shows user status emoji on the menu"
);
assert.equal(
query(".header-dropdown-toggle .user-status-background img.emoji").alt,
`:${userStatusFallbackEmoji}:`,
"shows user status emoji on the user avatar in the header"
);
});
test("setting user status", async function (assert) {
this.siteSettings.enable_user_status = true;
await visit("/");
await click(".header-dropdown-toggle.current-user");
await click(".menu-links-row .user-preferences-link");
await click(".user-status button");
await fillIn(".user-status-description", userStatus);
await click(".btn-primary");
assert.equal(
query(".header-dropdown-toggle .user-status-background img.emoji").alt,
`:${userStatusFallbackEmoji}:`,
"shows user status emoji on the user avatar in the header"
);
await click(".header-dropdown-toggle.current-user");
await click(".menu-links-row .user-preferences-link");
assert.equal(
query("div.quick-access-panel li.user-status span.d-button-label")
.innerText,
userStatus,
"shows user status description on the menu"
);
assert.equal(
query("div.quick-access-panel li.user-status img.emoji").alt,
`:${userStatusFallbackEmoji}:`,
"shows user status emoji on the menu"
);
});
test("updating user status", async function (assert) {
this.siteSettings.enable_user_status = true;
updateCurrentUser({ status: { description: userStatus } });
const updatedStatus = "off to dentist the second time";
await visit("/");
await click(".header-dropdown-toggle.current-user");
await click(".menu-links-row .user-preferences-link");
await click(".user-status button");
await fillIn(".user-status-description", updatedStatus);
await click(".btn-primary");
await click(".header-dropdown-toggle.current-user");
await click(".menu-links-row .user-preferences-link");
assert.equal(
query("div.quick-access-panel li.user-status span.d-button-label")
.innerText,
updatedStatus,
"shows user status description on the menu"
);
});
test("clearing user status", async function (assert) {
this.siteSettings.enable_user_status = true;
updateCurrentUser({ status: { description: userStatus } });
await visit("/");
await click(".header-dropdown-toggle.current-user");
await click(".menu-links-row .user-preferences-link");
await click(".user-status button");
await click(".btn.delete-status");
assert.notOk(exists(".header-dropdown-toggle .user-status-background"));
});
test("shows the trash button when editing status that was set before", async function (assert) {
this.siteSettings.enable_user_status = true;
updateCurrentUser({ status: { description: userStatus } });
await visit("/");
await click(".header-dropdown-toggle.current-user");
await click(".menu-links-row .user-preferences-link");
await click(".user-status button");
assert.ok(exists(".btn.delete-status"));
});
test("doesn't show the trash button when status wasn't set before", async function (assert) {
this.siteSettings.enable_user_status = true;
updateCurrentUser({ status: null });
await visit("/");
await click(".header-dropdown-toggle.current-user");
await click(".menu-links-row .user-preferences-link");
await click(".user-status button");
assert.notOk(exists(".btn.delete-status"));
});
});

View File

@ -41,6 +41,20 @@ discourseModule("Integration | Component | Widget | button", function (hooks) {
},
});
componentTest("emoji and text button", {
template: hbs`{{mount-widget widget="button" args=args}}`,
beforeEach() {
this.set("args", { emoji: "mega", label: "topic.create" });
},
test(assert) {
assert.ok(exists("button.widget-button"), "renders the widget");
assert.ok(exists("button img.emoji"), "it renders the emoji");
assert.ok(exists("button span.d-button-label"), "it renders the label");
},
});
componentTest("text only button", {
template: hbs`{{mount-widget widget="button" args=args}}`,

View File

@ -8,6 +8,7 @@
@import "common/foundation/base";
@import "common/select-kit/_index";
@import "common/components/_index";
@import "common/modal/_index";
@import "common/input_tip";
@import "common/topic-entrance";
@import "common/printer-friendly";

View File

@ -401,7 +401,7 @@ table {
.d-header .header-dropdown-toggle .do-not-disturb-background {
position: absolute;
left: 2px;
left: 0;
bottom: -1px;
z-index: 1002;
}
@ -424,6 +424,29 @@ table {
}
}
.d-header .header-dropdown-toggle .user-status-background {
position: absolute;
right: -3px;
bottom: -1px;
z-index: 1002;
}
.user-status-background {
display: flex;
align-items: center;
justify-content: center;
width: 1.25em;
height: 1.25em;
background-color: var(--tertiary-low);
border-radius: 50%;
.emoji {
width: 14px;
height: 14px;
display: block;
}
}
.user-menu .quick-access-panel li.do-not-disturb {
display: flex;
flex: 0 0 100%;

View File

@ -338,6 +338,13 @@
padding-top: 0.2em;
margin-right: 0.5em;
}
img.emoji {
height: 1em;
width: 1em;
padding-top: 0.2em;
margin-right: 0.5em;
}
}
.is-warning {
.d-icon-far-envelope {

View File

@ -300,3 +300,9 @@ $avatar_margin: -50px; // negative margin makes avatars extend above cards
}
}
}
h3.user-status {
img.emoji {
margin-bottom: 1px;
}
}

View File

@ -0,0 +1 @@
@import "user-status";

View File

@ -0,0 +1,38 @@
.user-status.modal {
.modal-inner-container {
box-sizing: border-box;
min-width: 310px;
}
.modal-footer {
margin: 0;
border-top: 0;
padding: 10px 0;
.delete-status {
margin-left: auto;
margin-right: 0;
}
}
.modal-body {
width: 375px;
box-sizing: border-box;
@media (max-width: 600px) {
width: 100%;
}
.ember-text-field.user-status-description {
min-width: 220px;
width: 100%;
margin-bottom: 0.5em;
}
.user-status-description-wrap {
display: inline-flex;
width: 100%;
align-items: end;
}
}
}

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
class UserStatusController < ApplicationController
requires_login
def set
ensure_feature_enabled
raise Discourse::InvalidParameters.new(:description) if params[:description].blank?
current_user.set_status!(params[:description])
render json: success_json
end
def clear
ensure_feature_enabled
current_user.clear_status!
render json: success_json
end
private
def ensure_feature_enabled
raise ActionController::RoutingError.new("Not Found") if !SiteSetting.enable_user_status
end
end

View File

@ -119,7 +119,8 @@ class UsersController < ApplicationController
:card_background_upload,
:primary_group,
:flair_group,
:primary_email
:primary_email,
:user_status
)
users = users.filter { |u| guardian.can_see_profile?(u) }

View File

@ -63,6 +63,7 @@ class User < ActiveRecord::Base
has_many :muted_user_records, class_name: 'MutedUser', dependent: :delete_all
has_many :ignored_user_records, class_name: 'IgnoredUser', dependent: :delete_all
has_many :do_not_disturb_timings, dependent: :delete_all
has_one :user_status, dependent: :destroy
# dependent deleting handled via before_destroy (special cases)
has_many :user_actions
@ -1500,6 +1501,23 @@ class User < ActiveRecord::Base
end
end
def clear_status!
user_status.destroy! if user_status
end
def set_status!(description)
now = Time.zone.now
if user_status
user_status.update!(description: description, set_at: now)
else
self.user_status = UserStatus.create!(
user_id: id,
description: description,
set_at: now
)
end
end
protected
def badge_grant

25
app/models/user_status.rb Normal file
View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
class UserStatus < ActiveRecord::Base
belongs_to :user
validate :ends_at_greater_than_set_at,
if: Proc.new { |t| t.will_save_change_to_set_at? || t.will_save_change_to_ends_at? }
def ends_at_greater_than_set_at
if ends_at && set_at > ends_at
errors.add(:ends_at, I18n.t("user_status.errors.ends_at_should_be_greater_than_set_at"))
end
end
end
# == Schema Information
#
# Table name: user_statuses
#
# user_id :integer not null, primary key
# emoji :string
# description :string not null
# set_at :datetime not null
# ends_at :datetime
#

View File

@ -70,7 +70,8 @@ class CurrentUserSerializer < BasicUserSerializer
:default_calendar,
:bookmark_auto_delete_preference,
:pending_posts_count,
:experimental_sidebar_enabled
:experimental_sidebar_enabled,
:status
delegate :user_stat, to: :object, private: true
delegate :any_posts, :draft_count, :pending_posts_count, :read_faq?, to: :user_stat
@ -336,4 +337,12 @@ class CurrentUserSerializer < BasicUserSerializer
def include_experimental_sidebar_enabled?
SiteSetting.enable_experimental_sidebar
end
def include_status?
SiteSetting.enable_user_status
end
def status
UserStatusSerializer.new(object.user_status, root: false)
end
end

View File

@ -66,7 +66,8 @@ class UserCardSerializer < BasicUserSerializer
:flair_color,
:featured_topic,
:timezone,
:pending_posts_count
:pending_posts_count,
:status
untrusted_attributes :bio_excerpt,
:website,
@ -222,6 +223,14 @@ class UserCardSerializer < BasicUserSerializer
object.card_background_upload&.url
end
def include_status?
SiteSetting.enable_user_status
end
def status
UserStatusSerializer.new(user.user_status, root: false)
end
private
def custom_field_keys

View File

@ -0,0 +1,5 @@
# frozen_string_literal: true
class UserStatusSerializer < ApplicationSerializer
attributes :description, :emoji
end

View File

@ -37,6 +37,7 @@ class WebHookUserSerializer < UserSerializer
user_auth_token_logs
use_logo_small_as_avatar
pending_posts_count
status
}.each do |attr|
define_method("include_#{attr}?") do
false

View File

@ -1785,6 +1785,11 @@ en:
private_message: "message"
the_topic: "the topic"
user_status:
save: "Save"
set_custom_status: "Set custom status"
what_are_you_doing: "What are you doing?"
loading: "Loading..."
errors:
prev_page: "while trying to load"

View File

@ -5206,3 +5206,7 @@ en:
small_action_post_raw: "Continue discussion at %{new_title}."
fallback_username: "user"
user_status:
errors:
ends_at_should_be_greater_than_set_at: "ends_at should be greater than set_at"

View File

@ -1020,6 +1020,9 @@ Discourse::Application.routes.draw do
post "/presence/update" => "presence#update"
get "/presence/get" => "presence#get"
put "user-status" => "user_status#set"
delete "user-status" => "user_status#clear"
get "*url", to: 'permalinks#show', constraints: PermalinkConstraint.new
end
end

View File

@ -361,6 +361,10 @@ basic:
default: true
sitemap_page_size:
default: 10000
enable_user_status:
hidden: true
client: true
default: false
login:
invite_only:

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class CreateUserStatuses < ActiveRecord::Migration[7.0]
def up
create_table :user_statuses, id: false do |t|
t.integer :user_id, primary_key: true, null: false
t.string :emoji, null: true
t.string :description, null: false
t.datetime :set_at, null: false
t.datetime :ends_at, null: true
end
end
def down
drop_table :user_statuses
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
Fabricator(:user_status) do
user
set_at { Time.zone.now }
description { "off to dentists" }
emoji { "tooth" }
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
describe UserStatus do
fab!(:user) { Fabricate(:user) }
describe "validations" do
it 'is invalid when ends_at is before set_at' do
freeze_time
user_status = UserStatus.new(user: user, set_at: Time.zone.now, ends_at: 1.hour.ago)
user_status.valid?
expect(user_status.errors[:ends_at]).to be_present
end
end
end

View File

@ -0,0 +1,86 @@
# frozen_string_literal: true
describe UserStatusController do
describe '#set' do
it 'requires user to be logged in' do
put "/user-status.json", params: { description: "off to dentist" }
expect(response.status).to eq(403)
end
it "returns 404 if the feature is disabled" do
user = Fabricate(:user)
sign_in(user)
SiteSetting.enable_user_status = false
put "/user-status.json", params: { description: "off" }
expect(response.status).to eq(404)
end
describe 'feature is enabled and user is logged in' do
fab!(:user) { Fabricate(:user) }
before do
sign_in(user)
SiteSetting.enable_user_status = true
end
it "sets user status" do
status = "off to dentist"
put "/user-status.json", params: { description: status }
expect(user.user_status.description).to eq(status)
end
it 'the description parameter is mandatory' do
put "/user-status.json", params: {}
expect(response.status).to eq(400)
end
it "following calls update status" do
status = "off to dentist"
put "/user-status.json", params: { description: status }
user.reload
expect(user.user_status.description).to eq(status)
new_status = "working"
put "/user-status.json", params: { description: new_status }
user.reload
expect(user.user_status.description).to eq(new_status)
end
end
end
describe '#clear' do
it 'requires you to be logged in' do
delete "/user-status.json"
expect(response.status).to eq(403)
end
it "returns 404 if the feature is disabled" do
user = Fabricate(:user)
sign_in(user)
SiteSetting.enable_user_status = false
delete "/user-status.json"
expect(response.status).to eq(404)
end
describe 'feature is enabled and user is logged in' do
fab!(:user_status) { Fabricate(:user_status, description: "off to dentist") }
fab!(:user) { Fabricate(:user, user_status: user_status) }
before do
sign_in(user)
SiteSetting.enable_user_status = true
end
it "clears user status" do
delete "/user-status.json"
user.reload
expect(user.user_status).to be_nil
end
end
end
end

View File

@ -182,4 +182,27 @@ RSpec.describe CurrentUserSerializer do
expect(pending_posts_count).to eq 3
end
end
describe "#status" do
fab!(:user_status) { Fabricate(:user_status) }
fab!(:user) { Fabricate(:user, user_status: user_status) }
let(:serializer) { described_class.new(user, scope: Guardian.new(user), root: false) }
it "serializes when enabled" do
SiteSetting.enable_user_status = true
json = serializer.as_json
expect(json[:status]).to_not be_nil do |status|
expect(status.description).to eq(user_status.description)
expect(status.emoji).to eq(user_status.emoji)
end
end
it "doesn't serialize when disabled" do
SiteSetting.enable_user_status = false
json = serializer.as_json
expect(json.keys).not_to include :status
end
end
end

View File

@ -70,4 +70,27 @@ describe UserCardSerializer do
end
end
describe "#status" do
fab!(:user_status) { Fabricate(:user_status) }
fab!(:user) { Fabricate(:user, user_status: user_status) }
let(:serializer) { described_class.new(user, scope: Guardian.new(user), root: false) }
it "serializes when enabled" do
SiteSetting.enable_user_status = true
json = serializer.as_json
expect(json[:status]).to_not be_nil do |status|
expect(status.description).to eq(user_status.description)
expect(status.emoji).to eq(user_status.emoji)
end
end
it "doesn't serialize when disabled" do
SiteSetting.enable_user_status = false
json = serializer.as_json
expect(json.keys).not_to include :status
end
end
end