DEV: properly namespace chat (#20690)

This commit main goal was to comply with Zeitwerk and properly rely on autoloading. To achieve this, most resources have been namespaced under the `Chat` module.

- Given all models are now namespaced with `Chat::` and would change the stored types in DB when using polymorphism or STI (single table inheritance), this commit uses various Rails methods to ensure proper class is loaded and the stored name in DB is unchanged, eg: `Chat::Message` model will be stored as `"ChatMessage"`, and `"ChatMessage"` will correctly load `Chat::Message` model.
- Jobs are now using constants only, eg: `Jobs::Chat::Foo` and should only be enqueued this way

Notes:
- This commit also used this opportunity to limit the number of registered css files in plugin.rb
- `discourse_dev` support has been removed within this commit and will be reintroduced later

<!-- NOTE: All pull requests should have tests (rspec in Ruby, qunit in JavaScript). If your code does not include test coverage, please include an explanation of why it was omitted. -->
This commit is contained in:
Joffrey JAFFEUX
2023-03-17 14:24:38 +01:00
committed by GitHub
parent 74349e17c9
commit 12a18d4d55
343 changed files with 9077 additions and 8745 deletions

View File

@ -5,7 +5,7 @@ require "rails_helper"
RSpec.describe Category do
it_behaves_like "a chatable model" do
fab!(:chatable) { Fabricate(:category) }
let(:channel_class) { CategoryChannel }
let(:channel_class) { Chat::CategoryChannel }
end
it { is_expected.to have_one(:category_channel).dependent(:destroy) }

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
RSpec.describe CategoryChannel do
RSpec.describe Chat::CategoryChannel do
subject(:channel) { Fabricate.build(:category_channel) }
it_behaves_like "a chat channel model"

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
RSpec.describe ChatChannel do
RSpec.describe Chat::Channel do
fab!(:category_channel1) { Fabricate(:category_channel) }
fab!(:dm_channel1) { Fabricate(:direct_message_channel) }

View File

@ -2,7 +2,7 @@
require "rails_helper"
describe DeletedChatUser do
describe Chat::DeletedUser do
describe "#username" do
it "returns a default username" do
expect(subject.username).to eq(I18n.t("chat.deleted_chat_username"))

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
RSpec.describe DirectMessageChannel do
RSpec.describe Chat::DirectMessageChannel do
subject(:channel) { Fabricate.build(:direct_message_channel) }
it_behaves_like "a chat channel model"

View File

@ -2,14 +2,14 @@
require "rails_helper"
describe DirectMessage do
describe Chat::DirectMessage do
fab!(:user1) { Fabricate(:user, username: "chatdmfellow1") }
fab!(:user2) { Fabricate(:user, username: "chatdmuser") }
fab!(:chat_channel) { Fabricate(:direct_message_channel) }
it_behaves_like "a chatable model" do
fab!(:chatable) { Fabricate(:direct_message) }
let(:channel_class) { DirectMessageChannel }
let(:channel_class) { Chat::DirectMessageChannel }
end
describe "#chat_channel_title_for_user" do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
RSpec.describe ChatDraft do
RSpec.describe Chat::Draft do
before { SiteSetting.max_chat_draft_length = 100 }
it "errors when data.value is greater than `max_chat_draft_length`" do

View File

@ -2,38 +2,38 @@
require "rails_helper"
describe ChatMessage do
describe Chat::Message do
fab!(:message) { Fabricate(:chat_message, message: "hey friend, what's up?!") }
it { is_expected.to have_many(:chat_mentions).dependent(:destroy) }
describe ".cook" do
it "does not support HTML tags" do
cooked = ChatMessage.cook("<h1>test</h1>")
cooked = described_class.cook("<h1>test</h1>")
expect(cooked).to eq("<p>&lt;h1&gt;test&lt;/h1&gt;</p>")
end
it "does not support headings" do
cooked = ChatMessage.cook("## heading 2")
cooked = described_class.cook("## heading 2")
expect(cooked).to eq("<p>## heading 2</p>")
end
it "does not support horizontal rules" do
cooked = ChatMessage.cook("---")
cooked = described_class.cook("---")
expect(cooked).to eq("<p>---</p>")
end
it "supports backticks rule" do
cooked = ChatMessage.cook("`test`")
cooked = described_class.cook("`test`")
expect(cooked).to eq("<p><code>test</code></p>")
end
it "supports fence rule" do
cooked = ChatMessage.cook(<<~RAW)
cooked = described_class.cook(<<~RAW)
```
something = test
```
@ -46,7 +46,7 @@ describe ChatMessage do
end
it "supports fence rule with language support" do
cooked = ChatMessage.cook(<<~RAW)
cooked = described_class.cook(<<~RAW)
```ruby
Widget.triangulate(argument: "no u")
```
@ -59,13 +59,13 @@ describe ChatMessage do
end
it "supports code rule" do
cooked = ChatMessage.cook(" something = test")
cooked = described_class.cook(" something = test")
expect(cooked).to eq("<pre><code>something = test\n</code></pre>")
end
it "supports blockquote rule" do
cooked = ChatMessage.cook("> a quote")
cooked = described_class.cook("> a quote")
expect(cooked).to eq("<blockquote>\n<p>a quote</p>\n</blockquote>")
end
@ -77,7 +77,7 @@ describe ChatMessage do
avatar_src =
"//test.localhost#{User.system_avatar_template(post.user.username).gsub("{size}", "40")}"
cooked = ChatMessage.cook(<<~RAW)
cooked = described_class.cook(<<~RAW)
[quote="#{post.user.username}, post:#{post.post_number}, topic:#{topic.id}"]
Mark me...this will go down in history.
[/quote]
@ -120,8 +120,8 @@ describe ChatMessage do
)
other_messages_to_quote = [msg1, msg2]
cooked =
ChatMessage.cook(
ChatTranscriptService.new(
described_class.cook(
Chat::TranscriptService.new(
chat_channel,
Fabricate(:user),
messages_or_ids: other_messages_to_quote.map(&:id),
@ -166,13 +166,13 @@ describe ChatMessage do
end
it "supports strikethrough rule" do
cooked = ChatMessage.cook("~~test~~")
cooked = described_class.cook("~~test~~")
expect(cooked).to eq("<p><s>test</s></p>")
end
it "supports emphasis rule" do
cooked = ChatMessage.cook("**bold**")
cooked = described_class.cook("**bold**")
expect(cooked).to eq("<p><strong>bold</strong></p>")
end
@ -186,7 +186,7 @@ describe ChatMessage do
end
it "supports table markdown plugin" do
cooked = ChatMessage.cook(<<~RAW)
cooked = described_class.cook(<<~RAW)
| Command | Description |
| --- | --- |
| git status | List all new or modified files |
@ -215,7 +215,7 @@ describe ChatMessage do
end
it "supports onebox markdown plugin" do
cooked = ChatMessage.cook("https://www.example.com")
cooked = described_class.cook("https://www.example.com")
expect(cooked).to eq(
"<p><a href=\"https://www.example.com\" class=\"onebox\" target=\"_blank\" rel=\"noopener nofollow ugc\">https://www.example.com</a></p>",
@ -223,7 +223,7 @@ describe ChatMessage do
end
it "supports emoji plugin" do
cooked = ChatMessage.cook(":grin:")
cooked = described_class.cook(":grin:")
expect(cooked).to eq(
"<p><img src=\"/images/emoji/twitter/grin.png?v=12\" title=\":grin:\" class=\"emoji only-emoji\" alt=\":grin:\" loading=\"lazy\" width=\"20\" height=\"20\"></p>",
@ -231,7 +231,7 @@ describe ChatMessage do
end
it "supports mentions plugin" do
cooked = ChatMessage.cook("@mention")
cooked = described_class.cook("@mention")
expect(cooked).to eq("<p><span class=\"mention\">@mention</span></p>")
end
@ -242,7 +242,7 @@ describe ChatMessage do
category = Fabricate(:category)
cooked = ChatMessage.cook("##{category.slug}")
cooked = described_class.cook("##{category.slug}")
expect(cooked).to eq(
"<p><a class=\"hashtag\" href=\"#{category.url}\">#<span>#{category.slug}</span></a></p>",
@ -256,7 +256,7 @@ describe ChatMessage do
category = Fabricate(:category)
user = Fabricate(:user)
cooked = ChatMessage.cook("##{category.slug}", user_id: user.id)
cooked = described_class.cook("##{category.slug}", user_id: user.id)
expect(cooked).to eq(
"<p><a class=\"hashtag-cooked\" href=\"#{category.url}\" data-type=\"category\" data-slug=\"#{category.slug}\"><svg class=\"fa d-icon d-icon-folder svg-icon svg-node\"><use href=\"#folder\"></use></svg><span>#{category.name}</span></a></p>",
@ -266,7 +266,7 @@ describe ChatMessage do
it "supports censored plugin" do
watched_word = Fabricate(:watched_word, action: WatchedWord.actions[:censor])
cooked = ChatMessage.cook(watched_word.word)
cooked = described_class.cook(watched_word.word)
expect(cooked).to eq("<p>■■■■■</p>")
end
@ -293,13 +293,13 @@ describe ChatMessage do
gif =
Fabricate(:upload, original_filename: "cat.gif", width: 400, height: 300, extension: "gif")
message = Fabricate(:chat_message, message: "")
UploadReference.create(target: message, upload: gif)
message.attach_uploads([gif])
expect(message.excerpt).to eq "cat.gif"
end
it "supports autolink with <>" do
cooked = ChatMessage.cook("<https://github.com/discourse/discourse-chat/pull/468>")
cooked = described_class.cook("<https://github.com/discourse/discourse-chat/pull/468>")
expect(cooked).to eq(
"<p><a href=\"https://github.com/discourse/discourse-chat/pull/468\" rel=\"noopener nofollow ugc\">https://github.com/discourse/discourse-chat/pull/468</a></p>",
@ -307,7 +307,7 @@ describe ChatMessage do
end
it "supports lists" do
cooked = ChatMessage.cook(<<~MSG)
cooked = described_class.cook(<<~MSG)
wow look it's a list
* item 1
@ -324,14 +324,14 @@ describe ChatMessage do
end
it "supports inline emoji" do
cooked = ChatMessage.cook(":D")
cooked = described_class.cook(":D")
expect(cooked).to eq(<<~HTML.chomp)
<p><img src="/images/emoji/twitter/smiley.png?v=12" title=":smiley:" class="emoji only-emoji" alt=":smiley:" loading=\"lazy\" width=\"20\" height=\"20\"></p>
HTML
end
it "supports emoji shortcuts" do
cooked = ChatMessage.cook("this is a replace test :P :|")
cooked = described_class.cook("this is a replace test :P :|")
expect(cooked).to eq(<<~HTML.chomp)
<p>this is a replace test <img src="/images/emoji/twitter/stuck_out_tongue.png?v=12" title=":stuck_out_tongue:" class="emoji" alt=":stuck_out_tongue:" loading=\"lazy\" width=\"20\" height=\"20\"> <img src="/images/emoji/twitter/expressionless.png?v=12" title=":expressionless:" class="emoji" alt=":expressionless:" loading=\"lazy\" width=\"20\" height=\"20\"></p>
HTML
@ -339,7 +339,8 @@ describe ChatMessage do
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]")
cooked =
described_class.cook("[spoiler]the planet of the apes was earth all along[/spoiler]")
expect(cooked).to eq(
"<div class=\"spoiler\">\n<p>the planet of the apes was earth all along</p>\n</div>",
@ -352,7 +353,7 @@ describe ChatMessage do
it "cooks unicode mentions" do
user = Fabricate(:unicode_user)
cooked = ChatMessage.cook("<h1>@#{user.username}</h1>")
cooked = described_class.cook("<h1>@#{user.username}</h1>")
expect(cooked).to eq("<p>&lt;h1&gt;@#{user.username}&lt;/h1&gt;</p>")
end
@ -375,8 +376,7 @@ describe ChatMessage do
)
image2 =
Fabricate(:upload, original_filename: "meme.jpg", width: 10, height: 10, extension: "jpg")
UploadReference.create!(target: message, upload: image)
UploadReference.create!(target: message, upload: image2)
message.attach_uploads([image, image2])
expect(message.to_markdown).to eq(<<~MSG.chomp)
hey friend, what's up?!
@ -388,12 +388,12 @@ describe ChatMessage do
describe ".push_notification_excerpt" do
it "truncates to 400 characters" do
message = ChatMessage.new(message: "Hello, World!" * 40)
message = described_class.new(message: "Hello, World!" * 40)
expect(message.push_notification_excerpt.size).to eq(400)
end
it "encodes emojis" do
message = ChatMessage.new(message: ":grinning:")
message = described_class.new(message: ":grinning:")
expect(message.push_notification_excerpt).to eq("😀")
end
end
@ -407,7 +407,8 @@ describe ChatMessage do
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 =
described_class.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
@ -482,7 +483,7 @@ describe ChatMessage do
end
describe "bookmarks" do
before { register_test_bookmarkable(ChatMessageBookmarkable) }
before { register_test_bookmarkable(Chat::MessageBookmarkable) }
after { DiscoursePluginRegistry.reset_register!(:bookmarkables) }
@ -539,11 +540,11 @@ describe ChatMessage do
expect(chat_upload_count([upload_1, upload_2])).to eq(0)
expect(upload_references.count).to eq(2)
expect(upload_references.map(&:target_id).uniq).to eq([chat_message.id])
expect(upload_references.map(&:target_type).uniq).to eq(["ChatMessage"])
expect(upload_references.map(&:target_type).uniq).to eq([Chat::Message.sti_name])
end
it "does nothing if the message record is new" do
expect { ChatMessage.new.attach_uploads([upload_1, upload_2]) }.to not_change {
expect { described_class.new.attach_uploads([upload_1, upload_2]) }.to not_change {
chat_upload_count
}.and not_change { UploadReference.count }
end

View File

@ -2,13 +2,13 @@
require "rails_helper"
RSpec.describe ReviewableChatMessage, type: :model do
RSpec.describe Chat::ReviewableMessage, type: :model do
fab!(:moderator) { Fabricate(:moderator) }
fab!(:user) { Fabricate(:user) }
fab!(:chat_channel) { Fabricate(:chat_channel) }
fab!(:chat_message) { Fabricate(:chat_message, chat_channel: chat_channel, user: user) }
fab!(:reviewable) do
Fabricate(:reviewable_chat_message, target: chat_message, created_by: moderator)
Fabricate(:chat_reviewable_message, target: chat_message, created_by: moderator)
end
it "agree_and_keep agrees with the flag and doesn't delete the message" do
@ -23,7 +23,7 @@ RSpec.describe ReviewableChatMessage, type: :model do
reviewable.perform(moderator, :agree_and_delete)
expect(reviewable).to be_approved
expect(ChatMessage.with_deleted.find_by(id: chat_message_id).deleted_at).to be_present
expect(Chat::Message.with_deleted.find_by(id: chat_message_id).deleted_at).to be_present
end
it "agree_and_restore agrees with the flag and restores the message" do