diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-composer.js b/plugins/chat/assets/javascripts/discourse/components/chat-composer.js index aae58b67594..1fa3862c843 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-composer.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat-composer.js @@ -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); } } diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-message.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-message.hbs index c8bf254fcbe..0d76c37348c 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-message.hbs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-message.hbs @@ -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}} -
+
= 500) { + event.preventDefault(); + } + cancel(this._isPressingHandler); } diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-thread.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-thread.hbs index e2a1bf669fd..e67e20a5953 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-thread.hbs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-thread.hbs @@ -22,7 +22,7 @@ {{on "scroll" this.computeScrollState passive=true}} >
{{#each this.thread.messages key="id" as |message|}} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat/composer/channel.js b/plugins/chat/assets/javascripts/discourse/components/chat/composer/channel.js index 49a67daa4b7..5ad1bde3616 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat/composer/channel.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat/composer/channel.js @@ -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( diff --git a/plugins/chat/assets/javascripts/discourse/components/chat/composer/thread.js b/plugins/chat/assets/javascripts/discourse/components/chat/composer/thread.js index fdb94e77b03..ff1ff442b41 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat/composer/thread.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat/composer/thread.js @@ -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") { diff --git a/plugins/chat/assets/javascripts/discourse/lib/chat-messages-manager.js b/plugins/chat/assets/javascripts/discourse/lib/chat-messages-manager.js index 5ccaf95ec93..67a237394cd 100644 --- a/plugins/chat/assets/javascripts/discourse/lib/chat-messages-manager.js +++ b/plugins/chat/assets/javascripts/discourse/lib/chat-messages-manager.js @@ -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 + ); + } } diff --git a/plugins/chat/assets/javascripts/discourse/models/chat-channel.js b/plugins/chat/assets/javascripts/discourse/models/chat-channel.js index ed8eadfbf50..ef2af33eb38 100644 --- a/plugins/chat/assets/javascripts/discourse/models/chat-channel.js +++ b/plugins/chat/assets/javascripts/discourse/models/chat-channel.js @@ -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; } diff --git a/plugins/chat/assets/javascripts/discourse/models/chat-message.js b/plugins/chat/assets/javascripts/discourse/models/chat-message.js index 5f88728d716..d16e7623c98 100644 --- a/plugins/chat/assets/javascripts/discourse/models/chat-message.js +++ b/plugins/chat/assets/javascripts/discourse/models/chat-message.js @@ -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; } diff --git a/plugins/chat/assets/javascripts/discourse/models/chat-thread.js b/plugins/chat/assets/javascripts/discourse/models/chat-thread.js index 5f9ea26f3c1..0ec29be80d8 100644 --- a/plugins/chat/assets/javascripts/discourse/models/chat-thread.js +++ b/plugins/chat/assets/javascripts/discourse/models/chat-thread.js @@ -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]; } diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-channel-pane.js b/plugins/chat/assets/javascripts/discourse/services/chat-channel-pane.js index 5ed5bee8d1f..f07f3ad8eda 100644 --- a/plugins/chat/assets/javascripts/discourse/services/chat-channel-pane.js +++ b/plugins/chat/assets/javascripts/discourse/services/chat-channel-pane.js @@ -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; } diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-channel-thread-composer.js b/plugins/chat/assets/javascripts/discourse/services/chat-channel-thread-composer.js index d9b08e6e885..5c68f83d416 100644 --- a/plugins/chat/assets/javascripts/discourse/services/chat-channel-thread-composer.js +++ b/plugins/chat/assets/javascripts/discourse/services/chat-channel-thread-composer.js @@ -10,4 +10,9 @@ export default class ChatChannelThreadComposer extends ChatComposer { thread, }); } + + @action + replyTo() { + this.chat.activeMessage = null; + } } diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-channel-thread-pane.js b/plugins/chat/assets/javascripts/discourse/services/chat-channel-thread-pane.js index 2e03a27da6c..58922a2e5fd 100644 --- a/plugins/chat/assets/javascripts/discourse/services/chat-channel-thread-pane.js +++ b/plugins/chat/assets/javascripts/discourse/services/chat-channel-thread-pane.js @@ -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; - } } diff --git a/plugins/chat/spec/system/bookmark_message_spec.rb b/plugins/chat/spec/system/bookmark_message_spec.rb index 1f62d0ebf66..ab115b0eb47 100644 --- a/plugins/chat/spec/system/bookmark_message_spec.rb +++ b/plugins/chat/spec/system/bookmark_message_spec.rb @@ -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 diff --git a/plugins/chat/spec/system/chat/composer/shortcuts/channel_spec.rb b/plugins/chat/spec/system/chat/composer/shortcuts/channel_spec.rb new file mode 100644 index 00000000000..2d62ca13bbc --- /dev/null +++ b/plugins/chat/spec/system/chat/composer/shortcuts/channel_spec.rb @@ -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 diff --git a/plugins/chat/spec/system/chat/composer/shortcuts/thread_spec.rb b/plugins/chat/spec/system/chat/composer/shortcuts/thread_spec.rb index 33ac3299341..f416e7e4b20 100644 --- a/plugins/chat/spec/system/chat/composer/shortcuts/thread_spec.rb +++ b/plugins/chat/spec/system/chat/composer/shortcuts/thread_spec.rb @@ -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 diff --git a/plugins/chat/spec/system/chat_channel_spec.rb b/plugins/chat/spec/system/chat_channel_spec.rb index 26d2354f328..ad0633cfb24 100644 --- a/plugins/chat/spec/system/chat_channel_spec.rb +++ b/plugins/chat/spec/system/chat_channel_spec.rb @@ -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 diff --git a/plugins/chat/spec/system/chat_composer_spec.rb b/plugins/chat/spec/system/chat_composer_spec.rb index 1495913c85d..2264a564877 100644 --- a/plugins/chat/spec/system/chat_composer_spec.rb +++ b/plugins/chat/spec/system/chat_composer_spec.rb @@ -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 "doesn’t 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 "doesn’t allow to send" do chat_page.visit_channel(channel_1) diff --git a/plugins/chat/spec/system/hashtag_autocomplete_spec.rb b/plugins/chat/spec/system/hashtag_autocomplete_spec.rb index f3dfa9ba18a..2973832dd67 100644 --- a/plugins/chat/spec/system/hashtag_autocomplete_spec.rb +++ b/plugins/chat/spec/system/hashtag_autocomplete_spec.rb @@ -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, 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 df94ad88058..e0d12307fc3 100644 --- a/plugins/chat/spec/system/page_objects/chat/chat_channel.rb +++ b/plugins/chat/spec/system/page_objects/chat/chat_channel.rb @@ -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) diff --git a/plugins/chat/spec/system/page_objects/chat/chat_thread.rb b/plugins/chat/spec/system/page_objects/chat/chat_thread.rb index ab7f396e10e..794d1b2982f 100644 --- a/plugins/chat/spec/system/page_objects/chat/chat_thread.rb +++ b/plugins/chat/spec/system/page_objects/chat/chat_thread.rb @@ -63,6 +63,7 @@ module PageObjects fill_composer(text) click_send_message click_composer + text end def click_send_message diff --git a/plugins/chat/spec/system/page_objects/chat/components/composer.rb b/plugins/chat/spec/system/page_objects/chat/components/composer.rb index 3c3306893fa..5386c905ef4 100644 --- a/plugins/chat/spec/system/page_objects/chat/components/composer.rb +++ b/plugins/chat/spec/system/page_objects/chat/components/composer.rb @@ -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 diff --git a/plugins/chat/spec/system/page_objects/chat/components/composer_message_details.rb b/plugins/chat/spec/system/page_objects/chat/components/composer_message_details.rb index 694f1012401..c619ae4e0f4 100644 --- a/plugins/chat/spec/system/page_objects/chat/components/composer_message_details.rb +++ b/plugins/chat/spec/system/page_objects/chat/components/composer_message_details.rb @@ -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 diff --git a/plugins/chat/spec/system/page_objects/chat/components/message.rb b/plugins/chat/spec/system/page_objects/chat/components/message.rb index 4b656e68bf4..7bb9c0c8e78 100644 --- a/plugins/chat/spec/system/page_objects/chat/components/message.rb +++ b/plugins/chat/spec/system/page_objects/chat/components/message.rb @@ -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 diff --git a/plugins/chat/spec/system/page_objects/chat/components/messages.rb b/plugins/chat/spec/system/page_objects/chat/components/messages.rb index 42046ce01dd..a496369a5db 100644 --- a/plugins/chat/spec/system/page_objects/chat/components/messages.rb +++ b/plugins/chat/spec/system/page_objects/chat/components/messages.rb @@ -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 diff --git a/plugins/chat/spec/system/reply_to_message/mobile_spec.rb b/plugins/chat/spec/system/reply_to_message/mobile_spec.rb index e5c188753fe..3cc2fd9bebf 100644 --- a/plugins/chat/spec/system/reply_to_message/mobile_spec.rb +++ b/plugins/chat/spec/system/reply_to_message/mobile_spec.rb @@ -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 diff --git a/plugins/chat/spec/system/shortcuts/chat_composer_spec.rb b/plugins/chat/spec/system/shortcuts/chat_composer_spec.rb deleted file mode 100644 index 624851f28da..00000000000 --- a/plugins/chat/spec/system/shortcuts/chat_composer_spec.rb +++ /dev/null @@ -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 diff --git a/plugins/chat/spec/system/single_thread_spec.rb b/plugins/chat/spec/system/single_thread_spec.rb index 7cd8eafceea..d109ce69fb8 100644 --- a/plugins/chat/spec/system/single_thread_spec.rb +++ b/plugins/chat/spec/system/single_thread_spec.rb @@ -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 diff --git a/plugins/chat/spec/system/transcript_spec.rb b/plugins/chat/spec/system/transcript_spec.rb index e0ff76d3f7d..aa4fafbd041 100644 --- a/plugins/chat/spec/system/transcript_spec.rb +++ b/plugins/chat/spec/system/transcript_spec.rb @@ -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