require "rails_helper" RSpec.describe Comment, type: :model do let(:user) { create(:user, created_at: 3.weeks.ago) } let(:user2) { create(:user) } let(:article) { create(:article, user_id: user.id, published: true) } let(:article_with_video) { create(:article, :video, user_id: user.id, published: true) } let(:comment) { create(:comment, user_id: user2.id, commentable_id: article.id) } let(:video_comment) { create(:comment, user_id: user2.id, commentable_id: article_with_video.id) } let(:comment_2) { create(:comment, user_id: user2.id, commentable_id: article.id) } let(:child_comment) do build(:comment, user_id: user.id, commentable_id: article.id, parent_id: comment.id) end describe "validations" do subject { described_class.new(user: user, commentable: article) } let(:article) { Article.new(user: user2) } before do allow(article).to receive(:touch).and_return(true) end it { is_expected.to belong_to(:user) } it { is_expected.to belong_to(:commentable) } it { is_expected.to have_many(:reactions).dependent(:destroy) } it { is_expected.to have_many(:mentions).dependent(:destroy) } it { is_expected.to have_many(:notifications).dependent(:delete_all) } it { is_expected.to have_many(:notification_subscriptions).dependent(:destroy) } it { is_expected.to validate_presence_of(:commentable_id) } it { is_expected.to validate_presence_of(:body_markdown) } it { is_expected.to validate_uniqueness_of(:body_markdown).scoped_to(:user_id, :ancestry, :commentable_id, :commentable_type) } it { is_expected.to validate_length_of(:body_markdown).is_at_least(1).is_at_most(25_000) } it { is_expected.to validate_inclusion_of(:commentable_type).in_array(%w[Article PodcastEpisode]) } end it "gets proper generated ID code" do comment = described_class.new(id: 1) expect(comment.id_code_generated).to eq(comment.id.to_s(26)) end it "generates character count before saving" do expect(comment.markdown_character_count).to eq(comment.body_markdown.size) end describe "#processed_html" do let(:comment) { create(:comment, commentable: article, body_markdown: "# hello\n\nhy hey hey") } it "converts body_markdown to proper processed_html" do expect(comment.processed_html.include?("

")).to eq(true) end end it "adds timestamp url if commentable has video and timestamp" do video_comment.body_markdown = "I like the part at 4:30" video_comment.save expect(video_comment.processed_html.include?(">4:30")).to eq(true) video_comment.body_markdown = "I like the part at 4:30 and 5:50" video_comment.save expect(video_comment.processed_html.include?(">5:50")).to eq(true) video_comment.body_markdown = "I like the part at 5:30 and :55" video_comment.save expect(video_comment.processed_html.include?(">:55")).to eq(true) video_comment.body_markdown = "I like the part at 52:30" video_comment.save expect(video_comment.processed_html.include?(">52:30")).to eq(true) video_comment.body_markdown = "I like the part at 1:52:30 and 1:20" video_comment.save expect(video_comment.processed_html.include?(">1:52:30")).to eq(true) expect(video_comment.processed_html.include?(">1:20")).to eq(true) end it "does not add timestamp if commentable does not have video" do comment.body_markdown = "I like the part at 1:52:30 and 1:20" comment.save expect(comment.processed_html.include?(">1:52:30")).to eq(false) end it "adds rel=nofollow to links" do comment.body_markdown = "this is a comment with a link: http://dev.to" comment.save expect(comment.processed_html.include?('rel="nofollow"')).to eq(true) end it "adds a mention url if user is mentioned like @mention" do comment.body_markdown = "Hello @#{user.username}, you are cool." comment.save expect(comment.processed_html.include?("/#{user.username}")).to eq(true) expect(comment.processed_html.include?("href")).to eq(true) expect(comment.processed_html.include?("Hello ")).to eq(true) expect(comment.processed_html.size).to be < 450 end it "does not show year in readable time if not current year" do expect(comment.readable_publish_date).to eq(comment.created_at.strftime("%b %e")) end it "shows year in readable time if not current year" do comment.created_at = 1.year.ago last_year = 1.year.ago.year % 100 expect(comment.readable_publish_date.include?("'#{last_year}")).to eq(true) end it "returns a path" do expect(comment.path).not_to be(nil) end it "returns the properly formed path" do expect(comment.path).to eq("/#{comment.user.username}/comment/#{comment.id_code_generated}") end it "returns root article if no parent comment" do expect(comment.parent_or_root_article).to eq(comment.commentable) end it "returns root parent comment if exists" do expect(child_comment.parent_or_root_article).to eq(comment) end it "properly indexes" do comment.index! end describe "#parent_user" do it "returns the root article's user if no parent comment" do expect(comment.parent_user).to eq(user) end it "returns the root parent comment's user if root parent comment exists" do expect(child_comment.parent_user).to eq(user2) end end describe "#title" do it "is no more than 80 characters" do expect(comment.title.length).to be <= 80 end it "is allows title of greater length if passed" do expect(comment.title(5).length).to eq(5) end it "retains content from #processed_html" do comment.update_column(:processed_html, "Hello this is a post.") # Remove randomness text = comment.title.gsub("...", "").delete("\n") expect(comment.processed_html).to include CGI.unescapeHTML(text) end it "is converted to deleted if the comment is deleted" do comment.update_column(:deleted, true) expect(comment.title).to eq "[deleted]" end it "does not contain the wrong encoding" do comment.body_markdown = "It's the best post ever. It's so great." comment.save expect(comment.title).not_to include "'" end end describe "#custom_css" do it "returns nothing when no ltag was used" do expect(comment.custom_css).to eq("") end it "returns proper liquid tag classes if used" do text = "{% devcomment #{comment.id_code_generated} %}" ltag_comment = create(:comment, commentable_id: create(:article).id, body_markdown: text) expect(ltag_comment.custom_css).not_to eq("") end end describe "validity" do it "is invalid if commentable is unpublished article" do article.update_column(:published, false) comment = build(:comment, user_id: user.id, commentable_id: article.id) expect(comment).not_to be_valid end end describe "tree" do let(:article2) { create(:article) } let!(:tree_comment) { create(:comment, commentable: article2) } let!(:child) { create(:comment, commentable: article2, parent: tree_comment) } let!(:tree_comment2) { create(:comment, commentable: article2) } before { tree_comment.update_column(:score, 1) } it "returns a full tree" do comments = described_class.tree_for(article2) expect(comments).to eq(tree_comment => { child => {} }, tree_comment2 => {}) end it "returns part of the tree" do comments = described_class.tree_for(article2, 1) expect(comments).to eq(tree_comment => { child => {} }) end end describe "deleted" do let(:child_comment) { create(:comment, commentable: article, parent: comment, user: user) } it "deletes the comment's notifications" do create(:notification, notifiable: comment, user: user2) create(:notification, notifiable: child_comment, user: user) perform_enqueued_jobs do child_comment.update(deleted: true) expect(child_comment.notifications).to be_empty end end it "updates the notifications of the ancestors and descendants" do create(:notification, notifiable: comment, user: user2) create(:notification, notifiable: child_comment, user: user) perform_enqueued_jobs do comment.update(deleted: true) expect(child_comment.notifications.first.json_data["comment"]["ancestors"][0]["title"]).to eq "[deleted]" end end end describe "when algolia auto-indexing/removal is triggered" do context "when destroying" do it "doesn't schedule an ActiveJob on destroy" do comment = create(:comment, commentable: article) expect do comment.destroy end.not_to have_enqueued_job.on_queue("algoliasearch") end end context "when record.deleted == false" do it "checks auto-indexing" do expect do create(:comment, user_id: user2.id, commentable_id: article.id) end.to have_enqueued_job.with(kind_of(described_class), "index!").on_queue("algoliasearch") end end context "when record.deleted == true" do it "checks auto-indexing" do comment.deleted = true expect do comment.save! end.to have_enqueued_job.with(kind_of(described_class), "remove_algolia_index").on_queue("algoliasearch") end end end describe "when a comment is destroyed" do it "updates user last comment date" do expect do comment.destroy user2.reload end.to change(user2, :last_comment_at) end end describe "when a comment is created" do it "updates user last comment date" do expect do comment.save user2.reload end.to change(user2, :last_comment_at) end end include_examples "#sync_reactions_count", :article_comment end