slackrb 0.17.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 (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