state_machines_rspec 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +21 -0
- data/Gemfile +8 -0
- data/Guardfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +62 -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/state_machines_rspec.rb +11 -0
- data/lib/state_machines_rspec/state_machines_introspector.rb +70 -0
- data/lib/state_machines_rspec/version.rb +3 -0
- data/spec/integration/integration_spec.rb +327 -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/spec_helper.rb +12 -0
- data/state_machines_rspec.gemspec +33 -0
- metadata +233 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7bc32e2082329c18f5e6cd5a9f31a144ff030365
|
4
|
+
data.tar.gz: dd57f80ca2701a0a84e26f9ca513521f1300e45b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 648b785d5d365c292681bad98c02c6ae46cfb9dd052089a6e6f6e75d573c57ef43bb724e16a1772a52dfcfb9187ad1e85e9bfac4b181b48a61f2f884c902be38
|
7
|
+
data.tar.gz: bb21728cca9bd477710f8fcb6a7bf6f6be82b9422ba17f22fa53588fd2e869247751dc3aff4ad2f53a73fc277a0346e8357181827ba0f1c5259f1bcffb22511e
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
state_machines_rspec
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.1.2
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
## [0.1.3](https://github.com/modocache/state_machine_rspec/compare/v0.1.2...v0.1.3)
|
2
|
+
|
3
|
+
- Add `#description` to matchers.
|
4
|
+
- Update rspec dependency.
|
5
|
+
|
6
|
+
## [0.1.2](https://github.com/modocache/state_machine_rspec/compare/v0.1.1...v0.1.2)
|
7
|
+
|
8
|
+
- state_machine dependency updated from "~> 1.1.0" to ">= 1.1.0".
|
9
|
+
Fixes https://github.com/modocache/state_machine_rspec/issues/5.
|
10
|
+
|
11
|
+
## [0.1.1](https://github.com/modocache/state_machine_rspec/compare/v0.1.0...v0.1.1)
|
12
|
+
|
13
|
+
- `StateMachineRspec::Matchers::Events` conform to `Matchers::States` API, and now
|
14
|
+
take a `:on` parameter when specifying a non-default state_machine attribute. This
|
15
|
+
used to be specified with a `:state` parameter.
|
16
|
+
- Add Travis-CI build status image to README.
|
17
|
+
- Update README with instructions on including module in during RSpec configuration.
|
18
|
+
|
19
|
+
## [0.1.0](https://github.com/modocache/state_machine_rspec/tree/v0.1.0)
|
20
|
+
|
21
|
+
- 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,62 @@
|
|
1
|
+
# state_machine_rspec
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/modocache/state_machine_rspec.png?branch=master)](https://travis-ci.org/modocache/state_machine_rspec)
|
4
|
+
|
5
|
+
Custom matchers for [pluginaweek/state_machine](https://github.com/pluginaweek/state_machine).
|
6
|
+
|
7
|
+
|
8
|
+
## Matchers
|
9
|
+
|
10
|
+
### `have_state` & `reject_state`
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
describe Vehicle do
|
14
|
+
it { should have_states :parked, :idling, :stalled, :first_gear,
|
15
|
+
:second_gear, :third_gear }
|
16
|
+
it { should reject_state :flying }
|
17
|
+
|
18
|
+
it { should have_states :active, :off, on: :alarm_state }
|
19
|
+
it { should have_state :active, on: :alarm_state, value: 1 }
|
20
|
+
it { should 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 { should handle_events :shift_down, :crash, when: :third_gear }
|
29
|
+
it { should handle_events :enable_alarm, :disable_alarm,
|
30
|
+
when: :active, on: :alarm_state }
|
31
|
+
it { should reject_events :park, :ignite, :idle, :shift_up, :repair,
|
32
|
+
when: :third_gear }
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
|
37
|
+
## Installation
|
38
|
+
|
39
|
+
Add these lines to your application's Gemfile:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
group :test do
|
43
|
+
gem 'state_machine_rspec'
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
And include the matchers in `spec/spec_helper.rb`:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
RSpec.configure do |config|
|
51
|
+
config.include StateMachineRspec::Matchers
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
|
56
|
+
## Contributing
|
57
|
+
|
58
|
+
1. Fork it
|
59
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
60
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
61
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
62
|
+
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
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'matchers/states/matcher'
|
2
|
+
|
3
|
+
module StateMachinesRspec
|
4
|
+
module Matchers
|
5
|
+
def reject_states(state, *states)
|
6
|
+
RejectStateMatcher.new(states.unshift(state))
|
7
|
+
end
|
8
|
+
alias_method :reject_state, :reject_states
|
9
|
+
|
10
|
+
class RejectStateMatcher < StateMachinesRspec::Matchers::States::Matcher
|
11
|
+
def matches_states?(states)
|
12
|
+
no_defined_states?
|
13
|
+
end
|
14
|
+
|
15
|
+
def description
|
16
|
+
message = super
|
17
|
+
message << " on #{state_machine_scope.inspect}" if state_machine_scope
|
18
|
+
"not have #{message}"
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def no_defined_states?
|
24
|
+
defined_states = @introspector.defined_states(@states)
|
25
|
+
unless defined_states.empty?
|
26
|
+
@failure_message = "Did not expect #{@introspector.state_machine_attribute} " +
|
27
|
+
"to allow states: #{defined_states.join(', ')}"
|
28
|
+
end
|
29
|
+
|
30
|
+
defined_states.empty?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|