mirror of
https://github.com/discourse/discourse.git
synced 2025-01-20 17:33:00 +08:00
FEATURE: Add an emoji deny list site setting (#20929)
This feature will allow sites to define which emoji are not allowed. Emoji in this list should be excluded from the set we show in the core emoji picker used in the composer for posts when emoji are enabled. And they should not be allowed to be chosen to be added to messages or as reactions in chat. This feature prevents denied emoji from appearing in the following scenarios: - topic title and page title - private messages (topic title and body) - inserting emojis into a chat - reacting to chat messages - using the emoji picker (composer, user status etc) - using search within emoji picker It also takes into account the various ways that emojis can be accessed, such as: - emoji autocomplete suggestions - emoji favourites (auto populates when adding to emoji deny list for example) - emoji inline translations - emoji skintones (ie. for certain hand gestures)
This commit is contained in:
parent
5b1306cb54
commit
967010e545
|
@ -530,7 +530,11 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
|
||||
if (term === "") {
|
||||
if (this.emojiStore.favorites.length) {
|
||||
return resolve(this.emojiStore.favorites.slice(0, 5));
|
||||
return resolve(
|
||||
this.emojiStore.favorites
|
||||
.filter((f) => !this.site.denied_emojis?.includes(f))
|
||||
.slice(0, 5)
|
||||
);
|
||||
} else {
|
||||
return resolve([
|
||||
"slight_smile",
|
||||
|
@ -555,12 +559,13 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
return resolve([allTranslations[full]]);
|
||||
}
|
||||
|
||||
const emojiDenied = this.get("site.denied_emojis") || [];
|
||||
const match = term.match(/^:?(.*?):t([2-6])?$/);
|
||||
if (match) {
|
||||
const name = match[1];
|
||||
const scale = match[2];
|
||||
|
||||
if (isSkinTonableEmoji(name)) {
|
||||
if (isSkinTonableEmoji(name) && !emojiDenied.includes(name)) {
|
||||
if (scale) {
|
||||
return resolve([`${name}:t${scale}`]);
|
||||
} else {
|
||||
|
@ -572,6 +577,7 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
const options = emojiSearch(term, {
|
||||
maxResults: 5,
|
||||
diversity: this.emojiStore.diversity,
|
||||
exclude: emojiDenied,
|
||||
});
|
||||
|
||||
return resolve(options);
|
||||
|
|
|
@ -380,6 +380,7 @@ export default Component.extend({
|
|||
if (filter) {
|
||||
results.innerHTML = emojiSearch(filter.toLowerCase(), {
|
||||
diversity: this.emojiStore.diversity,
|
||||
exclude: this.site.denied_emojis,
|
||||
})
|
||||
.map(this._replaceEmoji)
|
||||
.join("");
|
||||
|
|
|
@ -19,6 +19,7 @@ function getOpts(opts) {
|
|||
currentUser: context.currentUser,
|
||||
censoredRegexp: context.site.censored_regexp,
|
||||
customEmojiTranslation: context.site.custom_emoji_translation,
|
||||
emojiDenyList: context.site.denied_emojis,
|
||||
siteSettings: context.siteSettings,
|
||||
formatUsername,
|
||||
watchedWordsReplace: context.site.watched_words_replace,
|
||||
|
@ -96,6 +97,7 @@ function createPrettyText(options) {
|
|||
|
||||
function emojiOptions() {
|
||||
let siteSettings = helperContext().siteSettings;
|
||||
let context = helperContext();
|
||||
if (!siteSettings.enable_emoji) {
|
||||
return;
|
||||
}
|
||||
|
@ -105,6 +107,7 @@ function emojiOptions() {
|
|||
emojiSet: siteSettings.emoji_set,
|
||||
enableEmojiShortcuts: siteSettings.enable_emoji_shortcuts,
|
||||
inlineEmoji: siteSettings.enable_inline_emoji_translation,
|
||||
emojiDenyList: context.site.denied_emojis,
|
||||
emojiCDNUrl: siteSettings.external_emoji_url,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -88,6 +88,11 @@ export function performEmojiUnescape(string, opts) {
|
|||
classes += ` ${opts.class}`;
|
||||
}
|
||||
|
||||
// hides denied emojis and aliases from the emoji picker
|
||||
if (opts.emojiDenyList?.includes(emojiVal)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const isReplacable =
|
||||
(isEmoticon || hasEndingColon || isUnicodeEmoticon) &&
|
||||
isReplacableInlineEmoji(string, index, opts.inlineEmoji);
|
||||
|
@ -184,6 +189,7 @@ let toSearch;
|
|||
export function emojiSearch(term, options) {
|
||||
const maxResults = options?.maxResults;
|
||||
const diversity = options?.diversity;
|
||||
const exclude = options?.exclude || [];
|
||||
if (maxResults === 0) {
|
||||
return [];
|
||||
}
|
||||
|
@ -200,7 +206,8 @@ export function emojiSearch(term, options) {
|
|||
|
||||
function addResult(t) {
|
||||
const val = aliasMap.get(t) || t;
|
||||
if (!results.includes(val)) {
|
||||
// dont add skin tone variations or alias of denied emoji to search results
|
||||
if (!results.includes(val) && !exclude.includes(val)) {
|
||||
if (diversity && diversity > 1 && isSkinTonableEmoji(val)) {
|
||||
results.push(`${val}:t${diversity}`);
|
||||
} else {
|
||||
|
|
|
@ -43,6 +43,7 @@ export function buildOptions(state) {
|
|||
customEmojiTranslation,
|
||||
watchedWordsReplace,
|
||||
watchedWordsLink,
|
||||
emojiDenyList,
|
||||
featuresOverride,
|
||||
markdownItRules,
|
||||
additionalOptions,
|
||||
|
@ -88,6 +89,7 @@ export function buildOptions(state) {
|
|||
disableEmojis,
|
||||
watchedWordsReplace,
|
||||
watchedWordsLink,
|
||||
emojiDenyList,
|
||||
featuresOverride,
|
||||
markdownItRules,
|
||||
additionalOptions,
|
||||
|
|
|
@ -196,7 +196,8 @@ function applyEmoji(
|
|||
enableShortcuts,
|
||||
inlineEmoji,
|
||||
customEmojiTranslation,
|
||||
watchedWordsReplacer
|
||||
watchedWordsReplacer,
|
||||
emojiDenyList
|
||||
) {
|
||||
let result = null;
|
||||
let start = 0;
|
||||
|
@ -224,6 +225,16 @@ function applyEmoji(
|
|||
});
|
||||
}
|
||||
|
||||
// prevent denied emoji and aliases from being rendered
|
||||
if (emojiDenyList?.length > 0) {
|
||||
emojiDenyList.forEach((emoji) => {
|
||||
if (content?.match(emoji)) {
|
||||
const regex = new RegExp(`:${emoji}:`, "g");
|
||||
content = content.replace(regex, "");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let end = content.length;
|
||||
|
||||
for (let i = 0; i < content.length - 1; i++) {
|
||||
|
@ -346,6 +357,7 @@ export function setup(helper) {
|
|||
opts.emojiSet = siteSettings.emoji_set || "";
|
||||
opts.customEmoji = state.customEmoji;
|
||||
opts.emojiCDNUrl = siteSettings.external_emoji_url;
|
||||
opts.emojiDenyList = state.emojiDenyList;
|
||||
});
|
||||
|
||||
helper.registerPlugin((md) => {
|
||||
|
@ -358,7 +370,8 @@ export function setup(helper) {
|
|||
md.options.discourse.features.emojiShortcuts,
|
||||
md.options.discourse.features.inlineEmoji,
|
||||
md.options.discourse.customEmojiTranslation,
|
||||
md.options.discourse.watchedWordsReplace
|
||||
md.options.discourse.watchedWordsReplace,
|
||||
md.options.discourse.emojiDenyList
|
||||
)
|
||||
)
|
||||
);
|
||||
|
|
|
@ -28,6 +28,14 @@ class Emoji
|
|||
Discourse.cache.fetch(cache_key("standard_emojis")) { load_standard }
|
||||
end
|
||||
|
||||
def self.allowed
|
||||
Discourse.cache.fetch(cache_key("allowed_emojis")) { load_allowed }
|
||||
end
|
||||
|
||||
def self.denied
|
||||
Discourse.cache.fetch(cache_key("denied_emojis")) { load_denied }
|
||||
end
|
||||
|
||||
def self.aliases
|
||||
db["aliases"]
|
||||
end
|
||||
|
@ -114,7 +122,7 @@ class Emoji
|
|||
end
|
||||
|
||||
def self.clear_cache
|
||||
%w[custom standard translations all].each do |key|
|
||||
%w[custom standard translations allowed denied all].each do |key|
|
||||
Discourse.cache.delete(cache_key("#{key}_emojis"))
|
||||
end
|
||||
global_emoji_cache.clear
|
||||
|
@ -150,6 +158,26 @@ class Emoji
|
|||
db["emojis"].map { |e| Emoji.create_from_db_item(e) }.compact
|
||||
end
|
||||
|
||||
def self.load_allowed
|
||||
denied_emojis = denied
|
||||
all_emojis = load_standard + load_custom
|
||||
|
||||
if denied_emojis.present?
|
||||
all_emojis.reject { |e| denied_emojis.include?(e.name) }
|
||||
else
|
||||
all_emojis
|
||||
end
|
||||
end
|
||||
|
||||
def self.load_denied
|
||||
if SiteSetting.emoji_deny_list.present?
|
||||
denied_emoji = SiteSetting.emoji_deny_list.split("|")
|
||||
if denied_emoji.size > 0
|
||||
denied_emoji.concat(denied_emoji.flat_map { |e| Emoji.aliases[e] }.compact)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.load_custom
|
||||
result = []
|
||||
|
||||
|
@ -243,6 +271,8 @@ class Emoji
|
|||
end
|
||||
|
||||
def self.lookup_unicode(name)
|
||||
return "" if denied&.include?(name)
|
||||
|
||||
@reverse_map ||=
|
||||
begin
|
||||
map = {}
|
||||
|
|
|
@ -41,6 +41,7 @@ class SiteSerializer < ApplicationSerializer
|
|||
:anonymous_default_sidebar_tags,
|
||||
:anonymous_sidebar_sections,
|
||||
:whispers_allowed_groups_names,
|
||||
:denied_emojis,
|
||||
)
|
||||
|
||||
has_many :archetypes, embed: :objects, serializer: ArchetypeSerializer
|
||||
|
@ -280,6 +281,14 @@ class SiteSerializer < ApplicationSerializer
|
|||
scope.can_see_whispers?
|
||||
end
|
||||
|
||||
def denied_emojis
|
||||
@denied_emojis ||= Emoji.denied
|
||||
end
|
||||
|
||||
def include_denied_emojis?
|
||||
denied_emojis.present?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ordered_flags(flags)
|
||||
|
|
|
@ -63,4 +63,6 @@ DiscourseEvent.on(:site_setting_changed) do |name, old_value, new_value|
|
|||
if name == :reviewable_low_priority_threshold && Reviewable.min_score_for_priority(:medium) > 0
|
||||
Reviewable.set_priorities(low: new_value)
|
||||
end
|
||||
|
||||
Emoji.clear_cache && Discourse.request_refresh! if name == :emoji_deny_list
|
||||
end
|
||||
|
|
|
@ -2264,6 +2264,7 @@ en:
|
|||
emoji_set: "How would you like your emoji?"
|
||||
emoji_autocomplete_min_chars: "Minimum number of characters required to trigger autocomplete emoji popup"
|
||||
enable_inline_emoji_translation: "Enables translation for inline emojis (without any space or punctuation before)"
|
||||
emoji_deny_list: "These emoji will not be available to use in menus or shortcodes."
|
||||
|
||||
approve_post_count: "The amount of posts from a new or basic user that must be approved"
|
||||
approve_unless_trust_level: "Posts for users below this trust level must be approved"
|
||||
|
|
|
@ -1012,6 +1012,11 @@ posting:
|
|||
zh_TW: true
|
||||
ja: true
|
||||
ko: true
|
||||
emoji_deny_list:
|
||||
type: emoji_list
|
||||
default: ""
|
||||
client: true
|
||||
refresh: true
|
||||
approve_post_count:
|
||||
default: 0
|
||||
approve_unless_trust_level:
|
||||
|
|
|
@ -202,6 +202,7 @@ module PrettyText
|
|||
__optInput.customEmoji = #{custom_emoji.to_json};
|
||||
__optInput.customEmojiTranslation = #{Plugin::CustomEmoji.translations.to_json};
|
||||
__optInput.emojiUnicodeReplacer = __emojiUnicodeReplacer;
|
||||
__optInput.emojiDenyList = #{Emoji.denied.to_json};
|
||||
__optInput.lookupUploadUrls = __lookupUploadUrls;
|
||||
__optInput.censoredRegexp = #{WordWatcher.serializable_word_matcher_regexp(:censor).to_json};
|
||||
__optInput.watchedWordsReplace = #{WordWatcher.word_matcher_regexps(:replace).to_json};
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module Chat
|
||||
class EmojisController < ::Chat::BaseController
|
||||
def index
|
||||
emojis = Emoji.all.group_by(&:group)
|
||||
emojis = Emoji.allowed.group_by(&:group)
|
||||
render json: MultiJson.dump(emojis)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -480,12 +480,13 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
return resolve([allTranslations[full]]);
|
||||
}
|
||||
|
||||
const emojiDenied = this.get("site.denied_emojis") || [];
|
||||
const match = term.match(/^:?(.*?):t([2-6])?$/);
|
||||
if (match) {
|
||||
const name = match[1];
|
||||
const scale = match[2];
|
||||
|
||||
if (isSkinTonableEmoji(name)) {
|
||||
if (isSkinTonableEmoji(name) && !emojiDenied.includes(name)) {
|
||||
if (scale) {
|
||||
return resolve([`${name}:t${scale}`]);
|
||||
} else {
|
||||
|
@ -497,6 +498,7 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
const options = emojiSearch(term, {
|
||||
maxResults: 5,
|
||||
diversity: this.chatEmojiReactionStore.diversity,
|
||||
exclude: emojiDenied,
|
||||
});
|
||||
|
||||
return resolve(options);
|
||||
|
|
|
@ -51,7 +51,9 @@ export default class ChatEmojiPicker extends Component {
|
|||
get groups() {
|
||||
const emojis = this.chatEmojiPickerManager.emojis;
|
||||
const favorites = {
|
||||
favorites: this.chatEmojiReactionStore.favorites.map((name) => {
|
||||
favorites: this.chatEmojiReactionStore.favorites
|
||||
.filter((f) => !this.site.denied_emojis?.includes(f))
|
||||
.map((name) => {
|
||||
return {
|
||||
name,
|
||||
group: "favorites",
|
||||
|
|
|
@ -102,6 +102,17 @@ RSpec.describe "Chat composer", type: :system, js: true do
|
|||
|
||||
expect(find(".chat-composer-input").value).to eq(":grimacing:")
|
||||
end
|
||||
|
||||
it "removes denied emojis from insert emoji picker" do
|
||||
SiteSetting.emoji_deny_list = "monkey|peach"
|
||||
|
||||
chat.visit_channel(channel_1)
|
||||
channel.open_action_menu
|
||||
channel.click_action_button("emoji")
|
||||
|
||||
expect(page).to have_no_selector("[data-emoji='monkey']")
|
||||
expect(page).to have_no_selector("[data-emoji='peach']")
|
||||
end
|
||||
end
|
||||
|
||||
context "when adding an emoji through the autocomplete" do
|
||||
|
@ -117,6 +128,17 @@ RSpec.describe "Chat composer", type: :system, js: true do
|
|||
|
||||
expect(find(".chat-composer-input").value).to eq(":grimacing: ")
|
||||
end
|
||||
|
||||
it "doesn't suggest denied emojis and aliases" do
|
||||
SiteSetting.emoji_deny_list = "peach|poop"
|
||||
chat.visit_channel(channel_1)
|
||||
|
||||
find(".chat-composer-input").fill_in(with: ":peac")
|
||||
expect(page).to have_no_selector(".emoji-shortname", text: "peach")
|
||||
|
||||
find(".chat-composer-input").fill_in(with: ":hank") # alias
|
||||
expect(page).to have_no_selector(".emoji-shortname", text: "poop")
|
||||
end
|
||||
end
|
||||
|
||||
context "when opening emoji picker through more button of the autocomplete" do
|
||||
|
|
|
@ -111,6 +111,18 @@ RSpec.describe "React to message", type: :system, js: true do
|
|||
|
||||
expect(channel).to have_reaction(message_1, reaction_1.emoji)
|
||||
end
|
||||
|
||||
it "removes denied emojis and aliases from reactions" do
|
||||
SiteSetting.emoji_deny_list = "fu"
|
||||
|
||||
sign_in(current_user)
|
||||
chat.visit_channel(category_channel_1)
|
||||
channel.hover_message(message_1)
|
||||
find(".chat-message-actions .react-btn").click
|
||||
|
||||
expect(page).to have_no_css(".chat-emoji-picker [data-emoji=\"fu\"]")
|
||||
expect(page).to have_no_css(".chat-emoji-picker [data-emoji=\"middle_finger\"]")
|
||||
end
|
||||
end
|
||||
|
||||
context "when using frequent reactions" do
|
||||
|
|
|
@ -23,6 +23,11 @@ RSpec.describe Emoji do
|
|||
end
|
||||
|
||||
describe ".lookup_unicode" do
|
||||
before do
|
||||
SiteSetting.emoji_deny_list = "peach"
|
||||
Emoji.clear_cache
|
||||
end
|
||||
|
||||
it "should return the emoji" do
|
||||
expect(Emoji.lookup_unicode("blonde_man")).to eq("👱")
|
||||
end
|
||||
|
@ -34,6 +39,10 @@ RSpec.describe Emoji do
|
|||
it "should return a skin toned emoji" do
|
||||
expect(Emoji.lookup_unicode("blonde_woman:t6")).to eq("👱🏿♀️")
|
||||
end
|
||||
|
||||
it "should not return a fu emoji when emoji is in emoji deny list site setting" do
|
||||
expect(Emoji.lookup_unicode("peach")).not_to eq("🍑")
|
||||
end
|
||||
end
|
||||
|
||||
describe ".url_for" do
|
||||
|
|
|
@ -765,6 +765,9 @@
|
|||
},
|
||||
"whispers_allowed_groups_names" : {
|
||||
"type": "array"
|
||||
},
|
||||
"denied_emojis" : {
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
101
spec/system/emojis/emoji_deny_list_spec.rb
Normal file
101
spec/system/emojis/emoji_deny_list_spec.rb
Normal file
|
@ -0,0 +1,101 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
describe "Emoji deny list", type: :system, js: true do
|
||||
let(:topic_page) { PageObjects::Pages::Topic.new }
|
||||
let(:composer) { PageObjects::Components::Composer.new }
|
||||
let(:emoji_picker) { PageObjects::Components::EmojiPicker.new }
|
||||
fab!(:admin) { Fabricate(:admin) }
|
||||
|
||||
before { sign_in(admin) }
|
||||
|
||||
describe "when editing admin settings" do
|
||||
before { SiteSetting.emoji_deny_list = "" }
|
||||
let(:site_settings_page) { PageObjects::Pages::AdminSettings.new }
|
||||
|
||||
it "should allow admin to update emoji deny list" do
|
||||
site_settings_page.visit_category("posting")
|
||||
|
||||
site_settings_page.select_from_emoji_list("emoji_deny_list", "fu", false)
|
||||
site_settings_page.select_from_emoji_list("emoji_deny_list", "poop")
|
||||
|
||||
expect(site_settings_page.values_in_list("emoji_deny_list")).to eq(%w[fu poop])
|
||||
end
|
||||
end
|
||||
|
||||
describe "when visiting topics" do
|
||||
SiteSetting.emoji_deny_list = "monkey"
|
||||
Emoji.clear_cache
|
||||
|
||||
fab!(:topic) { Fabricate(:topic, title: "Time for :monkey: business") }
|
||||
fab!(:post) { Fabricate(:post, topic: topic, raw: "We have no time to :monkey: around!") }
|
||||
|
||||
it "should remove denied emojis from page title, heading and body" do
|
||||
topic_page.visit_topic(topic)
|
||||
expect(page.title).to eq("Time for business - Discourse")
|
||||
expect(topic_page).to have_topic_title("Time for business")
|
||||
expect(page).not_to have_css(".emoji[title=':monkey:']")
|
||||
end
|
||||
end
|
||||
|
||||
describe "when using composer" do
|
||||
before do
|
||||
SiteSetting.emoji_deny_list = "fu|poop"
|
||||
Emoji.clear_cache && Discourse.request_refresh!
|
||||
end
|
||||
|
||||
fab!(:topic) { Fabricate(:topic) }
|
||||
fab!(:post) { Fabricate(:post, topic: topic) }
|
||||
|
||||
it "should remove denied emojis from emoji picker" do
|
||||
topic_page.visit_topic_and_open_composer(topic)
|
||||
expect(composer).to be_opened
|
||||
|
||||
composer.click_toolbar_button(10)
|
||||
expect(composer.emoji_picker).to be_visible
|
||||
|
||||
expect(emoji_picker.has_emoji?("fu")).to eq(false)
|
||||
end
|
||||
|
||||
it "should not show denied emojis and aliases in emoji autocomplete" do
|
||||
topic_page.visit_topic_and_open_composer(topic)
|
||||
|
||||
composer.type_content(":poop") # shows no results
|
||||
expect(composer).not_to have_emoji_autocomplete
|
||||
|
||||
composer.clear_content
|
||||
|
||||
composer.type_content(":middle") # middle_finger is alias
|
||||
expect(composer).not_to have_emoji_suggestion("fu")
|
||||
end
|
||||
|
||||
it "should not show denied emoji in preview" do
|
||||
topic_page.visit_topic_and_open_composer(topic)
|
||||
|
||||
composer.fill_content(":wave:")
|
||||
expect(composer).to have_emoji_preview("wave")
|
||||
|
||||
composer.clear_content
|
||||
|
||||
composer.fill_content(":fu:")
|
||||
expect(composer).not_to have_emoji_preview("fu")
|
||||
end
|
||||
end
|
||||
|
||||
describe "when using private messages" do
|
||||
before do
|
||||
SiteSetting.emoji_deny_list = "pancakes|monkey"
|
||||
Emoji.clear_cache && Discourse.request_refresh!
|
||||
end
|
||||
|
||||
fab!(:topic) do
|
||||
Fabricate(:private_message_topic, title: "Want to catch up for :pancakes: today?")
|
||||
end
|
||||
fab!(:post) { Fabricate(:post, topic: topic, raw: "Can we use the :monkey: emoji here?") }
|
||||
|
||||
it "should remove denied emojis from message title and body" do
|
||||
topic_page.visit_topic(topic)
|
||||
expect(topic_page).to have_topic_title("Want to catch up for today?")
|
||||
expect(post).not_to have_css(".emoji[title=':monkey:']")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,6 +4,7 @@ module PageObjects
|
|||
module Components
|
||||
class Composer < PageObjects::Components::Base
|
||||
COMPOSER_ID = "#reply-control"
|
||||
AUTOCOMPLETE_MENU = ".autocomplete.ac-emoji"
|
||||
|
||||
def opened?
|
||||
page.has_css?("#{COMPOSER_ID}.open")
|
||||
|
@ -14,6 +15,11 @@ module PageObjects
|
|||
self
|
||||
end
|
||||
|
||||
def click_toolbar_button(number)
|
||||
find(".d-editor-button-bar button:nth-child(#{number})").click
|
||||
self
|
||||
end
|
||||
|
||||
def fill_title(title)
|
||||
find("#{COMPOSER_ID} #reply-title").fill_in(with: title)
|
||||
self
|
||||
|
@ -54,6 +60,26 @@ module PageObjects
|
|||
find("#{COMPOSER_ID} .btn-primary .d-button-label")
|
||||
end
|
||||
|
||||
def emoji_picker
|
||||
find("#{COMPOSER_ID} .emoji-picker")
|
||||
end
|
||||
|
||||
def emoji_autocomplete
|
||||
find(AUTOCOMPLETE_MENU)
|
||||
end
|
||||
|
||||
def has_emoji_autocomplete?
|
||||
has_css?(AUTOCOMPLETE_MENU)
|
||||
end
|
||||
|
||||
def has_emoji_suggestion?(emoji)
|
||||
has_css?("#{AUTOCOMPLETE_MENU} .emoji-shortname", text: emoji)
|
||||
end
|
||||
|
||||
def has_emoji_preview?(emoji)
|
||||
page.has_css?(".d-editor-preview .emoji[title=':#{emoji}:']")
|
||||
end
|
||||
|
||||
def composer_input
|
||||
find("#{COMPOSER_ID} .d-editor .d-editor-input")
|
||||
end
|
||||
|
|
19
spec/system/page_objects/components/emoji_picker.rb
Normal file
19
spec/system/page_objects/components/emoji_picker.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module PageObjects
|
||||
module Components
|
||||
class EmojiPicker < PageObjects::Components::Base
|
||||
def select_emoji(emoji_name)
|
||||
find(".emoji-picker .emoji[title='#{emoji_name}']").click
|
||||
end
|
||||
|
||||
def search_emoji(emoji_name)
|
||||
find(".emoji-picker .search input").fill_in(with: emoji_name)
|
||||
end
|
||||
|
||||
def has_emoji?(emoji_name)
|
||||
page.has_css?(".emoji-picker .emoji[title='#{emoji_name}']")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -8,11 +8,32 @@ module PageObjects
|
|||
self
|
||||
end
|
||||
|
||||
def visit_category(category)
|
||||
page.visit("/admin/site_settings/category/#{category}")
|
||||
self
|
||||
end
|
||||
|
||||
def toggle_setting(setting_name, text = "")
|
||||
setting = find(".admin-detail .row.setting[data-setting='#{setting_name}']")
|
||||
setting.find(".setting-value span", text: text).click
|
||||
setting.find(".setting-controls button.ok").click
|
||||
end
|
||||
|
||||
def select_from_emoji_list(setting_name, text = "", save_changes = true)
|
||||
setting = find(".admin-detail .row.setting[data-setting='#{setting_name}']")
|
||||
setting.find(".setting-value .value-list > .value button").click
|
||||
setting.find(".setting-value .emoji-picker .emoji[title='#{text}']").click
|
||||
setting.find(".setting-controls button.ok").click if save_changes
|
||||
end
|
||||
|
||||
def values_in_list(setting_name)
|
||||
vals = []
|
||||
setting = find(".admin-detail .row.setting[data-setting='#{setting_name}']")
|
||||
setting
|
||||
.all(:css, ".setting-value .values .value .value-input span")
|
||||
.map { |e| vals << e.text }
|
||||
vals
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,6 +30,10 @@ module PageObjects
|
|||
self
|
||||
end
|
||||
|
||||
def has_topic_title?(text)
|
||||
has_css?("h1 .fancy-title", text: text)
|
||||
end
|
||||
|
||||
def has_post_content?(post)
|
||||
post_by_number(post).has_content? post.raw
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue
Block a user