require "rails_helper" RSpec.describe Article, type: :model do def build_and_validate_article(*args) article = build(:article, *args) article.validate! article end let_it_be(:user) { create(:user) } let!(:article) { create(:article, user: user) } include_examples "#sync_reactions_count", :article describe "validations" do it { is_expected.to validate_uniqueness_of(:canonical_url).allow_blank } it { is_expected.to validate_uniqueness_of(:slug).scoped_to(:user_id) } it { is_expected.to validate_uniqueness_of(:feed_source_url).allow_blank } it { is_expected.to validate_presence_of(:title) } it { is_expected.to validate_length_of(:title).is_at_most(128) } it { is_expected.to validate_length_of(:cached_tag_list).is_at_most(126) } it { is_expected.to belong_to(:user) } it { is_expected.to belong_to(:organization).optional } it { is_expected.to belong_to(:collection).optional.touch(true) } it { is_expected.to have_many(:comments) } it { is_expected.to have_many(:reactions).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(:user_id) } it { is_expected.not_to allow_value("foo").for(:main_image_background_hex_color) } context "when published" do before do allow(subject).to receive(:published?).and_return(true) # rubocop:disable RSpec/NamedSubject end it { is_expected.to validate_presence_of(:slug) } end describe "#main_image_background_hex_color" do it "must have true hex for image background" do article.main_image_background_hex_color = "hello" expect(article.valid?).to eq(false) article.main_image_background_hex_color = "#fff000" expect(article.valid?).to eq(true) end end describe "#main_image" do it "must have url for main image if present" do article.main_image = "hello" expect(article.valid?).to eq(false) article.main_image = "https://image.com/image.png" expect(article.valid?).to eq(true) end end describe "dates" do it "reject future dates" do expect(build(:article, with_date: true, date: Date.tomorrow).valid?).to be(false) end it "reject future dates even when it's published at" do article.published_at = Date.tomorrow expect(article.valid?).to be(false) end end describe "polls" do let!(:poll) { create(:poll, article: article) } it "does not allow the use of admin-only liquid tags for non-admins" do article.body_markdown = "hello hey hey hey {% poll #{poll.id} %}" expect(article.valid?).to eq(false) end it "allows admins" do article.user.add_role(:admin) article.body_markdown = "hello hey hey hey {% poll #{poll.id} %}" expect(article.valid?).to eq(true) end end describe "liquid tags" do it "is not valid if it contains invalid liquid tags" do body = "{% github /thepracticaldev/dev.to %}" article = build(:article, body_markdown: body) expect(article).not_to be_valid expect(article.errors[:base]).to eq(["Invalid Github Repo link"]) end it "is valid with valid liquid tags", :vcr do VCR.use_cassette("twitter_gem") do article = build_and_validate_article(with_tweet_tag: true) expect(article).to be_valid end end end end context "when data is extracted from evaluation of the front matter during validation" do let!(:title) { "Talk About It, Justify It" } let!(:slug) { "talk-about-it-justify-it" } let!(:test_article) { build(:article, title: title) } before { test_article.validate } describe "#title" do it "produces a proper title" do expect(test_article.title).to eq(title) end end describe "#slug" do it "produces a proper slug similar to the title" do expect(test_article.slug).to start_with(slug) end end describe "#tag" do it "parses tags" do expect(test_article.tag_list.length.positive?).to be(true) end it "accepts an empty tag list and returns empty array" do expect(build_and_validate_article(with_tags: false).tag_list).to be_empty end it "rejects more than 4 tags" do five_tags = "one, two, three, four, five" expect(build(:article, tags: five_tags).valid?).to be(false) end it "rejects tags with length > 30" do tags = "'testing tag length with more than 30 chars', tag" expect(build(:article, tags: tags).valid?).to be(false) end it "parses tags when description is empty" do body_markdown = "---\ntitle: Title\npublished: false\ndescription:\ntags: one\n---\n\n" expect(build_and_validate_article(body_markdown: body_markdown).tag_list).to eq(["one"]) end end describe "#description" do it "creates proper description when description is present" do body_markdown = "---\ntitle: Title\npublished: false\ndescription: hey hey hoho\ntags: one\n---\n\n" expect(build_and_validate_article(body_markdown: body_markdown).description).to eq("hey hey hoho") end it "creates proper description when description is not present and body is present and short, with no tags" do body_markdown = "---\ntitle: Title\npublished: false\ndescription:\ntags:\n---\n\nThis is the body yo" expect(build_and_validate_article(body_markdown: body_markdown).description).to eq("This is the body yo") end it "creates proper description when description is not present and body is present and short" do body_markdown = "---\ntitle: Title\npublished: false\ndescription:\ntags: heytag\n---\n\nThis is the body yo" expect(build_and_validate_article(body_markdown: body_markdown).description).to eq("This is the body yo") end it "creates proper description when description is not present and body is present and long" do paragraphs = Faker::Hipster.paragraph(sentence_count: 40) body_markdown = "---\ntitle: Title\npublished: false\ndescription:\ntags:\n---\n\n#{paragraphs}" expect(build_and_validate_article(body_markdown: body_markdown).description).to end_with("...") end end describe "#canonical_url" do let!(:article_with_canon_url) { build(:article, with_canonical_url: true) } before do article_with_canon_url.validate end it "parses does not assign canonical_url" do expect(article.canonical_url).to eq(nil) end it "parses canonical_url if canonical_url is present" do expect(article_with_canon_url.canonical_url).not_to be_nil end it "parses does not remove canonical_url" do initial_link = article_with_canon_url.canonical_url article_with_canon_url.body_markdown = build(:article).body_markdown article_with_canon_url.validate expect(article_with_canon_url.canonical_url).to eq(initial_link) end end describe "#reading_time" do it "produces a correct reading time" do expect(test_article.reading_time).to eq(1) end end describe "#processed_html" do it "fixes the issue with --- hr tags" do article = build_and_validate_article(with_hr_issue: true) expect(article.processed_html.include?("