From 85b14a03594d0f3507e337bb9148b4700ab6b0f4 Mon Sep 17 00:00:00 2001 From: Martin Brennan Date: Fri, 23 Dec 2022 10:04:41 +1000 Subject: [PATCH] DEV: Move chat transcript tests into system specs (#19434) We are all in on system specs, so this commit moves all the chat quoting acceptance tests (some of which have been skipped for a while) into system specs. --- .../components/chat-selection-manager.hbs | 2 +- .../discourse/initializers/chat-setup.js | 2 + .../system/page_objects/chat/chat_channel.rb | 4 + plugins/chat/spec/system/transcript_spec.rb | 245 ++++++++++++++++++ spec/rails_helper.rb | 2 + spec/support/system_helpers.rb | 1 + spec/system/page_objects/pages/topic.rb | 27 +- 7 files changed, 279 insertions(+), 4 deletions(-) create mode 100644 plugins/chat/spec/system/transcript_spec.rb diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-selection-manager.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-selection-manager.hbs index 8139d3972b7..14d3da398c5 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-selection-manager.hbs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-selection-manager.hbs @@ -10,7 +10,7 @@ {{/if}} - + {{#if this.showChatQuoteSuccess}} diff --git a/plugins/chat/assets/javascripts/discourse/initializers/chat-setup.js b/plugins/chat/assets/javascripts/discourse/initializers/chat-setup.js index c892853691c..69ee6ab84ee 100644 --- a/plugins/chat/assets/javascripts/discourse/initializers/chat-setup.js +++ b/plugins/chat/assets/javascripts/discourse/initializers/chat-setup.js @@ -83,6 +83,8 @@ export default { I18n.t("dates.long_no_year") ); } + + dateTimeEl.dataset.dateFormatted = true; }); }, { id: "chat-transcript-datetime" } 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 8919b1ee46e..5b53086adc5 100644 --- a/plugins/chat/spec/system/page_objects/chat/chat_channel.rb +++ b/plugins/chat/spec/system/page_objects/chat/chat_channel.rb @@ -23,6 +23,10 @@ module PageObjects has_no_css?(".chat-skeleton") end + def has_selection_management? + has_css?(".chat-selection-management") + end + def expand_message_actions(message) hover_message(message) click_more_buttons(message) diff --git a/plugins/chat/spec/system/transcript_spec.rb b/plugins/chat/spec/system/transcript_spec.rb new file mode 100644 index 00000000000..4c7723dbddb --- /dev/null +++ b/plugins/chat/spec/system/transcript_spec.rb @@ -0,0 +1,245 @@ +# frozen_string_literal: true + +RSpec.describe "Quoting chat message transcripts", type: :system, js: true do + fab!(:current_user) { Fabricate(:user) } + fab!(:chat_channel_1) { Fabricate(:chat_channel) } + + let(:chat_page) { PageObjects::Pages::Chat.new } + let(:chat_channel_page) { PageObjects::Pages::ChatChannel.new } + let(:topic_page) { PageObjects::Pages::Topic.new } + + before do + chat_system_bootstrap(Fabricate(:admin), [chat_channel_1]) + chat_channel_1.add(current_user) + sign_in(current_user) + end + + def select_message_desktop(message) + if page.has_css?(".chat-message-container.selecting-messages") + chat_channel_page.message_by_id(message.id).find(".chat-message-selector").click + else + chat_channel_page.message_by_id(message.id).hover + expect(page).to have_css(".chat-message-actions .more-buttons") + find(".chat-message-actions .more-buttons").click + find(".select-kit-row[data-value=\"selectMessage\"]").click + end + end + + def select_message_mobile(message) + if page.has_css?(".chat-message-container.selecting-messages") + chat_channel_page.message_by_id(message.id).find(".chat-message-selector").click + else + chat_channel_page.message_by_id(message.id).click(delay: 0.5) + find(".chat-message-action-item[data-id=\"selectMessage\"]").click + end + end + + def cdp_allow_clipboard_access! + cdp_params = { + origin: page.server_url, + permission: { + name: "clipboard-read", + }, + setting: "granted", + } + page.driver.browser.execute_cdp("Browser.setPermission", **cdp_params) + + cdp_params = { + origin: page.server_url, + permission: { + name: "clipboard-write", + }, + setting: "granted", + } + page.driver.browser.execute_cdp("Browser.setPermission", **cdp_params) + end + + def read_clipboard + page.evaluate_async_script("navigator.clipboard.readText().then(arguments[0])") + end + + def click_selection_button(button) + selector = + case button + when "quote" + "#chat-quote-btn" + when "copy" + "#chat-copy-btn" + when "cancel" + "#chat-cancel-selection-btn" + when "move" + "#chat-move-to-channel-btn" + end + within(".chat-selection-management-buttons") { find(selector).click } + end + + def copy_messages_to_clipboard(messages) + messages = Array.wrap(messages) + messages.each { |message| select_message_desktop(message) } + expect(chat_channel_page).to have_selection_management + click_selection_button("copy") + expect(page).to have_content("Chat quote copied to clipboard") + clip_text = read_clipboard + expect(clip_text.chomp).to eq(generate_transcript(messages, current_user)) + clip_text + end + + def generate_transcript(messages, acting_user) + messages = Array.wrap(messages) + ChatTranscriptService + .new(messages.first.chat_channel, acting_user, messages_or_ids: messages.map(&:id)) + .generate_markdown + .chomp + end + + describe "copying quote transcripts with the clipboard" do + before { cdp_allow_clipboard_access! } + + context "when quoting a single message into a topic" do + fab!(:post_1) { Fabricate(:post) } + fab!(:message_1) { Fabricate(:chat_message, chat_channel: chat_channel_1) } + + it "quotes the message" do + chat_page.visit_channel(chat_channel_1) + + expect(chat_channel_page).to have_no_loading_skeleton + + clip_text = copy_messages_to_clipboard(message_1) + topic_page.visit_topic_and_open_composer(post_1.topic) + topic_page.fill_in_composer("This is a new post!\n\n" + clip_text) + + within(".d-editor-preview") { expect(page).to have_css(".chat-transcript") } + + topic_page.send_reply + selector = topic_page.post_by_number_selector(2) + + expect(page).to have_css(selector) + within(selector) { expect(page).to have_css(".chat-transcript") } + end + end + + context "when quoting multiple messages into a topic" do + fab!(:post_1) { Fabricate(:post) } + fab!(:message_1) { Fabricate(:chat_message, chat_channel: chat_channel_1) } + fab!(:message_2) { Fabricate(:chat_message, chat_channel: chat_channel_1) } + + it "quotes the messages" do + chat_page.visit_channel(chat_channel_1) + + expect(chat_channel_page).to have_no_loading_skeleton + + clip_text = copy_messages_to_clipboard([message_1, message_2]) + topic_page.visit_topic_and_open_composer(post_1.topic) + topic_page.fill_in_composer("This is a new post!\n\n" + clip_text) + + within(".d-editor-preview") { expect(page).to have_css(".chat-transcript", count: 2) } + expect(page).to have_content("Originally sent in #{chat_channel_1.name}") + + topic_page.send_reply + + selector = topic_page.post_by_number_selector(2) + expect(page).to have_css(selector) + within(selector) { expect(page).to have_css(".chat-transcript", count: 2) } + end + end + + context "when quoting a message containing a onebox" do + fab!(:post_1) { Fabricate(:post) } + fab!(:message_1) { Fabricate(:chat_message, chat_channel: chat_channel_1) } + + before do + Oneboxer.stubs(:preview).returns( + "", + ) + message_1.update!(message: "http://www.example.com/has-title.html") + message_1.rebake! + end + + it "works" do + chat_page.visit_channel(chat_channel_1) + + expect(chat_channel_page).to have_no_loading_skeleton + + clip_text = copy_messages_to_clipboard(message_1) + topic_page.visit_topic_and_open_composer(post_1.topic) + topic_page.fill_in_composer(clip_text) + + within(".chat-transcript-messages") do + expect(page).to have_content("An interesting article") + end + end + end + + context "when quoting a message in another message" do + fab!(:message_1) { Fabricate(:chat_message, chat_channel: chat_channel_1) } + + it "quotes the message" do + chat_page.visit_channel(chat_channel_1) + + expect(chat_channel_page).to have_no_loading_skeleton + + clip_text = copy_messages_to_clipboard(message_1) + click_selection_button("cancel") + chat_channel_page.fill_composer(clip_text) + chat_channel_page.click_send_message + + expect(page).to have_selector(".chat-message", count: 2) + + message = ChatMessage.find_by(user: current_user, message: clip_text.chomp) + + within(chat_channel_page.message_by_id(message.id)) do + expect(page).to have_css(".chat-transcript") + end + end + end + end + + context "when quoting into a topic directly" do + fab!(:message_1) { Fabricate(:chat_message, chat_channel: chat_channel_1) } + let(:topic_title) { "Some topic title for testing" } + + it "opens the topic composer with correct state" do + chat_page.visit_channel(chat_channel_1) + + expect(chat_channel_page).to have_no_loading_skeleton + + select_message_desktop(message_1) + click_selection_button("quote") + + expect(topic_page).to have_expanded_composer + expect(topic_page).to have_composer_content(generate_transcript(message_1, current_user)) + expect(page).to have_css( + ".category-input .select-kit-header[data-value='#{chat_channel_1.chatable.id}']", + ) + expect(page).not_to have_current_path(chat_channel_1.chatable.url) + + topic_page.fill_in_composer_title(topic_title) + topic_page.send_reply + + selector = topic_page.post_by_number_selector(1) + expect(page).to have_css(selector) + within(selector) { expect(page).to have_css(".chat-transcript") } + + topic = Topic.find_by(user: current_user, title: topic_title) + expect(page).to have_current_path(topic.url) + end + + context "when on mobile" 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) + expect(chat_channel_page).to have_no_loading_skeleton + + select_message_mobile(message_1) + click_selection_button("quote") + + expect(topic_page).to have_expanded_composer + expect(topic_page).to have_composer_content(generate_transcript(message_1, current_user)) + expect(page).to have_current_path(chat_channel_1.chatable.url) + expect(page).to have_css( + ".category-input .select-kit-header[data-value='#{chat_channel_1.chatable.id}']", + ) + end + end + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index daa3ac9b868..006b888eeef 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -245,6 +245,8 @@ RSpec.configure do |config| allow: [Webdrivers::Chromedriver.base_url] ) + Capybara.disable_animation = true + Capybara.configure do |capybara_config| capybara_config.server_host = "localhost" capybara_config.server_port = 31337 diff --git a/spec/support/system_helpers.rb b/spec/support/system_helpers.rb index 9d5054a75d5..ce954dfa7b5 100644 --- a/spec/support/system_helpers.rb +++ b/spec/support/system_helpers.rb @@ -25,6 +25,7 @@ module SystemHelpers SiteSetting.force_hostname = Capybara.server_host SiteSetting.port = Capybara.server_port SiteSetting.external_system_avatars_enabled = false + SiteSetting.disable_avatar_education_message = true end def try_until_success(timeout: 2, frequency: 0.01) diff --git a/spec/system/page_objects/pages/topic.rb b/spec/system/page_objects/pages/topic.rb index 20a9675aad0..efe278d05cc 100644 --- a/spec/system/page_objects/pages/topic.rb +++ b/spec/system/page_objects/pages/topic.rb @@ -34,7 +34,11 @@ module PageObjects def post_by_number(post_or_number) post_or_number = post_or_number.is_a?(Post) ? post_or_number.post_number : post_or_number - find("#post_#{post_or_number}") + find(".topic-post:not(.staged) #post_#{post_or_number}") + end + + def post_by_number_selector(post_number) + ".topic-post:not(.staged) #post_#{post_number}" end def has_post_more_actions?(post) @@ -74,24 +78,41 @@ module PageObjects def click_reply_button find(".topic-footer-main-buttons > .create").click + has_expanded_composer? end def has_expanded_composer? has_css?("#reply-control.open") end + def find_composer + find("#reply-control .d-editor .d-editor-input") + end + def type_in_composer(input) - find("#reply-control .d-editor .d-editor-input").send_keys(input) + find_composer.send_keys(input) + end + + def fill_in_composer(input) + find_composer.fill_in(with: input) end def clear_composer - find("#reply-control .d-editor .d-editor-input").set("") + fill_in_composer("") + end + + def has_composer_content?(content) + find_composer.value == content end def send_reply find("#reply-control .save-or-cancel .create").click end + def fill_in_composer_title(title) + find("#reply-title").fill_in(with: title) + end + private def topic_footer_button_id(button)