lita-wizard 1.0.0

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