mirror of
https://github.com/discourse/discourse.git
synced 2025-05-25 09:57:25 +08:00

A system test in `system/search_spec.rb` was failing with the following error frequently on CI: ``` Failure/Error: expect(search_page).to have_heading_text("Search") expected `#<PageObjects::Pages::Search:0x00007fb9fcd3f028>.has_heading_text?("Search")` to be truthy, got false [Screenshot Image]: /__w/discourse/discourse/tmp/capybara/failures_r_spec_example_groups_search_when_using_full_page_search_on_mobile_works_and_clears_search_page_state_912.png ~~~~~~~ JS LOGS ~~~~~~~ (no logs) ~~~~~ END JS LOGS ~~~~~ ./spec/system/search_spec.rb:42:in `block (3 levels) in <main>' ./spec/rails_helper.rb:619:in `block (3 levels) in <top (required)>' /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/benchmark-0.4.0/lib/benchmark.rb:304:in `measure' ./spec/rails_helper.rb:619:in `block (2 levels) in <top (required)>' ./spec/rails_helper.rb:580:in `block (3 levels) in <top (required)>' /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/timeout-0.4.3/lib/timeout.rb:185:in `block in timeout' /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/timeout-0.4.3/lib/timeout.rb:192:in `timeout' ./spec/rails_helper.rb:570:in `block (2 levels) in <top (required)>' ./spec/rails_helper.rb:527:in `block (2 levels) in <top (required)>' /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/webmock-3.25.1/lib/webmock/rspec.rb:39:in `block (2 levels) in <top (required)>' ``` The failure screenshot shows that the "user" is on the homepage even though we have already clicked the search icon and ensured that the user can see the search container. I suspect there is some sort of race condition here since Capybara executes clicks in quick sucession where we clicked on both the homepage logo and the search icon. It may be possible that Ember redirected the user to the search page first before the browser was able to finish navigating the user to the `/` href. ### Reviewer notes Test flaked in https://github.com/discourse/discourse/actions/runs/14085443789/job/39448197089 with the following failure screenshot: 
430 lines
13 KiB
Ruby
430 lines
13 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.describe "Chat channel", type: :system do
|
|
fab!(:current_user) { Fabricate(:user) }
|
|
fab!(:channel_1) { Fabricate(:chat_channel) }
|
|
fab!(:message_1) { Fabricate(:chat_message, use_service: true, chat_channel: channel_1) }
|
|
|
|
let(:chat_page) { PageObjects::Pages::Chat.new }
|
|
let(:channel_page) { PageObjects::Pages::ChatChannel.new }
|
|
let(:sidebar_page) { PageObjects::Pages::Sidebar.new }
|
|
let(:side_panel_page) { PageObjects::Pages::ChatSidePanel.new }
|
|
|
|
before do
|
|
chat_system_bootstrap
|
|
channel_1.add(current_user)
|
|
sign_in(current_user)
|
|
end
|
|
|
|
context "when has unread threads" do
|
|
fab!(:thread_1) { Fabricate(:chat_thread, channel: channel_1) }
|
|
|
|
before do
|
|
channel_1.update!(threading_enabled: true)
|
|
thread_1.add(current_user)
|
|
Fabricate(:chat_message, thread: thread_1, use_service: true)
|
|
end
|
|
|
|
context "when visiting channel" do
|
|
it "opens thread panel" do
|
|
chat_page.visit_channel(channel_1)
|
|
|
|
expect(side_panel_page).to have_open_thread_list
|
|
end
|
|
end
|
|
|
|
context "when visiting channel on mobile", mobile: true do
|
|
it "doesn’t open thread panel" do
|
|
chat_page.visit_channel(channel_1)
|
|
|
|
expect(side_panel_page).to have_no_open_thread_list
|
|
end
|
|
end
|
|
|
|
context "when visiting thread" do
|
|
it "doesn’t open thread panel" do
|
|
chat_page.visit_thread(thread_1)
|
|
|
|
expect(side_panel_page).to have_no_open_thread_list
|
|
end
|
|
end
|
|
|
|
context "when opening channel message" do
|
|
it "doesn’t open thread panel" do
|
|
chat_page.visit_channel(channel_1, message_id: message_1.id)
|
|
|
|
expect(side_panel_page).to have_no_open_thread_list
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when first batch of messages doesnt fill page" do
|
|
before { Fabricate.times(30, :chat_message, user: current_user, chat_channel: channel_1) }
|
|
|
|
it "autofills for more messages" do
|
|
chat_page.prefers_full_page
|
|
visit("/")
|
|
# cheap trick to ensure the messages don't fill the initial page
|
|
page.execute_script(
|
|
"document.head.insertAdjacentHTML('beforeend', `<style>.chat-message-text{font-size:3px;}</style>`)",
|
|
)
|
|
sidebar_page.open_channel(channel_1)
|
|
|
|
expect(channel_page.messages).to have_message(id: message_1.id)
|
|
end
|
|
end
|
|
|
|
context "when sending a message" do
|
|
context "with lots of messages" do
|
|
before { Fabricate.times(50, :chat_message, chat_channel: channel_1) }
|
|
|
|
it "loads most recent messages" do
|
|
unloaded_message = Fabricate(:chat_message, chat_channel: channel_1)
|
|
chat_page.visit_channel(channel_1, message_id: message_1.id)
|
|
|
|
expect(channel_page.messages).to have_no_message(id: unloaded_message.id)
|
|
|
|
channel_page.send_message
|
|
|
|
expect(channel_page.messages).to have_message(id: unloaded_message.id)
|
|
end
|
|
end
|
|
|
|
context "with two sessions opened on same channel" do
|
|
it "syncs the messages" do
|
|
Jobs.run_immediately!
|
|
|
|
using_session(:tab_1) do
|
|
sign_in(current_user)
|
|
chat_page.visit_channel(channel_1)
|
|
end
|
|
|
|
using_session(:tab_2) do
|
|
sign_in(current_user)
|
|
chat_page.visit_channel(channel_1)
|
|
end
|
|
|
|
using_session(:tab_1) { channel_page.send_message("test_message") }
|
|
|
|
using_session(:tab_2) do
|
|
expect(channel_page.messages).to have_message(text: "test_message")
|
|
end
|
|
end
|
|
end
|
|
|
|
it "allows to edit this message once persisted" do
|
|
chat_page.visit_channel(channel_1)
|
|
channel_page.send_message("aaaaaa")
|
|
|
|
expect(channel_page.messages).to have_message(persisted: true, text: "aaaaaa")
|
|
|
|
last_message = find(".chat-message-container:last-child")
|
|
last_message.hover
|
|
|
|
expect(channel_page).to have_css(
|
|
".chat-message-actions-container[data-id='#{last_message["data-id"]}']",
|
|
)
|
|
end
|
|
end
|
|
|
|
context "when clicking the arrow button" do
|
|
before { Fabricate.times(50, :chat_message, chat_channel: channel_1) }
|
|
|
|
it "jumps to the bottom of the channel" do
|
|
unloaded_message = Fabricate(:chat_message, chat_channel: channel_1)
|
|
visit("/chat/c/-/#{channel_1.id}/#{message_1.id}")
|
|
|
|
expect(channel_page).to have_no_loading_skeleton
|
|
expect(page).to have_no_css("[data-id='#{unloaded_message.id}']")
|
|
|
|
find(".chat-scroll-to-bottom__button.visible").click
|
|
|
|
expect(channel_page).to have_no_loading_skeleton
|
|
expect(page).to have_css("[data-id='#{unloaded_message.id}']")
|
|
expect(page).to have_css(".-last-read[data-id='#{unloaded_message.id}']")
|
|
end
|
|
end
|
|
|
|
context "when returning to a channel where last read is not last message" do
|
|
it "scrolls to the correct last read message" do
|
|
channel_1.membership_for(current_user).update!(last_read_message: message_1)
|
|
messages = Fabricate.times(50, :chat_message, chat_channel: channel_1)
|
|
chat_page.visit_channel(channel_1)
|
|
|
|
expect(page).to have_css("[data-id='#{messages.first.id}']")
|
|
expect(page).to have_no_css("[data-id='#{messages.last.id}']")
|
|
end
|
|
end
|
|
|
|
context "when a new message is created" do
|
|
before { Fabricate.times(50, :chat_message, chat_channel: channel_1) }
|
|
|
|
it "doesn’t append the message when not at bottom" do
|
|
visit("/chat/c/-/#{channel_1.id}/#{message_1.id}")
|
|
|
|
expect(page).to have_css(".chat-scroll-to-bottom__button.visible")
|
|
|
|
new_message = Fabricate(:chat_message, chat_channel: channel_1, use_service: true)
|
|
|
|
expect(channel_page.messages).to have_no_message(id: new_message.id)
|
|
end
|
|
end
|
|
|
|
context "when a message contains mentions" do
|
|
fab!(:other_user) { Fabricate(:user) }
|
|
fab!(:message) do
|
|
Fabricate(
|
|
:chat_message,
|
|
chat_channel: channel_1,
|
|
message:
|
|
"hello @here @all @#{current_user.username} @#{other_user.username} @unexisting @system",
|
|
user: other_user,
|
|
)
|
|
end
|
|
|
|
before do
|
|
SiteSetting.enable_user_status = true
|
|
current_user.set_status!("off to dentist", "tooth")
|
|
other_user.set_status!("surfing", "man_surfing")
|
|
channel_1.add(other_user)
|
|
end
|
|
|
|
it "highlights the mentions" do
|
|
chat_page.visit_channel(channel_1)
|
|
|
|
expect(page).to have_selector(".mention.--wide", text: "@here")
|
|
expect(page).to have_selector(".mention.--wide", text: "@all")
|
|
expect(page).to have_selector(".mention.--current", text: "@#{current_user.username}")
|
|
expect(page).to have_selector(".mention", text: "@#{other_user.username}")
|
|
expect(page).to have_selector(".mention", text: "@unexisting")
|
|
expect(page).to have_selector(".mention.--bot", text: "@system")
|
|
end
|
|
|
|
it "renders user status on mentions" do
|
|
Fabricate(:user_chat_mention, user: current_user, chat_message: message)
|
|
Fabricate(:user_chat_mention, user: other_user, chat_message: message)
|
|
|
|
chat_page.visit_channel(channel_1)
|
|
|
|
expect(page).to have_selector(
|
|
".mention .user-status-message img[alt='#{current_user.user_status.emoji}']",
|
|
)
|
|
expect(page).to have_selector(
|
|
".mention .user-status-message img[alt='#{other_user.user_status.emoji}']",
|
|
)
|
|
end
|
|
|
|
it "renders user status when expanding collapsed message" do
|
|
message_1 =
|
|
Fabricate(
|
|
:chat_message,
|
|
chat_channel: channel_1,
|
|
message: "hello @#{other_user.username}",
|
|
user: current_user,
|
|
)
|
|
chat_page.visit_channel(channel_1)
|
|
|
|
channel_page.messages.delete(message_1)
|
|
channel_page.messages.restore(message_1)
|
|
|
|
expect(page).to have_selector(
|
|
".chat-message-container[data-id=\"#{message_1.id}\"] .mention .user-status-message img[alt='#{other_user.user_status.emoji}']",
|
|
)
|
|
|
|
other_user.set_status!("hello", "heart")
|
|
|
|
expect(page).to have_selector(
|
|
".chat-message-container[data-id=\"#{message_1.id}\"] .mention .user-status-message img[alt='#{other_user.user_status.emoji}']",
|
|
)
|
|
end
|
|
end
|
|
|
|
context "when reply is right under" do
|
|
fab!(:other_user) { Fabricate(:user) }
|
|
|
|
before do
|
|
Fabricate(:chat_message, in_reply_to: message_1, user: other_user, chat_channel: channel_1)
|
|
channel_1.add(other_user)
|
|
end
|
|
|
|
it "doesn’t show the reply-to line" do
|
|
chat_page.visit_channel(channel_1)
|
|
|
|
expect(page).to have_no_selector(".chat-reply__excerpt")
|
|
end
|
|
end
|
|
|
|
context "when reply is not directly connected" do
|
|
fab!(:other_user) { Fabricate(:user) }
|
|
|
|
before do
|
|
Fabricate(:chat_message, user: other_user, chat_channel: channel_1)
|
|
Fabricate(:chat_message, in_reply_to: message_1, user: other_user, chat_channel: channel_1)
|
|
channel_1.add(other_user)
|
|
end
|
|
|
|
it "shows the reply-to line" do
|
|
chat_page.visit_channel(channel_1)
|
|
|
|
expect(page).to have_selector(".chat-reply__excerpt")
|
|
end
|
|
end
|
|
|
|
context "when replying to message that has HTML tags" do
|
|
fab!(:other_user) { Fabricate(:user) }
|
|
fab!(:message_2) do
|
|
Fabricate(
|
|
:chat_message,
|
|
user: other_user,
|
|
chat_channel: channel_1,
|
|
use_service: true,
|
|
message: "<abbr>not abbr</abbr>",
|
|
)
|
|
end
|
|
|
|
before do
|
|
Fabricate(:chat_message, user: other_user, chat_channel: channel_1)
|
|
Fabricate(:chat_message, in_reply_to: message_2, user: current_user, chat_channel: channel_1)
|
|
channel_1.add(other_user)
|
|
|
|
stub_request(:get, "https://foo.com/").with(headers: { "Accept" => "*/*" }).to_return(
|
|
status: 200,
|
|
body: "",
|
|
headers: {
|
|
},
|
|
)
|
|
|
|
stub_request(:head, "https://foo.com/").with(headers: { "Host" => "foo.com" }).to_return(
|
|
status: 200,
|
|
body: "",
|
|
headers: {
|
|
},
|
|
)
|
|
end
|
|
|
|
it "renders text in the reply-to" do
|
|
chat_page.visit_channel(channel_1)
|
|
|
|
expect(find(".chat-reply .chat-reply__excerpt")["innerHTML"].strip).to eq(
|
|
"<abbr>not abbr</abbr>",
|
|
)
|
|
end
|
|
|
|
it "renders escaped HTML when including a #" do
|
|
update_message!(message_2, user: other_user, text: "#general <abbr>not abbr</abbr>")
|
|
chat_page.visit_channel(channel_1)
|
|
|
|
expect(find(".chat-reply .chat-reply__excerpt")["innerHTML"].strip).to eq(
|
|
"#general <abbr>not abbr</abbr>",
|
|
)
|
|
end
|
|
|
|
it "limits excerpt length" do
|
|
update_message!(message_2, user: other_user, text: ("a" * 160))
|
|
chat_page.visit_channel(channel_1)
|
|
|
|
expect(find(".chat-reply .chat-reply__excerpt")["innerHTML"].strip).to eq("a" * 150 + "…")
|
|
end
|
|
|
|
it "renders urls correclty in excerpts" do
|
|
update_message!(message_2, user: other_user, text: "https://foo.com")
|
|
chat_page.visit_channel(channel_1)
|
|
|
|
expect(find(".chat-reply .chat-reply__excerpt")["innerHTML"].strip).to eq("https://foo.com")
|
|
end
|
|
|
|
it "renders safe HTML like mentions (which are just links) in the reply-to" do
|
|
update_message!(
|
|
message_2,
|
|
user: other_user,
|
|
text: "@#{other_user.username} <abbr>not abbr</abbr>",
|
|
)
|
|
chat_page.visit_channel(channel_1)
|
|
|
|
expect(find(".chat-reply .chat-reply__excerpt")["innerHTML"].strip).to eq(
|
|
"@#{other_user.username} <abbr>not abbr</abbr>",
|
|
)
|
|
end
|
|
end
|
|
|
|
context "when messages are separated by a day" do
|
|
before { Fabricate(:chat_message, chat_channel: channel_1, created_at: 2.days.ago) }
|
|
|
|
it "shows a date separator" do
|
|
chat_page.visit_channel(channel_1)
|
|
|
|
expect(page).to have_selector(".chat-message-separator__text", text: "Today")
|
|
end
|
|
end
|
|
|
|
context "when a message contains code fence" do
|
|
fab!(:message_2) { Fabricate(:chat_message, chat_channel: channel_1, message: <<~MESSAGE) }
|
|
Here's a message with code highlighting
|
|
|
|
\`\`\`ruby
|
|
Widget.triangulate(arg: "test")
|
|
\`\`\`
|
|
MESSAGE
|
|
|
|
it "adds the correct lang" do
|
|
chat_page.visit_channel(channel_1)
|
|
|
|
expect(page).to have_selector("code.lang-ruby")
|
|
end
|
|
end
|
|
|
|
context "when scrolling" do
|
|
before { 50.times { Fabricate(:chat_message, chat_channel: channel_1) } }
|
|
|
|
it "resets the active message" do
|
|
chat_page.visit_channel(channel_1)
|
|
last_message = find(".chat-message-container:last-child")
|
|
last_message.hover
|
|
|
|
expect(page).to have_css(
|
|
".chat-message-actions-container[data-id='#{last_message["data-id"]}']",
|
|
)
|
|
|
|
find(".chat-messages-scroller").scroll_to(0, -1000)
|
|
|
|
expect(page).to have_no_css(
|
|
".chat-message-actions-container[data-id='#{last_message["data-id"]}']",
|
|
)
|
|
end
|
|
end
|
|
|
|
context "when opening message secondary options" do
|
|
it "doesn’t hide dropdown on mouseleave" do
|
|
chat_page.visit_channel(channel_1)
|
|
last_message = find(".chat-message-container:last-child")
|
|
last_message.hover
|
|
|
|
expect(page).to have_css(
|
|
".chat-message-actions-container[data-id='#{last_message["data-id"]}']",
|
|
)
|
|
|
|
find(".chat-message-actions-container .secondary-actions").click
|
|
expect(page).to have_css(
|
|
".chat-message-actions-container .secondary-actions .select-kit-body",
|
|
)
|
|
|
|
PageObjects::Components::Logo.new.hover
|
|
expect(page).to have_css(
|
|
".chat-message-actions-container .secondary-actions .select-kit-body",
|
|
)
|
|
|
|
find("#site-logo").click
|
|
expect(page).to have_no_css(
|
|
".chat-message-actions-container .secondary-actions .select-kit-body",
|
|
)
|
|
end
|
|
end
|
|
|
|
it "renders emojis in page title" do
|
|
channel_1.update!(name: ":dog: Dogs")
|
|
chat_page.visit_channel(channel_1)
|
|
|
|
expect(page).to have_title("#🐕 Dogs - Chat - Discourse")
|
|
end
|
|
end
|