motion-html-pipeline 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +379 -0
  3. data/lib/motion-html-pipeline.rb +14 -0
  4. data/lib/motion-html-pipeline/document_fragment.rb +27 -0
  5. data/lib/motion-html-pipeline/pipeline.rb +153 -0
  6. data/lib/motion-html-pipeline/pipeline/absolute_source_filter.rb +45 -0
  7. data/lib/motion-html-pipeline/pipeline/body_content.rb +42 -0
  8. data/lib/motion-html-pipeline/pipeline/disabled/@mention_filter.rb +140 -0
  9. data/lib/motion-html-pipeline/pipeline/disabled/autolink_filter.rb +27 -0
  10. data/lib/motion-html-pipeline/pipeline/disabled/camo_filter.rb +93 -0
  11. data/lib/motion-html-pipeline/pipeline/disabled/email_reply_filter.rb +66 -0
  12. data/lib/motion-html-pipeline/pipeline/disabled/emoji_filter.rb +125 -0
  13. data/lib/motion-html-pipeline/pipeline/disabled/markdown_filter.rb +37 -0
  14. data/lib/motion-html-pipeline/pipeline/disabled/plain_text_input_filter.rb +13 -0
  15. data/lib/motion-html-pipeline/pipeline/disabled/sanitization_filter.rb +137 -0
  16. data/lib/motion-html-pipeline/pipeline/disabled/syntax_highlight_filter.rb +44 -0
  17. data/lib/motion-html-pipeline/pipeline/disabled/toc_filter.rb +67 -0
  18. data/lib/motion-html-pipeline/pipeline/filter.rb +163 -0
  19. data/lib/motion-html-pipeline/pipeline/https_filter.rb +27 -0
  20. data/lib/motion-html-pipeline/pipeline/image_filter.rb +17 -0
  21. data/lib/motion-html-pipeline/pipeline/image_max_width_filter.rb +37 -0
  22. data/lib/motion-html-pipeline/pipeline/text_filter.rb +14 -0
  23. data/lib/motion-html-pipeline/pipeline/version.rb +5 -0
  24. data/spec/motion-html-pipeline/_helpers/mock_instumentation_service.rb +19 -0
  25. data/spec/motion-html-pipeline/pipeline/absolute_source_filter_spec.rb +47 -0
  26. data/spec/motion-html-pipeline/pipeline/disabled/auto_link_filter_spec.rb +33 -0
  27. data/spec/motion-html-pipeline/pipeline/disabled/camo_filter_spec.rb +75 -0
  28. data/spec/motion-html-pipeline/pipeline/disabled/email_reply_filter_spec.rb +64 -0
  29. data/spec/motion-html-pipeline/pipeline/disabled/emoji_filter_spec.rb +92 -0
  30. data/spec/motion-html-pipeline/pipeline/disabled/markdown_filter_spec.rb +112 -0
  31. data/spec/motion-html-pipeline/pipeline/disabled/plain_text_input_filter_spec.rb +20 -0
  32. data/spec/motion-html-pipeline/pipeline/disabled/sanitization_filter_spec.rb +164 -0
  33. data/spec/motion-html-pipeline/pipeline/disabled/syntax_highlighting_filter_spec.rb +59 -0
  34. data/spec/motion-html-pipeline/pipeline/disabled/toc_filter_spec.rb +137 -0
  35. data/spec/motion-html-pipeline/pipeline/https_filter_spec.rb +52 -0
  36. data/spec/motion-html-pipeline/pipeline/image_filter_spec.rb +37 -0
  37. data/spec/motion-html-pipeline/pipeline/image_max_width_filter_spec.rb +57 -0
  38. data/spec/motion-html-pipeline/pipeline_spec.rb +80 -0
  39. data/spec/spec_helper.rb +48 -0
  40. metadata +147 -0
@@ -0,0 +1,27 @@
1
+ module MotionHTMLPipeline
2
+ class Pipeline
3
+ # HTML Filter for replacing http references to :http_url with https versions.
4
+ # Subdomain references are not rewritten.
5
+ #
6
+ # Context options:
7
+ # :http_url - The HTTP url to force HTTPS. Falls back to :base_url
8
+ class HttpsFilter < Filter
9
+ def call
10
+ doc.css(%(a[href^="#{http_url}"])).each do |element|
11
+ element['href'] = element['href'].sub(/^http:/, 'https:')
12
+ end
13
+ doc
14
+ end
15
+
16
+ # HTTP url to replace. Falls back to :base_url
17
+ def http_url
18
+ context[:http_url] || context[:base_url]
19
+ end
20
+
21
+ # Raise error if :http_url undefined
22
+ def validate
23
+ needs :http_url unless http_url
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ module MotionHTMLPipeline
2
+ class Pipeline
3
+ # HTML Filter that converts image's url into <img> tag.
4
+ # For example, it will convert
5
+ # http://example.com/test.jpg
6
+ # into
7
+ # <img src="http://example.com/test.jpg" alt=""/>.
8
+
9
+ class ImageFilter < TextFilter
10
+ def call
11
+ @text.gsub(/(https|http)?:\/\/.+\.(jpg|jpeg|bmp|gif|png)(\?\S+)?/i) do |match|
12
+ %(<img src="#{match}" alt=""/>)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,37 @@
1
+ module MotionHTMLPipeline
2
+ class Pipeline
3
+ # This filter rewrites image tags with a max-width inline style and also wraps
4
+ # the image in an <a> tag that causes the full size image to be opened in a
5
+ # new tab.
6
+ #
7
+ # The max-width inline styles are especially useful in HTML email which
8
+ # don't use a global stylesheets.
9
+ class ImageMaxWidthFilter < Filter
10
+ def call
11
+ doc.css('img').each do |element|
12
+ # Skip if there's already a style attribute. Not sure how this
13
+ # would happen but we can reconsider it in the future.
14
+ next if element['style']
15
+
16
+ # Bail out if src doesn't look like a valid http url. trying to avoid weird
17
+ # js injection via javascript: urls.
18
+ next if element['src'].to_s.strip =~ /\Ajavascript/i
19
+
20
+ element['style'] = 'max-width:100%;'
21
+
22
+ link_image element unless has_ancestor?(element, %w[a])
23
+ end
24
+
25
+ doc
26
+ end
27
+
28
+ def link_image(element)
29
+ link = HTMLElement.alloc.initWithTagName('a', attributes: { href: element['src'], target: '_blank' })
30
+ link.appendNode(element.cloneNodeDeep(true))
31
+
32
+ parent = element.parentNode
33
+ parent.replaceChildNode(element, withNode: link)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,14 @@
1
+ module MotionHTMLPipeline
2
+ class Pipeline
3
+ class TextFilter < Filter
4
+ attr_reader :text
5
+
6
+ def initialize(text, context = nil, result = nil)
7
+ raise TypeError, 'text cannot be HTML' if text.is_a?(DocumentFragment)
8
+ # Ensure that this is always a string
9
+ @text = text.respond_to?(:to_str) ? text.to_str : text.to_s
10
+ super nil, context, result
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ module MotionHTMLPipeline
2
+ class Pipeline
3
+ VERSION = '0.1'.freeze
4
+ end
5
+ end
@@ -0,0 +1,19 @@
1
+ class MockedInstrumentationService
2
+ attr_reader :events
3
+ def initialize(event = nil, events = [])
4
+ @events = events
5
+ subscribe event
6
+ end
7
+
8
+ def instrument(event, payload = nil)
9
+ payload ||= {}
10
+ res = yield payload
11
+ events << [event, payload, res] if @subscribe == event
12
+ res
13
+ end
14
+
15
+ def subscribe(event)
16
+ @subscribe = event
17
+ @events
18
+ end
19
+ end
@@ -0,0 +1,47 @@
1
+ describe 'MotionHTMLPipeline::Pipeline::AbsoluteSourceFilterTest' do
2
+ AbsoluteSourceFilter = MotionHTMLPipeline::Pipeline::AbsoluteSourceFilter
3
+
4
+ before do
5
+ @image_base_url = 'http://assets.example.com'
6
+ @image_subpage_url = 'http://blog.example.com/a/post'
7
+ @options = {
8
+ image_base_url: @image_base_url,
9
+ image_subpage_url: @image_subpage_url
10
+ }
11
+ end
12
+
13
+ it 'test_rewrites_root_urls' do
14
+ orig = %(<p><img src="/img.png"></p>)
15
+
16
+ expect("<p><img src=\"#{@image_base_url}/img.png\"></p>")
17
+ .to eq AbsoluteSourceFilter.call(orig, @options).to_s
18
+ end
19
+
20
+ it 'test_rewrites_relative_urls' do
21
+ orig = %(<p><img src="post/img.png"></p>)
22
+
23
+ expect("<p><img src=\"#{@image_subpage_url}/img.png\"></p>")
24
+ .to eq AbsoluteSourceFilter.call(orig, @options).to_s
25
+ end
26
+
27
+ it 'test_does_not_rewrite_absolute_urls' do
28
+ orig = %(<p><img src="http://other.example.com/img.png"></p>)
29
+ result = AbsoluteSourceFilter.call(orig, @options).to_s
30
+
31
+ expect(result).not_to match(/@image_base_url/)
32
+ expect(result).not_to match(/@@image_subpage_url/)
33
+ end
34
+
35
+ it 'test_fails_when_context_is_missing' do
36
+ expect{ AbsoluteSourceFilter.call('<img src="img.png">', {}) }.to raise_error(RuntimeError)
37
+ expect{ AbsoluteSourceFilter.call('<img src="/img.png">', {}) }.to raise_error(RuntimeError)
38
+ end
39
+
40
+ it 'test_tells_you_where_context_is_required' do
41
+ expect{ AbsoluteSourceFilter.call('<img src="img.png">', {}) }
42
+ .to raise_error(RuntimeError, 'MotionHTMLPipeline::Pipeline::AbsoluteSourceFilter')
43
+
44
+ expect{ AbsoluteSourceFilter.call('<img src="/img.png">', {}) }
45
+ .to raise_error(RuntimeError, 'MotionHTMLPipeline::Pipeline::AbsoluteSourceFilter')
46
+ end
47
+ end
@@ -0,0 +1,33 @@
1
+ # describe 'MotionHTMLPipeline::Pipeline::AutolinkFilterTest' do
2
+ # AutolinkFilter = MotionHTMLPipeline::Pipeline::AutolinkFilter
3
+ #
4
+ # it '#test_uses_rinku_for_autolinking' do
5
+ # # just try to parse a complicated piece of HTML
6
+ # # that Rails auto_link cannot handle
7
+ # expect('<p>"<a href="http://www.github.com">http://www.github.com</a>"</p>')
8
+ # .to eq AutolinkFilter.to_html('<p>"http://www.github.com"</p>')
9
+ # end
10
+ #
11
+ # def test_autolink_option
12
+ # assert_equal '<p>"http://www.github.com"</p>',
13
+ # AutolinkFilter.to_html('<p>"http://www.github.com"</p>', autolink: false)
14
+ # end
15
+ #
16
+ # def test_autolink_link_attr
17
+ # assert_equal '<p>"<a href="http://www.github.com" target="_blank">http://www.github.com</a>"</p>',
18
+ # AutolinkFilter.to_html('<p>"http://www.github.com"</p>', link_attr: 'target="_blank"')
19
+ # end
20
+ #
21
+ # def test_autolink_flags
22
+ # assert_equal '<p>"<a href="http://github">http://github</a>"</p>',
23
+ # AutolinkFilter.to_html('<p>"http://github"</p>', flags: Rinku::AUTOLINK_SHORT_DOMAINS)
24
+ # end
25
+ #
26
+ # def test_autolink_skip_tags
27
+ # assert_equal '<code>"http://github.com"</code>',
28
+ # AutolinkFilter.to_html('<code>"http://github.com"</code>')
29
+ #
30
+ # assert_equal '<code>"<a href="http://github.com">http://github.com</a>"</code>',
31
+ # AutolinkFilter.to_html('<code>"http://github.com"</code>', skip_tags: %w[kbd script])
32
+ # end
33
+ # end
@@ -0,0 +1,75 @@
1
+ # describe 'MotionHTMLPipeline::Pipeline::CamoFilterTest' do
2
+ # CamoFilter = MotionHTMLPipeline::Pipeline::CamoFilter
3
+ #
4
+ # def setup
5
+ # @asset_proxy_url = 'https//assets.example.org'
6
+ # @asset_proxy_secret_key = 'ssssh-secret'
7
+ # @options = {
8
+ # asset_proxy: @asset_proxy_url,
9
+ # asset_proxy_secret_key: @asset_proxy_secret_key,
10
+ # asset_proxy_whitelist: [/(^|\.)github\.com$/]
11
+ # }
12
+ # end
13
+ #
14
+ # def test_asset_proxy_disabled
15
+ # orig = %(<p><img src="http://twitter.com/img.png"></p>)
16
+ # assert_equal orig,
17
+ # CamoFilter.call(orig, @options.merge(disable_asset_proxy: true)).to_s
18
+ # end
19
+ #
20
+ # def test_camouflaging_http_image_urls
21
+ # orig = %(<p><img src="http://twitter.com/img.png"></p>)
22
+ # assert_equal %(<p><img src="https//assets.example.org/a5ad43494e343b20d745586282be61ff530e6fa0/687474703a2f2f747769747465722e636f6d2f696d672e706e67" data-canonical-src="http://twitter.com/img.png"></p>),
23
+ # CamoFilter.call(orig, @options).to_s
24
+ # end
25
+ #
26
+ # def test_doesnt_rewrite_dotcom_image_urls
27
+ # orig = %(<p><img src="https://github.com/img.png"></p>)
28
+ # assert_equal orig, CamoFilter.call(orig, @options).to_s
29
+ # end
30
+ #
31
+ # def test_doesnt_rewrite_dotcom_subdomain_image_urls
32
+ # orig = %(<p><img src="https://raw.github.com/img.png"></p>)
33
+ # assert_equal orig, CamoFilter.call(orig, @options).to_s
34
+ # end
35
+ #
36
+ # def test_doesnt_rewrite_dotcom_subsubdomain_image_urls
37
+ # orig = %(<p><img src="https://f.assets.github.com/img.png"></p>)
38
+ # assert_equal orig, CamoFilter.call(orig, @options).to_s
39
+ # end
40
+ #
41
+ # def test_camouflaging_github_prefixed_image_urls
42
+ # orig = %(<p><img src="https://notgithub.com/img.png"></p>)
43
+ # assert_equal %(<p><img src="https//assets.example.org/5d4a96c69713f850520538e04cb9661035cfb534/68747470733a2f2f6e6f746769746875622e636f6d2f696d672e706e67" data-canonical-src="https://notgithub.com/img.png"></p>),
44
+ # CamoFilter.call(orig, @options).to_s
45
+ # end
46
+ #
47
+ # def test_doesnt_rewrite_absolute_image_urls
48
+ # orig = %(<p><img src="/img.png"></p>)
49
+ # assert_equal orig, CamoFilter.call(orig, @options).to_s
50
+ # end
51
+ #
52
+ # def test_doesnt_rewrite_relative_image_urls
53
+ # orig = %(<p><img src="img.png"></p>)
54
+ # assert_equal orig, CamoFilter.call(orig, @options).to_s
55
+ # end
56
+ #
57
+ # def test_camouflaging_https_image_urls
58
+ # orig = %(<p><img src="https://foo.com/img.png"></p>)
59
+ # assert_equal %(<p><img src="https//assets.example.org/3c5c6dc74fd6592d2596209dfcb8b7e5461383c8/68747470733a2f2f666f6f2e636f6d2f696d672e706e67" data-canonical-src="https://foo.com/img.png"></p>),
60
+ # CamoFilter.call(orig, @options).to_s
61
+ # end
62
+ #
63
+ # def test_handling_images_with_no_src_attribute
64
+ # orig = %(<p><img></p>)
65
+ # assert_equal orig, CamoFilter.call(orig, @options).to_s
66
+ # end
67
+ #
68
+ # def test_required_context_validation
69
+ # exception = assert_raises(ArgumentError) do
70
+ # CamoFilter.call('', {})
71
+ # end
72
+ # assert_match /:asset_proxy[^_]/, exception.message
73
+ # assert_match /:asset_proxy_secret_key/, exception.message
74
+ # end
75
+ # end
@@ -0,0 +1,64 @@
1
+ # describe 'MotionHTMLPipeline::Pipeline::EmailReplyFilterTest' do
2
+ # EmailReplyFilter = MotionHTMLPipeline::Pipeline::EmailReplyFilter
3
+ #
4
+ # def setup
5
+ # @body = <<-EMAIL
6
+ # Hey, don't send email addresses in comments. They aren't filtered.
7
+ #
8
+ # > On Mar 5, 2016, at 08:05, Boaty McBoatface <boatymcboatface@example.com> wrote:
9
+ # >
10
+ # > Sup. alreadyleaked@example.com
11
+ # >
12
+ # > —
13
+ # > Reply to this email directly or view it on GitHub.
14
+ # EMAIL
15
+ # end
16
+ #
17
+ # def test_doesnt_hide_by_default
18
+ # filter = EmailReplyFilter.new(@body)
19
+ # doc = filter.call.to_s
20
+ # assert_match /alreadyleaked@example.com/, doc
21
+ # assert_match /boatymcboatface@example.com/, doc
22
+ # end
23
+ #
24
+ # def test_hides_email_addresses_when_configured
25
+ # filter = EmailReplyFilter.new(@body, hide_quoted_email_addresses: true)
26
+ # doc = filter.call.to_s
27
+ # refute_match /boatymcboatface@example.com/, doc
28
+ # refute_match /alreadyleaked@example.com/, doc
29
+ # end
30
+ #
31
+ # def test_preserves_non_email_content_while_filtering
32
+ # str = <<-EMAIL
33
+ # > Thank you! I have some thoughts on this pull request.
34
+ # >
35
+ # > * acme provides cmake and a wrapper for it. Please use '$(TARGET)-cmake' instead of cmake -DCMAKE_TOOLCHAIN_FILE='$(CMAKE_TOOLCHAIN_FILE)' -DCMAKE_BUILD_TYPE=Release.
36
+ #
37
+ # Okay -- I'm afraid I just blindly copied the eigen3.mk file, since that's a library I'm familiar with :-)
38
+ #
39
+ # > * Do you need -DCMAKE_SYSTEM_PROCESSOR=x86?
40
+ #
41
+ # Yes, this is a bit dumb, but vc checks for that (or amd) to determine that it's not being built on ARM.
42
+ #
43
+ # --
44
+ # Boaty McBoatface | http://example.org
45
+ # EMAIL
46
+ #
47
+ # filter = EmailReplyFilter.new(str, hide_quoted_email_addresses: true)
48
+ # doc = filter.call.to_s
49
+ #
50
+ # expected = <<-EXPECTED
51
+ # <div class="email-quoted-reply"> Thank you! I have some thoughts on this pull request.
52
+ #
53
+ # * acme provides cmake and a wrapper for it. Please use &#39;$(TARGET)-cmake&#39; instead of cmake -DCMAKE_TOOLCHAIN_FILE=&#39;$(CMAKE_TOOLCHAIN_FILE)&#39; -DCMAKE_BUILD_TYPE=Release.</div>
54
+ # <div class="email-fragment">Okay -- I&#39;m afraid I just blindly copied the eigen3.mk file, since that&#39;s a library I&#39;m familiar with :-)</div>
55
+ # <div class="email-quoted-reply"> * Do you need -DCMAKE_SYSTEM_PROCESSOR=x86?</div>
56
+ # <div class="email-fragment">Yes, this is a bit dumb, but vc checks for that (or amd) to determine that it&#39;s not being built on ARM.</div>
57
+ # <span class="email-hidden-toggle"><a href="#">&hellip;</a></span><div class="email-hidden-reply" style="display:none"><div class="email-signature-reply">--
58
+ # Boaty McBoatface | http:&#47;&#47;example.org</div>
59
+ # </div>
60
+ # EXPECTED
61
+ #
62
+ # assert_equal(expected.chomp, doc)
63
+ # end
64
+ # end
@@ -0,0 +1,92 @@
1
+ # describe 'MotionHTMLPipeline::Pipeline::EmojiFilterTest' do
2
+ # EmojiFilter = MotionHTMLPipeline::Pipeline::EmojiFilter
3
+ #
4
+ # def test_emojify
5
+ # filter = EmojiFilter.new('<p>:shipit:</p>', asset_root: 'https://foo.com')
6
+ # doc = filter.call
7
+ # assert_match 'https://foo.com/emoji/shipit.png', doc.search('img').attr('src').value
8
+ # end
9
+ #
10
+ # def test_uri_encoding
11
+ # filter = EmojiFilter.new('<p>:+1:</p>', asset_root: 'https://foo.com')
12
+ # doc = filter.call
13
+ # assert_match 'https://foo.com/emoji/unicode/1f44d.png', doc.search('img').attr('src').value
14
+ # end
15
+ #
16
+ # def test_required_context_validation
17
+ # exception = assert_raises(ArgumentError) do
18
+ # EmojiFilter.call('', {})
19
+ # end
20
+ # assert_match /:asset_root/, exception.message
21
+ # end
22
+ #
23
+ # def test_custom_asset_path
24
+ # filter = EmojiFilter.new('<p>:+1:</p>', asset_path: ':file_name', asset_root: 'https://foo.com')
25
+ # doc = filter.call
26
+ # assert_match 'https://foo.com/unicode/1f44d.png', doc.search('img').attr('src').value
27
+ # end
28
+ #
29
+ # def test_not_emojify_in_code_tags
30
+ # body = '<code>:shipit:</code>'
31
+ # filter = EmojiFilter.new(body, asset_root: 'https://foo.com')
32
+ # doc = filter.call
33
+ # assert_equal body, doc.to_html
34
+ # end
35
+ #
36
+ # def test_not_emojify_in_tt_tags
37
+ # body = '<tt>:shipit:</tt>'
38
+ # filter = EmojiFilter.new(body, asset_root: 'https://foo.com')
39
+ # doc = filter.call
40
+ # assert_equal body, doc.to_html
41
+ # end
42
+ #
43
+ # def test_not_emojify_in_pre_tags
44
+ # body = '<pre>:shipit:</pre>'
45
+ # filter = EmojiFilter.new(body, asset_root: 'https://foo.com')
46
+ # doc = filter.call
47
+ # assert_equal body, doc.to_html
48
+ # end
49
+ #
50
+ # def test_not_emojify_in_custom_single_tag_foo
51
+ # body = '<foo>:shipit:</foo>'
52
+ # filter = EmojiFilter.new(body, asset_root: 'https://foo.com', ignored_ancestor_tags: %w[foo])
53
+ # doc = filter.call
54
+ # assert_equal body, doc.to_html
55
+ # end
56
+ #
57
+ # def test_not_emojify_in_custom_multiple_tags_foo_and_bar
58
+ # body = '<bar>:shipit:</bar>'
59
+ # filter = EmojiFilter.new(body, asset_root: 'https://foo.com', ignored_ancestor_tags: %w[foo bar])
60
+ # doc = filter.call
61
+ # assert_equal body, doc.to_html
62
+ # end
63
+ #
64
+ # def test_img_tag_attributes
65
+ # body = ':shipit:'
66
+ # filter = EmojiFilter.new(body, asset_root: 'https://foo.com')
67
+ # doc = filter.call
68
+ # assert_equal %(<img class="emoji" title=":shipit:" alt=":shipit:" src="https://foo.com/emoji/shipit.png" height="20" width="20" align="absmiddle">), doc.to_html
69
+ # end
70
+ #
71
+ # def test_img_tag_attributes_can_be_customized
72
+ # body = ':shipit:'
73
+ # filter = EmojiFilter.new(body, asset_root: 'https://foo.com', img_attrs: Hash('draggable' => 'false', 'height' => nil, 'width' => nil, 'align' => nil))
74
+ # doc = filter.call
75
+ # assert_equal %(<img class="emoji" title=":shipit:" alt=":shipit:" src="https://foo.com/emoji/shipit.png" draggable="false">), doc.to_html
76
+ # end
77
+ #
78
+ # def test_img_attrs_value_can_accept_proclike_object
79
+ # remove_colons = ->(name) { name.delete(':') }
80
+ # body = ':shipit:'
81
+ # filter = EmojiFilter.new(body, asset_root: 'https://foo.com', img_attrs: Hash('title' => remove_colons))
82
+ # doc = filter.call
83
+ # assert_equal %(<img class="emoji" title="shipit" alt=":shipit:" src="https://foo.com/emoji/shipit.png" height="20" width="20" align="absmiddle">), doc.to_html
84
+ # end
85
+ #
86
+ # def test_img_attrs_can_accept_symbolized_keys
87
+ # body = ':shipit:'
88
+ # filter = EmojiFilter.new(body, asset_root: 'https://foo.com', img_attrs: Hash(draggable: false, height: nil, width: nil, align: nil))
89
+ # doc = filter.call
90
+ # assert_equal %(<img class="emoji" title=":shipit:" alt=":shipit:" src="https://foo.com/emoji/shipit.png" draggable="false">), doc.to_html
91
+ # end
92
+ # end