require "rails_helper" RSpec.describe MarkdownProcessor::Parser, type: :service do let(:random_word) { Faker::Lorem.word } let(:basic_parsed_markdown) { described_class.new(random_word) } def generate_and_parse_markdown(raw_markdown) described_class.new(raw_markdown).finalize end it "renders plain text as-is" do expect(basic_parsed_markdown.finalize).to include(random_word) end it "escapes liquid tags in codeblock" do code_block = "```\n{% what %}\n```" expect(generate_and_parse_markdown(code_block)).to include("{% what %}") end it "escapes the `raw` Liquid tag in codeblocks" do code_block = "```\n{% raw %}some text{% endraw %}\n```" expect(generate_and_parse_markdown(code_block)).to include("{% raw %}", "{% endraw %}") end it "does not allow button tag" do button = "" expect(generate_and_parse_markdown(button)).not_to include("button") end it "does not render the escaped dashes when using a `raw` Liquid tag in codeblocks with syntax highlighting" do code_block = "```js\n{% raw %}some text{% endraw %}\n```" expect(generate_and_parse_markdown(code_block)).not_to include("----") end it "escapes some triple backticks within a codeblock when using tildes" do code_block = "​~~~\nhello\n// ```\nwhatever\n// ```\n~~~" number_of_triple_backticks = generate_and_parse_markdown(code_block).scan("```").count expect(number_of_triple_backticks).to eq(2) end it "does not remove the non-'raw tag related' four dashes" do code_block = "```\n----\n```" expect(generate_and_parse_markdown(code_block)).to include("----") end it "escapes the `raw` Liquid tag in codespans" do code_block = "``{% raw %}some text{% endraw %}``" expect(generate_and_parse_markdown(code_block)).to include("{% raw %}", "{% endraw %}") end it "escapes the `raw` Liquid tag in inline code" do code_block = "`{% raw %}some text{% endraw %}`" expect(generate_and_parse_markdown(code_block)).to include("{% raw %}", "{% endraw %}") end it "escapes codeblocks in numbered lists" do code_block = <<~CODE_BLOCK 1. Define your hooks in config file `lefthook.yml` ```yaml pre-push: parallel: true commands: rubocop: run: bundle exec rspec --fail-fast ``` CODE_BLOCK escaped_codeblock = generate_and_parse_markdown(code_block) expect(escaped_codeblock).not_to include("```") expect(escaped_codeblock).not_to include("`") expect(escaped_codeblock).to include("bundle exec rspec --fail-fast") end it "escapes liquid tags in code spans" do code_span = "``{% what %}``" expect(generate_and_parse_markdown(code_span)).to include("{% what %}") end it "renders double backtick code spans properly" do code_span = "``#{random_word}``" expect(generate_and_parse_markdown(code_span)).to include random_word end it "wraps figcaptions with figures" do code_span = "

Statement

\n
A fig
" test = generate_and_parse_markdown("

case:

#{code_span}") expect(test).to eq("

case:

\n
#{code_span}
\n\n\n\n") end it "does not wrap figcaptions already in figures" do code_span = "

Statement

\n
A fig
" test = generate_and_parse_markdown(code_span) expect(test).to eq("#{code_span}\n\n\n\n") end it "does not wrap figcaptions without predecessors" do code_span = "
A fig
" test = generate_and_parse_markdown(code_span) expect(test).to eq("#{code_span}\n\n") end it "converts code tag to triple backticks" do content = "\n this is some random code \n" code_block_object = described_class.new(content) test = code_block_object.convert_code_tags_to_triple_backticks(content) expect(test).not_to include("") expect(test).not_to include("") expect(test).to include("```") end it "converts multiple code tags to triple backticks" do content = "\n this is some random code \n\n\n\n more random code \n" code_block_object = described_class.new(content) test = code_block_object.convert_code_tags_to_triple_backticks(content) expect(test).not_to include("") expect(test).not_to include("") expect(test).to include("```") end it "ignores code tag if pre tag is present" do content = "
\n\n this is some random code \n\n
" code_block_object = described_class.new(content) test = code_block_object.convert_code_tags_to_triple_backticks(content) expect(test).to include("
\n")
    expect(test).to include("\n
") expect(test).not_to include("```") end it "ignores code tag if tags are inline" do content = " this is some random code " code_block_object = described_class.new(content) test = code_block_object.convert_code_tags_to_triple_backticks(content) expect(test).to include("") expect(test).to include("") expect(test).not_to include("```") end it "returns original content if code tag is not present" do content = "this is some random code" code_block_object = described_class.new(content) test = code_block_object.convert_code_tags_to_triple_backticks(content) expect(test).to be(content) end it "permits abbr tags" do result = generate_and_parse_markdown("OK") expect(result).to include("OK") end context "when rendering links markdown" do # the following specs are testing HTMLRouge it "renders properly if protocol http is included" do code_span = "[github](http://github.com)" test = generate_and_parse_markdown(code_span) expect(test).to eq("

github

\n\n") end it "renders properly if protocol https is included" do code_span = "[github](https://github.com)" test = generate_and_parse_markdown(code_span) expect(test).to eq("

github

\n\n") end it "renders properly if protocol is not included" do code_span = "[github](github.com)" test = generate_and_parse_markdown(code_span) expect(test).to eq("

github

\n\n") end it "renders properly relative paths" do code_span = "[career tag](/t/career)" test = generate_and_parse_markdown(code_span) app_protocol = ApplicationConfig["APP_PROTOCOL"] app_domain = ApplicationConfig["APP_DOMAIN"] expect(test).to eq("

career tag

\n\n") end it "renders properly anchored links" do code_span = "[Chapter 1](#chapter-1)" test = generate_and_parse_markdown(code_span) expect(test).to eq("

Chapter 1

\n\n") end it "does not render CSS classes" do expect(generate_and_parse_markdown("
")) .to exclude("class") .and exclude("w-100") end end describe "mentions" do let(:user) { build_stubbed(:user) } before { allow(User).to receive(:find_by).with(username: user.username).and_return(user) } it "works normally" do mention = "@#{user.username}" result = generate_and_parse_markdown(mention) expect(result).to include "" \ "@#{user.username} three four:

\n\n\n\n" # rubocop:enable Layout/LineLength expect(result).to eq(expected_result) end it "will not work in code tag" do mention = "this is a chunk of text `@#{user.username}`" result = generate_and_parse_markdown(mention) expect(result).to include "](http://a)') end end context "when provided with liquid tags" do it "does not raises error if liquid tag was used incorrectly" do bad_ltag = "{% #{random_word} %}" expect { generate_and_parse_markdown(bad_ltag) }.not_to raise_error end end context "when provided with kbd tag" do it "leaves the kbd tag in place" do inline_kbd = generate_and_parse_markdown("Ctrl + ,") inline_kbd = Nokogiri::HTML(inline_kbd).at("p").inner_html expect(inline_kbd).to eq("Ctrl + ,") end end describe "#tags_used" do let(:parsed_markdown) { described_class.new("{% youtube oHg5SJYRHA0 %}") } it "returns empty if no tag was used" do expect(basic_parsed_markdown.tags_used).to eq([]) end it "return tags used if it was used" do expect(parsed_markdown.tags_used).to eq([YoutubeTag]) end end context "when using gifs from Giphy as images" do let(:giphy_markdown_texts) do %w( ![source](https://media.giphy.com/media/3ow0TN2M8TH2aAn67F/giphy.gif) ![social](https://media.giphy.com/media/3ow0TN2M8TH2aAn67F/giphy.gif) ![small](https://media.giphy.com/media/3ow0TN2M8TH2aAn67F/200w_d.gif) ) end it "does not wrap giphy images with Cloudinary" do giphy_markdown_texts.each do |body_markdown| html = Nokogiri::HTML(generate_and_parse_markdown(body_markdown)) img_src = html.search("img")[0]["src"] expect(img_src).not_to include("https://res.cloudinary.com") end end it "uses the raw gif from i.giphy.com" do giphy_markdown_texts.each do |body_markdown| html = Nokogiri::HTML(generate_and_parse_markdown(body_markdown)) img_src = html.search("img")[0]["src"] expect(img_src).to start_with("https://i.giphy.com") end end end context "when an image is used" do let(:markdown_with_img) { "![](https://image.com/image.jpg)" } it "wraps image in link" do expect(generate_and_parse_markdown(markdown_with_img)).to include(":o::o::o::o::o::o::o:") expect(result).to include(":o::o:") end end context "when using Liquid variables" do it "prevents Liquid variables" do expect { generate_and_parse_markdown("{{ 'something' }}") }.to raise_error(StandardError) end it "allows Liquid variables in codeblocks" do expect { generate_and_parse_markdown("```\n{{ 'something' }}\n```") }.not_to raise_error end it "renders the text in the codeblock properly" do result = generate_and_parse_markdown("```\n{{ 'something' }}\n```") expect(result).to include("{{ 'something' }}") end it "allows Liquid variables within inline code" do expect { generate_and_parse_markdown("`{{ 'something' }}`") }.not_to raise_error end it "renders the inline code with the text properly" do result = generate_and_parse_markdown("`{{ 'something' }}`") expect(result).to include("{{ 'something' }}") end it "renders nested lists without linebreaks" do result = generate_and_parse_markdown("- [A](#a)\n - [B](#b)\n- [C](#c)") expect(result).not_to include("
") end end context "when word as snake case" do it "doesn't change word" do code_block = "word_italic_" expect(generate_and_parse_markdown(code_block)).to include("word_italic_") end end context "when double underline" do it "renders italic" do code_block = "word__italic__" expect(generate_and_parse_markdown(code_block)).to include("word_italic_") end end context "when adding syntax highlighting" do it "defaults to plaintext" do code_block = "```\ntext\n````" expect(generate_and_parse_markdown(code_block)).to include("highlight plaintext") end it "adds correct syntax highlighting to codeblocks when the hint is not lowercase" do code_block = "```Ada\nwith Ada.Directories;\n````" expect(generate_and_parse_markdown(code_block)).to include("highlight ada") end it "adds correct syntax highlighting to codeblocks when the hint is lowercase" do code_block = "```ada\nwith Ada.Directories;\n````" expect(generate_and_parse_markdown(code_block)).to include("highlight ada") end end context "when using a valid attribute" do let(:example_text) { "{% liquid example %}" } it "prevents the attribute from having Liquid tags inside" do text = '{% 404/404#%}' expect(generate_and_parse_markdown(text)).to exclude("{%") end it "does not scrub attributes in inline code" do inline_code = "`#{example_text}`" expect(generate_and_parse_markdown(inline_code)).to include(example_text) double_fenced_code = "``#{example_text}``" expect(generate_and_parse_markdown(double_fenced_code)).to include(example_text) end it "does not scrub attributes in codeblocks" do codeblock = "```\n#{example_text}\n```" expect(generate_and_parse_markdown(codeblock)).to include("{%") expect(generate_and_parse_markdown(codeblock)).to include("%}") end end context "with additional_liquid_tag_options" do it "passes those options to Liquid::Template.parse" do # rubocop:disable RSpec/VerifiedDoubles # # I don't want to delve into the implementation details of liquid to test what the parse # method's return value. parse_response = double("parse_response", render: "liquified!") # rubocop:enable RSpec/VerifiedDoubles allow(Liquid::Template).to receive(:parse).and_return(parse_response) described_class.new( "{% liquid example %}", source: :my_source, user: :my_user, liquid_tag_options: { policy: :my_policy }, ).finalize expect(Liquid::Template).to have_received(:parse) .with( "

{% liquid example %}

\n", { source: :my_source, policy: :my_policy, user: :my_user }, ) end end end