slack-notifier 1.5.1 → 2.4.0

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 (33) hide show
  1. checksums.yaml +5 -5
  2. data/lib/slack-notifier/config.rb +43 -0
  3. data/lib/slack-notifier/payload_middleware/at.rb +36 -0
  4. data/lib/slack-notifier/payload_middleware/base.rb +34 -0
  5. data/lib/slack-notifier/payload_middleware/channels.rb +21 -0
  6. data/lib/slack-notifier/payload_middleware/format_attachments.rb +44 -0
  7. data/lib/slack-notifier/payload_middleware/format_message.rb +20 -0
  8. data/lib/slack-notifier/payload_middleware/stack.rb +50 -0
  9. data/lib/slack-notifier/payload_middleware.rb +24 -0
  10. data/lib/slack-notifier/util/escape.rb +16 -0
  11. data/lib/slack-notifier/util/http_client.rb +64 -0
  12. data/lib/slack-notifier/util/link_formatter.rb +80 -0
  13. data/lib/slack-notifier/version.rb +3 -1
  14. data/lib/slack-notifier.rb +38 -60
  15. data/spec/end_to_end_spec.rb +95 -0
  16. data/spec/integration/ping_integration_test.rb +14 -5
  17. data/spec/lib/slack-notifier/config_spec.rb +71 -0
  18. data/spec/lib/slack-notifier/payload_middleware/at_spec.rb +25 -0
  19. data/spec/lib/slack-notifier/payload_middleware/base_spec.rb +76 -0
  20. data/spec/lib/slack-notifier/payload_middleware/channels_spec.rb +20 -0
  21. data/spec/lib/slack-notifier/payload_middleware/format_attachments_spec.rb +48 -0
  22. data/spec/lib/slack-notifier/payload_middleware/format_message_spec.rb +27 -0
  23. data/spec/lib/slack-notifier/payload_middleware/stack_spec.rb +119 -0
  24. data/spec/lib/slack-notifier/payload_middleware_spec.rb +33 -0
  25. data/spec/lib/slack-notifier/util/http_client_spec.rb +55 -0
  26. data/spec/lib/slack-notifier/util/link_formatter_spec.rb +163 -0
  27. data/spec/lib/slack-notifier_spec.rb +63 -128
  28. data/spec/spec_helper.rb +20 -5
  29. metadata +39 -13
  30. data/lib/slack-notifier/default_http_client.rb +0 -51
  31. data/lib/slack-notifier/link_formatter.rb +0 -62
  32. data/spec/lib/slack-notifier/default_http_client_spec.rb +0 -37
  33. data/spec/lib/slack-notifier/link_formatter_spec.rb +0 -78
@@ -1,7 +1,16 @@
1
+ # frozen_string_literal: true
1
2
  # encoding: utf-8
2
- require_relative '../../lib/slack-notifier'
3
3
 
4
- notifier = Slack::Notifier.new ENV['SLACK_WEBHOOK_URL'], username: 'notifier'
5
- puts "testing with ruby #{RUBY_VERSION}"
6
- notifier.ping "hello/こんにちは from notifier test script on ruby: #{RUBY_VERSION}\225"
7
- notifier.ping attachments: [{color:"#1BF5AF",fallback:"fallback",text:"attachment"}]
4
+ require_relative "../../lib/slack-notifier"
5
+
6
+ ruby = if defined?(JRUBY_VERSION)
7
+ "jruby #{JRUBY_VERSION}"
8
+ else
9
+ "ruby #{RUBY_VERSION}"
10
+ end
11
+ puts "testing with #{ruby}"
12
+
13
+ notifier = Slack::Notifier.new ENV["SLACK_WEBHOOK_URL"], username: "notifier"
14
+ notifier.ping "hello", channel: ["#general", "#random"]
15
+ notifier.ping "hello/こんにちは from notifier test script on #{ruby}\225"
16
+ notifier.ping attachments: [{ color: "#1BF5AF", fallback: "fallback", text: "attachment" }]
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Slack::Notifier::Config do
4
+ describe "#http_client" do
5
+ it "is Util::HTTPClient if not set" do
6
+ subject = described_class.new
7
+ expect(subject.http_client).to eq Slack::Notifier::Util::HTTPClient
8
+ end
9
+
10
+ it "sets a new client class if given one" do
11
+ new_client = class_double("Slack::Notifier::Util::HTTPClient", post: nil)
12
+ subject = described_class.new
13
+ subject.http_client new_client
14
+ expect(subject.http_client).to eq new_client
15
+ end
16
+
17
+ it "raises an ArgumentError if given class does not respond to ::post" do
18
+ subject = described_class.new
19
+ expect do
20
+ subject.http_client :nope
21
+ end.to raise_error ArgumentError
22
+ end
23
+ end
24
+
25
+ describe "#defaults" do
26
+ it "is an empty hash by default" do
27
+ subject = described_class.new
28
+ expect(subject.defaults).to eq({})
29
+ end
30
+
31
+ it "sets a hash to default if given" do
32
+ subject = described_class.new
33
+ subject.defaults foo: :bar
34
+ expect(subject.defaults).to eq foo: :bar
35
+ end
36
+
37
+ it "raises ArgumentError if not given a hash" do
38
+ subject = described_class.new
39
+ expect do
40
+ subject.defaults :nope
41
+ end.to raise_error ArgumentError
42
+ end
43
+ end
44
+
45
+ describe "#middleware" do
46
+ it "is [:format_message, :format_attachments, :at] if not set" do
47
+ subject = described_class.new
48
+
49
+ expect(subject.middleware).to eq %i[format_message format_attachments at channels]
50
+ end
51
+
52
+ it "takes an array or a splat of args" do
53
+ subject = described_class.new
54
+
55
+ subject.middleware :layer, :two
56
+ expect(subject.middleware).to eq %i[layer two]
57
+
58
+ subject.middleware %i[one layer]
59
+ expect(subject.middleware).to eq %i[one layer]
60
+ end
61
+
62
+ it "allows passing options to middleware stack" do
63
+ subject = described_class.new
64
+ subject.middleware one: { opts: :for_one },
65
+ two: { opts: :for_two }
66
+
67
+ expect(subject.middleware).to eq one: { opts: :for_one },
68
+ two: { opts: :for_two }
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Slack::Notifier::PayloadMiddleware::At do
4
+ it "can handle array at" do
5
+ subject = described_class.new(:notifier)
6
+ payload = { text: "hello", at: %i[john ken here] }
7
+
8
+ expect(subject.call(payload)).to eq text: "<@john> <@ken> <!here> hello"
9
+ end
10
+
11
+ it "can handle single at option" do
12
+ subject = described_class.new(:notifier)
13
+ payload = { text: "hello", at: :alice }
14
+
15
+ expect(subject.call(payload)).to eq text: "<@alice> hello"
16
+ end
17
+
18
+ it "generates :text in payload if given :at & no :text" do
19
+ subject = described_class.new(:notifier)
20
+ input_payload = { at: [:here], attachments: [{ text: "hello" }] }
21
+ output_payload = { text: "<!here> ", attachments: [{ text: "hello" }] }
22
+
23
+ expect(subject.call(input_payload)).to eq output_payload
24
+ end
25
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Slack::Notifier::PayloadMiddleware::Base do
4
+ before(:each) do
5
+ @registry_backup = Slack::Notifier::PayloadMiddleware.registry.dup
6
+ Slack::Notifier::PayloadMiddleware.send(:remove_instance_variable, :@registry)
7
+ end
8
+
9
+ after(:each) do
10
+ # cleanup middleware registry
11
+ Slack::Notifier::PayloadMiddleware.registry
12
+ Slack::Notifier::PayloadMiddleware.send(:remove_instance_variable, :@registry)
13
+
14
+ # cleanup object constants
15
+ Object.send(:remove_const, :Subject) if Object.constants.include?(:Subject)
16
+ Slack::Notifier::PayloadMiddleware.send(:instance_variable_set, :@registry, @registry_backup)
17
+ end
18
+
19
+ describe "::middleware_name" do
20
+ it "registers class w/ given name" do
21
+ class Subject < Slack::Notifier::PayloadMiddleware::Base
22
+ end
23
+
24
+ expect(Slack::Notifier::PayloadMiddleware)
25
+ .to receive(:register).with(Subject, :subject)
26
+
27
+ class Subject
28
+ middleware_name :subject
29
+ end
30
+ end
31
+
32
+ it "uses symbolized name to register" do
33
+ class Subject < Slack::Notifier::PayloadMiddleware::Base
34
+ end
35
+
36
+ expect(Slack::Notifier::PayloadMiddleware)
37
+ .to receive(:register).with(Subject, :subject)
38
+
39
+ class Subject
40
+ middleware_name "subject"
41
+ end
42
+ end
43
+ end
44
+
45
+ describe "::options" do
46
+ it "allows setting default options for a middleware" do
47
+ class Subject < Slack::Notifier::PayloadMiddleware::Base
48
+ options foo: :bar
49
+ end
50
+
51
+ subject = Subject.new(:notifier)
52
+ expect(subject.options).to eq foo: :bar
53
+
54
+ subject = Subject.new(:notifier, foo: :baz)
55
+ expect(subject.options).to eq foo: :baz
56
+ end
57
+ end
58
+
59
+ describe "#initialize" do
60
+ it "sets given notifier as notifier" do
61
+ expect(described_class.new(:notifier).notifier).to eq :notifier
62
+ end
63
+
64
+ it "sets given options as opts" do
65
+ expect(described_class.new(:notifier, opts: :options).options).to eq opts: :options
66
+ end
67
+ end
68
+
69
+ describe "#call" do
70
+ it "raises NoMethodError (expects subclass to define)" do
71
+ expect do
72
+ described_class.new(:notifier).call
73
+ end.to raise_exception NoMethodError
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Slack::Notifier::PayloadMiddleware::Channels do
4
+ it "leaves string channels alone" do
5
+ subject = described_class.new(:notifier)
6
+ payload = { text: "hello", channel: "hodor" }
7
+
8
+ expect(subject.call(payload)).to eq text: "hello", channel: "hodor"
9
+ end
10
+
11
+ it "splits payload into multiple if given an array of channels" do
12
+ subject = described_class.new(:notifier)
13
+ payload = { text: "hello", channel: %w[foo hodor] }
14
+
15
+ expect(subject.call(payload)).to eq [
16
+ { text: "hello", channel: "foo" },
17
+ { text: "hello", channel: "hodor" }
18
+ ]
19
+ end
20
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Slack::Notifier::PayloadMiddleware::FormatAttachments do
4
+ it "passes the text of attachments through linkformatter with options[:formats]" do
5
+ subject = described_class.new(:notifier, formats: [:html])
6
+ expect(Slack::Notifier::Util::LinkFormatter).to receive(:format)
7
+ .with("hello", formats: [:html])
8
+ subject.call(attachments: [{ text: "hello" }])
9
+ end
10
+
11
+ it "searches through string or symbol keys" do
12
+ subject = described_class.new(:notifier)
13
+ expect(Slack::Notifier::Util::LinkFormatter).to receive(:format)
14
+ .with("hello", formats: %i[html markdown])
15
+ subject.call("attachments" => [{ "text" => "hello" }])
16
+
17
+ subject = described_class.new(:notifier)
18
+ expect(Slack::Notifier::Util::LinkFormatter).to receive(:format)
19
+ .with("hello", formats: %i[html markdown])
20
+ subject.call(attachments: [{ text: "hello" }])
21
+ end
22
+
23
+ it "can handle a single attachment" do
24
+ subject = described_class.new(:notifier)
25
+ expect(Slack::Notifier::Util::LinkFormatter).to receive(:format)
26
+ .with("hello", formats: %i[html markdown])
27
+ subject.call(attachments: { text: "hello" })
28
+ end
29
+
30
+ it "wraps attachment into array if given as a single hash" do
31
+ params = {
32
+ attachments: { text: "hello" }
33
+ }
34
+ payload = {
35
+ attachments: [{ text: "hello" }]
36
+ }
37
+ subject = described_class.new(:notifier)
38
+
39
+ expect(subject.call(params)).to eq payload
40
+ end
41
+
42
+ it "returns the payload unmodified if not :attachments key" do
43
+ payload = { foo: :bar }
44
+ subject = described_class.new(:notifier)
45
+
46
+ expect(subject.call(payload)).to eq payload
47
+ end
48
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Slack::Notifier::PayloadMiddleware::FormatMessage do
4
+ it "passes the text through linkformatter with options[:formats]" do
5
+ subject = described_class.new(:notifier, formats: [:html])
6
+ expect(Slack::Notifier::Util::LinkFormatter).to receive(:format)
7
+ .with("hello", formats: [:html])
8
+ subject.call(text: "hello")
9
+
10
+ subject = described_class.new(:notifier)
11
+ expect(Slack::Notifier::Util::LinkFormatter).to receive(:format)
12
+ .with("hello", formats: %i[html markdown])
13
+ subject.call(text: "hello")
14
+
15
+ subject = described_class.new(:notifier, formats: [:markdown])
16
+ expect(Slack::Notifier::Util::LinkFormatter).to receive(:format)
17
+ .with("hello", formats: [:markdown])
18
+ subject.call(text: "hello")
19
+ end
20
+
21
+ it "returns the payload unmodified if not :text key" do
22
+ payload = { foo: :bar }
23
+ subject = described_class.new(:notifier)
24
+
25
+ expect(subject.call(payload)).to eq payload
26
+ end
27
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Slack::Notifier::PayloadMiddleware::Stack do
4
+ let(:return_one) do
5
+ double(call: 1)
6
+ end
7
+
8
+ let(:return_one_twice) do
9
+ double(call: [1, 1])
10
+ end
11
+
12
+ let(:return_two) do
13
+ double(call: 2)
14
+ end
15
+
16
+ let(:return_three) do
17
+ double(call: 3)
18
+ end
19
+
20
+ before(:each) do
21
+ # setup our middleware in the registry
22
+ @registry_backup = Slack::Notifier::PayloadMiddleware.registry.dup
23
+ Slack::Notifier::PayloadMiddleware.send(:remove_instance_variable, :@registry)
24
+
25
+ Slack::Notifier::PayloadMiddleware.register return_one, :return_one
26
+ Slack::Notifier::PayloadMiddleware.register return_one_twice, :return_one_twice
27
+ Slack::Notifier::PayloadMiddleware.register return_two, :return_two
28
+ Slack::Notifier::PayloadMiddleware.register return_three, :return_three
29
+ end
30
+
31
+ after(:each) do
32
+ # cleanup middleware registry
33
+ Slack::Notifier::PayloadMiddleware.send(:remove_instance_variable, :@registry)
34
+ Slack::Notifier::PayloadMiddleware.send(:instance_variable_set, :@registry, @registry_backup)
35
+ end
36
+
37
+ describe "::initialize" do
38
+ it "sets notifier to given notifier" do
39
+ expect(described_class.new(:notifier).notifier).to eq :notifier
40
+ end
41
+
42
+ it "has empty stack" do
43
+ expect(described_class.new(:notifier).stack).to match_array []
44
+ end
45
+ end
46
+
47
+ describe "#set" do
48
+ it "initializes each middleware w/ the notifier instance" do
49
+ expect(return_one).to receive(:new).with(:notifier)
50
+ expect(return_two).to receive(:new).with(:notifier)
51
+
52
+ described_class.new(:notifier).set(:return_one, :return_two)
53
+ end
54
+
55
+ it "creates the stack in an array" do
56
+ allow(return_one).to receive(:new).and_return(return_one)
57
+ allow(return_two).to receive(:new).and_return(return_two)
58
+
59
+ subject = described_class.new(:notifier)
60
+ subject.set(:return_one, :return_two)
61
+
62
+ expect(subject.stack).to be_a Array
63
+ expect(subject.stack.first.call).to eq 1
64
+ expect(subject.stack.last.call).to eq 2
65
+ end
66
+
67
+ it "creates a stack from hashes passing them as opts" do
68
+ expect(return_one).to receive(:new).with(:notifier, opts: :for_one)
69
+ expect(return_two).to receive(:new).with(:notifier, opts: :for_two)
70
+
71
+ subject = described_class.new(:notifier)
72
+ subject.set return_one: { opts: :for_one },
73
+ return_two: { opts: :for_two }
74
+ end
75
+
76
+ it "raises if a middleware is missing" do
77
+ expect do
78
+ described_class.new(:notifier).set(:missing)
79
+ end.to raise_exception KeyError
80
+ end
81
+ end
82
+
83
+ describe "#call" do
84
+ it "calls the middleware in order, passing return of each to the next" do
85
+ allow(return_one).to receive(:new).and_return(return_one)
86
+ allow(return_two).to receive(:new).and_return(return_two)
87
+ allow(return_three).to receive(:new).and_return(return_three)
88
+
89
+ subject = described_class.new(:notifier)
90
+ subject.set(:return_one, :return_three, :return_two)
91
+
92
+ expect(return_one).to receive(:call).with(5)
93
+ expect(return_three).to receive(:call).with(1)
94
+ expect(return_two).to receive(:call).with(3)
95
+
96
+ expect(subject.call(5)).to eq [2]
97
+ end
98
+
99
+ it "allows any middleware to return an array but other's don't need special behavior" do
100
+ allow(return_one_twice).to receive(:new).and_return(return_one_twice)
101
+ allow(return_two).to receive(:new).and_return(return_two)
102
+
103
+ subject = described_class.new(:notifier)
104
+ subject.set(:return_one_twice, :return_two)
105
+
106
+ expect(subject.call(5)).to eq [2, 2]
107
+ end
108
+
109
+ it "handles multiple middleware splitting payload" do
110
+ allow(return_one_twice).to receive(:new).and_return(return_one_twice)
111
+ allow(return_two).to receive(:new).and_return(return_two)
112
+
113
+ subject = described_class.new(:notifier)
114
+ subject.set(:return_one_twice, :return_one_twice, :return_two)
115
+
116
+ expect(subject.call(5)).to eq [2, 2, 2, 2]
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Slack::Notifier::PayloadMiddleware do
4
+ before(:each) do
5
+ @registry_backup = described_class.registry.dup
6
+ Slack::Notifier::PayloadMiddleware.send(:remove_instance_variable, :@registry)
7
+ end
8
+
9
+ after(:each) do
10
+ described_class.send(:remove_instance_variable, :@registry)
11
+ described_class.send(:instance_variable_set, :@registry, @registry_backup)
12
+ end
13
+
14
+ describe "::registry" do
15
+ it "returns a hash if nothing set" do
16
+ expect(described_class.registry).to eq({})
17
+ end
18
+
19
+ it "returns memoized version if already set" do
20
+ described_class.instance_variable_set(:@registry, "hodor")
21
+ expect(described_class.registry).to eq "hodor"
22
+ end
23
+ end
24
+
25
+ describe "::register" do
26
+ it "adds given class to key in registry" do
27
+ MyClass = Struct.new(:myclass)
28
+ described_class.register MyClass, :my_class
29
+
30
+ expect(described_class.registry[:my_class]).to eq MyClass
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Slack::Notifier::Util::HTTPClient do
4
+ describe "::post" do
5
+ it "initializes Util::HTTPClient with the given uri and params then calls" do
6
+ http_post_double = instance_double("Slack::Notifier::Util::HTTPClient")
7
+
8
+ expect(described_class)
9
+ .to receive(:new).with("uri", "params")
10
+ .and_return(http_post_double)
11
+ expect(http_post_double).to receive(:call)
12
+
13
+ described_class.post "uri", "params"
14
+ end
15
+
16
+ # http_post is really tested in the integration spec,
17
+ # where the internals are run through
18
+ end
19
+
20
+ describe "#initialize" do
21
+ it "allows setting of options for Net::HTTP" do
22
+ net_http_double = instance_double("Net::HTTP")
23
+ http_client = described_class.new URI.parse("http://example.com"),
24
+ http_options: { open_timeout: 5 }
25
+
26
+ allow(Net::HTTP).to receive(:new).and_return(net_http_double)
27
+ allow(net_http_double).to receive(:use_ssl=)
28
+ allow(net_http_double).to receive(:request).with(anything) do
29
+ Net::HTTPOK.new("GET", "200", "OK")
30
+ end
31
+
32
+ expect(net_http_double).to receive(:open_timeout=).with(5)
33
+
34
+ http_client.call
35
+ end
36
+ end
37
+
38
+ describe "#call" do
39
+ it "raises an error when the response is unsuccessful" do
40
+ net_http_double = instance_double("Net::HTTP")
41
+ http_client = described_class.new URI.parse("http://example.com"), {}
42
+ bad_request = Net::HTTPBadRequest.new("GET", "400", "Bad Request")
43
+
44
+ allow(bad_request).to receive(:body).and_return("something_bad")
45
+ allow(Net::HTTP).to receive(:new).and_return(net_http_double)
46
+ allow(net_http_double).to receive(:use_ssl=)
47
+ allow(net_http_double).to receive(:request).with(anything) do
48
+ bad_request
49
+ end
50
+
51
+ expect { http_client.call }.to raise_error(Slack::Notifier::APIError,
52
+ /something_bad \(HTTP Code 400\)/)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ # rubocop:disable Metrics/LineLength
5
+ RSpec.describe Slack::Notifier::Util::LinkFormatter do
6
+ describe "initialize & formatted" do
7
+ it "can be initialized without format args" do
8
+ subject = described_class.new("Hello World")
9
+ expect(subject.formatted()).to eq("Hello World")
10
+ end
11
+
12
+ it "can be initialized with format args" do
13
+ subject = described_class.new("Hello World", formats: [:html])
14
+ expect(subject.formatted()).to eq("Hello World")
15
+ end
16
+ end
17
+ describe "::format" do
18
+ it "formats html links" do
19
+ formatted = described_class.format("Hello World, enjoy <a href='http://example.com'>this</a>.")
20
+ expect(formatted).to include("<http://example.com|this>")
21
+ end
22
+
23
+ it "formats markdown links" do
24
+ formatted = described_class.format("Hello World, enjoy [this](http://example.com).")
25
+ expect(formatted).to include("<http://example.com|this>")
26
+ end
27
+
28
+ it "formats markdown links in brackets" do
29
+ formatted = described_class.format("Hello World, enjoy [[this](http://example.com) in brackets].")
30
+ expect(formatted).to eq("Hello World, enjoy [<http://example.com|this> in brackets].")
31
+ end
32
+
33
+ it "formats markdown links with no title" do
34
+ formatted = described_class.format("Hello World, enjoy [](http://example.com).")
35
+ expect(formatted).to include("<http://example.com>")
36
+ end
37
+
38
+ it "handles multiple html links" do
39
+ formatted = described_class.format("Hello World, enjoy <a href='http://example.com'>this</a><a href='http://example2.com'>this2</a>.")
40
+ expect(formatted).to include("<http://example.com|this>")
41
+ expect(formatted).to include("<http://example2.com|this2>")
42
+ end
43
+
44
+ it "handles multiple markdown links" do
45
+ formatted = described_class.format("Hello World, enjoy [this](http://example.com)[this2](http://example2.com).")
46
+ expect(formatted).to include("<http://example.com|this>")
47
+ expect(formatted).to include("<http://example2.com|this2>")
48
+ end
49
+
50
+ it "handles mixed html & markdown links" do
51
+ formatted = described_class.format("Hello World, enjoy [this](http://example.com)<a href='http://example2.com'>this2</a>.")
52
+ expect(formatted).to include("<http://example.com|this>")
53
+ expect(formatted).to include("<http://example2.com|this2>")
54
+ end
55
+
56
+ if "".respond_to? :scrub
57
+ context "when on ruby 2.1+ or have string-scrub installed" do
58
+ it "handles invalid unicode sequences" do
59
+ expect do
60
+ described_class.format("This sequence is invalid: \255")
61
+ end.not_to raise_error
62
+ end
63
+
64
+ it "replaces invalid unicode sequences with the unicode replacement character" do
65
+ formatted = described_class.format("\255")
66
+ expect(formatted).to eq "\uFFFD"
67
+ end
68
+ end
69
+ end
70
+
71
+ it "doesn't replace valid Japanese" do
72
+ formatted = described_class.format("こんにちは")
73
+ expect(formatted).to eq "こんにちは"
74
+ end
75
+
76
+ it "handles mailto links in markdown" do
77
+ formatted = described_class.format("[John](mailto:john@example.com)")
78
+ expect(formatted).to eq "<mailto:john@example.com|John>"
79
+ end
80
+
81
+ it "handles mailto links in html" do
82
+ formatted = described_class.format("<a href='mailto:john@example.com'>John</a>")
83
+ expect(formatted).to eq "<mailto:john@example.com|John>"
84
+ end
85
+
86
+ it "handles links with trailing parentheses" do
87
+ formatted = described_class.format("Hello World, enjoy [foo(bar)](http://example.com/foo(bar))<a href='http://example.com/bar(foo)'>bar(foo)</a>")
88
+ expect(formatted).to include("http://example.com/foo(bar)|foo(bar)")
89
+ expect(formatted).to include("http://example.com/bar(foo)|bar(foo)")
90
+ end
91
+
92
+ it "formats a number of differently formatted links" do
93
+ input_output = {
94
+ "Hello World, enjoy [this](http://example.com)." =>
95
+ "Hello World, enjoy <http://example.com|this>.",
96
+
97
+ "Hello World, enjoy [[this](http://example.com) in brackets]." =>
98
+ "Hello World, enjoy [<http://example.com|this> in brackets].",
99
+
100
+ "Hello World, enjoy ([this](http://example.com) in parens)." =>
101
+ "Hello World, enjoy (<http://example.com|this> in parens).",
102
+
103
+ "Hello World, enjoy [](http://example.com)." =>
104
+ "Hello World, enjoy <http://example.com>.",
105
+
106
+ "Hello World, enjoy [link with query](http://example.com?foo=bar)." =>
107
+ "Hello World, enjoy <http://example.com?foo=bar|link with query>.",
108
+
109
+ "Hello World, enjoy [link with fragment](http://example.com/#foo-bar)." =>
110
+ "Hello World, enjoy <http://example.com/#foo-bar|link with fragment>.",
111
+
112
+ "Hello World, enjoy [link with parens](http://example.com/foo(bar)/baz)." =>
113
+ "Hello World, enjoy <http://example.com/foo(bar)/baz|link with parens>.",
114
+
115
+ "Hello World, enjoy [link with query](http://example.com/(parens)?foo=bar)." =>
116
+ "Hello World, enjoy <http://example.com/(parens)?foo=bar|link with query>.",
117
+
118
+ "Hello World, enjoy [link with parens](http://example.com/baz?bang=foo(bar))." =>
119
+ "Hello World, enjoy <http://example.com/baz?bang=foo(bar)|link with parens>.",
120
+
121
+ "Hello World, enjoy [link with fragment](http://example.com/(parens)#foo-bar)." =>
122
+ "Hello World, enjoy <http://example.com/(parens)#foo-bar|link with fragment>.",
123
+
124
+ "Hello World, enjoy [link with fragment](http://example.com/#foo-bar=(baz))." =>
125
+ "Hello World, enjoy <http://example.com/#foo-bar=(baz)|link with fragment>.",
126
+
127
+ "Hello World, enjoy [this](http://example.com?foo=bar)[this2](http://example2.com)." =>
128
+ "Hello World, enjoy <http://example.com?foo=bar|this><http://example2.com|this2>.",
129
+
130
+ "Hello World, enjoy [this](http://example.com?foo=bar) [this2](http://example2.com/#fragment)." =>
131
+ "Hello World, enjoy <http://example.com?foo=bar|this> <http://example2.com/#fragment|this2>.",
132
+
133
+ "Hello World, enjoy [this](http://example.com)<a href='http://example2.com'>this2</a>." =>
134
+ "Hello World, enjoy <http://example.com|this><http://example2.com|this2>.",
135
+
136
+ "Hello world, [John](mailto:john@example.com)." =>
137
+ "Hello world, <mailto:john@example.com|John>.",
138
+
139
+ "Hello World, enjoy [foo(bar)](http://example.com/foo(bar))<a href='http://example.com/bar(foo)'>bar(foo)</a>" =>
140
+ "Hello World, enjoy <http://example.com/foo(bar)|foo(bar)><http://example.com/bar(foo)|bar(foo)>"
141
+ }
142
+
143
+ input_output.each do |input, output|
144
+ expect(described_class.format(input)).to eq output
145
+ end
146
+ end
147
+
148
+ context "with a configured stack" do
149
+ it "only formats html if html is the only item in formats" do
150
+ formatted = described_class.format("Hello World, enjoy [this](http://example.com)<a href='http://example2.com'>this2</a>.", formats: [:html])
151
+ expect(formatted).to eq "Hello World, enjoy [this](http://example.com)<http://example2.com|this2>."
152
+ end
153
+ it "only formats markdown if markdown is the only item in formats" do
154
+ formatted = described_class.format("Hello World, enjoy [this](http://example.com)<a href='http://example2.com'>this2</a>.", formats: [:markdown])
155
+ expect(formatted).to eq "Hello World, enjoy <http://example.com|this><a href='http://example2.com'>this2</a>."
156
+ end
157
+ it "doesn't format if formats is empty" do
158
+ formatted = described_class.format("Hello World, enjoy [this](http://example.com)<a href='http://example2.com'>this2</a>.", formats: [])
159
+ expect(formatted).to eq "Hello World, enjoy [this](http://example.com)<a href='http://example2.com'>this2</a>."
160
+ end
161
+ end
162
+ end
163
+ end