lita-wizard 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.rubocop.yml +51 -0
- data/.travis.yml +8 -0
- data/Gemfile +3 -0
- data/Guardfile +37 -0
- data/LICENSE +21 -0
- data/README.md +92 -0
- data/Rakefile +6 -0
- data/lib/lita-wizard.rb +10 -0
- data/lib/lita/extensions/wizard.rb +26 -0
- data/lib/lita/handlers/wizard.rb +10 -0
- data/lib/lita/wizard.rb +194 -0
- data/lib/lita/wizard_handler_mixin.rb +11 -0
- data/lita-wizard.gemspec +29 -0
- data/locales/en.yml +4 -0
- data/spec/lita/extensions/wizard_spec.rb +64 -0
- data/spec/lita/integration_spec.rb +122 -0
- data/spec/lita/wizard_spec.rb +77 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/test_wizard.rb +38 -0
- metadata +226 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0713263b1b47aac699bf0284c07c6fae25ec0c64
|
4
|
+
data.tar.gz: 3f292a189e77cdb39200411c52506cd93518d973
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 735eb9a13455a661717c83cc7f45abd4a086c7f970ff7399882d3dbf89717c9e3d197d7a30ca28e5f417f88652c73bacf9a386fc69b0eb1c53166cf299b7ccb9
|
7
|
+
data.tar.gz: 68b19a18b16e2b836882060236b60cf8c06c0f1eee5bd60ab93ba2c4fed758139646194145717b4a7773baa69e8ba76f8e6c64c2499389494418dc72d12444c3
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
AllCops:
|
2
|
+
DisplayCopNames: true
|
3
|
+
TargetRubyVersion: 2.3
|
4
|
+
|
5
|
+
|
6
|
+
Style/Encoding:
|
7
|
+
Enabled: false
|
8
|
+
|
9
|
+
Style/FrozenStringLiteralComment:
|
10
|
+
Enabled: false
|
11
|
+
|
12
|
+
Style/ClassAndModuleChildren:
|
13
|
+
EnforcedStyle: compact
|
14
|
+
|
15
|
+
Style/Documentation:
|
16
|
+
Enabled: false
|
17
|
+
|
18
|
+
Style/AndOr:
|
19
|
+
EnforcedStyle: conditionals
|
20
|
+
|
21
|
+
Style/EmptyLinesAroundClassBody:
|
22
|
+
EnforcedStyle: empty_lines
|
23
|
+
|
24
|
+
|
25
|
+
Style/MultilineOperationIndentation:
|
26
|
+
EnforcedStyle: indented
|
27
|
+
|
28
|
+
|
29
|
+
Style/PercentLiteralDelimiters:
|
30
|
+
PreferredDelimiters:
|
31
|
+
'%': []
|
32
|
+
'%i': []
|
33
|
+
'%q': ()
|
34
|
+
'%Q': ()
|
35
|
+
'%r': '{}'
|
36
|
+
'%s': []
|
37
|
+
'%w': []
|
38
|
+
'%W': []
|
39
|
+
'%x': ()
|
40
|
+
|
41
|
+
Style/StringLiterals:
|
42
|
+
EnforcedStyle: single_quotes
|
43
|
+
|
44
|
+
Style/StringLiteralsInInterpolation:
|
45
|
+
EnforcedStyle: single_quotes
|
46
|
+
|
47
|
+
Style/GuardClause:
|
48
|
+
MinBodyLength: 1
|
49
|
+
|
50
|
+
Metrics/LineLength:
|
51
|
+
Max: 120
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
guard :bundler do
|
2
|
+
require 'guard/bundler'
|
3
|
+
require 'guard/bundler/verify'
|
4
|
+
helper = Guard::Bundler::Verify.new
|
5
|
+
|
6
|
+
files = ['Gemfile']
|
7
|
+
files += Dir['*.gemspec'] if files.any? { |f| helper.uses_gemspec?(f) }
|
8
|
+
|
9
|
+
# Assume files are symlinked from somewhere
|
10
|
+
files.each { |file| watch(helper.real_path(file)) }
|
11
|
+
end
|
12
|
+
|
13
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
14
|
+
# rspec may be run, below are examples of the most common uses.
|
15
|
+
# * bundler: 'bundle exec rspec'
|
16
|
+
# * bundler binstubs: 'bin/rspec'
|
17
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
18
|
+
# installed the spring binstubs per the docs)
|
19
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
20
|
+
# * 'just' rspec: 'rspec'
|
21
|
+
|
22
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
23
|
+
require "guard/rspec/dsl"
|
24
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
25
|
+
|
26
|
+
# Feel free to open issues for suggestions and improvements
|
27
|
+
|
28
|
+
# RSpec files
|
29
|
+
rspec = dsl.rspec
|
30
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
31
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
32
|
+
watch(rspec.spec_files)
|
33
|
+
|
34
|
+
# Ruby files
|
35
|
+
ruby = dsl.ruby
|
36
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
37
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2016 Cristian Bica
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# lita-wizard
|
2
|
+
|
3
|
+
[](https://travis-ci.org/cristianbica/lita-wizard)
|
4
|
+
[](https://coveralls.io/r/cristianbica/lita-wizard)
|
5
|
+
|
6
|
+
A lita extension to build wizards (surveys, standups, etc). You can instruct your chat bot to ask several questions, validate responses.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add lita-wizard to your Lita plugin's gemspec:
|
11
|
+
|
12
|
+
``` ruby
|
13
|
+
spec.add_runtime_dependency "lita-wizard"
|
14
|
+
```
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
Create a subclass of `Lita::Wizard`
|
19
|
+
|
20
|
+
``` ruby
|
21
|
+
class MyWizard
|
22
|
+
|
23
|
+
# provide the wizard steps
|
24
|
+
step :name, label: "Your name:"
|
25
|
+
step :bio, label: "Tell me something about yourself:", multiline: true
|
26
|
+
step :lang, label: "What's your preferred programming language?", options: %w(ruby php)
|
27
|
+
step :years, label: "For how many years you're a programmer?", validate: /\d+/
|
28
|
+
step :really, label: "Really?", options: %w(yes no), if: ->(wizard) { value_for(:years).to_i > 15 }
|
29
|
+
|
30
|
+
# or you can have dynamic wizard steps
|
31
|
+
|
32
|
+
def steps
|
33
|
+
# return an array of objects responding to the following methods:
|
34
|
+
# name: a string / symbol
|
35
|
+
# lable: a string
|
36
|
+
# multiline: boolean
|
37
|
+
# (optional) validate: regexp
|
38
|
+
# (optional) options: array
|
39
|
+
# (optional) if: a proc
|
40
|
+
end
|
41
|
+
|
42
|
+
# you can override the following methods to customize the messages
|
43
|
+
|
44
|
+
def initial_message
|
45
|
+
"Great! I'm going to ask you some questions. During this time I cannot take regular commands. " \
|
46
|
+
"You can abort at any time by writing abort"
|
47
|
+
end
|
48
|
+
|
49
|
+
def abort_message
|
50
|
+
"Aborting. Resume your normal operations"
|
51
|
+
end
|
52
|
+
|
53
|
+
def final_message
|
54
|
+
"You're done!"
|
55
|
+
end
|
56
|
+
|
57
|
+
# You can implement the following methods to customize the wizard behaviour.
|
58
|
+
# The wizard has an instance method `meta` which contains some data you
|
59
|
+
# set when starting the wizard
|
60
|
+
|
61
|
+
def start_wizard
|
62
|
+
end
|
63
|
+
|
64
|
+
def abort_wizard
|
65
|
+
end
|
66
|
+
|
67
|
+
def finish_wizard
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
In your handler call `start_wizard` to initialize the process
|
74
|
+
|
75
|
+
|
76
|
+
``` ruby
|
77
|
+
route /^some command$/, :a_callback
|
78
|
+
|
79
|
+
def a_callback(request)
|
80
|
+
start_wizard(Mywizard, request.message, some_data: 1, other_data: 2)
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
## Contributing
|
85
|
+
|
86
|
+
1. Fork it ( https://github.com/cristianbica/lita-wizard/fork )
|
87
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
88
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
89
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
90
|
+
5. Create a new Pull Request
|
91
|
+
|
92
|
+
|
data/Rakefile
ADDED
data/lib/lita-wizard.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module Lita
|
2
|
+
module Extensions
|
3
|
+
class Wizard
|
4
|
+
def self.call(payload)
|
5
|
+
message = payload[:message]
|
6
|
+
route = payload[:route]
|
7
|
+
robot = payload[:robot]
|
8
|
+
|
9
|
+
# mark message as processed and return next time
|
10
|
+
return true if message.extensions[:processed_by_wizard]
|
11
|
+
message.extensions[:processed_by_wizard] = true
|
12
|
+
|
13
|
+
# if private messages and user has a pending wizard handle the message
|
14
|
+
if message.private_message? && Lita::Wizard.pending_wizard?(message.user.id)
|
15
|
+
handled = Lita::Wizard.handle_message(robot, message)
|
16
|
+
return true if handled
|
17
|
+
end
|
18
|
+
|
19
|
+
# return
|
20
|
+
!(route.extensions[:dummy] == true)
|
21
|
+
end
|
22
|
+
|
23
|
+
Lita.register_hook(:validate_route, self)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/lita/wizard.rb
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
class Lita::Wizard
|
4
|
+
|
5
|
+
attr_accessor :id, :robot, :message, :user_id, :current_step_index, :values, :meta
|
6
|
+
|
7
|
+
def initialize(robot, message, data = {})
|
8
|
+
@id = data['id'] || SecureRandom.hex(3)
|
9
|
+
@robot = robot
|
10
|
+
@message = message
|
11
|
+
@user_id = message.user.id
|
12
|
+
@current_step_index = (data['current_step_index'] || -1).to_i
|
13
|
+
@values = data['values'] || []
|
14
|
+
@meta = data['meta']
|
15
|
+
end
|
16
|
+
|
17
|
+
def advance
|
18
|
+
self.current_step_index += 1
|
19
|
+
save
|
20
|
+
if final_step?
|
21
|
+
finish_wizard
|
22
|
+
send_message final_message
|
23
|
+
destroy
|
24
|
+
elsif run_current_step?
|
25
|
+
if first_step?
|
26
|
+
start_wizard
|
27
|
+
send_message initial_message
|
28
|
+
end
|
29
|
+
message = step[:label]
|
30
|
+
message = "#{message} (Write done when finished)" if step[:multiline]
|
31
|
+
send_message message
|
32
|
+
else
|
33
|
+
advance
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def handle_message
|
38
|
+
if message.body == "abort"
|
39
|
+
send_message abort_message
|
40
|
+
abort_wizard
|
41
|
+
destroy
|
42
|
+
elsif step.nil?
|
43
|
+
send_message "Some error occured. Aborting."
|
44
|
+
destroy
|
45
|
+
elsif message.body == "done" && step[:multiline]
|
46
|
+
save
|
47
|
+
advance
|
48
|
+
elsif valid_response?
|
49
|
+
if step[:multiline]
|
50
|
+
values[current_step_index] ||= ""
|
51
|
+
values[current_step_index] << "\n"
|
52
|
+
values[current_step_index] << message.body
|
53
|
+
values[current_step_index].strip!
|
54
|
+
save
|
55
|
+
else
|
56
|
+
values[current_step_index] = message.body
|
57
|
+
save
|
58
|
+
advance
|
59
|
+
end
|
60
|
+
else
|
61
|
+
send_message @error_message
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def save
|
66
|
+
Lita.redis["pending-wizard-#{user_id.downcase}"] = to_json
|
67
|
+
end
|
68
|
+
|
69
|
+
def destroy
|
70
|
+
Lita.redis.del "pending-wizard-#{user_id.downcase}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_json
|
74
|
+
MultiJson.dump(as_json)
|
75
|
+
end
|
76
|
+
|
77
|
+
def as_json
|
78
|
+
{
|
79
|
+
'class' => self.class.name,
|
80
|
+
'id' => id,
|
81
|
+
'user_id' => user_id,
|
82
|
+
'current_step_index' => current_step_index,
|
83
|
+
'values' => values,
|
84
|
+
'meta' => meta
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
def step
|
89
|
+
steps[current_step_index]
|
90
|
+
end
|
91
|
+
|
92
|
+
def steps
|
93
|
+
self.class.steps
|
94
|
+
end
|
95
|
+
|
96
|
+
def run_current_step?
|
97
|
+
step[:if].nil? || instance_eval(&step[:if])
|
98
|
+
end
|
99
|
+
|
100
|
+
def final_step?
|
101
|
+
current_step_index == steps.size
|
102
|
+
end
|
103
|
+
|
104
|
+
def first_step?
|
105
|
+
current_step_index == 0
|
106
|
+
end
|
107
|
+
|
108
|
+
def value_for(step_name)
|
109
|
+
values[step_index(step_name)]
|
110
|
+
end
|
111
|
+
|
112
|
+
def step_index(step_name)
|
113
|
+
steps.index { |step| step.name == step_name }
|
114
|
+
end
|
115
|
+
|
116
|
+
def valid_response?
|
117
|
+
if step[:validate] && !step[:validate].match(message.body)
|
118
|
+
@error_message = 'Invalid format'
|
119
|
+
false
|
120
|
+
elsif step[:options] && !step[:options].include?(message.body)
|
121
|
+
@error_message = "Invalid response. Valid options: #{step[:options].join(', ')}"
|
122
|
+
false
|
123
|
+
else
|
124
|
+
true
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def initial_message
|
129
|
+
"Great! I'm going to ask you some questions. During this time I cannot take regular commands. " \
|
130
|
+
"You can abort at any time by writing abort"
|
131
|
+
end
|
132
|
+
|
133
|
+
def abort_message
|
134
|
+
"Aborting. Resume your normal operations"
|
135
|
+
end
|
136
|
+
|
137
|
+
def final_message
|
138
|
+
"You're done!"
|
139
|
+
end
|
140
|
+
|
141
|
+
def start_wizard
|
142
|
+
end
|
143
|
+
|
144
|
+
def abort_wizard
|
145
|
+
end
|
146
|
+
|
147
|
+
def finish_wizard
|
148
|
+
end
|
149
|
+
|
150
|
+
def send_message(body)
|
151
|
+
message.reply body
|
152
|
+
end
|
153
|
+
|
154
|
+
class << self
|
155
|
+
|
156
|
+
def start(robot, message, meta = {})
|
157
|
+
return false if pending_wizard?(message.user.id)
|
158
|
+
wizard = new(robot, message, 'meta' => meta)
|
159
|
+
wizard.advance
|
160
|
+
true
|
161
|
+
end
|
162
|
+
|
163
|
+
def handle_message(robot, message)
|
164
|
+
return false unless pending_wizard?(message.user.id)
|
165
|
+
wizard = restore(robot, message)
|
166
|
+
if wizard
|
167
|
+
wizard.handle_message
|
168
|
+
return true
|
169
|
+
end
|
170
|
+
false
|
171
|
+
end
|
172
|
+
|
173
|
+
def restore(robot, message)
|
174
|
+
data = MultiJson.load(Lita.redis["pending-wizard-#{message.user.id.downcase}"])
|
175
|
+
klass = eval(data['class'])
|
176
|
+
klass.new(robot, message, data)
|
177
|
+
rescue
|
178
|
+
nil
|
179
|
+
end
|
180
|
+
|
181
|
+
def pending_wizard?(user_id)
|
182
|
+
Lita.redis["pending-wizard-#{user_id.downcase}"]
|
183
|
+
end
|
184
|
+
|
185
|
+
def step(name, options = {})
|
186
|
+
steps << OpenStruct.new(options.merge(name: name))
|
187
|
+
end
|
188
|
+
|
189
|
+
def steps
|
190
|
+
@steps ||= []
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
end
|
data/lita-wizard.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = "lita-wizard"
|
3
|
+
spec.version = "1.0.0"
|
4
|
+
spec.authors = ["Cristian Bica"]
|
5
|
+
spec.email = ["cristian.bica@gmail.com"]
|
6
|
+
spec.description = "A lita extension to build wizards"
|
7
|
+
spec.summary = "A lita extension to build wizards (surveys, standups, etc)"
|
8
|
+
spec.homepage = "https://github.com/cristianbica/lita-wizard"
|
9
|
+
spec.license = "MIT"
|
10
|
+
spec.metadata = { "lita_plugin_type" => "extension" }
|
11
|
+
|
12
|
+
spec.files = `git ls-files`.split($/)
|
13
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
14
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
15
|
+
spec.require_paths = ["lib"]
|
16
|
+
|
17
|
+
spec.add_runtime_dependency "lita", ">= 4.7"
|
18
|
+
|
19
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
20
|
+
spec.add_development_dependency "pry"
|
21
|
+
spec.add_development_dependency "guard"
|
22
|
+
spec.add_development_dependency "guard-bundler"
|
23
|
+
spec.add_development_dependency "guard-rspec"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rack-test"
|
26
|
+
spec.add_development_dependency "rspec", ">= 3.0.0"
|
27
|
+
spec.add_development_dependency "simplecov"
|
28
|
+
spec.add_development_dependency "coveralls"
|
29
|
+
end
|
data/locales/en.yml
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Lita::Extensions::Wizard, lita: true do
|
4
|
+
let(:robot) { Lita::Robot.new(registry) }
|
5
|
+
let(:user) { Lita::User.create(42, name: "User") }
|
6
|
+
let(:room) { Lita::Room.create_or_update("#a", name: "#a") }
|
7
|
+
let(:message) { Lita::Message.new(robot, "test", Lita::Source.new(user: user, room: room, private_message: false)) }
|
8
|
+
let(:route) do
|
9
|
+
Lita::Handler::ChatRouter::Route.new.tap do |route|
|
10
|
+
route.extensions = {}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
let(:payload) do
|
14
|
+
{
|
15
|
+
robot: robot,
|
16
|
+
message: message,
|
17
|
+
route: route
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should stop if message has already been processed" do
|
22
|
+
message.extensions[:processed_by_wizard] = true
|
23
|
+
expect(message.extensions).to receive(:[]=).never
|
24
|
+
described_class.call(payload)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should mark the message as processed" do
|
28
|
+
expect(message.extensions).to receive(:[]=).with(:processed_by_wizard, true)
|
29
|
+
described_class.call(payload)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should check if the message is private" do
|
33
|
+
expect(message).to receive(:private_message?)
|
34
|
+
described_class.call(payload)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "shouldn't check if the user has a pending message if the message is public" do
|
38
|
+
allow(message).to receive(:private_message?).and_return(false)
|
39
|
+
expect(Lita::Wizard).to receive(:pending_wizard?).never
|
40
|
+
described_class.call(payload)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should check if the user has a pending message" do
|
44
|
+
allow(message).to receive(:private_message?).and_return(true)
|
45
|
+
expect(Lita::Wizard).to receive(:pending_wizard?)
|
46
|
+
described_class.call(payload)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "try to handle the message if private message and has pending wizard" do
|
50
|
+
allow(message).to receive(:private_message?).and_return(true)
|
51
|
+
allow(Lita::Wizard).to receive(:pending_wizard?).and_return(true)
|
52
|
+
expect(Lita::Wizard).to receive(:handle_message)
|
53
|
+
described_class.call(payload)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should return false if dummy route matched" do
|
57
|
+
route.extensions[:dummy] = true
|
58
|
+
expect(described_class.call(payload)).to be_falsey
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should return true if dummy route didn't match" do
|
62
|
+
expect(described_class.call(payload)).to be_truthy
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Lita::Handlers::Wizard, lita: true, lita_handler: true do
|
4
|
+
|
5
|
+
before do
|
6
|
+
robot.registry.register_hook(:validate_route, Lita::Extensions::Wizard)
|
7
|
+
end
|
8
|
+
|
9
|
+
def store_wizard(data = {})
|
10
|
+
data = data.merge(class: "TestWizard")
|
11
|
+
Lita.redis["pending-wizard-1"] = data.to_json
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should ignore messages if no pending wizard" do
|
15
|
+
send_message("test message", privately: true)
|
16
|
+
expect(replies).to be_empty
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should reply with the initial message if it's the first step" do
|
20
|
+
message = Lita::Message.new(robot, "test", source)
|
21
|
+
TestWizard.start(robot, message)
|
22
|
+
expect(replies.first).to eq("initial message")
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should call the start_wizard method if it's the first step" do
|
26
|
+
message = Lita::Message.new(robot, "test", source)
|
27
|
+
expect_any_instance_of(TestWizard).to receive(:start_wizard)
|
28
|
+
TestWizard.start(robot, message)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should reply with the first question when started the wizard" do
|
32
|
+
message = Lita::Message.new(robot, "test", source)
|
33
|
+
TestWizard.start(robot, message)
|
34
|
+
expect(replies.last).to eq("step one:")
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
it "should accept the answer of the first question" do
|
39
|
+
message = Lita::Message.new(robot, "test", source)
|
40
|
+
TestWizard.start(robot, message)
|
41
|
+
send_message("response", privately: true)
|
42
|
+
expect(replies.last).to match /^step two/
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should accept the answer for a single message question" do
|
46
|
+
store_wizard(current_step_index: 0)
|
47
|
+
send_message("response-one", privately: true)
|
48
|
+
expect(replies.last).to match /^step two/
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should accept the answer for a multiline message question" do
|
52
|
+
store_wizard(current_step_index: 1)
|
53
|
+
send_message("response-two line 1", privately: true)
|
54
|
+
send_message("response-two line 2", privately: true)
|
55
|
+
send_message("done", privately: true)
|
56
|
+
expect(replies.last).to match /^step three/
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should skip question which return false from the if block" do
|
60
|
+
store_wizard(current_step_index: 2)
|
61
|
+
send_message("response-three", privately: true)
|
62
|
+
expect(replies.last).to match /^step five/
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should not accept answer not matching the provided regexp" do
|
66
|
+
store_wizard(current_step_index: 4)
|
67
|
+
send_message("abc", privately: true)
|
68
|
+
expect(replies.last).to match /^Invalid format/
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should accept answer matching the provided regexp" do
|
72
|
+
store_wizard(current_step_index: 4)
|
73
|
+
send_message("123", privately: true)
|
74
|
+
expect(replies.last).to match /^step six/
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should not accept answer not in the options list" do
|
78
|
+
store_wizard(current_step_index: 5)
|
79
|
+
send_message("abc", privately: true)
|
80
|
+
expect(replies.last).to match /^Invalid response/
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should accept answer in the options list" do
|
84
|
+
store_wizard(current_step_index: 5)
|
85
|
+
send_message("one", privately: true)
|
86
|
+
expect(replies.last).to match /^final message/
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should reply with the last message when answering the last question" do
|
90
|
+
store_wizard(current_step_index: 5)
|
91
|
+
send_message("one", privately: true)
|
92
|
+
expect(replies.last).to match /^final message/
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should call the finish_wizard method when answering the last question" do
|
96
|
+
store_wizard(current_step_index: 5)
|
97
|
+
expect_any_instance_of(TestWizard).to receive(:finish_wizard)
|
98
|
+
send_message("one", privately: true)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should abort the wizard if requested by the user" do
|
102
|
+
store_wizard(current_step_index: 0)
|
103
|
+
send_message("abort", privately: true)
|
104
|
+
puts replies.inspect
|
105
|
+
expect(Lita::Wizard.pending_wizard?("1")).to be_falsey
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should send the abort message when aborting" do
|
109
|
+
store_wizard(current_step_index: 0)
|
110
|
+
send_message("abort", privately: true)
|
111
|
+
expect(replies.last).to match /^abort message/
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should call the abort_wizard method if requested by the user" do
|
115
|
+
store_wizard(current_step_index: 0)
|
116
|
+
expect_any_instance_of(TestWizard).to receive(:abort_wizard)
|
117
|
+
send_message("abort", privately: true)
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
|
122
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Lita::Wizard, lita: true do
|
4
|
+
let(:robot) { Lita::Robot.new(registry) }
|
5
|
+
let(:user) { Lita::User.create(42, name: "User") }
|
6
|
+
let(:room) { Lita::Room.create_or_update("#a", name: "#a") }
|
7
|
+
let(:message) { Lita::Message.new(robot, "test", Lita::Source.new(user: user, room: room, private_message: false)) }
|
8
|
+
|
9
|
+
context "starting a wizard" do
|
10
|
+
after { Lita::Wizard.start(robot, message) }
|
11
|
+
it "should check if a pending wizard exists for that user" do
|
12
|
+
expect(Lita::Wizard).to receive(:pending_wizard?).with("42").and_return(true)
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should try to start a new wizard if a pending one exists" do
|
17
|
+
allow(Lita::Wizard).to receive(:pending_wizard?).and_return(true)
|
18
|
+
expect(Lita::Wizard).to receive(:new).never
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should initialize a new wizard class an advance" do
|
22
|
+
mocked_wizard = double
|
23
|
+
expect(mocked_wizard).to receive(:advance)
|
24
|
+
allow(Lita::Wizard).to receive(:pending_wizard?).and_return(false)
|
25
|
+
expect(Lita::Wizard).to receive(:new).and_return(mocked_wizard)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "handling messages" do
|
30
|
+
after { Lita::Wizard.handle_message(robot, message) }
|
31
|
+
|
32
|
+
it "should handle message if there isn't a pending wizard" do
|
33
|
+
allow(Lita::Wizard).to receive(:pending_wizard?).and_return(false)
|
34
|
+
expect(Lita::Wizard).to receive(:restore).never
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should try to restore the wizard" do
|
38
|
+
allow(Lita::Wizard).to receive(:pending_wizard?).and_return(true)
|
39
|
+
expect(Lita::Wizard).to receive(:restore).once
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should restore the wizard and forward the message to be handled" do
|
43
|
+
allow(Lita::Wizard).to receive(:pending_wizard?).and_return(true)
|
44
|
+
mocked_wizard = double
|
45
|
+
expect(mocked_wizard).to receive(:handle_message)
|
46
|
+
expect(Lita::Wizard).to receive(:restore).and_return(mocked_wizard)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "restoring wizards" do
|
51
|
+
it "should return nil if there's no saved wizard" do
|
52
|
+
expect(Lita::Wizard.restore(robot, message)).to be_nil
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should return nil if data is malformed" do
|
56
|
+
expect(Lita::Wizard.restore(robot, message)).to be_nil
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should restore the wizard", :focus do
|
60
|
+
data = { class: "TestWizard", arg1: "43" }
|
61
|
+
Lita.redis["pending-wizard-42"] = data.to_json
|
62
|
+
expect(TestWizard).to receive(:new)#.with(robot, message, data)
|
63
|
+
Lita::Wizard.restore(robot, message)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "checking for a pending wizard" do
|
68
|
+
it "should return true if a pending wizard was saved" do
|
69
|
+
Lita.redis["pending-wizard-42"] = "1"
|
70
|
+
expect(Lita::Wizard.pending_wizard?("42")).to be_truthy
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should return false if no pending wizards saved" do
|
74
|
+
expect(Lita::Wizard.pending_wizard?("42")).to be_falsey
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "simplecov"
|
2
|
+
require "coveralls"
|
3
|
+
SimpleCov.formatters = [
|
4
|
+
SimpleCov::Formatter::HTMLFormatter,
|
5
|
+
Coveralls::SimpleCov::Formatter
|
6
|
+
]
|
7
|
+
SimpleCov.start { add_filter "/spec/" }
|
8
|
+
|
9
|
+
require "lita-wizard"
|
10
|
+
require "lita/rspec"
|
11
|
+
|
12
|
+
Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f }
|
13
|
+
|
14
|
+
# A compatibility mode is provided for older plugins upgrading from Lita 3. Since this plugin
|
15
|
+
# was generated with Lita 4, the compatibility mode should be left disabled.
|
16
|
+
Lita.version_3_compatibility_mode = false
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class TestWizard < Lita::Wizard
|
2
|
+
|
3
|
+
step :one,
|
4
|
+
label: 'step one:'
|
5
|
+
|
6
|
+
step :two,
|
7
|
+
label: 'step two:',
|
8
|
+
multiline: true
|
9
|
+
|
10
|
+
step :three,
|
11
|
+
label: 'step three:',
|
12
|
+
if: ->(_) { true }
|
13
|
+
|
14
|
+
step :four,
|
15
|
+
label: 'step four:',
|
16
|
+
if: ->(_) { false }
|
17
|
+
|
18
|
+
step :five,
|
19
|
+
label: 'step five:',
|
20
|
+
validate: /\d{3}/
|
21
|
+
|
22
|
+
step :six,
|
23
|
+
label: 'step six:',
|
24
|
+
options: %w(one two)
|
25
|
+
|
26
|
+
def initial_message
|
27
|
+
"initial message"
|
28
|
+
end
|
29
|
+
|
30
|
+
def abort_message
|
31
|
+
"abort message"
|
32
|
+
end
|
33
|
+
|
34
|
+
def final_message
|
35
|
+
"final message"
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,226 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lita-wizard
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Cristian Bica
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-06-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: lita
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.7'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: guard
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard-bundler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: guard-rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rack-test
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rspec
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 3.0.0
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 3.0.0
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: simplecov
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: coveralls
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
description: A lita extension to build wizards
|
168
|
+
email:
|
169
|
+
- cristian.bica@gmail.com
|
170
|
+
executables: []
|
171
|
+
extensions: []
|
172
|
+
extra_rdoc_files: []
|
173
|
+
files:
|
174
|
+
- ".gitignore"
|
175
|
+
- ".rspec"
|
176
|
+
- ".rubocop.yml"
|
177
|
+
- ".travis.yml"
|
178
|
+
- Gemfile
|
179
|
+
- Guardfile
|
180
|
+
- LICENSE
|
181
|
+
- README.md
|
182
|
+
- Rakefile
|
183
|
+
- lib/lita-wizard.rb
|
184
|
+
- lib/lita/extensions/wizard.rb
|
185
|
+
- lib/lita/handlers/wizard.rb
|
186
|
+
- lib/lita/wizard.rb
|
187
|
+
- lib/lita/wizard_handler_mixin.rb
|
188
|
+
- lita-wizard.gemspec
|
189
|
+
- locales/en.yml
|
190
|
+
- spec/lita/extensions/wizard_spec.rb
|
191
|
+
- spec/lita/integration_spec.rb
|
192
|
+
- spec/lita/wizard_spec.rb
|
193
|
+
- spec/spec_helper.rb
|
194
|
+
- spec/support/test_wizard.rb
|
195
|
+
homepage: https://github.com/cristianbica/lita-wizard
|
196
|
+
licenses:
|
197
|
+
- MIT
|
198
|
+
metadata:
|
199
|
+
lita_plugin_type: extension
|
200
|
+
post_install_message:
|
201
|
+
rdoc_options: []
|
202
|
+
require_paths:
|
203
|
+
- lib
|
204
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
209
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
210
|
+
requirements:
|
211
|
+
- - ">="
|
212
|
+
- !ruby/object:Gem::Version
|
213
|
+
version: '0'
|
214
|
+
requirements: []
|
215
|
+
rubyforge_project:
|
216
|
+
rubygems_version: 2.4.5.1
|
217
|
+
signing_key:
|
218
|
+
specification_version: 4
|
219
|
+
summary: A lita extension to build wizards (surveys, standups, etc)
|
220
|
+
test_files:
|
221
|
+
- spec/lita/extensions/wizard_spec.rb
|
222
|
+
- spec/lita/integration_spec.rb
|
223
|
+
- spec/lita/wizard_spec.rb
|
224
|
+
- spec/spec_helper.rb
|
225
|
+
- spec/support/test_wizard.rb
|
226
|
+
has_rdoc:
|