diff --git a/Gemfile b/Gemfile index e18f5638..8b54d56d 100644 --- a/Gemfile +++ b/Gemfile @@ -27,9 +27,10 @@ group :development do end group :test do - gem "commonmarker", "~> 1.0.0.pre4", require: false + gem "commonmarker", "~> 1.0.0.pre7", require: false gem "gemoji", "~> 3.0", require: false gem "gemojione", "~> 4.3", require: false + gem "minitest" gem "minitest-bisect", "~> 1.6" @@ -37,4 +38,5 @@ group :test do gem "nokogiri", "~> 1.13" gem "minitest-focus", "~> 1.1" + gem "rouge", "~> 3.1", require: false end diff --git a/README.md b/README.md index c3231e7d..1eb52fc9 100644 --- a/README.md +++ b/README.md @@ -230,19 +230,28 @@ end For more information on how to write effective `NodeFilter`s, refer to the provided filters, and see the underlying lib, [Selma](https://www.github.com/gjtorikian/selma) for more information. -- `AbsoluteSourceFilter` - replace relative image urls with fully qualified versions -- `EmojiFilter` - converts `::` to [emoji](http://www.emoji-cheat-sheet.com/)! -- `HttpsFilter` - Replacing http urls with https versions -- `ImageMaxWidthFilter` - link to full size image for large images -- `MentionFilter` - replace `@user` mentions with links -- `SanitizationFilter` - allow sanitize user markup -- `TableOfContentsFilter` - anchor headings with name attributes and generate Table of Contents html unordered list linking headings -- `TeamMentionFilter` - replace `@org/team` mentions with links +- `AbsoluteSourceFilter`: replace relative image urls with fully qualified versions +- `EmojiFilter`: converts `::` to [emoji](http://www.emoji-cheat-sheet.com/) + - (Note: the included `MarkdownFilter` will already convert emoji) +- `HttpsFilter`: Replacing http urls with https versions +- `ImageMaxWidthFilter`: link to full size image for large images +- `MentionFilter`: replace `@user` mentions with links +- `SanitizationFilter`: allow sanitize user markup +- `SyntaxHighlightFilter`: applies syntax highlighting to `pre` blocks + - (Note: the included `MarkdownFilter` will already apply highlighting) +- `TableOfContentsFilter`: anchor headings with name attributes and generate Table of Contents html unordered list linking headings +- `TeamMentionFilter`: replace `@org/team` mentions with links ## Dependencies -Since filters can be customized to your heart's content, gem dependencies are _not_ bundled; this project doesn't know which of the default filters you might use, and as such, you must bundle each filter's gem -dependencies yourself. +Since filters can be customized to your heart's content, gem dependencies are _not_ bundled; this project doesn't know which of the default filters you might use, and as such, you must bundle each filter's gem dependencies yourself. + +For example, `SyntaxHighlightFilter` uses [rouge](https://github.com/jneen/rouge) +to detect and highlight languages; to use the `SyntaxHighlightFilter`, you must add the following to your Gemfile: + +```ruby +gem "rouge" +``` > **Note** > See the [Gemfile](/Gemfile) `:test` group for any version requirements. diff --git a/UPGRADING.md b/UPGRADING.md index f0565678..3bd7e5a0 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -13,7 +13,6 @@ This project is now under a module called `HTMLPipeline`, not `HTML::Pipeline`. The following filters were removed: - `AutolinkFilter`: this is handled by [Commonmarker](https://www.github.com/gjtorikian/commonmarker) and can be disabled/enabled through the `MarkdownFilter`'s `context` hash -- `SyntaxHighlightFilter`: this is handled by [Commonmarker](https://www.github.com/gjtorikian/commonmarker) and can be disabled/enabled through the `MarkdownFilter`'s `context` hash - `SanitizationFilter`: this is handled by [Selma](https://www.github.com/gjtorikian/selma); configuration can be done through the `sanitization_config` hash - `EmailReplyFilter` diff --git a/lib/html_pipeline.rb b/lib/html_pipeline.rb index ad709561..4e542814 100644 --- a/lib/html_pipeline.rb +++ b/lib/html_pipeline.rb @@ -145,8 +145,11 @@ def call(text, context: {}, result: {}) context = context.freeze result ||= {} - payload = default_payload({ text_filters: @text_filters.map(&:name), - context: context, result: result, }) + payload = default_payload({ + text_filters: @text_filters.map(&:name), + context: context, + result: result, + }) instrument("call_text_filters.html_pipeline", payload) do result[:output] = @text_filters.inject(text) do |doc, filter| @@ -159,8 +162,11 @@ def call(text, context: {}, result: {}) html = @convert_filter.call(text) unless @convert_filter.nil? unless @node_filters.empty? - payload = default_payload({ node_filters: @node_filters.map { |f| f.class.name }, - context: context, result: result, }) + payload = default_payload({ + node_filters: @node_filters.map { |f| f.class.name }, + context: context, + result: result, + }) instrument("call_node_filters.html_pipeline", payload) do result[:output] = Selma::Rewriter.new(sanitizer: @sanitization_config, handlers: @node_filters).rewrite(html) end @@ -178,8 +184,11 @@ def call(text, context: {}, result: {}) # # Returns the result of the filter. def perform_filter(filter, doc, context: {}, result: {}) - payload = default_payload({ filter: filter.name, - context: context, result: result, }) + payload = default_payload({ + filter: filter.name, + context: context, + result: result, + }) instrument("call_filter.html_pipeline", payload) do filter.call(doc, context: context, result: result) end diff --git a/lib/html_pipeline/node_filter/syntax_highlight_filter.rb b/lib/html_pipeline/node_filter/syntax_highlight_filter.rb new file mode 100644 index 00000000..2fcda21b --- /dev/null +++ b/lib/html_pipeline/node_filter/syntax_highlight_filter.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +HTMLPipeline.require_dependency("rouge", "SyntaxHighlightFilter") + +class HTMLPipeline + class NodeFilter + # HTML Filter that syntax highlights text inside code blocks. + # + # Context options: + # + # :highlight => String represents the language to pick lexer. Defaults to empty string. + # :scope => String represents the class attribute adds to pre element after. + # Defaults to "highlight highlight-css" if highlights a css code block. + # + # This filter does not write any additional information to the context hash. + class SyntaxHighlightFilter < NodeFilter + def initialize(context: {}, result: {}) + super(context: context, result: result) + # TODO: test the optionality of this + @formatter = context[:formatter] || Rouge::Formatters::HTML.new + end + + SELECTOR = Selma::Selector.new(match_element: "pre", match_text_within: "pre") + + def selector + SELECTOR + end + + def handle_element(element) + default = context[:highlight]&.to_s + @lang = element["lang"] || default + + scope = context.fetch(:scope, "highlight") + + element["class"] = "#{scope} #{scope}-#{@lang}" if include_lang? + end + + def handle_text_chunk(text) + return if @lang.nil? + return if (lexer = lexer_for(@lang)).nil? + + content = text.to_s + + text.replace(highlight_with_timeout_handling(content, lexer), as: :html) + end + + def highlight_with_timeout_handling(text, lexer) + Rouge.highlight(text, lexer, @formatter) + rescue Timeout::Error => _e + text + end + + def lexer_for(lang) + Rouge::Lexer.find(lang) + end + + def include_lang? + !@lang.nil? && !@lang.empty? + end + end + end +end diff --git a/lib/html_pipeline/node_filter/table_of_contents_filter.rb b/lib/html_pipeline/node_filter/table_of_contents_filter.rb index 3017a8ea..6b8d8545 100644 --- a/lib/html_pipeline/node_filter/table_of_contents_filter.rb +++ b/lib/html_pipeline/node_filter/table_of_contents_filter.rb @@ -24,8 +24,10 @@ class NodeFilter # result[:output].to_s # # => "

\n..." class TableOfContentsFilter < NodeFilter - SELECTOR = Selma::Selector.new(match_element: "h1 a[href], h2 a[href], h3 a[href], h4 a[href], h5 a[href], h6 a[href]", - match_text_within: "h1, h2, h3, h4, h5, h6") + SELECTOR = Selma::Selector.new( + match_element: "h1 a[href], h2 a[href], h3 a[href], h4 a[href], h5 a[href], h6 a[href]", + match_text_within: "h1, h2, h3, h4, h5, h6", + ) def selector SELECTOR diff --git a/lib/html_pipeline/sanitization_filter.rb b/lib/html_pipeline/sanitization_filter.rb index 002d8115..020c5487 100644 --- a/lib/html_pipeline/sanitization_filter.rb +++ b/lib/html_pipeline/sanitization_filter.rb @@ -16,11 +16,70 @@ class SanitizationFilter # The main sanitization allowlist. Only these elements and attributes are # allowed through by default. DEFAULT_CONFIG = Selma::Sanitizer::Config.freeze_config({ - elements: ["h1", "h2", "h3", "h4", "h5", "h6", "br", "b", "i", "strong", "em", "a", "pre", "code", - "img", "tt", "div", "ins", "del", "sup", "sub", "p", "picture", "ol", "ul", "table", "thead", "tbody", "tfoot", - "blockquote", "dl", "dt", "dd", "kbd", "q", "samp", "var", "hr", "ruby", "rt", "rp", "li", "tr", "td", "th", - "s", "strike", "summary", "details", "caption", "figure", "figcaption", "abbr", "bdo", "cite", - "dfn", "mark", "small", "source", "span", "time", "wbr",], + elements: [ + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "br", + "b", + "i", + "strong", + "em", + "a", + "pre", + "code", + "img", + "tt", + "div", + "ins", + "del", + "sup", + "sub", + "p", + "picture", + "ol", + "ul", + "table", + "thead", + "tbody", + "tfoot", + "blockquote", + "dl", + "dt", + "dd", + "kbd", + "q", + "samp", + "var", + "hr", + "ruby", + "rt", + "rp", + "li", + "tr", + "td", + "th", + "s", + "strike", + "summary", + "details", + "caption", + "figure", + "figcaption", + "abbr", + "bdo", + "cite", + "dfn", + "mark", + "small", + "source", + "span", + "time", + "wbr", + ], attributes: { "a" => ["href"], @@ -31,13 +90,77 @@ class SanitizationFilter "ins" => ["cite"], "q" => ["cite"], "source" => ["srcset"], - all: ["abbr", "accept", "accept-charset", "accesskey", "action", "align", "alt", "aria-describedby", - "aria-hidden", "aria-label", "aria-labelledby", "axis", "border", "char", - "charoff", "charset", "checked", "clear", "cols", "colspan", "compact", "coords", "datetime", "dir", - "disabled", "enctype", "for", "frame", "headers", "height", "hreflang", "hspace", "id", "ismap", "label", "lang", - "maxlength", "media", "method", "multiple", "name", "nohref", "noshade", "nowrap", "open", "progress", - "prompt", "readonly", "rel", "rev", "role", "rows", "rowspan", "rules", "scope", "selected", "shape", - "size", "span", "start", "summary", "tabindex", "title", "type", "usemap", "valign", "value", "width", "itemprop",], + all: [ + "abbr", + "accept", + "accept-charset", + "accesskey", + "action", + "align", + "alt", + "aria-describedby", + "aria-hidden", + "aria-label", + "aria-labelledby", + "axis", + "border", + "char", + "charoff", + "charset", + "checked", + "clear", + "cols", + "colspan", + "compact", + "coords", + "datetime", + "dir", + "disabled", + "enctype", + "for", + "frame", + "headers", + "height", + "hreflang", + "hspace", + "id", + "ismap", + "label", + "lang", + "maxlength", + "media", + "method", + "multiple", + "name", + "nohref", + "noshade", + "nowrap", + "open", + "progress", + "prompt", + "readonly", + "rel", + "rev", + "role", + "rows", + "rowspan", + "rules", + "scope", + "selected", + "shape", + "size", + "span", + "start", + "summary", + "tabindex", + "title", + "type", + "usemap", + "valign", + "value", + "width", + "itemprop", + ], }, protocols: { "a" => { "href" => Selma::Sanitizer::Config::VALID_PROTOCOLS }.freeze, diff --git a/lib/html_pipeline/version.rb b/lib/html_pipeline/version.rb index fbe6f8a8..8d9b4e7e 100644 --- a/lib/html_pipeline/version.rb +++ b/lib/html_pipeline/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class HTMLPipeline - VERSION = "3.0.0.pre1" + VERSION = "3.0.0.pre2" end diff --git a/test/html_pipeline/convert_filter/markdown_filter_test.rb b/test/html_pipeline/convert_filter/markdown_filter_test.rb index e656e080..51827b85 100644 --- a/test/html_pipeline/convert_filter/markdown_filter_test.rb +++ b/test/html_pipeline/convert_filter/markdown_filter_test.rb @@ -62,78 +62,103 @@ def test_fenced_code_blocks_with_language def test_standard_extensions iframe = "" iframe_escaped = "<iframe src='http://www.google.com'></iframe>" - doc = MarkdownFilter.call(iframe, context: { markdown: { render: { unsafe_: true } } }) + doc = MarkdownFilter.call(iframe, context: { markdown: { render: { unsafe: true } } }) assert_equal(doc, iframe_escaped) end def test_changing_extensions iframe = "" - doc = MarkdownFilter.call(iframe, context: { markdown: { extension: { tagfilter: false }, render: { unsafe_: true } } }) + doc = MarkdownFilter.call(iframe, context: { markdown: { extension: { tagfilter: false }, render: { unsafe: true } } }) assert_equal(doc, iframe) end def test_without_tagfilter - options = { render: { unsafe_: true }, extension: { tagfilter: false } } + options = { render: { unsafe: true }, extension: { tagfilter: false } } script = "" results = MarkdownFilter.call(script, context: { markdown: options }) assert_equal(results, script) end + + def test_renders_emoji + html = MarkdownFilter.call(":raccoon:") + result = "

🦝

" + + assert_equal(result, html) + end end end class GFMTest < Minitest::Test def setup @gfm = MarkdownFilter - @context = { markdown: { render: { unsafe_: true }, plugins: { syntax_highlighter: nil } } } + @context = { markdown: { render: { unsafe: true }, plugins: { syntax_highlighter: nil } } } end def test_not_touch_single_underscores_inside_words - assert_equal("

foo_bar

", - @gfm.call("foo_bar", context: @context)) + assert_equal( + "

foo_bar

", + @gfm.call("foo_bar", context: @context), + ) end def test_not_touch_underscores_in_code_blocks - assert_equal("
foo_bar_baz\n
", - @gfm.call(" foo_bar_baz", context: @context)) + assert_equal( + "
foo_bar_baz\n
", + @gfm.call(" foo_bar_baz", context: @context), + ) end def test_not_touch_underscores_in_pre_blocks - assert_equal("
\nfoo_bar_baz\n
", - @gfm.call("
\nfoo_bar_baz\n
", context: @context)) + assert_equal( + "
\nfoo_bar_baz\n
", + @gfm.call("
\nfoo_bar_baz\n
", context: @context), + ) end def test_not_touch_two_or_more_underscores_inside_words - assert_equal("

foo_bar_baz

", - @gfm.call("foo_bar_baz", context: @context)) + assert_equal( + "

foo_bar_baz

", + @gfm.call("foo_bar_baz", context: @context), + ) end def test_turn_newlines_into_br_tags_in_simple_cases - assert_equal("

foo
\nbar

", - @gfm.call("foo \nbar", context: @context)) + assert_equal( + "

foo
\nbar

", + @gfm.call("foo \nbar", context: @context), + ) end def test_convert_newlines_in_all_groups - assert_equal("

apple
\npear
\norange

\n" \ - "

ruby
\npython
\nerlang

", - @gfm.call("apple \npear \norange \n\nruby \npython \nerlang", context: @context)) + assert_equal( + "

apple
\npear
\norange

\n" \ + "

ruby
\npython
\nerlang

", + @gfm.call("apple \npear \norange \n\nruby \npython \nerlang", context: @context), + ) end def test_convert_newlines_in_even_long_groups - assert_equal("

apple
\npear
\norange
\nbanana

\n" \ - "

ruby
\npython
\nerlang

", - @gfm.call("apple \npear \norange \nbanana \n\nruby \npython \nerlang", context: @context)) + assert_equal( + "

apple
\npear
\norange
\nbanana

\n" \ + "

ruby
\npython
\nerlang

", + @gfm.call("apple \npear \norange \nbanana \n\nruby \npython \nerlang", context: @context), + ) end def test_not_convert_newlines_in_lists options = Commonmarker::Config.merged_with_defaults({}) options[:extension].delete(:header_ids) - assert_equal("

foo

\n

bar

", - @gfm.call("# foo\n# bar", context: { markdown: options })) - assert_equal("
    \n
  • foo
  • \n
  • bar
  • \n
", - @gfm.call("* foo\n* bar", context: { markdown: options })) + assert_equal( + "

foo

\n

bar

", + @gfm.call("# foo\n# bar", context: { markdown: options }), + ) + assert_equal( + "
    \n
  • foo
  • \n
  • bar
  • \n
", + @gfm.call("* foo\n* bar", context: { markdown: options }), + ) end end diff --git a/test/html_pipeline/node_filter/absolute_source_filter_test.rb b/test/html_pipeline/node_filter/absolute_source_filter_test.rb index 0cd36a10..b5d23aad 100644 --- a/test/html_pipeline/node_filter/absolute_source_filter_test.rb +++ b/test/html_pipeline/node_filter/absolute_source_filter_test.rb @@ -18,15 +18,19 @@ def setup def test_rewrites_root_urls orig = %(

) - assert_equal("

", - AbsoluteSourceFilter.call(orig, context: @options).to_s) + assert_equal( + "

", + AbsoluteSourceFilter.call(orig, context: @options).to_s, + ) end def test_rewrites_relative_urls orig = %(

) - assert_equal("

", - AbsoluteSourceFilter.call(orig, context: @options).to_s) + assert_equal( + "

", + AbsoluteSourceFilter.call(orig, context: @options).to_s, + ) end def test_does_not_rewrite_absolute_urls diff --git a/test/html_pipeline/node_filter/https_filter_test.rb b/test/html_pipeline/node_filter/https_filter_test.rb index 98eb25b9..ef52f7b1 100644 --- a/test/html_pipeline/node_filter/https_filter_test.rb +++ b/test/html_pipeline/node_filter/https_filter_test.rb @@ -11,23 +11,31 @@ def setup end def test_http - assert_equal(%(
github.com), - HttpsFilter.call(%(github.com), context: @options)) + assert_equal( + %(github.com), + HttpsFilter.call(%(github.com), context: @options), + ) end def test_https - assert_equal(%(github.com), - HttpsFilter.call(%(github.com), context: @options)) + assert_equal( + %(github.com), + HttpsFilter.call(%(github.com), context: @options), + ) end def test_subdomain - assert_equal(%(github.com), - HttpsFilter.call(%(github.com), context: @options)) + assert_equal( + %(github.com), + HttpsFilter.call(%(github.com), context: @options), + ) end def test_other - assert_equal(%(github.io), - HttpsFilter.call(%(github.io), context: @options)) + assert_equal( + %(github.io), + HttpsFilter.call(%(github.io), context: @options), + ) end end end diff --git a/test/html_pipeline/node_filter/image_max_width_filter_test.rb b/test/html_pipeline/node_filter/image_max_width_filter_test.rb index df7f4f9d..347bed50 100644 --- a/test/html_pipeline/node_filter/image_max_width_filter_test.rb +++ b/test/html_pipeline/node_filter/image_max_width_filter_test.rb @@ -12,8 +12,10 @@ def test_rewrites_image_style_tags body = '

Screenshot:

' res = @filter.call(body) - assert_equal('

Screenshot:

', - res) + assert_equal( + '

Screenshot:

', + res, + ) end def test_leaves_existing_image_style_tags_alone @@ -21,8 +23,10 @@ def test_leaves_existing_image_style_tags_alone res = @filter.call(body) - assert_equal('

', - res) + assert_equal( + '

', + res, + ) end def test_links_to_image @@ -30,8 +34,10 @@ def test_links_to_image res = @filter.call(body) - assert_equal('

Screenshot:

', - res) + assert_equal( + '

Screenshot:

', + res, + ) end def test_doesnt_link_to_image_when_already_linked @@ -39,8 +45,10 @@ def test_doesnt_link_to_image_when_already_linked res = @filter.call(body) - assert_equal('

Screenshot:

', - res) + assert_equal( + '

Screenshot:

', + res, + ) end def test_doesnt_screw_up_inlined_images diff --git a/test/html_pipeline/node_filter/mention_filter_test.rb b/test/html_pipeline/node_filter/mention_filter_test.rb index bcd9b2d1..7aee6b12 100644 --- a/test/html_pipeline/node_filter/mention_filter_test.rb +++ b/test/html_pipeline/node_filter/mention_filter_test.rb @@ -8,11 +8,13 @@ def setup @filter = HTMLPipeline::NodeFilter::MentionFilter @context = { base_url: "/", info_url: nil, username_pattern: nil } - @pipeline = HTMLPipeline.new(convert_filter: - HTMLPipeline::ConvertFilter::MarkdownFilter.new, + @pipeline = HTMLPipeline.new( + convert_filter: + HTMLPipeline::ConvertFilter::MarkdownFilter.new, node_filters: [ HTMLPipeline::NodeFilter::MentionFilter.new, - ]) + ], + ) end def mentioned_usernames(body) @@ -27,8 +29,10 @@ def test_filtering_plain_text link = '@kneath' - assert_equal("

#{link}: check it out.

", - res) + assert_equal( + "

#{link}: check it out.

", + res, + ) end def test_not_replacing_mentions_in_pre_tags @@ -65,32 +69,40 @@ def test_html_injection body = "

@kneath <script>alert(0)</script>

" link = '@kneath' - assert_equal("

#{link} <script>alert(0)</script>

", - @filter.call(body, context: @context)) + assert_equal( + "

#{link} <script>alert(0)</script>

", + @filter.call(body, context: @context), + ) end def test_base_url_slash body = "

Hi, @jch!

" link = '@jch' - assert_equal("

Hi, #{link}!

", - @filter.call(body, context: { base_url: "/" })) + assert_equal( + "

Hi, #{link}!

", + @filter.call(body, context: { base_url: "/" }), + ) end def test_base_url_under_custom_route body = "

Hi, @jch!

" link = '@jch' - assert_equal("

Hi, #{link}!

", - @filter.call(body, context: @context.merge({ base_url: "/userprofile" }))) + assert_equal( + "

Hi, #{link}!

", + @filter.call(body, context: @context.merge({ base_url: "/userprofile" })), + ) end def test_base_url_slash_with_tilde body = "

Hi, @jch!

" link = '@jch' - assert_equal("

Hi, #{link}!

", - @filter.call(body, context: @context.merge({ base_url: "/~" }))) + assert_equal( + "

Hi, #{link}!

", + @filter.call(body, context: @context.merge({ base_url: "/~" })), + ) end def test_matches_usernames_in_body @@ -190,8 +202,10 @@ def test_username_pattern_can_be_customized link = '@_abc' - assert_equal("

#{link}: test.

", - res) + assert_equal( + "

#{link}: test.

", + res, + ) end def test_filter_does_not_create_a_new_object_for_default_username_pattern diff --git a/test/html_pipeline/node_filter/syntax_highlight_filter_test.rb b/test/html_pipeline/node_filter/syntax_highlight_filter_test.rb new file mode 100644 index 00000000..a2b47e0b --- /dev/null +++ b/test/html_pipeline/node_filter/syntax_highlight_filter_test.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require "test_helper" + +SyntaxHighlightFilter = HTMLPipeline::NodeFilter::SyntaxHighlightFilter + +class HTMLPipeline + class SyntaxHighlightFilterTest < Minitest::Test + def test_highlight_default + result = SyntaxHighlightFilter.call(\ + "
hello
", context: { highlight: "coffeescript" } + ) + + doc = Nokogiri.parse(result) + + refute_empty(doc.css(".highlight")) + refute_empty(doc.css(".highlight-coffeescript")) + end + + def test_highlight_default_will_not_override + result = SyntaxHighlightFilter.call(\ + "
hello
", context: { highlight: "coffeescript" } + ) + + doc = Nokogiri.parse(result) + + assert_empty(doc.css(".highlight-coffeescript")) + refute_empty(doc.css(".highlight-c")) + end + + def test_highlight_does_not_remove_pre_tag + result = SyntaxHighlightFilter.call(\ + "
hello
", context: { highlight: "coffeescript" } + ) + + doc = Nokogiri.parse(result) + + refute_empty(doc.css("pre")) + end + + def test_highlight_allows_optional_scope + result = SyntaxHighlightFilter.call(\ + "
hello
", context: { highlight: "coffeescript", scope: "test-scope" } + ) + + doc = Nokogiri.parse(result) + + refute_empty(doc.css("pre.test-scope")) + end + + def test_highlight_keeps_the_pre_tags_lang + result = SyntaxHighlightFilter.call(\ + "
hello
", context: { highlight: "coffeescript" } + ) + + doc = Nokogiri.parse(result) + + refute_empty(doc.css("pre[lang=c]")) + end + end +end diff --git a/test/html_pipeline/node_filter/table_of_contents_filter_test.rb b/test/html_pipeline/node_filter/table_of_contents_filter_test.rb index 33b62889..1d651dcc 100644 --- a/test/html_pipeline/node_filter/table_of_contents_filter_test.rb +++ b/test/html_pipeline/node_filter/table_of_contents_filter_test.rb @@ -136,12 +136,14 @@ def test_toc_is_complete STR result = TocPipeline.call(orig)[:toc] - expected = [{ href: "#funky-president-by-james-brown", text: ""Funky President" by James Brown" }, - { href: "#its-my-thing-by-marva-whitney", text: ""It's My Thing" by Marva Whitney" }, - { href: "#boogie-back-by-roy-ayers", text: ""Boogie Back" by Roy Ayers" }, - { href: "#feel-good-by-fancy", text: ""Feel Good" by Fancy" }, - { href: "#funky-drummer-by-james-brown", text: ""Funky Drummer" by James Brown" }, - { href: "#ruthless-villain-by-eazy-e", text: ""Ruthless Villain" by Eazy-E" },] + expected = [ + { href: "#funky-president-by-james-brown", text: ""Funky President" by James Brown" }, + { href: "#its-my-thing-by-marva-whitney", text: ""It's My Thing" by Marva Whitney" }, + { href: "#boogie-back-by-roy-ayers", text: ""Boogie Back" by Roy Ayers" }, + { href: "#feel-good-by-fancy", text: ""Feel Good" by Fancy" }, + { href: "#funky-drummer-by-james-brown", text: ""Funky Drummer" by James Brown" }, + { href: "#ruthless-villain-by-eazy-e", text: ""Ruthless Villain" by Eazy-E" }, + ] 0..6.times do |i| assert_equal(expected[i], result[i]) @@ -157,10 +159,14 @@ def test_anchors_with_utf8_characters rendered_h1s = Nokogiri::HTML(TocPipeline.call(orig)[:output]).search("h1").map(&:to_s) - assert_equal("

\nζ—₯本θͺž

", - rendered_h1s[0]) - assert_equal("

\nРусский

", - rendered_h1s[1]) + assert_equal( + "

\nζ—₯本θͺž

", + rendered_h1s[0], + ) + assert_equal( + "

\nРусский

", + rendered_h1s[1], + ) end def test_toc_with_utf8_characters @@ -172,10 +178,16 @@ def test_toc_with_utf8_characters result = TocPipeline.call(orig)[:toc] - expected = [{ href: "#%E6%97%A5%E6%9C%AC%E8%AA%9E", - text: "ζ—₯本θͺž", }, - { href: "#%D1%80%D1%83%D1%81%D1%81%D0%BA%D0%B8%D0%B9", - text: "Русский", },] + expected = [ + { + href: "#%E6%97%A5%E6%9C%AC%E8%AA%9E", + text: "ζ—₯本θͺž", + }, + { + href: "#%D1%80%D1%83%D1%81%D1%81%D0%BA%D0%B8%D0%B9", + text: "Русский", + }, + ] 0..2.times do |i| assert_equal(expected[i], result[i]) diff --git a/test/html_pipeline/node_filter/team_mention_filter_test.rb b/test/html_pipeline/node_filter/team_mention_filter_test.rb index 4c93dbab..44bb3f59 100644 --- a/test/html_pipeline/node_filter/team_mention_filter_test.rb +++ b/test/html_pipeline/node_filter/team_mention_filter_test.rb @@ -8,10 +8,12 @@ def setup @filter = HTMLPipeline::NodeFilter::TeamMentionFilter @pipeline = - HTMLPipeline.new(convert_filter: HTMLPipeline::ConvertFilter::MarkdownFilter.new, + HTMLPipeline.new( + convert_filter: HTMLPipeline::ConvertFilter::MarkdownFilter.new, node_filters: [ HTMLPipeline::NodeFilter::TeamMentionFilter.new, - ]) + ], + ) end def mentioned_teams(body) @@ -26,8 +28,10 @@ def test_filtering_plain_text link = '@github/team' - assert_equal("

#{link}: check it out.

", - res) + assert_equal( + "

#{link}: check it out.

", + res, + ) end def test_not_replacing_mentions_in_pre_tags @@ -65,39 +69,49 @@ def test_html_injection body = "

@github/team <script>alert(0)</script>

" link = '@github/team' - assert_equal("

#{link} <script>alert(0)</script>

", - @filter.call(body, context: { base_url: "/" })) + assert_equal( + "

#{link} <script>alert(0)</script>

", + @filter.call(body, context: { base_url: "/" }), + ) end def test_links_to_nothing_with_user_mention body = "

Hi, @kneath

" - assert_equal("

Hi, @kneath

", - @filter.call(body, context: { base_url: "/" })) + assert_equal( + "

Hi, @kneath

", + @filter.call(body, context: { base_url: "/" }), + ) end def test_base_url_slash body = "

Hi, @github/team!

" link = '@github/team' - assert_equal("

Hi, #{link}!

", - @filter.call(body, context: { base_url: "/" })) + assert_equal( + "

Hi, #{link}!

", + @filter.call(body, context: { base_url: "/" }), + ) end def test_base_url_under_custom_route body = "

Hi, @org/team!

" link = '@org/team' - assert_equal("

Hi, #{link}!

", - @filter.call(body, context: { base_url: "www.github.com" })) + assert_equal( + "

Hi, #{link}!

", + @filter.call(body, context: { base_url: "www.github.com" }), + ) end def test_base_url_slash_with_tilde body = "

Hi, @github/team!

" link = '@github/team' - assert_equal("

Hi, #{link}!

", - @filter.call(body, context: { base_url: "/~" })) + assert_equal( + "

Hi, #{link}!

", + @filter.call(body, context: { base_url: "/~" }), + ) end def test_multiple_team_mentions @@ -105,8 +119,10 @@ def test_multiple_team_mentions link_whale = '@github/whale' link_donut = '@github/donut' - assert_equal("

Hi, #{link_whale} and #{link_donut}!

", - @filter.call(body)) + assert_equal( + "

Hi, #{link_whale} and #{link_donut}!

", + @filter.call(body), + ) end def test_matches_teams_in_body @@ -206,8 +222,10 @@ def test_team_pattern_can_be_customized link = '@_abc/XYZ' - assert_equal("

#{link}: test

", - res) + assert_equal( + "

#{link}: test

", + res, + ) end def test_mention_link_filter diff --git a/test/html_pipeline/text_filter/image_filter_test.rb b/test/html_pipeline/text_filter/image_filter_test.rb index be67c278..bc1a0709 100644 --- a/test/html_pipeline/text_filter/image_filter_test.rb +++ b/test/html_pipeline/text_filter/image_filter_test.rb @@ -11,33 +11,45 @@ def setup end def test_jpg - assert_equal(%(), - @filter.call(%(http://example.com/test.jpg))) + assert_equal( + %(), + @filter.call(%(http://example.com/test.jpg)), + ) end def test_jpeg - assert_equal(%(), - @filter.call(%(http://example.com/test.jpeg))) + assert_equal( + %(), + @filter.call(%(http://example.com/test.jpeg)), + ) end def test_bmp - assert_equal(%(), - @filter.call(%(http://example.com/test.bmp))) + assert_equal( + %(), + @filter.call(%(http://example.com/test.bmp)), + ) end def test_gif - assert_equal(%(), - @filter.call(%(http://example.com/test.gif))) + assert_equal( + %(), + @filter.call(%(http://example.com/test.gif)), + ) end def test_png - assert_equal(%(), - @filter.call(%(http://example.com/test.png))) + assert_equal( + %(), + @filter.call(%(http://example.com/test.png)), + ) end def test_https_url - assert_equal(%(), - @filter.call(%(https://example.com/test.png))) + assert_equal( + %(), + @filter.call(%(https://example.com/test.png)), + ) end end end diff --git a/test/html_pipeline/text_filter/plain_text_input_filter_test.rb b/test/html_pipeline/text_filter/plain_text_input_filter_test.rb index 742afab9..79bec1fc 100644 --- a/test/html_pipeline/text_filter/plain_text_input_filter_test.rb +++ b/test/html_pipeline/text_filter/plain_text_input_filter_test.rb @@ -21,8 +21,10 @@ def test_wraps_input_in_a_div_element def test_html_escapes_plain_text_input doc = PlainTextInputFilter.call("See: ", context: {}) - assert_equal("
See: <http://example.org>
", - doc.to_s) + assert_equal( + "
See: <http://example.org>
", + doc.to_s, + ) end end end diff --git a/test/sanitization_filter_test.rb b/test/sanitization_filter_test.rb index 5e26f4ff..25a12f56 100644 --- a/test/sanitization_filter_test.rb +++ b/test/sanitization_filter_test.rb @@ -105,9 +105,13 @@ def test_allow_svg_elements_to_be_added assert_equal("\n", html) - config = { elements: ["svg", "circle"], - attributes: { "svg" => ["width"], - "circle" => ["cx", "cy", "r"], }, } + config = { + elements: ["svg", "circle"], + attributes: { + "svg" => ["width"], + "circle" => ["cx", "cy", "r"], + }, + } result = <<~FRAG