UX: deletes a message when editing to blank (#21785)

Editing a message to an empty string and sending it, will delete it.

This commit also refactors a lot of channel/thread composer shortcuts specs.

---

This commit also includes various spec fixes which have been flakey while finishing this pull request.
This commit is contained in:
Joffrey JAFFEUX 2023-05-30 18:15:34 +02:00 committed by GitHub
parent c3d51e9c0a
commit 67102f7e4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 347 additions and 299 deletions

View File

@ -20,6 +20,7 @@ import { isEmpty, isPresent } from "@ember/utils";
import { Promise } from "rsvp";
import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
import User from "discourse/models/user";
import ChatMessageInteractor from "discourse/plugins/chat/discourse/lib/chat-message-interactor";
export default class ChatComposer extends Component {
@service capabilities;
@ -78,11 +79,6 @@ export default class ChatComposer extends Component {
);
}
@action
sendMessage() {
this.args.onSendMessage(this.currentMessage);
}
@action
persistDraft() {}
@ -140,7 +136,9 @@ export default class ChatComposer extends Component {
get sendEnabled() {
return (
this.hasContent && !this.pane.sending && !this.inProgressUploadsCount > 0
(this.hasContent || this.currentMessage?.editing) &&
!this.pane.sending &&
!this.inProgressUploadsCount > 0
);
}
@ -248,6 +246,19 @@ export default class ChatComposer extends Component {
return;
}
if (
this.currentMessage.editing &&
this.currentMessage.message.length === 0
) {
new ChatMessageInteractor(
getOwner(this),
this.currentMessage,
this.context
).delete();
this.reset(this.args.channel, this.args.thread);
return;
}
if (this.site.mobileView) {
// prevents to hide the keyboard after sending a message
// we use direct DOM manipulation here because textareaInteractor.focus()
@ -349,11 +360,11 @@ export default class ChatComposer extends Component {
!this.hasContent &&
!this.currentMessage.editing
) {
if (event.shiftKey) {
this.composer.replyTo(this.pane?.lastMessage);
if (event.shiftKey && this.lastMessage?.replyable) {
this.composer.replyTo(this.lastMessage);
} else {
const editableMessage = this.pane?.lastCurrentUserMessage;
if (editableMessage) {
const editableMessage = this.lastUserMessage(this.currentUser);
if (editableMessage?.editable) {
this.composer.editMessage(editableMessage);
}
}

View File

@ -13,7 +13,7 @@
{{did-update this.decorateCookedMessage @message.version}}
{{on "touchmove" this.handleTouchMove passive=true}}
{{on "touchstart" this.handleTouchStart passive=true}}
{{on "touchend" this.handleTouchEnd passive=true}}
{{on "touchend" this.handleTouchEnd}}
{{on "mouseenter" this.onMouseEnter passive=true}}
{{on "mouseleave" this.onMouseLeave passive=true}}
{{on "mousemove" this.onMouseMove passive=true}}
@ -23,6 +23,7 @@
(if @message.highlighted "highlighted")
(if (eq @message.user.id this.currentUser.id) "is-by-current-user")
(if @message.staged "is-staged" "is-persisted")
(if @message.deletedAt "is-deleted")
}}
data-id={{@message.id}}
data-thread-id={{@message.thread.id}}
@ -44,7 +45,7 @@
{{/if}}
{{#if this.deletedAndCollapsed}}
<div class="chat-message-deleted">
<div class="chat-message-text chat-message-deleted">
<DButton
@class="btn-flat chat-message-expand"
@action={{this.expand}}

View File

@ -274,6 +274,7 @@ export default class ChatMessage extends Component {
this._handleLongPress();
}
this._touchStartAt = Date.now();
this._isPressingHandler = discourseLater(this._handleLongPress, 500);
}
@ -288,6 +289,11 @@ export default class ChatMessage extends Component {
handleTouchEnd(event) {
event.stopPropagation();
// this is to prevent the long press to register as a click
if (Date.now() - this._touchStartAt >= 500) {
event.preventDefault();
}
cancel(this._isPressingHandler);
}

View File

@ -22,7 +22,7 @@
{{on "scroll" this.computeScrollState passive=true}}
>
<div
class="chat-thread__messages chat-messages-container"
class="chat-thread__messages chat-messages-scroll chat-messages-container"
{{chat/on-resize this.didResizePane (hash delay=10)}}
>
{{#each this.thread.messages key="id" as |message|}}

View File

@ -40,6 +40,14 @@ export default class ChatComposerChannel extends ChatComposer {
this.chatApi.saveDraft(channelId, jsonDraft);
}
get lastMessage() {
return this.args.channel.lastMessage;
}
lastUserMessage(user) {
return this.args.channel.lastUserMessage(user);
}
get placeholder() {
if (!this.args.channel.canModifyMessages(this.currentUser)) {
return I18n.t(

View File

@ -21,6 +21,14 @@ export default class ChatComposerThread extends ChatComposer {
return I18n.t("chat.placeholder_thread");
}
get lastMessage() {
return this.args.thread.lastMessage;
}
lastUserMessage(user) {
return this.args.thread.lastUserMessage(user);
}
@action
onKeyDown(event) {
if (event.key === "Escape") {

View File

@ -48,4 +48,14 @@ export default class ChatMessagesManager {
findIndexOfMessage(id) {
return this.messages.findIndex((m) => m.id === id);
}
findLastMessage() {
return this.messages.findLast((message) => !message.deletedAt);
}
findLastUserMessage(user) {
return this.messages.findLast(
(message) => message.user.id === user.id && !message.deletedAt
);
}
}

View File

@ -169,6 +169,14 @@ export default class ChatChannel {
this.messagesManager.removeMessage(message);
}
get lastMessage() {
return this.messagesManager.findLastMessage();
}
lastUserMessage(user) {
return this.messagesManager.findLastUserMessage(user);
}
get messages() {
return this.messagesManager.messages;
}

View File

@ -124,6 +124,14 @@ export default class ChatMessage {
return message;
}
get replyable() {
return !this.staged && !this.error;
}
get editable() {
return !this.staged && !this.error;
}
get cooked() {
return this._cooked;
}

View File

@ -73,6 +73,14 @@ export default class ChatThread {
this.messagesManager.addMessages([message]);
}
get lastMessage() {
return this.messagesManager.findLastMessage();
}
lastUserMessage(user) {
return this.messagesManager.findLastUserMessage(user);
}
get routeModels() {
return [...this.channel.routeModels, this.id];
}

View File

@ -40,22 +40,6 @@ export default class ChatChannelPane extends Service {
this.selectingMessages = true;
}
get lastCurrentUserMessage() {
const lastCurrentUserMessage = this.chat.activeChannel.messages.findLast(
(message) => message.user.id === this.currentUser.id
);
if (!lastCurrentUserMessage) {
return;
}
if (lastCurrentUserMessage.staged || lastCurrentUserMessage.error) {
return;
}
return lastCurrentUserMessage;
}
get lastMessage() {
return this.chat.activeChannel.messages.lastObject;
}

View File

@ -10,4 +10,9 @@ export default class ChatChannelThreadComposer extends ChatComposer {
thread,
});
}
@action
replyTo() {
this.chat.activeMessage = null;
}
}

View File

@ -24,21 +24,4 @@ export default class ChatChannelThreadPane extends ChatChannelPane {
get composerService() {
return this.chatChannelThreadComposer;
}
get lastCurrentUserMessage() {
const lastCurrentUserMessage =
this.chat.activeChannel.activeThread.messages.findLast(
(message) => message.user.id === this.currentUser.id
);
if (!lastCurrentUserMessage) {
return;
}
if (lastCurrentUserMessage.staged || lastCurrentUserMessage.error) {
return;
}
return lastCurrentUserMessage;
}
}

View File

@ -3,8 +3,8 @@
RSpec.describe "Bookmark message", type: :system, js: true do
fab!(:current_user) { Fabricate(:user) }
let(:chat) { PageObjects::Pages::Chat.new }
let(:channel) { PageObjects::Pages::ChatChannel.new }
let(:chat_page) { PageObjects::Pages::Chat.new }
let(:channel_page) { PageObjects::Pages::ChatChannel.new }
let(:bookmark_modal) { PageObjects::Modals::Bookmark.new }
fab!(:category_channel_1) { Fabricate(:category_channel) }
@ -18,13 +18,13 @@ RSpec.describe "Bookmark message", type: :system, js: true do
context "when desktop" do
it "allows to bookmark a message" do
chat.visit_channel(category_channel_1)
channel.bookmark_message(message_1)
chat_page.visit_channel(category_channel_1)
channel_page.bookmark_message(message_1)
bookmark_modal.fill_name("Check this out later")
bookmark_modal.select_preset_reminder(:next_month)
expect(channel).to have_bookmarked_message(message_1)
expect(channel_page).to have_bookmarked_message(message_1)
end
context "when the user has a bookmark auto_delete_preference" do
@ -35,11 +35,11 @@ RSpec.describe "Bookmark message", type: :system, js: true do
end
it "is respected when the user creates a new bookmark" do
chat.visit_channel(category_channel_1)
channel.bookmark_message(message_1)
chat_page.visit_channel(category_channel_1)
channel_page.bookmark_message(message_1)
bookmark_modal.save
expect(channel).to have_bookmarked_message(message_1)
expect(channel_page).to have_bookmarked_message(message_1)
bookmark = Bookmark.find_by(bookmarkable: message_1, user: current_user)
expect(bookmark.auto_delete_preference).to eq(
@ -51,20 +51,13 @@ RSpec.describe "Bookmark message", type: :system, js: true do
context "when mobile", mobile: true do
it "allows to bookmark a message" do
chat.visit_channel(category_channel_1)
i = 0.5
try_until_success(timeout: 20) do
channel.message_by_id(message_1.id).click(delay: i)
first(".bookmark-btn")
i += 0.1
end
find(".bookmark-btn").click
chat_page.visit_channel(category_channel_1)
channel_page.bookmark_message(message_1)
bookmark_modal.fill_name("Check this out later")
bookmark_modal.select_preset_reminder(:next_month)
expect(channel).to have_bookmarked_message(message_1)
expect(channel_page).to have_bookmarked_message(message_1)
end
end
end

View File

@ -0,0 +1,128 @@
# frozen_string_literal: true
RSpec.describe "Chat | composer | shortcuts | channel", type: :system, js: true do
fab!(:channel_1) { Fabricate(:chat_channel) }
fab!(:current_user) { Fabricate(:admin) }
let(:chat) { PageObjects::Pages::Chat.new }
let(:channel_page) { PageObjects::Pages::ChatChannel.new }
let(:thread_page) { PageObjects::Pages::ChatThread.new }
let(:key_modifier) { RUBY_PLATFORM =~ /darwin/i ? :meta : :control }
before do
chat_system_bootstrap
channel_1.add(current_user)
sign_in(current_user)
end
context "when using meta + b" do
it "adds bold text" do
chat.visit_channel(channel_1)
channel_page.composer.bold_text_shortcut
expect(channel_page.composer.value).to eq("**strong text**")
end
end
context "when using meta + i" do
it "adds italic text" do
chat.visit_channel(channel_1)
channel_page.composer.emphasized_text_shortcut
expect(channel_page.composer.value).to eq("_emphasized text_")
end
end
context "when using meta + e" do
it "adds preformatted text" do
chat.visit_channel(channel_1)
channel_page.composer.indented_text_shortcut
expect(channel_page.composer.value).to eq("`indent preformatted text by 4 spaces`")
end
end
context "when the thread panel is also open" do
fab!(:user_2) { Fabricate(:user) }
fab!(:thread) do
chat_thread_chain_bootstrap(
channel: channel_1,
users: [current_user, user_2],
messages_count: 2,
)
end
before do
SiteSetting.enable_experimental_chat_threaded_discussions = true
channel_1.update!(threading_enabled: true)
end
it "directs the shortcut to the focused composer" do
chat.visit_channel(channel_1)
channel_page.message_thread_indicator(thread.original_message).click
channel_page.composer.emphasized_text_shortcut
expect(channel_page.composer.value).to eq("_emphasized text_")
expect(thread_page.composer.value).to eq("")
channel_page.composer.fill_in(with: "")
thread_page.composer.fill_in(with: "")
thread_page.composer.emphasized_text_shortcut
expect(channel_page.composer.value).to eq("")
expect(thread_page.composer.value).to eq("_emphasized text_")
end
end
context "when using ArrowUp" do
fab!(:message_1) { Fabricate(:chat_message, chat_channel: channel_1, user: current_user) }
fab!(:message_2) { Fabricate(:chat_message, chat_channel: channel_1) }
it "edits last editable message" do
chat.visit_channel(channel_1)
channel_page.composer.edit_last_message_shortcut
expect(channel_page.composer.message_details).to be_editing(message_1)
end
context "when last message is staged" do
it "does not edit a message" do
chat.visit_channel(channel_1)
page.driver.browser.network_conditions = { offline: true }
channel_page.send_message
channel_page.composer.edit_last_message_shortcut
expect(channel_page.composer.message_details).to have_no_message
ensure
page.driver.browser.network_conditions = { offline: false }
end
end
context "when last message is deleted" do
before { message_1.trash! }
it "does not edit a message" do
chat.visit_channel(channel_1)
channel_page.composer.edit_last_message_shortcut
expect(channel_page.composer.message_details).to have_no_message
end
end
context "with shift" do
it "starts replying to the last message" do
chat.visit_channel(channel_1)
channel_page.composer.reply_to_last_message_shortcut
expect(channel_page.composer.message_details).to be_replying_to(message_2)
end
end
end
end

View File

@ -2,8 +2,9 @@
RSpec.describe "Chat | composer | shortcuts | thread", type: :system, js: true do
fab!(:channel_1) { Fabricate(:chat_channel, threading_enabled: true) }
fab!(:current_user) { Fabricate(:user) }
fab!(:current_user) { Fabricate(:admin) }
fab!(:message_1) { Fabricate(:chat_message, chat_channel: channel_1) }
let(:chat_page) { PageObjects::Pages::Chat.new }
let(:thread_page) { PageObjects::Pages::ChatThread.new }
@ -15,36 +16,44 @@ RSpec.describe "Chat | composer | shortcuts | thread", type: :system, js: true d
end
describe "ArrowUp" do
let(:thread_1) { message_1.reload.thread }
fab!(:thread_1) { Fabricate(:chat_message, user: current_user, in_reply_to: message_1).thread }
let(:last_thread_message) { thread_1.replies.last }
context "when there are editable messages" do
let(:last_thread_message) { thread_1.replies.last }
before do
thread_message_1 = Fabricate(:chat_message, user: current_user, in_reply_to: message_1)
Fabricate(:chat_message, user: current_user, thread: thread_message_1.reload.thread)
end
before { Fabricate(:chat_message, user: current_user, thread: thread_1) }
it "starts editing the last editable message" do
chat_page.visit_thread(thread_1)
thread_page.composer.edit_last_message_shortcut
expect(thread_page.composer_message_details).to have_message(last_thread_message)
expect(thread_page.composer_message_details).to have_message(id: last_thread_message.id)
expect(thread_page.composer.value).to eq(last_thread_message.message)
end
end
context "when there are no editable messages" do
before { Fabricate(:chat_message, in_reply_to: message_1) }
context "when last message is staged" do
it "does not edit a message" do
chat_page.visit_thread(thread_1)
page.driver.browser.network_conditions = { offline: true }
thread_page.send_message
thread_page.composer.edit_last_message_shortcut
it "does nothing" do
expect(thread_page.composer.message_details).to have_no_message
ensure
page.driver.browser.network_conditions = { offline: false }
end
end
context "when last message is deleted" do
before { last_thread_message.trash! }
it "does not edit a message" do
chat_page.visit_thread(thread_1)
thread_page.composer.edit_last_message_shortcut
expect(thread_page.composer_message_details).to have_no_message
expect(thread_page.composer.value).to be_blank
expect(thread_page.composer.message_details).to have_no_message
end
end
end

View File

@ -36,8 +36,7 @@ RSpec.describe "Chat channel", type: :system, js: true do
)
sidebar_page.open_channel(channel_1)
expect(channel_page).to have_no_loading_skeleton
expect(channel_page.messages).to have_x_messages(51)
expect(channel_page.messages).to have_message(id: message_1.id)
end
end

View File

@ -3,19 +3,18 @@
RSpec.describe "Chat composer", type: :system, js: true do
fab!(:current_user) { Fabricate(:user) }
fab!(:channel_1) { Fabricate(:chat_channel) }
fab!(:message_1) { Fabricate(:chat_message, chat_channel: channel_1) }
fab!(:message_1) { Fabricate(:chat_message, user: current_user, chat_channel: channel_1) }
let(:chat_page) { PageObjects::Pages::Chat.new }
let(:channel_page) { PageObjects::Pages::ChatChannel.new }
before { chat_system_bootstrap }
before do
chat_system_bootstrap
channel_1.add(current_user)
sign_in(current_user)
end
context "when replying to a message" do
before do
channel_1.add(current_user)
sign_in(current_user)
end
it "adds the reply indicator to the composer" do
chat_page.visit_channel(channel_1)
channel_page.reply_to(message_1)
@ -43,11 +42,6 @@ RSpec.describe "Chat composer", type: :system, js: true do
context "when editing a message" do
fab!(:message_2) { Fabricate(:chat_message, chat_channel: channel_1, user: current_user) }
before do
channel_1.add(current_user)
sign_in(current_user)
end
it "adds the edit indicator" do
chat_page.visit_channel(channel_1)
channel_page.edit_message(message_2)
@ -95,11 +89,6 @@ RSpec.describe "Chat composer", type: :system, js: true do
end
context "when adding an emoji through the picker" do
before do
channel_1.add(current_user)
sign_in(current_user)
end
xit "adds the emoji to the composer" do
chat_page.visit_channel(channel_1)
channel_page.open_action_menu
@ -121,11 +110,6 @@ RSpec.describe "Chat composer", type: :system, js: true do
end
context "when adding an emoji through the autocomplete" do
before do
channel_1.add(current_user)
sign_in(current_user)
end
it "adds the emoji to the composer" do
chat_page.visit_channel(channel_1)
find(".chat-composer__input").fill_in(with: ":gri")
@ -147,11 +131,6 @@ RSpec.describe "Chat composer", type: :system, js: true do
end
context "when opening emoji picker through more button of the autocomplete" do
before do
channel_1.add(current_user)
sign_in(current_user)
end
xit "prefills the emoji picker filter input" do
chat_page.visit_channel(channel_1)
find(".chat-composer__input").fill_in(with: ":gri")
@ -173,11 +152,6 @@ RSpec.describe "Chat composer", type: :system, js: true do
end
context "when typing on keyboard" do
before do
channel_1.add(current_user)
sign_in(current_user)
end
it "propagates keys to composer" do
chat_page.visit_channel(channel_1)
@ -196,11 +170,6 @@ RSpec.describe "Chat composer", type: :system, js: true do
end
context "when pasting link over selected text" do
before do
channel_1.add(current_user)
sign_in(current_user)
end
it "outputs a markdown link" do
modifier = /darwin/i =~ RbConfig::CONFIG["host_os"] ? :command : :control
select_text = <<-JS
@ -226,12 +195,19 @@ RSpec.describe "Chat composer", type: :system, js: true do
end
end
context "when posting a message with length equal to minimum length" do
before do
SiteSetting.chat_minimum_message_length = 1
channel_1.add(current_user)
sign_in(current_user)
context "when editing a message with no length" do
it "deletes the message" do
chat_page.visit_channel(channel_1)
channel_page.composer.edit_last_message_shortcut
channel_page.composer.fill_in(with: "")
channel_page.click_send_message
expect(channel_page.messages).to have_message(deleted: 1)
end
end
context "when posting a message with length equal to minimum length" do
before { SiteSetting.chat_minimum_message_length = 1 }
it "works" do
chat_page.visit_channel(channel_1)
@ -243,11 +219,7 @@ RSpec.describe "Chat composer", type: :system, js: true do
end
context "when posting a message with length superior to minimum length" do
before do
SiteSetting.chat_minimum_message_length = 2
channel_1.add(current_user)
sign_in(current_user)
end
before { SiteSetting.chat_minimum_message_length = 2 }
it "doesnt allow to send" do
chat_page.visit_channel(channel_1)
@ -258,11 +230,6 @@ RSpec.describe "Chat composer", type: :system, js: true do
end
context "when upload is in progress" do
before do
channel_1.add(current_user)
sign_in(current_user)
end
it "doesnt allow to send" do
chat_page.visit_channel(channel_1)

View File

@ -25,7 +25,7 @@ describe "Using #hashtag autocompletion to search for and lookup channels",
it "searches for channels, categories, and tags with # and prioritises channels in the results" do
chat_page.visit_channel(channel1)
chat_channel_page.type_in_composer("this is #ra")
chat_channel_page.composer.fill_in(with: "this is #ra")
expect(page).to have_css(
".hashtag-autocomplete .hashtag-autocomplete__option .hashtag-autocomplete__link",
count: 3,

View File

@ -59,8 +59,7 @@ module PageObjects
end
def click_message_action_mobile(message, message_action)
expand_message_actions_mobile(message, delay: 0.5)
wait_for_animation(find(".chat-message-actions"), timeout: 5)
expand_message_actions_mobile(message, delay: 0.6)
find(".chat-message-actions [data-id=\"#{message_action}\"]").click
end
@ -69,8 +68,22 @@ module PageObjects
end
def bookmark_message(message)
hover_message(message)
find(".bookmark-btn").click
if page.has_css?("html.mobile-view", wait: 0)
click_message_action_mobile(message, "bookmark")
else
hover_message(message)
find(".bookmark-btn").click
end
end
def select_message(message)
if page.has_css?("html.mobile-view", wait: 0)
click_message_action_mobile(message, "select")
else
hover_message(message)
click_more_button
find("[data-value='select']").click
end
end
def click_more_button
@ -95,12 +108,6 @@ module PageObjects
find("[data-value='flag']").click
end
def select_message(message)
hover_message(message)
click_more_button
find("[data-value='select']").click
end
def delete_message(message)
hover_message(message)
click_more_button
@ -125,6 +132,7 @@ module PageObjects
click_send_message
click_composer
has_no_loading_skeleton?
text
end
def reply_to(message)

View File

@ -63,6 +63,7 @@ module PageObjects
fill_composer(text)
click_send_message
click_composer
text
end
def click_send_message

View File

@ -8,6 +8,8 @@ module PageObjects
SELECTOR = ".chat-composer__wrapper"
MODIFIER = RUBY_PLATFORM =~ /darwin/i ? :meta : :control
def initialize(context)
@context = context
end
@ -20,6 +22,10 @@ module PageObjects
find(context).find(SELECTOR).find(".chat-composer__input")
end
def fill_in(**args)
input.fill_in(**args)
end
def value
input.value
end
@ -32,12 +38,24 @@ module PageObjects
input.send_keys(%i[arrow_up])
end
def emphasized_text_shortcut
input.send_keys([MODIFIER, "i"])
end
def indented_text_shortcut
input.send_keys([MODIFIER, "e"])
end
def bold_text_shortcut
input.send_keys([MODIFIER, "b"])
end
def open_emoji_picker
find(context).find(SELECTOR).find(".chat-composer-button__btn.emoji").click
end
def editing_message?(message)
value == message.message && message_details.editing_message?(message)
value == message.message && message_details.editing?(message)
end
end
end

View File

@ -12,18 +12,29 @@ module PageObjects
@context = context
end
def has_message?(message, action: nil)
data_attributes = "[data-id=\"#{message.id}\"]"
data_attributes << "[data-action=\"#{action}\"]" if action
find(context).find(SELECTOR + data_attributes)
def component
find(context)
end
def has_no_message?
find(context).has_no_css?(SELECTOR)
def has_message?(**args)
selectors = SELECTOR
selectors += "[data-id=\"#{args[:id]}\"]" if args[:id]
selectors += "[data-action=\"#{args[:action]}\"]" if args[:action]
selector_method = args[:does_not_exist] ? :has_no_selector? : :has_selector?
component.send(selector_method, selectors)
end
def editing_message?(message)
has_message?(message, action: :edit)
def has_no_message?(**args)
has_message?(**args, does_not_exist: true)
end
def editing?(message)
has_message?(id: message.id, action: :edit)
end
def replying_to?(message)
has_message?(id: message.id, action: :reply)
end
end
end

View File

@ -17,18 +17,22 @@ module PageObjects
end
def exists?(**args)
text = args[:text]
selectors = SELECTOR
selectors += "[data-id=\"#{args[:id]}\"]" if args[:id]
selectors += ".is-persisted" if args[:persisted]
selectors += ".is-staged" if args[:staged]
if args[:deleted]
selectors += ".is-deleted"
text = I18n.t("js.chat.deleted", count: args[:deleted])
end
selector_method = args[:does_not_exist] ? :has_no_selector? : :has_selector?
if args[:text]
find(context).send(
selector_method,
selectors + " " + ".chat-message-text",
text: args[:text],
)
if text
find(context).send(selector_method, selectors + " " + ".chat-message-text", text: text)
else
find(context).send(selector_method, selectors)
end

View File

@ -6,7 +6,7 @@ module PageObjects
class Messages < PageObjects::Components::Base
attr_reader :context
SELECTOR = ".chat-message-container"
SELECTOR = ".chat-messages-scroll"
def initialize(context)
@context = context
@ -16,16 +16,16 @@ module PageObjects
find(context)
end
def message
PageObjects::Components::Chat::Message.new(context + " " + SELECTOR)
end
def has_message?(**args)
PageObjects::Components::Chat::Message.new(".chat-channel").exists?(**args)
message.exists?(**args)
end
def has_no_message?(**args)
PageObjects::Components::Chat::Message.new(".chat-channel").does_not_exist?(**args)
end
def has_x_messages?(count)
find(context).has_css?(SELECTOR, count: count, visible: :all)
message.does_not_exist?(**args)
end
end
end

View File

@ -27,10 +27,9 @@ RSpec.describe "Reply to message - channel - mobile", type: :system, js: true, m
expect(side_panel_page).to have_open_thread
thread_page.fill_composer("reply to message")
thread_page.click_send_message
text = thread_page.send_message
expect(thread_page).to have_message(text: "reply to message")
expect(thread_page.messages).to have_message(text: text, persisted: true)
thread_page.close

View File

@ -1,129 +0,0 @@
# frozen_string_literal: true
RSpec.describe "Shortcuts | chat composer", type: :system, js: true do
fab!(:channel_1) { Fabricate(:chat_channel) }
fab!(:current_user) { Fabricate(:user) }
let(:chat) { PageObjects::Pages::Chat.new }
let(:channel_page) { PageObjects::Pages::ChatChannel.new }
let(:key_modifier) { RUBY_PLATFORM =~ /darwin/i ? :meta : :control }
before do
chat_system_bootstrap
channel_1.add(current_user)
sign_in(current_user)
end
context "when using meta + l" do
xit "handles insert link shortcut" do
end
end
context "when using meta + b" do
it "adds bold text" do
chat.visit_channel(channel_1)
composer = find(".chat-composer__input")
composer.send_keys([key_modifier, "b"])
expect(composer.value).to eq("**strong text**")
end
end
context "when using meta + i" do
it "adds italic text" do
chat.visit_channel(channel_1)
composer = find(".chat-composer__input")
composer.send_keys([key_modifier, "i"])
expect(composer.value).to eq("_emphasized text_")
end
end
context "when using meta + e" do
it "adds preformatted text" do
chat.visit_channel(channel_1)
composer = find(".chat-composer__input")
composer.send_keys([key_modifier, "e"])
expect(composer.value).to eq("`indent preformatted text by 4 spaces`")
end
end
context "when the thread panel is also open" do
fab!(:user_2) { Fabricate(:user) }
fab!(:thread) do
chat_thread_chain_bootstrap(
channel: channel_1,
users: [current_user, user_2],
messages_count: 2,
)
end
before do
SiteSetting.enable_experimental_chat_threaded_discussions = true
channel_1.update!(threading_enabled: true)
end
it "directs the shortcut to the focused composer" do
chat.visit_channel(channel_1)
channel_page.message_thread_indicator(thread.original_message).click
composer = find(".chat-channel .chat-composer__input")
thread_composer = find(".chat-thread .chat-composer__input")
composer.send_keys([key_modifier, "i"])
expect(composer.value).to eq("_emphasized text_")
expect(thread_composer.value).to eq("")
composer.fill_in(with: "")
thread_composer.fill_in(with: "")
thread_composer.send_keys([key_modifier, "i"])
expect(composer.value).to eq("")
expect(thread_composer.value).to eq("_emphasized text_")
end
end
context "when using ArrowUp" do
fab!(:message_1) do
Fabricate(:chat_message, message: "message 1", chat_channel: channel_1, user: current_user)
end
fab!(:message_2) { Fabricate(:chat_message, message: "message 2", chat_channel: channel_1) }
it "edits last editable message" do
chat.visit_channel(channel_1)
find(".chat-composer__input").send_keys(:arrow_up)
expect(page.find(".chat-composer-message-details")).to have_content(message_1.message)
end
context "when last message is not editable" do
it "does not edit a message" do
chat.visit_channel(channel_1)
page.driver.browser.network_conditions = { offline: true }
channel_page.send_message("Hello world")
find(".chat-composer__input").send_keys(:arrow_up)
expect(page).to have_no_css(".chat-composer-message-details")
page.driver.browser.network_conditions = { offline: false }
end
end
context "with shift" do
it "starts replying to the last message" do
chat.visit_channel(channel_1)
find(".chat-composer__input").send_keys(%i[shift arrow_up])
expect(channel_page).to be_replying_to(message_2)
end
end
end
end

View File

@ -183,6 +183,7 @@ describe "Single thread in side panel", type: :system, js: true do
it "opens the side panel for a single thread using the indicator", mobile: true do
chat_page.visit_channel(channel)
channel_page.message_thread_indicator(thread.original_message).click
expect(side_panel).to have_open_thread(thread)
end
end

View File

@ -181,8 +181,7 @@ RSpec.describe "Quoting chat message transcripts", type: :system, js: true do
it "first navigates to the channel's category before opening the topic composer with the quote prefilled",
mobile: true do
chat_page.visit_channel(chat_channel_1)
chat_channel_page.click_message_action_mobile(message_1, "select")
chat_channel_page.select_message(message_1)
click_selection_button("quote")
expect(topic_page).to have_expanded_composer