slackrb 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +29 -0
  5. data/.rubocop_todo.yml +49 -0
  6. data/CHANGELOG.md +219 -0
  7. data/Dangerfile +5 -0
  8. data/Gemfile +20 -0
  9. data/LICENSE.md +22 -0
  10. data/README.md +766 -0
  11. data/Rakefile +21 -0
  12. data/lib/config/application.rb +14 -0
  13. data/lib/config/boot.rb +8 -0
  14. data/lib/config/environment.rb +5 -0
  15. data/lib/slack-ruby-bot/about.rb +9 -0
  16. data/lib/slack-ruby-bot/app.rb +56 -0
  17. data/lib/slack-ruby-bot/bot.rb +19 -0
  18. data/lib/slack-ruby-bot/client.rb +65 -0
  19. data/lib/slack-ruby-bot/commands/about.rb +14 -0
  20. data/lib/slack-ruby-bot/commands/base.rb +158 -0
  21. data/lib/slack-ruby-bot/commands/help.rb +43 -0
  22. data/lib/slack-ruby-bot/commands/hi.rb +16 -0
  23. data/lib/slack-ruby-bot/commands/support/attrs.rb +36 -0
  24. data/lib/slack-ruby-bot/commands/support/help.rb +84 -0
  25. data/lib/slack-ruby-bot/commands/support/match.rb +23 -0
  26. data/lib/slack-ruby-bot/commands/unknown.rb +13 -0
  27. data/lib/slack-ruby-bot/commands.rb +7 -0
  28. data/lib/slack-ruby-bot/config.rb +38 -0
  29. data/lib/slack-ruby-bot/hooks/hello.rb +35 -0
  30. data/lib/slack-ruby-bot/hooks/hook_support.rb +45 -0
  31. data/lib/slack-ruby-bot/hooks/message.rb +56 -0
  32. data/lib/slack-ruby-bot/hooks/set.rb +45 -0
  33. data/lib/slack-ruby-bot/hooks.rb +6 -0
  34. data/lib/slack-ruby-bot/mvc/controller/base.rb +172 -0
  35. data/lib/slack-ruby-bot/mvc/model/base.rb +27 -0
  36. data/lib/slack-ruby-bot/mvc/mvc.rb +7 -0
  37. data/lib/slack-ruby-bot/mvc/view/base.rb +30 -0
  38. data/lib/slack-ruby-bot/mvc.rb +3 -0
  39. data/lib/slack-ruby-bot/rspec/support/bots_for_tests.rb +35 -0
  40. data/lib/slack-ruby-bot/rspec/support/slack-ruby-bot/it_behaves_like_a_slack_bot.rb +16 -0
  41. data/lib/slack-ruby-bot/rspec/support/slack-ruby-bot/not_respond.rb +25 -0
  42. data/lib/slack-ruby-bot/rspec/support/slack-ruby-bot/respond_with_error.rb +36 -0
  43. data/lib/slack-ruby-bot/rspec/support/slack-ruby-bot/respond_with_slack_message.rb +34 -0
  44. data/lib/slack-ruby-bot/rspec/support/slack-ruby-bot/respond_with_slack_messages.rb +45 -0
  45. data/lib/slack-ruby-bot/rspec/support/slack-ruby-bot/start_typing.rb +32 -0
  46. data/lib/slack-ruby-bot/rspec/support/slack_api_key.rb +10 -0
  47. data/lib/slack-ruby-bot/rspec/support/slack_ruby_bot_configure.rb +15 -0
  48. data/lib/slack-ruby-bot/rspec/support/spec_helpers.rb +14 -0
  49. data/lib/slack-ruby-bot/rspec.rb +14 -0
  50. data/lib/slack-ruby-bot/server.rb +88 -0
  51. data/lib/slack-ruby-bot/support/loggable.rb +25 -0
  52. data/lib/slack-ruby-bot/version.rb +5 -0
  53. data/lib/slack-ruby-bot.rb +29 -0
  54. data/lib/slack_ruby_bot.rb +3 -0
  55. data/screenshots/aliases.gif +0 -0
  56. data/screenshots/create-classic-app.png +0 -0
  57. data/screenshots/demo.gif +0 -0
  58. data/screenshots/dms.gif +0 -0
  59. data/screenshots/help.png +0 -0
  60. data/screenshots/market.gif +0 -0
  61. data/screenshots/weather.gif +0 -0
  62. data/slack-ruby-bot.gemspec +32 -0
  63. data/slack.png +0 -0
  64. metadata +244 -0
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlackRubyBot
4
+ module Hooks
5
+ module HookSupport
6
+ def self.included(base)
7
+ base.cattr_accessor :hook_blocks
8
+
9
+ base.extend(ClassMethods)
10
+ end
11
+
12
+ module ClassMethods
13
+ def on(event_name, &block)
14
+ self.hook_blocks ||= {}
15
+
16
+ self.hook_blocks[event_name] ||= []
17
+ self.hook_blocks[event_name] << block
18
+ end
19
+ end
20
+
21
+ def on(event_name, handler)
22
+ _hooks.add(event_name, handler)
23
+ end
24
+
25
+ def flush_hook_blocks
26
+ return nil unless self.class.hook_blocks
27
+
28
+ add_hook_handlers(self.class.hook_blocks)
29
+ end
30
+
31
+ # TODO: This should be deprecated in favor of `on`
32
+ def add_hook_handlers(handler_hash)
33
+ handler_hash.each do |hook, handlers|
34
+ Array(handlers).each { |handler| on(hook, handler) }
35
+ end
36
+ end
37
+
38
+ # Temp use this method in order to deprecate `hooks` and revisit
39
+ def _hooks
40
+ @hooks ||= SlackRubyBot::Hooks::Set.new
41
+ end
42
+ private :_hooks
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlackRubyBot
4
+ module Hooks
5
+ class Message
6
+ def call(client, data)
7
+ return if !client.allow_message_loops? && client.message_to_self?(data)
8
+ return if !client.allow_bot_messages? && client.bot_message?(data)
9
+
10
+ prepare!(data)
11
+
12
+ result = child_command_classes.detect { |d| d.invoke(client, data) }
13
+ result ||= built_in_command_classes.detect { |d| d.invoke(client, data) }
14
+ result ||= SlackRubyBot::Commands::Unknown.tap { |d| d.invoke(client, data) }
15
+ result
16
+ end
17
+
18
+ private
19
+
20
+ def prepare!(data)
21
+ data.text = data.text.strip if data.text
22
+ end
23
+
24
+ #
25
+ # All commands.
26
+ #
27
+ # @return [Array] Descendants of SlackRubyBot::Commands::Base.
28
+ #
29
+ def command_classes
30
+ SlackRubyBot::Commands::Base.command_classes
31
+ end
32
+
33
+ #
34
+ # All non-built-in, ie. custom commands.
35
+ #
36
+ # @return [Array] Non-built-in descendants of SlackRubyBot::Commands::Base.
37
+ #
38
+ def child_command_classes
39
+ command_classes.reject do |k|
40
+ k.name&.starts_with?('SlackRubyBot::Commands::')
41
+ end
42
+ end
43
+
44
+ #
45
+ # All built-in commands.
46
+ #
47
+ # @return [Array] Built-in descendants of SlackRubyBot::Commands::Base.
48
+ #
49
+ def built_in_command_classes
50
+ command_classes.select do |k|
51
+ k.name&.starts_with?('SlackRubyBot::Commands::') && k != SlackRubyBot::Commands::Unknown
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlackRubyBot
4
+ module Hooks
5
+ class Set
6
+ attr_accessor :handlers, :client
7
+
8
+ def initialize(client = nil)
9
+ self.handlers = {}
10
+ self.client = client
11
+
12
+ @pending_flush = client.blank?
13
+ end
14
+
15
+ def add(hook_name, handler)
16
+ handlers[hook_name] ||= ::Set.new
17
+ handlers[hook_name] << handler
18
+
19
+ register_callback(hook_name)
20
+ end
21
+
22
+ def client=(client)
23
+ @client = client
24
+
25
+ flush_handlers if @pending_flush
26
+ end
27
+
28
+ protected
29
+
30
+ def register_callback(hook_name)
31
+ return unless client # We'll delay this until client is set
32
+
33
+ client.on hook_name do |data|
34
+ handlers[hook_name].each do |handler|
35
+ handler.call(client, data)
36
+ end
37
+ end
38
+ end
39
+
40
+ def flush_handlers
41
+ handlers.each_key { |hook| register_callback(hook) }
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'slack-ruby-bot/hooks/set'
4
+ require 'slack-ruby-bot/hooks/hello'
5
+ require 'slack-ruby-bot/hooks/message'
6
+ require 'slack-ruby-bot/hooks/hook_support'
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlackRubyBot
4
+ module MVC
5
+ module Controller
6
+ class Base
7
+ include ActiveSupport::Callbacks
8
+
9
+ class << self
10
+ attr_reader :abstract
11
+ alias abstract? abstract
12
+
13
+ def controllers
14
+ get_or_set_ivar(:@controllers, [])
15
+ end
16
+
17
+ def command_class
18
+ get_or_set_ivar(:@command_class, Class.new(SlackRubyBot::Commands::Base))
19
+ end
20
+
21
+ def aliases
22
+ get_or_set_ivar(:@aliases, Hash.new { |h, k| h[k] = [] })
23
+ end
24
+
25
+ def get_or_set_ivar(name, value)
26
+ unless (ivar = Base.instance_variable_get(name))
27
+ ivar = value
28
+ Base.instance_variable_set(name, ivar)
29
+ end
30
+ ivar
31
+ end
32
+
33
+ def reset!
34
+ # Remove any earlier anonymous classes from prior calls so we don't leak them
35
+ Commands::Base.command_classes.delete(Controller::Base.command_class) if Base.command_class
36
+
37
+ Base.instance_variable_set(:@command_class, nil)
38
+ Base.instance_variable_set(:@controllers, nil)
39
+ end
40
+
41
+ # Define a controller as abstract. See internal_methods for more
42
+ # details.
43
+ def abstract!
44
+ @abstract = true
45
+ end
46
+
47
+ def inherited(klass) # :nodoc:
48
+ # Define the abstract ivar on subclasses so that we don't get
49
+ # uninitialized ivar warnings
50
+ klass.instance_variable_set(:@abstract, false) unless klass.instance_variable_defined?(:@abstract)
51
+ super
52
+ end
53
+
54
+ def register_controller(controller)
55
+ # Only used to keep a reference around so the instance object doesn't get garbage collected
56
+ controllers << controller
57
+ klass = controller.class
58
+
59
+ methods = (klass.public_instance_methods(true) -
60
+ # Except for public instance methods of Base and its ancestors
61
+ internal_methods(klass) +
62
+ # Be sure to include shadowed public instance methods of this class
63
+ klass.public_instance_methods(false)).uniq.map(&:to_s)
64
+
65
+ methods.each do |name|
66
+ next if name[0] == '_'
67
+
68
+ commands = lookup_command_name(name)
69
+
70
+ # Generates a command for each controller method *and* its aliases
71
+ commands.each do |command_string|
72
+ # sprinkle a little syntactic sugar on top of existing `command` infrastructure
73
+ command_class.class_eval do
74
+ command command_string do |client, data, match|
75
+ controller.use_args(client, data, match)
76
+ controller.call_command
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ # A list of all internal methods for a controller. This finds the first
84
+ # abstract superclass of a controller, and gets a list of all public
85
+ # instance methods on that abstract class. Public instance methods of
86
+ # a controller would normally be considered action methods, so methods
87
+ # declared on abstract classes are being removed.
88
+ # (Controller::Base is defined as abstract)
89
+ def internal_methods(controller)
90
+ controller = controller.superclass until controller.abstract?
91
+ controller.public_instance_methods(true)
92
+ end
93
+
94
+ # Maps a controller method name to an alternate command name. Used in cases where
95
+ # a command can be called via multiple text strings.
96
+ #
97
+ # Call this method *after* defining the original method.
98
+ #
99
+ # Class.new(SlackRubyBot::MVC::Controller::Base) do
100
+ # def quxo_foo_bar
101
+ # client.say(channel: data.channel, text: "quxo foo bar: #{match[:expression]}")
102
+ # end
103
+ # # setup alias name after original method definition
104
+ # alternate_name :quxo_foo_bar, :another_text_string
105
+ # end
106
+ #
107
+ # This is equivalent to:
108
+ #
109
+ # e.g.
110
+ # command 'quxo foo bar', 'another text string' do |*args|
111
+ # ..
112
+ # end
113
+ def alternate_name(original_name, *alias_names)
114
+ command_name = convert_method_name_to_command_string(original_name)
115
+ command_aliases = alias_names.map do |name|
116
+ convert_method_name_to_command_string(name)
117
+ end
118
+
119
+ aliases[command_name] += command_aliases
120
+
121
+ alias_names.each { |alias_name| alias_method(alias_name, original_name) }
122
+ end
123
+
124
+ private
125
+
126
+ def lookup_command_name(name)
127
+ name = convert_method_name_to_command_string(name)
128
+ [name] + aliases[name]
129
+ end
130
+
131
+ def convert_method_name_to_command_string(name)
132
+ name.to_s.tr('_', ' ')
133
+ end
134
+ end
135
+
136
+ abstract!
137
+ reset!
138
+
139
+ attr_reader :model, :view, :client, :data, :match
140
+
141
+ def initialize(model, view)
142
+ @model = model
143
+ @view = view
144
+ self.class.register_controller(self)
145
+ end
146
+
147
+ # Hand off the latest updated objects to the +model+ and +view+ and
148
+ # update our +client+, +data+, and +match+ accessors.
149
+ def use_args(client, data, match)
150
+ @client = client
151
+ @data = data
152
+ @match = match
153
+ model.use_args(client, data, match)
154
+ view.use_args(client, data, match)
155
+ end
156
+
157
+ # Determine the command issued and call the corresponding instance method
158
+ def call_command
159
+ verb = match.captures[match.names.index('command')]
160
+ verb = normalize_command_string(verb)
161
+ public_send(verb)
162
+ end
163
+
164
+ private
165
+
166
+ def normalize_command_string(string)
167
+ string.downcase.tr(' ', '_')
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlackRubyBot
4
+ module MVC
5
+ module Model
6
+ class Base
7
+ include ActiveSupport::Callbacks
8
+
9
+ class << self
10
+ def inherited(klass) # :nodoc:
11
+ super
12
+ end
13
+ end
14
+
15
+ attr_reader :client, :data, :match
16
+
17
+ # Hand off the latest updated objects to the +model+ and +view+ and
18
+ # update our +client+, +data+, and +match+ accessors.
19
+ def use_args(client, data, match)
20
+ @client = client
21
+ @data = data
22
+ @match = match
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../mvc/controller/base'
4
+
5
+ require_relative '../mvc/model/base'
6
+
7
+ require_relative '../mvc/view/base'
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlackRubyBot
4
+ module MVC
5
+ module View
6
+ class Base
7
+ include ActiveSupport::Callbacks
8
+ extend Forwardable
9
+
10
+ class << self
11
+ def inherited(klass) # :nodoc:
12
+ super
13
+ end
14
+ end
15
+
16
+ attr_reader :client, :data, :match
17
+
18
+ def_delegators :@client, :say
19
+
20
+ # Hand off the latest updated objects to the +model+ and +view+ and
21
+ # update our +client+, +data+, and +match+ accessors.
22
+ def use_args(client, data, match)
23
+ @client = client
24
+ @data = data
25
+ @match = match
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'slack-ruby-bot/mvc/mvc'
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Testing
4
+ class WeatherBot < SlackRubyBot::Bot
5
+ help do
6
+ title 'Weather Bot'
7
+ desc 'This bot tells you the weather.'
8
+
9
+ command 'clouds' do
10
+ desc 'Tells you how many clouds there\'re above you.'
11
+ end
12
+
13
+ command '' do
14
+ desc 'empty description'
15
+ end
16
+
17
+ command 'command_without_description' do
18
+ end
19
+
20
+ command 'What\'s the weather in <city>?' do
21
+ desc 'Tells you the weather in a <city>.'
22
+ long_desc "Accurate 10 Day Weather Forecasts for thousands of places around the World.\n" \
23
+ 'We provide detailed Weather Forecasts over a 10 day period updated four times a day.'
24
+ end
25
+ end
26
+ end
27
+
28
+ class HelloCommand < SlackRubyBot::Commands::Base
29
+ help do
30
+ title 'hello'
31
+ desc 'Says hello.'
32
+ long_desc 'The long description'
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_examples 'a slack ruby bot' do
4
+ context 'not configured' do
5
+ before do
6
+ @slack_api_token = ENV.delete('SLACK_API_TOKEN')
7
+ SlackRubyBot.configure { |config| config.token = nil }
8
+ end
9
+ after do
10
+ ENV['SLACK_API_TOKEN'] = @slack_api_token
11
+ end
12
+ it 'requires SLACK_API_TOKEN' do
13
+ expect { described_class.instance }.to raise_error RuntimeError, 'Missing Slack API Token.'
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/expectations'
4
+
5
+ RSpec::Matchers.define :not_respond do
6
+ match do |actual|
7
+ client = respond_to?(:client) ? send(:client) : SlackRubyBot::Client.new
8
+
9
+ message_command = SlackRubyBot::Hooks::Message.new
10
+ channel, user, message, attachments = parse(actual)
11
+
12
+ expect(client).not_to receive(:message)
13
+ message_command.call(client, Hashie::Mash.new(text: message, channel: channel, user: user, attachments: attachments))
14
+ true
15
+ end
16
+
17
+ private
18
+
19
+ def parse(actual)
20
+ actual = { message: actual } unless actual.is_a?(Hash)
21
+ attachments = actual[:attachments]
22
+ attachments = [attachments] unless attachments.nil? || attachments.is_a?(Array)
23
+ [actual[:channel] || 'channel', actual[:user] || 'user', actual[:message], attachments]
24
+ end
25
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/expectations'
4
+
5
+ RSpec::Matchers.define :respond_with_error do |error, error_message|
6
+ match do |actual|
7
+ client = respond_to?(:client) ? send(:client) : SlackRubyBot::Client.new
8
+
9
+ message_command = SlackRubyBot::Hooks::Message.new
10
+ channel, user, message, attachments = parse(actual)
11
+
12
+ begin
13
+ expect do
14
+ message_command.call(client, Hashie::Mash.new(text: message, channel: channel, user: user, attachments: attachments))
15
+ end.to raise_error error, error_message
16
+ rescue RSpec::Expectations::ExpectationNotMetError => e
17
+ @error_message = e.message
18
+ raise e
19
+ end
20
+ true
21
+ end
22
+
23
+ failure_message do |actual|
24
+ _, _, message = parse(actual)
25
+ @error_message || "expected for '#{message}' to fail with '#{expected}'"
26
+ end
27
+
28
+ private
29
+
30
+ def parse(actual)
31
+ actual = { message: actual } unless actual.is_a?(Hash)
32
+ attachments = actual[:attachments]
33
+ attachments = [attachments] unless attachments.nil? || attachments.is_a?(Array)
34
+ [actual[:channel] || 'channel', actual[:user] || 'user', actual[:message], attachments]
35
+ end
36
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/expectations'
4
+
5
+ RSpec::Matchers.define :respond_with_slack_message do |expected|
6
+ include SlackRubyBot::SpecHelpers
7
+
8
+ match do |actual|
9
+ client = respond_to?(:client) ? send(:client) : SlackRubyBot::Client.new
10
+
11
+ message_command = SlackRubyBot::Hooks::Message.new
12
+ channel, user, message, attachments = parse(actual)
13
+
14
+ allow(client).to receive(:message) do |options|
15
+ @messages ||= []
16
+ @messages.push options
17
+ end
18
+
19
+ message_command.call(client, Hashie::Mash.new(text: message, channel: channel, user: user, attachments: attachments))
20
+
21
+ matcher = have_received(:message).once
22
+ matcher = matcher.with(hash_including(channel: channel, text: expected)) if channel && expected
23
+
24
+ expect(client).to matcher
25
+
26
+ true
27
+ end
28
+
29
+ failure_message do |_actual|
30
+ message = "expected to receive message with text: #{expected} once,\n received:"
31
+ message += @messages&.any? ? @messages.inspect : 'none'
32
+ message
33
+ end
34
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/expectations'
4
+
5
+ RSpec::Matchers.define :respond_with_slack_messages do |expected|
6
+ include SlackRubyBot::SpecHelpers
7
+
8
+ match do |actual|
9
+ raise ArgumentError, 'respond_with_slack_messages expects an array of ordered responses' if expected && !expected.respond_to?(:each)
10
+
11
+ client = respond_to?(:client) ? send(:client) : SlackRubyBot::Client.new
12
+
13
+ message_command = SlackRubyBot::Hooks::Message.new
14
+ channel, user, message, attachments = parse(actual)
15
+
16
+ @messages ||= []
17
+ allow(client).to receive(:message) do |options|
18
+ @messages.push options
19
+ end
20
+
21
+ message_command.call(client, Hashie::Mash.new(text: message, channel: channel, user: user, attachments: attachments))
22
+
23
+ @responses = []
24
+
25
+ if expected&.any?
26
+ expected.each do |exp|
27
+ @responses.push(expect(client).to(have_received(:message).with(hash_including(channel: channel, text: exp)).once))
28
+ end
29
+ else
30
+ expect(@messages.size).to be > 1
31
+ end
32
+
33
+ true
34
+ end
35
+
36
+ failure_message do |_actual|
37
+ if expected&.any?
38
+ expected.map do |exp|
39
+ "Expected text: #{exp}, got #{@messages[expected.index(exp)] || 'none'}" unless @responses[expected.index(exp)]
40
+ end.compact.join("\n")
41
+ else
42
+ "Expected to receive multiple messages, got #{@messages.any? ? @messages.size : 'none'}"
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/expectations'
4
+
5
+ RSpec::Matchers.define :start_typing do |expected|
6
+ include SlackRubyBot::SpecHelpers
7
+
8
+ match do |actual|
9
+ client = respond_to?(:client) ? send(:client) : SlackRubyBot::Client.new
10
+
11
+ message_command = SlackRubyBot::Hooks::Message.new
12
+
13
+ allow(client).to receive(:typing) do |options|
14
+ @test_options = options
15
+ end
16
+
17
+ channel, user, message, attachments = parse(actual)
18
+ message_command.call(client, Hashie::Mash.new(text: message, channel: channel, user: user, attachments: attachments))
19
+
20
+ matcher = have_received(:typing).once
21
+ matcher = matcher.with(expected) if expected&.any?
22
+ expect(client).to matcher
23
+
24
+ true
25
+ end
26
+
27
+ failure_message do |_actual|
28
+ message = "expected to receive typing with: #{expected} once,\n received:"
29
+ message += @test_options&.any? ? @test_options.inspect : ' none'
30
+ message
31
+ end
32
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ require 'botwayrb'
3
+
4
+ RSpec.configure do |config|
5
+ bw = Botwayrb::Core.new
6
+
7
+ config.before :each do
8
+ bw.get_token ||= 'test'
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.configure do |config|
4
+ config.before :each do
5
+ SlackRubyBot.configure do |c|
6
+ c.token = 'testtoken'
7
+ c.user = 'rubybot'
8
+ c.user_id = 'DEADBEEF'
9
+ end
10
+ end
11
+
12
+ config.after :each do
13
+ SlackRubyBot::Config.reset!
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlackRubyBot
4
+ module SpecHelpers
5
+ private
6
+
7
+ def parse(actual)
8
+ actual = { message: actual } unless actual.is_a?(Hash)
9
+ attachments = actual[:attachments]
10
+ attachments = [attachments] unless attachments.nil? || attachments.is_a?(Array)
11
+ [actual[:channel] || 'channel', actual[:user] || 'user', actual[:message], attachments]
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..'))
4
+
5
+ require 'rubygems'
6
+ require 'rspec'
7
+ require 'rack/test'
8
+
9
+ require 'config/environment'
10
+ require 'slack-ruby-bot'
11
+
12
+ Dir[File.join(File.dirname(__FILE__), 'rspec/support', '**/*.rb')].sort.each do |file|
13
+ require file
14
+ end