# frozen_string_literal: true require "rails_helper" describe ChatMessage do fab!(:message) { Fabricate(:chat_message, message: "hey friend, what's up?!") } describe ".cook" do it "does not support HTML tags" do cooked = ChatMessage.cook("

test

") expect(cooked).to eq("

<h1>test</h1>

") end it "does not support headings" do cooked = ChatMessage.cook("## heading 2") expect(cooked).to eq("

## heading 2

") end it "does not support horizontal rules" do cooked = ChatMessage.cook("---") expect(cooked).to eq("

---

") end it "supports backticks rule" do cooked = ChatMessage.cook("`test`") expect(cooked).to eq("

test

") end it "supports fence rule" do cooked = ChatMessage.cook(<<~RAW) ``` something = test ``` RAW expect(cooked).to eq(<<~COOKED.chomp)
something = test
      
COOKED end it "supports fence rule with language support" do cooked = ChatMessage.cook(<<~RAW) ```ruby Widget.triangulate(argument: "no u") ``` RAW expect(cooked).to eq(<<~COOKED.chomp)
Widget.triangulate(argument: "no u")
      
COOKED end it "supports code rule" do cooked = ChatMessage.cook(" something = test") expect(cooked).to eq("
something = test\n
") end it "supports blockquote rule" do cooked = ChatMessage.cook("> a quote") expect(cooked).to eq("
\n

a quote

\n
") end it "supports quote bbcode" do topic = Fabricate(:topic, title: "Some quotable topic") post = Fabricate(:post, topic: topic) SiteSetting.external_system_avatars_enabled = false avatar_src = "//test.localhost#{User.system_avatar_template(post.user.username).gsub("{size}", "40")}" cooked = ChatMessage.cook(<<~RAW) [quote="#{post.user.username}, post:#{post.post_number}, topic:#{topic.id}"] Mark me...this will go down in history. [/quote] RAW expect(cooked).to eq(<<~COOKED.chomp) COOKED end it "supports chat quote bbcode" do chat_channel = Fabricate(:category_channel, name: "testchannel") user = Fabricate(:user, username: "chatbbcodeuser") user2 = Fabricate(:user, username: "otherbbcodeuser") avatar_src = "//test.localhost#{User.system_avatar_template(user.username).gsub("{size}", "40")}" avatar_src2 = "//test.localhost#{User.system_avatar_template(user2.username).gsub("{size}", "40")}" msg1 = Fabricate( :chat_message, chat_channel: chat_channel, message: "this is the first message", user: user, ) msg2 = Fabricate( :chat_message, chat_channel: chat_channel, message: "and another cool one", user: user2, ) other_messages_to_quote = [msg1, msg2] cooked = ChatMessage.cook( ChatTranscriptService.new( chat_channel, Fabricate(:user), messages_or_ids: other_messages_to_quote.map(&:id), ).generate_markdown, ) expect(cooked).to eq(<<~COOKED.chomp)
Originally sent in testchannel
chatbbcodeuser

this is the first message

otherbbcodeuser

and another cool one

COOKED end it "supports strikethrough rule" do cooked = ChatMessage.cook("~~test~~") expect(cooked).to eq("

test

") end it "supports emphasis rule" do cooked = ChatMessage.cook("**bold**") expect(cooked).to eq("

bold

") end it "supports link markdown rule" do chat_message = Fabricate(:chat_message, message: "[test link](https://www.example.com)") expect(chat_message.cooked).to eq( "

test link

", ) end it "supports table markdown plugin" do cooked = ChatMessage.cook(<<~RAW) | Command | Description | | --- | --- | | git status | List all new or modified files | RAW expected = <<~COOKED
Command Description
git status List all new or modified files
COOKED expect(cooked).to eq(expected.chomp) end it "supports onebox markdown plugin" do cooked = ChatMessage.cook("https://www.example.com") expect(cooked).to eq( "

https://www.example.com

", ) end it "supports emoji plugin" do cooked = ChatMessage.cook(":grin:") expect(cooked).to eq( "

\":grin:\"

", ) end it "supports mentions plugin" do cooked = ChatMessage.cook("@mention") expect(cooked).to eq("

@mention

") end # TODO (martin) Remove this when enable_experimental_hashtag_autocomplete is default it "supports category-hashtag plugin" do category = Fabricate(:category) cooked = ChatMessage.cook("##{category.slug}") expect(cooked).to eq( "

##{category.slug}

", ) end it "supports hashtag-autocomplete plugin" do SiteSetting.chat_enabled = true SiteSetting.enable_experimental_hashtag_autocomplete = true category = Fabricate(:category) user = Fabricate(:user) cooked = ChatMessage.cook("##{category.slug}", user_id: user.id) expect(cooked).to eq( "

#{category.name}

", ) end it "supports censored plugin" do watched_word = Fabricate(:watched_word, action: WatchedWord.actions[:censor]) cooked = ChatMessage.cook(watched_word.word) expect(cooked).to eq("

■■■■■

") end it "includes links in pretty text excerpt if the raw message is a single link and the PrettyText excerpt is blank" do message = Fabricate.build( :chat_message, message: "https://twitter.com/EffinBirds/status/1518743508378697729", ) expect(message.excerpt).to eq("https://twitter.com/EffinBirds/status/1518743508378697729") message = Fabricate.build( :chat_message, message: "https://twitter.com/EffinBirds/status/1518743508378697729", cooked: <<~COOKED, \n COOKED ) expect(message.excerpt).to eq("https://twitter.com/EffinBirds/status/1518743508378697729") message = Fabricate.build( :chat_message, message: "wow check out these birbs https://twitter.com/EffinBirds/status/1518743508378697729", ) expect(message.excerpt).to eq( "wow check out these birbs https://twitter.com/Effi…", ) end it "returns an empty string if PrettyText.excerpt returns empty string" do message = Fabricate(:chat_message, message: <<~MSG) [quote="martin, post:30, topic:3179, full:true"] This is a real **quote** topic with some *markdown* in it I can quote. [/quote] MSG expect(message.excerpt).to eq("") end it "excerpts upload file name if message is empty" do gif = Fabricate(:upload, original_filename: "cat.gif", width: 400, height: 300, extension: "gif") message = Fabricate(:chat_message, message: "") ChatUpload.create(chat_message: message, upload: gif) expect(message.excerpt).to eq "cat.gif" end it "supports autolink with <>" do cooked = ChatMessage.cook("") expect(cooked).to eq( "

https://github.com/discourse/discourse-chat/pull/468

", ) end it "supports lists" do cooked = ChatMessage.cook(<<~MSG) wow look it's a list * item 1 * item 2 MSG expect(cooked).to eq(<<~HTML.chomp)

wow look it's a list

HTML end it "supports inline emoji" do cooked = ChatMessage.cook(":D") expect(cooked).to eq(<<~HTML.chomp)

:smiley:

HTML end it "supports emoji shortcuts" do cooked = ChatMessage.cook("this is a replace test :P :|") expect(cooked).to eq(<<~HTML.chomp)

this is a replace test :stuck_out_tongue: :expressionless:

HTML end it "supports spoilers" do if SiteSetting.respond_to?(:spoiler_enabled) && SiteSetting.spoiler_enabled cooked = ChatMessage.cook("[spoiler]the planet of the apes was earth all along[/spoiler]") expect(cooked).to eq( "
\n

the planet of the apes was earth all along

\n
", ) end end context "when unicode usernames are enabled" do before { SiteSetting.unicode_usernames = true } it "cooks unicode mentions" do user = Fabricate(:unicode_user) cooked = ChatMessage.cook("

@#{user.username}

") expect(cooked).to eq("

<h1>@#{user.username}</h1>

") end end end describe ".to_markdown" do it "renders the message without uploads" do expect(message.to_markdown).to eq("hey friend, what's up?!") end it "renders the message with uploads" do image = Fabricate( :upload, original_filename: "test_image.jpg", width: 400, height: 300, extension: "jpg", ) image2 = Fabricate(:upload, original_filename: "meme.jpg", width: 10, height: 10, extension: "jpg") ChatUpload.create(chat_message: message, upload: image) ChatUpload.create(chat_message: message, upload: image2) expect(message.to_markdown).to eq(<<~MSG.chomp) hey friend, what's up?! ![test_image.jpg|400x300](#{image.short_url}) ![meme.jpg|10x10](#{image2.short_url}) MSG end end describe ".push_notification_excerpt" do it "truncates to 400 characters" do message = ChatMessage.new(message: "Hello, World!" * 40) expect(message.push_notification_excerpt.size).to eq(400) end it "encodes emojis" do message = ChatMessage.new(message: ":grinning:") expect(message.push_notification_excerpt).to eq("😀") end end describe "blocking duplicate messages" do fab!(:channel) { Fabricate(:chat_channel, user_count: 10) } fab!(:user1) { Fabricate(:user) } fab!(:user2) { Fabricate(:user) } before { SiteSetting.chat_duplicate_message_sensitivity = 1 } it "blocks duplicate messages for the message, channel user, and message age requirements" do Fabricate(:chat_message, message: "this is duplicate", chat_channel: channel, user: user1) message = ChatMessage.new(message: "this is duplicate", chat_channel: channel, user: user2) message.validate_message(has_uploads: false) expect(message.errors.full_messages).to include(I18n.t("chat.errors.duplicate_message")) end end describe "#destroy" do it "nullify messages with in_reply_to_id to this destroyed message" do message_1 = Fabricate(:chat_message) message_2 = Fabricate(:chat_message, in_reply_to_id: message_1.id) message_3 = Fabricate(:chat_message, in_reply_to_id: message_2.id) expect(message_2.in_reply_to_id).to eq(message_1.id) message_1.destroy! expect(message_2.reload.in_reply_to_id).to be_nil expect(message_3.reload.in_reply_to_id).to eq(message_2.id) end it "destroys chat_message_revisions" do message_1 = Fabricate(:chat_message) revision_1 = Fabricate(:chat_message_revision, chat_message: message_1) message_1.destroy! expect { revision_1.reload }.to raise_error(ActiveRecord::RecordNotFound) end it "destroys chat_message_reactions" do message_1 = Fabricate(:chat_message) reaction_1 = Fabricate(:chat_message_reaction, chat_message: message_1) message_1.destroy! expect { reaction_1.reload }.to raise_error(ActiveRecord::RecordNotFound) end it "destroys chat_mention" do message_1 = Fabricate(:chat_message) mention_1 = Fabricate(:chat_mention, chat_message: message_1) message_1.destroy! expect { mention_1.reload }.to raise_error(ActiveRecord::RecordNotFound) end it "destroys chat_webhook_event" do message_1 = Fabricate(:chat_message) webhook_1 = Fabricate(:chat_webhook_event, chat_message: message_1) message_1.destroy! expect { webhook_1.reload }.to raise_error(ActiveRecord::RecordNotFound) end it "destroys chat_uploads" do message_1 = Fabricate(:chat_message) chat_upload_1 = Fabricate(:chat_upload, chat_message: message_1) message_1.destroy! expect { chat_upload_1.reload }.to raise_error(ActiveRecord::RecordNotFound) end describe "bookmarks" do before { Bookmark.register_bookmarkable(ChatMessageBookmarkable) } it "destroys bookmarks" do message_1 = Fabricate(:chat_message) bookmark_1 = Fabricate(:bookmark, bookmarkable: message_1) message_1.destroy! expect { bookmark_1.reload }.to raise_error(ActiveRecord::RecordNotFound) end end end describe "#rebake!" do fab!(:chat_message) { Fabricate(:chat_message) } describe "hashtags" do fab!(:category) { Fabricate(:category) } fab!(:group) { Fabricate(:group) } fab!(:secure_category) { Fabricate(:private_category, group: group) } before do SiteSetting.chat_enabled = true SiteSetting.enable_experimental_hashtag_autocomplete = true SiteSetting.suppress_secured_categories_from_admin = true end it "keeps the same hashtags the user has permission to after rebake" do group.add(chat_message.user) chat_message.update!( message: "this is the message ##{category.slug} ##{secure_category.slug} ##{chat_message.chat_channel.slug}", ) chat_message.cook chat_message.save! expect(chat_message.reload.cooked).to include(secure_category.name) chat_message.rebake! expect(chat_message.reload.cooked).to include(secure_category.name) end end end end