slack-notifier 1.5.1 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
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