state_machines-rspec 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +27 -0
- data/Gemfile +8 -0
- data/Guardfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +76 -0
- data/Rakefile +6 -0
- data/lib/matchers/events/handle_event.rb +35 -0
- data/lib/matchers/events/matcher.rb +71 -0
- data/lib/matchers/events/reject_event.rb +35 -0
- data/lib/matchers/states/have_state.rb +46 -0
- data/lib/matchers/states/matcher.rb +56 -0
- data/lib/matchers/states/reject_state.rb +34 -0
- data/lib/matchers/transitions/transition_from.rb +83 -0
- data/lib/state_machines_rspec.rb +12 -0
- data/lib/state_machines_rspec/state_machines_introspector.rb +74 -0
- data/lib/state_machines_rspec/version.rb +3 -0
- data/spec/integration/integration_spec.rb +332 -0
- data/spec/integration/models/vehicle.rb +119 -0
- data/spec/matchers/events/handle_event_spec.rb +133 -0
- data/spec/matchers/events/reject_event_spec.rb +133 -0
- data/spec/matchers/states/have_state_spec.rb +136 -0
- data/spec/matchers/states/reject_state_spec.rb +97 -0
- data/spec/matchers/transitions/transition_from_spec.rb +177 -0
- data/spec/spec_helper.rb +12 -0
- data/state_machines-rspec.gemspec +32 -0
- metadata +237 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9d664248d8e6c932e61f34434554594c78df0cf2
|
4
|
+
data.tar.gz: 2560495bea7dea3314ec24997358a8b549e476b1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cf306713a6bcab032cba6717a45cd9155b215f5d8c94b02de7d3963ad836fa1974cee557cdfb65772488901d3010ae0406826b97fc13e6f00315a1e1abe60126
|
7
|
+
data.tar.gz: 74af00d27917d5c98358139fb520131f6020225c2d005e0e8b824c91e3d9f9a72caa3839c1eb63b63126ed07596f79db6e41b8c66f75f5c29fcb72dab1558deb
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
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
|
18
|
+
*.swp
|
19
|
+
# JETBRAINS IDEs files
|
20
|
+
.idea
|
21
|
+
*.iml
|
22
|
+
|
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.4.1
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
## [0.4.0](https://github.com/state-machines/state_machines-rspec/compare/v0.3.2...v0.4.0)
|
2
|
+
- Renamed the gem to state_machines-rspec
|
3
|
+
- Gem ownership is now shared between multiple maintainers
|
4
|
+
- Removed deprecated syntax in examples
|
5
|
+
- Add transition from matcher
|
6
|
+
|
7
|
+
## [0.1.3](https://github.com/modocache/state_machine_rspec/compare/v0.1.2...v0.1.3)
|
8
|
+
|
9
|
+
- Add `#description` to matchers.
|
10
|
+
- Update rspec dependency.
|
11
|
+
|
12
|
+
## [0.1.2](https://github.com/modocache/state_machine_rspec/compare/v0.1.1...v0.1.2)
|
13
|
+
|
14
|
+
- state_machine dependency updated from "~> 1.1.0" to ">= 1.1.0".
|
15
|
+
Fixes https://github.com/modocache/state_machine_rspec/issues/5.
|
16
|
+
|
17
|
+
## [0.1.1](https://github.com/modocache/state_machine_rspec/compare/v0.1.0...v0.1.1)
|
18
|
+
|
19
|
+
- `StateMachineRspec::Matchers::Events` conform to `Matchers::States` API, and now
|
20
|
+
take a `:on` parameter when specifying a non-default state_machine attribute. This
|
21
|
+
used to be specified with a `:state` parameter.
|
22
|
+
- Add Travis-CI build status image to README.
|
23
|
+
- Update README with instructions on including module in during RSpec configuration.
|
24
|
+
|
25
|
+
## [0.1.0](https://github.com/modocache/state_machine_rspec/tree/v0.1.0)
|
26
|
+
|
27
|
+
- Initial release.
|
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 modocache
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# state_machines_rspec
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/state_machines-rspec.svg)](https://badge.fury.io/rb/state_machines-rspec)
|
3
|
+
|
4
|
+
Custom RSpec matchers for [state-machines/state_machine](https://github.com/state-machines/state_machine).
|
5
|
+
|
6
|
+
This repo is forked from [modocache/state_machine_rspec](https://github.com/modocache/state_machine_rspec).
|
7
|
+
|
8
|
+
## Matchers
|
9
|
+
|
10
|
+
### `have_state` & `reject_state`
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
describe Vehicle do
|
14
|
+
it { is_expected.to have_states :parked, :idling, :stalled, :first_gear,
|
15
|
+
:second_gear, :third_gear }
|
16
|
+
it { is_expected.to reject_state :flying }
|
17
|
+
|
18
|
+
it { is_expected.to have_states :active, :off, on: :alarm_state }
|
19
|
+
it { is_expected.to have_state :active, on: :alarm_state, value: 1 }
|
20
|
+
it { is_expected.to reject_states :broken, :ringing, on: :alarm_state }
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
### `handle_event` & `reject_event`
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
describe Vehicle do
|
28
|
+
it { is_expected.to handle_events :shift_down, :crash, when: :third_gear }
|
29
|
+
it { is_expected.to handle_events :enable_alarm, :disable_alarm,
|
30
|
+
when: :active, on: :alarm_state }
|
31
|
+
it { is_expected.to reject_events :park, :ignite, :idle, :shift_up, :repair,
|
32
|
+
when: :third_gear }
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
### `transition_from`
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
describe Vehicle do
|
40
|
+
it { is_expected.to transition_from :idling, to_state: :parked,
|
41
|
+
on_event: :park }
|
42
|
+
it { is_expected.to transition_from :idling, :first_gear,
|
43
|
+
to_state: :parked, on_event: :park }
|
44
|
+
it { is_expected.to transition_from :active, to_state: :off,
|
45
|
+
on_event: :disable_alarm, on: :alarm_state }
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
|
50
|
+
## Installation
|
51
|
+
|
52
|
+
Add these lines to your application's Gemfile:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
group :test do
|
56
|
+
gem 'state_machines_rspec'
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
(Note that the orginal `state_machine_rspec` used `state_machine` singular and I'm using `state_machines` plural to fit in with `state-machines/state_machines`.)
|
61
|
+
|
62
|
+
And include the matchers in `spec/spec_helper.rb` or `spec/rails_helper.rb`
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
RSpec.configure do |config|
|
66
|
+
config.include StateMachinesRspec::Matchers
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
## Contributing
|
71
|
+
|
72
|
+
1. Fork it
|
73
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
74
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
75
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
76
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'matchers/events/matcher'
|
2
|
+
|
3
|
+
module StateMachinesRspec
|
4
|
+
module Matchers
|
5
|
+
def handle_events(value, *values)
|
6
|
+
HandleEventMatcher.new(values.unshift(value))
|
7
|
+
end
|
8
|
+
alias_method :handle_event, :handle_events
|
9
|
+
|
10
|
+
class HandleEventMatcher < StateMachinesRspec::Matchers::Events::Matcher
|
11
|
+
def matches_events?(events)
|
12
|
+
!invalid_events?
|
13
|
+
end
|
14
|
+
|
15
|
+
def description
|
16
|
+
message = super
|
17
|
+
message << " on #{state_machine_scope.inspect}" if state_machine_scope
|
18
|
+
"handle #{message}"
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def invalid_events?
|
24
|
+
invalid_events = @introspector.invalid_events(@events)
|
25
|
+
unless invalid_events.empty?
|
26
|
+
@failure_message = "Expected to be able to handle events: " +
|
27
|
+
"#{invalid_events.join(', ')} in state: " +
|
28
|
+
"#{@introspector.current_state_value}"
|
29
|
+
end
|
30
|
+
|
31
|
+
!invalid_events.empty?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'active_support/core_ext/array/extract_options'
|
2
|
+
|
3
|
+
module StateMachinesRspec
|
4
|
+
module Matchers
|
5
|
+
module Events
|
6
|
+
class Matcher
|
7
|
+
attr_reader :failure_message
|
8
|
+
|
9
|
+
def initialize(events)
|
10
|
+
@options = events.extract_options!
|
11
|
+
@events = events
|
12
|
+
end
|
13
|
+
|
14
|
+
def matches?(subject)
|
15
|
+
@subject = subject
|
16
|
+
@introspector = StateMachinesIntrospector.new(@subject,
|
17
|
+
@options.fetch(:on, nil))
|
18
|
+
enter_when_state
|
19
|
+
return false if undefined_events?
|
20
|
+
return false unless matches_events?(@events)
|
21
|
+
@failure_message.nil?
|
22
|
+
end
|
23
|
+
|
24
|
+
def matches_events?(events)
|
25
|
+
raise NotImplementedError,
|
26
|
+
"subclasses of #{self.class} must override matches_events?"
|
27
|
+
end
|
28
|
+
|
29
|
+
def description
|
30
|
+
message = @events.map{ |event| event.inspect }.join(', ')
|
31
|
+
message << " when #{state_name.inspect}" if state_name
|
32
|
+
message
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def state_machine_scope
|
38
|
+
@options.fetch(:on, nil)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def enter_when_state
|
44
|
+
if state_name
|
45
|
+
unless when_state = @introspector.state(state_name)
|
46
|
+
raise StateMachinesIntrospectorError,
|
47
|
+
"#{@subject.class} does not define state: #{state_name}"
|
48
|
+
end
|
49
|
+
|
50
|
+
@subject.send("#{@introspector.state_machine_attribute}=",
|
51
|
+
when_state.value)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def state_name
|
56
|
+
@options.fetch(:when, nil)
|
57
|
+
end
|
58
|
+
|
59
|
+
def undefined_events?
|
60
|
+
undefined_events = @introspector.undefined_events(@events)
|
61
|
+
unless undefined_events.empty?
|
62
|
+
@failure_message = "state_machine: #{@introspector.state_machine_attribute} " +
|
63
|
+
"does not define events: #{undefined_events.join(', ')}"
|
64
|
+
end
|
65
|
+
|
66
|
+
!undefined_events.empty?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'matchers/events/matcher'
|
2
|
+
|
3
|
+
module StateMachinesRspec
|
4
|
+
module Matchers
|
5
|
+
def reject_events(value, *values)
|
6
|
+
RejectEventMatcher.new(values.unshift(value))
|
7
|
+
end
|
8
|
+
alias_method :reject_event, :reject_events
|
9
|
+
|
10
|
+
class RejectEventMatcher < StateMachinesRspec::Matchers::Events::Matcher
|
11
|
+
def matches_events?(events)
|
12
|
+
!valid_events?
|
13
|
+
end
|
14
|
+
|
15
|
+
def description
|
16
|
+
message = super
|
17
|
+
message << " on #{state_machine_scope.inspect}" if state_machine_scope
|
18
|
+
"reject #{message}"
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def valid_events?
|
24
|
+
valid_events = @introspector.valid_events(@events)
|
25
|
+
unless valid_events.empty?
|
26
|
+
@failure_message = "Did not expect to be able to handle events: " +
|
27
|
+
"#{valid_events.join(', ')} in state: " +
|
28
|
+
"#{@introspector.current_state_value}"
|
29
|
+
end
|
30
|
+
|
31
|
+
!valid_events.empty?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'matchers/states/matcher'
|
2
|
+
|
3
|
+
module StateMachinesRspec
|
4
|
+
module Matchers
|
5
|
+
def have_states(state, *states)
|
6
|
+
HaveStateMatcher.new(states.unshift(state))
|
7
|
+
end
|
8
|
+
alias_method :have_state, :have_states
|
9
|
+
|
10
|
+
class HaveStateMatcher < StateMachinesRspec::Matchers::States::Matcher
|
11
|
+
def matches_states?(states)
|
12
|
+
return false if undefined_states?
|
13
|
+
return false if incorrect_value?
|
14
|
+
@failure_message.nil?
|
15
|
+
end
|
16
|
+
|
17
|
+
def description
|
18
|
+
message = super
|
19
|
+
message << " == #{state_value.inspect}" if state_value
|
20
|
+
message << " on #{state_machine_scope.inspect}" if state_machine_scope
|
21
|
+
"have #{message}"
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def undefined_states?
|
27
|
+
undefined_states = @introspector.undefined_states(@states)
|
28
|
+
unless undefined_states.empty?
|
29
|
+
@failure_message = "Expected #{@introspector.state_machine_attribute} " +
|
30
|
+
"to allow states: #{undefined_states.join(', ')}"
|
31
|
+
end
|
32
|
+
|
33
|
+
!undefined_states.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
def incorrect_value?
|
37
|
+
if state_value && @introspector.state(@states.first).value != state_value
|
38
|
+
@failure_message = "Expected #{@states.first} to have value #{state_value}"
|
39
|
+
true
|
40
|
+
end
|
41
|
+
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'active_support/core_ext/array/extract_options'
|
2
|
+
|
3
|
+
module StateMachinesRspec
|
4
|
+
module Matchers
|
5
|
+
module States
|
6
|
+
class Matcher
|
7
|
+
attr_reader :failure_message
|
8
|
+
|
9
|
+
def initialize(states)
|
10
|
+
@options = states.extract_options!
|
11
|
+
@states = states
|
12
|
+
end
|
13
|
+
|
14
|
+
def description
|
15
|
+
@states.map{ |event| event.inspect }.join(', ')
|
16
|
+
end
|
17
|
+
|
18
|
+
def matches?(subject)
|
19
|
+
raise_if_multiple_values
|
20
|
+
|
21
|
+
@subject = subject
|
22
|
+
@introspector = StateMachinesIntrospector.new(@subject,
|
23
|
+
state_machine_scope)
|
24
|
+
|
25
|
+
return false unless matches_states?(@states)
|
26
|
+
@failure_message.nil?
|
27
|
+
end
|
28
|
+
|
29
|
+
def matches_states?(states)
|
30
|
+
raise NotImplementedError,
|
31
|
+
"subclasses of #{self.class} must override matches_states?"
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def state_machine_scope
|
37
|
+
@options.fetch(:on, nil)
|
38
|
+
end
|
39
|
+
|
40
|
+
def state_value
|
41
|
+
@options.fetch(:value, nil)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def raise_if_multiple_values
|
47
|
+
if @states.count > 1 && state_value
|
48
|
+
raise ArgumentError, 'cannot make value assertions on ' +
|
49
|
+
'multiple states at once'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|