mirror of
https://github.com/discourse/discourse.git
synced 2026-08-11 02:59:07 +08:00
Introduces a new composer redesign behind the `enable_composer_redesign` upcoming change, which is `conceptual` for now and thus not shown to admins. This change modernises the composer UI further, moving the toolbar to the bottom and trying an experimental approach to create more space for writing on mobile **Desktop** <img width="781" height="685" alt="image" src="https://github.com/user-attachments/assets/461e39f1-4409-4f8b-96b9-78a0c7a6877a" /> <img width="1490" height="681" alt="image" src="https://github.com/user-attachments/assets/c1422bc6-c0e2-4d75-8110-d24c507bbc4c" /> **Mobile** <img width="373" height="858" alt="image" src="https://github.com/user-attachments/assets/d3a3048b-a532-4151-b7f4-efac3b7ebe25" /> <img width="376" height="854" alt="image" src="https://github.com/user-attachments/assets/2a42503a-de55-4684-8edd-789c54eccd69" /> --------- Co-authored-by: Martin Brennan <martin@discourse.org>
479 lines
12 KiB
Ruby
Vendored
479 lines
12 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
module PageObjects
|
|
module Components
|
|
class Composer < PageObjects::Components::Base
|
|
COMPOSER_ID = "#reply-control"
|
|
AUTOCOMPLETE_MENU = ".autocomplete.ac-emoji"
|
|
HASHTAG_MENU = ".autocomplete.hashtag-autocomplete"
|
|
MENTION_MENU = ".autocomplete.ac-user"
|
|
RICH_EDITOR = ".d-editor-input.ProseMirror"
|
|
POST_LANGUAGE_SELECTOR = ".post-language-selector-trigger"
|
|
|
|
def initialize(composer_id = COMPOSER_ID)
|
|
@composer_id = composer_id
|
|
end
|
|
|
|
def rich_editor
|
|
find(RICH_EDITOR)
|
|
end
|
|
|
|
def has_rich_editor?
|
|
page.has_css?(RICH_EDITOR)
|
|
end
|
|
|
|
def has_no_rich_editor?
|
|
page.has_no_css?(RICH_EDITOR)
|
|
end
|
|
|
|
def opened?
|
|
page.has_css?("#{@composer_id}.open")
|
|
end
|
|
|
|
def closed?
|
|
page.has_css?("#{@composer_id}.closed", visible: :all)
|
|
end
|
|
|
|
def minimized?
|
|
page.has_css?("#{@composer_id}.draft")
|
|
end
|
|
|
|
def open_composer_actions
|
|
find(".composer-action-title .btn").click
|
|
self
|
|
end
|
|
|
|
def open_composer_actions_new
|
|
find(".composer-actions-trigger").click
|
|
self
|
|
end
|
|
|
|
def click_toolbar_button(button_class)
|
|
find(".d-editor-button-bar button.#{button_class}").click
|
|
self
|
|
end
|
|
|
|
def heading_menu
|
|
PageObjects::Components::DMenu.new(find(".d-editor-button-bar button.heading"))
|
|
end
|
|
|
|
def list_menu
|
|
PageObjects::Components::DMenu.new(find(".d-editor-button-bar button.list"))
|
|
end
|
|
|
|
def focus
|
|
find(composer_input_selector).click
|
|
self
|
|
end
|
|
|
|
def fill_title(title)
|
|
find("#{@composer_id} #reply-title").fill_in(with: title)
|
|
self
|
|
end
|
|
|
|
def has_input_title?(value)
|
|
has_field?("reply-title", with: value)
|
|
end
|
|
|
|
# Methods for when the enable_composer_redesign upcoming change is enabled.
|
|
|
|
def has_footer_toolbar?
|
|
page.has_css?("#{@composer_id} .composer-footer__toolbar .d-editor-button-bar")
|
|
end
|
|
|
|
def has_title_below_category_row?
|
|
page.has_css?("#{@composer_id} .title-and-category + .title-input")
|
|
end
|
|
|
|
def has_pm_recipients_in_category_row?
|
|
page.has_css?("#{@composer_id} .title-and-category .user-selector")
|
|
end
|
|
|
|
def has_toggle_toolbar_button?
|
|
page.has_css?("#{@composer_id} .toggle-toolbar")
|
|
end
|
|
|
|
# Methods for when the enable_composer_redesign upcoming change is disabled.
|
|
|
|
def has_no_footer_toolbar?
|
|
page.has_no_css?("#{@composer_id} .composer-footer")
|
|
end
|
|
|
|
def has_inline_toolbar?
|
|
page.has_css?("#{@composer_id} .d-editor-textarea-wrapper .d-editor-button-bar")
|
|
end
|
|
|
|
def has_title_in_category_row?
|
|
page.has_css?("#{@composer_id} .title-and-category .title-input")
|
|
end
|
|
|
|
def toggle_toolbar
|
|
find("#{@composer_id} .toggle-toolbar").click
|
|
self
|
|
end
|
|
|
|
def has_visible_toolbar?
|
|
page.has_css?("#{@composer_id} .d-editor-button-bar")
|
|
end
|
|
|
|
def has_no_visible_toolbar?
|
|
page.has_no_css?("#{@composer_id} .d-editor-button-bar")
|
|
end
|
|
|
|
def fill_content(content)
|
|
find("#{@composer_id} .d-editor .d-editor-input").fill_in(with: content)
|
|
self
|
|
end
|
|
|
|
def minimize
|
|
find("#{@composer_id} .toggle-minimize").click
|
|
self
|
|
end
|
|
|
|
def append_content(content)
|
|
current_content = composer_input.value
|
|
composer_input.set(current_content + content)
|
|
self
|
|
end
|
|
|
|
def fill_form_template_field(field, content)
|
|
form_template_field(field).fill_in(with: content)
|
|
self
|
|
end
|
|
|
|
def type_content(content)
|
|
composer_input.send_keys(content)
|
|
self
|
|
end
|
|
|
|
def clear_content
|
|
fill_content("")
|
|
end
|
|
|
|
def has_content?(content)
|
|
composer_input.value == content
|
|
end
|
|
|
|
def has_value?(value)
|
|
within(@composer_id) do
|
|
if value.nil?
|
|
has_no_field?(class: "d-editor-input")
|
|
else
|
|
has_field?(class: "d-editor-input", with: value)
|
|
end
|
|
end
|
|
end
|
|
|
|
def has_popup_content?(content)
|
|
composer_popup.has_content?(content)
|
|
end
|
|
|
|
def has_no_action?(action)
|
|
!actions.include?(action)
|
|
end
|
|
|
|
def has_no_action_id?(action_id)
|
|
has_no_css?(".composer-actions-dropdown [data-action-id='#{action_id}']")
|
|
end
|
|
|
|
def select_action(action)
|
|
find(action(action)).click
|
|
self
|
|
end
|
|
|
|
def select_action_by_id(action_id)
|
|
find(".composer-actions-dropdown [data-action-id='#{action_id}']").click
|
|
self
|
|
end
|
|
|
|
def reply_button_focused?
|
|
page.has_css?("#{@composer_id} .btn-primary:focus")
|
|
end
|
|
|
|
def create
|
|
find("#{@composer_id} .btn-primary").click
|
|
end
|
|
|
|
def action(action_title)
|
|
".composer-action-title .select-kit-collection li[title='#{action_title}']"
|
|
end
|
|
|
|
def actions
|
|
all(".composer-action-title .select-kit-collection li").map { |el| el[:title] }
|
|
end
|
|
|
|
def button_label
|
|
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 category_chooser
|
|
Components::SelectKit.new(".category-chooser")
|
|
end
|
|
|
|
def locale
|
|
find("#{@composer_id} .d-editor-button-bar #{POST_LANGUAGE_SELECTOR}")
|
|
end
|
|
|
|
def set_locale(locale)
|
|
click_toolbar_button("post-language-selector-trigger")
|
|
within("#d-menu-portals", visible: false) { find("button", text: locale).click }
|
|
end
|
|
|
|
def switch_category(category_name)
|
|
category_chooser.expand
|
|
category_chooser.select_row_by_name(category_name)
|
|
end
|
|
|
|
def preview
|
|
find("#{@composer_id} .d-editor-preview-wrapper")
|
|
end
|
|
|
|
def has_discard_draft_modal?
|
|
page.has_css?(".discard-draft-modal")
|
|
end
|
|
|
|
def has_hashtag_autocomplete?
|
|
has_css?(HASHTAG_MENU)
|
|
end
|
|
|
|
def has_mention_autocomplete?
|
|
has_css?(MENTION_MENU)
|
|
end
|
|
|
|
def mention_menu_autocomplete_username_list
|
|
find(MENTION_MENU).all("a").map { |a| a.text }
|
|
end
|
|
|
|
def has_emoji_autocomplete?
|
|
has_css?(AUTOCOMPLETE_MENU)
|
|
end
|
|
|
|
def has_no_emoji_autocomplete?
|
|
has_no_css?(AUTOCOMPLETE_MENU)
|
|
end
|
|
|
|
def has_emoji_autocomplete_selected?(index)
|
|
has_css?("#{AUTOCOMPLETE_MENU} ul li:nth-child(#{index}) a.selected")
|
|
end
|
|
|
|
EMOJI_SUGGESTION_SELECTOR = "#{AUTOCOMPLETE_MENU} .emoji-shortname"
|
|
|
|
def has_emoji_suggestion?(emoji)
|
|
has_css?(EMOJI_SUGGESTION_SELECTOR, text: emoji)
|
|
end
|
|
|
|
def has_no_emoji_suggestion?(emoji)
|
|
has_no_css?(EMOJI_SUGGESTION_SELECTOR, text: emoji)
|
|
end
|
|
|
|
def has_emoji_preview?(emoji)
|
|
page.has_css?(emoji_preview_selector(emoji))
|
|
end
|
|
|
|
def has_no_emoji_preview?(emoji)
|
|
page.has_no_css?(emoji_preview_selector(emoji))
|
|
end
|
|
|
|
def composer_input_selector
|
|
"#{@composer_id} .d-editor-input"
|
|
end
|
|
|
|
def has_no_composer_input?
|
|
page.has_no_css?(composer_input_selector)
|
|
end
|
|
|
|
def has_composer_input?
|
|
page.has_css?(composer_input_selector)
|
|
end
|
|
|
|
def has_composer_preview?
|
|
page.has_css?("#{@composer_id} .d-editor-preview-wrapper")
|
|
end
|
|
|
|
def has_no_composer_preview?
|
|
page.has_no_css?("#{@composer_id} .d-editor-preview-wrapper")
|
|
end
|
|
|
|
def has_composer_preview_toggle?
|
|
page.has_css?("#{@composer_id} .toggle-preview")
|
|
end
|
|
|
|
def has_no_composer_preview_toggle?
|
|
page.has_no_css?("#{@composer_id} .toggle-preview")
|
|
end
|
|
|
|
def has_form_template?
|
|
page.has_css?(".form-template-form__wrapper")
|
|
end
|
|
|
|
def has_form_template_field?(field)
|
|
page.has_css?(".form-template-field[data-field-type='#{field}']")
|
|
end
|
|
|
|
def has_form_template_field_required_indicator?(field)
|
|
page.has_css?(
|
|
".form-template-field[data-field-type='#{field}'] .form-template-field__required-indicator",
|
|
)
|
|
end
|
|
|
|
FORM_TEMPLATE_CHOOSER_SELECTOR = ".composer-select-form-template"
|
|
|
|
def has_no_form_template_chooser?
|
|
page.has_no_css?(FORM_TEMPLATE_CHOOSER_SELECTOR)
|
|
end
|
|
|
|
def has_form_template_chooser?
|
|
page.has_css?(FORM_TEMPLATE_CHOOSER_SELECTOR)
|
|
end
|
|
|
|
def has_form_template_field_error?(error)
|
|
page.has_css?(".form-template-field__error", text: error, visible: :all)
|
|
end
|
|
|
|
def has_no_form_template_field_error?(error)
|
|
page.has_no_css?(".form-template-field__error", text: error, visible: :all)
|
|
end
|
|
|
|
def has_form_template_field_label?(label)
|
|
page.has_css?(".form-template-field__label", text: label)
|
|
end
|
|
|
|
def has_form_template_field_description?(description)
|
|
page.has_css?(".form-template-field__description", text: description)
|
|
end
|
|
|
|
def has_post_error?(error)
|
|
page.has_css?(".popup-tip", text: error, visible: all)
|
|
end
|
|
|
|
def has_no_post_error?(error)
|
|
page.has_no_css?(".popup-tip", text: error, visible: all)
|
|
end
|
|
|
|
def composer_input
|
|
find("#{@composer_id} .d-editor-input")
|
|
end
|
|
|
|
def composer_popup
|
|
find("#{@composer_id} .composer-popup")
|
|
end
|
|
|
|
def form_template_field(field)
|
|
find(".form-template-field[data-field-type='#{field}']")
|
|
end
|
|
|
|
def move_cursor_after(text)
|
|
execute_script(<<~JS, text)
|
|
const text = arguments[0];
|
|
const composer = document.querySelector("#{@composer_id} .d-editor-input");
|
|
const index = composer.value.indexOf(text);
|
|
const position = index + text.length;
|
|
|
|
composer.focus();
|
|
composer.setSelectionRange(position, position);
|
|
JS
|
|
end
|
|
|
|
def select_all
|
|
find(composer_input_selector).send_keys([PLATFORM_KEY_MODIFIER, "a"])
|
|
end
|
|
|
|
def select_range(start_index, length)
|
|
execute_script(<<~JS, text)
|
|
const composer = document.querySelector("#{@composer_id} .d-editor-input");
|
|
composer.focus();
|
|
composer.setSelectionRange(#{start_index}, #{length});
|
|
JS
|
|
end
|
|
|
|
def select_range_rich_editor(start_index, length)
|
|
focus
|
|
select_text_range(RICH_EDITOR, start_index, length)
|
|
end
|
|
|
|
def submit
|
|
find("#{@composer_id} .save-or-cancel .create").click
|
|
end
|
|
|
|
def discard
|
|
find("#{@composer_id} .discard-button").click
|
|
end
|
|
|
|
def close
|
|
find("#{@composer_id} .toggle-save-and-close").click
|
|
end
|
|
|
|
def has_no_in_progress_uploads?
|
|
find("#{@composer_id}").has_no_css?("#file-uploading")
|
|
end
|
|
|
|
def has_in_progress_uploads?
|
|
find("#{@composer_id}").has_css?("#file-uploading")
|
|
end
|
|
|
|
def select_pm_user(username)
|
|
select_kit = PageObjects::Components::SelectKit.new("#private-message-users")
|
|
select_kit.expand
|
|
select_kit.search(username)
|
|
select_kit.select_row_by_value(username)
|
|
select_kit.collapse
|
|
end
|
|
|
|
def has_rich_editor_active?
|
|
find("#{@composer_id}").has_css?(".d-editor-container.--rich-editor-enabled")
|
|
end
|
|
|
|
def has_no_rich_editor_active?
|
|
find("#{@composer_id}").has_css?(".d-editor-container.--markdown-editor-enabled")
|
|
end
|
|
|
|
def has_markdown_editor_active?
|
|
has_no_rich_editor_active?
|
|
end
|
|
|
|
def toggle_rich_editor
|
|
rich = page.find(".composer-toggle-switch")["data-rich-editor"]
|
|
|
|
editor_toggle_switch.click
|
|
|
|
if rich
|
|
has_no_rich_editor_active?
|
|
else
|
|
has_rich_editor_active?
|
|
end
|
|
|
|
self
|
|
end
|
|
|
|
def has_toggle_switch?
|
|
page.has_css?("#{@composer_id} .composer-toggle-switch")
|
|
end
|
|
|
|
def has_no_toggle_switch?
|
|
page.has_no_css?("#{@composer_id} .composer-toggle-switch")
|
|
end
|
|
|
|
def editor_toggle_switch
|
|
find("#{@composer_id} .composer-toggle-switch")
|
|
end
|
|
|
|
def image_grid
|
|
Components::ComposerImageGrid.new(rich_editor)
|
|
end
|
|
|
|
private
|
|
|
|
def emoji_preview_selector(emoji)
|
|
".d-editor-preview .emoji[title=':#{emoji}:']"
|
|
end
|
|
end
|
|
end
|
|
end
|