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.
@@ -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
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format=documentation
2
+ --color
@@ -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
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.0
4
+ script: bundle exec rake
5
+ before_install:
6
+ - gem update --system
7
+ services:
8
+ - redis-server
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
@@ -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.
@@ -0,0 +1,92 @@
1
+ # lita-wizard
2
+
3
+ [![Build Status](https://travis-ci.org/cristianbica/lita-wizard.png?branch=master)](https://travis-ci.org/cristianbica/lita-wizard)
4
+ [![Coverage Status](https://coveralls.io/repos/cristianbica/lita-wizard/badge.png)](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
+
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,10 @@
1
+ require "lita"
2
+
3
+ Lita.load_locales Dir[File.expand_path(
4
+ File.join("..", "..", "locales", "*.yml"), __FILE__
5
+ )]
6
+
7
+ require "lita/wizard"
8
+ require "lita/extensions/wizard"
9
+ require "lita/handlers/wizard"
10
+ require "lita/wizard_handler_mixin"
@@ -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
@@ -0,0 +1,10 @@
1
+ module Lita
2
+ module Handlers
3
+ class Wizard < Handler
4
+ route(/.*/, nil, dummy: true) do |response|
5
+ end
6
+
7
+ Lita.register_handler(self)
8
+ end
9
+ end
10
+ end
@@ -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
@@ -0,0 +1,11 @@
1
+ module Lita
2
+ module WizardHandlerMixin
3
+ def start_wizard(klass, message, meta = {})
4
+ klass.start(robot, message, meta)
5
+ end
6
+ end
7
+
8
+ class Handler
9
+ prepend WizardHandlerMixin
10
+ end
11
+ end
@@ -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
@@ -0,0 +1,4 @@
1
+ en:
2
+ lita:
3
+ extensions:
4
+ wizard:
@@ -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
@@ -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: