diff --git a/app/models/user_option.rb b/app/models/user_option.rb index 84b0c2d463b..c93d7aea6f6 100644 --- a/app/models/user_option.rb +++ b/app/models/user_option.rb @@ -266,8 +266,6 @@ end # homepage_id :integer # theme_ids :integer default([]), not null, is an Array # hide_profile_and_presence :boolean default(FALSE), not null -# hide_profile :boolean default(FALSE), not null -# hide_presence :boolean default(FALSE), not null # text_size_key :integer default(0), not null # text_size_seq :integer default(0), not null # email_level :integer default(1), not null @@ -296,10 +294,14 @@ end # sidebar_show_count_of_new_items :boolean default(FALSE), not null # watched_precedence_over_muted :boolean # chat_separate_sidebar_mode :integer default(0), not null -# chat_send_shortcut :integer default(0), not null # topics_unread_when_closed :boolean default(TRUE), not null # show_thread_title_prompts :boolean default(TRUE), not null # enable_smart_lists :boolean default(TRUE), not null +# hide_profile :boolean default(FALSE), not null +# hide_presence :boolean default(FALSE), not null +# chat_send_shortcut :integer default(0), not null +# chat_quick_reaction_type :integer default(0), not null +# chat_quick_reactions_custom :string # # Indexes # diff --git a/plugins/chat/assets/javascripts/discourse/controllers/preferences-chat.js b/plugins/chat/assets/javascripts/discourse/controllers/preferences-chat.js index 3af87360378..658cf0f4949 100644 --- a/plugins/chat/assets/javascripts/discourse/controllers/preferences-chat.js +++ b/plugins/chat/assets/javascripts/discourse/controllers/preferences-chat.js @@ -19,8 +19,12 @@ const CHAT_ATTRS = [ "chat_header_indicator_preference", "chat_separate_sidebar_mode", "chat_send_shortcut", + "chat_quick_reaction_type", + "chat_quick_reactions_custom", ]; +export const CHAT_QUICK_REACTIONS_CUSTOM_DEFAULT = "heart|+1|smile"; + export const HEADER_INDICATOR_PREFERENCE_NEVER = "never"; export const HEADER_INDICATOR_PREFERENCE_DM_AND_MENTIONS = "dm_and_mentions"; export const HEADER_INDICATOR_PREFERENCE_ALL_NEW = "all_new"; @@ -32,6 +36,17 @@ export default class PreferencesChatController extends Controller { subpageTitle = i18n("chat.admin.title"); + chatQuickReactionTypes = [ + { + label: i18n("chat.quick_reaction_type.options.frequent"), + value: "frequent", + }, + { + label: i18n("chat.quick_reaction_type.options.custom"), + value: "custom", + }, + ]; + chatSendShortcutOptions = [ { label: i18n("chat.send_shortcut.enter.label"), @@ -99,6 +114,13 @@ export default class PreferencesChatController extends Controller { return this.model.get("user_option.chat_send_shortcut"); } + get chatQuickReactionsCustom() { + const emojis = + this.model.get("user_option.chat_quick_reactions_custom") || + CHAT_QUICK_REACTIONS_CUSTOM_DEFAULT; + return emojis.split("|"); + } + @discourseComputed chatSounds() { return Object.keys(CHAT_SOUNDS).map((value) => { @@ -114,6 +136,24 @@ export default class PreferencesChatController extends Controller { this.model.set("user_option.chat_sound", sound); } + @action + onChangeQuickReactionType(value) { + this.model.set("user_option.chat_quick_reaction_type", value); + if (value === "custom") { + this.model.set( + "user_option.chat_quick_reactions_custom", + this.chatQuickReactionsCustom.join("|") + ); + } + } + + @action + didSelectEmoji(index, selected) { + let emoji = this.chatQuickReactionsCustom; + emoji[index] = selected; + this.model.set("user_option.chat_quick_reactions_custom", emoji.join("|")); + } + @action save() { this.set("saved", false); diff --git a/plugins/chat/assets/javascripts/discourse/initializers/chat-user-options.js b/plugins/chat/assets/javascripts/discourse/initializers/chat-user-options.js index 79cf1be0250..b63da14b9c8 100644 --- a/plugins/chat/assets/javascripts/discourse/initializers/chat-user-options.js +++ b/plugins/chat/assets/javascripts/discourse/initializers/chat-user-options.js @@ -9,6 +9,8 @@ const CHAT_EMAIL_FREQUENCY = "chat_email_frequency"; const CHAT_HEADER_INDICATOR_PREFERENCE = "chat_header_indicator_preference"; const CHAT_SEPARATE_SIDEBAR_MODE = "chat_separate_sidebar_mode"; const CHAT_SEND_SHORTCUT = "chat_send_shortcut"; +const CHAT_QUICK_REACTION_TYPE = "chat_quick_reaction_type"; +const CHAT_QUICK_REACTIONS_CUSTOM = "chat_quick_reactions_custom"; export default { name: "chat-user-options", @@ -26,6 +28,8 @@ export default { api.addSaveableUserOptionField(CHAT_HEADER_INDICATOR_PREFERENCE); api.addSaveableUserOptionField(CHAT_SEPARATE_SIDEBAR_MODE); api.addSaveableUserOptionField(CHAT_SEND_SHORTCUT); + api.addSaveableUserOptionField(CHAT_QUICK_REACTION_TYPE); + api.addSaveableUserOptionField(CHAT_QUICK_REACTIONS_CUSTOM); } }); }, diff --git a/plugins/chat/assets/javascripts/discourse/lib/chat-message-interactor.js b/plugins/chat/assets/javascripts/discourse/lib/chat-message-interactor.js index 2afa8281bef..8546e13094f 100644 --- a/plugins/chat/assets/javascripts/discourse/lib/chat-message-interactor.js +++ b/plugins/chat/assets/javascripts/discourse/lib/chat-message-interactor.js @@ -60,13 +60,28 @@ export default class ChatemojiReactions { } get emojiReactions() { - const defaultReactions = this.siteSettings.default_emoji_reactions + const userQuickReactionsCustom = ( + (this.currentUser.user_option.chat_quick_reaction_type === "custom" && + this.currentUser.user_option.chat_quick_reactions_custom) || + "" + ) .split("|") .filter(Boolean); - return this.emojiStore - .favoritesForContext("chat") - .concat(defaultReactions) + const frequentReactions = this.emojiStore.favoritesForContext("chat"); + + const defaultReactions = + this.siteSettings.default_emoji_reactions.split("|"); + + const allReactionsInOrder = userQuickReactionsCustom + .concat(frequentReactions) + .concat(defaultReactions); + + return allReactionsInOrder + .filter((item, index) => { + return allReactionsInOrder.indexOf(item) === index; + }) + .filter(Boolean) .slice(0, 3) .map( (emoji) => diff --git a/plugins/chat/assets/javascripts/discourse/templates/preferences/chat.gjs b/plugins/chat/assets/javascripts/discourse/templates/preferences/chat.gjs index bfdca1fbb2d..dd374b473e7 100644 --- a/plugins/chat/assets/javascripts/discourse/templates/preferences/chat.gjs +++ b/plugins/chat/assets/javascripts/discourse/templates/preferences/chat.gjs @@ -1,8 +1,9 @@ import { Input } from "@ember/component"; -import { concat, fn, hash } from "@ember/helper"; +import { array, concat, fn, get, hash } from "@ember/helper"; import { on } from "@ember/modifier"; import RouteTemplate from "ember-route-template"; import { eq } from "truth-helpers"; +import EmojiPicker from "discourse/components/emoji-picker"; import SaveControls from "discourse/components/save-controls"; import withEventValue from "discourse/helpers/with-event-value"; import { i18n } from "discourse-i18n"; @@ -26,6 +27,52 @@ export default RouteTemplate( +
+ {{i18n + "chat.quick_reaction_type.title" + }} +
+ {{#each @controller.chatQuickReactionTypes as |option|}} +
+ +
+ {{/each}} +
+ + {{#if + (eq @controller.model.user_option.chat_quick_reaction_type "custom") + }} +
+ {{#each (array 0 1 2) as |index|}} + + {{/each}} +
+ {{/if}} +
+
%{username} invited you to join a chat channel" diff --git a/plugins/chat/db/migrate/20250307185912_add_chat_quick_reaction_preferences.rb b/plugins/chat/db/migrate/20250307185912_add_chat_quick_reaction_preferences.rb new file mode 100644 index 00000000000..aef4b3a8f22 --- /dev/null +++ b/plugins/chat/db/migrate/20250307185912_add_chat_quick_reaction_preferences.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true +class AddChatQuickReactionPreferences < ActiveRecord::Migration[7.2] + def change + add_column :user_options, :chat_quick_reaction_type, :integer, default: 0, null: false + add_column :user_options, :chat_quick_reactions_custom, :string + end +end diff --git a/plugins/chat/lib/chat/user_option_extension.rb b/plugins/chat/lib/chat/user_option_extension.rb index 78ba5a52957..cc7730f72c2 100644 --- a/plugins/chat/lib/chat/user_option_extension.rb +++ b/plugins/chat/lib/chat/user_option_extension.rb @@ -57,6 +57,10 @@ module Chat if !base.method_defined?(:show_thread_title_prompts?) base.attribute :show_thread_title_prompts, :boolean, default: true end + + if !base.method_defined?(:chat_quick_reaction_type_frequent?) + base.enum :chat_quick_reaction_type, { frequent: 0, custom: 1 }, prefix: true + end end end end diff --git a/plugins/chat/plugin.rb b/plugins/chat/plugin.rb index 312b9d358fa..7a5834e1150 100644 --- a/plugins/chat/plugin.rb +++ b/plugins/chat/plugin.rb @@ -54,6 +54,8 @@ after_initialize do DiscoursePluginRegistry.register_flag_applies_to_type("Chat::Message", self) UserUpdater::OPTION_ATTR.push(:chat_enabled) + UserUpdater::OPTION_ATTR.push(:chat_quick_reaction_type) + UserUpdater::OPTION_ATTR.push(:chat_quick_reactions_custom) UserUpdater::OPTION_ATTR.push(:only_chat_push_notifications) UserUpdater::OPTION_ATTR.push(:chat_sound) UserUpdater::OPTION_ATTR.push(:ignore_channel_wide_mention) @@ -251,6 +253,18 @@ after_initialize do add_to_serializer(:current_user_option, :chat_send_shortcut) { object.chat_send_shortcut } + add_to_serializer(:user_option, :chat_quick_reaction_type) { object.chat_quick_reaction_type } + add_to_serializer(:current_user_option, :chat_quick_reaction_type) do + object.chat_quick_reaction_type + end + + add_to_serializer(:user_option, :chat_quick_reactions_custom) do + object.chat_quick_reactions_custom + end + add_to_serializer(:current_user_option, :chat_quick_reactions_custom) do + object.chat_quick_reactions_custom + end + on(:site_setting_changed) do |name, old_value, new_value| user_option_field = Chat::RETENTION_SETTINGS_TO_USER_OPTION_FIELDS[name.to_sym] begin diff --git a/plugins/chat/spec/models/user_option_spec.rb b/plugins/chat/spec/models/user_option_spec.rb index 016676691c6..8a622a3b476 100644 --- a/plugins/chat/spec/models/user_option_spec.rb +++ b/plugins/chat/spec/models/user_option_spec.rb @@ -11,4 +11,9 @@ RSpec.describe UserOption do expect(described_class.new.show_thread_title_prompts).to eq(true) end end + describe "#chat_quick_reaction_type" do + it "is present with frequent as default" do + expect(described_class.new.chat_quick_reaction_type).to eq("frequent") + end + end end diff --git a/plugins/chat/spec/serializer/current_user_serializer_spec.rb b/plugins/chat/spec/serializer/current_user_serializer_spec.rb index d1a2a7c52ed..2be628e8428 100644 --- a/plugins/chat/spec/serializer/current_user_serializer_spec.rb +++ b/plugins/chat/spec/serializer/current_user_serializer_spec.rb @@ -19,6 +19,26 @@ RSpec.describe CurrentUserSerializer do end end + describe "#chat_quick_reaction_type" do + it "is present with default enum string" do + expect(serializer.as_json[:user_option][:chat_quick_reaction_type]).to eq("frequent") + end + end + + describe "#chat_quick_reactions_custom" do + it "is present with default enum string" do + expect(serializer.as_json[:user_option][:chat_quick_reactions_custom]).to eq(nil) + end + + context "with custom quick reactions" do + before { current_user.user_option.update!(chat_quick_reactions_custom: "tada|smiley") } + + it "is present" do + expect(serializer.as_json[:user_option][:chat_quick_reactions_custom]).to eq("tada|smiley") + end + end + end + describe "#chat_drafts" do context "when user can't chat" do before { SiteSetting.chat_allowed_groups = Group::AUTO_GROUPS[:staff] } diff --git a/plugins/chat/spec/serializer/user_serializer_spec.rb b/plugins/chat/spec/serializer/user_serializer_spec.rb index f4b1ed28c05..78667489b0a 100644 --- a/plugins/chat/spec/serializer/user_serializer_spec.rb +++ b/plugins/chat/spec/serializer/user_serializer_spec.rb @@ -1,15 +1,33 @@ # frozen_string_literal: true RSpec.describe UserSerializer do - fab!(:current_user) { Fabricate(:user) } + fab!(:user) - let(:serializer) do - described_class.new(current_user, scope: Guardian.new(current_user), root: false) - end + let(:serializer) { described_class.new(user, scope: Guardian.new(user), root: false) } describe "#chat_separate_sidebar_mode" do it "is present" do expect(serializer.as_json[:user_option][:chat_separate_sidebar_mode]).to eq("default") end end + + describe "#chat_quick_reaction_type" do + it "is present with default enum string" do + expect(serializer.as_json[:user_option][:chat_quick_reaction_type]).to eq("frequent") + end + end + + describe "#chat_quick_reactions_custom" do + it "is present with default enum string" do + expect(serializer.as_json[:user_option][:chat_quick_reactions_custom]).to eq(nil) + end + + context "with custom quick reactions" do + before { user.user_option.update!(chat_quick_reactions_custom: "tada|smiley") } + + it "is present" do + expect(serializer.as_json[:user_option][:chat_quick_reactions_custom]).to eq("tada|smiley") + end + end + end end diff --git a/plugins/chat/spec/system/page_objects/chat/chat_channel.rb b/plugins/chat/spec/system/page_objects/chat/chat_channel.rb index 7fe5a8f2498..bf98e177660 100644 --- a/plugins/chat/spec/system/page_objects/chat/chat_channel.rb +++ b/plugins/chat/spec/system/page_objects/chat/chat_channel.rb @@ -151,6 +151,10 @@ module PageObjects within(message_reactions_list(message)) { return find("[data-emoji-name=\"#{emoji}\"]") } end + def find_quick_reaction(emoji_name) + find(".chat-message-actions [data-emoji-name=\"#{emoji_name}\"]") + end + def has_reaction?(message, emoji, text = nil) within(message_reactions_list(message)) do has_css?("[data-emoji-name=\"#{emoji}\"]", text: text) diff --git a/plugins/chat/spec/system/page_objects/pages/user_preferences_chat.rb b/plugins/chat/spec/system/page_objects/pages/user_preferences_chat.rb new file mode 100644 index 00000000000..6dfb20d3813 --- /dev/null +++ b/plugins/chat/spec/system/page_objects/pages/user_preferences_chat.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module PageObjects + module Pages + class UserPreferencesChat < PageObjects::Pages::Base + def visit + page.visit("/my/preferences/chat") + self + end + + def emoji_picker_triggers + all(".emoji-picker-trigger", visible: true) + end + + def reaction_buttons + all(".emoji-pickers button") + end + + def reactions_selected + reaction_buttons.map { |b| b.find("img")[:title] } + end + + def select_option_value(selector, value) + select_kit = PageObjects::Components::SelectKit.new(selector) + select_kit.expand + select_kit.select_row_by_value(value) + end + + def selected_option_value(selector) + PageObjects::Components::SelectKit.new(selector).value + end + + def save_changes_and_refresh + page.find(".save-changes").click + # reloading the page happens in JS on save but capybara doesnt wait for it + # which can mess up navigating away too soon in tests + # so doing a manual refresh here to mimic + page.refresh + end + end + end +end diff --git a/plugins/chat/spec/system/user_chat_preferences_spec.rb b/plugins/chat/spec/system/user_chat_preferences_spec.rb index e682ae8933e..5e7bcb79b1f 100644 --- a/plugins/chat/spec/system/user_chat_preferences_spec.rb +++ b/plugins/chat/spec/system/user_chat_preferences_spec.rb @@ -3,7 +3,10 @@ RSpec.describe "User chat preferences", type: :system do fab!(:current_user) { Fabricate(:user) } + let(:user_preferences_chat_page) { PageObjects::Pages::UserPreferencesChat.new } + let(:emoji_picker) { PageObjects::Components::EmojiPicker.new } let(:chat) { PageObjects::Pages::Chat.new } + let(:channel) { PageObjects::Pages::ChatChannel.new } before do chat_system_bootstrap @@ -23,50 +26,89 @@ RSpec.describe "User chat preferences", type: :system do end it "shows a not found page" do - visit("/my/preferences/chat") + user_preferences_chat_page.visit expect(page).to have_content(I18n.t("page_not_found.title")) end end - it "can select chat sound" do - visit("/my/preferences") - find(".user-nav__preferences-chat", visible: :all).click - select_kit = PageObjects::Components::SelectKit.new("#user_chat_sounds") - select_kit.expand - select_kit.select_row_by_value("bell") - find(".save-changes").click + it "can change chat quick reaction type to custom and select emoji" do + user_preferences_chat_page.visit + find("#user_chat_quick_reaction_type_custom").click - expect(select_kit).to have_selected_value("bell") + expect(user_preferences_chat_page.emoji_picker_triggers.count).to eq 3 + expect(user_preferences_chat_page.reactions_selected.first).to eq "heart" + + user_preferences_chat_page.reaction_buttons.first.click + emoji_picker.select_emoji(":sweat_smile:") + user_preferences_chat_page.save_changes_and_refresh + + expect(page).to have_checked_field("user_chat_quick_reaction_type_custom") + expect(user_preferences_chat_page.reactions_selected.first).to eq "sweat_smile" end - it "can select header_indicator_preference" do - visit("/my/preferences") - find(".user-nav__preferences-chat", visible: :all).click - select_kit = PageObjects::Components::SelectKit.new("#user_chat_header_indicator_preference") - select_kit.expand - select_kit.select_row_by_value("dm_and_mentions") - find(".save-changes").click + describe "chat interface" do + fab!(:category_channel_1) { Fabricate(:category_channel) } + fab!(:message_1) { Fabricate(:chat_message, chat_channel: category_channel_1) } - expect(select_kit).to have_selected_value("dm_and_mentions") + it "sees expected quick-reactions on hover" do + sign_in(current_user) + + # save custom and look for reaction + user_preferences_chat_page.visit + find("#user_chat_quick_reaction_type_custom").click + user_preferences_chat_page.save_changes_and_refresh + chat.visit_channel(category_channel_1) + channel.hover_message(message_1) + + expect(channel.find_quick_reaction("smile")).to be_present + + # save frequent and look for reaction + user_preferences_chat_page.visit + find("#user_chat_quick_reaction_type_frequent").click + user_preferences_chat_page.save_changes_and_refresh + chat.visit_channel(category_channel_1) + channel.hover_message(message_1) + + expect(channel.find_quick_reaction("tada")).to be_present + end end - it "can select separate sidebar mode" do - visit("/my/preferences") - find(".user-nav__preferences-chat", visible: :all).click - select_kit = PageObjects::Components::SelectKit.new("#user_chat_separate_sidebar_mode") - select_kit.expand - select_kit.select_row_by_value("fullscreen") - find(".save-changes").click + shared_examples "select and save" do + it "can select and save" do + user_preferences_chat_page.visit + user_preferences_chat_page.select_option_value(sel, val) + user_preferences_chat_page.save_changes_and_refresh - expect(select_kit).to have_selected_value("fullscreen") + expect(user_preferences_chat_page.selected_option_value(sel)).to eq val + end + end + + describe "chat sound" do + include_examples "select and save" do + let(:sel) { "#user_chat_sounds" } + let(:val) { "bell" } + end + end + + describe "header_indicator_preference" do + include_examples "select and save" do + let(:sel) { "#user_chat_header_indicator_preference" } + let(:val) { "dm_and_mentions" } + end + end + + describe "separate sidebar mode" do + include_examples "select and save" do + let(:sel) { "#user_chat_separate_sidebar_mode" } + let(:val) { "fullscreen" } + end end it "can select send shorcut sidebar mode" do - visit("/my/preferences") - find(".user-nav__preferences-chat", visible: :all).click + user_preferences_chat_page.visit find("#chat_send_shortcut_meta_enter").click - find(".save-changes").click + user_preferences_chat_page.save_changes_and_refresh expect(page).to have_checked_field("chat_send_shortcut_meta_enter") end diff --git a/plugins/chat/test/javascripts/unit/lib/chat-message-interactor-test.js b/plugins/chat/test/javascripts/unit/lib/chat-message-interactor-test.js new file mode 100644 index 00000000000..8f8f26361c6 --- /dev/null +++ b/plugins/chat/test/javascripts/unit/lib/chat-message-interactor-test.js @@ -0,0 +1,122 @@ +import { getOwner } from "@ember/owner"; +import { setupTest } from "ember-qunit"; +import { module, test } from "qunit"; +import { + logIn, + updateCurrentUser, +} from "discourse/tests/helpers/qunit-helpers"; +import ChatMessageInteractor from "discourse/plugins/chat/discourse/lib/chat-message-interactor"; +import ChatFabricators from "discourse/plugins/chat/discourse/lib/fabricators"; + +module("Discourse Chat | Unit | chat-message-interactor", function (hooks) { + setupTest(hooks); + + hooks.beforeEach(function () { + logIn(getOwner(this)); + const message = new ChatFabricators(getOwner(this)).message(); + this.messageInteractor = new ChatMessageInteractor(getOwner(this), message); + this.emojiStore = getOwner(this).lookup("service:emoji-store"); + this.siteSettings = getOwner(this).lookup("service:site-settings"); + }); + + test("emojiReactions with no option uses site default", function (assert) { + assert.deepEqual( + this.messageInteractor.emojiReactions.map((r) => r.emoji), + ["+1", "heart", "tada"] + ); + }); + + test("emojiReactions empty when no frequent or site defaults", function (assert) { + this.siteSettings.default_emoji_reactions = ""; + + assert.deepEqual(this.messageInteractor.emojiReactions, []); + }); + + test("emojiReactions with user option frequent falls back to site defaults", function (assert) { + updateCurrentUser({ + user_option: { + chat_quick_reaction_type: "frequent", + }, + }); + + assert.deepEqual( + this.messageInteractor.emojiReactions.map((r) => r.emoji), + ["+1", "heart", "tada"] + ); + }); + + test("emojiReactions with top 3 frequent", function (assert) { + this.emojiStore.trackEmojiForContext("eyes", "chat"); + this.emojiStore.trackEmojiForContext("camera", "chat"); + this.emojiStore.trackEmojiForContext("butterfly", "chat"); + this.emojiStore.trackEmojiForContext("butterfly", "chat"); + this.emojiStore.trackEmojiForContext("laptop", "chat"); + + assert.deepEqual( + this.messageInteractor.emojiReactions.map((r) => r.emoji), + ["butterfly", "laptop", "camera"] + ); + }); + + test("emojiReactions with 1 frequent falls back to system", function (assert) { + this.emojiStore.trackEmojiForContext("butterfly", "chat"); + + assert.deepEqual( + this.messageInteractor.emojiReactions.map((r) => r.emoji), + ["butterfly", "+1", "heart"] + ); + }); + + test("emojiReactions uses custom user option", function (assert) { + updateCurrentUser({ + user_option: { + chat_quick_reaction_type: "custom", + chat_quick_reactions_custom: "grin|fearful|angry", + }, + }); + + assert.deepEqual( + this.messageInteractor.emojiReactions.map((r) => r.emoji), + ["grin", "fearful", "angry"] + ); + }); + + test("emojiReactions does not use custom if set to frequent", function (assert) { + updateCurrentUser({ + user_option: { + chat_quick_reaction_type: "frequent", + chat_quick_reactions_custom: "grin|fearful|angry", + }, + }); + + assert.deepEqual( + this.messageInteractor.emojiReactions.map((r) => r.emoji), + ["+1", "heart", "tada"] + ); + }); + + test("emojiReactions avoids duplicates from frequent and site", function (assert) { + this.emojiStore.trackEmojiForContext("+1", "chat"); + + assert.deepEqual( + this.messageInteractor.emojiReactions.map((r) => r.emoji), + ["+1", "heart", "tada"] + ); + }); + + test("emojiReactions avoids duplicates from custom + frequent + site", function (assert) { + updateCurrentUser({ + user_option: { + chat_quick_reaction_type: "custom", + chat_quick_reactions_custom: "+1|+1|+1", + }, + }); + this.emojiStore.trackEmojiForContext("+1", "chat"); + this.emojiStore.trackEmojiForContext("butterfly", "chat"); + + assert.deepEqual( + this.messageInteractor.emojiReactions.map((r) => r.emoji), + ["+1", "butterfly", "heart"] + ); + }); +});